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