linux/drivers/media/platform/s5p-tv/sii9234_drv.c
<<
>>
Prefs
   1/*
   2 * Samsung MHL interface driver
   3 *
   4 * Copyright (C) 2011 Samsung Electronics Co.Ltd
   5 * Author: Tomasz Stanislawski <t.stanislaws@samsung.com>
   6 *
   7 * This program is free software; you can redistribute  it and/or modify it
   8 * under  the terms of  the GNU General  Public License as published by the
   9 * Free Software Foundation;  either version 2 of the  License, or (at your
  10 * option) any later version.
  11 */
  12
  13#include <linux/delay.h>
  14#include <linux/err.h>
  15#include <linux/freezer.h>
  16#include <linux/gpio.h>
  17#include <linux/i2c.h>
  18#include <linux/interrupt.h>
  19#include <linux/irq.h>
  20#include <linux/kthread.h>
  21#include <linux/module.h>
  22#include <linux/pm_runtime.h>
  23#include <linux/regulator/machine.h>
  24#include <linux/slab.h>
  25
  26#include <linux/platform_data/media/sii9234.h>
  27#include <media/v4l2-subdev.h>
  28
  29MODULE_AUTHOR("Tomasz Stanislawski <t.stanislaws@samsung.com>");
  30MODULE_DESCRIPTION("Samsung MHL interface driver");
  31MODULE_LICENSE("GPL");
  32
  33struct sii9234_context {
  34        struct i2c_client *client;
  35        struct regulator *power;
  36        int gpio_n_reset;
  37        struct v4l2_subdev sd;
  38};
  39
  40static inline struct sii9234_context *sd_to_context(struct v4l2_subdev *sd)
  41{
  42        return container_of(sd, struct sii9234_context, sd);
  43}
  44
  45static inline int sii9234_readb(struct i2c_client *client, int addr)
  46{
  47        return i2c_smbus_read_byte_data(client, addr);
  48}
  49
  50static inline int sii9234_writeb(struct i2c_client *client, int addr, int value)
  51{
  52        return i2c_smbus_write_byte_data(client, addr, value);
  53}
  54
  55static inline int sii9234_writeb_mask(struct i2c_client *client, int addr,
  56        int value, int mask)
  57{
  58        int ret;
  59
  60        ret = i2c_smbus_read_byte_data(client, addr);
  61        if (ret < 0)
  62                return ret;
  63        ret = (ret & ~mask) | (value & mask);
  64        return i2c_smbus_write_byte_data(client, addr, ret);
  65}
  66
  67static inline int sii9234_readb_idx(struct i2c_client *client, int addr)
  68{
  69        int ret;
  70        ret = i2c_smbus_write_byte_data(client, 0xbc, addr >> 8);
  71        if (ret < 0)
  72                return ret;
  73        ret = i2c_smbus_write_byte_data(client, 0xbd, addr & 0xff);
  74        if (ret < 0)
  75                return ret;
  76        return i2c_smbus_read_byte_data(client, 0xbe);
  77}
  78
  79static inline int sii9234_writeb_idx(struct i2c_client *client, int addr,
  80        int value)
  81{
  82        int ret;
  83        ret = i2c_smbus_write_byte_data(client, 0xbc, addr >> 8);
  84        if (ret < 0)
  85                return ret;
  86        ret = i2c_smbus_write_byte_data(client, 0xbd, addr & 0xff);
  87        if (ret < 0)
  88                return ret;
  89        ret = i2c_smbus_write_byte_data(client, 0xbe, value);
  90        return ret;
  91}
  92
  93static inline int sii9234_writeb_idx_mask(struct i2c_client *client, int addr,
  94        int value, int mask)
  95{
  96        int ret;
  97
  98        ret = sii9234_readb_idx(client, addr);
  99        if (ret < 0)
 100                return ret;
 101        ret = (ret & ~mask) | (value & mask);
 102        return sii9234_writeb_idx(client, addr, ret);
 103}
 104
 105static int sii9234_reset(struct sii9234_context *ctx)
 106{
 107        struct i2c_client *client = ctx->client;
 108        struct device *dev = &client->dev;
 109        int ret, tries;
 110
 111        gpio_direction_output(ctx->gpio_n_reset, 1);
 112        mdelay(1);
 113        gpio_direction_output(ctx->gpio_n_reset, 0);
 114        mdelay(1);
 115        gpio_direction_output(ctx->gpio_n_reset, 1);
 116        mdelay(1);
 117
 118        /* going to TTPI mode */
 119        ret = sii9234_writeb(client, 0xc7, 0);
 120        if (ret < 0) {
 121                dev_err(dev, "failed to set TTPI mode\n");
 122                return ret;
 123        }
 124        for (tries = 0; tries < 100 ; ++tries) {
 125                ret = sii9234_readb(client, 0x1b);
 126                if (ret > 0)
 127                        break;
 128                if (ret < 0) {
 129                        dev_err(dev, "failed to reset device\n");
 130                        return -EIO;
 131                }
 132                mdelay(1);
 133        }
 134        if (tries == 100) {
 135                dev_err(dev, "maximal number of tries reached\n");
 136                return -EIO;
 137        }
 138
 139        return 0;
 140}
 141
 142static int sii9234_verify_version(struct i2c_client *client)
 143{
 144        struct device *dev = &client->dev;
 145        int family, rev, tpi_rev, dev_id, sub_id, hdcp, id;
 146
 147        family = sii9234_readb(client, 0x1b);
 148        rev = sii9234_readb(client, 0x1c) & 0x0f;
 149        tpi_rev = sii9234_readb(client, 0x1d) & 0x7f;
 150        dev_id = sii9234_readb_idx(client, 0x0103);
 151        sub_id = sii9234_readb_idx(client, 0x0102);
 152        hdcp = sii9234_readb(client, 0x30);
 153
 154        if (family < 0 || rev < 0 || tpi_rev < 0 || dev_id < 0 ||
 155                sub_id < 0 || hdcp < 0) {
 156                dev_err(dev, "failed to read chip's version\n");
 157                return -EIO;
 158        }
 159
 160        id = (dev_id << 8) | sub_id;
 161
 162        dev_info(dev, "chip: SiL%02x family: %02x, rev: %02x\n",
 163                id, family, rev);
 164        dev_info(dev, "tpi_rev:%02x, hdcp: %02x\n", tpi_rev, hdcp);
 165        if (id != 0x9234) {
 166                dev_err(dev, "not supported chip\n");
 167                return -ENODEV;
 168        }
 169
 170        return 0;
 171}
 172
 173static u8 data[][3] = {
 174/* setup from driver created by doonsoo45.kim */
 175        { 0x01, 0x05, 0x04 }, /* Enable Auto soft reset on SCDT = 0 */
 176        { 0x01, 0x08, 0x35 }, /* Power Up TMDS Tx Core */
 177        { 0x01, 0x0d, 0x1c }, /* HDMI Transcode mode enable */
 178        { 0x01, 0x2b, 0x01 }, /* Enable HDCP Compliance workaround */
 179        { 0x01, 0x79, 0x40 }, /* daniel test...MHL_INT */
 180        { 0x01, 0x80, 0x34 }, /* Enable Rx PLL Clock Value */
 181        { 0x01, 0x90, 0x27 }, /* Enable CBUS discovery */
 182        { 0x01, 0x91, 0xe5 }, /* Skip RGND detection */
 183        { 0x01, 0x92, 0x46 }, /* Force MHD mode */
 184        { 0x01, 0x93, 0xdc }, /* Disable CBUS pull-up during RGND measurement */
 185        { 0x01, 0x94, 0x66 }, /* 1.8V CBUS VTH & GND threshold */
 186        { 0x01, 0x95, 0x31 }, /* RGND block & single discovery attempt */
 187        { 0x01, 0x96, 0x22 }, /* use 1K and 2K setting */
 188        { 0x01, 0xa0, 0x10 }, /* SIMG: Term mode */
 189        { 0x01, 0xa1, 0xfc }, /* Disable internal Mobile HD driver */
 190        { 0x01, 0xa3, 0xfa }, /* SIMG: Output Swing  default EB, 3x Clk Mult */
 191        { 0x01, 0xa5, 0x80 }, /* SIMG: RGND Hysterisis, 3x mode for Beast */
 192        { 0x01, 0xa6, 0x0c }, /* SIMG: Swing Offset */
 193        { 0x02, 0x3d, 0x3f }, /* Power up CVCC 1.2V core */
 194        { 0x03, 0x00, 0x00 }, /* SIMG: correcting HW default */
 195        { 0x03, 0x11, 0x01 }, /* Enable TxPLL Clock */
 196        { 0x03, 0x12, 0x15 }, /* Enable Tx Clock Path & Equalizer */
 197        { 0x03, 0x13, 0x60 }, /* SIMG: Set termination value */
 198        { 0x03, 0x14, 0xf0 }, /* SIMG: Change CKDT level */
 199        { 0x03, 0x17, 0x07 }, /* SIMG: PLL Calrefsel */
 200        { 0x03, 0x1a, 0x20 }, /* VCO Cal */
 201        { 0x03, 0x22, 0xe0 }, /* SIMG: Auto EQ */
 202        { 0x03, 0x23, 0xc0 }, /* SIMG: Auto EQ */
 203        { 0x03, 0x24, 0xa0 }, /* SIMG: Auto EQ */
 204        { 0x03, 0x25, 0x80 }, /* SIMG: Auto EQ */
 205        { 0x03, 0x26, 0x60 }, /* SIMG: Auto EQ */
 206        { 0x03, 0x27, 0x40 }, /* SIMG: Auto EQ */
 207        { 0x03, 0x28, 0x20 }, /* SIMG: Auto EQ */
 208        { 0x03, 0x29, 0x00 }, /* SIMG: Auto EQ */
 209        { 0x03, 0x31, 0x0b }, /* SIMG: Rx PLL BW value from I2C BW ~ 4MHz */
 210        { 0x03, 0x45, 0x06 }, /* SIMG: DPLL Mode */
 211        { 0x03, 0x4b, 0x06 }, /* SIMG: Correcting HW default */
 212        { 0x03, 0x4c, 0xa0 }, /* Manual zone control */
 213        { 0x03, 0x4d, 0x02 }, /* SIMG: PLL Mode Value (order is important) */
 214};
 215
 216static int sii9234_set_internal(struct sii9234_context *ctx)
 217{
 218        struct i2c_client *client = ctx->client;
 219        int i, ret;
 220
 221        for (i = 0; i < ARRAY_SIZE(data); ++i) {
 222                int addr = (data[i][0] << 8) | data[i][1];
 223                ret = sii9234_writeb_idx(client, addr, data[i][2]);
 224                if (ret < 0)
 225                        return ret;
 226        }
 227        return 0;
 228}
 229
 230static int sii9234_runtime_suspend(struct device *dev)
 231{
 232        struct v4l2_subdev *sd = dev_get_drvdata(dev);
 233        struct sii9234_context *ctx = sd_to_context(sd);
 234        struct i2c_client *client = ctx->client;
 235
 236        dev_info(dev, "suspend start\n");
 237
 238        sii9234_writeb_mask(client, 0x1e, 3, 3);
 239        regulator_disable(ctx->power);
 240
 241        return 0;
 242}
 243
 244static int sii9234_runtime_resume(struct device *dev)
 245{
 246        struct v4l2_subdev *sd = dev_get_drvdata(dev);
 247        struct sii9234_context *ctx = sd_to_context(sd);
 248        struct i2c_client *client = ctx->client;
 249        int ret;
 250
 251        dev_info(dev, "resume start\n");
 252        ret = regulator_enable(ctx->power);
 253        if (ret < 0)
 254                return ret;
 255
 256        ret = sii9234_reset(ctx);
 257        if (ret)
 258                goto fail;
 259
 260        /* enable tpi */
 261        ret = sii9234_writeb_mask(client, 0x1e, 1, 0);
 262        if (ret < 0)
 263                goto fail;
 264        ret = sii9234_set_internal(ctx);
 265        if (ret < 0)
 266                goto fail;
 267
 268        return 0;
 269
 270fail:
 271        dev_err(dev, "failed to resume\n");
 272        regulator_disable(ctx->power);
 273
 274        return ret;
 275}
 276
 277static const struct dev_pm_ops sii9234_pm_ops = {
 278        .runtime_suspend = sii9234_runtime_suspend,
 279        .runtime_resume  = sii9234_runtime_resume,
 280};
 281
 282static int sii9234_s_power(struct v4l2_subdev *sd, int on)
 283{
 284        struct sii9234_context *ctx = sd_to_context(sd);
 285        int ret;
 286
 287        if (on)
 288                ret = pm_runtime_get_sync(&ctx->client->dev);
 289        else
 290                ret = pm_runtime_put(&ctx->client->dev);
 291        /* only values < 0 indicate errors */
 292        return ret < 0 ? ret : 0;
 293}
 294
 295static int sii9234_s_stream(struct v4l2_subdev *sd, int enable)
 296{
 297        struct sii9234_context *ctx = sd_to_context(sd);
 298
 299        /* (dis/en)able TDMS output */
 300        sii9234_writeb_mask(ctx->client, 0x1a, enable ? 0 : ~0 , 1 << 4);
 301        return 0;
 302}
 303
 304static const struct v4l2_subdev_core_ops sii9234_core_ops = {
 305        .s_power =  sii9234_s_power,
 306};
 307
 308static const struct v4l2_subdev_video_ops sii9234_video_ops = {
 309        .s_stream =  sii9234_s_stream,
 310};
 311
 312static const struct v4l2_subdev_ops sii9234_ops = {
 313        .core = &sii9234_core_ops,
 314        .video = &sii9234_video_ops,
 315};
 316
 317static int sii9234_probe(struct i2c_client *client,
 318                         const struct i2c_device_id *id)
 319{
 320        struct device *dev = &client->dev;
 321        struct sii9234_platform_data *pdata = dev->platform_data;
 322        struct sii9234_context *ctx;
 323        int ret;
 324
 325        ctx = devm_kzalloc(&client->dev, sizeof(*ctx), GFP_KERNEL);
 326        if (!ctx) {
 327                dev_err(dev, "out of memory\n");
 328                ret = -ENOMEM;
 329                goto fail;
 330        }
 331        ctx->client = client;
 332
 333        ctx->power = devm_regulator_get(dev, "hdmi-en");
 334        if (IS_ERR(ctx->power)) {
 335                dev_err(dev, "failed to acquire regulator hdmi-en\n");
 336                return PTR_ERR(ctx->power);
 337        }
 338
 339        ctx->gpio_n_reset = pdata->gpio_n_reset;
 340        ret = devm_gpio_request(dev, ctx->gpio_n_reset, "MHL_RST");
 341        if (ret) {
 342                dev_err(dev, "failed to acquire MHL_RST gpio\n");
 343                return ret;
 344        }
 345
 346        v4l2_i2c_subdev_init(&ctx->sd, client, &sii9234_ops);
 347
 348        pm_runtime_enable(dev);
 349
 350        /* enable device */
 351        ret = pm_runtime_get_sync(dev);
 352        if (ret)
 353                goto fail_pm;
 354
 355        /* verify chip version */
 356        ret = sii9234_verify_version(client);
 357        if (ret)
 358                goto fail_pm_get;
 359
 360        /* stop processing */
 361        pm_runtime_put(dev);
 362
 363        dev_info(dev, "probe successful\n");
 364
 365        return 0;
 366
 367fail_pm_get:
 368        pm_runtime_put_sync(dev);
 369
 370fail_pm:
 371        pm_runtime_disable(dev);
 372
 373fail:
 374        dev_err(dev, "probe failed\n");
 375
 376        return ret;
 377}
 378
 379static int sii9234_remove(struct i2c_client *client)
 380{
 381        struct device *dev = &client->dev;
 382
 383        pm_runtime_disable(dev);
 384
 385        dev_info(dev, "remove successful\n");
 386
 387        return 0;
 388}
 389
 390
 391static const struct i2c_device_id sii9234_id[] = {
 392        { "SII9234", 0 },
 393        { },
 394};
 395
 396MODULE_DEVICE_TABLE(i2c, sii9234_id);
 397static struct i2c_driver sii9234_driver = {
 398        .driver = {
 399                .name   = "sii9234",
 400                .pm = &sii9234_pm_ops,
 401        },
 402        .probe          = sii9234_probe,
 403        .remove         = sii9234_remove,
 404        .id_table = sii9234_id,
 405};
 406
 407module_i2c_driver(sii9234_driver);
 408