aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power/isp1704_charger.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/isp1704_charger.c')
-rw-r--r--drivers/power/isp1704_charger.c201
1 files changed, 161 insertions, 40 deletions
diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c
index 72512185f3e2..2ad9b14a5ce3 100644
--- a/drivers/power/isp1704_charger.c
+++ b/drivers/power/isp1704_charger.c
@@ -59,11 +59,61 @@ struct isp1704_charger {
59 struct notifier_block nb; 59 struct notifier_block nb;
60 struct work_struct work; 60 struct work_struct work;
61 61
62 char model[7]; 62 /* properties */
63 char model[8];
63 unsigned present:1; 64 unsigned present:1;
65 unsigned online:1;
66 unsigned current_max;
67
68 /* temp storage variables */
69 unsigned long event;
70 unsigned max_power;
64}; 71};
65 72
66/* 73/*
74 * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB
75 * chargers).
76 *
77 * REVISIT: The method is defined in Battery Charging Specification and is
78 * applicable to any ULPI transceiver. Nothing isp170x specific here.
79 */
80static inline int isp1704_charger_type(struct isp1704_charger *isp)
81{
82 u8 reg;
83 u8 func_ctrl;
84 u8 otg_ctrl;
85 int type = POWER_SUPPLY_TYPE_USB_DCP;
86
87 func_ctrl = otg_io_read(isp->otg, ULPI_FUNC_CTRL);
88 otg_ctrl = otg_io_read(isp->otg, ULPI_OTG_CTRL);
89
90 /* disable pulldowns */
91 reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;
92 otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), reg);
93
94 /* full speed */
95 otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL),
96 ULPI_FUNC_CTRL_XCVRSEL_MASK);
97 otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL),
98 ULPI_FUNC_CTRL_FULL_SPEED);
99
100 /* Enable strong pull-up on DP (1.5K) and reset */
101 reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
102 otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), reg);
103 usleep_range(1000, 2000);
104
105 reg = otg_io_read(isp->otg, ULPI_DEBUG);
106 if ((reg & 3) != 3)
107 type = POWER_SUPPLY_TYPE_USB_CDP;
108
109 /* recover original state */
110 otg_io_write(isp->otg, ULPI_FUNC_CTRL, func_ctrl);
111 otg_io_write(isp->otg, ULPI_OTG_CTRL, otg_ctrl);
112
113 return type;
114}
115
116/*
67 * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger 117 * 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. 118 * is actually a dedicated charger, the following steps need to be taken.
69 */ 119 */
@@ -127,16 +177,19 @@ static inline int isp1704_charger_verify(struct isp1704_charger *isp)
127static inline int isp1704_charger_detect(struct isp1704_charger *isp) 177static inline int isp1704_charger_detect(struct isp1704_charger *isp)
128{ 178{
129 unsigned long timeout; 179 unsigned long timeout;
130 u8 r; 180 u8 pwr_ctrl;
131 int ret = 0; 181 int ret = 0;
132 182
183 pwr_ctrl = otg_io_read(isp->otg, ISP1704_PWR_CTRL);
184
133 /* set SW control bit in PWR_CTRL register */ 185 /* set SW control bit in PWR_CTRL register */
134 otg_io_write(isp->otg, ISP1704_PWR_CTRL, 186 otg_io_write(isp->otg, ISP1704_PWR_CTRL,
135 ISP1704_PWR_CTRL_SWCTRL); 187 ISP1704_PWR_CTRL_SWCTRL);
136 188
137 /* enable manual charger detection */ 189 /* enable manual charger detection */
138 r = (ISP1704_PWR_CTRL_SWCTRL | ISP1704_PWR_CTRL_DPVSRC_EN); 190 otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL),
139 otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), r); 191 ISP1704_PWR_CTRL_SWCTRL
192 | ISP1704_PWR_CTRL_DPVSRC_EN);
140 usleep_range(1000, 2000); 193 usleep_range(1000, 2000);
141 194
142 timeout = jiffies + msecs_to_jiffies(300); 195 timeout = jiffies + msecs_to_jiffies(300);
@@ -147,7 +200,10 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp)
147 ret = isp1704_charger_verify(isp); 200 ret = isp1704_charger_verify(isp);
148 break; 201 break;
149 } 202 }
150 } while (!time_after(jiffies, timeout)); 203 } while (!time_after(jiffies, timeout) && isp->online);
204
205 /* recover original state */
206 otg_io_write(isp->otg, ISP1704_PWR_CTRL, pwr_ctrl);
151 207
152 return ret; 208 return ret;
153} 209}
@@ -155,52 +211,92 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp)
155static void isp1704_charger_work(struct work_struct *data) 211static void isp1704_charger_work(struct work_struct *data)
156{ 212{
157 int detect; 213 int detect;
214 unsigned long event;
215 unsigned power;
158 struct isp1704_charger *isp = 216 struct isp1704_charger *isp =
159 container_of(data, struct isp1704_charger, work); 217 container_of(data, struct isp1704_charger, work);
218 static DEFINE_MUTEX(lock);
160 219
161 /* 220 event = isp->event;
162 * FIXME Only supporting dedicated chargers even though isp1704 can 221 power = isp->max_power;
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 222
169 /* disable data pullups */ 223 mutex_lock(&lock);
170 if (isp->otg->gadget) 224
171 usb_gadget_disconnect(isp->otg->gadget); 225 switch (event) {
226 case USB_EVENT_VBUS:
227 isp->online = true;
228
229 /* detect charger */
230 detect = isp1704_charger_detect(isp);
231
232 if (detect) {
233 isp->present = detect;
234 isp->psy.type = isp1704_charger_type(isp);
235 }
172 236
173 /* detect charger */ 237 switch (isp->psy.type) {
174 detect = isp1704_charger_detect(isp); 238 case POWER_SUPPLY_TYPE_USB_DCP:
175 if (detect) { 239 isp->current_max = 1800;
176 isp->present = detect; 240 break;
177 power_supply_changed(&isp->psy); 241 case POWER_SUPPLY_TYPE_USB_CDP:
242 /*
243 * Only 500mA here or high speed chirp
244 * handshaking may break
245 */
246 isp->current_max = 500;
247 /* FALLTHROUGH */
248 case POWER_SUPPLY_TYPE_USB:
249 default:
250 /* enable data pullups */
251 if (isp->otg->gadget)
252 usb_gadget_connect(isp->otg->gadget);
253 }
254 break;
255 case USB_EVENT_NONE:
256 isp->online = false;
257 isp->current_max = 0;
258 isp->present = 0;
259 isp->current_max = 0;
260 isp->psy.type = POWER_SUPPLY_TYPE_USB;
261
262 /*
263 * Disable data pullups. We need to prevent the controller from
264 * enumerating.
265 *
266 * FIXME: This is here to allow charger detection with Host/HUB
267 * chargers. The pullups may be enabled elsewhere, so this can
268 * not be the final solution.
269 */
270 if (isp->otg->gadget)
271 usb_gadget_disconnect(isp->otg->gadget);
272 break;
273 case USB_EVENT_ENUMERATED:
274 if (isp->present)
275 isp->current_max = 1800;
276 else
277 isp->current_max = power;
278 break;
279 default:
280 goto out;
178 } 281 }
179 282
180 /* enable data pullups */ 283 power_supply_changed(&isp->psy);
181 if (isp->otg->gadget) 284out:
182 usb_gadget_connect(isp->otg->gadget); 285 mutex_unlock(&lock);
183} 286}
184 287
185static int isp1704_notifier_call(struct notifier_block *nb, 288static int isp1704_notifier_call(struct notifier_block *nb,
186 unsigned long event, void *unused) 289 unsigned long event, void *power)
187{ 290{
188 struct isp1704_charger *isp = 291 struct isp1704_charger *isp =
189 container_of(nb, struct isp1704_charger, nb); 292 container_of(nb, struct isp1704_charger, nb);
190 293
191 switch (event) { 294 isp->event = event;
192 case USB_EVENT_VBUS: 295
193 schedule_work(&isp->work); 296 if (power)
194 break; 297 isp->max_power = *((unsigned *)power);
195 case USB_EVENT_NONE: 298
196 if (isp->present) { 299 schedule_work(&isp->work);
197 isp->present = 0;
198 power_supply_changed(&isp->psy);
199 }
200 break;
201 default:
202 return NOTIFY_DONE;
203 }
204 300
205 return NOTIFY_OK; 301 return NOTIFY_OK;
206} 302}
@@ -216,6 +312,12 @@ static int isp1704_charger_get_property(struct power_supply *psy,
216 case POWER_SUPPLY_PROP_PRESENT: 312 case POWER_SUPPLY_PROP_PRESENT:
217 val->intval = isp->present; 313 val->intval = isp->present;
218 break; 314 break;
315 case POWER_SUPPLY_PROP_ONLINE:
316 val->intval = isp->online;
317 break;
318 case POWER_SUPPLY_PROP_CURRENT_MAX:
319 val->intval = isp->current_max;
320 break;
219 case POWER_SUPPLY_PROP_MODEL_NAME: 321 case POWER_SUPPLY_PROP_MODEL_NAME:
220 val->strval = isp->model; 322 val->strval = isp->model;
221 break; 323 break;
@@ -230,6 +332,8 @@ static int isp1704_charger_get_property(struct power_supply *psy,
230 332
231static enum power_supply_property power_props[] = { 333static enum power_supply_property power_props[] = {
232 POWER_SUPPLY_PROP_PRESENT, 334 POWER_SUPPLY_PROP_PRESENT,
335 POWER_SUPPLY_PROP_ONLINE,
336 POWER_SUPPLY_PROP_CURRENT_MAX,
233 POWER_SUPPLY_PROP_MODEL_NAME, 337 POWER_SUPPLY_PROP_MODEL_NAME,
234 POWER_SUPPLY_PROP_MANUFACTURER, 338 POWER_SUPPLY_PROP_MANUFACTURER,
235}; 339};
@@ -287,13 +391,13 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev)
287 if (!isp->otg) 391 if (!isp->otg)
288 goto fail0; 392 goto fail0;
289 393
394 isp->dev = &pdev->dev;
395 platform_set_drvdata(pdev, isp);
396
290 ret = isp1704_test_ulpi(isp); 397 ret = isp1704_test_ulpi(isp);
291 if (ret < 0) 398 if (ret < 0)
292 goto fail1; 399 goto fail1;
293 400
294 isp->dev = &pdev->dev;
295 platform_set_drvdata(pdev, isp);
296
297 isp->psy.name = "isp1704"; 401 isp->psy.name = "isp1704";
298 isp->psy.type = POWER_SUPPLY_TYPE_USB; 402 isp->psy.type = POWER_SUPPLY_TYPE_USB;
299 isp->psy.properties = power_props; 403 isp->psy.properties = power_props;
@@ -318,6 +422,23 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev)
318 422
319 dev_info(isp->dev, "registered with product id %s\n", isp->model); 423 dev_info(isp->dev, "registered with product id %s\n", isp->model);
320 424
425 /*
426 * Taking over the D+ pullup.
427 *
428 * FIXME: The device will be disconnected if it was already
429 * enumerated. The charger driver should be always loaded before any
430 * gadget is loaded.
431 */
432 if (isp->otg->gadget)
433 usb_gadget_disconnect(isp->otg->gadget);
434
435 /* Detect charger if VBUS is valid (the cable was already plugged). */
436 ret = otg_io_read(isp->otg, ULPI_USB_INT_STS);
437 if ((ret & ULPI_INT_VBUS_VALID) && !isp->otg->default_a) {
438 isp->event = USB_EVENT_VBUS;
439 schedule_work(&isp->work);
440 }
441
321 return 0; 442 return 0;
322fail2: 443fail2:
323 power_supply_unregister(&isp->psy); 444 power_supply_unregister(&isp->psy);