spi: gpio: Implement LSB First bitbang support
authorAndreas Färber <afaerber@suse.de>
Sat, 19 Feb 2022 13:15:48 +0000 (14:15 +0100)
committerMark Brown <broonie@kernel.org>
Tue, 22 Feb 2022 11:56:41 +0000 (11:56 +0000)
Add support for slave DT property spi-lsb-first, i.e., SPI_LSB_FIRST mode.
Duplicate the inline helpers bitbang_txrx_be_cpha{0,1} as LE versions.
Conditionally call them from all the spi-gpio txrx_word callbacks.

Some alternatives to this implementation approach were discussed back
then [0], but eventually it was considered reasonable.

[0] https://lore.kernel.org/linux-arm-kernel/20191212033952.5967-8-afaerber@suse.de/

Signed-off-by: Andreas Färber <afaerber@suse.de>
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
Tested-by: Christian Hewitt <christianshewitt@gmail.com>
Link: https://lore.kernel.org/r/feac3377-4ad1-77d8-9a18-3588d80fb909@gmail.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/spi-bitbang-txrx.h
drivers/spi/spi-gpio.c

index ae61d72c7d281091270b59d6c897cb95158839d3..267342dfa73880d3db4f1573f8a79ba32436e728 100644 (file)
@@ -41,6 +41,8 @@
  * chips need ... there may be several reasons you'd need to tweak timings
  * in these routines, not just to make it faster or slower to match a
  * particular CPU clock rate.
+ *
+ * ToDo: Maybe the bitrev macros can be used to improve the code?
  */
 
 static inline u32
@@ -106,3 +108,67 @@ bitbang_txrx_be_cpha1(struct spi_device *spi,
        }
        return word;
 }
+
+static inline u32
+bitbang_txrx_le_cpha0(struct spi_device *spi,
+               unsigned int nsecs, unsigned int cpol, unsigned int flags,
+               u32 word, u8 bits)
+{
+       /* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */
+
+       u32 oldbit = !(word & 1);
+       /* clock starts at inactive polarity */
+       for (; likely(bits); bits--) {
+
+               /* setup LSB (to slave) on trailing edge */
+               if ((flags & SPI_MASTER_NO_TX) == 0) {
+                       if ((word & 1) != oldbit) {
+                               setmosi(spi, word & 1);
+                               oldbit = word & 1;
+                       }
+               }
+               spidelay(nsecs);        /* T(setup) */
+
+               setsck(spi, !cpol);
+               spidelay(nsecs);
+
+               /* sample LSB (from slave) on leading edge */
+               word >>= 1;
+               if ((flags & SPI_MASTER_NO_RX) == 0)
+                       word |= getmiso(spi) << (bits - 1);
+               setsck(spi, cpol);
+       }
+       return word;
+}
+
+static inline u32
+bitbang_txrx_le_cpha1(struct spi_device *spi,
+               unsigned int nsecs, unsigned int cpol, unsigned int flags,
+               u32 word, u8 bits)
+{
+       /* if (cpol == 0) this is SPI_MODE_1; else this is SPI_MODE_3 */
+
+       u32 oldbit = !(word & 1);
+       /* clock starts at inactive polarity */
+       for (; likely(bits); bits--) {
+
+               /* setup LSB (to slave) on leading edge */
+               setsck(spi, !cpol);
+               if ((flags & SPI_MASTER_NO_TX) == 0) {
+                       if ((word & 1) != oldbit) {
+                               setmosi(spi, word & 1);
+                               oldbit = word & 1;
+                       }
+               }
+               spidelay(nsecs); /* T(setup) */
+
+               setsck(spi, cpol);
+               spidelay(nsecs);
+
+               /* sample LSB (from slave) on trailing edge */
+               word >>= 1;
+               if ((flags & SPI_MASTER_NO_RX) == 0)
+                       word |= getmiso(spi) << (bits - 1);
+       }
+       return word;
+}
index 0584f4d2fde29b8afd7145784c2d7c26ced08c8e..4b12c4964a6643bfef95fa02b815162b0811bcd0 100644 (file)
@@ -135,25 +135,37 @@ static inline int getmiso(const struct spi_device *spi)
 static u32 spi_gpio_txrx_word_mode0(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
-       return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha0(spi, nsecs, 0, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
 }
 
 static u32 spi_gpio_txrx_word_mode1(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
-       return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha1(spi, nsecs, 0, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
 }
 
 static u32 spi_gpio_txrx_word_mode2(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
-       return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha0(spi, nsecs, 1, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
 }
 
 static u32 spi_gpio_txrx_word_mode3(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
-       return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha1(spi, nsecs, 1, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
 }
 
 /*
@@ -170,28 +182,40 @@ static u32 spi_gpio_spec_txrx_word_mode0(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
        flags = spi->master->flags;
-       return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha0(spi, nsecs, 0, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
 }
 
 static u32 spi_gpio_spec_txrx_word_mode1(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
        flags = spi->master->flags;
-       return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha1(spi, nsecs, 0, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
 }
 
 static u32 spi_gpio_spec_txrx_word_mode2(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
        flags = spi->master->flags;
-       return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha0(spi, nsecs, 1, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
 }
 
 static u32 spi_gpio_spec_txrx_word_mode3(struct spi_device *spi,
                unsigned nsecs, u32 word, u8 bits, unsigned flags)
 {
        flags = spi->master->flags;
-       return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
+       if (unlikely(spi->mode & SPI_LSB_FIRST))
+               return bitbang_txrx_le_cpha1(spi, nsecs, 1, flags, word, bits);
+       else
+               return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
 }
 
 /*----------------------------------------------------------------------*/
@@ -378,7 +402,7 @@ static int spi_gpio_probe(struct platform_device *pdev)
 
        master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
        master->mode_bits = SPI_3WIRE | SPI_3WIRE_HIZ | SPI_CPHA | SPI_CPOL |
-                           SPI_CS_HIGH;
+                           SPI_CS_HIGH | SPI_LSB_FIRST;
        if (!spi_gpio->mosi) {
                /* HW configuration without MOSI pin
                 *