Unverified Commit b0d0e85f authored by Mark Brown's avatar Mark Brown
Browse files

Machine driver to support LPASS SC7280 sound card registration

Merge series from Srinivasa Rao Mandadapu <quic_srivasam@quicinc.com>:

This patch set is to add support for SC7280 sound card registration and
to add dt-bindings documentation file.
parents 23c2f139 57350bd4
Loading
Loading
Loading
Loading
+180 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/sound/google,sc7280-herobrine.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Google SC7280-Herobrine ASoC sound card driver

maintainers:
  - Srinivasa Rao Mandadapu <srivasam@codeaurora.org>
  - Judy Hsiao <judyhsiao@chromium.org>

description:
  This binding describes the SC7280 sound card which uses LPASS for audio.

properties:
  compatible:
    enum:
      - google,sc7280-herobrine

  audio-routing:
    $ref: /schemas/types.yaml#/definitions/non-unique-string-array
    description:
      A list of the connections between audio components. Each entry is a
      pair of strings, the first being the connection's sink, the second
      being the connection's source.

  model:
    $ref: /schemas/types.yaml#/definitions/string
    description: User specified audio sound card name

  "#address-cells":
    const: 1

  "#size-cells":
    const: 0

patternProperties:
  "^dai-link@[0-9a-f]$":
    description:
      Each subnode represents a dai link. Subnodes of each dai links would be
      cpu/codec dais.

    type: object

    properties:
      link-name:
        description: Indicates dai-link name and PCM stream name.
        $ref: /schemas/types.yaml#/definitions/string
        maxItems: 1

      reg:
        maxItems: 1
        description: dai link address.

      cpu:
        description: Holds subnode which indicates cpu dai.
        type: object
        properties:
          sound-dai: true

        required:
          - sound-dai

        additionalProperties: false

      codec:
        description: Holds subnode which indicates codec dai.
        type: object
        properties:
          sound-dai: true

        required:
          - sound-dai

        additionalProperties: false

    required:
      - link-name
      - cpu
      - codec
      - reg

    additionalProperties: false

required:
  - compatible
  - model
  - "#address-cells"
  - "#size-cells"

additionalProperties: false

examples:

  - |
    #include <dt-bindings/sound/qcom,lpass.h>
    sound {
        compatible = "google,sc7280-herobrine";
        model = "sc7280-wcd938x-max98360a-4dmic";

        audio-routing =
            "IN1_HPHL", "HPHL_OUT",
            "IN2_HPHR", "HPHR_OUT",
            "AMIC1", "MIC BIAS1",
            "AMIC2", "MIC BIAS2",
            "VA DMIC0", "MIC BIAS3",
            "VA DMIC1", "MIC BIAS3",
            "VA DMIC2", "MIC BIAS4",
            "VA DMIC3", "MIC BIAS4",
            "TX SWR_ADC0", "ADC1_OUTPUT",
            "TX SWR_ADC1", "ADC2_OUTPUT",
            "TX SWR_ADC2", "ADC3_OUTPUT",
            "TX SWR_DMIC0", "DMIC1_OUTPUT",
            "TX SWR_DMIC1", "DMIC2_OUTPUT",
            "TX SWR_DMIC2", "DMIC3_OUTPUT",
            "TX SWR_DMIC3", "DMIC4_OUTPUT";

        #address-cells = <1>;
        #size-cells = <0>;

        dai-link@0 {
            link-name = "WCD Playback";
            reg = <LPASS_CDC_DMA_RX0>;
            cpu {
                sound-dai = <&lpass_cpu LPASS_CDC_DMA_RX0>;
            };

            codec {
                sound-dai = <&wcd938x 0>, <&swr0 0>, <&rxmacro 0>;
            };
        };
        dai-link@1 {
            link-name = "WCD Capture";
            reg = <LPASS_CDC_DMA_TX3>;
            cpu {
                sound-dai = <&lpass_cpu LPASS_CDC_DMA_TX3>;
            };

            codec {
                sound-dai = <&wcd938x 1>, <&swr1 0>, <&txmacro 0>;
            };
        };

        dai-link@2 {
            link-name = "MI2S Playback";
            reg = <MI2S_SECONDARY>;
            cpu {
                sound-dai = <&lpass_cpu MI2S_SECONDARY>;
            };

            codec {
                sound-dai = <&max98360a>;
            };
        };

        dai-link@3 {
            link-name = "DMIC Capture";
            reg = <LPASS_CDC_DMA_VA_TX0>;
            cpu {
                sound-dai = <&lpass_cpu LPASS_CDC_DMA_VA_TX0>;
            };

            codec {
                sound-dai = <&vamacro 0>;
            };
        };

        dai-link@5 {
            link-name = "DP Playback";
            reg = <LPASS_DP_RX>;
            cpu {
                sound-dai = <&lpass_cpu LPASS_DP_RX>;
            };

            codec {
                sound-dai = <&mdss_dp>;
            };
        };
    };
+14 −0
Original line number Diff line number Diff line
@@ -176,4 +176,18 @@ config SND_SOC_SC7180
	  SC7180 SoC-based systems.
	  Say Y if you want to use audio device on this SoCs.

config SND_SOC_SC7280
	tristate "SoC Machine driver for SC7280 boards"
	depends on I2C && SOUNDWIRE || COMPILE_TEST
	select SND_SOC_QCOM_COMMON
	select SND_SOC_LPASS_SC7280
	select SND_SOC_MAX98357A
	select SND_SOC_WCD938X
	select SND_SOC_LPASS_RX_MACRO
	select SND_SOC_LPASS_TX_MACRO
	help
	  Add support for audio on Qualcomm Technologies Inc.
	  SC7280 SoC-based systems.
	  Say Y or M if you want to use audio device on this SoCs.

endif #SND_SOC_QCOM
+2 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ snd-soc-storm-objs := storm.o
snd-soc-apq8016-sbc-objs := apq8016_sbc.o
snd-soc-apq8096-objs := apq8096.o
snd-soc-sc7180-objs := sc7180.o
snd-soc-sc7280-objs := sc7280.o
snd-soc-sdm845-objs := sdm845.o
snd-soc-sm8250-objs := sm8250.o
snd-soc-qcom-common-objs := common.o
@@ -27,6 +28,7 @@ obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o
obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o
obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o
obj-$(CONFIG_SND_SOC_SC7180) += snd-soc-sc7180.o
obj-$(CONFIG_SND_SOC_SC7280) += snd-soc-sc7280.o
obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o
obj-$(CONFIG_SND_SOC_SM8250) += snd-soc-sm8250.o
obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o
+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include "lpass-hdmi.h"

#define LPASS_AHBIX_CLOCK_FREQUENCY		131072000
#define LPASS_MAX_PORTS			(LPASS_CDC_DMA_VA_TX8 + 1)
#define LPASS_MAX_MI2S_PORTS			(8)
#define LPASS_MAX_DMA_CHANNELS			(8)
#define LPASS_MAX_HDMI_DMA_CHANNELS		(4)
+284 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
//
// ALSA SoC Machine driver for sc7280

#include <linux/input.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <linux/soundwire/sdw.h>

#include "common.h"
#include "lpass.h"

struct sc7280_snd_data {
	struct snd_soc_card card;
	struct sdw_stream_runtime *sruntime[LPASS_MAX_PORTS];
	struct snd_soc_jack hs_jack;
	struct snd_soc_jack hdmi_jack;
	bool jack_setup;
	bool stream_prepared[LPASS_MAX_PORTS];
};

static void sc7280_jack_free(struct snd_jack *jack)
{
	struct snd_soc_component *component = jack->private_data;

	snd_soc_component_set_jack(component, NULL, NULL);
}

static int sc7280_headset_init(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_card *card = rtd->card;
	struct sc7280_snd_data *pdata = snd_soc_card_get_drvdata(card);
	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
	struct snd_soc_component *component = codec_dai->component;
	struct snd_jack *jack;
	int rval, i;

	if (!pdata->jack_setup) {
		rval = snd_soc_card_jack_new(card, "Headset Jack",
					     SND_JACK_HEADSET | SND_JACK_LINEOUT |
					     SND_JACK_MECHANICAL |
					     SND_JACK_BTN_0 | SND_JACK_BTN_1 |
					     SND_JACK_BTN_2 | SND_JACK_BTN_3 |
					     SND_JACK_BTN_4 | SND_JACK_BTN_5,
					     &pdata->hs_jack, NULL, 0);

		if (rval < 0) {
			dev_err(card->dev, "Unable to add Headset Jack\n");
			return rval;
		}

		jack = pdata->hs_jack.jack;

		snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
		snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
		snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
		snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);

		jack->private_data = component;
		jack->private_free = sc7280_jack_free;
		pdata->jack_setup = true;
	}
	switch (cpu_dai->id) {
	case LPASS_CDC_DMA_RX0:
	case LPASS_CDC_DMA_TX3:
		for_each_rtd_codec_dais(rtd, i, codec_dai) {
			rval = snd_soc_component_set_jack(component, &pdata->hs_jack, NULL);
			if (rval != 0 && rval != -ENOTSUPP) {
				dev_err(card->dev, "Failed to set jack: %d\n", rval);
				return rval;
			}
		}
		break;
	default:
		break;
	}

	return 0;
}

static int sc7280_hdmi_init(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_card *card = rtd->card;
	struct sc7280_snd_data *pdata = snd_soc_card_get_drvdata(card);
	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
	struct snd_soc_component *component = codec_dai->component;
	struct snd_jack *jack;
	int rval;

	rval = snd_soc_card_jack_new(card, "HDMI Jack",	SND_JACK_LINEOUT,
				     &pdata->hdmi_jack, NULL, 0);

	if (rval < 0) {
		dev_err(card->dev, "Unable to add HDMI Jack\n");
		return rval;
	}

	jack = pdata->hdmi_jack.jack;
	jack->private_data = component;
	jack->private_free = sc7280_jack_free;

	return snd_soc_component_set_jack(component, &pdata->hdmi_jack, NULL);
}

static int sc7280_init(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);

	switch (cpu_dai->id) {
	case LPASS_CDC_DMA_TX3:
		return sc7280_headset_init(rtd);
	case LPASS_CDC_DMA_RX0:
	case LPASS_CDC_DMA_VA_TX0:
	case MI2S_SECONDARY:
		return 0;
	case LPASS_DP_RX:
		return sc7280_hdmi_init(rtd);
	default:
		dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
	}

	return -EINVAL;
}

static int sc7280_snd_hw_params(struct snd_pcm_substream *substream,
				struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *codec_dai;
	const struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
	struct sc7280_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
	struct sdw_stream_runtime *sruntime;
	int i;

	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 48000, 48000);

	switch (cpu_dai->id) {
	case LPASS_CDC_DMA_TX3:
	case LPASS_CDC_DMA_RX0:
		for_each_rtd_codec_dais(rtd, i, codec_dai) {
			sruntime = snd_soc_dai_get_stream(codec_dai, substream->stream);
			if (sruntime != ERR_PTR(-ENOTSUPP))
				pdata->sruntime[cpu_dai->id] = sruntime;
		}
		break;
	}

	return 0;
}

static int sc7280_snd_swr_prepare(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	const struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
	struct sc7280_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
	struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
	int ret;

	if (!sruntime)
		return 0;

	if (data->stream_prepared[cpu_dai->id]) {
		sdw_disable_stream(sruntime);
		sdw_deprepare_stream(sruntime);
		data->stream_prepared[cpu_dai->id] = false;
	}

	ret = sdw_prepare_stream(sruntime);
	if (ret)
		return ret;

	ret = sdw_enable_stream(sruntime);
	if (ret) {
		sdw_deprepare_stream(sruntime);
		return ret;
	}
	data->stream_prepared[cpu_dai->id] = true;

	return ret;
}

static int sc7280_snd_prepare(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	const struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);

	switch (cpu_dai->id) {
	case LPASS_CDC_DMA_RX0:
	case LPASS_CDC_DMA_TX3:
		return sc7280_snd_swr_prepare(substream);
	default:
		break;
	}

	return 0;
}

static int sc7280_snd_hw_free(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct sc7280_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
	const struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
	struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];

	switch (cpu_dai->id) {
	case LPASS_CDC_DMA_RX0:
	case LPASS_CDC_DMA_TX3:
		if (sruntime && data->stream_prepared[cpu_dai->id]) {
			sdw_disable_stream(sruntime);
			sdw_deprepare_stream(sruntime);
			data->stream_prepared[cpu_dai->id] = false;
		}
		break;
	default:
		break;
	}
	return 0;
}

static const struct snd_soc_ops sc7280_ops = {
	.hw_params = sc7280_snd_hw_params,
	.hw_free = sc7280_snd_hw_free,
	.prepare = sc7280_snd_prepare,
};

static int sc7280_snd_platform_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card;
	struct sc7280_snd_data *data;
	struct device *dev = &pdev->dev;
	struct snd_soc_dai_link *link;
	int ret, i;

	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	card = &data->card;
	snd_soc_card_set_drvdata(card, data);

	card->owner = THIS_MODULE;
	card->driver_name = "SC7280";
	card->dev = dev;

	ret = qcom_snd_parse_of(card);
	if (ret)
		return ret;

	for_each_card_prelinks(card, i, link) {
		link->init = sc7280_init;
		link->ops = &sc7280_ops;
	}

	return devm_snd_soc_register_card(dev, card);
}

static const struct of_device_id sc7280_snd_device_id[]  = {
	{ .compatible = "google,sc7280-herobrine" },
	{}
};
MODULE_DEVICE_TABLE(of, sc7280_snd_device_id);

static struct platform_driver sc7280_snd_driver = {
	.probe = sc7280_snd_platform_probe,
	.driver = {
		.name = "msm-snd-sc7280",
		.of_match_table = sc7280_snd_device_id,
		.pm = &snd_soc_pm_ops,
	},
};
module_platform_driver(sc7280_snd_driver);

MODULE_DESCRIPTION("sc7280 ASoC Machine Driver");
MODULE_LICENSE("GPL");