1

I'd like to write my own SPI driver to configure the SPI interface by means of writing to the CS, FIFO, and CLK SPI registers. I have disabled the SPI interface in raspi-config.

I followed the documentation to get the registers' addresses, and know which bits to set. I want to use polling mode, so my CS bits look like this: 0x00040084. The important bits here are TA=1, CPOL=0 and CPHA=1.

I set CLK=0x00000010 so that it divides the core clock by 2.

I seem to have no issues when mapping the physical addresses to kernel space with ioremap. However, once I start writing to the FIFO register, I don't see the SCLK being active on an oscilloscope. Furthermore, when polling for DONE until it goes to 1, it never does. Eventually I just set TA=0.

For reference, I tried testing the SPI interface with an oscilloscope before by running spidev_test.c as suggested in the official website. There I could clearly see the SCLK, MOSI, and MISO signals.

What am I missing to get the SPI interface up and running?

Here is the kernel module.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>

#define DRIVER_AUTHOR "Moore Machine"
#define DRIVER_DESC "A basic SPI driver"

#define SPI0_BASE_ADDR 0x7e204000
#define SPI_REG_SIZE 32  // size in bits

/* SPI register offsets */
#define BCM2835_SPI_CS          0x00
#define BCM2835_SPI_FIFO        0x04
#define BCM2835_SPI_CLK         0x08
#define BCM2835_SPI_DLEN        0x0c
#define BCM2835_SPI_LTOH        0x10
#define BCM2835_SPI_DC          0x14

#define DEBUG 1

static int *ctrl_status, *fifo, *clk, *dlen, *lossi_toh, *dma_ctrl;

unsigned int cs_bits = 0x00040084;
unsigned int clk_bits = 0x00000010;

int8_t fifo_msg[] = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

static int __init spimod_init(void)
{
    int i;
    int is_done;

    printk(KERN_INFO " Initializing Simple SPI Test\n");

    /*Map physical memory into virtual address space of the kernel*/
    ctrl_status = ioremap(SPI0_BASE_ADDR+BCM2835_SPI_CS, SPI_REG_SIZE);
    if (!ctrl_status) {
        printk(KERN_ERR " Could not remap CS Register.\n");
        goto cs_error;
    }

    fifo = ioremap(SPI0_BASE_ADDR+BCM2835_SPI_FIFO, SPI_REG_SIZE);
    if (!fifo) {
        printk(KERN_ERR " Could not remap FIFO Register.\n");
        goto fifo_error;
    }   

    clk = ioremap_nocache(SPI0_BASE_ADDR+BCM2835_SPI_CLK, SPI_REG_SIZE);
    if (!clk) {
        printk(KERN_ERR " Could not remap CLK Register.\n");
        goto clk_error;
    }

    dlen = ioremap(SPI0_BASE_ADDR+BCM2835_SPI_DLEN, SPI_REG_SIZE);
    if (!dlen) {
        printk(KERN_ERR " Could not remap DLEN Register.\n");
        goto dlen_error;
    }

    lossi_toh = ioremap(SPI0_BASE_ADDR+BCM2835_SPI_LTOH, SPI_REG_SIZE);
    if (!lossi_toh) {
        printk(KERN_ERR " Could not remap LTOH Register.\n");
        goto ltoh_error;
    }

    dma_ctrl = ioremap(SPI0_BASE_ADDR+BCM2835_SPI_DC, SPI_REG_SIZE);
    if (!dma_ctrl) {
        printk(KERN_ERR " Could not remap DC Register.\n");
        goto dc_error;
    }

    /*Configure SPI registers*/
    printk(KERN_INFO " CS Register: %x\n", *ctrl_status);
    *ctrl_status = cs_bits;
    printk(KERN_INFO " CS Register: %x\n", *ctrl_status);

    printk(KERN_INFO " CLK Register: %x\n", *clk);
    *clk = clk_bits;
    printk(KERN_INFO " CLK Register: %x\n", *clk);

    /*Send message*/
    for (i = 0; i < 32; i++)
    {
        *fifo = fifo_msg[i];
#if DEBUG
        printk(KERN_INFO " FIFO Register: %x\n", *fifo);
#endif
    }

    for (i = 0; i < 1000000; i++)
    {
        is_done = *ctrl_status & 0x00010000;
        if (is_done) {
            printk(KERN_INFO " Transfer is Done: %x\n", *ctrl_status);
        }
    }

    /*Set TA=0*/
    *ctrl_status = 0xffffff7f & 0x00040084;  // set TA = 0
    printk(KERN_INFO " CS Register: %x\n", *ctrl_status);

    return 0;

dc_error:
    iounmap(lossi_toh);
ltoh_error:
    iounmap(dlen);
dlen_error:
    iounmap(clk);
clk_error:
    iounmap(fifo);
fifo_error:
    iounmap(ctrl_status);
cs_error:
    return -1;
}

static void __exit spimod_exit(void)
{
    iounmap(ctrl_status);
    iounmap(fifo);
    iounmap(clk);
    iounmap(dlen);
    iounmap(lossi_toh);
    iounmap(dma_ctrl);
    printk(KERN_INFO " Exiting Simple SPI Test\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

module_init(spimod_init);
module_exit(spimod_exit);
  • It's not quite clear what you're trying to do. This looks like an attempt to rewrite SPI driver, but you're probably missing clock gating, interrupts etc. You'd also need to disable existing driver that's probably bound to this peripheral. || What is your goal, maybe there are much simpler ways to achieve it? – domen Apr 3 '19 at 8:32
  • Hi @domen, I do want to write my own SPI driver. I have disabled spidev.c already, and I decided to do polling, which is why I didn't enable any interrupts. What would I need clock gating for? – David Apr 3 '19 at 8:40
  • Nothing leaps out as wrong. I'm not sure which clock source you are using for SPI so suggest you use a much lower divider like a thousand to make sure the clocks are visible. Also the peripheral base address changed for user space. I don't think it did for kernel space but that is worth double checking. – joan Apr 3 '19 at 8:49
  • @joan, thank you. I will try with a greater divider. I checked the peripheral address for kernel space, and yes, I believe it hasn't changed. – David Apr 3 '19 at 8:55
  • spidev exposes interface to userspace, but needs to communicate with kernel space spi bus master driver. I think you'll also need to disable that one. But then - what are you trying to do, and why not just change the existing spi driver to add your functionality? – domen Apr 3 '19 at 9:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.