当前位置: 代码迷 >> 综合 >> DeviceDriver(十三):SPI驱动
  详细解决方案

DeviceDriver(十三):SPI驱动

热度:88   发布时间:2023-10-24 00:27:11.0

一:SPI驱动框架简介

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;
}

二:SPI主机驱动分析

根据硬件信息,设备连接在imx6ull系列开发板的spi3接口上,查询共用设备树文件中的spi3节点:

ecspi3: ecspi@02010000 {#address-cells = <1>;#size-cells = <0>;compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02010000 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属性即可找到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)

三:SPI设备驱动分析

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   0x10b0MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK  0x10b1MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO    0x10b1MX6UL_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 <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#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");