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