162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ISP1704 USB Charger Detection driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2010 Nokia Corporation 662306a36Sopenharmony_ci * Copyright (C) 2012 - 2013 Pali Rohár <pali@kernel.org> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/types.h> 1462306a36Sopenharmony_ci#include <linux/device.h> 1562306a36Sopenharmony_ci#include <linux/sysfs.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/power_supply.h> 1862306a36Sopenharmony_ci#include <linux/delay.h> 1962306a36Sopenharmony_ci#include <linux/of.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 2262306a36Sopenharmony_ci#include <linux/usb/otg.h> 2362306a36Sopenharmony_ci#include <linux/usb/ulpi.h> 2462306a36Sopenharmony_ci#include <linux/usb/ch9.h> 2562306a36Sopenharmony_ci#include <linux/usb/gadget.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* Vendor specific Power Control register */ 2862306a36Sopenharmony_ci#define ISP1704_PWR_CTRL 0x3d 2962306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_SWCTRL (1 << 0) 3062306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_DET_COMP (1 << 1) 3162306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2) 3262306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3) 3362306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4) 3462306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5) 3562306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6) 3662306a36Sopenharmony_ci#define ISP1704_PWR_CTRL_HWDETECT (1 << 7) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define NXP_VENDOR_ID 0x04cc 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic u16 isp170x_id[] = { 4162306a36Sopenharmony_ci 0x1704, 4262306a36Sopenharmony_ci 0x1707, 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistruct isp1704_charger { 4662306a36Sopenharmony_ci struct device *dev; 4762306a36Sopenharmony_ci struct power_supply *psy; 4862306a36Sopenharmony_ci struct power_supply_desc psy_desc; 4962306a36Sopenharmony_ci struct gpio_desc *enable_gpio; 5062306a36Sopenharmony_ci struct usb_phy *phy; 5162306a36Sopenharmony_ci struct notifier_block nb; 5262306a36Sopenharmony_ci struct work_struct work; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci /* properties */ 5562306a36Sopenharmony_ci char model[8]; 5662306a36Sopenharmony_ci unsigned present:1; 5762306a36Sopenharmony_ci unsigned online:1; 5862306a36Sopenharmony_ci unsigned current_max; 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic inline int isp1704_read(struct isp1704_charger *isp, u32 reg) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci return usb_phy_io_read(isp->phy, reg); 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci return usb_phy_io_write(isp->phy, val, reg); 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci gpiod_set_value(isp->enable_gpio, on); 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* 7762306a36Sopenharmony_ci * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB 7862306a36Sopenharmony_ci * chargers). 7962306a36Sopenharmony_ci * 8062306a36Sopenharmony_ci * REVISIT: The method is defined in Battery Charging Specification and is 8162306a36Sopenharmony_ci * applicable to any ULPI transceiver. Nothing isp170x specific here. 8262306a36Sopenharmony_ci */ 8362306a36Sopenharmony_cistatic inline int isp1704_charger_type(struct isp1704_charger *isp) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci u8 reg; 8662306a36Sopenharmony_ci u8 func_ctrl; 8762306a36Sopenharmony_ci u8 otg_ctrl; 8862306a36Sopenharmony_ci int type = POWER_SUPPLY_TYPE_USB_DCP; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL); 9162306a36Sopenharmony_ci otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* disable pulldowns */ 9462306a36Sopenharmony_ci reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN; 9562306a36Sopenharmony_ci isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci /* full speed */ 9862306a36Sopenharmony_ci isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), 9962306a36Sopenharmony_ci ULPI_FUNC_CTRL_XCVRSEL_MASK); 10062306a36Sopenharmony_ci isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), 10162306a36Sopenharmony_ci ULPI_FUNC_CTRL_FULL_SPEED); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci /* Enable strong pull-up on DP (1.5K) and reset */ 10462306a36Sopenharmony_ci reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; 10562306a36Sopenharmony_ci isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg); 10662306a36Sopenharmony_ci usleep_range(1000, 2000); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci reg = isp1704_read(isp, ULPI_DEBUG); 10962306a36Sopenharmony_ci if ((reg & 3) != 3) 11062306a36Sopenharmony_ci type = POWER_SUPPLY_TYPE_USB_CDP; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* recover original state */ 11362306a36Sopenharmony_ci isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl); 11462306a36Sopenharmony_ci isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci return type; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci/* 12062306a36Sopenharmony_ci * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger 12162306a36Sopenharmony_ci * is actually a dedicated charger, the following steps need to be taken. 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_cistatic inline int isp1704_charger_verify(struct isp1704_charger *isp) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci int ret = 0; 12662306a36Sopenharmony_ci u8 r; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* Reset the transceiver */ 12962306a36Sopenharmony_ci r = isp1704_read(isp, ULPI_FUNC_CTRL); 13062306a36Sopenharmony_ci r |= ULPI_FUNC_CTRL_RESET; 13162306a36Sopenharmony_ci isp1704_write(isp, ULPI_FUNC_CTRL, r); 13262306a36Sopenharmony_ci usleep_range(1000, 2000); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* Set normal mode */ 13562306a36Sopenharmony_ci r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); 13662306a36Sopenharmony_ci isp1704_write(isp, ULPI_FUNC_CTRL, r); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci /* Clear the DP and DM pull-down bits */ 13962306a36Sopenharmony_ci r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN; 14062306a36Sopenharmony_ci isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* Enable strong pull-up on DP (1.5K) and reset */ 14362306a36Sopenharmony_ci r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; 14462306a36Sopenharmony_ci isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r); 14562306a36Sopenharmony_ci usleep_range(1000, 2000); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci /* Read the line state */ 14862306a36Sopenharmony_ci if (!isp1704_read(isp, ULPI_DEBUG)) { 14962306a36Sopenharmony_ci /* Disable strong pull-up on DP (1.5K) */ 15062306a36Sopenharmony_ci isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), 15162306a36Sopenharmony_ci ULPI_FUNC_CTRL_TERMSELECT); 15262306a36Sopenharmony_ci return 1; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* Is it a charger or PS/2 connection */ 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* Enable weak pull-up resistor on DP */ 15862306a36Sopenharmony_ci isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), 15962306a36Sopenharmony_ci ISP1704_PWR_CTRL_DP_WKPU_EN); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* Disable strong pull-up on DP (1.5K) */ 16262306a36Sopenharmony_ci isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), 16362306a36Sopenharmony_ci ULPI_FUNC_CTRL_TERMSELECT); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* Enable weak pull-down resistor on DM */ 16662306a36Sopenharmony_ci isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL), 16762306a36Sopenharmony_ci ULPI_OTG_CTRL_DM_PULLDOWN); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* It's a charger if the line states are clear */ 17062306a36Sopenharmony_ci if (!(isp1704_read(isp, ULPI_DEBUG))) 17162306a36Sopenharmony_ci ret = 1; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci /* Disable weak pull-up resistor on DP */ 17462306a36Sopenharmony_ci isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL), 17562306a36Sopenharmony_ci ISP1704_PWR_CTRL_DP_WKPU_EN); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return ret; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic inline int isp1704_charger_detect(struct isp1704_charger *isp) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci unsigned long timeout; 18362306a36Sopenharmony_ci u8 pwr_ctrl; 18462306a36Sopenharmony_ci int ret = 0; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* set SW control bit in PWR_CTRL register */ 18962306a36Sopenharmony_ci isp1704_write(isp, ISP1704_PWR_CTRL, 19062306a36Sopenharmony_ci ISP1704_PWR_CTRL_SWCTRL); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* enable manual charger detection */ 19362306a36Sopenharmony_ci isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), 19462306a36Sopenharmony_ci ISP1704_PWR_CTRL_SWCTRL 19562306a36Sopenharmony_ci | ISP1704_PWR_CTRL_DPVSRC_EN); 19662306a36Sopenharmony_ci usleep_range(1000, 2000); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(300); 19962306a36Sopenharmony_ci do { 20062306a36Sopenharmony_ci /* Check if there is a charger */ 20162306a36Sopenharmony_ci if (isp1704_read(isp, ISP1704_PWR_CTRL) 20262306a36Sopenharmony_ci & ISP1704_PWR_CTRL_VDAT_DET) { 20362306a36Sopenharmony_ci ret = isp1704_charger_verify(isp); 20462306a36Sopenharmony_ci break; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci } while (!time_after(jiffies, timeout) && isp->online); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* recover original state */ 20962306a36Sopenharmony_ci isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci return ret; 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci if (isp1704_charger_detect(isp) && 21762306a36Sopenharmony_ci isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP) 21862306a36Sopenharmony_ci return true; 21962306a36Sopenharmony_ci else 22062306a36Sopenharmony_ci return false; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic void isp1704_charger_work(struct work_struct *data) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct isp1704_charger *isp = 22662306a36Sopenharmony_ci container_of(data, struct isp1704_charger, work); 22762306a36Sopenharmony_ci static DEFINE_MUTEX(lock); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci mutex_lock(&lock); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci switch (isp->phy->last_event) { 23262306a36Sopenharmony_ci case USB_EVENT_VBUS: 23362306a36Sopenharmony_ci /* do not call wall charger detection more times */ 23462306a36Sopenharmony_ci if (!isp->present) { 23562306a36Sopenharmony_ci isp->online = true; 23662306a36Sopenharmony_ci isp->present = 1; 23762306a36Sopenharmony_ci isp1704_charger_set_power(isp, 1); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* detect wall charger */ 24062306a36Sopenharmony_ci if (isp1704_charger_detect_dcp(isp)) { 24162306a36Sopenharmony_ci isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; 24262306a36Sopenharmony_ci isp->current_max = 1800; 24362306a36Sopenharmony_ci } else { 24462306a36Sopenharmony_ci isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; 24562306a36Sopenharmony_ci isp->current_max = 500; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* enable data pullups */ 24962306a36Sopenharmony_ci if (isp->phy->otg->gadget) 25062306a36Sopenharmony_ci usb_gadget_connect(isp->phy->otg->gadget); 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) { 25462306a36Sopenharmony_ci /* 25562306a36Sopenharmony_ci * Only 500mA here or high speed chirp 25662306a36Sopenharmony_ci * handshaking may break 25762306a36Sopenharmony_ci */ 25862306a36Sopenharmony_ci if (isp->current_max > 500) 25962306a36Sopenharmony_ci isp->current_max = 500; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci if (isp->current_max > 100) 26262306a36Sopenharmony_ci isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci break; 26562306a36Sopenharmony_ci case USB_EVENT_NONE: 26662306a36Sopenharmony_ci isp->online = false; 26762306a36Sopenharmony_ci isp->present = 0; 26862306a36Sopenharmony_ci isp->current_max = 0; 26962306a36Sopenharmony_ci isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* 27262306a36Sopenharmony_ci * Disable data pullups. We need to prevent the controller from 27362306a36Sopenharmony_ci * enumerating. 27462306a36Sopenharmony_ci * 27562306a36Sopenharmony_ci * FIXME: This is here to allow charger detection with Host/HUB 27662306a36Sopenharmony_ci * chargers. The pullups may be enabled elsewhere, so this can 27762306a36Sopenharmony_ci * not be the final solution. 27862306a36Sopenharmony_ci */ 27962306a36Sopenharmony_ci if (isp->phy->otg->gadget) 28062306a36Sopenharmony_ci usb_gadget_disconnect(isp->phy->otg->gadget); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci isp1704_charger_set_power(isp, 0); 28362306a36Sopenharmony_ci break; 28462306a36Sopenharmony_ci default: 28562306a36Sopenharmony_ci goto out; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci power_supply_changed(isp->psy); 28962306a36Sopenharmony_ciout: 29062306a36Sopenharmony_ci mutex_unlock(&lock); 29162306a36Sopenharmony_ci} 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cistatic int isp1704_notifier_call(struct notifier_block *nb, 29462306a36Sopenharmony_ci unsigned long val, void *v) 29562306a36Sopenharmony_ci{ 29662306a36Sopenharmony_ci struct isp1704_charger *isp = 29762306a36Sopenharmony_ci container_of(nb, struct isp1704_charger, nb); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci schedule_work(&isp->work); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci return NOTIFY_OK; 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic int isp1704_charger_get_property(struct power_supply *psy, 30562306a36Sopenharmony_ci enum power_supply_property psp, 30662306a36Sopenharmony_ci union power_supply_propval *val) 30762306a36Sopenharmony_ci{ 30862306a36Sopenharmony_ci struct isp1704_charger *isp = power_supply_get_drvdata(psy); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci switch (psp) { 31162306a36Sopenharmony_ci case POWER_SUPPLY_PROP_PRESENT: 31262306a36Sopenharmony_ci val->intval = isp->present; 31362306a36Sopenharmony_ci break; 31462306a36Sopenharmony_ci case POWER_SUPPLY_PROP_ONLINE: 31562306a36Sopenharmony_ci val->intval = isp->online; 31662306a36Sopenharmony_ci break; 31762306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CURRENT_MAX: 31862306a36Sopenharmony_ci val->intval = isp->current_max; 31962306a36Sopenharmony_ci break; 32062306a36Sopenharmony_ci case POWER_SUPPLY_PROP_MODEL_NAME: 32162306a36Sopenharmony_ci val->strval = isp->model; 32262306a36Sopenharmony_ci break; 32362306a36Sopenharmony_ci case POWER_SUPPLY_PROP_MANUFACTURER: 32462306a36Sopenharmony_ci val->strval = "NXP"; 32562306a36Sopenharmony_ci break; 32662306a36Sopenharmony_ci default: 32762306a36Sopenharmony_ci return -EINVAL; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci return 0; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic enum power_supply_property power_props[] = { 33362306a36Sopenharmony_ci POWER_SUPPLY_PROP_PRESENT, 33462306a36Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 33562306a36Sopenharmony_ci POWER_SUPPLY_PROP_CURRENT_MAX, 33662306a36Sopenharmony_ci POWER_SUPPLY_PROP_MODEL_NAME, 33762306a36Sopenharmony_ci POWER_SUPPLY_PROP_MANUFACTURER, 33862306a36Sopenharmony_ci}; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic inline int isp1704_test_ulpi(struct isp1704_charger *isp) 34162306a36Sopenharmony_ci{ 34262306a36Sopenharmony_ci int vendor; 34362306a36Sopenharmony_ci int product; 34462306a36Sopenharmony_ci int i; 34562306a36Sopenharmony_ci int ret; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* Test ULPI interface */ 34862306a36Sopenharmony_ci ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa); 34962306a36Sopenharmony_ci if (ret < 0) 35062306a36Sopenharmony_ci return ret; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci ret = isp1704_read(isp, ULPI_SCRATCH); 35362306a36Sopenharmony_ci if (ret < 0) 35462306a36Sopenharmony_ci return ret; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci if (ret != 0xaa) 35762306a36Sopenharmony_ci return -ENODEV; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* Verify the product and vendor id matches */ 36062306a36Sopenharmony_ci vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW); 36162306a36Sopenharmony_ci vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8; 36262306a36Sopenharmony_ci if (vendor != NXP_VENDOR_ID) 36362306a36Sopenharmony_ci return -ENODEV; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW); 36662306a36Sopenharmony_ci product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) { 36962306a36Sopenharmony_ci if (product == isp170x_id[i]) { 37062306a36Sopenharmony_ci sprintf(isp->model, "isp%x", product); 37162306a36Sopenharmony_ci return product; 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci } 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci dev_err(isp->dev, "product id %x not matching known ids", product); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci return -ENODEV; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic int isp1704_charger_probe(struct platform_device *pdev) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci struct isp1704_charger *isp; 38362306a36Sopenharmony_ci int ret = -ENODEV; 38462306a36Sopenharmony_ci struct power_supply_config psy_cfg = {}; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); 38762306a36Sopenharmony_ci if (!isp) 38862306a36Sopenharmony_ci return -ENOMEM; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci isp->enable_gpio = devm_gpiod_get(&pdev->dev, "nxp,enable", 39162306a36Sopenharmony_ci GPIOD_OUT_HIGH); 39262306a36Sopenharmony_ci if (IS_ERR(isp->enable_gpio)) { 39362306a36Sopenharmony_ci ret = PTR_ERR(isp->enable_gpio); 39462306a36Sopenharmony_ci dev_err(&pdev->dev, "Could not get reset gpio: %d\n", ret); 39562306a36Sopenharmony_ci return ret; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci if (pdev->dev.of_node) 39962306a36Sopenharmony_ci isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); 40062306a36Sopenharmony_ci else 40162306a36Sopenharmony_ci isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci if (IS_ERR(isp->phy)) { 40462306a36Sopenharmony_ci ret = PTR_ERR(isp->phy); 40562306a36Sopenharmony_ci dev_err(&pdev->dev, "usb_get_phy failed\n"); 40662306a36Sopenharmony_ci goto fail0; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci isp->dev = &pdev->dev; 41062306a36Sopenharmony_ci platform_set_drvdata(pdev, isp); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci isp1704_charger_set_power(isp, 1); 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci ret = isp1704_test_ulpi(isp); 41562306a36Sopenharmony_ci if (ret < 0) { 41662306a36Sopenharmony_ci dev_err(&pdev->dev, "isp1704_test_ulpi failed\n"); 41762306a36Sopenharmony_ci goto fail1; 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci isp->psy_desc.name = "isp1704"; 42162306a36Sopenharmony_ci isp->psy_desc.type = POWER_SUPPLY_TYPE_USB; 42262306a36Sopenharmony_ci isp->psy_desc.properties = power_props; 42362306a36Sopenharmony_ci isp->psy_desc.num_properties = ARRAY_SIZE(power_props); 42462306a36Sopenharmony_ci isp->psy_desc.get_property = isp1704_charger_get_property; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci psy_cfg.drv_data = isp; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg); 42962306a36Sopenharmony_ci if (IS_ERR(isp->psy)) { 43062306a36Sopenharmony_ci ret = PTR_ERR(isp->psy); 43162306a36Sopenharmony_ci dev_err(&pdev->dev, "power_supply_register failed\n"); 43262306a36Sopenharmony_ci goto fail1; 43362306a36Sopenharmony_ci } 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci /* 43662306a36Sopenharmony_ci * REVISIT: using work in order to allow the usb notifications to be 43762306a36Sopenharmony_ci * made atomically in the future. 43862306a36Sopenharmony_ci */ 43962306a36Sopenharmony_ci INIT_WORK(&isp->work, isp1704_charger_work); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci isp->nb.notifier_call = isp1704_notifier_call; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci ret = usb_register_notifier(isp->phy, &isp->nb); 44462306a36Sopenharmony_ci if (ret) { 44562306a36Sopenharmony_ci dev_err(&pdev->dev, "usb_register_notifier failed\n"); 44662306a36Sopenharmony_ci goto fail2; 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci dev_info(isp->dev, "registered with product id %s\n", isp->model); 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci /* 45262306a36Sopenharmony_ci * Taking over the D+ pullup. 45362306a36Sopenharmony_ci * 45462306a36Sopenharmony_ci * FIXME: The device will be disconnected if it was already 45562306a36Sopenharmony_ci * enumerated. The charger driver should be always loaded before any 45662306a36Sopenharmony_ci * gadget is loaded. 45762306a36Sopenharmony_ci */ 45862306a36Sopenharmony_ci if (isp->phy->otg->gadget) 45962306a36Sopenharmony_ci usb_gadget_disconnect(isp->phy->otg->gadget); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci if (isp->phy->last_event == USB_EVENT_NONE) 46262306a36Sopenharmony_ci isp1704_charger_set_power(isp, 0); 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci /* Detect charger if VBUS is valid (the cable was already plugged). */ 46562306a36Sopenharmony_ci if (isp->phy->last_event == USB_EVENT_VBUS && 46662306a36Sopenharmony_ci !isp->phy->otg->default_a) 46762306a36Sopenharmony_ci schedule_work(&isp->work); 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci return 0; 47062306a36Sopenharmony_cifail2: 47162306a36Sopenharmony_ci power_supply_unregister(isp->psy); 47262306a36Sopenharmony_cifail1: 47362306a36Sopenharmony_ci isp1704_charger_set_power(isp, 0); 47462306a36Sopenharmony_cifail0: 47562306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci return ret; 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_cistatic int isp1704_charger_remove(struct platform_device *pdev) 48162306a36Sopenharmony_ci{ 48262306a36Sopenharmony_ci struct isp1704_charger *isp = platform_get_drvdata(pdev); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci usb_unregister_notifier(isp->phy, &isp->nb); 48562306a36Sopenharmony_ci power_supply_unregister(isp->psy); 48662306a36Sopenharmony_ci isp1704_charger_set_power(isp, 0); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci return 0; 48962306a36Sopenharmony_ci} 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci#ifdef CONFIG_OF 49262306a36Sopenharmony_cistatic const struct of_device_id omap_isp1704_of_match[] = { 49362306a36Sopenharmony_ci { .compatible = "nxp,isp1704", }, 49462306a36Sopenharmony_ci { .compatible = "nxp,isp1707", }, 49562306a36Sopenharmony_ci {}, 49662306a36Sopenharmony_ci}; 49762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, omap_isp1704_of_match); 49862306a36Sopenharmony_ci#endif 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic struct platform_driver isp1704_charger_driver = { 50162306a36Sopenharmony_ci .driver = { 50262306a36Sopenharmony_ci .name = "isp1704_charger", 50362306a36Sopenharmony_ci .of_match_table = of_match_ptr(omap_isp1704_of_match), 50462306a36Sopenharmony_ci }, 50562306a36Sopenharmony_ci .probe = isp1704_charger_probe, 50662306a36Sopenharmony_ci .remove = isp1704_charger_remove, 50762306a36Sopenharmony_ci}; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_cimodule_platform_driver(isp1704_charger_driver); 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ciMODULE_ALIAS("platform:isp1704_charger"); 51262306a36Sopenharmony_ciMODULE_AUTHOR("Nokia Corporation"); 51362306a36Sopenharmony_ciMODULE_DESCRIPTION("ISP170x USB Charger driver"); 51462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 515