linux/drivers/power/supply/isp1704_charger.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * ISP1704 USB Charger Detection driver
   4 *
   5 * Copyright (C) 2010 Nokia Corporation
   6 * Copyright (C) 2012 - 2013 Pali Rohár <pali@kernel.org>
   7 */
   8
   9#include <linux/kernel.h>
  10#include <linux/module.h>
  11#include <linux/err.h>
  12#include <linux/init.h>
  13#include <linux/types.h>
  14#include <linux/device.h>
  15#include <linux/sysfs.h>
  16#include <linux/platform_device.h>
  17#include <linux/power_supply.h>
  18#include <linux/delay.h>
  19#include <linux/of.h>
  20
  21#include <linux/gpio/consumer.h>
  22#include <linux/usb/otg.h>
  23#include <linux/usb/ulpi.h>
  24#include <linux/usb/ch9.h>
  25#include <linux/usb/gadget.h>
  26
  27/* Vendor specific Power Control register */
  28#define ISP1704_PWR_CTRL                0x3d
  29#define ISP1704_PWR_CTRL_SWCTRL         (1 << 0)
  30#define ISP1704_PWR_CTRL_DET_COMP       (1 << 1)
  31#define ISP1704_PWR_CTRL_BVALID_RISE    (1 << 2)
  32#define ISP1704_PWR_CTRL_BVALID_FALL    (1 << 3)
  33#define ISP1704_PWR_CTRL_DP_WKPU_EN     (1 << 4)
  34#define ISP1704_PWR_CTRL_VDAT_DET       (1 << 5)
  35#define ISP1704_PWR_CTRL_DPVSRC_EN      (1 << 6)
  36#define ISP1704_PWR_CTRL_HWDETECT       (1 << 7)
  37
  38#define NXP_VENDOR_ID                   0x04cc
  39
  40static u16 isp170x_id[] = {
  41        0x1704,
  42        0x1707,
  43};
  44
  45struct isp1704_charger {
  46        struct device                   *dev;
  47        struct power_supply             *psy;
  48        struct power_supply_desc        psy_desc;
  49        struct gpio_desc                *enable_gpio;
  50        struct usb_phy                  *phy;
  51        struct notifier_block           nb;
  52        struct work_struct              work;
  53
  54        /* properties */
  55        char                    model[8];
  56        unsigned                present:1;
  57        unsigned                online:1;
  58        unsigned                current_max;
  59};
  60
  61static inline int isp1704_read(struct isp1704_charger *isp, u32 reg)
  62{
  63        return usb_phy_io_read(isp->phy, reg);
  64}
  65
  66static inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val)
  67{
  68        return usb_phy_io_write(isp->phy, val, reg);
  69}
  70
  71static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on)
  72{
  73        gpiod_set_value(isp->enable_gpio, on);
  74}
  75
  76/*
  77 * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB
  78 * chargers).
  79 *
  80 * REVISIT: The method is defined in Battery Charging Specification and is
  81 * applicable to any ULPI transceiver. Nothing isp170x specific here.
  82 */
  83static inline int isp1704_charger_type(struct isp1704_charger *isp)
  84{
  85        u8 reg;
  86        u8 func_ctrl;
  87        u8 otg_ctrl;
  88        int type = POWER_SUPPLY_TYPE_USB_DCP;
  89
  90        func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL);
  91        otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL);
  92
  93        /* disable pulldowns */
  94        reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;
  95        isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg);
  96
  97        /* full speed */
  98        isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
  99                        ULPI_FUNC_CTRL_XCVRSEL_MASK);
 100        isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL),
 101                        ULPI_FUNC_CTRL_FULL_SPEED);
 102
 103        /* Enable strong pull-up on DP (1.5K) and reset */
 104        reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
 105        isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg);
 106        usleep_range(1000, 2000);
 107
 108        reg = isp1704_read(isp, ULPI_DEBUG);
 109        if ((reg & 3) != 3)
 110                type = POWER_SUPPLY_TYPE_USB_CDP;
 111
 112        /* recover original state */
 113        isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl);
 114        isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl);
 115
 116        return type;
 117}
 118
 119/*
 120 * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger
 121 * is actually a dedicated charger, the following steps need to be taken.
 122 */
 123static inline int isp1704_charger_verify(struct isp1704_charger *isp)
 124{
 125        int     ret = 0;
 126        u8      r;
 127
 128        /* Reset the transceiver */
 129        r = isp1704_read(isp, ULPI_FUNC_CTRL);
 130        r |= ULPI_FUNC_CTRL_RESET;
 131        isp1704_write(isp, ULPI_FUNC_CTRL, r);
 132        usleep_range(1000, 2000);
 133
 134        /* Set normal mode */
 135        r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);
 136        isp1704_write(isp, ULPI_FUNC_CTRL, r);
 137
 138        /* Clear the DP and DM pull-down bits */
 139        r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;
 140        isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r);
 141
 142        /* Enable strong pull-up on DP (1.5K) and reset */
 143        r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
 144        isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r);
 145        usleep_range(1000, 2000);
 146
 147        /* Read the line state */
 148        if (!isp1704_read(isp, ULPI_DEBUG)) {
 149                /* Disable strong pull-up on DP (1.5K) */
 150                isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
 151                                ULPI_FUNC_CTRL_TERMSELECT);
 152                return 1;
 153        }
 154
 155        /* Is it a charger or PS/2 connection */
 156
 157        /* Enable weak pull-up resistor on DP */
 158        isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),
 159                        ISP1704_PWR_CTRL_DP_WKPU_EN);
 160
 161        /* Disable strong pull-up on DP (1.5K) */
 162        isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
 163                        ULPI_FUNC_CTRL_TERMSELECT);
 164
 165        /* Enable weak pull-down resistor on DM */
 166        isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL),
 167                        ULPI_OTG_CTRL_DM_PULLDOWN);
 168
 169        /* It's a charger if the line states are clear */
 170        if (!(isp1704_read(isp, ULPI_DEBUG)))
 171                ret = 1;
 172
 173        /* Disable weak pull-up resistor on DP */
 174        isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL),
 175                        ISP1704_PWR_CTRL_DP_WKPU_EN);
 176
 177        return ret;
 178}
 179
 180static inline int isp1704_charger_detect(struct isp1704_charger *isp)
 181{
 182        unsigned long   timeout;
 183        u8              pwr_ctrl;
 184        int             ret = 0;
 185
 186        pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL);
 187
 188        /* set SW control bit in PWR_CTRL register */
 189        isp1704_write(isp, ISP1704_PWR_CTRL,
 190                        ISP1704_PWR_CTRL_SWCTRL);
 191
 192        /* enable manual charger detection */
 193        isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),
 194                        ISP1704_PWR_CTRL_SWCTRL
 195                        | ISP1704_PWR_CTRL_DPVSRC_EN);
 196        usleep_range(1000, 2000);
 197
 198        timeout = jiffies + msecs_to_jiffies(300);
 199        do {
 200                /* Check if there is a charger */
 201                if (isp1704_read(isp, ISP1704_PWR_CTRL)
 202                                & ISP1704_PWR_CTRL_VDAT_DET) {
 203                        ret = isp1704_charger_verify(isp);
 204                        break;
 205                }
 206        } while (!time_after(jiffies, timeout) && isp->online);
 207
 208        /* recover original state */
 209        isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl);
 210
 211        return ret;
 212}
 213
 214static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp)
 215{
 216        if (isp1704_charger_detect(isp) &&
 217                        isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP)
 218                return true;
 219        else
 220                return false;
 221}
 222
 223static void isp1704_charger_work(struct work_struct *data)
 224{
 225        struct isp1704_charger  *isp =
 226                container_of(data, struct isp1704_charger, work);
 227        static DEFINE_MUTEX(lock);
 228
 229        mutex_lock(&lock);
 230
 231        switch (isp->phy->last_event) {
 232        case USB_EVENT_VBUS:
 233                /* do not call wall charger detection more times */
 234                if (!isp->present) {
 235                        isp->online = true;
 236                        isp->present = 1;
 237                        isp1704_charger_set_power(isp, 1);
 238
 239                        /* detect wall charger */
 240                        if (isp1704_charger_detect_dcp(isp)) {
 241                                isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
 242                                isp->current_max = 1800;
 243                        } else {
 244                                isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;
 245                                isp->current_max = 500;
 246                        }
 247
 248                        /* enable data pullups */
 249                        if (isp->phy->otg->gadget)
 250                                usb_gadget_connect(isp->phy->otg->gadget);
 251                }
 252
 253                if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) {
 254                        /*
 255                         * Only 500mA here or high speed chirp
 256                         * handshaking may break
 257                         */
 258                        if (isp->current_max > 500)
 259                                isp->current_max = 500;
 260
 261                        if (isp->current_max > 100)
 262                                isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
 263                }
 264                break;
 265        case USB_EVENT_NONE:
 266                isp->online = false;
 267                isp->present = 0;
 268                isp->current_max = 0;
 269                isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;
 270
 271                /*
 272                 * Disable data pullups. We need to prevent the controller from
 273                 * enumerating.
 274                 *
 275                 * FIXME: This is here to allow charger detection with Host/HUB
 276                 * chargers. The pullups may be enabled elsewhere, so this can
 277                 * not be the final solution.
 278                 */
 279                if (isp->phy->otg->gadget)
 280                        usb_gadget_disconnect(isp->phy->otg->gadget);
 281
 282                isp1704_charger_set_power(isp, 0);
 283                break;
 284        default:
 285                goto out;
 286        }
 287
 288        power_supply_changed(isp->psy);
 289out:
 290        mutex_unlock(&lock);
 291}
 292
 293static int isp1704_notifier_call(struct notifier_block *nb,
 294                unsigned long val, void *v)
 295{
 296        struct isp1704_charger *isp =
 297                container_of(nb, struct isp1704_charger, nb);
 298
 299        schedule_work(&isp->work);
 300
 301        return NOTIFY_OK;
 302}
 303
 304static int isp1704_charger_get_property(struct power_supply *psy,
 305                                enum power_supply_property psp,
 306                                union power_supply_propval *val)
 307{
 308        struct isp1704_charger *isp = power_supply_get_drvdata(psy);
 309
 310        switch (psp) {
 311        case POWER_SUPPLY_PROP_PRESENT:
 312                val->intval = isp->present;
 313                break;
 314        case POWER_SUPPLY_PROP_ONLINE:
 315                val->intval = isp->online;
 316                break;
 317        case POWER_SUPPLY_PROP_CURRENT_MAX:
 318                val->intval = isp->current_max;
 319                break;
 320        case POWER_SUPPLY_PROP_MODEL_NAME:
 321                val->strval = isp->model;
 322                break;
 323        case POWER_SUPPLY_PROP_MANUFACTURER:
 324                val->strval = "NXP";
 325                break;
 326        default:
 327                return -EINVAL;
 328        }
 329        return 0;
 330}
 331
 332static enum power_supply_property power_props[] = {
 333        POWER_SUPPLY_PROP_PRESENT,
 334        POWER_SUPPLY_PROP_ONLINE,
 335        POWER_SUPPLY_PROP_CURRENT_MAX,
 336        POWER_SUPPLY_PROP_MODEL_NAME,
 337        POWER_SUPPLY_PROP_MANUFACTURER,
 338};
 339
 340static inline int isp1704_test_ulpi(struct isp1704_charger *isp)
 341{
 342        int vendor;
 343        int product;
 344        int i;
 345        int ret;
 346
 347        /* Test ULPI interface */
 348        ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa);
 349        if (ret < 0)
 350                return ret;
 351
 352        ret = isp1704_read(isp, ULPI_SCRATCH);
 353        if (ret < 0)
 354                return ret;
 355
 356        if (ret != 0xaa)
 357                return -ENODEV;
 358
 359        /* Verify the product and vendor id matches */
 360        vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW);
 361        vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8;
 362        if (vendor != NXP_VENDOR_ID)
 363                return -ENODEV;
 364
 365        product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW);
 366        product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8;
 367
 368        for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) {
 369                if (product == isp170x_id[i]) {
 370                        sprintf(isp->model, "isp%x", product);
 371                        return product;
 372                }
 373        }
 374
 375        dev_err(isp->dev, "product id %x not matching known ids", product);
 376
 377        return -ENODEV;
 378}
 379
 380static int isp1704_charger_probe(struct platform_device *pdev)
 381{
 382        struct isp1704_charger  *isp;
 383        int                     ret = -ENODEV;
 384        struct power_supply_config psy_cfg = {};
 385
 386        isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);
 387        if (!isp)
 388                return -ENOMEM;
 389
 390        isp->enable_gpio = devm_gpiod_get(&pdev->dev, "nxp,enable",
 391                                          GPIOD_OUT_HIGH);
 392        if (IS_ERR(isp->enable_gpio)) {
 393                ret = PTR_ERR(isp->enable_gpio);
 394                dev_err(&pdev->dev, "Could not get reset gpio: %d\n", ret);
 395                return ret;
 396        }
 397
 398        if (pdev->dev.of_node)
 399                isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
 400        else
 401                isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
 402
 403        if (IS_ERR(isp->phy)) {
 404                ret = PTR_ERR(isp->phy);
 405                dev_err(&pdev->dev, "usb_get_phy failed\n");
 406                goto fail0;
 407        }
 408
 409        isp->dev = &pdev->dev;
 410        platform_set_drvdata(pdev, isp);
 411
 412        isp1704_charger_set_power(isp, 1);
 413
 414        ret = isp1704_test_ulpi(isp);
 415        if (ret < 0) {
 416                dev_err(&pdev->dev, "isp1704_test_ulpi failed\n");
 417                goto fail1;
 418        }
 419
 420        isp->psy_desc.name              = "isp1704";
 421        isp->psy_desc.type              = POWER_SUPPLY_TYPE_USB;
 422        isp->psy_desc.properties        = power_props;
 423        isp->psy_desc.num_properties    = ARRAY_SIZE(power_props);
 424        isp->psy_desc.get_property      = isp1704_charger_get_property;
 425
 426        psy_cfg.drv_data                = isp;
 427
 428        isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg);
 429        if (IS_ERR(isp->psy)) {
 430                ret = PTR_ERR(isp->psy);
 431                dev_err(&pdev->dev, "power_supply_register failed\n");
 432                goto fail1;
 433        }
 434
 435        /*
 436         * REVISIT: using work in order to allow the usb notifications to be
 437         * made atomically in the future.
 438         */
 439        INIT_WORK(&isp->work, isp1704_charger_work);
 440
 441        isp->nb.notifier_call = isp1704_notifier_call;
 442
 443        ret = usb_register_notifier(isp->phy, &isp->nb);
 444        if (ret) {
 445                dev_err(&pdev->dev, "usb_register_notifier failed\n");
 446                goto fail2;
 447        }
 448
 449        dev_info(isp->dev, "registered with product id %s\n", isp->model);
 450
 451        /*
 452         * Taking over the D+ pullup.
 453         *
 454         * FIXME: The device will be disconnected if it was already
 455         * enumerated. The charger driver should be always loaded before any
 456         * gadget is loaded.
 457         */
 458        if (isp->phy->otg->gadget)
 459                usb_gadget_disconnect(isp->phy->otg->gadget);
 460
 461        if (isp->phy->last_event == USB_EVENT_NONE)
 462                isp1704_charger_set_power(isp, 0);
 463
 464        /* Detect charger if VBUS is valid (the cable was already plugged). */
 465        if (isp->phy->last_event == USB_EVENT_VBUS &&
 466                        !isp->phy->otg->default_a)
 467                schedule_work(&isp->work);
 468
 469        return 0;
 470fail2:
 471        power_supply_unregister(isp->psy);
 472fail1:
 473        isp1704_charger_set_power(isp, 0);
 474fail0:
 475        dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
 476
 477        return ret;
 478}
 479
 480static int isp1704_charger_remove(struct platform_device *pdev)
 481{
 482        struct isp1704_charger *isp = platform_get_drvdata(pdev);
 483
 484        usb_unregister_notifier(isp->phy, &isp->nb);
 485        power_supply_unregister(isp->psy);
 486        isp1704_charger_set_power(isp, 0);
 487
 488        return 0;
 489}
 490
 491#ifdef CONFIG_OF
 492static const struct of_device_id omap_isp1704_of_match[] = {
 493        { .compatible = "nxp,isp1704", },
 494        { .compatible = "nxp,isp1707", },
 495        {},
 496};
 497MODULE_DEVICE_TABLE(of, omap_isp1704_of_match);
 498#endif
 499
 500static struct platform_driver isp1704_charger_driver = {
 501        .driver = {
 502                .name = "isp1704_charger",
 503                .of_match_table = of_match_ptr(omap_isp1704_of_match),
 504        },
 505        .probe = isp1704_charger_probe,
 506        .remove = isp1704_charger_remove,
 507};
 508
 509module_platform_driver(isp1704_charger_driver);
 510
 511MODULE_ALIAS("platform:isp1704_charger");
 512MODULE_AUTHOR("Nokia Corporation");
 513MODULE_DESCRIPTION("ISP170x USB Charger driver");
 514MODULE_LICENSE("GPL");
 515