Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 // ChromeOS Embedded Controller extcon
0003 //
0004 // Copyright (C) 2017 Google, Inc.
0005 // Author: Benson Leung <bleung@chromium.org>
0006 
0007 #include <linux/extcon-provider.h>
0008 #include <linux/kernel.h>
0009 #include <linux/module.h>
0010 #include <linux/notifier.h>
0011 #include <linux/of.h>
0012 #include <linux/platform_data/cros_ec_commands.h>
0013 #include <linux/platform_data/cros_ec_proto.h>
0014 #include <linux/platform_device.h>
0015 #include <linux/slab.h>
0016 #include <linux/sched.h>
0017 
0018 struct cros_ec_extcon_info {
0019     struct device *dev;
0020     struct extcon_dev *edev;
0021 
0022     int port_id;
0023 
0024     struct cros_ec_device *ec;
0025 
0026     struct notifier_block notifier;
0027 
0028     unsigned int dr; /* data role */
0029     bool pr; /* power role (true if VBUS enabled) */
0030     bool dp; /* DisplayPort enabled */
0031     bool mux; /* SuperSpeed (usb3) enabled */
0032     unsigned int power_type;
0033 };
0034 
0035 static const unsigned int usb_type_c_cable[] = {
0036     EXTCON_USB,
0037     EXTCON_USB_HOST,
0038     EXTCON_DISP_DP,
0039     EXTCON_NONE,
0040 };
0041 
0042 enum usb_data_roles {
0043     DR_NONE,
0044     DR_HOST,
0045     DR_DEVICE,
0046 };
0047 
0048 /**
0049  * cros_ec_pd_command() - Send a command to the EC.
0050  * @info: pointer to struct cros_ec_extcon_info
0051  * @command: EC command
0052  * @version: EC command version
0053  * @outdata: EC command output data
0054  * @outsize: Size of outdata
0055  * @indata: EC command input data
0056  * @insize: Size of indata
0057  *
0058  * Return: 0 on success, <0 on failure.
0059  */
0060 static int cros_ec_pd_command(struct cros_ec_extcon_info *info,
0061                   unsigned int command,
0062                   unsigned int version,
0063                   void *outdata,
0064                   unsigned int outsize,
0065                   void *indata,
0066                   unsigned int insize)
0067 {
0068     struct cros_ec_command *msg;
0069     int ret;
0070 
0071     msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL);
0072     if (!msg)
0073         return -ENOMEM;
0074 
0075     msg->version = version;
0076     msg->command = command;
0077     msg->outsize = outsize;
0078     msg->insize = insize;
0079 
0080     if (outsize)
0081         memcpy(msg->data, outdata, outsize);
0082 
0083     ret = cros_ec_cmd_xfer_status(info->ec, msg);
0084     if (ret >= 0 && insize)
0085         memcpy(indata, msg->data, insize);
0086 
0087     kfree(msg);
0088     return ret;
0089 }
0090 
0091 /**
0092  * cros_ec_usb_get_power_type() - Get power type info about PD device attached
0093  * to given port.
0094  * @info: pointer to struct cros_ec_extcon_info
0095  *
0096  * Return: power type on success, <0 on failure.
0097  */
0098 static int cros_ec_usb_get_power_type(struct cros_ec_extcon_info *info)
0099 {
0100     struct ec_params_usb_pd_power_info req;
0101     struct ec_response_usb_pd_power_info resp;
0102     int ret;
0103 
0104     req.port = info->port_id;
0105     ret = cros_ec_pd_command(info, EC_CMD_USB_PD_POWER_INFO, 0,
0106                  &req, sizeof(req), &resp, sizeof(resp));
0107     if (ret < 0)
0108         return ret;
0109 
0110     return resp.type;
0111 }
0112 
0113 /**
0114  * cros_ec_usb_get_pd_mux_state() - Get PD mux state for given port.
0115  * @info: pointer to struct cros_ec_extcon_info
0116  *
0117  * Return: PD mux state on success, <0 on failure.
0118  */
0119 static int cros_ec_usb_get_pd_mux_state(struct cros_ec_extcon_info *info)
0120 {
0121     struct ec_params_usb_pd_mux_info req;
0122     struct ec_response_usb_pd_mux_info resp;
0123     int ret;
0124 
0125     req.port = info->port_id;
0126     ret = cros_ec_pd_command(info, EC_CMD_USB_PD_MUX_INFO, 0,
0127                  &req, sizeof(req),
0128                  &resp, sizeof(resp));
0129     if (ret < 0)
0130         return ret;
0131 
0132     return resp.flags;
0133 }
0134 
0135 /**
0136  * cros_ec_usb_get_role() - Get role info about possible PD device attached to a
0137  * given port.
0138  * @info: pointer to struct cros_ec_extcon_info
0139  * @polarity: pointer to cable polarity (return value)
0140  *
0141  * Return: role info on success, -ENOTCONN if no cable is connected, <0 on
0142  * failure.
0143  */
0144 static int cros_ec_usb_get_role(struct cros_ec_extcon_info *info,
0145                 bool *polarity)
0146 {
0147     struct ec_params_usb_pd_control pd_control;
0148     struct ec_response_usb_pd_control_v1 resp;
0149     int ret;
0150 
0151     pd_control.port = info->port_id;
0152     pd_control.role = USB_PD_CTRL_ROLE_NO_CHANGE;
0153     pd_control.mux = USB_PD_CTRL_MUX_NO_CHANGE;
0154     pd_control.swap = USB_PD_CTRL_SWAP_NONE;
0155     ret = cros_ec_pd_command(info, EC_CMD_USB_PD_CONTROL, 1,
0156                  &pd_control, sizeof(pd_control),
0157                  &resp, sizeof(resp));
0158     if (ret < 0)
0159         return ret;
0160 
0161     if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED))
0162         return -ENOTCONN;
0163 
0164     *polarity = resp.polarity;
0165 
0166     return resp.role;
0167 }
0168 
0169 /**
0170  * cros_ec_pd_get_num_ports() - Get number of EC charge ports.
0171  * @info: pointer to struct cros_ec_extcon_info
0172  *
0173  * Return: number of ports on success, <0 on failure.
0174  */
0175 static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info *info)
0176 {
0177     struct ec_response_usb_pd_ports resp;
0178     int ret;
0179 
0180     ret = cros_ec_pd_command(info, EC_CMD_USB_PD_PORTS,
0181                  0, NULL, 0, &resp, sizeof(resp));
0182     if (ret < 0)
0183         return ret;
0184 
0185     return resp.num_ports;
0186 }
0187 
0188 static const char *cros_ec_usb_role_string(unsigned int role)
0189 {
0190     return role == DR_NONE ? "DISCONNECTED" :
0191         (role == DR_HOST ? "DFP" : "UFP");
0192 }
0193 
0194 static const char *cros_ec_usb_power_type_string(unsigned int type)
0195 {
0196     switch (type) {
0197     case USB_CHG_TYPE_NONE:
0198         return "USB_CHG_TYPE_NONE";
0199     case USB_CHG_TYPE_PD:
0200         return "USB_CHG_TYPE_PD";
0201     case USB_CHG_TYPE_PROPRIETARY:
0202         return "USB_CHG_TYPE_PROPRIETARY";
0203     case USB_CHG_TYPE_C:
0204         return "USB_CHG_TYPE_C";
0205     case USB_CHG_TYPE_BC12_DCP:
0206         return "USB_CHG_TYPE_BC12_DCP";
0207     case USB_CHG_TYPE_BC12_CDP:
0208         return "USB_CHG_TYPE_BC12_CDP";
0209     case USB_CHG_TYPE_BC12_SDP:
0210         return "USB_CHG_TYPE_BC12_SDP";
0211     case USB_CHG_TYPE_OTHER:
0212         return "USB_CHG_TYPE_OTHER";
0213     case USB_CHG_TYPE_VBUS:
0214         return "USB_CHG_TYPE_VBUS";
0215     case USB_CHG_TYPE_UNKNOWN:
0216         return "USB_CHG_TYPE_UNKNOWN";
0217     default:
0218         return "USB_CHG_TYPE_UNKNOWN";
0219     }
0220 }
0221 
0222 static bool cros_ec_usb_power_type_is_wall_wart(unsigned int type,
0223                         unsigned int role)
0224 {
0225     switch (type) {
0226     /* FIXME : Guppy, Donnettes, and other chargers will be miscategorized
0227      * because they identify with USB_CHG_TYPE_C, but we can't return true
0228      * here from that code because that breaks Suzy-Q and other kinds of
0229      * USB Type-C cables and peripherals.
0230      */
0231     case USB_CHG_TYPE_PROPRIETARY:
0232     case USB_CHG_TYPE_BC12_DCP:
0233         return true;
0234     case USB_CHG_TYPE_PD:
0235     case USB_CHG_TYPE_C:
0236     case USB_CHG_TYPE_BC12_CDP:
0237     case USB_CHG_TYPE_BC12_SDP:
0238     case USB_CHG_TYPE_OTHER:
0239     case USB_CHG_TYPE_VBUS:
0240     case USB_CHG_TYPE_UNKNOWN:
0241     case USB_CHG_TYPE_NONE:
0242     default:
0243         return false;
0244     }
0245 }
0246 
0247 static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info *info,
0248                        bool force)
0249 {
0250     struct device *dev = info->dev;
0251     int role, power_type;
0252     unsigned int dr = DR_NONE;
0253     bool pr = false;
0254     bool polarity = false;
0255     bool dp = false;
0256     bool mux = false;
0257     bool hpd = false;
0258 
0259     power_type = cros_ec_usb_get_power_type(info);
0260     if (power_type < 0) {
0261         dev_err(dev, "failed getting power type err = %d\n",
0262             power_type);
0263         return power_type;
0264     }
0265 
0266     role = cros_ec_usb_get_role(info, &polarity);
0267     if (role < 0) {
0268         if (role != -ENOTCONN) {
0269             dev_err(dev, "failed getting role err = %d\n", role);
0270             return role;
0271         }
0272         dev_dbg(dev, "disconnected\n");
0273     } else {
0274         int pd_mux_state;
0275 
0276         dr = (role & PD_CTRL_RESP_ROLE_DATA) ? DR_HOST : DR_DEVICE;
0277         pr = (role & PD_CTRL_RESP_ROLE_POWER);
0278         pd_mux_state = cros_ec_usb_get_pd_mux_state(info);
0279         if (pd_mux_state < 0)
0280             pd_mux_state = USB_PD_MUX_USB_ENABLED;
0281 
0282         dp = pd_mux_state & USB_PD_MUX_DP_ENABLED;
0283         mux = pd_mux_state & USB_PD_MUX_USB_ENABLED;
0284         hpd = pd_mux_state & USB_PD_MUX_HPD_IRQ;
0285 
0286         dev_dbg(dev,
0287             "connected role 0x%x pwr type %d dr %d pr %d pol %d mux %d dp %d hpd %d\n",
0288             role, power_type, dr, pr, polarity, mux, dp, hpd);
0289     }
0290 
0291     /*
0292      * When there is no USB host (e.g. USB PD charger),
0293      * we are not really a UFP for the AP.
0294      */
0295     if (dr == DR_DEVICE &&
0296         cros_ec_usb_power_type_is_wall_wart(power_type, role))
0297         dr = DR_NONE;
0298 
0299     if (force || info->dr != dr || info->pr != pr || info->dp != dp ||
0300         info->mux != mux || info->power_type != power_type) {
0301         bool host_connected = false, device_connected = false;
0302 
0303         dev_dbg(dev, "Type/Role switch! type = %s role = %s\n",
0304             cros_ec_usb_power_type_string(power_type),
0305             cros_ec_usb_role_string(dr));
0306         info->dr = dr;
0307         info->pr = pr;
0308         info->dp = dp;
0309         info->mux = mux;
0310         info->power_type = power_type;
0311 
0312         if (dr == DR_DEVICE)
0313             device_connected = true;
0314         else if (dr == DR_HOST)
0315             host_connected = true;
0316 
0317         extcon_set_state(info->edev, EXTCON_USB, device_connected);
0318         extcon_set_state(info->edev, EXTCON_USB_HOST, host_connected);
0319         extcon_set_state(info->edev, EXTCON_DISP_DP, dp);
0320         extcon_set_property(info->edev, EXTCON_USB,
0321                     EXTCON_PROP_USB_VBUS,
0322                     (union extcon_property_value)(int)pr);
0323         extcon_set_property(info->edev, EXTCON_USB_HOST,
0324                     EXTCON_PROP_USB_VBUS,
0325                     (union extcon_property_value)(int)pr);
0326         extcon_set_property(info->edev, EXTCON_USB,
0327                     EXTCON_PROP_USB_TYPEC_POLARITY,
0328                     (union extcon_property_value)(int)polarity);
0329         extcon_set_property(info->edev, EXTCON_USB_HOST,
0330                     EXTCON_PROP_USB_TYPEC_POLARITY,
0331                     (union extcon_property_value)(int)polarity);
0332         extcon_set_property(info->edev, EXTCON_DISP_DP,
0333                     EXTCON_PROP_USB_TYPEC_POLARITY,
0334                     (union extcon_property_value)(int)polarity);
0335         extcon_set_property(info->edev, EXTCON_USB,
0336                     EXTCON_PROP_USB_SS,
0337                     (union extcon_property_value)(int)mux);
0338         extcon_set_property(info->edev, EXTCON_USB_HOST,
0339                     EXTCON_PROP_USB_SS,
0340                     (union extcon_property_value)(int)mux);
0341         extcon_set_property(info->edev, EXTCON_DISP_DP,
0342                     EXTCON_PROP_USB_SS,
0343                     (union extcon_property_value)(int)mux);
0344         extcon_set_property(info->edev, EXTCON_DISP_DP,
0345                     EXTCON_PROP_DISP_HPD,
0346                     (union extcon_property_value)(int)hpd);
0347 
0348         extcon_sync(info->edev, EXTCON_USB);
0349         extcon_sync(info->edev, EXTCON_USB_HOST);
0350         extcon_sync(info->edev, EXTCON_DISP_DP);
0351 
0352     } else if (hpd) {
0353         extcon_set_property(info->edev, EXTCON_DISP_DP,
0354                     EXTCON_PROP_DISP_HPD,
0355                     (union extcon_property_value)(int)hpd);
0356         extcon_sync(info->edev, EXTCON_DISP_DP);
0357     }
0358 
0359     return 0;
0360 }
0361 
0362 static int extcon_cros_ec_event(struct notifier_block *nb,
0363                 unsigned long queued_during_suspend,
0364                 void *_notify)
0365 {
0366     struct cros_ec_extcon_info *info;
0367     struct cros_ec_device *ec;
0368     u32 host_event;
0369 
0370     info = container_of(nb, struct cros_ec_extcon_info, notifier);
0371     ec = info->ec;
0372 
0373     host_event = cros_ec_get_host_event(ec);
0374     if (host_event & (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) |
0375               EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX))) {
0376         extcon_cros_ec_detect_cable(info, false);
0377         return NOTIFY_OK;
0378     }
0379 
0380     return NOTIFY_DONE;
0381 }
0382 
0383 static int extcon_cros_ec_probe(struct platform_device *pdev)
0384 {
0385     struct cros_ec_extcon_info *info;
0386     struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
0387     struct device *dev = &pdev->dev;
0388     struct device_node *np = dev->of_node;
0389     int numports, ret;
0390 
0391     info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
0392     if (!info)
0393         return -ENOMEM;
0394 
0395     info->dev = dev;
0396     info->ec = ec;
0397 
0398     if (np) {
0399         u32 port;
0400 
0401         ret = of_property_read_u32(np, "google,usb-port-id", &port);
0402         if (ret < 0) {
0403             dev_err(dev, "Missing google,usb-port-id property\n");
0404             return ret;
0405         }
0406         info->port_id = port;
0407     } else {
0408         info->port_id = pdev->id;
0409     }
0410 
0411     numports = cros_ec_pd_get_num_ports(info);
0412     if (numports < 0) {
0413         dev_err(dev, "failed getting number of ports! ret = %d\n",
0414             numports);
0415         return numports;
0416     }
0417 
0418     if (info->port_id >= numports) {
0419         dev_err(dev, "This system only supports %d ports\n", numports);
0420         return -ENODEV;
0421     }
0422 
0423     info->edev = devm_extcon_dev_allocate(dev, usb_type_c_cable);
0424     if (IS_ERR(info->edev)) {
0425         dev_err(dev, "failed to allocate extcon device\n");
0426         return -ENOMEM;
0427     }
0428 
0429     ret = devm_extcon_dev_register(dev, info->edev);
0430     if (ret < 0) {
0431         dev_err(dev, "failed to register extcon device\n");
0432         return ret;
0433     }
0434 
0435     extcon_set_property_capability(info->edev, EXTCON_USB,
0436                        EXTCON_PROP_USB_VBUS);
0437     extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
0438                        EXTCON_PROP_USB_VBUS);
0439     extcon_set_property_capability(info->edev, EXTCON_USB,
0440                        EXTCON_PROP_USB_TYPEC_POLARITY);
0441     extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
0442                        EXTCON_PROP_USB_TYPEC_POLARITY);
0443     extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
0444                        EXTCON_PROP_USB_TYPEC_POLARITY);
0445     extcon_set_property_capability(info->edev, EXTCON_USB,
0446                        EXTCON_PROP_USB_SS);
0447     extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
0448                        EXTCON_PROP_USB_SS);
0449     extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
0450                        EXTCON_PROP_USB_SS);
0451     extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
0452                        EXTCON_PROP_DISP_HPD);
0453 
0454     info->dr = DR_NONE;
0455     info->pr = false;
0456 
0457     platform_set_drvdata(pdev, info);
0458 
0459     /* Get PD events from the EC */
0460     info->notifier.notifier_call = extcon_cros_ec_event;
0461     ret = blocking_notifier_chain_register(&info->ec->event_notifier,
0462                            &info->notifier);
0463     if (ret < 0) {
0464         dev_err(dev, "failed to register notifier\n");
0465         return ret;
0466     }
0467 
0468     /* Perform initial detection */
0469     ret = extcon_cros_ec_detect_cable(info, true);
0470     if (ret < 0) {
0471         dev_err(dev, "failed to detect initial cable state\n");
0472         goto unregister_notifier;
0473     }
0474 
0475     return 0;
0476 
0477 unregister_notifier:
0478     blocking_notifier_chain_unregister(&info->ec->event_notifier,
0479                        &info->notifier);
0480     return ret;
0481 }
0482 
0483 static int extcon_cros_ec_remove(struct platform_device *pdev)
0484 {
0485     struct cros_ec_extcon_info *info = platform_get_drvdata(pdev);
0486 
0487     blocking_notifier_chain_unregister(&info->ec->event_notifier,
0488                        &info->notifier);
0489 
0490     return 0;
0491 }
0492 
0493 #ifdef CONFIG_PM_SLEEP
0494 static int extcon_cros_ec_suspend(struct device *dev)
0495 {
0496     return 0;
0497 }
0498 
0499 static int extcon_cros_ec_resume(struct device *dev)
0500 {
0501     int ret;
0502     struct cros_ec_extcon_info *info = dev_get_drvdata(dev);
0503 
0504     ret = extcon_cros_ec_detect_cable(info, true);
0505     if (ret < 0)
0506         dev_err(dev, "failed to detect cable state on resume\n");
0507 
0508     return 0;
0509 }
0510 
0511 static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops = {
0512     SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend, extcon_cros_ec_resume)
0513 };
0514 
0515 #define DEV_PM_OPS  (&extcon_cros_ec_dev_pm_ops)
0516 #else
0517 #define DEV_PM_OPS  NULL
0518 #endif /* CONFIG_PM_SLEEP */
0519 
0520 #ifdef CONFIG_OF
0521 static const struct of_device_id extcon_cros_ec_of_match[] = {
0522     { .compatible = "google,extcon-usbc-cros-ec" },
0523     { /* sentinel */ }
0524 };
0525 MODULE_DEVICE_TABLE(of, extcon_cros_ec_of_match);
0526 #endif /* CONFIG_OF */
0527 
0528 static struct platform_driver extcon_cros_ec_driver = {
0529     .driver = {
0530         .name  = "extcon-usbc-cros-ec",
0531         .of_match_table = of_match_ptr(extcon_cros_ec_of_match),
0532         .pm = DEV_PM_OPS,
0533     },
0534     .remove  = extcon_cros_ec_remove,
0535     .probe   = extcon_cros_ec_probe,
0536 };
0537 
0538 module_platform_driver(extcon_cros_ec_driver);
0539 
0540 MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver");
0541 MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
0542 MODULE_LICENSE("GPL v2");