blob: be9098fdc1e9a4be883ea74558b3163f4880f424 [file] [log] [blame]
/*
* 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, &reg[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");