diff options
Diffstat (limited to 'sound/soc/codecs/max9877.c')
-rw-r--r-- | sound/soc/codecs/max9877.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/sound/soc/codecs/max9877.c b/sound/soc/codecs/max9877.c new file mode 100644 index 000000000000..9e7e964a5fa3 --- /dev/null +++ b/sound/soc/codecs/max9877.c | |||
@@ -0,0 +1,308 @@ | |||
1 | /* | ||
2 | * max9877.c -- amp driver for max9877 | ||
3 | * | ||
4 | * Copyright (C) 2009 Samsung Electronics Co.Ltd | ||
5 | * Author: Joonyoung Shim <jy0922.shim@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms of the GNU General Public License as published by the | ||
9 | * Free Software Foundation; either version 2 of the License, or (at your | ||
10 | * option) any later version. | ||
11 | * | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/i2c.h> | ||
17 | #include <sound/soc.h> | ||
18 | #include <sound/tlv.h> | ||
19 | |||
20 | #include "max9877.h" | ||
21 | |||
22 | static struct i2c_client *i2c; | ||
23 | |||
24 | static u8 max9877_regs[5] = { 0x40, 0x00, 0x00, 0x00, 0x49 }; | ||
25 | |||
26 | static void max9877_write_regs(void) | ||
27 | { | ||
28 | unsigned int i; | ||
29 | u8 data[6]; | ||
30 | |||
31 | data[0] = MAX9877_INPUT_MODE; | ||
32 | for (i = 0; i < ARRAY_SIZE(max9877_regs); i++) | ||
33 | data[i + 1] = max9877_regs[i]; | ||
34 | |||
35 | if (i2c_master_send(i2c, data, 6) != 6) | ||
36 | dev_err(&i2c->dev, "i2c write failed\n"); | ||
37 | } | ||
38 | |||
39 | static int max9877_get_reg(struct snd_kcontrol *kcontrol, | ||
40 | struct snd_ctl_elem_value *ucontrol) | ||
41 | { | ||
42 | struct soc_mixer_control *mc = | ||
43 | (struct soc_mixer_control *)kcontrol->private_value; | ||
44 | unsigned int reg = mc->reg; | ||
45 | unsigned int shift = mc->shift; | ||
46 | unsigned int mask = mc->max; | ||
47 | unsigned int invert = mc->invert; | ||
48 | |||
49 | ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask; | ||
50 | |||
51 | if (invert) | ||
52 | ucontrol->value.integer.value[0] = | ||
53 | mask - ucontrol->value.integer.value[0]; | ||
54 | |||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | static int max9877_set_reg(struct snd_kcontrol *kcontrol, | ||
59 | struct snd_ctl_elem_value *ucontrol) | ||
60 | { | ||
61 | struct soc_mixer_control *mc = | ||
62 | (struct soc_mixer_control *)kcontrol->private_value; | ||
63 | unsigned int reg = mc->reg; | ||
64 | unsigned int shift = mc->shift; | ||
65 | unsigned int mask = mc->max; | ||
66 | unsigned int invert = mc->invert; | ||
67 | unsigned int val = (ucontrol->value.integer.value[0] & mask); | ||
68 | |||
69 | if (invert) | ||
70 | val = mask - val; | ||
71 | |||
72 | if (((max9877_regs[reg] >> shift) & mask) == val) | ||
73 | return 0; | ||
74 | |||
75 | max9877_regs[reg] &= ~(mask << shift); | ||
76 | max9877_regs[reg] |= val << shift; | ||
77 | max9877_write_regs(); | ||
78 | |||
79 | return 1; | ||
80 | } | ||
81 | |||
82 | static int max9877_get_2reg(struct snd_kcontrol *kcontrol, | ||
83 | struct snd_ctl_elem_value *ucontrol) | ||
84 | { | ||
85 | struct soc_mixer_control *mc = | ||
86 | (struct soc_mixer_control *)kcontrol->private_value; | ||
87 | unsigned int reg = mc->reg; | ||
88 | unsigned int reg2 = mc->rreg; | ||
89 | unsigned int shift = mc->shift; | ||
90 | unsigned int mask = mc->max; | ||
91 | |||
92 | ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask; | ||
93 | ucontrol->value.integer.value[1] = (max9877_regs[reg2] >> shift) & mask; | ||
94 | |||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static int max9877_set_2reg(struct snd_kcontrol *kcontrol, | ||
99 | struct snd_ctl_elem_value *ucontrol) | ||
100 | { | ||
101 | struct soc_mixer_control *mc = | ||
102 | (struct soc_mixer_control *)kcontrol->private_value; | ||
103 | unsigned int reg = mc->reg; | ||
104 | unsigned int reg2 = mc->rreg; | ||
105 | unsigned int shift = mc->shift; | ||
106 | unsigned int mask = mc->max; | ||
107 | unsigned int val = (ucontrol->value.integer.value[0] & mask); | ||
108 | unsigned int val2 = (ucontrol->value.integer.value[1] & mask); | ||
109 | unsigned int change = 1; | ||
110 | |||
111 | if (((max9877_regs[reg] >> shift) & mask) == val) | ||
112 | change = 0; | ||
113 | |||
114 | if (((max9877_regs[reg2] >> shift) & mask) == val2) | ||
115 | change = 0; | ||
116 | |||
117 | if (change) { | ||
118 | max9877_regs[reg] &= ~(mask << shift); | ||
119 | max9877_regs[reg] |= val << shift; | ||
120 | max9877_regs[reg2] &= ~(mask << shift); | ||
121 | max9877_regs[reg2] |= val2 << shift; | ||
122 | max9877_write_regs(); | ||
123 | } | ||
124 | |||
125 | return change; | ||
126 | } | ||
127 | |||
128 | static int max9877_get_out_mode(struct snd_kcontrol *kcontrol, | ||
129 | struct snd_ctl_elem_value *ucontrol) | ||
130 | { | ||
131 | u8 value = max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK; | ||
132 | |||
133 | if (value) | ||
134 | value -= 1; | ||
135 | |||
136 | ucontrol->value.integer.value[0] = value; | ||
137 | return 0; | ||
138 | } | ||
139 | |||
140 | static int max9877_set_out_mode(struct snd_kcontrol *kcontrol, | ||
141 | struct snd_ctl_elem_value *ucontrol) | ||
142 | { | ||
143 | u8 value = ucontrol->value.integer.value[0]; | ||
144 | |||
145 | value += 1; | ||
146 | |||
147 | if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK) == value) | ||
148 | return 0; | ||
149 | |||
150 | max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OUTMODE_MASK; | ||
151 | max9877_regs[MAX9877_OUTPUT_MODE] |= value; | ||
152 | max9877_write_regs(); | ||
153 | return 1; | ||
154 | } | ||
155 | |||
156 | static int max9877_get_osc_mode(struct snd_kcontrol *kcontrol, | ||
157 | struct snd_ctl_elem_value *ucontrol) | ||
158 | { | ||
159 | u8 value = (max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK); | ||
160 | |||
161 | value = value >> MAX9877_OSC_OFFSET; | ||
162 | |||
163 | ucontrol->value.integer.value[0] = value; | ||
164 | return 0; | ||
165 | } | ||
166 | |||
167 | static int max9877_set_osc_mode(struct snd_kcontrol *kcontrol, | ||
168 | struct snd_ctl_elem_value *ucontrol) | ||
169 | { | ||
170 | u8 value = ucontrol->value.integer.value[0]; | ||
171 | |||
172 | value = value << MAX9877_OSC_OFFSET; | ||
173 | if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK) == value) | ||
174 | return 0; | ||
175 | |||
176 | max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OSC_MASK; | ||
177 | max9877_regs[MAX9877_OUTPUT_MODE] |= value; | ||
178 | max9877_write_regs(); | ||
179 | return 1; | ||
180 | } | ||
181 | |||
182 | static const unsigned int max9877_pgain_tlv[] = { | ||
183 | TLV_DB_RANGE_HEAD(2), | ||
184 | 0, 1, TLV_DB_SCALE_ITEM(0, 900, 0), | ||
185 | 2, 2, TLV_DB_SCALE_ITEM(2000, 0, 0), | ||
186 | }; | ||
187 | |||
188 | static const unsigned int max9877_output_tlv[] = { | ||
189 | TLV_DB_RANGE_HEAD(4), | ||
190 | 0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1), | ||
191 | 8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0), | ||
192 | 16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0), | ||
193 | 24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0), | ||
194 | }; | ||
195 | |||
196 | static const char *max9877_out_mode[] = { | ||
197 | "INA -> SPK", | ||
198 | "INA -> HP", | ||
199 | "INA -> SPK and HP", | ||
200 | "INB -> SPK", | ||
201 | "INB -> HP", | ||
202 | "INB -> SPK and HP", | ||
203 | "INA + INB -> SPK", | ||
204 | "INA + INB -> HP", | ||
205 | "INA + INB -> SPK and HP", | ||
206 | }; | ||
207 | |||
208 | static const char *max9877_osc_mode[] = { | ||
209 | "1176KHz", | ||
210 | "1100KHz", | ||
211 | "700KHz", | ||
212 | }; | ||
213 | |||
214 | static const struct soc_enum max9877_enum[] = { | ||
215 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_out_mode), max9877_out_mode), | ||
216 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_osc_mode), max9877_osc_mode), | ||
217 | }; | ||
218 | |||
219 | static const struct snd_kcontrol_new max9877_controls[] = { | ||
220 | SOC_SINGLE_EXT_TLV("MAX9877 PGAINA Playback Volume", | ||
221 | MAX9877_INPUT_MODE, 0, 2, 0, | ||
222 | max9877_get_reg, max9877_set_reg, max9877_pgain_tlv), | ||
223 | SOC_SINGLE_EXT_TLV("MAX9877 PGAINB Playback Volume", | ||
224 | MAX9877_INPUT_MODE, 2, 2, 0, | ||
225 | max9877_get_reg, max9877_set_reg, max9877_pgain_tlv), | ||
226 | SOC_SINGLE_EXT_TLV("MAX9877 Amp Speaker Playback Volume", | ||
227 | MAX9877_SPK_VOLUME, 0, 31, 0, | ||
228 | max9877_get_reg, max9877_set_reg, max9877_output_tlv), | ||
229 | SOC_DOUBLE_R_EXT_TLV("MAX9877 Amp HP Playback Volume", | ||
230 | MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0, | ||
231 | max9877_get_2reg, max9877_set_2reg, max9877_output_tlv), | ||
232 | SOC_SINGLE_EXT("MAX9877 INB Stereo Switch", | ||
233 | MAX9877_INPUT_MODE, 4, 1, 1, | ||
234 | max9877_get_reg, max9877_set_reg), | ||
235 | SOC_SINGLE_EXT("MAX9877 INA Stereo Switch", | ||
236 | MAX9877_INPUT_MODE, 5, 1, 1, | ||
237 | max9877_get_reg, max9877_set_reg), | ||
238 | SOC_SINGLE_EXT("MAX9877 Zero-crossing detection Switch", | ||
239 | MAX9877_INPUT_MODE, 6, 1, 0, | ||
240 | max9877_get_reg, max9877_set_reg), | ||
241 | SOC_SINGLE_EXT("MAX9877 Bypass Mode Switch", | ||
242 | MAX9877_OUTPUT_MODE, 6, 1, 0, | ||
243 | max9877_get_reg, max9877_set_reg), | ||
244 | SOC_SINGLE_EXT("MAX9877 Shutdown Mode Switch", | ||
245 | MAX9877_OUTPUT_MODE, 7, 1, 1, | ||
246 | max9877_get_reg, max9877_set_reg), | ||
247 | SOC_ENUM_EXT("MAX9877 Output Mode", max9877_enum[0], | ||
248 | max9877_get_out_mode, max9877_set_out_mode), | ||
249 | SOC_ENUM_EXT("MAX9877 Oscillator Mode", max9877_enum[1], | ||
250 | max9877_get_osc_mode, max9877_set_osc_mode), | ||
251 | }; | ||
252 | |||
253 | /* This function is called from ASoC machine driver */ | ||
254 | int max9877_add_controls(struct snd_soc_codec *codec) | ||
255 | { | ||
256 | return snd_soc_add_controls(codec, max9877_controls, | ||
257 | ARRAY_SIZE(max9877_controls)); | ||
258 | } | ||
259 | EXPORT_SYMBOL_GPL(max9877_add_controls); | ||
260 | |||
261 | static int __devinit max9877_i2c_probe(struct i2c_client *client, | ||
262 | const struct i2c_device_id *id) | ||
263 | { | ||
264 | i2c = client; | ||
265 | |||
266 | max9877_write_regs(); | ||
267 | |||
268 | return 0; | ||
269 | } | ||
270 | |||
271 | static __devexit int max9877_i2c_remove(struct i2c_client *client) | ||
272 | { | ||
273 | i2c = NULL; | ||
274 | |||
275 | return 0; | ||
276 | } | ||
277 | |||
278 | static const struct i2c_device_id max9877_i2c_id[] = { | ||
279 | { "max9877", 0 }, | ||
280 | { } | ||
281 | }; | ||
282 | MODULE_DEVICE_TABLE(i2c, max9877_i2c_id); | ||
283 | |||
284 | static struct i2c_driver max9877_i2c_driver = { | ||
285 | .driver = { | ||
286 | .name = "max9877", | ||
287 | .owner = THIS_MODULE, | ||
288 | }, | ||
289 | .probe = max9877_i2c_probe, | ||
290 | .remove = __devexit_p(max9877_i2c_remove), | ||
291 | .id_table = max9877_i2c_id, | ||
292 | }; | ||
293 | |||
294 | static int __init max9877_init(void) | ||
295 | { | ||
296 | return i2c_add_driver(&max9877_i2c_driver); | ||
297 | } | ||
298 | module_init(max9877_init); | ||
299 | |||
300 | static void __exit max9877_exit(void) | ||
301 | { | ||
302 | i2c_del_driver(&max9877_i2c_driver); | ||
303 | } | ||
304 | module_exit(max9877_exit); | ||
305 | |||
306 | MODULE_DESCRIPTION("ASoC MAX9877 amp driver"); | ||
307 | MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); | ||
308 | MODULE_LICENSE("GPL"); | ||