Commit 56e1df491b803dc962ba47adcdaf4ad034c697a8

Authored by Tomasz Stanislawski
Committed by Mauro Carvalho Chehab
1 parent e861dccc6d

[media] v4l: s5p-tv: add sii9234 driver

SiI9234 is a converter of HDMI signal into MHL. The chip is present on some
boards from Samsung S5P family. The chip's configuration procedure is based on
MHD_SiI9234 driver created by doonsoo45.kim.

The driver is using:
- i2c framework
- v4l2 framework
- runtime PM

Signed-off-by: Tomasz Stanislawski <t.stanislaws@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

Showing 4 changed files with 468 additions and 0 deletions Side-by-side Diff

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