elan-mmcr.c revision 109327
1101225Sphk/*
2101225Sphk * ----------------------------------------------------------------------------
3101225Sphk * "THE BEER-WARE LICENSE" (Revision 42):
4101225Sphk * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
5101225Sphk * can do whatever you want with this stuff. If we meet some day, and you think
6101225Sphk * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7101225Sphk * ----------------------------------------------------------------------------
8101225Sphk *
9101225Sphk * $FreeBSD: head/sys/i386/i386/elan-mmcr.c 109327 2003-01-15 20:15:33Z phk $
10101225Sphk *
11102934Sphk * The AMD Elan sc520 is a system-on-chip gadget which is used in embedded
12102934Sphk * kind of things, see www.soekris.com for instance, and it has a few quirks
13102934Sphk * we need to deal with.
14102934Sphk * Unfortunately we cannot identify the gadget by CPUID output because it
15102934Sphk * depends on strapping options and only the stepping field may be useful
16102934Sphk * and those are undocumented from AMDs side.
17102934Sphk *
18102934Sphk * So instead we recognize the on-chip host-PCI bridge and call back from
19102934Sphk * sys/i386/pci/pci_bus.c to here if we find it.
20109327Sphk *
21109327Sphk * #ifdef ELAN_PPS
22109327Sphk *   The Elan has three general purpose counters, which when used just right
23109327Sphk *   can hardware timestamp external events with approx 250 nanoseconds
24109327Sphk *   resolution _and_ precision.  Connect the signal to TMR1IN and PIO7.
25109327Sphk *   (You can use any PIO pin, look for PIO7 to change this).  Use the
26109327Sphk *   PPS-API on the /dev/elan-mmcr device.
27109327Sphk * #endif ELAN_PPS
28101225Sphk */
29102934Sphk
30101225Sphk#include <sys/param.h>
31101225Sphk#include <sys/systm.h>
32101225Sphk#include <sys/kernel.h>
33101225Sphk#include <sys/conf.h>
34102934Sphk#include <sys/sysctl.h>
35102934Sphk#include <sys/timetc.h>
36102934Sphk#include <sys/proc.h>
37103482Sphk#include <sys/uio.h>
38103482Sphk#include <sys/lock.h>
39103482Sphk#include <sys/mutex.h>
40103482Sphk#include <sys/malloc.h>
41109327Sphk#include <sys/sysctl.h>
42109327Sphk#include <sys/timepps.h>
43101225Sphk
44101225Sphk#include <machine/md_var.h>
45101225Sphk
46102934Sphk#include <vm/vm.h>
47102934Sphk#include <vm/pmap.h>
48102934Sphk
49102934Sphkuint16_t *elan_mmcr;
50102934Sphk
51103482Sphk/* Relating to the /dev/soekris-errled */
52103482Sphkstatic struct mtx errled_mtx;
53103482Sphkstatic char *errled;
54103482Sphkstatic struct callout_handle errled_h = CALLOUT_HANDLE_INITIALIZER(&errled_h);
55103482Sphkstatic void timeout_errled(void *);
56102935Sphk
57109327Sphk#ifdef ELAN_PPS
58109327Sphk/* Relating to the PPS-api */
59109327Sphkstatic struct pps_state elan_pps;
60109327Sphk
61109327Sphkstatic void
62109327Sphkelan_poll_pps(struct timecounter *tc)
63109327Sphk{
64109327Sphk	static int state;
65109327Sphk	int i;
66109327Sphk
67109327Sphk	/* XXX: This is PIO7, change to your preference */
68109327Sphk	i = elan_mmcr[0xc30 / 2] & 0x80;
69109327Sphk	if (i == state)
70109327Sphk		return;
71109327Sphk	state = i;
72109327Sphk	if (!state)
73109327Sphk		return;
74109327Sphk	pps_capture(&elan_pps);
75109327Sphk	elan_pps.capcount =
76109327Sphk	    (elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2]) & 0xffff;
77109327Sphk	pps_event(&elan_pps, PPS_CAPTUREASSERT);
78109327Sphk}
79109327Sphk#endif /* ELAN_PPS */
80109327Sphk
81102935Sphkstatic unsigned
82102935Sphkelan_get_timecount(struct timecounter *tc)
83102935Sphk{
84102935Sphk	return (elan_mmcr[0xc84 / 2]);
85102935Sphk}
86102935Sphk
87109327Sphk/*
88109327Sphk * The Elan CPU can be run from a number of clock frequencies, this
89109327Sphk * allows you to override the default 33.3 MHZ.
90109327Sphk */
91109327Sphk#ifndef ELAN_XTAL
92109327Sphk#define ELAN_XTAL 33333333
93109327Sphk#endif
94109327Sphk
95102935Sphkstatic struct timecounter elan_timecounter = {
96102935Sphk	elan_get_timecount,
97109327Sphk	NULL,
98102935Sphk	0xffff,
99109327Sphk	ELAN_XTAL / 4,
100102935Sphk	"ELAN"
101102935Sphk};
102102935Sphk
103109327Sphkstatic int
104109327Sphksysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS)
105109327Sphk{
106109327Sphk	u_int f;
107109327Sphk	int error;
108109327Sphk
109109327Sphk	f = elan_timecounter.tc_frequency * 4;
110109327Sphk	error = sysctl_handle_int(oidp, &f, sizeof(f), req);
111109327Sphk	if (error == 0 && req->newptr != NULL)
112109327Sphk		elan_timecounter.tc_frequency = (f + 3) / 4;
113109327Sphk	return (error);
114109327Sphk}
115109327Sphk
116109327SphkSYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW,
117109327Sphk    0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", "");
118109327Sphk
119102934Sphkvoid
120102934Sphkinit_AMD_Elan_sc520(void)
121102934Sphk{
122102934Sphk	u_int new;
123102934Sphk	int i;
124102934Sphk
125103168Ssam	if (bootverbose)
126103168Ssam		printf("Doing h0h0magic for AMD Elan sc520\n");
127102934Sphk	elan_mmcr = pmap_mapdev(0xfffef000, 0x1000);
128102934Sphk
129102934Sphk	/*-
130102934Sphk	 * The i8254 is driven with a nonstandard frequency which is
131102934Sphk	 * derived thusly:
132102934Sphk	 *   f = 32768 * 45 * 25 / 31 = 1189161.29...
133102934Sphk	 * We use the sysctl to get the timecounter etc into whack.
134102934Sphk	 */
135102934Sphk
136102934Sphk	new = 1189161;
137102934Sphk	i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq",
138102934Sphk	    NULL, 0,
139102934Sphk	    &new, sizeof new,
140102934Sphk	    NULL);
141103168Ssam	if (bootverbose)
142103168Ssam		printf("sysctl machdep.i8254_freq=%d returns %d\n", new, i);
143102935Sphk
144102935Sphk	/* Start GP timer #2 and use it as timecounter, hz permitting */
145102935Sphk	elan_mmcr[0xc82 / 2] = 0xc001;
146109327Sphk
147109327Sphk#ifdef ELAN_PPS
148109327Sphk	/* Set up GP timer #1 as pps counter */
149109327Sphk	elan_mmcr[0xc24 / 2] &= ~0x10;
150109327Sphk	elan_mmcr[0xc7a / 2] = 0x8000 | 0x4000 | 0x10 | 0x1;
151109327Sphk	elan_pps.ppscap |= PPS_CAPTUREASSERT;
152109327Sphk	pps_init(&elan_pps);
153109327Sphk#endif
154109327Sphk
155102935Sphk	tc_init(&elan_timecounter);
156102934Sphk}
157102934Sphk
158102934Sphk
159101225Sphk/*
160101225Sphk * Device driver initialization stuff
161101225Sphk */
162101225Sphk
163103482Sphkstatic d_write_t elan_write;
164101225Sphkstatic d_ioctl_t elan_ioctl;
165101225Sphkstatic d_mmap_t elan_mmap;
166101225Sphk
167103482Sphk#define ELAN_MMCR	0
168103482Sphk#define ELAN_ERRLED	1
169103482Sphk
170101225Sphk#define CDEV_MAJOR 100			/* Share with xrpu */
171101225Sphkstatic struct cdevsw elan_cdevsw = {
172103482Sphk	/* open */	nullopen,
173103482Sphk	/* close */	nullclose,
174101225Sphk	/* read */	noread,
175103482Sphk	/* write */	elan_write,
176101225Sphk	/* ioctl */	elan_ioctl,
177101225Sphk	/* poll */	nopoll,
178101225Sphk	/* mmap */	elan_mmap,
179101225Sphk	/* strategy */	nostrategy,
180101225Sphk	/* name */	"elan",
181101225Sphk	/* maj */	CDEV_MAJOR,
182101225Sphk	/* dump */	nodump,
183101225Sphk	/* psize */	nopsize,
184101225Sphk	/* flags */	0,
185101225Sphk};
186101225Sphk
187103482Sphkstatic void
188103482Sphkelan_drvinit(void)
189101225Sphk{
190103482Sphk
191103482Sphk	if (elan_mmcr == NULL)
192103482Sphk		return;
193103482Sphk	printf("Elan-mmcr driver: MMCR at %p\n", elan_mmcr);
194103482Sphk	make_dev(&elan_cdevsw, ELAN_MMCR,
195103482Sphk	    UID_ROOT, GID_WHEEL, 0600, "elan-mmcr");
196103482Sphk	make_dev(&elan_cdevsw, ELAN_ERRLED,
197103482Sphk	    UID_ROOT, GID_WHEEL, 0600, "soekris-errled");
198103482Sphk	mtx_init(&errled_mtx, "Elan-errled", MTX_DEF, 0);
199103482Sphk	return;
200101225Sphk}
201101225Sphk
202103482SphkSYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE+CDEV_MAJOR,elan_drvinit,NULL);
203103482Sphk
204103482Sphk#define LED_ON()	do {elan_mmcr[0xc34 / 2] = 0x200;} while(0)
205103482Sphk#define LED_OFF()	do {elan_mmcr[0xc38 / 2] = 0x200;} while(0)
206103482Sphk
207103482Sphkstatic void
208103482Sphktimeout_errled(void *p)
209103482Sphk{
210103482Sphk	static enum {NOTHING, FLASH, DIGIT} mode;
211103482Sphk	static int count, cnt2, state;
212103482Sphk
213103482Sphk	mtx_lock(&errled_mtx);
214103482Sphk	if (p != NULL) {
215103482Sphk		mode = NOTHING;
216103482Sphk		/* Our instructions changed */
217103482Sphk		if (*errled == '1') {			/* Turn LED on */
218103482Sphk			LED_ON();
219103482Sphk		} else if (*errled == '0') {		/* Turn LED off */
220103482Sphk			LED_OFF();
221103482Sphk		} else if (*errled == 'f') {		/* Flash */
222103482Sphk			mode = FLASH;
223103482Sphk			cnt2 = 10;
224103482Sphk			if (errled[1] >= '1' && errled[1] <= '9')
225103482Sphk				cnt2 = errled[1] - '0';
226103482Sphk			cnt2 = hz / cnt2;
227103482Sphk			LED_ON();
228103482Sphk			errled_h = timeout(timeout_errled, NULL, cnt2);
229103482Sphk		} else if (*errled == 'd') {		/* Digit */
230103482Sphk			mode = DIGIT;
231103482Sphk			count = 0;
232103482Sphk			cnt2 = 0;
233103482Sphk			state = 0;
234103482Sphk			LED_OFF();
235103482Sphk			errled_h = timeout(timeout_errled, NULL, hz/10);
236103482Sphk		}
237103482Sphk	} else if (mode == FLASH) {
238103482Sphk		if (count)
239103482Sphk			LED_ON();
240103482Sphk		else
241103482Sphk			LED_OFF();
242103482Sphk		count = !count;
243103482Sphk		errled_h = timeout(timeout_errled, NULL, cnt2);
244103482Sphk	} else if (mode == DIGIT) {
245103482Sphk		if (cnt2 > 0) {
246103482Sphk			if (state) {
247103482Sphk				LED_OFF();
248103482Sphk				state = 0;
249103482Sphk				cnt2--;
250103482Sphk			} else {
251103482Sphk				LED_ON();
252103482Sphk				state = 1;
253103482Sphk			}
254103482Sphk			errled_h = timeout(timeout_errled, NULL, hz/5);
255103482Sphk		} else {
256103482Sphk			do
257103482Sphk				count++;
258103482Sphk			while (errled[count] != '\0' &&
259103482Sphk			    (errled[count] < '0' || errled[count] > '9'));
260103482Sphk			if (errled[count] == '\0') {
261103482Sphk				count = 0;
262103482Sphk				errled_h = timeout(timeout_errled, NULL, hz * 2);
263103482Sphk			} else {
264103482Sphk				cnt2 = errled[count] - '0';
265103482Sphk				state = 0;
266103482Sphk				errled_h = timeout(timeout_errled, NULL, hz);
267103482Sphk			}
268103482Sphk		}
269103482Sphk	}
270103482Sphk	mtx_unlock(&errled_mtx);
271103482Sphk	return;
272103482Sphk}
273103482Sphk
274103482Sphk/*
275103482Sphk * The write function is used for the error-LED.
276103482Sphk */
277103482Sphk
278101225Sphkstatic int
279103482Sphkelan_write(dev_t dev, struct uio *uio, int ioflag)
280103482Sphk{
281103482Sphk	int error;
282103482Sphk	char *s, *q;
283103482Sphk
284103482Sphk	if (minor(dev) != ELAN_ERRLED)
285103482Sphk		return (EOPNOTSUPP);
286103482Sphk
287103482Sphk	if (uio->uio_resid > 512)
288103482Sphk		return (EINVAL);
289103482Sphk	s = malloc(uio->uio_resid + 1, M_DEVBUF, M_WAITOK);
290103482Sphk	if (s == NULL)
291103482Sphk		return (ENOMEM);
292103482Sphk	untimeout(timeout_errled, NULL, errled_h);
293103482Sphk	s[uio->uio_resid] = '\0';
294103482Sphk	error = uiomove(s, uio->uio_resid, uio);
295103482Sphk	if (error) {
296103482Sphk		free(s, M_DEVBUF);
297103482Sphk		return (error);
298103482Sphk	}
299103482Sphk	mtx_lock(&errled_mtx);
300103482Sphk	q = errled;
301103482Sphk	errled = s;
302103482Sphk	mtx_unlock(&errled_mtx);
303103482Sphk	if (q != NULL)
304103482Sphk		free(q, M_DEVBUF);
305103482Sphk	timeout_errled(errled);
306103482Sphk
307103482Sphk	return(0);
308101225Sphk}
309101225Sphk
310101225Sphkstatic int
311101225Sphkelan_mmap(dev_t dev, vm_offset_t offset, int nprot)
312101225Sphk{
313103482Sphk
314103482Sphk	if (minor(dev) != ELAN_MMCR)
315103482Sphk		return (EOPNOTSUPP);
316101225Sphk	if (offset >= 0x1000)
317101225Sphk		return (-1);
318101225Sphk	return (i386_btop(0xfffef000));
319101225Sphk}
320101225Sphk
321101225Sphkstatic int
322101225Sphkelan_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct  thread *tdr)
323101225Sphk{
324109327Sphk	int error;
325109327Sphk
326109327Sphk	error = ENOTTY;
327109327Sphk#ifdef ELAN_PPS
328109327Sphk	error = pps_ioctl(cmd, arg, &elan_pps);
329109327Sphk	/*
330109327Sphk	 * We only want to incur the overhead of the PPS polling if we
331109327Sphk	 * are actually asked to timestamp.
332109327Sphk	 */
333109327Sphk	if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT)
334109327Sphk		elan_timecounter.tc_poll_pps = elan_poll_pps;
335109327Sphk	else
336109327Sphk		elan_timecounter.tc_poll_pps = NULL;
337109327Sphk	if (error != ENOTTY)
338109327Sphk		return (error);
339109327Sphk#endif /* ELAN_PPS */
340109327Sphk
341109327Sphk	/* Other future ioctl handling here */
342109327Sphk	return(error);
343101225Sphk}
344101225Sphk
345