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