text: /*-
   * ----------------------------------------------------------------------------
   * "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: releng/9.3/sys/i386/i386/elan-mmcr.c 214346 2010-10-25 15:28:03Z 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_ooffset_t offset, vm_paddr_t *paddr,
      int nprot, vm_memattr_t *memattr)
  {
  
  	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);
  
  