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