aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/tpa6130a2.c
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@nokia.com>2009-10-09 08:55:41 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-10-09 13:50:37 -0400
commit493b67efffc462703d583389aca96f850c18d3b3 (patch)
treeb6a682aeeddebaf3ea8071d008f14552416735b4 /sound/soc/codecs/tpa6130a2.c
parent69d2c2ae1dffac5fcd6130e459f250ae035b678f (diff)
ASoC: TPA6130A2 amplifier driver
Driver for Texas Instruments TPA6130A2 stereo headphone amplifier. The driver provides playback gain control and also pre-defined DAPM_HP widgets and DAPM routings for power management. The DAPM_HP widget names are: "TPA6130A2 Headphone Left" "TPA6130A2 Headphone Right" From soc machine drivers to use with the tpa6130a2 amplifier, the tpa6130a2_add_controls has to be called, which adds the alsa controls and the DAPM routing needed for the tpa6130a2. After that the machine driver can connect the codec's output with 'TPA6130A2 Left' and 'TPA6130A2 Right': {"TPA6130A2 Left", NULL, "CODEC LEFT OUT"}, {"TPA6130A2 Right", NULL, "CODEC RIGHT OUT"}, Internally the left and right channels are powered separately. When none of the channels are needed the amplifier is powered down: hard power: valid GPIO number is passed within platform data soft power: Using the software shutdown of the amplifier Signed-off-by: Peter Ujfalusi <peter.ujfalusi@nokia.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/codecs/tpa6130a2.c')
-rw-r--r--sound/soc/codecs/tpa6130a2.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c
new file mode 100644
index 000000000000..1b77c959e2dc
--- /dev/null
+++ b/sound/soc/codecs/tpa6130a2.c
@@ -0,0 +1,463 @@
1/*
2 * ALSA SoC Texas Instruments TPA6130A2 headset stereo amplifier driver
3 *
4 * Copyright (C) Nokia Corporation
5 *
6 * Author: Peter Ujfalusi <peter.ujfalusi@nokia.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 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA
21 */
22
23#include <linux/module.h>
24#include <linux/errno.h>
25#include <linux/device.h>
26#include <linux/i2c.h>
27#include <linux/gpio.h>
28#include <sound/tpa6130a2-plat.h>
29#include <sound/soc.h>
30#include <sound/soc-dapm.h>
31#include <sound/tlv.h>
32
33#include "tpa6130a2.h"
34
35struct i2c_client *tpa6130a2_client;
36
37/* This struct is used to save the context */
38struct tpa6130a2_data {
39 struct mutex mutex;
40 unsigned char regs[TPA6130A2_CACHEREGNUM];
41 int power_gpio;
42 unsigned char power_state;
43};
44
45static int tpa6130a2_i2c_read(int reg)
46{
47 struct tpa6130a2_data *data;
48 int val;
49
50 BUG_ON(tpa6130a2_client == NULL);
51 data = i2c_get_clientdata(tpa6130a2_client);
52
53 /* If powered off, return the cached value */
54 if (data->power_state) {
55 val = i2c_smbus_read_byte_data(tpa6130a2_client, reg);
56 if (val < 0)
57 dev_err(&tpa6130a2_client->dev, "Read failed\n");
58 else
59 data->regs[reg] = val;
60 } else {
61 val = data->regs[reg];
62 }
63
64 return val;
65}
66
67static int tpa6130a2_i2c_write(int reg, u8 value)
68{
69 struct tpa6130a2_data *data;
70 int val = 0;
71
72 BUG_ON(tpa6130a2_client == NULL);
73 data = i2c_get_clientdata(tpa6130a2_client);
74
75 if (data->power_state) {
76 val = i2c_smbus_write_byte_data(tpa6130a2_client, reg, value);
77 if (val < 0)
78 dev_err(&tpa6130a2_client->dev, "Write failed\n");
79 }
80
81 /* Either powered on or off, we save the context */
82 data->regs[reg] = value;
83
84 return val;
85}
86
87static u8 tpa6130a2_read(int reg)
88{
89 struct tpa6130a2_data *data;
90
91 BUG_ON(tpa6130a2_client == NULL);
92 data = i2c_get_clientdata(tpa6130a2_client);
93
94 return data->regs[reg];
95}
96
97static void tpa6130a2_initialize(void)
98{
99 struct tpa6130a2_data *data;
100 int i;
101
102 BUG_ON(tpa6130a2_client == NULL);
103 data = i2c_get_clientdata(tpa6130a2_client);
104
105 for (i = 1; i < TPA6130A2_REG_VERSION; i++)
106 tpa6130a2_i2c_write(i, data->regs[i]);
107}
108
109void tpa6130a2_power(int power)
110{
111 struct tpa6130a2_data *data;
112 u8 val;
113
114 BUG_ON(tpa6130a2_client == NULL);
115 data = i2c_get_clientdata(tpa6130a2_client);
116
117 mutex_lock(&data->mutex);
118 if (power) {
119 /* Power on */
120 if (data->power_gpio >= 0) {
121 gpio_set_value(data->power_gpio, 1);
122 data->power_state = 1;
123 tpa6130a2_initialize();
124 }
125 /* Clear SWS */
126 val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
127 val &= ~TPA6130A2_SWS;
128 tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
129 } else {
130 /* set SWS */
131 val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
132 val |= TPA6130A2_SWS;
133 tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
134 /* Power off */
135 if (data->power_gpio >= 0) {
136 gpio_set_value(data->power_gpio, 0);
137 data->power_state = 0;
138 }
139 }
140 mutex_unlock(&data->mutex);
141}
142
143static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol,
144 struct snd_ctl_elem_value *ucontrol)
145{
146 struct soc_mixer_control *mc =
147 (struct soc_mixer_control *)kcontrol->private_value;
148 struct tpa6130a2_data *data;
149 unsigned int reg = mc->reg;
150 unsigned int shift = mc->shift;
151 unsigned int mask = mc->max;
152 unsigned int invert = mc->invert;
153
154 BUG_ON(tpa6130a2_client == NULL);
155 data = i2c_get_clientdata(tpa6130a2_client);
156
157 mutex_lock(&data->mutex);
158
159 ucontrol->value.integer.value[0] =
160 (tpa6130a2_read(reg) >> shift) & mask;
161
162 if (invert)
163 ucontrol->value.integer.value[0] =
164 mask - ucontrol->value.integer.value[0];
165
166 mutex_unlock(&data->mutex);
167 return 0;
168}
169
170static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol,
171 struct snd_ctl_elem_value *ucontrol)
172{
173 struct soc_mixer_control *mc =
174 (struct soc_mixer_control *)kcontrol->private_value;
175 struct tpa6130a2_data *data;
176 unsigned int reg = mc->reg;
177 unsigned int shift = mc->shift;
178 unsigned int mask = mc->max;
179 unsigned int invert = mc->invert;
180 unsigned int val = (ucontrol->value.integer.value[0] & mask);
181 unsigned int val_reg;
182
183 BUG_ON(tpa6130a2_client == NULL);
184 data = i2c_get_clientdata(tpa6130a2_client);
185
186 if (invert)
187 val = mask - val;
188
189 mutex_lock(&data->mutex);
190
191 val_reg = tpa6130a2_read(reg);
192 if (((val_reg >> shift) & mask) == val) {
193 mutex_unlock(&data->mutex);
194 return 0;
195 }
196
197 val_reg &= ~(mask << shift);
198 val_reg |= val << shift;
199 tpa6130a2_i2c_write(reg, val_reg);
200
201 mutex_unlock(&data->mutex);
202
203 return 1;
204}
205
206/*
207 * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going
208 * down in gain.
209 */
210static const unsigned int tpa6130_tlv[] = {
211 TLV_DB_RANGE_HEAD(10),
212 0, 1, TLV_DB_SCALE_ITEM(-5950, 600, 0),
213 2, 3, TLV_DB_SCALE_ITEM(-5000, 250, 0),
214 4, 5, TLV_DB_SCALE_ITEM(-4550, 160, 0),
215 6, 7, TLV_DB_SCALE_ITEM(-4140, 190, 0),
216 8, 9, TLV_DB_SCALE_ITEM(-3650, 120, 0),
217 10, 11, TLV_DB_SCALE_ITEM(-3330, 160, 0),
218 12, 13, TLV_DB_SCALE_ITEM(-3040, 180, 0),
219 14, 20, TLV_DB_SCALE_ITEM(-2710, 110, 0),
220 21, 37, TLV_DB_SCALE_ITEM(-1960, 74, 0),
221 38, 63, TLV_DB_SCALE_ITEM(-720, 45, 0),
222};
223
224static const struct snd_kcontrol_new tpa6130a2_controls[] = {
225 SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume",
226 TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0,
227 tpa6130a2_get_reg, tpa6130a2_set_reg,
228 tpa6130_tlv),
229};
230
231/*
232 * Enable or disable channel (left or right)
233 * The bit number for mute and amplifier are the same per channel:
234 * bit 6: Right channel
235 * bit 7: Left channel
236 * in both registers.
237 */
238static void tpa6130a2_channel_enable(u8 channel, int enable)
239{
240 struct tpa6130a2_data *data;
241 u8 val;
242
243 BUG_ON(tpa6130a2_client == NULL);
244 data = i2c_get_clientdata(tpa6130a2_client);
245
246 if (enable) {
247 /* Enable channel */
248 /* Enable amplifier */
249 val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
250 val |= channel;
251 tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
252
253 /* Unmute channel */
254 val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE);
255 val &= ~channel;
256 tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val);
257 } else {
258 /* Disable channel */
259 /* Mute channel */
260 val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE);
261 val |= channel;
262 tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val);
263
264 /* Disable amplifier */
265 val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
266 val &= ~channel;
267 tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
268 }
269}
270
271static int tpa6130a2_left_event(struct snd_soc_dapm_widget *w,
272 struct snd_kcontrol *kcontrol, int event)
273{
274 switch (event) {
275 case SND_SOC_DAPM_POST_PMU:
276 tpa6130a2_channel_enable(TPA6130A2_HP_EN_L, 1);
277 break;
278 case SND_SOC_DAPM_POST_PMD:
279 tpa6130a2_channel_enable(TPA6130A2_HP_EN_L, 0);
280 break;
281 }
282 return 0;
283}
284
285static int tpa6130a2_right_event(struct snd_soc_dapm_widget *w,
286 struct snd_kcontrol *kcontrol, int event)
287{
288 switch (event) {
289 case SND_SOC_DAPM_POST_PMU:
290 tpa6130a2_channel_enable(TPA6130A2_HP_EN_R, 1);
291 break;
292 case SND_SOC_DAPM_POST_PMD:
293 tpa6130a2_channel_enable(TPA6130A2_HP_EN_R, 0);
294 break;
295 }
296 return 0;
297}
298
299static int tpa6130a2_supply_event(struct snd_soc_dapm_widget *w,
300 struct snd_kcontrol *kcontrol, int event)
301{
302 switch (event) {
303 case SND_SOC_DAPM_POST_PMU:
304 tpa6130a2_power(1);
305 break;
306 case SND_SOC_DAPM_POST_PMD:
307 tpa6130a2_power(0);
308 break;
309 }
310 return 0;
311}
312
313static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = {
314 SND_SOC_DAPM_PGA_E("TPA6130A2 Left", SND_SOC_NOPM,
315 0, 0, NULL, 0, tpa6130a2_left_event,
316 SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
317 SND_SOC_DAPM_PGA_E("TPA6130A2 Right", SND_SOC_NOPM,
318 0, 0, NULL, 0, tpa6130a2_right_event,
319 SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
320 SND_SOC_DAPM_SUPPLY("TPA6130A2 Enable", SND_SOC_NOPM,
321 0, 0, tpa6130a2_supply_event,
322 SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
323 /* Outputs */
324 SND_SOC_DAPM_HP("TPA6130A2 Headphone Left", NULL),
325 SND_SOC_DAPM_HP("TPA6130A2 Headphone Right", NULL),
326};
327
328static const struct snd_soc_dapm_route audio_map[] = {
329 {"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Left"},
330 {"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Right"},
331
332 {"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Enable"},
333 {"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Enable"},
334};
335
336int tpa6130a2_add_controls(struct snd_soc_codec *codec)
337{
338 snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets,
339 ARRAY_SIZE(tpa6130a2_dapm_widgets));
340
341 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
342
343 return snd_soc_add_controls(codec, tpa6130a2_controls,
344 ARRAY_SIZE(tpa6130a2_controls));
345
346}
347EXPORT_SYMBOL_GPL(tpa6130a2_add_controls);
348
349static int tpa6130a2_probe(struct i2c_client *client,
350 const struct i2c_device_id *id)
351{
352 struct device *dev;
353 struct tpa6130a2_data *data;
354 struct tpa6130a2_platform_data *pdata;
355 int ret;
356
357 dev = &client->dev;
358
359 if (client->dev.platform_data == NULL) {
360 dev_err(dev, "Platform data not set\n");
361 dump_stack();
362 return -ENODEV;
363 }
364
365 data = kzalloc(sizeof(*data), GFP_KERNEL);
366 if (data == NULL) {
367 dev_err(dev, "Can not allocate memory\n");
368 return -ENOMEM;
369 }
370
371 tpa6130a2_client = client;
372
373 i2c_set_clientdata(tpa6130a2_client, data);
374
375 pdata = (struct tpa6130a2_platform_data *)client->dev.platform_data;
376 data->power_gpio = pdata->power_gpio;
377
378 mutex_init(&data->mutex);
379
380 /* Set default register values */
381 data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS;
382 data->regs[TPA6130A2_REG_VOL_MUTE] = TPA6130A2_MUTE_R |
383 TPA6130A2_MUTE_L;
384
385 if (data->power_gpio >= 0) {
386 ret = gpio_request(data->power_gpio, "tpa6130a2 enable");
387 if (ret < 0) {
388 dev_err(dev, "Failed to request power GPIO (%d)\n",
389 data->power_gpio);
390 goto fail;
391 }
392 gpio_direction_output(data->power_gpio, 0);
393 } else {
394 data->power_state = 1;
395 tpa6130a2_initialize();
396 }
397
398 tpa6130a2_power(1);
399
400 /* Read version */
401 ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
402 TPA6130A2_VERSION_MASK;
403 if ((ret != 1) && (ret != 2))
404 dev_warn(dev, "UNTESTED version detected (%d)\n", ret);
405
406 /* Disable the chip */
407 tpa6130a2_power(0);
408
409 return 0;
410fail:
411 kfree(data);
412 i2c_set_clientdata(tpa6130a2_client, NULL);
413 tpa6130a2_client = 0;
414
415 return ret;
416}
417
418static int tpa6130a2_remove(struct i2c_client *client)
419{
420 struct tpa6130a2_data *data = i2c_get_clientdata(client);
421
422 tpa6130a2_power(0);
423
424 if (data->power_gpio >= 0)
425 gpio_free(data->power_gpio);
426 kfree(data);
427 tpa6130a2_client = 0;
428
429 return 0;
430}
431
432static const struct i2c_device_id tpa6130a2_id[] = {
433 { "tpa6130a2", 0 },
434 { }
435};
436MODULE_DEVICE_TABLE(i2c, tpa6130a2_id);
437
438static struct i2c_driver tpa6130a2_i2c_driver = {
439 .driver = {
440 .name = "tpa6130a2",
441 .owner = THIS_MODULE,
442 },
443 .probe = tpa6130a2_probe,
444 .remove = __devexit_p(tpa6130a2_remove),
445 .id_table = tpa6130a2_id,
446};
447
448static int __init tpa6130a2_init(void)
449{
450 return i2c_add_driver(&tpa6130a2_i2c_driver);
451}
452
453static void __exit tpa6130a2_exit(void)
454{
455 i2c_del_driver(&tpa6130a2_i2c_driver);
456}
457
458MODULE_AUTHOR("Peter Ujfalusi");
459MODULE_DESCRIPTION("TPA6130A2 Headphone amplifier driver");
460MODULE_LICENSE("GPL");
461
462module_init(tpa6130a2_init);
463module_exit(tpa6130a2_exit);