elan-mmcr.c revision 119452
1/*-
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
8 *
9 *
10 * The AMD Elan sc520 is a system-on-chip gadget which is used in embedded
11 * kind of things, see www.soekris.com for instance, and it has a few quirks
12 * we need to deal with.
13 * Unfortunately we cannot identify the gadget by CPUID output because it
14 * depends on strapping options and only the stepping field may be useful
15 * and those are undocumented from AMDs side.
16 *
17 * So instead we recognize the on-chip host-PCI bridge and call back from
18 * sys/i386/pci/pci_bus.c to here if we find it.
19 *
20 * #ifdef ELAN_PPS
21 *   The Elan has three general purpose counters, which when used just right
22 *   can hardware timestamp external events with approx 250 nanoseconds
23 *   resolution _and_ precision.  Connect the signal to TMR1IN and PIO7.
24 *   (You can use any PIO pin, look for PIO7 to change this).  Use the
25 *   PPS-API on the /dev/elan-mmcr device.
26 * #endif ELAN_PPS
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: head/sys/i386/i386/elan-mmcr.c 119452 2003-08-25 09:48:48Z obrien $");
31
32#include "opt_cpu.h"
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/kernel.h>
36#include <sys/conf.h>
37#include <sys/sysctl.h>
38#include <sys/timetc.h>
39#include <sys/proc.h>
40#include <sys/uio.h>
41#include <sys/lock.h>
42#include <sys/mutex.h>
43#include <sys/malloc.h>
44#include <sys/sysctl.h>
45#include <sys/timepps.h>
46#include <sys/watchdog.h>
47
48#include <machine/md_var.h>
49
50#include <vm/vm.h>
51#include <vm/pmap.h>
52
53uint16_t *elan_mmcr;
54
55/* Relating to the /dev/soekris-errled */
56static struct mtx errled_mtx;
57static char *errled;
58static struct callout_handle errled_h = CALLOUT_HANDLE_INITIALIZER(&errled_h);
59static void timeout_errled(void *);
60
61#ifdef ELAN_PPS
62/* Relating to the PPS-api */
63static struct pps_state elan_pps;
64
65static void
66elan_poll_pps(struct timecounter *tc)
67{
68	static int state;
69	int i;
70
71	/* XXX: This is PIO7, change to your preference */
72	i = elan_mmcr[0xc30 / 2] & 0x80;
73	if (i == state)
74		return;
75	state = i;
76	if (!state)
77		return;
78	pps_capture(&elan_pps);
79	elan_pps.capcount =
80	    (elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2]) & 0xffff;
81	pps_event(&elan_pps, PPS_CAPTUREASSERT);
82}
83#endif /* ELAN_PPS */
84
85static unsigned
86elan_get_timecount(struct timecounter *tc)
87{
88	return (elan_mmcr[0xc84 / 2]);
89}
90
91/*
92 * The Elan CPU can be run from a number of clock frequencies, this
93 * allows you to override the default 33.3 MHZ.
94 */
95#ifndef ELAN_XTAL
96#define ELAN_XTAL 33333333
97#endif
98
99static struct timecounter elan_timecounter = {
100	elan_get_timecount,
101	NULL,
102	0xffff,
103	ELAN_XTAL / 4,
104	"ELAN"
105};
106
107static int
108sysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS)
109{
110	u_int f;
111	int error;
112
113	f = elan_timecounter.tc_frequency * 4;
114	error = sysctl_handle_int(oidp, &f, sizeof(f), req);
115	if (error == 0 && req->newptr != NULL)
116		elan_timecounter.tc_frequency = (f + 3) / 4;
117	return (error);
118}
119
120SYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW,
121    0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", "");
122
123void
124init_AMD_Elan_sc520(void)
125{
126	u_int new;
127	int i;
128
129	if (bootverbose)
130		printf("Doing h0h0magic for AMD Elan sc520\n");
131	elan_mmcr = pmap_mapdev(0xfffef000, 0x1000);
132
133	/*-
134	 * The i8254 is driven with a nonstandard frequency which is
135	 * derived thusly:
136	 *   f = 32768 * 45 * 25 / 31 = 1189161.29...
137	 * We use the sysctl to get the timecounter etc into whack.
138	 */
139
140	new = 1189161;
141	i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq",
142	    NULL, 0,
143	    &new, sizeof new,
144	    NULL);
145	if (bootverbose)
146		printf("sysctl machdep.i8254_freq=%d returns %d\n", new, i);
147
148	/* Start GP timer #2 and use it as timecounter, hz permitting */
149	elan_mmcr[0xc82 / 2] = 0xc001;
150
151#ifdef ELAN_PPS
152	/* Set up GP timer #1 as pps counter */
153	elan_mmcr[0xc24 / 2] &= ~0x10;
154	elan_mmcr[0xc7a / 2] = 0x8000 | 0x4000 | 0x10 | 0x1;
155	elan_pps.ppscap |= PPS_CAPTUREASSERT;
156	pps_init(&elan_pps);
157#endif
158
159	tc_init(&elan_timecounter);
160}
161
162
163/*
164 * Device driver initialization stuff
165 */
166
167static d_write_t elan_write;
168static d_ioctl_t elan_ioctl;
169static d_mmap_t elan_mmap;
170
171#define ELAN_MMCR	0
172#define ELAN_ERRLED	1
173
174#define CDEV_MAJOR 100			/* Share with xrpu */
175static struct cdevsw elan_cdevsw = {
176	.d_open =	nullopen,
177	.d_close =	nullclose,
178	.d_write =	elan_write,
179	.d_ioctl =	elan_ioctl,
180	.d_mmap =	elan_mmap,
181	.d_name =	"elan",
182	.d_maj =	CDEV_MAJOR,
183};
184
185static void
186elan_drvinit(void)
187{
188
189	if (elan_mmcr == NULL)
190		return;
191	printf("Elan-mmcr driver: MMCR at %p\n", elan_mmcr);
192	make_dev(&elan_cdevsw, ELAN_MMCR,
193	    UID_ROOT, GID_WHEEL, 0600, "elan-mmcr");
194	make_dev(&elan_cdevsw, ELAN_ERRLED,
195	    UID_ROOT, GID_WHEEL, 0600, "soekris-errled");
196	mtx_init(&errled_mtx, "Elan-errled", MTX_DEF, 0);
197	return;
198}
199
200SYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE+CDEV_MAJOR,elan_drvinit,NULL);
201
202#define LED_ON()	do {elan_mmcr[0xc34 / 2] = 0x200;} while(0)
203#define LED_OFF()	do {elan_mmcr[0xc38 / 2] = 0x200;} while(0)
204
205static void
206timeout_errled(void *p)
207{
208	static enum {NOTHING, FLASH, DIGIT} mode;
209	static int count, cnt2, state;
210
211	mtx_lock(&errled_mtx);
212	if (p != NULL) {
213		mode = NOTHING;
214		/* Our instructions changed */
215		if (*errled == '1') {			/* Turn LED on */
216			LED_ON();
217		} else if (*errled == '0') {		/* Turn LED off */
218			LED_OFF();
219		} else if (*errled == 'f') {		/* Flash */
220			mode = FLASH;
221			cnt2 = 10;
222			if (errled[1] >= '1' && errled[1] <= '9')
223				cnt2 = errled[1] - '0';
224			cnt2 = hz / cnt2;
225			LED_ON();
226			errled_h = timeout(timeout_errled, NULL, cnt2);
227		} else if (*errled == 'd') {		/* Digit */
228			mode = DIGIT;
229			count = 0;
230			cnt2 = 0;
231			state = 0;
232			LED_OFF();
233			errled_h = timeout(timeout_errled, NULL, hz/10);
234		}
235	} else if (mode == FLASH) {
236		if (count)
237			LED_ON();
238		else
239			LED_OFF();
240		count = !count;
241		errled_h = timeout(timeout_errled, NULL, cnt2);
242	} else if (mode == DIGIT) {
243		if (cnt2 > 0) {
244			if (state) {
245				LED_OFF();
246				state = 0;
247				cnt2--;
248			} else {
249				LED_ON();
250				state = 1;
251			}
252			errled_h = timeout(timeout_errled, NULL, hz/5);
253		} else {
254			do
255				count++;
256			while (errled[count] != '\0' &&
257			    (errled[count] < '0' || errled[count] > '9'));
258			if (errled[count] == '\0') {
259				count = 0;
260				errled_h = timeout(timeout_errled, NULL, hz * 2);
261			} else {
262				cnt2 = errled[count] - '0';
263				state = 0;
264				errled_h = timeout(timeout_errled, NULL, hz);
265			}
266		}
267	}
268	mtx_unlock(&errled_mtx);
269	return;
270}
271
272/*
273 * The write function is used for the error-LED.
274 */
275
276static int
277elan_write(dev_t dev, struct uio *uio, int ioflag)
278{
279	int error;
280	char *s, *q;
281
282	if (minor(dev) != ELAN_ERRLED)
283		return (EOPNOTSUPP);
284
285	if (uio->uio_resid > 512)
286		return (EINVAL);
287	s = malloc(uio->uio_resid + 1, M_DEVBUF, M_WAITOK);
288	if (s == NULL)
289		return (ENOMEM);
290	untimeout(timeout_errled, NULL, errled_h);
291	s[uio->uio_resid] = '\0';
292	error = uiomove(s, uio->uio_resid, uio);
293	if (error) {
294		free(s, M_DEVBUF);
295		return (error);
296	}
297	mtx_lock(&errled_mtx);
298	q = errled;
299	errled = s;
300	mtx_unlock(&errled_mtx);
301	if (q != NULL)
302		free(q, M_DEVBUF);
303	timeout_errled(errled);
304
305	return(0);
306}
307
308static int
309elan_mmap(dev_t dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
310{
311
312	if (minor(dev) != ELAN_MMCR)
313		return (EOPNOTSUPP);
314	if (offset >= 0x1000)
315		return (-1);
316	*paddr = 0xfffef000;
317	return (0);
318}
319
320static int
321elan_watchdog(u_int spec)
322{
323	u_int u, v;
324	static u_int cur;
325
326	if (spec & ~__WD_LEGAL)
327		return (EINVAL);
328	switch (spec & (WD_ACTIVE|WD_PASSIVE)) {
329	case WD_ACTIVE:
330		u = spec & WD_INTERVAL;
331		if (u > 35)
332			return (EINVAL);
333		u = imax(u - 5, 24);
334		v = 2 << (u - 24);
335		v |= 0xc000;
336
337		/*
338		 * There is a bug in some silicon which prevents us from
339		 * writing to the WDTMRCTL register if the GP echo mode is
340		 * enabled.  GP echo mode on the other hand is desirable
341		 * for other reasons.  Save and restore the GP echo mode
342		 * around our hardware tom-foolery.
343		 */
344		u = elan_mmcr[0xc00 / 2];
345		elan_mmcr[0xc00 / 2] = 0;
346		if (v != cur) {
347			/* Clear the ENB bit */
348			elan_mmcr[0xcb0 / 2] = 0x3333;
349			elan_mmcr[0xcb0 / 2] = 0xcccc;
350			elan_mmcr[0xcb0 / 2] = 0;
351
352			/* Set new value */
353			elan_mmcr[0xcb0 / 2] = 0x3333;
354			elan_mmcr[0xcb0 / 2] = 0xcccc;
355			elan_mmcr[0xcb0 / 2] = v;
356			cur = v;
357		} else {
358			/* Just reset timer */
359			elan_mmcr[0xcb0 / 2] = 0xaaaa;
360			elan_mmcr[0xcb0 / 2] = 0x5555;
361		}
362		elan_mmcr[0xc00 / 2] = u;
363		return (0);
364	case WD_PASSIVE:
365		return (EOPNOTSUPP);
366	case 0:
367		u = elan_mmcr[0xc00 / 2];
368		elan_mmcr[0xc00 / 2] = 0;
369		elan_mmcr[0xcb0 / 2] = 0x3333;
370		elan_mmcr[0xcb0 / 2] = 0xcccc;
371		elan_mmcr[0xcb0 / 2] = 0x4080;
372		elan_mmcr[0xc00 / 2] = u;
373		cur = 0;
374		return (0);
375	default:
376		return (EINVAL);
377	}
378
379}
380
381static int
382elan_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct  thread *tdr)
383{
384	int error;
385
386	error = ENOTTY;
387#ifdef ELAN_PPS
388	error = pps_ioctl(cmd, arg, &elan_pps);
389	/*
390	 * We only want to incur the overhead of the PPS polling if we
391	 * are actually asked to timestamp.
392	 */
393	if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT)
394		elan_timecounter.tc_poll_pps = elan_poll_pps;
395	else
396		elan_timecounter.tc_poll_pps = NULL;
397	if (error != ENOTTY)
398		return (error);
399#endif /* ELAN_PPS */
400
401	if (cmd == WDIOCPATPAT)
402		return elan_watchdog(*((u_int*)arg));
403
404	/* Other future ioctl handling here */
405	return(error);
406}
407
408