[alsa-devel] [PATCH v3 00/10] ASoC: qcom: add support to apq8016 audio
Hi Mark,
Am resending the the patches with rebased on top of topic/qcom branch.
All these patches are tested and Acked.
Thankyou for reviewing the v2 patches, here is the v3 patchset after incorporating review comments and testing on Storm Board. This patchset adds apq8016 audio support into lpass driver. Existing Lpass driver can not be used as-it-is for apq8016 as it contains code specific to ipq806x. Also the driver only supports single i2s port, single dma channel and single bitclk control.
APQ8016 has 4 MI2S( Primary, Secondary, Tertiary, Quaternary) which can be routed to internal wcd codec or external codecs. This routing is controlled by 2 mux registers.
This patch series firstly re-organizes the lpass driver such that the SOC specific bits are moved away from the driver. And secondly the SOC specifics are now passed as lpass variant data which would include various register offsets, dma channel allocations and SOC specific clock handling.
Finally the last few patchs add apq8016 lpass and machine driver.
All these patches are tested for HDMI audio via adv7533 bridge and Analog audio on APQ8016-SBC, msm8916-mtp boards and Kenneth tested this patchset on ipq806x Storm board too.
Changes since v2(https://lkml.org/lkml/2015/5/16/236) - dropped 3 patches which are already applied by Mark. - rebased on top of topic/qcom branch. - changed dev_err to dev_warn spotted by Kenneth. - fixed typo spotted by kenneth.
Changes since v1(https://lwn.net/Articles/644266/) - Fixed Kconfig dependencies spotted by Kenneth - Fixed compilation error on ipq806x spotted by Kenneth - Fixed clk error messages spotted by Kenneth
Changes since RFC(https://lwn.net/Articles/642661/) - droped cleanup patches, as they are already applied by Mark. - prefixed shared functions with asoc_qcom_* as suggested by Kenneth - made LPASS driver depended on OF. - removed ipq806x specific bits from ipaif_reg.h - removed no_osr clk flag as suggested by Mark and Kenneth - fixed irq return values spotted by Kenneth - renamed the machine file to apq8016-sbc.c as suggested by Kenneth. - use dai->driver->id instead of dai->id as it breaks on Storm board.
--srini
Srinivas Kandagatla (10): ASoC: qcom: make lpass driver depend on OF ASoC: qcom: support bitclk and osrclk per i2s port ASoC: qcom: make osr clock optional ASoC: qcom: add dma channel control offset to variant data ASoC: qcom: Add ability to handle interrupts per dma channel ASoC: qcom: add bit map to track static dma channel allocations ASoC: qcom: Add apq8016 lpass driver support ASoC: qcom: add apq8016 sound card support ASoC: qcom: Document apq8016 bindings. ASoC: qcom: document apq8016 sbc machine driver bindings
.../devicetree/bindings/sound/qcom,apq8016-sbc.txt | 61 ++++++ .../devicetree/bindings/sound/qcom,lpass-cpu.txt | 13 +- include/dt-bindings/sound/apq8016-lpass.h | 9 + sound/soc/qcom/Kconfig | 17 ++ sound/soc/qcom/Makefile | 4 + sound/soc/qcom/apq8016_sbc.c | 215 ++++++++++++++++++ sound/soc/qcom/lpass-apq8016.c | 242 +++++++++++++++++++++ sound/soc/qcom/lpass-cpu.c | 77 +++++-- sound/soc/qcom/lpass-platform.c | 96 +++++--- sound/soc/qcom/lpass.h | 21 +- 10 files changed, 692 insertions(+), 63 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/qcom,apq8016-sbc.txt create mode 100644 include/dt-bindings/sound/apq8016-lpass.h create mode 100644 sound/soc/qcom/apq8016_sbc.c create mode 100644 sound/soc/qcom/lpass-apq8016.c
lpass driver only support DT so make this explicit in the kconfig.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index b30c2ba..190c27d 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -6,10 +6,12 @@ config SND_SOC_QCOM
config SND_SOC_LPASS_CPU tristate + depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_PLATFORM tristate + depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_IPQ806X
On Thursday 21 May 2015 22:52:41 Srinivas Kandagatla wrote:
config SND_SOC_LPASS_CPU tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_PLATFORM tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_IPQ806X
Could you instead make the drivers compile without OF being set?
I see that patch 7 and 8 introduces another two options doing
+config SND_SOC_LPASS_APQ8016 + tristate + depends on SND_SOC_QCOM + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM +
+config SND_SOC_APQ8016_SBC + tristate "SoC Audio support for APQ8016 SBC platforms" + depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST + select SND_SOC_LPASS_APQ8016 + help
so you get an obvious build failure here when SND_SOC_APQ8016_SBC gets selected with COMPILE_TEST on a platform that does not support OF.
Arnd
Thanks for looking at this patch.
On 22/05/15 08:24, Arnd Bergmann wrote:
On Thursday 21 May 2015 22:52:41 Srinivas Kandagatla wrote:
config SND_SOC_LPASS_CPU tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_PLATFORM tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_IPQ806X
Could you instead make the drivers compile without OF being set?
I see that patch 7 and 8 introduces another two options doing
+config SND_SOC_LPASS_APQ8016
tristate
depends on SND_SOC_QCOM
select SND_SOC_LPASS_CPU
select SND_SOC_LPASS_PLATFORM
+config SND_SOC_APQ8016_SBC
tristate "SoC Audio support for APQ8016 SBC platforms"
depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST
select SND_SOC_LPASS_APQ8016
help
so you get an obvious build failure here when SND_SOC_APQ8016_SBC gets selected with COMPILE_TEST on a platform that does not support OF.
Yes, this would fail. I think moving of_device_id structures in drivers under #ifdef CONFIG_OF and removing the depends on OF would fix it.
--srini
Arnd
On Friday 22 May 2015 12:53:44 Srinivas Kandagatla wrote:
Thanks for looking at this patch.
On 22/05/15 08:24, Arnd Bergmann wrote:
On Thursday 21 May 2015 22:52:41 Srinivas Kandagatla wrote:
config SND_SOC_LPASS_CPU tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_PLATFORM tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_IPQ806X
Could you instead make the drivers compile without OF being set?
I see that patch 7 and 8 introduces another two options doing
+config SND_SOC_LPASS_APQ8016
tristate
depends on SND_SOC_QCOM
select SND_SOC_LPASS_CPU
select SND_SOC_LPASS_PLATFORM
+config SND_SOC_APQ8016_SBC
tristate "SoC Audio support for APQ8016 SBC platforms"
depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST
select SND_SOC_LPASS_APQ8016
help
so you get an obvious build failure here when SND_SOC_APQ8016_SBC gets selected with COMPILE_TEST on a platform that does not support OF.
Yes, this would fail. I think moving of_device_id structures in drivers under #ifdef CONFIG_OF and removing the depends on OF would fix it.
Why?
Arnd
On 22/05/15 14:21, Arnd Bergmann wrote:
On Friday 22 May 2015 12:53:44 Srinivas Kandagatla wrote:
Thanks for looking at this patch.
On 22/05/15 08:24, Arnd Bergmann wrote:
On Thursday 21 May 2015 22:52:41 Srinivas Kandagatla wrote:
config SND_SOC_LPASS_CPU tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_PLATFORM tristate
depends on OF select REGMAP_MMIO
config SND_SOC_LPASS_IPQ806X
Could you instead make the drivers compile without OF being set?
I see that patch 7 and 8 introduces another two options doing
+config SND_SOC_LPASS_APQ8016
tristate
depends on SND_SOC_QCOM
select SND_SOC_LPASS_CPU
select SND_SOC_LPASS_PLATFORM
+config SND_SOC_APQ8016_SBC
tristate "SoC Audio support for APQ8016 SBC platforms"
depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST
select SND_SOC_LPASS_APQ8016
help
so you get an obvious build failure here when SND_SOC_APQ8016_SBC gets selected with COMPILE_TEST on a platform that does not support OF.
Yes, this would fail. I think moving of_device_id structures in drivers under #ifdef CONFIG_OF and removing the depends on OF would fix it.
Why?
Yes, you are correct we don't need the ifdef's anymore. I will send a fixup patch on top of what Mark has already applied.
--srini
Arnd
On Friday 22 May 2015 09:24:35 Arnd Bergmann wrote:
Could you instead make the drivers compile without OF being set?
I see that patch 7 and 8 introduces another two options doing
+config SND_SOC_LPASS_APQ8016
tristate
depends on SND_SOC_QCOM
select SND_SOC_LPASS_CPU
select SND_SOC_LPASS_PLATFORM
+config SND_SOC_APQ8016_SBC
tristate "SoC Audio support for APQ8016 SBC platforms"
depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST
select SND_SOC_LPASS_APQ8016
help
so you get an obvious build failure here when SND_SOC_APQ8016_SBC gets selected with COMPILE_TEST on a platform that does not support OF.
Actually, the patch 9bae4880acee ("ASoC: qcom: move ipq806x specific bits out of lpass driver.") already broke things more after I fixed it up. Please fold in this bit too before you send it again, and try to test the changes you send:
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index b30c2baa7501..08ebc9b8f915 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -20,7 +20,7 @@ config SND_SOC_LPASS_IPQ806X
config SND_SOC_STORM tristate "ASoC I2S support for Storm boards" - depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST + depends on SND_SOC_QCOM && (ARCH_QCOM || COMPILE_TEST) select SND_SOC_LPASS_IPQ806X select SND_SOC_MAX98357A help
Arnd
This patch adds support to allow bitclk and osrclk per i2s dai port. on APQ8016 there are 4 i2s ports each one has its own bit clks.
Without this patch its not possible to support multiple i2s ports in the lpass driver.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/lpass-cpu.c | 60 ++++++++++++++++++++++++++++++---------------- sound/soc/qcom/lpass.h | 5 ++-- 2 files changed, 43 insertions(+), 22 deletions(-)
diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index fd18133..96cb495 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -33,7 +33,7 @@ static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id, struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); int ret;
- ret = clk_set_rate(drvdata->mi2s_osr_clk, freq); + ret = clk_set_rate(drvdata->mi2s_osr_clk[dai->driver->id], freq); if (ret) dev_err(dai->dev, "%s() error setting mi2s osrclk to %u: %d\n", __func__, freq, ret); @@ -47,18 +47,18 @@ static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream, struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); int ret;
- ret = clk_prepare_enable(drvdata->mi2s_osr_clk); + ret = clk_prepare_enable(drvdata->mi2s_osr_clk[dai->diver->id]); 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); + ret = clk_prepare_enable(drvdata->mi2s_bit_clk[dai->driver->id]); if (ret) { dev_err(dai->dev, "%s() error in enabling mi2s bit clk: %d\n", __func__, ret); - clk_disable_unprepare(drvdata->mi2s_osr_clk); + clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); return ret; }
@@ -70,8 +70,8 @@ static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream, { struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
- clk_disable_unprepare(drvdata->mi2s_bit_clk); - clk_disable_unprepare(drvdata->mi2s_osr_clk); + clk_disable_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]); + clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); }
static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream, @@ -146,7 +146,8 @@ static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream, return ret; }
- ret = clk_set_rate(drvdata->mi2s_bit_clk, rate * bitwidth * 2); + ret = clk_set_rate(drvdata->mi2s_bit_clk[dai->driver->id], + rate * bitwidth * 2); if (ret) { dev_err(dai->dev, "%s() error setting mi2s bitclk to %u: %d\n", __func__, rate * bitwidth * 2, ret); @@ -354,7 +355,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) struct lpass_variant *variant; struct device *dev = &pdev->dev; const struct of_device_id *match; - int ret; + char clk_name[16]; + int ret, i, dai_id;
dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0); if (dsp_of_node) { @@ -400,18 +402,36 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) if (variant->init) variant->init(pdev);
- drvdata->mi2s_osr_clk = devm_clk_get(&pdev->dev, "mi2s-osr-clk"); - if (IS_ERR(drvdata->mi2s_osr_clk)) { - dev_err(&pdev->dev, "%s() error getting mi2s-osr-clk: %ld\n", - __func__, PTR_ERR(drvdata->mi2s_osr_clk)); - return PTR_ERR(drvdata->mi2s_osr_clk); - } - - drvdata->mi2s_bit_clk = devm_clk_get(&pdev->dev, "mi2s-bit-clk"); - if (IS_ERR(drvdata->mi2s_bit_clk)) { - dev_err(&pdev->dev, "%s() error getting mi2s-bit-clk: %ld\n", - __func__, PTR_ERR(drvdata->mi2s_bit_clk)); - return PTR_ERR(drvdata->mi2s_bit_clk); + for (i = 0; i < variant->num_dai; i++) { + dai_id = variant->dai_driver[i].id; + if (variant->num_dai > 1) + sprintf(clk_name, "mi2s-osr-clk%d", i); + else + sprintf(clk_name, "mi2s-osr-clk"); + + drvdata->mi2s_osr_clk[dai_id] = devm_clk_get(&pdev->dev, + clk_name); + if (IS_ERR(drvdata->mi2s_osr_clk[dai_id])) { + dev_err(&pdev->dev, + "%s() error getting mi2s-osr-clk: %ld\n", + __func__, + PTR_ERR(drvdata->mi2s_osr_clk[dai_id])); + return PTR_ERR(drvdata->mi2s_osr_clk[dai_id]); + } + + if (variant->num_dai > 1) + sprintf(clk_name, "mi2s-bit-clk%d", i); + else + sprintf(clk_name, "mi2s-bit-clk"); + + drvdata->mi2s_bit_clk[dai_id] = devm_clk_get(&pdev->dev, + clk_name); + if (IS_ERR(drvdata->mi2s_bit_clk[dai_id])) { + dev_err(&pdev->dev, + "%s() error getting mi2s-bit-clk: %ld\n", + __func__, PTR_ERR(drvdata->mi2s_bit_clk[i])); + return PTR_ERR(drvdata->mi2s_bit_clk[dai_id]); + } }
drvdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix-clk"); diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index caaf17f..75e9370 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -22,6 +22,7 @@ #include <linux/regmap.h>
#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 +#define LPASS_MAX_MI2S_PORTS (8)
/* Both the CPU DAI and platform drivers will access this data */ struct lpass_data { @@ -30,10 +31,10 @@ struct lpass_data { struct clk *ahbix_clk;
/* MI2S system clock */ - struct clk *mi2s_osr_clk; + struct clk *mi2s_osr_clk[LPASS_MAX_MI2S_PORTS];
/* MI2S bit clock (derived from system clock by a divider */ - struct clk *mi2s_bit_clk; + struct clk *mi2s_bit_clk[LPASS_MAX_MI2S_PORTS];
/* low-power audio interface (LPAIF) registers */ void __iomem *lpaif;
On Thu, May 21, 2015 at 10:52:49PM +0100, Srinivas Kandagatla wrote:
This patch adds support to allow bitclk and osrclk per i2s dai port. on APQ8016 there are 4 i2s ports each one has its own bit clks.
Applied, thanks.
Some LPASS integrations like on APQ8016 do not have OSR clk, so making osr clk optional would allow such integrations to use lpass driver.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/lpass-cpu.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 96cb495..407e24d 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -33,6 +33,9 @@ static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id, struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); int ret;
+ if (IS_ERR(drvdata->mi2s_osr_clk[dai->driver->id])) + return 0; + ret = clk_set_rate(drvdata->mi2s_osr_clk[dai->driver->id], freq); if (ret) dev_err(dai->dev, "%s() error setting mi2s osrclk to %u: %d\n", @@ -47,18 +50,23 @@ static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream, struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); int ret;
- ret = clk_prepare_enable(drvdata->mi2s_osr_clk[dai->diver->id]); - if (ret) { - dev_err(dai->dev, "%s() error in enabling mi2s osr clk: %d\n", - __func__, ret); - return ret; + if (!IS_ERR(drvdata->mi2s_osr_clk[dai->driver->id])) { + ret = clk_prepare_enable( + drvdata->mi2s_osr_clk[dai->driver->id]); + 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[dai->driver->id]); if (ret) { dev_err(dai->dev, "%s() error in enabling mi2s bit clk: %d\n", __func__, ret); - clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); + if (!IS_ERR(drvdata->mi2s_osr_clk[dai->driver->id])) + clk_disable_unprepare( + drvdata->mi2s_osr_clk[dai->driver->id]); return ret; }
@@ -71,7 +79,9 @@ static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream, struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]); - clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); + + if (!IS_ERR(drvdata->mi2s_osr_clk[dai->driver->id])) + clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); }
static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream, @@ -412,11 +422,10 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) drvdata->mi2s_osr_clk[dai_id] = devm_clk_get(&pdev->dev, clk_name); if (IS_ERR(drvdata->mi2s_osr_clk[dai_id])) { - dev_err(&pdev->dev, + dev_warn(&pdev->dev, "%s() error getting mi2s-osr-clk: %ld\n", __func__, PTR_ERR(drvdata->mi2s_osr_clk[dai_id])); - return PTR_ERR(drvdata->mi2s_osr_clk[dai_id]); }
if (variant->num_dai > 1)
On Thu, May 21, 2015 at 10:52:57PM +0100, Srinivas Kandagatla wrote:
Some LPASS integrations like on APQ8016 do not have OSR clk, so making osr clk optional would allow such integrations to use lpass driver.
Applied, thanks.
This patch adds ability to pass dma channel control bits start offset, which differ in differnet qcom SOCs. On apq8016 dma channel control bits start after an offset of 1.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/lpass-platform.c | 2 +- sound/soc/qcom/lpass.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index fc08891..8ab0ac1 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -91,7 +91,7 @@ static int lpass_platform_pcmops_hw_params(struct snd_pcm_substream *substream, unsigned int channels = params_channels(params); unsigned int regval; int bitwidth; - int ret, rdma_port = pcm_data->i2s_port; + int ret, rdma_port = pcm_data->i2s_port + v->rdmactl_audif_start;
bitwidth = snd_pcm_format_width(format); if (bitwidth < 0) { diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index 75e9370..023170a 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -61,6 +61,11 @@ struct lpass_variant { u32 rdma_reg_stride; u32 rdma_channels;
+ /** + * on SOCs like APQ8016 the channel control bits start + * at different offset to ipq806x + **/ + u32 rdmactl_audif_start; /* SOC specific intialization like clocks */ int (*init)(struct platform_device *pdev); int (*exit)(struct platform_device *pdev);
On Thu, May 21, 2015 at 10:53:05PM +0100, Srinivas Kandagatla wrote:
This patch adds ability to pass dma channel control bits start offset, which differ in differnet qcom SOCs. On apq8016 dma channel control bits start after an offset of 1.
Applied, thanks.
This patch adds ablity to lpass driver to handle interrupt per dma channel. Without this patch its not possible to use multipl ports on the lpass.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/lpass-platform.c | 94 ++++++++++++++++++++++++++--------------- sound/soc/qcom/lpass.h | 4 ++ 2 files changed, 63 insertions(+), 35 deletions(-)
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index 8ab0ac1..79688aa 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -356,27 +356,15 @@ static struct snd_pcm_ops lpass_platform_pcm_ops = { .mmap = lpass_platform_pcmops_mmap, };
-static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data) +static irqreturn_t lpass_dma_interrupt_handler( + struct snd_pcm_substream *substream, + struct lpass_data *drvdata, + int chan, u32 interrupts) { - struct snd_pcm_substream *substream = data; struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct lpass_data *drvdata = - snd_soc_platform_get_drvdata(soc_runtime->platform); struct lpass_variant *v = drvdata->variant; - struct lpass_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(soc_runtime); - unsigned int interrupts; irqreturn_t ret = IRQ_NONE; - int rv, chan = pcm_data->rdma_ch; - - rv = regmap_read(drvdata->lpaif_map, - LPAIF_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &interrupts); - if (rv) { - dev_err(soc_runtime->dev, "%s() error reading from irqstat reg: %d\n", - __func__, rv); - return IRQ_NONE; - } - - interrupts &= LPAIF_IRQ_ALL(chan); + int rv;
if (interrupts & LPAIF_IRQ_PER(chan)) { rv = regmap_write(drvdata->lpaif_map, @@ -422,6 +410,35 @@ static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data) return ret; }
+static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data) +{ + struct lpass_data *drvdata = data; + struct lpass_variant *v = drvdata->variant; + unsigned int irqs; + int rv, chan; + + rv = regmap_read(drvdata->lpaif_map, + LPAIF_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs); + if (rv) { + pr_err("%s() error reading from irqstat reg: %d\n", + __func__, rv); + return IRQ_NONE; + } + + /* Handle per channel interrupts */ + for (chan = 0; chan < LPASS_MAX_DMA_CHANNELS; chan++) { + if (irqs & LPAIF_IRQ_ALL(chan) && drvdata->substream[chan]) { + rv = lpass_dma_interrupt_handler( + drvdata->substream[chan], + drvdata, chan, irqs); + if (rv != IRQ_HANDLED) + return rv; + } + } + + return IRQ_HANDLED; +} + static int lpass_platform_alloc_buffer(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *soc_runtime) { @@ -477,6 +494,7 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) if (IS_ERR_VALUE(data->rdma_ch)) return data->rdma_ch;
+ drvdata->substream[data->rdma_ch] = substream; data->i2s_port = cpu_dai->driver->id;
snd_soc_pcm_set_drvdata(soc_runtime, data); @@ -488,29 +506,12 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) if (ret) return ret;
- ret = devm_request_irq(soc_runtime->dev, drvdata->lpaif_irq, - lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING, - "lpass-irq-lpaif", substream); - if (ret) { - dev_err(soc_runtime->dev, "%s() irq request failed: %d\n", - __func__, ret); - goto err_buf; - } - - /* ensure audio hardware is disabled */ - ret = regmap_write(drvdata->lpaif_map, - LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0); - if (ret) { - dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n", - __func__, ret); - return ret; - } ret = regmap_write(drvdata->lpaif_map, LPAIF_RDMACTL_REG(v, data->rdma_ch), 0); if (ret) { dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", __func__, ret); - return ret; + goto err_buf; }
return 0; @@ -530,6 +531,8 @@ static void lpass_platform_pcm_free(struct snd_pcm *pcm) struct lpass_pcm_data *data = snd_soc_pcm_get_drvdata(soc_runtime); struct lpass_variant *v = drvdata->variant;
+ drvdata->substream[data->rdma_ch] = NULL; + if (v->free_dma_channel) v->free_dma_channel(drvdata, data->rdma_ch);
@@ -545,6 +548,8 @@ static struct snd_soc_platform_driver lpass_platform_driver = { int asoc_qcom_lpass_platform_register(struct platform_device *pdev) { struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct lpass_variant *v = drvdata->variant; + int ret;
drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif"); if (drvdata->lpaif_irq < 0) { @@ -553,6 +558,25 @@ int asoc_qcom_lpass_platform_register(struct platform_device *pdev) return -ENODEV; }
+ /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0); + if (ret) { + dev_err(&pdev->dev, "%s() error writing to irqen reg: %d\n", + __func__, ret); + return ret; + } + + ret = devm_request_irq(&pdev->dev, drvdata->lpaif_irq, + lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING, + "lpass-irq-lpaif", drvdata); + if (ret) { + dev_err(&pdev->dev, "%s() irq request failed: %d\n", + __func__, ret); + return ret; + } + + return devm_snd_soc_register_platform(&pdev->dev, &lpass_platform_driver); } diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index 023170a..d572e7b 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -23,6 +23,7 @@
#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 #define LPASS_MAX_MI2S_PORTS (8) +#define LPASS_MAX_DMA_CHANNELS (8)
/* Both the CPU DAI and platform drivers will access this data */ struct lpass_data { @@ -47,6 +48,9 @@ struct lpass_data {
/* SOC specific variations in the LPASS IP integration */ struct lpass_variant *variant; + + /* used it for handling interrupt per dma channel */ + struct snd_pcm_substream *substream[LPASS_MAX_DMA_CHANNELS]; };
/* Vairant data per each SOC */
On Thu, May 21, 2015 at 10:53:14PM +0100, Srinivas Kandagatla wrote:
This patch adds ablity to lpass driver to handle interrupt per dma channel. Without this patch its not possible to use multipl ports on the lpass.
Applied, thanks.
This patch adds dma channel bit mask to lpass data to keep track of dma channel allocations. This flag would be used in apq8016 lpass driver.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/lpass.h | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index d572e7b..deecae9 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -49,6 +49,9 @@ struct lpass_data { /* SOC specific variations in the LPASS IP integration */ struct lpass_variant *variant;
+ /* bit map to keep track of static channel allocations */ + unsigned long rdma_ch_bit_map; + /* used it for handling interrupt per dma channel */ struct snd_pcm_substream *substream[LPASS_MAX_DMA_CHANNELS]; };
On Thu, May 21, 2015 at 10:53:21PM +0100, Srinivas Kandagatla wrote:
This patch adds dma channel bit mask to lpass data to keep track of dma channel allocations. This flag would be used in apq8016 lpass driver.
Applied, thanks.
This patch adds apq8016 lpass driver support. APQ8016 has 4 MI2S which can be routed to one internal codec and 2 external codec interfaces.
Primary, Secondary, Quaternary I2S can do Rx(playback) and Tertiary and Quaternary can do Tx(capture).
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- include/dt-bindings/sound/apq8016-lpass.h | 9 ++ sound/soc/qcom/Kconfig | 6 + sound/soc/qcom/Makefile | 2 + sound/soc/qcom/lpass-apq8016.c | 242 ++++++++++++++++++++++++++++++ sound/soc/qcom/lpass.h | 4 + 5 files changed, 263 insertions(+) create mode 100644 include/dt-bindings/sound/apq8016-lpass.h create mode 100644 sound/soc/qcom/lpass-apq8016.c
diff --git a/include/dt-bindings/sound/apq8016-lpass.h b/include/dt-bindings/sound/apq8016-lpass.h new file mode 100644 index 0000000..499076e --- /dev/null +++ b/include/dt-bindings/sound/apq8016-lpass.h @@ -0,0 +1,9 @@ +#ifndef __DT_APQ8016_LPASS_H +#define __DT_APQ8016_LPASS_H + +#define MI2S_PRIMARY 0 +#define MI2S_SECONDARY 1 +#define MI2S_TERTIARY 2 +#define MI2S_QUATERNARY 3 + +#endif /* __DT_APQ8016_LPASS_H */ diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 190c27d..eb2447b 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -20,6 +20,12 @@ config SND_SOC_LPASS_IPQ806X select SND_SOC_LPASS_CPU select SND_SOC_LPASS_PLATFORM
+config SND_SOC_LPASS_APQ8016 + tristate + depends on SND_SOC_QCOM + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + config SND_SOC_STORM tristate "ASoC I2S support for Storm boards" depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index f8aab91..ac76308 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -2,10 +2,12 @@ snd-soc-lpass-cpu-objs := lpass-cpu.o snd-soc-lpass-platform-objs := lpass-platform.o snd-soc-lpass-ipq806x-objs := lpass-ipq806x.o +snd-soc-lpass-apq8016-objs := lpass-apq8016.o
obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o obj-$(CONFIG_SND_SOC_LPASS_IPQ806X) += snd-soc-lpass-ipq806x.o +obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
# Machine snd-soc-storm-objs := storm.o diff --git a/sound/soc/qcom/lpass-apq8016.c b/sound/soc/qcom/lpass-apq8016.c new file mode 100644 index 0000000..367fc46 --- /dev/null +++ b/sound/soc/qcom/lpass-apq8016.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2010-2011,2013-2015 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. + * + * lpass-apq8016.c -- ALSA SoC CPU DAI driver for APQ8016 LPASS + * + */ + + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include <dt-bindings/sound/apq8016-lpass.h> +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = { + [MI2S_PRIMARY] = { + .id = MI2S_PRIMARY, + .name = "Primary MI2S", + .playback = { + .stream_name = "Primary 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 = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_SECONDARY] = { + .id = MI2S_SECONDARY, + .name = "Secondary MI2S", + .playback = { + .stream_name = "Secondary 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 = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_TERTIARY] = { + .id = MI2S_TERTIARY, + .name = "Tertiary MI2S", + .capture = { + .stream_name = "Tertiary Capture", + .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 = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_QUATERNARY] = { + .id = MI2S_QUATERNARY, + .name = "Quatenary MI2S", + .playback = { + .stream_name = "Quatenary 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, + }, + .capture = { + .stream_name = "Quatenary Capture", + .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 = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, +}; + +static int apq8016_lpass_alloc_dma_channel(struct lpass_data *drvdata) +{ + struct lpass_variant *v = drvdata->variant; + int chan = find_first_zero_bit(&drvdata->rdma_ch_bit_map, + v->rdma_channels); + + if (chan >= v->rdma_channels) + return -EBUSY; + + set_bit(chan, &drvdata->rdma_ch_bit_map); + + return chan; +} + +static int apq8016_lpass_free_dma_channel(struct lpass_data *drvdata, int chan) +{ + clear_bit(chan, &drvdata->rdma_ch_bit_map); + + return 0; +} + +static int apq8016_lpass_init(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int ret; + + drvdata->pcnoc_mport_clk = devm_clk_get(dev, "pcnoc-mport-clk"); + if (IS_ERR(drvdata->pcnoc_mport_clk)) { + dev_err(&pdev->dev, "%s() error getting pcnoc-mport-clk: %ld\n", + __func__, PTR_ERR(drvdata->pcnoc_mport_clk)); + return PTR_ERR(drvdata->pcnoc_mport_clk); + } + + ret = clk_prepare_enable(drvdata->pcnoc_mport_clk); + if (ret) { + dev_err(&pdev->dev, "%s() Error enabling pcnoc-mport-clk: %d\n", + __func__, ret); + return ret; + } + + drvdata->pcnoc_sway_clk = devm_clk_get(dev, "pcnoc-sway-clk"); + if (IS_ERR(drvdata->pcnoc_sway_clk)) { + dev_err(&pdev->dev, "%s() error getting pcnoc-sway-clk: %ld\n", + __func__, PTR_ERR(drvdata->pcnoc_sway_clk)); + return PTR_ERR(drvdata->pcnoc_sway_clk); + } + + ret = clk_prepare_enable(drvdata->pcnoc_sway_clk); + if (ret) { + dev_err(&pdev->dev, "%s() Error enabling pcnoc_sway_clk: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int apq8016_lpass_exit(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->pcnoc_mport_clk); + clk_disable_unprepare(drvdata->pcnoc_sway_clk); + + return 0; +} + + +struct lpass_variant apq8016_data = { + .i2sctrl_reg_base = 0x1000, + .i2sctrl_reg_stride = 0x1000, + .i2s_ports = 4, + .irq_reg_base = 0x6000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0x8400, + .rdma_reg_stride = 0x1000, + .rdma_channels = 2, + .rdmactl_audif_start = 1, + .dai_driver = apq8016_lpass_cpu_dai_driver, + .num_dai = ARRAY_SIZE(apq8016_lpass_cpu_dai_driver), + .init = apq8016_lpass_init, + .exit = apq8016_lpass_exit, + .alloc_dma_channel = apq8016_lpass_alloc_dma_channel, + .free_dma_channel = apq8016_lpass_free_dma_channel, +}; + +static const struct of_device_id apq8016_lpass_cpu_device_id[] = { + { .compatible = "qcom,lpass-cpu-apq8016", .data = &apq8016_data }, + {} +}; +MODULE_DEVICE_TABLE(of, apq8016_lpass_cpu_device_id); + +static struct platform_driver apq8016_lpass_cpu_platform_driver = { + .driver = { + .name = "apq8016-lpass-cpu", + .of_match_table = of_match_ptr(apq8016_lpass_cpu_device_id), + }, + .probe = asoc_qcom_lpass_cpu_platform_probe, + .remove = asoc_qcom_lpass_cpu_platform_remove, +}; +module_platform_driver(apq8016_lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("APQ8016 LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index deecae9..d6e86c1 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -54,6 +54,10 @@ struct lpass_data {
/* used it for handling interrupt per dma channel */ struct snd_pcm_substream *substream[LPASS_MAX_DMA_CHANNELS]; + + /* 8016 specific */ + struct clk *pcnoc_mport_clk; + struct clk *pcnoc_sway_clk; };
/* Vairant data per each SOC */
This patch adds apq8016 machine driver support. This patch was tested on two apq8016-sbc and msm8916-mtp board for both hdmi and analog audio features.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 9 ++ sound/soc/qcom/Makefile | 2 + sound/soc/qcom/apq8016_sbc.c | 215 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 sound/soc/qcom/apq8016_sbc.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index eb2447b..43b1f8c 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -34,3 +34,12 @@ config SND_SOC_STORM help Say Y or M if you want add support for SoC audio on the Qualcomm Technologies IPQ806X-based Storm board. + +config SND_SOC_APQ8016_SBC + tristate "SoC Audio support for APQ8016 SBC platforms" + depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST + select SND_SOC_LPASS_APQ8016 + help + Support for Qualcomm Technologies LPASS audio block in + APQ8016 SOC-based systems. + Say Y if you want to use audio devices on MI2S diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index ac76308..79e5c50 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -11,5 +11,7 @@ obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
# Machine snd-soc-storm-objs := storm.o +snd-soc-apq8016-sbc-objs := apq8016_sbc.o
obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o +obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o diff --git a/sound/soc/qcom/apq8016_sbc.c b/sound/soc/qcom/apq8016_sbc.c new file mode 100644 index 0000000..8958633 --- /dev/null +++ b/sound/soc/qcom/apq8016_sbc.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2015 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/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <dt-bindings/sound/apq8016-lpass.h> + +struct apq8016_sbc_data { + void __iomem *mic_iomux; + void __iomem *spkr_iomux; + struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ +}; + +#define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17) +#define MIC_CTRL_TLMM_SCLK_EN BIT(1) +#define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16)) + +static int apq8016_sbc_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card); + + if (cpu_dai->id == MI2S_QUATERNARY) { + /* Configure the Quat MI2S to TLMM */ + writel(readl(pdata->mic_iomux) | + MIC_CTRL_QUA_WS_SLAVE_SEL_10 | + MIC_CTRL_TLMM_SCLK_EN, + pdata->mic_iomux); + + return 0; + } else if (cpu_dai->id == MI2S_PRIMARY) { + writel(readl(pdata->spkr_iomux) | + SPKR_CTL_PRI_WS_SLAVE_SEL_11, + pdata->spkr_iomux); + + return 0; + } + + dev_err(card->dev, "unsupported cpu dai configuration\n"); + + return -EINVAL; +} + +static struct snd_soc_ops apq8016_sbc_soc_ops = { + .startup = apq8016_sbc_startup, +}; + +static struct apq8016_sbc_data *apq8016_sbc_parse_of(struct snd_soc_card *card) +{ + int num_links; + struct device *dev = card->dev; + struct snd_soc_dai_link *dai_link; + struct device_node *np, *codec, *cpu, *node = dev->of_node; + struct apq8016_sbc_data *data; + char *name; + int ret; + + /* Populate links */ + num_links = of_get_child_count(node); + + /* Allocate the private data and the DAI link array */ + data = devm_kzalloc(dev, sizeof(*data) + sizeof(*dai_link) * num_links, + GFP_KERNEL); + if (!data) + return ERR_PTR(-ENOMEM); + + card->dai_link = &data->dai_link[0]; + card->num_links = num_links; + + dai_link = data->dai_link; + + for_each_child_of_node(node, np) { + cpu = of_get_child_by_name(np, "cpu"); + codec = of_get_child_by_name(np, "codec"); + + if (!cpu || !codec) { + dev_err(dev, "Can't find cpu/codec DT node\n"); + return ERR_PTR(-EINVAL); + } + + dai_link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + if (!dai_link->cpu_of_node) { + dev_err(card->dev, "error getting cpu phandle\n"); + return ERR_PTR(-EINVAL); + } + + dai_link->codec_of_node = of_parse_phandle(codec, + "sound-dai", + 0); + if (!dai_link->codec_of_node) { + dev_err(card->dev, "error getting codec phandle\n"); + return ERR_PTR(-EINVAL); + } + + ret = snd_soc_of_get_dai_name(cpu, &dai_link->cpu_dai_name); + if (ret) { + dev_err(card->dev, "error getting cpu dai name\n"); + return ERR_PTR(ret); + } + + ret = snd_soc_of_get_dai_name(codec, &dai_link->codec_dai_name); + if (ret) { + dev_err(card->dev, "error getting codec dai name\n"); + return ERR_PTR(ret); + } + + dai_link->platform_of_node = dai_link->cpu_of_node; + /* For now we only support playback */ + dai_link->playback_only = true; + + if (of_property_read_bool(np, "external")) + name = "HDMI"; + + else + name = "Headset"; + + dai_link->ops = &apq8016_sbc_soc_ops; + dai_link->name = dai_link->stream_name = name; + + dai_link++; + } + + return data; +} + +static int apq8016_sbc_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + struct apq8016_sbc_data *data; + struct resource *res; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + data = apq8016_sbc_parse_of(card); + if (IS_ERR(data)) { + dev_err(&pdev->dev, "Error resolving dai links: %ld\n", + PTR_ERR(data)); + return PTR_ERR(data); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux"); + data->mic_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->mic_iomux)) + return PTR_ERR(data->mic_iomux); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux"); + data->spkr_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->spkr_iomux)) + return PTR_ERR(data->spkr_iomux); + + platform_set_drvdata(pdev, data); + snd_soc_card_set_drvdata(card, data); + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(&pdev->dev, "Error parsing card name: %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, "Error registering soundcard: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id apq8016_sbc_device_id[] = { + { .compatible = "qcom,apq8016-sbc-sndcard" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id); + +static struct platform_driver apq8016_sbc_platform_driver = { + .driver = { + .name = "qcom-apq8016-sbc", + .of_match_table = of_match_ptr(apq8016_sbc_device_id), + }, + .probe = apq8016_sbc_platform_probe, +}; +module_platform_driver(apq8016_sbc_platform_driver); + +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2");
This patch updates lpass bindings with apq8016 specific bindings.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt index e00732d..21c6483 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt @@ -4,12 +4,21 @@ This node models the Qualcomm Technologies Low-Power Audio SubSystem (LPASS).
Required properties:
-- compatible : "qcom,lpass-cpu" +- compatible : "qcom,lpass-cpu" or "qcom,apq8016-lpass-cpu" - clocks : Must contain an entry for each entry in clock-names. - clock-names : A list which must include the following entries: * "ahbix-clk" * "mi2s-osr-clk" * "mi2s-bit-clk" + : required clocks for "qcom,lpass-cpu-apq8016" + * "ahbix-clk" + * "mi2s-bit-clk0" + * "mi2s-bit-clk1" + * "mi2s-bit-clk2" + * "mi2s-bit-clk3" + * "pcnoc-mport-clk" + * "pcnoc-sway-clk" + - interrupts : Must contain an entry for each entry in interrupt-names. - interrupt-names : A list which must include the following entries: @@ -22,6 +31,8 @@ Required properties: - reg-names : A list which must include the following entries: * "lpass-lpaif"
+ + Optional properties:
- qcom,adsp : Phandle for the audio DSP node
On Thu, May 21, 2015 at 10:53:45PM +0100, Srinivas Kandagatla wrote:
This patch updates lpass bindings with apq8016 specific bindings.
Applied, thanks.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org
Are these DT binding test tools public anywhere? :P
This patch adds bindings for apq8016 sbc machine driver. On APQ8016 4 MI2S can be configured to different sinks like internal codec/external codec, this connection is controlled via 2 iomux registers.
Tested-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Kenneth Westfield kwestfie@codeaurora.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/sound/qcom,apq8016-sbc.txt | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,apq8016-sbc.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,apq8016-sbc.txt b/Documentation/devicetree/bindings/sound/qcom,apq8016-sbc.txt new file mode 100644 index 0000000..01d62be --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,apq8016-sbc.txt @@ -0,0 +1,61 @@ +* Qualcomm Technologies APQ8016 SBC ASoC machine driver + +This node models the Qualcomm Technologies APQ8016 SBC ASoC machine driver + +Required properties: + +- compatible : "qcom,apq8016-sbc-sndcard" + +- pinctrl-N : One property must exist for each entry in + pinctrl-names. See ../pinctrl/pinctrl-bindings.txt + for details of the property values. +- pinctrl-names : Must contain a "default" entry. +- reg : Must contain an address for each entry in reg-names. +- reg-names : A list which must include the following entries: + * "mic-iomux" + * "spkr-iomux" +- qcom,model : Name of the sound card. + +Dai-link subnode properties and subnodes: + +Required dai-link subnodes: + +- cpu : CPU sub-node +- codec : CODEC sub-node + +Required CPU/CODEC subnodes properties: + +-sound-dai : phandle and port of CPU/CODEC +-capture-dai : phandle and port of CPU/CODEC + +Optional CPU/CODEC subnodes properties: +- external : flag to indicate if the I2S is connected to external codec +Example: + +sound: sound { + compatible = "qcom,apq8016-sbc-sndcard"; + reg = <0x07702000 0x4>, <0x07702004 0x4>; + reg-names = "mic-iomux", "spkr-iomux"; + qcom,model = "DB410c"; + + /* I2S - Internal codec */ + internal-dai-link@0 { + cpu { /* PRIMARY */ + sound-dai = <&lpass MI2S_PRIMARY>; + }; + codec { + sound-dai = <&wcd_codec 0>; + }; + }; + + /* External Primary or External Secondary -ADV7533 HDMI */ + external-dai-link@0 { + external; + cpu { /* QUAT */ + sound-dai = <&lpass MI2S_QUATERNARY>; + }; + codec { + sound-dai = <&adv_bridge 0>; + }; + }; +};
participants (3)
-
Arnd Bergmann
-
Mark Brown
-
Srinivas Kandagatla