You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2021/07/01 06:44:59 UTC
[incubator-nuttx] 02/04: kinetis:SPI use eDMA
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git
commit e5a1b2e7970b48768cf078e3a7946676601f49ca
Author: David Sidrane <Da...@NscDg.com>
AuthorDate: Fri Jun 25 04:19:34 2021 -0700
kinetis:SPI use eDMA
Kinetis:SPI only allocate DMA once
---
arch/arm/src/kinetis/Kconfig | 55 ++++-
arch/arm/src/kinetis/kinetis_spi.c | 464 ++++++++++++++++++++++++++++++++++++-
2 files changed, 506 insertions(+), 13 deletions(-)
diff --git a/arch/arm/src/kinetis/Kconfig b/arch/arm/src/kinetis/Kconfig
index 78a868f..cbda473 100644
--- a/arch/arm/src/kinetis/Kconfig
+++ b/arch/arm/src/kinetis/Kconfig
@@ -330,6 +330,11 @@ config KINETIS_HAVE_DMA
bool
default n
+config KINETIS_SPI
+ bool
+ default n
+ select SPI
+
config KINETIS_HAVE_I2C1
bool
default n
@@ -536,14 +541,14 @@ config KINETIS_FLEXCAN1
config KINETIS_SPI0
bool "SPI0"
default n
- select SPI
+ select KINETIS_SPI
---help---
Support SPI0
config KINETIS_SPI1
bool "SPI1"
default n
- select SPI
+ select KINETIS_SPI
depends on KINETIS_HAVE_SPI1
---help---
Support SPI1
@@ -551,7 +556,7 @@ config KINETIS_SPI1
config KINETIS_SPI2
bool "SPI2"
default n
- select SPI
+ select KINETIS_SPI
depends on KINETIS_HAVE_SPI2
---help---
Support SPI2
@@ -752,7 +757,7 @@ config KINETIS_FTFL
Support FLASH
config KINETIS_EDMA
- bool "DMA"
+ bool "eDMA"
default n
depends on KINETIS_HAVE_DMA
select ARCH_DMA
@@ -1204,6 +1209,48 @@ config KINETIS_EDMA_EDBG
endmenu # eDMA Global Configuration
+menu "Kinetis SPI Configuration"
+ depends on KINETIS_SPI && KINETIS_EDMA
+
+config KINETIS_SPI_DMA
+ bool "SPI DMA"
+ depends on KINETIS_EDMA
+ default n
+ ---help---
+ Use DMA to improve SPI transfer performance.
+
+config KINETIS_SPI_DMATHRESHOLD
+ int "SPI DMA threshold"
+ default 4
+ depends on KINETIS_SPI_DMA
+ ---help---
+ When SPI DMA is enabled, small DMA transfers will still be performed
+ by polling logic. But we need a threshold value to determine what
+ is small.
+
+config KINETIS_SPI0_DMA
+ bool "SPI0 DMA"
+ default n
+ depends on KINETIS_SPI0 && KINETIS_SPI_DMA
+ ---help---
+ Use DMA to improve SPI0 transfer performance.
+
+config KINETIS_SPI1_DMA
+ bool "SPI1 DMA"
+ default n
+ depends on KINETIS_SPI1 && KINETIS_SPI_DMA
+ ---help---
+ Use DMA to improve SPI1 transfer performance.
+
+config KINETIS_SPI2_DMA
+ bool "SPI2 DMA"
+ default n
+ depends on KINETIS_SPI2 && KINETIS_SPI_DMA
+ ---help---
+ Use DMA to improve SPI2 transfer performance.
+
+endmenu # Kinetis SPI Configuration
+
if KINETIS_USBHS && USBHOST
menu "USB host controller driver (HCD) options"
diff --git a/arch/arm/src/kinetis/kinetis_spi.c b/arch/arm/src/kinetis/kinetis_spi.c
index 4659ceb..c5d51a0 100644
--- a/arch/arm/src/kinetis/kinetis_spi.c
+++ b/arch/arm/src/kinetis/kinetis_spi.c
@@ -67,7 +67,9 @@
#include "arm_arch.h"
#include "kinetis.h"
+#include "kinetis_edma.h"
#include "kinetis_spi.h"
+#include "hardware/kinetis_dmamux.h"
#include "hardware/kinetis_memorymap.h"
#include "hardware/kinetis_sim.h"
#include "hardware/kinetis_dspi.h"
@@ -83,20 +85,34 @@
#define KINETIS_SPI_CLK_MAX (BOARD_BUS_FREQ / 2)
#define KINETIS_SPI_CLK_INIT 400000
+#define SPI_SR_CLEAR (SPI_SR_TCF | SPI_SR_EOQF | SPI_SR_TFUF | \
+ SPI_SR_TFFF | SPI_SR_RFOF | SPI_SR_RFDF | \
+ SPI_SR_TXRXS)
+
/****************************************************************************
* Private Types
****************************************************************************/
struct kinetis_spidev_s
{
- struct spi_dev_s spidev; /* Externally visible part of the SPI interface */
- uint32_t spibase; /* Base address of SPI registers */
- sem_t exclsem; /* Held while chip is selected for mutual exclusion */
- uint32_t frequency; /* Requested clock frequency */
- uint32_t actual; /* Actual clock frequency */
- uint8_t nbits; /* Width of word in bits (8 to 16) */
- uint8_t mode; /* Mode 0,1,2,3 */
- uint8_t ctarsel; /* Which CTAR */
+ struct spi_dev_s spidev; /* Externally visible part of the SPI interface */
+ uint32_t spibase; /* Base address of SPI registers */
+ sem_t exclsem; /* Held while chip is selected for mutual exclusion */
+ uint32_t frequency; /* Requested clock frequency */
+ uint32_t actual; /* Actual clock frequency */
+ uint8_t nbits; /* Width of word in bits (8 to 16) */
+ uint8_t mode; /* Mode 0,1,2,3 */
+ uint8_t ctarsel; /* Which CTAR */
+#ifdef CONFIG_KINETIS_SPI_DMA
+ volatile uint32_t rxresult; /* Result of the RX DMA */
+ volatile uint32_t txresult; /* Result of the TX DMA */
+ const uint8_t rxch; /* The RX DMA channel number */
+ const uint8_t txch; /* The TX DMA channel number */
+ DMACH_HANDLE rxdma; /* DMA channel handle for RX transfers */
+ DMACH_HANDLE txdma; /* DMA channel handle for TX transfers */
+ sem_t rxsem; /* Wait for RX DMA to complete */
+ sem_t txsem; /* Wait for TX DMA to complete */
+#endif
};
/****************************************************************************
@@ -132,6 +148,21 @@ static inline void spi_wait_status(FAR struct kinetis_spidev_s *priv,
static uint16_t spi_send_data(FAR struct kinetis_spidev_s *priv,
uint16_t wd, bool last);
+/* DMA support */
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static int spi_dmarxwait(FAR struct kinetis_spidev_s *priv);
+static int spi_dmatxwait(FAR struct kinetis_spidev_s *priv);
+static inline void spi_dmarxwakeup(FAR struct kinetis_spidev_s *priv);
+static inline void spi_dmatxwakeup(FAR struct kinetis_spidev_s *priv);
+static void spi_dmarxcallback(DMACH_HANDLE handle, void *arg,
+ bool done, int result);
+static void spi_dmatxcallback(DMACH_HANDLE handle, void *arg,
+ bool done, int result);
+static inline void spi_dmarxstart(FAR struct kinetis_spidev_s *priv);
+static inline void spi_dmatxstart(FAR struct kinetis_spidev_s *priv);
+#endif
+
/* SPI methods */
static int spi_lock(FAR struct spi_dev_s *dev, bool lock);
@@ -197,6 +228,15 @@ static struct kinetis_spidev_s g_spi0dev =
},
.spibase = KINETIS_SPI0_BASE,
.ctarsel = KINETIS_SPI_CTAR0_OFFSET,
+#ifdef CONFIG_KINETIS_SPI_DMA
+# ifdef CONFIG_KINETIS_SPI0_DMA
+ .rxch = KINETIS_DMA_REQUEST_SRC_SPI0_RX,
+ .txch = KINETIS_DMA_REQUEST_SRC_SPI0_TX,
+# else
+ .rxch = 0,
+ .txch = 0,
+# endif
+#endif
};
#endif
@@ -237,6 +277,15 @@ static struct kinetis_spidev_s g_spi1dev =
},
.spibase = KINETIS_SPI1_BASE,
.ctarsel = KINETIS_SPI_CTAR0_OFFSET,
+#ifdef CONFIG_KINETIS_SPI_DMA
+# ifdef CONFIG_KINETIS_SPI1_DMA
+ .rxch = KINETIS_DMA_REQUEST_SRC_SPI1_RX,
+ .txch = KINETIS_DMA_REQUEST_SRC_SPI1_TX,
+# else
+ .rxch = 0,
+ .txch = 0,
+# endif
+#endif
};
#endif
@@ -277,6 +326,15 @@ static struct kinetis_spidev_s g_spi2dev =
},
.spibase = KINETIS_SPI2_BASE,
.ctarsel = KINETIS_SPI_CTAR0_OFFSET,
+#ifdef CONFIG_KINETIS_SPI_DMA
+# ifdef CONFIG_KINETIS_SPI2_DMA
+ .rxch = KINETIS_DMA_REQUEST_SRC_FTM3_CH6__SPI2_RX,
+ .txch = KINETIS_DMA_REQUEST_SRC_FTM3_CH7__SPI2_TX,
+# else
+ .rxch = 0,
+ .txch = 0,
+# endif
+#endif
};
#endif
@@ -285,6 +343,32 @@ static struct kinetis_spidev_s g_spi2dev =
****************************************************************************/
/****************************************************************************
+ * Name: spi_modifyreg
+ *
+ * Description:
+ * Atomic modification of the 32-bit contents of the SPI register at offset
+ *
+ * Input Parameters:
+ * priv - private SPI device structure
+ * offset - offset to the register of interest
+ * clearbits - bits to clear
+ * clearbits - bits to set
+ *
+ * Returned Value:
+ * None.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static inline void spi_modifyreg(FAR struct kinetis_spidev_s *priv,
+ uint8_t offset, uint32_t clearbits,
+ uint32_t setbits)
+{
+ modifyreg32(priv->spibase + offset, clearbits, setbits);
+}
+#endif
+
+/****************************************************************************
* Name: spi_getreg
*
* Description:
@@ -946,7 +1030,7 @@ static uint32_t spi_send(FAR struct spi_dev_s *dev, uint32_t wd)
}
/****************************************************************************
- * Name: spi_exchange
+ * Name: spi_exchange (no DMA). aka spi_exchange_nodma
*
* Description:
* Exchange a block of data on SPI without using DMA
@@ -966,8 +1050,15 @@ static uint32_t spi_send(FAR struct spi_dev_s *dev, uint32_t wd)
*
****************************************************************************/
+#if !defined(CONFIG_STM32_SPI_DMA) || defined(CONFIG_STM32_SPI_DMATHRESHOLD)
+# if !defined(CONFIG_KINETIS_SPI_DMA)
static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
FAR void *rxbuffer, size_t nwords)
+# else
+static void spi_exchange_nodma(FAR struct spi_dev_s *dev,
+ FAR const void *txbuffer,
+ FAR void *rxbuffer, size_t nwords)
+# endif
{
FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_s *)dev;
uint8_t *brxptr = (uint8_t *)rxbuffer;
@@ -1039,8 +1130,162 @@ static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
}
}
}
+#endif /* !defined(CONFIG_STM32_SPI_DMA) || defined(CONFIG_STM32_SPI_DMATHRESHOLD) */
/****************************************************************************
+ * Name: spi_exchange (with DMA capability)
+ *
+ * Description:
+ * Exchange a block of data on SPI using DMA
+ *
+ * Input Parameters:
+ * dev - Device-specific state data
+ * txbuffer - A pointer to the buffer of data to be sent
+ * rxbuffer - A pointer to a buffer in which to receive data
+ * nwords - the length of data to be exchaned in units of words.
+ * The wordsize is determined by the number of bits-per-word
+ * selected for the SPI interface. If nbits <= 8, the data is
+ * packed into uint8_t's; if nbits > 8, the data is packed into
+ * uint16_t's
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
+ FAR void *rxbuffer, size_t nwords)
+{
+ int ret;
+ size_t adjust;
+ ssize_t nbytes;
+ static uint8_t rxdummy[4] __attribute__((aligned(4)));
+ static const uint16_t txdummy = 0xffff;
+ FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_s *)dev;
+
+ DEBUGASSERT(priv != NULL);
+ DEBUGASSERT(priv && priv->spibase);
+ spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords);
+
+ /* Convert the number of word to a number of bytes */
+
+ nbytes = (priv->nbits > 8) ? nwords << 1 : nwords;
+
+ /* Invalid DMA channels fall back to non-DMA method. */
+
+ if (priv->rxdma == NULL || priv->txdma == NULL
+#ifdef CONFIG_KINETIS_SPI_DMATHRESHOLD
+ /* If this is a small SPI transfer, then let spi_exchange_nodma()
+ * do the work.
+ */
+
+ || nbytes <= CONFIG_KINETIS_SPI_DMATHRESHOLD
+#endif
+ )
+ {
+ spi_exchange_nodma(dev, txbuffer, rxbuffer, nwords);
+ return;
+ }
+
+ /* Halt the SPI */
+
+ spi_run(priv, false);
+
+ /* Flush FIFOs */
+
+ spi_modifyreg(priv, KINETIS_SPI_MCR_OFFSET,
+ SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF,
+ SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF);
+
+ /* Clear all status bits */
+
+ spi_write_status(priv, SPI_SR_CLEAR);
+
+ /* disable DMA */
+
+ spi_modifyreg(priv, KINETIS_SPI_RSER_OFFSET,
+ SPI_RSER_RFDF_RE | SPI_RSER_TFFF_RE |
+ SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_DIRS,
+ 0);
+
+ /* Set up the DMA */
+
+ adjust = (priv->nbits > 8) ? 2 : 1;
+
+ struct kinetis_edma_xfrconfig_s config;
+
+ config.saddr = priv->spibase + KINETIS_SPI_POPR_OFFSET;
+ config.daddr = (uint32_t) (rxbuffer ? rxbuffer : rxdummy);
+ config.soff = 0;
+ config.doff = rxbuffer ? adjust : 0;
+ config.iter = nbytes;
+ config.flags = EDMA_CONFIG_LINKTYPE_LINKNONE;
+ config.ssize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
+ config.dsize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
+ config.ttype = EDMA_PERIPH2MEM;
+ config.nbytes = adjust;
+#ifdef CONFIG_KINETIS_EDMA_ELINK
+ config.linkch = NULL;
+#endif
+ kinetis_dmach_xfrsetup(priv->rxdma, &config);
+
+ config.saddr = (uint32_t) (txbuffer ? txbuffer : &txdummy);
+ config.daddr = priv->spibase + KINETIS_SPI_PUSHR_OFFSET;
+ config.soff = txbuffer ? adjust : 0;
+ config.doff = 0;
+ config.iter = nbytes;
+ config.flags = EDMA_CONFIG_LINKTYPE_LINKNONE;
+ config.ssize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
+ config.dsize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
+ config.ttype = EDMA_MEM2PERIPH;
+ config.nbytes = adjust;
+#ifdef CONFIG_KINETIS_EDMA_ELINK
+ config.linkch = NULL;
+#endif
+ kinetis_dmach_xfrsetup(priv->txdma, &config);
+
+ spi_modifyreg(priv, KINETIS_SPI_RSER_OFFSET, 0 ,
+ SPI_RSER_RFDF_RE | SPI_RSER_TFFF_RE |
+ SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_DIRS);
+
+ /* Start the DMAs */
+
+ spi_dmarxstart(priv);
+ spi_run(priv, true);
+
+ spi_putreg(priv, KINETIS_SPI_TCR_OFFSET, 0);
+ spi_write_control(priv, SPI_PUSHR_CTAS_CTAR0);
+
+ spi_dmatxstart(priv);
+
+ /* Then wait for each to complete */
+
+ ret = spi_dmarxwait(priv);
+
+ if (ret < 0)
+ {
+ ret = spi_dmatxwait(priv);
+ }
+
+ /* Reset any status */
+
+ spi_write_status(priv, spi_getreg(priv, KINETIS_SPI_SR_OFFSET));
+
+ /* Halt SPI */
+
+ spi_run(priv, false);
+
+ /* Disable DMA */
+
+ spi_modifyreg(priv, KINETIS_SPI_RSER_OFFSET,
+ SPI_RSER_RFDF_RE | SPI_RSER_TFFF_RE |
+ SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_DIRS,
+ 0);
+}
+
+#endif /* CONFIG_KINETIS_SPI_DMA */
+/****************************************************************************
* Name: spi_sndblock
*
* Description:
@@ -1100,6 +1345,174 @@ static void spi_recvblock(FAR struct spi_dev_s *dev, FAR void *rxbuffer,
#endif
/****************************************************************************
+ * Name: spi_dmarxwait
+ *
+ * Description:
+ * Wait for DMA to complete.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static int spi_dmarxwait(FAR struct kinetis_spidev_s *priv)
+{
+ int ret;
+
+ /* Take the semaphore (perhaps waiting). If the result is zero, then the
+ * DMA must not really have completed.
+ */
+
+ do
+ {
+ ret = nxsem_wait_uninterruptible(&priv->rxsem);
+
+ /* The only expected error is ECANCELED which would occur if the
+ * calling thread were canceled.
+ */
+
+ DEBUGASSERT(ret == OK || ret == -ECANCELED);
+ }
+ while (priv->rxresult == 0 && ret == OK);
+
+ return ret;
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_dmatxwait
+ *
+ * Description:
+ * Wait for DMA to complete.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static int spi_dmatxwait(FAR struct kinetis_spidev_s *priv)
+{
+ int ret;
+
+ /* Take the semaphore (perhaps waiting). If the result is zero, then the
+ * DMA must not really have completed.
+ */
+
+ do
+ {
+ ret = nxsem_wait_uninterruptible(&priv->txsem);
+
+ /* The only expected error is ECANCELED which would occur if the
+ * calling thread were canceled.
+ */
+
+ DEBUGASSERT(ret == OK || ret == -ECANCELED);
+ }
+ while (priv->txresult == 0 && ret == OK);
+
+ return ret;
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_dmarxwakeup
+ *
+ * Description:
+ * Signal that DMA is complete
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static inline void spi_dmarxwakeup(FAR struct kinetis_spidev_s *priv)
+{
+ nxsem_post(&priv->rxsem);
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_dmatxwakeup
+ *
+ * Description:
+ * Signal that DMA is complete
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static inline void spi_dmatxwakeup(FAR struct kinetis_spidev_s *priv)
+{
+ nxsem_post(&priv->txsem);
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_dmarxcallback
+ *
+ * Description:
+ * Called when the RX DMA completes
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static void spi_dmarxcallback(DMACH_HANDLE handle, void *arg, bool done,
+ int result)
+{
+ FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_s *)arg;
+
+ priv->rxresult = result | 0x80000000; /* assure non-zero */
+ spi_dmarxwakeup(priv);
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_dmatxcallback
+ *
+ * Description:
+ * Called when the RX DMA completes
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static void spi_dmatxcallback(DMACH_HANDLE handle, void *arg, bool done,
+ int result)
+{
+ FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_s *)arg;
+
+ /* Wake-up the SPI driver */
+
+ priv->txresult = result | 0x80000000; /* assure non-zero */
+ spi_dmatxwakeup(priv);
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_dmarxstart
+ *
+ * Description:
+ * Start RX DMA
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static inline void spi_dmarxstart(FAR struct kinetis_spidev_s *priv)
+{
+ priv->rxresult = 0;
+ kinetis_dmach_start(priv->rxdma, spi_dmarxcallback, priv);
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_dmatxstart
+ *
+ * Description:
+ * Start TX DMA
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_KINETIS_SPI_DMA
+static inline void spi_dmatxstart(FAR struct kinetis_spidev_s *priv)
+{
+ priv->txresult = 0;
+ kinetis_dmach_start(priv->txdma, spi_dmatxcallback, priv);
+}
+#endif
+
+/****************************************************************************
* Public Functions
****************************************************************************/
@@ -1251,6 +1664,39 @@ FAR struct spi_dev_s *kinetis_spibus_initialize(int port)
/* Initialize the SPI semaphore that enforces mutually exclusive access */
nxsem_init(&priv->exclsem, 0, 1);
+#ifdef CONFIG_KINETIS_SPI_DMA
+ /* Initialize the SPI semaphores that is used to wait for DMA completion.
+ * This semaphore is used for signaling and, hence, should not have
+ * priority inheritance enabled.
+ */
+
+ if (priv->rxch && priv->txch)
+ {
+ if (priv->txdma == NULL && priv->rxdma == NULL)
+ {
+ nxsem_init(&priv->rxsem, 0, 0);
+ nxsem_init(&priv->txsem, 0, 0);
+
+ nxsem_set_protocol(&priv->rxsem, SEM_PRIO_NONE);
+ nxsem_set_protocol(&priv->txsem, SEM_PRIO_NONE);
+
+ priv->txdma = kinetis_dmach_alloc(priv->txch | DMAMUX_CHCFG_ENBL,
+ 0);
+ priv->rxdma = kinetis_dmach_alloc(priv->rxch | DMAMUX_CHCFG_ENBL,
+ 0);
+ DEBUGASSERT(priv->rxdma && priv->txdma);
+ spi_modifyreg(priv, KINETIS_SPI_MCR_OFFSET,
+ 0,
+ SPI_MCR_DIS_RXF | SPI_MCR_DIS_TXF
+ );
+ }
+ }
+ else
+ {
+ priv->rxdma = NULL;
+ priv->txdma = NULL;
+ }
+#endif
return &priv->spidev;
}