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