bm92t: convert to typec fwk

note: ai
This commit is contained in:
tmakin committed 2026-08-24 18:39:43 -05:00
1 parent 84af317cf3
commit ee65e16217
2 files changed
+280 -279

No files matched your search

+1
View File
@@ -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.
+279 -279
View File
@@ -28,13 +28,13 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/extcon-provider.h>
#include <linux/usb/role.h>
#include <linux/usb/typec.h>
#include <linux/usb/pd.h>
#include <linux/usb/pd_vdo.h>
#include <linux/regulator/consumer.h>
#include <linux/uaccess.h>
#ifdef CONFIG_SWITCH
#include <linux/switch.h>
#endif
#include <drm/drm_connector.h>
/* 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 <ctcaer@gmail.com>");
MODULE_DESCRIPTION("BM92TXX USB PD/DP management IC driver");
MODULE_DESCRIPTION("Rohm BM92TXX USB Type-C and PD controller driver");
MODULE_LICENSE("GPL");