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