blob: 79597def284874bf06554bbcdaa0a5cdcdb8dbf6 [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2021 Amlogic, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/amlogic/media/vout/peripheral_lcd.h>
#include "peripheral_lcd_drv.h"
inline void spi_dcx_init(void)
{
plcd_gpio_set(plcd_drv->pcfg->spi_cfg.dcx_index, 2);
}
inline void spi_dcx_high(void)
{
plcd_gpio_set(plcd_drv->pcfg->spi_cfg.dcx_index, 1);
}
inline void spi_dcx_low(void)
{
plcd_gpio_set(plcd_drv->pcfg->spi_cfg.dcx_index, 0);
}
inline void spi_rst_high(void)
{
plcd_gpio_set(plcd_drv->pcfg->spi_cfg.reset_index, 1);
}
inline void spi_rst_low(void)
{
plcd_gpio_set(plcd_drv->pcfg->spi_cfg.reset_index, 0);
}
int plcd_spi_write_dft(void *tbuf, int tlen, int word_bits)
{
struct spi_transfer xfer;
struct spi_message msg;
struct spi_config_s *spi_cfg = &plcd_drv->pcfg->spi_cfg;
int ret;
plcd_usleep(spi_cfg->cs_hold_delay);
spi_cfg->spi_dev->bits_per_word = word_bits;
spi_message_init(&msg);
memset(&xfer, 0, sizeof(xfer));
xfer.tx_buf = (void *)tbuf;
xfer.rx_buf = NULL;
xfer.tx_nbits = 1;
xfer.rx_nbits = 1;
xfer.len = tlen;
spi_message_add_tail(&xfer, &msg);
ret = spi_sync(spi_cfg->spi_dev, &msg);
if (ret)
LCDERR("%s\n", __func__);
return ret;
}
int plcd_spi_write_cmd1_data4(void *cbuf, int clen, void *dbuf, int dlen, int word_bits)
{
struct spi_transfer xfer_head;
struct spi_transfer xfer_data;
struct spi_message msg;
struct spi_config_s *spi_cfg = &plcd_drv->pcfg->spi_cfg;
int ret;
if (!spi_cfg->spi_dev) {
LCDERR("spi_cfg->spi_dev is NULL\n");
return -ENODEV;
}
plcd_usleep(spi_cfg->cs_hold_delay);
spi_cfg->spi_dev->bits_per_word = word_bits;
memset(&msg, 0, sizeof(msg));
memset(&xfer_head, 0, sizeof(xfer_head));
memset(&xfer_data, 0, sizeof(xfer_data));
spi_message_init(&msg);
xfer_head.tx_buf = (void *)cbuf;
xfer_head.rx_buf = NULL;
xfer_head.tx_nbits = 1;
xfer_head.rx_nbits = 1;
xfer_head.len = clen;
spi_message_add_tail(&xfer_head, &msg);
xfer_data.tx_buf = (void *)dbuf;
xfer_data.rx_buf = NULL;
xfer_data.tx_nbits = 4;
xfer_data.rx_nbits = 4;
xfer_data.len = dlen;
spi_message_add_tail(&xfer_data, &msg);
ret = spi_sync(spi_cfg->spi_dev, &msg);
if (ret)
LCDERR("%s\n", __func__);
return ret;
}
int plcd_spi_read(void *tbuf, int tlen, void *rbuf, int rlen)
{
struct spi_transfer xfer[2];
struct spi_config_s *spi_cfg = &plcd_drv->pcfg->spi_cfg;
struct spi_message msg;
int ret;
usleep_range(spi_cfg->cs_hold_delay, spi_cfg->cs_hold_delay + 5);
spi_message_init(&msg);
memset(&xfer, 0, sizeof(xfer));
xfer[0].tx_buf = (void *)tbuf;
xfer[0].rx_buf = NULL;
xfer[0].len = tlen;
spi_message_add_tail(&xfer[0], &msg);
xfer[1].tx_buf = NULL;
xfer[1].rx_buf = (void *)rbuf;
xfer[1].len = rlen;
spi_message_add_tail(&xfer[1], &msg);
ret = spi_sync(spi_cfg->spi_dev, &msg);
if (ret)
LCDERR("%s\n", __func__);
return ret;
}
static int plcd_spi_dev_probe(struct spi_device *spi)
{
int ret;
plcd_spi_dev = spi;
dev_set_drvdata(&spi->dev, plcd_drv);
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret)
LCDERR("spi setup failed\n");
return ret;
}
static void plcd_spi_dev_remove(struct spi_device *spi)
{
dev_set_drvdata(&spi->dev, NULL);
plcd_drv->pcfg->spi_cfg.spi_dev = NULL;
plcd_spi_dev = NULL;
}
#ifdef CONFIG_OF
static const struct of_device_id spi_of_plcd_match[] = {
{ .compatible = "amlogic,plcd-spi-device", },
{ }
};
MODULE_DEVICE_TABLE(of, spi_of_plcd_match);
#endif
static const struct spi_device_id plcd_spi_id[] = {
{ "plcd-spi-device", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, plcd_spi_id);
static struct spi_driver plcd_spi_dev_driver = {
.probe = plcd_spi_dev_probe,
.remove = plcd_spi_dev_remove,
.driver = {
.name = "peripheral_lcd_spi",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = spi_of_plcd_match,
#endif
},
.id_table = plcd_spi_id,
};
int plcd_spi_driver_remove(void)
{
if (plcd_drv->pcfg->spi_cfg.spi_dev)
spi_unregister_driver(&plcd_spi_dev_driver);
LCDPR("%s ok\n", __func__);
return 0;
}
static irqreturn_t spi_vsync_isr(int irq, void *dev_id)
{
// struct per_lcd_driver_s *plcd_drv = peripheral_lcd_get_driver();
if (plcd_drv->init_flag == 0)
return IRQ_HANDLED;
if (plcd_drv->vsync_isr_cb)
plcd_drv->vsync_isr_cb();
return IRQ_HANDLED;
}
int plcd_spi_vsync_irq_init(void)
{
unsigned int spi_vs_irq = 0;
int ret;
if (plcd_drv->vs_irq < 0) {
LCDERR("%s: no spi vsync_irq\n", __func__);
return -1;
}
spi_vs_irq = plcd_drv->vs_irq;
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;
}
void spi_test_pure_color(unsigned int index)
{
unsigned long c = 0;
unsigned int size, block_s;
unsigned int i = 0;
if (plcd_set_mem()) {
LCDERR("set mem failed\n");
return;
}
switch (plcd_drv->pcfg->cfmt) {
case CFMT_RGB888:
c = rgb888_color_data[index];
block_s = 3;
break;
case CFMT_RGB565:
c = rgb565_color_data[index];
block_s = 2;
break;
default:
LCDERR("unsupported color format\n");
return;
}
size = plcd_drv->pcfg->v * plcd_drv->pcfg->h;
for (i = 0; i < size * block_s; i += block_s) {
*((unsigned char *)plcd_drv->frame_addr + i) = c & 0xff;
*((unsigned char *)plcd_drv->frame_addr + i + 1) = (c >> 8) & 0xff;
if (block_s >= 3)
*((unsigned char *)plcd_drv->frame_addr + i + 2) = (c >> 16) & 0xff;
}
if (plcd_drv->frame_flush)
plcd_drv->frame_flush(plcd_drv->frame_addr);
else if (plcd_drv->frame_post)
plcd_drv->frame_post(plcd_drv->frame_addr,
0, plcd_drv->pcfg->h, 0, plcd_drv->pcfg->v);
else
LCDERR("device not support frame_flush or frame_post\n");
plcd_unset_mem();
}
/* 0: horizontal bar; 1: vertical bar; 2: horizontal line bar; 3: vertical line bar*/
void spi_test_color_bars(unsigned int index)
{
unsigned int i, j, k = 0, block_s, addr_shift;
unsigned char *_addr;
unsigned int *c;
if (plcd_set_mem()) {
LCDERR("set mem failed\n");
return;
}
switch (plcd_drv->pcfg->cfmt) {
case CFMT_RGB888:
c = rgb888_color_data;
block_s = 3;
break;
case CFMT_RGB565:
c = rgb565_color_data;
block_s = 2;
break;
default:
LCDERR("unsupported color format\n");
return;
}
for (i = 0; i < plcd_drv->pcfg->v; i++) {
for (j = 0; j < plcd_drv->pcfg->h; j++) {
addr_shift = (plcd_drv->pcfg->v * i + j) * block_s;
_addr = (unsigned char *)plcd_drv->frame_addr + addr_shift;
if (index == 0)
k = i / (plcd_drv->pcfg->h / 8 + 1);
else if (index == 1)
k = j / (plcd_drv->pcfg->v / 8 + 1);
else if (index == 2)
k = i % 8;
else
k = j % 8;
_addr[0] = c[k] & 0xff;
_addr[1] = (c[k] >> 8) & 0xff;
if (block_s == 3)
_addr[2] = (c[k] >> 16) & 0xff;
}
}
if (plcd_drv->frame_flush)
plcd_drv->frame_flush(plcd_drv->frame_addr);
else if (plcd_drv->frame_post)
plcd_drv->frame_post(plcd_drv->frame_addr,
0, plcd_drv->pcfg->h, 0, plcd_drv->pcfg->v);
else
LCDERR("device not support frame_flush or frame_post\n");
plcd_unset_mem();
}
int spi_test_dft(const char *buf)
{
int ret;
unsigned int index;
if (!plcd_drv->init_flag)
return -1;
switch (buf[0]) {
case 'c': /*color fill*/
ret = sscanf(buf, "color %d", &index);
spi_test_pure_color(index);
break;
case 'b': /*colorbar*/
ret = sscanf(buf, "bar %d", &index);
spi_test_color_bars(index);
break;
default:
LCDPR("unsupported command\n");
break;
}
return 0;
}
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 = plcd_drv->pcfg->init_on;
max_len = plcd_drv->pcfg->init_on_cnt;
} else {
table = plcd_drv->pcfg->init_off;
max_len = plcd_drv->pcfg->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)
plcd_gpio_set(table[i + 2], table[i + 3]);
if (cmd_size > 2) {
if (table[i + 4] > 0)
plcd_usleep(table[i + 4] * 1000);
}
} 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)
plcd_usleep(delay_ms * 1000);
} else if (type == PER_LCD_CMD_TYPE_CMD) {
plcd_spi_write_dft(&table[i + 2], cmd_size, 8);
plcd_usleep(1);
} else if (type == PER_LCD_CMD_TYPE_CMD_DELAY) {
plcd_spi_write_dft(&table[i + 2], (cmd_size - 1), 8);
plcd_usleep(1);
if (table[i + cmd_size + 1] > 0)
plcd_usleep(table[i + cmd_size + 1] * 1000);
} else {
LCDERR("%s: type 0x%02x invalid\n", __func__, type);
}
power_cmd_dynamic_next:
i += (cmd_size + 2);
step++;
}
return ret;
}
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 = plcd_drv->pcfg->cmd_size;
if (cmd_size < 2) {
LCDERR("%s: invalid cmd_size %d\n", __func__, cmd_size);
return -1;
}
if (flag) {
table = plcd_drv->pcfg->init_on;
max_len = plcd_drv->pcfg->init_on_cnt;
} else {
table = plcd_drv->pcfg->init_off;
max_len = plcd_drv->pcfg->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 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)
plcd_gpio_set(table[i + 1], table[i + 2]);
if (cmd_size > 3) {
if (table[i + 3] > 0)
plcd_usleep(table[i + 3] * 1000);
}
} 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];
plcd_usleep(delay_ms * 1000);
} else if (type == PER_LCD_CMD_TYPE_CMD) {
plcd_spi_write_dft(&table[i + 1], (cmd_size - 1), 8);
plcd_usleep(1);
} else if (type == PER_LCD_CMD_TYPE_CMD_DELAY) {
plcd_spi_write_dft(&table[i + 1], cmd_size, 8);
plcd_usleep(table[i + cmd_size - 1] * 1000);
} else {
LCDERR("%s: type 0x%02x invalid\n", __func__, type);
}
i += cmd_size;
step++;
}
return ret;
}
int spi_power_on_init_dft(void)
{
int ret = 0;
if (plcd_drv->pcfg->init_on_cnt < 1) {
LCDERR("%s: init_on_cnt %d is invalid\n", __func__, plcd_drv->pcfg->cmd_size);
return -1;
}
if (!plcd_drv->pcfg->init_on) {
LCDERR("%s: init_data is null\n", __func__);
return -1;
}
if (plcd_drv->pcfg->cmd_size == PER_LCD_CMD_SIZE_DYNAMIC)
ret = spi_power_cmd_dynamic_size(1);
else
ret = spi_power_cmd_fixed_size(1);
plcd_drv->init_flag = 1;
return ret;
}
int spi_power_off_dft(void)
{
int ret = 0;
if (plcd_drv->pcfg->init_off_cnt < 1) {
LCDERR("%s: init_off_cnt %d is invalid\n", __func__, plcd_drv->pcfg->cmd_size);
return -1;
}
if (!plcd_drv->pcfg->init_off) {
LCDERR("%s: init_data is null\n", __func__);
return -1;
}
if (plcd_drv->pcfg->cmd_size == PER_LCD_CMD_SIZE_DYNAMIC)
ret = spi_power_cmd_dynamic_size(0);
else
ret = spi_power_cmd_fixed_size(0);
plcd_drv->init_flag = 0;
return ret;
}
int __init plcd_spi_device_init(void)
{
LCDPR("plcd spi device init\n");
if (spi_register_driver(&plcd_spi_dev_driver)) {
LCDPR("plcd spi driver registration failed\n");
return -ENODEV;
}
return 0;
}
void __exit plcd_spi_device_exit(void)
{
spi_unregister_driver(&plcd_spi_dev_driver);
}