以leds-gpio.c为例,浅谈device-tree、pinctrl和GPIO子系统-程序员宅基地

技术标签: linux  嵌入式Linux  

一、前言

leds-gpio.c是内核源码drivers/leds/leds-gpio.c目录下的一个led驱动程序的例程(说明书位于Documentation/devicetree/bindings/leds/leds-gpio.txt),它使用了内核提供的led驱动框架(说明书位于Documentation/leds/leds-class.txt)。本文不具体分析led驱动框架,而是借由分析leds-gpio.ko模块的源文件leds-gpio.c来初步介绍一下device-tree、pinctrl和GPIO子系统,我觉得这比一上来就长篇大论系统地分析这三个子系统要好。这里不贴leds-gpio.c源文件的完整内容,建议大家直接去内核源码里找出这个源文件进行阅读,我这里使用BeagleBone AI这块板子为例进行介绍。本文引用的是4.14版本的内核源码。

二、设备树(Device Tree)

  1. 设备树是描述硬件资源的文件,最初是Linux内核先引入的设备树机制,后来u-boot也引入了设备树机制,这里只讨论Linux内核使用的设备树。内核源码中的Documentation/devicetree/usage-model.txt是设备树的说明书,也可以阅读Device Tree Usage。设备树的源文件扩展名为.dts和dtsi(也支持.h的头文件,内核源码的include/dt-bindings目录提供了一些标准的设备树.h头文件,主要是一些宏定义),二进制文件为.dtb和dtbo。内核中的platform虚拟总线会根据设备树自动展开所有设备,可以在/sys/devices/platform目录下查看相关设备文件,在驱动程序中可以调用platform提供的接口匹配并使用这些设备。

  2. 设备树中与leds-gpio.c相关的部分(ocp是on chip peripherals的缩写):

    /dts-v1/;
    ...
    / {
          
    ...
    	/*
    	 * XXX: Use a flat representation of the SOC interconnect.
    	 * The real OMAP interconnect network is quite complex.
    	 * Since it will not bring real advantage to represent that in DT for
    	 * the moment, just use a fake OCP bus entry to represent the whole bus
    	 * hierarchy.
    	 */
    	ocp: ocp {
          
    		compatible = "ti,dra7-l3-noc", "simple-bus";
    		#address-cells = <1>;
    		#size-cells = <1>;
    		ranges = <0x0 0x0 0x0 0xc0000000>;
    		ti,hwmods = "l3_main_1", "l3_main_2";
    		reg = <0x0 0x44000000 0x0 0x1000000>,
    		      <0x0 0x45000000 0x0 0x1000>;
    		interrupts-extended = <&crossbar_mpu GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
    				      <&wakeupgen GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;
    
    		l4_cfg: l4@4a000000 {
          
    			compatible = "ti,dra7-l4-cfg", "simple-bus";
    			#address-cells = <1>;
    			#size-cells = <1>;
    			ranges = <0 0x4a000000 0x22c000>;
    
    			scm: scm@2000 {
          
    				compatible = "ti,dra7-scm-core", "simple-bus";
    				reg = <0x2000 0x2000>;
    				#address-cells = <1>;
    				#size-cells = <1>;
    				ranges = <0 0x2000 0x2000>;
    ...
    				dra7_pmx_core: pinmux@1400 {
          
    					compatible = "ti,dra7-padconf",
    						     "pinctrl-single";
    					reg = <0x1400 0x0468>;
    					#address-cells = <1>;
    					#size-cells = <0>;
    					#pinctrl-cells = <1>;
    					#interrupt-cells = <1>;
    					interrupt-controller;
    					pinctrl-single,register-width = <32>;
    					pinctrl-single,function-mask = <0x3fffffff>;
    ...
                        led_pins_default: led_pins_default {
          
                            pinctrl-single,pins = <
                                DRA7XX_CORE_IOPAD(0x3528, PIN_OUTPUT | MUX_MODE14) /* AF6: vin1a_d13.gpio3_17  - USR0 */
                                DRA7XX_CORE_IOPAD(0x36c0, PIN_OUTPUT | MUX_MODE14) /* J11: mcasp1_axr3.gpio5_5 - USR1 */
                                DRA7XX_CORE_IOPAD(0x3520, PIN_OUTPUT | MUX_MODE14) /* AG5: vin1a_d12.gpio3_15  - USR2 */
                                DRA7XX_CORE_IOPAD(0x351c, PIN_OUTPUT | MUX_MODE14) /* AG3: vin1a_d10.gpio3_14  - USR3 */
                                DRA7XX_CORE_IOPAD(0x3500, PIN_OUTPUT | MUX_MODE14) /* AH6: vin1a_d3.gpio3_7    - USR4 */
                            >;
                        };
    ...
                    };
    ...
                };
    ...
            };
    ...
    		gpio3: gpio@48057000 {
          
    			compatible = "ti,omap4-gpio";
    			reg = <0x48057000 0x200>;
    			interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
    			ti,hwmods = "gpio3";
    			gpio-controller;
    			#gpio-cells = <2>;
    			interrupt-controller;
    			#interrupt-cells = <2>;
    		};
    ...
    		gpio5: gpio@4805b000 {
          
    			compatible = "ti,omap4-gpio";
    			reg = <0x4805b000 0x200>;
    			interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
    			ti,hwmods = "gpio5";
    			gpio-controller;
    			#gpio-cells = <2>;
    			interrupt-controller;
    			#interrupt-cells = <2>;
    		};
    ...
        };
    ...
    	leds {
          
    		compatible = "gpio-leds";
    		pinctrl-names = "default";
    		pinctrl-0 = <&led_pins_default>;
    
    		led0 {
          
    			label = "beaglebone:green:usr0";
    			gpios = <&gpio3 17 GPIO_ACTIVE_HIGH>;
    			linux,default-trigger = "heartbeat";
    			default-state = "off";
    		};
    
    		led1 {
          
    			label = "beaglebone:green:usr1";
    			gpios = <&gpio5 5 GPIO_ACTIVE_HIGH>;
    			linux,default-trigger = "mmc0";
    			default-state = "off";
    		};
    
    		led2 {
          
    			label = "beaglebone:green:usr2";
    			gpios = <&gpio3 15 GPIO_ACTIVE_HIGH>;
    			linux,default-trigger = "cpu";
    			default-state = "off";
    		};
    
    		led3 {
          
    			label = "beaglebone:green:usr3";
    			gpios = <&gpio3 14 GPIO_ACTIVE_HIGH>;
    			linux,default-trigger = "mmc1";
    			default-state = "off";
    		};
    
    		led4 {
          
    			label = "beaglebone:green:usr4";
    			gpios = <&gpio3 7 GPIO_ACTIVE_HIGH>;
    			linux,default-trigger = "netdev";
    			default-state = "off";
    		};
    	};
    ...
    };
    
  3. 如果查看的是反编译过的设备树源文件,那么被指向的节点下会多一个phandle属性,每个phandle属性的属性值在整个文件内都是唯一的,唯一标志了这个节点,以便于其它节点能够使用phandle的值引用这个节点;根节点下会多一个__symbols__节点,__symbols__节点记录了各个节点的别名及别名的值(节点路径),后续其它设备树文件对该节点的增改可以直接使用别名。

  4. 编译/反编译设备树:

    使用dtc命令编译/反编译设备树:

    dtc -I dts -O dtb -o xxx.dtb arch/arm/boot/dts/xxx.dts // 编译 dts 为 dtb
    dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb // 反编译 dtb 为 dts
    

    使用fdtdump命令反编译设备树:

    fdtdump devicetree.dtb > devicetree.dts
    
  5. 可以使用命令查看板子上正在运行的设备树:

    debian@beaglebone:~$ ls -l /proc/device-tree
    lrwxrwxrwx 1 root root 29 Mar 23 16:38 /proc/device-tree -> /sys/firmware/devicetree/base
    debian@beaglebone:~$ ls /sys/firmware/devicetree/base
    '#address-cells'   encoder@0                       name
    '#size-cells'      extcon_usb1                     ocp
     __symbols__       fixedregulator-vdd_5v           opp-table
     aliases           fixedregulator-vtt              pmu
     brcmf_pwrseq      gpioregulator-vdd_adc           reserved-memory
     cape_pins         gpu-subsystem                   serial-number
     chosen            interrupt-controller@48211000   soc
     cmem              interrupt-controller@48281000   thermal-zones
     compatible        interrupt-parent                timer
     connector@0       leds                            unused_pins
     cpus              memory@0
     emmc_pwrseq       model
    debian@beaglebone:~$ ls /sys/firmware/devicetree/base/leds/
    compatible  led0  led1  led2  led3  led4  name  pinctrl-0  pinctrl-names
    debian@beaglebone:~$ cat /sys/firmware/devicetree/base/leds/name ; echo
    leds
    debian@beaglebone:~$ cat /sys/firmware/devicetree/base/leds/compatible ; echo
    gpio-leds
    
  6. platform虚拟总线通过of_match_table中的compatible属性的值来找到与leds-gpio.c这个platform_driver相匹配的platform_device及其设备树节点,leds-gpio.c中相关部分如下:

    static const struct of_device_id of_gpio_leds_match[] = {
          
    	{
           .compatible = "gpio-leds", },
    	{
          },
    };
    
    MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
    ...
    static struct platform_driver gpio_led_driver = {
          
    	.probe		= gpio_led_probe,
    	.shutdown	= gpio_led_shutdown,
    	.driver		= {
          
    		.name	= "leds-gpio",
    		.of_match_table = of_gpio_leds_match,
    	},
    };
    
    module_platform_driver(gpio_led_driver);
    
  7. 在leds-gpio.c的gpio_leds_create函数内会调用如下函数来获取设备树节点内的信息:

    static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
    {
          
    ...
    	device_for_each_child_node(dev, child) {
          
    ...
    		ret = fwnode_property_read_string(child, "label", &led.name);
    ...
    		led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
    							     GPIOD_ASIS,
    							     led.name);
    ...
    		fwnode_property_read_string(child, "linux,default-trigger",
    					    &led.default_trigger);
    
    		if (!fwnode_property_read_string(child, "default-state",
    						 &state)) {
          
    ...
    		}
    
    		if (fwnode_property_present(child, "retain-state-suspended"))
    			led.retain_state_suspended = 1;
    		if (fwnode_property_present(child, "retain-state-shutdown"))
    			led.retain_state_shutdown = 1;
    		if (fwnode_property_present(child, "panic-indicator"))
    			led.panic_indicator = 1;
    ...
    }
    

三、设备树插件(Device Tree Overlay)

使用设备树插件的优点是可以比较方便地更改设备树(不需要重启系统),缺点是如果不更改u-boot配置文件的话,每次上电都需要手动添加设备树插件。设备树插件的说明书位于内核源码目录的Documentation/devicetree/overlay-notes.txt,下面给一个例子并且作简单说明。

i2c4-mpu6050.dts

/dts-v1/;
/plugin/;

/ {
    
	fragment@0 {
    
		target = <&i2c4>;
		__overlay__ {
    
			status = "okay";
            clock-frequency = <400000>;
			i2c_mpu6050@68 {
    
				compatible = "charming,i2c_mpu6050";
				reg = <0x68>;
				status = "okay";
			};
		};
	};
};

上面这个文件为i2c4节点(i2c4是别名)添加了一个i2c_mpu6050@68子节点,并且更改了status属性的值,还添加了一个clock-frequency属性。下面再从内核源码说明书内摘抄一段进一步说明:

Overlay DTS Format
------------------

The DTS of an overlay should have the following format:

{
	/* ignored properties by the overlay */

	fragment@0 {	/* first child node */

		target=<phandle>;	/* phandle target of the overlay */
	or
		target-path="/path";	/* target path of the overlay */

		__overlay__ {
			property-a;	/* add property-a to the target */
			node-a {	/* add to an existing, or create a node-a */
				...
			};
		};
	}
	fragment@1 {	/* second child node */
		...
	};
	/* more fragments follow */
}

Using the non-phandle based target method allows one to use a base DT which does
not contain a __symbols__ node, i.e. it was not compiled with the -@ option.
The __symbols__ node is only required for the target=<phandle> method, since it
contains the information required to map from a phandle to a tree location.

使用如下命令编译设备树插件:

dtc -I dts -O dtb -o i2c4-mpu6050.dtbo i2c4-mpu6050.dts

在BeagleBone AI上使用如下命令添加设备树插件:

sudo mkdir /sys/kernel/config/device-tree/overlays/i2c4-mpu6050
sudo sh -c "cat i2c4-mpu6050.dtbo > /sys/kernel/config/device-tree/overlays/i2c4-mpu6050/dtbo"

# 移除设备树插件
sudo rmdir /sys/kernel/config/device-tree/overlays/i2c4-mpu6050

但是内核对pinctrl相关的设备树插件支持得不够好,建议通过直接替换板子根文件系统boot目录里的dtb文件并执行sync命令后断电重启开发板这种方式来更改pinctrl相关部分的设备树。比如要使上面这个设备树添加的mpu6050驱动能正常工作的话,需要在板子启动前更改好设备树中i2c4使用的管脚的pinmux,所以我觉得设备树插件的用处不大。也可以为内核增加一个名为bone-pinmux-helper的驱动程序来支持使用设备树插件来更改pinmux,但是目前这个驱动程序并没有被并入内核源码。这个驱动程序的probe函数如下:

static int bone_pinmux_helper_probe(struct platform_device *pdev)
{
    
	struct pinctrl *pinctrl;

	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);

	/* don't bother with anything */
	return PTR_RET(pinctrl);
}

四、pinctrl子系统

通常芯片内部的功能逻辑电路可以映射到多组管脚上,比如在BeagleBone AI使用的am5729芯片上i2c4_scl就可以映射到P7/ D14/ AB4/ F20这四个不同的芯片管脚上面,i2c4_sda也可以映射到T9/ B14/ C18/ W7这四个不同的芯片管脚上面,因此每个具有多个功能的管脚都有一个pinmux的配置寄存器,具体到BeagleBone AI使用的am5729芯片上面来说就是名为CTRL_CORE_PAD_xxx的一系列寄存器。pinmux是硬件上的一个概念,而pinctrl是Linux内核用于配置pinmux的一个子系统的名字。因为每家芯片厂、甚至同一家芯片厂的不同系列芯片在pinctrl的底层实现上都略有差异,通过在内核源码里搜索pinmux节点的compatible属性的属性值(名字不一定是pinmux,但是可以从节点之间的引用关系可以判断出来配置管脚复用的那个节点叫什么名字)可以找到具体厂商实现该型号芯片pinmux功能的驱动程序和文档(如am5729芯片的pinctrl驱动程序是drivers/pinctrl/pinctrl-single.c,文档是Documentation/devicetree/bindings/pinctrl/pinctrl-single.txt),然后进行阅读。值得注意的是内核在调用driver的probe函数之前会将对应device的pinctrl设置为"default"状态(在drivers/base/dd.c文件中的really_probe函数内调用pinctrl_bind_pins函数,该函数定义于drivers/base/pinctrl.c文件)。当然刚开始的时候我们并不需要仔细去了解pinctrl子系统具体是如何实现的,只需要参考官方评估板的设备树文件,然后会根据自己的需求在设备树中正确地配置pinctrl子系统就行了,设备树中pinctrl关键字的用法可以参考内核文档Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt。还是以leds-gpio.ko为例介绍设备树中pinctrl的配置:

...
				dra7_pmx_core: pinmux@1400 {
    
					compatible = "ti,dra7-padconf",
						     "pinctrl-single";
...
                    led_pins_default: led_pins_default {
    
                        pinctrl-single,pins = <
                            DRA7XX_CORE_IOPAD(0x3528, PIN_OUTPUT | MUX_MODE14) /* AF6: vin1a_d13.gpio3_17  - USR0 */
                            DRA7XX_CORE_IOPAD(0x36c0, PIN_OUTPUT | MUX_MODE14) /* J11: mcasp1_axr3.gpio5_5 - USR1 */
                            DRA7XX_CORE_IOPAD(0x3520, PIN_OUTPUT | MUX_MODE14) /* AG5: vin1a_d12.gpio3_15  - USR2 */
                            DRA7XX_CORE_IOPAD(0x351c, PIN_OUTPUT | MUX_MODE14) /* AG3: vin1a_d10.gpio3_14  - USR3 */
                            DRA7XX_CORE_IOPAD(0x3500, PIN_OUTPUT | MUX_MODE14) /* AH6: vin1a_d3.gpio3_7    - USR4 */
                        >;
                    };
...
	leds {
    
		compatible = "gpio-leds";
		pinctrl-names = "default";
		pinctrl-0 = <&led_pins_default>;
...

pinctrl-names属性的值是一个字符串数组,leds节点只定义了一个该节点使用的pinctrl的默认状态。pinctrl-0是指pinctrl-names中第1个状态名对应的pinctrl配置,pinctrl-0属性的值是一个指针数组,其每个指针都指向pinmux节点的一个子节点,pinctrl-0属性的值代表这个pinctrl状态要用到的一些pinmux配置(am5729的pinctrl子系统对应的设备树节点就叫pinmux@1400,leds节点pinctrl的默认状态用到的pinmux配置就是pinmux@1400节点内的led_pins_default节点定义的那些pinmux。pinctrl-single,pins属性是与芯片厂商和芯片型号相关的,DRA7XX_CORE_IOPAD是一个宏(定义于内核源码的include/dt-bindings/pinctrl/dra.h文件),它将0x3528减去0x3400得到0x0128,因为芯片AF6管脚对应的pinmux寄存器的地址是0x4A003528(l4@4a000000+scm@2000+pinmux@1400=0x4A003400)。PIN_OUTPUT | MUX_MODE14这两个宏也定义于dra.h文件,或运算后的值0x0001000e代表该pinmux寄存器的值。

include/linux/pinctrl/pinctrl-state.h文件对内核中标准的四种pinctrl状态作了说明:

/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Standard pin control state definitions
 */

/**
 * @PINCTRL_STATE_DEFAULT: the state the pinctrl handle shall be put
 *	into as default, usually this means the pins are up and ready to
 *	be used by the device driver. This state is commonly used by
 *	hogs to configure muxing and pins at boot, and also as a state
 *	to go into when returning from sleep and idle in
 *	.pm_runtime_resume() or ordinary .resume() for example.
 * @PINCTRL_STATE_INIT: normally the pinctrl will be set to "default"
 *	before the driver's probe() function is called.  There are some
 *	drivers where that is not appropriate becausing doing so would
 *	glitch the pins.  In those cases you can add an "init" pinctrl
 *	which is the state of the pins before drive probe.  After probe
 *	if the pins are still in "init" state they'll be moved to
 *	"default".
 * @PINCTRL_STATE_IDLE: the state the pinctrl handle shall be put into
 *	when the pins are idle. This is a state where the system is relaxed
 *	but not fully sleeping - some power may be on but clocks gated for
 *	example. Could typically be set from a pm_runtime_suspend() or
 *	pm_runtime_idle() operation.
 * @PINCTRL_STATE_SLEEP: the state the pinctrl handle shall be put into
 *	when the pins are sleeping. This is a state where the system is in
 *	its lowest sleep state. Could typically be set from an
 *	ordinary .suspend() function.
 */
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_INIT "init"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"

下面以设备树中的mmc节点为例进一步介绍:

&dra7_pmx_core {
    
	mmc1_pins_default: mmc1_pins_default {
    
		pinctrl-single,pins = <
			DRA7XX_CORE_IOPAD(0x3754, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_clk.clk */
			DRA7XX_CORE_IOPAD(0x3758, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_cmd.cmd */
			DRA7XX_CORE_IOPAD(0x375c, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat0.dat0 */
			DRA7XX_CORE_IOPAD(0x3760, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat1.dat1 */
			DRA7XX_CORE_IOPAD(0x3764, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat2.dat2 */
			DRA7XX_CORE_IOPAD(0x3768, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat3.dat3 */
		>;
	};
...
};	/* dra74x-mmc-iodelay.dtsi */

mmc1: mmc@4809c000 {
    
    compatible = "ti,dra7-sdhci";
...
};	/* dra7.dtsi */

&mmc1 {
    
	status = "okay";
...
	pinctrl-names = "default", "hs", "sdr12", "sdr25", "sdr50", "ddr50", "sdr104";
	pinctrl-0 = <&mmc1_pins_default &microsd_extra_pins_default>;
	pinctrl-1 = <&mmc1_pins_hs &microsd_extra_pins_default>;
	pinctrl-2 = <&mmc1_pins_sdr12 &microsd_extra_pins_default>;
	pinctrl-3 = <&mmc1_pins_sdr25 &microsd_extra_pins_default>;
	pinctrl-4 = <&mmc1_pins_sdr50 &microsd_extra_pins_default>;
	pinctrl-5 = <&mmc1_pins_ddr50 &mmc1_iodelay_ddr_rev20_conf &microsd_extra_pins_default>;
	pinctrl-6 = <&mmc1_pins_sdr104 &mmc1_iodelay_sdr104_rev20_conf &microsd_extra_pins_default>;
};	/* am5729-beagleboneai.dts */

在内核源码中搜索"ti,dra7-sdhci"可知am5729的mmc驱动源码是drivers/mmc/host/sdhci-omap.c文件,因为该驱动程序需要直接调用pinctrl相关的函数(比如切换pinctrl状态),所以可以看到在该文件的前几行包含了linux/pinctrl/consumer.h头文件,下面摘抄了一些sdhci-omap.c文件中与pinctrl相关的部分:

...
static void sdhci_omap_set_timing(struct sdhci_omap_host *omap_host, u8 timing)
{
    
...
	ret = pinctrl_select_state(omap_host->pinctrl, pinctrl_state);
...
}
...
static struct pinctrl_state
*sdhci_omap_iodelay_pinctrl_state(struct sdhci_omap_host *omap_host, char *mode,
				  u32 *caps, u32 capmask)
{
    
...
	if (version) {
    
		snprintf(str, 20, "%s-%s", mode, version);
		pinctrl_state = pinctrl_lookup_state(omap_host->pinctrl, str);
	}

	if (IS_ERR(pinctrl_state))
		pinctrl_state = pinctrl_lookup_state(omap_host->pinctrl, mode);
...
}
...
static int sdhci_omap_config_iodelay_pinctrl_state(struct sdhci_omap_host
						   *omap_host)
{
    
...
	omap_host->pinctrl = devm_pinctrl_get(omap_host->dev);
...
	state = pinctrl_lookup_state(omap_host->pinctrl, "default");
...
}
...
static const struct of_device_id omap_sdhci_match[] = {
    
	{
     .compatible = "ti,dra7-sdhci", .data = &dra7_data },
	{
     .compatible = "ti,k2g-sdhci", .data = &k2g_data },
	{
    },
};
MODULE_DEVICE_TABLE(of, omap_sdhci_match);
...
static struct platform_driver sdhci_omap_driver = {
    
	.probe = sdhci_omap_probe,
	.remove = sdhci_omap_remove,
	.driver = {
    
		   .name = "sdhci-omap",
		   .of_match_table = omap_sdhci_match,
		  },
};

module_platform_driver(sdhci_omap_driver);
...

五、GPIO子系统

上一节说了如何通过更改设备树的pinmux节点将芯片的某个管脚配置成GPIO的功能,那接下来就应该说说如何配置(更改设备树)、使用GPIO功能(调用GPIO子系统提供的函数)了。每个芯片厂商对GPIO子系统的最底层的实现也是略有区别的,比如TI的GPIO设备树节点的compatible属性的值就是"ti,omap4-gpio",大家可以去内核源码里搜索这个值从而获取GPIO子系统的最底层驱动源码或文档(如drivers/gpio/gpio-omap.c),内核源码中也有一份关于GPIO子系统的中文说明书Documentation/translations/zh_CN/gpio.txt。一般来说设备树的ocp节点(用于保存片内外设控制器的设备树节点)的内容我们只需要更改pinmux子节点的内容即可,故ocp节点内的GPIO节点我们是不需要更改的,可以直接引用官方给出的.dtsi设备树头文件内的定义。我们只需要在需要使用GPIO的设备树节点里添加名为gpios的属性就行了,比如led3节点里的gpios = <&gpio3 14 GPIO_ACTIVE_HIGH>;属性(使用gpio3_14,默认高电平,其中&gpio3是一个指向gpio节点的指针)。接下来就看看leds-gpio.c驱动程序里与GPIO子系统相关的部分吧:

...
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
...
static void gpio_led_set(struct led_classdev *led_cdev,
	enum led_brightness value)
{
    
...
		if (led_dat->can_sleep)
			gpiod_set_value_cansleep(led_dat->gpiod, level);
		else
			gpiod_set_value(led_dat->gpiod, level);
...
}
...
static int create_gpio_led(const struct gpio_led *template,
	struct gpio_led_data *led_dat, struct device *parent,
	struct device_node *np, gpio_blink_set_t blink_set)
{
    
...
		/* skip leds that aren't available */
		if (!gpio_is_valid(template->gpio)) {
    
...
		}
...
		ret = devm_gpio_request_one(parent, template->gpio, flags,
					    template->name);
...
		led_dat->gpiod = gpio_to_desc(template->gpio);
...
	led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
...
		state = gpiod_get_value_cansleep(led_dat->gpiod);
...
	ret = gpiod_direction_output(led_dat->gpiod, state);
}
...
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
    
...
		led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
							     GPIOD_ASIS,
							     led.name);
...
}
...

leds-gpio.c使用了GPIO子系统提供的如下函数:

/**
 * gpiod_set_value_cansleep() - assign a gpio's value
 * @desc: gpio whose value will be assigned
 * @value: value to assign
 *
 * Set the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
 * account
 *
 * This function is to be called from contexts that can sleep.
 */
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);	/* drivers/gpio/gpiolib.c */

/**
 * gpiod_set_value() - assign a gpio's value
 * @desc: gpio whose value will be assigned
 * @value: value to assign
 *
 * Set the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
 * account
 *
 * This function should be called from contexts where we cannot sleep, and will
 * complain if the GPIO chip functions potentially sleep.
 */
void gpiod_set_value(struct gpio_desc *desc, int value);	/* drivers/gpio/gpiolib.c */

/*
 * "valid" GPIO numbers are nonnegative and may be passed to
 * setup routines like gpio_request().  only some valid numbers
 * can successfully be requested and used.
 *
 * Invalid GPIO numbers are useful for indicating no-such-GPIO in
 * platform data and other tables.
 */
static inline bool gpio_is_valid(int number)
{
    
	return number >= 0 && number < ARCH_NR_GPIOS;
}	/* include/asm-generic/gpio.h */

/**
 *      devm_gpio_request_one - request a single GPIO with initial setup
 *      @dev:   device to request for
 *      @gpio:  the GPIO number
 *      @flags: GPIO configuration as specified by GPIOF_*
 *      @label: a literal description string of this GPIO
 */
int devm_gpio_request_one(struct device *dev, unsigned gpio,
                          unsigned long flags, const char *label);	/* drivers/gpio/devres.c */

/**
 * gpio_to_desc - Convert a GPIO number to its descriptor
 * @gpio: global GPIO number
 *
 * Returns:
 * The GPIO descriptor associated with the given GPIO, or %NULL if no GPIO
 * with the given number exists in the system.
 */
struct gpio_desc *gpio_to_desc(unsigned gpio);	/* drivers/gpio/gpiolib.c */

/**
 * gpiod_cansleep() - report whether gpio value access may sleep
 * @desc: gpio to check
 *
 */
int gpiod_cansleep(const struct gpio_desc *desc);	/* drivers/gpio/gpiolib.c */

/**
 * gpiod_get_value_cansleep() - return a gpio's value
 * @desc: gpio whose value will be returned
 *
 * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into
 * account, or negative errno on failure.
 *
 * This function is to be called from contexts that can sleep.
 */
int gpiod_get_value_cansleep(const struct gpio_desc *desc);	/* drivers/gpio/gpiolib.c */

/**
 * gpiod_direction_output - set the GPIO direction to output
 * @desc:	GPIO to set to output
 * @value:	initial output value of the GPIO
 *
 * Set the direction of the passed GPIO to output, such as gpiod_set_value() can
 * be called safely on it. The initial value of the output must be specified
 * as the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
 * account.
 *
 * Return 0 in case of success, else an error code.
 */
int gpiod_direction_output(struct gpio_desc *desc, int value);	/* drivers/gpio/gpiolib.c */

static inline
struct gpio_desc *devm_fwnode_get_gpiod_from_child(struct device *dev,
						   const char *con_id,
						   struct fwnode_handle *child,
						   enum gpiod_flags flags,
						   const char *label);	/* include/linux/gpio/consumer.h */

六、leds-gpio.ko的妙用

leds-gpio.c还可以用来只配置pinmux而不配置led灯,即使用或者关闭哪些芯片引脚。比如am5729-beagleboneai.dts设备树根节点下有如下节点:

/ {
    
...
				dra7_pmx_core: pinmux@1400 {
    
...
                    cape_pins_default {
    
                        pinctrl-single,pins = <0x0000039c 0x0000000e 0x000003a0 0x0000000e ... >;
                        phandle = <0x00000137>;
                    };
...
                };
...
    cape_pins {
    
        compatible = "gpio-leds";
        pinctrl-names = "default";
        pinctrl-0 = <0x00000137>;
        phandle = <0x00000252>;
    };
...
};

在开发板中可以看到platform虚拟总线根据设备树为系统自动生成了cape_pins设备(因为cape_pins节点有compatible属性),但没有在/sys/class/leds下生成cape_pins设备的链接(因为cape_pins没有子节点,所以leds-gpio.c不会调用devm_of_led_classdev_register函数,也不会执行其它操作,但是pinctrl相关的属性会被应用。因为cape_pins设备与leds-gpio.ko相匹配,所以系统会自动调用pinctrl_bind_pins函数应用cape_pins节点pinctrl的"default"状态):

debian@beaglebone:~$ ls /sys/devices/platform/
 44000000.ocp        emmc_pwrseq             omap_dma_system.0   snd-soc-dummy
'Fixed MDIO bus.0'   encoder@0               omapdrm.0           soc
 alarmtimer          extcon_usb1             opp-table           ti-cpufreq
 brcmf_pwrseq        fixedregulator-vdd_5v   pmu                 timer
 cape_pins           fixedregulator-vtt      power               uevent
 cmem                gpioregulator-vdd_adc   reg-dummy           unused_pins
 connector@0         gpu-subsystem           regulatory.0        vgem
 cpufreq-dt          leds                    serial8250
debian@beaglebone:~$ ls /sys/class/
ata_device     dma          iommu         ptp           spi_master
ata_link       drm          leds          pwm           thermal
ata_port       extcon       mbox          regulator     tpm
backlight      firmware     mdio_bus      remoteproc    tpmrm
bdi            gpio         mem           rfkill        tty
block          graphics     misc          rpmsg_rpc     ubi
bluetooth      hidraw       mmc_host      rtc           udc
bsg            hwmon        mtd           scsi_device   uio
cmem           i2c-adapter  net           scsi_disk     vc
devcoredump    i2c-dev      phy           scsi_generic  video4linux
devfreq        ieee80211    power_supply  scsi_host     vtconsole
devfreq-event  input        pps           sound         watchdog
debian@beaglebone:~$ ls -l /sys/class/leds/
total 0
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr0 -> ../../devices/platform/leds/leds/beaglebone:green:usr0
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr1 -> ../../devices/platform/leds/leds/beaglebone:green:usr1
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr2 -> ../../devices/platform/leds/leds/beaglebone:green:usr2
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr3 -> ../../devices/platform/leds/leds/beaglebone:green:usr3
lrwxrwxrwx 1 root root 0 Mar 22 20:37 beaglebone:green:usr4 -> ../../devices/platform/leds/leds/beaglebone:green:usr4
lrwxrwxrwx 1 root root 0 Mar 22 20:37 mmc0:: -> ../../devices/platform/44000000.ocp/4809c000.mmc/leds/mmc0::
lrwxrwxrwx 1 root root 0 Mar 22 20:37 mmc1:: -> ../../devices/platform/44000000.ocp/480b4000.mmc/leds/mmc1::
lrwxrwxrwx 1 root root 0 Mar 22 20:37 mmc2:: -> ../../devices/platform/44000000.ocp/480d1000.mmc/leds/mmc2::

如果在设备树中去掉cape_pins节点的compatible属性之后,platform并不会生成cape_pins节点的device文件,又由于没有驱动程序匹配到cape_pins节点,所以cape_pins节点的pinctrl属性也不会生效:

debian@beaglebone:~$ ls /sys/devices/platform/
 44000000.ocp        encoder@0               omapdrm.0       soc
'Fixed MDIO bus.0'   extcon_usb1             opp-table       ti-cpufreq
 alarmtimer          fixedregulator-vdd_5v   pmu             timer
 brcmf_pwrseq        fixedregulator-vtt      power           uevent
 cmem                gpioregulator-vdd_adc   reg-dummy       unused_pins
 connector@0         gpu-subsystem           regulatory.0    vgem
 cpufreq-dt          leds                    serial8250
 emmc_pwrseq         omap_dma_system.0       snd-soc-dummy
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/CharmingSun/article/details/123767712

智能推荐

密码校验:大小写字母、字符、数字组合,最少8位_8位数字+字母+符号密码-程序员宅基地

文章浏览阅读8.7k次。校验规则:密码必须由大小写字母+字符+数子组合,最少八位,不允许4位及以上连续或重复的数字或字母PassWordUtil 工具类:public class PassWordUtil {/** * 密码是否是正序或反序连续4位及以上 * * @param pwd * @return true为正确,false为错误。 */public static boolean isPasswordContinuous(String pwd) { //正序次数 int count = _8位数字+字母+符号密码

国外冷门邮箱_小众邮箱-程序员宅基地

文章浏览阅读7.5k次。1、GmailGmail是由谷歌开发的免费电子邮箱服务。Gmail可以自动识别垃圾邮件,并自动将其重定向到垃圾邮件文件夹,30天后,将永久删除它们。邮件翻译器是Gmail最独特的功能之一,它使用谷歌翻译将信息翻译成可以阅读的语言,因此可以与来自世界各地的人进行联系。2、雅虎邮箱雅虎邮箱(Yahoo Mail)与其他免费电子邮箱服务对比,它具有最好的垃圾邮件过滤器之一,部分原因是这些过滤器是自动启用的,因此用户不必自行打开它们。3、OutlookOutlook使用起来相_小众邮箱

easyexcel 检查表头是否匹配_封装了easyExcel的工具类,包含字段校验功能,使用validate校验注解即可...-程序员宅基地

文章浏览阅读3.9k次。@date 2020/12/28 9:46*/@Slf4jpublic class ExcelUtil {/**导入Excel并做数据校验@param in Excel文件输入流@param t 返回对象的泛型@param businessValidator 业务校验的接口(自主去实现业务校验,比如:导入的用户是否已存在)@param ..._easy-excel 表头校验

WebStorm常用配置及快捷键_webstorm 行距-程序员宅基地

文章浏览阅读3.4k次。一、WebStorm常用配置WebStorm汉化包······>WebStorm汉化包及使用教程下载地址1. 如何更改主题与字体  (1)更改主题:file(文件)······>setting(设置)······>editor(编辑器)······>切换配色方案······>scheme(方案)······>选择你的主题( darcula)① 找到并单击..._webstorm 行距

分布式架构系统生成全局唯一序列号(研究)_分布式序列号-程序员宅基地

文章浏览阅读3.2k次,点赞2次,收藏2次。分布式架构系统生成全局唯一序列号(研究)【背景】:分布式架构下,唯一序列号生成是我们在设计一个系统,尤其是数据库使用分库分表的时候常常会遇见的问题。当分成若干个sharding表后,如何能够快速拿到一个唯一序列号,是经常遇到的问题。【特性需求】:1. 全局唯一;2. 支持高并发;3. 能够体现一定属性;4. 高可靠,容错单点故障;5. 高性能【业内解决方案】:(1)利用..._分布式序列号

selenium 安装与 chromedriver安装_browser = webdriver.chrome(path)-程序员宅基地

文章浏览阅读1.8k次。直接使用pip安装1 pip install selenium用 Chrome 浏览器来测试1 from selenium import webdriver2 3 browser = webdriver.Chrome()4 browser.get('http://www.baidu.com/')运行这段代码,会自动打开浏览器,然后访问百度。如果程序执行错误,浏览器没有..._browser = webdriver.chrome(path)

随便推点

Android BroadcastReceiver详解_android broadcastreceiver priority-程序员宅基地

文章浏览阅读452次。1. 什么是广播BroadcastReceiver是android四大组件之一,四大组件一般是需要在Manifest.xml清单文件注册,但广播可以在代码中动态注册。每一个应用都可以注册去接收系统或者其他应用发出的广播,自己也可以发送广播。例如系统开机就会发送android.intent.action.BOOT_COMPLETED,我们可以接受广播,作为触发器。2. 广播的分类 ..._android broadcastreceiver priority

UMP认识:(一)系统架构_ump系统架构 mnesia-程序员宅基地

文章浏览阅读1.8w次,点赞5次,收藏16次。UMP(Unified MySQL Platform)是由阿里集团核心系统数据库团队设计与实现的,提供低成本和高性能的MySQL云数据服务。UMP系统架构先不用着急看图,后面介绍各个组件后再来看就比较清楚了。(1)Mnesia:分布式数据库管理系统Mnesia支持事务、提供透明的数据分片、利用两阶段锁来实现分布式事务、具有线性扩展性(可扩展到至少50个节点)。M_ump系统架构 mnesia

[因果推断] Double Machine Learning-DML介绍(四)_作者指出dml的矩条件服从neyman orthogonality条件,因此即便估计有偏,依旧可以得-程序员宅基地

文章浏览阅读2k次。Double Machine Learning——一种去偏方法DML是一种处理基于观测数据进行因果建模的方法。大家已知的是,观测数据是有偏的,即存在特征X既影响目标outcome Y,又影响Treatment T。那么在进行因果建模之前,我们需要进行去偏处理,使得Treatment Y独立于特征X,此时的观测数据近似相当于RCT数据,之后我们就可以使用因果模型进行CATE评估了。HTE旨在量化Treatment对不同人群的差异影响,进而通过人群定向/数值策略的方式进行差异化处理。Double.._作者指出dml的矩条件服从neyman orthogonality条件,因此即便估计有偏,依旧可以得

java密码复杂度匹配规则_hutool 密码复杂度校验-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏3次。在开发中经常需要验证字符串是否匹配纯数字,纯大写,纯小写,大写小写数字特殊字符等任意两种,特别是在密码复杂度匹配时,现添加工具类校验字符串是否满足规则。因为需要设置不匹配的规则,比如至少匹配三种格式,那么久需要设置不能匹配两两组合。_hutool 密码复杂度校验

编译型语言与解释型语言_编译型语言和解释型语言的过程是-程序员宅基地

文章浏览阅读1k次。编译型语言在程序执行之前,有一个单独的编译过程,将程序翻译成机器语言就不用再进行翻译了。解释型语言,是在运行的时候将程序翻译成机器语言,所以运行速度相对于编C/C++ 等都是编译型语言,而Java,C#等都是解释型语言。虽然Java程序在运行之前也有一个编译过程,但是并不是将程序编译成机器语言,而是将它编译成字节码(可以理解为一个中间语言)。在运行的时候,由JVM将字节码再翻译成机器语言..._编译型语言和解释型语言的过程是

物联网云平台系统设计【一】_自拟题目设计一个物联网系统-程序员宅基地

文章浏览阅读1.6w次,点赞11次,收藏61次。物联网云平台系统设计##物联网云平台系统设计下面将谈到几个关键问题:设备如何接入网络? 设备间如何通信? 物联网数据的用途? 如何搭建起一个物联网系统框架呢?它的技术架构又是怎么样呢? 物联网终端软件系统架构? 物联网云平台系统架构?###1. 物联网设备如何接入到网络?只有设备接入到网络里面,才能算是物联网设备。这里涉及到2个关键点:接入方式以及网络通信方式。..._自拟题目设计一个物联网系统