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.9-rc8 513 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 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 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 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 = 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");