blob: f2ee22c7cfc0ab67dc49fca23c4681767d0c60fd [file] [log] [blame]
/*
* Driver for the Himax HX-8357 LCD Controller
*
* Copyright 2012 Free Electrons
*
* Licensed under the GPLv2 or later.
*/
#include <linux/delay.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/spi/spi.h>
#include <linux/sysfs.h>
#include "mxc_dispdrv.h"
#define xstr(s) str(s)
#define str(s) #s
#define HX8357_NUM_IM_PINS 3
#define HX8357_SWRESET 0x01
#define HX8357_GET_RED_CHANNEL 0x06
#define HX8357_GET_GREEN_CHANNEL 0x07
#define HX8357_GET_BLUE_CHANNEL 0x08
#define HX8357_GET_POWER_MODE 0x0a
#define HX8357_POWER_MODE_DISP_ON 0x04
#define HX8357_POWER_MODE_NORMAL 0x08
#define HX8357_POWER_MODE_AWAKE 0x10
#define HX8357_GET_MADCTL 0x0b
#define HX8357_GET_PIXEL_FORMAT 0x0c
#define HX8357_GET_DISPLAY_MODE 0x0d
#define HX8357_GET_SIGNAL_MODE 0x0e
#define HX8357_GET_DIAGNOSTIC_RESULT 0x0f
#define HX8357_ENTER_SLEEP_MODE 0x10
#define HX8357_EXIT_SLEEP_MODE 0x11
#define HX8357_ENTER_PARTIAL_MODE 0x12
#define HX8357_ENTER_NORMAL_MODE 0x13
#define HX8357_EXIT_INVERSION_MODE 0x20
#define HX8357_ENTER_INVERSION_MODE 0x21
#define HX8357_SET_DISPLAY_OFF 0x28
#define HX8357_SET_DISPLAY_ON 0x29
#define HX8357_SET_COLUMN_ADDRESS 0x2a
#define HX8357_SET_PAGE_ADDRESS 0x2b
#define HX8357_WRITE_MEMORY_START 0x2c
#define HX8357_READ_MEMORY_START 0x2e
#define HX8357_SET_PARTIAL_AREA 0x30
#define HX8357_SET_SCROLL_AREA 0x33
#define HX8357_SET_TEAR_OFF 0x34
#define HX8357_SET_TEAR_ON 0x35
#define HX8357_SET_ADDRESS_MODE 0x36
#define HX8357_SET_SCROLL_START 0x37
#define HX8357_EXIT_IDLE_MODE 0x38
#define HX8357_ENTER_IDLE_MODE 0x39
#define HX8357_SET_PIXEL_FORMAT 0x3a
#define HX8357_SET_PIXEL_FORMAT_DBI_3BIT (0x1)
#define HX8357_SET_PIXEL_FORMAT_DBI_16BIT (0x5)
#define HX8357_SET_PIXEL_FORMAT_DBI_18BIT (0x6)
#define HX8357_SET_PIXEL_FORMAT_DBI_24BIT (0x7)
#define HX8357_SET_PIXEL_FORMAT_DPI_3BIT (0x1 << 4)
#define HX8357_SET_PIXEL_FORMAT_DPI_16BIT (0x5 << 4)
#define HX8357_SET_PIXEL_FORMAT_DPI_18BIT (0x6 << 4)
#define HX8357_SET_PIXEL_FORMAT_DPI_24BIT (0x7 << 4)
#define HX8357_WRITE_MEMORY_CONTINUE 0x3c
#define HX8357_READ_MEMORY_CONTINUE 0x3e
#define HX8357_SET_TEAR_SCAN_LINES 0x44
#define HX8357_GET_SCAN_LINES 0x45
#define HX8357_READ_DDB_START 0xa1
#define HX8357_SET_DISPLAY_MODE 0xb4
#define HX8357_SET_DISPLAY_MODE_RGB_THROUGH (0x3)
#define HX8357_SET_DISPLAY_MODE_RGB_INTERFACE (1 << 4)
#define HX8357_SET_PANEL_DRIVING 0xc0
#define HX8357_SET_DISPLAY_FRAME 0xc5
#define HX8357_SET_RGB 0xc6
#define HX8357_SET_RGB_ENABLE_HIGH (1 << 1)
#define HX8357_SET_GAMMA 0xc8
#define HX8357_SET_POWER 0xd0
#define HX8357_SET_VCOM 0xd1
#define HX8357_SET_POWER_NORMAL 0xd2
#define HX8357_SET_PANEL_RELATED 0xe9
#define HX8369_NOOP 0x00
#define HX8369_DISPLAY_ID 0x69
#define HX8369_SET_DISPLAY_BRIGHTNESS 0x51
#define HX8369_WRITE_CABC_DISPLAY_VALUE 0x53
#define HX8369_WRITE_CABC_BRIGHT_CTRL 0x55
#define HX8369_WRITE_CABC_MIN_BRIGHTNESS 0x5e
#define HX8369_SET_OSC 0xb0
#define HX8369_SET_OSC_LEN 2
#define HX8369_SET_POWER 0xb1
#define HX8369_SET_POWER_LEN 20
#define HX8369_SET_DISPLAY_MODE 0xb2
#define HX8369_SET_DISPLAY_MODE_LEN 16
#define HX8369_SET_RGB_INTERFACE 0xb3
#define HX8369_SET_RGB_INTERFACE_LEN 3
#define HX8369_SET_DISPLAY_WAVEFORM_CYC 0xb4
#define HX8369_SET_DISPLAY_WAVEFORM_CYC_LEN 6
#define HX8369_SET_VCOM 0xb6
#define HX8369_SET_VCOM_LEN 3
#define HX8369_SET_DCDC_FDBK_CTRL 0xbf /* Undocumented, from Vendor */
#define HX8369_SET_DCDC_FDBK_CTRL_LEN 2
#define HX8369_SET_EXTENSION_COMMAND 0xb9
#define HX8369_SET_EXTENSION_COMMAND_LEN 4
#define HX8369_SET_PANEL 0xcc
#define HX8369_SET_PANEL_LEN 2
#define HX8369_SET_GIP 0xd5
#define HX8369_SET_GIP_LEN 27
#define HX8369_SET_GAMMA_CURVE_RELATED 0xe0
#define HX8369_SET_GAMMA_CURVE_RELATED_LEN 35
#define HX8369_GET_HXID 0xF4
#define HX8369_GET_POWER_MODE 0x0A
#define HX8369_READ_ID(x) (0xDA + (x))
struct hx8357_data {
struct mxc_dispdrv_driver driver;
struct mxc_dispdrv_handle *handle;
unsigned im_pins[HX8357_NUM_IM_PINS];
unsigned reset;
struct spi_device *spi;
int state;
int pre_suspend_state;
bool use_im_pins;
int (*init)(struct hx8357_data* lcd);
struct fb_info *fbi;
struct device dev;
};
#define HX8369_NUM_DISPLAY_TYPES 3
#define HX8369_FALLBACK_TYPE (HX8369_NUM_DISPLAY_TYPES - 1)
enum {
DISPLAY_ID_TIANMA = 0,
DISPLAY_ID_WINTEK,
DISPLAY_ID_LG
} display_id;
static const uint32_t hx8369_display_ids[HX8369_NUM_DISPLAY_TYPES] = {
0x000011, /* tianma TM040YDHP09 480x800 */
0x232221, /* wintek form factor */
0x000021, /* LG Form Factor */
};
static const u8 hx8357_seq_power[] = {
HX8357_SET_POWER, 0x44, 0x41, 0x06,
};
static const u8 hx8357_seq_vcom[] = {
HX8357_SET_VCOM, 0x40, 0x10,
};
static const u8 hx8357_seq_power_normal[] = {
HX8357_SET_POWER_NORMAL, 0x05, 0x12,
};
static const u8 hx8357_seq_panel_driving[] = {
HX8357_SET_PANEL_DRIVING, 0x14, 0x3b, 0x00, 0x02, 0x11,
};
static const u8 hx8357_seq_display_frame[] = {
HX8357_SET_DISPLAY_FRAME, 0x0c,
};
static const u8 hx8357_seq_panel_related[] = {
HX8357_SET_PANEL_RELATED, 0x01,
};
static const u8 hx8357_seq_undefined1[] = {
0xea, 0x03, 0x00, 0x00,
};
static const u8 hx8357_seq_undefined2[] = {
0xeb, 0x40, 0x54, 0x26, 0xdb,
};
static const u8 hx8357_seq_gamma[] = {
HX8357_SET_GAMMA, 0x00, 0x15, 0x00, 0x22, 0x00,
0x08, 0x77, 0x26, 0x77, 0x22, 0x04, 0x00,
};
static const u8 hx8357_seq_address_mode[] = {
HX8357_SET_ADDRESS_MODE, 0xc0,
};
static const u8 hx8357_seq_pixel_format[] = {
HX8357_SET_PIXEL_FORMAT,
HX8357_SET_PIXEL_FORMAT_DPI_24BIT |
HX8357_SET_PIXEL_FORMAT_DBI_24BIT,
};
static const u8 hx8357_seq_column_address[] = {
HX8357_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x01, 0xdf,
};
static const u8 hx8357_seq_page_address[] = {
HX8357_SET_PAGE_ADDRESS, 0x00, 0x00, 0x03, 0x1f,
};
static const u8 hx8357_seq_rgb[] = {
HX8357_SET_RGB, 0x02,
};
static const u8 hx8357_seq_display_mode[] = {
HX8357_SET_DISPLAY_MODE,
HX8357_SET_DISPLAY_MODE_RGB_THROUGH |
HX8357_SET_DISPLAY_MODE_RGB_INTERFACE,
};
static const u8 hx8369_seq_extension_command[HX8369_SET_EXTENSION_COMMAND_LEN] = {
HX8369_SET_EXTENSION_COMMAND, 0xff, 0x83, 0x69,
};
static const u8 hx8369_seq_display_related[HX8369_NUM_DISPLAY_TYPES][HX8369_SET_DISPLAY_MODE_LEN] = {
{
HX8369_SET_DISPLAY_MODE,
0x00,/*d[1:0] --Operate (display)*/
0x3b,/*RES_SEL=b010 RM=1 DFR=0 DM=01 480x640 RGB DPI*/
0x04,/*bp=6*/
0x04,/*fp=6*/
0xF0,/*sap=b0111 4u fixed current*/
0x00,/*GEN_ON*/
0xFF,/*GEN_OFF*/
0x00,/*RTN*/
0x00,/*TEI*/
0x00,/*TEP*/
0x00,/*TEP*/
0x04,/*BP_PE=6*/
0x04,/*FP_PE=6*/
0x00,/*RTN_PE*/
0x01,/*GON=1*/
},
{
HX8369_SET_DISPLAY_MODE,
0x00, 0x33, 0x03, 0x03, 0x70, 0x00, 0xFF,
0x06, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x01,
},
{
HX8369_SET_DISPLAY_MODE,
0x00, 0x33, 0x03, 0x03, 0x70, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x01,
},
};
static const u8 hx8369_seq_panel_waveform_cycle[HX8369_NUM_DISPLAY_TYPES][HX8369_SET_DISPLAY_WAVEFORM_CYC_LEN] = {
{
HX8369_SET_DISPLAY_WAVEFORM_CYC, 0x0a, 0x1d, 0x80, 0x00, 0x00,
},
{
HX8369_SET_DISPLAY_WAVEFORM_CYC, 0x00, 0x1c, 0x84, 0x06, 0x02,
},
{
HX8369_SET_DISPLAY_WAVEFORM_CYC, 0x0A, 0x1D, 0x69, 0x0E, 0x06,
},
};
static const u8 hx8369_seq_set_address_mode[] = {
HX8357_SET_ADDRESS_MODE, 0x08,
};
static const u8 hx8369_seq_vcom[HX8369_NUM_DISPLAY_TYPES][HX8369_SET_VCOM_LEN] = {{
HX8369_SET_VCOM, 0x3F, 0x3F,
},
{
HX8369_SET_VCOM, 0x3c, 0x3c,
},
{
HX8369_SET_VCOM, 0x31, 0x31,
},
};
static const u8 hx8369_seq_panel[HX8369_SET_PANEL_LEN] = {
HX8369_SET_PANEL, 0x02,
};
static const u8 hx8369_seq_gip[HX8369_NUM_DISPLAY_TYPES][HX8369_SET_GIP_LEN] = {{
HX8369_SET_GIP, 0x00, 0x01, 0x03, 0x34, 0x01, 0x05, 0x00, 0x70,
0x11, 0x13, 0x00, 0x00, 0x60, 0x24, 0x71, 0x35, 0x00, 0x00, 0x73,
0x05, 0x62, 0x14, 0x07, 0x0f, 0x04, 0x04,
},
{
HX8369_SET_GIP, 0x00, 0x08, 0x03, 0x00, 0x01, 0x0A, 0x00, 0x85,
0x11, 0x13, 0x00, 0x01, 0x00, 0x64, 0x00, 0x75, 0x01, 0x00, 0x44,
0x57, 0x44, 0x46, 0x07, 0x0F, 0x02, 0x03,
},
{
HX8369_SET_GIP, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x28, 0x98,
0x33, 0x37, 0x02, 0x31, 0x46, 0x8A, 0x57, 0x9B, 0x00, 0x00, 0x73,
0x15, 0x61, 0x04, 0x07, 0x0F, 0x04, 0x00,
},
};
static const u8 hx8369_seq_osc[HX8369_SET_OSC_LEN] = {
HX8369_SET_OSC, 0x01,
};
static const u8 hx8369_seq_power[HX8369_NUM_DISPLAY_TYPES][HX8369_SET_POWER_LEN] = {
{
HX8369_SET_POWER, 0x01, 0x00, 0x34, 0x0A, 0x00, 0x11, 0x11, 0x2f,
0x37, 0x3f, 0x3f, 0x01, 0x22, 0x01, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6,
},
{
HX8369_SET_POWER, 0x01, 0x00, 0x34, 0x07, 0x00, 0x10, 0x10, 0x22,
0x32, 0x3F, 0x3F, 0x07, 0x23, 0x01, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6,
},
{
HX8369_SET_POWER, 0x01, 0x00, 0x54, 0x0B, 0x00, 0x0F, 0x0F, 0x34,
0x3C, 0x3F, 0x3F, 0x07, 0x22, 0x01, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6,
},
};
static const u8 hx8369_seq_dcdc_fdbk_ctrl[HX8369_NUM_DISPLAY_TYPES][HX8369_SET_DCDC_FDBK_CTRL_LEN] = {
{
HX8369_NOOP,
},
{
HX8369_NOOP,
},
{
HX8369_SET_DCDC_FDBK_CTRL, 0x70,
},
};
static const u8 hx8369_seq_gamma_curve_related[HX8369_NUM_DISPLAY_TYPES][HX8369_SET_GAMMA_CURVE_RELATED_LEN] = {
{
HX8369_SET_GAMMA_CURVE_RELATED, 0x10, 0x1a, 0x26, 0x2a, 0x2b, 0x2f,
0x33, 0x4b, 0x04, 0x0a, 0x0e, 0x13, 0x15, 0x14, 0x14, 0x13, 0x15,
0x10, 0x1b, 0x26, 0x2a, 0x2b, 0x2f, 0x33, 0x4a, 0x00, 0x0a, 0x0e,
0x14, 0x16, 0x14, 0x14, 0x12, 0x18,
},
{
HX8369_SET_GAMMA_CURVE_RELATED, 0x00, 0x1A, 0x20, 0x37, 0x3B, 0x3F,
0x2F, 0x4D, 0x07, 0x0D, 0x0F, 0x12, 0x14, 0x12, 0x14, 0x10, 0x18,
0x00, 0x19, 0x21, 0x38, 0x3B, 0x3F, 0x2F, 0x4C, 0x06, 0x0D, 0x0E,
0x12, 0x14, 0x14, 0x14, 0x11, 0x17,
},
{
HX8369_SET_GAMMA_CURVE_RELATED, 0x00, 0x0D, 0x0F, 0x2F, 0x32, 0x3E,
0x1D, 0x3C, 0x09, 0x0E, 0x0D, 0x10, 0x12, 0x11, 0x12, 0x11, 0x18,
0x00, 0x0D, 0x0F, 0x2F, 0x32, 0x3E, 0x1D, 0x3C, 0x09, 0x0E, 0x0D,
0x10, 0x12, 0x11, 0x12, 0x11, 0x18,
},
};
static int hx8357_spi_write_then_read(struct hx8357_data *lcd,
u8 *txbuf, u16 txlen,
u8 *rxbuf, u16 rxlen)
{
struct spi_message msg;
struct spi_transfer xfer[2];
u16 *local_txbuf = NULL;
int ret = 0;
memset(xfer, 0, sizeof(xfer));
spi_message_init(&msg);
if (txlen) {
int i;
local_txbuf = kcalloc(txlen, sizeof(*local_txbuf), GFP_KERNEL);
if (!local_txbuf)
return -ENOMEM;
for (i = 0; i < txlen; i++) {
local_txbuf[i] = txbuf[i];
if (i > 0)
local_txbuf[i] |= 1 << 8;
}
xfer[0].len = 2 * txlen;
xfer[0].bits_per_word = 9;
xfer[0].tx_buf = local_txbuf;
spi_message_add_tail(&xfer[0], &msg);
}
if (rxlen) {
xfer[1].len = rxlen;
xfer[1].bits_per_word = 8;
xfer[1].rx_buf = rxbuf;
spi_message_add_tail(&xfer[1], &msg);
}
ret = spi_sync(lcd->spi, &msg);
if (ret < 0)
dev_err(&lcd->dev, "Couldn't send SPI data\n");
if (txlen)
kfree(local_txbuf);
return ret;
}
static inline int hx8357_spi_write_array(struct hx8357_data *lcd,
u8 *value, u8 len)
{
return hx8357_spi_write_then_read(lcd, value, len, NULL, 0);
}
static inline int hx8357_spi_write_byte(struct hx8357_data *lcd,
u8 value)
{
return hx8357_spi_write_then_read(lcd, &value, 1, NULL, 0);
}
static uint32_t hx8369_get_cmd(struct hx8357_data *lcd, u8 command)
{
u8 reg[2] = {0xFE, command};
u8 cmd = 0xff;
u8 buf = 0;
hx8357_spi_write_array(lcd, reg, 2);
hx8357_spi_write_then_read(lcd, &cmd, 1, &buf, 1);
return (uint32_t) buf;
};
static uint32_t hx8369_read_hxid(struct hx8357_data *lcd)
{
return hx8369_get_cmd(lcd, HX8369_GET_HXID);
};
static int hx8357_enter_standby(struct hx8357_data *lcd)
{
int ret = 0 ;
/*
* Controller must wait minimum 120 msec between exit_sleep and
* enter sleep.
*/
msleep(120);
ret = hx8357_spi_write_byte(lcd, HX8357_SET_DISPLAY_OFF);
if (ret < 0)
return ret;
ret = hx8357_spi_write_byte(lcd, HX8357_ENTER_SLEEP_MODE);
if (ret < 0)
return ret;
/*
* The controller needs 120ms when entering in sleep mode before we can
* send the command to exit sleep mode. Wait now to make sure wake is fast.
*/
msleep(120);
if(hx8369_read_hxid(lcd) != HX8369_DISPLAY_ID)
{
dev_warn(&lcd->dev, "Display mismatch after sleep. %02X\n", hx8369_read_hxid(lcd));
}
if(hx8369_get_cmd(lcd, HX8357_GET_POWER_MODE) != HX8357_POWER_MODE_NORMAL)
{
dev_warn(&lcd->dev, "reg 0a != 0x08. is %02X instead\n",hx8369_get_cmd(lcd, HX8357_GET_POWER_MODE));
}
return ret;
}
static int hx8357_exit_standby(struct hx8357_data *lcd)
{
int ret = 0;
msleep(17); //wait for at least 1 frame to ensure the pixel clock is running.
ret = hx8357_spi_write_byte(lcd, HX8357_EXIT_SLEEP_MODE);
if (ret < 0)
return ret;
/*From the manual:
*It takes 120msec to become Sleep Out mode after SLPOUT command issued.*/
msleep(120);
if(hx8369_read_hxid(lcd) != HX8369_DISPLAY_ID)
{
dev_warn(&lcd->dev, "Display mismatch after exit sleep. %02X\n", hx8369_read_hxid(lcd));
}
if(hx8369_get_cmd(lcd, HX8357_GET_POWER_MODE) != (HX8357_POWER_MODE_NORMAL | HX8357_POWER_MODE_AWAKE))
{
dev_warn(&lcd->dev, "reg 0a != 0x18. is %02X instead\n",hx8369_get_cmd(lcd, HX8357_GET_POWER_MODE));
}
if(ret == 0)
{
ret = hx8357_spi_write_byte(lcd, HX8357_SET_DISPLAY_ON);
}
msleep(10);
if(hx8369_read_hxid(lcd) != HX8369_DISPLAY_ID)
{
dev_warn(&lcd->dev, "Display mismatch after display on. %02X\n", hx8369_read_hxid(lcd));
}
if(hx8369_get_cmd(lcd, HX8357_GET_POWER_MODE) != (HX8357_POWER_MODE_NORMAL | HX8357_POWER_MODE_AWAKE | HX8357_POWER_MODE_DISP_ON))
{
dev_warn(&lcd->dev, "reg 0a != 0x1C. is %02X instead\n",hx8369_get_cmd(lcd, HX8357_GET_POWER_MODE));
}
return ret;
}
static void hx8357_lcd_reset(struct hx8357_data *lcd)
{
/* Reset the screen */
gpio_set_value(lcd->reset, 1);
usleep_range(10000, 12000);
gpio_set_value(lcd->reset, 0);
usleep_range(10000, 12000);
gpio_set_value(lcd->reset, 1);
/* The controller needs 120ms to recover from reset */
msleep(120);
}
static uint32_t hx8369_read_disp_id(struct hx8357_data *lcd)
{
uint32_t retval = 0;
int i;
for (i = 2; i >= 0; i--) {
u8 reg[2] = {0xFE, HX8369_READ_ID(i)};
u8 cmd = 0xff;
u8 buf = 0;
hx8357_spi_write_array(lcd, reg, 2);
hx8357_spi_write_then_read(lcd, &cmd, 1, &buf, 1);
retval = (retval << 8) | buf;
}
/*id2 bit 7 is always set. Clear it.*/
retval &= ~(1 << 15);
return retval;
}
static ssize_t show_hxid(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hx8357_data *lcd = dev_get_drvdata(dev);
return sprintf(buf, "%02x\n", hx8369_read_hxid(lcd));
}
static ssize_t show_panelid(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hx8357_data *lcd = dev_get_drvdata(dev);
return sprintf(buf, "%06x\n", hx8369_read_disp_id(lcd));
}
static DEVICE_ATTR(hxid, S_IRUGO, show_hxid, NULL);
static DEVICE_ATTR(panelid, S_IRUGO, show_panelid, NULL);
static int hx8357_lcd_init(struct hx8357_data *lcd)
{
int ret;
hx8357_lcd_reset(lcd);
/*
* Set the interface selection pins to SPI mode, with three
* wires
*/
if (lcd->use_im_pins) {
gpio_set_value_cansleep(lcd->im_pins[0], 1);
gpio_set_value_cansleep(lcd->im_pins[1], 0);
gpio_set_value_cansleep(lcd->im_pins[2], 1);
}
ret = hx8357_spi_write_array(lcd, hx8357_seq_power,
ARRAY_SIZE(hx8357_seq_power));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_vcom,
ARRAY_SIZE(hx8357_seq_vcom));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_power_normal,
ARRAY_SIZE(hx8357_seq_power_normal));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_panel_driving,
ARRAY_SIZE(hx8357_seq_panel_driving));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_display_frame,
ARRAY_SIZE(hx8357_seq_display_frame));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_panel_related,
ARRAY_SIZE(hx8357_seq_panel_related));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_undefined1,
ARRAY_SIZE(hx8357_seq_undefined1));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_undefined2,
ARRAY_SIZE(hx8357_seq_undefined2));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_gamma,
ARRAY_SIZE(hx8357_seq_gamma));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_address_mode,
ARRAY_SIZE(hx8357_seq_address_mode));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_pixel_format,
ARRAY_SIZE(hx8357_seq_pixel_format));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_column_address,
ARRAY_SIZE(hx8357_seq_column_address));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_page_address,
ARRAY_SIZE(hx8357_seq_page_address));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_rgb,
ARRAY_SIZE(hx8357_seq_rgb));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8357_seq_display_mode,
ARRAY_SIZE(hx8357_seq_display_mode));
if (ret < 0)
return ret;
ret = hx8357_spi_write_byte(lcd, HX8357_EXIT_SLEEP_MODE);
if (ret < 0)
return ret;
/*
* The controller needs 120ms to fully recover from exiting sleep mode
*/
msleep(120);
ret = hx8357_spi_write_byte(lcd, HX8357_SET_DISPLAY_ON);
if (ret < 0)
return ret;
usleep_range(5000, 7000);
ret = hx8357_spi_write_byte(lcd, HX8357_WRITE_MEMORY_START);
if (ret < 0)
return ret;
return 0;
}
static int hx8369_lcd_init(struct hx8357_data *lcd)
{
int ret;
int id;
int panel_id;
hx8357_lcd_reset(lcd);
ret = hx8357_spi_write_array(lcd, hx8369_seq_extension_command,
ARRAY_SIZE(hx8369_seq_extension_command));
if (ret < 0)
return ret;
usleep_range(10000, 12000);
dev_info(&lcd->dev, "Himax ID: %02x. should be " xstr(HX8369_DISPLAY_ID) ".\n", hx8369_read_hxid(lcd));
panel_id = hx8369_read_disp_id(lcd);
dev_info(&lcd->dev, "Panel ID: %06x\n", panel_id);
for (id = 0; id < HX8369_NUM_DISPLAY_TYPES; id++) {
if (hx8369_display_ids[id] == panel_id)
break;
}
if (id == HX8369_NUM_DISPLAY_TYPES) {
dev_warn(&lcd->dev, "Panel ID doesn't exist in table. Trying default\n");
id = HX8369_FALLBACK_TYPE;
}
if (id == DISPLAY_ID_LG) {
dev_warn(&lcd->dev, "special case, shift framebuffer by 3 pixels");
lcd->fbi->var.upper_margin += 3;
fb_set_var(lcd->fbi, &lcd->fbi->var);
}
ret = hx8357_spi_write_array(lcd, hx8369_seq_display_related[id],
ARRAY_SIZE(hx8369_seq_display_related[id]));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8369_seq_panel_waveform_cycle[id],
ARRAY_SIZE(hx8369_seq_panel_waveform_cycle[id]));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8369_seq_vcom[id],
ARRAY_SIZE(hx8369_seq_vcom[id]));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8369_seq_panel,
ARRAY_SIZE(hx8369_seq_panel));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8369_seq_gip[id],
ARRAY_SIZE(hx8369_seq_gip[id]));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8369_seq_power[id],
ARRAY_SIZE(hx8369_seq_power[id]));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8369_seq_dcdc_fdbk_ctrl[id],
ARRAY_SIZE(hx8369_seq_dcdc_fdbk_ctrl[id]));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcd, hx8369_seq_gamma_curve_related[id],
ARRAY_SIZE(hx8369_seq_gamma_curve_related[id]));
if (ret < 0)
return ret;
ret = hx8357_spi_write_byte(lcd, HX8357_EXIT_SLEEP_MODE);
if (ret < 0)
return ret;
/*
* The controller needs 120ms to fully recover from exiting sleep mode
*/
msleep(120);
ret = hx8357_spi_write_byte(lcd, HX8357_SET_DISPLAY_ON);
if (ret < 0)
return ret;
return 0;
}
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
static int hx8357_set_power(struct hx8357_data *lcd, int power)
{
int ret = 0;
if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->state))
{
uint32_t id;
ret = hx8357_exit_standby(lcd);
id = hx8369_read_hxid(lcd);
if (id != HX8369_DISPLAY_ID)
{
dev_err(&lcd->dev, "Version mismatch on set_power 0x%x\n", id);
//attempt to fully re-init the panel if resume didn't work.
ret = lcd->init(lcd);
//reboot if that failed
BUG_ON(ret != 0);
}
}
else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->state))
ret = hx8357_enter_standby(lcd);
if (ret == 0)
lcd->state = power;
else
dev_warn(&lcd->dev, "failed to set power mode %d\n", power);
return ret;
}
static int hx8357_get_power(struct hx8357_data *lcd)
{
return lcd->state;
}
static const struct of_device_id hx8357_dt_ids[] = {
{
.compatible = "himax,hx8357",
.data = hx8357_lcd_init,
},
{
.compatible = "himax,hx8369",
.data = hx8369_lcd_init,
},
{},
};
MODULE_DEVICE_TABLE(of, hx8357_dt_ids);
int mxc_hx8357_init(struct mxc_dispdrv_handle *handle, struct mxc_dispdrv_setting *setting)
{
int ret = -1;
struct hx8357_data *lcd = container_of(handle->drv, struct hx8357_data, driver);
lcd->fbi = setting->fbi;
if(lcd->init){
ret = lcd->init(lcd);
if (ret) {
dev_err(&lcd->dev, "Couldn't initialize panel\n");
}
}
return ret;
}
/* display driver enable function for extension */
int mxc_hx8357_enable (struct mxc_dispdrv_handle *handle, struct fb_info *fbi)
{
struct hx8357_data *lcd = container_of(handle->drv, struct hx8357_data, driver);
usleep_range(1000,2000);
return hx8357_set_power(lcd, FB_BLANK_UNBLANK);
}
/* display driver disable function, called at early part of fb_blank */
void mxc_hx8357_disable (struct mxc_dispdrv_handle *handle, struct fb_info *fbi)
{
struct hx8357_data *lcd = container_of(handle->drv, struct hx8357_data, driver);
hx8357_set_power(lcd, FB_BLANK_POWERDOWN);
usleep_range(1000,2000);
}
/* display driver setup function, called at early part of fb_set_par */
int mxc_hx8357_setup (struct mxc_dispdrv_handle *handle, struct fb_info *fbi)
{
return 0;
}
int hx8357_init(struct spi_device *spi)
{
struct hx8357_data *lcd;
const struct of_device_id *match;
int i, ret;
lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
if (!lcd) {
dev_err(&spi->dev, "Couldn't allocate lcd internal structure!\n");
return -ENOMEM;
}
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "SPI setup failed.\n");
return ret;
}
lcd->spi = spi;
lcd->dev = spi->dev;
match = of_match_device(hx8357_dt_ids, &spi->dev);
if (!match || !match->data)
return -EINVAL;
lcd->reset = of_get_named_gpio(spi->dev.of_node, "gpios-reset", 0);
if (!gpio_is_valid(lcd->reset)) {
dev_err(&spi->dev, "Missing dt property: gpios-reset\n");
return -EINVAL;
}
ret = devm_gpio_request_one(&spi->dev, lcd->reset,
GPIOF_OUT_INIT_HIGH,
"hx8357-reset");
if (ret) {
dev_err(&spi->dev,
"failed to request gpio %d: %d\n",
lcd->reset, ret);
return -EINVAL;
}
if (of_find_property(spi->dev.of_node, "im-gpios", NULL)) {
lcd->use_im_pins = 1;
for (i = 0; i < HX8357_NUM_IM_PINS; i++) {
lcd->im_pins[i] = of_get_named_gpio(spi->dev.of_node,
"im-gpios", i);
if (lcd->im_pins[i] == -EPROBE_DEFER) {
dev_info(&spi->dev, "GPIO requested is not here yet, deferring the probe\n");
return -EPROBE_DEFER;
}
if (!gpio_is_valid(lcd->im_pins[i])) {
dev_err(&spi->dev, "Missing dt property: im-gpios\n");
return -EINVAL;
}
ret = devm_gpio_request_one(&spi->dev, lcd->im_pins[i],
GPIOF_OUT_INIT_LOW,
"im_pins");
if (ret) {
dev_err(&spi->dev, "failed to request gpio %d: %d\n",
lcd->im_pins[i], ret);
return -EINVAL;
}
}
} else {
lcd->use_im_pins = 0;
}
lcd->driver.name = "hx8357";
lcd->driver.init = mxc_hx8357_init;
lcd->driver.enable = mxc_hx8357_enable;
lcd->driver.disable = mxc_hx8357_disable;
lcd->driver.setup = mxc_hx8357_setup;
lcd->handle = mxc_dispdrv_register(&lcd->driver);
spi_set_drvdata(spi, lcd);
lcd->init = match->data;
dev_set_drvdata(&lcd->dev, lcd);
ret = device_create_file(&lcd->dev, &dev_attr_hxid);
if (ret) {
dev_err(&spi->dev, "Failed to generate sys file hxid");
goto init_error;
}
ret = device_create_file(&lcd->dev, &dev_attr_panelid);
if (ret) {
dev_err(&spi->dev, "Failed to generate sys file panelid");
goto sysfs_exit;
}
dev_info(&spi->dev, "Panel probed.\n");
return 0;
sysfs_exit:
device_remove_file(&lcd->dev, &dev_attr_hxid);
init_error:
mxc_dispdrv_unregister(lcd->handle);
return ret;
}
static int hx8357_remove(struct spi_device *spi)
{
struct hx8357_data *lcd = spi_get_drvdata(spi);
mxc_dispdrv_unregister(lcd->handle);
return 0;
}
static struct spi_driver hx8357_driver = {
.probe = hx8357_init,
.remove = hx8357_remove,
.driver = {
.name = "hx8357",
.of_match_table = of_match_ptr(hx8357_dt_ids),
},
};
module_spi_driver(hx8357_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Himax HX-8357 LCD Driver");
MODULE_LICENSE("GPL");