aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/samsung_duallcd/ld9040.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/samsung_duallcd/ld9040.c')
-rw-r--r--drivers/video/samsung_duallcd/ld9040.c1234
1 files changed, 1234 insertions, 0 deletions
diff --git a/drivers/video/samsung_duallcd/ld9040.c b/drivers/video/samsung_duallcd/ld9040.c
new file mode 100644
index 0000000..2ff3af0
--- /dev/null
+++ b/drivers/video/samsung_duallcd/ld9040.c
@@ -0,0 +1,1234 @@
+/*
+ * LD9040 AMOLED LCD panel driver.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/wait.h>
+#include <linux/fb.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/lcd.h>
+#include <linux/backlight.h>
+#include <linux/ld9040.h>
+#ifdef CONFIG_HAS_EARLYSUSPEND
+#include <linux/earlysuspend.h>
+#endif
+
+#define SLEEPMSEC 0x1000
+#define ENDDEF 0x2000
+#define DEFMASK 0xFF00
+#define COMMAND_ONLY 0xFE
+#define DATA_ONLY 0xFF
+
+#define MIN_BRIGHTNESS 0
+#define MAX_BRIGHTNESS 255
+#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
+
+/* #define SMART_DIMMING */
+
+#ifdef SMART_DIMMING
+#include "ld9042_panel.h"
+#include "smart_dimming_ld9042.h"
+
+#define spidelay(nsecs) do {} while (0)
+
+#define GAMMA_PARAM_LEN 21
+
+#define LD9040_ID3 0x11
+#define LDI_ID_REG 0xDA
+#define LDI_ID_LEN 3
+#define LDI_MTP_REG 0xD6
+#define LDI_MTP_LEN 18
+
+#define ELVSS_OFFSET_MIN 0x0D
+#define ELVSS_OFFSET_1 0x0C
+#define ELVSS_OFFSET_2 0x09
+#define ELVSS_OFFSET_MAX 0x00
+#define ELVSS_LIMIT 0x29
+#endif
+
+struct lcd_info {
+ struct device *dev;
+ struct spi_device *spi;
+ unsigned int power;
+ unsigned int gamma_mode;
+ unsigned int current_gamma_mode;
+ unsigned int current_bl;
+ unsigned int bl;
+ unsigned int auto_brightness;
+ unsigned int ldi_enable;
+ unsigned int acl_enable;
+ unsigned int cur_acl;
+ struct mutex lock;
+ struct mutex bl_lock;
+ struct lcd_device *ld;
+ struct backlight_device *bd;
+ struct lcd_platform_data *lcd_pd;
+ struct early_suspend early_suspend;
+
+#ifdef SMART_DIMMING
+ unsigned char id[3];
+ struct str_smart_dim smart;
+#endif
+};
+
+enum {
+ GAMMA_30CD,
+ GAMMA_40CD,
+ GAMMA_70CD,
+ GAMMA_90CD,
+ GAMMA_100CD,
+ GAMMA_110CD,
+ GAMMA_120CD,
+ GAMMA_130CD,
+ GAMMA_140CD,
+ GAMMA_150CD,
+ GAMMA_160CD,
+ GAMMA_170CD,
+ GAMMA_180CD,
+ GAMMA_190CD,
+ GAMMA_200CD,
+ GAMMA_210CD,
+ GAMMA_220CD,
+ GAMMA_230CD,
+ GAMMA_240CD,
+ GAMMA_250CD,
+ GAMMA_300CD,
+ GAMMA_MAX
+};
+
+static const unsigned int candela_table[GAMMA_MAX] = {
+ 30, 40, 70, 90, 100, 110, 120, 130, 140, 150,
+ 160, 170, 180, 190, 200, 210, 220, 230, 240, 250,
+ 300
+};
+
+static int ld9040_spi_write_byte(struct lcd_info *lcd, int addr, int data)
+{
+ u16 buf[1];
+ struct spi_message msg;
+
+ struct spi_transfer xfer = {
+ .len = 2,
+ .tx_buf = buf,
+ };
+
+ buf[0] = (addr << 8) | data;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ return spi_sync(lcd->spi, &msg);
+}
+
+#ifdef SMART_DIMMING
+static int ld9040_spi_read(struct lcd_info *lcd, unsigned int addr,
+ unsigned char *buf, unsigned int len, unsigned int dummy_bit)
+{
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+ struct spi_ops *ops = pdata->ops;
+ unsigned int bits;
+
+ int i;
+ int j;
+
+ bits = lcd->spi->bits_per_word - 1;
+
+ mutex_lock(&lcd->lock);
+
+ ops->setcs(0);
+ spidelay(0);
+
+ for (j = bits; j >= 0; j--) {
+ ops->setsck(0);
+ spidelay(0);
+
+ ops->setmosi((addr >> j) & 1);
+ spidelay(0);
+
+ ops->setsck(1);
+ spidelay(0);
+ }
+
+ ops->setmosi2miso(1); /* SDI as input */
+ spidelay(0);
+
+ for (j = 0; j < dummy_bit; j++) {
+ ops->setsck(0);
+ spidelay(0);
+
+ ops->setsck(1);
+ spidelay(0);
+
+ ops->getmiso();
+ spidelay(0);
+ }
+
+ for (i = 0; i < len; i++) {
+ for (j = bits - 1; j >= 0; j--) {
+ ops->setsck(0);
+ spidelay(0);
+
+ ops->setsck(1);
+ spidelay(0);
+
+ buf[i] |= (unsigned char)(ops->getmiso() << j);
+ spidelay(0);
+ }
+ /* printk(KERN_INFO "0x%x, %d\n", buf[i], buf[i]); */
+ }
+
+ ops->setcs(1);
+ spidelay(0);
+
+ ops->setmosi2miso(0); /* SDI as output */
+ spidelay(0);
+
+ mutex_unlock(&lcd->lock);
+
+ return 0;
+}
+
+static int ld9040_read_id(struct lcd_info *lcd, unsigned int addr)
+{
+ unsigned char buf[1] = {0};
+
+ ld9040_spi_read(lcd, addr, buf, 1, 8);
+
+ return *buf;
+}
+
+static int spi_read_multi_byte(struct lcd_info *lcd,
+ unsigned int addr, unsigned char *buf, unsigned int len)
+{
+ if (len == 1)
+ ld9040_spi_read(lcd, addr, buf, len, 8);
+ else
+ ld9040_spi_read(lcd, addr, buf, len, 1);
+
+ return 0;
+}
+
+static void ld9042_init_smart_dimming_table_22(struct lcd_info *lcd)
+{
+ unsigned int i, j;
+ unsigned char gamma_22[GAMMA_PARAM_LEN] = {0,};
+
+ for (i = 0; i < GAMMA_MAX; i++) {
+ calc_gamma_table_22(&lcd->smart, candela_table[i], gamma_22);
+ for (j = 0; j < GAMMA_PARAM_LEN; j++)
+ ld9042_22gamma_table[i][j*2+1] = gamma_22[j];
+ }
+}
+
+static void ld9042_init_smart_dimming_table_19(struct lcd_info *lcd)
+{
+ unsigned int i, j;
+ unsigned char gamma_19[GAMMA_PARAM_LEN] = {0,};
+
+ for (i = 0; i < GAMMA_MAX; i++) {
+ calc_gamma_table_19(&lcd->smart, candela_table[i], gamma_19);
+ for (j = 0; j < GAMMA_PARAM_LEN; j++)
+ ld9042_19gamma_table[i][j*2+1] = gamma_19[j];
+ }
+}
+
+static void ld9042_init_smart_elvss_table(struct lcd_info *lcd)
+{
+ unsigned int i, j;
+ unsigned char elvss, b2;
+
+ elvss = lcd->id[2] & (~(BIT(6) | BIT(7)));
+
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ b2 = elvss + ELVSS_TABLE[i][j*2+1];
+ b2 = (b2 > ELVSS_LIMIT) ? ELVSS_LIMIT : b2;
+ ELVSS_TABLE[i][j*2+1] = b2;
+ }
+ }
+
+}
+#endif
+
+static int ld9040_spi_write(struct lcd_info *lcd,
+ unsigned char address, unsigned char command)
+{
+ int ret = 0;
+
+ if (address != DATA_ONLY)
+ ret = ld9040_spi_write_byte(lcd, 0x0, address);
+ if (command != COMMAND_ONLY)
+ ret = ld9040_spi_write_byte(lcd, 0x1, command);
+
+ return ret;
+}
+
+static int ld9040_panel_send_sequence(struct lcd_info *lcd,
+ const unsigned short *seq)
+{
+ int ret = 0, i = 0;
+ const unsigned short *wbuf;
+
+ mutex_lock(&lcd->lock);
+
+ wbuf = seq;
+
+ while ((wbuf[i] & DEFMASK) != ENDDEF) {
+ if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
+ ret = ld9040_spi_write(lcd, wbuf[i], wbuf[i+1]);
+ if (ret)
+ break;
+ } else if ((wbuf[i] & DEFMASK) == SLEEPMSEC)
+ msleep(wbuf[i+1]);
+ i += 2;
+ }
+
+ mutex_unlock(&lcd->lock);
+
+ return ret;
+}
+
+static int get_backlight_level_from_brightness(unsigned int brightness)
+{
+ int backlightlevel;
+
+ /* brightness setting from platform is from 0 to 255
+ * But in this driver, brightness is only supported from 0 to 24 */
+
+ switch (brightness) {
+ case 0 ... 29:
+ backlightlevel = GAMMA_30CD;
+ break;
+ case 30 ... 39:
+ backlightlevel = GAMMA_30CD;
+ break;
+ case 40 ... 49:
+ backlightlevel = GAMMA_40CD;
+ break;
+ case 50 ... 69:
+ backlightlevel = GAMMA_40CD;
+ break;
+ case 70 ... 79:
+ backlightlevel = GAMMA_70CD;
+ break;
+ case 80 ... 89:
+ backlightlevel = GAMMA_70CD;
+ break;
+ case 90 ... 99:
+ backlightlevel = GAMMA_90CD;
+ break;
+ case 100 ... 109:
+ backlightlevel = GAMMA_100CD;
+ break;
+ case 110 ... 119:
+ backlightlevel = GAMMA_110CD;
+ break;
+ case 120 ... 129:
+ backlightlevel = GAMMA_120CD;
+ break;
+ case 130 ... 139:
+ backlightlevel = GAMMA_130CD;
+ break;
+ case 140 ... 149:
+ backlightlevel = GAMMA_140CD;
+ break;
+ case 150 ... 159:
+ backlightlevel = GAMMA_150CD;
+ break;
+ case 160 ... 169:
+ backlightlevel = GAMMA_160CD;
+ break;
+ case 170 ... 179:
+ backlightlevel = GAMMA_170CD;
+ break;
+ case 180 ... 189:
+ backlightlevel = GAMMA_180CD;
+ break;
+ case 190 ... 199:
+ backlightlevel = GAMMA_190CD;
+ break;
+ case 200 ... 209:
+ backlightlevel = GAMMA_200CD;
+ break;
+ case 210 ... 219:
+ backlightlevel = GAMMA_210CD;
+ break;
+ case 220 ... 229:
+ backlightlevel = GAMMA_220CD;
+ break;
+ case 230 ... 239:
+ backlightlevel = GAMMA_230CD;
+ break;
+ case 240 ... 249:
+ backlightlevel = GAMMA_240CD;
+ break;
+ case 250 ... 254:
+ backlightlevel = GAMMA_250CD;
+ break;
+ case 255:
+ backlightlevel = GAMMA_300CD;
+ break;
+ default:
+ backlightlevel = GAMMA_160CD;
+ break;
+ }
+ return backlightlevel;
+}
+
+static int ld9040_gamma_ctl(struct lcd_info *lcd)
+{
+ int ret = 0;
+ const unsigned short *gamma;
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+
+ if (lcd->gamma_mode)
+ gamma = pdata->gamma19_table[lcd->bl];
+ else
+ gamma = pdata->gamma22_table[lcd->bl];
+
+ ret = ld9040_panel_send_sequence(lcd, gamma);
+ if (ret) {
+ ret = -EPERM;
+ goto gamma_err;
+ }
+
+ lcd->current_gamma_mode = lcd->gamma_mode;
+gamma_err:
+ return ret;
+}
+
+static int ld9040_set_elvss(struct lcd_info *lcd)
+{
+ int ret = 0;
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+
+ if (lcd->acl_enable) {
+ switch (lcd->bl) {
+ case GAMMA_30CD ... GAMMA_200CD: /* 30cd ~ 200cd */
+ ret = ld9040_panel_send_sequence(lcd, pdata->elvss_table[0]);
+ break;
+ case GAMMA_210CD ... GAMMA_300CD: /* 210cd ~ 300cd */
+ ret = ld9040_panel_send_sequence(lcd, pdata->elvss_table[1]);
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (lcd->bl) {
+ case GAMMA_30CD ... GAMMA_100CD: /* 30cd ~ 100cd */
+ ret = ld9040_panel_send_sequence(lcd, pdata->elvss_table[0]);
+ break;
+ case GAMMA_110CD ... GAMMA_160CD: /* 110cd ~ 160cd */
+ ret = ld9040_panel_send_sequence(lcd, pdata->elvss_table[1]);
+ break;
+ case GAMMA_170CD ... GAMMA_200CD: /* 170cd ~ 200cd */
+ ret = ld9040_panel_send_sequence(lcd, pdata->elvss_table[2]);
+ break;
+ case GAMMA_210CD ... GAMMA_300CD: /* 210cd ~ 300cd */
+ ret = ld9040_panel_send_sequence(lcd, pdata->elvss_table[3]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ dev_dbg(&lcd->ld->dev, "level = %d\n", lcd->bl);
+
+ if (ret) {
+ ret = -EPERM;
+ goto elvss_err;
+ }
+
+elvss_err:
+ return ret;
+}
+
+static int ld9040_set_acl(struct lcd_info *lcd)
+{
+ int ret = 0;
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+
+ if (lcd->acl_enable) {
+ if (lcd->cur_acl == 0) {
+ if (lcd->bl == 0 || lcd->bl == 1) {
+ ret = ld9040_panel_send_sequence(lcd, pdata->acl_table[0]);
+ dev_dbg(&lcd->ld->dev, "%s : cur_acl=%d, acl_off\n", __func__, lcd->cur_acl);
+ } else {
+ ret = ld9040_panel_send_sequence(lcd, pdata->acl_on);
+ dev_dbg(&lcd->ld->dev, "%s : cur_acl=%d, acl_on\n", __func__, lcd->cur_acl);
+ }
+ }
+ switch (lcd->bl) {
+ case GAMMA_30CD ... GAMMA_40CD: /* 30cd ~ 40cd */
+ if (lcd->cur_acl != 0) {
+ ret = ld9040_panel_send_sequence(lcd, pdata->acl_table[0]);
+ lcd->cur_acl = 0;
+ dev_dbg(&lcd->ld->dev, "%s : cur_acl=%d\n", __func__, lcd->cur_acl);
+ }
+ break;
+ case GAMMA_70CD ... GAMMA_250CD: /* 70cd ~ 250cd */
+ if (lcd->cur_acl != 40) {
+ ret = ld9040_panel_send_sequence(lcd, pdata->acl_table[1]);
+ lcd->cur_acl = 40;
+ dev_dbg(&lcd->ld->dev, "%s : cur_acl=%d\n", __func__, lcd->cur_acl);
+ }
+ break;
+ default:
+ if (lcd->cur_acl != 50) {
+ ret = ld9040_panel_send_sequence(lcd, pdata->acl_table[2]);
+ lcd->cur_acl = 50;
+ dev_dbg(&lcd->ld->dev, "%s : cur_acl=%d\n", __func__, lcd->cur_acl);
+ }
+ break;
+ }
+ } else {
+ ret = ld9040_panel_send_sequence(lcd, pdata->acl_table[0]);
+ lcd->cur_acl = 0;
+ dev_dbg(&lcd->ld->dev, "%s : cur_acl=%d\n", __func__, lcd->cur_acl);
+ }
+
+ if (ret) {
+ ret = -EPERM;
+ goto acl_err;
+ }
+
+acl_err:
+ return ret;
+}
+
+static int ld9040_ldi_init(struct lcd_info *lcd)
+{
+ int ret, i;
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+ const unsigned short *init_seq[] = {
+ pdata->seq_user_set,
+ pdata->seq_displayctl_set,
+ pdata->seq_gtcon_set,
+ pdata->seq_panelcondition_set,
+ pdata->acl_table[0],
+ pdata->sleep_out,
+ pdata->elvss_on,
+ pdata->seq_pwrctl_set,
+ pdata->gamma22_table[0],
+ };
+ for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
+ ret = ld9040_panel_send_sequence(lcd, init_seq[i]);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static int ld9040_ldi_enable(struct lcd_info *lcd)
+{
+ int ret = 0;
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+
+ ret = ld9040_panel_send_sequence(lcd, pdata->display_on);
+
+ return ret;
+}
+
+static int ld9040_ldi_disable(struct lcd_info *lcd)
+{
+ int ret;
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+
+ lcd->ldi_enable = 0;
+ ret = ld9040_panel_send_sequence(lcd, pdata->display_off);
+ ret = ld9040_panel_send_sequence(lcd, pdata->sleep_in);
+
+ return ret;
+}
+
+static int update_brightness(struct lcd_info *lcd, u8 force)
+{
+ int ret = 0, brightness;
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+
+ mutex_lock(&lcd->bl_lock);
+
+ brightness = lcd->bd->props.brightness;
+
+ if (unlikely(!lcd->auto_brightness && brightness > 250))
+ brightness = 250;
+
+ lcd->bl = get_backlight_level_from_brightness(brightness);
+
+ if ((force) || ((lcd->ldi_enable) && (lcd->current_bl != lcd->bl))) {
+
+ ret = ld9040_gamma_ctl(lcd);
+
+ ret |= ld9040_set_acl(lcd);
+
+ ret |= ld9040_set_elvss(lcd);
+
+ lcd->current_bl = lcd->bl;
+
+ dev_info(&lcd->ld->dev, "id=%d brightness=%d, bl=%d, candela=%d\n", pdata->lcdtype, brightness, lcd->bl, candela_table[lcd->bl]);
+ }
+
+ mutex_unlock(&lcd->bl_lock);
+
+ return ret;
+}
+
+
+static int ld9040_power_on(struct lcd_info *lcd)
+{
+ int ret = 0;
+ struct lcd_platform_data *pd = NULL;
+ pd = lcd->lcd_pd;
+
+ dev_info(&lcd->ld->dev, "%s\n", __func__);
+
+ if (!pd) {
+ dev_err(&lcd->ld->dev, "platform data is NULL.\n");
+ return -EFAULT;
+ }
+
+ if (!pd->power_on) {
+ dev_err(&lcd->ld->dev, "power_on is NULL.\n");
+ return -EFAULT;
+ } else {
+ pd->power_on(lcd->ld, 1);
+ msleep(pd->power_on_delay);
+ }
+
+ if (!pd->reset) {
+ dev_err(&lcd->ld->dev, "reset is NULL.\n");
+ return -EFAULT;
+ } else {
+ pd->reset(lcd->ld);
+ msleep(pd->reset_delay);
+ }
+
+ ret = ld9040_ldi_init(lcd);
+
+ if (ret) {
+ dev_err(&lcd->ld->dev, "failed to initialize ldi.\n");
+ goto err;
+ }
+
+ ret = ld9040_ldi_enable(lcd);
+ if (ret) {
+ dev_err(&lcd->ld->dev, "failed to enable ldi.\n");
+ goto err;
+ }
+
+ lcd->ldi_enable = 1;
+
+ update_brightness(lcd, 1);
+
+err:
+ return ret;
+}
+
+static int ld9040_power_off(struct lcd_info *lcd)
+{
+ int ret = 0;
+ struct lcd_platform_data *pd = NULL;
+
+ dev_info(&lcd->ld->dev, "%s\n", __func__);
+
+ pd = lcd->lcd_pd;
+ if (!pd) {
+ dev_err(&lcd->ld->dev, "platform data is NULL.\n");
+ return -EFAULT;
+ }
+
+ ret = ld9040_ldi_disable(lcd);
+ if (ret) {
+ dev_err(&lcd->ld->dev, "lcd setting failed.\n");
+ ret = -EIO;
+ goto err;
+ }
+
+ if (!pd->gpio_cfg_earlysuspend) {
+ dev_err(&lcd->ld->dev, "gpio_cfg_earlysuspend is NULL.\n");
+ ret = -EFAULT;
+ goto err;
+ } else
+ pd->gpio_cfg_earlysuspend(lcd->ld);
+
+ if (!pd->power_on) {
+ dev_err(&lcd->ld->dev, "power_on is NULL.\n");
+ ret = -EFAULT;
+ goto err;
+ } else {
+ msleep(pd->power_off_delay);
+ pd->power_on(lcd->ld, 0);
+ }
+
+err:
+ return ret;
+}
+
+static int ld9040_power(struct lcd_info *lcd, int power)
+{
+ int ret = 0;
+
+ if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
+ ret = ld9040_power_on(lcd);
+ else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
+ ret = ld9040_power_off(lcd);
+
+ if (!ret)
+ lcd->power = power;
+
+ return ret;
+}
+
+static int ld9040_set_power(struct lcd_device *ld, int power)
+{
+ struct lcd_info *lcd = lcd_get_data(ld);
+
+ if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
+ power != FB_BLANK_NORMAL) {
+ dev_err(&lcd->ld->dev, "power value should be 0, 1 or 4.\n");
+ return -EINVAL;
+ }
+
+ return ld9040_power(lcd, power);
+}
+
+static int ld9040_get_power(struct lcd_device *ld)
+{
+ struct lcd_info *lcd = lcd_get_data(ld);
+
+ return lcd->power;
+}
+
+static int ld9040_get_brightness(struct backlight_device *bd)
+{
+ struct lcd_info *lcd = bl_get_data(bd);
+
+ return candela_table[lcd->bl];
+}
+
+static int ld9040_set_brightness(struct backlight_device *bd)
+{
+ int ret = 0, brightness = bd->props.brightness;
+ struct lcd_info *lcd = bl_get_data(bd);
+
+ if (brightness < MIN_BRIGHTNESS ||
+ brightness > bd->props.max_brightness) {
+ dev_err(&bd->dev, "lcd brightness should be %d to %d. now %d\n",
+ MIN_BRIGHTNESS, MAX_BRIGHTNESS, brightness);
+ return -EINVAL;
+ }
+
+ if (lcd->ldi_enable) {
+ ret = update_brightness(lcd, 0);
+ if (ret < 0)
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static struct lcd_ops ld9040_lcd_ops = {
+ .set_power = ld9040_set_power,
+ .get_power = ld9040_get_power,
+};
+
+static const struct backlight_ops ld9040_backlight_ops = {
+ .get_brightness = ld9040_get_brightness,
+ .update_status = ld9040_set_brightness,
+};
+
+static ssize_t gamma_table_show(struct device *dev, struct
+device_attribute *attr, char *buf)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+ const unsigned short *wbuf;
+ int i, j;
+
+ for (i = 0; i < GAMMA_MAX; i++) {
+ wbuf = pdata->gamma22_table[i];
+ j = 1;
+ while ((wbuf[j] & DEFMASK) != ENDDEF) {
+ if ((wbuf[j] & DEFMASK) != SLEEPMSEC) {
+ if (wbuf[j] != DATA_ONLY)
+ printk("0x%02x, ", wbuf[j]);
+ }
+ j++;
+ }
+ printk("\n");
+ }
+
+ for (i = 0; i < GAMMA_MAX; i++) {
+ wbuf = pdata->gamma19_table[i];
+ j = 1;
+ while ((wbuf[j] & DEFMASK) != ENDDEF) {
+ if ((wbuf[j] & DEFMASK) != SLEEPMSEC) {
+ if (wbuf[j] != DATA_ONLY)
+ printk("0x%02x, ", wbuf[j]);
+ }
+ j++;
+ }
+ printk("\n");
+ }
+
+#ifdef SMART_DIMMING
+ for (i = 0; i < 4; i++) {
+ wbuf = pdata->elvss_table[i];
+ j = 1;
+ while ((wbuf[j] & DEFMASK) != ENDDEF) {
+ if ((wbuf[j] & DEFMASK) != SLEEPMSEC) {
+ if (wbuf[j] != DATA_ONLY)
+ printk("0x%02x, ", wbuf[j]);
+ }
+ j++;
+ }
+ printk("\n");
+ }
+#endif
+
+ return strlen(buf);
+}
+static DEVICE_ATTR(gamma_table, 0444, gamma_table_show, NULL);
+
+static ssize_t acl_set_show(struct device *dev, struct
+device_attribute *attr, char *buf)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ char temp[3];
+
+ sprintf(temp, "%d\n", lcd->acl_enable);
+ strcpy(buf, temp);
+
+ return strlen(buf);
+}
+static ssize_t acl_set_store(struct device *dev, struct
+device_attribute *attr, const char *buf, size_t size)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ int value;
+ int rc;
+
+ rc = strict_strtoul(buf, (unsigned int)0, (unsigned long *)&value);
+ if (rc < 0)
+ return rc;
+ else {
+ if (lcd->acl_enable != value) {
+ dev_info(&lcd->ld->dev, "%s - %d, %d\n", __func__, lcd->acl_enable, value);
+ mutex_lock(&lcd->bl_lock);
+ lcd->acl_enable = value;
+ if (lcd->ldi_enable)
+ ld9040_set_acl(lcd);
+ mutex_unlock(&lcd->bl_lock);
+ }
+ }
+ return size;
+}
+
+static DEVICE_ATTR(power_reduce, 0664,
+ acl_set_show, acl_set_store);
+
+static ssize_t lcdtype_show(struct device *dev, struct
+device_attribute *attr, char *buf)
+{
+
+ char temp[15];
+ sprintf(temp, "SMD_AMS427G03\n");
+ strcat(buf, temp);
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(lcd_type, 0664,
+ lcdtype_show, NULL);
+
+static ssize_t octa_lcdtype_show(struct device *dev, struct
+device_attribute *attr, char *buf)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ struct ld9040_panel_data *pdata = lcd->lcd_pd->pdata;
+ char temp[15];
+
+ switch (pdata->lcdtype) {
+ case LCDTYPE_SM2_A1:
+ sprintf(temp, "OCTA : SM2 (A1 line)\n");
+ strcat(buf, temp);
+ break;
+ case LCDTYPE_SM2_A2:
+ sprintf(temp, "OCTA : SM2 (A2 line)\n");
+ strcat(buf, temp);
+ break;
+ case LCDTYPE_M2:
+ sprintf(temp, "OCTA : M2\n");
+ strcat(buf, temp);
+ break;
+ default:
+ sprintf(temp, "error\n");
+ strcat(buf, temp);
+ dev_info(&lcd->ld->dev, "read octa lcd type failed.\n");
+ break;
+ }
+ return strlen(buf);
+
+}
+
+static DEVICE_ATTR(octa_lcdtype, 0664,
+ octa_lcdtype_show, NULL);
+
+static ssize_t ld9040_sysfs_show_gamma_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ char temp[10];
+
+ switch (lcd->gamma_mode) {
+ case 0:
+ sprintf(temp, "2.2 mode\n");
+ strcat(buf, temp);
+ break;
+ case 1:
+ sprintf(temp, "1.9 mode\n");
+ strcat(buf, temp);
+ break;
+ default:
+ dev_info(&lcd->ld->dev, "gamma mode should be 0:2.2, 1:1.9)\n");
+ break;
+ }
+
+ return strlen(buf);
+}
+
+static ssize_t ld9040_sysfs_store_gamma_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ int value;
+ int rc;
+
+ rc = strict_strtoul(buf, 0, (unsigned long *)&value);
+
+ if (rc < 0)
+ return rc;
+
+ if (value > 1) {
+ dev_err(dev, "there are only 2 types of gamma mode(0:2.2, 1:1.9)\n");
+ return len;
+ } else
+ dev_info(dev, "%s :: gamma_mode=%d\n", __func__, value);
+
+ if (lcd->ldi_enable) {
+ if ((lcd->current_bl == lcd->bl) && (lcd->current_gamma_mode == value))
+ dev_err(&lcd->ld->dev, "gamma_mode & brightness are same\n");
+ else {
+ mutex_lock(&lcd->bl_lock);
+ lcd->gamma_mode = value;
+ ld9040_gamma_ctl(lcd);
+ mutex_unlock(&lcd->bl_lock);
+ }
+ }
+ return len;
+}
+
+static DEVICE_ATTR(gamma_mode, 0664,
+ ld9040_sysfs_show_gamma_mode, ld9040_sysfs_store_gamma_mode);
+
+static ssize_t auto_brightness_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ char temp[3];
+
+ sprintf(temp, "%d\n", lcd->auto_brightness);
+ strcpy(buf, temp);
+
+ return strlen(buf);
+}
+
+static ssize_t auto_brightness_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct lcd_info *lcd = dev_get_drvdata(dev);
+ int value;
+ int rc;
+
+ rc = strict_strtoul(buf, (unsigned int)0, (unsigned long *)&value);
+ if (rc < 0)
+ return rc;
+ else {
+ if (lcd->auto_brightness != value) {
+ dev_info(dev, "%s - %d, %d\n", __func__, lcd->auto_brightness, value);
+ mutex_lock(&lcd->bl_lock);
+ lcd->auto_brightness = value;
+ mutex_unlock(&lcd->bl_lock);
+ if (lcd->ldi_enable)
+ update_brightness(lcd, 0);
+ }
+ }
+ return size;
+}
+
+static DEVICE_ATTR(auto_brightness, 0644, auto_brightness_show, auto_brightness_store);
+
+#if defined(CONFIG_PM)
+#ifdef CONFIG_HAS_EARLYSUSPEND
+void ld9040_early_suspend(struct early_suspend *h)
+{
+ struct lcd_info *lcd = container_of(h, struct lcd_info ,
+ early_suspend);
+ dev_info(&lcd->ld->dev, "+%s\n", __func__);
+ ld9040_power(lcd, FB_BLANK_POWERDOWN);
+ dev_info(&lcd->ld->dev, "-%s\n", __func__);
+
+ return ;
+}
+
+void ld9040_late_resume(struct early_suspend *h)
+{
+ struct lcd_info *lcd = container_of(h, struct lcd_info ,
+ early_suspend);
+ dev_info(&lcd->ld->dev, "+%s\n", __func__);
+ ld9040_power(lcd, FB_BLANK_UNBLANK);
+ dev_info(&lcd->ld->dev, "-%s\n", __func__);
+
+ return ;
+}
+#endif
+#endif
+
+static int ld9040_probe(struct spi_device *spi)
+{
+ int ret = 0;
+ struct lcd_info *lcd;
+ struct ld9040_panel_data *pdata;
+#ifdef SMART_DIMMING
+ unsigned int i;
+ u8 mtp_data[LDI_MTP_LEN] = {0,};
+#endif
+
+ lcd = kzalloc(sizeof(struct lcd_info), GFP_KERNEL);
+ if (!lcd) {
+ pr_err("failed to allocate for lcd\n");
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* ld9040 lcd panel uses 3-wire 9bits SPI Mode. */
+ spi->bits_per_word = 9;
+
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(&spi->dev, "spi setup failed.\n");
+ goto out_free_lcd;
+ }
+
+ lcd->spi = spi;
+ lcd->dev = &spi->dev;
+
+ lcd->lcd_pd = (struct lcd_platform_data *)spi->dev.platform_data;
+ if (!lcd->lcd_pd) {
+ dev_err(&spi->dev, "platform data is NULL.\n");
+ goto out_free_lcd;
+ }
+
+ pdata = lcd->lcd_pd->pdata;
+ if (IS_ERR_OR_NULL(pdata)) {
+ dev_err(&spi->dev, "panel data is NULL.\n");
+ goto out_free_lcd;
+ }
+
+ lcd->ld = lcd_device_register("panel", &spi->dev,
+ lcd, &ld9040_lcd_ops);
+ if (IS_ERR(lcd->ld)) {
+ ret = PTR_ERR(lcd->ld);
+ goto out_free_lcd;
+ }
+
+ lcd->bd = backlight_device_register("panel", &spi->dev,
+ lcd, &ld9040_backlight_ops, NULL);
+ if (IS_ERR(lcd->bd)) {
+ ret = PTR_ERR(lcd->bd);
+ goto out_free_backlight;
+ }
+
+ lcd->bd->props.max_brightness = MAX_BRIGHTNESS;
+ lcd->bd->props.brightness = candela_table[GAMMA_160CD];
+ lcd->bl = GAMMA_160CD;
+ lcd->current_bl = lcd->bl;
+ lcd->gamma_mode = 0;
+ lcd->current_gamma_mode = 0;
+
+ lcd->acl_enable = 0;
+ lcd->cur_acl = 0;
+
+ lcd->auto_brightness = 1;
+
+ ret = device_create_file(&lcd->ld->dev, &dev_attr_gamma_mode);
+ if (ret < 0)
+ dev_err(&lcd->ld->dev, "failed to add sysfs entries\n");
+
+ ret = device_create_file(&lcd->ld->dev, &dev_attr_power_reduce);
+ if (ret < 0)
+ dev_err(&lcd->ld->dev, "failed to add sysfs entries\n");
+
+ ret = device_create_file(&lcd->ld->dev, &dev_attr_lcd_type);
+ if (ret < 0)
+ dev_err(&lcd->ld->dev, "failed to add sysfs entries\n");
+
+ ret = device_create_file(&lcd->ld->dev, &dev_attr_octa_lcdtype);
+ if (ret < 0)
+ dev_err(&lcd->ld->dev, "failed to add sysfs entries\n");
+
+ ret = device_create_file(&lcd->ld->dev, &dev_attr_gamma_table);
+ if (ret < 0)
+ dev_err(&lcd->ld->dev, "failed to add sysfs entries\n");
+
+ ret = device_create_file(&lcd->bd->dev, &dev_attr_auto_brightness);
+ if (ret < 0)
+ dev_err(&lcd->ld->dev, "failed to add sysfs entries\n");
+
+ mutex_init(&lcd->lock);
+ mutex_init(&lcd->bl_lock);
+
+ /*
+ * if lcd panel was on from bootloader like u-boot then
+ * do not lcd on.
+ */
+ if (!lcd->lcd_pd->lcd_enabled) {
+ /*
+ * if lcd panel was off from bootloader then
+ * current lcd status is powerdown and then
+ * it enables lcd panel.
+ */
+ lcd->power = FB_BLANK_POWERDOWN;
+
+ ld9040_power(lcd, FB_BLANK_UNBLANK);
+ } else {
+ lcd->power = FB_BLANK_UNBLANK;
+ lcd->ldi_enable = 1;
+ }
+
+ dev_set_drvdata(&spi->dev, lcd);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ lcd->early_suspend.suspend = ld9040_early_suspend;
+ lcd->early_suspend.resume = ld9040_late_resume;
+ lcd->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB - 1;
+ register_early_suspend(&lcd->early_suspend);
+#endif
+
+ if (pdata->lcdtype == LCDTYPE_M2)
+ dev_info(&lcd->ld->dev, "%s : lcdtype : M2\n", __func__);
+ else if (pdata->lcdtype == LCDTYPE_SM2_A1)
+ dev_info(&lcd->ld->dev, "%s : lcdtype : SM2_A1\n", __func__);
+ else if (pdata->lcdtype == LCDTYPE_SM2_A2)
+ dev_info(&lcd->ld->dev, "%s : lcdtype : SM2_A2\n", __func__);
+ else /* UNKNOWN */
+ dev_info(&lcd->ld->dev, "%s : lcdtype : Unknown(SM2_A1)\n", __func__);
+
+ dev_info(&lcd->ld->dev, "ld9040 panel driver has been probed.\n");
+
+#ifdef SMART_DIMMING
+ for (i = 0; i < LDI_ID_LEN; i++) {
+ lcd->id[i] = ld9040_read_id(lcd, LDI_ID_REG + i);
+ lcd->smart.panelid[i] = lcd->id[i];
+ }
+
+ if (lcd->id[2] == LD9040_ID3)
+ return 0;
+
+ /* prepare initial data to operate smart dimming */
+
+ printk(KERN_INFO "id: %x, %x, %x", lcd->id[0], lcd->id[1], lcd->id[2]);
+
+ init_table_info_22(&lcd->smart);
+ init_table_info_19(&lcd->smart);
+
+ spi_read_multi_byte(lcd, LDI_MTP_REG, mtp_data, LDI_MTP_LEN);
+
+ calc_voltage_table(&lcd->smart, mtp_data);
+
+#if 0
+ for (i = 0; i < LDI_MTP_LEN; i++)
+ printk(KERN_INFO "%d\n", mtp_data[i]);
+#endif
+
+ ld9042_init_smart_dimming_table_22(lcd);
+ ld9042_init_smart_dimming_table_19(lcd);
+ ld9042_init_smart_elvss_table(lcd);
+
+ pdata->elvss_table = (const unsigned short **)ELVSS_TABLE;
+ pdata->gamma19_table = (const unsigned short **)ld9042_19gamma_table;
+ pdata->gamma22_table = (const unsigned short **)ld9042_22gamma_table;
+
+ update_brightness(lcd, 1);
+#endif
+
+ return 0;
+
+out_free_backlight:
+ lcd_device_unregister(lcd->ld);
+ kfree(lcd);
+ return ret;
+out_free_lcd:
+ kfree(lcd);
+ return ret;
+err_alloc:
+ return ret;
+}
+
+static int __devexit ld9040_remove(struct spi_device *spi)
+{
+ struct lcd_info *lcd = dev_get_drvdata(&spi->dev);
+
+ ld9040_power(lcd, FB_BLANK_POWERDOWN);
+ lcd_device_unregister(lcd->ld);
+ backlight_device_unregister(lcd->bd);
+ kfree(lcd);
+
+ return 0;
+}
+
+static struct spi_driver ld9040_driver = {
+ .driver = {
+ .name = "ld9040",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = ld9040_probe,
+ .remove = __devexit_p(ld9040_remove),
+};
+
+static int __init ld9040_init(void)
+{
+ return spi_register_driver(&ld9040_driver);
+}
+
+static void __exit ld9040_exit(void)
+{
+ spi_unregister_driver(&ld9040_driver);
+}
+
+module_init(ld9040_init);
+module_exit(ld9040_exit);
+
+MODULE_DESCRIPTION("LD9040 AMOLED LCD Driver");
+MODULE_LICENSE("GPL");
+