SPI同样使我们在单片机开发中较为常用的通信接口,常用于诸如FLASH、OLED、SD卡的流式数据的读写,全双工总线,具体的关于协议的知识这里就不说了,我们主要讨论Linux的SPI设备驱动框架以及我们如何去编写一个SPI设备驱动
Linux下的SPI驱动和I2C驱动类似,也是分为主机控制器驱动和设备驱动
SOC的SPI控制器驱动,Linux内核使用spi_master表示SPI主机驱动
spi_master结构体定义在\linux\spi\spi.h
这是一个庞大的结构体定义,封装了很多操作函数以、私有标志以及锁等元素
其中有两个比较重要的函数
int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
transfer函数和i2c_algorithm中的master_xfer函数一样,是控制器的数据传输函数
transfer_one_message函数也用于SPI数据发送,用于发送一个spi_message,SPI的数据通常会打包成一个spi_message然后用队列方式发送出去
对于SPI主机驱动来说主要就是实现transfer函数,以此来实现与设备的通信
与I2C主机驱动一样,SPI主机驱动一般由SOC厂商编写
SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后向Linux内核注册
与I2C设备驱动很类似,使用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;
};
与i2c_driver、platform_driver基本一样,设备和驱动匹配以后会执行probe函数
SPI的总线为spi_bus_type,定义在\drivers\spi\spi.c
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
可以看出匹配函数为spi_match_device
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;
}
可以使用设备树、ACPI和通过对比id_table匹配,在我们使用设备树时一般都是使用设备树来匹配的
一般由SOC厂商编写
imx6ull.dsi文件中
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有两个属性值"fsl,imx6ul-ecspi", “fsl,imx51-ecspi”,使用这两个属性值来查找主机驱动
主机驱动实现在\drivers\spi\spi-imx.c
static struct platform_device_id spi_imx_devtype[] = {
{
.name = "imx1-cspi",
.driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,
}, {
.name = "imx21-cspi",
.driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,
}, {
.name = "imx27-cspi",
.driver_data = (kernel_ulong_t) &imx27_cspi_devtype_data,
}, {
.name = "imx31-cspi",
.driver_data = (kernel_ulong_t) &imx31_cspi_devtype_data,
}, {
.name = "imx35-cspi",
.driver_data = (kernel_ulong_t) &imx35_cspi_devtype_data,
}, {
.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,imx1-cspi", .data = &imx1_cspi_devtype_data, },
{ .compatible = "fsl,imx21-cspi", .data = &imx21_cspi_devtype_data, },
{ .compatible = "fsl,imx27-cspi", .data = &imx27_cspi_devtype_data, },
{ .compatible = "fsl,imx31-cspi", .data = &imx31_cspi_devtype_data, },
{ .compatible = "fsl,imx35-cspi", .data = &imx35_cspi_devtype_data, },
{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },
{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },
{ /* sentinel */ }
};
上面的为SPI无设备树匹配表
下面的是设备树匹配表
我们看一下platform_driver驱动框架,SPI主机驱动使用了platform驱动框架
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,
};
spi_imx_probe函数会从设备树中读取相应的节点属性值,申请并初始化spi_master,最后调用spi_bitbang_start函数(spi_bitbang_start会调用spi_register_master函数)向linux内核注册spi_master
对于I.MX6U来讲,SPI主机的最终收发函数为spi_imx_transfer,此函数通过如下层层调用实现SPI数据发送
spi_imx_transfer
-> spi_imx_pio_transfer
-> spi_imx_push
-> spi_imx->tx
tx和rx这两个变量分别为SPI的数据发送和接收函数,而tx和rx这两个变量是结构体spi_imx_data 的成员
结构体 spi_imx_data定义如下
struct spi_imx_data {
struct spi_bitbang bitbang;
struct completion xfer_done;
void __iomem *base;
struct clk *clk_per;
struct clk *clk_ipg;
unsigned long spi_clk;
unsigned int count;
void (*tx)(struct spi_imx_data *);
void (*rx)(struct spi_imx_data *);
void *rx_buf;
const void *tx_buf;
unsigned int txfifo; /* number of words pushed in tx FIFO */
/* DMA */
unsigned int dma_is_inited;
unsigned int dma_finished;
bool usedma;
u32 rx_wml;
u32 tx_wml;
u32 rxt_wml;
struct completion dma_rx_completion;
struct completion dma_tx_completion;
struct dma_slave_config rx_config;
struct dma_slave_config tx_config;
const struct spi_imx_devtype_data *devtype_data;
int chipselect[0];
};
I.MX6U SPI主机驱动会维护一个 spi_imx_data类型的变量 spi_imx,并且使用 spi_imx_setupxfer函数来设置 spi_imx的 tx和 rx函数。
可以收发8、16、32位的数据
spi_imx_buf_tx_u8
spi_imx_buf_tx_u16
spi_imx_buf_tx_u32
spi_imx_buf_rx_u8
spi_imx_buf_rx_u16
spi_imx_buf_rx_u32
这六个函数是通过如下的方式创建的
#define MXC_SPI_BUF_RX(type) \
static void spi_imx_buf_rx_##type(struct spi_imx_data *spi_imx) \
{ \
unsigned int val = readl(spi_imx->base + MXC_CSPIRXDATA); \
\
if (spi_imx->rx_buf) { \
*(type *)spi_imx->rx_buf = val; \
spi_imx->rx_buf += sizeof(type); \
} \
}
#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)
通过使用‘##’拼接符的方式巧妙的创建了六个函数
根据使用的IO来创建或者修改pinctrl节点,要注意检查相应的IO有没有被其他的设备所使用,如果有的话要将其删除
例子:
pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x100b1 /* MISO*/
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x100b1 /* MOSI*/
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x100b1 /* CLK*/
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x100b0 /* CS*/
>;
};
这里很容易理解,就是对引脚的复用和IO配置
例子:
&ecspi1 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio4 9 0>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
status = "okay";
flash: m25p80@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "st,m25p32";
spi-max-frequency = <20000000>;
reg = <0>;
};
};
fsl,spi-num-chipselects 属性为1,表示只有一个设备
cs-gpios 表示片选信号为gpio4_IO09
pinctrl-names就是SPI设备使用的IO名字
pinctrl-0 所使用的IO对应的pinctrl节点
status 设置为okay
m25p80@0 设备为m25p80,0表示m25p80接到了ECSPI的通道0上
compatible SPI设备用于匹配驱动的标识
spi-max-frequency 设置SPI控制器的最高频率,要根据所使用的SPI设备来设置,这里设置为了20MHZ
reg 表示使用ECSPI的通道0
我们编写ICM20608的设备树节点信息的时候就参考这个内容
SPI设备驱动的核心是spi_driver
在内核注册成功spi_driver后就可以使用SPI核心层提供的API函数来对设备进行读写操作了
首先是spi_transfer结构体,此结构体用于描述SPI传输信息
struct spi_transfer {
/* it's ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
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是要进行传输的数据长度,SPI是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以spi_transfer中就没有发送长度和接收长度之分
spi_transfer需要组织成spi_message,spi_message也是一个结构体
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* completion is reported through a callback */
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;
/* for optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_master controller driver.
*/
struct list_head queue;
void *state;
};
在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init
void spi_message_init(struct spi_message *m);
初始化完成后需要将spi_transfer添加到spi_message队列中,这里我们要使用
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
spi_message准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待SPI数据传输完成,同步传输函数为 spi_sync
int spi_sync(struct spi_device *spi, struct spi_message *message);
异步传输不会阻塞的等待SPI数据传输完成,异步传输需要设置spi_message中的complete成员变量,complete是一个回调函数,当SPI数据传输完成后此函数会被调用,SPI异步传输函数为spi_async
int spi_async(struct spi_device *spi, struct spi_message *message);
SPI数据传输的步骤
我们使用的模块是正点原子开发板上板载的icm20608六轴传感器模块
可以读到的数据为温度、3轴加速度、3轴角速度数据
我们的编写的是设备驱动,所以分为修改设备树、编写驱动程序、编写应用程序三个部分
该传感器连接在SPI3上
pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x100b1 /* MISO*/
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x100b1 /* MOSI*/
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x100b1 /* CLK*/
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x100b0 /* CS*/
>;
};
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
spidev: icm20608@0 {
compatible = "alientek,icm20608";
spi-max-frequency = <8000000>;
reg = <0>;
};
};
注意:没有配置cs-gpios而是用了一个自己定义的cs-gpio(不带s),因为我们要自己控制片选引脚,如果使用cs-gpios属性点额话SPI主机驱动就会控制片选引脚
pinctrl-0引用了我们前面定义的pinctrl
spi-max-frequency为8MHZ这是因为icm20608的SPI口最大支持8M
这个头文件定义了一些相关的寄存器地址
#ifndef ICM20608_REG_H
#define ICM20608_REG_H
#define ICM20608G_ID 0XAF /* ID值 */
#define ICM20608D_ID 0XAE /* ID值 */
/* ICM20608寄存器
*复位后所有寄存器地址都为0,除了
*Register 107(0X6B) Power Management 1 = 0x40
*Register 117(0X75) WHO_AM_I = 0xAF或0xAE
*/
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define ICM20_SELF_TEST_X_GYRO 0x00
#define ICM20_SELF_TEST_Y_GYRO 0x01
#define ICM20_SELF_TEST_Z_GYRO 0x02
#define ICM20_SELF_TEST_X_ACCEL 0x0D
#define ICM20_SELF_TEST_Y_ACCEL 0x0E
#define ICM20_SELF_TEST_Z_ACCEL 0x0F
/* 陀螺仪静态偏移 */
#define ICM20_XG_OFFS_USRH 0x13
#define ICM20_XG_OFFS_USRL 0x14
#define ICM20_YG_OFFS_USRH 0x15
#define ICM20_YG_OFFS_USRL 0x16
#define ICM20_ZG_OFFS_USRH 0x17
#define ICM20_ZG_OFFS_USRL 0x18
#define ICM20_SMPLRT_DIV 0x19
#define ICM20_CONFIG 0x1A
#define ICM20_GYRO_CONFIG 0x1B
#define ICM20_ACCEL_CONFIG 0x1C
#define ICM20_ACCEL_CONFIG2 0x1D
#define ICM20_LP_MODE_CFG 0x1E
#define ICM20_ACCEL_WOM_THR 0x1F
#define ICM20_FIFO_EN 0x23
#define ICM20_FSYNC_INT 0x36
#define ICM20_INT_PIN_CFG 0x37
#define ICM20_INT_ENABLE 0x38
#define ICM20_INT_STATUS 0x3A
/* 加速度输出 */
#define ICM20_ACCEL_XOUT_H 0x3B
#define ICM20_ACCEL_XOUT_L 0x3C
#define ICM20_ACCEL_YOUT_H 0x3D
#define ICM20_ACCEL_YOUT_L 0x3E
#define ICM20_ACCEL_ZOUT_H 0x3F
#define ICM20_ACCEL_ZOUT_L 0x40
/* 温度输出 */
#define ICM20_TEMP_OUT_H 0x41
#define ICM20_TEMP_OUT_L 0x42
/* 陀螺仪输出 */
#define ICM20_GYRO_XOUT_H 0x43
#define ICM20_GYRO_XOUT_L 0x44
#define ICM20_GYRO_YOUT_H 0x45
#define ICM20_GYRO_YOUT_L 0x46
#define ICM20_GYRO_ZOUT_H 0x47
#define ICM20_GYRO_ZOUT_L 0x48
#define ICM20_SIGNAL_PATH_RESET 0x68
#define ICM20_ACCEL_INTEL_CTRL 0x69
#define ICM20_USER_CTRL 0x6A
#define ICM20_PWR_MGMT_1 0x6B
#define ICM20_PWR_MGMT_2 0x6C
#define ICM20_FIFO_COUNTH 0x72
#define ICM20_FIFO_COUNTL 0x73
#define ICM20_FIFO_R_W 0x74
#define ICM20_WHO_AM_I 0x75
/* 加速度静态偏移 */
#define ICM20_XA_OFFSET_H 0x77
#define ICM20_XA_OFFSET_L 0x78
#define ICM20_YA_OFFSET_H 0x7A
#define ICM20_YA_OFFSET_L 0x7B
#define ICM20_ZA_OFFSET_H 0x7D
#define ICM20_ZA_OFFSET_L 0x7E
#endif
这个是主要的驱动文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#include
//#include
#include "icm20608_reg.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;//SPI CS Pin
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;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);//alloced memory
gpio_set_value(dev->cs_gpio, 0);
/*first time : send reg addr to be read*/
txdata[0] = reg | 0x80;//set reg addr bit8 to 1(write state)
t->tx_buf = txdata;
t->len = 1;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
/*second time : read data*/
txdata[0] = 0xff;//send 0xff while read ,pointless
t->rx_buf = buf;
t->len = len;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
kfree(t);//free memory
gpio_set_value(dev->cs_gpio, 1);
return ret;
}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 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;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);//alloced memory
gpio_set_value(dev->cs_gpio, 0);
/*first time : send reg addr to be read */
txdata[0] = reg & ~0x80;//set reg addr bit8 to 1(write state)
t->tx_buf = txdata;
t->len = 1;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
/*second time : send data to write*/
t->tx_buf = buf;
t->len = len;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
kfree(t);//free memory
gpio_set_value(dev->cs_gpio, 1);
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)
{
unsigned char data[14];
icm20608_read_regs(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]);
}
void icm20608_reginit(void)
{
u8 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_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)
{
signed int 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 const struct file_operations icm20608_ops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
static int icm20608_peobe(struct spi_device *spi)
{
int ret = 0;
/*1.get device id*/
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);
}
/*2.register device*/
cdev_init(&icm20608dev.cdev, &icm20608_ops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
//printk("Probe OK2!\r\n");
/*3.create class*/
icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
if(IS_ERR(icm20608dev.class))
{
printk("CLASS ERROR!!\r\n");
return PTR_ERR(icm20608dev.class);
}
//printk("Probe OK3!\r\n");
/*4.create device*/
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
if(IS_ERR(icm20608dev.device))
{
return PTR_ERR(icm20608dev.device);
}
//printk("Probe OK4!\r\n");
/*5.get cs from dts*/
//icm20608dev.nd = of_find_node_by_path("soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
if(icm20608dev.nd == NULL)
{
printk("ecspi3 node not find!\r\n");
return -EINVAL;
}
//printk("Probe OK5!\r\n");
/*6.get gpio property from dts*/
icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
if(icm20608dev.cs_gpio <0)
{
printk("can't get cs-gpio!\r\n");
return -EINVAL;
}
/*7.set gpio output and set high*/
ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
if(ret < 0)
{
printk("can't set gpio!\r\n");
}
/*8.init spi_device*/
spi->mode = SPI_MODE_0;
spi_setup(spi);
icm20608dev.private_data = spi;//set private_data
/*9.init ICM20608 inside register*/
icm20608_reginit();
printk("Probe OK!\r\n");
return 0;
}
static int icm20608_remove(struct spi_device *spi)
{
/*delete device*/
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
/*unregister class and device*/
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
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_peobe,
.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");
MODULE_AUTHOR("GYY");
这个代码是比较长的,接下来我们对代码进行分析
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include
#include
#include
#include
#include
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
signed int databuf[7];
unsigned char data[14];
signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
signed int accel_x_adc, accel_y_adc, accel_z_adc;
signed int temp_adc;
float gyro_x_act, gyro_y_act, gyro_z_act;
float accel_x_act, accel_y_act, accel_z_act;
float temp_act;
int ret = 0;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) { /* 数据读取成功 */
gyro_x_adc = databuf[0];
gyro_y_adc = databuf[1];
gyro_z_adc = databuf[2];
accel_x_adc = databuf[3];
accel_y_adc = databuf[4];
accel_z_adc = databuf[5];
temp_adc = databuf[6];
/* 计算实际值 */
gyro_x_act = (float)(gyro_x_adc) / 16.4;
gyro_y_act = (float)(gyro_y_adc) / 16.4;
gyro_z_act = (float)(gyro_z_adc) / 16.4;
accel_x_act = (float)(accel_x_adc) / 2048;
accel_y_act = (float)(accel_y_adc) / 2048;
accel_z_act = (float)(accel_z_adc) / 2048;
temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;
printf("\r\n原始值:\r\n");
printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
printf("temp = %d\r\n", temp_adc);
printf("实际值:");
printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
printf("act temp = %.2f°C\r\n", temp_act);
}
usleep(100000); /*100ms */
}
close(fd); /* 关闭文件 */
return 0;
}
应用层的代码比较简单就是实现了从模块读取数据并打印