| /* |
| * Copyright (c) 2014 Nest, Inc. |
| * |
| * Author: Andrew LeCain <alecain@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 Tianma TM025ZDZ01 |
| * 320 x 320 TFT LCD display panel using the Renesas r61529a1 |
| * interface driver. |
| */ |
| |
| |
| #include <linux/module.h> |
| #include <linux/lcd.h> |
| #include <linux/delay.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/fs.h> |
| #include <linux/sysfs.h> |
| #include <linux/uaccess.h> |
| #include <linux/gpio.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/spi/spi.h> |
| #include <linux/time.h> |
| |
| /* |
| * Driver Strings |
| */ |
| #define TM025ZDZ01_DRIVER_NAME "Renesas r61529a1 LCD Driver" |
| #define TM025ZDZ01_DRIVER_VERSION "2014-4-23" |
| |
| |
| #define FRAME_DURATION (1000/60) |
| #define WAKE_RETRY_COUNT 5 |
| |
| /* |
| *user commands |
| */ |
| |
| #define NOP 0x00 |
| #define SOFT_RESET 0x01 |
| #define READ_DDB_START 0x04 |
| #define GET_POWER_MODE 0x0A |
| #define GET_ADDRESS_MODE 0x0B |
| #define GET_PIXEL_FORMAT 0x0C |
| #define GET_DISPLAY_MODE 0x0D |
| #define GET_SIGNAL_MODE 0x0E |
| #define GET_DIAG_RESULT 0x0F |
| |
| #define ENTER_SLEEP_MODE 0x10 |
| #define EXIT_SLEEP_MODE 0x11 |
| #define ENTER_PARTIAL_MODE 0x12 |
| #define ENTER_NORMAL_MODE 0x13 |
| #define EXIT_DEEP_SLEEP 0xFF |
| |
| #define EXIT_INVERT_MODE 0x20 |
| #define ENTER_INVERT_MODE 0x21 |
| #define SET_DISPLAY_OFF 0x28 |
| #define SET_DISPLAY_ON 0x29 |
| #define SET_COLUMN_ADDRESS 0x2A |
| #define SET_PAGE_ADDRESS 0x2B |
| #define WRITE_MEMORY_START 0x2C |
| #define READ_MEMORY_START 0x2E |
| |
| #define SET_PARTIAL_AREA 0x30 |
| #define SET_TEAR_OFF 0x34 |
| #define SET_TEAR_ON 0x35 |
| #define SET_ADDRESS_MODE 0x36 |
| #define EXIT_IDLE_MODE 0x38 |
| #define ENTER_IDLE_MODE 0x39 |
| #define SET_PIXEL_FORMAT 0x3A |
| #define WRITE_MEM_CONTINUE 0x3C |
| #define READ_MEMORY_CONTIUE 0x3E |
| |
| #define SET_TEAR_SCANLINE 0x44 |
| #define GET_SCANLINE 0x45 |
| |
| /* |
| *Manufacturer commands |
| */ |
| |
| #define MCAP 0xB0 |
| #define LOW_POWER_CONTROL 0xB1 |
| #define FRAME_MEM_ACCESS 0xB3 |
| #define DISPLAY_MODE 0xB4 |
| #define READ_CHECKSUM 0xB5 |
| #define DSI_CONTROL 0xB6 |
| #define MDDI_CONTROL 0xB7 |
| #define BACKLIGHT_CONTROL_1 0xB8 |
| #define BACKLIGHT_CONTROL_2 0xB9 |
| #define BACKLIGHT_CONTROL_3 0xBA |
| #define DEVICE_CODE_READ 0xBF |
| |
| #define DEVICE_CODE_1 0x01 |
| #define DEVICE_CODE_2 0x22 |
| #define DEVICE_CODE_3 0x15 |
| #define DEVICE_CODE_4 0x29 |
| |
| #define PANEL_DRIVE 0xC0 |
| #define V_TIMING 0xC1 |
| #define TEST_MODE 0xC3 |
| #define DRIVE_CONTROL 0xC4 |
| #define DPI_POLARITY 0xC6 |
| #define TEST_MODE_2 0xC7 |
| #define GAMMA_SETTING_A 0xC8 |
| #define GAMMA_SETTING_B 0xC9 |
| #define GAMMA_SETTING_C 0xCA |
| |
| #define CHARGE_PUMP 0xD0 |
| #define VCOM 0xD1 |
| #define TEST_MODE_4 0xD6 |
| #define TEST_MODE_5 0xD7 |
| #define TEST_MODE_6 0xD8 |
| #define TEST_MODE_7 0xD9 |
| #define TEST_MODE_8 0xDA |
| |
| static u8 r61529a1_soft_reset[]= { |
| SOFT_RESET |
| }; |
| static u8 r61529a1_set_mfg_mode[] = { |
| MCAP, 0x04 |
| }; |
| static u8 r61529a1_device_code_read[] = { |
| DEVICE_CODE_READ |
| }; |
| static u8 r61529a1_low_power_control[] = { |
| LOW_POWER_CONTROL, 0x01 |
| }; |
| static u8 r61529a1_enter_sleep_mode[] = { |
| ENTER_SLEEP_MODE |
| }; |
| static u8 r61529a1_display_off[] = { |
| SET_DISPLAY_OFF |
| }; |
| static u8 r61529a1_display_on[] = { |
| SET_DISPLAY_ON |
| }; |
| static u8 r61529a1_exit_sleep[] = { |
| EXIT_SLEEP_MODE |
| }; |
| static u8 r61529a1_page_address[] = { |
| SET_PAGE_ADDRESS, 0x00, 0x00, 0x01, 0x3F |
| }; |
| static u8 r61529a1_column_address[] = { |
| SET_COLUMN_ADDRESS, 0x00, 0x00, 0x01, 0x3F |
| }; |
| static u8 r61529a1_pixel_format[] = { |
| SET_PIXEL_FORMAT, 0x77 |
| }; |
| static u8 r61529a1_address_mode[] = { |
| SET_ADDRESS_MODE, 0xC1 |
| }; |
| static u8 r61529a1_charge_pump[] = { |
| CHARGE_PUMP, 0x99, 0x06, 0x08, 0x20, 0x0A, 0x04, 0x01, 0x00, 0x08, |
| 0x01, 0x00, 0x06, 0x01, 0x00, 0x00, 0x20 |
| }; |
| static u8 r61529a1_power[] = { |
| VCOM, 0x02, 0x1C, 0x1C, 0x36 |
| }; |
| static u8 r61529a1_gamma_c[] = { |
| GAMMA_SETTING_C, |
| 0x05, 0x0D, 0x1A, 0x28, 0x3C, 0x51, 0x3B, 0x31, 0x2B, 0x27, 0x23, 0x13, |
| 0x05, 0x0D, 0x1A, 0x28, 0x3C, 0x51, 0x3B, 0x31, 0x2B, 0x27, 0x23, 0x13 |
| }; |
| static u8 r61529a1_gamma_b[] = { |
| GAMMA_SETTING_B, |
| 0x05, 0x0D, 0x1A, 0x28, 0x3C, 0x51, 0x3B, 0x31, 0x2B, 0x27, 0x23, 0x13, |
| 0x05, 0x0D, 0x1A, 0x28, 0x3C, 0x51, 0x3B, 0x31, 0x2B, 0x27, 0x23, 0x13 |
| }; |
| static u8 r61529a1_gamma_a[] = { |
| GAMMA_SETTING_A, |
| 0x05, 0x0D, 0x1A, 0x28, 0x3C, 0x51, 0x3B, 0x31, 0x2B, 0x27, 0x23, 0x13, |
| 0x05, 0x0D, 0x1A, 0x28, 0x3C, 0x51, 0x3B, 0x31, 0x2B, 0x27, 0x23, 0x13 |
| }; |
| static u8 r61529a1_enable[] = { |
| DPI_POLARITY, 0x1C |
| }; |
| static u8 r61529a1_drive_control[] = { |
| DRIVE_CONTROL, 0x57, 0x00, 0x05, 0x03 |
| }; |
| static u8 r61529a1_frequency[] = { |
| V_TIMING, 0x13, 0x29, 0x08, 0x08, 0x00 |
| }; |
| static u8 r61529a1_panel_drive[] = { |
| PANEL_DRIVE, 0x13, 0x3F, 0x40, 0x10, 0x00, 0x01, 0x00, 0x55 |
| }; |
| static u8 r61529a1_display_mode[] = { |
| DISPLAY_MODE, 0x00 |
| }; |
| static u8 r61529a1_dfm[] = { |
| FRAME_MEM_ACCESS, 0x02, 0x00, 0x00, 0x01 |
| }; |
| |
| struct r61529a1_platform_data { |
| struct gpio_desc *reset_gpio; |
| struct gpio_desc *cs_gpio; |
| int power; |
| struct spi_device *spi; |
| }; |
| |
| /*lines to delay after vsync*/ |
| static ssize_t r61529a1_display_id_read(struct device *unused, struct device_attribute *attr, char *buf); |
| |
| static DEVICE_ATTR(display_id, S_IRUGO, r61529a1_display_id_read, NULL); |
| |
| static struct attribute *r61529a1_attributes[] = { |
| &dev_attr_display_id.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group r61529a1_attr_group = { |
| .attrs = r61529a1_attributes, |
| }; |
| |
| static int mipi_spi_write_then_read(struct lcd_device *lcdev, |
| u8 *txbuf, u16 txlen, |
| u8 *rxbuf, u16 rxlen, bool finish) |
| { |
| struct r61529a1_platform_data *lcd = lcd_get_data(lcdev); |
| 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); |
| } |
| |
| gpiod_set_value(lcd->cs_gpio, 1); |
| ret = spi_sync(lcd->spi, &msg); |
| |
| if (finish) gpiod_set_value(lcd->cs_gpio, 0); |
| |
| if (ret < 0) |
| dev_err(&lcdev->dev, "Couldn't send SPI data\n"); |
| |
| if (txlen) |
| kfree(local_txbuf); |
| |
| return ret; |
| } |
| |
| static int mipi_dbi_read_cmd(struct lcd_device *lcdev, u8 command, u8* outbuf, int size) |
| { |
| u8 reg[2] = {0xF5, 0xF6}; |
| u8 cmd = command; |
| u8 dummybuf[] = {0}; |
| |
| /* send start read command */ |
| mipi_spi_write_then_read(lcdev, reg, 1, NULL, 0, true); |
| |
| /* send user read command */ |
| mipi_spi_write_then_read(lcdev, &cmd, 1, dummybuf, 1, false); |
| mipi_spi_write_then_read(lcdev, NULL, 0, outbuf, size, true); |
| |
| /* send end read command */ |
| mipi_spi_write_then_read(lcdev, ®[1], 1, NULL, 0, true); |
| return size; |
| }; |
| |
| int init_display(struct lcd_device *lcdev) /* for void R61529A_initial_code(void) */ |
| { |
| char temp[4]; |
| struct r61529a1_platform_data *lcd = lcd_get_data(lcdev); |
| |
| if (!IS_ERR(lcd->reset_gpio)) { |
| dev_info(&lcdev->dev, "Resetting"); |
| gpiod_set_value(lcd->reset_gpio, 1); |
| msleep(10); |
| gpiod_set_value(lcd->reset_gpio, 0); |
| msleep(10); |
| } |
| /* ************************Start initial sequence**************************** */ |
| |
| /* soft reset */ |
| mipi_spi_write_then_read(lcdev, r61529a1_soft_reset, sizeof(r61529a1_soft_reset), NULL, 0, true); |
| msleep(5*FRAME_DURATION); |
| |
| /* send enable manufacture commands */ |
| mipi_spi_write_then_read(lcdev, r61529a1_set_mfg_mode, sizeof(r61529a1_set_mfg_mode), NULL, 0, true); |
| mipi_dbi_read_cmd(lcdev, DEVICE_CODE_READ, temp, 4); |
| |
| /* confirm display is awake */ |
| dev_printk(KERN_INFO, &lcdev->dev, "display ID: %08x", temp[0]<<24 | temp[1]<<16 | temp[2] << 8 | temp[3]); |
| if(temp[0] != DEVICE_CODE_1 || temp[1] != DEVICE_CODE_2 || temp[2] != DEVICE_CODE_3 || temp[3] != DEVICE_CODE_4) |
| { |
| dev_err(&lcdev->dev, "Display ID mismatch, should be %08x", |
| DEVICE_CODE_1 << 24 | DEVICE_CODE_2 << 18 | DEVICE_CODE_3 << 8 | DEVICE_CODE_4 ); |
| return -ENODEV; |
| } |
| |
| mipi_spi_write_then_read(lcdev, r61529a1_dfm, sizeof(r61529a1_dfm), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_display_mode, sizeof(r61529a1_display_mode), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_panel_drive, sizeof(r61529a1_panel_drive), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_frequency, sizeof(r61529a1_frequency), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_drive_control, sizeof(r61529a1_drive_control), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_enable, sizeof(r61529a1_enable), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_gamma_a, sizeof(r61529a1_gamma_a), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_gamma_b, sizeof(r61529a1_gamma_b), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_gamma_c, sizeof(r61529a1_gamma_c), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_charge_pump, sizeof(r61529a1_charge_pump), NULL, 0, true); |
| msleep(50); |
| mipi_spi_write_then_read(lcdev, r61529a1_power, sizeof(r61529a1_power), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_address_mode, sizeof(r61529a1_address_mode), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_pixel_format, sizeof(r61529a1_pixel_format), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_column_address, sizeof(r61529a1_column_address), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_page_address, sizeof(r61529a1_page_address), NULL, 0, true); |
| msleep(50); |
| |
| mipi_spi_write_then_read(lcdev, r61529a1_exit_sleep, sizeof(r61529a1_exit_sleep), NULL, 0, true); |
| msleep(6*FRAME_DURATION); |
| |
| mipi_spi_write_then_read(lcdev, r61529a1_display_on, sizeof(r61529a1_display_on), NULL, 0, true); |
| msleep(FRAME_DURATION); |
| |
| return 0; |
| } |
| |
| static ssize_t r61529a1_display_id_read(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| char temp[5]; |
| |
| struct lcd_device *lcdev = dev_get_drvdata(dev); |
| |
| mipi_dbi_read_cmd(lcdev, DEVICE_CODE_READ, temp, 4); |
| |
| return sprintf(buf, "%08x\n", temp[0]<<24 | temp[1]<<16 | temp[2] << 8 | temp[3]); |
| } |
| |
| static int r61529a1_power_on(struct lcd_device *lcdev) |
| { |
| int status = 0, i = 0; |
| struct r61529a1_platform_data *lcd = lcd_get_data(lcdev); |
| |
| dev_info(&lcdev->dev, "Power on\n"); |
| |
| for(i = 0; i < 6; i++) |
| { |
| gpiod_set_value(lcd->cs_gpio, 1); |
| usleep_range(1000,2000); |
| gpiod_set_value(lcd->cs_gpio, 0); |
| usleep_range(1000,2000); |
| } |
| |
| status = init_display(lcdev); |
| return status; |
| } |
| |
| static int r61529a1_power_off(struct lcd_device *lcdev) |
| { |
| |
| dev_printk(KERN_INFO, &lcdev->dev, "power off \n"); |
| |
| mipi_spi_write_then_read(lcdev, r61529a1_display_off, sizeof(r61529a1_display_off), NULL, 0, true); |
| msleep(FRAME_DURATION); |
| |
| mipi_spi_write_then_read(lcdev, r61529a1_enter_sleep_mode, sizeof(r61529a1_enter_sleep_mode), NULL, 0, true); |
| msleep(5*FRAME_DURATION); |
| |
| mipi_spi_write_then_read(lcdev, r61529a1_set_mfg_mode, sizeof(r61529a1_set_mfg_mode), NULL, 0, true); |
| mipi_spi_write_then_read(lcdev, r61529a1_low_power_control, sizeof(r61529a1_low_power_control), NULL, 0, true); |
| |
| return 0; |
| } |
| |
| /* blank early */ |
| static int r61529a1_early_set_power(struct lcd_device *lcdev, int power) |
| { |
| int status = 0; |
| struct r61529a1_platform_data *lcd = lcd_get_data(lcdev); |
| |
| dev_info(&lcdev->dev, "%s: power %d\n", __FUNCTION__, power); |
| |
| if(lcd->power != power) |
| { |
| if (power > FB_BLANK_NORMAL) |
| { |
| status = r61529a1_power_off(lcdev); |
| if (status == 0) |
| lcd->power = power; |
| } |
| |
| } |
| |
| return status; |
| } |
| |
| /* resume late */ |
| static int r61529a1_set_power(struct lcd_device *lcdev, int power) |
| { |
| int status = 0; |
| struct r61529a1_platform_data *lcd = lcd_get_data(lcdev); |
| |
| dev_info(&lcdev->dev, "%s: power %d\n", __FUNCTION__, power); |
| |
| if(lcd->power != power) |
| { |
| if (power <= FB_BLANK_NORMAL) |
| { |
| status = r61529a1_power_on(lcdev); |
| } |
| else |
| { |
| status = r61529a1_power_off(lcdev); |
| } |
| if (status == 0) |
| lcd->power = power; |
| } |
| |
| return status; |
| } |
| |
| static int r61529a1_get_power(struct lcd_device *lcdev) |
| { |
| struct r61529a1_platform_data *lcd = lcd_get_data(lcdev); |
| return lcd->power; |
| } |
| |
| static struct lcd_ops r61529a1_ops = { |
| .early_set_power = r61529a1_early_set_power, |
| .set_power = r61529a1_set_power, |
| .get_power = r61529a1_get_power, |
| }; |
| |
| static int r61529a1_probe(struct spi_device *spi) |
| { |
| int status; |
| struct r61529a1_platform_data *lcd; |
| struct lcd_device *lcdev; |
| |
| lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); |
| |
| status= spi_setup(spi); |
| if (status < 0) { |
| dev_err(&spi->dev, "SPI setup failed.\n"); |
| return status; |
| } |
| |
| lcd->spi = spi; |
| |
| lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset"); |
| if (!IS_ERR(lcd->reset_gpio)) { |
| gpiod_direction_output(lcd->reset_gpio, 0); |
| gpiod_set_value(lcd->reset_gpio, 0); |
| msleep(100); /* ensure we allow the power rails to settle */ |
| dev_info(&spi->dev, "registered: reset-gpios\n"); |
| } |
| else{ |
| dev_err(&spi->dev, "Failed to register reset gpio, %d\n", PTR_ERR(lcd->reset_gpio)); |
| return PTR_ERR(lcd->reset_gpio); |
| } |
| |
| lcd->cs_gpio= devm_gpiod_get(&spi->dev, "cs"); |
| if (!IS_ERR(lcd->cs_gpio)) { |
| gpiod_direction_output(lcd->cs_gpio, 1); |
| gpiod_set_value(lcd->reset_gpio, 0); /* disable. Direction output rejects flags? */ |
| dev_info(&spi->dev, "registered: cs-gpios\n"); |
| } |
| else{ |
| dev_err(&spi->dev, "Failed to register reset gpio, %d\n", PTR_ERR(lcd->cs_gpio)); |
| return PTR_ERR(lcd->cs_gpio); |
| } |
| |
| /* set up display framework */ |
| lcdev = devm_lcd_device_register(&spi->dev, "mxsfb", &spi->dev, lcd, |
| &r61529a1_ops); |
| |
| spi_set_drvdata(spi, lcdev); |
| |
| status = sysfs_create_group(&spi->dev.kobj, &r61529a1_attr_group); |
| |
| if (status) |
| goto sysfs_failed; |
| |
| /* init panel */ |
| status = init_display(lcdev); |
| |
| if (status) |
| goto init_failed; |
| |
| return 0; |
| init_failed: |
| sysfs_remove_group(&spi->dev.kobj, &r61529a1_attr_group); |
| sysfs_failed: |
| return status; |
| } |
| |
| static void r61529a1_remove(struct spi_device *spi) |
| { |
| sysfs_remove_group(&spi->dev.kobj, &r61529a1_attr_group); |
| } |
| |
| static const struct of_device_id r61529a1_dt_ids[] = { |
| { |
| .compatible = "renesas,61529a1", |
| }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, r61529a1_dt_ids); |
| |
| static struct spi_driver r61529a1_driver = { |
| .probe = r61529a1_probe, |
| .driver = { |
| .name = "r61529a1", |
| .of_match_table = r61529a1_dt_ids, |
| }, |
| }; |
| |
| module_spi_driver(r61529a1_driver); |
| |
| MODULE_AUTHOR("Nest, Inc."); |
| MODULE_DESCRIPTION(R61529A1_DRIVER_NAME); |
| MODULE_LICENSE("GPLv2"); |