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