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