1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) STMicroelectronics SA 2015 |
4 | * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com> |
5 | * for STMicroelectronics. |
6 | */ |
7 | |
8 | #include <linux/io.h> |
9 | #include <linux/module.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/reset.h> |
12 | #include <linux/mfd/syscon.h> |
13 | |
14 | #include <sound/soc.h> |
15 | #include <sound/soc-dapm.h> |
16 | |
17 | /* DAC definitions */ |
18 | |
19 | /* stih407 DAC registers */ |
20 | /* sysconf 5041: Audio-Gue-Control */ |
21 | #define STIH407_AUDIO_GLUE_CTRL 0x000000A4 |
22 | /* sysconf 5042: Audio-DAC-Control */ |
23 | #define STIH407_AUDIO_DAC_CTRL 0x000000A8 |
24 | |
25 | /* DAC definitions */ |
26 | #define STIH407_DAC_SOFTMUTE 0x0 |
27 | #define STIH407_DAC_STANDBY_ANA 0x1 |
28 | #define STIH407_DAC_STANDBY 0x2 |
29 | |
30 | #define STIH407_DAC_SOFTMUTE_MASK BIT(STIH407_DAC_SOFTMUTE) |
31 | #define STIH407_DAC_STANDBY_ANA_MASK BIT(STIH407_DAC_STANDBY_ANA) |
32 | #define STIH407_DAC_STANDBY_MASK BIT(STIH407_DAC_STANDBY) |
33 | |
34 | /* SPDIF definitions */ |
35 | #define SPDIF_BIPHASE_ENABLE 0x6 |
36 | #define SPDIF_BIPHASE_IDLE 0x7 |
37 | |
38 | #define SPDIF_BIPHASE_ENABLE_MASK BIT(SPDIF_BIPHASE_ENABLE) |
39 | #define SPDIF_BIPHASE_IDLE_MASK BIT(SPDIF_BIPHASE_IDLE) |
40 | |
41 | enum { |
42 | STI_SAS_DAI_SPDIF_OUT, |
43 | STI_SAS_DAI_ANALOG_OUT, |
44 | }; |
45 | |
46 | static const struct reg_default stih407_sas_reg_defaults[] = { |
47 | { STIH407_AUDIO_DAC_CTRL, 0x000000000 }, |
48 | { STIH407_AUDIO_GLUE_CTRL, 0x00000040 }, |
49 | }; |
50 | |
51 | struct sti_dac_audio { |
52 | struct regmap *regmap; |
53 | struct regmap *virt_regmap; |
54 | int mclk; |
55 | }; |
56 | |
57 | struct sti_spdif_audio { |
58 | struct regmap *regmap; |
59 | int mclk; |
60 | }; |
61 | |
62 | /* device data structure */ |
63 | struct sti_sas_dev_data { |
64 | const struct regmap_config *regmap; |
65 | const struct snd_soc_dai_ops *dac_ops; /* DAC function callbacks */ |
66 | const struct snd_soc_dapm_widget *dapm_widgets; /* dapms declaration */ |
67 | const int num_dapm_widgets; /* dapms declaration */ |
68 | const struct snd_soc_dapm_route *dapm_routes; /* route declaration */ |
69 | const int num_dapm_routes; /* route declaration */ |
70 | }; |
71 | |
72 | /* driver data structure */ |
73 | struct sti_sas_data { |
74 | struct device *dev; |
75 | const struct sti_sas_dev_data *dev_data; |
76 | struct sti_dac_audio dac; |
77 | struct sti_spdif_audio spdif; |
78 | }; |
79 | |
80 | /* Read a register from the sysconf reg bank */ |
81 | static int sti_sas_read_reg(void *context, unsigned int reg, |
82 | unsigned int *value) |
83 | { |
84 | struct sti_sas_data *drvdata = context; |
85 | int status; |
86 | u32 val; |
87 | |
88 | status = regmap_read(map: drvdata->dac.regmap, reg, val: &val); |
89 | *value = (unsigned int)val; |
90 | |
91 | return status; |
92 | } |
93 | |
94 | /* Read a register from the sysconf reg bank */ |
95 | static int sti_sas_write_reg(void *context, unsigned int reg, |
96 | unsigned int value) |
97 | { |
98 | struct sti_sas_data *drvdata = context; |
99 | |
100 | return regmap_write(map: drvdata->dac.regmap, reg, val: value); |
101 | } |
102 | |
103 | static int sti_sas_init_sas_registers(struct snd_soc_component *component, |
104 | struct sti_sas_data *data) |
105 | { |
106 | int ret; |
107 | /* |
108 | * DAC and SPDIF are activated by default |
109 | * put them in IDLE to save power |
110 | */ |
111 | |
112 | /* Initialise bi-phase formatter to disabled */ |
113 | ret = snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, |
114 | SPDIF_BIPHASE_ENABLE_MASK, val: 0); |
115 | |
116 | if (!ret) |
117 | /* Initialise bi-phase formatter idle value to 0 */ |
118 | ret = snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, |
119 | SPDIF_BIPHASE_IDLE_MASK, val: 0); |
120 | if (ret < 0) { |
121 | dev_err(component->dev, "Failed to update SPDIF registers\n" ); |
122 | return ret; |
123 | } |
124 | |
125 | /* Init DAC configuration */ |
126 | /* init configuration */ |
127 | ret = snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, |
128 | STIH407_DAC_STANDBY_MASK, |
129 | STIH407_DAC_STANDBY_MASK); |
130 | |
131 | if (!ret) |
132 | ret = snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, |
133 | STIH407_DAC_STANDBY_ANA_MASK, |
134 | STIH407_DAC_STANDBY_ANA_MASK); |
135 | if (!ret) |
136 | ret = snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, |
137 | STIH407_DAC_SOFTMUTE_MASK, |
138 | STIH407_DAC_SOFTMUTE_MASK); |
139 | |
140 | if (ret < 0) { |
141 | dev_err(component->dev, "Failed to update DAC registers\n" ); |
142 | return ret; |
143 | } |
144 | |
145 | return ret; |
146 | } |
147 | |
148 | /* |
149 | * DAC |
150 | */ |
151 | static int sti_sas_dac_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
152 | { |
153 | /* Sanity check only */ |
154 | if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) { |
155 | dev_err(dai->component->dev, |
156 | "%s: ERROR: Unsupported clocking 0x%x\n" , |
157 | __func__, fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK); |
158 | return -EINVAL; |
159 | } |
160 | |
161 | return 0; |
162 | } |
163 | |
164 | static const struct snd_soc_dapm_widget stih407_sas_dapm_widgets[] = { |
165 | SND_SOC_DAPM_OUT_DRV("DAC standby ana" , STIH407_AUDIO_DAC_CTRL, |
166 | STIH407_DAC_STANDBY_ANA, 1, NULL, 0), |
167 | SND_SOC_DAPM_DAC("DAC standby" , "dac_p" , STIH407_AUDIO_DAC_CTRL, |
168 | STIH407_DAC_STANDBY, 1), |
169 | SND_SOC_DAPM_OUTPUT("DAC Output" ), |
170 | }; |
171 | |
172 | static const struct snd_soc_dapm_route stih407_sas_route[] = { |
173 | {"DAC Output" , NULL, "DAC standby ana" }, |
174 | {"DAC standby ana" , NULL, "DAC standby" }, |
175 | }; |
176 | |
177 | |
178 | static int stih407_sas_dac_mute(struct snd_soc_dai *dai, int mute, int stream) |
179 | { |
180 | struct snd_soc_component *component = dai->component; |
181 | |
182 | if (mute) { |
183 | return snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, |
184 | STIH407_DAC_SOFTMUTE_MASK, |
185 | STIH407_DAC_SOFTMUTE_MASK); |
186 | } else { |
187 | return snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, |
188 | STIH407_DAC_SOFTMUTE_MASK, |
189 | val: 0); |
190 | } |
191 | } |
192 | |
193 | /* |
194 | * SPDIF |
195 | */ |
196 | static int sti_sas_spdif_set_fmt(struct snd_soc_dai *dai, |
197 | unsigned int fmt) |
198 | { |
199 | if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) { |
200 | dev_err(dai->component->dev, |
201 | "%s: ERROR: Unsupported clocking mask 0x%x\n" , |
202 | __func__, fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK); |
203 | return -EINVAL; |
204 | } |
205 | |
206 | return 0; |
207 | } |
208 | |
209 | /* |
210 | * sti_sas_spdif_trigger: |
211 | * Trigger function is used to ensure that BiPhase Formater is disabled |
212 | * before CPU dai is stopped. |
213 | * This is mandatory to avoid that BPF is stalled |
214 | */ |
215 | static int sti_sas_spdif_trigger(struct snd_pcm_substream *substream, int cmd, |
216 | struct snd_soc_dai *dai) |
217 | { |
218 | struct snd_soc_component *component = dai->component; |
219 | |
220 | switch (cmd) { |
221 | case SNDRV_PCM_TRIGGER_START: |
222 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
223 | return snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, |
224 | SPDIF_BIPHASE_ENABLE_MASK, |
225 | SPDIF_BIPHASE_ENABLE_MASK); |
226 | case SNDRV_PCM_TRIGGER_RESUME: |
227 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
228 | case SNDRV_PCM_TRIGGER_STOP: |
229 | case SNDRV_PCM_TRIGGER_SUSPEND: |
230 | return snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, |
231 | SPDIF_BIPHASE_ENABLE_MASK, |
232 | val: 0); |
233 | default: |
234 | return -EINVAL; |
235 | } |
236 | } |
237 | |
238 | static bool sti_sas_volatile_register(struct device *dev, unsigned int reg) |
239 | { |
240 | if (reg == STIH407_AUDIO_GLUE_CTRL) |
241 | return true; |
242 | |
243 | return false; |
244 | } |
245 | |
246 | /* |
247 | * CODEC DAIS |
248 | */ |
249 | |
250 | /* |
251 | * sti_sas_set_sysclk: |
252 | * get MCLK input frequency to check that MCLK-FS ratio is coherent |
253 | */ |
254 | static int sti_sas_set_sysclk(struct snd_soc_dai *dai, int clk_id, |
255 | unsigned int freq, int dir) |
256 | { |
257 | struct snd_soc_component *component = dai->component; |
258 | struct sti_sas_data *drvdata = dev_get_drvdata(dev: component->dev); |
259 | |
260 | if (dir == SND_SOC_CLOCK_OUT) |
261 | return 0; |
262 | |
263 | if (clk_id != 0) |
264 | return -EINVAL; |
265 | |
266 | switch (dai->id) { |
267 | case STI_SAS_DAI_SPDIF_OUT: |
268 | drvdata->spdif.mclk = freq; |
269 | break; |
270 | |
271 | case STI_SAS_DAI_ANALOG_OUT: |
272 | drvdata->dac.mclk = freq; |
273 | break; |
274 | } |
275 | |
276 | return 0; |
277 | } |
278 | |
279 | static int sti_sas_prepare(struct snd_pcm_substream *substream, |
280 | struct snd_soc_dai *dai) |
281 | { |
282 | struct snd_soc_component *component = dai->component; |
283 | struct sti_sas_data *drvdata = dev_get_drvdata(dev: component->dev); |
284 | struct snd_pcm_runtime *runtime = substream->runtime; |
285 | |
286 | switch (dai->id) { |
287 | case STI_SAS_DAI_SPDIF_OUT: |
288 | if ((drvdata->spdif.mclk / runtime->rate) != 128) { |
289 | dev_err(component->dev, "unexpected mclk-fs ratio\n" ); |
290 | return -EINVAL; |
291 | } |
292 | break; |
293 | case STI_SAS_DAI_ANALOG_OUT: |
294 | if ((drvdata->dac.mclk / runtime->rate) != 256) { |
295 | dev_err(component->dev, "unexpected mclk-fs ratio\n" ); |
296 | return -EINVAL; |
297 | } |
298 | break; |
299 | } |
300 | |
301 | return 0; |
302 | } |
303 | |
304 | static const struct snd_soc_dai_ops stih407_dac_ops = { |
305 | .set_fmt = sti_sas_dac_set_fmt, |
306 | .mute_stream = stih407_sas_dac_mute, |
307 | .prepare = sti_sas_prepare, |
308 | .set_sysclk = sti_sas_set_sysclk, |
309 | }; |
310 | |
311 | static const struct regmap_config stih407_sas_regmap = { |
312 | .reg_bits = 32, |
313 | .val_bits = 32, |
314 | .fast_io = true, |
315 | .max_register = STIH407_AUDIO_DAC_CTRL, |
316 | .reg_defaults = stih407_sas_reg_defaults, |
317 | .num_reg_defaults = ARRAY_SIZE(stih407_sas_reg_defaults), |
318 | .volatile_reg = sti_sas_volatile_register, |
319 | .cache_type = REGCACHE_MAPLE, |
320 | .reg_read = sti_sas_read_reg, |
321 | .reg_write = sti_sas_write_reg, |
322 | }; |
323 | |
324 | static const struct sti_sas_dev_data stih407_data = { |
325 | .regmap = &stih407_sas_regmap, |
326 | .dac_ops = &stih407_dac_ops, |
327 | .dapm_widgets = stih407_sas_dapm_widgets, |
328 | .num_dapm_widgets = ARRAY_SIZE(stih407_sas_dapm_widgets), |
329 | .dapm_routes = stih407_sas_route, |
330 | .num_dapm_routes = ARRAY_SIZE(stih407_sas_route), |
331 | }; |
332 | |
333 | static struct snd_soc_dai_driver sti_sas_dai[] = { |
334 | { |
335 | .name = "sas-dai-spdif-out" , |
336 | .id = STI_SAS_DAI_SPDIF_OUT, |
337 | .playback = { |
338 | .stream_name = "spdif_p" , |
339 | .channels_min = 2, |
340 | .channels_max = 2, |
341 | .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | |
342 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | |
343 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | |
344 | SNDRV_PCM_RATE_192000, |
345 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
346 | SNDRV_PCM_FMTBIT_S32_LE, |
347 | }, |
348 | .ops = (struct snd_soc_dai_ops[]) { |
349 | { |
350 | .set_fmt = sti_sas_spdif_set_fmt, |
351 | .trigger = sti_sas_spdif_trigger, |
352 | .set_sysclk = sti_sas_set_sysclk, |
353 | .prepare = sti_sas_prepare, |
354 | } |
355 | }, |
356 | }, |
357 | { |
358 | .name = "sas-dai-dac" , |
359 | .id = STI_SAS_DAI_ANALOG_OUT, |
360 | .playback = { |
361 | .stream_name = "dac_p" , |
362 | .channels_min = 2, |
363 | .channels_max = 2, |
364 | .rates = SNDRV_PCM_RATE_8000_48000, |
365 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
366 | SNDRV_PCM_FMTBIT_S32_LE, |
367 | }, |
368 | }, |
369 | }; |
370 | |
371 | #ifdef CONFIG_PM_SLEEP |
372 | static int sti_sas_resume(struct snd_soc_component *component) |
373 | { |
374 | struct sti_sas_data *drvdata = dev_get_drvdata(dev: component->dev); |
375 | |
376 | return sti_sas_init_sas_registers(component, data: drvdata); |
377 | } |
378 | #else |
379 | #define sti_sas_resume NULL |
380 | #endif |
381 | |
382 | static int sti_sas_component_probe(struct snd_soc_component *component) |
383 | { |
384 | struct sti_sas_data *drvdata = dev_get_drvdata(dev: component->dev); |
385 | |
386 | return sti_sas_init_sas_registers(component, data: drvdata); |
387 | } |
388 | |
389 | static struct snd_soc_component_driver sti_sas_driver = { |
390 | .probe = sti_sas_component_probe, |
391 | .resume = sti_sas_resume, |
392 | .idle_bias_on = 1, |
393 | .use_pmdown_time = 1, |
394 | .endianness = 1, |
395 | }; |
396 | |
397 | static const struct of_device_id sti_sas_dev_match[] = { |
398 | { |
399 | .compatible = "st,stih407-sas-codec" , |
400 | .data = &stih407_data, |
401 | }, |
402 | {}, |
403 | }; |
404 | MODULE_DEVICE_TABLE(of, sti_sas_dev_match); |
405 | |
406 | static int sti_sas_driver_probe(struct platform_device *pdev) |
407 | { |
408 | struct device_node *pnode = pdev->dev.of_node; |
409 | struct sti_sas_data *drvdata; |
410 | const struct of_device_id *of_id; |
411 | |
412 | /* Allocate device structure */ |
413 | drvdata = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct sti_sas_data), |
414 | GFP_KERNEL); |
415 | if (!drvdata) |
416 | return -ENOMEM; |
417 | |
418 | /* Populate data structure depending on compatibility */ |
419 | of_id = of_match_node(matches: sti_sas_dev_match, node: pnode); |
420 | if (!of_id->data) { |
421 | dev_err(&pdev->dev, "data associated to device is missing\n" ); |
422 | return -EINVAL; |
423 | } |
424 | |
425 | drvdata->dev_data = (struct sti_sas_dev_data *)of_id->data; |
426 | |
427 | /* Initialise device structure */ |
428 | drvdata->dev = &pdev->dev; |
429 | |
430 | /* Request the DAC & SPDIF registers memory region */ |
431 | drvdata->dac.virt_regmap = devm_regmap_init(&pdev->dev, NULL, drvdata, |
432 | drvdata->dev_data->regmap); |
433 | if (IS_ERR(ptr: drvdata->dac.virt_regmap)) { |
434 | dev_err(&pdev->dev, "audio registers not enabled\n" ); |
435 | return PTR_ERR(ptr: drvdata->dac.virt_regmap); |
436 | } |
437 | |
438 | /* Request the syscon region */ |
439 | drvdata->dac.regmap = |
440 | syscon_regmap_lookup_by_phandle(np: pnode, property: "st,syscfg" ); |
441 | if (IS_ERR(ptr: drvdata->dac.regmap)) { |
442 | dev_err(&pdev->dev, "syscon registers not available\n" ); |
443 | return PTR_ERR(ptr: drvdata->dac.regmap); |
444 | } |
445 | drvdata->spdif.regmap = drvdata->dac.regmap; |
446 | |
447 | sti_sas_dai[STI_SAS_DAI_ANALOG_OUT].ops = drvdata->dev_data->dac_ops; |
448 | |
449 | /* Set dapms*/ |
450 | sti_sas_driver.dapm_widgets = drvdata->dev_data->dapm_widgets; |
451 | sti_sas_driver.num_dapm_widgets = drvdata->dev_data->num_dapm_widgets; |
452 | |
453 | sti_sas_driver.dapm_routes = drvdata->dev_data->dapm_routes; |
454 | sti_sas_driver.num_dapm_routes = drvdata->dev_data->num_dapm_routes; |
455 | |
456 | /* Store context */ |
457 | dev_set_drvdata(dev: &pdev->dev, data: drvdata); |
458 | |
459 | return devm_snd_soc_register_component(dev: &pdev->dev, component_driver: &sti_sas_driver, |
460 | dai_drv: sti_sas_dai, |
461 | ARRAY_SIZE(sti_sas_dai)); |
462 | } |
463 | |
464 | static struct platform_driver sti_sas_platform_driver = { |
465 | .driver = { |
466 | .name = "sti-sas-codec" , |
467 | .of_match_table = sti_sas_dev_match, |
468 | }, |
469 | .probe = sti_sas_driver_probe, |
470 | }; |
471 | |
472 | module_platform_driver(sti_sas_platform_driver); |
473 | |
474 | MODULE_DESCRIPTION("audio codec for STMicroelectronics sti platforms" ); |
475 | MODULE_AUTHOR("Arnaud.pouliquen@st.com" ); |
476 | MODULE_LICENSE("GPL v2" ); |
477 | |