/*
   TCAD8.C - TCtask - Serial I/O interface routines.

   V1.1   T.Wagner
   V1.2   TECON Ltd.
   V1.3   "DIYA" Co.
*/
/*
   The tables used are:

      port_list   The pointer to the list of defined ports. Newly
                  defined ports are added to the end of the list.
                  Ports can never be deleted. Each port descriptor
                  contains the hardware info for the port, plus a
                  pointer to the associated sio-control-block if the
                  port was initialized.

      port_last   The pointer to the last element in port_list.

      irq_array   Is an array of pointers to sio-control-blocks. For
                  each possible IRQ-line the entry in this table points
                  to the first block active for this line. If the IRQ
                  is shared, sio-blocks are chained into this list via
                  their "next" pointer.

      irq_procs   Contains the pointer to the interrupt handler function
                  for the corresponding IRQ-line.

      sio_data          Contains the statically defined sio control blocks.
      port_descr  Contains the statically defined port descriptor blocks.

   NOTE:    You can not dynamically define ports for IRQ-lines that
            have no interrupt handler function defined. To be completely
            flexible, you would have to define sioint-handlers for all
            possible IRQs and enter the addresses into the irq_procs array.

   CAUTION: Please restrict the installation and removal of v24-
            ports to *one* task. The manipulation of the lists is
            not protected, so simultaneous install/remove calls
            may cause trouble. Since ports are normally installed and
            removed in the main task, the protection of critical regions
            seemed unnecessary.
*/

#include "tsk.h"
#include "tclocal.h"
#include "sio_ad8.h"

#define MAX_IRQ   16        /* Maximum number of interrupt lines,
                                                   16 for AT, 8 for XT. Can be left at 16 */

#define CHAIN_IRQBIT        0x04  /* Chained int controller IRQ bit */

#define RESTORE_DEFAULT 1     /* Restore parameter for remove_all */

typedef void (interrupt * intprocptr)(void);

#define inta00          0x20         /* 8259 interrupt controller control-port */
#define inta01          0x21         /* 8259 interrupt controller mask-port */

#define inta10          0xa0         /* secondary 8259 control-port for IRQ 8-15 */
#define inta11          0xa1         /* secondary 8259 mask-port for IRQ 8-15 */

#define eoi          0x20         /* end of interrupt signal for 8259 */

#define intdata    0x01   /* Enable Interrupts except Line status */

#define rxreadybit 0x02      /* готов пPиемник                     */
#define txreadybit 0x01      /* готов пеPедатчик             */
#define portready  0x03      /* готов поpт                     */

#define transmit_on 0x31     /* РазРешение пеРедачи             */
#define receive_on  0x14     /* РазРешение пРиема             */
#define duplex_on   0x35     /* РазРешение пеРедачи и приема */
#define prog_reset  0x40     /* пРогРаммный сбРос канала     */

/* Note: In version 1.1, port offsets start at 0, not 8 */

#define base_0             0x300        /*  начальный базовый адРес поРтов   */
#define intenable    0x312        /*  РегистР РазРеш/блокиР пРеРывания */
#define reset_all    0x313        /*  РегистР общего сбРоса             */
#define receiv_red   0x311        /*  РегистР готовности пРиемников    */
#define transm_red   0x310        /*  РегистР готовности пеРедатчиков  */
#define linecontrol  0x01

/*    Default values for initialising the ports:   */

#define dflt_baud   4800     /* Baud Rate Divisor: 4800 Baud */
#define dflt_lcon   0xfe   /* Line Control: четность, 2 Stop, 8 Data */

/*    Defined baud rates:    */

local word_s baud_table [] = {
                            1200,  128,
                            2400,   64,
                            4800,   32,
                            9600,   16,
                           19200,    8,
                               0,    0 };

/*-------------------------------------------------------------------------*/

/*          All that ports must be defined on-line.        */

/*    Note: In version 1.1, port offsets start at 0, not 8        */

/*-------------------------------------------------------------------------*/

local void interrupt sioint3 (void);

/*  Table of Interrupt handler functions for each IRQ line:  */

local intprocptr irq_procs [MAX_IRQ] = {  NULL,    /* IRQ 0 */
                                          NULL,    /* IRQ 1 */
                                          NULL,    /* IRQ 2 */
                                          sioint3, /* IRQ 3 */
                                          NULL,    /* IRQ 4 */
                                          NULL,    /* IRQ 5 */
                                          NULL,    /* IRQ 6 */
                                          NULL,    /* IRQ 7 */
                                          NULL,    /* IRQ 8 */
                                          NULL,    /* IRQ 9 */
                                          NULL,    /* IRQ 10 */
                                          NULL,    /* IRQ 11 */
                                          NULL,    /* IRQ 12 */
                                          NULL,    /* IRQ 13 */
                                          NULL,    /* IRQ 14 */
                                          NULL };  /* IRQ 15 */

/* When adding entries to port_descr, be sure to chain the
   elements in ascending order via the first field, and to
   increase the internal port number in the second field. */

local sioptr  irq_array [MAX_IRQ] = { NULL };

local portptr port_list = NULL;
local portptr port_last = NULL;
local word_s ports = 0;
local byte mask_i_p = 0;            /* маска установленных поРтов */

extern funcptr ad8_remove_func;
/*-------------------------------------------------------------------------*/

local void interrupt sioint3 (void)
{
  sioptr curr;
  tcbptr tsrd;
  pipeptr pip;
  byte ready;

  tsk_sti();

  while((tsk_inp(receiv_red) | tsk_inp(transm_red)) & mask_i_p) {
        for (curr = irq_array [3]; curr != NULL; curr = curr->next) {
          ready = tsk_inp(curr->port_base+linecontrol);

          if(ready & portready) {
                if(ready & txreadybit) {
                  tsk_outp(curr->port_base,*curr->xmit_buf++);
                  if(!--curr->len_xmit)
                        tsk_outp(curr->port_base+linecontrol,receive_on);
                }

                else {
                  if(ready & 0x10) curr->flags=1;
                  pip = (pipeptr)&curr->rcv_pipe;
                  tsk_cli();

                  if(pip->filled < pip->bufsize) {
                        pip->contents[pip->inptr++] = tsk_inp(curr->port_base);
                        if(pip->inptr >= pip->bufsize) pip->inptr = 0;
                        pip->filled++;

                        if((tsrd = pip->wait_read) != NULL) {
                          pip->wait_read = tsk_runable(tsrd);
                          tsrd->retptr = (nearptr)pip->contents[pip->outptr++];
                          if(pip->outptr >= pip->bufsize) pip->outptr = 0;
                          pip->filled--;
                        }
                  }
                  else tsk_outp(curr->port_base+linecontrol,0x10); /* запpет В-В */

                  tsk_sti();
                }
      }
    }
  }
  tsk_outp (inta00, eoi);
}
/*-------------------------------------------------------------------------*/

word_s ad8_define_port (word_s base, byte irq, byte vector)
{
#if (TSK_DYNAMIC)
   portptr portp;

   if (irq >= MAX_IRQ || irq_procs [irq] == NULL) return -1;

   if ((portp = tsk_alloc (sizeof (port_data))) == NULL) return -1;

   if(!(portp->pnum = ports)) tsk_outp(reset_all,0);

   portp->base = base;
   portp->irq = irq;
   portp->vector = vector;
   portp->next = NULL;
   portp->sio = NULL;

   if (port_list == NULL) port_list = portp;
   else port_last->next = portp;

   port_last = portp;
   ports++;

   return portp->pnum;

#else
   return -1;
#endif
}
/*---------------------------------------------------------------*/

local sioptr ret_error (sioptr sio)
{
   sio->port->sio = NULL;
#if (TSK_DYNAMIC)
   tsk_free (sio);
#endif
   return NULL;
}
/*----------------------------------------------------------------*/

sioptr ad8_install (word_s port, nearptr rcvbuf, word rcvsize)
{
   sioptr sio;
   portptr portp;
   word_s pbase;
//   intprocptr *intptr;
   word_s i, inta;

#if (TSK_NAMEPAR)
   static char xname [] = "SIOnXMIT", rname [] = "SIOnRCV";
   xname [3] = rname [3] = (char)(port & 0x7f) + '0';
#endif

   if (port < 0 || !rcvsize) return NULL;

   portp = port_list;
   if(port > ports) return NULL;

   while(portp->pnum != port) portp=portp->next;
   if (portp->sio != NULL) return NULL;         /* Port already in use */

#if (TSK_DYNAMIC)
   if((portp->sio = sio = tsk_alloc(sizeof(sio_datarec)))==NULL) return NULL;
#else
   return NULL;
#endif

   pbase = sio->port_base = portp->base;
   sio->port = portp;

   /* Port seems OK */

   if (create_pipe (&sio->rcv_pipe, rcvbuf, rcvsize
#if (TSK_NAMEPAR)
                , rname
#endif
                                ) == NULL)  return ret_error (sio);

   sio->civect = portp->vector;
   sio->irqbit = (byte)(1 << (portp->irq & 0x07));
   sio->len_xmit=0;
   sio->clcontrol = dflt_lcon;
   ad8_change_baud(sio,dflt_baud);
   tsk_outp (sio->port_base + linecontrol, dflt_lcon);
   tsk_nop();

   tsk_outp (intenable,0);

   if (irq_array [portp->irq] == NULL)
    {

      sio->savvect=get_vect_d (sio->civect);
      set_vect_d(sio->civect,irq_procs [portp->irq]);

//      intptr = (intprocptr *)(sio->civect * 4);
//      tsk_cli ();
//      sio->savvect = (nearptr)*intptr;
//      *intptr = irq_procs [portp->irq];
//      tsk_sti ();

    }

   inta = (portp->irq > 7) ? inta11 : inta01;

   if (irq_array [portp->irq] == NULL) {
          if (portp->irq > 7) {
                 i = tsk_inp (inta01) & ~CHAIN_IRQBIT;
                 tsk_nop();
                 tsk_outp (inta01, (byte)i);
          }
      sio->save_irq = (byte)((i = tsk_inp (inta)) & sio->irqbit);
      tsk_nop();
      tsk_outp (inta, (byte)(i & ~sio->irqbit));
   }

   else  sio->save_irq = (irq_array [portp->irq])->save_irq;

   tsk_cli ();
   sio->next = irq_array [portp->irq];
   irq_array [portp->irq] = sio;
   tsk_sti ();

   mask_i_p |= 1 << ((sio->port_base - base_0) >> 1);

   ad8_remove_func =(funcptr)ad8_remove_all;
   tsk_outp (intenable, intdata);
   return sio;
}
/*-----------------------------------------------------------------*/

void ad8_remove (sioptr sio, word_s restore)
{
//   intprocptr *intptr;
   word_s pbase, i, inta;
   portptr portp;
   sioptr curr, last;

   pbase = sio->port_base;
   portp = sio->port;

   last = NULL;

   curr = irq_array [portp->irq];
   while (curr != sio && curr != NULL) {
      last = curr;
      curr = curr->next;
   }
   if (curr == NULL) return;

   tsk_outp (intenable, 0);
   tsk_cli ();
   if (last == NULL) irq_array [portp->irq] = sio->next;
   else last->next = sio->next;
   tsk_sti ();

   inta = (portp->irq > 7) ? inta11 : inta01;

   if (restore) {
          if (irq_array [portp->irq] == NULL) {
                 tsk_cli ();
                 i = tsk_inp (inta) & ~sio->irqbit;
                 tsk_nop();
                 tsk_outp (inta, (byte)(i | sio->save_irq));
          }
   }

   else if (irq_array [portp->irq] == NULL) {
      tsk_cli ();
      i = tsk_inp (inta) | sio->irqbit;
      tsk_nop();
      tsk_outp (inta, (byte)i);
   }

   if (irq_array [portp->irq] == NULL) {

      set_vect_d(sio->civect,sio->savvect);

//      tsk_cli ();
//      intptr = (intprocptr *)(sio->civect * 4);
//      *intptr = (intprocptr)sio->savvect;

   }
//   tsk_sti ();

   portp->sio = NULL;
   delete_pipe (&sio->rcv_pipe);

   mask_i_p &= 1 << ((base_0 - sio->port_base) >> 1);

#if (TSK_DYNAMIC)
      tsk_free (sio);
#endif
}
/*-----------------------------------------------------------------*/

void ad8_remove_all (void)
{
   word_s i;
   sioptr sio;

   for (i = 0; i < MAX_IRQ; i++)
          while ((sio = irq_array [i]) != NULL)
                 ad8_remove (sio, RESTORE_DEFAULT);
}
/*-------------------------------------------------------------------------*/

void ad8_change_baud (sioptr sio, word_s rate)
{
   word_s i;
   word_s port_baud, port_mode, val_instr;

   for (i = 0; baud_table [i]; i += 2) if(baud_table [i] == rate) break;

   if (!(i = baud_table [i + 1])) return;

   switch(sio->port_base) {

          case 0x300 : port_baud=0x314; port_mode=0x317; val_instr=0x16; break;
          case 0x302 : port_baud=0x315; port_mode=0x317; val_instr=0x56; break;
          case 0x304 : port_baud=0x316; port_mode=0x317; val_instr=0x96; break;
          case 0x306 : port_baud=0x318; port_mode=0x31b; val_instr=0x16; break;
          case 0x308 : port_baud=0x319; port_mode=0x31b; val_instr=0x56; break;
          case 0x30a : port_baud=0x31a; port_mode=0x31b; val_instr=0x96; break;
          case 0x30c : port_baud=0x31c; port_mode=0x31f; val_instr=0x16; break;
          case 0x30e : port_baud=0x31d; port_mode=0x31f; val_instr=0x56; break;
   }

   tsk_outp (port_mode, (byte)val_instr);
   tsk_nop();
   tsk_outp (port_baud,(byte)i);
}
/*----------------------------------------------------------------*/

void ad8_change_mode(sioptr sio, word_s len, word_s par, word_s n)
{
  switch (len) {
    case 5:  len = 0x00; break;
    case 6:  len = 0x04; break;
    case 7:  len = 0x08; break;
    case 8:  len = 0x0c; break;
    default: return;
  }

  switch (n) {
    case 1:  n = 0x40; break;
    case 2:  n = 0xc0; break;
    default: return;
  }

  tsk_outp (sio->port_base + linecontrol, prog_reset);
  tsk_nop();
  sio->clcontrol = 0x12 | len | par | n;
  tsk_outp (sio->port_base + linecontrol, sio->clcontrol);
  tsk_nop();
}
/*-------------------------------------------------------------------*/

void ad8_send (sioptr sio, char *buf, word len)
{
   sio->xmit_buf = buf;
   sio->len_xmit = len;
   tsk_outp(sio->port_base + linecontrol, duplex_on);
}
/*-------------------------------------------------------------------*/

word_s ad8_receive (sioptr sio, dword timeout)
{
   return read_pipe (&sio->rcv_pipe, timeout);
}
/*--------------------------------------------------------------------*/

word_s ad8_check (sioptr sio)
{
   return check_pipe (&sio->rcv_pipe);
}
/*--------------------------------------------------------------------*/