Support for hardware resolultion change
[microdia.git] / sn9c20x.c
blobc869f8fea56f9d9733b68bfadbd24f5636730539
1 /**
2 * @file sn9c20x.c
3 * @author Dave Neuer
4 * @date 2008-03-02
5 * @version v0.0.0
7 * @brief Common functions and data for the Sonix SN9C20x webcam bridge chips.
9 * @note Copyright (C) Dave Neuer
11 * @par Licences
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include <linux/delay.h>
29 #include <linux/errno.h>
30 #include <linux/string.h>
31 #include "microdia.h"
32 #include "sn9c20x.h"
35 int sn9c20x_i2c_ack_wait(struct usb_microdia *, bool, bool *);
38 struct sn9c20x_win_size {
39 int hstart;
40 int hsize;
41 int vstart;
42 int vsize;
43 int scale;
44 } sn9c20x_win_sizes[] = {
46 .hstart = 0,
47 .hsize = 160,
48 .vstart = 0,
49 .vsize = 120,
50 .scale = SN9C20X_1_4_SCALE,
53 .hstart = 0,
54 .hsize = 320,
55 .vstart = 0,
56 .vsize = 240,
57 .scale = SN9C20X_1_2_SCALE,
60 .hstart = 0,
61 .hsize = 640,
62 .vstart = 0,
63 .vsize = 480,
64 .scale = SN9C20X_NO_SCALE,
69 /**
70 * @brief Initializes IC2-registers 0x10c0-0x10c7
72 * @param dev Pointer to the device
73 * @param flags The appropriate flags for bus speed and physical connection
74 * @param slave The id of the I2C slave device
76 * @return Zero (success) or negative (USB-error value)
79 int sn9c20x_initialize_i2c(struct usb_microdia * dev, __u8 flags, __u8 slave)
81 __u8 buf[8];
82 int ret;
84 buf[0] = 0x81 & flags;
85 buf[1] = slave;
86 buf[2] = 0x00;
87 buf[3] = 0x00;
88 buf[4] = 0x00;
89 buf[5] = 0x00;
90 buf[6] = 0x00;
91 buf[7] = 0x00;
92 ret = usb_microdia_control_write(dev, 0x10c0, buf, 8);
93 if (ret < 0)
94 return ret;
95 else
96 return 0;
99 /**
100 * @brief Read up to 5 bytes of data from an I2C slave
102 * @param dev Pointer to the device
103 * @param slave The id of the I2C slave device
104 * @param nbytes Number of bytes to read
105 * @param address The address of the register on the slave to read
106 * @param flags The appropriate flags for bus speed and physical connection
107 * @param result A pointer to the location at which the result should be stored
109 * @return Zero for success or a negative error value
112 int sn9c20x_read_i2c_data(struct usb_microdia * dev, __u8 slave, __u8 nbytes, __u8 address,
113 __u8 flags, __u8 * result)
115 int ret, i, j;
116 __u8 row[5];
118 if(!dev || nbytes > 4)
119 return -EINVAL;
121 /* first, we must do a dummy write of just the address */
122 ret = sn9c20x_write_i2c_data(dev, slave, 0, address, flags, NULL);
123 if(ret < 0)
124 return ret;
126 memset(row, 0, 5);
127 /* now we issue the same command but with the read bit set
128 * and no slave register address */
129 ret = sn9c20x_write_i2c_data(dev, slave, nbytes - 1, 0, flags |
130 SN9C20X_I2C_READ, row);
131 if(ret < 0)
132 return ret;
134 /* finally, ask the bridge for the data */
135 ret = usb_microdia_control_read(dev, 0x10c2, row, 5);
136 if(ret < 0)
137 return ret;
139 for(i = 0, j = 5 - nbytes; i < nbytes; i++, j++)
140 result[i] = row[j];
142 return 0;
146 * @brief Read up to 4 bytes of data from an I2C slave an return them as 16bit values
148 * @param dev Pointer to the device
149 * @param slave The id of the I2C slave device
150 * @param datalen Number of 16bit values to read
151 * @param address The address of the register on the slave to read
152 * @param flags The appropriate flags for bus speed and physical connection
153 * @param result A pointer to the location at which the result should be stored
155 * @return Zero for success or a negative error value
158 int sn9c20x_read_i2c_data16(struct usb_microdia * dev, __u8 slave, __u8 datalen, __u8 address,
159 __u8 flags, __u16 * result)
161 __u8 result8[4];
162 __u8 k;
163 int ret;
165 if(datalen > 2) return -EINVAL;
166 ret = sn9c20x_read_i2c_data(dev, slave, 2*datalen, address, flags, result8);
167 for (k=0; k<datalen; k++)
168 result[k] = (result8[k*2] << 8) | result8[k*2+1];
169 return ret;
172 static const char *wasread = "read from";
173 static const char *waswrite = "write to";
176 * @brief Write up to 5 bytes of data to an I2C slave
178 * @param dev Pointer to the device
179 * @param slave The id of the I2C slave device
180 * @param nbytes The number of bytes of data
181 * @param address The address of the register on the slave to write
182 * @param flags The appropriate flags for bus speed and physical connection
183 * @param data An array containing the data to write
185 * @return Zero for success or a negative error value
188 int sn9c20x_write_i2c_data(struct usb_microdia * dev, __u8 slave, __u8 nbytes,
189 __u8 address, __u8 flags, const __u8 data[nbytes])
191 int ret, i;
192 __u8 row[8];
193 bool slave_error = 0;
195 if(!dev || (nbytes > 0 && !data) || nbytes > 4)
196 return -EINVAL;
198 /* from the point of view of the bridge, the length
199 * includes the address */
200 row[0] = flags | ((nbytes + 1) << 4);
201 row[1] = slave;
202 row[2] = address;
203 row[7] = 0x10; /* I think this means we want an ack */
205 for(i = 0; i < 4; i++)
206 row[i + 3] = i < nbytes ? data[i] : 0;
208 UDIA_DEBUG("I2C %s %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n",
209 (flags & SN9C20X_I2C_READ ? wasread : waswrite), address,
210 row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7]);
212 ret = usb_microdia_control_write(dev, 0x10c0, row, 8);
213 if(ret >= 0)
214 ret = sn9c20x_i2c_ack_wait(dev, flags & SN9C20X_I2C_400KHZ,
215 &slave_error);
217 if(slave_error) {
218 UDIA_ERROR("I2C slave 0x%02x returned error during %s address 0x%02x\n",
219 slave,
220 (flags & SN9C20X_I2C_READ ? wasread : waswrite),
221 address);
222 return -1000; // there should be no interference with USB errors
225 if(ret < 0) {
226 /* we got no ack */
227 UDIA_ERROR("No ack from I2C slave 0x%02x for %s address 0x%02x\n",
228 slave,
229 (flags & SN9C20X_I2C_READ ? wasread : waswrite),
230 address);
231 return ret;
234 return 0;
238 * @brief Write up to 2 16bit values als single bytes to an I2C slave
240 * @param dev Pointer to the device
241 * @param slave The id of the I2C slave device
242 * @param datalen The number of 16bit data values to write
243 * @param address The address of the register on the slave to write
244 * @param flags The appropriate flags for bus speed and physical connection
245 * @param data An array containing the data to write
247 * @return Zero for success or a negative error value
250 int sn9c20x_write_i2c_data16(struct usb_microdia * dev, __u8 slave, __u8 datalen,
251 __u8 address, __u8 flags, const __u16 data[datalen])
253 __u8 data8[4];
254 __u8 k;
255 int ret;
257 if(datalen > 2) return -EINVAL;
258 for (k=0; k<datalen; k++)
260 data8[k*2] = data[k] >> 8;
261 data8[k*2+1] = data[k] & 0xff;
263 ret = sn9c20x_write_i2c_data(dev, slave, 2*datalen, address, flags, data8);
264 return ret;
268 * @brief Wait until the I2C slave is ready for the next operation
270 * @param dev Pointer to the device
271 * @param highspeed
272 * @param slave_error
274 * @return Zero for success or a negative error value
277 int sn9c20x_i2c_ack_wait(struct usb_microdia *dev, bool highspeed, bool * slave_error)
279 int ret, i;
280 __u8 readbuf;
281 int delay = highspeed ? 100 : 400;
283 for(i = 0; i< 5; i++) {
284 ret = usb_microdia_control_read(dev, 0x10c0, &readbuf, 1);
286 if(ret < 0)
287 return ret;
288 else if(readbuf & SN9C20X_I2C_ERROR) {
289 *slave_error = 1;
290 /* probably should come up w/ an error value and
291 * return it via the error return */
292 return 0;
294 else if(readbuf & SN9C20X_I2C_READY)
295 return 0;
296 else
297 udelay(delay);
300 return -EBUSY;
304 int sn9c20x_set_contrast(struct usb_microdia *dev)
306 /* from 0x26 to 0x4b */
307 __u8 brightness_contrast[21] = {0x16, 0x0, 0x2b, 0x0, 0x8, 0x0, 0xf6, 0x0f,
308 0xd2, 0x0f, 0x38, 0x0, 0x34, 0x0, 0xcf, 0x0f,
309 0xfd, 0x0f, 0x0, 0x0, 0x0};
310 __u8 contrast_val = (dev->vsettings.contrast >> 8) * 0x25 / 0x100;
311 __u8 brightness_val = dev->vsettings.brightness >> 8;
313 brightness_val -= 0x80;
314 brightness_contrast[18] = brightness_val;
316 contrast_val += 0x26;
317 brightness_contrast[2] = contrast_val;
318 brightness_contrast[0] = 0x13 + (brightness_contrast[2] - 0x26) * 0x13 / 0x25;
319 brightness_contrast[4] = 0x7 + (brightness_contrast[2] - 0x26) * 0x7 / 0x25;
321 return usb_microdia_control_write(dev, 0x10e1, brightness_contrast, 21);
324 int sn9c20x_set_brightness(struct usb_microdia *dev)
326 return dev_microdia_camera_set_contrast(dev);
329 int sn9c20x_set_gamma(struct usb_microdia *dev)
331 int value = (dev->vsettings.whiteness >> 8) * 0xb8 / 0x100;
332 int r = 0;
334 __u8 gamma_val[17] = {0x0a, 0x13, 0x25, 0x37, 0x45, 0x55, 0x65, 0x74,
335 0x83, 0x92, 0xa1, 0xb0, 0xbf, 0xce, 0xdf, 0xea, 0xf5};
337 gamma_val[0] = 0x0a;
338 gamma_val[1] = 0x13 + (value * (0xcb - 0x13) / 0xb8);
339 gamma_val[2] = 0x25 + (value * (0xee - 0x25) / 0xb8);
340 gamma_val[3] = 0x37 + (value * (0xfa - 0x37) / 0xb8);
341 gamma_val[4] = 0x45 + (value * (0xfc - 0x45) / 0xb8);
342 gamma_val[5] = 0x55 + (value * (0xfb - 0x55) / 0xb8);
343 gamma_val[6] = 0x65 + (value * (0xfc - 0x65) / 0xb8);
344 gamma_val[7] = 0x74 + (value * (0xfd - 0x74) / 0xb8);
345 gamma_val[8] = 0x83 + (value * (0xfe - 0x83) / 0xb8);
346 gamma_val[9] = 0x92 + (value * (0xfc - 0x92) / 0xb8);
347 gamma_val[10] = 0xa1 + (value * (0xfc - 0xa1) / 0xb8);
348 gamma_val[11] = 0xb0 + (value * (0xfc - 0xb0) / 0xb8);
349 gamma_val[12] = 0xbf + (value * (0xfb - 0xbf) / 0xb8);
350 gamma_val[13] = 0xce + (value * (0xfb - 0xce) / 0xb8);
351 gamma_val[14] = 0xdf + (value * (0xfd - 0xdf) / 0xb8);
352 gamma_val[15] = 0xea + (value * (0xf9 - 0xea) / 0xb8);
353 gamma_val[16] = 0xf5;
355 r = usb_microdia_control_write(dev, 0x1190, gamma_val, 17);
357 return r;
360 int sn9c20x_set_sharpness(struct usb_microdia *dev)
362 __u8 val[1];
363 int ret;
365 ret = usb_microdia_control_read(dev, SN9C20X_SHARPNESS, val, 1);
366 if (ret < 0)
367 return ret;
368 val[0] = (val[0] & 0xc0) | (dev->vsettings.sharpness & 0x3f);
369 ret = usb_microdia_control_write(dev, SN9C20X_SHARPNESS, val, 1);
370 if (ret < 0)
371 return ret;
372 else
373 return 0;
376 int sn9c20x_set_rgb_gain(struct usb_microdia *dev)
378 __u8 val[4];
379 int ret;
381 memcpy(&val, &(dev->vsettings.rgb_gain), 4);
382 ret = usb_microdia_control_write(dev, SN9C20X_RED_GAIN, val, 4);
383 if (ret < 0)
384 return ret;
385 else
386 return 0;
389 int sn9c20x_get_closest_resolution(int *width, int *height)
391 int i;
393 for (i = ARRAY_SIZE(sn9c20x_win_sizes) - 1; i >= 0; i--) {
394 if (*width >= sn9c20x_win_sizes[i].hsize
395 && *height >= sn9c20x_win_sizes[i].vsize)
396 break;
399 *width = sn9c20x_win_sizes[i].hsize;
400 *height = sn9c20x_win_sizes[i].vsize;
402 return i;
405 int sn9c20x_set_resolution(struct usb_microdia *dev,
406 int width, int height)
408 int ret;
409 __u8 scale;
410 __u8 window[5];
411 struct sn9c20x_win_size *wsize;
413 ret = sn9c20x_get_closest_resolution(&width, &height);
414 wsize = &sn9c20x_win_sizes[ret];
416 dev->vsettings.format.width = width;
417 dev->vsettings.format.height = height;
419 window[0] = wsize->hstart >> 2; window[1] = wsize->hsize >> 2;
420 window[2] = wsize->vstart >> 1; window[3] = wsize->vsize >> 1;
421 window[4] = 0x00;
423 usb_microdia_control_read(dev, SN9C20X_SCALE, &scale, 1);
424 scale = (scale & 0xCF) | wsize->scale;
425 usb_microdia_control_write(dev, SN9C20X_HSTART, window, 5);
426 usb_microdia_control_write(dev, SN9C20X_SCALE, &scale, 1);
429 * queue.frame_size is only ever used in isoc_handler
430 * to test if the ISOC data is the correct size
431 * This modificate pre calculates this rather
432 * than doing the calculating in the isoc_handler
434 dev->queue.frame_size = width * height +
435 (width * height) / dev->frame_size_divisor;
438 UDIA_DEBUG("Set mode [%dx%d]\n", width, height);
440 return 0;