From 40b7424975e8868079003fda0824b7febbc7acbc Mon Sep 17 00:00:00 2001
From: Alan <Alan>
Date: Sat, 20 May 2023 17:26:21 +0800
Subject: [PATCH 11/13] Add: ws2812 RGB driver for allwinner H616

---
 drivers/leds/rgb/Kconfig       |   7 +
 drivers/leds/rgb/Makefile      |   1 +
 drivers/leds/rgb/leds-ws2812.c | 230 +++++++++++++++++++++++++++++++++
 3 files changed, 238 insertions(+)
 create mode 100644 drivers/leds/rgb/leds-ws2812.c

diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 204cf470beae..b3f78f0775ea 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -27,3 +27,10 @@ config LEDS_QCOM_LPG
 	  If compiled as a module, the module will be named leds-qcom-lpg.
 
 endif # LEDS_CLASS_MULTICOLOR
+
+config LEDS_WS2812
+	tristate "WS2812 RGB support for allwinner H616"
+	depends on PINCTRL_SUN50I_H616
+
+	help
+	  Say Y here if you want to use the WS2812.
\ No newline at end of file
diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
index 0675bc0f6e18..16bcdbd71150 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -2,3 +2,4 @@
 
 obj-$(CONFIG_LEDS_PWM_MULTICOLOR)	+= leds-pwm-multicolor.o
 obj-$(CONFIG_LEDS_QCOM_LPG)		+= leds-qcom-lpg.o
+obj-$(CONFIG_LEDS_WS2812)		+= leds-ws2812.o
diff --git a/drivers/leds/rgb/leds-ws2812.c b/drivers/leds/rgb/leds-ws2812.c
new file mode 100644
index 000000000000..a89030fe815e
--- /dev/null
+++ b/drivers/leds/rgb/leds-ws2812.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023, The Linux Foundation. All rights reserved.
+ */
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <asm/uaccess.h>
+#include <linux/moduleparam.h>
+#include <linux/ioctl.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/rbtree.h>
+#include <linux/ktime.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+#include <linux/time.h>
+#include <linux/hrtimer.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+
+#define GPIO_BASE 0x0300B000
+#define GPIO_DAT_OFFSET(n) ((n)*0x0024 + 0x10)
+
+static uint32_t ws2812_pin = 0;
+static volatile uint32_t *ws2812_gpio_port;
+static volatile uint32_t ws2812_gpio_bit;
+static volatile uint32_t ws2812_set_val = 0;
+static volatile uint32_t ws2812_reset_val = 0;
+
+DEFINE_SPINLOCK(lock);
+
+// ws2812 reset
+static void ws2812_rst(void)
+{
+	*ws2812_gpio_port &= ~ws2812_gpio_bit;
+	udelay(200);// RES low voltage time, Above 50µs
+}
+
+static void ws2812_Write_24Bits(uint32_t grb)
+{
+	uint8_t i;
+	for (i = 0; i < 24; i++)
+	{
+		if (grb & 0x800000)
+		{
+			// loop for delay about 700ns
+			*ws2812_gpio_port = ws2812_set_val;
+			*ws2812_gpio_port = ws2812_set_val;
+			*ws2812_gpio_port = ws2812_set_val;
+			*ws2812_gpio_port = ws2812_set_val;
+			*ws2812_gpio_port = ws2812_set_val;
+			*ws2812_gpio_port = ws2812_set_val;
+			*ws2812_gpio_port = ws2812_set_val;
+			// loop for delay about 600ns
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+		}
+		else
+		{
+			// loop for delay about 200ns
+			*ws2812_gpio_port = ws2812_set_val;
+			*ws2812_gpio_port = ws2812_set_val;
+			// loop for delay about 800ns
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+			*ws2812_gpio_port = ws2812_reset_val;
+		}
+		grb <<= 1;
+	}
+}
+
+static void ws2812_write_array(uint32_t *rgb, uint32_t cnt)
+{
+	uint32_t i = 0;
+	unsigned long flags;
+
+	for (i = 0; i < cnt; i++)
+	{
+		// rgb -> grb
+		rgb[i] = (((rgb[i] >> 16) & 0xff) << 8) | (((rgb[i] >> 8) & 0xff) << 16) | ((rgb[i]) & 0xff);
+	}
+
+	spin_lock_irqsave(&lock, flags);
+	ws2812_set_val = *ws2812_gpio_port | ws2812_gpio_bit;
+	ws2812_reset_val = *ws2812_gpio_port & (~ws2812_gpio_bit);
+	ws2812_rst();
+	for (i = 0; i < cnt; i++)
+	{
+		ws2812_Write_24Bits(rgb[i]);
+	}
+	spin_unlock_irqrestore(&lock, flags);
+}
+
+ssize_t ws2812_read(struct file *file, char __user *user, size_t bytesize, loff_t *this_loff_t)
+{
+	return 0;
+}
+
+ssize_t ws2812_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	uint32_t rgb[255];
+	unsigned long ret = 0;
+
+	if (count > 255 * 4) count = 255 * 4;
+	ret = copy_from_user(&rgb[0], user_buf, count);
+	if (ret < 0)
+	{
+		printk("copy_from_user fail!!!\n");
+		return -1;
+	}
+
+	ws2812_write_array((uint32_t *)rgb, count / 4);
+
+	return 0;
+}
+
+int ws2812_open(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+int ws2812_close(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static struct file_operations ws2812_ops = {
+	.owner = THIS_MODULE,
+	.open = ws2812_open,
+	.release = ws2812_close,
+	.write = ws2812_write,
+};
+
+static struct miscdevice ws2812_misc_dev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "ws2812-led",
+	.fops = &ws2812_ops,
+};
+
+static int ws2812_probe(struct platform_device *pdev)
+{
+	int ret;
+	enum of_gpio_flags flag;
+	struct device_node *ws2812_gpio_node = pdev->dev.of_node;
+	uint32_t rgb_cnt = 0;
+	uint32_t rgb[255];
+
+	of_property_read_u32(ws2812_gpio_node, "rgb_cnt", &rgb_cnt);
+	if (rgb_cnt > 255)
+		rgb_cnt = 255;
+
+	of_property_read_u32_array(ws2812_gpio_node, "rgb_value", rgb, rgb_cnt);
+	ws2812_pin = of_get_named_gpio_flags(ws2812_gpio_node, "gpios", 0, &flag);
+	if (!gpio_is_valid(ws2812_pin))
+	{
+		printk(KERN_ERR "ws2812: gpio: %d is invalid\n", ws2812_pin);
+		return -ENODEV;
+	}
+
+	ws2812_gpio_port = ioremap(GPIO_BASE + GPIO_DAT_OFFSET((ws2812_pin >> 5)), 4);
+	ws2812_gpio_bit = 1 << (ws2812_pin & 0x001F);
+
+	if (gpio_request(ws2812_pin, "ws2812-gpio"))
+	{
+		printk(KERN_ERR "ws2812: gpio %d request failed!\n", ws2812_pin);
+		gpio_free(ws2812_pin);
+		return -ENODEV;
+	}
+	gpio_direction_output(ws2812_pin, 0);
+
+	ret = misc_register(&ws2812_misc_dev);
+	msleep(50);
+
+	ws2812_write_array(rgb, rgb_cnt);
+
+	return 0;
+}
+
+static int ws2812_remove(struct platform_device *pdev)
+{
+	misc_deregister(&ws2812_misc_dev);
+	gpio_free(ws2812_pin);
+
+	return 0;
+}
+
+static const struct of_device_id ws2812_of_match[] = {
+	{.compatible = "rgb-ws2812"},
+	{/* sentinel */}};
+
+MODULE_DEVICE_TABLE(of, ws2812_of_match);
+
+static struct platform_driver ws2812_driver = {
+	.probe		= ws2812_probe,
+	.remove		= ws2812_remove,
+	.driver		= {
+		.name	= "ws2812_ctl",
+		.of_match_table = ws2812_of_match,
+	},
+};
+
+module_platform_driver(ws2812_driver);
+
+MODULE_AUTHOR("MacLodge, Alan Ma <tech@biqu3d.com>");
+MODULE_DESCRIPTION("WS2812 RGB driver for Allwinner");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ws2812_ctl");
-- 
2.34.1

