Extcon 设备子系统

概述

Extcon(外部连接器)子系统为 Linux 系统中的外部连接器管理提供了一个统一的框架。它允许驱动程序报告外部连接器的状态,并为用户空间查询和监视这些状态提供标准化的接口。

Extcon 对于具有多种连接选项的现代设备(如智能手机、平板电脑和笔记本电脑)特别有用。它有助于管理各种类型的连接器,包括

  1. USB 连接器(例如 USB-C、micro-USB)

  2. 充电端口(例如快充、无线充电)

  3. 音频插孔(例如 3.5mm 耳机插孔)

  4. 视频输出(例如 HDMI、DisplayPort)

  5. 扩展坞

实际应用示例

  1. 智能手机 USB-C 端口:智能手机上的单个 USB-C 端口可以充当多种功能。Extcon 可以管理此端口的不同状态,例如: - USB 数据连接 - 充电(快充、USB Power Delivery 等各种类型) - 音频输出(USB-C 耳机) - 视频输出(USB-C 转 HDMI 适配器)

  2. 笔记本电脑扩展坞:当笔记本电脑连接到扩展坞时,会同时建立多个连接。Extcon 可以处理以下项的状态变化: - 供电(Power delivery) - 外部显示器 - USB 集线器连接 - 以太网连接

  3. 无线充电板:Extcon 可以管理无线充电连接的状态,允许系统在设备放置在充电板上或从充电板上移开时做出相应的响应。

  4. 智能电视 HDMI 端口:在智能电视中,Extcon 可以管理多个 HDMI 端口,检测设备是连接还是断开,并可能识别设备类型(例如游戏机、机顶盒、蓝光播放器)。

Extcon 框架通过提供一种标准化的方法来报告和查询连接器状态、处理互斥连接以及管理连接器属性,简化了针对这些复杂场景的驱动程序开发。这使得现代设备能够更强大、更灵活地处理外部连接。

关键组件

extcon_dev

代表 Extcon 设备的内核结构体

struct extcon_dev {
    const char *name;
    const unsigned int *supported_cable;
    const u32 *mutually_exclusive;

    /* Internal data */
    struct device dev;
    unsigned int id;
    struct raw_notifier_head nh_all;
    struct raw_notifier_head *nh;
    struct list_head entry;
    int max_supported;
    spinlock_t lock;
    u32 state;

    /* Sysfs related */
    struct device_type extcon_dev_type;
    struct extcon_cable *cables;
    struct attribute_group attr_g_muex;
    struct attribute **attrs_muex;
    struct device_attribute *d_attrs_muex;
};

关键字段

  • name:Extcon 设备的名称

  • supported_cable:支持的线缆类型数组

  • mutually_exclusive:定义互斥线缆类型的数组。此字段对于强制执行硬件约束至关重要。它是一个 32 位无符号整数数组,其中每个元素代表一组互斥的线缆类型。该数组应当以 0 结尾。

    例如

    static const u32 mutually_exclusive[] = {
        BIT(0) | BIT(1),  /* Cable 0 and 1 are mutually exclusive */
        BIT(2) | BIT(3) | BIT(4),  /* Cables 2, 3, and 4 are mutually exclusive */
        0  /* Terminator */
    };
    

    在此示例中,线缆 0 和 1 不能同时连接,线缆 2、3 和 4 也是互斥的。这对于诸如单个端口可以作为 USB 或 HDMI(但不能同时作为两者)等场景非常有用。

    Extcon 核心使用此信息来防止出现无效的线缆状态组合,从而确保报告的状态始终与硬件功能保持一致。

  • state:设备的当前状态(已连接线缆的位图)

extcon_cable

表示由 Extcon 设备管理的单个线缆

struct extcon_cable {
    struct extcon_dev *edev;
    int cable_index;
    struct attribute_group attr_g;
    struct device_attribute attr_name;
    struct device_attribute attr_state;
    struct attribute *attrs[3];
    union extcon_property_value usb_propval[EXTCON_PROP_USB_CNT];
    union extcon_property_value chg_propval[EXTCON_PROP_CHG_CNT];
    union extcon_property_value jack_propval[EXTCON_PROP_JACK_CNT];
    union extcon_property_value disp_propval[EXTCON_PROP_DISP_CNT];
    DECLARE_BITMAP(usb_bits, EXTCON_PROP_USB_CNT);
    DECLARE_BITMAP(chg_bits, EXTCON_PROP_CHG_CNT);
    DECLARE_BITMAP(jack_bits, EXTCON_PROP_JACK_CNT);
    DECLARE_BITMAP(disp_bits, EXTCON_PROP_DISP_CNT);
};

核心函数

int extcon_get_state(struct extcon_dev *edev, const unsigned int id)

获取外部连接器的状态。

参数

struct extcon_dev *edev

extcon 设备

const unsigned int id

指示外部连接器的唯一 id

描述

成功时返回 0,失败时返回错误码。

int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool state)

设置外部连接器的状态。

参数

struct extcon_dev *edev

extcon 设备

unsigned int id

指示外部连接器的唯一 id

bool state

外部连接器的新状态。默认语义为 true:已连接 / false:已断开。

描述

请注意,此函数在设置外部连接器状态时不发送通知。要同步外部连接器的状态,必须使用 extcon_set_state_sync()extcon_sync()

成功时返回 0,失败时返回错误码。

int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool state)

通过同步设置外部连接器的状态。

参数

struct extcon_dev *edev

extcon 设备

unsigned int id

指示外部连接器的唯一 id

bool state

外部连接器的新状态。默认语义为 true:已连接 / false:已断开。

描述

请注意,此函数设置外部连接器的状态,并通过发送通知来同步该状态。

成功时返回 0,失败时返回错误码。

int extcon_get_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value *prop_val)

获取外部连接器的属性值。

参数

struct extcon_dev *edev

extcon 设备

unsigned int id

指示外部连接器的唯一 id

unsigned int prop

指示 extcon 属性的属性 id

union extcon_property_value *prop_val

存储 extcon 属性值的指针

描述

请注意,在获取外部连接器的属性值时,该外部连接器应当处于连接状态。如果是断开状态,函数将返回 0 且不包含属性值。此外,每个属性都应根据 extcon 类型包含在支持的属性列表中。

成功时返回 0,失败时返回错误码。

Sysfs 接口

Extcon 设备公开了以下 sysfs 属性

  • name:Extcon 设备的名称

  • state:所有受支持线缆的当前状态

  • cable.N/name:第 N 个受支持线缆的名称

  • cable.N/state:第 N 个受支持线缆的状态

用法示例

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/extcon.h>

struct my_extcon_data {
    struct extcon_dev *edev;
    struct device *dev;
};

static const unsigned int my_extcon_cable[] = {
    EXTCON_USB,
    EXTCON_USB_HOST,
    EXTCON_NONE,
};

static int my_extcon_probe(struct platform_device *pdev)
{
    struct my_extcon_data *data;
    int ret;

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

    data->dev = &pdev->dev;

    /* Initialize extcon device */
    data->edev = devm_extcon_dev_allocate(data->dev, my_extcon_cable);
    if (IS_ERR(data->edev)) {
        dev_err(data->dev, "Failed to allocate extcon device\n");
        return PTR_ERR(data->edev);
    }

    /* Register extcon device */
    ret = devm_extcon_dev_register(data->dev, data->edev);
    if (ret < 0) {
        dev_err(data->dev, "Failed to register extcon device\n");
        return ret;
    }

    platform_set_drvdata(pdev, data);

    /* Example: Set initial state */
    extcon_set_state_sync(data->edev, EXTCON_USB, true);

    dev_info(data->dev, "My extcon driver probed successfully\n");
    return 0;
}

static int my_extcon_remove(struct platform_device *pdev)
{
    struct my_extcon_data *data = platform_get_drvdata(pdev);

    /* Example: Clear state before removal */
    extcon_set_state_sync(data->edev, EXTCON_USB, false);

    dev_info(data->dev, "My extcon driver removed\n");
    return 0;
}

static const struct of_device_id my_extcon_of_match[] = {
    { .compatible = "my,extcon-device", },
    { },
};
MODULE_DEVICE_TABLE(of, my_extcon_of_match);

static struct platform_driver my_extcon_driver = {
    .driver = {
        .name = "my-extcon-driver",
        .of_match_table = my_extcon_of_match,
    },
    .probe = my_extcon_probe,
    .remove = my_extcon_remove,
};

module_platform_driver(my_extcon_driver);

此示例演示了:

  • 定义支持的线缆类型(在此例中为 USB 和 USB Host)。

  • 分配并注册一个 extcon 设备。

  • 为线缆设置初始状态(在此例中为 USB 已连接)。

  • 在移除驱动程序时清除状态。