从零开始之驱动发开、linux驱动(六、字符驱动之多个led驱动)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82429837

上一节我们写出了简单的可以操控硬件的单个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提供的静态映射接口

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82429837
今日推荐