/*-
 * Copyright (c) 1992, 1993, 1995 Eugene W. Stark
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Eugene W. Stark.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY EUGENE W. STARK (THE AUTHOR) ``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 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.
 */

#include "tw.h"
#if NTW > 0

/*
 * Driver configuration parameters
 */

/*
 * Time for 1/2 of a power line cycle, in microseconds.
 * Change this to 10000 for 50Hz power.  Phil Sampson
 * (vk2jnt@gw.vk2jnt.ampr.org OR sampson@gidday.enet.dec.com)
 * reports that this works (at least in Australia) using a
 * TW7223 module (a local version of the TW523).
 */
#define HALFCYCLE 8333			/* 1/2 cycle = 8333us at 60Hz */

/*
 * Undefine the following if you don't have the high-resolution "microtime"
 * routines (leave defined for FreeBSD, which has them).
 */
#define HIRESTIME

/*
 * End of driver configuration parameters
 */

/*
 * FreeBSD Device Driver for X-10 POWERHOUSE (tm)
 * Two-Way Power Line Interface, Model #TW523
 *
 * written by Eugene W. Stark (stark@cs.sunysb.edu)
 * December 2, 1992
 *
 * NOTES:
 *
 * The TW523 is a carrier-current modem for home control/automation purposes.
 * It is made by:
 *
 * 	X-10 Inc.
 * 	185A LeGrand Ave.
 * 	Northvale, NJ 07647
 * 	USA
 * 	(201) 784-9700 or 1-800-526-0027
 *
 * 	X-10 Home Controls Inc.
 * 	1200 Aerowood Drive, Unit 20
 * 	Mississauga, Ontario
 *	(416) 624-4446 or 1-800-387-3346
 *
 * The TW523 is designed for communications using the X-10 protocol,
 * which is compatible with a number of home control systems, including
 * Radio Shack "Plug 'n Power(tm)" and Stanley "Lightmaker(tm)."
 * I bought my TW523 from:
 *
 *	Home Control Concepts
 *	9353-C Activity Road
 *	San Diego, CA 92126
 *	(619) 693-8887
 *
 * They supplied me with the TW523 (which has an RJ-11 four-wire modular
 * telephone connector), a modular cable, an RJ-11 to DB-25 connector with
 * internal wiring, documentation from X-10 on the TW523 (very good),
 * an instruction manual by Home Control Concepts (not very informative),
 * and a floppy disk containing binary object code of some demonstration/test
 * programs and of a C function library suitable for controlling the TW523
 * by an IBM PC under MS-DOS (not useful to me other than to verify that
 * the unit worked).  I suggest saving money and buying the bare TW523
 * rather than the TW523 development kit (what I bought), because if you
 * are running FreeBSD you don't really care about the DOS binaries.
 *
 * The interface to the TW-523 consists of four wires on the RJ-11 connector,
 * which are jumpered to somewhat more wires on the DB-25 connector, which
 * in turn is intended to plug into the PC parallel printer port.  I dismantled
 * the DB-25 connector to find out what they had done:
 *
 *	Signal		RJ-11 pin	DB-25 pin(s)	Parallel Port
 *	Transmit TX	  4 (Y)		2, 4, 6, 8	Data out
 *	Receive RX	  3 (G)		10, 14		-ACK, -AutoFeed
 *	Common		  2 (R)		25		Common
 *	Zero crossing	  1 (B)		17		-Select Input
 *
 * The zero crossing signal is used to synchronize transmission to the
 * zero crossings of the AC line, as detailed in the X-10 documentation.
 * It would be nice if one could generate interrupts with this signal,
 * however one needs interrupts on both the rising and falling edges,
 * and the -ACK signal to the parallel port interrupts only on the falling
 * edge, so it can't be done without additional hardware.
 *
 * In this driver, the transmit function is performed in a non-interrupt-driven
 * fashion, by polling the zero crossing signal to determine when a transition
 * has occurred.  This wastes CPU time during transmission, but it seems like
 * the best that can be done without additional hardware.  One problem with
 * the scheme is that preemption of the CPU during transmission can cause loss
 * of sync.  The driver tries to catch this, by noticing that a long delay
 * loop has somehow become foreshortened, and the transmission is aborted with
 * an error return.  It is up to the user level software to handle this
 * situation (most likely by retrying the transmission).
 */

#include "param.h"
#include "systm.h"
#include "proc.h"
#include "user.h"
#include "buf.h"
#include "kernel.h"
#include "ioctl.h"
#include "uio.h"
#include "syslog.h"
#include "select.h"

#define MIN(a,b)	((a)<(b)?(a):(b))

#ifdef HIRESTIME
#include "time.h"
#endif /* HIRESTIME */

#include "i386/isa/isa_device.h"

/*
 * Transmission is done by calling write() to send three byte packets of data.
 * The first byte contains a four bit house code (0=A to 15=P).
 * The second byte contains five bit unit/key code (0=unit 1 to 15=unit 16,
 * 16=All Units Off to 31 = Status Request).  The third byte specifies
 * the number of times the packet is to be transmitted without any
 * gaps between successive transmissions.  Normally this is 2, as per
 * the X-10 documentation, but sometimes (e.g. for bright and dim codes)
 * it can be another value.  Each call to write can specify an arbitrary
 * number of data bytes.  An incomplete packet is buffered until a subsequent
 * call to write() provides data to complete it.  At most one packet will
 * actually be processed in any call to write().  Successive calls to write()
 * leave a three-cycle gap between transmissions, per the X-10 documentation.
 *
 * Reception is done using read().
 * The driver produces a series of three-character packets.
 * In each packet, the first character consists of flags,
 * the second character is a four bit house code (0-15),
 * and the third character is a five bit key/function code (0-31).
 * The flags are the following:
 */

#define TW_RCV_LOCAL	1  /* The packet arrived during a local transmission */
#define TW_RCV_ERROR	2  /* An invalid/corrupted packet was received */

/*
 * IBM PC parallel port definitions relevant to TW523
 */

#define tw_data 0			/* Data to tw523 (R/W) */

#define tw_status 1			/* Status of tw523 (R) */
#define	TWS_RDATA		0x40	/* tw523 receive data */

#define tw_control 2			/* Control tw523 (R/W) */
#define	TWC_SYNC		0x08	/* tw523 sync (pin 17) */
#define	TWC_ENA			0x10	/* tw523 interrupt enable */

/*
 * Miscellaneous defines
 */

#define	TWUNIT(dev)	(minor(dev))	/* Extract unit number from device */
#define TWPRI		(PZERO+8)	/* I don't know any better, so let's */
					/* use the same as the line printer */

int twprobe();
int twattach();
void twintr(int unit);

struct isa_driver twdriver = {
  twprobe, twattach, "tw"
};

/*
 * Software control structure for TW523
 */

#define TWS_XMITTING	 1	/* Transmission in progress */
#define TWS_RCVING	 2	/* Reception in progress */
#define TWS_WANT	 4	/* A process wants received data */
#define TWS_OPEN	 8	/* Is it currently open? */

#define TW_SIZE		3*60	/* Enough for about 10 sec. of input */

struct tw_sc {
  u_int sc_port;		/* I/O Port */
  u_int sc_state;		/* Current software control state */
  struct selinfo sc_selp;	/* Information for select() */
  u_char sc_xphase;		/* Current state of sync (for transmitter) */
  u_char sc_rphase;		/* Current state of sync (for receiver) */
  u_char sc_flags;		/* Flags for current reception */
  short sc_rcount;		/* Number of bits received so far */
  int sc_bits;			/* Bits received so far */
  u_char sc_pkt[3];		/* Packet not yet transmitted */
  short sc_pktsize;		/* How many bytes in the packet? */
  u_char sc_buf[TW_SIZE];	/* We buffer our own input */
  int sc_nextin;		/* Next free slot in circular buffer */
  int sc_nextout;		/* First used slot in circular buffer */
#ifdef HIRESTIME
  int sc_xtimes[22];		/* Times for bits in current xmit packet */
  int sc_rtimes[22];		/* Times for bits in current rcv packet */
#endif /* HIRESTIME */
} tw_sc[NTW];

static void twdelay25();
static void twdelayn(int n);
static void twsetuptimes(int *a);
static int wait_for_zero(struct tw_sc *sc);
static int twgetbytes(struct tw_sc *sc, u_char *p, int cnt);
static int twsend(struct tw_sc *sc, int h, int k, int cnt);
static int next_zero(struct tw_sc *sc);
static int twchecktime(int target, int tol);

/*
 * Counter value for delay loop.
 * It is adjusted by twprobe so that the delay loop takes about 25us.
 */

#define TWDELAYCOUNT 161		/* Works on my 486DX/33 */
int twdelaycount;

/*
 * Twdelay25 is used for very short delays of about 25us.
 * It is implemented with a calibrated delay loop, and should be
 * fairly accurate ... unless we are preempted by an interrupt.
 *
 * We use this to wait for zero crossings because the X-10 specs say we
 * are supposed to assert carrier within 25us when one happens.
 * I don't really believe we can do this, but the X-10 devices seem to be
 * fairly forgiving.
 */

static void twdelay25()
{
  int cnt;
  for(cnt = twdelaycount; cnt; cnt--);	/* Should take about 25us */
}

/*
 * Twdelayn is used to time the length of the 1ms carrier pulse.
 * This is not very critical, but if we have high-resolution time-of-day
 * we check it every apparent 200us to make sure we don't get too far off
 * if we happen to be interrupted during the delay.
 */

static void twdelayn(int n)
{
#ifdef HIRESTIME
  int t, d;
  struct timeval tv;
  microtime(&tv);
  t = tv.tv_usec;
  t += n;
#endif /* HIRESTIME */
  while(n > 0) {
    twdelay25();
    n -= 25;
#ifdef HIRESTIME
    if((n & 0x7) == 0) {
      microtime(&tv);
      d = tv.tv_usec - t;
      if(d >= 0 && d < 1000000) return;
    }
#endif /* HIRESTIME */
  }
}

int twprobe(idp)
     struct isa_device *idp;
{
  struct tw_sc sc;
  int d;
  int tries;

  sc.sc_port = idp->id_iobase;
  /*
   * Iteratively check the timing of a few sync transitions, and adjust
   * the loop delay counter, if necessary, to bring the timing reported
   * by wait_for_zero() close to HALFCYCLE.  Give up if anything
   * ridiculous happens.
   */
  if(twdelaycount == 0) {  /* Only adjust timing for first unit */
    twdelaycount = TWDELAYCOUNT;
    for(tries = 0; tries < 10; tries++) {
      sc.sc_xphase = inb(idp->id_iobase + tw_control) & TWC_SYNC;
      if(wait_for_zero(&sc) >= 0) {
	d = wait_for_zero(&sc);
	if(d <= HALFCYCLE/100 || d >= HALFCYCLE*100) {
	  twdelaycount = 0;
	  return(0);
	}
	twdelaycount = (twdelaycount * d)/HALFCYCLE;
      }
    }
  }
  /*
   * Now do a final check, just to make sure
   */
  sc.sc_xphase = inb(idp->id_iobase + tw_control) & TWC_SYNC;
  if(wait_for_zero(&sc) >= 0) {
    d = wait_for_zero(&sc);
    if(d <= (HALFCYCLE * 110)/100 && d >= (HALFCYCLE * 90)/100) return(1);
  }
  return(0);
}

int twattach(idp)
	struct isa_device *idp;
{
  struct tw_sc *sc;

  sc = &tw_sc[idp->id_unit];
  sc->sc_port = idp->id_iobase;
  sc->sc_state = 0;
  return (1);
}

int twopen(dev, flag, mode, p)
     dev_t dev;
     int flag;
     int mode;
     struct proc *p;
{
  struct tw_sc *sc = &tw_sc[TWUNIT(dev)];
  int s;
  int port;

  s = spltty();
  if(sc->sc_state == 0) {
    sc->sc_state = TWS_OPEN;
    sc->sc_nextin = sc->sc_nextout = 0;
    sc->sc_pktsize = 0;
    outb(sc->sc_port+tw_control, TWC_ENA);
  }
  splx(s);
  return(0);
}

int twclose(dev, flag, mode, p)
     dev_t dev;
     int flag;
     int mode;
     struct proc *p;
{
  struct tw_sc *sc = &tw_sc[TWUNIT(dev)];
  int s;
  int port = sc->sc_port;

  s = spltty();
  sc->sc_state = 0;
  outb(sc->sc_port+tw_control, 0);
  splx(s);
  return(0);
}

int twread(dev, uio, ioflag)
     dev_t dev;
     struct uio *uio;
     int ioflag;
{
  u_char buf[3];
  struct tw_sc *sc = &tw_sc[TWUNIT(dev)];
  int error, cnt, s;

  s = spltty();
  cnt = MIN(uio->uio_resid, 3);
  if((error = twgetbytes(sc, buf, cnt)) == 0) {
    error = uiomove(buf, cnt, uio);
  }
  splx(s);
  return(error);
}

int twwrite(dev, uio, ioflag)
     dev_t dev;
     struct uio *uio;
     int ioflag;
{
  struct tw_sc *sc;
  int house, key, reps;
  int s, error;
  int cnt;

  sc = &tw_sc[TWUNIT(dev)];
  /*
   * Note: Although I had intended to allow concurrent transmitters,
   * there is a potential problem here if two processes both write
   * into the sc_pkt buffer at the same time.  The following code
   * is an additional critical section that needs to be synchronized.
   */
  s = spltty();
  cnt = MIN(3 - sc->sc_pktsize, uio->uio_resid);
  if(error = uiomove(&(sc->sc_pkt[sc->sc_pktsize]), cnt, uio)) {
    splx(s);
    return(error);
  }
  sc->sc_pktsize += cnt;
  if(sc->sc_pktsize < 3) {  /* Only transmit 3-byte packets */
    splx(s);
    return(0);
  }
  sc->sc_pktsize = 0;
  /*
   * Collect house code, key code, and rep count, and check for sanity.
   */
  house = sc->sc_pkt[0];
  key = sc->sc_pkt[1];
  reps = sc->sc_pkt[2];
  if(house >= 16 || key >= 32) {
    splx(s);
    return(ENODEV);
  }
  /*
   * Synchronize with the receiver operating in the bottom half, and
   * also with concurrent transmitters.
   * We don't want to interfere with a packet currently being received,
   * and we would like the receiver to recognize when a packet has
   * originated locally.
   */
  while(sc->sc_state & (TWS_RCVING | TWS_XMITTING)) {
    if(error = tsleep((caddr_t)sc, TWPRI|PCATCH, "twwrite", 0)) {
      splx(s);
      return(error);
    }
  }
  sc->sc_state |= TWS_XMITTING;
  /*
   * Everything looks OK, let's do the transmission.
   */
  splx(s);  /* Enable interrupts because this takes a LONG time */
  error = twsend(sc, house, key, reps);
  s = spltty();
  sc->sc_state &= ~TWS_XMITTING;
  wakeup((caddr_t)sc);
  splx(s);
  if(error) return(EIO);
  else return(0);
}

/*
 * Determine if there is data available for reading
 */

int twselect(dev, rw, p)
     dev_t dev;
     int rw;
     struct proc *p;
{
  struct tw_sc *sc;
  struct proc *pp;
  int s, i;

  sc = &tw_sc[TWUNIT(dev)];
  s = spltty();
  if(sc->sc_nextin != sc->sc_nextout) {
    splx(s);
    return(1);
  }
  selrecord(p, &sc->sc_selp);
  splx(s);
  return(0);
}

/*
 * X-10 Protocol
 */

#define X10_START_LENGTH 4
char X10_START[] = { 1, 1, 1, 0 };

/*
 * Each bit of the 4-bit house code and 5-bit key code
 * is transmitted twice, once in true form, and then in
 * complemented form.  This is already taken into account
 * in the following tables.
 */

#define X10_HOUSE_LENGTH 8
char X10_HOUSE[16][8] = {
	0, 1, 1, 0, 1, 0, 0, 1,		/* A = 0110 */
	1, 0, 1, 0, 1, 0, 0, 1,		/* B = 1110 */
	0, 1, 0, 1, 1, 0, 0, 1,		/* C = 0010 */
	1, 0, 0, 1, 1, 0, 0, 1,		/* D = 1010 */
	0, 1, 0, 1, 0, 1, 1, 0,		/* E = 0001 */
	1, 0, 0, 1, 0, 1, 1, 0,		/* F = 1001 */
	0, 1, 1, 0, 0, 1, 1, 0,		/* G = 0101 */
	1, 0, 1, 0, 0, 1, 1, 0,		/* H = 1101 */
	0, 1, 1, 0, 1, 0, 1, 0,		/* I = 0111 */
	1, 0, 1, 0, 1, 0, 1, 0,		/* J = 1111 */
	0, 1, 0, 1, 1, 0, 1, 0,		/* K = 0011 */
	1, 0, 0, 1, 1, 0, 1, 0,		/* L = 1011 */
	0, 1, 0, 1, 0, 1, 0, 1,		/* M = 0000 */
	1, 0, 0, 1, 0, 1, 0, 1,		/* N = 1000 */
	0, 1, 1, 0, 0, 1, 0, 1,		/* O = 0100 */
	1, 0, 1, 0, 0, 1, 0, 1		/* P = 1100 */
};

#define X10_KEY_LENGTH 10
char X10_KEY[32][10] = {
	0, 1, 1, 0, 1, 0, 0, 1, 0, 1,	/* 01100 => 1 */
	1, 0, 1, 0, 1, 0, 0, 1, 0, 1,	/* 11100 => 2 */
	0, 1, 0, 1, 1, 0, 0, 1, 0, 1,	/* 00100 => 3 */
	1, 0, 0, 1, 1, 0, 0, 1, 0, 1,	/* 10100 => 4 */
	0, 1, 0, 1, 0, 1, 1, 0, 0, 1,	/* 00010 => 5 */
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1,	/* 10010 => 6 */
	0, 1, 1, 0, 0, 1, 1, 0, 0, 1,	/* 01010 => 7 */
	1, 0, 1, 0, 0, 1, 1, 0, 0, 1,	/* 11010 => 8 */
	0, 1, 1, 0, 1, 0, 1, 0, 0, 1,	/* 01110 => 9 */
	1, 0, 1, 0, 1, 0, 1, 0, 0, 1,	/* 11110 => 10 */
	0, 1, 0, 1, 1, 0, 1, 0, 0, 1,	/* 00110 => 11 */
	1, 0, 0, 1, 1, 0, 1, 0, 0, 1,	/* 10110 => 12 */
	0, 1, 0, 1, 0, 1, 0, 1, 0, 1,	/* 00000 => 13 */
	1, 0, 0, 1, 0, 1, 0, 1, 0, 1,	/* 10000 => 14 */
	0, 1, 1, 0, 0, 1, 0, 1, 0, 1,	/* 01000 => 15 */
	1, 0, 1, 0, 0, 1, 0, 1, 0, 1,	/* 11000 => 16 */
	0, 1, 0, 1, 0, 1, 0, 1, 1, 0,	/* 00001 => All Units Off */
	0, 1, 0, 1, 0, 1, 1, 0, 1, 0,	/* 00011 => All Units On */
	0, 1, 0, 1, 1, 0, 0, 1, 1, 0,	/* 00101 => On */
	0, 1, 0, 1, 1, 0, 1, 0, 1, 0,	/* 00111 => Off */
	0, 1, 1, 0, 0, 1, 0, 1, 1, 0,	/* 01001 => Dim */
	0, 1, 1, 0, 0, 1, 1, 0, 1, 0,	/* 01011 => Bright */
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0,	/* 01101 => All LIGHTS Off */
	0, 1, 1, 0, 1, 0, 1, 0, 1, 0,	/* 01111 => Extended Code */
	1, 0, 0, 1, 0, 1, 0, 1, 1, 0,	/* 10001 => Hail Request */
	1, 0, 0, 1, 0, 1, 1, 0, 1, 0,	/* 10011 => Hail Acknowledge */
	1, 0, 0, 1, 1, 0, 0, 1, 1, 0,	/* 10101 => Preset Dim 0 */
	1, 0, 0, 1, 1, 0, 1, 0, 1, 0,	/* 10111 => Preset Dim 1 */
	1, 0, 1, 0, 0, 1, 0, 1, 0, 1,	/* 11000 => Extended Data (analog) */
	1, 0, 1, 0, 0, 1, 1, 0, 1, 0,	/* 11011 => Status = on */
	1, 0, 1, 0, 1, 0, 0, 1, 1, 0,	/* 11101 => Status = off */
	1, 0, 1, 0, 1, 0, 1, 0, 1, 0	/* 11111 => Status request */
};

/*
 * Tables for mapping received X-10 code back to house/key number.
 */

short X10_HOUSE_INV[16] = { 12,  4,  2, 10, 14,  6,  0,  8,
			    13,  5,  3, 11, 15,  7,  1,  9 };

short X10_KEY_INV[32]   = { 12, 16,  4, 17,  2, 18, 10, 19,
			    14, 20,  6, 21,  0, 22,  8, 23,
			    13, 24,  5, 25,  3, 26, 11, 27,
			    15, 28,  7, 29,  1, 30,  9, 31 };

/*
 * Transmit a packet containing house code h and key code k
 */

#define TWRETRY		10		/* Try 10 times to sync with AC line */

static int twsend(sc, h, k, cnt)
struct tw_sc *sc;
int h, k, cnt;
{
  int i;
  int port = sc->sc_port;

  /*
   * Make sure we get a reliable sync with a power line zero crossing
   */
  for(i = 0; i < TWRETRY; i++) {
    if(wait_for_zero(sc) > 100) goto insync;
  }
  log(LOG_ERR, "TWXMIT: failed to sync.\n");
  return(-1);

 insync:
  /*
   * Be sure to leave 3 cycles space between transmissions
   */
  for(i = 6; i > 0; i--)
	if(next_zero(sc) < 0) return(-1);
  /*
   * The packet is transmitted cnt times, with no gaps.
   */
  while(cnt--) {
    /*
     * Transmit the start code
     */
    for(i = 0; i < X10_START_LENGTH; i++) {
      outb(port+tw_data, X10_START[i] ? 0xff : 0x00);  /* Waste no time! */
#ifdef HIRESTIME
      if(i == 0) twsetuptimes(sc->sc_xtimes);
      if(twchecktime(sc->sc_xtimes[i], HALFCYCLE/20) == 0) {
	outb(port+tw_data, 0);
	return(-1);
      }
#endif /* HIRESTIME */
      twdelayn(1000);	/* 1ms pulse width */
      outb(port+tw_data, 0);
      if(next_zero(sc) < 0) return(-1);
    }
    /*
     * Transmit the house code
     */
    for(i = 0; i < X10_HOUSE_LENGTH; i++) {
      outb(port+tw_data, X10_HOUSE[h][i] ? 0xff : 0x00);  /* Waste no time! */
#ifdef HIRESTIME
      if(twchecktime(sc->sc_xtimes[i+X10_START_LENGTH], HALFCYCLE/20) == 0) {
	outb(port+tw_data, 0);
	return(-1);
      }
#endif /* HIRESTIME */
      twdelayn(1000);	/* 1ms pulse width */
      outb(port+tw_data, 0);
      if(next_zero(sc) < 0) return(-1);
    }
    /*
     * Transmit the unit/key code
     */
    for(i = 0; i < X10_KEY_LENGTH; i++) {
      outb(port+tw_data, X10_KEY[k][i] ? 0xff : 0x00);
#ifdef HIRESTIME
      if(twchecktime(sc->sc_xtimes[i+X10_START_LENGTH+X10_HOUSE_LENGTH],
			HALFCYCLE/20) == 0) {
	outb(port+tw_data, 0);
	return(-1);
      }
#endif /* HIRESTIME */
      twdelayn(1000);	/* 1ms pulse width */
      outb(port+tw_data, 0);
      if(next_zero(sc) < 0) return(-1);
    }
  }
  return(0);
}

/*
 * Waste CPU cycles to get in sync with a power line zero crossing.
 * The value returned is roughly how many microseconds we wasted before
 * seeing the transition.  To avoid wasting time forever, we give up after
 * waiting patiently for 1/4 sec (15 power line cycles at 60 Hz),
 * which is more than the 11 cycles it takes to transmit a full
 * X-10 packet.
 */

static int wait_for_zero(sc)
struct tw_sc *sc;
{
  int i, old, new, max, cnt;
  int port = sc->sc_port + tw_control;

  old = sc->sc_xphase;
  max = 10000;		/* 10000 * 25us = 0.25 sec */
  i = 0;
  while(max--) {
    new = inb(port) & TWC_SYNC;
    if(new != old) {
      sc->sc_xphase = new;
      return(i*25);
    }
    i++;
    twdelay25();
  }
  return(-1);
}

/*
 * Wait for the next zero crossing transition, and if we don't have
 * high-resolution time-of-day, check to see that the zero crossing
 * appears to be arriving on schedule.
 * We expect to be waiting almost a full half-cycle (8.333ms-1ms = 7.333ms).
 * If we don't seem to wait very long, something is wrong (like we got
 * preempted!) and we should abort the transmission because
 * there's no telling how long it's really been since the
 * last bit was transmitted.
 */

static int next_zero(sc)
struct tw_sc *sc;
{
  int d;
#ifdef HIRESTIME
  if((d = wait_for_zero(sc)) < 0) {
#else
  if((d = wait_for_zero(sc)) < 6000 || d > 8500) {
	/* No less than 6.0ms, no more than 8.5ms */
#endif /* HIRESTIME */
    log(LOG_ERR, "TWXMIT framing error: %d\n", d);
    return(-1);
  }
  return(0);
}

/*
 * Put a three-byte packet into the circular buffer
 * Should be called at priority spltty()
 */

static int twputpkt(sc, p)
struct tw_sc *sc;
u_char *p;
{
  int i, next;

  for(i = 0; i < 3; i++) {
    next = sc->sc_nextin+1;
    if(next >= TW_SIZE) next = 0;
    if(next == sc->sc_nextout) {  /* Buffer full */
/*
      log(LOG_ERR, "TWRCV: Buffer overrun\n");
 */
      return(1);
    }
    sc->sc_buf[sc->sc_nextin] = *p++;
    sc->sc_nextin = next;
  }
  if(sc->sc_state & TWS_WANT) {
    sc->sc_state &= ~TWS_WANT;
    wakeup((caddr_t)(&sc->sc_buf));
  }
  selwakeup(&sc->sc_selp);
  return(0);
}

/*
 * Get bytes from the circular buffer
 * Should be called at priority spltty()
 */

static int twgetbytes(sc, p, cnt)
struct tw_sc *sc;
u_char *p;
int cnt;
{
  int error;

  while(cnt--) {
    while(sc->sc_nextin == sc->sc_nextout) {  /* Buffer empty */
      sc->sc_state |= TWS_WANT;
      if(error = tsleep((caddr_t)(&sc->sc_buf), TWPRI|PCATCH, "twread", 0)) {
	return(error);
      }
    }
    *p++ = sc->sc_buf[sc->sc_nextout++];
    if(sc->sc_nextout >= TW_SIZE) sc->sc_nextout = 0;
  }
  return(0);
}

/*
 * Abort reception that has failed to complete in the required time.
 */

void twabortrcv(sc)
struct tw_sc *sc;
{
  int s;
  u_char pkt[3];

  s = spltty();
  sc->sc_state &= ~TWS_RCVING;
  sc->sc_flags |= TW_RCV_ERROR;
  pkt[0] = sc->sc_flags;
  pkt[1] = pkt[2] = 0;
  twputpkt(sc, pkt);
  log(LOG_ERR, "TWRCV: aborting (%x, %d)\n", sc->sc_bits, sc->sc_rcount);
  wakeup((caddr_t)sc);
  splx(s);
}

/*
 * This routine handles interrupts that occur when there is a falling
 * transition on the RX input.  There isn't going to be a transition
 * on every bit (some are zero), but if we are smart and keep track of
 * how long it's been since the last interrupt (via the zero crossing
 * detect line and/or high-resolution time-of-day routine), we can
 * reconstruct the transmission without having to poll.
 */

void twintr(unit)
int unit;
{
  struct tw_sc *sc = &tw_sc[unit];
  int port;
  int newphase;
  u_char pkt[3];

  port = sc->sc_port;
  /*
   * Ignore any interrupts that occur if the device is not open.
   */
  if(sc->sc_state == 0) return;
  newphase = inb(port + tw_control) & TWC_SYNC;
  /*
   * NEW PACKET:
   * If we aren't currently receiving a packet, set up a new packet
   * and put in the first "1" bit that has just arrived.
   * Arrange for the reception to be aborted if too much time goes by.
   */
  if((sc->sc_state & TWS_RCVING) == 0) {
#ifdef HIRESTIME
    twsetuptimes(sc->sc_rtimes);
#endif /* HIRESTIME */
    sc->sc_state |= TWS_RCVING;
    sc->sc_rcount = 1;
    if(sc->sc_state & TWS_XMITTING) sc->sc_flags = TW_RCV_LOCAL;
    else sc->sc_flags = 0;
    sc->sc_bits = 0;
    sc->sc_rphase = newphase;
    timeout((timeout_func_t)twabortrcv, (caddr_t)sc, hz/4);
    return;
  }
  /*
   * START CODE:
   * The second and third bits are a special case.
   */
  if(sc->sc_rcount < 3) {
#ifdef HIRESTIME
    if(twchecktime(sc->sc_rtimes[sc->sc_rcount], HALFCYCLE/3)
       && newphase != sc->sc_rphase) {
#else
    if(newphase != sc->sc_rphase) {
#endif
      sc->sc_rcount++;
    } else {
      /*
       * Invalid start code -- abort reception.
       */
      sc->sc_state &= ~TWS_RCVING;
      sc->sc_flags |= TW_RCV_ERROR;
/*
      pkt[0] = sc->sc_flags;
      pkt[1] = pkt[2] = 0;
      twputpkt(sc, pkt);
      wakeup((caddr_t)sc);
 */
      untimeout((timeout_func_t)twabortrcv, (caddr_t)sc);
      log(LOG_ERR, "TWRCV: Invalid start code\n");
      return;
    }
    if(sc->sc_rcount == 3) {
      /*
       * We've gotten three "1" bits in a row.  The start code
       * is really 1110, but this might be followed by a zero
       * bit from the house code, so if we wait any longer we
       * might be confused about the first house code bit.
       * So, we guess that the start code is correct and insert
       * the trailing zero without actually having seen it.
       * We don't change sc_rphase in this case, because two
       * bit arrivals in a row preserve parity.
       */
      sc->sc_rcount++;
      return;
    }
    /*
     * Update sc_rphase to the current phase before returning.
     */
    sc->sc_rphase = newphase;
    return;
  }
  /*
   * GENERAL CASE:
   * Now figure out what the current bit is that just arrived.
   * The X-10 protocol transmits each data bit twice: once in
   * true form and once in complemented form on the next half
   * cycle.  So, there will be at least one interrupt per bit.
   * By comparing the phase we see at the time of the interrupt
   * with the saved sc_rphase, we can tell on which half cycle
   * the interrupt occrred.  This assumes, of course, that the
   * packet is well-formed.  We do the best we can at trying to
   * catch errors by aborting if too much time has gone by, and
   * by tossing out a packet if too many bits arrive, but the
   * whole scheme is probably not as robust as if we had a nice
   * interrupt on every half cycle of the power line.
   * If we have high-resolution time-of-day routines, then we
   * can do a bit more sanity checking.
   */

  /*
   * A complete packet is 22 half cycles.
   */
  if(sc->sc_rcount <= 20) {
#ifdef HIRESTIME
    if((newphase == sc->sc_rphase &&
	twchecktime(sc->sc_rtimes[sc->sc_rcount+1], HALFCYCLE/3) == 0)
       || (newphase != sc->sc_rphase &&
	   twchecktime(sc->sc_rtimes[sc->sc_rcount], HALFCYCLE/3) == 0)) {
      sc->sc_flags |= TW_RCV_ERROR;
    } else {
#endif /* HIRESTIME */
      sc->sc_bits = (sc->sc_bits << 1)
                           | ((newphase == sc->sc_rphase) ? 0x0 : 0x1);
      sc->sc_rcount += 2;
#ifdef HIRESTIME
    }
#endif /* HIRESTIME */
  }
  if(sc->sc_rcount >= 22 || sc->sc_flags & TW_RCV_ERROR) {
    if(sc->sc_rcount != 22) {
      sc->sc_flags |= TW_RCV_ERROR;
      pkt[0] = sc->sc_flags;
      pkt[1] = pkt[2] = 0;
    } else {
      pkt[0] = sc->sc_flags;
      pkt[1] = X10_HOUSE_INV[(sc->sc_bits & 0x1e0) >> 5];
      pkt[2] = X10_KEY_INV[sc->sc_bits & 0x1f];
    }
    sc->sc_state &= ~TWS_RCVING;
    twputpkt(sc, pkt);
    untimeout((timeout_func_t)twabortrcv, (caddr_t)sc);
    if(sc->sc_flags & TW_RCV_ERROR)
      log(LOG_ERR, "TWRCV: invalid packet: (%d, %x)\n",
	  sc->sc_rcount, sc->sc_bits);
    wakeup((caddr_t)sc);
  }
}

#ifdef HIRESTIME
/*
 * Initialize an array of 22 times, starting from the current
 * microtime and continuing for the next 21 half cycles.
 * We use the times as a reference to make sure transmission
 * or reception is on schedule.
 */

static void twsetuptimes(int *a)
{
  struct timeval tv;
  int i, t;

  microtime(&tv);
  t = tv.tv_usec;
  for(i = 0; i < 22; i++) {
    *a++ = t;
    t += HALFCYCLE;
    if(t >= 1000000) t -= 1000000;
  }
}

/*
 * Check the current time against a slot in a previously set up
 * timing array, and make sure that it looks like we are still
 * on schedule.
 */

static int twchecktime(int target, int tol)
{
  struct timeval tv;
  int t, d;

  microtime(&tv);
  t = tv.tv_usec;
  d = (target - t) >= 0 ? (target - t) : (t - target);
  if(d > 500000) d = 1000000-d;
  if(d <= tol && d >= -tol) {
    return(1);
  } else {
    log(LOG_ERR, "TWCHK: timing off by %dus (>= %dus)\n", d, tol);
    return(0);
  }
}
#endif /* HIRESTIME */

#endif NTW
