Merge tag 'devdax-for-5.1' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm...
[linux-2.6-block.git] / drivers / media / i2c / tw2804.c
CommitLineData
12be52a9
HV
1/*
2 * Copyright (C) 2005-2006 Micronas USA Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License (Version 2) as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12be52a9
HV
12 */
13
14#include <linux/module.h>
15#include <linux/init.h>
16#include <linux/i2c.h>
17#include <linux/videodev2.h>
18#include <linux/ioctl.h>
19#include <linux/slab.h>
20#include <media/v4l2-subdev.h>
21#include <media/v4l2-device.h>
12be52a9
HV
22#include <media/v4l2-ctrls.h>
23
24#define TW2804_REG_AUTOGAIN 0x02
25#define TW2804_REG_HUE 0x0f
26#define TW2804_REG_SATURATION 0x10
27#define TW2804_REG_CONTRAST 0x11
28#define TW2804_REG_BRIGHTNESS 0x12
29#define TW2804_REG_COLOR_KILLER 0x14
30#define TW2804_REG_GAIN 0x3c
31#define TW2804_REG_CHROMA_GAIN 0x3d
32#define TW2804_REG_BLUE_BALANCE 0x3e
33#define TW2804_REG_RED_BALANCE 0x3f
34
35struct tw2804 {
36 struct v4l2_subdev sd;
37 struct v4l2_ctrl_handler hdl;
38 u8 channel:2;
39 u8 input:1;
40 int norm;
41};
42
43static const u8 global_registers[] = {
44 0x39, 0x00,
45 0x3a, 0xff,
46 0x3b, 0x84,
47 0x3c, 0x80,
48 0x3d, 0x80,
49 0x3e, 0x82,
50 0x3f, 0x82,
8dbee53b 51 0x78, 0x00,
12be52a9
HV
52 0xff, 0xff, /* Terminator (reg 0xff does not exist) */
53};
54
55static const u8 channel_registers[] = {
56 0x01, 0xc4,
57 0x02, 0xa5,
58 0x03, 0x20,
59 0x04, 0xd0,
60 0x05, 0x20,
61 0x06, 0xd0,
62 0x07, 0x88,
63 0x08, 0x20,
64 0x09, 0x07,
65 0x0a, 0xf0,
66 0x0b, 0x07,
67 0x0c, 0xf0,
68 0x0d, 0x40,
69 0x0e, 0xd2,
70 0x0f, 0x80,
71 0x10, 0x80,
72 0x11, 0x80,
73 0x12, 0x80,
74 0x13, 0x1f,
75 0x14, 0x00,
76 0x15, 0x00,
77 0x16, 0x00,
78 0x17, 0x00,
79 0x18, 0xff,
80 0x19, 0xff,
81 0x1a, 0xff,
82 0x1b, 0xff,
83 0x1c, 0xff,
84 0x1d, 0xff,
85 0x1e, 0xff,
86 0x1f, 0xff,
87 0x20, 0x07,
88 0x21, 0x07,
89 0x22, 0x00,
90 0x23, 0x91,
91 0x24, 0x51,
92 0x25, 0x03,
93 0x26, 0x00,
94 0x27, 0x00,
95 0x28, 0x00,
96 0x29, 0x00,
97 0x2a, 0x00,
98 0x2b, 0x00,
99 0x2c, 0x00,
100 0x2d, 0x00,
101 0x2e, 0x00,
102 0x2f, 0x00,
103 0x30, 0x00,
104 0x31, 0x00,
105 0x32, 0x00,
106 0x33, 0x00,
107 0x34, 0x00,
108 0x35, 0x00,
109 0x36, 0x00,
110 0x37, 0x00,
111 0xff, 0xff, /* Terminator (reg 0xff does not exist) */
112};
113
114static int write_reg(struct i2c_client *client, u8 reg, u8 value, u8 channel)
115{
116 return i2c_smbus_write_byte_data(client, reg | (channel << 6), value);
117}
118
119static int write_regs(struct i2c_client *client, const u8 *regs, u8 channel)
120{
121 int ret;
122 int i;
123
124 for (i = 0; regs[i] != 0xff; i += 2) {
125 ret = i2c_smbus_write_byte_data(client,
126 regs[i] | (channel << 6), regs[i + 1]);
127 if (ret < 0)
128 return ret;
129 }
130 return 0;
131}
132
133static int read_reg(struct i2c_client *client, u8 reg, u8 channel)
134{
135 return i2c_smbus_read_byte_data(client, (reg) | (channel << 6));
136}
137
138static inline struct tw2804 *to_state(struct v4l2_subdev *sd)
139{
140 return container_of(sd, struct tw2804, sd);
141}
142
143static inline struct tw2804 *to_state_from_ctrl(struct v4l2_ctrl *ctrl)
144{
145 return container_of(ctrl->handler, struct tw2804, hdl);
146}
147
148static int tw2804_log_status(struct v4l2_subdev *sd)
149{
150 struct tw2804 *state = to_state(sd);
151
152 v4l2_info(sd, "Standard: %s\n",
153 state->norm & V4L2_STD_525_60 ? "60 Hz" : "50 Hz");
154 v4l2_info(sd, "Channel: %d\n", state->channel);
155 v4l2_info(sd, "Input: %d\n", state->input);
156 return v4l2_ctrl_subdev_log_status(sd);
157}
158
159/*
160 * These volatile controls are needed because all four channels share
161 * these controls. So a change made to them through one channel would
162 * require another channel to be updated.
163 *
164 * Normally this would have been done in a different way, but since the one
165 * board that uses this driver sees this single chip as if it was on four
166 * different i2c adapters (each adapter belonging to a separate instance of
167 * the same USB driver) there is no reliable method that I have found to let
168 * the instances know about each other.
169 *
170 * So implementing these global registers as volatile is the best we can do.
171 */
172static int tw2804_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
173{
174 struct tw2804 *state = to_state_from_ctrl(ctrl);
175 struct i2c_client *client = v4l2_get_subdevdata(&state->sd);
176
177 switch (ctrl->id) {
178 case V4L2_CID_GAIN:
179 ctrl->val = read_reg(client, TW2804_REG_GAIN, 0);
180 return 0;
181
182 case V4L2_CID_CHROMA_GAIN:
183 ctrl->val = read_reg(client, TW2804_REG_CHROMA_GAIN, 0);
184 return 0;
185
186 case V4L2_CID_BLUE_BALANCE:
187 ctrl->val = read_reg(client, TW2804_REG_BLUE_BALANCE, 0);
188 return 0;
189
190 case V4L2_CID_RED_BALANCE:
191 ctrl->val = read_reg(client, TW2804_REG_RED_BALANCE, 0);
192 return 0;
193 }
194 return 0;
195}
196
197static int tw2804_s_ctrl(struct v4l2_ctrl *ctrl)
198{
199 struct tw2804 *state = to_state_from_ctrl(ctrl);
200 struct i2c_client *client = v4l2_get_subdevdata(&state->sd);
201 int addr;
202 int reg;
203
204 switch (ctrl->id) {
205 case V4L2_CID_AUTOGAIN:
206 addr = TW2804_REG_AUTOGAIN;
207 reg = read_reg(client, addr, state->channel);
208 if (reg < 0)
209 return reg;
210 if (ctrl->val == 0)
211 reg &= ~(1 << 7);
212 else
213 reg |= 1 << 7;
214 return write_reg(client, addr, reg, state->channel);
215
216 case V4L2_CID_COLOR_KILLER:
217 addr = TW2804_REG_COLOR_KILLER;
218 reg = read_reg(client, addr, state->channel);
219 if (reg < 0)
220 return reg;
221 reg = (reg & ~(0x03)) | (ctrl->val == 0 ? 0x02 : 0x03);
222 return write_reg(client, addr, reg, state->channel);
223
224 case V4L2_CID_GAIN:
225 return write_reg(client, TW2804_REG_GAIN, ctrl->val, 0);
226
227 case V4L2_CID_CHROMA_GAIN:
228 return write_reg(client, TW2804_REG_CHROMA_GAIN, ctrl->val, 0);
229
230 case V4L2_CID_BLUE_BALANCE:
231 return write_reg(client, TW2804_REG_BLUE_BALANCE, ctrl->val, 0);
232
233 case V4L2_CID_RED_BALANCE:
234 return write_reg(client, TW2804_REG_RED_BALANCE, ctrl->val, 0);
235
236 case V4L2_CID_BRIGHTNESS:
237 return write_reg(client, TW2804_REG_BRIGHTNESS,
238 ctrl->val, state->channel);
239
240 case V4L2_CID_CONTRAST:
241 return write_reg(client, TW2804_REG_CONTRAST,
242 ctrl->val, state->channel);
243
244 case V4L2_CID_SATURATION:
245 return write_reg(client, TW2804_REG_SATURATION,
246 ctrl->val, state->channel);
247
248 case V4L2_CID_HUE:
249 return write_reg(client, TW2804_REG_HUE,
250 ctrl->val, state->channel);
251
252 default:
253 break;
254 }
255 return -EINVAL;
256}
257
258static int tw2804_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
259{
260 struct tw2804 *dec = to_state(sd);
261 struct i2c_client *client = v4l2_get_subdevdata(sd);
262 bool is_60hz = norm & V4L2_STD_525_60;
263 u8 regs[] = {
264 0x01, is_60hz ? 0xc4 : 0x84,
265 0x09, is_60hz ? 0x07 : 0x04,
266 0x0a, is_60hz ? 0xf0 : 0x20,
267 0x0b, is_60hz ? 0x07 : 0x04,
268 0x0c, is_60hz ? 0xf0 : 0x20,
269 0x0d, is_60hz ? 0x40 : 0x4a,
270 0x16, is_60hz ? 0x00 : 0x40,
271 0x17, is_60hz ? 0x00 : 0x40,
272 0x20, is_60hz ? 0x07 : 0x0f,
273 0x21, is_60hz ? 0x07 : 0x0f,
274 0xff, 0xff,
275 };
276
277 write_regs(client, regs, dec->channel);
278 dec->norm = norm;
279 return 0;
280}
281
282static int tw2804_s_video_routing(struct v4l2_subdev *sd, u32 input, u32 output,
283 u32 config)
284{
285 struct tw2804 *dec = to_state(sd);
286 struct i2c_client *client = v4l2_get_subdevdata(sd);
287 int reg;
288
289 if (config && config - 1 != dec->channel) {
290 if (config > 4) {
291 dev_err(&client->dev,
292 "channel %d is not between 1 and 4!\n", config);
293 return -EINVAL;
294 }
295 dec->channel = config - 1;
296 dev_dbg(&client->dev, "initializing TW2804 channel %d\n",
297 dec->channel);
298 if (dec->channel == 0 &&
299 write_regs(client, global_registers, 0) < 0) {
300 dev_err(&client->dev,
301 "error initializing TW2804 global registers\n");
302 return -EIO;
303 }
304 if (write_regs(client, channel_registers, dec->channel) < 0) {
305 dev_err(&client->dev,
306 "error initializing TW2804 channel %d\n",
307 dec->channel);
308 return -EIO;
309 }
310 }
311
312 if (input > 1)
313 return -EINVAL;
314
315 if (input == dec->input)
316 return 0;
317
318 reg = read_reg(client, 0x22, dec->channel);
319
320 if (reg >= 0) {
321 if (input == 0)
322 reg &= ~(1 << 2);
323 else
324 reg |= 1 << 2;
325 reg = write_reg(client, 0x22, reg, dec->channel);
326 }
327
328 if (reg >= 0)
329 dec->input = input;
330 else
331 return reg;
332 return 0;
333}
334
12be52a9
HV
335static const struct v4l2_ctrl_ops tw2804_ctrl_ops = {
336 .g_volatile_ctrl = tw2804_g_volatile_ctrl,
337 .s_ctrl = tw2804_s_ctrl,
338};
339
340static const struct v4l2_subdev_video_ops tw2804_video_ops = {
8774bed9 341 .s_std = tw2804_s_std,
12be52a9 342 .s_routing = tw2804_s_video_routing,
12be52a9
HV
343};
344
345static const struct v4l2_subdev_core_ops tw2804_core_ops = {
346 .log_status = tw2804_log_status,
12be52a9
HV
347};
348
349static const struct v4l2_subdev_ops tw2804_ops = {
350 .core = &tw2804_core_ops,
351 .video = &tw2804_video_ops,
352};
353
354static int tw2804_probe(struct i2c_client *client,
355 const struct i2c_device_id *id)
356{
357 struct i2c_adapter *adapter = client->adapter;
358 struct tw2804 *state;
359 struct v4l2_subdev *sd;
360 struct v4l2_ctrl *ctrl;
361 int err;
362
363 if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
364 return -ENODEV;
365
c02b211d 366 state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
12be52a9
HV
367 if (state == NULL)
368 return -ENOMEM;
369 sd = &state->sd;
370 v4l2_i2c_subdev_init(sd, client, &tw2804_ops);
371 state->channel = -1;
372 state->norm = V4L2_STD_NTSC;
373
374 v4l2_ctrl_handler_init(&state->hdl, 10);
375 v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
376 V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
377 v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
378 V4L2_CID_CONTRAST, 0, 255, 1, 128);
379 v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
380 V4L2_CID_SATURATION, 0, 255, 1, 128);
381 v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
382 V4L2_CID_HUE, 0, 255, 1, 128);
383 v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
384 V4L2_CID_COLOR_KILLER, 0, 1, 1, 0);
385 v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
386 V4L2_CID_AUTOGAIN, 0, 1, 1, 0);
387 ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
388 V4L2_CID_GAIN, 0, 255, 1, 128);
389 if (ctrl)
390 ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
391 ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
392 V4L2_CID_CHROMA_GAIN, 0, 255, 1, 128);
393 if (ctrl)
394 ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
395 ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
396 V4L2_CID_BLUE_BALANCE, 0, 255, 1, 122);
397 if (ctrl)
398 ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
399 ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops,
400 V4L2_CID_RED_BALANCE, 0, 255, 1, 122);
401 if (ctrl)
402 ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
403 sd->ctrl_handler = &state->hdl;
404 err = state->hdl.error;
405 if (err) {
406 v4l2_ctrl_handler_free(&state->hdl);
12be52a9
HV
407 return err;
408 }
409
410 v4l_info(client, "chip found @ 0x%02x (%s)\n",
411 client->addr << 1, client->adapter->name);
412
413 return 0;
414}
415
416static int tw2804_remove(struct i2c_client *client)
417{
418 struct v4l2_subdev *sd = i2c_get_clientdata(client);
419 struct tw2804 *state = to_state(sd);
420
421 v4l2_device_unregister_subdev(sd);
422 v4l2_ctrl_handler_free(&state->hdl);
12be52a9
HV
423 return 0;
424}
425
426static const struct i2c_device_id tw2804_id[] = {
427 { "tw2804", 0 },
428 { }
429};
430MODULE_DEVICE_TABLE(i2c, tw2804_id);
431
432static struct i2c_driver tw2804_driver = {
433 .driver = {
434 .name = "tw2804",
435 },
436 .probe = tw2804_probe,
437 .remove = tw2804_remove,
438 .id_table = tw2804_id,
439};
440
441module_i2c_driver(tw2804_driver);
442
443MODULE_LICENSE("GPL v2");
444MODULE_DESCRIPTION("TW2804/TW2802 V4L2 i2c driver");
445MODULE_AUTHOR("Micronas USA Inc");