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