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