162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Copyright (c) 2019 BayLibre, SAS.
462306a36Sopenharmony_ci// Author: Jerome Brunet <jbrunet@baylibre.com>
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/bitfield.h>
762306a36Sopenharmony_ci#include <linux/clk.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <sound/pcm_params.h>
1062306a36Sopenharmony_ci#include <linux/regmap.h>
1162306a36Sopenharmony_ci#include <linux/reset.h>
1262306a36Sopenharmony_ci#include <sound/soc.h>
1362306a36Sopenharmony_ci#include <sound/soc-dai.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <dt-bindings/sound/meson-g12a-tohdmitx.h>
1662306a36Sopenharmony_ci#include "meson-codec-glue.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define G12A_TOHDMITX_DRV_NAME "g12a-tohdmitx"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define TOHDMITX_CTRL0			0x0
2162306a36Sopenharmony_ci#define  CTRL0_ENABLE_SHIFT		31
2262306a36Sopenharmony_ci#define  CTRL0_I2S_DAT_SEL_SHIFT	12
2362306a36Sopenharmony_ci#define  CTRL0_I2S_DAT_SEL		(0x3 << CTRL0_I2S_DAT_SEL_SHIFT)
2462306a36Sopenharmony_ci#define  CTRL0_I2S_LRCLK_SEL		GENMASK(9, 8)
2562306a36Sopenharmony_ci#define  CTRL0_I2S_BLK_CAP_INV		BIT(7)
2662306a36Sopenharmony_ci#define  CTRL0_I2S_BCLK_O_INV		BIT(6)
2762306a36Sopenharmony_ci#define  CTRL0_I2S_BCLK_SEL		GENMASK(5, 4)
2862306a36Sopenharmony_ci#define  CTRL0_SPDIF_CLK_CAP_INV	BIT(3)
2962306a36Sopenharmony_ci#define  CTRL0_SPDIF_CLK_O_INV		BIT(2)
3062306a36Sopenharmony_ci#define  CTRL0_SPDIF_SEL_SHIFT		1
3162306a36Sopenharmony_ci#define  CTRL0_SPDIF_SEL		(0x1 << CTRL0_SPDIF_SEL_SHIFT)
3262306a36Sopenharmony_ci#define  CTRL0_SPDIF_CLK_SEL		BIT(0)
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic const char * const g12a_tohdmitx_i2s_mux_texts[] = {
3562306a36Sopenharmony_ci	"I2S A", "I2S B", "I2S C",
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic int g12a_tohdmitx_i2s_mux_put_enum(struct snd_kcontrol *kcontrol,
3962306a36Sopenharmony_ci				   struct snd_ctl_elem_value *ucontrol)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	struct snd_soc_component *component =
4262306a36Sopenharmony_ci		snd_soc_dapm_kcontrol_component(kcontrol);
4362306a36Sopenharmony_ci	struct snd_soc_dapm_context *dapm =
4462306a36Sopenharmony_ci		snd_soc_dapm_kcontrol_dapm(kcontrol);
4562306a36Sopenharmony_ci	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
4662306a36Sopenharmony_ci	unsigned int mux, changed;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	if (ucontrol->value.enumerated.item[0] >= e->items)
4962306a36Sopenharmony_ci		return -EINVAL;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
5262306a36Sopenharmony_ci	changed = snd_soc_component_test_bits(component, e->reg,
5362306a36Sopenharmony_ci					      CTRL0_I2S_DAT_SEL,
5462306a36Sopenharmony_ci					      FIELD_PREP(CTRL0_I2S_DAT_SEL,
5562306a36Sopenharmony_ci							 mux));
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (!changed)
5862306a36Sopenharmony_ci		return 0;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	/* Force disconnect of the mux while updating */
6162306a36Sopenharmony_ci	snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	snd_soc_component_update_bits(component, e->reg,
6462306a36Sopenharmony_ci				      CTRL0_I2S_DAT_SEL |
6562306a36Sopenharmony_ci				      CTRL0_I2S_LRCLK_SEL |
6662306a36Sopenharmony_ci				      CTRL0_I2S_BCLK_SEL,
6762306a36Sopenharmony_ci				      FIELD_PREP(CTRL0_I2S_DAT_SEL, mux) |
6862306a36Sopenharmony_ci				      FIELD_PREP(CTRL0_I2S_LRCLK_SEL, mux) |
6962306a36Sopenharmony_ci				      FIELD_PREP(CTRL0_I2S_BCLK_SEL, mux));
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return 1;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic SOC_ENUM_SINGLE_DECL(g12a_tohdmitx_i2s_mux_enum, TOHDMITX_CTRL0,
7762306a36Sopenharmony_ci			    CTRL0_I2S_DAT_SEL_SHIFT,
7862306a36Sopenharmony_ci			    g12a_tohdmitx_i2s_mux_texts);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic const struct snd_kcontrol_new g12a_tohdmitx_i2s_mux =
8162306a36Sopenharmony_ci	SOC_DAPM_ENUM_EXT("I2S Source", g12a_tohdmitx_i2s_mux_enum,
8262306a36Sopenharmony_ci			  snd_soc_dapm_get_enum_double,
8362306a36Sopenharmony_ci			  g12a_tohdmitx_i2s_mux_put_enum);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic const char * const g12a_tohdmitx_spdif_mux_texts[] = {
8662306a36Sopenharmony_ci	"SPDIF A", "SPDIF B",
8762306a36Sopenharmony_ci};
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic int g12a_tohdmitx_spdif_mux_put_enum(struct snd_kcontrol *kcontrol,
9062306a36Sopenharmony_ci					    struct snd_ctl_elem_value *ucontrol)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct snd_soc_component *component =
9362306a36Sopenharmony_ci		snd_soc_dapm_kcontrol_component(kcontrol);
9462306a36Sopenharmony_ci	struct snd_soc_dapm_context *dapm =
9562306a36Sopenharmony_ci		snd_soc_dapm_kcontrol_dapm(kcontrol);
9662306a36Sopenharmony_ci	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
9762306a36Sopenharmony_ci	unsigned int mux, changed;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (ucontrol->value.enumerated.item[0] >= e->items)
10062306a36Sopenharmony_ci		return -EINVAL;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
10362306a36Sopenharmony_ci	changed = snd_soc_component_test_bits(component, TOHDMITX_CTRL0,
10462306a36Sopenharmony_ci					      CTRL0_SPDIF_SEL,
10562306a36Sopenharmony_ci					      FIELD_PREP(CTRL0_SPDIF_SEL, mux));
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	if (!changed)
10862306a36Sopenharmony_ci		return 0;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* Force disconnect of the mux while updating */
11162306a36Sopenharmony_ci	snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	snd_soc_component_update_bits(component, TOHDMITX_CTRL0,
11462306a36Sopenharmony_ci				      CTRL0_SPDIF_SEL |
11562306a36Sopenharmony_ci				      CTRL0_SPDIF_CLK_SEL,
11662306a36Sopenharmony_ci				      FIELD_PREP(CTRL0_SPDIF_SEL, mux) |
11762306a36Sopenharmony_ci				      FIELD_PREP(CTRL0_SPDIF_CLK_SEL, mux));
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return 1;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic SOC_ENUM_SINGLE_DECL(g12a_tohdmitx_spdif_mux_enum, TOHDMITX_CTRL0,
12562306a36Sopenharmony_ci			    CTRL0_SPDIF_SEL_SHIFT,
12662306a36Sopenharmony_ci			    g12a_tohdmitx_spdif_mux_texts);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic const struct snd_kcontrol_new g12a_tohdmitx_spdif_mux =
12962306a36Sopenharmony_ci	SOC_DAPM_ENUM_EXT("SPDIF Source", g12a_tohdmitx_spdif_mux_enum,
13062306a36Sopenharmony_ci			  snd_soc_dapm_get_enum_double,
13162306a36Sopenharmony_ci			  g12a_tohdmitx_spdif_mux_put_enum);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic const struct snd_kcontrol_new g12a_tohdmitx_out_enable =
13462306a36Sopenharmony_ci	SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOHDMITX_CTRL0,
13562306a36Sopenharmony_ci				    CTRL0_ENABLE_SHIFT, 1, 0);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic const struct snd_soc_dapm_widget g12a_tohdmitx_widgets[] = {
13862306a36Sopenharmony_ci	SND_SOC_DAPM_MUX("I2S SRC", SND_SOC_NOPM, 0, 0,
13962306a36Sopenharmony_ci			 &g12a_tohdmitx_i2s_mux),
14062306a36Sopenharmony_ci	SND_SOC_DAPM_SWITCH("I2S OUT EN", SND_SOC_NOPM, 0, 0,
14162306a36Sopenharmony_ci			    &g12a_tohdmitx_out_enable),
14262306a36Sopenharmony_ci	SND_SOC_DAPM_MUX("SPDIF SRC", SND_SOC_NOPM, 0, 0,
14362306a36Sopenharmony_ci			 &g12a_tohdmitx_spdif_mux),
14462306a36Sopenharmony_ci	SND_SOC_DAPM_SWITCH("SPDIF OUT EN", SND_SOC_NOPM, 0, 0,
14562306a36Sopenharmony_ci			    &g12a_tohdmitx_out_enable),
14662306a36Sopenharmony_ci};
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic const struct snd_soc_dai_ops g12a_tohdmitx_input_ops = {
14962306a36Sopenharmony_ci	.probe		= meson_codec_glue_input_dai_probe,
15062306a36Sopenharmony_ci	.remove		= meson_codec_glue_input_dai_remove,
15162306a36Sopenharmony_ci	.hw_params	= meson_codec_glue_input_hw_params,
15262306a36Sopenharmony_ci	.set_fmt	= meson_codec_glue_input_set_fmt,
15362306a36Sopenharmony_ci};
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic const struct snd_soc_dai_ops g12a_tohdmitx_output_ops = {
15662306a36Sopenharmony_ci	.startup	= meson_codec_glue_output_startup,
15762306a36Sopenharmony_ci};
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci#define TOHDMITX_SPDIF_FORMATS					\
16062306a36Sopenharmony_ci	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |	\
16162306a36Sopenharmony_ci	 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci#define TOHDMITX_I2S_FORMATS					\
16462306a36Sopenharmony_ci	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |	\
16562306a36Sopenharmony_ci	 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE |	\
16662306a36Sopenharmony_ci	 SNDRV_PCM_FMTBIT_S32_LE)
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci#define TOHDMITX_STREAM(xname, xsuffix, xfmt, xchmax)		\
16962306a36Sopenharmony_ci{								\
17062306a36Sopenharmony_ci	.stream_name	= xname " " xsuffix,			\
17162306a36Sopenharmony_ci	.channels_min	= 1,					\
17262306a36Sopenharmony_ci	.channels_max	= (xchmax),				\
17362306a36Sopenharmony_ci	.rate_min       = 8000,					\
17462306a36Sopenharmony_ci	.rate_max	= 192000,				\
17562306a36Sopenharmony_ci	.formats	= (xfmt),				\
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci#define TOHDMITX_IN(xname, xid, xfmt, xchmax) {				\
17962306a36Sopenharmony_ci	.name = xname,							\
18062306a36Sopenharmony_ci	.id = (xid),							\
18162306a36Sopenharmony_ci	.playback = TOHDMITX_STREAM(xname, "Playback", xfmt, xchmax),	\
18262306a36Sopenharmony_ci	.ops = &g12a_tohdmitx_input_ops,				\
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci#define TOHDMITX_OUT(xname, xid, xfmt, xchmax) {			\
18662306a36Sopenharmony_ci	.name = xname,							\
18762306a36Sopenharmony_ci	.id = (xid),							\
18862306a36Sopenharmony_ci	.capture = TOHDMITX_STREAM(xname, "Capture", xfmt, xchmax),	\
18962306a36Sopenharmony_ci	.ops = &g12a_tohdmitx_output_ops,				\
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic struct snd_soc_dai_driver g12a_tohdmitx_dai_drv[] = {
19362306a36Sopenharmony_ci	TOHDMITX_IN("I2S IN A", TOHDMITX_I2S_IN_A,
19462306a36Sopenharmony_ci		    TOHDMITX_I2S_FORMATS, 8),
19562306a36Sopenharmony_ci	TOHDMITX_IN("I2S IN B", TOHDMITX_I2S_IN_B,
19662306a36Sopenharmony_ci		    TOHDMITX_I2S_FORMATS, 8),
19762306a36Sopenharmony_ci	TOHDMITX_IN("I2S IN C", TOHDMITX_I2S_IN_C,
19862306a36Sopenharmony_ci		    TOHDMITX_I2S_FORMATS, 8),
19962306a36Sopenharmony_ci	TOHDMITX_OUT("I2S OUT", TOHDMITX_I2S_OUT,
20062306a36Sopenharmony_ci		     TOHDMITX_I2S_FORMATS, 8),
20162306a36Sopenharmony_ci	TOHDMITX_IN("SPDIF IN A", TOHDMITX_SPDIF_IN_A,
20262306a36Sopenharmony_ci		    TOHDMITX_SPDIF_FORMATS, 2),
20362306a36Sopenharmony_ci	TOHDMITX_IN("SPDIF IN B", TOHDMITX_SPDIF_IN_B,
20462306a36Sopenharmony_ci		    TOHDMITX_SPDIF_FORMATS, 2),
20562306a36Sopenharmony_ci	TOHDMITX_OUT("SPDIF OUT", TOHDMITX_SPDIF_OUT,
20662306a36Sopenharmony_ci		     TOHDMITX_SPDIF_FORMATS, 2),
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic int g12a_tohdmi_component_probe(struct snd_soc_component *c)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	/* Initialize the static clock parameters */
21262306a36Sopenharmony_ci	return snd_soc_component_write(c, TOHDMITX_CTRL0,
21362306a36Sopenharmony_ci		     CTRL0_I2S_BLK_CAP_INV | CTRL0_SPDIF_CLK_CAP_INV);
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic const struct snd_soc_dapm_route g12a_tohdmitx_routes[] = {
21762306a36Sopenharmony_ci	{ "I2S SRC", "I2S A", "I2S IN A Playback" },
21862306a36Sopenharmony_ci	{ "I2S SRC", "I2S B", "I2S IN B Playback" },
21962306a36Sopenharmony_ci	{ "I2S SRC", "I2S C", "I2S IN C Playback" },
22062306a36Sopenharmony_ci	{ "I2S OUT EN", "Switch", "I2S SRC" },
22162306a36Sopenharmony_ci	{ "I2S OUT Capture", NULL, "I2S OUT EN" },
22262306a36Sopenharmony_ci	{ "SPDIF SRC", "SPDIF A", "SPDIF IN A Playback" },
22362306a36Sopenharmony_ci	{ "SPDIF SRC", "SPDIF B", "SPDIF IN B Playback" },
22462306a36Sopenharmony_ci	{ "SPDIF OUT EN", "Switch", "SPDIF SRC" },
22562306a36Sopenharmony_ci	{ "SPDIF OUT Capture", NULL, "SPDIF OUT EN" },
22662306a36Sopenharmony_ci};
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic const struct snd_soc_component_driver g12a_tohdmitx_component_drv = {
22962306a36Sopenharmony_ci	.probe			= g12a_tohdmi_component_probe,
23062306a36Sopenharmony_ci	.dapm_widgets		= g12a_tohdmitx_widgets,
23162306a36Sopenharmony_ci	.num_dapm_widgets	= ARRAY_SIZE(g12a_tohdmitx_widgets),
23262306a36Sopenharmony_ci	.dapm_routes		= g12a_tohdmitx_routes,
23362306a36Sopenharmony_ci	.num_dapm_routes	= ARRAY_SIZE(g12a_tohdmitx_routes),
23462306a36Sopenharmony_ci	.endianness		= 1,
23562306a36Sopenharmony_ci};
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic const struct regmap_config g12a_tohdmitx_regmap_cfg = {
23862306a36Sopenharmony_ci	.reg_bits	= 32,
23962306a36Sopenharmony_ci	.val_bits	= 32,
24062306a36Sopenharmony_ci	.reg_stride	= 4,
24162306a36Sopenharmony_ci};
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic const struct of_device_id g12a_tohdmitx_of_match[] = {
24462306a36Sopenharmony_ci	{ .compatible = "amlogic,g12a-tohdmitx", },
24562306a36Sopenharmony_ci	{}
24662306a36Sopenharmony_ci};
24762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, g12a_tohdmitx_of_match);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic int g12a_tohdmitx_probe(struct platform_device *pdev)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
25262306a36Sopenharmony_ci	void __iomem *regs;
25362306a36Sopenharmony_ci	struct regmap *map;
25462306a36Sopenharmony_ci	int ret;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	ret = device_reset(dev);
25762306a36Sopenharmony_ci	if (ret)
25862306a36Sopenharmony_ci		return ret;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	regs = devm_platform_ioremap_resource(pdev, 0);
26162306a36Sopenharmony_ci	if (IS_ERR(regs))
26262306a36Sopenharmony_ci		return PTR_ERR(regs);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	map = devm_regmap_init_mmio(dev, regs, &g12a_tohdmitx_regmap_cfg);
26562306a36Sopenharmony_ci	if (IS_ERR(map)) {
26662306a36Sopenharmony_ci		dev_err(dev, "failed to init regmap: %ld\n",
26762306a36Sopenharmony_ci			PTR_ERR(map));
26862306a36Sopenharmony_ci		return PTR_ERR(map);
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	return devm_snd_soc_register_component(dev,
27262306a36Sopenharmony_ci			&g12a_tohdmitx_component_drv, g12a_tohdmitx_dai_drv,
27362306a36Sopenharmony_ci			ARRAY_SIZE(g12a_tohdmitx_dai_drv));
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic struct platform_driver g12a_tohdmitx_pdrv = {
27762306a36Sopenharmony_ci	.driver = {
27862306a36Sopenharmony_ci		.name = G12A_TOHDMITX_DRV_NAME,
27962306a36Sopenharmony_ci		.of_match_table = g12a_tohdmitx_of_match,
28062306a36Sopenharmony_ci	},
28162306a36Sopenharmony_ci	.probe = g12a_tohdmitx_probe,
28262306a36Sopenharmony_ci};
28362306a36Sopenharmony_cimodule_platform_driver(g12a_tohdmitx_pdrv);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ciMODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
28662306a36Sopenharmony_ciMODULE_DESCRIPTION("Amlogic G12a To HDMI Tx Control Codec Driver");
28762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
288