linux SPI 驱动
Posted 九章_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux SPI 驱动相关的知识,希望对你有一定的参考价值。
系列文章
I.MX6ULL 手册查找使用方法 实战点亮LED(寄存器版)
I.MX6ULL 手册查找使用方法 实战点亮LED(固件库版本)
linux 字符设备驱动实战
linux LED设备驱动文件
linux 设备树(.dts)实战解析
linux 使用设备树点亮LED 实战
linux 驱动中并发与竞争
linux 内核定时器
linux 内核中断理解
linux 驱动阻塞和非阻塞
linux 内核异步通知
linux platform驱动框架
linux 内核自带的LED灯驱动
linux misc设备驱动
linux input子系统
linux 深入理解I2C内核驱动
文章目录
前言
SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC
的 SPI 控制器接口,不管是什么 SPI 设备,SPI 控制器部分的驱动都是一样,重点是SPI 设备驱动。
1、SPI 主机驱动
SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动,Linux 内核
使用 spi_master 表示 SPI 主机驱动,spi_master 是个结构体。
struct spi_master {
struct device dev;
struct list_head list;
s16 bus_num;
u16 num_chipselect;
u16 dma_alignment;
u16 mode_bits;
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;
/* 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_master */
void (*cleanup)(struct spi_device *spi);
bool (*can_dma)(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer);
int (*prepare_transfer_hardware)(struct spi_master *master);
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
int (*unprepare_transfer_hardware)(struct spi_master *master);
int (*prepare_message)(struct spi_master *master,
struct spi_message *message);
int (*unprepare_message)(struct spi_master *master,
struct spi_message *message);
1)transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函
数
2)transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message
SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master。
1、spi_master 申请与释放
struct spi_master *spi_alloc_master(struct device *dev,
unsigned size)
释放通过 spi_master_put 函数来完成
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 设备驱动
spi 设备驱动也和 i2c 设备驱动也很类似,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_driver 和 i2c_driver、platform_driver 基本一样,当 SPI 设备和驱动匹配成功
以后 probe 函数就会执行
spi_driver 初始化完成以后需要向 Linux 内核注册,注册与注销如下
int spi_register_driver(struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)
3、SPI 设备和驱动匹配过程
SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platform、I2C 等驱动一样,SPI
总线为 spi_bus_type
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
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;
}
- of_driver_match_device 函数用于完成设备树设备和驱动匹配
- acpi_driver_match_device 函数用于 ACPI 形式的匹配。
- spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI
设备名字和 spi_device_id 的 name 字段是否相等,
4、SPI框架, 系统已经做了什么?
SPI 主机驱动注册
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";
};
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,
}
}
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 */ }
};
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,
};
“fsl,imx6ul-ecspi”匹配项.
当设备和驱动匹配成功以后 spi_imx_probe 函数就会执行
spi_imx_probe 函数会从设备树中读取相应的节点属性值,申请并初始化 spi_master,最后
调用 spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)向 Linux 内核注册
spi_master。
spi_imx_transfer
-> spi_imx_pio_transfer
-> spi_imx_push
-> spi_imx->tx
static int spi_imx_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *of_id =
of_match_device(spi_imx_dt_ids, &pdev->dev);
struct spi_imx_master *mxc_platform_info =
dev_get_platdata(&pdev->dev);
struct spi_master *master;
struct spi_imx_data *spi_imx;
struct resource *res;
spi_imx->bitbang.chipselect = spi_imx_chipselect;
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
spi_imx->bitbang.master->setup = spi_imx_setup;
spi_imx->bitbang.master->cleanup = spi_imx_cleanup;
spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;
spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;
}
static int spi_imx_setupxfer(struct spi_device *spi,
struct spi_transfer *t)
{
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
struct spi_imx_config config;
int ret;
config.bpw = t ? t->bits_per_word : spi->bits_per_word;
config.speed_hz = t ? t->speed_hz : spi->max_speed_hz;
config.mode = spi->mode;
config.cs = spi->chip_select;
if (!config.speed_hz)
config.speed_hz = spi->max_speed_hz;
if (!config.bpw)
config.bpw = spi->bits_per_word;
/* 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;
}
#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)
5、SPI框架, 开发人员需要做什么?
1、IO 的 pinctrl 子节点创建与修改
使用的 IO 来创建或修改 pinctrl 子节点,管脚复用。
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复用功能,设置片选,时钟,MISO,MOSI
2、SPI 设备节点的创建与修改
&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>;
};
};
设备树SPI理解
- fsl,spi-num-chipselects”属性为 1,表示只有一个设备
- “cs-gpios”属性,也就是片选信号为 GPIO4_IO09
- “pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点
- 将 ecspi1 节点的“status”属性改为“okay”
- m25p80@0”后面的“0”表示 m25p80 的接到了 ECSPI 的通道 0上
- SPI 设备的 compatible 属性值,用于匹配设备驱动
- “spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的SPI 设备来设置
- reg 属性设置 m25p80 这个设备所使用的 ECSPI 通道,0:代表0通道与m25p80@0 的0一致。
3、SPI 设备数据收发处理流程
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
unsigned cs_change:1;
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;
/* 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;
};
spi_message相关的函数
1、初始化函数为spi_message_init
void spi_message_init(struct spi_message *m)
2、将 spi_transfer 添加到 spi_message 队列中
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
3、数据传输
同步传输 阻塞
int spi_sync(struct spi_device *spi, struct spi_message *message)
异步传输
int spi_async(struct spi_device *spi, struct spi_message *message)
异步传输需要设置 spi_message 中的 complete成员变量,complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。
总结:SPI 数据传输步骤如下:
1. 申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量
2. 使用 spi_message_init 函数初始化 spi_message
3. 使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中
4. 使用 spi_sync 函数完成 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;
}
/*多字节接收 */
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;
}
4、SPI 设备驱动(spi_driver)
驱动模板:
static int xxx_probe(struct spi_device *spi)
{
}
static int xxx_remove(struct spi_device *spi)
{
}
/* 传统匹配方式ID列表 */
static const struct spi_device_id ixxx_id[] = {
{"alientek,icm20608", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "alientek,icm20608" },
{ /* Sentinel */ }
};
/* SPI驱动结构体 */
static struct spi_driverxxx_driver = {
.probe = ixxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = icm20608_id,
};
static int __init xxx_init(void)
{
return spi_register_driver(&ixxx_driver);
}
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&xxx_driver);
}
5、编写应用程序,通过字符设备驱动(ops),读取数据
6、SPI 实战 (ICM-20608)
ICM-20608 是 InvenSense 出品的一款 6 轴 MEMS 传感器,包括 3 轴加速度和 3 轴陀螺仪
其特性如下:
- 陀螺仪支持 X,Y 和 Z 三轴输出,内部集成 16 位 ADC,测量范围可设置:±250,±
500,±1000 和±2000°/s - 加速度计支持 X,Y 和 Z 轴输出,内部集成 16 位 ADC,测量范围可设置:±2g,±4g,
±4g,±8g 和±16g。 - 用户可编程中断
- 内部包含 512 字节的 FIFO
- 内部包含一个数字温度传感器。
- 耐 10000g 的冲击
- 支持快速 I2C,速度可达 400KHz
- 支持 SPI,速度可达 8MHz
关于 ICM-20608 的详细寄存器和位的介绍请参考 ICM-20608 的寄存器手册
原理图如下:
1、IO 的 pinctrl 子节点创建与修改
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*/
>;
};
UART2_TX_DATA 这个 IO 是 ICM20608 的片选信号,复用为了普通的 GPIO
2、SPI 设备节点的创建与修改
设备树中,在 ecspi3 节点追加 icm20608 子节点
&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>;
};
};
SPI说明:
- spi-num-chipselects 设置当前片选数量为 1,因为就只接了一个 ICM20608
- cs-gpio 自己定义的“cs-gpio”属性,因为我们要自己控制片选引脚
- 设置 IO 要使用的 pinctrl 子节点,节点信息见1(IO 的 pinctrl 子节点创建与修改)
- 节点状态(status)设置为“enable”
- icm20608 设备子节点,因为 icm20608 连接在 ECSPI3 的第 0 个通道上,因此@后面为 0
- 节点属性兼容值为“alientek,icm20608”,
- 设置 SPI 最大时钟频率为 8MHz,这是 ICM20608 的 SPI 接口所能支持的最大的时钟频率。
- icm20608 连接在通道 0 上,因此 reg 为 0
4、SPI 设备驱动(spi_driver)
.h 文件
#ifndef ICM20608_H
#define ICM20608_H
#define ICM20608G_ID 0XAF /* ID值 */
#define ICM20608D_ID 0XAE /* ID值 */
/*
*复位后所有寄存器地址都为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
.c 文件
#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; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
int cs_gpio; /* 片选所使用的GPIO编号 */
signed int gyro_x_adc; /* 陀螺仪X轴原始值 */
signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */
signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */
signed int accel_x_adc; /* 加速度计X轴原始值 */
signed int accel_y_adc; /* 加速度计Y轴原始值 */
signed int accel_z_adc; /* 加速度计Z轴原始值 */
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)
{
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); /* 申请内存 */
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */
/* 第1次,发送要读取的寄存地址 */
txdata[0] = reg & ~0x80; /* 写数据的时候寄存器地址bit8要清零 */
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次,发送要写入的数据 */
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)
{
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]);
}
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;
}
/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
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_probe(struct spi_device *spi)
{
int ret = 0;
/* 1、构建设备号 */
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、注册设备 */
cdev_init(&icm20608dev.cdev, &icm20608_ops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
/* 3、创建类 */
icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
if (IS_ERR(icm20608dev.class)) {
return PTR_ERR(icm20608dev.class);
}
/* 4、创建设备 */
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
if (IS_ERR(icm20608dev.device)) {
return PTR_ERR(icm20608dev.device);
}
/* 获取设备树中cs片选信号 */
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;
}
/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
if(icm20608dev.cs_gpio < 0) {
printk("can't get cs-gpio");
return -EINVAL;
}
/* 3、设置GPIO1_IO20为输出,并且输出高电平 */
ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\\r\\n");
}
/*初始化spi_device */
spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
spi_setup(spi);
icm20608dev.private_data = spi; /* 设置私有数据 */
/* 初始化ICM20608内部寄存器 */
icm20608_reginit();
return 0;
}
static int icm20608_remove(struct spi_device *spi)
{
/* 删除设备 */
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
/* 注销掉类和设备 */
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
{"alientek,icm20608", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek,icm20608" },
{ /* Sentinel */ }
};
/* SPI驱动结构体 */
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");
应用程序
#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 <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
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;
}
以上是关于linux SPI 驱动的主要内容,如果未能解决你的问题,请参考以下文章