root/drivers/media/i2c/tw9900.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for the Techwell TW9900 multi-standard video decoder.
 *
 * Copyright (C) 2018 Fuzhou Rockchip Electronics Co., Ltd.
 * Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com>
 * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
 */

#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <media/media-entity.h>
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/v4l2-subdev.h>

#define TW9900_REG_CHIP_ID                      0x00
#define TW9900_REG_CHIP_STATUS                  0x01
#define TW9900_REG_CHIP_STATUS_VDLOSS           BIT(7)
#define TW9900_REG_CHIP_STATUS_HLOCK            BIT(6)
#define TW9900_REG_OUT_FMT_CTL                  0x03
#define TW9900_REG_OUT_FMT_CTL_STANDBY          0xA7
#define TW9900_REG_OUT_FMT_CTL_STREAMING        0xA0
#define TW9900_REG_CKHY_HSDLY                   0x04
#define TW9900_REG_OUT_CTRL_I                   0x05
#define TW9900_REG_ANALOG_CTL                   0x06
#define TW9900_REG_CROP_HI                      0x07
#define TW9900_REG_VDELAY_LO                    0x08
#define TW9900_REG_VACTIVE_LO                   0x09
#define TW9900_REG_HACTIVE_LO                   0x0B
#define TW9900_REG_CNTRL1                       0x0C
#define TW9900_REG_BRIGHT_CTL                   0x10
#define TW9900_REG_CONTRAST_CTL                 0x11
#define TW9900_REG_VBI_CNTL                     0x19
#define TW9900_REG_ANAL_CTL_II                  0x1A
#define TW9900_REG_OUT_CTRL_II                  0x1B
#define TW9900_REG_STD                          0x1C
#define TW9900_REG_STD_AUTO_PROGRESS            BIT(7)
#define TW9900_STDNOW_MASK                      GENMASK(6, 4)
#define TW9900_REG_STDR                         0x1D
#define TW9900_REG_MISSCNT                      0x26
#define TW9900_REG_MISC_CTL_II                  0x2F
#define TW9900_REG_VVBI                         0x55

#define TW9900_CHIP_ID                          0x00
#define TW9900_STD_NTSC_M                       0
#define TW9900_STD_PAL_BDGHI                    1
#define TW9900_STD_AUTO                         7

#define TW9900_VIDEO_POLL_TRIES                 20

struct regval {
        u8 addr;
        u8 val;
};

struct tw9900_mode {
        u32 width;
        u32 height;
        u32 std;
        const struct regval *reg_list;
        int n_regs;
};

struct tw9900 {
        struct i2c_client *client;
        struct gpio_desc *reset_gpio;
        struct regulator *regulator;

        struct v4l2_subdev subdev;
        struct v4l2_ctrl_handler hdl;
        struct media_pad pad;

        /* Serialize access to hardware and global state. */
        struct mutex mutex;

        bool streaming;
        const struct tw9900_mode *cur_mode;
};

#define to_tw9900(sd) container_of(sd, struct tw9900, subdev)

static const struct regval tw9900_init_regs[] = {
        { TW9900_REG_MISC_CTL_II,       0xE6 },
        { TW9900_REG_MISSCNT,           0x24 },
        { TW9900_REG_OUT_FMT_CTL,       0xA7 },
        { TW9900_REG_ANAL_CTL_II,       0x0A },
        { TW9900_REG_VDELAY_LO,         0x19 },
        { TW9900_REG_STD,               0x00 },
        { TW9900_REG_VACTIVE_LO,        0xF0 },
        { TW9900_REG_STD,               0x07 },
        { TW9900_REG_CKHY_HSDLY,        0x00 },
        { TW9900_REG_ANALOG_CTL,        0x80 },
        { TW9900_REG_CNTRL1,            0xDC },
        { TW9900_REG_OUT_CTRL_I,        0x98 },
};

static const struct regval tw9900_pal_regs[] = {
        { TW9900_REG_STD,               0x01 },
};

static const struct regval tw9900_ntsc_regs[] = {
        { TW9900_REG_OUT_FMT_CTL,       0xA4 },
        { TW9900_REG_VDELAY_LO,         0x12 },
        { TW9900_REG_VACTIVE_LO,        0xF0 },
        { TW9900_REG_CROP_HI,           0x02 },
        { TW9900_REG_HACTIVE_LO,        0xD0 },
        { TW9900_REG_VBI_CNTL,          0x01 },
        { TW9900_REG_STD,               0x00 },
};

static const struct tw9900_mode supported_modes[] = {
        {
                .width = 720,
                .height = 480,
                .std = V4L2_STD_NTSC,
                .reg_list = tw9900_ntsc_regs,
                .n_regs = ARRAY_SIZE(tw9900_ntsc_regs),
        },
        {
                .width = 720,
                .height = 576,
                .std = V4L2_STD_PAL,
                .reg_list = tw9900_pal_regs,
                .n_regs = ARRAY_SIZE(tw9900_pal_regs),
        },
};

static int tw9900_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
        int ret;

        ret = i2c_smbus_write_byte_data(client, reg, val);
        if (ret < 0)
                dev_err(&client->dev, "write reg error: %d\n", ret);

        return ret;
}

static int tw9900_write_array(struct i2c_client *client,
                              const struct regval *regs, int n_regs)
{
        int i, ret = 0;

        for (i = 0; i < n_regs; i++) {
                ret = tw9900_write_reg(client, regs[i].addr, regs[i].val);
                if (ret)
                        return ret;
        }

        return 0;
}

static int tw9900_read_reg(struct i2c_client *client, u8 reg)
{
        int ret;

        ret = i2c_smbus_read_byte_data(client, reg);
        if (ret < 0)
                dev_err(&client->dev, "read reg error: %d\n", ret);

        return ret;
}

static void tw9900_fill_fmt(const struct tw9900_mode *mode,
                            struct v4l2_mbus_framefmt *fmt)
{
        fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
        fmt->width = mode->width;
        fmt->height = mode->height;
        fmt->field = V4L2_FIELD_NONE;
        fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
        fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
        fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M);
        fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M);
}

static int tw9900_get_fmt(struct v4l2_subdev *sd,
                          struct v4l2_subdev_state *sd_state,
                          struct v4l2_subdev_format *fmt)
{
        struct tw9900 *tw9900 = to_tw9900(sd);
        struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;

        mutex_lock(&tw9900->mutex);
        tw9900_fill_fmt(tw9900->cur_mode, mbus_fmt);
        mutex_unlock(&tw9900->mutex);

        return 0;
}

static int tw9900_set_fmt(struct v4l2_subdev *sd,
                          struct v4l2_subdev_state *sd_state,
                          struct v4l2_subdev_format *fmt)
{
        struct tw9900 *tw9900 = to_tw9900(sd);
        struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;

        mutex_lock(&tw9900->mutex);

        if (tw9900->streaming) {
                mutex_unlock(&tw9900->mutex);
                return -EBUSY;
        }

        tw9900_fill_fmt(tw9900->cur_mode, mbus_fmt);

        mutex_unlock(&tw9900->mutex);

        return 0;
}

static int tw9900_enum_mbus_code(struct v4l2_subdev *sd,
                                 struct v4l2_subdev_state *sd_state,
                                 struct v4l2_subdev_mbus_code_enum *code)
{
        if (code->index > 0)
                return -EINVAL;

        code->code = MEDIA_BUS_FMT_UYVY8_2X8;

        return 0;
}

static int tw9900_s_ctrl(struct v4l2_ctrl *ctrl)
{
        struct tw9900 *tw9900 = container_of(ctrl->handler, struct tw9900, hdl);
        int ret;

        if (pm_runtime_suspended(&tw9900->client->dev))
                return 0;

        /* v4l2_ctrl_lock() locks tw9900->mutex. */
        switch (ctrl->id) {
        case V4L2_CID_BRIGHTNESS:
                ret = tw9900_write_reg(tw9900->client, TW9900_REG_BRIGHT_CTL,
                                       (u8)ctrl->val);
                break;
        case V4L2_CID_CONTRAST:
                ret = tw9900_write_reg(tw9900->client, TW9900_REG_CONTRAST_CTL,
                                       (u8)ctrl->val);
                break;
        default:
                ret = -EINVAL;
                break;
        }

        return ret;
}

static int tw9900_s_stream(struct v4l2_subdev *sd, int on)
{
        struct tw9900 *tw9900 = to_tw9900(sd);
        struct i2c_client *client = tw9900->client;
        int ret;

        mutex_lock(&tw9900->mutex);

        if (tw9900->streaming == on) {
                mutex_unlock(&tw9900->mutex);
                return 0;
        }

        mutex_unlock(&tw9900->mutex);

        if (on) {
                ret = pm_runtime_resume_and_get(&client->dev);
                if (ret < 0)
                        return ret;

                mutex_lock(&tw9900->mutex);

                ret = __v4l2_ctrl_handler_setup(sd->ctrl_handler);
                if (ret)
                        goto err_unlock;

                ret = tw9900_write_array(tw9900->client,
                                         tw9900->cur_mode->reg_list,
                                         tw9900->cur_mode->n_regs);
                if (ret)
                        goto err_unlock;

                ret = tw9900_write_reg(client, TW9900_REG_OUT_FMT_CTL,
                                       TW9900_REG_OUT_FMT_CTL_STREAMING);
                if (ret)
                        goto err_unlock;

                tw9900->streaming = on;

                mutex_unlock(&tw9900->mutex);

        } else {
                mutex_lock(&tw9900->mutex);

                ret = tw9900_write_reg(client, TW9900_REG_OUT_FMT_CTL,
                                       TW9900_REG_OUT_FMT_CTL_STANDBY);
                if (ret)
                        goto err_unlock;

                tw9900->streaming = on;

                mutex_unlock(&tw9900->mutex);

                pm_runtime_put(&client->dev);
        }

        return 0;

err_unlock:
        mutex_unlock(&tw9900->mutex);
        pm_runtime_put(&client->dev);

        return ret;
}

static int tw9900_subscribe_event(struct v4l2_subdev *sd,
                                  struct v4l2_fh *fh,
                                  struct v4l2_event_subscription *sub)
{
        switch (sub->type) {
        case V4L2_EVENT_SOURCE_CHANGE:
                return v4l2_src_change_event_subdev_subscribe(sd, fh, sub);
        case V4L2_EVENT_CTRL:
                return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
        default:
                return -EINVAL;
        }
}

static int tw9900_s_std(struct v4l2_subdev *sd, v4l2_std_id std)
{
        struct tw9900 *tw9900 = to_tw9900(sd);
        const struct tw9900_mode *mode = NULL;
        int i;

        if (!(std & (V4L2_STD_NTSC | V4L2_STD_PAL)))
                return -EINVAL;

        for (i = 0; i < ARRAY_SIZE(supported_modes); i++)
                if (supported_modes[i].std & std)
                        mode = &supported_modes[i];
        if (!mode)
                return -EINVAL;

        mutex_lock(&tw9900->mutex);
        tw9900->cur_mode = mode;
        mutex_unlock(&tw9900->mutex);

        return 0;
}

static int tw9900_get_stream_std(struct tw9900 *tw9900,
                                 v4l2_std_id *std)
{
        int cur_std, ret;

        lockdep_assert_held(&tw9900->mutex);

        ret = tw9900_read_reg(tw9900->client, TW9900_REG_STD);
        if (ret < 0) {
                *std = V4L2_STD_UNKNOWN;
                return ret;
        }

        cur_std = FIELD_GET(TW9900_STDNOW_MASK, ret);
        switch (cur_std) {
        case TW9900_STD_NTSC_M:
                *std = V4L2_STD_NTSC;
                break;
        case TW9900_STD_PAL_BDGHI:
                *std = V4L2_STD_PAL;
                break;
        case TW9900_STD_AUTO:
                *std = V4L2_STD_UNKNOWN;
                break;
        default:
                *std = V4L2_STD_UNKNOWN;
                break;
        }

        return 0;
}

static int tw9900_g_std(struct v4l2_subdev *sd, v4l2_std_id *std)
{
        struct tw9900 *tw9900 = to_tw9900(sd);

        mutex_lock(&tw9900->mutex);
        *std = tw9900->cur_mode->std;
        mutex_unlock(&tw9900->mutex);

        return 0;
}

static int tw9900_start_autodetect(struct tw9900 *tw9900)
{
        int ret;

        lockdep_assert_held(&tw9900->mutex);

        ret = tw9900_write_reg(tw9900->client, TW9900_REG_STDR,
                               BIT(TW9900_STD_NTSC_M) |
                               BIT(TW9900_STD_PAL_BDGHI));
        if (ret)
                return ret;

        ret = tw9900_write_reg(tw9900->client, TW9900_REG_STD,
                               TW9900_STD_AUTO);
        if (ret)
                return ret;

        ret = tw9900_write_reg(tw9900->client, TW9900_REG_STDR,
                               BIT(TW9900_STD_NTSC_M) |
                               BIT(TW9900_STD_PAL_BDGHI) |
                               BIT(TW9900_STD_AUTO));
        if (ret)
                return ret;

        /*
         * Autodetect takes a while to start, and during the starting sequence
         * the autodetection status is reported as done.
         */
        msleep(30);

        return 0;
}

static int tw9900_detect_done(struct tw9900 *tw9900, bool *done)
{
        int ret;

        lockdep_assert_held(&tw9900->mutex);

        ret = tw9900_read_reg(tw9900->client, TW9900_REG_STD);
        if (ret < 0)
                return ret;

        *done = !(ret & TW9900_REG_STD_AUTO_PROGRESS);

        return 0;
}

static int tw9900_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
{
        struct tw9900 *tw9900 = to_tw9900(sd);
        bool done = false;
        int i, ret;

        mutex_lock(&tw9900->mutex);

        if (tw9900->streaming) {
                mutex_unlock(&tw9900->mutex);
                return -EBUSY;
        }

        mutex_unlock(&tw9900->mutex);

        ret = pm_runtime_resume_and_get(&tw9900->client->dev);
        if (ret < 0)
                return ret;

        mutex_lock(&tw9900->mutex);

        ret = tw9900_start_autodetect(tw9900);
        if (ret)
                goto out_unlock;

        for (i = 0; i < TW9900_VIDEO_POLL_TRIES; i++) {
                ret = tw9900_detect_done(tw9900, &done);
                if (ret)
                        goto out_unlock;

                if (done)
                        break;

                msleep(20);
        }

        if (!done) {
                ret = -ETIMEDOUT;
                goto out_unlock;
        }

        ret = tw9900_get_stream_std(tw9900, std);

out_unlock:
        mutex_unlock(&tw9900->mutex);
        pm_runtime_put(&tw9900->client->dev);

        return ret;
}

static int tw9900_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *std)
{
        *std = V4L2_STD_NTSC | V4L2_STD_PAL;

        return 0;
}

static int tw9900_g_input_status(struct v4l2_subdev *sd, u32 *status)
{
        struct tw9900 *tw9900 = to_tw9900(sd);
        int ret;

        mutex_lock(&tw9900->mutex);

        if (tw9900->streaming) {
                mutex_unlock(&tw9900->mutex);
                return -EBUSY;
        }

        mutex_unlock(&tw9900->mutex);

        *status = V4L2_IN_ST_NO_SIGNAL;

        ret = pm_runtime_resume_and_get(&tw9900->client->dev);
        if (ret < 0)
                return ret;

        mutex_lock(&tw9900->mutex);
        ret = tw9900_read_reg(tw9900->client, TW9900_REG_CHIP_STATUS);
        mutex_unlock(&tw9900->mutex);

        pm_runtime_put(&tw9900->client->dev);

        if (ret < 0)
                return ret;

        *status = ret & TW9900_REG_CHIP_STATUS_HLOCK ? 0 : V4L2_IN_ST_NO_SIGNAL;

        return 0;
}

static const struct v4l2_subdev_core_ops tw9900_core_ops = {
        .subscribe_event        = tw9900_subscribe_event,
        .unsubscribe_event      = v4l2_event_subdev_unsubscribe,
};

static const struct v4l2_subdev_video_ops tw9900_video_ops = {
        .s_std          = tw9900_s_std,
        .g_std          = tw9900_g_std,
        .querystd       = tw9900_querystd,
        .g_tvnorms      = tw9900_g_tvnorms,
        .g_input_status = tw9900_g_input_status,
        .s_stream       = tw9900_s_stream,
};

static const struct v4l2_subdev_pad_ops tw9900_pad_ops = {
        .enum_mbus_code = tw9900_enum_mbus_code,
        .get_fmt        = tw9900_get_fmt,
        .set_fmt        = tw9900_set_fmt,
};

static const struct v4l2_subdev_ops tw9900_subdev_ops = {
        .core   = &tw9900_core_ops,
        .video  = &tw9900_video_ops,
        .pad    = &tw9900_pad_ops,
};

static const struct v4l2_ctrl_ops tw9900_ctrl_ops = {
        .s_ctrl = tw9900_s_ctrl,
};

static int tw9900_check_id(struct tw9900 *tw9900,
                           struct i2c_client *client)
{
        struct device *dev = &tw9900->client->dev;
        int ret;

        ret = pm_runtime_resume_and_get(&tw9900->client->dev);
        if (ret < 0)
                return ret;

        mutex_lock(&tw9900->mutex);
        ret = tw9900_read_reg(client, TW9900_CHIP_ID);
        mutex_unlock(&tw9900->mutex);

        pm_runtime_put(&tw9900->client->dev);

        if (ret < 0)
                return ret;

        if (ret != TW9900_CHIP_ID) {
                dev_err(dev, "Unexpected decoder id %#x\n", ret);
                return -ENODEV;
        }

        return 0;
}

static int tw9900_runtime_resume(struct device *dev)
{
        struct i2c_client *client = to_i2c_client(dev);
        struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct tw9900 *tw9900 = to_tw9900(sd);
        int ret;

        mutex_lock(&tw9900->mutex);

        if (tw9900->reset_gpio)
                gpiod_set_value_cansleep(tw9900->reset_gpio, 1);

        ret = regulator_enable(tw9900->regulator);
        if (ret < 0) {
                mutex_unlock(&tw9900->mutex);
                return ret;
        }

        usleep_range(50000, 52000);

        if (tw9900->reset_gpio)
                gpiod_set_value_cansleep(tw9900->reset_gpio, 0);

        usleep_range(1000, 2000);

        ret = tw9900_write_array(tw9900->client, tw9900_init_regs,
                                 ARRAY_SIZE(tw9900_init_regs));

        mutex_unlock(&tw9900->mutex);

        /* This sleep is needed for the Horizontal Sync PLL to lock. */
        msleep(300);

        return ret;
}

static int tw9900_runtime_suspend(struct device *dev)
{
        struct i2c_client *client = to_i2c_client(dev);
        struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct tw9900 *tw9900 = to_tw9900(sd);

        mutex_lock(&tw9900->mutex);

        if (tw9900->reset_gpio)
                gpiod_set_value_cansleep(tw9900->reset_gpio, 1);

        regulator_disable(tw9900->regulator);

        mutex_unlock(&tw9900->mutex);

        return 0;
}

static int tw9900_probe(struct i2c_client *client)
{
        struct device *dev = &client->dev;
        struct v4l2_ctrl_handler *hdl;
        struct tw9900 *tw9900;
        int ret = 0;

        tw9900 = devm_kzalloc(dev, sizeof(*tw9900), GFP_KERNEL);
        if (!tw9900)
                return -ENOMEM;

        tw9900->client = client;
        tw9900->cur_mode = &supported_modes[0];

        tw9900->reset_gpio = devm_gpiod_get_optional(dev, "reset",
                                                     GPIOD_OUT_LOW);
        if (IS_ERR(tw9900->reset_gpio))
                return dev_err_probe(dev, PTR_ERR(tw9900->reset_gpio),
                                     "Failed to get reset gpio\n");

        tw9900->regulator = devm_regulator_get(&tw9900->client->dev, "vdd");
        if (IS_ERR(tw9900->regulator))
                return dev_err_probe(dev, PTR_ERR(tw9900->regulator),
                                     "Failed to get power regulator\n");

        v4l2_i2c_subdev_init(&tw9900->subdev, client, &tw9900_subdev_ops);
        tw9900->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
                                V4L2_SUBDEV_FL_HAS_EVENTS;

        mutex_init(&tw9900->mutex);

        hdl = &tw9900->hdl;

        ret = v4l2_ctrl_handler_init(hdl, 2);
        if (ret)
                goto err_destory_mutex;

        hdl->lock = &tw9900->mutex;

        v4l2_ctrl_new_std(hdl, &tw9900_ctrl_ops, V4L2_CID_BRIGHTNESS,
                          -128, 127, 1, 0);
        v4l2_ctrl_new_std(hdl, &tw9900_ctrl_ops, V4L2_CID_CONTRAST,
                          0, 255, 1, 0x60);

        tw9900->subdev.ctrl_handler = hdl;
        if (hdl->error) {
                ret = hdl->error;
                goto err_free_handler;
        }

        tw9900->pad.flags = MEDIA_PAD_FL_SOURCE;
        tw9900->subdev.entity.function = MEDIA_ENT_F_DV_DECODER;

        ret = media_entity_pads_init(&tw9900->subdev.entity, 1, &tw9900->pad);
        if (ret < 0)
                goto err_free_handler;

        pm_runtime_set_suspended(dev);
        pm_runtime_enable(dev);

        ret = tw9900_check_id(tw9900, client);
        if (ret)
                goto err_disable_pm;

        ret = v4l2_async_register_subdev(&tw9900->subdev);
        if (ret) {
                dev_err(dev, "v4l2 async register subdev failed\n");
                goto err_disable_pm;
        }

        return 0;

err_disable_pm:
        pm_runtime_disable(dev);
        media_entity_cleanup(&tw9900->subdev.entity);
err_free_handler:
        v4l2_ctrl_handler_free(hdl);
err_destory_mutex:
        mutex_destroy(&tw9900->mutex);

        return ret;
}

static void tw9900_remove(struct i2c_client *client)
{
        struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct tw9900 *tw9900 = to_tw9900(sd);

        v4l2_async_unregister_subdev(sd);
        media_entity_cleanup(&sd->entity);
        v4l2_ctrl_handler_free(sd->ctrl_handler);

        pm_runtime_disable(&client->dev);

        mutex_destroy(&tw9900->mutex);
}

static const struct dev_pm_ops tw9900_pm_ops = {
        .runtime_suspend = tw9900_runtime_suspend,
        .runtime_resume = tw9900_runtime_resume,
};

static const struct i2c_device_id tw9900_id[] = {
        { "tw9900" },
        { }
};
MODULE_DEVICE_TABLE(i2c, tw9900_id);

static const struct of_device_id tw9900_of_match[] = {
        { .compatible = "techwell,tw9900" },
        {},
};
MODULE_DEVICE_TABLE(of, tw9900_of_match);

static struct i2c_driver tw9900_i2c_driver = {
        .driver = {
                .name           = "tw9900",
                .pm             = &tw9900_pm_ops,
                .of_match_table = tw9900_of_match,
        },
        .probe    = tw9900_probe,
        .remove   = tw9900_remove,
        .id_table = tw9900_id,
};

module_i2c_driver(tw9900_i2c_driver);

MODULE_DESCRIPTION("tw9900 decoder driver");
MODULE_LICENSE("GPL");