平台设备驱动程序是基于总线系统实现的,由三个部分组成:
- 总线,这里的总线使用的是内核自带的 platform_bus_type 总线,无需我们进行实现;
- 设备,这个驱动主要是对 LED 灯的操作,因此设备需要记录 LED 的资源;
- 驱动,驱动完成点灯、关灯等操作;
设备
定义设备结构体:
#define S3C2440_GPFCON 0x56000050 /* led引脚的配置寄存器 */ #define DEVICE_NAME "s3c2440-leds" static struct platform_device leds_device = { .name = DEVICE_NAME, .id = -1, .num_resources = ARRAY_SIZE(leds_resource), .resource = leds_resource, .dev = { .release = leds_dev_release, }, };
GPFCONF 地址在 s3c2440 芯片中为 0x56000050。
资源定义,我们的驱动程序是用于驱动 led 的,因此 resource 记录的是 led 的信息
static struct resource leds_resource[] = { [0] = { /* 配置和数据 寄存器 */ .start = S3C2440_GPFCON, .end = S3C2440_GPFCON + 8 - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = 4, /* 第4位 */ .end = 4, .flags = IORESOURCE_IO, .name = "led" }, };
flags:代表资源的类型,常见有 IORESOURCE_MEM 和 IORESOURCE_IO。详细参考 Linux内核 struct resource 结构体_Bin Watson的博客-CSDN博客
LED 的原理图如上,这里我们使用 nLED1 作为我们的资源,对应的是 GPFCONF 的第4组配置。
因此我们设置 IORESOURCE_IO 的 start = 4;定义 init 函数:init 函数只需要完成注册平台设备即可
static int __init leds_dev_init(void) { platform_device_register(&leds_device); printk(KERN_INFO "leds device register.\n"); return 0; }
定义 exit 函数:卸载平台设备即可
static void __exit leds_dev_exit(void) { platform_device_unregister(&leds_device); printk(KERN_INFO "leds device unregister.\n"); }
实现 dev 的 release 函数。
在释放 struct device 设备时,device_release 函数会调用 device 中的 release 函数,如果没有实现该函数,就会产生如下图的错误。
我们只需要简单定义一下,不需要进行具体的操作。static void leds_dev_release(struct inode * inode, struct file * file) { /* do nothing */ }
驱动
驱动程序在 jz2440_按键驱动程序的防抖机制_Bin Watson的博客-CSDN博客 的基础上进行修改的。
以前的 LED 驱动程序需要与应用程序交互和硬件交互,同时还需要管理设备资源。
而使用了平台设备之后,我们把设备资源封装在了 platform_device 文件中。我们只需要简单的修改 platform_device 中的资源,即可将我们的驱动程序移植到其它开发板上。
驱动程序需要完成以下工作:
首先,我们需要创建一个 device_driver 设备驱动的结构体:
#define DRIVER_NAME "s3c2440_leds" /* 驱动名 */ static struct platform_driver leds_device_driver = { .probe = leds_device_probe, .remove = __devexit_p(leds_device_remove), .driver = { .name = DRIVER_NAME, } };
需要注意的是:驱动名 DRIVER_NAME 与我们前面的设备名 DEVICE_NAME 必须是一至的。
原因是 platform_bus_type 的 .match 函数的匹配机制是 对比二者的名字是否相同,相同则算匹配。
leds_device_probe 和 leds_device_remove 函数我们在后面进行说明。
init 函数:注册 driver 驱动程序
static int __init leds_driver_init(void) { int error = platform_driver_register(&leds_device_driver); printk(DRIVER_NAME "leds driver register.\n"); return error; }
exit 函数:卸载 driver 驱动程序
static void __exit leds_driver_exit(void) { platform_driver_unregister(&leds_device_driver); printk(DRIVER_NAME "leds driver unregister.\n"); }
当平台设备加载进内核后,会与 platform_bus_type 总线上的 platform_device 进行匹配,若匹配成功则会调用 platform_driver 中的 .probe 函数。
.probe 函数相当于替换掉了原来 LED 驱动程序(jz2440_按键驱动程序的防抖机制_Bin Watson的博客-CSDN博客 )中的 init 函数。
/* 一些全局变量 */ static unsigned int major; /* 主设备号 */ static struct class *s3c2440_class; static struct class_device *s3c2440_class_device; static volatile unsigned long *gpio_con; /* 配置寄存器 */ static volatile unsigned long *gpio_dat; /* 数据寄存器 */ static unsigned long led_pin; /* 用于记录是哪一个led引脚 */ static int leds_device_probe(struct platform_device *pdev) { struct resource *res, *pin; int error; printk(DRIVER_NAME "leds_device_probe, found leds\n"); /* 注册字符设备驱动 */ major = register_chrdev(0, DRIVER_NAME, &leds_driver_fops); /* fops file_operations结构体 */ if (major < 0) { printk(DRIVER_NAME " can't register major number\n"); error = major; goto fail; } /* 创建逻辑类,会在内核是/sys目录下生成一个s3c2440_leds目录 */ s3c2440_class = class_create(THIS_MODULE, DRIVER_NAME); if (IS_ERR(s3c2440_class)) { error = PTR_ERR(s3c2440_class); goto fail1; } /* 创建逻辑类,会在内核是/dev目录下生成一个leds设备 */ s3c2440_class_device = class_device_create(s3c2440_class, NULL, MKDEV(major, 0), NULL, "leds"); if (unlikely(IS_ERR(s3c2440_class_device))) { error = PTR_ERR(s3c2440_class_device); goto fail2; } /* 根据platform_device的资源,进行ioremap操作 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pin = platform_get_resource(pdev, IORESOURCE_IO, 0); if (!res || !pin) { printk(DRIVER_NAME "leds driver get platform resource [%s] failed.\n", (res == NULL ? "IORESOURCE_MEM" : "IORESOURCE_IO")); error = -1; goto fail3; } gpio_con = (volatile unsigned long*)ioremap(res->start, res->end - res->start + 1); gpio_dat = gpio_con + 1; led_pin = pin->start; return 0; fail3: class_device_destroy(s3c2440_class, MKDEV(major, 0)); fail2: class_destroy(s3c2440_class); fail1: unregister_chrdev(major, DRIVER_NAME); fail: return error; }
当 driver 卸载时,会对应的调用 platform_driver 中的 .remove 函数。
remove 相当于替换掉了原始 LED 驱动程序中的 exit函数。负责对 .probe 申请的资源进行释放。
static int leds_device_remove(struct platform_device *pdev) { printk("leds_device_remove, remove leds\n"); class_device_destroy(s3c2440_class, MKDEV(major, 0)); class_destroy(s3c2440_class); unregister_chrdev(major, DRIVER_NAME); return 0; }
当应用程序打开 /dev/leds 时就会访问到 leds_driver 驱动程序中的 file_operation 结构体:
static int s3c2440_leds_open(struct inode *inode, struct file *file)
{
*gpio_con &= ~(0x3 << (led_pin * 2)); /* 清零 */
*gpio_con |= (0x1 << (led_pin * 2)); /* 设置为输出引脚 */
return 0;
}
static int s3c2440_leds_close(struct inode *inode, struct file *file)
{
return 0;
}
static int s3c2440_leds_write(struct file * file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long val;
int ret;
ret = copy_from_user(&val, buf, count);
if (val == 1)
*gpio_dat &= ~(1 << led_pin); /* 点灯 */
else
*gpio_dat |= (1 << led_pin); /* 灭灯 */
return 0;
}
static struct file_operations leds_driver_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = s3c2440_leds_open,
.release = s3c2440_leds_close,
.write = s3c2440_leds_write,
};
完整的程序
基于平台设备实现的驱动程序-Linux文档类资源-CSDN文库
测试
编写完 device 和 driver 后,我们就可以拿到开发板进行测试了
输入如下的指令:
我们可以看到 LED 被点亮了。