Date: Tue, 27 Dec 2016 22:37:24 +0000 (UTC) From: Oleksandr Tymoshenko <gonzo@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r310645 - in head/sys: conf dev/intel modules modules/intelspi Message-ID: <201612272237.uBRMbO6S036722@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: gonzo Date: Tue Dec 27 22:37:24 2016 New Revision: 310645 URL: https://svnweb.freebsd.org/changeset/base/310645 Log: [intelspi] Add SPI driver for Intel BayTrail SoC Add SPI mode (PIO-only) support for Intel Synchronous Serial Port that can be found in several Intel's products starting from PXA family. Most of implementations have slight differences in behavior and in addresses for registers subset. This driver covers only BayTrail SoC implementation for it's the only hardware I have to test it on. Driver attaches to ACPI bus only and does not have PCI or FDT support for now due to lack of hardware to test it on. "intelspi" is the best name I've managed to come up with. Linux driver name (spi-pxa2xx) does not make sense because current implementation does not support actual PXA2xx SoCs. And as far as I know there is no codename assigned to Intel SSP chip. Reviewed by: br, manu MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D8896 Added: head/sys/dev/intel/ head/sys/dev/intel/spi.c (contents, props changed) head/sys/modules/intelspi/ head/sys/modules/intelspi/Makefile (contents, props changed) Modified: head/sys/conf/files.amd64 head/sys/conf/files.i386 head/sys/modules/Makefile Modified: head/sys/conf/files.amd64 ============================================================================== --- head/sys/conf/files.amd64 Tue Dec 27 22:31:06 2016 (r310644) +++ head/sys/conf/files.amd64 Tue Dec 27 22:37:24 2016 (r310645) @@ -228,6 +228,7 @@ dev/if_ndis/if_ndis.c optional ndis 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 Modified: head/sys/conf/files.i386 ============================================================================== --- head/sys/conf/files.i386 Tue Dec 27 22:31:06 2016 (r310644) +++ head/sys/conf/files.i386 Tue Dec 27 22:37:24 2016 (r310645) @@ -275,6 +275,7 @@ dev/if_ndis/if_ndis.c optional ndis 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 Added: head/sys/dev/intel/spi.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/intel/spi.c Tue Dec 27 22:37:24 2016 (r310645) @@ -0,0 +1,533 @@ +/*- + * Copyright (c) 2016 Oleksandr Tymoshenko <gonzo@FreeBSD.org> + * 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 "opt_acpi.h" + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/proc.h> +#include <sys/rman.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/spibus/spi.h> +#include <dev/spibus/spibusvar.h> + +#include <contrib/dev/acpica/include/acpi.h> +#include <contrib/dev/acpica/include/accommon.h> + +#include <dev/acpica/acpivar.h> + +#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); Modified: head/sys/modules/Makefile ============================================================================== --- head/sys/modules/Makefile Tue Dec 27 22:31:06 2016 (r310644) +++ head/sys/modules/Makefile Tue Dec 27 22:37:24 2016 (r310645) @@ -166,6 +166,7 @@ SUBDIR= \ ${_igb} \ ${_iir} \ imgact_binmisc \ + ${_intelspi} \ ${_io} \ ${_ioat} \ ${_ipoib} \ @@ -627,6 +628,7 @@ _hyperv= hyperv _ichwd= ichwd _ida= ida _iir= iir +_intelspi= intelspi _ipmi= ipmi _ips= ips _isci= isci Added: head/sys/modules/intelspi/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/modules/intelspi/Makefile Tue Dec 27 22:37:24 2016 (r310645) @@ -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 <bsd.kmod.mk>
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201612272237.uBRMbO6S036722>