The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/i386/i386/elan-mmcr.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    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 CPU_ELAN_PPS
   21  *   The Elan has three general purpose counters, and when two of these
   22  *   are used just right they can hardware timestamp external events with
   23  *   approx 125 nsec resolution and +/- 125 nsec precision.
   24  *
   25  *   Connect the signal to TMR1IN and a GPIO pin, and configure the GPIO pin
   26  *   with a 'P' in sysctl machdep.elan_gpio_config.
   27  *
   28  *   The rising edge of the signal will start timer 1 counting up from
   29  *   zero, and when the timecounter polls for PPS, both counter 1 & 2 is
   30  *   read, as well as the GPIO bit.  If a rising edge has happened, the
   31  *   contents of timer 1 which is how long time ago the edge happened,
   32  *   is subtracted from timer 2 to give us a "true time stamp".
   33  *
   34  *   Echoing the PPS signal on any GPIO pin is supported (set it to 'e'
   35  *   or 'E' (inverted) in the sysctl)  The echo signal should only be
   36  *   used as a visual indication, not for calibration since it suffers
   37  *   from 1/hz (or more) jitter which the timestamps are compensated for.
   38  * #endif CPU_ELAN_PPS
   39  */
   40 
   41 #include <sys/cdefs.h>
   42 __FBSDID("$FreeBSD: releng/5.2/sys/i386/i386/elan-mmcr.c 123015 2003-11-27 20:27:29Z phk $");
   43 
   44 #include "opt_cpu.h"
   45 #include <sys/param.h>
   46 #include <sys/systm.h>
   47 #include <sys/kernel.h>
   48 #include <sys/conf.h>
   49 #include <sys/sysctl.h>
   50 #include <sys/syslog.h>
   51 #include <sys/timetc.h>
   52 #include <sys/proc.h>
   53 #include <sys/uio.h>
   54 #include <sys/lock.h>
   55 #include <sys/mutex.h>
   56 #include <sys/malloc.h>
   57 #include <sys/sysctl.h>
   58 #include <sys/timepps.h>
   59 #include <sys/watchdog.h>
   60 
   61 #include <dev/led/led.h>
   62 #include <machine/md_var.h>
   63 
   64 #include <vm/vm.h>
   65 #include <vm/pmap.h>
   66 
   67 static char gpio_config[33];
   68 
   69 uint16_t *elan_mmcr;
   70 
   71 #ifdef CPU_ELAN_PPS
   72 static struct pps_state elan_pps;
   73 u_int   pps_a, pps_d;
   74 u_int   echo_a, echo_d;
   75 #endif /* CPU_ELAN_PPS */
   76 u_int   led_cookie[32];
   77 dev_t   led_dev[32];
   78 
   79 static void
   80 gpio_led(void *cookie, int state)
   81 {
   82         u_int u, v;
   83 
   84         u = *(int *)cookie;
   85         v = u & 0xffff;
   86         u >>= 16;
   87         if (!state)
   88                 v ^= 0xc;
   89         elan_mmcr[v / 2] = u;
   90 }
   91 
   92 static int
   93 sysctl_machdep_elan_gpio_config(SYSCTL_HANDLER_ARGS)
   94 {
   95         u_int u, v;
   96         int i, np, ne;
   97         int error;
   98         char buf[32], tmp[10];
   99 
  100         error = SYSCTL_OUT(req, gpio_config, 33);
  101         if (error != 0 || req->newptr == NULL)
  102                 return (error);
  103         if (req->newlen != 32)
  104                 return (EINVAL);
  105         error = SYSCTL_IN(req, buf, 32);
  106         if (error != 0)
  107                 return (error);
  108         /* Disallow any disabled pins and count pps and echo */
  109         np = ne = 0;
  110         for (i = 0; i < 32; i++) {
  111                 if (gpio_config[i] == '-' && (buf[i] != '-' && buf[i] != '.'))
  112                         return (EPERM);
  113                 if (buf[i] == 'P') {
  114                         np++;
  115                         if (np > 1)
  116                                 return (EINVAL);
  117                 }
  118                 if (buf[i] == 'e' || buf[i] == 'E') {
  119                         ne++;
  120                         if (ne > 1)
  121                                 return (EINVAL);
  122                 }
  123                 if (buf[i] != 'L' && buf[i] != 'l'
  124 #ifdef CPU_ELAN_PPS
  125                     && buf[i] != 'P' && buf[i] != 'E' && buf[i] != 'e'
  126 #endif /* CPU_ELAN_PPS */
  127                     && buf[i] != '.' && buf[i] != '-')
  128                         return (EINVAL);
  129         }
  130 #ifdef CPU_ELAN_PPS
  131         if (np == 0)
  132                 pps_a = pps_d = 0;
  133         if (ne == 0)
  134                 echo_a = echo_d = 0;
  135 #endif
  136         for (i = 0; i < 32; i++) {
  137                 u = 1 << (i & 0xf);
  138                 if (i >= 16)
  139                         v = 2;
  140                 else
  141                         v = 0;
  142                 if (buf[i] != 'l' && buf[i] != 'L' && led_dev[i] != NULL) {
  143                         led_destroy(led_dev[i]);        
  144                         led_dev[i] = NULL;
  145                         elan_mmcr[(0xc2a + v) / 2] &= ~u;
  146                 }
  147                 switch (buf[i]) {
  148 #ifdef CPU_ELAN_PPS
  149                 case 'P':
  150                         pps_d = u;
  151                         pps_a = 0xc30 + v;
  152                         elan_mmcr[(0xc2a + v) / 2] &= ~u;
  153                         gpio_config[i] = buf[i];
  154                         break;
  155                 case 'e':
  156                 case 'E':
  157                         echo_d = u;
  158                         if (buf[i] == 'E')
  159                                 echo_a = 0xc34 + v;
  160                         else
  161                                 echo_a = 0xc38 + v;
  162                         elan_mmcr[(0xc2a + v) / 2] |= u;
  163                         gpio_config[i] = buf[i];
  164                         break;
  165 #endif /* CPU_ELAN_PPS */
  166                 case 'l':
  167                 case 'L':
  168                         if (buf[i] == 'L')
  169                                 led_cookie[i] = (0xc34 + v) | (u << 16);
  170                         else
  171                                 led_cookie[i] = (0xc38 + v) | (u << 16);
  172                         if (led_dev[i])
  173                                 break;
  174                         sprintf(tmp, "gpio%d", i);
  175                         led_dev[i] =
  176                             led_create(gpio_led, &led_cookie[i], tmp);
  177                         elan_mmcr[(0xc2a + v) / 2] |= u;
  178                         gpio_config[i] = buf[i];
  179                         break;
  180                 case '.':
  181                         gpio_config[i] = buf[i];
  182                         break;
  183                 case '-':
  184                 default:
  185                         break;
  186                 }
  187         }
  188         return (0);
  189 }
  190 
  191 SYSCTL_OID(_machdep, OID_AUTO, elan_gpio_config, CTLTYPE_STRING | CTLFLAG_RW,
  192     NULL, 0, sysctl_machdep_elan_gpio_config, "A", "Elan CPU GPIO pin config");
  193 
  194 #ifdef CPU_ELAN_PPS
  195 static void
  196 elan_poll_pps(struct timecounter *tc)
  197 {
  198         static int state;
  199         int i;
  200         u_int u;
  201 
  202         /*
  203          * Order is important here.  We need to check the state of the GPIO
  204          * pin first, in order to avoid reading timer 1 right before the
  205          * state change.  Technically pps_a may be zero in which case we
  206          * harmlessly read the REVID register and the contents of pps_d is
  207          * of no concern.
  208          */
  209         i = elan_mmcr[pps_a / 2] & pps_d;
  210 
  211         /*
  212          * Subtract timer1 from timer2 to compensate for time from the
  213          * edge until now.
  214          */
  215         u = elan_mmcr[0xc84 / 2] - elan_mmcr[0xc7c / 2];
  216 
  217         /* If state did not change or we don't have a GPIO pin, return */
  218         if (i == state || pps_a == 0)
  219                 return;
  220 
  221         state = i;
  222 
  223         /* If the state is "low", flip the echo GPIO and return.  */
  224         if (!i) {
  225                 if (echo_a)
  226                         elan_mmcr[(echo_a ^ 0xc) / 2] = echo_d;
  227                 return;
  228         }
  229 
  230         /* State is "high", record the pps data */
  231         pps_capture(&elan_pps);
  232         elan_pps.capcount = u & 0xffff;
  233         pps_event(&elan_pps, PPS_CAPTUREASSERT);
  234 
  235         /* Twiddle echo bit */
  236         if (echo_a)
  237                 elan_mmcr[echo_a / 2] = echo_d;
  238 }
  239 #endif /* CPU_ELAN_PPS */
  240 
  241 static unsigned
  242 elan_get_timecount(struct timecounter *tc)
  243 {
  244 
  245         /* Read timer2, end of story */
  246         return (elan_mmcr[0xc84 / 2]);
  247 }
  248 
  249 /*
  250  * The Elan CPU can be run from a number of clock frequencies, this
  251  * allows you to override the default 33.3 MHZ.
  252  */
  253 #ifndef CPU_ELAN_XTAL
  254 #define CPU_ELAN_XTAL 33333333
  255 #endif
  256 
  257 static struct timecounter elan_timecounter = {
  258         elan_get_timecount,
  259         NULL,
  260         0xffff,
  261         CPU_ELAN_XTAL / 4,
  262         "ELAN",
  263         1000
  264 };
  265 
  266 static int
  267 sysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS)
  268 {
  269         u_int f;
  270         int error;
  271 
  272         f = elan_timecounter.tc_frequency * 4;
  273         error = sysctl_handle_int(oidp, &f, sizeof(f), req);
  274         if (error == 0 && req->newptr != NULL) 
  275                 elan_timecounter.tc_frequency = (f + 3) / 4;
  276         return (error);
  277 }
  278 
  279 SYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW,
  280     0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", "");
  281 
  282 /*
  283  * Positively identifying the Elan can only be done through the PCI id of
  284  * the host-bridge, this function is called from i386/pci/pci_bus.c.
  285  */
  286 void
  287 init_AMD_Elan_sc520(void)
  288 {
  289         u_int new;
  290         int i;
  291 
  292         elan_mmcr = pmap_mapdev(0xfffef000, 0x1000);
  293 
  294         /*-
  295          * The i8254 is driven with a nonstandard frequency which is
  296          * derived thusly:
  297          *   f = 32768 * 45 * 25 / 31 = 1189161.29...
  298          * We use the sysctl to get the i8254 (timecounter etc) into whack.
  299          */
  300         
  301         new = 1189161;
  302         i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq", 
  303             NULL, 0, &new, sizeof new, NULL);
  304         if (bootverbose || 1)
  305                 printf("sysctl machdep.i8254_freq=%d returns %d\n", new, i);
  306 
  307         /* Start GP timer #2 and use it as timecounter, hz permitting */
  308         elan_mmcr[0xc8e / 2] = 0x0;
  309         elan_mmcr[0xc82 / 2] = 0xc001;
  310 
  311 #ifdef CPU_ELAN_PPS
  312         /* Set up GP timer #1 as pps counter */
  313         elan_mmcr[0xc24 / 2] &= ~0x10;
  314         elan_mmcr[0xc7a / 2] = 0x8000 | 0x4000 | 0x10 | 0x1;
  315         elan_mmcr[0xc7e / 2] = 0x0;
  316         elan_mmcr[0xc80 / 2] = 0x0;
  317         elan_pps.ppscap |= PPS_CAPTUREASSERT;
  318         pps_init(&elan_pps);
  319 #endif
  320         tc_init(&elan_timecounter);
  321 }
  322 
  323 static d_ioctl_t elan_ioctl;
  324 static d_mmap_t elan_mmap;
  325 
  326 static struct cdevsw elan_cdevsw = {
  327         .d_ioctl =      elan_ioctl,
  328         .d_mmap =       elan_mmap,
  329         .d_name =       "elan",
  330 };
  331 
  332 static void
  333 elan_drvinit(void)
  334 {
  335 
  336         /* If no elan found, just return */
  337         if (elan_mmcr == NULL)
  338                 return;
  339 
  340         printf("Elan-mmcr driver: MMCR at %p.%s\n", 
  341             elan_mmcr,
  342 #ifdef CPU_ELAN_PPS
  343             " PPS support."
  344 #else
  345             ""
  346 #endif
  347             );
  348 
  349         make_dev(&elan_cdevsw, 0,
  350             UID_ROOT, GID_WHEEL, 0600, "elan-mmcr");
  351 
  352 #ifdef CPU_SOEKRIS
  353         /* Create the error LED on GPIO9 */
  354         led_cookie[9] = 0x02000c34;
  355         led_dev[9] = led_create(gpio_led, &led_cookie[9], "error");
  356         
  357         /* Disable the unavailable GPIO pins */
  358         strcpy(gpio_config, "-----....--..--------..---------");
  359 #else /* !CPU_SOEKRIS */
  360         /* We don't know which pins are available so enable them all */
  361         strcpy(gpio_config, "................................");
  362 #endif /* CPU_SOEKRIS */
  363 }
  364 
  365 SYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, elan_drvinit, NULL);
  366 
  367 static int
  368 elan_mmap(dev_t dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
  369 {
  370 
  371         if (offset >= 0x1000) 
  372                 return (-1);
  373         *paddr = 0xfffef000;
  374         return (0);
  375 }
  376 
  377 static int
  378 elan_watchdog(u_int spec)
  379 {
  380         u_int u, v;
  381         static u_int cur;
  382 
  383         if (spec & ~__WD_LEGAL)
  384                 return (EINVAL);
  385         switch (spec & (WD_ACTIVE|WD_PASSIVE)) {
  386         case WD_ACTIVE:
  387                 u = spec & WD_INTERVAL;
  388                 if (u > 35)
  389                         return (EINVAL);
  390                 u = imax(u - 5, 24);
  391                 v = 2 << (u - 24);
  392                 v |= 0xc000;
  393 
  394                 /*
  395                  * There is a bug in some silicon which prevents us from
  396                  * writing to the WDTMRCTL register if the GP echo mode is
  397                  * enabled.  GP echo mode on the other hand is desirable
  398                  * for other reasons.  Save and restore the GP echo mode
  399                  * around our hardware tom-foolery.
  400                  */
  401                 u = elan_mmcr[0xc00 / 2];
  402                 elan_mmcr[0xc00 / 2] = 0;
  403                 if (v != cur) {
  404                         /* Clear the ENB bit */
  405                         elan_mmcr[0xcb0 / 2] = 0x3333;
  406                         elan_mmcr[0xcb0 / 2] = 0xcccc;
  407                         elan_mmcr[0xcb0 / 2] = 0;
  408 
  409                         /* Set new value */
  410                         elan_mmcr[0xcb0 / 2] = 0x3333;
  411                         elan_mmcr[0xcb0 / 2] = 0xcccc;
  412                         elan_mmcr[0xcb0 / 2] = v;
  413                         cur = v;
  414                 } else {
  415                         /* Just reset timer */
  416                         elan_mmcr[0xcb0 / 2] = 0xaaaa;
  417                         elan_mmcr[0xcb0 / 2] = 0x5555;
  418                 }
  419                 elan_mmcr[0xc00 / 2] = u;
  420                 return (0);
  421         case WD_PASSIVE:
  422                 return (EOPNOTSUPP);
  423         case 0:
  424                 u = elan_mmcr[0xc00 / 2];
  425                 elan_mmcr[0xc00 / 2] = 0;
  426                 elan_mmcr[0xcb0 / 2] = 0x3333;
  427                 elan_mmcr[0xcb0 / 2] = 0xcccc;
  428                 elan_mmcr[0xcb0 / 2] = 0x4080;
  429                 elan_mmcr[0xc00 / 2] = u;
  430                 cur = 0;
  431                 return (0);
  432         default:
  433                 return (EINVAL);
  434         }
  435 
  436 }
  437 
  438 static int
  439 elan_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct  thread *tdr)
  440 {
  441         int error;
  442 
  443         error = ENOTTY;
  444 
  445 #ifdef CPU_ELAN_PPS
  446         if (pps_a != 0)
  447                 error = pps_ioctl(cmd, arg, &elan_pps);
  448         /*
  449          * We only want to incur the overhead of the PPS polling if we
  450          * are actually asked to timestamp.
  451          */
  452         if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT) {
  453                 elan_timecounter.tc_poll_pps = elan_poll_pps;
  454         } else {
  455                 elan_timecounter.tc_poll_pps = NULL;
  456         }
  457         if (error != ENOTTY)
  458                 return (error);
  459 #endif
  460 
  461         if (cmd == WDIOCPATPAT)
  462                 return elan_watchdog(*((u_int*)arg));
  463 
  464         /* Other future ioctl handling here */
  465         return(error);
  466 }
  467 

Cache object: 8e99c32ab26bcf57a41ca0b45d4c8f0d


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.