]>
Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
bd023ada AD |
2 | /* |
3 | * tas5720.c - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier | |
4 | * | |
5 | * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com | |
6 | * | |
7 | * Author: Andreas Dannenberg <dannenberg@ti.com> | |
bd023ada AD |
8 | */ |
9 | ||
10 | #include <linux/module.h> | |
11 | #include <linux/errno.h> | |
12 | #include <linux/device.h> | |
13 | #include <linux/i2c.h> | |
14 | #include <linux/pm_runtime.h> | |
15 | #include <linux/regmap.h> | |
16 | #include <linux/slab.h> | |
17 | #include <linux/regulator/consumer.h> | |
18 | #include <linux/delay.h> | |
19 | ||
20 | #include <sound/pcm.h> | |
21 | #include <sound/pcm_params.h> | |
22 | #include <sound/soc.h> | |
23 | #include <sound/soc-dapm.h> | |
24 | #include <sound/tlv.h> | |
25 | ||
26 | #include "tas5720.h" | |
27 | ||
28 | /* Define how often to check (and clear) the fault status register (in ms) */ | |
29 | #define TAS5720_FAULT_CHECK_INTERVAL 200 | |
30 | ||
872bcad2 AD |
31 | enum tas572x_type { |
32 | TAS5720, | |
33 | TAS5722, | |
34 | }; | |
35 | ||
bd023ada AD |
36 | static const char * const tas5720_supply_names[] = { |
37 | "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ | |
38 | "pvdd", /* Class-D amp and analog power supply (connected). */ | |
39 | }; | |
40 | ||
41 | #define TAS5720_NUM_SUPPLIES ARRAY_SIZE(tas5720_supply_names) | |
42 | ||
43 | struct tas5720_data { | |
a06919f5 | 44 | struct snd_soc_component *component; |
bd023ada AD |
45 | struct regmap *regmap; |
46 | struct i2c_client *tas5720_client; | |
872bcad2 | 47 | enum tas572x_type devtype; |
bd023ada AD |
48 | struct regulator_bulk_data supplies[TAS5720_NUM_SUPPLIES]; |
49 | struct delayed_work fault_check_work; | |
50 | unsigned int last_fault; | |
51 | }; | |
52 | ||
53 | static int tas5720_hw_params(struct snd_pcm_substream *substream, | |
54 | struct snd_pcm_hw_params *params, | |
55 | struct snd_soc_dai *dai) | |
56 | { | |
a06919f5 | 57 | struct snd_soc_component *component = dai->component; |
bd023ada AD |
58 | unsigned int rate = params_rate(params); |
59 | bool ssz_ds; | |
60 | int ret; | |
61 | ||
62 | switch (rate) { | |
63 | case 44100: | |
64 | case 48000: | |
65 | ssz_ds = false; | |
66 | break; | |
67 | case 88200: | |
68 | case 96000: | |
69 | ssz_ds = true; | |
70 | break; | |
71 | default: | |
a06919f5 | 72 | dev_err(component->dev, "unsupported sample rate: %u\n", rate); |
bd023ada AD |
73 | return -EINVAL; |
74 | } | |
75 | ||
a06919f5 | 76 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, |
bd023ada AD |
77 | TAS5720_SSZ_DS, ssz_ds); |
78 | if (ret < 0) { | |
a06919f5 | 79 | dev_err(component->dev, "error setting sample rate: %d\n", ret); |
bd023ada AD |
80 | return ret; |
81 | } | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
86 | static int tas5720_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
87 | { | |
a06919f5 | 88 | struct snd_soc_component *component = dai->component; |
bd023ada AD |
89 | u8 serial_format; |
90 | int ret; | |
91 | ||
92 | if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { | |
a06919f5 | 93 | dev_vdbg(component->dev, "DAI Format master is not found\n"); |
bd023ada AD |
94 | return -EINVAL; |
95 | } | |
96 | ||
97 | switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | | |
98 | SND_SOC_DAIFMT_INV_MASK)) { | |
99 | case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): | |
100 | /* 1st data bit occur one BCLK cycle after the frame sync */ | |
101 | serial_format = TAS5720_SAIF_I2S; | |
102 | break; | |
103 | case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF): | |
104 | /* | |
105 | * Note that although the TAS5720 does not have a dedicated DSP | |
106 | * mode it doesn't care about the LRCLK duty cycle during TDM | |
107 | * operation. Therefore we can use the device's I2S mode with | |
108 | * its delaying of the 1st data bit to receive DSP_A formatted | |
109 | * data. See device datasheet for additional details. | |
110 | */ | |
111 | serial_format = TAS5720_SAIF_I2S; | |
112 | break; | |
113 | case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF): | |
114 | /* | |
115 | * Similar to DSP_A, we can use the fact that the TAS5720 does | |
116 | * not care about the LRCLK duty cycle during TDM to receive | |
117 | * DSP_B formatted data in LEFTJ mode (no delaying of the 1st | |
118 | * data bit). | |
119 | */ | |
120 | serial_format = TAS5720_SAIF_LEFTJ; | |
121 | break; | |
122 | case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): | |
123 | /* No delay after the frame sync */ | |
124 | serial_format = TAS5720_SAIF_LEFTJ; | |
125 | break; | |
126 | default: | |
a06919f5 | 127 | dev_vdbg(component->dev, "DAI Format is not found\n"); |
bd023ada AD |
128 | return -EINVAL; |
129 | } | |
130 | ||
a06919f5 | 131 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, |
bd023ada AD |
132 | TAS5720_SAIF_FORMAT_MASK, |
133 | serial_format); | |
134 | if (ret < 0) { | |
a06919f5 | 135 | dev_err(component->dev, "error setting SAIF format: %d\n", ret); |
bd023ada AD |
136 | return ret; |
137 | } | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
142 | static int tas5720_set_dai_tdm_slot(struct snd_soc_dai *dai, | |
143 | unsigned int tx_mask, unsigned int rx_mask, | |
144 | int slots, int slot_width) | |
145 | { | |
a06919f5 | 146 | struct snd_soc_component *component = dai->component; |
db658f40 | 147 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); |
bd023ada AD |
148 | unsigned int first_slot; |
149 | int ret; | |
150 | ||
151 | if (!tx_mask) { | |
a06919f5 | 152 | dev_err(component->dev, "tx masks must not be 0\n"); |
bd023ada AD |
153 | return -EINVAL; |
154 | } | |
155 | ||
156 | /* | |
157 | * Determine the first slot that is being requested. We will only | |
158 | * use the first slot that is found since the TAS5720 is a mono | |
159 | * amplifier. | |
160 | */ | |
161 | first_slot = __ffs(tx_mask); | |
162 | ||
163 | if (first_slot > 7) { | |
a06919f5 | 164 | dev_err(component->dev, "slot selection out of bounds (%u)\n", |
bd023ada AD |
165 | first_slot); |
166 | return -EINVAL; | |
167 | } | |
168 | ||
169 | /* Enable manual TDM slot selection (instead of I2C ID based) */ | |
a06919f5 | 170 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, |
bd023ada AD |
171 | TAS5720_TDM_CFG_SRC, TAS5720_TDM_CFG_SRC); |
172 | if (ret < 0) | |
a06919f5 | 173 | goto error_snd_soc_component_update_bits; |
bd023ada AD |
174 | |
175 | /* Configure the TDM slot to process audio from */ | |
a06919f5 | 176 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL2_REG, |
bd023ada AD |
177 | TAS5720_TDM_SLOT_SEL_MASK, first_slot); |
178 | if (ret < 0) | |
a06919f5 | 179 | goto error_snd_soc_component_update_bits; |
bd023ada | 180 | |
db658f40 AD |
181 | /* Configure TDM slot width. This is only applicable to TAS5722. */ |
182 | switch (tas5720->devtype) { | |
183 | case TAS5722: | |
184 | ret = snd_soc_component_update_bits(component, TAS5722_DIGITAL_CTRL2_REG, | |
185 | TAS5722_TDM_SLOT_16B, | |
186 | slot_width == 16 ? | |
187 | TAS5722_TDM_SLOT_16B : 0); | |
188 | if (ret < 0) | |
189 | goto error_snd_soc_component_update_bits; | |
190 | break; | |
191 | default: | |
192 | break; | |
193 | } | |
194 | ||
bd023ada AD |
195 | return 0; |
196 | ||
a06919f5 KM |
197 | error_snd_soc_component_update_bits: |
198 | dev_err(component->dev, "error configuring TDM mode: %d\n", ret); | |
bd023ada AD |
199 | return ret; |
200 | } | |
201 | ||
202 | static int tas5720_mute(struct snd_soc_dai *dai, int mute) | |
203 | { | |
a06919f5 | 204 | struct snd_soc_component *component = dai->component; |
bd023ada AD |
205 | int ret; |
206 | ||
a06919f5 | 207 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL2_REG, |
bd023ada AD |
208 | TAS5720_MUTE, mute ? TAS5720_MUTE : 0); |
209 | if (ret < 0) { | |
a06919f5 | 210 | dev_err(component->dev, "error (un-)muting device: %d\n", ret); |
bd023ada AD |
211 | return ret; |
212 | } | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | static void tas5720_fault_check_work(struct work_struct *work) | |
218 | { | |
219 | struct tas5720_data *tas5720 = container_of(work, struct tas5720_data, | |
220 | fault_check_work.work); | |
a06919f5 | 221 | struct device *dev = tas5720->component->dev; |
bd023ada AD |
222 | unsigned int curr_fault; |
223 | int ret; | |
224 | ||
225 | ret = regmap_read(tas5720->regmap, TAS5720_FAULT_REG, &curr_fault); | |
226 | if (ret < 0) { | |
227 | dev_err(dev, "failed to read FAULT register: %d\n", ret); | |
228 | goto out; | |
229 | } | |
230 | ||
231 | /* Check/handle all errors except SAIF clock errors */ | |
232 | curr_fault &= TAS5720_OCE | TAS5720_DCE | TAS5720_OTE; | |
233 | ||
234 | /* | |
235 | * Only flag errors once for a given occurrence. This is needed as | |
236 | * the TAS5720 will take time clearing the fault condition internally | |
237 | * during which we don't want to bombard the system with the same | |
238 | * error message over and over. | |
239 | */ | |
240 | if ((curr_fault & TAS5720_OCE) && !(tas5720->last_fault & TAS5720_OCE)) | |
241 | dev_crit(dev, "experienced an over current hardware fault\n"); | |
242 | ||
243 | if ((curr_fault & TAS5720_DCE) && !(tas5720->last_fault & TAS5720_DCE)) | |
244 | dev_crit(dev, "experienced a DC detection fault\n"); | |
245 | ||
246 | if ((curr_fault & TAS5720_OTE) && !(tas5720->last_fault & TAS5720_OTE)) | |
247 | dev_crit(dev, "experienced an over temperature fault\n"); | |
248 | ||
249 | /* Store current fault value so we can detect any changes next time */ | |
250 | tas5720->last_fault = curr_fault; | |
251 | ||
252 | if (!curr_fault) | |
253 | goto out; | |
254 | ||
255 | /* | |
256 | * Periodically toggle SDZ (shutdown bit) H->L->H to clear any latching | |
257 | * faults as long as a fault condition persists. Always going through | |
258 | * the full sequence no matter the first return value to minimizes | |
259 | * chances for the device to end up in shutdown mode. | |
260 | */ | |
261 | ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, | |
262 | TAS5720_SDZ, 0); | |
263 | if (ret < 0) | |
264 | dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); | |
265 | ||
266 | ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, | |
267 | TAS5720_SDZ, TAS5720_SDZ); | |
268 | if (ret < 0) | |
269 | dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); | |
270 | ||
271 | out: | |
272 | /* Schedule the next fault check at the specified interval */ | |
273 | schedule_delayed_work(&tas5720->fault_check_work, | |
274 | msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); | |
275 | } | |
276 | ||
a06919f5 | 277 | static int tas5720_codec_probe(struct snd_soc_component *component) |
bd023ada | 278 | { |
a06919f5 | 279 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); |
872bcad2 | 280 | unsigned int device_id, expected_device_id; |
bd023ada AD |
281 | int ret; |
282 | ||
a06919f5 | 283 | tas5720->component = component; |
bd023ada AD |
284 | |
285 | ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), | |
286 | tas5720->supplies); | |
287 | if (ret != 0) { | |
a06919f5 | 288 | dev_err(component->dev, "failed to enable supplies: %d\n", ret); |
bd023ada AD |
289 | return ret; |
290 | } | |
291 | ||
872bcad2 AD |
292 | /* |
293 | * Take a liberal approach to checking the device ID to allow the | |
294 | * driver to be used even if the device ID does not match, however | |
295 | * issue a warning if there is a mismatch. | |
296 | */ | |
bd023ada AD |
297 | ret = regmap_read(tas5720->regmap, TAS5720_DEVICE_ID_REG, &device_id); |
298 | if (ret < 0) { | |
a06919f5 | 299 | dev_err(component->dev, "failed to read device ID register: %d\n", |
bd023ada AD |
300 | ret); |
301 | goto probe_fail; | |
302 | } | |
303 | ||
872bcad2 AD |
304 | switch (tas5720->devtype) { |
305 | case TAS5720: | |
306 | expected_device_id = TAS5720_DEVICE_ID; | |
307 | break; | |
308 | case TAS5722: | |
309 | expected_device_id = TAS5722_DEVICE_ID; | |
310 | break; | |
311 | default: | |
a06919f5 | 312 | dev_err(component->dev, "unexpected private driver data\n"); |
872bcad2 | 313 | return -EINVAL; |
bd023ada AD |
314 | } |
315 | ||
872bcad2 | 316 | if (device_id != expected_device_id) |
a06919f5 | 317 | dev_warn(component->dev, "wrong device ID. expected: %u read: %u\n", |
872bcad2 AD |
318 | expected_device_id, device_id); |
319 | ||
bd023ada | 320 | /* Set device to mute */ |
a06919f5 | 321 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL2_REG, |
bd023ada AD |
322 | TAS5720_MUTE, TAS5720_MUTE); |
323 | if (ret < 0) | |
a06919f5 | 324 | goto error_snd_soc_component_update_bits; |
bd023ada AD |
325 | |
326 | /* | |
327 | * Enter shutdown mode - our default when not playing audio - to | |
328 | * minimize current consumption. On the TAS5720 there is no real down | |
329 | * side doing so as all device registers are preserved and the wakeup | |
330 | * of the codec is rather quick which we do using a dapm widget. | |
331 | */ | |
a06919f5 | 332 | ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, |
bd023ada AD |
333 | TAS5720_SDZ, 0); |
334 | if (ret < 0) | |
a06919f5 | 335 | goto error_snd_soc_component_update_bits; |
bd023ada AD |
336 | |
337 | INIT_DELAYED_WORK(&tas5720->fault_check_work, tas5720_fault_check_work); | |
338 | ||
339 | return 0; | |
340 | ||
a06919f5 KM |
341 | error_snd_soc_component_update_bits: |
342 | dev_err(component->dev, "error configuring device registers: %d\n", ret); | |
bd023ada AD |
343 | |
344 | probe_fail: | |
345 | regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), | |
346 | tas5720->supplies); | |
347 | return ret; | |
348 | } | |
349 | ||
a06919f5 | 350 | static void tas5720_codec_remove(struct snd_soc_component *component) |
bd023ada | 351 | { |
a06919f5 | 352 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); |
bd023ada AD |
353 | int ret; |
354 | ||
355 | cancel_delayed_work_sync(&tas5720->fault_check_work); | |
356 | ||
357 | ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), | |
358 | tas5720->supplies); | |
359 | if (ret < 0) | |
a06919f5 | 360 | dev_err(component->dev, "failed to disable supplies: %d\n", ret); |
bd023ada AD |
361 | }; |
362 | ||
363 | static int tas5720_dac_event(struct snd_soc_dapm_widget *w, | |
364 | struct snd_kcontrol *kcontrol, int event) | |
365 | { | |
a06919f5 KM |
366 | struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); |
367 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); | |
bd023ada AD |
368 | int ret; |
369 | ||
370 | if (event & SND_SOC_DAPM_POST_PMU) { | |
371 | /* Take TAS5720 out of shutdown mode */ | |
a06919f5 | 372 | ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, |
bd023ada AD |
373 | TAS5720_SDZ, TAS5720_SDZ); |
374 | if (ret < 0) { | |
a06919f5 | 375 | dev_err(component->dev, "error waking component: %d\n", ret); |
bd023ada AD |
376 | return ret; |
377 | } | |
378 | ||
379 | /* | |
380 | * Observe codec shutdown-to-active time. The datasheet only | |
381 | * lists a nominal value however just use-it as-is without | |
382 | * additional padding to minimize the delay introduced in | |
383 | * starting to play audio (actually there is other setup done | |
384 | * by the ASoC framework that will provide additional delays, | |
385 | * so we should always be safe). | |
386 | */ | |
387 | msleep(25); | |
388 | ||
389 | /* Turn on TAS5720 periodic fault checking/handling */ | |
390 | tas5720->last_fault = 0; | |
391 | schedule_delayed_work(&tas5720->fault_check_work, | |
392 | msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); | |
393 | } else if (event & SND_SOC_DAPM_PRE_PMD) { | |
394 | /* Disable TAS5720 periodic fault checking/handling */ | |
395 | cancel_delayed_work_sync(&tas5720->fault_check_work); | |
396 | ||
397 | /* Place TAS5720 in shutdown mode to minimize current draw */ | |
a06919f5 | 398 | ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, |
bd023ada AD |
399 | TAS5720_SDZ, 0); |
400 | if (ret < 0) { | |
a06919f5 | 401 | dev_err(component->dev, "error shutting down component: %d\n", |
bd023ada AD |
402 | ret); |
403 | return ret; | |
404 | } | |
405 | } | |
406 | ||
407 | return 0; | |
408 | } | |
409 | ||
410 | #ifdef CONFIG_PM | |
a06919f5 | 411 | static int tas5720_suspend(struct snd_soc_component *component) |
bd023ada | 412 | { |
a06919f5 | 413 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); |
bd023ada AD |
414 | int ret; |
415 | ||
416 | regcache_cache_only(tas5720->regmap, true); | |
417 | regcache_mark_dirty(tas5720->regmap); | |
418 | ||
419 | ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), | |
420 | tas5720->supplies); | |
421 | if (ret < 0) | |
a06919f5 | 422 | dev_err(component->dev, "failed to disable supplies: %d\n", ret); |
bd023ada AD |
423 | |
424 | return ret; | |
425 | } | |
426 | ||
a06919f5 | 427 | static int tas5720_resume(struct snd_soc_component *component) |
bd023ada | 428 | { |
a06919f5 | 429 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); |
bd023ada AD |
430 | int ret; |
431 | ||
432 | ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), | |
433 | tas5720->supplies); | |
434 | if (ret < 0) { | |
a06919f5 | 435 | dev_err(component->dev, "failed to enable supplies: %d\n", ret); |
bd023ada AD |
436 | return ret; |
437 | } | |
438 | ||
439 | regcache_cache_only(tas5720->regmap, false); | |
440 | ||
441 | ret = regcache_sync(tas5720->regmap); | |
442 | if (ret < 0) { | |
a06919f5 | 443 | dev_err(component->dev, "failed to sync regcache: %d\n", ret); |
bd023ada AD |
444 | return ret; |
445 | } | |
446 | ||
447 | return 0; | |
448 | } | |
449 | #else | |
450 | #define tas5720_suspend NULL | |
451 | #define tas5720_resume NULL | |
452 | #endif | |
453 | ||
454 | static bool tas5720_is_volatile_reg(struct device *dev, unsigned int reg) | |
455 | { | |
456 | switch (reg) { | |
457 | case TAS5720_DEVICE_ID_REG: | |
458 | case TAS5720_FAULT_REG: | |
459 | return true; | |
460 | default: | |
461 | return false; | |
462 | } | |
463 | } | |
464 | ||
465 | static const struct regmap_config tas5720_regmap_config = { | |
466 | .reg_bits = 8, | |
467 | .val_bits = 8, | |
468 | ||
469 | .max_register = TAS5720_MAX_REG, | |
470 | .cache_type = REGCACHE_RBTREE, | |
471 | .volatile_reg = tas5720_is_volatile_reg, | |
472 | }; | |
473 | ||
d5eb436a AD |
474 | static const struct regmap_config tas5722_regmap_config = { |
475 | .reg_bits = 8, | |
476 | .val_bits = 8, | |
477 | ||
478 | .max_register = TAS5722_MAX_REG, | |
479 | .cache_type = REGCACHE_RBTREE, | |
480 | .volatile_reg = tas5720_is_volatile_reg, | |
481 | }; | |
482 | ||
bd023ada AD |
483 | /* |
484 | * DAC analog gain. There are four discrete values to select from, ranging | |
485 | * from 19.2 dB to 26.3dB. | |
486 | */ | |
487 | static const DECLARE_TLV_DB_RANGE(dac_analog_tlv, | |
488 | 0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0), | |
489 | 0x1, 0x1, TLV_DB_SCALE_ITEM(2070, 0, 0), | |
490 | 0x2, 0x2, TLV_DB_SCALE_ITEM(2350, 0, 0), | |
491 | 0x3, 0x3, TLV_DB_SCALE_ITEM(2630, 0, 0), | |
492 | ); | |
493 | ||
494 | /* | |
ec94c177 AD |
495 | * DAC digital volumes. From -103.5 to 24 dB in 0.5 dB or 0.25 dB steps |
496 | * depending on the device. Note that setting the gain below -100 dB | |
497 | * (register value <0x7) is effectively a MUTE as per device datasheet. | |
498 | * | |
499 | * Note that for the TAS5722 the digital volume controls are actually split | |
500 | * over two registers, so we need custom getters/setters for access. | |
bd023ada | 501 | */ |
ec94c177 AD |
502 | static DECLARE_TLV_DB_SCALE(tas5720_dac_tlv, -10350, 50, 0); |
503 | static DECLARE_TLV_DB_SCALE(tas5722_dac_tlv, -10350, 25, 0); | |
504 | ||
505 | static int tas5722_volume_get(struct snd_kcontrol *kcontrol, | |
506 | struct snd_ctl_elem_value *ucontrol) | |
507 | { | |
508 | struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); | |
509 | unsigned int val; | |
510 | ||
511 | snd_soc_component_read(component, TAS5720_VOLUME_CTRL_REG, &val); | |
512 | ucontrol->value.integer.value[0] = val << 1; | |
513 | ||
514 | snd_soc_component_read(component, TAS5722_DIGITAL_CTRL2_REG, &val); | |
515 | ucontrol->value.integer.value[0] |= val & TAS5722_VOL_CONTROL_LSB; | |
516 | ||
517 | return 0; | |
518 | } | |
519 | ||
520 | static int tas5722_volume_set(struct snd_kcontrol *kcontrol, | |
521 | struct snd_ctl_elem_value *ucontrol) | |
522 | { | |
523 | struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); | |
524 | unsigned int sel = ucontrol->value.integer.value[0]; | |
525 | ||
526 | snd_soc_component_write(component, TAS5720_VOLUME_CTRL_REG, sel >> 1); | |
527 | snd_soc_component_update_bits(component, TAS5722_DIGITAL_CTRL2_REG, | |
528 | TAS5722_VOL_CONTROL_LSB, sel); | |
529 | ||
530 | return 0; | |
531 | } | |
bd023ada AD |
532 | |
533 | static const struct snd_kcontrol_new tas5720_snd_controls[] = { | |
534 | SOC_SINGLE_TLV("Speaker Driver Playback Volume", | |
ec94c177 AD |
535 | TAS5720_VOLUME_CTRL_REG, 0, 0xff, 0, tas5720_dac_tlv), |
536 | SOC_SINGLE_TLV("Speaker Driver Analog Gain", TAS5720_ANALOG_CTRL_REG, | |
537 | TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), | |
538 | }; | |
539 | ||
540 | static const struct snd_kcontrol_new tas5722_snd_controls[] = { | |
541 | SOC_SINGLE_EXT_TLV("Speaker Driver Playback Volume", | |
542 | 0, 0, 511, 0, | |
543 | tas5722_volume_get, tas5722_volume_set, | |
544 | tas5722_dac_tlv), | |
bd023ada AD |
545 | SOC_SINGLE_TLV("Speaker Driver Analog Gain", TAS5720_ANALOG_CTRL_REG, |
546 | TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), | |
547 | }; | |
548 | ||
549 | static const struct snd_soc_dapm_widget tas5720_dapm_widgets[] = { | |
550 | SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0), | |
551 | SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas5720_dac_event, | |
552 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), | |
553 | SND_SOC_DAPM_OUTPUT("OUT") | |
554 | }; | |
555 | ||
556 | static const struct snd_soc_dapm_route tas5720_audio_map[] = { | |
557 | { "DAC", NULL, "DAC IN" }, | |
558 | { "OUT", NULL, "DAC" }, | |
559 | }; | |
560 | ||
a06919f5 KM |
561 | static const struct snd_soc_component_driver soc_component_dev_tas5720 = { |
562 | .probe = tas5720_codec_probe, | |
563 | .remove = tas5720_codec_remove, | |
564 | .suspend = tas5720_suspend, | |
565 | .resume = tas5720_resume, | |
566 | .controls = tas5720_snd_controls, | |
567 | .num_controls = ARRAY_SIZE(tas5720_snd_controls), | |
568 | .dapm_widgets = tas5720_dapm_widgets, | |
569 | .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), | |
570 | .dapm_routes = tas5720_audio_map, | |
571 | .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), | |
572 | .idle_bias_on = 1, | |
573 | .use_pmdown_time = 1, | |
574 | .endianness = 1, | |
575 | .non_legacy_dai_naming = 1, | |
bd023ada AD |
576 | }; |
577 | ||
ec94c177 AD |
578 | static const struct snd_soc_component_driver soc_component_dev_tas5722 = { |
579 | .probe = tas5720_codec_probe, | |
580 | .remove = tas5720_codec_remove, | |
581 | .suspend = tas5720_suspend, | |
582 | .resume = tas5720_resume, | |
583 | .controls = tas5722_snd_controls, | |
584 | .num_controls = ARRAY_SIZE(tas5722_snd_controls), | |
585 | .dapm_widgets = tas5720_dapm_widgets, | |
586 | .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), | |
587 | .dapm_routes = tas5720_audio_map, | |
588 | .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), | |
589 | .idle_bias_on = 1, | |
590 | .use_pmdown_time = 1, | |
591 | .endianness = 1, | |
592 | .non_legacy_dai_naming = 1, | |
593 | }; | |
594 | ||
bd023ada AD |
595 | /* PCM rates supported by the TAS5720 driver */ |
596 | #define TAS5720_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ | |
597 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) | |
598 | ||
599 | /* Formats supported by TAS5720 driver */ | |
600 | #define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ | |
601 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) | |
602 | ||
eb59d73c | 603 | static const struct snd_soc_dai_ops tas5720_speaker_dai_ops = { |
bd023ada AD |
604 | .hw_params = tas5720_hw_params, |
605 | .set_fmt = tas5720_set_dai_fmt, | |
606 | .set_tdm_slot = tas5720_set_dai_tdm_slot, | |
607 | .digital_mute = tas5720_mute, | |
608 | }; | |
609 | ||
610 | /* | |
611 | * TAS5720 DAI structure | |
612 | * | |
613 | * Note that were are advertising .playback.channels_max = 2 despite this being | |
614 | * a mono amplifier. The reason for that is that some serial ports such as TI's | |
615 | * McASP module have a minimum number of channels (2) that they can output. | |
616 | * Advertising more channels than we have will allow us to interface with such | |
617 | * a serial port without really any negative side effects as the TAS5720 will | |
618 | * simply ignore any extra channel(s) asides from the one channel that is | |
619 | * configured to be played back. | |
620 | */ | |
621 | static struct snd_soc_dai_driver tas5720_dai[] = { | |
622 | { | |
623 | .name = "tas5720-amplifier", | |
624 | .playback = { | |
625 | .stream_name = "Playback", | |
626 | .channels_min = 1, | |
627 | .channels_max = 2, | |
628 | .rates = TAS5720_RATES, | |
629 | .formats = TAS5720_FORMATS, | |
630 | }, | |
631 | .ops = &tas5720_speaker_dai_ops, | |
632 | }, | |
633 | }; | |
634 | ||
635 | static int tas5720_probe(struct i2c_client *client, | |
636 | const struct i2c_device_id *id) | |
637 | { | |
638 | struct device *dev = &client->dev; | |
639 | struct tas5720_data *data; | |
d5eb436a | 640 | const struct regmap_config *regmap_config; |
bd023ada AD |
641 | int ret; |
642 | int i; | |
643 | ||
644 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
645 | if (!data) | |
646 | return -ENOMEM; | |
647 | ||
648 | data->tas5720_client = client; | |
872bcad2 AD |
649 | data->devtype = id->driver_data; |
650 | ||
d5eb436a AD |
651 | switch (id->driver_data) { |
652 | case TAS5720: | |
653 | regmap_config = &tas5720_regmap_config; | |
654 | break; | |
655 | case TAS5722: | |
656 | regmap_config = &tas5722_regmap_config; | |
657 | break; | |
658 | default: | |
659 | dev_err(dev, "unexpected private driver data\n"); | |
660 | return -EINVAL; | |
661 | } | |
662 | data->regmap = devm_regmap_init_i2c(client, regmap_config); | |
bd023ada AD |
663 | if (IS_ERR(data->regmap)) { |
664 | ret = PTR_ERR(data->regmap); | |
665 | dev_err(dev, "failed to allocate register map: %d\n", ret); | |
666 | return ret; | |
667 | } | |
668 | ||
669 | for (i = 0; i < ARRAY_SIZE(data->supplies); i++) | |
670 | data->supplies[i].supply = tas5720_supply_names[i]; | |
671 | ||
672 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), | |
673 | data->supplies); | |
674 | if (ret != 0) { | |
675 | dev_err(dev, "failed to request supplies: %d\n", ret); | |
676 | return ret; | |
677 | } | |
678 | ||
679 | dev_set_drvdata(dev, data); | |
680 | ||
ec94c177 AD |
681 | switch (id->driver_data) { |
682 | case TAS5720: | |
683 | ret = devm_snd_soc_register_component(&client->dev, | |
684 | &soc_component_dev_tas5720, | |
685 | tas5720_dai, | |
686 | ARRAY_SIZE(tas5720_dai)); | |
687 | break; | |
688 | case TAS5722: | |
689 | ret = devm_snd_soc_register_component(&client->dev, | |
690 | &soc_component_dev_tas5722, | |
691 | tas5720_dai, | |
692 | ARRAY_SIZE(tas5720_dai)); | |
693 | break; | |
694 | default: | |
695 | dev_err(dev, "unexpected private driver data\n"); | |
696 | return -EINVAL; | |
697 | } | |
bd023ada | 698 | if (ret < 0) { |
a06919f5 | 699 | dev_err(dev, "failed to register component: %d\n", ret); |
bd023ada AD |
700 | return ret; |
701 | } | |
702 | ||
703 | return 0; | |
704 | } | |
705 | ||
bd023ada | 706 | static const struct i2c_device_id tas5720_id[] = { |
872bcad2 AD |
707 | { "tas5720", TAS5720 }, |
708 | { "tas5722", TAS5722 }, | |
bd023ada AD |
709 | { } |
710 | }; | |
711 | MODULE_DEVICE_TABLE(i2c, tas5720_id); | |
712 | ||
713 | #if IS_ENABLED(CONFIG_OF) | |
714 | static const struct of_device_id tas5720_of_match[] = { | |
715 | { .compatible = "ti,tas5720", }, | |
872bcad2 | 716 | { .compatible = "ti,tas5722", }, |
bd023ada AD |
717 | { }, |
718 | }; | |
719 | MODULE_DEVICE_TABLE(of, tas5720_of_match); | |
720 | #endif | |
721 | ||
722 | static struct i2c_driver tas5720_i2c_driver = { | |
723 | .driver = { | |
724 | .name = "tas5720", | |
725 | .of_match_table = of_match_ptr(tas5720_of_match), | |
726 | }, | |
727 | .probe = tas5720_probe, | |
bd023ada AD |
728 | .id_table = tas5720_id, |
729 | }; | |
730 | ||
731 | module_i2c_driver(tas5720_i2c_driver); | |
732 | ||
733 | MODULE_AUTHOR("Andreas Dannenberg <dannenberg@ti.com>"); | |
734 | MODULE_DESCRIPTION("TAS5720 Audio amplifier driver"); | |
735 | MODULE_LICENSE("GPL"); |