15-SPI Linux驱动代码实现

mac2024-05-11  36

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):  01 - 第一个内核模块程序  02 - 注册字符设备驱动  03 - open & close 函数的应用  04 - read & write 函数的应用  05 - ioctl 的应用  06 - ioctl LED灯硬件分析  07 - ioctl 控制LED软件实现(寄存器操作)  08 - ioctl 控制LED软件实现(库函数操作)  09 - 注册字符设备的另一种方法(常用)  10 - 一个cdev实现对多个设备的支持  11 - 四个cdev控制四个LED设备  12 - 虚拟串口驱动  13 - I2C驱动  14 - SPI协议及驱动讲解  15 - SPI Linux驱动代码实现  16 - 非阻塞型I/O  17 - 阻塞型I/O  18 - I/O多路复用之 select  19 - I/O多路复用之 poll  20 - I/O多路复用之 epoll  21 - 异步通知

文章目录

1 spi_drv.c2 user_lib.c3 test.c4 user.h5 pdebug.h6 Makefile

1 spi_drv.c

 主要包含了spi驱动的注册、注销、probe、ioctl和读写函数

#include <linux/module.h> #include <linux/spi/spi.h> #include <linux/spi/spidev.h> #include <linux/mod_devicetable.h> #include <linux/slab.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/stddef.h> #include <linux/delay.h> #include <asm-generic/gpio.h> #include <asm/gpio.h> #include "pdebug.h" #define THTF_SPI_CHRDEV_NAME "my_spi_chrdev" #define THTF_SPI_CLASS_NAME "my_spi_class" #define THTF_SPI_DEVICE_NAME "my_spi_device" struct my_spi_dev{ dev_t dev_no; struct spi_device *myspi_dev; struct cdev cdev; struct class *cls; struct device *dev; }; static int my_spi_open(struct inode *inode, struct file *filp) { struct my_spi_dev *my_spi_dev_temp = NULL; PDEBUG("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__); my_spi_dev_temp = container_of(inode->i_cdev, struct my_spi_dev, cdev); filp->private_data = my_spi_dev_temp; return 0; } static int my_spi_release(struct inode *inode, struct file *filp) { PDEBUG("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__); filp->private_data = NULL; return 0; } static ssize_t my_spi_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset) { struct my_spi_dev *my_spi_dev_temp = filp->private_data; int ret; unsigned char buf[4] = {0}; PDEBUG("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__); ret = spi_read(my_spi_dev_temp->myspi_dev, buf, size); if (ret < 0) { printk("spi_read failed.\n"); goto err0; } PDEBUG("0x%02x 0x%02x 0x%02x 0x%02x..\n", buf[0], buf[1], buf[2], buf[3]); ret = copy_to_user(userbuf, buf, size); if ( ret ) { printk("copy_to_user failed.\n"); goto err0; } return 0; err0: return ret; } static ssize_t my_spi_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset) { struct my_spi_dev *my_spi_dev_temp = filp->private_data; int ret; unsigned char buf[3]; PDEBUG("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__); ret = copy_from_user(buf, userbuf, size); if (ret < 0) { printk("copy_from_user failed.\n"); goto err0; } ret = spi_write(my_spi_dev_temp->myspi_dev, buf, 3); if (ret < 0) { printk("spi_write failed.\n"); goto err0; } return 0; err0: return ret; } static long my_spi_ioctl(struct file *filp, unsigned int cmd, unsigned long args) { int ret; u16 mode; u32 max_speed_hz; u8 bits_per_word; struct my_spi_dev *my_spi_dev_temp = filp->private_data; PDEBUG("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__); if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) { printk("SPI_IOC_MAGIC error.\n"); ret = -ENOTTY; /* not a typewriter */ goto err0; } switch (cmd) { case SPI_IOC_RD_MODE: /* 获取SPI主机控制器的模式 */ ret = __put_user(my_spi_dev_temp->myspi_dev->mode, (__u8 __user *)args); /* 复制的内存是简单类型,如char,int ,long等 */ if (ret < 0) { printk("SPI_IOC_RD_MODE __put_user failed.\n"); goto err0; } break; case SPI_IOC_RD_BITS_PER_WORD: /* 获取SPI的字长 */ ret = __put_user(my_spi_dev_temp->myspi_dev->bits_per_word, (__u8 __user *)args); if (ret < 0) { printk("SPI_IOC_RD_MODE __put_user failed.\n"); goto err0; } break; case SPI_IOC_RD_MAX_SPEED_HZ: /* 获取SPI的最高工作速率 */ ret = __put_user(my_spi_dev_temp->myspi_dev->max_speed_hz, (__u8 __user *)args); if (ret < 0) { printk("SPI_IOC_RD_MODE __put_user failed.\n"); goto err0; } break; case SPI_IOC_WR_MODE: /* 设置SPI主机控制器的模式 */ ret = __get_user(mode, (u8 __user *)args); if (ret < 0) { printk("SPI_IOC_WR_MODE __get_user failed.\n"); goto err0; } my_spi_dev_temp->myspi_dev->mode = mode; ret = spi_setup(my_spi_dev_temp->myspi_dev); if (ret < 0) { printk("SPI_IOC_WR_MODE spi_setup failed.\n"); goto err0; } break; case SPI_IOC_WR_BITS_PER_WORD: /* 设置SPI的字长 */ ret = __get_user(bits_per_word, (u8 __user *)args); if (ret < 0) { printk("SPI_IOC_WR_BITS_PER_WORD __get_user failed.\n"); goto err0; } my_spi_dev_temp->myspi_dev->bits_per_word = bits_per_word; ret = spi_setup(my_spi_dev_temp->myspi_dev); if (ret < 0) { printk("SPI_IOC_WR_BITS_PER_WORD spi_setup failed.\n"); goto err0; } break; case SPI_IOC_WR_MAX_SPEED_HZ: /* 设置SPI的最大工作速率 */ ret = __get_user(max_speed_hz, (u32 __user *)args); if (ret < 0) { printk("SPI_IOC_WR_MAX_SPEED_HZ __get_user failed.\n"); goto err0; } my_spi_dev_temp->myspi_dev->max_speed_hz = max_speed_hz; ret = spi_setup(my_spi_dev_temp->myspi_dev); if (ret < 0) { printk("SPI_IOC_WR_MAX_SPEED_HZ spi_setup failed.\n"); goto err0; } break; default: ; } return 0; err0: return ret<0 ? ret : -ret; } struct file_operations my_spi_ops = { .open = my_spi_open, .release = my_spi_release, .read = my_spi_read, .write = my_spi_write, .unlocked_ioctl = my_spi_ioctl, }; struct spi_device_id my_spi_id[] = { {"spidev0"}, {}, }; MODULE_DEVICE_TABLE(spi, my_spi_id); static int my_spi_probe(struct spi_device *spi) { int ret; struct my_spi_dev *my_temp_spi_dev; PDEBUG("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__); spi->mode = SPI_MODE_0; spi->bits_per_word = 8; ret = spi_setup(spi); if (ret < 0) { printk("spi_setup failed.\n"); goto spi_setup_err; } my_temp_spi_dev = kzalloc(sizeof(struct my_spi_dev), GFP_KERNEL); /* 申请空间 */ if ( IS_ERR(my_temp_spi_dev) ) { ret = PTR_ERR(my_temp_spi_dev); printk("kzalloc failed.\n"); goto kzalloc_err; } spi_set_drvdata(spi, my_temp_spi_dev); /* spi->dev.driver_data */ my_temp_spi_dev->myspi_dev = spi; /* 把内核解析设备树生成的spi设备结构体保存在自己定义的机构体中 */ ret = alloc_chrdev_region(&my_temp_spi_dev->dev_no, 0, 1, THTF_SPI_CHRDEV_NAME); if (ret < 0) { printk("alloc_chrdev_region failed.\n"); goto alloc_err; } PDEBUG("my_spi_major is %d.\n", MAJOR(my_temp_spi_dev->dev_no)); /* 打印主设备号 */ cdev_init(&my_temp_spi_dev->cdev, &my_spi_ops); /* 将字符设备和文件操作方法描述集绑定 */ ret = cdev_add(&my_temp_spi_dev->cdev, my_temp_spi_dev->dev_no, 1); if (ret < 0) { printk("cdev_add failed.\n"); goto add_err; } my_temp_spi_dev->cls = class_create(THIS_MODULE, THTF_SPI_CLASS_NAME); if ( IS_ERR(my_temp_spi_dev->cls) ) { ret = PTR_ERR(my_temp_spi_dev->cls); printk("class_create failed.\n"); goto cls_err; } my_temp_spi_dev->dev = device_create(my_temp_spi_dev->cls, NULL, my_temp_spi_dev->dev_no, NULL, THTF_SPI_DEVICE_NAME); if ( IS_ERR(my_temp_spi_dev->dev) ) { ret = PTR_ERR(my_temp_spi_dev->dev); printk("device_create failed.\n"); goto dev_err; } return 0; dev_err: class_destroy(my_temp_spi_dev->cls); cls_err: cdev_del(&my_temp_spi_dev->cdev); add_err: unregister_chrdev_region(my_temp_spi_dev->dev_no, 1); alloc_err: kfree(my_temp_spi_dev); kzalloc_err: spi_setup_err: return ret; } static int my_spi_remove(struct spi_device *spi) { struct my_spi_dev *my_temp_spi_dev = spi_get_drvdata(spi); PDEBUG("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__); device_destroy(my_temp_spi_dev->cls, my_temp_spi_dev->dev_no); class_destroy(my_temp_spi_dev->cls); cdev_del(&my_temp_spi_dev->cdev); unregister_chrdev_region(my_temp_spi_dev->dev_no, 1); kfree(my_temp_spi_dev); return 0; } static struct spi_driver my_spi_drv = { .probe = my_spi_probe, .remove = my_spi_remove, .id_table = my_spi_id, .driver = { .name = "spidev0", .owner = THIS_MODULE, }, }; module_spi_driver(my_spi_drv); /* 注册注销驱动 */ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("my spi drv");

2 user_lib.c

 主要包含了应用层spi模式、字长和频率的设置及获取,以及spi读写函数的实现。

#include <linux/spi/spidev.h> #include "user.h" u8 str_to_hex(const char *p, u8 *data) { int i; int len = strlen(p); u8 temp = 0; for (i=0; i<len; i++) { if (p[i]>='a' && p[i]<='f') { temp = 16*temp + (p[i]-'a'+10); } else if (p[i]>='A' && p[i]<='F') { temp = 16*temp + (p[i]-'A'+10); } else if (p[i]>='0' && p[i]<='9') { temp = 16*temp + p[i]-'0'; } } *data = temp; } int get_my_spi_mode(u16 *mode) { int ret; ret = ioctl(my_spi_fd, SPI_IOC_RD_MODE, mode); /* 成功0 失败-1 */ if(ret == -1) { printf("get_my_spi_mode failed.\n"); return -1; } return 0; } int get_my_spi_bits_per_word(u8 *bits_per_word) { int ret; ret = ioctl(my_spi_fd, SPI_IOC_RD_BITS_PER_WORD, bits_per_word); if(ret == -1) { printf("get_my_spi_bits_per_word failed.\n"); return -1; } } int get_my_spi_max_speed_hz(u32 *max_speed_hz) { int ret; ret = ioctl(my_spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, max_speed_hz); if (ret == -1) { printf("get_my_spi_max_speed_hz failed.\n"); return -1; } } int set_my_spi_mode(u16 *mode) { int ret; ret = ioctl(my_spi_fd, SPI_IOC_WR_MODE, mode); if(ret == -1) { printf("get_my_spi_mode failed.\n"); return -1; } } int set_my_spi_bits_per_word(u8 *bits_per_word) { int ret; ret = ioctl(my_spi_fd, SPI_IOC_WR_BITS_PER_WORD, bits_per_word); if(ret == -1) { printf("get_my_spi_bits_per_word failed.\n"); return -1; } } int set_my_spi_max_speed_hz(u32 *max_speed_hz) { int ret; ret = ioctl(my_spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, max_speed_hz); if(ret == -1) { printf("get_my_spi_mode failed.\n"); return -1; } } int spi_user_write(u8 *write_buf, u32 len) { int ret; ret = write(my_spi_fd, write_buf, len); /* 成功:写入的字节数 失败:-1 */ if (ret == -1) { printf("spi_user_write failed.\n"); return -1; } return ret; } int spi_user_read(u8 *read_buf, u32 len) { int ret; ret = read(my_spi_fd, read_buf, len); /* 成功:读到的字节数 失败:-1 */ if (ret == -1) { printf("spi_user_read failed.\n"); return -1; } return ret; }

3 test.c

 应用层的测试代码

#include "user.h" int main(int argc, const char *argv[]) { int i, ret, size = 4; u16 mode; u32 max_speed_hz; u8 bits_per_word; u8 write_buf[4] = {0x00, 0x00, 0x00}; u8 read_buf[4] = {0x00, 0x00, 0x00, 0x00}; char str0[3] = "\0"; char str1[3] = "\0"; char str2[3] = "\0"; char str3[3] = "\0"; my_spi_fd = open("/dev/my_spi_device", O_RDWR, 0666); if (my_spi_fd < 0) { perror("open spi_dev"); return -1; } get_my_spi_mode(&mode); printf("mode = %d.\n", mode); get_my_spi_bits_per_word(&bits_per_word); printf("bits_per_word = %d\n", bits_per_word); get_my_spi_max_speed_hz(&max_speed_hz); printf("max_speed_hz = %d\n", max_speed_hz); while (1) { printf("input write_buf[0] write_buf[1] write_buf[2]: "); scanf("%s%s%s", str0, str1, str2); str_to_hex(str0, &write_buf[0]); str_to_hex(str1, &write_buf[1]); str_to_hex(str2, &write_buf[2]); str_to_hex(str3, &write_buf[2]); spi_user_write(write_buf, sizeof(write_buf)); memset(read_buf, 0,sizeof(read_buf) ); spi_user_read(read_buf, size); for (i=0; i<4; i++) { printf("read_buf[%d] = 0X%02X\n", i, read_buf[i]); } } close(my_spi_fd); return 0; err0: return -1; }

4 user.h

 自定义头文件

#ifndef _USER_H_ #define _USER_H_ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; int my_spi_fd; u8 str_to_hex(const char *, u8 *); int get_my_spi_mode(u16 *); int get_my_spi_bits_per_word(u8 *); int get_my_spi_max_speed_hz(u32 *); int set_my_spi_mode(u16 *); int set_my_spi_bits_per_word(u8 *); int set_my_spi_max_speed_hz(u32 *); int spi_user_write(u8 *, u32); int spi_user_read(u8 *, u32); #endif /* user.h */

5 pdebug.h

 此部门摘自博客 Linux内核驱动调试  在调试时使用printk会打印调试信息,而通常打印的调试信息较多,为了在调试完毕时不用将 这些调试信息手动删除或者注释,参考上面的博客,将printk重新定义成PDEBUG。8行__KERNEL__是定义了的,在4行定义了DEBUG,所以会使用10行的语句。如果不想打印调试 信息,将4行注释掉,DEBUG没有定义,会执行15行的语句,此时PDEBUG宏定义成空,打印信息不输出。 */

#ifndef _PDEBUG_H_ #define _PDEBUG_H_ #define DEBUG #undef PDEBUG #ifdef DEBUG #ifdef __KERNEL__ //#define PDEBUG(fmt, args...) printk( KERN_EMERG "leds: " fmt, ## args) #define PDEBUG(fmt, args...) printk(fmt, ## args) #else #define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) #endif #else #define PDEBUG(fmt, args...) #endif #undef PDEBUGG #define PDEBUGG(fmt, args...)

6 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/ PWD := $(shell pwd) EXEC = app OBJS = test.o user_lib.o CC = arm-linux-gnueabihf-gcc $(EXEC):$(OBJS) make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules $(CC) $^ -o $@ .o:.c $(CC) -c $< clean: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean rm app obj-m += spi_drv.o
最新回复(0)