xref: /openbmc/linux/drivers/media/i2c/tw9903.c (revision aaeb31c0)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
20890ec19SHans Verkuil /*
30890ec19SHans Verkuil  * Copyright (C) 2005-2006 Micronas USA Inc.
40890ec19SHans Verkuil  */
50890ec19SHans Verkuil 
60890ec19SHans Verkuil #include <linux/module.h>
70890ec19SHans Verkuil #include <linux/init.h>
80890ec19SHans Verkuil #include <linux/i2c.h>
90890ec19SHans Verkuil #include <linux/videodev2.h>
100890ec19SHans Verkuil #include <linux/ioctl.h>
110890ec19SHans Verkuil #include <media/v4l2-device.h>
120890ec19SHans Verkuil #include <media/v4l2-ctrls.h>
130890ec19SHans Verkuil #include <linux/slab.h>
140890ec19SHans Verkuil 
150890ec19SHans Verkuil MODULE_DESCRIPTION("TW9903 I2C subdev driver");
160890ec19SHans Verkuil MODULE_LICENSE("GPL v2");
170890ec19SHans Verkuil 
180890ec19SHans Verkuil /*
190890ec19SHans Verkuil  * This driver is based on the wis-tw9903.c source that was in
200890ec19SHans Verkuil  * drivers/staging/media/go7007. That source had commented out code for
210890ec19SHans Verkuil  * saturation and scaling (neither seemed to work). If anyone ever gets
220890ec19SHans Verkuil  * hardware to test this driver, then that code might be useful to look at.
230890ec19SHans Verkuil  * You need to get the kernel sources of, say, kernel 3.8 where that
240890ec19SHans Verkuil  * wis-tw9903 driver is still present.
250890ec19SHans Verkuil  */
260890ec19SHans Verkuil 
270890ec19SHans Verkuil struct tw9903 {
280890ec19SHans Verkuil 	struct v4l2_subdev sd;
290890ec19SHans Verkuil 	struct v4l2_ctrl_handler hdl;
300890ec19SHans Verkuil 	v4l2_std_id norm;
310890ec19SHans Verkuil };
320890ec19SHans Verkuil 
to_state(struct v4l2_subdev * sd)330890ec19SHans Verkuil static inline struct tw9903 *to_state(struct v4l2_subdev *sd)
340890ec19SHans Verkuil {
350890ec19SHans Verkuil 	return container_of(sd, struct tw9903, sd);
360890ec19SHans Verkuil }
370890ec19SHans Verkuil 
380890ec19SHans Verkuil static const u8 initial_registers[] = {
390890ec19SHans Verkuil 	0x02, 0x44, /* input 1, composite */
400890ec19SHans Verkuil 	0x03, 0x92, /* correct digital format */
410890ec19SHans Verkuil 	0x04, 0x00,
420890ec19SHans Verkuil 	0x05, 0x80, /* or 0x00 for PAL */
430890ec19SHans Verkuil 	0x06, 0x40, /* second internal current reference */
440890ec19SHans Verkuil 	0x07, 0x02, /* window */
450890ec19SHans Verkuil 	0x08, 0x14, /* window */
460890ec19SHans Verkuil 	0x09, 0xf0, /* window */
470890ec19SHans Verkuil 	0x0a, 0x81, /* window */
480890ec19SHans Verkuil 	0x0b, 0xd0, /* window */
490890ec19SHans Verkuil 	0x0c, 0x8c,
500890ec19SHans Verkuil 	0x0d, 0x00, /* scaling */
510890ec19SHans Verkuil 	0x0e, 0x11, /* scaling */
520890ec19SHans Verkuil 	0x0f, 0x00, /* scaling */
530890ec19SHans Verkuil 	0x10, 0x00, /* brightness */
540890ec19SHans Verkuil 	0x11, 0x60, /* contrast */
550890ec19SHans Verkuil 	0x12, 0x01, /* sharpness */
560890ec19SHans Verkuil 	0x13, 0x7f, /* U gain */
570890ec19SHans Verkuil 	0x14, 0x5a, /* V gain */
580890ec19SHans Verkuil 	0x15, 0x00, /* hue */
590890ec19SHans Verkuil 	0x16, 0xc3, /* sharpness */
600890ec19SHans Verkuil 	0x18, 0x00,
610890ec19SHans Verkuil 	0x19, 0x58, /* vbi */
620890ec19SHans Verkuil 	0x1a, 0x80,
630890ec19SHans Verkuil 	0x1c, 0x0f, /* video norm */
640890ec19SHans Verkuil 	0x1d, 0x7f, /* video norm */
650890ec19SHans Verkuil 	0x20, 0xa0, /* clamping gain (working 0x50) */
660890ec19SHans Verkuil 	0x21, 0x22,
670890ec19SHans Verkuil 	0x22, 0xf0,
680890ec19SHans Verkuil 	0x23, 0xfe,
690890ec19SHans Verkuil 	0x24, 0x3c,
700890ec19SHans Verkuil 	0x25, 0x38,
710890ec19SHans Verkuil 	0x26, 0x44,
720890ec19SHans Verkuil 	0x27, 0x20,
730890ec19SHans Verkuil 	0x28, 0x00,
740890ec19SHans Verkuil 	0x29, 0x15,
750890ec19SHans Verkuil 	0x2a, 0xa0,
760890ec19SHans Verkuil 	0x2b, 0x44,
770890ec19SHans Verkuil 	0x2c, 0x37,
780890ec19SHans Verkuil 	0x2d, 0x00,
790890ec19SHans Verkuil 	0x2e, 0xa5, /* burst PLL control (working: a9) */
800890ec19SHans Verkuil 	0x2f, 0xe0, /* 0xea is blue test frame -- 0xe0 for normal */
810890ec19SHans Verkuil 	0x31, 0x00,
820890ec19SHans Verkuil 	0x33, 0x22,
830890ec19SHans Verkuil 	0x34, 0x11,
840890ec19SHans Verkuil 	0x35, 0x35,
850890ec19SHans Verkuil 	0x3b, 0x05,
860890ec19SHans Verkuil 	0x06, 0xc0, /* reset device */
870890ec19SHans Verkuil 	0x00, 0x00, /* Terminator (reg 0x00 is read-only) */
880890ec19SHans Verkuil };
890890ec19SHans Verkuil 
write_reg(struct v4l2_subdev * sd,u8 reg,u8 value)900890ec19SHans Verkuil static int write_reg(struct v4l2_subdev *sd, u8 reg, u8 value)
910890ec19SHans Verkuil {
920890ec19SHans Verkuil 	struct i2c_client *client = v4l2_get_subdevdata(sd);
930890ec19SHans Verkuil 
940890ec19SHans Verkuil 	return i2c_smbus_write_byte_data(client, reg, value);
950890ec19SHans Verkuil }
960890ec19SHans Verkuil 
write_regs(struct v4l2_subdev * sd,const u8 * regs)970890ec19SHans Verkuil static int write_regs(struct v4l2_subdev *sd, const u8 *regs)
980890ec19SHans Verkuil {
990890ec19SHans Verkuil 	int i;
1000890ec19SHans Verkuil 
1010890ec19SHans Verkuil 	for (i = 0; regs[i] != 0x00; i += 2)
1020890ec19SHans Verkuil 		if (write_reg(sd, regs[i], regs[i + 1]) < 0)
1030890ec19SHans Verkuil 			return -1;
1040890ec19SHans Verkuil 	return 0;
1050890ec19SHans Verkuil }
1060890ec19SHans Verkuil 
tw9903_s_video_routing(struct v4l2_subdev * sd,u32 input,u32 output,u32 config)1070890ec19SHans Verkuil static int tw9903_s_video_routing(struct v4l2_subdev *sd, u32 input,
1080890ec19SHans Verkuil 				      u32 output, u32 config)
1090890ec19SHans Verkuil {
1100890ec19SHans Verkuil 	write_reg(sd, 0x02, 0x40 | (input << 1));
1110890ec19SHans Verkuil 	return 0;
1120890ec19SHans Verkuil }
1130890ec19SHans Verkuil 
tw9903_s_std(struct v4l2_subdev * sd,v4l2_std_id norm)1140890ec19SHans Verkuil static int tw9903_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
1150890ec19SHans Verkuil {
1160890ec19SHans Verkuil 	struct tw9903 *dec = to_state(sd);
1170890ec19SHans Verkuil 	bool is_60hz = norm & V4L2_STD_525_60;
118d4a2cea4SHans Verkuil 	static const u8 config_60hz[] = {
119d4a2cea4SHans Verkuil 		0x05, 0x80,
120d4a2cea4SHans Verkuil 		0x07, 0x02,
121d4a2cea4SHans Verkuil 		0x08, 0x14,
122d4a2cea4SHans Verkuil 		0x09, 0xf0,
123d4a2cea4SHans Verkuil 		0,    0,
124d4a2cea4SHans Verkuil 	};
125d4a2cea4SHans Verkuil 	static const u8 config_50hz[] = {
126d4a2cea4SHans Verkuil 		0x05, 0x00,
127d4a2cea4SHans Verkuil 		0x07, 0x12,
128d4a2cea4SHans Verkuil 		0x08, 0x18,
129d4a2cea4SHans Verkuil 		0x09, 0x20,
1300890ec19SHans Verkuil 		0,    0,
1310890ec19SHans Verkuil 	};
1320890ec19SHans Verkuil 
133d4a2cea4SHans Verkuil 	write_regs(sd, is_60hz ? config_60hz : config_50hz);
1340890ec19SHans Verkuil 	dec->norm = norm;
1350890ec19SHans Verkuil 	return 0;
1360890ec19SHans Verkuil }
1370890ec19SHans Verkuil 
1380890ec19SHans Verkuil 
tw9903_s_ctrl(struct v4l2_ctrl * ctrl)1390890ec19SHans Verkuil static int tw9903_s_ctrl(struct v4l2_ctrl *ctrl)
1400890ec19SHans Verkuil {
1410890ec19SHans Verkuil 	struct tw9903 *dec = container_of(ctrl->handler, struct tw9903, hdl);
1420890ec19SHans Verkuil 	struct v4l2_subdev *sd = &dec->sd;
1430890ec19SHans Verkuil 
1440890ec19SHans Verkuil 	switch (ctrl->id) {
1450890ec19SHans Verkuil 	case V4L2_CID_BRIGHTNESS:
1460890ec19SHans Verkuil 		write_reg(sd, 0x10, ctrl->val);
1470890ec19SHans Verkuil 		break;
1480890ec19SHans Verkuil 	case V4L2_CID_CONTRAST:
1490890ec19SHans Verkuil 		write_reg(sd, 0x11, ctrl->val);
1500890ec19SHans Verkuil 		break;
1510890ec19SHans Verkuil 	case V4L2_CID_HUE:
1520890ec19SHans Verkuil 		write_reg(sd, 0x15, ctrl->val);
1530890ec19SHans Verkuil 		break;
1540890ec19SHans Verkuil 	default:
1550890ec19SHans Verkuil 		return -EINVAL;
1560890ec19SHans Verkuil 	}
1570890ec19SHans Verkuil 	return 0;
1580890ec19SHans Verkuil }
1590890ec19SHans Verkuil 
tw9903_log_status(struct v4l2_subdev * sd)1600890ec19SHans Verkuil static int tw9903_log_status(struct v4l2_subdev *sd)
1610890ec19SHans Verkuil {
1620890ec19SHans Verkuil 	struct tw9903 *dec = to_state(sd);
1630890ec19SHans Verkuil 	bool is_60hz = dec->norm & V4L2_STD_525_60;
1640890ec19SHans Verkuil 
1650890ec19SHans Verkuil 	v4l2_info(sd, "Standard: %d Hz\n", is_60hz ? 60 : 50);
1660890ec19SHans Verkuil 	v4l2_ctrl_subdev_log_status(sd);
1670890ec19SHans Verkuil 	return 0;
1680890ec19SHans Verkuil }
1690890ec19SHans Verkuil 
1700890ec19SHans Verkuil /* --------------------------------------------------------------------------*/
1710890ec19SHans Verkuil 
1720890ec19SHans Verkuil static const struct v4l2_ctrl_ops tw9903_ctrl_ops = {
1730890ec19SHans Verkuil 	.s_ctrl = tw9903_s_ctrl,
1740890ec19SHans Verkuil };
1750890ec19SHans Verkuil 
1760890ec19SHans Verkuil static const struct v4l2_subdev_core_ops tw9903_core_ops = {
1770890ec19SHans Verkuil 	.log_status = tw9903_log_status,
1780890ec19SHans Verkuil };
1790890ec19SHans Verkuil 
1800890ec19SHans Verkuil static const struct v4l2_subdev_video_ops tw9903_video_ops = {
1818774bed9SLaurent Pinchart 	.s_std = tw9903_s_std,
1820890ec19SHans Verkuil 	.s_routing = tw9903_s_video_routing,
1830890ec19SHans Verkuil };
1840890ec19SHans Verkuil 
1850890ec19SHans Verkuil static const struct v4l2_subdev_ops tw9903_ops = {
1860890ec19SHans Verkuil 	.core = &tw9903_core_ops,
1870890ec19SHans Verkuil 	.video = &tw9903_video_ops,
1880890ec19SHans Verkuil };
1890890ec19SHans Verkuil 
1900890ec19SHans Verkuil /* --------------------------------------------------------------------------*/
1910890ec19SHans Verkuil 
tw9903_probe(struct i2c_client * client)1924fa8bcc3SUwe Kleine-König static int tw9903_probe(struct i2c_client *client)
1930890ec19SHans Verkuil {
1940890ec19SHans Verkuil 	struct tw9903 *dec;
1950890ec19SHans Verkuil 	struct v4l2_subdev *sd;
1960890ec19SHans Verkuil 	struct v4l2_ctrl_handler *hdl;
1970890ec19SHans Verkuil 
1980890ec19SHans Verkuil 	/* Check if the adapter supports the needed features */
1990890ec19SHans Verkuil 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
2000890ec19SHans Verkuil 		return -EIO;
2010890ec19SHans Verkuil 
2020890ec19SHans Verkuil 	v4l_info(client, "chip found @ 0x%02x (%s)\n",
2030890ec19SHans Verkuil 			client->addr << 1, client->adapter->name);
2040890ec19SHans Verkuil 
205c02b211dSLaurent Pinchart 	dec = devm_kzalloc(&client->dev, sizeof(*dec), GFP_KERNEL);
2060890ec19SHans Verkuil 	if (dec == NULL)
2070890ec19SHans Verkuil 		return -ENOMEM;
2080890ec19SHans Verkuil 	sd = &dec->sd;
2090890ec19SHans Verkuil 	v4l2_i2c_subdev_init(sd, client, &tw9903_ops);
2100890ec19SHans Verkuil 	hdl = &dec->hdl;
2110890ec19SHans Verkuil 	v4l2_ctrl_handler_init(hdl, 4);
2120890ec19SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tw9903_ctrl_ops,
2130890ec19SHans Verkuil 		V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
2140890ec19SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tw9903_ctrl_ops,
2150890ec19SHans Verkuil 		V4L2_CID_CONTRAST, 0, 255, 1, 0x60);
2160890ec19SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tw9903_ctrl_ops,
2170890ec19SHans Verkuil 		V4L2_CID_HUE, -128, 127, 1, 0);
2180890ec19SHans Verkuil 	sd->ctrl_handler = hdl;
2190890ec19SHans Verkuil 	if (hdl->error) {
2200890ec19SHans Verkuil 		int err = hdl->error;
2210890ec19SHans Verkuil 
2220890ec19SHans Verkuil 		v4l2_ctrl_handler_free(hdl);
2230890ec19SHans Verkuil 		return err;
2240890ec19SHans Verkuil 	}
2250890ec19SHans Verkuil 
2260890ec19SHans Verkuil 	/* Initialize tw9903 */
2270890ec19SHans Verkuil 	dec->norm = V4L2_STD_NTSC;
2280890ec19SHans Verkuil 
2290890ec19SHans Verkuil 	if (write_regs(sd, initial_registers) < 0) {
2300890ec19SHans Verkuil 		v4l2_err(client, "error initializing TW9903\n");
2310890ec19SHans Verkuil 		return -EINVAL;
2320890ec19SHans Verkuil 	}
2330890ec19SHans Verkuil 
2340890ec19SHans Verkuil 	return 0;
2350890ec19SHans Verkuil }
2360890ec19SHans Verkuil 
tw9903_remove(struct i2c_client * client)237ed5c2f5fSUwe Kleine-König static void tw9903_remove(struct i2c_client *client)
2380890ec19SHans Verkuil {
2390890ec19SHans Verkuil 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
2400890ec19SHans Verkuil 
2410890ec19SHans Verkuil 	v4l2_device_unregister_subdev(sd);
2420890ec19SHans Verkuil 	v4l2_ctrl_handler_free(&to_state(sd)->hdl);
2430890ec19SHans Verkuil }
2440890ec19SHans Verkuil 
2450890ec19SHans Verkuil /* ----------------------------------------------------------------------- */
2460890ec19SHans Verkuil 
2470890ec19SHans Verkuil static const struct i2c_device_id tw9903_id[] = {
2480890ec19SHans Verkuil 	{ "tw9903", 0 },
2490890ec19SHans Verkuil 	{ }
2500890ec19SHans Verkuil };
2510890ec19SHans Verkuil MODULE_DEVICE_TABLE(i2c, tw9903_id);
2520890ec19SHans Verkuil 
2530890ec19SHans Verkuil static struct i2c_driver tw9903_driver = {
2540890ec19SHans Verkuil 	.driver = {
2550890ec19SHans Verkuil 		.name	= "tw9903",
2560890ec19SHans Verkuil 	},
257*aaeb31c0SUwe Kleine-König 	.probe = tw9903_probe,
2580890ec19SHans Verkuil 	.remove = tw9903_remove,
2590890ec19SHans Verkuil 	.id_table = tw9903_id,
2600890ec19SHans Verkuil };
2610890ec19SHans Verkuil module_i2c_driver(tw9903_driver);
262