[RFC PATH 3/3] phy: ulpi: add support for NXP ISP170X USB PHY

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This driver is based on drivers/power/isp1704_power.c. It
simply converts the original driver to ulpi driver.

Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
---
 drivers/phy/ulpi/Kconfig        |  10 +
 drivers/phy/ulpi/Makefile       |   1 +
 drivers/phy/ulpi/isp1704_ulpi.c | 446 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 457 insertions(+)
 create mode 100644 drivers/phy/ulpi/isp1704_ulpi.c

diff --git a/drivers/phy/ulpi/Kconfig b/drivers/phy/ulpi/Kconfig
index 3211aaa..1340df7 100644
--- a/drivers/phy/ulpi/Kconfig
+++ b/drivers/phy/ulpi/Kconfig
@@ -9,3 +9,13 @@ config ULPI_PHY
 
 	  If unsure, say Y.
 
+if ULPI_PHY
+
+config ULPI_ISP170X
+	tristate "NXP ISP170X USB PHY module"
+	depends on POWER_SUPPLY
+	select USB_PHY
+	help
+	  Support for NXP ISP170X ULPI PHY.
+
+endif
diff --git a/drivers/phy/ulpi/Makefile b/drivers/phy/ulpi/Makefile
index 7ed0895..d873e37 100644
--- a/drivers/phy/ulpi/Makefile
+++ b/drivers/phy/ulpi/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_ULPI_PHY)		+= ulpi.o
+obj-$(CONFIG_ULPI_ISP170X)	+= isp1704_ulpi.o
diff --git a/drivers/phy/ulpi/isp1704_ulpi.c b/drivers/phy/ulpi/isp1704_ulpi.c
new file mode 100644
index 0000000..301fe9b
--- /dev/null
+++ b/drivers/phy/ulpi/isp1704_ulpi.c
@@ -0,0 +1,446 @@
+/**
+ * ISP1704 USB ULPI PHY driver
+ *
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+ *
+ * The code is based on drivers/power/isp1704_charger.c
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2012 - 2013 Pali Rohár <pali.rohar@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/ulpi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/power_supply.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/gadget.h>
+
+/* Vendor specific Power Control register */
+#define ISP1704_PWR_CTRL		0x3d
+#define ISP1704_PWR_CTRL_SWCTRL		(1 << 0)
+#define ISP1704_PWR_CTRL_DET_COMP	(1 << 1)
+#define ISP1704_PWR_CTRL_BVALID_RISE	(1 << 2)
+#define ISP1704_PWR_CTRL_BVALID_FALL	(1 << 3)
+#define ISP1704_PWR_CTRL_DP_WKPU_EN	(1 << 4)
+#define ISP1704_PWR_CTRL_VDAT_DET	(1 << 5)
+#define ISP1704_PWR_CTRL_DPVSRC_EN	(1 << 6)
+#define ISP1704_PWR_CTRL_HWDETECT	(1 << 7)
+
+#define NXP_VENDOR_ID	0x04cc
+
+struct isp1704_phy {
+	struct notifier_block nb;
+	struct power_supply psy;
+	struct work_struct work;
+	struct gpio_desc *gpio;
+	struct usb_phy phy;
+	struct usb_otg otg;
+	struct device *dev;
+
+	/* properties */
+	char model[8];
+	unsigned present:1;
+	unsigned online:1;
+	unsigned current_max;
+};
+
+static inline int isp1704_read(struct isp1704_phy *isp, u8 addr)
+{
+	return ulpi_read(to_ulpi_dev(isp->dev), addr);
+}
+
+static inline int isp1704_write(struct isp1704_phy *isp, u8 addr, u8 val)
+{
+	return ulpi_write(to_ulpi_dev(isp->dev), addr, val);
+}
+
+/* -------------------------------------------------------------------------- */
+
+/**
+ * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB
+ * chargers).
+ *
+ * REVISIT: The method is defined in Battery Charging Specification and is
+ * applicable to any ULPI transceiver. Nothing isp170x specific here.
+ */
+static inline int isp1704_type(struct isp1704_phy *isp)
+{
+	int type = POWER_SUPPLY_TYPE_USB_DCP;
+	u8 func_ctrl;
+	u8 otg_ctrl;
+	u8 val;
+
+	func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL);
+	otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL);
+
+	/* disable pulldowns */
+	val = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;
+	isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), val);
+
+	/* full speed */
+	isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+		      ULPI_FUNC_CTRL_XCVRSEL_MASK);
+	isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL),
+		      ULPI_FUNC_CTRL_FULL_SPEED);
+
+	/* Enable strong pull-up on DP (1.5K) and reset */
+	val = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
+	isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), val);
+	usleep_range(1000, 2000);
+
+	val = isp1704_read(isp, ULPI_DEBUG);
+	if ((val & 3) != 3)
+		type = POWER_SUPPLY_TYPE_USB_CDP;
+
+	/* recover original state */
+	isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl);
+	isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl);
+
+	return type;
+}
+
+/**
+ * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger
+ * is actually a dedicated charger, the following steps need to be taken.
+ */
+static inline int isp1704_verify(struct isp1704_phy *isp)
+{
+	int ret = 0;
+	u8 val;
+
+	/* Reset the transceiver */
+	val = isp1704_read(isp, ULPI_FUNC_CTRL);
+	val |= ULPI_FUNC_CTRL_RESET;
+	isp1704_write(isp, ULPI_FUNC_CTRL, val);
+	usleep_range(1000, 2000);
+
+	/* Set normal mode */
+	val &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);
+	isp1704_write(isp, ULPI_FUNC_CTRL, val);
+
+	/* Clear the DP and DM pull-down bits */
+	val = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;
+	isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), val);
+
+	/* Enable strong pull-up on DP (1.5K) and reset */
+	val = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
+	isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), val);
+	usleep_range(1000, 2000);
+
+	/* Read the line state */
+	if (!isp1704_read(isp, ULPI_DEBUG)) {
+		/* Disable strong pull-up on DP (1.5K) */
+		isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+			      ULPI_FUNC_CTRL_TERMSELECT);
+		return 1;
+	}
+
+	/* Is it a charger or PS/2 connection */
+
+	/* Enable weak pull-up resistor on DP */
+	isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),
+		      ISP1704_PWR_CTRL_DP_WKPU_EN);
+
+	/* Disable strong pull-up on DP (1.5K) */
+	isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+		      ULPI_FUNC_CTRL_TERMSELECT);
+
+	/* Enable weak pull-down resistor on DM */
+	isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL),
+		      ULPI_OTG_CTRL_DM_PULLDOWN);
+
+	/* It's a charger if the line states are clear */
+	if (!(isp1704_read(isp, ULPI_DEBUG)))
+		ret = 1;
+
+	/* Disable weak pull-up resistor on DP */
+	isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL),
+		      ISP1704_PWR_CTRL_DP_WKPU_EN);
+
+	return ret;
+}
+
+static inline int isp1704_detect(struct isp1704_phy *isp)
+{
+	unsigned long timeout;
+	u8 pwr_ctrl;
+	int ret = 0;
+
+	pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL);
+
+	/* set SW control bit in PWR_CTRL register */
+	isp1704_write(isp, ISP1704_PWR_CTRL,
+		      ISP1704_PWR_CTRL_SWCTRL);
+
+	/* enable manual charger detection */
+	isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),
+		      ISP1704_PWR_CTRL_SWCTRL | ISP1704_PWR_CTRL_DPVSRC_EN);
+	usleep_range(1000, 2000);
+
+	timeout = jiffies + msecs_to_jiffies(300);
+	do {
+		/* Check if there is a charger */
+		if (isp1704_read(isp, ISP1704_PWR_CTRL) &
+		    ISP1704_PWR_CTRL_VDAT_DET) {
+			ret = isp1704_verify(isp);
+			break;
+		}
+	} while (!time_after(jiffies, timeout) && isp->online);
+
+	/* recover original state */
+	isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl);
+
+	return ret;
+}
+
+static void isp1704_work(struct work_struct *data)
+{
+	struct isp1704_phy *isp = container_of(data, struct isp1704_phy, work);
+	static DEFINE_MUTEX(lock);
+
+	mutex_lock(&lock);
+
+	switch (isp->phy.last_event) {
+	case USB_EVENT_VBUS:
+		/* do not call wall charger detection more times */
+		if (!isp->present) {
+			gpiod_set_value(isp->gpio, 1);
+			isp->online = 1;
+			isp->present = isp1704_detect(isp);
+
+			if (isp->present)
+				isp->psy.type = isp1704_type(isp);
+
+			if (isp->psy.type == POWER_SUPPLY_TYPE_USB_DCP) {
+				isp->current_max = 1800;
+			} else {
+				isp->psy.type = POWER_SUPPLY_TYPE_USB;
+				isp->current_max = 500;
+			}
+
+			/* enable data pullups */
+			if (isp->otg.gadget)
+				usb_gadget_connect(isp->otg.gadget);
+		}
+		break;
+	case USB_EVENT_NONE:
+		isp->online = false;
+		isp->present = 0;
+		isp->current_max = 0;
+		isp->psy.type = POWER_SUPPLY_TYPE_USB;
+
+		/*
+		 * Disable data pullups. We need to prevent the controller from
+		 * enumerating.
+		 *
+		 * FIXME: This is here to allow charger detection with Host/HUB
+		 * chargers. The pullups may be enabled elsewhere, so this can
+		 * not be the final solution.
+		 */
+		if (isp->otg.gadget)
+			usb_gadget_disconnect(isp->otg.gadget);
+
+		gpiod_set_value(isp->gpio, 0);
+		break;
+	default:
+		goto out;
+	}
+
+	power_supply_changed(&isp->psy);
+out:
+	mutex_unlock(&lock);
+}
+
+static int isp1704_notifier_call(struct notifier_block *nb,
+				 unsigned long val, void *v)
+{
+	struct isp1704_phy *isp = container_of(nb, struct isp1704_phy, nb);
+
+	schedule_work(&isp->work);
+
+	return NOTIFY_OK;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static int isp1704_get_property(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	struct isp1704_phy *isp = container_of(psy, struct isp1704_phy, psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = isp->present;
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = isp->online;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		val->intval = isp->current_max;
+		break;
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = isp->model;
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = "NXP";
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static enum power_supply_property power_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+/* -------------------------------------------------------------------------- */
+
+static int isp1704_power_on(struct phy *phy)
+{
+	struct isp1704_phy *isp = phy_to_ulpi_drvdata(phy);
+
+	gpiod_set_value(isp->gpio, 1);
+
+	return 0;
+}
+
+static int isp1704_power_off(struct phy *phy)
+{
+	struct isp1704_phy *isp = phy_to_ulpi_drvdata(phy);
+
+	gpiod_set_value(isp->gpio, 0);
+
+	return 0;
+}
+
+static const struct phy_ops isp1704_phy_ops = {
+	.owner = THIS_MODULE,
+	.power_on = isp1704_power_on,
+	.power_off = isp1704_power_off,
+};
+
+/* -------------------------------------------------------------------------- */
+
+static int isp1704_set_peripheral(struct usb_otg *otg, struct usb_gadget *g)
+{
+	otg->gadget = g;
+	if (!g)
+		otg->phy->state = OTG_STATE_UNDEFINED;
+
+	return 0;
+}
+
+static int isp1704_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+	otg->host = host;
+	if (!host)
+		otg->phy->state = OTG_STATE_UNDEFINED;
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static int isp1704_probe(struct ulpi_dev *udev)
+{
+	struct isp1704_phy *isp;
+	struct gpio_desc *gpio;
+	int ret;
+
+	isp = devm_kzalloc(&udev->dev, sizeof(*isp), GFP_KERNEL);
+	if (!isp)
+		return -ENOMEM;
+
+	gpio = devm_gpiod_get(&udev->dev, NULL);
+	if (!IS_ERR(gpio)) {
+		ret = gpiod_direction_output(gpio, 0);
+		if (ret)
+			return ret;
+		isp->gpio = gpio;
+	}
+
+	isp->dev = &udev->dev;
+	sprintf(isp->model, "isp%x", udev->id.product);
+
+	isp->phy.dev	= &udev->dev;
+	isp->phy.otg	= &isp->otg;
+	isp->phy.label	= isp->model;
+	isp->phy.type	= USB_PHY_TYPE_USB2;
+
+	isp->otg.phy		= &isp->phy;
+	isp->otg.set_host	= isp1704_set_host;
+	isp->otg.set_peripheral	= isp1704_set_peripheral;
+
+	isp->psy.name		= isp->model;
+	isp->psy.type		= POWER_SUPPLY_TYPE_USB;
+	isp->psy.properties	= power_props;
+	isp->psy.num_properties	= ARRAY_SIZE(power_props);
+	isp->psy.get_property	= isp1704_get_property;
+
+	ret = power_supply_register(isp->dev, &isp->psy);
+	if (ret)
+		return ret;
+
+	usb_add_phy_dev(&isp->phy);
+
+	ATOMIC_INIT_NOTIFIER_HEAD(&isp->phy.notifier);
+	INIT_WORK(&isp->work, isp1704_work);
+
+	isp->nb.notifier_call = isp1704_notifier_call;
+
+	ret = usb_register_notifier(&isp->phy, &isp->nb);
+	if (ret) {
+		power_supply_unregister(&isp->psy);
+		usb_remove_phy(&isp->phy);
+		return ret;
+	}
+
+	dev_set_drvdata(&udev->dev, isp);
+
+	return 0;
+}
+
+static void isp1704_remove(struct ulpi_dev *udev)
+{
+	struct isp1704_phy *isp = dev_get_drvdata(&udev->dev);
+
+	power_supply_unregister(&isp->psy);
+	usb_remove_phy(&isp->phy);
+}
+
+struct ulpi_device_id isp1704_ulpi_id[] = {
+	{ NXP_VENDOR_ID, 0x1704, },
+	{ NXP_VENDOR_ID, 0x1707, },
+	{ },
+};
+
+static struct ulpi_driver isp1704_driver = {
+	.id_table = isp1704_ulpi_id,
+	.phy_ops = &isp1704_phy_ops,
+	.probe = isp1704_probe,
+	.remove = isp1704_remove,
+	.driver = {
+		.name = "isp170x",
+		.owner = THIS_MODULE,
+	},
+};
+
+module_ulpi_driver(isp1704_driver);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ISP170X ULPI PHY driver");
-- 
1.8.4.4

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux