diff options
Diffstat (limited to 'drivers/phy/ti')
-rw-r--r-- | drivers/phy/ti/Kconfig | 78 | ||||
-rw-r--r-- | drivers/phy/ti/Makefile | 7 | ||||
-rw-r--r-- | drivers/phy/ti/phy-da8xx-usb.c | 251 | ||||
-rw-r--r-- | drivers/phy/ti/phy-dm816x-usb.c | 290 | ||||
-rw-r--r-- | drivers/phy/ti/phy-omap-control.c | 360 | ||||
-rw-r--r-- | drivers/phy/ti/phy-omap-usb2.c | 439 | ||||
-rw-r--r-- | drivers/phy/ti/phy-ti-pipe3.c | 697 | ||||
-rw-r--r-- | drivers/phy/ti/phy-tusb1210.c | 182 | ||||
-rw-r--r-- | drivers/phy/ti/phy-twl4030-usb.c | 839 |
9 files changed, 3143 insertions, 0 deletions
diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig new file mode 100644 index 000000000000..20503562666c --- /dev/null +++ b/drivers/phy/ti/Kconfig @@ -0,0 +1,78 @@ +# +# Phy drivers for TI platforms +# +config PHY_DA8XX_USB + tristate "TI DA8xx USB PHY Driver" + depends on ARCH_DAVINCI_DA8XX + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the USB PHY on DA8xx SoCs. + + This driver controls both the USB 1.1 PHY and the USB 2.0 PHY. + +config PHY_DM816X_USB + tristate "TI dm816x USB PHY driver" + depends on ARCH_OMAP2PLUS + depends on USB_SUPPORT + select GENERIC_PHY + select USB_PHY + help + Enable this for dm816x USB to work. + +config OMAP_CONTROL_PHY + tristate "OMAP CONTROL PHY Driver" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + help + Enable this to add support for the PHY part present in the control + module. This driver has API to power on the USB2 PHY and to write to + the mailbox. The mailbox is present only in omap4 and the register to + power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an + additional register to power on USB3 PHY/SATA PHY/PCIE PHY + (PIPE3 PHY). + +config OMAP_USB2 + tristate "OMAP USB2 PHY Driver" + depends on ARCH_OMAP2PLUS + depends on USB_SUPPORT + select GENERIC_PHY + select USB_PHY + select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP + help + Enable this to support the transceiver that is part of SOC. This + driver takes care of all the PHY functionality apart from comparator. + The USB OTG controller communicates with the comparator using this + driver. + +config TI_PIPE3 + tristate "TI PIPE3 PHY Driver" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + select GENERIC_PHY + select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP + help + Enable this to support the PIPE3 PHY that is part of TI SOCs. This + driver takes care of all the PHY functionality apart from comparator. + This driver interacts with the "OMAP Control PHY Driver" to power + on/off the PHY. + +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 TWL4030_USB + tristate "TWL4030 USB Transceiver Driver" + depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS + depends on USB_SUPPORT + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't 'y' + select GENERIC_PHY + select USB_PHY + help + Enable this to support the USB OTG transceiver on TWL4030 + family chips (including the TWL5030 and TPS659x0 devices). + This transceiver supports high and full speed devices plus, + in host mode, low speed. diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile new file mode 100644 index 000000000000..0cc3a1a557a3 --- /dev/null +++ b/drivers/phy/ti/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o +obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o +obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o +obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o +obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o +obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o +obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o diff --git a/drivers/phy/ti/phy-da8xx-usb.c b/drivers/phy/ti/phy-da8xx-usb.c new file mode 100644 index 000000000000..1b82bff6330f --- /dev/null +++ b/drivers/phy/ti/phy-da8xx-usb.c @@ -0,0 +1,251 @@ +/* + * phy-da8xx-usb - TI DaVinci DA8xx USB PHY driver + * + * Copyright (C) 2016 David Lechner <david@lechnology.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; version 2 of the License. + * + * 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/clk.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/mfd/da8xx-cfgchip.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define PHY_INIT_BITS (CFGCHIP2_SESENDEN | CFGCHIP2_VBDTCTEN) + +struct da8xx_usb_phy { + struct phy_provider *phy_provider; + struct phy *usb11_phy; + struct phy *usb20_phy; + struct clk *usb11_clk; + struct clk *usb20_clk; + struct regmap *regmap; +}; + +static int da8xx_usb11_phy_power_on(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(d_phy->usb11_clk); + if (ret) + return ret; + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM, + CFGCHIP2_USB1SUSPENDM); + + return 0; +} + +static int da8xx_usb11_phy_power_off(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM, 0); + + clk_disable_unprepare(d_phy->usb11_clk); + + return 0; +} + +static const struct phy_ops da8xx_usb11_phy_ops = { + .power_on = da8xx_usb11_phy_power_on, + .power_off = da8xx_usb11_phy_power_off, + .owner = THIS_MODULE, +}; + +static int da8xx_usb20_phy_power_on(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(d_phy->usb20_clk); + if (ret) + return ret; + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN, 0); + + return 0; +} + +static int da8xx_usb20_phy_power_off(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN, + CFGCHIP2_OTGPWRDN); + + clk_disable_unprepare(d_phy->usb20_clk); + + return 0; +} + +static int da8xx_usb20_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + u32 val; + + switch (mode) { + case PHY_MODE_USB_HOST: /* Force VBUS valid, ID = 0 */ + val = CFGCHIP2_OTGMODE_FORCE_HOST; + break; + case PHY_MODE_USB_DEVICE: /* Force VBUS valid, ID = 1 */ + val = CFGCHIP2_OTGMODE_FORCE_DEVICE; + break; + case PHY_MODE_USB_OTG: /* Don't override the VBUS/ID comparators */ + val = CFGCHIP2_OTGMODE_NO_OVERRIDE; + break; + default: + return -EINVAL; + } + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGMODE_MASK, + val); + + return 0; +} + +static const struct phy_ops da8xx_usb20_phy_ops = { + .power_on = da8xx_usb20_phy_power_on, + .power_off = da8xx_usb20_phy_power_off, + .set_mode = da8xx_usb20_phy_set_mode, + .owner = THIS_MODULE, +}; + +static struct phy *da8xx_usb_phy_of_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct da8xx_usb_phy *d_phy = dev_get_drvdata(dev); + + if (!d_phy) + return ERR_PTR(-ENODEV); + + switch (args->args[0]) { + case 0: + return d_phy->usb20_phy; + case 1: + return d_phy->usb11_phy; + default: + return ERR_PTR(-EINVAL); + } +} + +static int da8xx_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct da8xx_usb_phy *d_phy; + + d_phy = devm_kzalloc(dev, sizeof(*d_phy), GFP_KERNEL); + if (!d_phy) + return -ENOMEM; + + if (node) + d_phy->regmap = syscon_regmap_lookup_by_compatible( + "ti,da830-cfgchip"); + else + d_phy->regmap = syscon_regmap_lookup_by_pdevname("syscon"); + if (IS_ERR(d_phy->regmap)) { + dev_err(dev, "Failed to get syscon\n"); + return PTR_ERR(d_phy->regmap); + } + + d_phy->usb11_clk = devm_clk_get(dev, "usb11_phy"); + if (IS_ERR(d_phy->usb11_clk)) { + dev_err(dev, "Failed to get usb11_phy clock\n"); + return PTR_ERR(d_phy->usb11_clk); + } + + d_phy->usb20_clk = devm_clk_get(dev, "usb20_phy"); + if (IS_ERR(d_phy->usb20_clk)) { + dev_err(dev, "Failed to get usb20_phy clock\n"); + return PTR_ERR(d_phy->usb20_clk); + } + + d_phy->usb11_phy = devm_phy_create(dev, node, &da8xx_usb11_phy_ops); + if (IS_ERR(d_phy->usb11_phy)) { + dev_err(dev, "Failed to create usb11 phy\n"); + return PTR_ERR(d_phy->usb11_phy); + } + + d_phy->usb20_phy = devm_phy_create(dev, node, &da8xx_usb20_phy_ops); + if (IS_ERR(d_phy->usb20_phy)) { + dev_err(dev, "Failed to create usb20 phy\n"); + return PTR_ERR(d_phy->usb20_phy); + } + + platform_set_drvdata(pdev, d_phy); + phy_set_drvdata(d_phy->usb11_phy, d_phy); + phy_set_drvdata(d_phy->usb20_phy, d_phy); + + if (node) { + d_phy->phy_provider = devm_of_phy_provider_register(dev, + da8xx_usb_phy_of_xlate); + if (IS_ERR(d_phy->phy_provider)) { + dev_err(dev, "Failed to create phy provider\n"); + return PTR_ERR(d_phy->phy_provider); + } + } else { + int ret; + + ret = phy_create_lookup(d_phy->usb11_phy, "usb-phy", + "ohci-da8xx"); + if (ret) + dev_warn(dev, "Failed to create usb11 phy lookup\n"); + ret = phy_create_lookup(d_phy->usb20_phy, "usb-phy", + "musb-da8xx"); + if (ret) + dev_warn(dev, "Failed to create usb20 phy lookup\n"); + } + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), + PHY_INIT_BITS, PHY_INIT_BITS); + + return 0; +} + +static int da8xx_usb_phy_remove(struct platform_device *pdev) +{ + struct da8xx_usb_phy *d_phy = platform_get_drvdata(pdev); + + if (!pdev->dev.of_node) { + phy_remove_lookup(d_phy->usb20_phy, "usb-phy", "musb-da8xx"); + phy_remove_lookup(d_phy->usb11_phy, "usb-phy", "ohci-da8xx"); + } + + return 0; +} + +static const struct of_device_id da8xx_usb_phy_ids[] = { + { .compatible = "ti,da830-usb-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, da8xx_usb_phy_ids); + +static struct platform_driver da8xx_usb_phy_driver = { + .probe = da8xx_usb_phy_probe, + .remove = da8xx_usb_phy_remove, + .driver = { + .name = "da8xx-usb-phy", + .of_match_table = da8xx_usb_phy_ids, + }, +}; + +module_platform_driver(da8xx_usb_phy_driver); + +MODULE_ALIAS("platform:da8xx-usb-phy"); +MODULE_AUTHOR("David Lechner <david@lechnology.com>"); +MODULE_DESCRIPTION("TI DA8xx USB PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-dm816x-usb.c b/drivers/phy/ti/phy-dm816x-usb.c new file mode 100644 index 000000000000..cbcce7cf0028 --- /dev/null +++ b/drivers/phy/ti/phy-dm816x-usb.c @@ -0,0 +1,290 @@ +/* + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/usb/phy_companion.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/delay.h> +#include <linux/phy/phy.h> +#include <linux/of_platform.h> + +#include <linux/mfd/syscon.h> + +/* + * TRM has two sets of USB_CTRL registers.. The correct register bits + * are in TRM section 24.9.8.2 USB_CTRL Register. The TRM documents the + * phy as being SR70LX Synopsys USB 2.0 OTG nanoPHY. It also seems at + * least dm816x rev c ignores writes to USB_CTRL register, but the TI + * kernel is writing to those so it's possible that later revisions + * have worknig USB_CTRL register. + * + * Also note that At least USB_CTRL register seems to be dm816x specific + * according to the TRM. It's possible that USBPHY_CTRL is more generic, + * but that would have to be checked against the SR70LX documentation + * which does not seem to be publicly available. + * + * Finally, the phy on dm814x and am335x is different from dm816x. + */ +#define DM816X_USB_CTRL_PHYCLKSRC BIT(8) /* 1 = PLL ref clock */ +#define DM816X_USB_CTRL_PHYSLEEP1 BIT(1) /* Enable the first phy */ +#define DM816X_USB_CTRL_PHYSLEEP0 BIT(0) /* Enable the second phy */ + +#define DM816X_USBPHY_CTRL_TXRISETUNE 1 +#define DM816X_USBPHY_CTRL_TXVREFTUNE 0xc +#define DM816X_USBPHY_CTRL_TXPREEMTUNE 0x2 + +struct dm816x_usb_phy { + struct regmap *syscon; + struct device *dev; + unsigned int instance; + struct clk *refclk; + struct usb_phy phy; + unsigned int usb_ctrl; /* Shared between phy0 and phy1 */ + unsigned int usbphy_ctrl; +}; + +static int dm816x_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int dm816x_usb_phy_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int dm816x_usb_phy_init(struct phy *x) +{ + struct dm816x_usb_phy *phy = phy_get_drvdata(x); + unsigned int val; + int error; + + if (clk_get_rate(phy->refclk) != 24000000) + dev_warn(phy->dev, "nonstandard phy refclk\n"); + + /* Set PLL ref clock and put phys to sleep */ + error = regmap_update_bits(phy->syscon, phy->usb_ctrl, + DM816X_USB_CTRL_PHYCLKSRC | + DM816X_USB_CTRL_PHYSLEEP1 | + DM816X_USB_CTRL_PHYSLEEP0, + 0); + regmap_read(phy->syscon, phy->usb_ctrl, &val); + if ((val & 3) != 0) + dev_info(phy->dev, + "Working dm816x USB_CTRL! (0x%08x)\n", + val); + + /* + * TI kernel sets these values for "symmetrical eye diagram and + * better signal quality" so let's assume somebody checked the + * values with a scope and set them here too. + */ + regmap_read(phy->syscon, phy->usbphy_ctrl, &val); + val |= DM816X_USBPHY_CTRL_TXRISETUNE | + DM816X_USBPHY_CTRL_TXVREFTUNE | + DM816X_USBPHY_CTRL_TXPREEMTUNE; + regmap_write(phy->syscon, phy->usbphy_ctrl, val); + + return 0; +} + +static const struct phy_ops ops = { + .init = dm816x_usb_phy_init, + .owner = THIS_MODULE, +}; + +static int __maybe_unused dm816x_usb_phy_runtime_suspend(struct device *dev) +{ + struct dm816x_usb_phy *phy = dev_get_drvdata(dev); + unsigned int mask, val; + int error = 0; + + mask = BIT(phy->instance); + val = ~BIT(phy->instance); + error = regmap_update_bits(phy->syscon, phy->usb_ctrl, + mask, val); + if (error) + dev_err(phy->dev, "phy%i failed to power off\n", + phy->instance); + clk_disable(phy->refclk); + + return 0; +} + +static int __maybe_unused dm816x_usb_phy_runtime_resume(struct device *dev) +{ + struct dm816x_usb_phy *phy = dev_get_drvdata(dev); + unsigned int mask, val; + int error; + + error = clk_enable(phy->refclk); + if (error) + return error; + + /* + * Note that at least dm816x rev c does not seem to do + * anything with the USB_CTRL register. But let's follow + * what the TI tree is doing in case later revisions use + * USB_CTRL. + */ + mask = BIT(phy->instance); + val = BIT(phy->instance); + error = regmap_update_bits(phy->syscon, phy->usb_ctrl, + mask, val); + if (error) { + dev_err(phy->dev, "phy%i failed to power on\n", + phy->instance); + clk_disable(phy->refclk); + return error; + } + + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(dm816x_usb_phy_pm_ops, + dm816x_usb_phy_runtime_suspend, + dm816x_usb_phy_runtime_resume, + NULL); + +#ifdef CONFIG_OF +static const struct of_device_id dm816x_usb_phy_id_table[] = { + { + .compatible = "ti,dm8168-usb-phy", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, dm816x_usb_phy_id_table); +#endif + +static int dm816x_usb_phy_probe(struct platform_device *pdev) +{ + struct dm816x_usb_phy *phy; + struct resource *res; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct usb_otg *otg; + const struct of_device_id *of_id; + const struct usb_phy_data *phy_data; + int error; + + of_id = of_match_device(of_match_ptr(dm816x_usb_phy_id_table), + &pdev->dev); + if (!of_id) + return -EINVAL; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + phy->syscon = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "syscon"); + if (IS_ERR(phy->syscon)) + return PTR_ERR(phy->syscon); + + /* + * According to sprs614e.pdf, the first usb_ctrl is shared and + * the second instance for usb_ctrl is reserved.. Also the + * register bits are different from earlier TRMs. + */ + phy->usb_ctrl = 0x20; + phy->usbphy_ctrl = (res->start & 0xff) + 4; + if (phy->usbphy_ctrl == 0x2c) + phy->instance = 1; + + phy_data = of_id->data; + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + phy->dev = &pdev->dev; + phy->phy.dev = phy->dev; + phy->phy.label = "dm8168_usb_phy"; + phy->phy.otg = otg; + phy->phy.type = USB_PHY_TYPE_USB2; + otg->set_host = dm816x_usb_phy_set_host; + otg->set_peripheral = dm816x_usb_phy_set_peripheral; + otg->usb_phy = &phy->phy; + + platform_set_drvdata(pdev, phy); + + phy->refclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->refclk)) + return PTR_ERR(phy->refclk); + error = clk_prepare(phy->refclk); + if (error) + return error; + + pm_runtime_enable(phy->dev); + generic_phy = devm_phy_create(phy->dev, NULL, &ops); + if (IS_ERR(generic_phy)) + return PTR_ERR(generic_phy); + + phy_set_drvdata(generic_phy, phy); + + phy_provider = devm_of_phy_provider_register(phy->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + usb_add_phy_dev(&phy->phy); + + return 0; +} + +static int dm816x_usb_phy_remove(struct platform_device *pdev) +{ + struct dm816x_usb_phy *phy = platform_get_drvdata(pdev); + + usb_remove_phy(&phy->phy); + pm_runtime_disable(phy->dev); + clk_unprepare(phy->refclk); + + return 0; +} + +static struct platform_driver dm816x_usb_phy_driver = { + .probe = dm816x_usb_phy_probe, + .remove = dm816x_usb_phy_remove, + .driver = { + .name = "dm816x-usb-phy", + .pm = &dm816x_usb_phy_pm_ops, + .of_match_table = of_match_ptr(dm816x_usb_phy_id_table), + }, +}; + +module_platform_driver(dm816x_usb_phy_driver); + +MODULE_ALIAS("platform:dm816x_usb"); +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); +MODULE_DESCRIPTION("dm816x usb phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-omap-control.c b/drivers/phy/ti/phy-omap-control.c new file mode 100644 index 000000000000..e9c41b3fa0ee --- /dev/null +++ b/drivers/phy/ti/phy-omap-control.c @@ -0,0 +1,360 @@ +/* + * omap-control-phy.c - The PHY part of control module. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.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. + * + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * 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/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/phy/omap_control_phy.h> + +/** + * omap_control_pcie_pcs - set the PCS delay count + * @dev: the control module device + * @delay: 8 bit delay value + */ +void omap_control_pcie_pcs(struct device *dev, u8 delay) +{ + u32 val; + struct omap_control_phy *control_phy; + + if (IS_ERR(dev) || !dev) { + pr_err("%s: invalid device\n", __func__); + return; + } + + control_phy = dev_get_drvdata(dev); + if (!control_phy) { + dev_err(dev, "%s: invalid control phy device\n", __func__); + return; + } + + if (control_phy->type != OMAP_CTRL_TYPE_PCIE) { + dev_err(dev, "%s: unsupported operation\n", __func__); + return; + } + + val = readl(control_phy->pcie_pcs); + val &= ~(OMAP_CTRL_PCIE_PCS_MASK << + OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT); + val |= (delay << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT); + writel(val, control_phy->pcie_pcs); +} +EXPORT_SYMBOL_GPL(omap_control_pcie_pcs); + +/** + * omap_control_phy_power - power on/off the phy using control module reg + * @dev: the control module device + * @on: 0 or 1, based on powering on or off the PHY + */ +void omap_control_phy_power(struct device *dev, int on) +{ + u32 val; + unsigned long rate; + struct omap_control_phy *control_phy; + + if (IS_ERR(dev) || !dev) { + pr_err("%s: invalid device\n", __func__); + return; + } + + control_phy = dev_get_drvdata(dev); + if (!control_phy) { + dev_err(dev, "%s: invalid control phy device\n", __func__); + return; + } + + if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) + return; + + val = readl(control_phy->power); + + switch (control_phy->type) { + case OMAP_CTRL_TYPE_USB2: + if (on) + val &= ~OMAP_CTRL_DEV_PHY_PD; + else + val |= OMAP_CTRL_DEV_PHY_PD; + break; + + case OMAP_CTRL_TYPE_PCIE: + case OMAP_CTRL_TYPE_PIPE3: + rate = clk_get_rate(control_phy->sys_clk); + rate = rate/1000000; + + if (on) { + val &= ~(OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK); + val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWERON << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + val |= rate << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; + } else { + val &= ~OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK; + val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWEROFF << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + } + break; + + case OMAP_CTRL_TYPE_DRA7USB2: + if (on) + val &= ~OMAP_CTRL_USB2_PHY_PD; + else + val |= OMAP_CTRL_USB2_PHY_PD; + break; + + case OMAP_CTRL_TYPE_AM437USB2: + if (on) { + val &= ~(AM437X_CTRL_USB2_PHY_PD | + AM437X_CTRL_USB2_OTG_PD); + val |= (AM437X_CTRL_USB2_OTGVDET_EN | + AM437X_CTRL_USB2_OTGSESSEND_EN); + } else { + val &= ~(AM437X_CTRL_USB2_OTGVDET_EN | + AM437X_CTRL_USB2_OTGSESSEND_EN); + val |= (AM437X_CTRL_USB2_PHY_PD | + AM437X_CTRL_USB2_OTG_PD); + } + break; + default: + dev_err(dev, "%s: type %d not recognized\n", + __func__, control_phy->type); + break; + } + + writel(val, control_phy->power); +} +EXPORT_SYMBOL_GPL(omap_control_phy_power); + +/** + * omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core that a usb + * device has been connected. + */ +static void omap_control_usb_host_mode(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND); + val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high + * impedance + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core that it has been + * connected to a usb host. + */ +static void omap_control_usb_device_mode(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~OMAP_CTRL_DEV_SESSEND; + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID | + OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high + * impedance + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core it's now in + * disconnected state. + */ +static void omap_control_usb_set_sessionend(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID); + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_set_mode - Calls to functions to set USB in one of host mode + * or device mode or to denote disconnected state + * @dev: the control module device + * @mode: The mode to which usb should be configured + * + * This is an API to write to the mailbox register to notify the usb core that + * a usb device has been connected. + */ +void omap_control_usb_set_mode(struct device *dev, + enum omap_control_usb_mode mode) +{ + struct omap_control_phy *ctrl_phy; + + if (IS_ERR(dev) || !dev) + return; + + ctrl_phy = dev_get_drvdata(dev); + if (!ctrl_phy) { + dev_err(dev, "Invalid control phy device\n"); + return; + } + + if (ctrl_phy->type != OMAP_CTRL_TYPE_OTGHS) + return; + + switch (mode) { + case USB_MODE_HOST: + omap_control_usb_host_mode(ctrl_phy); + break; + case USB_MODE_DEVICE: + omap_control_usb_device_mode(ctrl_phy); + break; + case USB_MODE_DISCONNECT: + omap_control_usb_set_sessionend(ctrl_phy); + break; + default: + dev_vdbg(dev, "invalid omap control usb mode\n"); + } +} +EXPORT_SYMBOL_GPL(omap_control_usb_set_mode); + +static const enum omap_control_phy_type otghs_data = OMAP_CTRL_TYPE_OTGHS; +static const enum omap_control_phy_type usb2_data = OMAP_CTRL_TYPE_USB2; +static const enum omap_control_phy_type pipe3_data = OMAP_CTRL_TYPE_PIPE3; +static const enum omap_control_phy_type pcie_data = OMAP_CTRL_TYPE_PCIE; +static const enum omap_control_phy_type dra7usb2_data = OMAP_CTRL_TYPE_DRA7USB2; +static const enum omap_control_phy_type am437usb2_data = OMAP_CTRL_TYPE_AM437USB2; + +static const struct of_device_id omap_control_phy_id_table[] = { + { + .compatible = "ti,control-phy-otghs", + .data = &otghs_data, + }, + { + .compatible = "ti,control-phy-usb2", + .data = &usb2_data, + }, + { + .compatible = "ti,control-phy-pipe3", + .data = &pipe3_data, + }, + { + .compatible = "ti,control-phy-pcie", + .data = &pcie_data, + }, + { + .compatible = "ti,control-phy-usb2-dra7", + .data = &dra7usb2_data, + }, + { + .compatible = "ti,control-phy-usb2-am437", + .data = &am437usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_control_phy_id_table); + +static int omap_control_phy_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct of_device_id *of_id; + struct omap_control_phy *control_phy; + + of_id = of_match_device(omap_control_phy_id_table, &pdev->dev); + if (!of_id) + return -EINVAL; + + control_phy = devm_kzalloc(&pdev->dev, sizeof(*control_phy), + GFP_KERNEL); + if (!control_phy) + return -ENOMEM; + + control_phy->dev = &pdev->dev; + control_phy->type = *(enum omap_control_phy_type *)of_id->data; + + if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "otghs_control"); + control_phy->otghs_control = devm_ioremap_resource( + &pdev->dev, res); + if (IS_ERR(control_phy->otghs_control)) + return PTR_ERR(control_phy->otghs_control); + } else { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "power"); + control_phy->power = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(control_phy->power)) { + dev_err(&pdev->dev, "Couldn't get power register\n"); + return PTR_ERR(control_phy->power); + } + } + + if (control_phy->type == OMAP_CTRL_TYPE_PIPE3 || + control_phy->type == OMAP_CTRL_TYPE_PCIE) { + control_phy->sys_clk = devm_clk_get(control_phy->dev, + "sys_clkin"); + if (IS_ERR(control_phy->sys_clk)) { + pr_err("%s: unable to get sys_clkin\n", __func__); + return -EINVAL; + } + } + + if (control_phy->type == OMAP_CTRL_TYPE_PCIE) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pcie_pcs"); + control_phy->pcie_pcs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(control_phy->pcie_pcs)) + return PTR_ERR(control_phy->pcie_pcs); + } + + dev_set_drvdata(control_phy->dev, control_phy); + + return 0; +} + +static struct platform_driver omap_control_phy_driver = { + .probe = omap_control_phy_probe, + .driver = { + .name = "omap-control-phy", + .of_match_table = omap_control_phy_id_table, + }, +}; + +static int __init omap_control_phy_init(void) +{ + return platform_driver_register(&omap_control_phy_driver); +} +subsys_initcall(omap_control_phy_init); + +static void __exit omap_control_phy_exit(void) +{ + platform_driver_unregister(&omap_control_phy_driver); +} +module_exit(omap_control_phy_exit); + +MODULE_ALIAS("platform:omap_control_phy"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP Control Module PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-omap-usb2.c b/drivers/phy/ti/phy-omap-usb2.c new file mode 100644 index 000000000000..fe909fd8144f --- /dev/null +++ b/drivers/phy/ti/phy-omap-usb2.c @@ -0,0 +1,439 @@ +/* + * omap-usb2.c - USB PHY, talking to musb controller in OMAP. + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.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. + * + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * 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/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/phy/omap_usb.h> +#include <linux/usb/phy_companion.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/delay.h> +#include <linux/phy/omap_control_phy.h> +#include <linux/phy/phy.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/of_platform.h> + +#define USB2PHY_DISCON_BYP_LATCH (1 << 31) +#define USB2PHY_ANA_CONFIG1 0x4c + +/** + * omap_usb2_set_comparator - links the comparator present in the sytem with + * this phy + * @comparator - the companion phy(comparator) for this phy + * + * The phy companion driver should call this API passing the phy_companion + * filled with set_vbus and start_srp to be used by usb phy. + * + * For use by phy companion driver + */ +int omap_usb2_set_comparator(struct phy_companion *comparator) +{ + struct omap_usb *phy; + struct usb_phy *x = usb_get_phy(USB_PHY_TYPE_USB2); + + if (IS_ERR(x)) + return -ENODEV; + + phy = phy_to_omapusb(x); + phy->comparator = comparator; + return 0; +} +EXPORT_SYMBOL_GPL(omap_usb2_set_comparator); + +static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled) +{ + struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->set_vbus(phy->comparator, enabled); +} + +static int omap_usb_start_srp(struct usb_otg *otg) +{ + struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->start_srp(phy->comparator); +} + +static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_phy_power(struct omap_usb *phy, int on) +{ + u32 val; + int ret; + + if (!phy->syscon_phy_power) { + omap_control_phy_power(phy->control_dev, on); + return 0; + } + + if (on) + val = phy->power_on; + else + val = phy->power_off; + + ret = regmap_update_bits(phy->syscon_phy_power, phy->power_reg, + phy->mask, val); + return ret; +} + +static int omap_usb_power_off(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + return omap_usb_phy_power(phy, false); +} + +static int omap_usb_power_on(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + return omap_usb_phy_power(phy, true); +} + +static int omap_usb2_disable_clocks(struct omap_usb *phy) +{ + clk_disable(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_disable(phy->optclk); + + return 0; +} + +static int omap_usb2_enable_clocks(struct omap_usb *phy) +{ + int ret; + + ret = clk_enable(phy->wkupclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto err0; + } + + if (!IS_ERR(phy->optclk)) { + ret = clk_enable(phy->optclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable optclk %d\n", ret); + goto err1; + } + } + + return 0; + +err1: + clk_disable(phy->wkupclk); + +err0: + return ret; +} + +static int omap_usb_init(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + u32 val; + + omap_usb2_enable_clocks(phy); + + if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { + /* + * + * Reduce the sensitivity of internal PHY by enabling the + * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This + * resolves issues with certain devices which can otherwise + * be prone to false disconnects. + * + */ + val = omap_usb_readl(phy->phy_base, USB2PHY_ANA_CONFIG1); + val |= USB2PHY_DISCON_BYP_LATCH; + omap_usb_writel(phy->phy_base, USB2PHY_ANA_CONFIG1, val); + } + + return 0; +} + +static int omap_usb_exit(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + return omap_usb2_disable_clocks(phy); +} + +static const struct phy_ops ops = { + .init = omap_usb_init, + .exit = omap_usb_exit, + .power_on = omap_usb_power_on, + .power_off = omap_usb_power_off, + .owner = THIS_MODULE, +}; + +static const struct usb_phy_data omap_usb2_data = { + .label = "omap_usb2", + .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data omap5_usb2_data = { + .label = "omap5_usb2", + .flags = 0, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data dra7x_usb2_data = { + .label = "dra7x_usb2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data dra7x_usb2_phy2_data = { + .label = "dra7x_usb2_phy2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_USB2_PHY_PD, + .power_off = OMAP_USB2_PHY_PD, +}; + +static const struct usb_phy_data am437x_usb2_data = { + .label = "am437x_usb2", + .flags = 0, + .mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD | + AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD, +}; + +static const struct of_device_id omap_usb2_id_table[] = { + { + .compatible = "ti,omap-usb2", + .data = &omap_usb2_data, + }, + { + .compatible = "ti,omap5-usb2", + .data = &omap5_usb2_data, + }, + { + .compatible = "ti,dra7x-usb2", + .data = &dra7x_usb2_data, + }, + { + .compatible = "ti,dra7x-usb2-phy2", + .data = &dra7x_usb2_phy2_data, + }, + { + .compatible = "ti,am437x-usb2", + .data = &am437x_usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_usb2_id_table); + +static int omap_usb2_probe(struct platform_device *pdev) +{ + struct omap_usb *phy; + struct phy *generic_phy; + struct resource *res; + struct phy_provider *phy_provider; + struct usb_otg *otg; + struct device_node *node = pdev->dev.of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + const struct of_device_id *of_id; + struct usb_phy_data *phy_data; + + of_id = of_match_device(omap_usb2_id_table, &pdev->dev); + + if (!of_id) + return -EINVAL; + + phy_data = (struct usb_phy_data *)of_id->data; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + phy->dev = &pdev->dev; + + phy->phy.dev = phy->dev; + phy->phy.label = phy_data->label; + phy->phy.otg = otg; + phy->phy.type = USB_PHY_TYPE_USB2; + phy->mask = phy_data->mask; + phy->power_on = phy_data->power_on; + phy->power_off = phy_data->power_off; + + if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->phy_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->phy_base)) + return PTR_ERR(phy->phy_base); + phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT; + } + + phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->syscon_phy_power)) { + dev_dbg(&pdev->dev, + "can't get syscon-phy-power, using control device\n"); + phy->syscon_phy_power = NULL; + + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(&pdev->dev, + "Failed to get control device phandle\n"); + return -EINVAL; + } + + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + return -EINVAL; + } + phy->control_dev = &control_pdev->dev; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(&pdev->dev, + "couldn't get power reg. offset\n"); + return -EINVAL; + } + } + + otg->set_host = omap_usb_set_host; + otg->set_peripheral = omap_usb_set_peripheral; + if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS) + otg->set_vbus = omap_usb_set_vbus; + if (phy_data->flags & OMAP_USB2_HAS_START_SRP) + otg->start_srp = omap_usb_start_srp; + otg->usb_phy = &phy->phy; + + platform_set_drvdata(pdev, phy); + pm_runtime_enable(phy->dev); + + generic_phy = devm_phy_create(phy->dev, NULL, &ops); + if (IS_ERR(generic_phy)) { + pm_runtime_disable(phy->dev); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, phy); + omap_usb_power_off(generic_phy); + + phy_provider = devm_of_phy_provider_register(phy->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + pm_runtime_disable(phy->dev); + return PTR_ERR(phy_provider); + } + + phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); + if (IS_ERR(phy->wkupclk)) { + dev_warn(&pdev->dev, "unable to get wkupclk, trying old name\n"); + phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); + pm_runtime_disable(phy->dev); + return PTR_ERR(phy->wkupclk); + } else { + dev_warn(&pdev->dev, + "found usb_phy_cm_clk32k, please fix DTS\n"); + } + } + clk_prepare(phy->wkupclk); + + phy->optclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->optclk)) { + dev_dbg(&pdev->dev, "unable to get refclk, trying old name\n"); + phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); + if (IS_ERR(phy->optclk)) { + dev_dbg(&pdev->dev, + "unable to get usb_otg_ss_refclk960m\n"); + } else { + dev_warn(&pdev->dev, + "found usb_otg_ss_refclk960m, please fix DTS\n"); + } + } + + if (!IS_ERR(phy->optclk)) + clk_prepare(phy->optclk); + + usb_add_phy_dev(&phy->phy); + + return 0; +} + +static int omap_usb2_remove(struct platform_device *pdev) +{ + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_unprepare(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_unprepare(phy->optclk); + usb_remove_phy(&phy->phy); + pm_runtime_disable(phy->dev); + + return 0; +} + +static struct platform_driver omap_usb2_driver = { + .probe = omap_usb2_probe, + .remove = omap_usb2_remove, + .driver = { + .name = "omap-usb2", + .of_match_table = omap_usb2_id_table, + }, +}; + +module_platform_driver(omap_usb2_driver); + +MODULE_ALIAS("platform:omap_usb2"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP USB2 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c new file mode 100644 index 000000000000..9c84d32c6f60 --- /dev/null +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -0,0 +1,697 @@ +/* + * phy-ti-pipe3 - PIPE3 PHY driver. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.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. + * + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * 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/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/phy/phy.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/delay.h> +#include <linux/phy/omap_control_phy.h> +#include <linux/of_platform.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> + +#define PLL_STATUS 0x00000004 +#define PLL_GO 0x00000008 +#define PLL_CONFIGURATION1 0x0000000C +#define PLL_CONFIGURATION2 0x00000010 +#define PLL_CONFIGURATION3 0x00000014 +#define PLL_CONFIGURATION4 0x00000020 + +#define PLL_REGM_MASK 0x001FFE00 +#define PLL_REGM_SHIFT 0x9 +#define PLL_REGM_F_MASK 0x0003FFFF +#define PLL_REGM_F_SHIFT 0x0 +#define PLL_REGN_MASK 0x000001FE +#define PLL_REGN_SHIFT 0x1 +#define PLL_SELFREQDCO_MASK 0x0000000E +#define PLL_SELFREQDCO_SHIFT 0x1 +#define PLL_SD_MASK 0x0003FC00 +#define PLL_SD_SHIFT 10 +#define SET_PLL_GO 0x1 +#define PLL_LDOPWDN BIT(15) +#define PLL_TICOPWDN BIT(16) +#define PLL_LOCK 0x2 +#define PLL_IDLE 0x1 + +#define SATA_PLL_SOFT_RESET BIT(18) + +#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000 +#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14 + +#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000 +#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22 + +#define PIPE3_PHY_TX_RX_POWERON 0x3 +#define PIPE3_PHY_TX_RX_POWEROFF 0x0 + +#define PCIE_PCS_MASK 0xFF0000 +#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 + +/* + * This is an Empirical value that works, need to confirm the actual + * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status + * to be correctly reflected in the PIPE3PHY_PLL_STATUS register. + */ +#define PLL_IDLE_TIME 100 /* in milliseconds */ +#define PLL_LOCK_TIME 100 /* in milliseconds */ + +struct pipe3_dpll_params { + u16 m; + u8 n; + u8 freq:3; + u8 sd; + u32 mf; +}; + +struct pipe3_dpll_map { + unsigned long rate; + struct pipe3_dpll_params params; +}; + +struct ti_pipe3 { + void __iomem *pll_ctrl_base; + struct device *dev; + struct device *control_dev; + struct clk *wkupclk; + struct clk *sys_clk; + struct clk *refclk; + struct clk *div_clk; + struct pipe3_dpll_map *dpll_map; + struct regmap *phy_power_syscon; /* ctrl. reg. acces */ + struct regmap *pcs_syscon; /* ctrl. reg. acces */ + struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */ + unsigned int dpll_reset_reg; /* reg. index within syscon */ + unsigned int power_reg; /* power reg. index within syscon */ + unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */ + bool sata_refclk_enabled; +}; + +static struct pipe3_dpll_map dpll_map_usb[] = { + {12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */ + {16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */ + {19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */ + {20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */ + {26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */ + {38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */ + { }, /* Terminator */ +}; + +static struct pipe3_dpll_map dpll_map_sata[] = { + {12000000, {1000, 7, 4, 6, 0} }, /* 12 MHz */ + {16800000, {714, 7, 4, 6, 0} }, /* 16.8 MHz */ + {19200000, {625, 7, 4, 6, 0} }, /* 19.2 MHz */ + {20000000, {600, 7, 4, 6, 0} }, /* 20 MHz */ + {26000000, {461, 7, 4, 6, 0} }, /* 26 MHz */ + {38400000, {312, 7, 4, 6, 0} }, /* 38.4 MHz */ + { }, /* Terminator */ +}; + +static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset) +{ + return __raw_readl(addr + offset); +} + +static inline void ti_pipe3_writel(void __iomem *addr, unsigned offset, + u32 data) +{ + __raw_writel(data, addr + offset); +} + +static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(struct ti_pipe3 *phy) +{ + unsigned long rate; + struct pipe3_dpll_map *dpll_map = phy->dpll_map; + + rate = clk_get_rate(phy->sys_clk); + + for (; dpll_map->rate; dpll_map++) { + if (rate == dpll_map->rate) + return &dpll_map->params; + } + + dev_err(phy->dev, "No DPLL configuration for %lu Hz SYS CLK\n", rate); + + return NULL; +} + +static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy); +static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy); + +static int ti_pipe3_power_off(struct phy *x) +{ + u32 val; + int ret; + struct ti_pipe3 *phy = phy_get_drvdata(x); + + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 0); + return 0; + } + + val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val); + return ret; +} + +static int ti_pipe3_power_on(struct phy *x) +{ + u32 val; + u32 mask; + int ret; + unsigned long rate; + struct ti_pipe3 *phy = phy_get_drvdata(x); + + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 1); + return 0; + } + + rate = clk_get_rate(phy->sys_clk); + if (!rate) { + dev_err(phy->dev, "Invalid clock rate\n"); + return -EINVAL; + } + rate = rate / 1000000; + mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK; + val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + mask, val); + return ret; +} + +static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy) +{ + u32 val; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(PLL_LOCK_TIME); + do { + cpu_relax(); + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if (val & PLL_LOCK) + return 0; + } while (!time_after(jiffies, timeout)); + + dev_err(phy->dev, "DPLL failed to lock\n"); + return -EBUSY; +} + +static int ti_pipe3_dpll_program(struct ti_pipe3 *phy) +{ + u32 val; + struct pipe3_dpll_params *dpll_params; + + dpll_params = ti_pipe3_get_dpll_params(phy); + if (!dpll_params) + return -EINVAL; + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGN_MASK; + val |= dpll_params->n << PLL_REGN_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val &= ~PLL_SELFREQDCO_MASK; + val |= dpll_params->freq << PLL_SELFREQDCO_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGM_MASK; + val |= dpll_params->m << PLL_REGM_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4); + val &= ~PLL_REGM_F_MASK; + val |= dpll_params->mf << PLL_REGM_F_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3); + val &= ~PLL_SD_MASK; + val |= dpll_params->sd << PLL_SD_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val); + + ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); + + return ti_pipe3_dpll_wait_lock(phy); +} + +static int ti_pipe3_init(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + u32 val; + int ret = 0; + + ti_pipe3_enable_clocks(phy); + /* + * Set pcie_pcs register to 0x96 for proper functioning of phy + * as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table + * 18-1804. + */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { + if (!phy->pcs_syscon) { + omap_control_pcie_pcs(phy->control_dev, 0x96); + return 0; + } + + val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT; + ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg, + PCIE_PCS_MASK, val); + return ret; + } + + /* Bring it out of IDLE if it is IDLE */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + if (val & PLL_IDLE) { + val &= ~PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + ret = ti_pipe3_dpll_wait_lock(phy); + } + + /* SATA has issues if re-programmed when locked */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node, + "ti,phy-pipe3-sata")) + return ret; + + /* Program the DPLL */ + ret = ti_pipe3_dpll_program(phy); + if (ret) { + ti_pipe3_disable_clocks(phy); + return -EINVAL; + } + + return ret; +} + +static int ti_pipe3_exit(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + u32 val; + unsigned long timeout; + + /* If dpll_reset_syscon is not present we wont power down SATA DPLL + * due to Errata i783 + */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") && + !phy->dpll_reset_syscon) + return 0; + + /* PCIe doesn't have internal DPLL */ + if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { + /* Put DPLL in IDLE mode */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val |= PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + /* wait for LDO and Oscillator to power down */ + timeout = jiffies + msecs_to_jiffies(PLL_IDLE_TIME); + do { + cpu_relax(); + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if ((val & PLL_TICOPWDN) && (val & PLL_LDOPWDN)) + break; + } while (!time_after(jiffies, timeout)); + + if (!(val & PLL_TICOPWDN) || !(val & PLL_LDOPWDN)) { + dev_err(phy->dev, "Failed to power down: PLL_STATUS 0x%x\n", + val); + return -EBUSY; + } + } + + /* i783: SATA needs control bit toggle after PLL unlock */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) { + regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, + SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET); + regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, + SATA_PLL_SOFT_RESET, 0); + } + + ti_pipe3_disable_clocks(phy); + + return 0; +} +static const struct phy_ops ops = { + .init = ti_pipe3_init, + .exit = ti_pipe3_exit, + .power_on = ti_pipe3_power_on, + .power_off = ti_pipe3_power_off, + .owner = THIS_MODULE, +}; + +static const struct of_device_id ti_pipe3_id_table[]; + +static int ti_pipe3_get_clk(struct ti_pipe3 *phy) +{ + struct clk *clk; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + + phy->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(phy->refclk)) { + dev_err(dev, "unable to get refclk\n"); + /* older DTBs have missing refclk in SATA PHY + * so don't bail out in case of SATA PHY. + */ + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) + return PTR_ERR(phy->refclk); + } + + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + phy->wkupclk = devm_clk_get(dev, "wkupclk"); + if (IS_ERR(phy->wkupclk)) { + dev_err(dev, "unable to get wkupclk\n"); + return PTR_ERR(phy->wkupclk); + } + } else { + phy->wkupclk = ERR_PTR(-ENODEV); + } + + if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") || + phy->phy_power_syscon) { + phy->sys_clk = devm_clk_get(dev, "sysclk"); + if (IS_ERR(phy->sys_clk)) { + dev_err(dev, "unable to get sysclk\n"); + return -EINVAL; + } + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + clk = devm_clk_get(dev, "dpll_ref"); + if (IS_ERR(clk)) { + dev_err(dev, "unable to get dpll ref clk\n"); + return PTR_ERR(clk); + } + clk_set_rate(clk, 1500000000); + + clk = devm_clk_get(dev, "dpll_ref_m2"); + if (IS_ERR(clk)) { + dev_err(dev, "unable to get dpll ref m2 clk\n"); + return PTR_ERR(clk); + } + clk_set_rate(clk, 100000000); + + clk = devm_clk_get(dev, "phy-div"); + if (IS_ERR(clk)) { + dev_err(dev, "unable to get phy-div clk\n"); + return PTR_ERR(clk); + } + clk_set_rate(clk, 100000000); + + phy->div_clk = devm_clk_get(dev, "div-clk"); + if (IS_ERR(phy->div_clk)) { + dev_err(dev, "unable to get div-clk\n"); + return PTR_ERR(phy->div_clk); + } + } else { + phy->div_clk = ERR_PTR(-ENODEV); + } + + return 0; +} + +static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) +{ + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + + phy->phy_power_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->phy_power_syscon)) { + dev_dbg(dev, + "can't get syscon-phy-power, using control device\n"); + phy->phy_power_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(dev, "couldn't get power reg. offset\n"); + return -EINVAL; + } + } + + if (!phy->phy_power_syscon) { + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(dev, "Failed to get control device phandle\n"); + return -EINVAL; + } + + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(dev, "Failed to get control device\n"); + return -EINVAL; + } + + phy->control_dev = &control_pdev->dev; + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pcs"); + if (IS_ERR(phy->pcs_syscon)) { + dev_dbg(dev, + "can't get syscon-pcs, using omap control\n"); + phy->pcs_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pcs", 1, + &phy->pcie_pcs_reg)) { + dev_err(dev, + "couldn't get pcie pcs reg. offset\n"); + return -EINVAL; + } + } + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pllreset"); + if (IS_ERR(phy->dpll_reset_syscon)) { + dev_info(dev, + "can't get syscon-pllreset, sata dpll won't idle\n"); + phy->dpll_reset_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pllreset", 1, + &phy->dpll_reset_reg)) { + dev_err(dev, + "couldn't get pllreset reg. offset\n"); + return -EINVAL; + } + } + } + + return 0; +} + +static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy) +{ + struct resource *res; + const struct of_device_id *match; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct platform_device *pdev = to_platform_device(dev); + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) + return 0; + + match = of_match_device(ti_pipe3_id_table, dev); + if (!match) + return -EINVAL; + + phy->dpll_map = (struct pipe3_dpll_map *)match->data; + if (!phy->dpll_map) { + dev_err(dev, "no DPLL data\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(dev, res); + return PTR_ERR_OR_ZERO(phy->pll_ctrl_base); +} + +static int ti_pipe3_probe(struct platform_device *pdev) +{ + struct ti_pipe3 *phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->dev = dev; + + ret = ti_pipe3_get_pll_base(phy); + if (ret) + return ret; + + ret = ti_pipe3_get_sysctrl(phy); + if (ret) + return ret; + + ret = ti_pipe3_get_clk(phy); + if (ret) + return ret; + + platform_set_drvdata(pdev, phy); + pm_runtime_enable(dev); + + /* + * Prevent auto-disable of refclk for SATA PHY due to Errata i783 + */ + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + if (!IS_ERR(phy->refclk)) { + clk_prepare_enable(phy->refclk); + phy->sata_refclk_enabled = true; + } + } + + generic_phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(generic_phy)) + return PTR_ERR(generic_phy); + + phy_set_drvdata(generic_phy, phy); + + ti_pipe3_power_off(generic_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int ti_pipe3_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy) +{ + int ret = 0; + + if (!IS_ERR(phy->refclk)) { + ret = clk_prepare_enable(phy->refclk); + if (ret) { + dev_err(phy->dev, "Failed to enable refclk %d\n", ret); + return ret; + } + } + + if (!IS_ERR(phy->wkupclk)) { + ret = clk_prepare_enable(phy->wkupclk); + if (ret) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto disable_refclk; + } + } + + if (!IS_ERR(phy->div_clk)) { + ret = clk_prepare_enable(phy->div_clk); + if (ret) { + dev_err(phy->dev, "Failed to enable div_clk %d\n", ret); + goto disable_wkupclk; + } + } + + return 0; + +disable_wkupclk: + if (!IS_ERR(phy->wkupclk)) + clk_disable_unprepare(phy->wkupclk); + +disable_refclk: + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); + + return ret; +} + +static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy) +{ + if (!IS_ERR(phy->wkupclk)) + clk_disable_unprepare(phy->wkupclk); + if (!IS_ERR(phy->refclk)) { + clk_disable_unprepare(phy->refclk); + /* + * SATA refclk needs an additional disable as we left it + * on in probe to avoid Errata i783 + */ + if (phy->sata_refclk_enabled) { + clk_disable_unprepare(phy->refclk); + phy->sata_refclk_enabled = false; + } + } + + if (!IS_ERR(phy->div_clk)) + clk_disable_unprepare(phy->div_clk); +} + +static const struct of_device_id ti_pipe3_id_table[] = { + { + .compatible = "ti,phy-usb3", + .data = dpll_map_usb, + }, + { + .compatible = "ti,omap-usb3", + .data = dpll_map_usb, + }, + { + .compatible = "ti,phy-pipe3-sata", + .data = dpll_map_sata, + }, + { + .compatible = "ti,phy-pipe3-pcie", + }, + {} +}; +MODULE_DEVICE_TABLE(of, ti_pipe3_id_table); + +static struct platform_driver ti_pipe3_driver = { + .probe = ti_pipe3_probe, + .remove = ti_pipe3_remove, + .driver = { + .name = "ti-pipe3", + .of_match_table = ti_pipe3_id_table, + }, +}; + +module_platform_driver(ti_pipe3_driver); + +MODULE_ALIAS("platform:ti_pipe3"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TI PIPE3 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c new file mode 100644 index 000000000000..b8ec39ac4dfc --- /dev/null +++ b/drivers/phy/ti/phy-tusb1210.c @@ -0,0 +1,182 @@ +/** + * 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/ulpi/regs.h> +#include <linux/gpio/consumer.h> +#include <linux/phy/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 int tusb1210_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct tusb1210 *tusb = phy_get_drvdata(phy); + int ret; + + ret = ulpi_read(tusb->ulpi, ULPI_OTG_CTRL); + if (ret < 0) + return ret; + + switch (mode) { + case PHY_MODE_USB_HOST: + ret |= (ULPI_OTG_CTRL_DRVVBUS_EXT + | ULPI_OTG_CTRL_ID_PULLUP + | ULPI_OTG_CTRL_DP_PULLDOWN + | ULPI_OTG_CTRL_DM_PULLDOWN); + ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); + ret |= ULPI_OTG_CTRL_DRVVBUS; + break; + case PHY_MODE_USB_DEVICE: + ret &= ~(ULPI_OTG_CTRL_DRVVBUS + | ULPI_OTG_CTRL_DP_PULLDOWN + | ULPI_OTG_CTRL_DM_PULLDOWN); + ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); + ret &= ~ULPI_OTG_CTRL_DRVVBUS_EXT; + break; + default: + /* nothing */ + return 0; + } + + return ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); +} + +static const struct phy_ops phy_ops = { + .power_on = tusb1210_power_on, + .power_off = tusb1210_power_off, + .set_mode = tusb1210_set_mode, + .owner = THIS_MODULE, +}; + +static int tusb1210_probe(struct ulpi *ulpi) +{ + struct tusb1210 *tusb; + u8 val, reg; + + tusb = devm_kzalloc(&ulpi->dev, sizeof(*tusb), GFP_KERNEL); + if (!tusb) + return -ENOMEM; + + tusb->gpio_reset = devm_gpiod_get_optional(&ulpi->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(tusb->gpio_reset)) + return PTR_ERR(tusb->gpio_reset); + + gpiod_set_value_cansleep(tusb->gpio_reset, 1); + + tusb->gpio_cs = devm_gpiod_get_optional(&ulpi->dev, "cs", + GPIOD_OUT_LOW); + if (IS_ERR(tusb->gpio_cs)) + return PTR_ERR(tusb->gpio_cs); + + gpiod_set_value_cansleep(tusb->gpio_cs, 1); + + /* + * 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, }, /* TUSB1210 */ + { TI_VENDOR_ID, 0x1508, }, /* TUSB1211 */ + { }, +}; +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/ti/phy-twl4030-usb.c b/drivers/phy/ti/phy-twl4030-usb.c new file mode 100644 index 000000000000..2990b3965460 --- /dev/null +++ b/drivers/phy/ti/phy-twl4030-usb.c @@ -0,0 +1,839 @@ +/* + * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004-2007 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Contact: Felipe Balbi <felipe.balbi@nokia.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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + * - HS USB ULPI mode works. + * - 3-pin mode support may be added in future. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/usb/otg.h> +#include <linux/phy/phy.h> +#include <linux/pm_runtime.h> +#include <linux/usb/musb.h> +#include <linux/usb/ulpi.h> +#include <linux/i2c/twl.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> + +/* Register defines */ + +#define MCPC_CTRL 0x30 +#define MCPC_CTRL_RTSOL (1 << 7) +#define MCPC_CTRL_EXTSWR (1 << 6) +#define MCPC_CTRL_EXTSWC (1 << 5) +#define MCPC_CTRL_VOICESW (1 << 4) +#define MCPC_CTRL_OUT64K (1 << 3) +#define MCPC_CTRL_RTSCTSSW (1 << 2) +#define MCPC_CTRL_HS_UART (1 << 0) + +#define MCPC_IO_CTRL 0x33 +#define MCPC_IO_CTRL_MICBIASEN (1 << 5) +#define MCPC_IO_CTRL_CTS_NPU (1 << 4) +#define MCPC_IO_CTRL_RXD_PU (1 << 3) +#define MCPC_IO_CTRL_TXDTYP (1 << 2) +#define MCPC_IO_CTRL_CTSTYP (1 << 1) +#define MCPC_IO_CTRL_RTSTYP (1 << 0) + +#define MCPC_CTRL2 0x36 +#define MCPC_CTRL2_MCPC_CK_EN (1 << 0) + +#define OTHER_FUNC_CTRL 0x80 +#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4) +#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2) + +#define OTHER_IFC_CTRL 0x83 +#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6) +#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5) +#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4) +#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3) +#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2) +#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0) + +#define OTHER_INT_EN_RISE 0x86 +#define OTHER_INT_EN_FALL 0x89 +#define OTHER_INT_STS 0x8C +#define OTHER_INT_LATCH 0x8D +#define OTHER_INT_VB_SESS_VLD (1 << 7) +#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */ +#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */ +#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */ +#define OTHER_INT_MANU (1 << 1) +#define OTHER_INT_ABNORMAL_STRESS (1 << 0) + +#define ID_STATUS 0x96 +#define ID_RES_FLOAT (1 << 4) +#define ID_RES_440K (1 << 3) +#define ID_RES_200K (1 << 2) +#define ID_RES_102K (1 << 1) +#define ID_RES_GND (1 << 0) + +#define POWER_CTRL 0xAC +#define POWER_CTRL_OTG_ENAB (1 << 5) + +#define OTHER_IFC_CTRL2 0xAF +#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4) +#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3) +#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */ +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0) + +#define REG_CTRL_EN 0xB2 +#define REG_CTRL_ERROR 0xB5 +#define ULPI_I2C_CONFLICT_INTEN (1 << 0) + +#define OTHER_FUNC_CTRL2 0xB8 +#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0) + +/* following registers do not have separate _clr and _set registers */ +#define VBUS_DEBOUNCE 0xC0 +#define ID_DEBOUNCE 0xC1 +#define VBAT_TIMER 0xD3 +#define PHY_PWR_CTRL 0xFD +#define PHY_PWR_PHYPWD (1 << 0) +#define PHY_CLK_CTRL 0xFE +#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2) +#define PHY_CLK_CTRL_CLK32K_EN (1 << 1) +#define REQ_PHY_DPLL_CLK (1 << 0) +#define PHY_CLK_CTRL_STS 0xFF +#define PHY_DPLL_CLK (1 << 0) + +/* In module TWL_MODULE_PM_MASTER */ +#define STS_HW_CONDITIONS 0x0F + +/* In module TWL_MODULE_PM_RECEIVER */ +#define VUSB_DEDICATED1 0x7D +#define VUSB_DEDICATED2 0x7E +#define VUSB1V5_DEV_GRP 0x71 +#define VUSB1V5_TYPE 0x72 +#define VUSB1V5_REMAP 0x73 +#define VUSB1V8_DEV_GRP 0x74 +#define VUSB1V8_TYPE 0x75 +#define VUSB1V8_REMAP 0x76 +#define VUSB3V1_DEV_GRP 0x77 +#define VUSB3V1_TYPE 0x78 +#define VUSB3V1_REMAP 0x79 + +/* In module TWL4030_MODULE_INTBR */ +#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 musb_vbus_id_status stat) +{ + return stat == MUSB_VBUS_VALID || + stat == MUSB_ID_GROUND; +} + +struct twl4030_usb { + struct usb_phy phy; + struct device *dev; + + /* TWL4030 internal USB regulator supplies */ + struct regulator *usb1v5; + struct regulator *usb1v8; + struct regulator *usb3v1; + + /* for vbus reporting with irqs disabled */ + struct mutex lock; + + /* pin configuration */ + enum twl4030_usb_mode usb_mode; + + int irq; + enum musb_vbus_id_status linkstat; + bool vbus_supplied; + bool musb_mailbox_pending; + + struct delayed_work id_workaround_work; +}; + +/* internal define on top of container_of */ +#define phy_to_twl(x) container_of((x), struct twl4030_usb, phy) + +/*-------------------------------------------------------------------------*/ + +static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl, + u8 module, u8 data, u8 address) +{ + u8 check; + + if ((twl_i2c_write_u8(module, data, address) >= 0) && + (twl_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 1, module, address, check, data); + + /* Failed once: Try again */ + if ((twl_i2c_write_u8(module, data, address) >= 0) && + (twl_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 2, module, address, check, data); + + /* Failed again: Return error */ + return -EBUSY; +} + +#define twl4030_usb_write_verify(twl, address, data) \ + twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address)) + +static inline int twl4030_usb_write(struct twl4030_usb *twl, + u8 address, u8 data) +{ + int ret = 0; + + ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address); + if (ret < 0) + dev_dbg(twl->dev, + "TWL4030:USB:Write[0x%x] Error %d\n", address, ret); + return ret; +} + +static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address) +{ + u8 data; + int ret = 0; + + ret = twl_i2c_read_u8(module, &data, address); + if (ret >= 0) + ret = data; + else + dev_dbg(twl->dev, + "TWL4030:readb[0x%x,0x%x] Error %d\n", + module, address, ret); + + return ret; +} + +static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) +{ + return twl4030_readb(twl, TWL_MODULE_USB, address); +} + +/*-------------------------------------------------------------------------*/ + +static inline int +twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, ULPI_SET(reg), bits); +} + +static inline int +twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, ULPI_CLR(reg), bits); +} + +/*-------------------------------------------------------------------------*/ + +static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) +{ + int ret; + + ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS); + if (ret < 0 || !(ret & PHY_DPLL_CLK)) + /* + * if clocks are off, registers are not updated, + * but we can assume we don't drive VBUS in this case + */ + return false; + + ret = twl4030_usb_read(twl, ULPI_OTG_CTRL); + if (ret < 0) + return false; + + return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; +} + +static enum musb_vbus_id_status + twl4030_usb_linkstat(struct twl4030_usb *twl) +{ + int status; + enum musb_vbus_id_status linkstat = MUSB_UNKNOWN; + + twl->vbus_supplied = false; + + /* + * For ID/VBUS sensing, see manual section 15.4.8 ... + * except when using only battery backup power, two + * comparators produce VBUS_PRES and ID_PRES signals, + * which don't match docs elsewhere. But ... BIT(7) + * and BIT(2) of STS_HW_CONDITIONS, respectively, do + * seem to match up. If either is true the USB_PRES + * signal is active, the OTG module is activated, and + * its interrupt may be raised (may wake the system). + */ + status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS); + if (status < 0) + dev_err(twl->dev, "USB link status err %d\n", status); + else if (status & (BIT(7) | BIT(2))) { + if (status & BIT(7)) { + if (twl4030_is_driving_vbus(twl)) + status &= ~BIT(7); + else + twl->vbus_supplied = true; + } + + if (status & BIT(2)) + linkstat = MUSB_ID_GROUND; + else if (status & BIT(7)) + linkstat = MUSB_VBUS_VALID; + else + linkstat = MUSB_VBUS_OFF; + } else { + if (twl->linkstat != MUSB_UNKNOWN) + linkstat = MUSB_VBUS_OFF; + } + + kobject_uevent(&twl->dev->kobj, linkstat == MUSB_VBUS_VALID + ? KOBJ_ONLINE : KOBJ_OFFLINE); + + dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", + status, status, linkstat); + + /* REVISIT this assumes host and peripheral controllers + * are registered, and that both are active... + */ + + return linkstat; +} + +static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) +{ + twl->usb_mode = mode; + + switch (mode) { + case T2_USB_MODE_ULPI: + twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL, + ULPI_IFC_CTRL_CARKITMODE); + twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL, + ULPI_FUNC_CTRL_XCVRSEL_MASK | + ULPI_FUNC_CTRL_OPMODE_MASK); + break; + case -1: + /* FIXME: power on defaults */ + break; + default: + dev_err(twl->dev, "unsupported T2 transceiver mode %d\n", + mode); + break; + } +} + +static void twl4030_i2c_access(struct twl4030_usb *twl, int on) +{ + unsigned long timeout; + int val = twl4030_usb_read(twl, PHY_CLK_CTRL); + + if (val >= 0) { + if (on) { + /* enable DPLL to access PHY registers over I2C */ + val |= REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + + timeout = jiffies + HZ; + while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK) + && time_before(jiffies, timeout)) + udelay(10); + if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK)) + dev_err(twl->dev, "Timeout setting T2 HSUSB " + "PHY DPLL clock\n"); + } else { + /* let ULPI control the DPLL clock */ + val &= ~REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + } + } +} + +static void __twl4030_phy_power(struct twl4030_usb *twl, int on) +{ + u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); + + if (on) + pwr &= ~PHY_PWR_PHYPWD; + else + pwr |= PHY_PWR_PHYPWD; + + WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); +} + +static int __maybe_unused twl4030_usb_runtime_suspend(struct device *dev) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + + dev_dbg(twl->dev, "%s\n", __func__); + + __twl4030_phy_power(twl, 0); + regulator_disable(twl->usb1v5); + regulator_disable(twl->usb1v8); + regulator_disable(twl->usb3v1); + + return 0; +} + +static int __maybe_unused twl4030_usb_runtime_resume(struct device *dev) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + int res; + + dev_dbg(twl->dev, "%s\n", __func__); + + res = regulator_enable(twl->usb3v1); + if (res) + dev_err(twl->dev, "Failed to enable usb3v1\n"); + + res = regulator_enable(twl->usb1v8); + if (res) + dev_err(twl->dev, "Failed to enable usb1v8\n"); + + /* + * Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP + * in twl4030) resets the VUSB_DEDICATED2 register. This reset + * enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to + * SLEEP. We work around this by clearing the bit after usv3v1 + * is re-activated. This ensures that VUSB3V1 is really active. + */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); + + res = regulator_enable(twl->usb1v5); + if (res) + dev_err(twl->dev, "Failed to enable usb1v5\n"); + + __twl4030_phy_power(twl, 1); + twl4030_usb_write(twl, PHY_CLK_CTRL, + twl4030_usb_read(twl, PHY_CLK_CTRL) | + (PHY_CLK_CTRL_CLOCKGATING_EN | + PHY_CLK_CTRL_CLK32K_EN)); + + twl4030_i2c_access(twl, 1); + twl4030_usb_set_mode(twl, twl->usb_mode); + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_i2c_access(twl, 0); + /* + * According to the TPS65950 TRM, there has to be at least 50ms + * delay between setting POWER_CTRL_OTG_ENAB and enabling charging + * so wait here so that a fully enabled phy can be expected after + * resume + */ + msleep(50); + return 0; +} + +static int twl4030_phy_power_off(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + dev_dbg(twl->dev, "%s\n", __func__); + + return 0; +} + +static int twl4030_phy_power_on(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + dev_dbg(twl->dev, "%s\n", __func__); + pm_runtime_get_sync(twl->dev); + schedule_delayed_work(&twl->id_workaround_work, HZ); + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_put_autosuspend(twl->dev); + + return 0; +} + +static int twl4030_usb_ldo_init(struct twl4030_usb *twl) +{ + /* Enable writing to power configuration registers */ + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, + TWL4030_PM_MASTER_PROTECT_KEY); + + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, + TWL4030_PM_MASTER_PROTECT_KEY); + + /* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/ + /*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/ + + /* input to VUSB3V1 LDO is from VBAT, not VBUS */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); + + /* Initialize 3.1V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP); + + twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1"); + if (IS_ERR(twl->usb3v1)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); + + /* Initialize 1.5V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP); + + twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5"); + if (IS_ERR(twl->usb1v5)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); + + /* Initialize 1.8V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP); + + twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8"); + if (IS_ERR(twl->usb1v8)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); + + /* disable access to power configuration registers */ + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, + TWL4030_PM_MASTER_PROTECT_KEY); + + return 0; +} + +static ssize_t twl4030_usb_vbus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + int ret = -EINVAL; + + mutex_lock(&twl->lock); + ret = sprintf(buf, "%s\n", + twl->vbus_supplied ? "on" : "off"); + mutex_unlock(&twl->lock); + + return ret; +} +static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); + +static irqreturn_t twl4030_usb_irq(int irq, void *_twl) +{ + struct twl4030_usb *twl = _twl; + enum musb_vbus_id_status status; + bool status_changed = false; + int err; + + status = twl4030_usb_linkstat(twl); + + mutex_lock(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + status_changed = + cable_present(twl->linkstat) != + cable_present(status); + twl->linkstat = status; + } + mutex_unlock(&twl->lock); + + if (status_changed) { + /* FIXME add a set_power() method so that B-devices can + * configure the charger appropriately. It's not always + * correct to consume VBUS power, and how much current to + * consume is a function of the USB configuration chosen + * by the host. + * + * REVISIT usb_gadget_vbus_connect(...) as needed, ditto + * its disconnect() sibling, when changing to/from the + * USB_LINK_VBUS state. musb_hdrc won't care until it + * starts to handle softconnect right. + */ + if (cable_present(status)) { + pm_runtime_get_sync(twl->dev); + } else { + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_put_autosuspend(twl->dev); + } + twl->musb_mailbox_pending = true; + } + if (twl->musb_mailbox_pending) { + err = musb_mailbox(status); + if (!err) + twl->musb_mailbox_pending = false; + } + + /* don't schedule during sleep - irq works right then */ + if (status == MUSB_ID_GROUND && pm_runtime_active(twl->dev)) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } + + if (irq) + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + + return IRQ_HANDLED; +} + +static void twl4030_id_workaround_work(struct work_struct *work) +{ + struct twl4030_usb *twl = container_of(work, struct twl4030_usb, + id_workaround_work.work); + + twl4030_usb_irq(0, twl); +} + +static int twl4030_phy_init(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + pm_runtime_get_sync(twl->dev); + twl->linkstat = MUSB_UNKNOWN; + schedule_delayed_work(&twl->id_workaround_work, HZ); + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_put_autosuspend(twl->dev); + + return 0; +} + +static int twl4030_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + if (!otg) + return -ENODEV; + + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + if (!otg) + return -ENODEV; + + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static const struct phy_ops ops = { + .init = twl4030_phy_init, + .power_on = twl4030_phy_power_on, + .power_off = twl4030_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct dev_pm_ops twl4030_usb_pm_ops = { + SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend, + twl4030_usb_runtime_resume, NULL) +}; + +static int twl4030_usb_probe(struct platform_device *pdev) +{ + struct twl4030_usb_data *pdata = dev_get_platdata(&pdev->dev); + struct twl4030_usb *twl; + struct phy *phy; + int status, err; + struct usb_otg *otg; + struct device_node *np = pdev->dev.of_node; + struct phy_provider *phy_provider; + + twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); + if (!twl) + return -ENOMEM; + + if (np) + of_property_read_u32(np, "usb_mode", + (enum twl4030_usb_mode *)&twl->usb_mode); + else if (pdata) { + twl->usb_mode = pdata->usb_mode; + } else { + dev_err(&pdev->dev, "twl4030 initialized without pdata\n"); + return -EINVAL; + } + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + twl->dev = &pdev->dev; + twl->irq = platform_get_irq(pdev, 0); + twl->vbus_supplied = false; + twl->linkstat = MUSB_UNKNOWN; + twl->musb_mailbox_pending = false; + + twl->phy.dev = twl->dev; + twl->phy.label = "twl4030"; + twl->phy.otg = otg; + twl->phy.type = USB_PHY_TYPE_USB2; + + otg->usb_phy = &twl->phy; + otg->set_host = twl4030_set_host; + otg->set_peripheral = twl4030_set_peripheral; + + phy = devm_phy_create(twl->dev, NULL, &ops); + if (IS_ERR(phy)) { + dev_dbg(&pdev->dev, "Failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, twl); + + phy_provider = devm_of_phy_provider_register(twl->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + /* init mutex for workqueue */ + mutex_init(&twl->lock); + + INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work); + + err = twl4030_usb_ldo_init(twl); + if (err) { + dev_err(&pdev->dev, "ldo init failed\n"); + return err; + } + usb_add_phy_dev(&twl->phy); + + platform_set_drvdata(pdev, twl); + if (device_create_file(&pdev->dev, &dev_attr_vbus)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier); + + 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. + * + * FIXME we actually shouldn't start enabling it until the + * USB controller drivers have said they're ready, by calling + * set_host() and/or set_peripheral() ... OTG_capable boards + * need both handles, otherwise just one suffices. + */ + status = devm_request_threaded_irq(twl->dev, twl->irq, NULL, + twl4030_usb_irq, IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl); + if (status < 0) { + dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n", + twl->irq, status); + return status; + } + + if (pdata) + err = phy_create_lookup(phy, "usb", "musb-hdrc.0"); + if (err) + return err; + + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_put_autosuspend(twl->dev); + + dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); + return 0; +} + +static int twl4030_usb_remove(struct platform_device *pdev) +{ + struct twl4030_usb *twl = platform_get_drvdata(pdev); + int val; + + usb_remove_phy(&twl->phy); + pm_runtime_get_sync(twl->dev); + cancel_delayed_work(&twl->id_workaround_work); + device_remove_file(twl->dev, &dev_attr_vbus); + + /* set transceiver mode to power on defaults */ + twl4030_usb_set_mode(twl, -1); + + /* idle ulpi before powering off */ + if (cable_present(twl->linkstat)) + pm_runtime_put_noidle(twl->dev); + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_put_sync(twl->dev); + pm_runtime_disable(twl->dev); + + /* autogate 60MHz ULPI clock, + * clear dpll clock request for i2c access, + * disable 32KHz + */ + val = twl4030_usb_read(twl, PHY_CLK_CTRL); + if (val >= 0) { + val |= PHY_CLK_CTRL_CLOCKGATING_EN; + val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK); + twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val); + } + + /* disable complete OTG block */ + twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id twl4030_usb_id_table[] = { + { .compatible = "ti,twl4030-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, twl4030_usb_id_table); +#endif + +static struct platform_driver twl4030_usb_driver = { + .probe = twl4030_usb_probe, + .remove = twl4030_usb_remove, + .driver = { + .name = "twl4030_usb", + .pm = &twl4030_usb_pm_ops, + .of_match_table = of_match_ptr(twl4030_usb_id_table), + }, +}; + +static int __init twl4030_usb_init(void) +{ + return platform_driver_register(&twl4030_usb_driver); +} +subsys_initcall(twl4030_usb_init); + +static void __exit twl4030_usb_exit(void) +{ + platform_driver_unregister(&twl4030_usb_driver); +} +module_exit(twl4030_usb_exit); + +MODULE_ALIAS("platform:twl4030_usb"); +MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation"); +MODULE_DESCRIPTION("TWL4030 USB transceiver driver"); +MODULE_LICENSE("GPL"); |