1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * LCD/Backlight Driver for Sharp Zaurus Handhelds (various models) 4 * 5 * Copyright (c) 2004-2006 Richard Purdie 6 * 7 * Based on Sharp's 2.4 Backlight Driver 8 * 9 * Copyright (c) 2008 Marvell International Ltd. 10 * Converted to SPI device based LCD/Backlight device driver 11 * by Eric Miao <eric.miao@marvell.com> 12 */ 13 14 #include <linux/module.h> 15 #include <linux/kernel.h> 16 #include <linux/init.h> 17 #include <linux/delay.h> 18 #include <linux/gpio/consumer.h> 19 #include <linux/fb.h> 20 #include <linux/lcd.h> 21 #include <linux/spi/spi.h> 22 #include <linux/spi/corgi_lcd.h> 23 #include <linux/slab.h> 24 #include <asm/mach/sharpsl_param.h> 25 26 #define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL) 27 28 /* Register Addresses */ 29 #define RESCTL_ADRS 0x00 30 #define PHACTRL_ADRS 0x01 31 #define DUTYCTRL_ADRS 0x02 32 #define POWERREG0_ADRS 0x03 33 #define POWERREG1_ADRS 0x04 34 #define GPOR3_ADRS 0x05 35 #define PICTRL_ADRS 0x06 36 #define POLCTRL_ADRS 0x07 37 38 /* Register Bit Definitions */ 39 #define RESCTL_QVGA 0x01 40 #define RESCTL_VGA 0x00 41 42 #define POWER1_VW_ON 0x01 /* VW Supply FET ON */ 43 #define POWER1_GVSS_ON 0x02 /* GVSS(-8V) Power Supply ON */ 44 #define POWER1_VDD_ON 0x04 /* VDD(8V),SVSS(-4V) Power Supply ON */ 45 46 #define POWER1_VW_OFF 0x00 /* VW Supply FET OFF */ 47 #define POWER1_GVSS_OFF 0x00 /* GVSS(-8V) Power Supply OFF */ 48 #define POWER1_VDD_OFF 0x00 /* VDD(8V),SVSS(-4V) Power Supply OFF */ 49 50 #define POWER0_COM_DCLK 0x01 /* COM Voltage DC Bias DAC Serial Data Clock */ 51 #define POWER0_COM_DOUT 0x02 /* COM Voltage DC Bias DAC Serial Data Out */ 52 #define POWER0_DAC_ON 0x04 /* DAC Power Supply ON */ 53 #define POWER0_COM_ON 0x08 /* COM Power Supply ON */ 54 #define POWER0_VCC5_ON 0x10 /* VCC5 Power Supply ON */ 55 56 #define POWER0_DAC_OFF 0x00 /* DAC Power Supply OFF */ 57 #define POWER0_COM_OFF 0x00 /* COM Power Supply OFF */ 58 #define POWER0_VCC5_OFF 0x00 /* VCC5 Power Supply OFF */ 59 60 #define PICTRL_INIT_STATE 0x01 61 #define PICTRL_INIOFF 0x02 62 #define PICTRL_POWER_DOWN 0x04 63 #define PICTRL_COM_SIGNAL_OFF 0x08 64 #define PICTRL_DAC_SIGNAL_OFF 0x10 65 66 #define POLCTRL_SYNC_POL_FALL 0x01 67 #define POLCTRL_EN_POL_FALL 0x02 68 #define POLCTRL_DATA_POL_FALL 0x04 69 #define POLCTRL_SYNC_ACT_H 0x08 70 #define POLCTRL_EN_ACT_L 0x10 71 72 #define POLCTRL_SYNC_POL_RISE 0x00 73 #define POLCTRL_EN_POL_RISE 0x00 74 #define POLCTRL_DATA_POL_RISE 0x00 75 #define POLCTRL_SYNC_ACT_L 0x00 76 #define POLCTRL_EN_ACT_H 0x00 77 78 #define PHACTRL_PHASE_MANUAL 0x01 79 #define DEFAULT_PHAD_QVGA (9) 80 #define DEFAULT_COMADJ (125) 81 82 struct corgi_lcd { 83 struct spi_device *spi_dev; 84 struct lcd_device *lcd_dev; 85 struct backlight_device *bl_dev; 86 87 int limit_mask; 88 int intensity; 89 int power; 90 int mode; 91 char buf[2]; 92 93 struct gpio_desc *backlight_on; 94 struct gpio_desc *backlight_cont; 95 96 void (*kick_battery)(void); 97 }; 98 99 static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int reg, uint8_t val); 100 101 static struct corgi_lcd *the_corgi_lcd; 102 static unsigned long corgibl_flags; 103 #define CORGIBL_SUSPENDED 0x01 104 #define CORGIBL_BATTLOW 0x02 105 106 /* 107 * This is only a pseudo I2C interface. We can't use the standard kernel 108 * routines as the interface is write only. We just assume the data is acked... 109 */ 110 static void lcdtg_ssp_i2c_send(struct corgi_lcd *lcd, uint8_t data) 111 { 112 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, data); 113 udelay(10); 114 } 115 116 static void lcdtg_i2c_send_bit(struct corgi_lcd *lcd, uint8_t data) 117 { 118 lcdtg_ssp_i2c_send(lcd, data); 119 lcdtg_ssp_i2c_send(lcd, data | POWER0_COM_DCLK); 120 lcdtg_ssp_i2c_send(lcd, data); 121 } 122 123 static void lcdtg_i2c_send_start(struct corgi_lcd *lcd, uint8_t base) 124 { 125 lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT); 126 lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK); 127 lcdtg_ssp_i2c_send(lcd, base); 128 } 129 130 static void lcdtg_i2c_send_stop(struct corgi_lcd *lcd, uint8_t base) 131 { 132 lcdtg_ssp_i2c_send(lcd, base); 133 lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK); 134 lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT); 135 } 136 137 static void lcdtg_i2c_send_byte(struct corgi_lcd *lcd, 138 uint8_t base, uint8_t data) 139 { 140 int i; 141 142 for (i = 0; i < 8; i++) { 143 if (data & 0x80) 144 lcdtg_i2c_send_bit(lcd, base | POWER0_COM_DOUT); 145 else 146 lcdtg_i2c_send_bit(lcd, base); 147 data <<= 1; 148 } 149 } 150 151 static void lcdtg_i2c_wait_ack(struct corgi_lcd *lcd, uint8_t base) 152 { 153 lcdtg_i2c_send_bit(lcd, base); 154 } 155 156 static void lcdtg_set_common_voltage(struct corgi_lcd *lcd, 157 uint8_t base_data, uint8_t data) 158 { 159 /* Set Common Voltage to M62332FP via I2C */ 160 lcdtg_i2c_send_start(lcd, base_data); 161 lcdtg_i2c_send_byte(lcd, base_data, 0x9c); 162 lcdtg_i2c_wait_ack(lcd, base_data); 163 lcdtg_i2c_send_byte(lcd, base_data, 0x00); 164 lcdtg_i2c_wait_ack(lcd, base_data); 165 lcdtg_i2c_send_byte(lcd, base_data, data); 166 lcdtg_i2c_wait_ack(lcd, base_data); 167 lcdtg_i2c_send_stop(lcd, base_data); 168 } 169 170 static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int adrs, uint8_t data) 171 { 172 struct spi_message msg; 173 struct spi_transfer xfer = { 174 .len = 1, 175 .cs_change = 0, 176 .tx_buf = lcd->buf, 177 }; 178 179 lcd->buf[0] = ((adrs & 0x07) << 5) | (data & 0x1f); 180 spi_message_init(&msg); 181 spi_message_add_tail(&xfer, &msg); 182 183 return spi_sync(lcd->spi_dev, &msg); 184 } 185 186 /* Set Phase Adjust */ 187 static void lcdtg_set_phadadj(struct corgi_lcd *lcd, int mode) 188 { 189 int adj; 190 191 switch (mode) { 192 case CORGI_LCD_MODE_VGA: 193 /* Setting for VGA */ 194 adj = sharpsl_param.phadadj; 195 adj = (adj < 0) ? PHACTRL_PHASE_MANUAL : 196 PHACTRL_PHASE_MANUAL | ((adj & 0xf) << 1); 197 break; 198 case CORGI_LCD_MODE_QVGA: 199 default: 200 /* Setting for QVGA */ 201 adj = (DEFAULT_PHAD_QVGA << 1) | PHACTRL_PHASE_MANUAL; 202 break; 203 } 204 205 corgi_ssp_lcdtg_send(lcd, PHACTRL_ADRS, adj); 206 } 207 208 static void corgi_lcd_power_on(struct corgi_lcd *lcd) 209 { 210 int comadj; 211 212 /* Initialize Internal Logic & Port */ 213 corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, 214 PICTRL_POWER_DOWN | PICTRL_INIOFF | 215 PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF | 216 PICTRL_DAC_SIGNAL_OFF); 217 218 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, 219 POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_OFF | 220 POWER0_COM_OFF | POWER0_VCC5_OFF); 221 222 corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, 223 POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF); 224 225 /* VDD(+8V), SVSS(-4V) ON */ 226 corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, 227 POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON); 228 mdelay(3); 229 230 /* DAC ON */ 231 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, 232 POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON | 233 POWER0_COM_OFF | POWER0_VCC5_OFF); 234 235 /* INIB = H, INI = L */ 236 /* PICTL[0] = H , PICTL[1] = PICTL[2] = PICTL[4] = L */ 237 corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, 238 PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF); 239 240 /* Set Common Voltage */ 241 comadj = sharpsl_param.comadj; 242 if (comadj < 0) 243 comadj = DEFAULT_COMADJ; 244 245 lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF | 246 POWER0_VCC5_OFF, comadj); 247 248 /* VCC5 ON, DAC ON */ 249 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, 250 POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON | 251 POWER0_COM_OFF | POWER0_VCC5_ON); 252 253 /* GVSS(-8V) ON, VDD ON */ 254 corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, 255 POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON); 256 mdelay(2); 257 258 /* COM SIGNAL ON (PICTL[3] = L) */ 259 corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_INIT_STATE); 260 261 /* COM ON, DAC ON, VCC5_ON */ 262 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, 263 POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON | 264 POWER0_COM_ON | POWER0_VCC5_ON); 265 266 /* VW ON, GVSS ON, VDD ON */ 267 corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, 268 POWER1_VW_ON | POWER1_GVSS_ON | POWER1_VDD_ON); 269 270 /* Signals output enable */ 271 corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, 0); 272 273 /* Set Phase Adjust */ 274 lcdtg_set_phadadj(lcd, lcd->mode); 275 276 /* Initialize for Input Signals from ATI */ 277 corgi_ssp_lcdtg_send(lcd, POLCTRL_ADRS, 278 POLCTRL_SYNC_POL_RISE | POLCTRL_EN_POL_RISE | 279 POLCTRL_DATA_POL_RISE | POLCTRL_SYNC_ACT_L | 280 POLCTRL_EN_ACT_H); 281 udelay(1000); 282 283 switch (lcd->mode) { 284 case CORGI_LCD_MODE_VGA: 285 corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA); 286 break; 287 case CORGI_LCD_MODE_QVGA: 288 default: 289 corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA); 290 break; 291 } 292 } 293 294 static void corgi_lcd_power_off(struct corgi_lcd *lcd) 295 { 296 /* 60Hz x 2 frame = 16.7msec x 2 = 33.4 msec */ 297 msleep(34); 298 299 /* (1)VW OFF */ 300 corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, 301 POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON); 302 303 /* (2)COM OFF */ 304 corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_COM_SIGNAL_OFF); 305 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, 306 POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_ON); 307 308 /* (3)Set Common Voltage Bias 0V */ 309 lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF | 310 POWER0_VCC5_ON, 0); 311 312 /* (4)GVSS OFF */ 313 corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, 314 POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON); 315 316 /* (5)VCC5 OFF */ 317 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, 318 POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_OFF); 319 320 /* (6)Set PDWN, INIOFF, DACOFF */ 321 corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, 322 PICTRL_INIOFF | PICTRL_DAC_SIGNAL_OFF | 323 PICTRL_POWER_DOWN | PICTRL_COM_SIGNAL_OFF); 324 325 /* (7)DAC OFF */ 326 corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, 327 POWER0_DAC_OFF | POWER0_COM_OFF | POWER0_VCC5_OFF); 328 329 /* (8)VDD OFF */ 330 corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS, 331 POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF); 332 } 333 334 static int corgi_lcd_set_mode(struct lcd_device *ld, struct fb_videomode *m) 335 { 336 struct corgi_lcd *lcd = lcd_get_data(ld); 337 int mode = CORGI_LCD_MODE_QVGA; 338 339 if (m->xres == 640 || m->xres == 480) 340 mode = CORGI_LCD_MODE_VGA; 341 342 if (lcd->mode == mode) 343 return 0; 344 345 lcdtg_set_phadadj(lcd, mode); 346 347 switch (mode) { 348 case CORGI_LCD_MODE_VGA: 349 corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA); 350 break; 351 case CORGI_LCD_MODE_QVGA: 352 default: 353 corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA); 354 break; 355 } 356 357 lcd->mode = mode; 358 return 0; 359 } 360 361 static int corgi_lcd_set_power(struct lcd_device *ld, int power) 362 { 363 struct corgi_lcd *lcd = lcd_get_data(ld); 364 365 if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power)) 366 corgi_lcd_power_on(lcd); 367 368 if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power)) 369 corgi_lcd_power_off(lcd); 370 371 lcd->power = power; 372 return 0; 373 } 374 375 static int corgi_lcd_get_power(struct lcd_device *ld) 376 { 377 struct corgi_lcd *lcd = lcd_get_data(ld); 378 379 return lcd->power; 380 } 381 382 static struct lcd_ops corgi_lcd_ops = { 383 .get_power = corgi_lcd_get_power, 384 .set_power = corgi_lcd_set_power, 385 .set_mode = corgi_lcd_set_mode, 386 }; 387 388 static int corgi_bl_get_intensity(struct backlight_device *bd) 389 { 390 struct corgi_lcd *lcd = bl_get_data(bd); 391 392 return lcd->intensity; 393 } 394 395 static int corgi_bl_set_intensity(struct corgi_lcd *lcd, int intensity) 396 { 397 int cont; 398 399 if (intensity > 0x10) 400 intensity += 0x10; 401 402 corgi_ssp_lcdtg_send(lcd, DUTYCTRL_ADRS, intensity); 403 404 /* Bit 5 via GPIO_BACKLIGHT_CONT */ 405 cont = !!(intensity & 0x20); 406 407 if (lcd->backlight_cont) 408 gpiod_set_value_cansleep(lcd->backlight_cont, cont); 409 410 if (lcd->backlight_on) 411 gpiod_set_value_cansleep(lcd->backlight_on, intensity); 412 413 if (lcd->kick_battery) 414 lcd->kick_battery(); 415 416 lcd->intensity = intensity; 417 return 0; 418 } 419 420 static int corgi_bl_update_status(struct backlight_device *bd) 421 { 422 struct corgi_lcd *lcd = bl_get_data(bd); 423 int intensity = backlight_get_brightness(bd); 424 425 if (corgibl_flags & CORGIBL_SUSPENDED) 426 intensity = 0; 427 428 if ((corgibl_flags & CORGIBL_BATTLOW) && intensity > lcd->limit_mask) 429 intensity = lcd->limit_mask; 430 431 return corgi_bl_set_intensity(lcd, intensity); 432 } 433 434 void corgi_lcd_limit_intensity(int limit) 435 { 436 if (limit) 437 corgibl_flags |= CORGIBL_BATTLOW; 438 else 439 corgibl_flags &= ~CORGIBL_BATTLOW; 440 441 backlight_update_status(the_corgi_lcd->bl_dev); 442 } 443 EXPORT_SYMBOL(corgi_lcd_limit_intensity); 444 445 static const struct backlight_ops corgi_bl_ops = { 446 .get_brightness = corgi_bl_get_intensity, 447 .update_status = corgi_bl_update_status, 448 }; 449 450 #ifdef CONFIG_PM_SLEEP 451 static int corgi_lcd_suspend(struct device *dev) 452 { 453 struct corgi_lcd *lcd = dev_get_drvdata(dev); 454 455 corgibl_flags |= CORGIBL_SUSPENDED; 456 corgi_bl_set_intensity(lcd, 0); 457 corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN); 458 return 0; 459 } 460 461 static int corgi_lcd_resume(struct device *dev) 462 { 463 struct corgi_lcd *lcd = dev_get_drvdata(dev); 464 465 corgibl_flags &= ~CORGIBL_SUSPENDED; 466 corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK); 467 backlight_update_status(lcd->bl_dev); 468 return 0; 469 } 470 #endif 471 472 static SIMPLE_DEV_PM_OPS(corgi_lcd_pm_ops, corgi_lcd_suspend, corgi_lcd_resume); 473 474 static int setup_gpio_backlight(struct corgi_lcd *lcd, 475 struct corgi_lcd_platform_data *pdata) 476 { 477 struct spi_device *spi = lcd->spi_dev; 478 479 lcd->backlight_on = devm_gpiod_get_optional(&spi->dev, 480 "BL_ON", GPIOD_OUT_LOW); 481 if (IS_ERR(lcd->backlight_on)) 482 return PTR_ERR(lcd->backlight_on); 483 484 lcd->backlight_cont = devm_gpiod_get_optional(&spi->dev, "BL_CONT", 485 GPIOD_OUT_LOW); 486 if (IS_ERR(lcd->backlight_cont)) 487 return PTR_ERR(lcd->backlight_cont); 488 489 return 0; 490 } 491 492 static int corgi_lcd_probe(struct spi_device *spi) 493 { 494 struct backlight_properties props; 495 struct corgi_lcd_platform_data *pdata = dev_get_platdata(&spi->dev); 496 struct corgi_lcd *lcd; 497 int ret = 0; 498 499 if (pdata == NULL) { 500 dev_err(&spi->dev, "platform data not available\n"); 501 return -EINVAL; 502 } 503 504 lcd = devm_kzalloc(&spi->dev, sizeof(struct corgi_lcd), GFP_KERNEL); 505 if (!lcd) 506 return -ENOMEM; 507 508 lcd->spi_dev = spi; 509 510 lcd->lcd_dev = devm_lcd_device_register(&spi->dev, "corgi_lcd", 511 &spi->dev, lcd, &corgi_lcd_ops); 512 if (IS_ERR(lcd->lcd_dev)) 513 return PTR_ERR(lcd->lcd_dev); 514 515 lcd->power = FB_BLANK_POWERDOWN; 516 lcd->mode = (pdata) ? pdata->init_mode : CORGI_LCD_MODE_VGA; 517 518 memset(&props, 0, sizeof(struct backlight_properties)); 519 props.type = BACKLIGHT_RAW; 520 props.max_brightness = pdata->max_intensity; 521 lcd->bl_dev = devm_backlight_device_register(&spi->dev, "corgi_bl", 522 &spi->dev, lcd, &corgi_bl_ops, 523 &props); 524 if (IS_ERR(lcd->bl_dev)) 525 return PTR_ERR(lcd->bl_dev); 526 527 lcd->bl_dev->props.brightness = pdata->default_intensity; 528 lcd->bl_dev->props.power = FB_BLANK_UNBLANK; 529 530 ret = setup_gpio_backlight(lcd, pdata); 531 if (ret) 532 return ret; 533 534 lcd->kick_battery = pdata->kick_battery; 535 536 spi_set_drvdata(spi, lcd); 537 corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK); 538 backlight_update_status(lcd->bl_dev); 539 540 lcd->limit_mask = pdata->limit_mask; 541 the_corgi_lcd = lcd; 542 return 0; 543 } 544 545 static int corgi_lcd_remove(struct spi_device *spi) 546 { 547 struct corgi_lcd *lcd = spi_get_drvdata(spi); 548 549 lcd->bl_dev->props.power = FB_BLANK_UNBLANK; 550 lcd->bl_dev->props.brightness = 0; 551 backlight_update_status(lcd->bl_dev); 552 corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN); 553 return 0; 554 } 555 556 static struct spi_driver corgi_lcd_driver = { 557 .driver = { 558 .name = "corgi-lcd", 559 .pm = &corgi_lcd_pm_ops, 560 }, 561 .probe = corgi_lcd_probe, 562 .remove = corgi_lcd_remove, 563 }; 564 565 module_spi_driver(corgi_lcd_driver); 566 567 MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00"); 568 MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"); 569 MODULE_LICENSE("GPL"); 570 MODULE_ALIAS("spi:corgi-lcd"); 571