Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[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>

authored by

Tomasz Stanislawski and committed by
Mauro Carvalho Chehab
56e1df49 e861dccc

+468
+10
drivers/media/video/s5p-tv/Kconfig
··· 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
+2
drivers/media/video/s5p-tv/Makefile
··· 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
+432
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);
+24
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 */