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

Cache object: 07678a94f243b0268ff8b5c8d26cd8e9


[ 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.