Linux SPI 驱动

1.SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC的 SPI 控制器接口。

2.SPI 主机驱动

SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中。

3. spi硬件图:

 

&ecspi3 { 
    //fsl,spi-num-chipselects = <1>;  /* 1个片选 */
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi3>;
    cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;  /* 指定片选引脚,低电平有效 */
    status = "okay";
    
    /* 挂载到spi3线上的设备节点 */
    spidev: icm20608@0{    /* 因为 icm20608 连接在 ECSPI3 的第 0 个通道上,因此@后面为 0。 */
        compatible = "invensense,icm20608";
        interrupt-parent = <&gpio1>;
        interrupts = <1 1>;
        spi-max-frequency = <8000000>; /* 设置 SPI 最大时钟频率为 8MHz,这是 ICM20608 的 SPI 接口所能支持的最大的时钟频率。 */
        reg = <0>;  /* icm20608 连接在通道 0 上,因此 reg 为 0。 */
    };
};
&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        pinctrl_ecspi3: ecspi3 {              
                    fsl,pins = <
                MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI         0x000010B0
                MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO         0x000010B0
                MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK       0x000010B0
                //MX6UL_PAD_UART2_TX_DATA__ECSPI3_SS0        0x000010B0
				/*片选信号不作为硬件片选,而是作为普通的GPIO,我们在程序里面自行控制片选脚。*/
                MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20        0x000010B0
                MX6UL_PAD_GPIO1_IO01__GPIO1_IO01           0x000010B0
            >;
        };
     };
};

4. SPI 设备驱动

1)icm20608 设备结构体创建

struct icm20608_dev {
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号      */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类    */
    struct device *device;  /* 设备 */
    void *private_data;     /* 私有数据 */
    int cs_gpio;            /* 片选所使用的 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;

2) icm20608 的 spi_driver 注册与注销 即 3)probe&remove 函数

     spi_driver 注册示例程序
/* probe 函数 */
static int xxx_probe(struct spi_device *spi)
{
	/* 具体函数内容 */
	return 0;
}

/* remove 函数 */
static int xxx_remove(struct spi_device *spi)
{
	/* 具体函数内容 */
	return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {
	{"xxx", 0},
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx" },
	{ /* Sentinel */ }
};

/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {
	.probe = xxx_probe,
	.remove = xxx_remove,
	.driver = {
			.owner = THIS_MODULE,
			.name = "xxx",
			.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /*在驱动入口函数中调用 spi_register_driver 来注册 spi_driver。*/	
    return spi_register_driver(&xxx_driver);
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /*在驱动出口函数中调用 spi_unregister_driver 来注销 spi_driver。*/	
    spi_unregister_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

4)icm20608 寄存器读写与初始化

 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 数据同步传输。

/*
 * @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)
{
    xxxxx
}

/*
 * @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)
{
    xxxxx
}

/*
 * @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)
{
    xxxxx
}

/*
 * ICM20608 内部寄存器初始化函数
 * @param : 无
 * @return : 无
 */
void icm20608_reginit(void)
{
    xxxxx
}

5)  字符设备驱动框架

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

5.效果图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值