/*++
linux/drivers/video/wmt/devices/lcd-b079xan01.c
Copyright (c) 2013 WonderMedia Technologies, Inc.
This program is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software Foundation,
either version 2 of the License, or (at your option) any later version.
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 .
WonderMedia Technologies, Inc.
10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
--*/
#include
#include
#include
#include
#include
#include
#include "../lcd.h"
#define DRIVERNAME "ssd2828"
#define DELAY_MASK 0xff000000
#undef pr_err
#undef pr_info
#undef pr_warning
#define pr_err(fmt, args...) printk("[" DRIVERNAME "] " fmt, ##args)
#define pr_info(fmt, args...) printk("[" DRIVERNAME "] " fmt, ##args)
#define pr_warning(fmt, args...) printk("[" DRIVERNAME "] " fmt, ##args)
extern void lcd_power_on(bool on);
struct ssd2828_chip {
struct spi_device *spi;
int id;
int gpio_reset;
};
static int wmt_lcd_panel_id(void)
{
char buf[96];
int len = sizeof(buf);
int type, id = 0;
if (wmt_getsyspara("wmt.display.param", buf, &len)) {
return -ENODEV;
}
sscanf(buf, "%d:%d", &type, &id);
return id;
}
// B079XAN01
static const uint32_t b079xan01_init_sequence[] = {
0x7000B1, 0x723240, //VSA=50, HAS=64
0x7000B2, 0x725078, //VBP=30+50, HBP=56+64
0x7000B3, 0x72243C, //VFP=36, HFP=60
0x7000B4, 0x720300, //HACT=768
0x7000B5, 0x720400, //VACT=1024
0x7000B6, 0x72000b, //burst mode, 24bpp loosely packed
0x7000DE, 0x720003, //no of lane=4
0x7000D6, 0x720005, //RGB order and packet number in blanking period
0x7000B9, 0x720000, //disable PLL
//lane speed=576 (24MHz * 24 = 576)
//may modify according to requirement, 500Mbps to 560Mbps
//LP clock : 576 / 9 / 8 = 8 MHz
0x7000BA, 0x728018,
0x7000BB, 0x720008,
0x7000B9, 0x720001, //enable PPL
0x7000C4, 0x720001, //enable BTA
0x7000B7, 0x720342, //enter LP mode
0x7000B8, 0x720000, //VC
0x7000BC, 0x720000, //set packet size
0x700011, //sleep out cmd
DELAY_MASK + 200,
0x700029, //display on
DELAY_MASK + 200,
0x7000B7, 0x72030b, //video mode on
};
static lcd_parm_t lcd_b079xan01_parm = {
.bits_per_pixel = 24,
.capability = 0,
.vmode = {
.name = "B079XAN01",
.refresh = 60,
.xres = 768,
.yres = 1024,
.pixclock = KHZ2PICOS(64800),
.left_margin = 56,
.right_margin = 60,
.upper_margin = 30,
.lower_margin = 36,
.hsync_len = 64,
.vsync_len = 50,
.sync = 0,
.vmode = 0,
.flag = 0,
},
};
static lcd_parm_t *lcd_b079xan01_get_parm(int arg)
{
return &lcd_b079xan01_parm;
}
// BP080WX7
static const uint32_t bp080wx7_init_sequence[] = {
0x7000B1, 0x720202, // VSA=02 , HSA=02
0x7000B2, 0x720828, // VBP+VSA=8, HBP+HSA=40
0x7000B3, 0x72040A, // VFP=04 , HFP=10
0x7000B4, 0x720500, // HACT=1280
0x7000B5, 0x720320, // vACT=800
0x7000B6, 0x720007, // Non burst mode with sync event 24bpp
//DELAY_MASK + 10,
0x7000DE, 0x720003, // 4lanes
0x7000D6, 0x720005, // BGR
0x7000B9, 0x720000,
//DELAY_MASK + 10,
0x7000BA, 0x72C012, // PLL=24*16 = 384MHz
0x7000BB, 0x720008, // LP CLK=8.3MHz
0x7000B9, 0x720001,
//DELAY_MASK + 200,
0x7000B8, 0x720000, // Virtual Channel 0
0x7000B7, 0x720342, // LP Mode
//DELAY_MASK + 10,
0x7000BC, 0x720000,
0x700011, //sleep out
DELAY_MASK + 200,
0x7000BC, 0x720000,
0x700029, //display on
//DELAY_MASK + 50,
0x7000B7, 0x72034B, //Video Mode
DELAY_MASK + 200,
0x7000BC, 0x720000,
0x700029, //display on
};
//Timing parameter for 3.0" QVGA LCD
#define VBPD (6)
#define VFPD (4)
#define VSPW (2)
#define HBPD (38)
#define HFPD (10)
#define HSPW (2)
// setenv wmt.display.tmr 64800:0:8:47:1280:45:4:16:800:16
static lcd_parm_t lcd_bp080wx7_parm = {
.bits_per_pixel = 24,
.capability = 0,
.vmode = {
.name = "BP080WX7",
.refresh = 60,
.xres = 1280,
.yres = 800,
.pixclock = KHZ2PICOS(64800),
.left_margin = HBPD,
.right_margin = HFPD,
.upper_margin = VBPD,
.lower_margin = VFPD,
.hsync_len = HSPW,
.vsync_len = VSPW,
.sync = 0,
.vmode = 0,
.flag = 0,
},
};
static lcd_parm_t *lcd_bp080wx7_get_parm(int arg)
{
return &lcd_bp080wx7_parm;
}
// SSD2828 api
static int ssd2828_read(struct spi_device *spi, uint8_t reg)
{
int ret;
uint8_t buf1[3] = { 0x70, 0x00, 0x00 };
uint8_t buf2[3] = { 0x73, 0x00, 0x00 };
buf1[2] = reg;
ret = spi_write(spi, buf1, 3);
if (ret) {
pr_err("spi_write ret=%d\n", ret);
return ret;
}
ret = spi_w8r16(spi, buf2[0]);
if (ret < 0) {
pr_err("spi_write ret=%d\n", ret);
}
return ret;
}
#if 0
static int ssd2828_write(struct spi_device *spi, uint8_t reg, uint16_t data)
{
int ret;
uint8_t buf_reg[3] = { 0x70, 0x00, 0x00 };
uint8_t buf_data[3] = { 0x72, 0x00, 0x00 };
buf_reg[2] = reg;
buf_data[1] = (data >> 8) & 0xff;
buf_data[2] = data & 0xff;
ret = spi_write(spi, buf_reg, 3);
if (ret) {
pr_err("spi_write ret=%d,w cmd=0x%06x\n", ret, data);
return ret;
}
ret = spi_write(spi, buf_data, 3);
if (ret)
pr_err("spi_write ret=%d,w cmd=0x%06x\n", ret, data);
return ret;
}
#endif
static inline int spi_write_24bit(struct spi_device *spi, uint32_t data)
{
int ret;
uint8_t buf[3];
buf[0] = (data >> 16) & 0xff;
buf[1] = (data >> 8) & 0xff;
buf[2] = data & 0xff;
ret = spi_write(spi, buf, 3);
if (ret)
pr_err("spi_write ret=%d,w cmd=0x%06x\n", ret, data);
return ret;
}
static inline void ssd2828_hw_reset(struct ssd2828_chip *chip)
{
lcd_power_on(1);
msleep(10);
gpio_direction_output(chip->gpio_reset, 1);
msleep(10);
gpio_direction_output(chip->gpio_reset, 0);
msleep(200);
gpio_direction_output(chip->gpio_reset, 1);
msleep(200);
}
static int ssd2828_hw_init(struct ssd2828_chip *chip)
{
const uint32_t *init_sequence;
size_t n;
int i, ret = 0;
ssd2828_hw_reset(chip);
ret = ssd2828_read(chip->spi, 0xB0);
if (ret < 0 || ret != 0x2828) {
pr_err("Error: SSD2828 not found!\n");
return -ENODEV;
}
switch (chip->id) {
case LCD_B079XAN01:
init_sequence = b079xan01_init_sequence;
n = ARRAY_SIZE(b079xan01_init_sequence);
break;
case LCD_BP080WX7:
init_sequence = bp080wx7_init_sequence;
n = ARRAY_SIZE(bp080wx7_init_sequence);
break;
default:
return -EINVAL;
}
for (i = 0; i < n; i++) {
if ((init_sequence[i] & DELAY_MASK) == DELAY_MASK) {
msleep(init_sequence[i] & 0xff);
} else {
ret = spi_write_24bit(chip->spi, init_sequence[i]);
if (ret)
break;
}
}
return ret;
}
static ssize_t option_port_testmode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
char *s = buf;
int ret;
int reg;
struct spi_device *spi = container_of(dev, struct spi_device, dev);
s += sprintf(s, "register value\n");
for (reg = 0xb0; reg <= 0xff; reg++) {
ret = ssd2828_read(spi, (uint8_t)reg);
if (ret < 0)
goto out;
s += sprintf(s, "reg 0x%02X : 0x%04x\n", reg, ret);
}
s += sprintf(s, "=========\n");
out:
return (s - buf);
}
static ssize_t option_port_testmode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return 0;
}
static DEVICE_ATTR(testmode, S_IRUGO,
option_port_testmode_show,
option_port_testmode_store);
static int __devinit ssd2828_spi_probe(struct spi_device *spi)
{
struct ssd2828_chip *chip;
int gpio = WMT_PIN_GP0_GPIO0;
int ret;
ret = gpio_request(gpio, "SSD2828 Reset");
if (ret) {
dev_err(&spi->dev, "can not open GPIO %d\n", gpio);
return ret;
}
ret = ssd2828_read(spi, 0xB0);
if (ret < 0 || ret != 0x2828) {
pr_err("Error: SSD2828 not found!\n");
return -ENODEV;
}
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->spi = spi;
chip->id = wmt_lcd_panel_id();
chip->gpio_reset = gpio;
spi_set_drvdata(spi, chip);
ret = sysfs_create_file(&spi->dev.kobj, &dev_attr_testmode.attr);
if (unlikely(ret)) {
pr_err("ssd2828 sysfs_create_file failed\n");
}
return ret;
}
static int ssd2828_spi_resume(struct spi_device *spi)
{
struct ssd2828_chip *chip = dev_get_drvdata(&spi->dev);
return ssd2828_hw_init(chip);
}
static struct spi_driver ssd2828_driver = {
.driver = {
.name = DRIVERNAME,
.owner = THIS_MODULE,
},
.probe = ssd2828_spi_probe,
.resume = ssd2828_spi_resume,
};
static struct spi_board_info ssd2828_spi_info[] __initdata = {
{
.modalias = DRIVERNAME,
.bus_num = 1,
.chip_select = 0,
.max_speed_hz = 12000000,
.irq = -1,
.mode = SPI_CLK_MODE3,
},
};
static int __init ssd2828_init(void)
{
int ret;
switch (wmt_lcd_panel_id()) {
case LCD_B079XAN01:
case LCD_BP080WX7:
break;
default:
pr_warning("lcd for ssd2828 not found\n");
return -EINVAL;
}
ret = spi_register_board_info(ssd2828_spi_info,
ARRAY_SIZE(ssd2828_spi_info));
if (ret) {
pr_err("spi_register_board_info failed\n");
return ret;
}
ret = spi_register_driver(&ssd2828_driver);
if (ret) {
pr_err("spi_register_driver failed\n");
return ret;
}
lcd_panel_register(LCD_B079XAN01, (void *)lcd_b079xan01_get_parm);
lcd_panel_register(LCD_BP080WX7, (void *)lcd_bp080wx7_get_parm);
pr_info("spi %s register success\n", DRIVERNAME);
return 0;
}
static void ssd2828_exit(void)
{
spi_unregister_driver(&ssd2828_driver);
}
module_init(ssd2828_init);
module_exit(ssd2828_exit);
MODULE_AUTHOR("Sam Mei");
MODULE_DESCRIPTION("WonderMedia Mipi LCD Driver");
MODULE_LICENSE("GPL");