英文

PHY 子系统

作者:

Kishon Vijay Abraham I <kishon@ti.com>

本文档解释了通用 PHY 框架及其提供的 API 以及如何使用。

简介

PHY 是物理层的缩写。它用于将设备连接到物理介质,例如,USB 控制器具有 PHY 来提供诸如串行化、解串行化、编码、解码之类的功能,并负责获得所需的数据传输速率。请注意,某些 USB 控制器将 PHY 功能嵌入其中,而另一些则使用外部 PHY。使用 PHY 的其他外围设备包括无线 LAN、以太网、SATA 等。

创建此框架的目的是将分布在 Linux 内核各处的 PHY 驱动程序迁移到 drivers/phy 中,以提高代码重用率和更好的代码可维护性。

此框架仅对使用外部 PHY 的设备有用(PHY 功能未嵌入到控制器中)。

注册/注销 PHY 提供程序

PHY 提供程序是指实现一个或多个 PHY 实例的实体。对于 PHY 提供程序仅实现单个 PHY 实例的简单情况,该框架提供其自己的 of_xlate 实现,即 of_phy_simple_xlate。如果 PHY 提供程序实现多个实例,则应提供其自己的 of_xlate 实现。of_xlate 仅用于 dt 启动情况。

#define of_phy_provider_register(dev, xlate)    \
        __of_phy_provider_register((dev), NULL, THIS_MODULE, (xlate))

#define devm_of_phy_provider_register(dev, xlate)       \
        __devm_of_phy_provider_register((dev), NULL, THIS_MODULE,
                                        (xlate))

of_phy_provider_register 和 devm_of_phy_provider_register 宏可用于注册 phy_provider,它将设备和 of_xlate 作为参数。对于 dt 启动情况,所有 PHY 提供程序都应使用上述 2 个宏之一来注册 PHY 提供程序。

通常,与 PHY 提供程序关联的设备树节点将包含一组子节点,每个子节点代表一个单独的 PHY。某些绑定可能会将子节点嵌套在额外的层中以实现上下文和可扩展性,在这种情况下,可以使用低级别的 of_phy_provider_register_full() 和 devm_of_phy_provider_register_full() 宏来覆盖包含子节点的节点。

#define of_phy_provider_register_full(dev, children, xlate) \
        __of_phy_provider_register(dev, children, THIS_MODULE, xlate)

#define devm_of_phy_provider_register_full(dev, children, xlate) \
        __devm_of_phy_provider_register_full(dev, children,
                                             THIS_MODULE, xlate)

void devm_of_phy_provider_unregister(struct device *dev,
        struct phy_provider *phy_provider);
void of_phy_provider_unregister(struct phy_provider *phy_provider);

devm_of_phy_provider_unregister 和 of_phy_provider_unregister 可用于注销 PHY。

创建 PHY

PHY 驱动程序应创建 PHY,以便其他外围控制器可以使用它。PHY 框架提供了 2 个 API 来创建 PHY。

struct phy *phy_create(struct device *dev, struct device_node *node,
                       const struct phy_ops *ops);
struct phy *devm_phy_create(struct device *dev,
                            struct device_node *node,
                            const struct phy_ops *ops);

PHY 驱动程序可以通过传递设备指针和 phy ops 来使用上述 2 个 API 之一来创建 PHY。phy_ops 是一组用于执行 PHY 操作的函数指针,例如 init、exit、power_on 和 power_off。

为了取消引用私有数据 (在 phy_ops 中),phy 提供程序驱动程序可以在创建 PHY 后使用 phy_set_drvdata(),并在 phy_ops 中使用 phy_get_drvdata() 来取回私有数据。

获取对 PHY 的引用

在控制器可以使用 PHY 之前,它必须获取对它的引用。此框架提供以下 API 来获取对 PHY 的引用。

struct phy *phy_get(struct device *dev, const char *string);
struct phy *devm_phy_get(struct device *dev, const char *string);
struct phy *devm_phy_optional_get(struct device *dev,
                                  const char *string);
struct phy *devm_of_phy_get(struct device *dev, struct device_node *np,
                            const char *con_id);
struct phy *devm_of_phy_optional_get(struct device *dev,
                                     struct device_node *np,
                                     const char *con_id);
struct phy *devm_of_phy_get_by_index(struct device *dev,
                                     struct device_node *np,
                                     int index);

phy_get、devm_phy_get 和 devm_phy_optional_get 可用于获取 PHY。在 dt 启动的情况下,字符串参数应包含 dt 数据中给出的 phy 名称,而在非 dt 启动的情况下,它应包含 PHY 的标签。两个 devm_phy_get 会在成功获取 PHY 时使用 devres 将设备与 PHY 关联。在驱动程序分离时,会在 devres 数据上调用释放函数,并释放 devres 数据。当 phy 是可选的时,应使用 _optional_get 变体。这些函数永远不会返回 -ENODEV,而是在找不到 phy 时返回 NULL。某些通用驱动程序(例如 ehci)可能会使用多个 phy。在这种情况下,可以使用 devm_of_phy_get 或 devm_of_phy_get_by_index 来根据名称或索引获取 phy 引用。

应该注意的是,NULL 是有效的 phy 引用。在 NULL phy 上的所有 phy 使用者调用都将变为 NOP。也就是说,当应用于 NULL phy 时,释放调用、phy_init() 和 phy_exit() 调用以及 phy_power_on() 和 phy_power_off() 调用都是 NOP。NULL phy 在设备中处理可选 phy 设备时非常有用。

API 调用顺序

一般的调用顺序应为

[devm_][of_]phy_get()
phy_init()
phy_power_on()
[phy_set_mode[_ext]()]
...
phy_power_off()
phy_exit()
[[of_]phy_put()]

某些 PHY 驱动程序可能不实现 phy_init()phy_power_on(),但控制器应始终调用这些函数以与其他 PHY 兼容。某些 PHY 可能需要 phy_set_mode,而其他 PHY 可能使用默认模式(通常通过设备树或其他固件配置)。为了兼容性,如果您知道将要使用的模式,则应始终调用此函数。通常,此函数应在 phy_power_on() 之后调用,尽管某些 PHY 驱动程序可能允许在任何时间调用它。

释放对 PHY 的引用

当控制器不再需要 PHY 时,它必须释放使用上述部分中提到的 API 获取的对 PHY 的引用。PHY 框架提供了 2 个 API 来释放对 PHY 的引用。

void phy_put(struct phy *phy);
void devm_phy_put(struct device *dev, struct phy *phy);

这两个 API 都用于释放对 PHY 的引用,而 devm_phy_put 会销毁与此 PHY 关联的 devres。

销毁 PHY

当创建 PHY 的驱动程序被卸载时,它应使用以下 2 个 API 之一销毁其创建的 PHY

void phy_destroy(struct phy *phy);
void devm_phy_destroy(struct device *dev, struct phy *phy);

这两个 API 都销毁 PHY,而 devm_phy_destroy 会销毁与此 PHY 关联的 devres。

PM 运行时

此子系统已启用 pm 运行时。因此,在创建 PHY 时,将调用此子系统创建的 phy 设备的 pm_runtime_enable,而在销毁 PHY 时,将调用 pm_runtime_disable。请注意,此子系统创建的 phy 设备将是调用 phy_create (PHY 提供程序设备) 的设备的子设备。

因此,由于父子关系,此子系统创建的 phy_device 的 pm_runtime_get_sync 将调用 PHY 提供程序设备的 pm_runtime_get_sync。还应该注意的是,phy_power_on 和 phy_power_off 分别执行 phy_pm_runtime_get_sync 和 phy_pm_runtime_put。还有导出的 API,例如 phy_pm_runtime_get、phy_pm_runtime_get_sync、phy_pm_runtime_put、phy_pm_runtime_put_sync、phy_pm_runtime_allow 和 phy_pm_runtime_forbid 用于执行 PM 操作。

PHY 映射

为了在没有 DeviceTree 帮助的情况下获得对 PHY 的引用,该框架提供了查找功能,该查找功能可以与 clkdev 相比较,后者允许将 clk 结构绑定到设备。当 struct phy 的句柄已存在时,可以在运行时进行查找。

该框架提供以下 API 用于注册和注销查找

int phy_create_lookup(struct phy *phy, const char *con_id,
                      const char *dev_id);
void phy_remove_lookup(struct phy *phy, const char *con_id,
                       const char *dev_id);

设备树绑定

有关 PHY dt 绑定的文档可以在 @ Documentation/devicetree/bindings/phy/phy-bindings.txt 中找到