From adc3327467c035509939246203cd5a8a3dc85a42 Mon Sep 17 00:00:00 2001 From: Thomas Makin Date: Fri, 21 Aug 2026 11:27:59 -0500 Subject: [PATCH] bm92t: convert to typec fwk note: ai --- drivers/usb/typec/Kconfig | 1 + drivers/usb/typec/bm92txx.c | 558 ++++++++++++++++++------------------ 2 files changed, 280 insertions(+), 279 deletions(-) diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index a9160d6e36bc..5176562ea8b0 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -68,6 +68,7 @@ config TYPEC_BM92TXX tristate "Rohm Semiconductor BM92TXX USB Type-C Support" depends on I2C depends on USB_ROLE_SWITCH + depends on DRM help Say Y or M here if your system has Rohm BM92TXX USB Type-C and Power Delivery controller. diff --git a/drivers/usb/typec/bm92txx.c b/drivers/usb/typec/bm92txx.c index f9517a53e63e..24489316fd5f 100644 --- a/drivers/usb/typec/bm92txx.c +++ b/drivers/usb/typec/bm92txx.c @@ -28,13 +28,13 @@ #include #include #include -#include #include +#include +#include +#include #include #include -#ifdef CONFIG_SWITCH -#include -#endif +#include /* Registers */ @@ -258,16 +258,6 @@ #define STOP_HPD_CMD 0x3939 /* Stop handling HPD */ #define STOP_HPD_EXIT_DP_CMD 0x3B3B /* Stop handling HPD and exit DP Alt mode */ -/* General defines */ -#define PDO_TYPE_FIXED 0 -#define PDO_TYPE_BATT 1 -#define PDO_TYPE_VAR 2 - -#define PDO_INFO_DR_DATA (1 << 5) -#define PDO_INFO_USB_COMM (1 << 6) -#define PDO_INFO_EXT_POWER (1 << 7) -#define PDO_INFO_HP_CAP (1 << 8) -#define PDO_INFO_DR_POWER (1 << 9) /* VDM/VDO */ #define VDM_CMD_RESERVED 0x00 @@ -286,14 +276,6 @@ #define VDM_UNSTRUCTURED 0x00 #define VDM_STRUCTURED 0x80 -/* VDM Discover ID */ -#define VDO_ID_TYPE_NONE 0 -#define VDO_ID_TYPE_PD_HUB 1 -#define VDO_ID_TYPE_PD_PERIPH 2 -#define VDO_ID_TYPE_PASS_CBL 3 -#define VDO_ID_TYPE_ACTI_CBL 4 -#define VDO_ID_TYPE_ALTERNATE 5 - /* VDM Discover Mode Caps [From device (UFP_U) to host (DFP_U)] */ #define VDO_DP_UFP_D BIT(0) /* DisplayPort Sink */ #define VDO_DP_DFP_D BIT(1) /* DisplayPort Source */ @@ -394,47 +376,6 @@ enum bm92t_state_type { DOCK_CONFIG_HANDLED }; -struct __packed pd_object { - unsigned int amp:10; - unsigned int volt:10; - unsigned int info:10; - unsigned int type:2; -}; - -struct __packed rd_object { - unsigned int max_amp:10; - unsigned int op_amp:10; - unsigned int info:6; - unsigned int usb_comms:1; - unsigned int mismatch:1; - unsigned int obj_no:4; -}; - -struct __packed vd_object { - unsigned int vid:16; - unsigned int rsvd:10; - unsigned int modal:1; - unsigned int type:3; - unsigned int ufp:1; - unsigned int dfp:1; - - unsigned int xid; - - unsigned int bcd:16; - unsigned int pid:16; - - unsigned int prod_type; -}; - -struct bm92t_device { - int pdo_no; - unsigned int charging_limit; - bool drd_support; - bool is_nintendo_dock; - struct pd_object pdo; - struct vd_object vdo; -}; - struct bm92t_platform_data { int vconn_en_gpio; @@ -452,6 +393,14 @@ struct bm92t_platform_data { unsigned int pd_15v_current_limit; }; +struct bm92t_device { + int pdo_no; + unsigned int charging_limit; + bool drd_support; + bool is_nintendo_dock; + u32 pdo; +}; + struct bm92t_info { struct i2c_client *i2c_client; struct bm92t_platform_data *pdata; @@ -462,7 +411,10 @@ struct bm92t_info { int state; bool first_init; - struct extcon_dev *edev; + struct typec_port *port; + struct typec_partner *partner; + struct usb_pd_identity partner_identity; + struct delayed_work oneshot_work; struct delayed_work power_work; @@ -480,9 +432,7 @@ struct bm92t_info { struct bm92t_device cable; struct usb_role_switch *role_sw; -#ifdef CONFIG_SWITCH - struct switch_dev pd_switch; -#endif + struct fwnode_handle *connector_fwnode; }; static const char * const states[] = { @@ -514,28 +464,6 @@ static const char * const states[] = { "DOCK_CONFIG_HANDLED" }; -static const unsigned int bm92t_extcon_cable[] = { - EXTCON_USB_HOST, /* Id */ - EXTCON_USB, /* Vbus */ - EXTCON_CHG_USB_PD, /* USB-PD */ - EXTCON_DISP_DP, /* DisplayPort. Handled by HPD so not used. */ - EXTCON_NONE -}; - -struct bm92t_extcon_cables { - unsigned int cable; - char *name; -}; - -static const struct bm92t_extcon_cables bm92t_extcon_cable_names[] = { - { EXTCON_USB_HOST, "USB HOST"}, - { EXTCON_USB, "USB"}, - { EXTCON_CHG_USB_PD, "USB-PD"}, - { EXTCON_DISP_DP, "DisplayPort"}, - { EXTCON_NONE, "None"}, - { -1, "Unknown"} -}; - /* charger current input limits */ static const unsigned int current_input_limits[] = { 100, 150, 500, 900, 1200, 1500, 2000, 3000 @@ -594,7 +522,7 @@ static int bm92t_write_reg(struct bm92t_info *info, xfer_msg[0].buf = buf; dev_dbg(&info->i2c_client->dev, - "write reg cmd = 0x%02X len = %d\n", buf[0], len); + "write reg cmd = 0x%02X len = %d\n", buf[0], len); return (i2c_transfer(info->i2c_client->adapter, xfer_msg, 1) != 1); } @@ -642,8 +570,8 @@ static int bm92t_send_cmd(struct bm92t_info *info, unsigned short *cmd) ret = bm92t_write_reg(info, msg, 3); dev_dbg(&info->i2c_client->dev, - "Sent cmd 0x%02X 0x%02X return value %d\n", - _cmd[0], _cmd[1], ret); + "Sent cmd 0x%02X 0x%02X return value %d\n", + _cmd[0], _cmd[1], ret); return ret; } @@ -717,7 +645,6 @@ static int bm92t_handle_dp_config_and_hpd(struct bm92t_info *info) unsigned char cfg[6] = {OUTGOING_VDM_REG, 0x04, VDO_DP_SUPPORT | VDO_DP_U_UFP_D, 0x00, 0x00, 0x00}; - union extcon_property_value prop; /* Set primary pin assignment by lanes supported */ unsigned char pin_cfg = (info->pdata->dp_lanes == 4) ? @@ -726,7 +653,7 @@ static int bm92t_handle_dp_config_and_hpd(struct bm92t_info *info) /* Read DisplayPort Capabilities */ err = bm92t_read_reg(info, INCOMING_VDM_REG, msg, sizeof(msg)); dev_info(&info->i2c_client->dev, - "DP Pin assignments: %02X %02X\n", msg[2], msg[3]); + "DP Pin assignments: %02X %02X\n", msg[2], msg[3]); /* Prepare UFP_U as UFP_D configuration */ for (i = 0; i < 2; i++) { @@ -775,9 +702,8 @@ static int bm92t_handle_dp_config_and_hpd(struct bm92t_info *info) /* Configure DP Alt mode and start handling HPD */ bm92t_send_cmd(info, &cmd); - prop.intval = valid_lanes > info->pdata->dp_lanes ? - info->pdata->dp_lanes : valid_lanes; - + info->cable.charging_limit = valid_lanes > info->pdata->dp_lanes ? + info->pdata->dp_lanes : valid_lanes; } else { dev_warn(&info->i2c_client->dev, "No compatible DP Pin assignment (%d: %02X %02X %02X)!\n", @@ -934,56 +860,20 @@ ret: return err; } -static char *bm92t_extcon_cable_get_name(const unsigned int cable) +static void bm92t_set_data_role(struct bm92t_info *info, + enum typec_data_role role, bool attached) { - int i, count; + enum usb_role usb_role = USB_ROLE_NONE; - count = ARRAY_SIZE(bm92t_extcon_cable_names); - - for (i = 0; i < count; i++) { - if (bm92t_extcon_cable_names[i].cable == cable) - return bm92t_extcon_cable_names[i].name; + if (attached) { + if (role == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; } - return bm92t_extcon_cable_names[count - 1].name; -} - -static void bm92t_usb_role_update(struct bm92t_info *info) -{ - enum usb_role role; - - /* Consider host mode highest priority */ - if (extcon_get_state(info->edev, EXTCON_USB_HOST)) - role = USB_ROLE_HOST; - else if (extcon_get_state(info->edev, EXTCON_USB)) - role = USB_ROLE_DEVICE; - else - role = USB_ROLE_NONE; - - if (usb_role_switch_get_role(info->role_sw) != role) - usb_role_switch_set_role(info->role_sw, role); -} - -static void bm92t_extcon_cable_update(struct bm92t_info *info, - const unsigned int cable, bool is_attached) -{ - int state = extcon_get_state(info->edev, cable); - - if (state != is_attached) { - dev_info(&info->i2c_client->dev, "extcon cable (%02d: %s) %s\n", - cable, bm92t_extcon_cable_get_name(cable), - is_attached ? "attached" : "detached"); - extcon_set_state(info->edev, cable, is_attached); - } - - switch (cable) { - case EXTCON_USB: - case EXTCON_USB_HOST: - bm92t_usb_role_update(info); - break; - default: - break; - } + usb_role_switch_set_role(info->role_sw, usb_role); + typec_set_data_role(info->port, role); } static inline void bm92t_state_machine(struct bm92t_info *info, int state) @@ -1040,15 +930,9 @@ static void bm92t_power_work(struct work_struct *work) bm92t_set_current_limit(info, info->cable.charging_limit * 1000u); info->pd_charging_enabled = true; - - extcon_set_state(info->edev, EXTCON_CHG_USB_PD, true); - -#ifdef CONFIG_SWITCH - switch_set_state(&info->pd_switch, 1); -#endif } -static void bm92t_extcon_cable_set_init_state(struct work_struct *work) +static void bm92t_port_set_init_state(struct work_struct *work) { struct bm92t_info *info = container_of(to_delayed_work(work), struct bm92t_info, oneshot_work); @@ -1069,13 +953,8 @@ static void bm92t_extcon_cable_set_init_state(struct work_struct *work) /* Enable DisplayPort Alerts */ bm92t_set_dp_alerts(info, info->pdata->dp_alerts_enable); - /* Initialize states for extcons */ - bm92t_extcon_cable_update(info, EXTCON_DISP_DP, false); - bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); - bm92t_extcon_cable_update(info, EXTCON_USB, false); - dev_info(&info->i2c_client->dev, - "extcon cable is set to init state\n"); + "port is set to init state\n"); msleep(100); /* Wait a bit */ queue_work(info->event_wq, &info->work); @@ -1086,16 +965,16 @@ static bool bm92t_check_pdo(struct bm92t_info *info) int i, err, pdos_no; struct device *dev; unsigned char pdos[29]; - struct pd_object pdo[7]; + u32 pdo[7]; unsigned int prev_wattage = 0; - unsigned int amperage, voltage, wattage, type; + unsigned int amperage, voltage, wattage; dev = &info->i2c_client->dev; memset(&info->cable, 0, sizeof(struct bm92t_device)); err = bm92t_read_reg(info, READ_PDOS_SRC_REG, pdos, sizeof(pdos)); - pdos_no = pdos[0] / sizeof(struct pd_object); + pdos_no = pdos[0] / sizeof(u32); /* Check if errors or no pdo received */ if (err || !pdos_no) @@ -1105,27 +984,27 @@ static bool bm92t_check_pdo(struct bm92t_info *info) memcpy(pdo, pdos + 1, pdos[0]); for (i = 0; i < pdos_no; ++i) { dev_info(dev, "PDO %d: %4dmA %5dmV %s\n", - i + 1, pdo[i].amp * 10, pdo[i].volt * 50, - (pdo[i].info & PDO_INFO_DR_DATA) ? "DRD" : "No DRD"); + i + 1, pdo_fixed_current(pdo[i]), + pdo_fixed_voltage(pdo[i]), + (pdo[i] & PDO_FIXED_DATA_SWAP) ? "DRD" : "No DRD"); } - if (pdo[0].info & PDO_INFO_DR_DATA) + if (pdo[0] & PDO_FIXED_DATA_SWAP) info->cable.drd_support = true; /* Check for dock mode */ if (!info->pdata->dock_power_limit_disable && pdos_no == 2 && - (pdo[0].volt * 50) == DOCK_ID_VOLTAGE_MV && - (pdo[0].amp * 10) == DOCK_ID_CURRENT_MA) { + pdo_fixed_voltage(pdo[0]) == DOCK_ID_VOLTAGE_MV && + pdo_fixed_current(pdo[0]) == DOCK_ID_CURRENT_MA) { /* Only accept 15V, >= 2.6A for dock mode. */ - if (pdo[1].type == PDO_TYPE_FIXED && - (pdo[1].volt * 50) == DOCK_INPUT_VOLTAGE_MV && - (pdo[1].amp * 10) >= DOCK_INPUT_CURRENT_LIMIT_MIN_MA && - (pdo[1].amp * 10) <= DOCK_INPUT_CURRENT_LIMIT_MAX_MA) { + if (pdo_type(pdo[1]) == PDO_TYPE_FIXED && + pdo_fixed_voltage(pdo[1]) == DOCK_INPUT_VOLTAGE_MV && + pdo_fixed_current(pdo[1]) >= DOCK_INPUT_CURRENT_LIMIT_MIN_MA && + pdo_fixed_current(pdo[1]) <= DOCK_INPUT_CURRENT_LIMIT_MAX_MA) { dev_info(dev, "Device in Nintendo mode\n"); info->cable.pdo_no = 2; - memcpy(&info->cable.pdo, &pdo[1], - sizeof(struct pd_object)); + info->cable.pdo = pdo[1]; return 1; } @@ -1135,9 +1014,8 @@ static bool bm92t_check_pdo(struct bm92t_info *info) /* Not in dock mode. Check for max possible wattage */ for (i = 0; i < pdos_no; ++i) { - type = pdo[i].type; - voltage = pdo[i].volt * 50; - amperage = pdo[i].amp * 10; + voltage = pdo_fixed_voltage(pdo[i]); + amperage = pdo_fixed_current(pdo[i]); wattage = voltage * amperage; /* Only USB-PD defined voltages with max 15V. */ @@ -1152,17 +1030,16 @@ static bool bm92t_check_pdo(struct bm92t_info *info) } /* Only accept <= 3A and select max wattage with max voltage. */ - if (type == PDO_TYPE_FIXED && + if (pdo_type(pdo[i]) == PDO_TYPE_FIXED && amperage >= PD_INPUT_CURRENT_LIMIT_MIN_MA && amperage <= PD_INPUT_CURRENT_LIMIT_MAX_MA) { if (wattage > prev_wattage || - (voltage > (info->cable.pdo.volt * 50) && + (voltage > pdo_fixed_voltage(info->cable.pdo) && wattage && wattage == prev_wattage) || (!info->cable.pdo_no && !amperage && voltage == 5000)) { prev_wattage = wattage; info->cable.pdo_no = i + 1; - memcpy(&info->cable.pdo, &pdo[i], - sizeof(struct pd_object)); + info->cable.pdo = pdo[i]; } } } @@ -1177,26 +1054,25 @@ static bool bm92t_check_pdo(struct bm92t_info *info) static int bm92t_send_rdo(struct bm92t_info *info) { - struct rd_object rdo = { 0 }; + u32 rdo; unsigned char msg[6] = { SET_RDO_REG, 0x04, 0x00, 0x00, 0x00, 0x00}; unsigned short cmd = SEND_RDO_CMD; /* Calculate operating current */ - bm92t_calculate_current_limit(info, info->cable.pdo.volt * 50, - info->cable.pdo.amp * 10); + bm92t_calculate_current_limit(info, pdo_fixed_voltage(info->cable.pdo), + pdo_fixed_current(info->cable.pdo)); dev_info(&info->i2c_client->dev, - "Requesting %d: min %dmA, max %4dmA, %5dmV\n", - info->cable.pdo_no, info->cable.charging_limit, - info->cable.pdo.amp * 10, - info->cable.pdo.volt * 50); + "Requesting %d: min %dmA, max %4dmA, %5dmV\n", + info->cable.pdo_no, info->cable.charging_limit, + pdo_fixed_current(info->cable.pdo), + pdo_fixed_voltage(info->cable.pdo)); - rdo.usb_comms = 1; - rdo.obj_no = info->cable.pdo_no; - rdo.max_amp = info->cable.pdo.amp; - rdo.op_amp = info->cable.charging_limit / 10; + rdo = RDO_FIXED(info->cable.pdo_no, info->cable.charging_limit, + pdo_fixed_current(info->cable.pdo), + RDO_USB_COMM | RDO_NO_SUSPEND); - memcpy(&msg[2], &rdo, sizeof(struct rd_object)); + memcpy(&msg[2], &rdo, sizeof(u32)); if (bm92t_write_reg(info, msg, sizeof(msg))) { dev_err(&info->i2c_client->dev, "Send RDO failure!\n"); @@ -1281,36 +1157,84 @@ static void bm92t_usbhub_dp_sleep(struct bm92t_info *info, bool sleep) } static void bm92t_print_dp_dev_info(struct device *dev, - struct vd_object *vdo) + struct usb_pd_identity *id) { - dev_info(dev, "Connected PD device:\n"); - dev_info(dev, "VID: %04X, PID: %04X\n", vdo->vid, vdo->pid); + u32 idh = id->id_header; - switch (vdo->type) { - case VDO_ID_TYPE_NONE: + dev_info(dev, "Connected PD device:\n"); + dev_info(dev, "VID: %04X, PID: %04X\n", + PD_IDH_VID(idh), PD_PRODUCT_PID(id->product)); + + switch (PD_IDH_PTYPE(idh)) { + case IDH_PTYPE_UNDEF: dev_info(dev, "Type: Undefined\n"); break; - case VDO_ID_TYPE_PD_HUB: + case IDH_PTYPE_HUB: dev_info(dev, "Type: PD HUB\n"); break; - case VDO_ID_TYPE_PD_PERIPH: + case IDH_PTYPE_PERIPH: dev_info(dev, "Type: PD Peripheral\n"); break; - case VDO_ID_TYPE_PASS_CBL: + case IDH_PTYPE_PCABLE: dev_info(dev, "Type: Passive Cable\n"); break; - case VDO_ID_TYPE_ACTI_CBL: + case IDH_PTYPE_ACABLE: dev_info(dev, "Type: Active Cable\n"); break; - case VDO_ID_TYPE_ALTERNATE: + case IDH_PTYPE_AMA: dev_info(dev, "Type: Alternate Mode Adapter\n"); break; default: - dev_info(dev, "Type: Unknown (%d)\n", vdo->type); + dev_info(dev, "Type: Unknown (%d)\n", PD_IDH_PTYPE(idh)); break; } } +static void bm92t_connect(struct bm92t_info *info, unsigned short status1_data) +{ + struct typec_partner_desc desc; + + if (info->partner) + return; + + memset(&info->partner_identity, 0, sizeof(info->partner_identity)); + + desc.usb_pd = true; + desc.accessory = TYPEC_ACCESSORY_NONE; + desc.identity = &info->partner_identity; + + info->partner = typec_register_partner(info->port, &desc); + if (IS_ERR(info->partner)) { + dev_err(&info->i2c_client->dev, "failed to register partner\n"); + info->partner = NULL; + return; + } + + typec_set_pwr_opmode(info->port, TYPEC_PWR_MODE_PD); + typec_set_pwr_role(info->port, bm92t_is_dfp(status1_data) ? + TYPEC_SOURCE : TYPEC_SINK); + typec_set_vconn_role(info->port, TYPEC_SOURCE); + bm92t_set_data_role(info, bm92t_is_dfp(status1_data) ? + TYPEC_HOST : TYPEC_DEVICE, true); +} + +static void bm92t_disconnect(struct bm92t_info *info, unsigned short status1_data) +{ + if (info->partner) { + typec_unregister_partner(info->partner); + info->partner = NULL; + } + + typec_set_pwr_opmode(info->port, TYPEC_PWR_MODE_USB); + typec_set_pwr_role(info->port, TYPEC_SINK); + typec_set_vconn_role(info->port, TYPEC_SINK); + bm92t_set_data_role(info, TYPEC_DEVICE, false); + + if (info->connector_fwnode) + drm_connector_oob_hotplug_event(info->connector_fwnode, + connector_status_disconnected); +} + static void bm92t_event_handler(struct work_struct *work) { static bool sys_reset; @@ -1318,8 +1242,7 @@ static void bm92t_event_handler(struct work_struct *work) int i, err; struct bm92t_info *info; struct device *dev; - struct pd_object curr_pdo; - struct rd_object curr_rdo; + u32 curr_pdo, curr_rdo; unsigned short cmd; unsigned short alert_data; unsigned short status1_data; @@ -1374,9 +1297,7 @@ static void bm92t_event_handler(struct work_struct *work) if (err) { dev_err(dev, "Internal error occurred. Ecode = %d\n", err); bm92t_state_machine(info, INIT_STATE); - bm92t_extcon_cable_update(info, EXTCON_DISP_DP, false); - bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); - bm92t_extcon_cable_update(info, EXTCON_USB, false); + bm92t_disconnect(info, status1_data); bm92t_set_vbus_enable(info, false); if (bm92t_is_plugged(status1_data) || alert_data & ALERT_SNK_FAULT || @@ -1420,18 +1341,11 @@ src_fault: if (info->pd_charging_enabled) { bm92t_set_current_limit(info, 0); info->pd_charging_enabled = false; - bm92t_extcon_cable_update(info, - EXTCON_CHG_USB_PD, false); -#ifdef CONFIG_SWITCH - switch_set_state(&info->pd_switch, 0); -#endif } /* Reset USB modes and state */ retries_usbhub = 10; - bm92t_extcon_cable_update(info, EXTCON_DISP_DP, false); - bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); - bm92t_extcon_cable_update(info, EXTCON_USB, false); + bm92t_disconnect(info, status1_data); bm92t_state_machine(info, INIT_STATE); goto ret; } else if (status1_data & STATUS1_SRC_MODE && /* OTG plug event */ @@ -1439,9 +1353,7 @@ src_fault: /* Enable VBUS for sourcing power to OTG device */ bm92t_set_vbus_enable(info, true); - /* Set USB to host mode */ - bm92t_extcon_cable_update(info, EXTCON_USB, false); - bm92t_extcon_cable_update(info, EXTCON_USB_HOST, true); + bm92t_connect(info, status1_data); goto ret; } else if (alert_data & ALERT_CONTRACT) { /* When there's a plug-in wake-up, check if a new @@ -1496,13 +1408,14 @@ handle_new_state: goto init_contract_out; } + /* Register partner if not already */ + bm92t_connect(info, status1_data); + /* Negotiate new power profile */ if (!bm92t_check_pdo(info)) { dev_info(dev, "Power Negotiation not supported\n"); bm92t_state_machine(info, INIT_STATE); msleep(550); /* WAR: charger good power test */ - bm92t_extcon_cable_update(info, - EXTCON_USB, true); goto init_contract_out; } @@ -1518,7 +1431,7 @@ init_contract_out: /* Check if forced workqueue and unplugged */ if (!alert_data && !bm92t_is_plugged(status1_data)) - bm92t_extcon_cable_update(info, EXTCON_USB, false); + bm92t_disconnect(info, status1_data); break; case NEW_PDO: @@ -1529,19 +1442,20 @@ init_contract_out: /* Check PDO/RDO */ err = bm92t_read_reg(info, CURRENT_PDO_REG, pdo, sizeof(pdo)); - memcpy(&curr_pdo, &pdo[1], sizeof(struct pd_object)); + memcpy(&curr_pdo, &pdo[1], sizeof(u32)); err = bm92t_read_reg(info, CURRENT_RDO_REG, rdo, sizeof(rdo)); - memcpy(&curr_rdo, &rdo[1], sizeof(struct rd_object)); + memcpy(&curr_rdo, &rdo[1], sizeof(u32)); dev_info(dev, "New PD Contract:\n"); dev_info(dev, "PDO: %d: %dmA, %dmV\n", - info->cable.pdo_no, curr_pdo.amp * 10, - curr_pdo.volt * 50); + info->cable.pdo_no, pdo_fixed_current(curr_pdo), + pdo_fixed_voltage(curr_pdo)); dev_info(dev, "RDO: op %dmA, %dmA max\n", - curr_rdo.op_amp * 10, curr_rdo.max_amp * 10); + rdo_op_current(curr_rdo), + rdo_max_current(curr_rdo)); - if (curr_rdo.mismatch) + if (curr_rdo & RDO_CAP_MISMATCH) dev_err(dev, "PD mismatch!\n"); cmd = PS_RDY_CMD; @@ -1552,7 +1466,6 @@ init_contract_out: case PS_RDY_SENT: if (bm92t_is_success(alert_data)) { - bm92t_extcon_cable_update(info, EXTCON_USB_HOST, true); schedule_delayed_work(&info->power_work, msecs_to_jiffies(2000)); @@ -1580,15 +1493,15 @@ init_contract_out: bm92t_is_plugged(status1_data) && bm92t_is_lastcmd_ok(info, "DR_SWAP_CMD", status1_data) && bm92t_is_dfp(status1_data)) { + bm92t_set_data_role(info, TYPEC_HOST, true); bm92t_send_vdm(info, vdm_discover_id_msg, sizeof(vdm_discover_id_msg)); bm92t_state_machine(info, VDM_DISC_ID_SENT); } else if (bm92t_is_plugged(status1_data) && bm92t_lastcmd_status(status1_data) == - LASTCMD_REJECTED) { + LASTCMD_REJECTED) { /* UFP rejected data role swap. */ - bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); - bm92t_extcon_cable_update(info, EXTCON_USB, true); + bm92t_set_data_role(info, TYPEC_DEVICE, true); bm92t_state_machine(info, INIT_STATE); } break; @@ -1618,15 +1531,31 @@ init_contract_out: err = bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); - memcpy(&info->cable.vdo, &vdm[5], - sizeof(struct vd_object)); + /* VDM layout: [0]=size, [1]=cmd/ack, [2]=structured, + * [3-4]=SVID, [5..]=VDO objects + */ + info->partner_identity.id_header = + vdm[5] | (vdm[6] << 8) | + (vdm[7] << 16) | (vdm[8] << 24); + info->partner_identity.cert_stat = + vdm[9] | (vdm[10] << 8) | + (vdm[11] << 16) | (vdm[12] << 24); + info->partner_identity.product = + vdm[13] | (vdm[14] << 8) | + (vdm[15] << 16) | (vdm[16] << 24); - bm92t_print_dp_dev_info(dev, &info->cable.vdo); + bm92t_print_dp_dev_info(dev, &info->partner_identity); + + if (info->partner) + typec_partner_set_identity(info->partner); /* Check if Nintendo dock. */ - if (!(info->cable.vdo.type == VDO_ID_TYPE_ALTERNATE && - info->cable.vdo.vid == VID_NINTENDO && - info->cable.vdo.pid == PID_NIN_DOCK)) { + if (!(PD_IDH_PTYPE(info->partner_identity.id_header) + == IDH_PTYPE_AMA && + PD_IDH_VID(info->partner_identity.id_header) + == VID_NINTENDO && + PD_PRODUCT_PID(info->partner_identity.product) + == PID_NIN_DOCK)) { dev_info(dev, "VID/PID not Nintendo Dock\n"); if (info->pdata->dp_disable) { @@ -1780,8 +1709,12 @@ init_contract_out: } else bm92t_state_machine(info, DOCK_CONFIG_HANDLED); - bm92t_extcon_cable_update(info, - EXTCON_DISP_DP, true); + + /* Report DP connection to DRM */ + if (info->connector_fwnode) + drm_connector_oob_hotplug_event( + info->connector_fwnode, + connector_status_connected); } } break; @@ -1806,11 +1739,9 @@ init_contract_out: vdm[7] == (VDM_NCMD_DEVICE_STATE + 1)) { /* Check if USB HUB is supported */ if (vdm[11] & 0x02) { - bm92t_extcon_cable_update(info, - EXTCON_USB_HOST, false); + bm92t_set_data_role(info, TYPEC_DEVICE, false); msleep(500); - bm92t_extcon_cable_update(info, - EXTCON_USB, true); + bm92t_set_data_role(info, TYPEC_DEVICE, true); dev_warn(dev, "Dock has old FW!\n"); } dev_info(dev, "Dock state: %02X %02X %02X %02X\n", @@ -1944,6 +1875,21 @@ static void bm92t_remove(struct i2c_client *client) { struct bm92t_info *info = i2c_get_clientdata(client); + if (client->irq > 0) + free_irq(client->irq, info); + + cancel_delayed_work_sync(&info->oneshot_work); + cancel_delayed_work_sync(&info->power_work); + cancel_work_sync(&info->work); + destroy_workqueue(info->event_wq); + + if (info->partner) + typec_unregister_partner(info->partner); + + typec_unregister_port(info->port); + usb_role_switch_put(info->role_sw); + fwnode_handle_put(info->connector_fwnode); + #ifdef CONFIG_DEBUG_FS debugfs_remove_recursive(info->debugfs_root); #endif @@ -2347,12 +2293,57 @@ static struct bm92t_platform_data *bm92t_parse_dt(struct device *dev) } #endif +static int bm92t_try_role(struct typec_port *port, int role) +{ + return -EOPNOTSUPP; +} + +static int bm92t_dr_set(struct typec_port *port, enum typec_data_role role) +{ + struct bm92t_info *info = typec_get_drvdata(port); + unsigned short cmd = DR_SWAP_CMD; + + return bm92t_send_cmd(info, &cmd); +} + +static int bm92t_pr_set(struct typec_port *port, enum typec_role role) +{ + struct bm92t_info *info = typec_get_drvdata(port); + unsigned short cmd = PR_SWAP_CMD; + + return bm92t_send_cmd(info, &cmd); +} + +static int bm92t_port_type_set(struct typec_port *port, + enum typec_port_type type) +{ + struct bm92t_info *info = typec_get_drvdata(port); + + switch (type) { + case TYPEC_PORT_SNK: + return bm92t_set_source_mode(info, SPDSRC12_OFF); + case TYPEC_PORT_SRC: + case TYPEC_PORT_DRP: + return bm92t_set_source_mode(info, SPDSRC12_ON); + default: + return -EINVAL; + } +} + +static const struct typec_operations bm92t_ops = { + .try_role = bm92t_try_role, + .dr_set = bm92t_dr_set, + .pr_set = bm92t_pr_set, + .port_type_set = bm92t_port_type_set, +}; + static int bm92t_probe(struct i2c_client *client) { struct bm92t_info *info; - struct fwnode_handle *ep, *remote; + struct fwnode_handle *connector; struct regulator *batt_chg_reg; struct regulator *vbus_reg, *vbus_src_reg; + struct typec_capability typec_cap = { }; int err; unsigned short reg_value; @@ -2418,26 +2409,26 @@ static int bm92t_probe(struct i2c_client *client) gpio_direction_output(info->pdata->vconn_en_gpio, 0); } - ep = fwnode_graph_get_endpoint_by_id( - dev_fwnode(&client->dev), 0, 0, 0); - if (!ep) { - dev_err(&client->dev, "Endpoint not found\n"); + /* Get connector fwnode (st style) */ + connector = device_get_named_child_node(&client->dev, "connector"); + if (!connector) { + dev_err(&client->dev, "Connector node not found\n"); return -ENODEV; } + fw_devlink_purge_absent_suppliers(connector); - remote = fwnode_graph_get_remote_endpoint(ep); - if (!remote) { - dev_err(&client->dev, "Remote not found\n"); - return -ENODEV; - } - - info->role_sw = fwnode_usb_role_switch_get(remote); + info->role_sw = fwnode_usb_role_switch_get(connector); if (IS_ERR(info->role_sw)) { err = PTR_ERR(info->role_sw); - dev_err(&client->dev, "Failed to retrieve fwnode: %d\n", err); - return err; + dev_err(&client->dev, "Failed to get usb role switch: %d\n", err); + goto err_put_connector; } + /* Optional DP connector fwnode for HPD reporting */ + info->connector_fwnode = fwnode_find_reference(connector, "displayport", 0); + if (IS_ERR(info->connector_fwnode)) + info->connector_fwnode = NULL; + i2c_set_clientdata(client, info); info->i2c_client = client; @@ -2449,25 +2440,29 @@ static int bm92t_probe(struct i2c_client *client) info->state = INIT_STATE; info->first_init = true; - /* Allocate extcon */ - info->edev = devm_extcon_dev_allocate(&client->dev, bm92t_extcon_cable); - if (IS_ERR(info->edev)) { - dev_err(&client->dev, "kzalloc error\n"); - return -ENOMEM; - } + /* Register typec port */ + typec_cap.revision = USB_TYPEC_REV_1_2; + typec_cap.pd_revision = 0x200; + typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_DRD; + typec_cap.driver_data = info; + typec_cap.ops = &bm92t_ops; + typec_cap.fwnode = connector; - /* Register extcon */ - err = devm_extcon_dev_register(&client->dev, info->edev); - if (err < 0) { - dev_err(&client->dev, "Cannot register extcon device\n"); - return err; + info->port = typec_register_port(&client->dev, &typec_cap); + if (IS_ERR(info->port)) { + err = PTR_ERR(info->port); + dev_err(&client->dev, "failed to register typec port: %d\n", err); + goto err_put_role; } /* Create workqueue */ info->event_wq = create_singlethread_workqueue("bm92t-event-queue"); if (!info->event_wq) { dev_err(&client->dev, "Cannot create work queue\n"); - return -ENOMEM; + err = -ENOMEM; + goto err_unreg_port; } err = bm92t_read_reg(info, FW_TYPE_REG, @@ -2483,7 +2478,8 @@ static int bm92t_probe(struct i2c_client *client) if (info->fw_revision <= 0x644) { dev_err(&client->dev, "fw revision not supported\n"); - return -EINVAL; + err = -EINVAL; + goto err_destroy_wq; } /* Disable Source mode at boot */ @@ -2491,7 +2487,7 @@ static int bm92t_probe(struct i2c_client *client) INIT_WORK(&info->work, bm92t_event_handler); INIT_DELAYED_WORK(&info->oneshot_work, - bm92t_extcon_cable_set_init_state); + bm92t_port_set_init_state); INIT_DELAYED_WORK(&info->power_work, bm92t_power_work); @@ -2509,18 +2505,11 @@ static int bm92t_probe(struct i2c_client *client) IRQF_TRIGGER_LOW | IRQF_ONESHOT, "bm92t", info)) { dev_err(&client->dev, "Request irq failed\n"); - destroy_workqueue(info->event_wq); - return -EBUSY; + err = -EBUSY; + goto err_destroy_wq; } } -#ifdef CONFIG_SWITCH - info->pd_switch.name = pd_switch_name; - err = switch_dev_register(&info->pd_switch); - if (err) - dev_err(&client->dev, "failed to register pd switch, err=%d\n", err); -#endif - #ifdef CONFIG_DEBUG_FS bm92t_debug_init(info); #endif @@ -2537,7 +2526,18 @@ static int bm92t_probe(struct i2c_client *client) dev_info(&client->dev, "init done\n"); + fwnode_handle_put(connector); return 0; + +err_destroy_wq: + destroy_workqueue(info->event_wq); +err_unreg_port: + typec_unregister_port(info->port); +err_put_role: + usb_role_switch_put(info->role_sw); +err_put_connector: + fwnode_handle_put(connector); + return err; } #ifdef CONFIG_PM @@ -2653,5 +2653,5 @@ static void __exit bm92t_exit(void) module_exit(bm92t_exit); MODULE_AUTHOR("CTCaer "); -MODULE_DESCRIPTION("BM92TXX USB PD/DP management IC driver"); +MODULE_DESCRIPTION("Rohm BM92TXX USB Type-C and PD controller driver"); MODULE_LICENSE("GPL");