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