1c6983166SBenson Leung /** 2c6983166SBenson Leung * drivers/extcon/extcon-usbc-cros-ec - ChromeOS Embedded Controller extcon 3c6983166SBenson Leung * 4c6983166SBenson Leung * Copyright (C) 2017 Google, Inc 5c6983166SBenson Leung * Author: Benson Leung <bleung@chromium.org> 6c6983166SBenson Leung * 7c6983166SBenson Leung * This software is licensed under the terms of the GNU General Public 8c6983166SBenson Leung * License version 2, as published by the Free Software Foundation, and 9c6983166SBenson Leung * may be copied, distributed, and modified under those terms. 10c6983166SBenson Leung * 11c6983166SBenson Leung * This program is distributed in the hope that it will be useful, 12c6983166SBenson Leung * but WITHOUT ANY WARRANTY; without even the implied warranty of 13c6983166SBenson Leung * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14c6983166SBenson Leung * GNU General Public License for more details. 15c6983166SBenson Leung */ 16c6983166SBenson Leung 17*176aa360SChanwoo Choi #include <linux/extcon-provider.h> 18c6983166SBenson Leung #include <linux/kernel.h> 19c6983166SBenson Leung #include <linux/mfd/cros_ec.h> 20c6983166SBenson Leung #include <linux/module.h> 21c6983166SBenson Leung #include <linux/notifier.h> 22c6983166SBenson Leung #include <linux/of.h> 23c6983166SBenson Leung #include <linux/platform_device.h> 24c6983166SBenson Leung #include <linux/slab.h> 25c6983166SBenson Leung #include <linux/sched.h> 26c6983166SBenson Leung 27c6983166SBenson Leung struct cros_ec_extcon_info { 28c6983166SBenson Leung struct device *dev; 29c6983166SBenson Leung struct extcon_dev *edev; 30c6983166SBenson Leung 31c6983166SBenson Leung int port_id; 32c6983166SBenson Leung 33c6983166SBenson Leung struct cros_ec_device *ec; 34c6983166SBenson Leung 35c6983166SBenson Leung struct notifier_block notifier; 36c6983166SBenson Leung 37c6983166SBenson Leung bool dp; /* DisplayPort enabled */ 38c6983166SBenson Leung bool mux; /* SuperSpeed (usb3) enabled */ 39c6983166SBenson Leung unsigned int power_type; 40c6983166SBenson Leung }; 41c6983166SBenson Leung 42c6983166SBenson Leung static const unsigned int usb_type_c_cable[] = { 43c6983166SBenson Leung EXTCON_DISP_DP, 44c6983166SBenson Leung EXTCON_NONE, 45c6983166SBenson Leung }; 46c6983166SBenson Leung 47c6983166SBenson Leung /** 48c6983166SBenson Leung * cros_ec_pd_command() - Send a command to the EC. 49c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 50c6983166SBenson Leung * @command: EC command 51c6983166SBenson Leung * @version: EC command version 52c6983166SBenson Leung * @outdata: EC command output data 53c6983166SBenson Leung * @outsize: Size of outdata 54c6983166SBenson Leung * @indata: EC command input data 55c6983166SBenson Leung * @insize: Size of indata 56c6983166SBenson Leung * 57c6983166SBenson Leung * Return: 0 on success, <0 on failure. 58c6983166SBenson Leung */ 59c6983166SBenson Leung static int cros_ec_pd_command(struct cros_ec_extcon_info *info, 60c6983166SBenson Leung unsigned int command, 61c6983166SBenson Leung unsigned int version, 62c6983166SBenson Leung void *outdata, 63c6983166SBenson Leung unsigned int outsize, 64c6983166SBenson Leung void *indata, 65c6983166SBenson Leung unsigned int insize) 66c6983166SBenson Leung { 67c6983166SBenson Leung struct cros_ec_command *msg; 68c6983166SBenson Leung int ret; 69c6983166SBenson Leung 70c6983166SBenson Leung msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); 711cf76c4eSChristophe JAILLET if (!msg) 721cf76c4eSChristophe JAILLET return -ENOMEM; 73c6983166SBenson Leung 74c6983166SBenson Leung msg->version = version; 75c6983166SBenson Leung msg->command = command; 76c6983166SBenson Leung msg->outsize = outsize; 77c6983166SBenson Leung msg->insize = insize; 78c6983166SBenson Leung 79c6983166SBenson Leung if (outsize) 80c6983166SBenson Leung memcpy(msg->data, outdata, outsize); 81c6983166SBenson Leung 82c6983166SBenson Leung ret = cros_ec_cmd_xfer_status(info->ec, msg); 83c6983166SBenson Leung if (ret >= 0 && insize) 84c6983166SBenson Leung memcpy(indata, msg->data, insize); 85c6983166SBenson Leung 86c6983166SBenson Leung kfree(msg); 87c6983166SBenson Leung return ret; 88c6983166SBenson Leung } 89c6983166SBenson Leung 90c6983166SBenson Leung /** 91c6983166SBenson Leung * cros_ec_usb_get_power_type() - Get power type info about PD device attached 92c6983166SBenson Leung * to given port. 93c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 94c6983166SBenson Leung * 95c6983166SBenson Leung * Return: power type on success, <0 on failure. 96c6983166SBenson Leung */ 97c6983166SBenson Leung static int cros_ec_usb_get_power_type(struct cros_ec_extcon_info *info) 98c6983166SBenson Leung { 99c6983166SBenson Leung struct ec_params_usb_pd_power_info req; 100c6983166SBenson Leung struct ec_response_usb_pd_power_info resp; 101c6983166SBenson Leung int ret; 102c6983166SBenson Leung 103c6983166SBenson Leung req.port = info->port_id; 104c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_POWER_INFO, 0, 105c6983166SBenson Leung &req, sizeof(req), &resp, sizeof(resp)); 106c6983166SBenson Leung if (ret < 0) 107c6983166SBenson Leung return ret; 108c6983166SBenson Leung 109c6983166SBenson Leung return resp.type; 110c6983166SBenson Leung } 111c6983166SBenson Leung 112c6983166SBenson Leung /** 113c6983166SBenson Leung * cros_ec_usb_get_pd_mux_state() - Get PD mux state for given port. 114c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 115c6983166SBenson Leung * 116c6983166SBenson Leung * Return: PD mux state on success, <0 on failure. 117c6983166SBenson Leung */ 118c6983166SBenson Leung static int cros_ec_usb_get_pd_mux_state(struct cros_ec_extcon_info *info) 119c6983166SBenson Leung { 120c6983166SBenson Leung struct ec_params_usb_pd_mux_info req; 121c6983166SBenson Leung struct ec_response_usb_pd_mux_info resp; 122c6983166SBenson Leung int ret; 123c6983166SBenson Leung 124c6983166SBenson Leung req.port = info->port_id; 125c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_MUX_INFO, 0, 126c6983166SBenson Leung &req, sizeof(req), 127c6983166SBenson Leung &resp, sizeof(resp)); 128c6983166SBenson Leung if (ret < 0) 129c6983166SBenson Leung return ret; 130c6983166SBenson Leung 131c6983166SBenson Leung return resp.flags; 132c6983166SBenson Leung } 133c6983166SBenson Leung 134c6983166SBenson Leung /** 135c6983166SBenson Leung * cros_ec_usb_get_role() - Get role info about possible PD device attached to a 136c6983166SBenson Leung * given port. 137c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 138c6983166SBenson Leung * @polarity: pointer to cable polarity (return value) 139c6983166SBenson Leung * 140c6983166SBenson Leung * Return: role info on success, -ENOTCONN if no cable is connected, <0 on 141c6983166SBenson Leung * failure. 142c6983166SBenson Leung */ 143c6983166SBenson Leung static int cros_ec_usb_get_role(struct cros_ec_extcon_info *info, 144c6983166SBenson Leung bool *polarity) 145c6983166SBenson Leung { 146c6983166SBenson Leung struct ec_params_usb_pd_control pd_control; 147c6983166SBenson Leung struct ec_response_usb_pd_control_v1 resp; 148c6983166SBenson Leung int ret; 149c6983166SBenson Leung 150c6983166SBenson Leung pd_control.port = info->port_id; 151c6983166SBenson Leung pd_control.role = USB_PD_CTRL_ROLE_NO_CHANGE; 152c6983166SBenson Leung pd_control.mux = USB_PD_CTRL_MUX_NO_CHANGE; 153c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_CONTROL, 1, 154c6983166SBenson Leung &pd_control, sizeof(pd_control), 155c6983166SBenson Leung &resp, sizeof(resp)); 156c6983166SBenson Leung if (ret < 0) 157c6983166SBenson Leung return ret; 158c6983166SBenson Leung 159c6983166SBenson Leung if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) 160c6983166SBenson Leung return -ENOTCONN; 161c6983166SBenson Leung 162c6983166SBenson Leung *polarity = resp.polarity; 163c6983166SBenson Leung 164c6983166SBenson Leung return resp.role; 165c6983166SBenson Leung } 166c6983166SBenson Leung 167c6983166SBenson Leung /** 168c6983166SBenson Leung * cros_ec_pd_get_num_ports() - Get number of EC charge ports. 169c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 170c6983166SBenson Leung * 171c6983166SBenson Leung * Return: number of ports on success, <0 on failure. 172c6983166SBenson Leung */ 173c6983166SBenson Leung static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info *info) 174c6983166SBenson Leung { 175c6983166SBenson Leung struct ec_response_usb_pd_ports resp; 176c6983166SBenson Leung int ret; 177c6983166SBenson Leung 178c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_PORTS, 179c6983166SBenson Leung 0, NULL, 0, &resp, sizeof(resp)); 180c6983166SBenson Leung if (ret < 0) 181c6983166SBenson Leung return ret; 182c6983166SBenson Leung 183c6983166SBenson Leung return resp.num_ports; 184c6983166SBenson Leung } 185c6983166SBenson Leung 186c6983166SBenson Leung static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info *info, 187c6983166SBenson Leung bool force) 188c6983166SBenson Leung { 189c6983166SBenson Leung struct device *dev = info->dev; 190c6983166SBenson Leung int role, power_type; 191c6983166SBenson Leung bool polarity = false; 192c6983166SBenson Leung bool dp = false; 193c6983166SBenson Leung bool mux = false; 194c6983166SBenson Leung bool hpd = false; 195c6983166SBenson Leung 196c6983166SBenson Leung power_type = cros_ec_usb_get_power_type(info); 197c6983166SBenson Leung if (power_type < 0) { 198c6983166SBenson Leung dev_err(dev, "failed getting power type err = %d\n", 199c6983166SBenson Leung power_type); 200c6983166SBenson Leung return power_type; 201c6983166SBenson Leung } 202c6983166SBenson Leung 203c6983166SBenson Leung role = cros_ec_usb_get_role(info, &polarity); 204c6983166SBenson Leung if (role < 0) { 205c6983166SBenson Leung if (role != -ENOTCONN) { 206c6983166SBenson Leung dev_err(dev, "failed getting role err = %d\n", role); 207c6983166SBenson Leung return role; 208c6983166SBenson Leung } 209c6983166SBenson Leung } else { 210c6983166SBenson Leung int pd_mux_state; 211c6983166SBenson Leung 212c6983166SBenson Leung pd_mux_state = cros_ec_usb_get_pd_mux_state(info); 213c6983166SBenson Leung if (pd_mux_state < 0) 214c6983166SBenson Leung pd_mux_state = USB_PD_MUX_USB_ENABLED; 215c6983166SBenson Leung 216c6983166SBenson Leung dp = pd_mux_state & USB_PD_MUX_DP_ENABLED; 217c6983166SBenson Leung mux = pd_mux_state & USB_PD_MUX_USB_ENABLED; 218c6983166SBenson Leung hpd = pd_mux_state & USB_PD_MUX_HPD_IRQ; 219c6983166SBenson Leung } 220c6983166SBenson Leung 221c6983166SBenson Leung if (force || info->dp != dp || info->mux != mux || 222c6983166SBenson Leung info->power_type != power_type) { 223c6983166SBenson Leung 224c6983166SBenson Leung info->dp = dp; 225c6983166SBenson Leung info->mux = mux; 226c6983166SBenson Leung info->power_type = power_type; 227c6983166SBenson Leung 228c6983166SBenson Leung extcon_set_state(info->edev, EXTCON_DISP_DP, dp); 229c6983166SBenson Leung 230c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 231c6983166SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY, 232c6983166SBenson Leung (union extcon_property_value)(int)polarity); 233c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 234c6983166SBenson Leung EXTCON_PROP_USB_SS, 235c6983166SBenson Leung (union extcon_property_value)(int)mux); 236c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 237c6983166SBenson Leung EXTCON_PROP_DISP_HPD, 238c6983166SBenson Leung (union extcon_property_value)(int)hpd); 239c6983166SBenson Leung 240c6983166SBenson Leung extcon_sync(info->edev, EXTCON_DISP_DP); 241c6983166SBenson Leung 242c6983166SBenson Leung } else if (hpd) { 243c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 244c6983166SBenson Leung EXTCON_PROP_DISP_HPD, 245c6983166SBenson Leung (union extcon_property_value)(int)hpd); 246c6983166SBenson Leung extcon_sync(info->edev, EXTCON_DISP_DP); 247c6983166SBenson Leung } 248c6983166SBenson Leung 249c6983166SBenson Leung return 0; 250c6983166SBenson Leung } 251c6983166SBenson Leung 252c6983166SBenson Leung static int extcon_cros_ec_event(struct notifier_block *nb, 253c6983166SBenson Leung unsigned long queued_during_suspend, 254c6983166SBenson Leung void *_notify) 255c6983166SBenson Leung { 256c6983166SBenson Leung struct cros_ec_extcon_info *info; 257c6983166SBenson Leung struct cros_ec_device *ec; 258c6983166SBenson Leung u32 host_event; 259c6983166SBenson Leung 260c6983166SBenson Leung info = container_of(nb, struct cros_ec_extcon_info, notifier); 261c6983166SBenson Leung ec = info->ec; 262c6983166SBenson Leung 263c6983166SBenson Leung host_event = cros_ec_get_host_event(ec); 264c6983166SBenson Leung if (host_event & (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) | 265c6983166SBenson Leung EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX))) { 266c6983166SBenson Leung extcon_cros_ec_detect_cable(info, false); 267c6983166SBenson Leung return NOTIFY_OK; 268c6983166SBenson Leung } 269c6983166SBenson Leung 270c6983166SBenson Leung return NOTIFY_DONE; 271c6983166SBenson Leung } 272c6983166SBenson Leung 273c6983166SBenson Leung static int extcon_cros_ec_probe(struct platform_device *pdev) 274c6983166SBenson Leung { 275c6983166SBenson Leung struct cros_ec_extcon_info *info; 276c6983166SBenson Leung struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 277c6983166SBenson Leung struct device *dev = &pdev->dev; 278c6983166SBenson Leung struct device_node *np = dev->of_node; 279c6983166SBenson Leung int numports, ret; 280c6983166SBenson Leung 281c6983166SBenson Leung info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); 282c6983166SBenson Leung if (!info) 283c6983166SBenson Leung return -ENOMEM; 284c6983166SBenson Leung 285c6983166SBenson Leung info->dev = dev; 286c6983166SBenson Leung info->ec = ec; 287c6983166SBenson Leung 288c6983166SBenson Leung if (np) { 289c6983166SBenson Leung u32 port; 290c6983166SBenson Leung 291c6983166SBenson Leung ret = of_property_read_u32(np, "google,usb-port-id", &port); 292c6983166SBenson Leung if (ret < 0) { 293c6983166SBenson Leung dev_err(dev, "Missing google,usb-port-id property\n"); 294c6983166SBenson Leung return ret; 295c6983166SBenson Leung } 296c6983166SBenson Leung info->port_id = port; 297c6983166SBenson Leung } else { 298c6983166SBenson Leung info->port_id = pdev->id; 299c6983166SBenson Leung } 300c6983166SBenson Leung 301c6983166SBenson Leung numports = cros_ec_pd_get_num_ports(info); 302c6983166SBenson Leung if (numports < 0) { 303c6983166SBenson Leung dev_err(dev, "failed getting number of ports! ret = %d\n", 304c6983166SBenson Leung numports); 305c6983166SBenson Leung return numports; 306c6983166SBenson Leung } 307c6983166SBenson Leung 308c6983166SBenson Leung if (info->port_id >= numports) { 309c6983166SBenson Leung dev_err(dev, "This system only supports %d ports\n", numports); 310c6983166SBenson Leung return -ENODEV; 311c6983166SBenson Leung } 312c6983166SBenson Leung 313c6983166SBenson Leung info->edev = devm_extcon_dev_allocate(dev, usb_type_c_cable); 314c6983166SBenson Leung if (IS_ERR(info->edev)) { 315c6983166SBenson Leung dev_err(dev, "failed to allocate extcon device\n"); 316c6983166SBenson Leung return -ENOMEM; 317c6983166SBenson Leung } 318c6983166SBenson Leung 319c6983166SBenson Leung ret = devm_extcon_dev_register(dev, info->edev); 320c6983166SBenson Leung if (ret < 0) { 321c6983166SBenson Leung dev_err(dev, "failed to register extcon device\n"); 322c6983166SBenson Leung return ret; 323c6983166SBenson Leung } 324c6983166SBenson Leung 325c6983166SBenson Leung extcon_set_property_capability(info->edev, EXTCON_DISP_DP, 326c6983166SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY); 327c6983166SBenson Leung extcon_set_property_capability(info->edev, EXTCON_DISP_DP, 328c6983166SBenson Leung EXTCON_PROP_USB_SS); 329c6983166SBenson Leung extcon_set_property_capability(info->edev, EXTCON_DISP_DP, 330c6983166SBenson Leung EXTCON_PROP_DISP_HPD); 331c6983166SBenson Leung 332c6983166SBenson Leung platform_set_drvdata(pdev, info); 333c6983166SBenson Leung 334c6983166SBenson Leung /* Get PD events from the EC */ 335c6983166SBenson Leung info->notifier.notifier_call = extcon_cros_ec_event; 336c6983166SBenson Leung ret = blocking_notifier_chain_register(&info->ec->event_notifier, 337c6983166SBenson Leung &info->notifier); 338c6983166SBenson Leung if (ret < 0) { 339c6983166SBenson Leung dev_err(dev, "failed to register notifier\n"); 340c6983166SBenson Leung return ret; 341c6983166SBenson Leung } 342c6983166SBenson Leung 343c6983166SBenson Leung /* Perform initial detection */ 344c6983166SBenson Leung ret = extcon_cros_ec_detect_cable(info, true); 345c6983166SBenson Leung if (ret < 0) { 346c6983166SBenson Leung dev_err(dev, "failed to detect initial cable state\n"); 347c6983166SBenson Leung goto unregister_notifier; 348c6983166SBenson Leung } 349c6983166SBenson Leung 350c6983166SBenson Leung return 0; 351c6983166SBenson Leung 352c6983166SBenson Leung unregister_notifier: 353c6983166SBenson Leung blocking_notifier_chain_unregister(&info->ec->event_notifier, 354c6983166SBenson Leung &info->notifier); 355c6983166SBenson Leung return ret; 356c6983166SBenson Leung } 357c6983166SBenson Leung 358c6983166SBenson Leung static int extcon_cros_ec_remove(struct platform_device *pdev) 359c6983166SBenson Leung { 360c6983166SBenson Leung struct cros_ec_extcon_info *info = platform_get_drvdata(pdev); 361c6983166SBenson Leung 362c6983166SBenson Leung blocking_notifier_chain_unregister(&info->ec->event_notifier, 363c6983166SBenson Leung &info->notifier); 364c6983166SBenson Leung 365c6983166SBenson Leung return 0; 366c6983166SBenson Leung } 367c6983166SBenson Leung 368c6983166SBenson Leung #ifdef CONFIG_PM_SLEEP 369c6983166SBenson Leung static int extcon_cros_ec_suspend(struct device *dev) 370c6983166SBenson Leung { 371c6983166SBenson Leung return 0; 372c6983166SBenson Leung } 373c6983166SBenson Leung 374c6983166SBenson Leung static int extcon_cros_ec_resume(struct device *dev) 375c6983166SBenson Leung { 376c6983166SBenson Leung int ret; 377c6983166SBenson Leung struct cros_ec_extcon_info *info = dev_get_drvdata(dev); 378c6983166SBenson Leung 379c6983166SBenson Leung ret = extcon_cros_ec_detect_cable(info, true); 380c6983166SBenson Leung if (ret < 0) 381c6983166SBenson Leung dev_err(dev, "failed to detect cable state on resume\n"); 382c6983166SBenson Leung 383c6983166SBenson Leung return 0; 384c6983166SBenson Leung } 385c6983166SBenson Leung 386c6983166SBenson Leung static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops = { 387c6983166SBenson Leung SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend, extcon_cros_ec_resume) 388c6983166SBenson Leung }; 389c6983166SBenson Leung 390c6983166SBenson Leung #define DEV_PM_OPS (&extcon_cros_ec_dev_pm_ops) 391c6983166SBenson Leung #else 392c6983166SBenson Leung #define DEV_PM_OPS NULL 393c6983166SBenson Leung #endif /* CONFIG_PM_SLEEP */ 394c6983166SBenson Leung 395c6983166SBenson Leung #ifdef CONFIG_OF 396c6983166SBenson Leung static const struct of_device_id extcon_cros_ec_of_match[] = { 397c6983166SBenson Leung { .compatible = "google,extcon-usbc-cros-ec" }, 398c6983166SBenson Leung { /* sentinel */ } 399c6983166SBenson Leung }; 400c6983166SBenson Leung MODULE_DEVICE_TABLE(of, extcon_cros_ec_of_match); 401c6983166SBenson Leung #endif /* CONFIG_OF */ 402c6983166SBenson Leung 403c6983166SBenson Leung static struct platform_driver extcon_cros_ec_driver = { 404c6983166SBenson Leung .driver = { 405c6983166SBenson Leung .name = "extcon-usbc-cros-ec", 406c6983166SBenson Leung .of_match_table = of_match_ptr(extcon_cros_ec_of_match), 407c6983166SBenson Leung .pm = DEV_PM_OPS, 408c6983166SBenson Leung }, 409c6983166SBenson Leung .remove = extcon_cros_ec_remove, 410c6983166SBenson Leung .probe = extcon_cros_ec_probe, 411c6983166SBenson Leung }; 412c6983166SBenson Leung 413c6983166SBenson Leung module_platform_driver(extcon_cros_ec_driver); 414c6983166SBenson Leung 415c6983166SBenson Leung MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver"); 416c6983166SBenson Leung MODULE_AUTHOR("Benson Leung <bleung@chromium.org>"); 417c6983166SBenson Leung MODULE_LICENSE("GPL"); 418