--- /dev/null
+/****************************************************************************
+ *
+ * Copyright 2016 Samsung Electronics All Rights Reserved.
+ *
+ * Licensed 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.
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * include/tinyara/spi/spi_bitbang.c
+ *
+ * Copyright (C) 2013 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * 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.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT OWNER 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <tinyara/config.h>
+
+#include <tinyara/spi/spi_bitbang.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* Usage ********************************************************************/
+/* To use this logic, you should provide a C file that does the following:
+ *
+ * - Defines SPI_SETSCK and SPI_CLRSCK to set and clear the SCK signal
+ * - Defines SPI_SETMOSI and SPI_CLRMOSI to set and clear the MISO signal
+ * - Defines SPI_GETMISO to sample the MISO state
+ * - Defines SPI_PERBIT_NSEC which is the minimum time to transfer one bit.
+ * This determines the maximum frequency.
+ * - Other configuration options:
+ * SPI_BITBAND_LOOPSPERMSEC - Delay loop calibration
+ * SPI_BITBANG_DISABLEMODE0 - Define to disable Mode 0 support
+ * SPI_BITBANG_DISABLEMODE1 - Define to disable Mode 1 support
+ * SPI_BITBANG_DISABLEMODE2 - Define to disable Mode 2 support
+ * SPI_BITBANG_DISABLEMODE3 - Define to disable Mode 3 support
+ * - Provide implementations of spi_select(), spi_status(), and spi_cmddata().
+ * - Then include this file
+ * - Provide an initialization function that initializes the GPIO pins used
+ * in the bit bang interface and calls spi_create_bitbang().
+ */
+
+/* Debug ********************************************************************/
+/* Check if SPI debut is enabled (non-standard.. no support in
+ * include/debug.h
+ */
+
+#ifndef CONFIG_DEBUG
+#undef CONFIG_DEBUG_VERBOSE
+#undef CONFIG_DEBUG_SPI
+#endif
+
+#ifdef CONFIG_DEBUG_SPI
+#define spidbg lldbg
+#ifdef CONFIG_DEBUG_VERBOSE
+#define spivdbg lldbg
+#else
+#define spivdbg(x...)
+#endif
+#else
+#define spidbg(x...)
+#define spivdbg(x...)
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static void spi_delay(uint32_t holdtime);
+static void spi_select(FAR struct spi_bitbang_s *priv, enum spi_dev_e devid, bool selected);
+static uint32_t spi_setfrequency(FAR struct spi_bitbang_s *priv, uint32_t frequency);
+static void spi_setmode(FAR struct spi_bitbang_s *priv, enum spi_mode_e mode);
+#ifndef SPI_BITBANG_DISABLEMODE0
+static uint16_t spi_bitexchange0(uint16_t dataout, uint32_t holdtime);
+#endif
+#ifndef SPI_BITBANG_DISABLEMODE1
+static uint16_t spi_bitexchange1(uint16_t dataout, uint32_t holdtime);
+#endif
+#ifndef SPI_BITBANG_DISABLEMODE2
+static uint16_t spi_bitexchange2(uint16_t dataout, uint32_t holdtime);
+#endif
+#ifndef SPI_BITBANG_DISABLEMODE3
+static uint16_t spi_bitexchange3(uint16_t dataout, uint32_t holdtime);
+#endif
+static uint16_t spi_exchange(FAR struct spi_bitbang_s *priv, uint16_t dataout);
+static uint8_t spi_status(FAR struct spi_bitbang_s *priv, enum spi_dev_e devid);
+#ifdef CONFIG_SPI_CMDDATA
+static int spi_cmddata(FAR struct spi_bitbang_s *priv, enum spi_dev_e devid, bool cmd);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct spi_bitbang_ops_s g_spiops = {
+ spi_select, /* select */
+ spi_setfrequency, /* setfrequency */
+ spi_setmode, /* setmode */
+ spi_exchange, /* exchange */
+ spi_status, /* status */
+#ifdef CONFIG_SPI_CMDDATA
+ spi_cmddata, /* cmddata */
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+/****************************************************************************
+ * Name: spi_delay
+ *
+ * Description:
+ * Delay for a specified number of loops
+ *
+ * Input Parameters:
+ * count - The number of loops
+ *
+ * Returned Value:
+ * Returns the actual frequency selected
+ *
+ ****************************************************************************/
+
+static void spi_delay(uint32_t holdtime)
+{
+ volatile int i;
+
+ for (i = 0; i < holdtime; i++) ;
+}
+
+/****************************************************************************
+ * 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(FAR struct spi_bitbang_s *priv, uint32_t frequency)
+{
+ uint32_t pnsec;
+
+ /* SPI frequency cannot be precisely controlled with a bit-bang interface.
+ * Freqency corresponds to delay in toggle the SPI clock line: Set high,
+ * wait, set low, wait, set high, wait, etc.
+ *
+ * Here we calcalute the half period of the frequency in nanoseconds (i.e.,
+ * the amount of time that the clock should remain in the high or low state).
+ *
+ * frequency = psec / 1 sec psec = full period in seconds
+ * psec = 1 sec / frequency
+ * pnsec = 1000000000 nsec / (2 * frequency) pnsec = full period in nsec
+ *
+ * As examples:
+ * 1) frequency = 400KHz; SPI_PERBIT_NSEC = 100
+ * pnsec = (2500 - 100) / 2 = 1200
+ * 2) frequency = 20MHz; SPI_PERBIT_NSEC = 100
+ * pnsec = (50 - 100( / 2 -> 0
+ */
+
+ pnsec = (1000000000ul + (frequency >> 1)) / frequency;
+
+ /* Minus the bit transfer overhead */
+
+ if (pnsec > SPI_PERBIT_NSEC) {
+ pnsec -= SPI_PERBIT_NSEC;
+ } else {
+ pnsec = 0;
+ }
+
+ /* The hold time in nanoseconds is then half this */
+
+ pnsec = (pnsec + 1) >> 1;
+
+ /* But what we really want is the hold time in loop counts. We know that
+ * SPI_BITBAND_LOOPSPERMSEC is the number of times through a delay loop
+ * to get 1 millisecond.
+ *
+ * SPI_BITBAND_LOOPSPERMSEC / 1000000 is then the number of counts
+ * to get 1 nanosecond. In reality, this is a number less than zero. But
+ * then we can use this to calculate:
+ *
+ * holdtime loops/hold = pnsec nsec/hold * (SPI_BITBAND_LOOPSPERMSEC / 1000000) loops/nsec
+ *
+ * As examples:
+ * 1) frequency = 400KHz; SPI_PERBIT_NSEC = 100; pnsec = 1200;
+ * SPI_BITBAND_LOOPSPERMSEC = 5000
+ * holdtime = (1200 * 5000 + 500000) / 1000000 = 6
+ * 2) frequency = 20MHz; SPI_PERBIT_NSEC = 100; pnsec = 0;
+ * SPI_BITBAND_LOOPSPERMSEC = 5000
+ * holdtime = (0 * 5000 + 500000) / 1000000 = 0
+ */
+
+ priv->holdtime = (pnsec * SPI_BITBAND_LOOPSPERMSEC + 500000) / 1000000;
+
+ /* Let's do our best to calculate the actual frequency
+ *
+ * As examples:
+ * 1) frequency = 400KHz; SPI_PERBIT_NSEC = 100;
+ * SPI_BITBAND_LOOPSPERMSEC = 5000; holdtime = 6
+ * pnsec = 2 * 1000000 * 6 / 5000 + 100 = 2500 nsec
+ * frequency = 400KHz
+ * 2) frequency = 20MHz; SPI_PERBIT_NSEC = 100; holdtime = 0
+ * SPI_BITBAND_LOOPSPERMSEC = 5000; holdtime = 0
+ * pnsec = 2 * 0 * 6 / 5000 + 100 = 100 nsec
+ * frequency = 10MHz
+ */
+
+ pnsec = 2 * 1000000 * priv->holdtime / SPI_BITBAND_LOOPSPERMSEC + SPI_PERBIT_NSEC;
+ frequency = 1000000000ul / pnsec;
+ return frequency;
+}
+
+/****************************************************************************
+ * Name: spi_setmode
+ *
+ * Description:
+ * Select the current SPI mode
+ *
+ * Input Parameters:
+ * dev - Device-specific state data
+ * mode - the new SPI mode
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+static void spi_setmode(FAR struct spi_bitbang_s *priv, enum spi_mode_e mode)
+{
+ spivdbg("mode=%d\n", mode);
+
+ switch (mode) {
+ case SPIDEV_MODE0: /* CPOL=0; CPHA=0 */
+#ifndef SPI_BITBANG_DISABLEMODE0
+ SPI_CLRSCK; /* Resting level of the clock is low */
+ priv->exchange = spi_bitexchange0;
+#else
+ DEBUGPANIC();
+#endif
+ break;
+
+ case SPIDEV_MODE1: /* CPOL=0; CPHA=1 */
+#ifndef SPI_BITBANG_DISABLEMODE1
+ SPI_CLRSCK; /* Resting level of the clock is low */
+ priv->exchange = spi_bitexchange1;
+#else
+ DEBUGPANIC();
+#endif
+ break;
+
+ case SPIDEV_MODE2: /* CPOL=1; CPHA=0 */
+#ifndef SPI_BITBANG_DISABLEMODE2
+ SPI_SETSCK; /* Resting level of the clock is high */
+ priv->exchange = spi_bitexchange2;
+#else
+ DEBUGPANIC();
+#endif
+ break;
+
+ case SPIDEV_MODE3: /* CPOL=1; CPHA=1 */
+#ifndef SPI_BITBANG_DISABLEMODE3
+ SPI_SETSCK; /* Resting level of the clock is high */
+ priv->exchange = spi_bitexchange3;
+#else
+ DEBUGPANIC();
+#endif
+ break;
+
+ default:
+ DEBUGPANIC();
+ break;
+ }
+}
+
+/****************************************************************************
+ * Name: spi_bitexchange0
+ *
+ * Description:
+ * Exchange one bit in mode 0
+ *
+ * MODE 0: CPOL=0 and CPHA = 0
+ * The base value of the clock is zero. Data is captured on the clock's
+ * rising edge and data is propagated on the falling edge (high->low
+ * transition).
+ *
+ * /CS --+
+ * |
+ * +-----------------------------------------------------------------
+ * <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
+ * +------------+ +------------+
+ * | | | |
+ * SCLK ---------------+ +------------+ +------------
+ * `- Set MOSI | `- Set MOSI | `- Set MOSI
+ * `- Sample MISO `- Sample MISO
+ *
+ * MISO /-------------X-----------\/------------X-------------\/-----------
+ * MOSO \-------------X-----------/\------------X-------------/\-----------
+ *
+ * Input Parameters:
+ * dev - Device-specific state data
+ * lock - true: Lock spi bus, false: unlock SPI bus
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifndef SPI_BITBANG_DISABLEMODE0
+static uint16_t spi_bitexchange0(uint16_t dataout, uint32_t holdtime)
+{
+ uint16_t datain;
+
+ /* Here the clock is is in the resting set (low). Set MOSI output and wait
+ * for the hold time
+ */
+
+ if (dataout != 0) {
+ SPI_SETMOSI;
+ } else {
+ SPI_CLRMOSI;
+ }
+
+ spi_delay(holdtime);
+
+ /* Set the clock high and sample MISO */
+
+ SPI_SETSCK;
+ datain = (uint16_t)SPI_GETMISO;
+
+ /* Wait the required amount of hold time then put the clock back in the
+ * resting state.
+ */
+
+ spi_delay(holdtime);
+ SPI_CLRSCK;
+
+ return datain;
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_bitexchange1
+ *
+ * Description:
+ * Exchange one bit in mode 1
+ *
+ * MODE 1: CPOL=0 and CPHA = 1
+ * The base value of the clock is zero. Data is captured on the clock's
+ * falling edge and data is propagated on the rising edge
+ *
+ * /CS --+
+ * |
+ * +-----------------------------------------------------------------
+ * <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
+ * +------------+ +------------+
+ * | | | |
+ * SCLK -+ +------------+ +------------
+ * `- Set MOSI | `- Set MOSI |
+ * `- Sample MISO `- Sample MISO
+ *
+ * MISO /-----------X------------\/-----------X-------------\/------------
+ * MOSO \-----------X------------/\-----------X-------------/\------------
+ *
+ * Input Parameters:
+ * dev - Device-specific state data
+ * lock - true: Lock spi bus, false: unlock SPI bus
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifndef SPI_BITBANG_DISABLEMODE1
+static uint16_t spi_bitexchange1(uint16_t dataout, uint32_t holdtime)
+{
+ uint16_t datain;
+
+ /* The clock should be in the resting state (low). Set the clock to the
+ * high state and set MOSI.
+ */
+
+ SPI_SETSCK;
+ if (dataout != 0) {
+ SPI_SETMOSI;
+ } else {
+ SPI_CLRMOSI;
+ }
+
+ /* Wait for the required hold time then put the clock back into the
+ * resting (low) state.
+ */
+
+ spi_delay(holdtime);
+ SPI_CLRSCK;
+
+ /* Sample MISO on the falling edge and wait for the hold time again */
+
+ datain = (uint16_t)SPI_GETMISO;
+ spi_delay(holdtime);
+
+ return datain;
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_bitexchange2
+ *
+ * Description:
+ * Exchange one bit in mode 2
+ *
+ * MODE 2: CPOL=1 and CPHA = 0
+ * The base value of the clock is one. Data is captured on the clock's
+ * falling edge and data is propagated on the rising edge.
+ *
+ * /CS --+
+ * |
+ * +-----------------------------------------------------------------
+ * <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
+ * ---------------+ +------------+ +------------
+ * | | | |
+ * SCLK +------------+ +------------+
+ * `- Set MOSI | `- Set MOSI | `- Set MOSI
+ * `- Sample MISO `- Sample MISO
+ *
+ * MISO /-------------X------------\/-----------X-------------\/-----------
+ * MOSO \-------------X------------/\-----------X-------------/\-----------
+ *
+ * Input Parameters:
+ * dev - Device-specific state data
+ * lock - true: Lock spi bus, false: unlock SPI bus
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifndef SPI_BITBANG_DISABLEMODE2
+static uint16_t spi_bitexchange2(uint16_t dataout, uint32_t holdtime)
+{
+ uint16_t datain;
+
+ /* Here the clock is is in the resting set (high). Set MOSI output and wait
+ * for the hold time
+ */
+
+ if (dataout != 0) {
+ SPI_SETMOSI;
+ } else {
+ SPI_CLRMOSI;
+ }
+
+ spi_delay(holdtime);
+
+ /* Set the clock low and sample MISO */
+
+ SPI_CLRSCK;
+ datain = (uint16_t)SPI_GETMISO;
+
+ /* Wait the required amount of hold time then put the clock back in the
+ * resting state (high).
+ */
+
+ spi_delay(holdtime);
+ SPI_SETSCK;
+
+ return datain;
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_bitexchange3
+ *
+ * Description:
+ * Exchange one bit in mode 3
+ *
+ * MODE 3: CPOL=1 and CPHA = 1
+ * The base value of the clock is one. Data is captured on the clock's
+ * rising edge and data is propagated on the falling edge.
+ *
+ * /CS --+
+ * |
+ * +-----------------------------------------------------------------
+ * <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
+ * -+ +------------+ +------------
+ * | | | |
+ * SCLK +------------+ +------------+
+ * ` Set MOSI | `- Set MOSI |
+ * `- Sample MISO `- Sample MISO
+ *
+ * MISO /-----------X------------\/-----------X-------------\/------------
+ * MOSO \-----------X------------/\-----------X-------------/\------------
+ *
+ * Input Parameters:
+ * dev - Device-specific state data
+ * lock - true: Lock spi bus, false: unlock SPI bus
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifndef SPI_BITBANG_DISABLEMODE3
+static uint16_t spi_bitexchange3(uint16_t dataout, uint32_t holdtime)
+{
+ uint16_t datain;
+
+ /* The clock should be in the resting state (high). Set the clock to the
+ * low state and set MOSI.
+ */
+
+ SPI_CLRSCK; /* Clock transition before setting MOSI */
+ if (dataout != 0) {
+ SPI_SETMOSI; /* Set MOSI if the bit is set */
+ } else {
+ SPI_CLRMOSI; /* Clear MOSI if the bit is not set */
+ }
+
+ /* Wait for the required hold time then put the clock back into the
+ * resting (high) state.
+ */
+
+ spi_delay(holdtime);
+ SPI_SETSCK;
+
+ /* Sample MISO on the rising edge and wait for the hold time again */
+
+ datain = (uint16_t)SPI_GETMISO;
+ spi_delay(holdtime);
+
+ return datain;
+}
+#endif
+
+/****************************************************************************
+ * Name: spi_exchange
+ *
+ * Description:
+ * Exahange on word of data on SPI
+ *
+ * Input Parameters:
+ * priv - Device-specific state data
+ * data - The TX data to be exchanged with the slave
+ *
+ * Returned Value:
+ * The RX data received from the slave
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_SPI_BITBANG_VARWIDTH
+static uint16_t spi_exchange(FAR struct spi_bitbang_s *priv, uint16_t dataout)
+{
+ bitexchange_t exchange = priv->exchange;
+ uint32_t holdtime = priv->holdtime;
+ uint16_t datain;
+ uint16_t bit;
+ int shift;
+
+ /* Transfer each bit. This might be better done with straight-line
+ * logic because the loop overhead will limit our maximum transfer
+ * rate.
+ */
+
+ shift = priv->nbits - 1 for (bit = 1 << shift; bit != 0; bit >>= 1) {
+ /* Shift to make space for the next, less significant bit.
+ * Then exchange bits with the slave an OR in the new, returned
+ * bit.
+ */
+
+ datain <<= 1;
+ datain |= exchange(dataout & bit, holdtime);
+ }
+
+ return datain;
+}
+
+#else
+static uint16_t spi_exchange(FAR struct spi_bitbang_s *priv, uint16_t dataout)
+{
+ bitexchange_t exchange = priv->exchange;
+ uint32_t holdtime = priv->holdtime;
+ uint8_t datain;
+
+ /* Transfer each bit. This is better done with straight-line logic
+ * when possible because the loop overhead will limit our maximum transfer
+ * rate.
+ */
+
+ /* Exchange bit 7 with the slave */
+
+ datain = priv->exchange(dataout & (1 << 7), holdtime);
+
+ /* Exchange bit 6 with the slave */
+
+ datain <<= 1;
+ datain |= priv->exchange(dataout & (1 << 6), holdtime);
+
+ /* Exchange bit 5 with the slave */
+
+ datain <<= 1;
+ datain |= priv->exchange(dataout & (1 << 5), holdtime);
+
+ /* Exchange bit 4 with the slave */
+
+ datain <<= 1;
+ datain |= priv->exchange(dataout & (1 << 4), holdtime);
+
+ /* Exchange bit 3 with the slave */
+
+ datain <<= 1;
+ datain |= priv->exchange(dataout & (1 << 3), holdtime);
+
+ /* Exchange bit 2 with the slave */
+
+ datain <<= 1;
+ datain |= priv->exchange(dataout & (1 << 2), holdtime);
+
+ /* Exchange bit 1 with the slave */
+
+ datain <<= 1;
+ datain |= priv->exchange(dataout & (1 << 1), holdtime);
+
+ /* Exchange bit 0 with the slave */
+
+ datain <<= 1;
+ datain |= priv->exchange(dataout & (1 << 0), holdtime);
+
+ return datain;
+}
+#endif