嵌入式系统内核镜像相关(六)
文章目录
- 前言
- 一、从这个设备树代码入手
- 二、各个节点的分析
- 2.1 根节点
- 2.2 phy0节点与usb0节点
- 2.2.1 echi、ohci、uhci和xhci的USB解释
- 2.2.1.1 UHCI(Universal Host Controller Interface)
- 2.2.1.2 OHCI(Open Host Controller Interface)
- 2.2.1.3 EHCI(Enhanced Host Controller Interface)
- 2.2.1.4 xHCI(eXtensible Host Controller Interface)
- 2.2.2 generic.txt文档内容
- 2.2.3 ulpi.txt文档内容
- 2.2.4 什么是ULPI?
- 2.2.4.1 ULPI的主要特点
- 2.2.4.2 ULPI的应用场景
- 2.2.5 越挖越有——USB驱动
- 2.2.6 启发
- 2.3 leds节点
- 2.3.1 common.txt文档内容
- 2.3.2 leds-gpio.txt文档内容
- 2.3.3 xilinx wiki中的leds-gpio
- 2.3.4 system-user.dtsi下的leds-gpio
- 2.3.5 gpio.txt文档内容
- 2.3.5.1 gpios属性
- 2.3.5.1.1 gpio-specifier最佳实践
- 2.3.5.2 gpio-controller节点
- 2.3.5.2.1 gpio-和pin-controller交互
- 2.3.6 LED驱动
- 总结
前言
虽然我在《嵌入式系统内核镜像相关(四)》中翻译了《Devicetree Specification Release v0.4》,但具体到设备树代码,光靠这本书似乎难以支持我完全看懂。
列举几个疑问:
1、《Devicetree Specification Release v0.4》提到了“compatible”
和“model”
,如下图。看了不少例子,大多都有“compatible”
,但没有“model”
。那么第一个问题就是:怎么确定哪些属性名称是被需要的,以及应当修改成什么值
?
2、第二个问题就是:哪些节点是被需要写入设备树代码中的
?(这个问题我没在相关文档里找到答案,找了工程师问了问,基本上PS
端不需要,只需要写入PL
端相关的设备树)
3、第三个问题就是:被引用的节点怎么修改
?归根结底还是第一个问题。
4、除以上3个问题外,还有大量问题需要思考,比如ranges
怎么设置,比如u-boot,dm-pre-reloc;
莫名其妙?
以上问题,我虽然提出来了,但碍于功底还不够深厚,没法儿做到全面回答。不过有一点是肯定的,通过grep
大法找到了相关文档。借助相关文档,可以在一定程度上拨开迷雾。
本篇的目的是尝试解释常见设备的设备树代码,当然也会结合前面写的几篇设备树相关的文档。
本篇会拆分成2部分讲。
这2篇后的下一篇尝试魔改某个程序的system-user.dtsi
,看看会发生什么现象。(个人感觉只有会解释错误,才能说明自己真懂了!)
一、从这个设备树代码入手
以system-user.dtsi
为例:
/include/ "system-conf.dtsi"#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1/ { model ="Puzhi Electronic Technology (Shanghai) Co. , Ltd PZ-ZYNQ7000 Development Board"; compatible = "xlnx,zynq-7000"; usb_phy0: phy0@e0002000 {compatible = "ulpi-phy";#phy-cells = <0>;reg = <0xe0002000 0x1000>;view-port = <0x0170>;drv-vbus;};leds {compatible = "gpio-leds";gpio-led1 {label = "pl_led1";gpios = <&gpio0 59 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led2 {label = "pl_led2";gpios = <&gpio0 60 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led3 {label = "pl_led3";gpios = <&gpio0 61 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led4 {label = "pl_led4";gpios = <&gpio0 62 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led5 {label = "pl_led5";gpios = <&gpio0 63 GPIO_ACTIVE_HIGH>;default-state = "on";};};};&axi_ethernet_0 {local-mac-address = [00 0a 35 00 00 00]; phy-handle = <&phy1>; xlnx,has-mdio = <0x1>; phy-mode = "rgmii"; mdio { phy1: phy@1 { device_type = "ethernet-phy"; reg = <1>; };};
};&usb0 {status = "okay";dr_mode = "host";usb-phy = <&usb_phy0>;
};&uart0 {
u-boot,dm-pre-reloc;
};&uart1 {
u-boot,dm-pre-reloc;
};&axi_dynclk_0 {compatible = "digilent,axi-dynclk";clocks = <&clkc 15>;#clock-cells = <0>;
};&v_tc_0 {compatible = "xlnx,v-tc-5.01.a";
};&amba_pl {hdmi_encoder_0:hdmi_encoder {compatible = "digilent,drm-encoder";
};xilinx_drm {compatible = "xlnx,drm";xlnx,vtc = <&v_tc_0>;xlnx,connector-type = "HDMIA";xlnx,encoder-slave = <&hdmi_encoder_0>;clocks = <&axi_dynclk_0>;dglnt,edid-i2c = <&i2c0>;planes {xlnx,pixel-format = "rgb888";plane0 {dmas = <&axi_vdma_0 0>;dma-names = "dma";};};};
};
有根节点、phy0
节点(注意usb_phy0
是其标签,而不是节点)、leds
节点、gpio-ledx
节点、axi_ethernet_0
节点、usb0
节点、uart0
和uart1
节点、axi_dynclk_0
节点、v_tc_0
节点和amba_pl
节点。
以上节点:如果节点没有reg
属性,则必须省略@unit-address
,可以对照一下。另外根节点没有节点名称或单元地址(unit-address
)。它由正斜杠(/
)标识。
因为compatible
属性几乎都有,因此可以尝试使用compatible
后面的属性值去grep
。
二、各个节点的分析
2.1 根节点
根节点的核心信息就是:
model ="Puzhi Electronic Technology (Shanghai) Co. , Ltd PZ-ZYNQ7000 Development Board"; compatible = "xlnx,zynq-7000";
根据要求,model
的定义规范如下:
而compatible
的定义规范如下:
所以在加载OS时,OS会首先尝试找到支持xlnx,zynq-7000
的设备驱动程序。
2.2 phy0节点与usb0节点
该节点的核心信息有2段:
usb_phy0: phy0@e0002000 {compatible = "ulpi-phy";#phy-cells = <0>;reg = <0xe0002000 0x1000>;view-port = <0x0170>;drv-vbus;};
与
&usb0 {status = "okay";dr_mode = "host";usb-phy = <&usb_phy0>;
};
先看第一段。相关的地址信息可以从vitis sdk
中找到:
为解释usb_phy0
,找了bindings
里面的文档:
其中后面的ehci
、ohci
、uhci
和xhci
后面出现了,我通过kimichat
做个简单解释!
另外zynq7000.dtsi
中的usb
设备树如下:
usb0: usb@e0002000 {compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2";status = "disabled";clocks = <&clkc 28>;interrupt-parent = <&intc>;interrupts = <0 21 4>;reg = <0xe0002000 0x1000>;phy_type = "ulpi";};usb1: usb@e0003000 {compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2";status = "disabled";clocks = <&clkc 29>;interrupt-parent = <&intc>;interrupts = <0 44 4>;reg = <0xe0003000 0x1000>;phy_type = "ulpi";};
2.2.1 echi、ohci、uhci和xhci的USB解释
以上均为USB
主机控制器接口标准
2.2.1.1 UHCI(Universal Host Controller Interface)
- 定义:UHCI 是 Intel 主导的 USB 1.0 和 USB 1.1 的接口标准。
- 特点:
- 软件驱动复杂:UHCI 的软件驱动任务较重,需要实现较为复杂的逻辑。
- 硬件成本低:UHCI 的硬件线路相对简单,因此硬件成本较低。
- 兼容性:主要用于 Intel 和 VIA 的芯片组。
- 应用场景:适用于需要较低硬件成本的 USB 1.1 设备。
2.2.1.2 OHCI(Open Host Controller Interface)
- 定义:OHCI 是由 Compaq、Microsoft 等公司提出的 USB 1.1 的主机控制器接口规范。
- 特点:
- 硬件复杂:OHCI 的硬件设计相对复杂,硬件需要完成更多的任务。
- 软件驱动简单:由于硬件承担了较多的工作,软件驱动的实现相对简单。
- 兼容性:不仅支持 USB,还支持 Apple 的火线(Firewire,IEEE 1394)接口。
- 应用场景:主要用于非 x86 架构的 USB 设备,如扩展卡、嵌入式开发板的 USB 主控。
2.2.1.3 EHCI(Enhanced Host Controller Interface)
- 定义:EHCI 是 Intel 主导的 USB 2.0 的接口标准。
- 特点:
- 高速功能:EHCI 仅提供 USB 2.0 的高速功能(480 Mbps),不支持全速(12 Mbps)或低速(1.5 Mbps)设备。
- 依赖其他接口:需要依靠 UHCI 或 OHCI 来支持全速或低速设备。
- 应用场景:适用于需要高速数据传输的 USB 2.0 设备。
2.2.1.4 xHCI(eXtensible Host Controller Interface)
- 定义:xHCI 是最新的 USB 3.0 的接口标准。
- 特点:
- 速度提升:支持 USB 3.0 SuperSpeed(5 Gbps),同时兼容 USB 2.0 和 USB 1.1 的设备。
- 节能:在节能方面有较大提升。
- 虚拟化支持:支持虚拟化技术。
- 兼容性:目标是替换 UHCI、OHCI 和 EHCI。
- 应用场景:适用于需要高速数据传输、节能和虚拟化支持的 USB 3.0 设备。
2.2.2 generic.txt文档内容
采用机翻,如下:
可选属性:
- maximum-speed:告知USB控制器我们希望工作在某个速度上限。有效参数包括“super-speed”(超速)、“high-speed”(高速)、“full-speed”(全速)和“low-speed”(低速)。若未通过设备树传递此参数,USB控制器应默认为其最大硬件能力速度。
- dr_mode:告知双角色(Dual-Role)USB控制器我们希望工作在特定模式。有效参数为“host”(主机)、“peripheral”(外设)和“otg”(OTG)。若此属性未通过设备树传递,USB双角色控制器应默认为OTG模式。
- phy_type:告知USB控制器我们希望配置核心以支持选定的UTMI+ PHY,其具有8位或16位接口。有效参数为“utmi”和“utmi_wide”。若未通过设备树传递此参数,USB控制器应默认为硬件能力所支持的类型。
- otg-rev:告知USB驱动程序设备及其描述符所符合的OTG和EH补充标准的版本号,以二进制编码十进制表示(例如,2.0表示为0200H)。如果启用了任何真正的OTG特性(HNP/SRP/ADP),则使用此属性。如果需要ADP,则otg-rev应为0x0200或更高版本。
- companion:伴生设备的phandle。
- hnp-disable:告知OTG控制器我们希望禁用OTG HNP。通常,HNP是真正OTG的基本功能,除非你希望它仅作为支持SRP的B设备。
- srp-disable:告知OTG控制器我们希望禁用OTG SRP。SRP对于OTG设备是可选的。
- adp-disable:告知OTG控制器我们希望禁用OTG ADP。ADP对于OTG设备是可选的。
这是一个USB控制器的属性示例:
dwc3@4a030000 {compatible = "synopsys,dwc3";reg = <0x4a030000 0xcfff>;interrupts = <0 92 4>;usb-phy = <&usb2_phy>, <&usb3_phy>;maximum-speed = "super-speed";dr_mode = "otg";phy_type = "utmi_wide";otg-rev = <0x0200>;adp-disable;
};
所以usb
工作在host
(也就是主机)模式!
2.2.3 ulpi.txt文档内容
位于ULPI
连接后面的PHY
可以通过以下绑定进行描述。主机控制器应有一个名为“ulpi”
的子节点,该节点下应有一个启用的节点,代表总线上的ULPI
设备。
usb {compatible = "vendor,usb-controller";ulpi {phy {compatible = "vendor,phy";};};
};
以上信息不足以帮助解释phy0
,而关键词盲猜是ulpi
2.2.4 什么是ULPI?
使用ulpi
之前,还是先看看这是个啥?通过kimichat
联网搜索得到的内容:
ULPI(UTMI+ Low Pin Interface)是一种用于USB控制器和USB PHY(物理层)通信的协议。它是UTMI(USB 2.0 Transceiver Macrocell Interface)的低引脚版本,旨在减少引脚数量,同时保持高性能。
2.2.4.1 ULPI的主要特点
- 引脚数量减少:ULPI通过减少引脚数量,简化了USB设计。
- 支持多种模式:ULPI支持主机、设备和OTG(On-The-Go)模式。
- 兼容性:ULPI兼容USB 2.0协议规范。
- 数据传输速度:支持USB 2.0的高速(480 Mbps)、全速(12 Mbps)和低速(1.5 Mbps)数据传输。
- 低功耗:ULPI设计注重低功耗,适合移动应用和电池供电设备。
2.2.4.2 ULPI的应用场景
- 外置PHY:ULPI常用于外置USB PHY芯片,如USB3300。
- 系统级芯片(SoC):ULPI可用于SoC设计,将USB PHY与USB控制器连接。
- 便携设备:ULPI的低功耗和小尺寸使其适用于移动设备和便携式设备。
2.2.5 越挖越有——USB驱动
顺着ulpi
接着使用grep
大法!
挖到驱动老巢,我寻思我咋不需要insmod
,这是因为petalinux
将驱动编译进内核了。
打开看看phy-ulpi.c
的代码,如下:
// SPDX-License-Identifier: GPL-2.0+
/** Generic ULPI USB transceiver support** Copyright (C) 2009 Daniel Mack <daniel@caiaq.de>** Based on sources from** Sascha Hauer <s.hauer@pengutronix.de>* Freescale Semiconductors*/#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/usb.h>
#include <linux/usb/otg.h>
#include <linux/usb/ulpi.h>
#include <linux/usb/phy.h>struct ulpi_info {unsigned int id;char *name;
};#define ULPI_ID(vendor, product) (((vendor) << 16) | (product))
#define ULPI_INFO(_id, _name) \{ \.id = (_id), \.name = (_name), \}/* ULPI hardcoded IDs, used for probing */
static struct ulpi_info ulpi_ids[] = {ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"),ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"),ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"),ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"),ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"),
};struct ulpi_phy {struct usb_phy *usb_phy;void __iomem *regs;unsigned int vp_offset;unsigned int flags;
};static int ulpi_set_otg_flags(struct usb_phy *phy)
{unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN |ULPI_OTG_CTRL_DM_PULLDOWN;if (phy->flags & ULPI_OTG_ID_PULLUP)flags |= ULPI_OTG_CTRL_ID_PULLUP;/** ULPI Specification rev.1.1 default* for Dp/DmPulldown is enabled.*/if (phy->flags & ULPI_OTG_DP_PULLDOWN_DIS)flags &= ~ULPI_OTG_CTRL_DP_PULLDOWN;if (phy->flags & ULPI_OTG_DM_PULLDOWN_DIS)flags &= ~ULPI_OTG_CTRL_DM_PULLDOWN;if (phy->flags & ULPI_OTG_EXTVBUSIND)flags |= ULPI_OTG_CTRL_EXTVBUSIND;return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
}static int ulpi_set_fc_flags(struct usb_phy *phy)
{unsigned int flags = 0;/** ULPI Specification rev.1.1 default* for XcvrSelect is Full Speed.*/if (phy->flags & ULPI_FC_HS)flags |= ULPI_FUNC_CTRL_HIGH_SPEED;else if (phy->flags & ULPI_FC_LS)flags |= ULPI_FUNC_CTRL_LOW_SPEED;else if (phy->flags & ULPI_FC_FS4LS)flags |= ULPI_FUNC_CTRL_FS4LS;elseflags |= ULPI_FUNC_CTRL_FULL_SPEED;if (phy->flags & ULPI_FC_TERMSEL)flags |= ULPI_FUNC_CTRL_TERMSELECT;/** ULPI Specification rev.1.1 default* for OpMode is Normal Operation.*/if (phy->flags & ULPI_FC_OP_NODRV)flags |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING;else if (phy->flags & ULPI_FC_OP_DIS_NRZI)flags |= ULPI_FUNC_CTRL_OPMODE_DISABLE_NRZI;else if (phy->flags & ULPI_FC_OP_NSYNC_NEOP)flags |= ULPI_FUNC_CTRL_OPMODE_NOSYNC_NOEOP;elseflags |= ULPI_FUNC_CTRL_OPMODE_NORMAL;/** ULPI Specification rev.1.1 default* for SuspendM is Powered.*/flags |= ULPI_FUNC_CTRL_SUSPENDM;return usb_phy_io_write(phy, flags, ULPI_FUNC_CTRL);
}static int ulpi_set_ic_flags(struct usb_phy *phy)
{unsigned int flags = 0;if (phy->flags & ULPI_IC_AUTORESUME)flags |= ULPI_IFC_CTRL_AUTORESUME;if (phy->flags & ULPI_IC_EXTVBUS_INDINV)flags |= ULPI_IFC_CTRL_EXTERNAL_VBUS;if (phy->flags & ULPI_IC_IND_PASSTHRU)flags |= ULPI_IFC_CTRL_PASSTHRU;if (phy->flags & ULPI_IC_PROTECT_DIS)flags |= ULPI_IFC_CTRL_PROTECT_IFC_DISABLE;return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL);
}static int ulpi_set_flags(struct usb_phy *phy)
{int ret;ret = ulpi_set_otg_flags(phy);if (ret)return ret;ret = ulpi_set_ic_flags(phy);if (ret)return ret;return ulpi_set_fc_flags(phy);
}static int ulpi_check_integrity(struct usb_phy *phy)
{int ret, i;unsigned int val = 0x55;for (i = 0; i < 2; i++) {ret = usb_phy_io_write(phy, val, ULPI_SCRATCH);if (ret < 0)return ret;ret = usb_phy_io_read(phy, ULPI_SCRATCH);if (ret < 0)return ret;if (ret != val) {pr_err("ULPI integrity check: failed!");return -ENODEV;}val = val << 1;}pr_info("ULPI integrity check: passed.\n");return 0;
}static int ulpi_init(struct usb_phy *phy)
{int i, vid, pid, ret;u32 ulpi_id = 0;for (i = 0; i < 4; i++) {ret = usb_phy_io_read(phy, ULPI_PRODUCT_ID_HIGH - i);if (ret < 0)return ret;ulpi_id = (ulpi_id << 8) | ret;}vid = ulpi_id & 0xffff;pid = ulpi_id >> 16;pr_info("ULPI transceiver vendor/product ID 0x%04x/0x%04x\n", vid, pid);for (i = 0; i < ARRAY_SIZE(ulpi_ids); i++) {if (ulpi_ids[i].id == ULPI_ID(vid, pid)) {pr_info("Found %s ULPI transceiver.\n",ulpi_ids[i].name);break;}}ret = ulpi_check_integrity(phy);if (ret)return ret;return ulpi_set_flags(phy);
}static int ulpi_set_host(struct usb_otg *otg, struct usb_bus *host)
{struct usb_phy *phy = otg->usb_phy;unsigned int flags = usb_phy_io_read(phy, ULPI_IFC_CTRL);if (!host) {otg->host = NULL;return 0;}otg->host = host;flags &= ~(ULPI_IFC_CTRL_6_PIN_SERIAL_MODE |ULPI_IFC_CTRL_3_PIN_SERIAL_MODE |ULPI_IFC_CTRL_CARKITMODE);if (phy->flags & ULPI_IC_6PIN_SERIAL)flags |= ULPI_IFC_CTRL_6_PIN_SERIAL_MODE;else if (phy->flags & ULPI_IC_3PIN_SERIAL)flags |= ULPI_IFC_CTRL_3_PIN_SERIAL_MODE;else if (phy->flags & ULPI_IC_CARKIT)flags |= ULPI_IFC_CTRL_CARKITMODE;return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL);
}static int ulpi_set_vbus(struct usb_otg *otg, bool on)
{struct usb_phy *phy = otg->usb_phy;unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL);flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT);if (on) {if (phy->flags & ULPI_OTG_DRVVBUS)flags |= ULPI_OTG_CTRL_DRVVBUS;if (phy->flags & ULPI_OTG_DRVVBUS_EXT)flags |= ULPI_OTG_CTRL_DRVVBUS_EXT;}return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
}static int usbphy_set_vbus(struct usb_phy *phy, int on)
{unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL);flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT);if (on) {if (phy->flags & ULPI_OTG_DRVVBUS)flags |= ULPI_OTG_CTRL_DRVVBUS;if (phy->flags & ULPI_OTG_DRVVBUS_EXT)flags |= ULPI_OTG_CTRL_DRVVBUS_EXT;}return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
}struct usb_phy *
otg_ulpi_create(struct usb_phy_io_ops *ops,unsigned int flags)
{struct usb_phy *phy;struct usb_otg *otg;phy = kzalloc(sizeof(*phy), GFP_KERNEL);if (!phy)return NULL;otg = kzalloc(sizeof(*otg), GFP_KERNEL);if (!otg) {kfree(phy);return NULL;}phy->label = "ULPI";phy->flags = flags;phy->io_ops = ops;phy->otg = otg;phy->init = ulpi_init;phy->set_vbus = usbphy_set_vbus;otg->usb_phy = phy;otg->set_host = ulpi_set_host;otg->set_vbus = ulpi_set_vbus;return phy;
}
EXPORT_SYMBOL_GPL(otg_ulpi_create);static int ulpi_phy_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;struct resource *res;struct ulpi_phy *uphy;bool flag;int ret;uphy = devm_kzalloc(&pdev->dev, sizeof(*uphy), GFP_KERNEL);if (!uphy)return -ENOMEM;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);uphy->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res));if (IS_ERR(uphy->regs))return PTR_ERR(uphy->regs);ret = of_property_read_u32(np, "view-port", &uphy->vp_offset);if (IS_ERR(uphy->regs)) {dev_err(&pdev->dev, "view-port register not specified\n");return PTR_ERR(uphy->regs);}flag = of_property_read_bool(np, "drv-vbus");if (flag)uphy->flags |= ULPI_OTG_DRVVBUS | ULPI_OTG_DRVVBUS_EXT;uphy->usb_phy = otg_ulpi_create(&ulpi_viewport_access_ops, uphy->flags);uphy->usb_phy->dev = &pdev->dev;uphy->usb_phy->io_priv = uphy->regs + uphy->vp_offset;ret = usb_add_phy_dev(uphy->usb_phy);if (ret < 0)return ret;return 0;
}static int ulpi_phy_remove(struct platform_device *pdev)
{struct ulpi_phy *uphy = platform_get_drvdata(pdev);usb_remove_phy(uphy->usb_phy);return 0;
}static const struct of_device_id ulpi_phy_table[] = {{ .compatible = "ulpi-phy" },{ },
};
MODULE_DEVICE_TABLE(of, ulpi_phy_table);static struct platform_driver ulpi_phy_driver = {.probe = ulpi_phy_probe,.remove = ulpi_phy_remove,.driver = {.name = "ulpi-phy",.of_match_table = ulpi_phy_table,},
};
module_platform_driver(ulpi_phy_driver);MODULE_DESCRIPTION("ULPI PHY driver");
MODULE_LICENSE("GPL v2");
不难发现上述驱动代码包括了如下:
1、compatible = "ulpi-phy";
2、view-port = <0x0170>;
中的view-port
3、drv-vbus;
另外 #phy-cells = <0>;
表示每个PHY
描述符包含0个单元,即该PHY
不需要额外的配置参数。
此外,status
的取值可以参考:
关于这部分驱动代码暂时不做解释,可以知道的是:
1、设备树的部分信息可能来自于对应的驱动。2、设备树的信息如何被驱动调用!!!
2.2.6 启发
1、基于2.2.5
的内容告诉我,仅仅依靠bindings
的文档只能解决一部分问题,更多的设备树信息需要依靠内置的驱动文件;
2、驱动文件尽管内置,但应该可以被修改,以适配实际的需求。
3、内置驱动也可以单独编译成.ko
文件后insmod
。
以上三点是今天的最大收获!!!
2.3 leds节点
bindings
文档可以通过grep
搜索得到:
这里的leds
文件夹内容如下:
大部分内容是针对不同厂家的leds
的设备树撰写进行解释说明。我们需要关注的内容不多,就common.txt
和leds-gpio.txt
(可能你也注意到了leds-pwm.txt
的存在,这个在大部分开发板教程中都会提到,不过我就不展开了!)
2.3.1 common.txt文档内容
common.txt
直接机翻了,内容如下:
LED
和flash LED
设备提供与电流调节器相同的基本功能,但扩展了LED
和flash LED
特定功能,如闪烁模式、闪光灯超时、闪光灯故障和外部闪光灯模式。
许多LED
设备暴露的不仅仅是一个电流输出,可以连接到一个或多个独立的LED
组件。由于连接的排列可以影响LED
设备初始化的方式,LED
组件必须与LED
设备绑定紧密耦合。它们由父LED
设备绑定的子节点表示。
子节点的可选属性:
led-sources
:LED
连接到的设备电流输出列表。输出通过必须在LED
设备绑定文档中定义的数字来识别。label
:LED
的标签。如果省略,标签取自节点名称(不包括单元地址)。它必须唯一标识一个设备,即没有其他LED
类设备可以分配相同的标签。default-state
:LED
的初始状态。有效值为"on"
、"off"
和"keep"
。如果LED
已经打开或关闭,并且default-state
属性设置为相同值,则LED
不应产生闪烁。"keep"
设置将保持LED
在其当前状态,而不产生故障。如果此属性不存在,默认为关闭。linux,default-trigger
:如果存在,此参数是一个字符串,定义分配给LED
的触发器。当前触发器为:
"backlight" - LED将作为背光,由帧缓冲区系统控制"default-on" - LED将打开(但对于leds-gpio参见Documentation/devicetree/bindings/leds/leds-gpio.txt中的default-state属性)"heartbeat" - LED基于负载平均速率"双闪" (这个我在《嵌入式系统内核镜像相关(二)》中已经使用!!!)"disk-activity" - LED指示磁盘活动"ide-disk" - LED指示IDE磁盘活动(已弃用),在新实现中使用"disk-activity""timer" - LED以固定、可配置速率闪烁
-
led-max-microamp
:LED
最大供电电流,以微安为单位。对于引入硬件损坏风险的板配置,此属性可以是强制性的,以防设置过大电流。对于具有可配置电流的flash LED
控制器,此属性对于非闪光灯模式(例如手电筒或指示器)中的LED
是强制性的。 -
panic-indicator
:此属性指定LED
应用作恐慌指示器。 -
trigger-sources
:应用作触发此LED
活动的设备列表。一些LED
可以与特定设备相关,并应以某种方式指示其状态。例如,USB 2.0 LED
可能对USB 2.0
端口中的设备作出反应。另一个常见例子是具有多个以太网端口的交换机或路由器,每个端口都有其自己的LED
分配(假设它们不是硬连线)。在这种情况下,此属性应包含相关源设备的phandle(s)
。在许多情况下,LED
可以与多个设备相关(例如,一个USB LED
与多个USB
端口)。每个源应由设备树中的节点表示,并由phandle
和一组phandle
参数引用。参数长度应由源节点中的#trigger-source-cells
属性指定。
flash LED
子节点的必需属性:
flash-max-microamp
:flash LED
最大供电电流,以微安为单位。flash-max-timeout-us
:flash LED
关闭之前的最超时时间,以微秒为单位。
对于没有可配置电流的控制器,可以省略flash-max-microamp
属性。
对于没有可配置超时的控制器,可以省略flash-max-timeout-us
属性。
- 触发源提供者:每个触发源应由设备树节点表示。它可能是例如一个
USB
端口或以太网设备。
触发源的必需属性:
#trigger-source-cells
:源触发器中的单元格数。对于简单触发源的节点(例如特定USB端口),通常为0。
示例1:
gpio-leds {compatible = "gpio-leds";system-status {label = "Status";linux,default-trigger = "heartbeat";gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;};usb {gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;trigger-sources = <&ohci_port1>, <&ehci_port1>;};
};
示例1节点内部第一段和第二段看懂不难,第三段定义了usb
节点,通过led
指示usb
的插拔状态。
示例2:
max77693-led {compatible = "maxim,max77693-led";camera-flash {label = "Flash";led-sources = <0>, <1>;led-max-microamp = <50000>;flash-max-microamp = <320000>;flash-max-timeout-us = <500000>;};
};
flash led
没用过,先不予评论。
2.3.2 leds-gpio.txt文档内容
leds-gpio.txt
的文档内容机翻如下:
必需属性:
compatible
:应为“gpio-leds”
。
每个LED
都作为gpio-leds
设备的一个子节点来表示。每个节点的名称代表相应LED
的名称。
LED
子节点属性:
gpios
:应指定LED
的GPIO
,参见“gpios属性”
在Documentation/devicetree/bindings/gpio/gpio.txt
。对于低电平有效的LED
,应使用GPIO
指定符中的标志来表示。label
:(可选)
参见Documentation/devicetree/bindings/leds/common.txt
linux,default-trigger
:(可选)
参见Documentation/devicetree/bindings/leds/common.txt
default-state
:(可选)LED
的初始状态。
参见Documentation/devicetree/bindings/leds/common.txt
retain-state-suspended
:(可选)可以保留挂起状态。例如charge-led gpio
。retain-state-shutdown
:(可选)在关机时保留LED
的状态。
在BMC
系统中有用,例如当BMC
重新启动而主机仍然运行时。panic-indicator
:(可选)
参见Documentation/devicetree/bindings/leds/common.txt
示例:
#include <dt-bindings/gpio/gpio.h>leds {compatible = "gpio-leds";hdd {label = "Disk Activity";gpios = <&mcu_pio 0 GPIO_ACTIVE_LOW>;linux,default-trigger = "disk-activity";};fault {gpios = <&mcu_pio 1 GPIO_ACTIVE_HIGH>;/* 如果BIOS检测到硬件故障,则保持LED亮起 */default-state = "keep";};
};
run-control {compatible = "gpio-leds";red {gpios = <&mpc8572 6 GPIO_ACTIVE_HIGH>;default-state = "off";};green {gpios = <&mpc8572 7 GPIO_ACTIVE_HIGH>;default-state = "on";};
};
leds {compatible = "gpio-leds";charger-led {gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;linux,default-trigger = "max8903-charger-charging";retain-state-suspended;};
};
2.3.3 xilinx wiki中的leds-gpio
xilinx wiki
中的leds-gpio
如下:
gpio-leds {compatible = "gpio-leds";led-ds23 {label = "led-ds23";gpios = <&ps7_gpio_0 10 0>;default-state = "on";linux,default-trigger = "heartbeat";};
};
结合前面的2个txt文件可以看明白!
2.3.4 system-user.dtsi下的leds-gpio
前述设备树中leds-gpio
如下:
leds {compatible = "gpio-leds";gpio-led1 {label = "pl_led1";gpios = <&gpio0 59 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led2 {label = "pl_led2";gpios = <&gpio0 60 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led3 {label = "pl_led3";gpios = <&gpio0 61 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led4 {label = "pl_led4";gpios = <&gpio0 62 GPIO_ACTIVE_HIGH>;default-state = "on";};gpio-led5 {label = "pl_led5";gpios = <&gpio0 63 GPIO_ACTIVE_HIGH>;default-state = "on";};};
结合前面的解释已经不难理解了,补充一点,label
最后可视化的效果是驱动名。此外,&gpio0
中的gpio0
来源解释如下:
可以通过检查./build/tmp/work-shared/plnx-zynq7/kernel-source/arch/arm/boot/dts/zynq-7000.dtsi
文件可以发现:
gpio0: gpio@e000a000 {compatible = "xlnx,zynq-gpio-1.0";#gpio-cells = <2>;clocks = <&clkc 42>;gpio-controller;interrupt-controller;#interrupt-cells = <2>;interrupt-parent = <&intc>;interrupts = <0 20 4>;reg = <0xe000a000 0x1000>;};
所以这里的&gpio0
大概率就是引用gpio0
(也就是phandle
)以获取基址寄存器内容,从而根据相对地址将gpio
绑定到led
上。
gpio0
是标签,而gpio
是设备节点名,且该设备的地址为0xe000a000
,怎么确定的?我在后面展开!
compatible = "xlnx,zynq-gpio-1.0";
怎么确定compatible
的值是这样的!非常简单,找到和xilinx
相关的gpio
文件即可(不过翻车了,考虑到上面这段gpio0
标签的节点是zynq-7000
的dtsi文件,且自动生成,我也不去计较了!):
Xilinx plb/axi GPIO controllerDual channel GPIO controller with configurable number of pins
(from 1 to 32 per channel). Every pin can be configured as
input/output/tristate. Both channels share the same global IRQ but
local interrupts can be enabled on channel basis.Required properties:
- compatible : Should be "xlnx,xps-gpio-1.00.a"
- reg : Address and length of the register set for the device
- #gpio-cells : Should be two or three. The first cell is the pin number,The second cell is used to specify channel offset:0 - first channel8 - second channelThe third cell is optional and used to specify flags. Use the macrosdefined in include/dt-bindings/gpio/gpio.h
- gpio-controller : Marks the device node as a GPIO controller.Optional properties:
- clock-names : Should be "s_axi_aclk"
- clocks: Input clock specifier. Refer to common clock bindings.
- interrupts : Interrupt mapping for GPIO IRQ.
- xlnx,all-inputs : if n-th bit is setup, GPIO-n is input
- xlnx,dout-default : if n-th bit is 1, GPIO-n default value is 1
- xlnx,gpio-width : gpio width
- xlnx,tri-default : if n-th bit is 1, GPIO-n is in tristate mode
- xlnx,is-dual : if 1, controller also uses the second channel
- xlnx,all-inputs-2 : as above but for the second channel
- xlnx,dout-default-2 : as above but the second channel
- xlnx,gpio2-width : as above but for the second channel
- xlnx,tri-default-2 : as above but for the second channel
- xlnx,no-init : No initialisation at probeExample:
gpio: gpio@40000000 {#gpio-cells = <2>;compatible = "xlnx,xps-gpio-1.00.a";gpio-controller ;clock-names = "s_axi_aclk";clocks = <&clkc 71>;interrupt-parent = <µblaze_0_intc>;interrupts = < 6 2 >;reg = < 0x40000000 0x10000 >;xlnx,all-inputs = <0x0>;xlnx,all-inputs-2 = <0x0>;xlnx,dout-default = <0x0>;xlnx,dout-default-2 = <0x0>;xlnx,gpio-width = <0x2>;xlnx,gpio2-width = <0x2>;xlnx,interrupt-present = <0x1>;xlnx,is-dual = <0x1>;xlnx,tri-default = <0xffffffff>;xlnx,tri-default-2 = <0xffffffff>;
} ;Example to demonstrate how reset-gpios property is used in drivers:driver: driver@80000000 {compatible = "xlnx,driver";reset-gpios = <&gpio 0 0 GPIO_ACTIVE_LOW>; /* gpio phandle, gpio pin-number, channel offset, flag state */reg = <0x0 0x80000000 0x0 0x10000>;
};
#gpio-cells
定义gpio-specifier
的单元数。gpio-specifier
用于指定GPIO
控制器中的具体GPIO
引脚。这里表示每个gpio-specifier
包含2个单元。
clocks = <&clkc 42>;
定义该设备使用的时钟源。&clkc
也是一个phandle
,指向时钟控制器节点,42
表示该设备使用的时钟源的索引。
gpio-controller;
是一个标记属性,表示该设备是gpio
控制器。
interrupt-controller;
表示该设备是一个中断控制器。
#interrupt-cells = <2>;
定义中断描述符的单元数,表示每个中断描述符包含2个单元。
interrupt-parent = <&intc>;
定义该设备的中断父节点,&intc
也是一个phandle
,指向中断控制器节点。
interrupts = <0 20 4>;
定义该设备使用的中断,该设备使用中断号为20
,优先级为4
的中断。
那么reg = <0xe000a000 0x1000>;
怎么确定的?其实可以回到vitis sdk
去看一下,答案清晰明了!!!换句话说,基地址是0xe000a000
,地址宽度是0x1000
,具体可以通过0xe000aFFF
减去0xe000a000
得到。
以下的2.3.5
就当作知识拓展了!!!
2.3.5 gpio.txt文档内容
这段内容可以参考Documentation/devicetree/bindings/gpio/gpio.txt
我直接机翻了!
2.3.5.1 gpios属性
使用GPIO的节点应通过一个或多个属性来指定它们,每个属性包含一个“gpio-list”:
- gpio-list:由一个或多个“single-gpio”组成,格式为
<single-gpio> [gpio-list]
。 - single-gpio:由
<gpio-phandle> <gpio-specifier>
组成。- gpio-phandle:指向GPIO控制器节点的phandle。
- gpio-specifier:一个包含
#gpio-cells
个元素的数组,用于指定具体的GPIO(控制器特定)。
GPIO属性应命名为"[<name>-]gpios"
,其中<name>
表示该GPIO对设备的用途。虽然出于兼容性原因,不存在<name>
的情况被视为有效(对应“gpios”属性),但新绑定中不允许使用。此外,名为"[<name>-]gpio"
的GPIO属性也是有效的,旧绑定中使用了它,但仅出于兼容性原因支持,新绑定不应使用,因为它已被弃用。
GPIO属性可以包含一个或多个GPIO phandle,但在极少数情况下才应包含多个。如果设备使用多个具有不同功能的GPIO,应在各自的属性下引用它们,并赋予有意义的名称。只有当多个GPIO服务于相同功能时(例如,并行数据线),才接受GPIO数组。
每个gpios
属性的确切用途必须在设备的设备树绑定中进行文档说明。
以下示例可用于描述用作设备使能和位带数据信号的GPIO引脚:
gpio1: gpio1 {gpio-controller;#gpio-cells = <2>;
};
gpio2: gpio2 {gpio-controller;#gpio-cells = <1>;
};
[...]enable-gpios = <&gpio2 2>;
data-gpios = <&gpio1 12 0>,<&gpio1 13 0>,<&gpio1 14 0>,<&gpio1 15 0>;
注意,gpio-specifier
的长度取决于控制器。在上述示例中,&gpio1
使用2个单元来指定一个GPIO,而&gpio2
只使用一个。
gpio-specifier
可能编码:bank
、bank
内的引脚位置、引脚是否为开漏以及引脚是否逻辑反转。
每个指定符单元的确切含义是控制器特定的,必须在设备的设备树绑定中进行文档说明。
然而,大多数控制器在最后一个单元中指定一个通用标志位字段,因此对于这些情况,应尽可能使用include/dt-bindings/gpio/gpio.h
中定义的宏:
使用GPIO的节点示例:
node {enable-gpios = <&qe_pio_e 18 GPIO_ACTIVE_HIGH>;
};
GPIO_ACTIVE_HIGH
为0,因此在此示例中,gpio-specifier
为“18 0”,编码GPIO引脚编号和"qe_pio_e"
gpio-controller接受的GPIO标志。
最后一个单元的可选标准位字段指定符:
- 位0:0表示高电平有效,1表示低电平有效。
- 位1:0表示推挽式接线,参见推挽输出;1表示单端接线,参见单端三极管。
- 位2:0表示开漏,1表示开漏,参见开漏。
- 位3:0表示在睡眠/低功耗模式下保持输出状态;1表示在睡眠/低功耗模式下可能丢失输出状态。
2.3.5.1.1 gpio-specifier最佳实践
gpio-specifier
应包含一个标志,指示GPIO极性;高电平有效或低电平有效。如果包含,应遵循以下最佳实践:
gpio-specifier
的极性标志应表示在GPIO控制器处实现(或表示,对于输入)设备处逻辑断言值的物理电平。逻辑断言的精确定义应由设备的绑定定义。如果板子在GPIO控制器和设备之间反转信号,那么gpio-specifier
将表示与设备引脚处信号相反的物理电平。
当设备的信号极性可配置时,设备的绑定必须:
a) 定义信号的单一静态极性,期望使用该绑定的任何软件都会静态地将设备编程为使用该信号极性。
静态极性的选择可以是:
a1) (推荐)由绑定特定的DT属性决定。
或者:
a2) 由DT绑定本身静态定义。
特别是,不能从gpio-specifier
推导出极性,因为那将阻止DT分别表示设备中可配置信号极性的两个正交概念,以及可能的板级信号反转。
或者:
b) 选择设备信号极性的一个单一选项,并在绑定中记录此选择。gpio-specifier
应表示信号的极性(在GPIO控制器处),假设设备已配置为此特定信号极性选择。如果软件选择将设备编程为生成或接收相反极性的信号,则软件将负责正确解释(反转)GPIO控制器处的GPIO信号。
2.3.5.2 gpio-controller节点
每个GPIO控制器节点都必须包含一个空的“gpio-controller”属性,以及一个#gpio-cells
整数属性,该属性指示gpio-specifier
中的单元数。
一些片上系统(SoCs)使用GPIO bank的概念。GPIO bank是一个硬件IP内核在硅片上的一个实例,通常以一组连续的I/O地址的形式暴露给程序员。通常,每个这样的bank在设备树中作为一个单独的gpio-controller
节点暴露,反映了硬件是通过多次重用相同的IP块合成的事实。
可选地,GPIO控制器可以有一个“ngpios”属性。此属性指示可用GPIO槽位中正在使用的槽位数量。一个典型的例子是:硬件寄存器是32位宽的,但只有18位有物理对应物。驱动程序通常被编写为可以使用所有32位,但IP块在许多设计中被重用,有些使用所有32位,有些使用18位,有些使用12位。在这种情况下,设置"ngpios = <18>;"
通知驱动程序只有前18个GPIO,本地偏移量为0…17,正在使用。
如果这些GPIO不是从偏移量0…N-1开始的前N个GPIO,则需要额外的一组元组来指定哪些GPIO不可用,使用gpio-reserved-ranges
绑定。此属性指示不能使用的GPIO的起始位置和大小。
可选地,GPIO控制器可以有一个“gpio-line-names”属性。这是一个字符串数组,定义从GPIO控制器引出的GPIO线的名称。此名称应是系统中最有意义的生产者名称,例如表示用途的电源轨名称。不鼓励使用像引脚名称这样的封装名称:这些线路的名称是不透明的(因为它们被定义为通用目的),这样的名称通常没有多大帮助。例如,“MMC-CD”、“Red LED Vdd”和“ethernet reset”是合理的线路名称,因为它们描述了线路的用途。“GPIO0”不是给GPIO线路起的好名字。不鼓励使用占位符:如果在设计中GPIO线路的用途未定义,则使用“”(空字符串)。名称从传递的数组的左侧开始,从线路偏移量0开始依次分配。即使传递的名称数量少于ngpios
(即数组不完整),仍会使用直到最后一个提供的有效线路索引。
示例:
gpio-controller@00000000 {compatible = "foo";reg = <0x00000000 0x1000>;gpio-controller;#gpio-cells = <2>;ngpios = <18>;gpio-reserved-ranges = <0 4>, <12 2>;gpio-line-names = "MMC-CD", "MMC-WP", "VDD eth", "RST eth", "LED R","LED G", "LED B", "Col A", "Col B", "Col C", "Col D","Row A", "Row B", "Row C", "Row D", "NMI button","poweroff", "reset";
}
GPIO芯片可以包含GPIO hog定义。GPIO hog是一种机制,作为gpio-controller
的驱动程序探测函数的一部分,自动请求和配置GPIO。
每个GPIO hog定义作为GPIO控制器的一个子节点表示。
必需属性:
- gpio-hog:一个属性,指定该子节点代表一个GPIO hog。
- gpios:存储每个要影响的GPIO的GPIO信息(id、标志等)。应包含其父节点(GPIO控制器节点)中指定的单元数的整数倍。
以下属性中只有一个会被扫描,按以下顺序显示。这意味着当存在多个属性时,它们将按以下顺序搜索,第一个匹配项被视为预期配置。 - input:一个属性,指定将GPIO方向设置为输入。
- output-low:一个属性,指定将GPIO方向设置为输出,并且值为低。
- output-high:一个属性,指定将GPIO方向设置为输出,并且值为高。
可选属性:
- line-name:GPIO标签名称。如果不存在,则使用节点名称。
两个SOC GPIO bank定义为gpio-controller
节点的示例:
qe_pio_a: gpio-controller@1400 {compatible = "fsl,qe-pario-bank-a", "fsl,qe-pario-bank";reg = <0x1400 0x18>;gpio-controller;#gpio-cells = <2>;line_b {gpio-hog;gpios = <6 0>;output-low;line-name = "foo-bar-gpio";};
};qe_pio_e: gpio-controller@1460 {compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";reg = <0x1460 0x18>;gpio-controller;#gpio-cells = <2>;
};
2.3.5.2.1 gpio-和pin-controller交互
GPIO控制器提供的一个或所有GPIO可能通过pin控制器路由到封装上的引脚。这允许在GPIO和其他功能之间对这些引脚进行复用。
表示哪些GPIO对应于哪些pin控制器上的哪些引脚是有用的。下面描述的gpio-ranges
属性表示这一点,并包含如下信息结构:
- gpio-range-list:由一个或多个
single-gpio-range
组成,格式为<single-gpio-range> [gpio-range-list]
。 - single-gpio-range:可以是
numeric-gpio-range
或named-gpio-range
。- numeric-gpio-range:
<pinctrl-phandle> <gpio-base> <pinctrl-base> <count>
。 - named-gpio-range:
<pinctrl-phandle> <gpio-base> '<0 0>'
。- pinctrl-phandle:指向pin控制器节点的phandle。
- gpio-base:GPIO控制器中的基础GPIO ID。
- pinctrl-base:pin控制器中的基础pinctrl引脚ID。
- count:此范围内的GPIO/引脚数量。
- numeric-gpio-range:
上面提到的“pin控制器节点”必须符合../pinctrl/pinctrl-bindings.txt
中描述的绑定。
如果使用命名的GPIO范围(范围中<pinctrl-base>
和<count>
都设置为0),则gpio-ranges-group-names
属性包含gpio-ranges
中的每个single-gpio-range
的一个字符串:
- gpiorange-names-list:由一个或多个
gpiorange-name
组成,格式为<gpiorange-name> [gpiorange-names-list]
。- gpiorange-name:与相应pin控制器中的GPIO范围关联的pingroup的名称。
对应于数值范围的gpiorange-names-list
元素包含空字符串。对应于命名范围的gpiorange-names-list
元素包含在相应pin控制器中定义的pingroup的名称。范围内的引脚/GPIO数量是该pingroup中的引脚数量。
此绑定的早期版本要求所有被任何gpio-ranges
属性引用的pin控制器节点都包含一个名为#gpio-range-cells
的属性,其值为<3>
。此要求现已弃用。然而,该属性可能仍然存在于旧设备树中,出于兼容性原因,甚至在需要与旧软件兼容的新设备树中仍然是必需的。
示例1:
qe_pio_e: gpio-controller@1460 {#gpio-cells = <2>;compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";reg = <0x1460 0x18>;gpio-controller;gpio-ranges = <&pinctrl1 0 20 10>, <&pinctrl2 10 50 20>;
};
在此示例中,一个单独的GPIO控制器将GPIO 0…9路由到pin控制器pinctrl1
的引脚20…29,将GPIO 10…29路由到pin控制器pinctrl2
的引脚50…69。
示例2:
gpio_pio_i: gpio-controller@14b0 {#gpio-cells = <2>;compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";reg = <0x1480 0x18>;gpio-controller;gpio-ranges = <&pinctrl1 0 20 10>,<&pinctrl2 10 0 0>,<&pinctrl1 15 0 10>,<&pinctrl2 25 0 0>;gpio-ranges-group-names = "","foo","","bar";
};
在此示例中,相对于两个pin控制器定义了三个GPIO范围。pinctrl1
的GPIO范围使用引脚编号定义,而相对于pinctrl2
的GPIO范围命名为“foo”和“bar”。
2.3.6 LED驱动
和phy-ulpi.c
类似,LED
也有驱动文件:
leds-gpio.c
如下:
/** LEDs driver for GPIOs** Copyright (C) 2007 8D Technologies inc.* Raphael Assenat <raph@8d.com>* Copyright (C) 2008 Freescale Semiconductor, Inc.** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.**/
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>struct gpio_led_data {struct led_classdev cdev;struct gpio_desc *gpiod;u8 can_sleep;u8 blinking;gpio_blink_set_t platform_gpio_blink_set;
};static inline struct gpio_led_data *cdev_to_gpio_led_data(struct led_classdev *led_cdev)
{return container_of(led_cdev, struct gpio_led_data, cdev);
}static void gpio_led_set(struct led_classdev *led_cdev,enum led_brightness value)
{struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);int level;if (value == LED_OFF)level = 0;elselevel = 1;if (led_dat->blinking) {led_dat->platform_gpio_blink_set(led_dat->gpiod, level,NULL, NULL);led_dat->blinking = 0;} else {if (led_dat->can_sleep)gpiod_set_value_cansleep(led_dat->gpiod, level);elsegpiod_set_value(led_dat->gpiod, level);}
}static int gpio_led_set_blocking(struct led_classdev *led_cdev,enum led_brightness value)
{gpio_led_set(led_cdev, value);return 0;
}static int gpio_blink_set(struct led_classdev *led_cdev,unsigned long *delay_on, unsigned long *delay_off)
{struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);led_dat->blinking = 1;return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK,delay_on, delay_off);
}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)
{int ret, state;led_dat->gpiod = template->gpiod;if (!led_dat->gpiod) {/** This is the legacy code path for platform code that* still uses GPIO numbers. Ultimately we would like to get* rid of this block completely.*/unsigned long flags = GPIOF_OUT_INIT_LOW;/* skip leds that aren't available */if (!gpio_is_valid(template->gpio)) {dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",template->gpio, template->name);return 0;}if (template->active_low)flags |= GPIOF_ACTIVE_LOW;ret = devm_gpio_request_one(parent, template->gpio, flags,template->name);if (ret < 0)return ret;led_dat->gpiod = gpio_to_desc(template->gpio);if (!led_dat->gpiod)return -EINVAL;}led_dat->cdev.name = template->name;led_dat->cdev.default_trigger = template->default_trigger;led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);if (!led_dat->can_sleep)led_dat->cdev.brightness_set = gpio_led_set;elseled_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;led_dat->blinking = 0;if (blink_set) {led_dat->platform_gpio_blink_set = blink_set;led_dat->cdev.blink_set = gpio_blink_set;}if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {state = gpiod_get_value_cansleep(led_dat->gpiod);if (state < 0)return state;} else {state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);}led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;if (!template->retain_state_suspended)led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;if (template->panic_indicator)led_dat->cdev.flags |= LED_PANIC_INDICATOR;if (template->retain_state_shutdown)led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;ret = gpiod_direction_output(led_dat->gpiod, state);if (ret < 0)return ret;return devm_of_led_classdev_register(parent, np, &led_dat->cdev);
}struct gpio_leds_priv {int num_leds;struct gpio_led_data leds[];
};static inline int sizeof_gpio_leds_priv(int num_leds)
{return sizeof(struct gpio_leds_priv) +(sizeof(struct gpio_led_data) * num_leds);
}static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{struct device *dev = &pdev->dev;struct fwnode_handle *child;struct gpio_leds_priv *priv;int count, ret;count = device_get_child_node_count(dev);if (!count)return ERR_PTR(-ENODEV);priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);if (!priv)return ERR_PTR(-ENOMEM);device_for_each_child_node(dev, child) {struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];struct gpio_led led = {};const char *state = NULL;struct device_node *np = to_of_node(child);ret = fwnode_property_read_string(child, "label", &led.name);if (ret && IS_ENABLED(CONFIG_OF) && np)led.name = np->name;if (!led.name) {fwnode_handle_put(child);return ERR_PTR(-EINVAL);}led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,GPIOD_ASIS,led.name);if (IS_ERR(led.gpiod)) {fwnode_handle_put(child);return ERR_CAST(led.gpiod);}fwnode_property_read_string(child, "linux,default-trigger",&led.default_trigger);if (!fwnode_property_read_string(child, "default-state",&state)) {if (!strcmp(state, "keep"))led.default_state = LEDS_GPIO_DEFSTATE_KEEP;else if (!strcmp(state, "on"))led.default_state = LEDS_GPIO_DEFSTATE_ON;elseled.default_state = LEDS_GPIO_DEFSTATE_OFF;}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;ret = create_gpio_led(&led, led_dat, dev, np, NULL);if (ret < 0) {fwnode_handle_put(child);return ERR_PTR(ret);}led_dat->cdev.dev->of_node = np;priv->num_leds++;}return priv;
}static const struct of_device_id of_gpio_leds_match[] = {{ .compatible = "gpio-leds", },{},
};MODULE_DEVICE_TABLE(of, of_gpio_leds_match);static int gpio_led_probe(struct platform_device *pdev)
{struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);struct gpio_leds_priv *priv;int i, ret = 0;if (pdata && pdata->num_leds) {priv = devm_kzalloc(&pdev->dev,sizeof_gpio_leds_priv(pdata->num_leds),GFP_KERNEL);if (!priv)return -ENOMEM;priv->num_leds = pdata->num_leds;for (i = 0; i < priv->num_leds; i++) {ret = create_gpio_led(&pdata->leds[i], &priv->leds[i],&pdev->dev, NULL,pdata->gpio_blink_set);if (ret < 0)return ret;}} else {priv = gpio_leds_create(pdev);if (IS_ERR(priv))return PTR_ERR(priv);}platform_set_drvdata(pdev, priv);return 0;
}static void gpio_led_shutdown(struct platform_device *pdev)
{struct gpio_leds_priv *priv = platform_get_drvdata(pdev);int i;for (i = 0; i < priv->num_leds; i++) {struct gpio_led_data *led = &priv->leds[i];if (!(led->cdev.flags & LED_RETAIN_AT_SHUTDOWN))gpio_led_set(&led->cdev, LED_OFF);}
}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);MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>");
MODULE_DESCRIPTION("GPIO LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-gpio");
先只做展示,不做解释!后面看驱动有素材了!!!
总结
对参考文件的设备树代码的若干节点进行了解释,然后一步步深挖,挖了三点启发,算是本篇分析的最大收获!
另外也发现了内置的驱动文件,为后续驱动的解读和改写做了点准备!