/*
 * serial.c
 * --------
 * 30/6/23
 * serial port interface
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <kernel.h>
#include <swis.h>
#include "modhdr.h"
#include "module.h"
#include "serial.h"

// HAL UART entry points
#define HAL_UARTPorts           65
#define HAL_UARTStartUp         66
#define HAL_UARTShutdown        67
#define HAL_UARTFeatures        68
#define HAL_UARTReceiveByte     69
#define HAL_UARTTransmitByte    70
#define HAL_UARTLineStatus      71
#define HAL_UARTInterruptEnable 72
#define HAL_UARTRate            73
#define HAL_UARTFormat          74
#define HAL_UARTFIFOSize        75
#define HAL_UARTFIFOClear       76
#define HAL_UARTFIFOEnable      77
#define HAL_UARTFIFOThreshold   78
#define HAL_UARTInterruptID     79
#define HAL_UARTBreak           80
#define HAL_UARTModemControl    81
#define HAL_UARTModemStatus     82
#define HAL_UARTDevice          83
#define HAL_UARTDefault         84

serial_t serial;


/*
 * serial_find
 * -----------
 * Searches for unused serial ports. If found, stores the device name.
 * Returns the number of ports found.
 */
int serial_find(void)
{
  const char
    devices[] = "devices:",
    name[]    = "Serial*";
  #define OFFSET (sizeof(devices) - 1)

  char buf[64];
  _kernel_swi_regs regs;
  int i = 0;

  strcpy(buf, devices);

  regs.r[0] = 9; // list
  regs.r[1] = (int)devices;
  regs.r[2] = (int)buf + OFFSET;
  regs.r[3] = 1;
  regs.r[4] = 0;
  regs.r[5] = sizeof(buf) - OFFSET;
  regs.r[6] = (int)name;

  while((regs.r[4] != -1) && (i < MAX_PORTS))
  {
    regs.r[3] = 1;
    _kernel_swi(OS_GBPB, &regs, &regs);
    if (regs.r[3] == 1)
    {
      _kernel_swi_regs r;
      r.r[0] = 0x8f; // open
      r.r[1] = (int)buf;
      if(_kernel_swi(OS_Find, &r, &r) == NULL)
        if(r.r[0] != 0)
        {
          r.r[1] = r.r[0];
          r.r[0] = 0; // close
          if(_kernel_swi(OS_Find, &r, &r) == NULL)
            strcpy(serial.port[i++].device, buf + OFFSET);
        }
    }
  }

  return serial.ports = i;
}


/*
 * serial_open
 * -----------
 * Opens input and output streams for the given port
 * The baud rate of 31250 is not directly available, so open the port with
 * the default rate and then set the required rate via OS_Hardware.
 * Returns any error.
 */
_kernel_oserror *serial_open(int port)
{
  _kernel_swi_regs regs;
  _kernel_oserror* err = NULL;
  port_t *u = &serial.port[port];

  char str[64];
  sprintf(str, "devices#data8;stop1;noparity;nohandshake;noblock:%s", u->device);
  regs.r[1] = (int)str;

  // note:
  // 'nohandshake', 'noblock', and 'size', do not seem to work on the default port ??
  // So on receive, never read more than is available.
  // On transmit, for 'size', use an additional buffer.
  // The Pi4 HAL only makes 1 uart available so I cannot test these issues on anything other
  // than the default port.

  regs.r[0] = 0x4F; // open input (Rx)
  if((err = _kernel_swi(OS_Find, &regs, &regs)) != NULL)
    return err;
  u->stream[RX].handle = regs.r[0];

  regs.r[0] = 0x8F; // open output (Tx)
  if((err = _kernel_swi(OS_Find, &regs, &regs)) != NULL)
    return err;
  u->stream[TX].handle = regs.r[0];

  // ensure no handshake is set
  regs.r[0] = 0;
  regs.r[1] = 2;
  regs.r[2] = ~2;
  _kernel_swi(OS_SerialOp, &regs, &regs);

  // set baud rate
  regs.r[0] = port;
  regs.r[1] = 31250 * 16; // required baud rate
  regs.r[8] = 0;          // Call HAL routine
  regs.r[9] = HAL_UARTRate;
  _kernel_swi(OS_Hardware, &regs, &regs);

  u->open = 1;

  return err;
}


/*
 * serial_close
 * ------------
 * Closes input and output streams for the given port.
 * Returns any error.
 */
_kernel_oserror *serial_close(int port)
{
  _kernel_swi_regs regs;
  _kernel_oserror* err;
  port_t *u = &serial.port[port];

  if(!u->open)
    return NULL;

  int i;
  for(i=0; i<NUM_STREAMS; i++)
  {
    regs.r[0] = 0;
    regs.r[1] = u->stream[i].handle;
    if((err = _kernel_swi(OS_Find, &regs, &regs)) != NULL)
      return err;
  }
  u->open = 0;

  return NULL;
}


/*
 * serial_status
 * -------------
 * For Rx, returns the number of bytes waiting to be read in the receive stream.
 * For Tx, returns the free space available in the transmit stream.
 */
int serial_status(int handle)
{
  _kernel_swi_regs regs;

  regs.r[0] = 2; // read extent
  regs.r[1] = handle;
  _kernel_swi(OS_Args, &regs, &regs);

  return regs.r[2];
}


/*
 * serial_read
 * -----------
 * Reads n bytes from the file handle to buf.
 * Returns any error.
 */
_kernel_oserror *serial_read(int handle, char* buf, int n)
{
  _kernel_swi_regs regs;

  regs.r[0] = 4;
  regs.r[1] = handle;
  regs.r[2] = (int) buf;
  regs.r[3] = n;
  regs.r[4] = 0;
  return _kernel_swi(OS_GBPB, &regs, &regs);
}


/*
 * serial_write
 * ------------
 * Writes n bytes from buf to the file handle.
 * Returns any error.
 */
_kernel_oserror *serial_write(int handle, char* buf, int n)
{
  _kernel_swi_regs regs;

  regs.r[0] = 2;
  regs.r[1] = handle;
  regs.r[2] = (int) buf;
  regs.r[3] = n;
  regs.r[4] = 0;
  return _kernel_swi(OS_GBPB, &regs, &regs);
}


/*
 * serial_report
 * -------------
 * Displays connected port info
 */
void serial_report(void)
{
  _kernel_swi_regs regs;
  int i;

  for(i=0; i<MAX_PORTS; i++)
  {
    port_t *u = &serial.port[i];

    if(u->open)
    {
      printf("Port %d:\n", i);
      printf("  Device: %s\n", u->device);
      printf("  Rx handle: %X\n", u->stream[RX].handle);
      printf("  Tx handle: %X\n", u->stream[TX].handle);

      // read baud rate
      regs.r[0] = i;   // port
      regs.r[1] = -1;  // read baud rate
      regs.r[8] = 0;   // Call HAL routine
      regs.r[9] = HAL_UARTRate;
      _kernel_swi(OS_Hardware, &regs, &regs);
      printf("  Baud rate: %d\n", regs.r[0] / 16);
/*
      // read the fifo sizes
      int rx, tx;
      regs.r[0] = i;   // port
      regs.r[1] = (int)&rx;
      regs.r[2] = (int)&tx;
      regs.r[8] = 0;   // Call HAL routine
      regs.r[9] = HAL_UARTFIFOSize;
      _kernel_swi(OS_Hardware, &regs, &regs);
      printf("  FIFO size: rx %d, tx %d\n", rx, tx);
*/
    }
  }
}



