Linux驅動開發(十九):SPI驅動

簡介

SPI同樣使我們在單片機開發中較爲常用的通信接口,常用於諸如FLASH、OLED、SD卡的流式數據的讀寫,全雙工總線,具體的關於協議的知識這裏就不說了,我們主要討論Linux的SPI設備驅動框架以及我們如何去編寫一個SPI設備驅動
Linux下的SPI驅動和I2C驅動類似,也是分爲主機控制器驅動和設備驅動

驅動框架介紹

SPI主機驅動

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內核註冊

spi_master的申請與釋放

  • 申請:
    struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
    dev:設備,一般是platform_device中的dev成員變量
    size:私有數據大小,可以通過spi_master_get_devdata函數獲取這些私有數據
  • 釋放:
    static inline void spi_master_put(struct spi_master *master)

spi_master的註冊與註銷

  • 註冊:
    int spi_register_master(struct spi_master *master);
  • 註銷:
    void spi_unregister_master(struct spi_master *master);

SPI設備驅動

與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_driver註冊與註銷

  • 註冊:
    int spi_register_driver(struct spi_driver *sdrv);
  • 註銷:
    void spi_unregister_driver(struct spi_driver *sdrv)

SPI驅動和設備的匹配過程

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)

通過使用‘##’拼接符的方式巧妙的創建了六個函數

SPI設備驅動編寫流程

SPI設備描述

pinctrl子節點的創建與修改

根據使用的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配置

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

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設備驅動的核心是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數據傳輸的步驟

  • 1、申請並初始化 spi_transfer,設置 spi_transfer的 tx_buf成員變量, tx_buf爲要發送的數據。然後設置 rx_buf成員變量, rx_buf保存着接收到的數據。最後設置 len成員變量,也就是要進行數據通信的長度。
  • 2、使用 spi_message_init函數初始化 spi_message
  • 3、使用 spi_message_add_tail函數將前面設置好的 spi_transfer添加到 spi_message隊中。
  • 4、使用 spi_sync函數完成 SPI數據同步傳輸。

實驗程序編寫

硬件介紹

我們使用的模塊是正點原子開發板上板載的icm20608六軸傳感器模塊
可以讀到的數據爲溫度、3軸加速度、3軸角速度數據
我們的編寫的是設備驅動,所以分爲修改設備樹、編寫驅動程序、編寫應用程序三個部分

修改設備樹

pinctrl

該傳感器連接在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

&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

編寫驅動程序

icm20608_reg.h

這個頭文件定義了一些相關的寄存器地址

#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

icm20608_driver.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 <linux/slab.h>
//#include <linux/gfp.h>
#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");

這個代碼是比較長的,接下來我們對代碼進行分析

驅動代碼分析

  • 在icm20608_init函數中我們調用了spi_register_driver來註冊了一個SPI驅動,傳入了一個icm20608_driver 參數
  • icm20608_driver 中定義了probe和remove函數以及設備和驅動的匹配規則,我們可以使用設備樹和id_tables兩種匹配方式
  • 在probe函數中主要完成字符設備的註冊、GPIO的獲取以及初始化以及SPI設備的初始化
  • 設備驅動的實現關鍵就是提供給應用層接口,icm20608_ops就是該設備驅動的操作函數,我們實現了icm20608_open、icm20608_read和icm20608_release函數
  • icm20608_read函數中我們就實現了從模塊讀取溫度、角速度、加速度數據,我們可以在應用層調用read函數來讀取

應用層代碼

#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>

/*
 * @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;
}

應用層的代碼比較簡單就是實現了從模塊讀取數據並打印

發佈了123 篇原創文章 · 獲贊 616 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章