// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * SPI driver for WorldSemi WS2812B (and compatible) addressable LED strips.
 *
 * Each WS2812B bit is encoded as 3 SPI bits so that the MOSI line reproduces
 * the required high/low timings when the SPI clock is ~2.4 MHz:
 *
 *   WS2812 bit 0  ->  SPI 1 0 0   (short high pulse)
 *   WS2812 bit 1  ->  SPI 1 1 0   (long  high pulse)
 *
 * On Microchip SAMA5D2 the Atmel SPI / Flexcom SPI controller uses DMA for
 * transfers >= 16 bytes (see drivers/spi/spi-atmel.c), so a full strip update
 * is handed to the XDMAC with no CPU bit-banging.
 *
 * Userspace interface: misc character device /dev/ws2812b
 *   write(fd, rgb, n * 3)  - n LEDs as packed RGB bytes (R,G,B,R,G,B,...)
 *   The driver converts to GRB wire order and refreshes the strip.
 *
 * Device tree (example under a SPI controller):
 *   ws2812b@0 {
 *       compatible = "worldsemi,ws2812b-spi";
 *       reg = <0>;
 *       spi-max-frequency = <2400000>;
 *       num-leds = <8>;
 *   };
 *
 * Copyright (C) 2026 Acme Systems / Sergio Tanzilli
 */

#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
#include <linux/uaccess.h>

/* SPI clock for 3-bit encoding (3 * 800 kHz WS2812 bitrate). */
#define WS2812B_SPI_HZ			2400000

/*
 * Reset / latch: line must stay low >= 50 us (WS2812B datasheet).
 * Some clones want ~280 us; 300 us is safe.
 * At 2.4 MHz, one byte is 8/2.4e6 s ≈ 3.33 us → 96 bytes ≈ 320 us.
 */
#define WS2812B_RESET_BYTES		96

/* 3 SPI bytes per colour byte (8 bits * 3 SPI bits). */
#define WS2812B_SPI_BYTES_PER_COLOUR	3
#define WS2812B_COLOURS_PER_LED		3
#define WS2812B_SPI_BYTES_PER_LED	\
	(WS2812B_COLOURS_PER_LED * WS2812B_SPI_BYTES_PER_COLOUR)

#define WS2812B_MAX_LEDS		1024
#define WS2812B_DEFAULT_LEDS		8

/* SPI bit patterns for one WS2812 data bit (3 bits, left-aligned in packing). */
#define WS2812B_CODE_0			0x4	/* 0b100 */
#define WS2812B_CODE_1			0x6	/* 0b110 */

struct ws2812b_priv {
	struct spi_device	*spi;
	struct mutex		lock;
	struct miscdevice	miscdev;
	char			miscname[32];

	u32			num_leds;
	/* Pixel buffer in wire order GRB, num_leds * 3 bytes */
	u8			*pixels;
	/* Encoded SPI stream: data + reset trailer */
	u8			*spi_buf;
	size_t			spi_buf_len;
};

/*
 * Expand one colour byte into 3 SPI bytes (24 SPI bits).
 * WS2812 expects MSB first.
 */
static void ws2812b_encode_byte(u8 *dst, u8 val)
{
	u32 bits = 0;
	int i;

	for (i = 7; i >= 0; i--) {
		bits <<= 3;
		bits |= (val & BIT(i)) ? WS2812B_CODE_1 : WS2812B_CODE_0;
	}

	dst[0] = (bits >> 16) & 0xff;
	dst[1] = (bits >> 8) & 0xff;
	dst[2] = bits & 0xff;
}

static void ws2812b_encode_frame(struct ws2812b_priv *priv)
{
	size_t i;
	u8 *out = priv->spi_buf;

	for (i = 0; i < priv->num_leds * WS2812B_COLOURS_PER_LED; i++) {
		ws2812b_encode_byte(out, priv->pixels[i]);
		out += WS2812B_SPI_BYTES_PER_COLOUR;
	}

	/* Trailing zeros: MOSI stays low → reset / latch */
	memset(out, 0, WS2812B_RESET_BYTES);
}

static int ws2812b_flush(struct ws2812b_priv *priv)
{
	struct spi_transfer xfer = {
		.tx_buf		= priv->spi_buf,
		.len		= priv->spi_buf_len,
		.speed_hz	= WS2812B_SPI_HZ,
		.bits_per_word	= 8,
	};
	struct spi_message msg;

	ws2812b_encode_frame(priv);

	/*
	 * spi_sync() on atmel,at91rm9200-spi uses XDMAC when len >= 16
	 * (DMA_MIN_BYTES in spi-atmel.c). A single LED already expands to
	 * 9 + reset bytes, so all real updates go through DMA.
	 */
	spi_message_init(&msg);
	spi_message_add_tail(&xfer, &msg);

	return spi_sync(priv->spi, &msg);
}

/*
 * Convert packed RGB from userspace into GRB pixel buffer.
 * count is number of complete LEDs in @rgb.
 */
static void ws2812b_store_rgb(struct ws2812b_priv *priv,
			      const u8 *rgb, size_t count)
{
	size_t i;

	for (i = 0; i < count; i++) {
		u8 r = rgb[i * 3 + 0];
		u8 g = rgb[i * 3 + 1];
		u8 b = rgb[i * 3 + 2];

		/* Wire order: G, R, B */
		priv->pixels[i * 3 + 0] = g;
		priv->pixels[i * 3 + 1] = r;
		priv->pixels[i * 3 + 2] = b;
	}

	/* LEDs not present in the write are cleared */
	if (count < priv->num_leds)
		memset(priv->pixels + count * 3, 0,
		       (priv->num_leds - count) * 3);
}

static ssize_t ws2812b_write(struct file *file, const char __user *buf,
			     size_t count, loff_t *ppos)
{
	struct ws2812b_priv *priv = file->private_data;
	size_t max_bytes = priv->num_leds * 3;
	size_t leds;
	u8 *tmp;
	int ret;

	if (count == 0)
		return 0;

	if (count > max_bytes)
		count = max_bytes;

	/* Only whole LEDs */
	leds = count / 3;
	count = leds * 3;
	if (count == 0)
		return -EINVAL;

	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	mutex_lock(&priv->lock);
	ws2812b_store_rgb(priv, tmp, leds);
	ret = ws2812b_flush(priv);
	mutex_unlock(&priv->lock);

	kfree(tmp);

	if (ret)
		return ret;

	return count;
}

static ssize_t ws2812b_read(struct file *file, char __user *buf,
			    size_t count, loff_t *ppos)
{
	struct ws2812b_priv *priv = file->private_data;
	size_t total = priv->num_leds * 3;
	u8 *tmp;
	size_t i;
	ssize_t ret;

	if (*ppos >= total)
		return 0;
	if (count > total - *ppos)
		count = total - *ppos;

	/* Only report whole-LED multiples from current offset */
	if ((*ppos % 3) || (count % 3))
		return -EINVAL;

	tmp = kmalloc(count, GFP_KERNEL);
	if (!tmp)
		return -ENOMEM;

	mutex_lock(&priv->lock);
	for (i = 0; i < count / 3; i++) {
		size_t led = (*ppos / 3) + i;
		/* GRB -> RGB */
		tmp[i * 3 + 0] = priv->pixels[led * 3 + 1];
		tmp[i * 3 + 1] = priv->pixels[led * 3 + 0];
		tmp[i * 3 + 2] = priv->pixels[led * 3 + 2];
	}
	mutex_unlock(&priv->lock);

	if (copy_to_user(buf, tmp, count))
		ret = -EFAULT;
	else {
		*ppos += count;
		ret = count;
	}

	kfree(tmp);
	return ret;
}

static int ws2812b_open(struct inode *inode, struct file *file)
{
	struct miscdevice *m = file->private_data;
	struct ws2812b_priv *priv = container_of(m, struct ws2812b_priv, miscdev);

	file->private_data = priv;
	return 0;
}

static const struct file_operations ws2812b_fops = {
	.owner		= THIS_MODULE,
	.open		= ws2812b_open,
	.write		= ws2812b_write,
	.read		= ws2812b_read,
	.llseek		= default_llseek,
};

static ssize_t num_leds_show(struct device *dev,
			     struct device_attribute *attr, char *buf)
{
	struct miscdevice *m = dev_get_drvdata(dev);
	struct ws2812b_priv *priv =
		container_of(m, struct ws2812b_priv, miscdev);

	return sysfs_emit(buf, "%u\n", priv->num_leds);
}
static DEVICE_ATTR_RO(num_leds);

static struct attribute *ws2812b_attrs[] = {
	&dev_attr_num_leds.attr,
	NULL,
};
ATTRIBUTE_GROUPS(ws2812b);

static int ws2812b_probe(struct spi_device *spi)
{
	struct device *dev = &spi->dev;
	struct ws2812b_priv *priv;
	u32 num_leds = WS2812B_DEFAULT_LEDS;
	int ret;

	if (device_property_read_u32(dev, "num-leds", &num_leds) &&
	    device_property_read_u32(dev, "chain-length", &num_leds))
		dev_info(dev, "num-leds not set, defaulting to %u\n", num_leds);

	if (num_leds == 0 || num_leds > WS2812B_MAX_LEDS) {
		dev_err(dev, "invalid num-leds %u (1..%u)\n",
			num_leds, WS2812B_MAX_LEDS);
		return -EINVAL;
	}

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->spi = spi;
	priv->num_leds = num_leds;
	mutex_init(&priv->lock);

	priv->spi_buf_len = num_leds * WS2812B_SPI_BYTES_PER_LED +
			    WS2812B_RESET_BYTES;

	priv->pixels = devm_kcalloc(dev, num_leds, 3, GFP_KERNEL);
	if (!priv->pixels)
		return -ENOMEM;

	/*
	 * DMA-friendly buffer: spi-atmel maps tx_buf for DMA. Use a plain
	 * kmalloc buffer (DMA-safe on ARM with dma-mapping coherent alloc
	 * not strictly required for streaming map, but word-aligned GFP is fine).
	 */
	priv->spi_buf = devm_kzalloc(dev, priv->spi_buf_len, GFP_KERNEL | GFP_DMA);
	if (!priv->spi_buf)
		return -ENOMEM;

	spi->mode = SPI_MODE_0;
	spi->bits_per_word = 8;
	spi->max_speed_hz = WS2812B_SPI_HZ;
	ret = spi_setup(spi);
	if (ret) {
		dev_err(dev, "spi_setup failed: %d\n", ret);
		return ret;
	}

	/* Clear strip at probe */
	ret = ws2812b_flush(priv);
	if (ret) {
		dev_err(dev, "initial flush failed: %d\n", ret);
		return ret;
	}

	snprintf(priv->miscname, sizeof(priv->miscname), "ws2812b");
	priv->miscdev.minor = MISC_DYNAMIC_MINOR;
	priv->miscdev.name = priv->miscname;
	priv->miscdev.fops = &ws2812b_fops;
	priv->miscdev.parent = dev;
	priv->miscdev.groups = ws2812b_groups;

	ret = misc_register(&priv->miscdev);
	if (ret) {
		dev_err(dev, "misc_register failed: %d\n", ret);
		return ret;
	}

	spi_set_drvdata(spi, priv);

	dev_info(dev,
		 "WS2812B strip ready: %u LEDs, SPI %u Hz, %zu-byte DMA frame, /dev/%s\n",
		 num_leds, WS2812B_SPI_HZ, priv->spi_buf_len, priv->miscname);

	return 0;
}

static void ws2812b_remove(struct spi_device *spi)
{
	struct ws2812b_priv *priv = spi_get_drvdata(spi);

	mutex_lock(&priv->lock);
	memset(priv->pixels, 0, priv->num_leds * 3);
	ws2812b_flush(priv);
	mutex_unlock(&priv->lock);

	misc_deregister(&priv->miscdev);
}

static const struct of_device_id ws2812b_dt_ids[] = {
	{ .compatible = "worldsemi,ws2812b-spi" },
	{ .compatible = "acme,ws2812b-spi" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ws2812b_dt_ids);

static const struct spi_device_id ws2812b_spi_ids[] = {
	{ "ws2812b-spi", 0 },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, ws2812b_spi_ids);

static struct spi_driver ws2812b_driver = {
	.driver = {
		.name		= "ws2812b-spi",
		.of_match_table	= ws2812b_dt_ids,
	},
	.probe	= ws2812b_probe,
	.remove	= ws2812b_remove,
	.id_table = ws2812b_spi_ids,
};
module_spi_driver(ws2812b_driver);

MODULE_AUTHOR("Sergio Tanzilli <tanzilli@acmesystems.it>");
MODULE_DESCRIPTION("WS2812B addressable LED strip over SPI (DMA-friendly)");
MODULE_LICENSE("GPL");
