11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 28c0984e5SSebastian Reichel /* 38c0984e5SSebastian Reichel * ISP1704 USB Charger Detection driver 48c0984e5SSebastian Reichel * 58c0984e5SSebastian Reichel * Copyright (C) 2010 Nokia Corporation 68c0984e5SSebastian Reichel * Copyright (C) 2012 - 2013 Pali Rohár <pali.rohar@gmail.com> 78c0984e5SSebastian Reichel */ 88c0984e5SSebastian Reichel 98c0984e5SSebastian Reichel #include <linux/kernel.h> 108c0984e5SSebastian Reichel #include <linux/module.h> 118c0984e5SSebastian Reichel #include <linux/err.h> 128c0984e5SSebastian Reichel #include <linux/init.h> 138c0984e5SSebastian Reichel #include <linux/types.h> 148c0984e5SSebastian Reichel #include <linux/device.h> 158c0984e5SSebastian Reichel #include <linux/sysfs.h> 168c0984e5SSebastian Reichel #include <linux/platform_device.h> 178c0984e5SSebastian Reichel #include <linux/power_supply.h> 188c0984e5SSebastian Reichel #include <linux/delay.h> 198c0984e5SSebastian Reichel #include <linux/of.h> 208c0984e5SSebastian Reichel 21f5d782d4SSebastian Reichel #include <linux/gpio/consumer.h> 228c0984e5SSebastian Reichel #include <linux/usb/otg.h> 238c0984e5SSebastian Reichel #include <linux/usb/ulpi.h> 248c0984e5SSebastian Reichel #include <linux/usb/ch9.h> 258c0984e5SSebastian Reichel #include <linux/usb/gadget.h> 268c0984e5SSebastian Reichel 278c0984e5SSebastian Reichel /* Vendor specific Power Control register */ 288c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL 0x3d 298c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_SWCTRL (1 << 0) 308c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_DET_COMP (1 << 1) 318c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2) 328c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3) 338c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4) 348c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_VDAT_DET (1 << 5) 358c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6) 368c0984e5SSebastian Reichel #define ISP1704_PWR_CTRL_HWDETECT (1 << 7) 378c0984e5SSebastian Reichel 388c0984e5SSebastian Reichel #define NXP_VENDOR_ID 0x04cc 398c0984e5SSebastian Reichel 408c0984e5SSebastian Reichel static u16 isp170x_id[] = { 418c0984e5SSebastian Reichel 0x1704, 428c0984e5SSebastian Reichel 0x1707, 438c0984e5SSebastian Reichel }; 448c0984e5SSebastian Reichel 458c0984e5SSebastian Reichel struct isp1704_charger { 468c0984e5SSebastian Reichel struct device *dev; 478c0984e5SSebastian Reichel struct power_supply *psy; 488c0984e5SSebastian Reichel struct power_supply_desc psy_desc; 49f5d782d4SSebastian Reichel struct gpio_desc *enable_gpio; 508c0984e5SSebastian Reichel struct usb_phy *phy; 518c0984e5SSebastian Reichel struct notifier_block nb; 528c0984e5SSebastian Reichel struct work_struct work; 538c0984e5SSebastian Reichel 548c0984e5SSebastian Reichel /* properties */ 558c0984e5SSebastian Reichel char model[8]; 568c0984e5SSebastian Reichel unsigned present:1; 578c0984e5SSebastian Reichel unsigned online:1; 588c0984e5SSebastian Reichel unsigned current_max; 598c0984e5SSebastian Reichel }; 608c0984e5SSebastian Reichel 618c0984e5SSebastian Reichel static inline int isp1704_read(struct isp1704_charger *isp, u32 reg) 628c0984e5SSebastian Reichel { 638c0984e5SSebastian Reichel return usb_phy_io_read(isp->phy, reg); 648c0984e5SSebastian Reichel } 658c0984e5SSebastian Reichel 668c0984e5SSebastian Reichel static inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val) 678c0984e5SSebastian Reichel { 688c0984e5SSebastian Reichel return usb_phy_io_write(isp->phy, val, reg); 698c0984e5SSebastian Reichel } 708c0984e5SSebastian Reichel 718c0984e5SSebastian Reichel static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) 728c0984e5SSebastian Reichel { 73f5d782d4SSebastian Reichel gpiod_set_value(isp->enable_gpio, on); 748c0984e5SSebastian Reichel } 758c0984e5SSebastian Reichel 768c0984e5SSebastian Reichel /* 778c0984e5SSebastian Reichel * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB 788c0984e5SSebastian Reichel * chargers). 798c0984e5SSebastian Reichel * 808c0984e5SSebastian Reichel * REVISIT: The method is defined in Battery Charging Specification and is 818c0984e5SSebastian Reichel * applicable to any ULPI transceiver. Nothing isp170x specific here. 828c0984e5SSebastian Reichel */ 838c0984e5SSebastian Reichel static inline int isp1704_charger_type(struct isp1704_charger *isp) 848c0984e5SSebastian Reichel { 858c0984e5SSebastian Reichel u8 reg; 868c0984e5SSebastian Reichel u8 func_ctrl; 878c0984e5SSebastian Reichel u8 otg_ctrl; 888c0984e5SSebastian Reichel int type = POWER_SUPPLY_TYPE_USB_DCP; 898c0984e5SSebastian Reichel 908c0984e5SSebastian Reichel func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL); 918c0984e5SSebastian Reichel otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL); 928c0984e5SSebastian Reichel 938c0984e5SSebastian Reichel /* disable pulldowns */ 948c0984e5SSebastian Reichel reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN; 958c0984e5SSebastian Reichel isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg); 968c0984e5SSebastian Reichel 978c0984e5SSebastian Reichel /* full speed */ 988c0984e5SSebastian Reichel isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), 998c0984e5SSebastian Reichel ULPI_FUNC_CTRL_XCVRSEL_MASK); 1008c0984e5SSebastian Reichel isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), 1018c0984e5SSebastian Reichel ULPI_FUNC_CTRL_FULL_SPEED); 1028c0984e5SSebastian Reichel 1038c0984e5SSebastian Reichel /* Enable strong pull-up on DP (1.5K) and reset */ 1048c0984e5SSebastian Reichel reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; 1058c0984e5SSebastian Reichel isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg); 1068c0984e5SSebastian Reichel usleep_range(1000, 2000); 1078c0984e5SSebastian Reichel 1088c0984e5SSebastian Reichel reg = isp1704_read(isp, ULPI_DEBUG); 1098c0984e5SSebastian Reichel if ((reg & 3) != 3) 1108c0984e5SSebastian Reichel type = POWER_SUPPLY_TYPE_USB_CDP; 1118c0984e5SSebastian Reichel 1128c0984e5SSebastian Reichel /* recover original state */ 1138c0984e5SSebastian Reichel isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl); 1148c0984e5SSebastian Reichel isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl); 1158c0984e5SSebastian Reichel 1168c0984e5SSebastian Reichel return type; 1178c0984e5SSebastian Reichel } 1188c0984e5SSebastian Reichel 1198c0984e5SSebastian Reichel /* 1208c0984e5SSebastian Reichel * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger 1218c0984e5SSebastian Reichel * is actually a dedicated charger, the following steps need to be taken. 1228c0984e5SSebastian Reichel */ 1238c0984e5SSebastian Reichel static inline int isp1704_charger_verify(struct isp1704_charger *isp) 1248c0984e5SSebastian Reichel { 1258c0984e5SSebastian Reichel int ret = 0; 1268c0984e5SSebastian Reichel u8 r; 1278c0984e5SSebastian Reichel 1288c0984e5SSebastian Reichel /* Reset the transceiver */ 1298c0984e5SSebastian Reichel r = isp1704_read(isp, ULPI_FUNC_CTRL); 1308c0984e5SSebastian Reichel r |= ULPI_FUNC_CTRL_RESET; 1318c0984e5SSebastian Reichel isp1704_write(isp, ULPI_FUNC_CTRL, r); 1328c0984e5SSebastian Reichel usleep_range(1000, 2000); 1338c0984e5SSebastian Reichel 1348c0984e5SSebastian Reichel /* Set normal mode */ 1358c0984e5SSebastian Reichel r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); 1368c0984e5SSebastian Reichel isp1704_write(isp, ULPI_FUNC_CTRL, r); 1378c0984e5SSebastian Reichel 1388c0984e5SSebastian Reichel /* Clear the DP and DM pull-down bits */ 1398c0984e5SSebastian Reichel r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN; 1408c0984e5SSebastian Reichel isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r); 1418c0984e5SSebastian Reichel 1428c0984e5SSebastian Reichel /* Enable strong pull-up on DP (1.5K) and reset */ 1438c0984e5SSebastian Reichel r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; 1448c0984e5SSebastian Reichel isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r); 1458c0984e5SSebastian Reichel usleep_range(1000, 2000); 1468c0984e5SSebastian Reichel 1478c0984e5SSebastian Reichel /* Read the line state */ 1488c0984e5SSebastian Reichel if (!isp1704_read(isp, ULPI_DEBUG)) { 1498c0984e5SSebastian Reichel /* Disable strong pull-up on DP (1.5K) */ 1508c0984e5SSebastian Reichel isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), 1518c0984e5SSebastian Reichel ULPI_FUNC_CTRL_TERMSELECT); 1528c0984e5SSebastian Reichel return 1; 1538c0984e5SSebastian Reichel } 1548c0984e5SSebastian Reichel 1558c0984e5SSebastian Reichel /* Is it a charger or PS/2 connection */ 1568c0984e5SSebastian Reichel 1578c0984e5SSebastian Reichel /* Enable weak pull-up resistor on DP */ 1588c0984e5SSebastian Reichel isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), 1598c0984e5SSebastian Reichel ISP1704_PWR_CTRL_DP_WKPU_EN); 1608c0984e5SSebastian Reichel 1618c0984e5SSebastian Reichel /* Disable strong pull-up on DP (1.5K) */ 1628c0984e5SSebastian Reichel isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), 1638c0984e5SSebastian Reichel ULPI_FUNC_CTRL_TERMSELECT); 1648c0984e5SSebastian Reichel 1658c0984e5SSebastian Reichel /* Enable weak pull-down resistor on DM */ 1668c0984e5SSebastian Reichel isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL), 1678c0984e5SSebastian Reichel ULPI_OTG_CTRL_DM_PULLDOWN); 1688c0984e5SSebastian Reichel 1698c0984e5SSebastian Reichel /* It's a charger if the line states are clear */ 1708c0984e5SSebastian Reichel if (!(isp1704_read(isp, ULPI_DEBUG))) 1718c0984e5SSebastian Reichel ret = 1; 1728c0984e5SSebastian Reichel 1738c0984e5SSebastian Reichel /* Disable weak pull-up resistor on DP */ 1748c0984e5SSebastian Reichel isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL), 1758c0984e5SSebastian Reichel ISP1704_PWR_CTRL_DP_WKPU_EN); 1768c0984e5SSebastian Reichel 1778c0984e5SSebastian Reichel return ret; 1788c0984e5SSebastian Reichel } 1798c0984e5SSebastian Reichel 1808c0984e5SSebastian Reichel static inline int isp1704_charger_detect(struct isp1704_charger *isp) 1818c0984e5SSebastian Reichel { 1828c0984e5SSebastian Reichel unsigned long timeout; 1838c0984e5SSebastian Reichel u8 pwr_ctrl; 1848c0984e5SSebastian Reichel int ret = 0; 1858c0984e5SSebastian Reichel 1868c0984e5SSebastian Reichel pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL); 1878c0984e5SSebastian Reichel 1888c0984e5SSebastian Reichel /* set SW control bit in PWR_CTRL register */ 1898c0984e5SSebastian Reichel isp1704_write(isp, ISP1704_PWR_CTRL, 1908c0984e5SSebastian Reichel ISP1704_PWR_CTRL_SWCTRL); 1918c0984e5SSebastian Reichel 1928c0984e5SSebastian Reichel /* enable manual charger detection */ 1938c0984e5SSebastian Reichel isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), 1948c0984e5SSebastian Reichel ISP1704_PWR_CTRL_SWCTRL 1958c0984e5SSebastian Reichel | ISP1704_PWR_CTRL_DPVSRC_EN); 1968c0984e5SSebastian Reichel usleep_range(1000, 2000); 1978c0984e5SSebastian Reichel 1988c0984e5SSebastian Reichel timeout = jiffies + msecs_to_jiffies(300); 1998c0984e5SSebastian Reichel do { 2008c0984e5SSebastian Reichel /* Check if there is a charger */ 2018c0984e5SSebastian Reichel if (isp1704_read(isp, ISP1704_PWR_CTRL) 2028c0984e5SSebastian Reichel & ISP1704_PWR_CTRL_VDAT_DET) { 2038c0984e5SSebastian Reichel ret = isp1704_charger_verify(isp); 2048c0984e5SSebastian Reichel break; 2058c0984e5SSebastian Reichel } 2068c0984e5SSebastian Reichel } while (!time_after(jiffies, timeout) && isp->online); 2078c0984e5SSebastian Reichel 2088c0984e5SSebastian Reichel /* recover original state */ 2098c0984e5SSebastian Reichel isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl); 2108c0984e5SSebastian Reichel 2118c0984e5SSebastian Reichel return ret; 2128c0984e5SSebastian Reichel } 2138c0984e5SSebastian Reichel 2148c0984e5SSebastian Reichel static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp) 2158c0984e5SSebastian Reichel { 2168c0984e5SSebastian Reichel if (isp1704_charger_detect(isp) && 2178c0984e5SSebastian Reichel isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP) 2188c0984e5SSebastian Reichel return true; 2198c0984e5SSebastian Reichel else 2208c0984e5SSebastian Reichel return false; 2218c0984e5SSebastian Reichel } 2228c0984e5SSebastian Reichel 2238c0984e5SSebastian Reichel static void isp1704_charger_work(struct work_struct *data) 2248c0984e5SSebastian Reichel { 2258c0984e5SSebastian Reichel struct isp1704_charger *isp = 2268c0984e5SSebastian Reichel container_of(data, struct isp1704_charger, work); 2278c0984e5SSebastian Reichel static DEFINE_MUTEX(lock); 2288c0984e5SSebastian Reichel 2298c0984e5SSebastian Reichel mutex_lock(&lock); 2308c0984e5SSebastian Reichel 2318c0984e5SSebastian Reichel switch (isp->phy->last_event) { 2328c0984e5SSebastian Reichel case USB_EVENT_VBUS: 2338c0984e5SSebastian Reichel /* do not call wall charger detection more times */ 2348c0984e5SSebastian Reichel if (!isp->present) { 2358c0984e5SSebastian Reichel isp->online = true; 2368c0984e5SSebastian Reichel isp->present = 1; 2378c0984e5SSebastian Reichel isp1704_charger_set_power(isp, 1); 2388c0984e5SSebastian Reichel 2398c0984e5SSebastian Reichel /* detect wall charger */ 2408c0984e5SSebastian Reichel if (isp1704_charger_detect_dcp(isp)) { 2418c0984e5SSebastian Reichel isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; 2428c0984e5SSebastian Reichel isp->current_max = 1800; 2438c0984e5SSebastian Reichel } else { 2448c0984e5SSebastian Reichel isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; 2458c0984e5SSebastian Reichel isp->current_max = 500; 2468c0984e5SSebastian Reichel } 2478c0984e5SSebastian Reichel 2488c0984e5SSebastian Reichel /* enable data pullups */ 2498c0984e5SSebastian Reichel if (isp->phy->otg->gadget) 2508c0984e5SSebastian Reichel usb_gadget_connect(isp->phy->otg->gadget); 2518c0984e5SSebastian Reichel } 2528c0984e5SSebastian Reichel 2538c0984e5SSebastian Reichel if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) { 2548c0984e5SSebastian Reichel /* 2558c0984e5SSebastian Reichel * Only 500mA here or high speed chirp 2568c0984e5SSebastian Reichel * handshaking may break 2578c0984e5SSebastian Reichel */ 2588c0984e5SSebastian Reichel if (isp->current_max > 500) 2598c0984e5SSebastian Reichel isp->current_max = 500; 2608c0984e5SSebastian Reichel 2618c0984e5SSebastian Reichel if (isp->current_max > 100) 2628c0984e5SSebastian Reichel isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; 2638c0984e5SSebastian Reichel } 2648c0984e5SSebastian Reichel break; 2658c0984e5SSebastian Reichel case USB_EVENT_NONE: 2668c0984e5SSebastian Reichel isp->online = false; 2678c0984e5SSebastian Reichel isp->present = 0; 2688c0984e5SSebastian Reichel isp->current_max = 0; 2698c0984e5SSebastian Reichel isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; 2708c0984e5SSebastian Reichel 2718c0984e5SSebastian Reichel /* 2728c0984e5SSebastian Reichel * Disable data pullups. We need to prevent the controller from 2738c0984e5SSebastian Reichel * enumerating. 2748c0984e5SSebastian Reichel * 2758c0984e5SSebastian Reichel * FIXME: This is here to allow charger detection with Host/HUB 2768c0984e5SSebastian Reichel * chargers. The pullups may be enabled elsewhere, so this can 2778c0984e5SSebastian Reichel * not be the final solution. 2788c0984e5SSebastian Reichel */ 2798c0984e5SSebastian Reichel if (isp->phy->otg->gadget) 2808c0984e5SSebastian Reichel usb_gadget_disconnect(isp->phy->otg->gadget); 2818c0984e5SSebastian Reichel 2828c0984e5SSebastian Reichel isp1704_charger_set_power(isp, 0); 2838c0984e5SSebastian Reichel break; 2848c0984e5SSebastian Reichel default: 2858c0984e5SSebastian Reichel goto out; 2868c0984e5SSebastian Reichel } 2878c0984e5SSebastian Reichel 2888c0984e5SSebastian Reichel power_supply_changed(isp->psy); 2898c0984e5SSebastian Reichel out: 2908c0984e5SSebastian Reichel mutex_unlock(&lock); 2918c0984e5SSebastian Reichel } 2928c0984e5SSebastian Reichel 2938c0984e5SSebastian Reichel static int isp1704_notifier_call(struct notifier_block *nb, 2948c0984e5SSebastian Reichel unsigned long val, void *v) 2958c0984e5SSebastian Reichel { 2968c0984e5SSebastian Reichel struct isp1704_charger *isp = 2978c0984e5SSebastian Reichel container_of(nb, struct isp1704_charger, nb); 2988c0984e5SSebastian Reichel 2998c0984e5SSebastian Reichel schedule_work(&isp->work); 3008c0984e5SSebastian Reichel 3018c0984e5SSebastian Reichel return NOTIFY_OK; 3028c0984e5SSebastian Reichel } 3038c0984e5SSebastian Reichel 3048c0984e5SSebastian Reichel static int isp1704_charger_get_property(struct power_supply *psy, 3058c0984e5SSebastian Reichel enum power_supply_property psp, 3068c0984e5SSebastian Reichel union power_supply_propval *val) 3078c0984e5SSebastian Reichel { 3088c0984e5SSebastian Reichel struct isp1704_charger *isp = power_supply_get_drvdata(psy); 3098c0984e5SSebastian Reichel 3108c0984e5SSebastian Reichel switch (psp) { 3118c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_PRESENT: 3128c0984e5SSebastian Reichel val->intval = isp->present; 3138c0984e5SSebastian Reichel break; 3148c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_ONLINE: 3158c0984e5SSebastian Reichel val->intval = isp->online; 3168c0984e5SSebastian Reichel break; 3178c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_CURRENT_MAX: 3188c0984e5SSebastian Reichel val->intval = isp->current_max; 3198c0984e5SSebastian Reichel break; 3208c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_MODEL_NAME: 3218c0984e5SSebastian Reichel val->strval = isp->model; 3228c0984e5SSebastian Reichel break; 3238c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_MANUFACTURER: 3248c0984e5SSebastian Reichel val->strval = "NXP"; 3258c0984e5SSebastian Reichel break; 3268c0984e5SSebastian Reichel default: 3278c0984e5SSebastian Reichel return -EINVAL; 3288c0984e5SSebastian Reichel } 3298c0984e5SSebastian Reichel return 0; 3308c0984e5SSebastian Reichel } 3318c0984e5SSebastian Reichel 3328c0984e5SSebastian Reichel static enum power_supply_property power_props[] = { 3338c0984e5SSebastian Reichel POWER_SUPPLY_PROP_PRESENT, 3348c0984e5SSebastian Reichel POWER_SUPPLY_PROP_ONLINE, 3358c0984e5SSebastian Reichel POWER_SUPPLY_PROP_CURRENT_MAX, 3368c0984e5SSebastian Reichel POWER_SUPPLY_PROP_MODEL_NAME, 3378c0984e5SSebastian Reichel POWER_SUPPLY_PROP_MANUFACTURER, 3388c0984e5SSebastian Reichel }; 3398c0984e5SSebastian Reichel 3408c0984e5SSebastian Reichel static inline int isp1704_test_ulpi(struct isp1704_charger *isp) 3418c0984e5SSebastian Reichel { 3428c0984e5SSebastian Reichel int vendor; 3438c0984e5SSebastian Reichel int product; 3448c0984e5SSebastian Reichel int i; 3458c0984e5SSebastian Reichel int ret = -ENODEV; 3468c0984e5SSebastian Reichel 3478c0984e5SSebastian Reichel /* Test ULPI interface */ 3488c0984e5SSebastian Reichel ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa); 3498c0984e5SSebastian Reichel if (ret < 0) 3508c0984e5SSebastian Reichel return ret; 3518c0984e5SSebastian Reichel 3528c0984e5SSebastian Reichel ret = isp1704_read(isp, ULPI_SCRATCH); 3538c0984e5SSebastian Reichel if (ret < 0) 3548c0984e5SSebastian Reichel return ret; 3558c0984e5SSebastian Reichel 3568c0984e5SSebastian Reichel if (ret != 0xaa) 3578c0984e5SSebastian Reichel return -ENODEV; 3588c0984e5SSebastian Reichel 3598c0984e5SSebastian Reichel /* Verify the product and vendor id matches */ 3608c0984e5SSebastian Reichel vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW); 3618c0984e5SSebastian Reichel vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8; 3628c0984e5SSebastian Reichel if (vendor != NXP_VENDOR_ID) 3638c0984e5SSebastian Reichel return -ENODEV; 3648c0984e5SSebastian Reichel 3658c0984e5SSebastian Reichel product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW); 3668c0984e5SSebastian Reichel product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8; 3678c0984e5SSebastian Reichel 3688c0984e5SSebastian Reichel for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) { 3698c0984e5SSebastian Reichel if (product == isp170x_id[i]) { 3708c0984e5SSebastian Reichel sprintf(isp->model, "isp%x", product); 3718c0984e5SSebastian Reichel return product; 3728c0984e5SSebastian Reichel } 3738c0984e5SSebastian Reichel } 3748c0984e5SSebastian Reichel 3758c0984e5SSebastian Reichel dev_err(isp->dev, "product id %x not matching known ids", product); 3768c0984e5SSebastian Reichel 3778c0984e5SSebastian Reichel return -ENODEV; 3788c0984e5SSebastian Reichel } 3798c0984e5SSebastian Reichel 3808c0984e5SSebastian Reichel static int isp1704_charger_probe(struct platform_device *pdev) 3818c0984e5SSebastian Reichel { 3828c0984e5SSebastian Reichel struct isp1704_charger *isp; 3838c0984e5SSebastian Reichel int ret = -ENODEV; 3848c0984e5SSebastian Reichel struct power_supply_config psy_cfg = {}; 3858c0984e5SSebastian Reichel 3868c0984e5SSebastian Reichel isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); 3878c0984e5SSebastian Reichel if (!isp) 3888c0984e5SSebastian Reichel return -ENOMEM; 3898c0984e5SSebastian Reichel 390f5d782d4SSebastian Reichel isp->enable_gpio = devm_gpiod_get(&pdev->dev, "nxp,enable", 391f5d782d4SSebastian Reichel GPIOD_OUT_HIGH); 392f5d782d4SSebastian Reichel if (IS_ERR(isp->enable_gpio)) { 393f5d782d4SSebastian Reichel ret = PTR_ERR(isp->enable_gpio); 394f5d782d4SSebastian Reichel dev_err(&pdev->dev, "Could not get reset gpio: %d\n", ret); 395f5d782d4SSebastian Reichel return ret; 396f5d782d4SSebastian Reichel } 397f5d782d4SSebastian Reichel 398f5d782d4SSebastian Reichel if (pdev->dev.of_node) 3998c0984e5SSebastian Reichel isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); 4008c0984e5SSebastian Reichel else 4018c0984e5SSebastian Reichel isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); 4028c0984e5SSebastian Reichel 4038c0984e5SSebastian Reichel if (IS_ERR(isp->phy)) { 4048c0984e5SSebastian Reichel ret = PTR_ERR(isp->phy); 4058c0984e5SSebastian Reichel dev_err(&pdev->dev, "usb_get_phy failed\n"); 4068c0984e5SSebastian Reichel goto fail0; 4078c0984e5SSebastian Reichel } 4088c0984e5SSebastian Reichel 4098c0984e5SSebastian Reichel isp->dev = &pdev->dev; 4108c0984e5SSebastian Reichel platform_set_drvdata(pdev, isp); 4118c0984e5SSebastian Reichel 4128c0984e5SSebastian Reichel isp1704_charger_set_power(isp, 1); 4138c0984e5SSebastian Reichel 4148c0984e5SSebastian Reichel ret = isp1704_test_ulpi(isp); 4158c0984e5SSebastian Reichel if (ret < 0) { 4168c0984e5SSebastian Reichel dev_err(&pdev->dev, "isp1704_test_ulpi failed\n"); 4178c0984e5SSebastian Reichel goto fail1; 4188c0984e5SSebastian Reichel } 4198c0984e5SSebastian Reichel 4208c0984e5SSebastian Reichel isp->psy_desc.name = "isp1704"; 4218c0984e5SSebastian Reichel isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; 4228c0984e5SSebastian Reichel isp->psy_desc.properties = power_props; 4238c0984e5SSebastian Reichel isp->psy_desc.num_properties = ARRAY_SIZE(power_props); 4248c0984e5SSebastian Reichel isp->psy_desc.get_property = isp1704_charger_get_property; 4258c0984e5SSebastian Reichel 4268c0984e5SSebastian Reichel psy_cfg.drv_data = isp; 4278c0984e5SSebastian Reichel 4288c0984e5SSebastian Reichel isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg); 4298c0984e5SSebastian Reichel if (IS_ERR(isp->psy)) { 4308c0984e5SSebastian Reichel ret = PTR_ERR(isp->psy); 4318c0984e5SSebastian Reichel dev_err(&pdev->dev, "power_supply_register failed\n"); 4328c0984e5SSebastian Reichel goto fail1; 4338c0984e5SSebastian Reichel } 4348c0984e5SSebastian Reichel 4358c0984e5SSebastian Reichel /* 4368c0984e5SSebastian Reichel * REVISIT: using work in order to allow the usb notifications to be 4378c0984e5SSebastian Reichel * made atomically in the future. 4388c0984e5SSebastian Reichel */ 4398c0984e5SSebastian Reichel INIT_WORK(&isp->work, isp1704_charger_work); 4408c0984e5SSebastian Reichel 4418c0984e5SSebastian Reichel isp->nb.notifier_call = isp1704_notifier_call; 4428c0984e5SSebastian Reichel 4438c0984e5SSebastian Reichel ret = usb_register_notifier(isp->phy, &isp->nb); 4448c0984e5SSebastian Reichel if (ret) { 4458c0984e5SSebastian Reichel dev_err(&pdev->dev, "usb_register_notifier failed\n"); 4468c0984e5SSebastian Reichel goto fail2; 4478c0984e5SSebastian Reichel } 4488c0984e5SSebastian Reichel 4498c0984e5SSebastian Reichel dev_info(isp->dev, "registered with product id %s\n", isp->model); 4508c0984e5SSebastian Reichel 4518c0984e5SSebastian Reichel /* 4528c0984e5SSebastian Reichel * Taking over the D+ pullup. 4538c0984e5SSebastian Reichel * 4548c0984e5SSebastian Reichel * FIXME: The device will be disconnected if it was already 4558c0984e5SSebastian Reichel * enumerated. The charger driver should be always loaded before any 4568c0984e5SSebastian Reichel * gadget is loaded. 4578c0984e5SSebastian Reichel */ 4588c0984e5SSebastian Reichel if (isp->phy->otg->gadget) 4598c0984e5SSebastian Reichel usb_gadget_disconnect(isp->phy->otg->gadget); 4608c0984e5SSebastian Reichel 4618c0984e5SSebastian Reichel if (isp->phy->last_event == USB_EVENT_NONE) 4628c0984e5SSebastian Reichel isp1704_charger_set_power(isp, 0); 4638c0984e5SSebastian Reichel 4648c0984e5SSebastian Reichel /* Detect charger if VBUS is valid (the cable was already plugged). */ 4658c0984e5SSebastian Reichel if (isp->phy->last_event == USB_EVENT_VBUS && 4668c0984e5SSebastian Reichel !isp->phy->otg->default_a) 4678c0984e5SSebastian Reichel schedule_work(&isp->work); 4688c0984e5SSebastian Reichel 4698c0984e5SSebastian Reichel return 0; 4708c0984e5SSebastian Reichel fail2: 4718c0984e5SSebastian Reichel power_supply_unregister(isp->psy); 4728c0984e5SSebastian Reichel fail1: 4738c0984e5SSebastian Reichel isp1704_charger_set_power(isp, 0); 4748c0984e5SSebastian Reichel fail0: 4758c0984e5SSebastian Reichel dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); 4768c0984e5SSebastian Reichel 4778c0984e5SSebastian Reichel return ret; 4788c0984e5SSebastian Reichel } 4798c0984e5SSebastian Reichel 4808c0984e5SSebastian Reichel static int isp1704_charger_remove(struct platform_device *pdev) 4818c0984e5SSebastian Reichel { 4828c0984e5SSebastian Reichel struct isp1704_charger *isp = platform_get_drvdata(pdev); 4838c0984e5SSebastian Reichel 4848c0984e5SSebastian Reichel usb_unregister_notifier(isp->phy, &isp->nb); 4858c0984e5SSebastian Reichel power_supply_unregister(isp->psy); 4868c0984e5SSebastian Reichel isp1704_charger_set_power(isp, 0); 4878c0984e5SSebastian Reichel 4888c0984e5SSebastian Reichel return 0; 4898c0984e5SSebastian Reichel } 4908c0984e5SSebastian Reichel 4918c0984e5SSebastian Reichel #ifdef CONFIG_OF 4928c0984e5SSebastian Reichel static const struct of_device_id omap_isp1704_of_match[] = { 4938c0984e5SSebastian Reichel { .compatible = "nxp,isp1704", }, 4948c0984e5SSebastian Reichel { .compatible = "nxp,isp1707", }, 4958c0984e5SSebastian Reichel {}, 4968c0984e5SSebastian Reichel }; 4978c0984e5SSebastian Reichel MODULE_DEVICE_TABLE(of, omap_isp1704_of_match); 4988c0984e5SSebastian Reichel #endif 4998c0984e5SSebastian Reichel 5008c0984e5SSebastian Reichel static struct platform_driver isp1704_charger_driver = { 5018c0984e5SSebastian Reichel .driver = { 5028c0984e5SSebastian Reichel .name = "isp1704_charger", 5038c0984e5SSebastian Reichel .of_match_table = of_match_ptr(omap_isp1704_of_match), 5048c0984e5SSebastian Reichel }, 5058c0984e5SSebastian Reichel .probe = isp1704_charger_probe, 5068c0984e5SSebastian Reichel .remove = isp1704_charger_remove, 5078c0984e5SSebastian Reichel }; 5088c0984e5SSebastian Reichel 5098c0984e5SSebastian Reichel module_platform_driver(isp1704_charger_driver); 5108c0984e5SSebastian Reichel 5118c0984e5SSebastian Reichel MODULE_ALIAS("platform:isp1704_charger"); 5128c0984e5SSebastian Reichel MODULE_AUTHOR("Nokia Corporation"); 5138c0984e5SSebastian Reichel MODULE_DESCRIPTION("ISP170x USB Charger driver"); 5148c0984e5SSebastian Reichel MODULE_LICENSE("GPL"); 515