[alsa-devel] [Patch V4 00/10] ASoC: QCOM: Add support for ipq806x SOC
From: Kenneth Westfield kwestfie@codeaurora.org
This patch series adds support for I2S audio playback 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 contains an MI2S port, which is what these drivers are configured to use. The LPAIF also contains a DMA engine that is dedicated to moving audio samples into the transmit FIFO of the MI2S port. In addition, there is also low-power memory (LPM) within the audio subsystem, which is used for buffering the audio samples.
The development board being used for testing contains the ipq806x SOC and a Maxim max98357a DAC/amp. One bus from the MI2S port of the SOC is connected to the DAC/amp for stereo playback. This bus is configured so that the SOC is bus master and consists of DATA, LRCLK, and BCLK. The DAC/amp does not need MCLK to operate. In addition, a single GPIO pin from the SOC is connected to the same DAC/amp, which gives enable/disable control over the DAC/amp.
The specific drivers added are: * a codec DAI driver for controlling the DAC/amp * a CPU DAI driver for controlling the MI2S port * a platform driver for controlling the LPAIF DMA engine
These drivers, together, are tied into simple-audio-card to complete the audio implementation. Corresponding additions to the device tree for the ipq806x SOC and its documentation has also been added. Also, as this is a new directory, the MAINTAINERS file has been updated as well.
The LPASS also contains clocks that need to be controlled. Those drivers have been submitted as a separate patch series: [PATCH v3 0/8] qcom audio clock control drivers http://lkml.org/lkml/2015/1/19/656
= Changes since V3 [Patch V3 00/10] ASoC: QCOM: Add support for ipq806x SOC http://mailman.alsa-project.org/pipermail/alsa-devel/2014-December/085694.ht...
* Placed the content of the inline functions into the callbacks. * Replaced use of readl/writel register access functions with regmap access functions. Notable exception is the ISR, which uses ioread32/iowrite32. * Rearranged the sequencing of the hardware block enables to fit within the ASoC framework callbacks, while remaining functional. REQ 1: The hardware requires the enable sequence to be: LPAIF-DMA[enable],then LPAIF-MI2S[enable], then DAC-GPIO[enable] REQ 2: The hardware requires the disable sequence to be: DAC-GPIO[disable], then LPAIF-MI2S[disable] * Corrected the implementation of the pointer callback. * Utilize the LPM to buffer audio samples, rather than memory external to LPASS. * Corrected the interrupt clearing in the ISR. * Implemented a default system clock (defined by the simple-card DT node), and optional LPASS DT node modifiers that can alter the system clock in order to expand the range of available bit clock frequencies. * Addressed all of the remaining issues raised by Mark Brown. * General code cleanup.
= Changes since V2 [Patch v2 00/11] ASoC: QCOM: Add support for ipq806x SOC http://mailman.alsa-project.org/pipermail/alsa-devel/2014-December/085186.ht...
* 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.
= Changes since V1 [PATCH 0/9] ASoC: QCOM: Add support for ipq806x SOC http://mailman.alsa-project.org/pipermail/alsa-devel/2014-November/084322.ht...
* Remove the native LPAIF driver, and move its functionality to the CPU DAI driver. * Add a codec driver to manage the pins going to the external DAC (previously managed by the machine driver). * Use devm_* and dev_* where possible. * ISR only handles relevant DMA channel now. * Update device tree documentation to reflect changes. * General code cleanup.
Kenneth Westfield (10): MAINTAINERS: Add QCOM audio ASoC maintainer ASoC: max98357a: 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 LPASS 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 + .../devicetree/bindings/sound/qcom,lpass-cpu.txt | 66 +++ 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 | 138 +++++ sound/soc/qcom/Kconfig | 27 + sound/soc/qcom/Makefile | 6 + sound/soc/qcom/lpass-cpu.c | 557 +++++++++++++++++++++ sound/soc/qcom/lpass-lpaif-ipq806x.h | 170 +++++++ sound/soc/qcom/lpass-platform.c | 514 +++++++++++++++++++ sound/soc/qcom/lpass.h | 64 +++ 15 files changed, 1594 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/max98357a.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-cpu.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.c create mode 100644 sound/soc/qcom/lpass-lpaif-ipq806x.h create mode 100644 sound/soc/qcom/lpass-platform.c create mode 100644 sound/soc/qcom/lpass.h
From: Kenneth Westfield kwestfie@codeaurora.org
Add maintainers for the Qualcomm Technologies sound drivers.
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 f1763c50f071a19df82ef6980c2c6e7f18cbc280..4cb829d0bd0ef452e23a687d9b9ec726e6dd0633 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5183,6 +5183,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 DAC.
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..a7a149a236e55b8372b7cb3622cd6a6c664d4e2d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/max98357a.txt @@ -0,0 +1,14 @@ +Maxim MAX98357A audio DAC + +This node models the Maxim MAX98357A DAC. + +Required properties: +- compatible : "maxim,max98357a" +- sdmode-gpios : GPIO specifier for the GPIO -> DAC SDMODE pin + +Example: + +max98357a { + compatible = "maxim,max98357a"; + sdmode-gpios = <&qcom_pinmux 25 0>; +};
On Thu, Feb 05, 2015 at 12:53:38PM -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 DAC.
Applied, thanks.
From: Kenneth Westfield kwestfie@codeaurora.org
Add documentation to the sound directory of the device-tree bindings for the IPQ806x LPASS CPU DAI device.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- .../devicetree/bindings/sound/qcom,lpass-cpu.txt | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt new file mode 100644 index 0000000000000000000000000000000000000000..7406ae52aec196f136883eb01afbc6c425bdc465 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt @@ -0,0 +1,66 @@ +* Qualcomm Technologies LPASS CPU DAI + +This node models the Qualcomm Technologies LPASS DAI ports. + +Required properties: + +- compatible : "qcom,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" +- interrupts : Must contain an entry for each entry in + interrupt-names. +- interrupt-names : A list which must include the following entries: + * "lpass-irq-lpaif" +- 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: + * "lpass-lpaif" + * "lpass-lpm" + +Optional properties: + +- qcom,system-clock-shift : Add this bool property if the default + frequency of the system clock needs to + be reduced. +- qcom,system-clock-shift-compare : A numerical value used to right-shift + the default system clock frequency for + comparison with the target bit clock + frequency. +- qcom,system-clock-shift-amount : A numerical value used to right-shift + the default system clock frequency. +- qcom,alternate-sysclk : Add this bool property if the default + frequency of the system clock cannot + divide down to the target bit clock + frequency. +- qcom,alternate-sysclk-bitwidth : A numerical value representing the + sample bitwidth which requires use of + the alternate system clock frequency. +- qcom,alternate-sysclk-frequency : A numerical value representing the new + system clock frequency to use. + +Example: + +lpass-cpu@28100000 { + compatible = "qcom,lpass-cpu"; + 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-irq-lpaif"; + pinctrl-names = "default", "idle"; + pinctrl-0 = <&mi2s_default>; + pinctrl-1 = <&mi2s_idle>; + reg = <0x28100000 0x10000>, <0x28400000 0x4000>; + reg-names = "lpass-lpaif", "lpass-lpm"; + qcom,system-clock-shift; + qcom,system-clock-shift-compare = <4>; + qcom,system-clock-shift-amount = <3>; + qcom,alternate-sysclk; + qcom,alternate-systclk-bitwidth = <24>; + qcom,alternate-systclk-frequency = <4608000>; +};
On Thu, Feb 05, 2015 at 12:53:39PM -0800, Kenneth Westfield wrote:
+- qcom,system-clock-shift : Add this bool property if the default
frequency of the system clock needs to
be reduced.
+- qcom,system-clock-shift-compare : A numerical value used to right-shift
the default system clock frequency for
comparison with the target bit clock
frequency.
+- qcom,system-clock-shift-amount : A numerical value used to right-shift
the default system clock frequency.
+- qcom,alternate-sysclk : Add this bool property if the default
frequency of the system clock cannot
divide down to the target bit clock
frequency.
+- qcom,alternate-sysclk-bitwidth : A numerical value representing the
sample bitwidth which requires use of
the alternate system clock frequency.
+- qcom,alternate-sysclk-frequency : A numerical value representing the new
system clock frequency to use.
None of these seem like they are appropriate for device tree properties, they appear to be choosing a specific clocking configuration which is something that would normally be done as part of the system integration in the machine driver rather than in the DAI driver. This binding won't work in cases where the clocks are being changed at runtime and would limit systems where that becomes possible in future.
Further, the interface seems too low level - it's specifying individual dividers and so on which would normally be things that can trivially be calculated or inferred given the input and target clock rates.
On Sat, Feb 07, 2015 at 06:18:23AM +0800, Mark Brown wrote:
On Thu, Feb 05, 2015 at 12:53:39PM -0800, Kenneth Westfield wrote:
+- qcom,system-clock-shift : Add this bool property if the default
frequency of the system clock needs to
be reduced.
+- qcom,system-clock-shift-compare : A numerical value used to right-shift
the default system clock frequency for
comparison with the target bit clock
frequency.
+- qcom,system-clock-shift-amount : A numerical value used to right-shift
the default system clock frequency.
+- qcom,alternate-sysclk : Add this bool property if the default
frequency of the system clock cannot
divide down to the target bit clock
frequency.
+- qcom,alternate-sysclk-bitwidth : A numerical value representing the
sample bitwidth which requires use of
the alternate system clock frequency.
+- qcom,alternate-sysclk-frequency : A numerical value representing the new
system clock frequency to use.
None of these seem like they are appropriate for device tree properties, they appear to be choosing a specific clocking configuration which is something that would normally be done as part of the system integration in the machine driver rather than in the DAI driver. This binding won't work in cases where the clocks are being changed at runtime and would limit systems where that becomes possible in future.
So I add a machine driver that selects the clocking freq in hw_params and calls set_sysclk in the DAIs.
The DT node for the machine driver would look something like: default_system_clock_frequency = < xxxxxx >; alternate_system_clock_frequency = < xxxxxx >; cpu_dai = < &cpu >; codec_dai = < &codec >; pinctrl... ?
Does this sound ok? Also, would it make sense to move the pinctrl back to the machine driver?
On Sun, Feb 08, 2015 at 10:38:23PM -0800, Kenneth Westfield wrote:
So I add a machine driver that selects the clocking freq in hw_params and calls set_sysclk in the DAIs.
The DT node for the machine driver would look something like: default_system_clock_frequency = < xxxxxx >; alternate_system_clock_frequency = < xxxxxx >; cpu_dai = < &cpu >; codec_dai = < &codec >; pinctrl... ?
Why are the system clock frequencies being specified in the DT at all - can't we either figure out the constraints from something else or just set the rates to something sensible that the driver knows (allowing for improvements in the driver in the future).
Does this sound ok? Also, would it make sense to move the pinctrl back to the machine driver?
Why would we want to do that?
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 | 138 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 sound/soc/codecs/max98357a.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3190eed43c38d8145536d1804f7686b108f97641..064e6c18e10923fd75609b750405dbc33f9db6af 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 select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C @@ -456,6 +457,9 @@ config SND_SOC_MAX98090 config SND_SOC_MAX98095 tristate
+config SND_SOC_MAX98357A + tristate + config SND_SOC_MAX9850 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bbdfd1e1c182f9ee102f532f391f522e77a8922b..69b8666d187a0e8d1e2a532e68e2509acc95da7e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -64,6 +64,7 @@ snd-soc-max9768-objs := max9768.o snd-soc-max98088-objs := max98088.o snd-soc-max98090-objs := max98090.o snd-soc-max98095-objs := max98095.o +snd-soc-max98357a-objs := max98357a.o snd-soc-max9850-objs := max9850.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o @@ -245,6 +246,7 @@ obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o obj-$(CONFIG_SND_SOC_MAX98090) += snd-soc-max98090.o obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o +obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o diff --git a/sound/soc/codecs/max98357a.c b/sound/soc/codecs/max98357a.c new file mode 100644 index 0000000000000000000000000000000000000000..98b915314d7a00390060411fb1b910aec6f46e9d --- /dev/null +++ b/sound/soc/codecs/max98357a.c @@ -0,0 +1,138 @@ +/* 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. + * + * 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, "%s() unable to get sdmode GPIO: %ld\n", + __func__, PTR_ERR(sdmode)); + 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 = DRV_NAME "-playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + }, + .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: %d\n", + __func__, ret); + + return ret; +} + +static int max98357a_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id max98357a_device_id[] = { + { .compatible = "maxim," DRV_NAME, }, + {} +}; +#endif + +static struct platform_driver max98357a_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(max98357a_device_id), + }, + .probe = max98357a_platform_probe, + .remove = max98357a_platform_remove, +}; +module_platform_driver(max98357a_platform_driver); + +MODULE_DESCRIPTION("Maxim MAX98357A Codec Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, max98357a_device_id);
On Thu, Feb 05, 2015 at 12:53:40PM -0800, Kenneth Westfield wrote:
From: Kenneth Westfield kwestfie@codeaurora.org
Add codec driver for the Maxim MAX98357A DAC.
Applied, thanks.
From: Kenneth Westfield kwestfie@codeaurora.org
Add the LPASS header files for ipq806x SOC. This includes the register definitions for the ipq806x LPAIF, and the structure definition for the driver data.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-lpaif-ipq806x.h | 170 +++++++++++++++++++++++++++++++++++ sound/soc/qcom/lpass.h | 64 +++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 sound/soc/qcom/lpass-lpaif-ipq806x.h create mode 100644 sound/soc/qcom/lpass.h
diff --git a/sound/soc/qcom/lpass-lpaif-ipq806x.h b/sound/soc/qcom/lpass-lpaif-ipq806x.h new file mode 100644 index 0000000000000000000000000000000000000000..0f6dc74e37a2b182865497585365974703a5a8b8 --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif-ipq806x.h @@ -0,0 +1,170 @@ +/* + * 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. + */ + +#ifndef __LPASS_LPAIF_H__ +#define __LPASS_LPAIF_H__ + +#define LPAIF_BANK_OFFSET 0x1000 + +/* LPAIF I2S */ + +#define LPAIF_I2SCTL_REG_BASE 0x0010 +#define LPAIF_I2SCTL_REG_STRIDE 0x4 +#define LPAIF_I2SCTL_REG_ADDR(addr, port) \ + (LPAIF_I2SCTL_REG_BASE + (addr) + (LPAIF_I2SCTL_REG_STRIDE * (port))) + +enum lpaif_i2s_ports { + LPAIF_I2S_PORT_MIN = 0, + + 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, + + LPAIF_I2S_PORT_MAX = 4, + LPAIF_I2S_PORT_NUM = 5, +}; + +#define LPAIF_I2SCTL_REG(port) LPAIF_I2SCTL_REG_ADDR(0x0, (port)) + +#define LPAIF_I2SCTL_LOOPBACK_MASK 0x8000 +#define LPAIF_I2SCTL_LOOPBACK_SHIFT 15 +#define LPAIF_I2SCTL_LOOPBACK_DISABLE (0 << LPAIF_I2SCTL_LOOPBACK_SHIFT) +#define LPAIF_I2SCTL_LOOPBACK_ENABLE (1 << LPAIF_I2SCTL_LOOPBACK_SHIFT) + +#define LPAIF_I2SCTL_SPKEN_MASK 0x4000 +#define LPAIF_I2SCTL_SPKEN_SHIFT 14 +#define LPAIF_I2SCTL_SPKEN_DISABLE (0 << LPAIF_I2SCTL_SPKEN_SHIFT) +#define LPAIF_I2SCTL_SPKEN_ENABLE (1 << LPAIF_I2SCTL_SPKEN_SHIFT) + +#define LPAIF_I2SCTL_SPKMODE_MASK 0x3C00 +#define LPAIF_I2SCTL_SPKMODE_SHIFT 10 +#define LPAIF_I2SCTL_SPKMODE_NONE (0 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD0 (1 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD1 (2 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD2 (3 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD3 (4 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_QUAD01 (5 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_QUAD23 (6 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_6CH (7 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_8CH (8 << LPAIF_I2SCTL_SPKMODE_SHIFT) + +#define LPAIF_I2SCTL_SPKMONO_MASK 0x0200 +#define LPAIF_I2SCTL_SPKMONO_SHIFT 9 +#define LPAIF_I2SCTL_SPKMONO_STEREO (0 << LPAIF_I2SCTL_SPKMONO_SHIFT) +#define LPAIF_I2SCTL_SPKMONO_MONO (1 << LPAIF_I2SCTL_SPKMONO_SHIFT) + +#define LPAIF_I2SCTL_WSSRC_MASK 0x0004 +#define LPAIF_I2SCTL_WSSRC_SHIFT 2 +#define LPAIF_I2SCTL_WSSRC_INTERNAL (0 << LPAIF_I2SCTL_WSSRC_SHIFT) +#define LPAIF_I2SCTL_WSSRC_EXTERNAL (1 << LPAIF_I2SCTL_WSSRC_SHIFT) + +#define LPAIF_I2SCTL_BITWIDTH_MASK 0x0003 +#define LPAIF_I2SCTL_BITWIDTH_SHIFT 0 +#define LPAIF_I2SCTL_BITWIDTH_16 (0 << LPAIF_I2SCTL_BITWIDTH_SHIFT) +#define LPAIF_I2SCTL_BITWIDTH_24 (1 << LPAIF_I2SCTL_BITWIDTH_SHIFT) +#define LPAIF_I2SCTL_BITWIDTH_32 (2 << LPAIF_I2SCTL_BITWIDTH_SHIFT) + +/* LPAIF IRQ */ + +#define LPAIF_IRQ_REG_BASE 0x3000 +#define LPAIF_IRQ_REG_STRIDE 0x1000 +#define LPAIF_IRQ_REG_ADDR(addr, port) \ + (LPAIF_IRQ_REG_BASE + (addr) + (LPAIF_IRQ_REG_STRIDE * (port))) + +enum lpaif_irq_ports { + LPAIF_IRQ_PORT_MIN = 0, + + LPAIF_IRQ_PORT_HOST = 0, + LPAIF_IRQ_PORT_ADSP = 1, + + LPAIF_IRQ_PORT_MAX = 2, + LPAIF_IRQ_PORT_NUM = 3, +}; + +#define LPAIF_IRQEN_REG(port) LPAIF_IRQ_REG_ADDR(0x0, (port)) +#define LPAIF_IRQSTAT_REG(port) LPAIF_IRQ_REG_ADDR(0x4, (port)) +#define LPAIF_IRQCLEAR_REG(port) LPAIF_IRQ_REG_ADDR(0xC, (port)) + +#define LPAIF_IRQ_BITSTRIDE 3 +#define LPAIF_IRQ_PER(chan) (1 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_XRUN(chan) (2 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_ERR(chan) (4 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_ALL(chan) (7 << (LPAIF_IRQ_BITSTRIDE * (chan))) + +/* LPAIF DMA */ + +#define LPAIF_RDMA_REG_BASE 0x6000 +#define LPAIF_RDMA_REG_STRIDE 0x1000 +#define LPAIF_RDMA_REG_ADDR(addr, chan) \ + (LPAIF_RDMA_REG_BASE + (addr) + (LPAIF_RDMA_REG_STRIDE * (chan))) + +enum lpaif_dma_channels { + LPAIF_RDMA_CHAN_MIN = 0, + + LPAIF_RDMA_CHAN_MI2S = 0, + LPAIF_RDMA_CHAN_PCM0 = 1, + LPAIF_RDMA_CHAN_PCM1 = 2, + + LPAIF_RDMA_CHAN_MAX = 4, + LPAIF_RDMA_CHAN_NUM = 5, +}; + +#define LPAIF_RDMACTL_REG(chan) LPAIF_RDMA_REG_ADDR(0x00, (chan)) +#define LPAIF_RDMABASE_REG(chan) LPAIF_RDMA_REG_ADDR(0x04, (chan)) +#define LPAIF_RDMABUFF_REG(chan) LPAIF_RDMA_REG_ADDR(0x08, (chan)) +#define LPAIF_RDMACURR_REG(chan) LPAIF_RDMA_REG_ADDR(0x0C, (chan)) +#define LPAIF_RDMAPER_REG(chan) LPAIF_RDMA_REG_ADDR(0x10, (chan)) + +#define LPAIF_RDMACTL_BURSTEN_MASK 0x800 +#define LPAIF_RDMACTL_BURSTEN_SHIFT 11 +#define LPAIF_RDMACTL_BURSTEN_SINGLE (0 << LPAIF_RDMACTL_BURSTEN_SHIFT) +#define LPAIF_RDMACTL_BURSTEN_INCR4 (1 << LPAIF_RDMACTL_BURSTEN_SHIFT) + +#define LPAIF_RDMACTL_WPSCNT_MASK 0x700 +#define LPAIF_RDMACTL_WPSCNT_SHIFT 8 +#define LPAIF_RDMACTL_WPSCNT_ONE (0 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_TWO (1 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_THREE (2 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_FOUR (3 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_SIX (5 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_EIGHT (7 << LPAIF_RDMACTL_WPSCNT_SHIFT) + +#define LPAIF_RDMACTL_AUDINTF_MASK 0x0F0 +#define LPAIF_RDMACTL_AUDINTF_SHIFT 4 +#define LPAIF_RDMACTL_AUDINTF_NONE (0 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_CODEC (1 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_PCM (2 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_SEC_I2S (3 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_MI2S (4 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_HDMI (5 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_SEC_PCM (7 << LPAIF_RDMACTL_AUDINTF_SHIFT) + +#define LPAIF_RDMACTL_FIFOWM_MASK 0x00E +#define LPAIF_RDMACTL_FIFOWM_SHIFT 1 +#define LPAIF_RDMACTL_FIFOWM_1 (0 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_2 (1 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_3 (2 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_4 (3 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_5 (4 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_6 (5 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_7 (6 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_8 (7 << LPAIF_RDMACTL_FIFOWM_SHIFT) + +#define LPAIF_RDMACTL_ENABLE_MASK 0x1 +#define LPAIF_RDMACTL_ENABLE_SHIFT 0 +#define LPAIF_RDMACTL_ENABLE_OFF (0 << LPAIF_RDMACTL_ENABLE_SHIFT) +#define LPAIF_RDMACTL_ENABLE_ON (1 << LPAIF_RDMACTL_ENABLE_SHIFT) + +#endif /* __LPASS_LPAIF_H__ */ diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h new file mode 100644 index 0000000000000000000000000000000000000000..7bb7ac5db397dcd0614ee276cc20727bdf55ad64 --- /dev/null +++ b/sound/soc/qcom/lpass.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#ifndef __LPASS_H__ +#define __LPASS_H__ + +#define LPASS_AHBIX_CLOCK_FREQUENCY 131072 + +/* Both the CPU DAI driver and platform driver will access this data */ +struct lpass_data { + + /* clocks inside the low-power audio subsystem (LPASS) domain */ + struct clk *ahbix_clk; + struct clk *mi2s_bit_clk; + struct clk *mi2s_osr_clk; + + /* default system (or OSR) clock frequency */ + unsigned int default_sysclk_freq; + + /* + * if enabled and the target BIT clock frequency is below the range of + * the clock divider, then the system clock needs to be reduced by + * right-shifting the default clock frequency + */ + bool sysclk_shift_enable; + unsigned int sysclk_shift_compare; + unsigned int sysclk_shift_amount; + + /* + * if enabled and the target BIT clock frequency can not be accurately + * derived by the clock divider for the given bitwidth, then use an + * alternate system clock frequency + */ + bool alt_sysclk_enable; + unsigned int alt_sysclk_enable_bitwidth; + unsigned int alt_sysclk_freq; + + /* memory-mapped registers for the low-power audio interface (LPAIF) */ + void __iomem *lpaif; + struct regmap *lpaif_map; + + /* handle for the low-power audio interface (LPAIF) interrupts */ + int lpaif_irq; + + /* memory-mapped RAM designated for holding the audio buffer(s) */ + void __iomem *lpm; + dma_addr_t lpm_phys; + unsigned int lpm_size; + atomic_t lpm_lock; +}; + +int asoc_qcom_lpass_platform_register(struct platform_device *); + +#endif /* __LPASS_H__ */
On Thu, Feb 05, 2015 at 12:53:41PM -0800, Kenneth Westfield wrote:
+#define __LPASS_H__
+#define LPASS_AHBIX_CLOCK_FREQUENCY 131072
+/* Both the CPU DAI driver and platform driver will access this data */ +struct lpass_data {
- /* clocks inside the low-power audio subsystem (LPASS) domain */
- struct clk *ahbix_clk;
This uses struct clk so it needs at least a forward declaration of it if not just a straight inclusion of linux/clk.h. There's several other types and annotations that are referenced without an include to ensure the compiler knows about them, the general idea is to avoid implicit dependencies and thee surprising build breaks they cause.
From: Kenneth Westfield kwestfie@codeaurora.org
Add the CPU DAI driver for the Qualcomm Technologies low-power audio subsystem (LPASS).
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-cpu.c | 557 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 557 insertions(+) create mode 100644 sound/soc/qcom/lpass-cpu.c
diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c new file mode 100644 index 0000000000000000000000000000000000000000..8e58fa628667eca058bfbf35e29085b202f471f6 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu.c @@ -0,0 +1,557 @@ +/* + * 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. + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "lpass-lpaif-ipq806x.h" +#include "lpass.h" + +#define DRV_NAME "lpass-cpu" + +static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + + drvdata->default_sysclk_freq = freq; + + return 0; +} + +static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_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_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_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_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct lpass_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); + unsigned long bclk_freq, oclk_freq; + unsigned int regval; + int bitwidth, ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "%s() invalid bit width given\n", __func__); + return bitwidth; + } + + regval = 0; + regval |= LPAIF_I2SCTL_LOOPBACK_DISABLE; + regval |= LPAIF_I2SCTL_WSSRC_INTERNAL; + + switch (bitwidth) { + case 16: + regval |= LPAIF_I2SCTL_BITWIDTH_16; + break; + case 24: + regval |= LPAIF_I2SCTL_BITWIDTH_24; + break; + case 32: + regval |= LPAIF_I2SCTL_BITWIDTH_32; + break; + default: + dev_err(dai->dev, "%s() invalid bitwidth given: %u\n", + __func__, bitwidth); + return -EINVAL; + } + + switch (channels) { + case 1: + regval |= LPAIF_I2SCTL_SPKMODE_SD0; + regval |= LPAIF_I2SCTL_SPKMONO_MONO; + break; + case 2: + regval |= LPAIF_I2SCTL_SPKMODE_SD0; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 4: + regval |= LPAIF_I2SCTL_SPKMODE_QUAD01; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 6: + regval |= LPAIF_I2SCTL_SPKMODE_6CH; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 8: + regval |= LPAIF_I2SCTL_SPKMODE_8CH; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + default: + dev_err(dai->dev, "%s() invalid channels given: %u\n", + __func__, channels); + return -EINVAL; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), regval); + if (ret) { + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + return ret; + } + + /* + * adjust the OSR clock so that the BIT clock can successfully be + * derived from it by the hardware + */ + bclk_freq = rate * bitwidth * 2; + if (bitwidth == drvdata->alt_sysclk_enable_bitwidth && + drvdata->alt_sysclk_enable) + oclk_freq = drvdata->alt_sysclk_freq; + else if (!drvdata->sysclk_shift_enable) + oclk_freq = drvdata->default_sysclk_freq; + else if (bclk_freq >= drvdata->default_sysclk_freq >> + drvdata->sysclk_shift_compare) + oclk_freq = drvdata->default_sysclk_freq; + else + oclk_freq = drvdata->default_sysclk_freq >> + drvdata->sysclk_shift_amount; + + ret = clk_set_rate(drvdata->mi2s_osr_clk, oclk_freq); + if (ret) { + dev_err(dai->dev, "%s() error setting mi2s osrclk to %lu: %d\n", + __func__, oclk_freq, ret); + return ret; + } + + ret = clk_set_rate(drvdata->mi2s_bit_clk, bclk_freq); + if (ret) { + dev_err(dai->dev, "%s() error setting mi2s bitclk to %lu: %d\n", + __func__, bclk_freq, ret); + return ret; + } + + return 0; +} + +static int lpass_cpu_daiops_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 0); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + + return ret; +} + +static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + unsigned int reg, mask, val; + int ret; + + reg = LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S); + mask = LPAIF_I2SCTL_SPKEN_MASK; + val = LPAIF_I2SCTL_SPKEN_ENABLE; + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + + return ret; +} + +static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + unsigned int reg, mask, val; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S); + mask = LPAIF_I2SCTL_SPKEN_MASK; + val = LPAIF_I2SCTL_SPKEN_ENABLE; + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + reg = LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S); + mask = LPAIF_I2SCTL_SPKEN_MASK; + val = LPAIF_I2SCTL_SPKEN_DISABLE; + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + break; + } + + return ret; +} + +static struct snd_soc_dai_ops lpass_cpu_dai_ops = { + .set_sysclk = lpass_cpu_daiops_set_sysclk, + .startup = lpass_cpu_daiops_startup, + .shutdown = lpass_cpu_daiops_shutdown, + .hw_params = lpass_cpu_daiops_hw_params, + .hw_free = lpass_cpu_daiops_hw_free, + .prepare = lpass_cpu_daiops_prepare, + .trigger = lpass_cpu_daiops_trigger, +}; + +static int lpass_cpu_dai_probe(struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 0); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + + return ret; +} + +static struct snd_soc_dai_driver lpass_cpu_dai_driver = { + .playback = { + .stream_name = DRV_NAME "-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_dai_probe, + .ops = &lpass_cpu_dai_ops, +}; + +static const struct snd_soc_component_driver lpass_cpu_comp_driver = { + .name = DRV_NAME, +}; + +static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i) + if (reg == LPAIF_I2SCTL_REG(i)) + return true; + + for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) { + if (reg == LPAIF_IRQEN_REG(i)) + return true; + if (reg == LPAIF_IRQCLEAR_REG(i)) + return true; + } + + for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) { + if (reg == LPAIF_RDMACTL_REG(i)) + return true; + if (reg == LPAIF_RDMABASE_REG(i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(i)) + return true; + if (reg == LPAIF_RDMAPER_REG(i)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i) + if (reg == LPAIF_I2SCTL_REG(i)) + return true; + + for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) { + if (reg == LPAIF_IRQEN_REG(i)) + return true; + if (reg == LPAIF_IRQSTAT_REG(i)) + return true; + } + + for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) { + if (reg == LPAIF_RDMACTL_REG(i)) + return true; + if (reg == LPAIF_RDMABASE_REG(i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(i)) + return true; + if (reg == LPAIF_RDMACURR_REG(i)) + return true; + if (reg == LPAIF_RDMAPER_REG(i)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) + if (reg == LPAIF_IRQSTAT_REG(i)) + return true; + + for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) + if (reg == LPAIF_RDMACURR_REG(i)) + return true; + + return false; +} + +static const struct regmap_config lpass_cpu_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = LPAIF_RDMAPER_REG(LPAIF_RDMA_CHAN_MAX), + .writeable_reg = lpass_cpu_regmap_writeable, + .readable_reg = lpass_cpu_regmap_readable, + .volatile_reg = lpass_cpu_regmap_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static int lpass_cpu_parse_of(struct device *dev, struct lpass_data *drvdata) +{ + struct device_node *np = dev->of_node; + int ret; + + if (!np || !of_device_is_available(np)) { + dev_err(dev, "%s() no available info for %s\n", __func__, + DRV_NAME); + return -EINVAL; + } + + if (of_property_read_bool(np, "qcom,system-clock-shift")) { + ret = of_property_read_u32(np, + "qcom,system-clock-shift-compare", + &drvdata->sysclk_shift_compare); + if (ret) { + dev_err(dev, "%s() qcom,system-clock-shift-compare not found: %d\n", + __func__, ret); + return -EINVAL; + } + ret = of_property_read_u32(np, + "qcom,system-clock-shift-amount", + &drvdata->sysclk_shift_amount); + if (ret) { + dev_err(dev, "%s() qcom,system-clock-shift-amount not found: %d\n", + __func__, ret); + return -EINVAL; + } + drvdata->sysclk_shift_enable = true; + } else { + drvdata->sysclk_shift_enable = false; + } + + if (of_property_read_bool(np, "qcom,alternate-sysclk")) { + ret = of_property_read_u32(np, + "qcom,alternate-sysclk-bitwidth", + &drvdata->alt_sysclk_enable_bitwidth); + if (ret) { + dev_err(dev, "%s() qcom,alternate-sysclk-bitwidth not found: %d\n", + __func__, ret); + return -EINVAL; + } + ret = of_property_read_u32(np, + "qcom,alternate-sysclk-frequency", + &drvdata->alt_sysclk_freq); + if (ret) { + dev_err(dev, "%s() qcom,alternate-sysclk-frequency not found: %d\n", + __func__, ret); + return -EINVAL; + } + drvdata->alt_sysclk_enable = true; + } else { + drvdata->alt_sysclk_enable = false; + } + + return 0; +} + +static int lpass_cpu_platform_probe(struct platform_device *pdev) +{ + struct lpass_data *drvdata; + struct resource *res; + int ret; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + + ret = lpass_cpu_parse_of(&pdev->dev, drvdata); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif"); + if (!res) { + dev_err(&pdev->dev, "%s() error getting resource\n", __func__); + return -ENODEV; + } + + drvdata->lpaif = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR((void const __force *)drvdata->lpaif)) { + dev_err(&pdev->dev, "%s() error mapping reg resource: %ld\n", + __func__, + PTR_ERR((void const __force *)drvdata->lpaif)); + return PTR_ERR((void const __force *)drvdata->lpaif); + } + + drvdata->lpaif_map = devm_regmap_init_mmio(&pdev->dev, drvdata->lpaif, + &lpass_cpu_regmap_config); + if (IS_ERR(drvdata->lpaif_map)) { + dev_err(&pdev->dev, "%s() error initializing regmap: %ld\n", + __func__, PTR_ERR(drvdata->lpaif_map)); + return PTR_ERR(drvdata->lpaif_map); + } + + 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); + } + + 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: %ld\n", + __func__, PTR_ERR(drvdata->ahbix_clk)); + return PTR_ERR(drvdata->ahbix_clk); + } + + ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY); + if (ret) { + dev_err(&pdev->dev, "%s() error setting rate on ahbix_clk: %d\n", + __func__, ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->ahbix_clk); + if (ret) { + dev_err(&pdev->dev, "%s() Error enabling ahbix_clk: %d\n", + __func__, ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &lpass_cpu_comp_driver, &lpass_cpu_dai_driver, 1); + if (ret) { + dev_err(&pdev->dev, "%s() error registering cpu driver: %d\n", + __func__, ret); + goto err_clk; + } + + ret = asoc_qcom_lpass_platform_register(pdev); + if (ret) { + dev_err(&pdev->dev, "%s() error registering platform driver: %d\n", + __func__, ret); + goto err_clk; + } + + return 0; + +err_clk: + clk_disable_unprepare(drvdata->ahbix_clk); + return ret; +} + +static int lpass_cpu_platform_remove(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->ahbix_clk); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id lpass_cpu_device_id[] = { + {.compatible = "qcom," DRV_NAME}, + {} +}; +#endif + +static struct platform_driver lpass_cpu_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(lpass_cpu_device_id), + }, + .probe = lpass_cpu_platform_probe, + .remove = lpass_cpu_platform_remove, +}; +module_platform_driver(lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("QTi LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_cpu_device_id);
On Thu, Feb 05, 2015 at 12:53:42PM -0800, Kenneth Westfield wrote:
- int bitwidth, ret;
- bitwidth = snd_pcm_format_width(format);
- if (bitwidth < 0) {
dev_err(dai->dev, "%s() invalid bit width given\n", __func__);
Print the error code.
- regval = 0;
- regval |= LPAIF_I2SCTL_LOOPBACK_DISABLE;
- regval |= LPAIF_I2SCTL_WSSRC_INTERNAL;
Why not just write a single assignment statement?
- default:
dev_err(dai->dev, "%s() invalid bitwidth given: %u\n",
__func__, bitwidth);
bitwidth is a signed type but you are using an unsigned format specifier here.
- reg = LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S);
- mask = LPAIF_I2SCTL_SPKEN_MASK;
- val = LPAIF_I2SCTL_SPKEN_ENABLE;
- ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val);
None of these intermediate variables seem to be doing a lot, why not just specify the constants directly as arguments (that's the more normal style)? A similar thing applies in several other places in this file.
+#ifdef CONFIG_OF +static const struct of_device_id lpass_cpu_device_id[] = {
- {.compatible = "qcom," DRV_NAME},
- {}
+}; +#endif
Using DRV_NAME in the compatible like this makes it impossible to grep for the driver which isn't helpful. In general I prefer not to use DRV_NAME at all (exactly how often do we change the driver name?) but in this case it's actively harmful.
From: Kenneth Westfield kwestfie@codeaurora.org
Add platform driver for the Qualcomm Technologies low-power audio subsystem (LPASS) ports.
Signed-off-by: Kenneth Westfield kwestfie@codeaurora.org Acked-by: Banajit Goswami bgoswami@codeaurora.org --- sound/soc/qcom/lpass-platform.c | 514 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 514 insertions(+) create mode 100644 sound/soc/qcom/lpass-platform.c
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c new file mode 100644 index 0000000000000000000000000000000000000000..ce526119a2c4a38537e8c9777e0556f15e5a660f --- /dev/null +++ b/sound/soc/qcom/lpass-platform.c @@ -0,0 +1,514 @@ +/* + * 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. + */ + +#include <linux/module.h> +#include <linux/io.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "lpass-lpaif-ipq806x.h" +#include "lpass.h" + +#define DRV_NAME "lpass-platform" + +#define LPASS_PLATFORM_CHANNELS_MIN 1 +#define LPASS_PLATFORM_CHANNELS_MAX 8 +#define LPASS_PLATFORM_PERIODS_MIN 2 +#define LPASS_PLATFORM_PERIODS_MAX 2 +#define LPASS_PLATFORM_RATE_MIN 8000 +#define LPASS_PLATFORM_RATE_MAX 192000 + +static struct snd_pcm_hardware lpass_platform_hardware = { + .info = 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 = LPASS_PLATFORM_RATE_MIN, + .rate_max = LPASS_PLATFORM_RATE_MAX, + .channels_min = LPASS_PLATFORM_CHANNELS_MIN, + .channels_max = LPASS_PLATFORM_CHANNELS_MAX, + .periods_min = LPASS_PLATFORM_PERIODS_MIN, + .periods_max = LPASS_PLATFORM_PERIODS_MAX, +}; + +static int lpass_platform_pcmops_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_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + int ret; + + lpass_platform_hardware.buffer_bytes_max = drvdata->lpm_size; + lpass_platform_hardware.period_bytes_max = + drvdata->lpm_size / LPASS_PLATFORM_PERIODS_MIN; + lpass_platform_hardware.period_bytes_min = + drvdata->lpm_size / LPASS_PLATFORM_PERIODS_MAX; + snd_soc_set_runtime_hwparams(substream, &lpass_platform_hardware); + + runtime->dma_bytes = drvdata->lpm_size; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(soc_runtime->dev, "%s() setting constraints failed: %d\n", + __func__, ret); + return -EINVAL; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int lpass_platform_pcmops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int regval; + 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; + } + + regval = 0; + regval |= LPAIF_RDMACTL_BURSTEN_INCR4; + regval |= LPAIF_RDMACTL_AUDINTF_MI2S; + regval |= LPAIF_RDMACTL_FIFOWM_8; + + switch (bitwidth) { + case 16: + switch (channels) { + case 1: + case 2: + regval |= LPAIF_RDMACTL_WPSCNT_ONE; + break; + case 4: + regval |= LPAIF_RDMACTL_WPSCNT_TWO; + break; + case 6: + regval |= LPAIF_RDMACTL_WPSCNT_THREE; + break; + case 8: + regval |= LPAIF_RDMACTL_WPSCNT_FOUR; + break; + default: + dev_err(soc_runtime->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: + regval |= LPAIF_RDMACTL_WPSCNT_ONE; + break; + case 2: + regval |= LPAIF_RDMACTL_WPSCNT_TWO; + break; + case 4: + regval |= LPAIF_RDMACTL_WPSCNT_FOUR; + break; + case 6: + regval |= LPAIF_RDMACTL_WPSCNT_SIX; + break; + case 8: + regval |= LPAIF_RDMACTL_WPSCNT_EIGHT; + break; + default: + dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + break; + default: + dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%u, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), regval); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), 0); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_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_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + unsigned int reg, mask, val; + int ret; + + reg = LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S); + val = runtime->dma_addr; + ret = regmap_write(drvdata->lpaif_map, reg, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmabase reg: %d\n", + __func__, ret); + return ret; + } + + reg = LPAIF_RDMABUFF_REG(LPAIF_RDMA_CHAN_MI2S); + val = (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1; + ret = regmap_write(drvdata->lpaif_map, reg, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmabuff reg: %d\n", + __func__, ret); + return ret; + } + + reg = LPAIF_RDMAPER_REG(LPAIF_RDMA_CHAN_MI2S); + val = (snd_pcm_lib_period_bytes(substream) >> 2) - 1; + ret = regmap_write(drvdata->lpaif_map, reg, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmaper reg: %d\n", + __func__, ret); + return ret; + } + + reg = LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S); + mask = LPAIF_RDMACTL_ENABLE_MASK; + val = LPAIF_RDMACTL_ENABLE_ON; + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + unsigned int reg, mask, val; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* clear status before enabling interrupts */ + reg = LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST); + val = LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S); + ret = regmap_write(drvdata->lpaif_map, reg, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n", + __func__, ret); + return ret; + } + + reg = LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST); + mask = LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S); + val = LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S); + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n", + __func__, ret); + return ret; + } + + reg = LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S); + mask = LPAIF_RDMACTL_ENABLE_MASK; + val = LPAIF_RDMACTL_ENABLE_ON; + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + reg = LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S); + mask = LPAIF_RDMACTL_ENABLE_MASK; + val = LPAIF_RDMACTL_ENABLE_OFF; + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + reg = LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST); + mask = LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S); + val = 0; + ret = regmap_update_bits(drvdata->lpaif_map, reg, mask, val); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n", + __func__, ret); + return ret; + } + break; + } + + return 0; +} + +static snd_pcm_uframes_t lpass_platform_pcmops_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + unsigned int base_addr, curr_addr; + int ret; + + ret = regmap_read(drvdata->lpaif_map, + LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S), &base_addr); + if (ret) { + dev_err(soc_runtime->dev, "%s() error reading from rdmabase reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_read(drvdata->lpaif_map, + LPAIF_RDMACURR_REG(LPAIF_RDMA_CHAN_MI2S), &curr_addr); + if (ret) { + dev_err(soc_runtime->dev, "%s() error reading from rdmacurr reg: %d\n", + __func__, ret); + return ret; + } + + return bytes_to_frames(substream->runtime, curr_addr - base_addr); +} + +static struct snd_pcm_ops lpass_platform_pcm_ops = { + .open = lpass_platform_pcmops_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = lpass_platform_pcmops_hw_params, + .hw_free = lpass_platform_pcmops_hw_free, + .prepare = lpass_platform_pcmops_prepare, + .trigger = lpass_platform_pcmops_trigger, + .pointer = lpass_platform_pcmops_pointer, +}; + +static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data) +{ + 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); + const u32 status_offset = LPAIF_IRQSTAT_REG(LPAIF_IRQ_PORT_HOST); + const u32 clear_offset = LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST); + const u32 period_bitmask = LPAIF_IRQ_PER(LPAIF_RDMA_CHAN_MI2S); + const u32 xrun_bitmask = LPAIF_IRQ_XRUN(LPAIF_RDMA_CHAN_MI2S); + const u32 error_bitmask = LPAIF_IRQ_ERR(LPAIF_RDMA_CHAN_MI2S); + const u32 chan_bitmask = LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S); + u32 interrupts; + irqreturn_t ret = IRQ_NONE; + + interrupts = ioread32(drvdata->lpaif + status_offset) & chan_bitmask; + + if (likely(interrupts & period_bitmask)) { + iowrite32(period_bitmask, drvdata->lpaif + clear_offset); + snd_pcm_period_elapsed(substream); + ret = IRQ_HANDLED; + } + + if (unlikely(interrupts & xrun_bitmask)) { + iowrite32(xrun_bitmask, drvdata->lpaif + clear_offset); + dev_warn(soc_runtime->dev, "%s() xrun warning\n", __func__); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + ret = IRQ_HANDLED; + } + + if (unlikely(interrupts & error_bitmask)) { + iowrite32(error_bitmask, drvdata->lpaif + clear_offset); + dev_err(soc_runtime->dev, "%s() bus access error\n", __func__); + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int lpass_platform_alloc_buffer(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + + if (atomic_cmpxchg(&drvdata->lpm_lock, 0, 1)) { + dev_err(soc_runtime->dev, "%s() buffer already in use\n", + __func__); + return -ENOMEM; + } + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = soc_runtime->dev; + buf->private_data = NULL; + buf->area = (unsigned char __force *)drvdata->lpm; + buf->addr = drvdata->lpm_phys; + buf->bytes = drvdata->lpm_size; + + return 0; +} + +static void lpass_platform_free_buffer(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + + if (buf->area == (unsigned char __force *)drvdata->lpm) { + buf->area = NULL; + atomic_dec(&drvdata->lpm_lock); + } else { + dev_warn(soc_runtime->dev, "%s() attempting to free invalid buffer\n", + __func__); + } +} + +static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_pcm *pcm = soc_runtime->pcm; + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + int ret; + + ret = lpass_platform_alloc_buffer(substream, 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(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(LPAIF_RDMA_CHAN_MI2S), 0); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + return 0; + +err_buf: + lpass_platform_free_buffer(substream, soc_runtime); + return ret; +} + +static void lpass_platform_pcm_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; + + lpass_platform_free_buffer(substream, soc_runtime); +} + +static struct snd_soc_platform_driver lpass_platform_driver = { + .pcm_new = lpass_platform_pcm_new, + .pcm_free = lpass_platform_pcm_free, + .ops = &lpass_platform_pcm_ops, +}; + +int asoc_qcom_lpass_platform_register(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpm"); + if (!res) { + dev_err(&pdev->dev, "%s() error getting resource\n", __func__); + return -ENODEV; + } + + drvdata->lpm = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR((unsigned char __force *)drvdata->lpm)) { + dev_err(&pdev->dev, "%s() error mapping lpm resource: %ld\n", + __func__, + PTR_ERR((unsigned char __force *)drvdata->lpm)); + return PTR_ERR((unsigned char __force *)drvdata->lpm); + } + drvdata->lpm_phys = res->start; + drvdata->lpm_size = resource_size(res); + atomic_set(&drvdata->lpm_lock, 0); + + drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif"); + if (drvdata->lpaif_irq < 0) { + dev_err(&pdev->dev, "%s() error getting irq handle: %d\n", + __func__, drvdata->lpaif_irq); + return -ENODEV; + } + + return devm_snd_soc_register_platform(&pdev->dev, + &lpass_platform_driver); +} +EXPORT_SYMBOL(asoc_qcom_lpass_platform_register); + +MODULE_DESCRIPTION("QTi LPASS Platform Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME);
On Thu, Feb 05, 2015 at 12:53:43PM -0800, Kenneth Westfield wrote:
- irqreturn_t ret = IRQ_NONE;
- interrupts = ioread32(drvdata->lpaif + status_offset) & chan_bitmask;
Elsewhere we're using regmap... why not here and how does this play with regmap?
- if (likely(interrupts & period_bitmask)) {
In general it's better not to use likely() and unlikely() annotations unless you've got some evidence that they actually improve things (which should be explained somewhere). They make the code that bit noisier and there's some potential for them to cause the compiler to do unhelpful things.
+static int lpass_platform_alloc_buffer(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *soc_runtime)
+{
- struct snd_dma_buffer *buf = &substream->dma_buffer;
- struct lpass_data *drvdata =
snd_soc_platform_get_drvdata(soc_runtime->platform);
- if (atomic_cmpxchg(&drvdata->lpm_lock, 0, 1)) {
cmpxchg() and atomics are both among the more error prone synchronization primitives the kernel has. In cases where it is appropriate to use them it is very important that the code be entirely clear about what is going on so that not only are we clear that the concurrency handling is safe but we can also modify the code safely in the future.
This driver has no documentation at all for this variable so we've at least got a problem with the clarity side here. Looking at the code it's not entirely clear to me wat exactly is being protected or why we're not using a simpler mechanism like variable protected by a mutex.
- return devm_snd_soc_register_platform(&pdev->dev,
&lpass_platform_driver);
+} +EXPORT_SYMBOL(asoc_qcom_lpass_platform_register);
ASoC APIs are all exported EXPORT_SYMBOL_GPL, their users should be too.
From: Kenneth Westfield kwestfie@codeaurora.org
Define the LPASS platform driver and the LPASS CPU DAI driver configuration, and how to build them.
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..d33fc5db86a34dc2e3f8146a1aea28c0e2ae8839 --- /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_LPASS_CPU + 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_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + 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_LPASS_PLATFORM + tristate + depends on SND_SOC_QCOM diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..2b8b0230eeb53acf36bb333c78abf7e95147ac2a --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,6 @@ +# Platform +snd-soc-lpass-cpu-objs := lpass-cpu.o +snd-soc-lpass-platform-objs := lpass-platform.o + +obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o +obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o
On Thu, Feb 05, 2015 at 12:53:44PM -0800, Kenneth Westfield wrote:
+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_LPASS_CPU
- select SND_SOC_LPASS_PLATFORM
select SND_SOC_MAX98357A
No, we don't have blocks like this selecting simple-card for any other platforms using simple-card so we shouldn't do that here either. Just let all the drivers be individually selectable.
You've also got a mix of tabs and spaces going on.
From: Kenneth Westfield kwestfie@codeaurora.org
Allow for the Qualcomm Technologies ASoC 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 dcc79aa0236b548bfe5408fe56689241fc597e97..3ba52da18bc69a9bb41c84627cfc7d08f47e3bf0 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 5b3c8f67c8db7a29ff7199a6103d445428978125..974ba708b4826a03077a58251434a311542d5e3c 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 Qualcomm Technologies LPASS hardware for the ipq806x SOC.
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..c608575e3d184a5d84d550d47af8c597e022ba4a 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>
/ { @@ -118,6 +119,21 @@ regulator; };
+ lpass-cpu@28100000 { + compatible = "qcom,lpass-cpu"; + 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-irq-lpaif"; + reg = <0x28100000 0x10000>, <0x28400000 0x4000>; + reg-names = "lpass-lpaif", "lpass-lpm"; + }; + gsbi2: gsbi@12480000 { compatible = "qcom,gsbi-v1.0.0"; reg = <0x12480000 0x100>; @@ -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>; + }; }; };
On Thu, Feb 05, 2015 at 12:53:36PM -0800, Kenneth Westfield wrote:
This patch series adds support for I2S audio playback 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 contains an MI2S port, which is what these drivers are configured to use. The LPAIF also contains a DMA engine that is dedicated to moving audio samples into the transmit FIFO of the MI2S port. In addition, there is also low-power memory (LPM) within the audio subsystem, which is used for buffering the audio samples.
This is implementing an AP centric audio system design where the AP directly programs all the audio hardware. Given that pretty much all public Qualcomm systems use a DSP centric model where the AP interacts only with a DSP which deals with DMA and the physical interfaces it seems reasonable to suppose that this system also has a DSP which at some future point people are likely to want to use.
I'd really like to see some discussion as to how this is all supposed to be handled - how will these direct hardware access drivers and device trees work when someone does want to use the DSP (without causing problems), and how will we transition from one to the other. This is particularly pressing if there are use cases where people will want to switch between the two modes at runtime.
What I'm trying to avoid here is being in a situation where we have existing stable DT bindings which we have to support but which conflict with the way that people want to use the systems.
On Sat, Feb 07, 2015 at 06:32:29AM +0800, Mark Brown wrote:
On Thu, Feb 05, 2015 at 12:53:36PM -0800, Kenneth Westfield wrote:
This patch series adds support for I2S audio playback 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 contains an MI2S port, which is what these drivers are configured to use. The LPAIF also contains a DMA engine that is dedicated to moving audio samples into the transmit FIFO of the MI2S port. In addition, there is also low-power memory (LPM) within the audio subsystem, which is used for buffering the audio samples.
This is implementing an AP centric audio system design where the AP directly programs all the audio hardware. Given that pretty much all public Qualcomm systems use a DSP centric model where the AP interacts only with a DSP which deals with DMA and the physical interfaces it seems reasonable to suppose that this system also has a DSP which at some future point people are likely to want to use.
I'd really like to see some discussion as to how this is all supposed to be handled - how will these direct hardware access drivers and device trees work when someone does want to use the DSP (without causing problems), and how will we transition from one to the other. This is particularly pressing if there are use cases where people will want to switch between the two modes at runtime.
What I'm trying to avoid here is being in a situation where we have existing stable DT bindings which we have to support but which conflict with the way that people want to use the systems.
The ipq806x SOC has no LPASS DSP. On SOCs with a DSP, these drivers would not be enabled.
These drivers are prefixed with "lpass" to differentiate themselves from other drivers that would interact with a DSP, rather than the LPASS hardware directly.
On Sun, Feb 08, 2015 at 10:45:11PM -0800, Kenneth Westfield wrote:
On Sat, Feb 07, 2015 at 06:32:29AM +0800, Mark Brown wrote:
I'd really like to see some discussion as to how this is all supposed to be handled - how will these direct hardware access drivers and device trees work when someone does want to use the DSP (without causing problems), and how will we transition from one to the other. This is particularly pressing if there are use cases where people will want to switch between the two modes at runtime.
What I'm trying to avoid here is being in a situation where we have existing stable DT bindings which we have to support but which conflict with the way that people want to use the systems.
The ipq806x SOC has no LPASS DSP. On SOCs with a DSP, these drivers would not be enabled.
OK, but I'm guessing that they're using the same IP that is in other SoCs which do have the DSP so even if you don't care for this device it might still be an issue.
These drivers are prefixed with "lpass" to differentiate themselves from other drivers that would interact with a DSP, rather than the LPASS hardware directly.
Right, it may be that all that's needed here is some indication as to how to describe a system which *does* have a DSP. Perhaps require that the devices be children of the DSP, that way if people want to access the hardware directly they can load a dummy driver for the DSP that just passes things through if they don't want to use the DSP?
On Tue, Feb 10, 2015 at 06:26:34PM -0800, Mark Brown wrote:
On Sun, Feb 08, 2015 at 10:45:11PM -0800, Kenneth Westfield wrote:
On Sat, Feb 07, 2015 at 06:32:29AM +0800, Mark Brown wrote:
I'd really like to see some discussion as to how this is all supposed
to
be handled - how will these direct hardware access drivers and device trees work when someone does want to use the DSP (without causing problems), and how will we transition from one to the other. This is particularly pressing if there are use cases where people will want to switch between the two modes at runtime.
What I'm trying to avoid here is being in a situation where we have existing stable DT bindings which we have to support but which
conflict
with the way that people want to use the systems.
The ipq806x SOC has no LPASS DSP. On SOCs with a DSP, these drivers would not be enabled.
OK, but I'm guessing that they're using the same IP that is in other SoCs which do have the DSP so even if you don't care for this device it might still be an issue.
These drivers are prefixed with "lpass" to differentiate themselves from other drivers that would interact with a DSP, rather than the LPASS hardware directly.
Right, it may be that all that's needed here is some indication as to how to describe a system which *does* have a DSP. Perhaps require that the devices be children of the DSP, that way if people want to access the hardware directly they can load a dummy driver for the DSP that just passes things through if they don't want to use the DSP?
Replacing DSP-based drivers with LPASS-based drivers would be something that should be handled by Kconfig selections. For the DT, the DSP-related nodes and the LPASS-related nodes shouldn't overlap. There should be a DSP-based DT binding and a separate LPASS-based DT binding. Tying one or the other to the sound node (but not both), should work.
On Wed, Feb 11, 2015 at 05:05:52PM -0800, Kenneth Westfield wrote:
Replacing DSP-based drivers with LPASS-based drivers would be something that should be handled by Kconfig selections. For the DT, the DSP-related
No, it shouldn't be. We should have the ability to build a single kernel image which will run on many systems, including both your system with a DSP and other systems without.
nodes and the LPASS-related nodes shouldn't overlap. There should be a DSP-based DT binding and a separate LPASS-based DT binding. Tying one or the other to the sound node (but not both), should work.
The selection of DSP use sounds like something which isn't part of the description of the hardware but rather a runtime policy decision (at least in so far as non-DSP is ever an option).
On 2/11/2015 6:53 PM, Mark Brown wrote:
On Wed, Feb 11, 2015 at 05:05:52PM -0800, Kenneth Westfield wrote:
Replacing DSP-based drivers with LPASS-based drivers would be something that should be handled by Kconfig selections. For the DT, the DSP-related
No, it shouldn't be. We should have the ability to build a single kernel image which will run on many systems, including both your system with a DSP and other systems without.
Is there expectation that DTB flashed onto the system would define nodes to bind with both LPASS-based driver and DSP-based driver? I hope not as we want to keep LPASS-based driver & DSP-based driver mutually exclusive.
nodes and the LPASS-related nodes shouldn't overlap. There should be a DSP-based DT binding and a separate LPASS-based DT binding. Tying one or the other to the sound node (but not both), should work.
The selection of DSP use sounds like something which isn't part of the description of the hardware but rather a runtime policy decision (at least in so far as non-DSP is ever an option).
Put aside IPQ8064, I would say it is actually more of build time policy decision for QC SoCs with DSP. XPU is programmed by trust zone to allow certain LPASS registers to be accessed by the chosen processor. If ADSP and app processor would have to have access to audio interfaces and DMA, resources partition(i.e # of DMAs go to ADSP while rest of DMA go to app processor is decided after analyzing expected concurrency use case. For case of 8016, MDSP would simply expect it has access to all audio subsystem except digital core of CODEC.
Thanks Patrick
On Wed, Feb 11, 2015 at 11:20:33PM -0800, Patrick Lai wrote:
On 2/11/2015 6:53 PM, Mark Brown wrote:
On Wed, Feb 11, 2015 at 05:05:52PM -0800, Kenneth Westfield wrote:
Replacing DSP-based drivers with LPASS-based drivers would be something that should be handled by Kconfig selections. For the DT, the DSP-related
No, it shouldn't be. We should have the ability to build a single kernel image which will run on many systems, including both your system with a DSP and other systems without.
Is there expectation that DTB flashed onto the system would define nodes to bind with both LPASS-based driver and DSP-based driver? I hope not as we want to keep LPASS-based driver & DSP-based driver mutually exclusive.
DTB time selections are a separate thing to Kconfig changes like Kenneth was proposing. They're more viable though it'd be a lot better to avoid needing them, designing out the possibility of doing something is often a sure fire way of finding a user.
The selection of DSP use sounds like something which isn't part of the description of the hardware but rather a runtime policy decision (at least in so far as non-DSP is ever an option).
Put aside IPQ8064, I would say it is actually more of build time policy decision for QC SoCs with DSP. XPU is programmed by trust zone to allow certain LPASS registers to be accessed by the chosen processor. If ADSP and app processor would have to have access to audio interfaces and DMA, resources partition(i.e # of DMAs go to ADSP while rest of DMA go to app processor is decided after analyzing expected concurrency use case. For case of 8016, MDSP would simply expect it has access to all audio subsystem except digital core of CODEC.
But is it possible to configure the TrustZone firmware to leave things open? That's the tricky case.
Actually, can we read the configuration TrustZone did? That might be the best answer here, the DT can describe the silicon and then we can check at runtime which bits of it we're actually allowed to talk to.
participants (3)
-
Kenneth Westfield
-
Mark Brown
-
Patrick Lai