jz2440_基于平台设备的LED驱动程序

发布于:2022-07-27 ⋅ 阅读:(399) ⋅ 点赞:(0)

平台设备驱动程序是基于总线系统实现的,由三个部分组成:

  1. 总线,这里的总线使用的是内核自带的 platform_bus_type 总线,无需我们进行实现;
  2. 设备,这个驱动主要是对 LED 灯的操作,因此设备需要记录 LED 的资源;
  3. 驱动,驱动完成点灯、关灯等操作;

设备

  1. 定义设备结构体:

    #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。

  2. 资源定义,我们的驱动程序是用于驱动 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;

  3. 定义 init 函数:init 函数只需要完成注册平台设备即可

    static int __init leds_dev_init(void)
    {
    	platform_device_register(&leds_device);
    	printk(KERN_INFO "leds device register.\n");
    	return 0;
    }
    
  4. 定义 exit 函数:卸载平台设备即可

    static void __exit leds_dev_exit(void)
    {
    	platform_device_unregister(&leds_device);
    	printk(KERN_INFO "leds device unregister.\n");
    }
    
  5. 实现 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 中的资源,即可将我们的驱动程序移植到其它开发板上。

驱动程序需要完成以下工作:

  1. 首先,我们需要创建一个 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 函数我们在后面进行说明。

  2. 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;
    }
    
  3. exit 函数:卸载 driver 驱动程序

    static void __exit leds_driver_exit(void)
    {
    	platform_driver_unregister(&leds_device_driver);
    	printk(DRIVER_NAME "leds driver unregister.\n");
    }
    
  4. 当平台设备加载进内核后,会与 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;
    }
    
  5. 当 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 被点亮了。
在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到