学习记录—基于韦东山老师教程
- 字符串驱动开发流程
(1)确定主设备号
(2)定义自己的file_operation结构体
(3)实现对应的drv_open、drv_write等函数
(4)编写一个入口函数,用来注册驱动程序
(5)相应的编写一个出口函数,用于卸载驱动程序 - led 开发的步骤
(1)看原理图,确定GPIO端口和输出高低电平时led的亮灭情况
(2)使能:CCM用于设置向GPIO模块提供时钟,Clock Controller Module (时钟控制模块),找到对应的芯片手册,改变对应寄存器相应位的值
(3)设置引脚工作模式为GPIO模式
(4)设置引脚方向(GPIOx_GDIR):每位对应一个引脚,1-output,0-input
(5)设置输出引脚电平(GPIOx_DR):每位对应一个引脚,1-高电平,0-低电平
(6)读取引脚的电平(GPIOx_PSR):每位对应一个引脚,1-高电平,0-低电平 - 驱动程序的编写
(1)加入头文件,打开Linux内核的源码,随便打开一个驱动程序,将其包含的头文件复制进去;
(2)确定主设备号,可以先定为零,后调用register_chrdev()函数,返回一个合法的组设备好;
(3)定义file_operation结构体:同理去找一个驱动程序,模仿着定义自己需要的结构体。
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
};
(4)实现相应的驱动函数:
/* write函数 */
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
char val;
int retu;
/* copy_from_user: get data from app */
retu = copy_from_user(&val, buf, 1);
/* to set gpio register: 1/0 */
if (val){
/* set gpio to let led on */
*GPIO5_DR &= ~(1<<3);
}
else {
/*set gpio to let led off */
*GPIO5_DR |= (1<<3);
}
return 0;
}
/* open函数 */
static int led_open(struct inode *inode, struct file *file) {
/*
* enable gpio
* configure gpio5_io3 as gpio
* configure gpio5_io3 as output
*/
*IOMUXC_GPIO5_IO03 &= ~0xf;
*IOMUXC_GPIO5_IO03 |= 0x5;
*GPIO5_GDIR |= (1<<3);
return 0;
}
(5)在驱动开发中不能直接使用硬件地址,而是需要用ioremap()函数映射实际的地址再使用,此例子中因为此GPIO模块默认使能,且不需要查看电平高低,便省去对应两步。
/* register */
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 Address: 229_0000h base + 14h offset = 229_0014h 0x2290000+0x14
static volatile unsigned int *IOMUXC_GPIO5_IO03;
// gpio5 020A_C000
// GPIO direction register input/output Address: Base address + 4h offset 0x20AC004
static volatile unsigned int *GPIO5_GDIR;
// GPIO data register 0/1 Address: Base address + 0h offset 0X20AC000
static volatile unsigned int *GPIO5_DR;
/* ioremap 映射实际地址 */
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 Address: 229_0000h base + 14h offset = 229_0014h 0x2290000+0x14
IOMUXC_GPIO5_IO03 = ioremap(0x2290000+0x14, 4);
// gpio5 020A_C000
// GPIO direction register input/output Address: Base address + 4h offset 0x20AC004
GPIO5_GDIR = ioremap(0x20AC004, 4);
// GPIO data register 0/1 Address: Base address + 0h offset 0X20AC000
GPIO5_DR = ioremap(0X20AC000, 4);
(6)编写入口函数‘
/* 入口函数 */
static int __init led_init(void) {
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册驱动程序 */
major = register_chrdev(0, "yuelong_led", &led_fops); //接收传回的主设备号,用于出口函数销毁
/* ioremap 映射实际地址 */
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 Address: 229_0000h base + 14h offset = 229_0014h 0x2290000+0x14
IOMUXC_GPIO5_IO03 = ioremap(0x2290000+0x14, 4);
// gpio5 020A_C000
// GPIO direction register input/output Address: Base address + 4h offset 0x20AC004
GPIO5_GDIR = ioremap(0x20AC004, 4);
// GPIO data register 0/1 Address: Base address + 0h offset 0X20AC000
GPIO5_DR = ioremap(0X20AC000, 4);
/* 创建类和节点 */
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); // 创建了 /dev/myled的设备节点
return 0;
}
(7)编写出口函数
/* 出口函数 */
static void __exit led_exit(void) {
/* 清楚映射实际地址 */
iounmap(IOMUXC_GPIO5_IO03);
iounmap(GPIO5_GDIR);
iounmap(GPIO5_DR);
/* 销毁节点和类 */
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
/* 注销驱动程序 */
unregister_chrdev(major, "yuelong_led");
}
(8)声明上述的入口与出口函数,才算真正的实现注册与卸载驱动程序,并且声明GPL协议:
// 声明上诉的入口和出口函数
module_init(led_init);
module_exit(led_exit);
// GPL协议
MODULE_LICENSE("GPL");