brintos

brintos / linux-shallow public Read only

0
0
Text · 12.6 KiB · 860d861 Raw
513 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * ISP1704 USB Charger Detection driver4 *5 * Copyright (C) 2010 Nokia Corporation6 * Copyright (C) 2012 - 2013 Pali Rohár <pali@kernel.org>7 */8 9#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/err.h>12#include <linux/init.h>13#include <linux/types.h>14#include <linux/device.h>15#include <linux/sysfs.h>16#include <linux/platform_device.h>17#include <linux/power_supply.h>18#include <linux/delay.h>19#include <linux/of.h>20 21#include <linux/gpio/consumer.h>22#include <linux/usb/otg.h>23#include <linux/usb/ulpi.h>24#include <linux/usb/ch9.h>25#include <linux/usb/gadget.h>26 27/* Vendor specific Power Control register */28#define ISP1704_PWR_CTRL		0x3d29#define ISP1704_PWR_CTRL_SWCTRL		(1 << 0)30#define ISP1704_PWR_CTRL_DET_COMP	(1 << 1)31#define ISP1704_PWR_CTRL_BVALID_RISE	(1 << 2)32#define ISP1704_PWR_CTRL_BVALID_FALL	(1 << 3)33#define ISP1704_PWR_CTRL_DP_WKPU_EN	(1 << 4)34#define ISP1704_PWR_CTRL_VDAT_DET	(1 << 5)35#define ISP1704_PWR_CTRL_DPVSRC_EN	(1 << 6)36#define ISP1704_PWR_CTRL_HWDETECT	(1 << 7)37 38#define NXP_VENDOR_ID			0x04cc39 40static u16 isp170x_id[] = {41	0x1704,42	0x1707,43};44 45struct isp1704_charger {46	struct device			*dev;47	struct power_supply		*psy;48	struct power_supply_desc	psy_desc;49	struct gpio_desc		*enable_gpio;50	struct usb_phy			*phy;51	struct notifier_block		nb;52	struct work_struct		work;53 54	/* properties */55	char			model[8];56	unsigned		present:1;57	unsigned		online:1;58	unsigned		current_max;59};60 61static inline int isp1704_read(struct isp1704_charger *isp, u32 reg)62{63	return usb_phy_io_read(isp->phy, reg);64}65 66static inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val)67{68	return usb_phy_io_write(isp->phy, val, reg);69}70 71static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on)72{73	gpiod_set_value(isp->enable_gpio, on);74}75 76/*77 * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB78 * chargers).79 *80 * REVISIT: The method is defined in Battery Charging Specification and is81 * applicable to any ULPI transceiver. Nothing isp170x specific here.82 */83static inline int isp1704_charger_type(struct isp1704_charger *isp)84{85	u8 reg;86	u8 func_ctrl;87	u8 otg_ctrl;88	int type = POWER_SUPPLY_TYPE_USB_DCP;89 90	func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL);91	otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL);92 93	/* disable pulldowns */94	reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;95	isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg);96 97	/* full speed */98	isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),99			ULPI_FUNC_CTRL_XCVRSEL_MASK);100	isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL),101			ULPI_FUNC_CTRL_FULL_SPEED);102 103	/* Enable strong pull-up on DP (1.5K) and reset */104	reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;105	isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg);106	usleep_range(1000, 2000);107 108	reg = isp1704_read(isp, ULPI_DEBUG);109	if ((reg & 3) != 3)110		type = POWER_SUPPLY_TYPE_USB_CDP;111 112	/* recover original state */113	isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl);114	isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl);115 116	return type;117}118 119/*120 * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger121 * is actually a dedicated charger, the following steps need to be taken.122 */123static inline int isp1704_charger_verify(struct isp1704_charger *isp)124{125	int	ret = 0;126	u8	r;127 128	/* Reset the transceiver */129	r = isp1704_read(isp, ULPI_FUNC_CTRL);130	r |= ULPI_FUNC_CTRL_RESET;131	isp1704_write(isp, ULPI_FUNC_CTRL, r);132	usleep_range(1000, 2000);133 134	/* Set normal mode */135	r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);136	isp1704_write(isp, ULPI_FUNC_CTRL, r);137 138	/* Clear the DP and DM pull-down bits */139	r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;140	isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r);141 142	/* Enable strong pull-up on DP (1.5K) and reset */143	r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;144	isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r);145	usleep_range(1000, 2000);146 147	/* Read the line state */148	if (!isp1704_read(isp, ULPI_DEBUG)) {149		/* Disable strong pull-up on DP (1.5K) */150		isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),151				ULPI_FUNC_CTRL_TERMSELECT);152		return 1;153	}154 155	/* Is it a charger or PS/2 connection */156 157	/* Enable weak pull-up resistor on DP */158	isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),159			ISP1704_PWR_CTRL_DP_WKPU_EN);160 161	/* Disable strong pull-up on DP (1.5K) */162	isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),163			ULPI_FUNC_CTRL_TERMSELECT);164 165	/* Enable weak pull-down resistor on DM */166	isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL),167			ULPI_OTG_CTRL_DM_PULLDOWN);168 169	/* It's a charger if the line states are clear */170	if (!(isp1704_read(isp, ULPI_DEBUG)))171		ret = 1;172 173	/* Disable weak pull-up resistor on DP */174	isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL),175			ISP1704_PWR_CTRL_DP_WKPU_EN);176 177	return ret;178}179 180static inline int isp1704_charger_detect(struct isp1704_charger *isp)181{182	unsigned long	timeout;183	u8		pwr_ctrl;184	int		ret = 0;185 186	pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL);187 188	/* set SW control bit in PWR_CTRL register */189	isp1704_write(isp, ISP1704_PWR_CTRL,190			ISP1704_PWR_CTRL_SWCTRL);191 192	/* enable manual charger detection */193	isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),194			ISP1704_PWR_CTRL_SWCTRL195			| ISP1704_PWR_CTRL_DPVSRC_EN);196	usleep_range(1000, 2000);197 198	timeout = jiffies + msecs_to_jiffies(300);199	do {200		/* Check if there is a charger */201		if (isp1704_read(isp, ISP1704_PWR_CTRL)202				& ISP1704_PWR_CTRL_VDAT_DET) {203			ret = isp1704_charger_verify(isp);204			break;205		}206	} while (!time_after(jiffies, timeout) && isp->online);207 208	/* recover original state */209	isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl);210 211	return ret;212}213 214static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp)215{216	if (isp1704_charger_detect(isp) &&217			isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP)218		return true;219	else220		return false;221}222 223static void isp1704_charger_work(struct work_struct *data)224{225	struct isp1704_charger	*isp =226		container_of(data, struct isp1704_charger, work);227	static DEFINE_MUTEX(lock);228 229	mutex_lock(&lock);230 231	switch (isp->phy->last_event) {232	case USB_EVENT_VBUS:233		/* do not call wall charger detection more times */234		if (!isp->present) {235			isp->online = true;236			isp->present = 1;237			isp1704_charger_set_power(isp, 1);238 239			/* detect wall charger */240			if (isp1704_charger_detect_dcp(isp)) {241				isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;242				isp->current_max = 1800;243			} else {244				isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;245				isp->current_max = 500;246			}247 248			/* enable data pullups */249			if (isp->phy->otg->gadget)250				usb_gadget_connect(isp->phy->otg->gadget);251		}252 253		if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) {254			/*255			 * Only 500mA here or high speed chirp256			 * handshaking may break257			 */258			if (isp->current_max > 500)259				isp->current_max = 500;260 261			if (isp->current_max > 100)262				isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;263		}264		break;265	case USB_EVENT_NONE:266		isp->online = false;267		isp->present = 0;268		isp->current_max = 0;269		isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;270 271		/*272		 * Disable data pullups. We need to prevent the controller from273		 * enumerating.274		 *275		 * FIXME: This is here to allow charger detection with Host/HUB276		 * chargers. The pullups may be enabled elsewhere, so this can277		 * not be the final solution.278		 */279		if (isp->phy->otg->gadget)280			usb_gadget_disconnect(isp->phy->otg->gadget);281 282		isp1704_charger_set_power(isp, 0);283		break;284	default:285		goto out;286	}287 288	power_supply_changed(isp->psy);289out:290	mutex_unlock(&lock);291}292 293static int isp1704_notifier_call(struct notifier_block *nb,294		unsigned long val, void *v)295{296	struct isp1704_charger *isp =297		container_of(nb, struct isp1704_charger, nb);298 299	schedule_work(&isp->work);300 301	return NOTIFY_OK;302}303 304static int isp1704_charger_get_property(struct power_supply *psy,305				enum power_supply_property psp,306				union power_supply_propval *val)307{308	struct isp1704_charger *isp = power_supply_get_drvdata(psy);309 310	switch (psp) {311	case POWER_SUPPLY_PROP_PRESENT:312		val->intval = isp->present;313		break;314	case POWER_SUPPLY_PROP_ONLINE:315		val->intval = isp->online;316		break;317	case POWER_SUPPLY_PROP_CURRENT_MAX:318		val->intval = isp->current_max;319		break;320	case POWER_SUPPLY_PROP_MODEL_NAME:321		val->strval = isp->model;322		break;323	case POWER_SUPPLY_PROP_MANUFACTURER:324		val->strval = "NXP";325		break;326	default:327		return -EINVAL;328	}329	return 0;330}331 332static enum power_supply_property power_props[] = {333	POWER_SUPPLY_PROP_PRESENT,334	POWER_SUPPLY_PROP_ONLINE,335	POWER_SUPPLY_PROP_CURRENT_MAX,336	POWER_SUPPLY_PROP_MODEL_NAME,337	POWER_SUPPLY_PROP_MANUFACTURER,338};339 340static inline int isp1704_test_ulpi(struct isp1704_charger *isp)341{342	int vendor;343	int product;344	int i;345	int ret;346 347	/* Test ULPI interface */348	ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa);349	if (ret < 0)350		return ret;351 352	ret = isp1704_read(isp, ULPI_SCRATCH);353	if (ret < 0)354		return ret;355 356	if (ret != 0xaa)357		return -ENODEV;358 359	/* Verify the product and vendor id matches */360	vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW);361	vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8;362	if (vendor != NXP_VENDOR_ID)363		return -ENODEV;364 365	product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW);366	product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8;367 368	for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) {369		if (product == isp170x_id[i]) {370			sprintf(isp->model, "isp%x", product);371			return product;372		}373	}374 375	dev_err(isp->dev, "product id %x not matching known ids", product);376 377	return -ENODEV;378}379 380static int isp1704_charger_probe(struct platform_device *pdev)381{382	struct isp1704_charger	*isp;383	int			ret = -ENODEV;384	struct power_supply_config psy_cfg = {};385 386	isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);387	if (!isp)388		return -ENOMEM;389 390	isp->enable_gpio = devm_gpiod_get(&pdev->dev, "nxp,enable",391					  GPIOD_OUT_HIGH);392	if (IS_ERR(isp->enable_gpio)) {393		ret = PTR_ERR(isp->enable_gpio);394		dev_err(&pdev->dev, "Could not get reset gpio: %d\n", ret);395		return ret;396	}397 398	if (pdev->dev.of_node)399		isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);400	else401		isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);402 403	if (IS_ERR(isp->phy)) {404		ret = PTR_ERR(isp->phy);405		dev_err(&pdev->dev, "usb_get_phy failed\n");406		goto fail0;407	}408 409	isp->dev = &pdev->dev;410	platform_set_drvdata(pdev, isp);411 412	isp1704_charger_set_power(isp, 1);413 414	ret = isp1704_test_ulpi(isp);415	if (ret < 0) {416		dev_err(&pdev->dev, "isp1704_test_ulpi failed\n");417		goto fail1;418	}419 420	isp->psy_desc.name		= "isp1704";421	isp->psy_desc.type		= POWER_SUPPLY_TYPE_USB;422	isp->psy_desc.properties	= power_props;423	isp->psy_desc.num_properties	= ARRAY_SIZE(power_props);424	isp->psy_desc.get_property	= isp1704_charger_get_property;425 426	psy_cfg.drv_data		= isp;427 428	isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg);429	if (IS_ERR(isp->psy)) {430		ret = PTR_ERR(isp->psy);431		dev_err(&pdev->dev, "power_supply_register failed\n");432		goto fail1;433	}434 435	/*436	 * REVISIT: using work in order to allow the usb notifications to be437	 * made atomically in the future.438	 */439	INIT_WORK(&isp->work, isp1704_charger_work);440 441	isp->nb.notifier_call = isp1704_notifier_call;442 443	ret = usb_register_notifier(isp->phy, &isp->nb);444	if (ret) {445		dev_err(&pdev->dev, "usb_register_notifier failed\n");446		goto fail2;447	}448 449	dev_info(isp->dev, "registered with product id %s\n", isp->model);450 451	/*452	 * Taking over the D+ pullup.453	 *454	 * FIXME: The device will be disconnected if it was already455	 * enumerated. The charger driver should be always loaded before any456	 * gadget is loaded.457	 */458	if (isp->phy->otg->gadget)459		usb_gadget_disconnect(isp->phy->otg->gadget);460 461	if (isp->phy->last_event == USB_EVENT_NONE)462		isp1704_charger_set_power(isp, 0);463 464	/* Detect charger if VBUS is valid (the cable was already plugged). */465	if (isp->phy->last_event == USB_EVENT_VBUS &&466			!isp->phy->otg->default_a)467		schedule_work(&isp->work);468 469	return 0;470fail2:471	power_supply_unregister(isp->psy);472fail1:473	isp1704_charger_set_power(isp, 0);474fail0:475	dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);476 477	return ret;478}479 480static void isp1704_charger_remove(struct platform_device *pdev)481{482	struct isp1704_charger *isp = platform_get_drvdata(pdev);483 484	usb_unregister_notifier(isp->phy, &isp->nb);485	power_supply_unregister(isp->psy);486	isp1704_charger_set_power(isp, 0);487}488 489#ifdef CONFIG_OF490static const struct of_device_id omap_isp1704_of_match[] = {491	{ .compatible = "nxp,isp1704", },492	{ .compatible = "nxp,isp1707", },493	{},494};495MODULE_DEVICE_TABLE(of, omap_isp1704_of_match);496#endif497 498static struct platform_driver isp1704_charger_driver = {499	.driver = {500		.name = "isp1704_charger",501		.of_match_table = of_match_ptr(omap_isp1704_of_match),502	},503	.probe = isp1704_charger_probe,504	.remove_new = isp1704_charger_remove,505};506 507module_platform_driver(isp1704_charger_driver);508 509MODULE_ALIAS("platform:isp1704_charger");510MODULE_AUTHOR("Nokia Corporation");511MODULE_DESCRIPTION("ISP170x USB Charger driver");512MODULE_LICENSE("GPL");513