1、普通驱动编写,一般实际操作简单的就可以直接填空,操作的最关键的地方就是init和read write ioctrl。
比如字符驱动我如果需要读取外部数据或者写入直接修改自己定义的read write,这是你自己修改的最关键的地方,在init里ioremap,然后read write操作虚拟地址读写。
在你入口init函数里面通过register_chardev()和设备号注册,把fileoperation告诉内核,fileoperation里面是实例化的你自己写的read write ioctrl等,fileoperation虽然很关键,但实际写的时候这个里面只有实例化代码很少也很简单。
基本框架如下:
my_gpio_open{};my_gpio_write{};my_gpio_read{};my_gpio_ioctl{};static const struct file_operations my_gpio_fops{.owner = THIS_MODULE,.open = my_gpio_open,
.read = my_gpio_read,
.unlocked_ioctl = my_gpio_ioctl,};int __init fifo_init(void){fifo_Regs1 = ioremap(MY_GPIO_PHY_ADDR1,0xFFFF);ret = misc_register(&fifo_dev);//把fileoperation结构体告诉内核}void __exit my_gpio_exit(void)
{iounmap(GPIO_Regs1);iounmap(GPIO_Regs2);misc_deregister(&my_gpio_dev);printk("read_data_dev: Module exit\n");
}module_init(my_gpio_init);
module_exit(my_gpio_exit);MODULE_LICENSE("GPL");
2、上面那样写在多个设备的情况下修改比较麻烦,他这里给了两种通用的驱动编写框架
第一种很简单就是分成两层
一层leddrv.c和上下对接
另一层对下实现底层驱动ing,和我们最常用的直接写差不多
这样用A板子的时候就编译A和leddrv.c,B的时候就B和leddrv.c
第二种就是,以面向对象的思维,对第一种方法做个改进,把init和ctl包成一个结构体,对上层开放相当于一个对象。
led_opr.h
#ifndef _LED_OPR_H
#define _LED_OPR_Hstruct led_operations {int num;int (*init) (int which); /* 初始化LED, which-哪个LED */ int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
};struct led_operations *get_board_led_opr(void);#endif
leddrv.c
#include #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include #include "led_opr.h"/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;#define MIN(a, b) (a static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;struct inode *inode = file_inode(file);int minor = iminor(inode);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);/* 根据次设备号和status控制LED */p_led_opr->ctl(minor, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{int minor = iminor(node);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 根据次设备号初始化LED */p_led_opr->init(minor);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的file_operations结构体 */
static struct file_operations led_drv = {.owner = THIS_MODULE,.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_close,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{int err;int i;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */led_class = class_create(THIS_MODULE, "100ask_led_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "led");return -1;}p_led_opr = get_board_led_opr();for (i = 0; i num; i++)device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{int i;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);for (i = 0; i num; i++)device_destroy(led_class, MKDEV(major, i)); /* /dev/100ask_led0,1,... */device_destroy(led_class, MKDEV(major, 0));class_destroy(led_class);unregister_chrdev(major, "100ask_led");
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
board_rk3399.c
#include #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led_opr.h"static volatile unsigned int *CRU_CLKGATE_CON31;
static volatile unsigned int *GRF_GPIO2D_IOMUX ;
static volatile unsigned int *GPIO2_SWPORTA_DDR;
static volatile unsigned int *GPIO2_SWPORTA_DR ;static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
{//printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);if (which &#61;&#61; 0){if (!CRU_CLKGATE_CON31){CRU_CLKGATE_CON31 &#61; ioremap(0xFF760000 &#43; 0x037c, 4);GRF_GPIO2D_IOMUX &#61; ioremap(0xFF770000 &#43; 0x0e00c, 4);GPIO2_SWPORTA_DDR &#61; ioremap(0xFF780000 &#43; 0x0004, 4);GPIO2_SWPORTA_DR &#61; ioremap(0xFF780000 &#43; 0x0000, 4);}/* rk3399 GPIO2_D3 *//* a. 使能GPIO2* set CRU to enable GPIO2* CRU_CLKGATE_CON31 0xFF760000 &#43; 0x037c* (1<<(3&#43;16)) | (0<<3)*/*CRU_CLKGATE_CON31 &#61; (1<<(3&#43;16)) | (0<<3);/* b. 设置GPIO2_D3用于GPIO* set PMU/GRF to configure GPIO2_D3 as GPIO* GRF_GPIO2D_IOMUX 0xFF770000 &#43; 0x0e00c* bit[7:6] &#61; 0b00* (3<<(6&#43;16)) | (0<<6)*/*GRF_GPIO2D_IOMUX &#61; (3<<(6&#43;16)) | (0<<6);/* c. 设置GPIO2_D3作为output引脚* set GPIO_SWPORTA_DDR to configure GPIO2_D3 as output* GPIO_SWPORTA_DDR 0xFF780000 &#43; 0x0004* bit[27] &#61; 0b1*/ *GPIO2_SWPORTA_DDR |&#61; (1<<27);}return 0;
}static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮, 0-灭*/
{//printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");if (which &#61;&#61; 0){if (status) /* on: output 1 */{ /* d. 设置GPIO2_D3输出高电平* set GPIO_SWPORTA_DR to configure GPIO2_D3 output 1* GPIO_SWPORTA_DR 0xFF780000 &#43; 0x0000* bit[27] &#61; 0b1*/ *GPIO2_SWPORTA_DR |&#61; (1<<27);}else /* off : output 0 */{/* e. 设置GPIO2_D3输出低电平* set GPIO_SWPORTA_DR to configure GPIO2_D3 output 0* GPIO_SWPORTA_DR 0xFF780000 &#43; 0x0000* bit[27] &#61; 0b0*/*GPIO2_SWPORTA_DR &&#61; ~(1<<27);}}return 0;
}static struct led_operations board_demo_led_opr &#61; {.num &#61; 1,.init &#61; board_demo_led_init,.ctl &#61; board_demo_led_ctl,
};struct led_operations *get_board_led_opr(void)
{return &board_demo_led_opr;
}