Commit | Line | Data |
---|---|---|
9db9ed97 JS |
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 | if (i2c_master_send(i2c, max9877_regs, 5) != 5) | |
29 | dev_err(&i2c->dev, "i2c write failed\n"); | |
30 | } | |
31 | ||
32 | static int max9877_get_reg(struct snd_kcontrol *kcontrol, | |
33 | struct snd_ctl_elem_value *ucontrol) | |
34 | { | |
35 | struct soc_mixer_control *mc = | |
36 | (struct soc_mixer_control *)kcontrol->private_value; | |
37 | int reg = mc->reg; | |
38 | int reg2 = mc->rreg; | |
39 | int shift = mc->shift; | |
40 | int mask = mc->max; | |
41 | ||
42 | ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask; | |
43 | ||
44 | if (reg2) | |
45 | ucontrol->value.integer.value[1] = | |
46 | (max9877_regs[reg2] >> shift) & mask; | |
47 | ||
48 | return 0; | |
49 | } | |
50 | ||
51 | static int max9877_set_reg(struct snd_kcontrol *kcontrol, | |
52 | struct snd_ctl_elem_value *ucontrol) | |
53 | { | |
54 | struct soc_mixer_control *mc = | |
55 | (struct soc_mixer_control *)kcontrol->private_value; | |
56 | int reg = mc->reg; | |
57 | int reg2 = mc->rreg; | |
58 | int shift = mc->shift; | |
59 | int mask = mc->max; | |
60 | int change = 1; | |
61 | int change2 = 1; | |
62 | int ret = 0; | |
63 | ||
64 | if (((max9877_regs[reg] >> shift) & mask) == | |
65 | ucontrol->value.integer.value[0]) | |
66 | change = 0; | |
67 | ||
68 | if (reg2) | |
69 | if (((max9877_regs[reg2] >> shift) & mask) == | |
70 | ucontrol->value.integer.value[1]) | |
71 | change2 = 0; | |
72 | ||
73 | if (change) { | |
74 | max9877_regs[reg] &= ~(mask << shift); | |
75 | max9877_regs[reg] |= ucontrol->value.integer.value[0] << shift; | |
76 | ret = change; | |
77 | } | |
78 | ||
79 | if (reg2 && change2) { | |
80 | max9877_regs[reg2] &= ~(mask << shift); | |
81 | max9877_regs[reg2] |= ucontrol->value.integer.value[1] << shift; | |
82 | ret = change2; | |
83 | } | |
84 | ||
85 | if (ret) | |
86 | max9877_write_regs(); | |
87 | ||
88 | return ret; | |
89 | } | |
90 | ||
91 | static int max9877_get_out_mode(struct snd_kcontrol *kcontrol, | |
92 | struct snd_ctl_elem_value *ucontrol) | |
93 | { | |
94 | u8 value = max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK; | |
95 | ||
96 | if (value) | |
97 | value -= 1; | |
98 | ||
99 | ucontrol->value.integer.value[0] = value; | |
100 | return 0; | |
101 | } | |
102 | ||
103 | static int max9877_set_out_mode(struct snd_kcontrol *kcontrol, | |
104 | struct snd_ctl_elem_value *ucontrol) | |
105 | { | |
106 | u8 value = ucontrol->value.integer.value[0]; | |
107 | ||
108 | if (value) | |
109 | value += 1; | |
110 | ||
111 | if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK) == value) | |
112 | return 0; | |
113 | ||
114 | max9877_regs[MAX9877_OUTPUT_MODE] |= value; | |
115 | max9877_write_regs(); | |
116 | return 1; | |
117 | } | |
118 | ||
119 | static int max9877_get_osc_mode(struct snd_kcontrol *kcontrol, | |
120 | struct snd_ctl_elem_value *ucontrol) | |
121 | { | |
122 | u8 value = (max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK); | |
123 | ||
124 | value = value >> MAX9877_OSC_OFFSET; | |
125 | ||
126 | ucontrol->value.integer.value[0] = value; | |
127 | return 0; | |
128 | } | |
129 | ||
130 | static int max9877_set_osc_mode(struct snd_kcontrol *kcontrol, | |
131 | struct snd_ctl_elem_value *ucontrol) | |
132 | { | |
133 | u8 value = ucontrol->value.integer.value[0]; | |
134 | ||
135 | value = value << MAX9877_OSC_OFFSET; | |
136 | if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK) == value) | |
137 | return 0; | |
138 | ||
139 | max9877_regs[MAX9877_OUTPUT_MODE] |= value; | |
140 | max9877_write_regs(); | |
141 | return 1; | |
142 | } | |
143 | ||
144 | static const unsigned int max9877_pgain_tlv[] = { | |
145 | TLV_DB_RANGE_HEAD(2), | |
146 | 0, 1, TLV_DB_SCALE_ITEM(0, 900, 0), | |
147 | 2, 2, TLV_DB_SCALE_ITEM(2000, 0, 0), | |
148 | }; | |
149 | ||
150 | static const unsigned int max9877_output_tlv[] = { | |
151 | TLV_DB_RANGE_HEAD(4), | |
152 | 0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1), | |
153 | 8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0), | |
154 | 16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0), | |
155 | 24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0), | |
156 | }; | |
157 | ||
158 | static const char *max9877_out_mode[] = { | |
159 | "INA -> SPK", | |
160 | "INA -> HP", | |
161 | "INA -> SPK and HP", | |
162 | "INB -> SPK", | |
163 | "INB -> HP", | |
164 | "INB -> SPK and HP", | |
165 | "INA + INB -> SPK", | |
166 | "INA + INB -> HP", | |
167 | "INA + INB -> SPK and HP", | |
168 | }; | |
169 | ||
170 | static const char *max9877_osc_mode[] = { | |
171 | "1176KHz", | |
172 | "1100KHz", | |
173 | "700KHz", | |
174 | }; | |
175 | ||
176 | static const struct soc_enum max9877_enum[] = { | |
177 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_out_mode), max9877_out_mode), | |
178 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_osc_mode), max9877_osc_mode), | |
179 | }; | |
180 | ||
181 | static const struct snd_kcontrol_new max9877_controls[] = { | |
182 | SOC_SINGLE_EXT_TLV("MAX9877 PGAINA Playback Volume", | |
183 | MAX9877_INPUT_MODE, 0, 2, 0, | |
184 | max9877_get_reg, max9877_set_reg, max9877_pgain_tlv), | |
185 | SOC_SINGLE_EXT_TLV("MAX9877 PGAINB Playback Volume", | |
186 | MAX9877_INPUT_MODE, 2, 2, 0, | |
187 | max9877_get_reg, max9877_set_reg, max9877_pgain_tlv), | |
188 | SOC_SINGLE_EXT_TLV("MAX9877 Amp Speaker Playback Volume", | |
189 | MAX9877_SPK_VOLUME, 0, 31, 0, | |
190 | max9877_get_reg, max9877_set_reg, max9877_output_tlv), | |
191 | SOC_DOUBLE_R_EXT_TLV("MAX9877 Amp HP Playback Volume", | |
192 | MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0, | |
193 | max9877_get_reg, max9877_set_reg, max9877_output_tlv), | |
194 | SOC_SINGLE_EXT("MAX9877 INB Stereo Switch", | |
195 | MAX9877_INPUT_MODE, 4, 1, 1, | |
196 | max9877_get_reg, max9877_set_reg), | |
197 | SOC_SINGLE_EXT("MAX9877 INA Stereo Switch", | |
198 | MAX9877_INPUT_MODE, 5, 1, 1, | |
199 | max9877_get_reg, max9877_set_reg), | |
200 | SOC_SINGLE_EXT("MAX9877 Zero-crossing detection Switch", | |
201 | MAX9877_INPUT_MODE, 6, 1, 0, | |
202 | max9877_get_reg, max9877_set_reg), | |
203 | SOC_SINGLE_EXT("MAX9877 Bypass Mode Switch", | |
204 | MAX9877_OUTPUT_MODE, 6, 1, 0, | |
205 | max9877_get_reg, max9877_set_reg), | |
206 | SOC_SINGLE_EXT("MAX9877 Shutdown Mode Switch", | |
207 | MAX9877_OUTPUT_MODE, 7, 1, 1, | |
208 | max9877_get_reg, max9877_set_reg), | |
209 | SOC_ENUM_EXT("MAX9877 Output Mode", max9877_enum[0], | |
210 | max9877_get_out_mode, max9877_set_out_mode), | |
211 | SOC_ENUM_EXT("MAX9877 Oscillator Mode", max9877_enum[1], | |
212 | max9877_get_osc_mode, max9877_set_osc_mode), | |
213 | }; | |
214 | ||
215 | /* This function is called from ASoC machine driver */ | |
216 | int max9877_add_controls(struct snd_soc_codec *codec) | |
217 | { | |
218 | return snd_soc_add_controls(codec, max9877_controls, | |
219 | ARRAY_SIZE(max9877_controls)); | |
220 | } | |
221 | EXPORT_SYMBOL_GPL(max9877_add_controls); | |
222 | ||
223 | static int __devinit max9877_i2c_probe(struct i2c_client *client, | |
224 | const struct i2c_device_id *id) | |
225 | { | |
226 | i2c = client; | |
227 | ||
228 | max9877_write_regs(); | |
229 | ||
230 | return 0; | |
231 | } | |
232 | ||
233 | static __devexit int max9877_i2c_remove(struct i2c_client *client) | |
234 | { | |
235 | i2c = NULL; | |
236 | ||
237 | return 0; | |
238 | } | |
239 | ||
240 | static const struct i2c_device_id max9877_i2c_id[] = { | |
241 | { "max9877", 0 }, | |
242 | { } | |
243 | }; | |
244 | MODULE_DEVICE_TABLE(i2c, max9877_i2c_id); | |
245 | ||
246 | static struct i2c_driver max9877_i2c_driver = { | |
247 | .driver = { | |
248 | .name = "max9877", | |
249 | .owner = THIS_MODULE, | |
250 | }, | |
251 | .probe = max9877_i2c_probe, | |
252 | .remove = __devexit_p(max9877_i2c_remove), | |
253 | .id_table = max9877_i2c_id, | |
254 | }; | |
255 | ||
256 | static int __init max9877_init(void) | |
257 | { | |
258 | return i2c_add_driver(&max9877_i2c_driver); | |
259 | } | |
260 | module_init(max9877_init); | |
261 | ||
262 | static void __exit max9877_exit(void) | |
263 | { | |
264 | i2c_del_driver(&max9877_i2c_driver); | |
265 | } | |
266 | module_exit(max9877_exit); | |
267 | ||
268 | MODULE_DESCRIPTION("ASoC MAX9877 amp driver"); | |
269 | MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); | |
270 | MODULE_LICENSE("GPL"); |