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

  1. of_driver_match_device 函数用于完成设备树设备和驱动匹配
  2. acpi_driver_match_device 函数用于 ACPI 形式的匹配。
  3. 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理解

  1. fsl,spi-num-chipselects”属性为 1,表示只有一个设备
  2. “cs-gpios”属性,也就是片选信号为 GPIO4_IO09
  3. “pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点
  4. 将 ecspi1 节点的“status”属性改为“okay”
  5. m25p80@0”后面的“0”表示 m25p80 的接到了 ECSPI 的通道 0上
  6. SPI 设备的 compatible 属性值,用于匹配设备驱动
  7. “spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的SPI 设备来设置
  8. 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 轴陀螺仪
其特性如下:

  1. 陀螺仪支持 X,Y 和 Z 三轴输出,内部集成 16 位 ADC,测量范围可设置:±250,±
    500,±1000 和±2000°/s
  2. 加速度计支持 X,Y 和 Z 轴输出,内部集成 16 位 ADC,测量范围可设置:±2g,±4g,
    ±4g,±8g 和±16g。
  3. 用户可编程中断
  4. 内部包含 512 字节的 FIFO
  5. 内部包含一个数字温度传感器。
  6. 耐 10000g 的冲击
  7. 支持快速 I2C,速度可达 400KHz
  8. 支持 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说明:

  1. spi-num-chipselects 设置当前片选数量为 1,因为就只接了一个 ICM20608
  2. cs-gpio 自己定义的“cs-gpio”属性,因为我们要自己控制片选引脚
  3. 设置 IO 要使用的 pinctrl 子节点,节点信息见1(IO 的 pinctrl 子节点创建与修改)
  4. 节点状态(status)设置为“enable”
  5. icm20608 设备子节点,因为 icm20608 连接在 ECSPI3 的第 0 个通道上,因此@后面为 0
  6. 节点属性兼容值为“alientek,icm20608”,
  7. 设置 SPI 最大时钟频率为 8MHz,这是 ICM20608 的 SPI 接口所能支持的最大的时钟频率。
  8. 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 驱动的主要内容,如果未能解决你的问题,请参考以下文章

Linux——Linux驱动之玩转SPI(上)Linux下SPI驱动框架简析及SPI设备驱动代码框架实现步骤

Linux驱动分析之SPI控制器

Linux驱动分析之SPI设备

Linux驱动开发SPI

Linux驱动开发SPI

Linux驱动开发SPI