Author: manu
Date: Sun Jan 19 20:04:44 2020
New Revision: 356895
URL: https://svnweb.freebsd.org/changeset/base/356895

Log:
  zilinx/zy7_qspi: Add a qspi driver for Zynq platforms.
  
  This is a qspi driver for the Xilinx Zynq-7000 chip.
  It could be useful for anyone wanting to boot a system from flash memory
  instead of SD cards.
  
  Submitted by: Thomas Skibo (thomassk...@yahoo.com)
  Differential Revision:        https://reviews.freebsd.org/D14698

Added:
  head/sys/arm/xilinx/zy7_qspi.c   (contents, props changed)
Modified:
  head/sys/arm/conf/GENERIC
  head/sys/arm/conf/ZEDBOARD
  head/sys/arm/xilinx/files.zynq7
  head/sys/dev/flash/mx25lreg.h
  head/sys/dts/arm/zedboard.dts
  head/sys/dts/arm/zybo.dts
  head/sys/dts/arm/zynq-7000.dtsi

Modified: head/sys/arm/conf/GENERIC
==============================================================================
--- head/sys/arm/conf/GENERIC   Sun Jan 19 19:57:14 2020        (r356894)
+++ head/sys/arm/conf/GENERIC   Sun Jan 19 20:04:44 2020        (r356895)
@@ -172,6 +172,7 @@ device              spigen
 device         bcm2835_spi
 device         mv_spi
 device         ti_spi
+device         zy7_qspi                # Xilinx Zynq QSPI controller
 
 # ADC support
 device         ti_adc

Modified: head/sys/arm/conf/ZEDBOARD
==============================================================================
--- head/sys/arm/conf/ZEDBOARD  Sun Jan 19 19:57:14 2020        (r356894)
+++ head/sys/arm/conf/ZEDBOARD  Sun Jan 19 20:04:44 2020        (r356895)
@@ -57,6 +57,10 @@ device               pty
 device         uart
 device         gpio
 
+device         spibus
+device         mx25l
+device         zy7_qspi                # Xilinx Zynq QSPI controller
+
 device         md
 device         mmc                     # mmc/sd bus
 device         mmcsd                   # mmc/sd flash cards

Modified: head/sys/arm/xilinx/files.zynq7
==============================================================================
--- head/sys/arm/xilinx/files.zynq7     Sun Jan 19 19:57:14 2020        
(r356894)
+++ head/sys/arm/xilinx/files.zynq7     Sun Jan 19 20:04:44 2020        
(r356895)
@@ -13,4 +13,5 @@ dev/cadence/if_cgem.c                         optional cgem
 arm/xilinx/zy7_ehci.c                          optional ehci
 arm/xilinx/uart_dev_cdnc.c                     optional uart
 arm/xilinx/zy7_gpio.c                          optional gpio
+arm/xilinx/zy7_qspi.c                          optional zy7_qspi
 

Added: head/sys/arm/xilinx/zy7_qspi.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/sys/arm/xilinx/zy7_qspi.c      Sun Jan 19 20:04:44 2020        
(r356895)
@@ -0,0 +1,763 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2018 Thomas Skibo <thomassk...@yahoo.com>
+ * 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$");
+
+/*
+ * This is a driver for the Quad-SPI Flash Controller in the Xilinx
+ * Zynq-7000 SoC.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+#include <sys/uio.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <machine/stdarg.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 <dev/flash/mx25lreg.h>
+
+#include "spibus_if.h"
+
+static struct ofw_compat_data compat_data[] = {
+       {"xlnx,zy7_qspi",               1},
+       {"xlnx,zynq-qspi-1.0",          1},
+       {NULL,                          0}
+};
+
+struct zy7_qspi_softc {
+       device_t                dev;
+       device_t                child;
+       struct mtx              sc_mtx;
+       struct resource         *mem_res;
+       struct resource         *irq_res;
+       void                    *intrhandle;
+
+       uint32_t                cfg_reg_shadow;
+       uint32_t                lqspi_cfg_shadow;
+       uint32_t                spi_clock;
+       uint32_t                ref_clock;
+       unsigned int            spi_clk_real_freq;
+       unsigned int            rx_overflows;
+       unsigned int            tx_underflows;
+       unsigned int            interrupts;
+       unsigned int            stray_ints;
+       struct spi_command      *cmd;
+       int                     tx_bytes;       /* tx_cmd_sz + tx_data_sz */
+       int                     tx_bytes_sent;
+       int                     rx_bytes;       /* rx_cmd_sz + rx_data_sz */
+       int                     rx_bytes_rcvd;
+       int                     busy;
+       int                     is_dual;
+       int                     is_stacked;
+       int                     is_dio;
+};
+
+#define ZY7_QSPI_DEFAULT_SPI_CLOCK     50000000
+
+#define QSPI_SC_LOCK(sc)               mtx_lock(&(sc)->sc_mtx)
+#define        QSPI_SC_UNLOCK(sc)              mtx_unlock(&(sc)->sc_mtx)
+#define QSPI_SC_LOCK_INIT(sc) \
+       mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->dev), NULL, MTX_DEF)
+#define QSPI_SC_LOCK_DESTROY(sc)       mtx_destroy(&(sc)->sc_mtx)
+#define QSPI_SC_ASSERT_LOCKED(sc)      mtx_assert(&(sc)->sc_mtx, MA_OWNED)
+
+#define RD4(sc, off)           (bus_read_4((sc)->mem_res, (off)))
+#define WR4(sc, off, val)      (bus_write_4((sc)->mem_res, (off), (val)))
+
+/*
+ * QSPI device registers.
+ * Reference: Zynq-7000 All Programmable SoC Technical Reference Manual.
+ * (v1.12.2) July 1, 2018.  Xilinx doc UG585.
+ */
+#define ZY7_QSPI_CONFIG_REG            0x0000
+#define   ZY7_QSPI_CONFIG_IFMODE               (1U << 31)
+#define   ZY7_QSPI_CONFIG_ENDIAN               (1 << 26)
+#define   ZY7_QSPI_CONFIG_HOLDB_DR             (1 << 19)
+#define   ZY7_QSPI_CONFIG_RSVD1                        (1 << 17) /* must be 1 
*/
+#define   ZY7_QSPI_CONFIG_MANSTRT              (1 << 16)
+#define   ZY7_QSPI_CONFIG_MANSTRTEN            (1 << 15)
+#define   ZY7_QSPI_CONFIG_SSFORCE              (1 << 14)
+#define   ZY7_QSPI_CONFIG_PCS                  (1 << 10)
+#define   ZY7_QSPI_CONFIG_REF_CLK              (1 << 8)
+#define   ZY7_QSPI_CONFIG_FIFO_WIDTH_MASK      (3 << 6)
+#define   ZY7_QSPI_CONFIG_FIFO_WIDTH32         (3 << 6)
+#define   ZY7_QSPI_CONFIG_BAUD_RATE_DIV_MASK   (7 << 3)
+#define   ZY7_QSPI_CONFIG_BAUD_RATE_DIV_SHIFT  3
+#define   ZY7_QSPI_CONFIG_BAUD_RATE_DIV(x)     ((x) << 3) /* divide by 2<<x */
+#define   ZY7_QSPI_CONFIG_CLK_PH               (1 << 2)   /* clock phase */
+#define   ZY7_QSPI_CONFIG_CLK_POL              (1 << 1)   /* clock polarity */
+#define   ZY7_QSPI_CONFIG_MODE_SEL             (1 << 0)   /* master enable */
+
+#define ZY7_QSPI_INTR_STAT_REG         0x0004
+#define ZY7_QSPI_INTR_EN_REG           0x0008
+#define ZY7_QSPI_INTR_DIS_REG          0x000c
+#define ZY7_QSPI_INTR_MASK_REG         0x0010
+#define   ZY7_QSPI_INTR_TX_FIFO_UNDERFLOW      (1 << 6)
+#define   ZY7_QSPI_INTR_RX_FIFO_FULL           (1 << 5)
+#define   ZY7_QSPI_INTR_RX_FIFO_NOT_EMPTY      (1 << 4)
+#define   ZY7_QSPI_INTR_TX_FIFO_FULL           (1 << 3)
+#define   ZY7_QSPI_INTR_TX_FIFO_NOT_FULL       (1 << 2)
+#define   ZY7_QSPI_INTR_RX_OVERFLOW            (1 << 0)
+
+#define ZY7_QSPI_EN_REG                        0x0014
+#define   ZY7_SPI_ENABLE                       1
+
+#define ZY7_QSPI_DELAY_REG             0x0018
+#define   ZY7_QSPI_DELAY_NSS_MASK              (0xffU << 24)
+#define   ZY7_QSPI_DELAY_NSS_SHIFT             24
+#define   ZY7_QSPI_DELAY_NSS(x)                        ((x) << 24)
+#define   ZY7_QSPI_DELAY_BTWN_MASK             (0xff << 16)
+#define   ZY7_QSPI_DELAY_BTWN_SHIFT            16
+#define   ZY7_QSPI_DELAY_BTWN(x)               ((x) << 16)
+#define   ZY7_QSPI_DELAY_AFTER_MASK            (0xff << 8)
+#define   ZY7_QSPI_DELAY_AFTER_SHIFT           8
+#define   ZY7_QSPI_DELAY_AFTER(x)              ((x) << 8)
+#define   ZY7_QSPI_DELAY_INIT_MASK             0xff
+#define   ZY7_QSPI_DELAY_INIT_SHIFT            0
+#define   ZY7_QSPI_DELAY_INIT(x)               (x)
+
+#define ZY7_QSPI_TXD0_REG              0x001c
+#define ZY7_QSPI_RX_DATA_REG           0x0020
+
+#define ZY7_QSPI_SLV_IDLE_CT_REG       0x0024
+#define   ZY7_QSPI_SLV_IDLE_CT_MASK            0xff
+
+#define ZY7_QSPI_TX_THRESH_REG         0x0028
+#define ZY7_QSPI_RX_THRESH_REG         0x002c
+
+#define ZY7_QSPI_GPIO_REG              0x0030
+#define   ZY7_QSPI_GPIO_WP_N                   1
+
+#define ZY7_QSPI_LPBK_DLY_ADJ_REG      0x0038
+#define   ZY7_QSPI_LPBK_DLY_ADJ_LPBK_SEL       (1 << 8)
+#define   ZY7_QSPI_LPBK_DLY_ADJ_LPBK_PH                (1 << 7)
+#define   ZY7_QSPI_LPBK_DLY_ADJ_USE_LPBK       (1 << 5)
+#define   ZY7_QSPI_LPBK_DLY_ADJ_DLY1_MASK      (3 << 3)
+#define   ZY7_QSPI_LPBK_DLY_ADJ_DLY1_SHIFT     3
+#define   ZY7_QSPI_LPBK_DLY_ADJ_DLY1(x)                ((x) << 3)
+#define   ZY7_QSPI_LPBK_DLY_ADJ_DLY0_MASK      7
+#define   ZY7_QSPI_LPBK_DLY_ADJ_DLY0_SHIFT     0
+#define   ZY7_QSPI_LPBK_DLY_ADJ_DLY0(x)                (x)
+
+#define ZY7_QSPI_TXD1_REG              0x0080
+#define ZY7_QSPI_TXD2_REG              0x0084
+#define ZY7_QSPI_TXD3_REG              0x0088
+
+#define ZY7_QSPI_LQSPI_CFG_REG         0x00a0
+#define   ZY7_QSPI_LQSPI_CFG_LINEAR            (1U << 31)
+#define   ZY7_QSPI_LQSPI_CFG_TWO_MEM           (1 << 30)
+#define   ZY7_QSPI_LQSPI_CFG_SEP_BUS           (1 << 29)
+#define   ZY7_QSPI_LQSPI_CFG_U_PAGE            (1 << 28)
+#define   ZY7_QSPI_LQSPI_CFG_MODE_EN           (1 << 25)
+#define   ZY7_QSPI_LQSPI_CFG_MODE_ON           (1 << 24)
+#define   ZY7_QSPI_LQSPI_CFG_MODE_BITS_MASK    (0xff << 16)
+#define   ZY7_QSPI_LQSPI_CFG_MODE_BITS_SHIFT   16
+#define   ZY7_QSPI_LQSPI_CFG_MODE_BITS(x)      ((x) << 16)
+#define   ZY7_QSPI_LQSPI_CFG_DUMMY_BYTES_MASK  (7 << 8)
+#define   ZY7_QSPI_LQSPI_CFG_DUMMY_BYTES_SHIFT 8
+#define   ZY7_QSPI_LQSPI_CFG_DUMMY_BYTES(x)    ((x) << 8)
+#define   ZY7_QSPI_LQSPI_CFG_INST_CODE_MASK    0xff
+#define   ZY7_QSPI_LQSPI_CFG_INST_CODE_SHIFT   0
+#define   ZY7_QSPI_LQSPI_CFG_INST_CODE(x)      (x)
+
+#define ZY7_QSPI_LQSPI_STS_REG         0x00a4
+#define   ZY7_QSPI_LQSPI_STS_D_FSM_ERR         (1 << 2)
+#define   ZY7_QSPI_LQSPI_STS_WR_RECVD          (1 << 1)
+
+#define ZY7_QSPI_MOD_ID_REG            0x00fc
+
+static int zy7_qspi_detach(device_t);
+
+/* Fill hardware fifo with command and data bytes. */
+static void
+zy7_qspi_write_fifo(struct zy7_qspi_softc *sc, int nbytes)
+{
+       int n, nvalid;
+       uint32_t data;
+
+       while (nbytes > 0) {
+               nvalid = MIN(4, nbytes);
+               data = 0xffffffff;
+
+               /*
+                * A hardware bug forces us to wait until the tx fifo is
+                * empty before writing partial words.  We'll come back
+                * next tx interrupt.
+                */
+               if (nvalid < 4 && (RD4(sc, ZY7_QSPI_INTR_STAT_REG) &
+                   ZY7_QSPI_INTR_TX_FIFO_NOT_FULL) == 0)
+                       return;
+
+               if (sc->tx_bytes_sent < sc->cmd->tx_cmd_sz) {
+                       /* Writing command. */
+                       n = MIN(nvalid, sc->cmd->tx_cmd_sz -
+                           sc->tx_bytes_sent);
+                       memcpy(&data, (uint8_t *)sc->cmd->tx_cmd +
+                           sc->tx_bytes_sent, n);
+
+                       if (nvalid > n) {
+                               /* Writing start of data. */
+                               memcpy((uint8_t *)&data + n,
+                                   sc->cmd->tx_data, nvalid - n);
+                       }
+               } else
+                       /* Writing data. */
+                       memcpy(&data, (uint8_t *)sc->cmd->tx_data +
+                           (sc->tx_bytes_sent - sc->cmd->tx_cmd_sz), nvalid);
+
+               switch (nvalid) {
+               case 1:
+                       WR4(sc, ZY7_QSPI_TXD1_REG, data);
+                       break;
+               case 2:
+                       WR4(sc, ZY7_QSPI_TXD2_REG, data);
+                       break;
+               case 3:
+                       WR4(sc, ZY7_QSPI_TXD3_REG, data);
+                       break;
+               case 4:
+                       WR4(sc, ZY7_QSPI_TXD0_REG, data);
+                       break;
+               }
+
+               sc->tx_bytes_sent += nvalid;
+               nbytes -= nvalid;
+       }
+}
+
+
+/* Read hardware fifo data into command response and data buffers. */
+static void
+zy7_qspi_read_fifo(struct zy7_qspi_softc *sc)
+{
+       int n, nbytes;
+       uint32_t data;
+
+       do {
+               data = RD4(sc, ZY7_QSPI_RX_DATA_REG);
+               nbytes = MIN(4, sc->rx_bytes - sc->rx_bytes_rcvd);
+
+               /*
+                * Last word in non-word-multiple transfer is packed
+                * non-intuitively.
+                */
+               if (nbytes < 4)
+                       data >>= 8 * (4 - nbytes);
+
+               if (sc->rx_bytes_rcvd < sc->cmd->rx_cmd_sz) {
+                       /* Reading command. */
+                       n = MIN(nbytes, sc->cmd->rx_cmd_sz -
+                           sc->rx_bytes_rcvd);
+                       memcpy((uint8_t *)sc->cmd->rx_cmd + sc->rx_bytes_rcvd,
+                           &data, n);
+                       sc->rx_bytes_rcvd += n;
+                       nbytes -= n;
+                       data >>= 8 * n;
+               }
+
+               if (nbytes > 0) {
+                       /* Reading data. */
+                       memcpy((uint8_t *)sc->cmd->rx_data +
+                           (sc->rx_bytes_rcvd - sc->cmd->rx_cmd_sz),
+                           &data, nbytes);
+                       sc->rx_bytes_rcvd += nbytes;
+               }
+
+       } while (sc->rx_bytes_rcvd < sc->rx_bytes &&
+                (RD4(sc, ZY7_QSPI_INTR_STAT_REG) &
+                 ZY7_QSPI_INTR_RX_FIFO_NOT_EMPTY) != 0);
+}
+
+/* End a transfer early by draining rx fifo and disabling interrupts. */
+static void
+zy7_qspi_abort_transfer(struct zy7_qspi_softc *sc)
+{
+       /* Drain receive fifo. */
+       while ((RD4(sc, ZY7_QSPI_INTR_STAT_REG) &
+               ZY7_QSPI_INTR_RX_FIFO_NOT_EMPTY) != 0)
+               (void)RD4(sc, ZY7_QSPI_RX_DATA_REG);
+
+       /* Shut down interrupts. */
+       WR4(sc, ZY7_QSPI_INTR_DIS_REG,
+           ZY7_QSPI_INTR_RX_OVERFLOW |
+           ZY7_QSPI_INTR_RX_FIFO_NOT_EMPTY |
+           ZY7_QSPI_INTR_TX_FIFO_NOT_FULL);
+}
+
+
+static void
+zy7_qspi_intr(void *arg)
+{
+       struct zy7_qspi_softc *sc = (struct zy7_qspi_softc *)arg;
+       uint32_t istatus;
+
+       QSPI_SC_LOCK(sc);
+
+       sc->interrupts++;
+
+       istatus = RD4(sc, ZY7_QSPI_INTR_STAT_REG);
+
+       /* Stray interrupts can happen if a transfer gets interrupted. */
+       if (!sc->busy) {
+               sc->stray_ints++;
+               QSPI_SC_UNLOCK(sc);
+               return;
+       }
+
+       if ((istatus & ZY7_QSPI_INTR_RX_OVERFLOW) != 0) {
+               device_printf(sc->dev, "rx fifo overflow!\n");
+               sc->rx_overflows++;
+
+               /* Clear status bit. */
+               WR4(sc, ZY7_QSPI_INTR_STAT_REG,
+                   ZY7_QSPI_INTR_RX_OVERFLOW);
+       }
+
+       /* Empty receive fifo before any more transmit data is sent. */
+       if (sc->rx_bytes_rcvd < sc->rx_bytes &&
+           (istatus & ZY7_QSPI_INTR_RX_FIFO_NOT_EMPTY) != 0) {
+               zy7_qspi_read_fifo(sc);
+               if (sc->rx_bytes_rcvd == sc->rx_bytes)
+                       /* Disable receive interrupts. */
+                       WR4(sc, ZY7_QSPI_INTR_DIS_REG,
+                           ZY7_QSPI_INTR_RX_FIFO_NOT_EMPTY |
+                           ZY7_QSPI_INTR_RX_OVERFLOW);
+       }
+
+       /*
+        * Transmit underflows aren't really a bug because a hardware
+        * bug forces us to allow the tx fifo to go empty between full
+        * and partial fifo writes.  Why bother counting?
+        */
+       if ((istatus & ZY7_QSPI_INTR_TX_FIFO_UNDERFLOW) != 0) {
+               sc->tx_underflows++;
+
+               /* Clear status bit. */
+               WR4(sc, ZY7_QSPI_INTR_STAT_REG,
+                   ZY7_QSPI_INTR_TX_FIFO_UNDERFLOW);
+       }
+
+       /* Fill transmit fifo. */
+       if (sc->tx_bytes_sent < sc->tx_bytes &&
+           (istatus & ZY7_QSPI_INTR_TX_FIFO_NOT_FULL) != 0) {
+               zy7_qspi_write_fifo(sc, MIN(240, sc->tx_bytes -
+                       sc->tx_bytes_sent));
+
+               if (sc->tx_bytes_sent == sc->tx_bytes) {
+                       /*
+                        * Disable transmit FIFO interrupt, enable receive
+                        * FIFO interrupt.
+                        */
+                       WR4(sc, ZY7_QSPI_INTR_DIS_REG,
+                           ZY7_QSPI_INTR_TX_FIFO_NOT_FULL);
+                       WR4(sc, ZY7_QSPI_INTR_EN_REG,
+                           ZY7_QSPI_INTR_RX_FIFO_NOT_EMPTY);
+               }
+       }
+
+       /* Finished with transfer? */
+       if (sc->tx_bytes_sent == sc->tx_bytes &&
+           sc->rx_bytes_rcvd == sc->rx_bytes) {
+
+               /* De-assert CS. */
+               sc->cfg_reg_shadow |= ZY7_QSPI_CONFIG_PCS;
+               WR4(sc, ZY7_QSPI_CONFIG_REG, sc->cfg_reg_shadow);
+
+               wakeup(sc->dev);
+       }
+
+       QSPI_SC_UNLOCK(sc);
+}
+
+/* Initialize hardware. */
+static int
+zy7_qspi_init_hw(struct zy7_qspi_softc *sc)
+{
+       uint32_t baud_div;
+
+       /* Configure LQSPI Config register.  Disable linear mode. */
+       sc->lqspi_cfg_shadow = RD4(sc, ZY7_QSPI_LQSPI_CFG_REG);
+       sc->lqspi_cfg_shadow &= ~(ZY7_QSPI_LQSPI_CFG_LINEAR |
+                                 ZY7_QSPI_LQSPI_CFG_TWO_MEM |
+                                 ZY7_QSPI_LQSPI_CFG_SEP_BUS);
+       if (sc->is_dual) {
+               sc->lqspi_cfg_shadow |= ZY7_QSPI_LQSPI_CFG_TWO_MEM;
+               if (sc->is_stacked) {
+                       sc->lqspi_cfg_shadow &=
+                           ~ZY7_QSPI_LQSPI_CFG_INST_CODE_MASK;
+                       sc->lqspi_cfg_shadow |=
+                           ZY7_QSPI_LQSPI_CFG_INST_CODE(sc->is_dio ?
+                               CMD_READ_DUAL_IO : CMD_READ_QUAD_OUTPUT);
+               } else
+                       sc->lqspi_cfg_shadow |= ZY7_QSPI_LQSPI_CFG_SEP_BUS;
+       }
+       WR4(sc, ZY7_QSPI_LQSPI_CFG_REG, sc->lqspi_cfg_shadow);
+
+       /* Find best clock divider. */
+       baud_div = 0;
+       while ((sc->ref_clock >> (baud_div + 1)) > sc->spi_clock &&
+              baud_div < 8)
+               baud_div++;
+       if (baud_div >= 8) {
+               device_printf(sc->dev, "cannot configure clock divider: ref=%d"
+                   " spi=%d.\n", sc->ref_clock, sc->spi_clock);
+               return (EINVAL);
+       }
+       sc->spi_clk_real_freq = sc->ref_clock >> (baud_div + 1);
+
+       /*
+        * If divider is 2 (the max speed), use internal loopback master
+        * clock for read data.  (See section 12.3.1 in ref man.)
+        */
+       if (baud_div == 0)
+               WR4(sc, ZY7_QSPI_LPBK_DLY_ADJ_REG,
+                   ZY7_QSPI_LPBK_DLY_ADJ_USE_LPBK |
+                   ZY7_QSPI_LPBK_DLY_ADJ_DLY1(0) |
+                   ZY7_QSPI_LPBK_DLY_ADJ_DLY0(0));
+       else
+               WR4(sc, ZY7_QSPI_LPBK_DLY_ADJ_REG, 0);
+
+       /* Set up configuration register. */
+       sc->cfg_reg_shadow =
+               ZY7_QSPI_CONFIG_IFMODE |
+               ZY7_QSPI_CONFIG_HOLDB_DR |
+               ZY7_QSPI_CONFIG_RSVD1 |
+               ZY7_QSPI_CONFIG_SSFORCE |
+               ZY7_QSPI_CONFIG_PCS |
+               ZY7_QSPI_CONFIG_FIFO_WIDTH32 |
+               ZY7_QSPI_CONFIG_BAUD_RATE_DIV(baud_div) |
+               ZY7_QSPI_CONFIG_MODE_SEL;
+       WR4(sc, ZY7_QSPI_CONFIG_REG, sc->cfg_reg_shadow);
+
+       /*
+        * Set thresholds.  We must use 1 for tx threshold because there
+        * is no fifo empty flag and we need one to implement a bug
+        * workaround.
+        */
+       WR4(sc, ZY7_QSPI_TX_THRESH_REG, 1);
+       WR4(sc, ZY7_QSPI_RX_THRESH_REG, 1);
+
+       /* Clear and disable all interrupts. */
+       WR4(sc, ZY7_QSPI_INTR_STAT_REG, ~0);
+       WR4(sc, ZY7_QSPI_INTR_DIS_REG, ~0);
+
+       /* Enable SPI. */
+       WR4(sc, ZY7_QSPI_EN_REG, ZY7_SPI_ENABLE);
+
+       return (0);
+}
+
+
+static void
+zy7_qspi_add_sysctls(device_t dev)
+{
+       struct zy7_qspi_softc *sc = device_get_softc(dev);
+       struct sysctl_ctx_list *ctx;
+       struct sysctl_oid_list *child;
+
+       ctx = device_get_sysctl_ctx(dev);
+       child = SYSCTL_CHILDREN(device_get_sysctl_tree(dev));
+
+       SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "spi_clk_real_freq", CTLFLAG_RD,
+           &sc->spi_clk_real_freq, 0, "SPI clock real frequency");
+
+       SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "rx_overflows", CTLFLAG_RD,
+           &sc->rx_overflows, 0, "RX FIFO overflow events");
+
+       SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "tx_underflows", CTLFLAG_RD,
+           &sc->tx_underflows, 0, "TX FIFO underflow events");
+
+       SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "interrupts", CTLFLAG_RD,
+           &sc->interrupts, 0, "interrupt calls");
+
+       SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "stray_ints", CTLFLAG_RD,
+           &sc->stray_ints, 0, "stray interrupts");
+}
+
+
+static int
+zy7_qspi_probe(device_t dev)
+{
+
+       if (!ofw_bus_status_okay(dev))
+               return (ENXIO);
+
+       if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+               return (ENXIO);
+
+       device_set_desc(dev, "Zynq Quad-SPI Flash Controller");
+
+       return (BUS_PROBE_DEFAULT);
+}
+
+
+static int
+zy7_qspi_attach(device_t dev)
+{
+       struct zy7_qspi_softc *sc;
+       int rid, err;
+       phandle_t node;
+       pcell_t cell;
+
+       sc = device_get_softc(dev);
+       sc->dev = dev;
+
+       QSPI_SC_LOCK_INIT(sc);
+
+       /* Get ref-clock, spi-clock, and other properties. */
+       node = ofw_bus_get_node(dev);
+       if (OF_getprop(node, "ref-clock", &cell, sizeof(cell)) > 0)
+               sc->ref_clock = fdt32_to_cpu(cell);
+       else {
+               device_printf(dev, "must have ref-clock property\n");
+               return (ENXIO);
+       }
+       if (OF_getprop(node, "spi-clock", &cell, sizeof(cell)) > 0)
+               sc->spi_clock = fdt32_to_cpu(cell);
+       else
+               sc->spi_clock = ZY7_QSPI_DEFAULT_SPI_CLOCK;
+       if (OF_getprop(node, "is-stacked", &cell, sizeof(cell)) > 0 &&
+           fdt32_to_cpu(cell) != 0) {
+               sc->is_dual = 1;
+               sc->is_stacked = 1;
+       } else if (OF_getprop(node, "is-dual", &cell, sizeof(cell)) > 0 &&
+                  fdt32_to_cpu(cell) != 0)
+               sc->is_dual = 1;
+       if (OF_getprop(node, "is-dio", &cell, sizeof(cell)) > 0 &&
+           fdt32_to_cpu(cell) != 0)
+               sc->is_dio = 1;
+
+       /* Get memory resource. */
+       rid = 0;
+       sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+           RF_ACTIVE);
+       if (sc->mem_res == NULL) {
+               device_printf(dev, "could not allocate memory resources.\n");
+               zy7_qspi_detach(dev);
+               return (ENOMEM);
+       }
+
+       /* Allocate IRQ. */
+       rid = 0;
+       sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+           RF_ACTIVE);
+       if (sc->irq_res == NULL) {
+               device_printf(dev, "could not allocate IRQ resource.\n");
+               zy7_qspi_detach(dev);
+               return (ENOMEM);
+       }
+
+       /* Activate the interrupt. */
+       err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
+           NULL, zy7_qspi_intr, sc, &sc->intrhandle);
+       if (err) {
+               device_printf(dev, "could not setup IRQ.\n");
+               zy7_qspi_detach(dev);
+               return (err);
+       }
+
+       /* Configure the device. */
+       err = zy7_qspi_init_hw(sc);
+       if (err) {
+               zy7_qspi_detach(dev);
+               return (err);
+       }
+
+       sc->child = device_add_child(dev, "spibus", -1);
+
+       zy7_qspi_add_sysctls(dev);
+
+       /* Attach spibus driver as a child later when interrupts work. */
+       config_intrhook_oneshot((ich_func_t)bus_generic_attach, dev);
+
+       return (0);
+}
+
+static int
+zy7_qspi_detach(device_t dev)
+{
+       struct zy7_qspi_softc *sc = device_get_softc(dev);
+
+       if (device_is_attached(dev))
+               bus_generic_detach(dev);
+
+       /* Delete child bus. */
+       if (sc->child)
+               device_delete_child(dev, sc->child);
+
+       /* Disable hardware. */
+       if (sc->mem_res != NULL) {
+               /* Disable SPI. */
+               WR4(sc, ZY7_QSPI_EN_REG, 0);
+
+               /* Clear and disable all interrupts. */
+               WR4(sc, ZY7_QSPI_INTR_STAT_REG, ~0);
+               WR4(sc, ZY7_QSPI_INTR_DIS_REG, ~0);
+       }
+
+       /* Teardown and release interrupt. */
+       if (sc->irq_res != NULL) {
+               if (sc->intrhandle)
+                       bus_teardown_intr(dev, sc->irq_res, sc->intrhandle);
+               bus_release_resource(dev, SYS_RES_IRQ,
+                   rman_get_rid(sc->irq_res), sc->irq_res);
+       }
+
+       /* Release memory resource. */
+       if (sc->mem_res != NULL)
+               bus_release_resource(dev, SYS_RES_MEMORY,
+                   rman_get_rid(sc->mem_res), sc->mem_res);
+
+       QSPI_SC_LOCK_DESTROY(sc);
+
+       return (0);
+}
+
+
+static phandle_t
+zy7_qspi_get_node(device_t bus, device_t dev)
+{
+
+       return (ofw_bus_get_node(bus));
+}
+
+
+static int
+zy7_qspi_transfer(device_t dev, device_t child, struct spi_command *cmd)
+{
+       struct zy7_qspi_softc *sc = device_get_softc(dev);
+       int 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"));
+
+       if (sc->is_dual && cmd->tx_data_sz % 2 != 0) {
+               device_printf(dev, "driver does not support odd byte data "
+                   "transfers in dual mode. (sz=%d)\n", cmd->tx_data_sz);
+               return (EINVAL);
+       }
+
+       QSPI_SC_LOCK(sc);
+
+       /* Wait for controller available. */
+       while (sc->busy != 0) {
+               err = mtx_sleep(dev, &sc->sc_mtx, 0, "zqspi0", 0);
+               if (err) {
+                       QSPI_SC_UNLOCK(sc);
+                       return (err);
+               }
+       }
+
+       /* Start transfer. */
+       sc->busy = 1;
+       sc->cmd = cmd;
+       sc->tx_bytes = sc->cmd->tx_cmd_sz + sc->cmd->tx_data_sz;
+       sc->tx_bytes_sent = 0;
+       sc->rx_bytes = sc->cmd->rx_cmd_sz + sc->cmd->rx_data_sz;
+       sc->rx_bytes_rcvd = 0;
+
+       /* Enable interrupts.  zy7_qspi_intr() will handle transfer. */
+       WR4(sc, ZY7_QSPI_INTR_EN_REG,
+           ZY7_QSPI_INTR_TX_FIFO_NOT_FULL |
+           ZY7_QSPI_INTR_RX_OVERFLOW);
+
+#ifdef SPI_XFER_U_PAGE /* XXX: future support for stacked memories. */
+       if (sc->is_stacked) {
+               if ((cmd->flags & SPI_XFER_U_PAGE) != 0)
+                       sc->lqspi_cfg_shadow |= ZY7_QSPI_LQSPI_CFG_U_PAGE;
+               else
+                       sc->lqspi_cfg_shadow &= ~ZY7_QSPI_LQSPI_CFG_U_PAGE;
+               WR4(sc, ZY7_QSPI_LQSPI_CFG_REG, sc->lqspi_cfg_shadow);
+       }
+#endif
+
+       /* Assert CS. */
+       sc->cfg_reg_shadow &= ~ZY7_QSPI_CONFIG_PCS;
+       WR4(sc, ZY7_QSPI_CONFIG_REG, sc->cfg_reg_shadow);
+
+       /* Wait for completion. */
+       err = mtx_sleep(dev, &sc->sc_mtx, 0, "zqspi1", hz * 2);
+       if (err)
+               zy7_qspi_abort_transfer(sc);
+
+       /* Release controller. */
+       sc->busy = 0;
+       wakeup_one(dev);
+
+       QSPI_SC_UNLOCK(sc);
+
+       return (err);
+}
+
+static device_method_t zy7_qspi_methods[] = {
+       /* Device interface */
+       DEVMETHOD(device_probe,         zy7_qspi_probe),
+       DEVMETHOD(device_attach,        zy7_qspi_attach),
+       DEVMETHOD(device_detach,        zy7_qspi_detach),
+
+       /* SPI interface */
+       DEVMETHOD(spibus_transfer,      zy7_qspi_transfer),
+
+       /* ofw_bus interface */
+       DEVMETHOD(ofw_bus_get_node,     zy7_qspi_get_node),
+
+       DEVMETHOD_END
+};
+
+
+static driver_t zy7_qspi_driver = {
+       "zy7_qspi",
+       zy7_qspi_methods,
+       sizeof(struct zy7_qspi_softc),
+};
+static devclass_t zy7_qspi_devclass;
+
+DRIVER_MODULE(zy7_qspi, simplebus, zy7_qspi_driver, zy7_qspi_devclass, 0, 0);
+DRIVER_MODULE(ofw_spibus, zy7_qspi, ofw_spibus_driver, ofw_spibus_devclass, 0, 
0);
+SIMPLEBUS_PNP_INFO(compat_data);
+MODULE_DEPEND(zy7_qspi, ofw_spibus, 1, 1, 1);

Modified: head/sys/dev/flash/mx25lreg.h
==============================================================================
--- head/sys/dev/flash/mx25lreg.h       Sun Jan 19 19:57:14 2020        
(r356894)
+++ head/sys/dev/flash/mx25lreg.h       Sun Jan 19 20:04:44 2020        
(r356895)
@@ -42,6 +42,8 @@
 #define CMD_WRITE_STATUS       0x01
 #define CMD_READ               0x03
 #define CMD_FAST_READ          0x0B
+#define CMD_READ_DUAL_IO       0xBB
+#define CMD_READ_QUAD_OUTPUT   0x6B
 #define CMD_PAGE_PROGRAM       0x02
 #define CMD_SECTOR_ERASE       0xD8
 #define CMD_BULK_ERASE         0xC7

Modified: head/sys/dts/arm/zedboard.dts
==============================================================================
--- head/sys/dts/arm/zedboard.dts       Sun Jan 19 19:57:14 2020        
(r356894)
+++ head/sys/dts/arm/zedboard.dts       Sun Jan 19 20:04:44 2020        
(r356895)
@@ -60,6 +60,15 @@
        status = "okay";
 };
 
+&qspi0 {
+       status = "okay";
+
+       flash0 {
+              compatible = "st,m25p", "s25fl128";
+              spi-chipselect = <0>;
+       };
+};
+
 &sdhci0 {
        status = "okay";
 };

Modified: head/sys/dts/arm/zybo.dts
==============================================================================
--- head/sys/dts/arm/zybo.dts   Sun Jan 19 19:57:14 2020        (r356894)
+++ head/sys/dts/arm/zybo.dts   Sun Jan 19 20:04:44 2020        (r356895)
@@ -60,6 +60,15 @@
        status = "okay";
 };
 
+&qspi0 {
+       status = "okay";
+
+       flash0 {
+              compatible = "st,m25p", "s25fl128";
+              spi-chipselect = <0>;
+       };
+};
+
 &sdhci0 {
        status = "okay";
 };

Modified: head/sys/dts/arm/zynq-7000.dtsi
==============================================================================
--- head/sys/dts/arm/zynq-7000.dtsi     Sun Jan 19 19:57:14 2020        
(r356894)
+++ head/sys/dts/arm/zynq-7000.dtsi     Sun Jan 19 20:04:44 2020        
(r356895)
@@ -202,7 +202,8 @@
                        reg = <0xd000 0x1000>;
                        interrupts = <0 19 4>;
                        interrupt-parent = <&GIC>;
-                       spi-clock = <50000000>;
+                       ref-clock = <200000000>; // 200 Mhz
+                       spi-clock = <50000000>;  // 50 Mhz
                };
 
                // SDIO controllers
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to