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  * $FreeBSD: releng/5.1/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 
   51 uint16_t *elan_mmcr;
   52 
   53 /* Relating to the /dev/soekris-errled */
   54 static struct mtx errled_mtx;
   55 static char *errled;
   56 static struct callout_handle errled_h = CALLOUT_HANDLE_INITIALIZER(&errled_h);
   57 static void timeout_errled(void *);
   58 
   59 #ifdef ELAN_PPS
   60 /* Relating to the PPS-api */
   61 static struct pps_state elan_pps;
   62 
   63 static void
   64 elan_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 
   83 static unsigned
   84 elan_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 
   97 static struct timecounter elan_timecounter = {
   98         elan_get_timecount,
   99         NULL,
  100         0xffff,
  101         ELAN_XTAL / 4,
  102         "ELAN"
  103 };
  104 
  105 static int
  106 sysctl_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 
  118 SYSCTL_PROC(_machdep, OID_AUTO, elan_freq, CTLTYPE_UINT | CTLFLAG_RW,
  119     0, sizeof (u_int), sysctl_machdep_elan_freq, "IU", "");
  120 
  121 void
  122 init_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 
  165 static d_write_t elan_write;
  166 static d_ioctl_t elan_ioctl;
  167 static 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 */
  173 static 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 
  183 static void
  184 elan_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 
  198 SYSINIT(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 
  203 static void
  204 timeout_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 == '') {            /* 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] - '';
  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] < '' || 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] - '';
  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 
  274 static int
  275 elan_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 
  306 static int
  307 elan_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 
  318 static int
  319 elan_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 
  379 static int
  380 elan_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 

Cache object: 6e83d3013f186fbde408564a05d3be06


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