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");