mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 06:31:49 +00:00
Merge remote-tracking branches 'asoc/topic/dpcm', 'asoc/topic/dt', 'asoc/topic/dwc' and 'asoc/topic/fsl' into asoc-next
This commit is contained in:
commit
dd0111dcd6
@ -12,6 +12,10 @@ Required properties:
|
||||
one for receive.
|
||||
- dma-names : "tx" for the transmit channel, "rx" for the receive channel.
|
||||
|
||||
Optional properties:
|
||||
- interrupts: The interrupt line number for the I2S controller. Add this
|
||||
parameter if the I2S controller that you are using does not support DMA.
|
||||
|
||||
For more details on the 'dma', 'dma-names', 'clock' and 'clock-names'
|
||||
properties please check:
|
||||
* resource-names.txt
|
||||
|
@ -58,7 +58,7 @@ Required properties:
|
||||
* DMIC (stands for Digital Microphone Jack)
|
||||
|
||||
Note: The "Mic Jack" and "AMIC" are redundant while
|
||||
coexsiting in order to support the old bindings
|
||||
coexisting in order to support the old bindings
|
||||
of wm8962 and sgtl5000.
|
||||
|
||||
Optional properties:
|
||||
|
@ -3,7 +3,7 @@ ASoC Machine Driver
|
||||
|
||||
The ASoC machine (or board) driver is the code that glues together all the
|
||||
component drivers (e.g. codecs, platforms and DAIs). It also describes the
|
||||
relationships between each componnent which include audio paths, GPIOs,
|
||||
relationships between each component which include audio paths, GPIOs,
|
||||
interrupts, clocking, jacks and voltage regulators.
|
||||
|
||||
The machine driver can contain codec and platform specific code. It registers
|
||||
|
@ -358,6 +358,7 @@ struct snd_soc_dapm_context;
|
||||
struct regulator;
|
||||
struct snd_soc_dapm_widget_list;
|
||||
struct snd_soc_dapm_update;
|
||||
enum snd_soc_dapm_direction;
|
||||
|
||||
int dapm_regulator_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event);
|
||||
@ -454,7 +455,9 @@ void dapm_mark_endpoints_dirty(struct snd_soc_card *card);
|
||||
|
||||
/* dapm path query */
|
||||
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
||||
struct snd_soc_dapm_widget_list **list);
|
||||
struct snd_soc_dapm_widget_list **list,
|
||||
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
|
||||
enum snd_soc_dapm_direction));
|
||||
|
||||
struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm(
|
||||
struct snd_kcontrol *kcontrol);
|
||||
|
@ -7,4 +7,13 @@ config SND_DESIGNWARE_I2S
|
||||
Synopsys desigwnware I2S device. The device supports upto
|
||||
maximum of 8 channels each for play and record.
|
||||
|
||||
config SND_DESIGNWARE_PCM
|
||||
tristate "PCM PIO extension for I2S driver"
|
||||
depends on SND_DESIGNWARE_I2S
|
||||
help
|
||||
Say Y, M or N if you want to add a custom ALSA extension that registers
|
||||
a PCM and uses PIO to transfer data.
|
||||
|
||||
This functionality is specially suited for I2S devices that don't have
|
||||
DMA support.
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
# SYNOPSYS Platform Support
|
||||
obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o
|
||||
|
||||
ifdef CONFIG_SND_DESIGNWARE_PCM
|
||||
obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o
|
||||
endif
|
||||
|
@ -24,90 +24,7 @@
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
/* common register for all channel */
|
||||
#define IER 0x000
|
||||
#define IRER 0x004
|
||||
#define ITER 0x008
|
||||
#define CER 0x00C
|
||||
#define CCR 0x010
|
||||
#define RXFFR 0x014
|
||||
#define TXFFR 0x018
|
||||
|
||||
/* I2STxRxRegisters for all channels */
|
||||
#define LRBR_LTHR(x) (0x40 * x + 0x020)
|
||||
#define RRBR_RTHR(x) (0x40 * x + 0x024)
|
||||
#define RER(x) (0x40 * x + 0x028)
|
||||
#define TER(x) (0x40 * x + 0x02C)
|
||||
#define RCR(x) (0x40 * x + 0x030)
|
||||
#define TCR(x) (0x40 * x + 0x034)
|
||||
#define ISR(x) (0x40 * x + 0x038)
|
||||
#define IMR(x) (0x40 * x + 0x03C)
|
||||
#define ROR(x) (0x40 * x + 0x040)
|
||||
#define TOR(x) (0x40 * x + 0x044)
|
||||
#define RFCR(x) (0x40 * x + 0x048)
|
||||
#define TFCR(x) (0x40 * x + 0x04C)
|
||||
#define RFF(x) (0x40 * x + 0x050)
|
||||
#define TFF(x) (0x40 * x + 0x054)
|
||||
|
||||
/* I2SCOMPRegisters */
|
||||
#define I2S_COMP_PARAM_2 0x01F0
|
||||
#define I2S_COMP_PARAM_1 0x01F4
|
||||
#define I2S_COMP_VERSION 0x01F8
|
||||
#define I2S_COMP_TYPE 0x01FC
|
||||
|
||||
/*
|
||||
* Component parameter register fields - define the I2S block's
|
||||
* configuration.
|
||||
*/
|
||||
#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25)
|
||||
#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22)
|
||||
#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19)
|
||||
#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16)
|
||||
#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9)
|
||||
#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7)
|
||||
#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6)
|
||||
#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5)
|
||||
#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4)
|
||||
#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2)
|
||||
#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0)
|
||||
|
||||
#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10)
|
||||
#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7)
|
||||
#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3)
|
||||
#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0)
|
||||
|
||||
/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
|
||||
#define COMP_MAX_WORDSIZE (1 << 3)
|
||||
#define COMP_MAX_DATA_WIDTH (1 << 2)
|
||||
|
||||
#define MAX_CHANNEL_NUM 8
|
||||
#define MIN_CHANNEL_NUM 2
|
||||
|
||||
union dw_i2s_snd_dma_data {
|
||||
struct i2s_dma_data pd;
|
||||
struct snd_dmaengine_dai_dma_data dt;
|
||||
};
|
||||
|
||||
struct dw_i2s_dev {
|
||||
void __iomem *i2s_base;
|
||||
struct clk *clk;
|
||||
int active;
|
||||
unsigned int capability;
|
||||
unsigned int quirks;
|
||||
unsigned int i2s_reg_comp1;
|
||||
unsigned int i2s_reg_comp2;
|
||||
struct device *dev;
|
||||
u32 ccr;
|
||||
u32 xfer_resolution;
|
||||
u32 fifo_th;
|
||||
|
||||
/* data related to DMA transfers b/w i2s and DMAC */
|
||||
union dw_i2s_snd_dma_data play_dma_data;
|
||||
union dw_i2s_snd_dma_data capture_dma_data;
|
||||
struct i2s_clk_config_data config;
|
||||
int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
|
||||
};
|
||||
#include "local.h"
|
||||
|
||||
static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
|
||||
{
|
||||
@ -145,26 +62,100 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
|
||||
}
|
||||
}
|
||||
|
||||
static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream,
|
||||
int chan_nr)
|
||||
{
|
||||
u32 i, irq;
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
for (i = 0; i < (chan_nr / 2); i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30);
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < (chan_nr / 2); i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream,
|
||||
int chan_nr)
|
||||
{
|
||||
u32 i, irq;
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
for (i = 0; i < (chan_nr / 2); i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < (chan_nr / 2); i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct dw_i2s_dev *dev = dev_id;
|
||||
bool irq_valid = false;
|
||||
u32 isr[4];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
|
||||
|
||||
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
|
||||
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
/*
|
||||
* Check if TX fifo is empty. If empty fill FIFO with samples
|
||||
* NOTE: Only two channels supported
|
||||
*/
|
||||
if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) {
|
||||
dw_pcm_push_tx(dev);
|
||||
irq_valid = true;
|
||||
}
|
||||
|
||||
/* Data available. Record mode not supported in PIO mode */
|
||||
if (isr[i] & ISR_RXDA)
|
||||
irq_valid = true;
|
||||
|
||||
/* Error Handling: TX */
|
||||
if (isr[i] & ISR_TXFO) {
|
||||
dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i);
|
||||
irq_valid = true;
|
||||
}
|
||||
|
||||
/* Error Handling: TX */
|
||||
if (isr[i] & ISR_RXFO) {
|
||||
dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i);
|
||||
irq_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (irq_valid)
|
||||
return IRQ_HANDLED;
|
||||
else
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static void i2s_start(struct dw_i2s_dev *dev,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct i2s_clk_config_data *config = &dev->config;
|
||||
u32 i, irq;
|
||||
i2s_write_reg(dev->i2s_base, IER, 1);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
for (i = 0; i < (config->chan_nr / 2); i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
|
||||
}
|
||||
i2s_write_reg(dev->i2s_base, IER, 1);
|
||||
i2s_enable_irqs(dev, substream->stream, config->chan_nr);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
i2s_write_reg(dev->i2s_base, ITER, 1);
|
||||
} else {
|
||||
for (i = 0; i < (config->chan_nr / 2); i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
|
||||
}
|
||||
else
|
||||
i2s_write_reg(dev->i2s_base, IRER, 1);
|
||||
}
|
||||
|
||||
i2s_write_reg(dev->i2s_base, CER, 1);
|
||||
}
|
||||
@ -172,24 +163,14 @@ static void i2s_start(struct dw_i2s_dev *dev,
|
||||
static void i2s_stop(struct dw_i2s_dev *dev,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
u32 i = 0, irq;
|
||||
|
||||
i2s_clear_irqs(dev, substream->stream);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
i2s_write_reg(dev->i2s_base, ITER, 0);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30);
|
||||
}
|
||||
} else {
|
||||
else
|
||||
i2s_write_reg(dev->i2s_base, IRER, 0);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(i));
|
||||
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03);
|
||||
}
|
||||
}
|
||||
i2s_disable_irqs(dev, substream->stream, 8);
|
||||
|
||||
if (!dev->active) {
|
||||
i2s_write_reg(dev->i2s_base, CER, 0);
|
||||
@ -223,7 +204,7 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream,
|
||||
|
||||
static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
|
||||
{
|
||||
u32 ch_reg, irq;
|
||||
u32 ch_reg;
|
||||
struct i2s_clk_config_data *config = &dev->config;
|
||||
|
||||
|
||||
@ -235,16 +216,12 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
|
||||
dev->xfer_resolution);
|
||||
i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
|
||||
dev->fifo_th - 1);
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
|
||||
i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30);
|
||||
i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
|
||||
} else {
|
||||
i2s_write_reg(dev->i2s_base, RCR(ch_reg),
|
||||
dev->xfer_resolution);
|
||||
i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
|
||||
dev->fifo_th - 1);
|
||||
irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
|
||||
i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03);
|
||||
i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
|
||||
}
|
||||
|
||||
@ -278,7 +255,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt");
|
||||
dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -626,7 +603,7 @@ static int dw_i2s_probe(struct platform_device *pdev)
|
||||
const struct i2s_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct dw_i2s_dev *dev;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
int ret, irq;
|
||||
struct snd_soc_dai_driver *dw_i2s_dai;
|
||||
const char *clk_id;
|
||||
|
||||
@ -651,6 +628,16 @@ static int dw_i2s_probe(struct platform_device *pdev)
|
||||
|
||||
dev->dev = &pdev->dev;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq >= 0) {
|
||||
ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0,
|
||||
pdev->name, dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to request irq\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
|
||||
dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
|
||||
if (pdata) {
|
||||
@ -697,12 +684,24 @@ static int dw_i2s_probe(struct platform_device *pdev)
|
||||
|
||||
if (!pdata) {
|
||||
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
|
||||
if (ret) {
|
||||
if (ret == -EPROBE_DEFER) {
|
||||
dev_err(&pdev->dev,
|
||||
"Could not register PCM: %d\n", ret);
|
||||
goto err_clk_disable;
|
||||
"failed to register PCM, deferring probe\n");
|
||||
return ret;
|
||||
} else if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"Could not register DMA PCM: %d\n"
|
||||
"falling back to PIO mode\n", ret);
|
||||
ret = dw_pcm_register(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"Could not register PIO PCM: %d\n",
|
||||
ret);
|
||||
goto err_clk_disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
return 0;
|
||||
|
||||
|
225
sound/soc/dwc/designware_pcm.c
Normal file
225
sound/soc/dwc/designware_pcm.c
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* ALSA SoC Synopsys PIO PCM for I2S driver
|
||||
*
|
||||
* sound/soc/dwc/designware_pcm.c
|
||||
*
|
||||
* Copyright (C) 2016 Synopsys
|
||||
* Jose Abreu <joabreu@synopsys.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include "local.h"
|
||||
|
||||
#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN)
|
||||
#define PERIOD_BYTES_MIN 4096
|
||||
#define PERIODS_MIN 2
|
||||
|
||||
#define dw_pcm_tx_fn(sample_bits) \
|
||||
static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \
|
||||
struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \
|
||||
bool *period_elapsed) \
|
||||
{ \
|
||||
const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \
|
||||
unsigned int period_pos = tx_ptr % runtime->period_size; \
|
||||
int i; \
|
||||
\
|
||||
for (i = 0; i < dev->fifo_th; i++) { \
|
||||
iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \
|
||||
iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \
|
||||
period_pos++; \
|
||||
if (++tx_ptr >= runtime->buffer_size) \
|
||||
tx_ptr = 0; \
|
||||
} \
|
||||
*period_elapsed = period_pos >= runtime->period_size; \
|
||||
return tx_ptr; \
|
||||
}
|
||||
|
||||
dw_pcm_tx_fn(16);
|
||||
dw_pcm_tx_fn(32);
|
||||
|
||||
#undef dw_pcm_tx_fn
|
||||
|
||||
static const struct snd_pcm_hardware dw_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER,
|
||||
.rates = SNDRV_PCM_RATE_32000 |
|
||||
SNDRV_PCM_RATE_44100 |
|
||||
SNDRV_PCM_RATE_48000,
|
||||
.rate_min = 32000,
|
||||
.rate_max = 48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = BUFFER_BYTES_MAX,
|
||||
.period_bytes_min = PERIOD_BYTES_MIN,
|
||||
.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
|
||||
.periods_min = PERIODS_MIN,
|
||||
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
|
||||
.fifo_size = 16,
|
||||
};
|
||||
|
||||
void dw_pcm_push_tx(struct dw_i2s_dev *dev)
|
||||
{
|
||||
struct snd_pcm_substream *tx_substream;
|
||||
bool tx_active, period_elapsed;
|
||||
|
||||
rcu_read_lock();
|
||||
tx_substream = rcu_dereference(dev->tx_substream);
|
||||
tx_active = tx_substream && snd_pcm_running(tx_substream);
|
||||
if (tx_active) {
|
||||
unsigned int tx_ptr = READ_ONCE(dev->tx_ptr);
|
||||
unsigned int new_tx_ptr = dev->tx_fn(dev, tx_substream->runtime,
|
||||
tx_ptr, &period_elapsed);
|
||||
cmpxchg(&dev->tx_ptr, tx_ptr, new_tx_ptr);
|
||||
|
||||
if (period_elapsed)
|
||||
snd_pcm_period_elapsed(tx_substream);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_pcm_push_tx);
|
||||
|
||||
static int dw_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware);
|
||||
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
runtime->private_data = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
synchronize_rcu();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct dw_i2s_dev *dev = runtime->private_data;
|
||||
int ret;
|
||||
|
||||
switch (params_channels(hw_params)) {
|
||||
case 2:
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "invalid channels number\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (params_format(hw_params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
dev->tx_fn = dw_pcm_tx_16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
dev->tx_fn = dw_pcm_tx_32;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "invalid format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
dev_err(dev->dev, "only playback is available\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct dw_i2s_dev *dev = runtime->private_data;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
WRITE_ONCE(dev->tx_ptr, 0);
|
||||
rcu_assign_pointer(dev->tx_substream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
rcu_assign_pointer(dev->tx_substream, NULL);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct dw_i2s_dev *dev = runtime->private_data;
|
||||
snd_pcm_uframes_t pos = READ_ONCE(dev->tx_ptr);
|
||||
|
||||
return pos < runtime->buffer_size ? pos : 0;
|
||||
}
|
||||
|
||||
static int dw_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
size_t size = dw_pcm_hardware.buffer_bytes_max;
|
||||
|
||||
return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
|
||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL), size, size);
|
||||
}
|
||||
|
||||
static void dw_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
static const struct snd_pcm_ops dw_pcm_ops = {
|
||||
.open = dw_pcm_open,
|
||||
.close = dw_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = dw_pcm_hw_params,
|
||||
.hw_free = dw_pcm_hw_free,
|
||||
.trigger = dw_pcm_trigger,
|
||||
.pointer = dw_pcm_pointer,
|
||||
};
|
||||
|
||||
static const struct snd_soc_platform_driver dw_pcm_platform = {
|
||||
.pcm_new = dw_pcm_new,
|
||||
.pcm_free = dw_pcm_free,
|
||||
.ops = &dw_pcm_ops,
|
||||
};
|
||||
|
||||
int dw_pcm_register(struct platform_device *pdev)
|
||||
{
|
||||
return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_pcm_register);
|
128
sound/soc/dwc/local.h
Normal file
128
sound/soc/dwc/local.h
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com)
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __DESIGNWARE_LOCAL_H
|
||||
#define __DESIGNWARE_LOCAL_H
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/designware_i2s.h>
|
||||
|
||||
/* common register for all channel */
|
||||
#define IER 0x000
|
||||
#define IRER 0x004
|
||||
#define ITER 0x008
|
||||
#define CER 0x00C
|
||||
#define CCR 0x010
|
||||
#define RXFFR 0x014
|
||||
#define TXFFR 0x018
|
||||
|
||||
/* Interrupt status register fields */
|
||||
#define ISR_TXFO BIT(5)
|
||||
#define ISR_TXFE BIT(4)
|
||||
#define ISR_RXFO BIT(1)
|
||||
#define ISR_RXDA BIT(0)
|
||||
|
||||
/* I2STxRxRegisters for all channels */
|
||||
#define LRBR_LTHR(x) (0x40 * x + 0x020)
|
||||
#define RRBR_RTHR(x) (0x40 * x + 0x024)
|
||||
#define RER(x) (0x40 * x + 0x028)
|
||||
#define TER(x) (0x40 * x + 0x02C)
|
||||
#define RCR(x) (0x40 * x + 0x030)
|
||||
#define TCR(x) (0x40 * x + 0x034)
|
||||
#define ISR(x) (0x40 * x + 0x038)
|
||||
#define IMR(x) (0x40 * x + 0x03C)
|
||||
#define ROR(x) (0x40 * x + 0x040)
|
||||
#define TOR(x) (0x40 * x + 0x044)
|
||||
#define RFCR(x) (0x40 * x + 0x048)
|
||||
#define TFCR(x) (0x40 * x + 0x04C)
|
||||
#define RFF(x) (0x40 * x + 0x050)
|
||||
#define TFF(x) (0x40 * x + 0x054)
|
||||
|
||||
/* I2SCOMPRegisters */
|
||||
#define I2S_COMP_PARAM_2 0x01F0
|
||||
#define I2S_COMP_PARAM_1 0x01F4
|
||||
#define I2S_COMP_VERSION 0x01F8
|
||||
#define I2S_COMP_TYPE 0x01FC
|
||||
|
||||
/*
|
||||
* Component parameter register fields - define the I2S block's
|
||||
* configuration.
|
||||
*/
|
||||
#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25)
|
||||
#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22)
|
||||
#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19)
|
||||
#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16)
|
||||
#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9)
|
||||
#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7)
|
||||
#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6)
|
||||
#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5)
|
||||
#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4)
|
||||
#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2)
|
||||
#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0)
|
||||
|
||||
#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10)
|
||||
#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7)
|
||||
#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3)
|
||||
#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0)
|
||||
|
||||
/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
|
||||
#define COMP_MAX_WORDSIZE (1 << 3)
|
||||
#define COMP_MAX_DATA_WIDTH (1 << 2)
|
||||
|
||||
#define MAX_CHANNEL_NUM 8
|
||||
#define MIN_CHANNEL_NUM 2
|
||||
|
||||
union dw_i2s_snd_dma_data {
|
||||
struct i2s_dma_data pd;
|
||||
struct snd_dmaengine_dai_dma_data dt;
|
||||
};
|
||||
|
||||
struct dw_i2s_dev {
|
||||
void __iomem *i2s_base;
|
||||
struct clk *clk;
|
||||
int active;
|
||||
unsigned int capability;
|
||||
unsigned int quirks;
|
||||
unsigned int i2s_reg_comp1;
|
||||
unsigned int i2s_reg_comp2;
|
||||
struct device *dev;
|
||||
u32 ccr;
|
||||
u32 xfer_resolution;
|
||||
u32 fifo_th;
|
||||
|
||||
/* data related to DMA transfers b/w i2s and DMAC */
|
||||
union dw_i2s_snd_dma_data play_dma_data;
|
||||
union dw_i2s_snd_dma_data capture_dma_data;
|
||||
struct i2s_clk_config_data config;
|
||||
int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
|
||||
|
||||
/* data related to PIO transfers (TX) */
|
||||
bool use_pio;
|
||||
struct snd_pcm_substream __rcu *tx_substream;
|
||||
unsigned int (*tx_fn)(struct dw_i2s_dev *dev,
|
||||
struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
|
||||
bool *period_elapsed);
|
||||
unsigned int tx_ptr;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_DESIGNWARE_PCM)
|
||||
void dw_pcm_push_tx(struct dw_i2s_dev *dev);
|
||||
int dw_pcm_register(struct platform_device *pdev);
|
||||
#else
|
||||
void dw_pcm_push_tx(struct dw_i2s_dev *dev) { }
|
||||
int dw_pcm_register(struct platform_device *pdev)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -4,6 +4,7 @@ comment "Common SoC Audio options for Freescale CPUs:"
|
||||
|
||||
config SND_SOC_FSL_ASRC
|
||||
tristate "Asynchronous Sample Rate Converter (ASRC) module support"
|
||||
depends on HAS_DMA
|
||||
select REGMAP_MMIO
|
||||
select SND_SOC_GENERIC_DMAENGINE_PCM
|
||||
help
|
||||
|
@ -1073,7 +1073,11 @@ static int dapm_widget_list_create(struct snd_soc_dapm_widget_list **list,
|
||||
*/
|
||||
static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
|
||||
struct list_head *list, enum snd_soc_dapm_direction dir,
|
||||
int (*fn)(struct snd_soc_dapm_widget *, struct list_head *))
|
||||
int (*fn)(struct snd_soc_dapm_widget *, struct list_head *,
|
||||
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
|
||||
enum snd_soc_dapm_direction)),
|
||||
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
|
||||
enum snd_soc_dapm_direction))
|
||||
{
|
||||
enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir);
|
||||
struct snd_soc_dapm_path *path;
|
||||
@ -1088,6 +1092,11 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
|
||||
if (list)
|
||||
list_add_tail(&widget->work_list, list);
|
||||
|
||||
if (custom_stop_condition && custom_stop_condition(widget, dir)) {
|
||||
widget->endpoints[dir] = 1;
|
||||
return widget->endpoints[dir];
|
||||
}
|
||||
|
||||
if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) {
|
||||
widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget);
|
||||
return widget->endpoints[dir];
|
||||
@ -1106,7 +1115,7 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
|
||||
|
||||
if (path->connect) {
|
||||
path->walking = 1;
|
||||
con += fn(path->node[dir], list);
|
||||
con += fn(path->node[dir], list, custom_stop_condition);
|
||||
path->walking = 0;
|
||||
}
|
||||
}
|
||||
@ -1119,23 +1128,37 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
|
||||
/*
|
||||
* Recursively check for a completed path to an active or physically connected
|
||||
* output widget. Returns number of complete paths.
|
||||
*
|
||||
* Optionally, can be supplied with a function acting as a stopping condition.
|
||||
* This function takes the dapm widget currently being examined and the walk
|
||||
* direction as an arguments, it should return true if the walk should be
|
||||
* stopped and false otherwise.
|
||||
*/
|
||||
static int is_connected_output_ep(struct snd_soc_dapm_widget *widget,
|
||||
struct list_head *list)
|
||||
struct list_head *list,
|
||||
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i,
|
||||
enum snd_soc_dapm_direction))
|
||||
{
|
||||
return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT,
|
||||
is_connected_output_ep);
|
||||
is_connected_output_ep, custom_stop_condition);
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursively check for a completed path to an active or physically connected
|
||||
* input widget. Returns number of complete paths.
|
||||
*
|
||||
* Optionally, can be supplied with a function acting as a stopping condition.
|
||||
* This function takes the dapm widget currently being examined and the walk
|
||||
* direction as an arguments, it should return true if the walk should be
|
||||
* stopped and false otherwise.
|
||||
*/
|
||||
static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
|
||||
struct list_head *list)
|
||||
struct list_head *list,
|
||||
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i,
|
||||
enum snd_soc_dapm_direction))
|
||||
{
|
||||
return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN,
|
||||
is_connected_input_ep);
|
||||
is_connected_input_ep, custom_stop_condition);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1143,15 +1166,24 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
|
||||
* @dai: the soc DAI.
|
||||
* @stream: stream direction.
|
||||
* @list: list of active widgets for this stream.
|
||||
* @custom_stop_condition: (optional) a function meant to stop the widget graph
|
||||
* walk based on custom logic.
|
||||
*
|
||||
* Queries DAPM graph as to whether an valid audio stream path exists for
|
||||
* the initial stream specified by name. This takes into account
|
||||
* current mixer and mux kcontrol settings. Creates list of valid widgets.
|
||||
*
|
||||
* Optionally, can be supplied with a function acting as a stopping condition.
|
||||
* This function takes the dapm widget currently being examined and the walk
|
||||
* direction as an arguments, it should return true if the walk should be
|
||||
* stopped and false otherwise.
|
||||
*
|
||||
* Returns the number of valid paths or negative error.
|
||||
*/
|
||||
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
||||
struct snd_soc_dapm_widget_list **list)
|
||||
struct snd_soc_dapm_widget_list **list,
|
||||
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
|
||||
enum snd_soc_dapm_direction))
|
||||
{
|
||||
struct snd_soc_card *card = dai->component->card;
|
||||
struct snd_soc_dapm_widget *w;
|
||||
@ -1171,9 +1203,11 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
||||
}
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
paths = is_connected_output_ep(dai->playback_widget, &widgets);
|
||||
paths = is_connected_output_ep(dai->playback_widget, &widgets,
|
||||
custom_stop_condition);
|
||||
else
|
||||
paths = is_connected_input_ep(dai->capture_widget, &widgets);
|
||||
paths = is_connected_input_ep(dai->capture_widget, &widgets,
|
||||
custom_stop_condition);
|
||||
|
||||
/* Drop starting point */
|
||||
list_del(widgets.next);
|
||||
@ -1268,8 +1302,8 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
|
||||
|
||||
DAPM_UPDATE_STAT(w, power_checks);
|
||||
|
||||
in = is_connected_input_ep(w, NULL);
|
||||
out = is_connected_output_ep(w, NULL);
|
||||
in = is_connected_input_ep(w, NULL, NULL);
|
||||
out = is_connected_output_ep(w, NULL, NULL);
|
||||
return out != 0 && in != 0;
|
||||
}
|
||||
|
||||
@ -1928,8 +1962,8 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
|
||||
in = 0;
|
||||
out = 0;
|
||||
} else {
|
||||
in = is_connected_input_ep(w, NULL);
|
||||
out = is_connected_output_ep(w, NULL);
|
||||
in = is_connected_input_ep(w, NULL, NULL);
|
||||
out = is_connected_output_ep(w, NULL, NULL);
|
||||
}
|
||||
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d",
|
||||
|
@ -1287,6 +1287,46 @@ static int widget_in_list(struct snd_soc_dapm_widget_list *list,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget,
|
||||
enum snd_soc_dapm_direction dir)
|
||||
{
|
||||
struct snd_soc_card *card = widget->dapm->card;
|
||||
struct snd_soc_pcm_runtime *rtd;
|
||||
int i;
|
||||
|
||||
if (dir == SND_SOC_DAPM_DIR_OUT) {
|
||||
list_for_each_entry(rtd, &card->rtd_list, list) {
|
||||
if (!rtd->dai_link->no_pcm)
|
||||
continue;
|
||||
|
||||
if (rtd->cpu_dai->playback_widget == widget)
|
||||
return true;
|
||||
|
||||
for (i = 0; i < rtd->num_codecs; ++i) {
|
||||
struct snd_soc_dai *dai = rtd->codec_dais[i];
|
||||
if (dai->playback_widget == widget)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else { /* SND_SOC_DAPM_DIR_IN */
|
||||
list_for_each_entry(rtd, &card->rtd_list, list) {
|
||||
if (!rtd->dai_link->no_pcm)
|
||||
continue;
|
||||
|
||||
if (rtd->cpu_dai->capture_widget == widget)
|
||||
return true;
|
||||
|
||||
for (i = 0; i < rtd->num_codecs; ++i) {
|
||||
struct snd_soc_dai *dai = rtd->codec_dais[i];
|
||||
if (dai->capture_widget == widget)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int dpcm_path_get(struct snd_soc_pcm_runtime *fe,
|
||||
int stream, struct snd_soc_dapm_widget_list **list)
|
||||
{
|
||||
@ -1294,7 +1334,8 @@ int dpcm_path_get(struct snd_soc_pcm_runtime *fe,
|
||||
int paths;
|
||||
|
||||
/* get number of valid DAI paths and their widgets */
|
||||
paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list);
|
||||
paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list,
|
||||
dpcm_end_walk_at_be);
|
||||
|
||||
dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths,
|
||||
stream ? "capture" : "playback");
|
||||
|
Loading…
Reference in New Issue
Block a user