diff options
Diffstat (limited to 'drivers/video/backlight/s6d6aa1.c')
-rw-r--r-- | drivers/video/backlight/s6d6aa1.c | 976 |
1 files changed, 976 insertions, 0 deletions
diff --git a/drivers/video/backlight/s6d6aa1.c b/drivers/video/backlight/s6d6aa1.c new file mode 100644 index 0000000..15a0b63 --- /dev/null +++ b/drivers/video/backlight/s6d6aa1.c @@ -0,0 +1,976 @@ +/* linux/drivers/video/backlight/s6d6aa1.c + * + * MIPI-DSI based s6d6aa1 TFT-LCD 4.77 inch panel driver. + * + * Joongmock Shin <jmock.shin@samsung.com> + * Eunchul Kim <chulspro.kim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/ctype.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/lcd.h> +#include <linux/lcd-property.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/regulator/consumer.h> +#include <linux/firmware.h> +#include <video/mipi_display.h> + +#if defined(CONFIG_ARM_EXYNOS4_DISPLAY_DEVFREQ) || defined(CONFIG_DISPFREQ_OPP) +#include <linux/devfreq/exynos4_display.h> +#endif + +#include <plat/mipi_dsim2.h> +#include "s6d6aa1.h" + +#define VER_16 (0x10) /* MACH_SLP_REDWOORD */ +#define LDI_FW_PATH "s6d6aa1/reg_%s.bin" +#define MAX_STR 255 +#define MAX_READ_LENGTH 64 +#define MIN_BRIGHTNESS (0) +#define MAX_BRIGHTNESS (0xff) +#define DSCTL_VFLIP (1 << 7) +#define DSCTL_HFLIP (1 << 6) + +/* + * FIXME:!!simple init vs full init + * If lcd don't working simple sequence, + * we use full initialization sequence + */ +#define SIMPLE_INIT + +#define POWER_IS_ON(pwr) ((pwr) == FB_BLANK_UNBLANK) +#define POWER_IS_OFF(pwr) ((pwr) == FB_BLANK_POWERDOWN) +#define POWER_IS_NRM(pwr) ((pwr) == FB_BLANK_NORMAL) + +#define lcd_to_master(a) (a->dsim_dev->master) +#define lcd_to_master_ops(a) ((lcd_to_master(a))->master_ops) + +/* white magic mode */ +enum wm_mode { + WM_MODE_MIN = 0x00, + WM_MODE_NORMAL = WM_MODE_MIN, + WM_MODE_CONSERVATIVE, + WM_MODE_MEDIUM, + WM_MODE_AGGRESSIVE, + WM_MODE_OUTDOOR, + WM_MODE_MAX = WM_MODE_OUTDOOR +}; + +struct panel_model { + int ver; + char *name; +}; + +struct s6d6aa1 { + struct device *dev; + struct lcd_device *ld; + struct backlight_device *bd; + struct mipi_dsim_lcd_device *dsim_dev; + struct lcd_platform_data *ddi_pd; + struct lcd_property *property; + + struct regulator *reg_vddi; + struct regulator *reg_vdd; + +#if defined(CONFIG_ARM_EXYNOS4_DISPLAY_DEVFREQ) || defined(CONFIG_DISPFREQ_OPP) + struct notifier_block nb_disp; +#endif + struct mutex lock; + + unsigned int ver; + unsigned int power; + enum wm_mode wm_mode; + unsigned int cur_addr; + + const struct panel_model *model; + unsigned int model_count; +}; + +static void s6d6aa1_delay(unsigned int msecs) +{ + /* refer from documentation/timers/timers-howto.txt */ + if (msecs < 20) + usleep_range(msecs*1000, (msecs+1)*1000); + else + msleep(msecs); +} + +static void s6d6aa1_sleep_in(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, 0x10, 0x00); +} + +static void s6d6aa1_sleep_out(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, 0x11, 0x00); +} + +static void s6d6aa1_apply_level_1_key(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send[] = { + 0xF0, 0x5A, 0x5A + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6d6aa1_apply_level_2_key(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send[] = { + 0xF1, 0x5A, 0x5A + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6d6aa1_read_id(struct s6d6aa1 *lcd, u8 *mtp_id) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_read(lcd_to_master(lcd), + MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM, + 0xDA, 1, &mtp_id[0]); + ops->cmd_read(lcd_to_master(lcd), + MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM, + 0xDB, 1, &mtp_id[1]); + ops->cmd_read(lcd_to_master(lcd), + MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM, + 0xDC, 1, &mtp_id[2]); +} + +static void s6d6aa1_write_ddb(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send[] = { + 0xB4, 0x59, 0x10, 0x10, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6d6aa1_bcm_mode(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0xC1, 0x03); +} + +static void s6d6aa1_wrbl_ctl(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send[] = { + 0xC3, 0x7C, 0x00, 0x22 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6d6aa1_sony_ip_setting(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send1[] = { + 0xC4, 0x72, 0xFF, 0x72, 0xFF, 0x72, 0xFF, 0x72, 0x72, + 0x05, 0x0F, 0x1F, 0x01, 0x00, 0x00 + }; + const unsigned char data_to_send2[] = { + 0xC5, 0x80, 0x80, 0x80, 0x60, 0x4E, 0x36, 0x83, 0x85, + 0x01, 0xFF, 0x20, 0x40, 0x50 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send1, ARRAY_SIZE(data_to_send1)); + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send2, ARRAY_SIZE(data_to_send2)); +} + +/* + * FIXME:!!simple init vs full init + * If lcd don't working simple sequence, + * we use full initialization sequence + */ +#ifdef SIMPLE_INIT +static void s6d6aa1_disp_ctl(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + struct lcd_property *property = lcd->property; + unsigned char cfg = 0; + + if (property) { + if (property->flip & LCD_PROPERTY_FLIP_VERTICAL) + cfg |= DSCTL_VFLIP; + + if (property->flip & LCD_PROPERTY_FLIP_HORIZONTAL) + cfg |= DSCTL_HFLIP; + } + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0x36, cfg); +} +#else +static void s6d6aa1_disp_ctl(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0xEF, 0x02); +} +#endif + +static void s6d6aa1_source_ctl(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send[] = { + 0xF2, 0x03, 0x03, 0x91, 0x85 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6d6aa1_pwr_ctl(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send[] = { + 0xF4, 0x04, 0x0B, 0x07, 0x07, 0x10, 0x14, 0x0D, 0x0C, + 0xAD, 0x00, 0x33, 0x33 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6d6aa1_panel_ctl(struct s6d6aa1 *lcd, int high_freq) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send[] = { + 0xF6, 0x0B, 0x11, 0x0F, 0x25, 0x0A, 0x00, 0x13, 0x22, + 0x1B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, + 0x12, 0x32, 0x51 + }; + + /* ToDo : Low requency control */ + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6d6aa1_mount_ctl(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0xF7, 0x00); +} + +static int s6d6aa1_gamma_ctrl(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const unsigned char data_to_send1[] = { + 0xFA, 0x1C, 0x3F, 0x20, 0xDF, 0xEB, 0xEB, 0xE3, 0x61, + 0x61, 0xDB, 0x94, 0x15, 0xD9, 0xD8, 0xDB, 0xE1, 0xE4, + 0xE6, 0xE3, 0xA2, 0x49, 0xDC, 0x7F, 0x5E, 0xDD, 0x6B, + 0x6A, 0xA3, 0xE1, 0xE2, 0x9B, 0x55, 0xD6, 0x99, 0x59, + 0x9C, 0xA1, 0xA4, 0x64, 0xA0, 0x9F, 0x06, 0x80, 0xBF, + 0xB0, 0xB1, 0xFB, 0xFA, 0xF4, 0x31, 0x72, 0xEC, 0x25, + 0xA5, 0xA7, 0xE9, 0x6B, 0xAE, 0xB1, 0xB4, 0x35, 0xF8, + 0x9F + }; + + const unsigned char data_to_send2[] = { + 0xFB, 0x1C, 0x3F, 0x20, 0xDF, 0xEB, 0xEB, 0xE3, 0x61, + 0x61, 0xDB, 0x94, 0x15, 0xD9, 0xD8, 0xDB, 0xE1, 0xE4, + 0xE6, 0xE3, 0xA2, 0x49, 0xDC, 0x7F, 0x5E, 0xDD, 0x6B, + 0x6A, 0xA3, 0xE1, 0xE2, 0x9B, 0x55, 0xD6, 0x99, 0x59, + 0x9C, 0xA1, 0xA4, 0x64, 0xA0, 0x9F, 0x06, 0x80, 0xBF, + 0xB0, 0xB1, 0xFB, 0xFA, 0xF4, 0x31, 0x72, 0xEC, 0x25, + 0xA5, 0xA7, 0xE9, 0x6B, 0xAE, 0xB1, 0xB4, 0x35, 0xF8, + 0x9F + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send1, ARRAY_SIZE(data_to_send1)); + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send2, ARRAY_SIZE(data_to_send2)); + + return 0; +} + +static int s6d6aa1_panel_init(struct s6d6aa1 *lcd) +{ + s6d6aa1_sleep_out(lcd); + s6d6aa1_delay(140); +/* + * FIXME:!!simple init vs full init + * If lcd don't working simple sequence, + * we use full initialization sequence + */ +#ifdef SIMPLE_INIT + s6d6aa1_disp_ctl(lcd); +#else + s6d6aa1_apply_level_1_key(lcd); + s6d6aa1_apply_level_2_key(lcd); + + s6d6aa1_write_ddb(lcd); + s6d6aa1_bcm_mode(lcd); + s6d6aa1_wrbl_ctl(lcd); + s6d6aa1_sony_ip_setting(lcd); + s6d6aa1_disp_ctl(lcd); + s6d6aa1_source_ctl(lcd); + s6d6aa1_pwr_ctl(lcd); + s6d6aa1_panel_ctl(lcd, 1); + s6d6aa1_mount_ctl(lcd); + + s6d6aa1_gamma_ctrl(lcd); + + /* wait more than 10ms */ + s6d6aa1_delay(lcd->ddi_pd->power_on_delay); +#endif + + return 0; +} + +static void s6d6aa1_write_disbv(struct s6d6aa1 *lcd, + unsigned int brightness) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0x51, brightness); +} + +static void s6d6aa1_write_ctrld(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0x53, 0x2C); +} + +static void s6d6aa1_write_cabc(struct s6d6aa1 *lcd, + enum wm_mode wm_mode) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0x55, wm_mode); +} + +static void s6d6aa1_display_on(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, 0x29, 0x00); +} + +static void s6d6aa1_display_off(struct s6d6aa1 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, 0x28, 0x00); +} + +static int s6d6aa1_early_set_power(struct lcd_device *ld, int power) +{ + struct s6d6aa1 *lcd = lcd_get_data(ld); + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + int ret = 0; + + if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && + power != FB_BLANK_NORMAL) { + dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); + return -EINVAL; + } + + if (lcd->power == power) { + dev_err(lcd->dev, "power mode is same as previous one.\n"); + return -EINVAL; + } + + if (ops->set_early_blank_mode) { + /* LCD power off */ + if ((POWER_IS_OFF(power) && POWER_IS_ON(lcd->power)) + || (POWER_IS_ON(lcd->power) && POWER_IS_NRM(power))) { + ret = ops->set_early_blank_mode(lcd_to_master(lcd), + power); + if (!ret && lcd->power != power) + lcd->power = power; + } + } + + return ret; +} + +static int s6d6aa1_set_power(struct lcd_device *ld, int power) +{ + struct s6d6aa1 *lcd = lcd_get_data(ld); + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + int ret = 0; + + if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && + power != FB_BLANK_NORMAL) { + dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); + return -EINVAL; + } + + if (lcd->power == power) { + dev_err(lcd->dev, "power mode is same as previous one.\n"); + return -EINVAL; + } + + if (ops->set_blank_mode) { + ret = ops->set_blank_mode(lcd_to_master(lcd), power); + if (!ret && lcd->power != power) + lcd->power = power; + } + + return ret; +} + +static int s6d6aa1_get_power(struct lcd_device *ld) +{ + struct s6d6aa1 *lcd = lcd_get_data(ld); + + return lcd->power; +} + +static struct lcd_ops s6d6aa1_lcd_ops = { + .early_set_power = s6d6aa1_early_set_power, + .set_power = s6d6aa1_set_power, + .get_power = s6d6aa1_get_power, +}; + +static int s6d6aa1_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static int s6d6aa1_set_brightness(struct backlight_device *bd) +{ + int ret = 0, brightness = bd->props.brightness; + struct s6d6aa1 *lcd = bl_get_data(bd); + + if (lcd->power == FB_BLANK_POWERDOWN) { + dev_err(lcd->dev, + "lcd off: brightness set failed.\n"); + return -EINVAL; + } + + if (brightness < MIN_BRIGHTNESS || + brightness > bd->props.max_brightness) { + dev_err(lcd->dev, "lcd brightness should be %d to %d.\n", + MIN_BRIGHTNESS, MAX_BRIGHTNESS); + return -EINVAL; + } + + s6d6aa1_write_disbv(lcd, brightness); + return ret; +} + +static const struct backlight_ops s6d6aa1_backlight_ops = { + .get_brightness = s6d6aa1_get_brightness, + .update_status = s6d6aa1_set_brightness, +}; + +static ssize_t wm_mode_show(struct device *dev, struct + device_attribute * attr, char *buf) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(dev); + char temp[3]; + + sprintf(temp, "%d\n", lcd->wm_mode); + strcpy(buf, temp); + + return strlen(buf); +} + +static ssize_t wm_mode_store(struct device *dev, struct + device_attribute * attr, const char *buf, size_t size) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(dev); + unsigned int value; + int rc; + + rc = strict_strtoul(buf, (unsigned int)0, (unsigned long *)&value); + if (rc < 0) + return rc; + + if (value < WM_MODE_MIN || value > WM_MODE_MAX) { + dev_info(dev, "failed to set white magic mode to %d\n", + value); + return -EINVAL; + } + + if (lcd->wm_mode != value) { + dev_info(dev, "wm mode changed from %d to %d\n", + lcd->wm_mode, value); + lcd->wm_mode = value; + s6d6aa1_write_cabc(lcd, lcd->wm_mode); + } + return size; +} + +static ssize_t lcd_type_show(struct device *dev, struct + device_attribute * attr, char *buf) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(dev); + char temp[32]; + int i; + + for (i = 0; i < lcd->model_count; i++) { + if (lcd->ver == lcd->model[i].ver) + break; + } + + if (i == lcd->model_count) + return -EINVAL; + + sprintf(temp, "%s\n", lcd->model[i].name); + strcpy(buf, temp); + + return strlen(buf); +} + +static int s6d6aa1_read_reg(struct s6d6aa1 *lcd, unsigned int addr, char *buf) +{ + unsigned char data[MAX_READ_LENGTH]; + unsigned int size; + int i; + int pos = 0; + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + memset(data, 0x0, ARRAY_SIZE(data)); + size = ops->cmd_read(lcd_to_master(lcd), + MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM, + addr, MAX_READ_LENGTH, data); + if (!size) { + dev_err(lcd->dev, "failed to read 0x%.2x register.\n", addr); + return size; + } + + pos += sprintf(buf, "0x%.2x, ", addr); + for (i = 1; i < size+1; i++) { + if (i % 9 == 0) + pos += sprintf(buf+pos, "\n"); + pos += sprintf(buf+pos, "0x%.2x, ", data[i-1]); + } + pos += sprintf(buf+pos, "\n"); + + return pos; +} + +static int s6d6aa1_write_reg(struct s6d6aa1 *lcd, char *name) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + const struct firmware *fw; + char fw_path[MAX_STR+1]; + int ret = 0; + + mutex_lock(&lcd->lock); + snprintf(fw_path, MAX_STR, LDI_FW_PATH, name); + + ret = request_firmware(&fw, fw_path, lcd->dev); + if (ret) { + dev_err(lcd->dev, "failed to request firmware.\n"); + mutex_unlock(&lcd->lock); + return ret; + } + + if (fw->size == 1) + ret = ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + (unsigned int)fw->data[0], 0); + else if (fw->size == 2) + ret = ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, + (unsigned int)fw->data[0], fw->data[1]); + else + ret = ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)fw->data, fw->size); + if (ret) + dev_err(lcd->dev, "failed to write 0x%.2x register and %d error.\n", + fw->data[0], ret); + + release_firmware(fw); + mutex_unlock(&lcd->lock); + + return ret; +} + +static ssize_t read_reg_show(struct device *dev, struct + device_attribute * attr, char *buf) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(dev); + + if (lcd->cur_addr == 0) { + dev_err(dev, "failed to set current lcd register.\n"); + return -EINVAL; + } + + return s6d6aa1_read_reg(lcd, lcd->cur_addr, buf); +} + +static ssize_t read_reg_store(struct device *dev, struct + device_attribute * attr, const char *buf, size_t size) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret = sscanf(buf, "0x%x", &value); + if (ret < 0) + return ret; + + dev_info(dev, "success to set 0x%x address.\n", value); + lcd->cur_addr = value; + + return size; +} + +static ssize_t write_reg_store(struct device *dev, struct + device_attribute * attr, const char *buf, size_t size) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(dev); + char name[32]; + int ret; + + ret = sscanf(buf, "%s", name); + if (ret < 0) + return ret; + + ret = s6d6aa1_write_reg(lcd, name); + if (ret < 0) + return ret; + + dev_info(dev, "success to set %s address.\n", name); + + return size; +} + +static struct device_attribute device_attrs[] = { + __ATTR(wm_mode, S_IRUGO|S_IWUSR|S_IWGRP, + wm_mode_show, wm_mode_store), + __ATTR(lcd_type, S_IRUGO, + lcd_type_show, NULL), + __ATTR(read_reg, S_IRUGO|S_IWUSR|S_IWGRP, + read_reg_show, read_reg_store), + __ATTR(write_reg, S_IWUSR|S_IWGRP, + NULL, write_reg_store), +}; + +static struct panel_model s6d6aa1_model[] = { + { + .ver = VER_16, /* MACH_SLP_REDWOORD */ + .name = "SMD_ACX445AKM", + } +}; + +#if defined(CONFIG_ARM_EXYNOS4_DISPLAY_DEVFREQ) || defined(CONFIG_DISPFREQ_OPP) +static int s6d6aa1_notifier_callback(struct notifier_block *this, + unsigned long event, void *_data) +{ + struct s6d6aa1 *lcd = container_of(this, struct s6d6aa1, nb_disp); + + if (lcd->power == FB_BLANK_POWERDOWN) + return NOTIFY_DONE; + + switch (event) { + case EXYNOS4_DISPLAY_LV_HF: + s6d6aa1_panel_ctl(lcd, 1); + break; + case EXYNOS4_DISPLAY_LV_LF: + s6d6aa1_panel_ctl(lcd, 0); + break; + default: + return NOTIFY_BAD; + } + + return NOTIFY_DONE; +} +#endif + +static void s6d6aa1_regulator_ctl(struct s6d6aa1 *lcd, bool enable) +{ + mutex_lock(&lcd->lock); + + if (enable) { + if (lcd->reg_vddi) + regulator_enable(lcd->reg_vddi); + + if (lcd->reg_vdd) + regulator_enable(lcd->reg_vdd); + } else { + if (lcd->reg_vdd) + regulator_disable(lcd->reg_vdd); + + if (lcd->reg_vddi) + regulator_disable(lcd->reg_vddi); + } + + mutex_unlock(&lcd->lock); +} + +static void s6d6aa1_power_on(struct mipi_dsim_lcd_device *dsim_dev, + unsigned int enable) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(&dsim_dev->dev); + + dev_dbg(lcd->dev, "%s:enable[%d]\n", __func__, enable); + + if (enable) { + /* lcd power on */ + s6d6aa1_regulator_ctl(lcd, true); + + s6d6aa1_delay(lcd->ddi_pd->reset_delay); + + /* lcd reset high */ + if (lcd->ddi_pd->reset) + lcd->ddi_pd->reset(lcd->ld); + + /* wait more than 10ms */ + s6d6aa1_delay(10); + } else { + /* lcd reset low */ + if (lcd->ddi_pd->reset) + lcd->ddi_pd->reset(lcd->ld); + + /* lcd power off */ + s6d6aa1_regulator_ctl(lcd, false); + } +} + +static int s6d6aa1_check_mtp(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(&dsim_dev->dev); + u8 mtp_id[3] = {0, }; + + s6d6aa1_read_id(lcd, mtp_id); + if (mtp_id[0] == 0x00) { + dev_err(lcd->dev, "read id failed\n"); + return -EIO; + } + + lcd->ver = mtp_id[1]; + dev_info(lcd->dev, + "Read ID : %x, %x, %x\n", mtp_id[0], mtp_id[1], mtp_id[2]); + + return 0; +} + +static void s6d6aa1_set_sequence(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(&dsim_dev->dev); + struct backlight_device *bd = lcd->bd; + + s6d6aa1_panel_init(lcd); + + s6d6aa1_write_disbv(lcd, bd->props.brightness); + s6d6aa1_write_ctrld(lcd); + s6d6aa1_write_cabc(lcd, lcd->wm_mode); + s6d6aa1_display_on(lcd); +} + +static int s6d6aa1_probe(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6d6aa1 *lcd; + int ret; + int i; + + lcd = kzalloc(sizeof(struct s6d6aa1), GFP_KERNEL); + if (!lcd) { + dev_err(&dsim_dev->dev, "failed to allocate s6d6aa1 structure.\n"); + return -ENOMEM; + } + + lcd->dsim_dev = dsim_dev; + lcd->ddi_pd = (struct lcd_platform_data *)dsim_dev->platform_data; + lcd->dev = &dsim_dev->dev; + + mutex_init(&lcd->lock); + + lcd->reg_vddi = regulator_get(lcd->dev, "VDDI"); + if (IS_ERR(lcd->reg_vddi)) { + ret = PTR_ERR(lcd->reg_vddi); + dev_err(lcd->dev, "failed to get %s regulator (%d)\n", + "VDDI", ret); + lcd->reg_vddi = NULL; + } + + lcd->reg_vdd = regulator_get(lcd->dev, "VDD"); + if (IS_ERR(lcd->reg_vdd)) { + ret = PTR_ERR(lcd->reg_vdd); + dev_err(lcd->dev, "failed to get %s regulator (%d)\n", + "VDD", ret); + lcd->reg_vdd = NULL; + } + + lcd->ld = lcd_device_register("s6d6aa1", lcd->dev, lcd, + &s6d6aa1_lcd_ops); + if (IS_ERR(lcd->ld)) { + dev_err(lcd->dev, "failed to register lcd ops.\n"); + ret = PTR_ERR(lcd->ld); + goto err_regulator; + } + + lcd->bd = backlight_device_register("s6d6aa1-bl", lcd->dev, lcd, + &s6d6aa1_backlight_ops, NULL); + if (IS_ERR(lcd->bd)) { + dev_err(lcd->dev, "failed to register backlight ops.\n"); + ret = PTR_ERR(lcd->bd); + goto err_unregister_lcd; + } + + s6d6aa1_regulator_ctl(lcd, true); + + if (lcd->ddi_pd) + lcd->property = lcd->ddi_pd->pdata; + +#if defined(CONFIG_ARM_EXYNOS4_DISPLAY_DEVFREQ) || defined(CONFIG_DISPFREQ_OPP) + if (lcd->property && lcd->property->dynamic_refresh) { + lcd->nb_disp.notifier_call = s6d6aa1_notifier_callback; + ret = exynos4_display_register_client(&lcd->nb_disp); + if (ret < 0) + dev_warn(&lcd->ld->dev, "failed to register exynos-display notifier\n"); + } +#endif + + lcd->bd->props.max_brightness = MAX_BRIGHTNESS; + lcd->bd->props.brightness = MAX_BRIGHTNESS; + lcd->power = FB_BLANK_UNBLANK; + lcd->wm_mode = WM_MODE_CONSERVATIVE; + lcd->model = s6d6aa1_model; + lcd->model_count = ARRAY_SIZE(s6d6aa1_model); + for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { + ret = device_create_file(&lcd->ld->dev, + &device_attrs[i]); + if (ret < 0) { + dev_err(&lcd->ld->dev, "failed to add sysfs entries\n"); + break; + } + } + + dev_set_drvdata(&dsim_dev->dev, lcd); + dev_info(lcd->dev, "probed s6d6aa1 panel driver(%s).\n", + dev_name(&lcd->ld->dev)); + + return 0; + +err_unregister_lcd: + lcd_device_unregister(lcd->ld); + +err_regulator: + regulator_put(lcd->reg_vdd); + regulator_put(lcd->reg_vddi); + + kfree(lcd); + + return ret; +} + +static void s6d6aa1_remove(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(&dsim_dev->dev); + + backlight_device_unregister(lcd->bd); + lcd_device_unregister(lcd->ld); + + regulator_put(lcd->reg_vdd); + regulator_put(lcd->reg_vddi); + +#if defined(CONFIG_ARM_EXYNOS4_DISPLAY_DEVFREQ) || defined(CONFIG_DISPFREQ_OPP) + if (lcd->property && lcd->property->dynamic_refresh) + exynos4_display_unregister_client(&lcd->nb_disp); +#endif + kfree(lcd); +} + +#ifdef CONFIG_PM +static int s6d6aa1_suspend(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6d6aa1_display_off(lcd); + s6d6aa1_sleep_in(lcd); + s6d6aa1_delay(lcd->ddi_pd->power_off_delay); + + return 0; +} + +static int s6d6aa1_resume(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6d6aa1 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6d6aa1_sleep_out(lcd); + s6d6aa1_delay(lcd->ddi_pd->power_on_delay); + + return 0; +} +#else +#define s6d6aa1_suspend NULL +#define s6d6aa1_resume NULL +#endif + +static struct mipi_dsim_lcd_driver s6d6aa1_dsim_ddi_driver = { + .name = "s6d6aa1", + .id = -1, + + .power_on = s6d6aa1_power_on, + .check_mtp = s6d6aa1_check_mtp, + .set_sequence = s6d6aa1_set_sequence, + .probe = s6d6aa1_probe, + .remove = s6d6aa1_remove, + .suspend = s6d6aa1_suspend, + .resume = s6d6aa1_resume, +}; + +static int s6d6aa1_init(void) +{ + s5p_mipi_dsi_register_lcd_driver(&s6d6aa1_dsim_ddi_driver); + + return 0; +} + +static void s6d6aa1_exit(void) +{ + return; +} + +module_init(s6d6aa1_init); +module_exit(s6d6aa1_exit); + + +MODULE_AUTHOR("Joongmock Shin <jmock.shin@samsung.com>"); +MODULE_AUTHOR("Eunchul Kim <chulspro.kim@samsung.com>"); +MODULE_DESCRIPTION("MIPI-DSI based s6d6aa1 TFT-LCD Panel Driver"); +MODULE_LICENSE("GPL"); + |