[RFC,04/25] spi: gpio: Implement LSB First bitbang support
diff mbox series

Message ID 20191212033952.5967-5-afaerber@suse.de
State New, archived
Headers show
Series
  • arm64: realtek: Add Xnano X5 and implement TM1628/FD628/AiP1618 LED controllers
Related show

Commit Message

Andreas Färber Dec. 12, 2019, 3:39 a.m. UTC
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.
Make checkpatch.pl happy by changing "unsigned" to "unsigned int".

Conditionally call them from all the spi-gpio txrx_word callbacks.

Signed-off-by: Andreas Färber <afaerber@suse.de>
---
 drivers/spi/spi-bitbang-txrx.h | 68 ++++++++++++++++++++++++++++++++++++++++--
 drivers/spi/spi-gpio.c         | 42 ++++++++++++++++++++------
 2 files changed, 99 insertions(+), 11 deletions(-)

Comments

Geert Uytterhoeven Dec. 12, 2019, 8:40 a.m. UTC | #1
Hi Andreas,

On Thu, Dec 12, 2019 at 4:41 AM Andreas Färber <afaerber@suse.de> wrote:
> 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.
> Make checkpatch.pl happy by changing "unsigned" to "unsigned int".
>
> Conditionally call them from all the spi-gpio txrx_word callbacks.
>
> Signed-off-by: Andreas Färber <afaerber@suse.de>

Thanks for your patch!

> --- a/drivers/spi/spi-gpio.c
> +++ b/drivers/spi/spi-gpio.c
> @@ -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);
>  }

Duplicating all functions sounds a bit wasteful to me.

What about reverting the word first, and calling the normal functions?

    if (unlikely(spi->mode & SPI_LSB_FIRST)) {
            if (bits <= 8)
                    word = bitrev8(word) >> (bits - 8);
            else if (bits <= 16)
                    word = bitrev16(word) >> (bits - 16);
            else
                    word = bitrev32(word) >> (bits - 32);
    }
    return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);

Gr{oetje,eeting}s,

                        Geert
Andreas Färber Dec. 12, 2019, 3:14 p.m. UTC | #2
Hi Geert,

Am 12.12.19 um 09:40 schrieb Geert Uytterhoeven:
> On Thu, Dec 12, 2019 at 4:41 AM Andreas Färber <afaerber@suse.de> wrote:
>> 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.
>> Make checkpatch.pl happy by changing "unsigned" to "unsigned int".
>>
>> Conditionally call them from all the spi-gpio txrx_word callbacks.
>>
>> Signed-off-by: Andreas Färber <afaerber@suse.de>
> 
> Thanks for your patch!

NP. I prefer fixing issues at the source over awkward workarounds. :)

>> --- a/drivers/spi/spi-gpio.c
>> +++ b/drivers/spi/spi-gpio.c
>> @@ -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);
>>  }
> 
> Duplicating all functions sounds a bit wasteful to me.

Two functions duplicated, eight function calls duplicated.

> What about reverting the word first, and calling the normal functions?
> 
>     if (unlikely(spi->mode & SPI_LSB_FIRST)) {
>             if (bits <= 8)
>                     word = bitrev8(word) >> (bits - 8);
>             else if (bits <= 16)
>                     word = bitrev16(word) >> (bits - 16);
>             else
>                     word = bitrev32(word) >> (bits - 32);
>     }
>     return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);

Hm, wasn't aware of those helpers, so I opted not to loop over the bits
for reversing myself, as Thomas Gleixner disliked bit loops in irqchip.
Performance appeared to be a concern here, too.

Eight functions duplicated then. I don't like that - at least we should
pack that into an inline helper or macro, to not copy&paste even more
lines around. Who knows, maybe we'll get 64-bit support at one point?

Do you think it would be acceptable to instead encapsulate this inside
the _be inline helpers? That would lead the name ad absurdum, of course,
but we would then need to do it only twice, not eight times.

However, either way would seem to make the LSB code path slower than MSB
due to the prepended reversal.

Delays are already stubbed out, with open TODOs for further inlining
functions that are being dispatched today.

So from that angle I don't see a better way than either duplicating the
functions or using some macro magic to #include the header twice. If we
wanted to go down that path, we could probably de-duplicate the existing
two functions, too, but I was trying to err on the cautious side, since
I don't have setups to test all four code paths myself (and a ton of
more relevant but less fun patches to flush out ;)).

Regards,
Andreas
Mark Brown Dec. 12, 2019, 5:19 p.m. UTC | #3
On Thu, Dec 12, 2019 at 04:14:59PM +0100, Andreas Färber wrote:
> Am 12.12.19 um 09:40 schrieb Geert Uytterhoeven:
> > On Thu, Dec 12, 2019 at 4:41 AM Andreas Färber <afaerber@suse.de> wrote:
> >> 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.
> >> Make checkpatch.pl happy by changing "unsigned" to "unsigned int".

Separate patch for this?

> So from that angle I don't see a better way than either duplicating the
> functions or using some macro magic to #include the header twice. If we
> wanted to go down that path, we could probably de-duplicate the existing
> two functions, too, but I was trying to err on the cautious side, since
> I don't have setups to test all four code paths myself (and a ton of
> more relevant but less fun patches to flush out ;)).

Yeah, I don't think there's any great options here with the potential
performance issues - probably the nicest thing would be to autogenerate
lots of variants but I think that's far more trouble than it's worth.
Andreas Färber Dec. 12, 2019, 9:08 p.m. UTC | #4
Am 12.12.19 um 18:19 schrieb Mark Brown:
> On Thu, Dec 12, 2019 at 04:14:59PM +0100, Andreas Färber wrote:
>> Am 12.12.19 um 09:40 schrieb Geert Uytterhoeven:
>>> On Thu, Dec 12, 2019 at 4:41 AM Andreas Färber <afaerber@suse.de> wrote:
>>>> 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.
>>>> Make checkpatch.pl happy by changing "unsigned" to "unsigned int".
> 
> Separate patch for this?

For the checkpatch cleanup? Or helpers preparation vs. spi-gpio.c usage?

>> So from that angle I don't see a better way than either duplicating the
>> functions or using some macro magic to #include the header twice. If we
>> wanted to go down that path, we could probably de-duplicate the existing
>> two functions, too, but I was trying to err on the cautious side, since
>> I don't have setups to test all four code paths myself (and a ton of
>> more relevant but less fun patches to flush out ;)).
> 
> Yeah, I don't think there's any great options here with the potential
> performance issues - probably the nicest thing would be to autogenerate
> lots of variants but I think that's far more trouble than it's worth.

Maybe add another code comment to revisit that idea later then?

Thanks,
Andreas
Mark Brown Dec. 13, 2019, 11:42 a.m. UTC | #5
On Thu, Dec 12, 2019 at 10:08:24PM +0100, Andreas Färber wrote:
> Am 12.12.19 um 18:19 schrieb Mark Brown:

> >>>> Duplicate the inline helpers bitbang_txrx_be_cpha{0,1} as LE versions.
> >>>> Make checkpatch.pl happy by changing "unsigned" to "unsigned int".

> > Separate patch for this?

> For the checkpatch cleanup? Or helpers preparation vs. spi-gpio.c usage?

At least the checkpatch cleanup.

> > Yeah, I don't think there's any great options here with the potential
> > performance issues - probably the nicest thing would be to autogenerate
> > lots of variants but I think that's far more trouble than it's worth.

> Maybe add another code comment to revisit that idea later then?

Sure, if you like.

Patch
diff mbox series

diff --git a/drivers/spi/spi-bitbang-txrx.h b/drivers/spi/spi-bitbang-txrx.h
index ae61d72c7d28..999a89325325 100644
--- a/drivers/spi/spi-bitbang-txrx.h
+++ b/drivers/spi/spi-bitbang-txrx.h
@@ -45,7 +45,7 @@ 
 
 static inline u32
 bitbang_txrx_be_cpha0(struct spi_device *spi,
-		unsigned nsecs, unsigned cpol, unsigned flags,
+		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 */
@@ -77,7 +77,7 @@  bitbang_txrx_be_cpha0(struct spi_device *spi,
 
 static inline u32
 bitbang_txrx_be_cpha1(struct spi_device *spi,
-		unsigned nsecs, unsigned cpol, unsigned flags,
+		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 */
@@ -106,3 +106,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;
+}
diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c
index 7ceb0ba27b75..493723eda844 100644
--- a/drivers/spi/spi-gpio.c
+++ b/drivers/spi/spi-gpio.c
@@ -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);
 }
 
 /*----------------------------------------------------------------------*/
@@ -389,7 +413,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
 		 *