]> bbs.cooldavid.org Git - net-next-2.6.git/blob - drivers/power/isp1704_charger.c
bnx2x: Disable local BHes to prevent a dead-lock situation
[net-next-2.6.git] / 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
37 /* Vendor specific Power Control register */
38 #define ISP1704_PWR_CTRL                0x3d
39 #define ISP1704_PWR_CTRL_SWCTRL         (1 << 0)
40 #define ISP1704_PWR_CTRL_DET_COMP       (1 << 1)
41 #define ISP1704_PWR_CTRL_BVALID_RISE    (1 << 2)
42 #define ISP1704_PWR_CTRL_BVALID_FALL    (1 << 3)
43 #define ISP1704_PWR_CTRL_DP_WKPU_EN     (1 << 4)
44 #define ISP1704_PWR_CTRL_VDAT_DET       (1 << 5)
45 #define ISP1704_PWR_CTRL_DPVSRC_EN      (1 << 6)
46 #define ISP1704_PWR_CTRL_HWDETECT       (1 << 7)
47
48 #define NXP_VENDOR_ID                   0x04cc
49
50 static u16 isp170x_id[] = {
51         0x1704,
52         0x1707,
53 };
54
55 struct isp1704_charger {
56         struct device           *dev;
57         struct power_supply     psy;
58         struct otg_transceiver  *otg;
59         struct notifier_block   nb;
60         struct work_struct      work;
61
62         char                    model[7];
63         unsigned                present:1;
64 };
65
66 /*
67  * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger
68  * is actually a dedicated charger, the following steps need to be taken.
69  */
70 static inline int isp1704_charger_verify(struct isp1704_charger *isp)
71 {
72         int     ret = 0;
73         u8      r;
74
75         /* Reset the transceiver */
76         r = otg_io_read(isp->otg, ULPI_FUNC_CTRL);
77         r |= ULPI_FUNC_CTRL_RESET;
78         otg_io_write(isp->otg, ULPI_FUNC_CTRL, r);
79         usleep_range(1000, 2000);
80
81         /* Set normal mode */
82         r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);
83         otg_io_write(isp->otg, ULPI_FUNC_CTRL, r);
84
85         /* Clear the DP and DM pull-down bits */
86         r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;
87         otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), r);
88
89         /* Enable strong pull-up on DP (1.5K) and reset */
90         r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
91         otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), r);
92         usleep_range(1000, 2000);
93
94         /* Read the line state */
95         if (!otg_io_read(isp->otg, ULPI_DEBUG)) {
96                 /* Disable strong pull-up on DP (1.5K) */
97                 otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL),
98                                 ULPI_FUNC_CTRL_TERMSELECT);
99                 return 1;
100         }
101
102         /* Is it a charger or PS/2 connection */
103
104         /* Enable weak pull-up resistor on DP */
105         otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL),
106                         ISP1704_PWR_CTRL_DP_WKPU_EN);
107
108         /* Disable strong pull-up on DP (1.5K) */
109         otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL),
110                         ULPI_FUNC_CTRL_TERMSELECT);
111
112         /* Enable weak pull-down resistor on DM */
113         otg_io_write(isp->otg, ULPI_SET(ULPI_OTG_CTRL),
114                         ULPI_OTG_CTRL_DM_PULLDOWN);
115
116         /* It's a charger if the line states are clear */
117         if (!(otg_io_read(isp->otg, ULPI_DEBUG)))
118                 ret = 1;
119
120         /* Disable weak pull-up resistor on DP */
121         otg_io_write(isp->otg, ULPI_CLR(ISP1704_PWR_CTRL),
122                         ISP1704_PWR_CTRL_DP_WKPU_EN);
123
124         return ret;
125 }
126
127 static inline int isp1704_charger_detect(struct isp1704_charger *isp)
128 {
129         unsigned long   timeout;
130         u8              r;
131         int             ret = 0;
132
133         /* set SW control bit in PWR_CTRL register */
134         otg_io_write(isp->otg, ISP1704_PWR_CTRL,
135                         ISP1704_PWR_CTRL_SWCTRL);
136
137         /* enable manual charger detection */
138         r = (ISP1704_PWR_CTRL_SWCTRL | ISP1704_PWR_CTRL_DPVSRC_EN);
139         otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), r);
140         usleep_range(1000, 2000);
141
142         timeout = jiffies + msecs_to_jiffies(300);
143         do {
144                 /* Check if there is a charger */
145                 if (otg_io_read(isp->otg, ISP1704_PWR_CTRL)
146                                 & ISP1704_PWR_CTRL_VDAT_DET) {
147                         ret = isp1704_charger_verify(isp);
148                         break;
149                 }
150         } while (!time_after(jiffies, timeout));
151
152         return ret;
153 }
154
155 static void isp1704_charger_work(struct work_struct *data)
156 {
157         int                     detect;
158         struct isp1704_charger  *isp =
159                 container_of(data, struct isp1704_charger, work);
160
161         /*
162          * FIXME Only supporting dedicated chargers even though isp1704 can
163          * detect HUB and HOST chargers. If the device has already been
164          * enumerated, the detection will break the connection.
165          */
166         if (isp->otg->state != OTG_STATE_B_IDLE)
167                 return;
168
169         /* disable data pullups */
170         if (isp->otg->gadget)
171                 usb_gadget_disconnect(isp->otg->gadget);
172
173         /* detect charger */
174         detect = isp1704_charger_detect(isp);
175         if (detect) {
176                 isp->present = detect;
177                 power_supply_changed(&isp->psy);
178         }
179
180         /* enable data pullups */
181         if (isp->otg->gadget)
182                 usb_gadget_connect(isp->otg->gadget);
183 }
184
185 static int isp1704_notifier_call(struct notifier_block *nb,
186                 unsigned long event, void *unused)
187 {
188         struct isp1704_charger *isp =
189                 container_of(nb, struct isp1704_charger, nb);
190
191         switch (event) {
192         case USB_EVENT_VBUS:
193                 schedule_work(&isp->work);
194                 break;
195         case USB_EVENT_NONE:
196                 if (isp->present) {
197                         isp->present = 0;
198                         power_supply_changed(&isp->psy);
199                 }
200                 break;
201         default:
202                 return NOTIFY_DONE;
203         }
204
205         return NOTIFY_OK;
206 }
207
208 static int isp1704_charger_get_property(struct power_supply *psy,
209                                 enum power_supply_property psp,
210                                 union power_supply_propval *val)
211 {
212         struct isp1704_charger *isp =
213                 container_of(psy, struct isp1704_charger, psy);
214
215         switch (psp) {
216         case POWER_SUPPLY_PROP_PRESENT:
217                 val->intval = isp->present;
218                 break;
219         case POWER_SUPPLY_PROP_MODEL_NAME:
220                 val->strval = isp->model;
221                 break;
222         case POWER_SUPPLY_PROP_MANUFACTURER:
223                 val->strval = "NXP";
224                 break;
225         default:
226                 return -EINVAL;
227         }
228         return 0;
229 }
230
231 static enum power_supply_property power_props[] = {
232         POWER_SUPPLY_PROP_PRESENT,
233         POWER_SUPPLY_PROP_MODEL_NAME,
234         POWER_SUPPLY_PROP_MANUFACTURER,
235 };
236
237 static inline int isp1704_test_ulpi(struct isp1704_charger *isp)
238 {
239         int vendor;
240         int product;
241         int i;
242         int ret = -ENODEV;
243
244         /* Test ULPI interface */
245         ret = otg_io_write(isp->otg, ULPI_SCRATCH, 0xaa);
246         if (ret < 0)
247                 return ret;
248
249         ret = otg_io_read(isp->otg, ULPI_SCRATCH);
250         if (ret < 0)
251                 return ret;
252
253         if (ret != 0xaa)
254                 return -ENODEV;
255
256         /* Verify the product and vendor id matches */
257         vendor = otg_io_read(isp->otg, ULPI_VENDOR_ID_LOW);
258         vendor |= otg_io_read(isp->otg, ULPI_VENDOR_ID_HIGH) << 8;
259         if (vendor != NXP_VENDOR_ID)
260                 return -ENODEV;
261
262         product = otg_io_read(isp->otg, ULPI_PRODUCT_ID_LOW);
263         product |= otg_io_read(isp->otg, ULPI_PRODUCT_ID_HIGH) << 8;
264
265         for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) {
266                 if (product == isp170x_id[i]) {
267                         sprintf(isp->model, "isp%x", product);
268                         return product;
269                 }
270         }
271
272         dev_err(isp->dev, "product id %x not matching known ids", product);
273
274         return -ENODEV;
275 }
276
277 static int __devinit isp1704_charger_probe(struct platform_device *pdev)
278 {
279         struct isp1704_charger  *isp;
280         int                     ret = -ENODEV;
281
282         isp = kzalloc(sizeof *isp, GFP_KERNEL);
283         if (!isp)
284                 return -ENOMEM;
285
286         isp->otg = otg_get_transceiver();
287         if (!isp->otg)
288                 goto fail0;
289
290         ret = isp1704_test_ulpi(isp);
291         if (ret < 0)
292                 goto fail1;
293
294         isp->dev = &pdev->dev;
295         platform_set_drvdata(pdev, isp);
296
297         isp->psy.name           = "isp1704";
298         isp->psy.type           = POWER_SUPPLY_TYPE_USB;
299         isp->psy.properties     = power_props;
300         isp->psy.num_properties = ARRAY_SIZE(power_props);
301         isp->psy.get_property   = isp1704_charger_get_property;
302
303         ret = power_supply_register(isp->dev, &isp->psy);
304         if (ret)
305                 goto fail1;
306
307         /*
308          * REVISIT: using work in order to allow the otg notifications to be
309          * made atomically in the future.
310          */
311         INIT_WORK(&isp->work, isp1704_charger_work);
312
313         isp->nb.notifier_call = isp1704_notifier_call;
314
315         ret = otg_register_notifier(isp->otg, &isp->nb);
316         if (ret)
317                 goto fail2;
318
319         dev_info(isp->dev, "registered with product id %s\n", isp->model);
320
321         return 0;
322 fail2:
323         power_supply_unregister(&isp->psy);
324 fail1:
325         otg_put_transceiver(isp->otg);
326 fail0:
327         kfree(isp);
328
329         dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
330
331         return ret;
332 }
333
334 static int __devexit isp1704_charger_remove(struct platform_device *pdev)
335 {
336         struct isp1704_charger *isp = platform_get_drvdata(pdev);
337
338         otg_unregister_notifier(isp->otg, &isp->nb);
339         power_supply_unregister(&isp->psy);
340         otg_put_transceiver(isp->otg);
341         kfree(isp);
342
343         return 0;
344 }
345
346 static struct platform_driver isp1704_charger_driver = {
347         .driver = {
348                 .name = "isp1704_charger",
349         },
350         .probe = isp1704_charger_probe,
351         .remove = __devexit_p(isp1704_charger_remove),
352 };
353
354 static int __init isp1704_charger_init(void)
355 {
356         return platform_driver_register(&isp1704_charger_driver);
357 }
358 module_init(isp1704_charger_init);
359
360 static void __exit isp1704_charger_exit(void)
361 {
362         platform_driver_unregister(&isp1704_charger_driver);
363 }
364 module_exit(isp1704_charger_exit);
365
366 MODULE_ALIAS("platform:isp1704_charger");
367 MODULE_AUTHOR("Nokia Corporation");
368 MODULE_DESCRIPTION("ISP170x USB Charger driver");
369 MODULE_LICENSE("GPL");