summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Fincham <michael@hotplate.co.nz>2019-05-14 19:33:36 +1200
committerGitHub <noreply@github.com>2019-05-14 19:33:36 +1200
commited1293bdf54b5c3364c1fd01fccf45e5b3e1c96b (patch)
tree6bed4f57626246373303412a682cc7fb6918fd6c
parenteacdf5a3c58587ec071ae464cfb2425803dfa297 (diff)
downloadatomic-clock-master.tar.gz
atomic-clock-master.tar.bz2
atomic-clock-master.zip
Create inspiration.cHEADmaster
-rw-r--r--inspiration.c503
1 files changed, 503 insertions, 0 deletions
diff --git a/inspiration.c b/inspiration.c
new file mode 100644
index 0000000..390ceb7
--- /dev/null
+++ b/inspiration.c
@@ -0,0 +1,503 @@
+/*-
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ *
+ * The AMD Elan sc520 is a system-on-chip gadget which is used in embedded
+ * kind of things, see www.soekris.com for instance, and it has a few quirks
+ * we need to deal with.
+ * Unfortunately we cannot identify the gadget by CPUID output because it
+ * depends on strapping options and only the stepping field may be useful
+ * and those are undocumented from AMDs side.
+ *
+ * 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 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>
+__FBSDID("$FreeBSD: stable/8/sys/i386/i386/elan-mmcr.c 215043 2010-11-09 18:43:59Z jhb $");
+
+#include "opt_cpu.h"
+#include <sys/param.h>
+#include <sys/systm.h>
+#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>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+#include <sys/timepps.h>
+#include <sys/watchdog.h>
+
+#include <dev/led/led.h>
+#include <machine/md_var.h>
+#include <machine/elan_mmcr.h>
+#include <machine/pc/bios.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+static char gpio_config[33];
+
+static volatile uint16_t *mmcrptr;
+volatile struct elan_mmcr *elan_mmcr;
+
+#ifdef CPU_ELAN_PPS
+static struct pps_state elan_pps;
+static volatile uint16_t *pps_ap[3];
+static u_int pps_a, pps_d;
+static u_int echo_a, echo_d;
+#endif /* CPU_ELAN_PPS */
+
+#ifdef CPU_SOEKRIS
+
+static struct bios_oem bios_soekris = {
+ { 0xf0000, 0xf1000 },
+ {
+ { "Soekris", 0, 8 }, /* Soekris Engineering. */
+ { "net4", 0, 8 }, /* net45xx */
+ { "comBIOS", 0, 54 }, /* comBIOS ver. 1.26a 20040819 ... */
+ { NULL, 0, 0 },
+ }
+};
+
+#endif
+
+static u_int led_cookie[32];
+static struct cdev *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;
+ mmcrptr[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];
+ char 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] = gpio_config[i];
+ if (gpio_config[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;
+#ifdef CPU_SOEKRIS
+ if (i == 9)
+ ;
+ else
+#endif
+ if (buf[i] != 'l' && buf[i] != 'L' && led_dev[i] != NULL) {
+ led_destroy(led_dev[i]);
+ led_dev[i] = NULL;
+ mmcrptr[(0xc2a + v) / 2] &= ~u;
+ }
+ switch (buf[i]) {
+#ifdef CPU_ELAN_PPS
+ case 'P':
+ pps_d = u;
+ pps_a = 0xc30 + v;
+ pps_ap[0] = &mmcrptr[pps_a / 2];
+ pps_ap[1] = &elan_mmcr->GPTMR2CNT;
+ pps_ap[2] = &elan_mmcr->GPTMR1CNT;
+ mmcrptr[(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;
+ mmcrptr[(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);
+ mmcrptr[(0xc2a + v) / 2] |= u;
+ gpio_config[i] = buf[i];
+ led_dev[i] =
+ led_create(gpio_led, &led_cookie[i], tmp);
+ 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;
+ uint16_t u, x, y, z;
+ register_t saveintr;
+
+ /*
+ * Grab the HW state as quickly and compactly as we can. Disable
+ * interrupts to avoid measuring our interrupt service time on
+ * hw with quality clock sources.
+ */
+ saveintr = intr_disable();
+ x = *pps_ap[0]; /* state, must be first, see below */
+ y = *pps_ap[1]; /* timer2 */
+ z = *pps_ap[2]; /* timer1 */
+ intr_restore(saveintr);
+
+ /*
+ * 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 = x & pps_d;
+
+ /* If state did not change or we don't have a GPIO pin, return */
+ if (i == state || pps_a == 0)
+ return;
+
+ state = i;
+
+ /* If the state is "low", flip the echo GPIO and return. */
+ if (!i) {
+ if (echo_a)
+ mmcrptr[(echo_a ^ 0xc) / 2] = echo_d;
+ return;
+ }
+
+ /*
+ * Subtract timer1 from timer2 to compensate for time from the
+ * edge until we read the counters.
+ */
+ u = y - z;
+
+ pps_capture(&elan_pps);
+ elan_pps.capcount = u;
+ pps_event(&elan_pps, PPS_CAPTUREASSERT);
+
+ /* Twiddle echo bit */
+ if (echo_a)
+ mmcrptr[echo_a / 2] = echo_d;
+}
+#endif /* CPU_ELAN_PPS */
+
+static unsigned
+elan_get_timecount(struct timecounter *tc)
+{
+
+ /* Read timer2, end of story */
+ return (elan_mmcr->GPTMR2CNT);
+}
+
+/*
+ * The Elan CPU can be run from a number of clock frequencies, this
+ * allows you to override the default 33.3 MHZ.
+ */
+#ifndef CPU_ELAN_XTAL
+#define CPU_ELAN_XTAL 33333333
+#endif
+
+static struct timecounter elan_timecounter = {
+ elan_get_timecount,
+ NULL,
+ 0xffff,
+ CPU_ELAN_XTAL / 4,
+ "ELAN",
+ 1000
+};
+
+static int
+sysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS)
+{
+ u_int f;
+ int error;
+
+ f = elan_timecounter.tc_frequency * 4;
+ error = sysctl_handle_int(oidp, &f, 0, req);
+ if (error == 0 && req->newptr != NULL)
+ elan_timecounter.tc_frequency = (f + 3) / 4;
+ return (error);
+}
+
+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;
+
+ mmcrptr = pmap_mapdev(0xfffef000, 0x1000);
+ elan_mmcr = (volatile struct elan_mmcr *)mmcrptr;
+
+ /*-
+ * 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 i8254 (timecounter etc) into whack.
+ */
+
+ new = 1189161;
+ i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq",
+ NULL, 0, &new, sizeof new, NULL, 0);
+ 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->GPTMR2MAXCMPA = 0;
+ elan_mmcr->GPTMR2CTL = 0xc001;
+
+#ifdef CPU_ELAN_PPS
+ /* Set up GP timer #1 as pps counter */
+ elan_mmcr->CSPFS &= ~0x10;
+ elan_mmcr->GPTMR1CTL = 0x8000 | 0x4000 | 0x10 | 0x1;
+ elan_mmcr->GPTMR1MAXCMPA = 0x0;
+ elan_mmcr->GPTMR1MAXCMPB = 0x0;
+ elan_pps.ppscap |= PPS_CAPTUREASSERT;
+ pps_init(&elan_pps);
+#endif
+ tc_init(&elan_timecounter);
+}
+
+static void
+elan_watchdog(void *foo __unused, u_int spec, int *error)
+{
+ u_int u, v, w;
+ static u_int cur;
+
+ u = spec & WD_INTERVAL;
+ if (u > 0 && u <= 35) {
+ u = imax(u - 5, 24);
+ v = 2 << (u - 24);
+ v |= 0xc000;
+
+ /*
+ * There is a bug in some silicon which prevents us from
+ * writing to the WDTMRCTL register if the GP echo mode is
+ * enabled. GP echo mode on the other hand is desirable
+ * for other reasons. Save and restore the GP echo mode
+ * around our hardware tom-foolery.
+ */
+ w = elan_mmcr->GPECHO;
+ elan_mmcr->GPECHO = 0;
+ if (v != cur) {
+ /* Clear the ENB bit */
+ elan_mmcr->WDTMRCTL = 0x3333;
+ elan_mmcr->WDTMRCTL = 0xcccc;
+ elan_mmcr->WDTMRCTL = 0;
+
+ /* Set new value */
+ elan_mmcr->WDTMRCTL = 0x3333;
+ elan_mmcr->WDTMRCTL = 0xcccc;
+ elan_mmcr->WDTMRCTL = v;
+ cur = v;
+ } else {
+ /* Just reset timer */
+ elan_mmcr->WDTMRCTL = 0xaaaa;
+ elan_mmcr->WDTMRCTL = 0x5555;
+ }
+ elan_mmcr->GPECHO = w;
+ *error = 0;
+ } else {
+ w = elan_mmcr->GPECHO;
+ elan_mmcr->GPECHO = 0;
+ elan_mmcr->WDTMRCTL = 0x3333;
+ elan_mmcr->WDTMRCTL = 0xcccc;
+ elan_mmcr->WDTMRCTL = 0x4080;
+ elan_mmcr->WDTMRCTL = w; /* XXX What does this statement do? */
+ elan_mmcr->GPECHO = w;
+ cur = 0;
+ }
+}
+
+static int
+elan_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
+{
+
+ if (offset >= 0x1000)
+ return (-1);
+ *paddr = 0xfffef000;
+ return (0);
+}
+static int
+elan_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *tdr)
+{
+ int error;
+
+ error = ENOIOCTL;
+
+#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) {
+ elan_timecounter.tc_poll_pps = elan_poll_pps;
+ } else {
+ elan_timecounter.tc_poll_pps = NULL;
+ }
+ if (error != ENOIOCTL)
+ return (error);
+#endif
+
+ return(error);
+}
+
+static struct cdevsw elan_cdevsw = {
+ .d_version = D_VERSION,
+ .d_flags = D_NEEDGIANT,
+ .d_ioctl = elan_ioctl,
+ .d_mmap = elan_mmap,
+ .d_name = "elan",
+};
+
+static void
+elan_drvinit(void)
+{
+
+#ifdef CPU_SOEKRIS
+#define BIOS_OEM_MAXLEN 72
+ static u_char bios_oem[BIOS_OEM_MAXLEN] = "\0";
+#endif /* CPU_SOEKRIS */
+
+ /* If no elan found, just return */
+ if (mmcrptr == NULL)
+ return;
+
+ printf("Elan-mmcr driver: MMCR at %p.%s\n",
+ mmcrptr,
+#ifdef CPU_ELAN_PPS
+ " PPS support."
+#else
+ ""
+#endif
+ );
+
+ make_dev(&elan_cdevsw, 0,
+ UID_ROOT, GID_WHEEL, 0600, "elan-mmcr");
+
+#ifdef CPU_SOEKRIS
+ if ( bios_oem_strings(&bios_soekris, bios_oem, BIOS_OEM_MAXLEN) > 0 )
+ printf("Elan-mmcr %s\n", bios_oem);
+
+ /* 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 */
+
+ EVENTHANDLER_REGISTER(watchdog_list, elan_watchdog, NULL, 0);
+}
+
+SYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, elan_drvinit, NULL);