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 32MODULE_AUTHOR("Tomasz Stanislawski <t.stanislaws@samsung.com>"); 33MODULE_DESCRIPTION("Samsung MHL interface driver"); 34MODULE_LICENSE("GPL"); 35 36struct sii9234_context { 37 struct i2c_client *client; 38 struct regulator *power; 39 int gpio_n_reset; 40 struct v4l2_subdev sd; 41}; 42 43static inline struct sii9234_context *sd_to_context(struct v4l2_subdev *sd) 44{ 45 return container_of(sd, struct sii9234_context, sd); 46} 47 48static inline int sii9234_readb(struct i2c_client *client, int addr) 49{ 50 return i2c_smbus_read_byte_data(client, addr); 51} 52 53static 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 58static 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 70static 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 82static 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 96static 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 108static 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 145static 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 176static 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 219static 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 233static 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 247static 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 271fail: 272 dev_err(dev, "failed to resume\n"); 273 regulator_disable(ctx->power); 274 275 return ret; 276} 277 278static const struct dev_pm_ops sii9234_pm_ops = { 279 .runtime_suspend = sii9234_runtime_suspend, 280 .runtime_resume = sii9234_runtime_resume, 281}; 282 283static 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 296static 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 305static const struct v4l2_subdev_core_ops sii9234_core_ops = { 306 .s_power = sii9234_s_power, 307}; 308 309static const struct v4l2_subdev_video_ops sii9234_video_ops = { 310 .s_stream = sii9234_s_stream, 311}; 312 313static const struct v4l2_subdev_ops sii9234_ops = { 314 .core = &sii9234_core_ops, 315 .video = &sii9234_video_ops, 316}; 317 318static 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 369fail_pm_get: 370 pm_runtime_put_sync(dev); 371 372fail_pm: 373 pm_runtime_disable(dev); 374 gpio_free(ctx->gpio_n_reset); 375 376fail_power: 377 regulator_put(ctx->power); 378 379fail_ctx: 380 kfree(ctx); 381 382fail: 383 dev_err(dev, "probe failed\n"); 384 385 return ret; 386} 387 388static 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 405static const struct i2c_device_id sii9234_id[] = { 406 { "SII9234", 0 }, 407 { }, 408}; 409 410MODULE_DEVICE_TABLE(i2c, sii9234_id); 411static 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 422static int __init sii9234_init(void) 423{ 424 return i2c_add_driver(&sii9234_driver); 425} 426module_init(sii9234_init); 427 428static void __exit sii9234_exit(void) 429{ 430 i2c_del_driver(&sii9234_driver); 431} 432module_exit(sii9234_exit); 433