forked from Minki/linux
chrome platform changes for v6.1
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. -----BEGIN PGP SIGNATURE----- iIkEABYIADEWIQS0yQeDP3cjLyifNRUrxTEGBto89AUCYzut7RMcdHp1bmdiaUBr ZXJuZWwub3JnAAoJECvFMQYG2jz011UA/ioEJfKhK0HT4uwe6TfW6hAtmnk4ZipR Q7dTPH272723AP47SkcfYJK/GxBV9GIxyqOqfnhbFaxWu6fid2a4D8UCAw== =/Xux -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
1c2daf5218
@ -54,3 +54,25 @@ Description:
|
||||
this feature.
|
||||
|
||||
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>
|
||||
L: chrome-platform@lists.linux.dev
|
||||
S: Maintained
|
||||
F: drivers/platform/chrome/cros_ec_typec.c
|
||||
F: drivers/platform/chrome/cros_typec_switch.c
|
||||
|
||||
CHROMEOS EC USB PD NOTIFY DRIVER
|
||||
M: Prashant Malani <pmalani@chromium.org>
|
||||
|
@ -265,6 +265,17 @@ config CHROMEOS_PRIVACY_SCREEN
|
||||
this should probably always be built into the kernel to avoid or
|
||||
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"
|
||||
|
||||
# Kunit test cases
|
||||
|
@ -12,6 +12,7 @@ obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
|
||||
obj-$(CONFIG_CROS_EC) += cros_ec.o
|
||||
obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.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_SPI) += cros_ec_spi.o
|
||||
cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
|
||||
|
@ -740,6 +740,7 @@ static int __init
|
||||
chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop,
|
||||
const struct chromeos_laptop *src)
|
||||
{
|
||||
struct i2c_peripheral *i2c_peripherals;
|
||||
struct i2c_peripheral *i2c_dev;
|
||||
struct i2c_board_info *info;
|
||||
int i;
|
||||
@ -748,17 +749,15 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop,
|
||||
if (!src->num_i2c_peripherals)
|
||||
return 0;
|
||||
|
||||
cros_laptop->i2c_peripherals = kmemdup(src->i2c_peripherals,
|
||||
src->num_i2c_peripherals *
|
||||
sizeof(*src->i2c_peripherals),
|
||||
GFP_KERNEL);
|
||||
if (!cros_laptop->i2c_peripherals)
|
||||
i2c_peripherals = kmemdup(src->i2c_peripherals,
|
||||
src->num_i2c_peripherals *
|
||||
sizeof(*src->i2c_peripherals),
|
||||
GFP_KERNEL);
|
||||
if (!i2c_peripherals)
|
||||
return -ENOMEM;
|
||||
|
||||
cros_laptop->num_i2c_peripherals = src->num_i2c_peripherals;
|
||||
|
||||
for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) {
|
||||
i2c_dev = &cros_laptop->i2c_peripherals[i];
|
||||
for (i = 0; i < src->num_i2c_peripherals; i++) {
|
||||
i2c_dev = &i2c_peripherals[i];
|
||||
info = &i2c_dev->board_info;
|
||||
|
||||
error = chromeos_laptop_setup_irq(i2c_dev);
|
||||
@ -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;
|
||||
|
||||
err_out:
|
||||
while (--i >= 0) {
|
||||
i2c_dev = &cros_laptop->i2c_peripherals[i];
|
||||
i2c_dev = &i2c_peripherals[i];
|
||||
info = &i2c_dev->board_info;
|
||||
if (!IS_ERR_OR_NULL(info->fwnode))
|
||||
fwnode_remove_software_node(info->fwnode);
|
||||
}
|
||||
kfree(cros_laptop->i2c_peripherals);
|
||||
kfree(i2c_peripherals);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
buf.u.req1.sleep_event = sleep_event;
|
||||
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);
|
||||
if ((sleep_event == HOST_SLEEP_EVENT_S3_RESUME) ||
|
||||
@ -188,6 +188,7 @@ int cros_ec_register(struct cros_ec_device *ec_dev)
|
||||
ec_dev->max_passthru = 0;
|
||||
ec_dev->ec = 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);
|
||||
if (!ec_dev->din)
|
||||
@ -349,10 +350,16 @@ EXPORT_SYMBOL(cros_ec_suspend);
|
||||
|
||||
static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev)
|
||||
{
|
||||
bool wake_event;
|
||||
|
||||
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,
|
||||
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,
|
||||
if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
|
||||
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,
|
||||
s_mem.buffer);
|
||||
if (num <= 0)
|
||||
|
@ -470,6 +470,9 @@ static int cros_ec_debugfs_probe(struct platform_device *pd)
|
||||
debugfs_create_x32("last_resume_result", 0444, debug_info->dir,
|
||||
&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;
|
||||
|
||||
dev_set_drvdata(&pd->dev, ec);
|
||||
|
@ -773,6 +773,7 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev,
|
||||
u8 event_type;
|
||||
u32 host_event;
|
||||
int ret;
|
||||
u32 ver_mask;
|
||||
|
||||
/*
|
||||
* Default value for wake_event.
|
||||
@ -794,6 +795,37 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev,
|
||||
return get_keyboard_state_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)
|
||||
return ret;
|
||||
|
||||
|
@ -20,12 +20,14 @@
|
||||
#include <linux/usb/typec_altmode.h>
|
||||
#include <linux/usb/typec_dp.h>
|
||||
#include <linux/usb/typec_mux.h>
|
||||
#include <linux/usb/typec_retimer.h>
|
||||
#include <linux/usb/typec_tbt.h>
|
||||
#include <linux/usb/role.h>
|
||||
|
||||
#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. */
|
||||
enum {
|
||||
@ -55,6 +57,7 @@ struct cros_typec_port {
|
||||
struct usb_pd_identity c_identity;
|
||||
struct typec_switch *ori_sw;
|
||||
struct typec_mux *mux;
|
||||
struct typec_retimer *retimer;
|
||||
struct usb_role_switch *role_sw;
|
||||
|
||||
/* Variables keeping track of switch state. */
|
||||
@ -70,6 +73,11 @@ struct cros_typec_port {
|
||||
struct ec_response_typec_discovery *disc_data;
|
||||
struct list_head partner_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. */
|
||||
@ -143,6 +151,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port,
|
||||
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);
|
||||
if (IS_ERR(port->ori_sw)) {
|
||||
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,
|
||||
return 0;
|
||||
|
||||
role_sw_err:
|
||||
usb_role_switch_put(port->role_sw);
|
||||
ori_sw_err:
|
||||
typec_switch_put(port->ori_sw);
|
||||
mux_err:
|
||||
ori_sw_err:
|
||||
typec_retimer_put(port->retimer);
|
||||
retimer_sw_err:
|
||||
typec_mux_put(port->mux);
|
||||
|
||||
mux_err:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
port->state.alt = NULL;
|
||||
@ -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);
|
||||
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);
|
||||
}
|
||||
@ -228,6 +258,14 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec,
|
||||
|
||||
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);
|
||||
port->mux_flags = USB_PD_MUX_NONE;
|
||||
|
||||
@ -411,9 +449,14 @@ unregister_ports:
|
||||
|
||||
static int cros_typec_usb_safe_state(struct cros_typec_port *port)
|
||||
{
|
||||
int ret;
|
||||
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,
|
||||
port->state.data = &dp_data;
|
||||
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,
|
||||
@ -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) {
|
||||
port->state.alt = NULL;
|
||||
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 {
|
||||
dev_dbg(typec->dev,
|
||||
"Unrecognized mode requested, mux flags: %x\n",
|
||||
@ -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++) {
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.svid = sop_disc->svids[i].svid;
|
||||
desc.mode = j;
|
||||
desc.mode = j + 1;
|
||||
desc.vdo = sop_disc->svids[i].mode_vdo[j];
|
||||
|
||||
if (is_partner)
|
||||
@ -902,6 +952,46 @@ static int cros_typec_send_clear_event(struct cros_typec_data *typec, int port_n
|
||||
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)
|
||||
{
|
||||
struct ec_response_typec_status resp;
|
||||
@ -949,6 +1039,8 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num
|
||||
}
|
||||
if (resp.sop_connected)
|
||||
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 &&
|
||||
|
321
drivers/platform/chrome/cros_typec_switch.c
Normal file
321
drivers/platform/chrome/cros_typec_switch.c
Normal file
@ -0,0 +1,321 @@
|
||||
// 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 {
|
||||
TYPEC_CONTROL_COMMAND_EXIT_MODES,
|
||||
TYPEC_CONTROL_COMMAND_CLEAR_EVENTS,
|
||||
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 {
|
||||
uint8_t port;
|
||||
uint8_t command; /* enum typec_control_command */
|
||||
@ -5739,6 +5752,8 @@ struct ec_params_typec_control {
|
||||
union {
|
||||
uint32_t clear_events_mask;
|
||||
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];
|
||||
};
|
||||
} __ec_align1;
|
||||
@ -5817,6 +5832,9 @@ enum tcpc_cc_polarity {
|
||||
#define PD_STATUS_EVENT_SOP_DISC_DONE BIT(0)
|
||||
#define PD_STATUS_EVENT_SOP_PRIME_DISC_DONE BIT(1)
|
||||
#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 {
|
||||
uint8_t port;
|
||||
|
@ -169,6 +169,7 @@ struct cros_ec_device {
|
||||
int event_size;
|
||||
u32 host_event_wake_mask;
|
||||
u32 last_resume_result;
|
||||
u16 suspend_timeout_ms;
|
||||
ktime_t last_event_time;
|
||||
struct notifier_block notifier_ready;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user