<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[D1s ledc驱动代码bug，DMA模式无法使用]]></title><description><![CDATA[<p dir="auto">在leds-sunxi.c里面这块是选择发射方式的<br />
如果小于32个灯那么就用fifo<br />
超过32个的就用DMA<br />
但是我在控制超过58个灯的过程中发现，这个驱动超过32个，系统就死机了<br />
我一开始认为是我代码的问题<br />
后来我改了这个驱动的条件直接全部用DMA模式发送<br />
发现直接就死机了<br />
如果修改了那个条件，把SUNXI_LEDC_FIFO_DEPTH加大到64也是不行的</p>
<pre><code>if (led-&gt;length &lt;= SUNXI_LEDC_FIFO_DEPTH) {
</code></pre>
<p dir="auto">在超过32个灯的时候会报错<br />
sunxi_ledc_irq_handler()1279 - there exists fifo overflow issue, irq_status=0x818010!</p>
<p dir="auto">现在看来问题应该还是出在了这段DMA模式下控制ledc驱动器发送数据的代码上了，为啥会死机呢？</p>
<pre><code>if (0) {

		dprintk(DEBUG_INFO, "cpu xfer\n");
		ktime_get_coarse_real_ts64(&amp;(led-&gt;start_time));
		sunxi_ledc_set_time(led);
		sunxi_ledc_set_output_mode(led, led-&gt;output_mode.str);
		sunxi_ledc_set_cpu_mode(led);
		sunxi_ledc_set_length(led);

		sunxi_ledc_enable_irq(LEDC_TRANS_FINISH_INT_EN | LEDC_WAITDATA_TIMEOUT_INT_EN
				| LEDC_FIFO_OVERFLOW_INT_EN | LEDC_GLOBAL_INT_EN);

		sunxi_ledc_enable(led);

		for (i = 0; i &lt; led-&gt;length; i++)
			sunxi_set_reg(LEDC_DATA_REG_OFFSET, led-&gt;data[i]);

	} else {
		dprintk(DEBUG_INFO, "dma xfer\n");

		size = led-&gt;length * 4;
		led-&gt;src_dma = dma_map_single(dev, led-&gt;data,
					size, DMA_TO_DEVICE);
		dst_addr = led-&gt;res-&gt;start + LEDC_DATA_REG_OFFSET;

		flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;

		slave_config.direction = DMA_MEM_TO_DEV;
		slave_config.src_addr = led-&gt;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 = 4;
		slave_config.dst_maxburst = 4;

		err = dmaengine_slave_config(led-&gt;dma_chan, &amp;slave_config);
		if (err &lt; 0) {
			LED_ERR("dmaengine_slave_config failed!\n");
			return;
		}

		dma_desc = dmaengine_prep_slave_single(led-&gt;dma_chan,
							led-&gt;src_dma,
							size,
							DMA_MEM_TO_DEV,
							flags);
		if (!dma_desc) {
			LED_ERR("dmaengine_prep_slave_single failed!\n");
			return;
		}

		dma_desc-&gt;callback = sunxi_ledc_dma_callback;

		dmaengine_submit(dma_desc);
		dma_async_issue_pending(led-&gt;dma_chan);

		ktime_get_coarse_real_ts64(&amp;(led-&gt;start_time));
		sunxi_ledc_set_time(led);
		sunxi_ledc_set_output_mode(led, led-&gt;output_mode.str);
		sunxi_ledc_set_dma_mode(led);
		sunxi_ledc_set_length(led);
		sunxi_ledc_enable_irq(LEDC_TRANS_FINISH_INT_EN | LEDC_WAITDATA_TIMEOUT_INT_EN
				| LEDC_FIFO_OVERFLOW_INT_EN | LEDC_GLOBAL_INT_EN);
		sunxi_ledc_enable(led);
	}
</code></pre>
]]></description><link>https://bbs.aw-ol.com/topic/5071/d1s-ledc驱动代码bug-dma模式无法使用</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 12:41:13 GMT</lastBuildDate><atom:link href="https://bbs.aw-ol.com/topic/5071.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 22 Feb 2024 17:24:23 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to D1s ledc驱动代码bug，DMA模式无法使用 on Tue, 27 Feb 2024 18:07:52 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://bbs.aw-ol.com/uid/1915">@awwwwa</a> 终于破案了，那个dts里面ledc的配置有个数据被我改过 wait_data_time_ns 这玩意被我改得很小，我那会测试melis的时候32个灯数据，需要刷很快，所以这个数值就不能大，一旦了，数据就错，灯乱闪，我本以为这个数值不影响DMA，但是有影响</p>
<p dir="auto">我改回到原来那个600000 就一切正常了，DMA能用了，如果这个数值设置太小就完蛋了，只要开启了DMA，系统就直接卡死！那个ledc的手册里面肯定没写全功能！</p>
<p dir="auto">手册里面写了这么句<br />
wait_data_time_ns：通过该节点可设置和读取 LEDC 内部 FIFO 等待数据的时间容忍度，<br />
范围为 80ns-655us。</p>
<p dir="auto">我改坏掉的</p>
<pre><code>&amp;ledc {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = &lt;&amp;ledc_pins_a&gt;;
	pinctrl-1 = &lt;&amp;ledc_pins_b&gt;;
	led_count = &lt;58&gt;;
	output_mode = "GRB";
	//trans_mode  = "DMA";

	dmas = &lt;&amp;dma 42&gt;, &lt;&amp;dma 42&gt;;
	dma-names = "tx", "rx";
	
	reset_ns = &lt;84&gt;;
	t1h_ns = &lt;800&gt;;
	t1l_ns = &lt;320&gt;;
	t0h_ns = &lt;300&gt;;
	t0l_ns = &lt;800&gt;;
	
	wait_time0_ns = &lt;84&gt;;
	wait_time1_ns = &lt;84&gt;;
	wait_data_time_ns = &lt;3000&gt;;
	status	= "okay";
};
</code></pre>
<p dir="auto">就是那个 wait_data_time_ns 这个数值在我测试melis的时候被我改过，我以为仅仅是fifo会影响到，但是DMA模式下应该这个参数也有影响</p>
<p dir="auto">改回来</p>
<pre><code>&amp;ledc {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = &lt;&amp;ledc_pins_a&gt;;
	pinctrl-1 = &lt;&amp;ledc_pins_b&gt;;
	led_count = &lt;58&gt;;
	output_mode = "GRB";
	//trans_mode  = "DMA";

	dmas = &lt;&amp;dma 42&gt;, &lt;&amp;dma 42&gt;;
	dma-names = "tx", "rx";
	
	reset_ns = &lt;84&gt;;
	t1h_ns = &lt;800&gt;;
	t1l_ns = &lt;320&gt;;
	t0h_ns = &lt;300&gt;;
	t0l_ns = &lt;800&gt;;
	
	wait_time0_ns = &lt;84&gt;;
	wait_time1_ns = &lt;84&gt;;
	wait_data_time_ns = &lt;600000&gt;;
	status	= "okay";
};
</code></pre>
<p dir="auto">大佬还有个问题想请教下<br />
就是在tina里面能不能一口气把所有的灯的数据都设置好<br />
然后再发送啊？<br />
如果是<br />
echo 1 &gt; /sys/class/leds/sunxi_led0b/brightness</p>
<p dir="auto">这样每改一次就要发送一次，效率太低</p>
<p dir="auto">要同时驱动很多灯的时候 FPS就上不去啦</p>
<p dir="auto">这要怎么处理呀</p>
]]></description><link>https://bbs.aw-ol.com/post/21461</link><guid isPermaLink="true">https://bbs.aw-ol.com/post/21461</guid><dc:creator><![CDATA[leomini5]]></dc:creator><pubDate>Tue, 27 Feb 2024 18:07:52 GMT</pubDate></item><item><title><![CDATA[Reply to D1s ledc驱动代码bug，DMA模式无法使用 on Fri, 23 Feb 2024 02:25:35 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://bbs.aw-ol.com/uid/1915">@awwwwa</a></p>
<p dir="auto">弄不好真是RV的bug我这是D1s上出现这种情况，我翻了一圈论坛里面貌似都是d1s 会出现这种情况，超过32个灯就出错了，那么这会是芯片的问题，还是代码的问题呢，如果是代码的问题还能搞搞好，如果是芯片的问题我就直接贴一片t113起来得了……</p>
]]></description><link>https://bbs.aw-ol.com/post/21349</link><guid isPermaLink="true">https://bbs.aw-ol.com/post/21349</guid><dc:creator><![CDATA[leomini5]]></dc:creator><pubDate>Fri, 23 Feb 2024 02:25:35 GMT</pubDate></item><item><title><![CDATA[Reply to D1s ledc驱动代码bug，DMA模式无法使用 on Fri, 23 Feb 2024 02:17:28 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://bbs.aw-ol.com/uid/4314">@leomini5</a> 我这边没有D1s的板卡，在T113上测试通过，测试1024颗灯，这个可能是RV才有的bug？</p>
]]></description><link>https://bbs.aw-ol.com/post/21347</link><guid isPermaLink="true">https://bbs.aw-ol.com/post/21347</guid><dc:creator><![CDATA[awwwwa]]></dc:creator><pubDate>Fri, 23 Feb 2024 02:17:28 GMT</pubDate></item><item><title><![CDATA[Reply to D1s ledc驱动代码bug，DMA模式无法使用 on Fri, 23 Feb 2024 02:07:48 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://bbs.aw-ol.com/uid/1915">@awwwwa</a></p>
<p dir="auto">大佬我这三份文件都和你发的是一样的</p>
<p dir="auto">我测试了半天那个echo只要改变超过32个灯就直接挂掉了</p>
<p dir="auto">如果把判断条件直接改成DMA模式，一个灯都亮不了呀</p>
<p dir="auto">之前在melis里面测试的时候DMA模式就一直没能成功运行<br />
现在换tina了问题依旧<br />
只有cpu模式下能跑不超过32个灯</p>
<pre><code>[   34.124663] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
[   34.134597] Oops [#1]
[   34.137125] Modules linked in: xr829 sunxi_gpadc
[   34.142277] CPU: 0 PID: 5 Comm: kworker/0:0 Not tainted 5.4.61 #152
[   34.149274] Workqueue: events set_brightness_delayed
[   34.154805] sepc: ffffffe000321d74 ra : ffffffe000321f0c sp : ffffffe00386fcc0
[   34.162844]  gp : ffffffe00067eef8 tp : ffffffe00384ab00 t0 : 0000000000000080
[   34.170884]  t1 : 0000000043265443 t2 : 000000000000000a s0 : ffffffe00386fd50
[   34.178923]  s1 : ffffffe003265240 a0 : 0000000000000000 a1 : 0000000043265444
[   34.186961]  a2 : 0000000000000004 a3 : 0000000000000001 a4 : 0000000000000001
[   34.195000]  a5 : 0000000400000004 a6 : 00000000ffffffff a7 : 0000000000000004
[   34.203039]  s2 : ffffffe003a07410 s3 : 0000000000000004 s4 : 0000000000000001
[   34.211079]  s5 : ffffffe003ed9300 s6 : 0000000000000000 s7 : ffffffe00062ac70
[   34.219118]  s8 : 0000000000000001 s9 : 0000000000000402 s10: ffffffe00062ae28
[   34.227157]  s11: 0000000000000000 t3 : 0000000000000000 t4 : 0000000000000005
[   34.235196]  t5 : ffffffff80000000 t6 : 0000000000040000
[   34.241106] sstatus: 0000000200000120 sbadaddr: 0000000000000000 scause: 000000000000000d
[   34.250748] ---[ end trace b914e592328ab01f ]---
</code></pre>
]]></description><link>https://bbs.aw-ol.com/post/21345</link><guid isPermaLink="true">https://bbs.aw-ol.com/post/21345</guid><dc:creator><![CDATA[leomini5]]></dc:creator><pubDate>Fri, 23 Feb 2024 02:07:48 GMT</pubDate></item><item><title><![CDATA[Reply to D1s ledc驱动代码bug，DMA模式无法使用 on Fri, 23 Feb 2024 01:10:06 GMT]]></title><description><![CDATA[<p dir="auto">之前在其他帖发过DMA模式的patch，这里同步下</p>
<p dir="auto">lichee/linux-5.4/drivers/leds/leds-sunxi.c</p>
<pre><code>// SPDX-License-Identifier: GPL-2.0-only
/*
 * drivers/leds/leds-sunxi.c - Allwinner RGB LED Driver
 *
 * Copyright (C) 2018 Allwinner Technology Limited. All rights reserved.
 *      http://www.allwinnertech.com
 *
 *Author : Albert Yu &lt;yuxyun@allwinnertech.com&gt;
 *	   Lewis &lt;liuyu@allwinnertech.com&gt;
 * 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 &lt;linux/module.h&gt;
#include &lt;linux/delay.h&gt;
#include &lt;linux/leds.h&gt;
#include &lt;linux/io.h&gt;
#include &lt;linux/of.h&gt;
#include &lt;linux/slab.h&gt;
#include &lt;linux/clk.h&gt;
#include &lt;linux/dmaengine.h&gt;
#include &lt;linux/interrupt.h&gt;
#include &lt;linux/platform_device.h&gt;
#include &lt;linux/pinctrl/consumer.h&gt;
#include &lt;linux/dma-mapping.h&gt;
#include &lt;linux/debugfs.h&gt;
#include &lt;linux/uaccess.h&gt;
#include &lt;linux/delay.h&gt;
#include &lt;linux/regulator/consumer.h&gt;
#include &lt;linux/reset.h&gt;

#if IS_ENABLED(CONFIG_PM)
#include &lt;linux/pm.h&gt;
#endif
#include "leds-sunxi.h"

/* For debug */
#define LED_ERR(fmt, arg...) pr_err("%s()%d - "fmt, __func__, __LINE__, ##arg)

#define dprintk(level_mask, fmt, arg...)				\
do {									\
	if (unlikely(debug_mask &amp; level_mask))				\
		pr_warn("%s()%d - "fmt, __func__, __LINE__, ##arg);	\
} while (0)

static u32 debug_mask = 1;
static struct sunxi_led *sunxi_led_global;
static struct class *led_class;

#define sunxi_slave_id(d, s) (((d)&lt;&lt;16) | (s))

/*For Driver */
static void led_dump_reg(struct sunxi_led *led, u32 offset, u32 len)
{
	u32 i;
	u8 buf[64], cnt = 0;

	for (i = 0; i &lt; len; i = i + REG_INTERVAL) {
		if (i%HEXADECIMAL == 0)
			cnt += sprintf(buf + cnt, "0x%08x: ",
					(u32)(led-&gt;res-&gt;start + offset + i));

		cnt += sprintf(buf + cnt, "%08x ",
				readl(led-&gt;iomem_reg_base + offset + i));

		if (i%HEXADECIMAL == REG_CL) {
			pr_warn("%s\n", buf);
			cnt = 0;
		}
	}
}

static void sunxi_clk_get(struct sunxi_led *led)
{
	struct device *dev = led-&gt;dev;
	struct device_node *np = dev-&gt;of_node;

	led-&gt;clk_ledc = of_clk_get(np, 0);
	if (IS_ERR(led-&gt;clk_ledc))
		LED_ERR("failed to get clk_ledc!\n");

	led-&gt;clk_cpuapb = of_clk_get(np, 1);
	if (IS_ERR(led-&gt;clk_cpuapb))
		LED_ERR("failed to get clk_cpuapb!\n");
}

static void sunxi_clk_put(struct sunxi_led *led)
{
	clk_put(led-&gt;clk_ledc);
	clk_put(led-&gt;clk_cpuapb);
	led-&gt;clk_ledc = NULL;
	led-&gt;clk_cpuapb = NULL;
}

static void sunxi_clk_enable(struct sunxi_led *led)
{
	clk_prepare_enable(led-&gt;clk_ledc);
	clk_prepare_enable(led-&gt;clk_cpuapb);
}

static void sunxi_clk_disable(struct sunxi_led *led)
{
	clk_disable_unprepare(led-&gt;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_global;
	u32 value = ioread32(((u8 *)led-&gt;iomem_reg_base) + offset);

	return value;
}

static void sunxi_set_reg(int offset, u32 value)
{
	struct sunxi_led *led = sunxi_led_global;

	iowrite32(value, ((u8 *)led-&gt;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-&gt;reset_ns &lt; min || led-&gt;reset_ns &gt; max) {
		LED_ERR("invalid parameter, reset_ns should be %u-%u!\n",
				min, max);
		return;
	}

	n = (led-&gt;reset_ns - 42) / 42;
	reg_val = sunxi_get_reg(LED_RESET_TIMING_CTRL_REG_OFFSET);
	reg_val &amp;= ~(mask &lt;&lt; 16);
	reg_val |= (n &lt;&lt; 16);
	sunxi_set_reg(LED_RESET_TIMING_CTRL_REG_OFFSET, reg_val);
}

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-&gt;t1h_ns &lt; min || led-&gt;t1h_ns &gt; max) {
		LED_ERR("invalid parameter, t1h_ns should be %u-%u!\n",
				min, max);
		return;
	}

	n = (led-&gt;t1h_ns - 42) / 42;
	reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET);
	reg_val &amp;= ~(mask &lt;&lt; shift);
	reg_val |= n &lt;&lt; shift;
	sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val);
}

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-&gt;t1l_ns &lt; min || led-&gt;t1l_ns &gt; max) {
		LED_ERR("invalid parameter, t1l_ns should be %u-%u!\n",
				min, max);
		return;
	}

	n = (led-&gt;t1l_ns - 42) / 42;
	reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET);
	reg_val &amp;= ~(mask &lt;&lt; shift);
	reg_val |= n &lt;&lt; shift;
	sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val);
}

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-&gt;t0h_ns &lt; min || led-&gt;t0h_ns &gt; max) {
		LED_ERR("invalid parameter, t0h_ns should be %u-%u!\n",
			min, max);
		return;
	}

	n = (led-&gt;t0h_ns - 42) / 42;
	reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET);
	reg_val &amp;= ~(mask &lt;&lt; shift);
	reg_val |= n &lt;&lt; shift;
	sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val);
}

static inline void sunxi_set_t0l_ns(struct sunxi_led *led)
{
	u32 n, reg_val;
	u32 min = SUNXI_T0L_MIN_NS;
	u32 max = SUNXI_T0L_MAX_NS;

	if (led-&gt;t0l_ns &lt; min || led-&gt;t0l_ns &gt; max) {
		LED_ERR("invalid parameter, t0l_ns should be %u-%u!\n",
				min, max);
		return;
	}

	n = (led-&gt;t0l_ns - 42) / 42;
	reg_val = sunxi_get_reg(LED_T01_TIMING_CTRL_REG_OFFSET);
	reg_val &amp;= ~0x3F;
	reg_val |= n;
	sunxi_set_reg(LED_T01_TIMING_CTRL_REG_OFFSET, reg_val);
}

static inline void sunxi_set_wait_time0_ns(struct sunxi_led *led)
{
	u32 n, reg_val;
	u32 min = SUNXI_WAIT_TIME0_MIN_NS;
	u32 max = SUNXI_WAIT_TIME0_MAX_NS;

	if (led-&gt;wait_time0_ns &lt; min || led-&gt;wait_time0_ns &gt; max) {
		LED_ERR("invalid parameter, wait_time0_ns should be %u-%u!\n",
				min, max);
		return;
	}

	n = (led-&gt;wait_time0_ns - 42) / 42;
	reg_val = (1 &lt;&lt; 8) | n;
	sunxi_set_reg(LEDC_WAIT_TIME0_CTRL_REG, reg_val);
}

static inline void sunxi_set_wait_time1_ns(struct sunxi_led *led)
{
	unsigned long long tmp, max = SUNXI_WAIT_TIME1_MAX_NS;
	u32 min = SUNXI_WAIT_TIME1_MIN_NS;
	u32 n, reg_val;

	if (led-&gt;wait_time1_ns &lt; min || led-&gt;wait_time1_ns &gt; max) {
		LED_ERR("invalid parameter, wait_time1_ns should be %u-%llu!\n",
			min, max);
		return;
	}

	tmp = led-&gt;wait_time1_ns;
	n = div_u64(tmp, 42);
	n -= 1;
	reg_val = (1 &lt;&lt; 31) | n;
	sunxi_set_reg(LEDC_WAIT_TIME1_CTRL_REG, reg_val);
}

static inline void sunxi_set_wait_data_time_ns(struct sunxi_led *led)
{
	u32 min, max;
#ifndef SUNXI_FPGA_LEDC
	u32 mask = 0x1FFF, shift = 16, reg_val = 0, n;
#endif
	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-&gt;wait_data_time_ns &lt; min || led-&gt;wait_data_time_ns &gt; max) {
		LED_ERR("invalid parameter, wait_data_time_ns should be %u-%u!\n",
			min, max);
		return;
	}

#ifndef SUNXI_FPGA_LEDC
	n = (led-&gt;wait_data_time_ns - 42) / 42;
	reg_val &amp;= ~(mask &lt;&lt; shift);
	reg_val |= (n &lt;&lt; shift);
	sunxi_set_reg(LEDC_DATA_FINISH_CNT_REG_OFFSET, reg_val);
#endif /* SUNXI_FPGA_LEDC */
}

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-&gt;length;

	if (length == 0)
		return;

	if (length &gt; led-&gt;led_count)
		return;

	reg_val = sunxi_get_reg(LEDC_CTRL_REG_OFFSET);
	reg_val &amp;= ~(0x1FFF &lt;&lt; 16);
	reg_val |=  length &lt;&lt; 16;
	sunxi_set_reg(LEDC_CTRL_REG_OFFSET, reg_val);

	reg_val = sunxi_get_reg(LED_RESET_TIMING_CTRL_REG_OFFSET);
	reg_val &amp;= ~0x3FF;
	reg_val |= length - 1;
	sunxi_set_reg(LED_RESET_TIMING_CTRL_REG_OFFSET, reg_val);
}

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(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;
	} else {
		val = led-&gt;output_mode.val;
	}

	reg_val &amp;= ~(mask &lt;&lt; shift);
	reg_val |= val;

	sunxi_set_reg(LEDC_CTRL_REG_OFFSET, reg_val);

	if (str != NULL) {
		if (strncmp(str, led-&gt;output_mode.str, 3))
			memcpy(led-&gt;output_mode.str, str, 3);
	}

	if (val != led-&gt;output_mode.val)
		led-&gt;output_mode.val = val;
}

static void sunxi_ledc_enable_irq(u32 mask)
{
	u32 reg_val = 0;

	reg_val |= mask;
	sunxi_set_reg(LEDC_INT_CTRL_REG_OFFSET, reg_val);
}

static void sunxi_ledc_disable_irq(u32 mask)
{
	u32 reg_val = 0;

	reg_val = sunxi_get_reg(LEDC_INT_CTRL_REG_OFFSET);
	reg_val &amp;= ~mask;
	sunxi_set_reg(LEDC_INT_CTRL_REG_OFFSET, reg_val);
}

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)
{
	u32 reg_val = sunxi_get_reg(LEDC_CTRL_REG_OFFSET);

	sunxi_ledc_disable_irq(LEDC_TRANS_FINISH_INT_EN | LEDC_FIFO_CPUREQ_INT_EN
			| LEDC_WAITDATA_TIMEOUT_INT_EN | LEDC_FIFO_OVERFLOW_INT_EN
			| LEDC_GLOBAL_INT_EN);

	if (debug_mask &amp; DEBUG_INFO2) {
		dprintk(DEBUG_INFO2, "dump reg:\n");
		led_dump_reg(led, 0, 0x30);
	}

	reg_val |= 1 &lt;&lt; 1;
	sunxi_set_reg(LEDC_CTRL_REG_OFFSET, reg_val);
}

#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_global;

	min = SUNXI_RESET_TIME_MIN_NS;
	max = SUNXI_RESET_TIME_MAX_NS;

	if (count &gt;= sizeof(buffer))
		goto err_out;

	if (copy_from_user(buffer, buf, count))
		goto err_out;

	buffer[count] = '\0';

	err = kstrtoul(buffer, 10, &amp;val);
	if (err)
		goto err_out;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;reset_ns = val;
	sunxi_set_reset_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%u\n", led-&gt;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_global;

	min = SUNXI_T1H_MIN_NS;
	max = SUNXI_T1H_MAX_NS;

	if (count &gt;= sizeof(buffer))
		return -EINVAL;

	if (copy_from_user(buffer, buf, count))
		return -EFAULT;

	buffer[count] = '\0';

	err = kstrtoul(buffer, 10, &amp;val);
	if (err)
		return -EINVAL;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;t1h_ns = val;

	sunxi_set_t1h_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%u\n", led-&gt;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_global;

	min = SUNXI_T1L_MIN_NS;
	max = SUNXI_T1L_MAX_NS;

	if (count &gt;= sizeof(buffer))
		goto err_out;

	if (copy_from_user(buffer, buf, count))
		goto err_out;

	buffer[count] = '\0';

	err = kstrtoul(buffer, 10, &amp;val);
	if (err)
		goto err_out;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;t1l_ns = val;
	sunxi_set_t1l_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%u\n", led-&gt;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_global;

	min = SUNXI_T0H_MIN_NS;
	max = SUNXI_T0H_MAX_NS;

	if (count &gt;= sizeof(buffer))
		goto err_out;

	if (copy_from_user(buffer, buf, count))
		goto err_out;

	buffer[count] = '\0';

	err = kstrtoul(buffer, 10, &amp;val);
	if (err)
		goto err_out;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;t0h_ns = val;
	sunxi_set_t0h_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%u\n", led-&gt;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_global;

	min = SUNXI_T0L_MIN_NS;
	max = SUNXI_T0L_MAX_NS;

	if (count &gt;= sizeof(buffer))
		goto err_out;

	if (copy_from_user(buffer, buf, count))
		goto err_out;

	buffer[count] = '\0';

	err = kstrtoul(buffer, 10, &amp;val);
	if (err)
		goto err_out;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;t0l_ns = val;
	sunxi_set_t0l_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%u\n", led-&gt;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_global;

	min = SUNXI_WAIT_TIME0_MIN_NS;
	max = SUNXI_WAIT_TIME0_MAX_NS;

	if (count &gt;= sizeof(buffer))
		goto err_out;

	if (copy_from_user(buffer, buf, count))
		goto err_out;

	buffer[count] = '\0';

	err = kstrtoul(buffer, 10, &amp;val);
	if (err)
		goto err_out;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;wait_time0_ns = val;
	sunxi_set_wait_time0_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%u\n", led-&gt;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;
	unsigned long long max;
	unsigned long long val;
	struct sunxi_led *led = sunxi_led_global;

	min = SUNXI_WAIT_TIME1_MIN_NS;
	max = SUNXI_WAIT_TIME1_MAX_NS;

	if (count &gt;= sizeof(buffer))
		goto err_out;

	if (copy_from_user(buffer, buf, count))
		goto err_out;

	buffer[count] = '\0';

	err = kstrtoull(buffer, 10, &amp;val);
	if (err)
		goto err_out;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;wait_time1_ns = val;
	sunxi_set_wait_time1_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%lld\n", led-&gt;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_global;

	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 &gt;= sizeof(buffer))
		goto err_out;

	if (copy_from_user(buffer, buf, count))
		goto err_out;

	buffer[count] = '\0';

	err = kstrtoul(buffer, 10, &amp;val);
	if (err)
		goto err_out;

	if (val &lt; min || val &gt; max)
		goto err_out;

	led-&gt;wait_data_time_ns = val;
	sunxi_set_wait_data_time_ns(led);

	*offp += count;

	return count;

err_out:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%u\n", led-&gt;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_global;

	for (i = 0; i &lt; led-&gt;led_count; i++) {
		if (!(i % 4)) {
			if (i + 4 &lt;= led-&gt;led_count)
				seq_printf(s, "%04d-%04d", i, i + 4);
			else
				seq_printf(s, "%04d-%04d", i, led-&gt;led_count);
		}
		seq_printf(s, " 0x%08x", led-&gt;data[i]);
		if (((i % 4) == 3) || (i == led-&gt;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-&gt;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_global;

	if (count &gt;= 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:
	LED_ERR("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_global;

	r = snprintf(buffer, 64, "%s\n", led-&gt;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 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 &gt;&gt; 16;
	minor_ver = reg_val &amp; 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;

	debugfs_dir = debugfs_create_dir("sunxi_leds", NULL);
	if (IS_ERR_OR_NULL(debugfs_dir)) {
		LED_ERR("debugfs_create_dir failed!\n");
		return;
	}

	led-&gt;debugfs_dir = debugfs_dir;

	debugfs_file = debugfs_create_file("reset_ns", 0660,
				debugfs_dir, NULL, &amp;reset_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for reset_ns failed!\n");

	debugfs_file = debugfs_create_file("t1h_ns", 0660,
				debugfs_dir, NULL, &amp;t1h_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for t1h_ns failed!\n");

	debugfs_file = debugfs_create_file("t1l_ns", 0660,
				debugfs_dir, NULL, &amp;t1l_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for t1l_ns failed!\n");

	debugfs_file = debugfs_create_file("t0h_ns", 0660,
				debugfs_dir, NULL, &amp;t0h_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for t0h_ns failed!\n");

	debugfs_file = debugfs_create_file("t0l_ns", 0660,
				debugfs_dir, NULL, &amp;t0l_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for t0l_ns failed!\n");

	debugfs_file = debugfs_create_file("wait_time0_ns", 0660,
				debugfs_dir, NULL, &amp;wait_time0_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for wait_time0_ns failed!\n");

	debugfs_file = debugfs_create_file("wait_time1_ns", 0660,
				debugfs_dir, NULL, &amp;wait_time1_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for wait_time1_ns failed!\n");

	debugfs_file = debugfs_create_file("wait_data_time_ns", 0660,
				debugfs_dir, NULL, &amp;wait_data_time_ns_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for wait_data_time_ns failed!\n");

	debugfs_file = debugfs_create_file("data", 0440,
				debugfs_dir, NULL, &amp;data_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for data failed!\n");

	debugfs_file = debugfs_create_file("output_mode", 0660,
				debugfs_dir, NULL, &amp;output_mode_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for output_mode failed!\n");

	if (!debugfs_file)
		LED_ERR("debugfs_create_file for trans_mode failed!\n");

	debugfs_file = debugfs_create_file("hwversion", 0440,
				debugfs_dir, NULL, &amp;hwversion_fops);
	if (!debugfs_file)
		LED_ERR("debugfs_create_file for hwversion failed!\n");
}

static void sunxi_led_remove_debugfs(struct sunxi_led *led)
{
	debugfs_remove_recursive(led-&gt;debugfs_dir);
}
#endif /* CONFIG_DEBUG_FS */

static void sunxi_ledc_set_dma_mode(struct sunxi_led *led)
{
	u32 reg_val = 0;

	reg_val |= 1 &lt;&lt; 5;
	sunxi_set_reg(LEDC_DMA_CTRL_REG, reg_val);

	sunxi_ledc_disable_irq(LEDC_FIFO_CPUREQ_INT_EN);
}

static void sunxi_ledc_set_cpu_mode(struct sunxi_led *led)
{
	u32 reg_val = 0;

	reg_val &amp;= ~(1 &lt;&lt; 5);
	sunxi_set_reg(LEDC_DMA_CTRL_REG, reg_val);

	sunxi_ledc_enable_irq(LEDC_FIFO_CPUREQ_INT_EN);
}

static void sunxi_ledc_dma_callback(void *param)
{
	dprintk(DEBUG_INFO, "finish\n");
}

static void sunxi_ledc_trans_data(struct sunxi_led *led)
{
	int i, err;
	size_t size;
	unsigned long flags;
	phys_addr_t dst_addr;
	struct dma_slave_config slave_config;
	struct device *dev = led-&gt;dev;
	struct dma_async_tx_descriptor *dma_desc;

	/* less than 32 lights use cpu transmission. */
	/* more than 32 lights use dma transmission. */
	if (led-&gt;length &lt;= SUNXI_LEDC_FIFO_DEPTH) {
		dprintk(DEBUG_INFO, "cpu xfer\n");
		ktime_get_coarse_real_ts64(&amp;(led-&gt;start_time));
		sunxi_ledc_set_time(led);
		sunxi_ledc_set_output_mode(led, led-&gt;output_mode.str);
		sunxi_ledc_set_cpu_mode(led);
		sunxi_ledc_set_length(led);

		sunxi_ledc_enable_irq(LEDC_TRANS_FINISH_INT_EN | LEDC_WAITDATA_TIMEOUT_INT_EN
				| LEDC_FIFO_OVERFLOW_INT_EN | LEDC_GLOBAL_INT_EN);

		sunxi_ledc_enable(led);

		for (i = 0; i &lt; led-&gt;length; i++)
			sunxi_set_reg(LEDC_DATA_REG_OFFSET, led-&gt;data[i]);

	} else {
		dprintk(DEBUG_INFO, "dma xfer\n");

		size = led-&gt;length * 4;
		led-&gt;src_dma = dma_map_single(dev, led-&gt;data,
					size, DMA_TO_DEVICE);
		dst_addr = led-&gt;res-&gt;start + LEDC_DATA_REG_OFFSET;

		flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;

		slave_config.direction = DMA_MEM_TO_DEV;
		slave_config.src_addr = led-&gt;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 = 4;
		slave_config.dst_maxburst = 4;

		err = dmaengine_slave_config(led-&gt;dma_chan, &amp;slave_config);
		if (err &lt; 0) {
			LED_ERR("dmaengine_slave_config failed!\n");
			return;
		}

		dma_desc = dmaengine_prep_slave_single(led-&gt;dma_chan,
							led-&gt;src_dma,
							size,
							DMA_MEM_TO_DEV,
							flags);
		if (!dma_desc) {
			LED_ERR("dmaengine_prep_slave_single failed!\n");
			return;
		}

		dma_desc-&gt;callback = sunxi_ledc_dma_callback;

		dmaengine_submit(dma_desc);
		dma_async_issue_pending(led-&gt;dma_chan);

		ktime_get_coarse_real_ts64(&amp;(led-&gt;start_time));
		sunxi_ledc_set_time(led);
		sunxi_ledc_set_output_mode(led, led-&gt;output_mode.str);
		sunxi_ledc_set_dma_mode(led);
		sunxi_ledc_set_length(led);
		sunxi_ledc_enable_irq(LEDC_TRANS_FINISH_INT_EN | LEDC_WAITDATA_TIMEOUT_INT_EN
				| LEDC_FIFO_OVERFLOW_INT_EN | LEDC_GLOBAL_INT_EN);
		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 &amp;= ~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 &amp;= ~irq;
	sunxi_set_reg(LEDC_INT_STS_REG_OFFSET, reg_val);
}

static int sunxi_ledc_complete(struct sunxi_led *led)
{
	unsigned long flags = 0;
	unsigned long timeout = 0;
	u32 reg_val;

	/*wait_event_timeout return 0   : timeout
	 *wait_event_timeout return &gt; 0 : thr left time
	 * */
	timeout = wait_event_timeout(led-&gt;wait, led-&gt;result, 5*HZ);
	if (timeout == 0) {
		reg_val = sunxi_get_reg(LEDC_INT_STS_REG_OFFSET);
		pr_err("LEDC INTERRUPT STATUS REG IS %x", reg_val);
		LED_ERR("led xfer timeout\n");
		reg_val = sunxi_get_reg(LEDC_INT_STS_REG_OFFSET);
		pr_err("LEDC INTERRUPT STATUS REG IS %x", reg_val);
		return -ETIME;
	} else if (led-&gt;result == RESULT_ERR) {
		return -ECOMM;
	}

	dprintk(DEBUG_INFO, "xfer complete\n");

	spin_lock_irqsave(&amp;led-&gt;lock, flags);
	led-&gt;result = 0;
	spin_unlock_irqrestore(&amp;led-&gt;lock, flags);

	return 0;
}

static irqreturn_t sunxi_ledc_irq_handler(int irq, void *dev_id)
{
	long delta_time_ns;
	u32 irq_status, max_ns;
	struct sunxi_led *led = sunxi_led_global;
	struct device *dev = led-&gt;dev;
	struct timespec64 current_time;

	spin_lock(&amp;led-&gt;lock);

	irq_status = sunxi_get_reg(LEDC_INT_STS_REG_OFFSET);

	sunxi_ledc_clear_all_irq();

	if (irq_status &amp; LEDC_TRANS_FINISH_INT) {
		sunxi_ledc_reset(led);
		led-&gt;result = RESULT_COMPLETE;
		goto out;
	}

	if (irq_status &amp; LEDC_WAITDATA_TIMEOUT_INT) {
		ktime_get_coarse_real_ts64(&amp;current_time);
		delta_time_ns = current_time.tv_sec - led-&gt;start_time.tv_sec;
		delta_time_ns *= 1000 * 1000 * 1000;
		delta_time_ns += current_time.tv_nsec - led-&gt;start_time.tv_nsec;

		max_ns = led-&gt;wait_data_time_ns;

		if (delta_time_ns &lt;= max_ns) {
			spin_unlock(&amp;led-&gt;lock);
			return IRQ_HANDLED;
		}

		sunxi_ledc_reset(led);

		if (delta_time_ns &lt;= max_ns * 2) {
			sunxi_ledc_trans_data(led);
		} else {
			LED_ERR("wait time is more than %d ns,"
				"going to reset ledc and drop this operation!\n",
				max_ns);
			led-&gt;result = RESULT_ERR;
		}

		goto out;
	}

	if (irq_status &amp; LEDC_FIFO_OVERFLOW_INT) {
		LED_ERR("there exists fifo overflow issue, irq_status=0x%x!\n",
				irq_status);
		sunxi_ledc_reset(led);
		led-&gt;result = RESULT_ERR;
		goto out;
	}

out:
	if (led-&gt;dma_chan)
		dma_unmap_single(dev, led-&gt;src_dma, led-&gt;length * 4, DMA_TO_DEVICE);
	wake_up(&amp;led-&gt;wait);
	led-&gt;length = 0;
	spin_unlock(&amp;led-&gt;lock);
	return IRQ_HANDLED;
}

static int sunxi_ledc_irq_init(struct sunxi_led *led)
{
	int err;
	struct device *dev = led-&gt;dev;
	unsigned long flags = 0;
	const char *name = "ledcirq";
	struct platform_device *pdev;

	pdev = container_of(dev, struct platform_device, dev);

	spin_lock_init(&amp;led-&gt;lock);

	led-&gt;irqnum = platform_get_irq(pdev, 0);
	if (led-&gt;irqnum &lt; 0)
		LED_ERR("failed to get ledc irq!\n");

	err = request_irq(led-&gt;irqnum, sunxi_ledc_irq_handler,
				flags, name, dev);
	if (err) {
		LED_ERR("failed to install IRQ handler for irqnum %d\n",
			led-&gt;irqnum);
		return -EPERM;
	}

	return 0;
}

static void sunxi_ledc_irq_deinit(struct sunxi_led *led)
{
	free_irq(led-&gt;irqnum, led-&gt;dev);
	sunxi_ledc_disable_irq(LEDC_TRANS_FINISH_INT_EN | LEDC_FIFO_CPUREQ_INT_EN
			| LEDC_WAITDATA_TIMEOUT_INT_EN | LEDC_FIFO_OVERFLOW_INT_EN
			| LEDC_GLOBAL_INT_EN);
}

static void sunxi_ledc_pinctrl_init(struct sunxi_led *led)
{
	struct device *dev = led-&gt;dev;
	struct pinctrl *pinctrl = devm_pinctrl_get_select_default(dev);

	led-&gt;pctrl = pinctrl;
	if (IS_ERR(pinctrl))
		LED_ERR("devm_pinctrl_get_select_default failed!\n");
}

static int led_regulator_request(struct sunxi_led *led)
{
	struct regulator *regu = NULL;

	/* Consider "n*" as nocare. Support "none", "nocare", "null", "" etc. */
	if ((led-&gt;regulator_id[0] == 'n') || (led-&gt;regulator_id[0] == 0))
		return 0;

	regu = regulator_get(NULL, led-&gt;regulator_id);
	if (IS_ERR(regu)) {
		LED_ERR("get regulator %s failed!\n", led-&gt;regulator_id);
		return -1;
	}
	led-&gt;regulator = regu;

	return 0;
}

static int led_regulator_release(struct sunxi_led *led)
{
	if (led-&gt;regulator == NULL)
		return 0;

	regulator_put(led-&gt;regulator);
	led-&gt;regulator = NULL;

	return 1;
}

static int sunxi_ledc_dma_get(struct sunxi_led *led)
{
	if (led-&gt;dma_chan == NULL) {
		led-&gt;dma_chan = dma_request_chan(led-&gt;dev, "tx");
		if (IS_ERR(led-&gt;dma_chan)) {
			LED_ERR("failed to get the DMA channel!\n");
			return -EFAULT;
		}
	}
	return 0;
}

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_global;
	int err;

	pinfo = container_of(led_cdev, struct sunxi_led_info, cdev);

	switch (pinfo-&gt;type) {
	case LED_TYPE_G:
		pcdev_group = container_of(pinfo,
			struct sunxi_led_classdev_group, g);
		g = value;
		shift = 16;
		break;
	case LED_TYPE_R:
		pcdev_group = container_of(pinfo,
			struct sunxi_led_classdev_group, r);
		r = value;
		shift = 8;
		break;

	case LED_TYPE_B:
		pcdev_group = container_of(pinfo,
			struct sunxi_led_classdev_group, b);
		b = value;
		shift = 0;
		break;
	}

	old_data = led-&gt;data[pcdev_group-&gt;led_num];
	if (((old_data &gt;&gt; shift) &amp; 0xFF) == value)
		return 0;

	if (pinfo-&gt;type != LED_TYPE_R)
		r = pcdev_group-&gt;r.cdev.brightness;
	if (pinfo-&gt;type != LED_TYPE_G)
		g = pcdev_group-&gt;g.cdev.brightness;
	if (pinfo-&gt;type != LED_TYPE_B)
		b = pcdev_group-&gt;b.cdev.brightness;

	/* LEDC treats input data as GRB by default */
	new_data = (g &lt;&lt; 16) | (r &lt;&lt; 8) | b;
	length = pcdev_group-&gt;led_num + 1;

	spin_lock_irqsave(&amp;led-&gt;lock, flags);
	led-&gt;data[pcdev_group-&gt;led_num] = new_data;
	led-&gt;length = length;
	spin_unlock_irqrestore(&amp;led-&gt;lock, flags);

	/* prepare for dma xfer, dynamic apply dma channel */
	if (led-&gt;length &gt; SUNXI_LEDC_FIFO_DEPTH) {
		err = sunxi_ledc_dma_get(led);
		if (err)
			return err;
	}

	sunxi_ledc_trans_data(led);
	if (debug_mask &amp; DEBUG_INFO2) {
		dprintk(DEBUG_INFO2, "dump reg:\n");
		led_dump_reg(led, 0, 0x30);
	}

	sunxi_ledc_complete(led);

	if (debug_mask &amp; DEBUG_INFO1)
		pr_warn("num = %03u\n", length);

	return 0;
}

static int sunxi_register_led_classdev(struct sunxi_led *led)
{
	int i, err;
	size_t size;
	struct device *dev = led-&gt;dev;
	struct led_classdev *pcdev_RGB;

	dprintk(DEBUG_INIT, "led_classdev start\n");
	if (!led-&gt;led_count)
		led-&gt;led_count = SUNXI_DEFAULT_LED_COUNT;

	size = sizeof(struct sunxi_led_classdev_group) * led-&gt;led_count;
	led-&gt;pcdev_group = devm_kzalloc(dev, size, GFP_KERNEL);
	if (!led-&gt;pcdev_group)
		return -ENOMEM;

	for (i = 0; i &lt; led-&gt;led_count; i++) {
		led-&gt;pcdev_group[i].r.type = LED_TYPE_R;
		pcdev_RGB = &amp;led-&gt;pcdev_group[i].r.cdev;
		pcdev_RGB-&gt;name = devm_kzalloc(dev, 16, GFP_KERNEL);
		if (!pcdev_RGB-&gt;name)
			return -ENOMEM;
		sprintf((char *)pcdev_RGB-&gt;name, "sunxi_led%dr", i);
		pcdev_RGB-&gt;brightness = LED_OFF;
		pcdev_RGB-&gt;brightness_set_blocking = sunxi_set_led_brightness;
		pcdev_RGB-&gt;dev = dev;
		err = led_classdev_register(dev, pcdev_RGB);
		if (err &lt; 0) {
			LED_ERR("led_classdev_register %s failed!\n",
				pcdev_RGB-&gt;name);
			return err;
		}

		led-&gt;pcdev_group[i].g.type = LED_TYPE_G;
		pcdev_RGB = &amp;led-&gt;pcdev_group[i].g.cdev;
		pcdev_RGB-&gt;name = devm_kzalloc(dev, 16, GFP_KERNEL);
		if (!pcdev_RGB-&gt;name)
			return -ENOMEM;
		sprintf((char *)pcdev_RGB-&gt;name, "sunxi_led%dg", i);
		pcdev_RGB-&gt;brightness = LED_OFF;
		pcdev_RGB-&gt;brightness_set_blocking = sunxi_set_led_brightness;
		pcdev_RGB-&gt;dev = dev;
		err = led_classdev_register(dev, pcdev_RGB);
		if (err &lt; 0) {
			LED_ERR("led_classdev_register %s failed!\n",
			pcdev_RGB-&gt;name);
			return err;
		}

		led-&gt;pcdev_group[i].b.type = LED_TYPE_B;
		pcdev_RGB = &amp;led-&gt;pcdev_group[i].b.cdev;
		pcdev_RGB-&gt;name = devm_kzalloc(dev, 16, GFP_KERNEL);
		if (!pcdev_RGB-&gt;name)
			return -ENOMEM;
		sprintf((char *)pcdev_RGB-&gt;name, "sunxi_led%db", i);
		pcdev_RGB-&gt;brightness = LED_OFF;
		pcdev_RGB-&gt;brightness_set_blocking = sunxi_set_led_brightness;
		pcdev_RGB-&gt;dev = dev;
		err = led_classdev_register(dev, pcdev_RGB);
		if (err &lt; 0) {
			LED_ERR("led_classdev_register %s failed!\n",
					pcdev_RGB-&gt;name);
			return err;
		}

		led-&gt;pcdev_group[i].led_num = i;
	}

	size = sizeof(u32) * led-&gt;led_count;
	led-&gt;data = devm_kzalloc(dev, size, GFP_KERNEL);
	if (!led-&gt;data)
		return -ENOMEM;

	return 0;
}

static void sunxi_unregister_led_classdev(struct sunxi_led *led)
{
	int i;

	for (i = 0; i &lt; led-&gt;led_count; i++) {
		kfree(led-&gt;pcdev_group[i].b.cdev.name);
		led-&gt;pcdev_group[i].b.cdev.name = NULL;
		kfree(led-&gt;pcdev_group[i].g.cdev.name);
		led-&gt;pcdev_group[i].g.cdev.name = NULL;
		kfree(led-&gt;pcdev_group[i].r.cdev.name);
		led-&gt;pcdev_group[i].r.cdev.name = NULL;
		led_classdev_unregister(&amp;led-&gt;pcdev_group[i].b.cdev);
		led_classdev_unregister(&amp;led-&gt;pcdev_group[i].g.cdev);
		led_classdev_unregister(&amp;led-&gt;pcdev_group[i].r.cdev);
	}
	kfree(led-&gt;data);
	led-&gt;data = NULL;


	kfree(led-&gt;pcdev_group);
	led-&gt;pcdev_group = NULL;
}

static inline int sunxi_get_u32_of_property(const char *propname, int *val)
{
	int err;
	struct sunxi_led *led = sunxi_led_global;
	struct device *dev = led-&gt;dev;
	struct device_node *np = dev-&gt;of_node;

	err = of_property_read_u32(np, propname, val);
	if (err &lt; 0)
		LED_ERR("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_global;
	struct device *dev = led-&gt;dev;
	struct device_node *np = dev-&gt;of_node;

	err = of_property_read_string(np, propname, out_string);
	if (err &lt; 0)
		LED_ERR("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", &amp;val);
	if (!err)
		led-&gt;led_count = val;

	memcpy(led-&gt;output_mode.str, "GRB", 3);
	led-&gt;output_mode.val = SUNXI_OUTPUT_GRB;
	err = sunxi_get_str_of_property("output_mode", &amp;str);
	if (!err)
		if (!strncmp(str, "BRG", 3) ||
			!strncmp(str, "GBR", 3) ||
			!strncmp(str, "RGB", 3) ||
			!strncmp(str, "RBG", 3) ||
			!strncmp(str, "BGR", 3))
			memcpy(led-&gt;output_mode.str, str, 3);

	err =  sunxi_get_str_of_property("led_regulator", &amp;str);
	if (!err) {
		if (strlen(str) &gt;= sizeof(led-&gt;regulator_id))
			LED_ERR("illegal regulator id\n");
		else {
			strcpy(led-&gt;regulator_id, str);
			pr_info("led_regulator: %s\n", led-&gt;regulator_id);
		}
	}

	err = sunxi_get_u32_of_property("reset_ns", &amp;val);
	if (!err)
		led-&gt;reset_ns = val;

	err = sunxi_get_u32_of_property("t1h_ns", &amp;val);
	if (!err)
		led-&gt;t1h_ns = val;

	err = sunxi_get_u32_of_property("t1l_ns", &amp;val);
	if (!err)
		led-&gt;t1l_ns = val;

	err = sunxi_get_u32_of_property("t0h_ns", &amp;val);
	if (!err)
		led-&gt;t0h_ns = val;

	err = sunxi_get_u32_of_property("t0l_ns", &amp;val);
	if (!err)
		led-&gt;t0l_ns = val;

	err = sunxi_get_u32_of_property("wait_time0_ns", &amp;val);
	if (!err)
		led-&gt;wait_time0_ns = val;

	err = sunxi_get_u32_of_property("wait_time1_ns", &amp;val);
	if (!err)
		led-&gt;wait_time1_ns = val;

	err = sunxi_get_u32_of_property("wait_data_time_ns", &amp;val);
	if (!err)
		led-&gt;wait_data_time_ns = val;
}
static void sunxi_led_set_all(struct sunxi_led *led, u8 channel,
		enum led_brightness value)
{
	u32 i;
	struct led_classdev *led_cdev;

	if (channel%3 == 0) {
		for (i = 0; i &lt; led-&gt;led_count; i++) {
			led_cdev = &amp;led-&gt;pcdev_group[i].r.cdev;
			mutex_lock(&amp;led_cdev-&gt;led_access);
			sunxi_set_led_brightness(led_cdev, value);
			mutex_unlock(&amp;led_cdev-&gt;led_access);
		}
	} else if (channel%3 == 1) {
		for (i = 0; i &lt; led-&gt;led_count; i++) {
			led_cdev = &amp;led-&gt;pcdev_group[i].g.cdev;
			mutex_lock(&amp;led_cdev-&gt;led_access);
			sunxi_set_led_brightness(led_cdev, value);
			mutex_unlock(&amp;led_cdev-&gt;led_access);
		}
	} else {
		for (i = 0; i &lt; led-&gt;led_count; i++) {
			led_cdev = &amp;led-&gt;pcdev_group[i].b.cdev;
			mutex_lock(&amp;led_cdev-&gt;led_access);
			sunxi_set_led_brightness(led_cdev, value);
			mutex_unlock(&amp;led_cdev-&gt;led_access);
		}
	}
}

static ssize_t led_show(struct class *class,
			struct class_attribute *attr,
			char *buf)
{
	struct sunxi_led *led = sunxi_led_global;

	sunxi_led_set_all(led, 0, 0);
	sunxi_led_set_all(led, 1, 0);
	sunxi_led_set_all(led, 2, 0);

	sunxi_led_set_all(led, 0, 20);
	msleep(500);
	sunxi_led_set_all(led, 1, 20);
	msleep(500);
	sunxi_led_set_all(led, 2, 20);
	msleep(500);

	sunxi_led_set_all(led, 0, 0);
	sunxi_led_set_all(led, 1, 0);
	sunxi_led_set_all(led, 2, 0);

	return 0;
}

static struct class_attribute led_class_attrs[] = {
	__ATTR(light, 0644, led_show, NULL),
	//__ATTR_NULL,
};

static void led_node_init(void)
{
	int i;
	int err;
	/* sys/class/led/xxx */
	for (i = 0; i &lt; ARRAY_SIZE(led_class_attrs); i++) {
		err = class_create_file(led_class, &amp;led_class_attrs[i]);
		if (err) {
			LED_ERR("class_create_file() failed!\n");
			while (i--)
				class_remove_file(led_class, &amp;led_class_attrs[i]);
			class_destroy(led_class);
			led_class = NULL;
		}
	}
}

static int sunxi_led_probe(struct platform_device *pdev)
{
	int err;
	struct sunxi_led *led;
	struct device *dev = &amp;pdev-&gt;dev;
	struct resource *mem_res = NULL;
	int ret;

	dprintk(DEBUG_INIT, "start\n");

	led = devm_kzalloc(dev, sizeof(struct sunxi_led), GFP_KERNEL);
	if (!led)
		return -ENOMEM;

	sunxi_led_global = led;

	platform_set_drvdata(pdev, led);
	led-&gt;dev = dev;

	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (mem_res == NULL) {
		LED_ERR("failed to get MEM res\n");
		ret = -ENXIO;
		goto emem;
	}

	if (!request_mem_region(mem_res-&gt;start, resource_size(mem_res),
				mem_res-&gt;name)) {
		LED_ERR("failed to request mem region\n");
		ret = -EINVAL;
		goto emem;
	}

	led-&gt;iomem_reg_base = ioremap(mem_res-&gt;start, resource_size(mem_res));
	if (!led-&gt;iomem_reg_base) {
		ret = -EIO;
		goto eiomap;
	}
	led-&gt;res = mem_res;

	led-&gt;output_mode.str = devm_kzalloc(dev, 3, GFP_KERNEL);
	if (!led-&gt;output_mode.str) {
		ret = -ENOMEM;
		goto ezalloc_str;
	}

	sunxi_get_para_of_property(led);

	err = led_regulator_request(led);
	if (err &lt; 0) {
		LED_ERR("request regulator failed!\n");
		ret = err;
		goto eregulator;
	}

	err = sunxi_register_led_classdev(led);
	if (err) {
		LED_ERR("failed to register led classdev\n");
		ret = err;
		goto eclassdev;
	}

	sunxi_ledc_set_time(led);

	led-&gt;reset = devm_reset_control_get(&amp;pdev-&gt;dev, NULL);
	if (IS_ERR(led-&gt;reset)) {
		LED_ERR("get reset clk error\n");
		return -EINVAL;
	}
	ret = reset_control_deassert(led-&gt;reset);
	if (ret) {
		LED_ERR("deassert clk error, ret:%d\n", ret);
		return ret;
	}

	sunxi_clk_init(led);

	init_waitqueue_head(&amp;led-&gt;wait);

	err = sunxi_ledc_irq_init(led);
	if (err) {
		LED_ERR("failed to init irq\n");
		ret = err;
		goto eirq;
	}

	sunxi_ledc_pinctrl_init(led);

#ifdef CONFIG_DEBUG_FS
	sunxi_led_create_debugfs(led);
#endif /* CONFIG_DEBUG_FS */

	led_class = class_create(THIS_MODULE, "led");
	if (IS_ERR(led_class)) {
		LED_ERR("class_register err\n");
		class_destroy(led_class);
		ret = -EFAULT;
		goto eclass;
	}
	led_node_init();
	dprintk(DEBUG_INIT, "finish\n");
	return 0;

eclass:
#ifdef CONFIG_DEBUG_FS
	sunxi_led_remove_debugfs(led);
#endif /* CONFIG_DEBUG_FS */

	sunxi_ledc_irq_deinit(led);

eirq:
	sunxi_unregister_led_classdev(led);
	sunxi_clk_deinit(led);

eclassdev:
	led_regulator_release(led);

eregulator:
	kfree(led-&gt;output_mode.str);

ezalloc_str:
	iounmap(led-&gt;iomem_reg_base);
	led-&gt;iomem_reg_base = NULL;

eiomap:
	release_mem_region(mem_res-&gt;start, resource_size(mem_res));

emem:
	kfree(led);
	return ret;
}

static int sunxi_led_remove(struct platform_device *pdev)
{
	struct sunxi_led *led = platform_get_drvdata(pdev);

	class_destroy(led_class);

#ifdef CONFIG_DEBUG_FS
	sunxi_led_remove_debugfs(led);
#endif /* CONFIG_DEBUG_FS */

	if (led-&gt;dma_chan) {
		dmaengine_terminate_all(led-&gt;dma_chan);
		dma_release_channel(led-&gt;dma_chan);
		led-&gt;dma_chan = NULL;
	}

	sunxi_ledc_irq_deinit(led);

	sunxi_unregister_led_classdev(led);
	sunxi_clk_deinit(led);

	led_regulator_release(led);

	kfree(led-&gt;output_mode.str);
	led-&gt;output_mode.str = NULL;

	iounmap(led-&gt;iomem_reg_base);
	led-&gt;iomem_reg_base = NULL;

	release_mem_region(led-&gt;res-&gt;start, resource_size(led-&gt;res));

	kfree(led);
	led = NULL;

	dprintk(DEBUG_INIT, "finish\n");
	return 0;
}

#if IS_ENABLED(CONFIG_PM)
static inline void sunxi_led_save_regs(struct sunxi_led *led)
{
	int i;

	for (i = 0; i &lt; ARRAY_SIZE(sunxi_led_regs_offset); i++)
		led-&gt;regs_backup[i] = readl(led-&gt;iomem_reg_base + sunxi_led_regs_offset[i]);
}

static inline void sunxi_led_restore_regs(struct sunxi_led *led)
{
	int i;

	for (i = 0; i &lt; ARRAY_SIZE(sunxi_led_regs_offset); i++)
		writel(led-&gt;regs_backup[i], led-&gt;iomem_reg_base + sunxi_led_regs_offset[i]);
}

static void sunxi_led_enable_irq(struct sunxi_led *led)
{
	enable_irq(led-&gt;irqnum);
}

static void sunxi_led_disable_irq(struct sunxi_led *led)
{
	disable_irq_nosync(led-&gt;irqnum);
}

static int sunxi_led_gpio_state_select(struct sunxi_led *led, char *name)
{
	int err;
	struct pinctrl_state *pctrl_state;

	pctrl_state = pinctrl_lookup_state(led-&gt;pctrl, name);
	if (IS_ERR(pctrl_state)) {
		dev_err(led-&gt;dev, "pinctrl_lookup_state(%s) failed! return %p\n",
				name, pctrl_state);
		return PTR_ERR(pctrl_state);
	}

	err = pinctrl_select_state(led-&gt;pctrl, pctrl_state);
	if (err &lt; 0) {
		dev_err(led-&gt;dev, "pinctrl_select_state(%s) failed! return %d\n",
				name, err);
		return err;
	}

	return 0;
}

static void sunxi_led_enable_clk(struct sunxi_led *led)
{
	clk_prepare_enable(led-&gt;clk_ledc);
	clk_prepare_enable(led-&gt;clk_cpuapb);
}

static void sunxi_led_disable_clk(struct sunxi_led *led)
{
	clk_disable_unprepare(led-&gt;clk_cpuapb);
	clk_disable_unprepare(led-&gt;clk_ledc);
}

static int sunxi_led_power_on(struct sunxi_led *led)
{
	int err;

	if (led-&gt;regulator == NULL)
		return 0;

	err = regulator_enable(led-&gt;regulator);
	if (err) {
		dev_err(led-&gt;dev, "enable regulator %s failed!\n", led-&gt;regulator_id);
		return err;
	}
	return 0;
}

static int sunxi_led_power_off(struct sunxi_led *led)
{
	int err;

	if (led-&gt;regulator == NULL)
		return 0;

	err = regulator_disable(led-&gt;regulator);
	if (err) {
		dev_err(led-&gt;dev, "disable regulator %s failed!\n", led-&gt;regulator_id);
		return err;
	}
	return 0;
}

static int sunxi_led_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct sunxi_led *led = platform_get_drvdata(pdev);

	dev_dbg(led-&gt;dev, "[%s] enter standby\n", __func__);

	sunxi_led_disable_irq(led);

	sunxi_led_save_regs(led);

	sunxi_led_gpio_state_select(led, PINCTRL_STATE_SLEEP);

	sunxi_led_disable_clk(led);

	reset_control_assert(led-&gt;reset);

	sunxi_led_power_off(led);

	return 0;
}

static int sunxi_led_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct sunxi_led *led = platform_get_drvdata(pdev);

	dev_dbg(led-&gt;dev, "[%s] return from standby\n", __func__);

	sunxi_led_power_on(led);

	reset_control_deassert(led-&gt;reset);

	sunxi_led_enable_clk(led);

	sunxi_led_gpio_state_select(led, PINCTRL_STATE_DEFAULT);

	sunxi_led_restore_regs(led);

	sunxi_led_enable_irq(led);

	return 0;
}

static const struct dev_pm_ops sunxi_led_pm_ops = {
	.suspend = sunxi_led_suspend,
	.resume = sunxi_led_resume,
};

#define SUNXI_LED_PM_OPS (&amp;sunxi_led_pm_ops)
#endif

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",
		.owner	= THIS_MODULE,
#if IS_ENABLED(CONFIG_PM)
		.pm	= SUNXI_LED_PM_OPS,
#endif
		.of_match_table = sunxi_led_dt_ids,
	},
};

module_platform_driver(sunxi_led_driver);
module_param_named(debug, debug_mask, int, 0664);

MODULE_ALIAS("sunxi leds dirver");
MODULE_ALIAS("platform : leds dirver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.2.3");
MODULE_AUTHOR("Albert Yu &lt;yuxyun@allwinnertech.com&gt;");
MODULE_AUTHOR("liuyu &lt;SWCliuyus@allwinnertech.com&gt;");
MODULE_DESCRIPTION("Allwinner ledc-controller driver");
</code></pre>
<p dir="auto">lichee/linux-5.4/drivers/leds/leds-sunxi.h</p>
<pre><code>/*
 * Copyright (C) 2018 Allwinner Technology Limited. All rights reserved.
 * Albert Yu &lt;yuxyun@allwinnertech.com&gt;
 *
 * 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 &lt;linux/device.h&gt;
#include &lt;linux/list.h&gt;
#include &lt;linux/mutex.h&gt;
#include &lt;linux/rwsem.h&gt;
#include &lt;linux/spinlock.h&gt;
#include &lt;linux/timer.h&gt;
#include &lt;linux/workqueue.h&gt;

#define HEXADECIMAL	(0x10)
#define REG_INTERVAL	(0x04)
#define REG_CL		(0x0c)

#define RESULT_COMPLETE	1
#define RESULT_ERR	2

#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 84
#define SUNXI_RESET_TIME_MAX_NS 327000

#define SUNXI_T1H_MIN_NS 84
#define SUNXI_T1H_MAX_NS 2560

#define SUNXI_T1L_MIN_NS 84
#define SUNXI_T1L_MAX_NS 1280

#define SUNXI_T0H_MIN_NS 84
#define SUNXI_T0H_MAX_NS 1280

#define SUNXI_T0L_MIN_NS 84
#define SUNXI_T0L_MAX_NS 2560

#define SUNXI_WAIT_TIME0_MIN_NS 84
#define SUNXI_WAIT_TIME0_MAX_NS 10000

#define SUNXI_WAIT_TIME1_MIN_NS 84
#define SUNXI_WAIT_TIME1_MAX_NS 85000000000

#define SUNXI_WAIT_DATA_TIME_MIN_NS 84
#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 &lt;&lt; 6,
	SUNXI_OUTPUT_GBR = 1 &lt;&lt; 6,
	SUNXI_OUTPUT_RGB = 2 &lt;&lt; 6,
	SUNXI_OUTPUT_RBG = 3 &lt;&lt; 6,
	SUNXI_OUTPUT_BGR = 4 &lt;&lt; 6,
	SUNXI_OUTPUT_BRG = 5 &lt;&lt; 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
};

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 &lt;&lt; 0),
	LEDC_FIFO_CPUREQ_INT_EN      = (1 &lt;&lt; 1),
	LEDC_WAITDATA_TIMEOUT_INT_EN = (1 &lt;&lt; 3),
	LEDC_FIFO_OVERFLOW_INT_EN    = (1 &lt;&lt; 4),
	LEDC_GLOBAL_INT_EN           = (1 &lt;&lt; 5),
};

enum sunxi_ledc_irq_status_reg {
	LEDC_TRANS_FINISH_INT     = (1 &lt;&lt; 0),
	LEDC_FIFO_CPUREQ_INT      = (1 &lt;&lt; 1),
	LEDC_WAITDATA_TIMEOUT_INT = (1 &lt;&lt; 3),
	LEDC_FIFO_OVERFLOW_INT    = (1 &lt;&lt; 4),
	LEDC_FIFO_FULL            = (1 &lt;&lt; 16),
	LEDC_FIFO_EMPTY           = (1 &lt;&lt; 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;
};

static u32 sunxi_led_regs_offset[] = {
	LEDC_CTRL_REG_OFFSET,
	LED_RESET_TIMING_CTRL_REG_OFFSET,
	LED_T01_TIMING_CTRL_REG_OFFSET,
	LEDC_WAIT_TIME0_CTRL_REG,
	LEDC_WAIT_TIME1_CTRL_REG,
	LEDC_INT_CTRL_REG_OFFSET,
#ifndef SUNXI_FPGA_LEDC
	LEDC_DATA_FINISH_CNT_REG_OFFSET,
#endif
};

struct sunxi_led {
	u32 reset_ns;
	u32 t1h_ns;
	u32 t1l_ns;
	u32 t0h_ns;
	u32 t0l_ns;
	u32 wait_time0_ns;
	unsigned long long wait_time1_ns;
	u32 wait_data_time_ns;
	u32 irqnum;
	u32 led_count;
	u32 *data;
	u32 length;
	u8 result;
	spinlock_t lock;
	struct device *dev;
	dma_addr_t src_dma;
	struct dma_chan *dma_chan;
	wait_queue_head_t wait;
	struct timespec64 start_time;
	struct clk *clk_ledc;
	struct clk *clk_cpuapb;
	struct pinctrl *pctrl;
	void __iomem *iomem_reg_base;
	struct resource	*res;
	struct sunxi_ledc_output_mode output_mode;
	struct sunxi_led_classdev_group *pcdev_group;
	struct dentry *debugfs_dir;
	char regulator_id[16];
	struct regulator *regulator;
	struct reset_control *reset;
	u32 regs_backup[ARRAY_SIZE(sunxi_led_regs_offset)];
};

enum {
	DEBUG_INIT    = 1U &lt;&lt; 0,
	DEBUG_SUSPEND = 1U &lt;&lt; 1,
	DEBUG_INFO    = 1U &lt;&lt; 2,
	DEBUG_INFO1   = 1U &lt;&lt; 3,
	DEBUG_INFO2   = 1U &lt;&lt; 4,
};

#endif /* __LINUX_LEDS_SUNXI_H */
</code></pre>
<p dir="auto">kernel/linux-5.4/arch/riscv/boot/dts/sunxi/sun20iw1p1.dtsi</p>
<pre><code>		ledc: ledc@2008000 {
			#address-cells = &lt;1&gt;;
			#size-cells = &lt;0&gt;;
			compatible = "allwinner,sunxi-leds";
			reg = &lt;0x0 0x02008000 0x0 0x400&gt;;
			interrupts-extended = &lt;&amp;plic0 36 IRQ_TYPE_LEVEL_HIGH&gt;;
			interrupt-names = "ledcirq";
			clocks = &lt;&amp;ccu CLK_LEDC&gt;, &lt;&amp;ccu CLK_BUS_LEDC&gt;;
			clock-names = "clk_ledc", "clk_cpuapb";
			dmas = &lt;&amp;dma 42&gt;, &lt;&amp;dma 42&gt;;
			dma-names = "rx", "tx";
			resets = &lt;&amp;ccu RST_BUS_LEDC&gt;;
			reset-names = "ledc_reset";
			status = "disable";
		};
</code></pre>
]]></description><link>https://bbs.aw-ol.com/post/21341</link><guid isPermaLink="true">https://bbs.aw-ol.com/post/21341</guid><dc:creator><![CDATA[awwwwa]]></dc:creator><pubDate>Fri, 23 Feb 2024 01:10:06 GMT</pubDate></item></channel></rss>