513 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * ISP1704 USB Charger Detection driver4 *5 * Copyright (C) 2010 Nokia Corporation6 * Copyright (C) 2012 - 2013 Pali Rohár <pali@kernel.org>7 */8 9#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/err.h>12#include <linux/init.h>13#include <linux/types.h>14#include <linux/device.h>15#include <linux/sysfs.h>16#include <linux/platform_device.h>17#include <linux/power_supply.h>18#include <linux/delay.h>19#include <linux/of.h>20 21#include <linux/gpio/consumer.h>22#include <linux/usb/otg.h>23#include <linux/usb/ulpi.h>24#include <linux/usb/ch9.h>25#include <linux/usb/gadget.h>26 27/* Vendor specific Power Control register */28#define ISP1704_PWR_CTRL 0x3d29#define ISP1704_PWR_CTRL_SWCTRL (1 << 0)30#define ISP1704_PWR_CTRL_DET_COMP (1 << 1)31#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2)32#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3)33#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4)34#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5)35#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6)36#define ISP1704_PWR_CTRL_HWDETECT (1 << 7)37 38#define NXP_VENDOR_ID 0x04cc39 40static u16 isp170x_id[] = {41 0x1704,42 0x1707,43};44 45struct isp1704_charger {46 struct device *dev;47 struct power_supply *psy;48 struct power_supply_desc psy_desc;49 struct gpio_desc *enable_gpio;50 struct usb_phy *phy;51 struct notifier_block nb;52 struct work_struct work;53 54 /* properties */55 char model[8];56 unsigned present:1;57 unsigned online:1;58 unsigned current_max;59};60 61static inline int isp1704_read(struct isp1704_charger *isp, u32 reg)62{63 return usb_phy_io_read(isp->phy, reg);64}65 66static inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val)67{68 return usb_phy_io_write(isp->phy, val, reg);69}70 71static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on)72{73 gpiod_set_value(isp->enable_gpio, on);74}75 76/*77 * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB78 * chargers).79 *80 * REVISIT: The method is defined in Battery Charging Specification and is81 * applicable to any ULPI transceiver. Nothing isp170x specific here.82 */83static inline int isp1704_charger_type(struct isp1704_charger *isp)84{85 u8 reg;86 u8 func_ctrl;87 u8 otg_ctrl;88 int type = POWER_SUPPLY_TYPE_USB_DCP;89 90 func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL);91 otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL);92 93 /* disable pulldowns */94 reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;95 isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg);96 97 /* full speed */98 isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),99 ULPI_FUNC_CTRL_XCVRSEL_MASK);100 isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL),101 ULPI_FUNC_CTRL_FULL_SPEED);102 103 /* Enable strong pull-up on DP (1.5K) and reset */104 reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;105 isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg);106 usleep_range(1000, 2000);107 108 reg = isp1704_read(isp, ULPI_DEBUG);109 if ((reg & 3) != 3)110 type = POWER_SUPPLY_TYPE_USB_CDP;111 112 /* recover original state */113 isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl);114 isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl);115 116 return type;117}118 119/*120 * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger121 * is actually a dedicated charger, the following steps need to be taken.122 */123static inline int isp1704_charger_verify(struct isp1704_charger *isp)124{125 int ret = 0;126 u8 r;127 128 /* Reset the transceiver */129 r = isp1704_read(isp, ULPI_FUNC_CTRL);130 r |= ULPI_FUNC_CTRL_RESET;131 isp1704_write(isp, ULPI_FUNC_CTRL, r);132 usleep_range(1000, 2000);133 134 /* Set normal mode */135 r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);136 isp1704_write(isp, ULPI_FUNC_CTRL, r);137 138 /* Clear the DP and DM pull-down bits */139 r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;140 isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r);141 142 /* Enable strong pull-up on DP (1.5K) and reset */143 r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;144 isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r);145 usleep_range(1000, 2000);146 147 /* Read the line state */148 if (!isp1704_read(isp, ULPI_DEBUG)) {149 /* Disable strong pull-up on DP (1.5K) */150 isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),151 ULPI_FUNC_CTRL_TERMSELECT);152 return 1;153 }154 155 /* Is it a charger or PS/2 connection */156 157 /* Enable weak pull-up resistor on DP */158 isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),159 ISP1704_PWR_CTRL_DP_WKPU_EN);160 161 /* Disable strong pull-up on DP (1.5K) */162 isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),163 ULPI_FUNC_CTRL_TERMSELECT);164 165 /* Enable weak pull-down resistor on DM */166 isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL),167 ULPI_OTG_CTRL_DM_PULLDOWN);168 169 /* It's a charger if the line states are clear */170 if (!(isp1704_read(isp, ULPI_DEBUG)))171 ret = 1;172 173 /* Disable weak pull-up resistor on DP */174 isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL),175 ISP1704_PWR_CTRL_DP_WKPU_EN);176 177 return ret;178}179 180static inline int isp1704_charger_detect(struct isp1704_charger *isp)181{182 unsigned long timeout;183 u8 pwr_ctrl;184 int ret = 0;185 186 pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL);187 188 /* set SW control bit in PWR_CTRL register */189 isp1704_write(isp, ISP1704_PWR_CTRL,190 ISP1704_PWR_CTRL_SWCTRL);191 192 /* enable manual charger detection */193 isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),194 ISP1704_PWR_CTRL_SWCTRL195 | ISP1704_PWR_CTRL_DPVSRC_EN);196 usleep_range(1000, 2000);197 198 timeout = jiffies + msecs_to_jiffies(300);199 do {200 /* Check if there is a charger */201 if (isp1704_read(isp, ISP1704_PWR_CTRL)202 & ISP1704_PWR_CTRL_VDAT_DET) {203 ret = isp1704_charger_verify(isp);204 break;205 }206 } while (!time_after(jiffies, timeout) && isp->online);207 208 /* recover original state */209 isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl);210 211 return ret;212}213 214static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp)215{216 if (isp1704_charger_detect(isp) &&217 isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP)218 return true;219 else220 return false;221}222 223static void isp1704_charger_work(struct work_struct *data)224{225 struct isp1704_charger *isp =226 container_of(data, struct isp1704_charger, work);227 static DEFINE_MUTEX(lock);228 229 mutex_lock(&lock);230 231 switch (isp->phy->last_event) {232 case USB_EVENT_VBUS:233 /* do not call wall charger detection more times */234 if (!isp->present) {235 isp->online = true;236 isp->present = 1;237 isp1704_charger_set_power(isp, 1);238 239 /* detect wall charger */240 if (isp1704_charger_detect_dcp(isp)) {241 isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;242 isp->current_max = 1800;243 } else {244 isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;245 isp->current_max = 500;246 }247 248 /* enable data pullups */249 if (isp->phy->otg->gadget)250 usb_gadget_connect(isp->phy->otg->gadget);251 }252 253 if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) {254 /*255 * Only 500mA here or high speed chirp256 * handshaking may break257 */258 if (isp->current_max > 500)259 isp->current_max = 500;260 261 if (isp->current_max > 100)262 isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;263 }264 break;265 case USB_EVENT_NONE:266 isp->online = false;267 isp->present = 0;268 isp->current_max = 0;269 isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;270 271 /*272 * Disable data pullups. We need to prevent the controller from273 * enumerating.274 *275 * FIXME: This is here to allow charger detection with Host/HUB276 * chargers. The pullups may be enabled elsewhere, so this can277 * not be the final solution.278 */279 if (isp->phy->otg->gadget)280 usb_gadget_disconnect(isp->phy->otg->gadget);281 282 isp1704_charger_set_power(isp, 0);283 break;284 default:285 goto out;286 }287 288 power_supply_changed(isp->psy);289out:290 mutex_unlock(&lock);291}292 293static int isp1704_notifier_call(struct notifier_block *nb,294 unsigned long val, void *v)295{296 struct isp1704_charger *isp =297 container_of(nb, struct isp1704_charger, nb);298 299 schedule_work(&isp->work);300 301 return NOTIFY_OK;302}303 304static int isp1704_charger_get_property(struct power_supply *psy,305 enum power_supply_property psp,306 union power_supply_propval *val)307{308 struct isp1704_charger *isp = power_supply_get_drvdata(psy);309 310 switch (psp) {311 case POWER_SUPPLY_PROP_PRESENT:312 val->intval = isp->present;313 break;314 case POWER_SUPPLY_PROP_ONLINE:315 val->intval = isp->online;316 break;317 case POWER_SUPPLY_PROP_CURRENT_MAX:318 val->intval = isp->current_max;319 break;320 case POWER_SUPPLY_PROP_MODEL_NAME:321 val->strval = isp->model;322 break;323 case POWER_SUPPLY_PROP_MANUFACTURER:324 val->strval = "NXP";325 break;326 default:327 return -EINVAL;328 }329 return 0;330}331 332static enum power_supply_property power_props[] = {333 POWER_SUPPLY_PROP_PRESENT,334 POWER_SUPPLY_PROP_ONLINE,335 POWER_SUPPLY_PROP_CURRENT_MAX,336 POWER_SUPPLY_PROP_MODEL_NAME,337 POWER_SUPPLY_PROP_MANUFACTURER,338};339 340static inline int isp1704_test_ulpi(struct isp1704_charger *isp)341{342 int vendor;343 int product;344 int i;345 int ret;346 347 /* Test ULPI interface */348 ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa);349 if (ret < 0)350 return ret;351 352 ret = isp1704_read(isp, ULPI_SCRATCH);353 if (ret < 0)354 return ret;355 356 if (ret != 0xaa)357 return -ENODEV;358 359 /* Verify the product and vendor id matches */360 vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW);361 vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8;362 if (vendor != NXP_VENDOR_ID)363 return -ENODEV;364 365 product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW);366 product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8;367 368 for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) {369 if (product == isp170x_id[i]) {370 sprintf(isp->model, "isp%x", product);371 return product;372 }373 }374 375 dev_err(isp->dev, "product id %x not matching known ids", product);376 377 return -ENODEV;378}379 380static int isp1704_charger_probe(struct platform_device *pdev)381{382 struct isp1704_charger *isp;383 int ret = -ENODEV;384 struct power_supply_config psy_cfg = {};385 386 isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);387 if (!isp)388 return -ENOMEM;389 390 isp->enable_gpio = devm_gpiod_get(&pdev->dev, "nxp,enable",391 GPIOD_OUT_HIGH);392 if (IS_ERR(isp->enable_gpio)) {393 ret = PTR_ERR(isp->enable_gpio);394 dev_err(&pdev->dev, "Could not get reset gpio: %d\n", ret);395 return ret;396 }397 398 if (pdev->dev.of_node)399 isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);400 else401 isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);402 403 if (IS_ERR(isp->phy)) {404 ret = PTR_ERR(isp->phy);405 dev_err(&pdev->dev, "usb_get_phy failed\n");406 goto fail0;407 }408 409 isp->dev = &pdev->dev;410 platform_set_drvdata(pdev, isp);411 412 isp1704_charger_set_power(isp, 1);413 414 ret = isp1704_test_ulpi(isp);415 if (ret < 0) {416 dev_err(&pdev->dev, "isp1704_test_ulpi failed\n");417 goto fail1;418 }419 420 isp->psy_desc.name = "isp1704";421 isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;422 isp->psy_desc.properties = power_props;423 isp->psy_desc.num_properties = ARRAY_SIZE(power_props);424 isp->psy_desc.get_property = isp1704_charger_get_property;425 426 psy_cfg.drv_data = isp;427 428 isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg);429 if (IS_ERR(isp->psy)) {430 ret = PTR_ERR(isp->psy);431 dev_err(&pdev->dev, "power_supply_register failed\n");432 goto fail1;433 }434 435 /*436 * REVISIT: using work in order to allow the usb notifications to be437 * made atomically in the future.438 */439 INIT_WORK(&isp->work, isp1704_charger_work);440 441 isp->nb.notifier_call = isp1704_notifier_call;442 443 ret = usb_register_notifier(isp->phy, &isp->nb);444 if (ret) {445 dev_err(&pdev->dev, "usb_register_notifier failed\n");446 goto fail2;447 }448 449 dev_info(isp->dev, "registered with product id %s\n", isp->model);450 451 /*452 * Taking over the D+ pullup.453 *454 * FIXME: The device will be disconnected if it was already455 * enumerated. The charger driver should be always loaded before any456 * gadget is loaded.457 */458 if (isp->phy->otg->gadget)459 usb_gadget_disconnect(isp->phy->otg->gadget);460 461 if (isp->phy->last_event == USB_EVENT_NONE)462 isp1704_charger_set_power(isp, 0);463 464 /* Detect charger if VBUS is valid (the cable was already plugged). */465 if (isp->phy->last_event == USB_EVENT_VBUS &&466 !isp->phy->otg->default_a)467 schedule_work(&isp->work);468 469 return 0;470fail2:471 power_supply_unregister(isp->psy);472fail1:473 isp1704_charger_set_power(isp, 0);474fail0:475 dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);476 477 return ret;478}479 480static void isp1704_charger_remove(struct platform_device *pdev)481{482 struct isp1704_charger *isp = platform_get_drvdata(pdev);483 484 usb_unregister_notifier(isp->phy, &isp->nb);485 power_supply_unregister(isp->psy);486 isp1704_charger_set_power(isp, 0);487}488 489#ifdef CONFIG_OF490static const struct of_device_id omap_isp1704_of_match[] = {491 { .compatible = "nxp,isp1704", },492 { .compatible = "nxp,isp1707", },493 {},494};495MODULE_DEVICE_TABLE(of, omap_isp1704_of_match);496#endif497 498static struct platform_driver isp1704_charger_driver = {499 .driver = {500 .name = "isp1704_charger",501 .of_match_table = of_match_ptr(omap_isp1704_of_match),502 },503 .probe = isp1704_charger_probe,504 .remove_new = isp1704_charger_remove,505};506 507module_platform_driver(isp1704_charger_driver);508 509MODULE_ALIAS("platform:isp1704_charger");510MODULE_AUTHOR("Nokia Corporation");511MODULE_DESCRIPTION("ISP170x USB Charger driver");512MODULE_LICENSE("GPL");513