1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Copyright (c) 2020 BayLibre, SAS. |
4 | // Author: Jerome Brunet <jbrunet@baylibre.com> |
5 | |
6 | #include <linux/clk.h> |
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> |
14 | |
15 | #define BLOCK_EN 0x00 |
16 | #define LORN_EN 0 |
17 | #define LORP_EN 1 |
18 | #define LOLN_EN 2 |
19 | #define LOLP_EN 3 |
20 | #define DACR_EN 4 |
21 | #define DACL_EN 5 |
22 | #define DACR_INV 20 |
23 | #define DACL_INV 21 |
24 | #define DACR_SRC 22 |
25 | #define DACL_SRC 23 |
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 |
32 | #define GAIN_H 31 |
33 | #define GAIN_L 23 |
34 | #define VOL_CTRL1 0x08 |
35 | #define DAC_MONO 8 |
36 | #define RAMP_RATE 10 |
37 | #define VC_RAMP_MODE 12 |
38 | #define MUTE_MODE 13 |
39 | #define UNMUTE_MODE 14 |
40 | #define DAC_SOFT_MUTE 15 |
41 | #define DACR_VC 16 |
42 | #define DACL_VC 24 |
43 | #define LINEOUT_CFG 0x0c |
44 | #define LORN_POL 0 |
45 | #define LORP_POL 4 |
46 | #define LOLN_POL 8 |
47 | #define LOLP_POL 12 |
48 | #define POWER_CFG 0x10 |
49 | |
50 | struct t9015 { |
51 | struct regulator *avdd; |
52 | }; |
53 | |
54 | static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
55 | { |
56 | struct snd_soc_component *component = dai->component; |
57 | unsigned int val; |
58 | |
59 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
60 | case SND_SOC_DAIFMT_CBM_CFM: |
61 | val = I2S_MODE; |
62 | break; |
63 | |
64 | case SND_SOC_DAIFMT_CBS_CFS: |
65 | val = 0; |
66 | break; |
67 | |
68 | default: |
69 | return -EINVAL; |
70 | } |
71 | |
72 | snd_soc_component_update_bits(component, BLOCK_EN, I2S_MODE, val); |
73 | |
74 | if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) && |
75 | ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_LEFT_J)) |
76 | return -EINVAL; |
77 | |
78 | return 0; |
79 | } |
80 | |
81 | static const struct snd_soc_dai_ops t9015_dai_ops = { |
82 | .set_fmt = t9015_dai_set_fmt, |
83 | }; |
84 | |
85 | static struct snd_soc_dai_driver t9015_dai = { |
86 | .name = "t9015-hifi" , |
87 | .playback = { |
88 | .stream_name = "Playback" , |
89 | .channels_min = 1, |
90 | .channels_max = 2, |
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), |
96 | }, |
97 | .ops = &t9015_dai_ops, |
98 | }; |
99 | |
100 | static const DECLARE_TLV_DB_MINMAX_MUTE(dac_vol_tlv, -9525, 0); |
101 | |
102 | static const char * const ramp_rate_txt[] = { "Fast" , "Slow" }; |
103 | static SOC_ENUM_SINGLE_DECL(ramp_rate_enum, VOL_CTRL1, RAMP_RATE, |
104 | ramp_rate_txt); |
105 | |
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); |
108 | |
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); |
111 | |
112 | static const char * const mono_txt[] = { "Stereo" , "Mono" }; |
113 | static SOC_ENUM_SINGLE_DECL(mono_enum, VOL_CTRL1, DAC_MONO, mono_txt); |
114 | |
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), |
121 | |
122 | /* Ramp Controls */ |
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), |
127 | }; |
128 | |
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); |
133 | |
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, |
144 | NULL, 0), |
145 | SND_SOC_DAPM_OUT_DRV("Right+ Driver" , BLOCK_EN, LORP_EN, 0, |
146 | NULL, 0), |
147 | SND_SOC_DAPM_OUT_DRV("Left- Driver" , BLOCK_EN, LOLN_EN, 0, |
148 | NULL, 0), |
149 | SND_SOC_DAPM_OUT_DRV("Left+ Driver" , BLOCK_EN, LOLP_EN, 0, |
150 | NULL, 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" ), |
155 | }; |
156 | |
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" , }, |
174 | }; |
175 | |
176 | static int t9015_set_bias_level(struct snd_soc_component *component, |
177 | enum snd_soc_bias_level level) |
178 | { |
179 | struct t9015 *priv = snd_soc_component_get_drvdata(c: component); |
180 | enum snd_soc_bias_level now = |
181 | snd_soc_component_get_bias_level(component); |
182 | int ret; |
183 | |
184 | switch (level) { |
185 | case SND_SOC_BIAS_ON: |
186 | snd_soc_component_update_bits(component, BLOCK_EN, |
187 | BIAS_CURRENT_EN, |
188 | BIAS_CURRENT_EN); |
189 | break; |
190 | case SND_SOC_BIAS_PREPARE: |
191 | snd_soc_component_update_bits(component, BLOCK_EN, |
192 | BIAS_CURRENT_EN, |
193 | val: 0); |
194 | break; |
195 | case SND_SOC_BIAS_STANDBY: |
196 | ret = regulator_enable(regulator: priv->avdd); |
197 | if (ret) { |
198 | dev_err(component->dev, "AVDD enable failed\n" ); |
199 | return ret; |
200 | } |
201 | |
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); |
206 | |
207 | mdelay(200); |
208 | snd_soc_component_update_bits(component, BLOCK_EN, |
209 | VMID_GEN_FAST, |
210 | val: 0); |
211 | } |
212 | |
213 | break; |
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, |
217 | val: 0); |
218 | |
219 | regulator_disable(regulator: priv->avdd); |
220 | break; |
221 | } |
222 | |
223 | return 0; |
224 | } |
225 | |
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, |
235 | .endianness = 1, |
236 | }; |
237 | |
238 | static const struct regmap_config t9015_regmap_config = { |
239 | .reg_bits = 32, |
240 | .reg_stride = 4, |
241 | .val_bits = 32, |
242 | .max_register = POWER_CFG, |
243 | }; |
244 | |
245 | static int t9015_probe(struct platform_device *pdev) |
246 | { |
247 | struct device *dev = &pdev->dev; |
248 | struct t9015 *priv; |
249 | void __iomem *regs; |
250 | struct regmap *regmap; |
251 | struct clk *pclk; |
252 | int ret; |
253 | |
254 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
255 | if (!priv) |
256 | return -ENOMEM; |
257 | platform_set_drvdata(pdev, data: priv); |
258 | |
259 | pclk = devm_clk_get_enabled(dev, id: "pclk" ); |
260 | if (IS_ERR(ptr: pclk)) |
261 | return dev_err_probe(dev, err: PTR_ERR(ptr: pclk), fmt: "failed to get core clock\n" ); |
262 | |
263 | priv->avdd = devm_regulator_get(dev, id: "AVDD" ); |
264 | if (IS_ERR(ptr: priv->avdd)) |
265 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->avdd), fmt: "failed to AVDD\n" ); |
266 | |
267 | ret = device_reset(dev); |
268 | if (ret) { |
269 | dev_err(dev, "reset failed\n" ); |
270 | return ret; |
271 | } |
272 | |
273 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
274 | if (IS_ERR(ptr: regs)) { |
275 | dev_err(dev, "register map failed\n" ); |
276 | return PTR_ERR(ptr: regs); |
277 | } |
278 | |
279 | regmap = devm_regmap_init_mmio(dev, regs, &t9015_regmap_config); |
280 | if (IS_ERR(ptr: regmap)) { |
281 | dev_err(dev, "regmap init failed\n" ); |
282 | return PTR_ERR(ptr: regmap); |
283 | } |
284 | |
285 | /* |
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 |
289 | */ |
290 | regmap_write(map: regmap, LINEOUT_CFG, val: 0x1111); |
291 | |
292 | return devm_snd_soc_register_component(dev, component_driver: &t9015_codec_driver, |
293 | dai_drv: &t9015_dai, num_dai: 1); |
294 | } |
295 | |
296 | static const struct of_device_id t9015_ids[] __maybe_unused = { |
297 | { .compatible = "amlogic,t9015" , }, |
298 | { } |
299 | }; |
300 | MODULE_DEVICE_TABLE(of, t9015_ids); |
301 | |
302 | static struct platform_driver t9015_driver = { |
303 | .driver = { |
304 | .name = "t9015-codec" , |
305 | .of_match_table = of_match_ptr(t9015_ids), |
306 | }, |
307 | .probe = t9015_probe, |
308 | }; |
309 | |
310 | module_platform_driver(t9015_driver); |
311 | |
312 | MODULE_DESCRIPTION("ASoC Amlogic T9015 codec driver" ); |
313 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>" ); |
314 | MODULE_LICENSE("GPL" ); |
315 | |