本文主要探讨使用210的gpiolib库编写led驱动。
gpio.h
#define S5PV210_GPIO_A0_NR (8)
......
#define S5PV210_GPIO_ETC4_NR (6)
定义端口的GPIO数目
#define S5PV210_GPIO_NEXT(__gpio) \
((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
定义端口起始GPIO值(gpiochip0,gpiochip9...)
#define S5PV210_GPA0(_nr) (S5PV210_GPIO_A0_START + (_nr))
...
#define S5PV210_ETC4(_nr) (S5PV210_GPIO_ETC4_START + (_nr))
定义每个GPIO号码,宏可获取每个GPIO号码, S5PV210_GPA0(4)为GPA0_4的号码
/sys/class/gpio
export文件写入端口n号到export将生成gpion目录(用于端口测试)
unexport文件写入端口n号到export将删除gpion目录
gpiochipn:
base为端口号(GPA0为0)
label端口名称
ngpio为gpio数目(gpio = base)
device
power电源相关
subsystem GPIO所属的子系统
uevent GPIO状态改变,通知用户
gpion:
active_low(1:GPIO高是逻辑低,0:GPIO高是逻辑高)
device
direction(输入:in, 输出:out)
edge中断触发(none:无,rising:上升沿触发,falling:下降沿触发,both:上升沿和下降沿触发)
power电源相关
subsystem GPIO所属的子系统
uevent GPIO状态改变,通知用户
value(0、1)
LED内核驱动框架
drivers/leds为LED硬件驱动目录,led-class.c和led-core.c为内核提供,其他leds-xxx.c为厂商提供,leds-xxx.c调用led-class.c和led-core.c的接口实现led驱动
leds-s3c24xx.c调用led_classdev_register注册LED驱动,led_classdev_register在drivers/leds/led-class.c定义
led_classdev_register函数创建leds类设备
210的led驱动为drivers/char/led/x210-led.c
static int s3c24xx_led_probe(struct platform_device *dev)
{
...
/* register our new led device */
ret = led_classdev_register(&dev->dev, &led->cdev);
if (ret < 0) {
dev_err(&dev->dev, "led_classdev_register failed\n");
kfree(led);
return ret;
}
}
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;
}
struct led_classdev {
const char *name;
int brightness;
int max_brightness;
int flags;
/* Lower 16 bits reflect status */
#define LED_SUSPENDED (1 << 0)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16)
/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
/* Activate hardware accelerated blink, delays are in
* miliseconds and if none is provided then a sensible default
* should be chosen. The call can adjust the timings if it can't
* match the values specified exactly. */
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off);
struct device *dev;
struct list_head node; /* LED Device list */
const char *default_trigger; /* Trigger to use */
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock;
struct led_trigger *trigger;
struct list_head trig_list;
void *trigger_data;
#endif
};
//led-class.c
subsys_initcall(leds_init);
//include/linux/init.h
#define subsys_initcall(fn) __define_initcall("4",fn,4)
//include/linux/init.h
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
//推导出自定义段
.initcall4.init
//arch/arm/kernel/vmlinux.lds
__initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
subsys_initcall宏,定义在linux/init.h中,宏将其声明的函数放到.initcall4.init段
module_init宏将声明函数放到.initcall6.init段
内核在启动过程中按照.initcalln.init(n=1..8)的层级依次初始化
led_class_attrs
subsys_initcall(leds_init);
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 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,
};
leds_init中创建了leds类(/sys/class/leds),led_class_attrs数组内容为/sys/class/leds/目录里的文件,该文件为内核向应用层提供的操作接口(类似设备文件)
led_brightness_show函数类似read,led_brightness_store雷日write,他们对应struct led_classdev结构体的读和写方法
写方法:void (*brightness_set)(struct led_classdev *led_cdev,enum led_brightness brightness);
读方法:法enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
gpiolib
//mach-mdkc110.c
#ifdef CONFIG_MACH_SMDKC110
MACHINE_START(SMDKC110, "SMDKC110")
#elif CONFIG_MACH_SMDKV210
MACHINE_START(SMDKV210, "SMDKV210")
#endif
/* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
.phys_io = S3C_PA_UART & 0xfff00000,
.io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc,
.boot_params = S5P_PA_SDRAM + 0x100,
//.fixup = smdkv210_fixup,
.init_irq = s5pv210_init_irq,
.map_io = smdkc110_map_io,
.init_machine = smdkc110_machine_init,
.timer = &s5p_systimer,
MACHINE_END
static void __init smdkc110_map_io(void)
{
s5p_init_io(NULL, 0, S5P_VA_CHIPID);
s3c24xx_init_clocks(24000000);
s5pv210_gpiolib_init();
s3c24xx_init_uarts(smdkc110_uartcfgs, ARRAY_SIZE(smdkc110_uartcfgs));
s5p_reserve_bootmem(smdkc110_media_devs, ARRAY_SIZE(smdkc110_media_devs));
#ifdef CONFIG_MTD_ONENAND
s5pc110_device_onenand.name = "s5pc110-onenand";
#endif
#ifdef CONFIG_MTD_NAND
s3c_device_nand.name = "s5pv210-nand";
#endif
s5p_device_rtc.name = "smdkc110-rtc";
}
初始化gpiolib库
//gpiolib.c
__init int s5pv210_gpiolib_init(void)
{
struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;
int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit);
int i = 0;
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
chip->base = S5PV210_BANK_BASE(i);
}
samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);
return 0;
}
struct s3c_gpio_chip {
struct gpio_chip chip;
struct s3c_gpio_cfg *config;
struct s3c_gpio_pm *pm;
void __iomem *base;
int eint_offset;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[7];
#endif
};
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
int (*request)(struct gpio_chip *chip,
unsigned offset);
void (*free)(struct gpio_chip *chip,
unsigned offset);
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);
int (*get)(struct gpio_chip *chip,
unsigned offset);
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);
int (*set_debounce)(struct gpio_chip *chip,
unsigned offset, unsigned debounce);
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int base;
u16 ngpio;
const char *const *names;
unsigned can_sleep:1;
unsigned exported:1;
};
static struct s3c_gpio_chip s5pv210_gpio_4bit[] = {
{
.chip = {
.base = S5PV210_GPA0(0),
.ngpio = S5PV210_GPIO_A0_NR,
.label = "GPA0",
.to_irq = s5p_gpiolib_gpioint_to_irq,
},
},
{
.chip = {
.base = S5PV210_GPA1(0),
.ngpio = S5PV210_GPIO_A1_NR,
.label = "GPA1",
.to_irq = s5p_gpiolib_gpioint_to_irq,
},
}
......
}
struct s3c_gpio_cfg {
unsigned int cfg_eint;
s3c_gpio_pull_t (*get_pull)(struct s3c_gpio_chip *chip, unsigned offs);
int (*set_pull)(struct s3c_gpio_chip *chip, unsigned offs,
s3c_gpio_pull_t pull);
int (*set_pin)(struct s3c_gpio_chip *chip, unsigned offs,
s3c_gpio_pull_t level);
unsigned (*get_config)(struct s3c_gpio_chip *chip, unsigned offs);
int (*set_config)(struct s3c_gpio_chip *chip, unsigned offs,
unsigned config);
};
void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
int nr_chips)
{
for (; nr_chips > 0; nr_chips--, chip++) {
samsung_gpiolib_add_4bit(chip);
s3c_gpiolib_add(chip);
}
}
struct gpio_chip chip;单口各模式定义(输入,输出,设置,读取)
int base;端口的基址编号
u16 ngpio;io口数量
const char *const *names;端口名
int (*to_irq)(struct gpio_chip *chip;基于io口计算中号方法
s5pv210_gpio_4bit为是s3c_gpio_chip结构体数组,用于gpiolib定义注册端口(未定义注册模式及模式操作方法)
struct s3c_gpio_cfg *config;端口上下拉等操作定义
void __iomem *base;端口寄存器虚拟地址基地址(JPA0端口地址,端口包含多个io口)
samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);定义注册端口模式及模式操作方法和端口注册
samsung_gpiolib_add_4bit(chip);定义注册端口模式及模式操作方法
s3c_gpiolib_add(chip);端口注册(调用gpiochip_add函数将GPIO端口信息chip结构体链接gpiolib定义的gpio_desc数组)
int gpiochip_add(struct gpio_chip *chip)
{
......
/* these GPIO numbers must not be managed by another gpio_chip */
for (id = base; id < base + chip->ngpio; id++) {
if (gpio_desc[id].chip != NULL) {
status = -EBUSY;
break;
}
}
.....
}
gpiolib通用函数(drivers/gpio/gpiolib.c)
int gpiochip_add(struct gpio_chip *chip);
注册gpiolib
int __must_check gpiochip_remove(struct gpio_chip *chip);
清除注册
struct gpio {
unsigned gpio;
unsigned long flags;
const char *label;
};
flag(gpio.h):
#define GPIOF_DIR_OUT (0 << 0)
#define GPIOF_DIR_IN (1 << 0)
#define GPIOF_INIT_LOW (0 << 1)
#define GPIOF_INIT_HIGH (1 << 1)
#define GPIOF_IN (GPIOF_DIR_IN)
#define GPIOF_OUT_INIT_LOW (GPIOF_DIR_OUT | GPIOF_INIT_LOW)
#define GPIOF_OUT_INIT_HIGH (GPIOF_DIR_OUT | GPIOF_INIT_HIGH)
GPIOF_DIR_IN 输入
GPIOF_DIR_OUT 输出
GPIOF_INIT_LOW 输出低电平
GPIOF_INIT_HIGH 输出高电平
GPIOF_IN 输入
GPIOF_OUT_INIT_LOW 输出低电平
GPIOF_OUT_INIT_HIGH 输出高电平
int gpio_request(unsigned gpio, const char *label)
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label)
int gpio_request_array(struct gpio *array, size_t num);
申请gpio口
int gpio_is_valid(int number)
检查GPIO是否有效
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
设置GPIO为输入或输出模式
int __gpio_get_value(unsigned gpio);
读取gpio
void __gpio_set_value(unsigned gpio, int value);
设置GPIO
void gpio_free(unsigned gpio);
void gpio_free_array(struct gpio *array, size_t num);
释放申请的gpio
demo:
使用gpiolib编写led驱动
Makefile
# LED Platform Drivers
obj-$(CONFIG_LEDS_CXB_210) += led-cxb_210.o
Kconfig
if LEDS_CLASS
comment "LED drivers"
config LEDS_CXB_210
tristate "LED Support for x210 led "
help
x210 led form cxb.
menuconfig
Device Drivers --->
[*] LED Support --->
--- LED Support <*> LED Class Support
*** LED drivers ***
<*> LED Support for x210 led
led-cxb_210.c
#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>
#define LED1 S5PV210_GPJ0(3)
#define LED2 S5PV210_GPJ0(4)
#define LED3 S5PV210_GPJ0(5)
#define LED1_GPIO "led1_gpio3"
#define LED2_GPIO "led2_gpio4"
#define LED3_GPIO "led3_gpio5"
#define LED1_NAME "led1"
#define LED2_NAME "led2"
#define LED3_NAME "led3"
static struct led_classdev s5pv210_led1;
static struct led_classdev s5pv210_led2;
static struct led_classdev s5pv210_led3;
#define LED_OFF 1
#define LED_ON 0
struct gpio led[] = {
{S5PV210_GPJ0(3),GPIOF_OUT_INIT_HIGH,LED1_GPIO},
{S5PV210_GPJ0(4),GPIOF_OUT_INIT_HIGH,LED2_GPIO},
{S5PV210_GPJ0(5),GPIOF_OUT_INIT_HIGH,LED3_GPIO},
};
static void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness value)
{
if(value == LED_OFF)
{
gpio_set_value(LED1,LED_OFF);
}
else
{
gpio_set_value(LED1,LED_ON);
}
}
static void s5pv210_led2_set(struct led_classdev *led_cdev,enum led_brightness value)
{
if(value == LED_OFF)
{
gpio_set_value(LED1,LED_OFF);
}
else
{
gpio_set_value(LED1,LED_ON);
}
}
static void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness value)
{
if(value == LED_OFF)
{
gpio_set_value(LED1,LED_OFF);
}
else
{
gpio_set_value(LED1,LED_ON);
}
}
static int __init led_dev_init(void)
{
int ret = -1;
s5pv210_led1.name = LED1_NAME;
s5pv210_led1.brightness = 0;
s5pv210_led1.brightness_set = s5pv210_led1_set;
ret = led_classdev_register(NULL, &s5pv210_led1);
if(ret < 0)
{
printk(KERN_INFO "210_led1 register fail\n");
goto flag_1;
}
s5pv210_led2.name = LED2_NAME;
s5pv210_led2.brightness = 0;
s5pv210_led2.brightness_set = s5pv210_led2_set;
ret = led_classdev_register(NULL, &s5pv210_led2);
if(ret < 0)
{
printk(KERN_INFO "210_led2 register fail\n");
goto flag_2;
}
s5pv210_led3.name = LED3_NAME;
s5pv210_led3.brightness = 0;
s5pv210_led3.brightness_set = s5pv210_led3_set;
ret = led_classdev_register(NULL, &s5pv210_led3);
if(ret < 0)
{
printk(KERN_INFO "210_led3 register fail\n");
goto flag_3;
}
ret = gpio_request_array(led, ARRAY_SIZE(led));
if(ret < 0)
{
printk(KERN_INFO "request gpio fail\n");
goto flag_4;
}
return ret;
flag_4:
gpio_free_array(led,ARRAY_SIZE(led));
flag_3:
led_classdev_unregister(&s5pv210_led3);
flag_2:
led_classdev_unregister(&s5pv210_led2);
flag_1:
led_classdev_unregister(&s5pv210_led1);
return ret;
}
static void __exit led_dev_exit(void)
{
led_classdev_unregister(&s5pv210_led1);
led_classdev_unregister(&s5pv210_led2);
led_classdev_unregister(&s5pv210_led2);
gpio_free_array(led,ARRAY_SIZE(led));
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cxb");
MODULE_DESCRIPTION("led gpio module");
MODULE_ALIAS("led");