| /**************************************************************************** |
| * arch/arm/src/max326xx/max32660/max32660_spim.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * The external functions, max326_spi1/2/3select and max326_spi1/2/3status |
| * must be provided by board-specific logic. They are implementations of |
| * the select and status methods of the SPI interface defined by struct |
| * spi_ops_s (see include/nuttx/spi/spi.h). All other methods (including |
| * max326_spibus_initialize()) are provided by common MAX326 logic. To use |
| * this common SPI logic on your board: |
| * |
| * 1. Provide logic in max326_boardinitialize() to configure SPI chip |
| * select pins. |
| * 2. Provide max326_spi1/2/3select() and max326_spi1/2/3status() functions |
| * in your board-specific logic. These functions will perform chip |
| * selection and status operations using GPIOs in the way your board is |
| * configured. |
| * 3. Add a calls to max326_spibus_initialize() in your low level |
| * application initialization logic |
| * 4. The handle returned by max326_spibus_initialize() may then be used |
| * to bind the SPI driver to higher level logic (e.g., calling |
| * mmcsd_spislotinitialize(), for example, will bind the SPI driver to |
| * the SPI MMC/SD driver). |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/spi/spi.h> |
| |
| #include "arm_internal.h" |
| #include "chip.h" |
| #include "hardware/max326_pinmux.h" |
| #include "max326_clockconfig.h" |
| #include "max326_periphclks.h" |
| #include "max326_gpio.h" |
| #include "max326_dma.h" |
| #include "max326_spim.h" |
| |
| #include <arch/board/board.h> |
| |
| #if defined(CONFIG_MAX326XX_HAVE_SPIM) |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MAX326_SPI_DMA |
| # error DMA not yet supported |
| #endif |
| |
| #ifndef CONFIG_DEBUG_MEMCARD_INFO |
| # undef CONFIG_MAX326_SPI_REGDEBUG |
| #endif |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct max326_spidev_s |
| { |
| struct spi_dev_s dev; /* Externally visible part of the SPI interface */ |
| const void *txbuffer; /* Tx buffer pointer (may be NULL) */ |
| void *rxbuffer; /* Rx buffer pointer (may be NULL) */ |
| uint32_t base; /* SPI base address */ |
| uint32_t frequency; /* Requested clock frequency */ |
| uint32_t actual; /* Actual clock frequency */ |
| mutex_t lock; /* Held while chip is selected for mutual exclusion */ |
| uint16_t rxbytes; /* Number of bytes received into rxbuffer */ |
| uint16_t txbytes; /* Number of bytes sent from txbuffer */ |
| uint16_t xfrlen; /* Transfer length */ |
| bool initialized; /* True: SPI interface been initialized */ |
| bool data16; /* True: 16- vs 8-bit data transfers */ |
| bool wire3; /* True: 3- vs 4-pin mode */ |
| bool busy; /* True: Transfer started */ |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| uint8_t irq; /* SPI IRQ number */ |
| #endif |
| uint8_t nbits; /* Width of word in bits (4 through 16) */ |
| uint8_t mode; /* Mode 0,1,2,3 */ |
| |
| #ifdef CONFIG_MAX326_SPI_REGDEBUG |
| /* Debug stuff */ |
| |
| bool wrlast; /* Last was a write */ |
| uint32_t addrlast; /* Last address */ |
| uint32_t vallast; /* Last value */ |
| int ntimes; /* Number of times */ |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Helpers */ |
| |
| #ifdef CONFIG_MAX326_SPI_REGDEBUG |
| static bool spi_checkreg(struct max326_spidev_s *priv, bool wr, |
| uint32_t regval, uintptr_t regaddr); |
| static uint32_t spi_getreg(struct max326_spidev_s *priv, |
| unsigned int offset); |
| static void spi_putreg(struct max326_spidev_s *priv, |
| unsigned int offset, uint32_t value); |
| #else |
| # define spi_checkreg(priv,wr,regval,regaddr) (false) |
| static inline uint32_t spi_getreg(struct max326_spidev_s *priv, |
| unsigned int offset); |
| static inline void spi_putreg(struct max326_spidev_s *priv, |
| unsigned int offset, uint32_t value); |
| #endif |
| |
| static void spi_modify_ctrl0(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits); |
| static void spi_modify_ctrl1(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits); |
| static void spi_modify_ctrl2(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits); |
| static void spi_modify_dma(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits); |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| static void spi_modify_inten(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits) |
| #endif |
| |
| /* Interrupt support */ |
| |
| static int spi_poll(struct max326_spidev_s *priv); |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| static int spi_interrupt(int irq, void *context, void *arg); |
| #endif |
| |
| /* SPI methods */ |
| |
| static int spi_lock(struct spi_dev_s *dev, bool lock); |
| #ifdef CONFIG_MAX326XX_SPIM0 |
| static void spi0_select(struct spi_dev_s *dev, uint32_t devid, |
| bool selected); |
| #endif |
| static uint32_t spi_setfrequency(struct spi_dev_s *dev, |
| uint32_t frequency); |
| static void spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode); |
| static void spi_setbits(struct spi_dev_s *dev, int nbits); |
| #ifdef CONFIG_SPI_HWFEATURES |
| static int spi_hwfeatures(struct spi_dev_s *dev, |
| spi_hwfeatures_t features); |
| #endif |
| static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd); |
| static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, |
| void *rxbuffer, size_t nwords); |
| #ifndef CONFIG_SPI_EXCHANGE |
| static void spi_sndblock(struct spi_dev_s *dev, const void *txbuffer, |
| size_t nwords); |
| static void spi_recvblock(struct spi_dev_s *dev, void *rxbuffer, |
| size_t nwords); |
| #endif |
| |
| /* Initialization */ |
| |
| static void spi_bus_initialize(struct max326_spidev_s *priv); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* NOTE: This is somewhat over-designed since there is only a single SPI |
| * peripheral. However, it supports simple group to additional SPI |
| * peripherals by extending this logic. |
| */ |
| |
| #ifdef CONFIG_MAX326XX_SPIM0 |
| static const struct spi_ops_s g_sp0iops = |
| { |
| .lock = spi_lock, |
| .select = spi0_select, |
| .setfrequency = spi_setfrequency, |
| .setmode = spi_setmode, |
| .setbits = spi_setbits, |
| #ifdef CONFIG_SPI_HWFEATURES |
| .hwfeatures = spi_hwfeatures, |
| #endif |
| .status = max326_spi0status, |
| #ifdef CONFIG_SPI_CMDDATA |
| .cmddata = max326_spi0cmddata, |
| #endif |
| .send = spi_send, |
| #ifdef CONFIG_SPI_EXCHANGE |
| .exchange = spi_exchange, |
| #else |
| .sndblock = spi_sndblock, |
| .recvblock = spi_recvblock, |
| #endif |
| #ifdef CONFIG_SPI_CALLBACK |
| .registercallback = max326_spi0register, /* Provided externally */ |
| #else |
| .registercallback = 0, /* Not implemented */ |
| #endif |
| }; |
| |
| static struct max326_spidev_s g_spi0dev = |
| { |
| .dev = |
| { |
| .ops = &g_sp0iops, |
| }, |
| .base = MAX326_SPI0_BASE, |
| .lock = NXMUTEX_INITIALIZER, |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| .irq = MAX326_IRQ_SPI, |
| #endif |
| #ifdef CONFIG_MAX326XX_3WIRE |
| .wire3 = true, |
| #endif |
| }; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MAX326_SPI_REGDEBUG |
| static bool spi_checkreg(struct max326_spidev_s *priv, bool wr, |
| uint32_t regval, uintptr_t regaddr) |
| { |
| if (wr == priv->wrlast && /* Same kind of access? */ |
| regval == priv->vallast && /* Same value? */ |
| regaddr == priv->addrlast) /* Same address? */ |
| { |
| /* Yes, then just keep a count of the number of times we did this. */ |
| |
| priv->ntimes++; |
| return false; |
| } |
| else |
| { |
| /* Did we do the previous operation more than once? */ |
| |
| if (priv->ntimes > 0) |
| { |
| /* Yes... show how many times we did it */ |
| |
| mcinfo("...[Repeats %d times]...\n", priv->ntimes); |
| } |
| |
| /* Save information about the new access */ |
| |
| priv->wrlast = wr; |
| priv->vallast = regval; |
| priv->addrlast = regaddr; |
| priv->ntimes = 0; |
| } |
| |
| /* Return true if this is the first time that we have done this operation */ |
| |
| return true; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_getreg |
| * |
| * Description: |
| * Get the contents of the SPI register at offset |
| * |
| * Input Parameters: |
| * priv - private SPI device structure |
| * offset - offset to the register of interest |
| * |
| * Returned Value: |
| * The contents of the 32-bit register |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MAX326_SPI_REGDEBUG |
| static uint32_t spi_getreg(struct max326_spidev_s *priv, unsigned int offset) |
| { |
| uintptr_t regaddr = priv->base + offset; |
| uint32_t regval = getreg32(regaddr); |
| |
| if (spi_checkreg(priv, false, regval, regaddr)) |
| { |
| mcinfo("%08x->%08x\n", regaddr, regval); |
| } |
| |
| return regval; |
| } |
| #else |
| static inline uint32_t spi_getreg(struct max326_spidev_s *priv, |
| unsigned int offset) |
| { |
| return getreg32(priv->base + offset); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_putreg |
| * |
| * Description: |
| * Write a 16-bit value to the SPI register at offset |
| * |
| * Input Parameters: |
| * priv - Private SPI device structure |
| * offset - offset to the register of interest |
| * regval - the 32-bit value to be written |
| * |
| * Returned Value: |
| * The contents of the 16-bit register |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MAX326_SPI_REGDEBUG |
| static void spi_putreg(struct max326_spidev_s *priv, unsigned int offset, |
| uint32_t regval) |
| { |
| uintptr_t regaddr = priv->base + offset; |
| |
| if (spi_checkreg(priv, true, regval, regaddr)) |
| { |
| mcinfo("%08x<-%08x\n", regaddr, regval); |
| } |
| |
| putreg32(regval, regaddr); |
| } |
| #else |
| static inline void spi_putreg(struct max326_spidev_s *priv, |
| unsigned int offset, uint32_t regval) |
| { |
| putreg32(regval, priv->base + offset); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_modify_ctrl0 |
| * |
| * Description: |
| * Clear and set bits in the CTRL0 register |
| * |
| * Input Parameters: |
| * priv - Device-specific state data |
| * clrbits - The bits to clear |
| * setbits - The bits to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void spi_modify_ctrl0(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits) |
| { |
| uint32_t ctrl0; |
| |
| ctrl0 = spi_getreg(priv, MAX326_SPI_CTRL0_OFFSET); |
| ctrl0 &= ~clrbits; |
| ctrl0 |= setbits; |
| spi_putreg(priv, MAX326_SPI_CTRL0_OFFSET, ctrl0); |
| } |
| |
| /**************************************************************************** |
| * Name: spi_modify_ctrl1 |
| * |
| * Description: |
| * Clear and set bits in the CTRL1 register |
| * |
| * Input Parameters: |
| * priv - Device-specific state data |
| * clrbits - The bits to clear |
| * setbits - The bits to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void spi_modify_ctrl1(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits) |
| { |
| uint32_t ctrl1; |
| |
| ctrl1 = spi_getreg(priv, MAX326_SPI_CTRL1_OFFSET); |
| ctrl1 &= ~clrbits; |
| ctrl1 |= setbits; |
| spi_putreg(priv, MAX326_SPI_CTRL1_OFFSET, ctrl1); |
| } |
| |
| /**************************************************************************** |
| * Name: spi_modify_ctrl2 |
| * |
| * Description: |
| * Clear and set bits in the CTRL1 register |
| * |
| * Input Parameters: |
| * priv - Device-specific state data |
| * clrbits - The bits to clear |
| * setbits - The bits to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void spi_modify_ctrl2(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits) |
| { |
| uint32_t ctrl2; |
| |
| ctrl2 = spi_getreg(priv, MAX326_SPI_CTRL2_OFFSET); |
| ctrl2 &= ~clrbits; |
| ctrl2 |= setbits; |
| spi_putreg(priv, MAX326_SPI_CTRL2_OFFSET, ctrl2); |
| } |
| |
| /**************************************************************************** |
| * Name: spi_modify_dma |
| * |
| * Description: |
| * Clear and set bits in the DMA register |
| * |
| * Input Parameters: |
| * priv - Device-specific state data |
| * clrbits - The bits to clear |
| * setbits - The bits to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void spi_modify_dma(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits) |
| { |
| uint32_t dma; |
| |
| dma = spi_getreg(priv, MAX326_SPI_DMA_OFFSET); |
| dma &= ~clrbits; |
| dma |= setbits; |
| spi_putreg(priv, MAX326_SPI_DMA_OFFSET, dma); |
| } |
| |
| /**************************************************************************** |
| * Name: spi_modify_inten |
| * |
| * Description: |
| * Clear and set bits in the INTEN register |
| * |
| * Input Parameters: |
| * priv - Device-specific state data |
| * clrbits - The bits to clear |
| * setbits - The bits to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| static void spi_modify_inten(struct max326_spidev_s *priv, uint32_t setbits, |
| uint32_t clrbits) |
| { |
| uint32_t inten; |
| |
| inten = spi_getreg(priv, MAX326_SPI_INTEN_OFFSET); |
| inten &= ~clrbits; |
| inten |= setbits; |
| spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, inten); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_poll |
| * |
| * Description: |
| * Handle SPI events. This may be called repeatedly in polled mode or may |
| * be called from spi_interrupt() in interrupt mode. |
| * |
| * Returned Value: |
| * Returns OK when the transfer is complete, -EBUSY is the transfer is |
| * still in progress. Could also return other negated errno values if |
| * an error is detected. |
| * |
| ****************************************************************************/ |
| |
| static int spi_poll(struct max326_spidev_s *priv) |
| { |
| const uint8_t *src; |
| uint8_t *dest; |
| uint32_t inten; |
| uint32_t length; |
| uint32_t regval; |
| uint32_t tmp; |
| int txavail; |
| int rxavail; |
| int remaining; |
| |
| /* Get the transfer size in units of bytes */ |
| |
| length = priv->data16 ? priv->xfrlen << 1 : priv->xfrlen; |
| |
| /* Is there a Tx source buffer? */ |
| |
| inten = 0; |
| if (priv->txbuffer != NULL) |
| { |
| /* Need to know when all bytes are transmitted. */ |
| |
| inten |= SPI_INT_TXEMPTY; |
| |
| /* Calculate how many bytes we can write to the FIFO */ |
| |
| regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET); |
| tmp = (regval & SPI_DMA_TXFIFOCNT_MASK) >> SPI_DMA_TXFIFOCNT_SHIFT; |
| txavail = MAX326_SPI_FIFO_DEPTH - tmp; |
| |
| if ((length - priv->txbytes) < txavail) |
| { |
| txavail = (length - priv->txbytes); |
| } |
| |
| if (priv->data16) |
| { |
| txavail &= ~1; |
| } |
| |
| /* Write Tx buffer data to the Tx FIFO */ |
| |
| src = &((const uint8_t *)priv->txbuffer)[priv->txbytes]; |
| while (txavail > 0) |
| { |
| dest = (uint8_t *)(priv->base + MAX326_SPI_DATA_OFFSET); |
| |
| if (txavail > 3) |
| { |
| *dest++ = *src++; |
| *dest++ = *src++; |
| *dest++ = *src++; |
| *dest = *src++; |
| |
| txavail -= 4; |
| priv->txbytes += 4; |
| } |
| else if (txavail > 1) |
| { |
| *dest++ = *src++; |
| *dest = *src++; |
| |
| txavail -= 2; |
| priv->txbytes += 2; |
| } |
| else if (!priv->data16) |
| { |
| *dest = *src++; |
| |
| txavail -= 1; |
| priv->txbytes += 1; |
| } |
| } |
| } |
| |
| remaining = length - priv->txbytes; |
| |
| /* Set the TX interrupts */ |
| |
| if (remaining > 0) |
| { |
| if (remaining > MAX326_SPI_FIFO_DEPTH) |
| { |
| /* Set the TX FIFO almost empty interrupt if we have to refill */ |
| |
| regval = SPI_DMA_TXFIFOLVL(MAX326_SPI_FIFO_DEPTH); |
| } |
| else |
| { |
| regval = SPI_DMA_TXFIFOLVL(remaining); |
| } |
| |
| spi_modify_dma(priv, regval, SPI_DMA_TXFIFOLVL_MASK); |
| inten |= SPI_INT_TXLEVEL; |
| } |
| |
| /* Break out if we've transmitted all the bytes and not receiving */ |
| |
| if ((priv->rxbuffer == NULL) && priv->txbytes == length && |
| (spi_getreg(priv, MAX326_SPI_DMA_OFFSET) & |
| SPI_DMA_TXFIFOCNT_MASK) == 0) |
| { |
| goto done; |
| } |
| |
| /* Read from the RX FIFO */ |
| |
| if (priv->rxbuffer != NULL) |
| { |
| /* Wait for there to be data in the RX FIFO */ |
| |
| regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET); |
| rxavail = (regval & SPI_DMA_RXFIFOCNT_MASK) >> SPI_DMA_RXFIFOCNT_SHIFT; |
| |
| if (length - priv->rxbytes < rxavail) |
| { |
| rxavail = length - priv->rxbytes; |
| } |
| |
| dest = &((uint8_t *)priv->rxbuffer)[priv->rxbytes]; |
| if (!priv->data16 || rxavail >= sizeof(uint16_t)) |
| { |
| /* Read data from the Rx FIFO */ |
| |
| src = (const uint8_t *)(priv->base + MAX326_SPI_DATA_OFFSET); |
| while (rxavail > 0) |
| { |
| if (rxavail > 3) |
| { |
| *dest++ = *src++; |
| *dest++ = *src++; |
| *dest++ = *src++; |
| *dest++ = *src; |
| |
| rxavail -= 4; |
| priv->rxbytes += 4; |
| } |
| else if (rxavail > 1) |
| { |
| *dest++ = *src++; |
| *dest++ = *src; |
| |
| rxavail -= 2; |
| priv->rxbytes += 2; |
| } |
| else |
| { |
| *dest++ = *src; |
| |
| rxavail -= 1; |
| priv->rxbytes += 1; |
| } |
| |
| /* Don't read less than 2 bytes if we are using 16-bit data |
| * transfers. |
| */ |
| |
| if (rxavail < sizeof(uint16_t) && priv->data16) |
| { |
| break; |
| } |
| } |
| } |
| |
| remaining = length - priv->rxbytes; |
| if (remaining > 0) |
| { |
| if (remaining > MAX326_SPI_FIFO_DEPTH) |
| { |
| regval = SPI_DMA_RXFIFOLVL(2); |
| } |
| else |
| { |
| regval = SPI_DMA_RXFIFOLVL(remaining - 1); |
| } |
| |
| spi_modify_dma(priv, regval, SPI_DMA_RXFIFOLVL_MASK); |
| inten |= SPI_INT_RXLEVEL; |
| } |
| |
| /* Break out if we've received all the bytes and we're not |
| * transmitting. |
| */ |
| |
| if (priv->txbuffer == NULL && priv->rxbytes == length) |
| { |
| goto done; |
| } |
| } |
| |
| /* Break out once we've transmitted and received all of the data. */ |
| |
| if (priv->rxbytes == length && priv->txbytes == length) |
| { |
| regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET); |
| if ((regval & SPI_DMA_TXFIFOCNT_MASK) == 0) |
| { |
| goto done; |
| } |
| } |
| |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| /* Enable interrupts for the next phase */ |
| |
| spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, inten); |
| #endif |
| |
| /* Return busy to indicate that we are not finished with the transfer */ |
| |
| return -EBUSY; |
| |
| done: |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| /* We are done.. disable interrupts */ |
| |
| spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, 0); |
| #endif |
| |
| /* Return OK to indicate that we are finished with the transfer */ |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: spi_interrupt |
| * |
| * Description: |
| * This is the SPI interrupt handler. It will be invoked when an |
| * interrupt received on the 'irq'. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| static int spi_interrupt(int irq, void *context, void *arg) |
| { |
| struct max326_spidev_s *priv = (struct max326_spidev_s *)arg; |
| uint32_t intfl; |
| uint32_t regval; |
| unsigned int rxavail; |
| unsigned int rxlevel; |
| int ret; |
| |
| /* Read pending interrupt flags, interrupt enables, and SPI status |
| * registers. |
| */ |
| |
| intfl = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET); |
| |
| /* Disable all interrupts */ |
| |
| spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, 0); |
| |
| /* Clear pending interrupt flags */ |
| |
| max326_serialout(priv, MAX326_SPI_INTFL_OFFSET, intfl & SPI_INT_ALL); |
| |
| /* Handle any active request */ |
| |
| if (intfl != 0) |
| { |
| do |
| { |
| ret = spi_poll(priv); |
| DEBUGASSERT(ret == OK || ret == -EBUSY); |
| |
| /* Check if there is more Rx data to be read */ |
| |
| regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET); |
| rxavail = (regval & SPI_DMA_TXFIFOCNT_MASK) >> |
| SPI_DMA_TXFIFOCNT_SHIFT; |
| rxlevel = (regval & SPI_DMA_RXFIFOLVL_MASK) >> |
| SPI_DMA_RXFIFOLVL_SHIFT; |
| } |
| while (/* RX buffer != NULL && */ rxavail > rxlevel); |
| } |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_lock |
| * |
| * Description: |
| * On SPI buses where there are multiple devices, it will be necessary to |
| * lock SPI to have exclusive access to the buses for a sequence of |
| * transfers. The bus should be locked before the chip is selected. After |
| * locking the SPI bus, the caller should then also call the setfrequency, |
| * setbits, and setmode methods to make sure that the SPI is properly |
| * configured for the device. If the SPI bus is being shared, then it |
| * may have been left in an incompatible state. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * lock - true: Lock spi bus, false: unlock SPI bus |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static int spi_lock(struct spi_dev_s *dev, bool lock) |
| { |
| struct max326_spidev_s *priv = (struct max326_spidev_s *)dev; |
| int ret; |
| |
| if (lock) |
| { |
| ret = nxmutex_lock(&priv->lock); |
| } |
| else |
| { |
| ret = nxmutex_unlock(&priv->lock); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: SPI_SELECT |
| * |
| * Description: |
| * Enable/disable the SPI chip select. The implementation of this method |
| * must include handshaking: If a device is selected, it must hold off |
| * all other attempts to select the device until the device is deselected. |
| * Required. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * devid - Identifies the device to select |
| * selected - true: slave selected, false: slave de-selected |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MAX326XX_SPIM0 |
| static void spi0_select(struct spi_dev_s *dev, uint32_t devid, bool selected) |
| { |
| struct max326_spidev_s *priv; |
| |
| /* Forward the call to the board-specific implementation */ |
| |
| DEBUGASSERT(dev != NULL); |
| max326_spi0select(dev, devid, selected); |
| |
| /* The hardware requires that the SPI block be disabled and re-enabled at |
| * end of each transaction to cancel the on ongoing transaction (while |
| * chip select is inactive). |
| */ |
| |
| if (!selected) |
| { |
| priv = (struct max326_spidev_s *)dev; |
| spi_modify_ctrl0(priv, 0, SPI_CTRL0_SPIEN); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_setfrequency |
| * |
| * Description: |
| * Set the SPI frequency. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * frequency - The SPI frequency requested |
| * |
| * Returned Value: |
| * Returns the actual frequency selected |
| * |
| ****************************************************************************/ |
| |
| static uint32_t spi_setfrequency(struct spi_dev_s *dev, uint32_t frequency) |
| { |
| struct max326_spidev_s *priv = (struct max326_spidev_s *)dev; |
| |
| /* Has the frequency changed? */ |
| |
| if (frequency != priv->frequency) |
| { |
| uint32_t pclk; |
| uint32_t tmpbaud; |
| uint32_t actual; |
| uint32_t error; |
| uint32_t regval; |
| unsigned int tmpscale; |
| unsigned int tmphigh; |
| unsigned int scale; |
| unsigned int high; |
| |
| /* The SPI clock derives from the PCLK: |
| * |
| * Fspi = Fpclk / (1 << CLKCFG.scale) |
| * |
| * The SPI high time is determined by Tspi and CLKCFG.high: |
| * |
| * Thi = Tspi * CLKCFG.high |
| * |
| * Similar the SPI low time id determined by Tspi and CLKCFG.low: |
| * |
| * Tlow = Tspi * CLKCFG.low |
| * |
| * The BAUD is the given by: |
| * |
| * Fbaud = 1 / (Thi + Tlow) |
| * |
| * If we assume that Thi == Tlow, then: |
| * |
| * Thi = Tspi * CLKCFG.high |
| * Fbaud = 1 / (2 * Thi) |
| * |
| * And |
| * |
| * Fbaud = Fpclk / CLKCFG.high / (1 << (CLKCFG.scale + 1) |
| * CLKCFG.high = Fpclk / (1 << (CLKCFG.scale + 1)) / Fbaud |
| * |
| * Example: Fpclk = 48MHz, Fbaud=1MHz |
| * |
| * scale CLKCFG.high Resulting Fbaud |
| * 0 24 1,000,0000 (exact) |
| * 1 12 1,000,0000 (exact) |
| * 2 6 1,000,0000 (exact) |
| * 3 3 1,000,0000 (exact) |
| * 4 1 1,500,0000 |
| * 5 0 Invalid |
| * 6 0 Invalid |
| * 7 0 Invalid |
| * 8 0 Invalid |
| * |
| * Fbaud / 2 = Fspi / CLKCFG.high |
| * Fbaud = 2 * Fspi / CLKCFG.high |
| */ |
| |
| /* There are only 9 possible values for CLKCFG.scale. Let's try them |
| * all and pick the best. |
| */ |
| |
| pclk = max326_pclk_frequency(); |
| DEBUGASSERT(frequency > 0 && frequency <= pclk); |
| error = UINT32_MAX; |
| |
| for (tmpscale = 0; tmpscale < 9; tmpscale++) |
| { |
| tmphigh = (pclk / frequency) >> (tmpscale + 1); |
| if (tmphigh > 0) |
| { |
| uint32_t tmperr; |
| |
| /* Calculate the frequency difference */ |
| |
| tmpbaud = (pclk / tmphigh) >> (tmpscale + 1); |
| if (tmpbaud > frequency) |
| { |
| tmperr = tmpbaud - frequency; |
| } |
| else |
| { |
| tmperr = frequency - tmpbaud; |
| } |
| |
| /* Save the best result we find (or the lowest scale in case |
| * of ties). |
| */ |
| |
| if (tmperr < error) |
| { |
| error = tmperr; |
| actual = tmpbaud; |
| scale = tmpscale; |
| high = tmphigh; |
| } |
| } |
| } |
| |
| regval = spi_getreg(priv, MAX326_SPI_CLKCFG_OFFSET); |
| regval &= ~(SPI_CLKCFG_LO_MASK | SPI_CLKCFG_HI_MASK | |
| SPI_CLKCFG_SCALE_MASK); |
| regval |= (SPI_CLKCFG_LO(high) | SPI_CLKCFG_HI(high) | |
| SPI_CLKCFG_SCALE(scale)); |
| spi_putreg(priv, MAX326_SPI_CLKCFG_OFFSET, regval); |
| |
| /* Save the frequency selection so that subsequent reconfigurations |
| * will be faster. |
| */ |
| |
| spiinfo("Frequency %d->%d\n", frequency, actual); |
| |
| priv->frequency = frequency; |
| priv->actual = actual; |
| } |
| |
| return priv->actual; |
| } |
| |
| /**************************************************************************** |
| * Name: spi_setmode |
| * |
| * Description: |
| * Set the SPI mode. see enum spi_mode_e for mode definitions |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * mode - The SPI mode requested |
| * |
| * Returned Value: |
| * Returns the actual frequency selected |
| * |
| ****************************************************************************/ |
| |
| static void spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode) |
| { |
| struct max326_spidev_s *priv = (struct max326_spidev_s *)dev; |
| uint16_t setbits; |
| uint16_t clrbits; |
| |
| spiinfo("mode=%d\n", mode); |
| |
| /* Has the mode changed? */ |
| |
| if (mode != priv->mode) |
| { |
| /* Yes... Set CR1 appropriately */ |
| |
| switch (mode) |
| { |
| case SPIDEV_MODE0: /* CPOL=0; CPHA=0 */ |
| setbits = 0; |
| clrbits = SPI_CTRL2_CLKPOL | SPI_CTRL2_CLKPHA; |
| break; |
| |
| case SPIDEV_MODE1: /* CPOL=0; CPHA=1 */ |
| setbits = SPI_CTRL2_CLKPHA; |
| clrbits = SPI_CTRL2_CLKPOL; |
| break; |
| |
| case SPIDEV_MODE2: /* CPOL=1; CPHA=0 */ |
| setbits = SPI_CTRL2_CLKPOL; |
| clrbits = SPI_CTRL2_CLKPHA; |
| break; |
| |
| case SPIDEV_MODE3: /* CPOL=1; CPHA=1 */ |
| setbits = SPI_CTRL2_CLKPOL | SPI_CTRL2_CLKPHA; |
| clrbits = 0; |
| break; |
| |
| default: |
| return; |
| } |
| |
| spi_modify_ctrl2(priv, setbits, clrbits); |
| |
| /* Save the mode so that subsequent re-configurations will be |
| * faster. |
| */ |
| |
| priv->mode = mode; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: spi_setbits |
| * |
| * Description: |
| * Set the number of bits per word. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * nbits - The number of bits requested |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void spi_setbits(struct spi_dev_s *dev, int nbits) |
| { |
| struct max326_spidev_s *priv = (struct max326_spidev_s *)dev; |
| |
| spiinfo("nbits=%d\n", nbits); |
| DEBUGASSERT(nbits > 0 && nbits <= 16); |
| |
| /* Has the number of bits changed? */ |
| |
| if (nbits != priv->nbits) |
| { |
| /* Yes... Set CTRL2 appropriately */ |
| |
| spi_modify_ctrl2(priv, SPI_CTRL2_NUMBITS(nbits), |
| SPI_CTRL2_NUMBITS_MASK); |
| |
| /* Will we have to do 16- or 8-bit data transfers? */ |
| |
| priv->data16 = (nbits > 8); |
| |
| /* Save the selection so that subsequent re-configurations will be |
| * faster. |
| */ |
| |
| priv->nbits = nbits; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: spi_hwfeatures |
| * |
| * Description: |
| * Set hardware-specific feature flags. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * features - H/W feature flags |
| * |
| * Returned Value: |
| * Zero (OK) if the selected H/W features are enabled; A negated errno |
| * value if any H/W feature is not supportable. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SPI_HWFEATURES |
| static int spi_hwfeatures(struct spi_dev_s *dev, spi_hwfeatures_t features) |
| { |
| return -ENOSYS; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_send |
| * |
| * Description: |
| * Exchange one word on SPI |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * wd - The word to send. the size of the data is determined by the |
| * number of bits selected for the SPI interface. |
| * |
| * Returned Value: |
| * response |
| * |
| ****************************************************************************/ |
| |
| static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd) |
| { |
| uint32_t ret; |
| |
| spiinfo("wd=%04u\n", wd); |
| spi_exchange(dev, &wd, &ret, 1); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: spi_exchange |
| * |
| * Description: |
| * Exchange a block of data on SPI |
| * |
| * 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 exchanged 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 |
| * |
| ****************************************************************************/ |
| |
| static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, |
| void *rxbuffer, size_t nwords) |
| { |
| struct max326_spidev_s *priv = (struct max326_spidev_s *)dev; |
| #ifndef CONFIG_MAX326_SPI_INTERRUPTS |
| uint32_t regval; |
| #endif |
| int ret; |
| |
| spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords); |
| DEBUGASSERT(priv != NULL && nwords > 0 && nwords <= UINT16_MAX); |
| DEBUGASSERT(txbuffer != NULL || rxbuffer != NULL); |
| |
| /* Setup for the transfer */ |
| |
| priv->txbuffer = txbuffer; |
| priv->rxbuffer = rxbuffer; |
| priv->txbytes = 0; |
| priv->rxbytes = 0; |
| priv->xfrlen = nwords; |
| priv->busy = false; |
| |
| /* NOTE: The MAX326 slave select pin is not used. It is controlled as a |
| * GPIO output from the upper half driver. |
| */ |
| |
| /* Setup the Rx number of words to exchange */ |
| |
| if (rxbuffer != NULL) |
| { |
| /* Note: If the SPI port is set to operate in 4-wire mode, the RXNUM |
| * field is ignored and the TXNUM field is used for both the number |
| * of words to receive or transmit. |
| */ |
| |
| if (priv->wire3) |
| { |
| spi_modify_ctrl1(priv, SPI_CTRL1_RXNUMCH(nwords), |
| SPI_CTRL1_RXNUMCH_MASK); |
| } |
| else |
| { |
| spi_modify_ctrl1(priv, SPI_CTRL1_TXNUMCH(nwords), |
| SPI_CTRL1_TXNUMCH_MASK); |
| } |
| |
| /* Enable the DMA Rx FIFO */ |
| |
| spi_modify_dma(priv, SPI_DMA_RXFIFOEN, 0); |
| } |
| else |
| { |
| /* No Rx data sink */ |
| |
| spi_modify_ctrl1(priv, 0, SPI_CTRL1_RXNUMCH_MASK); |
| spi_modify_dma(priv, 0, SPI_DMA_RXFIFOEN); |
| } |
| |
| /* Four wire mode always operates in full duplex. We must have some Tx |
| * buffer available in all cases. |
| */ |
| |
| if (!priv->wire3 && txbuffer == NULL) |
| { |
| size_t nbytes = priv->data16 ? nwords << 1 : nwords; |
| |
| /* We will dual-purpose the the Rx buffer, initialized to zero */ |
| |
| memset(priv->rxbuffer, 0, nbytes); |
| priv->txbuffer = priv->rxbuffer; |
| } |
| |
| /* Set up the number of Tx words to exchange */ |
| |
| if (priv->txbuffer != NULL) |
| { |
| spi_modify_ctrl1(priv, SPI_CTRL1_TXNUMCH(nwords), |
| SPI_CTRL1_TXNUMCH_MASK); |
| |
| /* Enable the DMA Tx FIFO */ |
| |
| spi_modify_dma(priv, SPI_DMA_TXFIFOEN, 0); |
| } |
| else |
| { |
| /* No Tx data source */ |
| |
| spi_modify_dma(priv, 0, SPI_DMA_TXFIFOEN); |
| } |
| |
| /* Flush the Rx and Tx FIFOs */ |
| |
| spi_modify_dma(priv, SPI_DMA_TXFIFOCLR | SPI_DMA_RXFIFOCLR, 0); |
| |
| /* Clear pending interrupts */ |
| |
| regval = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET); |
| spi_putreg(priv, MAX326_SPI_INTFL_OFFSET, regval); |
| |
| /* Re-enable SPI to start the next transfer */ |
| |
| spi_modify_ctrl0(priv, SPI_CTRL0_SPIEN, 0); |
| |
| /* Poll one time to setup the transfer */ |
| |
| ret = spi_poll(priv); |
| DEBUGASSERT(ret == OK || ret == -EBUSY); |
| |
| /* Initiate data transmission */ |
| |
| spi_modify_ctrl0(priv, SPI_CTRL0_START, 0); |
| priv->busy = true; |
| |
| #ifndef CONFIG_MAX326_SPI_INTERRUPTS |
| /* Poll repeatedly until the transfer is complete */ |
| |
| do |
| { |
| ret = spi_poll(priv); |
| } |
| while (ret != OK); |
| |
| /* Then wait for the master done interrupt */ |
| |
| do |
| { |
| regval = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET); |
| } |
| while ((regval & SPI_INT_MDONE) == 0); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: spi_sndblock |
| * |
| * Description: |
| * Send a block of data on SPI |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * txbuffer - A pointer to the buffer of data to be sent |
| * nwords - the length of data to send from the buffer in number 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 |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_SPI_EXCHANGE |
| static void spi_sndblock(struct spi_dev_s *dev, const void *txbuffer, |
| size_t nwords) |
| { |
| spiinfo("txbuffer=%p nwords=%d\n", txbuffer, nwords); |
| spi_exchange(dev, txbuffer, NULL, nwords); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_recvblock |
| * |
| * Description: |
| * Receive a block of data from SPI |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * rxbuffer - A pointer to the buffer in which to receive data |
| * nwords - the length of data that can be received in the buffer in |
| * number 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 |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_SPI_EXCHANGE |
| static void spi_recvblock(struct spi_dev_s *dev, void *rxbuffer, |
| size_t nwords) |
| { |
| spiinfo("rxbuffer=%p nwords=%d\n", rxbuffer, nwords); |
| spi_exchange(dev, NULL, rxbuffer, nwords); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: spi_bus_initialize |
| * |
| * Description: |
| * Initialize the selected SPI bus in its default state (Master, 8-bit, |
| * mode 0, etc.) |
| * |
| * Input Parameters: |
| * priv - private SPI device structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void spi_bus_initialize(struct max326_spidev_s *priv) |
| { |
| uint32_t setbits; |
| uint32_t clrbits; |
| uint32_t regval; |
| |
| /* Enable SPI */ |
| |
| spi_modify_ctrl0(priv, SPI_CTRL0_SPIEN, 0); |
| |
| /* Setup slaved select timing (even in Master mode?) */ |
| |
| regval = (SPI_SSTIME_SSACT1(1) | SPI_SSTIME_SSACT2(1) | |
| SPI_SSTIME_SSINACT(1)); |
| spi_putreg(priv, MAX326_SPI_SSTIME_OFFSET, regval); |
| |
| /* Configure CTRL0. Default configuration: |
| * Mode 0: CTRL2: CLKPHA=0 and CLKPOL=0 |
| * 8-bit: CTRL2: NUMBITS=8 |
| */ |
| |
| clrbits = SPI_CTRL2_CLKPHA | SPI_CTRL2_CLKPOL | SPI_CTRL2_NUMBITS_MASK; |
| setbits = SPI_CTRL2_NUMBITS(8); |
| spi_modify_ctrl2(priv, setbits, clrbits); |
| |
| priv->frequency = 0; |
| priv->nbits = 8; |
| priv->mode = SPIDEV_MODE0; |
| |
| /* Enable Master mode */ |
| |
| spi_modify_ctrl0(priv, SPI_CTRL0_MMEN, 0); |
| |
| /* Select a default frequency of approx. 400KHz */ |
| |
| spi_setfrequency((struct spi_dev_s *)priv, 400000); |
| |
| /* Set up 3- or 4-pin mode */ |
| |
| regval = priv->wire3 ? SPI_CTRL2_DATWIDTH_SINGLE : SPI_CTRL2_DATWIDTH_DUAL; |
| spi_modify_ctrl2(priv, regval, SPI_CTRL2_DATWIDTH_MASK); |
| |
| /* Disable all interrupts at the peripheral */ |
| |
| spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, 0); |
| |
| /* Clear pending interrupts */ |
| |
| regval = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET); |
| spi_putreg(priv, MAX326_SPI_INTFL_OFFSET, regval); |
| |
| #ifdef CONFIG_MAX326_SPI_INTERRUPTS |
| /* Attach the interrupt handler and enable the IRQ at the NVIC. |
| * Interrupts are (probably) still disabled at the SPI peripheral. |
| */ |
| |
| ret = irq_attach(priv->irq, spi_interrupt, priv); |
| if (ret == OK) |
| { |
| up_enable_irq(priv->irq); |
| } |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: max326_spibus_initialize |
| * |
| * Description: |
| * Initialize the selected SPI bus |
| * |
| * Input Parameters: |
| * Port number (for hardware that has multiple SPI interfaces) |
| * |
| * Returned Value: |
| * Valid SPI device structure reference on success; a NULL on failure |
| * |
| ****************************************************************************/ |
| |
| struct spi_dev_s *max326_spibus_initialize(int bus) |
| { |
| struct max326_spidev_s *priv = NULL; |
| |
| irqstate_t flags = enter_critical_section(); |
| |
| #ifdef CONFIG_MAX326XX_SPIM0 |
| if (bus == 0) |
| { |
| /* Select SPI0 */ |
| |
| priv = &g_spi0dev; |
| |
| /* Only configure if the bus is not already configured */ |
| |
| if (!priv->initialized) |
| { |
| /* Enable peripheral clocking */ |
| |
| max326_spi0_enableclk(); |
| |
| /* Configure SPI0 pins: SCK, MISO, and MOSI */ |
| |
| max326_gpio_config(GPIO_SPI0_SCK); |
| max326_gpio_config(GPIO_SPI0_MISO); |
| max326_gpio_config(GPIO_SPI0_MOSI); |
| |
| /* Set up default configuration: Master, 8-bit, etc. */ |
| |
| spi_bus_initialize(priv); |
| priv->initialized = true; |
| } |
| } |
| else |
| #endif |
| { |
| spierr("ERROR: Unsupported SPI bus: %d\n", bus); |
| } |
| |
| leave_critical_section(flags); |
| return (struct spi_dev_s *)priv; |
| } |
| |
| #endif /* CONFIG_MAX326XX_HAVE_SPIM */ |