1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-18 10:35:55 +00:00

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@
This commit is contained in:
Poul-Henning Kamp 2003-11-27 20:27:29 +00:00
parent 9491306ad3
commit 173c971bd6
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=123015

View File

@ -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));