Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4ff6b47f authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb: pd: Support Android dual_role_usb sysfs class"

parents 794f6f90 e0402850
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ config USB_PD
config USB_PD_POLICY
	tristate "USB Power Delivery Protocol and Policy Engine"
	depends on EXTCON
	depends on DUAL_ROLE_USB_INTF
	select USB_PD
	help
          Say Y here to enable USB PD protocol and policy engine.
+295 −12
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
 * GNU General Public License for more details.
 */

#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/ipc_logging.h>
@@ -24,6 +25,7 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/extcon.h>
#include <linux/usb/class-dual-role.h>
#include <linux/usb/usbpd.h>
#include "usbpd.h"

@@ -311,12 +313,19 @@ struct usbpd {

	enum power_supply_typec_mode typec_mode;
	enum power_supply_type	psy_type;
	enum power_supply_typec_power_role forced_pr;
	bool			vbus_present;

	enum data_role		current_dr;
	enum power_role		current_pr;
	bool			in_pr_swap;
	bool			pd_phy_opened;
	struct completion	swap_complete;

	struct dual_role_phy_instance	*dual_role;
	struct dual_role_phy_desc	dr_desc;
	bool			send_pr_swap;
	bool			send_dr_swap;

	struct regulator	*vbus;
	struct regulator	*vconn;
@@ -664,6 +673,8 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
			 */
		}

		dual_role_instance_changed(pd->dual_role);

		/* Set CC back to DRP toggle for the next disconnect */
		val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
		power_supply_set_property(pd->usb_psy,
@@ -777,6 +788,8 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
		}

		kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		complete(&pd->swap_complete);
		dual_role_instance_changed(pd->dual_role);
		break;

	case PE_SRC_HARD_RESET:
@@ -818,6 +831,8 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
			}
		}

		dual_role_instance_changed(pd->dual_role);

		ret = power_supply_get_property(pd->usb_psy,
				POWER_SUPPLY_PROP_PD_ALLOWED, &val);
		if (ret) {
@@ -907,6 +922,8 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
	case PE_SNK_READY:
		pd->in_explicit_contract = true;
		kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		complete(&pd->swap_complete);
		dual_role_instance_changed(pd->dual_role);
		break;

	case PE_SNK_TRANSITION_TO_DEFAULT:
@@ -1463,13 +1480,22 @@ static void usbpd_sm(struct work_struct *w)
			usleep_range(ERROR_RECOVERY_TIME * USEC_PER_MSEC,
				(ERROR_RECOVERY_TIME + 5) * USEC_PER_MSEC);

		/* set due to dual_role class "mode" change */
		if (pd->forced_pr != POWER_SUPPLY_TYPEC_PR_NONE)
			val.intval = pd->forced_pr;
		else
			/* Set CC back to DRP toggle */
			val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;

		power_supply_set_property(pd->usb_psy,
				POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &val);
		pd->forced_pr = POWER_SUPPLY_TYPEC_PR_NONE;

		pd->current_state = PE_UNKNOWN;

		kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		dual_role_instance_changed(pd->dual_role);

		goto sm_done;
	}

@@ -1624,7 +1650,6 @@ static void usbpd_sm(struct work_struct *w)
			}

			dr_swap(pd);
			kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		} else if (IS_CTRL(rx_msg, MSG_PR_SWAP)) {
			/* lock in current mode */
			set_power_role(pd, pd->current_pr);
@@ -1649,10 +1674,31 @@ static void usbpd_sm(struct work_struct *w)
			}

			vconn_swap(pd);
		} else {
			if (IS_DATA(rx_msg, MSG_VDM))
		} else if (IS_DATA(rx_msg, MSG_VDM)) {
			handle_vdm_rx(pd, rx_msg);
			else
		} else if (pd->send_pr_swap) {
			pd->send_pr_swap = false;
			ret = pd_send_msg(pd, MSG_PR_SWAP, NULL, 0, SOP_MSG);
			if (ret) {
				dev_err(&pd->dev, "Error sending PR Swap\n");
				usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
				break;
			}

			pd->current_state = PE_PRS_SRC_SNK_SEND_SWAP;
			kick_sm(pd, SENDER_RESPONSE_TIME);
		} else if (pd->send_dr_swap) {
			pd->send_dr_swap = false;
			ret = pd_send_msg(pd, MSG_DR_SWAP, NULL, 0, SOP_MSG);
			if (ret) {
				dev_err(&pd->dev, "Error sending DR Swap\n");
				usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
				break;
			}

			pd->current_state = PE_DRS_SEND_DR_SWAP;
			kick_sm(pd, SENDER_RESPONSE_TIME);
		} else {
			handle_vdm_tx(pd);
		}
		break;
@@ -1892,7 +1938,6 @@ static void usbpd_sm(struct work_struct *w)
			}

			dr_swap(pd);
			kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		} else if (IS_CTRL(rx_msg, MSG_PR_SWAP)) {
			/* lock in current mode */
			set_power_role(pd, pd->current_pr);
@@ -1934,10 +1979,31 @@ static void usbpd_sm(struct work_struct *w)
			}

			vconn_swap(pd);
		} else {
			if (IS_DATA(rx_msg, MSG_VDM))
		} else if (IS_DATA(rx_msg, MSG_VDM)) {
			handle_vdm_rx(pd, rx_msg);
			else
		} else if (pd->send_pr_swap) {
			pd->send_pr_swap = false;
			ret = pd_send_msg(pd, MSG_PR_SWAP, NULL, 0, SOP_MSG);
			if (ret) {
				dev_err(&pd->dev, "Error sending PR Swap\n");
				usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
				break;
			}

			pd->current_state = PE_PRS_SNK_SRC_SEND_SWAP;
			kick_sm(pd, SENDER_RESPONSE_TIME);
		} else if (pd->send_dr_swap) {
			pd->send_dr_swap = false;
			ret = pd_send_msg(pd, MSG_DR_SWAP, NULL, 0, SOP_MSG);
			if (ret) {
				dev_err(&pd->dev, "Error sending DR Swap\n");
				usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
				break;
			}

			pd->current_state = PE_DRS_SEND_DR_SWAP;
			kick_sm(pd, SENDER_RESPONSE_TIME);
		} else {
			handle_vdm_tx(pd);
		}
		break;
@@ -2272,6 +2338,198 @@ static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
	return 0;
}

static enum dual_role_property usbpd_dr_properties[] = {
	DUAL_ROLE_PROP_SUPPORTED_MODES,
	DUAL_ROLE_PROP_MODE,
	DUAL_ROLE_PROP_PR,
	DUAL_ROLE_PROP_DR,
};

static int usbpd_dr_get_property(struct dual_role_phy_instance *dual_role,
		enum dual_role_property prop, unsigned int *val)
{
	struct usbpd *pd = dual_role_get_drvdata(dual_role);

	if (!pd)
		return -ENODEV;

	switch (prop) {
	case DUAL_ROLE_PROP_MODE:
		/* For now associate UFP/DFP with data role only */
		if (pd->current_dr == DR_UFP)
			*val = DUAL_ROLE_PROP_MODE_UFP;
		else if (pd->current_dr == DR_DFP)
			*val = DUAL_ROLE_PROP_MODE_DFP;
		else
			*val = DUAL_ROLE_PROP_MODE_NONE;
		break;
	case DUAL_ROLE_PROP_PR:
		if (pd->current_pr == PR_SRC)
			*val = DUAL_ROLE_PROP_PR_SRC;
		else if (pd->current_pr == PR_SINK)
			*val = DUAL_ROLE_PROP_PR_SNK;
		else
			*val = DUAL_ROLE_PROP_PR_NONE;
		break;
	case DUAL_ROLE_PROP_DR:
		if (pd->current_dr == DR_UFP)
			*val = DUAL_ROLE_PROP_DR_DEVICE;
		else if (pd->current_dr == DR_DFP)
			*val = DUAL_ROLE_PROP_DR_HOST;
		else
			*val = DUAL_ROLE_PROP_DR_NONE;
		break;
	default:
		usbpd_warn(&pd->dev, "unsupported property %d\n", prop);
		return -ENODATA;
	}

	return 0;
}

static int usbpd_dr_set_property(struct dual_role_phy_instance *dual_role,
		enum dual_role_property prop, const unsigned int *val)
{
	struct usbpd *pd = dual_role_get_drvdata(dual_role);
	bool do_swap = false;

	if (!pd)
		return -ENODEV;

	switch (prop) {
	case DUAL_ROLE_PROP_MODE:
		usbpd_dbg(&pd->dev, "Setting mode to %d\n", *val);

		/*
		 * Forces disconnect on CC and re-establishes connection.
		 * This does not use PD-based PR/DR swap
		 */
		if (*val == DUAL_ROLE_PROP_MODE_UFP)
			pd->forced_pr = POWER_SUPPLY_TYPEC_PR_SINK;
		else if (*val == DUAL_ROLE_PROP_MODE_DFP)
			pd->forced_pr = POWER_SUPPLY_TYPEC_PR_SOURCE;

		/* new mode will be applied in disconnect handler */
		set_power_role(pd, PR_NONE);

		/* wait until it takes effect */
		while (pd->forced_pr != POWER_SUPPLY_TYPEC_PR_NONE)
			msleep(20);

		break;

	case DUAL_ROLE_PROP_DR:
		usbpd_dbg(&pd->dev, "Setting data_role to %d\n", *val);

		if (*val == DUAL_ROLE_PROP_DR_HOST) {
			if (pd->current_dr == DR_UFP)
				do_swap = true;
		} else if (*val == DUAL_ROLE_PROP_DR_DEVICE) {
			if (pd->current_dr == DR_DFP)
				do_swap = true;
		} else {
			usbpd_warn(&pd->dev, "setting data_role to 'none' unsupported\n");
			return -ENOTSUPP;
		}

		if (do_swap) {
			if (pd->current_state != PE_SRC_READY &&
					pd->current_state != PE_SNK_READY) {
				usbpd_err(&pd->dev, "data_role swap not allowed: PD not in Ready state\n");
				return -EAGAIN;
			}

			reinit_completion(&pd->swap_complete);
			pd->send_dr_swap = true;
			kick_sm(pd, 0);

			/* wait for operation to complete */
			if (!wait_for_completion_timeout(&pd->swap_complete,
					msecs_to_jiffies(100))) {
				usbpd_err(&pd->dev, "data_role swap timed out\n");
				return -ETIMEDOUT;
			}

			if ((*val == DUAL_ROLE_PROP_DR_HOST &&
					pd->current_dr != DR_DFP) ||
				(*val == DUAL_ROLE_PROP_DR_DEVICE &&
					 pd->current_dr != DR_UFP)) {
				usbpd_err(&pd->dev, "incorrect state (%s) after data_role swap\n",
						pd->current_dr == DR_DFP ?
						"dfp" : "ufp");
				return -EPROTO;
			}
		}

		break;

	case DUAL_ROLE_PROP_PR:
		usbpd_dbg(&pd->dev, "Setting power_role to %d\n", *val);

		if (*val == DUAL_ROLE_PROP_PR_SRC) {
			if (pd->current_pr == PR_SINK)
				do_swap = true;
		} else if (*val == DUAL_ROLE_PROP_PR_SNK) {
			if (pd->current_pr == PR_SRC)
				do_swap = true;
		} else {
			usbpd_warn(&pd->dev, "setting power_role to 'none' unsupported\n");
			return -ENOTSUPP;
		}

		if (do_swap) {
			if (pd->current_state != PE_SRC_READY &&
					pd->current_state != PE_SNK_READY) {
				usbpd_err(&pd->dev, "power_role swap not allowed: PD not in Ready state\n");
				return -EAGAIN;
			}

			reinit_completion(&pd->swap_complete);
			pd->send_pr_swap = true;
			kick_sm(pd, 0);

			/* wait for operation to complete */
			if (!wait_for_completion_timeout(&pd->swap_complete,
					msecs_to_jiffies(2000))) {
				usbpd_err(&pd->dev, "power_role swap timed out\n");
				return -ETIMEDOUT;
			}

			if ((*val == DUAL_ROLE_PROP_PR_SRC &&
					pd->current_pr != PR_SRC) ||
				(*val == DUAL_ROLE_PROP_PR_SNK &&
					 pd->current_pr != PR_SINK)) {
				usbpd_err(&pd->dev, "incorrect state (%s) after power_role swap\n",
						pd->current_pr == PR_SRC ?
						"source" : "sink");
				return -EPROTO;
			}
		}
		break;

	default:
		usbpd_warn(&pd->dev, "unsupported property %d\n", prop);
		return -ENOTSUPP;
	}

	return 0;
}

static int usbpd_dr_prop_writeable(struct dual_role_phy_instance *dual_role,
		enum dual_role_property prop)
{
	switch (prop) {
	case DUAL_ROLE_PROP_MODE:
	case DUAL_ROLE_PROP_DR:
	case DUAL_ROLE_PROP_PR:
		return 1;
	default:
		break;
	}

	return 0;
}

static int usbpd_uevent(struct device *dev, struct kobj_uevent_env *env)
{
	struct usbpd *pd = dev_get_drvdata(dev);
@@ -2754,6 +3012,30 @@ struct usbpd *usbpd_create(struct device *parent)
	pd->vconn_is_external = device_property_present(parent,
					"qcom,vconn-uses-external-source");

	/*
	 * Register the Android dual-role class (/sys/class/dual_role_usb/).
	 * The first instance should be named "otg_default" as that's what
	 * Android expects.
	 * Note this is different than the /sys/class/usbpd/ created above.
	 */
	pd->dr_desc.name = (num_pd_instances == 1) ?
				"otg_default" : dev_name(&pd->dev);
	pd->dr_desc.supported_modes = DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP;
	pd->dr_desc.properties = usbpd_dr_properties;
	pd->dr_desc.num_properties = ARRAY_SIZE(usbpd_dr_properties);
	pd->dr_desc.get_property = usbpd_dr_get_property;
	pd->dr_desc.set_property = usbpd_dr_set_property;
	pd->dr_desc.property_is_writeable = usbpd_dr_prop_writeable;

	pd->dual_role = devm_dual_role_instance_register(&pd->dev,
			&pd->dr_desc);
	if (IS_ERR(pd->dual_role)) {
		usbpd_err(&pd->dev, "could not register dual_role instance\n");
		goto unreg_psy;
	} else {
		pd->dual_role->drv_data = pd;
	}

	pd->current_pr = PR_NONE;
	pd->current_dr = DR_NONE;
	list_add_tail(&pd->instance, &_usbpd);
@@ -2761,6 +3043,7 @@ struct usbpd *usbpd_create(struct device *parent)
	spin_lock_init(&pd->rx_lock);
	INIT_LIST_HEAD(&pd->rx_q);
	INIT_LIST_HEAD(&pd->svid_handlers);
	init_completion(&pd->swap_complete);

	/* force read initial power_supply values */
	psy_changed(&pd->psy_nb, PSY_EVENT_PROP_CHANGED, pd->usb_psy);