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:
parent
9491306ad3
commit
173c971bd6
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=123015
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user