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