blob: 65e3c2c23acafcfae4eb80ac4cce71d9d0e6a3d9 [file] [log] [blame]
// 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/spi/spi.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/reboot.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/aml_gpio_consumer.h>
#include <linux/amlogic/media/vout/peripheral_lcd.h>
#include "peripheral_lcd_dev.h"
#include "peripheral_lcd_drv.h"
static struct spi_board_info per_lcd_spi_info = {
.modalias = "peripheral_lcd",
.mode = SPI_MODE_0,
.max_speed_hz = 1000000, /* 1MHz */
.bus_num = 0, /* SPI bus No. */
.chip_select = 0, /* the device index on the spi bus */
.controller_data = NULL,
};
static int per_lcd_dev_probe_flag;
static unsigned char *table_init_on_dft;
static unsigned char *table_init_off_dft;
static struct per_lcd_dev_config_s per_lcd_dev_conf = {
.init_loaded = 0,
.cs_hold_delay = 0,
.cs_clk_delay = 0,
.cmd_size = 4,
.init_on = NULL,
.init_off = NULL,
.init_on_cnt = 0,
.init_off_cnt = 0,
.name = "none",
};
static int per_lcd_dev_init_table_dynamic_load(struct device_node *of_node,
struct per_lcd_dev_config_s *per_lcd_dev_conf, int flag)
{
unsigned char cmd_size, type = 0;
int i = 0, j, val, max_len, step = 0, ret = 0;
unsigned char *table;
char propname[20];
if (flag) {
table = table_init_on_dft;
max_len = PER_INIT_ON_MAX;
sprintf(propname, "init_on");
} else {
table = table_init_off_dft;
max_len = PER_INIT_OFF_MAX;
sprintf(propname, "init_off");
}
if (!table) {
LCDERR("%s: init_table is null\n", __func__);
return -1;
}
while ((i + 1) < max_len) {
/* type */
ret = of_property_read_u32_index(of_node, propname, i, &val);
if (ret) {
LCDERR("%s: get %s type failed, step %d\n",
per_lcd_dev_conf->name, propname, step);
table[i] = PER_LCD_CMD_TYPE_END;
table[i + 1] = 0;
return -1;
}
table[i] = (unsigned char)val;
type = table[i];
/* cmd_size */
ret = of_property_read_u32_index(of_node, propname, (i + 1), &val);
if (ret) {
LCDERR("%s: get %s cmd_size failed, step %d\n",
per_lcd_dev_conf->name, propname, step);
table[i] = PER_LCD_CMD_TYPE_END;
table[i + 1] = 0;
return -1;
}
table[i + 1] = (unsigned char)val;
cmd_size = table[i + 1];
if (type == PER_LCD_CMD_TYPE_END)
break;
if (cmd_size == 0)
goto init_table_dynamic_dts_next;
if ((i + 2 + cmd_size) > max_len) {
LCDERR("%s: %s cmd_size out of support, step %d\n",
per_lcd_dev_conf->name, propname, step);
table[i] = PER_LCD_CMD_TYPE_END;
table[i + 1] = 0;
return -1;
}
/* data */
for (j = 0; j < cmd_size; j++) {
ret = of_property_read_u32_index(of_node, propname, (i + 2 + j), &val);
if (ret) {
LCDERR("%s: get %s data failed, step %d\n",
per_lcd_dev_conf->name, propname, step);
table[i] = PER_LCD_CMD_TYPE_END;
table[i + 1] = 0;
return -1;
}
table[i + 2 + j] = (unsigned char)val;
}
init_table_dynamic_dts_next:
i += (cmd_size + 2);
step++;
}
if (flag)
per_lcd_dev_conf->init_on_cnt = i + 2;
else
per_lcd_dev_conf->init_off_cnt = i + 2;
return 0;
}
static int per_lcd_dev_init_table_fixed_load(struct device_node *of_node,
struct per_lcd_dev_config_s *per_lcd_dev_conf, int flag)
{
unsigned char cmd_size;
int i = 0, j, val, max_len, step = 0, ret = 0;
unsigned char *table;
char propname[20];
if (flag) {
table = table_init_on_dft;
max_len = PER_INIT_ON_MAX;
sprintf(propname, "init_on");
} else {
table = table_init_off_dft;
max_len = PER_INIT_OFF_MAX;
sprintf(propname, "init_off");
}
if (!table) {
LCDERR("%s: init_table is null\n", __func__);
return -1;
}
cmd_size = per_lcd_dev_conf->cmd_size;
while (i < max_len) {
/* group detect */
if ((i + cmd_size) > max_len) {
LCDERR("%s: %s cmd_size out of support, step %d\n",
per_lcd_dev_conf->name, propname, step);
table[i] = PER_LCD_CMD_TYPE_END;
return -1;
}
for (j = 0; j < cmd_size; j++) {
ret = of_property_read_u32_index(of_node, propname,
(i + j), &val);
if (ret) {
LCDERR("%s: get %s failed, step %d\n",
per_lcd_dev_conf->name, propname, step);
table[i] = PER_LCD_CMD_TYPE_END;
return -1;
}
table[i + j] = (unsigned char)val;
}
if (table[i] == PER_LCD_CMD_TYPE_END)
break;
i += cmd_size;
step++;
}
if (flag)
per_lcd_dev_conf->init_on_cnt = i + cmd_size;
else
per_lcd_dev_conf->init_off_cnt = i + cmd_size;
return 0;
}
static int per_lcd_dev_tablet_init_dft_malloc(void)
{
table_init_on_dft = kcalloc(PER_INIT_ON_MAX,
sizeof(unsigned char), GFP_KERNEL);
if (!table_init_on_dft) {
LCDERR("failed to alloc init_on table\n");
return -1;
}
table_init_off_dft = kcalloc(PER_INIT_OFF_MAX,
sizeof(unsigned char), GFP_KERNEL);
if (!table_init_off_dft) {
LCDERR("failed to alloc init_off table\n");
kfree(table_init_on_dft);
return -1;
}
table_init_on_dft[0] = PER_LCD_CMD_TYPE_END;
table_init_on_dft[1] = 0;
table_init_off_dft[0] = PER_LCD_CMD_TYPE_END;
table_init_off_dft[1] = 0;
return 0;
}
static int per_lcd_dev_table_init_save(struct per_lcd_dev_config_s *per_lcd_dev_conf)
{
if (per_lcd_dev_conf->init_on_cnt > 0) {
per_lcd_dev_conf->init_on =
kcalloc(per_lcd_dev_conf->init_on_cnt,
sizeof(unsigned char), GFP_KERNEL);
if (!per_lcd_dev_conf->init_on) {
LCDERR("failed to alloc init_on table\n");
return -1;
}
memcpy(per_lcd_dev_conf->init_on, table_init_on_dft,
per_lcd_dev_conf->init_on_cnt * sizeof(unsigned char));
}
if (per_lcd_dev_conf->init_off_cnt > 0) {
per_lcd_dev_conf->init_off =
kcalloc(per_lcd_dev_conf->init_off_cnt,
sizeof(unsigned char), GFP_KERNEL);
if (!per_lcd_dev_conf->init_off) {
LCDERR("failed to alloc init_off table\n");
kfree(per_lcd_dev_conf->init_on);
return -1;
}
memcpy(per_lcd_dev_conf->init_off, table_init_off_dft,
per_lcd_dev_conf->init_off_cnt * sizeof(unsigned char));
}
return 0;
}
static int per_lcd_dev_get_config_from_dts(struct peripheral_lcd_driver_s *per_lcd_drv)
{
struct device_node *np = per_lcd_drv->dev->of_node;
struct device_node *child;
char per_lcd_propname[20];
unsigned int index, para[20], val;
const char *str;
int ret, i;
/* get device config */
index = per_lcd_drv->dev_index;
sprintf(per_lcd_propname, "per_lcd_%d", index);
LCDPR("load: %s\n", per_lcd_propname);
child = of_get_child_by_name(np, per_lcd_propname);
if (!child) {
LCDERR("child device_node is null\n");
return -1;
}
ret = of_property_read_string(child, "peripheral_lcd_dev_name", &str);
if (ret) {
LCDERR("failed to get peripheral_lcd_dev_name\n");
str = "peripheral_lcd_dev";
}
strncpy(per_lcd_dev_conf.name, str, sizeof(per_lcd_dev_conf.name));
per_lcd_dev_conf.name[sizeof(per_lcd_dev_conf.name) - 1] = '\0';
ret = of_property_read_u32_array(child, "resolution", para, 2);
if (ret) {
LCDERR("failed to get peripheral_lcd col_row\n");
} else {
per_lcd_dev_conf.col = (unsigned short)para[0];
per_lcd_dev_conf.row = (unsigned short)para[1];
}
LCDPR("get peripheral_lcd col = %d, row = %d\n",
per_lcd_dev_conf.col, per_lcd_dev_conf.row);
ret = of_property_read_u32_array(child, "per_lcd_attr", para, 6);
if (ret) {
LCDERR("failed to get peripheral_lcd_attr\n");
} else {
per_lcd_dev_conf.data_format = para[0];
per_lcd_dev_conf.color_format = para[1];
}
LCDPR("get peripheral_lcd data_format = %d, color_format = %d\n",
per_lcd_dev_conf.data_format, per_lcd_dev_conf.color_format);
ret = of_property_read_u32(child, "type", para);
if (ret) {
LCDERR("failed to get type\n");
per_lcd_dev_conf.type = PER_DEV_TYPE_NORMAL;
} else {
per_lcd_dev_conf.type = para[0];
if (per_lcd_debug_flag)
LCDPR("type: %d\n", per_lcd_dev_conf.type);
}
if (per_lcd_dev_conf.type >= PER_DEV_TYPE_MAX) {
LCDERR("type num is out of support\n");
return -1;
}
ret = per_lcd_dev_tablet_init_dft_malloc();
if (ret)
return -1;
switch (per_lcd_dev_conf.type) {
case PER_DEV_TYPE_SPI:
/* get spi config */
per_lcd_drv->spi_info = &per_lcd_spi_info;
ret = of_property_read_u32(child, "spi_bus_num", &val);
if (ret) {
LCDERR("failed to get spi_bus_num\n");
} else {
per_lcd_spi_info.bus_num = val;
if (per_lcd_debug_flag)
LCDPR("spi bus_num: %d\n", per_lcd_spi_info.bus_num);
}
ret = of_property_read_u32(child, "spi_chip_select", &val);
if (ret) {
LCDERR("failed to get spi_chip_select\n");
} else {
per_lcd_spi_info.chip_select = val;
if (per_lcd_debug_flag) {
LCDPR("spi chip_select: %d\n",
per_lcd_spi_info.chip_select);
}
}
ret = of_property_read_u32(child, "spi_max_frequency", &val);
if (ret) {
LCDERR("failed to get spi_chip_select\n");
} else {
per_lcd_spi_info.max_speed_hz = val;
if (per_lcd_debug_flag) {
LCDPR("spi max_speed_hz: %d\n",
per_lcd_spi_info.max_speed_hz);
}
}
ret = of_property_read_u32(child, "spi_mode", &val);
if (ret) {
LCDERR("failed to get spi_mode\n");
} else {
per_lcd_spi_info.mode = val;
if (per_lcd_debug_flag)
LCDPR("spi mode: %d\n", per_lcd_spi_info.mode);
}
ret = of_property_read_u32_array(child, "spi_cs_delay", para, 2);
if (ret) {
per_lcd_dev_conf.cs_hold_delay = 0;
per_lcd_dev_conf.cs_clk_delay = 0;
} else {
per_lcd_dev_conf.cs_hold_delay = para[0];
per_lcd_dev_conf.cs_clk_delay = para[1];
}
break;
case PER_DEV_TYPE_MCU_8080:
ret = of_property_read_u32(child, "max_gpio_num", para);
if (ret) {
LCDERR("failed to get max_gpio_num\n");
per_lcd_dev_conf.max_gpio_num = 0;
} else {
per_lcd_dev_conf.max_gpio_num = para[0];
for (i = 0; i < per_lcd_dev_conf.max_gpio_num; i++)
per_lcd_gpio_probe(i);
if (per_lcd_debug_flag)
LCDPR("max_gpio_num: %d\n",
per_lcd_dev_conf.max_gpio_num);
}
ret = of_property_read_u32_array(child, "mcu_attr", para, 13);
if (ret) {
per_lcd_dev_conf.reset_index = 0;
per_lcd_dev_conf.nCS_index = 0;
per_lcd_dev_conf.nRD_index = 0;
per_lcd_dev_conf.nWR_index = 0;
per_lcd_dev_conf.nRS_index = 0;
per_lcd_dev_conf.data0_index = 0;
per_lcd_dev_conf.data1_index = 0;
per_lcd_dev_conf.data2_index = 0;
per_lcd_dev_conf.data3_index = 0;
per_lcd_dev_conf.data4_index = 0;
per_lcd_dev_conf.data5_index = 0;
per_lcd_dev_conf.data6_index = 0;
per_lcd_dev_conf.data7_index = 0;
} else {
per_lcd_dev_conf.reset_index = para[0];
per_lcd_dev_conf.nCS_index = para[1];
per_lcd_dev_conf.nRD_index = para[2];
per_lcd_dev_conf.nWR_index = para[3];
per_lcd_dev_conf.nRS_index = para[4];
per_lcd_dev_conf.data0_index = para[5];
per_lcd_dev_conf.data1_index = para[6];
per_lcd_dev_conf.data2_index = para[7];
per_lcd_dev_conf.data3_index = para[8];
per_lcd_dev_conf.data4_index = para[9];
per_lcd_dev_conf.data5_index = para[10];
per_lcd_dev_conf.data6_index = para[11];
per_lcd_dev_conf.data7_index = para[12];
}
break;
case PER_DEV_TYPE_NORMAL:
default:
break;
}
/* get init_cmd */
ret = of_property_read_u32(child, "cmd_size", &val);
if (ret) {
LCDPR("no cmd_size\n");
per_lcd_dev_conf.cmd_size = 0;
goto per_lcd_get_config_init_table_err;
} else {
per_lcd_dev_conf.cmd_size = (unsigned char)val;
}
if (per_lcd_debug_flag) {
LCDPR("%s: cmd_size = %d\n",
per_lcd_dev_conf.name,
per_lcd_dev_conf.cmd_size);
}
if (per_lcd_dev_conf.cmd_size == PER_LCD_CMD_SIZE_DYNAMIC) {
ret = per_lcd_dev_init_table_dynamic_load(child, &per_lcd_dev_conf, 1);
if (ret)
goto per_lcd_get_config_init_table_err;
per_lcd_dev_init_table_dynamic_load(child, &per_lcd_dev_conf, 0);
} else {
ret = per_lcd_dev_init_table_fixed_load(child, &per_lcd_dev_conf, 1);
if (ret)
goto per_lcd_get_config_init_table_err;
per_lcd_dev_init_table_fixed_load(child, &per_lcd_dev_conf, 0);
}
if (ret == 0)
per_lcd_dev_conf.init_loaded = 1;
if (per_lcd_dev_conf.init_loaded > 0) {
ret = per_lcd_dev_table_init_save(&per_lcd_dev_conf);
if (ret)
goto per_lcd_get_config_init_table_err;
}
kfree(table_init_on_dft);
kfree(table_init_off_dft);
return 0;
per_lcd_get_config_init_table_err:
kfree(table_init_on_dft);
kfree(table_init_off_dft);
return -1;
}
static int per_lcd_dev_add_driver(struct peripheral_lcd_driver_s *per_lcd_drv)
{
int index = per_lcd_drv->dev_index;
int ret = 0;
switch (per_lcd_dev_conf.type) {
case PER_DEV_TYPE_SPI:
ret = per_lcd_spi_driver_add(per_lcd_drv);
break;
case PER_DEV_TYPE_MCU_8080:
break;
case PER_DEV_TYPE_NORMAL:
default:
break;
}
if (ret)
return ret;
ret = -1;
if (strcmp(per_lcd_dev_conf.name, "intel_8080") == 0)
ret = per_lcd_dev_8080_probe(per_lcd_drv);
else if (strcmp(per_lcd_dev_conf.name, "spi_st7789") == 0)
ret = per_lcd_dev_spi_probe(per_lcd_drv);
else
LCDPR("%s:unsupport device driver: %s(%d)\n",
__func__, per_lcd_dev_conf.name, index);
if (ret) {
LCDPR("add device driver failed: %s(%d)\n",
per_lcd_dev_conf.name, index);
} else {
per_lcd_dev_probe_flag = 1;
LCDPR("add device driver: %s(%d)\n",
per_lcd_dev_conf.name, index);
}
return ret;
}
static void per_lcd_dev_remove_driver(struct peripheral_lcd_driver_s *per_lcd_drv)
{
int index = per_lcd_drv->dev_index;
int ret = -1;
if ((strcmp(per_lcd_dev_conf.name, "intel_8080") == 0) ||
(strcmp(per_lcd_dev_conf.name, "8080") == 0))
ret = per_lcd_dev_8080_remove(per_lcd_drv);
else if (strcmp(per_lcd_dev_conf.name, "spi_st7789") == 0)
ret = per_lcd_dev_spi_remove(per_lcd_drv);
else
LCDPR("%s: unsupport device driver: %s(%d)\n",
__func__, per_lcd_dev_conf.name, index);
if (ret) {
LCDPR("remove device driver failed: %s(%d)\n",
per_lcd_dev_conf.name, index);
} else {
per_lcd_dev_probe_flag = 0;
LCDPR("remove device driver: %s(%d)\n",
per_lcd_dev_conf.name, index);
}
switch (per_lcd_dev_conf.type) {
case PER_DEV_TYPE_SPI:
per_lcd_spi_driver_remove(per_lcd_drv);
break;
case PER_DEV_TYPE_MCU_8080:
break;
case PER_DEV_TYPE_NORMAL:
default:
break;
}
}
static void per_lcd_config_update_dynamic_size(struct peripheral_lcd_driver_s *edrv, int flag)
{
unsigned char type, size, *table;
unsigned int max_len = 0, i = 0, index;
if (flag) {
max_len = edrv->per_lcd_dev_conf->init_on_cnt;
table = edrv->per_lcd_dev_conf->init_on;
} else {
max_len = edrv->per_lcd_dev_conf->init_off_cnt;
table = edrv->per_lcd_dev_conf->init_on;
}
while ((i + 1) < max_len) {
type = table[i];
size = table[i + 1];
if (type == PER_LCD_CMD_TYPE_END)
break;
if (size == 0)
goto per_lcd_config_update_dynamic_size_next;
if ((i + 2 + size) > max_len)
break;
if (type == PER_LCD_CMD_TYPE_GPIO) {
/* gpio probe */
index = table[i + 2];
if (index < PER_GPIO_MAX)
per_lcd_gpio_probe(index);
}
per_lcd_config_update_dynamic_size_next:
i += (size + 2);
}
}
static void per_lcd_config_update_fixed_size(struct peripheral_lcd_driver_s *edrv, int flag)
{
int i = 0, max_len = 0;
unsigned char type, cmd_size, index;
unsigned char *table;
cmd_size = edrv->per_lcd_dev_conf->cmd_size;
if (cmd_size < 2) {
LCDPR("[%d]: %s: dev_%d invalid cmd_size %d\n",
edrv->dev_index, __func__, edrv->dev_index, cmd_size);
return;
}
if (flag) {
max_len = edrv->per_lcd_dev_conf->init_on_cnt;
table = edrv->per_lcd_dev_conf->init_on;
} else {
max_len = edrv->per_lcd_dev_conf->init_off_cnt;
table = edrv->per_lcd_dev_conf->init_on;
}
while ((i + cmd_size) <= max_len) {
type = table[i];
if (type == PER_LCD_CMD_TYPE_END)
break;
if (type == PER_LCD_CMD_TYPE_GPIO) {
/* gpio probe */
index = table[i + 1];
if (index < PER_GPIO_MAX)
per_lcd_gpio_probe(index);
}
i += cmd_size;
}
}
static void per_lcd_config_update(struct peripheral_lcd_driver_s *per_drv)
{
if (per_drv->per_lcd_dev_conf->cmd_size == PER_LCD_CMD_SIZE_DYNAMIC) {
LCDPR("%s dynamic\n", __func__);
per_lcd_config_update_dynamic_size(per_drv, 1);
per_lcd_config_update_dynamic_size(per_drv, 0);
} else {
LCDPR("%s fixed\n", __func__);
per_lcd_config_update_fixed_size(per_drv, 1);
per_lcd_config_update_fixed_size(per_drv, 0);
}
}
int perl_lcd_dev_probe(struct peripheral_lcd_driver_s *per_lcd_drv)
{
int ret;
per_lcd_drv->per_lcd_dev_conf = &per_lcd_dev_conf;
ret = per_lcd_dev_get_config_from_dts(per_lcd_drv);
if (ret) {
LCDERR("%s: get dts config error\n", __func__);
per_lcd_drv->per_lcd_dev_conf = NULL;
return -1;
}
per_lcd_config_update(per_lcd_drv);
ret = per_lcd_dev_add_driver(per_lcd_drv);
LCDPR("%s: ret=%d\n", __func__, ret);
return ret;
}
void per_lcd_dev_remove(struct peripheral_lcd_driver_s *per_lcd_drv)
{
if (!per_lcd_drv->per_lcd_dev_conf)
return;
per_lcd_dev_remove_driver(per_lcd_drv);
LCDPR("%s OK\n", __func__);
}