Enable the PD irq and decode the contract. Signed-off-by: Angus Ainslie <angus@xxxxxxxx> --- drivers/usb/typec/tps6598x.c | 386 ++++++++++++++++++++++++++++++++--- 1 file changed, 360 insertions(+), 26 deletions(-) diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c index d5aa58c9da0a..2eb883ff822c 100644 --- a/drivers/usb/typec/tps6598x.c +++ b/drivers/usb/typec/tps6598x.c @@ -30,11 +30,19 @@ #define TPS_REG_STATUS 0x1a #define TPS_REG_SYSTEM_CONF 0x28 #define TPS_REG_CTRL_CONF 0x29 +#define TPS_REG_ACTIVE_CONTRACT_PDO 0x34 +#define TPS_REG_ACTIVE_CONTRACT_RDO 0x35 #define TPS_REG_POWER_STATUS 0x3f #define TPS_REG_RX_IDENTITY_SOP 0x48 +#define TPS_REG_DATA_STATUS 0x5f /* TPS_REG_INT_* bits */ #define TPS_REG_INT_PLUG_EVENT BIT(3) +#define TPS_REG_INT_NEW_CONTRACT_SNK BIT(12) +#define TPS_REG_INT_PWR_STATUS_UPDATE BIT(24) +#define TPS_REG_INT_DATA_STATUS_UPDATE BIT(25) +#define TPS_REG_INT_STATUS_UPDATE BIT(26) +#define TPS_REG_INT_PD_STATUS_UPDATE BIT(27) /* TPS_REG_STATUS bits */ #define TPS_STATUS_PLUG_PRESENT BIT(0) @@ -57,8 +65,16 @@ enum { }; /* TPS_REG_POWER_STATUS bits */ +#define TPS_POWER_STATUS_CONNECTION BIT(0) #define TPS_POWER_STATUS_SOURCESINK BIT(1) +#define TPS_POWER_STATUS_BC12_CON BIT(4) +#define TPS_POWER_STATUS_BC_SDP 0 +#define TPS_POWER_STATUS_BC_CDP 2 +#define TPS_POWER_STATUS_BC_DCP 3 + +#define TPS_POWER_STATUS_BCOPMODE(p) (((p) & GENMASK(6, 5)) >> 5) #define TPS_POWER_STATUS_PWROPMODE(p) (((p) & GENMASK(3, 2)) >> 2) +#define TPS_POWER_STATUS_SRC(p) (!(((p) & BIT(1)) >> 1)) /* TPS_REG_RX_IDENTITY_SOP */ struct tps6598x_rx_identity_reg { @@ -217,21 +233,237 @@ static void tps6598x_set_data_role(struct tps6598x *tps, } #ifdef CONFIG_EXTCON -static void tps6589x_set_extcon_state(struct tps6598x *tps, - u32 status, u16 pwr_status, bool state) + +/* + * the PDO decoding comes from here + * https://www.ti.com/lit/an/slva842/slva842.pdf + */ + +struct contract_terms { + u8 type; + bool dr_power; + bool dr_data; + bool higher_cap; + bool ext_powered; + bool usb_comms; + int max_voltage; + int max_current; + int max_power; +}; + +#define PDO_CONTRACT_TYPE(x) (x >> 30 & 0x3) +#define PDO_CONTRACT_FIXED 0 +#define PDO_CONTRACT_BATTERY 1 +#define PDO_CONTRACT_VARIABLE 2 + +#define PDO_CONTRACT_DR_POWER BIT(29) +#define PDO_CONTRACT_HIGHER_CAP BIT(28) +#define PDO_CONTRACT_EXTERNAL_PWR BIT(27) +#define PDO_CONTRACT_USB_COMMS BIT(26) +#define PDO_CONTRACT_DR_DATA BIT(25) + +#define PDO_CONTRACT_VOLTAGE(x) ((x >> 10 & 0x3ff) * 50) +#define PDO_CONTRACT_MIN_VOLTAGE(x) PDO_CONTRACT_VOLTAGE(x) +#define PDO_CONTRACT_MAX_VOLTAGE(x) ((x >> 20 & 0x3ff) * 50) +#define PDO_CONTRACT_CURRENT(x) ((x & 0x3ff) * 10) +#define PDO_CONTRACT_POWER(x) ((x & 0x3ff) * 250) + +static int tps6598x_decode_pdo_contract(struct tps6598x *tps, u32 contract, + struct contract_terms *terms) +{ + int min_voltage = 5000; // mV + + memset(terms, 0, sizeof(struct contract_terms)); + + dev_dbg(tps->dev, "%s 0x%x\n", __func__, contract); + + switch (PDO_CONTRACT_TYPE(contract)) { + case PDO_CONTRACT_FIXED: + terms->type = PDO_CONTRACT_FIXED; + if (contract & PDO_CONTRACT_DR_POWER) { + dev_dbg(tps->dev, "Dual role power\n"); + terms->dr_power = true; + } + if (contract & PDO_CONTRACT_DR_DATA) { + dev_dbg(tps->dev, "Dual role data\n"); + terms->dr_data = true; + } + if (contract & PDO_CONTRACT_HIGHER_CAP) { + dev_dbg(tps->dev, "Higher capbility\n"); + terms->higher_cap = true; + } + if (contract & PDO_CONTRACT_EXTERNAL_PWR) { + dev_dbg(tps->dev, "Externally powered\n"); + terms->ext_powered = true; + } + if (contract & PDO_CONTRACT_USB_COMMS) { + dev_dbg(tps->dev, "USB communications capable\n"); + terms->usb_comms = true; + } + terms->max_voltage = PDO_CONTRACT_VOLTAGE(contract); + terms->max_current = PDO_CONTRACT_CURRENT(contract); + terms->max_power = (terms->max_voltage * terms->max_current) / 1000; + dev_dbg(tps->dev, "Fixed contract uVMIN:uVMAX:uA:uW %d:%d:%d:%d\n", + min_voltage, terms->max_voltage, terms->max_current, + terms->max_power); + break; + case PDO_CONTRACT_BATTERY: + min_voltage = PDO_CONTRACT_MIN_VOLTAGE(contract); + terms->max_voltage = PDO_CONTRACT_MAX_VOLTAGE(contract); + terms->max_power = PDO_CONTRACT_POWER(contract); + terms->max_current = terms->max_power / (terms->max_voltage / 1000); + dev_dbg(tps->dev, "Battery contract uVMIN:uVMAX:uA:uW %d:%d:%d:%d\n", + min_voltage, terms->max_voltage, terms->max_current, + terms->max_power); + break; + case PDO_CONTRACT_VARIABLE: + min_voltage = PDO_CONTRACT_MIN_VOLTAGE(contract); + terms->max_voltage = PDO_CONTRACT_MAX_VOLTAGE(contract); + terms->max_current = PDO_CONTRACT_CURRENT(contract); + terms->max_power = (terms->max_voltage * terms->max_current) / 1000; + dev_dbg(tps->dev, "Variable contract uVMIN:uVMAX:uA:uW %d:%d:%d:%d\n", + min_voltage, terms->max_voltage, terms->max_current, + terms->max_power); + break; + default: + dev_warn(tps->dev, "Unknown supply\n"); + return -EINVAL; + } + + return 0; +} + +static int tps6598x_pdo_contract(struct tps6598x *tps, u32 contract) { union extcon_property_value val; - int max_current; + struct contract_terms terms; - if (TPS_STATUS_DATAROLE(status)) { - extcon_set_state(tps->edev, EXTCON_USB, false); - extcon_set_state(tps->edev, EXTCON_USB_HOST, state); + tps6598x_decode_pdo_contract(tps, contract, &terms); + + val.intval = terms.max_current; + extcon_set_property(tps->edev, EXTCON_USB, + EXTCON_PROP_USB_VBUS_CURRENT, + val); + extcon_set_property(tps->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_VBUS_CURRENT, + val); + + val.intval = terms.max_voltage; + extcon_set_property(tps->edev, EXTCON_USB, + EXTCON_PROP_USB_VBUS_VOLTAGE, + val); + extcon_set_property(tps->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_VBUS_VOLTAGE, + val); + + return terms.max_current; +} + +#define RDO_CONTRACT_OBJ_POSITION BIT(29) +#define RDO_CONTRACT_CAPABILITY_MISMATCH BIT(27) +#define RDO_CONTRACT_USB_COMMS BIT(26) +#define RDO_CONTRACT_NO_SUSPEND BIT(25) + +#define RDO_CONTRACT_CURRENT(x) ((x >> 10 & 0x3ff) * 10) +#define RDO_CONTRACT_POWER(x) ((x >> 10 & 0x3ff) * 250) +#define RDO_CONTRACT_MAX_CURRENT(x) ((x & 0x3ff) * 10) +#define RDO_CONTRACT_MAX_POWER(x) ((x & 0x3ff) * 250) + +static int tps6598x_decode_rdo_contract(struct tps6598x *tps, u32 contract, bool battery, + struct contract_terms *terms) +{ + memset(terms, 0, sizeof(struct contract_terms)); + + if (contract & RDO_CONTRACT_OBJ_POSITION) + dev_dbg(tps->dev, "Object position\n"); + if (contract & RDO_CONTRACT_CAPABILITY_MISMATCH) + dev_dbg(tps->dev, "Capbility mismatch\n"); + if (contract & RDO_CONTRACT_USB_COMMS) + dev_dbg(tps->dev, "USB communications capable\n"); + if (contract & RDO_CONTRACT_NO_SUSPEND) + dev_dbg(tps->dev, "No USB suspend\n"); + if (battery) { + terms->max_power = RDO_CONTRACT_MAX_POWER(contract); + dev_dbg(tps->dev, "Power %d max power %d\n", RDO_CONTRACT_POWER(contract), + terms->max_power); } else { - extcon_set_state(tps->edev, EXTCON_USB, state); - extcon_set_state(tps->edev, EXTCON_USB_HOST, false); + terms->max_current = RDO_CONTRACT_MAX_CURRENT(contract); /* mA */ + dev_dbg(tps->dev, "Current %d max current %d\n", RDO_CONTRACT_CURRENT(contract), + terms->max_current); } - val.intval = TPS_STATUS_PORTROLE(status); + return 0; +} + +static int tps6598x_rdo_contract(struct tps6598x *tps, u32 contract, bool battery) +{ + union extcon_property_value val; + struct contract_terms terms; + + if (battery) + dev_dbg(tps->dev, "Battery supply\n"); + + tps6598x_decode_rdo_contract(tps, contract, battery, &terms); + + if (terms.max_current > 0) { + val.intval = terms.max_current; + extcon_set_property(tps->edev, EXTCON_USB, + EXTCON_PROP_USB_VBUS_CURRENT, + val); + extcon_set_property(tps->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_VBUS_CURRENT, + val); + + } + + return terms.max_current; +} + +static int tps6598x_set_extcon_pd_state(struct tps6598x *tps, bool state) +{ + u32 pdo_contract, rdo_contract; + int ret, max_current; + + ret = tps6598x_read32(tps, + TPS_REG_ACTIVE_CONTRACT_RDO, + &rdo_contract); + if (ret) { + dev_dbg(tps->dev, + "failed to read RDO contract - try PDO contract\n"); + rdo_contract = 0; + } + + if (rdo_contract != 0) { + max_current = tps6598x_rdo_contract(tps, rdo_contract, false); + } else { + ret = tps6598x_read32(tps, + TPS_REG_ACTIVE_CONTRACT_PDO, + &pdo_contract); + max_current = tps6598x_pdo_contract(tps, pdo_contract); + if (ret || pdo_contract == 0) + dev_err(tps->dev, "failed to read PDO contract\n"); + } + + extcon_set_state(tps->edev, EXTCON_CHG_USB_PD, state); + extcon_sync(tps->edev, EXTCON_CHG_USB_PD); + + return max_current; +} + +static void tps6598x_set_extcon_power_state(struct tps6598x *tps, u32 status, + u16 pwr_status) +{ + int max_current = 100; + union extcon_property_value val; + u8 connected; + + /* USB C power source */ + val.intval = TPS_POWER_STATUS_SRC(pwr_status) && + TPS_STATUS_PORTROLE(status); + + dev_dbg(tps->dev, "Power status 0x%x Source %d\n", pwr_status, + val.intval); + extcon_set_property(tps->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_VBUS_SRC, val); @@ -240,28 +472,55 @@ static void tps6589x_set_extcon_state(struct tps6598x *tps, EXTCON_PROP_USB_VBUS_SRC, val); - extcon_set_state(tps->edev, EXTCON_CHG_USB_SDP, 0); - extcon_set_state(tps->edev, EXTCON_CHG_USB_DCP, 0); - extcon_set_state(tps->edev, EXTCON_CHG_USB_CDP, 0); - extcon_set_state(tps->edev, EXTCON_CHG_USB_SLOW, 0); - extcon_set_state(tps->edev, EXTCON_CHG_USB_FAST, 0); + connected = pwr_status & TPS_POWER_STATUS_CONNECTION; + + if (pwr_status & TPS_POWER_STATUS_BC12_CON) { + switch (TPS_POWER_STATUS_BCOPMODE(pwr_status)) { + case TPS_POWER_STATUS_BC_SDP: + /* TODO: detect USB3 900mA */ + extcon_set_state(tps->edev, + EXTCON_CHG_USB_SLOW, connected); + dev_dbg(tps->dev, "BC1.2 SDP detected\n"); + extcon_set_state(tps->edev, + EXTCON_CHG_USB_SDP, connected); + break; + case TPS_POWER_STATUS_BC_CDP: + dev_dbg(tps->dev, "BC1.2 CDP detected\n"); + extcon_set_state(tps->edev, + EXTCON_CHG_USB_CDP, connected); + break; + case TPS_POWER_STATUS_BC_DCP: + dev_dbg(tps->dev, "BC1.2 DCP detected\n"); + extcon_set_state(tps->edev, + EXTCON_CHG_USB_DCP, connected); + break; + default: + dev_dbg(tps->dev, "BC1.2 unknown - reserved\n"); + break; + + } + } switch (TPS_POWER_STATUS_PWROPMODE(pwr_status)) { case TYPEC_PWR_MODE_USB: - max_current = val.intval = 500; - extcon_set_state(tps->edev, EXTCON_CHG_USB_SDP, state); - extcon_set_state(tps->edev, EXTCON_CHG_USB_SLOW, state); + if (max_current < 500) + max_current = 500; + /* TODO: detect UBS3 900mA */ + extcon_set_state(tps->edev, EXTCON_CHG_USB_SLOW, connected); + extcon_set_state(tps->edev, EXTCON_CHG_USB_SDP, connected); break; case TYPEC_PWR_MODE_1_5A: - max_current = val.intval = 1500; - extcon_set_state(tps->edev, EXTCON_CHG_USB_CDP, state); + if (max_current < 1500) + max_current = 1500; + extcon_set_state(tps->edev, EXTCON_CHG_USB_CDP, connected); break; case TYPEC_PWR_MODE_3_0A: - max_current = val.intval = 3000; - extcon_set_state(tps->edev, EXTCON_CHG_USB_CDP, state); + if (max_current < 3000) + max_current = 3000; break; case TYPEC_PWR_MODE_PD: - max_current = 500; + extcon_set_state(tps->edev, EXTCON_CHG_USB_PD, connected); + max_current = tps6598x_set_extcon_pd_state(tps, connected); break; } @@ -272,6 +531,38 @@ static void tps6589x_set_extcon_state(struct tps6598x *tps, extcon_set_property(tps->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_VBUS_CURRENT, val); +} + +static void tps6598x_set_extcon_state(struct tps6598x *tps, + u32 status, u16 pwr_status, bool state) +{ + union extcon_property_value val; + + if (TPS_STATUS_DATAROLE(status)) { + extcon_set_state(tps->edev, EXTCON_USB, false); + extcon_set_state(tps->edev, EXTCON_USB_HOST, state); + } else { + extcon_set_state(tps->edev, EXTCON_USB, state); + extcon_set_state(tps->edev, EXTCON_USB_HOST, false); + } + + val.intval = TPS_STATUS_PORTROLE(status); + extcon_set_property(tps->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_VBUS, + val); + + extcon_set_property(tps->edev, EXTCON_USB, + EXTCON_PROP_USB_VBUS, + val); + + extcon_set_state(tps->edev, EXTCON_CHG_USB_SDP, 0); + extcon_set_state(tps->edev, EXTCON_CHG_USB_DCP, 0); + extcon_set_state(tps->edev, EXTCON_CHG_USB_CDP, 0); + extcon_set_state(tps->edev, EXTCON_CHG_USB_SLOW, 0); + extcon_set_state(tps->edev, EXTCON_CHG_USB_FAST, 0); + extcon_set_state(tps->edev, EXTCON_CHG_USB_PD, 0); + + tps6598x_set_extcon_power_state(tps, status, pwr_status); extcon_sync(tps->edev, EXTCON_CHG_USB_SDP); extcon_sync(tps->edev, EXTCON_CHG_USB_CDP); @@ -323,9 +614,9 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) if (desc.identity) typec_partner_set_identity(tps->partner); -#ifdef CONFIG_EXTCON - tps6598x_set_extcon_state(tps, status, pwr_status, true); -#endif + dev_dbg(tps->dev, "%s: mode 0x%x pwr_role %d vconn_role %d data_role %d\n", + __func__, mode, TPS_STATUS_PORTROLE(status), + TPS_STATUS_VCONN(status), TPS_STATUS_DATAROLE(status)); return 0; } @@ -494,7 +785,12 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) u64 event1; u64 event2; u32 status; + u32 data_status; + u16 power_status; int ret; +#ifdef CONFIG_EXTCON + bool connected; +#endif mutex_lock(&tps->lock); @@ -514,8 +810,20 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) goto err_clear_ints; } + connected = status & TPS_STATUS_PLUG_PRESENT; + ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &power_status); + if (ret < 0) + goto err_clear_ints; + + ret = tps6598x_read32(tps, TPS_REG_DATA_STATUS, &data_status); + if (ret < 0) + goto err_clear_ints; + + dev_dbg(tps->dev, "Status: 0x%x data: 0x%x power: 0x%x", status, + data_status, power_status); + /* Handle plug insert or removal */ - if ((event1 | event2) & TPS_REG_INT_PLUG_EVENT) { + if ((event1 | event2) & (TPS_REG_INT_PLUG_EVENT)) { if (status & TPS_STATUS_PLUG_PRESENT) { ret = tps6598x_connect(tps, status); if (ret) @@ -526,6 +834,19 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) } } +#ifdef CONFIG_EXTCON + /* updated power or data status register */ + /* power bits sometimes change without triggering an interrupt */ + if ((event1 | event2) & (TPS_REG_INT_STATUS_UPDATE | + TPS_REG_INT_PWR_STATUS_UPDATE | + TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_NEW_CONTRACT_SNK | + TPS_REG_INT_PD_STATUS_UPDATE)) { + dev_dbg(tps->dev, "%s: update extcon\n", __func__); + tps6598x_set_extcon_state(tps, status, power_status, connected); + } +#endif + err_clear_ints: tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event1); tps6598x_write64(tps, TPS_REG_INT_CLEAR2, event2); @@ -591,6 +912,7 @@ static int tps6598x_probe(struct i2c_client *client) u32 conf; u32 vid; int ret; + u64 mask; tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); if (!tps) @@ -735,6 +1057,18 @@ static int tps6598x_probe(struct i2c_client *client) goto err_role_put; } + mask = TPS_REG_INT_NEW_CONTRACT_SNK + | TPS_REG_INT_PLUG_EVENT + | TPS_REG_INT_PWR_STATUS_UPDATE + | TPS_REG_INT_DATA_STATUS_UPDATE + | TPS_REG_INT_STATUS_UPDATE + | TPS_REG_INT_PD_STATUS_UPDATE; + + ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, mask); + ret |= tps6598x_write64(tps, TPS_REG_INT_MASK2, mask); + if (ret < 0) + dev_err(&client->dev, "failed to set irq mask %d\n", ret); + i2c_set_clientdata(client, tps); return 0; -- 2.25.1