Linux驱动编程 - SPI子系统

目录

一、spi驱动框架整体介绍

二、platform匹配过程

1、dts设备树中的spi

2、spi的platform_driver驱动

3、spi_imx_probe() 中创建 spi_master 和 spi_device 过程分析

3.1 静态注册

3.2 dts设备树注册spi_device

三、spi_device 和 spi_driver匹配过程

1、spi_bus_type 总线 - drivers/spi/spi.c

2、spi设备 - dts设备树/spi添加子设备

3、spi驱动 - spi_driver驱动代码编写

3.1 spi_register_driver函数流程


简介:


SPI子系统和I2C框架很相似,源码中的主要差异如下表:

I2C

SPI

主机适配器(控制器)

struct i2c_adapter

struct spi_master

机控制器的操作方法

struct i2c_algorithm

struct spi_bitbang

从机设备

struct i2c_client

struct spi_device

从机设备板卡信息

struct i2c_board_info

struct spi_board_info

从机设备驱动

struct i2c_driver

struct spi_driver

一次完整的数据包

struct i2c_msg

struct spi_transfer

多个完整数据包的封装

struct spi_message

本文基于 imx6ull 分析

一、spi驱动框架整体介绍


        设备树 /spi 节点一般表示 spi控制器, 它会被转换为 platform_device, 在内核中有对应的platform_driver ;一般为厂商所配套的 platform_driver 文件(freescale的处理文件为spi-imx.c)。匹配后,运行platform_driver的probe函数,probe函数中会调用 spi_register_master(),注册spi_master,然后将设备树spi子节点都转换为 spi_device。每次注册spi_device 或 spi_driver 都会将dts中spi设备节点的 compatible 和 spi_driver 中的of_match_table 做匹配, 如果匹配上就调用 spi_driver 的 .probe 函数。

整个过程如图(借用韦东山课程中的图,从右往左看):

二、platform匹配过程


platform平台设备驱动模型参考:Linux驱动编程 - platform平台设备驱动总线_linux platform驱动编写-优快云博客

1、dts设备树中的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";
};

设备树的spi节点会生成 spi的 platform_device 设备。

2、spi的platform_driver驱动

/* drivers/spi/spi-imx.c */
static const struct of_device_id spi_imx_dt_ids[] = {
     ... ...
    { .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,
};
module_platform_driver(spi_imx_driver);

dts设备树的spi节点的 compatible 和 platform_driver 的 of_match_table的 compatible 匹配,则调用probe函数 spi_imx_probe。

3、spi_imx_probe() 中创建 spi_master 和 spi_device 过程分析

函数调用关系:

spi_imx_probe()				//drivers/spi/spi-imx.c
--->spi_bitbang_start()		//drivers\spi\spi-bitbang.c
--->--->spi_register_master()	//include/linux/spi/spi.h
--->--->--->spi_register_controller()	//drivers/spi/spi.c
--->--->--->--->device_add() /* 注册 spi master,如 spi0 */
/* 静态方式,循环 board_list 中的设备,调用spi_match_controller_to_boardinfo 如果有与本适配器匹配,就调用 spi_new_device() 注册 */
--->--->--->--->list_for_each_entry(bi, &board_list, list)
                	spi_match_controller_to_boardinfo(ctlr, &bi->board_info);
--->--->--->--->--->spi_new_device()
--->--->--->--->of_register_spi_devices();	//查询设备树spi子节点
--->--->--->--->--->for_each_available_child_of_node(ctlr->dev.of_node, nc) {
                		spi = of_register_spi_device(ctlr, nc);	//解析spi子节点内容,并注册 spi_device
            		}
--->--->--->--->--->--->spi_add_device()
--->--->--->--->--->--->--->device_add()		//注册spi子节点

spi_imx_probe() 源码分析:

static int spi_imx_probe(struct platform_device *pdev)
{
    /* 申请spi_master ,最后将 是spi_master 挂接到 spi_imx 结构体 */
    master = spi_alloc_master(&pdev->dev,
            sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
    /* ... */
    ret = spi_bitbang_start(&spi_imx->bitbang);
    /* ... */
}

spi_bitbang_start()分析

int spi_bitbang_start(struct spi_bitbang *bitbang)
{
    /* 配置 spi_master 各种回调函数 */
    /* .. */
    /* 注册spi控制器spi_master */
    ret = spi_register_master(spi_master_get(master));
}

spi_register_master()分析

int spi_register_master(struct spi_master *master)
{
    /* 不重要,获取片选啥的 */
    status = of_spi_register_master(master);
  
    /* 获取spi下标号 */
    if ((master->bus_num < 0) && master->dev.of_node)
        master->bus_num = of_alias_get_id(master->dev.of_node, "spi");

    /* 注册spi_master设备到 spi_bus_type,之后在sysfs可以看到 spi0 */
    dev_set_name(&master->dev, "spi%u", master->bus_num);
    status = device_add(&master->dev);

    /* 配置SPI队列 */
    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;
        }
    }

/* 静态方式,循环 board_list 中的设备,调用spi_match_master_to_boardinfo 
    如果有与本适配器匹配,就调用 spi_new_device() 注册
*/
    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);

/* 动态方式,从设备树spi子节点注册设备 */
    of_register_spi_devices(master);
}

3.1 静态注册

spi_match_master_to_boardinfo()源码

static void spi_match_master_to_boardinfo(struct spi_master *master,
                struct spi_board_info *bi)
{
    struct spi_device *dev;

    /* 判断bus_num */
    if (master->bus_num != bi->bus_num)
        return;

    /* 调用 spi_new_device() 注册 */
    dev = spi_new_device(master, bi);
    if (!dev)
        dev_err(master->dev.parent, "can't create new device for %s\n",
            bi->modalias);
}

3.2 dts设备树注册spi_device

of_register_spi_devices() 函数中,循环获取spi每个子节点调用 of_register_spi_device(),解析子节点内容并调用 spi_add_device() 注册 spi_device

static void of_register_spi_devices(struct spi_master *master)
{
    /* 注册设备树spi每一个子节点 */
    for_each_available_child_of_node(master->dev.of_node, nc) {
        spi = of_register_spi_device(master, nc);
        if (IS_ERR(spi))
            dev_warn(&master->dev, "Failed to create SPI device for %s\n",
                nc->full_name);
    }
}
#if defined(CONFIG_OF)
static struct spi_device *
of_register_spi_device(struct spi_master *master, struct device_node *nc)
{
    struct spi_device *spi;
    int rc;
    u32 value;

    /* Alloc an spi_device 分配一个spi设备 */
    spi = spi_alloc_device(master);
    if (!spi) {
        dev_err(&master->dev, "spi_device alloc error for %s\n",
            nc->full_name);
        rc = -ENOMEM;
        goto err_out;
    }

    /* Select device driver 获取 compatibel 属性 用于匹配spi driver */
    rc = of_modalias_node(nc, spi->modalias,
                sizeof(spi->modalias));
    if (rc < 0) {
        dev_err(&master->dev, "cannot find modalias for %s\n",
            nc->full_name);
        goto err_out;
    }

    /* Device address 获取 reg 属性作为片选编号 */
    rc = of_property_read_u32(nc, "reg", &value);
    if (rc) {
        dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",
            nc->full_name, rc);
        goto err_out;
    }
    spi->chip_select = value;

    /* Mode (clock phase/polarity/etc.) spi mode选择 */
    if (of_find_property(nc, "spi-cpha", NULL))
        spi->mode |= SPI_CPHA;
    if (of_find_property(nc, "spi-cpol", NULL))
        spi->mode |= SPI_CPOL;
    if (of_find_property(nc, "spi-cs-high", NULL))
        spi->mode |= SPI_CS_HIGH;
    if (of_find_property(nc, "spi-3wire", NULL))
        spi->mode |= SPI_3WIRE;
    if (of_find_property(nc, "spi-lsb-first", NULL))
        spi->mode |= SPI_LSB_FIRST;

    /* Device DUAL/QUAD mode */
    if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
        switch (value) {
        case 1:
            break;
        case 2:
            spi->mode |= SPI_TX_DUAL;
            break;
        case 4:
            spi->mode |= SPI_TX_QUAD;
            break;
        default:
            dev_warn(&master->dev,
                "spi-tx-bus-width %d not supported\n",
                value);
            break;
        }
    }

    if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
        switch (value) {
        case 1:
            break;
        case 2:
            spi->mode |= SPI_RX_DUAL;
            break;
        case 4:
            spi->mode |= SPI_RX_QUAD;
            break;
        default:
            dev_warn(&master->dev,
                "spi-rx-bus-width %d not supported\n",
                value);
            break;
        }
    }

    /* Device speed spi速度设置 */
    rc = of_property_read_u32(nc, "spi-max-frequency", &value);
    if (rc) {
        dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
            nc->full_name, rc);
        goto err_out;
    }
    spi->max_speed_hz = value;

    /* IRQ */
    spi->irq = irq_of_parse_and_map(nc, 0);

    /* Store a pointer to the node in the device structure */
    of_node_get(nc);//保存设备树节点
    spi->dev.of_node = nc;

    /* Register the new device 注册新的spi设备 */
    rc = spi_add_device(spi);
    if (rc) {
        dev_err(&master->dev, "spi_device register error %s\n",
            nc->full_name);
        goto err_out;
    }

    return spi;

err_out:
    spi_dev_put(spi);
    return ERR_PTR(rc);
}

spi_add_device()分析

int spi_add_device(struct spi_device *spi)
{
    static DEFINE_MUTEX(spi_add_lock);
    struct spi_master *master = spi->master;
    struct device *dev = master->dev.parent;
    int status;

    /* Chipselects are numbered 0..max; validate. */
    if (spi->chip_select >= master->num_chipselect) {
        dev_err(dev, "cs%d >= max %d\n",
            spi->chip_select,
            master->num_chipselect);
        return -EINVAL;
    }

    /* Set the bus ID string */
    spi_dev_set_name(spi);

    /* We need to make sure there's no other device with this
     * chipselect **BEFORE** we call setup(), else we'll trash
     * its configuration.  Lock against concurrent add() calls.
     */
    mutex_lock(&spi_add_lock);

    status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
    if (status) {
        dev_err(dev, "chipselect %d already in use\n",
                spi->chip_select);
        goto done;
    }

    if (master->cs_gpios)
        spi->cs_gpio = master->cs_gpios[spi->chip_select];

    /* Drivers may modify this initial i/o setup, but will
     * normally rely on the device being setup.  Devices
     * using SPI_CS_HIGH can't coexist well otherwise...
     */
    status = spi_setup(spi);
    if (status < 0) {
        dev_err(dev, "can't setup %s, status %d\n",
                dev_name(&spi->dev), status);
        goto done;
    }

    /* Device may be bound to an active driver when this returns */
    status = device_add(&spi->dev);
    if (status < 0)
        dev_err(dev, "can't add %s, status %d\n",
                dev_name(&spi->dev), status);
    else
        dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));

done:
    mutex_unlock(&spi_add_lock);
    return status;
}

由此可知,这里将dts设备树的/spi 转化为 spi_master,/spi 下的子节点生成 spi_device。

三、spi_device 和 spi_driver匹配过程

Linux内核的spi总线-设备-驱动模型完成spi_device 和 spi_driver匹配工作。

1、spi_bus_type 总线 - drivers/spi/spi.c

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

static int __init spi_init(void)
{
    ... ...
    /* 注册spi总线 */
	status = bus_register(&spi_bus_type);
	if (status < 0)
		goto err1;
    ... ...
}

postcore_initcall(spi_init);

bus_register(&spi_bus_type) 注册了spi总线 spi_bus_type ,之后在注册 spi_device 和 spi_driver 时会与spi_bus_type 建立联系。

再看下spi_bus_type总线的匹配函数,用于spi_device 和spi_driver的匹配。

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

	/* Check override first, and if set, only use the named driver */
	if (spi->driver_override)
		return strcmp(spi->driver_override, drv->name) == 0;

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

若 spi_device 和spi_driver的匹配成功,调用spi_driver的probe函数

2、spi设备 - dts设备树/spi添加子设备

我们需在dts设备树 /spi 添加icm20608子节点,如下图。系统自带的spi 的 platform平台设备驱动匹配会调用 spi_imx_probe() 会将 icm20608子节点生成spi_device。

icm20608 的属性 compatible 信息为"test,icm20608"需与spi_driver 驱动匹配。

&ecspi3	{
	fsl,spi-num-chipselects = <1>;			/* 片选数量为 1,因为就只接了一个 ICM20608 */
	cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;	/* 片选引脚 */
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi3>;			/* 设置 IO 要使用的 pinctrl 子节点 */
	status = "okay";

	spidev: icm20608@0 {		/* icm20608 连接在 ECSPI3 的第 0 个通道上,因此@后面为 0 */
		compatible = "test,icm20608";
		spi-max-frequency = <8000000>;		/*  SPI 最大时钟频率为 8MHz */
		reg = <0>;				/* icm20608 连接在通道 0 上,因此 reg 为 0 */
	};
};

3、spi驱动 - spi_driver驱动代码编写

spi_driver驱动代码由我们自己编写,我们要确保dts设备树中spi 设备的 compatible 与 spi_driver驱动代码中 of_match_table 的 compatible 相同,即可匹配上。

示例如下:

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

/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
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;
}

/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
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;
}

/*
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 data = 0;
	icm20608_read_regs(dev, reg, &data, 1);
	return data;
}

/*
 * @description	: 向icm20608指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */	

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

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量
 * 					  一般在open的时候将private_data似有向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &icm20608dev; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
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;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @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,
};

/*
 * ICM20608内部寄存器初始化函数 
 * @param  	: 无
 * @return 	: 无
 */
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						*/
}

 /*
  * @description     : spi驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : spi设备
  * @param - id      : spi设备ID
  * 
  */	
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属性,得到gpio编号 */
	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);					/* 设置好 spi_device 以后需要使用 spi_setup 配置一下 */
	icm20608dev.private_data = spi; /* 设置私有数据 */

	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit();		
	return 0;
}

/*
 * @description     : spi驱动的remove函数,移除spi驱动的时候此函数会执行
 * @param - client 	: spi设备
 * @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[] = {
	{"test,icm20608", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "test,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,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init icm20608_init(void)
{
	return spi_register_driver(&icm20608_driver);
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit icm20608_exit(void)
{
	spi_unregister_driver(&icm20608_driver);
}

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

分析 spi_register_driver 函数注册 spi_driver过程

/* include/linux/spi/spi.h */
#define spi_register_driver(driver) \
    __spi_register_driver(THIS_MODULE, driver)

/* drivers/spi/spi.c */
int __spi_register_driver(struct module *owner, struct spi_driver *sdrv)
{
	sdrv->driver.owner = owner;
	sdrv->driver.bus = &spi_bus_type;	//与 spi_bus_type 总线建立联系
	sdrv->driver.probe = spi_drv_probe;
	sdrv->driver.remove = spi_drv_remove;
	if (sdrv->shutdown)
		sdrv->driver.shutdown = spi_drv_shutdown;
	return driver_register(&sdrv->driver);
}
3.1 spi_register_driver函数流程

如果看过platform相关代码,可以发现 spi_register_driver 与 platform_driver_register 非常相像,他们都会调用 driver_register 函数来注册drv驱动,并最终调用 __driver_attach 函数。

spi_register_driver(&icm20608_driver)
--->(THIS_MODULE, &drv)    // drivers/spi/spi.c
--->--->driver_register(&drv->driver)               // drivers/base/driver.c
--->--->--->bus_add_driver(drv)                     // drivers/base/bus.c
--->--->--->--->driver_attach(drv)                  // drivers/base/dd.c
--->--->--->--->--->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) // drivers/base/bus.c
--->--->--->--->--->--->__driver_attach()           // drivers/base/dd.c
--->--->--->--->--->--->--->driver_match_device(drv, dev)    // dev 和 drv 匹配
--->--->--->--->--->--->--->driver_probe_device(drv, dev)
--->--->--->--->--->--->--->--->really_probe(dev, drv)
                                    dev->driver = drv;    // dev 和 drv 建立联系
                                    drv->probe(dev)

__driver_attach 调用 spi_bus_type 的match函数,完成spi_device 和 spi_driver 的匹配工作。匹配上就调用 spi_driver 驱动的probe函数。

static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;
 
	if (!driver_match_device(drv, dev))    // drv 和 dev 匹配
		return 0;
 
	if (dev->parent)	/* Needed for USB */
		device_lock(dev->parent);
	device_lock(dev);
	if (!dev->driver)
		driver_probe_device(drv, dev);    // 调用probe
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);
 
	return 0;
}

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

probe中就是字符设备驱动那套流程,完成 file_operations 中的 open、read、write等函数即可。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值