/*
 * Copyright (c) 2001 Tommy Bohlin <tommy@gatespace.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/* redlink.c
 */

#include <irda.h>
#include <stdio.h>
/**********************************************************************
 * Constants
 **********************************************************************/

static const char id_redlink[]="redlink";

/* Regsiter 0: Control register #1 */
#define REDLINK_REG0    0x00
#define REDLINK_TXEN    0x01 /* Enable transmitter */
#define REDLINK_RXEN    0x02 /* Enable receiver */

/* Register 1: Control register #2 */
#define REDLINK_REG1    0x10
#define REDLINK_LODB    0x01 /* Load new baud rate count value */
#define REDLINK_WIDE    0x04 /* Expand the maximum allowable pulse */

/* Register 4: Output Power register */
#define REDLINK_REG4    0x40
#define REDLINK_OP0     0x01 /* Enable LED1C output */
#define REDLINK_OP1     0x02 /* Enable LED2C output */
#define REDLINK_BLKR    0x04

/* Register 5: Receive Mode register */
#define REDLINK_REG5    0x50
#define REDLINK_RWIDL   0x01 /* fixed 1.6us pulse mode */

/* Register 6: Receive Sensitivity register #1 */
#define REDLINK_REG6    0x60
#define REDLINK_RS0     0x01 /* receive threshold bit 0 */
#define REDLINK_RS1     0x02 /* receive threshold bit 1 */

/* Register 7: Receive Sensitivity register #2 */
#define REDLINK_REG7    0x70
#define REDLINK_ENPOS   0x04 /* Ignore the falling edge */

/* Register 8,9: Baud Rate Dvider register #1,#2 */
#define REDLINK_REG8    0x80
#define REDLINK_REG9    0x90

#define REDLINK_2400    0x5f
#define REDLINK_9600    0x17
#define REDLINK_19200   0x0b
#define REDLINK_38400   0x05
#define REDLINK_57600   0x03
#define REDLINK_115200  0x01

/* Register 13: Control register #3 */
#define REDLINK_REG13   0xd0
#define REDLINK_SHDW    0x01 /* Enable access to shadow registers */

/* Register 15: Status register */
#define REDLINK_REG15   0xf0

/* Register 21: Control register #4 */
#define REDLINK_REG21   0x50
#define REDLINK_EXCK    0x02 /* Disable clock output driver */
#define REDLINK_OSCL    0x04 /* oscillator in low power, medium accuracy mode */

/**********************************************************************
 * Data structures
 **********************************************************************/

typedef struct Redlink {
  SerialDevice sd;
  SerialPort* sp;
  bool initial;
  int state;
  int speed;
} Redlink;

/**********************************************************************
 * Redlink control
 **********************************************************************/

static int rlnEncodeSpeed(int speed) {
  switch(speed) {
  case 115200: return REDLINK_115200;
  case 57600:  return REDLINK_57600;
  case 38400:  return REDLINK_38400;
  case 19200:  return REDLINK_19200;
  case 2400:   return REDLINK_2400;
  default:     return REDLINK_9600;
  }
}


static void speedTimer(void* sd)
{
  Redlink* rln=(Redlink*)sd;
  SerialPort* sp=rln->sp;

  switch(rln->state) {
  case 1:
    rln->state=2;
    rln->initial=FALSE;
    sp->setLine(sp,LINE_DTR); /*reset*/
    evtSetTimer(25,speedTimer,rln);
    break;
  case 2:
    sp->setLine(sp,LINE_RTS); /*command mode*/
    sp->putChar(sp,REDLINK_REG15);
    sp->putChar(sp,REDLINK_REG13 | REDLINK_SHDW);
    sp->putChar(sp,REDLINK_REG21 | REDLINK_EXCK | REDLINK_OSCL);
    sp->putChar(sp,REDLINK_REG13);
    sp->putChar(sp,REDLINK_REG7  | REDLINK_ENPOS);
    sp->putChar(sp,REDLINK_REG6  | REDLINK_RS0  | REDLINK_RS1);
    sp->putChar(sp,REDLINK_REG5  | REDLINK_RWIDL);
    sp->putChar(sp,REDLINK_REG4  | REDLINK_OP0  | REDLINK_OP1 | REDLINK_BLKR);
    sp->putChar(sp,REDLINK_REG0  | REDLINK_TXEN | REDLINK_RXEN);
    rln->state=3;
    evtSetTimer(20,speedTimer,rln);
	break;
  case 3:
    sp->setLine(sp,LINE_DTR|LINE_RTS);
    rln->state=4;
    evtSetTimer(1,speedTimer,rln);
	break;
  case 4:
    sp->setLine(sp,LINE_RTS); /*command mode*/
    sp->putChar(sp,REDLINK_REG8 | (rlnEncodeSpeed(rln->speed) & 0x0f));
    sp->putChar(sp,REDLINK_REG9 | ((rlnEncodeSpeed(rln->speed) >> 4) & 0x0f));
    sp->putChar(sp,REDLINK_REG1 | REDLINK_LODB | REDLINK_WIDE);
    rln->state=5;
    evtSetTimer(5,speedTimer,rln);
    break;
  case 5:
    sp->setLine(sp,LINE_DTR|LINE_RTS);
    if(rln->speed!=9600) sp->setSpeed(sp,rln->speed);
    rln->state=0;
    break;
  }
}

/**********************************************************************
 * Methods
 **********************************************************************/

static int rlnSetSpeed(SerialDevice* sd, int speed)
{
  Redlink* rln=(Redlink*)sd;
  SerialPort* sp=rln->sp;

  sp->setSpeed(sp,9600);
  rln->speed=speed;
  if(rln->initial) {
    rln->state=1;
    sp->setLine(sp,LINE_DTR|LINE_RTS); /*power on*/
    /* Must wait at least 50ms after initial
     * power on to charge internal capacitor
     */
    evtSetTimer(50,speedTimer,rln);
    return 300;
  } else {
    rln->state=2;
    sp->setLine(sp,LINE_DTR); /*reset*/
    evtSetTimer(25,speedTimer,rln);
    return 300;
  }
}

static int rlnGetSpeedMask(SerialDevice* sd)
{
  Redlink* rln=(Redlink*)sd;
  int mask=rln->sp->getSpeedMask(rln->sp);

  mask&=SPEED_9600|SPEED_19200|SPEED_38400|SPEED_57600|SPEED_115200;

  return mask;
}

static int rlnGetMinTurnaroundMask(SerialDevice* sd)
{
/*  return MIN_TA_10ms|MIN_TA_5ms|MIN_TA_1ms|MIN_TA_500us|
    MIN_TA_100us|MIN_TA_50us|MIN_TA_10us; */
  return MIN_TA_10ms|MIN_TA_5ms;
}

static int rlnGetChar(SerialDevice* sd)
{
  Redlink* rln=(Redlink*)sd;

  return rln->sp->getChar(rln->sp);
}

static void rlnPutChar(SerialDevice* sd, int c)
{
  Redlink* rln=(Redlink*)sd;

  if(rln->state==0) rln->sp->putChar(rln->sp,c);
}

static void rlnClose(SerialDevice* sd)
{
  Redlink* rln=(Redlink*)sd;
  SerialPort* sp=rln->sp;

  evtCancelTimer(speedTimer,rln);
  sp->setLine(sp,0);
  sp->handle=0;
  sp->status=0;
  freeMem(rln);
}

static void rlnStatus(SerialPort* sp, int event)
{
  Redlink* rln=(Redlink*)sp->handle;

  if(rln->sd.status) rln->sd.status(&rln->sd,event);
}

/**********************************************************************
 * External interface
 **********************************************************************/

SerialDevice* createRedlinkDevice(SerialPort* sp)
{
  Redlink* rln=allocMem(id_redlink,sizeof(Redlink));

  rln->sd.close=rlnClose;
  rln->sd.setSpeed=rlnSetSpeed;
  rln->sd.getSpeedMask=rlnGetSpeedMask;
  rln->sd.getMinTurnaroundMask=rlnGetMinTurnaroundMask;
  rln->sd.getChar=rlnGetChar;
  rln->sd.putChar=rlnPutChar;
  rln->sd.handle=0;
  rln->sd.status=0;
  rln->sp=sp;
  rln->initial=TRUE;
  rln->state=0;
  rln->speed=0;

  sp->handle=rln;
  sp->status=rlnStatus;

  return &rln->sd;
}
