Fix 6270 camera to work with hardware resolution switching
[microdia.git] / mt9vx11.c
blobb193c27686e3f4f52bb96fd11e1ccd95dd2f3f88
1 /**
2 * @file mt9vx11.c
3 * @author Comer352l
4 * @date 2008-04-25
6 * @brief Common functions and data for the Micron MT9Vx11 sensors.
8 * @note Copyright (C) Comer352l
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 "microdia.h"
29 #include "sn9c20x.h"
30 #include "mt9vx11.h"
34 int mt9v111_select_address_space(struct usb_microdia *dev, __u8 address_space)
36 __u8 buf[2];
37 int retI2C;
38 int k;
40 /* check if selection is valid: */
41 if ((address_space != MT9V111_ADDRESSSPACE_IFP) && (address_space != MT9V111_ADDRESSSPACE_SENSOR)) {
42 UDIA_INFO("Error: invalid register address space selection for sensor MT9V111/MI0360SOC !\n");
43 return -1;
45 /* read address space slection register: */
46 k = 0;
47 retI2C = -1;
48 while ((k < 3) && (retI2C != 0)) {
49 retI2C = sn9c20x_read_i2c_data(dev, MT9V111_I2C_SLAVE_ADDRESS, 2, 0x01, SN9C20X_I2C_2WIRE, buf);
50 if (retI2C != 0 && k < 2)
51 udelay(1000);
52 k++;
54 if (retI2C != 0) {
55 UDIA_INFO("Error: MT9V111/MI0360SOC (I2C-slave 0x5c): read of reg0x01 (address space selection) failed !\n");
56 return -1;
58 /* check current address space: */
59 if ((buf[0] != 0x00) || (buf[1] != address_space)) {
60 k = 0;
61 retI2C = -1;
62 while ((k < 3) && (retI2C != 0)) {
63 /* switch address space: */
64 buf[0] = 0x00; buf[1] = address_space;
65 retI2C = sn9c20x_write_i2c_data(dev, MT9V111_I2C_SLAVE_ADDRESS, 2, 0x01, SN9C20X_I2C_2WIRE, buf);
66 if (retI2C != 0 && k < 2)
67 udelay(1000);
68 k++;
70 if (retI2C != 0) {
71 if (address_space == MT9V111_ADDRESSSPACE_IFP)
72 UDIA_INFO("Error: MT9V111/MI0360SOC (I2C-slave 0x5c): switch to IFP address space failed !\n");
73 else
74 UDIA_INFO("Error: MT9V111/MI0360SOC (I2C-slave 0x5c): switch to sensor core address space failed !\n");
75 return -1;
78 return 0;
82 int mt9vx11_sensor_probe(struct usb_microdia *dev)
84 __u8 buf[2];
85 int ret;
86 int retI2C;
87 int k;
89 /* *** Probe MT9V011/MI0360: */
90 /* read chip version: */
91 for (k = 0; k < 3; k++) {
92 retI2C = sn9c20x_read_i2c_data(dev, MT9V011_I2C_SLAVE_ADDRESS, 2, 0xff, SN9C20X_I2C_2WIRE, buf);
93 if (retI2C == 0) {
94 if ((buf[0] == 0x82) && (buf[1] == 0x43)) {
95 UDIA_INFO("Detected sensor: MT9V011/MI0360 (chip version: 0x%02X%02X)\n", buf[0], buf[1]);
96 dev->sensor_slave_address = MT9V011_I2C_SLAVE_ADDRESS;
97 return 0;
98 } else
99 UDIA_DEBUG("I2C-slave 0x5d returned invalid chip version: 0x%02X%02X\n", buf[0], buf[1]);
100 } else
101 udelay(500);
103 /* NOTE: DNT DigiMicro 1.3 (microscope camera):
104 * This device uses sensor MT9V111, but slave 0x5d is also successfully read.
105 * Registers 0x00, 0x36 and 0xff of slave 0x5d return chip version 0x0000.
108 /* *** Probe MT9V111/MI0360SOC: */
109 /* switch register address space: */
110 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_SENSOR);
111 if (ret != 0)
112 return -1;
113 /* read chip version: */
114 for (k = 0; k < 3; k++) {
115 retI2C = sn9c20x_read_i2c_data(dev, MT9V111_I2C_SLAVE_ADDRESS, 2, 0xff, SN9C20X_I2C_2WIRE, buf);
116 if (retI2C == 0) {
117 if ((buf[0] == 0x82) && (buf[1] == 0x3a)) {
118 UDIA_INFO("Detected sensor: MT9V111/MI0360SOC (chip version: 0x%02X%02X)\n", buf[0], buf[1]);
119 dev->sensor_slave_address = MT9V111_I2C_SLAVE_ADDRESS;
120 return 0;
121 } else
122 UDIA_DEBUG("I2C-slave 0x5c returned invalid chip version: 0x%02X%02X\n", buf[0], buf[1]);
123 } else
124 udelay(500);
127 /* FIXME: always switch back to last register address space */
129 UDIA_INFO("Error: sensor probe failed !\n");
130 dev->sensor_slave_address = 0;
131 return -1;
135 int mt9v111_setup_autoexposure(struct usb_microdia *dev)
137 __u16 buf[1];
138 int ret = 0;
139 int ret2 = 0;
141 /* Check if sensor is MT9V111: */
142 if (dev->sensor_slave_address != MT9V111_I2C_SLAVE_ADDRESS)
143 return -ENODEV;
144 /* Switch to IFP-register address space: */
145 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_IFP);
146 if (ret < 0)
147 return -11; /* -EAGAIN */
148 /* Set target luminance and tracking accuracy: */
149 buf[0] = MT9V111_IFP_AE_TRACKINGACCURACY(12) /* 0-255 (default: 16) */
150 | MT9V111_IFP_AE_TARGETLUMINANCE(100); /* 0-255 (default: 100) */
151 ret = sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
152 MT9V111_IFPREG_AE_TARGETLUMCTL, dev->sensor_flags, buf);
153 /* Set speed and sensitivity: */
154 buf[0] = MT9V111_IFP_AE_DELAY(4) /* 0-7 (fastest-slowest) (default: 4) */
155 | MT9V111_IFP_AE_SPEED(4) /* 0-7 (fastest-slowest) (default: 4) */
156 | MT9V111_IFP_AE_STEPSIZE_MEDIUM;
157 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
158 MT9V111_IFPREG_AE_SPEEDSENSCTL, dev->sensor_flags, buf);
159 /* Horizontal boundaries of AE measurement window: */
160 buf[0] = MT9V111_IFP_AE_WINBOUNDARY_RIGHT(1020) /* 0-1020 (pixel) (default: 1020) */
161 | MT9V111_IFP_AE_WINBOUNDARY_LEFT(12); /* 0-1020 (pixel) (default: 12) */
162 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
163 MT9V111_IFPREG_AE_HWINBOUNDARY, dev->sensor_flags, buf);
164 /* Vertical boundaries of AE measurement window: */
165 buf[0] = MT9V111_IFP_AE_WINBOUNDARY_BOTTOM(510) /* 0-510 (pixel) (default: 510) */
166 | MT9V111_IFP_AE_WINBOUNDARY_TOP(32); /* 0-510 (pixel) (default: 32) */
167 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
168 MT9V111_IFPREG_AE_VWINBOUNDARY, dev->sensor_flags, buf);
169 /* Horizontal boundaries of AE measurement window for back light compensation: */
170 buf[0] = MT9V111_IFP_AE_WINBOUNDARY_RIGHT_BLC(480) /* 0-1020 (pixel) (default: 480) */
171 | MT9V111_IFP_AE_WINBOUNDARY_LEFT_BLC(160); /* 0-1020 (pixel) (default: 160) */
172 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
173 MT9V111_IFPREG_AE_HWINBOUNDARY_BLC, dev->sensor_flags, buf);
174 /* Vertical boundaries of AE measurement window for back light compensation: */
175 buf[0] = MT9V111_IFP_AE_WINBOUNDARY_BOTTOM_BLC(360) /* 0-510 (pixel) (default: 360) */
176 | MT9V111_IFP_AE_WINBOUNDARY_TOP_BLC(120); /* 0-510 (pixel) (default: 120) */
177 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
178 MT9V111_IFPREG_AE_VWINBOUNDARY_BLC, dev->sensor_flags, buf);
179 /* Set digital gains limit: */
180 buf[0] = MT9V111_IFP_AE_MAXGAIN_POSTLS(64) /* 0-255 (default: 64) */
181 | MT9V111_IFP_AE_MAXGAIN_PRELS(16); /* 0-255 (default: 16) */
182 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
183 MT9V111_IFPREG_AE_DGAINSLIMITS, dev->sensor_flags, buf);
184 /* Read current value of IFP-register 0x06: */
185 ret2 = sn9c20x_read_i2c_data16(dev, dev->sensor_slave_address, 1,
186 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
187 if (ret2 == 0) {
188 /* Set new value for register 0x06: */
189 buf[0] = (buf[0] & 0xffe3) | MT9V111_IFP_AE_WIN_COMBINED | MT9V111_IFP_OPMODE_BYPASSCOLORCORR;
190 /* NOTE: BYPASS COLOR CORRECTION HAS TO BE SET FOR PERMANENT AE ! */
191 /* Write new value to IFP-register 0x06:*/
192 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
193 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
196 if (ret < 0 || ret2 < 0) {
197 UDIA_INFO("One or more errors occured during setup of AE registers.\n");
198 return -1;
200 return 0;
204 int mt9v111_setup_autowhitebalance(struct usb_microdia *dev)
206 __u16 buf[1];
207 int ret = 0;
209 /* Check if sensor is MT9V111: */
210 if (dev->sensor_slave_address != MT9V111_I2C_SLAVE_ADDRESS)
211 return -ENODEV;
212 /* Switch to IFP-register address space: */
213 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_IFP);
214 if (ret < 0)
215 return -11; /* -EAGAIN */
216 /* Set AWB tint: */
217 buf[0] = MT9V111_IFP_AWB_ADDON_RED(0) /* 0-255 (default: 0) */
218 | MT9V111_IFP_AWB_ADDON_BLUE(0); /* 0-255 (default: 0) */
219 ret = sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
220 MT9V111_IFPREG_AWB_TINT, dev->sensor_flags, buf);
221 /* Set AWB speed and color saturation: */
222 buf[0] = MT9V111_IFP_AWB_SATURATION_FULL | MT9V111_IFP_AWB_AUTOSATLOWLIGHT_ENABLE
223 | MT9V111_IFP_AWB_DELAY(4) /* 0-7 (fastest-slowest) (default: 4) */
224 | MT9V111_IFP_AWB_SPEED(4); /* 0-7 (fastest-slowest) (default: 4) */
225 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
226 MT9V111_IFPREG_AWB_SPEEDCOLORSATCTL, dev->sensor_flags, buf);
227 /* Boundaries of AWB measurement window: */
228 buf[0] = MT9V111_IFP_AWB_WINBOUNDARY_TOP(0) /* 0-480 (pixel) (default: 0) */
229 | MT9V111_IFP_AWB_WINBOUNDARY_BOTTOM(480) /* 0-480 (pixel) (default: 448) */
230 | MT9V111_IFP_AWB_WINBOUNDARY_LEFT(0) /* 0-960 (pixel) (default: 0) */
231 | MT9V111_IFP_AWB_WINBOUNDARY_RIGHT(640); /* 0-960 (pixel) (default: 640) */
232 ret += sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
233 MT9V111_IFPREG_AWB_WINBOUNDARY, dev->sensor_flags, buf);
235 if (ret < 0) {
236 UDIA_INFO("One or more errors occured during setup of AWB registers.\n");
237 return -1;
239 return 0;
243 int mt9vx11_set_exposure(struct usb_microdia *dev)
245 int ret = 0;
246 __u8 buf[2];
248 if (dev->sensor_slave_address == MT9V111_I2C_SLAVE_ADDRESS)
249 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_SENSOR);
251 if (ret < 0)
252 return -11; /* -EAGAIN */
254 buf[0] = (dev->vsettings.exposure >> 12);
255 buf[1] = 0;
256 ret |= sn9c20x_write_i2c_data(dev, dev->sensor_slave_address, 2, 0x09, dev->sensor_flags, buf);
257 /* Maybe we have to disable AE/AWB/flicker avoidence (currently not used)
258 for MT9V111 sensor, because IFP controls this register if one of them
259 is enabled. */
261 return ret;
265 int mt9vx11_set_hvflip(struct usb_microdia *dev)
267 int ret = 0;
268 __u8 buf[2];
270 if ((dev->vsettings.hflip > 1) || (dev->vsettings.hflip < 0))
271 return -EINVAL;
272 if ((dev->vsettings.vflip > 1) || (dev->vsettings.vflip < 0))
273 return -EINVAL;
275 if (dev->sensor_slave_address == MT9V111_I2C_SLAVE_ADDRESS)
276 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_SENSOR);
277 if (ret < 0)
278 return -11; /* -EAGAIN */
280 ret = sn9c20x_read_i2c_data(dev, dev->sensor_slave_address, 2, 0x20, dev->sensor_flags, buf);
281 if (ret < 0)
282 return ret;
284 if (dev->vsettings.hflip) {
285 buf[0] |= 0x80; /* (MSB) set bit 15: read out from bottom to top (upside down) */
286 buf[1] |= 0x80; /* (LSB) set bit 7: readout starting 1 row later */
287 } else {
288 buf[0] &= 0x7f; /* (MSB) unset bit 15: normal readout */
289 buf[1] &= 0x7f; /* (LSB) unset bit 7: normal readout */
291 if (dev->vsettings.vflip) {
292 buf[0] |= 0x40; /* (MSB) set bit 14: read out from right to left (mirrored) */
293 buf[1] |= 0x20; /* (LSB) set bit 5: readout starting 1 column later */
294 } else {
295 buf[0] &= 0xbf; /* (MSB) unset bit 14: normal readout */
296 buf[1] &= 0xdf; /* (LSB) unset bit 5: normal readout */
299 ret = sn9c20x_write_i2c_data(dev, dev->sensor_slave_address, 2, 0x20, dev->sensor_flags, buf);
300 return ret;
304 int mt9v111_set_autoexposure(struct usb_microdia *dev)
306 __u16 buf[1];
307 int ret = 0;
309 /* Check if sensor is MT9V111: */
310 if (dev->sensor_slave_address != MT9V111_I2C_SLAVE_ADDRESS)
311 return -ENODEV;
312 /* Switch to IFP-register address space: */
313 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_IFP);
314 if (ret < 0)
315 return -11; /* -EAGAIN */
316 /* Read current value of IFP-register 0x06: */
317 ret = sn9c20x_read_i2c_data16(dev, dev->sensor_slave_address, 1,
318 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
319 if (ret < 0) {
320 UDIA_ERROR("Error: setting of auto exposure failed: error while reading from IFP-register 0x06\n");
321 return ret;
323 /* Set new value for register 0x06: */
324 if (dev->vsettings.auto_exposure == 1) {
325 /* Enable automatic exposure: */
326 buf[0] |= MT9V111_IFP_OPMODE_AUTOEXPOSURE;
327 } else if (dev->vsettings.auto_exposure == 0) {
328 /* Disable automatic exposure: */
329 buf[0] &= ~MT9V111_IFP_OPMODE_AUTOEXPOSURE;
330 } else
331 return -EINVAL;
332 /* Write new value to IFP-register 0x06: */
333 ret = sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
334 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
335 if (ret < 0) {
336 UDIA_ERROR("Error: setting of auto exposure failed: error while writing to IFP-register 0x06\n");
337 return ret;
339 return 0;
343 int mt9v111_set_autowhitebalance(struct usb_microdia *dev)
345 __u16 buf[1];
346 int ret = 0;
348 /* Check if sensor is MT9V111: */
349 if (dev->sensor_slave_address != MT9V111_I2C_SLAVE_ADDRESS)
350 return -ENODEV;
351 /* Switch to IFP-register address space: */
352 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_IFP);
353 if (ret < 0)
354 return -11; /* -EAGAIN */
355 /* Read current value of IFP-register 0x06: */
356 ret = sn9c20x_read_i2c_data16(dev, dev->sensor_slave_address, 1,
357 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
358 if (ret < 0) {
359 UDIA_ERROR("Error: setting of auto whitebalance failed: error while reading from IFP-register 0x06\n");
360 return ret;
362 /* Set new value for register 0x06: */
363 if (dev->vsettings.auto_whitebalance == 1) {
364 /* Enable automatic exposure: */
365 buf[0] |= MT9V111_IFP_OPMODE_AUTOWHITEBALANCE;
366 } else if (dev->vsettings.auto_whitebalance == 0) {
367 /* Disable automatic exposure: */
368 buf[0] &= ~MT9V111_IFP_OPMODE_AUTOWHITEBALANCE;
369 } else
370 return -EINVAL;
371 /* Write new value to IFP-register 0x06:*/
372 ret = sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
373 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
374 if (ret < 0) {
375 UDIA_ERROR("Error: setting of auto whitebalance failed: error while writing to IFP-register 0x06\n");
376 return ret;
378 return 0;
382 int mt9v111_set_autocorrections(struct usb_microdia *dev, int enable)
384 __u16 buf[1];
385 int ret = 0;
387 /* Check if sensor is MT9V111: */
388 if (dev->sensor_slave_address != MT9V111_I2C_SLAVE_ADDRESS)
389 return -ENODEV;
390 /* Switch to IFP-register address space: */
391 ret = mt9v111_select_address_space(dev, MT9V111_ADDRESSSPACE_IFP);
392 if (ret < 0)
393 return -11; /* -EAGAIN */
394 /* Read current value of IFP-register 0x06: */
395 ret = sn9c20x_read_i2c_data16(dev, dev->sensor_slave_address, 1,
396 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
397 if (ret < 0) {
398 UDIA_ERROR("Error: setting of sensor auto-correction functions failed: error while reading from IFP-register 0x06\n");
399 return ret;
401 /* Set new value for register 0x06: */
402 if (enable == 1)
403 buf[0] |= MT9V111_IFP_OPMODE_APERTURECORR | MT9V111_IFP_OPMODE_ONTHEFLYDEFECTCORR;
404 else if (enable == 0)
405 buf[0] &= ~(MT9V111_IFP_OPMODE_APERTURECORR | MT9V111_IFP_OPMODE_ONTHEFLYDEFECTCORR);
406 else
407 return -EINVAL;
408 /* Write new value to IFP-register 0x06: */
409 ret = sn9c20x_write_i2c_data16(dev, dev->sensor_slave_address, 1,
410 MT9V111_IFPREG_OPMODECTL, dev->sensor_flags, buf);
411 if (ret < 0) {
412 UDIA_ERROR("Error: setting of sensor auto-correction functions failed: error while writing to IFP-register 0x06\n");
413 return ret;
415 return 0;