diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/media/video/tegra/soc380.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'drivers/media/video/tegra/soc380.c')
-rw-r--r-- | drivers/media/video/tegra/soc380.c | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/soc380.c b/drivers/media/video/tegra/soc380.c new file mode 100644 index 00000000000..7f2c1361466 --- /dev/null +++ b/drivers/media/video/tegra/soc380.c | |||
@@ -0,0 +1,473 @@ | |||
1 | /* | ||
2 | * soc380.c - soc380 sensor driver | ||
3 | * | ||
4 | * Copyright (c) 2011, NVIDIA, All Rights Reserved. | ||
5 | * | ||
6 | * Contributors: | ||
7 | * Abhinav Sinha <absinha@nvidia.com> | ||
8 | * | ||
9 | * Leverage OV2710.c | ||
10 | * | ||
11 | * This file is licensed under the terms of the GNU General Public License | ||
12 | * version 2. This program is licensed "as is" without any warranty of any | ||
13 | * kind, whether express or implied. | ||
14 | */ | ||
15 | |||
16 | /** | ||
17 | * SetMode Sequence for 640x480. Phase 0. Sensor Dependent. | ||
18 | * This sequence should put sensor in streaming mode for 640x480 | ||
19 | * This is usually given by the FAE or the sensor vendor. | ||
20 | */ | ||
21 | |||
22 | #include <linux/delay.h> | ||
23 | #include <linux/fs.h> | ||
24 | #include <linux/i2c.h> | ||
25 | #include <linux/miscdevice.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/uaccess.h> | ||
28 | #include <media/soc380.h> | ||
29 | |||
30 | struct soc380_reg { | ||
31 | u16 addr; | ||
32 | u16 val; | ||
33 | }; | ||
34 | |||
35 | struct soc380_info { | ||
36 | int mode; | ||
37 | struct i2c_client *i2c_client; | ||
38 | struct soc380_platform_data *pdata; | ||
39 | }; | ||
40 | |||
41 | #define SOC380_TABLE_WAIT_MS 0 | ||
42 | #define SOC380_TABLE_END 1 | ||
43 | #define SOC380_MAX_RETRIES 3 | ||
44 | |||
45 | static struct soc380_reg mode_640x480[] = { | ||
46 | {0x001A, 0x0011}, | ||
47 | |||
48 | {SOC380_TABLE_WAIT_MS, 1}, | ||
49 | |||
50 | {0x001A, 0x0010}, | ||
51 | |||
52 | {SOC380_TABLE_WAIT_MS, 1}, | ||
53 | |||
54 | {0x0018, 0x4028}, | ||
55 | {0x001A, 0x0210}, | ||
56 | {0x0010, 0x021c}, | ||
57 | {0x0012, 0x0000}, | ||
58 | {0x0014, 0x244B}, | ||
59 | |||
60 | {SOC380_TABLE_WAIT_MS, 10}, | ||
61 | |||
62 | {0x0014, 0x304B}, | ||
63 | |||
64 | {SOC380_TABLE_WAIT_MS, 50}, | ||
65 | |||
66 | {0x0014, 0xB04A}, | ||
67 | |||
68 | {0x098C, 0x2703}, | ||
69 | {0x0990, 0x0280}, | ||
70 | {0x098C, 0x2705}, | ||
71 | {0x0990, 0x01E0}, | ||
72 | {0x098C, 0x2707}, | ||
73 | {0x0990, 0x0280}, | ||
74 | {0x098C, 0x2709}, | ||
75 | {0x0990, 0x01E0}, | ||
76 | {0x098C, 0x270D}, | ||
77 | {0x0990, 0x0000}, | ||
78 | {0x098C, 0x270F}, | ||
79 | {0x0990, 0x0000}, | ||
80 | {0x098C, 0x2711}, | ||
81 | {0x0990, 0x01E7}, | ||
82 | {0x098C, 0x2713}, | ||
83 | {0x0990, 0x0287}, | ||
84 | {0x098C, 0x2715}, | ||
85 | {0x0990, 0x0001}, | ||
86 | {0x098C, 0x2717}, | ||
87 | {0x0990, 0x0026}, | ||
88 | {0x098C, 0x2719}, | ||
89 | {0x0990, 0x001A}, | ||
90 | {0x098C, 0x271B}, | ||
91 | {0x0990, 0x006B}, | ||
92 | {0x098C, 0x271D}, | ||
93 | {0x0990, 0x006B}, | ||
94 | {0x098C, 0x271F}, | ||
95 | {0x0990, 0x022A}, | ||
96 | {0x098C, 0x2721}, | ||
97 | {0x0990, 0x034A}, | ||
98 | {0x098C, 0x2723}, | ||
99 | {0x0990, 0x0000}, | ||
100 | {0x098C, 0x2725}, | ||
101 | {0x0990, 0x0000}, | ||
102 | {0x098C, 0x2727}, | ||
103 | {0x0990, 0x01E7}, | ||
104 | {0x098C, 0x2729}, | ||
105 | {0x0990, 0x0287}, | ||
106 | {0x098C, 0x272B}, | ||
107 | {0x0990, 0x0001}, | ||
108 | {0x098C, 0x272D}, | ||
109 | {0x0990, 0x0026}, | ||
110 | {0x098C, 0x272F}, | ||
111 | {0x0990, 0x001A}, | ||
112 | {0x098C, 0x2731}, | ||
113 | {0x0990, 0x006B}, | ||
114 | {0x098C, 0x2733}, | ||
115 | {0x0990, 0x006B}, | ||
116 | {0x098C, 0x2735}, | ||
117 | {0x0990, 0x022A}, | ||
118 | {0x098C, 0x2737}, | ||
119 | {0x0990, 0x034A}, | ||
120 | {0x098C, 0x2739}, | ||
121 | {0x0990, 0x0000}, | ||
122 | {0x098C, 0x273B}, | ||
123 | {0x0990, 0x027F}, | ||
124 | {0x098C, 0x273D}, | ||
125 | {0x0990, 0x0000}, | ||
126 | {0x098C, 0x273F}, | ||
127 | {0x0990, 0x01DF}, | ||
128 | {0x098C, 0x2747}, | ||
129 | {0x0990, 0x0000}, | ||
130 | {0x098C, 0x2749}, | ||
131 | {0x0990, 0x027F}, | ||
132 | {0x098C, 0x274B}, | ||
133 | {0x0990, 0x0000}, | ||
134 | {0x098C, 0x274D}, | ||
135 | {0x0990, 0x01DF}, | ||
136 | {0x098C, 0x222D}, | ||
137 | {0x0990, 0x008B}, | ||
138 | {0x098C, 0xA408}, | ||
139 | {0x0990, 0x0021}, | ||
140 | {0x098C, 0xA409}, | ||
141 | {0x0990, 0x0023}, | ||
142 | {0x098C, 0xA40A}, | ||
143 | {0x0990, 0x0028}, | ||
144 | {0x098C, 0xA40B}, | ||
145 | {0x0990, 0x002A}, | ||
146 | {0x098C, 0x2411}, | ||
147 | {0x0990, 0x008B}, | ||
148 | {0x098C, 0x2413}, | ||
149 | {0x0990, 0x00A6}, | ||
150 | {0x098C, 0x2415}, | ||
151 | {0x0990, 0x008B}, | ||
152 | {0x098C, 0x2417}, | ||
153 | {0x0990, 0x00A6}, | ||
154 | {0x098C, 0xA404}, | ||
155 | {0x0990, 0x0010}, | ||
156 | {0x098C, 0xA40D}, | ||
157 | {0x0990, 0x0002}, | ||
158 | {0x098C, 0xA40E}, | ||
159 | {0x0990, 0x0003}, | ||
160 | {0x098C, 0xA410}, | ||
161 | {0x0990, 0x000A}, | ||
162 | {0x098C, 0xA215}, | ||
163 | {0x0990, 0x0003}, | ||
164 | {0x098C, 0xA20C}, | ||
165 | {0x0990, 0x0003}, | ||
166 | |||
167 | {0x098C, 0xA103}, | ||
168 | {0x0990, 0x0006}, | ||
169 | {SOC380_TABLE_WAIT_MS, 100}, | ||
170 | |||
171 | {0x098C, 0xA103}, | ||
172 | {0x0990, 0x0005}, | ||
173 | {SOC380_TABLE_WAIT_MS, 50}, | ||
174 | |||
175 | {SOC380_TABLE_END, 0x0000} | ||
176 | }; | ||
177 | |||
178 | enum { | ||
179 | SOC380_MODE_680x480, | ||
180 | }; | ||
181 | |||
182 | static struct soc380_reg *mode_table[] = { | ||
183 | [SOC380_MODE_680x480] = mode_640x480, | ||
184 | }; | ||
185 | |||
186 | static int soc380_read_reg(struct i2c_client *client, u16 addr, u16 *val) | ||
187 | { | ||
188 | int err; | ||
189 | struct i2c_msg msg[2]; | ||
190 | unsigned char data[4]; | ||
191 | |||
192 | if (!client->adapter) | ||
193 | return -ENODEV; | ||
194 | |||
195 | msg[0].addr = client->addr; | ||
196 | msg[0].flags = 0; | ||
197 | msg[0].len = 2; | ||
198 | msg[0].buf = data; | ||
199 | |||
200 | /* high byte goes out first */ | ||
201 | data[0] = (u8) (addr >> 8); | ||
202 | data[1] = (u8) (addr & 0xff); | ||
203 | |||
204 | msg[1].addr = client->addr; | ||
205 | msg[1].flags = I2C_M_RD; | ||
206 | msg[1].len = 2; | ||
207 | msg[1].buf = data + 2; | ||
208 | |||
209 | err = i2c_transfer(client->adapter, msg, 2); | ||
210 | |||
211 | if (err != 2) | ||
212 | return -EINVAL; | ||
213 | |||
214 | *val = data[2] << 8 | data[3]; | ||
215 | |||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | static int soc380_write_reg(struct i2c_client *client, u16 addr, u16 val) | ||
220 | { | ||
221 | int err; | ||
222 | struct i2c_msg msg; | ||
223 | unsigned char data[4]; | ||
224 | int retry = 0; | ||
225 | |||
226 | if (!client->adapter) | ||
227 | return -ENODEV; | ||
228 | |||
229 | data[0] = (u8) (addr >> 8); | ||
230 | data[1] = (u8) (addr & 0xff); | ||
231 | data[2] = (u8) (val >> 8); | ||
232 | data[3] = (u8) (val & 0xff); | ||
233 | |||
234 | msg.addr = client->addr; | ||
235 | msg.flags = 0; | ||
236 | msg.len = 4; | ||
237 | msg.buf = data; | ||
238 | |||
239 | do { | ||
240 | err = i2c_transfer(client->adapter, &msg, 1); | ||
241 | if (err == 1) | ||
242 | return 0; | ||
243 | retry++; | ||
244 | pr_err("soc380: i2c transfer failed, retrying %x %x\n", | ||
245 | addr, val); | ||
246 | msleep(3); | ||
247 | } while (retry <= SOC380_MAX_RETRIES); | ||
248 | |||
249 | return err; | ||
250 | } | ||
251 | |||
252 | static int soc380_write_table(struct i2c_client *client, | ||
253 | const struct soc380_reg table[], | ||
254 | const struct soc380_reg override_list[], | ||
255 | int num_override_regs) | ||
256 | { | ||
257 | int err; | ||
258 | const struct soc380_reg *next; | ||
259 | int i; | ||
260 | u16 val; | ||
261 | |||
262 | for (next = table; next->addr != SOC380_TABLE_END; next++) { | ||
263 | if (next->addr == SOC380_TABLE_WAIT_MS) { | ||
264 | msleep(next->val); | ||
265 | continue; | ||
266 | } | ||
267 | |||
268 | val = next->val; | ||
269 | |||
270 | /* When an override list is passed in, replace the reg */ | ||
271 | /* value to write if the reg is in the list */ | ||
272 | if (override_list) { | ||
273 | for (i = 0; i < num_override_regs; i++) { | ||
274 | if (next->addr == override_list[i].addr) { | ||
275 | val = override_list[i].val; | ||
276 | break; | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | err = soc380_write_reg(client, next->addr, val); | ||
282 | if (err) | ||
283 | return err; | ||
284 | } | ||
285 | return 0; | ||
286 | } | ||
287 | |||
288 | static int soc380_set_mode(struct soc380_info *info, struct soc380_mode *mode) | ||
289 | { | ||
290 | int sensor_mode; | ||
291 | int err; | ||
292 | |||
293 | pr_info("%s: xres %u yres %u\n", __func__, mode->xres, mode->yres); | ||
294 | if (mode->xres == 640 && mode->yres == 480) | ||
295 | sensor_mode = SOC380_MODE_680x480; | ||
296 | else { | ||
297 | pr_err("%s: invalid resolution supplied to set mode %d %d\n", | ||
298 | __func__, mode->xres, mode->yres); | ||
299 | return -EINVAL; | ||
300 | } | ||
301 | |||
302 | err = soc380_write_table(info->i2c_client, mode_table[sensor_mode], | ||
303 | NULL, 0); | ||
304 | if (err) | ||
305 | return err; | ||
306 | |||
307 | info->mode = sensor_mode; | ||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | static int soc380_get_status(struct soc380_info *info, | ||
312 | struct soc380_status *dev_status) | ||
313 | { | ||
314 | int err; | ||
315 | |||
316 | err = soc380_write_reg(info->i2c_client, 0x98C, dev_status->data); | ||
317 | if (err) | ||
318 | return err; | ||
319 | |||
320 | err = soc380_read_reg(info->i2c_client, 0x0990, | ||
321 | (u16 *) &dev_status->status); | ||
322 | if (err) | ||
323 | return err; | ||
324 | |||
325 | return err; | ||
326 | } | ||
327 | |||
328 | static long soc380_ioctl(struct file *file, | ||
329 | unsigned int cmd, unsigned long arg) | ||
330 | { | ||
331 | int err; | ||
332 | struct soc380_info *info = file->private_data; | ||
333 | |||
334 | switch (cmd) { | ||
335 | case SOC380_IOCTL_SET_MODE: | ||
336 | { | ||
337 | struct soc380_mode mode; | ||
338 | if (copy_from_user(&mode, | ||
339 | (const void __user *)arg, | ||
340 | sizeof(struct soc380_mode))) { | ||
341 | return -EFAULT; | ||
342 | } | ||
343 | |||
344 | return soc380_set_mode(info, &mode); | ||
345 | } | ||
346 | case SOC380_IOCTL_GET_STATUS: | ||
347 | { | ||
348 | struct soc380_status dev_status; | ||
349 | if (copy_from_user(&dev_status, | ||
350 | (const void __user *)arg, | ||
351 | sizeof(struct soc380_status))) { | ||
352 | return -EFAULT; | ||
353 | } | ||
354 | |||
355 | err = soc380_get_status(info, &dev_status); | ||
356 | if (err) | ||
357 | return err; | ||
358 | if (copy_to_user((void __user *)arg, &dev_status, | ||
359 | sizeof(struct soc380_status))) { | ||
360 | return -EFAULT; | ||
361 | } | ||
362 | return 0; | ||
363 | } | ||
364 | default: | ||
365 | return -EINVAL; | ||
366 | } | ||
367 | return 0; | ||
368 | } | ||
369 | |||
370 | static struct soc380_info *info; | ||
371 | |||
372 | static int soc380_open(struct inode *inode, struct file *file) | ||
373 | { | ||
374 | struct soc380_status dev_status; | ||
375 | int err; | ||
376 | |||
377 | file->private_data = info; | ||
378 | if (info->pdata && info->pdata->power_on) | ||
379 | info->pdata->power_on(); | ||
380 | |||
381 | dev_status.data = 0; | ||
382 | dev_status.status = 0; | ||
383 | err = soc380_get_status(info, &dev_status); | ||
384 | return err; | ||
385 | } | ||
386 | |||
387 | int soc380_release(struct inode *inode, struct file *file) | ||
388 | { | ||
389 | if (info->pdata && info->pdata->power_off) | ||
390 | info->pdata->power_off(); | ||
391 | file->private_data = NULL; | ||
392 | return 0; | ||
393 | } | ||
394 | |||
395 | static const struct file_operations soc380_fileops = { | ||
396 | .owner = THIS_MODULE, | ||
397 | .open = soc380_open, | ||
398 | .unlocked_ioctl = soc380_ioctl, | ||
399 | .release = soc380_release, | ||
400 | }; | ||
401 | |||
402 | static struct miscdevice soc380_device = { | ||
403 | .minor = MISC_DYNAMIC_MINOR, | ||
404 | .name = "soc380", | ||
405 | .fops = &soc380_fileops, | ||
406 | }; | ||
407 | |||
408 | static int soc380_probe(struct i2c_client *client, | ||
409 | const struct i2c_device_id *id) | ||
410 | { | ||
411 | int err; | ||
412 | |||
413 | pr_info("soc380: probing sensor.\n"); | ||
414 | |||
415 | info = kzalloc(sizeof(struct soc380_info), GFP_KERNEL); | ||
416 | if (!info) { | ||
417 | pr_err("soc380: Unable to allocate memory!\n"); | ||
418 | return -ENOMEM; | ||
419 | } | ||
420 | |||
421 | err = misc_register(&soc380_device); | ||
422 | if (err) { | ||
423 | pr_err("soc380: Unable to register misc device!\n"); | ||
424 | kfree(info); | ||
425 | return err; | ||
426 | } | ||
427 | |||
428 | info->pdata = client->dev.platform_data; | ||
429 | info->i2c_client = client; | ||
430 | |||
431 | i2c_set_clientdata(client, info); | ||
432 | return 0; | ||
433 | } | ||
434 | |||
435 | static int soc380_remove(struct i2c_client *client) | ||
436 | { | ||
437 | struct soc380_info *info; | ||
438 | info = i2c_get_clientdata(client); | ||
439 | misc_deregister(&soc380_device); | ||
440 | kfree(info); | ||
441 | return 0; | ||
442 | } | ||
443 | |||
444 | static const struct i2c_device_id soc380_id[] = { | ||
445 | { "soc380", 0 }, | ||
446 | { }, | ||
447 | }; | ||
448 | |||
449 | MODULE_DEVICE_TABLE(i2c, soc380_id); | ||
450 | |||
451 | static struct i2c_driver soc380_i2c_driver = { | ||
452 | .driver = { | ||
453 | .name = "soc380", | ||
454 | .owner = THIS_MODULE, | ||
455 | }, | ||
456 | .probe = soc380_probe, | ||
457 | .remove = soc380_remove, | ||
458 | .id_table = soc380_id, | ||
459 | }; | ||
460 | |||
461 | static int __init soc380_init(void) | ||
462 | { | ||
463 | pr_info("soc380 sensor driver loading\n"); | ||
464 | return i2c_add_driver(&soc380_i2c_driver); | ||
465 | } | ||
466 | |||
467 | static void __exit soc380_exit(void) | ||
468 | { | ||
469 | i2c_del_driver(&soc380_i2c_driver); | ||
470 | } | ||
471 | |||
472 | module_init(soc380_init); | ||
473 | module_exit(soc380_exit); | ||