Linux SPI 解析

设备框架

在linux系统中,为了提高代码利用率,引入了设备驱动模型分层的概念,将代码分为驱动和设备,并利用总线对设备和驱动进行管理。SPI子系统便是由SPI总线对设备和驱动进行管理。
在这里插入图片描述

SPI总线注册

首先检查Linux下Menuconfig中SPI总线是否开启,具体目录为->device drivers->spi support,打开Freescale i.MX SPI controllers。
打开后,Linux系统会在开机时进行SPI总线注册。

static int __init spi_init(void)
{
	int	status;

	buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
	if (!buf) {
		status = -ENOMEM;
		goto err0;
	}

	status = bus_register(&spi_bus_type);
	if (status < 0)
		goto err1;

	status = class_register(&spi_master_class);
	if (status < 0)
		goto err2;

	if (IS_ENABLED(CONFIG_OF_DYNAMIC))
		WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));

	return 0;

err2:
	bus_unregister(&spi_bus_type);
err1:
	kfree(buf);
	buf = NULL;
err0:
	return status;
}

在这个函数中,总线注册成功后,会在sys/bus 下面生成一个spi 总线,然后在系统中新增一个设备类,sys/class/目录下会可以找到spi_master 类。

设备匹配

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
};

当属于spi_bus_type的device或者driver注册时,会调用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;
}
  • of_driver_match_device为第一种匹配方法,它会将设备驱动中的of_match_table与设备树中的compatible属性进行匹配;
  • acpi_driver_match_device为第二种匹配方法,它会将acpi_match_table的compatible和device的of_node的compatible匹配;
  • spi_match_id会比较spi设备名字和spi_device_id的name属性相等;
  • 最后,会比较spi_device中modalias成员和device_driver中的name成员变量是否相等。

主机驱动

IMX6ULL共有4个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";
};

该设备树的初始化代码在->drivers->spi->spi-imx.c中

static struct platform_driver spi_imx_driver = {
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = spi_imx_dt_ids,
		   .pm = IMX_SPI_PM,
	},
	.id_table = spi_imx_devtype,
	.probe = spi_imx_probe,
	.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);

可以看出,虽然SPI总线是bus_type,但是spi设备驱动仍是platform_driver,其中of_match_table为

static const struct of_device_id spi_imx_dt_ids[] = {
		...
	{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },
	{ /* sentinel */ }
};

当匹配成功后,会调用probe函数spi_imx_probe并初始化spi_master

	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;
	int i, ret, num_cs, irq;

	if (!np && !mxc_platform_info) {
		dev_err(&pdev->dev, "can't get the platform data\n");
		return -EINVAL;
	}

	ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
	if (ret < 0) {
		if (mxc_platform_info)
			num_cs = mxc_platform_info->num_chipselect;
		else
			return ret;
	}

	master = spi_alloc_master(&pdev->dev,
			sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
	if (!master)
		return -ENOMEM;

将spi_master指向bitbang并设置CS个数,初始化传输函数引脚等

	spi_imx = spi_master_get_devdata(master);
	spi_imx->bitbang.master = master;

	for (i = 0; i < master->num_chipselect; i++) {
		int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
		if (!gpio_is_valid(cs_gpio) && mxc_platform_info)
			cs_gpio = mxc_platform_info->chipselect[i];

		spi_imx->chipselect[i] = cs_gpio;
		if (!gpio_is_valid(cs_gpio))
			continue;

		ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],
					DRIVER_NAME);
		if (ret) {
			dev_err(&pdev->dev, "can't get cs gpios\n");
			goto out_master_put;
		}
	}

	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;
	spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;

最后,调用spi_bitbang_start函数,该函数会调用spi_register_master函数,注册spi_master。

int spi_register_master(struct spi_master *master) {
	...
	status = of_spi_register_master(master);
	if (status)
		return status;
	...
	dev_set_name(&master->dev, "spi%u", master->bus_num);
	status = device_add(&master->dev);
	if (status < 0)
		goto done;
	dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
			dynamic ? " (dynamic)" : "");

	if (master->transfer)
		dev_info(dev, "master is unqueued, this is deprecated\n");
	else {
		status = spi_master_initialize_queue(master);
		if (status) {
			device_del(&master->dev);
			goto done;
		}
	}
	...
	mutex_lock(&board_lock);
	list_add_tail(&master->list, &spi_master_list);
	list_for_each_entry(bi, &board_list, list)
		spi_match_master_to_boardinfo(master, &bi->board_info);
	mutex_unlock(&board_lock);
	...
	of_register_spi_devices(master);
	...
done:
	return status;
}

该函数会判断初始化片选引脚是否添加成功,并判断spi_master是否设置了transfer函数,最后,其会将master加入链表中并注册spi设备信息。

数据传输流程

从前面的分析中不难看出spi数据传输函数为spi_imx_transfer,该函数需要spi_device和spi_transfer结构体,该结构体的具体内容如下:

struct spi_device {
        struct device         dev;      /*spi控制器对应的device结构
        struct spi_master   *master;      /*设备使用的master结构,挂在哪个主控制器下*/
        u32                     max_speed_hz;   /*通讯时钟最大频率*/
        u8                      chip_select;     /*片选号,每个master支持多个spi_device  */ 
        u8                      mode;
#define SPI_CPHA        0x01                    /* clock phase */
#define SPI_CPOL        0x02                    /* clock polarity */
#define SPI_MODE_0      (0|0)                   /* (original MicroWire) */
#define SPI_MODE_1      (0|SPI_CPHA)
#define SPI_MODE_2      (SPI_CPOL|0)
#define SPI_MODE_3      (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH     0x04                    /* chipselect active high? */
#define SPI_LSB_FIRST   0x08                    /* per-word bits-on-wire */
#define SPI_3WIRE       0x10                    /* SI/SO signals shared */
#define SPI_LOOP        0x20                    /* loopback mode */
#define SPI_NO_CS       0x40                    /* 1 dev/bus, no chipselect */
#define SPI_READY       0x80                    /* slave pulls low to pause */
        u8                      bits_per_word;    /*每个字长的比特数,默认是8*/
        int                     irq;
        void                    *controller_state; /*控制器状态*/
        void                    *controller_data;   /*控制器数据*/
        char                    modalias[SPI_NAME_SIZE]; /* 设备驱动的名字 */
        int                     cs_gpio;        /* chip select gpio */

};

一个device代表一个物理意义上的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; /*缓冲区长度,tx和rx的大小(字节数)。指它们各自的大小*/

        dma_addr_t      tx_dma; /*tx的dma地址*/
        dma_addr_t      rx_dma;  /*rx的dma地址*/
 
        unsigned        cs_change:1; /*当前spi_transfer发送完成之后重新片选*/
        u8              bits_per_word; /*每个字长的比特数,0代表使用spi_device中的默认值8*/
        u16             delay_usecs; /*发送完成一个spi_transfer后的延时时间,此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息*/
        u32             speed_hz; /*通信时钟。如果是0,使用默认值*/

#ifdef CONFIG_SPI_LOMBO
        struct lombo_spi_operate_para *esop;
#endif

        struct list_head transfer_list; /*用于链接到spi_message,用来连接的双向链接节点*/

};

数据准备

static inline void spi_message_init(struct spi_message *m)
{
	memset(m, 0, sizeof *m);
	INIT_LIST_HEAD(&m->transfers);
}

初始化spi_message:清空message,初始化transfers链表头。

static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
	list_add_tail(&t->transfer_list, &m->transfers);
}

将spi_transfer加入到spi_message的链表尾部。

数据传输

系统提供了两种传输方式,分别是同步和异步,所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。而异步方式则正好相反,数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。

设备驱动程序编写

使用ICM20608作为实验设备,代码参考自正点原子。首先需要在设备树下添加pinctrl节点描述ICM20608的引脚信息,

		pinctrl_ecspi3: ecspi3grp{
			fsl,pins = <
				MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 		0x10b0 //片选信号
				MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK        0x10b1 //CLK信号
				MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI			0x10b1 //MOSI信号
				MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO			0x10b1 //MISO信号
			>;
		};

然后在ecspi3中追加ICM20608子节点

&ecspi3{
	fsl,spi-num-chipselects = <1>;					/* 一个片选 */
	cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;	/* 片选引脚,软件片选 */
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi3>;//设置 IO 要使用的 pinctrl 子节点
	status = "okay";

/* 对应的icm20608子节点 */
	spidev0: icm20608@0 {/* @后面的0表示icm20608连接到 ECSPI3 的第 0 个通道上*/
		reg = <0>;//表示icm20608连接到 ECSPI3 的第 0 个通道上
		compatible = "alientek,icm20608";//兼容属性
		spi-max-frequency = <8000000>;/* SPI时钟频率8MHZ */
	};
};

随后便可编写SPI驱动

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include "icm20608reg.h"

#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"

/* 设备结构体 */
struct icm20608_dev
{
    int major;
    int minor;
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    void *private_data;
    int cs_gpio;
    struct device_node *nd;
    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;

#if 0
/* SPI读寄存器 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    int ret = 0;
    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);

    /* 构建spi_transfer */
    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);

    /* 第一步:发送要读取的地址 */
    txdata[0] = reg | 0x80;
    t->tx_buf = txdata;
    t->len = 1;

    spi_message_init(&m);
    spi_message_add_tail(t, &m);
    ret = spi_sync(spi, &m);

    /* 第二步:读取数据 */
    txdata[0] = 0xff; /* 无效的 */
    t->rx_buf = buf;
    t->len = len;

    spi_message_init(&m);
    spi_message_add_tail(t, &m);
    ret = spi_sync(spi, &m);

    kfree(t);

    gpio_set_value(dev->cs_gpio, 1); /* 拉高片选 */
    return ret;
}
/* SPI写寄存器 */
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
{
    int ret = 0;
    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);

    /* 构建spi_transfer */
    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);

    /* 第一步:发送要读取的地址 */
    txdata[0] = reg & ~0x80;
    t->tx_buf = txdata;
    t->len = 1;

    spi_message_init(&m);
    spi_message_add_tail(t, &m);
    ret = spi_sync(spi, &m);

    /* 第二步:读取数据 */

    t->tx_buf = buf;
    t->len = len;

    spi_message_init(&m);
    spi_message_add_tail(t, &m);
    ret = spi_sync(spi, &m);

    kfree(t);

    gpio_set_value(dev->cs_gpio, 1); /* 拉高片选 */
    return ret;
}
#endif

/* SPI读寄存器 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    u8 data = 0;
    struct spi_device *spi = (struct spi_device *)dev->private_data;
    //gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */

    data = reg | 0x80;
    spi_write_then_read(spi, &data, 1, buf, len);

    // spi_write(spi, &data, 1); /* 发送要读取的寄存器地址 */
    // spi_read(spi, buf, len);  /*读取数据*/

    //gpio_set_value(dev->cs_gpio, 1); /* 拉高片选 */
    return 0;
}

/* SPI写寄存器 */
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
{
    u8 data = 0;
    u8 *txdata;
    struct spi_device *spi = (struct spi_device *)dev->private_data;

    txdata = kzalloc(len + 1, GFP_KERNEL);

    //gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */

    txdata[0] = reg & ~0x80;         /* 要写的寄存器地址 */
    memcpy(&txdata[1], buf, len);    /* 要发送的数据拷贝到txdata里面 */
    spi_write(spi, txdata, len + 1); /* 发送要写的寄存器地址 */

    // spi_write(spi, &data, 1); /* 发送要写的寄存器地址 */
    // spi_write(spi, buf, len); /* 发送要写的寄存器地址 */

    kfree(txdata);
    //gpio_set_value(dev->cs_gpio, 1); /* 拉高片选 */
    return 0;
}

/*ICM20608读取单个寄存器 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
    u8 data = 0;
    icm20608_read_regs(dev, reg, &data, 1);
    return data;
}

/*ICM20608写一个寄存器 */
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    u8 buf = value;
    icm20608_write_regs(dev, reg, &buf, 1);
}

/*
 * @description	: 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
 * 				: 三轴加速度计和内部温度。
 * @param - dev	: ICM20608设备
 * @return 		: 无。
 */
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]);
}

/* ICM20608初始化 */
void icm20608_reginit(struct icm20608_dev *dev)
{
    u8 value = 0;
    icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80); /* 复位,复位后为0x40,睡眠模式 */
    mdelay(50);
    icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01); /* 关闭睡眠,自动选择时钟 */
    mdelay(50);

    value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);
    printk("ICM20608 ID=%#X\r\n", value);

    value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);
    printk("ICM20_PWR_MGMT_1=%#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;
}

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_fops = {
    .owner = THIS_MODULE,
    .open = icm20608_open,
    .read = icm20608_read,
    .release = icm20608_release,
};

/*
* @description : spi 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
* @param - spi : spi 设备
*/
static int icm20608_probe(struct spi_device *spi)
{
    int ret = 0;
    printk("icm20608_probe\r\n");

    /* 搭建字符设备驱动框架,在/dev/下 */
    /* 2,注册字符设备 */
    icm20608dev.major = 0; /* 由系统分配主设备号 */

    if (icm20608dev.major)
    { /* 给定主设备号 */
        icm20608dev.devid = MKDEV(icm20608dev.major, 0);
        ret = register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
    }
    else
    { /* 没有给定主设备号 */
        ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
        icm20608dev.major = MAJOR(icm20608dev.devid);
        icm20608dev.minor = MINOR(icm20608dev.devid);
    }
    if (ret < 0)
    {
        printk("icm20608 chrdev_region err!\r\n");
        goto fail_devid;
    }
    printk("icm20608 major=%d, minor=%d\r\n", icm20608dev.major, icm20608dev.minor);

    /* 3,注册字符设备 */
    icm20608dev.cdev.owner = THIS_MODULE;
    cdev_init(&icm20608dev.cdev, &icm20608_fops);
    ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
    if (ret < 0)
    {
        goto fail_cdev;
    }

    /* 4,自动创建设备节点 */
    icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
    if (IS_ERR(icm20608dev.class))
    {
        ret = PTR_ERR(icm20608dev.class);
        goto fail_class;
    }

    icm20608dev.device = device_create(icm20608dev.class, NULL,
                                       icm20608dev.devid, NULL, ICM20608_NAME);
    if (IS_ERR(icm20608dev.device))
    {
        ret = PTR_ERR(icm20608dev.device);
        goto fail_device;
    }

#if 0
    /* 获取片选引脚 */
    icm20608dev.nd = of_get_parent(spi->dev.of_node);
    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");
        goto fail_gpio;
    }
    ret = gpio_request(icm20608dev.cs_gpio, "cs");
    if (ret < 0)
    {
        printk("cs_gpio request failed!\r\n");
    }
    ret = gpio_direction_output(icm20608dev.cs_gpio, 1); /* 默认高电平 */
#endif

    /* 初始化spi_device */
    spi->mode = SPI_MODE_0;
    spi_setup(spi);

    /* 设置icm20608dev的私有数据为spi */
    icm20608dev.private_data = spi;

    /* 初始化icm20608 寄存器 */
    icm20608_reginit(&icm20608dev);

    return 0;
fail_gpio:

fail_device:
    class_destroy(icm20608dev.class);
fail_class:
    cdev_del(&icm20608dev.cdev);
fail_cdev:
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
fail_devid:
    return ret;
}

/*
* @description : i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
* @param - spi : spi 设备
*/
static int icm20608_remove(struct spi_device *spi)
{
    /* 1,删除字符设备 */
    cdev_del(&icm20608dev.cdev);

    /* 2,注销设备号 */
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);

    /* 3,摧毁设备 */
    device_destroy(icm20608dev.class, icm20608dev.devid);

    /* 4,摧毁类 */
    class_destroy(icm20608dev.class);

    /*5.释放片选IO */
    gpio_free(icm20608dev.cs_gpio);

    return 0;
}

/* 传统匹配方式 ID 列表 */
struct spi_device_id icm20608_id[] = {
    {"alientek,icm20608", 0},
    {}};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
    {
        .compatible = "alientek,icm20608",
    },
    {}};

/* SPI 驱动结构体 */
struct spi_driver icm20608_driver = {

    .probe = icm20608_probe,
    .remove = icm20608_remove,

    .driver = {
        .name = "icm20608",
        .owner = THIS_MODULE,
        .of_match_table = icm20608_of_match,
    },

    .id_table = icm20608_id,

};

/*驱动入口函数*/
static int __init icm20608_init(void)
{
    int ret = 0;
    ret = spi_register_driver(&icm20608_driver);

    return ret;
}

/*驱动出口函数*/
static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值