Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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