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 供电) - 音频输出 (USB-C 耳机) - 视频输出 (USB-C 转 HDMI 适配器)

  2. 笔记本电脑扩展坞:当笔记本电脑连接到扩展坞时,会同时建立多个连接。Extcon 可以处理以下状态变化: - 供电 - 外接显示器 - 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 主机)。

  • 分配和注册 extcon 设备。

  • 为电缆设置初始状态(本例中为 USB 已连接)。

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