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