Add doxygen comments to some functions in microdia-debugfs.c
[microdia.git] / sn9c20x.c
blob8b8c3c05ba97325f28c11c1e9749424de96bb12a
1 /**
2 * @file sn9c20x.c
3 * @author Dave Neuer
4 * @date 2008-03-02
6 * @brief Common functions and data for the Sonix SN9C20x webcam bridge chips.
8 * @note Copyright (C) Dave Neuer
10 * @par Licences
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include <linux/delay.h>
28 #include <linux/errno.h>
29 #include <linux/string.h>
30 #include "microdia.h"
31 #include "sn9c20x.h"
34 struct sn9c20x_win_size {
35 int hstart;
36 int hsize;
37 int vstart;
38 int vsize;
39 int scale;
40 } sn9c20x_win_sizes[] = {
42 .hstart = 0,
43 .hsize = 160,
44 .vstart = 0,
45 .vsize = 120,
46 .scale = SN9C20X_1_4_SCALE,
49 .hstart = 0,
50 .hsize = 320,
51 .vstart = 0,
52 .vsize = 240,
53 .scale = SN9C20X_1_2_SCALE,
56 .hstart = 0,
57 .hsize = 640,
58 .vstart = 0,
59 .vsize = 480,
60 .scale = SN9C20X_NO_SCALE,
64 /**
65 * @brief This function initializes the SN9C20x bridge, these are the bare minimum writes that must be done for the bridge to work correctly.
67 * @author Neekhil
69 * @param dev Pointer to the device
71 * @return Zero for success or error value
74 int sn9c20x_initialize(struct usb_microdia *dev)
76 int ret;
77 __u16 reg;
78 __u8 buf[9];
80 /* Could these be the bridge ADC controls, video stream seems jagged w/o this write */
81 reg = 0x1188;
82 buf[0] = 0x86;
83 ret = usb_microdia_control_write(dev, reg, buf, 1);
84 if (ret < 0)
85 goto err;
87 /* No video stream if we don't do this. Could be wake up Bridge from soft sleep/suspend? */
88 reg = 0x11b8;
89 buf[0] = 0x38;
90 ret = usb_microdia_control_write(dev, reg, buf, 1);
91 if (ret < 0)
92 goto err;
94 /* No video stream w/o this write */
95 reg = 0x1001;
96 buf[0] = 0x84;
97 ret = usb_microdia_control_write(dev, reg, buf, 1);
98 if (ret < 0)
99 goto err;
101 /* Some sort of Brightness control */
102 reg = 0x118b;
103 buf[0] = 0x0c;
104 ret = usb_microdia_control_write(dev, reg, buf, 1);
105 if (ret < 0)
106 goto err;
108 /* All 3 writes below are required else NO video stream */
109 reg = 0x10e0;
110 buf[0] = 0x4b;
111 ret = usb_microdia_control_write(dev, reg, buf, 1);
112 if (ret < 0)
113 goto err;
114 reg = 0x1180;
115 buf[0] = 0x01; buf[1] = 0x00; buf[2] = 0x01; buf[3] = 0x00; buf[4] = 0x28;
116 buf[5] = 0x3c;
117 ret = usb_microdia_control_write(dev, reg, buf, 6);
118 if (ret < 0)
119 goto err;
121 /* Some sort of brightness control */
122 reg = 0x11ba;
123 buf[0] = 0x0b;
124 ret = usb_microdia_control_write(dev, reg, buf, 1);
125 if (ret < 0)
126 goto err;
128 /* we get only green bands if we don't do this write */
129 reg = 0x1002;
130 buf[0] = 0x18;
131 ret = usb_microdia_control_write(dev, reg, buf, 1);
132 if (ret < 0)
133 goto err;
135 /* Bridge Auto-Exposure setting ? */
136 reg = 0x118a;
137 buf[0] = 0x04;
138 ret = usb_microdia_control_write(dev, reg, buf, 1);
139 if (ret < 0)
140 goto err;
142 /* Bridge Auto-Exposure setting ? */
143 reg = 0x0395;
144 buf[0] = 0x04;
145 ret = usb_microdia_control_write(dev, reg, buf, 1);
146 if (ret < 0)
147 goto err;
149 reg = 0x1061;
150 buf[0] = 0x03;
151 ret = usb_microdia_control_write(dev, reg, buf, 1);
152 if (ret < 0)
153 goto err;
155 /* These 2 writes finally start the video stream */
156 reg = 0x1007;
157 buf[0] = 0x60;
158 ret = usb_microdia_control_write(dev, reg, buf, 1);
159 if (ret < 0)
160 goto err;
161 reg = 0x1006;
162 buf[0] = 0x40;
163 ret = usb_microdia_control_write(dev, reg, buf, 1);
164 if (ret < 0)
165 goto err;
167 return 0;
169 err:
170 UDIA_ERROR("r/w in bridge register %x failed (%d)!\n", reg, ret);
171 return ret;
175 * @brief Initializes Micro-Controller's I2C interface
177 * @author Neekhil
179 * @param dev Pointer to the device
181 * @return Zero (success) or negative (USB-error value)
184 int sn9c20x_i2c_initialize(struct usb_microdia *dev)
186 __u8 buf[9];
187 int ret;
189 buf[0] = dev->sensor_flags;
190 buf[1] = dev->sensor_slave_address;
191 buf[2] = 0x00;
192 buf[3] = 0x00;
193 buf[4] = 0x00;
194 buf[5] = 0x00;
195 buf[6] = 0x00;
196 buf[7] = 0x00;
197 buf[8] = 0x03;
198 /* Initialize I2C registers to avoid getting no ACK at first I2C operation: */
199 /* Get green diagonal bands w/o this dummy write, Bridge does not know sensor address ? */
200 ret = usb_microdia_control_write(dev, 0x10c0, buf, 9);
201 if (ret < 0)
202 return ret;
203 else
204 return 0;
208 * @brief Wait until the I2C slave is ready for the next operation
210 * @param dev Pointer to the device
211 * @param highspeed
212 * @param slave_error
214 * @return Zero for success or a negative error value
217 int sn9c20x_i2c_ack_wait(struct usb_microdia *dev, bool highspeed, bool *slave_error)
219 int ret, i;
220 __u8 readbuf;
221 int delay = highspeed ? 100 : 400;
223 for (i = 0; i < 5; i++) {
224 ret = usb_microdia_control_read(dev, 0x10c0, &readbuf, 1);
226 if (ret < 0)
227 return ret;
228 else if (readbuf & SN9C20X_I2C_ERROR) {
229 *slave_error = 1;
230 /* probably should come up w/ an error value and
231 * return it via the error return */
232 return 0;
233 } else if (readbuf & SN9C20X_I2C_READY)
234 return 0;
235 else
236 udelay(delay);
238 return -EBUSY;
242 * @brief Read up to 5 bytes of data from an I2C slave
244 * @param dev Pointer to the device
245 * @param slave The id of the I2C slave device
246 * @param nbytes Number of bytes to read
247 * @param address The address of the register on the slave to read
248 * @param flags The appropriate flags for bus speed and physical connection
249 * @param result A pointer to the location at which the result should be stored
251 * @return Zero for success or a negative error value
254 int sn9c20x_read_i2c_data(struct usb_microdia *dev, __u8 slave, __u8 nbytes, __u8 address,
255 __u8 flags, __u8 *result)
257 int ret, i, j;
258 __u8 row[5];
260 if (!dev || nbytes > 4)
261 return -EINVAL;
263 /* first, we must do a dummy write of just the address */
264 ret = sn9c20x_write_i2c_data(dev, slave, 0, address, flags, NULL);
265 if (ret < 0)
266 return ret;
268 memset(row, 0, 5);
269 /* now we issue the same command but with the read bit set
270 * and no slave register address */
271 ret = sn9c20x_write_i2c_data(dev, slave, nbytes - 1, 0, flags |
272 SN9C20X_I2C_READ, row);
273 if (ret < 0)
274 return ret;
276 /* finally, ask the bridge for the data */
277 ret = usb_microdia_control_read(dev, 0x10c2, row, 5);
278 if (ret < 0)
279 return ret;
281 for (i = 0, j = 5 - nbytes; i < nbytes; i++, j++)
282 result[i] = row[j];
284 return 0;
288 * @brief Read up to 4 bytes of data from an I2C slave an return them as 16bit values
290 * @param dev Pointer to the device
291 * @param slave The id of the I2C slave device
292 * @param datalen Number of 16bit values to read
293 * @param address The address of the register on the slave to read
294 * @param flags The appropriate flags for bus speed and physical connection
295 * @param result A pointer to the location at which the result should be stored
297 * @return Zero for success or a negative error value
300 int sn9c20x_read_i2c_data16(struct usb_microdia *dev, __u8 slave, __u8 datalen, __u8 address,
301 __u8 flags, __u16 *result)
303 __u8 result8[4];
304 __u8 k;
305 int ret;
307 if (datalen > 2)
308 return -EINVAL;
309 ret = sn9c20x_read_i2c_data(dev, slave, 2*datalen, address, flags, result8);
310 for (k = 0; k < datalen; k++)
311 result[k] = (result8[k*2] << 8) | result8[k*2+1];
312 return ret;
315 static const char *wasread = "read from";
316 static const char *waswrite = "write to";
319 * @brief Write up to 5 bytes of data to an I2C slave
321 * @param dev Pointer to the device
322 * @param slave The id of the I2C slave device
323 * @param nbytes The number of bytes of data
324 * @param address The address of the register on the slave to write
325 * @param flags The appropriate flags for bus speed and physical connection
326 * @param data An array containing the data to write
328 * @return Zero for success or a negative error value
331 int sn9c20x_write_i2c_data(struct usb_microdia *dev, __u8 slave, __u8 nbytes,
332 __u8 address, __u8 flags, const __u8 data[nbytes])
334 int ret, i;
335 __u8 row[8];
336 bool slave_error = 0;
338 if (!dev || (nbytes > 0 && !data) || nbytes > 4)
339 return -EINVAL;
341 /* from the point of view of the bridge, the length
342 * includes the address */
343 row[0] = flags | ((nbytes + 1) << 4);
344 row[1] = slave;
345 row[2] = address;
346 row[7] = 0x10; /* I think this means we want an ack */
348 for (i = 0; i < 4; i++)
349 row[i + 3] = i < nbytes ? data[i] : 0;
351 UDIA_DEBUG("I2C %s %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n",
352 (flags & SN9C20X_I2C_READ ? wasread : waswrite), address,
353 row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7]);
355 ret = usb_microdia_control_write(dev, 0x10c0, row, 8);
356 if (ret >= 0)
357 ret = sn9c20x_i2c_ack_wait(dev, flags & SN9C20X_I2C_400KHZ,
358 &slave_error);
360 if (slave_error) {
361 UDIA_ERROR("I2C slave 0x%02x returned error during %s address 0x%02x\n",
362 slave,
363 (flags & SN9C20X_I2C_READ ? wasread : waswrite),
364 address);
365 return -1000;
366 /* there should be no interference with USB errors */
369 if (ret < 0) {
370 /* we got no ack */
371 UDIA_ERROR("No ack from I2C slave 0x%02x for %s address 0x%02x\n",
372 slave,
373 (flags & SN9C20X_I2C_READ ? wasread : waswrite),
374 address);
375 return ret;
378 return 0;
382 * @brief Write up to 2 16bit values als single bytes to an I2C slave
384 * @param dev Pointer to the device
385 * @param slave The id of the I2C slave device
386 * @param datalen The number of 16bit data values to write
387 * @param address The address of the register on the slave to write
388 * @param flags The appropriate flags for bus speed and physical connection
389 * @param data An array containing the data to write
391 * @return Zero for success or a negative error value
394 int sn9c20x_write_i2c_data16(struct usb_microdia *dev, __u8 slave, __u8 datalen,
395 __u8 address, __u8 flags, const __u16 data[datalen])
397 __u8 data8[4];
398 __u8 k;
399 int ret;
401 if (datalen > 2)
402 return -EINVAL;
403 for (k = 0; k < datalen; k++) {
404 data8[k*2] = data[k] >> 8;
405 data8[k*2+1] = data[k] & 0xff;
407 ret = sn9c20x_write_i2c_data(dev, slave, 2*datalen, address, flags, data8);
408 return ret;
412 * @brief Set contrast inside sn9c20x chip
414 * @author Comer352l
416 * @param dev Pointer to the device
418 * @return Zero (success) or negative (USB-error value)
421 int sn9c20x_set_contrast(struct usb_microdia *dev)
423 /* from 0x26 to 0x4b */
424 __u8 brightness_contrast[21] = {0x16, 0x0, 0x2b, 0x0, 0x8, 0x0, 0xf6, 0x0f,
425 0xd2, 0x0f, 0x38, 0x0, 0x34, 0x0, 0xcf, 0x0f,
426 0xfd, 0x0f, 0x0, 0x0, 0x0};
427 __u8 contrast_val = (dev->vsettings.contrast >> 8) * 0x25 / 0x100;
428 __u8 brightness_val = dev->vsettings.brightness >> 8;
430 brightness_val -= 0x80;
431 brightness_contrast[18] = brightness_val;
433 contrast_val += 0x26;
434 brightness_contrast[2] = contrast_val;
435 brightness_contrast[0] = 0x13 + (brightness_contrast[2] - 0x26) * 0x13 / 0x25;
436 brightness_contrast[4] = 0x7 + (brightness_contrast[2] - 0x26) * 0x7 / 0x25;
438 return usb_microdia_control_write(dev, 0x10e1, brightness_contrast, 21);
442 * @brief Set brightness inside sn9c20x chip
444 * @author Comer352l
446 * @param dev Pointer to the device
448 * @return Zero (success) or negative (USB-error value)
450 * Wrapper for sn9c20x_set_contrast
453 int sn9c20x_set_brightness(struct usb_microdia *dev)
455 return dev_microdia_camera_set_contrast(dev);
459 * @brief Set gamma inside sn9c20x chip
461 * @author Comer352l
463 * @param dev Pointer to the device
465 * @return Zero (success) or negative (USB-error value)
468 int sn9c20x_set_gamma(struct usb_microdia *dev)
470 int value = (dev->vsettings.whiteness >> 8) * 0xb8 / 0x100;
471 int r = 0;
473 __u8 gamma_val[17] = {0x0a, 0x13, 0x25, 0x37, 0x45, 0x55, 0x65, 0x74,
474 0x83, 0x92, 0xa1, 0xb0, 0xbf, 0xce, 0xdf, 0xea, 0xf5};
476 gamma_val[0] = 0x0a;
477 gamma_val[1] = 0x13 + (value * (0xcb - 0x13) / 0xb8);
478 gamma_val[2] = 0x25 + (value * (0xee - 0x25) / 0xb8);
479 gamma_val[3] = 0x37 + (value * (0xfa - 0x37) / 0xb8);
480 gamma_val[4] = 0x45 + (value * (0xfc - 0x45) / 0xb8);
481 gamma_val[5] = 0x55 + (value * (0xfb - 0x55) / 0xb8);
482 gamma_val[6] = 0x65 + (value * (0xfc - 0x65) / 0xb8);
483 gamma_val[7] = 0x74 + (value * (0xfd - 0x74) / 0xb8);
484 gamma_val[8] = 0x83 + (value * (0xfe - 0x83) / 0xb8);
485 gamma_val[9] = 0x92 + (value * (0xfc - 0x92) / 0xb8);
486 gamma_val[10] = 0xa1 + (value * (0xfc - 0xa1) / 0xb8);
487 gamma_val[11] = 0xb0 + (value * (0xfc - 0xb0) / 0xb8);
488 gamma_val[12] = 0xbf + (value * (0xfb - 0xbf) / 0xb8);
489 gamma_val[13] = 0xce + (value * (0xfb - 0xce) / 0xb8);
490 gamma_val[14] = 0xdf + (value * (0xfd - 0xdf) / 0xb8);
491 gamma_val[15] = 0xea + (value * (0xf9 - 0xea) / 0xb8);
492 gamma_val[16] = 0xf5;
494 r = usb_microdia_control_write(dev, 0x1190, gamma_val, 17);
496 return r;
500 * @brief Set sharpness inside sn9c20x chip
502 * @author Comer352l
504 * @param dev Pointer to the device
506 * @return Zero (success) or negative (USB-error value)
509 int sn9c20x_set_sharpness(struct usb_microdia *dev)
511 __u8 val[1];
512 int ret;
514 ret = usb_microdia_control_read(dev, SN9C20X_SHARPNESS, val, 1);
515 if (ret < 0)
516 return ret;
517 val[0] = (val[0] & 0xc0) | (dev->vsettings.sharpness & 0x3f);
518 ret = usb_microdia_control_write(dev, SN9C20X_SHARPNESS, val, 1);
519 if (ret < 0)
520 return ret;
521 else
522 return 0;
526 * @brief Set colour gain inside sn9c20x chip
528 * @author Brian Johnson
530 * @param dev Pointer to the device
532 * @return Zero (success) or negative (USB-error value)
535 int sn9c20x_set_rgb_gain(struct usb_microdia *dev)
537 __u8 val[4];
538 int ret;
540 memcpy(&val, &(dev->vsettings.rgb_gain), 4);
541 ret = usb_microdia_control_write(dev, SN9C20X_RED_GAIN, val, 4);
542 if (ret < 0)
543 return ret;
544 else
545 return 0;
549 * @brief Calculate closest resolution to input from application
551 * @author Brian Johnson
553 * @param width Requested width of video stream
554 * @param height Requested height of video stream
556 * @retval width Closest possible width of video stream
557 * @retval height Closest possible height of video stream
559 * @return Number of the
562 int sn9c20x_get_closest_resolution(int *width, int *height)
564 int i;
566 for (i = ARRAY_SIZE(sn9c20x_win_sizes) - 1; i >= 0; i--) {
567 if (*width >= sn9c20x_win_sizes[i].hsize
568 && *height >= sn9c20x_win_sizes[i].vsize)
569 break;
572 *width = sn9c20x_win_sizes[i].hsize;
573 *height = sn9c20x_win_sizes[i].vsize;
575 return i;
579 * @brief Set resolution inside sn9c20x chip
581 * @author Brian Johnson
583 * @param dev Pointer to the device
584 * @param width Width
585 * @param height Height
587 * @return 0
590 int sn9c20x_set_resolution(struct usb_microdia *dev,
591 int width, int height)
593 int ret;
594 __u8 scale;
595 __u8 window[5];
596 struct sn9c20x_win_size *wsize;
598 ret = sn9c20x_get_closest_resolution(&width, &height);
599 wsize = &sn9c20x_win_sizes[ret];
601 dev->vsettings.format.width = width;
602 dev->vsettings.format.height = height;
604 window[0] = wsize->hstart >> 2; window[1] = wsize->hsize >> 2;
605 window[2] = wsize->vstart >> 1; window[3] = wsize->vsize >> 1;
606 window[4] = 0x00;
608 usb_microdia_control_read(dev, SN9C20X_SCALE, &scale, 1);
609 scale = (scale & 0xCF) | wsize->scale;
610 usb_microdia_control_write(dev, SN9C20X_HSTART, window, 5);
611 usb_microdia_control_write(dev, SN9C20X_SCALE, &scale, 1);
614 * queue.frame_size is only ever used in isoc_handler
615 * to test if the ISOC data is the correct size
616 * This modificate pre calculates this rather
617 * than doing the calculating in the isoc_handler
619 dev->queue.frame_size = width * height +
620 (width * height) / dev->frame_size_divisor;
623 UDIA_DEBUG("Set mode [%dx%d]\n", width, height);
625 return 0;
629 * @brief Set exposure inside sn9c20x chip
631 * @param dev
633 * @returns 0 or negative error value
635 * @author GWater
637 int sn9c20x_set_exposure(struct usb_microdia *dev)
639 __u8 buf;
640 int ret;
641 int exposure = dev->vsettings.exposure;
643 buf = (__u8) (exposure * 0x00ff / 0xffff) & 0x00ff;
645 /* exposure can't be 0 - below 0x04 the image freezes */
646 if (buf < 0x05)
647 buf = 0x05;
649 /* write new value to register 0x118a */
650 ret = usb_microdia_control_write(dev, 0x118a, &buf, 1);
651 if (ret < 0) {
652 UDIA_ERROR("Error: setting exposure failed: "
653 "error while writing to register 0x118a\n");
654 return ret;
657 return 0;