summaryrefslogtreecommitdiffstats
path: root/sys/i386
diff options
context:
space:
mode:
authorphk <phk@FreeBSD.org>2003-11-27 20:27:29 +0000
committerphk <phk@FreeBSD.org>2003-11-27 20:27:29 +0000
commiteb312cd01c001326cc52e03a1752662f041bb22d (patch)
treefe87032b969b66b0b67c81aad7885002e655899d /sys/i386
parent9657f39ceda86d7a63e97b4101092ac3976a696e (diff)
downloadFreeBSD-src-eb312cd01c001326cc52e03a1752662f041bb22d.zip
FreeBSD-src-eb312cd01c001326cc52e03a1752662f041bb22d.tar.gz
Refactor AMD Elan 520 CPU support.
Make it possible to configure GPIO pins as led(4) devices, PPS inputs and PPS-echo outputs with a sysctl. Led(4) and PPS-echo can be configured for active-high or active-low. Be more complete in initialization of timecounter hardware. Approved by: re@
Diffstat (limited to 'sys/i386')
-rw-r--r--sys/i386/i386/elan-mmcr.c283
1 files changed, 222 insertions, 61 deletions
diff --git a/sys/i386/i386/elan-mmcr.c b/sys/i386/i386/elan-mmcr.c
index 1d37299..876193c 100644
--- a/sys/i386/i386/elan-mmcr.c
+++ b/sys/i386/i386/elan-mmcr.c
@@ -17,13 +17,25 @@
* So instead we recognize the on-chip host-PCI bridge and call back from
* sys/i386/pci/pci_bus.c to here if we find it.
*
- * #ifdef ELAN_PPS
- * The Elan has three general purpose counters, which when used just right
- * can hardware timestamp external events with approx 250 nanoseconds
- * resolution _and_ precision. Connect the signal to TMR1IN and PIO7.
- * (You can use any PIO pin, look for PIO7 to change this). Use the
- * PPS-API on the /dev/elan-mmcr device.
- * #endif ELAN_PPS
+ * #ifdef CPU_ELAN_PPS
+ * The Elan has three general purpose counters, and when two of these
+ * are used just right they can hardware timestamp external events with
+ * approx 125 nsec resolution and +/- 125 nsec precision.
+ *
+ * Connect the signal to TMR1IN and a GPIO pin, and configure the GPIO pin
+ * with a 'P' in sysctl machdep.elan_gpio_config.
+ *
+ * The rising edge of the signal will start timer 1 counting up from
+ * zero, and when the timecounter polls for PPS, both counter 1 & 2 is
+ * read, as well as the GPIO bit. If a rising edge has happened, the
+ * contents of timer 1 which is how long time ago the edge happened,
+ * is subtracted from timer 2 to give us a "true time stamp".
+ *
+ * Echoing the PPS signal on any GPIO pin is supported (set it to 'e'
+ * or 'E' (inverted) in the sysctl) The echo signal should only be
+ * used as a visual indication, not for calibration since it suffers
+ * from 1/hz (or more) jitter which the timestamps are compensated for.
+ * #endif CPU_ELAN_PPS
*/
#include <sys/cdefs.h>
@@ -35,6 +47,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/sysctl.h>
+#include <sys/syslog.h>
#include <sys/timetc.h>
#include <sys/proc.h>
#include <sys/uio.h>
@@ -51,35 +64,185 @@ __FBSDID("$FreeBSD$");
#include <vm/vm.h>
#include <vm/pmap.h>
+static char gpio_config[33];
+
uint16_t *elan_mmcr;
-#ifdef ELAN_PPS
-/* Relating to the PPS-api */
+#ifdef CPU_ELAN_PPS
static struct pps_state elan_pps;
+u_int pps_a, pps_d;
+u_int echo_a, echo_d;
+#endif /* CPU_ELAN_PPS */
+u_int led_cookie[32];
+dev_t led_dev[32];
static void
+gpio_led(void *cookie, int state)
+{
+ u_int u, v;
+
+ u = *(int *)cookie;
+ v = u & 0xffff;
+ u >>= 16;
+ if (!state)
+ v ^= 0xc;
+ elan_mmcr[v / 2] = u;
+}
+
+static int
+sysctl_machdep_elan_gpio_config(SYSCTL_HANDLER_ARGS)
+{
+ u_int u, v;
+ int i, np, ne;
+ int error;
+ char buf[32], tmp[10];
+
+ error = SYSCTL_OUT(req, gpio_config, 33);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+ if (req->newlen != 32)
+ return (EINVAL);
+ error = SYSCTL_IN(req, buf, 32);
+ if (error != 0)
+ return (error);
+ /* Disallow any disabled pins and count pps and echo */
+ np = ne = 0;
+ for (i = 0; i < 32; i++) {
+ if (gpio_config[i] == '-' && (buf[i] != '-' && buf[i] != '.'))
+ return (EPERM);
+ if (buf[i] == 'P') {
+ np++;
+ if (np > 1)
+ return (EINVAL);
+ }
+ if (buf[i] == 'e' || buf[i] == 'E') {
+ ne++;
+ if (ne > 1)
+ return (EINVAL);
+ }
+ if (buf[i] != 'L' && buf[i] != 'l'
+#ifdef CPU_ELAN_PPS
+ && buf[i] != 'P' && buf[i] != 'E' && buf[i] != 'e'
+#endif /* CPU_ELAN_PPS */
+ && buf[i] != '.' && buf[i] != '-')
+ return (EINVAL);
+ }
+#ifdef CPU_ELAN_PPS
+ if (np == 0)
+ pps_a = pps_d = 0;
+ if (ne == 0)
+ echo_a = echo_d = 0;
+#endif
+ for (i = 0; i < 32; i++) {
+ u = 1 << (i & 0xf);
+ if (i >= 16)
+ v = 2;
+ else
+ v = 0;
+ if (buf[i] != 'l' && buf[i] != 'L' && led_dev[i] != NULL) {
+ led_destroy(led_dev[i]);
+ led_dev[i] = NULL;
+ elan_mmcr[(0xc2a + v) / 2] &= ~u;
+ }
+ switch (buf[i]) {
+#ifdef CPU_ELAN_PPS
+ case 'P':
+ pps_d = u;
+ pps_a = 0xc30 + v;
+ elan_mmcr[(0xc2a + v) / 2] &= ~u;
+ gpio_config[i] = buf[i];
+ break;
+ case 'e':
+ case 'E':
+ echo_d = u;
+ if (buf[i] == 'E')
+ echo_a = 0xc34 + v;
+ else
+ echo_a = 0xc38 + v;
+ elan_mmcr[(0xc2a + v) / 2] |= u;
+ gpio_config[i] = buf[i];
+ break;
+#endif /* CPU_ELAN_PPS */
+ case 'l':
+ case 'L':
+ if (buf[i] == 'L')
+ led_cookie[i] = (0xc34 + v) | (u << 16);
+ else
+ led_cookie[i] = (0xc38 + v) | (u << 16);
+ if (led_dev[i])
+ break;
+ sprintf(tmp, "gpio%d", i);
+ led_dev[i] =
+ led_create(gpio_led, &led_cookie[i], tmp);
+ elan_mmcr[(0xc2a + v) / 2] |= u;
+ gpio_config[i] = buf[i];
+ break;
+ case '.':
+ gpio_config[i] = buf[i];
+ break;
+ case '-':
+ default:
+ break;
+ }
+ }
+ return (0);
+}
+
+SYSCTL_OID(_machdep, OID_AUTO, elan_gpio_config, CTLTYPE_STRING | CTLFLAG_RW,
+ NULL, 0, sysctl_machdep_elan_gpio_config, "A", "Elan CPU GPIO pin config");
+
+#ifdef CPU_ELAN_PPS
+static void
elan_poll_pps(struct timecounter *tc)
{
static int state;
int i;
+ u_int u;
- /* XXX: This is PIO7, change to your preference */
- i = elan_mmcr[0xc30 / 2] & 0x80;
- if (i == state)
+ /*
+ * Order is important here. We need to check the state of the GPIO
+ * pin first, in order to avoid reading timer 1 right before the
+ * state change. Technically pps_a may be zero in which case we
+ * harmlessly read the REVID register and the contents of pps_d is
+ * of no concern.
+ */
+ i = elan_mmcr[pps_a / 2] & pps_d;
+
+ /*
+ * Subtract timer1 from timer2 to compensate for time from the
+ * edge until now.
+ */
+ u = elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2];
+
+ /* If state did not change or we don't have a GPIO pin, return */
+ if (i == state || pps_a == 0)
return;
+
state = i;
- if (!state)
+
+ /* If the state is "low", flip the echo GPIO and return. */
+ if (!i) {
+ if (echo_a)
+ elan_mmcr[(echo_a ^ 0xc) / 2] = echo_d;
return;
+ }
+
+ /* State is "high", record the pps data */
pps_capture(&elan_pps);
- elan_pps.capcount =
- (elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2]) & 0xffff;
+ elan_pps.capcount = u & 0xffff;
pps_event(&elan_pps, PPS_CAPTUREASSERT);
+
+ /* Twiddle echo bit */
+ if (echo_a)
+ elan_mmcr[echo_a / 2] = echo_d;
}
-#endif /* ELAN_PPS */
+#endif /* CPU_ELAN_PPS */
static unsigned
elan_get_timecount(struct timecounter *tc)
{
+
+ /* Read timer2, end of story */
return (elan_mmcr[0xc84 / 2]);
}
@@ -87,15 +250,15 @@ elan_get_timecount(struct timecounter *tc)
* The Elan CPU can be run from a number of clock frequencies, this
* allows you to override the default 33.3 MHZ.
*/
-#ifndef ELAN_XTAL
-#define ELAN_XTAL 33333333
+#ifndef CPU_ELAN_XTAL
+#define CPU_ELAN_XTAL 33333333
#endif
static struct timecounter elan_timecounter = {
elan_get_timecount,
NULL,
0xffff,
- ELAN_XTAL / 4,
+ CPU_ELAN_XTAL / 4,
"ELAN",
1000
};
@@ -116,69 +279,50 @@ sysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS)
SYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW,
0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", "");
+/*
+ * Positively identifying the Elan can only be done through the PCI id of
+ * the host-bridge, this function is called from i386/pci/pci_bus.c.
+ */
void
init_AMD_Elan_sc520(void)
{
u_int new;
int i;
- if (bootverbose)
- printf("Doing h0h0magic for AMD Elan sc520\n");
elan_mmcr = pmap_mapdev(0xfffef000, 0x1000);
/*-
* The i8254 is driven with a nonstandard frequency which is
* derived thusly:
* f = 32768 * 45 * 25 / 31 = 1189161.29...
- * We use the sysctl to get the timecounter etc into whack.
+ * We use the sysctl to get the i8254 (timecounter etc) into whack.
*/
new = 1189161;
i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq",
- NULL, 0,
- &new, sizeof new,
- NULL);
- if (bootverbose)
+ NULL, 0, &new, sizeof new, NULL);
+ if (bootverbose || 1)
printf("sysctl machdep.i8254_freq=%d returns %d\n", new, i);
/* Start GP timer #2 and use it as timecounter, hz permitting */
+ elan_mmcr[0xc8e / 2] = 0x0;
elan_mmcr[0xc82 / 2] = 0xc001;
-#ifdef ELAN_PPS
+#ifdef CPU_ELAN_PPS
/* Set up GP timer #1 as pps counter */
elan_mmcr[0xc24 / 2] &= ~0x10;
elan_mmcr[0xc7a / 2] = 0x8000 | 0x4000 | 0x10 | 0x1;
+ elan_mmcr[0xc7e / 2] = 0x0;
+ elan_mmcr[0xc80 / 2] = 0x0;
elan_pps.ppscap |= PPS_CAPTUREASSERT;
pps_init(&elan_pps);
#endif
-
tc_init(&elan_timecounter);
}
static d_ioctl_t elan_ioctl;
static d_mmap_t elan_mmap;
-#ifdef CPU_SOEKRIS
-/* Support for /dev/led/error */
-static u_int soekris_errled_cookie = 0x200;
-static dev_t soekris_errled_dev;
-
-static void
-gpio_led(void *cookie, int state)
-{
- u_int u;
-
- u = *(u_int *)cookie;
-
- if (state)
- elan_mmcr[0xc34 / 2] = u;
- else
- elan_mmcr[0xc38 / 2] = u;
-}
-#endif /* CPU_SOEKRIS */
-
-#define ELAN_MMCR 0
-
static struct cdevsw elan_cdevsw = {
.d_ioctl = elan_ioctl,
.d_mmap = elan_mmap,
@@ -189,17 +333,33 @@ static void
elan_drvinit(void)
{
+ /* If no elan found, just return */
if (elan_mmcr == NULL)
return;
- printf("Elan-mmcr driver: MMCR at %p\n", elan_mmcr);
- make_dev(&elan_cdevsw, ELAN_MMCR,
+
+ printf("Elan-mmcr driver: MMCR at %p.%s\n",
+ elan_mmcr,
+#ifdef CPU_ELAN_PPS
+ " PPS support."
+#else
+ ""
+#endif
+ );
+
+ make_dev(&elan_cdevsw, 0,
UID_ROOT, GID_WHEEL, 0600, "elan-mmcr");
#ifdef CPU_SOEKRIS
- soekris_errled_dev =
- led_create(gpio_led, &soekris_errled_cookie, "error");
+ /* Create the error LED on GPIO9 */
+ led_cookie[9] = 0x02000c34;
+ led_dev[9] = led_create(gpio_led, &led_cookie[9], "error");
+
+ /* Disable the unavailable GPIO pins */
+ strcpy(gpio_config, "-----....--..--------..---------");
+#else /* !CPU_SOEKRIS */
+ /* We don't know which pins are available so enable them all */
+ strcpy(gpio_config, "................................");
#endif /* CPU_SOEKRIS */
- return;
}
SYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, elan_drvinit, NULL);
@@ -208,8 +368,6 @@ static int
elan_mmap(dev_t dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
{
- if (minor(dev) != ELAN_MMCR)
- return (EOPNOTSUPP);
if (offset >= 0x1000)
return (-1);
*paddr = 0xfffef000;
@@ -283,19 +441,22 @@ elan_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct thread *tdr)
int error;
error = ENOTTY;
-#ifdef ELAN_PPS
- error = pps_ioctl(cmd, arg, &elan_pps);
+
+#ifdef CPU_ELAN_PPS
+ if (pps_a != 0)
+ error = pps_ioctl(cmd, arg, &elan_pps);
/*
* We only want to incur the overhead of the PPS polling if we
* are actually asked to timestamp.
*/
- if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT)
+ if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT) {
elan_timecounter.tc_poll_pps = elan_poll_pps;
- else
+ } else {
elan_timecounter.tc_poll_pps = NULL;
+ }
if (error != ENOTTY)
return (error);
-#endif /* ELAN_PPS */
+#endif
if (cmd == WDIOCPATPAT)
return elan_watchdog(*((u_int*)arg));
OpenPOWER on IntegriCloud