Messages in this thread Patch in this message | | | From | Jon Flatley <> | Subject | [PATCH 3/3] platform: chrome: Added cros-ec-typec driver | Date | Tue, 12 Nov 2019 19:10:44 -0800 |
| |
Adds the cros-ec-typec driver for implementing the USB Type-C connector class for cros-ec devices.
Signed-off-by: Jon Flatley <jflat@chromium.org> --- drivers/mfd/cros_ec_dev.c | 7 +- drivers/platform/chrome/Kconfig | 11 + drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/cros_ec_typec.c | 457 ++++++++++++++++++++++++ 4 files changed, 473 insertions(+), 3 deletions(-) create mode 100644 drivers/platform/chrome/cros_ec_typec.c
diff --git a/drivers/mfd/cros_ec_dev.c b/drivers/mfd/cros_ec_dev.c index 6e6dfd6c1871..1136ef986695 100644 --- a/drivers/mfd/cros_ec_dev.c +++ b/drivers/mfd/cros_ec_dev.c @@ -78,9 +78,10 @@ static const struct mfd_cell cros_ec_rtc_cells[] = { { .name = "cros-ec-rtc", }, }; -static const struct mfd_cell cros_usbpd_charger_cells[] = { +static const struct mfd_cell cros_usbpd_cells[] = { { .name = "cros-usbpd-charger", }, { .name = "cros-usbpd-logger", }, + { .name = "cros-ec-typec" }, }; static const struct cros_feature_to_cells cros_subdevices[] = { @@ -96,8 +97,8 @@ static const struct cros_feature_to_cells cros_subdevices[] = { }, { .id = EC_FEATURE_USB_PD, - .mfd_cells = cros_usbpd_charger_cells, - .num_cells = ARRAY_SIZE(cros_usbpd_charger_cells), + .mfd_cells = cros_usbpd_cells, + .num_cells = ARRAY_SIZE(cros_usbpd_cells), }, }; diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index d4a55b64bc29..1be5c86f2aad 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -210,6 +210,17 @@ config CROS_EC_SYSFS To compile this driver as a module, choose M here: the module will be called cros_ec_sysfs. +config CROS_EC_TYPEC + tristate "ChromeOS Embedded Controller USB Type-C class subdriver" + depends on TYPEC && CROS_EC_USBPD_NOTIFY + default n + help + Say Y here to enable the USB Type-C class subdriver for ChromeOS + devices. + + To compile this driver as a module, choose M here: the + module will be called cros-ec-typec. + config CROS_USBPD_LOGGER tristate "Logging driver for USB PD charger" depends on CHARGER_CROS_USBPD diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index efa355ab526f..bb298c083f1e 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -22,5 +22,6 @@ obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o obj-$(CONFIG_CROS_EC_SYSFS) += cros_ec_sysfs.o obj-$(CONFIG_CROS_USBPD_LOGGER) += cros_usbpd_logger.o obj-$(CONFIG_CROS_EC_USBPD_NOTIFY) += cros_ec_usbpd_notify.o +obj-$(CONFIG_CROS_EC_TYPEC) += cros_ec_typec.o obj-$(CONFIG_WILCO_EC) += wilco_ec/ diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c new file mode 100644 index 000000000000..262b914f0a2e --- /dev/null +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_usbpd - ChromeOS EC Power Delivery Driver + * + * Copyright (C) 2019 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mfd/cros_ec.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_usbpd_notify.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/usb/typec.h> +#include <linux/slab.h> + +#define DRV_NAME "cros-ec-typec" + +struct port_data { + int port_num; + struct typec_port *port; + struct typec_partner *partner; + struct usb_pd_identity p_identity; + struct typec_data *typec; + struct typec_capability caps; +}; + +struct typec_data { + struct device *dev; + struct cros_ec_dev *ec_dev; + struct port_data *ports[EC_USB_PD_MAX_PORTS]; + unsigned int num_ports; + struct notifier_block notifier; + + int (*port_update)(struct typec_data *typec, int port_num); +}; + +#define caps_to_port_data(_caps_) container_of(_caps_, struct port_data, caps) + +static int cros_typec_ec_command(struct typec_data *typec, + unsigned int version, + unsigned int command, + void *outdata, + unsigned int outsize, + void *indata, + unsigned int insize) +{ + struct cros_ec_dev *ec_dev = typec->ec_dev; + struct cros_ec_command *msg; + int ret; + char host_cmd[32]; + + msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->version = version; + msg->command = ec_dev->cmd_offset + command; + msg->outsize = outsize; + msg->insize = insize; + + if (outsize) + memcpy(msg->data, outdata, outsize); + + sprintf(host_cmd, "typec HC 0x%x req: ", command); + print_hex_dump(KERN_DEBUG, host_cmd, DUMP_PREFIX_NONE, 16, 1, outdata, + outsize, 0); + + ret = cros_ec_cmd_xfer_status(typec->ec_dev->ec_dev, msg); + if (ret >= 0 && insize) + memcpy(indata, msg->data, insize); + sprintf(host_cmd, "typec HC 0x%x res: ", command); + print_hex_dump(KERN_DEBUG, host_cmd, DUMP_PREFIX_NONE, 16, 1, indata, + insize, 0); + + kfree(msg); + return ret; +} + +static int cros_typec_get_cmd_version(struct typec_data *typec, int cmd, + uint8_t *version_mask) +{ + struct ec_params_get_cmd_versions req_v0; + struct ec_params_get_cmd_versions_v1 req_v1; + struct ec_response_get_cmd_versions res; + int ret; + + req_v1.cmd = cmd; + ret = cros_typec_ec_command(typec, 1, EC_CMD_GET_CMD_VERSIONS, &req_v1, + sizeof(req_v1), &res, sizeof(res)); + if (ret < 0) { + req_v0.cmd = cmd; + ret = cros_typec_ec_command(typec, 0, EC_CMD_GET_CMD_VERSIONS, + &req_v0, sizeof(req_v0), &res, sizeof(res)); + if (ret < 0) + return ret; + } + *version_mask = res.version_mask; + dev_dbg(typec->dev, "EC CMD 0x%hhx has version mask 0x%hhx\n", cmd, + *version_mask); + return 0; +} + +static int cros_typec_query_pd_port_count(struct typec_data *typec) +{ + struct ec_response_usb_pd_ports res; + int ret; + + ret = cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_PORTS, NULL, 0, + &res, sizeof(res)); + if (ret < 0) + return ret; + typec->num_ports = res.num_ports; + return 0; +} + +static int cros_typec_port_update(struct typec_data *typec, + int port_num, + struct ec_response_usb_pd_control *res, + size_t res_size, + int cmd_ver) +{ + struct port_data *port; + struct ec_params_usb_pd_control req; + int ret; + + if (port_num < 0 || port_num >= typec->num_ports) { + dev_err(typec->dev, "cannot get status for invalid port %d\n", + port_num); + return -EPERM; + } + port = typec->ports[port_num]; + + req.port = port_num; + req.role = USB_PD_CTRL_ROLE_NO_CHANGE; + req.mux = USB_PD_CTRL_MUX_NO_CHANGE; + req.swap = USB_PD_CTRL_SWAP_NONE; + + ret = cros_typec_ec_command(typec, cmd_ver, EC_CMD_USB_PD_CONTROL, &req, + sizeof(req), res, res_size); + if (ret < 0) + return ret; + dev_dbg(typec->dev, "Enabled %d: 0x%hhx\n", port_num, res->enabled); + dev_dbg(typec->dev, "Role %d: 0x%hhx\n", port_num, res->role); + dev_dbg(typec->dev, "Polarity %d: 0x%hhx\n", port_num, res->polarity); + + return 0; +} + +static int cros_typec_query_pd_info(struct typec_data *typec, int port_num) +{ + struct ec_params_usb_pd_info_request req; + struct ec_params_usb_pd_discovery_entry res; + int ret; + + req.port = port_num; + ret = cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_DISCOVERY, &req, + sizeof(req), &res, sizeof(res)); + if (ret < 0) + return ret; + /* FIXME Needs the rest of the info in PD Spec 6.4.4.3.1.1 */ + typec->ports[port_num]->p_identity.id_header = res.vid; + + /* FIXME Needs bcdDevice to match PD Spec 6.4.4.3.1.3 */ + typec->ports[port_num]->p_identity.product = res.pid << 16; + return 0; +} + +static int cros_typec_port_update_v0(struct typec_data *typec, int port_num) +{ + struct port_data *port; + struct ec_response_usb_pd_control res; + enum typec_orientation polarity; + int ret; + + port = typec->ports[port_num]; + ret = cros_typec_port_update(typec, port_num, &res, sizeof(res), 0); + if (ret < 0) + return ret; + dev_dbg(typec->dev, "State %d: %hhx\n", port_num, res.state); + + if (!res.enabled) + polarity = TYPEC_ORIENTATION_NONE; + else if (!res.polarity) + polarity = TYPEC_ORIENTATION_NORMAL; + else + polarity = TYPEC_ORIENTATION_REVERSE; + + typec_set_pwr_role(port->port, res.role ? TYPEC_SOURCE : TYPEC_SINK); + typec_set_orientation(port->port, polarity); + + return 0; +} + +static int cros_typec_add_partner(struct typec_data *typec, int port_num, + bool pd_enabled) +{ + struct port_data *port; + struct typec_partner_desc p_desc; + int ret; + + port = typec->ports[port_num]; + p_desc.usb_pd = pd_enabled; + p_desc.identity = &port->p_identity; + + port->partner = typec_register_partner(port->port, &p_desc); + if (IS_ERR_OR_NULL(port->partner)) { + dev_err(typec->dev, "Port %d partner register failed\n", + port_num); + port->partner = NULL; + return PTR_ERR(port->partner); + } + + ret = cros_typec_query_pd_info(typec, port_num); + if (ret < 0) { + dev_err(typec->dev, "Port %d PD query failed\n", port_num); + typec_unregister_partner(port->partner); + port->partner = NULL; + return ret; + } + + ret = typec_partner_set_identity(port->partner); + return ret; +} + +static int cros_typec_set_port_params_v1_v2(struct typec_data *typec, + int port_num, struct ec_response_usb_pd_control_v1 *res) +{ + struct port_data *port; + enum typec_orientation polarity; + int ret; + + port = typec->ports[port_num]; + if (!(res->enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) + polarity = TYPEC_ORIENTATION_NONE; + else if (!res->polarity) + polarity = TYPEC_ORIENTATION_NORMAL; + else + polarity = TYPEC_ORIENTATION_REVERSE; + typec_set_orientation(port->port, polarity); + typec_set_data_role(port->port, res->role & PD_CTRL_RESP_ROLE_DATA ? + TYPEC_HOST : TYPEC_DEVICE); + typec_set_pwr_role(port->port, res->role & PD_CTRL_RESP_ROLE_POWER ? + TYPEC_SOURCE : TYPEC_SINK); + typec_set_vconn_role(port->port, res->role & PD_CTRL_RESP_ROLE_VCONN ? + TYPEC_SOURCE : TYPEC_SINK); + + if (res->enabled & PD_CTRL_RESP_ENABLED_CONNECTED && !port->partner) { + bool pd_enabled = + res->enabled & PD_CTRL_RESP_ENABLED_PD_CAPABLE; + ret = cros_typec_add_partner(typec, port_num, pd_enabled); + if (!ret) + return ret; + } + if (!(res->enabled & PD_CTRL_RESP_ENABLED_CONNECTED) && port->partner) { + typec_unregister_partner(port->partner); + port->partner = NULL; + } + return 0; +} + +static int cros_typec_port_update_v1(struct typec_data *typec, int port_num) +{ + /*struct port_data *port;*/ + struct ec_response_usb_pd_control_v1 res; + struct ec_response_usb_pd_control *res_v0 = + (struct ec_response_usb_pd_control *) &res; + int ret; + + ret = cros_typec_port_update(typec, port_num, res_v0, sizeof(res), 1); + if (ret < 0) + return ret; + dev_dbg(typec->dev, "State %d: %s\n", port_num, res.state); + + ret = cros_typec_set_port_params_v1_v2(typec, port_num, &res); + return ret; +} + +static int cros_typec_try_role(const struct typec_capability *cap, int role) +{ + return 0; +} + +static int cros_typec_dr_set(const struct typec_capability *cap, + enum typec_data_role role) +{ + return 0; +} + +static int cros_typec_pr_set(const struct typec_capability *cap, + enum typec_role role) +{ + return 0; +} + +static int cros_typec_vconn_set(const struct typec_capability *cap, + enum typec_role role) +{ + return 0; +} + +static int cros_typec_ec_event(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_notify) +{ + struct typec_data *typec; + int i; + + typec = container_of(nb, struct typec_data, notifier); + + for (i = 0; i < typec->num_ports; ++i) + typec->port_update(typec, i); + + return NOTIFY_DONE; + +} + +static void cros_typec_unregister_notifier(void *data) +{ + struct typec_data *typec = data; + + cros_ec_usbpd_unregister_notify(&typec->notifier); +} + +static int cros_typec_probe(struct platform_device *pd) +{ + struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); + struct device *dev = &pd->dev; + struct typec_data *typec; + uint8_t ver_mask; + int i; + int ret; + + dev_dbg(dev, "Probing Cros EC Type-C device.\n"); + + typec = devm_kzalloc(dev, sizeof(*typec), GFP_KERNEL); + if (!typec) + return -ENOMEM; + + typec->dev = dev; + typec->ec_dev = ec_dev; + + platform_set_drvdata(pd, typec); + + ret = cros_typec_query_pd_port_count(typec); + if (ret < 0) { + dev_err(dev, "Failed to get PD port count from EC\n"); + return ret; + } + if (typec->num_ports > EC_USB_PD_MAX_PORTS) { + dev_err(dev, "EC reported too many ports. got: %d, max: %d\n", + typec->num_ports, EC_USB_PD_MAX_PORTS); + return -EOVERFLOW; + } + + ret = cros_typec_get_cmd_version(typec, EC_CMD_USB_PD_CONTROL, + &ver_mask); + if (ret < 0) { + dev_err(dev, "Failed to get supported PD command versions\n"); + return ret; + } + /* No reason to support EC_CMD_USB_PD_CONTROL v2 as it doesn't add any + * useful information + */ + if (ver_mask & EC_VER_MASK(1)) { + dev_dbg(dev, "Using PD command ver 1\n"); + typec->port_update = cros_typec_port_update_v1; + } else { + dev_dbg(dev, "Using PD command ver 0\n"); + typec->port_update = cros_typec_port_update_v0; + } + + for (i = 0; i < typec->num_ports; ++i) { + struct port_data *port; + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) { + ret = -ENOMEM; + goto unregister_ports; + } + port->typec = typec; + port->port_num = i; + typec->ports[i] = port; + + /* TODO Make sure these values are accurate */ + port->caps.type = TYPEC_PORT_DRP; + port->caps.data = TYPEC_PORT_DFP; + port->caps.prefer_role = TYPEC_SINK; + + port->caps.try_role = cros_typec_try_role; + port->caps.dr_set = cros_typec_dr_set; + port->caps.pr_set = cros_typec_pr_set; + port->caps.vconn_set = cros_typec_vconn_set; + port->caps.port_type_set = NULL; /* Not permitted by PD spec */ + + port->port = typec_register_port(dev, &port->caps); + if (IS_ERR_OR_NULL(port->port)) { + dev_err(dev, "Failed to register typec port %d\n", i); + ret = PTR_ERR(port->port); + goto unregister_ports; + } + + ret = typec->port_update(typec, i); + if (ret < 0) { + dev_err(dev, "Failed to update typec port %d\n", i); + goto unregister_ports; + } + } + + typec->notifier.notifier_call = cros_typec_ec_event; + ret = cros_ec_usbpd_register_notify(&typec->notifier); + if (ret < 0) { + dev_warn(dev, "Failed to register notifier\n"); + } else { + ret = devm_add_action_or_reset(dev, + cros_typec_unregister_notifier, typec); + if (ret < 0) + goto unregister_ports; + dev_dbg(dev, "Registered EC notifier\n"); + } + + return 0; + +unregister_ports: + for (i = 0; i < typec->num_ports; ++i) { + if (typec->ports[i] && typec->ports[i]->port) + typec_unregister_port(typec->ports[i]->port); + } + + return ret; +} + +static struct platform_driver cros_ec_typec_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_typec_probe +}; + +module_platform_driver(cros_ec_typec_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC USB-C connectors"); +MODULE_AUTHOR("Jon Flatley <jflat@chromium.org>"); +MODULE_ALIAS("platform:" DRV_NAME); -- 2.24.0.432.g9d3f5f5b63-goog
| |