3
votes

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

2
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
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
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
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
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

2 Answers

2
votes

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

0
votes

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.