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