SPI驱动框架同I2C类似,分为主机控制器驱动和设备驱动。
1、SPI主机驱动
SPI主机驱动就是SOC的SPI控制器驱动,Linux内核使用spi_master表示SPI主机驱动:
struct spi_master {
struct device dev;
struct list_head list;
s16 bus_num;
u16 num_chipselect;
u16 dma_alignment;
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
... ...
/* limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;
... ...
/* other constraints relevant to this driver */
u16 flags;
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
bool bus_lock_flag;
int (*setup)(struct spi_device *spi);
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
... ...
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
... ...
};
其中transfer函数和i2c中的master_xfer函数一样是控制器数据传输函数。transfer_one_message函数也用于SPI数据发送,用于发送一个spi_message,SPI的数据会打包成spi_message,然后以队列的方式发送出去。
SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后想内核注册spi_master。
(1)spi_master申请与释放
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
static inline void spi_master_put(struct spi_master *master)
(2)spi_master注册与注销
int spi_register_master(struct spi_master *master)
void spi_unregister_master(struct spi_master *master)
2、SPI设备驱动
Linux内核使用spi_driver结构体来表示spi设备驱动,这个需要我们来实现:
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;
};
可以看出当spi设备和驱动匹配成功后probe函数就会被执行。
spi_driver初始化完成后还需要向Linux内核注册:
int spi_register_driver(struct spi_driver *sdrv)
注销函数:
void spi_unregister_driver(struct spi_driver *sdrv)
spi_driver注册示例:
/* probe 函数 */
static int xxx_probe(struct spi_device *spi)
{
/* 具体函数内容 */
return 0;
}
/* remove 函数 */
static int xxx_remove(struct spi_device *spi)
{
/* 具体函数内容 */
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};
/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init xxx_init(void)
{
return spi_register_driver(&xxx_driver);
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
spi_unregister_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
3、SPI设备和驱动匹配过程
spi设备和驱动匹配过程是由spi总线来完成的:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
spi的匹配函数:
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;
}
根据硬件信息,设备连接在imx6ull系列开发板的spi3接口上,查询共用设备树文件中的spi3节点:
ecspi3: ecspi@02010000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x02010000 0x4000>;
interrupts = ;
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属性即可找到Linux内核中SPI3主机控制器驱动:
static struct platform_device_id spi_imx_devtype[] = {
... ...
{
.name = "imx51-ecspi",
.driver_data = (kernel_ulong_t) &imx51_ecspi_devtype_data,
}, {
.name = "imx6ul-ecspi",
.driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,
}, {
/* sentinel */
}
};
static const struct of_device_id spi_imx_dt_ids[] = {
... ...
{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },
{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = spi_imx_dt_ids,
.pm = IMX_SPI_PM,
},
.id_table = spi_imx_devtype,
.probe = spi_imx_probe,
.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);
其中spi_imx_devtype数据为SPI无设备树匹配表,spi_imx_dt_ids为SPI设备树匹配表,上一章中的I2C同理。
spi_imx_probe函数会从设备树中读取相应的节点属性值,申请并初始化spi_master,最后调用spi_bitbang_start(注册函数为spi_register_master)函数向Linux内核注册spi_master:
-->>static int spi_imx_probe(struct platform_device *pdev)
-->>spi_bitbang_start(&spi_imx->bitbang);
-->>spi_register_master(spi_master_get(master));
spi_imx_probe函数中有两个设置很重要,一个是SPI主机的最终数据收发函数spi_imx_transfer,一个是设置收发函数指向的函数spi_imx_setupxfer:
spi_imx_probe:
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
-->>spi_imx_transfer
-->>spi_imx_pio_transfer
-->>spi_imx_push
-->>spi_imx->tx(spi_imx);
-->>spi_imx_setupxfer
-->>
/* Initialize the functions for transfer */
if (config.bpw <= 8) {
spi_imx->rx = spi_imx_buf_rx_u8;
spi_imx->tx = spi_imx_buf_tx_u8;
spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
} else if (config.bpw <= 16) {
spi_imx->rx = spi_imx_buf_rx_u16;
spi_imx->tx = spi_imx_buf_tx_u16;
spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
} else {
spi_imx->rx = spi_imx_buf_rx_u32;
spi_imx->tx = spi_imx_buf_tx_u32;
spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
}
#define MXC_SPI_BUF_TX(type) \
static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \
{ \
type val = 0; \
\
if (spi_imx->tx_buf) { \
val = *(type *)spi_imx->tx_buf; \
spi_imx->tx_buf += sizeof(type); \
} \
\
spi_imx->count -= sizeof(type); \
\
writel(val, spi_imx->base + MXC_CSPITXDATA); \
}
MXC_SPI_BUF_RX(u8)
MXC_SPI_BUF_TX(u8)
MXC_SPI_BUF_RX(u16)
MXC_SPI_BUF_TX(u16)
MXC_SPI_BUF_RX(u32)
MXC_SPI_BUF_TX(u32)
1、初始化驱动数据
前面已经讲过注册spi_driver,现在就可以对设备进行读写操作,其中有两个结构体需要熟知:
spi_transfer :tx_buf保存着要发送的数据,rx_buf保存接收到的数据,len是要进行传输的数据长度。
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;
};
spi_message:在使用spi_message之前需要对其进行初始化,初始化函数为spi_message_init
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;
struct list_head queue;
void *state;
};
void spi_message_init(struct spi_message *m)
初始化完成之后需要将spi_transfer添加到spi_message队列中:spi_message_add_tail
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
2、数据收发
当spi数据准备好之后就可以进行数据传输了,数据传输分为同步传输和异步传输。
同步传输:会阻塞等待SPI数据传输完成
int spi_sync(struct spi_device *spi, struct spi_message *message)
异步传输:不会阻塞,需要设置spi_message中的complete成员变量,它是一个回调函数,当SPI异步传输完成以后此函数就会被调用。
int spi_async(struct spi_device *spi, struct spi_message *message)
3、读写操作示例
/* 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;
}
1、修改设备树
(1)添加ICM20608设备的IO引脚
pinctrl_ecspi3: icm20608 {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1
>;
};
(2)在ecspi3节点中增加icm20608子节点
&ecspi3 {
fsl,spi-num-chipselects = <1>; /* 设置当前片选数量为1 */
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 选用自定义“cs-gpio”属性,而非系统片选属性*/
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>; /* 设置IO要使用的pinctrl子节点 */
status = "okay";
spidev: icm20608@0 { /* 设备连接在ecspi3的第0个通道上 */
compatible = "alientek, icm20608";
spi-max-frequency = <8000000>; /* SPI最大时钟频率为8MHz */
reg = <0>;
};
};
2、驱动
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "icm20608reg.h"
#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"
struct icm20608_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
int cs_gpio;
signed int gyro_x_adc;
signed int gyro_y_adc;
signed int gyro_z_adc;
signed int accel_x_adc;
signed int accel_y_adc;
signed int accel_z_adc;
signed int temp_adc;
};
static struct icm20608_dev icm20608dev;
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低,选中ICM20608 */
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次,发送要读取的寄存地址 */
txdata[0] = reg | 0x80; /* 写数据的时候寄存器地址bit8要置1 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
/* 第2次,读取数据 */
txdata[0] = 0xff; /* 随便一个值,此处无意义 */
t->rx_buf = buf; /* 读取到的数据 */
t->len = len; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1); /* 片选拉高,释放ICM20608 */
return ret;
}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
uint8_t char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
gpio_set_value(dev->cs_gpio, 0);
/* 第1次,发送要读取的寄存地址 */
txdata[0] = reg & ~0x80;
t->tx_buf = txdata;
t->led = 1;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
/* 第2次,发送要写入的数据 */
t->tx_buf = buf; /* 要写入的数据 */
t->len = len; /* 写入的字节数 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放ICM20608 */
return ret;
}
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
u8 data = 0;
icm20608_read_regs(dev, reg, &data, 1);
return data;
}
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
u8 buf = value;
icm20608_write_regs(dev, reg, &buf, 1);
}
void icm20608_readdata(struct icm20608_dev *dev)
{
uint8_t data[14];
icm20608_read_onereg(dev, ICM20_ACCEL_XOUT_H, data, 14);
dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}
static int icm20608_open(struct inode *inode, struct file *filp)
{
filp->private_data = &icm20608dev;
return 0;
}
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int16_t data[7];
long err = 0;
struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
icm20608_readdata(dev);
data[0] = dev->gyro_x_adc;
data[1] = dev->gyro_y_adc;
data[2] = dev->gyro_z_adc;
data[3] = dev->accel_x_adc;
data[4] = dev->accel_y_adc;
data[5] = dev->accel_z_adc;
data[6] = dev->temp_adc;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int icm20608_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations icm20608_fops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
void icm20608_reginit(void)
{
uint8_t value = 0;
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
mdelay(50);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
mdelay(50);
value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
printk("ICM20608 ID = %#X\r\n", value);
icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */
icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */
icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */
icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */
icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */
}
static int icm20608_probe(struct spi_device *spi)
{
int ret = 0;
if(icm20608dev.major)
{
icm20608dev.devid = MKDEV(icm20608dev.major, 0);
register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
}
else
{
alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
icm20608dev.major = MAJOR(icm20608dev.devid);
}
cdev_init(&icm20608dev.cdev, &icm20608_fops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.device, NULL, ICM20608_NAME);
icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
if(ret < 0){
printk("can't set gpio!\r\n");
}
spi->mode = SPI_MODE_0;
spi_setup(spi);
icm20608dev.private_data = spi;
icm20608_reginit();
return 0;
}
static int icm20608_remove(struct spi_device *spi)
{
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
return 0;
}
static const struct spi_device_id icm20608_id[] = {
{ "alientek, icm20608", 0 },
{}
};
static const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek, icm20608" },
{}
};
static struct spi_driver icm20608_driver = {
.probe = icm20608_probe,
.remove = icm20608_remove,
.driver = {
.owner = THIS_MODULE,
.name = "icm20608",
.of_match_table = icm20608_of_match,
},
.id_table = icm20608_id,
};
static int __init icm20608_init(void)
{
return spi_register_driver(&icm20608_driver);
}
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");