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