usb: typec: ucsi: Register USB Power Delivery Capabilities
UCSI allows the USB PD capabilities to be read with the GET_PDO command. This will register those capabilities and make them visible to user space. Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Co-developed-by: Rajaram Regupathy <rajaram.regupathy@intel.com> Signed-off-by: Rajaram Regupathy <rajaram.regupathy@intel.com> Signed-off-by: Saranya Gopal <saranya.gopal@intel.com> Link: https://lore.kernel.org/r/20230102062108.838423-1-saranya.gopal@intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
9aa1afc8f6
commit
b04e1747fb
|
@ -562,8 +562,9 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
|
|||
}
|
||||
}
|
||||
|
||||
static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
|
||||
u32 *pdos, int offset, int num_pdos)
|
||||
static int ucsi_read_pdos(struct ucsi_connector *con,
|
||||
enum typec_role role, int is_partner,
|
||||
u32 *pdos, int offset, int num_pdos)
|
||||
{
|
||||
struct ucsi *ucsi = con->ucsi;
|
||||
u64 command;
|
||||
|
@ -573,7 +574,7 @@ static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
|
|||
command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner);
|
||||
command |= UCSI_GET_PDOS_PDO_OFFSET(offset);
|
||||
command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1);
|
||||
command |= UCSI_GET_PDOS_SRC_PDOS;
|
||||
command |= is_source(role) ? UCSI_GET_PDOS_SRC_PDOS : 0;
|
||||
ret = ucsi_send_command(ucsi, command, pdos + offset,
|
||||
num_pdos * sizeof(u32));
|
||||
if (ret < 0 && ret != -ETIMEDOUT)
|
||||
|
@ -582,30 +583,43 @@ static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ucsi_get_pdos(struct ucsi_connector *con, enum typec_role role,
|
||||
int is_partner, u32 *pdos)
|
||||
{
|
||||
u8 num_pdos;
|
||||
int ret;
|
||||
|
||||
/* UCSI max payload means only getting at most 4 PDOs at a time */
|
||||
ret = ucsi_read_pdos(con, role, is_partner, pdos, 0, UCSI_MAX_PDOS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */
|
||||
if (num_pdos < UCSI_MAX_PDOS)
|
||||
return num_pdos;
|
||||
|
||||
/* get the remaining PDOs, if any */
|
||||
ret = ucsi_read_pdos(con, role, is_partner, pdos, UCSI_MAX_PDOS,
|
||||
PDO_MAX_OBJECTS - UCSI_MAX_PDOS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret / sizeof(u32) + num_pdos;
|
||||
}
|
||||
|
||||
static int ucsi_get_src_pdos(struct ucsi_connector *con)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* UCSI max payload means only getting at most 4 PDOs at a time */
|
||||
ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS);
|
||||
ret = ucsi_get_pdos(con, TYPEC_SOURCE, 1, con->src_pdos);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */
|
||||
if (con->num_pdos < UCSI_MAX_PDOS)
|
||||
return 0;
|
||||
|
||||
/* get the remaining PDOs, if any */
|
||||
ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS,
|
||||
PDO_MAX_OBJECTS - UCSI_MAX_PDOS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
con->num_pdos += ret / sizeof(u32);
|
||||
con->num_pdos = ret;
|
||||
|
||||
ucsi_port_psy_changed(con);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ucsi_check_altmodes(struct ucsi_connector *con)
|
||||
|
@ -630,6 +644,72 @@ static int ucsi_check_altmodes(struct ucsi_connector *con)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ucsi_register_partner_pdos(struct ucsi_connector *con)
|
||||
{
|
||||
struct usb_power_delivery_desc desc = { con->ucsi->cap.pd_version };
|
||||
struct usb_power_delivery_capabilities_desc caps;
|
||||
struct usb_power_delivery_capabilities *cap;
|
||||
int ret;
|
||||
|
||||
if (con->partner_pd)
|
||||
return 0;
|
||||
|
||||
con->partner_pd = usb_power_delivery_register(NULL, &desc);
|
||||
if (IS_ERR(con->partner_pd))
|
||||
return PTR_ERR(con->partner_pd);
|
||||
|
||||
ret = ucsi_get_pdos(con, TYPEC_SOURCE, 1, caps.pdo);
|
||||
if (ret > 0) {
|
||||
if (ret < PDO_MAX_OBJECTS)
|
||||
caps.pdo[ret] = 0;
|
||||
|
||||
caps.role = TYPEC_SOURCE;
|
||||
cap = usb_power_delivery_register_capabilities(con->partner_pd, &caps);
|
||||
if (IS_ERR(cap))
|
||||
return PTR_ERR(cap);
|
||||
|
||||
con->partner_source_caps = cap;
|
||||
|
||||
ret = typec_partner_set_usb_power_delivery(con->partner, con->partner_pd);
|
||||
if (ret) {
|
||||
usb_power_delivery_unregister_capabilities(con->partner_source_caps);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ucsi_get_pdos(con, TYPEC_SINK, 1, caps.pdo);
|
||||
if (ret > 0) {
|
||||
if (ret < PDO_MAX_OBJECTS)
|
||||
caps.pdo[ret] = 0;
|
||||
|
||||
caps.role = TYPEC_SINK;
|
||||
|
||||
cap = usb_power_delivery_register_capabilities(con->partner_pd, &caps);
|
||||
if (IS_ERR(cap))
|
||||
return PTR_ERR(cap);
|
||||
|
||||
con->partner_sink_caps = cap;
|
||||
|
||||
ret = typec_partner_set_usb_power_delivery(con->partner, con->partner_pd);
|
||||
if (ret) {
|
||||
usb_power_delivery_unregister_capabilities(con->partner_sink_caps);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ucsi_unregister_partner_pdos(struct ucsi_connector *con)
|
||||
{
|
||||
usb_power_delivery_unregister_capabilities(con->partner_sink_caps);
|
||||
con->partner_sink_caps = NULL;
|
||||
usb_power_delivery_unregister_capabilities(con->partner_source_caps);
|
||||
con->partner_source_caps = NULL;
|
||||
usb_power_delivery_unregister(con->partner_pd);
|
||||
con->partner_pd = NULL;
|
||||
}
|
||||
|
||||
static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
|
||||
{
|
||||
switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
|
||||
|
@ -638,6 +718,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
|
|||
typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
|
||||
ucsi_partner_task(con, ucsi_get_src_pdos, 30, 0);
|
||||
ucsi_partner_task(con, ucsi_check_altmodes, 30, 0);
|
||||
ucsi_partner_task(con, ucsi_register_partner_pdos, 1, HZ);
|
||||
break;
|
||||
case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
|
||||
con->rdo = 0;
|
||||
|
@ -696,6 +777,7 @@ static void ucsi_unregister_partner(struct ucsi_connector *con)
|
|||
if (!con->partner)
|
||||
return;
|
||||
|
||||
ucsi_unregister_partner_pdos(con);
|
||||
ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP);
|
||||
typec_unregister_partner(con->partner);
|
||||
con->partner = NULL;
|
||||
|
@ -800,6 +882,10 @@ static void ucsi_handle_connector_change(struct work_struct *work)
|
|||
if (con->status.flags & UCSI_CONSTAT_CONNECTED) {
|
||||
ucsi_register_partner(con);
|
||||
ucsi_partner_task(con, ucsi_check_connection, 1, HZ);
|
||||
|
||||
if (UCSI_CONSTAT_PWR_OPMODE(con->status.flags) ==
|
||||
UCSI_CONSTAT_PWR_OPMODE_PD)
|
||||
ucsi_partner_task(con, ucsi_register_partner_pdos, 1, HZ);
|
||||
} else {
|
||||
ucsi_unregister_partner(con);
|
||||
}
|
||||
|
@ -1036,6 +1122,9 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con)
|
|||
|
||||
static int ucsi_register_port(struct ucsi *ucsi, int index)
|
||||
{
|
||||
struct usb_power_delivery_desc desc = { ucsi->cap.pd_version};
|
||||
struct usb_power_delivery_capabilities_desc pd_caps;
|
||||
struct usb_power_delivery_capabilities *pd_cap;
|
||||
struct ucsi_connector *con = &ucsi->connector[index];
|
||||
struct typec_capability *cap = &con->typec_cap;
|
||||
enum typec_accessory *accessory = cap->accessory;
|
||||
|
@ -1114,6 +1203,41 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
|
|||
goto out;
|
||||
}
|
||||
|
||||
con->pd = usb_power_delivery_register(ucsi->dev, &desc);
|
||||
|
||||
ret = ucsi_get_pdos(con, TYPEC_SOURCE, 0, pd_caps.pdo);
|
||||
if (ret > 0) {
|
||||
if (ret < PDO_MAX_OBJECTS)
|
||||
pd_caps.pdo[ret] = 0;
|
||||
|
||||
pd_caps.role = TYPEC_SOURCE;
|
||||
pd_cap = usb_power_delivery_register_capabilities(con->pd, &pd_caps);
|
||||
if (IS_ERR(pd_cap)) {
|
||||
ret = PTR_ERR(pd_cap);
|
||||
goto out;
|
||||
}
|
||||
|
||||
con->port_source_caps = pd_cap;
|
||||
typec_port_set_usb_power_delivery(con->port, con->pd);
|
||||
}
|
||||
|
||||
memset(&pd_caps, 0, sizeof(pd_caps));
|
||||
ret = ucsi_get_pdos(con, TYPEC_SINK, 0, pd_caps.pdo);
|
||||
if (ret > 0) {
|
||||
if (ret < PDO_MAX_OBJECTS)
|
||||
pd_caps.pdo[ret] = 0;
|
||||
|
||||
pd_caps.role = TYPEC_SINK;
|
||||
pd_cap = usb_power_delivery_register_capabilities(con->pd, &pd_caps);
|
||||
if (IS_ERR(pd_cap)) {
|
||||
ret = PTR_ERR(pd_cap);
|
||||
goto out;
|
||||
}
|
||||
|
||||
con->port_sink_caps = pd_cap;
|
||||
typec_port_set_usb_power_delivery(con->port, con->pd);
|
||||
}
|
||||
|
||||
/* Alternate modes */
|
||||
ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON);
|
||||
if (ret) {
|
||||
|
@ -1152,8 +1276,8 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
|
|||
if (con->status.flags & UCSI_CONSTAT_CONNECTED) {
|
||||
typec_set_pwr_role(con->port,
|
||||
!!(con->status.flags & UCSI_CONSTAT_PWR_DIR));
|
||||
ucsi_pwr_opmode_change(con);
|
||||
ucsi_register_partner(con);
|
||||
ucsi_pwr_opmode_change(con);
|
||||
ucsi_port_psy_changed(con);
|
||||
}
|
||||
|
||||
|
@ -1259,6 +1383,13 @@ err_unregister:
|
|||
ucsi_unregister_port_psy(con);
|
||||
if (con->wq)
|
||||
destroy_workqueue(con->wq);
|
||||
|
||||
usb_power_delivery_unregister_capabilities(con->port_sink_caps);
|
||||
con->port_sink_caps = NULL;
|
||||
usb_power_delivery_unregister_capabilities(con->port_source_caps);
|
||||
con->port_source_caps = NULL;
|
||||
usb_power_delivery_unregister(con->pd);
|
||||
con->pd = NULL;
|
||||
typec_unregister_port(con->port);
|
||||
con->port = NULL;
|
||||
}
|
||||
|
@ -1422,6 +1553,12 @@ void ucsi_unregister(struct ucsi *ucsi)
|
|||
ucsi_unregister_port_psy(&ucsi->connector[i]);
|
||||
if (ucsi->connector[i].wq)
|
||||
destroy_workqueue(ucsi->connector[i].wq);
|
||||
usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_sink_caps);
|
||||
ucsi->connector[i].port_sink_caps = NULL;
|
||||
usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_source_caps);
|
||||
ucsi->connector[i].port_source_caps = NULL;
|
||||
usb_power_delivery_unregister(ucsi->connector[i].pd);
|
||||
ucsi->connector[i].pd = NULL;
|
||||
typec_unregister_port(ucsi->connector[i].port);
|
||||
}
|
||||
|
||||
|
|
|
@ -339,6 +339,14 @@ struct ucsi_connector {
|
|||
u32 src_pdos[PDO_MAX_OBJECTS];
|
||||
int num_pdos;
|
||||
|
||||
/* USB PD objects */
|
||||
struct usb_power_delivery *pd;
|
||||
struct usb_power_delivery_capabilities *port_source_caps;
|
||||
struct usb_power_delivery_capabilities *port_sink_caps;
|
||||
struct usb_power_delivery *partner_pd;
|
||||
struct usb_power_delivery_capabilities *partner_source_caps;
|
||||
struct usb_power_delivery_capabilities *partner_sink_caps;
|
||||
|
||||
struct usb_role_switch *usb_role_sw;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue