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