/*** machine.h  -*- C -*- */
/** Atmel AVR implementation for an abstract machine. */

/*** Ivan Shmakov, 2019, 2020 */
/**
 ** To the extent possible under law, the author(s) have dedicated
 ** all copyright and related and neighboring rights to this software
 ** to the public domain worldwide.  This software is distributed
 ** without any warranty.
 **
 ** You should have received a copy of the CC0 Public Domain Dedication
 ** along with this software.  If not, see
 ** <http://creativecommons.org/publicdomain/zero/1.0/>.
 */

/*** History */

/** 0.X 2020-03-14
 **     New machine_eeprom_read and write functions.  Tentatively
 **     prepended channel argument to SPI (non-zero mapped to USART
 **     in SPI master mode) and UART (ignored) functions declarations.
 **/

/*** Code */
#ifndef MACHINE_H
#define MACHINE_H

#include <assert.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <stddef.h>             /* for ptrdiff_t */
#include <stdint.h>
#include <util/atomic.h>


/** FIXME: static_assert does not quite work with inline functions? */
#define x_assert(x, y) assert ((y) && (x))

#if ! defined (F_CPU)
#warning "F_CPU not defined; UART, etc. will not be available"
#else
#define MACHINE_TIMER_LIMIT     (256L)
#define MACHINE_TIMER_US \
  (1 + (-1LL + 1000000LL * (MACHINE_TIMER_LIMIT) * (MACHINE_TIMER_DIVISOR)) \
   / (F_CPU))
#if defined (BAUD)
#include <util/setbaud.h>
#else
#warning "BAUD is not defined; UART will not be available"
#endif
#endif


#if ! defined (MACHINE_TIMER_DIVISOR)
#define MACHINE_TIMER_DIVISOR   (1024)
#elif    MACHINE_TIMER_DIVISOR != 1024 && MACHINE_TIMER_DIVISOR != 256 \
      && MACHINE_TIMER_DIVISOR !=   64 && MACHINE_TIMER_DIVISOR !=   8 \
      && MACHINE_TIMER_DIVISOR !=    1
#error "MACHINE_TIMER_DIVISOR must be either of 1024, 256, 64, 8 or 1"
#endif


/** ATmega8 compatibility */
#if (defined (TIMSK) && ! defined (TIMSK0))
#define TIMSK0  TIMSK
#endif
#if (defined (TCCR0) && ! defined (TCCR0B))
#define TCCR0B  TCCR0
#endif
#if ! defined (UBRR0H) && defined (UBRRH)
#define UDR0    UDR
#define UBRR0H  UBRRH
#define UBRR0L  UBRRL
#define UCSR0A  UCSRA
#define UCSR0B  UCSRB
#define UCSR0C  UCSRC
#define U2X0    U2X
#define UDRE0   UDRE
#define RXC0    RXC
#define TXEN0   TXEN
#define RXEN0   RXEN
#define TXCIE0  TXCIE
#define RXCIE0  RXCIE
#define UCSZ00  UCSZ0
#endif
#ifdef  URSEL
#define URSELBIT  (1 << (URSEL))
#else
#define URSELBIT  0
#endif 


/** NB: generally, writes to machine_flags should be inside
        ATOMIC_BLOCK, as those get translated into read-modify-write */
volatile struct {
  unsigned  timer_p     : 1;
  unsigned  spi_ready_p : 1;
} machine_flags = { 0 };

volatile uint_least8_t machine_spi_last_read, machine_spi_last_write = 0;


ISR (TIMER0_OVF_vect) {
  machine_flags.timer_p = 1;
}

static inline int_least8_t machine_spi_master_p (uint_least8_t);

ISR (SPI_STC_vect) {
  machine_flags.spi_ready_p = 1;

  /** NB: a kind of a buffer implemented in software */
  machine_spi_last_read = SPDR;
  if (! machine_spi_master_p (0)) {
    SPDR = machine_spi_last_write;
  }
}

ISR (USART_RX_vect) {
  /* do nothing */
}

ISR (USART_TX_vect) {
  /* do nothing */
}


static inline void
machine_init (void)
{
  wdt_enable (WDTO_2S);
  sleep_enable ();
}

static inline void
machine_wait (void)
{
  /** NB: we only explicitly enable interrupts here */
  sei ();
  wdt_reset ();
  sleep_cpu ();
}


static inline void
machine_timer_setup (void)
{
  TCCR0B
    = (  ((MACHINE_TIMER_DIVISOR == 1024 || MACHINE_TIMER_DIVISOR == 256)
          ? (1 << CS02) : 0)
       | ((MACHINE_TIMER_DIVISOR ==   64 || MACHINE_TIMER_DIVISOR ==   8)
          ? (1 << CS01) : 0)
       | ((MACHINE_TIMER_DIVISOR == 1024 || MACHINE_TIMER_DIVISOR ==  64
         || MACHINE_TIMER_DIVISOR == 1)
          ? (1 << CS00) : 0));
  TIMSK0  |= (1     <<  TOIE0); /* timer 0 overflow */
}

static inline int_least8_t
machine_timer_ticked_p (void)
{
  int_least8_t r;
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
    r = machine_flags.timer_p;
    machine_flags.timer_p
      = 0;
  }
  /* . */
  return r;
}


static inline uint_least8_t
machine_eeprom_read (ptrdiff_t address)
{
  /* . */
  return
    eeprom_read_byte ((const uint8_t *)address);
}

static inline void
machine_eeprom_write (ptrdiff_t address, uint_least8_t v)
{
  eeprom_write_byte ((uint8_t *)address, v);
  /* . */
}


#if defined (F_CPU) && defined (BAUD)
static inline void machine_uart_setup (uint_least8_t);
static inline int_least8_t machine_uart_can_read_p (uint_least8_t);
static inline int_least8_t machine_uart_can_write_p (uint_least8_t);
static inline int_least16_t machine_uart_read (uint_least8_t);
static inline void machine_uart_write (uint_least8_t, uint_least8_t);
#endif


static inline int_least8_t
machine_spi_master_p (uint_least8_t cc)
{
  /* . */
  return (cc ? 1 : ((1 << MSTR) & SPCR));
}

static inline void
machine_spi_master_init (uint_least8_t cc)
{
  if (cc) {
    /** FIXME: we assume that UBRR is 0 at this point, as required */
    /** FIXME: allow enabling only Rx or only Tx */
#if 1
    UCSR0B = ((1   << TXEN0)  | (1 << RXEN0)
              | (1 << TXCIE0) | (1 << RXCIE0));
#else
    /** FIXME: transmit-only mode hardcoded for now */
    UCSR0B = ((1   << TXEN0)
              | (1 << TXCIE0));
#endif

    /** master SPI */
    /** FIXME: no way to configure UCPOL, UCPHA, UDORD */
    UCSR0C = (URSELBIT
              | (1 << UMSEL01) | (1 << UMSEL00)); /* master SPI mode */

    /** NB: UBRR is set the last */
    UBRR0H  = UBRRH_VALUE;
    UBRR0L  = UBRRL_VALUE;
#if USE_2X
    UCSR0A |=   (1 << U2X0);
#else
    UCSR0A &= ~ (1 << U2X0);
#endif

    /* . */
    return;
  }

  /** FIXME: ATmega8 and the likes only? */
  DDRB  |= (1 <<   DDB2);
  PORTB |= (1 << PORTB2);
  /** FIXME: no way to configure DORD, CPOL, CPHA, SPI2X, SPR0.1 */
  SPCR = ((1 << SPIE) | (1 << SPE) | (1 << MSTR) | (1 << SPR1));
  machine_flags.spi_ready_p = 1;
}

static inline void
machine_spi_slave_init (uint_least8_t cc)
{
  x_assert (0 == cc, "slave mode only supported for SPI channel 0");

  /** FIXME: no way to configure DORD, CPOL, CPHA */
  SPCR = ((1 << SPIE) | (1 << SPE));
  machine_flags.spi_ready_p = 0;
}

static inline int_least8_t
machine_spi_ready (uint_least8_t cc)
{
  if (cc) {
    /** FIXME: now this seems ad-hoc? */
    /* . */
    return machine_uart_can_write_p (0);
  }

  /* . */
  return machine_flags.spi_ready_p;
}

static inline uint_least8_t
machine_spi_read (uint_least8_t cc)
{
  if (cc) {
    /* . */
    return machine_uart_read (0);
  }

  ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
    if (! machine_spi_master_p (cc)) {
      machine_flags.spi_ready_p = 0;
    }
  }

  /* . */
  return machine_spi_last_read;
}

static inline void
machine_spi_write (uint_least8_t cc, uint_least8_t v)
{
  if (cc) {
    machine_uart_write (0, v);
    /* . */
    return;
  }

  ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
    if (machine_spi_master_p (cc)) {
      machine_flags.spi_ready_p = 0;
    }
    SPDR = machine_spi_last_write = v;
  }
}


#if defined (F_CPU) && defined (BAUD)
static inline void
machine_uart_setup (uint_least8_t cc)
{
  UBRR0H  = UBRRH_VALUE;
  UBRR0L  = UBRRL_VALUE;
#if USE_2X
  UCSR0A |=   (1 << U2X0);
#else
  UCSR0A &= ~ (1 << U2X0);
#endif

  /** FIXME: allow enabling only Rx or only Tx */
  UCSR0B = ((1   << TXEN0)  | (1 << RXEN0)
            | (1 << TXCIE0) | (1 << RXCIE0));
  /** Asynchronous, 8N1 */
  UCSR0C  = URSELBIT | (3 << UCSZ00);
}

static inline int_least8_t
machine_uart_can_read_p (uint_least8_t cc)
{
  /* . */
  return (UCSR0A & (1 << RXC0));
}

static inline int_least8_t
machine_uart_can_write_p (uint_least8_t cc)
{
  return (UCSR0A & (1 << UDRE0));
}

static inline int_least16_t
machine_uart_read (uint_least8_t cc)
{
  /* . */
  return (machine_uart_can_read_p (cc) ? UDR0 : -1);
}

static inline void
machine_uart_write (uint_least8_t cc, uint_least8_t v)
{
  UDR0 = v;
}
#endif


static inline void
machine_port_setup (uint_least32_t read_mask, uint_least32_t write_mask)
{
  x_assert ((read_mask & write_mask) == 0, "read and write masks clash");

#ifdef DDRA
  DDRA = (write_mask & 0377);
#else
  x_assert (((write_mask | read_mask) & 0377) == 0,
            "No port A on this chip");
#endif
  read_mask >>= 8;
  write_mask >>= 8;
#ifdef DDRB
  DDRB = (write_mask & 0377);
#else
  x_assert (((write_mask | read_mask) & 0377) == 0,
            "No port B on this chip");
#endif
  read_mask >>= 8;
  write_mask >>= 8;
#ifdef DDRC
  DDRC = (write_mask & 0377);
#else
  x_assert (((write_mask | read_mask) & 0377) == 0,
            "No port C on this chip");
#endif
  read_mask >>= 8;
  write_mask >>= 8;
#ifdef DDRD
  DDRD = (write_mask & 0377);
#else
  x_assert (((write_mask | read_mask) & 0377) == 0,
            "No port D on this chip");
#endif
}

static inline uint_least32_t
machine_port_read (uint_least8_t shift, uint_least32_t mask)
{
  uint_least32_t r = 0, mm = (mask << shift);
#ifndef PINA
  x_assert (0 == (0xff & mm), "No port A on this chip");
#else
  if ((0xff & mm) != 0) {
    uint_least32_t v = PINA;
    r |= (mask & (v >> shift));
  }
#endif
#ifndef PINB
  x_assert (0 == (0xff00 & mm), "No port B on this chip");
#else
  if ((0xff00 & mm) != 0) {
    uint_least32_t v = PINB;
    r |= (mask & ((v << 8) >> shift));
  }
#endif
#ifndef PINC
  x_assert (0 == (0xff0000 & mm), "No port C on this chip");
#else
  if ((0xff0000 & mm) != 0) {
    uint_least32_t v = PINC;
    r |= (mask & ((v << 16) >> shift));
  }
#endif
#ifndef PIND
  x_assert (0 == (0xff000000 & mm), "No port D on this chip");
#else
  if ((0xff000000 & mm) != 0) {
    uint_least32_t v = PIND;
    r |= (mask & ((v << 24) >> shift));
  }
#endif

  /* . */
  return r;
}

static inline void
machine_port_write (uint_least32_t data, uint_least32_t mask)
{
#ifdef PORTA
  if ((0377 & mask)) {
    PORTA = ((PORTA & (0377 ^ mask)) | (0377 & data));
  }
#else
  x_assert ((mask & 0377) == 0, "No port A on this chip");
#endif
  data >>= 8;
  mask >>= 8;
#ifdef PORTB
  if ((0377 & mask)) {
    PORTB = ((PORTB & (0377 ^ mask)) | (0377 & data));
  }
#else
  x_assert ((mask & 0377) == 0, "No port B on this chip");
#endif
  data >>= 8;
  mask >>= 8;
#ifdef PORTC
  if ((0377 & mask)) {
    PORTC = ((PORTC & (0377 ^ mask)) | (0377 & data));
  }
#else
  x_assert ((mask & 0377) == 0, "No port C on this chip");
#endif
  data >>= 8;
  mask >>= 8;
#ifdef PORTD
  if ((0377 & mask)) {
    PORTD = ((PORTD & (0377 ^ mask)) | (0377 & data));
  }
#else
  x_assert ((mask & 0377) == 0, "No port D on this chip");
#endif
}

#endif
/*** machine.h ends here */
