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 v2.6.37-rc3 369 lines 9.2 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 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");