上一节我们写出了简单的可以操控硬件的单个led驱动。
本节我们主要给出利用多个次设备号,来实现多个led驱动的注册。
同时来使用下gpiolib库,来使用一些专用于gpio的操作。
下面是主要代码片段。
其作用是自从分配主设备号,并使用其次设备号0来同时控制三盏led,次设备号1,2,3来分别控制每一栈
#include <linux/fs.h> /* 包含file_operation结构体 */
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <asm/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <asm/gpio.h>
#include <linux/gfp.h>
#include <plat/gpio-cfg.h>
#define GPJ0CON_PA 0xe0200240
static unsigned int major;
static struct class *leds_class;
static struct device *led_dev[4];
struct gpj0 {
unsigned int gpj0con;
unsigned int gpj0dat;
};
static struct gpj0* p_gpj0 = NULL;
static int leds_drv_open(struct inode *inode, struct file *file)
{
int minor = MINOR(inode->i_rdev); /* 得到次设备号 */
printk("leds_drv_open\n");
switch(minor)
{
case 0:
/* 初始化gpj0_3.4.5为输出 */
p_gpj0->gpj0con &= ~((0xf << 3*4)|(0xf << 4*4)|(0xf << 5*4));
p_gpj0->gpj0con |= (1 << 3*4)|(1 << 4*4)|(1 << 5*4);
/* 默认关闭led灯 */
p_gpj0->gpj0dat |= (1 << 3)|(1 << 4)|(1 << 5);
break;
case 1:
/* 初始化gpj0_3.4.5为输出 */
p_gpj0->gpj0con &= ~(0xf << 3*4);
p_gpj0->gpj0con |= (1 << 3*4);
/* 默认关闭led灯 */
p_gpj0->gpj0dat |= (1 << 3);
break;
case 2:
s3c_gpio_cfgpin(S5PV210_GPJ0(4), S3C_GPIO_SFN(1)); /* 输出模式 */
s3c_gpio_setpull(S5PV210_GPJ0(4), S3C_GPIO_PULL_UP); /* 上拉 */
gpio_set_value(S5PV210_GPJ0(4),1); /* 设置关闭led */
break;
case 3:
s3c_gpio_cfgpin(S5PV210_GPJ0(5), S3C_GPIO_SFN(1));
s3c_gpio_setpull(S5PV210_GPJ0(5), S3C_GPIO_PULL_UP);
p_gpj0->gpj0dat |= (1 << 5);
break;
default:
break;
}
return 0;
}
static ssize_t leds_drv_write(struct file *file, const char __user * from, size_t len, loff_t *ppos)
{
char buf[10];
int ret, val;
int minor = MINOR(file->f_inode->i_rdev);
printk("leds_drv_write\n");
memset(buf, 0,10);
/* 拷贝用户空间的数据到内核空间 */
ret = copy_from_user(buf, from, 3);
if(ret)
{
printk("copy_from_user fail\n");
}
if(strcmp("on", buf))
{
val = 1;
}
else
{
val = 0;
}
/* 根据参数设置led的输出 */
switch(minor)
{
case 0:
if(val)
p_gpj0->gpj0dat |= 7<<3;
else
p_gpj0->gpj0dat &= ~(7<<3);
break;
case 1:
gpio_set_value(S5PV210_GPJ0(3),val);
break;
case 2:
gpio_set_value(S5PV210_GPJ0(4),val);
break;
case 3:
gpio_set_value(S5PV210_GPJ0(5),val);
break;
default:
break;
}
return 0;
}
static const struct file_operations leds_drv_file_operation = {
.owner = THIS_MODULE,
.open = leds_drv_open,
.write = leds_drv_write,
};
static int __init leds_drv_init(void)
{
int i;
printk("leds_drv_init\n");
major = register_chrdev(0,"leds_drv",&leds_drv_file_operation);
/* 映射io寄存器 */
p_gpj0 = ioremap(GPJ0CON_PA, 0x08);
if(NULL == p_gpj0)
{
printk("ioremap fail\n");
return -1;
}
/* 创建一个类 */
leds_class = class_create(THIS_MODULE, "leds_class");
if(!leds_class)
{
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
}
/* 创建从属这个类的设备 */
led_dev[0] = device_create(leds_class,NULL,MKDEV(major, 0), NULL, "leds");
if (IS_ERR(led_dev))
return PTR_ERR(led_dev);
/* 创建其它三个设备 */
for(i = 1; i < 4; i++)
{
led_dev[i] = device_create(leds_class,NULL,MKDEV(major, i), NULL, "led%d",i);
if (IS_ERR(led_dev))
return PTR_ERR(led_dev);
}
if(gpio_request(S5PV210_GPJ0(3), "led1-gpj0-3"))
{
return -1;
}
if(gpio_request(S5PV210_GPJ0(4), "led2-gpj0-4"))
{
return -1;
}
if(gpio_request(S5PV210_GPJ0(5), "led3-gpj0-5"))
{
return -1;
}
return 0;
}
static void __exit leds_drv_exit(void)
{
int i;
unregister_chrdev(major,"leds_drv");
printk("leds_drv_exit\n");
/* 使用完取消映射 */
iounmap(p_gpj0);
/* 注销类里面的设备 */
for(i = 0; i < 4; i++)
{
device_unregister(led_dev[i]);
}
/* 注销类 */
class_destroy(leds_class);
gpio_free(S5PV210_GPJ0(3));
gpio_free(S5PV210_GPJ0(4));
gpio_free(S5PV210_GPJ0(5));
}
module_init(leds_drv_init);
module_exit(leds_drv_exit);
MODULE_LICENSE("GPL");
先从注册函数说起
/* 创建其它三个设备 */
for(i = 1; i < 4; i++)
{
led_dev[i] = device_create(leds_class,NULL,MKDEV(major, i), NULL, "led%d",i);
if (IS_ERR(led_dev))
return PTR_ERR(led_dev);
}
注册函数中的其它部分我们都已经分析过了,起始这个很类似,即使用同一个主设备号,不同的次设备号注册多个设备(次设备的名字是通过类似printf的格式打印做的)。
这里用到了gpiolib库,我们主要来说一下,gpiolib库。
前两节的ioremap可以映射所有的寄存器(arm),当然也包括gpio的寄存器。
我们的使用是在映射后的地址空间内,以写内存的方式来操纵。
在arm中没什么问题,但在其它架构可能就有问题。
系统提供了比较通用的操纵寄存器的方法。
通常我们操纵寄存器,比较通用的写法是使用上面提供的接口来操纵。(后面的驱动代码都会使用这种方式)
注:这种使用寄存器的步骤
1.ioremap映射寄存器
2.readl/writel等读写寄存器
3.最终用完后iounmap释放映射(页表)寄存器
当然这里我们是使用gpio的寄存器,它是寄存器中的一种。
因为其特殊性,linux内核专门做了一个库,称为gpiolib.
其特殊性:因为gpio可以被多个外部外设复用(其它的如i2c,lcd等都是专用的),所以如果在一个外设正在使用时,另一个也使用,会导致时序错乱,所以所以必须要等到前一个使用完后另一个才能使用,这样才能让驱动完美配合。
介于前面的所说的种种原因,最简单的方法是,和其它资源一样,使用某个IO时先申请,申请成功再使用,否则睡眠等待资源到来或注册这个驱动失败。
关于gpiolib:
1.内核开发人员实现通用的接口(没实际操作)
2.各平台实现平台或具体cpu的具体细节后提交给内核开发者(指芯片原厂)
以下几个是内核中申请和释放gpio的函数
driver/gpio/gpiolib.c
int gpio_request(unsigned gpio, const char *label); /* 申请一个gpio */
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label); /* 申请一个gpio 并用flag初始化这个gpio */
int gpio_request_array(const struct gpio *array, size_t num); /* 申请多个gpio,里面调用的gpio_request_one */
申请gpio起始很简单,gpiolib中维护着一个表bitmap[数组],表中每个bit表示一个io,如果这个io没用,则这个
bit为0,如果用了,则标记为1。即请求时为0,则可以请求到,为1时请求会失败。
void gpio_free(unsigned gpio);
void gpio_free_array(const struct gpio *array, size_t num);
有申请,当然必须有释放了,释放也很简单,把bitmap表中的响应位置0即可。
可以看到上面的参数中有个gpio的参数,它表示gpio的标号,即系统要为每个gpio用数字作标记。
label表示这个gpio的名称(可以随便起,为了方便在查看使用情况)
请求和释放多个时,传入是gpio结构,起始它也是我们说的前面的几个参数的组合打包。
/**
* struct gpio - a structure describing a GPIO with configuration
* @gpio: the GPIO number
* @flags: GPIO configuration as specified by GPIOF_*
* @label: a literal description string of this GPIO
*/
struct gpio {
unsigned gpio;
unsigned long flags;
const char *label;
};
接下来看一下具体的芯片gpiolib库对gpio的标号分配
arch/arm/mach-s5pv210/include/mach/gpio.h
每组gpio的个数
/* GPIO bank sizes */
#define S5PV210_GPIO_A0_NR (8)
#define S5PV210_GPIO_A1_NR (4)
#define S5PV210_GPIO_B_NR (8)
#define S5PV210_GPIO_C0_NR (5)
..........
#define S5PV210_GPIO_J1_NR (6)
#define S5PV210_GPIO_J2_NR (8)
#define S5PV210_GPIO_J3_NR (8)
#define S5PV210_GPIO_J4_NR (5)
#define S5PV210_GPIO_MP01_NR (8)
#define S5PV210_GPIO_MP02_NR (4)
#define S5PV210_GPIO_MP03_NR (8)
#define S5PV210_GPIO_MP04_NR (8)
#define S5PV210_GPIO_MP05_NR (8)
每组gpio的起始标号
#define S5PV210_GPIO_NEXT(__gpio) \
((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
enum s5p_gpio_number {
S5PV210_GPIO_A0_START = 0,
S5PV210_GPIO_A1_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_A0),
S5PV210_GPIO_B_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_A1),
S5PV210_GPIO_C0_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_B),
.............
S5PV210_GPIO_MP03_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_MP02),
S5PV210_GPIO_MP04_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_MP03),
S5PV210_GPIO_MP05_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_MP04),
};
我们以GPIOB为例来分析
S5PV210_GPIO_B_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_A1)
#define S5PV210_GPIO_NEXT(__gpio) \
((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
S5PV210_GPIO_NEXT(S5PV210_GPIO_A1)
((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
S5PV210_GPIO_A1_START + S5PV210_GPIO_A1_NR + CONFIG_S3C_GPIO_SPACE + 1
.....
((0 + 8 + CONFIG_S3C_GPIO_SPACE + 1) + 4 + CONFIG_S3C_GPIO_SPACE + 1)
其中这个CONFIG_S3C_GPIO_SPACE 实在.config文件中可以查找到的
可以发现每组之间都多增加了1个,作为间隔
每个的gpio编号 = 组起始编号 + 组内偏移
/* S5PV210 GPIO number definitions */
#define S5PV210_GPA0(_nr) (S5PV210_GPIO_A0_START + (_nr))
#define S5PV210_GPA1(_nr) (S5PV210_GPIO_A1_START + (_nr))
#define S5PV210_GPB(_nr) (S5PV210_GPIO_B_START + (_nr))
#define S5PV210_GPC0(_nr) (S5PV210_GPIO_C0_START + (_nr))
#define S5PV210_GPC1(_nr) (S5PV210_GPIO_C1_START + (_nr))
.......
#define S5PV210_MP02(_nr) (S5PV210_GPIO_MP02_START + (_nr))
#define S5PV210_MP03(_nr) (S5PV210_GPIO_MP03_START + (_nr))
#define S5PV210_MP04(_nr) (S5PV210_GPIO_MP04_START + (_nr))
#define S5PV210_MP05(_nr) (S5PV210_GPIO_MP05_START + (_nr))
关于gpiolib库的实现
1.主要是实现下面的这个结构体
/**
* struct gpio_chip - abstract a GPIO controller
* @label: for diagnostics
* @dev: optional device providing the GPIOs
* @owner: helps prevent removal of modules exporting active GPIOs
* @list: links gpio_chips together for traversal
* @request: optional hook for chip-specific activation, such as
* enabling module power and clock; may sleep
* @free: optional hook for chip-specific deactivation, such as
* disabling module power and clock; may sleep
* @get_direction: returns direction for signal "offset", 0=out, 1=in,
* (same as GPIOF_DIR_XXX), or negative error
* @direction_input: configures signal "offset" as input, or returns error
* @direction_output: configures signal "offset" as output, or returns error
* @get: returns value for signal "offset"; for output signals this
* returns either the value actually sensed, or zero
* @set: assigns output value for signal "offset"
* @set_debounce: optional hook for setting debounce time for specified gpio in
* interrupt triggered gpio chips
* @to_irq: optional hook supporting non-static gpio_to_irq() mappings;
* implementation may not sleep
* @dbg_show: optional routine to show contents in debugfs; default code
* will be used when this is omitted, but custom code can show extra
* state (such as pullup/pulldown configuration).
* @base: identifies the first GPIO number handled by this chip; or, if
* negative during registration, requests dynamic ID allocation.
* @ngpio: the number of GPIOs handled by this controller; the last GPIO
* handled is (base + ngpio - 1).
* @desc: array of ngpio descriptors. Private.
* @names: if set, must be an array of strings to use as alternative
* names for the GPIOs in this chip. Any entry in the array
* may be NULL if there is no alias for the GPIO, however the
* array must be @ngpio entries long. A name can include a single printk
* format specifier for an unsigned int. It is substituted by the actual
* number of the gpio.
* @can_sleep: flag must be set iff get()/set() methods sleep, as they
* must while accessing GPIO expander chips over I2C or SPI. This
* implies that if the chip supports IRQs, these IRQs need to be threaded
* as the chip access may sleep when e.g. reading out the IRQ status
* registers.
* @exported: flags if the gpiochip is exported for use from sysfs. Private.
*
* A gpio_chip can help platforms abstract various sources of GPIOs so
* they can all be accessed through a common programing interface.
* Example sources would be SOC controllers, FPGAs, multifunction
* chips, dedicated GPIO expanders, and so on.
*
* Each chip controls a number of signals, identified in method calls
* by "offset" values in the range 0..(@ngpio - 1). When those signals
* are referenced through calls like gpio_get_value(gpio), the offset
* is calculated by subtracting @base from the gpio number.
*/
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
struct list_head list;
int (*request)(struct gpio_chip *chip,
unsigned offset);
void (*free)(struct gpio_chip *chip,
unsigned offset);
int (*get_direction)(struct gpio_chip *chip,
unsigned offset);
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);
int (*get)(struct gpio_chip *chip,
unsigned offset);
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
int (*set_debounce)(struct gpio_chip *chip,
unsigned offset,
unsigned debounce);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int base;
u16 ngpio;
struct gpio_desc *desc;
const char *const *names;
bool can_sleep;
bool exported;
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* With CONFIG_GPIO_IRQCHIP we get an irqchip inside the gpiolib
* to handle IRQs for most practical cases.
*/
struct irq_chip *irqchip;
struct irq_domain *irqdomain;
unsigned int irq_base;
irq_flow_handler_t irq_handler;
unsigned int irq_default_type;
#endif
#if defined(CONFIG_OF_GPIO)
/*
* If CONFIG_OF is enabled, then all GPIO controllers described in the
* device tree automatically may have an OF translation
*/
struct device_node *of_node;
int of_gpio_n_cells;
int (*of_xlate)(struct gpio_chip *gc,
const struct of_phandle_args *gpiospec, u32 *flags);
#endif
#ifdef CONFIG_PINCTRL
/*
* If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
* describe the actual pin range which they serve in an SoC. This
* information would be used by pinctrl subsystem to configure
* corresponding pins for gpio usage.
*/
struct list_head pin_ranges;
#endif
};
2.内提供的接口
/* 这个几个是通用的直接在io层面使用 */
static inline int gpio_get_value(unsigned int gpio)
{
return __gpio_get_value(gpio);
}
static inline void gpio_set_value(unsigned int gpio, int value)
{
__gpio_set_value(gpio, value);
}
static inline int gpio_cansleep(unsigned int gpio)
{
return __gpio_cansleep(gpio);
}
static inline int gpio_to_irq(unsigned int gpio)
{
return __gpio_to_irq(gpio);
}
static inline int irq_to_gpio(unsigned int irq)
{
return -EINVAL;
}
/* CONFIG_GPIOLIB: bindings for managed devices that want to request gpios */
struct device;
/* 下面几个是在具体的设备中使用的,里面也调用上面的了,但多了一步是在卸载设备时自动释放申请的io */
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,
unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);
举例分析:
1.gpio_set_value
static inline void gpio_set_value(unsigned int gpio, int value)
{
__gpio_set_value(gpio, value);
}
static inline void __gpio_set_value(unsigned gpio, int value)
{
return gpiod_set_raw_value(gpio_to_desc(gpio), value);
}
void gpiod_set_raw_value(struct gpio_desc *desc, int value)
{
if (!desc)
return;
/* Should be using gpiod_set_value_cansleep() */
WARN_ON(desc->chip->can_sleep);
_gpiod_set_raw_value(desc, value);
}
static void _gpiod_set_raw_value(struct gpio_desc *desc, bool value)
{
struct gpio_chip *chip;
chip = desc->chip;
trace_gpio_value(desc_to_gpio(desc), 0, value);
if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
_gpio_set_open_drain_value(desc, value);
else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
_gpio_set_open_source_value(desc, value);
else
chip->set(chip, gpio_chip_hwgpio(desc), value); /* 最终调用这里(具体硬件厂商实现) */
}
2.devm_gpio_request
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label)
{
unsigned *dr;
int rc;
/* 申请空间等,并绑定好注销函数,将来卸载设备时会调用这个,在卸载设备的函数中释放申请的io */
dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL);
if (!dr)
return -ENOMEM;
rc = gpio_request(gpio, label); /* 申请io */
if (rc) {
devres_free(dr);
return rc;
}
*dr = gpio;
devres_add(dev, dr); /* 注册资源 */
return 0;
}
3.厂商的接口和内核的绑定(三星平台为例)
/* TODO: cleanup soc_is_* */
static __init int samsung_gpiolib_init(void)
{
struct samsung_gpio_chip *chip;
int i, nr_chips;
int group = 0;
/*
* Currently there are two drivers that can provide GPIO support for
* Samsung SoCs. For device tree enabled platforms, the new
* pinctrl-samsung driver is used, providing both GPIO and pin control
* interfaces. For legacy (non-DT) platforms this driver is used.
*/
if (of_have_populated_dt())
return -ENODEV;
/* 平台的通用配置 */
samsung_gpiolib_set_cfg(samsung_gpio_cfgs, ARRAY_SIZE(samsung_gpio_cfgs));
if (soc_is_s3c24xx()) {
......
} else if (soc_is_s3c64xx()) {
......
} else if (soc_is_s5p6440()) {
......
} else if (soc_is_s5p6450()) {
......
} else if (soc_is_s5pc100()) {
......
} else if (soc_is_s5pv210()) { /* 我们只关注这里 */
group = 0;
chip = s5pv210_gpios_4bit; /* gpio资源表,见下分析 */
nr_chips = ARRAY_SIZE(s5pv210_gpios_4bit);
/* 填充表中通用的参数 */
for (i = 0; i < nr_chips; i++, chip++) {
if (!chip->config) {
chip->config = &samsung_gpio_cfgs[3];
chip->group = group++;
}
}
/* 下面函数很重要,即绑定好厂商的和内核通用的 */
samsung_gpiolib_add_4bit_chips(s5pv210_gpios_4bit, nr_chips, S5P_VA_GPIO);
#if defined(CONFIG_CPU_S5PV210) && defined(CONFIG_S5P_GPIO_INT)
s5p_register_gpioint_bank(IRQ_GPIOINT, 0, S5P_GPIOINT_GROUP_MAXNR);
#endif
} else {
WARN(1, "Unknown SoC in gpio-samsung, no GPIOs added\n");
return -ENODEV;
}
return 0;
}
core_initcall(samsung_gpiolib_init); /* core_initcall是最早被调用的,回忆一下module_init */
struct samsung_gpio_chip {
struct gpio_chip chip; /* 内核的通用的 */
struct samsung_gpio_cfg *config; /* 其它内三星自己在内核基础上封装的 */
struct samsung_gpio_pm *pm;
void __iomem *base;
int irq_base;
int group;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[4];
#endif
u32 bitmap_gpio_int;
};
/* 使用了我们前面的所有定义的GPIO组,等组成的一个包含所有gpio的表,申请时有没有这个gpio存在,查这个表就知道了 */
static struct samsung_gpio_chip s5pv210_gpios_4bit[] = {
#ifdef CONFIG_CPU_S5PV210
{
.chip = {
.base = S5PV210_GPA0(0),
.ngpio = S5PV210_GPIO_A0_NR,
.label = "GPA0",
},
}, {
.chip = {
.base = S5PV210_GPA1(0),
.ngpio = S5PV210_GPIO_A1_NR,
.label = "GPA1",
},
.................
}, {
.base = (S5P_VA_GPIO + 0xC60),
.irq_base = IRQ_EINT(24),
.chip = {
.base = S5PV210_GPH3(0),
.ngpio = S5PV210_GPIO_H3_NR,
.label = "GPH3",
.to_irq = samsung_gpiolib_to_irq,
},
},
#endif
};
samsung_gpiolib_add_4bit_chips(s5pv210_gpios_4bit, nr_chips, S5P_VA_GPIO);
参数1:表地址
参数2:表中元素个数
参数3:GPIO寄存器在内核空间(3~4G)的虚拟地址的基地址
static void __init samsung_gpiolib_add_4bit_chips(struct samsung_gpio_chip *chip,
int nr_chips, void __iomem *base)
{
int i;
for (i = 0 ; i < nr_chips; i++, chip++) {
chip->chip.direction_input = samsung_gpiolib_4bit_input; /* 绑定通用的操作接口,4bit是指配置寄存器配置各种模式是用4bit */
chip->chip.direction_output = samsung_gpiolib_4bit_output; /* 绑定通用的操作接口 */
if (!chip->config)
chip->config = &samsung_gpio_cfgs[2];
if (!chip->pm)
chip->pm = __gpio_pm(&samsung_gpio_pm_4bit);
if ((base != NULL) && (chip->base == NULL))
chip->base = base + ((i) * 0x20); /* 每接口占0x20个字节的寄存器,这里直接做好每组寄存器在内核空间的映射地址,使用时会很方便 */
chip->bitmap_gpio_int = 0;
samsung_gpiolib_add(chip); /* 继续添加操作接口 */
}
}
static void __init samsung_gpiolib_add(struct samsung_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
int ret;
BUG_ON(!chip->base);
BUG_ON(!gc->label);
BUG_ON(!gc->ngpio);
spin_lock_init(&chip->lock);
if (!gc->direction_input) /* 上面4bit的已经添加了,这里就不会添加2bit的 */
gc->direction_input = samsung_gpiolib_2bit_input;
if (!gc->direction_output)
gc->direction_output = samsung_gpiolib_2bit_output;
if (!gc->set)
gc->set = samsung_gpiolib_set; /* 设置高低电平 */
if (!gc->get)
gc->get = samsung_gpiolib_get; /* 得到当前电平 */
#ifdef CONFIG_PM
if (chip->pm != NULL) {
if (!chip->pm->save || !chip->pm->resume)
pr_err("gpio: %s has missing PM functions\n",
gc->label);
} else
pr_err("gpio: %s has no PM function\n", gc->label);
#endif
/* gpiochip_add() prints own failure message on error. */
ret = gpiochip_add(gc); /* 把该组加入到链表中 */
if (ret >= 0)
s3c_gpiolib_track(chip);
}
4.前面的函数绑定什么的都很简答,唯一要说明的是,上面的添加函数的那个参数S5P_VA_GPIO,可以看到已经是VA即虚拟地址了
arch/arm/plat-samsung/include/plat/map-s5p.h
#define S3C_ADDR_BASE 0xF6000000
#ifndef __ASSEMBLY__
#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#endif
#define S3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */
#define S3C_VA_SYS S3C_ADDR(0x00100000) /* system control */
#define S3C_VA_MEM S3C_ADDR(0x00200000) /* memory control */
.......
#define S5P_VA_GPIO S3C_ADDR(0x02200000)
可以看到我们的GPIO寄存器的虚拟地址被映射到了0xF8200000这个地址了,对比下面很明显也不是物理地址,所以肯定是虚拟地址
5.关于资源的静态映射
arch/arm/mach-s5pv210/common.c
/* Initial IO mappings,所有的寄存器资源va,pa所在页号等详细参数 */
static struct map_desc s5pv210_iodesc[] __initdata = {
{
.virtual = (unsigned long)S5P_VA_CHIPID,
.pfn = __phys_to_pfn(S5PV210_PA_CHIPID),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_SYS,
.pfn = __phys_to_pfn(S5PV210_PA_SYSCON),
.length = SZ_64K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_TIMER,
.pfn = __phys_to_pfn(S5PV210_PA_TIMER),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_WATCHDOG,
.pfn = __phys_to_pfn(S5PV210_PA_WATCHDOG),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_SROMC,
.pfn = __phys_to_pfn(S5PV210_PA_SROMC),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_SYSTIMER,
.pfn = __phys_to_pfn(S5PV210_PA_SYSTIMER),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_GPIO,
.pfn = __phys_to_pfn(S5PV210_PA_GPIO),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)VA_VIC0,
.pfn = __phys_to_pfn(S5PV210_PA_VIC0),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)VA_VIC1,
.pfn = __phys_to_pfn(S5PV210_PA_VIC1),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)VA_VIC2,
.pfn = __phys_to_pfn(S5PV210_PA_VIC2),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)VA_VIC3,
.pfn = __phys_to_pfn(S5PV210_PA_VIC3),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_UART,
.pfn = __phys_to_pfn(S3C_PA_UART),
.length = SZ_512K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_DMC0,
.pfn = __phys_to_pfn(S5PV210_PA_DMC0),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_DMC1,
.pfn = __phys_to_pfn(S5PV210_PA_DMC1),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_USB_HSPHY,
.pfn =__phys_to_pfn(S5PV210_PA_HSPHY),
.length = SZ_4K,
.type = MT_DEVICE,
}
};
/*
* s5pv210_map_io
*
* register the standard cpu IO areas
*/
void __init s5pv210_init_io(struct map_desc *mach_desc, int size)
{
/* initialize the io descriptors we need for initialization */
iotable_init(s5pv210_iodesc, ARRAY_SIZE(s5pv210_iodesc)); /* 初始化,即注册上面表的内容 */
if (mach_desc)
iotable_init(mach_desc, size);
/* detect cpu id and rev. */
s5p_init_cpu(S5P_VA_CHIPID);
s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));
samsung_pwm_set_platdata(&s5pv210_pwm_variant);
}
这个函数在mmu.c中,肯定和虚拟内存映射有关。这里就不展开说了,后面有时间开一个专题说。
主要做两件事:1.创建页表 2.利用我们传进来的物理地址所在页号,虚拟 地址,映射类型等参数填充上面创建的页表项
说明:
1.动态映射和静态映射不冲突,可以都有(默认启动阶段已经静态映射完了,我们上面程序既用了系统提供的gpiolib的静态,也自己动态ioremap映射了两个寄存器)
2.四级页表中 静态映射所有寄存器会占用一部分(先启动,会占用靠前的部分),其映射的虚拟地址在某个内核空间的地址(我们的#define S3C_ADDR_BASE 0xF6000000,到#define S5P_VA_GIC_DIST S3C_ADDR(0x02820000) + 偏移),而后面驱动里面动态映射中页的页表会在静态映射的后面,同时它的虚拟地址也会靠后。
3.在一个进程中,同一个(一组)寄存器可以多次ioremap,每次ioremap,都会为其建立新的页表,即也会有不同的虚拟地址。但这些虚拟地址都是映射的同一片物理地址,所以无论操纵那一个ioremap返回的虚拟地址,最终都操作能够的是那块物理地址。
上面程序的不完美的地方:
1.错误处理很不完美,甚至可以说没有错误处理。
2.程序动态映射和静态映射乱做一团。
下一节改进:
1.错误处理
2.全部采用gpiolib提供的静态映射接口