diff options
author | 2020-08-11 17:28:32 -0700 | |
---|---|---|
committer | 2020-08-11 17:28:32 -0700 | |
commit | fb893de323e2d39f7a1f6df425703a2edbdf56ea (patch) | |
tree | 213f4c1e722a878364e2a31b314dc55a1607f018 /drivers/platform | |
parent | Merge tag 'for-linus-5.9-ofs1' of git://git.kernel.org/pub/scm/linux/kernel/g... (diff) | |
parent | platform/chrome: cros_ec_proto: check for missing EC_CMD_HOST_EVENT_GET_WAKE_... (diff) | |
download | linux-fb893de323e2d39f7a1f6df425703a2edbdf56ea.tar.gz linux-fb893de323e2d39f7a1f6df425703a2edbdf56ea.tar.bz2 linux-fb893de323e2d39f7a1f6df425703a2edbdf56ea.zip |
Merge tag 'tag-chrome-platform-for-v5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux
Pull chrome platform updates from Benson Leung:
"cros_ec_typec:
- Add support for switch control and alternate modes to the Chrome EC
Type C port driver
- Add basic suspend/resume support
sensorhub:
- Fix timestamp overflow issue
- Fix legacy timestamp spreading on Nami systems
cros_ec_proto:
- After removing all users of, stop exporting cros_ec_cmd_xfer
- Check for missing EC_CMD_HOST_EVENT_GET_WAKE_MASK and ignore
wakeups on old ECs
misc:
- Documentation warning cleanup
- Fix double unlock issue in ishtp"
* tag 'tag-chrome-platform-for-v5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux: (21 commits)
platform/chrome: cros_ec_proto: check for missing EC_CMD_HOST_EVENT_GET_WAKE_MASK
platform/chrome: cros_ec_proto: ignore unnecessary wakeups on old ECs
platform/chrome: cros_ec_sensorhub: Simplify legacy timestamp spreading
platform/chrome: cros_ec_proto: Do not export cros_ec_cmd_xfer()
platform/chrome: cros_ec_typec: Unregister partner on error
platform/chrome: cros_ec_sensorhub: Fix EC timestamp overflow
platform/chrome: cros_ec_typec: Add PM support
platform/chrome: cros_ec_typec: Use workqueue for port update
platform/chrome: cros_ec_typec: Add a dependency on USB_ROLE_SWITCH
platform/chrome: cros_ec_ishtp: Fix a double-unlock issue
platform/chrome: cros_ec_rpmsg: Document missing struct parameters
platform/chrome: cros_ec_spi: Document missing function parameters
platform/chrome: cros_ec_typec: Add TBT compat support
platform/chrome: cros_ec: Add TBT pd_ctrl fields
platform/chrome: cros_ec_typec: Make configure_mux static
platform/chrome: cros_ec_typec: Support DP alt mode
platform/chrome: cros_ec_typec: Add USB mux control
platform/chrome: cros_ec_typec: Register PD CTRL cmd v2
platform/chrome: cros_ec: Update mux state bits
platform/chrome: cros_ec_typec: Register Type C switches
...
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/chrome/Kconfig | 1 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_debugfs.c | 24 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_ishtp.c | 4 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_proto.c | 42 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_rpmsg.c | 3 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_sensorhub_ring.c | 98 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_spi.c | 4 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_typec.c | 399 |
8 files changed, 459 insertions, 116 deletions
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index cf072153bdc5..a056031dee81 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -218,6 +218,7 @@ config CROS_EC_TYPEC tristate "ChromeOS EC Type-C Connector Control" depends on MFD_CROS_EC_DEV && TYPEC depends on CROS_USBPD_NOTIFY + depends on USB_ROLE_SWITCH default MFD_CROS_EC_DEV help If you say Y here, you get support for accessing Type C connector diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index ecfada00e6c5..272c89837d74 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -242,6 +242,25 @@ static ssize_t cros_ec_pdinfo_read(struct file *file, read_buf, p - read_buf); } +static bool cros_ec_uptime_is_supported(struct cros_ec_device *ec_dev) +{ + struct { + struct cros_ec_command cmd; + struct ec_response_uptime_info resp; + } __packed msg = {}; + int ret; + + msg.cmd.command = EC_CMD_GET_UPTIME_INFO; + msg.cmd.insize = sizeof(msg.resp); + + ret = cros_ec_cmd_xfer_status(ec_dev, &msg.cmd); + if (ret == -EPROTO && msg.cmd.result == EC_RES_INVALID_COMMAND) + return false; + + /* Other errors maybe a transient error, do not rule about support. */ + return true; +} + static ssize_t cros_ec_uptime_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { @@ -444,8 +463,9 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info, &cros_ec_pdinfo_fops); - debugfs_create_file("uptime", 0444, debug_info->dir, debug_info, - &cros_ec_uptime_fops); + if (cros_ec_uptime_is_supported(ec->ec_dev)) + debugfs_create_file("uptime", 0444, debug_info->dir, debug_info, + &cros_ec_uptime_fops); debugfs_create_x32("last_resume_result", 0444, debug_info->dir, &ec->ec_dev->last_resume_result); diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index ed794a7ddba9..81364029af36 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -681,8 +681,10 @@ static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device) /* Register croc_ec_dev mfd */ rv = cros_ec_dev_init(client_data); - if (rv) + if (rv) { + down_write(&init_lock); goto end_cros_ec_dev_init_error; + } return 0; diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 3e745e0fe092..8d52b3b4bd4e 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -208,6 +208,12 @@ static int cros_ec_get_host_event_wake_mask(struct cros_ec_device *ec_dev, msg->insize = sizeof(*r); ret = send_command(ec_dev, msg); + if (ret >= 0) { + if (msg->result == EC_RES_INVALID_COMMAND) + return -EOPNOTSUPP; + if (msg->result != EC_RES_SUCCESS) + return -EPROTO; + } if (ret > 0) { r = (struct ec_response_host_event_mask *)msg->data; *mask = r->mask; @@ -469,14 +475,33 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev) &ver_mask); ec_dev->host_sleep_v1 = (ret >= 0 && (ver_mask & EC_VER_MASK(1))); - /* - * Get host event wake mask, assume all events are wake events - * if unavailable. - */ + /* Get host event wake mask. */ ret = cros_ec_get_host_event_wake_mask(ec_dev, proto_msg, &ec_dev->host_event_wake_mask); - if (ret < 0) - ec_dev->host_event_wake_mask = U32_MAX; + if (ret < 0) { + /* + * If the EC doesn't support EC_CMD_HOST_EVENT_GET_WAKE_MASK, + * use a reasonable default. Note that we ignore various + * battery, AC status, and power-state events, because (a) + * those can be quite common (e.g., when sitting at full + * charge, on AC) and (b) these are not actionable wake events; + * if anything, we'd like to continue suspending (to save + * power), not wake up. + */ + ec_dev->host_event_wake_mask = U32_MAX & + ~(BIT(EC_HOST_EVENT_AC_DISCONNECTED) | + BIT(EC_HOST_EVENT_BATTERY_LOW) | + BIT(EC_HOST_EVENT_BATTERY_CRITICAL) | + BIT(EC_HOST_EVENT_PD_MCU) | + BIT(EC_HOST_EVENT_BATTERY_STATUS)); + /* + * Old ECs may not support this command. Complain about all + * other errors. + */ + if (ret != -EOPNOTSUPP) + dev_err(ec_dev->dev, + "failed to retrieve wake mask: %d\n", ret); + } ret = 0; @@ -496,8 +521,8 @@ EXPORT_SYMBOL(cros_ec_query_all); * * Return: 0 on success or negative error code. */ -int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, - struct cros_ec_command *msg) +static int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) { int ret; @@ -541,7 +566,6 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, return ret; } -EXPORT_SYMBOL(cros_ec_cmd_xfer); /** * cros_ec_cmd_xfer_status() - Send a command to the ChromeOS EC. diff --git a/drivers/platform/chrome/cros_ec_rpmsg.c b/drivers/platform/chrome/cros_ec_rpmsg.c index 7e8629e3db74..30d0ba3b8889 100644 --- a/drivers/platform/chrome/cros_ec_rpmsg.c +++ b/drivers/platform/chrome/cros_ec_rpmsg.c @@ -38,6 +38,9 @@ struct cros_ec_rpmsg_response { * @rpdev: rpmsg device we are connected to * @xfer_ack: completion for host command transfer. * @host_event_work: Work struct for pending host event. + * @ept: The rpmsg endpoint of this channel. + * @has_pending_host_event: Boolean used to check if there is a pending event. + * @probe_done: Flag to indicate that probe is done. */ struct cros_ec_rpmsg { struct rpmsg_device *rpdev; diff --git a/drivers/platform/chrome/cros_ec_sensorhub_ring.c b/drivers/platform/chrome/cros_ec_sensorhub_ring.c index 24e48d96ed76..8921f24e83ba 100644 --- a/drivers/platform/chrome/cros_ec_sensorhub_ring.c +++ b/drivers/platform/chrome/cros_ec_sensorhub_ring.c @@ -419,9 +419,7 @@ cros_ec_sensor_ring_process_event(struct cros_ec_sensorhub *sensorhub, * Disable filtering since we might add more jitter * if b is in a random point in time. */ - new_timestamp = fifo_timestamp - - fifo_info->timestamp * 1000 + - in->timestamp * 1000; + new_timestamp = c - b * 1000 + a * 1000; /* * The timestamp can be stale if we had to use the fifo * info timestamp. @@ -675,29 +673,22 @@ done_with_this_batch: * cros_ec_sensor_ring_spread_add_legacy: Calculate proper timestamps then * add to ringbuffer (legacy). * - * Note: This assumes we're running old firmware, where every sample's timestamp - * is after the sample. Run if tight_timestamps == false. - * - * If there is a sample with a proper timestamp + * Note: This assumes we're running old firmware, where timestamp + * is inserted after its sample(s)e. There can be several samples between + * timestamps, so several samples can have the same timestamp. * * timestamp | count * ----------------- - * older_unprocess_out --> TS1 | 1 - * TS1 | 2 - * out --> TS1 | 3 - * next_out --> TS2 | - * - * We spread time for the samples [older_unprocess_out .. out] - * between TS1 and TS2: [TS1+1/4, TS1+2/4, TS1+3/4, TS2]. + * 1st sample --> TS1 | 1 + * TS2 | 2 + * TS2 | 3 + * TS3 | 4 + * last_out --> * - * If we reach the end of the samples, we compare with the - * current timestamp: * - * older_unprocess_out --> TS1 | 1 - * TS1 | 2 - * out --> TS1 | 3 + * We spread time for the samples using perod p = (current - TS1)/4. + * between TS1 and TS2: [TS1+p/4, TS1+2p/4, TS1+3p/4, current_timestamp]. * - * We know have [TS1+1/3, TS1+2/3, current timestamp] */ static void cros_ec_sensor_ring_spread_add_legacy(struct cros_ec_sensorhub *sensorhub, @@ -710,58 +701,37 @@ cros_ec_sensor_ring_spread_add_legacy(struct cros_ec_sensorhub *sensorhub, int i; for_each_set_bit(i, &sensor_mask, sensorhub->sensor_num) { - s64 older_timestamp; s64 timestamp; - struct cros_ec_sensors_ring_sample *older_unprocess_out = - sensorhub->ring; - struct cros_ec_sensors_ring_sample *next_out; - int count = 1; - - for (out = sensorhub->ring; out < last_out; out = next_out) { - s64 time_period; + int count = 0; + s64 time_period; - next_out = out + 1; + for (out = sensorhub->ring; out < last_out; out++) { if (out->sensor_id != i) continue; /* Timestamp to start with */ - older_timestamp = out->timestamp; - - /* Find next sample. */ - while (next_out < last_out && next_out->sensor_id != i) - next_out++; + timestamp = out->timestamp; + out++; + count = 1; + break; + } + for (; out < last_out; out++) { + /* Find last sample. */ + if (out->sensor_id != i) + continue; + count++; + } + if (count == 0) + continue; - if (next_out >= last_out) { - timestamp = current_timestamp; - } else { - timestamp = next_out->timestamp; - if (timestamp == older_timestamp) { - count++; - continue; - } - } + /* Spread uniformly between the first and last samples. */ + time_period = div_s64(current_timestamp - timestamp, count); - /* - * The next sample has a new timestamp, spread the - * unprocessed samples. - */ - if (next_out < last_out) - count++; - time_period = div_s64(timestamp - older_timestamp, - count); - - for (; older_unprocess_out <= out; - older_unprocess_out++) { - if (older_unprocess_out->sensor_id != i) - continue; - older_timestamp += time_period; - older_unprocess_out->timestamp = - older_timestamp; - } - count = 1; - /* The next_out sample has a valid timestamp, skip. */ - next_out++; - older_unprocess_out = next_out; + for (out = sensorhub->ring; out < last_out; out++) { + if (out->sensor_id != i) + continue; + timestamp += time_period; + out->timestamp = timestamp; } } diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c index d09260382550..dfa1f816a45f 100644 --- a/drivers/platform/chrome/cros_ec_spi.c +++ b/drivers/platform/chrome/cros_ec_spi.c @@ -148,6 +148,10 @@ static int terminate_request(struct cros_ec_device *ec_dev) * receive_n_bytes - receive n bytes from the EC. * * Assumes buf is a pointer into the ec_dev->din buffer + * + * @ec_dev: ChromeOS EC device. + * @buf: Pointer to the buffer receiving the data. + * @n: Number of bytes received. */ static int receive_n_bytes(struct cros_ec_device *ec_dev, u8 *buf, int n) { diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 66b8d21092af..3fcd27ec9ad8 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -14,9 +14,21 @@ #include <linux/platform_data/cros_usbpd_notify.h> #include <linux/platform_device.h> #include <linux/usb/typec.h> +#include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> +#include <linux/usb/typec_tbt.h> +#include <linux/usb/role.h> #define DRV_NAME "cros-ec-typec" +/* Supported alt modes. */ +enum { + CROS_EC_ALTMODE_DP = 0, + CROS_EC_ALTMODE_TBT, + CROS_EC_ALTMODE_MAX, +}; + /* Per port data. */ struct cros_typec_port { struct typec_port *port; @@ -25,6 +37,16 @@ struct cros_typec_port { struct typec_partner *partner; /* Port partner PD identity info. */ struct usb_pd_identity p_identity; + struct typec_switch *ori_sw; + struct typec_mux *mux; + struct usb_role_switch *role_sw; + + /* Variables keeping track of switch state. */ + struct typec_mux_state state; + uint8_t mux_flags; + + /* Port alt modes. */ + struct typec_altmode p_altmode[CROS_EC_ALTMODE_MAX]; }; /* Platform-specific data for the Chrome OS EC Type C controller. */ @@ -32,10 +54,11 @@ struct cros_typec_data { struct device *dev; struct cros_ec_device *ec; int num_ports; - unsigned int cmd_ver; + unsigned int pd_ctrl_ver; /* Array of ports, indexed by port number. */ struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; struct notifier_block nb; + struct work_struct port_work; }; static int cros_typec_parse_port_props(struct typec_capability *cap, @@ -84,6 +107,81 @@ static int cros_typec_parse_port_props(struct typec_capability *cap, return 0; } +static int cros_typec_get_switch_handles(struct cros_typec_port *port, + struct fwnode_handle *fwnode, + struct device *dev) +{ + port->mux = fwnode_typec_mux_get(fwnode, NULL); + if (IS_ERR(port->mux)) { + dev_dbg(dev, "Mux handle not found.\n"); + goto mux_err; + } + + port->ori_sw = fwnode_typec_switch_get(fwnode); + if (IS_ERR(port->ori_sw)) { + dev_dbg(dev, "Orientation switch handle not found.\n"); + goto ori_sw_err; + } + + port->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(port->role_sw)) { + dev_dbg(dev, "USB role switch handle not found.\n"); + goto role_sw_err; + } + + return 0; + +role_sw_err: + usb_role_switch_put(port->role_sw); +ori_sw_err: + typec_switch_put(port->ori_sw); +mux_err: + typec_mux_put(port->mux); + + return -ENODEV; +} + +static int cros_typec_add_partner(struct cros_typec_data *typec, int port_num, + bool pd_en) +{ + struct cros_typec_port *port = typec->ports[port_num]; + struct typec_partner_desc p_desc = { + .usb_pd = pd_en, + }; + int ret = 0; + + /* + * Fill an initial PD identity, which will then be updated with info + * from the EC. + */ + p_desc.identity = &port->p_identity; + + port->partner = typec_register_partner(port->port, &p_desc); + if (IS_ERR(port->partner)) { + ret = PTR_ERR(port->partner); + port->partner = NULL; + } + + return ret; +} + +static void cros_typec_remove_partner(struct cros_typec_data *typec, + int port_num) +{ + struct cros_typec_port *port = typec->ports[port_num]; + + port->state.alt = NULL; + port->state.mode = TYPEC_STATE_USB; + port->state.data = NULL; + + usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE); + typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE); + typec_mux_set(port->mux, &port->state); + + typec_unregister_partner(port->partner); + port->partner = NULL; +} + static void cros_unregister_ports(struct cros_typec_data *typec) { int i; @@ -91,10 +189,40 @@ static void cros_unregister_ports(struct cros_typec_data *typec) for (i = 0; i < typec->num_ports; i++) { if (!typec->ports[i]) continue; + cros_typec_remove_partner(typec, i); + usb_role_switch_put(typec->ports[i]->role_sw); + typec_switch_put(typec->ports[i]->ori_sw); + typec_mux_put(typec->ports[i]->mux); typec_unregister_port(typec->ports[i]->port); } } +/* + * Fake the alt mode structs until we actually start registering Type C port + * and partner alt modes. + */ +static void cros_typec_register_port_altmodes(struct cros_typec_data *typec, + int port_num) +{ + struct cros_typec_port *port = typec->ports[port_num]; + + /* All PD capable CrOS devices are assumed to support DP altmode. */ + port->p_altmode[CROS_EC_ALTMODE_DP].svid = USB_TYPEC_DP_SID; + port->p_altmode[CROS_EC_ALTMODE_DP].mode = USB_TYPEC_DP_MODE; + + /* + * Register TBT compatibility alt mode. The EC will not enter the mode + * if it doesn't support it, so it's safe to register it unconditionally + * here for now. + */ + port->p_altmode[CROS_EC_ALTMODE_TBT].svid = USB_TYPEC_TBT_SID; + port->p_altmode[CROS_EC_ALTMODE_TBT].mode = TYPEC_ANY_MODE; + + port->state.alt = NULL; + port->state.mode = TYPEC_STATE_USB; + port->state.data = NULL; +} + static int cros_typec_init_ports(struct cros_typec_data *typec) { struct device *dev = typec->dev; @@ -153,6 +281,13 @@ static int cros_typec_init_ports(struct cros_typec_data *typec) ret = PTR_ERR(cros_port->port); goto unregister_ports; } + + ret = cros_typec_get_switch_handles(cros_port, fwnode, dev); + if (ret) + dev_dbg(dev, "No switch control for port %d\n", + port_num); + + cros_typec_register_port_altmodes(typec, port_num); } return 0; @@ -193,30 +328,6 @@ static int cros_typec_ec_command(struct cros_typec_data *typec, return ret; } -static int cros_typec_add_partner(struct cros_typec_data *typec, int port_num, - bool pd_en) -{ - struct cros_typec_port *port = typec->ports[port_num]; - struct typec_partner_desc p_desc = { - .usb_pd = pd_en, - }; - int ret = 0; - - /* - * Fill an initial PD identity, which will then be updated with info - * from the EC. - */ - p_desc.identity = &port->p_identity; - - port->partner = typec_register_partner(port->port, &p_desc); - if (IS_ERR(port->partner)) { - ret = PTR_ERR(port->partner); - port->partner = NULL; - } - - return ret; -} - static void cros_typec_set_port_params_v0(struct cros_typec_data *typec, int port_num, struct ec_response_usb_pd_control *resp) { @@ -270,16 +381,166 @@ static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, } else { if (!typec->ports[port_num]->partner) return; + cros_typec_remove_partner(typec, port_num); + } +} - typec_unregister_partner(typec->ports[port_num]->partner); - typec->ports[port_num]->partner = NULL; +static int cros_typec_get_mux_info(struct cros_typec_data *typec, int port_num, + struct ec_response_usb_pd_mux_info *resp) +{ + struct ec_params_usb_pd_mux_info req = { + .port = port_num, + }; + + return cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_MUX_INFO, &req, + sizeof(req), resp, sizeof(*resp)); +} + +static int cros_typec_usb_safe_state(struct cros_typec_port *port) +{ + port->state.mode = TYPEC_STATE_SAFE; + + return typec_mux_set(port->mux, &port->state); +} + +/* + * Spoof the VDOs that were likely communicated by the partner for TBT alt + * mode. + */ +static int cros_typec_enable_tbt(struct cros_typec_data *typec, + int port_num, + struct ec_response_usb_pd_control_v2 *pd_ctrl) +{ + struct cros_typec_port *port = typec->ports[port_num]; + struct typec_thunderbolt_data data; + int ret; + + if (typec->pd_ctrl_ver < 2) { + dev_err(typec->dev, + "PD_CTRL version too old: %d\n", typec->pd_ctrl_ver); + return -ENOTSUPP; + } + + /* Device Discover Mode VDO */ + data.device_mode = TBT_MODE; + + if (pd_ctrl->control_flags & USB_PD_CTRL_TBT_LEGACY_ADAPTER) + data.device_mode = TBT_SET_ADAPTER(TBT_ADAPTER_TBT3); + + /* Cable Discover Mode VDO */ + data.cable_mode = TBT_MODE; + data.cable_mode |= TBT_SET_CABLE_SPEED(pd_ctrl->cable_speed); + + if (pd_ctrl->control_flags & USB_PD_CTRL_OPTICAL_CABLE) + data.cable_mode |= TBT_CABLE_OPTICAL; + + if (pd_ctrl->control_flags & USB_PD_CTRL_ACTIVE_LINK_UNIDIR) + data.cable_mode |= TBT_CABLE_LINK_TRAINING; + + if (pd_ctrl->cable_gen) + data.cable_mode |= TBT_CABLE_ROUNDED; + + /* Enter Mode VDO */ + data.enter_vdo = TBT_SET_CABLE_SPEED(pd_ctrl->cable_speed); + + if (pd_ctrl->control_flags & USB_PD_CTRL_ACTIVE_CABLE) + data.enter_vdo |= TBT_ENTER_MODE_ACTIVE_CABLE; + + if (!port->state.alt) { + port->state.alt = &port->p_altmode[CROS_EC_ALTMODE_TBT]; + ret = cros_typec_usb_safe_state(port); + if (ret) + return ret; + } + + port->state.data = &data; + port->state.mode = TYPEC_TBT_MODE; + + return typec_mux_set(port->mux, &port->state); +} + +/* Spoof the VDOs that were likely communicated by the partner. */ +static int cros_typec_enable_dp(struct cros_typec_data *typec, + int port_num, + struct ec_response_usb_pd_control_v2 *pd_ctrl) +{ + struct cros_typec_port *port = typec->ports[port_num]; + struct typec_displayport_data dp_data; + int ret; + + if (typec->pd_ctrl_ver < 2) { + dev_err(typec->dev, + "PD_CTRL version too old: %d\n", typec->pd_ctrl_ver); + return -ENOTSUPP; + } + + /* Status VDO. */ + dp_data.status = DP_STATUS_ENABLED; + if (port->mux_flags & USB_PD_MUX_HPD_IRQ) + dp_data.status |= DP_STATUS_IRQ_HPD; + if (port->mux_flags & USB_PD_MUX_HPD_LVL) + dp_data.status |= DP_STATUS_HPD_STATE; + + /* Configuration VDO. */ + dp_data.conf = DP_CONF_SET_PIN_ASSIGN(pd_ctrl->dp_mode); + if (!port->state.alt) { + port->state.alt = &port->p_altmode[CROS_EC_ALTMODE_DP]; + ret = cros_typec_usb_safe_state(port); + if (ret) + return ret; } + + port->state.data = &dp_data; + port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode)); + + return typec_mux_set(port->mux, &port->state); +} + +static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, + uint8_t mux_flags, + struct ec_response_usb_pd_control_v2 *pd_ctrl) +{ + struct cros_typec_port *port = typec->ports[port_num]; + enum typec_orientation orientation; + int ret; + + if (!port->partner) + return 0; + + if (mux_flags & USB_PD_MUX_POLARITY_INVERTED) + orientation = TYPEC_ORIENTATION_REVERSE; + else + orientation = TYPEC_ORIENTATION_NORMAL; + + ret = typec_switch_set(port->ori_sw, orientation); + if (ret) + return ret; + + if (mux_flags & USB_PD_MUX_TBT_COMPAT_ENABLED) { + ret = cros_typec_enable_tbt(typec, port_num, pd_ctrl); + } else if (mux_flags & USB_PD_MUX_DP_ENABLED) { + ret = cros_typec_enable_dp(typec, port_num, pd_ctrl); + } else if (mux_flags & USB_PD_MUX_SAFE_MODE) { + ret = cros_typec_usb_safe_state(port); + } else if (mux_flags & USB_PD_MUX_USB_ENABLED) { + port->state.alt = NULL; + port->state.mode = TYPEC_STATE_USB; + ret = typec_mux_set(port->mux, &port->state); + } else { + dev_info(typec->dev, + "Unsupported mode requested, mux flags: %x\n", + mux_flags); + ret = -ENOTSUPP; + } + + return ret; } static int cros_typec_port_update(struct cros_typec_data *typec, int port_num) { struct ec_params_usb_pd_control req; - struct ec_response_usb_pd_control_v1 resp; + struct ec_response_usb_pd_control_v2 resp; + struct ec_response_usb_pd_mux_info mux_resp; int ret; if (port_num < 0 || port_num >= typec->num_ports) { @@ -293,7 +554,7 @@ static int cros_typec_port_update(struct cros_typec_data *typec, int port_num) req.mux = USB_PD_CTRL_MUX_NO_CHANGE; req.swap = USB_PD_CTRL_SWAP_NONE; - ret = cros_typec_ec_command(typec, typec->cmd_ver, + ret = cros_typec_ec_command(typec, typec->pd_ctrl_ver, EC_CMD_USB_PD_CONTROL, &req, sizeof(req), &resp, sizeof(resp)); if (ret < 0) @@ -304,13 +565,33 @@ static int cros_typec_port_update(struct cros_typec_data *typec, int port_num) dev_dbg(typec->dev, "Polarity %d: 0x%hhx\n", port_num, resp.polarity); dev_dbg(typec->dev, "State %d: %s\n", port_num, resp.state); - if (typec->cmd_ver == 1) - cros_typec_set_port_params_v1(typec, port_num, &resp); + if (typec->pd_ctrl_ver != 0) + cros_typec_set_port_params_v1(typec, port_num, + (struct ec_response_usb_pd_control_v1 *)&resp); else cros_typec_set_port_params_v0(typec, port_num, (struct ec_response_usb_pd_control *) &resp); - return 0; + /* Update the switches if they exist, according to requested state */ + ret = cros_typec_get_mux_info(typec, port_num, &mux_resp); + if (ret < 0) { + dev_warn(typec->dev, + "Failed to get mux info for port: %d, err = %d\n", + port_num, ret); + return 0; + } + + /* No change needs to be made, let's exit early. */ + if (typec->ports[port_num]->mux_flags == mux_resp.flags) + return 0; + + typec->ports[port_num]->mux_flags = mux_resp.flags; + ret = cros_typec_configure_mux(typec, port_num, mux_resp.flags, &resp); + if (ret) + dev_warn(typec->dev, "Configure muxes failed, err = %d\n", ret); + + return usb_role_switch_set_role(typec->ports[port_num]->role_sw, + !!(resp.role & PD_CTRL_RESP_ROLE_DATA)); } static int cros_typec_get_cmd_version(struct cros_typec_data *typec) @@ -327,22 +608,22 @@ static int cros_typec_get_cmd_version(struct cros_typec_data *typec) if (ret < 0) return ret; - if (resp.version_mask & EC_VER_MASK(1)) - typec->cmd_ver = 1; + if (resp.version_mask & EC_VER_MASK(2)) + typec->pd_ctrl_ver = 2; + else if (resp.version_mask & EC_VER_MASK(1)) + typec->pd_ctrl_ver = 1; else - typec->cmd_ver = 0; + typec->pd_ctrl_ver = 0; dev_dbg(typec->dev, "PD Control has version mask 0x%hhx\n", - typec->cmd_ver); + typec->pd_ctrl_ver); return 0; } -static int cros_ec_typec_event(struct notifier_block *nb, - unsigned long host_event, void *_notify) +static void cros_typec_port_work(struct work_struct *work) { - struct cros_typec_data *typec = container_of(nb, struct cros_typec_data, - nb); + struct cros_typec_data *typec = container_of(work, struct cros_typec_data, port_work); int ret, i; for (i = 0; i < typec->num_ports; i++) { @@ -350,6 +631,14 @@ static int cros_ec_typec_event(struct notifier_block *nb, if (ret < 0) dev_warn(typec->dev, "Update failed for port: %d\n", i); } +} + +static int cros_ec_typec_event(struct notifier_block *nb, + unsigned long host_event, void *_notify) +{ + struct cros_typec_data *typec = container_of(nb, struct cros_typec_data, nb); + + schedule_work(&typec->port_work); return NOTIFY_OK; } @@ -408,6 +697,12 @@ static int cros_typec_probe(struct platform_device *pdev) if (ret < 0) return ret; + INIT_WORK(&typec->port_work, cros_typec_port_work); + + /* + * Safe to call port update here, since we haven't registered the + * PD notifier yet. + */ for (i = 0; i < typec->num_ports; i++) { ret = cros_typec_port_update(typec, i); if (ret < 0) @@ -426,11 +721,35 @@ unregister_ports: return ret; } +static int __maybe_unused cros_typec_suspend(struct device *dev) +{ + struct cros_typec_data *typec = dev_get_drvdata(dev); + + cancel_work_sync(&typec->port_work); + + return 0; +} + +static int __maybe_unused cros_typec_resume(struct device *dev) +{ + struct cros_typec_data *typec = dev_get_drvdata(dev); + + /* Refresh port state. */ + schedule_work(&typec->port_work); + + return 0; +} + +static const struct dev_pm_ops cros_typec_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cros_typec_suspend, cros_typec_resume) +}; + static struct platform_driver cros_typec_driver = { .driver = { .name = DRV_NAME, .acpi_match_table = ACPI_PTR(cros_typec_acpi_id), .of_match_table = of_match_ptr(cros_typec_of_match), + .pm = &cros_typec_pm_ops, }, .probe = cros_typec_probe, }; |