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