blob: 72512185f3e281febf049f23dde76464782ed92c [file] [log] [blame]
Heikki Krogerusec464752010-08-19 15:09:36 +03001/*
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
50static u16 isp170x_id[] = {
51 0x1704,
52 0x1707,
53};
54
55struct 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 */
70static 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
127static 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
155static 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
185static 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
208static 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
231static enum power_supply_property power_props[] = {
232 POWER_SUPPLY_PROP_PRESENT,
233 POWER_SUPPLY_PROP_MODEL_NAME,
234 POWER_SUPPLY_PROP_MANUFACTURER,
235};
236
237static 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
277static 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;
322fail2:
323 power_supply_unregister(&isp->psy);
324fail1:
325 otg_put_transceiver(isp->otg);
326fail0:
327 kfree(isp);
328
329 dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
330
331 return ret;
332}
333
334static 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
346static 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
354static int __init isp1704_charger_init(void)
355{
356 return platform_driver_register(&isp1704_charger_driver);
357}
358module_init(isp1704_charger_init);
359
360static void __exit isp1704_charger_exit(void)
361{
362 platform_driver_unregister(&isp1704_charger_driver);
363}
364module_exit(isp1704_charger_exit);
365
366MODULE_ALIAS("platform:isp1704_charger");
367MODULE_AUTHOR("Nokia Corporation");
368MODULE_DESCRIPTION("ISP170x USB Charger driver");
369MODULE_LICENSE("GPL");