[alsa-devel] [Patch v2 00/11] 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 Codec DAI driver that controls the SOC external pins - a CPU DAI driver for controlling the LPASS-LPAIF block - a PCM MI2S platform driver - a machine driver that ties the three drivers together
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.
== Updates from my previous post:
[PATCH 00/9] ASoC: QCOM: Add support for ipq806x SOC http://thread.gmane.org/gmane.linux.ports.arm.msm/10701
- remove the native LPAIF driver, and move its functionality to the CPU DAI driver - add a codec driver to manage the pins going to the external DAC (previously managed by the machine driver) - use devm_* and dev_* where possible - ISR only handles relevant DMA channel now - update device tree documentation to reflect changes - general code cleanup
== Additional patch series upon which this patch series has device-tree dependencies (lcc)
[PATCH v2 0/8] qcom audio clock control drivers http://thread.gmane.org/gmane.linux.ports.arm.msm/10793
Kenneth Westfield (11): MAINTAINERS: Add QCOM audio ASoC maintainer ASoC: qcom: Add device tree binding docs ASoC: ipq806x: add LPAIF header file ASoC: codec: Add Maxim codec driver ASoC: ipq806x: Add LPASS CPU DAI driver ASoC: ipq806x: Add I2S PCM platform driver ASoC: ipq806x: Add machine driver for IPQ806X SOC ASoC: codec: Add ability to build QCOM codec 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 | 25 ++ .../bindings/sound/qcom,lpass-cpu-dai.txt | 32 ++ .../bindings/sound/qcom,lpass-pcm-mi2s.txt | 12 + .../bindings/sound/qcom,max98357a-codec.txt | 23 ++ MAINTAINERS | 7 + arch/arm/boot/dts/qcom-ipq8064.dtsi | 33 ++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/max98357a.c | 267 ++++++++++++ sound/soc/qcom/Kconfig | 25 ++ sound/soc/qcom/Makefile | 11 + sound/soc/qcom/ipq806x.c | 127 ++++++ sound/soc/qcom/lpass-cpu-mi2s.c | 374 +++++++++++++++++ sound/soc/qcom/lpass-cpu-mi2s.h | 48 +++ sound/soc/qcom/lpass-lpaif.h | 154 +++++++ sound/soc/qcom/lpass-pcm-mi2s.c | 452 +++++++++++++++++++++ 18 files changed, 1598 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-pcm-mi2s.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,max98357a-codec.txt create mode 100644 sound/soc/codecs/max98357a.c 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-mi2s.c create mode 100644 sound/soc/qcom/lpass-cpu-mi2s.h create mode 100644 sound/soc/qcom/lpass-lpaif.h create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.c
From: Kenneth Westfield kwestfie@codeaurora.org
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index e6bff3afaeaeb84b0a4fe99fbd941c38596473f4..0d862732ae301e25f2203e684da21249ae9b4094 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5148,6 +5148,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.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- .../bindings/sound/qcom,ipq806x-snd-card.txt | 25 +++++++++++++++++ .../bindings/sound/qcom,lpass-cpu-dai.txt | 32 ++++++++++++++++++++++ .../bindings/sound/qcom,lpass-pcm-mi2s.txt | 12 ++++++++ .../bindings/sound/qcom,max98357a-codec.txt | 23 ++++++++++++++++ 4 files changed, 92 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-pcm-mi2s.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,max98357a-codec.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..10178737b4bdc6108475f48726bdbf40b6b044b5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,ipq806x-snd-card.txt @@ -0,0 +1,25 @@ +* 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 + * <any string is valid> +- platform : This is a phandle reference to platform device driver node (for the soundcard dai-link) +- cpu : This is a phandle reference to CPU DAI device driver node (for the soundcard dai-link) +- codec : This is a phandle reference to codec DAI device driver node (for the soundcard dai-link) +- codec-dai : This is a string that names the codec DAI device (for the soundcard dai-link) + * max98357a-codec-dai + +Example: + +sound { + compatible = "qcom,ipq806x-snd-card"; + model = "ipq806x-snd-card"; + platform = <&lpass_pcm_mi2s>; + cpu = <&lpass_cpu_mi2s>; + codec = <&max98357a_codec>; + codec-dai = "max98357a-codec-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..ee94af49f90aedb0889f1ddf043780a80e585f62 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu-dai.txt @@ -0,0 +1,32 @@ +* Qualcomm Technologies IPQ806x LPASS DAI + +This node models the Qualcomm Technologies IPQ806x LPASS MI2S DAI port. + +Required properties: +- compatible : "qcom,lpass-cpu-mi2s" +- reg : Address space for the LPASS audio interface registers +- reg-names : The name of the LPASS audio interface register address space + * lpass-lpaif-mem +- clocks : A list of clock specifiers for the audio interface + * AHBIX bus clock + * MI2S OSR clock + * MI2S Bit clock +- clock-names : A list of audio interface clock names + * ahbix_clk + * mi2s_osr_clk + * mi2s_bit_clk +- interrupts : Phandle to the LPASS audio interface interrupt +- interrupt-names : The name of the LPASS audio interface interrupt + * lpass-lpaif-irq + +Example: + +lpass-cpu-mi2s { + compatible = "qcom,lpass-cpu-dai"; + reg = <0x28100000 0x10000>; + reg-names = "lpass-lpaif-mem"; + clocks = <&lcc AHBIX_CLK>, <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>; + clock-names = "ahbix_clk", "mi2s_osr_clk", "mi2s_bit_clk"; + 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..09c04b7f37b51076860f1df10114e0a47b921404 --- /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"; +}; diff --git a/Documentation/devicetree/bindings/sound/qcom,max98357a-codec.txt b/Documentation/devicetree/bindings/sound/qcom,max98357a-codec.txt new file mode 100644 index 0000000000000000000000000000000000000000..14bc14b1049f4138123db8fd17e8926545cbb218 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,max98357a-codec.txt @@ -0,0 +1,23 @@ +* Maxim MAX98357A DAC + +This node models the Maxim MAX98357A DAC as a codec DAI. +This is for audio on the Qualcomm IPQ806x SOC. + +Required properties: +- compatible : "qcom,max98357a-codec" +- dac-gpios : Phandle to the GPIO specifier for the GPIO -> DAC SDMODE pin +- pinctrl-names : A list of names indicating the state of the MI2S pins + * mi2s-disabled + * mi2s-enabled +- pinctrl-0 : The disabled state of the MI2S pins +- pinctrl-1 : The enabled state of the MI2S pins + +Example: + +max98357a-codec { + compatible = "qcom,max98357a-codec"; + dac-gpios = <&qcom_pinmux 25 0>; + pinctrl-names = "mi2s-disabled", "mi2s_enabled"; + pinctrl-0 = <&mi2s_disabled>; + pinctrl-1 = <&mi2s_enabled>; +};
On Mon, Dec 08, 2014 at 02:01:04PM -0800, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add documentation to the sound directory of the device-tree bindings for IPQ806x audio drivers.
Please don't send multiple separate changes in a single patch. One change per patch.
.../bindings/sound/qcom,max98357a-codec.txt | 23 ++++++++++++++++
You are reporting the vendor for this Maxim device as Qualcomm which isn't right, please review the meaning of the vendor information in the device tree binding strings.
From: Kenneth Westfield kwestfie@codeaurora.org
Add the LPASS LPAIF header file in Qualcomm Technologies SoCs. This primarily contains the register definitions.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-lpaif.h | 154 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 sound/soc/qcom/lpass-lpaif.h
diff --git a/sound/soc/qcom/lpass-lpaif.h b/sound/soc/qcom/lpass-lpaif.h new file mode 100644 index 0000000000000000000000000000000000000000..cdc81fbe6d0be9d57860b4ba80e0b3549e4cca76 --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif.h @@ -0,0 +1,154 @@ +/* + * 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 + +/* LPAIF DMA Configuration/Control */ + +#define LPAIF_DMA_BASE 0x6000 +#define LPAIF_DMA_INDEX(ch) (LPAIF_BANK_OFFSET * (ch)) +#define LPAIF_DMA_ADDR(ch, addr) (LPAIF_DMA_BASE \ + + (LPAIF_DMA_INDEX(ch) \ + + (addr))) + +#define LPAIF_DMA_CTL(x) LPAIF_DMA_ADDR((x), 0x00) +#define LPAIF_DMA_BASEADDR(x) LPAIF_DMA_ADDR((x), 0x04) +#define LPAIF_DMA_BUFFLEN(x) LPAIF_DMA_ADDR((x), 0x08) +#define LPAIF_DMA_CURRADDR(x) LPAIF_DMA_ADDR((x), 0x0c) +#define LPAIF_DMA_PERLEN(x) LPAIF_DMA_ADDR((x), 0x10) +#define LPAIF_DMA_PERCNT(x) LPAIF_DMA_ADDR((x), 0x14) +#define LPAIF_DMA_FRM(x) LPAIF_DMA_ADDR((x), 0x18) +#define LPAIF_DMA_FRMCLR(x) LPAIF_DMA_ADDR((x), 0x1c) +#define LPAIF_DMA_SETBUFFCNT(x) LPAIF_DMA_ADDR((x), 0x20) +#define LPAIF_DMA_SETPERCNT(x) LPAIF_DMA_ADDR((x), 0x24) + +#define LPAIF_DMACTL_BURST_EN_SHIFT 11 +#define LPAIF_DMACTL_BURST_EN (1 << LPAIF_DMACTL_BURST_EN_SHIFT) + +#define LPAIF_DMACTL_WPSCNT_MASK 0x700 +#define LPAIF_DMACTL_WPSCNT_SHIFT 8 +#define LPAIF_DMACTL_WPSCNT_SINGLE (0 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_DOUBLE (1 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_TRIPLE (2 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_QUAD (3 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_SIXPACK (5 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_OCTAL (7 << LPAIF_DMACTL_WPSCNT_SHIFT) + +#define LPAIF_DMACTL_AUDIO_INTF_MASK 0x0F0 +#define LPAIF_DMACTL_AUDIO_INTF_SHIFT 4 +#define LPAIF_DMACTL_AUDIO_INTF_NONE (0 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_CODEC (1 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_PCM (2 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_SEC_I2S (3 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_MI2S (4 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_HDMI (5 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_MIXOUT (6 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_LB1 (7 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) +#define LPAIF_DMACTL_AUDIO_INTF_LB2 (8 << LPAIF_DMACTL_AUDIO_INTF_SHIFT) + +#define LPAIF_DMACTL_FIFO_WM_MASK 0x00E +#define LPAIF_DMACTL_FIFO_WM_SHIFT 1 +#define LPAIF_DMACTL_FIFO_WM_1 (0 << LPAIF_DMACTL_FIFO_WM_SHIFT) +#define LPAIF_DMACTL_FIFO_WM_2 (1 << LPAIF_DMACTL_FIFO_WM_SHIFT) +#define LPAIF_DMACTL_FIFO_WM_3 (2 << LPAIF_DMACTL_FIFO_WM_SHIFT) +#define LPAIF_DMACTL_FIFO_WM_4 (3 << LPAIF_DMACTL_FIFO_WM_SHIFT) +#define LPAIF_DMACTL_FIFO_WM_5 (4 << LPAIF_DMACTL_FIFO_WM_SHIFT) +#define LPAIF_DMACTL_FIFO_WM_6 (5 << LPAIF_DMACTL_FIFO_WM_SHIFT) +#define LPAIF_DMACTL_FIFO_WM_7 (6 << LPAIF_DMACTL_FIFO_WM_SHIFT) +#define LPAIF_DMACTL_FIFO_WM_8 (7 << LPAIF_DMACTL_FIFO_WM_SHIFT) + +#define LPAIF_DMACTL_ENABLE_SHIFT 0 +#define LPAIF_DMACTL_ENABLE (1 << LPAIF_DMACTL_ENABLE_SHIFT) + +/* LPAIF DMA Interrupt Control */ + +#define LPAIF_DMAIRQ_BASE 0x3000 +#define LPAIF_DMAIRQ_INDEX(x) (LPAIF_BANK_OFFSET * (x)) +#define LPAIF_DMAIRQ_ADDR(irq, addr) (LPAIF_DMAIRQ_BASE \ + + LPAIF_DMAIRQ_INDEX(irq) \ + + (addr)) + +#define LPAIF_DMAIRQ_EN(x) LPAIF_DMAIRQ_ADDR((x), 0x00) +#define LPAIF_DMAIRQ_STAT(x) LPAIF_DMAIRQ_ADDR((x), 0x04) +#define LPAIF_DMAIRQ_RAW_STAT(x) LPAIF_DMAIRQ_ADDR((x), 0x08) +#define LPAIF_DMAIRQ_CLEAR(x) LPAIF_DMAIRQ_ADDR((x), 0x0c) +#define LPAIF_DMAIRQ_FORCE(x) LPAIF_DMAIRQ_ADDR((x), 0x10) + +#define LPAIF_DMAIRQ_SHIFT 3 +#define LPAIF_DMAIRQ_PER(x) (1 << (LPAIF_DMAIRQ_SHIFT * (x))) +#define LPAIF_DMAIRQ_XRUN(x) (2 << (LPAIF_DMAIRQ_SHIFT * (x))) +#define LPAIF_DMAIRQ_ERR(x) (4 << (LPAIF_DMAIRQ_SHIFT * (x))) +#define LPAIF_DMAIRQ_ALL(x) (7 << (LPAIF_DMAIRQ_SHIFT * (x))) + +/* LPAIF I2S Configuration/Control */ + +#define LPAIF_MI2S_CTL_OFFSET(x) (0x0010 + 0x4 * (x)) + +#define LPAIF_MI2SCTL_LB_SHIFT 15 +#define LPAIF_MI2SCTL_LB (1 << LPAIF_MI2SCTL_LB_SHIFT) + +#define LPAIF_MI2SCTL_SPKEN_SHIFT 14 +#define LPAIF_MI2SCTL_SPKEN (1 << LPAIF_MI2SCTL_SPKEN_SHIFT) + +#define LPAIF_MI2SCTL_SPKMODE_MASK 0x3C00 +#define LPAIF_MI2SCTL_SPKMODE_SHIFT 10 +#define LPAIF_MI2SCTL_SPKMODE_NONE (0 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_SD0 (1 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_SD1 (2 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_SD2 (3 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_SD3 (4 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_QUAD01 (5 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_QUAD23 (6 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_6CH (7 << LPAIF_MI2SCTL_SPKMODE_SHIFT) +#define LPAIF_MI2SCTL_SPKMODE_8CH (8 << LPAIF_MI2SCTL_SPKMODE_SHIFT) + +#define LPAIF_MI2SCTL_SPKMONO_MASK 0x0200 +#define LPAIF_MI2SCTL_SPKMONO_SHIFT 9 +#define LPAIF_MI2SCTL_SPKMONO_STEREO (0 << LPAIF_MI2SCTL_SPKMONO_SHIFT) +#define LPAIF_MI2SCTL_SPKMONO_MONO (1 << LPAIF_MI2SCTL_SPKMONO_SHIFT) + +#define LPAIF_MI2SCTL_WS_SHIFT 2 +#define LPAIF_MI2SCTL_WS (1 << LPAIF_MI2SCTL_WS_SHIFT) + +#define LPAIF_MI2SCTL_BITRATE_MASK 0x3 +#define LPAIF_MI2SCTL_BITRATE_SHIFT 0 +#define LPAIF_MI2SCTL_BITRATE_16 (0 << LPAIF_MI2SCTL_BITRATE_SHIFT) +#define LPAIF_MI2SCTL_BITRATE_24 (1 << LPAIF_MI2SCTL_BITRATE_SHIFT) +#define LPAIF_MI2SCTL_BITRATE_32 (2 << LPAIF_MI2SCTL_BITRATE_SHIFT) + +enum lpaif_dma_interface_channels { + LPAIF_DMA_RD_CH_MI2S = 0, + LPAIF_DMA_RD_CH_PCM0 = 1, + LPAIF_DMA_RD_CH_PCM1 = 2, + LPAIF_DMA_WR_CH_PCM0 = 5, + LPAIF_DMA_WR_CH_PCM1 = 6, + LPAIF_DMA_WR_CH_MI2S = 6, +}; + +enum lpaif_i2s_interface_ports { + LPAIF_I2S_PORT_CODEC_SPK = 0, + LPAIF_I2S_PORT_CODEC_MIC = 1, + LPAIF_I2S_PORT_SEC_SPK = 2, + LPAIF_I2S_PORT_SEC_MIC = 3, + LPAIF_I2S_PORT_MI2S = 4, +}; + +enum lpaif_irq_receivers { + LPAIF_IRQ_RECV_HOST = 0, + LPAIF_IRQ_RECV_ADSP = 1, +}; + +#endif /* _LPASS_LPAIF_H */
From: Kenneth Westfield kwestfie@codeaurora.org
Add codec driver for the MAX98357A DAC.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/codecs/max98357a.c | 267 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 sound/soc/codecs/max98357a.c
diff --git a/sound/soc/codecs/max98357a.c b/sound/soc/codecs/max98357a.c new file mode 100644 index 0000000000000000000000000000000000000000..16def09e98c2318a73e136d92f0c53c81313f10f --- /dev/null +++ b/sound/soc/codecs/max98357a.c @@ -0,0 +1,267 @@ +/* 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/gpio.h> +#include <sound/soc.h> + +#define DRV_NAME "max98357a-codec" + +enum pinctrl_pin_state { + STATE_DISABLED = 0, + STATE_ENABLED = 1 +}; +static const char * const pin_states[] = {"Disabled", "Enabled"}; + +struct max98357a_codec_pinctrl { + struct pinctrl *pinctrl; + struct pinctrl_state *disabled; + struct pinctrl_state *enabled; + enum pinctrl_pin_state curr_state; +}; + +struct max98357a_codec_data { + struct gpio_desc *dac; + struct max98357a_codec_pinctrl mi2s; +}; + +static int max98357a_codec_set_pinctrl(struct max98357a_codec_pinctrl *mi2s) +{ + int ret; + + pr_debug("%s: curr_state = %s\n", __func__, + pin_states[mi2s->curr_state]); + + switch (mi2s->curr_state) { + case STATE_DISABLED: + ret = pinctrl_select_state(mi2s->pinctrl, mi2s->enabled); + if (ret) { + pr_err("%s: pinctrl_select_state failed with %d\n", + __func__, ret); + return -EIO; + } + mi2s->curr_state = STATE_ENABLED; + break; + case STATE_ENABLED: + pr_err("%s: MI2S pins already enabled\n", __func__); + break; + default: + pr_err("%s: MI2S pin state is invalid: %d\n", __func__, + mi2s->curr_state); + return -EINVAL; + } + + return 0; +} + +static int max98357a_codec_reset_pinctrl(struct max98357a_codec_pinctrl *mi2s) +{ + int ret; + + pr_debug("%s: curr_state = %s\n", __func__, + pin_states[mi2s->curr_state]); + + switch (mi2s->curr_state) { + case STATE_ENABLED: + ret = pinctrl_select_state(mi2s->pinctrl, mi2s->disabled); + if (ret) { + pr_err("%s: pinctrl_select_state failed with %d\n", + __func__, ret); + return -EIO; + } + mi2s->curr_state = STATE_DISABLED; + break; + case STATE_DISABLED: + pr_err("%s: MI2S pins already disabled\n", __func__); + break; + default: + pr_err("%s: MI2S pin state is invalid: %d\n", __func__, + mi2s->curr_state); + return -EINVAL; + } + + return 0; +} + +static int max98357a_codec_get_pinctrl(struct snd_soc_dai *dai) +{ + int ret; + struct max98357a_codec_data *prtd = snd_soc_dai_get_drvdata(dai); + struct max98357a_codec_pinctrl *mi2s = &prtd->mi2s; + struct pinctrl *pinctrl; + + pinctrl = devm_pinctrl_get(dai->dev); + if (IS_ERR_OR_NULL(pinctrl)) { + dev_err(dai->dev, "%s: Unable to get pinctrl handle: %d\n", + __func__, PTR_ERR_OR_ZERO(pinctrl)); + return -EINVAL; + } + mi2s->pinctrl = pinctrl; + + /* get all the states handles from Device Tree */ + mi2s->disabled = pinctrl_lookup_state(pinctrl, "mi2s-disabled"); + if (IS_ERR(mi2s->disabled)) { + dev_err(dai->dev, "%s: could not get disabled pinstate: %d\n", + __func__, PTR_ERR_OR_ZERO(mi2s->disabled)); + return -EINVAL; + } + + mi2s->enabled = pinctrl_lookup_state(pinctrl, "mi2s-enabled"); + if (IS_ERR(mi2s->enabled)) { + dev_err(dai->dev, "%s: could not get enabled pinstate: %d\n", + __func__, PTR_ERR_OR_ZERO(mi2s->enabled)); + return -EINVAL; + } + + /* Reset the MI2S pins to the disabled state */ + ret = pinctrl_select_state(mi2s->pinctrl, mi2s->disabled); + if (ret) { + dev_err(dai->dev, "%s: Disable MI2S pins failed with %d\n", + __func__, ret); + return -EIO; + } + mi2s->curr_state = STATE_DISABLED; + + return 0; +} + +static int max98357a_codec_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct max98357a_codec_data *prtd = snd_soc_dai_get_drvdata(dai); + + ret = max98357a_codec_set_pinctrl(&prtd->mi2s); + if (ret) { + dev_err(dai->dev, "%s: MI2S pinctrl set failed with %d\n", + __func__, ret); + return ret; + } + + gpiod_set_value(prtd->dac, 1); + + return 0; +} + +static void max98357a_codec_daiops_shutdown( + struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + int ret; + struct max98357a_codec_data *prtd = snd_soc_dai_get_drvdata(dai); + + gpiod_set_value(prtd->dac, 0); + + ret = max98357a_codec_reset_pinctrl(&prtd->mi2s); + if (ret) + dev_err(dai->dev, "%s: MI2S pinctrl set failed with %d\n", + __func__, ret); +} + +static struct snd_soc_dai_ops max98357a_codec_dai_ops = { + .startup = max98357a_codec_daiops_startup, + .shutdown = max98357a_codec_daiops_shutdown, +}; + +static int max98357a_codec_dai_probe(struct snd_soc_dai *dai) +{ + int ret; + struct max98357a_codec_data *prtd = snd_soc_dai_get_drvdata(dai); + + prtd->dac = devm_gpiod_get(dai->dev, "dac"); + if (IS_ERR(prtd->dac)) { + dev_err(dai->dev, "unable to get DAC GPIO\n"); + return PTR_ERR(prtd->dac); + } + gpiod_direction_output(prtd->dac, 0); + + ret = max98357a_codec_get_pinctrl(dai); + if (ret) { + dev_err(dai->dev, "%s: Parsing MI2S pinctrl failed: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static struct snd_soc_dai_driver max98357a_codec_dai_driver = { + .name = "max98357a-codec-dai", + .playback = { + .stream_name = "max98357a-codec-playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + }, + .probe = &max98357a_codec_dai_probe, + .ops = &max98357a_codec_dai_ops, +}; + +static int max98357a_codec_platform_probe(struct platform_device *pdev) +{ + int ret; + struct max98357a_codec_data *prtd; + struct snd_soc_codec_driver *codec_driver; + + prtd = devm_kzalloc(&pdev->dev, sizeof(struct max98357a_codec_data), + GFP_KERNEL); + if (!prtd) + return -ENOMEM; + platform_set_drvdata(pdev, prtd); + + codec_driver = devm_kzalloc(&pdev->dev, + sizeof(struct snd_soc_codec_driver), GFP_KERNEL); + if (!codec_driver) + return -ENOMEM; + + ret = snd_soc_register_codec(&pdev->dev, codec_driver, + &max98357a_codec_dai_driver, 1); + if (ret) + dev_err(&pdev->dev, "%s: error registering codec dais\n", + __func__); + + return ret; +} + +static int max98357a_codec_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static const struct of_device_id max98357a_codec_dt_match[] = { + { .compatible = "qcom,max98357a-codec", }, + {} +}; + +static struct platform_driver max98357a_codec_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = max98357a_codec_dt_match, + }, + .probe = max98357a_codec_platform_probe, + .remove = max98357a_codec_platform_remove, +}; +module_platform_driver(max98357a_codec_platform_driver); + +MODULE_DESCRIPTION("QCOM MAX98357A CODEC DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, max98357a_codec_dt_match);
On Mon, Dec 08, 2014 at 02:01:06PM -0800, Kenneth Westfield wrote:
+enum pinctrl_pin_state {
- STATE_DISABLED = 0,
- STATE_ENABLED = 1
+}; +static const char * const pin_states[] = {"Disabled", "Enabled"};
This looks like you are trying to reimplement some of the generic support provided by the pinctrl framework - please don't do that. It looks like you should be using the standard idle and default states.
However I'm also questioning why this device is using pinctrl at all. As far as I can see from the code it's a dumb external device with just an enable control and hence no pin control support so it's not a device I'd expect to have any pinmux to control. Why is it doing this? There are also substantial problems throughout the relevant code but probably the best thing is just to remove it all.
+static int max98357a_codec_set_pinctrl(struct max98357a_codec_pinctrl *mi2s) +{
- int ret;
- pr_debug("%s: curr_state = %s\n", __func__,
pin_states[mi2s->curr_state]);
To repeat my previous review comments: use dev_ prints.
+static struct snd_soc_dai_driver max98357a_codec_dai_driver = {
- .name = "max98357a-codec-dai",
- .playback = {
.stream_name = "max98357a-codec-playback",
.formats = SNDRV_PCM_FMTBIT_S16 |
SNDRV_PCM_FMTBIT_S24 |
SNDRV_PCM_FMTBIT_S32,
.rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000,
.rate_min = 8000,
.rate_max = 96000,
.channels_min = 1,
.channels_max = 2,
- },
- .probe = &max98357a_codec_dai_probe,
- .ops = &max98357a_codec_dai_ops,
+};
This CODEC driver has no DAPM support. I'm surprised this works at all, it's certainly not OK for upstream - you need to implement at least stub DAPM support.
From: Kenneth Westfield kwestfie@codeaurora.org
Add the CPU DAI driver for the QCOM LPASS SOC.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-cpu-mi2s.c | 374 ++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass-cpu-mi2s.h | 48 ++++++ 2 files changed, 422 insertions(+) create mode 100644 sound/soc/qcom/lpass-cpu-mi2s.c create mode 100644 sound/soc/qcom/lpass-cpu-mi2s.h
diff --git a/sound/soc/qcom/lpass-cpu-mi2s.c b/sound/soc/qcom/lpass-cpu-mi2s.c new file mode 100644 index 0000000000000000000000000000000000000000..b506064b4e7c3807170e1bff1daaef016221e5f4 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu-mi2s.c @@ -0,0 +1,374 @@ +/* + * 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 <linux/dma-mapping.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "lpass-lpaif.h" +#include "lpass-cpu-mi2s.h" + +#define DRV_NAME "lpass-cpu-mi2s" + +#define LPASS_OSR_TO_BIT_DIVIDER 4 + +static void lpass_lpaif_mi2s_playback(struct lpass_cpu_mi2s_data *pdata, + int enable) +{ + u32 cfg; + + cfg = readl(pdata->base + LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S)); + + if (enable) + cfg |= LPAIF_MI2SCTL_SPKEN; + else + cfg &= ~LPAIF_MI2SCTL_SPKEN; + + cfg &= ~LPAIF_MI2SCTL_WS; + + writel(cfg, pdata->base + LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S)); +} + +static int lpass_lpaif_mi2s_bitwidth(struct lpass_cpu_mi2s_data *pdata, + u32 bitwidth) +{ + u32 cfg; + + cfg = readl(pdata->base + LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S)); + + cfg &= ~LPAIF_MI2SCTL_BITRATE_MASK; + + switch (bitwidth) { + case SNDRV_PCM_FORMAT_S16: + cfg |= LPAIF_MI2SCTL_BITRATE_16; + break; + case SNDRV_PCM_FORMAT_S24: + cfg |= LPAIF_MI2SCTL_BITRATE_24; + break; + case SNDRV_PCM_FORMAT_S32: + cfg |= LPAIF_MI2SCTL_BITRATE_32; + break; + default: + pr_err("%s: invalid bitwidth given: %u\n", __func__, bitwidth); + return -EINVAL; + } + + writel(cfg, pdata->base + LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S)); + + return 0; +} + +static int lpass_lpaif_mi2s_channels(struct lpass_cpu_mi2s_data *pdata, + u32 channels, u32 bitwidth) +{ + u32 cfg; + + cfg = readl(pdata->base + LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S)); + + cfg &= ~LPAIF_MI2SCTL_SPKMODE_MASK; + cfg &= ~LPAIF_MI2SCTL_SPKMONO_MASK; + + switch (channels) { + case 1: + cfg |= LPAIF_MI2SCTL_SPKMODE_SD0; + cfg |= LPAIF_MI2SCTL_SPKMONO_MONO; + break; + case 2: + cfg |= LPAIF_MI2SCTL_SPKMODE_SD0; + cfg |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 4: + cfg |= LPAIF_MI2SCTL_SPKMODE_QUAD01; + cfg |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 6: + cfg |= LPAIF_MI2SCTL_SPKMODE_6CH; + cfg |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 8: + cfg |= LPAIF_MI2SCTL_SPKMODE_8CH; + cfg |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + default: + pr_err("%s: invalid channels given: %u\n", __func__, channels); + return -EINVAL; + } + + writel(cfg, pdata->base + LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S)); + + return 0; +} + + +static int lpass_cpu_mi2s_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + u32 ret; + u32 bit_act; + u32 bitwidth = params_format(params); + u32 channels = params_channels(params); + u32 rate = params_rate(params); + struct lpass_cpu_mi2s_data *prtd = snd_soc_dai_get_drvdata(dai); + + bit_act = snd_pcm_format_width(bitwidth); + if (bit_act < 0) { + dev_err(dai->dev, "%s: Invalid bit width given\n", __func__); + return bit_act; + } + + ret = lpass_lpaif_mi2s_channels(prtd, channels, bit_act); + if (ret) { + dev_err(dai->dev, "%s: Channel setting unsuccessful\n", + __func__); + return -EINVAL; + } + + ret = lpass_lpaif_mi2s_bitwidth(prtd, bitwidth); + if (ret) { + dev_err(dai->dev, "%s: Could not set bit width in HW\n", + __func__); + return -EINVAL; + } + + ret = clk_set_rate(prtd->mi2s_osr_clk, + (rate * bit_act * channels * LPASS_OSR_TO_BIT_DIVIDER)); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + + ret = clk_prepare_enable(prtd->mi2s_osr_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + + ret = clk_set_rate(prtd->mi2s_bit_clk, rate * bit_act * channels); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s bit clk: %d\n", + __func__, ret); + goto err; + } + + ret = clk_prepare_enable(prtd->mi2s_bit_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s bit clk: %d\n", + __func__, ret); + goto err; + } + + prtd->mi2s_clocks_enabled = 1; + + return 0; + +err: + clk_disable_unprepare(prtd->mi2s_osr_clk); + + return ret; +} + +static int lpass_cpu_mi2s_daiops_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_cpu_mi2s_data *prtd = snd_soc_dai_get_drvdata(dai); + + if (prtd->mi2s_clocks_enabled) { + clk_disable_unprepare(prtd->mi2s_osr_clk); + clk_disable_unprepare(prtd->mi2s_bit_clk); + } + prtd->mi2s_clocks_enabled = 0; + + return 0; +} + +static int lpass_cpu_mi2s_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int ret = 0; + struct lpass_cpu_mi2s_data *prtd = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + lpass_lpaif_mi2s_playback(prtd, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + lpass_lpaif_mi2s_playback(prtd, 0); + break; + default: + dev_err(dai->dev, "%s: Invalid trigger command given\n", + __func__); + ret = -EINVAL; + break; + } + + return ret; +} +static struct snd_soc_dai_ops lpass_cpu_mi2s_ops = { + .hw_params = lpass_cpu_mi2s_daiops_hw_params, + .hw_free = lpass_cpu_mi2s_daiops_hw_free, + .trigger = lpass_cpu_mi2s_daiops_trigger, +}; + +static int lpass_cpu_mi2s_dai_probe(struct snd_soc_dai *dai) +{ + struct lpass_cpu_mi2s_data *prtd = snd_soc_dai_get_drvdata(dai); + + prtd->mi2s_osr_clk = devm_clk_get(dai->dev, "mi2s_osr_clk"); + if (IS_ERR(prtd->mi2s_osr_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n", + __func__); + return PTR_ERR(prtd->mi2s_osr_clk); + } + + prtd->mi2s_bit_clk = devm_clk_get(dai->dev, "mi2s_bit_clk"); + if (IS_ERR(prtd->mi2s_bit_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n", + __func__); + return PTR_ERR(prtd->mi2s_bit_clk); + } + + prtd->mi2s_clocks_enabled = 0; + + /* disable MI2S port */ + lpass_lpaif_mi2s_playback(prtd, 0); + + return 0; +} + +static struct snd_soc_dai_driver lpass_cpu_mi2s_dai_driver = { + .name = "lpass-cpu-mi2s-dai", + .playback = { + .stream_name = "lpass-cpu-mi2s-playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &lpass_cpu_mi2s_dai_probe, + .ops = &lpass_cpu_mi2s_ops, +}; + +static const struct snd_soc_component_driver lpass_cpu_mi2s_comp_driver = { + .name = DRV_NAME, +}; + +static int lpass_cpu_mi2s_platform_probe(struct platform_device *pdev) +{ + int ret; + struct resource *lpass_res; + struct lpass_cpu_mi2s_data *prtd; + + prtd = devm_kzalloc(&pdev->dev, sizeof(struct lpass_cpu_mi2s_data), + GFP_KERNEL); + if (!prtd) + return -ENOMEM; + platform_set_drvdata(pdev, prtd); + + prtd->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix_clk"); + if (IS_ERR(prtd->ahbix_clk)) { + dev_err(&pdev->dev, "%s: Error in getting ahbix_clk\n", + __func__); + return PTR_ERR(prtd->ahbix_clk); + } + + clk_set_rate(prtd->ahbix_clk, 131072); + ret = clk_prepare_enable(prtd->ahbix_clk); + if (ret) { + dev_err(&pdev->dev, "%s: Error in enabling ahbix_clk\n", + __func__); + return ret; + } + + lpass_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "lpass-lpaif-mem"); + if (!lpass_res) { + dev_err(&pdev->dev, "%s: error getting resource\n", __func__); + ret = -ENODEV; + goto err_clk; + } + + prtd->base = devm_ioremap_resource(&pdev->dev, lpass_res); + if (IS_ERR(prtd->base)) { + dev_err(&pdev->dev, "%s: error remapping resource\n", + __func__); + ret = PTR_ERR(prtd->base); + goto err_clk; + } + + prtd->irqnum = platform_get_irq_byname(pdev, "lpass-lpaif-irq"); + if (prtd->irqnum < 0) { + dev_err(&pdev->dev, "%s: failed get irq res\n", __func__); + return -ENODEV; + } + + prtd->irq_acquired = 0; + + ret = devm_snd_soc_register_component(&pdev->dev, + &lpass_cpu_mi2s_comp_driver, + &lpass_cpu_mi2s_dai_driver, 1); + if (ret) { + dev_err(&pdev->dev, "%s: error registering soc dai\n", + __func__); + goto err_clk; + } + + return 0; + +err_clk: + clk_disable_unprepare(prtd->ahbix_clk); + return ret; +} + +static int lpass_cpu_mi2s_platform_remove(struct platform_device *pdev) +{ + struct lpass_cpu_mi2s_data *prtd = platform_get_drvdata(pdev); + + clk_disable_unprepare(prtd->ahbix_clk); + + return 0; +} + +static const struct of_device_id lpass_cpu_mi2s_dt_match[] = { + {.compatible = "qcom,lpass-cpu-mi2s"}, + {} +}; + +static struct platform_driver lpass_cpu_mi2s_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = lpass_cpu_mi2s_dt_match, + }, + .probe = lpass_cpu_mi2s_platform_probe, + .remove = lpass_cpu_mi2s_platform_remove, +}; +module_platform_driver(lpass_cpu_mi2s_platform_driver); + +MODULE_DESCRIPTION("QCOM LPASS MI2S CPU DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_cpu_mi2s_dt_match); diff --git a/sound/soc/qcom/lpass-cpu-mi2s.h b/sound/soc/qcom/lpass-cpu-mi2s.h new file mode 100644 index 0000000000000000000000000000000000000000..4227a3661d2a90214e3e8bd43d21d3d3345da531 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu-mi2s.h @@ -0,0 +1,48 @@ +/* + * 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_CPU_MI2S_H +#define _LPASS_CPU_MI2S_H + +enum pinctrl_pin_state { + STATE_DISABLED = 0, + STATE_ENABLED = 1 +}; +static const char *const pin_states[] = {"Disabled", "Enabled"}; + +struct mi2s_pinctrl_info { + struct pinctrl *pinctrl; + struct pinctrl_state *disabled; + struct pinctrl_state *enabled; + enum pinctrl_pin_state curr_state; +}; + +/* + * Device data for the multi-channel I2S port in the low-power audio + * interface (LPAIF) within the low-power audio subsystem (LPASS). + * Both the CPU DAI driver and platform driver will access this. + */ +struct lpass_cpu_mi2s_data { + void __iomem *base; + struct clk *ahbix_clk; + struct clk *mi2s_bit_clk; + struct clk *mi2s_osr_clk; + int mi2s_clocks_enabled; + struct mi2s_pinctrl_info mi2s_pinfo; + int irqnum; + int irq_acquired; + uint8_t prepare_start; + uint32_t period_index; +}; + +#endif /* _LPASS_CPU_MI2S_H */
On Mon, Dec 08, 2014 at 02:01:07PM -0800, Kenneth Westfield wrote:
Please stop CCing Rob Herring's Calxeda address, it bounces.
- default:
pr_err("%s: invalid bitwidth given: %u\n", __func__, bitwidth);
return -EINVAL;
- }
Repeating again: dev_err().
- ret = lpass_lpaif_mi2s_channels(prtd, channels, bit_act);
- ret = lpass_lpaif_mi2s_bitwidth(prtd, bitwidth);
Just inline these helper functions, they're basically just abstracting a single switch statement each which adds little if anything.
+static int lpass_cpu_mi2s_daiops_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct lpass_cpu_mi2s_data *prtd = snd_soc_dai_get_drvdata(dai);
- if (prtd->mi2s_clocks_enabled) {
clk_disable_unprepare(prtd->mi2s_osr_clk);
clk_disable_unprepare(prtd->mi2s_bit_clk);
- }
This seems problematic, why is the clock being disabled here rather than in a place matching that where it was enabled so we don't need to do this checking. I suspect you should be using a DAPM widget to manage the clocks.
- prtd->irq_acquired = 0;
What is this supposed to do? It looks write only.
+#ifndef _LPASS_CPU_MI2S_H +#define _LPASS_CPU_MI2S_H
+enum pinctrl_pin_state {
- STATE_DISABLED = 0,
- STATE_ENABLED = 1
+}; +static const char *const pin_states[] = {"Disabled", "Enabled"};
This apppears to be the same pinctrl stuff you had in the Maxim CODEC driver. Similar issues with reproducing core pinctrl functionality apply here too, and the fact that the code has been cut'n'pasted between different drivers isn't a good sign.
From: Kenneth Westfield kwestfie@codeaurora.org
Add PCM platform driver for the LPASS I2S port.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-pcm-mi2s.c | 452 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 sound/soc/qcom/lpass-pcm-mi2s.c
diff --git a/sound/soc/qcom/lpass-pcm-mi2s.c b/sound/soc/qcom/lpass-pcm-mi2s.c new file mode 100644 index 0000000000000000000000000000000000000000..75ae2bc7d038a48845d502169d87ed55a1c0783d --- /dev/null +++ b/sound/soc/qcom/lpass-pcm-mi2s.c @@ -0,0 +1,452 @@ +/* + * 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-cpu-mi2s.h" + +#define DRV_NAME "lpass-pcm-mi2s" + +/* MI2S HW params */ +#define LPASS_MI2S_PERIOD_SIZE (8064) +#define LPASS_MI2S_PERIODS_MIN (2) +#define LPASS_MI2S_PERIODS_MAX (4) +#define LPASS_MI2S_BUFF_SIZE_MIN (LPASS_MI2S_PERIOD_SIZE * \ + LPASS_MI2S_PERIODS_MIN) +#define LPASS_MI2S_BUFF_SIZE_MAX (LPASS_MI2S_PERIOD_SIZE * \ + LPASS_MI2S_PERIODS_MAX) + +static struct snd_pcm_hardware lpass_pcm_mi2s_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 = 1, + .channels_max = 8, + .buffer_bytes_max = LPASS_MI2S_BUFF_SIZE_MAX, + .period_bytes_max = LPASS_MI2S_PERIOD_SIZE, + .period_bytes_min = LPASS_MI2S_PERIOD_SIZE, + .periods_min = LPASS_MI2S_PERIODS_MIN, + .periods_max = LPASS_MI2S_PERIODS_MAX, + .fifo_size = 0, +}; + +static int lpass_lpaif_int_enable(struct lpass_cpu_mi2s_data *prtd) +{ + u32 intr_val; + + /* clear status before enabling interrupt */ + writel(LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S), + prtd->base + LPAIF_DMAIRQ_CLEAR(LPAIF_IRQ_RECV_HOST)); + + intr_val = readl(prtd->base + LPAIF_DMAIRQ_EN(LPAIF_IRQ_RECV_HOST)); + intr_val |= LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S); + writel(intr_val, prtd->base + LPAIF_DMAIRQ_EN(LPAIF_IRQ_RECV_HOST)); + + return 0; +} + +static int lpass_lpaif_int_disable(struct lpass_cpu_mi2s_data *prtd) +{ + u32 intr_val; + + intr_val = readl(prtd->base + LPAIF_DMAIRQ_EN(LPAIF_IRQ_RECV_HOST)); + intr_val &= ~LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S); + writel(intr_val, prtd->base + LPAIF_DMAIRQ_EN(LPAIF_IRQ_RECV_HOST)); + + return 0; +} + +static int lpass_lpaif_cfg_dma(struct lpass_cpu_mi2s_data *prtd, + dma_addr_t src_start, int buffer_size, int period_size, + int channels, int bitwidth) +{ + int ret = 0; + u32 cfg; + + lpass_lpaif_int_enable(prtd); + + writel(src_start, prtd->base + + LPAIF_DMA_BASEADDR(LPAIF_DMA_RD_CH_MI2S)); + writel((buffer_size >> 2) - 1, prtd->base + + LPAIF_DMA_BUFFLEN(LPAIF_DMA_RD_CH_MI2S)); + writel((period_size >> 2) - 1, prtd->base + + LPAIF_DMA_PERLEN(LPAIF_DMA_RD_CH_MI2S)); + + cfg = 0; + cfg |= LPAIF_DMACTL_BURST_EN; + cfg |= LPAIF_DMACTL_AUDIO_INTF_MI2S; + cfg |= LPAIF_DMACTL_FIFO_WM_8; + cfg |= LPAIF_DMACTL_ENABLE; + + switch (bitwidth) { + case 16: + switch (channels) { + case 1: + case 2: + cfg |= LPAIF_DMACTL_WPSCNT_SINGLE; + break; + case 4: + cfg |= LPAIF_DMACTL_WPSCNT_DOUBLE; + break; + case 6: + cfg |= LPAIF_DMACTL_WPSCNT_TRIPLE; + break; + case 8: + cfg |= LPAIF_DMACTL_WPSCNT_QUAD; + break; + default: + pr_err("%s: invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + ret = -EINVAL; + } + break; + case 24: + case 32: + switch (channels) { + case 1: + cfg |= LPAIF_DMACTL_WPSCNT_SINGLE; + break; + case 2: + cfg |= LPAIF_DMACTL_WPSCNT_DOUBLE; + break; + case 4: + cfg |= LPAIF_DMACTL_WPSCNT_QUAD; + break; + case 6: + cfg |= LPAIF_DMACTL_WPSCNT_SIXPACK; + break; + case 8: + cfg |= LPAIF_DMACTL_WPSCNT_OCTAL; + break; + default: + pr_err("%s: invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + ret = -EINVAL; + } + break; + default: + pr_err("%s: invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + ret = -EINVAL; + } + + if (!ret) + writel(cfg, prtd->base + LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S)); + + return ret; +} + +static void lpass_lpaif_dma_stop_clear(struct lpass_cpu_mi2s_data *prtd) +{ + writel(0x0, prtd->base + LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S)); +} + +static void lpass_lpaif_dma_stop(struct lpass_cpu_mi2s_data *prtd) +{ + u32 cfg; + + cfg = readl(prtd->base + LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S)); + cfg &= ~LPAIF_DMACTL_ENABLE; + writel(cfg, prtd->base + LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S)); +} + +static int lpass_pcm_mi2s_alloc_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + + size = lpass_pcm_mi2s_hardware_playback.buffer_bytes_max; + + 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) { + dev_err(prtd->dev, "%s: Could not allocate DMA buffer\n", + __func__); + return -ENOMEM; + } + buf->bytes = size; + + return 0; +} + +static void lpass_pcm_mi2s_free_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 irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + u32 intrsrc; + u32 has_xrun, pending; + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct lpass_cpu_mi2s_data *prtd = + snd_soc_dai_get_drvdata(soc_prtd->cpu_dai); + + intrsrc = readl(prtd->base + LPAIF_DMAIRQ_STAT(LPAIF_IRQ_RECV_HOST)); + intrsrc &= LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S); + writel(intrsrc, prtd->base + LPAIF_DMAIRQ_CLEAR(LPAIF_IRQ_RECV_HOST)); + + pending = intrsrc & LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S); + has_xrun = pending & LPAIF_DMAIRQ_XRUN(LPAIF_DMA_RD_CH_MI2S); + + if (unlikely(has_xrun) && snd_pcm_running(substream)) { + dev_warn(soc_prtd->dev, "%s: xrun warning\n", __func__); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + pending &= ~LPAIF_DMAIRQ_XRUN(LPAIF_DMA_RD_CH_MI2S); + ret = IRQ_HANDLED; + } + + if (pending & LPAIF_DMAIRQ_PER(LPAIF_DMA_RD_CH_MI2S)) { + if (++prtd->period_index >= runtime->periods) + prtd->period_index = 0; + snd_pcm_period_elapsed(substream); + pending &= ~LPAIF_DMAIRQ_PER(LPAIF_DMA_RD_CH_MI2S); + ret = IRQ_HANDLED; + } + + if (pending & LPAIF_DMAIRQ_XRUN(LPAIF_DMA_RD_CH_MI2S)) { + snd_pcm_period_elapsed(substream); + dev_warn(soc_prtd->dev, "%s: xrun warning\n", __func__); + ret = IRQ_HANDLED; + } + + if (pending & LPAIF_DMAIRQ_ERR(LPAIF_DMA_RD_CH_MI2S)) { + dev_err(soc_prtd->dev, "%s: Bus access error\n", __func__); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int lpass_pcm_mi2s_open(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct lpass_cpu_mi2s_data *prtd = + snd_soc_dai_get_drvdata(soc_prtd->cpu_dai); + + prtd->prepare_start = 0; + prtd->period_index = 0; + + runtime->dma_bytes = lpass_pcm_mi2s_hardware_playback.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &lpass_pcm_mi2s_hardware_playback); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(soc_prtd->dev, + "%s: snd_pcm_hw_constraint_integer failed\n", + __func__); + return -EINVAL; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int lpass_pcm_mi2s_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct lpass_cpu_mi2s_data *prtd = + snd_soc_dai_get_drvdata(soc_prtd->cpu_dai); + + lpass_lpaif_dma_stop_clear(prtd); + lpass_lpaif_int_disable(prtd); + + return 0; +} + +static int lpass_pcm_mi2s_prepare(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct lpass_cpu_mi2s_data *prtd = + snd_soc_dai_get_drvdata(soc_prtd->cpu_dai); + + /* xrun recovery */ + if (prtd->prepare_start) + return 0; + + lpass_lpaif_dma_stop(prtd); + prtd->prepare_start = 1; + + ret = lpass_lpaif_cfg_dma(prtd, runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), runtime->channels, + runtime->sample_bits); + if (ret) { + dev_err(soc_prtd->dev, "%s: Error in configuring DMA\n", + __func__); + ret = -EINVAL; + goto err; + } + + return 0; + +err: + return ret; +} + +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 snd_pcm_uframes_t lpass_pcm_mi2s_pointer( + struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t offset; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct lpass_cpu_mi2s_data *prtd = + snd_soc_dai_get_drvdata(soc_prtd->cpu_dai); + + offset = prtd->period_index * runtime->period_size; + + return offset >= (runtime->buffer_size) ? 0 : offset; +} + +static struct snd_pcm_ops lpass_pcm_mi2s_soc_ops = { + .open = lpass_pcm_mi2s_open, + .close = lpass_pcm_mi2s_close, + .prepare = lpass_pcm_mi2s_prepare, + .mmap = lpass_pcm_mi2s_mmap, + .pointer = lpass_pcm_mi2s_pointer, + .ioctl = snd_pcm_lib_ioctl, +}; + +static int lpass_pcm_mi2s_soc_new(struct snd_soc_pcm_runtime *soc_prtd) +{ + int ret; + struct snd_card *card = soc_prtd->card->snd_card; + struct snd_pcm *pcm = soc_prtd->pcm; + struct lpass_cpu_mi2s_data *prtd = + snd_soc_dai_get_drvdata(soc_prtd->cpu_dai); + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + + 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; + + ret = lpass_pcm_mi2s_alloc_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + + ret = request_irq(prtd->irqnum, lpass_pcm_mi2s_irq, + IRQF_TRIGGER_RISING, "lpass-lpaif-intr", substream); + if (ret) { + dev_err(soc_prtd->dev, "%s: irq resource request failed\n", + __func__); + goto err; + } + prtd->irq_acquired = 1; + + return 0; + +err: + lpass_pcm_mi2s_free_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + return ret; +} + +static void lpass_pcm_mi2s_soc_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct lpass_cpu_mi2s_data *prtd = + snd_soc_dai_get_drvdata(soc_prtd->cpu_dai); + + lpass_pcm_mi2s_free_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + + disable_irq(prtd->irqnum); + if (prtd->irq_acquired) + free_irq(prtd->irqnum, NULL); + prtd->irq_acquired = 0; +} + +static struct snd_soc_platform_driver lpass_pcm_mi2s_soc_driver = { + .pcm_new = lpass_pcm_mi2s_soc_new, + .pcm_free = lpass_pcm_mi2s_soc_free, + .ops = &lpass_pcm_mi2s_soc_ops, +}; + +static int lpass_pcm_mi2s_platform_probe(struct platform_device *pdev) +{ + int ret; + + ret = devm_snd_soc_register_platform(&pdev->dev, + &lpass_pcm_mi2s_soc_driver); + if (ret) + dev_err(&pdev->dev, "%s: Failed to register pcm device: %d\n", + __func__, ret); + + return ret; +} + +static const struct of_device_id lpass_pcm_mi2s_dt_match[] = { + { .compatible = "qcom,lpass-pcm-mi2s", }, + {} +}; + +static struct platform_driver lpass_pcm_mi2s_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = lpass_pcm_mi2s_dt_match, + }, + .probe = lpass_pcm_mi2s_platform_probe, +}; +module_platform_driver(lpass_pcm_mi2s_platform_driver); + +MODULE_DESCRIPTION("QCOM LPASS MI2S PLATFORM DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_pcm_mi2s_dt_match);
On Mon, Dec 08, 2014 at 02:01:08PM -0800, Kenneth Westfield wrote:
+static int lpass_pcm_mi2s_platform_probe(struct platform_device *pdev) +{
- int ret;
- ret = devm_snd_soc_register_platform(&pdev->dev,
&lpass_pcm_mi2s_soc_driver);
- if (ret)
dev_err(&pdev->dev, "%s: Failed to register pcm device: %d\n",
__func__, ret);
- return ret;
+}
+static const struct of_device_id lpass_pcm_mi2s_dt_match[] = {
- { .compatible = "qcom,lpass-pcm-mi2s", },
- {}
+};
This device which is intended to appear in the device tree and accesses hardware acquires no resources on probe. That indicates that there's something wrong with the way you're modelling things in device tree; my best guess would be that it's part of the I2S controller and should be being instantiated from the I2S DAI driver code not the DT - many other drivers use this model, it's very standard.
From: Kenneth Westfield kwestfie@codeaurora.org
Add machine driver for the IPQ806X LPASS SOC.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/ipq806x.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 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..58bc64105ce6176cbe9408e5a75e4a7d7be437a1 --- /dev/null +++ b/sound/soc/qcom/ipq806x.c @@ -0,0 +1,127 @@ +/* + * 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 <sound/soc.h> + +#define DRV_NAME "ipq806x-machine" + +static struct snd_soc_dai_link ipq806x_machine_dai_link = { + .name = "IPQ806x Media1", + .stream_name = "MultiMedia1", +}; + +static int ipq806x_populate_dai_link_of_nodes(struct snd_soc_card *card) +{ + int ret; + struct device *cdev = card->dev; + struct snd_soc_dai_link *dai_link = card->dai_link; + struct device_node *np; + + np = of_parse_phandle(cdev->of_node, "platform", 0); + if (!np) { + dev_err(cdev, "%s: no phandle for platform driver\n", + __func__); + return -ENODEV; + } + dai_link->platform_of_node = np; + + np = of_parse_phandle(cdev->of_node, "cpu", 0); + if (!np) { + dev_err(cdev, "%s: no phandle for cpu-dai driver\n", __func__); + return -ENODEV; + } + dai_link->cpu_of_node = np; + + np = of_parse_phandle(cdev->of_node, "codec", 0); + if (!np) { + dev_err(cdev, "%s: no phandle for codec driver\n", __func__); + return -ENODEV; + } + dai_link->codec_of_node = np; + + ret = of_property_read_string(cdev->of_node, "codec-dai", + &dai_link->codec_dai_name); + if (ret) { + dev_err(cdev, "%s: no name for codec dai\n", __func__); + return ret; + } + + return 0; +} + +static struct snd_soc_card ipq806x_machine_soc_card = { + .name = DRV_NAME, + .dev = NULL, +}; + +static int ipq806x_machine_platform_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card = &ipq806x_machine_soc_card; + + if (card->dev) { + dev_err(&pdev->dev, "soundcard instance already created\n"); + return -ENODEV; + } + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(&pdev->dev, "parse card name failed, err:%d\n", ret); + return ret; + } + + card->dai_link = &ipq806x_machine_dai_link; + card->num_links = 1; + + ret = ipq806x_populate_dai_link_of_nodes(card); + if (ret) { + dev_err(&pdev->dev, "could not resolve dai links, err:%d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret == -EPROBE_DEFER) { + card->dev = NULL; + return ret; + } else if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id ipq806x_machine_dt_match[] = { + { .compatible = "qcom,ipq806x-snd-card", }, + {}, +}; + +static struct platform_driver ipq806x_machine_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = ipq806x_machine_dt_match, + .pm = &snd_soc_pm_ops, + }, + .probe = ipq806x_machine_platform_probe, +}; +module_platform_driver(ipq806x_machine_platform_driver); + +MODULE_DESCRIPTION("QCOM IPQ806X MACHINE DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, ipq806x_asoc_machine_of_match);
On Mon, Dec 08, 2014 at 02:01:09PM -0800, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add machine driver for the IPQ806X LPASS SOC.
This looks like it should be able to use simple-card - is there something I'm missing here?
From: Kenneth Westfield kwestfie@codeaurora.org
Now that all codec drivers are in place, allow them to build.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/codecs/Kconfig | 4 ++++ sound/soc/codecs/Makefile | 2 ++ 2 files changed, 6 insertions(+)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 883c5778b309322797a9b49b38b2ff117eec079b..499351a3537e56caf308825fec9612d366131ced 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -69,6 +69,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98088 if I2C select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C + select SND_SOC_MAX98357A if SND_SOC_QCOM select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C @@ -827,6 +828,9 @@ config SND_SOC_LM4857 config SND_SOC_MAX9768 tristate
+config SND_SOC_MAX98357A + tristate + config SND_SOC_MAX9877 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bbdfd1e1c182f9ee102f532f391f522e77a8922b..1a5840859c13c0cfd8039c8bf5a40521c60dcf9c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -173,6 +173,7 @@ snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o
# Amp +snd-soc-max98357a-objs := max98357a.o snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-tas2552-objs := tas2552.o @@ -351,5 +352,6 @@ obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp +obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
On Mon, Dec 08, 2014 at 02:01:10PM -0800, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Now that all codec drivers are in place, allow them to build.
This isn't a Qualcomm CODEC and this should be part of the patch adding the driver.
From: Kenneth Westfield kwestfie@codeaurora.org
Now all drivers are in place, allow them to build.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/Kconfig | 25 +++++++++++++++++++++++++ sound/soc/qcom/Makefile | 11 +++++++++++ 2 files changed, 36 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..cd4d88bc079c02d3a665b163aefca798fa8a08dc --- /dev/null +++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,25 @@ +config SND_SOC_QCOM + tristate "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 such as I2S, PCM, + S/PDIF, etc. + +config SND_SOC_CPU_MI2S + tristate + depends on SND_SOC_QCOM + +config SND_SOC_IPQ806X + tristate "SoC Audio support for IPQ806x based platforms" + depends on SND_SOC_QCOM || ARCH_QCOM + select SND_SOC_PCM_MI2S + select SND_SOC_CPU_MI2S + select SND_SOC_MAX98357A + help + Support for Qualcomm Technologies LPASS audio block in IPQ806X SOC-based systems. + Say Y if you want to use audio devices such as I2S, PCM, + S/PDIF, etc. + +config SND_SOC_PCM_MI2S + tristate + depends on SND_SOC_QCOM diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..0c5779a72c8ef95e3d6da12c76303997d91d11a6 --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,11 @@ +# Platform +snd-soc-lpass-pcm-mi2s-objs := lpass-pcm-mi2s.o +snd-soc-lpass-cpu-mi2s-objs := lpass-cpu-mi2s.o + +obj-$(CONFIG_SND_SOC_PCM_MI2S) += snd-soc-lpass-pcm-mi2s.o +obj-$(CONFIG_SND_SOC_CPU_MI2S) += snd-soc-lpass-cpu-mi2s.o + +# Machine +snd-soc-ipq806x-objs := ipq806x.o + +obj-$(CONFIG_SND_SOC_IPQ806X) += snd-soc-ipq806x.o
On Mon, Dec 08, 2014 at 02:01:11PM -0800, Kenneth Westfield wrote:
+config SND_SOC_CPU_MI2S
- tristate
- depends on SND_SOC_QCOM
+config SND_SOC_IPQ806X
- tristate "SoC Audio support for IPQ806x based platforms"
- depends on SND_SOC_QCOM || ARCH_QCOM
- select SND_SOC_PCM_MI2S
- select SND_SOC_CPU_MI2S
select SND_SOC_MAX98357A
Any reason for these not to have an || COMPILE_TEST dependency?
From: Kenneth Westfield kwestfie@codeaurora.org
Allow for the QCOM LPASS drivers to build.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-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.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-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..fb1d6f6e290b9c645eb82fc4403d0fba48305f81 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,31 @@ ranges; compatible = "simple-bus";
+ lpass_pcm_mi2s: lpass-pcm-mi2s { + compatible = "qcom,lpass-pcm-mi2s"; + status = "disabled"; + }; + + lpass_cpu_mi2s: lpass-cpu-mi2s { + compatible = "qcom,lpass-cpu-mi2s"; + status = "disabled"; + reg = <0x28100000 0x10000>; + reg-names = "lpass-lpaif-mem"; + clocks = <&lcc AHBIX_CLK>, + <&lcc MI2S_OSR_CLK>, + <&lcc MI2S_BIT_CLK>; + clock-names = "ahbix_clk", + "mi2s_osr_clk", + "mi2s_bit_clk"; + interrupts = <0 85 1>; + interrupt-names = "lpass-lpaif-irq"; + }; + + max98357a_codec: max98357a-codec { + compatible = "qcom,max98357a-codec"; + status = "disabled"; + }; + qcom_pinmux: pinmux@800000 { compatible = "qcom,ipq8064-pinctrl"; reg = <0x800000 0x4000>; @@ -234,6 +260,13 @@ }; };
+ lcc: clock-controller@28000000 { + compatible = "qcom,lcc-ipq8064"; + reg = <0x28000000 0x1000>; + #clock-cells = <1>; + #reset-cells = <1>; + }; + sata_phy: sata-phy@1b400000 { compatible = "qcom,ipq806x-sata-phy"; reg = <0x1b400000 0x200>;
On Mon, Dec 08, 2014 at 02:01:13PM -0800, Kenneth Westfield wrote:
max98357a_codec: max98357a-codec {
compatible = "qcom,max98357a-codec";
status = "disabled";
};
- qcom_pinmux: pinmux@800000 {
This appears to be adding an off-SoC CODEC driver to the .dtsi for the SoC. Don't do that, just let the relevant boards register the devices on them.
participants (2)
-
Kenneth Westfield
-
Mark Brown