summaryrefslogtreecommitdiffstats
path: root/drivers/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/Kconfig16
-rw-r--r--drivers/phy/Makefile2
-rw-r--r--drivers/phy/phy-brcmstb-sata.c216
-rw-r--r--drivers/phy/phy-core.c67
-rw-r--r--drivers/phy/phy-miphy28lp.c9
-rw-r--r--drivers/phy/phy-miphy365x.c9
-rw-r--r--drivers/phy/phy-rcar-gen2.c6
-rw-r--r--drivers/phy/phy-sun4i-usb.c9
-rw-r--r--drivers/phy/phy-tusb1210.c153
-rw-r--r--drivers/phy/phy-twl4030-usb.c34
-rw-r--r--drivers/phy/ulpi_phy.h31
11 files changed, 504 insertions, 48 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index fc9b9f0ea91e..15cab070f582 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -313,4 +313,20 @@ config PHY_QCOM_UFS
help
Support for UFS PHY on QCOM chipsets.
+config PHY_TUSB1210
+ tristate "TI TUSB1210 ULPI PHY module"
+ depends on USB_ULPI_BUS
+ select GENERIC_PHY
+ help
+ Support for TI TUSB1210 USB ULPI PHY.
+
+config PHY_BRCMSTB_SATA
+ tristate "Broadcom STB SATA PHY driver"
+ depends on ARCH_BRCMSTB
+ depends on OF
+ select GENERIC_PHY
+ help
+ Enable this to support the SATA3 PHY on 28nm Broadcom STB SoCs.
+ Likely useful only with CONFIG_SATA_BRCMSTB enabled.
+
endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index f12625178780..bc92b427e5ca 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -40,3 +40,5 @@ obj-$(CONFIG_PHY_STIH41X_USB) += phy-stih41x-usb.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o
+obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
+obj-$(CONFIG_PHY_BRCMSTB_SATA) += phy-brcmstb-sata.o
diff --git a/drivers/phy/phy-brcmstb-sata.c b/drivers/phy/phy-brcmstb-sata.c
new file mode 100644
index 000000000000..b7e303d28caf
--- /dev/null
+++ b/drivers/phy/phy-brcmstb-sata.c
@@ -0,0 +1,216 @@
+/*
+ * Broadcom SATA3 AHCI Controller PHY Driver
+ *
+ * Copyright © 2009-2015 Broadcom Corporation
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define SATA_MDIO_BANK_OFFSET 0x23c
+#define SATA_MDIO_REG_OFFSET(ofs) ((ofs) * 4)
+#define SATA_MDIO_REG_SPACE_SIZE 0x1000
+#define SATA_MDIO_REG_LENGTH 0x1f00
+
+#define MAX_PORTS 2
+
+/* Register offset between PHYs in PCB space */
+#define SATA_MDIO_REG_SPACE_SIZE 0x1000
+
+struct brcm_sata_port {
+ int portnum;
+ struct phy *phy;
+ struct brcm_sata_phy *phy_priv;
+ bool ssc_en;
+};
+
+struct brcm_sata_phy {
+ struct device *dev;
+ void __iomem *phy_base;
+
+ struct brcm_sata_port phys[MAX_PORTS];
+};
+
+enum sata_mdio_phy_regs_28nm {
+ PLL_REG_BANK_0 = 0x50,
+ PLL_REG_BANK_0_PLLCONTROL_0 = 0x81,
+
+ TXPMD_REG_BANK = 0x1a0,
+ TXPMD_CONTROL1 = 0x81,
+ TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0),
+ TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1),
+ TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82,
+ TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83,
+ TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff,
+ TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84,
+ TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff,
+};
+
+static inline void __iomem *brcm_sata_phy_base(struct brcm_sata_port *port)
+{
+ struct brcm_sata_phy *priv = port->phy_priv;
+
+ return priv->phy_base + (port->portnum * SATA_MDIO_REG_SPACE_SIZE);
+}
+
+static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs,
+ u32 msk, u32 value)
+{
+ u32 tmp;
+
+ writel(bank, addr + SATA_MDIO_BANK_OFFSET);
+ tmp = readl(addr + SATA_MDIO_REG_OFFSET(ofs));
+ tmp = (tmp & msk) | value;
+ writel(tmp, addr + SATA_MDIO_REG_OFFSET(ofs));
+}
+
+/* These defaults were characterized by H/W group */
+#define FMIN_VAL_DEFAULT 0x3df
+#define FMAX_VAL_DEFAULT 0x3df
+#define FMAX_VAL_SSC 0x83
+
+static void brcm_sata_cfg_ssc_28nm(struct brcm_sata_port *port)
+{
+ void __iomem *base = brcm_sata_phy_base(port);
+ struct brcm_sata_phy *priv = port->phy_priv;
+ u32 tmp;
+
+ /* override the TX spread spectrum setting */
+ tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC;
+ brcm_sata_mdio_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp);
+
+ /* set fixed min freq */
+ brcm_sata_mdio_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2,
+ ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK,
+ FMIN_VAL_DEFAULT);
+
+ /* set fixed max freq depending on SSC config */
+ if (port->ssc_en) {
+ dev_info(priv->dev, "enabling SSC on port %d\n", port->portnum);
+ tmp = FMAX_VAL_SSC;
+ } else {
+ tmp = FMAX_VAL_DEFAULT;
+ }
+
+ brcm_sata_mdio_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3,
+ ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp);
+}
+
+static int brcm_sata_phy_init(struct phy *phy)
+{
+ struct brcm_sata_port *port = phy_get_drvdata(phy);
+
+ brcm_sata_cfg_ssc_28nm(port);
+
+ return 0;
+}
+
+static struct phy_ops phy_ops_28nm = {
+ .init = brcm_sata_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id brcm_sata_phy_of_match[] = {
+ { .compatible = "brcm,bcm7445-sata-phy" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match);
+
+static int brcm_sata_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *dn = dev->of_node, *child;
+ struct brcm_sata_phy *priv;
+ struct resource *res;
+ struct phy_provider *provider;
+ int count = 0;
+
+ if (of_get_child_count(dn) == 0)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy");
+ priv->phy_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->phy_base))
+ return PTR_ERR(priv->phy_base);
+
+ for_each_available_child_of_node(dn, child) {
+ unsigned int id;
+ struct brcm_sata_port *port;
+
+ if (of_property_read_u32(child, "reg", &id)) {
+ dev_err(dev, "missing reg property in node %s\n",
+ child->name);
+ return -EINVAL;
+ }
+
+ if (id >= MAX_PORTS) {
+ dev_err(dev, "invalid reg: %u\n", id);
+ return -EINVAL;
+ }
+ if (priv->phys[id].phy) {
+ dev_err(dev, "already registered port %u\n", id);
+ return -EINVAL;
+ }
+
+ port = &priv->phys[id];
+ port->portnum = id;
+ port->phy_priv = priv;
+ port->phy = devm_phy_create(dev, child, &phy_ops_28nm);
+ port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc");
+ if (IS_ERR(port->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(port->phy);
+ }
+
+ phy_set_drvdata(port->phy, port);
+ count++;
+ }
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "could not register PHY provider\n");
+ return PTR_ERR(provider);
+ }
+
+ dev_info(dev, "registered %d port(s)\n", count);
+
+ return 0;
+}
+
+static struct platform_driver brcm_sata_phy_driver = {
+ .probe = brcm_sata_phy_probe,
+ .driver = {
+ .of_match_table = brcm_sata_phy_of_match,
+ .name = "brcmstb-sata-phy",
+ }
+};
+module_platform_driver(brcm_sata_phy_driver);
+
+MODULE_DESCRIPTION("Broadcom STB SATA PHY driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marc Carino");
+MODULE_AUTHOR("Brian Norris");
+MODULE_ALIAS("platform:phy-brcmstb-sata");
diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c
index 63bc12d7a73e..fc48fac003a6 100644
--- a/drivers/phy/phy-core.c
+++ b/drivers/phy/phy-core.c
@@ -367,13 +367,21 @@ static struct phy *_of_phy_get(struct device_node *np, int index)
phy_provider = of_phy_provider_lookup(args.np);
if (IS_ERR(phy_provider) || !try_module_get(phy_provider->owner)) {
phy = ERR_PTR(-EPROBE_DEFER);
- goto err0;
+ goto out_unlock;
+ }
+
+ if (!of_device_is_available(args.np)) {
+ dev_warn(phy_provider->dev, "Requested PHY is disabled\n");
+ phy = ERR_PTR(-ENODEV);
+ goto out_put_module;
}
phy = phy_provider->of_xlate(phy_provider->dev, &args);
+
+out_put_module:
module_put(phy_provider->owner);
-err0:
+out_unlock:
mutex_unlock(&phy_provider_mutex);
of_node_put(args.np);
@@ -623,6 +631,38 @@ struct phy *devm_of_phy_get(struct device *dev, struct device_node *np,
EXPORT_SYMBOL_GPL(devm_of_phy_get);
/**
+ * devm_of_phy_get_by_index() - lookup and obtain a reference to a phy by index.
+ * @dev: device that requests this phy
+ * @np: node containing the phy
+ * @index: index of the phy
+ *
+ * Gets the phy using _of_phy_get(), and associates a device with it using
+ * devres. On driver detach, release function is invoked on the devres data,
+ * then, devres data is freed.
+ *
+ */
+struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np,
+ int index)
+{
+ struct phy **ptr, *phy;
+
+ ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ phy = _of_phy_get(np, index);
+ if (!IS_ERR(phy)) {
+ *ptr = phy;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_of_phy_get_by_index);
+
+/**
* phy_create() - create a new phy
* @dev: device that is creating the new phy
* @node: device node of the phy
@@ -651,16 +691,6 @@ struct phy *phy_create(struct device *dev, struct device_node *node,
goto free_phy;
}
- /* phy-supply */
- phy->pwr = regulator_get_optional(dev, "phy");
- if (IS_ERR(phy->pwr)) {
- if (PTR_ERR(phy->pwr) == -EPROBE_DEFER) {
- ret = -EPROBE_DEFER;
- goto free_ida;
- }
- phy->pwr = NULL;
- }
-
device_initialize(&phy->dev);
mutex_init(&phy->mutex);
@@ -674,6 +704,16 @@ struct phy *phy_create(struct device *dev, struct device_node *node,
if (ret)
goto put_dev;
+ /* phy-supply */
+ phy->pwr = regulator_get_optional(&phy->dev, "phy");
+ if (IS_ERR(phy->pwr)) {
+ ret = PTR_ERR(phy->pwr);
+ if (ret == -EPROBE_DEFER)
+ goto put_dev;
+
+ phy->pwr = NULL;
+ }
+
ret = device_add(&phy->dev);
if (ret)
goto put_dev;
@@ -689,9 +729,6 @@ put_dev:
put_device(&phy->dev); /* calls phy_release() which frees resources */
return ERR_PTR(ret);
-free_ida:
- ida_simple_remove(&phy_ida, phy->id);
-
free_phy:
kfree(phy);
return ERR_PTR(ret);
diff --git a/drivers/phy/phy-miphy28lp.c b/drivers/phy/phy-miphy28lp.c
index c4cc11dcb2a2..5e257ef7ac05 100644
--- a/drivers/phy/phy-miphy28lp.c
+++ b/drivers/phy/phy-miphy28lp.c
@@ -367,7 +367,7 @@ static struct miphy28lp_pll_gen pcie_pll_gen[] = {
static inline void miphy28lp_set_reset(struct miphy28lp_phy *miphy_phy)
{
- void *base = miphy_phy->base;
+ void __iomem *base = miphy_phy->base;
u8 val;
/* Putting Macro in reset */
@@ -391,7 +391,7 @@ static inline void miphy28lp_set_reset(struct miphy28lp_phy *miphy_phy)
static inline void miphy28lp_pll_calibration(struct miphy28lp_phy *miphy_phy,
struct pll_ratio *pll_ratio)
{
- void *base = miphy_phy->base;
+ void __iomem *base = miphy_phy->base;
u8 val;
/* Applying PLL Settings */
@@ -1107,11 +1107,6 @@ static struct phy *miphy28lp_xlate(struct device *dev,
struct device_node *phynode = args->np;
int ret, index = 0;
- if (!of_device_is_available(phynode)) {
- dev_warn(dev, "Requested PHY is disabled\n");
- return ERR_PTR(-ENODEV);
- }
-
if (args->args_count != 1) {
dev_err(dev, "Invalid number of cells in 'phy' property\n");
return ERR_PTR(-EINVAL);
diff --git a/drivers/phy/phy-miphy365x.c b/drivers/phy/phy-miphy365x.c
index 019c2d75344e..0ff354d6e183 100644
--- a/drivers/phy/phy-miphy365x.c
+++ b/drivers/phy/phy-miphy365x.c
@@ -441,8 +441,8 @@ static int miphy365x_init(struct phy *phy)
return ret;
}
-int miphy365x_get_addr(struct device *dev, struct miphy365x_phy *miphy_phy,
- int index)
+static int miphy365x_get_addr(struct device *dev,
+ struct miphy365x_phy *miphy_phy, int index)
{
struct device_node *phynode = miphy_phy->phy->dev.of_node;
const char *name;
@@ -476,11 +476,6 @@ static struct phy *miphy365x_xlate(struct device *dev,
struct device_node *phynode = args->np;
int ret, index;
- if (!of_device_is_available(phynode)) {
- dev_warn(dev, "Requested PHY is disabled\n");
- return ERR_PTR(-ENODEV);
- }
-
if (args->args_count != 1) {
dev_err(dev, "Invalid number of cells in 'phy' property\n");
return ERR_PTR(-EINVAL);
diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c
index 97d45f47d1ad..39d9b2995435 100644
--- a/drivers/phy/phy-rcar-gen2.c
+++ b/drivers/phy/phy-rcar-gen2.c
@@ -195,6 +195,7 @@ static struct phy_ops rcar_gen2_phy_ops = {
static const struct of_device_id rcar_gen2_phy_match_table[] = {
{ .compatible = "renesas,usb-phy-r8a7790" },
{ .compatible = "renesas,usb-phy-r8a7791" },
+ { .compatible = "renesas,usb-phy-r8a7794" },
{ }
};
MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table);
@@ -206,11 +207,6 @@ static struct phy *rcar_gen2_phy_xlate(struct device *dev,
struct device_node *np = args->np;
int i;
- if (!of_device_is_available(np)) {
- dev_warn(dev, "Requested PHY is disabled\n");
- return ERR_PTR(-ENODEV);
- }
-
drv = dev_get_drvdata(dev);
if (!drv)
return ERR_PTR(-EINVAL);
diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c
index a2b08f3ccb03..e17c539e4f6f 100644
--- a/drivers/phy/phy-sun4i-usb.c
+++ b/drivers/phy/phy-sun4i-usb.c
@@ -30,6 +30,7 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/phy/phy.h>
+#include <linux/phy/phy-sun4i-usb.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
@@ -58,6 +59,7 @@
#define PHY_OTG_FUNC_EN 0x28
#define PHY_VBUS_DET_EN 0x29
#define PHY_DISCON_TH_SEL 0x2a
+#define PHY_SQUELCH_DETECT 0x3c
#define MAX_PHYS 3
@@ -204,6 +206,13 @@ static int sun4i_usb_phy_power_off(struct phy *_phy)
return 0;
}
+void sun4i_usb_phy_set_squelch_detect(struct phy *_phy, bool enabled)
+{
+ struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
+
+ sun4i_usb_phy_write(phy, PHY_SQUELCH_DETECT, enabled ? 0 : 2, 2);
+}
+
static struct phy_ops sun4i_usb_phy_ops = {
.init = sun4i_usb_phy_init,
.exit = sun4i_usb_phy_exit,
diff --git a/drivers/phy/phy-tusb1210.c b/drivers/phy/phy-tusb1210.c
new file mode 100644
index 000000000000..07efdd318bdc
--- /dev/null
+++ b/drivers/phy/phy-tusb1210.c
@@ -0,0 +1,153 @@
+/**
+ * tusb1210.c - TUSB1210 USB ULPI PHY driver
+ *
+ * Copyright (C) 2015 Intel Corporation
+ *
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/module.h>
+#include <linux/ulpi/driver.h>
+#include <linux/gpio/consumer.h>
+
+#include "ulpi_phy.h"
+
+#define TUSB1210_VENDOR_SPECIFIC2 0x80
+#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT 0
+#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT 4
+#define TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT 6
+
+struct tusb1210 {
+ struct ulpi *ulpi;
+ struct phy *phy;
+ struct gpio_desc *gpio_reset;
+ struct gpio_desc *gpio_cs;
+ u8 vendor_specific2;
+};
+
+static int tusb1210_power_on(struct phy *phy)
+{
+ struct tusb1210 *tusb = phy_get_drvdata(phy);
+
+ gpiod_set_value_cansleep(tusb->gpio_reset, 1);
+ gpiod_set_value_cansleep(tusb->gpio_cs, 1);
+
+ /* Restore the optional eye diagram optimization value */
+ if (tusb->vendor_specific2)
+ ulpi_write(tusb->ulpi, TUSB1210_VENDOR_SPECIFIC2,
+ tusb->vendor_specific2);
+
+ return 0;
+}
+
+static int tusb1210_power_off(struct phy *phy)
+{
+ struct tusb1210 *tusb = phy_get_drvdata(phy);
+
+ gpiod_set_value_cansleep(tusb->gpio_reset, 0);
+ gpiod_set_value_cansleep(tusb->gpio_cs, 0);
+
+ return 0;
+}
+
+static struct phy_ops phy_ops = {
+ .power_on = tusb1210_power_on,
+ .power_off = tusb1210_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int tusb1210_probe(struct ulpi *ulpi)
+{
+ struct gpio_desc *gpio;
+ struct tusb1210 *tusb;
+ u8 val, reg;
+ int ret;
+
+ tusb = devm_kzalloc(&ulpi->dev, sizeof(*tusb), GFP_KERNEL);
+ if (!tusb)
+ return -ENOMEM;
+
+ gpio = devm_gpiod_get(&ulpi->dev, "reset");
+ if (!IS_ERR(gpio)) {
+ ret = gpiod_direction_output(gpio, 0);
+ if (ret)
+ return ret;
+ gpiod_set_value_cansleep(gpio, 1);
+ tusb->gpio_reset = gpio;
+ }
+
+ gpio = devm_gpiod_get(&ulpi->dev, "cs");
+ if (!IS_ERR(gpio)) {
+ ret = gpiod_direction_output(gpio, 0);
+ if (ret)
+ return ret;
+ gpiod_set_value_cansleep(gpio, 1);
+ tusb->gpio_cs = gpio;
+ }
+
+ /*
+ * VENDOR_SPECIFIC2 register in TUSB1210 can be used for configuring eye
+ * diagram optimization and DP/DM swap.
+ */
+
+ /* High speed output drive strength configuration */
+ device_property_read_u8(&ulpi->dev, "ihstx", &val);
+ reg = val << TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT;
+
+ /* High speed output impedance configuration */
+ device_property_read_u8(&ulpi->dev, "zhsdrv", &val);
+ reg |= val << TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT;
+
+ /* DP/DM swap control */
+ device_property_read_u8(&ulpi->dev, "datapolarity", &val);
+ reg |= val << TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT;
+
+ if (reg) {
+ ulpi_write(ulpi, TUSB1210_VENDOR_SPECIFIC2, reg);
+ tusb->vendor_specific2 = reg;
+ }
+
+ tusb->phy = ulpi_phy_create(ulpi, &phy_ops);
+ if (IS_ERR(tusb->phy))
+ return PTR_ERR(tusb->phy);
+
+ tusb->ulpi = ulpi;
+
+ phy_set_drvdata(tusb->phy, tusb);
+ ulpi_set_drvdata(ulpi, tusb);
+ return 0;
+}
+
+static void tusb1210_remove(struct ulpi *ulpi)
+{
+ struct tusb1210 *tusb = ulpi_get_drvdata(ulpi);
+
+ ulpi_phy_destroy(ulpi, tusb->phy);
+}
+
+#define TI_VENDOR_ID 0x0451
+
+static const struct ulpi_device_id tusb1210_ulpi_id[] = {
+ { TI_VENDOR_ID, 0x1507, },
+ { },
+};
+MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id);
+
+static struct ulpi_driver tusb1210_driver = {
+ .id_table = tusb1210_ulpi_id,
+ .probe = tusb1210_probe,
+ .remove = tusb1210_remove,
+ .driver = {
+ .name = "tusb1210",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_ulpi_driver(tusb1210_driver);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TUSB1210 ULPI PHY driver");
diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c
index bc42d6a8939f..3a707dd14238 100644
--- a/drivers/phy/phy-twl4030-usb.c
+++ b/drivers/phy/phy-twl4030-usb.c
@@ -144,6 +144,16 @@
#define PMBR1 0x0D
#define GPIO_USB_4PIN_ULPI_2430C (3 << 0)
+/*
+ * If VBUS is valid or ID is ground, then we know a
+ * cable is present and we need to be runtime-enabled
+ */
+static inline bool cable_present(enum omap_musb_vbus_id_status stat)
+{
+ return stat == OMAP_MUSB_VBUS_VALID ||
+ stat == OMAP_MUSB_ID_GROUND;
+}
+
struct twl4030_usb {
struct usb_phy phy;
struct device *dev;
@@ -386,8 +396,6 @@ static int twl4030_usb_runtime_suspend(struct device *dev)
struct twl4030_usb *twl = dev_get_drvdata(dev);
dev_dbg(twl->dev, "%s\n", __func__);
- if (pm_runtime_suspended(dev))
- return 0;
__twl4030_phy_power(twl, 0);
regulator_disable(twl->usb1v5);
@@ -403,8 +411,6 @@ static int twl4030_usb_runtime_resume(struct device *dev)
int res;
dev_dbg(twl->dev, "%s\n", __func__);
- if (pm_runtime_active(dev))
- return 0;
res = regulator_enable(twl->usb3v1);
if (res)
@@ -536,8 +542,10 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
mutex_lock(&twl->lock);
if (status >= 0 && status != twl->linkstat) {
+ status_changed =
+ cable_present(twl->linkstat) !=
+ cable_present(status);
twl->linkstat = status;
- status_changed = true;
}
mutex_unlock(&twl->lock);
@@ -553,15 +561,11 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
* USB_LINK_VBUS state. musb_hdrc won't care until it
* starts to handle softconnect right.
*/
- if ((status == OMAP_MUSB_VBUS_VALID) ||
- (status == OMAP_MUSB_ID_GROUND)) {
- if (pm_runtime_suspended(twl->dev))
- pm_runtime_get_sync(twl->dev);
+ if (cable_present(status)) {
+ pm_runtime_get_sync(twl->dev);
} else {
- if (pm_runtime_active(twl->dev)) {
- pm_runtime_mark_last_busy(twl->dev);
- pm_runtime_put_autosuspend(twl->dev);
- }
+ pm_runtime_mark_last_busy(twl->dev);
+ pm_runtime_put_autosuspend(twl->dev);
}
omap_musb_mailbox(status);
}
@@ -711,7 +715,6 @@ static int twl4030_usb_probe(struct platform_device *pdev)
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
pm_runtime_enable(&pdev->dev);
- pm_runtime_get_sync(&pdev->dev);
/* Our job is to use irqs and status from the power module
* to keep the transceiver disabled when nothing's connected.
@@ -767,6 +770,9 @@ static int twl4030_usb_remove(struct platform_device *pdev)
/* disable complete OTG block */
twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
+
+ if (cable_present(twl->linkstat))
+ pm_runtime_put_noidle(twl->dev);
pm_runtime_mark_last_busy(twl->dev);
pm_runtime_put(twl->dev);
diff --git a/drivers/phy/ulpi_phy.h b/drivers/phy/ulpi_phy.h
new file mode 100644
index 000000000000..ac49fb6285ee
--- /dev/null
+++ b/drivers/phy/ulpi_phy.h
@@ -0,0 +1,31 @@
+#include <linux/phy/phy.h>
+
+/**
+ * Helper that registers PHY for a ULPI device and adds a lookup for binding it
+ * and it's controller, which is always the parent.
+ */
+static inline struct phy
+*ulpi_phy_create(struct ulpi *ulpi, struct phy_ops *ops)
+{
+ struct phy *phy;
+ int ret;
+
+ phy = phy_create(&ulpi->dev, NULL, ops);
+ if (IS_ERR(phy))
+ return phy;
+
+ ret = phy_create_lookup(phy, "usb2-phy", dev_name(ulpi->dev.parent));
+ if (ret) {
+ phy_destroy(phy);
+ return ERR_PTR(ret);
+ }
+
+ return phy;
+}
+
+/* Remove a PHY that was created with ulpi_phy_create() and it's lookup. */
+static inline void ulpi_phy_destroy(struct ulpi *ulpi, struct phy *phy)
+{
+ phy_remove_lookup(phy, "usb2-phy", dev_name(ulpi->dev.parent));
+ phy_destroy(phy);
+}