/*
 * fw_app.c
 *
 * Copyright (C) 2019-2020  Sylvain Munaut <tnt@246tNt.com>
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include <no2usb/usb.h>
#include <no2usb/usb_dfu_rt.h>

#include "console.h"
#include "e1.h"
#include "led.h"
#include "misc.h"
#include "mini-printf.h"
#include "spi.h"
#include "usb_e1.h"
#include "utils.h"


extern const struct usb_stack_descriptors app_stack_desc;

static void
serial_no_init()
{
	uint8_t buf[8];
	char *id, *desc;
	int i;

	flash_manuf_id(buf);
	printf("Flash Manufacturer : %s\n", hexstr(buf, 3, true));

	flash_unique_id(buf);
	printf("Flash Unique ID    : %s\n", hexstr(buf, 8, true));

	/* Overwrite descriptor string */
		/* In theory in rodata ... but nothing is ro here */
	id = hexstr(buf, 8, false);
	desc = (char*)app_stack_desc.str[1];
	for (i=0; i<16; i++)
		desc[2 + (i << 1)] = id[i];
}

static void
boot_dfu(void)
{
	/* Force re-enumeration */
	usb_disconnect();

	/* Boot firmware */
	reboot(1);
}

void
usb_dfu_rt_cb_reboot(void)
{
	boot_dfu();
}



static uint8_t
liu_read_reg(unsigned chan, uint8_t reg)
{
        uint8_t cmd = reg | 0x20;
	uint8_t rv;
        struct spi_xfer_chunk xfer[2] = {
                { .data = (void*)&cmd, .len = 1, .read = false, .write = true,  },
                { .data = (void*)&rv,  .len = 1, .read = true,  .write = false, },
        };
        spi_xfer(SPI_CS_LIU(chan), xfer, 2);
	return rv;
}

static void
liu_write_reg(unsigned chan, uint8_t reg, uint8_t val)
{
        uint8_t cmd[2] = { reg, val };
        struct spi_xfer_chunk xfer[2] = {
                { .data = (void*)cmd, .len = 2, .read = false, .write = true,  },
	};
        spi_xfer(SPI_CS_LIU(chan), xfer, 1);
}



#define USB_RT_LIU_REG_WRITE	((1 << 8) | 0x42)
#define USB_RT_LIU_REG_READ	((2 << 8) | 0xc2)


static bool
_liu_reg_write_done_cb(struct usb_xfer *xfer)
{
	struct usb_ctrl_req *req = xfer->cb_ctx;
	unsigned chan = req->wIndex - 0x81;

	liu_write_reg(chan, req->wValue, xfer->data[0]);

	return true;
}

static enum usb_fnd_resp
_liu_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
{
	unsigned chan;

	/* If this a vendor request for the E1 endpoints */
	if (USB_REQ_RCPT(req) != USB_REQ_RCPT_EP)
		return USB_FND_CONTINUE;

	if (req->wIndex < 0x81 || req->wIndex > 0x82)
		return USB_FND_CONTINUE;

	if (USB_REQ_TYPE(req) != USB_REQ_TYPE_VENDOR)
		return USB_FND_CONTINUE;

	chan = req->wIndex - 0x81;

	/* */
	switch (req->wRequestAndType)
	{
	case USB_RT_LIU_REG_WRITE:
		xfer->len = 1;
		xfer->cb_done = _liu_reg_write_done_cb;
		xfer->cb_ctx = req;
		break;

	case USB_RT_LIU_REG_READ:
		xfer->len = 1;
		xfer->data[0] = liu_read_reg(chan, req->wValue);
		break;

	default:
		goto error;
	}

	return USB_FND_SUCCESS;

error:
	return USB_FND_ERROR;
}

static struct usb_fn_drv _liu_drv = {
	.ctrl_req = _liu_ctrl_req,
};



void main()
{
	int cmd = 0;

	/* Init console IO */
	console_init();
	puts("Booting App image..\n");

	/* LED */
	led_init();
	led_state(true);

	/* SPI */
	spi_init();

	/* Enable USB directly */
	serial_no_init();
	usb_init(&app_stack_desc);
	usb_dfu_rt_init();
	usb_register_function_driver(&_liu_drv);
	usb_e1_init();

	usb_connect();
	e1_init();

	/* Main loop */
	while (1)
	{
		/* Prompt ? */
		if (cmd >= 0)
			printf("Command> ");

		/* Poll for command */
		cmd = getchar_nowait();

		if (cmd >= 0) {
			if (cmd > 32 && cmd < 127) {
				putchar(cmd);
				putchar('\r');
				putchar('\n');
			}

			switch (cmd)
			{
			case 'u':
				usb_debug_print();
				break;
			case 'e':
				e1_debug_print(false);
				break;
			case 'E':
				e1_debug_print(true);
				break;
			default:
				break;
			}
		}

		/* USB poll */
		usb_poll();

		/* E1 poll */
		e1_poll();
		usb_e1_run();
	}
}