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