From 6742064aef7f1fba8e68d30b2e726918a5d66790 Mon Sep 17 00:00:00 2001 From: Piotr Stankiewicz Date: Fri, 13 May 2016 17:03:55 +0100 Subject: ASoC: dapm: support user-defined stop condition in dai_get_connected_widgets Certain situations may warrant examining DAPM paths only to a certain arbitrary point, as opposed to always following them to the end. For instance, when establishing a connection between a front-end DAI link and a back-end DAI link in a DPCM path, it does not make sense to walk the DAPM graph beyond the first widget associated with a back-end link. This patch introduces a mechanism which lets a user of dai_get_connected_widgets supply a function which will be called for every node during the graph walk. When invoked, this function can execute arbitrary logic to decide whether the walk, given a DAPM widget and walk direction, should be terminated at that point or continued as normal. Signed-off-by: Piotr Stankiewicz Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 58 ++++++++++++++++++++++++++++++++++++++++------------ sound/soc/soc-pcm.c | 3 ++- 2 files changed, 47 insertions(+), 14 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index c4464858bf01..db781f6faaec 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,9 @@ 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)) + return con; + 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 +1113,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 +1126,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 +1164,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 +1201,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 +1300,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 +1960,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..c2b0aa82f3f1 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1294,7 +1294,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, + NULL); dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, stream ? "capture" : "playback"); -- cgit v1.2.3-59-g8ed1b From 5fdd022c20264791310b188ec4a080bcb8647d23 Mon Sep 17 00:00:00 2001 From: Piotr Stankiewicz Date: Fri, 13 May 2016 17:03:56 +0100 Subject: ASoC: dpcm: play nice with CODEC<->CODEC links Currently in situations where a normal CODEC to CODEC link follows a DPCM DAI, an error in the following form will be logged: ASoC: can't get [playback|capture] BE for ASoC: no BE found for This happens because all widgets in a path containing a DPCM DAI will be passed to dpcm_add_paths, which will try to interpret the CODEC<->CODEC as if it were a DPCM DAI, in turn causing the error. This patch aims to resolve the described issue by stopping the DPCM graph walk, initiated from dpcm_path_get, at the first widget associated with a DPCM BE. Signed-off-by: Piotr Stankiewicz Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index c2b0aa82f3f1..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) { @@ -1295,7 +1335,7 @@ int dpcm_path_get(struct snd_soc_pcm_runtime *fe, /* get number of valid DAI paths and their widgets */ paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list, - NULL); + dpcm_end_walk_at_be); dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, stream ? "capture" : "playback"); -- cgit v1.2.3-59-g8ed1b From b1d32feb9a1c0d26d1749519d598b676bc7b5d80 Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Mon, 23 May 2016 11:02:22 +0100 Subject: ASoC: dwc: Add helper functions to disable/enable irqs Helper functions to disable and enable the I2S interrupts were added. Only the interrupts of the used channels are enabled. Also, there is no need to enable irqs at dw_i2s_config(), they are already enabled at startup. Signed-off-by: Jose Abreu Cc: Carlos Palminha Cc: Mark Brown Cc: Liam Girdwood Cc: Jaroslav Kysela Cc: Takashi Iwai Cc: Rob Herring Cc: Alexey Brodkin Cc: linux-snps-arc@lists.infradead.org Cc: alsa-devel@alsa-project.org Cc: devicetree@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Mark Brown --- sound/soc/dwc/designware_i2s.c | 68 +++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 27 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index 0db69b7e9617..4c4f0dc24f10 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -145,26 +145,54 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream) } } -static void i2s_start(struct dw_i2s_dev *dev, - struct snd_pcm_substream *substream) +static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream, + int chan_nr) { - 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++) { + 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); } - i2s_write_reg(dev->i2s_base, ITER, 1); } else { - for (i = 0; i < (config->chan_nr / 2); i++) { + 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); } - i2s_write_reg(dev->i2s_base, IRER, 1); } +} + +static void i2s_start(struct dw_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + struct i2s_clk_config_data *config = &dev->config; + + 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 + i2s_write_reg(dev->i2s_base, IRER, 1); i2s_write_reg(dev->i2s_base, CER, 1); } @@ -172,24 +200,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 +241,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 +253,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); } -- cgit v1.2.3-59-g8ed1b From 121a01521b1ef7440ea285aa3aae02bf005e5635 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Thu, 2 Jun 2016 18:29:24 +0530 Subject: ASoC: fsl: fix build failure m32r allmodconfig build is failing with the error: ERROR: "bad_dma_ops" [sound/soc/fsl/snd-soc-fsl-asrc.ko] undefined! The code is using DMA but the related dependency is not mentioned in the Kconfig. Signed-off-by: Sudip Mukherjee Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') 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 -- cgit v1.2.3-59-g8ed1b From 79361b2b98b7b64bcf71e0aa4e4dac497bcb94bc Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Thu, 9 Jun 2016 12:47:05 +0100 Subject: ASoC: dwc: Add PIO PCM extension A PCM extension was added to I2S driver so that audio samples are transferred using PIO mode. The PCM supports two channels @ 16 or 32 bits with rates 32k, 44.1k and 48k. Although the mainline I2S driver uses ALSA DMA engine the I2S controller can be built without DMA support, therefore this is the reason why this extension was added. Signed-off-by: Jose Abreu Cc: Carlos Palminha Cc: Mark Brown Cc: Liam Girdwood Cc: Jaroslav Kysela Cc: Takashi Iwai Cc: Rob Herring Cc: Alexey Brodkin Cc: linux-snps-arc@lists.infradead.org Cc: alsa-devel@alsa-project.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Mark Brown --- sound/soc/dwc/Kconfig | 9 ++ sound/soc/dwc/Makefile | 1 + sound/soc/dwc/designware_i2s.c | 161 +++++++++++++---------------- sound/soc/dwc/designware_pcm.c | 225 +++++++++++++++++++++++++++++++++++++++++ sound/soc/dwc/local.h | 128 +++++++++++++++++++++++ 5 files changed, 436 insertions(+), 88 deletions(-) create mode 100644 sound/soc/dwc/designware_pcm.c create mode 100644 sound/soc/dwc/local.h (limited to 'sound/soc') 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..1b48bcccbc51 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -1,3 +1,4 @@ # SYNOPSYS Platform Support obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o +obj-$(CONFIG_SND_DESIGNWARE_PCM) += designware_pcm.o diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index 4c4f0dc24f10..591854e97190 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) { @@ -181,6 +98,52 @@ static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream, } } +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) { @@ -640,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; @@ -665,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) { @@ -711,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, + "failed to register PCM, deferring probe\n"); + return ret; + } else if (ret) { dev_err(&pdev->dev, - "Could not register PCM: %d\n", ret); - goto err_clk_disable; + "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 -- cgit v1.2.3-59-g8ed1b From 09464974eaa8325c4cd22c3cab743a110644fb31 Mon Sep 17 00:00:00 2001 From: Jeeja KP Date: Wed, 15 Jun 2016 11:16:55 +0530 Subject: ASoC: dapm: Fix to return correct path list in is_connected_ep. In is_connected_ep, when custom_stop_condition is true, need to return the correct paths instead of con which is 0. Fixes: 6742064aef7f('ASoC: dapm: support user-defined stop condition in dai_get_connected_widgets') Signed-off-by: Jeeja KP Signed-off-by: Vinod Koul Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index db781f6faaec..3c3f027d21bd 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1092,8 +1092,10 @@ 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)) - return con; + 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); -- cgit v1.2.3-59-g8ed1b From 3276d0aa0bf475cc6cfb505487b2a2f3f762aebb Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 20 Jun 2016 18:01:19 +0200 Subject: ASoC: dwc: make pcm support built-in when necessary The new PIO mode for the dwc audio driver causes a link failure when it is built as a loadable module but the audio driver is built-in: sound/built-in.o: In function `i2s_irq_handler': :(.text+0x58c64): undefined reference to `dw_pcm_push_tx' sound/built-in.o: In function `dw_i2s_probe': :(.text+0x593dc): undefined reference to `dw_pcm_register' We could link both into a single module, but apparently the author intended them to be separate, so this instead changes the Makefile to force the pcm module to be built-in if the base module is. This is a bit hacky but not as bad as trying to work around it in Kconfig language. Signed-off-by: Arnd Bergmann Fixes: 79361b2b98b7 ("ASoC: dwc: Add PIO PCM extension") Signed-off-by: Mark Brown --- sound/soc/dwc/Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile index 1b48bcccbc51..38f1ca31c5fa 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -1,4 +1,5 @@ # SYNOPSYS Platform Support obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o -obj-$(CONFIG_SND_DESIGNWARE_PCM) += designware_pcm.o - +ifdef CONFIG_SND_DESIGNWARE_PCM +obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o +endif -- cgit v1.2.3-59-g8ed1b From 57072ae122178416f84304fa54c4d0204c6cec1a Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 24 Jun 2016 18:14:53 +0100 Subject: SoC: dwc: trivial fix of spelling mistake "unsuppted" -> "unsupported" trivial fix to spelling mistake in dev_err message Signed-off-by: Colin Ian King Signed-off-by: Mark Brown --- sound/soc/dwc/designware_i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index 591854e97190..dc97f4349e66 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -255,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; } -- cgit v1.2.3-59-g8ed1b