1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (C) 2005-2006 Micronas USA Inc.
4*4882a593Smuzhiyun */
5*4882a593Smuzhiyun
6*4882a593Smuzhiyun #include <linux/module.h>
7*4882a593Smuzhiyun #include <linux/init.h>
8*4882a593Smuzhiyun #include <linux/i2c.h>
9*4882a593Smuzhiyun #include <linux/videodev2.h>
10*4882a593Smuzhiyun #include <linux/ioctl.h>
11*4882a593Smuzhiyun #include <linux/slab.h>
12*4882a593Smuzhiyun #include <media/v4l2-device.h>
13*4882a593Smuzhiyun #include <media/v4l2-ctrls.h>
14*4882a593Smuzhiyun
15*4882a593Smuzhiyun MODULE_DESCRIPTION("TW9906 I2C subdev driver");
16*4882a593Smuzhiyun MODULE_LICENSE("GPL v2");
17*4882a593Smuzhiyun
18*4882a593Smuzhiyun struct tw9906 {
19*4882a593Smuzhiyun struct v4l2_subdev sd;
20*4882a593Smuzhiyun struct v4l2_ctrl_handler hdl;
21*4882a593Smuzhiyun v4l2_std_id norm;
22*4882a593Smuzhiyun };
23*4882a593Smuzhiyun
to_state(struct v4l2_subdev * sd)24*4882a593Smuzhiyun static inline struct tw9906 *to_state(struct v4l2_subdev *sd)
25*4882a593Smuzhiyun {
26*4882a593Smuzhiyun return container_of(sd, struct tw9906, sd);
27*4882a593Smuzhiyun }
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun static const u8 initial_registers[] = {
30*4882a593Smuzhiyun 0x02, 0x40, /* input 0, composite */
31*4882a593Smuzhiyun 0x03, 0xa2, /* correct digital format */
32*4882a593Smuzhiyun 0x05, 0x81, /* or 0x01 for PAL */
33*4882a593Smuzhiyun 0x07, 0x02, /* window */
34*4882a593Smuzhiyun 0x08, 0x14, /* window */
35*4882a593Smuzhiyun 0x09, 0xf0, /* window */
36*4882a593Smuzhiyun 0x0a, 0x10, /* window */
37*4882a593Smuzhiyun 0x0b, 0xd0, /* window */
38*4882a593Smuzhiyun 0x0d, 0x00, /* scaling */
39*4882a593Smuzhiyun 0x0e, 0x11, /* scaling */
40*4882a593Smuzhiyun 0x0f, 0x00, /* scaling */
41*4882a593Smuzhiyun 0x10, 0x00, /* brightness */
42*4882a593Smuzhiyun 0x11, 0x60, /* contrast */
43*4882a593Smuzhiyun 0x12, 0x11, /* sharpness */
44*4882a593Smuzhiyun 0x13, 0x7e, /* U gain */
45*4882a593Smuzhiyun 0x14, 0x7e, /* V gain */
46*4882a593Smuzhiyun 0x15, 0x00, /* hue */
47*4882a593Smuzhiyun 0x19, 0x57, /* vbi */
48*4882a593Smuzhiyun 0x1a, 0x0f,
49*4882a593Smuzhiyun 0x1b, 0x40,
50*4882a593Smuzhiyun 0x29, 0x03,
51*4882a593Smuzhiyun 0x55, 0x00,
52*4882a593Smuzhiyun 0x6b, 0x26,
53*4882a593Smuzhiyun 0x6c, 0x36,
54*4882a593Smuzhiyun 0x6d, 0xf0,
55*4882a593Smuzhiyun 0x6e, 0x41,
56*4882a593Smuzhiyun 0x6f, 0x13,
57*4882a593Smuzhiyun 0xad, 0x70,
58*4882a593Smuzhiyun 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */
59*4882a593Smuzhiyun };
60*4882a593Smuzhiyun
write_reg(struct v4l2_subdev * sd,u8 reg,u8 value)61*4882a593Smuzhiyun static int write_reg(struct v4l2_subdev *sd, u8 reg, u8 value)
62*4882a593Smuzhiyun {
63*4882a593Smuzhiyun struct i2c_client *client = v4l2_get_subdevdata(sd);
64*4882a593Smuzhiyun
65*4882a593Smuzhiyun return i2c_smbus_write_byte_data(client, reg, value);
66*4882a593Smuzhiyun }
67*4882a593Smuzhiyun
write_regs(struct v4l2_subdev * sd,const u8 * regs)68*4882a593Smuzhiyun static int write_regs(struct v4l2_subdev *sd, const u8 *regs)
69*4882a593Smuzhiyun {
70*4882a593Smuzhiyun int i;
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun for (i = 0; regs[i] != 0x00; i += 2)
73*4882a593Smuzhiyun if (write_reg(sd, regs[i], regs[i + 1]) < 0)
74*4882a593Smuzhiyun return -1;
75*4882a593Smuzhiyun return 0;
76*4882a593Smuzhiyun }
77*4882a593Smuzhiyun
tw9906_s_video_routing(struct v4l2_subdev * sd,u32 input,u32 output,u32 config)78*4882a593Smuzhiyun static int tw9906_s_video_routing(struct v4l2_subdev *sd, u32 input,
79*4882a593Smuzhiyun u32 output, u32 config)
80*4882a593Smuzhiyun {
81*4882a593Smuzhiyun write_reg(sd, 0x02, 0x40 | (input << 1));
82*4882a593Smuzhiyun return 0;
83*4882a593Smuzhiyun }
84*4882a593Smuzhiyun
tw9906_s_std(struct v4l2_subdev * sd,v4l2_std_id norm)85*4882a593Smuzhiyun static int tw9906_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
86*4882a593Smuzhiyun {
87*4882a593Smuzhiyun struct tw9906 *dec = to_state(sd);
88*4882a593Smuzhiyun bool is_60hz = norm & V4L2_STD_525_60;
89*4882a593Smuzhiyun static const u8 config_60hz[] = {
90*4882a593Smuzhiyun 0x05, 0x81,
91*4882a593Smuzhiyun 0x07, 0x02,
92*4882a593Smuzhiyun 0x08, 0x14,
93*4882a593Smuzhiyun 0x09, 0xf0,
94*4882a593Smuzhiyun 0, 0,
95*4882a593Smuzhiyun };
96*4882a593Smuzhiyun static const u8 config_50hz[] = {
97*4882a593Smuzhiyun 0x05, 0x01,
98*4882a593Smuzhiyun 0x07, 0x12,
99*4882a593Smuzhiyun 0x08, 0x18,
100*4882a593Smuzhiyun 0x09, 0x20,
101*4882a593Smuzhiyun 0, 0,
102*4882a593Smuzhiyun };
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun write_regs(sd, is_60hz ? config_60hz : config_50hz);
105*4882a593Smuzhiyun dec->norm = norm;
106*4882a593Smuzhiyun return 0;
107*4882a593Smuzhiyun }
108*4882a593Smuzhiyun
tw9906_s_ctrl(struct v4l2_ctrl * ctrl)109*4882a593Smuzhiyun static int tw9906_s_ctrl(struct v4l2_ctrl *ctrl)
110*4882a593Smuzhiyun {
111*4882a593Smuzhiyun struct tw9906 *dec = container_of(ctrl->handler, struct tw9906, hdl);
112*4882a593Smuzhiyun struct v4l2_subdev *sd = &dec->sd;
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun switch (ctrl->id) {
115*4882a593Smuzhiyun case V4L2_CID_BRIGHTNESS:
116*4882a593Smuzhiyun write_reg(sd, 0x10, ctrl->val);
117*4882a593Smuzhiyun break;
118*4882a593Smuzhiyun case V4L2_CID_CONTRAST:
119*4882a593Smuzhiyun write_reg(sd, 0x11, ctrl->val);
120*4882a593Smuzhiyun break;
121*4882a593Smuzhiyun case V4L2_CID_HUE:
122*4882a593Smuzhiyun write_reg(sd, 0x15, ctrl->val);
123*4882a593Smuzhiyun break;
124*4882a593Smuzhiyun default:
125*4882a593Smuzhiyun return -EINVAL;
126*4882a593Smuzhiyun }
127*4882a593Smuzhiyun return 0;
128*4882a593Smuzhiyun }
129*4882a593Smuzhiyun
tw9906_log_status(struct v4l2_subdev * sd)130*4882a593Smuzhiyun static int tw9906_log_status(struct v4l2_subdev *sd)
131*4882a593Smuzhiyun {
132*4882a593Smuzhiyun struct tw9906 *dec = to_state(sd);
133*4882a593Smuzhiyun bool is_60hz = dec->norm & V4L2_STD_525_60;
134*4882a593Smuzhiyun
135*4882a593Smuzhiyun v4l2_info(sd, "Standard: %d Hz\n", is_60hz ? 60 : 50);
136*4882a593Smuzhiyun v4l2_ctrl_subdev_log_status(sd);
137*4882a593Smuzhiyun return 0;
138*4882a593Smuzhiyun }
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun /* --------------------------------------------------------------------------*/
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun static const struct v4l2_ctrl_ops tw9906_ctrl_ops = {
143*4882a593Smuzhiyun .s_ctrl = tw9906_s_ctrl,
144*4882a593Smuzhiyun };
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun static const struct v4l2_subdev_core_ops tw9906_core_ops = {
147*4882a593Smuzhiyun .log_status = tw9906_log_status,
148*4882a593Smuzhiyun };
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun static const struct v4l2_subdev_video_ops tw9906_video_ops = {
151*4882a593Smuzhiyun .s_std = tw9906_s_std,
152*4882a593Smuzhiyun .s_routing = tw9906_s_video_routing,
153*4882a593Smuzhiyun };
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun static const struct v4l2_subdev_ops tw9906_ops = {
156*4882a593Smuzhiyun .core = &tw9906_core_ops,
157*4882a593Smuzhiyun .video = &tw9906_video_ops,
158*4882a593Smuzhiyun };
159*4882a593Smuzhiyun
tw9906_probe(struct i2c_client * client,const struct i2c_device_id * id)160*4882a593Smuzhiyun static int tw9906_probe(struct i2c_client *client,
161*4882a593Smuzhiyun const struct i2c_device_id *id)
162*4882a593Smuzhiyun {
163*4882a593Smuzhiyun struct tw9906 *dec;
164*4882a593Smuzhiyun struct v4l2_subdev *sd;
165*4882a593Smuzhiyun struct v4l2_ctrl_handler *hdl;
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun /* Check if the adapter supports the needed features */
168*4882a593Smuzhiyun if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
169*4882a593Smuzhiyun return -EIO;
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun v4l_info(client, "chip found @ 0x%02x (%s)\n",
172*4882a593Smuzhiyun client->addr << 1, client->adapter->name);
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun dec = devm_kzalloc(&client->dev, sizeof(*dec), GFP_KERNEL);
175*4882a593Smuzhiyun if (dec == NULL)
176*4882a593Smuzhiyun return -ENOMEM;
177*4882a593Smuzhiyun sd = &dec->sd;
178*4882a593Smuzhiyun v4l2_i2c_subdev_init(sd, client, &tw9906_ops);
179*4882a593Smuzhiyun hdl = &dec->hdl;
180*4882a593Smuzhiyun v4l2_ctrl_handler_init(hdl, 4);
181*4882a593Smuzhiyun v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops,
182*4882a593Smuzhiyun V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
183*4882a593Smuzhiyun v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops,
184*4882a593Smuzhiyun V4L2_CID_CONTRAST, 0, 255, 1, 0x60);
185*4882a593Smuzhiyun v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops,
186*4882a593Smuzhiyun V4L2_CID_HUE, -128, 127, 1, 0);
187*4882a593Smuzhiyun sd->ctrl_handler = hdl;
188*4882a593Smuzhiyun if (hdl->error) {
189*4882a593Smuzhiyun int err = hdl->error;
190*4882a593Smuzhiyun
191*4882a593Smuzhiyun v4l2_ctrl_handler_free(hdl);
192*4882a593Smuzhiyun return err;
193*4882a593Smuzhiyun }
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun /* Initialize tw9906 */
196*4882a593Smuzhiyun dec->norm = V4L2_STD_NTSC;
197*4882a593Smuzhiyun
198*4882a593Smuzhiyun if (write_regs(sd, initial_registers) < 0) {
199*4882a593Smuzhiyun v4l2_err(client, "error initializing TW9906\n");
200*4882a593Smuzhiyun return -EINVAL;
201*4882a593Smuzhiyun }
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun return 0;
204*4882a593Smuzhiyun }
205*4882a593Smuzhiyun
tw9906_remove(struct i2c_client * client)206*4882a593Smuzhiyun static int tw9906_remove(struct i2c_client *client)
207*4882a593Smuzhiyun {
208*4882a593Smuzhiyun struct v4l2_subdev *sd = i2c_get_clientdata(client);
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun v4l2_device_unregister_subdev(sd);
211*4882a593Smuzhiyun v4l2_ctrl_handler_free(&to_state(sd)->hdl);
212*4882a593Smuzhiyun return 0;
213*4882a593Smuzhiyun }
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun /* ----------------------------------------------------------------------- */
216*4882a593Smuzhiyun
217*4882a593Smuzhiyun static const struct i2c_device_id tw9906_id[] = {
218*4882a593Smuzhiyun { "tw9906", 0 },
219*4882a593Smuzhiyun { }
220*4882a593Smuzhiyun };
221*4882a593Smuzhiyun MODULE_DEVICE_TABLE(i2c, tw9906_id);
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun static struct i2c_driver tw9906_driver = {
224*4882a593Smuzhiyun .driver = {
225*4882a593Smuzhiyun .name = "tw9906",
226*4882a593Smuzhiyun },
227*4882a593Smuzhiyun .probe = tw9906_probe,
228*4882a593Smuzhiyun .remove = tw9906_remove,
229*4882a593Smuzhiyun .id_table = tw9906_id,
230*4882a593Smuzhiyun };
231*4882a593Smuzhiyun module_i2c_driver(tw9906_driver);
232