[PATCH v2 2/2] ASoC: qcom: lpass-cpu: Make I2S SD lines configurable
Srinivas Kandagatla
srinivas.kandagatla at linaro.org
Wed May 6 13:17:16 CEST 2020
Thanks Stephan for doing this,
On 25/04/2020 19:46, Stephan Gerhold wrote:
> The LPASS hardware allows configuring the MI2S SD lines to use
> when playing/recording audio. However, at the moment the lpass-cpu
> driver has SD0 hard-coded for mono/stereo (or additional fixed
> SD lines for more channels).
>
> For weird reasons there seems to be hardware that uses one of the
> other SD lines for mono/stereo. For example, some Samsung devices
> use an external Speaker amplifier connected to Quaternary MI2S.
> For some reason, the SD line for audio playback was connected to
> SD1 rather than SD0. (I have no idea why...)
> At the moment, the lpass-cpu driver cannot be configured to work
> for the Speaker on these devices.
>
> The q6afe driver already allows configuring the MI2S SD lines
> through the "qcom,sd-lines" device tree property, but this works
> only when routing audio through the ADSP.
>
> This commit adds a very similar configuration for the lpass-cpu driver.
> It is now possible to add additional subnodes to the lpass device in
> the device tree, to configure the SD lines for playback and/or capture.
> E.g. for the Samsung devices mentioned above:
>
> &lpass {
> dai at 3 {
> reg = <MI2S_QUATERNARY>;
> qcom,playback-sd-lines = <1>;
> };
> };
>
> qcom,playback/capture-sd-lines takes a list of SD lines (0-3)
> in the same format as the q6afe driver. (The difference here is that
> q6afe has separate DAIs for playback/capture, while lpass-cpu has one
> for both...)
>
> For backwards compatibility with older device trees, the lpass-cpu driver
> defaults to LPAIF_I2SCTL_MODE_8CH if the subnode for a DAI is missing.
> This is equivalent to the previous behavior: Up to 8 channels can be
> configured, and SD0/QUAT01 will be chosen when setting up a stream
> with fewer channels.
>
> This allows the speaker to work on Samsung MSM8916 devices
> that use an external speaker amplifier.
>
> Cc: Srinivas Kandagatla <srinivas.kandagatla at linaro.org>
> Signed-off-by: Stephan Gerhold <stephan at gerhold.net>
> ---
> Changes in v2:
> - Suggest generic node name (dai at ...) for subnodes in example above.
>
> v1: https://lore.kernel.org/alsa-devel/20200406135608.126171-2-stephan@gerhold.net/
> ---
> sound/soc/qcom/lpass-cpu.c | 196 +++++++++++++++++++++++--------
> sound/soc/qcom/lpass-lpaif-reg.h | 30 ++---
> sound/soc/qcom/lpass.h | 4 +
> 3 files changed, 166 insertions(+), 64 deletions(-)
>
LGTM,
Reviewed-by: Srinivas Kandagatla <srinivas.kandagatla at linaro.org>
> diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c
> index dbce7e92baf3..de2b6d60ce6a 100644
> --- a/sound/soc/qcom/lpass-cpu.c
> +++ b/sound/soc/qcom/lpass-cpu.c
> @@ -19,6 +19,16 @@
> #include "lpass-lpaif-reg.h"
> #include "lpass.h"
>
> +#define LPASS_CPU_MAX_MI2S_LINES 4
> +#define LPASS_CPU_I2S_SD0_MASK BIT(0)
> +#define LPASS_CPU_I2S_SD1_MASK BIT(1)
> +#define LPASS_CPU_I2S_SD2_MASK BIT(2)
> +#define LPASS_CPU_I2S_SD3_MASK BIT(3)
> +#define LPASS_CPU_I2S_SD0_1_MASK GENMASK(1, 0)
> +#define LPASS_CPU_I2S_SD2_3_MASK GENMASK(3, 2)
> +#define LPASS_CPU_I2S_SD0_1_2_MASK GENMASK(2, 0)
> +#define LPASS_CPU_I2S_SD0_1_2_3_MASK GENMASK(3, 0)
> +
> static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id,
> unsigned int freq, int dir)
> {
> @@ -72,6 +82,7 @@ static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
> snd_pcm_format_t format = params_format(params);
> unsigned int channels = params_channels(params);
> unsigned int rate = params_rate(params);
> + unsigned int mode;
> unsigned int regval;
> int bitwidth, ret;
>
> @@ -99,60 +110,84 @@ static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
> return -EINVAL;
> }
>
> - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> - 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;
> + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> + mode = drvdata->mi2s_playback_sd_mode[dai->driver->id];
> + else
> + mode = drvdata->mi2s_capture_sd_mode[dai->driver->id];
> +
> + if (!mode) {
> + dev_err(dai->dev, "no line is assigned\n");
> + return -EINVAL;
> + }
> +
> + switch (channels) {
> + case 1:
> + case 2:
> + switch (mode) {
> + case LPAIF_I2SCTL_MODE_QUAD01:
> + case LPAIF_I2SCTL_MODE_6CH:
> + case LPAIF_I2SCTL_MODE_8CH:
> + mode = LPAIF_I2SCTL_MODE_SD0;
> break;
> - case 8:
> - regval |= LPAIF_I2SCTL_SPKMODE_8CH;
> - regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
> + case LPAIF_I2SCTL_MODE_QUAD23:
> + mode = LPAIF_I2SCTL_MODE_SD2;
> break;
> - default:
> - dev_err(dai->dev, "invalid channels given: %u\n",
> - channels);
> + }
> +
> + break;
> + case 4:
> + if (mode < LPAIF_I2SCTL_MODE_QUAD01) {
> + dev_err(dai->dev, "cannot configure 4 channels with mode %d\n",
> + mode);
> return -EINVAL;
> }
> - } else {
> - switch (channels) {
> - case 1:
> - regval |= LPAIF_I2SCTL_MICMODE_SD0;
> - regval |= LPAIF_I2SCTL_MICMONO_MONO;
> - break;
> - case 2:
> - regval |= LPAIF_I2SCTL_MICMODE_SD0;
> - regval |= LPAIF_I2SCTL_MICMONO_STEREO;
> - break;
> - case 4:
> - regval |= LPAIF_I2SCTL_MICMODE_QUAD01;
> - regval |= LPAIF_I2SCTL_MICMONO_STEREO;
> - break;
> - case 6:
> - regval |= LPAIF_I2SCTL_MICMODE_6CH;
> - regval |= LPAIF_I2SCTL_MICMONO_STEREO;
> +
> + switch (mode) {
> + case LPAIF_I2SCTL_MODE_6CH:
> + case LPAIF_I2SCTL_MODE_8CH:
> + mode = LPAIF_I2SCTL_MODE_QUAD01;
> break;
> - case 8:
> - regval |= LPAIF_I2SCTL_MICMODE_8CH;
> - regval |= LPAIF_I2SCTL_MICMONO_STEREO;
> + }
> + break;
> + case 6:
> + if (mode < LPAIF_I2SCTL_MODE_6CH) {
> + dev_err(dai->dev, "cannot configure 6 channels with mode %d\n",
> + mode);
> + return -EINVAL;
> + }
> +
> + switch (mode) {
> + case LPAIF_I2SCTL_MODE_8CH:
> + mode = LPAIF_I2SCTL_MODE_6CH;
> break;
> - default:
> - dev_err(dai->dev, "invalid channels given: %u\n",
> - channels);
> + }
> + break;
> + case 8:
> + if (mode < LPAIF_I2SCTL_MODE_8CH) {
> + dev_err(dai->dev, "cannot configure 8 channels with mode %d\n",
> + mode);
> return -EINVAL;
> }
> + break;
> + default:
> + dev_err(dai->dev, "invalid channels given: %u\n", channels);
> + return -EINVAL;
> + }
> +
> + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> + regval |= LPAIF_I2SCTL_SPKMODE(mode);
> +
> + if (channels >= 2)
> + regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
> + else
> + regval |= LPAIF_I2SCTL_SPKMONO_MONO;
> + } else {
> + regval |= LPAIF_I2SCTL_MICMODE(mode);
> +
> + if (channels >= 2)
> + regval |= LPAIF_I2SCTL_MICMONO_STEREO;
> + else
> + regval |= LPAIF_I2SCTL_MICMONO_MONO;
> }
>
> ret = regmap_write(drvdata->lpaif_map,
> @@ -413,6 +448,73 @@ static struct regmap_config lpass_cpu_regmap_config = {
> .cache_type = REGCACHE_FLAT,
> };
>
> +static unsigned int of_lpass_cpu_parse_sd_lines(struct device *dev,
> + struct device_node *node,
> + const char *name)
> +{
> + unsigned int lines[LPASS_CPU_MAX_MI2S_LINES];
> + unsigned int sd_line_mask = 0;
> + int num_lines, i;
> +
> + num_lines = of_property_read_variable_u32_array(node, name, lines, 0,
> + LPASS_CPU_MAX_MI2S_LINES);
> + if (num_lines < 0)
> + return LPAIF_I2SCTL_MODE_NONE;
> +
> + for (i = 0; i < num_lines; i++)
> + sd_line_mask |= BIT(lines[i]);
> +
> + switch (sd_line_mask) {
> + case LPASS_CPU_I2S_SD0_MASK:
> + return LPAIF_I2SCTL_MODE_SD0;
> + case LPASS_CPU_I2S_SD1_MASK:
> + return LPAIF_I2SCTL_MODE_SD1;
> + case LPASS_CPU_I2S_SD2_MASK:
> + return LPAIF_I2SCTL_MODE_SD2;
> + case LPASS_CPU_I2S_SD3_MASK:
> + return LPAIF_I2SCTL_MODE_SD3;
> + case LPASS_CPU_I2S_SD0_1_MASK:
> + return LPAIF_I2SCTL_MODE_QUAD01;
> + case LPASS_CPU_I2S_SD2_3_MASK:
> + return LPAIF_I2SCTL_MODE_QUAD23;
> + case LPASS_CPU_I2S_SD0_1_2_MASK:
> + return LPAIF_I2SCTL_MODE_6CH;
> + case LPASS_CPU_I2S_SD0_1_2_3_MASK:
> + return LPAIF_I2SCTL_MODE_8CH;
> + default:
> + dev_err(dev, "Unsupported SD line mask: %#x\n", sd_line_mask);
> + return LPAIF_I2SCTL_MODE_NONE;
> + }
> +}
> +
> +static void of_lpass_cpu_parse_dai_data(struct device *dev,
> + struct lpass_data *data)
> +{
> + struct device_node *node;
> + int ret, id;
> +
> + /* Allow all channels by default for backwards compatibility */
> + for (id = 0; id < data->variant->num_dai; id++) {
> + data->mi2s_playback_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH;
> + data->mi2s_capture_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH;
> + }
> +
> + for_each_child_of_node(dev->of_node, node) {
> + ret = of_property_read_u32(node, "reg", &id);
> + if (ret || id < 0 || id >= data->variant->num_dai) {
> + dev_err(dev, "valid dai id not found: %d\n", ret);
> + continue;
> + }
> +
> + data->mi2s_playback_sd_mode[id] =
> + of_lpass_cpu_parse_sd_lines(dev, node,
> + "qcom,playback-sd-lines");
> + data->mi2s_capture_sd_mode[id] =
> + of_lpass_cpu_parse_sd_lines(dev, node,
> + "qcom,capture-sd-lines");
> + }
> +}
> +
> int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev)
> {
> struct lpass_data *drvdata;
> @@ -442,6 +544,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev)
> drvdata->variant = (struct lpass_variant *)match->data;
> variant = drvdata->variant;
>
> + of_lpass_cpu_parse_dai_data(dev, drvdata);
> +
> res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif");
>
> drvdata->lpaif = devm_ioremap_resource(&pdev->dev, res);
> diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h
> index 3d74ae123e9d..72a3e2f69572 100644
> --- a/sound/soc/qcom/lpass-lpaif-reg.h
> +++ b/sound/soc/qcom/lpass-lpaif-reg.h
> @@ -22,17 +22,19 @@
> #define LPAIF_I2SCTL_SPKEN_DISABLE (0 << LPAIF_I2SCTL_SPKEN_SHIFT)
> #define LPAIF_I2SCTL_SPKEN_ENABLE (1 << LPAIF_I2SCTL_SPKEN_SHIFT)
>
> +#define LPAIF_I2SCTL_MODE_NONE 0
> +#define LPAIF_I2SCTL_MODE_SD0 1
> +#define LPAIF_I2SCTL_MODE_SD1 2
> +#define LPAIF_I2SCTL_MODE_SD2 3
> +#define LPAIF_I2SCTL_MODE_SD3 4
> +#define LPAIF_I2SCTL_MODE_QUAD01 5
> +#define LPAIF_I2SCTL_MODE_QUAD23 6
> +#define LPAIF_I2SCTL_MODE_6CH 7
> +#define LPAIF_I2SCTL_MODE_8CH 8
> +
> #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_SPKMODE(mode) ((mode) << LPAIF_I2SCTL_SPKMODE_SHIFT)
>
> #define LPAIF_I2SCTL_SPKMONO_MASK 0x0200
> #define LPAIF_I2SCTL_SPKMONO_SHIFT 9
> @@ -46,15 +48,7 @@
>
> #define LPAIF_I2SCTL_MICMODE_MASK GENMASK(7, 4)
> #define LPAIF_I2SCTL_MICMODE_SHIFT 4
> -#define LPAIF_I2SCTL_MICMODE_NONE (0 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_SD0 (1 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_SD1 (2 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_SD2 (3 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_SD3 (4 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_QUAD01 (5 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_QUAD23 (6 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_6CH (7 << LPAIF_I2SCTL_MICMODE_SHIFT)
> -#define LPAIF_I2SCTL_MICMODE_8CH (8 << LPAIF_I2SCTL_MICMODE_SHIFT)
> +#define LPAIF_I2SCTL_MICMODE(mode) ((mode) << LPAIF_I2SCTL_MICMODE_SHIFT)
>
> #define LPAIF_I2SCTL_MIMONO_MASK GENMASK(3, 3)
> #define LPAIF_I2SCTL_MICMONO_SHIFT 3
> diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h
> index 17113d380dcc..bd19ec57c73d 100644
> --- a/sound/soc/qcom/lpass.h
> +++ b/sound/soc/qcom/lpass.h
> @@ -29,6 +29,10 @@ struct lpass_data {
> /* MI2S bit clock (derived from system clock by a divider */
> struct clk *mi2s_bit_clk[LPASS_MAX_MI2S_PORTS];
>
> + /* MI2S SD lines to use for playback/capture */
> + unsigned int mi2s_playback_sd_mode[LPASS_MAX_MI2S_PORTS];
> + unsigned int mi2s_capture_sd_mode[LPASS_MAX_MI2S_PORTS];
> +
> /* low-power audio interface (LPAIF) registers */
> void __iomem *lpaif;
>
>
More information about the Alsa-devel
mailing list