4

I am writing a kernel module to read and write to SPI device (CC1200). My linux device does not have native SPI, so I am trying to bit-bang the bus.

I found that linux has built-in bitbang code (linux/spi/spi_bitbang.h), but I am confused how to set it up. It needs structs as spi_device and spi_master, each requiring struct device, which requires structs as kobject and many many more, most of them I have no idea what to do with them, and how they are needed for simple bit-banging.

I have looked online for examples, but i found literally none. Not a single use of the included bitbang code, only some references that it is "easy"

I will be very thankful for any help, maybe the bitbang lib is not even the good path. Maybe I can write my own (how to do it efficiently? I have 4 cores, but it is running lots of stuff in the background)

Thanks

Maros Macko
  • 89
  • 1
  • 7
  • How can you even bit-bang SPI from a PC? It's not a real-time system. SPI is very picky with timing. The correct solution is probably to have the PC speak with a MCU. – Lundin Sep 05 '19 at 09:36
  • Not a PC, more like a SoC. I am rewriting the software, hardware is custom, but already "done" - device is on the bus. The old driver is proprietary and does not function anymore (for the needs), so I know someone did it somehow. Also, the SoC will be SPI master, so I control the clock, which should not be problematic for timings. SPI is very flexible with timings as far as I know. Device also does not have minimal frequency needed for comunication. – Maros Macko Sep 05 '19 at 09:43
  • 1
    So it is RTLinux or some such? And how "picky SPI" is rather depends on what you are communicating with. ADC, custom IC, memories, displays, radio chips, MEMS etc all tend to be picky with timing. You can't send 4 bits of the clock and then come back an age later to send the rest. Unless the slave is a dumb shift register, then it isn't sensitive. – Lundin Sep 05 '19 at 11:31
  • It is armv7l Linux 4.4.35 on a HiSilicon SoC. I am comunicating with a generic 868MHz RF chip CC1200, which I want to set up to only catch wmbus (one way) packets and then read them from userspace program. I have compiled kernel module for this chip, but it uses different wireless protocol on the 868 band, and it is closed source, so I have to redo it whole, and I am not having easy time finding any examples online. – Maros Macko Sep 05 '19 at 11:49
  • 1
    So it some sort of PC still. I doubt your radio chip will be happy if you break off SPI transmission to go chew context switches for an age or two. It all depends on your real-time requirements and the timing requirements of the chip. At any rate, bit-banging is always a bad idea, even when you have a proper MCU. – Lundin Sep 05 '19 at 11:59
  • I know it is not ideal, but I really don't have other options. The CPU has 4 cores, so I think I can use precise timers to get the job done. Also as I mentioned, the datasheet for device does not specify minimal frequency, or max t(h) / t(l), so I can have it slow. CPU is fast, so few microseconds delay should not be a problem. I hope... Well, I have to work with what I have. – Maros Macko Sep 05 '19 at 12:26
  • It doesn't matter if it has 99 cores, you still won't get real-time characteristics. My experience of working with exactly these kind of radio chips is that they love to design their own "non-standard SPI" timing requirements, such as demanding extra pulses between packages and crap like that. I haven't used TI chips (because I like to get customer support...), but most of their competition tend to use their own non-standard timing. – Lundin Sep 05 '19 at 12:51
  • Yes ofcourse, nowhere near real-time, but "good enough" is what I am aiming for :) As I said, it worked before me, so it has to work somehow now. I think I will write my own bitbang code accessing GPIOs directly, because I got nowhere with the official driver. – Maros Macko Sep 05 '19 at 13:20
  • Maybe consider some UART to SPI bridge adapter then? Or in case you are lucky, TI got some reference design evaluation board you can grab, with MSP430 or whatever they fancy + your CC radio. They've had lots of boards like that in the past. – Lundin Sep 05 '19 at 13:27
  • Well, as I said, I am unfortunately not able to. This is already functional working (and some versions even years deployed) product. No hardware changes possible. It works now, but closed-source driver for other wireless protocol. – Maros Macko Sep 05 '19 at 13:37

2 Answers2

2

Because of the nature of SPI where data is clocked and read by the master there is nothing wrong with the bit banging driver for the master, as the slave should not relay on a stable clock. But of course it depends on the slave device if this will work in practice or not.

If you are using the linux kernel there is no need to implement your own bit-banging driver as there already is one spi-gpio.c

My guess how to get it up and running would be by defining what GPIO pins to use in the devicetree, then the driver would be able to act as any of the other physical layer drivers.

I had a quick glance at drivers/spi/spi-gpio.c source code, and there is even a short user guide how to directly access the GPIO pins inline without using the generic GPIO layer overhead.

/*
 * Because the overhead of going through four GPIO procedure calls
 * per transferred bit can make performance a problem, this code
 * is set up so that you can use it in either of two ways:
 *
 *   - The slow generic way:  set up platform_data to hold the GPIO
 *     numbers used for MISO/MOSI/SCK, and issue procedure calls for
 *     each of them.  This driver can handle several such busses.
 *
 *   - The quicker inlined way:  only helps with platform GPIO code
 *     that inlines operations for constant GPIOs.  This can give
 *     you tight (fast!) inner loops, but each such bus needs a
 *     new driver.  You'll define a new C file, with Makefile and
 *     Kconfig support; the C code can be a total of six lines:
 *
 *    #define DRIVER_NAME  "myboard_spi2"
 *    #define  SPI_MISO_GPIO  119
 *    #define  SPI_MOSI_GPIO  120
 *    #define  SPI_SCK_GPIO   121
 *    #define  SPI_N_CHIPSEL  4
 *    #include "spi-gpio.c"
 */

PS are you sure your platform does not have spi, all the SoC I have worked with from HiSilicon have had one. I would double check this first

Simson
  • 3,373
  • 2
  • 24
  • 38
  • 1
    I looked at the linux kernel implementation of generic bitbang, but there were too many structs that I had no idea how to initialize, so I ended up making my own subroutines. Ended up pretty reliable, only debug kernel prints seem to be very slow, but despite that the slave device appears to be okay with the jitter and delays between bytes. – Maros Macko Sep 27 '19 at 10:06
  • 1
    Also, I think that my HiSilicon chip has some SPI, but I am working on existing implementation of a product, an my goal is to re-purpose it, so I have to work with what I have, unfortunately. And it is connected to generic GPIO pin. – Maros Macko Sep 27 '19 at 10:09
  • 1
    The main benefit from using one of the standard implementations would be, it is maintained by someone who would hopefully fix security problems and already connected to the rest of the system for instance `/dev/spidev0.0`. If that is not needed a custom implementation you understand is probably faster and safer than a misconfigured generic driver. – Simson Sep 27 '19 at 13:31
  • Ok, so this may be a dumb question / post as I'm a noob when it comes to the "BFM" inside the Linux kernel. How do I get from the `spi-gpio.ko` module (which is loaded and running) to having a device in `/dev`, there is absolutely no documentation anywhere and everyone just says it's easy and to "just use `spi-gpio.c`". Well I've compiled it, inserted it and boom `/sys/class/bus/spi*` is empty and so is `/dev`. What am I missing? – Jeremiah Mar 30 '23 at 09:01
  • I saw and upvoted your question at https://stackoverflow.com/questions/75886347/spi-bitbang-driver-in-linux which has better viability than this comment. It was a long time since I worked with this so I don't have a good answer at hand. – Simson Mar 31 '23 at 01:29
0

EDIT: It seems they've changed the interface and you can't do this any more in modern kernels from 4.19 and later, for the same reason you can't do it with i2c. https://forum.openwrt.org/t/i2c-kernel-4-19-i2c-gpio-custom/49213 I will leave this here for now, perhaps it will be useful. I myself am using older kernels for home automation but it's on a private network.

My original answer:

Whilst you can probably get this working by hacking your own kernel module together instead you can investigate spi-gpio-custom, simply load the module and pass the pins you want to use as parameters, you can do everything at run-time and don't need to 'compile-in' the pins you want to use. You can find this module in the OpenWrt project:

https://github.com/openwrt/openwrt/tree/openwrt-19.07/package/kernel/spi-gpio-custom/src

I would expect that to give you some kind of SPI device when initialised that you can write to from user-space. Note that I haven't used this module myself, but some time ago I've used the i2c counterpart which works in similar way.

Alternatively, you could consider introducing an Arduino to talk to your SPI bus then translate the SPI data into something your Linux device understands.

The Nano board will probably do what you need and costs peanuts if you don't want to make up your own board.

Then for the interface, you would then have a few choices: USB, UART, i2c or 1wire even a parallel interface if you have the pins. There is a 1-wire slave library on github for instance: https://github.com/smurfix/owslave.

Keeely
  • 895
  • 9
  • 21