[alsa-devel] [PATCH 0/9] ASoC: QCOM: Add support for ipq806x SOC
From: Kenneth Westfield kwestfie@codeaurora.org
This set of patches adds support for audio on the Qualcomm Technologies ipq806x SOC.
The ipq806x SOC has audio-related hardware blocks in its low-power audio subsystem (or LPASS). One of the relevant blocks in the LPASS is its low-power audio interface (or LPAIF). This encapsulates the MI2S port, which is what these drivers are configured to use. The I2S pins are connected to an external DAC/amp chip. In addition, a single GPIO is connected to the same DAC/amp, which gives the SOC enable/disable control.
The specific drivers added are: - a machine driver that handles the board-specific pins - a native driver that handles hardware access to the LPAIF - a CPU DAI driver for controlling the LPAIF block - a PCM MI2S platform driver
Corresponding additions to the device tree for the ipq806x and its documentation has also been added. Also, as this is a new directory, the MAINTAINERS file has been updated as well.
- Ken
Kenneth Westfield (9): MAINTAINERS: Add QCOM audio ASoC maintainer ASoC: qcom: Add device tree binding docs ASoC: ipq806x: add native LPAIF driver ASoC: ipq806x: Add LPASS CPU DAI driver ASoC: ipq806x: Add I2S PCM platform driver ASoC: ipq806x: Add machine driver for IPQ806X SOC ASoC: qcom: Add ability to build QCOM drivers ASoC: Allow for building QCOM drivers ARM: dts: Model IPQ LPASS audio hardware
.../bindings/sound/qcom,ipq806x-snd-card.txt | 41 ++ .../bindings/sound/qcom,lpass-cpu-dai.txt | 20 + .../devicetree/bindings/sound/qcom,lpass-lpaif.txt | 21 + .../bindings/sound/qcom,lpass-pcm-mi2s.txt | 12 + MAINTAINERS | 7 + arch/arm/boot/dts/qcom-ipq8064.dtsi | 33 ++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/qcom/Kconfig | 43 ++ sound/soc/qcom/Makefile | 11 + sound/soc/qcom/ipq806x.c | 221 ++++++++++ sound/soc/qcom/lpass-cpu-dai.c | 307 +++++++++++++ sound/soc/qcom/lpass-lpaif.c | 488 +++++++++++++++++++++ sound/soc/qcom/lpass-lpaif.h | 181 ++++++++ sound/soc/qcom/lpass-pcm-mi2s.c | 390 ++++++++++++++++ sound/soc/qcom/lpass-pcm-mi2s.h | 40 ++ 16 files changed, 1817 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,ipq806x-snd-card.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-cpu-dai.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-lpaif.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-pcm-mi2s.txt create mode 100644 sound/soc/qcom/Kconfig create mode 100644 sound/soc/qcom/Makefile create mode 100644 sound/soc/qcom/ipq806x.c create mode 100644 sound/soc/qcom/lpass-cpu-dai.c create mode 100644 sound/soc/qcom/lpass-lpaif.c create mode 100644 sound/soc/qcom/lpass-lpaif.h create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.c create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.h
From: Kenneth Westfield kwestfie@codeaurora.org
Change-Id: I3e33dcff96e85c7be790f9b84d7755858a52644e Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index e4f712785554a691f7f31dba57b600102b32554e..fe9b336a3b91e641abc8cbaab3fdcf6a2696026a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5093,6 +5093,13 @@ F: drivers/char/ipmi/ F: include/linux/ipmi* F: include/uapi/linux/ipmi*
+QCOM AUDIO (ASoC) DRIVERS +M: Patrick Lai plai@codeaurora.org +M: Banajit Goswami bgoswami@codeaurora.org +L: alsa-devel@alsa-project.org (moderated for non-subscribers) +S: Supported +F: sound/soc/qcom/ + IPS SCSI RAID DRIVER M: Adaptec OEM Raid Solutions aacraid@adaptec.com L: linux-scsi@vger.kernel.org
From: Kenneth Westfield kwestfie@codeaurora.org
Add documentation to the sound directory of the device-tree bindings for IPQ806x audio drivers.
Change-Id: I7dd08be2a68579675a6916ecb72b943dea0e352b Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- .../bindings/sound/qcom,ipq806x-snd-card.txt | 41 ++++++++++++++++++++++ .../bindings/sound/qcom,lpass-cpu-dai.txt | 20 +++++++++++ .../devicetree/bindings/sound/qcom,lpass-lpaif.txt | 21 +++++++++++ .../bindings/sound/qcom,lpass-pcm-mi2s.txt | 12 +++++++ 4 files changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,ipq806x-snd-card.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-cpu-dai.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-lpaif.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-pcm-mi2s.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,ipq806x-snd-card.txt b/Documentation/devicetree/bindings/sound/qcom,ipq806x-snd-card.txt new file mode 100644 index 0000000000000000000000000000000000000000..1187a7f37be3820bbd5bdf4f87ca6d37dcbe1454 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,ipq806x-snd-card.txt @@ -0,0 +1,41 @@ +* Qualcomm Technologies IPQ806x SoundCard + +This node models the Qualcomm Technologies IPQ806x LPASS Audio SoundCard, +with a connection between the CPU MI2S DAI and the external DAC. + +Required properties: +- compatible : "qcom,ipq806x-snd-card" +- qcom,model : The user-visible name of this sound card +- pinctrl-0 : The default state of the MI2S pins +- pinctrl-names : The name of the default state +- dac-gpios : GPIO specifier to the GPIO -> DAC SDMODE pin +- clocks : A list of clock specifiers in the following order: + * AHBIX bus clock +- clock-names : A list of names in the following order: + * ahbix_clk +asoc-platform : This is phandle list containing the references to platform device + nodes that are used as part of the sound card dai-links. +asoc-platform-names : This property contains list of platform names. The order of + the platform names should match to that of the phandle order + given in "asoc-platform". +asoc-cpu : This is phandle list containing the references to cpu dai device nodes + that are used as part of the sound card dai-links. +asoc-cpu-names : This property contains list of cpu dai names. The order of the + cpu dai names should match to that of the phandle order given + in "asoc-cpu". + +Example: + +sound { + compatible = "qcom,ipq806x-snd-card"; + model = "ipq806x-snd-card"; + pinctrl-0 = <&mi2s_pins>; + pinctrl-names = "default"; + dac-gpios = <&qcom_pinmux 25 0>; + clocks = <&lcc AHBIX_CLK>; + clock-names = "ahbix_clk"; + asoc-platform = <&pcm0>; + asoc-platform-names = "lpass-pcm-mi2s"; + asoc-cpu = <&dai_mi2s>; + asoc-cpu-names = "lpass-cpu-dai"; +}; diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu-dai.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu-dai.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b700c9727fef9969af810376071d2e390625fac --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu-dai.txt @@ -0,0 +1,20 @@ +* Qualcomm Technologies IPQ806x LPASS DAI + +This node models the Qualcomm Technologies IPQ806x LPASS I2S DAI port. + +Required properties: +- compatible : "qcom,lpass-cpu-dai" +- clocks : A list of clock specifiers in the following order: + * MI2S OSR clock + * MI2S Bit clock +- clock-names : A list of names in the following order: + * mi2s_osr_clk + * mi2s_bit_clk + +Example: + +lpass-cpu-dai { + compatible = "qcom,lpass-cpu-dai"; + clocks = <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>; + clock-names = "mi2s_osr_clk", "mi2s_bit_clk"; +}; diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-lpaif.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-lpaif.txt new file mode 100644 index 0000000000000000000000000000000000000000..d4feebb81aa9faebe2352a72e02a21dffc2ab331 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-lpaif.txt @@ -0,0 +1,21 @@ +* Qualcomm Technologies IPQ806x LPASS driver + +This node models the Qualcomm Technologies IPQ806x LPASS Audio driver, +which controls the various components within the subsystem. + +Required properties: +- compatible : "qcom,lpass-lpaif" +- reg : Address space for the LPASS subsystem registers +- reg-names : The name of the LPASS subsystem register address space +- interrupts : Phandle to the LPASS interrupt +- interrupt-names : The names of the LPASS interrupt + +Example: + +lpass-lpaif" + compatible = "qcom,lpass-lpaif"; + reg = <0x28100000 0x10000>; + reg-names = "lpass-lpaif-mem"; + interrupts = <0 85 1>; + interrupt-names = "lpass-lpaif-irq"; +}; diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-pcm-mi2s.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-pcm-mi2s.txt new file mode 100644 index 0000000000000000000000000000000000000000..d2ff501d44f7b7aa790cdadc8ba75c6a8bf37ccd --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-pcm-mi2s.txt @@ -0,0 +1,12 @@ +* Qualcomm Technologies IPQ806x PCM audio interface + +This node models the Qualcomm Technologies IPQ806x PCM audio interface. + +Required properties: +- compatible: "qcom,lpass-pcm-mi2s" + +Example: + +lpass-pcm-mi2s { + compatible = "qcom,lpass-pcm-mi2s"; +};
On Wed, Nov 19, 2014 at 10:52:42AM -0800, Kenneth Westfield wrote:
+* Qualcomm Technologies IPQ806x SoundCard
+This node models the Qualcomm Technologies IPQ806x LPASS Audio SoundCard, +with a connection between the CPU MI2S DAI and the external DAC.
+Required properties: +- compatible : "qcom,ipq806x-snd-card" +- qcom,model : The user-visible name of this sound card +- pinctrl-0 : The default state of the MI2S pins +- pinctrl-names : The name of the default state
Why is a sound card doing pin control? I would expect that the component devices would do their own pin control. Also if you have named pin control states the set of valid names should be specified.
+- dac-gpios : GPIO specifier to the GPIO -> DAC SDMODE pin
Simiarly why is a sound card controlling the DAC GPIOs, is this not part of the CODEC?
+- clocks : A list of clock specifiers in the following order:
* AHBIX bus clock
+- clock-names : A list of names in the following order:
* ahbix_clk
Again I'd really expect any devices on the AHB to be controlling the AHB related clocks rather than a sound card doing it.
+asoc-platform : This is phandle list containing the references to platform device
nodes that are used as part of the sound card dai-links.
+asoc-platform-names : This property contains list of platform names. The order of
the platform names should match to that of the phandle order
given in "asoc-platform".
The device tree bindings should be OS neutral but ASoC is a Linux thing. This needs to be written in terms of the hardware it's describing.
+Required properties: +- compatible : "qcom,lpass-cpu-dai" +- clocks : A list of clock specifiers in the following order:
* MI2S OSR clock
* MI2S Bit clock
+- clock-names : A list of names in the following order:
* mi2s_osr_clk
* mi2s_bit_clk
If there are names (which is good) why is the ordering important? The whole point in having a mandatory list of names is to remove the ordering and completeness requirements.
+Required properties: +- compatible : "qcom,lpass-lpaif" +- reg : Address space for the LPASS subsystem registers +- reg-names : The name of the LPASS subsystem register address space +- interrupts : Phandle to the LPASS interrupt +- interrupt-names : The names of the LPASS interrupt
Again you need to document the valid names.
index 0000000000000000000000000000000000000000..d2ff501d44f7b7aa790cdadc8ba75c6a8bf37ccd --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-pcm-mi2s.txt @@ -0,0 +1,12 @@ +* Qualcomm Technologies IPQ806x PCM audio interface
+This node models the Qualcomm Technologies IPQ806x PCM audio interface.
+Required properties: +- compatible: "qcom,lpass-pcm-mi2s"
+Example:
+lpass-pcm-mi2s {
- compatible = "qcom,lpass-pcm-mi2s";
+};
This doesn't appear to describe hardware - there are no register addresses or anything. I'd guess this is most likely part of another hardware block and should be handled by the driver for that device.
From: Kenneth Westfield kwestfie@codeaurora.org
Add the native LPAIF driver for LPASS block in Qualcomm Technologies SoCs.
Change-Id: I0f06f73a1267d7721209e58ce18e0d4897001141 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-lpaif.c | 488 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass-lpaif.h | 181 ++++++++++++++++ 2 files changed, 669 insertions(+) create mode 100644 sound/soc/qcom/lpass-lpaif.c create mode 100644 sound/soc/qcom/lpass-lpaif.h
diff --git a/sound/soc/qcom/lpass-lpaif.c b/sound/soc/qcom/lpass-lpaif.c new file mode 100644 index 0000000000000000000000000000000000000000..e62843fe9bc4c63c3c7c119a9f076085b16a56b3 --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/types.h> +#include <sound/soc.h> +#include "lpass-lpaif.h" + +#define DRV_NAME "lpass-lpaif" +#define DRV_VERSION "1.0" + +struct lpaif_dai_baseinfo { + void __iomem *base; +}; + +struct lpaif_dai_drv { + unsigned char *buffer; + dma_addr_t buffer_phys; + int channels; + irqreturn_t (*callback)(int intrsrc, void *private_data); + void *private_data; + int in_use; + unsigned int buffer_len; + unsigned int period_len; + unsigned int master_mode; +}; + +static struct lpaif_dai_baseinfo lpaif_dai_info; +static struct lpaif_dai_drv *lpaif_dai[LPAIF_MAX_CHANNELS]; +static spinlock_t lpaif_lock; +static struct resource *lpaif_irq; + +static int lpaif_pcm_int_enable(uint8_t dma_ch) +{ + uint32_t intr_val; + uint32_t status_val; + unsigned long flags; + + if (dma_ch >= LPAIF_MAX_CHANNELS) { + pr_err("%s: invalid DMA channel given: %hhu\n", + __func__, dma_ch); + return -EINVAL; + } + + spin_lock_irqsave(&lpaif_lock, flags); + + /* clear status before enabling interrupt */ + status_val = readl(lpaif_dai_info.base + LPAIF_IRQ_CLEAR(0)); + status_val |= LPAIF_PER_CH(dma_ch); + writel(status_val, lpaif_dai_info.base + LPAIF_IRQ_CLEAR(0)); + + intr_val = readl(lpaif_dai_info.base + LPAIF_IRQ_EN(0)); + intr_val |= LPAIF_PER_CH(dma_ch); + writel(intr_val, lpaif_dai_info.base + LPAIF_IRQ_EN(0)); + + spin_unlock_irqrestore(&lpaif_lock, flags); + + return 0; +} + +static int lpaif_pcm_int_disable(uint8_t dma_ch) +{ + uint32_t intr_val; + unsigned long flags; + + if (dma_ch >= LPAIF_MAX_CHANNELS) { + pr_err("%s: invalid DMA channel given: %hhu\n", + __func__, dma_ch); + return -EINVAL; + } + + spin_lock_irqsave(&lpaif_lock, flags); + + intr_val = readl(lpaif_dai_info.base + LPAIF_IRQ_EN(0)); + intr_val &= ~LPAIF_PER_CH(dma_ch); + writel(intr_val, lpaif_dai_info.base + LPAIF_IRQ_EN(0)); + + spin_unlock_irqrestore(&lpaif_lock, flags); + + return 0; +} + +void lpaif_cfg_i2s_playback(uint8_t enable, uint32_t mode, uint32_t off) +{ + uint32_t cfg; + unsigned long flags; + + spin_lock_irqsave(&lpaif_lock, flags); + + cfg = readl(lpaif_dai_info.base + LPAIF_MI2S_CTL_OFFSET(off)); + + if (enable) + cfg |= LPAIF_SPK_EN; + else + cfg &= ~LPAIF_SPK_EN; + + cfg |= mode << LPAIF_SPK_MODE; + cfg &= ~LPAIF_WS; + + writel(cfg, lpaif_dai_info.base + LPAIF_MI2S_CTL_OFFSET(off)); + + spin_unlock_irqrestore(&lpaif_lock, flags); +} + +int lpaif_cfg_mi2s_hwparams_bit_width(uint32_t bit_width, uint32_t off) +{ + int ret = 0; + uint32_t cfg; + unsigned long flags; + + spin_lock_irqsave(&lpaif_lock, flags); + + cfg = readl(lpaif_dai_info.base + LPAIF_MI2S_CTL_OFFSET(off)); + cfg &= ~LPAIF_BIT_MASK; + + switch (bit_width) { + case SNDRV_PCM_FORMAT_S16: + cfg |= LPAIF_BIT_RATE16; + break; + case SNDRV_PCM_FORMAT_S24: + cfg |= LPAIF_BIT_RATE24; + break; + case SNDRV_PCM_FORMAT_S32: + cfg |= LPAIF_BIT_RATE32; + break; + default: + pr_err("%s: invalid bitwidth given: %u\n", + __func__, bit_width); + ret = -EINVAL; + break; + } + + if (!ret) + writel(cfg, lpaif_dai_info.base + LPAIF_MI2S_CTL_OFFSET(off)); + + spin_unlock_irqrestore(&lpaif_lock, flags); + + return ret; +} + +int lpaif_cfg_mi2s_playback_hwparams_channels(uint32_t channels, uint32_t off, + uint32_t bit_width) +{ + int ret = 0; + uint32_t cfg; + unsigned long flags; + + spin_lock_irqsave(&lpaif_lock, flags); + + cfg = readl(lpaif_dai_info.base + LPAIF_MI2S_CTL_OFFSET(off)); + cfg &= ~LPAIF_SPK_MODE_MASK; + + switch (channels) { + case 2: + cfg |= LPAIF_SPK_MODE_SD0; + break; + case 4: + cfg |= LPAIF_SPK_MODE_QUAD01; + break; + case 6: + cfg |= LPAIF_SPK_MODE_6CH; + break; + case 8: + cfg |= LPAIF_SPK_MODE_8CH; + break; + default: + pr_err("%s: invalid channels given: %u\n", __func__, channels); + ret = -EINVAL; + break; + } + + if (!ret) + writel(cfg, lpaif_dai_info.base + LPAIF_MI2S_CTL_OFFSET(off)); + + spin_unlock_irqrestore(&lpaif_lock, flags); + + return ret; +} + +static int lpaif_dai_config_dma(uint32_t dma_ch) +{ + if (dma_ch >= LPAIF_MAX_CHANNELS) { + pr_err("%s: invalid DMA channel given: %u\n", + __func__, dma_ch); + return -EINVAL; + } + + writel(lpaif_dai[dma_ch]->buffer_phys, + lpaif_dai_info.base + LPAIF_DMA_BASE(dma_ch)); + writel(((lpaif_dai[dma_ch]->buffer_len >> 2) - 1), + lpaif_dai_info.base + LPAIF_DMA_BUFF_LEN(dma_ch)); + writel(((lpaif_dai[dma_ch]->period_len >> 2) - 1), + lpaif_dai_info.base + LPAIF_DMA_PER_LEN(dma_ch)); + + return 0; +} + +static int lpaif_dai_cfg_dma_ch(uint32_t dma_ch, uint32_t channels, + uint32_t bit_width) +{ + int ret = 0; + uint32_t cfg; + unsigned long flags; + + if (dma_ch >= LPAIF_MAX_CHANNELS) { + pr_err("%s: invalid DMA channel given: %u\n", + __func__, dma_ch); + return -EINVAL; + } + + spin_lock_irqsave(&lpaif_lock, flags); + + cfg = readl(lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + if ((dma_ch == LPAIF_MI2S_DMA_RD_CH) || + (dma_ch == LPAIF_MI2S_DMA_WR_CH)) { + cfg |= LPAIF_DMACTL_AUDIO_INTF_MI2S; + cfg &= ~LPAIF_DMACTL_WPSCNT_MASK; + + if ((bit_width == 16) && (channels == 2)) { + cfg |= LPAIF_DMACTL_WPSCNT_MONO; + } else if (((bit_width == 16) && (channels == 4)) || + (((bit_width == 24) || (bit_width == 32)) && + (channels == 2))) { + cfg |= LPAIF_DMACTL_WPSCNT_STEREO; + } else if ((bit_width == 16) && (channels == 6)) { + cfg |= LPAIF_DMACTL_WPSCNT_3CH; + } else if (((bit_width == 16) && (channels == 8)) || + (((bit_width == 32) || (bit_width == 24)) && + (channels == 4))) { + cfg |= LPAIF_DMACTL_WPSCNT_4CH; + } else if (((bit_width == 24) || (bit_width == 32)) && + (channels == 6)) { + cfg |= LPAIF_DMACTL_WPSCNT_6CH; + } else if (((bit_width == 24) || (bit_width == 32)) && + (channels == 8)) { + cfg |= LPAIF_DMACTL_WPSCNT_8CH; + } else { + pr_err("%s: invalid PCM config given: bw=%u, ch=%u\n", + __func__, bit_width, channels); + ret = -EINVAL; + } + } + + if (!ret) + writel(cfg, lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + spin_unlock_irqrestore(&lpaif_lock, flags); + return ret; +} + +int lpaif_cfg_dma(uint32_t dma_ch, struct lpaif_dai_dma_params *params, + uint32_t bit_width, bool enable_intr) +{ + int ret; + uint32_t cfg; + + lpaif_dai[dma_ch]->buffer = params->buffer; + lpaif_dai[dma_ch]->buffer_phys = params->src_start; + lpaif_dai[dma_ch]->channels = params->channels; + lpaif_dai[dma_ch]->buffer_len = params->buffer_size; + lpaif_dai[dma_ch]->period_len = params->period_size; + + ret = lpaif_dai_config_dma(dma_ch); + if (ret) { + pr_err("%s: error configuring DMA block: %d\n", __func__, ret); + return ret; + } + + if (enable_intr) + lpaif_pcm_int_enable(dma_ch); + + ret = lpaif_dai_cfg_dma_ch(dma_ch, params->channels, bit_width); + if (ret) { + pr_err("%s: error configuring DMA channel: %d\n", + __func__, ret); + lpaif_pcm_int_disable(dma_ch); + return ret; + } + + cfg = readl(lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + cfg |= LPAIF_DMACTL_FIFO_WM_8 | LPAIF_DMACTL_BURST_EN; + writel(cfg, lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + cfg = readl(lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + cfg |= LPAIF_DMACTL_ENABLE; + writel(cfg, lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + return 0; +} + +int lpaif_dai_stop(uint32_t dma_ch) +{ + writel(0x0, lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + return 0; +} + +uint8_t lpaif_dma_stop(uint8_t dma_ch) +{ + uint32_t cfg; + + cfg = readl(lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + cfg &= ~LPAIF_DMACTL_ENABLE; + writel(cfg, lpaif_dai_info.base + LPAIF_DMA_CTL(dma_ch)); + return 0; +} + +void lpaif_register_dma_irq_handler(int dma_ch, + irqreturn_t (*callback)(int intrsrc, void *private_data), + void *private_data) +{ + lpaif_dai[dma_ch]->callback = callback; + lpaif_dai[dma_ch]->private_data = private_data; +} + +void lpaif_unregister_dma_irq_handler(int dma_ch) +{ + lpaif_dai[dma_ch]->callback = NULL; + lpaif_dai[dma_ch]->private_data = NULL; +} + +/* + * Logic to find the dma channel from interrupt. + * In total we have 9 channels, each channel records the transcation + * status. Either one of ths 3 status will be recorded per transcation + * (PER_CH,UNDER_RUN,OVER_RUN) + */ +static int lpaif_dai_find_dma_channel(uint32_t intrsrc) +{ + uint32_t dma_channel = 0; + + while (dma_channel < LPAIF_MAX_CHANNELS) { + if (intrsrc & LPAIF_PER_CH(dma_channel)) + return dma_channel; + + dma_channel++; + } + + return -1; +} + +/* ISR for handling LPAIF interrupts */ +static irqreturn_t lpaif_dai_irq_handler(int irq, void *data) +{ + unsigned long flag; + uint32_t intrsrc; + uint32_t dma_ch; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&lpaif_lock, flag); + intrsrc = readl(lpaif_dai_info.base + LPAIF_IRQ_STAT(0)); + writel(intrsrc, lpaif_dai_info.base + LPAIF_IRQ_CLEAR(0)); + spin_unlock_irqrestore(&lpaif_lock, flag); + + while (intrsrc) { + dma_ch = lpaif_dai_find_dma_channel(intrsrc); + if (dma_ch != -1) { + if (lpaif_dai[dma_ch]->callback) { + + ret = lpaif_dai[dma_ch]->callback(intrsrc, + lpaif_dai[dma_ch]->private_data); + } + intrsrc &= ~LPAIF_PER_CH(dma_ch); + } else { + pr_err("%s: error getting channel\n", __func__); + break; + } + } + return ret; +} + +static void lpaif_dai_ch_free(void) +{ + int i; + + for (i = 0; i < LPAIF_MAX_CHANNELS; i++) + kfree(lpaif_dai[i]); +} + +static int lpaif_dai_probe(struct platform_device *pdev) +{ + uint8_t i; + int32_t rc; + struct resource *lpa_res; + struct device *lpaif_device; + + lpaif_device = &pdev->dev; + + lpa_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "lpass-lpaif-mem"); + if (!lpa_res) { + dev_err(&pdev->dev, "%s: error getting resource\n", __func__); + return -ENODEV; + } + lpaif_dai_info.base = ioremap(lpa_res->start, + (lpa_res->end - lpa_res->start)); + if (!lpaif_dai_info.base) { + dev_err(&pdev->dev, "%s: error remapping resource\n", + __func__); + return -ENOMEM; + } + + lpaif_irq = platform_get_resource_byname( + pdev, IORESOURCE_IRQ, "lpass-lpaif-irq"); + if (!lpaif_irq) { + dev_err(&pdev->dev, "%s: failed get irq res\n", __func__); + rc = -ENODEV; + goto error; + } + + rc = request_irq(lpaif_irq->start, lpaif_dai_irq_handler, + IRQF_TRIGGER_RISING, "lpass-lpaif-intr", NULL); + + if (rc < 0) { + dev_err(&pdev->dev, "%s: irq resource request failed\n", + __func__); + goto error; + } + + /* + * Allocating memory for all the LPA_IF DMA channels + */ + for (i = 0; i < LPAIF_MAX_CHANNELS; i++) { + lpaif_dai[i] = kzalloc(sizeof(struct lpaif_dai_drv), + GFP_KERNEL); + if (!lpaif_dai[i]) { + rc = -ENOMEM; + goto error_irq; + } + } + spin_lock_init(&lpaif_lock); + return 0; + +error_irq: + free_irq(lpaif_irq->start, NULL); + lpaif_dai_ch_free(); +error: + iounmap(lpaif_dai_info.base); + return rc; +} + +static int lpaif_dai_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < LPAIF_MAX_CHANNELS; i++) + lpaif_dai_stop(i); + synchronize_irq(lpaif_irq->start); + free_irq(lpaif_irq->start, NULL); + iounmap(lpaif_dai_info.base); + lpaif_dai_ch_free(); + return 0; +} + +static const struct of_device_id lpaif_dai_dt_match[] = { + {.compatible = "qcom,lpass-lpaif"}, + {} +}; + +static struct platform_driver lpass_lpaif_driver = { + .probe = lpaif_dai_probe, + .remove = lpaif_dai_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = lpaif_dai_dt_match, + }, +}; +module_platform_driver(lpass_lpaif_driver); + +MODULE_DESCRIPTION("QCOM LPASS LPAIF Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpaif_dai_dt_match); +MODULE_VERSION(DRV_VERSION); diff --git a/sound/soc/qcom/lpass-lpaif.h b/sound/soc/qcom/lpass-lpaif.h new file mode 100644 index 0000000000000000000000000000000000000000..e10731bb2cef96e31ebf7a92b9ba3e8ee22e0360 --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LPASS_LPAIF_H +#define _LPASS_LPAIF_H + +#define LPAIF_BANK_OFFSET 0x1000 + +/* Audio DMA registers for DMA channel confuguration */ +#define LPAIF_DMA_CH_CTL_BASE 0x6000 +#define LPAIF_DMA_CH_INDEX(ch) (LPAIF_BANK_OFFSET * ch) + +#define LPAIF_DMA_CTRL_ADDR(ch, addr) (LPAIF_DMA_CH_CTL_BASE \ + + (LPAIF_DMA_CH_INDEX(ch) \ + + addr)) + +#define LPAIF_DMA_CTL(x) LPAIF_DMA_CTRL_ADDR(x, 0x00) +#define LPAIF_BURST_EN (1 << 11) +#define LPAIF_WPSCNT_ONE (0 << 8) +#define LPAIF_WPSCNT_TWO (1 << 8) +#define LPAIF_WPSCNT_THREE (2 << 8) +#define LPAIF_WPSCNT_FOUR (3 << 8) +#define LPAIF_WPSCNT_SIX (5 << 8) +#define LPAIF_WPSCNT_EIGHT (7 << 8) +#define LPAIF_AUDIO_INTF_NONE (0 << 4) +#define LPAIF_AUDIO_INTF_CODEC (1 << 4) +#define LPAIF_AUDIO_INTF_PCM (2 << 4) +#define LPAIF_AUDIO_INTF_SEC_I2S (3 << 4) +#define LPAIF_AUDIO_INTF_MI2S (4 << 4) +#define LPAIF_AUDIO_INTF_HDMI (5 << 4) +#define LPAIF_AUDIO_INTF_MIXOUT (6 << 4) +#define LPAIF_AUDIO_INTF_LOOPBACK1 (7 << 4) +#define LPAIF_AUDIO_INTF_LOOPBACK2 (8 << 4) +#define LPAIF_FIFO_WATERMRK(x) ((x & 0x7) << 1) +#define LPAIF_ENABLE (1 << 0) + +#define LPAIF_DMA_BASE(x) LPAIF_DMA_CTRL_ADDR(x, 0x04) +#define LPAIF_BASE_ADDR (0xFFFFFFFF << 4) + +#define LPAIF_DMA_BUFF_LEN(x) LPAIF_DMA_CTRL_ADDR(x, 0x08) +#define LPAIF_DMA_CURR_ADDR(x) LPAIF_DMA_CTRL_ADDR(x, 0x0c) +#define LPAIF_DMA_PER_LEN(x) LPAIF_DMA_CTRL_ADDR(x, 0x10) +#define LPAIF_DMA_PER_CNT(x) LPAIF_DMA_CTRL_ADDR(x, 0x14) +#define LPAIF_DMA_FRM(x) LPAIF_DMA_CTRL_ADDR(x, 0x18) +#define LPAIF_DMA_FRMCLR(x) LPAIF_DMA_CTRL_ADDR(x, 0x1c) +#define LPAIF_DMA_SET_BUFF_CNT(x) LPAIF_DMA_CTRL_ADDR(x, 0x20) +#define LPAIF_DMA_SET_PER_CNT(x) LPAIF_DMA_CTRL_ADDR(x, 0x24) + +#define LPAIF_MAX_CHANNELS 9 + +#define LPAIF_CODEC_SPK 0x0 +#define LPAIF_CODEC_MIC 0x1 +#define LPAIF_SEC_SPK 0x2 +#define LPAIF_SEC_MIC 0x3 +#define LPAIF_MI2S 0x4 + +#define LPAIF_LB (1 << 15) +#define LPAIF_SPK_EN (1 << 14) + +#define LPAIF_SPK_MODE_MASK 0x3C00 +#define LPAIF_SPK_MODE 10 +#define LPAIF_SPK_MODE_NONE (0 << 10) +#define LPAIF_SPK_MODE_SD0 (1 << 10) +#define LPAIF_SPK_MODE_SD1 (2 << 10) +#define LPAIF_SPK_MODE_SD2 (3 << 10) +#define LPAIF_SPK_MODE_SD3 (4 << 10) +#define LPAIF_SPK_MODE_QUAD01 (5 << 10) +#define LPAIF_SPK_MODE_QUAD23 (6 << 10) +#define LPAIF_SPK_MODE_6CH (7 << 10) +#define LPAIF_SPK_MODE_8CH (8 << 10) + +#define LPAIF_WS (1 << 2) + +#define LPAIF_BIT_MASK (0x3) +#define LPAIF_BIT_RATE16 (0 << 0) +#define LPAIF_BIT_RATE24 (1 << 0) +#define LPAIF_BIT_RATE32 (2 << 0) + +#define LPAIF_MI2S_CTL_OFFSET(x) (0x0010 + (0x4 * x)) + +/* LPAIF INTERRUPT CTRL */ + +#define LPAIF_DMA_IRQ_BASE 0x3000 +#define LPAIF_DMA_IRQ_INDEX(x) (LPAIF_BANK_OFFSET * x) +#define LPAIF_DMA_IRQ_ADDR(irq, addr) (LPAIF_DMA_IRQ_BASE \ + + LPAIF_DMA_IRQ_INDEX(irq) \ + + addr) + +#define LPAIF_IRQ_EN(x) LPAIF_DMA_IRQ_ADDR(x, 0x00) +#define LPAIF_IRQ_STAT(x) LPAIF_DMA_IRQ_ADDR(x, 0x04) +#define LPAIF_IRQ_RAW_STAT(x) LPAIF_DMA_IRQ_ADDR(x, 0x08) +#define LPAIF_IRQ_CLEAR(x) LPAIF_DMA_IRQ_ADDR(x, 0x0c) +#define LPAIF_IRQ_FORCE(x) LPAIF_DMA_IRQ_ADDR(x, 0x10) +#define LPAIF_PER_CH(x) (1 << (3 * x)) +#define LPAIF_UNDER_CH(x) (2 << (3 * x)) +#define LPAIF_ERR_CH(x) (4 << (3 * x)) + +/* DMA CTRL */ + +#define LPAIF_DMACTL_BURST_EN (1 << 11) +#define LPAIF_DMACTL_WPSCNT_MASK (0x700) +#define LPAIF_DMACTL_WPSCNT_MONO (0 << 8) +#define LPAIF_DMACTL_WPSCNT_STEREO (1 << 8) +#define LPAIF_DMACTL_WPSCNT_STEREO_2CH (0 << 8) +#define LPAIF_DMACTL_WPSCNT_3CH (2 << 8) +#define LPAIF_DMACTL_WPSCNT_4CH (3 << 8) +#define LPAIF_DMACTL_WPSCNT_5CH (4 << 8) +#define LPAIF_DMACTL_WPSCNT_6CH (5 << 8) +#define LPAIF_DMACTL_WPSCNT_7CH (6 << 8) +#define LPAIF_DMACTL_WPSCNT_8CH (7 << 8) + +#define LPAIF_DMACTL_AUDIO_INTF_MASK (0xF0) +#define LPAIF_DMACTL_AUDIO_INTF_NONE (0 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_CODEC (1 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_PCM (2 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_SEC_I2S (3 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_MI2S (4 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_HDMI (5 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_MIXOUT (6 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_LB1 (7 << 4) +#define LPAIF_DMACTL_AUDIO_INTF_LB2 (8 << 4) + +#define LPAIF_DMACTL_FIFO_WM_1 (0 << 1) +#define LPAIF_DMACTL_FIFO_WM_2 (1 << 1) +#define LPAIF_DMACTL_FIFO_WM_3 (2 << 1) +#define LPAIF_DMACTL_FIFO_WM_4 (3 << 1) +#define LPAIF_DMACTL_FIFO_WM_5 (4 << 1) +#define LPAIF_DMACTL_FIFO_WM_6 (5 << 1) +#define LPAIF_DMACTL_FIFO_WM_7 (6 << 1) +#define LPAIF_DMACTL_FIFO_WM_8 (7 << 1) + +#define LPAIF_DMACTL_ENABLE (1 << 0) + +enum lpaif_dma_intf_wr_ch { + LPAIF_MIN_DMA_WR_CH = 5, + LPAIF_PCM0_DMA_WR_CH = 5, + LPAIF_PCM1_DMA_WR_CH = 6, + LPAIF_MI2S_DMA_WR_CH = 6, + LPAIF_MAX_DMA_WR_CH = 8, +}; + +enum lpaif_dma_intf_rd_ch { + LPAIF_MIN_DMA_RD_CH = 0, + LPAIF_MI2S_DMA_RD_CH = 0, + LPAIF_PCM0_DMA_RD_CH = 1, + LPAIF_PCM1_DMA_RD_CH = 2, + LPAIF_MAX_DMA_RD_CH = 4, +}; + +struct lpaif_dai_dma_params { + u8 *buffer; + uint32_t src_start; + uint32_t bus_id; + int buffer_size; + int period_size; + int channels; +}; + +void lpaif_cfg_i2s_playback(uint8_t enable, uint32_t mode, uint32_t off); +int lpaif_cfg_mi2s_hwparams_bit_width(uint32_t bit_width, uint32_t off); +int lpaif_cfg_mi2s_playback_hwparams_channels(uint32_t channels, + uint32_t off, uint32_t bit_width); +int lpaif_cfg_dma(uint32_t dma_ch, struct lpaif_dai_dma_params *params, + uint32_t bit_width, bool enable_intr); +int lpaif_dai_stop(uint32_t dma_ch); +uint8_t lpaif_dma_stop(uint8_t dma_ch); +void lpaif_register_dma_irq_handler(int dma_ch, + irqreturn_t (*callback)(int intr_src, void *private_data), + void *private_data); +void lpaif_unregister_dma_irq_handler(int dma_ch); +#endif /* _LPASS_LPAIF_H */
On 11/19/2014 07:52 PM, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add the native LPAIF driver for LPASS block in Qualcomm Technologies SoCs.
Change-Id: I0f06f73a1267d7721209e58ce18e0d4897001141 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
sound/soc/qcom/lpass-lpaif.c | 488 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass-lpaif.h | 181 ++++++++++++++++ 2 files changed, 669 insertions(+) create mode 100644 sound/soc/qcom/lpass-lpaif.c create mode 100644 sound/soc/qcom/lpass-lpaif.h
diff --git a/sound/soc/qcom/lpass-lpaif.c b/sound/soc/qcom/lpass-lpaif.c new file mode 100644 index 0000000000000000000000000000000000000000..e62843fe9bc4c63c3c7c119a9f076085b16a56b3 --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif.c @@ -0,0 +1,488 @@ +/*
- Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 and
- only version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/types.h> +#include <sound/soc.h> +#include "lpass-lpaif.h"
+#define DRV_NAME "lpass-lpaif" +#define DRV_VERSION "1.0"
+struct lpaif_dai_baseinfo {
- void __iomem *base;
+};
+struct lpaif_dai_drv {
- unsigned char *buffer;
- dma_addr_t buffer_phys;
- int channels;
- irqreturn_t (*callback)(int intrsrc, void *private_data);
- void *private_data;
- int in_use;
- unsigned int buffer_len;
- unsigned int period_len;
- unsigned int master_mode;
+};
+static struct lpaif_dai_baseinfo lpaif_dai_info; +static struct lpaif_dai_drv *lpaif_dai[LPAIF_MAX_CHANNELS]; +static spinlock_t lpaif_lock; +static struct resource *lpaif_irq;
Please don't use global state for device drivers. Make the state device instance specific.
[...]
+static int lpaif_dai_probe(struct platform_device *pdev) +{
- uint8_t i;
- int32_t rc;
- struct resource *lpa_res;
- struct device *lpaif_device;
- lpaif_device = &pdev->dev;
- lpa_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"lpass-lpaif-mem");
- if (!lpa_res) {
dev_err(&pdev->dev, "%s: error getting resource\n", __func__);
return -ENODEV;
- }
- lpaif_dai_info.base = ioremap(lpa_res->start,
(lpa_res->end - lpa_res->start));
It's probably better to use devm_ioremap_resource here.
- if (!lpaif_dai_info.base) {
dev_err(&pdev->dev, "%s: error remapping resource\n",
__func__);
return -ENOMEM;
- }
- lpaif_irq = platform_get_resource_byname(
pdev, IORESOURCE_IRQ, "lpass-lpaif-irq");
platform_get_irq_byname
- if (!lpaif_irq) {
dev_err(&pdev->dev, "%s: failed get irq res\n", __func__);
rc = -ENODEV;
goto error;
- }
- rc = request_irq(lpaif_irq->start, lpaif_dai_irq_handler,
IRQF_TRIGGER_RISING, "lpass-lpaif-intr", NULL);
- if (rc < 0) {
dev_err(&pdev->dev, "%s: irq resource request failed\n",
__func__);
goto error;
- }
- /*
* Allocating memory for all the LPA_IF DMA channels
*/
- for (i = 0; i < LPAIF_MAX_CHANNELS; i++) {
lpaif_dai[i] = kzalloc(sizeof(struct lpaif_dai_drv),
GFP_KERNEL);
if (!lpaif_dai[i]) {
rc = -ENOMEM;
goto error_irq;
}
- }
- spin_lock_init(&lpaif_lock);
This needs to be initialized before you request the interrupt as the interrupt handler is using the spinlock.
- return 0;
+error_irq:
- free_irq(lpaif_irq->start, NULL);
- lpaif_dai_ch_free();
+error:
- iounmap(lpaif_dai_info.base);
- return rc;
+}
+static int lpaif_dai_remove(struct platform_device *pdev) +{
- int i;
- for (i = 0; i < LPAIF_MAX_CHANNELS; i++)
lpaif_dai_stop(i);
- synchronize_irq(lpaif_irq->start);
free_irq does a synchronize_irq, not need to call it manually.
- free_irq(lpaif_irq->start, NULL);
- iounmap(lpaif_dai_info.base);
- lpaif_dai_ch_free();
- return 0;
+}
[..]
On Thu, November 20, 2014 4:32 am, Lars-Peter Clausen wrote:
On 11/19/2014 07:52 PM, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add the native LPAIF driver for LPASS block in Qualcomm Technologies SoCs.
Change-Id: I0f06f73a1267d7721209e58ce18e0d4897001141 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
sound/soc/qcom/lpass-lpaif.c | 488 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass-lpaif.h | 181 ++++++++++++++++ 2 files changed, 669 insertions(+) create mode 100644 sound/soc/qcom/lpass-lpaif.c create mode 100644 sound/soc/qcom/lpass-lpaif.h
diff --git a/sound/soc/qcom/lpass-lpaif.c b/sound/soc/qcom/lpass-lpaif.c new file mode 100644 index 0000000000000000000000000000000000000000..e62843fe9bc4c63c3c7c119a9f076085b16a56b3 --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif.c @@ -0,0 +1,488 @@ +/*
- Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 and
- only version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/types.h> +#include <sound/soc.h> +#include "lpass-lpaif.h"
+#define DRV_NAME "lpass-lpaif" +#define DRV_VERSION "1.0"
+struct lpaif_dai_baseinfo {
- void __iomem *base;
+};
+struct lpaif_dai_drv {
- unsigned char *buffer;
- dma_addr_t buffer_phys;
- int channels;
- irqreturn_t (*callback)(int intrsrc, void *private_data);
- void *private_data;
- int in_use;
- unsigned int buffer_len;
- unsigned int period_len;
- unsigned int master_mode;
+};
+static struct lpaif_dai_baseinfo lpaif_dai_info; +static struct lpaif_dai_drv *lpaif_dai[LPAIF_MAX_CHANNELS]; +static spinlock_t lpaif_lock; +static struct resource *lpaif_irq;
Please don't use global state for device drivers. Make the state device instance specific.
[...]
+static int lpaif_dai_probe(struct platform_device *pdev) +{
- uint8_t i;
- int32_t rc;
- struct resource *lpa_res;
- struct device *lpaif_device;
- lpaif_device = &pdev->dev;
- lpa_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"lpass-lpaif-mem");
- if (!lpa_res) {
dev_err(&pdev->dev, "%s: error getting resource\n", __func__);
return -ENODEV;
- }
- lpaif_dai_info.base = ioremap(lpa_res->start,
(lpa_res->end - lpa_res->start));
It's probably better to use devm_ioremap_resource here.
- if (!lpaif_dai_info.base) {
dev_err(&pdev->dev, "%s: error remapping resource\n",
__func__);
return -ENOMEM;
- }
- lpaif_irq = platform_get_resource_byname(
pdev, IORESOURCE_IRQ, "lpass-lpaif-irq");
platform_get_irq_byname
- if (!lpaif_irq) {
dev_err(&pdev->dev, "%s: failed get irq res\n", __func__);
rc = -ENODEV;
goto error;
- }
- rc = request_irq(lpaif_irq->start, lpaif_dai_irq_handler,
IRQF_TRIGGER_RISING, "lpass-lpaif-intr", NULL);
- if (rc < 0) {
dev_err(&pdev->dev, "%s: irq resource request failed\n",
__func__);
goto error;
- }
- /*
* Allocating memory for all the LPA_IF DMA channels
*/
- for (i = 0; i < LPAIF_MAX_CHANNELS; i++) {
lpaif_dai[i] = kzalloc(sizeof(struct lpaif_dai_drv),
GFP_KERNEL);
if (!lpaif_dai[i]) {
rc = -ENOMEM;
goto error_irq;
}
- }
- spin_lock_init(&lpaif_lock);
This needs to be initialized before you request the interrupt as the interrupt handler is using the spinlock.
- return 0;
+error_irq:
- free_irq(lpaif_irq->start, NULL);
- lpaif_dai_ch_free();
+error:
- iounmap(lpaif_dai_info.base);
- return rc;
+}
+static int lpaif_dai_remove(struct platform_device *pdev) +{
- int i;
- for (i = 0; i < LPAIF_MAX_CHANNELS; i++)
lpaif_dai_stop(i);
- synchronize_irq(lpaif_irq->start);
free_irq does a synchronize_irq, not need to call it manually.
- free_irq(lpaif_irq->start, NULL);
- iounmap(lpaif_dai_info.base);
- lpaif_dai_ch_free();
- return 0;
+}
[..] _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Lars
Thank you for your comments. I will separately address each comment shortly.
On Wed, Nov 19, 2014 at 10:52:43AM -0800, Kenneth Westfield wrote:
+#define DRV_NAME "lpass-lpaif" +#define DRV_VERSION "1.0"
Don't add versions like this, the kernel is already more than adequately versioned and nobody is ever going to bother to update it.
+static int lpaif_pcm_int_enable(uint8_t dma_ch) +{
- uint32_t intr_val;
- uint32_t status_val;
- unsigned long flags;
- if (dma_ch >= LPAIF_MAX_CHANNELS) {
pr_err("%s: invalid DMA channel given: %hhu\n",
__func__, dma_ch);
dev_err().
+void lpaif_cfg_i2s_playback(uint8_t enable, uint32_t mode, uint32_t off) +{
The kernel types for fixed size unsigned integers are u8, u32 and so on.
if ((bit_width == 16) && (channels == 2)) {
cfg |= LPAIF_DMACTL_WPSCNT_MONO;
} else if (((bit_width == 16) && (channels == 4)) ||
switch statements please, it's both more legible and more extensible.
+void lpaif_register_dma_irq_handler(int dma_ch,
irqreturn_t (*callback)(int intrsrc, void *private_data),
void *private_data)
+{
- lpaif_dai[dma_ch]->callback = callback;
- lpaif_dai[dma_ch]->private_data = private_data;
+}
+void lpaif_unregister_dma_irq_handler(int dma_ch) +{
- lpaif_dai[dma_ch]->callback = NULL;
- lpaif_dai[dma_ch]->private_data = NULL;
+}
What is this doing? Linux already has a perfectly good interface for requesting and releasing interrupts...
+static int lpaif_dai_find_dma_channel(uint32_t intrsrc) +{
- uint32_t dma_channel = 0;
- while (dma_channel < LPAIF_MAX_CHANNELS) {
if (intrsrc & LPAIF_PER_CH(dma_channel))
return dma_channel;
dma_channel++;
- }
A comment explaining why we can't just map directly might be helpful here.
- return -1;
Real error codes please.
+static irqreturn_t lpaif_dai_irq_handler(int irq, void *data) +{
- unsigned long flag;
- uint32_t intrsrc;
- uint32_t dma_ch;
- irqreturn_t ret = IRQ_NONE;
- spin_lock_irqsave(&lpaif_lock, flag);
- intrsrc = readl(lpaif_dai_info.base + LPAIF_IRQ_STAT(0));
- writel(intrsrc, lpaif_dai_info.base + LPAIF_IRQ_CLEAR(0));
- spin_unlock_irqrestore(&lpaif_lock, flag);
This appears to be unconditionally acknowleding all interrupts, even those we don't understand. This is bad practice - we should at least be logging an error and ideally returning IRQ_NONE if we don't understand the interrupt and letting the core error handling work for us. For example it looks like this will happily and silently acknowledge both error and underrun interrupts from the hardware.
It's also not at all obvious why we're taking this spinlock in the interrupt handler, that's *extremely* unusual. What is being protected - the code needs to make that clear?
- while (intrsrc) {
dma_ch = lpaif_dai_find_dma_channel(intrsrc);
if (dma_ch != -1) {
if (lpaif_dai[dma_ch]->callback) {
ret = lpaif_dai[dma_ch]->callback(intrsrc,
lpaif_dai[dma_ch]->private_data);
}
intrsrc &= ~LPAIF_PER_CH(dma_ch);
} else {
pr_err("%s: error getting channel\n", __func__);
break;
}
- }
- return ret;
+}
This all looks like a very simple demux of a single register - don't we have a generic irqchip that can handle it? It looks like that's what you're trying to implement here, each DMA channel could be requesting the three interrupts it has separately rather than open coding an interrupt controller here.
I'm not actually immediately seeing one right now but it'd be simple to add (or regmap-irq could do it if we used a regmap, though it assumes threading and isn't a great fit).
+static struct platform_driver lpass_lpaif_driver = {
- .probe = lpaif_dai_probe,
- .remove = lpaif_dai_remove,
- .driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
No need for .owner on platform devices any more.
From: Kenneth Westfield kwestfie@codeaurora.org
Add the CPU DAI driver for the QCOM LPASS SOC.
Change-Id: I64ac4407dd32bb9a3066d4b7427292002eaf5d14 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-cpu-dai.c | 307 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 sound/soc/qcom/lpass-cpu-dai.c
diff --git a/sound/soc/qcom/lpass-cpu-dai.c b/sound/soc/qcom/lpass-cpu-dai.c new file mode 100644 index 0000000000000000000000000000000000000000..0693e761e01fd416f52684977bcfaf1579dc259c --- /dev/null +++ b/sound/soc/qcom/lpass-cpu-dai.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "lpass-lpaif.h" +#include "lpass-pcm-mi2s.h" + +#define DRV_NAME "lpass-cpu-dai" +#define DRV_VERSION "1.0" + +#define LPASS_INVALID (-1) + +struct mi2s_hw_params { + uint8_t channels; + uint32_t freq; + uint8_t bit_width; +}; + +static struct clk *lpaif_mi2s_bit_clk; +static struct clk *lpaif_mi2s_osr_clk; +static struct mi2s_hw_params mi2s_params; + +/* + * Returns the OSR to BIT clock divider that would be used with the given + * stream characteristics. + */ +static uint32_t lpass_cpu_get_bit_div(uint32_t samp_freq, uint32_t bit_width, + uint32_t channels) +{ + if (channels == 8) { + if (bit_width == 24 && + ((samp_freq != 176400) && + (samp_freq != 192000))) + return 2; + return 1; + } else if (channels == 6) { + if ((bit_width == 32 && ((samp_freq == 176400) || + (samp_freq == 192000))) || (bit_width == 24 && + (samp_freq == 192000))) + return 1; + else + return 2; + } + + switch (samp_freq) { + case 8000: + case 16000: + case 32000: + case 64000: + if (bit_width == 16) + return 4; + break; + case 48000: + if (bit_width == 24) + return 4; + break; + case 96000: + case 192000: + return 4; + case 176400: + return 2; + case 11025: + case 22050: + case 44100: + case 88200: + default: + pr_err("%s: Invalid PCM format given\n", __func__); + return LPASS_INVALID; + } + + return 4; +} + +static int lpass_cpu_mi2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + uint32_t ret = 0; + uint32_t bit_act; + uint16_t bit_div; + uint32_t bit_width = params_format(params); + uint32_t channels = params_channels(params); + uint32_t rate = params_rate(params); + struct snd_pcm_runtime *runtime = substream->runtime; + struct lpass_runtime_data_t *prtd = runtime->private_data; + struct mi2s_hw_params curr_params; + + bit_act = snd_pcm_format_width(bit_width); + if (bit_act == LPASS_INVALID) { + dev_err(dai->dev, "%s: Invalid bit width given\n", __func__); + return -EINVAL; + } + + prtd->pcm_stream_info.bit_width = bit_act; + curr_params.freq = rate; + curr_params.channels = channels; + curr_params.bit_width = bit_act; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* disable SPKR to make sure it will start in sane state */ + lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S); + + /* + * Set channel info, it will take effect only if SPKR is + * enabled + */ + ret = lpaif_cfg_mi2s_playback_hwparams_channels(channels, + LPAIF_MI2S, bit_act); + } else { + dev_err(dai->dev, "%s: Invalid stream direction\n", __func__); + return -EINVAL; + } + + /* Make sure our channel setting was success */ + if (ret) { + /* shutdown would be called where we restore stuff */ + dev_err(dai->dev, "%s: Channel setting unsuccessful\n", + __func__); + return -EINVAL; + } + + mi2s_params.freq = rate; + mi2s_params.channels = channels; + mi2s_params.bit_width = bit_act; + + ret = lpaif_cfg_mi2s_hwparams_bit_width(bit_width, LPAIF_MI2S); + if (ret) { + dev_err(dai->dev, "%s: Could not set bit width in HW\n", + __func__); + return -EINVAL; + } + + bit_div = lpass_cpu_get_bit_div(rate, bit_act, channels); + if (bit_div == LPASS_INVALID) { + dev_err(dai->dev, "%s: Could not get bit clk divider\n", + __func__); + return -EINVAL; + } + + ret = clk_set_rate(lpaif_mi2s_osr_clk, + (rate * bit_act * channels * bit_div)); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s osr clk\n", + __func__); + return ret; + } + ret = clk_prepare_enable(lpaif_mi2s_osr_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s osr clk\n", + __func__); + return ret; + } + prtd->lpaif_clk.is_osr_clk_enabled = 1; + + ret = clk_set_rate(lpaif_mi2s_bit_clk, rate * bit_act * channels); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s bit clk\n", + __func__); + return ret; + } + ret = clk_prepare_enable(lpaif_mi2s_bit_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s bit clk\n", + __func__); + return ret; + } + prtd->lpaif_clk.is_bit_clk_enabled = 1; + + return 0; +} + +static int lpass_cpu_mi2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int lpass_cpu_mi2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + + lpaif_mi2s_osr_clk = clk_get(dai->dev, "mi2s_osr_clk"); + if (IS_ERR(lpaif_mi2s_osr_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n", + __func__); + return PTR_ERR(lpaif_mi2s_osr_clk); + } + + lpaif_mi2s_bit_clk = clk_get(dai->dev, "mi2s_bit_clk"); + if (IS_ERR(lpaif_mi2s_bit_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n", + __func__); + return PTR_ERR(lpaif_mi2s_bit_clk); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S); + } else { + dev_err(dai->dev, "%s: Invalid stream direction\n", __func__); + return -EINVAL; + } + + return 0; +} + +static void lpass_cpu_mi2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct lpass_runtime_data_t *prtd = runtime->private_data; + + if (prtd->lpaif_clk.is_osr_clk_enabled) + clk_disable_unprepare(lpaif_mi2s_osr_clk); + clk_put(lpaif_mi2s_osr_clk); + + if (prtd->lpaif_clk.is_bit_clk_enabled) + clk_disable_unprepare(lpaif_mi2s_bit_clk); + clk_put(lpaif_mi2s_bit_clk); +} + +static int lpass_cpu_mi2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + return 0; +} + +static struct snd_soc_dai_ops lpass_cpu_mi2s_ops = { + .startup = lpass_cpu_mi2s_startup, + .prepare = lpass_cpu_mi2s_prepare, + .hw_params = lpass_cpu_mi2s_hw_params, + .shutdown = lpass_cpu_mi2s_shutdown, + .set_fmt = lpass_cpu_mi2s_set_fmt, +}; + +static struct snd_soc_dai_driver lpass_cpu_dais[] = { + { + .playback = { + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .ops = &lpass_cpu_mi2s_ops, + .name = "lpass-mi2s-dai" + }, +}; + +static const struct snd_soc_component_driver lpass_cpu_component = { + .name = DRV_NAME, +}; + +static int lpass_cpu_dai_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_component(&pdev->dev, &lpass_cpu_component, + lpass_cpu_dais, ARRAY_SIZE(lpass_cpu_dais)); + if (ret) + dev_err(&pdev->dev, "%s: error registering soc dais\n", + __func__); + + return ret; +} + +static int lpass_cpu_dai_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + return 0; +} + +static const struct of_device_id lpass_cpu_dai_dt_match[] = { + {.compatible = "qcom,lpass-cpu-dai"}, + {} +}; + +static struct platform_driver lpass_cpu_dai_driver = { + .probe = lpass_cpu_dai_probe, + .remove = lpass_cpu_dai_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = lpass_cpu_dai_dt_match, + }, +}; +module_platform_driver(lpass_cpu_dai_driver); + +MODULE_DESCRIPTION("QCOM LPASS CPU DAI DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_cpu_dai_dt_match); +MODULE_VERSION(DRV_VERSION);
On 11/19/14, 12:52 PM, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add the CPU DAI driver for the QCOM LPASS SOC.
Change-Id: I64ac4407dd32bb9a3066d4b7427292002eaf5d14 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
sound/soc/qcom/lpass-cpu-dai.c | 307 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 sound/soc/qcom/lpass-cpu-dai.c
diff --git a/sound/soc/qcom/lpass-cpu-dai.c b/sound/soc/qcom/lpass-cpu-dai.c new file mode 100644 index 0000000000000000000000000000000000000000..0693e761e01fd416f52684977bcfaf1579dc259c --- /dev/null +++ b/sound/soc/qcom/lpass-cpu-dai.c @@ -0,0 +1,307 @@ +/*
- Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 and
- only version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "lpass-lpaif.h" +#include "lpass-pcm-mi2s.h"
+#define DRV_NAME "lpass-cpu-dai" +#define DRV_VERSION "1.0"
+#define LPASS_INVALID (-1)
+struct mi2s_hw_params {
- uint8_t channels;
- uint32_t freq;
- uint8_t bit_width;
+};
+static struct clk *lpaif_mi2s_bit_clk; +static struct clk *lpaif_mi2s_osr_clk; +static struct mi2s_hw_params mi2s_params;
+/*
- Returns the OSR to BIT clock divider that would be used with the given
- stream characteristics.
- */
+static uint32_t lpass_cpu_get_bit_div(uint32_t samp_freq, uint32_t bit_width,
uint32_t channels)
+{
- if (channels == 8) {
if (bit_width == 24 &&
((samp_freq != 176400) &&
(samp_freq != 192000)))
return 2;
return 1;
- } else if (channels == 6) {
if ((bit_width == 32 && ((samp_freq == 176400) ||
(samp_freq == 192000))) || (bit_width == 24 &&
(samp_freq == 192000)))
return 1;
else
return 2;
- }
- switch (samp_freq) {
- case 8000:
- case 16000:
- case 32000:
- case 64000:
if (bit_width == 16)
return 4;
Does this test have any value? the behavior is identical in all cases.
break;
- case 48000:
if (bit_width == 24)
return 4;
same.
break;
- case 96000:
- case 192000:
return 4;
- case 176400:
return 2;
- case 11025:
- case 22050:
- case 44100:
- case 88200:
- default:
pr_err("%s: Invalid PCM format given\n", __func__);
return LPASS_INVALID;
- }
- return 4;
+}
+static int lpass_cpu_mi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- uint32_t ret = 0;
- uint32_t bit_act;
- uint16_t bit_div;
- uint32_t bit_width = params_format(params);
- uint32_t channels = params_channels(params);
- uint32_t rate = params_rate(params);
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- struct mi2s_hw_params curr_params;
- bit_act = snd_pcm_format_width(bit_width);
- if (bit_act == LPASS_INVALID) {
dev_err(dai->dev, "%s: Invalid bit width given\n", __func__);
return -EINVAL;
- }
- prtd->pcm_stream_info.bit_width = bit_act;
- curr_params.freq = rate;
- curr_params.channels = channels;
- curr_params.bit_width = bit_act;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* disable SPKR to make sure it will start in sane state */
lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S);
/*
* Set channel info, it will take effect only if SPKR is
* enabled
*/
ret = lpaif_cfg_mi2s_playback_hwparams_channels(channels,
LPAIF_MI2S, bit_act);
- } else {
dev_err(dai->dev, "%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
- /* Make sure our channel setting was success */
- if (ret) {
/* shutdown would be called where we restore stuff */
dev_err(dai->dev, "%s: Channel setting unsuccessful\n",
__func__);
return -EINVAL;
- }
- mi2s_params.freq = rate;
- mi2s_params.channels = channels;
- mi2s_params.bit_width = bit_act;
- ret = lpaif_cfg_mi2s_hwparams_bit_width(bit_width, LPAIF_MI2S);
- if (ret) {
dev_err(dai->dev, "%s: Could not set bit width in HW\n",
__func__);
return -EINVAL;
- }
- bit_div = lpass_cpu_get_bit_div(rate, bit_act, channels);
- if (bit_div == LPASS_INVALID) {
dev_err(dai->dev, "%s: Could not get bit clk divider\n",
__func__);
return -EINVAL;
- }
- ret = clk_set_rate(lpaif_mi2s_osr_clk,
(rate * bit_act * channels * bit_div));
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s osr clk\n",
__func__);
return ret;
- }
- ret = clk_prepare_enable(lpaif_mi2s_osr_clk);
- if (ret) {
dev_err(dai->dev, "%s: error in enabling mi2s osr clk\n",
__func__);
return ret;
- }
- prtd->lpaif_clk.is_osr_clk_enabled = 1;
- ret = clk_set_rate(lpaif_mi2s_bit_clk, rate * bit_act * channels);
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s bit clk\n",
__func__);
return ret;
- }
- ret = clk_prepare_enable(lpaif_mi2s_bit_clk);
- if (ret) {
dev_err(dai->dev, "%s: error in enabling mi2s bit clk\n",
__func__);
return ret;
- }
- prtd->lpaif_clk.is_bit_clk_enabled = 1;
- return 0;
+}
+static int lpass_cpu_mi2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- return 0;
+}
+static int lpass_cpu_mi2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- lpaif_mi2s_osr_clk = clk_get(dai->dev, "mi2s_osr_clk");
- if (IS_ERR(lpaif_mi2s_osr_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n",
__func__);
return PTR_ERR(lpaif_mi2s_osr_clk);
- }
- lpaif_mi2s_bit_clk = clk_get(dai->dev, "mi2s_bit_clk");
- if (IS_ERR(lpaif_mi2s_bit_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n",
__func__);
return PTR_ERR(lpaif_mi2s_bit_clk);
- }
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S);
- } else {
dev_err(dai->dev, "%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
- return 0;
+}
+static void lpass_cpu_mi2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- if (prtd->lpaif_clk.is_osr_clk_enabled)
clk_disable_unprepare(lpaif_mi2s_osr_clk);
- clk_put(lpaif_mi2s_osr_clk);
- if (prtd->lpaif_clk.is_bit_clk_enabled)
clk_disable_unprepare(lpaif_mi2s_bit_clk);
- clk_put(lpaif_mi2s_bit_clk);
+}
+static int lpass_cpu_mi2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{
- return 0;
+}
+static struct snd_soc_dai_ops lpass_cpu_mi2s_ops = {
- .startup = lpass_cpu_mi2s_startup,
- .prepare = lpass_cpu_mi2s_prepare,
- .hw_params = lpass_cpu_mi2s_hw_params,
- .shutdown = lpass_cpu_mi2s_shutdown,
- .set_fmt = lpass_cpu_mi2s_set_fmt,
+};
+static struct snd_soc_dai_driver lpass_cpu_dais[] = {
- {
.playback = {
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16 |
SNDRV_PCM_FMTBIT_S24 |
SNDRV_PCM_FMTBIT_S32,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.ops = &lpass_cpu_mi2s_ops,
.name = "lpass-mi2s-dai"
- },
+};
+static const struct snd_soc_component_driver lpass_cpu_component = {
- .name = DRV_NAME,
+};
+static int lpass_cpu_dai_probe(struct platform_device *pdev) +{
- int ret;
- ret = snd_soc_register_component(&pdev->dev, &lpass_cpu_component,
lpass_cpu_dais, ARRAY_SIZE(lpass_cpu_dais));
- if (ret)
dev_err(&pdev->dev, "%s: error registering soc dais\n",
__func__);
- return ret;
+}
+static int lpass_cpu_dai_remove(struct platform_device *pdev) +{
- snd_soc_unregister_component(&pdev->dev);
- return 0;
+}
+static const struct of_device_id lpass_cpu_dai_dt_match[] = {
- {.compatible = "qcom,lpass-cpu-dai"},
- {}
+};
+static struct platform_driver lpass_cpu_dai_driver = {
- .probe = lpass_cpu_dai_probe,
- .remove = lpass_cpu_dai_remove,
- .driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = lpass_cpu_dai_dt_match,
- },
+}; +module_platform_driver(lpass_cpu_dai_driver);
+MODULE_DESCRIPTION("QCOM LPASS CPU DAI DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_cpu_dai_dt_match); +MODULE_VERSION(DRV_VERSION);
On Wed, November 19, 2014 1:17 pm, Pierre-Louis Bossart wrote:
On 11/19/14, 12:52 PM, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add the CPU DAI driver for the QCOM LPASS SOC.
Change-Id: I64ac4407dd32bb9a3066d4b7427292002eaf5d14 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
sound/soc/qcom/lpass-cpu-dai.c | 307 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 sound/soc/qcom/lpass-cpu-dai.c
diff --git a/sound/soc/qcom/lpass-cpu-dai.c b/sound/soc/qcom/lpass-cpu-dai.c new file mode 100644 index 0000000000000000000000000000000000000000..0693e761e01fd416f52684977bcfaf1579dc259c --- /dev/null +++ b/sound/soc/qcom/lpass-cpu-dai.c @@ -0,0 +1,307 @@ +/*
- Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 and
- only version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "lpass-lpaif.h" +#include "lpass-pcm-mi2s.h"
+#define DRV_NAME "lpass-cpu-dai" +#define DRV_VERSION "1.0"
+#define LPASS_INVALID (-1)
+struct mi2s_hw_params {
- uint8_t channels;
- uint32_t freq;
- uint8_t bit_width;
+};
+static struct clk *lpaif_mi2s_bit_clk; +static struct clk *lpaif_mi2s_osr_clk; +static struct mi2s_hw_params mi2s_params;
+/*
- Returns the OSR to BIT clock divider that would be used with the given
- stream characteristics.
- */
+static uint32_t lpass_cpu_get_bit_div(uint32_t samp_freq, uint32_t bit_width,
uint32_t channels)
+{
- if (channels == 8) {
if (bit_width == 24 &&
((samp_freq != 176400) &&
(samp_freq != 192000)))
return 2;
return 1;
- } else if (channels == 6) {
if ((bit_width == 32 && ((samp_freq == 176400) ||
(samp_freq == 192000))) || (bit_width == 24 &&
(samp_freq == 192000)))
return 1;
else
return 2;
- }
- switch (samp_freq) {
- case 8000:
- case 16000:
- case 32000:
- case 64000:
if (bit_width == 16)
return 4;
Does this test have any value? the behavior is identical in all cases.
break;
- case 48000:
if (bit_width == 24)
return 4;
same.
break;
- case 96000:
- case 192000:
return 4;
- case 176400:
return 2;
- case 11025:
- case 22050:
- case 44100:
- case 88200:
- default:
pr_err("%s: Invalid PCM format given\n", __func__);
return LPASS_INVALID;
- }
- return 4;
+}
+static int lpass_cpu_mi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- uint32_t ret = 0;
- uint32_t bit_act;
- uint16_t bit_div;
- uint32_t bit_width = params_format(params);
- uint32_t channels = params_channels(params);
- uint32_t rate = params_rate(params);
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- struct mi2s_hw_params curr_params;
- bit_act = snd_pcm_format_width(bit_width);
- if (bit_act == LPASS_INVALID) {
dev_err(dai->dev, "%s: Invalid bit width given\n", __func__);
return -EINVAL;
- }
- prtd->pcm_stream_info.bit_width = bit_act;
- curr_params.freq = rate;
- curr_params.channels = channels;
- curr_params.bit_width = bit_act;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* disable SPKR to make sure it will start in sane state */
lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S);
/*
* Set channel info, it will take effect only if SPKR is
* enabled
*/
ret = lpaif_cfg_mi2s_playback_hwparams_channels(channels,
LPAIF_MI2S, bit_act);
- } else {
dev_err(dai->dev, "%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
- /* Make sure our channel setting was success */
- if (ret) {
/* shutdown would be called where we restore stuff */
dev_err(dai->dev, "%s: Channel setting unsuccessful\n",
__func__);
return -EINVAL;
- }
- mi2s_params.freq = rate;
- mi2s_params.channels = channels;
- mi2s_params.bit_width = bit_act;
- ret = lpaif_cfg_mi2s_hwparams_bit_width(bit_width, LPAIF_MI2S);
- if (ret) {
dev_err(dai->dev, "%s: Could not set bit width in HW\n",
__func__);
return -EINVAL;
- }
- bit_div = lpass_cpu_get_bit_div(rate, bit_act, channels);
- if (bit_div == LPASS_INVALID) {
dev_err(dai->dev, "%s: Could not get bit clk divider\n",
__func__);
return -EINVAL;
- }
- ret = clk_set_rate(lpaif_mi2s_osr_clk,
(rate * bit_act * channels * bit_div));
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s osr clk\n",
__func__);
return ret;
- }
- ret = clk_prepare_enable(lpaif_mi2s_osr_clk);
- if (ret) {
dev_err(dai->dev, "%s: error in enabling mi2s osr clk\n",
__func__);
return ret;
- }
- prtd->lpaif_clk.is_osr_clk_enabled = 1;
- ret = clk_set_rate(lpaif_mi2s_bit_clk, rate * bit_act * channels);
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s bit clk\n",
__func__);
return ret;
- }
- ret = clk_prepare_enable(lpaif_mi2s_bit_clk);
- if (ret) {
dev_err(dai->dev, "%s: error in enabling mi2s bit clk\n",
__func__);
return ret;
- }
- prtd->lpaif_clk.is_bit_clk_enabled = 1;
- return 0;
+}
+static int lpass_cpu_mi2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- return 0;
+}
+static int lpass_cpu_mi2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- lpaif_mi2s_osr_clk = clk_get(dai->dev, "mi2s_osr_clk");
- if (IS_ERR(lpaif_mi2s_osr_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n",
__func__);
return PTR_ERR(lpaif_mi2s_osr_clk);
- }
- lpaif_mi2s_bit_clk = clk_get(dai->dev, "mi2s_bit_clk");
- if (IS_ERR(lpaif_mi2s_bit_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n",
__func__);
return PTR_ERR(lpaif_mi2s_bit_clk);
- }
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S);
- } else {
dev_err(dai->dev, "%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
- return 0;
+}
+static void lpass_cpu_mi2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- if (prtd->lpaif_clk.is_osr_clk_enabled)
clk_disable_unprepare(lpaif_mi2s_osr_clk);
- clk_put(lpaif_mi2s_osr_clk);
- if (prtd->lpaif_clk.is_bit_clk_enabled)
clk_disable_unprepare(lpaif_mi2s_bit_clk);
- clk_put(lpaif_mi2s_bit_clk);
+}
+static int lpass_cpu_mi2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{
- return 0;
+}
+static struct snd_soc_dai_ops lpass_cpu_mi2s_ops = {
- .startup = lpass_cpu_mi2s_startup,
- .prepare = lpass_cpu_mi2s_prepare,
- .hw_params = lpass_cpu_mi2s_hw_params,
- .shutdown = lpass_cpu_mi2s_shutdown,
- .set_fmt = lpass_cpu_mi2s_set_fmt,
+};
+static struct snd_soc_dai_driver lpass_cpu_dais[] = {
- {
.playback = {
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16 |
SNDRV_PCM_FMTBIT_S24 |
SNDRV_PCM_FMTBIT_S32,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.ops = &lpass_cpu_mi2s_ops,
.name = "lpass-mi2s-dai"
- },
+};
+static const struct snd_soc_component_driver lpass_cpu_component = {
- .name = DRV_NAME,
+};
+static int lpass_cpu_dai_probe(struct platform_device *pdev) +{
- int ret;
- ret = snd_soc_register_component(&pdev->dev, &lpass_cpu_component,
lpass_cpu_dais, ARRAY_SIZE(lpass_cpu_dais));
- if (ret)
dev_err(&pdev->dev, "%s: error registering soc dais\n",
__func__);
- return ret;
+}
+static int lpass_cpu_dai_remove(struct platform_device *pdev) +{
- snd_soc_unregister_component(&pdev->dev);
- return 0;
+}
+static const struct of_device_id lpass_cpu_dai_dt_match[] = {
- {.compatible = "qcom,lpass-cpu-dai"},
- {}
+};
+static struct platform_driver lpass_cpu_dai_driver = {
- .probe = lpass_cpu_dai_probe,
- .remove = lpass_cpu_dai_remove,
- .driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = lpass_cpu_dai_dt_match,
- },
+}; +module_platform_driver(lpass_cpu_dai_driver);
+MODULE_DESCRIPTION("QCOM LPASS CPU DAI DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_cpu_dai_dt_match); +MODULE_VERSION(DRV_VERSION);
-- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Thank you for your comments. I will separately address each comment shortly.
On Wed, Nov 19, 2014 at 07:52:44PM +0100, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add the CPU DAI driver for the QCOM LPASS SOC.
Change-Id: I64ac4407dd32bb9a3066d4b7427292002eaf5d14 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
[...]
+#include <linux/module.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "lpass-lpaif.h" +#include "lpass-pcm-mi2s.h"
This header and the associated structures are not added until 5/9: "ASoC: ipq806x: Add I2S PCM platform driver"...
+#define DRV_NAME "lpass-cpu-dai" +#define DRV_VERSION "1.0"
+#define LPASS_INVALID (-1)
+struct mi2s_hw_params {
- uint8_t channels;
- uint32_t freq;
- uint8_t bit_width;
+};
This struct, the static global instance of it below ('mi2s_params'), and the additional use of it in lpass_cpu_mi2s_hw_params() ('curr_params') are only ever written, never read.
+static struct clk *lpaif_mi2s_bit_clk; +static struct clk *lpaif_mi2s_osr_clk;
It would seem more logical to me to put these in allocated private driver data for the DAI, managed from (struct snd_soc_dai_driver).probe/remove.
+static struct mi2s_hw_params mi2s_params;
[...]
+static int lpass_cpu_mi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
[...]
- ret = clk_set_rate(lpaif_mi2s_osr_clk,
(rate * bit_act * channels * bit_div));
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s osr clk\n",
__func__);
return ret;
- }
- ret = clk_prepare_enable(lpaif_mi2s_osr_clk);
- if (ret) {
dev_err(dai->dev, "%s: error in enabling mi2s osr clk\n",
__func__);
return ret;
- }
- prtd->lpaif_clk.is_osr_clk_enabled = 1;
- ret = clk_set_rate(lpaif_mi2s_bit_clk, rate * bit_act * channels);
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s bit clk\n",
__func__);
return ret;
clk_disable_unprepare(lpaif_mi2s_osr_clk)?
- }
- ret = clk_prepare_enable(lpaif_mi2s_bit_clk);
- if (ret) {
dev_err(dai->dev, "%s: error in enabling mi2s bit clk\n",
__func__);
return ret;
clk_disable_unprepare(lpaif_mi2s_bit_clk)? clk_disable_unprepare(lpaif_mi2s_osr_clk)?
- }
- prtd->lpaif_clk.is_bit_clk_enabled = 1;
- return 0;
+}
+static int lpass_cpu_mi2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- return 0;
+}
Isn't this ((struct snd_soc_dai_ops).prepare) optional?
+static int lpass_cpu_mi2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- lpaif_mi2s_osr_clk = clk_get(dai->dev, "mi2s_osr_clk");
- if (IS_ERR(lpaif_mi2s_osr_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n",
__func__);
return PTR_ERR(lpaif_mi2s_osr_clk);
- }
- lpaif_mi2s_bit_clk = clk_get(dai->dev, "mi2s_bit_clk");
- if (IS_ERR(lpaif_mi2s_bit_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n",
__func__);
return PTR_ERR(lpaif_mi2s_bit_clk);
clk_put(lpaif_mi2s_osr_clk)?
- }
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S);
- } else {
dev_err(dai->dev, "%s: Invalid stream direction\n", __func__);
return -EINVAL;
clk_put(lpaif_mi2s_bit_clk)? clk_put(lpaif_mi2s_osr_clk)?
- }
- return 0;
+}
+static void lpass_cpu_mi2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- if (prtd->lpaif_clk.is_osr_clk_enabled)
clk_disable_unprepare(lpaif_mi2s_osr_clk);
This behavior is a bit odd. If you clk_prepare_enable() the clocks in .hw_params, shouldn't you clk_disable_unprepare() in .hw_free? Then you wouldn't need these booleans, or the associated lpaif_clk struct.
- clk_put(lpaif_mi2s_osr_clk);
- if (prtd->lpaif_clk.is_bit_clk_enabled)
clk_disable_unprepare(lpaif_mi2s_bit_clk);
- clk_put(lpaif_mi2s_bit_clk);
+}
-Courtney
On 11/19/2014 07:52 PM, Kenneth Westfield wrote: [...]
+static struct clk *lpaif_mi2s_bit_clk; +static struct clk *lpaif_mi2s_osr_clk; +static struct mi2s_hw_params mi2s_params;
Again, no global state please. [...]
+static int lpass_cpu_mi2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- return 0;
+}
[...]
+static int lpass_cpu_mi2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{
- return 0;
+}
There is no need to implement empty dummy functions the core handles it just fine if the callbacks are NULL.
[...]
+static struct snd_soc_dai_driver lpass_cpu_dais[] = {
- {
.playback = {
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16 |
SNDRV_PCM_FMTBIT_S24 |
SNDRV_PCM_FMTBIT_S32,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.ops = &lpass_cpu_mi2s_ops,
.name = "lpass-mi2s-dai"
- },
+};
If there is only one DAI no need to make this a array.
+static const struct snd_soc_component_driver lpass_cpu_component = {
- .name = DRV_NAME,
+};
+static int lpass_cpu_dai_probe(struct platform_device *pdev) +{
- int ret;
- ret = snd_soc_register_component(&pdev->dev, &lpass_cpu_component,
lpass_cpu_dais, ARRAY_SIZE(lpass_cpu_dais));
devm_
- if (ret)
dev_err(&pdev->dev, "%s: error registering soc dais\n",
__func__);
- return ret;
+}
[...]
On Wed, Nov 19, 2014 at 10:52:44AM -0800, Kenneth Westfield wrote:
- if (channels == 8) {
if (bit_width == 24 &&
((samp_freq != 176400) &&
(samp_freq != 192000)))
return 2;
return 1;
Coding style - there's more brackets than are needed coupled with some strange indentation (eg, the second samp_freq line being indented to the outside bracket when it's still within that bracket). Use of switch statements would probably help, at least on channels.
+static int lpass_cpu_mi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- uint32_t ret = 0;
- uint32_t bit_act;
- uint16_t bit_div;
- uint32_t bit_width = params_format(params);
- uint32_t channels = params_channels(params);
- uint32_t rate = params_rate(params);
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- struct mi2s_hw_params curr_params;
- bit_act = snd_pcm_format_width(bit_width);
- if (bit_act == LPASS_INVALID) {
snd_pcm_format_width() returns an error code on error, LPASS_INVALID is not an error code. Check the return value for error codes...
dev_err(dai->dev, "%s: Invalid bit width given\n", __func__);
return -EINVAL;
...and just return them.
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* disable SPKR to make sure it will start in sane state */
lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S);
Shouldn't we be doing this on probe and/or resume if it's required?
/*
* Set channel info, it will take effect only if SPKR is
* enabled
*/
ret = lpaif_cfg_mi2s_playback_hwparams_channels(channels,
LPAIF_MI2S, bit_act);
- } else {
dev_err(dai->dev, "%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
If the device only supports playback no need to have any conditional code here, the core will prevent capture being started.
- ret = clk_set_rate(lpaif_mi2s_osr_clk,
(rate * bit_act * channels * bit_div));
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s osr clk\n",
__func__);
return ret;
- }
- ret = clk_prepare_enable(lpaif_mi2s_osr_clk);
- if (ret) {
dev_err(dai->dev, "%s: error in enabling mi2s osr clk\n",
__func__);
return ret;
- }
- prtd->lpaif_clk.is_osr_clk_enabled = 1;
Coding style, more blank lines between blocks here. Also not clear why we're tracking if the clock is enabled when we do it unconditonally.
+static int lpass_cpu_mi2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- return 0;
+}
Remove empty functions.
+static int lpass_cpu_mi2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- lpaif_mi2s_osr_clk = clk_get(dai->dev, "mi2s_osr_clk");
- if (IS_ERR(lpaif_mi2s_osr_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n",
__func__);
return PTR_ERR(lpaif_mi2s_osr_clk);
- }
No, request resources in probe(). That way deferred probe works and we don't get mysterious errors at runtime if things go wrong.
From: Kenneth Westfield kwestfie@codeaurora.org
Add PCM platform driver for the LPASS I2S port.
Change-Id: If6516fb615b16551817fd9248c1c704fe521fb32 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-pcm-mi2s.c | 390 ++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass-pcm-mi2s.h | 40 +++++ 2 files changed, 430 insertions(+) create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.c create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.h
diff --git a/sound/soc/qcom/lpass-pcm-mi2s.c b/sound/soc/qcom/lpass-pcm-mi2s.c new file mode 100644 index 0000000000000000000000000000000000000000..110b088d156c6d06cbe054920fc63fd064a0ac45 --- /dev/null +++ b/sound/soc/qcom/lpass-pcm-mi2s.c @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <sound/soc.h> +#include "lpass-lpaif.h" +#include "lpass-pcm-mi2s.h" + +#define DRV_NAME "lpass-pcm-mi2s" +#define DRV_VERSION "1.0" + +/* MI2S Hw params */ +#define LPASS_MI2S_NO_OF_PERIODS (65) +#define LPASS_MI2S_PERIOD_BYTES_MIN (8064) +#define LPASS_MI2S_BUFF_SIZE (LPASS_MI2S_PERIOD_BYTES_MIN * \ + LPASS_MI2S_NO_OF_PERIODS) + +static struct snd_pcm_hardware lpass_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = LPASS_MI2S_BUFF_SIZE, + .period_bytes_max = (LPASS_MI2S_BUFF_SIZE) / 2, + .period_bytes_min = LPASS_MI2S_PERIOD_BYTES_MIN, + .periods_min = LPASS_MI2S_NO_OF_PERIODS, + .periods_max = LPASS_MI2S_NO_OF_PERIODS, + .fifo_size = 0, +}; + +static int lpass_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + size = lpass_pcm_hardware_playback.buffer_bytes_max; + } else { + pr_err("%s: Invalid stream direction\n", __func__); + return -EINVAL; + } + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + pr_err("%s: Could not allocate DMA buffer\n", __func__); + return -ENOMEM; + } + buf->bytes = size; + + return 0; +} + +static void lpass_pcm_free_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + substream = pcm->streams[stream].substream; + buf = &substream->dma_buffer; + if (buf->area) { + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + } + buf->area = NULL; +} + +static irqreturn_t lpass_pcm_mi2s_irq(int intrsrc, void *data) +{ + int dma_ch; + uint32_t ret = IRQ_NONE; + uint32_t has_xrun, pending; + + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct lpass_runtime_data_t *prtd = runtime->private_data; + + if (prtd) { + dma_ch = prtd->lpaif_info.dma_ch; + } else { + pr_debug("%s: received interrupt w/o runtime\n", __func__); + return IRQ_NONE; + } + + pending = intrsrc & (LPAIF_UNDER_CH(dma_ch) | LPAIF_PER_CH(dma_ch) | + LPAIF_ERR_CH(dma_ch)); + + has_xrun = pending & LPAIF_UNDER_CH(dma_ch); + + if (unlikely(has_xrun) && substream->runtime && + snd_pcm_running(substream)) { + pr_debug("%s: xrun warning\n", __func__); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + pending &= ~LPAIF_UNDER_CH(dma_ch); + ret = IRQ_HANDLED; + } + + if (pending & LPAIF_PER_CH(dma_ch)) { + if (++prtd->pcm_stream_info.period_index >= runtime->periods) + prtd->pcm_stream_info.period_index = 0; + snd_pcm_period_elapsed(substream); + pending &= ~LPAIF_PER_CH(dma_ch); + ret = IRQ_HANDLED; + } + + if (pending & LPAIF_UNDER_CH(dma_ch)) { + snd_pcm_period_elapsed(substream); + pr_debug("%s: xrun warning\n", __func__); + ret = IRQ_HANDLED; + } + + if (pending & LPAIF_ERR_CH(dma_ch)) { + pr_debug("%s: Bus access warning\n", __func__); + ret = IRQ_HANDLED; + } + + return ret; +} + +static snd_pcm_uframes_t lpass_pcm_mi2s_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct lpass_runtime_data_t *prtd = runtime->private_data; + snd_pcm_uframes_t offset; + + offset = prtd->pcm_stream_info.period_index * runtime->period_size; + + return offset >= (runtime->buffer_size) ? 0 : offset; +} + +static int lpass_pcm_mi2s_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); +} + +static int lpass_pcm_mi2s_prepare(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct lpaif_dai_dma_params dma_params; + struct lpass_runtime_data_t *prtd = runtime->private_data; + + if (!prtd) { + pr_err("%s: Error in getting runtime data\n", __func__); + return -EINVAL; + } + + /* + * This is the case for under\over-run, we have already + * configured the DMA registers for this stream + */ + if (prtd->pcm_stream_info.pcm_prepare_start) + return 0; + + lpaif_dma_stop(prtd->lpaif_info.dma_ch); + prtd->pcm_stream_info.pcm_prepare_start = 1; + prtd->lpaif_info.lpa_if_dma_start = 0; + + memset(&dma_params, 0, sizeof(dma_params)); + dma_params.src_start = runtime->dma_addr; + dma_params.buffer_size = snd_pcm_lib_buffer_bytes(substream); + dma_params.period_size = snd_pcm_lib_period_bytes(substream); + dma_params.channels = runtime->channels; + ret = lpaif_cfg_dma(prtd->lpaif_info.dma_ch, &dma_params, + prtd->pcm_stream_info.bit_width, + 1 /*enable intr*/); + if (ret) { + pr_err("%s: Error in configuring DMA\n", __func__); + return -EINVAL; + } + + lpaif_register_dma_irq_handler(prtd->lpaif_info.dma_ch, + lpass_pcm_mi2s_irq, substream); + + return 0; +} + +static int lpass_pcm_mi2s_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct lpass_runtime_data_t *prtd = runtime->private_data; + + if (prtd) { + lpaif_dai_stop(prtd->lpaif_info.dma_ch); + lpaif_unregister_dma_irq_handler(prtd->lpaif_info.dma_ch); + kfree(prtd); + } + + return 0; +} + +static int lpass_pcm_mi2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + lpaif_cfg_i2s_playback(1, 0, LPAIF_MI2S); + } else { + pr_err("%s: Invalid stream direction\n", __func__); + ret = -EINVAL; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S); + } else { + pr_err("%s: Invalid stream direction\n", __func__); + ret = -EINVAL; + } + break; + default: + pr_err("%s: Invalid trigger command given\n", __func__); + ret = -EINVAL; + break; + } + return ret; +} + +static int lpass_pcm_mi2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct lpass_runtime_data_t *prtd = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + prtd->pcm_stream_info.pcm_prepare_start = 0; + prtd->pcm_stream_info.period_index = 0; + return 0; +} + +static int lpass_pcm_mi2s_open(struct snd_pcm_substream *substream) +{ + int ret; + struct lpass_runtime_data_t *prtd; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->dma_bytes = + lpass_pcm_hardware_playback.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &lpass_pcm_hardware_playback); + } else { + pr_err("%s: Invalid stream direction\n", __func__); + return -EINVAL; + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_err("%s: snd_pcm_hw_constraint_integer failed\n", __func__); + return -EINVAL; + } + + prtd = kzalloc(sizeof(struct lpass_runtime_data_t), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + prtd->pcm_stream_info.pcm_prepare_start = 0; + prtd->lpaif_clk.is_bit_clk_enabled = 0; + prtd->lpaif_clk.is_osr_clk_enabled = 0; + prtd->lpaif_info.dma_ch = LPAIF_MI2S_DMA_RD_CH; + + prtd->pcm_stream_info.substream = substream; + runtime->private_data = prtd; + + return 0; +} + +static struct snd_pcm_ops lpass_asoc_pcm_mi2s_ops = { + .open = lpass_pcm_mi2s_open, + .hw_params = lpass_pcm_mi2s_hw_params, + .trigger = lpass_pcm_mi2s_trigger, + .ioctl = snd_pcm_lib_ioctl, + .close = lpass_pcm_mi2s_close, + .prepare = lpass_pcm_mi2s_prepare, + .mmap = lpass_pcm_mi2s_mmap, + .pointer = lpass_pcm_mi2s_pointer, +}; + +static void lpass_asoc_pcm_mi2s_free(struct snd_pcm *pcm) +{ + lpass_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +} + +static int lpass_asoc_pcm_mi2s_new(struct snd_soc_pcm_runtime *prtd) +{ + struct snd_card *card = prtd->card->snd_card; + struct snd_pcm *pcm = prtd->pcm; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &card->dev->coherent_dma_mask; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = lpass_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + } else { + pr_err("%s: Invalid stream direction\n", __func__); + return -EINVAL; + } + + return ret; +} + +static struct snd_soc_platform_driver lpass_asoc_pcm_mi2s_platform = { + .ops = &lpass_asoc_pcm_mi2s_ops, + .pcm_new = lpass_asoc_pcm_mi2s_new, + .pcm_free = lpass_asoc_pcm_mi2s_free, +}; + +static int lpass_pcm_mi2s_driver_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_platform(&pdev->dev, + &lpass_asoc_pcm_mi2s_platform); + if (ret) + dev_err(&pdev->dev, "%s: Failed to register pcm device: %d\n", + __func__, ret); + + return ret; +} + +static int lpass_pcm_mi2s_driver_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static const struct of_device_id lpass_pcm_mi2s_dt_match[] = { + { .compatible = "qcom,lpass-pcm-mi2s", }, + {} +}; + +static struct platform_driver lpass_pcm_mi2s_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = lpass_pcm_mi2s_dt_match, + }, + .probe = lpass_pcm_mi2s_driver_probe, + .remove = lpass_pcm_mi2s_driver_remove, +}; +module_platform_driver(lpass_pcm_mi2s_driver); + +MODULE_DESCRIPTION("LPASS PCM MI2S Platform Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_pcm_mi2s_dt_match); +MODULE_VERSION(DRV_VERSION); diff --git a/sound/soc/qcom/lpass-pcm-mi2s.h b/sound/soc/qcom/lpass-pcm-mi2s.h new file mode 100644 index 0000000000000000000000000000000000000000..edd43c4d419d1999babe20d4e6f58c77df38c6e1 --- /dev/null +++ b/sound/soc/qcom/lpass-pcm-mi2s.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LPASS_PCM_MI2S_H +#define _LPAS_PCM_MI2S_H + +struct lpass_pcm_stream_t { + uint8_t pcm_prepare_start; + uint32_t period_index; + struct snd_pcm_substream *substream; + uint32_t bit_width; +}; + +struct lpass_lpaif_t { + uint8_t lpa_if_dma_start; + uint8_t dma_ch; +}; + +struct lpass_lpaif_clk_t { + uint8_t is_bit_clk_enabled; + uint8_t is_osr_clk_enabled; +}; + +struct lpass_runtime_data_t { + struct lpass_pcm_stream_t pcm_stream_info; + struct lpass_lpaif_t lpaif_info; + struct lpass_lpaif_clk_t lpaif_clk; +}; + +#endif /* _LPASS_PCM_MI2S_H */
On 11/19/14, 12:52 PM, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add PCM platform driver for the LPASS I2S port.
Change-Id: If6516fb615b16551817fd9248c1c704fe521fb32 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
sound/soc/qcom/lpass-pcm-mi2s.c | 390 ++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass-pcm-mi2s.h | 40 +++++ 2 files changed, 430 insertions(+) create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.c create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.h
diff --git a/sound/soc/qcom/lpass-pcm-mi2s.c b/sound/soc/qcom/lpass-pcm-mi2s.c new file mode 100644 index 0000000000000000000000000000000000000000..110b088d156c6d06cbe054920fc63fd064a0ac45 --- /dev/null +++ b/sound/soc/qcom/lpass-pcm-mi2s.c @@ -0,0 +1,390 @@ +/*
- Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 and
- only version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <sound/soc.h> +#include "lpass-lpaif.h" +#include "lpass-pcm-mi2s.h"
+#define DRV_NAME "lpass-pcm-mi2s" +#define DRV_VERSION "1.0"
+/* MI2S Hw params */ +#define LPASS_MI2S_NO_OF_PERIODS (65) +#define LPASS_MI2S_PERIOD_BYTES_MIN (8064) +#define LPASS_MI2S_BUFF_SIZE (LPASS_MI2S_PERIOD_BYTES_MIN * \
LPASS_MI2S_NO_OF_PERIODS)
+static struct snd_pcm_hardware lpass_pcm_hardware_playback = {
- .info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16 |
SNDRV_PCM_FMTBIT_S24 |
SNDRV_PCM_FMTBIT_S32,
- .rates = SNDRV_PCM_RATE_8000_192000,
- .rate_min = 8000,
- .rate_max = 192000,
- .channels_min = 2,
- .channels_max = 8,
- .buffer_bytes_max = LPASS_MI2S_BUFF_SIZE,
- .period_bytes_max = (LPASS_MI2S_BUFF_SIZE) / 2,
- .period_bytes_min = LPASS_MI2S_PERIOD_BYTES_MIN,
- .periods_min = LPASS_MI2S_NO_OF_PERIODS,
- .periods_max = LPASS_MI2S_NO_OF_PERIODS,
Did you really mean 65 periods min and max? .periods_min is typically 1..4, 16 in rare cases.
- .fifo_size = 0,
+};
+static int lpass_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{
- struct snd_pcm_substream *substream = pcm->streams[stream].substream;
- struct snd_dma_buffer *buf = &substream->dma_buffer;
- size_t size;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
size = lpass_pcm_hardware_playback.buffer_bytes_max;
- } else {
pr_err("%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
- buf->dev.type = SNDRV_DMA_TYPE_DEV;
- buf->dev.dev = pcm->card->dev;
- buf->private_data = NULL;
- buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
- if (!buf->area) {
pr_err("%s: Could not allocate DMA buffer\n", __func__);
return -ENOMEM;
- }
- buf->bytes = size;
- return 0;
+}
+static void lpass_pcm_free_dma_buffer(struct snd_pcm *pcm, int stream) +{
- struct snd_pcm_substream *substream;
- struct snd_dma_buffer *buf;
- substream = pcm->streams[stream].substream;
- buf = &substream->dma_buffer;
- if (buf->area) {
dma_free_coherent(pcm->card->dev, buf->bytes,
buf->area, buf->addr);
- }
- buf->area = NULL;
+}
+static irqreturn_t lpass_pcm_mi2s_irq(int intrsrc, void *data) +{
- int dma_ch;
- uint32_t ret = IRQ_NONE;
- uint32_t has_xrun, pending;
- struct snd_pcm_substream *substream = data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- if (prtd) {
dma_ch = prtd->lpaif_info.dma_ch;
- } else {
pr_debug("%s: received interrupt w/o runtime\n", __func__);
return IRQ_NONE;
- }
- pending = intrsrc & (LPAIF_UNDER_CH(dma_ch) | LPAIF_PER_CH(dma_ch) |
LPAIF_ERR_CH(dma_ch));
- has_xrun = pending & LPAIF_UNDER_CH(dma_ch);
- if (unlikely(has_xrun) && substream->runtime &&
snd_pcm_running(substream)) {
pr_debug("%s: xrun warning\n", __func__);
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
pending &= ~LPAIF_UNDER_CH(dma_ch);
ret = IRQ_HANDLED;
- }
- if (pending & LPAIF_PER_CH(dma_ch)) {
if (++prtd->pcm_stream_info.period_index >= runtime->periods)
prtd->pcm_stream_info.period_index = 0;
snd_pcm_period_elapsed(substream);
pending &= ~LPAIF_PER_CH(dma_ch);
ret = IRQ_HANDLED;
- }
- if (pending & LPAIF_UNDER_CH(dma_ch)) {
snd_pcm_period_elapsed(substream);
pr_debug("%s: xrun warning\n", __func__);
ret = IRQ_HANDLED;
- }
- if (pending & LPAIF_ERR_CH(dma_ch)) {
pr_debug("%s: Bus access warning\n", __func__);
ret = IRQ_HANDLED;
- }
- return ret;
+}
+static snd_pcm_uframes_t lpass_pcm_mi2s_pointer(
struct snd_pcm_substream *substream)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- snd_pcm_uframes_t offset;
- offset = prtd->pcm_stream_info.period_index * runtime->period_size;
- return offset >= (runtime->buffer_size) ? 0 : offset;
+}
+static int lpass_pcm_mi2s_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- return dma_mmap_coherent(substream->pcm->card->dev, vma,
runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
+}
+static int lpass_pcm_mi2s_prepare(struct snd_pcm_substream *substream) +{
- int ret;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpaif_dai_dma_params dma_params;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- if (!prtd) {
pr_err("%s: Error in getting runtime data\n", __func__);
return -EINVAL;
- }
- /*
* This is the case for under\over-run, we have already
* configured the DMA registers for this stream
*/
- if (prtd->pcm_stream_info.pcm_prepare_start)
return 0;
- lpaif_dma_stop(prtd->lpaif_info.dma_ch);
- prtd->pcm_stream_info.pcm_prepare_start = 1;
- prtd->lpaif_info.lpa_if_dma_start = 0;
- memset(&dma_params, 0, sizeof(dma_params));
- dma_params.src_start = runtime->dma_addr;
- dma_params.buffer_size = snd_pcm_lib_buffer_bytes(substream);
- dma_params.period_size = snd_pcm_lib_period_bytes(substream);
- dma_params.channels = runtime->channels;
- ret = lpaif_cfg_dma(prtd->lpaif_info.dma_ch, &dma_params,
prtd->pcm_stream_info.bit_width,
1 /*enable intr*/);
- if (ret) {
pr_err("%s: Error in configuring DMA\n", __func__);
return -EINVAL;
- }
- lpaif_register_dma_irq_handler(prtd->lpaif_info.dma_ch,
lpass_pcm_mi2s_irq, substream);
- return 0;
+}
+static int lpass_pcm_mi2s_close(struct snd_pcm_substream *substream) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- if (prtd) {
lpaif_dai_stop(prtd->lpaif_info.dma_ch);
lpaif_unregister_dma_irq_handler(prtd->lpaif_info.dma_ch);
kfree(prtd);
- }
- return 0;
+}
+static int lpass_pcm_mi2s_trigger(struct snd_pcm_substream *substream, int cmd) +{
- int ret = 0;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
lpaif_cfg_i2s_playback(1, 0, LPAIF_MI2S);
} else {
pr_err("%s: Invalid stream direction\n", __func__);
ret = -EINVAL;
}
break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
lpaif_cfg_i2s_playback(0, 0, LPAIF_MI2S);
} else {
pr_err("%s: Invalid stream direction\n", __func__);
ret = -EINVAL;
}
break;
- default:
pr_err("%s: Invalid trigger command given\n", __func__);
ret = -EINVAL;
break;
- }
- return ret;
+}
+static int lpass_pcm_mi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
- prtd->pcm_stream_info.pcm_prepare_start = 0;
- prtd->pcm_stream_info.period_index = 0;
- return 0;
+}
+static int lpass_pcm_mi2s_open(struct snd_pcm_substream *substream) +{
- int ret;
- struct lpass_runtime_data_t *prtd;
- struct snd_pcm_runtime *runtime = substream->runtime;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
runtime->dma_bytes =
lpass_pcm_hardware_playback.buffer_bytes_max;
snd_soc_set_runtime_hwparams(substream,
&lpass_pcm_hardware_playback);
- } else {
pr_err("%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
- ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
- if (ret < 0) {
pr_err("%s: snd_pcm_hw_constraint_integer failed\n", __func__);
return -EINVAL;
- }
- prtd = kzalloc(sizeof(struct lpass_runtime_data_t), GFP_KERNEL);
- if (prtd == NULL)
return -ENOMEM;
- prtd->pcm_stream_info.pcm_prepare_start = 0;
- prtd->lpaif_clk.is_bit_clk_enabled = 0;
- prtd->lpaif_clk.is_osr_clk_enabled = 0;
- prtd->lpaif_info.dma_ch = LPAIF_MI2S_DMA_RD_CH;
- prtd->pcm_stream_info.substream = substream;
- runtime->private_data = prtd;
- return 0;
+}
+static struct snd_pcm_ops lpass_asoc_pcm_mi2s_ops = {
- .open = lpass_pcm_mi2s_open,
- .hw_params = lpass_pcm_mi2s_hw_params,
- .trigger = lpass_pcm_mi2s_trigger,
- .ioctl = snd_pcm_lib_ioctl,
- .close = lpass_pcm_mi2s_close,
- .prepare = lpass_pcm_mi2s_prepare,
- .mmap = lpass_pcm_mi2s_mmap,
- .pointer = lpass_pcm_mi2s_pointer,
+};
+static void lpass_asoc_pcm_mi2s_free(struct snd_pcm *pcm) +{
- lpass_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
+}
+static int lpass_asoc_pcm_mi2s_new(struct snd_soc_pcm_runtime *prtd) +{
- struct snd_card *card = prtd->card->snd_card;
- struct snd_pcm *pcm = prtd->pcm;
- int ret = 0;
- if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
- if (!card->dev->dma_mask)
card->dev->dma_mask = &card->dev->coherent_dma_mask;
- if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = lpass_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
- } else {
pr_err("%s: Invalid stream direction\n", __func__);
return -EINVAL;
- }
- return ret;
+}
+static struct snd_soc_platform_driver lpass_asoc_pcm_mi2s_platform = {
- .ops = &lpass_asoc_pcm_mi2s_ops,
- .pcm_new = lpass_asoc_pcm_mi2s_new,
- .pcm_free = lpass_asoc_pcm_mi2s_free,
+};
+static int lpass_pcm_mi2s_driver_probe(struct platform_device *pdev) +{
- int ret;
- ret = snd_soc_register_platform(&pdev->dev,
&lpass_asoc_pcm_mi2s_platform);
- if (ret)
dev_err(&pdev->dev, "%s: Failed to register pcm device: %d\n",
__func__, ret);
- return ret;
+}
+static int lpass_pcm_mi2s_driver_remove(struct platform_device *pdev) +{
- snd_soc_unregister_platform(&pdev->dev);
- return 0;
+}
+static const struct of_device_id lpass_pcm_mi2s_dt_match[] = {
- { .compatible = "qcom,lpass-pcm-mi2s", },
- {}
+};
+static struct platform_driver lpass_pcm_mi2s_driver = {
- .driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = lpass_pcm_mi2s_dt_match,
- },
- .probe = lpass_pcm_mi2s_driver_probe,
- .remove = lpass_pcm_mi2s_driver_remove,
+}; +module_platform_driver(lpass_pcm_mi2s_driver);
+MODULE_DESCRIPTION("LPASS PCM MI2S Platform Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_pcm_mi2s_dt_match); +MODULE_VERSION(DRV_VERSION); diff --git a/sound/soc/qcom/lpass-pcm-mi2s.h b/sound/soc/qcom/lpass-pcm-mi2s.h new file mode 100644 index 0000000000000000000000000000000000000000..edd43c4d419d1999babe20d4e6f58c77df38c6e1 --- /dev/null +++ b/sound/soc/qcom/lpass-pcm-mi2s.h @@ -0,0 +1,40 @@ +/*
- Copyright (c) 2014 The Linux Foundation. All rights reserved.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 and
- only version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef _LPASS_PCM_MI2S_H +#define _LPAS_PCM_MI2S_H
+struct lpass_pcm_stream_t {
- uint8_t pcm_prepare_start;
- uint32_t period_index;
- struct snd_pcm_substream *substream;
- uint32_t bit_width;
+};
+struct lpass_lpaif_t {
- uint8_t lpa_if_dma_start;
- uint8_t dma_ch;
+};
+struct lpass_lpaif_clk_t {
- uint8_t is_bit_clk_enabled;
- uint8_t is_osr_clk_enabled;
+};
+struct lpass_runtime_data_t {
- struct lpass_pcm_stream_t pcm_stream_info;
- struct lpass_lpaif_t lpaif_info;
- struct lpass_lpaif_clk_t lpaif_clk;
+};
+#endif /* _LPASS_PCM_MI2S_H */
On Wed, Nov 19, 2014 at 10:52:45AM -0800, Kenneth Westfield wrote:
- .periods_min = LPASS_MI2S_NO_OF_PERIODS,
- .periods_max = LPASS_MI2S_NO_OF_PERIODS,
As Pierre said this is really odd - it appears to be just a single possible value.
+static irqreturn_t lpass_pcm_mi2s_irq(int intrsrc, void *data) +{
- int dma_ch;
- uint32_t ret = IRQ_NONE;
- uint32_t has_xrun, pending;
- struct snd_pcm_substream *substream = data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
Here we rely on runtime.
- if (prtd) {
dma_ch = prtd->lpaif_info.dma_ch;
- } else {
pr_debug("%s: received interrupt w/o runtime\n", __func__);
return IRQ_NONE;
- }
Here we print (as a debug message, not a dev_err()) an error saying we lack a runtime (actually it's private data).
- if (unlikely(has_xrun) && substream->runtime &&
snd_pcm_running(substream)) {
Here we check if runtime (which we already dereferenced) is non-NULL.
- if (pending & LPAIF_UNDER_CH(dma_ch)) {
snd_pcm_period_elapsed(substream);
pr_debug("%s: xrun warning\n", __func__);
ret = IRQ_HANDLED;
- }
- if (pending & LPAIF_ERR_CH(dma_ch)) {
pr_debug("%s: Bus access warning\n", __func__);
ret = IRQ_HANDLED;
- }
These errors should be logged as such.
+static int lpass_pcm_mi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct lpass_runtime_data_t *prtd = runtime->private_data;
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
- prtd->pcm_stream_info.pcm_prepare_start = 0;
- prtd->pcm_stream_info.period_index = 0;
- return 0;
+}
This appears to ignore the params passed so hw_params() seems like the wrong place - open()?
From: Kenneth Westfield kwestfie@codeaurora.org
Add machine driver for the IPQ806X LPASS SOC.
Change-Id: Ica26398fafd3098cdd12dcf45ebccec2ad820002 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/ipq806x.c | 221 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 sound/soc/qcom/ipq806x.c
diff --git a/sound/soc/qcom/ipq806x.c b/sound/soc/qcom/ipq806x.c new file mode 100644 index 0000000000000000000000000000000000000000..e973bd71fa7fe117e73b1b562ec2758b3379e98f --- /dev/null +++ b/sound/soc/qcom/ipq806x.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/module.h> +#include <sound/soc.h> + +#define DRV_NAME "ipq806x-asoc-snd" +#define DRV_VERSION "1.0" + +struct ipq806x_asoc_mach_data { + struct clk *ahbix_clk; + struct gpio_desc *dac_desc; +}; + +static struct snd_soc_dai_link ipq_snd_dai[] = { + /* Front end DAI Links */ + { + .name = "IPQ806x Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "lpass-cpu-dai", + .platform_name = "lpass-pcm-mi2s", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, +}; + + +static int ipq806x_populate_dai_link_component_of_node( + struct snd_soc_card *card) +{ + int i, index; + int ret = 0; + struct device *cdev = card->dev; + struct snd_soc_dai_link *dai_link = card->dai_link; + struct device_node *np; + + if (!cdev) { + pr_err("%s: Sound card device memory NULL\n", __func__); + return -ENODEV; + } + + for (i = 0; i < card->num_links; i++) { + if (dai_link[i].platform_of_node && dai_link[i].cpu_of_node) + continue; + + /* populate platform_of_node for snd card dai links */ + if (dai_link[i].platform_name && + !dai_link[i].platform_of_node) { + index = of_property_match_string(cdev->of_node, + "asoc-platform-names", + dai_link[i].platform_name); + if (index < 0) { + pr_err("%s: No match found for platform name: %s\n", + __func__, + dai_link[i].platform_name); + ret = index; + goto err; + } + np = of_parse_phandle(cdev->of_node, "asoc-platform", + index); + if (!np) { + pr_err("%s: retrieving phandle for platform %s, index %d failed\n", + __func__, + dai_link[i].platform_name, + index); + ret = -ENODEV; + goto err; + } + dai_link[i].platform_of_node = np; + dai_link[i].platform_name = NULL; + } + + /* populate cpu_of_node for snd card dai links */ + if (dai_link[i].cpu_dai_name && !dai_link[i].cpu_of_node) { + index = of_property_match_string(cdev->of_node, + "asoc-cpu-names", + dai_link[i].cpu_dai_name); + if (index >= 0) { + np = of_parse_phandle(cdev->of_node, + "asoc-cpu", index); + if (!np) { + pr_err("%s: retrieving phandle for cpu dai %s failed\n", + __func__, + dai_link[i].cpu_dai_name); + ret = -ENODEV; + goto err; + } + dai_link[i].cpu_of_node = np; + dai_link[i].cpu_dai_name = NULL; + } + } + } + +err: + return ret; +} + +static struct snd_soc_card snd_soc_card_ipq = { + .name = DRV_NAME, +}; + +static int ipq806x_asoc_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_card_ipq; + struct ipq806x_asoc_mach_data *pdata; + int ret; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct ipq806x_asoc_mach_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, pdata); + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(&pdev->dev, "parse card name failed, err:%d\n", + ret); + goto err; + } + card->dai_link = ipq_snd_dai; + card->num_links = ARRAY_SIZE(ipq_snd_dai); + + ret = ipq806x_populate_dai_link_component_of_node(card); + if (ret) { + ret = -EPROBE_DEFER; + goto err; + } + + pdata->dac_desc = devm_gpiod_get(&pdev->dev, "dac"); + if (IS_ERR(pdata->dac_desc)) { + pr_err("unable to get dac-gpio\n"); + ret = PTR_ERR(pdata->dac_desc); + goto err; + } + gpiod_direction_output(pdata->dac_desc, 0); + gpiod_set_value(pdata->dac_desc, 1); + + pdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix_clk"); + if (IS_ERR(pdata->ahbix_clk)) { + dev_err(&pdev->dev, "%s: Error in getting ahbix_clk\n", + __func__); + ret = PTR_ERR(pdata->ahbix_clk); + goto err; + } + + clk_set_rate(pdata->ahbix_clk, 131072); + ret = clk_prepare_enable(pdata->ahbix_clk); + if (IS_ERR_VALUE(ret)) { + dev_err(&pdev->dev, "%s: Error in enabling ahbix_clk\n", + __func__); + goto err; + } + + ret = snd_soc_register_card(card); + if (ret == -EPROBE_DEFER) { + goto err_clock; + } else if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_clock; + } + + return 0; + +err_clock: + clk_disable_unprepare(pdata->ahbix_clk); + +err: + return ret; +} + +static int ipq806x_asoc_machine_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct ipq806x_asoc_mach_data *pdata = snd_soc_card_get_drvdata(card); + + clk_disable_unprepare(pdata->ahbix_clk); + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id ipq806x_asoc_machine_of_match[] = { + { .compatible = "qcom,ipq806x-snd-card", }, + {}, +}; + +static struct platform_driver ipq806x_asoc_machine_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = ipq806x_asoc_machine_of_match, + }, + .probe = ipq806x_asoc_machine_probe, + .remove = ipq806x_asoc_machine_remove, +}; +module_platform_driver(ipq806x_asoc_machine_driver); + +MODULE_DESCRIPTION("ALSA SoC IPQ806x Machine Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, ipq806x_asoc_machine_of_match); +MODULE_VERSION(DRV_VERSION);
On Wed, Nov 19, 2014 at 10:52:46AM -0800, Kenneth Westfield wrote:
+static struct snd_soc_dai_link ipq_snd_dai[] = {
- /* Front end DAI Links */
- {
.name = "IPQ806x Media1",
.stream_name = "MultiMedia1",
.cpu_dai_name = "lpass-cpu-dai",
.platform_name = "lpass-pcm-mi2s",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
- },
+};
OK, so the CODEC here is actually a dumb DAC with a GPIO enable? You should write a driver for this CODEC that imposes the constraints the device has and manages the GPIO, then it looks like you should be able to use simple-card.
From: Kenneth Westfield kwestfie@codeaurora.org
Now all drivers are in place, allow them to build.
Change-Id: I16b9c4c2796bc1cf86aecae5fc500e685906fa8f Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/Kconfig | 43 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/Makefile | 11 +++++++++++ 2 files changed, 54 insertions(+) create mode 100644 sound/soc/qcom/Kconfig create mode 100644 sound/soc/qcom/Makefile
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..00cbdf8b29e68d32154a854a8bbfe2ac13b8b985 --- /dev/null +++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,43 @@ +# +# Copyright (c) 2013-2014 The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +config SND_SOC_CPU_DAI + bool + depends on SND_SOC_QCOM + +config SND_SOC_IPQ806X + bool "SoC Audio support for IPQ806x based platforms" + depends on SND_SOC_QCOM || ARCH_QCOM + select SND_SOC_PCM_I2S + select SND_SOC_CPU_DAI + select SND_SOC_LPAIF + help + Support for Qualcomm Technologies LPASS audio block in IPQ806X SOC-based systems. + Say Y if you want to use audio devices that will run on these boards. + +config SND_SOC_LPAIF + bool + depends on SND_SOC_QCOM + +config SND_SOC_PCM_I2S + bool + depends on SND_SOC_QCOM + +config SND_SOC_QCOM + bool "SoC Audio support for QCOM platforms" + help + Support for audio in Qualcomm Technologies SOC-based systems. + Say Y if you want to use audio devices that will run on IPQ806x and similar SOCs. + diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..0a74177df75f7aa5f7fbaab35da9b92689fa3d6a --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,11 @@ +#QCOM SOUNDCARDS + +snd-soc-ipq806x-objs := ipq806x.o +snd-soc-lpass-pcm-mi2s-objs := lpass-pcm-mi2s.o +snd-soc-lpass-cpu-dai-objs := lpass-cpu-dai.o +snd-soc-lpass-lpaif-objs := lpass-lpaif.o + +obj-$(CONFIG_SND_SOC_IPQ806X) += snd-soc-ipq806x.o +obj-$(CONFIG_SND_SOC_PCM_I2S) += snd-soc-lpass-pcm-mi2s.o +obj-$(CONFIG_SND_SOC_CPU_DAI) += snd-soc-lpass-cpu-dai.o +obj-$(CONFIG_SND_SOC_LPAIF) += snd-soc-lpass-lpaif.o
On Wed, Nov 19, 2014 at 10:52:47AM -0800, Kenneth Westfield wrote:
+++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,43 @@ +# +# Copyright (c) 2013-2014 The Linux Foundation. All rights reserved.
Is there some kind of copyright assignment on file or something? It's weird to have someone else without obvious authorship own the copyright...
+# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Please just license the file under the GPL, all the drivers that get built from here are GPLed - I can't see any reason for this to have an odd license.
+config SND_SOC_QCOM
- bool "SoC Audio support for QCOM platforms"
- help
Support for audio in Qualcomm Technologies SOC-based systems.
Say Y if you want to use audio devices that will run on IPQ806x and similar SOCs.
I'd expect this to be the first thing in the file not the last. Why are things in here bools, shouldn't they be tristates?
index 0000000000000000000000000000000000000000..0a74177df75f7aa5f7fbaab35da9b92689fa3d6a --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,11 @@ +#QCOM SOUNDCARDS
This appears to be CPU drivers as well as sound cards (as you'd expect for machine drivers) so the comment isn't quite right - just remove it.
On 11/25/2014 02:07 PM, Mark Brown wrote:
On Wed, Nov 19, 2014 at 10:52:47AM -0800, Kenneth Westfield wrote:
+++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,43 @@
...
+# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Please just license the file under the GPL, all the drivers that get built from here are GPLed - I can't see any reason for this to have an odd license.
Why is there even a license blurb here? Just drop it.
- Bryan
From: Kenneth Westfield kwestfie@codeaurora.org
Allow for the QCOM LPASS drivers to build.
Change-Id: Ic870d8e9487cebe634c8bd2f1c7263c27237febc Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + 2 files changed, 2 insertions(+)
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 7d5d6444a83737ffa3c01acbe1925de619e0390a..b98eebf0e30bba19ec24acc7f0e6ab155e5e602b 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -47,6 +47,7 @@ source "sound/soc/kirkwood/Kconfig" source "sound/soc/intel/Kconfig" source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" +source "sound/soc/qcom/Kconfig" source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/sh/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 865e090c80616024a552da5e33d2796b9be1dbbd..3d78a8d874dc98dd6219b33e3f2c100ef0334bfc 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ +obj-$(CONFIG_SND_SOC) += qcom/ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += sh/
From: Kenneth Westfield kwestfie@codeaurora.org
Model the LPASS audio hardware for the IPQ806X.
Change-Id: Ide1aa0d09c23d4496aa9c40e3c9878a968261f11 Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org --- arch/arm/boot/dts/qcom-ipq8064.dtsi | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+)
diff --git a/arch/arm/boot/dts/qcom-ipq8064.dtsi b/arch/arm/boot/dts/qcom-ipq8064.dtsi index 63b2146f563b541e4994697af5ee1bbb41a4abd1..0e5b3b625f0442964aa7fbbc993c6c818fe99041 100644 --- a/arch/arm/boot/dts/qcom-ipq8064.dtsi +++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi @@ -2,6 +2,7 @@
#include "skeleton.dtsi" #include <dt-bindings/clock/qcom,gcc-ipq806x.h> +#include <dt-bindings/clock/qcom,lcc-ipq806x.h> #include <dt-bindings/soc/qcom,gsbi.h>
/ { @@ -66,6 +67,38 @@ ranges; compatible = "simple-bus";
+ sound { + compatible = "qcom,ipq806x-snd-card"; + status = "disabled"; + clocks = <&lcc AHBIX_CLK>; + clock-names = "ahbix_clk"; + asoc-platform = <&pcm0>; + asoc-platform-names = "lpass-pcm-mi2s"; + asoc-cpu = <&dai_mi2s>; + asoc-cpu-names = "lpass-cpu-dai"; + }; + + lpass-lpaif { + compatible = "qcom,lpass-lpaif"; + status = "disabled"; + reg = <0x28100000 0x10000>; + reg-names = "lpass-lpaif-mem"; + interrupts = <0 85 1>; + interrupt-names = "lpass-lpaif-irq"; + }; + + dai_mi2s: lpass-cpu-dai { + compatible = "qcom,lpass-cpu-dai"; + status = "disabled"; + clocks = <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>; + clock-names = "mi2s_osr_clk", "mi2s_bit_clk"; + }; + + pcm0: lpass-pcm-mi2s { + compatible = "qcom,lpass-pcm-mi2s"; + status = "disabled"; + }; + qcom_pinmux: pinmux@800000 { compatible = "qcom,ipq8064-pinctrl"; reg = <0x800000 0x4000>;
On Wed, Nov 19, 2014 at 07:52:49PM +0100, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Model the LPASS audio hardware for the IPQ806X.
Change-Id: Ide1aa0d09c23d4496aa9c40e3c9878a968261f11
As Kumar mentioned, please exclude this.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
Typically, the order of these SoB should match some sort of chain of delivery: - The first should be the author of the patch - The last should match the email source (you) <-- doesn't seem to be the case
arch/arm/boot/dts/qcom-ipq8064.dtsi | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+)
diff --git a/arch/arm/boot/dts/qcom-ipq8064.dtsi b/arch/arm/boot/dts/qcom-ipq8064.dtsi index 63b2146f563b541e4994697af5ee1bbb41a4abd1..0e5b3b625f0442964aa7fbbc993c6c818fe99041 100644 --- a/arch/arm/boot/dts/qcom-ipq8064.dtsi +++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi @@ -2,6 +2,7 @@
#include "skeleton.dtsi" #include <dt-bindings/clock/qcom,gcc-ipq806x.h> +#include <dt-bindings/clock/qcom,lcc-ipq806x.h>
Neither this file nor an associated clock controller driver exists in mainline. Is there some other series this depends on?
-Courtney
On Wed, November 19, 2014 2:54 pm, Courtney Cavin wrote:
On Wed, Nov 19, 2014 at 07:52:49PM +0100, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Model the LPASS audio hardware for the IPQ806X.
Change-Id: Ide1aa0d09c23d4496aa9c40e3c9878a968261f11
As Kumar mentioned, please exclude this.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Banajit Goswami bgoswami@codeaurora.org
Typically, the order of these SoB should match some sort of chain of delivery:
- The first should be the author of the patch
- The last should match the email source (you) <-- doesn't seem to be the case
arch/arm/boot/dts/qcom-ipq8064.dtsi | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+)
diff --git a/arch/arm/boot/dts/qcom-ipq8064.dtsi b/arch/arm/boot/dts/qcom-ipq8064.dtsi index 63b2146f563b541e4994697af5ee1bbb41a4abd1..0e5b3b625f0442964aa7fbbc993c6c818fe99041 100644 --- a/arch/arm/boot/dts/qcom-ipq8064.dtsi +++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi @@ -2,6 +2,7 @@
#include "skeleton.dtsi" #include <dt-bindings/clock/qcom,gcc-ipq806x.h> +#include <dt-bindings/clock/qcom,lcc-ipq806x.h>
Neither this file nor an associated clock controller driver exists in mainline. Is there some other series this depends on?
-Courtney _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Courtney
Thank you for your comments. I will separately address each comment shortly.
On Wed, Nov 19, 2014 at 10:52:49AM -0800, Kenneth Westfield wrote:
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi +++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
sound {
compatible = "qcom,ipq806x-snd-card";
status = "disabled";
clocks = <&lcc AHBIX_CLK>;
clock-names = "ahbix_clk";
asoc-platform = <&pcm0>;
asoc-platform-names = "lpass-pcm-mi2s";
asoc-cpu = <&dai_mi2s>;
asoc-cpu-names = "lpass-cpu-dai";
};
This appears to be part of the board but...
lpass-lpaif {
compatible = "qcom,lpass-lpaif";
status = "disabled";
reg = <0x28100000 0x10000>;
reg-names = "lpass-lpaif-mem";
interrupts = <0 85 1>;
interrupt-names = "lpass-lpaif-irq";
};
dai_mi2s: lpass-cpu-dai {
compatible = "qcom,lpass-cpu-dai";
status = "disabled";
clocks = <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>;
clock-names = "mi2s_osr_clk", "mi2s_bit_clk";
};
pcm0: lpass-pcm-mi2s {
compatible = "qcom,lpass-pcm-mi2s";
status = "disabled";
};
...these are all part of the SoC. I'd expect these to go into separate files.
On Nov 19, 2014, at 12:52 PM, Kenneth Westfield kwestfie@codeaurora.org wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
This set of patches adds support for audio on the Qualcomm Technologies ipq806x SOC.
The ipq806x SOC has audio-related hardware blocks in its low-power audio subsystem (or LPASS). One of the relevant blocks in the LPASS is its low-power audio interface (or LPAIF). This encapsulates the MI2S port, which is what these drivers are configured to use. The I2S pins are connected to an external DAC/amp chip. In addition, a single GPIO is connected to the same DAC/amp, which gives the SOC enable/disable control.
The specific drivers added are:
- a machine driver that handles the board-specific pins
- a native driver that handles hardware access to the LPAIF
- a CPU DAI driver for controlling the LPAIF block
- a PCM MI2S platform driver
Corresponding additions to the device tree for the ipq806x and its documentation has also been added. Also, as this is a new directory, the MAINTAINERS file has been updated as well.
- Ken
Some general comments, you shouldn’t have gerrit Change-Id’s in commit messages, the Kconfig/Makefile changes should be part of the patch that adds the code associated with them.
- k
On Wed, Nov 19, 2014 at 02:16:10PM -0600, Kumar Gala wrote:
Kumar, as I keep saying please fix your mailer to word wrap within paragraphs.
Some general comments, you shouldn’t have gerrit Change-Id’s in commit
Indeed.
messages, the Kconfig/Makefile changes should be part of the patch that adds the code associated with them.
No, it's totally fine for a large new driver like this that's split over many commits to add the Kconfig and Makefile at the end - there is no value in trying to split them up.
On Wed, November 19, 2014 12:16 pm, Kumar Gala wrote:
On Nov 19, 2014, at 12:52 PM, Kenneth Westfield kwestfie@codeaurora.org wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
This set of patches adds support for audio on the Qualcomm Technologies ipq806x SOC.
The ipq806x SOC has audio-related hardware blocks in its low-power audio subsystem (or LPASS). One of the relevant blocks in the LPASS is its low-power audio interface (or LPAIF). This encapsulates the MI2S port, which is what these drivers are configured to use. The I2S pins are connected to an external DAC/amp chip. In addition, a single GPIO is connected to the same DAC/amp, which gives the SOC enable/disable control.
The specific drivers added are:
- a machine driver that handles the board-specific pins
- a native driver that handles hardware access to the LPAIF
- a CPU DAI driver for controlling the LPAIF block
- a PCM MI2S platform driver
Corresponding additions to the device tree for the ipq806x and its documentation has also been added. Also, as this is a new directory, the MAINTAINERS file has been updated as well.
- Ken
Some general comments, you shouldnt have gerrit Change-Ids in commit messages, the Kconfig/Makefile changes should be part of the patch that adds the code associated with them.
- k
-- Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Thank you for your comments. I will separately address each comment shortly.
On Fri, Nov 21, 2014 at 12:24:33PM -0800, Kenneth Westfield wrote:
Thank you for your comments. I will separately address each comment shortly.
Please stop sending mails like this for every mail people send you - there's no content in them, it's just adding to the volume of mail people have to read. If you're making some specific comment that's obviously fine but just saying effectively "I'm going to send you more mail in future" isn't adding much.
Please also when you do reply to mails delete unneeded context from the mail, this makes it much easier for people to find any new content you have added.
participants (7)
-
Bryan Huntsman
-
Courtney Cavin
-
Kenneth Westfield
-
Kumar Gala
-
Lars-Peter Clausen
-
Mark Brown
-
Pierre-Louis Bossart