LInux驱动开发笔记(十)SPI子系统及其驱动

文章目录

  • 前言
  • 一、SPI驱动框架
  • 二、总线驱动
    • 2.1 SPI总线的运行机制
    • 2.2 重要数据结构
      • 2.2.1 spi_controller
      • 2.2.2 spi_driver
      • 2.2.3 spi_device
      • 2.2.4 spi_transfer
      • 2.2.5 spi_message
  • 三、设备驱动的编写
    • 3.1 设备树的修改
    • 3.2 相关API函数
      • 3.2.1 spi_setup( )
      • 3.2.2 spi_message_init( )
      • 3.2.3 spi_message_add_tail( )
      • 3.2.4 spi_transfer_init( )
      • 3.2.5 spi_sync( )和spi_async( )
      • 3.2.6 spi_register_driver( )
      • 3.2.7 spi_unregister_driver( )
    • 3.3 SPI驱动的信息传递
    • 3.4 SPI驱动的设计框架
    • 3.5 具体功能的实现
      • 3.5.1 _init函数和_exit函数
      • 3.5.2 .probe函数
      • 3.5.3 .remove函数
      • 3.5.4 数据传输函数


前言

  前章我们已经学习了iic子系统驱动,这部分我们继续spi子系统驱动的学习。


一、SPI驱动框架

  SPI同样可分为spi总线驱动和spi设备驱动, 我们也只需要进行spi设备驱动的编写。SPI设备驱动涉及到SPI设备驱动、SPI核心层、SPI主机驱动,具体功能如下:
SPI核心层:SPI核心层是SPI子系统的中间层,提供了一个通用的接口以便设备驱动可以与SPI主机驱动交互。核心层负责管理SPI总线上的所有设备,并提供基本的数据传输功能。提供SPI控制器驱动和设备驱动的注册方法、注销方法,SPI Core提供操作接口函数,允许一个spi master,spi driver 和spi device初始化。
SPI主机驱动:SPI主机驱动与具体的硬件SPI控制器直接交互,负责实际的数据传输和控制操作,它实现了SPI核心层定义的接口,并提供对硬件的抽象。其主要包含SPI硬件体系结构中适配器(spi控制器)的控制,实现spi总线的硬件访问操作。
SPI设备驱动:SPI设备驱动是针对特定SPI从设备的驱动程序,负责与特定的硬件设备交互,主要职责包括设备的初始化、配置以及数据传输。设备驱动通常会调用SPI核心层提供的接口来完成这些任务。
在这里插入图片描述

二、总线驱动

2.1 SPI总线的运行机制

  linux系统在开机的时候就会自动执行spi_init函数,进行spi总线注册。当总线注册成功之后,会在sys/bus下面生成一个spi总线,然后在系统中新增一个设备类,sys/class/目录下会可以找到spi_master类。

//注册SPI总线
static int __init spi_init(void)
{
    int     status;
    ...
    status = bus_register(&spi_bus_type);
    ...
    status = class_register(&spi_master_class);
    ...
}

struct bus_type spi_bus_type = {
    .name           = "spi",
    .dev_groups     = spi_dev_groups,
    .match          = spi_match_device,
    .uevent         = spi_uevent,
};


//匹配规则,同IIC子系统,常用设备树匹配的方法
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
    const struct spi_device *spi = to_spi_device(dev);
    const struct spi_driver *sdrv = to_spi_driver(drv);

    /* Attempt an OF style match */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi);

    return strcmp(spi->modalias, drv->name) == 0;
}

  使用的rk3568芯片有4个spi控制器,对应的设备树存在4个节点,利用平台总线module_platform_driver(rockchip_spi_driver), 间接调用platform_driver_register 和 platform_driver_unregister,实现平台驱动函数的注册和注销。
  当匹配到对应驱动时时,调用rockchip_spi_probe函数,进行初始化,获取设备树节点信息,初始化spi时钟、dma、中断等。

2.2 重要数据结构

2.2.1 spi_controller

  spi_controller 结构体用于表示一个SPI控制器,它包含了控制器的属性和方法。在Linux中,该结构体定义在 <linux/spi/spi.h> 头文件中。

struct spi_controller {
    struct device   		 dev;
    ...
    struct list_head 		 list;
    s16                      bus_num;
    u16                      num_chipselect;
    ...
    struct spi_message       *cur_msg;
    ...
    int     (*setup)(struct spi_device *spi);
    int     (*transfer)(struct spi_device *spi,struct spi_message *mesg);
    void    (*cleanup)(struct spi_device *spi);
    struct kthread_worker    kworker;
    struct task_struct       *kworker_task;
    struct kthread_work      pump_messages;
    struct list_head         queue;
    struct spi_message       *cur_msg;
    ...
    int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,struct spi_transfer *transfer);
    int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
    int (*transfer_one_message)(struct spi_controller *ctlr,struct spi_message *mesg);
    void (*set_cs)(struct spi_device *spi, bool enable);
    ...
    int                     *cs_gpios;
}

成员说明:

  • dev:该字段表示该SPI控制器对应的设备对象,继承自Linux内核中的设备模型。
  • list: 这是一个链表节点,用于将SPI控制器连接到全局的SPI控制器链表中。
  • bus_num: spi控制器编号。
  • num_chipselect:支持的芯片选择线数量。
  • cur_msg:指向当前正在处理的SPI消息的指针。
  • setup:函数指针,用于执行SPI设备的初始化和配置操作。
  • transfer:指向数据传输函数的指针,用于把数据加入控制器的消息队列中,实现SPI数据的传输。
  • cleanup:函数指针,用于执行SPI设备的清理和释放操作。
  • kworker: 内核线程工人,spi可以使用异步传输方式发送数据。
  • pump_messages: 具体传输工作。
  • queue: 所有等待传输的消息队列挂在该链表下。
  • transfer_one_message: 发送一个spi消息,类似IIC适配器里的algo->master_xfer,产生spi通信时序。
  • cs_gpios: 记录spi上具体的片选信号。

2.2.2 spi_driver

  spi_driver 结构体用于表示一个SPI设备的驱动程序,在Linux中,该结构体定义在 <linux/spi/spi.h> 头文件中。

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

成员说明:

  • id_table:匹配表
  • driver:设备驱动的基本信息,如驱动名字、所属的模块等。
  • probe:指向探测函数的指针,用于初始化并注册SPI设备。
  • shutdown:指向关闭函数的函数指针,用于关闭SPI设备。当系统关闭或卸载设备驱动时,会调用该函数。
  • remove:指向移除函数的指针,用于卸载SPI设备。

2.2.3 spi_device

  spi_device 结构体用于表示一个具体的SPI设备,它与硬件设备相对应。在Linux中,该结构体定义在 <linux/spi/spi.h> 头文件中。

struct spi_device {
    struct device           dev;
    struct spi_controller   *controller;
    struct spi_controller   *master;        /* compatibility layer */
    u32                     max_speed_hz;
    u8                      chip_select;
    u8                      bits_per_word;
    u16                     mode;
   #define  SPI_CPHA        0x01                    /* clock phase */
   #define  SPI_CPOL        0x02                    /* clock polarity */
   #define  SPI_MODE_0      (0|0)                   /* (original MicroWire) */
   #define  SPI_MODE_1      (0|SPI_CPHA)
   #define  SPI_MODE_2      (SPI_CPOL|0)
   #define  SPI_MODE_3      (SPI_CPOL|SPI_CPHA)
   #define  SPI_CS_HIGH     0x04                    /* chipselect active high? */
   #define  SPI_LSB_FIRST   0x08                    /* per-word bits-on-wire */
   #define  SPI_3WIRE       0x10                    /* SI/SO signals shared */
   #define  SPI_LOOP        0x20                    /* loopback mode */
   #define  SPI_NO_CS       0x40                    /* 1 dev/bus, no chipselect */
   #define  SPI_READY       0x80                    /* slave pulls low to pause */
   #define  SPI_TX_DUAL     0x100                   /* transmit with 2 wires */
   #define  SPI_TX_QUAD     0x200                   /* transmit with 4 wires */
   #define  SPI_RX_DUAL     0x400                   /* receive with 2 wires */
   #define  SPI_RX_QUAD     0x800                   /* receive with 4 wires */
    int                     irq;
    void                    *controller_state;
    void                    *controller_data;
    char                    modalias[SPI_NAME_SIZE];
    int                     cs_gpio;        /* chip select gpio */

    /* the statistics */
    struct spi_statistics   statistics;
};

成员说明:

  • controller:指向控制器的指针,表示该设备所属的SPI控制器。
  • master:指向主机的指针,表示该设备所属的SPI主机。
  • chip_select:芯片选择线的编号。
  • max_speed_hz:设备支持的最大时钟频率。
  • mode:设备的SPI模式(CPOL、CPHA)。
  • bits_per_word:每个数据字的位数。
  • mode: SPI工作模式,工作模式如以上代码中的宏定义。
  • irq: 如果使用了中断,它用于指定中断号

2.2.4 spi_transfer

  spi_transfer 结构体用于表示SPI数据传输的信息,包括要发送和接收的数据、数据长度等。在Linux中,该结构体定义在 <linux/spi/spi.h> 头文件中。

struct spi_transfer {
    const void      *tx_buf;
    void            *rx_buf;
    unsigned        len;

    dma_addr_t      tx_dma;
    dma_addr_t      rx_dma;
    struct sg_table tx_sg;
    struct sg_table rx_sg;

    unsigned        cs_change:1;
    unsigned        tx_nbits:3;
    unsigned        rx_nbits:3;
#define     SPI_NBITS_SINGLE        0x01 /* 1bit transfer */
#define     SPI_NBITS_DUAL          0x02 /* 2bits transfer */
#define     SPI_NBITS_QUAD          0x04 /* 4bits transfer */
    u8              bits_per_word;
    u16             delay_usecs;
    u32             speed_hz;

    struct list_head transfer_list;
};

成员说明:

  • tx_buf:指向要发送数据的缓冲区。
  • rx_buf:指向接收数据的缓冲区。
  • len:数据长度。
  • tx_dma、rx_dma: 如果使用了DAM,用于指定tx或rx DMA地址。
  • speed_hz:数据传输的时钟频率。
  • bits_per_word:每个数据字的位数。
  • delay_usecs:传输完成后的延迟时间。

2.2.5 spi_message

  spi_message 结构体用于表示一个SPI数据传输的完整消息,包含了一个或多个 spi_transfer 结构体。在Linux中,该结构体定义在 <linux/spi/spi.h> 头文件中。

struct spi_message {
      ...
    struct list_head        transfers;
    struct spi_device       *spi;
    unsigned                is_dma_mapped:1;
    struct list_head        queue;
    void 					*state;
      ...
};

成员说明:

  • spi:指向SPI设备的指针。
  • is_dma_mapped:表示数据是否已经进行DMA映射。
  • transfers:一个链表,包含了多个 spi_transfer 结构体,表示要进行的多个数据传输。
  • queue:用于在 SPI 控制器内部排队等待处理的消息队列,通常由 SPI 主控制器驱动程序使用。
  • state:当前驱动程序的状态指针,用于在传输过程中保存和恢复状态。

三、设备驱动的编写

3.1 设备树的修改

  相关瑞芯微官方给出的ic3控制器的设备树代码这里就不再赘述了,如下所示:

spi3 {
    spi3m1_pins: spi3m1-pins {
        rockchip,pins =
            /* spi3_clkm1 */
            <4 RK_PC2 2 &pcfg_pull_none>,
            /* spi3_misom1 */
            <4 RK_PC5 2 &pcfg_pull_none>,
            /* spi3_mosim1 */
            <4 RK_PC3 2 &pcfg_pull_none>;
    };

    spi3m1_cs0: spi3m1-cs0 {
        rockchip,pins =
            /* spi3_cs0m1 */
            <4 RK_PC6 2 &pcfg_pull_none>;
    };

    spi3m1_cs1: spi3m1-cs1 {
        rockchip,pins =
            /* spi3_cs1m1 */
            <4 RK_PD1 2 &pcfg_pull_none>;
    };
};

spi3-hs {
            spi3m1_pins_hs: spi3m1-pins {
                    rockchip,pins =
                            /* spi3_clkm1 */
                            <4 RK_PC2 2 &pcfg_pull_up_drv_level_1>,
                            /* spi3_misom1 */
                            <4 RK_PC5 2 &pcfg_pull_up_drv_level_1>,
                            /* spi3_mosim1 */
                            <4 RK_PC3 2 &pcfg_pull_up_drv_level_1>;
            };

            spi3m1_cs0_hs: spi3m1-cs0 {
                    rockchip,pins =
                            /* spi3_cs0m1 */
                            <4 RK_PC6 2 &pcfg_pull_up_drv_level_1>;
            };

            spi3m1_cs1_hs: spi3m1-cs1 {
                    rockchip,pins =
                            /* spi3_cs1m1 */
                            <4 RK_PD1 2 &pcfg_pull_up_drv_level_1>;
            };
    };

  以下是我们需要修改的部分:

&spi3{
    status = "okay";
    
    //定义引脚控制的状态名称,用于选择引脚配置
    pinctrl-names = "default", "high_speed";
    
    //引脚控制的默认状态,引用了 spi3m1_cs0 和 spi3m1_pins 引脚配置
    pinctrl-0 = <&spi3m1_cs0 &spi3m1_pins>;
    
    //高速模式下的引脚控制状态,引用了 spi3m1_cs0 和 spi3m1_pins_hs 引脚配置
    pinctrl-1 = <&spi3m1_cs0 &spi3m1_pins_hs>;
    
    //定义芯片选择(CS)引脚的GPIO配置。这里使用了GPIO4的PC6引脚,并且是低电平有效(GPIO_ACTIVE_LOW)
    cs-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>;

    spi_oled@0 {
        status = "okay";
        compatible = "company,myspi";
        
        //SPI设备的地址,即芯片选择(CS)编号,这里是CS0
        reg = <0>;
        
        //SPI设备的最大时钟频率,单位为赫兹(Hz)                        
        spi-max-frequency = <24000000>;   
        
        //定义一个数据/命令控制引脚,使用GPIO3的PA7引脚,并且是高电平有效(GPIO_ACTIVE_HIGH)
        dc_control_pin = <&gpio3 RK_PA7 GPIO_ACTIVE_HIGH>;
        
        //定义引脚控制的状态名称,用于选择引脚配置
        pinctrl-names = "default";
        
        //引脚控制的默认状态,引用了 spi_oled_pin 引脚配置
        pinctrl-0 = <&myspi>;
    };
};

&pinctrl {
    myspi {
        myspi: myspi {
            rockchip,pins = <3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>;
        };
    };
}; 

3.2 相关API函数

3.2.1 spi_setup( )

  函数设置spi设备的片选信号、传输单位、最大传输速率等,函数中调用spi控制器的成员controller->setup(), 也就是master->setup,在前面的函数rockchip_spi_probe()中初始化了“ctlr->setup = rockchip_spi_setup;”。

int spi_setup(struct spi_device *spi)
  • 参数:
    • spi spi_device spi设备结构体
  • 返回值:
    • 成功: 0
    • 失败: 其他任何值都为错误码

3.2.2 spi_message_init( )

//初始化spi_message
static inline void spi_message_init(struct spi_message *m)
{
    memset(m, 0, sizeof *m);
    spi_message_init_no_memset(m);
}
  • 参数:

    • m:spi_message 结构体指针,spi_message结构体定义和介绍可在前面关键数据结构中找到。
  • 返回值: 无。

3.2.3 spi_message_add_tail( )

  这个函数很简单就是将将spi_transfer结构体添加到spi_message队列的末尾。

//
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
    list_add_tail(&t->transfer_list, &m->transfers);
}
  1. 参数:
    • t:指向要添加的SPI传输结构体的指针。
    • m:指向SPI消息结构体的指针。

3.2.4 spi_transfer_init( )

//初始化SPI传输结构体
void spi_transfer_init(struct spi_transfer *t);
  1. 参数:
    • t:指向要添加的SPI传输结构体的指针。

3.2.5 spi_sync( )和spi_async( )

  这两个函数都用于阻塞当前线程进行数据传输,spi_sync()内部调用__spi_sync()/__spi_async函数,mutex_lock()和mutex_unlock()为互斥锁的加锁和解锁,互斥锁这部分内容在之前应用层TCP/UDP协议中提及到过。

//进行同步SPI数据传输
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
    int ret;
	//上锁
    mutex_lock(&spi->controller->bus_lock_mutex);
    //
    ret = __spi_sync(spi, message);
    //解锁
    mutex_unlock(&spi->controller->bus_lock_mutex);

    return ret;
}
  1. 参数:
    • spi:指向SPI设备的指针。
    • message:包含要传输的数据的SPI消息结构体。
      返回值:成功时返回0,失败时返回负错误代码。
//进行异步SPI数据传输
int spi_async(struct spi_device *spi, struct spi_message *message)
{
    ...
    ret = __spi_async(spi, message);
    ...
}
  1. 参数:
    • spi:指向SPI设备的指针。
    • message:包含要传输的数据的SPI消息结构体。
  2. 返回值:
    • 0:成功
    • 负数:错误码

3.2.6 spi_register_driver( )

//注册一个SPI设备驱动程序
int spi_register_driver(struct spi_driver *sdrv);
  1. 参数:
    • sdrv:指向要注册的SPI设备驱动程序的指针。
  2. 返回值:
    • 0:成功
    • 负数:错误码

3.2.7 spi_unregister_driver( )

//从系统中注销一个SPI设备驱动程序
void spi_unregister_driver(struct spi_driver *sdrv);
  1. 参数:
  2. sdrv:指向要注销的SPI设备驱动程序的指针

3.3 SPI驱动的信息传递

  spi_message通过成员变量queue将一系列的spi_message串联起来,第一个spi_message挂在spi_controller结构体的queue下面。除此之外, spi_message中的transfers_list成员变量也可以在不同的spi_transfer之间相互串联。
在这里插入图片描述
结构体之间的关系
  spi_transfer 和 spi_message:一个 spi_message 结构体包含多个 spi_transfer 结构体,通过 transfers_list将它们串联起来。这表示一个 SPI 事务中可以包含多个数据传输操作。
  spi_message 和 spi_controller:一个 spi_message 通过 SPI 控制器(spi_controller)进行处理。spi_controller 的 transfer 函数会接收一个 spi_message,并按照 spi_message 中定义的顺序执行所有 spi_transfer 操作。
数据传输流程
流程示例

  1. 创建一个 spi_message 结构体,并添加多个 spi_transfer 到这个 spi_message 中。
  2. 将 spi_message 提交给 SPI 子系统。
  3. SPI 子系统找到合适的 spi_controller,并调用其transfer 函数来处理 spi_message。
  4. spi_controller 按顺序处理 spi_message 中的每个spi_transfer,并进行实际的数据传输。
  5. 当所有 spi_transfer 完成后,调用 spi_message 的complete 回调函数(如果有的话)。
    数据结构体切换的问题
      在 SPI 子系统中,通过 spi_sync/spi_async函数进行消息传输时,每次调用 spi_sync/spi_async都会处理一个完整的 spi_message 结构体,包含多个 spi_transfer 结构体。这意味着如果你需要在传输过程中切换不同的 spi_message传输操作,需要通过多个spi_sync/spi_async来实现,而不能像 spi_transfer 那样提前多次利用spi_message_add_tail来制定顺序,然后使用spi_sync/spi_async统一进行数据输出。
//示例
static int spi_example_transfer(struct spi_device *spi)
{
    int ret;

    // 第一个 spi_message
    struct spi_message msg1;
    u8 tx_buf1[] = {0x01, 0x02, 0x03, 0x04};
    struct spi_transfer tx_transfer1 = {
        .tx_buf = tx_buf1,
        .rx_buf = NULL,
        .len = sizeof(tx_buf1),
        .cs_change = 1,    // 在传输完成后改变 CS 线
        .delay_usecs = 10, // 传输完成后延迟 10 微秒
        .speed_hz = spi->max_speed_hz,
        .bits_per_word = 8,
    };

    spi_message_init(&msg1);
    spi_message_add_tail(&tx_transfer1, &msg1);

    // 提交第一个 spi_message
    ret = spi_sync(spi, &msg1);
    if (ret) {
        dev_err(&spi->dev, "SPI transfer 1 failed: %d\n", ret);
        return ret;
    }

    // 第二个 spi_message
    struct spi_message msg2;
    u8 tx_buf2[] = {0x05, 0x06, 0x07, 0x08};
    u8 rx_buf2[4];
    struct spi_transfer tx_transfer2 = {
        .tx_buf = tx_buf2,
        .rx_buf = NULL,
        .len = sizeof(tx_buf2),
        .cs_change = 0,
        .delay_usecs = 0,
        .speed_hz = spi->max_speed_hz,
        .bits_per_word = 8,
    };
    struct spi_transfer rx_transfer2 = {
        .tx_buf = NULL,
        .rx_buf = rx_buf2,
        .len = sizeof(rx_buf2),
        .cs_change = 0,
        .delay_usecs = 0,
        .speed_hz = spi->max_speed_hz,
        .bits_per_word = 8,
    };

    spi_message_init(&msg2);
    spi_message_add_tail(&tx_transfer2, &msg2);
    spi_message_add_tail(&rx_transfer2, &msg2);

    // 提交第二个 spi_message
    ret = spi_sync(spi, &msg2);
    if (ret) {
        dev_err(&spi->dev, "SPI transfer 2 failed: %d\n", ret);
        return ret;
    }

    // 处理接收到的数据
    for (int i = 0; i < sizeof(rx_buf2); i++) {
        pr_info("Received byte %d: %02x\n", i, rx_buf2[i]);
    }

    return 0;
}

3.4 SPI驱动的设计框架

  本次实验大致采用SPI驱动和字符设备,这里着重讲一下这部分的设计思路:

  1. 本实验采用设备树匹配的方式进行匹配,故而需要设置spi_driver结构体。
//指定ID匹配表
static const struct spi_device_id oled_device_id[] = {
    {"fire,spi_oled", 0},
    {}
};

//指定设备树匹配表
static const struct of_device_id oled_of_match_table[] = {
    {.compatible = "fire,spi_oled"},
    {}
};

//spi总线设备结构体
struct spi_driver oled_driver = {
    .probe = oled_probe,
    .remove = oled_remove,
    .id_table = oled_device_id,
    .driver = {
            .name = "myspi",
            .owner = THIS_MODULE,
            .of_match_table = of_match_ptr(oled_of_match_table),
    },
};
  1. 出入口函数的编写,这里我们采用字符设备的写法,需要编写module_init和module_exti,并且在其中分别使用spi_register_driver( )函数和spi_unregister_driver( )函数进行spi总线的注册和注销。
  2. 字符设备相关内容的编写,即在module_init进行alloc_chrdev_region(内存申请)、cdev_init(字符设备的申请),cdev_add(字符设备的添加)、class_create(类的创建)、device_create(设备的创建);在module_exti中进行device_destroy(设备删除)、class_destroy(清除类)、cdev_del(清除设备号)、unregister_chrdev_region(注销字符设备);及各种资源的释放。
  3. probe函数的编写,这部分主要是利用of_get_named_gpio( )进行申请gpio、gpio_request( )和gpio_direction_output( )进行函数控制D/C引脚、以及通过spi_setup( )初始化spi。
  4. remove函数的编写,这部分与probe函数对应即可,利用gpio_free( )释放gpio资源。
  5. 编写operations结构体相关函数,包括open、write、read、release。

3.5 具体功能的实现

3.5.1 _init函数和_exit函数

static int __init oled_driver_init(void)
{
    int ret = 0;
    printk("\t driver has entered! \n");

    ret = alloc_chrdev_region(&oled_devno, 0, DEV_CNT, DEV_NAME);
    if (ret < 0) {
        printk("\t alloc_chrdev_region err! \n");
        goto alloc_err;
    }

    oled_chr_dev.owner = THIS_MODULE;
    cdev_init(&oled_chr_dev, &oled_chr_dev_fops);

    ret = cdev_add(&oled_chr_dev, oled_devno, DEV_CNT);
    if (ret < 0) {
        printk("\t cdev_add err! \n");
        goto add_err;
    }

    class_oled = class_create(THIS_MODULE, DEV_NAME);
    if (IS_ERR(class_oled)) {
        ret = PTR_ERR(class_oled);
        printk("\t class_create err! \n");
        goto class_err;
    }

    device_oled = device_create(class_oled, NULL, oled_devno, NULL, DEV_NAME);
    if (IS_ERR(device_oled)) {
        ret = PTR_ERR(device_oled);
        printk("\t device_create err! \n");
        goto device_err;
    }

    ret = spi_register_driver(&oled_driver);
    if (ret < 0) {
        printk("\t spi_register_driver err! \n");
        goto spi_err;
    }

    return 0;

spi_err:
    device_destroy(class_oled, oled_devno);
device_err:
    class_destroy(class_oled);
class_err:
    cdev_del(&oled_chr_dev);
add_err:
    unregister_chrdev_region(oled_devno, DEV_CNT);
alloc_err:
    return ret;
}
module_init(oled_driver_init);

static void __exit oled_driver_exit(void)
{
    spi_unregister_driver(&oled_driver);
    device_destroy(class_oled, oled_devno);         //清除设备
    class_destroy(class_oled);                      //清除类
    cdev_del(&oled_chr_dev);                        //清除设备号
    unregister_chrdev_region(oled_devno, DEV_CNT);  //取消注册字符设备
    printk("\t driver has exited! \n");
}
module_exit(oled_driver_exit);

3.5.2 .probe函数

static int oled_probe(struct spi_device *spi_device)
{
    int ret;
    struct device_node *node = spi_device->dev.of_node;
    printk("\t device has matched! \n");

    spi_cs = of_get_named_gpio(node, "dc_control_pin", 0); //进行申请gpio
    printk("\t spi_cs is %d \n", spi_cs);

    ret = gpio_request(spi_cs, "spi_cs");
    if (ret < 0) {
        printk("\t gpio_request err! \n");
        return ret;
    }

    gpio_direction_output(spi_cs, 1);

    /*初始化spi*/
    spi_bus_device = spi_device;
    spi_bus_device->mode = SPI_MODE_0;
    spi_bus_device->max_speed_hz = 2000000;
    ret = spi_setup(spi_bus_device);
    if (ret < 0) {
        printk("\t spi_setup err! \n");
        goto spi_err;
    }

    printk("spibus has init successfully\n");
    printk("max_speed_hz = %d\n", spi_bus_device->max_speed_hz);
    printk("chip_select = %d\n", (int)spi_bus_device->chip_select);
    printk("bits_per_word = %d\n", (int)spi_bus_device->bits_per_word);
    printk("mode = %02X\n", spi_bus_device->mode);
    printk("cs_gpio = %02X\n", spi_bus_device->cs_gpio);
    return 0;

spi_err:
    gpio_free(spi_cs);
    return ret;
}

3.5.3 .remove函数

static int oled_remove(struct spi_device *spi_device)
{
    printk("device has removed!\n");
    gpio_free(spi_cs);
    return 0;
}

3.5.4 数据传输函数

//向 oled 发送一个字节
static int oled_send_one_u8(struct spi_device *spi_device, u8 data)
{
    int error = 0;
    u8 tx_data = data;
    struct spi_message message;   //定义发送的消息
    struct spi_transfer *transfer; //定义传输结构体

    //设置 D/C引脚为高电平
    gpio_direction_output(spi_cs, 1);

    //申请空间
    transfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    if (!transfer) {
        printk("kzalloc error!\n");
        return -ENOMEM;
    }

    //填充message和transfer结构体
    transfer->tx_buf = &tx_data;
    transfer->len = 1;
    spi_message_init(&message);
    spi_message_add_tail(transfer, &message);
	
	//同步发送 SPI 消息
    error = spi_sync(spi_device, &message);
    kfree(transfer);
    if (error != 0)
    {
        printk("spi_sync error! \n");
        return -1;
    }
    return 0;
}

免责声明:本内容部分参考野火科技及其他相关公开资料,若有侵权或者勘误请联系作者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/728880.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在windows 台式机电脑部署GLM4大模型

参考这篇文章在windows笔记本电脑部署GLM4大模型_16g显卡本地部署glm4-CSDN博客 我的环境&#xff08;PC台式机电脑&#xff1a; 处理器 Intel(R) Core(TM) i9-14900K 3.20 GHz 机带 RAM 32.0 GB (31.8 GB 可用)、32G内存、NVIDIA RTX4080&#xff08;16G&#xff09;…

深入理解Open vSwitch(OVS):原理、架构与操作

一、引言 随着云计算和虚拟化技术的不断发展&#xff0c;网络虚拟化成为了构建灵活、可扩展网络架构的关键技术之一。Open vSwitch&#xff08;OVS&#xff09;作为一种功能强大的开源虚拟交换机&#xff0c;被广泛应用于云计算和虚拟化环境中&#xff0c;为虚拟机提供高效、灵…

前端调试技巧

1、利用console打印日志 2、利用debugger关键字&#xff0c;浏览器f12调用到方法debugger处会断点住&#xff0c;可以利用浏览器调试工具查看变量 a.监视表达式可以添加想要观察的变量 b.调用堆栈可以观察方法调用链 3、xhr断点 请求地址包含v1.0/banner_theme/pagelist&a…

预制舱变电站高压室巡检机器人系统

一、背景 预制舱变电站高压室由于空间狭小、设备紧凑&#xff0c;传统的巡检方式往往需要人工进入高压室进行巡检&#xff0c;不仅存在安全风险&#xff0c;而且巡检效率低下&#xff0c;难以满足日益增长的电力设备运维需求。 二、预制舱高压室巡检机器人系统 预制舱高压室巡…

express+vue在线im实现【四】

往期内容 expressvue在线im实现【一】 expressvue在线im实现【二】 expressvue在线im实现【三】 本期示例 本期总结 支持了音频的录制和发送&#xff0c;如果觉得对你有用&#xff0c;还请点个免费的收藏与关注 下期安排 在线语音 具体实现 <template><kl-dial…

【总结】ui自动化selenium知识点总结

1. 大致原理 首页安装第三方库selenium库&#xff0c; 其次要下载好浏览器驱动文件&#xff0c;比如谷歌的 chromedriver.exe&#xff0c;配置上环境变量。 使用selenium的webdriver类去创建一个浏览器驱动对象赋值叫driver&#xff0c;一个浏览器驱动对象就可以 实现 对浏…

【PHP项目实战训练】——使用thinkphp框架对数据进行增删改查功能

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

国内怎样使用GPT4 turbo

GPT是当前最为熟知的大模型&#xff0c;它优越的性能一直遥遥领先于其它一众厂商&#xff0c;然而如此优秀的AI在中国境内却是无法正常使用的。本文将告诉你4种使用gpt4的方法&#xff0c;让你突破限制顺利使用。 官方售价是20美元/月&#xff0c;40次提问/3小时&#xff0c;需…

嵌入式系统软件开发环境_2.一般架构

1.Eclipse框架 嵌入式系统软件开发环境是可帮助用户开发嵌入式软件的一组工具的集合&#xff0c;其架构的主要特征离不开“集成”问题&#xff0c;采用什么样的架构框架是决定开发环境优劣主要因素。Eclipse框架是当前嵌入式系统软件开发环境被普遍公认的一种基础环境框架。目…

vscode插件开发之 - TestController

TesController概要介绍 TestController 组件是用于实现自定义测试框架和集成测试结果的。它允许开发者定义自己的测试运行器&#xff0c;以支持在VSCode中运行和展示测试。以下是一些使用 TestController 组件的主要场景&#xff1a; 自定义测试框架&#xff1a;如果你正在开发…

深度学习算法informer(时序预测)(三)(Encoder)

一、EncoderLayer架构如图&#xff08;不改变输入形状&#xff09; 二、ConvLayer架构如图&#xff08;输入形状中特征维度减半&#xff09; 三、Encoder整体 包括三部分 1. 多层EncoderLayer 2. 多层ConvLayer 3. 层归一化 代码如下 class AttentionLayer(nn.Module):de…

世界奇观短视频制作,AI加持,新手也能月入上万

在这个数字化的时代&#xff0c;短视频已经成为了人们获取信息和娱乐的重要途径。特别是那些展示世界奇观的短视频&#xff0c;如极端的气候、危险的动物、美丽的自然景观等&#xff0c;这些主题具有很强的吸引力&#xff0c;能够引起观众的兴趣和好奇心。那么&#xff0c;如何…

运算放大器(运放)反相放大器电路

运算放大器(运放)反相放大器电路 设计目标 输入ViMin输入ViMax输出VoMin输出VoMax频率f电源Vcc电源Vee–7V7V–14V14V3kHz15V–15V 设计说明 该设计将输入信号 Vi 反相并应用 –2V/V 的信号增益。输入信号通常来自低阻抗源&#xff0c;因为该电路的输入阻抗由输入电阻器 R1…

深度学习神经网络协同过滤模型(NCF)与用户协同过滤(UCF)的区别

一、效果图 点我查看在线demo 二、启发式推荐系统 推荐系统的核心是根据用户的兴趣需求&#xff0c;给用户推荐喜欢的内容。常用的推荐算法有启发式推荐算法&#xff0c;可分为基于用户的 协同过滤&#xff0c;基于物品的协同过滤。 1、基于用户的协同过滤&#xff08;UCF…

【云岚到家】-day04-1-数据同步方案-Canal-MQ

【云岚到家】-day04-1-数据同步方案-Canal-MQ 1 服务搜索1.1 服务搜索技术方案1.1.1 需求分析1.1.2 技术方案1.1.2.1 使用Elasticsearch进行全文检索1.1.2.2 索引同步方案 1.1.3 CanalMQ1.1.3.1 MySQL主从数据同步1.1.3.2 Canal工作流程1.1.3.3 具体实现方案 1.2 MQ技术方案1.2…

Linux连接工具MobaXterm详细使用教程

目录 一、MobaXterm的下载 1、访问官网 2、下载便携版 3、启动MobaXterm 二、MobaXterm基本使用设置 1、新建会话 2、使用ssh连接第一个会话 3、设置主密码 4、主界面 5、sftp文件上传下载 6、文件拖拽的上传下载 7.右键粘贴 8、查看服务器监测信息​编辑 9、个…

文件扫描工具哪个好?便捷的文件扫描工具推荐

对于初入职场的大学毕业生&#xff0c;申请就业补贴是一项不可忽视的福利。 它不仅能够为新生活带来经济上的缓解&#xff0c;也有助于职业生涯的顺利起步。面对申请过程中需提交的文件&#xff0c;如纸质劳动合同&#xff0c;不必烦恼。市面上众多文件扫描软件能助你一臂之力…

Oracle最终还是杀死了MySQL

起因 大约15年前&#xff0c;Oracle收购了Sun公司&#xff0c;从而也拥有了MySQL&#xff0c;互联网上关于Oracle何时会“扼杀MySQL”的讨论此起彼伏。 当时流传着各种理论&#xff1a;从彻底扼杀 MySQL 以减少对 Oracle 专有数据库的竞争&#xff0c;到干掉 MySQL 开源项目&…

vcpkg安装opencv中的特殊问题记录(无法找到opencv_corexd.dll)

我是按照网上的vcpkg安装opencv方法进行的&#xff08;比如这篇&#xff1a;从0开始在visual studio上安装opencv&#xff08;超详细&#xff0c;针对小白&#xff09;&#xff09;&#xff0c;但是中间出现了一些别人没有遇到的问题&#xff0c;虽然原因没有找到&#xff0c;但…

离子污染测试仪有哪些检测方法?校准机构如何选择?

离子污染测试仪是许多生产企业会使用到的一种仪器&#xff0c;作为一种高精度的操作仪器&#xff0c;在长时间的使用下&#xff0c;仪器磨损和失准也是常见状况&#xff0c;因此企业都会进行定期校准来维护仪器&#xff0c;那么离子污染测试仪有哪些检测方法&#xff1f;校准机…