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/arm/mv/a37x0_spi.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  * Copyright (c) 2018, 2019 Rubicon Communications, LLC (Netgate)
    3  *
    4  * Redistribution and use in source and binary forms, with or without
    5  * modification, are permitted provided that the following conditions
    6  * are met:
    7  * 1. Redistributions of source code must retain the above copyright
    8  *    notice, this list of conditions and the following disclaimer.
    9  * 2. Redistributions in binary form must reproduce the above copyright
   10  *    notice, this list of conditions and the following disclaimer in the
   11  *    documentation and/or other materials provided with the distribution.
   12  *
   13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
   14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
   17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   23  *
   24  */
   25 
   26 #include <sys/cdefs.h>
   27 __FBSDID("$FreeBSD$");
   28 
   29 #include <sys/param.h>
   30 #include <sys/systm.h>
   31 #include <sys/bus.h>
   32 #include <sys/kernel.h>
   33 #include <sys/module.h>
   34 #include <sys/mutex.h>
   35 #include <sys/rman.h>
   36 
   37 #include <machine/bus.h>
   38 #include <machine/resource.h>
   39 #include <machine/intr.h>
   40 
   41 #include <dev/ofw/ofw_bus.h>
   42 #include <dev/ofw/ofw_bus_subr.h>
   43 #include <dev/spibus/spi.h>
   44 #include <dev/spibus/spibusvar.h>
   45 
   46 #include "spibus_if.h"
   47 
   48 struct a37x0_spi_softc {
   49         device_t                sc_dev;
   50         struct mtx              sc_mtx;
   51         struct resource         *sc_mem_res;
   52         struct resource         *sc_irq_res;
   53         struct spi_command      *sc_cmd;
   54         bus_space_tag_t         sc_bst;
   55         bus_space_handle_t      sc_bsh;
   56         uint32_t                sc_len;
   57         uint32_t                sc_maxfreq;
   58         uint32_t                sc_read;
   59         uint32_t                sc_flags;
   60         uint32_t                sc_written;
   61         void                    *sc_intrhand;
   62 };
   63 
   64 #define A37X0_SPI_WRITE(_sc, _off, _val)                \
   65     bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off), (_val))
   66 #define A37X0_SPI_READ(_sc, _off)                       \
   67     bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off))
   68 #define A37X0_SPI_LOCK(_sc)     mtx_lock(&(_sc)->sc_mtx)
   69 #define A37X0_SPI_UNLOCK(_sc)   mtx_unlock(&(_sc)->sc_mtx)
   70 
   71 #define A37X0_SPI_BUSY                  (1 << 0)
   72 /*
   73  * While the A3700 utils from Marvell usually sets the QSF clock to 200MHz,
   74  * there is no guarantee that it is correct without the proper clock framework
   75  * to retrieve the actual TBG and PLL settings.
   76  */
   77 #define A37X0_SPI_CLOCK                 200000000       /* QSF Clock 200MHz */
   78 
   79 #define A37X0_SPI_CONTROL               0x0
   80 #define  A37X0_SPI_CS_SHIFT             16
   81 #define  A37X0_SPI_CS_MASK              (0xf << A37X0_SPI_CS_SHIFT)
   82 #define A37X0_SPI_CONF                  0x4
   83 #define  A37X0_SPI_WFIFO_THRS_SHIFT     28
   84 #define  A37X0_SPI_RFIFO_THRS_SHIFT     24
   85 #define  A37X0_SPI_AUTO_CS_EN           (1 << 20)
   86 #define  A37X0_SPI_DMA_WR_EN            (1 << 19)
   87 #define  A37X0_SPI_DMA_RD_EN            (1 << 18)
   88 #define  A37X0_SPI_FIFO_MODE            (1 << 17)
   89 #define  A37X0_SPI_SRST                 (1 << 16)
   90 #define  A37X0_SPI_XFER_START           (1 << 15)
   91 #define  A37X0_SPI_XFER_STOP            (1 << 14)
   92 #define  A37X0_SPI_INSTR_PIN            (1 << 13)
   93 #define  A37X0_SPI_ADDR_PIN             (1 << 12)
   94 #define  A37X0_SPI_DATA_PIN_MASK        0x3
   95 #define  A37X0_SPI_DATA_PIN_SHIFT       10
   96 #define  A37X0_SPI_FIFO_FLUSH           (1 << 9)
   97 #define  A37X0_SPI_RW_EN                (1 << 8)
   98 #define  A37X0_SPI_CLK_POL              (1 << 7)
   99 #define  A37X0_SPI_CLK_PHASE            (1 << 6)
  100 #define  A37X0_SPI_BYTE_LEN             (1 << 5)
  101 #define  A37X0_SPI_PSC_MASK             0x1f
  102 #define A37X0_SPI_DATA_OUT              0x8
  103 #define A37X0_SPI_DATA_IN               0xc
  104 #define A37X0_SPI_INTR_STAT             0x28
  105 #define A37X0_SPI_INTR_MASK             0x2c
  106 #define  A37X0_SPI_RDY                  (1 << 1)
  107 #define  A37X0_SPI_XFER_DONE            (1 << 0)
  108 
  109 static struct ofw_compat_data compat_data[] = {
  110         { "marvell,armada-3700-spi",    1 },
  111         { NULL, 0 }
  112 };
  113 
  114 static void a37x0_spi_intr(void *);
  115 
  116 static int
  117 a37x0_spi_wait(struct a37x0_spi_softc *sc, int timeout, uint32_t reg,
  118     uint32_t mask)
  119 {
  120         int i;
  121 
  122         for (i = 0; i < timeout; i++) {
  123                 if ((A37X0_SPI_READ(sc, reg) & mask) == 0)
  124                         return (0);
  125                 DELAY(100);
  126         }
  127 
  128         return (ETIMEDOUT);
  129 }
  130 
  131 static int
  132 a37x0_spi_probe(device_t dev)
  133 {
  134 
  135         if (!ofw_bus_status_okay(dev))
  136                 return (ENXIO);
  137         if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
  138                 return (ENXIO);
  139         device_set_desc(dev, "Armada 37x0 SPI controller");
  140 
  141         return (BUS_PROBE_DEFAULT);
  142 }
  143 
  144 static int
  145 a37x0_spi_attach(device_t dev)
  146 {
  147         int err, rid;
  148         pcell_t maxfreq;
  149         struct a37x0_spi_softc *sc;
  150         uint32_t reg;
  151 
  152         sc = device_get_softc(dev);
  153         sc->sc_dev = dev;
  154 
  155         rid = 0;
  156         sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
  157             RF_ACTIVE);
  158         if (!sc->sc_mem_res) {
  159                 device_printf(dev, "cannot allocate memory window\n");
  160                 return (ENXIO);
  161         }
  162 
  163         sc->sc_bst = rman_get_bustag(sc->sc_mem_res);
  164         sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res);
  165 
  166         rid = 0;
  167         sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
  168             RF_ACTIVE);
  169         if (!sc->sc_irq_res) {
  170                 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
  171                 device_printf(dev, "cannot allocate interrupt\n");
  172                 return (ENXIO);
  173         }
  174 
  175         /* Make sure that no CS is asserted. */
  176         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
  177         A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK);
  178 
  179         /* Reset FIFO. */
  180         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
  181         A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_FIFO_FLUSH);
  182         err = a37x0_spi_wait(sc, 20, A37X0_SPI_CONF, A37X0_SPI_FIFO_FLUSH);
  183         if (err != 0) {
  184                 bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
  185                 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
  186                 device_printf(dev, "cannot flush the controller fifo.\n");
  187                 return (ENXIO);
  188         }
  189 
  190         /* Reset the Controller. */
  191         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
  192         A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_SRST);
  193         DELAY(1000);
  194         /* Enable the single byte IO, disable FIFO. */
  195         reg &= ~(A37X0_SPI_FIFO_MODE | A37X0_SPI_BYTE_LEN);
  196         A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
  197 
  198         /* Disable and clear interrupts. */
  199         A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0);
  200         reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
  201         A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg);
  202 
  203         /* Hook up our interrupt handler. */
  204         if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
  205             NULL, a37x0_spi_intr, sc, &sc->sc_intrhand)) {
  206                 bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
  207                 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
  208                 device_printf(dev, "cannot setup the interrupt handler\n");
  209                 return (ENXIO);
  210         }
  211 
  212         mtx_init(&sc->sc_mtx, "a37x0_spi", NULL, MTX_DEF);
  213 
  214         /* Read the controller max-frequency. */
  215         if (OF_getencprop(ofw_bus_get_node(dev), "spi-max-frequency", &maxfreq,
  216             sizeof(maxfreq)) == -1)
  217                 maxfreq = 0;
  218         sc->sc_maxfreq = maxfreq;
  219 
  220         device_add_child(dev, "spibus", -1);
  221 
  222         /* Probe and attach the spibus when interrupts are available. */
  223         return (bus_delayed_attach_children(dev));
  224 }
  225 
  226 static int
  227 a37x0_spi_detach(device_t dev)
  228 {
  229         int err;
  230         struct a37x0_spi_softc *sc;
  231 
  232         if ((err = device_delete_children(dev)) != 0)
  233                 return (err);
  234         sc = device_get_softc(dev);
  235         mtx_destroy(&sc->sc_mtx);
  236         if (sc->sc_intrhand)
  237                 bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand);
  238         if (sc->sc_irq_res)
  239                 bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
  240         if (sc->sc_mem_res)
  241                 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
  242 
  243         return (0);
  244 }
  245 
  246 static __inline void
  247 a37x0_spi_rx_byte(struct a37x0_spi_softc *sc)
  248 {
  249         struct spi_command *cmd;
  250         uint32_t read;
  251         uint8_t *p;
  252 
  253         if (sc->sc_read == sc->sc_len)
  254                 return;
  255         cmd = sc->sc_cmd;
  256         p = (uint8_t *)cmd->rx_cmd;
  257         read = sc->sc_read++;
  258         if (read >= cmd->rx_cmd_sz) {
  259                 p = (uint8_t *)cmd->rx_data;
  260                 read -= cmd->rx_cmd_sz;
  261         }
  262         p[read] = A37X0_SPI_READ(sc, A37X0_SPI_DATA_IN) & 0xff;
  263 }
  264 
  265 static __inline void
  266 a37x0_spi_tx_byte(struct a37x0_spi_softc *sc)
  267 {
  268         struct spi_command *cmd;
  269         uint32_t written;
  270         uint8_t *p;
  271 
  272         if (sc->sc_written == sc->sc_len)
  273                 return;
  274         cmd = sc->sc_cmd;
  275         p = (uint8_t *)cmd->tx_cmd;
  276         written = sc->sc_written++;
  277         if (written >= cmd->tx_cmd_sz) {
  278                 p = (uint8_t *)cmd->tx_data;
  279                 written -= cmd->tx_cmd_sz;
  280         }
  281         A37X0_SPI_WRITE(sc, A37X0_SPI_DATA_OUT, p[written]);
  282 }
  283 
  284 static __inline void
  285 a37x0_spi_set_clock(struct a37x0_spi_softc *sc, uint32_t clock)
  286 {
  287         uint32_t psc, reg;
  288 
  289         if (sc->sc_maxfreq > 0 && clock > sc->sc_maxfreq)
  290                 clock = sc->sc_maxfreq;
  291         psc = A37X0_SPI_CLOCK / clock;
  292         if ((A37X0_SPI_CLOCK % clock) > 0)
  293                 psc++;
  294         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
  295         reg &= ~A37X0_SPI_PSC_MASK;
  296         reg |= psc & A37X0_SPI_PSC_MASK;
  297         A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
  298 }
  299 
  300 static __inline void
  301 a37x0_spi_set_pins(struct a37x0_spi_softc *sc, uint32_t npins)
  302 {
  303         uint32_t reg;
  304 
  305         /* Sets single, dual or quad SPI mode. */
  306         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
  307         reg &= ~(A37X0_SPI_DATA_PIN_MASK << A37X0_SPI_DATA_PIN_SHIFT);
  308         reg |= (npins / 2) << A37X0_SPI_DATA_PIN_SHIFT;
  309         reg |= A37X0_SPI_INSTR_PIN | A37X0_SPI_ADDR_PIN;
  310         A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
  311 }
  312 
  313 static __inline void
  314 a37x0_spi_set_mode(struct a37x0_spi_softc *sc, uint32_t mode)
  315 {
  316         uint32_t reg;
  317 
  318         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
  319         switch (mode) {
  320         case 0:
  321                 reg &= ~(A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL);
  322                 break;
  323         case 1:
  324                 reg &= ~A37X0_SPI_CLK_POL;
  325                 reg |= A37X0_SPI_CLK_PHASE;
  326                 break;
  327         case 2:
  328                 reg &= ~A37X0_SPI_CLK_PHASE;
  329                 reg |= A37X0_SPI_CLK_POL;
  330                 break;
  331         case 3:
  332                 reg |= (A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL);
  333                 break;
  334         }
  335         A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
  336 }
  337 
  338 static void
  339 a37x0_spi_intr(void *arg)
  340 {
  341         struct a37x0_spi_softc *sc;
  342         uint32_t status;
  343 
  344         sc = (struct a37x0_spi_softc *)arg;
  345         A37X0_SPI_LOCK(sc);
  346 
  347         /* Filter stray interrupts. */
  348         if ((sc->sc_flags & A37X0_SPI_BUSY) == 0) {
  349                 A37X0_SPI_UNLOCK(sc);
  350                 return;
  351         }
  352 
  353         status = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
  354         if (status & A37X0_SPI_XFER_DONE)
  355                 a37x0_spi_rx_byte(sc);
  356 
  357         /* Clear the interrupt status. */
  358         A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, status);
  359 
  360         /* Check for end of transfer. */
  361         if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len)
  362                 wakeup(sc->sc_dev);
  363         else
  364                 a37x0_spi_tx_byte(sc);
  365 
  366         A37X0_SPI_UNLOCK(sc);
  367 }
  368 
  369 static int
  370 a37x0_spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
  371 {
  372         int timeout;
  373         struct a37x0_spi_softc *sc;
  374         uint32_t clock, cs, mode, reg;
  375 
  376         KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz,
  377             ("TX/RX command sizes should be equal"));
  378         KASSERT(cmd->tx_data_sz == cmd->rx_data_sz,
  379             ("TX/RX data sizes should be equal"));
  380 
  381         /* Get the proper data for this child. */
  382         spibus_get_cs(child, &cs);
  383         cs &= ~SPIBUS_CS_HIGH;
  384         if (cs > 3) {
  385                 device_printf(dev,
  386                     "Invalid CS %d requested by %s\n", cs,
  387                     device_get_nameunit(child));
  388                 return (EINVAL);
  389         }
  390         spibus_get_clock(child, &clock);
  391         if (clock == 0) {
  392                 device_printf(dev,
  393                     "Invalid clock %uHz requested by %s\n", clock,
  394                     device_get_nameunit(child));
  395                 return (EINVAL);
  396         }
  397         spibus_get_mode(child, &mode);
  398         if (mode > 3) {
  399                 device_printf(dev,
  400                     "Invalid mode %u requested by %s\n", mode,
  401                     device_get_nameunit(child));
  402                 return (EINVAL);
  403         }
  404 
  405         sc = device_get_softc(dev);
  406         A37X0_SPI_LOCK(sc);
  407 
  408         /* Wait until the controller is free. */
  409         while (sc->sc_flags & A37X0_SPI_BUSY)
  410                 mtx_sleep(dev, &sc->sc_mtx, 0, "a37x0_spi", 0);
  411 
  412         /* Now we have control over SPI controller. */
  413         sc->sc_flags = A37X0_SPI_BUSY;
  414 
  415         /* Set transfer mode and clock. */
  416         a37x0_spi_set_mode(sc, mode);
  417         a37x0_spi_set_pins(sc, 1);
  418         a37x0_spi_set_clock(sc, clock);
  419 
  420         /* Set CS. */
  421         A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, 1 << (A37X0_SPI_CS_SHIFT + cs));
  422 
  423         /* Save a pointer to the SPI command. */
  424         sc->sc_cmd = cmd;
  425         sc->sc_read = 0;
  426         sc->sc_written = 0;
  427         sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz;
  428 
  429         /* Clear interrupts. */
  430         reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
  431         A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg);
  432 
  433         while ((sc->sc_len - sc->sc_written) > 0) {
  434                 /*
  435                  * Write to start the transmission and read the byte
  436                  * back when ready.
  437                  */
  438                 a37x0_spi_tx_byte(sc);
  439                 timeout = 1000;
  440                 while (--timeout > 0) {
  441                         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
  442                         if (reg & A37X0_SPI_XFER_DONE)
  443                                 break;
  444                         DELAY(1);
  445                 }
  446                 if (timeout == 0)
  447                         break;
  448                 a37x0_spi_rx_byte(sc);
  449         }
  450 
  451         /* Stop the controller. */
  452         reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
  453         A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK);
  454         A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0);
  455 
  456         /* Release the controller and wakeup the next thread waiting for it. */
  457         sc->sc_flags = 0;
  458         wakeup_one(dev);
  459         A37X0_SPI_UNLOCK(sc);
  460 
  461         return ((timeout == 0) ? EIO : 0);
  462 }
  463 
  464 static phandle_t
  465 a37x0_spi_get_node(device_t bus, device_t dev)
  466 {
  467 
  468         return (ofw_bus_get_node(bus));
  469 }
  470 
  471 static device_method_t a37x0_spi_methods[] = {
  472         /* Device interface */
  473         DEVMETHOD(device_probe,         a37x0_spi_probe),
  474         DEVMETHOD(device_attach,        a37x0_spi_attach),
  475         DEVMETHOD(device_detach,        a37x0_spi_detach),
  476 
  477         /* SPI interface */
  478         DEVMETHOD(spibus_transfer,      a37x0_spi_transfer),
  479 
  480         /* ofw_bus interface */
  481         DEVMETHOD(ofw_bus_get_node,     a37x0_spi_get_node),
  482 
  483         DEVMETHOD_END
  484 };
  485 
  486 static devclass_t a37x0_spi_devclass;
  487 
  488 static driver_t a37x0_spi_driver = {
  489         "spi",
  490         a37x0_spi_methods,
  491         sizeof(struct a37x0_spi_softc),
  492 };
  493 
  494 DRIVER_MODULE(a37x0_spi, simplebus, a37x0_spi_driver, a37x0_spi_devclass, 0, 0);

Cache object: 338cee83b96b6c090b735f91da104f12


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