linux的SPI设备驱动程序

        串行外设接口(SPI)是四线总线:MOSI、MISO、串行时钟SCK和片选CS。它常用于连接闪存、AD/DA转换器。主设备生成时钟和管理片选CS,速度可达80MB,远超I2C总线。

一、驱动程序架构

        SPI设备在内核中表示为struct spi_device{},管理他们的驱动程序的实例是struct spi_driver{}。spi的拓扑结构如下图:

linux的SPI设备驱动程序_第1张图片

1. 设备spi_device{}结构

struct spi_device {
    struct device dev;
    struct spi_master *master;  // 表示设备所连接的SPI控制器
    u32 max_speed_hz; // 设备的最大时钟频率,可以在传输时用spi_transfer().speed_hz参数修改频率
    u8 chip_select;  // 
    u8 bits_per_word;
    u16 mode; // 指定是LSB还是MSB,默认是MSB
    int irq;  //这代表中断号,应该将它传递给request_irq来接收此设备的中断。
    [...];
    int cs_gpio;
};

2. 驱动spi_driver{}结构

        spi_driver{}结构体

struct spi_driver {
    const struct spi_device_id *id_table;
    struct device_driver driver;
    int (*probe)(struct spi_device *spi);
    int (*remove)(struct spi_device *spi);
    int (*shutdown)(struct spi_device *spi);
};


// SPI可以修改CS状态、每字的位数、时钟,其probe函数如下
static int my_probe(struct spi_device *spi) {
    int ret;
    
    [...]
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 20000000; // 设备的最大时钟数
    spi->bits_per_word = 16; // 每个字的位数
    ret = spi_setup(spi);
    if (ret < 0) return ret;
    [...] // 其他省略的内容

    
    return ret;
}

可以获取对应的struct spi_device_id{}的指针,另外也支持void spi_set_drvdata(struct spi_device *, void *)和void *spi_get_drvdata(spi_device *spi)获取driverdata,使用示例如下:

struct mc33880 {
    struct mutext lock;
    u8 bar;
    struct foo chip;
    struct spi_device spi;
};

static int mc33880_probe(struct spi_device *spi) {
    struct mc33880 *mc;

    [...] // 设备配置
    mc = devm_kzalloc(&spi_dev, sizeof(*mc), GFP_KERNEL);
    mutex_init(&mc->lock);
    spi_set_drvdata(mc);
    mc->spi = spi;
    [...]  // 其他配置
    return 0;
}

static int mc33880_remove(struct spi_device *spi) {
    struct mc33880 *mc = spi_get_drvdata(spi);
    mutex_destroy(&mc->lock);
    [...] // 其他需要注销的内容
}

static struct spi_driver mc33880_driver {
    .driver = {.name = "", .of_match_table = NULL,},
    .probe = mc33880_probe,
    .remove = mc33880_remove,
    /*...其他省略字段*/
};

// 代替了spi_register_driver(drv), spi_unregister_driver(drv)流程,使用如下:
module_spi_driver(&mc33880_driver);

3. 驱动程序和设备配置 

        对于SPI设备,必须使用spi_device_id{}数组以供device_id进行匹配。

struct spi_device_id {
    char name[SPI_NAME_SIZE];
    kernel_ulong_t driver_data;
};

i). 使用spi_driver.id_table自动probe spi设备的示例如下:

static struct spi_device_id foo_id_table[] = {
    {"foo", 0},{"bar", 1}, {}
};

MODULE_DEVICE_TABLE(spi, foo_id_table);

static struct spi_driver foo_driver = {
    .driver = {.name="KBUILD_MODULE"},
    .id_table = foo_id_table, .probe = foo_probe, .remove = foo_remove,
};
module_spi_driver(foo_driver);

ii). 对应上述在驱动程序中的设置,需要在SoC的板文件中需要注册spi board info,示例如下:

struct my_platform_data {
    int foo; bool bar;
};

static struct my_platform_data mpfd = {
    .foo = 15, .bar = true,
};

static struct spi_board_info my_board_spi_board_info[] __initdata = {
{
    //modalias 必须与spi设备驱动程序的名称相同
    .modalias = "ad7887",
    .max_speed_hz = 1000000,
    .bus_num = 0,
    .irq = GPIO_IRQ(40),
    .chip_select = 3,
    .platform_data = &mpfd,
    .mode = SPI_MOD_3,
},
{
    //modalias 必须与spi设备驱动程序的名称相同
    .modalias = "spidev",
    .max_speed_hz = 1000000,
    .bus_num = 1,
    .chip_select = 0,
    .mode = SPI_MOD_3,
},
};

static int __init board_init(void)
{
    [...]
    spi_register_board_info(my_board_spi_board_info, 2);
    [...]
    return 0;
}

iii). SPI和设备树DT

        spi设备属于DT设备中的非存储器映射设备系列,可以寻址。这里的寻址是分配个控制器的CS片选信号的顺序编号。下面是SPI设备示例:

&ecspi1 {
    fsl,spi-num-chipselects = <3>;
    cs-gpios = <&gpio5 17 0>,<&gpio5 17 0>,<&gpio5 17 0>;
    pinctrl-0 = <&pinctrl_ecspi1 &pinctrl_ecspi_cs>;
    #address-cell=<1>;
    #size-cell=<0>;
    compatible=""fsl,imx6q-ecspi", "fsl,imx51-ecspi";
    reg = <0x02008000 0x4000>;
    status = "okay";

    ad7606r8_0: ad7606r8@0 {
        compatible = "ad7606-8";
        reg = <0>;
        spi-frequency = <10000000>;
        interrupt-parent = <&gpio4>;
        interrupts = <30 0x0>;
    };
    label : fake_device@1 {
        compatible = "packt,foobar-device";
        reg = <1>;
        spi-cs-high;
    };
    mcp2115can: can@0 {
        compatible = "microchip,mcp2151";
        reg = <2>;
        spi-frequency = <10000000>;
        interrupt-parent = <&gpio4>;
        interrupts = <29 IRQ_TYPE_LEVEL_LOW>;
        clocks = <&clk8m>;
    };
};

对应的使用上述DT的对应驱动实现方式如下:

static struct of_device_id foobar_of_match[] = {
    {.compatible = "packt,foobar-device"},
    {.compatible = "packt,foobar-device"},
    {},
};
MODULE_DEVICE_TABLE(of, foobar_of_match);

static struct spi_driver foo_driver = {
    .driver = { .name="foo", 
    .of_match_table = of_match_ptr(foobar_of_match)/*此处为DT的match数组*/,},
    .probe = foo_probe, .id_table = foo_id_table,
};

static int foo_probe(struct spi_device *spi) {
    const struct of_device_id *match;
    match = of_match_device(of_match_ptr(foobar_of_match), &spi->dev);
    if (match) {
        /*of_match相关代码*/
    } else {
        /*spi_device_id{}配置相关代码*/
    }
}

module_spi_driver(foo_driver);

二、访问与客户端通信

        SPI IO模型有一组消息队列组成。在提交若干个spi_message时,这些结构以同步或异步的方式处理完成。单个消息由一个或多个struct spi_transfer{}对象组成,每个对象代表全双工SPI传输。

结构体如下:

struct spi_transfer {
    const void *tx_buf; // 缓冲区要写入的数据
    void *rx_buf; // 要读取的数据
    unsigned len;
    dma_addr_t tx_dma; // 当spi_message.is_dma_mapp被设置为1时,使用tx_buf;
    dma_addr_t rx_dma;
    unsigned cs_change:1;
    unsigned tx_nbits:3;
    unsigned rx_nbits:3;
    u8 bits_per_word;
    u16 delay_usecs;
    u32 speed_hz;
};

struct spi_message {
    struct list_head transfers; // 消息中的transfer按照复工顺序处理
    struct spi_device *spi;
    struct is_dma_mapped:1;
    /*通过回调报告完成情况*/
    void (*complete)(void *context);
    unsigned frame_length;
    unsigned actual_length;
    int status;
};

SPI消息传输的主要函数:

// 在消息提交到总线之前,必须先初始化
void spi_message_init(struct spi_message *message);

// 对于要添加到spi_message中的每个spi_transfer,使用如下函数添加
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);

// 同步传输函数
it spi_sync(struct spi_device *spi, struct spi_message *msg);

// 异步spi传输,异步传输时,所提供的回调函数在传输完成时执行
it spi_sync(struct spi_device *spi, struct spi_message *msg);

// 另外,SPI核心提供了对spi_sync()的包装函数,简化一些少量数据传输的场景
int spi_read(struct spi_device *spi, void *buf, size_t len);
int spi_write(struct spi_device *spi, const void *buf, size_t len);
int spi_write(struct spi_device *spi, const void *txbuf, size_t n_tx,
                                      void *rxbuf, size_t n_rx);

SPI消息传输示例:

struct data{
    char buffer[10];
    char cmd[2]
    int foo
};

struct data my_data[3];
initialized_data(my_data, 3);

struct spi_transfer multi_xfer[3];
struct spi_message msg;

multi_xfer[0].rx_buf = data[0].buffer;
multi_xfer[0].len = 5;
multi_xfer[0].cs_change = 1;

multi_xfer[1].tx_buf = data[1].cmd;
multi_xfer[1]len = 2;
multi_xfer[1].cs_change = 1;

multi_xfer[0].rx_buf = data[0].buffer;
multi_xfer[0].len = 10;

spi_message_init(&msg);
spi_message_add_tail(&multi_xfer[0], &msg);
spi_message_add_tail(&multi_xfer[1], &msg);
spi_message_add_tail(&multi_xfer[2], &msg);
ret = spi_sync(spi_device, &msg);

三、SPI用户模式驱动程序

        使用用户模式SPI设备驱动程序,相当于是给设备绑定了标准SPI总线slave驱动程序——驱动程序spidev。在DT中绑定驱动的方式如下:

spidev@0x00 {
    compatilble = "spidev";
    spi_max_frequency = <800000>;
    reg = <0>; // 绑定后会在出现设备文件/dev/spidev0.0供用户空间访问
};

        可以调用read/write函数或ioctl()访问spi设备文件,调用read/write时,一次只能读或者写。如果要全双工读和写,则必须使用ioctl。

        通过总线ioctl发送数据时,可以使用SPI_IOC_MESSAGE(N)发送请求,提供全双工访问和复合操作。可以参考内核源码中的示例documentation/spi/spidev_test.c。

你可能感兴趣的:(linux驱动开发,linux,驱动开发,arm开发,嵌入式硬件)