diff options
-rw-r--r-- | drivers/video/omap2/displays/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/omap2/displays/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/omap2/displays/panel-n8x0.c | 747 | ||||
-rw-r--r-- | include/video/omap-panel-n8x0.h | 15 |
4 files changed, 770 insertions, 0 deletions
diff --git a/drivers/video/omap2/displays/Kconfig b/drivers/video/omap2/displays/Kconfig index 16e3eab444c4..0760c0448a06 100644 --- a/drivers/video/omap2/displays/Kconfig +++ b/drivers/video/omap2/displays/Kconfig @@ -62,4 +62,11 @@ config PANEL_ACX565AKM select BACKLIGHT_CLASS_DEVICE help This is the LCD panel used on Nokia N900 + +config PANEL_N8X0 + tristate "N8X0 Panel" + depends on OMAP2_DSS_RFBI && SPI + select BACKLIGHT_CLASS_DEVICE + help + This is the LCD panel used on Nokia N8x0 endmenu diff --git a/drivers/video/omap2/displays/Makefile b/drivers/video/omap2/displays/Makefile index abfda35d82d4..fbfafc6eebb4 100644 --- a/drivers/video/omap2/displays/Makefile +++ b/drivers/video/omap2/displays/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_PANEL_TAAL) += panel-taal.o obj-$(CONFIG_PANEL_PICODLP) += panel-picodlp.o obj-$(CONFIG_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o obj-$(CONFIG_PANEL_ACX565AKM) += panel-acx565akm.o +obj-$(CONFIG_PANEL_N8X0) += panel-n8x0.o diff --git a/drivers/video/omap2/displays/panel-n8x0.c b/drivers/video/omap2/displays/panel-n8x0.c new file mode 100644 index 000000000000..150e8bae35a1 --- /dev/null +++ b/drivers/video/omap2/displays/panel-n8x0.c @@ -0,0 +1,747 @@ +/* #define DEBUG */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/backlight.h> +#include <linux/fb.h> + +#include <video/omapdss.h> +#include <video/omap-panel-n8x0.h> + +#define BLIZZARD_REV_CODE 0x00 +#define BLIZZARD_CONFIG 0x02 +#define BLIZZARD_PLL_DIV 0x04 +#define BLIZZARD_PLL_LOCK_RANGE 0x06 +#define BLIZZARD_PLL_CLOCK_SYNTH_0 0x08 +#define BLIZZARD_PLL_CLOCK_SYNTH_1 0x0a +#define BLIZZARD_PLL_MODE 0x0c +#define BLIZZARD_CLK_SRC 0x0e +#define BLIZZARD_MEM_BANK0_ACTIVATE 0x10 +#define BLIZZARD_MEM_BANK0_STATUS 0x14 +#define BLIZZARD_PANEL_CONFIGURATION 0x28 +#define BLIZZARD_HDISP 0x2a +#define BLIZZARD_HNDP 0x2c +#define BLIZZARD_VDISP0 0x2e +#define BLIZZARD_VDISP1 0x30 +#define BLIZZARD_VNDP 0x32 +#define BLIZZARD_HSW 0x34 +#define BLIZZARD_VSW 0x38 +#define BLIZZARD_DISPLAY_MODE 0x68 +#define BLIZZARD_INPUT_WIN_X_START_0 0x6c +#define BLIZZARD_DATA_SOURCE_SELECT 0x8e +#define BLIZZARD_DISP_MEM_DATA_PORT 0x90 +#define BLIZZARD_DISP_MEM_READ_ADDR0 0x92 +#define BLIZZARD_POWER_SAVE 0xE6 +#define BLIZZARD_NDISP_CTRL_STATUS 0xE8 + +/* Data source select */ +/* For S1D13745 */ +#define BLIZZARD_SRC_WRITE_LCD_BACKGROUND 0x00 +#define BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE 0x01 +#define BLIZZARD_SRC_WRITE_OVERLAY_ENABLE 0x04 +#define BLIZZARD_SRC_DISABLE_OVERLAY 0x05 +/* For S1D13744 */ +#define BLIZZARD_SRC_WRITE_LCD 0x00 +#define BLIZZARD_SRC_BLT_LCD 0x06 + +#define BLIZZARD_COLOR_RGB565 0x01 +#define BLIZZARD_COLOR_YUV420 0x09 + +#define BLIZZARD_VERSION_S1D13745 0x01 /* Hailstorm */ +#define BLIZZARD_VERSION_S1D13744 0x02 /* Blizzard */ + +#define MIPID_CMD_READ_DISP_ID 0x04 +#define MIPID_CMD_READ_RED 0x06 +#define MIPID_CMD_READ_GREEN 0x07 +#define MIPID_CMD_READ_BLUE 0x08 +#define MIPID_CMD_READ_DISP_STATUS 0x09 +#define MIPID_CMD_RDDSDR 0x0F +#define MIPID_CMD_SLEEP_IN 0x10 +#define MIPID_CMD_SLEEP_OUT 0x11 +#define MIPID_CMD_DISP_OFF 0x28 +#define MIPID_CMD_DISP_ON 0x29 + +static struct panel_drv_data { + struct mutex lock; + + struct omap_dss_device *dssdev; + struct spi_device *spidev; + struct backlight_device *bldev; + + int blizzard_ver; +} s_drv_data; + + +static inline +struct panel_n8x0_data *get_board_data(const struct omap_dss_device *dssdev) +{ + return dssdev->data; +} + +static inline +struct panel_drv_data *get_drv_data(const struct omap_dss_device *dssdev) +{ + return &s_drv_data; +} + + +static inline void blizzard_cmd(u8 cmd) +{ + omap_rfbi_write_command(&cmd, 1); +} + +static inline void blizzard_write(u8 cmd, const u8 *buf, int len) +{ + omap_rfbi_write_command(&cmd, 1); + omap_rfbi_write_data(buf, len); +} + +static inline void blizzard_read(u8 cmd, u8 *buf, int len) +{ + omap_rfbi_write_command(&cmd, 1); + omap_rfbi_read_data(buf, len); +} + +static u8 blizzard_read_reg(u8 cmd) +{ + u8 data; + blizzard_read(cmd, &data, 1); + return data; +} + +static void blizzard_ctrl_setup_update(struct omap_dss_device *dssdev, + int x, int y, int w, int h) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + u8 tmp[18]; + int x_end, y_end; + + x_end = x + w - 1; + y_end = y + h - 1; + + tmp[0] = x; + tmp[1] = x >> 8; + tmp[2] = y; + tmp[3] = y >> 8; + tmp[4] = x_end; + tmp[5] = x_end >> 8; + tmp[6] = y_end; + tmp[7] = y_end >> 8; + + /* scaling? */ + tmp[8] = x; + tmp[9] = x >> 8; + tmp[10] = y; + tmp[11] = y >> 8; + tmp[12] = x_end; + tmp[13] = x_end >> 8; + tmp[14] = y_end; + tmp[15] = y_end >> 8; + + tmp[16] = BLIZZARD_COLOR_RGB565; + + if (ddata->blizzard_ver == BLIZZARD_VERSION_S1D13745) + tmp[17] = BLIZZARD_SRC_WRITE_LCD_BACKGROUND; + else + tmp[17] = ddata->blizzard_ver == BLIZZARD_VERSION_S1D13744 ? + BLIZZARD_SRC_WRITE_LCD : + BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE; + + omap_rfbi_configure(dssdev, 16, 8); + + blizzard_write(BLIZZARD_INPUT_WIN_X_START_0, tmp, 18); + + omap_rfbi_configure(dssdev, 16, 16); +} + +static void mipid_transfer(struct spi_device *spi, int cmd, const u8 *wbuf, + int wlen, u8 *rbuf, int rlen) +{ + struct spi_message m; + struct spi_transfer *x, xfer[4]; + u16 w; + int r; + + spi_message_init(&m); + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + cmd &= 0xff; + x->tx_buf = &cmd; + x->bits_per_word = 9; + x->len = 2; + spi_message_add_tail(x, &m); + + if (wlen) { + x++; + x->tx_buf = wbuf; + x->len = wlen; + x->bits_per_word = 9; + spi_message_add_tail(x, &m); + } + + if (rlen) { + x++; + x->rx_buf = &w; + x->len = 1; + spi_message_add_tail(x, &m); + + if (rlen > 1) { + /* Arrange for the extra clock before the first + * data bit. + */ + x->bits_per_word = 9; + x->len = 2; + + x++; + x->rx_buf = &rbuf[1]; + x->len = rlen - 1; + spi_message_add_tail(x, &m); + } + } + + r = spi_sync(spi, &m); + if (r < 0) + dev_dbg(&spi->dev, "spi_sync %d\n", r); + + if (rlen) + rbuf[0] = w & 0xff; +} + +static inline void mipid_cmd(struct spi_device *spi, int cmd) +{ + mipid_transfer(spi, cmd, NULL, 0, NULL, 0); +} + +static inline void mipid_write(struct spi_device *spi, + int reg, const u8 *buf, int len) +{ + mipid_transfer(spi, reg, buf, len, NULL, 0); +} + +static inline void mipid_read(struct spi_device *spi, + int reg, u8 *buf, int len) +{ + mipid_transfer(spi, reg, NULL, 0, buf, len); +} + +static void set_data_lines(struct spi_device *spi, int data_lines) +{ + u16 par; + + switch (data_lines) { + case 16: + par = 0x150; + break; + case 18: + par = 0x160; + break; + case 24: + par = 0x170; + break; + } + + mipid_write(spi, 0x3a, (u8 *)&par, 2); +} + +static void send_init_string(struct spi_device *spi) +{ + u16 initpar[] = { 0x0102, 0x0100, 0x0100 }; + mipid_write(spi, 0xc2, (u8 *)initpar, sizeof(initpar)); +} + +static void send_display_on(struct spi_device *spi) +{ + mipid_cmd(spi, MIPID_CMD_DISP_ON); +} + +static void send_display_off(struct spi_device *spi) +{ + mipid_cmd(spi, MIPID_CMD_DISP_OFF); +} + +static void send_sleep_out(struct spi_device *spi) +{ + mipid_cmd(spi, MIPID_CMD_SLEEP_OUT); + msleep(120); +} + +static void send_sleep_in(struct spi_device *spi) +{ + mipid_cmd(spi, MIPID_CMD_SLEEP_IN); + msleep(50); +} + +static int n8x0_panel_power_on(struct omap_dss_device *dssdev) +{ + int r; + struct panel_n8x0_data *bdata = get_board_data(dssdev); + struct panel_drv_data *ddata = get_drv_data(dssdev); + struct spi_device *spi = ddata->spidev; + u8 rev, conf; + u8 display_id[3]; + const char *panel_name; + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + return 0; + + gpio_direction_output(bdata->ctrl_pwrdown, 1); + + if (bdata->platform_enable) { + r = bdata->platform_enable(dssdev); + if (r) + goto err_plat_en; + } + + r = omapdss_rfbi_display_enable(dssdev); + if (r) + goto err_rfbi_en; + + rev = blizzard_read_reg(BLIZZARD_REV_CODE); + conf = blizzard_read_reg(BLIZZARD_CONFIG); + + switch (rev & 0xfc) { + case 0x9c: + ddata->blizzard_ver = BLIZZARD_VERSION_S1D13744; + dev_info(&dssdev->dev, "s1d13744 LCD controller rev %d " + "initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07); + break; + case 0xa4: + ddata->blizzard_ver = BLIZZARD_VERSION_S1D13745; + dev_info(&dssdev->dev, "s1d13745 LCD controller rev %d " + "initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07); + break; + default: + dev_err(&dssdev->dev, "invalid s1d1374x revision %02x\n", rev); + r = -ENODEV; + goto err_inv_chip; + } + + /* panel */ + + gpio_direction_output(bdata->panel_reset, 1); + + mipid_read(spi, MIPID_CMD_READ_DISP_ID, display_id, 3); + dev_dbg(&spi->dev, "MIPI display ID: %02x%02x%02x\n", + display_id[0], display_id[1], display_id[2]); + + switch (display_id[0]) { + case 0x45: + panel_name = "lph8923"; + break; + case 0x83: + panel_name = "ls041y3"; + break; + default: + dev_err(&dssdev->dev, "invalid display ID 0x%x\n", + display_id[0]); + r = -ENODEV; + goto err_inv_panel; + } + + dev_info(&dssdev->dev, "%s rev %02x LCD detected\n", + panel_name, display_id[1]); + + send_sleep_out(spi); + send_init_string(spi); + set_data_lines(spi, 24); + send_display_on(spi); + + return 0; + +err_inv_panel: + /* + * HACK: we should turn off the panel here, but there is some problem + * with the initialization sequence, and we fail to init the panel if we + * have turned it off + */ + /* gpio_direction_output(bdata->panel_reset, 0); */ +err_inv_chip: + omapdss_rfbi_display_disable(dssdev); +err_rfbi_en: + if (bdata->platform_disable) + bdata->platform_disable(dssdev); +err_plat_en: + gpio_direction_output(bdata->ctrl_pwrdown, 0); + return r; +} + +static void n8x0_panel_power_off(struct omap_dss_device *dssdev) +{ + struct panel_n8x0_data *bdata = get_board_data(dssdev); + struct panel_drv_data *ddata = get_drv_data(dssdev); + struct spi_device *spi = ddata->spidev; + + if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) + return; + + send_display_off(spi); + send_sleep_in(spi); + + if (bdata->platform_disable) + bdata->platform_disable(dssdev); + + /* + * HACK: we should turn off the panel here, but there is some problem + * with the initialization sequence, and we fail to init the panel if we + * have turned it off + */ + /* gpio_direction_output(bdata->panel_reset, 0); */ + gpio_direction_output(bdata->ctrl_pwrdown, 0); + omapdss_rfbi_display_disable(dssdev); +} + +static const struct rfbi_timings n8x0_panel_timings = { + .cs_on_time = 0, + + .we_on_time = 9000, + .we_off_time = 18000, + .we_cycle_time = 36000, + + .re_on_time = 9000, + .re_off_time = 27000, + .re_cycle_time = 36000, + + .access_time = 27000, + .cs_off_time = 36000, + + .cs_pulse_width = 0, +}; + +static int n8x0_bl_update_status(struct backlight_device *dev) +{ + struct omap_dss_device *dssdev = dev_get_drvdata(&dev->dev); + struct panel_n8x0_data *bdata = get_board_data(dssdev); + struct panel_drv_data *ddata = get_drv_data(dssdev); + int r; + int level; + + mutex_lock(&ddata->lock); + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + level = dev->props.brightness; + else + level = 0; + + dev_dbg(&dssdev->dev, "update brightness to %d\n", level); + + if (!bdata->set_backlight) + r = -EINVAL; + else + r = bdata->set_backlight(dssdev, level); + + mutex_unlock(&ddata->lock); + + return r; +} + +static int n8x0_bl_get_intensity(struct backlight_device *dev) +{ + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + return dev->props.brightness; + + return 0; +} + +static const struct backlight_ops n8x0_bl_ops = { + .get_brightness = n8x0_bl_get_intensity, + .update_status = n8x0_bl_update_status, +}; + +static int n8x0_panel_probe(struct omap_dss_device *dssdev) +{ + struct panel_n8x0_data *bdata = get_board_data(dssdev); + struct panel_drv_data *ddata; + struct backlight_device *bldev; + struct backlight_properties props; + int r; + + dev_dbg(&dssdev->dev, "probe\n"); + + if (!bdata) + return -EINVAL; + + s_drv_data.dssdev = dssdev; + + ddata = &s_drv_data; + + mutex_init(&ddata->lock); + + dssdev->panel.config = OMAP_DSS_LCD_TFT; + dssdev->panel.timings.x_res = 800; + dssdev->panel.timings.y_res = 480; + dssdev->ctrl.pixel_size = 16; + dssdev->ctrl.rfbi_timings = n8x0_panel_timings; + + memset(&props, 0, sizeof(props)); + props.max_brightness = 127; + props.type = BACKLIGHT_PLATFORM; + bldev = backlight_device_register(dev_name(&dssdev->dev), &dssdev->dev, + dssdev, &n8x0_bl_ops, &props); + if (IS_ERR(bldev)) { + r = PTR_ERR(bldev); + dev_err(&dssdev->dev, "register backlight failed\n"); + return r; + } + + ddata->bldev = bldev; + + bldev->props.fb_blank = FB_BLANK_UNBLANK; + bldev->props.power = FB_BLANK_UNBLANK; + bldev->props.brightness = 127; + + n8x0_bl_update_status(bldev); + + return 0; +} + +static void n8x0_panel_remove(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + struct backlight_device *bldev; + + dev_dbg(&dssdev->dev, "remove\n"); + + bldev = ddata->bldev; + bldev->props.power = FB_BLANK_POWERDOWN; + n8x0_bl_update_status(bldev); + backlight_device_unregister(bldev); + + dev_set_drvdata(&dssdev->dev, NULL); +} + +static int n8x0_panel_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + int r; + + dev_dbg(&dssdev->dev, "enable\n"); + + mutex_lock(&ddata->lock); + + rfbi_bus_lock(); + + r = n8x0_panel_power_on(dssdev); + + rfbi_bus_unlock(); + + if (r) { + mutex_unlock(&ddata->lock); + return r; + } + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + mutex_unlock(&ddata->lock); + + return 0; +} + +static void n8x0_panel_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + + dev_dbg(&dssdev->dev, "disable\n"); + + mutex_lock(&ddata->lock); + + rfbi_bus_lock(); + + n8x0_panel_power_off(dssdev); + + rfbi_bus_unlock(); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; + + mutex_unlock(&ddata->lock); +} + +static int n8x0_panel_suspend(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + + dev_dbg(&dssdev->dev, "suspend\n"); + + mutex_lock(&ddata->lock); + + rfbi_bus_lock(); + + n8x0_panel_power_off(dssdev); + + rfbi_bus_unlock(); + + dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; + + mutex_unlock(&ddata->lock); + + return 0; +} + +static int n8x0_panel_resume(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + int r; + + dev_dbg(&dssdev->dev, "resume\n"); + + mutex_lock(&ddata->lock); + + rfbi_bus_lock(); + + r = n8x0_panel_power_on(dssdev); + + rfbi_bus_unlock(); + + if (r) { + mutex_unlock(&ddata->lock); + return r; + } + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + mutex_unlock(&ddata->lock); + + return 0; +} + +static void n8x0_panel_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + *timings = dssdev->panel.timings; +} + +static void n8x0_panel_get_resolution(struct omap_dss_device *dssdev, + u16 *xres, u16 *yres) +{ + *xres = dssdev->panel.timings.x_res; + *yres = dssdev->panel.timings.y_res; +} + +static void update_done(void *data) +{ + rfbi_bus_unlock(); +} + +static int n8x0_panel_update(struct omap_dss_device *dssdev, + u16 x, u16 y, u16 w, u16 h) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + + dev_dbg(&dssdev->dev, "update\n"); + + mutex_lock(&ddata->lock); + rfbi_bus_lock(); + + omap_rfbi_prepare_update(dssdev, &x, &y, &w, &h); + + blizzard_ctrl_setup_update(dssdev, x, y, w, h); + + omap_rfbi_update(dssdev, x, y, w, h, update_done, NULL); + + mutex_unlock(&ddata->lock); + + return 0; +} + +static int n8x0_panel_sync(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = get_drv_data(dssdev); + + dev_dbg(&dssdev->dev, "sync\n"); + + mutex_lock(&ddata->lock); + rfbi_bus_lock(); + rfbi_bus_unlock(); + mutex_unlock(&ddata->lock); + + return 0; +} + +static struct omap_dss_driver n8x0_panel_driver = { + .probe = n8x0_panel_probe, + .remove = n8x0_panel_remove, + + .enable = n8x0_panel_enable, + .disable = n8x0_panel_disable, + .suspend = n8x0_panel_suspend, + .resume = n8x0_panel_resume, + + .update = n8x0_panel_update, + .sync = n8x0_panel_sync, + + .get_resolution = n8x0_panel_get_resolution, + .get_recommended_bpp = omapdss_default_get_recommended_bpp, + + .get_timings = n8x0_panel_get_timings, + + .driver = { + .name = "n8x0_panel", + .owner = THIS_MODULE, + }, +}; + +/* PANEL */ + +static int mipid_spi_probe(struct spi_device *spi) +{ + dev_dbg(&spi->dev, "mipid_spi_probe\n"); + + spi->mode = SPI_MODE_0; + + s_drv_data.spidev = spi; + + return 0; +} + +static int mipid_spi_remove(struct spi_device *spi) +{ + dev_dbg(&spi->dev, "mipid_spi_remove\n"); + return 0; +} + +static struct spi_driver mipid_spi_driver = { + .driver = { + .name = "lcd_mipid", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mipid_spi_probe, + .remove = __devexit_p(mipid_spi_remove), +}; + +static int __init n8x0_panel_drv_init(void) +{ + int r; + + r = spi_register_driver(&mipid_spi_driver); + if (r) { + pr_err("n8x0_panel: spi driver registration failed\n"); + return r; + } + + r = omap_dss_register_driver(&n8x0_panel_driver); + if (r) { + pr_err("n8x0_panel: dss driver registration failed\n"); + spi_unregister_driver(&mipid_spi_driver); + return r; + } + + return 0; +} + +static void __exit n8x0_panel_drv_exit(void) +{ + spi_unregister_driver(&mipid_spi_driver); + + omap_dss_unregister_driver(&n8x0_panel_driver); +} + +module_init(n8x0_panel_drv_init); +module_exit(n8x0_panel_drv_exit); +MODULE_LICENSE("GPL"); diff --git a/include/video/omap-panel-n8x0.h b/include/video/omap-panel-n8x0.h new file mode 100644 index 000000000000..50a1302e2cfd --- /dev/null +++ b/include/video/omap-panel-n8x0.h @@ -0,0 +1,15 @@ +#ifndef __OMAP_PANEL_N8X0_H +#define __OMAP_PANEL_N8X0_H + +struct omap_dss_device; + +struct panel_n8x0_data { + int (*platform_enable)(struct omap_dss_device *dssdev); + void (*platform_disable)(struct omap_dss_device *dssdev); + int panel_reset; + int ctrl_pwrdown; + + int (*set_backlight)(struct omap_dss_device *dssdev, int level); +}; + +#endif |