diff options
author | Robert Jarzmik <robert.jarzmik@free.fr> | 2008-08-14 11:02:51 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2008-10-12 07:36:50 -0400 |
commit | 77110abbfbfd7673be6d18ef0875350eabee7532 (patch) | |
tree | 979b59efc1e0c2266333ae1578ffaab81e65a78f /drivers/media/video/mt9m111.c | |
parent | 9cc6493d4b5b06e976e527737301d90044037b57 (diff) |
V4L/DVB (8684): Add support for Micron MT9M111 camera.
Adds support for Micron MT9M111 camera chip, with basic
features :
- view rectangle configurable
- some output formats (bayer8, bayer10, rgb565, rgb555, ycbycr)
- autoexposure
- only highpower mode context used (ie. context B)
create mode 100644 drivers/media/video/mt9m111.c
Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/video/mt9m111.c')
-rw-r--r-- | drivers/media/video/mt9m111.c | 960 |
1 files changed, 960 insertions, 0 deletions
diff --git a/drivers/media/video/mt9m111.c b/drivers/media/video/mt9m111.c new file mode 100644 index 000000000000..0c88e5d6dc44 --- /dev/null +++ b/drivers/media/video/mt9m111.c | |||
@@ -0,0 +1,960 @@ | |||
1 | /* | ||
2 | * Driver for MT9M111 CMOS Image Sensor from Micron | ||
3 | * | ||
4 | * Copyright (C) 2008, Robert Jarzmik <robert.jarzmik@free.fr> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | #include <linux/videodev2.h> | ||
11 | #include <linux/slab.h> | ||
12 | #include <linux/i2c.h> | ||
13 | #include <linux/log2.h> | ||
14 | #include <linux/gpio.h> | ||
15 | #include <linux/delay.h> | ||
16 | |||
17 | #include <media/v4l2-common.h> | ||
18 | #include <media/v4l2-chip-ident.h> | ||
19 | #include <media/soc_camera.h> | ||
20 | |||
21 | /* | ||
22 | * mt9m111 i2c address is 0x5d or 0x48 (depending on SAddr pin) | ||
23 | * The platform has to define i2c_board_info and call i2c_register_board_info() | ||
24 | */ | ||
25 | |||
26 | /* mt9m111: Sensor register addresses */ | ||
27 | #define MT9M111_CHIP_VERSION 0x000 | ||
28 | #define MT9M111_ROW_START 0x001 | ||
29 | #define MT9M111_COLUMN_START 0x002 | ||
30 | #define MT9M111_WINDOW_HEIGHT 0x003 | ||
31 | #define MT9M111_WINDOW_WIDTH 0x004 | ||
32 | #define MT9M111_HORIZONTAL_BLANKING_B 0x005 | ||
33 | #define MT9M111_VERTICAL_BLANKING_B 0x006 | ||
34 | #define MT9M111_HORIZONTAL_BLANKING_A 0x007 | ||
35 | #define MT9M111_VERTICAL_BLANKING_A 0x008 | ||
36 | #define MT9M111_SHUTTER_WIDTH 0x009 | ||
37 | #define MT9M111_ROW_SPEED 0x00a | ||
38 | #define MT9M111_EXTRA_DELAY 0x00b | ||
39 | #define MT9M111_SHUTTER_DELAY 0x00c | ||
40 | #define MT9M111_RESET 0x00d | ||
41 | #define MT9M111_READ_MODE_B 0x020 | ||
42 | #define MT9M111_READ_MODE_A 0x021 | ||
43 | #define MT9M111_FLASH_CONTROL 0x023 | ||
44 | #define MT9M111_GREEN1_GAIN 0x02b | ||
45 | #define MT9M111_BLUE_GAIN 0x02c | ||
46 | #define MT9M111_RED_GAIN 0x02d | ||
47 | #define MT9M111_GREEN2_GAIN 0x02e | ||
48 | #define MT9M111_GLOBAL_GAIN 0x02f | ||
49 | #define MT9M111_CONTEXT_CONTROL 0x0c8 | ||
50 | #define MT9M111_PAGE_MAP 0x0f0 | ||
51 | #define MT9M111_BYTE_WISE_ADDR 0x0f1 | ||
52 | |||
53 | #define MT9M111_RESET_SYNC_CHANGES (1 << 15) | ||
54 | #define MT9M111_RESET_RESTART_BAD_FRAME (1 << 9) | ||
55 | #define MT9M111_RESET_SHOW_BAD_FRAMES (1 << 8) | ||
56 | #define MT9M111_RESET_RESET_SOC (1 << 5) | ||
57 | #define MT9M111_RESET_OUTPUT_DISABLE (1 << 4) | ||
58 | #define MT9M111_RESET_CHIP_ENABLE (1 << 3) | ||
59 | #define MT9M111_RESET_ANALOG_STANDBY (1 << 2) | ||
60 | #define MT9M111_RESET_RESTART_FRAME (1 << 1) | ||
61 | #define MT9M111_RESET_RESET_MODE (1 << 0) | ||
62 | |||
63 | #define MT9M111_RMB_MIRROR_COLS (1 << 1) | ||
64 | #define MT9M111_RMB_MIRROR_ROWS (1 << 0) | ||
65 | #define MT9M111_CTXT_CTRL_RESTART (1 << 15) | ||
66 | #define MT9M111_CTXT_CTRL_DEFECTCOR_B (1 << 12) | ||
67 | #define MT9M111_CTXT_CTRL_RESIZE_B (1 << 10) | ||
68 | #define MT9M111_CTXT_CTRL_CTRL2_B (1 << 9) | ||
69 | #define MT9M111_CTXT_CTRL_GAMMA_B (1 << 8) | ||
70 | #define MT9M111_CTXT_CTRL_XENON_EN (1 << 7) | ||
71 | #define MT9M111_CTXT_CTRL_READ_MODE_B (1 << 3) | ||
72 | #define MT9M111_CTXT_CTRL_LED_FLASH_EN (1 << 2) | ||
73 | #define MT9M111_CTXT_CTRL_VBLANK_SEL_B (1 << 1) | ||
74 | #define MT9M111_CTXT_CTRL_HBLANK_SEL_B (1 << 0) | ||
75 | /* | ||
76 | * mt9m111: Colorpipe register addresses (0x100..0x1ff) | ||
77 | */ | ||
78 | #define MT9M111_OPER_MODE_CTRL 0x106 | ||
79 | #define MT9M111_OUTPUT_FORMAT_CTRL 0x108 | ||
80 | #define MT9M111_REDUCER_XZOOM_B 0x1a0 | ||
81 | #define MT9M111_REDUCER_XSIZE_B 0x1a1 | ||
82 | #define MT9M111_REDUCER_YZOOM_B 0x1a3 | ||
83 | #define MT9M111_REDUCER_YSIZE_B 0x1a4 | ||
84 | #define MT9M111_REDUCER_XZOOM_A 0x1a6 | ||
85 | #define MT9M111_REDUCER_XSIZE_A 0x1a7 | ||
86 | #define MT9M111_REDUCER_YZOOM_A 0x1a9 | ||
87 | #define MT9M111_REDUCER_YSIZE_A 0x1aa | ||
88 | |||
89 | #define MT9M111_OUTPUT_FORMAT_CTRL2_A 0x13a | ||
90 | #define MT9M111_OUTPUT_FORMAT_CTRL2_B 0x19b | ||
91 | |||
92 | #define MT9M111_OPMODE_AUTOEXPO_EN (1 << 14) | ||
93 | |||
94 | |||
95 | #define MT9M111_OUTFMT_PROCESSED_BAYER (1 << 14) | ||
96 | #define MT9M111_OUTFMT_BYPASS_IFP (1 << 10) | ||
97 | #define MT9M111_OUTFMT_INV_PIX_CLOCK (1 << 9) | ||
98 | #define MT9M111_OUTFMT_RGB (1 << 8) | ||
99 | #define MT9M111_OUTFMT_RGB565 (0x0 << 6) | ||
100 | #define MT9M111_OUTFMT_RGB555 (0x1 << 6) | ||
101 | #define MT9M111_OUTFMT_RGB444x (0x2 << 6) | ||
102 | #define MT9M111_OUTFMT_RGBx444 (0x3 << 6) | ||
103 | #define MT9M111_OUTFMT_TST_RAMP_OFF (0x0 << 4) | ||
104 | #define MT9M111_OUTFMT_TST_RAMP_COL (0x1 << 4) | ||
105 | #define MT9M111_OUTFMT_TST_RAMP_ROW (0x2 << 4) | ||
106 | #define MT9M111_OUTFMT_TST_RAMP_FRAME (0x3 << 4) | ||
107 | #define MT9M111_OUTFMT_SHIFT_3_UP (1 << 3) | ||
108 | #define MT9M111_OUTFMT_AVG_CHROMA (1 << 2) | ||
109 | #define MT9M111_OUTFMT_SWAP_YCbCr_C_Y (1 << 1) | ||
110 | #define MT9M111_OUTFMT_SWAP_RGB_EVEN (1 << 1) | ||
111 | #define MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr (1 << 0) | ||
112 | /* | ||
113 | * mt9m111: Camera control register addresses (0x200..0x2ff not implemented) | ||
114 | */ | ||
115 | |||
116 | #define reg_read(reg) mt9m111_reg_read(icd, MT9M111_##reg) | ||
117 | #define reg_write(reg, val) mt9m111_reg_write(icd, MT9M111_##reg, (val)) | ||
118 | #define reg_set(reg, val) mt9m111_reg_set(icd, MT9M111_##reg, (val)) | ||
119 | #define reg_clear(reg, val) mt9m111_reg_clear(icd, MT9M111_##reg, (val)) | ||
120 | |||
121 | #define MT9M111_MIN_DARK_ROWS 8 | ||
122 | #define MT9M111_MIN_DARK_COLS 24 | ||
123 | #define MT9M111_MAX_HEIGHT 1024 | ||
124 | #define MT9M111_MAX_WIDTH 1280 | ||
125 | |||
126 | #define COL_FMT(_name, _depth, _fourcc, _colorspace) \ | ||
127 | { .name = _name, .depth = _depth, .fourcc = _fourcc, \ | ||
128 | .colorspace = _colorspace } | ||
129 | #define RGB_FMT(_name, _depth, _fourcc) \ | ||
130 | COL_FMT(_name, _depth, _fourcc, V4L2_COLORSPACE_SRGB) | ||
131 | |||
132 | static const struct soc_camera_data_format mt9m111_colour_formats[] = { | ||
133 | COL_FMT("YCrYCb 8 bit", 8, V4L2_PIX_FMT_YUYV, V4L2_COLORSPACE_JPEG), | ||
134 | RGB_FMT("RGB 565", 16, V4L2_PIX_FMT_RGB565), | ||
135 | RGB_FMT("RGB 555", 16, V4L2_PIX_FMT_RGB555), | ||
136 | RGB_FMT("Bayer (sRGB) 10 bit", 10, V4L2_PIX_FMT_SBGGR16), | ||
137 | RGB_FMT("Bayer (sRGB) 8 bit", 8, V4L2_PIX_FMT_SBGGR8), | ||
138 | }; | ||
139 | |||
140 | enum mt9m111_context { | ||
141 | HIGHPOWER = 0, | ||
142 | LOWPOWER, | ||
143 | }; | ||
144 | |||
145 | struct mt9m111 { | ||
146 | struct i2c_client *client; | ||
147 | struct soc_camera_device icd; | ||
148 | int model; /* V4L2_IDENT_MT9M111* codes from v4l2-chip-ident.h */ | ||
149 | enum mt9m111_context context; | ||
150 | unsigned int left, top, width, height; | ||
151 | u32 pixfmt; | ||
152 | unsigned char autoexposure; | ||
153 | unsigned char datawidth; | ||
154 | unsigned int powered:1; | ||
155 | unsigned int hflip:1; | ||
156 | unsigned int vflip:1; | ||
157 | unsigned int swap_rgb_even_odd:1; | ||
158 | unsigned int swap_rgb_red_blue:1; | ||
159 | unsigned int swap_yuv_y_chromas:1; | ||
160 | unsigned int swap_yuv_cb_cr:1; | ||
161 | }; | ||
162 | |||
163 | static int reg_page_map_set(struct i2c_client *client, const u16 reg) | ||
164 | { | ||
165 | int ret; | ||
166 | u16 page; | ||
167 | static int lastpage = -1; /* PageMap cache value */ | ||
168 | |||
169 | page = (reg >> 8); | ||
170 | if (page == lastpage) | ||
171 | return 0; | ||
172 | if (page > 2) | ||
173 | return -EINVAL; | ||
174 | |||
175 | ret = i2c_smbus_write_word_data(client, MT9M111_PAGE_MAP, swab16(page)); | ||
176 | if (ret >= 0) | ||
177 | lastpage = page; | ||
178 | return ret; | ||
179 | } | ||
180 | |||
181 | static int mt9m111_reg_read(struct soc_camera_device *icd, const u16 reg) | ||
182 | { | ||
183 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
184 | struct i2c_client *client = mt9m111->client; | ||
185 | int ret; | ||
186 | |||
187 | ret = reg_page_map_set(client, reg); | ||
188 | if (!ret) | ||
189 | ret = swab16(i2c_smbus_read_word_data(client, (reg & 0xff))); | ||
190 | |||
191 | dev_dbg(&icd->dev, "read reg.%03x -> %04x\n", reg, ret); | ||
192 | return ret; | ||
193 | } | ||
194 | |||
195 | static int mt9m111_reg_write(struct soc_camera_device *icd, const u16 reg, | ||
196 | const u16 data) | ||
197 | { | ||
198 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
199 | struct i2c_client *client = mt9m111->client; | ||
200 | int ret; | ||
201 | |||
202 | ret = reg_page_map_set(client, reg); | ||
203 | if (ret >= 0) | ||
204 | ret = i2c_smbus_write_word_data(mt9m111->client, (reg & 0xff), | ||
205 | swab16(data)); | ||
206 | dev_dbg(&icd->dev, "write reg.%03x = %04x -> %d\n", reg, data, ret); | ||
207 | return ret; | ||
208 | } | ||
209 | |||
210 | static int mt9m111_reg_set(struct soc_camera_device *icd, const u16 reg, | ||
211 | const u16 data) | ||
212 | { | ||
213 | int ret; | ||
214 | |||
215 | ret = mt9m111_reg_read(icd, reg); | ||
216 | if (ret >= 0) | ||
217 | ret = mt9m111_reg_write(icd, reg, ret | data); | ||
218 | return ret; | ||
219 | } | ||
220 | |||
221 | static int mt9m111_reg_clear(struct soc_camera_device *icd, const u16 reg, | ||
222 | const u16 data) | ||
223 | { | ||
224 | int ret; | ||
225 | |||
226 | ret = mt9m111_reg_read(icd, reg); | ||
227 | return mt9m111_reg_write(icd, reg, ret & ~data); | ||
228 | } | ||
229 | |||
230 | static int mt9m111_set_context(struct soc_camera_device *icd, | ||
231 | enum mt9m111_context ctxt) | ||
232 | { | ||
233 | int valB = MT9M111_CTXT_CTRL_RESTART | MT9M111_CTXT_CTRL_DEFECTCOR_B | ||
234 | | MT9M111_CTXT_CTRL_RESIZE_B | MT9M111_CTXT_CTRL_CTRL2_B | ||
235 | | MT9M111_CTXT_CTRL_GAMMA_B | MT9M111_CTXT_CTRL_READ_MODE_B | ||
236 | | MT9M111_CTXT_CTRL_VBLANK_SEL_B | ||
237 | | MT9M111_CTXT_CTRL_HBLANK_SEL_B; | ||
238 | int valA = MT9M111_CTXT_CTRL_RESTART; | ||
239 | |||
240 | if (ctxt == HIGHPOWER) | ||
241 | return reg_write(CONTEXT_CONTROL, valB); | ||
242 | else | ||
243 | return reg_write(CONTEXT_CONTROL, valA); | ||
244 | } | ||
245 | |||
246 | static int mt9m111_setup_rect(struct soc_camera_device *icd) | ||
247 | { | ||
248 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
249 | int ret = 0, is_raw_format; | ||
250 | int width = mt9m111->width; | ||
251 | int height = mt9m111->height; | ||
252 | |||
253 | if ((mt9m111->pixfmt == V4L2_PIX_FMT_SBGGR8) | ||
254 | || (mt9m111->pixfmt == V4L2_PIX_FMT_SBGGR16)) | ||
255 | is_raw_format = 1; | ||
256 | else | ||
257 | is_raw_format = 0; | ||
258 | |||
259 | if (ret >= 0) | ||
260 | ret = reg_write(COLUMN_START, mt9m111->left); | ||
261 | if (ret >= 0) | ||
262 | ret = reg_write(ROW_START, mt9m111->top); | ||
263 | |||
264 | if (is_raw_format) { | ||
265 | if (ret >= 0) | ||
266 | ret = reg_write(WINDOW_WIDTH, width); | ||
267 | if (ret >= 0) | ||
268 | ret = reg_write(WINDOW_HEIGHT, height); | ||
269 | } else { | ||
270 | if (ret >= 0) | ||
271 | ret = reg_write(REDUCER_XZOOM_B, MT9M111_MAX_WIDTH); | ||
272 | if (ret >= 0) | ||
273 | ret = reg_write(REDUCER_YZOOM_B, MT9M111_MAX_HEIGHT); | ||
274 | if (ret >= 0) | ||
275 | ret = reg_write(REDUCER_XSIZE_B, width); | ||
276 | if (ret >= 0) | ||
277 | ret = reg_write(REDUCER_YSIZE_B, height); | ||
278 | if (ret >= 0) | ||
279 | ret = reg_write(REDUCER_XZOOM_A, MT9M111_MAX_WIDTH); | ||
280 | if (ret >= 0) | ||
281 | ret = reg_write(REDUCER_YZOOM_A, MT9M111_MAX_HEIGHT); | ||
282 | if (ret >= 0) | ||
283 | ret = reg_write(REDUCER_XSIZE_A, width); | ||
284 | if (ret >= 0) | ||
285 | ret = reg_write(REDUCER_YSIZE_A, height); | ||
286 | } | ||
287 | |||
288 | return ret; | ||
289 | } | ||
290 | |||
291 | static int mt9m111_setup_pixfmt(struct soc_camera_device *icd, u16 outfmt) | ||
292 | { | ||
293 | int ret; | ||
294 | |||
295 | ret = reg_write(OUTPUT_FORMAT_CTRL2_A, outfmt); | ||
296 | if (ret >= 0) | ||
297 | ret = reg_write(OUTPUT_FORMAT_CTRL2_B, outfmt); | ||
298 | return ret; | ||
299 | } | ||
300 | |||
301 | static int mt9m111_setfmt_bayer8(struct soc_camera_device *icd) | ||
302 | { | ||
303 | return mt9m111_setup_pixfmt(icd, MT9M111_OUTFMT_PROCESSED_BAYER); | ||
304 | } | ||
305 | |||
306 | static int mt9m111_setfmt_bayer10(struct soc_camera_device *icd) | ||
307 | { | ||
308 | |||
309 | return mt9m111_setup_pixfmt(icd, MT9M111_OUTFMT_BYPASS_IFP); | ||
310 | } | ||
311 | |||
312 | static int mt9m111_setfmt_rgb565(struct soc_camera_device *icd) | ||
313 | { | ||
314 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
315 | int val = 0; | ||
316 | |||
317 | if (mt9m111->swap_rgb_red_blue) | ||
318 | val |= MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr; | ||
319 | if (mt9m111->swap_rgb_even_odd) | ||
320 | val |= MT9M111_OUTFMT_SWAP_RGB_EVEN; | ||
321 | val |= MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565; | ||
322 | |||
323 | return mt9m111_setup_pixfmt(icd, val); | ||
324 | } | ||
325 | |||
326 | static int mt9m111_setfmt_rgb555(struct soc_camera_device *icd) | ||
327 | { | ||
328 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
329 | int val = 0; | ||
330 | |||
331 | if (mt9m111->swap_rgb_red_blue) | ||
332 | val |= MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr; | ||
333 | if (mt9m111->swap_rgb_even_odd) | ||
334 | val |= MT9M111_OUTFMT_SWAP_RGB_EVEN; | ||
335 | val |= MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB555; | ||
336 | |||
337 | return mt9m111_setup_pixfmt(icd, val); | ||
338 | } | ||
339 | |||
340 | static int mt9m111_setfmt_yuv(struct soc_camera_device *icd) | ||
341 | { | ||
342 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
343 | int val = 0; | ||
344 | |||
345 | if (mt9m111->swap_yuv_cb_cr) | ||
346 | val |= MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr; | ||
347 | if (mt9m111->swap_yuv_y_chromas) | ||
348 | val |= MT9M111_OUTFMT_SWAP_YCbCr_C_Y; | ||
349 | |||
350 | return mt9m111_setup_pixfmt(icd, val); | ||
351 | } | ||
352 | |||
353 | static int mt9m111_enable(struct soc_camera_device *icd) | ||
354 | { | ||
355 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
356 | int ret; | ||
357 | |||
358 | ret = reg_set(RESET, MT9M111_RESET_CHIP_ENABLE); | ||
359 | if (ret >= 0) | ||
360 | mt9m111->powered = 1; | ||
361 | return ret; | ||
362 | } | ||
363 | |||
364 | static int mt9m111_disable(struct soc_camera_device *icd) | ||
365 | { | ||
366 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
367 | int ret; | ||
368 | |||
369 | ret = reg_clear(RESET, MT9M111_RESET_CHIP_ENABLE); | ||
370 | if (ret >= 0) | ||
371 | mt9m111->powered = 0; | ||
372 | return ret; | ||
373 | } | ||
374 | |||
375 | static int mt9m111_reset(struct soc_camera_device *icd) | ||
376 | { | ||
377 | int ret; | ||
378 | |||
379 | ret = reg_set(RESET, MT9M111_RESET_RESET_MODE); | ||
380 | if (ret >= 0) | ||
381 | ret = reg_set(RESET, MT9M111_RESET_RESET_SOC); | ||
382 | if (ret >= 0) | ||
383 | ret = reg_clear(RESET, MT9M111_RESET_RESET_MODE | ||
384 | | MT9M111_RESET_RESET_SOC); | ||
385 | return ret; | ||
386 | } | ||
387 | |||
388 | static int mt9m111_start_capture(struct soc_camera_device *icd) | ||
389 | { | ||
390 | return 0; | ||
391 | } | ||
392 | |||
393 | static int mt9m111_stop_capture(struct soc_camera_device *icd) | ||
394 | { | ||
395 | return 0; | ||
396 | } | ||
397 | |||
398 | static unsigned long mt9m111_query_bus_param(struct soc_camera_device *icd) | ||
399 | { | ||
400 | return SOCAM_MASTER | SOCAM_PCLK_SAMPLE_RISING | | ||
401 | SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_HIGH | | ||
402 | SOCAM_DATAWIDTH_8; | ||
403 | } | ||
404 | |||
405 | static int mt9m111_set_bus_param(struct soc_camera_device *icd, unsigned long f) | ||
406 | { | ||
407 | return 0; | ||
408 | } | ||
409 | |||
410 | static int mt9m111_set_pixfmt(struct soc_camera_device *icd, u32 pixfmt) | ||
411 | { | ||
412 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
413 | int ret = 0; | ||
414 | |||
415 | switch (pixfmt) { | ||
416 | case V4L2_PIX_FMT_SBGGR8: | ||
417 | ret = mt9m111_setfmt_bayer8(icd); | ||
418 | break; | ||
419 | case V4L2_PIX_FMT_SBGGR16: | ||
420 | ret = mt9m111_setfmt_bayer10(icd); | ||
421 | break; | ||
422 | case V4L2_PIX_FMT_RGB555: | ||
423 | ret = mt9m111_setfmt_rgb555(icd); | ||
424 | break; | ||
425 | case V4L2_PIX_FMT_RGB565: | ||
426 | ret = mt9m111_setfmt_rgb565(icd); | ||
427 | break; | ||
428 | case V4L2_PIX_FMT_YUYV: | ||
429 | ret = mt9m111_setfmt_yuv(icd); | ||
430 | break; | ||
431 | default: | ||
432 | dev_err(&icd->dev, "Pixel format not handled : %x\n", pixfmt); | ||
433 | ret = -EINVAL; | ||
434 | } | ||
435 | |||
436 | if (ret >= 0) | ||
437 | mt9m111->pixfmt = pixfmt; | ||
438 | |||
439 | return ret; | ||
440 | } | ||
441 | |||
442 | static int mt9m111_set_fmt_cap(struct soc_camera_device *icd, | ||
443 | __u32 pixfmt, struct v4l2_rect *rect) | ||
444 | { | ||
445 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
446 | int ret = 0; | ||
447 | |||
448 | mt9m111->left = rect->left; | ||
449 | mt9m111->top = rect->top; | ||
450 | mt9m111->width = rect->width; | ||
451 | mt9m111->height = rect->height; | ||
452 | |||
453 | dev_dbg(&icd->dev, "%s fmt=%x left=%d, top=%d, width=%d, height=%d\n", | ||
454 | __func__, pixfmt, mt9m111->left, mt9m111->top, mt9m111->width, | ||
455 | mt9m111->height); | ||
456 | |||
457 | ret = mt9m111_setup_rect(icd); | ||
458 | if (ret >= 0) | ||
459 | ret = mt9m111_set_pixfmt(icd, pixfmt); | ||
460 | return ret < 0 ? ret : 0; | ||
461 | } | ||
462 | |||
463 | static int mt9m111_try_fmt_cap(struct soc_camera_device *icd, | ||
464 | struct v4l2_format *f) | ||
465 | { | ||
466 | if (f->fmt.pix.height > MT9M111_MAX_HEIGHT) | ||
467 | f->fmt.pix.height = MT9M111_MAX_HEIGHT; | ||
468 | if (f->fmt.pix.width > MT9M111_MAX_WIDTH) | ||
469 | f->fmt.pix.width = MT9M111_MAX_WIDTH; | ||
470 | |||
471 | return 0; | ||
472 | } | ||
473 | |||
474 | static int mt9m111_get_chip_id(struct soc_camera_device *icd, | ||
475 | struct v4l2_chip_ident *id) | ||
476 | { | ||
477 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
478 | |||
479 | if (id->match_type != V4L2_CHIP_MATCH_I2C_ADDR) | ||
480 | return -EINVAL; | ||
481 | |||
482 | if (id->match_chip != mt9m111->client->addr) | ||
483 | return -ENODEV; | ||
484 | |||
485 | id->ident = mt9m111->model; | ||
486 | id->revision = 0; | ||
487 | |||
488 | return 0; | ||
489 | } | ||
490 | |||
491 | #ifdef CONFIG_VIDEO_ADV_DEBUG | ||
492 | static int mt9m111_get_register(struct soc_camera_device *icd, | ||
493 | struct v4l2_register *reg) | ||
494 | { | ||
495 | int val; | ||
496 | |||
497 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
498 | |||
499 | if (reg->match_type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0x2ff) | ||
500 | return -EINVAL; | ||
501 | if (reg->match_chip != mt9m111->client->addr) | ||
502 | return -ENODEV; | ||
503 | |||
504 | val = mt9m111_reg_read(icd, reg->reg); | ||
505 | reg->val = (u64)val; | ||
506 | |||
507 | if (reg->val > 0xffff) | ||
508 | return -EIO; | ||
509 | |||
510 | return 0; | ||
511 | } | ||
512 | |||
513 | static int mt9m111_set_register(struct soc_camera_device *icd, | ||
514 | struct v4l2_register *reg) | ||
515 | { | ||
516 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
517 | |||
518 | if (reg->match_type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0x2ff) | ||
519 | return -EINVAL; | ||
520 | |||
521 | if (reg->match_chip != mt9m111->client->addr) | ||
522 | return -ENODEV; | ||
523 | |||
524 | if (mt9m111_reg_write(icd, reg->reg, reg->val) < 0) | ||
525 | return -EIO; | ||
526 | |||
527 | return 0; | ||
528 | } | ||
529 | #endif | ||
530 | |||
531 | static const struct v4l2_queryctrl mt9m111_controls[] = { | ||
532 | { | ||
533 | .id = V4L2_CID_VFLIP, | ||
534 | .type = V4L2_CTRL_TYPE_BOOLEAN, | ||
535 | .name = "Flip Verticaly", | ||
536 | .minimum = 0, | ||
537 | .maximum = 1, | ||
538 | .step = 1, | ||
539 | .default_value = 0, | ||
540 | }, { | ||
541 | .id = V4L2_CID_HFLIP, | ||
542 | .type = V4L2_CTRL_TYPE_BOOLEAN, | ||
543 | .name = "Flip Horizontaly", | ||
544 | .minimum = 0, | ||
545 | .maximum = 1, | ||
546 | .step = 1, | ||
547 | .default_value = 0, | ||
548 | }, { /* gain = 1/32*val (=>gain=1 if val==32) */ | ||
549 | .id = V4L2_CID_GAIN, | ||
550 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
551 | .name = "Gain", | ||
552 | .minimum = 0, | ||
553 | .maximum = 63 * 2 * 2, | ||
554 | .step = 1, | ||
555 | .default_value = 32, | ||
556 | .flags = V4L2_CTRL_FLAG_SLIDER, | ||
557 | }, { | ||
558 | .id = V4L2_CID_EXPOSURE_AUTO, | ||
559 | .type = V4L2_CTRL_TYPE_BOOLEAN, | ||
560 | .name = "Auto Exposure", | ||
561 | .minimum = 0, | ||
562 | .maximum = 1, | ||
563 | .step = 1, | ||
564 | .default_value = 1, | ||
565 | } | ||
566 | }; | ||
567 | |||
568 | static int mt9m111_video_probe(struct soc_camera_device *); | ||
569 | static void mt9m111_video_remove(struct soc_camera_device *); | ||
570 | static int mt9m111_get_control(struct soc_camera_device *, | ||
571 | struct v4l2_control *); | ||
572 | static int mt9m111_set_control(struct soc_camera_device *, | ||
573 | struct v4l2_control *); | ||
574 | static int mt9m111_resume(struct soc_camera_device *icd); | ||
575 | static int mt9m111_init(struct soc_camera_device *icd); | ||
576 | static int mt9m111_release(struct soc_camera_device *icd); | ||
577 | |||
578 | static struct soc_camera_ops mt9m111_ops = { | ||
579 | .owner = THIS_MODULE, | ||
580 | .probe = mt9m111_video_probe, | ||
581 | .remove = mt9m111_video_remove, | ||
582 | .init = mt9m111_init, | ||
583 | .resume = mt9m111_resume, | ||
584 | .release = mt9m111_release, | ||
585 | .start_capture = mt9m111_start_capture, | ||
586 | .stop_capture = mt9m111_stop_capture, | ||
587 | .set_fmt_cap = mt9m111_set_fmt_cap, | ||
588 | .try_fmt_cap = mt9m111_try_fmt_cap, | ||
589 | .query_bus_param = mt9m111_query_bus_param, | ||
590 | .set_bus_param = mt9m111_set_bus_param, | ||
591 | .controls = mt9m111_controls, | ||
592 | .num_controls = ARRAY_SIZE(mt9m111_controls), | ||
593 | .get_control = mt9m111_get_control, | ||
594 | .set_control = mt9m111_set_control, | ||
595 | .get_chip_id = mt9m111_get_chip_id, | ||
596 | #ifdef CONFIG_VIDEO_ADV_DEBUG | ||
597 | .get_register = mt9m111_get_register, | ||
598 | .set_register = mt9m111_set_register, | ||
599 | #endif | ||
600 | }; | ||
601 | |||
602 | static int mt9m111_set_flip(struct soc_camera_device *icd, int flip, int mask) | ||
603 | { | ||
604 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
605 | int ret; | ||
606 | |||
607 | if (mt9m111->context == HIGHPOWER) { | ||
608 | if (flip) | ||
609 | ret = reg_set(READ_MODE_B, mask); | ||
610 | else | ||
611 | ret = reg_clear(READ_MODE_B, mask); | ||
612 | } else { | ||
613 | if (flip) | ||
614 | ret = reg_set(READ_MODE_A, mask); | ||
615 | else | ||
616 | ret = reg_clear(READ_MODE_A, mask); | ||
617 | } | ||
618 | |||
619 | return ret; | ||
620 | } | ||
621 | |||
622 | static int mt9m111_get_global_gain(struct soc_camera_device *icd) | ||
623 | { | ||
624 | unsigned int data, gain; | ||
625 | |||
626 | data = reg_read(GLOBAL_GAIN); | ||
627 | if (data >= 0) | ||
628 | gain = ((data & (1 << 10)) * 2) | ||
629 | | ((data & (1 << 9)) * 2) | ||
630 | | (data & 0x2f); | ||
631 | else | ||
632 | gain = data; | ||
633 | |||
634 | return gain; | ||
635 | } | ||
636 | static int mt9m111_set_global_gain(struct soc_camera_device *icd, int gain) | ||
637 | { | ||
638 | u16 val; | ||
639 | |||
640 | if (gain > 63 * 2 * 2) | ||
641 | return -EINVAL; | ||
642 | |||
643 | icd->gain = gain; | ||
644 | if ((gain >= 64 * 2) && (gain < 63 * 2 * 2)) | ||
645 | val = (1 << 10) | (1 << 9) | (gain / 4); | ||
646 | else if ((gain >= 64) && (gain < 64 * 2)) | ||
647 | val = (1<<9) | (gain / 2); | ||
648 | else | ||
649 | val = gain; | ||
650 | |||
651 | return reg_write(GLOBAL_GAIN, val); | ||
652 | } | ||
653 | |||
654 | static int mt9m111_set_autoexposure(struct soc_camera_device *icd, int on) | ||
655 | { | ||
656 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
657 | int ret; | ||
658 | |||
659 | if (on) | ||
660 | ret = reg_set(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOEXPO_EN); | ||
661 | else | ||
662 | ret = reg_clear(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOEXPO_EN); | ||
663 | |||
664 | if (ret >= 0) | ||
665 | mt9m111->autoexposure = on; | ||
666 | |||
667 | return ret; | ||
668 | } | ||
669 | static int mt9m111_get_control(struct soc_camera_device *icd, | ||
670 | struct v4l2_control *ctrl) | ||
671 | { | ||
672 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
673 | int data; | ||
674 | |||
675 | switch (ctrl->id) { | ||
676 | case V4L2_CID_VFLIP: | ||
677 | if (mt9m111->context == HIGHPOWER) | ||
678 | data = reg_read(READ_MODE_B); | ||
679 | else | ||
680 | data = reg_read(READ_MODE_A); | ||
681 | |||
682 | if (data < 0) | ||
683 | return -EIO; | ||
684 | ctrl->value = !!(data & MT9M111_RMB_MIRROR_ROWS); | ||
685 | break; | ||
686 | case V4L2_CID_HFLIP: | ||
687 | if (mt9m111->context == HIGHPOWER) | ||
688 | data = reg_read(READ_MODE_B); | ||
689 | else | ||
690 | data = reg_read(READ_MODE_A); | ||
691 | |||
692 | if (data < 0) | ||
693 | return -EIO; | ||
694 | ctrl->value = !!(data & MT9M111_RMB_MIRROR_COLS); | ||
695 | break; | ||
696 | case V4L2_CID_GAIN: | ||
697 | data = mt9m111_get_global_gain(icd); | ||
698 | if (data < 0) | ||
699 | return data; | ||
700 | ctrl->value = data; | ||
701 | break; | ||
702 | case V4L2_CID_EXPOSURE_AUTO: | ||
703 | ctrl->value = mt9m111->autoexposure; | ||
704 | break; | ||
705 | } | ||
706 | return 0; | ||
707 | } | ||
708 | |||
709 | static int mt9m111_set_control(struct soc_camera_device *icd, | ||
710 | struct v4l2_control *ctrl) | ||
711 | { | ||
712 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
713 | const struct v4l2_queryctrl *qctrl; | ||
714 | int ret = 0; | ||
715 | |||
716 | qctrl = soc_camera_find_qctrl(&mt9m111_ops, ctrl->id); | ||
717 | |||
718 | if (!qctrl) | ||
719 | return -EINVAL; | ||
720 | |||
721 | switch (ctrl->id) { | ||
722 | case V4L2_CID_VFLIP: | ||
723 | mt9m111->vflip = ctrl->value; | ||
724 | ret = mt9m111_set_flip(icd, ctrl->value, | ||
725 | MT9M111_RMB_MIRROR_ROWS); | ||
726 | break; | ||
727 | case V4L2_CID_HFLIP: | ||
728 | mt9m111->hflip = ctrl->value; | ||
729 | ret = mt9m111_set_flip(icd, ctrl->value, | ||
730 | MT9M111_RMB_MIRROR_COLS); | ||
731 | break; | ||
732 | case V4L2_CID_GAIN: | ||
733 | ret = mt9m111_set_global_gain(icd, ctrl->value); | ||
734 | break; | ||
735 | case V4L2_CID_EXPOSURE_AUTO: | ||
736 | ret = mt9m111_set_autoexposure(icd, ctrl->value); | ||
737 | break; | ||
738 | default: | ||
739 | ret = -EINVAL; | ||
740 | } | ||
741 | |||
742 | return ret < 0 ? -EIO : 0; | ||
743 | } | ||
744 | |||
745 | int mt9m111_restore_state(struct soc_camera_device *icd) | ||
746 | { | ||
747 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
748 | |||
749 | mt9m111_set_context(icd, mt9m111->context); | ||
750 | mt9m111_set_pixfmt(icd, mt9m111->pixfmt); | ||
751 | mt9m111_setup_rect(icd); | ||
752 | mt9m111_set_flip(icd, mt9m111->hflip, MT9M111_RMB_MIRROR_COLS); | ||
753 | mt9m111_set_flip(icd, mt9m111->vflip, MT9M111_RMB_MIRROR_ROWS); | ||
754 | mt9m111_set_global_gain(icd, icd->gain); | ||
755 | mt9m111_set_autoexposure(icd, mt9m111->autoexposure); | ||
756 | return 0; | ||
757 | } | ||
758 | |||
759 | static int mt9m111_resume(struct soc_camera_device *icd) | ||
760 | { | ||
761 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
762 | int ret = 0; | ||
763 | |||
764 | if (mt9m111->powered) { | ||
765 | ret = mt9m111_enable(icd); | ||
766 | if (ret >= 0) | ||
767 | mt9m111_reset(icd); | ||
768 | if (ret >= 0) | ||
769 | mt9m111_restore_state(icd); | ||
770 | } | ||
771 | return ret; | ||
772 | } | ||
773 | |||
774 | static int mt9m111_init(struct soc_camera_device *icd) | ||
775 | { | ||
776 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
777 | int ret; | ||
778 | |||
779 | mt9m111->context = HIGHPOWER; | ||
780 | ret = mt9m111_enable(icd); | ||
781 | if (ret >= 0) | ||
782 | mt9m111_reset(icd); | ||
783 | if (ret >= 0) | ||
784 | mt9m111_set_context(icd, mt9m111->context); | ||
785 | if (ret >= 0) | ||
786 | mt9m111_set_autoexposure(icd, mt9m111->autoexposure); | ||
787 | if (ret < 0) | ||
788 | dev_err(&icd->dev, "mt9m111 init failed: %d\n", ret); | ||
789 | return ret ? -EIO : 0; | ||
790 | } | ||
791 | |||
792 | static int mt9m111_release(struct soc_camera_device *icd) | ||
793 | { | ||
794 | int ret; | ||
795 | |||
796 | ret = mt9m111_disable(icd); | ||
797 | if (ret < 0) | ||
798 | dev_err(&icd->dev, "mt9m111 release failed: %d\n", ret); | ||
799 | |||
800 | return ret ? -EIO : 0; | ||
801 | } | ||
802 | |||
803 | /* | ||
804 | * Interface active, can use i2c. If it fails, it can indeed mean, that | ||
805 | * this wasn't our capture interface, so, we wait for the right one | ||
806 | */ | ||
807 | static int mt9m111_video_probe(struct soc_camera_device *icd) | ||
808 | { | ||
809 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
810 | s32 data; | ||
811 | int ret; | ||
812 | |||
813 | /* | ||
814 | * We must have a parent by now. And it cannot be a wrong one. | ||
815 | * So this entire test is completely redundant. | ||
816 | */ | ||
817 | if (!icd->dev.parent || | ||
818 | to_soc_camera_host(icd->dev.parent)->nr != icd->iface) | ||
819 | return -ENODEV; | ||
820 | |||
821 | ret = mt9m111_enable(icd); | ||
822 | if (ret) | ||
823 | goto ei2c; | ||
824 | ret = mt9m111_reset(icd); | ||
825 | if (ret) | ||
826 | goto ei2c; | ||
827 | |||
828 | data = reg_read(CHIP_VERSION); | ||
829 | |||
830 | switch (data) { | ||
831 | case 0x143a: | ||
832 | mt9m111->model = V4L2_IDENT_MT9M111; | ||
833 | icd->formats = mt9m111_colour_formats; | ||
834 | icd->num_formats = ARRAY_SIZE(mt9m111_colour_formats); | ||
835 | break; | ||
836 | default: | ||
837 | ret = -ENODEV; | ||
838 | dev_err(&icd->dev, | ||
839 | "No MT9M111 chip detected, register read %x\n", data); | ||
840 | goto ei2c; | ||
841 | } | ||
842 | |||
843 | dev_info(&icd->dev, "Detected a MT9M111 chip ID 0x143a\n"); | ||
844 | |||
845 | ret = soc_camera_video_start(icd); | ||
846 | if (ret) | ||
847 | goto eisis; | ||
848 | |||
849 | mt9m111->autoexposure = 1; | ||
850 | |||
851 | mt9m111->swap_rgb_even_odd = 1; | ||
852 | mt9m111->swap_rgb_red_blue = 1; | ||
853 | |||
854 | return 0; | ||
855 | eisis: | ||
856 | ei2c: | ||
857 | return ret; | ||
858 | } | ||
859 | |||
860 | static void mt9m111_video_remove(struct soc_camera_device *icd) | ||
861 | { | ||
862 | struct mt9m111 *mt9m111 = container_of(icd, struct mt9m111, icd); | ||
863 | |||
864 | dev_dbg(&icd->dev, "Video %x removed: %p, %p\n", mt9m111->client->addr, | ||
865 | mt9m111->icd.dev.parent, mt9m111->icd.vdev); | ||
866 | soc_camera_video_stop(&mt9m111->icd); | ||
867 | } | ||
868 | |||
869 | static int mt9m111_probe(struct i2c_client *client, | ||
870 | const struct i2c_device_id *did) | ||
871 | { | ||
872 | struct mt9m111 *mt9m111; | ||
873 | struct soc_camera_device *icd; | ||
874 | struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); | ||
875 | struct soc_camera_link *icl = client->dev.platform_data; | ||
876 | int ret; | ||
877 | |||
878 | if (!icl) { | ||
879 | dev_err(&client->dev, "MT9M111 driver needs platform data\n"); | ||
880 | return -EINVAL; | ||
881 | } | ||
882 | |||
883 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { | ||
884 | dev_warn(&adapter->dev, | ||
885 | "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); | ||
886 | return -EIO; | ||
887 | } | ||
888 | |||
889 | mt9m111 = kzalloc(sizeof(struct mt9m111), GFP_KERNEL); | ||
890 | if (!mt9m111) | ||
891 | return -ENOMEM; | ||
892 | |||
893 | mt9m111->client = client; | ||
894 | i2c_set_clientdata(client, mt9m111); | ||
895 | |||
896 | /* Second stage probe - when a capture adapter is there */ | ||
897 | icd = &mt9m111->icd; | ||
898 | icd->ops = &mt9m111_ops; | ||
899 | icd->control = &client->dev; | ||
900 | icd->x_min = MT9M111_MIN_DARK_COLS; | ||
901 | icd->y_min = MT9M111_MIN_DARK_ROWS; | ||
902 | icd->x_current = icd->x_min; | ||
903 | icd->y_current = icd->y_min; | ||
904 | icd->width_min = MT9M111_MIN_DARK_ROWS; | ||
905 | icd->width_max = MT9M111_MAX_WIDTH; | ||
906 | icd->height_min = MT9M111_MIN_DARK_COLS; | ||
907 | icd->height_max = MT9M111_MAX_HEIGHT; | ||
908 | icd->y_skip_top = 0; | ||
909 | icd->iface = icl->bus_id; | ||
910 | |||
911 | ret = soc_camera_device_register(icd); | ||
912 | if (ret) | ||
913 | goto eisdr; | ||
914 | return 0; | ||
915 | |||
916 | eisdr: | ||
917 | kfree(mt9m111); | ||
918 | return ret; | ||
919 | } | ||
920 | |||
921 | static int mt9m111_remove(struct i2c_client *client) | ||
922 | { | ||
923 | struct mt9m111 *mt9m111 = i2c_get_clientdata(client); | ||
924 | soc_camera_device_unregister(&mt9m111->icd); | ||
925 | kfree(mt9m111); | ||
926 | |||
927 | return 0; | ||
928 | } | ||
929 | |||
930 | static const struct i2c_device_id mt9m111_id[] = { | ||
931 | { "mt9m111", 0 }, | ||
932 | { } | ||
933 | }; | ||
934 | MODULE_DEVICE_TABLE(i2c, mt9m111_id); | ||
935 | |||
936 | static struct i2c_driver mt9m111_i2c_driver = { | ||
937 | .driver = { | ||
938 | .name = "mt9m111", | ||
939 | }, | ||
940 | .probe = mt9m111_probe, | ||
941 | .remove = mt9m111_remove, | ||
942 | .id_table = mt9m111_id, | ||
943 | }; | ||
944 | |||
945 | static int __init mt9m111_mod_init(void) | ||
946 | { | ||
947 | return i2c_add_driver(&mt9m111_i2c_driver); | ||
948 | } | ||
949 | |||
950 | static void __exit mt9m111_mod_exit(void) | ||
951 | { | ||
952 | i2c_del_driver(&mt9m111_i2c_driver); | ||
953 | } | ||
954 | |||
955 | module_init(mt9m111_mod_init); | ||
956 | module_exit(mt9m111_mod_exit); | ||
957 | |||
958 | MODULE_DESCRIPTION("Micron MT9M111 Camera driver"); | ||
959 | MODULE_AUTHOR("Robert Jarzmik"); | ||
960 | MODULE_LICENSE("GPL"); | ||