linux的SPI设备驱动程序

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

一、驱动程序架构

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

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设备文件</dev/spidev0.0.>,调用read/write时,一次只能读或者写。如果要全双工读和写,则必须使用ioctl。

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

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux SPI屏幕驱动是一种用于控制SPI接口屏幕的驱动程序。下面是一个简单的SPI屏幕驱动程序的示例: ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/spi/spi.h> static struct spi_device *spi_device; static int spi_screen_probe(struct spi_device *spi) { printk(KERN_INFO "SPI screen probe called.\n"); spi_device = spi; return 0; } static int spi_screen_remove(struct spi_device *spi) { printk(KERN_INFO "SPI screen remove called.\n"); spi_device = NULL; return 0; } static struct spi_driver spi_screen_driver = { .driver = { .name = "spi_screen", .owner = THIS_MODULE, }, .probe = spi_screen_probe, .remove = spi_screen_remove, }; static int __init spi_screen_init(void) { int ret; printk(KERN_INFO "SPI screen driver init called.\n"); ret = spi_register_driver(&spi_screen_driver); if (ret < 0) { printk(KERN_ERR "Failed to register SPI screen driver.\n"); return ret; } return 0; } static void __exit spi_screen_exit(void) { printk(KERN_INFO "SPI screen driver exit called.\n"); spi_unregister_driver(&spi_screen_driver); } module_init(spi_screen_init); module_exit(spi_screen_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("SPI screen driver"); ``` 这个驱动程序使用了Linux SPI框架提供的API来与SPI设备进行通信。在probe函数中,我们可以初始化SPI设备并进行必要的设置。在remove函数中,我们可以清理并关闭SPI设备。 如果你想了解更多关于Linux SPI屏幕驱动的信息,可以参考以下资料: - 《Linux设备驱动开发详解》 - 《Linux内核源代码情景分析》 - 《Linux驱动开发实战》

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值