| /* |
| * Copyright (c) 2010 Nest Labs, Inc. |
| * |
| * Author: Grant Erickson <grant@nestlabs.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. |
| * |
| * 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, see |
| * <http://www.gnu.org/licenses/>. |
| * |
| * Description: |
| * This file is the LCD panel driver for the Giantplus GPM1145A |
| * 320 x 480 TFT LCD display panel using the Ilitek ILI9481 |
| * interface driver. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/gpio.h> |
| #include <linux/spi/ili9481.h> |
| #include <linux/spi/spi.h> |
| |
| #include <plat/display.h> |
| |
| /* Preprocessor Definitions */ |
| |
| /* |
| * Driver Strings |
| */ |
| #define GPM1145A0_DRIVER_NAME "Giantplus GPM1145A0 LCD Driver" |
| #define GPM1145A0_DRIVER_VERSION "2010-10-17" |
| |
| /* |
| * 32- and 8-bit encode and decode macros |
| */ |
| |
| #define U32_ENCODE(b3, b2, b1, b0) ((((b3) & 0xff) << 24) | \ |
| (((b2) & 0xff) << 16) | \ |
| (((b1) & 0xff) << 8) | \ |
| (((b0) & 0xff) << 0)) |
| #define U32_B3_DECODE(u32) (((u32) >> 24) & 0xFF) |
| #define U32_B2_DECODE(u32) (((u32) >> 16) & 0xFF) |
| #define U32_B1_DECODE(u32) (((u32) >> 8) & 0xFF) |
| #define U32_B0_DECODE(u32) (((u32) >> 0) & 0xFF) |
| |
| /* |
| * Ilitek ILI9481 Slave SPI Protocol Definitions |
| */ |
| #define ILI9481_SPI_XSCK_MAX 8333333 |
| #define ILI9481_SPI_MODE SPI_MODE_3 |
| #define ILI9481_SPI_BITS_PER_WORD 9 |
| #define ILI9481_SPI_BYTES_PER_WORD sizeof (ili9481_word) |
| |
| /* |
| * Ilitek ILI9481 Reset Timings |
| */ |
| #define ILI9481_TRES_LOW_MS_MIN 10 |
| #define ILI9481_TRES_HIGH_MS_MIN 50 |
| |
| /* |
| * Ilitek ILI9481 Commands |
| */ |
| |
| #define ILI9481_REG_VAL(bit, value) \ |
| ((value) << (bit)) |
| #define ILI9481_REG_VAL_ENCODE(shift, mask, value) \ |
| (((value) << (shift)) & (mask)) |
| |
| /* Control Commands */ |
| |
| #define ILI9481_OP_CMD_NOP 0x00 |
| #define ILI9481_OP_CMD_SOFT_RESET 0x01 |
| #define ILI9481_OP_CMD_ENTER_SLEEP_MODE 0x10 |
| #define ILI9481_OP_CMD_EXIT_SLEEP_MODE 0x11 |
| #define ILI9481_OP_CMD_ENTER_PARTIAL_MODE 0x12 |
| #define ILI9481_OP_CMD_ENTER_NORMAL_MODE 0x13 |
| #define ILI9481_OP_CMD_EXIT_INVERT_MODE 0x20 |
| #define ILI9481_OP_CMD_ENTER_INVERT_MODE 0x21 |
| #define ILI9481_OP_CMD_DISPLAY_OFF 0x28 |
| #define ILI9481_OP_CMD_DISPLAY_ON 0x29 |
| #define ILI9481_OP_CMD_TEAR_OFF 0x34 |
| #define ILI9481_OP_CMD_EXIT_IDLE_MODE 0x38 |
| #define ILI9481_OP_CMD_ENTER_IDLE_MODE 0x39 |
| |
| /* Read / Get Commands */ |
| |
| #define ILI9481_OP_GET_RED_CHANNEL 0x06 |
| #define ILI9481_OP_GET_GREEN_CHANNEL 0x07 |
| #define ILI9481_OP_GET_BLUE_CHANNEL 0x08 |
| #define ILI9481_OP_GET_POWER_MODE 0x0A |
| #define ILI9481_OP_GET_ADDRESS_MODE 0x0B |
| #define ILI9481_OP_GET_PIXEL_FORMAT 0x0C |
| #define ILI9481_OP_GET_DISPLAY_MODE 0x0D |
| #define ILI9481_OP_GET_SIGNAL_MODE 0x0E |
| #define ILI9481_OP_GET_DIAGNOSTIC_RESULT 0x0F |
| #define ILI9481_OP_GET_MEMORY_START 0x2E |
| #define ILI9481_OP_GET_MEMORY_CONTINUE 0x3E |
| #define ILI9481_OP_GET_SCANLINE 0x45 |
| #define ILI9481_OP_GET_DDB_START 0xA1 |
| #define ILI9481_OP_GET_DEVICE_CODE 0xBF |
| #define ILI9481_OP_GET_NV_MEMORY_STATUS 0xE2 |
| |
| /* Write / Set Commands */ |
| |
| #define ILI9481_OP_SET_GAMMA_CURVE 0x26 |
| #define ILI9481_OP_SET_COLUMN_ADDRESS 0x2A |
| #define ILI9481_OP_SET_PAGE_ADDRESS 0x2B |
| #define ILI9481_OP_SET_MEMORY_START 0x2C |
| #define ILI9481_OP_SET_LUT 0x2D |
| #define ILI9481_OP_SET_PARTIAL_AREA 0x30 |
| #define ILI9481_OP_SET_SCROLL_AREA 0x33 |
| #define ILI9481_OP_SET_TEAR_ON 0x35 |
| #define ILI9481_OP_SET_ADDRESS_MODE 0x36 |
| #define ILI9481_ADDRESS_MODE_PAGE_ADDR_ORDER_TTOB ILI9481_REG_VAL(7, 0) |
| #define ILI9481_ADDRESS_MODE_PAGE_ADDR_ORDER_BTOT ILI9481_REG_VAL(7, 1) |
| #define ILI9481_ADDRESS_MODE_COL_ADDR_ORDER_TTOB ILI9481_REG_VAL(6, 0) |
| #define ILI9481_ADDRESS_MODE_COL_ADDR_ORDER_BTOT ILI9481_REG_VAL(6, 1) |
| #define ILI9481_ADDRESS_MODE_PAGE_COL_NORMAL ILI9481_REG_VAL(5, 0) |
| #define ILI9481_ADDRESS_MODE_PAGE_COL_REVERSE ILI9481_REG_VAL(5, 1) |
| #define ILI9481_ADDRESS_MODE_LINE_ADDR_ORDER_TTOB ILI9481_REG_VAL(4, 0) |
| #define ILI9481_ADDRESS_MODE_LINE_ADDR_ORDER_BTOT ILI9481_REG_VAL(4, 1) |
| #define ILI9481_ADDRESS_MODE_PIXEL_ORDER_RGB ILI9481_REG_VAL(3, 0) |
| #define ILI9481_ADDRESS_MODE_PIXEL_ORDER_BGR ILI9481_REG_VAL(3, 1) |
| #define ILI9481_ADDRESS_MODE_HFLIP_OFF ILI9481_REG_VAL(1, 0) |
| #define ILI9481_ADDRESS_MODE_HFLIP_ON ILI9481_REG_VAL(1, 1) |
| #define ILI9481_ADDRESS_MODE_VFLIP_OFF ILI9481_REG_VAL(0, 0) |
| #define ILI9481_ADDRESS_MODE_VFLIP_ON ILI9481_REG_VAL(0, 1) |
| #define ILI9481_OP_SET_SCROLL_START 0x37 |
| #define ILI9481_OP_SET_PIXEL_FORMAT 0x3A |
| #define ILI9481_PIXEL_FORMAT_3BPP ILI9481_REG_VAL(0, 0x11) |
| #define ILI9481_PIXEL_FORMAT_16BPP ILI9481_REG_VAL(0, 0x55) |
| #define ILI9481_PIXEL_FORMAT_18BPP ILI9481_REG_VAL(0, 0x66) |
| #define ILI9481_OP_SET_MEMORY_CONTINUE 0x3C |
| #define ILI9481_OP_SET_TEAR_SCANLINE 0x44 |
| |
| /* Transfer Commands */ |
| |
| #define ILI9481_OP_XFR_COMMAND_ACCESS_PROTECT 0xB0 |
| #define ILI9481_OP_XFR_FRAME_MEM_ACCESS_IFACE_SETTING 0xB3 |
| #define ILI9481_OP_XFR_DISPLAY_MODE_FRAME_MEM_WR_MODE 0xB4 |
| #define ILI9481_DISPLAY_MODE_FRAME_MEM_WR_MODE_DM_SCLK ILI9481_REG_VAL(0, 0) |
| #define ILI9481_DISPLAY_MODE_FRAME_MEM_WR_MODE_DM_DPI ILI9481_REG_VAL(0, 1) |
| #define ILI9481_DISPLAY_MODE_FRAME_MEM_WR_MODE_RM_DBI ILI9481_REG_VAL(4, 0) |
| #define ILI9481_DISPLAY_MODE_FRAME_MEM_WR_MODE_RM_DPI ILI9481_REG_VAL(4, 1) |
| #define ILI9481_OP_XFR_PANEL_DRIVING_SETTING 0xC0 |
| #define ILI9481_OP_XFR_NORMAL_MODE_TIMING_SETTING 0xC1 |
| #define ILI9481_OP_XFR_PARTIAL_MODE_TIMING_SETTING 0xC2 |
| #define ILI9481_OP_XFR_IDLE_MODE_TIMING_SETTING 0xC3 |
| #define ILI9481_OP_XFR_FRAME_RATE_AND_INVERSION_CONTROL 0xC5 |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_125HZ ILI9481_REG_VAL(0, 0x0) |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_100HZ ILI9481_REG_VAL(0, 0x1) |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_85HZ ILI9481_REG_VAL(0, 0x2) |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_72HZ ILI9481_REG_VAL(0, 0x3) |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_56HZ ILI9481_REG_VAL(0, 0x4) |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_50HZ ILI9481_REG_VAL(0, 0x5) |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_45HZ ILI9481_REG_VAL(0, 0x6) |
| #define ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_42HZ ILI9481_REG_VAL(0, 0x7) |
| #define ILI9481_OP_XFR_INTERFACE_CONTROL 0xC6 |
| #define ILI9481_INTERFACE_CONTROL_DOUT_ENABLE ILI9481_REG_VAL(7, 0) |
| #define ILI9481_INTERFACE_CONTROL_DOUT_DISABLE ILI9481_REG_VAL(7, 1) |
| #define ILI9481_INTERFACE_CONTROL_VSPL_LO ILI9481_REG_VAL(4, 0) |
| #define ILI9481_INTERFACE_CONTROL_VSPL_HI ILI9481_REG_VAL(4, 1) |
| #define ILI9481_INTERFACE_CONTROL_HSPL_LO ILI9481_REG_VAL(3, 0) |
| #define ILI9481_INTERFACE_CONTROL_HSPL_HI ILI9481_REG_VAL(3, 1) |
| #define ILI9481_INTERFACE_CONTROL_EPL_LO ILI9481_REG_VAL(1, 0) |
| #define ILI9481_INTERFACE_CONTROL_EPL_HI ILI9481_REG_VAL(1, 1) |
| #define ILI9481_INTERFACE_CONTROL_DPL_RISING ILI9481_REG_VAL(0, 0) |
| #define ILI9481_INTERFACE_CONTROL_DPL_FALLING ILI9481_REG_VAL(0, 1) |
| |
| #define ILI9481_OP_XFR_GAMMA_SETTING 0xC8 |
| #define ILI9481_OP_XFR_POWER_SETTING 0xD0 |
| #define ILI9481_OP_XFR_VCOM_CONTROL 0xD1 |
| #define ILI9481_OP_XFR_NORMAL_MODE_POWER_SETTING 0xD2 |
| #define ILI9481_OP_XFR_PARTIAL_MODE_POWER_SETTING 0xD3 |
| #define ILI9481_OP_XFR_IDLE_MODE_POWER_SETTING 0xD4 |
| #define ILI9481_OP_XFR_NV_MEMORY_WRITE 0xE0 |
| #define ILI9481_OP_XFR_NV_MEMORY_CONTROL 0xE1 |
| #define ILI9481_OP_XFR_NV_MEMORY_PROTECTION 0xE3 |
| |
| /* |
| * All commands and read and write parameters are 9 bits. There are |
| * 8-bits of lower-order data and high-order bit, D/CX. For commands, |
| * D/CX is set to 0, for parameters, it is set to 1. To accomodate |
| * this, we need 16-bits for each parameter. |
| */ |
| |
| #define ILI9481_COMMAND_MASK 0xff |
| #define ILI9481_PARAM_MASK 0xff |
| |
| #define ILI9481_COMMAND_DCX 0x00 |
| #define ILI9481_PARAM_DCX 0x01 |
| |
| #define ILI9481_WORD_ENCODE(dcx, mask, x) (((dcx) << 8) | ((x) & (mask))) |
| #define ILI9481_WORD_DECODE(x, mask) ((x) & (mask)) |
| |
| #define ILI9481_COMMAND_ENCODE(x) ILI9481_WORD_ENCODE(ILI9481_COMMAND_DCX, ILI9481_COMMAND_MASK, x) |
| #define ILI9481_PARAM_ENCODE(x) ILI9481_WORD_ENCODE(ILI9481_PARAM_DCX, ILI9481_PARAM_MASK, x) |
| #define ILI9481_PARAM_DECODE(x) ILI9481_WORD_DECODE(x, ILI9481_PARAM_MASK) |
| |
| #define ILI9481_PARAMS_TO_BYTES(n) ((n) * ILI9481_SPI_BYTES_PER_WORD) |
| #define ILI9481_BYTES_TO_PARAMS(b) ((b) / ILI9481_SPI_BYTES_PER_WORD) |
| |
| #define FFRAMEHZ 60 |
| #define TFRAMEMS (1000 / FFRAMEHZ) |
| |
| #define fdelay(frames) mdelay(frames * TFRAMEMS) |
| |
| /* Type Definitions */ |
| |
| struct ili9481_device { |
| int enabled:1, |
| suspended:1; |
| struct spi_device * spi; |
| struct omap_dss_device * dssdev; |
| struct backlight_device * backlight; |
| }; |
| |
| typedef u16 ili9481_word; |
| |
| /* Function Prototypes */ |
| |
| static int gpm1145a0_dss_probe(struct omap_dss_device *dssdev); |
| static void gpm1145a0_dss_remove(struct omap_dss_device *dssdev); |
| static int gpm1145a0_dss_enable(struct omap_dss_device *dssdev); |
| static void gpm1145a0_dss_disable(struct omap_dss_device *dssdev); |
| static int gpm1145a0_dss_suspend(struct omap_dss_device *dssdev); |
| static int gpm1145a0_dss_resume(struct omap_dss_device *dssdev); |
| |
| static int ili9481_spi_probe(struct spi_device *spi); |
| static int ili9481_spi_remove(struct spi_device *spi); |
| |
| /* Global Variables */ |
| |
| static struct omap_video_timings giantplus_gpm1145a0_timings = { |
| .x_res = 320, // Horizontal resolution, pixels |
| .y_res = 480, // Vertical resolution, pixels |
| |
| .pixel_clock = 8000, // Pixel clock, kHz |
| |
| .hsw = 2, // Horizontal synchronization pulse width |
| .hfp = 3, // Horizontal front porch, pixels clocks |
| .hbp = 3, // Horizontal back porch, pixel clocks |
| |
| .vsw = 2, // Vertical synchronization pulse width |
| .vfp = 4, // Vertical front porch, line clocks |
| .vbp = 2, // Vertical back porch, line clocks |
| }; |
| |
| static struct omap_dss_driver giantplus_gpm1145a0_driver = { |
| .probe = gpm1145a0_dss_probe, |
| .remove = gpm1145a0_dss_remove, |
| |
| .enable = gpm1145a0_dss_enable, |
| .disable = gpm1145a0_dss_disable, |
| .suspend = gpm1145a0_dss_suspend, |
| .resume = gpm1145a0_dss_resume, |
| |
| .driver = { |
| .name = "giantplus_gpm1145a0", |
| .owner = THIS_MODULE, |
| } |
| }; |
| |
| static struct spi_driver ilitek_ili9481_spi_driver = { |
| .driver = { |
| .name = "ili9481", |
| .bus = &spi_bus_type, |
| .owner = THIS_MODULE, |
| }, |
| .probe = ili9481_spi_probe, |
| .remove = __devexit_p(ili9481_spi_remove), |
| }; |
| |
| static struct ili9481_device ili9481_dev; |
| |
| static void ili9481_transfer(struct ili9481_device * id, u8 operation, |
| const u8 *wbuf, unsigned int wlen, |
| u8 *rbuf, unsigned int rlen) |
| { |
| struct spi_message m; |
| struct spi_transfer *x, xfer[4]; |
| ili9481_word command; |
| ili9481_word word; |
| int status; |
| |
| BUG_ON(id->spi == NULL); |
| |
| dev_dbg(&id->spi->dev, "%s transfer %#02x %u byte%s out @ %p, " |
| "%u byte%s in @ %p.\n", |
| ((wbuf != NULL) ? |
| ((rbuf != NULL) ? "Bi-directional" : "Write") : |
| ((rbuf != NULL) ? "Read" : "Command")), |
| operation, |
| wlen, ((wlen == 1) ? "" : "s"), wbuf, |
| rlen, ((rlen == 1) ? "" : "s"), rbuf); |
| |
| spi_message_init(&m); |
| |
| memset(xfer, 0, sizeof(xfer)); |
| x = &xfer[0]; |
| |
| // At minimum, we have a command, read, write or bi-directional |
| // transfer operation to handle that requires a single send |
| // phase. Handle that phase. |
| |
| command = ILI9481_COMMAND_ENCODE(operation); |
| |
| x->tx_buf = &command; |
| x->bits_per_word = ILI9481_SPI_BITS_PER_WORD; |
| x->len = ILI9481_SPI_BYTES_PER_WORD; |
| |
| spi_message_add_tail(x, &m); |
| |
| // If we have a write or bi-directional transfer operation, handle |
| // the send phase for it. |
| |
| if ((wlen > 0) && (wbuf != NULL)) { |
| x++; |
| |
| x->tx_buf = wbuf; |
| x->bits_per_word = ILI9481_SPI_BITS_PER_WORD; |
| x->len = wlen; |
| |
| spi_message_add_tail(x, &m); |
| } |
| |
| // If we have a read or bi-directional transfer operation, handle |
| // the receive phase for it. |
| |
| if ((rlen > 0) && (rbuf != NULL)) { |
| x++; |
| x->rx_buf = &word; |
| x->len = 1; |
| |
| spi_message_add_tail(x, &m); |
| |
| // Arrange for the extra clock before the first |
| // data bit. |
| |
| if (rlen > 1) { |
| x->bits_per_word = ILI9481_SPI_BITS_PER_WORD; |
| x->len = ILI9481_SPI_BYTES_PER_WORD; |
| |
| x++; |
| x->rx_buf = &rbuf[1]; |
| x->len = rlen - 1; |
| |
| spi_message_add_tail(x, &m); |
| } |
| } |
| |
| status = spi_sync(id->spi, &m); |
| |
| if (status < 0) { |
| dev_warn(&id->spi->dev, "ILI9481 SPI transfer failed with status %d\n", |
| status); |
| goto done; |
| } |
| |
| if (rlen) { |
| rbuf[0] = ILI9481_PARAM_DECODE(word); |
| } |
| |
| done: |
| return; |
| } |
| |
| static void ili9481_command(struct ili9481_device * id, u8 operation) |
| { |
| ili9481_transfer(id, operation, NULL, 0, NULL, 0); |
| } |
| |
| static void ili9481_read(struct ili9481_device * id, u8 operation, |
| u8 *buffer, unsigned int length) |
| { |
| ili9481_transfer(id, operation, NULL, 0, buffer, length); |
| } |
| |
| static void ili9481_write(struct ili9481_device * id, u8 operation, |
| const u8 *buffer, unsigned int length) |
| { |
| ili9481_transfer(id, operation, buffer, length, NULL, 0); |
| } |
| |
| static void ili9481_reset(unsigned long gpio, bool inverted) |
| { |
| const bool asserted = inverted; |
| |
| // First, ensure the reset line is deasserted for a "sufficiently |
| // long" time as we don't know it's initial condition. Then, |
| // assert it for at least the required time. Finally, deassert it |
| // again for at least the required time. |
| |
| gpio_set_value(gpio, !asserted); |
| mdelay(4); |
| |
| gpio_set_value(gpio, asserted); |
| mdelay(ILI9481_TRES_LOW_MS_MIN * 2); |
| |
| gpio_set_value(gpio, !asserted); |
| mdelay(ILI9481_TRES_HIGH_MS_MIN * 2); |
| } |
| |
| static bool ili9481_detect(struct ili9481_device * id) |
| { |
| const u8 length = 6; |
| u8 buffer[length]; |
| u32 code = 0; |
| const char * name = NULL; |
| bool detected = false; |
| |
| ili9481_read(id, ILI9481_OP_GET_DEVICE_CODE, buffer, length); |
| |
| code = U32_ENCODE(buffer[1], buffer[2], buffer[3], buffer[4]); |
| |
| switch (code) { |
| |
| case 0x02049481: |
| name = "Giantplus GPM1145A0"; |
| detected = true; |
| break; |
| |
| default: |
| dev_err(&id->spi->dev, "Unrecognized display ID: %08x\n", code); |
| name = NULL; |
| detected = false; |
| break; |
| |
| } |
| |
| if (name != NULL) { |
| dev_info(&id->spi->dev, "%s (%08x) LCD detected.\n", name, code); |
| } |
| |
| return (detected); |
| } |
| |
| static void ili9481_power_on(struct ili9481_device * id) |
| { |
| int status = 0; |
| const unsigned int maxparam = 12; |
| ili9481_word params[maxparam]; |
| unsigned int nparams; |
| struct omap_dss_device *dss; |
| struct ili9481_platform_data *pdata; |
| u32 width, height; |
| |
| dss = id->dssdev; |
| pdata = id->spi->dev.platform_data; |
| |
| // First, allow the platform to do any necessary steps (turn on |
| // rails, etc.). |
| |
| if (dss->platform_enable) { |
| status = dss->platform_enable(dss); |
| |
| if (status) { |
| dev_err(&dss->dev, "The platform failed to enable the display.\n"); |
| goto done; |
| } |
| } |
| |
| // Then, wait at least 1 ms. |
| |
| mdelay(1 * 2); |
| |
| // Next, issue the power-on reset. |
| |
| ili9481_reset(pdata->reset.gpio, pdata->reset.inverted); |
| |
| // Then, issue a soft reset and wait at least 20 ms. |
| |
| ili9481_command(id, ILI9481_OP_CMD_SOFT_RESET); |
| |
| mdelay(20 * 2); |
| |
| // Then, send the exit sleep mode command and wait at least 80 ms. |
| |
| ili9481_command(id, ILI9481_OP_CMD_EXIT_SLEEP_MODE); |
| |
| mdelay(80 * 2); |
| |
| // Then, program the display settings and power supply operation. |
| |
| params[0] = ILI9481_PARAM_ENCODE(ILI9481_ADDRESS_MODE_PAGE_ADDR_ORDER_BTOT | |
| ILI9481_ADDRESS_MODE_COL_ADDR_ORDER_TTOB | |
| ILI9481_ADDRESS_MODE_PAGE_COL_NORMAL | |
| ILI9481_ADDRESS_MODE_LINE_ADDR_ORDER_TTOB | |
| ILI9481_ADDRESS_MODE_PIXEL_ORDER_BGR | |
| ILI9481_ADDRESS_MODE_HFLIP_OFF | |
| ILI9481_ADDRESS_MODE_VFLIP_OFF); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_SET_ADDRESS_MODE, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(ILI9481_PIXEL_FORMAT_18BPP); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_SET_PIXEL_FORMAT, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x07); |
| params[1] = ILI9481_PARAM_ENCODE(0x42); |
| params[2] = ILI9481_PARAM_ENCODE(0x1b); |
| nparams = 3; |
| ili9481_write(id, |
| ILI9481_OP_XFR_POWER_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x00); |
| params[1] = ILI9481_PARAM_ENCODE(0x24); |
| params[2] = ILI9481_PARAM_ENCODE(0x12); |
| nparams = 3; |
| ili9481_write(id, |
| ILI9481_OP_XFR_VCOM_CONTROL, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x02); |
| params[1] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 2; |
| ili9481_write(id, |
| ILI9481_OP_XFR_NORMAL_MODE_POWER_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x01); |
| params[1] = ILI9481_PARAM_ENCODE(0x22); |
| nparams = 2; |
| ili9481_write(id, |
| ILI9481_OP_XFR_PARTIAL_MODE_POWER_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x01); |
| params[1] = ILI9481_PARAM_ENCODE(0x22); |
| nparams = 2; |
| ili9481_write(id, |
| ILI9481_OP_XFR_IDLE_MODE_POWER_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x10); |
| params[1] = ILI9481_PARAM_ENCODE(0x3b); |
| params[2] = ILI9481_PARAM_ENCODE(0x00); |
| params[3] = ILI9481_PARAM_ENCODE(0x02); |
| params[4] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 5; |
| ili9481_write(id, |
| ILI9481_OP_XFR_PANEL_DRIVING_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x10); |
| params[1] = ILI9481_PARAM_ENCODE(0x10); |
| params[2] = ILI9481_PARAM_ENCODE(0x22); |
| nparams = 3; |
| ili9481_write(id, |
| ILI9481_OP_XFR_NORMAL_MODE_TIMING_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(ILI9481_FRAME_RATE_AND_INVERSION_CONTROL_56HZ); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_XFR_FRAME_RATE_AND_INVERSION_CONTROL, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(ILI9481_INTERFACE_CONTROL_DOUT_DISABLE | |
| ILI9481_INTERFACE_CONTROL_VSPL_LO | |
| ILI9481_INTERFACE_CONTROL_HSPL_LO | |
| ILI9481_INTERFACE_CONTROL_EPL_HI | |
| ILI9481_INTERFACE_CONTROL_DPL_FALLING); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_XFR_INTERFACE_CONTROL, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x00); |
| params[1] = ILI9481_PARAM_ENCODE(0x37); |
| params[2] = ILI9481_PARAM_ENCODE(0x02); |
| params[3] = ILI9481_PARAM_ENCODE(0x26); |
| params[4] = ILI9481_PARAM_ENCODE(0x0b); |
| params[5] = ILI9481_PARAM_ENCODE(0x08); |
| params[6] = ILI9481_PARAM_ENCODE(0x46); |
| params[7] = ILI9481_PARAM_ENCODE(0x03); |
| params[8] = ILI9481_PARAM_ENCODE(0x77); |
| params[9] = ILI9481_PARAM_ENCODE(0x52); |
| params[10] = ILI9481_PARAM_ENCODE(0x00); |
| params[11] = ILI9481_PARAM_ENCODE(0x1e); |
| nparams = 12; |
| ili9481_write(id, |
| ILI9481_OP_XFR_GAMMA_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_XFR_NV_MEMORY_WRITE, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_XFR_NV_MEMORY_CONTROL, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x00); |
| params[1] = ILI9481_PARAM_ENCODE(0x00); |
| params[2] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 3; |
| ili9481_read(id, |
| ILI9481_OP_GET_NV_MEMORY_STATUS, |
| (u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x00); |
| params[1] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 2; |
| ili9481_write(id, |
| ILI9481_OP_XFR_NV_MEMORY_PROTECTION, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_XFR_COMMAND_ACCESS_PROTECT, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x02); |
| params[1] = ILI9481_PARAM_ENCODE(0x00); |
| params[2] = ILI9481_PARAM_ENCODE(0x00); |
| params[3] = ILI9481_PARAM_ENCODE(0x00); |
| nparams = 4; |
| ili9481_write(id, |
| ILI9481_OP_XFR_FRAME_MEM_ACCESS_IFACE_SETTING, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(ILI9481_DISPLAY_MODE_FRAME_MEM_WR_MODE_DM_DPI | |
| ILI9481_DISPLAY_MODE_FRAME_MEM_WR_MODE_RM_DPI); |
| nparams = 1; |
| ili9481_write(id, |
| ILI9481_OP_XFR_DISPLAY_MODE_FRAME_MEM_WR_MODE, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| // The following three power control commands are undocumented by |
| // either Ilitek or Giantplus |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x40); |
| params[1] = ILI9481_PARAM_ENCODE(0x0f); |
| nparams = 2; |
| ili9481_write(id, |
| 0xf3, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x80); |
| nparams = 1; |
| ili9481_write(id, |
| 0xf6, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| params[0] = ILI9481_PARAM_ENCODE(0x80); |
| params[1] = ILI9481_PARAM_ENCODE(0x01); |
| nparams = 2; |
| ili9481_write(id, |
| 0xf7, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| // Next, send the display on command. |
| |
| ili9481_command(id, ILI9481_OP_CMD_DISPLAY_ON); |
| |
| // Finally, prepare the display to receive write data, setting the |
| // column and page addresses to the display extents. |
| |
| width = id->dssdev->panel.timings.x_res; |
| |
| params[0] = ILI9481_PARAM_ENCODE(U32_B3_DECODE(width - 1)); |
| params[1] = ILI9481_PARAM_ENCODE(U32_B2_DECODE(width - 1)); |
| params[2] = ILI9481_PARAM_ENCODE(U32_B1_DECODE(width - 1)); |
| params[3] = ILI9481_PARAM_ENCODE(U32_B0_DECODE(width - 1)); |
| nparams = 4; |
| ili9481_write(id, |
| ILI9481_OP_SET_COLUMN_ADDRESS, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| height = id->dssdev->panel.timings.y_res; |
| |
| params[0] = ILI9481_PARAM_ENCODE(U32_B3_DECODE(height - 1)); |
| params[1] = ILI9481_PARAM_ENCODE(U32_B2_DECODE(height - 1)); |
| params[2] = ILI9481_PARAM_ENCODE(U32_B1_DECODE(height - 1)); |
| params[3] = ILI9481_PARAM_ENCODE(U32_B0_DECODE(height - 1)); |
| nparams = 4; |
| ili9481_write(id, |
| ILI9481_OP_SET_PAGE_ADDRESS, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| ili9481_write(id, |
| ILI9481_OP_SET_MEMORY_START, |
| (const u8 *)params, |
| ILI9481_PARAMS_TO_BYTES(nparams)); |
| |
| done: |
| return; |
| } |
| |
| static void ili9481_power_off(struct ili9481_device * id) |
| { |
| // First, run the display off command |
| |
| ili9481_command(id, ILI9481_OP_CMD_DISPLAY_OFF); |
| |
| // Next, enter sleep mode |
| |
| ili9481_command(id, ILI9481_OP_CMD_ENTER_SLEEP_MODE); |
| |
| // Wait at least 2 frames |
| |
| fdelay(2 * 2); |
| |
| // Finally, allow the platform to do any necessary steps (turn off |
| // rails, etc.) |
| |
| if (id->dssdev->platform_disable) { |
| id->dssdev->platform_disable(id->dssdev); |
| } |
| } |
| |
| static int gpm1145a0_dss_probe(struct omap_dss_device *dssdev) |
| { |
| struct ili9481_device *id = NULL; |
| struct spi_device *spi = NULL; |
| struct ili9481_platform_data *pdata = NULL; |
| int status = 0; |
| |
| id = &ili9481_dev; |
| |
| // Otherwise, the detect succeeded. So, cache a reference to the |
| // OMAP DSS device in our device-private data. |
| |
| id->dssdev = dssdev; |
| |
| spi = id->spi; |
| pdata = spi->dev.platform_data; |
| |
| // Platform data is required. Without it, we cannot determine the |
| // reset GPIO and reset polarity. And without those, we cannot |
| // successfully initialize the display. |
| |
| if (pdata == NULL) { |
| dev_err(&spi->dev, "Could not retrieve platform data for display. " |
| "Unable to initialize display.\n"); |
| status = -EINVAL; |
| goto done; |
| } |
| |
| // If we were supplied platform-specific data, request and assign |
| // the reset GPIO. |
| |
| status = gpio_request(pdata->reset.gpio, "ili9481 reset"); |
| |
| if (status) { |
| dev_err(&spi->dev, |
| "Couldn't reserve GPIO %ld for ili9481 reset.\n", |
| pdata->reset.gpio); |
| goto done; |
| } |
| |
| status = gpio_direction_output(pdata->reset.gpio, |
| !pdata->reset.inverted); |
| |
| if (status) { |
| dev_err(&spi->dev, |
| "Couldn't set GPIO %ld output for ili9481 reset.\n", |
| pdata->reset.gpio); |
| goto err; |
| } |
| |
| // Configure the panel as a TFT LCD with both horizontal and |
| // vertical sync. |
| |
| dssdev->panel.config = (OMAP_DSS_LCD_TFT | |
| OMAP_DSS_LCD_IVS | |
| OMAP_DSS_LCD_IHS); |
| |
| // Copy the default timing parameters. |
| |
| dssdev->panel.timings = giantplus_gpm1145a0_timings; |
| |
| err: |
| if (pdata != NULL) { |
| gpio_free(pdata->reset.gpio); |
| } |
| |
| done: |
| return (status); |
| } |
| |
| static void gpm1145a0_dss_remove(struct omap_dss_device *dssdev) |
| { |
| struct spi_device *spi; |
| struct ili9481_platform_data *pdata; |
| |
| spi = ili9481_dev.spi; |
| pdata = spi->dev.platform_data; |
| |
| if (pdata != NULL) { |
| gpio_free(pdata->reset.gpio); |
| } |
| |
| return; |
| } |
| |
| static int gpm1145a0_dss_enable(struct omap_dss_device *dssdev) |
| { |
| struct ili9481_device * id = NULL; |
| int status = 0; |
| |
| id = &ili9481_dev; |
| |
| ili9481_power_on(id); |
| |
| id->enabled = true; |
| dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; |
| |
| return (status); |
| } |
| |
| static void gpm1145a0_dss_disable(struct omap_dss_device *dssdev) |
| { |
| struct ili9481_device * id = NULL; |
| |
| id = &ili9481_dev; |
| |
| ili9481_power_off(id); |
| |
| id->enabled = false; |
| dssdev->state = OMAP_DSS_DISPLAY_DISABLED; |
| } |
| |
| static int gpm1145a0_dss_suspend(struct omap_dss_device *dssdev) |
| { |
| struct ili9481_device * id = NULL; |
| int status = 0; |
| |
| id = &ili9481_dev; |
| |
| ili9481_power_off(id); |
| |
| id->suspended = true; |
| dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; |
| |
| return (status); |
| } |
| |
| static int gpm1145a0_dss_resume(struct omap_dss_device *dssdev) |
| { |
| struct ili9481_device * id = NULL; |
| int status = 0; |
| |
| id = &ili9481_dev; |
| |
| ili9481_power_on(id); |
| |
| id->suspended = false; |
| dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; |
| |
| return (status); |
| } |
| |
| static int ili9481_spi_probe(struct spi_device *spi) |
| { |
| struct device *dev = &spi->dev; |
| struct ili9481_device *id = NULL; |
| int status = 0; |
| |
| // Check to ensure the specified platform SPI clock doesn't exceed |
| // the allowed maximum. |
| |
| if (spi->max_speed_hz > ILI9481_SPI_XSCK_MAX) { |
| dev_err(dev, "The SPI interface clock must be less than or " |
| "equal to %d KHz\n", ILI9481_SPI_XSCK_MAX / 1000); |
| return (-EINVAL); |
| } |
| |
| // Get the device private data and save a reference to the SPI |
| // device pointer. |
| |
| id = &ili9481_dev; |
| id->spi = spi; |
| |
| |
| // The Ilitek ILI9481 SPI interface requires mode 3. Bits-per-word |
| // is variable and is set on a per-transfer basis. |
| |
| spi->mode = ILI9481_SPI_MODE; |
| |
| // Set up the SPI controller interface for the chip select channel |
| // we'll be using for SPI transactions associated with this |
| // device. |
| |
| status = spi_setup(spi); |
| |
| if (status < 0) { |
| dev_err(dev, "Failed to setup SPI controller with error %d\n", status); |
| goto done; |
| } |
| |
| // Register our device private data with the SPI driver. |
| |
| spi_set_drvdata(spi, id); |
| |
| #if 0 |
| // XXX - We cannot currently perform a detect because we have a |
| // write-only SPI interface to the panel. |
| // |
| // First, attempt to probe the panel. If we cannot find a match, |
| // there's no sense in going any further. |
| |
| if (!ili9481_detect(id)) { |
| dev_err(&spi->dev, "Could not detect an ILI9481-compatible panel.\n"); |
| status = -ENODEV; |
| goto done; |
| } |
| #endif |
| |
| // Register our panel-/module-specific methods with the OMAP DSS |
| // driver. |
| |
| omap_dss_register_driver(&giantplus_gpm1145a0_driver); |
| |
| done: |
| return (status); |
| } |
| |
| static int ili9481_spi_remove(struct spi_device *spi) |
| { |
| int status = 0; |
| |
| omap_dss_unregister_driver(&giantplus_gpm1145a0_driver); |
| |
| return (status); |
| } |
| |
| static int __init giantplus_gpm1145a0_init(void) |
| { |
| int status = 0; |
| |
| pr_info("%s %s\n", GPM1145A0_DRIVER_NAME, GPM1145A0_DRIVER_VERSION); |
| |
| status = spi_register_driver(&ilitek_ili9481_spi_driver); |
| |
| return (status); |
| } |
| |
| static void __exit giantplus_gpm1145a0_exit(void) |
| { |
| spi_unregister_driver(&ilitek_ili9481_spi_driver); |
| } |
| |
| module_init(giantplus_gpm1145a0_init); |
| module_exit(giantplus_gpm1145a0_exit); |
| |
| MODULE_AUTHOR("Nest Labs, Inc."); |
| MODULE_DESCRIPTION(GPM1145A0_DRIVER_NAME); |
| MODULE_LICENSE("GPLv2"); |