Skip to content

Commit

Permalink
usb: typec: tps6598x: Add USB role switching logic
Browse files Browse the repository at this point in the history
This patch adds USB role switch support to the tps6598x.

The setup to initiate or accept a data-role switch is both assumed and
currently required to be baked-into the firmware as described in TI's
document here.

Link: https://www.ti.com/lit/an/slva843a/slva843a.pdf

With this change its possible to use the USB role-switch API to detect and
notify role-switches to downstream consumers.

Tested with a ChipIdea controller on a Qualcomm MSM8939.

Cc: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Nikolaus Voss <nikolaus.voss@loewensteinmedical.de>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Gustavo A. R. Silva <garsilva@embeddedor.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: linux-usb@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
Link: https://lore.kernel.org/r/20200511231930.2825183-2-bryan.odonoghue@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Bryan O'Donoghue authored and Greg Kroah-Hartman committed May 13, 2020
1 parent 0ef1f6e commit 18a6c86
Showing 1 changed file with 50 additions and 7 deletions.
57 changes: 50 additions & 7 deletions drivers/usb/typec/tps6598x.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <linux/regmap.h>
#include <linux/interrupt.h>
#include <linux/usb/typec.h>
#include <linux/usb/role.h>

/* Register offsets */
#define TPS_REG_VID 0x00
Expand Down Expand Up @@ -94,6 +95,7 @@ struct tps6598x {
struct typec_port *port;
struct typec_partner *partner;
struct usb_pd_identity partner_identity;
struct usb_role_switch *role_sw;
};

/*
Expand Down Expand Up @@ -190,6 +192,23 @@ static int tps6598x_read_partner_identity(struct tps6598x *tps)
return 0;
}

static void tps6598x_set_data_role(struct tps6598x *tps,
enum typec_data_role role, bool connected)
{
enum usb_role role_val;

if (role == TYPEC_HOST)
role_val = USB_ROLE_HOST;
else
role_val = USB_ROLE_DEVICE;

if (!connected)
role_val = USB_ROLE_NONE;

usb_role_switch_set_role(tps->role_sw, role_val);
typec_set_data_role(tps->port, role);
}

static int tps6598x_connect(struct tps6598x *tps, u32 status)
{
struct typec_partner_desc desc;
Expand Down Expand Up @@ -220,7 +239,7 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status)
typec_set_pwr_opmode(tps->port, mode);
typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status));
typec_set_data_role(tps->port, TPS_STATUS_DATAROLE(status));
tps6598x_set_data_role(tps, TPS_STATUS_DATAROLE(status), true);

tps->partner = typec_register_partner(tps->port, &desc);
if (IS_ERR(tps->partner))
Expand All @@ -240,7 +259,7 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB);
typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status));
typec_set_data_role(tps->port, TPS_STATUS_DATAROLE(status));
tps6598x_set_data_role(tps, TPS_STATUS_DATAROLE(status), false);
}

static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
Expand Down Expand Up @@ -328,7 +347,7 @@ static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role)
goto out_unlock;
}

typec_set_data_role(tps->port, role);
tps6598x_set_data_role(tps, role, true);

out_unlock:
mutex_unlock(&tps->lock);
Expand Down Expand Up @@ -452,6 +471,7 @@ static int tps6598x_probe(struct i2c_client *client)
{
struct typec_capability typec_cap = { };
struct tps6598x *tps;
struct fwnode_handle *fwnode;
u32 status;
u32 conf;
u32 vid;
Expand Down Expand Up @@ -495,11 +515,22 @@ static int tps6598x_probe(struct i2c_client *client)
if (ret < 0)
return ret;

fwnode = device_get_named_child_node(&client->dev, "connector");
if (IS_ERR(fwnode))
return PTR_ERR(fwnode);

tps->role_sw = fwnode_usb_role_switch_get(fwnode);
if (IS_ERR(tps->role_sw)) {
ret = PTR_ERR(tps->role_sw);
goto err_fwnode_put;
}

typec_cap.revision = USB_TYPEC_REV_1_2;
typec_cap.pd_revision = 0x200;
typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
typec_cap.driver_data = tps;
typec_cap.ops = &tps6598x_ops;
typec_cap.fwnode = fwnode;

switch (TPS_SYSCONF_PORTINFO(conf)) {
case TPS_PORTINFO_SINK_ACCESSORY:
Expand All @@ -525,12 +556,16 @@ static int tps6598x_probe(struct i2c_client *client)
typec_cap.data = TYPEC_PORT_DFP;
break;
default:
return -ENODEV;
ret = -ENODEV;
goto err_role_put;
}

tps->port = typec_register_port(&client->dev, &typec_cap);
if (IS_ERR(tps->port))
return PTR_ERR(tps->port);
if (IS_ERR(tps->port)) {
ret = PTR_ERR(tps->port);
goto err_role_put;
}
fwnode_handle_put(fwnode);

if (status & TPS_STATUS_PLUG_PRESENT) {
ret = tps6598x_connect(tps, status);
Expand All @@ -545,12 +580,19 @@ static int tps6598x_probe(struct i2c_client *client)
if (ret) {
tps6598x_disconnect(tps, 0);
typec_unregister_port(tps->port);
return ret;
goto err_role_put;
}

i2c_set_clientdata(client, tps);

return 0;

err_role_put:
usb_role_switch_put(tps->role_sw);
err_fwnode_put:
fwnode_handle_put(fwnode);

return ret;
}

static int tps6598x_remove(struct i2c_client *client)
Expand All @@ -559,6 +601,7 @@ static int tps6598x_remove(struct i2c_client *client)

tps6598x_disconnect(tps, 0);
typec_unregister_port(tps->port);
usb_role_switch_put(tps->role_sw);

return 0;
}
Expand Down

0 comments on commit 18a6c86

Please sign in to comment.