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-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-2  -  FREEBSD-11-1  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-4  -  FREEBSD-10-3  -  FREEBSD-10-2  -  FREEBSD-10-1  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-3  -  FREEBSD-9-2  -  FREEBSD-9-1  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-4  -  FREEBSD-8-3  -  FREEBSD-8-2  -  FREEBSD-8-1  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-4  -  FREEBSD-7-3  -  FREEBSD-7-2  -  FREEBSD-7-1  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-4  -  FREEBSD-6-3  -  FREEBSD-6-2  -  FREEBSD-6-1  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-5  -  FREEBSD-5-4  -  FREEBSD-5-3  -  FREEBSD-5-2  -  FREEBSD-5-1  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  linux-2.6  -  linux-2.4.22  -  MK83  -  MK84  -  PLAN9  -  DFBSD  -  NETBSD  -  NETBSD5  -  NETBSD4  -  NETBSD3  -  NETBSD20  -  OPENBSD  -  xnu-517  -  xnu-792  -  xnu-792.6.70  -  xnu-1228  -  xnu-1456.1.26  -  xnu-1699.24.8  -  xnu-2050.18.24  -  OPENSOLARIS  -  minix-3-1-1 
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$");
   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 #include <machine/elan_mmcr.h>
   64 
   65 #include <vm/vm.h>
   66 #include <vm/pmap.h>
   67 
   68 static char gpio_config[33];
   69 
   70 static volatile uint16_t *mmcrptr;
   71 volatile struct elan_mmcr *elan_mmcr;
   72 
   73 #ifdef CPU_ELAN_PPS
   74 static struct pps_state elan_pps;
   75 static volatile uint16_t *pps_ap[3];
   76 static u_int    pps_a, pps_d;
   77 static u_int    echo_a, echo_d;
   78 #endif /* CPU_ELAN_PPS */
   79 
   80 static u_int    led_cookie[32];
   81 static struct cdev *led_dev[32];
   82 
   83 static void
   84 gpio_led(void *cookie, int state)
   85 {
   86         u_int u, v;
   87 
   88         u = *(int *)cookie;
   89         v = u & 0xffff;
   90         u >>= 16;
   91         if (!state)
   92                 v ^= 0xc;
   93         mmcrptr[v / 2] = u;
   94 }
   95 
   96 static int
   97 sysctl_machdep_elan_gpio_config(SYSCTL_HANDLER_ARGS)
   98 {
   99         u_int u, v;
  100         int i, np, ne;
  101         int error;
  102         char buf[32];
  103         char tmp[10];
  104 
  105         error = SYSCTL_OUT(req, gpio_config, 33);
  106         if (error != 0 || req->newptr == NULL)
  107                 return (error);
  108         if (req->newlen != 32)
  109                 return (EINVAL);
  110         error = SYSCTL_IN(req, buf, 32);
  111         if (error != 0)
  112                 return (error);
  113         /* Disallow any disabled pins and count pps and echo */
  114         np = ne = 0;
  115         for (i = 0; i < 32; i++) {
  116                 if (gpio_config[i] == '-' && buf[i] == '.')
  117                         buf[i] = gpio_config[i];
  118                 if (gpio_config[i] == '-' && buf[i] != '-')
  119                         return (EPERM);
  120                 if (buf[i] == 'P') {
  121                         np++;
  122                         if (np > 1)
  123                                 return (EINVAL);
  124                 }
  125                 if (buf[i] == 'e' || buf[i] == 'E') {
  126                         ne++;
  127                         if (ne > 1)
  128                                 return (EINVAL);
  129                 }
  130                 if (buf[i] != 'L' && buf[i] != 'l'
  131 #ifdef CPU_ELAN_PPS
  132                     && buf[i] != 'P' && buf[i] != 'E' && buf[i] != 'e'
  133 #endif /* CPU_ELAN_PPS */
  134                     && buf[i] != '.' && buf[i] != '-')
  135                         return (EINVAL);
  136         }
  137 #ifdef CPU_ELAN_PPS
  138         if (np == 0)
  139                 pps_a = pps_d = 0;
  140         if (ne == 0)
  141                 echo_a = echo_d = 0;
  142 #endif
  143         for (i = 0; i < 32; i++) {
  144                 u = 1 << (i & 0xf);
  145                 if (i >= 16)
  146                         v = 2;
  147                 else
  148                         v = 0;
  149 #ifdef CPU_SOEKRIS
  150                 if (i == 9)
  151                         ;
  152                 else
  153 #endif
  154                 if (buf[i] != 'l' && buf[i] != 'L' && led_dev[i] != NULL) {
  155                         led_destroy(led_dev[i]);        
  156                         led_dev[i] = NULL;
  157                         mmcrptr[(0xc2a + v) / 2] &= ~u;
  158                 }
  159                 switch (buf[i]) {
  160 #ifdef CPU_ELAN_PPS
  161                 case 'P':
  162                         pps_d = u;
  163                         pps_a = 0xc30 + v;
  164                         pps_ap[0] = &mmcrptr[pps_a / 2];
  165                         pps_ap[1] = &elan_mmcr->GPTMR2CNT;
  166                         pps_ap[2] = &elan_mmcr->GPTMR1CNT;
  167                         mmcrptr[(0xc2a + v) / 2] &= ~u;
  168                         gpio_config[i] = buf[i];
  169                         break;
  170                 case 'e':
  171                 case 'E':
  172                         echo_d = u;
  173                         if (buf[i] == 'E')
  174                                 echo_a = 0xc34 + v;
  175                         else
  176                                 echo_a = 0xc38 + v;
  177                         mmcrptr[(0xc2a + v) / 2] |= u;
  178                         gpio_config[i] = buf[i];
  179                         break;
  180 #endif /* CPU_ELAN_PPS */
  181                 case 'l':
  182                 case 'L':
  183                         if (buf[i] == 'L')
  184                                 led_cookie[i] = (0xc34 + v) | (u << 16);
  185                         else
  186                                 led_cookie[i] = (0xc38 + v) | (u << 16);
  187                         if (led_dev[i])
  188                                 break;
  189                         sprintf(tmp, "gpio%d", i);
  190                         mmcrptr[(0xc2a + v) / 2] |= u;
  191                         gpio_config[i] = buf[i];
  192                         led_dev[i] =
  193                             led_create(gpio_led, &led_cookie[i], tmp);
  194                         break;
  195                 case '.':
  196                         gpio_config[i] = buf[i];
  197                         break;
  198                 case '-':
  199                 default:
  200                         break;
  201                 }
  202         }
  203         return (0);
  204 }
  205 
  206 SYSCTL_OID(_machdep, OID_AUTO, elan_gpio_config, CTLTYPE_STRING | CTLFLAG_RW,
  207     NULL, 0, sysctl_machdep_elan_gpio_config, "A", "Elan CPU GPIO pin config");
  208 
  209 #ifdef CPU_ELAN_PPS
  210 static void
  211 elan_poll_pps(struct timecounter *tc)
  212 {
  213         static int state;
  214         int i;
  215         uint16_t u, x, y, z;
  216         u_long eflags;
  217 
  218         /*
  219          * Grab the HW state as quickly and compactly as we can.  Disable
  220          * interrupts to avoid measuring our interrupt service time on
  221          * hw with quality clock sources.
  222          */
  223         eflags = read_eflags();
  224         disable_intr();
  225         x = *pps_ap[0]; /* state, must be first, see below */
  226         y = *pps_ap[1]; /* timer2 */
  227         z = *pps_ap[2]; /* timer1 */
  228         write_eflags(eflags);
  229 
  230         /*
  231          * Order is important here.  We need to check the state of the GPIO
  232          * pin first, in order to avoid reading timer 1 right before the
  233          * state change.  Technically pps_a may be zero in which case we
  234          * harmlessly read the REVID register and the contents of pps_d is
  235          * of no concern.
  236          */
  237 
  238         i = x & pps_d;
  239 
  240         /* If state did not change or we don't have a GPIO pin, return */
  241         if (i == state || pps_a == 0)
  242                 return;
  243 
  244         state = i;
  245 
  246         /* If the state is "low", flip the echo GPIO and return.  */
  247         if (!i) {
  248                 if (echo_a)
  249                         mmcrptr[(echo_a ^ 0xc) / 2] = echo_d;
  250                 return;
  251         }
  252 
  253         /*
  254          * Subtract timer1 from timer2 to compensate for time from the
  255          * edge until we read the counters.
  256          */
  257         u = y - z;
  258 
  259         pps_capture(&elan_pps);
  260         elan_pps.capcount = u;
  261         pps_event(&elan_pps, PPS_CAPTUREASSERT);
  262 
  263         /* Twiddle echo bit */
  264         if (echo_a)
  265                 mmcrptr[echo_a / 2] = echo_d;
  266 }
  267 #endif /* CPU_ELAN_PPS */
  268 
  269 static unsigned
  270 elan_get_timecount(struct timecounter *tc)
  271 {
  272 
  273         /* Read timer2, end of story */
  274         return (elan_mmcr->GPTMR2CNT);
  275 }
  276 
  277 /*
  278  * The Elan CPU can be run from a number of clock frequencies, this
  279  * allows you to override the default 33.3 MHZ.
  280  */
  281 #ifndef CPU_ELAN_XTAL
  282 #define CPU_ELAN_XTAL 33333333
  283 #endif
  284 
  285 static struct timecounter elan_timecounter = {
  286         elan_get_timecount,
  287         NULL,
  288         0xffff,
  289         CPU_ELAN_XTAL / 4,
  290         "ELAN",
  291         1000
  292 };
  293 
  294 static int
  295 sysctl_machdep_elan_freq(SYSCTL_HANDLER_ARGS)
  296 {
  297         u_int f;
  298         int error;
  299 
  300         f = elan_timecounter.tc_frequency * 4;
  301         error = sysctl_handle_int(oidp, &f, sizeof(f), req);
  302         if (error == 0 && req->newptr != NULL) 
  303                 elan_timecounter.tc_frequency = (f + 3) / 4;
  304         return (error);
  305 }
  306 
  307 SYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW,
  308     0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", "");
  309 
  310 /*
  311  * Positively identifying the Elan can only be done through the PCI id of
  312  * the host-bridge, this function is called from i386/pci/pci_bus.c.
  313  */
  314 void
  315 init_AMD_Elan_sc520(void)
  316 {
  317         u_int new;
  318         int i;
  319 
  320         mmcrptr = pmap_mapdev(0xfffef000, 0x1000);
  321         elan_mmcr = (volatile struct elan_mmcr *)mmcrptr;
  322 
  323         /*-
  324          * The i8254 is driven with a nonstandard frequency which is
  325          * derived thusly:
  326          *   f = 32768 * 45 * 25 / 31 = 1189161.29...
  327          * We use the sysctl to get the i8254 (timecounter etc) into whack.
  328          */
  329         
  330         new = 1189161;
  331         i = kernel_sysctlbyname(&thread0, "machdep.i8254_freq", 
  332             NULL, 0, &new, sizeof new, NULL);
  333         if (bootverbose || 1)
  334                 printf("sysctl machdep.i8254_freq=%d returns %d\n", new, i);
  335 
  336         /* Start GP timer #2 and use it as timecounter, hz permitting */
  337         elan_mmcr->GPTMR2MAXCMPA = 0;
  338         elan_mmcr->GPTMR2CTL = 0xc001;
  339 
  340 #ifdef CPU_ELAN_PPS
  341         /* Set up GP timer #1 as pps counter */
  342         elan_mmcr->CSPFS &= ~0x10;
  343         elan_mmcr->GPTMR1CTL = 0x8000 | 0x4000 | 0x10 | 0x1;
  344         elan_mmcr->GPTMR1MAXCMPA = 0x0;
  345         elan_mmcr->GPTMR1MAXCMPB = 0x0;
  346         elan_pps.ppscap |= PPS_CAPTUREASSERT;
  347         pps_init(&elan_pps);
  348 #endif
  349         tc_init(&elan_timecounter);
  350 }
  351 
  352 static void
  353 elan_watchdog(void *foo __unused, u_int spec, int *error)
  354 {
  355         u_int u, v;
  356         static u_int cur;
  357 
  358         u = spec & WD_INTERVAL;
  359         if (spec && u <= 35) {
  360                 u = imax(u - 5, 24);
  361                 v = 2 << (u - 24);
  362                 v |= 0xc000;
  363 
  364                 /*
  365                  * There is a bug in some silicon which prevents us from
  366                  * writing to the WDTMRCTL register if the GP echo mode is
  367                  * enabled.  GP echo mode on the other hand is desirable
  368                  * for other reasons.  Save and restore the GP echo mode
  369                  * around our hardware tom-foolery.
  370                  */
  371                 u = elan_mmcr->GPECHO;
  372                 elan_mmcr->GPECHO = 0;
  373                 if (v != cur) {
  374                         /* Clear the ENB bit */
  375                         elan_mmcr->WDTMRCTL = 0x3333;
  376                         elan_mmcr->WDTMRCTL = 0xcccc;
  377                         elan_mmcr->WDTMRCTL = 0;
  378 
  379                         /* Set new value */
  380                         elan_mmcr->WDTMRCTL = 0x3333;
  381                         elan_mmcr->WDTMRCTL = 0xcccc;
  382                         elan_mmcr->WDTMRCTL = v;
  383                         cur = v;
  384                 } else {
  385                         /* Just reset timer */
  386                         elan_mmcr->WDTMRCTL = 0xaaaa;
  387                         elan_mmcr->WDTMRCTL = 0x5555;
  388                 }
  389                 elan_mmcr->GPECHO = u;
  390                 *error = 0;
  391                 return;
  392         } else {
  393                 u = elan_mmcr->GPECHO;
  394                 elan_mmcr->GPECHO = 0;
  395                 elan_mmcr->WDTMRCTL = 0x3333;
  396                 elan_mmcr->WDTMRCTL = 0xcccc;
  397                 elan_mmcr->WDTMRCTL = 0x4080;
  398                 elan_mmcr->WDTMRCTL = u;
  399                 elan_mmcr->GPECHO = u;
  400                 cur = 0;
  401                 return;
  402         }
  403 }
  404 
  405 static int
  406 elan_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
  407 {
  408 
  409         if (offset >= 0x1000) 
  410                 return (-1);
  411         *paddr = 0xfffef000;
  412         return (0);
  413 }
  414 static int
  415 elan_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct  thread *tdr)
  416 {
  417         int error;
  418 
  419         error = ENOIOCTL;
  420 
  421 #ifdef CPU_ELAN_PPS
  422         if (pps_a != 0)
  423                 error = pps_ioctl(cmd, arg, &elan_pps);
  424         /*
  425          * We only want to incur the overhead of the PPS polling if we
  426          * are actually asked to timestamp.
  427          */
  428         if (elan_pps.ppsparam.mode & PPS_CAPTUREASSERT) {
  429                 elan_timecounter.tc_poll_pps = elan_poll_pps;
  430         } else {
  431                 elan_timecounter.tc_poll_pps = NULL;
  432         }
  433         if (error != ENOIOCTL)
  434                 return (error);
  435 #endif
  436 
  437         return(error);
  438 }
  439 
  440 static struct cdevsw elan_cdevsw = {
  441         .d_version =    D_VERSION,
  442         .d_flags =      D_NEEDGIANT,
  443         .d_ioctl =      elan_ioctl,
  444         .d_mmap =       elan_mmap,
  445         .d_name =       "elan",
  446 };
  447 
  448 static void
  449 elan_drvinit(void)
  450 {
  451 
  452         /* If no elan found, just return */
  453         if (mmcrptr == NULL)
  454                 return;
  455 
  456         printf("Elan-mmcr driver: MMCR at %p.%s\n", 
  457             mmcrptr,
  458 #ifdef CPU_ELAN_PPS
  459             " PPS support."
  460 #else
  461             ""
  462 #endif
  463             );
  464 
  465         make_dev(&elan_cdevsw, 0,
  466             UID_ROOT, GID_WHEEL, 0600, "elan-mmcr");
  467 
  468 #ifdef CPU_SOEKRIS
  469         /* Create the error LED on GPIO9 */
  470         led_cookie[9] = 0x02000c34;
  471         led_dev[9] = led_create(gpio_led, &led_cookie[9], "error");
  472         
  473         /* Disable the unavailable GPIO pins */
  474         strcpy(gpio_config, "-----....--..--------..---------");
  475 #else /* !CPU_SOEKRIS */
  476         /* We don't know which pins are available so enable them all */
  477         strcpy(gpio_config, "................................");
  478 #endif /* CPU_SOEKRIS */
  479 
  480         EVENTHANDLER_REGISTER(watchdog_list, elan_watchdog, NULL, 0);
  481 }
  482 
  483 SYSINIT(elan, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, elan_drvinit, NULL);
  484 

Cache object: 3963cf09cc340284d1a66d05c6cbede1


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