bm92t: convert to typec fwk
note: ai
This commit is contained in:
1 parent
84af317cf3
commit
ee65e16217
2 files changed
+280
-279
No files matched your search
@@ -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
@@ -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");
|
||||
Reference in new issue
Block a user