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 17176aa360SChanwoo 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 37*c7eb47f9SBenson Leung unsigned int dr; /* data role */ 38*c7eb47f9SBenson Leung bool pr; /* power role (true if VBUS enabled) */ 39c6983166SBenson Leung bool dp; /* DisplayPort enabled */ 40c6983166SBenson Leung bool mux; /* SuperSpeed (usb3) enabled */ 41c6983166SBenson Leung unsigned int power_type; 42c6983166SBenson Leung }; 43c6983166SBenson Leung 44c6983166SBenson Leung static const unsigned int usb_type_c_cable[] = { 45*c7eb47f9SBenson Leung EXTCON_USB, 46*c7eb47f9SBenson Leung EXTCON_USB_HOST, 47c6983166SBenson Leung EXTCON_DISP_DP, 48c6983166SBenson Leung EXTCON_NONE, 49c6983166SBenson Leung }; 50c6983166SBenson Leung 51*c7eb47f9SBenson Leung enum usb_data_roles { 52*c7eb47f9SBenson Leung DR_NONE, 53*c7eb47f9SBenson Leung DR_HOST, 54*c7eb47f9SBenson Leung DR_DEVICE, 55*c7eb47f9SBenson Leung }; 56*c7eb47f9SBenson Leung 57c6983166SBenson Leung /** 58c6983166SBenson Leung * cros_ec_pd_command() - Send a command to the EC. 59c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 60c6983166SBenson Leung * @command: EC command 61c6983166SBenson Leung * @version: EC command version 62c6983166SBenson Leung * @outdata: EC command output data 63c6983166SBenson Leung * @outsize: Size of outdata 64c6983166SBenson Leung * @indata: EC command input data 65c6983166SBenson Leung * @insize: Size of indata 66c6983166SBenson Leung * 67c6983166SBenson Leung * Return: 0 on success, <0 on failure. 68c6983166SBenson Leung */ 69c6983166SBenson Leung static int cros_ec_pd_command(struct cros_ec_extcon_info *info, 70c6983166SBenson Leung unsigned int command, 71c6983166SBenson Leung unsigned int version, 72c6983166SBenson Leung void *outdata, 73c6983166SBenson Leung unsigned int outsize, 74c6983166SBenson Leung void *indata, 75c6983166SBenson Leung unsigned int insize) 76c6983166SBenson Leung { 77c6983166SBenson Leung struct cros_ec_command *msg; 78c6983166SBenson Leung int ret; 79c6983166SBenson Leung 80c6983166SBenson Leung msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); 811cf76c4eSChristophe JAILLET if (!msg) 821cf76c4eSChristophe JAILLET return -ENOMEM; 83c6983166SBenson Leung 84c6983166SBenson Leung msg->version = version; 85c6983166SBenson Leung msg->command = command; 86c6983166SBenson Leung msg->outsize = outsize; 87c6983166SBenson Leung msg->insize = insize; 88c6983166SBenson Leung 89c6983166SBenson Leung if (outsize) 90c6983166SBenson Leung memcpy(msg->data, outdata, outsize); 91c6983166SBenson Leung 92c6983166SBenson Leung ret = cros_ec_cmd_xfer_status(info->ec, msg); 93c6983166SBenson Leung if (ret >= 0 && insize) 94c6983166SBenson Leung memcpy(indata, msg->data, insize); 95c6983166SBenson Leung 96c6983166SBenson Leung kfree(msg); 97c6983166SBenson Leung return ret; 98c6983166SBenson Leung } 99c6983166SBenson Leung 100c6983166SBenson Leung /** 101c6983166SBenson Leung * cros_ec_usb_get_power_type() - Get power type info about PD device attached 102c6983166SBenson Leung * to given port. 103c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 104c6983166SBenson Leung * 105c6983166SBenson Leung * Return: power type on success, <0 on failure. 106c6983166SBenson Leung */ 107c6983166SBenson Leung static int cros_ec_usb_get_power_type(struct cros_ec_extcon_info *info) 108c6983166SBenson Leung { 109c6983166SBenson Leung struct ec_params_usb_pd_power_info req; 110c6983166SBenson Leung struct ec_response_usb_pd_power_info resp; 111c6983166SBenson Leung int ret; 112c6983166SBenson Leung 113c6983166SBenson Leung req.port = info->port_id; 114c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_POWER_INFO, 0, 115c6983166SBenson Leung &req, sizeof(req), &resp, sizeof(resp)); 116c6983166SBenson Leung if (ret < 0) 117c6983166SBenson Leung return ret; 118c6983166SBenson Leung 119c6983166SBenson Leung return resp.type; 120c6983166SBenson Leung } 121c6983166SBenson Leung 122c6983166SBenson Leung /** 123c6983166SBenson Leung * cros_ec_usb_get_pd_mux_state() - Get PD mux state for given port. 124c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 125c6983166SBenson Leung * 126c6983166SBenson Leung * Return: PD mux state on success, <0 on failure. 127c6983166SBenson Leung */ 128c6983166SBenson Leung static int cros_ec_usb_get_pd_mux_state(struct cros_ec_extcon_info *info) 129c6983166SBenson Leung { 130c6983166SBenson Leung struct ec_params_usb_pd_mux_info req; 131c6983166SBenson Leung struct ec_response_usb_pd_mux_info resp; 132c6983166SBenson Leung int ret; 133c6983166SBenson Leung 134c6983166SBenson Leung req.port = info->port_id; 135c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_MUX_INFO, 0, 136c6983166SBenson Leung &req, sizeof(req), 137c6983166SBenson Leung &resp, sizeof(resp)); 138c6983166SBenson Leung if (ret < 0) 139c6983166SBenson Leung return ret; 140c6983166SBenson Leung 141c6983166SBenson Leung return resp.flags; 142c6983166SBenson Leung } 143c6983166SBenson Leung 144c6983166SBenson Leung /** 145c6983166SBenson Leung * cros_ec_usb_get_role() - Get role info about possible PD device attached to a 146c6983166SBenson Leung * given port. 147c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 148c6983166SBenson Leung * @polarity: pointer to cable polarity (return value) 149c6983166SBenson Leung * 150c6983166SBenson Leung * Return: role info on success, -ENOTCONN if no cable is connected, <0 on 151c6983166SBenson Leung * failure. 152c6983166SBenson Leung */ 153c6983166SBenson Leung static int cros_ec_usb_get_role(struct cros_ec_extcon_info *info, 154c6983166SBenson Leung bool *polarity) 155c6983166SBenson Leung { 156c6983166SBenson Leung struct ec_params_usb_pd_control pd_control; 157c6983166SBenson Leung struct ec_response_usb_pd_control_v1 resp; 158c6983166SBenson Leung int ret; 159c6983166SBenson Leung 160c6983166SBenson Leung pd_control.port = info->port_id; 161c6983166SBenson Leung pd_control.role = USB_PD_CTRL_ROLE_NO_CHANGE; 162c6983166SBenson Leung pd_control.mux = USB_PD_CTRL_MUX_NO_CHANGE; 163*c7eb47f9SBenson Leung pd_control.swap = USB_PD_CTRL_SWAP_NONE; 164c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_CONTROL, 1, 165c6983166SBenson Leung &pd_control, sizeof(pd_control), 166c6983166SBenson Leung &resp, sizeof(resp)); 167c6983166SBenson Leung if (ret < 0) 168c6983166SBenson Leung return ret; 169c6983166SBenson Leung 170c6983166SBenson Leung if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) 171c6983166SBenson Leung return -ENOTCONN; 172c6983166SBenson Leung 173c6983166SBenson Leung *polarity = resp.polarity; 174c6983166SBenson Leung 175c6983166SBenson Leung return resp.role; 176c6983166SBenson Leung } 177c6983166SBenson Leung 178c6983166SBenson Leung /** 179c6983166SBenson Leung * cros_ec_pd_get_num_ports() - Get number of EC charge ports. 180c6983166SBenson Leung * @info: pointer to struct cros_ec_extcon_info 181c6983166SBenson Leung * 182c6983166SBenson Leung * Return: number of ports on success, <0 on failure. 183c6983166SBenson Leung */ 184c6983166SBenson Leung static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info *info) 185c6983166SBenson Leung { 186c6983166SBenson Leung struct ec_response_usb_pd_ports resp; 187c6983166SBenson Leung int ret; 188c6983166SBenson Leung 189c6983166SBenson Leung ret = cros_ec_pd_command(info, EC_CMD_USB_PD_PORTS, 190c6983166SBenson Leung 0, NULL, 0, &resp, sizeof(resp)); 191c6983166SBenson Leung if (ret < 0) 192c6983166SBenson Leung return ret; 193c6983166SBenson Leung 194c6983166SBenson Leung return resp.num_ports; 195c6983166SBenson Leung } 196c6983166SBenson Leung 197*c7eb47f9SBenson Leung static const char *cros_ec_usb_role_string(unsigned int role) 198*c7eb47f9SBenson Leung { 199*c7eb47f9SBenson Leung return role == DR_NONE ? "DISCONNECTED" : 200*c7eb47f9SBenson Leung (role == DR_HOST ? "DFP" : "UFP"); 201*c7eb47f9SBenson Leung } 202*c7eb47f9SBenson Leung 203*c7eb47f9SBenson Leung static const char *cros_ec_usb_power_type_string(unsigned int type) 204*c7eb47f9SBenson Leung { 205*c7eb47f9SBenson Leung switch (type) { 206*c7eb47f9SBenson Leung case USB_CHG_TYPE_NONE: 207*c7eb47f9SBenson Leung return "USB_CHG_TYPE_NONE"; 208*c7eb47f9SBenson Leung case USB_CHG_TYPE_PD: 209*c7eb47f9SBenson Leung return "USB_CHG_TYPE_PD"; 210*c7eb47f9SBenson Leung case USB_CHG_TYPE_PROPRIETARY: 211*c7eb47f9SBenson Leung return "USB_CHG_TYPE_PROPRIETARY"; 212*c7eb47f9SBenson Leung case USB_CHG_TYPE_C: 213*c7eb47f9SBenson Leung return "USB_CHG_TYPE_C"; 214*c7eb47f9SBenson Leung case USB_CHG_TYPE_BC12_DCP: 215*c7eb47f9SBenson Leung return "USB_CHG_TYPE_BC12_DCP"; 216*c7eb47f9SBenson Leung case USB_CHG_TYPE_BC12_CDP: 217*c7eb47f9SBenson Leung return "USB_CHG_TYPE_BC12_CDP"; 218*c7eb47f9SBenson Leung case USB_CHG_TYPE_BC12_SDP: 219*c7eb47f9SBenson Leung return "USB_CHG_TYPE_BC12_SDP"; 220*c7eb47f9SBenson Leung case USB_CHG_TYPE_OTHER: 221*c7eb47f9SBenson Leung return "USB_CHG_TYPE_OTHER"; 222*c7eb47f9SBenson Leung case USB_CHG_TYPE_VBUS: 223*c7eb47f9SBenson Leung return "USB_CHG_TYPE_VBUS"; 224*c7eb47f9SBenson Leung case USB_CHG_TYPE_UNKNOWN: 225*c7eb47f9SBenson Leung return "USB_CHG_TYPE_UNKNOWN"; 226*c7eb47f9SBenson Leung default: 227*c7eb47f9SBenson Leung return "USB_CHG_TYPE_UNKNOWN"; 228*c7eb47f9SBenson Leung } 229*c7eb47f9SBenson Leung } 230*c7eb47f9SBenson Leung 231*c7eb47f9SBenson Leung static bool cros_ec_usb_power_type_is_wall_wart(unsigned int type, 232*c7eb47f9SBenson Leung unsigned int role) 233*c7eb47f9SBenson Leung { 234*c7eb47f9SBenson Leung switch (type) { 235*c7eb47f9SBenson Leung /* FIXME : Guppy, Donnettes, and other chargers will be miscategorized 236*c7eb47f9SBenson Leung * because they identify with USB_CHG_TYPE_C, but we can't return true 237*c7eb47f9SBenson Leung * here from that code because that breaks Suzy-Q and other kinds of 238*c7eb47f9SBenson Leung * USB Type-C cables and peripherals. 239*c7eb47f9SBenson Leung */ 240*c7eb47f9SBenson Leung case USB_CHG_TYPE_PROPRIETARY: 241*c7eb47f9SBenson Leung case USB_CHG_TYPE_BC12_DCP: 242*c7eb47f9SBenson Leung return true; 243*c7eb47f9SBenson Leung case USB_CHG_TYPE_PD: 244*c7eb47f9SBenson Leung case USB_CHG_TYPE_C: 245*c7eb47f9SBenson Leung case USB_CHG_TYPE_BC12_CDP: 246*c7eb47f9SBenson Leung case USB_CHG_TYPE_BC12_SDP: 247*c7eb47f9SBenson Leung case USB_CHG_TYPE_OTHER: 248*c7eb47f9SBenson Leung case USB_CHG_TYPE_VBUS: 249*c7eb47f9SBenson Leung case USB_CHG_TYPE_UNKNOWN: 250*c7eb47f9SBenson Leung case USB_CHG_TYPE_NONE: 251*c7eb47f9SBenson Leung default: 252*c7eb47f9SBenson Leung return false; 253*c7eb47f9SBenson Leung } 254*c7eb47f9SBenson Leung } 255*c7eb47f9SBenson Leung 256c6983166SBenson Leung static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info *info, 257c6983166SBenson Leung bool force) 258c6983166SBenson Leung { 259c6983166SBenson Leung struct device *dev = info->dev; 260c6983166SBenson Leung int role, power_type; 261*c7eb47f9SBenson Leung unsigned int dr = DR_NONE; 262*c7eb47f9SBenson Leung bool pr = false; 263c6983166SBenson Leung bool polarity = false; 264c6983166SBenson Leung bool dp = false; 265c6983166SBenson Leung bool mux = false; 266c6983166SBenson Leung bool hpd = false; 267c6983166SBenson Leung 268c6983166SBenson Leung power_type = cros_ec_usb_get_power_type(info); 269c6983166SBenson Leung if (power_type < 0) { 270c6983166SBenson Leung dev_err(dev, "failed getting power type err = %d\n", 271c6983166SBenson Leung power_type); 272c6983166SBenson Leung return power_type; 273c6983166SBenson Leung } 274c6983166SBenson Leung 275c6983166SBenson Leung role = cros_ec_usb_get_role(info, &polarity); 276c6983166SBenson Leung if (role < 0) { 277c6983166SBenson Leung if (role != -ENOTCONN) { 278c6983166SBenson Leung dev_err(dev, "failed getting role err = %d\n", role); 279c6983166SBenson Leung return role; 280c6983166SBenson Leung } 281*c7eb47f9SBenson Leung dev_dbg(dev, "disconnected\n"); 282c6983166SBenson Leung } else { 283c6983166SBenson Leung int pd_mux_state; 284c6983166SBenson Leung 285*c7eb47f9SBenson Leung dr = (role & PD_CTRL_RESP_ROLE_DATA) ? DR_HOST : DR_DEVICE; 286*c7eb47f9SBenson Leung pr = (role & PD_CTRL_RESP_ROLE_POWER); 287c6983166SBenson Leung pd_mux_state = cros_ec_usb_get_pd_mux_state(info); 288c6983166SBenson Leung if (pd_mux_state < 0) 289c6983166SBenson Leung pd_mux_state = USB_PD_MUX_USB_ENABLED; 290c6983166SBenson Leung 291c6983166SBenson Leung dp = pd_mux_state & USB_PD_MUX_DP_ENABLED; 292c6983166SBenson Leung mux = pd_mux_state & USB_PD_MUX_USB_ENABLED; 293c6983166SBenson Leung hpd = pd_mux_state & USB_PD_MUX_HPD_IRQ; 294*c7eb47f9SBenson Leung 295*c7eb47f9SBenson Leung dev_dbg(dev, 296*c7eb47f9SBenson Leung "connected role 0x%x pwr type %d dr %d pr %d pol %d mux %d dp %d hpd %d\n", 297*c7eb47f9SBenson Leung role, power_type, dr, pr, polarity, mux, dp, hpd); 298c6983166SBenson Leung } 299c6983166SBenson Leung 300*c7eb47f9SBenson Leung /* 301*c7eb47f9SBenson Leung * When there is no USB host (e.g. USB PD charger), 302*c7eb47f9SBenson Leung * we are not really a UFP for the AP. 303*c7eb47f9SBenson Leung */ 304*c7eb47f9SBenson Leung if (dr == DR_DEVICE && 305*c7eb47f9SBenson Leung cros_ec_usb_power_type_is_wall_wart(power_type, role)) 306*c7eb47f9SBenson Leung dr = DR_NONE; 307c6983166SBenson Leung 308*c7eb47f9SBenson Leung if (force || info->dr != dr || info->pr != pr || info->dp != dp || 309*c7eb47f9SBenson Leung info->mux != mux || info->power_type != power_type) { 310*c7eb47f9SBenson Leung bool host_connected = false, device_connected = false; 311*c7eb47f9SBenson Leung 312*c7eb47f9SBenson Leung dev_dbg(dev, "Type/Role switch! type = %s role = %s\n", 313*c7eb47f9SBenson Leung cros_ec_usb_power_type_string(power_type), 314*c7eb47f9SBenson Leung cros_ec_usb_role_string(dr)); 315*c7eb47f9SBenson Leung info->dr = dr; 316*c7eb47f9SBenson Leung info->pr = pr; 317c6983166SBenson Leung info->dp = dp; 318c6983166SBenson Leung info->mux = mux; 319c6983166SBenson Leung info->power_type = power_type; 320c6983166SBenson Leung 321*c7eb47f9SBenson Leung if (dr == DR_DEVICE) 322*c7eb47f9SBenson Leung device_connected = true; 323*c7eb47f9SBenson Leung else if (dr == DR_HOST) 324*c7eb47f9SBenson Leung host_connected = true; 325c6983166SBenson Leung 326*c7eb47f9SBenson Leung extcon_set_state(info->edev, EXTCON_USB, device_connected); 327*c7eb47f9SBenson Leung extcon_set_state(info->edev, EXTCON_USB_HOST, host_connected); 328*c7eb47f9SBenson Leung extcon_set_state(info->edev, EXTCON_DISP_DP, dp); 329*c7eb47f9SBenson Leung extcon_set_property(info->edev, EXTCON_USB, 330*c7eb47f9SBenson Leung EXTCON_PROP_USB_VBUS, 331*c7eb47f9SBenson Leung (union extcon_property_value)(int)pr); 332*c7eb47f9SBenson Leung extcon_set_property(info->edev, EXTCON_USB_HOST, 333*c7eb47f9SBenson Leung EXTCON_PROP_USB_VBUS, 334*c7eb47f9SBenson Leung (union extcon_property_value)(int)pr); 335*c7eb47f9SBenson Leung extcon_set_property(info->edev, EXTCON_USB, 336*c7eb47f9SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY, 337*c7eb47f9SBenson Leung (union extcon_property_value)(int)polarity); 338*c7eb47f9SBenson Leung extcon_set_property(info->edev, EXTCON_USB_HOST, 339*c7eb47f9SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY, 340*c7eb47f9SBenson Leung (union extcon_property_value)(int)polarity); 341c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 342c6983166SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY, 343c6983166SBenson Leung (union extcon_property_value)(int)polarity); 344*c7eb47f9SBenson Leung extcon_set_property(info->edev, EXTCON_USB, 345*c7eb47f9SBenson Leung EXTCON_PROP_USB_SS, 346*c7eb47f9SBenson Leung (union extcon_property_value)(int)mux); 347*c7eb47f9SBenson Leung extcon_set_property(info->edev, EXTCON_USB_HOST, 348*c7eb47f9SBenson Leung EXTCON_PROP_USB_SS, 349*c7eb47f9SBenson Leung (union extcon_property_value)(int)mux); 350c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 351c6983166SBenson Leung EXTCON_PROP_USB_SS, 352c6983166SBenson Leung (union extcon_property_value)(int)mux); 353c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 354c6983166SBenson Leung EXTCON_PROP_DISP_HPD, 355c6983166SBenson Leung (union extcon_property_value)(int)hpd); 356c6983166SBenson Leung 357*c7eb47f9SBenson Leung extcon_sync(info->edev, EXTCON_USB); 358*c7eb47f9SBenson Leung extcon_sync(info->edev, EXTCON_USB_HOST); 359c6983166SBenson Leung extcon_sync(info->edev, EXTCON_DISP_DP); 360c6983166SBenson Leung 361c6983166SBenson Leung } else if (hpd) { 362c6983166SBenson Leung extcon_set_property(info->edev, EXTCON_DISP_DP, 363c6983166SBenson Leung EXTCON_PROP_DISP_HPD, 364c6983166SBenson Leung (union extcon_property_value)(int)hpd); 365c6983166SBenson Leung extcon_sync(info->edev, EXTCON_DISP_DP); 366c6983166SBenson Leung } 367c6983166SBenson Leung 368c6983166SBenson Leung return 0; 369c6983166SBenson Leung } 370c6983166SBenson Leung 371c6983166SBenson Leung static int extcon_cros_ec_event(struct notifier_block *nb, 372c6983166SBenson Leung unsigned long queued_during_suspend, 373c6983166SBenson Leung void *_notify) 374c6983166SBenson Leung { 375c6983166SBenson Leung struct cros_ec_extcon_info *info; 376c6983166SBenson Leung struct cros_ec_device *ec; 377c6983166SBenson Leung u32 host_event; 378c6983166SBenson Leung 379c6983166SBenson Leung info = container_of(nb, struct cros_ec_extcon_info, notifier); 380c6983166SBenson Leung ec = info->ec; 381c6983166SBenson Leung 382c6983166SBenson Leung host_event = cros_ec_get_host_event(ec); 383c6983166SBenson Leung if (host_event & (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) | 384c6983166SBenson Leung EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX))) { 385c6983166SBenson Leung extcon_cros_ec_detect_cable(info, false); 386c6983166SBenson Leung return NOTIFY_OK; 387c6983166SBenson Leung } 388c6983166SBenson Leung 389c6983166SBenson Leung return NOTIFY_DONE; 390c6983166SBenson Leung } 391c6983166SBenson Leung 392c6983166SBenson Leung static int extcon_cros_ec_probe(struct platform_device *pdev) 393c6983166SBenson Leung { 394c6983166SBenson Leung struct cros_ec_extcon_info *info; 395c6983166SBenson Leung struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 396c6983166SBenson Leung struct device *dev = &pdev->dev; 397c6983166SBenson Leung struct device_node *np = dev->of_node; 398c6983166SBenson Leung int numports, ret; 399c6983166SBenson Leung 400c6983166SBenson Leung info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); 401c6983166SBenson Leung if (!info) 402c6983166SBenson Leung return -ENOMEM; 403c6983166SBenson Leung 404c6983166SBenson Leung info->dev = dev; 405c6983166SBenson Leung info->ec = ec; 406c6983166SBenson Leung 407c6983166SBenson Leung if (np) { 408c6983166SBenson Leung u32 port; 409c6983166SBenson Leung 410c6983166SBenson Leung ret = of_property_read_u32(np, "google,usb-port-id", &port); 411c6983166SBenson Leung if (ret < 0) { 412c6983166SBenson Leung dev_err(dev, "Missing google,usb-port-id property\n"); 413c6983166SBenson Leung return ret; 414c6983166SBenson Leung } 415c6983166SBenson Leung info->port_id = port; 416c6983166SBenson Leung } else { 417c6983166SBenson Leung info->port_id = pdev->id; 418c6983166SBenson Leung } 419c6983166SBenson Leung 420c6983166SBenson Leung numports = cros_ec_pd_get_num_ports(info); 421c6983166SBenson Leung if (numports < 0) { 422c6983166SBenson Leung dev_err(dev, "failed getting number of ports! ret = %d\n", 423c6983166SBenson Leung numports); 424c6983166SBenson Leung return numports; 425c6983166SBenson Leung } 426c6983166SBenson Leung 427c6983166SBenson Leung if (info->port_id >= numports) { 428c6983166SBenson Leung dev_err(dev, "This system only supports %d ports\n", numports); 429c6983166SBenson Leung return -ENODEV; 430c6983166SBenson Leung } 431c6983166SBenson Leung 432c6983166SBenson Leung info->edev = devm_extcon_dev_allocate(dev, usb_type_c_cable); 433c6983166SBenson Leung if (IS_ERR(info->edev)) { 434c6983166SBenson Leung dev_err(dev, "failed to allocate extcon device\n"); 435c6983166SBenson Leung return -ENOMEM; 436c6983166SBenson Leung } 437c6983166SBenson Leung 438c6983166SBenson Leung ret = devm_extcon_dev_register(dev, info->edev); 439c6983166SBenson Leung if (ret < 0) { 440c6983166SBenson Leung dev_err(dev, "failed to register extcon device\n"); 441c6983166SBenson Leung return ret; 442c6983166SBenson Leung } 443c6983166SBenson Leung 444*c7eb47f9SBenson Leung extcon_set_property_capability(info->edev, EXTCON_USB, 445*c7eb47f9SBenson Leung EXTCON_PROP_USB_VBUS); 446*c7eb47f9SBenson Leung extcon_set_property_capability(info->edev, EXTCON_USB_HOST, 447*c7eb47f9SBenson Leung EXTCON_PROP_USB_VBUS); 448*c7eb47f9SBenson Leung extcon_set_property_capability(info->edev, EXTCON_USB, 449*c7eb47f9SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY); 450*c7eb47f9SBenson Leung extcon_set_property_capability(info->edev, EXTCON_USB_HOST, 451*c7eb47f9SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY); 452c6983166SBenson Leung extcon_set_property_capability(info->edev, EXTCON_DISP_DP, 453c6983166SBenson Leung EXTCON_PROP_USB_TYPEC_POLARITY); 454*c7eb47f9SBenson Leung extcon_set_property_capability(info->edev, EXTCON_USB, 455*c7eb47f9SBenson Leung EXTCON_PROP_USB_SS); 456*c7eb47f9SBenson Leung extcon_set_property_capability(info->edev, EXTCON_USB_HOST, 457*c7eb47f9SBenson Leung EXTCON_PROP_USB_SS); 458c6983166SBenson Leung extcon_set_property_capability(info->edev, EXTCON_DISP_DP, 459c6983166SBenson Leung EXTCON_PROP_USB_SS); 460c6983166SBenson Leung extcon_set_property_capability(info->edev, EXTCON_DISP_DP, 461c6983166SBenson Leung EXTCON_PROP_DISP_HPD); 462c6983166SBenson Leung 463*c7eb47f9SBenson Leung info->dr = DR_NONE; 464*c7eb47f9SBenson Leung info->pr = false; 465*c7eb47f9SBenson Leung 466c6983166SBenson Leung platform_set_drvdata(pdev, info); 467c6983166SBenson Leung 468c6983166SBenson Leung /* Get PD events from the EC */ 469c6983166SBenson Leung info->notifier.notifier_call = extcon_cros_ec_event; 470c6983166SBenson Leung ret = blocking_notifier_chain_register(&info->ec->event_notifier, 471c6983166SBenson Leung &info->notifier); 472c6983166SBenson Leung if (ret < 0) { 473c6983166SBenson Leung dev_err(dev, "failed to register notifier\n"); 474c6983166SBenson Leung return ret; 475c6983166SBenson Leung } 476c6983166SBenson Leung 477c6983166SBenson Leung /* Perform initial detection */ 478c6983166SBenson Leung ret = extcon_cros_ec_detect_cable(info, true); 479c6983166SBenson Leung if (ret < 0) { 480c6983166SBenson Leung dev_err(dev, "failed to detect initial cable state\n"); 481c6983166SBenson Leung goto unregister_notifier; 482c6983166SBenson Leung } 483c6983166SBenson Leung 484c6983166SBenson Leung return 0; 485c6983166SBenson Leung 486c6983166SBenson Leung unregister_notifier: 487c6983166SBenson Leung blocking_notifier_chain_unregister(&info->ec->event_notifier, 488c6983166SBenson Leung &info->notifier); 489c6983166SBenson Leung return ret; 490c6983166SBenson Leung } 491c6983166SBenson Leung 492c6983166SBenson Leung static int extcon_cros_ec_remove(struct platform_device *pdev) 493c6983166SBenson Leung { 494c6983166SBenson Leung struct cros_ec_extcon_info *info = platform_get_drvdata(pdev); 495c6983166SBenson Leung 496c6983166SBenson Leung blocking_notifier_chain_unregister(&info->ec->event_notifier, 497c6983166SBenson Leung &info->notifier); 498c6983166SBenson Leung 499c6983166SBenson Leung return 0; 500c6983166SBenson Leung } 501c6983166SBenson Leung 502c6983166SBenson Leung #ifdef CONFIG_PM_SLEEP 503c6983166SBenson Leung static int extcon_cros_ec_suspend(struct device *dev) 504c6983166SBenson Leung { 505c6983166SBenson Leung return 0; 506c6983166SBenson Leung } 507c6983166SBenson Leung 508c6983166SBenson Leung static int extcon_cros_ec_resume(struct device *dev) 509c6983166SBenson Leung { 510c6983166SBenson Leung int ret; 511c6983166SBenson Leung struct cros_ec_extcon_info *info = dev_get_drvdata(dev); 512c6983166SBenson Leung 513c6983166SBenson Leung ret = extcon_cros_ec_detect_cable(info, true); 514c6983166SBenson Leung if (ret < 0) 515c6983166SBenson Leung dev_err(dev, "failed to detect cable state on resume\n"); 516c6983166SBenson Leung 517c6983166SBenson Leung return 0; 518c6983166SBenson Leung } 519c6983166SBenson Leung 520c6983166SBenson Leung static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops = { 521c6983166SBenson Leung SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend, extcon_cros_ec_resume) 522c6983166SBenson Leung }; 523c6983166SBenson Leung 524c6983166SBenson Leung #define DEV_PM_OPS (&extcon_cros_ec_dev_pm_ops) 525c6983166SBenson Leung #else 526c6983166SBenson Leung #define DEV_PM_OPS NULL 527c6983166SBenson Leung #endif /* CONFIG_PM_SLEEP */ 528c6983166SBenson Leung 529c6983166SBenson Leung #ifdef CONFIG_OF 530c6983166SBenson Leung static const struct of_device_id extcon_cros_ec_of_match[] = { 531c6983166SBenson Leung { .compatible = "google,extcon-usbc-cros-ec" }, 532c6983166SBenson Leung { /* sentinel */ } 533c6983166SBenson Leung }; 534c6983166SBenson Leung MODULE_DEVICE_TABLE(of, extcon_cros_ec_of_match); 535c6983166SBenson Leung #endif /* CONFIG_OF */ 536c6983166SBenson Leung 537c6983166SBenson Leung static struct platform_driver extcon_cros_ec_driver = { 538c6983166SBenson Leung .driver = { 539c6983166SBenson Leung .name = "extcon-usbc-cros-ec", 540c6983166SBenson Leung .of_match_table = of_match_ptr(extcon_cros_ec_of_match), 541c6983166SBenson Leung .pm = DEV_PM_OPS, 542c6983166SBenson Leung }, 543c6983166SBenson Leung .remove = extcon_cros_ec_remove, 544c6983166SBenson Leung .probe = extcon_cros_ec_probe, 545c6983166SBenson Leung }; 546c6983166SBenson Leung 547c6983166SBenson Leung module_platform_driver(extcon_cros_ec_driver); 548c6983166SBenson Leung 549c6983166SBenson Leung MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver"); 550c6983166SBenson Leung MODULE_AUTHOR("Benson Leung <bleung@chromium.org>"); 551c6983166SBenson Leung MODULE_LICENSE("GPL"); 552