blob: 045f8d0e80ad778c82f122f9c4d709354944d32d [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
#include <linux/notifier.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/amlogic/media/vout/peripheral_lcd.h>
#include "peripheral_lcd_drv.h"
#include "peripheral_lcd_dev.h"
static struct per_lcd_dev_config_s *dev_conf;
struct spi_device *spi;
static int init_flag;
static inline void dcx_init(void)
{
per_lcd_gpio_set(dev_conf->dcx_index, 2);
}
static inline void dcx_high(void)
{
per_lcd_gpio_set(dev_conf->dcx_index, 1);
}
static inline void dcx_low(void)
{
per_lcd_gpio_set(dev_conf->dcx_index, 0);
}
static inline void rst_high(void)
{
per_lcd_gpio_set(dev_conf->reset_index, 1);
}
static inline void rst_low(void)
{
per_lcd_gpio_set(dev_conf->reset_index, 0);
}
static unsigned long long reverse_bytes(unsigned long long value)
{
return (value & 0x00000000000000FFUL) << 48 |
(value & 0x000000000000FF00UL) << 48 |
(value & 0x0000000000FF0000UL) << 16 |
(value & 0x00000000FF000000UL) << 16 |
(value & 0x000000FF00000000UL) >> 16 |
(value & 0x0000FF0000000000UL) >> 16 |
(value & 0x00FF000000000000UL) >> 48 |
(value & 0xFF00000000000000UL) >> 48;
}
static int endian64bit_convert(void *src_data, int size)
{
unsigned char *src = (unsigned char *)src_data;
if (size % 8) {
LCDERR("%s: size error: %d\n", __func__, size);
return -1;
}
while (size > 0) {
*(unsigned long long *)src =
reverse_bytes(*(unsigned long long *)src);
src += 8;
size -= 8;
}
return 0;
}
static void write_comm(struct spi_device *spi, unsigned char command)
{
dcx_low();
per_lcd_delay_us(1);
per_lcd_spi_write(spi, &command, 1, 8);
}
static void write_data(struct spi_device *spi, unsigned char data)
{
dcx_high();
per_lcd_delay_us(1);
per_lcd_spi_write(spi, &data, 1, 8);
}
static void set_spi_display_window(struct spi_device *spi,
unsigned int x_start, unsigned int y_start,
unsigned int x_end, unsigned int y_end)
{
write_comm(spi, 0x2A);
write_data(spi, x_start >> 8);
write_data(spi, x_start & 0xff);
write_data(spi, x_end >> 8);
write_data(spi, x_end & 0xff);
write_comm(spi, 0x2B);
write_data(spi, y_start >> 8);
write_data(spi, y_start & 0xff);
write_data(spi, y_end >> 8);
write_data(spi, y_end & 0xff);
write_comm(spi, 0x2c);
}
static void per_lcd_write_frame(unsigned char *addr, unsigned short x0,
unsigned short x1, unsigned short y0,
unsigned short y1)
{
int ret, size;
set_spi_display_window(spi, x0, y0, x1, y1);
dcx_high();
per_lcd_delay_ms(1);
size = (x1 - x0) * (y1 - y0) * 2;
endian64bit_convert(addr, size);
ret = per_lcd_spi_write(spi, addr, size, 64);
if (ret)
LCDERR("%s: fail\n", __func__);
}
static int frame_flush(void)
{
return 0;
}
static int set_color_format(unsigned int cfmt)
{
return 0;
}
static int set_gamma(unsigned char *table, unsigned int rgb_sel)
{
return 0;
}
static int set_flush_rate(unsigned int rate)
{
return 0;
}
static int frame_post(unsigned char *addr, unsigned short x0,
unsigned short x1, unsigned short y0, unsigned short y1)
{
if (!addr) {
LCDERR("buff is null\n");
return -1;
}
per_lcd_write_frame(addr, x0, x1, y0, y1);
return 0;
}
static int spi_power_cmd_dynamic_size(int flag)
{
unsigned char *table;
int i = 0, j, step = 0, max_len = 0;
unsigned char type, cmd_size;
int delay_ms, ret = 0;
if (flag) {
table = dev_conf->init_on;
max_len = dev_conf->init_on_cnt;
} else {
table = dev_conf->init_off;
max_len = dev_conf->init_off_cnt;
}
while ((i + 1) < max_len) {
type = table[i];
if (type == PER_LCD_CMD_TYPE_END)
break;
if (per_lcd_debug_flag) {
LCDPR("%s: step %d: type=0x%02x, cmd_size=%d\n",
__func__, step, type, table[i + 1]);
}
cmd_size = table[i + 1];
if (cmd_size == 0)
goto power_cmd_dynamic_next;
if ((i + 2 + cmd_size) > max_len)
break;
if (type == PER_LCD_CMD_TYPE_NONE) {
/* do nothing */
} else if (type == PER_LCD_CMD_TYPE_GPIO) {
if (cmd_size < 2) {
LCDERR("step %d: invalid cmd_size %d for GPIO\n",
step, cmd_size);
goto power_cmd_dynamic_next;
}
if (table[i + 2] < PER_GPIO_MAX)
per_lcd_gpio_set(table[i + 2], table[i + 3]);
if (cmd_size > 2) {
if (table[i + 4] > 0)
per_lcd_delay_ms(table[i + 4]);
}
} else if (type == PER_LCD_CMD_TYPE_DELAY) {
delay_ms = 0;
for (j = 0; j < cmd_size; j++)
delay_ms += table[i + 2 + j];
if (delay_ms > 0)
per_lcd_delay_ms(delay_ms);
} else if (type == PER_LCD_CMD_TYPE_CMD) {
per_lcd_spi_write(spi, &table[i + 2], cmd_size, 8);
per_lcd_delay_us(1);
} else if (type == PER_LCD_CMD_TYPE_CMD_DELAY) {
per_lcd_spi_write(spi, &table[i + 2], (cmd_size - 1), 8);
per_lcd_delay_us(1);
if (table[i + cmd_size + 1] > 0)
per_lcd_delay_ms(table[i + cmd_size + 1]);
} else {
LCDERR("%s: type 0x%02x invalid\n", __func__, type);
}
power_cmd_dynamic_next:
i += (cmd_size + 2);
step++;
}
return ret;
}
static int spi_power_cmd_fixed_size(int flag)
{
unsigned char *table;
int i = 0, j, step = 0, max_len = 0;
unsigned char type, cmd_size;
int delay_ms, ret = 0;
cmd_size = dev_conf->cmd_size;
if (cmd_size < 2) {
LCDERR("%s: invalid cmd_size %d\n", __func__, cmd_size);
return -1;
}
if (flag) {
table = dev_conf->init_on;
max_len = dev_conf->init_on_cnt;
} else {
table = dev_conf->init_off;
max_len = dev_conf->init_off_cnt;
}
while ((i + cmd_size) <= max_len) {
type = table[i];
if (type == PER_LCD_CMD_TYPE_END)
break;
if (per_lcd_debug_flag) {
LCDPR("%s: step %d: type=0x%02x, cmd_size=%d\n",
__func__, step, type, cmd_size);
}
if (type == PER_LCD_CMD_TYPE_NONE) {
/* do nothing */
} else if (type == PER_LCD_CMD_TYPE_GPIO) {
if (table[i + 1] < PER_GPIO_MAX)
per_lcd_gpio_set(table[i + 1], table[i + 2]);
if (cmd_size > 3) {
if (table[i + 3] > 0)
per_lcd_delay_ms(table[i + 3]);
}
} else if (type == PER_LCD_CMD_TYPE_DELAY) {
delay_ms = 0;
for (j = 0; j < (cmd_size - 1); j++)
delay_ms += table[i + 1 + j];
if (delay_ms > 0)
per_lcd_delay_ms(delay_ms);
} else if (type == PER_LCD_CMD_TYPE_CMD) {
per_lcd_spi_write(spi, &table[i + 1],
(cmd_size - 1), 8);
per_lcd_delay_us(1);
} else if (type == PER_LCD_CMD_TYPE_CMD_DELAY) {
per_lcd_spi_write(spi, &table[i + 1], cmd_size, 8);
if (table[i + cmd_size - 1] > 0)
per_lcd_delay_ms(table[i + cmd_size - 1]);
} else {
LCDERR("%s: type 0x%02x invalid\n", __func__, type);
}
i += cmd_size;
step++;
}
return ret;
}
static void spi_fill_screen_color(unsigned int index)
{
unsigned char *buf;
unsigned long c = 0;
unsigned int size;
int i = 0;
if (dev_conf->data_format == 0) {
c = rgb888_color_data[index];
} else if (dev_conf->data_format == 1) {
c = rgb666_color_data[index];
} else if (dev_conf->data_format == 2) {
c = rgb565_color_data[i];
size = dev_conf->row * dev_conf->col * 2;
buf = kcalloc((size * 2), sizeof(unsigned int), GFP_KERNEL);
if (!buf) {
LCDERR("%s: failed to alloc buf\n", __func__);
return;
}
for (i = 0; i < (size * 2); i += 2) {
buf[i] = c & 0xff;
buf[i + 1] = (c >> 8) & 0xff;
}
per_lcd_write_frame(buf, 0, dev_conf->col, 0, dev_conf->row);
kfree(buf);
buf = NULL;
} else {
LCDERR("unsupport data_format\n");
}
}
static void spi_write_color_bars(void)
{
unsigned int i, j, k = 0;
unsigned char *buf;
unsigned int size;
if (!dev_conf->data_format) {
size = dev_conf->row * dev_conf->col * 3;
buf = kmalloc((sizeof(unsigned int) * size), GFP_KERNEL);
if (!buf) {
LCDERR("%s: failed to alloc buf\n", __func__);
return;
}
for (i = 0; i < 8; i++) {
for (j = 0; j < (size >> 3); j++) {
buf[k] = rgb565_color_data[i] & 0xff;
buf[k + 1] = (rgb565_color_data[i] >> 8) & 0xff;
buf[k + 2] = (rgb888_color_data[i] >> 16)
& 0xff;
k += 3;
}
}
} else if (dev_conf->data_format == 1) {
size = dev_conf->row * dev_conf->col * 3;
buf = kmalloc((sizeof(unsigned int) * size), GFP_KERNEL);
if (!buf) {
LCDERR("%s: failed to alloc buf\n", __func__);
return;
}
for (i = 0; i < 8; i++) {
for (j = 0; j < (size >> 3); j++) {
buf[k] = rgb565_color_data[i] & 0xff;
buf[k + 1] = (rgb565_color_data[i] >> 8) & 0xff;
buf[k + 2] = (rgb888_color_data[i] >> 16) & 0x3;
k += 3;
}
}
} else if (dev_conf->data_format == 2) {
size = dev_conf->row * dev_conf->col * 2;
buf = kmalloc((sizeof(unsigned int) * size), GFP_KERNEL);
if (!buf) {
LCDERR("%s: failed to alloc buf\n", __func__);
return;
}
for (i = 0; i < 8; i++) {
for (j = 0; j < (size >> 3); j += 2) {
buf[k] = rgb565_color_data[i] & 0xff;
buf[k + 1] = (rgb565_color_data[i] >> 8) & 0xff;
k += 2;
}
}
} else {
LCDERR("unsupport data_format\n");
return;
}
per_lcd_write_frame(buf, 0, dev_conf->col, 0, dev_conf->row);
kfree(buf);
buf = NULL;
}
static int spi_power_on_init(void)
{
int ret = 0;
if (dev_conf->cmd_size < 1) {
LCDERR("%s: cmd_size %d is invalid\n",
__func__, dev_conf->cmd_size);
return -1;
}
if (!dev_conf->init_on) {
LCDERR("%s: init_data is null\n", __func__);
return -1;
}
if (dev_conf->cmd_size == PER_LCD_CMD_SIZE_DYNAMIC)
ret = spi_power_cmd_dynamic_size(1);
else
ret = spi_power_cmd_fixed_size(1);
init_flag = 1;
return ret;
}
static int spi_test(const char *buf)
{
int ret;
unsigned int index;
switch (buf[0]) {
case 'c': /*color fill*/
if (!init_flag)
spi_power_on_init();
ret = sscanf(buf, "color %d", &index);
spi_fill_screen_color(index);
break;
case 'b': /*colorbar*/
if (!init_flag)
spi_power_on_init();
spi_write_color_bars();
break;
default:
LCDPR("unsupport\n");
break;
}
return 0;
}
static irqreturn_t spi_vsync_isr(int irq, void *dev_id)
{
struct peripheral_lcd_driver_s *per_lcd_drv = peripheral_lcd_get_driver();
if (init_flag == 0)
return IRQ_HANDLED;
if (per_lcd_drv->vsync_isr_cb)
per_lcd_drv->vsync_isr_cb();
return IRQ_HANDLED;
}
static int spi_vsync_irq_init(struct peripheral_lcd_driver_s *per_lcd_drv)
{
unsigned int spi_vs_irq = 0;
int ret;
if (!per_lcd_drv->res_vs_irq) {
LCDERR("%s: no vsync_irq\n", __func__);
return -1;
}
spi_vs_irq = per_lcd_drv->res_vs_irq->start;
ret = request_irq(spi_vs_irq, spi_vsync_isr, IRQF_TRIGGER_RISING,
"per_lcd_spi_vsync_irq", (void *)"per_lcd");
if (ret) {
LCDERR("can't request spi_vs_irq\n");
return -1;
}
if (per_lcd_debug_flag)
LCDPR("request spi_vs_irq successful\n");
return 0;
}
int per_lcd_dev_spi_probe(struct peripheral_lcd_driver_s *per_lcd_drv)
{
int ret;
if (!per_lcd_drv->spi_dev) {
LCDERR("spi_dev is null\n");
return -1;
}
dev_conf = per_lcd_drv->per_lcd_dev_conf;
per_lcd_drv->frame_post = frame_post;
per_lcd_drv->frame_flush = frame_flush;
per_lcd_drv->set_color_format = set_color_format;
per_lcd_drv->set_gamma = set_gamma;
per_lcd_drv->set_flush_rate = set_flush_rate;
per_lcd_drv->vsync_isr_cb = te_cb;
per_lcd_drv->enable = spi_power_on_init;
per_lcd_drv->test = spi_test;
spi = per_lcd_drv->spi_dev;
spi_vsync_irq_init(per_lcd_drv);
per_lcd_gpio_probe(dev_conf->dcx_index);
ret = spi_power_on_init();
if (!ret)
spi_fill_screen_color(0);
return 0;
}
int per_lcd_dev_spi_remove(struct peripheral_lcd_driver_s *per_lcd_drv)
{
return 0;
}