Commit 1c2daf52 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'tag-chrome-platform-for-v6.1' of...

Merge tag 'tag-chrome-platform-for-v6.1' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux

Pull chrome platform updates from Tzung-Bi Shih:
 "cros_ec_proto:
   - Fix protocol failure if EC firmware jumps to RO part

  cros_typec_switch:
   - Add USB Type-C switch driver for mode switches and retimers
   - Integrate to EC for retimers, status update, and mode switches
   - Clean-ups

  cros_ec_typec:
   - Clean-ups
   - Use partner PDOs to register USB PD capabilities

  chromeos_laptop:
   - Fix a double-free

  cros_ec_chardev:
   - Check data length from userland to avoid a memory corruption

  cros_ec:
   - Expose suspend_timeout_ms in debugfs
   - Notify the PM about wake events during resume"

* tag 'tag-chrome-platform-for-v6.1' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux:
  platform/chrome: cros_ec: Notify the PM of wake events during resume
  platform/chrome: cros_ec_typec: Register partner PDOs
  platform/chrome: cros_typec_switch: Inline DRV_NAME
  platform/chrome: cros_typec_switch: Use PTR_ERR_OR_ZERO() to simplify
  platform/chrome: cros_typec_switch: Remove impossible condition
  platform/chrome: cros_typec_switch: Add missing newline on printk
  platform/chrome: cros_ec_typec: Correct alt mode index
  platform/chrome: cros_ec_typec: Add bit offset for DP VDO
  platform/chrome: cros_ec: Expose suspend_timeout_ms in debugfs
  platform/chrome: fix memory corruption in ioctl
  platform/chrome: fix double-free in chromeos_laptop_prepare()
  platform/chrome: cros_ec_typec: Get retimer handle
  platform/chrome: cros_ec_typec: Cleanup switch handle return paths
  platform/chrome: cros_typec_switch: Register mode switches
  platform/chrome: cros_typec_switch: Add event check
  platform/chrome: cros_typec_switch: Set EC retimer
  platform/chrome: cros_typec_switch: Add switch driver
  platform/chrome: Add Type-C mux set command definitions
  platform/chrome: cros_ec_proto: Update version on GET_NEXT_EVENT failure
parents c3f13bbf 8edd2752
...@@ -54,3 +54,25 @@ Description: ...@@ -54,3 +54,25 @@ Description:
this feature. this feature.
Output will be in the format: "0x%08x\n". Output will be in the format: "0x%08x\n".
What: /sys/kernel/debug/<cros-ec-device>/suspend_timeout_ms
Date: August 2022
KernelVersion: 6.1
Description:
Some ECs have a feature where they will track transitions of
a hardware-controlled sleep line, such as Intel's SLP_S0 line,
in order to detect cases where a system failed to go into deep
sleep states. The suspend_timeout_ms file controls the amount of
time in milliseconds the EC will wait before declaring a sleep
timeout event and attempting to wake the system.
Supply 0 to use the default value coded into EC firmware. Supply
65535 (EC_HOST_SLEEP_TIMEOUT_INFINITE) to disable the EC sleep
failure detection mechanism. Values in between 0 and 65535
indicate the number of milliseconds the EC should wait after a
sleep transition before declaring a timeout. This includes both
the duration after a sleep command was received but before the
hardware line changed, as well as the duration between when the
hardware line changed and the kernel sent an EC resume command.
Output will be in the format: "%u\n".
...@@ -4904,6 +4904,7 @@ M: Prashant Malani <pmalani@chromium.org> ...@@ -4904,6 +4904,7 @@ M: Prashant Malani <pmalani@chromium.org>
L: chrome-platform@lists.linux.dev L: chrome-platform@lists.linux.dev
S: Maintained S: Maintained
F: drivers/platform/chrome/cros_ec_typec.c F: drivers/platform/chrome/cros_ec_typec.c
F: drivers/platform/chrome/cros_typec_switch.c
CHROMEOS EC USB PD NOTIFY DRIVER CHROMEOS EC USB PD NOTIFY DRIVER
M: Prashant Malani <pmalani@chromium.org> M: Prashant Malani <pmalani@chromium.org>
......
...@@ -265,6 +265,17 @@ config CHROMEOS_PRIVACY_SCREEN ...@@ -265,6 +265,17 @@ config CHROMEOS_PRIVACY_SCREEN
this should probably always be built into the kernel to avoid or this should probably always be built into the kernel to avoid or
minimize drm probe deferral. minimize drm probe deferral.
config CROS_TYPEC_SWITCH
tristate "ChromeOS EC Type-C Switch Control"
depends on MFD_CROS_EC_DEV && TYPEC && ACPI
default MFD_CROS_EC_DEV
help
If you say Y here, you get support for configuring the ChromeOS EC Type-C
muxes and retimers.
To compile this driver as a module, choose M here: the module will be
called cros_typec_switch.
source "drivers/platform/chrome/wilco_ec/Kconfig" source "drivers/platform/chrome/wilco_ec/Kconfig"
# Kunit test cases # Kunit test cases
......
...@@ -12,6 +12,7 @@ obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o ...@@ -12,6 +12,7 @@ obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
obj-$(CONFIG_CROS_EC) += cros_ec.o obj-$(CONFIG_CROS_EC) += cros_ec.o
obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o
obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o
obj-$(CONFIG_CROS_TYPEC_SWITCH) += cros_typec_switch.o
obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o
obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o
cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
......
...@@ -740,6 +740,7 @@ static int __init ...@@ -740,6 +740,7 @@ static int __init
chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop,
const struct chromeos_laptop *src) const struct chromeos_laptop *src)
{ {
struct i2c_peripheral *i2c_peripherals;
struct i2c_peripheral *i2c_dev; struct i2c_peripheral *i2c_dev;
struct i2c_board_info *info; struct i2c_board_info *info;
int i; int i;
...@@ -748,17 +749,15 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, ...@@ -748,17 +749,15 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop,
if (!src->num_i2c_peripherals) if (!src->num_i2c_peripherals)
return 0; return 0;
cros_laptop->i2c_peripherals = kmemdup(src->i2c_peripherals, i2c_peripherals = kmemdup(src->i2c_peripherals,
src->num_i2c_peripherals * src->num_i2c_peripherals *
sizeof(*src->i2c_peripherals), sizeof(*src->i2c_peripherals),
GFP_KERNEL); GFP_KERNEL);
if (!cros_laptop->i2c_peripherals) if (!i2c_peripherals)
return -ENOMEM; return -ENOMEM;
cros_laptop->num_i2c_peripherals = src->num_i2c_peripherals; for (i = 0; i < src->num_i2c_peripherals; i++) {
i2c_dev = &i2c_peripherals[i];
for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) {
i2c_dev = &cros_laptop->i2c_peripherals[i];
info = &i2c_dev->board_info; info = &i2c_dev->board_info;
error = chromeos_laptop_setup_irq(i2c_dev); error = chromeos_laptop_setup_irq(i2c_dev);
...@@ -775,16 +774,19 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, ...@@ -775,16 +774,19 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop,
} }
} }
cros_laptop->i2c_peripherals = i2c_peripherals;
cros_laptop->num_i2c_peripherals = src->num_i2c_peripherals;
return 0; return 0;
err_out: err_out:
while (--i >= 0) { while (--i >= 0) {
i2c_dev = &cros_laptop->i2c_peripherals[i]; i2c_dev = &i2c_peripherals[i];
info = &i2c_dev->board_info; info = &i2c_dev->board_info;
if (!IS_ERR_OR_NULL(info->fwnode)) if (!IS_ERR_OR_NULL(info->fwnode))
fwnode_remove_software_node(info->fwnode); fwnode_remove_software_node(info->fwnode);
} }
kfree(cros_laptop->i2c_peripherals); kfree(i2c_peripherals);
return error; return error;
} }
......
...@@ -115,7 +115,7 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) ...@@ -115,7 +115,7 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event)
if (ec_dev->host_sleep_v1) { if (ec_dev->host_sleep_v1) {
buf.u.req1.sleep_event = sleep_event; buf.u.req1.sleep_event = sleep_event;
buf.u.req1.suspend_params.sleep_timeout_ms = buf.u.req1.suspend_params.sleep_timeout_ms =
EC_HOST_SLEEP_TIMEOUT_DEFAULT; ec_dev->suspend_timeout_ms;
buf.msg.outsize = sizeof(buf.u.req1); buf.msg.outsize = sizeof(buf.u.req1);
if ((sleep_event == HOST_SLEEP_EVENT_S3_RESUME) || if ((sleep_event == HOST_SLEEP_EVENT_S3_RESUME) ||
...@@ -188,6 +188,7 @@ int cros_ec_register(struct cros_ec_device *ec_dev) ...@@ -188,6 +188,7 @@ int cros_ec_register(struct cros_ec_device *ec_dev)
ec_dev->max_passthru = 0; ec_dev->max_passthru = 0;
ec_dev->ec = NULL; ec_dev->ec = NULL;
ec_dev->pd = NULL; ec_dev->pd = NULL;
ec_dev->suspend_timeout_ms = EC_HOST_SLEEP_TIMEOUT_DEFAULT;
ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
if (!ec_dev->din) if (!ec_dev->din)
...@@ -349,10 +350,16 @@ EXPORT_SYMBOL(cros_ec_suspend); ...@@ -349,10 +350,16 @@ EXPORT_SYMBOL(cros_ec_suspend);
static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev) static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev)
{ {
bool wake_event;
while (ec_dev->mkbp_event_supported && while (ec_dev->mkbp_event_supported &&
cros_ec_get_next_event(ec_dev, NULL, NULL) > 0) cros_ec_get_next_event(ec_dev, &wake_event, NULL) > 0) {
blocking_notifier_call_chain(&ec_dev->event_notifier, blocking_notifier_call_chain(&ec_dev->event_notifier,
1, ec_dev); 1, ec_dev);
if (wake_event && device_may_wakeup(ec_dev->dev))
pm_wakeup_event(ec_dev->dev, 0);
}
} }
/** /**
......
...@@ -327,6 +327,9 @@ static long cros_ec_chardev_ioctl_readmem(struct cros_ec_dev *ec, ...@@ -327,6 +327,9 @@ static long cros_ec_chardev_ioctl_readmem(struct cros_ec_dev *ec,
if (copy_from_user(&s_mem, arg, sizeof(s_mem))) if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
return -EFAULT; return -EFAULT;
if (s_mem.bytes > sizeof(s_mem.buffer))
return -EINVAL;
num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes,
s_mem.buffer); s_mem.buffer);
if (num <= 0) if (num <= 0)
......
...@@ -470,6 +470,9 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) ...@@ -470,6 +470,9 @@ static int cros_ec_debugfs_probe(struct platform_device *pd)
debugfs_create_x32("last_resume_result", 0444, debug_info->dir, debugfs_create_x32("last_resume_result", 0444, debug_info->dir,
&ec->ec_dev->last_resume_result); &ec->ec_dev->last_resume_result);
debugfs_create_u16("suspend_timeout_ms", 0664, debug_info->dir,
&ec->ec_dev->suspend_timeout_ms);
ec->debug_info = debug_info; ec->debug_info = debug_info;
dev_set_drvdata(&pd->dev, ec); dev_set_drvdata(&pd->dev, ec);
......
...@@ -773,6 +773,7 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev, ...@@ -773,6 +773,7 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev,
u8 event_type; u8 event_type;
u32 host_event; u32 host_event;
int ret; int ret;
u32 ver_mask;
/* /*
* Default value for wake_event. * Default value for wake_event.
...@@ -794,6 +795,37 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev, ...@@ -794,6 +795,37 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev,
return get_keyboard_state_event(ec_dev); return get_keyboard_state_event(ec_dev);
ret = get_next_event(ec_dev); ret = get_next_event(ec_dev);
/*
* -ENOPROTOOPT is returned when EC returns EC_RES_INVALID_VERSION.
* This can occur when EC based device (e.g. Fingerprint MCU) jumps to
* the RO image which doesn't support newer version of the command. In
* this case we will attempt to update maximum supported version of the
* EC_CMD_GET_NEXT_EVENT.
*/
if (ret == -ENOPROTOOPT) {
dev_dbg(ec_dev->dev,
"GET_NEXT_EVENT returned invalid version error.\n");
ret = cros_ec_get_host_command_version_mask(ec_dev,
EC_CMD_GET_NEXT_EVENT,
&ver_mask);
if (ret < 0 || ver_mask == 0)
/*
* Do not change the MKBP supported version if we can't
* obtain supported version correctly. Please note that
* calling EC_CMD_GET_NEXT_EVENT returned
* EC_RES_INVALID_VERSION which means that the command
* is present.
*/
return -ENOPROTOOPT;
ec_dev->mkbp_event_supported = fls(ver_mask);
dev_dbg(ec_dev->dev, "MKBP support version changed to %u\n",
ec_dev->mkbp_event_supported - 1);
/* Try to get next event with new MKBP support version set. */
ret = get_next_event(ec_dev);
}
if (ret <= 0) if (ret <= 0)
return ret; return ret;
......
...@@ -20,12 +20,14 @@ ...@@ -20,12 +20,14 @@
#include <linux/usb/typec_altmode.h> #include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h> #include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.h> #include <linux/usb/typec_mux.h>
#include <linux/usb/typec_retimer.h>
#include <linux/usb/typec_tbt.h> #include <linux/usb/typec_tbt.h>
#include <linux/usb/role.h> #include <linux/usb/role.h>
#define DRV_NAME "cros-ec-typec" #define DRV_NAME "cros-ec-typec"
#define DP_PORT_VDO (BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) | DP_CAP_DFP_D) #define DP_PORT_VDO (DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)) | \
DP_CAP_DFP_D)
/* Supported alt modes. */ /* Supported alt modes. */
enum { enum {
...@@ -55,6 +57,7 @@ struct cros_typec_port { ...@@ -55,6 +57,7 @@ struct cros_typec_port {
struct usb_pd_identity c_identity; struct usb_pd_identity c_identity;
struct typec_switch *ori_sw; struct typec_switch *ori_sw;
struct typec_mux *mux; struct typec_mux *mux;
struct typec_retimer *retimer;
struct usb_role_switch *role_sw; struct usb_role_switch *role_sw;
/* Variables keeping track of switch state. */ /* Variables keeping track of switch state. */
...@@ -70,6 +73,11 @@ struct cros_typec_port { ...@@ -70,6 +73,11 @@ struct cros_typec_port {
struct ec_response_typec_discovery *disc_data; struct ec_response_typec_discovery *disc_data;
struct list_head partner_mode_list; struct list_head partner_mode_list;
struct list_head plug_mode_list; struct list_head plug_mode_list;
/* PDO-related structs */
struct usb_power_delivery *partner_pd;
struct usb_power_delivery_capabilities *partner_src_caps;
struct usb_power_delivery_capabilities *partner_sink_caps;
}; };
/* Platform-specific data for the Chrome OS EC Type C controller. */ /* Platform-specific data for the Chrome OS EC Type C controller. */
...@@ -143,6 +151,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, ...@@ -143,6 +151,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port,
goto mux_err; goto mux_err;
} }
port->retimer = fwnode_typec_retimer_get(fwnode);
if (IS_ERR(port->retimer)) {
dev_dbg(dev, "Retimer handle not found.\n");
goto retimer_sw_err;
}
port->ori_sw = fwnode_typec_switch_get(fwnode); port->ori_sw = fwnode_typec_switch_get(fwnode);
if (IS_ERR(port->ori_sw)) { if (IS_ERR(port->ori_sw)) {
dev_dbg(dev, "Orientation switch handle not found.\n"); dev_dbg(dev, "Orientation switch handle not found.\n");
...@@ -158,12 +172,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, ...@@ -158,12 +172,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port,
return 0; return 0;
role_sw_err: role_sw_err:
usb_role_switch_put(port->role_sw);
ori_sw_err:
typec_switch_put(port->ori_sw); typec_switch_put(port->ori_sw);
mux_err: ori_sw_err:
typec_retimer_put(port->retimer);
retimer_sw_err:
typec_mux_put(port->mux); typec_mux_put(port->mux);
mux_err:
return -ENODEV; return -ENODEV;
} }
...@@ -206,6 +220,21 @@ static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int po ...@@ -206,6 +220,21 @@ static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int po
} }
} }
/*
* Map the Type-C Mux state to retimer state and call the retimer set function. We need this
* because we re-use the Type-C mux state for retimers.
*/
static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_mux_state state)
{
struct typec_retimer_state rstate = {
.alt = state.alt,
.mode = state.mode,
.data = state.data,
};
return typec_retimer_set(retimer, &rstate);
}
static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) static int cros_typec_usb_disconnect_state(struct cros_typec_port *port)
{ {
port->state.alt = NULL; port->state.alt = NULL;
...@@ -214,6 +243,7 @@ static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) ...@@ -214,6 +243,7 @@ static int cros_typec_usb_disconnect_state(struct cros_typec_port *port)
usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE); usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE);
typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE); typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE);
cros_typec_retimer_set(port->retimer, port->state);
return typec_mux_set(port->mux, &port->state); return typec_mux_set(port->mux, &port->state);
} }
...@@ -228,6 +258,14 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec, ...@@ -228,6 +258,14 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec,
cros_typec_unregister_altmodes(typec, port_num, true); cros_typec_unregister_altmodes(typec, port_num, true);
typec_partner_set_usb_power_delivery(port->partner, NULL);
usb_power_delivery_unregister_capabilities(port->partner_sink_caps);
port->partner_sink_caps = NULL;
usb_power_delivery_unregister_capabilities(port->partner_src_caps);
port->partner_src_caps = NULL;
usb_power_delivery_unregister(port->partner_pd);
port->partner_pd = NULL;
cros_typec_usb_disconnect_state(port); cros_typec_usb_disconnect_state(port);
port->mux_flags = USB_PD_MUX_NONE; port->mux_flags = USB_PD_MUX_NONE;
...@@ -411,9 +449,14 @@ static int cros_typec_init_ports(struct cros_typec_data *typec) ...@@ -411,9 +449,14 @@ static int cros_typec_init_ports(struct cros_typec_data *typec)
static int cros_typec_usb_safe_state(struct cros_typec_port *port) static int cros_typec_usb_safe_state(struct cros_typec_port *port)
{ {
int ret;
port->state.mode = TYPEC_STATE_SAFE; port->state.mode = TYPEC_STATE_SAFE;
return typec_mux_set(port->mux, &port->state); ret = cros_typec_retimer_set(port->retimer, port->state);
if (!ret)
ret = typec_mux_set(port->mux, &port->state);
return ret;
} }
/* /*
...@@ -510,7 +553,11 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec, ...@@ -510,7 +553,11 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec,
port->state.data = &dp_data; port->state.data = &dp_data;
port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode)); port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode));
return typec_mux_set(port->mux, &port->state); ret = cros_typec_retimer_set(port->retimer, port->state);
if (!ret)
ret = typec_mux_set(port->mux, &port->state);
return ret;
} }
static int cros_typec_enable_usb4(struct cros_typec_data *typec, static int cros_typec_enable_usb4(struct cros_typec_data *typec,
...@@ -599,7 +646,10 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, ...@@ -599,7 +646,10 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num,
} else if (port->mux_flags & USB_PD_MUX_USB_ENABLED) { } else if (port->mux_flags & USB_PD_MUX_USB_ENABLED) {
port->state.alt = NULL; port->state.alt = NULL;
port->state.mode = TYPEC_STATE_USB; port->state.mode = TYPEC_STATE_USB;
ret = typec_mux_set(port->mux, &port->state);
ret = cros_typec_retimer_set(port->retimer, port->state);
if (!ret)
ret = typec_mux_set(port->mux, &port->state);
} else { } else {
dev_dbg(typec->dev, dev_dbg(typec->dev,
"Unrecognized mode requested, mux flags: %x\n", "Unrecognized mode requested, mux flags: %x\n",
...@@ -697,7 +747,7 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_ ...@@ -697,7 +747,7 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_
for (j = 0; j < sop_disc->svids[i].mode_count; j++) { for (j = 0; j < sop_disc->svids[i].mode_count; j++) {
memset(&desc, 0, sizeof(desc)); memset(&desc, 0, sizeof(desc));
desc.svid = sop_disc->svids[i].svid; desc.svid = sop_disc->svids[i].svid;
desc.mode = j; desc.mode = j + 1;
desc.vdo = sop_disc->svids[i].mode_vdo[j]; desc.vdo = sop_disc->svids[i].mode_vdo[j];
if (is_partner) if (is_partner)
...@@ -902,6 +952,46 @@ static int cros_typec_send_clear_event(struct cros_typec_data *typec, int port_n ...@@ -902,6 +952,46 @@ static int cros_typec_send_clear_event(struct cros_typec_data *typec, int port_n
sizeof(req), NULL, 0); sizeof(req), NULL, 0);
} }
static void cros_typec_register_partner_pdos(struct cros_typec_data *typec,
struct ec_response_typec_status *resp, int port_num)
{
struct usb_power_delivery_capabilities_desc caps_desc = {};
struct usb_power_delivery_desc desc = {
.revision = (le16_to_cpu(resp->sop_revision) & 0xff00) >> 4,
};
struct cros_typec_port *port = typec->ports[port_num];
if (!port->partner || port->partner_pd)
return;
/* If no caps are available, don't bother creating a device. */
if (!resp->source_cap_count && !resp->sink_cap_count)
return;
port->partner_pd = usb_power_delivery_register(NULL, &desc);
if (IS_ERR(port->partner_pd)) {
dev_warn(typec->dev, "Failed to register partner PD device, port: %d\n", port_num);
return;
}
typec_partner_set_usb_power_delivery(port->partner, port->partner_pd);
memcpy(caps_desc.pdo, resp->source_cap_pdos, sizeof(u32) * resp->source_cap_count);
caps_desc.role = TYPEC_SOURCE;
port->partner_src_caps = usb_power_delivery_register_capabilities(port->partner_pd,
&caps_desc);
if (IS_ERR(port->partner_src_caps))
dev_warn(typec->dev, "Failed to register source caps, port: %d\n", port_num);
memset(&caps_desc, 0, sizeof(caps_desc));
memcpy(caps_desc.pdo, resp->sink_cap_pdos, sizeof(u32) * resp->sink_cap_count);
caps_desc.role = TYPEC_SINK;
port->partner_sink_caps = usb_power_delivery_register_capabilities(port->partner_pd,
&caps_desc);
if (IS_ERR(port->partner_sink_caps))
dev_warn(typec->dev, "Failed to register sink caps, port: %d\n", port_num);
}
static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num) static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num)
{ {
struct ec_response_typec_status resp; struct ec_response_typec_status resp;
...@@ -949,6 +1039,8 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num ...@@ -949,6 +1039,8 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num
} }
if (resp.sop_connected) if (resp.sop_connected)
typec_set_pwr_opmode(typec->ports[port_num]->port, TYPEC_PWR_MODE_PD); typec_set_pwr_opmode(typec->ports[port_num]->port, TYPEC_PWR_MODE_PD);
cros_typec_register_partner_pdos(typec, &resp, port_num);
} }
if (resp.events & PD_STATUS_EVENT_SOP_PRIME_DISC_DONE && if (resp.events & PD_STATUS_EVENT_SOP_PRIME_DISC_DONE &&
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2022 Google LLC
*
* This driver provides the ability to configure Type-C muxes and retimers which are controlled by
* the ChromeOS EC.
*/
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.h>
#include <linux/usb/typec_retimer.h>
/* Handles and other relevant data required for each port's switches. */
struct cros_typec_port {
int port_num;
struct typec_mux_dev *mode_switch;
struct typec_retimer *retimer;
struct cros_typec_switch_data *sdata;
};
/* Driver-specific data. */
struct cros_typec_switch_data {
struct device *dev;
struct cros_ec_device *ec;
struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS];
};
static int cros_typec_cmd_mux_set(struct cros_typec_switch_data *sdata, int port_num, u8 index,
u8 state)
{
struct ec_params_typec_control req = {
.port = port_num,
.command = TYPEC_CONTROL_COMMAND_USB_MUX_SET,
.mux_params = {
.mux_index = index,
.mux_flags = state,
},
};
return cros_ec_cmd(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, sizeof(req), NULL, 0);
}
static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *alt)
{
int ret = -EOPNOTSUPP;
if (mode == TYPEC_STATE_SAFE)
ret = USB_PD_MUX_SAFE_MODE;
else if (mode == TYPEC_STATE_USB)
ret = USB_PD_MUX_USB_ENABLED;
else if (alt && alt->svid == USB_TYPEC_DP_SID)
ret = USB_PD_MUX_DP_ENABLED;
return ret;
}
static int cros_typec_send_clear_event(struct cros_typec_switch_data *sdata, int port_num,
u32 events_mask)
{
struct ec_params_typec_control req = {
.port = port_num,
.command = TYPEC_CONTROL_COMMAND_CLEAR_EVENTS,
.clear_events_mask = events_mask,
};
return cros_ec_cmd(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, sizeof(req), NULL, 0);
}
static bool cros_typec_check_event(struct cros_typec_switch_data *sdata, int port_num, u32 mask)
{
struct ec_response_typec_status resp;
struct ec_params_typec_status req = {
.port = port_num,
};
int ret;
ret = cros_ec_cmd(sdata->ec, 0, EC_CMD_TYPEC_STATUS, &req, sizeof(req),
&resp, sizeof(resp));
if (ret < 0) {
dev_warn(sdata->dev, "EC_CMD_TYPEC_STATUS failed for port: %d\n", port_num);
return false;
}
if (resp.events & mask)
return true;
return false;
}
/*
* The ChromeOS EC treats both mode-switches and retimers as "muxes" for the purposes of the
* host command API. This common function configures and verifies the retimer/mode-switch
* according to the provided setting.
*/
static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int port_num, int index,
unsigned long mode, struct typec_altmode *alt)
{
unsigned long end;
u32 event_mask;
u8 mux_state;
int ret;
ret = cros_typec_get_mux_state(mode, alt);
if (ret < 0)
return ret;
mux_state = (u8)ret;
/* Clear any old mux set done event. */
if (index == 0)
event_mask = PD_STATUS_EVENT_MUX_0_SET_DONE;
else
event_mask = PD_STATUS_EVENT_MUX_1_SET_DONE;
ret = cros_typec_send_clear_event(sdata, port_num, event_mask);
if (ret < 0)
return ret;
/* Send the set command. */
ret = cros_typec_cmd_mux_set(sdata, port_num, index, mux_state);
if (ret < 0)
return ret;
/* Check for the mux set done event. */
end = jiffies + msecs_to_jiffies(1000);
do {
if (cros_typec_check_event(sdata, port_num, event_mask))
return 0;
usleep_range(500, 1000);
} while (time_before(jiffies, end));
dev_err(sdata->dev, "Timed out waiting for mux set done on index: %d, state: %d\n",
index, mux_state);
return -ETIMEDOUT;
}
static int cros_typec_mode_switch_set(struct typec_mux_dev *mode_switch,
struct typec_mux_state *state)
{
struct cros_typec_port *port = typec_mux_get_drvdata(mode_switch);
/* Mode switches have index 0. */
return cros_typec_configure_mux(port->sdata, port->port_num, 0, state->mode, state->alt);
}
static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state)
{
struct cros_typec_port *port = typec_retimer_get_drvdata(retimer);
/* Retimers have index 1. */
return cros_typec_configure_mux(port->sdata, port->port_num, 1, state->mode, state->alt);
}
static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata)
{
int i;
for (i = 0; i < EC_USB_PD_MAX_PORTS; i++) {
if (!sdata->ports[i])
continue;
typec_retimer_unregister(sdata->ports[i]->retimer);
typec_mux_unregister(sdata->ports[i]->mode_switch);
}
}
static int cros_typec_register_mode_switch(struct cros_typec_port *port,
struct fwnode_handle *fwnode)
{
struct typec_mux_desc mode_switch_desc = {
.fwnode = fwnode,
.drvdata = port,
.name = fwnode_get_name(fwnode),
.set = cros_typec_mode_switch_set,
};
port->mode_switch = typec_mux_register(port->sdata->dev, &mode_switch_desc);
return PTR_ERR_OR_ZERO(port->mode_switch);
}
static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode)
{
struct typec_retimer_desc retimer_desc = {
.fwnode = fwnode,
.drvdata = port,
.name = fwnode_get_name(fwnode),
.set = cros_typec_retimer_set,
};
port->retimer = typec_retimer_register(port->sdata->dev, &retimer_desc);
return PTR_ERR_OR_ZERO(port->retimer);
}
static int cros_typec_register_switches(struct cros_typec_switch_data *sdata)
{
struct cros_typec_port *port;
struct device *dev = sdata->dev;
struct fwnode_handle *fwnode;
struct acpi_device *adev;
unsigned long long index;
int nports, ret;
nports = device_get_child_node_count(dev);
if (nports == 0) {
dev_err(dev, "No switch devices found.\n");
return -ENODEV;
}
device_for_each_child_node(dev, fwnode) {
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
if (!port) {
ret = -ENOMEM;
goto err_switch;
}
adev = to_acpi_device_node(fwnode);
if (!adev) {
dev_err(fwnode->dev, "Couldn't get ACPI device handle\n");
ret = -ENODEV;
goto err_switch;
}
ret = acpi_evaluate_integer(adev->handle, "_ADR", NULL, &index);
if (ACPI_FAILURE(ret)) {
dev_err(fwnode->dev, "_ADR wasn't evaluated\n");
ret = -ENODATA;
goto err_switch;
}
if (index >= EC_USB_PD_MAX_PORTS) {
dev_err(fwnode->dev, "Invalid port index number: %llu\n", index);
ret = -EINVAL;
goto err_switch;
}
port->sdata = sdata;
port->port_num = index;
sdata->ports[index] = port;
ret = cros_typec_register_retimer(port, fwnode);
if (ret) {
dev_err(dev, "Retimer switch register failed\n");
goto err_switch;
}
dev_dbg(dev, "Retimer switch registered for index %llu\n", index);
if (!device_property_present(fwnode->dev, "mode-switch"))
continue;
ret = cros_typec_register_mode_switch(port, fwnode);
if (ret) {
dev_err(dev, "Mode switch register failed\n");
goto err_switch;
}
dev_dbg(dev, "Mode switch registered for index %llu\n", index);
}
return 0;
err_switch:
cros_typec_unregister_switches(sdata);
return ret;
}
static int cros_typec_switch_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cros_typec_switch_data *sdata;
sdata = devm_kzalloc(dev, sizeof(*sdata), GFP_KERNEL);
if (!sdata)
return -ENOMEM;
sdata->dev = dev;
sdata->ec = dev_get_drvdata(pdev->dev.parent);
platform_set_drvdata(pdev, sdata);
return cros_typec_register_switches(sdata);
}
static int cros_typec_switch_remove(struct platform_device *pdev)
{
struct cros_typec_switch_data *sdata = platform_get_drvdata(pdev);
cros_typec_unregister_switches(sdata);
return 0;
}
#ifdef CONFIG_ACPI
static const struct acpi_device_id cros_typec_switch_acpi_id[] = {
{ "GOOG001A", 0 },
{}
};
MODULE_DEVICE_TABLE(acpi, cros_typec_switch_acpi_id);
#endif
static struct platform_driver cros_typec_switch_driver = {
.driver = {
.name = "cros-typec-switch",
.acpi_match_table = ACPI_PTR(cros_typec_switch_acpi_id),
},
.probe = cros_typec_switch_probe,
.remove = cros_typec_switch_remove,
};
module_platform_driver(cros_typec_switch_driver);
MODULE_AUTHOR("Prashant Malani <pmalani@chromium.org>");
MODULE_DESCRIPTION("ChromeOS EC Type-C Switch control");
MODULE_LICENSE("GPL");
...@@ -5724,8 +5724,21 @@ enum typec_control_command { ...@@ -5724,8 +5724,21 @@ enum typec_control_command {
TYPEC_CONTROL_COMMAND_EXIT_MODES, TYPEC_CONTROL_COMMAND_EXIT_MODES,
TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, TYPEC_CONTROL_COMMAND_CLEAR_EVENTS,
TYPEC_CONTROL_COMMAND_ENTER_MODE, TYPEC_CONTROL_COMMAND_ENTER_MODE,
TYPEC_CONTROL_COMMAND_TBT_UFP_REPLY,
TYPEC_CONTROL_COMMAND_USB_MUX_SET,
}; };
/* Replies the AP may specify to the TBT EnterMode command as a UFP */
enum typec_tbt_ufp_reply {
TYPEC_TBT_UFP_REPLY_NAK,
TYPEC_TBT_UFP_REPLY_ACK,
};
struct typec_usb_mux_set {
uint8_t mux_index; /* Index of the mux to set in the chain */
uint8_t mux_flags; /* USB_PD_MUX_*-encoded USB mux state to set */
} __ec_align1;
struct ec_params_typec_control { struct ec_params_typec_control {
uint8_t port; uint8_t port;
uint8_t command; /* enum typec_control_command */ uint8_t command; /* enum typec_control_command */
...@@ -5739,6 +5752,8 @@ struct ec_params_typec_control { ...@@ -5739,6 +5752,8 @@ struct ec_params_typec_control {
union { union {
uint32_t clear_events_mask; uint32_t clear_events_mask;
uint8_t mode_to_enter; /* enum typec_mode */ uint8_t mode_to_enter; /* enum typec_mode */
uint8_t tbt_ufp_reply; /* enum typec_tbt_ufp_reply */
struct typec_usb_mux_set mux_params;
uint8_t placeholder[128]; uint8_t placeholder[128];
}; };
} __ec_align1; } __ec_align1;
...@@ -5817,6 +5832,9 @@ enum tcpc_cc_polarity { ...@@ -5817,6 +5832,9 @@ enum tcpc_cc_polarity {
#define PD_STATUS_EVENT_SOP_DISC_DONE BIT(0) #define PD_STATUS_EVENT_SOP_DISC_DONE BIT(0)
#define PD_STATUS_EVENT_SOP_PRIME_DISC_DONE BIT(1) #define PD_STATUS_EVENT_SOP_PRIME_DISC_DONE BIT(1)
#define PD_STATUS_EVENT_HARD_RESET BIT(2) #define PD_STATUS_EVENT_HARD_RESET BIT(2)
#define PD_STATUS_EVENT_DISCONNECTED BIT(3)
#define PD_STATUS_EVENT_MUX_0_SET_DONE BIT(4)
#define PD_STATUS_EVENT_MUX_1_SET_DONE BIT(5)
struct ec_params_typec_status { struct ec_params_typec_status {
uint8_t port; uint8_t port;
......
...@@ -169,6 +169,7 @@ struct cros_ec_device { ...@@ -169,6 +169,7 @@ struct cros_ec_device {
int event_size; int event_size;
u32 host_event_wake_mask; u32 host_event_wake_mask;
u32 last_resume_result; u32 last_resume_result;
u16 suspend_timeout_ms;
ktime_t last_event_time; ktime_t last_event_time;
struct notifier_block notifier_ready; struct notifier_block notifier_ready;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment