diff options
author | jkh <jkh@FreeBSD.org> | 1994-10-22 09:55:02 +0000 |
---|---|---|
committer | jkh <jkh@FreeBSD.org> | 1994-10-22 09:55:02 +0000 |
commit | 99c86cd93fb8b040ec86b387d0fbb86b599eaa57 (patch) | |
tree | 506285e5cb0e1f46c64877dc456baa886e8b6fca /sys/i386/isa/tw.c | |
parent | 99c795865532eda43f9937cfbea3f2b5f9d3dd79 (diff) | |
download | FreeBSD-src-99c86cd93fb8b040ec86b387d0fbb86b599eaa57.zip FreeBSD-src-99c86cd93fb8b040ec86b387d0fbb86b599eaa57.tar.gz |
Add tw.c for the X10 driver.
Diffstat (limited to 'sys/i386/isa/tw.c')
-rw-r--r-- | sys/i386/isa/tw.c | 990 |
1 files changed, 990 insertions, 0 deletions
diff --git a/sys/i386/isa/tw.c b/sys/i386/isa/tw.c new file mode 100644 index 0000000..0f87c54 --- /dev/null +++ b/sys/i386/isa/tw.c @@ -0,0 +1,990 @@ +/*- + * Copyright (c) 1992, 1993 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 + */ + +/* + * 386BSD 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 + * + * REVISION HISTORY + * + * Date Who What + * 12/2/92 stark Initial Release + * 2/7/93 stark Minor upgrades to timing code + * 3/4/93 stark Updated to use high-res. "microtime" routines + * 5/9/93 stark Minor changes to console error reporting + * 10/30/93 stark Clean-up for FreeBSD release + * 3/14/93 stark Clean-up for FreeBSD-1.1 release + * 9/17/94 stark Upgrade to FreeBSD-2.0 + * + * 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 386BSD 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 "tty.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" + +void twdelay25(); +void twdelayn(int n); +void twsetuptimes(int *a); +int twprobe(); +int twattach(); +void twintr(int unit); + +/* + * 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 */ + +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]; + +/* + * 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. + */ + +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. + */ + +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) + dev_t dev; + struct uio *uio; +{ + 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) + dev_t dev; + struct uio *uio; +{ + 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 */ + +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. + */ + +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. + */ + +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() + */ + +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() + */ + +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. + */ + +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. + */ + +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 |