diff options
-rw-r--r-- | sound/soc/codecs/Kconfig | 5 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 6 | ||||
-rw-r--r-- | sound/soc/codecs/max9877.c | 270 | ||||
-rw-r--r-- | sound/soc/codecs/max9877.h | 37 |
4 files changed, 318 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b674d3a9f785..4219a41d3867 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig | |||
@@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS | |||
17 | select SND_SOC_AK4104 if SPI_MASTER | 17 | select SND_SOC_AK4104 if SPI_MASTER |
18 | select SND_SOC_AK4535 if I2C | 18 | select SND_SOC_AK4535 if I2C |
19 | select SND_SOC_CS4270 if I2C | 19 | select SND_SOC_CS4270 if I2C |
20 | select SND_SOC_MAX9877 if I2C | ||
20 | select SND_SOC_PCM3008 | 21 | select SND_SOC_PCM3008 |
21 | select SND_SOC_SPDIF | 22 | select SND_SOC_SPDIF |
22 | select SND_SOC_SSM2602 if I2C | 23 | select SND_SOC_SSM2602 if I2C |
@@ -188,3 +189,7 @@ config SND_SOC_WM9712 | |||
188 | 189 | ||
189 | config SND_SOC_WM9713 | 190 | config SND_SOC_WM9713 |
190 | tristate | 191 | tristate |
192 | |||
193 | # Amp | ||
194 | config SND_SOC_MAX9877 | ||
195 | tristate | ||
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 301eb08ea5cb..cf111c978223 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile | |||
@@ -38,6 +38,9 @@ snd-soc-wm9705-objs := wm9705.o | |||
38 | snd-soc-wm9712-objs := wm9712.o | 38 | snd-soc-wm9712-objs := wm9712.o |
39 | snd-soc-wm9713-objs := wm9713.o | 39 | snd-soc-wm9713-objs := wm9713.o |
40 | 40 | ||
41 | # Amp | ||
42 | snd-soc-max9877-objs := max9877.o | ||
43 | |||
41 | obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o | 44 | obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o |
42 | obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o | 45 | obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o |
43 | obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o | 46 | obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o |
@@ -77,3 +80,6 @@ obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o | |||
77 | obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o | 80 | obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o |
78 | obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o | 81 | obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o |
79 | obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o | 82 | obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o |
83 | |||
84 | # Amp | ||
85 | obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o | ||
diff --git a/sound/soc/codecs/max9877.c b/sound/soc/codecs/max9877.c new file mode 100644 index 000000000000..7df9a7c0208b --- /dev/null +++ b/sound/soc/codecs/max9877.c | |||
@@ -0,0 +1,270 @@ | |||
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"); | ||
diff --git a/sound/soc/codecs/max9877.h b/sound/soc/codecs/max9877.h new file mode 100644 index 000000000000..6da72290ac58 --- /dev/null +++ b/sound/soc/codecs/max9877.h | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * max9877.h -- 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 | #ifndef _MAX9877_H | ||
15 | #define _MAX9877_H | ||
16 | |||
17 | #define MAX9877_INPUT_MODE 0x00 | ||
18 | #define MAX9877_SPK_VOLUME 0x01 | ||
19 | #define MAX9877_HPL_VOLUME 0x02 | ||
20 | #define MAX9877_HPR_VOLUME 0x03 | ||
21 | #define MAX9877_OUTPUT_MODE 0x04 | ||
22 | |||
23 | /* MAX9877_INPUT_MODE */ | ||
24 | #define MAX9877_INB (1 << 4) | ||
25 | #define MAX9877_INA (1 << 5) | ||
26 | #define MAX9877_ZCD (1 << 6) | ||
27 | |||
28 | /* MAX9877_OUTPUT_MODE */ | ||
29 | #define MAX9877_OUTMODE_MASK (15 << 0) | ||
30 | #define MAX9877_OSC_MASK (3 << 4) | ||
31 | #define MAX9877_OSC_OFFSET 4 | ||
32 | #define MAX9877_BYPASS (1 << 6) | ||
33 | #define MAX9877_SHDN (1 << 7) | ||
34 | |||
35 | extern int max9877_add_controls(struct snd_soc_codec *codec); | ||
36 | |||
37 | #endif | ||