1a4c8aaa5SJingoo Han /* 2a4c8aaa5SJingoo Han * ams369fg06 AMOLED LCD panel driver. 3a4c8aaa5SJingoo Han * 4a4c8aaa5SJingoo Han * Copyright (c) 2011 Samsung Electronics Co., Ltd. 5a4c8aaa5SJingoo Han * Author: Jingoo Han <jg1.han@samsung.com> 6a4c8aaa5SJingoo Han * 7a4c8aaa5SJingoo Han * Derived from drivers/video/s6e63m0.c 8a4c8aaa5SJingoo Han * 9a4c8aaa5SJingoo Han * This program is free software; you can redistribute it and/or modify it 10a4c8aaa5SJingoo Han * under the terms of the GNU General Public License as published by the 11a4c8aaa5SJingoo Han * Free Software Foundation; either version 2 of the License, or (at your 12a4c8aaa5SJingoo Han * option) any later version. 13a4c8aaa5SJingoo Han * 14a4c8aaa5SJingoo Han * This program is distributed in the hope that it will be useful, but 15a4c8aaa5SJingoo Han * WITHOUT ANY WARRANTY; without even the implied warranty of 16a4c8aaa5SJingoo Han * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17a4c8aaa5SJingoo Han * General Public License for more details. 18a4c8aaa5SJingoo Han * 19a4c8aaa5SJingoo Han * You should have received a copy of the GNU General Public License along 20a4c8aaa5SJingoo Han * with this program; if not, write to the Free Software Foundation, Inc., 21a4c8aaa5SJingoo Han * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22a4c8aaa5SJingoo Han */ 23a4c8aaa5SJingoo Han 24a4c8aaa5SJingoo Han #include <linux/wait.h> 25a4c8aaa5SJingoo Han #include <linux/fb.h> 26a4c8aaa5SJingoo Han #include <linux/delay.h> 27a4c8aaa5SJingoo Han #include <linux/gpio.h> 28a4c8aaa5SJingoo Han #include <linux/spi/spi.h> 29a4c8aaa5SJingoo Han #include <linux/lcd.h> 30a4c8aaa5SJingoo Han #include <linux/backlight.h> 31a4c8aaa5SJingoo Han 32a4c8aaa5SJingoo Han #define SLEEPMSEC 0x1000 33a4c8aaa5SJingoo Han #define ENDDEF 0x2000 34a4c8aaa5SJingoo Han #define DEFMASK 0xFF00 35a4c8aaa5SJingoo Han #define COMMAND_ONLY 0xFE 36a4c8aaa5SJingoo Han #define DATA_ONLY 0xFF 37a4c8aaa5SJingoo Han 38a4c8aaa5SJingoo Han #define MAX_GAMMA_LEVEL 5 39a4c8aaa5SJingoo Han #define GAMMA_TABLE_COUNT 21 40a4c8aaa5SJingoo Han 41a4c8aaa5SJingoo Han #define MIN_BRIGHTNESS 0 42a4c8aaa5SJingoo Han #define MAX_BRIGHTNESS 255 43a4c8aaa5SJingoo Han #define DEFAULT_BRIGHTNESS 150 44a4c8aaa5SJingoo Han 45a4c8aaa5SJingoo Han struct ams369fg06 { 46a4c8aaa5SJingoo Han struct device *dev; 47a4c8aaa5SJingoo Han struct spi_device *spi; 48a4c8aaa5SJingoo Han unsigned int power; 49a4c8aaa5SJingoo Han struct lcd_device *ld; 50a4c8aaa5SJingoo Han struct backlight_device *bd; 51a4c8aaa5SJingoo Han struct lcd_platform_data *lcd_pd; 52a4c8aaa5SJingoo Han }; 53a4c8aaa5SJingoo Han 54a4c8aaa5SJingoo Han static const unsigned short seq_display_on[] = { 55a4c8aaa5SJingoo Han 0x14, 0x03, 56a4c8aaa5SJingoo Han ENDDEF, 0x0000 57a4c8aaa5SJingoo Han }; 58a4c8aaa5SJingoo Han 59a4c8aaa5SJingoo Han static const unsigned short seq_display_off[] = { 60a4c8aaa5SJingoo Han 0x14, 0x00, 61a4c8aaa5SJingoo Han ENDDEF, 0x0000 62a4c8aaa5SJingoo Han }; 63a4c8aaa5SJingoo Han 64a4c8aaa5SJingoo Han static const unsigned short seq_stand_by_on[] = { 65a4c8aaa5SJingoo Han 0x1D, 0xA1, 66a4c8aaa5SJingoo Han SLEEPMSEC, 200, 67a4c8aaa5SJingoo Han ENDDEF, 0x0000 68a4c8aaa5SJingoo Han }; 69a4c8aaa5SJingoo Han 70a4c8aaa5SJingoo Han static const unsigned short seq_stand_by_off[] = { 71a4c8aaa5SJingoo Han 0x1D, 0xA0, 72a4c8aaa5SJingoo Han SLEEPMSEC, 250, 73a4c8aaa5SJingoo Han ENDDEF, 0x0000 74a4c8aaa5SJingoo Han }; 75a4c8aaa5SJingoo Han 76a4c8aaa5SJingoo Han static const unsigned short seq_setting[] = { 77a4c8aaa5SJingoo Han 0x31, 0x08, 78a4c8aaa5SJingoo Han 0x32, 0x14, 79a4c8aaa5SJingoo Han 0x30, 0x02, 80a4c8aaa5SJingoo Han 0x27, 0x01, 81a4c8aaa5SJingoo Han 0x12, 0x08, 82a4c8aaa5SJingoo Han 0x13, 0x08, 83a4c8aaa5SJingoo Han 0x15, 0x00, 84a4c8aaa5SJingoo Han 0x16, 0x00, 85a4c8aaa5SJingoo Han 86a4c8aaa5SJingoo Han 0xef, 0xd0, 87a4c8aaa5SJingoo Han DATA_ONLY, 0xe8, 88a4c8aaa5SJingoo Han 89a4c8aaa5SJingoo Han 0x39, 0x44, 90a4c8aaa5SJingoo Han 0x40, 0x00, 91a4c8aaa5SJingoo Han 0x41, 0x3f, 92a4c8aaa5SJingoo Han 0x42, 0x2a, 93a4c8aaa5SJingoo Han 0x43, 0x27, 94a4c8aaa5SJingoo Han 0x44, 0x27, 95a4c8aaa5SJingoo Han 0x45, 0x1f, 96a4c8aaa5SJingoo Han 0x46, 0x44, 97a4c8aaa5SJingoo Han 0x50, 0x00, 98a4c8aaa5SJingoo Han 0x51, 0x00, 99a4c8aaa5SJingoo Han 0x52, 0x17, 100a4c8aaa5SJingoo Han 0x53, 0x24, 101a4c8aaa5SJingoo Han 0x54, 0x26, 102a4c8aaa5SJingoo Han 0x55, 0x1f, 103a4c8aaa5SJingoo Han 0x56, 0x43, 104a4c8aaa5SJingoo Han 0x60, 0x00, 105a4c8aaa5SJingoo Han 0x61, 0x3f, 106a4c8aaa5SJingoo Han 0x62, 0x2a, 107a4c8aaa5SJingoo Han 0x63, 0x25, 108a4c8aaa5SJingoo Han 0x64, 0x24, 109a4c8aaa5SJingoo Han 0x65, 0x1b, 110a4c8aaa5SJingoo Han 0x66, 0x5c, 111a4c8aaa5SJingoo Han 112a4c8aaa5SJingoo Han 0x17, 0x22, 113a4c8aaa5SJingoo Han 0x18, 0x33, 114a4c8aaa5SJingoo Han 0x19, 0x03, 115a4c8aaa5SJingoo Han 0x1a, 0x01, 116a4c8aaa5SJingoo Han 0x22, 0xa4, 117a4c8aaa5SJingoo Han 0x23, 0x00, 118a4c8aaa5SJingoo Han 0x26, 0xa0, 119a4c8aaa5SJingoo Han 120a4c8aaa5SJingoo Han 0x1d, 0xa0, 121a4c8aaa5SJingoo Han SLEEPMSEC, 300, 122a4c8aaa5SJingoo Han 123a4c8aaa5SJingoo Han 0x14, 0x03, 124a4c8aaa5SJingoo Han 125a4c8aaa5SJingoo Han ENDDEF, 0x0000 126a4c8aaa5SJingoo Han }; 127a4c8aaa5SJingoo Han 128a4c8aaa5SJingoo Han /* gamma value: 2.2 */ 129a4c8aaa5SJingoo Han static const unsigned int ams369fg06_22_250[] = { 130a4c8aaa5SJingoo Han 0x00, 0x3f, 0x2a, 0x27, 0x27, 0x1f, 0x44, 131a4c8aaa5SJingoo Han 0x00, 0x00, 0x17, 0x24, 0x26, 0x1f, 0x43, 132a4c8aaa5SJingoo Han 0x00, 0x3f, 0x2a, 0x25, 0x24, 0x1b, 0x5c, 133a4c8aaa5SJingoo Han }; 134a4c8aaa5SJingoo Han 135a4c8aaa5SJingoo Han static const unsigned int ams369fg06_22_200[] = { 136a4c8aaa5SJingoo Han 0x00, 0x3f, 0x28, 0x29, 0x27, 0x21, 0x3e, 137a4c8aaa5SJingoo Han 0x00, 0x00, 0x10, 0x25, 0x27, 0x20, 0x3d, 138a4c8aaa5SJingoo Han 0x00, 0x3f, 0x28, 0x27, 0x25, 0x1d, 0x53, 139a4c8aaa5SJingoo Han }; 140a4c8aaa5SJingoo Han 141a4c8aaa5SJingoo Han static const unsigned int ams369fg06_22_150[] = { 142a4c8aaa5SJingoo Han 0x00, 0x3f, 0x2d, 0x29, 0x28, 0x23, 0x37, 143a4c8aaa5SJingoo Han 0x00, 0x00, 0x0b, 0x25, 0x28, 0x22, 0x36, 144a4c8aaa5SJingoo Han 0x00, 0x3f, 0x2b, 0x28, 0x26, 0x1f, 0x4a, 145a4c8aaa5SJingoo Han }; 146a4c8aaa5SJingoo Han 147a4c8aaa5SJingoo Han static const unsigned int ams369fg06_22_100[] = { 148a4c8aaa5SJingoo Han 0x00, 0x3f, 0x30, 0x2a, 0x2b, 0x24, 0x2f, 149a4c8aaa5SJingoo Han 0x00, 0x00, 0x00, 0x25, 0x29, 0x24, 0x2e, 150a4c8aaa5SJingoo Han 0x00, 0x3f, 0x2f, 0x29, 0x29, 0x21, 0x3f, 151a4c8aaa5SJingoo Han }; 152a4c8aaa5SJingoo Han 153a4c8aaa5SJingoo Han static const unsigned int ams369fg06_22_50[] = { 154a4c8aaa5SJingoo Han 0x00, 0x3f, 0x3c, 0x2c, 0x2d, 0x27, 0x24, 155a4c8aaa5SJingoo Han 0x00, 0x00, 0x00, 0x22, 0x2a, 0x27, 0x23, 156a4c8aaa5SJingoo Han 0x00, 0x3f, 0x3b, 0x2c, 0x2b, 0x24, 0x31, 157a4c8aaa5SJingoo Han }; 158a4c8aaa5SJingoo Han 159a4c8aaa5SJingoo Han struct ams369fg06_gamma { 160a4c8aaa5SJingoo Han unsigned int *gamma_22_table[MAX_GAMMA_LEVEL]; 161a4c8aaa5SJingoo Han }; 162a4c8aaa5SJingoo Han 163a4c8aaa5SJingoo Han static struct ams369fg06_gamma gamma_table = { 164a4c8aaa5SJingoo Han .gamma_22_table[0] = (unsigned int *)&ams369fg06_22_50, 165a4c8aaa5SJingoo Han .gamma_22_table[1] = (unsigned int *)&ams369fg06_22_100, 166a4c8aaa5SJingoo Han .gamma_22_table[2] = (unsigned int *)&ams369fg06_22_150, 167a4c8aaa5SJingoo Han .gamma_22_table[3] = (unsigned int *)&ams369fg06_22_200, 168a4c8aaa5SJingoo Han .gamma_22_table[4] = (unsigned int *)&ams369fg06_22_250, 169a4c8aaa5SJingoo Han }; 170a4c8aaa5SJingoo Han 171a4c8aaa5SJingoo Han static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data) 172a4c8aaa5SJingoo Han { 173a4c8aaa5SJingoo Han u16 buf[1]; 174a4c8aaa5SJingoo Han struct spi_message msg; 175a4c8aaa5SJingoo Han 176a4c8aaa5SJingoo Han struct spi_transfer xfer = { 177a4c8aaa5SJingoo Han .len = 2, 178a4c8aaa5SJingoo Han .tx_buf = buf, 179a4c8aaa5SJingoo Han }; 180a4c8aaa5SJingoo Han 181a4c8aaa5SJingoo Han buf[0] = (addr << 8) | data; 182a4c8aaa5SJingoo Han 183a4c8aaa5SJingoo Han spi_message_init(&msg); 184a4c8aaa5SJingoo Han spi_message_add_tail(&xfer, &msg); 185a4c8aaa5SJingoo Han 186a4c8aaa5SJingoo Han return spi_sync(lcd->spi, &msg); 187a4c8aaa5SJingoo Han } 188a4c8aaa5SJingoo Han 189a4c8aaa5SJingoo Han static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address, 190a4c8aaa5SJingoo Han unsigned char command) 191a4c8aaa5SJingoo Han { 192a4c8aaa5SJingoo Han int ret = 0; 193a4c8aaa5SJingoo Han 194a4c8aaa5SJingoo Han if (address != DATA_ONLY) 195a4c8aaa5SJingoo Han ret = ams369fg06_spi_write_byte(lcd, 0x70, address); 196a4c8aaa5SJingoo Han if (command != COMMAND_ONLY) 197a4c8aaa5SJingoo Han ret = ams369fg06_spi_write_byte(lcd, 0x72, command); 198a4c8aaa5SJingoo Han 199a4c8aaa5SJingoo Han return ret; 200a4c8aaa5SJingoo Han } 201a4c8aaa5SJingoo Han 202a4c8aaa5SJingoo Han static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd, 203a4c8aaa5SJingoo Han const unsigned short *wbuf) 204a4c8aaa5SJingoo Han { 205a4c8aaa5SJingoo Han int ret = 0, i = 0; 206a4c8aaa5SJingoo Han 207a4c8aaa5SJingoo Han while ((wbuf[i] & DEFMASK) != ENDDEF) { 208a4c8aaa5SJingoo Han if ((wbuf[i] & DEFMASK) != SLEEPMSEC) { 209a4c8aaa5SJingoo Han ret = ams369fg06_spi_write(lcd, wbuf[i], wbuf[i+1]); 210a4c8aaa5SJingoo Han if (ret) 211a4c8aaa5SJingoo Han break; 212a4c8aaa5SJingoo Han } else 213a4c8aaa5SJingoo Han mdelay(wbuf[i+1]); 214a4c8aaa5SJingoo Han i += 2; 215a4c8aaa5SJingoo Han } 216a4c8aaa5SJingoo Han 217a4c8aaa5SJingoo Han return ret; 218a4c8aaa5SJingoo Han } 219a4c8aaa5SJingoo Han 220a4c8aaa5SJingoo Han static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd, 221a4c8aaa5SJingoo Han const unsigned int *gamma) 222a4c8aaa5SJingoo Han { 223a4c8aaa5SJingoo Han unsigned int i = 0; 224a4c8aaa5SJingoo Han int ret = 0; 225a4c8aaa5SJingoo Han 226a4c8aaa5SJingoo Han for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) { 227a4c8aaa5SJingoo Han ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]); 228a4c8aaa5SJingoo Han ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]); 229a4c8aaa5SJingoo Han ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]); 230a4c8aaa5SJingoo Han if (ret) { 231a4c8aaa5SJingoo Han dev_err(lcd->dev, "failed to set gamma table.\n"); 232a4c8aaa5SJingoo Han goto gamma_err; 233a4c8aaa5SJingoo Han } 234a4c8aaa5SJingoo Han } 235a4c8aaa5SJingoo Han 236a4c8aaa5SJingoo Han gamma_err: 237a4c8aaa5SJingoo Han return ret; 238a4c8aaa5SJingoo Han } 239a4c8aaa5SJingoo Han 240a4c8aaa5SJingoo Han static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness) 241a4c8aaa5SJingoo Han { 242a4c8aaa5SJingoo Han int ret = 0; 243a4c8aaa5SJingoo Han int gamma = 0; 244a4c8aaa5SJingoo Han 245a4c8aaa5SJingoo Han if ((brightness >= 0) && (brightness <= 50)) 246a4c8aaa5SJingoo Han gamma = 0; 247a4c8aaa5SJingoo Han else if ((brightness > 50) && (brightness <= 100)) 248a4c8aaa5SJingoo Han gamma = 1; 249a4c8aaa5SJingoo Han else if ((brightness > 100) && (brightness <= 150)) 250a4c8aaa5SJingoo Han gamma = 2; 251a4c8aaa5SJingoo Han else if ((brightness > 150) && (brightness <= 200)) 252a4c8aaa5SJingoo Han gamma = 3; 253a4c8aaa5SJingoo Han else if ((brightness > 200) && (brightness <= 255)) 254a4c8aaa5SJingoo Han gamma = 4; 255a4c8aaa5SJingoo Han 256a4c8aaa5SJingoo Han ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]); 257a4c8aaa5SJingoo Han 258a4c8aaa5SJingoo Han return ret; 259a4c8aaa5SJingoo Han } 260a4c8aaa5SJingoo Han 261a4c8aaa5SJingoo Han static int ams369fg06_ldi_init(struct ams369fg06 *lcd) 262a4c8aaa5SJingoo Han { 263a4c8aaa5SJingoo Han int ret, i; 264a4c8aaa5SJingoo Han static const unsigned short *init_seq[] = { 265a4c8aaa5SJingoo Han seq_setting, 266a4c8aaa5SJingoo Han seq_stand_by_off, 267a4c8aaa5SJingoo Han }; 268a4c8aaa5SJingoo Han 269a4c8aaa5SJingoo Han for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 270a4c8aaa5SJingoo Han ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 271a4c8aaa5SJingoo Han if (ret) 272a4c8aaa5SJingoo Han break; 273a4c8aaa5SJingoo Han } 274a4c8aaa5SJingoo Han 275a4c8aaa5SJingoo Han return ret; 276a4c8aaa5SJingoo Han } 277a4c8aaa5SJingoo Han 278a4c8aaa5SJingoo Han static int ams369fg06_ldi_enable(struct ams369fg06 *lcd) 279a4c8aaa5SJingoo Han { 280a4c8aaa5SJingoo Han int ret, i; 281a4c8aaa5SJingoo Han static const unsigned short *init_seq[] = { 282a4c8aaa5SJingoo Han seq_stand_by_off, 283a4c8aaa5SJingoo Han seq_display_on, 284a4c8aaa5SJingoo Han }; 285a4c8aaa5SJingoo Han 286a4c8aaa5SJingoo Han for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 287a4c8aaa5SJingoo Han ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 288a4c8aaa5SJingoo Han if (ret) 289a4c8aaa5SJingoo Han break; 290a4c8aaa5SJingoo Han } 291a4c8aaa5SJingoo Han 292a4c8aaa5SJingoo Han return ret; 293a4c8aaa5SJingoo Han } 294a4c8aaa5SJingoo Han 295a4c8aaa5SJingoo Han static int ams369fg06_ldi_disable(struct ams369fg06 *lcd) 296a4c8aaa5SJingoo Han { 297a4c8aaa5SJingoo Han int ret, i; 298a4c8aaa5SJingoo Han 299a4c8aaa5SJingoo Han static const unsigned short *init_seq[] = { 300a4c8aaa5SJingoo Han seq_display_off, 301a4c8aaa5SJingoo Han seq_stand_by_on, 302a4c8aaa5SJingoo Han }; 303a4c8aaa5SJingoo Han 304a4c8aaa5SJingoo Han for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 305a4c8aaa5SJingoo Han ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 306a4c8aaa5SJingoo Han if (ret) 307a4c8aaa5SJingoo Han break; 308a4c8aaa5SJingoo Han } 309a4c8aaa5SJingoo Han 310a4c8aaa5SJingoo Han return ret; 311a4c8aaa5SJingoo Han } 312a4c8aaa5SJingoo Han 313a4c8aaa5SJingoo Han static int ams369fg06_power_is_on(int power) 314a4c8aaa5SJingoo Han { 315a4c8aaa5SJingoo Han return ((power) <= FB_BLANK_NORMAL); 316a4c8aaa5SJingoo Han } 317a4c8aaa5SJingoo Han 318a4c8aaa5SJingoo Han static int ams369fg06_power_on(struct ams369fg06 *lcd) 319a4c8aaa5SJingoo Han { 320a4c8aaa5SJingoo Han int ret = 0; 321a4c8aaa5SJingoo Han struct lcd_platform_data *pd = NULL; 322a4c8aaa5SJingoo Han struct backlight_device *bd = NULL; 323a4c8aaa5SJingoo Han 324a4c8aaa5SJingoo Han pd = lcd->lcd_pd; 325a4c8aaa5SJingoo Han if (!pd) { 326a4c8aaa5SJingoo Han dev_err(lcd->dev, "platform data is NULL.\n"); 327a4c8aaa5SJingoo Han return -EFAULT; 328a4c8aaa5SJingoo Han } 329a4c8aaa5SJingoo Han 330a4c8aaa5SJingoo Han bd = lcd->bd; 331a4c8aaa5SJingoo Han if (!bd) { 332a4c8aaa5SJingoo Han dev_err(lcd->dev, "backlight device is NULL.\n"); 333a4c8aaa5SJingoo Han return -EFAULT; 334a4c8aaa5SJingoo Han } 335a4c8aaa5SJingoo Han 336a4c8aaa5SJingoo Han if (!pd->power_on) { 337a4c8aaa5SJingoo Han dev_err(lcd->dev, "power_on is NULL.\n"); 338a4c8aaa5SJingoo Han return -EFAULT; 339a4c8aaa5SJingoo Han } else { 340a4c8aaa5SJingoo Han pd->power_on(lcd->ld, 1); 341a4c8aaa5SJingoo Han mdelay(pd->power_on_delay); 342a4c8aaa5SJingoo Han } 343a4c8aaa5SJingoo Han 344a4c8aaa5SJingoo Han if (!pd->reset) { 345a4c8aaa5SJingoo Han dev_err(lcd->dev, "reset is NULL.\n"); 346a4c8aaa5SJingoo Han return -EFAULT; 347a4c8aaa5SJingoo Han } else { 348a4c8aaa5SJingoo Han pd->reset(lcd->ld); 349a4c8aaa5SJingoo Han mdelay(pd->reset_delay); 350a4c8aaa5SJingoo Han } 351a4c8aaa5SJingoo Han 352a4c8aaa5SJingoo Han ret = ams369fg06_ldi_init(lcd); 353a4c8aaa5SJingoo Han if (ret) { 354a4c8aaa5SJingoo Han dev_err(lcd->dev, "failed to initialize ldi.\n"); 355a4c8aaa5SJingoo Han return ret; 356a4c8aaa5SJingoo Han } 357a4c8aaa5SJingoo Han 358a4c8aaa5SJingoo Han ret = ams369fg06_ldi_enable(lcd); 359a4c8aaa5SJingoo Han if (ret) { 360a4c8aaa5SJingoo Han dev_err(lcd->dev, "failed to enable ldi.\n"); 361a4c8aaa5SJingoo Han return ret; 362a4c8aaa5SJingoo Han } 363a4c8aaa5SJingoo Han 364a4c8aaa5SJingoo Han /* set brightness to current value after power on or resume. */ 365a4c8aaa5SJingoo Han ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness); 366a4c8aaa5SJingoo Han if (ret) { 367a4c8aaa5SJingoo Han dev_err(lcd->dev, "lcd gamma setting failed.\n"); 368a4c8aaa5SJingoo Han return ret; 369a4c8aaa5SJingoo Han } 370a4c8aaa5SJingoo Han 371a4c8aaa5SJingoo Han return 0; 372a4c8aaa5SJingoo Han } 373a4c8aaa5SJingoo Han 374a4c8aaa5SJingoo Han static int ams369fg06_power_off(struct ams369fg06 *lcd) 375a4c8aaa5SJingoo Han { 376a4c8aaa5SJingoo Han int ret = 0; 377a4c8aaa5SJingoo Han struct lcd_platform_data *pd = NULL; 378a4c8aaa5SJingoo Han 379a4c8aaa5SJingoo Han pd = lcd->lcd_pd; 380a4c8aaa5SJingoo Han if (!pd) { 381a4c8aaa5SJingoo Han dev_err(lcd->dev, "platform data is NULL\n"); 382a4c8aaa5SJingoo Han return -EFAULT; 383a4c8aaa5SJingoo Han } 384a4c8aaa5SJingoo Han 385a4c8aaa5SJingoo Han ret = ams369fg06_ldi_disable(lcd); 386a4c8aaa5SJingoo Han if (ret) { 387a4c8aaa5SJingoo Han dev_err(lcd->dev, "lcd setting failed.\n"); 388a4c8aaa5SJingoo Han return -EIO; 389a4c8aaa5SJingoo Han } 390a4c8aaa5SJingoo Han 391a4c8aaa5SJingoo Han mdelay(pd->power_off_delay); 392a4c8aaa5SJingoo Han 393a4c8aaa5SJingoo Han if (!pd->power_on) { 394a4c8aaa5SJingoo Han dev_err(lcd->dev, "power_on is NULL.\n"); 395a4c8aaa5SJingoo Han return -EFAULT; 396a4c8aaa5SJingoo Han } else 397a4c8aaa5SJingoo Han pd->power_on(lcd->ld, 0); 398a4c8aaa5SJingoo Han 399a4c8aaa5SJingoo Han return 0; 400a4c8aaa5SJingoo Han } 401a4c8aaa5SJingoo Han 402a4c8aaa5SJingoo Han static int ams369fg06_power(struct ams369fg06 *lcd, int power) 403a4c8aaa5SJingoo Han { 404a4c8aaa5SJingoo Han int ret = 0; 405a4c8aaa5SJingoo Han 406a4c8aaa5SJingoo Han if (ams369fg06_power_is_on(power) && 407a4c8aaa5SJingoo Han !ams369fg06_power_is_on(lcd->power)) 408a4c8aaa5SJingoo Han ret = ams369fg06_power_on(lcd); 409a4c8aaa5SJingoo Han else if (!ams369fg06_power_is_on(power) && 410a4c8aaa5SJingoo Han ams369fg06_power_is_on(lcd->power)) 411a4c8aaa5SJingoo Han ret = ams369fg06_power_off(lcd); 412a4c8aaa5SJingoo Han 413a4c8aaa5SJingoo Han if (!ret) 414a4c8aaa5SJingoo Han lcd->power = power; 415a4c8aaa5SJingoo Han 416a4c8aaa5SJingoo Han return ret; 417a4c8aaa5SJingoo Han } 418a4c8aaa5SJingoo Han 419a4c8aaa5SJingoo Han static int ams369fg06_get_power(struct lcd_device *ld) 420a4c8aaa5SJingoo Han { 421a4c8aaa5SJingoo Han struct ams369fg06 *lcd = lcd_get_data(ld); 422a4c8aaa5SJingoo Han 423a4c8aaa5SJingoo Han return lcd->power; 424a4c8aaa5SJingoo Han } 425a4c8aaa5SJingoo Han 426a4c8aaa5SJingoo Han static int ams369fg06_set_power(struct lcd_device *ld, int power) 427a4c8aaa5SJingoo Han { 428a4c8aaa5SJingoo Han struct ams369fg06 *lcd = lcd_get_data(ld); 429a4c8aaa5SJingoo Han 430a4c8aaa5SJingoo Han if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && 431a4c8aaa5SJingoo Han power != FB_BLANK_NORMAL) { 432a4c8aaa5SJingoo Han dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); 433a4c8aaa5SJingoo Han return -EINVAL; 434a4c8aaa5SJingoo Han } 435a4c8aaa5SJingoo Han 436a4c8aaa5SJingoo Han return ams369fg06_power(lcd, power); 437a4c8aaa5SJingoo Han } 438a4c8aaa5SJingoo Han 439a4c8aaa5SJingoo Han static int ams369fg06_get_brightness(struct backlight_device *bd) 440a4c8aaa5SJingoo Han { 441a4c8aaa5SJingoo Han return bd->props.brightness; 442a4c8aaa5SJingoo Han } 443a4c8aaa5SJingoo Han 444a4c8aaa5SJingoo Han static int ams369fg06_set_brightness(struct backlight_device *bd) 445a4c8aaa5SJingoo Han { 446a4c8aaa5SJingoo Han int ret = 0; 447a4c8aaa5SJingoo Han int brightness = bd->props.brightness; 448a4c8aaa5SJingoo Han struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev); 449a4c8aaa5SJingoo Han 450a4c8aaa5SJingoo Han if (brightness < MIN_BRIGHTNESS || 451a4c8aaa5SJingoo Han brightness > bd->props.max_brightness) { 452a4c8aaa5SJingoo Han dev_err(&bd->dev, "lcd brightness should be %d to %d.\n", 453a4c8aaa5SJingoo Han MIN_BRIGHTNESS, MAX_BRIGHTNESS); 454a4c8aaa5SJingoo Han return -EINVAL; 455a4c8aaa5SJingoo Han } 456a4c8aaa5SJingoo Han 457a4c8aaa5SJingoo Han ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness); 458a4c8aaa5SJingoo Han if (ret) { 459a4c8aaa5SJingoo Han dev_err(&bd->dev, "lcd brightness setting failed.\n"); 460a4c8aaa5SJingoo Han return -EIO; 461a4c8aaa5SJingoo Han } 462a4c8aaa5SJingoo Han 463a4c8aaa5SJingoo Han return ret; 464a4c8aaa5SJingoo Han } 465a4c8aaa5SJingoo Han 466a4c8aaa5SJingoo Han static struct lcd_ops ams369fg06_lcd_ops = { 467a4c8aaa5SJingoo Han .get_power = ams369fg06_get_power, 468a4c8aaa5SJingoo Han .set_power = ams369fg06_set_power, 469a4c8aaa5SJingoo Han }; 470a4c8aaa5SJingoo Han 471a4c8aaa5SJingoo Han static const struct backlight_ops ams369fg06_backlight_ops = { 472a4c8aaa5SJingoo Han .get_brightness = ams369fg06_get_brightness, 473a4c8aaa5SJingoo Han .update_status = ams369fg06_set_brightness, 474a4c8aaa5SJingoo Han }; 475a4c8aaa5SJingoo Han 476a4c8aaa5SJingoo Han static int __devinit ams369fg06_probe(struct spi_device *spi) 477a4c8aaa5SJingoo Han { 478a4c8aaa5SJingoo Han int ret = 0; 479a4c8aaa5SJingoo Han struct ams369fg06 *lcd = NULL; 480a4c8aaa5SJingoo Han struct lcd_device *ld = NULL; 481a4c8aaa5SJingoo Han struct backlight_device *bd = NULL; 482ef22f6a7SAxel Lin struct backlight_properties props; 483a4c8aaa5SJingoo Han 484a4c8aaa5SJingoo Han lcd = kzalloc(sizeof(struct ams369fg06), GFP_KERNEL); 485a4c8aaa5SJingoo Han if (!lcd) 486a4c8aaa5SJingoo Han return -ENOMEM; 487a4c8aaa5SJingoo Han 488a4c8aaa5SJingoo Han /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */ 489a4c8aaa5SJingoo Han spi->bits_per_word = 16; 490a4c8aaa5SJingoo Han 491a4c8aaa5SJingoo Han ret = spi_setup(spi); 492a4c8aaa5SJingoo Han if (ret < 0) { 493a4c8aaa5SJingoo Han dev_err(&spi->dev, "spi setup failed.\n"); 494a4c8aaa5SJingoo Han goto out_free_lcd; 495a4c8aaa5SJingoo Han } 496a4c8aaa5SJingoo Han 497a4c8aaa5SJingoo Han lcd->spi = spi; 498a4c8aaa5SJingoo Han lcd->dev = &spi->dev; 499a4c8aaa5SJingoo Han 500a4c8aaa5SJingoo Han lcd->lcd_pd = spi->dev.platform_data; 501a4c8aaa5SJingoo Han if (!lcd->lcd_pd) { 502a4c8aaa5SJingoo Han dev_err(&spi->dev, "platform data is NULL\n"); 503a4c8aaa5SJingoo Han goto out_free_lcd; 504a4c8aaa5SJingoo Han } 505a4c8aaa5SJingoo Han 506a4c8aaa5SJingoo Han ld = lcd_device_register("ams369fg06", &spi->dev, lcd, 507a4c8aaa5SJingoo Han &ams369fg06_lcd_ops); 508a4c8aaa5SJingoo Han if (IS_ERR(ld)) { 509a4c8aaa5SJingoo Han ret = PTR_ERR(ld); 510a4c8aaa5SJingoo Han goto out_free_lcd; 511a4c8aaa5SJingoo Han } 512a4c8aaa5SJingoo Han 513a4c8aaa5SJingoo Han lcd->ld = ld; 514a4c8aaa5SJingoo Han 515ef22f6a7SAxel Lin memset(&props, 0, sizeof(struct backlight_properties)); 516ef22f6a7SAxel Lin props.type = BACKLIGHT_RAW; 517ef22f6a7SAxel Lin props.max_brightness = MAX_BRIGHTNESS; 518ef22f6a7SAxel Lin 519a4c8aaa5SJingoo Han bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd, 520ef22f6a7SAxel Lin &ams369fg06_backlight_ops, &props); 521a4c8aaa5SJingoo Han if (IS_ERR(bd)) { 522a4c8aaa5SJingoo Han ret = PTR_ERR(bd); 523a4c8aaa5SJingoo Han goto out_lcd_unregister; 524a4c8aaa5SJingoo Han } 525a4c8aaa5SJingoo Han 526a4c8aaa5SJingoo Han bd->props.brightness = DEFAULT_BRIGHTNESS; 527a4c8aaa5SJingoo Han lcd->bd = bd; 528a4c8aaa5SJingoo Han 529a4c8aaa5SJingoo Han if (!lcd->lcd_pd->lcd_enabled) { 530a4c8aaa5SJingoo Han /* 531a4c8aaa5SJingoo Han * if lcd panel was off from bootloader then 532a4c8aaa5SJingoo Han * current lcd status is powerdown and then 533a4c8aaa5SJingoo Han * it enables lcd panel. 534a4c8aaa5SJingoo Han */ 535a4c8aaa5SJingoo Han lcd->power = FB_BLANK_POWERDOWN; 536a4c8aaa5SJingoo Han 537a4c8aaa5SJingoo Han ams369fg06_power(lcd, FB_BLANK_UNBLANK); 538a4c8aaa5SJingoo Han } else 539a4c8aaa5SJingoo Han lcd->power = FB_BLANK_UNBLANK; 540a4c8aaa5SJingoo Han 541a4c8aaa5SJingoo Han dev_set_drvdata(&spi->dev, lcd); 542a4c8aaa5SJingoo Han 543a4c8aaa5SJingoo Han dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n"); 544a4c8aaa5SJingoo Han 545a4c8aaa5SJingoo Han return 0; 546a4c8aaa5SJingoo Han 547a4c8aaa5SJingoo Han out_lcd_unregister: 548a4c8aaa5SJingoo Han lcd_device_unregister(ld); 549a4c8aaa5SJingoo Han out_free_lcd: 550a4c8aaa5SJingoo Han kfree(lcd); 551a4c8aaa5SJingoo Han return ret; 552a4c8aaa5SJingoo Han } 553a4c8aaa5SJingoo Han 554a4c8aaa5SJingoo Han static int __devexit ams369fg06_remove(struct spi_device *spi) 555a4c8aaa5SJingoo Han { 556a4c8aaa5SJingoo Han struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 557a4c8aaa5SJingoo Han 558a4c8aaa5SJingoo Han ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 559a4c8aaa5SJingoo Han backlight_device_unregister(lcd->bd); 560a4c8aaa5SJingoo Han lcd_device_unregister(lcd->ld); 561a4c8aaa5SJingoo Han kfree(lcd); 562a4c8aaa5SJingoo Han 563a4c8aaa5SJingoo Han return 0; 564a4c8aaa5SJingoo Han } 565a4c8aaa5SJingoo Han 566a4c8aaa5SJingoo Han #if defined(CONFIG_PM) 567a4c8aaa5SJingoo Han static unsigned int before_power; 568a4c8aaa5SJingoo Han 569a4c8aaa5SJingoo Han static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg) 570a4c8aaa5SJingoo Han { 571a4c8aaa5SJingoo Han int ret = 0; 572a4c8aaa5SJingoo Han struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 573a4c8aaa5SJingoo Han 574a4c8aaa5SJingoo Han dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power); 575a4c8aaa5SJingoo Han 576a4c8aaa5SJingoo Han before_power = lcd->power; 577a4c8aaa5SJingoo Han 578a4c8aaa5SJingoo Han /* 579a4c8aaa5SJingoo Han * when lcd panel is suspend, lcd panel becomes off 580a4c8aaa5SJingoo Han * regardless of status. 581a4c8aaa5SJingoo Han */ 582a4c8aaa5SJingoo Han ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 583a4c8aaa5SJingoo Han 584a4c8aaa5SJingoo Han return ret; 585a4c8aaa5SJingoo Han } 586a4c8aaa5SJingoo Han 587a4c8aaa5SJingoo Han static int ams369fg06_resume(struct spi_device *spi) 588a4c8aaa5SJingoo Han { 589a4c8aaa5SJingoo Han int ret = 0; 590a4c8aaa5SJingoo Han struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 591a4c8aaa5SJingoo Han 592a4c8aaa5SJingoo Han /* 593a4c8aaa5SJingoo Han * after suspended, if lcd panel status is FB_BLANK_UNBLANK 594a4c8aaa5SJingoo Han * (at that time, before_power is FB_BLANK_UNBLANK) then 595a4c8aaa5SJingoo Han * it changes that status to FB_BLANK_POWERDOWN to get lcd on. 596a4c8aaa5SJingoo Han */ 597a4c8aaa5SJingoo Han if (before_power == FB_BLANK_UNBLANK) 598a4c8aaa5SJingoo Han lcd->power = FB_BLANK_POWERDOWN; 599a4c8aaa5SJingoo Han 600a4c8aaa5SJingoo Han dev_dbg(&spi->dev, "before_power = %d\n", before_power); 601a4c8aaa5SJingoo Han 602a4c8aaa5SJingoo Han ret = ams369fg06_power(lcd, before_power); 603a4c8aaa5SJingoo Han 604a4c8aaa5SJingoo Han return ret; 605a4c8aaa5SJingoo Han } 606a4c8aaa5SJingoo Han #else 607a4c8aaa5SJingoo Han #define ams369fg06_suspend NULL 608a4c8aaa5SJingoo Han #define ams369fg06_resume NULL 609a4c8aaa5SJingoo Han #endif 610a4c8aaa5SJingoo Han 611a4c8aaa5SJingoo Han static void ams369fg06_shutdown(struct spi_device *spi) 612a4c8aaa5SJingoo Han { 613a4c8aaa5SJingoo Han struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 614a4c8aaa5SJingoo Han 615a4c8aaa5SJingoo Han ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 616a4c8aaa5SJingoo Han } 617a4c8aaa5SJingoo Han 618a4c8aaa5SJingoo Han static struct spi_driver ams369fg06_driver = { 619a4c8aaa5SJingoo Han .driver = { 620a4c8aaa5SJingoo Han .name = "ams369fg06", 621a4c8aaa5SJingoo Han .bus = &spi_bus_type, 622a4c8aaa5SJingoo Han .owner = THIS_MODULE, 623a4c8aaa5SJingoo Han }, 624a4c8aaa5SJingoo Han .probe = ams369fg06_probe, 625a4c8aaa5SJingoo Han .remove = __devexit_p(ams369fg06_remove), 626a4c8aaa5SJingoo Han .shutdown = ams369fg06_shutdown, 627a4c8aaa5SJingoo Han .suspend = ams369fg06_suspend, 628a4c8aaa5SJingoo Han .resume = ams369fg06_resume, 629a4c8aaa5SJingoo Han }; 630a4c8aaa5SJingoo Han 631a4c8aaa5SJingoo Han static int __init ams369fg06_init(void) 632a4c8aaa5SJingoo Han { 633a4c8aaa5SJingoo Han return spi_register_driver(&ams369fg06_driver); 634a4c8aaa5SJingoo Han } 635a4c8aaa5SJingoo Han 636a4c8aaa5SJingoo Han static void __exit ams369fg06_exit(void) 637a4c8aaa5SJingoo Han { 638a4c8aaa5SJingoo Han spi_unregister_driver(&ams369fg06_driver); 639a4c8aaa5SJingoo Han } 640a4c8aaa5SJingoo Han 641a4c8aaa5SJingoo Han module_init(ams369fg06_init); 642a4c8aaa5SJingoo Han module_exit(ams369fg06_exit); 643a4c8aaa5SJingoo Han 644a4c8aaa5SJingoo Han MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); 645a4c8aaa5SJingoo Han MODULE_DESCRIPTION("ams369fg06 LCD Driver"); 646a4c8aaa5SJingoo Han MODULE_LICENSE("GPL"); 647