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