1 // SPDX-License-Identifier: GPL-2.0
3 // Copyright (c) 2020 BayLibre, SAS.
4 // Author: Jerome Brunet <jbrunet@baylibre.com>
6 #include <linux/bitfield.h>
8 #include <linux/module.h>
9 #include <sound/pcm_params.h>
10 #include <linux/regmap.h>
11 #include <linux/regulator/consumer.h>
12 #include <linux/reset.h>
13 #include <sound/soc.h>
14 #include <sound/soc-dai.h>
16 #include <dt-bindings/sound/meson-g12a-toacodec.h>
18 #include "meson-codec-glue.h"
20 #define G12A_TOACODEC_DRV_NAME "g12a-toacodec"
22 #define TOACODEC_CTRL0 0x0
23 #define CTRL0_ENABLE_SHIFT 31
24 #define CTRL0_DAT_SEL_SM1_MSB 19
25 #define CTRL0_DAT_SEL_SM1_LSB 18
26 #define CTRL0_DAT_SEL_MSB 15
27 #define CTRL0_DAT_SEL_LSB 14
28 #define CTRL0_LANE_SEL_SM1 16
29 #define CTRL0_LANE_SEL 12
30 #define CTRL0_LRCLK_SEL_SM1_MSB 14
31 #define CTRL0_LRCLK_SEL_SM1_LSB 12
32 #define CTRL0_LRCLK_SEL_MSB 9
33 #define CTRL0_LRCLK_SEL_LSB 8
34 #define CTRL0_LRCLK_INV_SM1 BIT(10)
35 #define CTRL0_BLK_CAP_INV_SM1 BIT(9)
36 #define CTRL0_BLK_CAP_INV BIT(7)
37 #define CTRL0_BCLK_O_INV_SM1 BIT(8)
38 #define CTRL0_BCLK_O_INV BIT(6)
39 #define CTRL0_BCLK_SEL_SM1_MSB 6
40 #define CTRL0_BCLK_SEL_MSB 5
41 #define CTRL0_BCLK_SEL_LSB 4
42 #define CTRL0_MCLK_SEL GENMASK(2, 0)
44 #define TOACODEC_OUT_CHMAX 2
46 struct g12a_toacodec
{
47 struct regmap_field
*field_dat_sel
;
48 struct regmap_field
*field_lrclk_sel
;
49 struct regmap_field
*field_bclk_sel
;
52 struct g12a_toacodec_match_data
{
53 const struct snd_soc_component_driver
*component_drv
;
54 struct reg_field field_dat_sel
;
55 struct reg_field field_lrclk_sel
;
56 struct reg_field field_bclk_sel
;
59 static const char * const g12a_toacodec_mux_texts
[] = {
60 "I2S A", "I2S B", "I2S C",
63 static int g12a_toacodec_mux_put_enum(struct snd_kcontrol
*kcontrol
,
64 struct snd_ctl_elem_value
*ucontrol
)
66 struct snd_soc_component
*component
=
67 snd_soc_dapm_kcontrol_component(kcontrol
);
68 struct g12a_toacodec
*priv
= snd_soc_component_get_drvdata(component
);
69 struct snd_soc_dapm_context
*dapm
=
70 snd_soc_dapm_kcontrol_dapm(kcontrol
);
71 struct soc_enum
*e
= (struct soc_enum
*)kcontrol
->private_value
;
72 unsigned int mux
, reg
;
74 if (ucontrol
->value
.enumerated
.item
[0] >= e
->items
)
77 mux
= snd_soc_enum_item_to_val(e
, ucontrol
->value
.enumerated
.item
[0]);
78 regmap_field_read(priv
->field_dat_sel
, ®
);
83 /* Force disconnect of the mux while updating */
84 snd_soc_dapm_mux_update_power(dapm
, kcontrol
, 0, NULL
, NULL
);
86 regmap_field_write(priv
->field_dat_sel
, mux
);
87 regmap_field_write(priv
->field_lrclk_sel
, mux
);
88 regmap_field_write(priv
->field_bclk_sel
, mux
);
92 * On this soc, the glue gets the MCLK directly from the clock
93 * controller instead of going the through the TDM interface.
95 * Here we assume interface A uses clock A, etc ... While it is
96 * true for now, it could be different. Instead the glue should
97 * find out the clock used by the interface and select the same
98 * source. For that, we will need regmap backed clock mux which
99 * is a work in progress
101 snd_soc_component_update_bits(component
, e
->reg
,
103 FIELD_PREP(CTRL0_MCLK_SEL
, mux
));
105 snd_soc_dapm_mux_update_power(dapm
, kcontrol
, mux
, e
, NULL
);
110 static SOC_ENUM_SINGLE_DECL(g12a_toacodec_mux_enum
, TOACODEC_CTRL0
,
112 g12a_toacodec_mux_texts
);
114 static SOC_ENUM_SINGLE_DECL(sm1_toacodec_mux_enum
, TOACODEC_CTRL0
,
115 CTRL0_DAT_SEL_SM1_LSB
,
116 g12a_toacodec_mux_texts
);
118 static const struct snd_kcontrol_new g12a_toacodec_mux
=
119 SOC_DAPM_ENUM_EXT("Source", g12a_toacodec_mux_enum
,
120 snd_soc_dapm_get_enum_double
,
121 g12a_toacodec_mux_put_enum
);
123 static const struct snd_kcontrol_new sm1_toacodec_mux
=
124 SOC_DAPM_ENUM_EXT("Source", sm1_toacodec_mux_enum
,
125 snd_soc_dapm_get_enum_double
,
126 g12a_toacodec_mux_put_enum
);
128 static const struct snd_kcontrol_new g12a_toacodec_out_enable
=
129 SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0
,
130 CTRL0_ENABLE_SHIFT
, 1, 0);
132 static const struct snd_soc_dapm_widget g12a_toacodec_widgets
[] = {
133 SND_SOC_DAPM_MUX("SRC", SND_SOC_NOPM
, 0, 0,
135 SND_SOC_DAPM_SWITCH("OUT EN", SND_SOC_NOPM
, 0, 0,
136 &g12a_toacodec_out_enable
),
139 static const struct snd_soc_dapm_widget sm1_toacodec_widgets
[] = {
140 SND_SOC_DAPM_MUX("SRC", SND_SOC_NOPM
, 0, 0,
142 SND_SOC_DAPM_SWITCH("OUT EN", SND_SOC_NOPM
, 0, 0,
143 &g12a_toacodec_out_enable
),
146 static int g12a_toacodec_input_hw_params(struct snd_pcm_substream
*substream
,
147 struct snd_pcm_hw_params
*params
,
148 struct snd_soc_dai
*dai
)
150 struct meson_codec_glue_input
*data
;
153 ret
= meson_codec_glue_input_hw_params(substream
, params
, dai
);
157 /* The glue will provide 1 lane out of the 4 to the output */
158 data
= meson_codec_glue_input_get_data(dai
);
159 data
->params
.channels_min
= min_t(unsigned int, TOACODEC_OUT_CHMAX
,
160 data
->params
.channels_min
);
161 data
->params
.channels_max
= min_t(unsigned int, TOACODEC_OUT_CHMAX
,
162 data
->params
.channels_max
);
167 static const struct snd_soc_dai_ops g12a_toacodec_input_ops
= {
168 .probe
= meson_codec_glue_input_dai_probe
,
169 .remove
= meson_codec_glue_input_dai_remove
,
170 .hw_params
= g12a_toacodec_input_hw_params
,
171 .set_fmt
= meson_codec_glue_input_set_fmt
,
174 static const struct snd_soc_dai_ops g12a_toacodec_output_ops
= {
175 .startup
= meson_codec_glue_output_startup
,
178 #define TOACODEC_STREAM(xname, xsuffix, xchmax) \
180 .stream_name = xname " " xsuffix, \
182 .channels_max = (xchmax), \
184 .rate_max = 192000, \
185 .formats = AXG_TDM_FORMATS, \
188 #define TOACODEC_INPUT(xname, xid) { \
191 .playback = TOACODEC_STREAM(xname, "Playback", 8), \
192 .ops = &g12a_toacodec_input_ops, \
195 #define TOACODEC_OUTPUT(xname, xid) { \
198 .capture = TOACODEC_STREAM(xname, "Capture", TOACODEC_OUT_CHMAX), \
199 .ops = &g12a_toacodec_output_ops, \
202 static struct snd_soc_dai_driver g12a_toacodec_dai_drv
[] = {
203 TOACODEC_INPUT("IN A", TOACODEC_IN_A
),
204 TOACODEC_INPUT("IN B", TOACODEC_IN_B
),
205 TOACODEC_INPUT("IN C", TOACODEC_IN_C
),
206 TOACODEC_OUTPUT("OUT", TOACODEC_OUT
),
209 static int g12a_toacodec_component_probe(struct snd_soc_component
*c
)
211 /* Initialize the static clock parameters */
212 return snd_soc_component_write(c
, TOACODEC_CTRL0
,
216 static int sm1_toacodec_component_probe(struct snd_soc_component
*c
)
218 /* Initialize the static clock parameters */
219 return snd_soc_component_write(c
, TOACODEC_CTRL0
,
220 CTRL0_BLK_CAP_INV_SM1
);
223 static const struct snd_soc_dapm_route g12a_toacodec_routes
[] = {
224 { "SRC", "I2S A", "IN A Playback" },
225 { "SRC", "I2S B", "IN B Playback" },
226 { "SRC", "I2S C", "IN C Playback" },
227 { "OUT EN", "Switch", "SRC" },
228 { "OUT Capture", NULL
, "OUT EN" },
231 static const struct snd_kcontrol_new g12a_toacodec_controls
[] = {
232 SOC_SINGLE("Lane Select", TOACODEC_CTRL0
, CTRL0_LANE_SEL
, 3, 0),
235 static const struct snd_kcontrol_new sm1_toacodec_controls
[] = {
236 SOC_SINGLE("Lane Select", TOACODEC_CTRL0
, CTRL0_LANE_SEL_SM1
, 3, 0),
239 static const struct snd_soc_component_driver g12a_toacodec_component_drv
= {
240 .probe
= g12a_toacodec_component_probe
,
241 .controls
= g12a_toacodec_controls
,
242 .num_controls
= ARRAY_SIZE(g12a_toacodec_controls
),
243 .dapm_widgets
= g12a_toacodec_widgets
,
244 .num_dapm_widgets
= ARRAY_SIZE(g12a_toacodec_widgets
),
245 .dapm_routes
= g12a_toacodec_routes
,
246 .num_dapm_routes
= ARRAY_SIZE(g12a_toacodec_routes
),
250 static const struct snd_soc_component_driver sm1_toacodec_component_drv
= {
251 .probe
= sm1_toacodec_component_probe
,
252 .controls
= sm1_toacodec_controls
,
253 .num_controls
= ARRAY_SIZE(sm1_toacodec_controls
),
254 .dapm_widgets
= sm1_toacodec_widgets
,
255 .num_dapm_widgets
= ARRAY_SIZE(sm1_toacodec_widgets
),
256 .dapm_routes
= g12a_toacodec_routes
,
257 .num_dapm_routes
= ARRAY_SIZE(g12a_toacodec_routes
),
261 static const struct regmap_config g12a_toacodec_regmap_cfg
= {
267 static const struct g12a_toacodec_match_data g12a_toacodec_match_data
= {
268 .component_drv
= &g12a_toacodec_component_drv
,
269 .field_dat_sel
= REG_FIELD(TOACODEC_CTRL0
, 14, 15),
270 .field_lrclk_sel
= REG_FIELD(TOACODEC_CTRL0
, 8, 9),
271 .field_bclk_sel
= REG_FIELD(TOACODEC_CTRL0
, 4, 5),
274 static const struct g12a_toacodec_match_data sm1_toacodec_match_data
= {
275 .component_drv
= &sm1_toacodec_component_drv
,
276 .field_dat_sel
= REG_FIELD(TOACODEC_CTRL0
, 18, 19),
277 .field_lrclk_sel
= REG_FIELD(TOACODEC_CTRL0
, 12, 14),
278 .field_bclk_sel
= REG_FIELD(TOACODEC_CTRL0
, 4, 6),
281 static const struct of_device_id g12a_toacodec_of_match
[] = {
283 .compatible
= "amlogic,g12a-toacodec",
284 .data
= &g12a_toacodec_match_data
,
287 .compatible
= "amlogic,sm1-toacodec",
288 .data
= &sm1_toacodec_match_data
,
292 MODULE_DEVICE_TABLE(of
, g12a_toacodec_of_match
);
294 static int g12a_toacodec_probe(struct platform_device
*pdev
)
296 const struct g12a_toacodec_match_data
*data
;
297 struct device
*dev
= &pdev
->dev
;
298 struct g12a_toacodec
*priv
;
303 data
= device_get_match_data(dev
);
305 dev_err(dev
, "failed to match device\n");
309 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
313 platform_set_drvdata(pdev
, priv
);
315 ret
= device_reset(dev
);
319 regs
= devm_platform_ioremap_resource(pdev
, 0);
321 return PTR_ERR(regs
);
323 map
= devm_regmap_init_mmio(dev
, regs
, &g12a_toacodec_regmap_cfg
);
325 dev_err(dev
, "failed to init regmap: %ld\n",
330 priv
->field_dat_sel
= devm_regmap_field_alloc(dev
, map
, data
->field_dat_sel
);
331 if (IS_ERR(priv
->field_dat_sel
))
332 return PTR_ERR(priv
->field_dat_sel
);
334 priv
->field_lrclk_sel
= devm_regmap_field_alloc(dev
, map
, data
->field_lrclk_sel
);
335 if (IS_ERR(priv
->field_lrclk_sel
))
336 return PTR_ERR(priv
->field_lrclk_sel
);
338 priv
->field_bclk_sel
= devm_regmap_field_alloc(dev
, map
, data
->field_bclk_sel
);
339 if (IS_ERR(priv
->field_bclk_sel
))
340 return PTR_ERR(priv
->field_bclk_sel
);
342 return devm_snd_soc_register_component(dev
,
343 data
->component_drv
, g12a_toacodec_dai_drv
,
344 ARRAY_SIZE(g12a_toacodec_dai_drv
));
347 static struct platform_driver g12a_toacodec_pdrv
= {
349 .name
= G12A_TOACODEC_DRV_NAME
,
350 .of_match_table
= g12a_toacodec_of_match
,
352 .probe
= g12a_toacodec_probe
,
354 module_platform_driver(g12a_toacodec_pdrv
);
356 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
357 MODULE_DESCRIPTION("Amlogic G12a To Internal DAC Codec Driver");
358 MODULE_LICENSE("GPL v2");