/*
 * Copyright (c) 2024 Javad Rahimipetroudi <javad.rahimipetroudi@mind.be>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ti_tlc59731

/**
 * @file
 * @brief LED driver for the TLC59731 LED driver.
 *
 * TLC59731 is a 3-Channel, 8-Bit, PWM LED Driver
 * With Single-Wire Interface (EasySet)
 *
 * The EasySet protocol is based on short pulses and the time between
 * them. At least one pulse must be sent every T_CYCLE, which can be
 * between 1.67us and 50us. We want to go as fast as possible, but
 * delays under 1us don't work very well, so we settle on 5us for the
 * cycle time.
 * A pulse must be high for at least 14ns. In practice, turning a GPIO on
 * and immediately off again already takes longer than that, so no delay
 * is needed there.
 * A zero is represented by no additional pulses within a cycle.
 * A one is represented by an additional pulse between 275ns and 2.5us
 * (half a cycle) after the first one. We need at least some delay to get to
 * 275ns, but because of the limited granularity of k_busy_wait we use a
 * full 1us. After the pulse, we wait an additional T_CYCLE_1 to complete
 * the cycle. This time can be slightly shorter because the second pulse
 * already closes the cycle.
 * Finally we need to keep the line low for T_H0 to complete the address
 * for a single chip, and T_H1 to complete the write for all chips.
 */

#include <zephyr/drivers/led_strip.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tlc59731, CONFIG_LED_STRIP_LOG_LEVEL);

/* Pulse timing */
#define TLC59731_DELAY     0x01 /* us */
#define TLC59731_T_CYCLE_0 0x04 /* us */
#define TLC59731_T_CYCLE_1 0x01 /* us */
#define TLC59731_T_H0      (4 * TLC59731_T_CYCLE_0)
#define TLC59731_T_H1      (8 * TLC59731_T_CYCLE_0)
/* Threshould levels */
#define TLC59731_HIGH      0x01
#define TLC59731_LOW       0x00

/* Write command */
#define TLC59731_WR 0x3A

struct tlc59731_cfg {
	struct gpio_dt_spec sdi_gpio;
	size_t length;
};

static inline int rgb_pulse(const struct gpio_dt_spec *led_dev)
{
	int fret = 0;

	fret = gpio_pin_set_dt(led_dev, TLC59731_HIGH);
	if (fret != 0) {
		return fret;
	}

	fret = gpio_pin_set_dt(led_dev, TLC59731_LOW);
	if (fret != 0) {
		return fret;
	}

	return fret;
}

static int rgb_write_bit(const struct gpio_dt_spec *led_dev, uint8_t data)
{
	rgb_pulse(led_dev);

	k_busy_wait(TLC59731_DELAY);

	if (data) {
		rgb_pulse(led_dev);
		k_busy_wait(TLC59731_T_CYCLE_1);
	} else {
		k_busy_wait(TLC59731_T_CYCLE_0);
	}

	return 0;
}

static int rgb_write_data(const struct gpio_dt_spec *led_dev, uint8_t data)
{
	int8_t idx = 7;

	while (idx >= 0) {
		rgb_write_bit(led_dev, data & BIT((idx--)));
	}

	return 0;
}

static int tlc59731_led_set_color(const struct device *dev, struct led_rgb *pixel)
{

	const struct tlc59731_cfg *tlc_conf = dev->config;
	const struct gpio_dt_spec *led_gpio = &tlc_conf->sdi_gpio;

	rgb_write_data(led_gpio, TLC59731_WR);
	rgb_write_data(led_gpio, pixel->r);
	rgb_write_data(led_gpio, pixel->g);
	rgb_write_data(led_gpio, pixel->b);

	return 0;
}

static int tlc59731_gpio_update_rgb(const struct device *dev, struct led_rgb *pixels,
				    size_t num_pixels)
{
	size_t i;
	int err = 0;

	for (i = 0; i < num_pixels; i++) {
		err = tlc59731_led_set_color(dev, &pixels[i]);
		if (err) {
			break;
		}
	}

	return err;
}

static size_t tlc59731_length(const struct device *dev)
{
	const struct tlc59731_cfg *config = dev->config;

	return config->length;
}

static DEVICE_API(led_strip, tlc59731_gpio_api) = {
	.update_rgb = tlc59731_gpio_update_rgb,
	.length = tlc59731_length,
};

static int tlc59731_gpio_init(const struct device *dev)
{
	const struct tlc59731_cfg *tlc_conf = dev->config;
	const struct gpio_dt_spec *led = &tlc_conf->sdi_gpio;
	int err = 0;

	if (!device_is_ready(led->port)) {
		LOG_ERR("%s: no LEDs found (DT child nodes missing)", dev->name);
		err = -ENODEV;
		goto scape;
	}

	err = gpio_pin_configure_dt(led, GPIO_OUTPUT_ACTIVE);
	if (err < 0) {
		LOG_ERR("%s: Unable to setup SDI port", dev->name);
		err = -EIO;
		goto scape;
	}

	err = gpio_pin_set_dt(led, TLC59731_LOW);
	if (err < 0) {
		LOG_ERR("%s: Unable to set the SDI-GPIO)", dev->name);
		err = -EIO;
		goto scape;
	}

	gpio_pin_set_dt(led, TLC59731_HIGH);
	gpio_pin_set_dt(led, TLC59731_LOW);

	k_busy_wait((TLC59731_DELAY + TLC59731_T_CYCLE_0));
scape:
	return err;
}

#define TLC59731_DEVICE(i)                                                                         \
	static struct tlc59731_cfg tlc59731_cfg_##i = {                                            \
		.sdi_gpio = GPIO_DT_SPEC_INST_GET(i, gpios),                                       \
		.length = DT_INST_PROP(i, chain_length),                                           \
	};                                                                                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(i, tlc59731_gpio_init, NULL, NULL, &tlc59731_cfg_##i, POST_KERNEL,   \
			      CONFIG_LED_STRIP_INIT_PRIORITY, &tlc59731_gpio_api);
DT_INST_FOREACH_STATUS_OKAY(TLC59731_DEVICE)
