usb: pd: convert to using Type-C class

The dual_role_usb class is deprecated and no longer carried on this
kernel. Switch PD policy engine to implement an instance of the USB
Type-C class instead as it offers equivalent functionality and more.
A port partner is registered/unregistered on each attach/detach.
Userspace clients will need to be updated to handle this switch.

Change-Id: I7a6592431a95b7b44b2c118d4876a7cf06b3cb5b
Signed-off-by: Jack Pham <jackp@codeaurora.org>
This commit is contained in:
Jack Pham 2018-12-07 00:24:48 -08:00
parent 774cfb4032
commit 7f7c6cae54
2 changed files with 142 additions and 182 deletions

View file

@ -10,7 +10,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
depends on TYPEC
select USB_PD
help
Say Y here to enable USB PD protocol and policy engine.

View file

@ -18,7 +18,7 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/extcon-provider.h>
#include <linux/usb/class-dual-role.h>
#include <linux/usb/typec.h>
#include <linux/usb/usbpd.h>
#include "usbpd.h"
@ -428,9 +428,13 @@ struct usbpd {
struct completion tx_chunk_request;
u8 next_tx_chunk;
struct typec_capability typec_caps;
struct typec_port *typec_port;
struct typec_partner *partner;
struct typec_partner_desc partner_desc;
struct usb_pd_identity partner_identity;
struct mutex swap_lock;
struct dual_role_phy_instance *dual_role;
struct dual_role_phy_desc dr_desc;
bool send_pr_swap;
bool send_dr_swap;
@ -564,7 +568,17 @@ static void start_usb_peripheral_work(struct work_struct *w)
pd->current_pr = PR_SINK;
pd->current_dr = DR_UFP;
start_usb_peripheral(pd);
dual_role_instance_changed(pd->dual_role);
typec_set_data_role(pd->typec_port, TYPEC_DEVICE);
typec_set_pwr_role(pd->typec_port, TYPEC_SINK);
typec_set_pwr_opmode(pd->typec_port,
pd->typec_mode - POWER_SUPPLY_TYPEC_SOURCE_DEFAULT);
if (!pd->partner) {
memset(&pd->partner_identity, 0, sizeof(pd->partner_identity));
pd->partner_desc.usb_pd = false;
pd->partner_desc.accessory = TYPEC_ACCESSORY_NONE;
pd->partner = typec_register_partner(pd->typec_port,
&pd->partner_desc);
}
}
/**
@ -1813,6 +1827,7 @@ static void dr_swap(struct usbpd *pd)
stop_usb_host(pd);
if (pd->peer_usb_comm)
start_usb_peripheral(pd);
typec_set_data_role(pd->typec_port, TYPEC_DEVICE);
} else if (pd->current_dr == DR_UFP) {
pd->current_dr = DR_DFP;
pd_phy_update_roles(pd->current_dr, pd->current_pr);
@ -1821,14 +1836,14 @@ static void dr_swap(struct usbpd *pd)
if (pd->peer_usb_comm)
start_usb_host(pd, true);
typec_set_data_role(pd->typec_port, TYPEC_HOST);
/* ensure host is started before allowing DP */
extcon_blocking_sync(pd->extcon, EXTCON_USB_HOST, 0);
usbpd_send_svdm(pd, USBPD_SID, USBPD_SVDM_DISCOVER_IDENTITY,
SVDM_CMD_TYPE_INITIATOR, 0, NULL, 0);
}
dual_role_instance_changed(pd->dual_role);
}
@ -2061,9 +2076,18 @@ static void enter_state_src_startup(struct usbpd *pd)
pd->current_dr = DR_DFP;
start_usb_host(pd, true);
pd->ss_lane_svid = 0x0;
typec_set_data_role(pd->typec_port, TYPEC_HOST);
}
dual_role_instance_changed(pd->dual_role);
typec_set_pwr_role(pd->typec_port, TYPEC_SOURCE);
if (!pd->partner) {
typec_set_pwr_opmode(pd->typec_port, TYPEC_PWR_MODE_1_5A);
memset(&pd->partner_identity, 0, sizeof(pd->partner_identity));
pd->partner_desc.usb_pd = false;
pd->partner_desc.accessory = TYPEC_ACCESSORY_NONE;
pd->partner = typec_register_partner(pd->typec_port,
&pd->partner_desc);
}
val.intval = 1; /* Rp-1.5A; SinkTxNG for PD 3.0 */
power_supply_set_property(pd->usb_psy,
@ -2290,7 +2314,7 @@ static void enter_state_src_ready(struct usbpd *pd)
kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
complete(&pd->is_ready);
dual_role_instance_changed(pd->dual_role);
typec_set_pwr_opmode(pd->typec_port, TYPEC_PWR_MODE_PD);
}
static void handle_state_src_ready(struct usbpd *pd, struct rx_msg *rx_msg)
@ -2497,9 +2521,19 @@ static void enter_state_snk_startup(struct usbpd *pd)
val.intval == POWER_SUPPLY_TYPE_USB_CDP)
start_usb_peripheral(pd);
}
typec_set_data_role(pd->typec_port, TYPEC_DEVICE);
}
dual_role_instance_changed(pd->dual_role);
typec_set_pwr_role(pd->typec_port, TYPEC_SINK);
if (!pd->partner) {
typec_set_pwr_opmode(pd->typec_port,
pd->typec_mode - POWER_SUPPLY_TYPEC_SOURCE_DEFAULT);
memset(&pd->partner_identity, 0, sizeof(pd->partner_identity));
pd->partner_desc.usb_pd = false;
pd->partner_desc.accessory = TYPEC_ACCESSORY_NONE;
pd->partner = typec_register_partner(pd->typec_port,
&pd->partner_desc);
}
if (usbpd_startup_common(pd, &phy_params))
return;
@ -2744,7 +2778,7 @@ static void enter_state_snk_ready(struct usbpd *pd)
kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
complete(&pd->is_ready);
dual_role_instance_changed(pd->dual_role);
typec_set_pwr_opmode(pd->typec_port, TYPEC_PWR_MODE_PD);
}
static bool handle_ctrl_snk_ready(struct usbpd *pd, struct rx_msg *rx_msg)
@ -3401,7 +3435,8 @@ static void handle_disconnect(struct usbpd *pd)
pd->current_state = PE_UNKNOWN;
kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
dual_role_instance_changed(pd->dual_role);
typec_unregister_partner(pd->partner);
pd->partner = NULL;
if (pd->has_dp) {
pd->has_dp = false;
@ -3704,55 +3739,6 @@ 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_do_swap(struct usbpd *pd, bool dr_swap,
unsigned int timeout_ms)
{
@ -3795,127 +3781,106 @@ static int usbpd_do_swap(struct usbpd *pd, bool dr_swap,
return 0;
}
static int usbpd_dr_set_property(struct dual_role_phy_instance *dual_role,
enum dual_role_property prop, const unsigned int *val)
static int usbpd_typec_dr_set(const struct typec_capability *cap,
enum typec_data_role role)
{
struct usbpd *pd = dual_role_get_drvdata(dual_role);
struct usbpd *pd = container_of(cap, struct usbpd, typec_caps);
bool do_swap = false;
int ret;
if (!pd)
return -ENODEV;
usbpd_dbg(&pd->dev, "Setting data role to %d\n", role);
switch (prop) {
case DUAL_ROLE_PROP_MODE:
usbpd_dbg(&pd->dev, "Setting mode to %d\n", *val);
if (role == TYPEC_HOST) {
if (pd->current_dr == DR_UFP)
do_swap = true;
} else if (role == TYPEC_DEVICE) {
if (pd->current_dr == DR_DFP)
do_swap = true;
} else {
usbpd_warn(&pd->dev, "invalid role\n");
return -EINVAL;
}
/*
* 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;
if (do_swap) {
ret = usbpd_do_swap(pd, true, 100);
if (ret)
return ret;
/* 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 ((role == TYPEC_HOST && pd->current_dr != DR_DFP) ||
(role == TYPEC_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;
}
if (do_swap) {
ret = usbpd_do_swap(pd, true, 100);
if (ret)
return ret;
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) {
ret = usbpd_do_swap(pd, false, 2000);
if (ret)
return ret;
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)
static int usbpd_typec_pr_set(const struct typec_capability *cap,
enum typec_role role)
{
struct usbpd *pd = dual_role_get_drvdata(dual_role);
struct usbpd *pd = container_of(cap, struct usbpd, typec_caps);
bool do_swap = false;
int ret;
switch (prop) {
case DUAL_ROLE_PROP_MODE:
return 1;
case DUAL_ROLE_PROP_DR:
case DUAL_ROLE_PROP_PR:
if (pd)
return pd->current_state == PE_SNK_READY ||
pd->current_state == PE_SRC_READY;
break;
default:
break;
usbpd_dbg(&pd->dev, "Setting power role to %d\n", role);
if (role == TYPEC_SOURCE) {
if (pd->current_pr == PR_SINK)
do_swap = true;
} else if (role == TYPEC_SINK) {
if (pd->current_pr == PR_SRC)
do_swap = true;
} else {
usbpd_warn(&pd->dev, "invalid role\n");
return -EINVAL;
}
if (do_swap) {
ret = usbpd_do_swap(pd, false, 2000);
if (ret)
return ret;
if ((role == TYPEC_SOURCE && pd->current_pr != PR_SRC) ||
(role == TYPEC_SINK && 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;
}
}
return 0;
}
static int usbpd_typec_port_type_set(const struct typec_capability *cap,
enum typec_port_type type)
{
struct usbpd *pd = container_of(cap, struct usbpd, typec_caps);
usbpd_dbg(&pd->dev, "Setting mode to %d\n", type);
if (type == TYPEC_PORT_DRP)
return 0;
/*
* Forces disconnect on CC and re-establishes connection.
* This does not use PD-based PR/DR swap
*/
if (type == TYPEC_PORT_SNK)
pd->forced_pr = POWER_SUPPLY_TYPEC_PR_SINK;
else if (type == TYPEC_PORT_SRC)
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);
return 0;
}
@ -4707,27 +4672,22 @@ struct usbpd *usbpd_create(struct device *parent)
}
/*
* 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.
* Register a Type-C class instance (/sys/class/typec/portX).
* 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->typec_caps.type = TYPEC_PORT_DRP;
pd->typec_caps.data = TYPEC_PORT_DRD;
pd->typec_caps.revision = 0x0130;
pd->typec_caps.pd_revision = 0x0300;
pd->typec_caps.dr_set = usbpd_typec_dr_set;
pd->typec_caps.pr_set = usbpd_typec_pr_set;
pd->typec_caps.port_type_set = usbpd_typec_port_type_set;
pd->partner_desc.identity = &pd->partner_identity;
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");
pd->typec_port = typec_register_port(parent, &pd->typec_caps);
if (IS_ERR(pd->typec_port)) {
usbpd_err(&pd->dev, "could not register typec port\n");
goto put_psy;
} else {
pd->dual_role->drv_data = pd;
}
pd->current_pr = PR_NONE;