elan-mmcr.c revision 121944
1119452Sobrien/*-
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 *
10102934Sphk * The AMD Elan sc520 is a system-on-chip gadget which is used in embedded
11102934Sphk * kind of things, see www.soekris.com for instance, and it has a few quirks
12102934Sphk * we need to deal with.
13102934Sphk * Unfortunately we cannot identify the gadget by CPUID output because it
14102934Sphk * depends on strapping options and only the stepping field may be useful
15102934Sphk * and those are undocumented from AMDs side.
16102934Sphk *
17102934Sphk * So instead we recognize the on-chip host-PCI bridge and call back from
18102934Sphk * sys/i386/pci/pci_bus.c to here if we find it.
19109327Sphk *
20109327Sphk * #ifdef ELAN_PPS
21109327Sphk *   The Elan has three general purpose counters, which when used just right
22109327Sphk *   can hardware timestamp external events with approx 250 nanoseconds
23109327Sphk *   resolution _and_ precision.  Connect the signal to TMR1IN and PIO7.
24109327Sphk *   (You can use any PIO pin, look for PIO7 to change this).  Use the
25109327Sphk *   PPS-API on the /dev/elan-mmcr device.
26109327Sphk * #endif ELAN_PPS
27101225Sphk */
28102934Sphk
29115683Sobrien#include <sys/cdefs.h>
30115683Sobrien__FBSDID("$FreeBSD: head/sys/i386/i386/elan-mmcr.c 121944 2003-11-03 11:03:40Z phk $");
31115683Sobrien
32111138Sphk#include "opt_cpu.h"
33101225Sphk#include <sys/param.h>
34101225Sphk#include <sys/systm.h>
35101225Sphk#include <sys/kernel.h>
36101225Sphk#include <sys/conf.h>
37102934Sphk#include <sys/sysctl.h>
38102934Sphk#include <sys/timetc.h>
39102934Sphk#include <sys/proc.h>
40103482Sphk#include <sys/uio.h>
41103482Sphk#include <sys/lock.h>
42103482Sphk#include <sys/mutex.h>
43103482Sphk#include <sys/malloc.h>
44109327Sphk#include <sys/sysctl.h>
45109327Sphk#include <sys/timepps.h>
46111647Sphk#include <sys/watchdog.h>
47101225Sphk
48121944Sphk#include <dev/led/led.h>
49101225Sphk#include <machine/md_var.h>
50101225Sphk
51102934Sphk#include <vm/vm.h>
52102934Sphk#include <vm/pmap.h>
53102934Sphk
54102934Sphkuint16_t *elan_mmcr;
55102934Sphk
56109327Sphk#ifdef ELAN_PPS
57109327Sphk/* Relating to the PPS-api */
58109327Sphkstatic struct pps_state elan_pps;
59109327Sphk
60109327Sphkstatic void
61109327Sphkelan_poll_pps(struct timecounter *tc)
62109327Sphk{
63109327Sphk	static int state;
64109327Sphk	int i;
65109327Sphk
66109327Sphk	/* XXX: This is PIO7, change to your preference */
67109327Sphk	i = elan_mmcr[0xc30 / 2] & 0x80;
68109327Sphk	if (i == state)
69109327Sphk		return;
70109327Sphk	state = i;
71109327Sphk	if (!state)
72109327Sphk		return;
73109327Sphk	pps_capture(&elan_pps);
74109327Sphk	elan_pps.capcount =
75109327Sphk	    (elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2]) & 0xffff;
76109327Sphk	pps_event(&elan_pps, PPS_CAPTUREASSERT);
77109327Sphk}
78109327Sphk#endif /* ELAN_PPS */
79109327Sphk
80102935Sphkstatic unsigned
81102935Sphkelan_get_timecount(struct timecounter *tc)
82102935Sphk{
83102935Sphk	return (elan_mmcr[0xc84 / 2]);
84102935Sphk}
85102935Sphk
86109327Sphk/*
87109327Sphk * The Elan CPU can be run from a number of clock frequencies, this
88109327Sphk * allows you to override the default 33.3 MHZ.
89109327Sphk */
90109327Sphk#ifndef ELAN_XTAL
91109327Sphk#define ELAN_XTAL 33333333
92109327Sphk#endif
93109327Sphk
94102935Sphkstatic struct timecounter elan_timecounter = {
95102935Sphk	elan_get_timecount,
96109327Sphk	NULL,
97102935Sphk	0xffff,
98109327Sphk	ELAN_XTAL / 4,
99119715Sphk	"ELAN",
100119715Sphk	1000
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
158101225Sphkstatic d_ioctl_t elan_ioctl;
159101225Sphkstatic d_mmap_t elan_mmap;
160101225Sphk
161121944Sphk#ifdef CPU_SOEKRIS
162121944Sphk/* Support for /dev/led/error */
163121944Sphkstatic u_int soekris_errled_cookie = 0x200;
164121944Sphkstatic dev_t soekris_errled_dev;
165121944Sphk
166121944Sphkstatic void
167121944Sphkgpio_led(void *cookie, int state)
168121944Sphk{
169121944Sphk	u_int u;
170121944Sphk
171121944Sphk	u = *(u_int *)cookie;
172121944Sphk
173121944Sphk	if (state)
174121944Sphk		elan_mmcr[0xc34 / 2] = u;
175121944Sphk	else
176121944Sphk		elan_mmcr[0xc38 / 2] = u;
177121944Sphk}
178121944Sphk#endif /* CPU_SOEKRIS */
179121944Sphk
180103482Sphk#define ELAN_MMCR	0
181103482Sphk
182101225Sphkstatic struct cdevsw elan_cdevsw = {
183111815Sphk	.d_ioctl =	elan_ioctl,
184111815Sphk	.d_mmap =	elan_mmap,
185111815Sphk	.d_name =	"elan",
186101225Sphk};
187101225Sphk
188103482Sphkstatic void
189103482Sphkelan_drvinit(void)
190101225Sphk{
191103482Sphk
192103482Sphk	if (elan_mmcr == NULL)
193103482Sphk		return;
194103482Sphk	printf("Elan-mmcr driver: MMCR at %p\n", elan_mmcr);
195103482Sphk	make_dev(&elan_cdevsw, ELAN_MMCR,
196103482Sphk	    UID_ROOT, GID_WHEEL, 0600, "elan-mmcr");
197121944Sphk
198121944Sphk#ifdef CPU_SOEKRIS
199121944Sphk	soekris_errled_dev =
200121944Sphk	    led_create(gpio_led, &soekris_errled_cookie, "error");
201121944Sphk#endif /* CPU_SOEKRIS */
202103482Sphk	return;
203101225Sphk}
204101225Sphk
205121942SphkSYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, elan_drvinit, NULL);
206103482Sphk
207101225Sphkstatic int
208112569Sjakeelan_mmap(dev_t dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
209101225Sphk{
210103482Sphk
211103482Sphk	if (minor(dev) != ELAN_MMCR)
212103482Sphk		return (EOPNOTSUPP);
213101225Sphk	if (offset >= 0x1000)
214101225Sphk		return (-1);
215111462Smux	*paddr = 0xfffef000;
216111462Smux	return (0);
217101225Sphk}
218101225Sphk
219101225Sphkstatic int
220111647Sphkelan_watchdog(u_int spec)
221111647Sphk{
222111647Sphk	u_int u, v;
223111647Sphk	static u_int cur;
224111647Sphk
225111647Sphk	if (spec & ~__WD_LEGAL)
226111647Sphk		return (EINVAL);
227111647Sphk	switch (spec & (WD_ACTIVE|WD_PASSIVE)) {
228111647Sphk	case WD_ACTIVE:
229111647Sphk		u = spec & WD_INTERVAL;
230111647Sphk		if (u > 35)
231111647Sphk			return (EINVAL);
232111647Sphk		u = imax(u - 5, 24);
233111647Sphk		v = 2 << (u - 24);
234111647Sphk		v |= 0xc000;
235111647Sphk
236111647Sphk		/*
237111647Sphk		 * There is a bug in some silicon which prevents us from
238111647Sphk		 * writing to the WDTMRCTL register if the GP echo mode is
239111647Sphk		 * enabled.  GP echo mode on the other hand is desirable
240111647Sphk		 * for other reasons.  Save and restore the GP echo mode
241111647Sphk		 * around our hardware tom-foolery.
242111647Sphk		 */
243111647Sphk		u = elan_mmcr[0xc00 / 2];
244111647Sphk		elan_mmcr[0xc00 / 2] = 0;
245111647Sphk		if (v != cur) {
246111647Sphk			/* Clear the ENB bit */
247111647Sphk			elan_mmcr[0xcb0 / 2] = 0x3333;
248111647Sphk			elan_mmcr[0xcb0 / 2] = 0xcccc;
249111647Sphk			elan_mmcr[0xcb0 / 2] = 0;
250111647Sphk
251111647Sphk			/* Set new value */
252111647Sphk			elan_mmcr[0xcb0 / 2] = 0x3333;
253111647Sphk			elan_mmcr[0xcb0 / 2] = 0xcccc;
254111647Sphk			elan_mmcr[0xcb0 / 2] = v;
255111647Sphk			cur = v;
256111647Sphk		} else {
257111647Sphk			/* Just reset timer */
258111647Sphk			elan_mmcr[0xcb0 / 2] = 0xaaaa;
259111647Sphk			elan_mmcr[0xcb0 / 2] = 0x5555;
260111647Sphk		}
261111647Sphk		elan_mmcr[0xc00 / 2] = u;
262111647Sphk		return (0);
263111647Sphk	case WD_PASSIVE:
264111647Sphk		return (EOPNOTSUPP);
265111647Sphk	case 0:
266111647Sphk		u = elan_mmcr[0xc00 / 2];
267111647Sphk		elan_mmcr[0xc00 / 2] = 0;
268111647Sphk		elan_mmcr[0xcb0 / 2] = 0x3333;
269111647Sphk		elan_mmcr[0xcb0 / 2] = 0xcccc;
270111647Sphk		elan_mmcr[0xcb0 / 2] = 0x4080;
271111647Sphk		elan_mmcr[0xc00 / 2] = u;
272111647Sphk		cur = 0;
273111647Sphk		return (0);
274111647Sphk	default:
275111647Sphk		return (EINVAL);
276111647Sphk	}
277111647Sphk
278111647Sphk}
279111647Sphk
280111647Sphkstatic int
281101225Sphkelan_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct  thread *tdr)
282101225Sphk{
283109327Sphk	int error;
284109327Sphk
285109327Sphk	error = ENOTTY;
286109327Sphk#ifdef ELAN_PPS
287109327Sphk	error = pps_ioctl(cmd, arg, &elan_pps);
288109327Sphk	/*
289109327Sphk	 * We only want to incur the overhead of the PPS polling if we
290109327Sphk	 * are actually asked to timestamp.
291109327Sphk	 */
292109327Sphk	if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT)
293109327Sphk		elan_timecounter.tc_poll_pps = elan_poll_pps;
294109327Sphk	else
295109327Sphk		elan_timecounter.tc_poll_pps = NULL;
296109327Sphk	if (error != ENOTTY)
297109327Sphk		return (error);
298109327Sphk#endif /* ELAN_PPS */
299109327Sphk
300111647Sphk	if (cmd == WDIOCPATPAT)
301111647Sphk		return elan_watchdog(*((u_int*)arg));
302111647Sphk
303109327Sphk	/* Other future ioctl handling here */
304109327Sphk	return(error);
305101225Sphk}
306101225Sphk
307