Loading drivers/usb/pd/Kconfig +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading drivers/usb/pd/policy_engine.c +295 −12 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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" Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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: Loading Loading @@ -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) { Loading Loading @@ -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: Loading Loading @@ -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; } Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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); Loading Loading
drivers/usb/pd/Kconfig +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
drivers/usb/pd/policy_engine.c +295 −12 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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" Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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: Loading Loading @@ -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) { Loading Loading @@ -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: Loading Loading @@ -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; } Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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); Loading