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