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