关于pinctrl驱动的一些实践与理解

CPU:F1C100S
目标系统:linux5.2

suniv-f1c100s-licheepi-nano 设备树有这样一段:

	pio: pinctrl@1c20800 {
			compatible = "allwinner,suniv-f1c100s-pinctrl";
			reg = <0x01c20800 0x400>;
			interrupts = <38>, <39>, <40>;
			clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
			clock-names = "apb", "hosc", "losc";
			gpio-controller;
			interrupt-controller;
			#interrupt-cells = <3>;
			#gpio-cells = <3>;

			uart0_pe_pins: uart0-pe-pins {
				pins = "PE0", "PE1";
				function = "uart0";
			};
			
			lcd_rgb666_pins: lcd-rgb666-pins {
				pins = "PD0", "PD1", "PD2", "PD3", "PD4",
				       "PD5", "PD6", "PD7", "PD8", "PD9",
				       "PD10", "PD11", "PD12", "PD13", "PD14",
				       "PD15", "PD16", "PD17", "PD18", "PD19",
				       "PD20", "PD21";
				function = "lcd";
			};
			
			mmc0_pins: mmc0-pins {
				pins = "PF0", "PF1", "PF2", "PF3", "PF4", "PF5";
				function = "mmc0";
			};
		};

首先我给自己提出问题:

1、pinctrl资源怎么和pinctrl驱动匹配的(linux内核是怎么做的?)
2、reg = <0x01c20800 0x400>; gpio的物理地址在内核哪里被映射成虚拟地址的?
3、每次启动映射的虚拟地址是固定的? 【注:在内核代码不变的情况下,包括驱动】
4、probe函数在获取设备相关信息(例如映射后的GPIO虚拟基地址)后怎么保存,供以后调用的?
5、既然pinctrl驱动的工作已经做完了,我们怎么去使用GPIO(gpio的操作)?

下面解决问题:

1、pinctrl资源怎么和pinctrl驱动匹配的(linux内核是怎么做的?)

首先我找到了pinctrl驱动程序所在位置(linux-nano-5.2-tf\drivers\pinctrl\sunxi\pinctrl-suniv-f1c100s.c)

pinctrl-suniv-f1c100s.c分析

.......前面代码省略,看最下面
static const struct of_device_id suniv_f1c100s_pinctrl_match[] = {
	{ .compatible = "allwinner,suniv-f1c100s-pinctrl", },  //去设备设备树中匹配"allwinner,suniv-f1c100s-pinctrl"
	{}
};

static struct platform_driver suniv_f1c100s_pinctrl_driver = {
	.probe	= suniv_pinctrl_probe,
	.driver	= {
		.name		= "suniv-f1c100s-pinctrl",
		.of_match_table	= suniv_f1c100s_pinctrl_match,  //匹配设备树中的pinctrl
	},
};
builtin_platform_driver(suniv_f1c100s_pinctrl_driver); //在这里注册了suniv_f1c100s_pinctrl_driver这个驱动

最终匹配到了pinctrl资源到驱动中。

2、reg = <0x01c20800 0x400>; gpio的物理地址在内核哪里被映射成虚拟地址的?

估计应该是驱动程序进行映射的。
好了看看驱动程序注册后要执行的代码:

.probe	= suniv_pinctrl_probe
static int suniv_pinctrl_probe(struct platform_device *pdev)
{
	return sunxi_pinctrl_init(pdev,
				  &suniv_f1c100s_pinctrl_data);
}

sunxi_pinctrl_init–>调用了sunxi_pinctrl_init_with_variant如下:

int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,
				    const struct sunxi_pinctrl_desc *desc,
				    unsigned long variant)
{
	struct device_node *node = pdev->dev.of_node;
	struct pinctrl_desc *pctrl_desc;
	struct pinctrl_pin_desc *pins;
	struct sunxi_pinctrl *pctl;
	struct pinmux_ops *pmxops;
	struct resource *res;
	int i, ret, last_pin, pin_idx;
	struct clk *clk;

	pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
	if (!pctl)
		return -ENOMEM;
	platform_set_drvdata(pdev, pctl);

	raw_spin_lock_init(&pctl->lock);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	pctl->membase = devm_ioremap_resource(&pdev->dev, res); //物理地址映射
	.....后面代码省略

pctl->membase = devm_ioremap_resource(&pdev->dev, res); 就是这句把物理地址映射成虚拟地址的。
我们可以看看devm_ioremap_resource函数。

void __iomem *devm_ioremap_resource(struct device *dev,
				    const struct resource *res)
{
	resource_size_t size;
	void __iomem *dest_ptr;

	BUG_ON(!dev);

	if (!res || resource_type(res) != IORESOURCE_MEM) {
		dev_err(dev, "invalid resource\n");
		return IOMEM_ERR_PTR(-EINVAL);
	}

	size = resource_size(res);

	if (!devm_request_mem_region(dev, res->start, size, dev_name(dev))) {
		dev_err(dev, "can't request region for resource %pR\n", res);
		return IOMEM_ERR_PTR(-EBUSY);
	}

	dest_ptr = devm_ioremap(dev, res->start, size);
	if (!dest_ptr) {
		dev_err(dev, "ioremap failed for resource %pR\n", res);
		devm_release_mem_region(dev, res->start, size);
		dest_ptr = IOMEM_ERR_PTR(-ENOMEM);
	}

	return dest_ptr;
}

其中2个比较重点的函数devm_request_mem_region、devm_ioremap ,先申请后映射。最后返回dest_ptr。
dest_ptr:映射后的虚拟地址

3、每次启动映射的虚拟地址是固定的?
下面使用printk打印映射后的虚拟地址。

int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,
				    const struct sunxi_pinctrl_desc *desc,
				    unsigned long variant)
{
	struct device_node *node = pdev->dev.of_node;
	struct pinctrl_desc *pctrl_desc;
	struct pinctrl_pin_desc *pins;
	struct sunxi_pinctrl *pctl;
	struct pinmux_ops *pmxops;
	struct resource *res;
	int i, ret, last_pin, pin_idx;
	unsigned long test_vmadr ;  //++++++++++++添加一个长整形变量
	struct clk *clk;

	pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
	if (!pctl)
		return -ENOMEM;
	platform_set_drvdata(pdev, pctl);

	raw_spin_lock_init(&pctl->lock);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	pctl->membase = devm_ioremap_resource(&pdev->dev, res);
	/*

	*/
	test_vmadr =*(unsigned long *)pctl->membase;//++++++++++++获取该虚拟地址的值
	printk(KERN_EMERG "PinCtrl_Driver(-- %lx --)\n",test_vmadr );//++++++++++++打印该虚拟地址的值
	test_vmadr =(unsigned long *)pctl->membase;//++++++++++++获取该虚拟地址
	printk(KERN_EMERG "PinCtrl_Driver(-- %lx --)\n",test_vmadr );//++++++++++++打印该虚拟地址

重启开发板,多重启几次观察终端始终输出如下值:

PinCtrl_Driver(-- 7777 --)
PinCtrl_Driver(-- c284f800  --)

结论: 在驱动中每次物理地址映射后的虚拟地址和虚拟地址的值固定(在内核代码不变的情况下)。

4、probe函数在获取设备相关信息(例如映射后的GPIO虚拟基地址)后怎么保存,供以后调用的?
通常,会在驱动的probe函数中获取device的相关信息,然后使用 platform_set_drvdata进行保存。在其他函数中要用到时(比如remove),通过platform_get_drvdata来获取。

int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,
				    const struct sunxi_pinctrl_desc *desc,
				    unsigned long variant)
{
	struct device_node *node = pdev->dev.of_node;
	struct pinctrl_desc *pctrl_desc;
	struct pinctrl_pin_desc *pins;
	struct sunxi_pinctrl *pctl;
	struct pinmux_ops *pmxops;
	struct resource *res;
	int i, ret, last_pin, pin_idx;
	struct clk *clk;

	pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);  // 为pctl 结构体申请内存(保存pinctrl 设备信息)
	if (!pctl)
		return -ENOMEM;
	platform_set_drvdata(pdev, pctl); //保存pctl结构体在内存中的指针,供以后调用 
	
	....省略代码

5、既然pinctrl驱动的工作已经做完了,我们怎么去使用GPIO(gpio的操作)?

引用内容来自: http://blog.csdn.net/tommy_wxie/article/details/9427047

Linux内核中gpio是最简单,最常用的资源(和 interrupt ,dma,timer一样)驱动程序,应用程序都能够通过相应的接口使用gpio,gpio使用0~MAX_INT之间的整数标识,不能使用负数,gpio与硬件体系密切相关的,不过linux有一个框架处理gpio,能够使用统一的接口来操作gpio.在讲gpio核心(gpiolib.c)之前先来看看gpio是怎么使用的。

用户空间访问gpio,即通过sysfs接口访问gpio,下面是/sys/class/gpio目录下的三种文件
--export/unexport文件
–gpioN指代具体的gpio引脚
–gpio_chipN指代gpio控制器

必须知道以上接口没有标准device文件和它们的链接。

用户空间操作GPIO

通过sysfs在用户空间使用GPIO,首先将GPIO使用export 导出到用户空间之中(方法如下)
cd /sys/class/gpio/ ;先访问/sys/class/gpio目录
echo 132 > export ;执行会创建gpio132这个目录
cd /sys/class/gpio/gpio132

设置为输入,并读值:
echo “in” > direction
cat value

设置为输出,并设置值:
cd /sys/class/gpio/gpio132
echo “out” > direction
echo 1 > value 或 echo 0 > value

内核空间操作GPIO

二 内核中gpio的使用

     1 测试gpio端口是否合法 int gpio_is_valid(int number); 
     
     2 申请某个gpio端口当然在申请之前需要显示的配置该gpio端口的pinmux
        int gpio_request(unsigned gpio, const char *label)
        
     3 标记gpio的使用方向包括输入还是输出
       /*成功返回零失败返回负的错误值*/ 
       int gpio_direction_input(unsigned gpio); 
       int gpio_direction_output(unsigned gpio, int value); 
       
     4 获得gpio引脚的值和设置gpio引脚的值(对于输出)
        int gpio_get_value(unsigned gpio);
        void gpio_set_value(unsigned gpio, int value); 
        
     5 gpio当作中断口使用
        int gpio_to_irq(unsigned gpio); 
        返回的值即中断编号可以传给request_irq()free_irq()
        内核通过调用该函数将gpio端口转换为中断,在用户空间也有类似方法
        
     6 导出gpio端口到用户空间
       int gpio_export(unsigned gpio, bool direction_may_change); 
        内核可以对已经被gpio_request()申请的gpio端口的导出进行明确的管理,
        参数direction_may_change表示用户程序是否允许修改gpio的方向,假如可以
        则参数direction_may_change为真
        /* 撤销GPIO的导出 */ 
        void gpio_unexport(); 

 

你可能感兴趣的:(❏【F1C100S学习】,❏【linux内核分析】)