/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- * * $FreeBSD$ * * This device-driver helps the userland controlprogram for a LORAN-C * receiver avoid monopolizing the CPU. * * This is clearly a candidate for the "most weird hardware support in * FreeBSD" prize. At this time only two copies of the receiver are * known to exist in the entire world. * * Details can be found at: * ftp://ftp.eecis.udel.edu/pub/ntp/loran.tar.Z * */ #ifdef _KERNEL #include #include #include #include #include #include #include #include #endif /* _KERNEL */ typedef TAILQ_HEAD(, datapoint) dphead_t; struct datapoint { /* Fields used by kernel */ u_int64_t scheduled; u_int code; u_int fri; u_int agc; u_int phase; u_int width; u_int par; u_int isig; u_int qsig; u_int ssig; u_int64_t epoch; TAILQ_ENTRY(datapoint) list; int vco; int bounce; pid_t pid; struct timespec when; int priority; dphead_t *home; /* Fields used only in userland */ void (*proc)(struct datapoint *); void *ident; int index; char *name; /* Fields used only in userland */ double ival; double qval; double sval; double mval; }; /* * Mode register (PAR) hardware definitions */ #define INTEG 0x03 /* integrator mask */ #define INTEG_1000us 0 #define INTEG_264us 1 #define INTEG_36us 2 #define INTEG_SHORT 3 #define GATE 0x0C /* gate source mask */ #define GATE_OPEN 0x0 #define GATE_GRI 0x4 #define GATE_PCI 0x8 #define GATE_STB 0xc #define MSB 0x10 /* load dac high-order bits */ #define IEN 0x20 /* enable interrupt bit */ #define EN5 0x40 /* enable counter 5 bit */ #define ENG 0x80 /* enable gri bit */ #define VCO_SHIFT 8 /* bits of fraction on VCO value */ #define VCO (2048 << VCO_SHIFT) /* initial vco dac (0 V)*/ #define PGUARD 990 /* program guard time (cycle) (990!) */ #ifdef _KERNEL #define NLORAN 10 /* Allow ten minor devices */ #define NDUMMY 4 /* How many idlers we want */ #define PORT 0x0300 /* controller port address */ #define GRI 800 /* pulse-group gate (cycle) */ /* * Analog/digital converter (ADC) hardware definitions */ #define ADC PORT+2 /* adc buffer (r)/address (w) */ #define ADCGO PORT+3 /* adc status (r)/adc start (w) */ #define ADC_START 0x01 /* converter start bit (w) */ #define ADC_BUSY 0x01 /* converter busy bit (r) */ #define ADC_DONE 0x80 /* converter done bit (r) */ #define ADC_I 0 /* i channel (phase) */ #define ADC_Q 1 /* q channel (amplitude) */ #define ADC_S 2 /* s channel (agc) */ /* * Digital/analog converter (DAC) hardware definitions * Note: output voltage increases with value programmed; the buffer * is loaded in two 8-bit bytes, the lsb 8 bits with the MSB bit off in * the PAR register, the msb 4 bits with the MSB on. */ #define DACA PORT+4 /* vco (dac a) buffer (w) */ #define DACB PORT+5 /* agc (dac b) buffer (w) */ #define LOAD_DAC(dac, val) if (0) { } else { \ par &= ~MSB; outb(PAR, par); outb((dac), (val) & 0xff); \ par |= MSB; outb(PAR, par); outb((dac), ((val) >> 8) & 0xff); \ } /* * Pulse-code generator (CODE) hardware definitions * Note: bits are shifted out from the lsb first */ #define CODE PORT+6 /* pulse-code buffer (w) */ #define MPCA 0xCA /* LORAN-C master pulse code group a */ #define MPCB 0x9F /* LORAN-C master pulse code group b */ #define SPCA 0xF9 /* LORAN-C slave pulse code group a */ #define SPCB 0xAC /* LORAN-C slave pulse code group b */ /* * Mode register (PAR) hardware definitions */ #define PAR PORT+7 /* parameter buffer (w) */ #define TGC PORT+0 /* stc control port (r/w) */ #define TGD PORT+1 /* stc data port (r/w) */ /* * Timing generator (STC) hardware commands */ /* argument sssss = counter numbers 5-1 */ #define TG_LOADDP 0x00 /* load data pointer */ /* argument ee = element (all groups except ggg = 000 or 111) */ #define MODEREG 0x00 /* mode register */ #define LOADREG 0x08 /* load register */ #define HOLDREG 0x10 /* hold register */ #define HOLDINC 0x18 /* hold register (hold cycle increm) */ /* argument ee = element (group ggg = 111) */ #define ALARM1 0x07 /* alarm register 1 */ #define ALARM2 0x0F /* alarm register 2 */ #define MASTER 0x17 /* master mode register */ #define STATUS 0x1F /* status register */ #define ARM 0x20 /* arm counters */ #define LOAD 0x40 /* load counters */ #define TG_LOADARM 0x60 /* load and arm counters */ #define DISSAVE 0x80 /* disarm and save counters */ #define TG_SAVE 0xA0 /* save counters */ #define DISARM 0xC0 /* disarm counters */ /* argument nnn = counter number */ #define SETTOG 0xE8 /* set toggle output HIGH for counter */ #define CLRTOG 0xE0 /* set toggle output LOW for counter */ #define STEP 0xF0 /* step counter */ /* argument eeggg, where ee = element, ggg - counter group */ /* no arguments */ #define ENABDPS 0xE0 /* enable data pointer sequencing */ #define ENABFOUT 0xE6 /* enable fout */ #define ENAB8 0xE7 /* enable 8-bit data bus */ #define DSABDPS 0xE8 /* disable data pointer sequencing */ #define ENAB16 0xEF /* enable 16-bit data bus */ #define DSABFOUT 0xEE /* disable fout */ #define ENABPFW 0xF8 /* enable prefetch for write */ #define DSABPFW 0xF9 /* disable prefetch for write */ #define TG_RESET 0xFF /* master reset */ #define LOAD_9513(index, val) if (0) {} else { \ outb(TGC, TG_LOADDP + (index)); \ outb(TGD, (val) & 0xff); \ outb(TGD, ((val) >> 8) & 0xff); \ } #define NENV 40 /* size of envelope filter */ #define CLOCK 50 /* clock period (clock) */ #define CYCLE 10 /* carrier period (us) */ #define PCX (NENV * CLOCK) /* envelope gate (clock) */ #define STROBE 50 /* strobe gate (clock) */ /**********************************************************************/ extern struct cdevsw loran_cdevsw; static dphead_t minors[NLORAN + 1], working; static struct datapoint dummy[NDUMMY], *first, *second; static u_int64_t ticker; static u_char par; static MALLOC_DEFINE(M_LORAN, "Loran", "Loran datapoints"); static int loranerror; static char lorantext[160]; static u_int vco_is; static u_int vco_should; static u_int vco_want; static u_int64_t vco_when; static int64_t vco_error; /**********************************************************************/ static int loranprobe (struct isa_device *dvp); static void init_tgc (void); static int loranattach (struct isa_device *isdp); static void loranenqueue (struct datapoint *); static d_open_t loranopen; static d_close_t loranclose; static d_read_t loranread; static d_write_t loranwrite; static ointhand2_t loranintr; extern struct timecounter loran_timecounter; /**********************************************************************/ int loranprobe(struct isa_device *dvp) { static int once; if (!once++) cdevsw_add(&loran_cdevsw); /* We need to be a "fast-intr" */ dvp->id_ri_flags |= RI_FAST; dvp->id_iobase = PORT; return (8); } static u_short tg_init[] = { /* stc initialization vector */ 0x0562, 12, 13, /* counter 1 (p0) Mode J */ 0x0262, PGUARD, GRI, /* counter 2 (gri) Mode J */ 0x8562, PCX, 5000 - PCX, /* counter 3 (pcx) */ 0xc562, 0, STROBE, /* counter 4 (stb) Mode L */ 0x052a, 0, 0 /* counter 5 (out) */ }; static void init_tgc(void) { int i; /* Initialize the 9513A */ outb(TGC, TG_RESET); outb(TGC, LOAD+0x1f); /* reset STC chip */ LOAD_9513(MASTER, 0x8af0); outb(TGC, TG_LOADDP+1); tg_init[4] = 7499 - GRI; for (i = 0; i < 5*3; i++) { outb(TGD, tg_init[i]); outb(TGD, tg_init[i] >> 8); } outb(TGC, TG_LOADARM+0x1f); /* let the good times roll */ } int loranattach(struct isa_device *isdp) { int i; isdp->id_ointr = loranintr; /* We need to be a "fast-intr" */ isdp->id_ri_flags |= RI_FAST; printf("loran0: LORAN-C Receiver\n"); vco_want = vco_should = VCO; vco_is = vco_should >> VCO_SHIFT; LOAD_DAC(DACA, vco_is); init_tgc(); init_timecounter(&loran_timecounter); TAILQ_INIT(&working); for (i = 0; i < NLORAN + 1; i++) { TAILQ_INIT(&minors[i]); } for (i = 0; i < NDUMMY; i++) { dummy[i].agc = 4095; dummy[i].code = 0xac; dummy[i].fri = PGUARD; dummy[i].scheduled = PGUARD * 2 * i; dummy[i].phase = 50; dummy[i].width = 50; dummy[i].priority = NLORAN * 256; dummy[i].home = &minors[NLORAN]; if (i == 0) first = &dummy[i]; else if (i == 1) second = &dummy[i]; else TAILQ_INSERT_TAIL(&working, &dummy[i], list); } inb(ADC); /* Flush any old result */ outb(ADC, ADC_S); par = ENG|IEN; outb(PAR, par); return (1); } static int loranopen (dev_t dev, int flags, int fmt, struct proc *p) { int idx; idx = minor(dev); if (idx >= NLORAN) return (ENODEV); return(0); } static int loranclose(dev_t dev, int flags, int fmt, struct proc *p) { return(0); } static int loranread(dev_t dev, struct uio * uio, int ioflag) { u_long ef; struct datapoint *this; int err, c; int idx; idx = minor(dev); if (loranerror) { printf("Loran0: %s", lorantext); loranerror = 0; return(EIO); } if (TAILQ_EMPTY(&minors[idx])) tsleep ((caddr_t)&minors[idx], (PZERO + 8) |PCATCH, "loranrd", hz*2); if (TAILQ_EMPTY(&minors[idx])) return(0); this = TAILQ_FIRST(&minors[idx]); ef = read_eflags(); disable_intr(); TAILQ_REMOVE(&minors[idx], this, list); write_eflags(ef); c = imin(uio->uio_resid, (int)sizeof *this); err = uiomove((caddr_t)this, c, uio); FREE(this, M_LORAN); return(err); } static void loranenqueue(struct datapoint *dp) { struct datapoint *dpp; TAILQ_FOREACH(dpp, &working, list) { if (dpp->priority <= dp->priority) continue; TAILQ_INSERT_BEFORE(dpp, dp, list); return; } TAILQ_INSERT_TAIL(&working, dp, list); } static int loranwrite(dev_t dev, struct uio * uio, int ioflag) { u_long ef; int err = 0, c; struct datapoint *this; int idx; u_int64_t dt; u_int64_t when; idx = minor(dev); MALLOC(this, struct datapoint *, sizeof *this, M_LORAN, M_WAITOK); c = imin(uio->uio_resid, (int)sizeof *this); err = uiomove((caddr_t)this, c, uio); if (err) { FREE(this, M_LORAN); return (err); } if (this->fri == 0) { FREE(this, M_LORAN); return (EINVAL); } this->par &= INTEG|GATE; /* XXX more checks needed! */ this->home = &minors[idx]; this->priority &= 0xff; this->priority += idx * 256; this->bounce = 0; when = second->scheduled + PGUARD; if (when > this->scheduled) { dt = when - this->scheduled; dt -= dt % this->fri; this->scheduled += dt; } ef = read_eflags(); disable_intr(); loranenqueue(this); write_eflags(ef); if (this->vco >= 0) vco_want = this->vco; return(err); } static void loranintr(int unit) { u_long ef; int status = 0, i; #if 0 int count = 0; #endif int delay; u_int64_t when; struct timespec there, then; struct datapoint *dp, *done; ef = read_eflags(); disable_intr(); /* * Pick up the measurement which just completed, and setup * the next measurement. We have 1100 microseconds for this * of which some eaten by the A/D of the S channel and the * interrupt to get us here. */ done = first; nanotime(&there); done->ssig = inb(ADC); par &= ~(ENG | IEN); outb(PAR, par); outb(ADC, ADC_I); outb(ADCGO, ADC_START); /* Interlude: while we wait: setup the next measurement */ LOAD_DAC(DACB, second->agc); outb(CODE, second->code); par &= ~(INTEG|GATE); par |= second->par; par |= ENG | IEN; while (!(inb(ADCGO) & ADC_DONE)) continue; done->isig = inb(ADC); outb(ADC, ADC_Q); outb(ADCGO, ADC_START); /* Interlude: while we wait: setup the next measurement */ /* * We need to load this from the opposite register due to some * weirdness which you can read about in in the 9513 manual on * page 1-26 under "LOAD" */ LOAD_9513(0x0c, second->phase); LOAD_9513(0x14, second->phase); outb(TGC, TG_LOADARM + 0x08); LOAD_9513(0x14, second->width); while (!(inb(ADCGO) & ADC_DONE)) continue; done->qsig = inb(ADC); outb(ADC, ADC_S); outb(PAR, par); /* * End of VERY time critical stuff, we have 8 msec to find * the next measurement and program the delay. */ status = inb(TGC); nanotime(&then); first = second; second = 0; when = first->scheduled + PGUARD; TAILQ_FOREACH(dp, &working, list) { while (dp->scheduled < when) dp->scheduled += dp->fri; if (second && dp->scheduled + PGUARD >= second->scheduled) continue; second = dp; } delay = (second->scheduled - first->scheduled) - GRI; LOAD_9513(0x0a, delay); /* Done, the rest is leisure work */ vco_error += ((vco_is << VCO_SHIFT) - vco_should) * (ticker - vco_when); vco_should = vco_want; i = vco_should >> VCO_SHIFT; if (vco_error < 0) i++; if (vco_is != i) { LOAD_DAC(DACA, i); vco_is = i; } vco_when = ticker; /* Check if we overran */ status &= 0x0c; #if 0 if (status) { outb(TGC, TG_SAVE + 2); /* save counter #2 */ outb(TGC, TG_LOADDP + 0x12); /* hold counter #2 */ count = inb(TGD); count |= inb(TGD) << 8; LOAD_9513(0x12, GRI) } #endif if (status) { printf( "Missed: %02x %d first:%p second:%p %.09ld\n", status, delay, first, second, then.tv_nsec - there.tv_nsec); first->bounce++; } TAILQ_REMOVE(&working, second, list); if (done->bounce) { done->bounce = 0; loranenqueue(done); } else { done->epoch = ticker; done->vco = vco_is; done->when = there; TAILQ_INSERT_TAIL(done->home, done, list); wakeup((caddr_t)done->home); } ticker = first->scheduled; while ((dp = TAILQ_FIRST(&minors[NLORAN])) != NULL) { TAILQ_REMOVE(&minors[NLORAN], dp, list); TAILQ_INSERT_TAIL(&working, dp, list); } when = second->scheduled + PGUARD; TAILQ_FOREACH(dp, &working, list) { while (dp->scheduled < when) dp->scheduled += dp->fri; } write_eflags(ef); } /**********************************************************************/ static unsigned loran_get_timecount(struct timecounter *tc) { unsigned count; u_long ef; ef = read_eflags(); disable_intr(); outb(TGC, TG_SAVE + 0x10); /* save counter #5 */ outb(TGC, TG_LOADDP +0x15); /* hold counter #5 */ count = inb(TGD); count |= inb(TGD) << 8; write_eflags(ef); return (count); } static struct timecounter loran_timecounter = { loran_get_timecount, /* get_timecount */ 0, /* no pps_poll */ 0xffff, /* counter_mask */ 5000000, /* frequency */ "loran" /* name */ }; SYSCTL_OPAQUE(_debug, OID_AUTO, loran_timecounter, CTLFLAG_RD, &loran_timecounter, sizeof(loran_timecounter), "S,timecounter", ""); /**********************************************************************/ struct isa_driver lorandriver = { loranprobe, loranattach, "loran" }; #define CDEV_MAJOR 94 static struct cdevsw loran_cdevsw = { /* open */ loranopen, /* close */ loranclose, /* read */ loranread, /* write */ loranwrite, /* ioctl */ noioctl, /* poll */ nopoll, /* mmap */ nommap, /* strategy */ nostrategy, /* name */ "loran", /* maj */ CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -1 }; #endif /* _KERNEL */