Commit | Line | Data |
---|---|---|
d203a7ec RB |
1 | /* |
2 | * indycam.c - Silicon Graphics IndyCam digital camera driver | |
3 | * | |
4 | * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> | |
5 | * Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi> | |
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 version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
d203a7ec RB |
12 | #include <linux/delay.h> |
13 | #include <linux/errno.h> | |
14 | #include <linux/fs.h> | |
495515b3 | 15 | #include <linux/init.h> |
d203a7ec RB |
16 | #include <linux/kernel.h> |
17 | #include <linux/major.h> | |
495515b3 | 18 | #include <linux/module.h> |
d203a7ec | 19 | #include <linux/mm.h> |
495515b3 | 20 | #include <linux/slab.h> |
d203a7ec | 21 | |
d203a7ec | 22 | /* IndyCam decodes stream of photons into digital image representation ;-) */ |
cf4e9484 | 23 | #include <linux/videodev2.h> |
d203a7ec | 24 | #include <linux/i2c.h> |
cf4e9484 HV |
25 | #include <media/v4l2-common.h> |
26 | #include <media/v4l2-i2c-drv-legacy.h> | |
d203a7ec RB |
27 | |
28 | #include "indycam.h" | |
29 | ||
a637a114 | 30 | #define INDYCAM_MODULE_VERSION "0.0.5" |
d203a7ec RB |
31 | |
32 | MODULE_DESCRIPTION("SGI IndyCam driver"); | |
33 | MODULE_VERSION(INDYCAM_MODULE_VERSION); | |
34 | MODULE_AUTHOR("Mikael Nousiainen <tmnousia@cc.hut.fi>"); | |
35 | MODULE_LICENSE("GPL"); | |
36 | ||
cf4e9484 HV |
37 | static unsigned short normal_i2c[] = { 0x56 >> 1, I2C_CLIENT_END }; |
38 | ||
39 | I2C_CLIENT_INSMOD; | |
40 | ||
a637a114 LM |
41 | // #define INDYCAM_DEBUG |
42 | ||
d203a7ec RB |
43 | #ifdef INDYCAM_DEBUG |
44 | #define dprintk(x...) printk("IndyCam: " x); | |
45 | #define indycam_regdump(client) indycam_regdump_debug(client) | |
46 | #else | |
47 | #define dprintk(x...) | |
48 | #define indycam_regdump(client) | |
49 | #endif | |
50 | ||
d203a7ec RB |
51 | struct indycam { |
52 | struct i2c_client *client; | |
a637a114 | 53 | u8 version; |
d203a7ec RB |
54 | }; |
55 | ||
a637a114 | 56 | static const u8 initseq[] = { |
d203a7ec | 57 | INDYCAM_CONTROL_AGCENA, /* INDYCAM_CONTROL */ |
a637a114 | 58 | INDYCAM_SHUTTER_60, /* INDYCAM_SHUTTER */ |
d203a7ec RB |
59 | INDYCAM_GAIN_DEFAULT, /* INDYCAM_GAIN */ |
60 | 0x00, /* INDYCAM_BRIGHTNESS (read-only) */ | |
61 | INDYCAM_RED_BALANCE_DEFAULT, /* INDYCAM_RED_BALANCE */ | |
62 | INDYCAM_BLUE_BALANCE_DEFAULT, /* INDYCAM_BLUE_BALANCE */ | |
63 | INDYCAM_RED_SATURATION_DEFAULT, /* INDYCAM_RED_SATURATION */ | |
64 | INDYCAM_BLUE_SATURATION_DEFAULT,/* INDYCAM_BLUE_SATURATION */ | |
65 | }; | |
66 | ||
67 | /* IndyCam register handling */ | |
68 | ||
a637a114 | 69 | static int indycam_read_reg(struct i2c_client *client, u8 reg, u8 *value) |
d203a7ec RB |
70 | { |
71 | int ret; | |
72 | ||
a637a114 | 73 | if (reg == INDYCAM_REG_RESET) { |
d203a7ec RB |
74 | dprintk("indycam_read_reg(): " |
75 | "skipping write-only register %d\n", reg); | |
76 | *value = 0; | |
77 | return 0; | |
78 | } | |
79 | ||
80 | ret = i2c_smbus_read_byte_data(client, reg); | |
a637a114 | 81 | |
d203a7ec RB |
82 | if (ret < 0) { |
83 | printk(KERN_ERR "IndyCam: indycam_read_reg(): read failed, " | |
84 | "register = 0x%02x\n", reg); | |
85 | return ret; | |
86 | } | |
87 | ||
a637a114 | 88 | *value = (u8)ret; |
d203a7ec RB |
89 | |
90 | return 0; | |
91 | } | |
92 | ||
a637a114 | 93 | static int indycam_write_reg(struct i2c_client *client, u8 reg, u8 value) |
d203a7ec RB |
94 | { |
95 | int err; | |
96 | ||
a637a114 LM |
97 | if ((reg == INDYCAM_REG_BRIGHTNESS) |
98 | || (reg == INDYCAM_REG_VERSION)) { | |
d203a7ec RB |
99 | dprintk("indycam_write_reg(): " |
100 | "skipping read-only register %d\n", reg); | |
101 | return 0; | |
102 | } | |
103 | ||
104 | dprintk("Writing Reg %d = 0x%02x\n", reg, value); | |
105 | err = i2c_smbus_write_byte_data(client, reg, value); | |
a637a114 | 106 | |
d203a7ec RB |
107 | if (err) { |
108 | printk(KERN_ERR "IndyCam: indycam_write_reg(): write failed, " | |
109 | "register = 0x%02x, value = 0x%02x\n", reg, value); | |
110 | } | |
111 | return err; | |
112 | } | |
113 | ||
a637a114 LM |
114 | static int indycam_write_block(struct i2c_client *client, u8 reg, |
115 | u8 length, u8 *data) | |
d203a7ec | 116 | { |
a637a114 | 117 | int i, err; |
d203a7ec | 118 | |
a637a114 | 119 | for (i = 0; i < length; i++) { |
d203a7ec RB |
120 | err = indycam_write_reg(client, reg + i, data[i]); |
121 | if (err) | |
122 | return err; | |
123 | } | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | /* Helper functions */ | |
129 | ||
130 | #ifdef INDYCAM_DEBUG | |
131 | static void indycam_regdump_debug(struct i2c_client *client) | |
132 | { | |
133 | int i; | |
a637a114 | 134 | u8 val; |
d203a7ec RB |
135 | |
136 | for (i = 0; i < 9; i++) { | |
137 | indycam_read_reg(client, i, &val); | |
138 | dprintk("Reg %d = 0x%02x\n", i, val); | |
139 | } | |
140 | } | |
141 | #endif | |
142 | ||
a637a114 | 143 | static int indycam_get_control(struct i2c_client *client, |
cf4e9484 | 144 | struct v4l2_control *ctrl) |
d203a7ec | 145 | { |
a637a114 LM |
146 | struct indycam *camera = i2c_get_clientdata(client); |
147 | u8 reg; | |
148 | int ret = 0; | |
149 | ||
cf4e9484 HV |
150 | switch (ctrl->id) { |
151 | case V4L2_CID_AUTOGAIN: | |
152 | case V4L2_CID_AUTO_WHITE_BALANCE: | |
a637a114 LM |
153 | ret = indycam_read_reg(client, INDYCAM_REG_CONTROL, ®); |
154 | if (ret) | |
155 | return -EIO; | |
cf4e9484 | 156 | if (ctrl->id == V4L2_CID_AUTOGAIN) |
a637a114 LM |
157 | ctrl->value = (reg & INDYCAM_CONTROL_AGCENA) |
158 | ? 1 : 0; | |
159 | else | |
160 | ctrl->value = (reg & INDYCAM_CONTROL_AWBCTL) | |
161 | ? 1 : 0; | |
162 | break; | |
cf4e9484 | 163 | case V4L2_CID_EXPOSURE: |
a637a114 LM |
164 | ret = indycam_read_reg(client, INDYCAM_REG_SHUTTER, ®); |
165 | if (ret) | |
166 | return -EIO; | |
167 | ctrl->value = ((s32)reg == 0x00) ? 0xff : ((s32)reg - 1); | |
168 | break; | |
cf4e9484 | 169 | case V4L2_CID_GAIN: |
a637a114 LM |
170 | ret = indycam_read_reg(client, INDYCAM_REG_GAIN, ®); |
171 | if (ret) | |
172 | return -EIO; | |
173 | ctrl->value = (s32)reg; | |
174 | break; | |
cf4e9484 | 175 | case V4L2_CID_RED_BALANCE: |
a637a114 LM |
176 | ret = indycam_read_reg(client, INDYCAM_REG_RED_BALANCE, ®); |
177 | if (ret) | |
178 | return -EIO; | |
179 | ctrl->value = (s32)reg; | |
180 | break; | |
cf4e9484 | 181 | case V4L2_CID_BLUE_BALANCE: |
a637a114 LM |
182 | ret = indycam_read_reg(client, INDYCAM_REG_BLUE_BALANCE, ®); |
183 | if (ret) | |
184 | return -EIO; | |
185 | ctrl->value = (s32)reg; | |
186 | break; | |
187 | case INDYCAM_CONTROL_RED_SATURATION: | |
188 | ret = indycam_read_reg(client, | |
189 | INDYCAM_REG_RED_SATURATION, ®); | |
190 | if (ret) | |
191 | return -EIO; | |
192 | ctrl->value = (s32)reg; | |
193 | break; | |
194 | case INDYCAM_CONTROL_BLUE_SATURATION: | |
195 | ret = indycam_read_reg(client, | |
196 | INDYCAM_REG_BLUE_SATURATION, ®); | |
197 | if (ret) | |
198 | return -EIO; | |
199 | ctrl->value = (s32)reg; | |
200 | break; | |
cf4e9484 | 201 | case V4L2_CID_GAMMA: |
a637a114 LM |
202 | if (camera->version == CAMERA_VERSION_MOOSE) { |
203 | ret = indycam_read_reg(client, | |
204 | INDYCAM_REG_GAMMA, ®); | |
205 | if (ret) | |
206 | return -EIO; | |
207 | ctrl->value = (s32)reg; | |
208 | } else { | |
209 | ctrl->value = INDYCAM_GAMMA_DEFAULT; | |
210 | } | |
211 | break; | |
212 | default: | |
213 | ret = -EINVAL; | |
214 | } | |
d203a7ec | 215 | |
a637a114 | 216 | return ret; |
d203a7ec RB |
217 | } |
218 | ||
a637a114 | 219 | static int indycam_set_control(struct i2c_client *client, |
cf4e9484 | 220 | struct v4l2_control *ctrl) |
d203a7ec | 221 | { |
a637a114 LM |
222 | struct indycam *camera = i2c_get_clientdata(client); |
223 | u8 reg; | |
224 | int ret = 0; | |
225 | ||
cf4e9484 HV |
226 | switch (ctrl->id) { |
227 | case V4L2_CID_AUTOGAIN: | |
228 | case V4L2_CID_AUTO_WHITE_BALANCE: | |
a637a114 LM |
229 | ret = indycam_read_reg(client, INDYCAM_REG_CONTROL, ®); |
230 | if (ret) | |
231 | break; | |
d203a7ec | 232 | |
cf4e9484 | 233 | if (ctrl->id == V4L2_CID_AUTOGAIN) { |
a637a114 LM |
234 | if (ctrl->value) |
235 | reg |= INDYCAM_CONTROL_AGCENA; | |
236 | else | |
237 | reg &= ~INDYCAM_CONTROL_AGCENA; | |
238 | } else { | |
239 | if (ctrl->value) | |
240 | reg |= INDYCAM_CONTROL_AWBCTL; | |
241 | else | |
242 | reg &= ~INDYCAM_CONTROL_AWBCTL; | |
243 | } | |
244 | ||
245 | ret = indycam_write_reg(client, INDYCAM_REG_CONTROL, reg); | |
246 | break; | |
cf4e9484 | 247 | case V4L2_CID_EXPOSURE: |
a637a114 LM |
248 | reg = (ctrl->value == 0xff) ? 0x00 : (ctrl->value + 1); |
249 | ret = indycam_write_reg(client, INDYCAM_REG_SHUTTER, reg); | |
250 | break; | |
cf4e9484 | 251 | case V4L2_CID_GAIN: |
a637a114 LM |
252 | ret = indycam_write_reg(client, INDYCAM_REG_GAIN, ctrl->value); |
253 | break; | |
cf4e9484 | 254 | case V4L2_CID_RED_BALANCE: |
a637a114 LM |
255 | ret = indycam_write_reg(client, INDYCAM_REG_RED_BALANCE, |
256 | ctrl->value); | |
257 | break; | |
cf4e9484 | 258 | case V4L2_CID_BLUE_BALANCE: |
a637a114 LM |
259 | ret = indycam_write_reg(client, INDYCAM_REG_BLUE_BALANCE, |
260 | ctrl->value); | |
261 | break; | |
262 | case INDYCAM_CONTROL_RED_SATURATION: | |
263 | ret = indycam_write_reg(client, INDYCAM_REG_RED_SATURATION, | |
264 | ctrl->value); | |
265 | break; | |
266 | case INDYCAM_CONTROL_BLUE_SATURATION: | |
267 | ret = indycam_write_reg(client, INDYCAM_REG_BLUE_SATURATION, | |
268 | ctrl->value); | |
269 | break; | |
cf4e9484 | 270 | case V4L2_CID_GAMMA: |
a637a114 LM |
271 | if (camera->version == CAMERA_VERSION_MOOSE) { |
272 | ret = indycam_write_reg(client, INDYCAM_REG_GAMMA, | |
273 | ctrl->value); | |
274 | } | |
275 | break; | |
276 | default: | |
277 | ret = -EINVAL; | |
d203a7ec | 278 | } |
d203a7ec | 279 | |
a637a114 | 280 | return ret; |
d203a7ec RB |
281 | } |
282 | ||
283 | /* I2C-interface */ | |
284 | ||
cf4e9484 HV |
285 | static int indycam_command(struct i2c_client *client, unsigned int cmd, |
286 | void *arg) | |
287 | { | |
288 | /* The old video_decoder interface just isn't enough, | |
289 | * so we'll use some custom commands. */ | |
290 | switch (cmd) { | |
291 | case VIDIOC_G_CTRL: | |
292 | return indycam_get_control(client, arg); | |
293 | ||
294 | case VIDIOC_S_CTRL: | |
295 | return indycam_set_control(client, arg); | |
296 | ||
297 | default: | |
298 | return -EINVAL; | |
299 | } | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
304 | static int indycam_probe(struct i2c_client *client, | |
305 | const struct i2c_device_id *id) | |
d203a7ec RB |
306 | { |
307 | int err = 0; | |
308 | struct indycam *camera; | |
d203a7ec | 309 | |
cf4e9484 HV |
310 | v4l_info(client, "chip found @ 0x%x (%s)\n", |
311 | client->addr << 1, client->adapter->name); | |
d203a7ec | 312 | |
7408187d | 313 | camera = kzalloc(sizeof(struct indycam), GFP_KERNEL); |
cf4e9484 HV |
314 | if (!camera) |
315 | return -ENOMEM; | |
d203a7ec | 316 | |
d203a7ec RB |
317 | i2c_set_clientdata(client, camera); |
318 | ||
319 | camera->client = client; | |
320 | ||
a637a114 LM |
321 | camera->version = i2c_smbus_read_byte_data(client, |
322 | INDYCAM_REG_VERSION); | |
d203a7ec RB |
323 | if (camera->version != CAMERA_VERSION_INDY && |
324 | camera->version != CAMERA_VERSION_MOOSE) { | |
cf4e9484 HV |
325 | kfree(camera); |
326 | return -ENODEV; | |
d203a7ec | 327 | } |
cf4e9484 | 328 | |
d203a7ec RB |
329 | printk(KERN_INFO "IndyCam v%d.%d detected\n", |
330 | INDYCAM_VERSION_MAJOR(camera->version), | |
331 | INDYCAM_VERSION_MINOR(camera->version)); | |
332 | ||
333 | indycam_regdump(client); | |
334 | ||
335 | // initialize | |
a637a114 | 336 | err = indycam_write_block(client, 0, sizeof(initseq), (u8 *)&initseq); |
d203a7ec | 337 | if (err) { |
c84e6036 | 338 | printk(KERN_ERR "IndyCam initialization failed\n"); |
cf4e9484 HV |
339 | kfree(camera); |
340 | return -EIO; | |
d203a7ec RB |
341 | } |
342 | ||
343 | indycam_regdump(client); | |
344 | ||
345 | // white balance | |
a637a114 | 346 | err = indycam_write_reg(client, INDYCAM_REG_CONTROL, |
d203a7ec RB |
347 | INDYCAM_CONTROL_AGCENA | INDYCAM_CONTROL_AWBCTL); |
348 | if (err) { | |
a637a114 | 349 | printk(KERN_ERR "IndyCam: White balancing camera failed\n"); |
cf4e9484 HV |
350 | kfree(camera); |
351 | return -EIO; | |
d203a7ec RB |
352 | } |
353 | ||
354 | indycam_regdump(client); | |
355 | ||
356 | printk(KERN_INFO "IndyCam initialized\n"); | |
357 | ||
358 | return 0; | |
d203a7ec RB |
359 | } |
360 | ||
cf4e9484 | 361 | static int indycam_remove(struct i2c_client *client) |
d203a7ec RB |
362 | { |
363 | struct indycam *camera = i2c_get_clientdata(client); | |
364 | ||
d203a7ec | 365 | kfree(camera); |
d203a7ec RB |
366 | return 0; |
367 | } | |
368 | ||
cf4e9484 | 369 | static int indycam_legacy_probe(struct i2c_adapter *adapter) |
d203a7ec | 370 | { |
cf4e9484 | 371 | return adapter->id == I2C_HW_SGI_VINO; |
d203a7ec RB |
372 | } |
373 | ||
cf4e9484 HV |
374 | static const struct i2c_device_id indycam_id[] = { |
375 | { "indycam", 0 }, | |
376 | { } | |
377 | }; | |
378 | MODULE_DEVICE_TABLE(i2c, indycam_id); | |
379 | ||
380 | static struct v4l2_i2c_driver_data v4l2_i2c_data = { | |
381 | .name = "indycam", | |
382 | .driverid = I2C_DRIVERID_INDYCAM, | |
383 | .command = indycam_command, | |
384 | .probe = indycam_probe, | |
385 | .remove = indycam_remove, | |
386 | .legacy_probe = indycam_legacy_probe, | |
387 | .id_table = indycam_id, | |
d203a7ec | 388 | }; |