drivers/usb/common/usb-conn-gpio.c

Source file repositories/reference/linux-study-clean/drivers/usb/common/usb-conn-gpio.c

File Facts

System
Linux kernel
Corpus path
drivers/usb/common/usb-conn-gpio.c
Extension
.c
Size
9351 bytes
Lines
376
Domain
Driver Families
Bucket
drivers/usb
Inferred role
Driver Families: implementation source
Status
source implementation candidate

Why This File Exists

Repeatable hardware-adapter layer. Deep compatibility for every driver is out of scope; this atlas records patterns, probe lifecycles, bus glue, IRQ/DMA usage, and links back to core abstractions.

Dependency Surface

Detected Declarations

Annotated Snippet

struct usb_conn_info {
	struct device *dev;
	int conn_id; /* store the IDA-allocated ID */
	struct usb_role_switch *role_sw;
	enum usb_role last_role;
	struct regulator *vbus;
	struct delayed_work dw_det;
	unsigned long debounce_jiffies;

	struct gpio_desc *id_gpiod;
	struct gpio_desc *vbus_gpiod;
	int id_irq;
	int vbus_irq;

	struct power_supply_desc desc;
	struct power_supply *charger;
	bool initial_detection;
};

/*
 * "DEVICE" = VBUS and "HOST" = !ID, so we have:
 * Both "DEVICE" and "HOST" can't be set as active at the same time
 * so if "HOST" is active (i.e. ID is 0)  we keep "DEVICE" inactive
 * even if VBUS is on.
 *
 *  Role          |   ID  |  VBUS
 * ------------------------------------
 *  [1] DEVICE    |   H   |   H
 *  [2] NONE      |   H   |   L
 *  [3] HOST      |   L   |   H
 *  [4] HOST      |   L   |   L
 *
 * In case we have only one of these signals:
 * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1
 * - ID only - we want to distinguish between [1] and [4], so VBUS = ID
 */
static void usb_conn_detect_cable(struct work_struct *work)
{
	struct usb_conn_info *info;
	enum usb_role role;
	int id, vbus, ret;

	info = container_of(to_delayed_work(work),
			    struct usb_conn_info, dw_det);

	/* check ID and VBUS */
	id = info->id_gpiod ?
		gpiod_get_value_cansleep(info->id_gpiod) : 1;
	vbus = info->vbus_gpiod ?
		gpiod_get_value_cansleep(info->vbus_gpiod) : id;

	if (!id)
		role = USB_ROLE_HOST;
	else if (vbus)
		role = USB_ROLE_DEVICE;
	else
		role = USB_ROLE_NONE;

	dev_dbg(info->dev, "role %s -> %s, gpios: id %d, vbus %d\n",
		usb_role_string(info->last_role), usb_role_string(role), id, vbus);

	if (!info->initial_detection && info->last_role == role) {
		dev_warn(info->dev, "repeated role: %s\n", usb_role_string(role));
		return;
	}

	info->initial_detection = false;

	if (info->last_role == USB_ROLE_HOST && info->vbus)
		regulator_disable(info->vbus);

	ret = usb_role_switch_set_role(info->role_sw, role);
	if (ret)
		dev_err(info->dev, "failed to set role: %d\n", ret);

	if (role == USB_ROLE_HOST && info->vbus) {
		ret = regulator_enable(info->vbus);
		if (ret)
			dev_err(info->dev, "enable vbus regulator failed\n");
	}

	info->last_role = role;

	if (info->vbus)
		dev_dbg(info->dev, "vbus regulator is %s\n",
			str_enabled_disabled(regulator_is_enabled(info->vbus)));

	power_supply_changed(info->charger);
}

Annotation

Implementation Notes