diff --git a/Documentation/devicetree/bindings/sound/designware-i2s.txt b/Documentation/devicetree/bindings/sound/designware-i2s.txt index 7bb54247f8e8..6a536d570e29 100644 --- a/Documentation/devicetree/bindings/sound/designware-i2s.txt +++ b/Documentation/devicetree/bindings/sound/designware-i2s.txt @@ -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 diff --git a/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt b/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt index ceaef5126989..f749e2744824 100644 --- a/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt +++ b/Documentation/devicetree/bindings/sound/fsl-asoc-card.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: diff --git a/Documentation/sound/alsa/soc/machine.txt b/Documentation/sound/alsa/soc/machine.txt index 74056dba52be..6bf2d2063b52 100644 --- a/Documentation/sound/alsa/soc/machine.txt +++ b/Documentation/sound/alsa/soc/machine.txt @@ -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 diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 0efeb38ae059..f60d755f7ac6 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -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); diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig index d50e08517dce..c297efe43861 100644 --- a/sound/soc/dwc/Kconfig +++ b/sound/soc/dwc/Kconfig @@ -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. diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile index 319371f690f4..38f1ca31c5fa 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -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 diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index 0db69b7e9617..dc97f4349e66 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -24,90 +24,7 @@ #include #include #include - -/* 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; diff --git a/sound/soc/dwc/designware_pcm.c b/sound/soc/dwc/designware_pcm.c new file mode 100644 index 000000000000..4a83a22fa3cb --- /dev/null +++ b/sound/soc/dwc/designware_pcm.c @@ -0,0 +1,225 @@ +/* + * ALSA SoC Synopsys PIO PCM for I2S driver + * + * sound/soc/dwc/designware_pcm.c + * + * Copyright (C) 2016 Synopsys + * Jose Abreu + * + * 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 +#include +#include +#include +#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); diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h new file mode 100644 index 000000000000..68afd7577343 --- /dev/null +++ b/sound/soc/dwc/local.h @@ -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 +#include +#include +#include +#include +#include + +/* 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 diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 35aabf9dc503..19bdcac71775 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -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 diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index cc8f480251e7..8698c26773b3 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -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", diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index aa99dac31b3b..60d702f8b9f0 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -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");