linux的SPI设备驱动程序

这篇具有很好参考价值的文章主要介绍了linux的SPI设备驱动程序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

一、驱动程序架构

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

linux spi应用程序,linux驱动开发,linux,驱动开发,arm开发,嵌入式硬件

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。文章来源地址https://www.toymoban.com/news/detail-641654.html

到了这里,关于linux的SPI设备驱动程序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux 下spi设备驱动

    参考: Linux kernel 有关 spi 设备树参数解析 - 走看看 Linux SPI驱动框架(1)——核心层_linux spi驱动模型_绍兴小贵宁的博客-CSDN博客 Linux SPI驱动框架(2)——控制器驱动层_全志h3 spi驱动_绍兴小贵宁的博客-CSDN博客 Linux SPI驱动框架(3)——设备驱动层_linux spi字符设备驱动_绍兴小贵宁的

    2024年02月09日
    浏览(35)
  • 第五章 I/O管理 五、输入/输出应用程序接口&设备驱动程序接口

    目录 一、应用程序接口 二、阻塞和非阻塞I/O 阻塞I/O: 非阻塞I/O: 三、设备驱动程序 以前的统一接口不适用了,现在改为了几种不同的接口 阻塞I/O: 应用程序发出I/O系统调用,进程需转为阻塞态等待。 eg:字符设备接口―一从键盘读一个字符get 非阻塞I/O: 应用程序发出I/O系统调

    2024年02月07日
    浏览(42)
  • 5.2.10.应用程序如何调用驱动 mknod /dev/test c 250 0 创建设备文件,应用app 程序 调用 我们 驱动 壳子

    5.2.10.应用程序如何调用驱动 5.2.10.1、驱动设备文件的创建 (1)何为设备文件     索引驱动 (2)设备文件的关键信息是:设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。         4颗LED不可能 都占用 主设备号,设备号

    2024年02月16日
    浏览(41)
  • Linux驱动开发—最详细应用程序调用驱动程序解析

    Linux下进行驱动开发,完全将驱动程序与应用程序隔开,中间通过 C标准库函数 以及 系统调用 完成驱动层和应用层的数据交换。 驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过 对“/dev/xxx” (xxx 是具体的驱动文件名字) 的文件进行相应的操作 即可实

    2024年02月16日
    浏览(47)
  • 嵌入式Linux驱动开发——解决/sys/bus/spi/devices下没有对应的spi设备文件

    最近在学习Linux驱动开发中SPI总线的驱动框架,但在修改完设备树添加完对应的spi设备节点后,理应在/sys/bus/spi下会有对应的spi设备,我的目录下面没有。 无spi设备 然后我查看了/proc/device-tree,发现有对应的spi设备节点,我就先没有过多理会这个问题。 /proc/device-tree下有对应

    2024年02月16日
    浏览(43)
  • Linux设备驱动程序(一)——设备驱动简介

    这一部分主要是用来介绍 Linux 设备驱动程序的一些基本概念,包括:Linux 设备驱动程序的作用、内核功能的划分、设备和模块的分类以及版本编号。 设备驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作

    2024年02月05日
    浏览(87)
  • Linux 设备驱动程序(四)

    Linux 内核设计与实现 深入理解 Linux 内核 Linux 设备驱动程序(一) Linux 设备驱动程序(二) Linux 设备驱动程序(三) Linux 设备驱动程序(四) Linux设备驱动开发详解 深入理解Linux虚拟内存管理     ⇐ ⇒ ⇔ ⇆ ⇒ ⟺ ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳㉑㉒㉓㉔㉕

    2024年02月16日
    浏览(44)
  • Linux设备驱动开发学习笔记(等待队列,锁,字符驱动程序,设备树,i2C...)

    container_of函数可以通过结构体的成员变量检索出整个结构体 函数原型: 内核开发者只实现了循环双链表,因为这个结构能够实现FIFO和LIFO,并且内核开发者要保持最少代码。 为了支持链表,代码中要添加的头文件是linux/list.h。内核中链表实现核心部分的数据结构 是struct li

    2024年01月22日
    浏览(51)
  • Linux 驱动开发基础知识——LED 模板驱动程序的改造:设备树(十一)

     个人名片: 🦁作者简介:学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755@qq.com 🦉个人WeChat:Vir2021GKBS 🐼 本文由妄北y原创,首发CSDN 🎊🎊🎊 🐨座右铭:大多数人想要改造这个世界,但却罕有人想改造自己。 专栏导航: 妄北y系列专栏导航: C/C++的基

    2024年02月21日
    浏览(43)
  • QEMU学习(六):SPI设备仿真及驱动开发

            SPI和I2C一样也是很常用的串行通信协议,并且框架都很类似,都分主机控制器驱动和设备驱动,主机控制器也就是SOC的SPI控制器接口,一般linux内核都自带主机控制器,我们要做的就是SPI设备驱动。 下面是QEMU自带的SPI模拟程序,位于qemuhwssiimx_spi.c,可以看到当我

    2024年01月24日
    浏览(121)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包