Linux SPI 驱动示例

一 Linux 下 SPI 驱动框架

SPI 驱动框架分为主机控制器驱动和设备驱动,主机控制器也就是 SOC 的 SPI 控制器接口。

1.1 SPI 主机驱动

SPI 主机驱动就是 SOC SPI 控制器驱动,Linux 内核使用 spi_master结构体表示 SPI 主机驱动。

1.2 SPI 设备驱动

Linux 内核使用 spi_driver 结构体来表示 spi 设备 驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver
spi_driver 注册和注销函数原型如下
int spi_register_driver(struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)
 
spi_driver 注册示例程序
 
1 /* probe 函数 */
2 static int xxx_probe(struct spi_device *spi) 
3 { 
4 /* 具体函数内容 */
5 return 0; 
6 } 
7 
8 /* remove 函数 */
9 static int xxx_remove(struct spi_device *spi)
10 {
11 /* 具体函数内容 */
12 return 0;
13 }
14 /* 传统匹配方式 ID 列表 */
15 static const struct spi_device_id xxx_id[] = {
16 {"xxx", 0}, 
17 {}
18 };
19
20 /* 设备树匹配列表 */
21 static const struct of_device_id xxx_of_match[] = {
22 { .compatible = "xxx" },
23 { /* Sentinel */ }
24 };
25
26 /* SPI 驱动结构体 */
27 static struct spi_driver xxx_driver = {
28 .probe = xxx_probe,
29 .remove = xxx_remove,
30 .driver = {
31 .owner = THIS_MODULE,
32 .name = "xxx",
33 .of_match_table = xxx_of_match,
34 },
35 .id_table = xxx_id,
36 };
37 
38 /* 驱动入口函数 */
39 static int __init xxx_init(void)
40 {
41 return spi_register_driver(&xxx_driver);
42 }
43
44 /* 驱动出口函数 */
45 static void __exit xxx_exit(void)
46 {
47 spi_unregister_driver(&xxx_driver);
48 }
49
50 module_init(xxx_init);
51 module_exit(xxx_exit);

1.3 SPI 设备和驱动匹配过程
SPI
设备和驱动的匹配过程是由 SPI 总线来完成的,和 platformI2C 等驱动一样。
SPI
设备和驱动的匹配函数为 spi_match_device,定义在 drivers/spi/spi.c 文件中。

二、I.MX6U SPI 主机驱动

SPI 主机驱动一般都由 SOC 厂商编写好了。I.MX6U ECSPI 主机驱动文件为 drivers/spi/spi-imx.c。

三、SPI 设备驱动编写流程

3.1SPI 设备信息描述

1、在设备树中根据所使用的 IO 来创建或修改 pinctrl 子节点

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

2、SPI 设备节点的创建与修改

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



3.2 SPI 设备数据收发处理流程
SPI
设备驱动的核心是 spi_driver,当我们向 Linux 内 核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。

SPI 数据传输步骤如下:
①、申请并初始化 spi_transfer结构体 ,设置 spi_transfer tx_buf 成员变量, tx_buf 为要发送的数据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是 要进行数据通信的长度。
②、使用 spi_message_init 函数初始化 spi_message
③、使用 spi_message_add_tail 函数将前面设置好的 spi_transfer 添加到 spi_message 队列中。
④、使用 spi_sync 函数完成 SPI 数据同步传输。
 
SPI 数据读写操作示例代码
 

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

/* SPI 多字节接收 */
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.1 设备树修改

iomuxc 节点中添加一个新的pinctrl子节点来描述 ICM20608 所使用的 SPI 引脚。(同3.1)
在 ecspi3
节点追加 icm20608 子节点。(同3.1)
 

4.2示例代码
icm20608 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;



/***************************SPI设备数据收发处理****************************/

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

/***************************spi设备的初始化****************************/
/* 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,
};







/***************************SPI 设备驱动注册注销流程****************************/

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


    /* 初始化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");


驱动代码中主要包含四个部分

1)SPI设备数据收发处理
2)传感器数据的读取,spi设备的初始化
3)  文件操作集
4)  SPI 设备驱动注册注销流程

示例代码中spi数据收发处理使用的函数为spi_write_then_read函数和spi_write函数

int spi_write_then_read(struct spi_device *spi,
		const void *txbuf, unsigned n_tx,
		void *rxbuf, unsigned n_rx)
{
	static DEFINE_MUTEX(lock);

	int			status;
	struct spi_message	message;
	struct spi_transfer	x[2];
	u8			*local_buf;

	/* Use preallocated DMA-safe buffer if we can.  We can't avoid
	 * copying here, (as a pure convenience thing), but we can
	 * keep heap costs out of the hot path unless someone else is
	 * using the pre-allocated buffer or the transfer is too large.
	 */
	if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) {
		local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx),
				    GFP_KERNEL | GFP_DMA);
		if (!local_buf)
			return -ENOMEM;
	} else {
		local_buf = buf;
	}

	spi_message_init(&message);
	memset(x, 0, sizeof(x));
	if (n_tx) {
		x[0].len = n_tx;
		spi_message_add_tail(&x[0], &message);
	}
	if (n_rx) {
		x[1].len = n_rx;
		spi_message_add_tail(&x[1], &message);
	}

	memcpy(local_buf, txbuf, n_tx);
	x[0].tx_buf = local_buf;
	x[1].rx_buf = local_buf + n_tx;

	/* do the i/o */
	status = spi_sync(spi, &message);
	if (status == 0)
		memcpy(rxbuf, x[1].rx_buf, n_rx);

	if (x[0].tx_buf == buf)
		mutex_unlock(&lock);
	else
		kfree(local_buf);

	return status;
}
EXPORT_SYMBOL_GPL(spi_write_then_read);




static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(spi, &m);
}

从上面两个函数看出,这两个函数也是通过调用以下三个函数完成数据的传输

spi_message_init(&m);
spi_message_add_tail(&t, &m);
spi_sync(spi, &m);
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值