|  | ======================= | 
|  | Extcon Device Subsystem | 
|  | ======================= | 
|  |  | 
|  | Overview | 
|  | ======== | 
|  |  | 
|  | The Extcon (External Connector) subsystem provides a unified framework for | 
|  | managing external connectors in Linux systems. It allows drivers to report | 
|  | the state of external connectors and provides a standardized interface for | 
|  | userspace to query and monitor these states. | 
|  |  | 
|  | Extcon is particularly useful in modern devices with multiple connectivity | 
|  | options, such as smartphones, tablets, and laptops. It helps manage various | 
|  | types of connectors, including: | 
|  |  | 
|  | 1. USB connectors (e.g., USB-C, micro-USB) | 
|  | 2. Charging ports (e.g., fast charging, wireless charging) | 
|  | 3. Audio jacks (e.g., 3.5mm headphone jack) | 
|  | 4. Video outputs (e.g., HDMI, DisplayPort) | 
|  | 5. Docking stations | 
|  |  | 
|  | Real-world examples: | 
|  |  | 
|  | 1. Smartphone USB-C port: | 
|  | A single USB-C port on a smartphone can serve multiple functions. Extcon | 
|  | can manage the different states of this port, such as: | 
|  | - USB data connection | 
|  | - Charging (various types like fast charging, USB Power Delivery) | 
|  | - Audio output (USB-C headphones) | 
|  | - Video output (USB-C to HDMI adapter) | 
|  |  | 
|  | 2. Laptop docking station: | 
|  | When a laptop is connected to a docking station, multiple connections are | 
|  | made simultaneously. Extcon can handle the state changes for: | 
|  | - Power delivery | 
|  | - External displays | 
|  | - USB hub connections | 
|  | - Ethernet connectivity | 
|  |  | 
|  | 3. Wireless charging pad: | 
|  | Extcon can manage the state of a wireless charging connection, allowing | 
|  | the system to respond appropriately when a device is placed on or removed | 
|  | from the charging pad. | 
|  |  | 
|  | 4. Smart TV HDMI ports: | 
|  | In a smart TV, Extcon can manage multiple HDMI ports, detecting when | 
|  | devices are connected or disconnected, and potentially identifying the | 
|  | type of device (e.g., gaming console, set-top box, Blu-ray player). | 
|  |  | 
|  | The Extcon framework simplifies the development of drivers for these complex | 
|  | scenarios by providing a standardized way to report and query connector | 
|  | states, handle mutually exclusive connections, and manage connector | 
|  | properties. This allows for more robust and flexible handling of external | 
|  | connections in modern devices. | 
|  |  | 
|  | Key Components | 
|  | ============== | 
|  |  | 
|  | extcon_dev | 
|  | ---------- | 
|  |  | 
|  | The core structure representing an Extcon device:: | 
|  |  | 
|  | 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; | 
|  | }; | 
|  |  | 
|  | Key fields: | 
|  |  | 
|  | - ``name``: Name of the Extcon device | 
|  | - ``supported_cable``: Array of supported cable types | 
|  | - ``mutually_exclusive``: Array defining mutually exclusive cable types | 
|  | This field is crucial for enforcing hardware constraints. It's an array of | 
|  | 32-bit unsigned integers, where each element represents a set of mutually | 
|  | exclusive cable types. The array should be terminated with a 0. | 
|  |  | 
|  | For example: | 
|  |  | 
|  | :: | 
|  |  | 
|  | 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 */ | 
|  | }; | 
|  |  | 
|  | In this example, cables 0 and 1 cannot be connected simultaneously, and | 
|  | cables 2, 3, and 4 are also mutually exclusive. This is useful for | 
|  | scenarios like a single port that can either be USB or HDMI, but not both | 
|  | at the same time. | 
|  |  | 
|  | The Extcon core uses this information to prevent invalid combinations of | 
|  | cable states, ensuring that the reported states are always consistent | 
|  | with the hardware capabilities. | 
|  |  | 
|  | - ``state``: Current state of the device (bitmap of connected cables) | 
|  |  | 
|  |  | 
|  | extcon_cable | 
|  | ------------ | 
|  |  | 
|  | Represents an individual cable managed by an Extcon device:: | 
|  |  | 
|  | 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); | 
|  | }; | 
|  |  | 
|  | Core Functions | 
|  | ============== | 
|  |  | 
|  | .. kernel-doc:: drivers/extcon/extcon.c | 
|  | :identifiers: extcon_get_state | 
|  |  | 
|  | .. kernel-doc:: drivers/extcon/extcon.c | 
|  | :identifiers: extcon_set_state | 
|  |  | 
|  | .. kernel-doc:: drivers/extcon/extcon.c | 
|  | :identifiers: extcon_set_state_sync | 
|  |  | 
|  | .. kernel-doc:: drivers/extcon/extcon.c | 
|  | :identifiers: extcon_get_property | 
|  |  | 
|  |  | 
|  | Sysfs Interface | 
|  | =============== | 
|  |  | 
|  | Extcon devices expose the following sysfs attributes: | 
|  |  | 
|  | - ``name``: Name of the Extcon device | 
|  | - ``state``: Current state of all supported cables | 
|  | - ``cable.N/name``: Name of the Nth supported cable | 
|  | - ``cable.N/state``: State of the Nth supported cable | 
|  |  | 
|  | Usage Example | 
|  | ------------- | 
|  |  | 
|  | .. code-block:: c | 
|  |  | 
|  | #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); | 
|  |  | 
|  | This example demonstrates: | 
|  | --------------------------- | 
|  |  | 
|  | - Defining supported cable types (USB and USB Host in this case). | 
|  | - Allocating and registering an extcon device. | 
|  | - Setting an initial state for a cable (USB connected in this example). | 
|  | - Clearing the state when the driver is removed. |