//https://cdn.sparkfun.com/datasheets/Components/LED/APA102C.pdf
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#define COMPAT "shiji,apa102c"
#define DEVICE_NAME "apa102c"
#define PREFIX KERN_INFO DEVICE_NAME ": "
#define DEBUG(...) if(debug) pr_info(PREFIX __VA_ARGS__);
#define LEDS   9
#define MARKER 0xe0
#define OFF    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
#define TEST   "\x80\x00\x00\x01\x80\x40\x00\x01\x80\x80\x00\x01\x00\x80\x00\x01\x00\x00\x80\x01\x80\x00\x80\x01\x00\x00\x00\x00\x80\x80\x80\x01\x00\x00\x00\x00"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mochi the Dog");
MODULE_DESCRIPTION("A trivial cdev interface to a series of APA102C LEDs.");
MODULE_VERSION("0.1");

static int major;
static int debug = 0;
static struct gpio_desc *dat;
static struct gpio_desc *clk;
static struct class *device_class;
static struct device *device;

module_param(debug, int, 0600);

void            send(char);
void            send_n(char *, int);
void            send_all(const char *);
static int      open(struct inode *, struct file *);
static int      release(struct inode *, struct file *);
static ssize_t  read(struct file *, char *, size_t, loff_t *);
static ssize_t  write(struct file *, const char *, size_t, loff_t *);
static int      probe(struct platform_device *);
static int      remove(struct platform_device *);

static struct file_operations file_functions = {
    .read    = read,
    .write   = write,
    .open    = open,
    .release = release,
    };

static const struct of_device_id of_match[] = {
    { .compatible = COMPAT, },
    {},
    };

MODULE_DEVICE_TABLE(of, of_match);

static struct platform_driver pdriver = {
    .probe  = probe,
    .remove = remove,
    .driver = {
        .name = DEVICE_NAME,
        .of_match_table = of_match,
        },
    };

module_platform_driver(pdriver);

void send(char byte) {
    int i;
    for(i = 0; i < 8; i++) {
        gpiod_set_value(dat, byte & 0x80);
        gpiod_set_value(clk, 1);
        //usleep_range(50, 100);
        gpiod_set_value(clk, 0);
        byte <<= 1;
        }
    }

void send_n(char *bytes, int n) {
    int i ;
    for(i = 0; i < n; i++) send(bytes[i]);
    }

void send_all(const char *bytes) {
    int i;
    // Start frame. 32 low bits.
    send_n("\x00\x00\x00\x00", 4);
    for(i = 0; i < LEDS; i++) {
        // MARKER | brighness (5 bits).
        send(MARKER | *(bytes + 3));
        // blue
        send(*(bytes + 2));
        // green
        send(*(bytes + 1));
        // red
        send(*bytes);
        // Move up 4 bytes.
        bytes += 4;
        }
    // Send frame. 32 high bits.
    send_n("\xff\xff\xff\xff", 4);
    }

static int probe(struct platform_device *pdev) {
    DEBUG("Initializing.");
    major = register_chrdev(0, DEVICE_NAME, &file_functions);
    DEBUG("Device major: %d", major);
    dat = gpiod_get(&pdev->dev, "dat", GPIOD_OUT_LOW);
    clk = gpiod_get(&pdev->dev, "clk", GPIOD_OUT_LOW);
    DEBUG("GPIO dat: %d", desc_to_gpio(dat));
    DEBUG("GPIO clk: %d", desc_to_gpio(clk));
    device_class = class_create(THIS_MODULE, DEVICE_NAME);
    device = device_create(device_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    DEBUG("Displaying test pattern.");
    send_all(TEST);
    return 0;
    }

static int remove(struct platform_device *pdev) {
    DEBUG("Removing device.\n");
    send_all(OFF);
    gpiod_put(dat);
    gpiod_put(clk);
    unregister_chrdev(major, DEVICE_NAME);
    device_destroy(device_class, MKDEV(major, 0));
    class_destroy(device_class);
    return 0;
    }

static int open(struct inode *inode, struct file *file) {
    DEBUG(".open.\n");
    return 0;
    }

static int release(struct inode *inode, struct file *file) {
    DEBUG(".release.\n");
    return 0;
    }

static ssize_t read(struct file *file, char *buffer, size_t length, loff_t *offset) {
    DEBUG(".read.\n");
    return 0;
    }

static ssize_t write(struct file *file, const char *buffer, size_t length, loff_t *offset) {
    DEBUG(".write.\n");
    if(length == LEDS * 4) {
        send_all(buffer);
        }
    else {
        DEBUG("Got %d bytes. Expecting %d.", length, LEDS * 4);
        }
    return length;
    }
