[alsa-devel] [Patch V3 00/10] 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
These drivers, together, are used by simple-audio-card to complete the audio implementation. 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 v2 00/11] ASoC: QCOM: Add support for ipq806x SOC http://thread.gmane.org/gmane.linux.ports.arm.msm/10989
- Removed the PCM platform driver from the DTS platform and tied it to the CPU DAI driver. - Changed I2S pinctrl to use generic naming convention and moved control to CPU DAI driver. It should be controlled now by soc-core's pinctrl_pm_* functionality. - Added stub DAPM support in codec driver. As the DAC GPIO needs to be enabled last when starting playback, and disabled first when stopping playback, it seems as though the trigger function may be the place for this. Suggestions are welcome for a better place to put this. - Removed machine driver and tied DAI drivers to simple-audio-card. - Packaged the build files and Maxim codec files together in one change. - Removed QCOM as vendor from Maxim code and documentation. - Separated the SOC and board definitions into the correct DTS files. - 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 (10): MAINTAINERS: Add QCOM audio ASoC maintainer ASoC: qcom: Document MAX98357A bindings ASoC: qcom: Document LPASS CPU bindings ASoC: codec: Add MAX98357A codec driver ASoC: ipq806x: add LPASS header files ASoC: ipq806x: Add LPASS CPU DAI driver ASoC: ipq806x: Add I2S PCM platform driver ASoC: qcom: Add ability to build QCOM drivers ASoC: Allow for building QCOM drivers ARM: dts: Model IPQ LPASS audio hardware
.../devicetree/bindings/sound/max98357a.txt | 14 + .../bindings/sound/qcom,lpass-cpu-mi2s.txt | 42 ++ MAINTAINERS | 7 + arch/arm/boot/dts/qcom-ipq8064.dtsi | 23 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/max98357a.c | 136 ++++++ sound/soc/qcom/Kconfig | 27 ++ sound/soc/qcom/Makefile | 6 + sound/soc/qcom/lpass-cpu-mi2s.c | 378 ++++++++++++++++ sound/soc/qcom/lpass-lpaif-reg.h | 155 +++++++ sound/soc/qcom/lpass-mi2s.h | 34 ++ sound/soc/qcom/lpass-pcm-mi2s.c | 486 +++++++++++++++++++++ 15 files changed, 1316 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/max98357a.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-cpu-mi2s.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/lpass-cpu-mi2s.c create mode 100644 sound/soc/qcom/lpass-lpaif-reg.h create mode 100644 sound/soc/qcom/lpass-mi2s.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 ddb9ac8d32b3eddc46d66bd408392ae09b637ea7..19141e405644cd9b4d41a4d291d5644513bea337 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5169,6 +5169,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 the Maxim MAX98357A audio codec driver.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- Documentation/devicetree/bindings/sound/max98357a.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/max98357a.txt
diff --git a/Documentation/devicetree/bindings/sound/max98357a.txt b/Documentation/devicetree/bindings/sound/max98357a.txt new file mode 100644 index 0000000000000000000000000000000000000000..718e9578d42d522db15fc41a42a96eca327ac641 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/max98357a.txt @@ -0,0 +1,14 @@ +Maxim MAX98357A audio DAC + +This node models the Maxim MAX98357A DAC as a codec DAI. + +Required properties: +- compatible : "maxim,max98357a" +- sdmode-gpios: Phandle to the GPIO specifier for the GPIO -> DAC SDMODE pin + +Example: + +max98357a { + compatible = "maxim,max98357a"; + sdmode-gpios = <&qcom_pinmux 25 0>; +};
On Wed, Dec 24, 2014 at 08:42:02AM -0800, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add documentation to the sound directory of the device-tree bindings for the Maxim MAX98357A audio codec driver.
Your subject line says "qcom" but this isn't a Qualcomm thing...
+Required properties: +- compatible : "maxim,max98357a" +- sdmode-gpios: Phandle to the GPIO specifier for the GPIO -> DAC SDMODE pin
+max98357a {
- compatible = "maxim,max98357a";
- sdmode-gpios = <&qcom_pinmux 25 0>;
A GPIO specifier is not a phandle (as your example shows). Just say it's a GPIO specifier.
From: Kenneth Westfield kwestfie@codeaurora.org
Add documentation to the sound directory of the device-tree bindings for the IPQ806x LPASS CPU DAI driver.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- .../bindings/sound/qcom,lpass-cpu-mi2s.txt | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-cpu-mi2s.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu-mi2s.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu-mi2s.txt new file mode 100644 index 0000000000000000000000000000000000000000..b411adffb8ce8759ba70334e30b597206735b25f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu-mi2s.txt @@ -0,0 +1,42 @@ +* Qualcomm Technologies IPQ806x LPASS DAI + +This node models the Qualcomm Technologies IPQ806x LPASS MI2S DAI port. + +Required properties: +- compatible: "qcom,lpass-cpu-mi2s" +- 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 +- ahbix-frequency : Specifies AHBIX bus clock frequency +- interrupts : Phandle to the LPASS audio interface interrupt +- interrupt-names : The name of the LPASS audio interface interrupt + * lpass-lpaif-irq +- pinctrl-names : A list of names indicating the state of the MI2S pins + * default + * idle +- pinctrl-0 : The default state of the MI2S pins +- pinctrl-1 : The idle state of the MI2S pins +- reg : Address space for the LPASS audio interface registers +- reg-names : The name of the LPASS audio interface register address space + * lpass-lpaif-mem + +Example: + +lpass-cpu-mi2s { + compatible = "qcom,lpass-cpu-mi2s"; + clocks = <&lcc AHBIX_CLK>, <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>; + clock-names = "ahbix_clk", "mi2s_osr_clk", "mi2s_bit_clk"; + ahbix-clkfrq = <300000>; + interrupts = <0 85 1>; + interrupt-names = "lpass-lpaif-irq"; + pinctrl-names = "default", "idle"; + pinctrl-0 = <&mi2s_default>; + pinctrl-1 = <&mi2s_idle>; + reg = <0x28100000 0x10000>; + reg-names = "lpass-lpaif-mem"; +};
On Wed, Dec 24, 2014 at 08:42:03AM -0800, Kenneth Westfield wrote:
+- ahbix-frequency : Specifies AHBIX bus clock frequency
This is a really weird thing to see in a DT, what's going on here?
+- pinctrl-names : A list of names indicating the state of the MI2S pins
* default
* idle
+- pinctrl-0 : The default state of the MI2S pins +- pinctrl-1 : The idle state of the MI2S pins
This doesn't make much sense. We've got a name lookup array for the pin states but the actual pin states are in distinct numbered properties which can't be reordered. What's going on here?
From: Kenneth Westfield kwestfie@codeaurora.org
Add codec driver for the Maxim MAX98357A DAC.
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 + sound/soc/codecs/max98357a.c | 136 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 sound/soc/codecs/max98357a.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8349f982a586841a3ac9e7b0526af05699c7deaf..8e0baea18da79e6d614e1280dc7ab4b5974e009c 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 @@ -829,6 +830,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 diff --git a/sound/soc/codecs/max98357a.c b/sound/soc/codecs/max98357a.c new file mode 100644 index 0000000000000000000000000000000000000000..8f621e2a52ce1868198a0830a3c3e194c8b4b2cb --- /dev/null +++ b/sound/soc/codecs/max98357a.c @@ -0,0 +1,136 @@ +/* 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. + * + * max98357a.c -- MAX98357A ALSA SoC Codec driver + */ + +#include <linux/module.h> +#include <linux/gpio.h> +#include <sound/soc.h> + +#define DRV_NAME "max98357a" + +static int max98357a_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct gpio_desc *sdmode = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + gpiod_set_value(sdmode, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + gpiod_set_value(sdmode, 0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget max98357a_dapm_widgets[] = { + SND_SOC_DAPM_DAC("SDMode", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("Speaker"), +}; + +static const struct snd_soc_dapm_route max98357a_dapm_routes[] = { + {"Speaker", NULL, "SDMode"}, +}; + +static int max98357a_codec_probe(struct snd_soc_codec *codec) +{ + struct gpio_desc *sdmode; + + sdmode = devm_gpiod_get(codec->dev, "sdmode"); + if (IS_ERR(sdmode)) { + dev_err(codec->dev, "unable to get SDMODE GPIO\n"); + return PTR_ERR(sdmode); + } + gpiod_direction_output(sdmode, 0); + snd_soc_codec_set_drvdata(codec, sdmode); + + return 0; +} + +static struct snd_soc_codec_driver max98357a_codec_driver = { + .probe = max98357a_codec_probe, + .dapm_widgets = max98357a_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98357a_dapm_widgets), + .dapm_routes = max98357a_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max98357a_dapm_routes), +}; + +static struct snd_soc_dai_ops max98357a_dai_ops = { + .trigger = max98357a_daiops_trigger, +}; + +static struct snd_soc_dai_driver max98357a_dai_driver = { + .name = DRV_NAME, + .playback = { + .stream_name = "max98357a-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 = 2, + }, + .ops = &max98357a_dai_ops, +}; + +static int max98357a_platform_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_codec(&pdev->dev, &max98357a_codec_driver, + &max98357a_dai_driver, 1); + if (ret) + dev_err(&pdev->dev, "%s: error registering codec driver\n", + __func__); + + return ret; +} + +static int max98357a_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static const struct of_device_id max98357a_dt_match[] = { + { .compatible = "maxim,max98357a", }, + {} +}; + +static struct platform_driver max98357a_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = max98357a_dt_match, + }, + .probe = max98357a_platform_probe, + .remove = max98357a_platform_remove, +}; +module_platform_driver(max98357a_platform_driver); + +MODULE_DESCRIPTION("Maxim MAX98357A Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, max98357a_dt_match);
On Wed, Dec 24, 2014 at 08:42:04AM -0800, Kenneth Westfield wrote:
+++ 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
What does this have to do with SND_SOC_QCOM?
# Amp +snd-soc-max98357a-objs := max98357a.o
Why is this CODEC driver being sorted with the amplifier drivers?
+static struct platform_driver max98357a_platform_driver = {
- .driver = {
.name = DRV_NAME,
.of_match_table = max98357a_dt_match,
of_match_ptr().
From: Kenneth Westfield kwestfie@codeaurora.org
Add the LPASS header files for ipq806x SOCs. This includes the register definitions for the LPAIF, and the structure definition for the CPU DAI.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-lpaif-reg.h | 155 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass-mi2s.h | 34 +++++++++ 2 files changed, 189 insertions(+) create mode 100644 sound/soc/qcom/lpass-lpaif-reg.h create mode 100644 sound/soc/qcom/lpass-mi2s.h
diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h new file mode 100644 index 0000000000000000000000000000000000000000..54fad17034fe894ccb4e34db1e733e6dcce32c6a --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif-reg.h @@ -0,0 +1,155 @@ +/* + * 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_REG_H +#define _LPASS_LPAIF_REG_H + +#define LPAIF_BANK_OFFSET 0x1000 + +/* 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_BITWIDTH_MASK 0x3 +#define LPAIF_MI2SCTL_BITWIDTH_SHIFT 0 +#define LPAIF_MI2SCTL_BITWIDTH_16 (0 << LPAIF_MI2SCTL_BITWIDTH_SHIFT) +#define LPAIF_MI2SCTL_BITWIDTH_24 (1 << LPAIF_MI2SCTL_BITWIDTH_SHIFT) +#define LPAIF_MI2SCTL_BITWIDTH_32 (2 << LPAIF_MI2SCTL_BITWIDTH_SHIFT) + +/* 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))) + +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_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_dmairq_interface_receivers { + LPAIF_IRQ_RECV_HOST = 0, + LPAIF_IRQ_RECV_ADSP = 1, + LPAIF_IRQ_RECV_UNKN = 2, +}; + +#endif /* _LPASS_LPAIF_REG_H */ diff --git a/sound/soc/qcom/lpass-mi2s.h b/sound/soc/qcom/lpass-mi2s.h new file mode 100644 index 0000000000000000000000000000000000000000..61779faa70ae7325a460c79ab4cd0aae2f490ee8 --- /dev/null +++ b/sound/soc/qcom/lpass-mi2s.h @@ -0,0 +1,34 @@ +/* + * 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_MI2S_H +#define _LPASS_MI2S_H + +/* + * 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_mi2s_data { + void __iomem *base; + struct clk *ahbix_clk; + struct clk *mi2s_bit_clk; + struct clk *mi2s_osr_clk; + int irqnum; + uint8_t prepare_start; + uint32_t period_index; +}; + +int lpass_pcm_mi2s_platform_register(struct device *dev); + +#endif /* _LPASS_MI2S_H */
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 | 378 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 sound/soc/qcom/lpass-cpu-mi2s.c
diff --git a/sound/soc/qcom/lpass-cpu-mi2s.c b/sound/soc/qcom/lpass-cpu-mi2s.c new file mode 100644 index 0000000000000000000000000000000000000000..dc1bd53a78c3d6c097909dd240b6580efae19577 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu-mi2s.c @@ -0,0 +1,378 @@ +/* + * 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-reg.h" +#include "lpass-mi2s.h" + +#define DRV_NAME "lpass-cpu-mi2s" + +#define LPASS_OSR_TO_BIT_DIVIDER 4 + +static inline int lpass_lpaif_mi2s_config(struct snd_soc_dai *dai, + unsigned int channels, unsigned int bitwidth) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + u32 value; + + value = 0; + + value &= ~LPAIF_MI2SCTL_WS; + + switch (bitwidth) { + case 16: + value |= LPAIF_MI2SCTL_BITWIDTH_16; + break; + case 24: + value |= LPAIF_MI2SCTL_BITWIDTH_24; + break; + case 32: + value |= LPAIF_MI2SCTL_BITWIDTH_32; + break; + default: + dev_err(dai->dev, "%s: invalid bitwidth given: %u\n", __func__, + bitwidth); + return -EINVAL; + } + + switch (channels) { + case 1: + value |= LPAIF_MI2SCTL_SPKMODE_SD0; + value |= LPAIF_MI2SCTL_SPKMONO_MONO; + break; + case 2: + value |= LPAIF_MI2SCTL_SPKMODE_SD0; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 4: + value |= LPAIF_MI2SCTL_SPKMODE_QUAD01; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 6: + value |= LPAIF_MI2SCTL_SPKMODE_6CH; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 8: + value |= LPAIF_MI2SCTL_SPKMODE_8CH; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + default: + dev_err(dai->dev, "%s: invalid channels given: %u\n", __func__, + channels); + return -EINVAL; + } + + writel(value, drvdata->base + mi2s_control_offset); + + return 0; +} + +static inline void lpass_lpaif_mi2s_playback_start(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + u32 value; + + value = readl(drvdata->base + mi2s_control_offset); + value |= LPAIF_MI2SCTL_SPKEN; + writel(value, drvdata->base + mi2s_control_offset); +} + +static inline void lpass_lpaif_mi2s_playback_stop(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + u32 value; + + value = readl(drvdata->base + mi2s_control_offset); + value &= ~LPAIF_MI2SCTL_SPKEN; + writel(value, drvdata->base + mi2s_control_offset); +} + +static inline void lpass_lpaif_mi2s_playback_stop_clear(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + + writel(0, drvdata->base + mi2s_control_offset); +} + +static int lpass_cpu_mi2s_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_prepare_enable(drvdata->mi2s_osr_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->mi2s_bit_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s bit clk: %d\n", + __func__, ret); + clk_disable_unprepare(drvdata->mi2s_osr_clk); + return ret; + } + + return 0; +} + +static void lpass_cpu_mi2s_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(drvdata->mi2s_bit_clk); + clk_disable_unprepare(drvdata->mi2s_osr_clk); +} + +static int lpass_cpu_mi2s_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + int bitwidth; + int ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "%s: Invalid bit width given\n", __func__); + return bitwidth; + } + + ret = lpass_lpaif_mi2s_config(dai, channels, bitwidth); + if (ret) { + dev_err(dai->dev, "%s: Channel setting unsuccessful\n", + __func__); + return -EINVAL; + } + + ret = clk_set_rate(drvdata->mi2s_osr_clk, + (rate * bitwidth * 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_set_rate(drvdata->mi2s_bit_clk, rate * bitwidth * channels); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s bit clk: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lpass_cpu_mi2s_daiops_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + lpass_lpaif_mi2s_playback_stop_clear(dai); + + return 0; +} + +static int lpass_cpu_mi2s_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + lpass_lpaif_mi2s_playback_start(dai); + + return 0; +} + +static int lpass_cpu_mi2s_daiops_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_set_rate(drvdata->mi2s_osr_clk, freq); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static struct snd_soc_dai_ops lpass_cpu_mi2s_dai_ops = { + .startup = lpass_cpu_mi2s_daiops_startup, + .shutdown = lpass_cpu_mi2s_daiops_shutdown, + .hw_params = lpass_cpu_mi2s_daiops_hw_params, + .hw_free = lpass_cpu_mi2s_daiops_hw_free, + .prepare = lpass_cpu_mi2s_daiops_prepare, + .set_sysclk = lpass_cpu_mi2s_daiops_set_sysclk, +}; + +static int lpass_cpu_mi2s_dai_probe(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + + drvdata->mi2s_osr_clk = devm_clk_get(dai->dev, "mi2s_osr_clk"); + if (IS_ERR(drvdata->mi2s_osr_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n", + __func__); + return PTR_ERR(drvdata->mi2s_osr_clk); + } + + drvdata->mi2s_bit_clk = devm_clk_get(dai->dev, "mi2s_bit_clk"); + if (IS_ERR(drvdata->mi2s_bit_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n", + __func__); + return PTR_ERR(drvdata->mi2s_bit_clk); + } + + /* ensure MI2S port is disabled */ + lpass_lpaif_mi2s_playback_stop_clear(dai); + + return 0; +} + +static struct snd_soc_dai_driver lpass_cpu_mi2s_dai_driver = { + .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_dai_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) +{ + struct device_node *of_node = pdev->dev.of_node; + struct lpass_mi2s_data *drvdata; + struct resource *lpass_res; + u32 freq; + int ret; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_mi2s_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + + 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__); + return -ENODEV; + } + + drvdata->base = devm_ioremap_resource(&pdev->dev, lpass_res); + if (IS_ERR(drvdata->base)) { + dev_err(&pdev->dev, "%s: error remapping resource\n", + __func__); + return PTR_ERR(drvdata->base); + } + + drvdata->irqnum = platform_get_irq_byname(pdev, "lpass-lpaif-irq"); + if (drvdata->irqnum < 0) { + dev_err(&pdev->dev, "%s: failed get irq res\n", __func__); + return -ENODEV; + } + + drvdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix_clk"); + if (IS_ERR(drvdata->ahbix_clk)) { + dev_err(&pdev->dev, "%s: Error getting ahbix_clk\n", __func__); + return PTR_ERR(drvdata->ahbix_clk); + } + + if (of_property_read_bool(of_node, "ahbix-frequency")) + of_property_read_u32(of_node, "ahbix-frequency", &freq); + clk_set_rate(drvdata->ahbix_clk, freq); + + ret = clk_prepare_enable(drvdata->ahbix_clk); + if (ret) { + dev_err(&pdev->dev, "%s: Error enabling ahbix_clk\n", __func__); + return ret; + } + + 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; + } + + ret = lpass_pcm_mi2s_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "%s: error registering pcm device\n", + __func__); + goto err_clk; + } + + return 0; + +err_clk: + clk_disable_unprepare(drvdata->ahbix_clk); + return ret; +} + +static int lpass_cpu_mi2s_platform_remove(struct platform_device *pdev) +{ + struct lpass_mi2s_data *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->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);
On Wed, Dec 24, 2014 at 08:42:06AM -0800, Kenneth Westfield wrote:
+static inline int lpass_lpaif_mi2s_config(struct snd_soc_dai *dai,
unsigned int channels, unsigned int bitwidth)
+{
This is *really* big for an inline function and doesn't have any obvious need to be one if the compiler doesn't decide to do it for itself. Since it only gets called from hw_params() I'm not even sure why it's a function at all... Similarly for some of the other functions, there's no obvious reason to specify inline.
+static inline void lpass_lpaif_mi2s_playback_start(struct snd_soc_dai *dai) +{
- struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai);
- u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S);
- u32 value;
- value = readl(drvdata->base + mi2s_control_offset);
- value |= LPAIF_MI2SCTL_SPKEN;
- writel(value, drvdata->base + mi2s_control_offset);
+}
+static inline void lpass_lpaif_mi2s_playback_stop(struct snd_soc_dai *dai) +{
Defining functions for every read/modify/write operation is going to make it harder to trace through the code and find out what the actual operations are. If the functions took parameters that allowed things to be factored out that'd be one thing but they're not doing that. Plus...
+static int lpass_cpu_mi2s_daiops_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- lpass_lpaif_mi2s_playback_stop_clear(dai);
- return 0;
+}
...you're only calling most of these functions from one place which consists only of a call to that function. This is all just making things more complicated than they need to be.
+static int lpass_cpu_mi2s_daiops_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- lpass_lpaif_mi2s_playback_start(dai);
Why are we not doing this stuff in the trigger opertaion? All the start and stop stuff looks like it's in the wrong place.
+static int lpass_cpu_mi2s_daiops_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
+{
- struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai);
- int ret;
- ret = clk_set_rate(drvdata->mi2s_osr_clk, freq);
- if (ret) {
dev_err(dai->dev, "%s: error in setting mi2s osr clk: %d\n",
__func__, ret);
return ret;
- }
How is this supposed to work - we also set this clock unconditionally in hw_params()?
+static int lpass_cpu_mi2s_dai_probe(struct snd_soc_dai *dai) +{
- struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai);
- drvdata->mi2s_osr_clk = devm_clk_get(dai->dev, "mi2s_osr_clk");
- if (IS_ERR(drvdata->mi2s_osr_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n",
__func__);
return PTR_ERR(drvdata->mi2s_osr_clk);
- }
- drvdata->mi2s_bit_clk = devm_clk_get(dai->dev, "mi2s_bit_clk");
- if (IS_ERR(drvdata->mi2s_bit_clk)) {
dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n",
__func__);
return PTR_ERR(drvdata->mi2s_bit_clk);
- }
Why are we acquiring these at the DAI level?
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 | 486 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 486 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..7cf2ef9525388e9cad1c1b532cd6bc3b46a48b9d --- /dev/null +++ b/sound/soc/qcom/lpass-pcm-mi2s.c @@ -0,0 +1,486 @@ +/* + * 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 <sound/pcm_params.h> +#include "lpass-lpaif-reg.h" +#include "lpass-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 inline void lpass_lpaif_int_enable(struct snd_soc_platform *platform) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 clear_offset = LPAIF_DMAIRQ_CLEAR(LPAIF_IRQ_RECV_HOST); + u32 enable_offset = LPAIF_DMAIRQ_EN(LPAIF_IRQ_RECV_HOST); + u32 chan_bitmask = LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S); + u32 value; + + /* clear status before enabling interrupts */ + writel(chan_bitmask, drvdata->base + clear_offset); + + value = readl(drvdata->base + enable_offset); + value |= chan_bitmask; + writel(value, drvdata->base + enable_offset); +} + +static inline void lpass_lpaif_int_disable(struct snd_soc_platform *platform) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 enable_offset = LPAIF_DMAIRQ_EN(LPAIF_IRQ_RECV_HOST); + u32 chan_bitmask = LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S); + u32 value; + + value = readl(drvdata->base + enable_offset); + value &= ~chan_bitmask; + writel(value, drvdata->base + enable_offset); +} + +static inline u32 lpass_lpaif_int_retrieve(struct snd_soc_platform *platform) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 status_offset = LPAIF_DMAIRQ_STAT(LPAIF_IRQ_RECV_HOST); + u32 clear_offset = LPAIF_DMAIRQ_CLEAR(LPAIF_IRQ_RECV_HOST); + u32 chan_bitmask = LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S); + u32 value; + + value = readl(drvdata->base + status_offset); + value &= chan_bitmask; + writel(value, drvdata->base + clear_offset); + + return value; +} + +static inline int lpass_lpaif_dma_set_format(struct snd_soc_platform *platform, + unsigned int channels, unsigned int bitwidth) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 dma_control_offset = LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S); + u32 value = 0; + + value |= LPAIF_DMACTL_BURST_EN; + value |= LPAIF_DMACTL_AUDIO_INTF_MI2S; + value |= LPAIF_DMACTL_FIFO_WM_8; + + switch (bitwidth) { + case 16: + switch (channels) { + case 1: + case 2: + value |= LPAIF_DMACTL_WPSCNT_SINGLE; + break; + case 4: + value |= LPAIF_DMACTL_WPSCNT_DOUBLE; + break; + case 6: + value |= LPAIF_DMACTL_WPSCNT_TRIPLE; + break; + case 8: + value |= LPAIF_DMACTL_WPSCNT_QUAD; + break; + default: + dev_err(platform->dev, "%s: invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + break; + case 24: + case 32: + switch (channels) { + case 1: + value |= LPAIF_DMACTL_WPSCNT_SINGLE; + break; + case 2: + value |= LPAIF_DMACTL_WPSCNT_DOUBLE; + break; + case 4: + value |= LPAIF_DMACTL_WPSCNT_QUAD; + break; + case 6: + value |= LPAIF_DMACTL_WPSCNT_SIXPACK; + break; + case 8: + value |= LPAIF_DMACTL_WPSCNT_OCTAL; + break; + default: + dev_err(platform->dev, "%s: invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + break; + default: + dev_err(platform->dev, "%s: invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + + writel(value, drvdata->base + dma_control_offset); + + return 0; +} + +static inline void lpass_lpaif_dma_set_addr(struct snd_soc_platform *platform, + dma_addr_t src_start, size_t buffer_size, size_t period_size) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 base_address_offset = LPAIF_DMA_BASEADDR(LPAIF_DMA_RD_CH_MI2S); + u32 buffer_length_offset = LPAIF_DMA_BUFFLEN(LPAIF_DMA_RD_CH_MI2S); + u32 period_length_offset = LPAIF_DMA_PERLEN(LPAIF_DMA_RD_CH_MI2S); + + writel(src_start, drvdata->base + base_address_offset); + writel((buffer_size >> 2) - 1, drvdata->base + buffer_length_offset); + writel((period_size >> 2) - 1, drvdata->base + period_length_offset); +} + +static inline void lpass_lpaif_dma_start(struct snd_soc_platform *platform) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 dma_control_offset = LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S); + u32 value; + + value = readl(drvdata->base + dma_control_offset); + value |= LPAIF_DMACTL_ENABLE; + writel(value, drvdata->base + dma_control_offset); +} + +static inline void lpass_lpaif_dma_stop(struct snd_soc_platform *platform) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 dma_control_offset = LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S); + u32 value; + + value = readl(drvdata->base + dma_control_offset); + value &= ~LPAIF_DMACTL_ENABLE; + writel(value, drvdata->base + dma_control_offset); +} + +static inline void lpass_lpaif_dma_stop_clear(struct snd_soc_platform *platform) +{ + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(platform); + u32 dma_control_offset = LPAIF_DMA_CTL(LPAIF_DMA_RD_CH_MI2S); + + writel(0, drvdata->base + dma_control_offset); +} + +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 *soc_runtime = 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(soc_runtime->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) +{ + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + u32 xrun_bitmask = LPAIF_DMAIRQ_XRUN(LPAIF_DMA_RD_CH_MI2S); + u32 period_bitmask = LPAIF_DMAIRQ_PER(LPAIF_DMA_RD_CH_MI2S); + u32 error_bitmask = LPAIF_DMAIRQ_ERR(LPAIF_DMA_RD_CH_MI2S); + u32 pending; + irqreturn_t ret = IRQ_NONE; + + pending = lpass_lpaif_int_retrieve(soc_runtime->platform); + + if (unlikely(pending & xrun_bitmask) && snd_pcm_running(substream)) { + dev_warn(soc_runtime->dev, "%s: xrun warning\n", __func__); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + pending &= ~xrun_bitmask; + ret = IRQ_HANDLED; + } + + if (pending & period_bitmask) { + if (++drvdata->period_index >= runtime->periods) + drvdata->period_index = 0; + snd_pcm_period_elapsed(substream); + pending &= ~period_bitmask; + ret = IRQ_HANDLED; + } + + if (pending & xrun_bitmask) { + snd_pcm_period_elapsed(substream); + dev_warn(soc_runtime->dev, "%s: xrun warning\n", __func__); + ret = IRQ_HANDLED; + } + + if (pending & error_bitmask) { + dev_err(soc_runtime->dev, "%s: Bus access error\n", __func__); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int lpass_pcm_mi2s_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + int ret; + + drvdata->prepare_start = 0; + drvdata->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_runtime->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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + int bitwidth; + int ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(soc_runtime->dev, "%s: Invalid bit width given\n", + __func__); + return bitwidth; + } + + ret = lpass_lpaif_dma_set_format(soc_runtime->platform, channels, + bitwidth); + if (ret) { + dev_err(soc_runtime->dev, "%s: Error setting DMA format\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int lpass_pcm_mi2s_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + + lpass_lpaif_dma_stop_clear(soc_runtime->platform); + lpass_lpaif_int_disable(soc_runtime->platform); + + return 0; +} + +static int lpass_pcm_mi2s_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + + /* xrun recovery */ + if (drvdata->prepare_start) + return 0; + drvdata->prepare_start = 1; + + lpass_lpaif_dma_set_addr(soc_runtime->platform, runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream)); + + lpass_lpaif_int_enable(soc_runtime->platform); + lpass_lpaif_dma_start(soc_runtime->platform); + + return 0; +} + +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) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + snd_pcm_uframes_t offset; + + offset = drvdata->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, + .hw_params = lpass_pcm_mi2s_hw_params, + .hw_free = lpass_pcm_mi2s_hw_free, + .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_runtime) +{ + struct snd_card *card = soc_runtime->card->snd_card; + struct snd_pcm *pcm = soc_runtime->pcm; + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + int ret; + + 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(drvdata->irqnum, lpass_pcm_mi2s_irq, + IRQF_TRIGGER_RISING, "lpass-lpaif-intr", substream); + if (ret) { + dev_err(soc_runtime->dev, "%s: irq resource request failed\n", + __func__); + goto err_buf; + } + + /* ensure DMA hw is turned off */ + lpass_lpaif_dma_stop_clear(soc_runtime->platform); + lpass_lpaif_int_disable(soc_runtime->platform); + + return 0; + +err_buf: + 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_runtime = substream->private_data; + struct lpass_mi2s_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + + lpass_pcm_mi2s_free_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + + disable_irq(drvdata->irqnum); + free_irq(drvdata->irqnum, NULL); +} + +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, +}; + +int lpass_pcm_mi2s_platform_register(struct device *dev) +{ + return devm_snd_soc_register_platform(dev, &lpass_pcm_mi2s_soc_driver); +} + +MODULE_DESCRIPTION("QCOM LPASS MI2S PLATFORM DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME);
On Wed, Dec 24, 2014 at 08:42:07AM -0800, Kenneth Westfield wrote:
+static inline u32 lpass_lpaif_int_retrieve(struct snd_soc_platform *platform) +{
- struct lpass_mi2s_data *drvdata =
snd_soc_platform_get_drvdata(platform);
- u32 status_offset = LPAIF_DMAIRQ_STAT(LPAIF_IRQ_RECV_HOST);
- u32 clear_offset = LPAIF_DMAIRQ_CLEAR(LPAIF_IRQ_RECV_HOST);
- u32 chan_bitmask = LPAIF_DMAIRQ_ALL(LPAIF_DMA_RD_CH_MI2S);
- u32 value;
- value = readl(drvdata->base + status_offset);
- value &= chan_bitmask;
- writel(value, drvdata->base + clear_offset);
- return value;
+}
More of these tiny inline functions in this driver, and in this case there's a definite readability problem since this is not just "retrieve", it's reading and acking the interrupts which means that...
+static irqreturn_t lpass_pcm_mi2s_irq(int irq, void *data) +{
- struct snd_pcm_substream *substream = data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
- struct lpass_mi2s_data *drvdata =
snd_soc_platform_get_drvdata(soc_runtime->platform);
- u32 xrun_bitmask = LPAIF_DMAIRQ_XRUN(LPAIF_DMA_RD_CH_MI2S);
- u32 period_bitmask = LPAIF_DMAIRQ_PER(LPAIF_DMA_RD_CH_MI2S);
- u32 error_bitmask = LPAIF_DMAIRQ_ERR(LPAIF_DMA_RD_CH_MI2S);
- u32 pending;
- irqreturn_t ret = IRQ_NONE;
- pending = lpass_lpaif_int_retrieve(soc_runtime->platform);
- if (unlikely(pending & xrun_bitmask) && snd_pcm_running(substream)) {
dev_warn(soc_runtime->dev, "%s: xrun warning\n", __func__);
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
pending &= ~xrun_bitmask;
ret = IRQ_HANDLED;
- }
...all this code working out if we actually handled an interrupt might be lying.
- if (pending & period_bitmask) {
if (++drvdata->period_index >= runtime->periods)
drvdata->period_index = 0;
snd_pcm_period_elapsed(substream);
pending &= ~period_bitmask;
ret = IRQ_HANDLED;
- }
If we miss an interrupt then...
+static snd_pcm_uframes_t lpass_pcm_mi2s_pointer(
struct snd_pcm_substream *substream)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
- struct lpass_mi2s_data *drvdata =
snd_soc_platform_get_drvdata(soc_runtime->platform);
- snd_pcm_uframes_t offset;
- offset = drvdata->period_index * runtime->period_size;
- return offset >= (runtime->buffer_size) ? 0 : offset;
+}
...this will be broken. Why is the pointer operation not reading from the hardware?
+static struct snd_pcm_ops lpass_pcm_mi2s_soc_ops = {
- .open = lpass_pcm_mi2s_open,
- .hw_params = lpass_pcm_mi2s_hw_params,
- .hw_free = lpass_pcm_mi2s_hw_free,
- .prepare = lpass_pcm_mi2s_prepare,
- .mmap = lpass_pcm_mi2s_mmap,
- .pointer = lpass_pcm_mi2s_pointer,
- .ioctl = snd_pcm_lib_ioctl,
+};
Again the start and stopn are misplaced and should be in the trigger like with other drivers.
+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_runtime = substream->private_data;
- struct lpass_mi2s_data *drvdata =
snd_soc_platform_get_drvdata(soc_runtime->platform);
- lpass_pcm_mi2s_free_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
- disable_irq(drvdata->irqnum);
- free_irq(drvdata->irqnum, NULL);
This is weird, why the manual disable_irq()?
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 | 27 +++++++++++++++++++++++++++ sound/soc/qcom/Makefile | 6 ++++++ 2 files changed, 33 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..847530a7fe5caa980b763fc3f77889825c20f47b --- /dev/null +++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,27 @@ +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 || COMPILE_TEST + select SND_SIMPLE_CARD + 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..214f3617d016386b445466e14e47288f9179590a --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,6 @@ +# 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
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 | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
diff --git a/arch/arm/boot/dts/qcom-ipq8064.dtsi b/arch/arm/boot/dts/qcom-ipq8064.dtsi index 63b2146f563b541e4994697af5ee1bbb41a4abd1..f34f29c5683908a5d33c5e1ff14d2c4fb05737df 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,21 @@ ranges; compatible = "simple-bus";
+ lpass-cpu-mi2s { + compatible = "qcom,lpass-cpu-mi2s"; + status = "disabled"; + 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"; + reg = <0x28100000 0x10000>; + reg-names = "lpass-lpaif-mem"; + }; + qcom_pinmux: pinmux@800000 { compatible = "qcom,ipq8064-pinctrl"; reg = <0x800000 0x4000>; @@ -279,5 +295,12 @@ #clock-cells = <1>; #reset-cells = <1>; }; + + lcc: clock-controller@28000000 { + compatible = "qcom,lcc-ipq8064"; + reg = <0x28000000 0x1000>; + #clock-cells = <1>; + #reset-cells = <1>; + }; }; };
participants (2)
-
Kenneth Westfield
-
Mark Brown