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");