elan-mmcr.c revision 121944
1119452Sobrien/*- 2101225Sphk * ---------------------------------------------------------------------------- 3101225Sphk * "THE BEER-WARE LICENSE" (Revision 42): 4101225Sphk * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you 5101225Sphk * can do whatever you want with this stuff. If we meet some day, and you think 6101225Sphk * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 7101225Sphk * ---------------------------------------------------------------------------- 8101225Sphk * 9101225Sphk * 10102934Sphk * The AMD Elan sc520 is a system-on-chip gadget which is used in embedded 11102934Sphk * kind of things, see www.soekris.com for instance, and it has a few quirks 12102934Sphk * we need to deal with. 13102934Sphk * Unfortunately we cannot identify the gadget by CPUID output because it 14102934Sphk * depends on strapping options and only the stepping field may be useful 15102934Sphk * and those are undocumented from AMDs side. 16102934Sphk * 17102934Sphk * So instead we recognize the on-chip host-PCI bridge and call back from 18102934Sphk * sys/i386/pci/pci_bus.c to here if we find it. 19109327Sphk * 20109327Sphk * #ifdef ELAN_PPS 21109327Sphk * The Elan has three general purpose counters, which when used just right 22109327Sphk * can hardware timestamp external events with approx 250 nanoseconds 23109327Sphk * resolution _and_ precision. Connect the signal to TMR1IN and PIO7. 24109327Sphk * (You can use any PIO pin, look for PIO7 to change this). Use the 25109327Sphk * PPS-API on the /dev/elan-mmcr device. 26109327Sphk * #endif ELAN_PPS 27101225Sphk */ 28102934Sphk 29115683Sobrien#include <sys/cdefs.h> 30115683Sobrien__FBSDID("$FreeBSD: head/sys/i386/i386/elan-mmcr.c 121944 2003-11-03 11:03:40Z phk $"); 31115683Sobrien 32111138Sphk#include "opt_cpu.h" 33101225Sphk#include <sys/param.h> 34101225Sphk#include <sys/systm.h> 35101225Sphk#include <sys/kernel.h> 36101225Sphk#include <sys/conf.h> 37102934Sphk#include <sys/sysctl.h> 38102934Sphk#include <sys/timetc.h> 39102934Sphk#include <sys/proc.h> 40103482Sphk#include <sys/uio.h> 41103482Sphk#include <sys/lock.h> 42103482Sphk#include <sys/mutex.h> 43103482Sphk#include <sys/malloc.h> 44109327Sphk#include <sys/sysctl.h> 45109327Sphk#include <sys/timepps.h> 46111647Sphk#include <sys/watchdog.h> 47101225Sphk 48121944Sphk#include <dev/led/led.h> 49101225Sphk#include <machine/md_var.h> 50101225Sphk 51102934Sphk#include <vm/vm.h> 52102934Sphk#include <vm/pmap.h> 53102934Sphk 54102934Sphkuint16_t *elan_mmcr; 55102934Sphk 56109327Sphk#ifdef ELAN_PPS 57109327Sphk/* Relating to the PPS-api */ 58109327Sphkstatic struct pps_state elan_pps; 59109327Sphk 60109327Sphkstatic void 61109327Sphkelan_poll_pps(struct timecounter *tc) 62109327Sphk{ 63109327Sphk static int state; 64109327Sphk int i; 65109327Sphk 66109327Sphk /* XXX: This is PIO7, change to your preference */ 67109327Sphk i = elan_mmcr[0xc30 / 2] & 0x80; 68109327Sphk if (i == state) 69109327Sphk return; 70109327Sphk state = i; 71109327Sphk if (!state) 72109327Sphk return; 73109327Sphk pps_capture(&elan_pps); 74109327Sphk elan_pps.capcount = 75109327Sphk (elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2]) & 0xffff; 76109327Sphk pps_event(&elan_pps, PPS_CAPTUREASSERT); 77109327Sphk} 78109327Sphk#endif /* ELAN_PPS */ 79109327Sphk 80102935Sphkstatic unsigned 81102935Sphkelan_get_timecount(struct timecounter *tc) 82102935Sphk{ 83102935Sphk return (elan_mmcr[0xc84 / 2]); 84102935Sphk} 85102935Sphk 86109327Sphk/* 87109327Sphk * The Elan CPU can be run from a number of clock frequencies, this 88109327Sphk * allows you to override the default 33.3 MHZ. 89109327Sphk */ 90109327Sphk#ifndef ELAN_XTAL 91109327Sphk#define ELAN_XTAL 33333333 92109327Sphk#endif 93109327Sphk 94102935Sphkstatic struct timecounter elan_timecounter = { 95102935Sphk elan_get_timecount, 96109327Sphk NULL, 97102935Sphk 0xffff, 98109327Sphk ELAN_XTAL / 4, 99119715Sphk "ELAN", 100119715Sphk 1000 101102935Sphk}; 102102935Sphk 103109327Sphkstatic int 104109327Sphksysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS) 105109327Sphk{ 106109327Sphk u_int f; 107109327Sphk int error; 108109327Sphk 109109327Sphk f = elan_timecounter.tc_frequency * 4; 110109327Sphk error = sysctl_handle_int(oidp, &f, sizeof(f), req); 111109327Sphk if (error == 0 && req->newptr != NULL) 112109327Sphk elan_timecounter.tc_frequency = (f + 3) / 4; 113109327Sphk return (error); 114109327Sphk} 115109327Sphk 116109327SphkSYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW, 117109327Sphk 0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", ""); 118109327Sphk 119102934Sphkvoid 120102934Sphkinit_AMD_Elan_sc520(void) 121102934Sphk{ 122102934Sphk u_int new; 123102934Sphk int i; 124102934Sphk 125103168Ssam if (bootverbose) 126103168Ssam printf("Doing h0h0magic for AMD Elan sc520\n"); 127102934Sphk elan_mmcr = pmap_mapdev(0xfffef000, 0x1000); 128102934Sphk 129102934Sphk /*- 130102934Sphk * The i8254 is driven with a nonstandard frequency which is 131102934Sphk * derived thusly: 132102934Sphk * f = 32768 * 45 * 25 / 31 = 1189161.29... 133102934Sphk * We use the sysctl to get the timecounter etc into whack. 134102934Sphk */ 135102934Sphk 136102934Sphk new = 1189161; 137102934Sphk i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq", 138102934Sphk NULL, 0, 139102934Sphk &new, sizeof new, 140102934Sphk NULL); 141103168Ssam if (bootverbose) 142103168Ssam printf("sysctl machdep.i8254_freq=%d returns %d\n", new, i); 143102935Sphk 144102935Sphk /* Start GP timer #2 and use it as timecounter, hz permitting */ 145102935Sphk elan_mmcr[0xc82 / 2] = 0xc001; 146109327Sphk 147109327Sphk#ifdef ELAN_PPS 148109327Sphk /* Set up GP timer #1 as pps counter */ 149109327Sphk elan_mmcr[0xc24 / 2] &= ~0x10; 150109327Sphk elan_mmcr[0xc7a / 2] = 0x8000 | 0x4000 | 0x10 | 0x1; 151109327Sphk elan_pps.ppscap |= PPS_CAPTUREASSERT; 152109327Sphk pps_init(&elan_pps); 153109327Sphk#endif 154109327Sphk 155102935Sphk tc_init(&elan_timecounter); 156102934Sphk} 157102934Sphk 158101225Sphkstatic d_ioctl_t elan_ioctl; 159101225Sphkstatic d_mmap_t elan_mmap; 160101225Sphk 161121944Sphk#ifdef CPU_SOEKRIS 162121944Sphk/* Support for /dev/led/error */ 163121944Sphkstatic u_int soekris_errled_cookie = 0x200; 164121944Sphkstatic dev_t soekris_errled_dev; 165121944Sphk 166121944Sphkstatic void 167121944Sphkgpio_led(void *cookie, int state) 168121944Sphk{ 169121944Sphk u_int u; 170121944Sphk 171121944Sphk u = *(u_int *)cookie; 172121944Sphk 173121944Sphk if (state) 174121944Sphk elan_mmcr[0xc34 / 2] = u; 175121944Sphk else 176121944Sphk elan_mmcr[0xc38 / 2] = u; 177121944Sphk} 178121944Sphk#endif /* CPU_SOEKRIS */ 179121944Sphk 180103482Sphk#define ELAN_MMCR 0 181103482Sphk 182101225Sphkstatic struct cdevsw elan_cdevsw = { 183111815Sphk .d_ioctl = elan_ioctl, 184111815Sphk .d_mmap = elan_mmap, 185111815Sphk .d_name = "elan", 186101225Sphk}; 187101225Sphk 188103482Sphkstatic void 189103482Sphkelan_drvinit(void) 190101225Sphk{ 191103482Sphk 192103482Sphk if (elan_mmcr == NULL) 193103482Sphk return; 194103482Sphk printf("Elan-mmcr driver: MMCR at %p\n", elan_mmcr); 195103482Sphk make_dev(&elan_cdevsw, ELAN_MMCR, 196103482Sphk UID_ROOT, GID_WHEEL, 0600, "elan-mmcr"); 197121944Sphk 198121944Sphk#ifdef CPU_SOEKRIS 199121944Sphk soekris_errled_dev = 200121944Sphk led_create(gpio_led, &soekris_errled_cookie, "error"); 201121944Sphk#endif /* CPU_SOEKRIS */ 202103482Sphk return; 203101225Sphk} 204101225Sphk 205121942SphkSYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, elan_drvinit, NULL); 206103482Sphk 207101225Sphkstatic int 208112569Sjakeelan_mmap(dev_t dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) 209101225Sphk{ 210103482Sphk 211103482Sphk if (minor(dev) != ELAN_MMCR) 212103482Sphk return (EOPNOTSUPP); 213101225Sphk if (offset >= 0x1000) 214101225Sphk return (-1); 215111462Smux *paddr = 0xfffef000; 216111462Smux return (0); 217101225Sphk} 218101225Sphk 219101225Sphkstatic int 220111647Sphkelan_watchdog(u_int spec) 221111647Sphk{ 222111647Sphk u_int u, v; 223111647Sphk static u_int cur; 224111647Sphk 225111647Sphk if (spec & ~__WD_LEGAL) 226111647Sphk return (EINVAL); 227111647Sphk switch (spec & (WD_ACTIVE|WD_PASSIVE)) { 228111647Sphk case WD_ACTIVE: 229111647Sphk u = spec & WD_INTERVAL; 230111647Sphk if (u > 35) 231111647Sphk return (EINVAL); 232111647Sphk u = imax(u - 5, 24); 233111647Sphk v = 2 << (u - 24); 234111647Sphk v |= 0xc000; 235111647Sphk 236111647Sphk /* 237111647Sphk * There is a bug in some silicon which prevents us from 238111647Sphk * writing to the WDTMRCTL register if the GP echo mode is 239111647Sphk * enabled. GP echo mode on the other hand is desirable 240111647Sphk * for other reasons. Save and restore the GP echo mode 241111647Sphk * around our hardware tom-foolery. 242111647Sphk */ 243111647Sphk u = elan_mmcr[0xc00 / 2]; 244111647Sphk elan_mmcr[0xc00 / 2] = 0; 245111647Sphk if (v != cur) { 246111647Sphk /* Clear the ENB bit */ 247111647Sphk elan_mmcr[0xcb0 / 2] = 0x3333; 248111647Sphk elan_mmcr[0xcb0 / 2] = 0xcccc; 249111647Sphk elan_mmcr[0xcb0 / 2] = 0; 250111647Sphk 251111647Sphk /* Set new value */ 252111647Sphk elan_mmcr[0xcb0 / 2] = 0x3333; 253111647Sphk elan_mmcr[0xcb0 / 2] = 0xcccc; 254111647Sphk elan_mmcr[0xcb0 / 2] = v; 255111647Sphk cur = v; 256111647Sphk } else { 257111647Sphk /* Just reset timer */ 258111647Sphk elan_mmcr[0xcb0 / 2] = 0xaaaa; 259111647Sphk elan_mmcr[0xcb0 / 2] = 0x5555; 260111647Sphk } 261111647Sphk elan_mmcr[0xc00 / 2] = u; 262111647Sphk return (0); 263111647Sphk case WD_PASSIVE: 264111647Sphk return (EOPNOTSUPP); 265111647Sphk case 0: 266111647Sphk u = elan_mmcr[0xc00 / 2]; 267111647Sphk elan_mmcr[0xc00 / 2] = 0; 268111647Sphk elan_mmcr[0xcb0 / 2] = 0x3333; 269111647Sphk elan_mmcr[0xcb0 / 2] = 0xcccc; 270111647Sphk elan_mmcr[0xcb0 / 2] = 0x4080; 271111647Sphk elan_mmcr[0xc00 / 2] = u; 272111647Sphk cur = 0; 273111647Sphk return (0); 274111647Sphk default: 275111647Sphk return (EINVAL); 276111647Sphk } 277111647Sphk 278111647Sphk} 279111647Sphk 280111647Sphkstatic int 281101225Sphkelan_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct thread *tdr) 282101225Sphk{ 283109327Sphk int error; 284109327Sphk 285109327Sphk error = ENOTTY; 286109327Sphk#ifdef ELAN_PPS 287109327Sphk error = pps_ioctl(cmd, arg, &elan_pps); 288109327Sphk /* 289109327Sphk * We only want to incur the overhead of the PPS polling if we 290109327Sphk * are actually asked to timestamp. 291109327Sphk */ 292109327Sphk if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT) 293109327Sphk elan_timecounter.tc_poll_pps = elan_poll_pps; 294109327Sphk else 295109327Sphk elan_timecounter.tc_poll_pps = NULL; 296109327Sphk if (error != ENOTTY) 297109327Sphk return (error); 298109327Sphk#endif /* ELAN_PPS */ 299109327Sphk 300111647Sphk if (cmd == WDIOCPATPAT) 301111647Sphk return elan_watchdog(*((u_int*)arg)); 302111647Sphk 303109327Sphk /* Other future ioctl handling here */ 304109327Sphk return(error); 305101225Sphk} 306101225Sphk 307