Index: head/sys/conf/files.amd64 =================================================================== --- head/sys/conf/files.amd64 +++ head/sys/conf/files.amd64 @@ -228,6 +228,7 @@ dev/if_ndis/if_ndis_pccard.c optional ndis pccard dev/if_ndis/if_ndis_pci.c optional ndis cardbus | ndis pci dev/if_ndis/if_ndis_usb.c optional ndis usb +dev/intel/spi.c optional intelspi dev/io/iodev.c optional io dev/ioat/ioat.c optional ioat pci dev/ioat/ioat_test.c optional ioat pci Index: head/sys/conf/files.i386 =================================================================== --- head/sys/conf/files.i386 +++ head/sys/conf/files.i386 @@ -275,6 +275,7 @@ dev/if_ndis/if_ndis_pccard.c optional ndis pccard dev/if_ndis/if_ndis_pci.c optional ndis cardbus | ndis pci dev/if_ndis/if_ndis_usb.c optional ndis usb +dev/intel/spi.c optional intelspi dev/io/iodev.c optional io dev/ipmi/ipmi.c optional ipmi dev/ipmi/ipmi_acpi.c optional ipmi acpi Index: head/sys/dev/intel/spi.c =================================================================== --- head/sys/dev/intel/spi.c +++ head/sys/dev/intel/spi.c @@ -0,0 +1,533 @@ +/*- + * Copyright (c) 2016 Oleksandr Tymoshenko + * 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 +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +#include "spibus_if.h" + +/** + * Macros for driver mutex locking + */ +#define INTELSPI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define INTELSPI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define INTELSPI_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ + "intelspi", MTX_DEF) +#define INTELSPI_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) +#define INTELSPI_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) +#define INTELSPI_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) + +#define INTELSPI_WRITE(_sc, _off, _val) \ + bus_write_4((_sc)->sc_mem_res, (_off), (_val)) +#define INTELSPI_READ(_sc, _off) \ + bus_read_4((_sc)->sc_mem_res, (_off)) + +#define INTELSPI_BUSY 0x1 +#define TX_FIFO_THRESHOLD 2 +#define RX_FIFO_THRESHOLD 2 +#define CLOCK_DIV_10MHZ 5 +#define DATA_SIZE_8BITS 8 + +#define CS_LOW 0 +#define CS_HIGH 1 + +#define INTELSPI_SSPREG_SSCR0 0x0 +#define SSCR0_SCR(n) (((n) - 1) << 8) +#define SSCR0_SSE (1 << 7) +#define SSCR0_FRF_SPI (0 << 4) +#define SSCR0_DSS(n) (((n) - 1) << 0) +#define INTELSPI_SSPREG_SSCR1 0x4 +#define SSCR1_TINTE (1 << 19) +#define SSCR1_RFT(n) (((n) - 1) << 10) +#define SSCR1_RFT_MASK (0xf << 10) +#define SSCR1_TFT(n) (((n) - 1) << 6) +#define SSCR1_SPI_SPH (1 << 4) +#define SSCR1_SPI_SPO (1 << 3) +#define SSCR1_MODE_MASK (SSCR1_SPI_SPO | SSCR1_SPI_SPH) +#define SSCR1_MODE_0 (0) +#define SSCR1_MODE_1 (SSCR1_SPI_SPH) +#define SSCR1_MODE_2 (SSCR1_SPI_SPO) +#define SSCR1_MODE_3 (SSCR1_SPI_SPO | SSCR1_SPI_SPH) +#define SSCR1_TIE (1 << 1) +#define SSCR1_RIE (1 << 0) +#define INTELSPI_SSPREG_SSSR 0x8 +#define SSSR_RFL_MASK (0xf << 12) +#define SSSR_TFL_MASK (0xf << 8) +#define SSSR_RNE (1 << 3) +#define SSSR_TNF (1 << 2) +#define INTELSPI_SSPREG_SSITR 0xC +#define INTELSPI_SSPREG_SSDR 0x10 +#define INTELSPI_SSPREG_SSTO 0x28 +#define INTELSPI_SSPREG_SSPSP 0x2C +#define INTELSPI_SSPREG_SSTSA 0x30 +#define INTELSPI_SSPREG_SSRSA 0x34 +#define INTELSPI_SSPREG_SSTSS 0x38 +#define INTELSPI_SSPREG_SSACD 0x3C +#define INTELSPI_SSPREG_ITF 0x40 +#define INTELSPI_SSPREG_SITF 0x44 +#define INTELSPI_SSPREG_SIRF 0x48 +#define INTELSPI_SSPREG_PRV_CLOCK_PARAMS 0x400 +#define INTELSPI_SSPREG_RESETS 0x404 +#define INTELSPI_SSPREG_GENERAL 0x408 +#define INTELSPI_SSPREG_SSP_REG 0x40C +#define INTELSPI_SSPREG_SPI_CS_CTRL 0x418 +#define SPI_CS_CTRL_CS_MASK (3) +#define SPI_CS_CTRL_SW_MODE (1 << 0) +#define SPI_CS_CTRL_HW_MODE (1 << 0) +#define SPI_CS_CTRL_CS_HIGH (1 << 1) +#define SPI_CS_CTRL_CS_LOW (0 << 1) + +struct intelspi_softc { + ACPI_HANDLE sc_handle; + device_t sc_dev; + struct mtx sc_mtx; + int sc_mem_rid; + struct resource *sc_mem_res; + int sc_irq_rid; + struct resource *sc_irq_res; + void *sc_irq_ih; + struct spi_command *sc_cmd; + uint32_t sc_len; + uint32_t sc_read; + uint32_t sc_flags; + uint32_t sc_written; +}; + +static int intelspi_probe(device_t dev); +static int intelspi_attach(device_t dev); +static int intelspi_detach(device_t dev); +static void intelspi_intr(void *); + +static int +intelspi_txfifo_full(struct intelspi_softc *sc) +{ + uint32_t sssr; + + INTELSPI_ASSERT_LOCKED(sc); + + sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); + if (sssr & SSSR_TNF) + return (0); + + return (1); +} + +static int +intelspi_rxfifo_empty(struct intelspi_softc *sc) +{ + uint32_t sssr; + + INTELSPI_ASSERT_LOCKED(sc); + + sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); + if (sssr & SSSR_RNE) + return (0); + else + return (1); +} + +static void +intelspi_fill_tx_fifo(struct intelspi_softc *sc) +{ + struct spi_command *cmd; + uint32_t written; + uint8_t *data; + + INTELSPI_ASSERT_LOCKED(sc); + + cmd = sc->sc_cmd; + while (sc->sc_written < sc->sc_len && + !intelspi_txfifo_full(sc)) { + 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; + } + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSDR, data[written]); + } +} + +static void +intelspi_drain_rx_fifo(struct intelspi_softc *sc) +{ + struct spi_command *cmd; + uint32_t read; + uint8_t *data; + + INTELSPI_ASSERT_LOCKED(sc); + + cmd = sc->sc_cmd; + while (sc->sc_read < sc->sc_len && + !intelspi_rxfifo_empty(sc)) { + 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; + } + data[read] = INTELSPI_READ(sc, INTELSPI_SSPREG_SSDR) & 0xff; + } +} + +static int +intelspi_transaction_done(struct intelspi_softc *sc) +{ + int txfifo_empty; + uint32_t sssr; + + INTELSPI_ASSERT_LOCKED(sc); + + if (sc->sc_written != sc->sc_len || + sc->sc_read != sc->sc_len) + return (0); + + sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); + txfifo_empty = ((sssr & SSSR_TFL_MASK) == 0) && + (sssr & SSSR_TNF); + + if (txfifo_empty && !(sssr & SSSR_RNE)) + return (1); + + return (0); +} + +static int +intelspi_transact(struct intelspi_softc *sc) +{ + + INTELSPI_ASSERT_LOCKED(sc); + + /* TX - Fill up the FIFO. */ + intelspi_fill_tx_fifo(sc); + + /* RX - Drain the FIFO. */ + intelspi_drain_rx_fifo(sc); + + /* Check for end of transfer. */ + return intelspi_transaction_done(sc); +} + +static void +intelspi_intr(void *arg) +{ + struct intelspi_softc *sc; + uint32_t reg; + + sc = (struct intelspi_softc *)arg; + + INTELSPI_LOCK(sc); + if ((sc->sc_flags & INTELSPI_BUSY) == 0) { + INTELSPI_UNLOCK(sc); + return; + } + + /* Check if SSP if off */ + reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); + if (reg == 0xffffffffU) { + INTELSPI_UNLOCK(sc); + return; + } + + /* Check for end of transfer. */ + if (intelspi_transact(sc)) { + /* Disable interrupts */ + reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); + reg &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg); + wakeup(sc->sc_dev); + } + + INTELSPI_UNLOCK(sc); +} + +static void +intelspi_init(struct intelspi_softc *sc) +{ + uint32_t reg; + + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0); + + /* Manual CS control */ + reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SPI_CS_CTRL); + reg &= ~(SPI_CS_CTRL_CS_MASK); + reg |= (SPI_CS_CTRL_SW_MODE | SPI_CS_CTRL_CS_HIGH); + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SPI_CS_CTRL, reg); + + /* Set TX/RX FIFO IRQ threshold levels */ + reg = SSCR1_TFT(TX_FIFO_THRESHOLD) | SSCR1_RFT(RX_FIFO_THRESHOLD); + /* + * Set SPI mode. This should be part of transaction or sysctl + */ + reg |= SSCR1_MODE_0; + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg); + + /* + * Parent clock on Minowboard Turbot is 50MHz + * divide it by 5 to set to more or less reasonable + * value. But this should be part of transaction config + * or sysctl + */ + reg = SSCR0_SCR(CLOCK_DIV_10MHZ); + /* Put SSP in SPI mode */ + reg |= SSCR0_FRF_SPI; + /* Data size */ + reg |= SSCR0_DSS(DATA_SIZE_8BITS); + /* Enable SSP */ + reg |= SSCR0_SSE; + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, reg); +} + +static void +intelspi_set_cs(struct intelspi_softc *sc, int level) +{ + uint32_t reg; + + reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SPI_CS_CTRL); + reg &= ~(SPI_CS_CTRL_CS_MASK); + reg |= SPI_CS_CTRL_SW_MODE; + + if (level == CS_HIGH) + reg |= SPI_CS_CTRL_CS_HIGH; + else + reg |= SPI_CS_CTRL_CS_LOW; + + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SPI_CS_CTRL, reg); +} + +static int +intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd) +{ + struct intelspi_softc *sc; + int err; + uint32_t sscr1; + + sc = device_get_softc(dev); + err = 0; + + 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")); + + INTELSPI_LOCK(sc); + + /* If the controller is in use wait until it is available. */ + while (sc->sc_flags & INTELSPI_BUSY) { + err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", 0); + if (err == EINTR) { + INTELSPI_UNLOCK(sc); + return (err); + } + } + + /* Now we have control over SPI controller. */ + sc->sc_flags = INTELSPI_BUSY; + + /* Save a pointer to the SPI command. */ + sc->sc_cmd = cmd; + sc->sc_read = 0; + sc->sc_written = 0; + sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; + + /* Enable CS */ + intelspi_set_cs(sc, CS_LOW); + /* Transfer as much as possible to FIFOs */ + if (!intelspi_transact(sc)) { + /* If FIFO is not large enough - enable interrupts */ + sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); + sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); + + /* and wait for transaction to complete */ + err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", hz * 2); + } + + /* de-asser CS */ + intelspi_set_cs(sc, CS_HIGH); + + /* Clear transaction details */ + sc->sc_cmd = NULL; + sc->sc_read = 0; + sc->sc_written = 0; + sc->sc_len = 0; + + /* Make sure the SPI engine and interrupts are disabled. */ + sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); + sscr1 &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); + INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); + + /* Release the controller and wakeup the next thread waiting for it. */ + sc->sc_flags = 0; + wakeup_one(dev); + INTELSPI_UNLOCK(sc); + + /* + * Check for transfer timeout. The SPI controller doesn't + * return errors. + */ + if (err == EWOULDBLOCK) { + device_printf(sc->sc_dev, "transfer timeout\n"); + err = EIO; + } + + return (err); +} + +static int +intelspi_probe(device_t dev) +{ + static char *gpio_ids[] = { "80860F0E", NULL }; + + if (acpi_disabled("spi") || + ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids) == NULL) + return (ENXIO); + + device_set_desc(dev, "Intel SPI Controller"); + return (0); +} + +static int +intelspi_attach(device_t dev) +{ + struct intelspi_softc *sc; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + sc->sc_handle = acpi_get_handle(dev); + + INTELSPI_LOCK_INIT(sc); + + sc->sc_mem_rid = 0; + sc->sc_mem_res = bus_alloc_resource_any(sc->sc_dev, + SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); + if (sc->sc_mem_res == NULL) { + device_printf(dev, "can't allocate memory resource\n"); + goto error; + } + + sc->sc_irq_rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(sc->sc_dev, + SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(dev, "can't allocate IRQ resource\n"); + goto error; + } + + /* Hook up our interrupt handler. */ + if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, intelspi_intr, sc, &sc->sc_irq_ih)) { + device_printf(dev, "cannot setup the interrupt handler\n"); + goto error; + } + + intelspi_init(sc); + + device_add_child(dev, "spibus", -1); + + return (bus_generic_attach(dev)); + +error: + INTELSPI_LOCK_DESTROY(sc); + + if (sc->sc_mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->sc_mem_rid, sc->sc_mem_res); + + if (sc->sc_irq_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->sc_irq_rid, sc->sc_irq_res); + + return (ENXIO); +} + +static int +intelspi_detach(device_t dev) +{ + struct intelspi_softc *sc; + + sc = device_get_softc(dev); + + INTELSPI_LOCK_DESTROY(sc); + + if (sc->sc_irq_ih) + bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih); + + if (sc->sc_mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->sc_mem_rid, sc->sc_mem_res); + + if (sc->sc_irq_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->sc_irq_rid, sc->sc_irq_res); + + return (0); +} + +static device_method_t intelspi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, intelspi_probe), + DEVMETHOD(device_attach, intelspi_attach), + DEVMETHOD(device_detach, intelspi_detach), + + /* SPI interface */ + DEVMETHOD(spibus_transfer, intelspi_transfer), + + DEVMETHOD_END +}; + +static driver_t intelspi_driver = { + "spi", + intelspi_methods, + sizeof(struct intelspi_softc), +}; + +static devclass_t intelspi_devclass; +DRIVER_MODULE(intelspi, acpi, intelspi_driver, intelspi_devclass, 0, 0); +MODULE_DEPEND(intelspi, acpi, 1, 1, 1); +MODULE_DEPEND(intelspi, spibus, 1, 1, 1); Index: head/sys/modules/Makefile =================================================================== --- head/sys/modules/Makefile +++ head/sys/modules/Makefile @@ -166,6 +166,7 @@ ${_igb} \ ${_iir} \ imgact_binmisc \ + ${_intelspi} \ ${_io} \ ${_ioat} \ ${_ipoib} \ @@ -627,6 +628,7 @@ _ichwd= ichwd _ida= ida _iir= iir +_intelspi= intelspi _ipmi= ipmi _ips= ips _isci= isci Index: head/sys/modules/intelspi/Makefile =================================================================== --- head/sys/modules/intelspi/Makefile +++ head/sys/modules/intelspi/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../dev/intel +KMOD= intelspi +SRCS= spi.c +SRCS+= acpi_if.h device_if.h bus_if.h spibus_if.h + +.include