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.kroge...@linux.intel.com>
---
 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.kroge...@linux.intel.com>
+ *
+ * The code is based on drivers/power/isp1704_charger.c
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2012 - 2013 Pali Rohár <pali.ro...@gmail.com>
+ *
+ * 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to