5.3 LED字符设备驱动

1. 简单的LED设备驱动

1.1 静态映射操作LED
复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/string.h>

#define LED_MODULE_NAME	"static_led_module"

#define reg_GPJ0CON	*((volatile unsigned int *) S5PV210_GPJ0CON)
#define reg_GPJ0DAT *((volatile unsigned int *) S5PV210_GPJ0DAT)

int s_led_module_major;

// 主映射表:arch/arm/plat-s5p/include/plat/Map-s5p.h 
// 虚拟地址基地址定义:arch/arm/plat-samsung/include/plat/map-base.h 0xFD000000是虚拟映射地址的基地址
// GPIO相关的主映射表:arch/arm/mach-s5pv210/include/mach/regs-gpio.h
// GPIO具体寄存器定义:arch/arm/mach-s5pv210/include/mach/gpio-bank.h

static int static_led_module_read(struct file* file, char __user* buf, size_t count, loff_t* ppos)
{
	return 0;
}

static int static_led_module_write(struct file* file, const char __user* ubuf, size_t count, loff_t* ppos)
{
	return 0;
}

static int static_led_module_open(struct inode* inode, struct file* file)
{
	reg_GPJ0CON = 0x11111111;
	reg_GPJ0DAT = ((0 << 3) | (0 << 4) | (0 << 5));
	return 0;
}

static int static_led_module_release(struct inode* inode, struct file* file)
{
	reg_GPJ0DAT = ((1 << 3) | (1 << 4) | (1 << 5));
	return 0;
}

static const struct file_operations static_led_fops =
{
	.owner		= THIS_MODULE,
	.read		= static_led_module_read,
	.write		= static_led_module_write,
	.open		= static_led_module_open,
	.release	= static_led_module_release,
};

static int __init static_led_init(void)
{
	s_led_module_major = register_chrdev(0, LED_MODULE_NAME, &static_led_fops);
	if (s_led_module_major < 0)
	{
		printk(KERN_ERR "static_led_init register_chrdev failed\n");
		return -EINVAL;
	}
	return 0;
}

static void __exit static_led_exit(void)
{
	unregister_chrdev(s_led_module_major, LED_MODULE_NAME);
}

module_init(static_led_init);
module_exit(static_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xy_L");
MODULE_DESCRIPTION("static_led_module");
MODULE_ALIAS("static_led_test");
1.2 动态映射操作LED
复制代码
//向内核申请需要映射的内存资源
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0) 
 
//检查物理地址的合法性,建立页表(包括访问权限),完成物理地址到虚拟地址的转换
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);



//释放内存资源
#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))
 
//取消ioremap的映射
void iounmap(void * addr);
 
//需要先iounmap再进行release_mem_region

案例:

复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define LED_MODULE_NAME	"dynamic_led_module"

#define GPJ0CON    0xE0200240    //GPJ0CON物理地址
#define GPJ0DAT    0xE0200244    //GPJ0DAT物理地址
 
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;


struct cdev s_cdev;   /* 字符设备 */
dev_t s_devNo;        /* 设备号 */

static struct class* s_pmodule_class;


static int dynamic_led_module_read(struct file* file, char __user* buf, size_t count, loff_t* ppos)
{
    char kBuf[1024] = "module_test_read";
    int ret = copy_to_user(ubuf, kBuf, count);
    if (ret == 0)
        printk(KERN_DEBUG, "copy_to_user success");

	return 0;
}

static int dynamic_led_module_write(struct file* file, const char __user* ubuf, size_t count, loff_t* ppos)
{
    char kBuf[1024];
    int ret = copy_from_user(kBuf, ubuf, count);
    if (ret == 0)
        printk(KERN_DEBUG, "copy_from_user success");
        
    *pGPJ0CON = 0x11111111; 
    if (kBuf[0] = '1')
        *pGPJ0DAT = (0 << 3);
    else if (kBuf[0] = '0')
        *pGPJ0DAT = (1 << 3);

	return 0;
}

static int dynamic_led_module_open(struct inode* inode, struct file* file)
{
    //***************** 动态映射操作寄存器 *************/
    //动态映射GPJ0CON
    if (!request_mem_region(GPJ0CON, 4, "GPJ0CON"))
        return -EINVAL;

    pGPJ0CON = ioremap(GPJ0CON, 4);
    
    //动态映射GPJ0CON                                                   
    if (!request_mem_region(GPJ0DAT, 4, "GPJ0DAT"))
        return -EINVAL;
 
     pGPJ0DAT = ioremap(GPJ0DAT, 4);

	return 0;
}

static int dynamic_led_module_release(struct inode* inode, struct file* file)
{
	// 解除映射
    iounmap(pGPJ0CON);
    release_mem_region(GPJ0CON, 4);
    
    iounmap(pGPJ0DAT);
 
    release_mem_region(GPJ0DAT, 4);

	return 0;
}

static const struct file_operations dynamic_led_fops =
{
	.owner		= THIS_MODULE,
	.read		= dynamic_led_module_read,
	.write		= dynamic_led_module_write,
	.open		= dynamic_led_module_open,
	.release	= dynamic_led_module_release,
};

static int __init dynamic_led_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&s_devNo, 0, 1, LED_MODULE_NAME);
	if (ret)
	{
		printk(KERN_ERR "alloc_chrdev_region failed\n");
		return -EINVAL;
	}

    // 字符设备号和file_opertations绑定
	cdev_init(&s_cdev, &dynamic_led_fops );
 
	// 注册字符设备驱动
	ret = cdev_add(&s_cdev, s_devNo, 1);
	if (ret)
	{
		printk(KERN_ERR "cdev_add failed\n");
		return -EINVAL;
	}
 
	// 创建设备类文件
	s_pmodule_class = class_create(THIS_MODULE, "led_module_test");
	if (IS_ERR(s_pmodule_class))
		return -EINVAL;
 
	device_create(s_pmodule_class, NULL, s_devNo, NULL, "led_module");

	return 0;
}

static void __exit static_led_exit(void)
{
	// 销毁设备文件
	device_destroy(s_pmodule_class, s_devNo);
 
	// 销毁设备类
	class_destroy(s_pmodule_class);
 
	// 注销字符设备驱动
	cdev_del(&s_cdev);
 
	// 注销字符设备号
	unregister_chrdev_region(s_devNo, 1);
}

module_init(dynamic_led_init);
module_exit(dynamic_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xy_L");
MODULE_DESCRIPTION("dynamic_led_module");
MODULE_ALIAS("dynamic_led_test");

2. LED驱动框架

2.1 LED驱动框架分析

(1)Linux的LED驱动框架源代码在kernel/drivers/leds目录中。

(2)leds-class.c和leds-core.c就是内核封装的led驱动框架。

leds-class.c:

复制代码
/*
 * LED Class Core
 *
 * Copyright (C) 2005 John Lenz <[email protected]>
 * Copyright (C) 2005-2007 Richard Purdie <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/sysdev.h>
#include <linux/timer.h>
#include <linux/err.h>
#include <linux/ctype.h>
#include <linux/leds.h>
#include "leds.h"

static struct class *leds_class;

static void led_update_brightness(struct led_classdev *led_cdev)
{
	if (led_cdev->brightness_get)
		led_cdev->brightness = led_cdev->brightness_get(led_cdev);
}

static ssize_t led_brightness_show(struct device *dev, 
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	/* no lock needed for this */
	led_update_brightness(led_cdev);

	return sprintf(buf, "%u\n", led_cdev->brightness);
}

static ssize_t led_brightness_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	ssize_t ret = -EINVAL;
	char *after;
	unsigned long state = simple_strtoul(buf, &after, 10);
	size_t count = after - buf;

	if (isspace(*after))
		count++;

	if (count == size) {
		ret = count;

		if (state == LED_OFF)
			led_trigger_remove(led_cdev);
		led_set_brightness(led_cdev, state);
	}

	return ret;
}

static ssize_t led_max_brightness_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	return sprintf(buf, "%u\n", led_cdev->max_brightness);
}

static struct device_attribute led_class_attrs[] = {
	__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
	__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
	__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
	__ATTR_NULL,
};

/**
 * led_classdev_suspend - suspend an led_classdev.
 * @led_cdev: the led_classdev to suspend.
 */
void led_classdev_suspend(struct led_classdev *led_cdev)
{
	led_cdev->flags |= LED_SUSPENDED;
	led_cdev->brightness_set(led_cdev, 0);
}
EXPORT_SYMBOL_GPL(led_classdev_suspend);

/**
 * led_classdev_resume - resume an led_classdev.
 * @led_cdev: the led_classdev to resume.
 */
void led_classdev_resume(struct led_classdev *led_cdev)
{
	led_cdev->brightness_set(led_cdev, led_cdev->brightness);
	led_cdev->flags &= ~LED_SUSPENDED;
}
EXPORT_SYMBOL_GPL(led_classdev_resume);

static int led_suspend(struct device *dev, pm_message_t state)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_suspend(led_cdev);

	return 0;
}

static int led_resume(struct device *dev)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_resume(led_cdev);

	return 0;
}

/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
	led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
				      "%s", led_cdev->name);
	if (IS_ERR(led_cdev->dev))
		return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);
#endif
	/* add to the list of leds */
	down_write(&leds_list_lock);
	list_add_tail(&led_cdev->node, &leds_list);
	up_write(&leds_list_lock);

	if (!led_cdev->max_brightness)
		led_cdev->max_brightness = LED_FULL;

	led_update_brightness(led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);
#endif

	printk(KERN_DEBUG "Registered led device: %s\n",
			led_cdev->name);

	return 0;
}

EXPORT_SYMBOL_GPL(led_classdev_register);

/**
 * led_classdev_unregister - unregisters a object of led_properties class.
 * @led_cdev: the led device to unregister
 *
 * Unregisters a previously registered via led_classdev_register object.
 */
void led_classdev_unregister(struct led_classdev *led_cdev)
{
#ifdef CONFIG_LEDS_TRIGGERS
	down_write(&led_cdev->trigger_lock);
	if (led_cdev->trigger)
		led_trigger_set(led_cdev, NULL);
	up_write(&led_cdev->trigger_lock);
#endif

	device_unregister(led_cdev->dev);

	down_write(&leds_list_lock);
	list_del(&led_cdev->node);
	up_write(&leds_list_lock);
}
EXPORT_SYMBOL_GPL(led_classdev_unregister);

static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	leds_class->suspend = led_suspend;
	leds_class->resume = led_resume;
	leds_class->dev_attrs = led_class_attrs;
	return 0;
}

static void __exit leds_exit(void)
{
	class_destroy(leds_class);
}

subsys_initcall(leds_init);
module_exit(leds_exit);

MODULE_AUTHOR("John Lenz, Richard Purdie");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED Class Interface");

案例:

复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/errno.h>

#define GPIO_LED1	S5PV210_GPJ0(3)
#define GPIO_LED2	S5PV210_GPJ0(4)
#define GPIO_LED3	S5PV210_GPJ0(5)

static struct led_classdev s_led1_classdev;
static struct led_classdev s_led2_classdev;
static struct led_classdev s_led3_classdev;
/*
* led驱动框架
* kernel/driver/leds目录就是led驱动框架的源代码
* led-class.c
* led-core.c
*/ 

/*
* gpiolib
* struct s3c_gpio_chip
* s5pv210_gpio_4bit
*/

static void led1_set(struct led_classdev* led_cdev, enum led_brightness value)
{
	if (value == LED_OFF)
	{
		gpio_set_value(GPIO_LED1, 1);
	}
	else
	{
		gpio_set_value(GPIO_LED1, 0);
	}
}

static void led2_set(struct led_classdev* led_cdev, enum led_brightness value)
{
	if (value == LED_OFF)
	{
		gpio_set_value(GPIO_LED2, 1);
	}
	else
	{
		gpio_set_value(GPIO_LED2, 0);
	}
}

static void led3_set(struct led_classdev* led_cdev, enum led_brightness value)
{
	if (value == LED_OFF)
	{
		gpio_set_value(GPIO_LED3, 1);
	}
	else
	{
		gpio_set_value(GPIO_LED3, 1);
	}
}

static int __init led_driver_framework_init(void)
{
	int ret = 0;

	if (gpio_request(GPIO_LED1, "led1_gpj0.3"))
	{
		printk(KERN_ERR "gpio_request failed\n");
	}
	else
	{
		gpio_direction_output(GPIO_LED1, 1);
	}

	if (gpio_request(GPIO_LED2, "led2_gpj0.4"))
	{
		printk(KERN_ERR "gpio_request failed\n");
	}
	else
	{
		gpio_direction_output(GPIO_LED2, 1);
	}

	if (gpio_request(GPIO_LED3, "led3_gpj0.5"))
	{
		printk(KERN_ERR "gpio_request failed\n");
	}
	else
	{
		gpio_direction_output(GPIO_LED3, 1);
	}

	s_led1_classdev.name = "led1";
	s_led1_classdev.brightness = 0;
	s_led1_classdev.brightness_set = led1_set;

	ret = led_classdev_register(NULL, &s_led1_classdev);
	if (ret < 0)
	{
		printk(KERN_ERR "led1 led_classdev_register failed\n");
	}

	s_led2_classdev.name = "led2";
	s_led2_classdev.brightness = 0;
	s_led2_classdev.brightness_set = led2_set;

	ret = led_classdev_register(NULL, &s_led2_classdev);
	if (ret < 0)
	{
		printk(KERN_ERR "led3 led_classdev_register failed\n");
	}

	s_led3_classdev.name = "led3";
	s_led3_classdev.brightness = 0;
	s_led3_classdev.brightness_set = led3_set;

	ret = led_classdev_register(NULL, &s_led3_classdev);
	if (ret < 0)
	{
		printk(KERN_ERR "led3 led_classdev_register failed\n");
	}
	return ret;
}

static void __exit led_driver_framework_exit(void)
{
	led_classdev_unregister(&s_led1_classdev);
	led_classdev_unregister(&s_led2_classdev);
	led_classdev_unregister(&s_led3_classdev);

	gpio_free(GPIO_LED1);
	gpio_free(GPIO_LED2);
	gpio_free(GPIO_LED3);
}

module_init(led_driver_framework_init);
module_exit(led_driver_framework_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xy_L");
MODULE_DESCRIPTION("led_driver_framework");
MODULE_ALIAS("led_driver_framework_test");
相关推荐
皓月盈江1 小时前
国产Linux银河麒麟操作系统安装开源免费Draw.io(diagrams.net)替代Visio
linux·ubuntu·开源·draw.io·visio·银河麒麟操作系统·diagrams.net
2301_793102492 小时前
linux——C程序的编译与调试
linux
三体世界3 小时前
HTTPS加密原理
linux·开发语言·网络·c++·网络协议·http·https
CBCU3 小时前
关于ubuntu环境下vscode进行debug的随笔
linux·vscode·ubuntu
우 유4 小时前
【ing】Ubuntu安装Anaconda及环境配置\docker\pycharm
linux·运维·ubuntu
lqjun08274 小时前
在Ubuntu 24.04上安装cuDNN v8.x.x兼容CUDA 11.8
linux·运维·ubuntu
ZKf30FkG5 小时前
在 CentOS 7.9 中 Node 18.20.2 安装指南
linux·运维·centos
Virgocloud5 小时前
Centos更换镜像源-腾讯云镜像源
linux·centos·腾讯云
魂尾ac5 小时前
VMware Workstation Pro下Centos 7.9 安装
linux·运维·centos