From 5c101493f6f0139c4d7d14d619bc16ce66129fe7 Mon Sep 17 00:00:00 2001 From: wangzijiao Date: Wed, 5 Dec 2018 18:16:22 +0800 Subject: [PATCH] add new file for leds drive --- lichee/linux-4.9/drivers/leds/leds-sunxi.c | 1782 ++++++++++++++++++++ lichee/linux-4.9/drivers/leds/leds-sunxi.h | 161 ++ 2 files changed, 1943 insertions(+) create mode 100644 lichee/linux-4.9/drivers/leds/leds-sunxi.c create mode 100644 lichee/linux-4.9/drivers/leds/leds-sunxi.h diff --git a/lichee/linux-4.9/drivers/leds/leds-sunxi.c b/lichee/linux-4.9/drivers/leds/leds-sunxi.c new file mode 100644 index 000000000..8cfbc0d3e --- /dev/null +++ b/lichee/linux-4.9/drivers/leds/leds-sunxi.c @@ -0,0 +1,1782 @@ +/* + * drivers/leds/leds-sunxi.c - Allwinner RGB LED Driver + * + * Copyright (C) 2018 Allwinner Technology Limited. All rights reserved. + * Albert Yu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "leds-sunxi.h" + +static struct sunxi_led *sunxi_led; + +static void sunxi_ledc_trans_data(struct sunxi_led *led); +static void sunxi_ledc_set_trans_mode(struct sunxi_led *led, const char *mode); + +static void sunxi_clk_get(struct sunxi_led *led) +{ + struct device *dev = led->dev; + struct device_node *np = dev->of_node; + + led->clk_ledc = of_clk_get(np, 0); + if (IS_ERR(led->clk_ledc)) + dev_err(dev, "failed to get clk_ledc!\n"); + + led->clk_cpuapb = of_clk_get(np, 1); + if (IS_ERR(led->clk_cpuapb)) + dev_err(dev, "failed to get clk_cpuapb!\n"); +} + +static void sunxi_clk_put(struct sunxi_led *led) +{ + clk_put(led->clk_ledc); + clk_put(led->clk_cpuapb); +} + +static void sunxi_clk_enable(struct sunxi_led *led) +{ + clk_prepare_enable(led->clk_ledc); + clk_prepare_enable(led->clk_cpuapb); +} + +static void sunxi_clk_disable(struct sunxi_led *led) +{ + clk_disable_unprepare(led->clk_cpuapb); + clk_disable_unprepare(led->clk_ledc); +} + +static void sunxi_clk_init(struct sunxi_led *led) +{ + sunxi_clk_get(led); + sunxi_clk_enable(led); +} + +static void sunxi_clk_deinit(struct sunxi_led *led) +{ + sunxi_clk_disable(led); + sunxi_clk_put(led); +} + +static u32 sunxi_get_reg(int offset) +{ + struct sunxi_led *led = sunxi_led; + u32 value = ioread32(((u8 *)led->iomem_reg_base) + offset); + + return value; +} + +static void sunxi_set_reg(int offset, u32 value) +{ + struct sunxi_led *led = sunxi_led; + + iowrite32(value, ((u8 *)led->iomem_reg_base) + offset); +} + +static inline void sunxi_set_reset_ns(struct sunxi_led *led) +{ + u32 n, reg_val; + u32 mask = 0x1FFF; + u32 min = SUNXI_RESET_TIME_MIN_NS; + u32 max = SUNXI_RESET_TIME_MAX_NS; + + if (led->reset_ns < min || led->reset_ns > max) { + dev_err(led->dev, + "invalid parameter, reset_ns should be %d-%d!\n", + min, max); + goto out; + } + + n = (led->reset_ns - 42) / 42; + reg_val = sunxi_get_reg(LED_RESET_TIMING_CTRL_REG_OFFSET); + reg_val &= ~(mask << 16); + reg_val |= (n << 16); + sunxi_set_reg(LED_RESET_TIMING_CTRL_REG_OFFSET, reg_val); + +out: + reg_val = sunxi_get_reg(LED_RESET_TIMING_CTRL_REG_OFFSET); + n = (reg_val >> 16) & mask; + led->reset_ns = 42 * (n + 1); +} + +static inline void sunxi_set_t1h_ns(struct sunxi_led *led) +{ + u32 n, reg_val; + u32 mask = 0x3F; + u32 shift = 21; + u32 min = SUNXI_T1H_MIN_NS; + u32 max = SUNXI_T1H_MAX_NS; + + if (led->t1h_ns < min || led->t1h_ns > max) { + dev_err(led->dev, + "invalid parameter, t1h_ns should be %d-%d!\n", + min, max); + goto out; + } + + n = (led->t1h_ns - 42) / 42; + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + reg_val &= ~(mask << shift); + reg_val |= n << shift; + sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val); + +out: + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + n = (reg_val >> shift) & mask; + led->t1h_ns = 42 * (n + 1); +} + +static inline void sunxi_set_t1l_ns(struct sunxi_led *led) +{ + u32 n, reg_val; + u32 mask = 0x1F; + u32 shift = 16; + u32 min = SUNXI_T1L_MIN_NS; + u32 max = SUNXI_T1L_MAX_NS; + + if (led->t1l_ns < min || led->t1l_ns > max) { + dev_err(led->dev, + "invalid parameter, t1l_ns should be %d-%d!\n", + min, max); + goto out; + } + + n = (led->t1l_ns - 42) / 42; + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + reg_val &= ~(mask << shift); + reg_val |= n << shift; + sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val); + +out: + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + n = (reg_val >> shift) & mask; + led->t1l_ns = 42 * (n + 1); + +} + +static inline void sunxi_set_t0h_ns(struct sunxi_led *led) +{ + u32 n, reg_val; + u32 mask = 0x1F; + u32 shift = 6; + u32 min = SUNXI_T0H_MIN_NS; + u32 max = SUNXI_T0H_MAX_NS; + + if (led->t0h_ns < min || led->t0h_ns > max) { + dev_err(led->dev, + "invalid parameter, t0h_ns should be %d-%d!\n", + min, max); + goto out; + } + + n = (led->t0h_ns - 42) / 42; + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + reg_val &= ~(mask << shift); + reg_val |= n << shift; + sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val); + +out: + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + n = (reg_val >> shift) & mask; + led->t0h_ns = 42 * (n + 1); +} + +static inline void sunxi_set_t0l_ns(struct sunxi_led *led) +{ + u32 n, reg_val; + u32 mask = 0x3F; + u32 min = SUNXI_T0L_MIN_NS; + u32 max = SUNXI_T0L_MAX_NS; + + if (led->t0l_ns < min || led->t0l_ns > max) { + dev_err(led->dev, + "invalid parameter, t0l_ns should be %d-%d!\n", + min, max); + goto out; + } + + n = (led->t0l_ns - 42) / 42; + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + reg_val &= ~0x3F; + reg_val |= n; + sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val); + +out: + reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET); + n = reg_val & mask; + led->t0l_ns = 42 * (n + 1); +} + +static inline void sunxi_set_wait_time0_ns(struct sunxi_led *led) +{ + u32 n, reg_val; + u32 mask = 0xFF; + u32 min = SUNXI_WAIT_TIME0_MIN_NS; + u32 max = SUNXI_WAIT_TIME0_MAX_NS; + + if (led->wait_time0_ns < min || led->wait_time0_ns > max) { + dev_err(led->dev, + "invalid parameter, wait_time0_ns should be %d-%d!\n", + min, max); + goto out; + } + + n = (led->wait_time0_ns - 42) / 42; + reg_val = (1 << 8) | n; + sunxi_set_reg(LEDC_WAIT_TIME0_CTRL_REG, reg_val); + +out: + reg_val = sunxi_get_reg(LEDC_WAIT_TIME0_CTRL_REG); + n = reg_val & mask; + led->wait_time0_ns = 42 * (n + 1); +} + +static inline void sunxi_set_wait_time1_ns(struct sunxi_led *led) +{ + long long tmp; + u32 n, reg_val; + u32 mask = 0x7FFFFFFF; + u32 min = SUNXI_WAIT_TIME1_MIN_NS; + long long max = SUNXI_WAIT_TIME1_MAX_NS; + + if (led->wait_time1_ns < min || led->wait_time1_ns > max) { + dev_err(led->dev, + "invalid parameter, wait_time1_ns should be %u-%lld!\n", + min, max); + goto out; + } + + tmp = led->wait_time1_ns; + n = do_div(tmp, 42); + n = tmp - 1; + //n = (led->wait_time1_ns - 42) / 42; + reg_val = (1 << 31) | n; + sunxi_set_reg(LEDC_WAIT_TIME1_CTRL_REG, reg_val); + +out: + reg_val = sunxi_get_reg(LEDC_WAIT_TIME1_CTRL_REG); + n = reg_val & mask; + led->wait_time1_ns = 42 * (n + 1); +} + +static inline void sunxi_set_wait_data_time_ns(struct sunxi_led *led) +{ + u32 mask = 0x1FFF; + u32 shift = 16; + u32 reg_val = 0; + u32 n, min, max; + + min = SUNXI_WAIT_DATA_TIME_MIN_NS; +#ifdef SUNXI_FPGA_LEDC + /* + * For FPGA platforms, it is easy to meet wait data timeout for + * the obvious latency of task which is because of less cpu cores + * and lower cpu frequency compared with IC platforms, so here we + * permit long enough time latency. + */ + max = SUNXI_WAIT_DATA_TIME_MAX_NS_FPGA; +#else /* SUNXI_FPGA_LEDC */ + max = SUNXI_WAIT_DATA_TIME_MAX_NS_IC; +#endif /* SUNXI_FPGA_LEDC */ + + if (led->wait_data_time_ns < min || led->wait_data_time_ns > max) { + dev_err(led->dev, + "invalid parameter, wait_data_time_ns should be %d-%d!\n", + min, max); + goto out; + } + +#ifndef SUNXI_FPGA_LEDC + n = (led->wait_data_time_ns - 42) / 42; + reg_val &= ~(mask << shift); + reg_val |= (n << shift); + sunxi_set_reg(LEDC_DATA_FINISH_CNT_REG_OFFSET, reg_val); +#endif /* SUNXI_FPGA_LEDC */ + +out: +#ifdef SUNXI_FPGA_LEDC + if (led->wait_data_time_ns <= SUNXI_WAIT_DATA_TIME_MAX_NS_IC) +#endif /* SUNXI_FPGA_LEDC */ + { + reg_val = sunxi_get_reg(LEDC_DATA_FINISH_CNT_REG_OFFSET); + n = (reg_val >> shift) & mask; + led->wait_data_time_ns = 42 * (n + 1); + } +} + +static void sunxi_ledc_set_time(struct sunxi_led *led) +{ + sunxi_set_reset_ns(led); + sunxi_set_t1h_ns(led); + sunxi_set_t1l_ns(led); + sunxi_set_t0h_ns(led); + sunxi_set_t0l_ns(led); + sunxi_set_wait_time0_ns(led); + sunxi_set_wait_time1_ns(led); + sunxi_set_wait_data_time_ns(led); +} + +static void sunxi_ledc_set_length(struct sunxi_led *led) +{ + u32 reg_val; + u32 length = led->length; + + if (length == 0) + goto err_out; + + if (length > led->led_count) + goto err_out; + + reg_val = sunxi_get_reg(LEDC_CTRL_REG_OFFSET); + reg_val &= ~(0x1FF << 16); + reg_val |= length << 16; + sunxi_set_reg(LEDC_CTRL_REG_OFFSET, reg_val); + + reg_val = sunxi_get_reg(LED_RESET_TIMING_CTRL_REG_OFFSET); + reg_val &= ~0x3FF; + reg_val |= length - 1; + sunxi_set_reg(LED_RESET_TIMING_CTRL_REG_OFFSET, reg_val); + + return; + +err_out: + led->length = 0; +} + +static void sunxi_ledc_set_output_mode(struct sunxi_led *led, const char *str) +{ + u32 val; + u32 mask = 0x7; + u32 shift = 6; + u32 reg_val = sunxi_get_reg(LEDC_CTRL_REG_OFFSET); + + if (str != NULL) { + if (!strncmp(led->output_mode.str, str, 3)) + return; + + if (!strncmp(str, "GRB", 3)) + val = SUNXI_OUTPUT_GRB; + else if (!strncmp(str, "GBR", 3)) + val = SUNXI_OUTPUT_GBR; + else if (!strncmp(str, "RGB", 3)) + val = SUNXI_OUTPUT_RGB; + else if (!strncmp(str, "RBG", 3)) + val = SUNXI_OUTPUT_RBG; + else if (!strncmp(str, "BGR", 3)) + val = SUNXI_OUTPUT_BGR; + else if (!strncmp(str, "BRG", 3)) + val = SUNXI_OUTPUT_BRG; + else + return; + + memcpy(led->output_mode.str, str, 3); + } else { + val = led->output_mode.val; + } + + reg_val &= ~(mask << shift); + reg_val |= val; + + sunxi_set_reg(LEDC_CTRL_REG_OFFSET, reg_val); + + if (str) + memcpy(led->output_mode.str, str, 3); + + if (val != led->output_mode.val) + led->output_mode.val = val; +} + +static void sunxi_ledc_set_trans_mode(struct sunxi_led *led, const char *str) +{ + u32 val, reg_val; + + if (str != NULL) { + if (!strncmp(led->trans_mode.str, str, 3)) + return; + + if (!strncmp(str, "CPU", 3)) + val = LEDC_TRANS_CPU_MODE; + else if (!strncmp(str, "DMA", 3)) + val = LEDC_TRANS_DMA_MODE; + else + return; + + memcpy(led->trans_mode.str, str, 3); + } else { + val = led->trans_mode.val; + } + + reg_val = sunxi_get_reg(LEDC_DMA_CTRL_REG); + if (val == LEDC_TRANS_DMA_MODE) + reg_val |= 1 << 5; + else + reg_val &= ~(1 << 5); + reg_val &= ~0x1F; + reg_val |= SUNXI_LEDC_FIFO_TRIG_LEVEL; + sunxi_set_reg(LEDC_DMA_CTRL_REG, reg_val); + + reg_val = sunxi_get_reg(LEDC_INT_CTRL_REG_OFFSET); + if (val == LEDC_TRANS_DMA_MODE) + reg_val &= ~(1 << 1); + else + reg_val |= 1 << 1; + sunxi_set_reg(LEDC_INT_CTRL_REG_OFFSET, reg_val); + + if (val != led->trans_mode.val) + led->trans_mode.val = val; +} + +static bool sunxi_ledc_is_enabled(struct sunxi_led *led) +{ + u32 reg_val = sunxi_get_reg(LEDC_CTRL_REG_OFFSET); + + return reg_val & 1; +} + +static inline void sunxi_ledc_enable(struct sunxi_led *led) +{ + u32 reg_val; + + reg_val = sunxi_get_reg(LEDC_CTRL_REG_OFFSET); + reg_val |= 1; + sunxi_set_reg(LEDC_CTRL_REG_OFFSET, reg_val); +} + +static inline void sunxi_ledc_reset(struct sunxi_led *led) +{ + if (led->dma_chan) + dmaengine_terminate_all(led->dma_chan); + + led->transmitted_data = 0; + sunxi_set_reg(LEDC_CTRL_REG_OFFSET, 1 << 1); +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t reset_ns_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min, max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_RESET_TIME_MIN_NS; + max = SUNXI_RESET_TIME_MAX_NS; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + goto err_out; + + if (val < min || val > max) + goto err_out; + + led->reset_ns = val; + sunxi_set_reset_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, reset_ns should be %u-%u!\n", + min, max); + + return -EINVAL; +} + +static ssize_t reset_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%u\n", led->reset_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations reset_ns_fops = { + .owner = THIS_MODULE, + .write = reset_ns_write, + .read = reset_ns_read, +}; + +static ssize_t t1h_ns_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min, max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_T1H_MIN_NS; + max = SUNXI_T1H_MAX_NS; + + if (count >= sizeof(buffer)) + return -EINVAL; + + if (copy_from_user(buffer, buf, count)) + return -EFAULT; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + return -EINVAL; + + if (val < min || val > max) + goto err_out; + + led->t1h_ns = val; + + sunxi_set_t1h_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, t1h_ns should be %u-%u!\n", + min, max); + + return -EINVAL; +} + +static ssize_t t1h_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%u\n", led->t1h_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations t1h_ns_fops = { + .owner = THIS_MODULE, + .write = t1h_ns_write, + .read = t1h_ns_read, +}; + +static ssize_t t1l_ns_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min, max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_T1L_MIN_NS; + max = SUNXI_T1L_MAX_NS; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + goto err_out; + + if (val < min || val > max) + goto err_out; + + led->t1l_ns = val; + sunxi_set_t1l_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, t1l_ns should be %u-%u!\n", + min, max); + + return -EINVAL; +} + +static ssize_t t1l_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%u\n", led->t1l_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations t1l_ns_fops = { + .owner = THIS_MODULE, + .write = t1l_ns_write, + .read = t1l_ns_read, +}; + +static ssize_t t0h_ns_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min, max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_T0H_MIN_NS; + max = SUNXI_T0H_MAX_NS; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + goto err_out; + + if (val < min || val > max) + goto err_out; + + led->t0h_ns = val; + sunxi_set_t0h_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, t0h_ns should be %u-%u!\n", + min, max); + + return -EINVAL; +} + +static ssize_t t0h_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%u\n", led->t0h_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations t0h_ns_fops = { + .owner = THIS_MODULE, + .write = t0h_ns_write, + .read = t0h_ns_read, +}; + +static ssize_t t0l_ns_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min, max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_T0L_MIN_NS; + max = SUNXI_T0L_MAX_NS; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + goto err_out; + + if (val < min || val > max) + goto err_out; + + led->t0l_ns = val; + sunxi_set_t0l_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, t0l_ns should be %u-%u!\n", + min, max); + + return -EINVAL; +} + +static ssize_t t0l_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%u\n", led->t0l_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations t0l_ns_fops = { + .owner = THIS_MODULE, + .write = t0l_ns_write, + .read = t0l_ns_read, +}; + +static ssize_t wait_time0_ns_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min, max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_WAIT_TIME0_MIN_NS; + max = SUNXI_WAIT_TIME0_MAX_NS; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + goto err_out; + + if (val < min || val > max) + goto err_out; + + led->wait_time0_ns = val; + sunxi_set_wait_time0_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, wait_time0_ns should be %u-%u!\n", + min, max); + + return -EINVAL; +} + +static ssize_t wait_time0_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%u\n", led->wait_time0_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations wait_time0_ns_fops = { + .owner = THIS_MODULE, + .write = wait_time0_ns_write, + .read = wait_time0_ns_read, +}; + +static ssize_t wait_time1_ns_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min; + long long max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_WAIT_TIME1_MIN_NS; + max = SUNXI_WAIT_TIME1_MAX_NS; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + goto err_out; + + if (val < min || val > max) + goto err_out; + + led->wait_time1_ns = val; + sunxi_set_wait_time1_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, wait_time1_ns should be %u-%lld!\n", + min, max); + + return -EINVAL; +} + +static ssize_t wait_time1_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%lld\n", led->wait_time1_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations wait_time1_ns_fops = { + .owner = THIS_MODULE, + .write = wait_time1_ns_write, + .read = wait_time1_ns_read, +}; + +static ssize_t wait_data_time_ns_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *offp) +{ + int err; + char buffer[64]; + u32 min, max; + unsigned long val; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + min = SUNXI_WAIT_DATA_TIME_MIN_NS; +#ifdef SUNXI_FPGA_LEDC + max = SUNXI_WAIT_DATA_TIME_MAX_NS_FPGA; +#else + max = SUNXI_WAIT_DATA_TIME_MAX_NS_IC; +#endif + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + err = kstrtoul(buffer, 10, &val); + if (err) + goto err_out; + + if (val < min || val > max) + goto err_out; + + led->wait_data_time_ns = val; + sunxi_set_wait_data_time_ns(led); + + *offp += count; + + return count; + +err_out: + dev_err(dev, + "invalid parameter, wait_data_time_ns should be %u-%u!\n", + min, max); + + return -EINVAL; +} + +static ssize_t wait_data_time_ns_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%u\n", led->wait_data_time_ns); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations wait_data_time_ns_fops = { + .owner = THIS_MODULE, + .write = wait_data_time_ns_write, + .read = wait_data_time_ns_read, +}; + +static int data_show(struct seq_file *s, void *data) +{ + int i; + struct sunxi_led *led = sunxi_led; + + for (i = 0; i < led->led_count; i++) { + if (!(i % 4)) { + if (i + 4 <= led->led_count) + seq_printf(s, "%04d-%04d", i, i + 4); + else + seq_printf(s, "%04d-%04d", i, led->led_count); + } + seq_printf(s, " 0x%08x", led->data[i]); + if (((i % 4) == 3) || (i == led->led_count - 1)) + seq_puts(s, "\n"); + } + + return 0; +} + +static int data_open(struct inode *inode, struct file *file) +{ + return single_open(file, data_show, inode->i_private); +} + +static const struct file_operations data_fops = { + .owner = THIS_MODULE, + .open = data_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static ssize_t output_mode_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + char buffer[64]; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + sunxi_ledc_set_output_mode(led, buffer); + + *offp += count; + + return count; + +err_out: + dev_err(dev, "invalid parameter!\n"); + + return -EINVAL; +} + +static ssize_t output_mode_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%s\n", led->output_mode.str); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations output_mode_fops = { + .owner = THIS_MODULE, + .write = output_mode_write, + .read = output_mode_read, +}; + +static ssize_t trans_mode_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offp) +{ + char buffer[64]; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + + if (count >= sizeof(buffer)) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + goto err_out; + + buffer[count] = '\0'; + + sunxi_ledc_set_trans_mode(led, buffer); + + *offp += count; + + return count; + +err_out: + dev_err(dev, "invalid parameter!\n"); + + return -EINVAL; +} + + +static ssize_t trans_mode_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + struct sunxi_led *led = sunxi_led; + + r = snprintf(buffer, 64, "%s\n", led->trans_mode.str); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations trans_mode_fops = { + .owner = THIS_MODULE, + .write = trans_mode_write, + .read = trans_mode_read, +}; + +static ssize_t hwversion_read(struct file *filp, char __user *buf, + size_t count, loff_t *offp) +{ + int r; + char buffer[64]; + u32 reg_val, major_ver, minor_ver; + + reg_val = sunxi_get_reg(LEDC_VER_NUM_REG); + major_ver = reg_val >> 16; + minor_ver = reg_val & 0xF; + + r = snprintf(buffer, 64, "r%up%u\n", major_ver, minor_ver); + + return simple_read_from_buffer(buf, count, offp, buffer, r); +} + +static const struct file_operations hwversion_fops = { + .owner = THIS_MODULE, + .read = hwversion_read, +}; + +static void sunxi_led_create_debugfs(struct sunxi_led *led) +{ + struct dentry *debugfs_dir, *debugfs_file; + struct device *dev = led->dev; + + debugfs_dir = debugfs_create_dir("sunxi_leds", NULL); + if (IS_ERR_OR_NULL(debugfs_dir)) { + dev_err(dev, "debugfs_create_dir failed!\n"); + return; + } + + debugfs_file = debugfs_create_file("reset_ns", 0660, + debugfs_dir, NULL, &reset_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for reset_ns failed!\n"); + + debugfs_file = debugfs_create_file("t1h_ns", 0660, + debugfs_dir, NULL, &t1h_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for t1h_ns failed!\n"); + + debugfs_file = debugfs_create_file("t1l_ns", 0660, + debugfs_dir, NULL, &t1l_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for t1l_ns failed!\n"); + + debugfs_file = debugfs_create_file("t0h_ns", 0660, + debugfs_dir, NULL, &t0h_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for t0h_ns failed!\n"); + + debugfs_file = debugfs_create_file("t0l_ns", 0660, + debugfs_dir, NULL, &t0l_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for t0l_ns failed!\n"); + + debugfs_file = debugfs_create_file("wait_time0_ns", 0660, + debugfs_dir, NULL, &wait_time0_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for wait_time0_ns failed!\n"); + + debugfs_file = debugfs_create_file("wait_time1_ns", 0660, + debugfs_dir, NULL, &wait_time1_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for wait_time1_ns failed!\n"); + + debugfs_file = debugfs_create_file("wait_data_time_ns", 0660, + debugfs_dir, NULL, &wait_data_time_ns_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for wait_data_time_ns failed!\n"); + + debugfs_file = debugfs_create_file("data", 0440, + debugfs_dir, NULL, &data_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for data failed!\n"); + + debugfs_file = debugfs_create_file("output_mode", 0660, + debugfs_dir, NULL, &output_mode_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for output_mode failed!\n"); + + debugfs_file = debugfs_create_file("trans_mode", 0660, + debugfs_dir, NULL, &trans_mode_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for trans_mode failed!\n"); + + debugfs_file = debugfs_create_file("hwversion", 0440, + debugfs_dir, NULL, &hwversion_fops); + if (!debugfs_file) + dev_err(dev, "debugfs_create_file for hwversion failed!\n"); +} +#endif /* CONFIG_DEBUG_FS */ + +static void sunxi_ledc_dma_callback(void *param) +{ + struct sunxi_led *led = sunxi_led; + + dev_dbg(led->dev, "sunxi_ledc_dma_callback finish\n"); +} + +static void sunxi_ledc_trans_data(struct sunxi_led *led) +{ + int i, err; + size_t size; + u32 sub_length, delta_length; + unsigned int slave_id; + unsigned long flags; + phys_addr_t dst_addr; + struct dma_slave_config slave_config; + struct device *dev = led->dev; + struct dma_async_tx_descriptor *dma_desc; + + if (led->transmitted_data >= led->length) + return; + + delta_length = led->length - led->transmitted_data; + if (delta_length > SUNXI_LEDC_FIFO_TRIG_LEVEL) + sub_length = SUNXI_LEDC_FIFO_TRIG_LEVEL; + else + sub_length = delta_length; + + switch (led->trans_mode.val) { + case LEDC_TRANS_CPU_MODE: + for (i = 0; i < sub_length; i++) { + sunxi_set_reg(LEDC_DATA_REG_OFFSET, + led->data[led->transmitted_data]); + led->transmitted_data++; + } + + break; + + case LEDC_TRANS_DMA_MODE: + size = led->length * 4; + led->src_dma = dma_map_single(dev, led->data, + size, DMA_TO_DEVICE); + + dst_addr = SUNXI_LEDC_REG_BASE_ADDR + LEDC_DATA_REG_OFFSET; + slave_id = sunxi_slave_id(DRQDST_LEDC, DRQSRC_SDRAM); + flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK; + + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.src_addr = led->src_dma; + slave_config.dst_addr = dst_addr; + slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave_config.src_maxburst = 1; + slave_config.dst_maxburst = 1; + slave_config.slave_id = slave_id; + err = dmaengine_slave_config(led->dma_chan, &slave_config); + if (err < 0) { + dev_err(dev, "dmaengine_slave_config failed!\n"); + dma_unmap_single(dev, led->src_dma, + size, DMA_TO_DEVICE); + return; + } + + dma_desc = dmaengine_prep_slave_single(led->dma_chan, + led->src_dma, + size, + DMA_MEM_TO_DEV, + flags); + if (!dma_desc) { + dev_err(dev, "dmaengine_prep_slave_single failed!\n"); + dma_unmap_single(dev, led->src_dma, + size, DMA_TO_DEVICE); + return; + } + + dma_desc->callback = sunxi_ledc_dma_callback; + + dmaengine_submit(dma_desc); + dma_async_issue_pending(led->dma_chan); + + break; + } + + if (!sunxi_ledc_is_enabled(led)) { + sunxi_ledc_set_length(led); + sunxi_ledc_enable(led); + } +} + +static inline void sunxi_ledc_clear_all_irq(void) +{ + u32 reg_val = sunxi_get_reg(LEDC_INT_STS_REG_OFFSET); + + reg_val &= ~0x1F; + sunxi_set_reg(LEDC_INT_STS_REG_OFFSET, reg_val); +} + +static inline void sunxi_ledc_clear_irq(enum sunxi_ledc_irq_status_reg irq) +{ + u32 reg_val = sunxi_get_reg(LEDC_INT_STS_REG_OFFSET); + + reg_val &= ~irq; + sunxi_set_reg(LEDC_INT_STS_REG_OFFSET, reg_val); +} + +static irqreturn_t sunxi_ledc_irq_handler(int irq, void *dev_id) +{ + unsigned long flags; + long delta_time_ns; + u32 irq_status, max_ns; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + struct timespec64 current_time; + + spin_lock_irqsave(&led->lock, flags); + + irq_status = sunxi_get_reg(LEDC_INT_STS_REG_OFFSET); + + sunxi_ledc_clear_all_irq(); + + if (irq_status & LEDC_TRANS_FINISH_INT) { + if (led->dma_chan) + dma_unmap_single(dev, + led->src_dma, led->length * 4, + DMA_TO_DEVICE); + sunxi_ledc_reset(led); + led->length = 0; + goto out; + } + + if (irq_status & LEDC_WAITDATA_TIMEOUT_INT) { + current_time = current_kernel_time64(); + delta_time_ns = current_time.tv_sec - led->start_time.tv_sec; + delta_time_ns *= 1000 * 1000 * 1000; + delta_time_ns += current_time.tv_nsec - led->start_time.tv_nsec; + + max_ns = led->wait_data_time_ns; + + if (delta_time_ns <= max_ns) { + spin_unlock_irqrestore(&led->lock, flags); + return IRQ_HANDLED; + } + + if (led->dma_chan) + dmaengine_terminate_all(led->dma_chan); + + sunxi_ledc_reset(led); + + if (delta_time_ns <= max_ns * 2) { + sunxi_ledc_trans_data(led); + } else { + dev_err(dev, + "wait time is more than %d ns, going to reset ledc and drop this operation!\n", + max_ns); + led->length = 0; + } + + goto out; + } + + if (irq_status & LEDC_FIFO_OVERFLOW_INT) { + dev_err(dev, + "there exists fifo overflow issue, irq_status=0x%x!\n", + irq_status); + + sunxi_ledc_reset(led); + sunxi_ledc_trans_data(led); + + goto out; + } + + if (irq_status & LEDC_FIFO_CPUREQ_INT) { + if (led->trans_mode.val == LEDC_TRANS_CPU_MODE + && led->transmitted_data <= led->length) + sunxi_ledc_trans_data(led); + } + +out: + spin_unlock_irqrestore(&led->lock, flags); + + return IRQ_HANDLED; +} + +static int sunxi_ledc_irq_init(struct sunxi_led *led) +{ + int err; + u32 reg_val = 0; + struct device *dev = led->dev; + unsigned long flags = 0; + const char *name = "ledcirq"; + struct platform_device *pdev; + + pdev = container_of(dev, struct platform_device, dev); + + spin_lock_init(&led->lock); + + led->irqnum = platform_get_irq(pdev, 0); + if (led->irqnum < 0) + dev_err(dev, "failed to get ledc irq!\n"); + + err = request_irq(led->irqnum, sunxi_ledc_irq_handler, + flags, name, dev); + if (err) { + dev_err(dev, + "failed to install IRQ handler for irqnum %d\n", + led->irqnum); + return -EPERM; + } + + reg_val = sunxi_get_reg(LEDC_INT_CTRL_REG_OFFSET); + reg_val |= LEDC_GLOBAL_INT_EN; + reg_val |= LEDC_FIFO_OVERFLOW_INT_EN; + reg_val |= LEDC_WAITDATA_TIMEOUT_INT_EN; + if (led->trans_mode.val == LEDC_TRANS_CPU_MODE) + reg_val |= LEDC_FIFO_CPUREQ_INT_EN; + reg_val |= LEDC_TRANS_FINISH_INT_EN; + + sunxi_set_reg(LEDC_INT_CTRL_REG_OFFSET, reg_val); + + return 0; +} + +static void sunxi_ledc_irq_deinit(struct sunxi_led *led) +{ + u32 reg_val; + + free_irq(led->irqnum, led->dev); + + reg_val = sunxi_get_reg(LEDC_INT_CTRL_REG_OFFSET); + reg_val &= ~LEDC_TRANS_FINISH_INT_EN; + reg_val &= ~LEDC_FIFO_CPUREQ_INT_EN; + reg_val &= ~LEDC_WAITDATA_TIMEOUT_INT_EN; + reg_val &= ~LEDC_FIFO_OVERFLOW_INT_EN; + reg_val &= ~LEDC_GLOBAL_INT_EN; + sunxi_set_reg(LEDC_INT_CTRL_REG_OFFSET, reg_val); +} + +static void sunxi_ledc_pinctrl_init(struct sunxi_led *led) +{ + struct device *dev = led->dev; + struct pinctrl *pinctrl = devm_pinctrl_get_select_default(dev); + + if (IS_ERR(pinctrl)) + dev_warn(dev, "devm_pinctrl_get_select_default failed!\n"); +} + +static int sunxi_set_led_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + unsigned long flags; + u32 r, g, b, shift, old_data, new_data, length; + struct sunxi_led_info *pinfo; + struct sunxi_led_classdev_group *pcdev_group; + struct sunxi_led *led = sunxi_led; + + pinfo = container_of(led_cdev, struct sunxi_led_info, cdev); + + switch (pinfo->type) { + case LED_TYPE_R: + pcdev_group = container_of(pinfo, + struct sunxi_led_classdev_group, r); + r = value; + shift = 8; + break; + case LED_TYPE_G: + pcdev_group = container_of(pinfo, + struct sunxi_led_classdev_group, g); + g = value; + shift = 16; + break; + case LED_TYPE_B: + pcdev_group = container_of(pinfo, + struct sunxi_led_classdev_group, b); + b = value; + shift = 0; + break; + } + + old_data = led->data[pcdev_group->led_num]; + if (((old_data >> shift) & 0xFF) == value) + return 0; + + if (pinfo->type != LED_TYPE_R) + r = pcdev_group->r.cdev.brightness; + if (pinfo->type != LED_TYPE_G) + g = pcdev_group->g.cdev.brightness; + if (pinfo->type != LED_TYPE_B) + b = pcdev_group->b.cdev.brightness; + + /* LEDC treats input data as GRB by default */ + new_data = (g << 16) | (r << 8) | b; + + length = pcdev_group->led_num + 1; + + spin_lock_irqsave(&led->lock, flags); + + led->data[pcdev_group->led_num] = new_data; + + led->length = length; + + led->start_time = current_kernel_time64(); + + sunxi_ledc_trans_data(led); + + spin_unlock_irqrestore(&led->lock, flags); + + return 0; +} + +static int sunxi_register_led_classdev(struct sunxi_led *led) +{ + int i, err; + size_t size; + struct device *dev = led->dev; + struct led_classdev *pcdev; + + if (!led->led_count) + led->led_count = SUNXI_DEFAULT_LED_COUNT; + + size = sizeof(struct sunxi_led_classdev_group) * led->led_count; + led->pcdev_group = kzalloc(size, GFP_KERNEL); + if (!led->pcdev_group) + return -ENOMEM; + + for (i = 0; i < led->led_count; i++) { + led->pcdev_group[i].r.type = LED_TYPE_R; + pcdev = &led->pcdev_group[i].r.cdev; + pcdev->name = kzalloc(16, GFP_KERNEL); + sprintf((char *)pcdev->name, "sunxi_led%dr", i); + pcdev->brightness = LED_OFF; + pcdev->brightness_set_blocking = sunxi_set_led_brightness; + pcdev->dev = dev; + err = led_classdev_register(dev, pcdev); + if (err < 0) { + dev_err(dev, + "led_classdev_register %s failed!\n", + pcdev->name); + return err; + } + + led->pcdev_group[i].g.type = LED_TYPE_G; + pcdev = &led->pcdev_group[i].g.cdev; + pcdev->name = kzalloc(16, GFP_KERNEL); + sprintf((char *)pcdev->name, "sunxi_led%dg", i); + pcdev->brightness = LED_OFF; + pcdev->brightness_set_blocking = sunxi_set_led_brightness; + pcdev->dev = dev; + err = led_classdev_register(dev, pcdev); + if (err < 0) { + dev_err(dev, + "led_classdev_register %s failed!\n", + pcdev->name); + return err; + } + + led->pcdev_group[i].b.type = LED_TYPE_B; + pcdev = &led->pcdev_group[i].b.cdev; + pcdev->name = kzalloc(16, GFP_KERNEL); + sprintf((char *)pcdev->name, "sunxi_led%db", i); + pcdev->brightness = LED_OFF; + pcdev->brightness_set_blocking = sunxi_set_led_brightness; + pcdev->dev = dev; + err = led_classdev_register(dev, pcdev); + if (err < 0) { + dev_err(dev, + "led_classdev_register %s failed!\n", + pcdev->name); + return err; + } + + led->pcdev_group[i].led_num = i; + } + + size = sizeof(u32) * led->led_count; + led->data = kzalloc(size, GFP_KERNEL); + if (!led->data) + return -ENOMEM; + + return 0; +} + +static void sunxi_unregister_led_classdev(struct sunxi_led *led) +{ + int i; + + for (i = 0; i < led->led_count; i++) { + kfree(led->pcdev_group[i].r.cdev.name); + kfree(led->pcdev_group[i].g.cdev.name); + kfree(led->pcdev_group[i].b.cdev.name); + led_classdev_unregister(&led->pcdev_group[i].r.cdev); + led_classdev_unregister(&led->pcdev_group[i].g.cdev); + led_classdev_unregister(&led->pcdev_group[i].b.cdev); + } + + kfree(led->pcdev_group); + kfree(led->data); +} + +static inline int sunxi_get_u32_of_property(const char *propname, int *val) +{ + int err; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + struct device_node *np = dev->of_node; + + err = of_property_read_u32(np, propname, val); + if (err < 0) + dev_warn(dev, + "failed to get the value of propname %s!\n", + propname); + + return err; +} + +static inline int sunxi_get_str_of_property(const char *propname, + const char **out_string) +{ + int err; + struct sunxi_led *led = sunxi_led; + struct device *dev = led->dev; + struct device_node *np = dev->of_node; + + err = of_property_read_string(np, propname, out_string); + if (err < 0) + dev_warn(dev, + "failed to get the string of propname %s!\n", + propname); + + return err; +} + +static void sunxi_get_para_of_property(struct sunxi_led *led) +{ + int err; + u32 val; + const char *str; + + err = sunxi_get_u32_of_property("led_count", &val); + if (!err) + led->led_count = val; + + memcpy(led->output_mode.str, "GRB", 3); + led->output_mode.val = SUNXI_OUTPUT_GRB; + err = sunxi_get_str_of_property("output_mode", &str); + if (!err) + if (!strncmp(str, "BRG", 3) || + !strncmp(str, "GBR", 3) || + !strncmp(str, "RGB", 3) || + !strncmp(str, "RBG", 3) || + !strncmp(str, "BGR", 3) || + !strncmp(str, "BRG", 3)) + memcpy(led->output_mode.str, str, 3); + + memcpy(led->trans_mode.str, "DMA", 3); + led->trans_mode.val = LEDC_TRANS_DMA_MODE; + err = sunxi_get_str_of_property("trans_mode", &str); + if (!err) + if (!strncmp(str, "CPU", 3) || !strncmp(str, "DMA", 3)) + memcpy(led->trans_mode.str, str, 3); + + err = sunxi_get_u32_of_property("reset_ns", &val); + if (!err) + led->reset_ns = val; + + err = sunxi_get_u32_of_property("t1h_ns", &val); + if (!err) + led->t1h_ns = val; + + err = sunxi_get_u32_of_property("t1l_ns", &val); + if (!err) + led->t1l_ns = val; + + err = sunxi_get_u32_of_property("t0h_ns", &val); + if (!err) + led->t0h_ns = val; + + err = sunxi_get_u32_of_property("t0l_ns", &val); + if (!err) + led->t0l_ns = val; + + err = sunxi_get_u32_of_property("wait_time0_ns", &val); + if (!err) + led->wait_time0_ns = val; + + err = sunxi_get_u32_of_property("wait_time1_ns", &val); + if (!err) + led->wait_time1_ns = val; + + err = sunxi_get_u32_of_property("wait_data_time_ns", &val); + if (!err) + led->wait_data_time_ns = val; +} + +static int sunxi_led_probe(struct platform_device *pdev) +{ + int err; + dma_cap_mask_t mask; + struct sunxi_led *led; + struct device *dev = &pdev->dev; + + led = kzalloc(sizeof(struct sunxi_led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + sunxi_led = led; + + led->dev = dev; + + led->output_mode.str = kzalloc(3, GFP_KERNEL); + if (!led->output_mode.str) + return -ENOMEM; + + led->trans_mode.str = kzalloc(3, GFP_KERNEL); + if (!led->trans_mode.str) + return -ENOMEM; + + sunxi_get_para_of_property(led); + + err = sunxi_register_led_classdev(led); + if (err) + return err; + + /* Registers initialization */ + led->iomem_reg_base = ioremap(SUNXI_LEDC_REG_BASE_ADDR, + LEDC_TOTAL_REG_SIZE); + sunxi_ledc_set_time(led); + sunxi_ledc_set_trans_mode(led, NULL); + sunxi_ledc_set_output_mode(led, NULL); + + sunxi_clk_init(led); + + err = sunxi_ledc_irq_init(led); + if (err) + return err; + + sunxi_ledc_pinctrl_init(led); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + led->dma_chan = dma_request_channel(mask, NULL, NULL); + if (!led->dma_chan) { + dev_err(dev, "failed to get the DMA channel!\n"); + return 0; + } + +#ifdef CONFIG_DEBUG_FS + sunxi_led_create_debugfs(led); +#endif /* CONFIG_DEBUG_FS */ + + return 0; +} + +static int sunxi_led_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sunxi_led *led = dev_get_drvdata(dev); + + sunxi_ledc_irq_deinit(led); + + sunxi_unregister_led_classdev(led); + + iounmap(led->iomem_reg_base); + led->iomem_reg_base = NULL; + + sunxi_clk_deinit(led); + + if (led->dma_chan) + dma_release_channel(led->dma_chan); + + kfree(led->output_mode.str); + kfree(led->trans_mode.str); + kfree(led); + + return 0; +} + +static const struct of_device_id sunxi_led_dt_ids[] = { + {.compatible = "allwinner,sunxi-leds"}, + {}, +}; + +static struct platform_driver sunxi_led_driver = { + .probe = sunxi_led_probe, + .remove = sunxi_led_remove, + .driver = { + .name = "sunxi-leds", + .of_match_table = sunxi_led_dt_ids, + }, +}; + +module_platform_driver(sunxi_led_driver); + +MODULE_AUTHOR("Albert Yu "); +MODULE_DESCRIPTION("Allwinner LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/lichee/linux-4.9/drivers/leds/leds-sunxi.h b/lichee/linux-4.9/drivers/leds/leds-sunxi.h new file mode 100644 index 000000000..b78c9e660 --- /dev/null +++ b/lichee/linux-4.9/drivers/leds/leds-sunxi.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 Allwinner Technology Limited. All rights reserved. + * Albert Yu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#ifndef __LINUX_LEDS_SUNXI_H +#define __LINUX_LEDS_SUNXI_H + +#include +#include +#include +#include +#include +#include +#include + +#define SUNXI_LEDC_REG_BASE_ADDR 0x06700000 + +#define SUNXI_MAX_LED_COUNT 1024 + +#define SUNXI_DEFAULT_LED_COUNT 8 + +#define SUNXI_RESET_TIME_MIN_NS 42 +#define SUNXI_RESET_TIME_MAX_NS 327000 + +#define SUNXI_T1H_MIN_NS 42 +#define SUNXI_T1H_MAX_NS 2560 + +#define SUNXI_T1L_MIN_NS 42 +#define SUNXI_T1L_MAX_NS 1280 + +#define SUNXI_T0H_MIN_NS 42 +#define SUNXI_T0H_MAX_NS 1280 + +#define SUNXI_T0L_MIN_NS 42 +#define SUNXI_T0L_MAX_NS 2560 + +#define SUNXI_WAIT_TIME0_MIN_NS 42 +#define SUNXI_WAIT_TIME0_MAX_NS 10000 + +#define SUNXI_WAIT_TIME1_MIN_NS 42 +#define SUNXI_WAIT_TIME1_MAX_NS 85000000000 + +#define SUNXI_WAIT_DATA_TIME_MIN_NS 42 +#define SUNXI_WAIT_DATA_TIME_MAX_NS_IC 655000 +#define SUNXI_WAIT_DATA_TIME_MAX_NS_FPGA 20000000 + +#define SUNXI_LEDC_FIFO_DEPTH 32 /* 32 * 4 bytes */ +#define SUNXI_LEDC_FIFO_TRIG_LEVEL 15 + +#if defined(CONFIG_FPGA_V4_PLATFORM) || defined(CONFIG_FPGA_V7_PLATFORM) +#define SUNXI_FPGA_LEDC +#endif + +enum sunxi_ledc_output_mode_val { + SUNXI_OUTPUT_GRB = 0 << 6, + SUNXI_OUTPUT_GBR = 1 << 6, + SUNXI_OUTPUT_RGB = 2 << 6, + SUNXI_OUTPUT_RBG = 3 << 6, + SUNXI_OUTPUT_BGR = 4 << 6, + SUNXI_OUTPUT_BRG = 5 << 6 +}; + +struct sunxi_ledc_output_mode { + char *str; + enum sunxi_ledc_output_mode_val val; +}; + +enum sunxi_ledc_trans_mode_val { + LEDC_TRANS_CPU_MODE, + LEDC_TRANS_DMA_MODE +}; + +struct sunxi_ledc_trans_mode { + char *str; + enum sunxi_ledc_trans_mode_val val; +}; + +enum sunxi_ledc_reg { + LEDC_CTRL_REG_OFFSET = 0x00, + LED_T01_TIMING_CTRL_REG_OFFSET = 0x04, + LEDC_DATA_FINISH_CNT_REG_OFFSET = 0x08, + LED_RESET_TIMING_CTRL_REG_OFFSET = 0x0c, + LEDC_WAIT_TIME0_CTRL_REG = 0x10, + LEDC_DATA_REG_OFFSET = 0x14, + LEDC_DMA_CTRL_REG = 0x18, + LEDC_INT_CTRL_REG_OFFSET = 0x1c, + LEDC_INT_STS_REG_OFFSET = 0x20, + LEDC_WAIT_TIME1_CTRL_REG = 0x28, + LEDC_VER_NUM_REG = 0x2c, + LEDC_FIFO_DATA = 0x30, + LEDC_TOTAL_REG_SIZE = LEDC_FIFO_DATA + SUNXI_LEDC_FIFO_DEPTH +}; + +enum sunxi_ledc_irq_ctrl_reg { + LEDC_TRANS_FINISH_INT_EN = (1 << 0), + LEDC_FIFO_CPUREQ_INT_EN = (1 << 1), + LEDC_WAITDATA_TIMEOUT_INT_EN = (1 << 3), + LEDC_FIFO_OVERFLOW_INT_EN = (1 << 4), + LEDC_GLOBAL_INT_EN = (1 << 5), +}; + +enum sunxi_ledc_irq_status_reg { + LEDC_TRANS_FINISH_INT = (1 << 0), + LEDC_FIFO_CPUREQ_INT = (1 << 1), + LEDC_WAITDATA_TIMEOUT_INT = (1 << 3), + LEDC_FIFO_OVERFLOW_INT = (1 << 4), + LEDC_FIFO_FULL = (1 << 16), + LEDC_FIFO_EMPTY = (1 << 17), +}; + +enum sunxi_led_type { + LED_TYPE_R, + LED_TYPE_G, + LED_TYPE_B +}; + +struct sunxi_led_info { + enum sunxi_led_type type; + struct led_classdev cdev; +}; + +struct sunxi_led_classdev_group { + u32 led_num; + struct sunxi_led_info r; + struct sunxi_led_info g; + struct sunxi_led_info b; +}; + +struct sunxi_led { + u32 reset_ns; + u32 t1h_ns; + u32 t1l_ns; + u32 t0h_ns; + u32 t0l_ns; + u32 wait_time0_ns; + long long wait_time1_ns; + u32 wait_data_time_ns; + u32 irqnum; + u32 led_count; + u32 *data; + u32 length; + u32 transmitted_data; + spinlock_t lock; + struct device *dev; + dma_addr_t src_dma; + struct dma_chan *dma_chan; + struct timespec64 start_time; + struct clk *clk_ledc; + struct clk *clk_cpuapb; + void __iomem *iomem_reg_base; + struct sunxi_ledc_output_mode output_mode; + struct sunxi_ledc_trans_mode trans_mode; + struct sunxi_led_classdev_group *pcdev_group; +}; + +#endif /* __LINUX_LEDS_SUNXI_H */