blob: fe1132f2975c79dfd34926ab5673eb638fcc307b [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/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/lcd/aml_ldim.h>
#include <linux/amlogic/media/vout/lcd/aml_bl.h>
#include "../../lcd_common.h"
#include "ldim_drv.h"
#include "ldim_dev_drv.h"
#define NORMAL_MSG (0 << 7)
#define BROADCAST_MSG BIT(7)
#define BLOCK_DATA (0 << 6)
#define SINGLE_DATA BIT(6)
#define BLMCU_CLASS_NAME "blmcu"
#define VSYNC_INFO_FREQUENT 300
static DEFINE_MUTEX(spi_mutex);
static DEFINE_MUTEX(dev_mutex);
struct blmcu_s {
unsigned int dev_on_flag;
unsigned int dma_support;
unsigned short vsync_cnt;
unsigned int rbuf_size;
unsigned int tbuf_size;
unsigned int command;
unsigned int adim;
unsigned int pdim;
unsigned int apl;
/* local dimming driver smr api usage */
unsigned char *rbuf;
/* spi api internal used, don't use outside!!! */
unsigned char *tbuf;
};
struct blmcu_s *bl_mcu;
static int blmcu_hw_init_on(struct ldim_dev_driver_s *dev_drv)
{
unsigned char chip_id;// reg, temp[2];
//int i, retry_cnt = 0;
LDIMPR("%s\n", __func__);
chip_id = 0x01;
/* step 1: system power_on */
ldim_gpio_set(dev_drv, dev_drv->en_gpio, dev_drv->en_gpio_on);
/* step 2: delay for internal logic stable */
lcd_delay_ms(10);
/* step 3: Generate external VSYNC to VSYNC/PWM pin */
ldim_set_duty_pwm(&dev_drv->ldim_pwm_config);
ldim_set_duty_pwm(&dev_drv->analog_pwm_config);
dev_drv->pinmux_ctrl(dev_drv, 1);
/* step 4: delay for system clock and light bar PSU stable */
lcd_delay_ms(520);
return 0;
}
static int blmcu_hw_init_off(struct ldim_dev_driver_s *dev_drv)
{
ldim_gpio_set(dev_drv, dev_drv->en_gpio, dev_drv->en_gpio_off);
dev_drv->pinmux_ctrl(dev_drv, 0);
ldim_pwm_off(&dev_drv->ldim_pwm_config);
ldim_pwm_off(&dev_drv->analog_pwm_config);
return 0;
}
static void ldim_vs_debug_info(struct aml_ldim_driver_s *ldim_drv)
{
struct ldim_dev_driver_s *dev_drv = ldim_drv->dev_drv;
unsigned int i, j;
if (bl_mcu->vsync_cnt) //300 vsync print once
return;
if (ldim_debug_print != 3)
return;
LDIMPR("%s:\n", __func__);
j = (3 * dev_drv->zone_num + 1) / 2 + 7;
for (i = 0; i < j; i++)
LDIMPR("tbuf[%d]: 0x%x\n", i, bl_mcu->tbuf[i]);
pr_info("\n");
}
static inline void ldim_data_mapping(unsigned int *duty_buf,
unsigned int max, unsigned int min,
unsigned int zone_num)
{
unsigned int i, j, val, apl, k;
j = 0;
apl = 0;
for (i = 0; i < zone_num; i++) {
apl += duty_buf[i];
val = min + ((duty_buf[i] * (max - min)) / LD_DATA_MAX);
if (i % 2 == 0) {
bl_mcu->rbuf[j] = (val >> 4) & 0xff;
bl_mcu->rbuf[j + 1] = ((val & 0xf) << 4) & 0xff;
} else {
bl_mcu->rbuf[j + 1] |= (val >> 8) & 0xf;
bl_mcu->rbuf[j + 2] = val & 0xff;
j += 3;
}
}
bl_mcu->apl = apl / zone_num;
k = (3 * zone_num + 1) / 2;
for (i = 0; i < k; i++)
bl_mcu->tbuf[i + 4] = bl_mcu->rbuf[i];
bl_mcu->tbuf[0] = 0x55;
bl_mcu->tbuf[1] = 0xaa;
bl_mcu->tbuf[2] = (bl_mcu->command >> 8) & 0xff;
bl_mcu->tbuf[3] = bl_mcu->command & 0xff;
bl_mcu->tbuf[k + 4] = bl_mcu->pdim & 0xff;
bl_mcu->tbuf[k + 5] = bl_mcu->adim & 0xff;
bl_mcu->tbuf[k + 6] = (bl_mcu->apl >> 4) & 0xff; //apl only 8bit
for (i = k + 7; i < bl_mcu->tbuf_size; i++)
bl_mcu->tbuf[i] = 0;
}
static int blmcu_smr(struct aml_ldim_driver_s *ldim_drv, unsigned int *buf,
unsigned int len)
{
struct ldim_dev_driver_s *dev_drv = ldim_drv->dev_drv;
int ret = 0;
if (!bl_mcu)
return -1;
if (bl_mcu->vsync_cnt++ >= VSYNC_INFO_FREQUENT)
bl_mcu->vsync_cnt = 0;
if (bl_mcu->dev_on_flag == 0) {
if (bl_mcu->vsync_cnt == 0)
LDIMPR("%s: on_flag=%d\n", __func__, bl_mcu->dev_on_flag);
return 0;
}
if (len != dev_drv->zone_num) {
if (bl_mcu->vsync_cnt == 0)
LDIMERR("%s: data len %d invalid\n", __func__, len);
return -1;
}
if (!bl_mcu->rbuf) {
if (bl_mcu->vsync_cnt == 0)
LDIMERR("%s: rbuf is null\n", __func__);
return -1;
}
if (!bl_mcu->tbuf) {
if (bl_mcu->vsync_cnt == 0)
LDIMERR("%s: tbuf is null\n", __func__);
return -1;
}
mutex_lock(&dev_mutex);
ldim_data_mapping(buf, dev_drv->dim_max, dev_drv->dim_min,
dev_drv->zone_num);
ldim_vs_debug_info(ldim_drv);
ret = ldim_spi_write_async(dev_drv->spi_dev, bl_mcu->tbuf, bl_mcu->tbuf_size,
bl_mcu->dma_support, bl_mcu->tbuf_size);
mutex_unlock(&dev_mutex);
return ret;
}
static int blmcu_smr_dummy(struct aml_ldim_driver_s *ldim_drv)
{
return 0;
}
static int blmcu_fault_handler(struct aml_ldim_driver_s *ldim_drv)
{
int ret = 0;
return ret;
}
static int blmcu_config_update(struct aml_ldim_driver_s *ldim_drv)
{
int ret = 0;
LDIMPR("%s: func_en = %d\n", __func__, ldim_drv->func_en);
return ret;
}
static int blmcu_power_on(struct aml_ldim_driver_s *ldim_drv)
{
if (!bl_mcu)
return -1;
if (bl_mcu->dev_on_flag) {
LDIMPR("%s: blmcu is already on, exit\n", __func__);
return 0;
}
mutex_lock(&dev_mutex);
blmcu_hw_init_on(ldim_drv->dev_drv);
bl_mcu->dev_on_flag = 1;
bl_mcu->vsync_cnt = 0;
mutex_unlock(&dev_mutex);
LDIMPR("%s: ok\n", __func__);
return 0;
}
static int blmcu_power_off(struct aml_ldim_driver_s *ldim_drv)
{
LDIMPR("enter %s\n", __func__);
if (!bl_mcu)
return -1;
LDIMPR("enter %s111\n", __func__);
mutex_lock(&dev_mutex);
bl_mcu->dev_on_flag = 0;
blmcu_hw_init_off(ldim_drv->dev_drv);
mutex_unlock(&dev_mutex);
LDIMPR("%s: ok\n", __func__);
return 0;
}
static ssize_t blmcu_show(struct class *class, struct class_attribute *attr, char *buf)
{
struct aml_ldim_driver_s *ldim_drv = aml_ldim_get_driver();
struct ldim_dev_driver_s *dev_drv;
int len = 0;
if (!bl_mcu)
return sprintf(buf, "bl_mcu is null\n");
dev_drv = ldim_drv->dev_drv;
if (!strcmp(attr->attr.name, "blmcu_status")) {
len = sprintf(buf, "blmcu status:\n"
"dev_index = %d\n"
"dma_support = %d\n"
"on_flag = %d\n"
"vsync_cnt = %d\n"
"tbuf_size = %d\n"
"rbuf_size = %d\n"
"adim = 0x%x\n"
"pdim = 0x%x\n"
"command = 0x%x\n"
"apl = 0x%x\n"
"en_on = %d\n"
"en_off = %d\n"
"cs_hold_delay = %d\n"
"cs_clk_delay = %d\n"
"dim_max = 0x%03x\n"
"dim_min = 0x%03x\n",
dev_drv->index,
bl_mcu->dev_on_flag,
bl_mcu->dma_support,
bl_mcu->vsync_cnt,
bl_mcu->tbuf_size,
bl_mcu->rbuf_size,
bl_mcu->adim,
bl_mcu->pdim,
bl_mcu->command,
bl_mcu->apl,
dev_drv->en_gpio_on,
dev_drv->en_gpio_off,
dev_drv->cs_hold_delay,
dev_drv->cs_clk_delay,
dev_drv->dim_max,
dev_drv->dim_min);
} else {
return sprintf(buf, "invalid node\n");
}
return len;
}
static ssize_t blmcu_store(struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
struct aml_ldim_driver_s *ldim_drv = aml_ldim_get_driver();
struct ldim_dev_driver_s *dev_drv;
// unsigned int val;
int n = 0;
char *buf_orig, *ps, *token;
char **parm = NULL;
char str[3] = {' ', '\n', '\0'};
if (!buf)
return count;
buf_orig = kstrdup(buf, GFP_KERNEL);
if (!buf_orig)
return count;
parm = kcalloc(128, sizeof(char *), GFP_KERNEL);
if (!parm)
goto blmcu_store_end;
ps = buf_orig;
while (1) {
token = strsep(&ps, str);
if (!token)
break;
if (*token == '\0')
continue;
parm[n++] = token;
}
dev_drv = ldim_drv->dev_drv;
if (!strcmp(parm[0], "init")) {
mutex_lock(&dev_mutex);
blmcu_hw_init_on(dev_drv);
mutex_unlock(&dev_mutex);
} else if (!strcmp(parm[0], "adim")) {
if (parm[1]) {
if (kstrtouint(parm[1], 10, &bl_mcu->adim) < 0)
goto blmcu_store_err;
}
LDIMPR("adim: 0x%x\n", bl_mcu->adim);
} else if (!strcmp(parm[0], "pdim")) {
if (parm[1]) {
if (kstrtouint(parm[1], 10, &bl_mcu->pdim) < 0)
goto blmcu_store_err;
}
LDIMPR("pdim: 0x%x\n", bl_mcu->pdim);
} else if (!strcmp(parm[0], "cmd")) {
if (parm[1]) {
if (kstrtouint(parm[1], 10, &bl_mcu->command) < 0)
goto blmcu_store_err;
}
LDIMPR("command: 0x%x\n", bl_mcu->command);
} else if (!strcmp(parm[0], "dma")) {
if (parm[1]) {
if (kstrtouint(parm[1], 10, &bl_mcu->dma_support) < 0)
goto blmcu_store_err;
}
LDIMPR("dma_support: %d\n", bl_mcu->dma_support);
} else {
LDIMERR("argment error!\n");
}
blmcu_store_end:
kfree(buf_orig);
kfree(parm);
return count;
blmcu_store_err:
pr_info("invalid cmd!!!\n");
kfree(buf_orig);
kfree(parm);
return count;
}
static struct class_attribute blmcu_class_attrs[] = {
__ATTR(init, 0644, NULL, blmcu_store),
__ATTR(blmcu_status, 0644, blmcu_show, NULL),
};
static int blmcu_ldim_dev_update(struct ldim_dev_driver_s *dev_drv)
{
dev_drv->power_on = blmcu_power_on;
dev_drv->power_off = blmcu_power_off;
dev_drv->dev_smr = blmcu_smr;
dev_drv->dev_smr_dummy = blmcu_smr_dummy;
dev_drv->dev_err_handler = blmcu_fault_handler;
dev_drv->config_update = blmcu_config_update;
dev_drv->reg_write = NULL;
dev_drv->reg_read = NULL;
return 0;
}
int ldim_dev_blmcu_probe(struct aml_ldim_driver_s *ldim_drv)
{
struct ldim_dev_driver_s *dev_drv = ldim_drv->dev_drv;
int i, n;
if (!dev_drv) {
LDIMERR("%s: dev_drv is null\n", __func__);
return -1;
}
if (!dev_drv->spi_dev) {
LDIMERR("%s: spi_dev is null\n", __func__);
return -1;
}
bl_mcu = kzalloc(sizeof(*bl_mcu), GFP_KERNEL);
if (!bl_mcu) {
LDIMERR("%s: bl_mcu is error\n", __func__);
return -1;
}
bl_mcu->dev_on_flag = 0;
bl_mcu->vsync_cnt = 0;
bl_mcu->adim = 0xa0;
bl_mcu->pdim = 0xa0;
bl_mcu->command = 0x0014;
bl_mcu->dma_support = dev_drv->dma_support;
/* each zone 2 bytes */
bl_mcu->rbuf_size = 2 * dev_drv->zone_num;
bl_mcu->rbuf = kcalloc(bl_mcu->rbuf_size, sizeof(unsigned char), GFP_KERNEL);
if (!bl_mcu->rbuf) {
LDIMERR("%s: bl_mcu->rbuf is error\n", __func__);
goto ldim_dev_blmcu_probe_err0;
}
/* header + data + suffix */
/* according custom backlight mcu spi spec to set tbuf_size */
bl_mcu->tbuf_size = 7 + (dev_drv->zone_num * 3 + 1) / 2;
if (bl_mcu->dma_support) {
n = bl_mcu->tbuf_size;
bl_mcu->tbuf_size = ldim_spi_dma_cycle_align_byte(n);
LDIMPR("%s: bl_mcu->tbuf_size is %d --> %d\n", __func__, n, bl_mcu->tbuf_size);
}
bl_mcu->tbuf = kcalloc(bl_mcu->tbuf_size, sizeof(unsigned char), GFP_KERNEL);
if (!bl_mcu->tbuf) {
LDIMERR("%s: bl_mcu->tbuf is error\n", __func__);
goto ldim_dev_blmcu_probe_err1;
}
blmcu_ldim_dev_update(dev_drv);
if (dev_drv->class) {
for (i = 0; i < ARRAY_SIZE(blmcu_class_attrs); i++) {
if (class_create_file(dev_drv->class, &blmcu_class_attrs[i])) {
LDIMERR("create ldim_dev class attribute %s fail\n",
blmcu_class_attrs[i].attr.name);
}
}
}
bl_mcu->dev_on_flag = 1; /* default enable in uboot */
LDIMPR("%s ok\n", __func__);
return 0;
ldim_dev_blmcu_probe_err1:
kfree(bl_mcu->rbuf);
bl_mcu->rbuf_size = 0;
ldim_dev_blmcu_probe_err0:
kfree(bl_mcu);
bl_mcu = NULL;
return -1;
}
int ldim_dev_blmcu_remove(struct aml_ldim_driver_s *ldim_drv)
{
if (!bl_mcu)
return 0;
kfree(bl_mcu->rbuf);
bl_mcu->rbuf_size = 0;
kfree(bl_mcu->tbuf);
bl_mcu->tbuf_size = 0;
kfree(bl_mcu);
bl_mcu = NULL;
return 0;
}