summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorloos <loos@FreeBSD.org>2016-03-29 19:11:04 +0000
committerloos <loos@FreeBSD.org>2016-03-29 19:11:04 +0000
commit7fa93ae37c78d9c4648f15bbbfd8968db37c790b (patch)
treee48aaa1ab44fc1a54374b0b741c40182cca148c9
parent6f05cd22f0529efa92d1c3bf53cc9588751086ec (diff)
downloadFreeBSD-src-7fa93ae37c78d9c4648f15bbbfd8968db37c790b.zip
FreeBSD-src-7fa93ae37c78d9c4648f15bbbfd8968db37c790b.tar.gz
Add the SPI driver for am335x.
This driver works in PIO mode for now, interrupts are available only when FIFO is enabled. The FIFO cannot be used with arbitrary sizes which defeat its general use. At some point we can add DMA transfers where the FIFO can be more useful. Tested on uBMC (microBMC) and BBB. Sponsored by: Rubicon Communications (Netgate)
-rw-r--r--sys/arm/ti/am335x/am335x_prcm.c10
-rw-r--r--sys/arm/ti/files.ti1
-rw-r--r--sys/arm/ti/ti_hwmods.c3
-rw-r--r--sys/arm/ti/ti_prcm.h4
-rw-r--r--sys/arm/ti/ti_spi.c582
-rw-r--r--sys/arm/ti/ti_spireg.h97
-rw-r--r--sys/arm/ti/ti_spivar.h71
7 files changed, 768 insertions, 0 deletions
diff --git a/sys/arm/ti/am335x/am335x_prcm.c b/sys/arm/ti/am335x/am335x_prcm.c
index 8a6476d..f72bb54 100644
--- a/sys/arm/ti/am335x/am335x_prcm.c
+++ b/sys/arm/ti/am335x/am335x_prcm.c
@@ -64,6 +64,8 @@ __FBSDID("$FreeBSD$");
#define CM_PER_MMC0_CLKCTRL (CM_PER + 0x03C)
#define CM_PER_I2C2_CLKCTRL (CM_PER + 0x044)
#define CM_PER_I2C1_CLKCTRL (CM_PER + 0x048)
+#define CM_PER_SPI0_CLKCTRL (CM_PER + 0x04C)
+#define CM_PER_SPI1_CLKCTRL (CM_PER + 0x050)
#define CM_PER_UART1_CLKCTRL (CM_PER + 0x06C)
#define CM_PER_UART2_CLKCTRL (CM_PER + 0x070)
#define CM_PER_UART3_CLKCTRL (CM_PER + 0x074)
@@ -274,6 +276,10 @@ struct ti_clock_dev ti_am335x_clk_devmap[] = {
AM335X_GENERIC_CLOCK_DEV(I2C2_CLK),
AM335X_GENERIC_CLOCK_DEV(I2C3_CLK),
+ /* McSPI we use hwmods as reference, not units in spec */
+ AM335X_GENERIC_CLOCK_DEV(SPI0_CLK),
+ AM335X_GENERIC_CLOCK_DEV(SPI1_CLK),
+
/* TSC_ADC */
AM335X_GENERIC_CLOCK_DEV(TSC_ADC_CLK),
@@ -356,6 +362,10 @@ static struct am335x_clk_details g_am335x_clk_details[] = {
_CLK_DETAIL(I2C2_CLK, CM_PER_I2C1_CLKCTRL, 0),
_CLK_DETAIL(I2C3_CLK, CM_PER_I2C2_CLKCTRL, 0),
+ /* McSPI modules, hwmods start with spi0 */
+ _CLK_DETAIL(SPI0_CLK, CM_PER_SPI0_CLKCTRL, 0),
+ _CLK_DETAIL(SPI1_CLK, CM_PER_SPI1_CLKCTRL, 0),
+
/* TSC_ADC module */
_CLK_DETAIL(TSC_ADC_CLK, CM_WKUP_ADC_TSC_CLKCTRL, 0),
diff --git a/sys/arm/ti/files.ti b/sys/arm/ti/files.ti
index d4daab1..6f571c6 100644
--- a/sys/arm/ti/files.ti
+++ b/sys/arm/ti/files.ti
@@ -18,6 +18,7 @@ arm/ti/ti_gpio.c optional gpio
arm/ti/ti_gpio_if.m optional gpio
arm/ti/ti_i2c.c optional ti_i2c
arm/ti/ti_sdhci.c optional sdhci
+arm/ti/ti_spi.c optional ti_spi
dev/uart/uart_dev_ti8250.c optional uart
dev/uart/uart_dev_ns8250.c optional uart
diff --git a/sys/arm/ti/ti_hwmods.c b/sys/arm/ti/ti_hwmods.c
index 1488e55..db96235 100644
--- a/sys/arm/ti/ti_hwmods.c
+++ b/sys/arm/ti/ti_hwmods.c
@@ -76,6 +76,9 @@ struct hwmod ti_hwmods[] = {
{"epwmss1", PWMSS1_CLK},
{"epwmss2", PWMSS2_CLK},
+ {"spi0", SPI0_CLK},
+ {"spi1", SPI1_CLK},
+
{"timer1", TIMER1_CLK},
{"timer2", TIMER2_CLK},
{"timer3", TIMER3_CLK},
diff --git a/sys/arm/ti/ti_prcm.h b/sys/arm/ti/ti_prcm.h
index c40439a..61b6960 100644
--- a/sys/arm/ti/ti_prcm.h
+++ b/sys/arm/ti/ti_prcm.h
@@ -158,6 +158,10 @@ typedef enum {
/* RTC module */
RTC_CLK = 1900,
+
+ /* McSPI */
+ SPI0_CLK = 2000,
+ SPI1_CLK,
} clk_ident_t;
/*
diff --git a/sys/arm/ti/ti_spi.c b/sys/arm/ti/ti_spi.c
new file mode 100644
index 0000000..b092000
--- /dev/null
+++ b/sys/arm/ti/ti_spi.c
@@ -0,0 +1,582 @@
+/*-
+ * Copyright (c) 2016 Rubicon Communications, LLC (Netgate)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <machine/intr.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/spibus/spi.h>
+#include <dev/spibus/spibusvar.h>
+
+#include <arm/ti/ti_prcm.h>
+#include <arm/ti/ti_hwmods.h>
+#include <arm/ti/ti_spireg.h>
+#include <arm/ti/ti_spivar.h>
+
+#include "spibus_if.h"
+
+static void ti_spi_intr(void *);
+static int ti_spi_detach(device_t);
+
+#undef TI_SPI_DEBUG
+#ifdef TI_SPI_DEBUG
+#define IRQSTATUSBITS \
+ "\020\1TX0_EMPTY\2TX0_UNDERFLOW\3RX0_FULL\4RX0_OVERFLOW" \
+ "\5TX1_EMPTY\6TX1_UNDERFLOW\7RX1_FULL\11TX2_EMPTY" \
+ "\12TX1_UNDERFLOW\13RX2_FULL\15TX3_EMPTY\16TX3_UNDERFLOW" \
+ "\17RX3_FULL\22EOW"
+#define CONFBITS \
+ "\020\1PHA\2POL\7EPOL\17DMAW\20DMAR\21DPE0\22DPE1\23IS" \
+ "\24TURBO\25FORCE\30SBE\31SBPOL\34FFEW\35FFER\36CLKG"
+#define STATBITS \
+ "\020\1RXS\2TXS\3EOT\4TXFFE\5TXFFF\6RXFFE\7RXFFFF"
+#define MODULCTRLBITS \
+ "\020\1SINGLE\2NOSPIEN\3SLAVE\4SYST\10MOA\11FDAA"
+#define CTRLBITS \
+ "\020\1ENABLED"
+
+static void
+ti_spi_printr(device_t dev)
+{
+ int clk, conf, ctrl, div, i, j, wl;
+ struct ti_spi_softc *sc;
+ uint32_t reg;
+
+ sc = device_get_softc(dev);
+ reg = TI_SPI_READ(sc, MCSPI_SYSCONFIG);
+ device_printf(dev, "SYSCONFIG: %#x\n", reg);
+ reg = TI_SPI_READ(sc, MCSPI_SYSSTATUS);
+ device_printf(dev, "SYSSTATUS: %#x\n", reg);
+ reg = TI_SPI_READ(sc, MCSPI_IRQSTATUS);
+ device_printf(dev, "IRQSTATUS: 0x%b\n", reg, IRQSTATUSBITS);
+ reg = TI_SPI_READ(sc, MCSPI_IRQENABLE);
+ device_printf(dev, "IRQENABLE: 0x%b\n", reg, IRQSTATUSBITS);
+ reg = TI_SPI_READ(sc, MCSPI_MODULCTRL);
+ device_printf(dev, "MODULCTRL: 0x%b\n", reg, MODULCTRLBITS);
+ for (i = 0; i < sc->sc_numcs; i++) {
+ ctrl = TI_SPI_READ(sc, MCSPI_CTRL_CH(i));
+ conf = TI_SPI_READ(sc, MCSPI_CONF_CH(i));
+ device_printf(dev, "CH%dCONF: 0x%b\n", i, conf, CONFBITS);
+ if (conf & MCSPI_CONF_CLKG) {
+ div = (conf >> MCSPI_CONF_CLK_SHIFT) & MCSPI_CONF_CLK_MSK;
+ div |= ((ctrl >> MCSPI_CTRL_EXTCLK_SHIFT) & MCSPI_CTRL_EXTCLK_MSK) << 4;
+ } else {
+ div = 1;
+ j = (conf >> MCSPI_CONF_CLK_SHIFT) & MCSPI_CONF_CLK_MSK;
+ while (j-- > 0)
+ div <<= 1;
+ }
+ clk = TI_SPI_GCLK / div;
+ wl = ((conf >> MCSPI_CONF_WL_SHIFT) & MCSPI_CONF_WL_MSK) + 1;
+ device_printf(dev, "wordlen: %-2d clock: %d\n", wl, clk);
+ reg = TI_SPI_READ(sc, MCSPI_STAT_CH(i));
+ device_printf(dev, "CH%dSTAT: 0x%b\n", i, reg, STATBITS);
+ device_printf(dev, "CH%dCTRL: 0x%b\n", i, ctrl, CTRLBITS);
+ }
+ reg = TI_SPI_READ(sc, MCSPI_XFERLEVEL);
+ device_printf(dev, "XFERLEVEL: %#x\n", reg);
+}
+#endif
+
+static void
+ti_spi_set_clock(struct ti_spi_softc *sc, int ch, int freq)
+{
+ uint32_t clkdiv, conf, div, extclk, reg;
+
+ clkdiv = TI_SPI_GCLK / freq;
+ if (clkdiv > MCSPI_EXTCLK_MSK) {
+ extclk = 0;
+ clkdiv = 0;
+ div = 1;
+ while (TI_SPI_GCLK / div > freq && clkdiv <= 0xf) {
+ clkdiv++;
+ div <<= 1;
+ }
+ conf = clkdiv << MCSPI_CONF_CLK_SHIFT;
+ } else {
+ extclk = clkdiv >> 4;
+ clkdiv &= MCSPI_CONF_CLK_MSK;
+ conf = MCSPI_CONF_CLKG | clkdiv << MCSPI_CONF_CLK_SHIFT;
+ }
+
+ reg = TI_SPI_READ(sc, MCSPI_CTRL_CH(ch));
+ reg &= ~(MCSPI_CTRL_EXTCLK_MSK << MCSPI_CTRL_EXTCLK_SHIFT);
+ reg |= extclk << MCSPI_CTRL_EXTCLK_SHIFT;
+ TI_SPI_WRITE(sc, MCSPI_CTRL_CH(ch), reg);
+
+ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(ch));
+ reg &= ~(MCSPI_CONF_CLKG | MCSPI_CONF_CLK_MSK << MCSPI_CONF_CLK_SHIFT);
+ TI_SPI_WRITE(sc, MCSPI_CONF_CH(ch), reg | conf);
+}
+
+static int
+ti_spi_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+ if (!ofw_bus_is_compatible(dev, "ti,omap4-mcspi"))
+ return (ENXIO);
+
+ device_set_desc(dev, "TI McSPI controller");
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ti_spi_attach(device_t dev)
+{
+ int clk_id, err, i, rid, timeout;
+ struct ti_spi_softc *sc;
+ uint32_t rev;
+
+ sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+
+ /*
+ * Get the MMCHS device id from FDT. If it's not there use the newbus
+ * unit number (which will work as long as the devices are in order and
+ * none are skipped in the fdt). Note that this is a property we made
+ * up and added in freebsd, it doesn't exist in the published bindings.
+ */
+ clk_id = ti_hwmods_get_clock(dev);
+ if (clk_id == INVALID_CLK_IDENT) {
+ device_printf(dev,
+ "failed to get clock based on hwmods property\n");
+ return (EINVAL);
+ }
+
+ /* Activate the McSPI module. */
+ err = ti_prcm_clk_enable(clk_id);
+ if (err) {
+ device_printf(dev, "Error: failed to activate source clock\n");
+ return (err);
+ }
+
+ /* Get the number of available channels. */
+ if ((OF_getencprop(ofw_bus_get_node(dev), "ti,spi-num-cs",
+ &sc->sc_numcs, sizeof(sc->sc_numcs))) <= 0) {
+ sc->sc_numcs = 2;
+ }
+
+ rid = 0;
+ sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_mem_res) {
+ device_printf(dev, "cannot allocate memory window\n");
+ return (ENXIO);
+ }
+
+ sc->sc_bst = rman_get_bustag(sc->sc_mem_res);
+ sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res);
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
+ device_printf(dev, "cannot allocate interrupt\n");
+ return (ENXIO);
+ }
+
+ /* Hook up our interrupt handler. */
+ if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
+ NULL, ti_spi_intr, sc, &sc->sc_intrhand)) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
+ device_printf(dev, "cannot setup the interrupt handler\n");
+ return (ENXIO);
+ }
+
+ mtx_init(&sc->sc_mtx, "ti_spi", NULL, MTX_DEF);
+
+ /* Issue a softreset to the controller */
+ TI_SPI_WRITE(sc, MCSPI_SYSCONFIG, MCSPI_SYSCONFIG_SOFTRESET);
+ timeout = 1000;
+ while (!(TI_SPI_READ(sc, MCSPI_SYSSTATUS) &
+ MCSPI_SYSSTATUS_RESETDONE)) {
+ if (--timeout == 0) {
+ device_printf(dev,
+ "Error: Controller reset operation timed out\n");
+ ti_spi_detach(dev);
+ return (ENXIO);
+ }
+ DELAY(100);
+ }
+
+ /* Print the McSPI module revision. */
+ rev = TI_SPI_READ(sc, MCSPI_REVISION);
+ device_printf(dev,
+ "scheme: %#x func: %#x rtl: %d rev: %d.%d custom rev: %d\n",
+ (rev >> MCSPI_REVISION_SCHEME_SHIFT) & MCSPI_REVISION_SCHEME_MSK,
+ (rev >> MCSPI_REVISION_FUNC_SHIFT) & MCSPI_REVISION_FUNC_MSK,
+ (rev >> MCSPI_REVISION_RTL_SHIFT) & MCSPI_REVISION_RTL_MSK,
+ (rev >> MCSPI_REVISION_MAJOR_SHIFT) & MCSPI_REVISION_MAJOR_MSK,
+ (rev >> MCSPI_REVISION_MINOR_SHIFT) & MCSPI_REVISION_MINOR_MSK,
+ (rev >> MCSPI_REVISION_CUSTOM_SHIFT) & MCSPI_REVISION_CUSTOM_MSK);
+
+ /* Set Master mode, single channel. */
+ TI_SPI_WRITE(sc, MCSPI_MODULCTRL, MCSPI_MODULCTRL_SINGLE);
+
+ /* Clear pending interrupts and disable interrupts. */
+ TI_SPI_WRITE(sc, MCSPI_IRQENABLE, 0x0);
+ TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, 0xffff);
+
+ for (i = 0; i < sc->sc_numcs; i++) {
+ /*
+ * Default to SPI mode 0, CS active low, 8 bits word length and
+ * 500kHz clock.
+ */
+ TI_SPI_WRITE(sc, MCSPI_CONF_CH(i),
+ MCSPI_CONF_DPE0 | MCSPI_CONF_EPOL |
+ (8 - 1) << MCSPI_CONF_WL_SHIFT);
+ /* Set initial clock - 500kHz. */
+ ti_spi_set_clock(sc, i, 500000);
+ }
+
+#ifdef TI_SPI_DEBUG
+ ti_spi_printr(dev);
+#endif
+
+ device_add_child(dev, "spibus", -1);
+
+ return (bus_generic_attach(dev));
+}
+
+static int
+ti_spi_detach(device_t dev)
+{
+ struct ti_spi_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Clear pending interrupts and disable interrupts. */
+ TI_SPI_WRITE(sc, MCSPI_IRQENABLE, 0);
+ TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, 0xffff);
+
+ /* Reset controller. */
+ TI_SPI_WRITE(sc, MCSPI_SYSCONFIG, MCSPI_SYSCONFIG_SOFTRESET);
+
+ bus_generic_detach(dev);
+
+ mtx_destroy(&sc->sc_mtx);
+ if (sc->sc_intrhand)
+ bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand);
+ if (sc->sc_irq_res)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ if (sc->sc_mem_res)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
+
+ return (0);
+}
+
+static int
+ti_spi_fill_fifo(struct ti_spi_softc *sc)
+{
+ int bytes, timeout;
+ struct spi_command *cmd;
+ uint32_t written;
+ uint8_t *data;
+
+ cmd = sc->sc_cmd;
+ bytes = min(sc->sc_len - sc->sc_written, sc->sc_fifolvl);
+ while (bytes-- > 0) {
+ data = (uint8_t *)cmd->tx_cmd;
+ written = sc->sc_written++;
+ if (written >= cmd->tx_cmd_sz) {
+ data = (uint8_t *)cmd->tx_data;
+ written -= cmd->tx_cmd_sz;
+ }
+ if (sc->sc_fifolvl == 1) {
+ /* FIFO disabled. */
+ timeout = 1000;
+ while (--timeout > 0 && (TI_SPI_READ(sc,
+ MCSPI_STAT_CH(sc->sc_cs)) & MCSPI_STAT_TXS) == 0) {
+ DELAY(1);
+ }
+ if (timeout == 0)
+ return (-1);
+ }
+ TI_SPI_WRITE(sc, MCSPI_TX_CH(sc->sc_cs), data[written]);
+ }
+
+ return (0);
+}
+
+static int
+ti_spi_drain_fifo(struct ti_spi_softc *sc)
+{
+ int bytes, timeout;
+ struct spi_command *cmd;
+ uint32_t read;
+ uint8_t *data;
+
+ cmd = sc->sc_cmd;
+ bytes = min(sc->sc_len - sc->sc_read, sc->sc_fifolvl);
+ while (bytes-- > 0) {
+ data = (uint8_t *)cmd->rx_cmd;
+ read = sc->sc_read++;
+ if (read >= cmd->rx_cmd_sz) {
+ data = (uint8_t *)cmd->rx_data;
+ read -= cmd->rx_cmd_sz;
+ }
+ if (sc->sc_fifolvl == 1) {
+ /* FIFO disabled. */
+ timeout = 1000;
+ while (--timeout > 0 && (TI_SPI_READ(sc,
+ MCSPI_STAT_CH(sc->sc_cs)) & MCSPI_STAT_RXS) == 0) {
+ DELAY(1);
+ }
+ if (timeout == 0)
+ return (-1);
+ }
+ data[read] = TI_SPI_READ(sc, MCSPI_RX_CH(sc->sc_cs));
+ }
+
+ return (0);
+}
+
+static void
+ti_spi_intr(void *arg)
+{
+ int eow;
+ struct ti_spi_softc *sc;
+ uint32_t status;
+
+ eow = 0;
+ sc = (struct ti_spi_softc *)arg;
+ TI_SPI_LOCK(sc);
+ status = TI_SPI_READ(sc, MCSPI_IRQSTATUS);
+
+ /*
+ * No new TX_empty or RX_full event will be asserted while the CPU has
+ * not performed the number of writes or reads defined by
+ * MCSPI_XFERLEVEL[AEL] and MCSPI_XFERLEVEL[AFL]. It is responsibility
+ * of CPU perform the right number of writes and reads.
+ */
+ if (status & MCSPI_IRQ_TX0_EMPTY)
+ ti_spi_fill_fifo(sc);
+ if (status & MCSPI_IRQ_RX0_FULL)
+ ti_spi_drain_fifo(sc);
+
+ if (status & MCSPI_IRQ_EOW)
+ eow = 1;
+
+ /* Clear interrupt status. */
+ TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, status);
+
+ /* Check for end of transfer. */
+ if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len) {
+ sc->sc_flags |= TI_SPI_DONE;
+ wakeup(sc->sc_dev);
+ }
+
+ TI_SPI_UNLOCK(sc);
+}
+
+static int
+ti_spi_pio_transfer(struct ti_spi_softc *sc)
+{
+
+ while (sc->sc_len - sc->sc_written > 0) {
+ if (ti_spi_fill_fifo(sc) == -1)
+ return (EIO);
+ if (ti_spi_drain_fifo(sc) == -1)
+ return (EIO);
+ }
+
+ return (0);
+}
+
+static int
+ti_spi_gcd(int a, int b)
+{
+ int m;
+
+ while ((m = a % b) != 0) {
+ a = b;
+ b = m;
+ }
+
+ return (b);
+}
+
+static int
+ti_spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
+{
+ int cs, err;
+ struct ti_spi_softc *sc;
+ uint32_t reg;
+
+ sc = device_get_softc(dev);
+
+ KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz,
+ ("TX/RX command sizes should be equal"));
+ KASSERT(cmd->tx_data_sz == cmd->rx_data_sz,
+ ("TX/RX data sizes should be equal"));
+
+ /* Get the proper chip select for this child. */
+ spibus_get_cs(child, &cs);
+ if (cs < 0 || cs > sc->sc_numcs) {
+ device_printf(dev, "Invalid chip select %d requested by %s\n",
+ cs, device_get_nameunit(child));
+ return (EINVAL);
+ }
+
+ TI_SPI_LOCK(sc);
+
+ /* If the controller is in use wait until it is available. */
+ while (sc->sc_flags & TI_SPI_BUSY)
+ mtx_sleep(dev, &sc->sc_mtx, 0, "ti_spi", 0);
+
+ /* Now we have control over SPI controller. */
+ sc->sc_flags = TI_SPI_BUSY;
+
+ /* Save the SPI command data. */
+ sc->sc_cs = cs;
+ sc->sc_cmd = cmd;
+ sc->sc_read = 0;
+ sc->sc_written = 0;
+ sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz;
+ sc->sc_fifolvl = ti_spi_gcd(sc->sc_len, TI_SPI_FIFOSZ);
+ if (sc->sc_fifolvl < 2 || sc->sc_len > 0xffff)
+ sc->sc_fifolvl = 1; /* FIFO disabled. */
+ /* Disable FIFO for now. */
+ sc->sc_fifolvl = 1;
+
+ /* Use a safe clock - 500kHz. */
+ ti_spi_set_clock(sc, sc->sc_cs, 500000);
+
+ /* Disable the FIFO. */
+ TI_SPI_WRITE(sc, MCSPI_XFERLEVEL, 0);
+
+ /* 8 bits word, d0 miso, d1 mosi, mode 0 and CS active low. */
+ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs));
+ reg &= ~(MCSPI_CONF_FFER | MCSPI_CONF_FFEW | MCSPI_CONF_SBPOL |
+ MCSPI_CONF_SBE | MCSPI_CONF_TURBO | MCSPI_CONF_IS |
+ MCSPI_CONF_DPE1 | MCSPI_CONF_DPE0 | MCSPI_CONF_DMAR |
+ MCSPI_CONF_DMAW | MCSPI_CONF_EPOL);
+ reg |= MCSPI_CONF_DPE0 | MCSPI_CONF_EPOL | MCSPI_CONF_WL8BITS;
+ TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg);
+
+#if 0
+ /* Enable channel interrupts. */
+ reg = TI_SPI_READ(sc, MCSPI_IRQENABLE);
+ reg |= 0xf;
+ TI_SPI_WRITE(sc, MCSPI_IRQENABLE, reg);
+#endif
+
+ /* Start the transfer. */
+ reg = TI_SPI_READ(sc, MCSPI_CTRL_CH(sc->sc_cs));
+ TI_SPI_WRITE(sc, MCSPI_CTRL_CH(sc->sc_cs), reg | MCSPI_CTRL_ENABLE);
+
+ /* Force CS on. */
+ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs));
+ TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg |= MCSPI_CONF_FORCE);
+
+ err = 0;
+ if (sc->sc_fifolvl == 1)
+ err = ti_spi_pio_transfer(sc);
+
+ /* Force CS off. */
+ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs));
+ reg &= ~MCSPI_CONF_FORCE;
+ TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg);
+
+ /* Disable IRQs. */
+ reg = TI_SPI_READ(sc, MCSPI_IRQENABLE);
+ reg &= ~0xf;
+ TI_SPI_WRITE(sc, MCSPI_IRQENABLE, reg);
+ TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, 0xf);
+
+ /* Disable the SPI channel. */
+ reg = TI_SPI_READ(sc, MCSPI_CTRL_CH(sc->sc_cs));
+ reg &= ~MCSPI_CTRL_ENABLE;
+ TI_SPI_WRITE(sc, MCSPI_CTRL_CH(sc->sc_cs), reg);
+
+ /* Disable FIFO. */
+ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs));
+ reg &= ~(MCSPI_CONF_FFER | MCSPI_CONF_FFEW);
+ TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg);
+
+ /* Release the controller and wakeup the next thread waiting for it. */
+ sc->sc_flags = 0;
+ wakeup_one(dev);
+ TI_SPI_UNLOCK(sc);
+
+ return (err);
+}
+
+static phandle_t
+ti_spi_get_node(device_t bus, device_t dev)
+{
+
+ /* Share controller node with spibus. */
+ return (ofw_bus_get_node(bus));
+}
+
+static device_method_t ti_spi_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ti_spi_probe),
+ DEVMETHOD(device_attach, ti_spi_attach),
+ DEVMETHOD(device_detach, ti_spi_detach),
+
+ /* SPI interface */
+ DEVMETHOD(spibus_transfer, ti_spi_transfer),
+
+ /* ofw_bus interface */
+ DEVMETHOD(ofw_bus_get_node, ti_spi_get_node),
+
+ DEVMETHOD_END
+};
+
+static devclass_t ti_spi_devclass;
+
+static driver_t ti_spi_driver = {
+ "spi",
+ ti_spi_methods,
+ sizeof(struct ti_spi_softc),
+};
+
+DRIVER_MODULE(ti_spi, simplebus, ti_spi_driver, ti_spi_devclass, 0, 0);
diff --git a/sys/arm/ti/ti_spireg.h b/sys/arm/ti/ti_spireg.h
new file mode 100644
index 0000000..f31f55e
--- /dev/null
+++ b/sys/arm/ti/ti_spireg.h
@@ -0,0 +1,97 @@
+/*-
+ * Copyright (c) 2016 Rubicon Communications, LLC (Netgate)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TI_SPIREG_H_
+#define _TI_SPIREG_H_
+
+#define TI_SPI_GCLK 48000000U
+#define TI_SPI_FIFOSZ 32
+#define MCSPI_REVISION 0x0
+#define MCSPI_REVISION_SCHEME_SHIFT 30
+#define MCSPI_REVISION_SCHEME_MSK 0x3
+#define MCSPI_REVISION_FUNC_SHIFT 16
+#define MCSPI_REVISION_FUNC_MSK 0xfff
+#define MCSPI_REVISION_RTL_SHIFT 11
+#define MCSPI_REVISION_RTL_MSK 0x1f
+#define MCSPI_REVISION_MAJOR_SHIFT 8
+#define MCSPI_REVISION_MAJOR_MSK 0x7
+#define MCSPI_REVISION_CUSTOM_SHIFT 6
+#define MCSPI_REVISION_CUSTOM_MSK 0x3
+#define MCSPI_REVISION_MINOR_SHIFT 0
+#define MCSPI_REVISION_MINOR_MSK 0x3f
+#define MCSPI_SYSCONFIG 0x110
+#define MCSPI_SYSCONFIG_SOFTRESET (1 << 1)
+#define MCSPI_SYSSTATUS 0x114
+#define MCSPI_SYSSTATUS_RESETDONE (1 << 0)
+#define MCSPI_MODULCTRL 0x128
+#define MCSPI_MODULCTRL_SLAVE (1 << 2)
+#define MCSPI_MODULCTRL_SINGLE (1 << 0)
+#define MCSPI_IRQSTATUS 0x118
+#define MCSPI_IRQENABLE 0x11c
+#define MCSPI_IRQ_EOW (1 << 17)
+#define MCSPI_IRQ_RX0_OVERFLOW (1 << 3)
+#define MCSPI_IRQ_RX0_FULL (1 << 2)
+#define MCSPI_IRQ_TX0_UNDERFLOW (1 << 1)
+#define MCSPI_IRQ_TX0_EMPTY (1 << 0)
+#define MCSPI_CONF_CH(_c) (0x12c + 0x14 * (_c))
+#define MCSPI_CONF_CLKG (1 << 29)
+#define MCSPI_CONF_FFER (1 << 28)
+#define MCSPI_CONF_FFEW (1 << 27)
+#define MCSPI_CONF_SBPOL (1 << 24)
+#define MCSPI_CONF_SBE (1 << 23)
+#define MCSPI_CONF_FORCE (1 << 20)
+#define MCSPI_CONF_TURBO (1 << 19)
+#define MCSPI_CONF_IS (1 << 18)
+#define MCSPI_CONF_DPE1 (1 << 17)
+#define MCSPI_CONF_DPE0 (1 << 16)
+#define MCSPI_CONF_DMAR (1 << 15)
+#define MCSPI_CONF_DMAW (1 << 14)
+#define MCSPI_CONF_WL_MSK 0x1f
+#define MCSPI_CONF_WL_SHIFT 7
+#define MCSPI_CONF_WL8BITS (7 << MCSPI_CONF_WL_SHIFT)
+#define MCSPI_CONF_EPOL (1 << 6)
+#define MCSPI_CONF_CLK_MSK 0xf
+#define MCSPI_CONF_CLK_SHIFT 2
+#define MCSPI_CONF_POL (1 << 1)
+#define MCSPI_CONF_PHA (1 << 0)
+#define MCSPI_STAT_CH(_c) (0x130 + 0x14 * (_c))
+#define MCSPI_STAT_TXFFF (1 << 4)
+#define MCSPI_STAT_TXS (1 << 1)
+#define MCSPI_STAT_RXS (1 << 0)
+#define MCSPI_CTRL_CH(_c) (0x134 + 0x14 * (_c))
+#define MCSPI_EXTCLK_MSK 0xfff
+#define MCSPI_CTRL_EXTCLK_MSK 0xff
+#define MCSPI_CTRL_EXTCLK_SHIFT 8
+#define MCSPI_CTRL_ENABLE (1 << 0)
+#define MCSPI_TX_CH(_c) (0x138 + 0x14 * (_c))
+#define MCSPI_RX_CH(_c) (0x13c + 0x14 * (_c))
+#define MCSPI_XFERLEVEL 0x17c
+#define MCSPI_XFERLEVEL_AFL(_a) (((_a) >> 8) & 0xff)
+#define MCSPI_XFERLEVEL_AEL(_a) (((_a) >> 0) & 0xff)
+
+#endif /* _TI_SPIREG_H_ */
diff --git a/sys/arm/ti/ti_spivar.h b/sys/arm/ti/ti_spivar.h
new file mode 100644
index 0000000..89731f3
--- /dev/null
+++ b/sys/arm/ti/ti_spivar.h
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (c) 2016 Rubicon Communications, LLC (Netgate)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TI_SPIVAR_H_
+#define _TI_SPIVAR_H_
+
+struct ti_spi_softc {
+ bus_space_tag_t sc_bst;
+ bus_space_handle_t sc_bsh;
+ device_t sc_dev;
+ int sc_numcs;
+ struct mtx sc_mtx;
+ struct resource *sc_mem_res;
+ struct resource *sc_irq_res;
+ struct {
+ int cs;
+ int fifolvl;
+ struct spi_command *cmd;
+ uint32_t len;
+ uint32_t read;
+ uint32_t written;
+ } xfer;
+ uint32_t sc_flags;
+ void *sc_intrhand;
+#define sc_cs xfer.cs
+#define sc_fifolvl xfer.fifolvl
+#define sc_cmd xfer.cmd
+#define sc_len xfer.len
+#define sc_read xfer.read
+#define sc_written xfer.written
+};
+
+#define TI_SPI_BUSY 0x1
+#define TI_SPI_DONE 0x2
+
+#define TI_SPI_WRITE(_sc, _off, _val) \
+ bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off), (_val))
+#define TI_SPI_READ(_sc, _off) \
+ bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off))
+
+#define TI_SPI_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define TI_SPI_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+
+#endif /* _TI_SPIVAR_H_ */
OpenPOWER on IntegriCloud