Files
kernel_common_drivers/drivers/led/meson_led_unipolar_ctrl.c
T
junyi.zhao 9da510c799 led: adjust unipolar ctrl timming [1/1]
PD#SWPL-118117

Problem:
need to fix led ctrl timming

Solution:
adjust cycle 1.2 us
adjust high code 0.9 us
adjust low code 0.3 us

Verify:
t3x

Change-Id: Idd9c8befe3f526287eca828b27b956f90d4310f6
Signed-off-by: junyi.zhao <junyi.zhao@amlogic.com>
2023-04-04 00:15:45 -07:00

476 lines
14 KiB
C

// 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/platform_device.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/gpio/consumer.h>
#include <asm-generic/io.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/dma-mapping.h>
#include <linux/leds.h>
// #define M_DEBUG
#ifdef M_DEBUG
#define DEBUG
#define LEDCON_DBG(fmt, ...) pr_info("%s " fmt, "[DCON LED]", ##__VA_ARGS__)
#else
#define LEDCON_DBG(fmt, ...)
#endif
#define LED_MAX_NUM 32
#define COLOR_CHANNEL_NUM 3
/* Meson DCON_LED register map */
#define LED_CTRL_DATA_BASE 0x00
#define LED_CTRL_DATA_NUM 32
#define LED_CONTROL_REG (0x0020 << 2)
#define LED_CYCLE_RATIO_RES (0x0021 << 2)
/* Control DCON_LED fields */
#define CTRL_INTERRUPT_CLEAR BIT(31)
#define CTRL_OUTPUT_ENABLE BIT(30)
#define CTRL_START BIT(29)
#define CTRL_INTERRUPT_DISABLE BIT(28)
#define CTRL_LSB_FIRST BIT(27)
#define CTRL_PUT_RESET_CODE_SHIFT 11
#define CTRL_PUT_RESET_CODE_MSK GENMASK(26, 11)
#define CTRL_LED_NUMS_SHIFT 6
#define CTRL_LED_NUMS_MSK GENMASK(10, 6)
#define CTRL_LED_FRAME_NUMS_SHIFT 2
#define CTRL_LED_FRAME_NUMS_MSK GENMASK(5, 2)
#define CTRL_LED_DATA_FORMAT_SHIFT 0
#define CTRL_LED_DATA_FORMAT_MSK GENMASK(1, 0)
enum {
FORMAT_8BIT = 0,
FORMAT_16BIT,
FORMAT_24BIT,
FORMAT_32BIT,
};
/* Control DCON_LED RATIO fields */
#define RATIO_INTERRUPT_STATUS BIT(29)
#define RATIO_BUSY_STATUS BIT(28)
#define RATIO_PRE_RESETCODE BIT(27)
#define RATIO_RESET_DURATION_SHIFT 17
#define RATIO_RESET_DURATION_MSK GENMASK(26, 17)
#define RATIO_HIGH_CODE_SET_SHIFT 11
#define RATIO_HIGH_CODE_SET_MSK GENMASK(16, 11)
#define RATIO_LOW_CODE_SET_SHIFT 6
#define RATIO_LOW_CODE_SET_MSK GENMASK(10, 6)
#define RATIO_CYCLE_SHIFT 0
#define RATIO_CYCLE_MSK GENMASK(5, 0)
struct meson_unipolar_ctrl {
struct device *dev;
struct led_classdev cdev;
void __iomem *regs;
spinlock_t lock;//protection for led controller critical section
int irq;
uint led_num;
u8 *color_data;
};
#define MESON_UNIPOLAR_CTRL_CDEV_NAME "unipolar_led"
static void meson_unipolar_ctrl_set_mask(struct meson_unipolar_ctrl *dcon_led, int reg, u32 mask,
u32 val)
{
u32 data;
data = readl(dcon_led->regs + reg);
data &= ~mask;
data |= val & mask;
writel(data, dcon_led->regs + reg);
}
static void meson_unipolar_ctrl_xfer(struct meson_unipolar_ctrl *dcon_led)
{
meson_unipolar_ctrl_set_mask(dcon_led, LED_CONTROL_REG, CTRL_START, CTRL_START);
}
static void meson_unipolar_ctrl_init(struct meson_unipolar_ctrl *dcon_led)
{
/*disable interrupt*/
meson_unipolar_ctrl_set_mask(dcon_led,
LED_CONTROL_REG, CTRL_INTERRUPT_CLEAR, CTRL_INTERRUPT_CLEAR);
meson_unipolar_ctrl_set_mask(dcon_led,
LED_CONTROL_REG, CTRL_INTERRUPT_DISABLE, CTRL_INTERRUPT_DISABLE);
/*set led num*/
meson_unipolar_ctrl_set_mask(dcon_led, LED_CONTROL_REG,
CTRL_LED_NUMS_MSK, (dcon_led->led_num - 1) << CTRL_LED_NUMS_SHIFT);
/*set one frame data flow*/
// meson_unipolar_ctrl_set_mask(dcon_led,
// LED_CONTROL_REG,CTRL_LED_FRAME_NUMS_MSK, 0 << CTRL_LED_FRAME_NUMS_SHIFT);
/*set format RGB 24BIT MSB first*/
meson_unipolar_ctrl_set_mask(dcon_led, LED_CONTROL_REG,
CTRL_LED_DATA_FORMAT_MSK, FORMAT_24BIT << CTRL_LED_DATA_FORMAT_SHIFT);
/* pwm ratio set
*
* output 1---->h:0.9 l:0.3
* output 0---->h:0.3 l:0.9
*/
/*set led cycle 1/24*29 = 1.2us*/
meson_unipolar_ctrl_set_mask(dcon_led, LED_CYCLE_RATIO_RES,
RATIO_CYCLE_MSK, 0x1d << RATIO_CYCLE_SHIFT);
/*set high duty 1/24*22 = 0.9us*/
meson_unipolar_ctrl_set_mask(dcon_led, LED_CYCLE_RATIO_RES,
RATIO_HIGH_CODE_SET_MSK, 0x16 << RATIO_HIGH_CODE_SET_SHIFT);
/*set low duty 1/24*7 = 0.3us*/
meson_unipolar_ctrl_set_mask(dcon_led, LED_CYCLE_RATIO_RES,
RATIO_LOW_CODE_SET_MSK, 0x7 << RATIO_LOW_CODE_SET_SHIFT);
/*set reset duration 0x240*1.25 = 300 us*/
meson_unipolar_ctrl_set_mask(dcon_led, LED_CYCLE_RATIO_RES,
RATIO_RESET_DURATION_MSK, 0xF0 << RATIO_RESET_DURATION_SHIFT);
/*send data with out reset*/
// meson_unipolar_ctrl_set_mask(dcon_led,LED_CYCLE_RATIO_RES,
// RATIO_RESET_DURATION_MSK, RATIO_PRE_RESETCODE);
}
static void meson_unipolar_ctrl_put_data(struct meson_unipolar_ctrl *dcon_led)
{
u32 wdata = 0;
int i, j;
char *buf = dcon_led->color_data;
LEDCON_DBG("%s flush controller data reg\n", __func__);
spin_lock(&dcon_led->lock);
for (j = 0; j < dcon_led->led_num; j++) {
for (i = 0; i < COLOR_CHANNEL_NUM; i++) {
wdata |= *buf++ << ((2 - i) * 8);
LEDCON_DBG("%s: buffer ptr is 0x%x\n", __func__, buf);
}
LEDCON_DBG("flushing data 0x%x to 0x%x\n", wdata,
dcon_led->regs + LED_CTRL_DATA_BASE + j * 4);
writel(wdata, dcon_led->regs + LED_CTRL_DATA_BASE + j * 4);
wdata = 0;
}
spin_unlock(&dcon_led->lock);
}
static int meson_unipolar_ctrl_put_data_to_buffer(struct meson_unipolar_ctrl *dcon_led,
u8 buffer_id, u8 r_data, u8 g_data, u8 b_data)
{
spin_lock(&dcon_led->lock);
/*R G B*/
// dcon_led->color_data[buffer_id] = r_data;
// dcon_led->color_data[buffer_id + 1] = g_data;
// dcon_led->color_data[buffer_id + 2] = b_data;
/*G R B*/
dcon_led->color_data[buffer_id] = g_data;
dcon_led->color_data[buffer_id + 1] = r_data;
dcon_led->color_data[buffer_id + 2] = b_data;
spin_unlock(&dcon_led->lock);
return 0;
}
static int meson_unipolar_ctrl_set_singlecolors(u32 ledid,
struct meson_unipolar_ctrl *dcon_led, u32 color)
{
u8 r_data, g_data, b_data;
if (ledid > dcon_led->led_num - 1) {
dev_err(dcon_led->dev, "valid led id\n");
return -1;
}
LEDCON_DBG("%s set led id %d, color 0x%x\n", __func__, ledid, color);
r_data = (color & GENMASK(24, 16)) >> 16;
g_data = (color & GENMASK(15, 8)) >> 8;
b_data = color & GENMASK(7, 0);
LEDCON_DBG("%s set led id %d, color r:0x%x g:0x%x b:0x%x\n",
__func__, ledid, r_data, g_data, b_data);
meson_unipolar_ctrl_put_data_to_buffer(dcon_led,
ledid * COLOR_CHANNEL_NUM, r_data, g_data, b_data);
meson_unipolar_ctrl_put_data(dcon_led);
meson_unipolar_ctrl_xfer(dcon_led);
return 0;
}
static int meson_unipolar_ctrl_clear_all_colors(struct meson_unipolar_ctrl *dcon_led)
{
LEDCON_DBG("%s\n", __func__);
memset(dcon_led->color_data, 0, dcon_led->led_num * COLOR_CHANNEL_NUM);
meson_unipolar_ctrl_put_data(dcon_led);
meson_unipolar_ctrl_xfer(dcon_led);
return 0;
}
static ssize_t single_color_show(struct device *child,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(child);
struct meson_unipolar_ctrl *led_con = container_of(led_cdev,
struct meson_unipolar_ctrl, cdev);
u32 color;
int j;
ssize_t len = 0;
for (j = 0; j < led_con->led_num; j++) {
color = (led_con->color_data[j * COLOR_CHANNEL_NUM] << 16) +
(led_con->color_data[j * COLOR_CHANNEL_NUM + 1] << 8) +
led_con->color_data[j * COLOR_CHANNEL_NUM + 2];
len += snprintf(buf + len, PAGE_SIZE - len, "color%d=0x%x\n", j, color);
}
return len;
}
static ssize_t single_color_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(child);
struct meson_unipolar_ctrl *dcon_led = container_of(led_cdev,
struct meson_unipolar_ctrl, cdev);
int ret;
u32 id, color;
ret = sscanf(buf, "%d %x", &id, &color);
LEDCON_DBG("%s id:%d color 0x%x\n", __func__, id, color);
if (ret != 2) {
dev_err(dcon_led->dev, "Can't parse! usage:[id color]\n");
return -EINVAL;
}
meson_unipolar_ctrl_set_singlecolors(id, dcon_led, color);
return size;
}
static DEVICE_ATTR_RW(single_color);
static ssize_t colors_clear_all_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(child);
struct meson_unipolar_ctrl *dcon_led = container_of(led_cdev,
struct meson_unipolar_ctrl, cdev);
int ret;
u32 clear;
ret = kstrtou32(buf, 10, &clear);
LEDCON_DBG("%s id:%d clear 0x%x\n", __func__, clear);
if (ret) {
dev_err(dcon_led->dev, "Can't parse! usage:[echo 1 > colors_clear_all]\n");
return -EINVAL;
}
meson_unipolar_ctrl_clear_all_colors(dcon_led);
return size;
}
static DEVICE_ATTR_WO(colors_clear_all);
static ssize_t colors_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(child);
struct meson_unipolar_ctrl *dcon_led = container_of(led_cdev,
struct meson_unipolar_ctrl, cdev);
int ret, i, split_n;
u32 id_start, id_end, color;
char *str_buff;
void *str_buff_;
u8 r_data, g_data, b_data;
str_buff_ = kzalloc(size, GFP_KERNEL);
memcpy(str_buff_, buf, size);
str_buff = (char *)str_buff_;
ret = sscanf(str_buff, "%d %d %n", &id_start, &id_end, &split_n);
str_buff += split_n;
LEDCON_DBG("%s start_id:%d end_id:%d\n", __func__, id_start, id_end);
if (ret != 2 || id_start > id_end || id_end > LED_MAX_NUM - 1) {
dev_err(dcon_led->dev, "Can't parse! usage:[id_start id_end color1 color2...]\n");
kfree(str_buff_);
return -EINVAL;
}
for (i = 0; i < id_end - id_start + 1; i++) {
ret = sscanf(str_buff, "%x %n", &color, &split_n);
if (ret != 1) {
dev_err(dcon_led->dev, "Can't parse! usage:[id_start id_end color1 color2...]\n");
kfree(str_buff_);
return -EINVAL;
}
str_buff += split_n;
r_data = (color & GENMASK(24, 16)) >> 16;
g_data = (color & GENMASK(15, 8)) >> 8;
b_data = color & GENMASK(7, 0);
meson_unipolar_ctrl_put_data_to_buffer(dcon_led,
(id_start + i) * COLOR_CHANNEL_NUM, r_data, g_data, b_data);
}
meson_unipolar_ctrl_put_data(dcon_led);
meson_unipolar_ctrl_xfer(dcon_led);
kfree(str_buff_);
return size;
}
static DEVICE_ATTR_WO(colors);
static struct attribute *meson_unipolar_ctrl_attributes[] = {
&dev_attr_single_color.attr,
&dev_attr_colors_clear_all.attr,
&dev_attr_colors.attr,
NULL
};
static struct attribute_group meson_unipolar_ctrl_attribute_group = {
.attrs = meson_unipolar_ctrl_attributes
};
static irqreturn_t meson_unipolar_led_irq(int irqno, void *dev_id)
{
struct meson_unipolar_ctrl *dcon_led = dev_id;
LEDCON_DBG("interrupt\n");
meson_unipolar_ctrl_set_mask(dcon_led, LED_CONTROL_REG,
CTRL_INTERRUPT_CLEAR, CTRL_INTERRUPT_CLEAR);
return IRQ_HANDLED;
}
void meson_unipolar_set_brightness(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct meson_unipolar_ctrl *dcon_led = container_of(led_cdev,
struct meson_unipolar_ctrl, cdev);
memset(dcon_led->color_data, brightness, dcon_led->led_num * COLOR_CHANNEL_NUM);
meson_unipolar_ctrl_put_data(dcon_led);
meson_unipolar_ctrl_xfer(dcon_led);
}
static int unipolar_ctrl_probe(struct platform_device *pdev)
{
struct meson_unipolar_ctrl *dcon_led;
struct resource *mem;
int irq, ret = 0;
LEDCON_DBG("unipolar led ctrl probe start\n");
dcon_led = devm_kzalloc(&pdev->dev, sizeof(struct meson_unipolar_ctrl), GFP_KERNEL);
if (!dcon_led)
return -ENOMEM;
dcon_led->dev = &pdev->dev;
platform_set_drvdata(pdev, dcon_led);
spin_lock_init(&dcon_led->lock);
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
LEDCON_DBG("request mem start:0x%x, end:0x%x\n",
mem->start, mem->end);
dcon_led->regs = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(dcon_led->regs)) {
dev_err(dcon_led->dev, "fail to get mem resources\n");
return PTR_ERR(dcon_led->regs);
}
LEDCON_DBG("get mem start:0x%x, end:0x%x\n",
mem->start, mem->end);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "can't find IRQ\n");
return irq;
}
ret = devm_request_irq(&pdev->dev, irq, meson_unipolar_led_irq,
IRQF_TRIGGER_RISING, NULL, dcon_led);
if (ret < 0) {
dev_err(&pdev->dev, "can't request IRQ\n");
return ret;
}
dcon_led->irq = irq;
ret = device_property_read_u32(&pdev->dev, "led_number", &dcon_led->led_num);
if (ret < 0) {
dev_err(&pdev->dev, "Failure to get led num = %d\n", ret);
return ret;
}
LEDCON_DBG("get led num:%u\n", dcon_led->led_num);
if (dcon_led->led_num > LED_MAX_NUM) {
dev_err(&pdev->dev, "erro, LED num over than LED_MAX_NUM:%d\n", LED_MAX_NUM);
return -EINVAL;
}
dcon_led->color_data = devm_kzalloc(&pdev->dev,
dcon_led->led_num * COLOR_CHANNEL_NUM, GFP_KERNEL);
if (!dcon_led->color_data)
return -ENOMEM;
/* Disable the interrupt so that the system can enter low-power mode */
// disable_irq(dcon_led->irq);
meson_unipolar_ctrl_init(dcon_led);
dcon_led->cdev.name = MESON_UNIPOLAR_CTRL_CDEV_NAME;
dcon_led->cdev.brightness = 0;
dcon_led->cdev.max_brightness = 255;
dcon_led->cdev.brightness_set = meson_unipolar_set_brightness;
ret = led_classdev_register(dcon_led->dev, &dcon_led->cdev);
if (ret) {
dev_err(dcon_led->dev,
"unable to register led ret=%d\n", ret);
return ret;
}
ret = sysfs_create_group(&dcon_led->cdev.dev->kobj,
&meson_unipolar_ctrl_attribute_group);
if (ret) {
dev_err(dcon_led->dev, "unable to create unipolar led sysfs! ret = %d\n",
ret);
led_classdev_unregister(&dcon_led->cdev);
return ret;
}
return 0;
}
static int unipolar_ctrl_remove(struct platform_device *pdev)
{
struct meson_unipolar_ctrl *dcon_led = platform_get_drvdata(pdev);
pr_info("%s enter\n", __func__);
sysfs_remove_group(&dcon_led->cdev.dev->kobj,
&meson_unipolar_ctrl_attribute_group);
led_classdev_unregister(&dcon_led->cdev);
return 0;
}
static const struct of_device_id unipolar_ctrl_table[] = {
{
.compatible = "amlogic,led_unipolar_ctrl",
},
{},
};
MODULE_DEVICE_TABLE(of, unipolar_ctrl_table);
static struct platform_driver meson_led_unipolar_ctrl = {
.probe = unipolar_ctrl_probe,
.remove = unipolar_ctrl_remove,
.driver = {
.name = "meson_led_unipolar_ctrl",
.of_match_table = unipolar_ctrl_table,
},
};
int __init led_unipolar_ctrl_init(void)
{
int ret;
ret = platform_driver_register(&meson_led_unipolar_ctrl);
return ret;
}
void __exit led_unipolar_ctrl_exit(void)
{
platform_driver_unregister(&meson_led_unipolar_ctrl);
}