#include <libopencm3/stm32/f1/rcc.h>
#include <libopencm3/stm32/f1/gpio.h>
#include <libopencm3/stm32/spi.h>
#include <libopencm3/stm32/usart.h>

#include <stdio.h>
#include <errno.h>

void gpio_setup(void)
{
   rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPAEN);

   gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ,
      GPIO_CNF_OUTPUT_PUSHPULL, GPIO7);
}

void usart_setup()
{
   rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPAEN
      | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_USART1EN);

   gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);

   usart_set_baudrate(USART1, 57600);
   usart_set_databits(USART1, 8);
   usart_set_stopbits(USART1, USART_STOPBITS_1);
   usart_set_mode(USART1, USART_MODE_TX);
   usart_set_parity(USART1, USART_PARITY_NONE);
   usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);

   usart_enable(USART1);
}

void spi_setup()
{
   rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_SPI1EN);
   rcc_peripheral_enable_clock(&RCC_APB2ENR,
      RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN);

   gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ,
      GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO5 | GPIO7);
   gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ,
      GPIO_CNF_OUTPUT_PUSHPULL, GPIO4);

   spi_init_master(SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_64, SPI_CR1_CPOL_CLK_TO_1_WHEN_IDLE,
      SPI_CR1_CPHA_CLK_TRANSITION_2, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST);
   spi_enable_software_slave_management(SPI1);
   spi_set_nss_high(SPI1);
   
   spi_enable(SPI1);
}

int _write(int file, char *ptr, int len)
{
   int i;

   if (file == 1) {
      for (i = 0; i < len; i++) {
         usart_send_blocking(USART1, ptr[i]);
      }

      return i;
   }

   errno = EIO;
   return -1;
}

void ncs_set()
{
   gpio_clear(GPIOA, GPIO4);
}

void ncs_clear()
{
   gpio_set(GPIOA, GPIO4);
}

uint8_t adns3080_read(uint8_t addr)
{
   int i;

   ncs_set();

   for (i = 0; i < 200; i++)
      __asm__("nop");

   spi_xfer(SPI1, addr);

   for (i = 0; i < 200; i++)
      __asm__("nop");

   uint8_t val = spi_xfer(SPI1, 0x00);

   for (i = 0; i < 200; i++)
      __asm__("nop");

   ncs_clear();

   return val;
}

void adns3080_write(uint8_t addr, uint8_t val)
{
   int i;

   ncs_set();

   for (i = 0; i < 200; i++)
      __asm__("nop");

   spi_xfer(SPI1, 0x80 | addr);

   for (i = 0; i < 200; i++)
      __asm__("nop");

   spi_xfer(SPI1, val);

   for (i = 0; i < 200; i++)
      __asm__("nop");

   ncs_clear();
}

int main(void)
{
   int i;

   rcc_clock_setup_in_hse_8mhz_out_72mhz();

   setvbuf(stdin, NULL, _IONBF, 0);
   setvbuf(stdout, NULL, _IONBF, 0);

   gpio_setup();
   spi_setup();
   usart_setup();

   ncs_clear();

   for (i = 0; i < 100000; i++)
      __asm__("nop");

   while (true) {
      adns3080_write(0x13, 0x83);

      for (i = 0; i < 20000; i++)
         __asm__("nop");

      usart_send_blocking(USART1, 0xff);
      usart_send_blocking(USART1, 0xff);
      usart_send_blocking(USART1, 0xff);
      usart_send_blocking(USART1, 0xff);

      for (i = 0; i < 900; i++)
         usart_send_blocking(USART1, adns3080_read(0x13) & 0x3f);

      gpio_toggle(GPIOB, GPIO7);
   }

   return 0;
}