Skip to content

General low level SPI device driver

Niklas Ekström edited this page Dec 7, 2022 · 6 revisions

This is a proposal for a low level SPI device driver, whose interface can work with different kinds of SPI hardware adapters.

Note that this is a proposal, and I write it here to get feedback, so that we can converge on something we jointly believe in.

Hardware diagram:

    |--------------------|
    |       Amiga        |
    |--------------------|
              |
|----------------------------|
|        SPI adapter         |
|----------------------------|
       |0             |1
|------------|  |------------|
| SPI device |  | SPI device | <-- e.g. sd card module, ethernet module
|------------|  |------------|

At the hardware level, I make the following assumptions:

  • The hardware adapter has one or many units.
    • Each unit connects to one SPI device.
    • It may be possible to share the MOSI/MISO/SCLK pins between units, as long as there is one slave select (SS) pin for each unit.
    • The units are numbered 0, .., N-1.
  • It is assumed that each unit has some number of GPIO pins.
    • Often some GPIO pins are needed for handshaking or out-of-band communication (such as the card detect pin on an sd card SPI module).
    • The number of GPIO pins per unit should be between 0 and 8, and the number of pins should be possible to read using the driver.
    • GPIO pins could be configured as inputs or outputs. The hardware/driver may limit that certain GPIO pins can only be input or output, and that should be possible to find out from a call to the driver.
    • It should be possible to configure input pins to generate interrupts. An interrupt can be set to trigger at one of the following conditions (limitations may apply): LEVEL_HIGH, LEVEL_LOW, EDGE_RISE, EDGE_FALL. Again, the driver/hardware may limit which pins can be used for interrupts, and what trigger events are supported, and this can be found out from the driver.

A device driver, here referred to as spi.device, is written for each hardware design. This driver has a uniform interface that abstracts the implementation details of the hardware.

For each kind of SPI device, a higher level device driver is written. Higher level drivers can be written without knowledge of which specific SPI adapter will be used. The device specific driver will open the lower level spi.device driver, and call its routines in order to communicate with the SPI device. As an example for this discussion, let spisd.device be the name of a higher level device driver that adds support for a SPI sd card.

Software diagram:

  |----------|    |--------------|
  | fat95 FS |    | TCP/IP stack |
  |----------|    |--------------|
       |                 |
|--------------|  |---------------|
| spisd.device |  | spieth.device |
|--------------|  |---------------|
         |             |
     |--------------------|
     |     spi.device     |
     |--------------------|

The API functions and commands defined by spi.device are:

  • Open(unit, ioRequest)
  • Close(ioRequest)
  • BeginIO(ioRequest) - Request the execution of a command. The commands are:
    • CMD_GET_CAPS - Return a structure with the capabilities of the unit, e.g., min/max baud rate, shared/individual SPI lines.
    • CMD_SET_MODE - Set the mode that determines SPI phase and polarity.
    • CMD_SET_BAUDRATE - Set the baudrate to the closest rate supported by the hardware, no higher than the rate specified, and return the actual rate that was set.
    • CMD_ACQUIRE_SPI - Acquire the SPI lines (MOSI/MISO/SCLK) for this unit. If the SPI lines are not shared between units then multiple units may acquire their SPI lines concurrently.
    • CMD_RELINQUISH_SPI - Relinquish the SPI lines.
    • CMD_SLAVE_SELECT - Assert or negate the slave select pin.
    • CMD_READ - Perform a SPI read transfer.
    • CMD_WRITE - Perform a SPI write transfer.
  • AbortIO(ioRequest)

The GPIO functionality is accessed using the following functions:

  • GpioGetCapabilities(ioRequest, gpioCaps) - Return a structure with capabilities of the unit, e.g., which pins can be outputs, inputs, irq, and which events can trigger interrupts (per pin).
  • GpioSetDir(ioRequest, pin, value) - Set the direction of a pin.
  • GpioSetDirMasked(ioRequest, mask, value) - Set the direction of masked pins.
  • GpioPut(ioRequest, pin, value) - Set the value of an output pin.
  • GpioPutMasked(ioRequest, mask, value) - Set the values of masked output pins.
  • GpioGet(ioRequest, pin) - Read an input pin.
  • GpioGetAll(ioRequest) - Read all input pins.
  • GpioSetIrqEnabled(ioRequest, pin, eventMask, enabled) - Enable/disable certain events for input pin.
  • GpioSetIrqEventsMasked(ioRequest, mask, events) - Set IRQ events for masked input pins.
  • GpioSetIrqCallback(ioRequest, intServer) - Setup an interrupt server routine.

I think this is mostly it, but I may have forgotten something.

I will create a branch in this repository to attempt to modify the software to use this design, and will update this text once that is done and I have evaluated if it worked out well.

Feel free to modify the text in this wiki, I have set it as publicly modifiable.

Clone this wiki locally