summaryrefslogtreecommitdiffstats
path: root/sys/i386/isa/loran.c
blob: c43bf8524c24ea3f335ab8fefbf7ec6c159ef966 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@FreeBSD.org> 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 <sys/param.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/uio.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/timetc.h>

#include <i386/isa/isa_device.h>
#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);
	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; XXX unimplemented - use newbus! */

	printf("loran0: LORAN-C Receiver\n");

	vco_want = vco_should = VCO;
	vco_is = vco_should >> VCO_SHIFT;
	LOAD_DAC(DACA, vco_is);
	 
	init_tgc();

	tc_init(&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 = {
	INTR_TYPE_TTY | INTR_FAST,
	loranprobe,
	loranattach,
	"loran"
};
COMPAT_ISA_DRIVER(loran, lorandriver);

#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 */
OpenPOWER on IntegriCloud