elan-mmcr.c revision 119452
1/*- 2 * ---------------------------------------------------------------------------- 3 * "THE BEER-WARE LICENSE" (Revision 42): 4 * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you 5 * can do whatever you want with this stuff. If we meet some day, and you think 6 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 7 * ---------------------------------------------------------------------------- 8 * 9 * 10 * The AMD Elan sc520 is a system-on-chip gadget which is used in embedded 11 * kind of things, see www.soekris.com for instance, and it has a few quirks 12 * we need to deal with. 13 * Unfortunately we cannot identify the gadget by CPUID output because it 14 * depends on strapping options and only the stepping field may be useful 15 * and those are undocumented from AMDs side. 16 * 17 * So instead we recognize the on-chip host-PCI bridge and call back from 18 * sys/i386/pci/pci_bus.c to here if we find it. 19 * 20 * #ifdef ELAN_PPS 21 * The Elan has three general purpose counters, which when used just right 22 * can hardware timestamp external events with approx 250 nanoseconds 23 * resolution _and_ precision. Connect the signal to TMR1IN and PIO7. 24 * (You can use any PIO pin, look for PIO7 to change this). Use the 25 * PPS-API on the /dev/elan-mmcr device. 26 * #endif ELAN_PPS 27 */ 28 29#include <sys/cdefs.h> 30__FBSDID("$FreeBSD: head/sys/i386/i386/elan-mmcr.c 119452 2003-08-25 09:48:48Z obrien $"); 31 32#include "opt_cpu.h" 33#include <sys/param.h> 34#include <sys/systm.h> 35#include <sys/kernel.h> 36#include <sys/conf.h> 37#include <sys/sysctl.h> 38#include <sys/timetc.h> 39#include <sys/proc.h> 40#include <sys/uio.h> 41#include <sys/lock.h> 42#include <sys/mutex.h> 43#include <sys/malloc.h> 44#include <sys/sysctl.h> 45#include <sys/timepps.h> 46#include <sys/watchdog.h> 47 48#include <machine/md_var.h> 49 50#include <vm/vm.h> 51#include <vm/pmap.h> 52 53uint16_t *elan_mmcr; 54 55/* Relating to the /dev/soekris-errled */ 56static struct mtx errled_mtx; 57static char *errled; 58static struct callout_handle errled_h = CALLOUT_HANDLE_INITIALIZER(&errled_h); 59static void timeout_errled(void *); 60 61#ifdef ELAN_PPS 62/* Relating to the PPS-api */ 63static struct pps_state elan_pps; 64 65static void 66elan_poll_pps(struct timecounter *tc) 67{ 68 static int state; 69 int i; 70 71 /* XXX: This is PIO7, change to your preference */ 72 i = elan_mmcr[0xc30 / 2] & 0x80; 73 if (i == state) 74 return; 75 state = i; 76 if (!state) 77 return; 78 pps_capture(&elan_pps); 79 elan_pps.capcount = 80 (elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2]) & 0xffff; 81 pps_event(&elan_pps, PPS_CAPTUREASSERT); 82} 83#endif /* ELAN_PPS */ 84 85static unsigned 86elan_get_timecount(struct timecounter *tc) 87{ 88 return (elan_mmcr[0xc84 / 2]); 89} 90 91/* 92 * The Elan CPU can be run from a number of clock frequencies, this 93 * allows you to override the default 33.3 MHZ. 94 */ 95#ifndef ELAN_XTAL 96#define ELAN_XTAL 33333333 97#endif 98 99static struct timecounter elan_timecounter = { 100 elan_get_timecount, 101 NULL, 102 0xffff, 103 ELAN_XTAL / 4, 104 "ELAN" 105}; 106 107static int 108sysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS) 109{ 110 u_int f; 111 int error; 112 113 f = elan_timecounter.tc_frequency * 4; 114 error = sysctl_handle_int(oidp, &f, sizeof(f), req); 115 if (error == 0 && req->newptr != NULL) 116 elan_timecounter.tc_frequency = (f + 3) / 4; 117 return (error); 118} 119 120SYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW, 121 0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", ""); 122 123void 124init_AMD_Elan_sc520(void) 125{ 126 u_int new; 127 int i; 128 129 if (bootverbose) 130 printf("Doing h0h0magic for AMD Elan sc520\n"); 131 elan_mmcr = pmap_mapdev(0xfffef000, 0x1000); 132 133 /*- 134 * The i8254 is driven with a nonstandard frequency which is 135 * derived thusly: 136 * f = 32768 * 45 * 25 / 31 = 1189161.29... 137 * We use the sysctl to get the timecounter etc into whack. 138 */ 139 140 new = 1189161; 141 i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq", 142 NULL, 0, 143 &new, sizeof new, 144 NULL); 145 if (bootverbose) 146 printf("sysctl machdep.i8254_freq=%d returns %d\n", new, i); 147 148 /* Start GP timer #2 and use it as timecounter, hz permitting */ 149 elan_mmcr[0xc82 / 2] = 0xc001; 150 151#ifdef ELAN_PPS 152 /* Set up GP timer #1 as pps counter */ 153 elan_mmcr[0xc24 / 2] &= ~0x10; 154 elan_mmcr[0xc7a / 2] = 0x8000 | 0x4000 | 0x10 | 0x1; 155 elan_pps.ppscap |= PPS_CAPTUREASSERT; 156 pps_init(&elan_pps); 157#endif 158 159 tc_init(&elan_timecounter); 160} 161 162 163/* 164 * Device driver initialization stuff 165 */ 166 167static d_write_t elan_write; 168static d_ioctl_t elan_ioctl; 169static d_mmap_t elan_mmap; 170 171#define ELAN_MMCR 0 172#define ELAN_ERRLED 1 173 174#define CDEV_MAJOR 100 /* Share with xrpu */ 175static struct cdevsw elan_cdevsw = { 176 .d_open = nullopen, 177 .d_close = nullclose, 178 .d_write = elan_write, 179 .d_ioctl = elan_ioctl, 180 .d_mmap = elan_mmap, 181 .d_name = "elan", 182 .d_maj = CDEV_MAJOR, 183}; 184 185static void 186elan_drvinit(void) 187{ 188 189 if (elan_mmcr == NULL) 190 return; 191 printf("Elan-mmcr driver: MMCR at %p\n", elan_mmcr); 192 make_dev(&elan_cdevsw, ELAN_MMCR, 193 UID_ROOT, GID_WHEEL, 0600, "elan-mmcr"); 194 make_dev(&elan_cdevsw, ELAN_ERRLED, 195 UID_ROOT, GID_WHEEL, 0600, "soekris-errled"); 196 mtx_init(&errled_mtx, "Elan-errled", MTX_DEF, 0); 197 return; 198} 199 200SYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE+CDEV_MAJOR,elan_drvinit,NULL); 201 202#define LED_ON() do {elan_mmcr[0xc34 / 2] = 0x200;} while(0) 203#define LED_OFF() do {elan_mmcr[0xc38 / 2] = 0x200;} while(0) 204 205static void 206timeout_errled(void *p) 207{ 208 static enum {NOTHING, FLASH, DIGIT} mode; 209 static int count, cnt2, state; 210 211 mtx_lock(&errled_mtx); 212 if (p != NULL) { 213 mode = NOTHING; 214 /* Our instructions changed */ 215 if (*errled == '1') { /* Turn LED on */ 216 LED_ON(); 217 } else if (*errled == '0') { /* Turn LED off */ 218 LED_OFF(); 219 } else if (*errled == 'f') { /* Flash */ 220 mode = FLASH; 221 cnt2 = 10; 222 if (errled[1] >= '1' && errled[1] <= '9') 223 cnt2 = errled[1] - '0'; 224 cnt2 = hz / cnt2; 225 LED_ON(); 226 errled_h = timeout(timeout_errled, NULL, cnt2); 227 } else if (*errled == 'd') { /* Digit */ 228 mode = DIGIT; 229 count = 0; 230 cnt2 = 0; 231 state = 0; 232 LED_OFF(); 233 errled_h = timeout(timeout_errled, NULL, hz/10); 234 } 235 } else if (mode == FLASH) { 236 if (count) 237 LED_ON(); 238 else 239 LED_OFF(); 240 count = !count; 241 errled_h = timeout(timeout_errled, NULL, cnt2); 242 } else if (mode == DIGIT) { 243 if (cnt2 > 0) { 244 if (state) { 245 LED_OFF(); 246 state = 0; 247 cnt2--; 248 } else { 249 LED_ON(); 250 state = 1; 251 } 252 errled_h = timeout(timeout_errled, NULL, hz/5); 253 } else { 254 do 255 count++; 256 while (errled[count] != '\0' && 257 (errled[count] < '0' || errled[count] > '9')); 258 if (errled[count] == '\0') { 259 count = 0; 260 errled_h = timeout(timeout_errled, NULL, hz * 2); 261 } else { 262 cnt2 = errled[count] - '0'; 263 state = 0; 264 errled_h = timeout(timeout_errled, NULL, hz); 265 } 266 } 267 } 268 mtx_unlock(&errled_mtx); 269 return; 270} 271 272/* 273 * The write function is used for the error-LED. 274 */ 275 276static int 277elan_write(dev_t dev, struct uio *uio, int ioflag) 278{ 279 int error; 280 char *s, *q; 281 282 if (minor(dev) != ELAN_ERRLED) 283 return (EOPNOTSUPP); 284 285 if (uio->uio_resid > 512) 286 return (EINVAL); 287 s = malloc(uio->uio_resid + 1, M_DEVBUF, M_WAITOK); 288 if (s == NULL) 289 return (ENOMEM); 290 untimeout(timeout_errled, NULL, errled_h); 291 s[uio->uio_resid] = '\0'; 292 error = uiomove(s, uio->uio_resid, uio); 293 if (error) { 294 free(s, M_DEVBUF); 295 return (error); 296 } 297 mtx_lock(&errled_mtx); 298 q = errled; 299 errled = s; 300 mtx_unlock(&errled_mtx); 301 if (q != NULL) 302 free(q, M_DEVBUF); 303 timeout_errled(errled); 304 305 return(0); 306} 307 308static int 309elan_mmap(dev_t dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) 310{ 311 312 if (minor(dev) != ELAN_MMCR) 313 return (EOPNOTSUPP); 314 if (offset >= 0x1000) 315 return (-1); 316 *paddr = 0xfffef000; 317 return (0); 318} 319 320static int 321elan_watchdog(u_int spec) 322{ 323 u_int u, v; 324 static u_int cur; 325 326 if (spec & ~__WD_LEGAL) 327 return (EINVAL); 328 switch (spec & (WD_ACTIVE|WD_PASSIVE)) { 329 case WD_ACTIVE: 330 u = spec & WD_INTERVAL; 331 if (u > 35) 332 return (EINVAL); 333 u = imax(u - 5, 24); 334 v = 2 << (u - 24); 335 v |= 0xc000; 336 337 /* 338 * There is a bug in some silicon which prevents us from 339 * writing to the WDTMRCTL register if the GP echo mode is 340 * enabled. GP echo mode on the other hand is desirable 341 * for other reasons. Save and restore the GP echo mode 342 * around our hardware tom-foolery. 343 */ 344 u = elan_mmcr[0xc00 / 2]; 345 elan_mmcr[0xc00 / 2] = 0; 346 if (v != cur) { 347 /* Clear the ENB bit */ 348 elan_mmcr[0xcb0 / 2] = 0x3333; 349 elan_mmcr[0xcb0 / 2] = 0xcccc; 350 elan_mmcr[0xcb0 / 2] = 0; 351 352 /* Set new value */ 353 elan_mmcr[0xcb0 / 2] = 0x3333; 354 elan_mmcr[0xcb0 / 2] = 0xcccc; 355 elan_mmcr[0xcb0 / 2] = v; 356 cur = v; 357 } else { 358 /* Just reset timer */ 359 elan_mmcr[0xcb0 / 2] = 0xaaaa; 360 elan_mmcr[0xcb0 / 2] = 0x5555; 361 } 362 elan_mmcr[0xc00 / 2] = u; 363 return (0); 364 case WD_PASSIVE: 365 return (EOPNOTSUPP); 366 case 0: 367 u = elan_mmcr[0xc00 / 2]; 368 elan_mmcr[0xc00 / 2] = 0; 369 elan_mmcr[0xcb0 / 2] = 0x3333; 370 elan_mmcr[0xcb0 / 2] = 0xcccc; 371 elan_mmcr[0xcb0 / 2] = 0x4080; 372 elan_mmcr[0xc00 / 2] = u; 373 cur = 0; 374 return (0); 375 default: 376 return (EINVAL); 377 } 378 379} 380 381static int 382elan_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct thread *tdr) 383{ 384 int error; 385 386 error = ENOTTY; 387#ifdef ELAN_PPS 388 error = pps_ioctl(cmd, arg, &elan_pps); 389 /* 390 * We only want to incur the overhead of the PPS polling if we 391 * are actually asked to timestamp. 392 */ 393 if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT) 394 elan_timecounter.tc_poll_pps = elan_poll_pps; 395 else 396 elan_timecounter.tc_poll_pps = NULL; 397 if (error != ENOTTY) 398 return (error); 399#endif /* ELAN_PPS */ 400 401 if (cmd == WDIOCPATPAT) 402 return elan_watchdog(*((u_int*)arg)); 403 404 /* Other future ioctl handling here */ 405 return(error); 406} 407 408