1 // SPDX-License-Identifier: GPL-2.0
3 // Copyright (c) 2020 BayLibre, SAS.
4 // Author: Jerome Brunet <jbrunet@baylibre.com>
7 #include <linux/delay.h>
8 #include <linux/module.h>
9 #include <linux/regmap.h>
10 #include <linux/regulator/consumer.h>
11 #include <linux/reset.h>
12 #include <sound/soc.h>
13 #include <sound/tlv.h>
26 #define REFP_BUF_EN BIT(12)
27 #define BIAS_CURRENT_EN BIT(13)
28 #define VMID_GEN_FAST BIT(14)
29 #define VMID_GEN_EN BIT(15)
30 #define I2S_MODE BIT(30)
31 #define VOL_CTRL0 0x04
34 #define VOL_CTRL1 0x08
37 #define VC_RAMP_MODE 12
39 #define UNMUTE_MODE 14
40 #define DAC_SOFT_MUTE 15
43 #define LINEOUT_CFG 0x0c
48 #define POWER_CFG 0x10
51 struct regulator
*avdd
;
54 static int t9015_dai_set_fmt(struct snd_soc_dai
*dai
, unsigned int fmt
)
56 struct snd_soc_component
*component
= dai
->component
;
59 switch (fmt
& SND_SOC_DAIFMT_MASTER_MASK
) {
60 case SND_SOC_DAIFMT_CBM_CFM
:
64 case SND_SOC_DAIFMT_CBS_CFS
:
72 snd_soc_component_update_bits(component
, BLOCK_EN
, I2S_MODE
, val
);
74 if (((fmt
& SND_SOC_DAIFMT_FORMAT_MASK
) != SND_SOC_DAIFMT_I2S
) &&
75 ((fmt
& SND_SOC_DAIFMT_FORMAT_MASK
) != SND_SOC_DAIFMT_LEFT_J
))
81 static const struct snd_soc_dai_ops t9015_dai_ops
= {
82 .set_fmt
= t9015_dai_set_fmt
,
85 static struct snd_soc_dai_driver t9015_dai
= {
88 .stream_name
= "Playback",
91 .rates
= SNDRV_PCM_RATE_8000_96000
,
92 .formats
= (SNDRV_PCM_FMTBIT_S8
|
93 SNDRV_PCM_FMTBIT_S16_LE
|
94 SNDRV_PCM_FMTBIT_S20_LE
|
95 SNDRV_PCM_FMTBIT_S24_LE
),
97 .ops
= &t9015_dai_ops
,
100 static const DECLARE_TLV_DB_MINMAX_MUTE(dac_vol_tlv
, -9525, 0);
102 static const char * const ramp_rate_txt
[] = { "Fast", "Slow" };
103 static SOC_ENUM_SINGLE_DECL(ramp_rate_enum
, VOL_CTRL1
, RAMP_RATE
,
106 static const char * const dacr_in_txt
[] = { "Right", "Left" };
107 static SOC_ENUM_SINGLE_DECL(dacr_in_enum
, BLOCK_EN
, DACR_SRC
, dacr_in_txt
);
109 static const char * const dacl_in_txt
[] = { "Left", "Right" };
110 static SOC_ENUM_SINGLE_DECL(dacl_in_enum
, BLOCK_EN
, DACL_SRC
, dacl_in_txt
);
112 static const char * const mono_txt
[] = { "Stereo", "Mono"};
113 static SOC_ENUM_SINGLE_DECL(mono_enum
, VOL_CTRL1
, DAC_MONO
, mono_txt
);
115 static const struct snd_kcontrol_new t9015_snd_controls
[] = {
116 /* Volume Controls */
117 SOC_ENUM("Playback Channel Mode", mono_enum
),
118 SOC_SINGLE("Playback Switch", VOL_CTRL1
, DAC_SOFT_MUTE
, 1, 1),
119 SOC_DOUBLE_TLV("Playback Volume", VOL_CTRL1
, DACL_VC
, DACR_VC
,
120 0xff, 0, dac_vol_tlv
),
123 SOC_ENUM("Ramp Rate", ramp_rate_enum
),
124 SOC_SINGLE("Volume Ramp Switch", VOL_CTRL1
, VC_RAMP_MODE
, 1, 0),
125 SOC_SINGLE("Mute Ramp Switch", VOL_CTRL1
, MUTE_MODE
, 1, 0),
126 SOC_SINGLE("Unmute Ramp Switch", VOL_CTRL1
, UNMUTE_MODE
, 1, 0),
129 static const struct snd_kcontrol_new t9015_right_dac_mux
=
130 SOC_DAPM_ENUM("Right DAC Source", dacr_in_enum
);
131 static const struct snd_kcontrol_new t9015_left_dac_mux
=
132 SOC_DAPM_ENUM("Left DAC Source", dacl_in_enum
);
134 static const struct snd_soc_dapm_widget t9015_dapm_widgets
[] = {
135 SND_SOC_DAPM_AIF_IN("Right IN", NULL
, 0, SND_SOC_NOPM
, 0, 0),
136 SND_SOC_DAPM_AIF_IN("Left IN", NULL
, 0, SND_SOC_NOPM
, 0, 0),
137 SND_SOC_DAPM_MUX("Right DAC Sel", SND_SOC_NOPM
, 0, 0,
138 &t9015_right_dac_mux
),
139 SND_SOC_DAPM_MUX("Left DAC Sel", SND_SOC_NOPM
, 0, 0,
140 &t9015_left_dac_mux
),
141 SND_SOC_DAPM_DAC("Right DAC", NULL
, BLOCK_EN
, DACR_EN
, 0),
142 SND_SOC_DAPM_DAC("Left DAC", NULL
, BLOCK_EN
, DACL_EN
, 0),
143 SND_SOC_DAPM_OUT_DRV("Right- Driver", BLOCK_EN
, LORN_EN
, 0,
145 SND_SOC_DAPM_OUT_DRV("Right+ Driver", BLOCK_EN
, LORP_EN
, 0,
147 SND_SOC_DAPM_OUT_DRV("Left- Driver", BLOCK_EN
, LOLN_EN
, 0,
149 SND_SOC_DAPM_OUT_DRV("Left+ Driver", BLOCK_EN
, LOLP_EN
, 0,
151 SND_SOC_DAPM_OUTPUT("LORN"),
152 SND_SOC_DAPM_OUTPUT("LORP"),
153 SND_SOC_DAPM_OUTPUT("LOLN"),
154 SND_SOC_DAPM_OUTPUT("LOLP"),
157 static const struct snd_soc_dapm_route t9015_dapm_routes
[] = {
158 { "Right IN", NULL
, "Playback" },
159 { "Left IN", NULL
, "Playback" },
160 { "Right DAC Sel", "Right", "Right IN" },
161 { "Right DAC Sel", "Left", "Left IN" },
162 { "Left DAC Sel", "Right", "Right IN" },
163 { "Left DAC Sel", "Left", "Left IN" },
164 { "Right DAC", NULL
, "Right DAC Sel" },
165 { "Left DAC", NULL
, "Left DAC Sel" },
166 { "Right- Driver", NULL
, "Right DAC" },
167 { "Right+ Driver", NULL
, "Right DAC" },
168 { "Left- Driver", NULL
, "Left DAC" },
169 { "Left+ Driver", NULL
, "Left DAC" },
170 { "LORN", NULL
, "Right- Driver", },
171 { "LORP", NULL
, "Right+ Driver", },
172 { "LOLN", NULL
, "Left- Driver", },
173 { "LOLP", NULL
, "Left+ Driver", },
176 static int t9015_set_bias_level(struct snd_soc_component
*component
,
177 enum snd_soc_bias_level level
)
179 struct t9015
*priv
= snd_soc_component_get_drvdata(component
);
180 enum snd_soc_bias_level now
=
181 snd_soc_component_get_bias_level(component
);
185 case SND_SOC_BIAS_ON
:
186 snd_soc_component_update_bits(component
, BLOCK_EN
,
190 case SND_SOC_BIAS_PREPARE
:
191 snd_soc_component_update_bits(component
, BLOCK_EN
,
195 case SND_SOC_BIAS_STANDBY
:
196 ret
= regulator_enable(priv
->avdd
);
198 dev_err(component
->dev
, "AVDD enable failed\n");
202 if (now
== SND_SOC_BIAS_OFF
) {
203 snd_soc_component_update_bits(component
, BLOCK_EN
,
204 VMID_GEN_EN
| VMID_GEN_FAST
| REFP_BUF_EN
,
205 VMID_GEN_EN
| VMID_GEN_FAST
| REFP_BUF_EN
);
208 snd_soc_component_update_bits(component
, BLOCK_EN
,
214 case SND_SOC_BIAS_OFF
:
215 snd_soc_component_update_bits(component
, BLOCK_EN
,
216 VMID_GEN_EN
| VMID_GEN_FAST
| REFP_BUF_EN
,
219 regulator_disable(priv
->avdd
);
226 static const struct snd_soc_component_driver t9015_codec_driver
= {
227 .set_bias_level
= t9015_set_bias_level
,
228 .controls
= t9015_snd_controls
,
229 .num_controls
= ARRAY_SIZE(t9015_snd_controls
),
230 .dapm_widgets
= t9015_dapm_widgets
,
231 .num_dapm_widgets
= ARRAY_SIZE(t9015_dapm_widgets
),
232 .dapm_routes
= t9015_dapm_routes
,
233 .num_dapm_routes
= ARRAY_SIZE(t9015_dapm_routes
),
234 .suspend_bias_off
= 1,
238 static const struct regmap_config t9015_regmap_config
= {
242 .max_register
= POWER_CFG
,
245 static int t9015_probe(struct platform_device
*pdev
)
247 struct device
*dev
= &pdev
->dev
;
250 struct regmap
*regmap
;
254 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
257 platform_set_drvdata(pdev
, priv
);
259 pclk
= devm_clk_get_enabled(dev
, "pclk");
261 return dev_err_probe(dev
, PTR_ERR(pclk
), "failed to get core clock\n");
263 priv
->avdd
= devm_regulator_get(dev
, "AVDD");
264 if (IS_ERR(priv
->avdd
))
265 return dev_err_probe(dev
, PTR_ERR(priv
->avdd
), "failed to AVDD\n");
267 ret
= device_reset(dev
);
269 dev_err(dev
, "reset failed\n");
273 regs
= devm_platform_ioremap_resource(pdev
, 0);
275 dev_err(dev
, "register map failed\n");
276 return PTR_ERR(regs
);
279 regmap
= devm_regmap_init_mmio(dev
, regs
, &t9015_regmap_config
);
280 if (IS_ERR(regmap
)) {
281 dev_err(dev
, "regmap init failed\n");
282 return PTR_ERR(regmap
);
286 * Initialize output polarity:
287 * ATM the output polarity is fixed but in the future it might useful
288 * to add DT property to set this depending on the platform needs
290 regmap_write(regmap
, LINEOUT_CFG
, 0x1111);
292 return devm_snd_soc_register_component(dev
, &t9015_codec_driver
,
296 static const struct of_device_id t9015_ids
[] __maybe_unused
= {
297 { .compatible
= "amlogic,t9015", },
300 MODULE_DEVICE_TABLE(of
, t9015_ids
);
302 static struct platform_driver t9015_driver
= {
304 .name
= "t9015-codec",
305 .of_match_table
= of_match_ptr(t9015_ids
),
307 .probe
= t9015_probe
,
310 module_platform_driver(t9015_driver
);
312 MODULE_DESCRIPTION("ASoC Amlogic T9015 codec driver");
313 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
314 MODULE_LICENSE("GPL");