| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 1 | /* Copyright (c) 2010, Code Aurora Forum. All rights reserved. | 
 | 2 |  * | 
 | 3 |  * This program is free software; you can redistribute it and/or modify | 
 | 4 |  * it under the terms of the GNU General Public License version 2 and | 
 | 5 |  * only version 2 as published by the Free Software Foundation. | 
 | 6 |  * | 
 | 7 |  * This program is distributed in the hope that it will be useful, | 
 | 8 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 9 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 10 |  * GNU General Public License for more details. | 
 | 11 |  */ | 
 | 12 |  | 
 | 13 | #include <linux/i2c.h> | 
 | 14 | #include <linux/delay.h> | 
 | 15 | #include "msm_fb.h" | 
 | 16 |  | 
 | 17 | #define DEVICE_NAME "sii9022" | 
 | 18 | #define SII9022_DEVICE_ID   0xB0 | 
 | 19 | #define SII9022_ISR                   0x3D | 
 | 20 | #define SII9022_ISR_RXS_STATUS        0x08 | 
 | 21 |  | 
 | 22 | static int lcdc_sii9022_panel_on(struct platform_device *pdev); | 
 | 23 | static int lcdc_sii9022_panel_off(struct platform_device *pdev); | 
 | 24 |  | 
 | 25 | static struct i2c_client *sii9022_i2c_client; | 
 | 26 |  | 
 | 27 | struct sii9022_data { | 
 | 28 | 	struct msm_hdmi_platform_data *pd; | 
 | 29 | 	struct platform_device *pdev; | 
 | 30 | 	struct work_struct work; | 
 | 31 | 	int x_res; | 
 | 32 | 	int y_res; | 
 | 33 | 	int sysfs_entry_created; | 
 | 34 | 	int hdmi_attached; | 
 | 35 | }; | 
 | 36 | static struct sii9022_data *dd; | 
 | 37 |  | 
 | 38 | struct sii9022_i2c_addr_data{ | 
 | 39 | 	u8 addr; | 
 | 40 | 	u8 data; | 
 | 41 | }; | 
 | 42 |  | 
 | 43 | /* video mode data */ | 
 | 44 | static u8 video_mode_data[] = { | 
 | 45 | 	0x00, | 
 | 46 | 	0xF9, 0x1C, 0x70, 0x17, 0x72, 0x06, 0xEE, 0x02, | 
 | 47 | }; | 
 | 48 |  | 
 | 49 | static u8 avi_io_format[] = { | 
 | 50 | 	0x09, | 
 | 51 | 	0x00, 0x00, | 
 | 52 | }; | 
 | 53 |  | 
 | 54 | /* power state */ | 
 | 55 | static struct sii9022_i2c_addr_data regset0[] = { | 
 | 56 | 	{ 0x60, 0x04 }, | 
 | 57 | 	{ 0x63, 0x00 }, | 
 | 58 | 	{ 0x1E, 0x00 }, | 
 | 59 | }; | 
 | 60 |  | 
 | 61 | static u8 video_infoframe[] = { | 
 | 62 | 	0x0C, | 
 | 63 | 	0xF0, 0x00, 0x68, 0x00, 0x04, 0x00, 0x19, 0x00, | 
 | 64 | 	0xE9, 0x02, 0x04, 0x01, 0x04, 0x06, | 
 | 65 | }; | 
 | 66 |  | 
 | 67 | /* configure audio */ | 
 | 68 | static struct sii9022_i2c_addr_data regset1[] = { | 
 | 69 | 	{ 0x26, 0x90 }, | 
 | 70 | 	{ 0x20, 0x90 }, | 
 | 71 | 	{ 0x1F, 0x80 }, | 
 | 72 | 	{ 0x26, 0x80 }, | 
 | 73 | 	{ 0x24, 0x02 }, | 
 | 74 | 	{ 0x25, 0x0B }, | 
 | 75 | 	{ 0xBC, 0x02 }, | 
 | 76 | 	{ 0xBD, 0x24 }, | 
 | 77 | 	{ 0xBE, 0x02 }, | 
 | 78 | }; | 
 | 79 |  | 
 | 80 | /* enable audio */ | 
 | 81 | static u8 misc_infoframe[] = { | 
 | 82 | 	0xBF, | 
 | 83 | 	0xC2, 0x84, 0x01, 0x0A, 0x6F, 0x02, 0x00, 0x00, | 
 | 84 | 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 
 | 85 | }; | 
 | 86 |  | 
 | 87 | /* set HDMI, active */ | 
 | 88 | static struct sii9022_i2c_addr_data regset2[] = { | 
 | 89 | 	{ 0x1A, 0x01 }, | 
 | 90 | 	{ 0x3D, 0x00 }, | 
 | 91 | 	{ 0x3C, 0x02 }, | 
 | 92 | }; | 
 | 93 |  | 
 | 94 | static struct msm_fb_panel_data sii9022_panel_data = { | 
 | 95 | 	.on = lcdc_sii9022_panel_on, | 
 | 96 | 	.off = lcdc_sii9022_panel_off, | 
 | 97 | }; | 
 | 98 |  | 
 | 99 | static struct platform_device sii9022_device = { | 
 | 100 | 	.name   = DEVICE_NAME, | 
 | 101 | 	.id	= 1, | 
 | 102 | 	.dev	= { | 
 | 103 | 		.platform_data = &sii9022_panel_data, | 
 | 104 | 	} | 
 | 105 | }; | 
 | 106 |  | 
 | 107 | static int send_i2c_data(struct i2c_client *client, | 
 | 108 | 			 struct sii9022_i2c_addr_data *regset, | 
 | 109 | 			 int size) | 
 | 110 | { | 
 | 111 | 	int i; | 
 | 112 | 	int rc = 0; | 
 | 113 |  | 
 | 114 | 	for (i = 0; i < size; i++) { | 
 | 115 | 		rc = i2c_smbus_write_byte_data( | 
 | 116 | 			client, | 
 | 117 | 			regset[i].addr, regset[i].data); | 
 | 118 | 		if (rc) | 
 | 119 | 			break; | 
 | 120 | 	} | 
 | 121 | 	return rc; | 
 | 122 | } | 
 | 123 |  | 
 | 124 | static void sii9022_work_f(struct work_struct *work) | 
 | 125 | { | 
 | 126 | 	int isr; | 
 | 127 |  | 
 | 128 | 	isr = i2c_smbus_read_byte_data(sii9022_i2c_client, SII9022_ISR); | 
 | 129 | 	if (isr < 0) { | 
 | 130 | 		dev_err(&sii9022_i2c_client->dev, | 
 | 131 | 			"i2c read of isr failed rc = 0x%x\n", isr); | 
 | 132 | 		return; | 
 | 133 | 	} | 
 | 134 | 	if (isr == 0) | 
 | 135 | 		return; | 
 | 136 |  | 
 | 137 | 	/* reset any set bits */ | 
 | 138 | 	i2c_smbus_write_byte_data(sii9022_i2c_client, SII9022_ISR, isr); | 
 | 139 | 	dd->hdmi_attached = isr & SII9022_ISR_RXS_STATUS; | 
 | 140 | 	if (dd->pd->cable_detect) | 
 | 141 | 		dd->pd->cable_detect(dd->hdmi_attached); | 
 | 142 | 	if (dd->hdmi_attached) { | 
 | 143 | 		dd->x_res = 1280; | 
 | 144 | 		dd->y_res = 720; | 
 | 145 | 	} else { | 
 | 146 | 		dd->x_res = sii9022_panel_data.panel_info.xres; | 
 | 147 | 		dd->y_res = sii9022_panel_data.panel_info.yres; | 
 | 148 | 	} | 
 | 149 | } | 
 | 150 |  | 
 | 151 | static irqreturn_t sii9022_interrupt(int irq, void *dev_id) | 
 | 152 | { | 
 | 153 | 	struct sii9022_data *dd = dev_id; | 
 | 154 |  | 
 | 155 | 	schedule_work(&dd->work); | 
 | 156 | 	return IRQ_HANDLED; | 
 | 157 | } | 
 | 158 |  | 
 | 159 | static int hdmi_sii_enable(struct i2c_client *client) | 
 | 160 | { | 
 | 161 | 	int rc; | 
 | 162 | 	int retries = 10; | 
 | 163 | 	int count; | 
 | 164 |  | 
 | 165 | 	rc = i2c_smbus_write_byte_data(client, 0xC7, 0x00); | 
 | 166 | 	if (rc) | 
 | 167 | 		goto enable_exit; | 
 | 168 |  | 
 | 169 | 	do { | 
 | 170 | 		msleep(1); | 
 | 171 | 		rc = i2c_smbus_read_byte_data(client, 0x1B); | 
 | 172 | 	} while ((rc != SII9022_DEVICE_ID) && retries--); | 
 | 173 |  | 
 | 174 | 	if (rc != SII9022_DEVICE_ID) | 
 | 175 | 		return -ENODEV; | 
 | 176 |  | 
 | 177 | 	rc = i2c_smbus_write_byte_data(client, 0x1A, 0x11); | 
 | 178 | 	if (rc) | 
 | 179 | 		goto enable_exit; | 
 | 180 |  | 
 | 181 | 	count = ARRAY_SIZE(video_mode_data); | 
 | 182 | 	rc = i2c_master_send(client, video_mode_data, count); | 
 | 183 | 	if (rc != count) { | 
 | 184 | 		rc = -EIO; | 
 | 185 | 		goto enable_exit; | 
 | 186 | 	} | 
 | 187 |  | 
 | 188 | 	rc = i2c_smbus_write_byte_data(client, 0x08, 0x20); | 
 | 189 | 	if (rc) | 
 | 190 | 		goto enable_exit; | 
 | 191 | 	count = ARRAY_SIZE(avi_io_format); | 
 | 192 | 	rc = i2c_master_send(client, avi_io_format, count); | 
 | 193 | 	if (rc != count) { | 
 | 194 | 		rc = -EIO; | 
 | 195 | 		goto enable_exit; | 
 | 196 | 	} | 
 | 197 |  | 
 | 198 | 	rc = send_i2c_data(client, regset0, ARRAY_SIZE(regset0)); | 
 | 199 | 	if (rc) | 
 | 200 | 		goto enable_exit; | 
 | 201 |  | 
 | 202 | 	count = ARRAY_SIZE(video_infoframe); | 
 | 203 | 	rc = i2c_master_send(client, video_infoframe, count); | 
 | 204 | 	if (rc != count) { | 
 | 205 | 		rc = -EIO; | 
 | 206 | 		goto enable_exit; | 
 | 207 | 	} | 
 | 208 |  | 
 | 209 | 	rc = send_i2c_data(client, regset1, ARRAY_SIZE(regset1)); | 
 | 210 | 	if (rc) | 
 | 211 | 		goto enable_exit; | 
 | 212 |  | 
 | 213 | 	count = ARRAY_SIZE(misc_infoframe); | 
 | 214 | 	rc = i2c_master_send(client, misc_infoframe, count); | 
 | 215 | 	if (rc != count) { | 
 | 216 | 		rc = -EIO; | 
 | 217 | 		goto enable_exit; | 
 | 218 | 	} | 
 | 219 |  | 
 | 220 | 	rc = send_i2c_data(client, regset2, ARRAY_SIZE(regset2)); | 
 | 221 | 	if (rc) | 
 | 222 | 		goto enable_exit; | 
 | 223 |  | 
 | 224 | 	return 0; | 
 | 225 | enable_exit: | 
 | 226 | 	printk(KERN_ERR "%s: exited rc=%d\n", __func__, rc); | 
 | 227 | 	return rc; | 
 | 228 | } | 
 | 229 |  | 
 | 230 | static ssize_t show_res(struct device *device, | 
 | 231 | 			 struct device_attribute *attr, char *buf) | 
 | 232 | { | 
 | 233 | 	return snprintf(buf, PAGE_SIZE, "%dx%d\n", dd->x_res, dd->y_res); | 
 | 234 | } | 
 | 235 |  | 
 | 236 | static struct device_attribute device_attrs[] = { | 
 | 237 | 	__ATTR(screen_resolution, S_IRUGO|S_IWUSR, show_res, NULL), | 
 | 238 | }; | 
 | 239 |  | 
 | 240 | static int lcdc_sii9022_panel_on(struct platform_device *pdev) | 
 | 241 | { | 
 | 242 | 	int rc; | 
 | 243 | 	if (!dd->sysfs_entry_created) { | 
 | 244 | 		dd->pdev = pdev; | 
 | 245 | 		rc = device_create_file(&pdev->dev, &device_attrs[0]); | 
 | 246 | 		if (!rc) | 
 | 247 | 			dd->sysfs_entry_created = 1; | 
 | 248 | 	} | 
 | 249 |  | 
 | 250 | 	rc = hdmi_sii_enable(sii9022_i2c_client); | 
 | 251 | 	if (rc) { | 
 | 252 | 		dd->hdmi_attached = 0; | 
 | 253 | 		dd->x_res = sii9022_panel_data.panel_info.xres; | 
 | 254 | 		dd->y_res = sii9022_panel_data.panel_info.yres; | 
 | 255 | 	} | 
 | 256 | 	if (dd->pd->irq) | 
 | 257 | 		enable_irq(dd->pd->irq); | 
 | 258 | 	/* Don't return the value from hdmi_sii_enable(). | 
 | 259 | 	 * It may fail on some ST1.5s, but we must return 0 from this | 
 | 260 | 	 * function in order for the on-board display to turn on. | 
 | 261 | 	 */ | 
 | 262 | 	return 0; | 
 | 263 | } | 
 | 264 |  | 
 | 265 | static int lcdc_sii9022_panel_off(struct platform_device *pdev) | 
 | 266 | { | 
 | 267 | 	if (dd->pd->irq) | 
 | 268 | 		disable_irq(dd->pd->irq); | 
 | 269 | 	return 0; | 
 | 270 | } | 
 | 271 |  | 
 | 272 | static const struct i2c_device_id hmdi_sii_id[] = { | 
 | 273 | 	{ DEVICE_NAME, 0 }, | 
 | 274 | 	{ } | 
 | 275 | }; | 
 | 276 |  | 
 | 277 | static int hdmi_sii_probe(struct i2c_client *client, | 
 | 278 | 			const struct i2c_device_id *id) | 
 | 279 | { | 
 | 280 | 	int rc; | 
 | 281 |  | 
 | 282 | 	if (!i2c_check_functionality(client->adapter, | 
 | 283 | 				     I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) | 
 | 284 | 		return -ENODEV; | 
 | 285 |  | 
 | 286 | 	dd = kzalloc(sizeof *dd, GFP_KERNEL); | 
 | 287 | 	if (!dd) { | 
 | 288 | 		rc = -ENOMEM; | 
 | 289 | 		goto probe_exit; | 
 | 290 | 	} | 
 | 291 | 	sii9022_i2c_client = client; | 
 | 292 | 	i2c_set_clientdata(client, dd); | 
 | 293 | 	dd->pd = client->dev.platform_data; | 
 | 294 | 	if (!dd->pd) { | 
 | 295 | 		rc = -ENODEV; | 
 | 296 | 		goto probe_free; | 
 | 297 | 	} | 
 | 298 | 	if (dd->pd->irq) { | 
 | 299 | 		INIT_WORK(&dd->work, sii9022_work_f); | 
 | 300 | 		rc = request_irq(dd->pd->irq, | 
 | 301 | 				 &sii9022_interrupt, | 
 | 302 | 				 IRQF_TRIGGER_FALLING, | 
 | 303 | 				 "sii9022_cable", dd); | 
 | 304 | 		if (rc) | 
 | 305 | 			goto probe_free; | 
 | 306 | 		disable_irq(dd->pd->irq); | 
 | 307 | 	} | 
 | 308 | 	msm_fb_add_device(&sii9022_device); | 
 | 309 | 	dd->x_res = sii9022_panel_data.panel_info.xres; | 
 | 310 | 	dd->y_res = sii9022_panel_data.panel_info.yres; | 
 | 311 |  | 
 | 312 | 	return 0; | 
 | 313 |  | 
 | 314 | probe_free: | 
 | 315 | 	i2c_set_clientdata(client, NULL); | 
 | 316 | 	kfree(dd); | 
 | 317 | probe_exit: | 
 | 318 | 	return rc; | 
 | 319 | } | 
 | 320 |  | 
 | 321 | static int __devexit hdmi_sii_remove(struct i2c_client *client) | 
 | 322 | { | 
 | 323 | 	int err = 0 ; | 
 | 324 | 	struct msm_hdmi_platform_data *pd; | 
 | 325 |  | 
 | 326 | 	if (dd->sysfs_entry_created) | 
 | 327 | 		device_remove_file(&dd->pdev->dev, &device_attrs[0]); | 
 | 328 | 	pd = client->dev.platform_data; | 
 | 329 | 	if (pd && pd->irq) | 
 | 330 | 		free_irq(pd->irq, dd); | 
 | 331 | 	i2c_set_clientdata(client, NULL); | 
 | 332 | 	kfree(dd); | 
 | 333 |  | 
 | 334 | 	return err ; | 
 | 335 | } | 
 | 336 |  | 
 | 337 | #ifdef CONFIG_PM | 
 | 338 | static int sii9022_suspend(struct device *dev) | 
 | 339 | { | 
 | 340 | 	if (dd && dd->pd && dd->pd->irq) | 
 | 341 | 		disable_irq(dd->pd->irq); | 
 | 342 | 	return 0; | 
 | 343 | } | 
 | 344 |  | 
 | 345 | static int sii9022_resume(struct device *dev) | 
 | 346 | { | 
 | 347 | 	if (dd && dd->pd && dd->pd->irq) | 
 | 348 | 		enable_irq(dd->pd->irq); | 
 | 349 | 	return 0; | 
 | 350 | } | 
 | 351 |  | 
 | 352 | static struct dev_pm_ops sii9022_pm_ops = { | 
 | 353 | 	.suspend = sii9022_suspend, | 
 | 354 | 	.resume = sii9022_resume, | 
 | 355 | }; | 
 | 356 | #endif | 
 | 357 |  | 
 | 358 | static struct i2c_driver hdmi_sii_i2c_driver = { | 
 | 359 | 	.driver = { | 
 | 360 | 		.name = DEVICE_NAME, | 
 | 361 | 		.owner = THIS_MODULE, | 
 | 362 | #ifdef CONFIG_PM | 
 | 363 | 		.pm     = &sii9022_pm_ops, | 
 | 364 | #endif | 
 | 365 | 	}, | 
 | 366 | 	.probe = hdmi_sii_probe, | 
 | 367 | 	.remove =  __exit_p(hdmi_sii_remove), | 
 | 368 | 	.id_table = hmdi_sii_id, | 
 | 369 | }; | 
 | 370 |  | 
 | 371 | static int __init lcdc_st15_init(void) | 
 | 372 | { | 
 | 373 | 	int ret; | 
 | 374 | 	struct msm_panel_info *pinfo; | 
 | 375 |  | 
 | 376 | 	if (msm_fb_detect_client("lcdc_st15")) | 
 | 377 | 		return 0; | 
 | 378 |  | 
 | 379 | 	pinfo = &sii9022_panel_data.panel_info; | 
 | 380 | 	pinfo->xres = 1366; | 
 | 381 | 	pinfo->yres = 768; | 
 | 382 | 	MSM_FB_SINGLE_MODE_PANEL(pinfo); | 
 | 383 | 	pinfo->type = LCDC_PANEL; | 
 | 384 | 	pinfo->pdest = DISPLAY_1; | 
 | 385 | 	pinfo->wait_cycle = 0; | 
 | 386 | 	pinfo->bpp = 24; | 
 | 387 | 	pinfo->fb_num = 2; | 
 | 388 | 	pinfo->clk_rate = 74250000; | 
 | 389 |  | 
 | 390 | 	pinfo->lcdc.h_back_porch = 120; | 
 | 391 | 	pinfo->lcdc.h_front_porch = 20; | 
 | 392 | 	pinfo->lcdc.h_pulse_width = 40; | 
 | 393 | 	pinfo->lcdc.v_back_porch = 25; | 
 | 394 | 	pinfo->lcdc.v_front_porch = 1; | 
 | 395 | 	pinfo->lcdc.v_pulse_width = 7; | 
 | 396 | 	pinfo->lcdc.border_clr = 0;      /* blk */ | 
 | 397 | 	pinfo->lcdc.underflow_clr = 0xff;        /* blue */ | 
 | 398 | 	pinfo->lcdc.hsync_skew = 0; | 
 | 399 |  | 
 | 400 | 	ret = i2c_add_driver(&hdmi_sii_i2c_driver); | 
 | 401 | 	if (ret) | 
 | 402 | 		printk(KERN_ERR "%s: failed to add i2c driver\n", __func__); | 
 | 403 |  | 
 | 404 | 	return ret; | 
 | 405 | } | 
 | 406 |  | 
 | 407 | static void __exit hdmi_sii_exit(void) | 
 | 408 | { | 
 | 409 | 	i2c_del_driver(&hdmi_sii_i2c_driver); | 
 | 410 | } | 
 | 411 |  | 
 | 412 | module_init(lcdc_st15_init); | 
 | 413 | module_exit(hdmi_sii_exit); |