Linux 驱动 | SPI子系统

SPI子系统

在这里插入图片描述
这些驱动的共同点:

  • 主机端驱动和外设端驱动分离

  • 通过一个核心层将某种总线协议进行抽象

  • 外设端驱动通过核心层API间接调用主机驱动提供的传输函数进行收发数据

  • IIC、SPI等不支持热拔插的总线需要板级信息描述,对于USB、PCI等支持热拔插的总线则不需要板级描述信息

----《linux设备驱动开发详解》

通过以上可以看到Linux下的SPI子系统和IIC、USB等子系统框架特别类似,都是分为两部分:主机控制器驱动设备驱动

在这里插入图片描述

SPI 子系统体系结构

在这里插入图片描述

主机控制器驱动

include\linux\spi\spi.h
linux使用struct spi_controller描述主机控制器

#define spi_master          spi_controller

struct spi_controller {
	struct device	dev;

	struct list_head list;
	
	s16			bus_num;

	u16			num_chipselect;  /* 片选信号数量 */

	......
	/* bitmask of supported bits_per_word for transfers */
	u32			bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))

	/* limits on transfer speed */
	u32			min_speed_hz;
	u32			max_speed_hz;

	/* other constraints relevant to this driver */
	u16			flags;
#define SPI_CONTROLLER_HALF_DUPLEX	BIT(0)	/* can't do full duplex */
#define SPI_CONTROLLER_NO_RX		BIT(1)	/* can't do buffer read */
#define SPI_CONTROLLER_NO_TX		BIT(2)	/* can't do buffer write */
#define SPI_CONTROLLER_MUST_RX		BIT(3)	/* requires rx */
#define SPI_CONTROLLER_MUST_TX		BIT(4)	/* requires tx */

#define SPI_MASTER_GPIO_SS		BIT(5)	/* GPIO CS must select slave */

	/* flag indicating this is an SPI slave controller */
	bool			slave;
	/*
	 * on some hardware transfer / message size may be constrained
	 * the limit may depend on device transfer settings
	 */
	size_t (*max_transfer_size)(struct spi_device *spi);
	size_t (*max_message_size)(struct spi_device *spi);

	/* I/O mutex */
	struct mutex		io_mutex;

	/* lock and mutex for SPI bus locking */
	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;

	/* flag indicating that the SPI bus is locked for exclusive use */
	bool			bus_lock_flag;

	int			(*setup)(struct spi_device *spi);
	int			(*transfer)(struct spi_device *spi,
						struct spi_message *mesg);

	/* called on release() to free memory provided by spi_controller */
	void			(*cleanup)(struct spi_device *spi);

	bool			(*can_dma)(struct spi_controller *ctlr,
					   struct spi_device *spi,
					   struct spi_transfer *xfer);
					   
	......
	
	struct spi_message		*cur_msg;
	bool				idling;
	bool				busy;
	bool				running;
	bool				rt;
	bool				auto_runtime_pm;
	bool                            cur_msg_prepared;
	bool				cur_msg_mapped;
	struct completion               xfer_completion;
	size_t				max_dma_len;

	......
	/* gpio chip select */
	int			*cs_gpios;
	
	int (*fw_translate_cs)(struct spi_controller *ctlr, unsigned cs);
};
  • 重点关注transfer成员函数,类似I2C,SPI也是最终调用该函数实现数据传输,SPI的数据以struct spi_message形式组织起来,添加到子系统维护的队列中进行发送。

SPI主机控制器驱动相关API

1、spi_master申请/释放

/* the spi driver core manages memory for the spi_controller classdev */
extern struct spi_controller *__spi_alloc_controller(struct device *host,
                        unsigned int size, bool slave);

static inline struct spi_controller *spi_alloc_master(struct device *host,
                              unsigned int size)
{
    return __spi_alloc_controller(host, size, false);
}

#define spi_master_put(_ctlr)       spi_controller_put(_ctlr)

static inline void spi_controller_put(struct spi_controller *ctlr)
{
    if (ctlr)
        put_device(&ctlr->dev);
}

2、spi_master注册/注销

#define spi_register_master(_ctlr)  spi_register_controller(_ctlr)
int spi_register_controller(struct spi_controller *ctlr)

#define spi_unregister_master(_ctlr)    spi_unregister_controller(_ctlr)
void spi_unregister_controller(struct spi_controller *ctlr)

SPI控制器驱动在设备树中的节点

  ecspi3: ecspi@2010000 {
      #address-cells = <1>;
      #size-cells = <0>;
      compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";  //匹配属性
      reg = <0x2010000 0x4000>;
      interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
      clocks = <&clks IMX6UL_CLK_ECSPI3>,
      <&clks IMX6UL_CLK_ECSPI3>;
      clock-names = "ipg", "per";
      dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
      dma-names = "rx", "tx";
      status = "disabled";
};

分别搜索compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi"的两个字符串内容就可以找到SOC相对应的SPI主机驱动在哪个文件中实现

设备驱动

SPI设备驱动分成两部分:
在这里插入图片描述

  • spi_driver类 ,spi驱动描述如何操作设备
  • spi_device 类 ,spi设备信息,也就是硬件信息

与platform_driver和platform_device类似

spi_driver注册/注销

int spi_register_driver(struct spi_driver *sdrv)  注册一个spi驱动
void spi_unregister_driver(struct spi_driver *sdrv)  注销一个spi驱动

设备驱动的设备树节点示例

&ecspi1 {
	fsl,spi-num-chipselects = <1>;           /* 片选引脚数 */
	cs-gpios = <&gpio4 26 0>;                /* 片选引脚 */
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi1>;
	status = "okay";

	xgj_oled@0 {
		dc-gpios = <&gpio4 24 0>;            /* 命令数据选择引脚 */ 
		rst-gpios = <&gpio1 2 0>;            /* 复位引脚 */   
		compatible = "xgj,oled";             /* 匹配属性 */
		spi-max-frequency = <20000000>;      /* 时钟频率 */
		reg = <0>;                           /* 0 通道 */ 
	};    
};
  • 设备挂在哪个spi下就追加子节点到哪个spi控制器的设备树节点下,此处&ecspi1 追加到spi1下
  • xgj_oled,追加的设备节点名

SPI数据收发

内核使用spi_tranfer类描述数据传输

struct spi_transfer {
    const void  *tx_buf;         //发送的buf
    void        *rx_buf;         //接收数据的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;
};

SPI传输数据还要组织成struct spi_message的形式添加到队列中进行传输

Linux中的SPI消息被抽象成spi_message

struct spi_message {
    struct list_head    transfers;
    struct spi_device   *spi;
    unsigned        is_dma_mapped:1;
    /* completion is reported through a callback */
    void            (*complete)(void *context);   //发送接收完成该函数会被调用
    void            *context;
    unsigned        frame_length;
    unsigned        actual_length;
    int         status;

    struct list_head    queue;
    void            *state;
    /* list of spi_res reources when the spi message is processed */
    struct list_head        resources;
};
//初始化spi_message和添加spi_transfer
static inline void spi_message_init(struct spi_message *m);  //初始化spi_message

static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m); 
//将spi_tranfer加入spi_message的消息队列中,在加入spi_tranfer之前必须先初始化spi_message

int spi_async(struct spi_device *spi, struct spi_message *message); //SPI异步传输数据

int spi_sync(struct spi_device *spi, struct spi_message *message); //SPI同步传输数据

示例:

/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len) 
{ 
    int ret; 
    struct spi_message m; 
     
    struct spi_transfer t = { 
        .tx_buf = buf, 
        .len = len, 
    }; 
 
    spi_message_init(&m);         /* 初始化 spi_message */ 
    spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ 
    ret = spi_sync(spi, &m);     /* 同步传输 */ 
    return ret; 
}

/* SPI 多字节接收 */ 
static int spi_receive(struct spi_device *spi, u8 *buf, int len) 
{ 
    int ret; 
    struct spi_message m; 
     
    struct spi_transfer t = { 
        .rx_buf = buf, 
        .len = len, 
    }; 
 
    spi_message_init(&m);         /* 初始化 spi_message */ 
    spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ 
    ret = spi_sync(spi, &m);     /* 同步传输 */ 
    return ret; 
}

SPI驱动编写模板

/*
 * @brief :   
 * @date :  2022-02-xx
 * @version : v1.0.0
 * @copyright(c) 2020 : OptoMedic company Co.,Ltd. All rights reserved
 * @Change Logs:   
 * @date         author         notes:  
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched/signal.h>
#include <linux/poll.h>
#include <linux/atomic.h>
#include <linux/spi/spi.h>

struct xxx_device {
    int dc_gpio;
    int rst_gpio;
    dev_t dev_no; /* 设备号 */
    struct cdev chrdev;
    struct class *class;
    struct spi_device *spi;
};

static struct xxx_device *xxx_dev;

static int _drv_open(struct inode *node, struct file *file)
{
    int ret = 0;
    ......
    return 0;
}

static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int ret = 0;
    ...
    return ret;
}

static long _drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = 0;

    switch (cmd)
    {
        case CMD1 :

            break;
        case CMD2 :

            break;
        ......
        ......
    }

    return ret;
}

static int _drv_release(struct inode *node, struct file *filp)
{

    return 0;
}

static struct file_operations xxx_drv_ops = {
    .owner = THIS_MODULE,
    .open = _drv_open,
    .read = _drv_read,
    .unlocked_ioctl = _drv_ioctl,
    .release = _drv_release,
};

static int _driver_probe(struct spi_device *spi)
{
    int err = 0;
    struct device *_dev;
    struct device_node *_dts_node;

    ......
    ......

    /* 内核自动分配设备号 */
    err = alloc_chrdev_region(&xxx_dev->dev_no, 0, 1, DEV_NAME);
    if (err < 0)
    {
        pr_err("Error: failed to register xxx, err: %d\n", err);
        goto exit_free_dev;
    }

    cdev_init(&xxx_dev->chrdev, &xxx_drv_ops);
    err = cdev_add(&xxx_dev->chrdev, xxx_dev->dev_no, 1);
    if (err)
    {
        printk("cdev add failed\r\n");
        goto exit_unregister;
    }

    xxx_dev->class = class_create(THIS_MODULE, DEV_NAME);
    if (IS_ERR(xxx_dev->class))
    {
        err = PTR_ERR(xxx_dev->class);
        goto exit_cdev_del;
    }

    /* 使驱动自动在用户空间创建设备节点 */
    _dev = device_create(xxx_dev->class, NULL, xxx_dev->dev_no, NULL, DEV_NAME);
    if (IS_ERR(_dev))
    { /* 判断指针是否合法 */
        err = PTR_ERR(_dev);
        goto exit_class_del;
    }

    xxx_dev->spi = spi;

    goto exit;

exit_class_del:
    class_destroy(xxx_dev->class);
exit_cdev_del:
    cdev_del(&xxx_dev->chrdev);
exit_unregister:
    unregister_chrdev_region(xxx_dev->dev_no, 1); /* 注销设备 */
exit_free_dev:
    kfree(xxx_dev);
    xxx_dev = NULL;
exit:
    return err;
}

static int _driver_remove(struct spi_device *spi)
{
    int ret = 0;

    device_destroy(xxx_dev->class, xxx_dev->dev_no);
    class_destroy(xxx_dev->class);
    cdev_del(&xxx_dev->chrdev);
    unregister_chrdev_region(xxx_dev->dev_no, 1); /* 注销设备 */
    kfree(xxx_dev);

    return ret;
}

/* 设备树的匹配列表 */
static struct of_device_id xxx_of_match_table[] = {
    {.compatible = "xxx"}, /* 通过设备树来匹配 */
    {},
};

/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id_table[] = {
    {.name = "xxx", 0},
    {}};

/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {
    .probe = _driver_probe,
    .remove = _driver_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "xxx,dev",
        .of_match_table = xxx_of_match_table,
    },
    .id_table = xxx_id_table,
};

module_spi_driver(xxx_driver);

MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");
  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欲盖弥彰1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值