diff options
-rw-r--r-- | drivers/media/i2c/Kconfig | 7 | ||||
-rw-r--r-- | drivers/media/i2c/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/i2c/s5k4ecgx.c | 1036 | ||||
-rw-r--r-- | include/media/s5k4ecgx.h | 37 |
4 files changed, 1081 insertions, 0 deletions
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index d3be0ca27361..24d78e28e493 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig | |||
@@ -507,6 +507,13 @@ config VIDEO_S5K6AA | |||
507 | This is a V4L2 sensor-level driver for Samsung S5K6AA(FX) 1.3M | 507 | This is a V4L2 sensor-level driver for Samsung S5K6AA(FX) 1.3M |
508 | camera sensor with an embedded SoC image signal processor. | 508 | camera sensor with an embedded SoC image signal processor. |
509 | 509 | ||
510 | config VIDEO_S5K4ECGX | ||
511 | tristate "Samsung S5K4ECGX sensor support" | ||
512 | depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API | ||
513 | ---help--- | ||
514 | This is a V4L2 sensor-level driver for Samsung S5K4ECGX 5M | ||
515 | camera sensor with an embedded SoC image signal processor. | ||
516 | |||
510 | source "drivers/media/i2c/smiapp/Kconfig" | 517 | source "drivers/media/i2c/smiapp/Kconfig" |
511 | 518 | ||
512 | comment "Flash devices" | 519 | comment "Flash devices" |
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 4a270f7983de..b1d62dfd49b8 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile | |||
@@ -57,6 +57,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o | |||
57 | obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o | 57 | obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o |
58 | obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o | 58 | obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o |
59 | obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o | 59 | obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o |
60 | obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o | ||
60 | obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o | 61 | obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o |
61 | obj-$(CONFIG_VIDEO_AS3645A) += as3645a.o | 62 | obj-$(CONFIG_VIDEO_AS3645A) += as3645a.o |
62 | obj-$(CONFIG_VIDEO_SMIAPP_PLL) += smiapp-pll.o | 63 | obj-$(CONFIG_VIDEO_SMIAPP_PLL) += smiapp-pll.o |
diff --git a/drivers/media/i2c/s5k4ecgx.c b/drivers/media/i2c/s5k4ecgx.c new file mode 100644 index 000000000000..49c1b3abb425 --- /dev/null +++ b/drivers/media/i2c/s5k4ecgx.c | |||
@@ -0,0 +1,1036 @@ | |||
1 | /* | ||
2 | * Driver for Samsung S5K4ECGX 1/4" 5Mp CMOS Image Sensor SoC | ||
3 | * with an Embedded Image Signal Processor. | ||
4 | * | ||
5 | * Copyright (C) 2012, Linaro, Sangwook Lee <sangwook.lee@linaro.org> | ||
6 | * Copyright (C) 2012, Insignal Co,. Ltd, Homin Lee <suapapa@insignal.co.kr> | ||
7 | * | ||
8 | * Based on s5k6aa and noon010pc30 driver | ||
9 | * Copyright (C) 2011, Samsung Electronics Co., Ltd. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | */ | ||
16 | |||
17 | #include <linux/clk.h> | ||
18 | #include <linux/crc32.h> | ||
19 | #include <linux/ctype.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/firmware.h> | ||
22 | #include <linux/gpio.h> | ||
23 | #include <linux/i2c.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/regulator/consumer.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <asm/unaligned.h> | ||
28 | |||
29 | #include <media/media-entity.h> | ||
30 | #include <media/s5k4ecgx.h> | ||
31 | #include <media/v4l2-ctrls.h> | ||
32 | #include <media/v4l2-device.h> | ||
33 | #include <media/v4l2-mediabus.h> | ||
34 | #include <media/v4l2-subdev.h> | ||
35 | |||
36 | static int debug; | ||
37 | module_param(debug, int, 0644); | ||
38 | |||
39 | #define S5K4ECGX_DRIVER_NAME "s5k4ecgx" | ||
40 | #define S5K4ECGX_FIRMWARE "s5k4ecgx.bin" | ||
41 | |||
42 | /* Firmware revision information */ | ||
43 | #define REG_FW_REVISION 0x700001a6 | ||
44 | #define REG_FW_VERSION 0x700001a4 | ||
45 | #define S5K4ECGX_REVISION_1_1 0x11 | ||
46 | #define S5K4ECGX_FW_VERSION 0x4ec0 | ||
47 | |||
48 | /* General purpose parameters */ | ||
49 | #define REG_USER_BRIGHTNESS 0x7000022c | ||
50 | #define REG_USER_CONTRAST 0x7000022e | ||
51 | #define REG_USER_SATURATION 0x70000230 | ||
52 | |||
53 | #define REG_G_ENABLE_PREV 0x7000023e | ||
54 | #define REG_G_ENABLE_PREV_CHG 0x70000240 | ||
55 | #define REG_G_NEW_CFG_SYNC 0x7000024a | ||
56 | #define REG_G_PREV_IN_WIDTH 0x70000250 | ||
57 | #define REG_G_PREV_IN_HEIGHT 0x70000252 | ||
58 | #define REG_G_PREV_IN_XOFFS 0x70000254 | ||
59 | #define REG_G_PREV_IN_YOFFS 0x70000256 | ||
60 | #define REG_G_CAP_IN_WIDTH 0x70000258 | ||
61 | #define REG_G_CAP_IN_HEIGHT 0x7000025a | ||
62 | #define REG_G_CAP_IN_XOFFS 0x7000025c | ||
63 | #define REG_G_CAP_IN_YOFFS 0x7000025e | ||
64 | #define REG_G_INPUTS_CHANGE_REQ 0x70000262 | ||
65 | #define REG_G_ACTIVE_PREV_CFG 0x70000266 | ||
66 | #define REG_G_PREV_CFG_CHG 0x70000268 | ||
67 | #define REG_G_PREV_OPEN_AFTER_CH 0x7000026a | ||
68 | |||
69 | /* Preview context register sets. n = 0...4. */ | ||
70 | #define PREG(n, x) ((n) * 0x30 + (x)) | ||
71 | #define REG_P_OUT_WIDTH(n) PREG(n, 0x700002a6) | ||
72 | #define REG_P_OUT_HEIGHT(n) PREG(n, 0x700002a8) | ||
73 | #define REG_P_FMT(n) PREG(n, 0x700002aa) | ||
74 | #define REG_P_PVI_MASK(n) PREG(n, 0x700002b4) | ||
75 | #define REG_P_FR_TIME_TYPE(n) PREG(n, 0x700002be) | ||
76 | #define FR_TIME_DYNAMIC 0 | ||
77 | #define FR_TIME_FIXED 1 | ||
78 | #define FR_TIME_FIXED_ACCURATE 2 | ||
79 | #define REG_P_FR_TIME_Q_TYPE(n) PREG(n, 0x700002c0) | ||
80 | #define FR_TIME_Q_DYNAMIC 0 | ||
81 | #define FR_TIME_Q_BEST_FRRATE 1 | ||
82 | #define FR_TIME_Q_BEST_QUALITY 2 | ||
83 | |||
84 | /* Frame period in 0.1 ms units */ | ||
85 | #define REG_P_MAX_FR_TIME(n) PREG(n, 0x700002c2) | ||
86 | #define REG_P_MIN_FR_TIME(n) PREG(n, 0x700002c4) | ||
87 | #define US_TO_FR_TIME(__t) ((__t) / 100) | ||
88 | #define REG_P_PREV_MIRROR(n) PREG(n, 0x700002d0) | ||
89 | #define REG_P_CAP_MIRROR(n) PREG(n, 0x700002d2) | ||
90 | |||
91 | #define REG_G_PREVZOOM_IN_WIDTH 0x70000494 | ||
92 | #define REG_G_PREVZOOM_IN_HEIGHT 0x70000496 | ||
93 | #define REG_G_PREVZOOM_IN_XOFFS 0x70000498 | ||
94 | #define REG_G_PREVZOOM_IN_YOFFS 0x7000049a | ||
95 | #define REG_G_CAPZOOM_IN_WIDTH 0x7000049c | ||
96 | #define REG_G_CAPZOOM_IN_HEIGHT 0x7000049e | ||
97 | #define REG_G_CAPZOOM_IN_XOFFS 0x700004a0 | ||
98 | #define REG_G_CAPZOOM_IN_YOFFS 0x700004a2 | ||
99 | |||
100 | /* n = 0...4 */ | ||
101 | #define REG_USER_SHARPNESS(n) (0x70000a28 + (n) * 0xb6) | ||
102 | |||
103 | /* Reduce sharpness range for user space API */ | ||
104 | #define SHARPNESS_DIV 8208 | ||
105 | #define TOK_TERM 0xffffffff | ||
106 | |||
107 | /* | ||
108 | * FIXME: This is copied from s5k6aa, because of no information | ||
109 | * in the S5K4ECGX datasheet. | ||
110 | * H/W register Interface (0xd0000000 - 0xd0000fff) | ||
111 | */ | ||
112 | #define AHB_MSB_ADDR_PTR 0xfcfc | ||
113 | #define GEN_REG_OFFSH 0xd000 | ||
114 | #define REG_CMDWR_ADDRH 0x0028 | ||
115 | #define REG_CMDWR_ADDRL 0x002a | ||
116 | #define REG_CMDRD_ADDRH 0x002c | ||
117 | #define REG_CMDRD_ADDRL 0x002e | ||
118 | #define REG_CMDBUF0_ADDR 0x0f12 | ||
119 | |||
120 | struct s5k4ecgx_frmsize { | ||
121 | struct v4l2_frmsize_discrete size; | ||
122 | /* Fixed sensor matrix crop rectangle */ | ||
123 | struct v4l2_rect input_window; | ||
124 | }; | ||
125 | |||
126 | struct regval_list { | ||
127 | u32 addr; | ||
128 | u16 val; | ||
129 | }; | ||
130 | |||
131 | /* | ||
132 | * TODO: currently only preview is supported and snapshot (capture) | ||
133 | * is not implemented yet | ||
134 | */ | ||
135 | static const struct s5k4ecgx_frmsize s5k4ecgx_prev_sizes[] = { | ||
136 | { | ||
137 | .size = { 176, 144 }, | ||
138 | .input_window = { 0x00, 0x00, 0x928, 0x780 }, | ||
139 | }, { | ||
140 | .size = { 352, 288 }, | ||
141 | .input_window = { 0x00, 0x00, 0x928, 0x780 }, | ||
142 | }, { | ||
143 | .size = { 640, 480 }, | ||
144 | .input_window = { 0x00, 0x00, 0xa00, 0x780 }, | ||
145 | }, { | ||
146 | .size = { 720, 480 }, | ||
147 | .input_window = { 0x00, 0x00, 0xa00, 0x6a8 }, | ||
148 | } | ||
149 | }; | ||
150 | |||
151 | #define S5K4ECGX_NUM_PREV ARRAY_SIZE(s5k4ecgx_prev_sizes) | ||
152 | |||
153 | struct s5k4ecgx_pixfmt { | ||
154 | enum v4l2_mbus_pixelcode code; | ||
155 | u32 colorspace; | ||
156 | /* REG_TC_PCFG_Format register value */ | ||
157 | u16 reg_p_format; | ||
158 | }; | ||
159 | |||
160 | /* By default value, output from sensor will be YUV422 0-255 */ | ||
161 | static const struct s5k4ecgx_pixfmt s5k4ecgx_formats[] = { | ||
162 | { V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 5 }, | ||
163 | }; | ||
164 | |||
165 | static const char * const s5k4ecgx_supply_names[] = { | ||
166 | /* | ||
167 | * Usually 2.8V is used for analog power (vdda) | ||
168 | * and digital IO (vddio, vdddcore) | ||
169 | */ | ||
170 | "vdda", | ||
171 | "vddio", | ||
172 | "vddcore", | ||
173 | "vddreg", /* The internal s5k4ecgx regulator's supply (1.8V) */ | ||
174 | }; | ||
175 | |||
176 | #define S5K4ECGX_NUM_SUPPLIES ARRAY_SIZE(s5k4ecgx_supply_names) | ||
177 | |||
178 | enum s5k4ecgx_gpio_id { | ||
179 | STBY, | ||
180 | RST, | ||
181 | GPIO_NUM, | ||
182 | }; | ||
183 | |||
184 | struct s5k4ecgx { | ||
185 | struct v4l2_subdev sd; | ||
186 | struct media_pad pad; | ||
187 | struct v4l2_ctrl_handler handler; | ||
188 | |||
189 | struct s5k4ecgx_platform_data *pdata; | ||
190 | const struct s5k4ecgx_pixfmt *curr_pixfmt; | ||
191 | const struct s5k4ecgx_frmsize *curr_frmsize; | ||
192 | struct mutex lock; | ||
193 | u8 streaming; | ||
194 | u8 set_params; | ||
195 | |||
196 | struct regulator_bulk_data supplies[S5K4ECGX_NUM_SUPPLIES]; | ||
197 | struct s5k4ecgx_gpio gpio[GPIO_NUM]; | ||
198 | }; | ||
199 | |||
200 | static inline struct s5k4ecgx *to_s5k4ecgx(struct v4l2_subdev *sd) | ||
201 | { | ||
202 | return container_of(sd, struct s5k4ecgx, sd); | ||
203 | } | ||
204 | |||
205 | static int s5k4ecgx_i2c_read(struct i2c_client *client, u16 addr, u16 *val) | ||
206 | { | ||
207 | u8 wbuf[2] = { addr >> 8, addr & 0xff }; | ||
208 | struct i2c_msg msg[2]; | ||
209 | u8 rbuf[2]; | ||
210 | int ret; | ||
211 | |||
212 | msg[0].addr = client->addr; | ||
213 | msg[0].flags = 0; | ||
214 | msg[0].len = 2; | ||
215 | msg[0].buf = wbuf; | ||
216 | |||
217 | msg[1].addr = client->addr; | ||
218 | msg[1].flags = I2C_M_RD; | ||
219 | msg[1].len = 2; | ||
220 | msg[1].buf = rbuf; | ||
221 | |||
222 | ret = i2c_transfer(client->adapter, msg, 2); | ||
223 | *val = be16_to_cpu(*((u16 *)rbuf)); | ||
224 | |||
225 | v4l2_dbg(4, debug, client, "i2c_read: 0x%04X : 0x%04x\n", addr, *val); | ||
226 | |||
227 | return ret == 2 ? 0 : ret; | ||
228 | } | ||
229 | |||
230 | static int s5k4ecgx_i2c_write(struct i2c_client *client, u16 addr, u16 val) | ||
231 | { | ||
232 | u8 buf[4] = { addr >> 8, addr & 0xff, val >> 8, val & 0xff }; | ||
233 | |||
234 | int ret = i2c_master_send(client, buf, 4); | ||
235 | v4l2_dbg(4, debug, client, "i2c_write: 0x%04x : 0x%04x\n", addr, val); | ||
236 | |||
237 | return ret == 4 ? 0 : ret; | ||
238 | } | ||
239 | |||
240 | static int s5k4ecgx_write(struct i2c_client *client, u32 addr, u16 val) | ||
241 | { | ||
242 | u16 high = addr >> 16, low = addr & 0xffff; | ||
243 | int ret; | ||
244 | |||
245 | v4l2_dbg(3, debug, client, "write: 0x%08x : 0x%04x\n", addr, val); | ||
246 | |||
247 | ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRH, high); | ||
248 | if (!ret) | ||
249 | ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRL, low); | ||
250 | if (!ret) | ||
251 | ret = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); | ||
252 | |||
253 | return ret; | ||
254 | } | ||
255 | |||
256 | static int s5k4ecgx_read(struct i2c_client *client, u32 addr, u16 *val) | ||
257 | { | ||
258 | u16 high = addr >> 16, low = addr & 0xffff; | ||
259 | int ret; | ||
260 | |||
261 | ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRH, high); | ||
262 | if (!ret) | ||
263 | ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRL, low); | ||
264 | if (!ret) | ||
265 | ret = s5k4ecgx_i2c_read(client, REG_CMDBUF0_ADDR, val); | ||
266 | if (!ret) | ||
267 | dev_err(&client->dev, "Failed to execute read command\n"); | ||
268 | |||
269 | return ret; | ||
270 | } | ||
271 | |||
272 | static int s5k4ecgx_read_fw_ver(struct v4l2_subdev *sd) | ||
273 | { | ||
274 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
275 | u16 hw_rev, fw_ver = 0; | ||
276 | int ret; | ||
277 | |||
278 | ret = s5k4ecgx_read(client, REG_FW_VERSION, &fw_ver); | ||
279 | if (ret < 0 || fw_ver != S5K4ECGX_FW_VERSION) { | ||
280 | v4l2_err(sd, "FW version check failed!\n"); | ||
281 | return -ENODEV; | ||
282 | } | ||
283 | |||
284 | ret = s5k4ecgx_read(client, REG_FW_REVISION, &hw_rev); | ||
285 | if (ret < 0) | ||
286 | return ret; | ||
287 | |||
288 | v4l2_info(sd, "chip found FW ver: 0x%x, HW rev: 0x%x\n", | ||
289 | fw_ver, hw_rev); | ||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | static int s5k4ecgx_set_ahb_address(struct v4l2_subdev *sd) | ||
294 | { | ||
295 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
296 | int ret; | ||
297 | |||
298 | /* Set APB peripherals start address */ | ||
299 | ret = s5k4ecgx_i2c_write(client, AHB_MSB_ADDR_PTR, GEN_REG_OFFSH); | ||
300 | if (ret < 0) | ||
301 | return ret; | ||
302 | /* | ||
303 | * FIXME: This is copied from s5k6aa, because of no information | ||
304 | * in s5k4ecgx's datasheet. | ||
305 | * sw_reset is activated to put device into idle status | ||
306 | */ | ||
307 | ret = s5k4ecgx_i2c_write(client, 0x0010, 0x0001); | ||
308 | if (ret < 0) | ||
309 | return ret; | ||
310 | |||
311 | ret = s5k4ecgx_i2c_write(client, 0x1030, 0x0000); | ||
312 | if (ret < 0) | ||
313 | return ret; | ||
314 | /* Halt ARM CPU */ | ||
315 | return s5k4ecgx_i2c_write(client, 0x0014, 0x0001); | ||
316 | } | ||
317 | |||
318 | #define FW_CRC_SIZE 4 | ||
319 | /* Register address, value are 4, 2 bytes */ | ||
320 | #define FW_RECORD_SIZE 6 | ||
321 | /* | ||
322 | * The firmware has following format: | ||
323 | * < total number of records (4 bytes + 2 bytes padding) N >, | ||
324 | * < record 0 >, ..., < record N - 1 >, < CRC32-CCITT (4-bytes) >, | ||
325 | * where "record" is a 4-byte register address followed by 2-byte | ||
326 | * register value (little endian). | ||
327 | * The firmware generator can be found in following git repository: | ||
328 | * git://git.linaro.org/people/sangwook/fimc-v4l2-app.git | ||
329 | */ | ||
330 | static int s5k4ecgx_load_firmware(struct v4l2_subdev *sd) | ||
331 | { | ||
332 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
333 | const struct firmware *fw; | ||
334 | const u8 *ptr; | ||
335 | int err, i, regs_num; | ||
336 | u32 addr, crc, crc_file, addr_inc = 0; | ||
337 | u16 val; | ||
338 | |||
339 | err = request_firmware(&fw, S5K4ECGX_FIRMWARE, sd->v4l2_dev->dev); | ||
340 | if (err) { | ||
341 | v4l2_err(sd, "Failed to read firmware %s\n", S5K4ECGX_FIRMWARE); | ||
342 | return err; | ||
343 | } | ||
344 | regs_num = le32_to_cpu(get_unaligned_le32(fw->data)); | ||
345 | |||
346 | v4l2_dbg(3, debug, sd, "FW: %s size %d register sets %d\n", | ||
347 | S5K4ECGX_FIRMWARE, fw->size, regs_num); | ||
348 | |||
349 | regs_num++; /* Add header */ | ||
350 | if (fw->size != regs_num * FW_RECORD_SIZE + FW_CRC_SIZE) { | ||
351 | err = -EINVAL; | ||
352 | goto fw_out; | ||
353 | } | ||
354 | crc_file = le32_to_cpu(get_unaligned_le32(fw->data + | ||
355 | regs_num * FW_RECORD_SIZE)); | ||
356 | crc = crc32_le(~0, fw->data, regs_num * FW_RECORD_SIZE); | ||
357 | if (crc != crc_file) { | ||
358 | v4l2_err(sd, "FW: invalid crc (%#x:%#x)\n", crc, crc_file); | ||
359 | err = -EINVAL; | ||
360 | goto fw_out; | ||
361 | } | ||
362 | ptr = fw->data + FW_RECORD_SIZE; | ||
363 | for (i = 1; i < regs_num; i++) { | ||
364 | addr = le32_to_cpu(get_unaligned_le32(ptr)); | ||
365 | ptr += sizeof(u32); | ||
366 | val = le16_to_cpu(get_unaligned_le16(ptr)); | ||
367 | ptr += sizeof(u16); | ||
368 | if (addr - addr_inc != 2) | ||
369 | err = s5k4ecgx_write(client, addr, val); | ||
370 | else | ||
371 | err = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); | ||
372 | if (err) | ||
373 | break; | ||
374 | addr_inc = addr; | ||
375 | } | ||
376 | fw_out: | ||
377 | release_firmware(fw); | ||
378 | return err; | ||
379 | } | ||
380 | |||
381 | /* Set preview and capture input window */ | ||
382 | static int s5k4ecgx_set_input_window(struct i2c_client *c, | ||
383 | const struct v4l2_rect *r) | ||
384 | { | ||
385 | int ret; | ||
386 | |||
387 | ret = s5k4ecgx_write(c, REG_G_PREV_IN_WIDTH, r->width); | ||
388 | if (!ret) | ||
389 | ret = s5k4ecgx_write(c, REG_G_PREV_IN_HEIGHT, r->height); | ||
390 | if (!ret) | ||
391 | ret = s5k4ecgx_write(c, REG_G_PREV_IN_XOFFS, r->left); | ||
392 | if (!ret) | ||
393 | ret = s5k4ecgx_write(c, REG_G_PREV_IN_YOFFS, r->top); | ||
394 | if (!ret) | ||
395 | ret = s5k4ecgx_write(c, REG_G_CAP_IN_WIDTH, r->width); | ||
396 | if (!ret) | ||
397 | ret = s5k4ecgx_write(c, REG_G_CAP_IN_HEIGHT, r->height); | ||
398 | if (!ret) | ||
399 | ret = s5k4ecgx_write(c, REG_G_CAP_IN_XOFFS, r->left); | ||
400 | if (!ret) | ||
401 | ret = s5k4ecgx_write(c, REG_G_CAP_IN_YOFFS, r->top); | ||
402 | |||
403 | return ret; | ||
404 | } | ||
405 | |||
406 | /* Set preview and capture zoom input window */ | ||
407 | static int s5k4ecgx_set_zoom_window(struct i2c_client *c, | ||
408 | const struct v4l2_rect *r) | ||
409 | { | ||
410 | int ret; | ||
411 | |||
412 | ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_WIDTH, r->width); | ||
413 | if (!ret) | ||
414 | ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_HEIGHT, r->height); | ||
415 | if (!ret) | ||
416 | ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_XOFFS, r->left); | ||
417 | if (!ret) | ||
418 | ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_YOFFS, r->top); | ||
419 | if (!ret) | ||
420 | ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_WIDTH, r->width); | ||
421 | if (!ret) | ||
422 | ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_HEIGHT, r->height); | ||
423 | if (!ret) | ||
424 | ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_XOFFS, r->left); | ||
425 | if (!ret) | ||
426 | ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_YOFFS, r->top); | ||
427 | |||
428 | return ret; | ||
429 | } | ||
430 | |||
431 | static int s5k4ecgx_set_output_framefmt(struct s5k4ecgx *priv) | ||
432 | { | ||
433 | struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); | ||
434 | int ret; | ||
435 | |||
436 | ret = s5k4ecgx_write(client, REG_P_OUT_WIDTH(0), | ||
437 | priv->curr_frmsize->size.width); | ||
438 | if (!ret) | ||
439 | ret = s5k4ecgx_write(client, REG_P_OUT_HEIGHT(0), | ||
440 | priv->curr_frmsize->size.height); | ||
441 | if (!ret) | ||
442 | ret = s5k4ecgx_write(client, REG_P_FMT(0), | ||
443 | priv->curr_pixfmt->reg_p_format); | ||
444 | return ret; | ||
445 | } | ||
446 | |||
447 | static int s5k4ecgx_init_sensor(struct v4l2_subdev *sd) | ||
448 | { | ||
449 | int ret; | ||
450 | |||
451 | ret = s5k4ecgx_set_ahb_address(sd); | ||
452 | |||
453 | /* The delay is from manufacturer's settings */ | ||
454 | msleep(100); | ||
455 | |||
456 | if (!ret) | ||
457 | ret = s5k4ecgx_load_firmware(sd); | ||
458 | if (ret) | ||
459 | v4l2_err(sd, "Failed to write initial settings\n"); | ||
460 | |||
461 | return ret; | ||
462 | } | ||
463 | |||
464 | static int s5k4ecgx_gpio_set_value(struct s5k4ecgx *priv, int id, u32 val) | ||
465 | { | ||
466 | if (!gpio_is_valid(priv->gpio[id].gpio)) | ||
467 | return 0; | ||
468 | gpio_set_value(priv->gpio[id].gpio, val); | ||
469 | |||
470 | return 1; | ||
471 | } | ||
472 | |||
473 | static int __s5k4ecgx_power_on(struct s5k4ecgx *priv) | ||
474 | { | ||
475 | int ret; | ||
476 | |||
477 | ret = regulator_bulk_enable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); | ||
478 | if (ret) | ||
479 | return ret; | ||
480 | usleep_range(30, 50); | ||
481 | |||
482 | /* The polarity of STBY is controlled by TSP */ | ||
483 | if (s5k4ecgx_gpio_set_value(priv, STBY, priv->gpio[STBY].level)) | ||
484 | usleep_range(30, 50); | ||
485 | |||
486 | if (s5k4ecgx_gpio_set_value(priv, RST, priv->gpio[RST].level)) | ||
487 | usleep_range(30, 50); | ||
488 | |||
489 | return 0; | ||
490 | } | ||
491 | |||
492 | static int __s5k4ecgx_power_off(struct s5k4ecgx *priv) | ||
493 | { | ||
494 | if (s5k4ecgx_gpio_set_value(priv, RST, !priv->gpio[RST].level)) | ||
495 | usleep_range(30, 50); | ||
496 | |||
497 | if (s5k4ecgx_gpio_set_value(priv, STBY, !priv->gpio[STBY].level)) | ||
498 | usleep_range(30, 50); | ||
499 | |||
500 | priv->streaming = 0; | ||
501 | |||
502 | return regulator_bulk_disable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); | ||
503 | } | ||
504 | |||
505 | /* Find nearest matching image pixel size. */ | ||
506 | static int s5k4ecgx_try_frame_size(struct v4l2_mbus_framefmt *mf, | ||
507 | const struct s5k4ecgx_frmsize **size) | ||
508 | { | ||
509 | unsigned int min_err = ~0; | ||
510 | int i = ARRAY_SIZE(s5k4ecgx_prev_sizes); | ||
511 | const struct s5k4ecgx_frmsize *fsize = &s5k4ecgx_prev_sizes[0], | ||
512 | *match = NULL; | ||
513 | |||
514 | while (i--) { | ||
515 | int err = abs(fsize->size.width - mf->width) | ||
516 | + abs(fsize->size.height - mf->height); | ||
517 | if (err < min_err) { | ||
518 | min_err = err; | ||
519 | match = fsize; | ||
520 | } | ||
521 | fsize++; | ||
522 | } | ||
523 | if (match) { | ||
524 | mf->width = match->size.width; | ||
525 | mf->height = match->size.height; | ||
526 | if (size) | ||
527 | *size = match; | ||
528 | return 0; | ||
529 | } | ||
530 | |||
531 | return -EINVAL; | ||
532 | } | ||
533 | |||
534 | static int s5k4ecgx_enum_mbus_code(struct v4l2_subdev *sd, | ||
535 | struct v4l2_subdev_fh *fh, | ||
536 | struct v4l2_subdev_mbus_code_enum *code) | ||
537 | { | ||
538 | if (code->index >= ARRAY_SIZE(s5k4ecgx_formats)) | ||
539 | return -EINVAL; | ||
540 | code->code = s5k4ecgx_formats[code->index].code; | ||
541 | |||
542 | return 0; | ||
543 | } | ||
544 | |||
545 | static int s5k4ecgx_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, | ||
546 | struct v4l2_subdev_format *fmt) | ||
547 | { | ||
548 | struct s5k4ecgx *priv = to_s5k4ecgx(sd); | ||
549 | struct v4l2_mbus_framefmt *mf; | ||
550 | |||
551 | if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { | ||
552 | if (fh) { | ||
553 | mf = v4l2_subdev_get_try_format(fh, 0); | ||
554 | fmt->format = *mf; | ||
555 | } | ||
556 | return 0; | ||
557 | } | ||
558 | |||
559 | mf = &fmt->format; | ||
560 | |||
561 | mutex_lock(&priv->lock); | ||
562 | mf->width = priv->curr_frmsize->size.width; | ||
563 | mf->height = priv->curr_frmsize->size.height; | ||
564 | mf->code = priv->curr_pixfmt->code; | ||
565 | mf->colorspace = priv->curr_pixfmt->colorspace; | ||
566 | mf->field = V4L2_FIELD_NONE; | ||
567 | mutex_unlock(&priv->lock); | ||
568 | |||
569 | return 0; | ||
570 | } | ||
571 | |||
572 | static const struct s5k4ecgx_pixfmt *s5k4ecgx_try_fmt(struct v4l2_subdev *sd, | ||
573 | struct v4l2_mbus_framefmt *mf) | ||
574 | { | ||
575 | int i = ARRAY_SIZE(s5k4ecgx_formats); | ||
576 | |||
577 | while (--i) | ||
578 | if (mf->code == s5k4ecgx_formats[i].code) | ||
579 | break; | ||
580 | mf->code = s5k4ecgx_formats[i].code; | ||
581 | |||
582 | return &s5k4ecgx_formats[i]; | ||
583 | } | ||
584 | |||
585 | static int s5k4ecgx_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, | ||
586 | struct v4l2_subdev_format *fmt) | ||
587 | { | ||
588 | struct s5k4ecgx *priv = to_s5k4ecgx(sd); | ||
589 | const struct s5k4ecgx_frmsize *fsize = NULL; | ||
590 | const struct s5k4ecgx_pixfmt *pf; | ||
591 | struct v4l2_mbus_framefmt *mf; | ||
592 | int ret = 0; | ||
593 | |||
594 | pf = s5k4ecgx_try_fmt(sd, &fmt->format); | ||
595 | s5k4ecgx_try_frame_size(&fmt->format, &fsize); | ||
596 | fmt->format.colorspace = V4L2_COLORSPACE_JPEG; | ||
597 | |||
598 | if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { | ||
599 | if (fh) { | ||
600 | mf = v4l2_subdev_get_try_format(fh, 0); | ||
601 | *mf = fmt->format; | ||
602 | } | ||
603 | return 0; | ||
604 | } | ||
605 | |||
606 | mutex_lock(&priv->lock); | ||
607 | if (!priv->streaming) { | ||
608 | priv->curr_frmsize = fsize; | ||
609 | priv->curr_pixfmt = pf; | ||
610 | priv->set_params = 1; | ||
611 | } else { | ||
612 | ret = -EBUSY; | ||
613 | } | ||
614 | mutex_unlock(&priv->lock); | ||
615 | |||
616 | return ret; | ||
617 | } | ||
618 | |||
619 | static const struct v4l2_subdev_pad_ops s5k4ecgx_pad_ops = { | ||
620 | .enum_mbus_code = s5k4ecgx_enum_mbus_code, | ||
621 | .get_fmt = s5k4ecgx_get_fmt, | ||
622 | .set_fmt = s5k4ecgx_set_fmt, | ||
623 | }; | ||
624 | |||
625 | /* | ||
626 | * V4L2 subdev controls | ||
627 | */ | ||
628 | static int s5k4ecgx_s_ctrl(struct v4l2_ctrl *ctrl) | ||
629 | { | ||
630 | struct v4l2_subdev *sd = &container_of(ctrl->handler, struct s5k4ecgx, | ||
631 | handler)->sd; | ||
632 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
633 | struct s5k4ecgx *priv = to_s5k4ecgx(sd); | ||
634 | unsigned int i; | ||
635 | int err = 0; | ||
636 | |||
637 | v4l2_dbg(1, debug, sd, "ctrl: 0x%x, value: %d\n", ctrl->id, ctrl->val); | ||
638 | |||
639 | mutex_lock(&priv->lock); | ||
640 | switch (ctrl->id) { | ||
641 | case V4L2_CID_CONTRAST: | ||
642 | err = s5k4ecgx_write(client, REG_USER_CONTRAST, ctrl->val); | ||
643 | break; | ||
644 | |||
645 | case V4L2_CID_SATURATION: | ||
646 | err = s5k4ecgx_write(client, REG_USER_SATURATION, ctrl->val); | ||
647 | break; | ||
648 | |||
649 | case V4L2_CID_SHARPNESS: | ||
650 | /* TODO: Revisit, is this setting for all presets ? */ | ||
651 | for (i = 0; i < 4 && !err; i++) | ||
652 | err = s5k4ecgx_write(client, REG_USER_SHARPNESS(i), | ||
653 | ctrl->val * SHARPNESS_DIV); | ||
654 | break; | ||
655 | |||
656 | case V4L2_CID_BRIGHTNESS: | ||
657 | err = s5k4ecgx_write(client, REG_USER_BRIGHTNESS, ctrl->val); | ||
658 | break; | ||
659 | } | ||
660 | mutex_unlock(&priv->lock); | ||
661 | if (err < 0) | ||
662 | v4l2_err(sd, "Failed to write s_ctrl err %d\n", err); | ||
663 | |||
664 | return err; | ||
665 | } | ||
666 | |||
667 | static const struct v4l2_ctrl_ops s5k4ecgx_ctrl_ops = { | ||
668 | .s_ctrl = s5k4ecgx_s_ctrl, | ||
669 | }; | ||
670 | |||
671 | /* | ||
672 | * Reading s5k4ecgx version information | ||
673 | */ | ||
674 | static int s5k4ecgx_registered(struct v4l2_subdev *sd) | ||
675 | { | ||
676 | int ret; | ||
677 | struct s5k4ecgx *priv = to_s5k4ecgx(sd); | ||
678 | |||
679 | mutex_lock(&priv->lock); | ||
680 | ret = __s5k4ecgx_power_on(priv); | ||
681 | if (!ret) { | ||
682 | ret = s5k4ecgx_read_fw_ver(sd); | ||
683 | __s5k4ecgx_power_off(priv); | ||
684 | } | ||
685 | mutex_unlock(&priv->lock); | ||
686 | |||
687 | return ret; | ||
688 | } | ||
689 | |||
690 | /* | ||
691 | * V4L2 subdev internal operations | ||
692 | */ | ||
693 | static int s5k4ecgx_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) | ||
694 | { | ||
695 | struct v4l2_mbus_framefmt *mf = v4l2_subdev_get_try_format(fh, 0); | ||
696 | |||
697 | mf->width = s5k4ecgx_prev_sizes[0].size.width; | ||
698 | mf->height = s5k4ecgx_prev_sizes[0].size.height; | ||
699 | mf->code = s5k4ecgx_formats[0].code; | ||
700 | mf->colorspace = V4L2_COLORSPACE_JPEG; | ||
701 | mf->field = V4L2_FIELD_NONE; | ||
702 | |||
703 | return 0; | ||
704 | } | ||
705 | |||
706 | static const struct v4l2_subdev_internal_ops s5k4ecgx_subdev_internal_ops = { | ||
707 | .registered = s5k4ecgx_registered, | ||
708 | .open = s5k4ecgx_open, | ||
709 | }; | ||
710 | |||
711 | static int s5k4ecgx_s_power(struct v4l2_subdev *sd, int on) | ||
712 | { | ||
713 | struct s5k4ecgx *priv = to_s5k4ecgx(sd); | ||
714 | int ret; | ||
715 | |||
716 | v4l2_dbg(1, debug, sd, "Switching %s\n", on ? "on" : "off"); | ||
717 | |||
718 | if (on) { | ||
719 | ret = __s5k4ecgx_power_on(priv); | ||
720 | if (ret < 0) | ||
721 | return ret; | ||
722 | /* Time to stabilize sensor */ | ||
723 | msleep(100); | ||
724 | ret = s5k4ecgx_init_sensor(sd); | ||
725 | if (ret < 0) | ||
726 | __s5k4ecgx_power_off(priv); | ||
727 | else | ||
728 | priv->set_params = 1; | ||
729 | } else { | ||
730 | ret = __s5k4ecgx_power_off(priv); | ||
731 | } | ||
732 | |||
733 | return ret; | ||
734 | } | ||
735 | |||
736 | static int s5k4ecgx_log_status(struct v4l2_subdev *sd) | ||
737 | { | ||
738 | v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); | ||
739 | |||
740 | return 0; | ||
741 | } | ||
742 | |||
743 | static const struct v4l2_subdev_core_ops s5k4ecgx_core_ops = { | ||
744 | .s_power = s5k4ecgx_s_power, | ||
745 | .log_status = s5k4ecgx_log_status, | ||
746 | }; | ||
747 | |||
748 | static int __s5k4ecgx_s_params(struct s5k4ecgx *priv) | ||
749 | { | ||
750 | struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); | ||
751 | const struct v4l2_rect *crop_rect = &priv->curr_frmsize->input_window; | ||
752 | int ret; | ||
753 | |||
754 | ret = s5k4ecgx_set_input_window(client, crop_rect); | ||
755 | if (!ret) | ||
756 | ret = s5k4ecgx_set_zoom_window(client, crop_rect); | ||
757 | if (!ret) | ||
758 | ret = s5k4ecgx_write(client, REG_G_INPUTS_CHANGE_REQ, 1); | ||
759 | if (!ret) | ||
760 | ret = s5k4ecgx_write(client, 0x70000a1e, 0x28); | ||
761 | if (!ret) | ||
762 | ret = s5k4ecgx_write(client, 0x70000ad4, 0x3c); | ||
763 | if (!ret) | ||
764 | ret = s5k4ecgx_set_output_framefmt(priv); | ||
765 | if (!ret) | ||
766 | ret = s5k4ecgx_write(client, REG_P_PVI_MASK(0), 0x52); | ||
767 | if (!ret) | ||
768 | ret = s5k4ecgx_write(client, REG_P_FR_TIME_TYPE(0), | ||
769 | FR_TIME_DYNAMIC); | ||
770 | if (!ret) | ||
771 | ret = s5k4ecgx_write(client, REG_P_FR_TIME_Q_TYPE(0), | ||
772 | FR_TIME_Q_BEST_FRRATE); | ||
773 | if (!ret) | ||
774 | ret = s5k4ecgx_write(client, REG_P_MIN_FR_TIME(0), | ||
775 | US_TO_FR_TIME(33300)); | ||
776 | if (!ret) | ||
777 | ret = s5k4ecgx_write(client, REG_P_MAX_FR_TIME(0), | ||
778 | US_TO_FR_TIME(66600)); | ||
779 | if (!ret) | ||
780 | ret = s5k4ecgx_write(client, REG_P_PREV_MIRROR(0), 0); | ||
781 | if (!ret) | ||
782 | ret = s5k4ecgx_write(client, REG_P_CAP_MIRROR(0), 0); | ||
783 | if (!ret) | ||
784 | ret = s5k4ecgx_write(client, REG_G_ACTIVE_PREV_CFG, 0); | ||
785 | if (!ret) | ||
786 | ret = s5k4ecgx_write(client, REG_G_PREV_OPEN_AFTER_CH, 1); | ||
787 | if (!ret) | ||
788 | ret = s5k4ecgx_write(client, REG_G_NEW_CFG_SYNC, 1); | ||
789 | if (!ret) | ||
790 | ret = s5k4ecgx_write(client, REG_G_PREV_CFG_CHG, 1); | ||
791 | |||
792 | return ret; | ||
793 | } | ||
794 | |||
795 | static int __s5k4ecgx_s_stream(struct s5k4ecgx *priv, int on) | ||
796 | { | ||
797 | struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); | ||
798 | int ret; | ||
799 | |||
800 | if (on && priv->set_params) { | ||
801 | ret = __s5k4ecgx_s_params(priv); | ||
802 | if (ret < 0) | ||
803 | return ret; | ||
804 | priv->set_params = 0; | ||
805 | } | ||
806 | /* | ||
807 | * This enables/disables preview stream only. Capture requests | ||
808 | * are not supported yet. | ||
809 | */ | ||
810 | ret = s5k4ecgx_write(client, REG_G_ENABLE_PREV, on); | ||
811 | if (ret < 0) | ||
812 | return ret; | ||
813 | return s5k4ecgx_write(client, REG_G_ENABLE_PREV_CHG, 1); | ||
814 | } | ||
815 | |||
816 | static int s5k4ecgx_s_stream(struct v4l2_subdev *sd, int on) | ||
817 | { | ||
818 | struct s5k4ecgx *priv = to_s5k4ecgx(sd); | ||
819 | int ret = 0; | ||
820 | |||
821 | v4l2_dbg(1, debug, sd, "Turn streaming %s\n", on ? "on" : "off"); | ||
822 | |||
823 | mutex_lock(&priv->lock); | ||
824 | |||
825 | if (priv->streaming == !on) { | ||
826 | ret = __s5k4ecgx_s_stream(priv, on); | ||
827 | if (!ret) | ||
828 | priv->streaming = on & 1; | ||
829 | } | ||
830 | |||
831 | mutex_unlock(&priv->lock); | ||
832 | return ret; | ||
833 | } | ||
834 | |||
835 | static const struct v4l2_subdev_video_ops s5k4ecgx_video_ops = { | ||
836 | .s_stream = s5k4ecgx_s_stream, | ||
837 | }; | ||
838 | |||
839 | static const struct v4l2_subdev_ops s5k4ecgx_ops = { | ||
840 | .core = &s5k4ecgx_core_ops, | ||
841 | .pad = &s5k4ecgx_pad_ops, | ||
842 | .video = &s5k4ecgx_video_ops, | ||
843 | }; | ||
844 | |||
845 | /* | ||
846 | * GPIO setup | ||
847 | */ | ||
848 | static int s5k4ecgx_config_gpio(int nr, int val, const char *name) | ||
849 | { | ||
850 | unsigned long flags = val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; | ||
851 | int ret; | ||
852 | |||
853 | if (!gpio_is_valid(nr)) | ||
854 | return 0; | ||
855 | ret = gpio_request_one(nr, flags, name); | ||
856 | if (!ret) | ||
857 | gpio_export(nr, 0); | ||
858 | |||
859 | return ret; | ||
860 | } | ||
861 | |||
862 | static void s5k4ecgx_free_gpios(struct s5k4ecgx *priv) | ||
863 | { | ||
864 | int i; | ||
865 | |||
866 | for (i = 0; i < ARRAY_SIZE(priv->gpio); i++) { | ||
867 | if (!gpio_is_valid(priv->gpio[i].gpio)) | ||
868 | continue; | ||
869 | gpio_free(priv->gpio[i].gpio); | ||
870 | priv->gpio[i].gpio = -EINVAL; | ||
871 | } | ||
872 | } | ||
873 | |||
874 | static int s5k4ecgx_config_gpios(struct s5k4ecgx *priv, | ||
875 | const struct s5k4ecgx_platform_data *pdata) | ||
876 | { | ||
877 | const struct s5k4ecgx_gpio *gpio = &pdata->gpio_stby; | ||
878 | int ret; | ||
879 | |||
880 | priv->gpio[STBY].gpio = -EINVAL; | ||
881 | priv->gpio[RST].gpio = -EINVAL; | ||
882 | |||
883 | ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_STBY"); | ||
884 | |||
885 | if (ret) { | ||
886 | s5k4ecgx_free_gpios(priv); | ||
887 | return ret; | ||
888 | } | ||
889 | priv->gpio[STBY] = *gpio; | ||
890 | if (gpio_is_valid(gpio->gpio)) | ||
891 | gpio_set_value(gpio->gpio, 0); | ||
892 | |||
893 | gpio = &pdata->gpio_reset; | ||
894 | |||
895 | ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_RST"); | ||
896 | if (ret) { | ||
897 | s5k4ecgx_free_gpios(priv); | ||
898 | return ret; | ||
899 | } | ||
900 | priv->gpio[RST] = *gpio; | ||
901 | if (gpio_is_valid(gpio->gpio)) | ||
902 | gpio_set_value(gpio->gpio, 0); | ||
903 | |||
904 | return 0; | ||
905 | } | ||
906 | |||
907 | static int s5k4ecgx_init_v4l2_ctrls(struct s5k4ecgx *priv) | ||
908 | { | ||
909 | const struct v4l2_ctrl_ops *ops = &s5k4ecgx_ctrl_ops; | ||
910 | struct v4l2_ctrl_handler *hdl = &priv->handler; | ||
911 | int ret; | ||
912 | |||
913 | ret = v4l2_ctrl_handler_init(hdl, 4); | ||
914 | if (ret) | ||
915 | return ret; | ||
916 | |||
917 | v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0); | ||
918 | v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); | ||
919 | v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); | ||
920 | |||
921 | /* Sharpness default is 24612, and then (24612/SHARPNESS_DIV) = 2 */ | ||
922 | v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -32704/SHARPNESS_DIV, | ||
923 | 24612/SHARPNESS_DIV, 1, 2); | ||
924 | if (hdl->error) { | ||
925 | ret = hdl->error; | ||
926 | v4l2_ctrl_handler_free(hdl); | ||
927 | return ret; | ||
928 | } | ||
929 | priv->sd.ctrl_handler = hdl; | ||
930 | |||
931 | return 0; | ||
932 | }; | ||
933 | |||
934 | static int s5k4ecgx_probe(struct i2c_client *client, | ||
935 | const struct i2c_device_id *id) | ||
936 | { | ||
937 | struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; | ||
938 | struct v4l2_subdev *sd; | ||
939 | struct s5k4ecgx *priv; | ||
940 | int ret, i; | ||
941 | |||
942 | if (pdata == NULL) { | ||
943 | dev_err(&client->dev, "platform data is missing!\n"); | ||
944 | return -EINVAL; | ||
945 | } | ||
946 | |||
947 | priv = devm_kzalloc(&client->dev, sizeof(struct s5k4ecgx), GFP_KERNEL); | ||
948 | if (!priv) | ||
949 | return -ENOMEM; | ||
950 | |||
951 | mutex_init(&priv->lock); | ||
952 | priv->streaming = 0; | ||
953 | |||
954 | sd = &priv->sd; | ||
955 | /* Registering subdev */ | ||
956 | v4l2_i2c_subdev_init(sd, client, &s5k4ecgx_ops); | ||
957 | strlcpy(sd->name, S5K4ECGX_DRIVER_NAME, sizeof(sd->name)); | ||
958 | |||
959 | sd->internal_ops = &s5k4ecgx_subdev_internal_ops; | ||
960 | /* Support v4l2 sub-device user space API */ | ||
961 | sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; | ||
962 | |||
963 | priv->pad.flags = MEDIA_PAD_FL_SOURCE; | ||
964 | sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; | ||
965 | ret = media_entity_init(&sd->entity, 1, &priv->pad, 0); | ||
966 | if (ret) | ||
967 | return ret; | ||
968 | |||
969 | ret = s5k4ecgx_config_gpios(priv, pdata); | ||
970 | if (ret) { | ||
971 | dev_err(&client->dev, "Failed to set gpios\n"); | ||
972 | goto out_err1; | ||
973 | } | ||
974 | for (i = 0; i < S5K4ECGX_NUM_SUPPLIES; i++) | ||
975 | priv->supplies[i].supply = s5k4ecgx_supply_names[i]; | ||
976 | |||
977 | ret = devm_regulator_bulk_get(&client->dev, S5K4ECGX_NUM_SUPPLIES, | ||
978 | priv->supplies); | ||
979 | if (ret) { | ||
980 | dev_err(&client->dev, "Failed to get regulators\n"); | ||
981 | goto out_err2; | ||
982 | } | ||
983 | ret = s5k4ecgx_init_v4l2_ctrls(priv); | ||
984 | if (ret) | ||
985 | goto out_err2; | ||
986 | |||
987 | priv->curr_pixfmt = &s5k4ecgx_formats[0]; | ||
988 | priv->curr_frmsize = &s5k4ecgx_prev_sizes[0]; | ||
989 | |||
990 | return 0; | ||
991 | |||
992 | out_err2: | ||
993 | s5k4ecgx_free_gpios(priv); | ||
994 | out_err1: | ||
995 | media_entity_cleanup(&priv->sd.entity); | ||
996 | |||
997 | return ret; | ||
998 | } | ||
999 | |||
1000 | static int s5k4ecgx_remove(struct i2c_client *client) | ||
1001 | { | ||
1002 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | ||
1003 | struct s5k4ecgx *priv = to_s5k4ecgx(sd); | ||
1004 | |||
1005 | mutex_destroy(&priv->lock); | ||
1006 | s5k4ecgx_free_gpios(priv); | ||
1007 | v4l2_device_unregister_subdev(sd); | ||
1008 | v4l2_ctrl_handler_free(&priv->handler); | ||
1009 | media_entity_cleanup(&sd->entity); | ||
1010 | |||
1011 | return 0; | ||
1012 | } | ||
1013 | |||
1014 | static const struct i2c_device_id s5k4ecgx_id[] = { | ||
1015 | { S5K4ECGX_DRIVER_NAME, 0 }, | ||
1016 | {} | ||
1017 | }; | ||
1018 | MODULE_DEVICE_TABLE(i2c, s5k4ecgx_id); | ||
1019 | |||
1020 | static struct i2c_driver v4l2_i2c_driver = { | ||
1021 | .driver = { | ||
1022 | .owner = THIS_MODULE, | ||
1023 | .name = S5K4ECGX_DRIVER_NAME, | ||
1024 | }, | ||
1025 | .probe = s5k4ecgx_probe, | ||
1026 | .remove = s5k4ecgx_remove, | ||
1027 | .id_table = s5k4ecgx_id, | ||
1028 | }; | ||
1029 | |||
1030 | module_i2c_driver(v4l2_i2c_driver); | ||
1031 | |||
1032 | MODULE_DESCRIPTION("Samsung S5K4ECGX 5MP SOC camera"); | ||
1033 | MODULE_AUTHOR("Sangwook Lee <sangwook.lee@linaro.org>"); | ||
1034 | MODULE_AUTHOR("Seok-Young Jang <quartz.jang@samsung.com>"); | ||
1035 | MODULE_LICENSE("GPL"); | ||
1036 | MODULE_FIRMWARE(S5K4ECGX_FIRMWARE); | ||
diff --git a/include/media/s5k4ecgx.h b/include/media/s5k4ecgx.h new file mode 100644 index 000000000000..90c1be792ffe --- /dev/null +++ b/include/media/s5k4ecgx.h | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * S5K4ECGX image sensor header file | ||
3 | * | ||
4 | * Copyright (C) 2012, Linaro | ||
5 | * Copyright (C) 2012, Samsung Electronics Co., Ltd. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | */ | ||
12 | |||
13 | #ifndef S5K4ECGX_H | ||
14 | #define S5K4ECGX_H | ||
15 | |||
16 | /** | ||
17 | * struct s5k4ecgx_gpio - data structure describing a GPIO | ||
18 | * @gpio : GPIO number | ||
19 | * @level: indicates active state of the @gpio | ||
20 | */ | ||
21 | struct s5k4ecgx_gpio { | ||
22 | int gpio; | ||
23 | int level; | ||
24 | }; | ||
25 | |||
26 | /** | ||
27 | * struct ss5k4ecgx_platform_data- s5k4ecgx driver platform data | ||
28 | * @gpio_reset: GPIO driving RESET pin | ||
29 | * @gpio_stby : GPIO driving STBY pin | ||
30 | */ | ||
31 | |||
32 | struct s5k4ecgx_platform_data { | ||
33 | struct s5k4ecgx_gpio gpio_reset; | ||
34 | struct s5k4ecgx_gpio gpio_stby; | ||
35 | }; | ||
36 | |||
37 | #endif /* S5K4ECGX_H */ | ||