Root/drivers/power/isp1704_charger.c

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

Archive Download this file



interactive