xref: /openbmc/linux/drivers/media/i2c/tw9906.c (revision 1802d0be)
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 
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 
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 
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 
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 
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 
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 
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 
160a000e9a0SHans Verkuil static int tw9906_probe(struct i2c_client *client,
161a000e9a0SHans Verkuil 			     const struct i2c_device_id *id)
162a000e9a0SHans Verkuil {
163a000e9a0SHans Verkuil 	struct tw9906 *dec;
164a000e9a0SHans Verkuil 	struct v4l2_subdev *sd;
165a000e9a0SHans Verkuil 	struct v4l2_ctrl_handler *hdl;
166a000e9a0SHans Verkuil 
167a000e9a0SHans Verkuil 	/* Check if the adapter supports the needed features */
168a000e9a0SHans Verkuil 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
169a000e9a0SHans Verkuil 		return -EIO;
170a000e9a0SHans Verkuil 
171a000e9a0SHans Verkuil 	v4l_info(client, "chip found @ 0x%02x (%s)\n",
172a000e9a0SHans Verkuil 			client->addr << 1, client->adapter->name);
173a000e9a0SHans Verkuil 
174c02b211dSLaurent Pinchart 	dec = devm_kzalloc(&client->dev, sizeof(*dec), GFP_KERNEL);
175a000e9a0SHans Verkuil 	if (dec == NULL)
176a000e9a0SHans Verkuil 		return -ENOMEM;
177a000e9a0SHans Verkuil 	sd = &dec->sd;
178a000e9a0SHans Verkuil 	v4l2_i2c_subdev_init(sd, client, &tw9906_ops);
179a000e9a0SHans Verkuil 	hdl = &dec->hdl;
180a000e9a0SHans Verkuil 	v4l2_ctrl_handler_init(hdl, 4);
181a000e9a0SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops,
182a000e9a0SHans Verkuil 		V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
183a000e9a0SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops,
184a000e9a0SHans Verkuil 		V4L2_CID_CONTRAST, 0, 255, 1, 0x60);
185a000e9a0SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops,
186a000e9a0SHans Verkuil 		V4L2_CID_HUE, -128, 127, 1, 0);
187a000e9a0SHans Verkuil 	sd->ctrl_handler = hdl;
188a000e9a0SHans Verkuil 	if (hdl->error) {
189a000e9a0SHans Verkuil 		int err = hdl->error;
190a000e9a0SHans Verkuil 
191a000e9a0SHans Verkuil 		v4l2_ctrl_handler_free(hdl);
192a000e9a0SHans Verkuil 		return err;
193a000e9a0SHans Verkuil 	}
194a000e9a0SHans Verkuil 
195a000e9a0SHans Verkuil 	/* Initialize tw9906 */
196a000e9a0SHans Verkuil 	dec->norm = V4L2_STD_NTSC;
197a000e9a0SHans Verkuil 
198a000e9a0SHans Verkuil 	if (write_regs(sd, initial_registers) < 0) {
199a000e9a0SHans Verkuil 		v4l2_err(client, "error initializing TW9906\n");
200a000e9a0SHans Verkuil 		return -EINVAL;
201a000e9a0SHans Verkuil 	}
202a000e9a0SHans Verkuil 
203a000e9a0SHans Verkuil 	return 0;
204a000e9a0SHans Verkuil }
205a000e9a0SHans Verkuil 
206a000e9a0SHans Verkuil static int tw9906_remove(struct i2c_client *client)
207a000e9a0SHans Verkuil {
208a000e9a0SHans Verkuil 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
209a000e9a0SHans Verkuil 
210a000e9a0SHans Verkuil 	v4l2_device_unregister_subdev(sd);
211a000e9a0SHans Verkuil 	v4l2_ctrl_handler_free(&to_state(sd)->hdl);
212a000e9a0SHans Verkuil 	return 0;
213a000e9a0SHans Verkuil }
214a000e9a0SHans Verkuil 
215a000e9a0SHans Verkuil /* ----------------------------------------------------------------------- */
216a000e9a0SHans Verkuil 
217a000e9a0SHans Verkuil static const struct i2c_device_id tw9906_id[] = {
218a000e9a0SHans Verkuil 	{ "tw9906", 0 },
219a000e9a0SHans Verkuil 	{ }
220a000e9a0SHans Verkuil };
221a000e9a0SHans Verkuil MODULE_DEVICE_TABLE(i2c, tw9906_id);
222a000e9a0SHans Verkuil 
223a000e9a0SHans Verkuil static struct i2c_driver tw9906_driver = {
224a000e9a0SHans Verkuil 	.driver = {
225a000e9a0SHans Verkuil 		.name	= "tw9906",
226a000e9a0SHans Verkuil 	},
227a000e9a0SHans Verkuil 	.probe = tw9906_probe,
228a000e9a0SHans Verkuil 	.remove = tw9906_remove,
229a000e9a0SHans Verkuil 	.id_table = tw9906_id,
230a000e9a0SHans Verkuil };
231a000e9a0SHans Verkuil module_i2c_driver(tw9906_driver);
232