From owner-svn-src-head@freebsd.org Mon Feb 26 02:28:34 2018 Return-Path: Delivered-To: svn-src-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 19C00F1E211; Mon, 26 Feb 2018 02:28:34 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id B277D7CA7D; Mon, 26 Feb 2018 02:28:33 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id A4CA4155A6; Mon, 26 Feb 2018 02:28:33 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id w1Q2SXS5096380; Mon, 26 Feb 2018 02:28:33 GMT (envelope-from ian@FreeBSD.org) Received: (from ian@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id w1Q2SW0l096371; Mon, 26 Feb 2018 02:28:32 GMT (envelope-from ian@FreeBSD.org) Message-Id: <201802260228.w1Q2SW0l096371@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: ian set sender to ian@FreeBSD.org using -f From: Ian Lepore Date: Mon, 26 Feb 2018 02:28:32 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r329999 - in head/sys: arm/freescale/imx modules/imx modules/imx/imx_spi X-SVN-Group: head X-SVN-Commit-Author: ian X-SVN-Commit-Paths: in head/sys: arm/freescale/imx modules/imx modules/imx/imx_spi X-SVN-Commit-Revision: 329999 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.25 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 26 Feb 2018 02:28:34 -0000 Author: ian Date: Mon Feb 26 02:28:32 2018 New Revision: 329999 URL: https://svnweb.freebsd.org/changeset/base/329999 Log: Add a SPI driver for imx5 and imx6. It can be compiled into the kernel with "device imx_spi" or loaded as a module, which is also named "imx_spi". Added: head/sys/arm/freescale/imx/imx_spi.c (contents, props changed) head/sys/modules/imx/imx_spi/ head/sys/modules/imx/imx_spi/Makefile (contents, props changed) Modified: head/sys/arm/freescale/imx/files.imx5 head/sys/arm/freescale/imx/files.imx6 head/sys/arm/freescale/imx/imx51_ccm.c head/sys/arm/freescale/imx/imx6_ccm.c head/sys/arm/freescale/imx/imx6_ccmreg.h head/sys/arm/freescale/imx/imx_ccmvar.h head/sys/modules/imx/Makefile Modified: head/sys/arm/freescale/imx/files.imx5 ============================================================================== --- head/sys/arm/freescale/imx/files.imx5 Mon Feb 26 02:12:09 2018 (r329998) +++ head/sys/arm/freescale/imx/files.imx5 Mon Feb 26 02:28:32 2018 (r329999) @@ -52,3 +52,6 @@ dev/vt/hw/fb/vt_early_fb.c optional vt # Fast Ethernet Controller dev/ffec/if_ffec.c optional ffec +# SPI +arm/freescale/imx/imx_spi.c optional imx_spi + Modified: head/sys/arm/freescale/imx/files.imx6 ============================================================================== --- head/sys/arm/freescale/imx/files.imx6 Mon Feb 26 02:12:09 2018 (r329998) +++ head/sys/arm/freescale/imx/files.imx6 Mon Feb 26 02:28:32 2018 (r329999) @@ -22,6 +22,7 @@ arm/freescale/imx/imx_machdep.c standard arm/freescale/imx/imx_gpt.c optional imx_gpt arm/freescale/imx/imx_gpio.c optional gpio arm/freescale/imx/imx_i2c.c optional fsliic +arm/freescale/imx/imx_spi.c optional imx_spi arm/freescale/imx/imx6_sdma.c optional fslsdma arm/freescale/imx/imx6_audmux.c optional sound arm/freescale/imx/imx6_ssi.c optional sound Modified: head/sys/arm/freescale/imx/imx51_ccm.c ============================================================================== --- head/sys/arm/freescale/imx/imx51_ccm.c Mon Feb 26 02:12:09 2018 (r329998) +++ head/sys/arm/freescale/imx/imx51_ccm.c Mon Feb 26 02:28:32 2018 (r329999) @@ -621,6 +621,13 @@ imx_ccm_usbphy_enable(device_t dev) } uint32_t +imx_ccm_ecspi_hz(void) +{ + + return (imx51_get_clock(IMX51CLK_CSPI_CLK_ROOT)); +} + +uint32_t imx_ccm_ipg_hz(void) { @@ -654,3 +661,4 @@ imx_ccm_ahb_hz(void) return (imx51_get_clock(IMX51CLK_AHB_CLK_ROOT)); } + Modified: head/sys/arm/freescale/imx/imx6_ccm.c ============================================================================== --- head/sys/arm/freescale/imx/imx6_ccm.c Mon Feb 26 02:12:09 2018 (r329998) +++ head/sys/arm/freescale/imx/imx6_ccm.c Mon Feb 26 02:28:32 2018 (r329999) @@ -96,8 +96,9 @@ ccm_init_gates(struct ccm_softc *sc) reg = CCGR0_AIPS_TZ1 | CCGR0_AIPS_TZ2 | CCGR0_ABPHDMA; WR4(sc, CCM_CCGR0, reg); - /* enet, epit, gpt */ - reg = CCGR1_ENET | CCGR1_EPIT1 | CCGR1_GPT; + /* enet, epit, gpt, spi */ + reg = CCGR1_ENET | CCGR1_EPIT1 | CCGR1_GPT | CCGR1_ECSPI1 | + CCGR1_ECSPI2 | CCGR1_ECSPI3 | CCGR1_ECSPI4 | CCGR1_ECSPI5; WR4(sc, CCM_CCGR1, reg); /* ipmux & ipsync (bridges), iomux, i2c */ @@ -349,6 +350,13 @@ imx6_ccm_sata_enable(void) WR4(ccm_sc, CCM_ANALOG_PLL_ENET, v); return 0; +} + +uint32_t +imx_ccm_ecspi_hz(void) +{ + + return (60000000); } uint32_t Modified: head/sys/arm/freescale/imx/imx6_ccmreg.h ============================================================================== --- head/sys/arm/freescale/imx/imx6_ccmreg.h Mon Feb 26 02:12:09 2018 (r329998) +++ head/sys/arm/freescale/imx/imx6_ccmreg.h Mon Feb 26 02:28:32 2018 (r329999) @@ -80,9 +80,15 @@ #define CCGR0_AIPS_TZ2 (0x3 << 2) #define CCGR0_ABPHDMA (0x3 << 4) #define CCM_CCGR1 0x06C +#define CCGR1_ECSPI1 (0x3 << 0) +#define CCGR1_ECSPI2 (0x3 << 2) +#define CCGR1_ECSPI3 (0x3 << 4) +#define CCGR1_ECSPI4 (0x3 << 6) +#define CCGR1_ECSPI5 (0x3 << 8) #define CCGR1_ENET (0x3 << 10) #define CCGR1_EPIT1 (0x3 << 12) #define CCGR1_EPIT2 (0x3 << 14) +#define CCGR1_ESAI (0x3 << 16) #define CCGR1_GPT (0x3 << 20) #define CCGR1_GPT_SERIAL (0x3 << 22) #define CCM_CCGR2 0x070 Modified: head/sys/arm/freescale/imx/imx_ccmvar.h ============================================================================== --- head/sys/arm/freescale/imx/imx_ccmvar.h Mon Feb 26 02:12:09 2018 (r329998) +++ head/sys/arm/freescale/imx/imx_ccmvar.h Mon Feb 26 02:28:32 2018 (r329999) @@ -43,6 +43,7 @@ * board setup code has to handle those things. */ +uint32_t imx_ccm_ecspi_hz(void); uint32_t imx_ccm_ipg_hz(void); uint32_t imx_ccm_perclk_hz(void); uint32_t imx_ccm_sdhci_hz(void); Added: head/sys/arm/freescale/imx/imx_spi.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/freescale/imx/imx_spi.c Mon Feb 26 02:28:32 2018 (r329999) @@ -0,0 +1,604 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2018 Ian Lepore + * 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$"); + +/* + * Driver for imx Enhanced Configurable SPI; master-mode only. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "spibus_if.h" + +#define ECSPI_RXDATA 0x00 +#define ECSPI_TXDATA 0x04 +#define ECSPI_CTLREG 0x08 +#define CTLREG_BLEN_SHIFT 20 +#define CTLREG_BLEN_MASK 0x0fff +#define CTLREG_CSEL_SHIFT 18 +#define CTLREG_CSEL_MASK 0x03 +#define CTLREG_DRCTL_SHIFT 16 +#define CTLREG_DRCTL_MASK 0x03 +#define CTLREG_PREDIV_SHIFT 12 +#define CTLREG_PREDIV_MASK 0x0f +#define CTLREG_POSTDIV_SHIFT 8 +#define CTLREG_POSTDIV_MASK 0x0f +#define CTLREG_CMODE_SHIFT 4 +#define CTLREG_CMODE_MASK 0x0f +#define CTLREG_CMODES_MASTER (CTLREG_CMODE_MASK << CTLREG_CMODE_SHIFT) +#define CTLREG_SMC (1u << 3) +#define CTLREG_XCH (1u << 2) +#define CTLREG_HT (1u << 1) +#define CTLREG_EN (1u << 0) +#define ECSPI_CFGREG 0x0c +#define CFGREG_HTLEN_SHIFT 24 +#define CFGREG_SCLKCTL_SHIFT 20 +#define CFGREG_DATACTL_SHIFT 16 +#define CFGREG_SSPOL_SHIFT 12 +#define CFGREG_SSCTL_SHIFT 8 +#define CFGREG_SCLKPOL_SHIFT 4 +#define CFGREG_SCLKPHA_SHIFT 0 +#define CFGREG_MASK 0x0f /* all CFGREG fields are 4 bits */ +#define ECSPI_INTREG 0x10 +#define INTREG_TCEN (1u << 7) +#define INTREG_ROEN (1u << 6) +#define INTREG_RFEN (1u << 5) +#define INTREG_RDREN (1u << 4) +#define INTREG_RREN (1u << 3) +#define INTREG_TFEN (1u << 2) +#define INTREG_TDREN (1u << 1) +#define INTREG_TEEN (1u << 0) +#define ECSPI_DMAREG 0x14 +#define DMA_RX_THRESH_SHIFT 16 +#define DMA_RX_THRESH_MASK 0x3f +#define DMA_TX_THRESH_SHIFT 0 +#define DMA_TX_THRESH_MASK 0x3f +#define ECSPI_STATREG 0x18 +#define SREG_TC (1u << 7) +#define SREG_RO (1u << 6) +#define SREG_RF (1u << 5) +#define SREG_RDR (1u << 4) +#define SREG_RR (1u << 3) +#define SREG_TF (1u << 2) +#define SREG_TDR (1u << 1) +#define SREG_TE (1u << 0) +#define ECSPI_PERIODREG 0x1c +#define ECSPI_TESTREG 0x20 + +#define CS_MAX 4 /* Max number of chip selects. */ +#define CS_MASK 0x03 /* Mask flag bits out of chipsel. */ + +#define FIFO_SIZE 64 +#define FIFO_RXTHRESH 32 +#define FIFO_TXTHRESH 32 + +struct spi_softc { + device_t dev; + device_t spibus; + struct mtx mtx; + struct resource *memres; + struct resource *intres; + void *inthandle; + gpio_pin_t cspins[CS_MAX]; + u_int debug; + u_int basefreq; + uint32_t ctlreg; + uint32_t intreg; + uint32_t fifocnt; + uint8_t *rxbuf; + uint32_t rxidx; + uint32_t rxlen; + uint8_t *txbuf; + uint32_t txidx; + uint32_t txlen; +}; + +static struct ofw_compat_data compat_data[] = { + {"fsl,imx51-ecspi", true}, + {"fsl,imx53-ecspi", true}, + {"fsl,imx6dl-ecspi", true}, + {"fsl,imx6q-ecspi", true}, + {"fsl,imx6sx-ecspi", true}, + {"fsl,imx6ul-ecspi", true}, + {NULL, false} +}; + +static inline uint32_t +RD4(struct spi_softc *sc, bus_size_t offset) +{ + + return (bus_read_4(sc->memres, offset)); +} + +static inline void +WR4(struct spi_softc *sc, bus_size_t offset, uint32_t value) +{ + + bus_write_4(sc->memres, offset, value); +} + +static u_int +spi_calc_clockdiv(struct spi_softc *sc, u_int busfreq) +{ + u_int post, pre; + + /* Returning 0 effectively sets both dividers to 1. */ + if (sc->basefreq <= busfreq) + return (0); + + /* + * Brute-force this; all real-world bus speeds are going to be found on + * the 1st or 2nd time through this loop. + */ + for (post = 0; post < 16; ++post) { + pre = ((sc->basefreq >> post) / busfreq) - 1; + if (pre < 16) + break; + } + if (post == 16) { + /* The lowest we can go is ~115 Hz. */ + pre = 15; + post = 15; + } + + if (sc->debug >= 2) { + device_printf(sc->dev, + "base %u bus %u; pre %u, post %u; actual busfreq %u\n", + sc->basefreq, busfreq, pre, post, + (sc->basefreq / (pre + 1)) / (1 << post)); + } + + return (pre << CTLREG_PREDIV_SHIFT) | (post << CTLREG_POSTDIV_SHIFT); +} + +static void +spi_set_chipsel(struct spi_softc *sc, u_int cs, bool active) +{ + bool pinactive; + + /* + * This is kinda crazy... the gpio pins for chipsel are defined as + * active-high in the dts, but are supposed to be treated as active-low + * by this driver. So to turn on chipsel we have to invert the value + * passed to gpio_pin_set_active(). Then, to make it more fun, any + * slave can say its chipsel is active-high, so if that option is + * on, we have to invert the value again. + */ + pinactive = !active ^ (bool)(cs & SPIBUS_CS_HIGH); + + if (sc->debug >= 2) { + device_printf(sc->dev, "chipsel %u changed to %u\n", + (cs & ~SPIBUS_CS_HIGH), pinactive); + } + + /* + * Change the pin, then do a dummy read of its current state to ensure + * that the state change reaches the hardware before proceeding. + */ + gpio_pin_set_active(sc->cspins[cs & ~SPIBUS_CS_HIGH], pinactive); + gpio_pin_is_active(sc->cspins[cs & ~SPIBUS_CS_HIGH], &pinactive); +} + +static void +spi_hw_setup(struct spi_softc *sc, u_int cs, u_int mode, u_int freq) +{ + uint32_t reg; + + /* + * Set up control register, and write it first to bring the device out + * of reset. + */ + sc->ctlreg = CTLREG_EN | CTLREG_CMODES_MASTER | CTLREG_SMC; + sc->ctlreg |= spi_calc_clockdiv(sc, freq); + sc->ctlreg |= 7 << CTLREG_BLEN_SHIFT; /* XXX byte at a time */ + WR4(sc, ECSPI_CTLREG, sc->ctlreg); + + /* + * Set up the config register. Note that we do all transfers with the + * SPI hardware's chip-select set to zero. The actual chip select is + * handled with a gpio pin. + */ + reg = 0; + if (cs & SPIBUS_CS_HIGH) + reg |= 1u << CFGREG_SSPOL_SHIFT; + if (mode & SPIBUS_MODE_CPHA) + reg |= 1u << CFGREG_SCLKPHA_SHIFT; + if (mode & SPIBUS_MODE_CPOL) { + reg |= 1u << CFGREG_SCLKPOL_SHIFT; + reg |= 1u << CFGREG_SCLKCTL_SHIFT; + } + WR4(sc, ECSPI_CFGREG, reg); + + /* + * Set up the rx/tx FIFO interrupt thresholds. + */ + reg = (FIFO_RXTHRESH << DMA_RX_THRESH_SHIFT); + reg |= (FIFO_TXTHRESH << DMA_TX_THRESH_SHIFT); + WR4(sc, ECSPI_DMAREG, reg); + + /* + * Do a dummy read, to make sure the preceding writes reach the spi + * hardware before we assert any gpio chip select. + */ + (void)RD4(sc, ECSPI_CFGREG); +} + +static void +spi_empty_rxfifo(struct spi_softc *sc) +{ + + while (sc->rxidx < sc->rxlen && (RD4(sc, ECSPI_STATREG) & SREG_RR)) { + sc->rxbuf[sc->rxidx++] = (uint8_t)RD4(sc, ECSPI_RXDATA); + --sc->fifocnt; + } +} + +static void +spi_fill_txfifo(struct spi_softc *sc) +{ + + while (sc->txidx < sc->txlen && sc->fifocnt < FIFO_SIZE) { + WR4(sc, ECSPI_TXDATA, sc->txbuf[sc->txidx++]); + ++sc->fifocnt; + } + + /* + * If we're out of data, disable tx data ready (threshold) interrupts, + * and enable tx fifo empty interrupts. + */ + if (sc->txidx == sc->txlen) + sc->intreg = (sc->intreg & ~INTREG_TDREN) | INTREG_TEEN; +} + +static void +spi_intr(void *arg) +{ + struct spi_softc *sc = arg; + uint32_t intreg, status; + + mtx_lock(&sc->mtx); + + sc = arg; + intreg = sc->intreg; + status = RD4(sc, ECSPI_STATREG); + WR4(sc, ECSPI_STATREG, status); /* Clear w1c bits. */ + + /* + * If we get an overflow error, just signal that the transfer is done + * and wakeup the waiting thread, which will see that txidx != txlen and + * return an IO error to the caller. + */ + if (__predict_false(status & SREG_RO)) { + if (sc->debug || bootverbose) { + device_printf(sc->dev, "rxoverflow rxidx %u txidx %u\n", + sc->rxidx, sc->txidx); + } + sc->intreg = 0; + wakeup(sc); + mtx_unlock(&sc->mtx); + return; + } + + if (status & SREG_RR) + spi_empty_rxfifo(sc); + + if (status & SREG_TDR) + spi_fill_txfifo(sc); + + /* + * If we're out of bytes to send... + * - If Transfer Complete is set (shift register is empty) and we've + * received everything we expect, we're all done. + * - Else if Tx Fifo Empty is set, we need to stop waiting for that and + * switch to waiting for Transfer Complete (wait for shift register + * to empty out), and also for Receive Ready (last of incoming data). + */ + if (sc->txidx == sc->txlen) { + if ((status & SREG_TC) && sc->fifocnt == 0) { + sc->intreg = 0; + wakeup(sc); + } else if (status & SREG_TE) { + sc->intreg &= ~(sc->intreg & ~INTREG_TEEN); + sc->intreg |= INTREG_TCEN | INTREG_RREN; + } + } + + /* + * If interrupt flags changed, write the new flags to the hardware and + * do a dummy readback to ensure the changes reach the hardware before + * we exit the isr. + */ + if (sc->intreg != intreg) { + WR4(sc, ECSPI_INTREG, sc->intreg); + (void)RD4(sc, ECSPI_INTREG); + } + + if (sc->debug >= 3) { + device_printf(sc->dev, + "spi_intr, sreg 0x%08x intreg was 0x%08x now 0x%08x\n", + status, intreg, sc->intreg); + } + + mtx_unlock(&sc->mtx); +} + +static int +spi_xfer_buf(struct spi_softc *sc, void *rxbuf, void *txbuf, uint32_t len) +{ + int err; + + if (sc->debug >= 1) { + device_printf(sc->dev, + "spi_xfer_buf, rxbuf %p txbuf %p len %u\n", + rxbuf, txbuf, len); + } + + if (len == 0) + return (0); + + sc->rxbuf = rxbuf; + sc->rxlen = len; + sc->rxidx = 0; + sc->txbuf = txbuf; + sc->txlen = len; + sc->txidx = 0; + sc->intreg = INTREG_RDREN | INTREG_TDREN; + spi_fill_txfifo(sc); + + /* Enable interrupts last; spi_fill_txfifo() can change sc->intreg */ + WR4(sc, ECSPI_INTREG, sc->intreg); + + err = 0; + while (err == 0 && sc->intreg != 0) + err = msleep(sc, &sc->mtx, 0, "imxspi", 10 * hz); + + if (sc->rxidx != sc->rxlen || sc->txidx != sc->txlen) + err = EIO; + + return (err); +} + +static int +spi_transfer(device_t dev, device_t child, struct spi_command *cmd) +{ + struct spi_softc *sc = device_get_softc(dev); + uint32_t cs, mode, clock; + int err; + + spibus_get_cs(child, &cs); + spibus_get_clock(child, &clock); + spibus_get_mode(child, &mode); + + if (cs > CS_MAX || sc->cspins[cs] == NULL) { + if (sc->debug || bootverbose) + device_printf(sc->dev, "Invalid chip select %u\n", cs); + return (EINVAL); + } + + mtx_lock(&sc->mtx); + + if (sc->debug >= 1) { + device_printf(sc->dev, + "spi_transfer, cs 0x%x clock %u mode %u\n", + cs, clock, mode); + } + + /* Set up the hardware and select the device. */ + spi_hw_setup(sc, cs, mode, clock); + spi_set_chipsel(sc, cs, true); + + /* Transfer command then data bytes. */ + err = 0; + if (cmd->tx_cmd_sz > 0) + err = spi_xfer_buf(sc, cmd->rx_cmd, cmd->tx_cmd, + cmd->tx_cmd_sz); + if (cmd->tx_data_sz > 0 && err == 0) + err = spi_xfer_buf(sc, cmd->rx_data, cmd->tx_data, + cmd->tx_data_sz); + + /* Deselect the device, turn off (and reset) hardware. */ + spi_set_chipsel(sc, cs, false); + WR4(sc, ECSPI_CTLREG, 0); + + mtx_unlock(&sc->mtx); + + return (err); +} + +static phandle_t +spi_get_node(device_t bus, device_t dev) +{ + + /* + * Share our controller node with our spibus child; it instantiates + * devices by walking the children contained within our node. + */ + return ofw_bus_get_node(bus); +} + +static int +spi_detach(device_t dev) +{ + struct spi_softc *sc = device_get_softc(dev); + int idx; + + mtx_lock(&sc->mtx); + + bus_generic_detach(sc->dev); + if (sc->spibus != NULL) + device_delete_child(dev, sc->spibus); + + for (idx = 0; idx < nitems(sc->cspins); ++idx) { + if (sc->cspins[idx] != NULL) + gpio_pin_release(sc->cspins[idx]); + } + + if (sc->inthandle != NULL) + bus_teardown_intr(sc->dev, sc->intres, sc->inthandle); + if (sc->intres != NULL) + bus_release_resource(sc->dev, SYS_RES_IRQ, 0, sc->intres); + if (sc->memres != NULL) + bus_release_resource(sc->dev, SYS_RES_MEMORY, 0, sc->memres); + + mtx_unlock(&sc->mtx); + mtx_destroy(&sc->mtx); + + return (0); +} + +static int +spi_attach(device_t dev) +{ + struct spi_softc *sc = device_get_softc(dev); + phandle_t node; + int err, idx, rid; + + sc->dev = dev; + sc->basefreq = imx_ccm_ecspi_hz(); + + mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + /* Set up debug-enable sysctl. */ + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), + OID_AUTO, "debug", CTLFLAG_RWTUN, &sc->debug, 0, + "Enable debug, higher values = more info"); + + /* Allocate mmio register access resources. */ + rid = 0; + sc->memres = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->memres == NULL) { + device_printf(sc->dev, "could not allocate registers\n"); + spi_detach(sc->dev); + return (ENXIO); + } + + /* Allocate interrupt resources and set up handler. */ + rid = 0; + sc->intres = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (sc->intres == NULL) { + device_printf(sc->dev, "could not allocate interrupt\n"); + device_detach(sc->dev); + return (ENXIO); + } + err = bus_setup_intr(sc->dev, sc->intres, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, spi_intr, sc, &sc->inthandle); + if (err != 0) { + device_printf(sc->dev, "could not setup interrupt handler"); + device_detach(sc->dev); + return (ENXIO); + } + + /* Allocate gpio pins for configured chip selects. */ + node = ofw_bus_get_node(sc->dev); + for (err = 0, idx = 0; err == 0 && idx < nitems(sc->cspins); ++idx) { + err = gpio_pin_get_by_ofw_propidx(sc->dev, node, "cs-gpios", + idx, &sc->cspins[idx]); + if (err == 0) { + gpio_pin_setflags(sc->cspins[idx], GPIO_PIN_OUTPUT); + } else if (sc->debug >= 2) { + device_printf(sc->dev, + "cannot configure gpio for chip select %u\n", idx); + } + } + + /* + * Hardware init: put all channels into Master mode, turn off the enable + * bit (gates off clocks); we only enable the hardware while xfers run. + */ + WR4(sc, ECSPI_CTLREG, CTLREG_CMODES_MASTER); + + /* Attach the bus driver. */ + sc->spibus = device_add_child(dev, "spibus", -1); + return (bus_generic_attach(sc->dev)); +} + +static int +spi_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "i.MX ECSPI Master"); + return (BUS_PROBE_DEFAULT); +} + +static device_method_t spi_methods[] = { + DEVMETHOD(device_probe, spi_probe), + DEVMETHOD(device_attach, spi_attach), + DEVMETHOD(device_detach, spi_detach), + + /* spibus_if */ + DEVMETHOD(spibus_transfer, spi_transfer), + + /* ofw_bus_if */ + DEVMETHOD(ofw_bus_get_node, spi_get_node), + + DEVMETHOD_END +}; + +static driver_t spi_driver = { + "imx_spi", + spi_methods, + sizeof(struct spi_softc), +}; + +static devclass_t spi_devclass; + +DRIVER_MODULE(imx_spi, simplebus, spi_driver, spi_devclass, 0, 0); +DRIVER_MODULE(ofw_spibus, imx_spi, ofw_spibus_driver, ofw_spibus_devclass, 0, 0); +MODULE_DEPEND(imx_spi, ofw_spibus, 1, 1, 1); Modified: head/sys/modules/imx/Makefile ============================================================================== --- head/sys/modules/imx/Makefile Mon Feb 26 02:12:09 2018 (r329998) +++ head/sys/modules/imx/Makefile Mon Feb 26 02:28:32 2018 (r329999) @@ -4,5 +4,6 @@ SUBDIR = \ ../ffec \ imx_i2c \ + imx_spi \ .include Added: head/sys/modules/imx/imx_spi/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/modules/imx/imx_spi/Makefile Mon Feb 26 02:28:32 2018 (r329999) @@ -0,0 +1,16 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/arm/freescale/imx + +KMOD= imx_spi +SRCS= imx_spi.c + +# Generated files... +SRCS+= \ + bus_if.h \ + device_if.h \ + ofw_bus_if.h \ + opt_platform.h \ + spibus_if.h \ + +.include