diff options
author | Mauro Carvalho Chehab <mchehab@redhat.com> | 2009-06-29 04:41:26 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2009-07-05 13:30:02 -0400 |
commit | 7dfba00d05f3c7db9510f3b54a472981cf1521af (patch) | |
tree | 0715f03c1f997a29e9aa4d5789a9bde51ff0f503 /drivers/media | |
parent | b179bc4579f67c6f1df524c48b28cacf0c7a1b91 (diff) |
V4L/DVB (12135): Add a driver for mt9v011 sensor
Adds driver for mt9v011 based on its datasheet, available at:
http://download.micron.com/pdf/datasheets/imaging/MT9V011.pdf
The driver was tested with a webcam that will be added on a next patch.
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/video/Kconfig | 8 | ||||
-rw-r--r-- | drivers/media/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/video/mt9v011.c | 355 | ||||
-rw-r--r-- | drivers/media/video/mt9v011.h | 35 |
4 files changed, 399 insertions, 0 deletions
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 061e147f6f26..84b6fc15519d 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig | |||
@@ -312,6 +312,14 @@ config VIDEO_OV7670 | |||
312 | OV7670 VGA camera. It currently only works with the M88ALP01 | 312 | OV7670 VGA camera. It currently only works with the M88ALP01 |
313 | controller. | 313 | controller. |
314 | 314 | ||
315 | config VIDEO_MT9V011 | ||
316 | tristate "Micron mt9v011 sensor support" | ||
317 | depends on I2C && VIDEO_V4L2 | ||
318 | ---help--- | ||
319 | This is a Video4Linux2 sensor-level driver for the Micron | ||
320 | mt0v011 1.3 Mpixel camera. It currently only works with the | ||
321 | em28xx driver. | ||
322 | |||
315 | config VIDEO_TCM825X | 323 | config VIDEO_TCM825X |
316 | tristate "TCM825x camera sensor support" | 324 | tristate "TCM825x camera sensor support" |
317 | depends on I2C && VIDEO_V4L2 | 325 | depends on I2C && VIDEO_V4L2 |
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 7fb3add1b387..9f2e3214a482 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile | |||
@@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o | |||
69 | obj-$(CONFIG_VIDEO_OV7670) += ov7670.o | 69 | obj-$(CONFIG_VIDEO_OV7670) += ov7670.o |
70 | obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o | 70 | obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o |
71 | obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o | 71 | obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o |
72 | obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o | ||
72 | 73 | ||
73 | obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o | 74 | obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o |
74 | obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o | 75 | obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o |
diff --git a/drivers/media/video/mt9v011.c b/drivers/media/video/mt9v011.c new file mode 100644 index 000000000000..4389197cedd7 --- /dev/null +++ b/drivers/media/video/mt9v011.c | |||
@@ -0,0 +1,355 @@ | |||
1 | /* | ||
2 | * mt9v011 -Micron 1/4-Inch VGA Digital Image Sensor | ||
3 | * | ||
4 | * Copyright (c) 2009 Mauro Carvalho Chehab (mchehab@redhat.com) | ||
5 | * This code is placed under the terms of the GNU General Public License v2 | ||
6 | */ | ||
7 | |||
8 | #include <linux/i2c.h> | ||
9 | #include <linux/videodev2.h> | ||
10 | #include <linux/delay.h> | ||
11 | #include <media/v4l2-device.h> | ||
12 | #include "mt9v011.h" | ||
13 | #include <media/v4l2-i2c-drv.h> | ||
14 | #include <media/v4l2-chip-ident.h> | ||
15 | |||
16 | MODULE_DESCRIPTION("Micron mt9v011 sensor driver"); | ||
17 | MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); | ||
18 | MODULE_LICENSE("GPL"); | ||
19 | |||
20 | |||
21 | static int debug; | ||
22 | module_param(debug, int, 0); | ||
23 | MODULE_PARM_DESC(debug, "Debug level (0-2)"); | ||
24 | |||
25 | /* supported controls */ | ||
26 | static struct v4l2_queryctrl mt9v011_qctrl[] = { | ||
27 | { | ||
28 | .id = V4L2_CID_GAIN, | ||
29 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
30 | .name = "Gain", | ||
31 | .minimum = 0, | ||
32 | .maximum = (1 << 10) - 1, | ||
33 | .step = 1, | ||
34 | .default_value = 0x0020, | ||
35 | .flags = 0, | ||
36 | }, { | ||
37 | .id = V4L2_CID_RED_BALANCE, | ||
38 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
39 | .name = "Red Balance", | ||
40 | .minimum = -1 << 9, | ||
41 | .maximum = (1 << 9) - 1, | ||
42 | .step = 1, | ||
43 | .default_value = 0, | ||
44 | .flags = 0, | ||
45 | }, { | ||
46 | .id = V4L2_CID_BLUE_BALANCE, | ||
47 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
48 | .name = "Blue Balance", | ||
49 | .minimum = -1 << 9, | ||
50 | .maximum = (1 << 9) - 1, | ||
51 | .step = 1, | ||
52 | .default_value = 0, | ||
53 | .flags = 0, | ||
54 | }, | ||
55 | }; | ||
56 | |||
57 | struct mt9v011 { | ||
58 | struct v4l2_subdev sd; | ||
59 | |||
60 | u16 global_gain, red_bal, blue_bal; | ||
61 | }; | ||
62 | |||
63 | static inline struct mt9v011 *to_mt9v011(struct v4l2_subdev *sd) | ||
64 | { | ||
65 | return container_of(sd, struct mt9v011, sd); | ||
66 | } | ||
67 | |||
68 | static int mt9v011_read(struct v4l2_subdev *sd, unsigned char addr) | ||
69 | { | ||
70 | struct i2c_client *c = v4l2_get_subdevdata(sd); | ||
71 | __be16 buffer; | ||
72 | int rc, val; | ||
73 | |||
74 | if (1 != (rc = i2c_master_send(c, &addr, 1))) | ||
75 | v4l2_dbg(0, debug, sd, | ||
76 | "i2c i/o error: rc == %d (should be 1)\n", rc); | ||
77 | |||
78 | msleep(10); | ||
79 | |||
80 | if (2 != (rc = i2c_master_recv(c, (char *)&buffer, 2))) | ||
81 | v4l2_dbg(0, debug, sd, | ||
82 | "i2c i/o error: rc == %d (should be 1)\n", rc); | ||
83 | |||
84 | val = be16_to_cpu(buffer); | ||
85 | |||
86 | v4l2_dbg(2, debug, sd, "mt9v011: read 0x%02x = 0x%04x\n", addr, val); | ||
87 | |||
88 | return val; | ||
89 | } | ||
90 | |||
91 | static void mt9v011_write(struct v4l2_subdev *sd, unsigned char addr, | ||
92 | u16 value) | ||
93 | { | ||
94 | struct i2c_client *c = v4l2_get_subdevdata(sd); | ||
95 | unsigned char buffer[3]; | ||
96 | int rc; | ||
97 | |||
98 | buffer[0] = addr; | ||
99 | buffer[1] = value >> 8; | ||
100 | buffer[2] = value & 0xff; | ||
101 | |||
102 | v4l2_dbg(2, debug, sd, | ||
103 | "mt9v011: writing 0x%02x 0x%04x\n", buffer[0], value); | ||
104 | if (3 != (rc = i2c_master_send(c, buffer, 3))) | ||
105 | v4l2_dbg(0, debug, sd, | ||
106 | "i2c i/o error: rc == %d (should be 3)\n", rc); | ||
107 | } | ||
108 | |||
109 | |||
110 | struct i2c_reg_value { | ||
111 | unsigned char reg; | ||
112 | u16 value; | ||
113 | }; | ||
114 | |||
115 | /* | ||
116 | * Values used at the original driver | ||
117 | * Some values are marked as Reserved at the datasheet | ||
118 | */ | ||
119 | static const struct i2c_reg_value mt9v011_init_default[] = { | ||
120 | /* guessed meaning - as mt9m111 */ | ||
121 | { R0D_MT9V011_RESET, 0x0001 }, | ||
122 | { R0D_MT9V011_RESET, 0x0000 }, | ||
123 | { R01_MT9V011_ROWSTART, 0x0008 }, | ||
124 | { R02_MT9V011_COLSTART, 0x0014 }, | ||
125 | { R03_MT9V011_HEIGHT, 0x01e0 }, | ||
126 | { R04_MT9V011_WIDTH, 0x0280 }, | ||
127 | { R05_MT9V011_HBLANK, 0x0001 }, | ||
128 | { R05_MT9V011_HBLANK, 0x0001 }, | ||
129 | { R0A_MT9V011_CLK_SPEED, 0x0000 }, | ||
130 | { R05_MT9V011_HBLANK, 0x000a }, | ||
131 | { 0x30, 0x0005 }, | ||
132 | { 0x34, 0x0100 }, | ||
133 | { 0x3d, 0x068f }, | ||
134 | { 0x40, 0x01e0 }, | ||
135 | { 0x52, 0x0100 }, | ||
136 | { 0x58, 0x0038 }, /* Datasheet default 0x0078 */ | ||
137 | { 0x59, 0x0723 }, /* Datasheet default 0x0703 */ | ||
138 | { 0x62, 0x041a }, /* Datasheet default 0x0418 */ | ||
139 | { R09_MT9V011_SHUTTER_WIDTH, 0x0418 }, | ||
140 | { R20_MT9V011_READ_MODE, 0x1100 }, | ||
141 | }; | ||
142 | |||
143 | static void set_balance(struct v4l2_subdev *sd) | ||
144 | { | ||
145 | struct mt9v011 *core = to_mt9v011(sd); | ||
146 | u16 green1_gain, green2_gain, blue_gain, red_gain; | ||
147 | |||
148 | green1_gain = core->global_gain; | ||
149 | green2_gain = core->global_gain; | ||
150 | |||
151 | blue_gain = core->global_gain + | ||
152 | core->global_gain * core->blue_bal / (1 << 9); | ||
153 | |||
154 | red_gain = core->global_gain + | ||
155 | core->global_gain * core->blue_bal / (1 << 9); | ||
156 | |||
157 | mt9v011_write(sd, R2B_MT9V011_GREEN_1_GAIN, green1_gain); | ||
158 | mt9v011_write(sd, R2E_MT9V011_GREEN_2_GAIN, green1_gain); | ||
159 | mt9v011_write(sd, R2C_MT9V011_BLUE_GAIN, blue_gain); | ||
160 | mt9v011_write(sd, R2D_MT9V011_RED_GAIN, red_gain); | ||
161 | } | ||
162 | |||
163 | static int mt9v011_reset(struct v4l2_subdev *sd, u32 val) | ||
164 | { | ||
165 | u16 version; | ||
166 | int i; | ||
167 | |||
168 | version = mt9v011_read(sd, R00_MT9V011_CHIP_VERSION); | ||
169 | |||
170 | if (version != MT9V011_VERSION) { | ||
171 | v4l2_info(sd, "*** unknown micron chip detected (0x%04x.\n", | ||
172 | version); | ||
173 | return -EINVAL; | ||
174 | |||
175 | } | ||
176 | |||
177 | for (i = 0; i < ARRAY_SIZE(mt9v011_init_default); i++) | ||
178 | mt9v011_write(sd, mt9v011_init_default[i].reg, | ||
179 | mt9v011_init_default[i].value); | ||
180 | |||
181 | set_balance(sd); | ||
182 | |||
183 | return 0; | ||
184 | }; | ||
185 | |||
186 | static int mt9v011_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) | ||
187 | { | ||
188 | struct mt9v011 *core = to_mt9v011(sd); | ||
189 | |||
190 | v4l2_dbg(1, debug, sd, "g_ctrl called\n"); | ||
191 | |||
192 | switch (ctrl->id) { | ||
193 | case V4L2_CID_GAIN: | ||
194 | ctrl->value = core->global_gain; | ||
195 | return 0; | ||
196 | case V4L2_CID_RED_BALANCE: | ||
197 | ctrl->value = core->red_bal; | ||
198 | return 0; | ||
199 | case V4L2_CID_BLUE_BALANCE: | ||
200 | ctrl->value = core->blue_bal; | ||
201 | return 0; | ||
202 | } | ||
203 | return -EINVAL; | ||
204 | } | ||
205 | |||
206 | static int mt9v011_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) | ||
207 | { | ||
208 | struct mt9v011 *core = to_mt9v011(sd); | ||
209 | u8 i, n; | ||
210 | n = ARRAY_SIZE(mt9v011_qctrl); | ||
211 | |||
212 | for (i = 0; i < n; i++) { | ||
213 | if (ctrl->id != mt9v011_qctrl[i].id) | ||
214 | continue; | ||
215 | if (ctrl->value < mt9v011_qctrl[i].minimum || | ||
216 | ctrl->value > mt9v011_qctrl[i].maximum) | ||
217 | return -ERANGE; | ||
218 | v4l2_dbg(1, debug, sd, "s_ctrl: id=%d, value=%d\n", | ||
219 | ctrl->id, ctrl->value); | ||
220 | break; | ||
221 | } | ||
222 | |||
223 | switch (ctrl->id) { | ||
224 | case V4L2_CID_GAIN: | ||
225 | core->global_gain = ctrl->value; | ||
226 | break; | ||
227 | case V4L2_CID_RED_BALANCE: | ||
228 | core->red_bal = ctrl->value; | ||
229 | break; | ||
230 | case V4L2_CID_BLUE_BALANCE: | ||
231 | core->blue_bal = ctrl->value; | ||
232 | break; | ||
233 | default: | ||
234 | return -EINVAL; | ||
235 | } | ||
236 | |||
237 | set_balance(sd); | ||
238 | |||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | #ifdef CONFIG_VIDEO_ADV_DEBUG | ||
243 | static int mt9v011_g_register(struct v4l2_subdev *sd, | ||
244 | struct v4l2_dbg_register *reg) | ||
245 | { | ||
246 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
247 | |||
248 | if (!v4l2_chip_match_i2c_client(client, ®->match)) | ||
249 | return -EINVAL; | ||
250 | if (!capable(CAP_SYS_ADMIN)) | ||
251 | return -EPERM; | ||
252 | |||
253 | reg->val = mt9v011_read(sd, reg->reg & 0xff); | ||
254 | reg->size = 2; | ||
255 | |||
256 | return 0; | ||
257 | } | ||
258 | |||
259 | static int mt9v011_s_register(struct v4l2_subdev *sd, | ||
260 | struct v4l2_dbg_register *reg) | ||
261 | { | ||
262 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
263 | |||
264 | if (!v4l2_chip_match_i2c_client(client, ®->match)) | ||
265 | return -EINVAL; | ||
266 | if (!capable(CAP_SYS_ADMIN)) | ||
267 | return -EPERM; | ||
268 | |||
269 | mt9v011_write(sd, reg->reg & 0xff, reg->val & 0xffff); | ||
270 | |||
271 | return 0; | ||
272 | } | ||
273 | #endif | ||
274 | |||
275 | static int mt9v011_g_chip_ident(struct v4l2_subdev *sd, | ||
276 | struct v4l2_dbg_chip_ident *chip) | ||
277 | { | ||
278 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
279 | |||
280 | return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_MT9V011, | ||
281 | MT9V011_VERSION); | ||
282 | } | ||
283 | |||
284 | static const struct v4l2_subdev_core_ops mt9v011_core_ops = { | ||
285 | .g_ctrl = mt9v011_g_ctrl, | ||
286 | .s_ctrl = mt9v011_s_ctrl, | ||
287 | .reset = mt9v011_reset, | ||
288 | .g_chip_ident = mt9v011_g_chip_ident, | ||
289 | #ifdef CONFIG_VIDEO_ADV_DEBUG | ||
290 | .g_register = mt9v011_g_register, | ||
291 | .s_register = mt9v011_s_register, | ||
292 | #endif | ||
293 | }; | ||
294 | |||
295 | static const struct v4l2_subdev_ops mt9v011_ops = { | ||
296 | .core = &mt9v011_core_ops, | ||
297 | }; | ||
298 | |||
299 | |||
300 | /**************************************************************************** | ||
301 | I2C Client & Driver | ||
302 | ****************************************************************************/ | ||
303 | |||
304 | static int mt9v011_probe(struct i2c_client *c, | ||
305 | const struct i2c_device_id *id) | ||
306 | { | ||
307 | struct mt9v011 *core; | ||
308 | struct v4l2_subdev *sd; | ||
309 | |||
310 | /* Check if the adapter supports the needed features */ | ||
311 | if (!i2c_check_functionality(c->adapter, | ||
312 | I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) | ||
313 | return -EIO; | ||
314 | |||
315 | core = kzalloc(sizeof(struct mt9v011), GFP_KERNEL); | ||
316 | if (!core) | ||
317 | return -ENOMEM; | ||
318 | |||
319 | core->global_gain = 0x0024; | ||
320 | |||
321 | sd = &core->sd; | ||
322 | v4l2_i2c_subdev_init(sd, c, &mt9v011_ops); | ||
323 | v4l_info(c, "chip found @ 0x%02x (%s)\n", | ||
324 | c->addr << 1, c->adapter->name); | ||
325 | |||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static int mt9v011_remove(struct i2c_client *c) | ||
330 | { | ||
331 | struct v4l2_subdev *sd = i2c_get_clientdata(c); | ||
332 | |||
333 | v4l2_dbg(1, debug, sd, | ||
334 | "mt9v011.c: removing mt9v011 adapter on address 0x%x\n", | ||
335 | c->addr << 1); | ||
336 | |||
337 | v4l2_device_unregister_subdev(sd); | ||
338 | kfree(to_mt9v011(sd)); | ||
339 | return 0; | ||
340 | } | ||
341 | |||
342 | /* ----------------------------------------------------------------------- */ | ||
343 | |||
344 | static const struct i2c_device_id mt9v011_id[] = { | ||
345 | { "mt9v011", 0 }, | ||
346 | { } | ||
347 | }; | ||
348 | MODULE_DEVICE_TABLE(i2c, mt9v011_id); | ||
349 | |||
350 | static struct v4l2_i2c_driver_data v4l2_i2c_data = { | ||
351 | .name = "mt9v011", | ||
352 | .probe = mt9v011_probe, | ||
353 | .remove = mt9v011_remove, | ||
354 | .id_table = mt9v011_id, | ||
355 | }; | ||
diff --git a/drivers/media/video/mt9v011.h b/drivers/media/video/mt9v011.h new file mode 100644 index 000000000000..9e443ee30558 --- /dev/null +++ b/drivers/media/video/mt9v011.h | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * mt9v011 -Micron 1/4-Inch VGA Digital Image Sensor | ||
3 | * | ||
4 | * Copyright (c) 2009 Mauro Carvalho Chehab (mchehab@redhat.com) | ||
5 | * This code is placed under the terms of the GNU General Public License v2 | ||
6 | */ | ||
7 | |||
8 | #ifndef MT9V011_H_ | ||
9 | #define MT9V011_H_ | ||
10 | |||
11 | #define R00_MT9V011_CHIP_VERSION 0x00 | ||
12 | #define R01_MT9V011_ROWSTART 0x01 | ||
13 | #define R02_MT9V011_COLSTART 0x02 | ||
14 | #define R03_MT9V011_HEIGHT 0x03 | ||
15 | #define R04_MT9V011_WIDTH 0x04 | ||
16 | #define R05_MT9V011_HBLANK 0x05 | ||
17 | #define R06_MT9V011_VBLANK 0x06 | ||
18 | #define R07_MT9V011_OUT_CTRL 0x07 | ||
19 | #define R09_MT9V011_SHUTTER_WIDTH 0x09 | ||
20 | #define R0A_MT9V011_CLK_SPEED 0x0a | ||
21 | #define R0B_MT9V011_RESTART 0x0b | ||
22 | #define R0C_MT9V011_SHUTTER_DELAY 0x0c | ||
23 | #define R0D_MT9V011_RESET 0x0d | ||
24 | #define R1E_MT9V011_DIGITAL_ZOOM 0x1e | ||
25 | #define R20_MT9V011_READ_MODE 0x20 | ||
26 | #define R2B_MT9V011_GREEN_1_GAIN 0x2b | ||
27 | #define R2C_MT9V011_BLUE_GAIN 0x2c | ||
28 | #define R2D_MT9V011_RED_GAIN 0x2d | ||
29 | #define R2E_MT9V011_GREEN_2_GAIN 0x2e | ||
30 | #define R35_MT9V011_GLOBAL_GAIN 0x35 | ||
31 | #define RF1_MT9V011_CHIP_ENABLE 0xf1 | ||
32 | |||
33 | #define MT9V011_VERSION 0x8243 | ||
34 | |||
35 | #endif | ||