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/mv_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) 2017-2018, Rubicon Communications, LLC (Netgate)
    3  * All rights reserved.
    4  *
    5  * Redistribution and use in source and binary forms, with or without
    6  * modification, are permitted provided that the following conditions
    7  * are met:
    8  * 1. Redistributions of source code must retain the above copyright
    9  *    notice, this list of conditions and the following disclaimer.
   10  * 2. Redistributions in binary form must reproduce the above copyright
   11  *    notice, this list of conditions and the following disclaimer in the
   12  *    documentation and/or other materials provided with the distribution.
   13  *
   14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
   15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
   18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   24  *
   25  */
   26 
   27 #include <sys/cdefs.h>
   28 __FBSDID("$FreeBSD$");
   29 
   30 #include <sys/param.h>
   31 #include <sys/systm.h>
   32 #include <sys/bus.h>
   33 
   34 #include <sys/kernel.h>
   35 #include <sys/lock.h>
   36 #include <sys/module.h>
   37 #include <sys/mutex.h>
   38 #include <sys/rman.h>
   39 
   40 #include <machine/bus.h>
   41 #include <machine/resource.h>
   42 #include <machine/intr.h>
   43 
   44 #include <dev/ofw/ofw_bus.h>
   45 #include <dev/ofw/ofw_bus_subr.h>
   46 #include <dev/spibus/spi.h>
   47 #include <dev/spibus/spibusvar.h>
   48 
   49 #include <arm/mv/mvvar.h>
   50 
   51 #include "spibus_if.h"
   52 
   53 struct mv_spi_softc {
   54         device_t                sc_dev;
   55         struct mtx              sc_mtx;
   56         struct resource         *sc_mem_res;
   57         struct resource         *sc_irq_res;
   58         struct spi_command      *sc_cmd;
   59         bus_space_tag_t         sc_bst;
   60         bus_space_handle_t      sc_bsh;
   61         uint32_t                sc_len;
   62         uint32_t                sc_read;
   63         uint32_t                sc_flags;
   64         uint32_t                sc_written;
   65         void                    *sc_intrhand;
   66 };
   67 
   68 #define MV_SPI_BUSY             0x1
   69 #define MV_SPI_WRITE(_sc, _off, _val)           \
   70     bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off), (_val))
   71 #define MV_SPI_READ(_sc, _off)                  \
   72     bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off))
   73 #define MV_SPI_LOCK(_sc)        mtx_lock(&(_sc)->sc_mtx)
   74 #define MV_SPI_UNLOCK(_sc)      mtx_unlock(&(_sc)->sc_mtx)
   75 
   76 #define MV_SPI_CONTROL          0
   77 #define MV_SPI_CTRL_CS_MASK             7
   78 #define MV_SPI_CTRL_CS_SHIFT            2
   79 #define MV_SPI_CTRL_SMEMREADY           (1 << 1)
   80 #define MV_SPI_CTRL_CS_ACTIVE           (1 << 0)
   81 #define MV_SPI_CONF             0x4
   82 #define MV_SPI_CONF_MODE_SHIFT          12
   83 #define MV_SPI_CONF_MODE_MASK           (3 << MV_SPI_CONF_MODE_SHIFT)
   84 #define MV_SPI_CONF_BYTELEN             (1 << 5)
   85 #define MV_SPI_CONF_CLOCK_SPR_MASK      0xf
   86 #define MV_SPI_CONF_CLOCK_SPPR_MASK     1
   87 #define MV_SPI_CONF_CLOCK_SPPR_SHIFT    4
   88 #define MV_SPI_CONF_CLOCK_SPPRHI_MASK   3
   89 #define MV_SPI_CONF_CLOCK_SPPRHI_SHIFT  6
   90 #define MV_SPI_CONF_CLOCK_MASK                                          \
   91     ((MV_SPI_CONF_CLOCK_SPPRHI_MASK << MV_SPI_CONF_CLOCK_SPPRHI_SHIFT) | \
   92     (MV_SPI_CONF_CLOCK_SPPR_MASK << MV_SPI_CONF_CLOCK_SPPR_SHIFT) |     \
   93     MV_SPI_CONF_CLOCK_SPR_MASK)
   94 #define MV_SPI_DATAOUT          0x8
   95 #define MV_SPI_DATAIN           0xc
   96 #define MV_SPI_INTR_STAT        0x10
   97 #define MV_SPI_INTR_MASK        0x14
   98 #define MV_SPI_INTR_SMEMREADY           (1 << 0)
   99 
  100 static struct ofw_compat_data compat_data[] = {
  101         {"marvell,armada-380-spi",      1},
  102         {NULL,                          0}
  103 };
  104 
  105 static void mv_spi_intr(void *);
  106 
  107 static int
  108 mv_spi_probe(device_t dev)
  109 {
  110 
  111         if (!ofw_bus_status_okay(dev))
  112                 return (ENXIO);
  113         if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
  114                 return (ENXIO);
  115 
  116         device_set_desc(dev, "Marvell SPI controller");
  117 
  118         return (BUS_PROBE_DEFAULT);
  119 }
  120 
  121 static int
  122 mv_spi_attach(device_t dev)
  123 {
  124         struct mv_spi_softc *sc;
  125         int rid;
  126         uint32_t reg;
  127 
  128         sc = device_get_softc(dev);
  129         sc->sc_dev = dev;
  130 
  131         rid = 0;
  132         sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
  133             RF_ACTIVE);
  134         if (!sc->sc_mem_res) {
  135                 device_printf(dev, "cannot allocate memory window\n");
  136                 return (ENXIO);
  137         }
  138 
  139         sc->sc_bst = rman_get_bustag(sc->sc_mem_res);
  140         sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res);
  141 
  142         rid = 0;
  143         sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
  144             RF_ACTIVE);
  145         if (!sc->sc_irq_res) {
  146                 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
  147                 device_printf(dev, "cannot allocate interrupt\n");
  148                 return (ENXIO);
  149         }
  150 
  151         /* Deactivate the bus - just in case... */
  152         reg = MV_SPI_READ(sc, MV_SPI_CONTROL);
  153         MV_SPI_WRITE(sc, MV_SPI_CONTROL, reg & ~MV_SPI_CTRL_CS_ACTIVE);
  154 
  155         /* Disable the two bytes FIFO. */
  156         reg = MV_SPI_READ(sc, MV_SPI_CONF);
  157         MV_SPI_WRITE(sc, MV_SPI_CONF, reg & ~MV_SPI_CONF_BYTELEN);
  158 
  159         /* Clear and disable interrupts. */
  160         MV_SPI_WRITE(sc, MV_SPI_INTR_MASK, 0);
  161         MV_SPI_WRITE(sc, MV_SPI_INTR_STAT, 0);
  162 
  163         /* Hook up our interrupt handler. */
  164         if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
  165             NULL, mv_spi_intr, sc, &sc->sc_intrhand)) {
  166                 bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
  167                 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
  168                 device_printf(dev, "cannot setup the interrupt handler\n");
  169                 return (ENXIO);
  170         }
  171 
  172         mtx_init(&sc->sc_mtx, "mv_spi", NULL, MTX_DEF);
  173 
  174         device_add_child(dev, "spibus", -1);
  175 
  176         /* Probe and attach the spibus when interrupts are available. */
  177         return (bus_delayed_attach_children(dev));
  178 }
  179 
  180 static int
  181 mv_spi_detach(device_t dev)
  182 {
  183         struct mv_spi_softc *sc;
  184 
  185         bus_generic_detach(dev);
  186 
  187         sc = device_get_softc(dev);
  188         mtx_destroy(&sc->sc_mtx);
  189         if (sc->sc_intrhand)
  190                 bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand);
  191         if (sc->sc_irq_res)
  192                 bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
  193         if (sc->sc_mem_res)
  194                 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
  195 
  196         return (0);
  197 }
  198 
  199 static __inline void
  200 mv_spi_rx_byte(struct mv_spi_softc *sc)
  201 {
  202         struct spi_command *cmd;
  203         uint32_t read;
  204         uint8_t *p;
  205 
  206         cmd = sc->sc_cmd; 
  207         p = (uint8_t *)cmd->rx_cmd;
  208         read = sc->sc_read++;
  209         if (read >= cmd->rx_cmd_sz) {
  210                 p = (uint8_t *)cmd->rx_data;
  211                 read -= cmd->rx_cmd_sz;
  212         }
  213         p[read] = MV_SPI_READ(sc, MV_SPI_DATAIN) & 0xff;
  214 }
  215 
  216 static __inline void
  217 mv_spi_tx_byte(struct mv_spi_softc *sc)
  218 {
  219         struct spi_command *cmd;
  220         uint32_t written;
  221         uint8_t *p;
  222 
  223         cmd = sc->sc_cmd; 
  224         p = (uint8_t *)cmd->tx_cmd;
  225         written = sc->sc_written++;
  226         if (written >= cmd->tx_cmd_sz) {
  227                 p = (uint8_t *)cmd->tx_data;
  228                 written -= cmd->tx_cmd_sz;
  229         }
  230         MV_SPI_WRITE(sc, MV_SPI_DATAOUT, p[written]);
  231 }
  232 
  233 static void
  234 mv_spi_intr(void *arg)
  235 {
  236         struct mv_spi_softc *sc;
  237 
  238         sc = (struct mv_spi_softc *)arg;
  239         MV_SPI_LOCK(sc);
  240 
  241         /* Filter stray interrupts. */
  242         if ((sc->sc_flags & MV_SPI_BUSY) == 0) {
  243                 MV_SPI_UNLOCK(sc);
  244                 return;
  245         }
  246 
  247         /* RX */
  248         mv_spi_rx_byte(sc);
  249 
  250         /* TX */
  251         mv_spi_tx_byte(sc);
  252 
  253         /* Check for end of transfer. */
  254         if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len)
  255                 wakeup(sc->sc_dev);
  256 
  257         MV_SPI_UNLOCK(sc);
  258 }
  259 
  260 static int
  261 mv_spi_psc_calc(uint32_t clock, uint32_t *spr, uint32_t *sppr)
  262 {
  263         uint32_t divider, tclk;
  264 
  265         tclk = get_tclk_armada38x();
  266         for (*spr = 2; *spr <= 15; (*spr)++) {
  267                 for (*sppr = 0; *sppr <= 7; (*sppr)++) {
  268                         divider = *spr * (1 << *sppr);
  269                         if (tclk / divider <= clock)
  270                                 return (0);
  271                 }
  272         }
  273 
  274         return (EINVAL);
  275 }
  276 
  277 static int
  278 mv_spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
  279 {
  280         struct mv_spi_softc *sc;
  281         uint32_t clock, cs, mode, reg, spr, sppr;
  282         int resid, timeout;
  283 
  284         KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz,
  285             ("TX/RX command sizes should be equal"));
  286         KASSERT(cmd->tx_data_sz == cmd->rx_data_sz,
  287             ("TX/RX data sizes should be equal"));
  288 
  289         /* Get the proper chip select, mode and clock for this transfer. */
  290         spibus_get_cs(child, &cs);
  291         cs &= ~SPIBUS_CS_HIGH;
  292         spibus_get_mode(child, &mode);
  293         if (mode > 3) {
  294                 device_printf(dev,
  295                     "Invalid mode %u requested by %s\n", mode,
  296                     device_get_nameunit(child));
  297                 return (EINVAL);
  298         }
  299         spibus_get_clock(child, &clock);
  300         if (clock == 0 || mv_spi_psc_calc(clock, &spr, &sppr) != 0) {
  301                 device_printf(dev,
  302                     "Invalid clock %uHz requested by %s\n", clock,
  303                     device_get_nameunit(child));
  304                 return (EINVAL);
  305         }
  306 
  307         sc = device_get_softc(dev);
  308         MV_SPI_LOCK(sc);
  309 
  310         /* Wait until the controller is free. */
  311         while (sc->sc_flags & MV_SPI_BUSY)
  312                 mtx_sleep(dev, &sc->sc_mtx, 0, "mv_spi", 0);
  313 
  314         /* Now we have control over SPI controller. */
  315         sc->sc_flags = MV_SPI_BUSY;
  316 
  317         /* Save a pointer to the SPI command. */
  318         sc->sc_cmd = cmd;
  319         sc->sc_read = 0;
  320         sc->sc_written = 0;
  321         sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz;
  322 
  323         /* Set SPI Mode and Clock. */
  324         reg = MV_SPI_READ(sc, MV_SPI_CONF);
  325         reg &= ~(MV_SPI_CONF_MODE_MASK | MV_SPI_CONF_CLOCK_MASK);
  326         reg |= mode << MV_SPI_CONF_MODE_SHIFT;
  327         reg |= spr & MV_SPI_CONF_CLOCK_SPR_MASK;
  328         reg |= (sppr & MV_SPI_CONF_CLOCK_SPPR_MASK) <<
  329             MV_SPI_CONF_CLOCK_SPPR_SHIFT;
  330         reg |= (sppr & MV_SPI_CONF_CLOCK_SPPRHI_MASK) <<
  331             MV_SPI_CONF_CLOCK_SPPRHI_SHIFT;
  332         MV_SPI_WRITE(sc, MV_SPI_CONTROL, reg);
  333 
  334         /* Set CS number and assert CS. */
  335         reg = (cs & MV_SPI_CTRL_CS_MASK) << MV_SPI_CTRL_CS_SHIFT;
  336         MV_SPI_WRITE(sc, MV_SPI_CONTROL, reg);
  337         reg = MV_SPI_READ(sc, MV_SPI_CONTROL);
  338         MV_SPI_WRITE(sc, MV_SPI_CONTROL, reg | MV_SPI_CTRL_CS_ACTIVE);
  339 
  340         while ((resid = sc->sc_len - sc->sc_written) > 0) {
  341                 MV_SPI_WRITE(sc, MV_SPI_INTR_STAT, 0);
  342 
  343                 /*
  344                  * Write to start the transmission and read the byte
  345                  * back when ready.
  346                  */
  347                 mv_spi_tx_byte(sc);
  348                 timeout = 1000;
  349                 while (--timeout > 0) {
  350                         reg = MV_SPI_READ(sc, MV_SPI_CONTROL);
  351                         if (reg & MV_SPI_CTRL_SMEMREADY)
  352                                 break;
  353                         DELAY(1);
  354                 }
  355                 if (timeout == 0)
  356                         break;
  357                 mv_spi_rx_byte(sc);
  358         }
  359 
  360         /* Stop the controller. */
  361         reg = MV_SPI_READ(sc, MV_SPI_CONTROL);
  362         MV_SPI_WRITE(sc, MV_SPI_CONTROL, reg & ~MV_SPI_CTRL_CS_ACTIVE);
  363         MV_SPI_WRITE(sc, MV_SPI_INTR_MASK, 0);
  364         MV_SPI_WRITE(sc, MV_SPI_INTR_STAT, 0);
  365 
  366         /* Release the controller and wakeup the next thread waiting for it. */
  367         sc->sc_flags = 0;
  368         wakeup_one(dev);
  369         MV_SPI_UNLOCK(sc);
  370 
  371         /*
  372          * Check for transfer timeout.  The SPI controller doesn't
  373          * return errors.
  374          */
  375         return ((timeout == 0) ? EIO : 0);
  376 }
  377 
  378 static phandle_t
  379 mv_spi_get_node(device_t bus, device_t dev)
  380 {
  381 
  382         return (ofw_bus_get_node(bus));
  383 }
  384 
  385 static device_method_t mv_spi_methods[] = {
  386         /* Device interface */
  387         DEVMETHOD(device_probe,         mv_spi_probe),
  388         DEVMETHOD(device_attach,        mv_spi_attach),
  389         DEVMETHOD(device_detach,        mv_spi_detach),
  390 
  391         /* SPI interface */
  392         DEVMETHOD(spibus_transfer,      mv_spi_transfer),
  393 
  394         /* ofw_bus interface */
  395         DEVMETHOD(ofw_bus_get_node,     mv_spi_get_node),
  396 
  397         DEVMETHOD_END
  398 };
  399 
  400 static driver_t mv_spi_driver = {
  401         "spi",
  402         mv_spi_methods,
  403         sizeof(struct mv_spi_softc),
  404 };
  405 
  406 DRIVER_MODULE(mv_spi, simplebus, mv_spi_driver, 0, 0);

Cache object: 12b237e5c65417e3811ac946686031cf


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