diff options
Diffstat (limited to 'drivers/media/video/wm8739.c')
-rw-r--r-- | drivers/media/video/wm8739.c | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/drivers/media/video/wm8739.c b/drivers/media/video/wm8739.c new file mode 100644 index 000000000000..a9b59c35cd67 --- /dev/null +++ b/drivers/media/video/wm8739.c | |||
@@ -0,0 +1,355 @@ | |||
1 | /* | ||
2 | * wm8739 | ||
3 | * | ||
4 | * Copyright (C) 2005 T. Adachi <tadachi@tadachi-net.com> | ||
5 | * | ||
6 | * Copyright (C) 2005 Hans Verkuil <hverkuil@xs4all.nl> | ||
7 | * - Cleanup | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
22 | */ | ||
23 | |||
24 | #include <linux/module.h> | ||
25 | #include <linux/types.h> | ||
26 | #include <linux/ioctl.h> | ||
27 | #include <asm/uaccess.h> | ||
28 | #include <linux/i2c.h> | ||
29 | #include <linux/i2c-id.h> | ||
30 | #include <linux/videodev.h> | ||
31 | #include <media/v4l2-common.h> | ||
32 | |||
33 | MODULE_DESCRIPTION("wm8739 driver"); | ||
34 | MODULE_AUTHOR("T. Adachi, Hans Verkuil"); | ||
35 | MODULE_LICENSE("GPL"); | ||
36 | |||
37 | static int debug = 0; | ||
38 | static unsigned short normal_i2c[] = { 0x34 >> 1, 0x36 >> 1, I2C_CLIENT_END }; | ||
39 | |||
40 | module_param(debug, int, 0644); | ||
41 | |||
42 | MODULE_PARM_DESC(debug, "Debug level (0-1)"); | ||
43 | |||
44 | |||
45 | I2C_CLIENT_INSMOD; | ||
46 | |||
47 | /* ------------------------------------------------------------------------ */ | ||
48 | |||
49 | enum { | ||
50 | R0 = 0, R1, | ||
51 | R5 = 5, R6, R7, R8, R9, R15 = 15, | ||
52 | TOT_REGS | ||
53 | }; | ||
54 | |||
55 | struct wm8739_state { | ||
56 | u32 clock_freq; | ||
57 | u8 muted; | ||
58 | u16 volume; | ||
59 | u16 balance; | ||
60 | u8 vol_l; /* +12dB to -34.5dB 1.5dB step (5bit) def:0dB */ | ||
61 | u8 vol_r; /* +12dB to -34.5dB 1.5dB step (5bit) def:0dB */ | ||
62 | }; | ||
63 | |||
64 | /* ------------------------------------------------------------------------ */ | ||
65 | |||
66 | static int wm8739_write(struct i2c_client *client, int reg, u16 val) | ||
67 | { | ||
68 | int i; | ||
69 | |||
70 | if (reg < 0 || reg >= TOT_REGS) { | ||
71 | v4l_err(client, "Invalid register R%d\n", reg); | ||
72 | return -1; | ||
73 | } | ||
74 | |||
75 | v4l_dbg(1, debug, client, "write: %02x %02x\n", reg, val); | ||
76 | |||
77 | for (i = 0; i < 3; i++) { | ||
78 | if (i2c_smbus_write_byte_data(client, (reg << 1) | | ||
79 | (val >> 8), val & 0xff) == 0) { | ||
80 | return 0; | ||
81 | } | ||
82 | } | ||
83 | v4l_err(client, "I2C: cannot write %03x to register R%d\n", val, reg); | ||
84 | return -1; | ||
85 | } | ||
86 | |||
87 | /* write regs to set audio volume etc */ | ||
88 | static void wm8739_set_audio(struct i2c_client *client) | ||
89 | { | ||
90 | struct wm8739_state *state = i2c_get_clientdata(client); | ||
91 | u16 mute = state->muted ? 0x80 : 0; | ||
92 | |||
93 | /* Volume setting: bits 0-4, 0x1f = 12 dB, 0x00 = -34.5 dB | ||
94 | * Default setting: 0x17 = 0 dB | ||
95 | */ | ||
96 | wm8739_write(client, R0, (state->vol_l & 0x1f) | mute); | ||
97 | wm8739_write(client, R1, (state->vol_r & 0x1f) | mute); | ||
98 | } | ||
99 | |||
100 | static int wm8739_get_ctrl(struct i2c_client *client, struct v4l2_control *ctrl) | ||
101 | { | ||
102 | struct wm8739_state *state = i2c_get_clientdata(client); | ||
103 | |||
104 | switch (ctrl->id) { | ||
105 | case V4L2_CID_AUDIO_MUTE: | ||
106 | ctrl->value = state->muted; | ||
107 | break; | ||
108 | |||
109 | case V4L2_CID_AUDIO_VOLUME: | ||
110 | ctrl->value = state->volume; | ||
111 | break; | ||
112 | |||
113 | case V4L2_CID_AUDIO_BALANCE: | ||
114 | ctrl->value = state->balance; | ||
115 | break; | ||
116 | |||
117 | default: | ||
118 | return -EINVAL; | ||
119 | } | ||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | static int wm8739_set_ctrl(struct i2c_client *client, struct v4l2_control *ctrl) | ||
124 | { | ||
125 | struct wm8739_state *state = i2c_get_clientdata(client); | ||
126 | unsigned int work_l, work_r; | ||
127 | |||
128 | switch (ctrl->id) { | ||
129 | case V4L2_CID_AUDIO_MUTE: | ||
130 | state->muted = ctrl->value; | ||
131 | break; | ||
132 | |||
133 | case V4L2_CID_AUDIO_VOLUME: | ||
134 | state->volume = ctrl->value; | ||
135 | break; | ||
136 | |||
137 | case V4L2_CID_AUDIO_BALANCE: | ||
138 | state->balance = ctrl->value; | ||
139 | break; | ||
140 | |||
141 | default: | ||
142 | return -EINVAL; | ||
143 | } | ||
144 | |||
145 | /* normalize ( 65535 to 0 -> 31 to 0 (12dB to -34.5dB) ) */ | ||
146 | work_l = (min(65536 - state->balance, 32768) * state->volume) / 32768; | ||
147 | work_r = (min(state->balance, (u16)32768) * state->volume) / 32768; | ||
148 | |||
149 | state->vol_l = (long)work_l * 31 / 65535; | ||
150 | state->vol_r = (long)work_r * 31 / 65535; | ||
151 | |||
152 | /* set audio volume etc. */ | ||
153 | wm8739_set_audio(client); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | /* ------------------------------------------------------------------------ */ | ||
158 | |||
159 | static struct v4l2_queryctrl wm8739_qctrl[] = { | ||
160 | { | ||
161 | .id = V4L2_CID_AUDIO_VOLUME, | ||
162 | .name = "Volume", | ||
163 | .minimum = 0, | ||
164 | .maximum = 65535, | ||
165 | .step = 65535/100, | ||
166 | .default_value = 58880, | ||
167 | .flags = 0, | ||
168 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
169 | },{ | ||
170 | .id = V4L2_CID_AUDIO_MUTE, | ||
171 | .name = "Mute", | ||
172 | .minimum = 0, | ||
173 | .maximum = 1, | ||
174 | .step = 1, | ||
175 | .default_value = 1, | ||
176 | .flags = 0, | ||
177 | .type = V4L2_CTRL_TYPE_BOOLEAN, | ||
178 | },{ | ||
179 | .id = V4L2_CID_AUDIO_BALANCE, | ||
180 | .name = "Balance", | ||
181 | .minimum = 0, | ||
182 | .maximum = 65535, | ||
183 | .step = 65535/100, | ||
184 | .default_value = 32768, | ||
185 | .flags = 0, | ||
186 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
187 | } | ||
188 | }; | ||
189 | |||
190 | /* ------------------------------------------------------------------------ */ | ||
191 | |||
192 | static int wm8739_command(struct i2c_client *client, unsigned int cmd, void *arg) | ||
193 | { | ||
194 | struct wm8739_state *state = i2c_get_clientdata(client); | ||
195 | |||
196 | switch (cmd) { | ||
197 | case VIDIOC_INT_AUDIO_CLOCK_FREQ: | ||
198 | { | ||
199 | u32 audiofreq = *(u32 *)arg; | ||
200 | |||
201 | state->clock_freq = audiofreq; | ||
202 | wm8739_write(client, R9, 0x000); /* de-activate */ | ||
203 | switch (audiofreq) { | ||
204 | case 44100: | ||
205 | wm8739_write(client, R8, 0x020); /* 256fps, fs=44.1k */ | ||
206 | break; | ||
207 | case 48000: | ||
208 | wm8739_write(client, R8, 0x000); /* 256fps, fs=48k */ | ||
209 | break; | ||
210 | case 32000: | ||
211 | wm8739_write(client, R8, 0x018); /* 256fps, fs=32k */ | ||
212 | break; | ||
213 | default: | ||
214 | break; | ||
215 | } | ||
216 | wm8739_write(client, R9, 0x001); /* activate */ | ||
217 | break; | ||
218 | } | ||
219 | |||
220 | case VIDIOC_G_CTRL: | ||
221 | return wm8739_get_ctrl(client, arg); | ||
222 | |||
223 | case VIDIOC_S_CTRL: | ||
224 | return wm8739_set_ctrl(client, arg); | ||
225 | |||
226 | case VIDIOC_QUERYCTRL: | ||
227 | { | ||
228 | struct v4l2_queryctrl *qc = arg; | ||
229 | int i; | ||
230 | |||
231 | for (i = 0; i < ARRAY_SIZE(wm8739_qctrl); i++) | ||
232 | if (qc->id && qc->id == wm8739_qctrl[i].id) { | ||
233 | memcpy(qc, &wm8739_qctrl[i], sizeof(*qc)); | ||
234 | return 0; | ||
235 | } | ||
236 | return -EINVAL; | ||
237 | } | ||
238 | |||
239 | case VIDIOC_LOG_STATUS: | ||
240 | v4l_info(client, "Frequency: %u Hz\n", state->clock_freq); | ||
241 | v4l_info(client, "Volume L: %02x%s\n", state->vol_l & 0x1f, | ||
242 | state->muted ? " (muted)" : ""); | ||
243 | v4l_info(client, "Volume R: %02x%s\n", state->vol_r & 0x1f, | ||
244 | state->muted ? " (muted)" : ""); | ||
245 | break; | ||
246 | |||
247 | default: | ||
248 | return -EINVAL; | ||
249 | } | ||
250 | |||
251 | return 0; | ||
252 | } | ||
253 | |||
254 | /* ------------------------------------------------------------------------ */ | ||
255 | |||
256 | /* i2c implementation */ | ||
257 | |||
258 | static struct i2c_driver i2c_driver; | ||
259 | |||
260 | static int wm8739_attach(struct i2c_adapter *adapter, int address, int kind) | ||
261 | { | ||
262 | struct i2c_client *client; | ||
263 | struct wm8739_state *state; | ||
264 | |||
265 | /* Check if the adapter supports the needed features */ | ||
266 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | ||
267 | return 0; | ||
268 | |||
269 | client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); | ||
270 | if (client == NULL) | ||
271 | return -ENOMEM; | ||
272 | |||
273 | client->addr = address; | ||
274 | client->adapter = adapter; | ||
275 | client->driver = &i2c_driver; | ||
276 | snprintf(client->name, sizeof(client->name) - 1, "wm8739"); | ||
277 | |||
278 | v4l_info(client, "chip found @ 0x%x (%s)\n", address << 1, adapter->name); | ||
279 | |||
280 | state = kmalloc(sizeof(struct wm8739_state), GFP_KERNEL); | ||
281 | if (state == NULL) { | ||
282 | kfree(client); | ||
283 | return -ENOMEM; | ||
284 | } | ||
285 | state->vol_l = 0x17; /* 0dB */ | ||
286 | state->vol_r = 0x17; /* 0dB */ | ||
287 | state->muted = 0; | ||
288 | state->balance = 32768; | ||
289 | /* normalize (12dB(31) to -34.5dB(0) [0dB(23)] -> 65535 to 0) */ | ||
290 | state->volume = ((long)state->vol_l + 1) * 65535 / 31; | ||
291 | state->clock_freq = 48000; | ||
292 | i2c_set_clientdata(client, state); | ||
293 | |||
294 | /* initialize wm8739 */ | ||
295 | wm8739_write(client, R15, 0x00); /* reset */ | ||
296 | wm8739_write(client, R5, 0x000); /* filter setting, high path, offet clear */ | ||
297 | wm8739_write(client, R6, 0x000); /* ADC, OSC, Power Off mode Disable */ | ||
298 | wm8739_write(client, R7, 0x049); /* Digital Audio interface format */ | ||
299 | /* Enable Master mode */ | ||
300 | /* 24 bit, MSB first/left justified */ | ||
301 | wm8739_write(client, R8, 0x000); /* sampling control */ | ||
302 | /* normal, 256fs, 48KHz sampling rate */ | ||
303 | wm8739_write(client, R9, 0x001); /* activate */ | ||
304 | wm8739_set_audio(client); /* set volume/mute */ | ||
305 | |||
306 | i2c_attach_client(client); | ||
307 | |||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | static int wm8739_probe(struct i2c_adapter *adapter) | ||
312 | { | ||
313 | if (adapter->class & I2C_CLASS_TV_ANALOG) | ||
314 | return i2c_probe(adapter, &addr_data, wm8739_attach); | ||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static int wm8739_detach(struct i2c_client *client) | ||
319 | { | ||
320 | int err; | ||
321 | |||
322 | err = i2c_detach_client(client); | ||
323 | if (err) | ||
324 | return err; | ||
325 | |||
326 | kfree(client); | ||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | /* ----------------------------------------------------------------------- */ | ||
331 | |||
332 | /* i2c implementation */ | ||
333 | static struct i2c_driver i2c_driver = { | ||
334 | .driver = { | ||
335 | .name = "wm8739", | ||
336 | }, | ||
337 | .id = I2C_DRIVERID_WM8739, | ||
338 | .attach_adapter = wm8739_probe, | ||
339 | .detach_client = wm8739_detach, | ||
340 | .command = wm8739_command, | ||
341 | }; | ||
342 | |||
343 | |||
344 | static int __init wm8739_init_module(void) | ||
345 | { | ||
346 | return i2c_add_driver(&i2c_driver); | ||
347 | } | ||
348 | |||
349 | static void __exit wm8739_cleanup_module(void) | ||
350 | { | ||
351 | i2c_del_driver(&i2c_driver); | ||
352 | } | ||
353 | |||
354 | module_init(wm8739_init_module); | ||
355 | module_exit(wm8739_cleanup_module); | ||