diff options
Diffstat (limited to 'drivers/phy')
| -rw-r--r-- | drivers/phy/Kconfig | 183 | ||||
| -rw-r--r-- | drivers/phy/Makefile | 22 | ||||
| -rw-r--r-- | drivers/phy/phy-bcm-kona-usb2.c | 156 | ||||
| -rw-r--r-- | drivers/phy/phy-core.c | 830 | ||||
| -rw-r--r-- | drivers/phy/phy-exynos-dp-video.c | 111 | ||||
| -rw-r--r-- | drivers/phy/phy-exynos-mipi-video.c | 176 | ||||
| -rw-r--r-- | drivers/phy/phy-exynos4210-usb2.c | 261 | ||||
| -rw-r--r-- | drivers/phy/phy-exynos4x12-usb2.c | 328 | ||||
| -rw-r--r-- | drivers/phy/phy-exynos5-usbdrd.c | 676 | ||||
| -rw-r--r-- | drivers/phy/phy-exynos5250-sata.c | 251 | ||||
| -rw-r--r-- | drivers/phy/phy-exynos5250-usb2.c | 404 | ||||
| -rw-r--r-- | drivers/phy/phy-mvebu-sata.c | 137 | ||||
| -rw-r--r-- | drivers/phy/phy-omap-control.c | 320 | ||||
| -rw-r--r-- | drivers/phy/phy-omap-usb2.c | 396 | ||||
| -rw-r--r-- | drivers/phy/phy-samsung-usb2.c | 229 | ||||
| -rw-r--r-- | drivers/phy/phy-samsung-usb2.h | 67 | ||||
| -rw-r--r-- | drivers/phy/phy-sun4i-usb.c | 336 | ||||
| -rw-r--r-- | drivers/phy/phy-ti-pipe3.c | 470 | ||||
| -rw-r--r-- | drivers/phy/phy-twl4030-usb.c | 815 | ||||
| -rw-r--r-- | drivers/phy/phy-xgene.c | 1750 |
20 files changed, 7918 insertions, 0 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig new file mode 100644 index 00000000000..64b98d242ea --- /dev/null +++ b/drivers/phy/Kconfig @@ -0,0 +1,183 @@ +# +# PHY +# + +menu "PHY Subsystem" + +config GENERIC_PHY + bool "PHY Core" + help + Generic PHY support. + + This framework is designed to provide a generic interface for PHY + devices present in the kernel. This layer will have the generic + API by which phy drivers can create PHY using the phy framework and + phy users can obtain reference to the PHY. All the users of this + framework should select this config. + +config PHY_EXYNOS_MIPI_VIDEO + tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver" + depends on HAS_IOMEM + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select GENERIC_PHY + default y if ARCH_S5PV210 || ARCH_EXYNOS + help + Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P + and EXYNOS SoCs. + +config PHY_MVEBU_SATA + def_bool y + depends on ARCH_KIRKWOOD || ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD + depends on OF + select GENERIC_PHY + +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_PHY + select GENERIC_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 TWL4030_USB + tristate "TWL4030 USB Transceiver Driver" + depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS + depends on USB_PHY + select GENERIC_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. + +config PHY_EXYNOS_DP_VIDEO + tristate "EXYNOS SoC series Display Port PHY driver" + depends on OF + depends on ARCH_EXYNOS || COMPILE_TEST + default ARCH_EXYNOS + select GENERIC_PHY + help + Support for Display Port PHY found on Samsung EXYNOS SoCs. + +config BCM_KONA_USB2_PHY + tristate "Broadcom Kona USB2 PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support the Broadcom Kona USB 2.0 PHY. + +config PHY_EXYNOS5250_SATA + tristate "Exynos5250 Sata SerDes/PHY driver" + depends on SOC_EXYNOS5250 + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + select I2C + select I2C_S3C2410 + select MFD_SYSCON + help + Enable this to support SATA SerDes/Phy found on Samsung's + Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s, + SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host + port to accept one SATA device. + +config PHY_SUN4I_USB + tristate "Allwinner sunxi SoC USB PHY driver" + depends on ARCH_SUNXI && HAS_IOMEM && OF + depends on RESET_CONTROLLER + select GENERIC_PHY + help + Enable this to support the transceiver that is part of Allwinner + sunxi SoCs. + + This driver controls the entire USB PHY block, both the USB OTG + parts, as well as the 2 regular USB 2 host PHYs. + +config PHY_SAMSUNG_USB2 + tristate "Samsung USB 2.0 PHY driver" + depends on HAS_IOMEM + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the Samsung USB 2.0 PHY driver for Samsung + SoCs. This driver provides the interface for USB 2.0 PHY. Support for + particular SoCs has to be enabled in addition to this driver. Number + and type of supported phys depends on the SoC. + +config PHY_EXYNOS4210_USB2 + bool "Support for Exynos 4210" + depends on PHY_SAMSUNG_USB2 + depends on CPU_EXYNOS4210 + help + Enable USB PHY support for Exynos 4210. This option requires that + Samsung USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of Exynos 4210 four + phys are available - device, host, HSIC0 and HSIC1. + +config PHY_EXYNOS4X12_USB2 + bool "Support for Exynos 4x12" + depends on PHY_SAMSUNG_USB2 + depends on (SOC_EXYNOS4212 || SOC_EXYNOS4412) + help + Enable USB PHY support for Exynos 4x12. This option requires that + Samsung USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of Exynos 4x12 four + phys are available - device, host, HSIC0 and HSIC1. + +config PHY_EXYNOS5250_USB2 + bool "Support for Exynos 5250" + depends on PHY_SAMSUNG_USB2 + depends on SOC_EXYNOS5250 + help + Enable USB PHY support for Exynos 5250. This option requires that + Samsung USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of Exynos 5250 four + phys are available - device, host, HSIC0 and HSIC. + +config PHY_EXYNOS5_USBDRD + tristate "Exynos5 SoC series USB DRD PHY driver" + depends on ARCH_EXYNOS5 && OF + depends on HAS_IOMEM + select GENERIC_PHY + select MFD_SYSCON + help + Enable USB DRD PHY support for Exynos 5 SoC series. + This driver provides PHY interface for USB 3.0 DRD controller + present on Exynos5 SoC series. + +config PHY_XGENE + tristate "APM X-Gene 15Gbps PHY support" + depends on HAS_IOMEM && OF && (ARM64 || COMPILE_TEST) + select GENERIC_PHY + help + This option enables support for APM X-Gene SoC multi-purpose PHY. + +endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile new file mode 100644 index 00000000000..b4f1d577060 --- /dev/null +++ b/drivers/phy/Makefile @@ -0,0 +1,22 @@ +# +# Makefile for the phy drivers. +# + +obj-$(CONFIG_GENERIC_PHY) += phy-core.o +obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o +obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o +obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o +obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.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_TWL4030_USB) += phy-twl4030-usb.o +obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o +obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o +obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o +phy-exynos-usb2-y += phy-samsung-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o +obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o +obj-$(CONFIG_PHY_XGENE) += phy-xgene.o diff --git a/drivers/phy/phy-bcm-kona-usb2.c b/drivers/phy/phy-bcm-kona-usb2.c new file mode 100644 index 00000000000..e94f5a6a564 --- /dev/null +++ b/drivers/phy/phy-bcm-kona-usb2.c @@ -0,0 +1,156 @@ +/* + * phy-bcm-kona-usb2.c - Broadcom Kona USB2 Phy Driver + * + * Copyright (C) 2013 Linaro Limited + * Matt Porter <mporter@linaro.org> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define OTGCTL (0) +#define OTGCTL_OTGSTAT2 BIT(31) +#define OTGCTL_OTGSTAT1 BIT(30) +#define OTGCTL_PRST_N_SW BIT(11) +#define OTGCTL_HRESET_N BIT(10) +#define OTGCTL_UTMI_LINE_STATE1 BIT(9) +#define OTGCTL_UTMI_LINE_STATE0 BIT(8) + +#define P1CTL (8) +#define P1CTL_SOFT_RESET BIT(1) +#define P1CTL_NON_DRIVING BIT(0) + +struct bcm_kona_usb { + void __iomem *regs; +}; + +static void bcm_kona_usb_phy_power(struct bcm_kona_usb *phy, int on) +{ + u32 val; + + val = readl(phy->regs + OTGCTL); + if (on) { + /* Configure and power PHY */ + val &= ~(OTGCTL_OTGSTAT2 | OTGCTL_OTGSTAT1 | + OTGCTL_UTMI_LINE_STATE1 | OTGCTL_UTMI_LINE_STATE0); + val |= OTGCTL_PRST_N_SW | OTGCTL_HRESET_N; + } else { + val &= ~(OTGCTL_PRST_N_SW | OTGCTL_HRESET_N); + } + writel(val, phy->regs + OTGCTL); +} + +static int bcm_kona_usb_phy_init(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + u32 val; + + /* Soft reset PHY */ + val = readl(phy->regs + P1CTL); + val &= ~P1CTL_NON_DRIVING; + val |= P1CTL_SOFT_RESET; + writel(val, phy->regs + P1CTL); + writel(val & ~P1CTL_SOFT_RESET, phy->regs + P1CTL); + /* Reset needs to be asserted for 2ms */ + mdelay(2); + writel(val | P1CTL_SOFT_RESET, phy->regs + P1CTL); + + return 0; +} + +static int bcm_kona_usb_phy_power_on(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + + bcm_kona_usb_phy_power(phy, 1); + + return 0; +} + +static int bcm_kona_usb_phy_power_off(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + + bcm_kona_usb_phy_power(phy, 0); + + return 0; +} + +static struct phy_ops ops = { + .init = bcm_kona_usb_phy_init, + .power_on = bcm_kona_usb_phy_power_on, + .power_off = bcm_kona_usb_phy_power_off, + .owner = THIS_MODULE, +}; + +static int bcm_kona_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm_kona_usb *phy; + struct resource *res; + struct phy *gphy; + struct phy_provider *phy_provider; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->regs)) + return PTR_ERR(phy->regs); + + platform_set_drvdata(pdev, phy); + + gphy = devm_phy_create(dev, &ops, NULL); + if (IS_ERR(gphy)) + return PTR_ERR(gphy); + + /* The Kona PHY supports an 8-bit wide UTMI interface */ + phy_set_bus_width(gphy, 8); + + phy_set_drvdata(gphy, phy); + + phy_provider = devm_of_phy_provider_register(dev, + of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id bcm_kona_usb2_dt_ids[] = { + { .compatible = "brcm,kona-usb2-phy" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, bcm_kona_usb2_dt_ids); + +static struct platform_driver bcm_kona_usb2_driver = { + .probe = bcm_kona_usb2_probe, + .driver = { + .name = "bcm-kona-usb2", + .owner = THIS_MODULE, + .of_match_table = bcm_kona_usb2_dt_ids, + }, +}; + +module_platform_driver(bcm_kona_usb2_driver); + +MODULE_ALIAS("platform:bcm-kona-usb2"); +MODULE_AUTHOR("Matt Porter <mporter@linaro.org>"); +MODULE_DESCRIPTION("BCM Kona USB 2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c new file mode 100644 index 00000000000..49c44653010 --- /dev/null +++ b/drivers/phy/phy-core.c @@ -0,0 +1,830 @@ +/* + * phy-core.c -- Generic Phy framework. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Kishon Vijay Abraham I <kishon@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. + */ + +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/idr.h> +#include <linux/pm_runtime.h> + +static struct class *phy_class; +static DEFINE_MUTEX(phy_provider_mutex); +static LIST_HEAD(phy_provider_list); +static DEFINE_IDA(phy_ida); + +static void devm_phy_release(struct device *dev, void *res) +{ + struct phy *phy = *(struct phy **)res; + + phy_put(phy); +} + +static void devm_phy_provider_release(struct device *dev, void *res) +{ + struct phy_provider *phy_provider = *(struct phy_provider **)res; + + of_phy_provider_unregister(phy_provider); +} + +static void devm_phy_consume(struct device *dev, void *res) +{ + struct phy *phy = *(struct phy **)res; + + phy_destroy(phy); +} + +static int devm_phy_match(struct device *dev, void *res, void *match_data) +{ + return res == match_data; +} + +static struct phy *phy_lookup(struct device *device, const char *port) +{ + unsigned int count; + struct phy *phy; + struct device *dev; + struct phy_consumer *consumers; + struct class_dev_iter iter; + + class_dev_iter_init(&iter, phy_class, NULL, NULL); + while ((dev = class_dev_iter_next(&iter))) { + phy = to_phy(dev); + + if (!phy->init_data) + continue; + count = phy->init_data->num_consumers; + consumers = phy->init_data->consumers; + while (count--) { + if (!strcmp(consumers->dev_name, dev_name(device)) && + !strcmp(consumers->port, port)) { + class_dev_iter_exit(&iter); + return phy; + } + consumers++; + } + } + + class_dev_iter_exit(&iter); + return ERR_PTR(-ENODEV); +} + +static struct phy_provider *of_phy_provider_lookup(struct device_node *node) +{ + struct phy_provider *phy_provider; + + list_for_each_entry(phy_provider, &phy_provider_list, list) { + if (phy_provider->dev->of_node == node) + return phy_provider; + } + + return ERR_PTR(-EPROBE_DEFER); +} + +int phy_pm_runtime_get(struct phy *phy) +{ + int ret; + + if (!pm_runtime_enabled(&phy->dev)) + return -ENOTSUPP; + + ret = pm_runtime_get(&phy->dev); + if (ret < 0 && ret != -EINPROGRESS) + pm_runtime_put_noidle(&phy->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_pm_runtime_get); + +int phy_pm_runtime_get_sync(struct phy *phy) +{ + int ret; + + if (!pm_runtime_enabled(&phy->dev)) + return -ENOTSUPP; + + ret = pm_runtime_get_sync(&phy->dev); + if (ret < 0) + pm_runtime_put_sync(&phy->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_pm_runtime_get_sync); + +int phy_pm_runtime_put(struct phy *phy) +{ + if (!pm_runtime_enabled(&phy->dev)) + return -ENOTSUPP; + + return pm_runtime_put(&phy->dev); +} +EXPORT_SYMBOL_GPL(phy_pm_runtime_put); + +int phy_pm_runtime_put_sync(struct phy *phy) +{ + if (!pm_runtime_enabled(&phy->dev)) + return -ENOTSUPP; + + return pm_runtime_put_sync(&phy->dev); +} +EXPORT_SYMBOL_GPL(phy_pm_runtime_put_sync); + +void phy_pm_runtime_allow(struct phy *phy) +{ + if (!pm_runtime_enabled(&phy->dev)) + return; + + pm_runtime_allow(&phy->dev); +} +EXPORT_SYMBOL_GPL(phy_pm_runtime_allow); + +void phy_pm_runtime_forbid(struct phy *phy) +{ + if (!pm_runtime_enabled(&phy->dev)) + return; + + pm_runtime_forbid(&phy->dev); +} +EXPORT_SYMBOL_GPL(phy_pm_runtime_forbid); + +int phy_init(struct phy *phy) +{ + int ret; + + if (!phy) + return 0; + + ret = phy_pm_runtime_get_sync(phy); + if (ret < 0 && ret != -ENOTSUPP) + return ret; + + mutex_lock(&phy->mutex); + if (phy->init_count == 0 && phy->ops->init) { + ret = phy->ops->init(phy); + if (ret < 0) { + dev_err(&phy->dev, "phy init failed --> %d\n", ret); + goto out; + } + } else { + ret = 0; /* Override possible ret == -ENOTSUPP */ + } + ++phy->init_count; + +out: + mutex_unlock(&phy->mutex); + phy_pm_runtime_put(phy); + return ret; +} +EXPORT_SYMBOL_GPL(phy_init); + +int phy_exit(struct phy *phy) +{ + int ret; + + if (!phy) + return 0; + + ret = phy_pm_runtime_get_sync(phy); + if (ret < 0 && ret != -ENOTSUPP) + return ret; + + mutex_lock(&phy->mutex); + if (phy->init_count == 1 && phy->ops->exit) { + ret = phy->ops->exit(phy); + if (ret < 0) { + dev_err(&phy->dev, "phy exit failed --> %d\n", ret); + goto out; + } + } + --phy->init_count; + +out: + mutex_unlock(&phy->mutex); + phy_pm_runtime_put(phy); + return ret; +} +EXPORT_SYMBOL_GPL(phy_exit); + +int phy_power_on(struct phy *phy) +{ + int ret; + + if (!phy) + return 0; + + ret = phy_pm_runtime_get_sync(phy); + if (ret < 0 && ret != -ENOTSUPP) + return ret; + + mutex_lock(&phy->mutex); + if (phy->power_count == 0 && phy->ops->power_on) { + ret = phy->ops->power_on(phy); + if (ret < 0) { + dev_err(&phy->dev, "phy poweron failed --> %d\n", ret); + goto out; + } + } else { + ret = 0; /* Override possible ret == -ENOTSUPP */ + } + ++phy->power_count; + mutex_unlock(&phy->mutex); + return 0; + +out: + mutex_unlock(&phy->mutex); + phy_pm_runtime_put_sync(phy); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_power_on); + +int phy_power_off(struct phy *phy) +{ + int ret; + + if (!phy) + return 0; + + mutex_lock(&phy->mutex); + if (phy->power_count == 1 && phy->ops->power_off) { + ret = phy->ops->power_off(phy); + if (ret < 0) { + dev_err(&phy->dev, "phy poweroff failed --> %d\n", ret); + mutex_unlock(&phy->mutex); + return ret; + } + } + --phy->power_count; + mutex_unlock(&phy->mutex); + phy_pm_runtime_put(phy); + + return 0; +} +EXPORT_SYMBOL_GPL(phy_power_off); + +/** + * _of_phy_get() - lookup and obtain a reference to a phy by phandle + * @np: device_node for which to get the phy + * @index: the index of the phy + * + * Returns the phy associated with the given phandle value, + * after getting a refcount to it or -ENODEV if there is no such phy or + * -EPROBE_DEFER if there is a phandle to the phy, but the device is + * not yet loaded. This function uses of_xlate call back function provided + * while registering the phy_provider to find the phy instance. + */ +static struct phy *_of_phy_get(struct device_node *np, int index) +{ + int ret; + struct phy_provider *phy_provider; + struct phy *phy = NULL; + struct of_phandle_args args; + + ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", + index, &args); + if (ret) + return ERR_PTR(-ENODEV); + + mutex_lock(&phy_provider_mutex); + 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; + } + + phy = phy_provider->of_xlate(phy_provider->dev, &args); + module_put(phy_provider->owner); + +err0: + mutex_unlock(&phy_provider_mutex); + of_node_put(args.np); + + return phy; +} + +/** + * of_phy_get() - lookup and obtain a reference to a phy using a device_node. + * @np: device_node for which to get the phy + * @con_id: name of the phy from device's point of view + * + * Returns the phy driver, after getting a refcount to it; or + * -ENODEV if there is no such phy. The caller is responsible for + * calling phy_put() to release that count. + */ +struct phy *of_phy_get(struct device_node *np, const char *con_id) +{ + struct phy *phy = NULL; + int index = 0; + + if (con_id) + index = of_property_match_string(np, "phy-names", con_id); + + phy = _of_phy_get(np, index); + if (IS_ERR(phy)) + return phy; + + if (!try_module_get(phy->ops->owner)) + return ERR_PTR(-EPROBE_DEFER); + + get_device(&phy->dev); + + return phy; +} +EXPORT_SYMBOL_GPL(of_phy_get); + +/** + * phy_put() - release the PHY + * @phy: the phy returned by phy_get() + * + * Releases a refcount the caller received from phy_get(). + */ +void phy_put(struct phy *phy) +{ + if (!phy || IS_ERR(phy)) + return; + + module_put(phy->ops->owner); + put_device(&phy->dev); +} +EXPORT_SYMBOL_GPL(phy_put); + +/** + * devm_phy_put() - release the PHY + * @dev: device that wants to release this phy + * @phy: the phy returned by devm_phy_get() + * + * destroys the devres associated with this phy and invokes phy_put + * to release the phy. + */ +void devm_phy_put(struct device *dev, struct phy *phy) +{ + int r; + + if (!phy) + return; + + r = devres_destroy(dev, devm_phy_release, devm_phy_match, phy); + dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n"); +} +EXPORT_SYMBOL_GPL(devm_phy_put); + +/** + * of_phy_simple_xlate() - returns the phy instance from phy provider + * @dev: the PHY provider device + * @args: of_phandle_args (not used here) + * + * Intended to be used by phy provider for the common case where #phy-cells is + * 0. For other cases where #phy-cells is greater than '0', the phy provider + * should provide a custom of_xlate function that reads the *args* and returns + * the appropriate phy. + */ +struct phy *of_phy_simple_xlate(struct device *dev, struct of_phandle_args + *args) +{ + struct phy *phy; + struct class_dev_iter iter; + struct device_node *node = dev->of_node; + + class_dev_iter_init(&iter, phy_class, NULL, NULL); + while ((dev = class_dev_iter_next(&iter))) { + phy = to_phy(dev); + if (node != phy->dev.of_node) + continue; + + class_dev_iter_exit(&iter); + return phy; + } + + class_dev_iter_exit(&iter); + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_GPL(of_phy_simple_xlate); + +/** + * phy_get() - lookup and obtain a reference to a phy. + * @dev: device that requests this phy + * @string: the phy name as given in the dt data or the name of the controller + * port for non-dt case + * + * Returns the phy driver, after getting a refcount to it; or + * -ENODEV if there is no such phy. The caller is responsible for + * calling phy_put() to release that count. + */ +struct phy *phy_get(struct device *dev, const char *string) +{ + int index = 0; + struct phy *phy; + + if (string == NULL) { + dev_WARN(dev, "missing string\n"); + return ERR_PTR(-EINVAL); + } + + if (dev->of_node) { + index = of_property_match_string(dev->of_node, "phy-names", + string); + phy = _of_phy_get(dev->of_node, index); + } else { + phy = phy_lookup(dev, string); + } + if (IS_ERR(phy)) + return phy; + + if (!try_module_get(phy->ops->owner)) + return ERR_PTR(-EPROBE_DEFER); + + get_device(&phy->dev); + + return phy; +} +EXPORT_SYMBOL_GPL(phy_get); + +/** + * phy_optional_get() - lookup and obtain a reference to an optional phy. + * @dev: device that requests this phy + * @string: the phy name as given in the dt data or the name of the controller + * port for non-dt case + * + * Returns the phy driver, after getting a refcount to it; or + * NULL if there is no such phy. The caller is responsible for + * calling phy_put() to release that count. + */ +struct phy *phy_optional_get(struct device *dev, const char *string) +{ + struct phy *phy = phy_get(dev, string); + + if (PTR_ERR(phy) == -ENODEV) + phy = NULL; + + return phy; +} +EXPORT_SYMBOL_GPL(phy_optional_get); + +/** + * devm_phy_get() - lookup and obtain a reference to a phy. + * @dev: device that requests this phy + * @string: the phy name as given in the dt data or phy device name + * for non-dt case + * + * Gets the phy using 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_phy_get(struct device *dev, const char *string) +{ + struct phy **ptr, *phy; + + ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + phy = phy_get(dev, string); + if (!IS_ERR(phy)) { + *ptr = phy; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return phy; +} +EXPORT_SYMBOL_GPL(devm_phy_get); + +/** + * devm_phy_optional_get() - lookup and obtain a reference to an optional phy. + * @dev: device that requests this phy + * @string: the phy name as given in the dt data or phy device name + * for non-dt case + * + * Gets the phy using 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. This differs to devm_phy_get() in + * that if the phy does not exist, it is not considered an error and + * -ENODEV will not be returned. Instead the NULL phy is returned, + * which can be passed to all other phy consumer calls. + */ +struct phy *devm_phy_optional_get(struct device *dev, const char *string) +{ + struct phy *phy = devm_phy_get(dev, string); + + if (PTR_ERR(phy) == -ENODEV) + phy = NULL; + + return phy; +} +EXPORT_SYMBOL_GPL(devm_phy_optional_get); + +/** + * devm_of_phy_get() - lookup and obtain a reference to a phy. + * @dev: device that requests this phy + * @np: node containing the phy + * @con_id: name of the phy from device's point of view + * + * 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(struct device *dev, struct device_node *np, + const char *con_id) +{ + 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, con_id); + if (!IS_ERR(phy)) { + *ptr = phy; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return phy; +} +EXPORT_SYMBOL_GPL(devm_of_phy_get); + +/** + * phy_create() - create a new phy + * @dev: device that is creating the new phy + * @ops: function pointers for performing phy operations + * @init_data: contains the list of PHY consumers or NULL + * + * Called to create a phy using phy framework. + */ +struct phy *phy_create(struct device *dev, const struct phy_ops *ops, + struct phy_init_data *init_data) +{ + int ret; + int id; + struct phy *phy; + + if (WARN_ON(!dev)) + return ERR_PTR(-EINVAL); + + phy = kzalloc(sizeof(*phy), GFP_KERNEL); + if (!phy) + return ERR_PTR(-ENOMEM); + + id = ida_simple_get(&phy_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + dev_err(dev, "unable to get id\n"); + ret = id; + goto free_phy; + } + + device_initialize(&phy->dev); + mutex_init(&phy->mutex); + + phy->dev.class = phy_class; + phy->dev.parent = dev; + phy->dev.of_node = dev->of_node; + phy->id = id; + phy->ops = ops; + phy->init_data = init_data; + + ret = dev_set_name(&phy->dev, "phy-%s.%d", dev_name(dev), id); + if (ret) + goto put_dev; + + ret = device_add(&phy->dev); + if (ret) + goto put_dev; + + if (pm_runtime_enabled(dev)) { + pm_runtime_enable(&phy->dev); + pm_runtime_no_callbacks(&phy->dev); + } + + return phy; + +put_dev: + put_device(&phy->dev); /* calls phy_release() which frees resources */ + return ERR_PTR(ret); + +free_phy: + kfree(phy); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(phy_create); + +/** + * devm_phy_create() - create a new phy + * @dev: device that is creating the new phy + * @ops: function pointers for performing phy operations + * @init_data: contains the list of PHY consumers or NULL + * + * Creates a new PHY device adding it to the PHY class. + * While at that, it also associates the device with the phy using devres. + * On driver detach, release function is invoked on the devres data, + * then, devres data is freed. + */ +struct phy *devm_phy_create(struct device *dev, const struct phy_ops *ops, + struct phy_init_data *init_data) +{ + struct phy **ptr, *phy; + + ptr = devres_alloc(devm_phy_consume, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + phy = phy_create(dev, ops, init_data); + if (!IS_ERR(phy)) { + *ptr = phy; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return phy; +} +EXPORT_SYMBOL_GPL(devm_phy_create); + +/** + * phy_destroy() - destroy the phy + * @phy: the phy to be destroyed + * + * Called to destroy the phy. + */ +void phy_destroy(struct phy *phy) +{ + pm_runtime_disable(&phy->dev); + device_unregister(&phy->dev); +} +EXPORT_SYMBOL_GPL(phy_destroy); + +/** + * devm_phy_destroy() - destroy the PHY + * @dev: device that wants to release this phy + * @phy: the phy returned by devm_phy_get() + * + * destroys the devres associated with this phy and invokes phy_destroy + * to destroy the phy. + */ +void devm_phy_destroy(struct device *dev, struct phy *phy) +{ + int r; + + r = devres_destroy(dev, devm_phy_consume, devm_phy_match, phy); + dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n"); +} +EXPORT_SYMBOL_GPL(devm_phy_destroy); + +/** + * __of_phy_provider_register() - create/register phy provider with the framework + * @dev: struct device of the phy provider + * @owner: the module owner containing of_xlate + * @of_xlate: function pointer to obtain phy instance from phy provider + * + * Creates struct phy_provider from dev and of_xlate function pointer. + * This is used in the case of dt boot for finding the phy instance from + * phy provider. + */ +struct phy_provider *__of_phy_provider_register(struct device *dev, + struct module *owner, struct phy * (*of_xlate)(struct device *dev, + struct of_phandle_args *args)) +{ + struct phy_provider *phy_provider; + + phy_provider = kzalloc(sizeof(*phy_provider), GFP_KERNEL); + if (!phy_provider) + return ERR_PTR(-ENOMEM); + + phy_provider->dev = dev; + phy_provider->owner = owner; + phy_provider->of_xlate = of_xlate; + + mutex_lock(&phy_provider_mutex); + list_add_tail(&phy_provider->list, &phy_provider_list); + mutex_unlock(&phy_provider_mutex); + + return phy_provider; +} +EXPORT_SYMBOL_GPL(__of_phy_provider_register); + +/** + * __devm_of_phy_provider_register() - create/register phy provider with the + * framework + * @dev: struct device of the phy provider + * @owner: the module owner containing of_xlate + * @of_xlate: function pointer to obtain phy instance from phy provider + * + * Creates struct phy_provider from dev and of_xlate function pointer. + * This is used in the case of dt boot for finding the phy instance from + * phy provider. While at that, it also associates the device with the + * phy provider using devres. On driver detach, release function is invoked + * on the devres data, then, devres data is freed. + */ +struct phy_provider *__devm_of_phy_provider_register(struct device *dev, + struct module *owner, struct phy * (*of_xlate)(struct device *dev, + struct of_phandle_args *args)) +{ + struct phy_provider **ptr, *phy_provider; + + ptr = devres_alloc(devm_phy_provider_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + phy_provider = __of_phy_provider_register(dev, owner, of_xlate); + if (!IS_ERR(phy_provider)) { + *ptr = phy_provider; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return phy_provider; +} +EXPORT_SYMBOL_GPL(__devm_of_phy_provider_register); + +/** + * of_phy_provider_unregister() - unregister phy provider from the framework + * @phy_provider: phy provider returned by of_phy_provider_register() + * + * Removes the phy_provider created using of_phy_provider_register(). + */ +void of_phy_provider_unregister(struct phy_provider *phy_provider) +{ + if (IS_ERR(phy_provider)) + return; + + mutex_lock(&phy_provider_mutex); + list_del(&phy_provider->list); + kfree(phy_provider); + mutex_unlock(&phy_provider_mutex); +} +EXPORT_SYMBOL_GPL(of_phy_provider_unregister); + +/** + * devm_of_phy_provider_unregister() - remove phy provider from the framework + * @dev: struct device of the phy provider + * + * destroys the devres associated with this phy provider and invokes + * of_phy_provider_unregister to unregister the phy provider. + */ +void devm_of_phy_provider_unregister(struct device *dev, + struct phy_provider *phy_provider) { + int r; + + r = devres_destroy(dev, devm_phy_provider_release, devm_phy_match, + phy_provider); + dev_WARN_ONCE(dev, r, "couldn't find PHY provider device resource\n"); +} +EXPORT_SYMBOL_GPL(devm_of_phy_provider_unregister); + +/** + * phy_release() - release the phy + * @dev: the dev member within phy + * + * When the last reference to the device is removed, it is called + * from the embedded kobject as release method. + */ +static void phy_release(struct device *dev) +{ + struct phy *phy; + + phy = to_phy(dev); + dev_vdbg(dev, "releasing '%s'\n", dev_name(dev)); + ida_simple_remove(&phy_ida, phy->id); + kfree(phy); +} + +static int __init phy_core_init(void) +{ + phy_class = class_create(THIS_MODULE, "phy"); + if (IS_ERR(phy_class)) { + pr_err("failed to create phy class --> %ld\n", + PTR_ERR(phy_class)); + return PTR_ERR(phy_class); + } + + phy_class->dev_release = phy_release; + + return 0; +} +module_init(phy_core_init); + +static void __exit phy_core_exit(void) +{ + class_destroy(phy_class); +} +module_exit(phy_core_exit); + +MODULE_DESCRIPTION("Generic PHY Framework"); +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-exynos-dp-video.c b/drivers/phy/phy-exynos-dp-video.c new file mode 100644 index 00000000000..0786fef842e --- /dev/null +++ b/drivers/phy/phy-exynos-dp-video.c @@ -0,0 +1,111 @@ +/* + * Samsung EXYNOS SoC series Display Port PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.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/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +/* DPTX_PHY_CONTROL register */ +#define EXYNOS_DPTX_PHY_ENABLE (1 << 0) + +struct exynos_dp_video_phy { + void __iomem *regs; +}; + +static int __set_phy_state(struct exynos_dp_video_phy *state, unsigned int on) +{ + u32 reg; + + reg = readl(state->regs); + if (on) + reg |= EXYNOS_DPTX_PHY_ENABLE; + else + reg &= ~EXYNOS_DPTX_PHY_ENABLE; + writel(reg, state->regs); + + return 0; +} + +static int exynos_dp_video_phy_power_on(struct phy *phy) +{ + struct exynos_dp_video_phy *state = phy_get_drvdata(phy); + + return __set_phy_state(state, 1); +} + +static int exynos_dp_video_phy_power_off(struct phy *phy) +{ + struct exynos_dp_video_phy *state = phy_get_drvdata(phy); + + return __set_phy_state(state, 0); +} + +static struct phy_ops exynos_dp_video_phy_ops = { + .power_on = exynos_dp_video_phy_power_on, + .power_off = exynos_dp_video_phy_power_off, + .owner = THIS_MODULE, +}; + +static int exynos_dp_video_phy_probe(struct platform_device *pdev) +{ + struct exynos_dp_video_phy *state; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy_provider *phy_provider; + struct phy *phy; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + state->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(state->regs)) + return PTR_ERR(state->regs); + + phy = devm_phy_create(dev, &exynos_dp_video_phy_ops, NULL); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create Display Port PHY\n"); + return PTR_ERR(phy); + } + phy_set_drvdata(phy, state); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + return 0; +} + +static const struct of_device_id exynos_dp_video_phy_of_match[] = { + { .compatible = "samsung,exynos5250-dp-video-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_dp_video_phy_of_match); + +static struct platform_driver exynos_dp_video_phy_driver = { + .probe = exynos_dp_video_phy_probe, + .driver = { + .name = "exynos-dp-video-phy", + .owner = THIS_MODULE, + .of_match_table = exynos_dp_video_phy_of_match, + } +}; +module_platform_driver(exynos_dp_video_phy_driver); + +MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); +MODULE_DESCRIPTION("Samsung EXYNOS SoC DP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-exynos-mipi-video.c b/drivers/phy/phy-exynos-mipi-video.c new file mode 100644 index 00000000000..ff026689358 --- /dev/null +++ b/drivers/phy/phy-exynos-mipi-video.c @@ -0,0 +1,176 @@ +/* + * Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki <s.nawrocki@samsung.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/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +/* MIPI_PHYn_CONTROL register offset: n = 0..1 */ +#define EXYNOS_MIPI_PHY_CONTROL(n) ((n) * 4) +#define EXYNOS_MIPI_PHY_ENABLE (1 << 0) +#define EXYNOS_MIPI_PHY_SRESETN (1 << 1) +#define EXYNOS_MIPI_PHY_MRESETN (1 << 2) +#define EXYNOS_MIPI_PHY_RESET_MASK (3 << 1) + +enum exynos_mipi_phy_id { + EXYNOS_MIPI_PHY_ID_CSIS0, + EXYNOS_MIPI_PHY_ID_DSIM0, + EXYNOS_MIPI_PHY_ID_CSIS1, + EXYNOS_MIPI_PHY_ID_DSIM1, + EXYNOS_MIPI_PHYS_NUM +}; + +#define is_mipi_dsim_phy_id(id) \ + ((id) == EXYNOS_MIPI_PHY_ID_DSIM0 || (id) == EXYNOS_MIPI_PHY_ID_DSIM1) + +struct exynos_mipi_video_phy { + spinlock_t slock; + struct video_phy_desc { + struct phy *phy; + unsigned int index; + } phys[EXYNOS_MIPI_PHYS_NUM]; + void __iomem *regs; +}; + +static int __set_phy_state(struct exynos_mipi_video_phy *state, + enum exynos_mipi_phy_id id, unsigned int on) +{ + void __iomem *addr; + u32 reg, reset; + + addr = state->regs + EXYNOS_MIPI_PHY_CONTROL(id / 2); + + if (is_mipi_dsim_phy_id(id)) + reset = EXYNOS_MIPI_PHY_MRESETN; + else + reset = EXYNOS_MIPI_PHY_SRESETN; + + spin_lock(&state->slock); + reg = readl(addr); + if (on) + reg |= reset; + else + reg &= ~reset; + writel(reg, addr); + + /* Clear ENABLE bit only if MRESETN, SRESETN bits are not set. */ + if (on) + reg |= EXYNOS_MIPI_PHY_ENABLE; + else if (!(reg & EXYNOS_MIPI_PHY_RESET_MASK)) + reg &= ~EXYNOS_MIPI_PHY_ENABLE; + + writel(reg, addr); + spin_unlock(&state->slock); + return 0; +} + +#define to_mipi_video_phy(desc) \ + container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index]); + +static int exynos_mipi_video_phy_power_on(struct phy *phy) +{ + struct video_phy_desc *phy_desc = phy_get_drvdata(phy); + struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); + + return __set_phy_state(state, phy_desc->index, 1); +} + +static int exynos_mipi_video_phy_power_off(struct phy *phy) +{ + struct video_phy_desc *phy_desc = phy_get_drvdata(phy); + struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); + + return __set_phy_state(state, phy_desc->index, 0); +} + +static struct phy *exynos_mipi_video_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct exynos_mipi_video_phy *state = dev_get_drvdata(dev); + + if (WARN_ON(args->args[0] >= EXYNOS_MIPI_PHYS_NUM)) + return ERR_PTR(-ENODEV); + + return state->phys[args->args[0]].phy; +} + +static struct phy_ops exynos_mipi_video_phy_ops = { + .power_on = exynos_mipi_video_phy_power_on, + .power_off = exynos_mipi_video_phy_power_off, + .owner = THIS_MODULE, +}; + +static int exynos_mipi_video_phy_probe(struct platform_device *pdev) +{ + struct exynos_mipi_video_phy *state; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy_provider *phy_provider; + unsigned int i; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + state->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(state->regs)) + return PTR_ERR(state->regs); + + dev_set_drvdata(dev, state); + spin_lock_init(&state->slock); + + for (i = 0; i < EXYNOS_MIPI_PHYS_NUM; i++) { + struct phy *phy = devm_phy_create(dev, + &exynos_mipi_video_phy_ops, NULL); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY %d\n", i); + return PTR_ERR(phy); + } + + state->phys[i].phy = phy; + state->phys[i].index = i; + phy_set_drvdata(phy, &state->phys[i]); + } + + phy_provider = devm_of_phy_provider_register(dev, + exynos_mipi_video_phy_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + return 0; +} + +static const struct of_device_id exynos_mipi_video_phy_of_match[] = { + { .compatible = "samsung,s5pv210-mipi-video-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_mipi_video_phy_of_match); + +static struct platform_driver exynos_mipi_video_phy_driver = { + .probe = exynos_mipi_video_phy_probe, + .driver = { + .of_match_table = exynos_mipi_video_phy_of_match, + .name = "exynos-mipi-video-phy", + .owner = THIS_MODULE, + } +}; +module_platform_driver(exynos_mipi_video_phy_driver); + +MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI CSI-2/DSI PHY driver"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-exynos4210-usb2.c b/drivers/phy/phy-exynos4210-usb2.c new file mode 100644 index 00000000000..236a52ad94e --- /dev/null +++ b/drivers/phy/phy-exynos4210-usb2.c @@ -0,0 +1,261 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4210 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski <k.debski@samsung.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/delay.h> +#include <linux/io.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define EXYNOS_4210_UPHYPWR 0x0 + +#define EXYNOS_4210_UPHYPWR_PHY0_SUSPEND BIT(0) +#define EXYNOS_4210_UPHYPWR_PHY0_PWR BIT(3) +#define EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define EXYNOS_4210_UPHYPWR_PHY0_SLEEP BIT(5) +#define EXYNOS_4210_UPHYPWR_PHY0 ( \ + EXYNOS_4210_UPHYPWR_PHY0_SUSPEND | \ + EXYNOS_4210_UPHYPWR_PHY0_PWR | \ + EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR | \ + EXYNOS_4210_UPHYPWR_PHY0_SLEEP) + +#define EXYNOS_4210_UPHYPWR_PHY1_SUSPEND BIT(6) +#define EXYNOS_4210_UPHYPWR_PHY1_PWR BIT(7) +#define EXYNOS_4210_UPHYPWR_PHY1_SLEEP BIT(8) +#define EXYNOS_4210_UPHYPWR_PHY1 ( \ + EXYNOS_4210_UPHYPWR_PHY1_SUSPEND | \ + EXYNOS_4210_UPHYPWR_PHY1_PWR | \ + EXYNOS_4210_UPHYPWR_PHY1_SLEEP) + +#define EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND BIT(9) +#define EXYNOS_4210_UPHYPWR_HSIC0_SLEEP BIT(10) +#define EXYNOS_4210_UPHYPWR_HSIC0 ( \ + EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND | \ + EXYNOS_4210_UPHYPWR_HSIC0_SLEEP) + +#define EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND BIT(11) +#define EXYNOS_4210_UPHYPWR_HSIC1_SLEEP BIT(12) +#define EXYNOS_4210_UPHYPWR_HSIC1 ( \ + EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND | \ + EXYNOS_4210_UPHYPWR_HSIC1_SLEEP) + +/* PHY clock control */ +#define EXYNOS_4210_UPHYCLK 0x4 + +#define EXYNOS_4210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET 0 +#define EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) + +#define EXYNOS_4210_UPHYCLK_PHY0_ID_PULLUP BIT(2) +#define EXYNOS_4210_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define EXYNOS_4210_UPHYCLK_PHY1_COMMON_ON BIT(7) + +/* PHY reset control */ +#define EXYNOS_4210_UPHYRST 0x8 + +#define EXYNOS_4210_URSTCON_PHY0 BIT(0) +#define EXYNOS_4210_URSTCON_OTG_HLINK BIT(1) +#define EXYNOS_4210_URSTCON_OTG_PHYLINK BIT(2) +#define EXYNOS_4210_URSTCON_PHY1_ALL BIT(3) +#define EXYNOS_4210_URSTCON_PHY1_P0 BIT(4) +#define EXYNOS_4210_URSTCON_PHY1_P1P2 BIT(5) +#define EXYNOS_4210_URSTCON_HOST_LINK_ALL BIT(6) +#define EXYNOS_4210_URSTCON_HOST_LINK_P0 BIT(7) +#define EXYNOS_4210_URSTCON_HOST_LINK_P1 BIT(8) +#define EXYNOS_4210_URSTCON_HOST_LINK_P2 BIT(9) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_4210_USB_ISOL_DEVICE_OFFSET 0x704 +#define EXYNOS_4210_USB_ISOL_DEVICE BIT(0) +#define EXYNOS_4210_USB_ISOL_HOST_OFFSET 0x708 +#define EXYNOS_4210_USB_ISOL_HOST BIT(0) + +/* USBYPHY1 Floating prevention */ +#define EXYNOS_4210_UPHY1CON 0x34 +#define EXYNOS_4210_UPHY1CON_FLOAT_PREVENTION 0x1 + +/* Mode switching SUB Device <-> Host */ +#define EXYNOS_4210_MODE_SWITCH_OFFSET 0x21c +#define EXYNOS_4210_MODE_SWITCH_MASK 1 +#define EXYNOS_4210_MODE_SWITCH_DEVICE 0 +#define EXYNOS_4210_MODE_SWITCH_HOST 1 + +enum exynos4210_phy_id { + EXYNOS4210_DEVICE, + EXYNOS4210_HOST, + EXYNOS4210_HSIC0, + EXYNOS4210_HSIC1, + EXYNOS4210_NUM_PHYS, +}; + +/* + * exynos4210_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos4210_rate_to_clk(unsigned long rate, u32 *reg) +{ + switch (rate) { + case 12 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ; + break; + case 48 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos4210_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS4210_DEVICE: + offset = EXYNOS_4210_USB_ISOL_DEVICE_OFFSET; + mask = EXYNOS_4210_USB_ISOL_DEVICE; + break; + case EXYNOS4210_HOST: + offset = EXYNOS_4210_USB_ISOL_HOST_OFFSET; + mask = EXYNOS_4210_USB_ISOL_HOST; + break; + default: + return; + }; + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static void exynos4210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 rstbits = 0; + u32 phypwr = 0; + u32 rst; + u32 pwr; + u32 clk; + + switch (inst->cfg->id) { + case EXYNOS4210_DEVICE: + phypwr = EXYNOS_4210_UPHYPWR_PHY0; + rstbits = EXYNOS_4210_URSTCON_PHY0; + break; + case EXYNOS4210_HOST: + phypwr = EXYNOS_4210_UPHYPWR_PHY1; + rstbits = EXYNOS_4210_URSTCON_PHY1_ALL | + EXYNOS_4210_URSTCON_PHY1_P0 | + EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_ALL | + EXYNOS_4210_URSTCON_HOST_LINK_P0; + writel(on, drv->reg_phy + EXYNOS_4210_UPHY1CON); + break; + case EXYNOS4210_HSIC0: + phypwr = EXYNOS_4210_UPHYPWR_HSIC0; + rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_P1; + break; + case EXYNOS4210_HSIC1: + phypwr = EXYNOS_4210_UPHYPWR_HSIC1; + rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_P2; + break; + }; + + if (on) { + clk = readl(drv->reg_phy + EXYNOS_4210_UPHYCLK); + clk &= ~EXYNOS_4210_UPHYCLK_PHYFSEL_MASK; + clk |= drv->ref_reg_val << EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET; + writel(clk, drv->reg_phy + EXYNOS_4210_UPHYCLK); + + pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); + + rst = readl(drv->reg_phy + EXYNOS_4210_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + } else { + pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); + } +} + +static int exynos4210_power_on(struct samsung_usb2_phy_instance *inst) +{ + /* Order of initialisation is important - first power then isolation */ + exynos4210_phy_pwr(inst, 1); + exynos4210_isol(inst, 0); + + return 0; +} + +static int exynos4210_power_off(struct samsung_usb2_phy_instance *inst) +{ + exynos4210_isol(inst, 1); + exynos4210_phy_pwr(inst, 0); + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos4210_phys[] = { + { + .label = "device", + .id = EXYNOS4210_DEVICE, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "host", + .id = EXYNOS4210_HOST, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS4210_HSIC0, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS4210_HSIC1, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + {}, +}; + +const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = { + .has_mode_switch = 0, + .num_phys = EXYNOS4210_NUM_PHYS, + .phys = exynos4210_phys, + .rate_to_clk = exynos4210_rate_to_clk, +}; diff --git a/drivers/phy/phy-exynos4x12-usb2.c b/drivers/phy/phy-exynos4x12-usb2.c new file mode 100644 index 00000000000..d92a7cc5698 --- /dev/null +++ b/drivers/phy/phy-exynos4x12-usb2.c @@ -0,0 +1,328 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4x12 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski <k.debski@samsung.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/delay.h> +#include <linux/io.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define EXYNOS_4x12_UPHYPWR 0x0 + +#define EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND BIT(0) +#define EXYNOS_4x12_UPHYPWR_PHY0_PWR BIT(3) +#define EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define EXYNOS_4x12_UPHYPWR_PHY0_SLEEP BIT(5) +#define EXYNOS_4x12_UPHYPWR_PHY0 ( \ + EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_PHY0_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY0_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND BIT(6) +#define EXYNOS_4x12_UPHYPWR_PHY1_PWR BIT(7) +#define EXYNOS_4x12_UPHYPWR_PHY1_SLEEP BIT(8) +#define EXYNOS_4x12_UPHYPWR_PHY1 ( \ + EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_PHY1_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY1_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND BIT(9) +#define EXYNOS_4x12_UPHYPWR_HSIC0_PWR BIT(10) +#define EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP BIT(11) +#define EXYNOS_4x12_UPHYPWR_HSIC0 ( \ + EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_HSIC0_PWR | \ + EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND BIT(12) +#define EXYNOS_4x12_UPHYPWR_HSIC1_PWR BIT(13) +#define EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP BIT(14) +#define EXYNOS_4x12_UPHYPWR_HSIC1 ( \ + EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_HSIC1_PWR | \ + EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP) + +/* PHY clock control */ +#define EXYNOS_4x12_UPHYCLK 0x4 + +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK (0x7 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET 0 +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6 (0x0 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ (0x1 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2 (0x3 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ (0x4 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ (0x5 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ (0x7 << 0) + +#define EXYNOS_4x12_UPHYCLK_PHY0_ID_PULLUP BIT(3) +#define EXYNOS_4x12_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON BIT(7) + +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_MASK (0x7f << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_OFFSET 10 +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_12MHZ (0x24 << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_15MHZ (0x1c << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_16MHZ (0x1a << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_19MHZ2 (0x15 << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_20MHZ (0x14 << 10) + +/* PHY reset control */ +#define EXYNOS_4x12_UPHYRST 0x8 + +#define EXYNOS_4x12_URSTCON_PHY0 BIT(0) +#define EXYNOS_4x12_URSTCON_OTG_HLINK BIT(1) +#define EXYNOS_4x12_URSTCON_OTG_PHYLINK BIT(2) +#define EXYNOS_4x12_URSTCON_HOST_PHY BIT(3) +#define EXYNOS_4x12_URSTCON_PHY1 BIT(4) +#define EXYNOS_4x12_URSTCON_HSIC0 BIT(5) +#define EXYNOS_4x12_URSTCON_HSIC1 BIT(6) +#define EXYNOS_4x12_URSTCON_HOST_LINK_ALL BIT(7) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P0 BIT(8) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P1 BIT(9) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P2 BIT(10) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_4x12_USB_ISOL_OFFSET 0x704 +#define EXYNOS_4x12_USB_ISOL_OTG BIT(0) +#define EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET 0x708 +#define EXYNOS_4x12_USB_ISOL_HSIC0 BIT(0) +#define EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET 0x70c +#define EXYNOS_4x12_USB_ISOL_HSIC1 BIT(0) + +/* Mode switching SUB Device <-> Host */ +#define EXYNOS_4x12_MODE_SWITCH_OFFSET 0x21c +#define EXYNOS_4x12_MODE_SWITCH_MASK 1 +#define EXYNOS_4x12_MODE_SWITCH_DEVICE 0 +#define EXYNOS_4x12_MODE_SWITCH_HOST 1 + +enum exynos4x12_phy_id { + EXYNOS4x12_DEVICE, + EXYNOS4x12_HOST, + EXYNOS4x12_HSIC0, + EXYNOS4x12_HSIC1, + EXYNOS4x12_NUM_PHYS, +}; + +/* + * exynos4x12_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos4x12_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos4x12_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS4x12_DEVICE: + case EXYNOS4x12_HOST: + offset = EXYNOS_4x12_USB_ISOL_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_OTG; + break; + case EXYNOS4x12_HSIC0: + offset = EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_HSIC0; + break; + case EXYNOS4x12_HSIC1: + offset = EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_HSIC1; + break; + default: + return; + }; + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static void exynos4x12_setup_clk(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 clk; + + clk = readl(drv->reg_phy + EXYNOS_4x12_UPHYCLK); + clk &= ~EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK; + clk |= drv->ref_reg_val << EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET; + writel(clk, drv->reg_phy + EXYNOS_4x12_UPHYCLK); +} + +static void exynos4x12_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 rstbits = 0; + u32 phypwr = 0; + u32 rst; + u32 pwr; + u32 mode = 0; + u32 switch_mode = 0; + + switch (inst->cfg->id) { + case EXYNOS4x12_DEVICE: + phypwr = EXYNOS_4x12_UPHYPWR_PHY0; + rstbits = EXYNOS_4x12_URSTCON_PHY0; + mode = EXYNOS_4x12_MODE_SWITCH_DEVICE; + switch_mode = 1; + break; + case EXYNOS4x12_HOST: + phypwr = EXYNOS_4x12_UPHYPWR_PHY1; + rstbits = EXYNOS_4x12_URSTCON_HOST_PHY; + mode = EXYNOS_4x12_MODE_SWITCH_HOST; + switch_mode = 1; + break; + case EXYNOS4x12_HSIC0: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC0; + rstbits = EXYNOS_4x12_URSTCON_HSIC1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P0 | + EXYNOS_4x12_URSTCON_HOST_PHY; + break; + case EXYNOS4x12_HSIC1: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC1; + rstbits = EXYNOS_4x12_URSTCON_HSIC1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P1; + break; + }; + + if (on) { + if (switch_mode) + regmap_update_bits(drv->reg_sys, + EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, mode); + + pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); + + rst = readl(drv->reg_phy + EXYNOS_4x12_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + } else { + pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); + } +} + +static int exynos4x12_power_on(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + + inst->enabled = 1; + exynos4x12_setup_clk(inst); + exynos4x12_phy_pwr(inst, 1); + exynos4x12_isol(inst, 0); + + /* Power on the device, as it is necessary for HSIC to work */ + if (inst->cfg->id == EXYNOS4x12_HSIC0) { + struct samsung_usb2_phy_instance *device = + &drv->instances[EXYNOS4x12_DEVICE]; + exynos4x12_phy_pwr(device, 1); + exynos4x12_isol(device, 0); + } + + return 0; +} + +static int exynos4x12_power_off(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + struct samsung_usb2_phy_instance *device = + &drv->instances[EXYNOS4x12_DEVICE]; + + inst->enabled = 0; + exynos4x12_isol(inst, 1); + exynos4x12_phy_pwr(inst, 0); + + if (inst->cfg->id == EXYNOS4x12_HSIC0 && !device->enabled) { + exynos4x12_isol(device, 1); + exynos4x12_phy_pwr(device, 0); + } + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos4x12_phys[] = { + { + .label = "device", + .id = EXYNOS4x12_DEVICE, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "host", + .id = EXYNOS4x12_HOST, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS4x12_HSIC0, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS4x12_HSIC1, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + {}, +}; + +const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config = { + .has_mode_switch = 1, + .num_phys = EXYNOS4x12_NUM_PHYS, + .phys = exynos4x12_phys, + .rate_to_clk = exynos4x12_rate_to_clk, +}; diff --git a/drivers/phy/phy-exynos5-usbdrd.c b/drivers/phy/phy-exynos5-usbdrd.c new file mode 100644 index 00000000000..76d862b2202 --- /dev/null +++ b/drivers/phy/phy-exynos5-usbdrd.c @@ -0,0 +1,676 @@ +/* + * Samsung EXYNOS5 SoC series USB DRD PHY driver + * + * Phy provider for USB 3.0 DRD controller on Exynos5 SoC series + * + * Copyright (C) 2014 Samsung Electronics Co., Ltd. + * Author: Vivek Gautam <gautam.vivek@samsung.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/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/exynos5-pmu.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +/* Exynos USB PHY registers */ +#define EXYNOS5_FSEL_9MHZ6 0x0 +#define EXYNOS5_FSEL_10MHZ 0x1 +#define EXYNOS5_FSEL_12MHZ 0x2 +#define EXYNOS5_FSEL_19MHZ2 0x3 +#define EXYNOS5_FSEL_20MHZ 0x4 +#define EXYNOS5_FSEL_24MHZ 0x5 +#define EXYNOS5_FSEL_50MHZ 0x7 + +/* EXYNOS5: USB 3.0 DRD PHY registers */ +#define EXYNOS5_DRD_LINKSYSTEM 0x04 + +#define LINKSYSTEM_FLADJ_MASK (0x3f << 1) +#define LINKSYSTEM_FLADJ(_x) ((_x) << 1) +#define LINKSYSTEM_XHCI_VERSION_CONTROL BIT(27) + +#define EXYNOS5_DRD_PHYUTMI 0x08 + +#define PHYUTMI_OTGDISABLE BIT(6) +#define PHYUTMI_FORCESUSPEND BIT(1) +#define PHYUTMI_FORCESLEEP BIT(0) + +#define EXYNOS5_DRD_PHYPIPE 0x0c + +#define EXYNOS5_DRD_PHYCLKRST 0x10 + +#define PHYCLKRST_EN_UTMISUSPEND BIT(31) + +#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23) +#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23) + +#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21) +#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21) + +#define PHYCLKRST_SSC_EN BIT(20) +#define PHYCLKRST_REF_SSP_EN BIT(19) +#define PHYCLKRST_REF_CLKDIV2 BIT(18) + +#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x32 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11) + +#define PHYCLKRST_FSEL_UTMI_MASK (0x7 << 5) +#define PHYCLKRST_FSEL_PIPE_MASK (0x7 << 8) +#define PHYCLKRST_FSEL(_x) ((_x) << 5) +#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5) +#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5) +#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5) +#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5) + +#define PHYCLKRST_RETENABLEN BIT(4) + +#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2) +#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2) +#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2) + +#define PHYCLKRST_PORTRESET BIT(1) +#define PHYCLKRST_COMMONONN BIT(0) + +#define EXYNOS5_DRD_PHYREG0 0x14 +#define EXYNOS5_DRD_PHYREG1 0x18 + +#define EXYNOS5_DRD_PHYPARAM0 0x1c + +#define PHYPARAM0_REF_USE_PAD BIT(31) +#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26) +#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26) + +#define EXYNOS5_DRD_PHYPARAM1 0x20 + +#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0) +#define PHYPARAM1_PCS_TXDEEMPH (0x1c) + +#define EXYNOS5_DRD_PHYTERM 0x24 + +#define EXYNOS5_DRD_PHYTEST 0x28 + +#define PHYTEST_POWERDOWN_SSP BIT(3) +#define PHYTEST_POWERDOWN_HSP BIT(2) + +#define EXYNOS5_DRD_PHYADP 0x2c + +#define EXYNOS5_DRD_PHYUTMICLKSEL 0x30 + +#define PHYUTMICLKSEL_UTMI_CLKSEL BIT(2) + +#define EXYNOS5_DRD_PHYRESUME 0x34 +#define EXYNOS5_DRD_LINKPORT 0x44 + +#define KHZ 1000 +#define MHZ (KHZ * KHZ) + +enum exynos5_usbdrd_phy_id { + EXYNOS5_DRDPHY_UTMI, + EXYNOS5_DRDPHY_PIPE3, + EXYNOS5_DRDPHYS_NUM, +}; + +struct phy_usb_instance; +struct exynos5_usbdrd_phy; + +struct exynos5_usbdrd_phy_config { + u32 id; + void (*phy_isol)(struct phy_usb_instance *inst, u32 on); + void (*phy_init)(struct exynos5_usbdrd_phy *phy_drd); + unsigned int (*set_refclk)(struct phy_usb_instance *inst); +}; + +struct exynos5_usbdrd_phy_drvdata { + const struct exynos5_usbdrd_phy_config *phy_cfg; + u32 pmu_offset_usbdrd0_phy; + u32 pmu_offset_usbdrd1_phy; +}; + +/** + * struct exynos5_usbdrd_phy - driver data for USB 3.0 PHY + * @dev: pointer to device instance of this platform device + * @reg_phy: usb phy controller register memory base + * @clk: phy clock for register access + * @drv_data: pointer to SoC level driver data structure + * @phys[]: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY + * instances each with its 'phy' and 'phy_cfg'. + * @extrefclk: frequency select settings when using 'separate + * reference clocks' for SS and HS operations + * @ref_clk: reference clock to PHY block from which PHY's + * operational clocks are derived + * @ref_rate: rate of above reference clock + */ +struct exynos5_usbdrd_phy { + struct device *dev; + void __iomem *reg_phy; + struct clk *clk; + const struct exynos5_usbdrd_phy_drvdata *drv_data; + struct phy_usb_instance { + struct phy *phy; + u32 index; + struct regmap *reg_pmu; + u32 pmu_offset; + const struct exynos5_usbdrd_phy_config *phy_cfg; + } phys[EXYNOS5_DRDPHYS_NUM]; + u32 extrefclk; + struct clk *ref_clk; + struct regulator *vbus; +}; + +static inline +struct exynos5_usbdrd_phy *to_usbdrd_phy(struct phy_usb_instance *inst) +{ + return container_of((inst), struct exynos5_usbdrd_phy, + phys[(inst)->index]); +} + +/* + * exynos5_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static unsigned int exynos5_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS5_FSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS5_FSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS5_FSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS5_FSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS5_FSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS5_FSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS5_FSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS5_FSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst, + unsigned int on) +{ + unsigned int val; + + if (!inst->reg_pmu) + return; + + val = on ? 0 : EXYNOS5_PHY_ENABLE; + + regmap_update_bits(inst->reg_pmu, inst->pmu_offset, + EXYNOS5_PHY_ENABLE, val); +} + +/* + * Sets the pipe3 phy's clk as EXTREFCLK (XXTI) which is internal clock + * from clock core. Further sets multiplier values and spread spectrum + * clock settings for SuperSpeed operations. + */ +static unsigned int +exynos5_usbdrd_pipe3_set_refclk(struct phy_usb_instance *inst) +{ + static u32 reg; + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + /* restore any previous reference clock settings */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + /* Use EXTREFCLK as ref clock */ + reg &= ~PHYCLKRST_REFCLKSEL_MASK; + reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK; + + /* FSEL settings corresponding to reference clock */ + reg &= ~PHYCLKRST_FSEL_PIPE_MASK | + PHYCLKRST_MPLL_MULTIPLIER_MASK | + PHYCLKRST_SSC_REFCLKSEL_MASK; + switch (phy_drd->extrefclk) { + case EXYNOS5_FSEL_50MHZ: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF | + PHYCLKRST_SSC_REFCLKSEL(0x00)); + break; + case EXYNOS5_FSEL_24MHZ: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x88)); + break; + case EXYNOS5_FSEL_20MHZ: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x00)); + break; + case EXYNOS5_FSEL_19MHZ2: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x88)); + break; + default: + dev_dbg(phy_drd->dev, "unsupported ref clk\n"); + break; + } + + return reg; +} + +/* + * Sets the utmi phy's clk as EXTREFCLK (XXTI) which is internal clock + * from clock core. Further sets the FSEL values for HighSpeed operations. + */ +static unsigned int +exynos5_usbdrd_utmi_set_refclk(struct phy_usb_instance *inst) +{ + static u32 reg; + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + /* restore any previous reference clock settings */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + reg &= ~PHYCLKRST_REFCLKSEL_MASK; + reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK; + + reg &= ~PHYCLKRST_FSEL_UTMI_MASK | + PHYCLKRST_MPLL_MULTIPLIER_MASK | + PHYCLKRST_SSC_REFCLKSEL_MASK; + reg |= PHYCLKRST_FSEL(phy_drd->extrefclk); + + return reg; +} + +static void exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) +{ + u32 reg; + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + /* Set Tx De-Emphasis level */ + reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; + reg |= PHYPARAM1_PCS_TXDEEMPH; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + reg &= ~PHYTEST_POWERDOWN_SSP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); +} + +static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) +{ + u32 reg; + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + /* Set Loss-of-Signal Detector sensitivity */ + reg &= ~PHYPARAM0_REF_LOSLEVEL_MASK; + reg |= PHYPARAM0_REF_LOSLEVEL; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + /* Set Tx De-Emphasis level */ + reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; + reg |= PHYPARAM1_PCS_TXDEEMPH; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + + /* UTMI Power Control */ + writel(PHYUTMI_OTGDISABLE, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + reg &= ~PHYTEST_POWERDOWN_HSP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); +} + +static int exynos5_usbdrd_phy_init(struct phy *phy) +{ + int ret; + u32 reg; + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + ret = clk_prepare_enable(phy_drd->clk); + if (ret) + return ret; + + /* Reset USB 3.0 PHY */ + writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0); + writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYRESUME); + + /* + * Setting the Frame length Adj value[6:1] to default 0x20 + * See xHCI 1.0 spec, 5.2.4 + */ + reg = LINKSYSTEM_XHCI_VERSION_CONTROL | + LINKSYSTEM_FLADJ(0x20); + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_LINKSYSTEM); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + /* Select PHY CLK source */ + reg &= ~PHYPARAM0_REF_USE_PAD; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + + /* This bit must be set for both HS and SS operations */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL); + reg |= PHYUTMICLKSEL_UTMI_CLKSEL; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL); + + /* UTMI or PIPE3 specific init */ + inst->phy_cfg->phy_init(phy_drd); + + /* reference clock settings */ + reg = inst->phy_cfg->set_refclk(inst); + + /* Digital power supply in normal operating mode */ + reg |= PHYCLKRST_RETENABLEN | + /* Enable ref clock for SS function */ + PHYCLKRST_REF_SSP_EN | + /* Enable spread spectrum */ + PHYCLKRST_SSC_EN | + /* Power down HS Bias and PLL blocks in suspend mode */ + PHYCLKRST_COMMONONN | + /* Reset the port */ + PHYCLKRST_PORTRESET; + + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + udelay(10); + + reg &= ~PHYCLKRST_PORTRESET; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + clk_disable_unprepare(phy_drd->clk); + + return 0; +} + +static int exynos5_usbdrd_phy_exit(struct phy *phy) +{ + int ret; + u32 reg; + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + ret = clk_prepare_enable(phy_drd->clk); + if (ret) + return ret; + + reg = PHYUTMI_OTGDISABLE | + PHYUTMI_FORCESUSPEND | + PHYUTMI_FORCESLEEP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI); + + /* Resetting the PHYCLKRST enable bits to reduce leakage current */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + reg &= ~(PHYCLKRST_REF_SSP_EN | + PHYCLKRST_SSC_EN | + PHYCLKRST_COMMONONN); + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + /* Control PHYTEST to remove leakage current */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + reg |= PHYTEST_POWERDOWN_SSP | + PHYTEST_POWERDOWN_HSP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + + clk_disable_unprepare(phy_drd->clk); + + return 0; +} + +static int exynos5_usbdrd_phy_power_on(struct phy *phy) +{ + int ret; + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + dev_dbg(phy_drd->dev, "Request to power_on usbdrd_phy phy\n"); + + clk_prepare_enable(phy_drd->ref_clk); + + /* Enable VBUS supply */ + if (phy_drd->vbus) { + ret = regulator_enable(phy_drd->vbus); + if (ret) { + dev_err(phy_drd->dev, "Failed to enable VBUS supply\n"); + goto fail_vbus; + } + } + + /* Power-on PHY*/ + inst->phy_cfg->phy_isol(inst, 0); + + return 0; + +fail_vbus: + clk_disable_unprepare(phy_drd->ref_clk); + + return ret; +} + +static int exynos5_usbdrd_phy_power_off(struct phy *phy) +{ + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + dev_dbg(phy_drd->dev, "Request to power_off usbdrd_phy phy\n"); + + /* Power-off the PHY */ + inst->phy_cfg->phy_isol(inst, 1); + + /* Disable VBUS supply */ + if (phy_drd->vbus) + regulator_disable(phy_drd->vbus); + + clk_disable_unprepare(phy_drd->ref_clk); + + return 0; +} + +static struct phy *exynos5_usbdrd_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct exynos5_usbdrd_phy *phy_drd = dev_get_drvdata(dev); + + if (WARN_ON(args->args[0] > EXYNOS5_DRDPHYS_NUM)) + return ERR_PTR(-ENODEV); + + return phy_drd->phys[args->args[0]].phy; +} + +static struct phy_ops exynos5_usbdrd_phy_ops = { + .init = exynos5_usbdrd_phy_init, + .exit = exynos5_usbdrd_phy_exit, + .power_on = exynos5_usbdrd_phy_power_on, + .power_off = exynos5_usbdrd_phy_power_off, + .owner = THIS_MODULE, +}; + +const struct exynos5_usbdrd_phy_config phy_cfg_exynos5[] = { + { + .id = EXYNOS5_DRDPHY_UTMI, + .phy_isol = exynos5_usbdrd_phy_isol, + .phy_init = exynos5_usbdrd_utmi_init, + .set_refclk = exynos5_usbdrd_utmi_set_refclk, + }, + { + .id = EXYNOS5_DRDPHY_PIPE3, + .phy_isol = exynos5_usbdrd_phy_isol, + .phy_init = exynos5_usbdrd_pipe3_init, + .set_refclk = exynos5_usbdrd_pipe3_set_refclk, + }, +}; + +const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .pmu_offset_usbdrd1_phy = EXYNOS5420_USBDRD1_PHY_CONTROL, +}; + +const struct exynos5_usbdrd_phy_drvdata exynos5250_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, +}; + +static const struct of_device_id exynos5_usbdrd_phy_of_match[] = { + { + .compatible = "samsung,exynos5250-usbdrd-phy", + .data = &exynos5250_usbdrd_phy + }, { + .compatible = "samsung,exynos5420-usbdrd-phy", + .data = &exynos5420_usbdrd_phy + }, + { }, +}; + +static int exynos5_usbdrd_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct exynos5_usbdrd_phy *phy_drd; + struct phy_provider *phy_provider; + struct resource *res; + const struct of_device_id *match; + const struct exynos5_usbdrd_phy_drvdata *drv_data; + struct regmap *reg_pmu; + u32 pmu_offset; + unsigned long ref_rate; + int i, ret; + int channel; + + phy_drd = devm_kzalloc(dev, sizeof(*phy_drd), GFP_KERNEL); + if (!phy_drd) + return -ENOMEM; + + dev_set_drvdata(dev, phy_drd); + phy_drd->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy_drd->reg_phy = devm_ioremap_resource(dev, res); + if (IS_ERR(phy_drd->reg_phy)) + return PTR_ERR(phy_drd->reg_phy); + + match = of_match_node(exynos5_usbdrd_phy_of_match, pdev->dev.of_node); + + drv_data = match->data; + phy_drd->drv_data = drv_data; + + phy_drd->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(phy_drd->clk)) { + dev_err(dev, "Failed to get clock of phy controller\n"); + return PTR_ERR(phy_drd->clk); + } + + phy_drd->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(phy_drd->ref_clk)) { + dev_err(dev, "Failed to get reference clock of usbdrd phy\n"); + return PTR_ERR(phy_drd->ref_clk); + } + ref_rate = clk_get_rate(phy_drd->ref_clk); + + ret = exynos5_rate_to_clk(ref_rate, &phy_drd->extrefclk); + if (ret) { + dev_err(phy_drd->dev, "Clock rate (%ld) not supported\n", + ref_rate); + return ret; + } + + reg_pmu = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,pmu-syscon"); + if (IS_ERR(reg_pmu)) { + dev_err(dev, "Failed to lookup PMU regmap\n"); + return PTR_ERR(reg_pmu); + } + + /* + * Exynos5420 SoC has multiple channels for USB 3.0 PHY, with + * each having separate power control registers. + * 'channel' facilitates to set such registers. + */ + channel = of_alias_get_id(node, "usbdrdphy"); + if (channel < 0) + dev_dbg(dev, "Not a multi-controller usbdrd phy\n"); + + switch (channel) { + case 1: + pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd1_phy; + break; + case 0: + default: + pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd0_phy; + break; + } + + /* Get Vbus regulator */ + phy_drd->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(phy_drd->vbus)) { + ret = PTR_ERR(phy_drd->vbus); + if (ret == -EPROBE_DEFER) + return ret; + + dev_warn(dev, "Failed to get VBUS supply regulator\n"); + phy_drd->vbus = NULL; + } + + dev_vdbg(dev, "Creating usbdrd_phy phy\n"); + + for (i = 0; i < EXYNOS5_DRDPHYS_NUM; i++) { + struct phy *phy = devm_phy_create(dev, &exynos5_usbdrd_phy_ops, + NULL); + if (IS_ERR(phy)) { + dev_err(dev, "Failed to create usbdrd_phy phy\n"); + return PTR_ERR(phy); + } + + phy_drd->phys[i].phy = phy; + phy_drd->phys[i].index = i; + phy_drd->phys[i].reg_pmu = reg_pmu; + phy_drd->phys[i].pmu_offset = pmu_offset; + phy_drd->phys[i].phy_cfg = &drv_data->phy_cfg[i]; + phy_set_drvdata(phy, &phy_drd->phys[i]); + } + + phy_provider = devm_of_phy_provider_register(dev, + exynos5_usbdrd_phy_xlate); + if (IS_ERR(phy_provider)) { + dev_err(phy_drd->dev, "Failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static struct platform_driver exynos5_usb3drd_phy = { + .probe = exynos5_usbdrd_phy_probe, + .driver = { + .of_match_table = exynos5_usbdrd_phy_of_match, + .name = "exynos5_usb3drd_phy", + .owner = THIS_MODULE, + } +}; + +module_platform_driver(exynos5_usb3drd_phy); +MODULE_DESCRIPTION("Samsung EXYNOS5 SoCs USB 3.0 DRD controller PHY driver"); +MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:exynos5_usb3drd_phy"); diff --git a/drivers/phy/phy-exynos5250-sata.c b/drivers/phy/phy-exynos5250-sata.c new file mode 100644 index 00000000000..05689450f93 --- /dev/null +++ b/drivers/phy/phy-exynos5250-sata.c @@ -0,0 +1,251 @@ +/* + * Samsung SATA SerDes(PHY) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Authors: Girish K S <ks.giri@samsung.com> + * Yuvaraj Kumar C D <yuvaraj.cd@samsung.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/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/spinlock.h> +#include <linux/mfd/syscon.h> + +#define SATAPHY_CONTROL_OFFSET 0x0724 +#define EXYNOS5_SATAPHY_PMU_ENABLE BIT(0) +#define EXYNOS5_SATA_RESET 0x4 +#define RESET_GLOBAL_RST_N BIT(0) +#define RESET_CMN_RST_N BIT(1) +#define RESET_CMN_BLOCK_RST_N BIT(2) +#define RESET_CMN_I2C_RST_N BIT(3) +#define RESET_TX_RX_PIPE_RST_N BIT(4) +#define RESET_TX_RX_BLOCK_RST_N BIT(5) +#define RESET_TX_RX_I2C_RST_N (BIT(6) | BIT(7)) +#define LINK_RESET 0xf0000 +#define EXYNOS5_SATA_MODE0 0x10 +#define SATA_SPD_GEN3 BIT(1) +#define EXYNOS5_SATA_CTRL0 0x14 +#define CTRL0_P0_PHY_CALIBRATED_SEL BIT(9) +#define CTRL0_P0_PHY_CALIBRATED BIT(8) +#define EXYNOS5_SATA_PHSATA_CTRLM 0xe0 +#define PHCTRLM_REF_RATE BIT(1) +#define PHCTRLM_HIGH_SPEED BIT(0) +#define EXYNOS5_SATA_PHSATA_STATM 0xf0 +#define PHSTATM_PLL_LOCKED BIT(0) + +#define PHY_PLL_TIMEOUT (usecs_to_jiffies(1000)) + +struct exynos_sata_phy { + struct phy *phy; + struct clk *phyclk; + void __iomem *regs; + struct regmap *pmureg; + struct i2c_client *client; +}; + +static int wait_for_reg_status(void __iomem *base, u32 reg, u32 checkbit, + u32 status) +{ + unsigned long timeout = jiffies + PHY_PLL_TIMEOUT; + + while (time_before(jiffies, timeout)) { + if ((readl(base + reg) & checkbit) == status) + return 0; + } + + return -EFAULT; +} + +static int exynos_sata_phy_power_on(struct phy *phy) +{ + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, true); + +} + +static int exynos_sata_phy_power_off(struct phy *phy) +{ + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, false); + +} + +static int exynos_sata_phy_init(struct phy *phy) +{ + u32 val = 0; + int ret = 0; + u8 buf[] = { 0x3a, 0x0b }; + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + ret = regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, true); + if (ret != 0) + dev_err(&sata_phy->phy->dev, "phy init failed\n"); + + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_GLOBAL_RST_N | RESET_CMN_RST_N | RESET_CMN_BLOCK_RST_N + | RESET_CMN_I2C_RST_N | RESET_TX_RX_PIPE_RST_N + | RESET_TX_RX_BLOCK_RST_N | RESET_TX_RX_I2C_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= LINK_RESET; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + val &= ~PHCTRLM_REF_RATE; + writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + + /* High speed enable for Gen3 */ + val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + val |= PHCTRLM_HIGH_SPEED; + writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + + val = readl(sata_phy->regs + EXYNOS5_SATA_CTRL0); + val |= CTRL0_P0_PHY_CALIBRATED_SEL | CTRL0_P0_PHY_CALIBRATED; + writel(val, sata_phy->regs + EXYNOS5_SATA_CTRL0); + + val = readl(sata_phy->regs + EXYNOS5_SATA_MODE0); + val |= SATA_SPD_GEN3; + writel(val, sata_phy->regs + EXYNOS5_SATA_MODE0); + + ret = i2c_master_send(sata_phy->client, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* release cmu reset */ + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val &= ~RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + ret = wait_for_reg_status(sata_phy->regs, + EXYNOS5_SATA_PHSATA_STATM, + PHSTATM_PLL_LOCKED, 1); + if (ret < 0) + dev_err(&sata_phy->phy->dev, + "PHY PLL locking failed\n"); + return ret; +} + +static struct phy_ops exynos_sata_phy_ops = { + .init = exynos_sata_phy_init, + .power_on = exynos_sata_phy_power_on, + .power_off = exynos_sata_phy_power_off, + .owner = THIS_MODULE, +}; + +static int exynos_sata_phy_probe(struct platform_device *pdev) +{ + struct exynos_sata_phy *sata_phy; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy_provider *phy_provider; + struct device_node *node; + int ret = 0; + + sata_phy = devm_kzalloc(dev, sizeof(*sata_phy), GFP_KERNEL); + if (!sata_phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + sata_phy->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(sata_phy->regs)) + return PTR_ERR(sata_phy->regs); + + sata_phy->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(sata_phy->pmureg)) { + dev_err(dev, "syscon regmap lookup failed.\n"); + return PTR_ERR(sata_phy->pmureg); + } + + node = of_parse_phandle(dev->of_node, + "samsung,exynos-sataphy-i2c-phandle", 0); + if (!node) + return -EINVAL; + + sata_phy->client = of_find_i2c_device_by_node(node); + if (!sata_phy->client) + return -EPROBE_DEFER; + + dev_set_drvdata(dev, sata_phy); + + sata_phy->phyclk = devm_clk_get(dev, "sata_phyctrl"); + if (IS_ERR(sata_phy->phyclk)) { + dev_err(dev, "failed to get clk for PHY\n"); + return PTR_ERR(sata_phy->phyclk); + } + + ret = clk_prepare_enable(sata_phy->phyclk); + if (ret < 0) { + dev_err(dev, "failed to enable source clk\n"); + return ret; + } + + sata_phy->phy = devm_phy_create(dev, &exynos_sata_phy_ops, NULL); + if (IS_ERR(sata_phy->phy)) { + clk_disable_unprepare(sata_phy->phyclk); + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(sata_phy->phy); + } + + phy_set_drvdata(sata_phy->phy, sata_phy); + + phy_provider = devm_of_phy_provider_register(dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + clk_disable_unprepare(sata_phy->phyclk); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static const struct of_device_id exynos_sata_phy_of_match[] = { + { .compatible = "samsung,exynos5250-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_sata_phy_of_match); + +static struct platform_driver exynos_sata_phy_driver = { + .probe = exynos_sata_phy_probe, + .driver = { + .of_match_table = exynos_sata_phy_of_match, + .name = "samsung,sata-phy", + .owner = THIS_MODULE, + } +}; +module_platform_driver(exynos_sata_phy_driver); + +MODULE_DESCRIPTION("Samsung SerDes PHY driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Girish K S <ks.giri@samsung.com>"); +MODULE_AUTHOR("Yuvaraj C D <yuvaraj.cd@samsung.com>"); diff --git a/drivers/phy/phy-exynos5250-usb2.c b/drivers/phy/phy-exynos5250-usb2.c new file mode 100644 index 00000000000..94179afda95 --- /dev/null +++ b/drivers/phy/phy-exynos5250-usb2.c @@ -0,0 +1,404 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 5250 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski <k.debski@samsung.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/delay.h> +#include <linux/io.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ +#define EXYNOS_5250_REFCLKSEL_CRYSTAL 0x0 +#define EXYNOS_5250_REFCLKSEL_XO 0x1 +#define EXYNOS_5250_REFCLKSEL_CLKCORE 0x2 + +#define EXYNOS_5250_FSEL_9MHZ6 0x0 +#define EXYNOS_5250_FSEL_10MHZ 0x1 +#define EXYNOS_5250_FSEL_12MHZ 0x2 +#define EXYNOS_5250_FSEL_19MHZ2 0x3 +#define EXYNOS_5250_FSEL_20MHZ 0x4 +#define EXYNOS_5250_FSEL_24MHZ 0x5 +#define EXYNOS_5250_FSEL_50MHZ 0x7 + +/* Normal host */ +#define EXYNOS_5250_HOSTPHYCTRL0 0x0 + +#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL BIT(31) +#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT 19 +#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_MASK \ + (0x3 << EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT) +#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT 16 +#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK \ + (0x7 << EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT) +#define EXYNOS_5250_HOSTPHYCTRL0_TESTBURNIN BIT(11) +#define EXYNOS_5250_HOSTPHYCTRL0_RETENABLE BIT(10) +#define EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N BIT(9) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_MASK (0x3 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_DUAL (0x0 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ID0 (0x1 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ANALOGTEST (0x2 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_SIDDQ BIT(6) +#define EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP BIT(5) +#define EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND BIT(4) +#define EXYNOS_5250_HOSTPHYCTRL0_WORDINTERFACE BIT(3) +#define EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST BIT(2) +#define EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST BIT(1) +#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST BIT(0) + +/* HSIC0 & HSIC1 */ +#define EXYNOS_5250_HSICPHYCTRL1 0x10 +#define EXYNOS_5250_HSICPHYCTRL2 0x20 + +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_MASK (0x3 << 23) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT (0x2 << 23) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_MASK (0x7f << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 (0x24 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_15 (0x1c << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_16 (0x1a << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_19_2 (0x15 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_20 (0x14 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_SIDDQ BIT(6) +#define EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP BIT(5) +#define EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND BIT(4) +#define EXYNOS_5250_HSICPHYCTRLX_WORDINTERFACE BIT(3) +#define EXYNOS_5250_HSICPHYCTRLX_UTMISWRST BIT(2) +#define EXYNOS_5250_HSICPHYCTRLX_PHYSWRST BIT(0) + +/* EHCI control */ +#define EXYNOS_5250_HOSTEHCICTRL 0x30 +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN BIT(29) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 BIT(28) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 BIT(27) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR16 BIT(26) +#define EXYNOS_5250_HOSTEHCICTRL_AUTOPPDONOVRCUREN BIT(25) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT 19 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT 13 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL2_SHIFT 7 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT 1 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_MASK \ + (0x1 << EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_SIMULATIONMODE BIT(0) + +/* OHCI control */ +#define EXYNOS_5250_HOSTOHCICTRL 0x34 +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT 1 +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_MASK \ + (0x3ff << EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT) +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVALEN BIT(0) + +/* USBOTG */ +#define EXYNOS_5250_USBOTGSYS 0x38 +#define EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET BIT(14) +#define EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG BIT(13) +#define EXYNOS_5250_USBOTGSYS_PHY_SW_RST BIT(12) +#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT 9 +#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK \ + (0x3 << EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT) +#define EXYNOS_5250_USBOTGSYS_ID_PULLUP BIT(8) +#define EXYNOS_5250_USBOTGSYS_COMMON_ON BIT(7) +#define EXYNOS_5250_USBOTGSYS_FSEL_SHIFT 4 +#define EXYNOS_5250_USBOTGSYS_FSEL_MASK \ + (0x3 << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT) +#define EXYNOS_5250_USBOTGSYS_FORCE_SLEEP BIT(3) +#define EXYNOS_5250_USBOTGSYS_OTGDISABLE BIT(2) +#define EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG BIT(1) +#define EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND BIT(0) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_5250_USB_ISOL_OTG_OFFSET 0x704 +#define EXYNOS_5250_USB_ISOL_OTG BIT(0) +#define EXYNOS_5250_USB_ISOL_HOST_OFFSET 0x708 +#define EXYNOS_5250_USB_ISOL_HOST BIT(0) + +/* Mode swtich register */ +#define EXYNOS_5250_MODE_SWITCH_OFFSET 0x230 +#define EXYNOS_5250_MODE_SWITCH_MASK 1 +#define EXYNOS_5250_MODE_SWITCH_DEVICE 0 +#define EXYNOS_5250_MODE_SWITCH_HOST 1 + +enum exynos4x12_phy_id { + EXYNOS5250_DEVICE, + EXYNOS5250_HOST, + EXYNOS5250_HSIC0, + EXYNOS5250_HSIC1, + EXYNOS5250_NUM_PHYS, +}; + +/* + * exynos5250_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos5250_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS_5250_FSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS_5250_FSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS_5250_FSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS_5250_FSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS_5250_FSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS_5250_FSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_5250_FSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS_5250_FSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos5250_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + offset = EXYNOS_5250_USB_ISOL_OTG_OFFSET; + mask = EXYNOS_5250_USB_ISOL_OTG; + break; + case EXYNOS5250_HOST: + offset = EXYNOS_5250_USB_ISOL_HOST_OFFSET; + mask = EXYNOS_5250_USB_ISOL_HOST; + break; + default: + return; + }; + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static int exynos5250_power_on(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 ctrl0; + u32 otg; + u32 ehci; + u32 ohci; + u32 hsic; + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + regmap_update_bits(drv->reg_sys, + EXYNOS_5250_MODE_SWITCH_OFFSET, + EXYNOS_5250_MODE_SWITCH_MASK, + EXYNOS_5250_MODE_SWITCH_DEVICE); + + /* OTG configuration */ + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + /* The clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; + otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; + /* Reset */ + otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); + otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_OTGDISABLE; + /* Ref clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; + otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << + EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + udelay(100); + otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_OTGDISABLE); + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + + + break; + case EXYNOS5250_HOST: + case EXYNOS5250_HSIC0: + case EXYNOS5250_HSIC1: + /* Host registers configuration */ + ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + /* The clock */ + ctrl0 &= ~EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK; + ctrl0 |= drv->ref_reg_val << + EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT; + + /* Reset */ + ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL | + EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | + EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | + EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP); + ctrl0 |= EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | + EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST | + EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N; + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + udelay(10); + ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | + EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST); + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + + /* OTG configuration */ + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + /* The clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; + otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; + /* Reset */ + otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); + otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_OTGDISABLE; + /* Ref clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; + otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << + EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + udelay(10); + otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET); + + /* HSIC phy configuration */ + hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | + EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | + EXYNOS_5250_HSICPHYCTRLX_PHYSWRST); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + udelay(10); + hsic &= ~EXYNOS_5250_HSICPHYCTRLX_PHYSWRST; + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + + /* Enable EHCI DMA burst */ + ehci = readl(drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); + ehci |= EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR16; + writel(ehci, drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); + + /* OHCI settings */ + ohci = readl(drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); + /* Following code is based on the old driver */ + ohci |= 0x1 << 3; + writel(ohci, drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); + + break; + } + inst->enabled = 1; + exynos5250_isol(inst, 0); + + return 0; +} + +static int exynos5250_power_off(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 ctrl0; + u32 otg; + u32 hsic; + + inst->enabled = 0; + exynos5250_isol(inst, 1); + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + otg |= (EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP); + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + break; + case EXYNOS5250_HOST: + ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + ctrl0 |= (EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | + EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | + EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL); + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + break; + case EXYNOS5250_HSIC0: + case EXYNOS5250_HSIC1: + hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | + EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | + EXYNOS_5250_HSICPHYCTRLX_SIDDQ | + EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP | + EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND + ); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + break; + } + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos5250_phys[] = { + { + .label = "device", + .id = EXYNOS5250_DEVICE, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "host", + .id = EXYNOS5250_HOST, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS5250_HSIC0, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS5250_HSIC1, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + {}, +}; + +const struct samsung_usb2_phy_config exynos5250_usb2_phy_config = { + .has_mode_switch = 1, + .num_phys = EXYNOS5250_NUM_PHYS, + .phys = exynos5250_phys, + .rate_to_clk = exynos5250_rate_to_clk, +}; diff --git a/drivers/phy/phy-mvebu-sata.c b/drivers/phy/phy-mvebu-sata.c new file mode 100644 index 00000000000..d70ecd6a1b3 --- /dev/null +++ b/drivers/phy/phy-mvebu-sata.c @@ -0,0 +1,137 @@ +/* + * phy-mvebu-sata.c: SATA Phy driver for the Marvell mvebu SoCs. + * + * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/phy/phy.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +struct priv { + struct clk *clk; + void __iomem *base; +}; + +#define SATA_PHY_MODE_2 0x0330 +#define MODE_2_FORCE_PU_TX BIT(0) +#define MODE_2_FORCE_PU_RX BIT(1) +#define MODE_2_PU_PLL BIT(2) +#define MODE_2_PU_IVREF BIT(3) +#define SATA_IF_CTRL 0x0050 +#define CTRL_PHY_SHUTDOWN BIT(9) + +static int phy_mvebu_sata_power_on(struct phy *phy) +{ + struct priv *priv = phy_get_drvdata(phy); + u32 reg; + + clk_prepare_enable(priv->clk); + + /* Enable PLL and IVREF */ + reg = readl(priv->base + SATA_PHY_MODE_2); + reg |= (MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | + MODE_2_PU_PLL | MODE_2_PU_IVREF); + writel(reg , priv->base + SATA_PHY_MODE_2); + + /* Enable PHY */ + reg = readl(priv->base + SATA_IF_CTRL); + reg &= ~CTRL_PHY_SHUTDOWN; + writel(reg, priv->base + SATA_IF_CTRL); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int phy_mvebu_sata_power_off(struct phy *phy) +{ + struct priv *priv = phy_get_drvdata(phy); + u32 reg; + + clk_prepare_enable(priv->clk); + + /* Disable PLL and IVREF */ + reg = readl(priv->base + SATA_PHY_MODE_2); + reg &= ~(MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | + MODE_2_PU_PLL | MODE_2_PU_IVREF); + writel(reg, priv->base + SATA_PHY_MODE_2); + + /* Disable PHY */ + reg = readl(priv->base + SATA_IF_CTRL); + reg |= CTRL_PHY_SHUTDOWN; + writel(reg, priv->base + SATA_IF_CTRL); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static struct phy_ops phy_mvebu_sata_ops = { + .power_on = phy_mvebu_sata_power_on, + .power_off = phy_mvebu_sata_power_off, + .owner = THIS_MODULE, +}; + +static int phy_mvebu_sata_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct resource *res; + struct priv *priv; + struct phy *phy; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(&pdev->dev, "sata"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + phy = devm_phy_create(&pdev->dev, &phy_mvebu_sata_ops, NULL); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + /* The boot loader may of left it on. Turn it off. */ + phy_mvebu_sata_power_off(phy); + + return 0; +} + +static const struct of_device_id phy_mvebu_sata_of_match[] = { + { .compatible = "marvell,mvebu-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_mvebu_sata_of_match); + +static struct platform_driver phy_mvebu_sata_driver = { + .probe = phy_mvebu_sata_probe, + .driver = { + .name = "phy-mvebu-sata", + .owner = THIS_MODULE, + .of_match_table = phy_mvebu_sata_of_match, + } +}; +module_platform_driver(phy_mvebu_sata_driver); + +MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); +MODULE_DESCRIPTION("Marvell MVEBU SATA PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-omap-control.c b/drivers/phy/phy-omap-control.c new file mode 100644 index 00000000000..311b4f9a513 --- /dev/null +++ b/drivers/phy/phy-omap-control.c @@ -0,0 +1,320 @@ +/* + * 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_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_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); + +#ifdef CONFIG_OF + +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 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-usb2-dra7", + .data = &dra7usb2_data, + }, + { + .compatible = "ti,control-phy-usb2-am437", + .data = &am437usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_control_phy_id_table); +#endif + + +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(of_match_ptr(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) { + dev_err(&pdev->dev, "unable to alloc memory for control phy\n"); + 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->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; + } + } + + 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", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(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/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c new file mode 100644 index 00000000000..34b396146c8 --- /dev/null +++ b/drivers/phy/phy-omap-usb2.c @@ -0,0 +1,396 @@ +/* + * 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/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->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->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) +{ + struct usb_phy *phy = otg->phy; + + otg->host = host; + if (!host) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct usb_phy *phy = otg->phy; + + otg->gadget = gadget; + if (!gadget) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_power_off(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + omap_control_phy_power(phy->control_dev, 0); + + return 0; +} + +static int omap_usb_power_on(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + omap_control_phy_power(phy->control_dev, 1); + + return 0; +} + +static int omap_usb_init(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + u32 val; + + 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 struct phy_ops ops = { + .init = omap_usb_init, + .power_on = omap_usb_power_on, + .power_off = omap_usb_power_off, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_OF +static const struct usb_phy_data omap_usb2_data = { + .label = "omap_usb2", + .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, +}; + +static const struct usb_phy_data omap5_usb2_data = { + .label = "omap5_usb2", + .flags = 0, +}; + +static const struct usb_phy_data dra7x_usb2_data = { + .label = "dra7x_usb2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, +}; + +static const struct usb_phy_data am437x_usb2_data = { + .label = "am437x_usb2", + .flags = 0, +}; + +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,am437x-usb2", + .data = &am437x_usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_usb2_id_table); +#endif + +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(of_match_ptr(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) { + dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n"); + return -ENOMEM; + } + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) { + dev_err(&pdev->dev, "unable to allocate memory for USB OTG\n"); + 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; + + 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; + } + + 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; + omap_control_phy_power(phy->control_dev, 0); + + 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->phy = &phy->phy; + + platform_set_drvdata(pdev, phy); + + generic_phy = devm_phy_create(phy->dev, &ops, NULL); + if (IS_ERR(generic_phy)) + return PTR_ERR(generic_phy); + + phy_set_drvdata(generic_phy, phy); + + pm_runtime_enable(phy->dev); + 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"); + 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"); + } + } else { + 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; +} + +#ifdef CONFIG_PM_RUNTIME + +static int omap_usb2_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_disable(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_disable(phy->optclk); + + return 0; +} + +static int omap_usb2_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + 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 const struct dev_pm_ops omap_usb2_pm_ops = { + SET_RUNTIME_PM_OPS(omap_usb2_runtime_suspend, omap_usb2_runtime_resume, + NULL) +}; + +#define DEV_PM_OPS (&omap_usb2_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +static struct platform_driver omap_usb2_driver = { + .probe = omap_usb2_probe, + .remove = omap_usb2_remove, + .driver = { + .name = "omap-usb2", + .owner = THIS_MODULE, + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(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/phy-samsung-usb2.c b/drivers/phy/phy-samsung-usb2.c new file mode 100644 index 00000000000..1e69a32c221 --- /dev/null +++ b/drivers/phy/phy-samsung-usb2.c @@ -0,0 +1,229 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski <k.debski@samsung.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/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include "phy-samsung-usb2.h" + +static int samsung_usb2_phy_power_on(struct phy *phy) +{ + struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); + struct samsung_usb2_phy_driver *drv = inst->drv; + int ret; + + dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n", + inst->cfg->label); + ret = clk_prepare_enable(drv->clk); + if (ret) + goto err_main_clk; + ret = clk_prepare_enable(drv->ref_clk); + if (ret) + goto err_instance_clk; + if (inst->cfg->power_on) { + spin_lock(&drv->lock); + ret = inst->cfg->power_on(inst); + spin_unlock(&drv->lock); + } + + return 0; + +err_instance_clk: + clk_disable_unprepare(drv->clk); +err_main_clk: + return ret; +} + +static int samsung_usb2_phy_power_off(struct phy *phy) +{ + struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); + struct samsung_usb2_phy_driver *drv = inst->drv; + int ret = 0; + + dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n", + inst->cfg->label); + if (inst->cfg->power_off) { + spin_lock(&drv->lock); + ret = inst->cfg->power_off(inst); + spin_unlock(&drv->lock); + } + clk_disable_unprepare(drv->ref_clk); + clk_disable_unprepare(drv->clk); + return ret; +} + +static struct phy_ops samsung_usb2_phy_ops = { + .power_on = samsung_usb2_phy_power_on, + .power_off = samsung_usb2_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct phy *samsung_usb2_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct samsung_usb2_phy_driver *drv; + + drv = dev_get_drvdata(dev); + if (!drv) + return ERR_PTR(-EINVAL); + + if (WARN_ON(args->args[0] >= drv->cfg->num_phys)) + return ERR_PTR(-ENODEV); + + return drv->instances[args->args[0]].phy; +} + +static const struct of_device_id samsung_usb2_phy_of_match[] = { +#ifdef CONFIG_PHY_EXYNOS4210_USB2 + { + .compatible = "samsung,exynos4210-usb2-phy", + .data = &exynos4210_usb2_phy_config, + }, +#endif +#ifdef CONFIG_PHY_EXYNOS4X12_USB2 + { + .compatible = "samsung,exynos4x12-usb2-phy", + .data = &exynos4x12_usb2_phy_config, + }, +#endif +#ifdef CONFIG_PHY_EXYNOS5250_USB2 + { + .compatible = "samsung,exynos5250-usb2-phy", + .data = &exynos5250_usb2_phy_config, + }, +#endif + { }, +}; +MODULE_DEVICE_TABLE(of, samsung_usb2_phy_of_match); + +static int samsung_usb2_phy_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct samsung_usb2_phy_config *cfg; + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct resource *mem; + struct samsung_usb2_phy_driver *drv; + int i, ret; + + if (!pdev->dev.of_node) { + dev_err(dev, "This driver is required to be instantiated from device tree\n"); + return -EINVAL; + } + + match = of_match_node(samsung_usb2_phy_of_match, pdev->dev.of_node); + if (!match) { + dev_err(dev, "of_match_node() failed\n"); + return -EINVAL; + } + cfg = match->data; + + drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) + + cfg->num_phys * sizeof(struct samsung_usb2_phy_instance), + GFP_KERNEL); + if (!drv) + return -ENOMEM; + + dev_set_drvdata(dev, drv); + spin_lock_init(&drv->lock); + + drv->cfg = cfg; + drv->dev = dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drv->reg_phy = devm_ioremap_resource(dev, mem); + if (IS_ERR(drv->reg_phy)) { + dev_err(dev, "Failed to map register memory (phy)\n"); + return PTR_ERR(drv->reg_phy); + } + + drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "samsung,pmureg-phandle"); + if (IS_ERR(drv->reg_pmu)) { + dev_err(dev, "Failed to map PMU registers (via syscon)\n"); + return PTR_ERR(drv->reg_pmu); + } + + if (drv->cfg->has_mode_switch) { + drv->reg_sys = syscon_regmap_lookup_by_phandle( + pdev->dev.of_node, "samsung,sysreg-phandle"); + if (IS_ERR(drv->reg_sys)) { + dev_err(dev, "Failed to map system registers (via syscon)\n"); + return PTR_ERR(drv->reg_sys); + } + } + + drv->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(drv->clk)) { + dev_err(dev, "Failed to get clock of phy controller\n"); + return PTR_ERR(drv->clk); + } + + drv->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(drv->ref_clk)) { + dev_err(dev, "Failed to get reference clock for the phy controller\n"); + return PTR_ERR(drv->ref_clk); + } + + drv->ref_rate = clk_get_rate(drv->ref_clk); + if (drv->cfg->rate_to_clk) { + ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val); + if (ret) + return ret; + } + + for (i = 0; i < drv->cfg->num_phys; i++) { + char *label = drv->cfg->phys[i].label; + struct samsung_usb2_phy_instance *p = &drv->instances[i]; + + dev_dbg(dev, "Creating phy \"%s\"\n", label); + p->phy = devm_phy_create(dev, &samsung_usb2_phy_ops, NULL); + if (IS_ERR(p->phy)) { + dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n", + label); + return PTR_ERR(p->phy); + } + + p->cfg = &drv->cfg->phys[i]; + p->drv = drv; + phy_set_bus_width(p->phy, 8); + phy_set_drvdata(p->phy, p); + } + + phy_provider = devm_of_phy_provider_register(dev, + samsung_usb2_phy_xlate); + if (IS_ERR(phy_provider)) { + dev_err(drv->dev, "Failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static struct platform_driver samsung_usb2_phy_driver = { + .probe = samsung_usb2_phy_probe, + .driver = { + .of_match_table = samsung_usb2_phy_of_match, + .name = "samsung-usb2-phy", + .owner = THIS_MODULE, + } +}; + +module_platform_driver(samsung_usb2_phy_driver); +MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver"); +MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:samsung-usb2-phy"); diff --git a/drivers/phy/phy-samsung-usb2.h b/drivers/phy/phy-samsung-usb2.h new file mode 100644 index 00000000000..45b3170652b --- /dev/null +++ b/drivers/phy/phy-samsung-usb2.h @@ -0,0 +1,67 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski <k.debski@samsung.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. + */ + +#ifndef _PHY_EXYNOS_USB2_H +#define _PHY_EXYNOS_USB2_H + +#include <linux/clk.h> +#include <linux/phy/phy.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/spinlock.h> + +#define KHZ 1000 +#define MHZ (KHZ * KHZ) + +struct samsung_usb2_phy_driver; +struct samsung_usb2_phy_instance; +struct samsung_usb2_phy_config; + +struct samsung_usb2_phy_instance { + const struct samsung_usb2_common_phy *cfg; + struct phy *phy; + struct samsung_usb2_phy_driver *drv; + bool enabled; +}; + +struct samsung_usb2_phy_driver { + const struct samsung_usb2_phy_config *cfg; + struct clk *clk; + struct clk *ref_clk; + unsigned long ref_rate; + u32 ref_reg_val; + struct device *dev; + void __iomem *reg_phy; + struct regmap *reg_pmu; + struct regmap *reg_sys; + spinlock_t lock; + struct samsung_usb2_phy_instance instances[0]; +}; + +struct samsung_usb2_common_phy { + int (*power_on)(struct samsung_usb2_phy_instance *); + int (*power_off)(struct samsung_usb2_phy_instance *); + unsigned int id; + char *label; +}; + + +struct samsung_usb2_phy_config { + const struct samsung_usb2_common_phy *phys; + int (*rate_to_clk)(unsigned long, u32 *); + unsigned int num_phys; + bool has_mode_switch; +}; + +extern const struct samsung_usb2_phy_config exynos4210_usb2_phy_config; +extern const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config; +extern const struct samsung_usb2_phy_config exynos5250_usb2_phy_config; +#endif diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c new file mode 100644 index 00000000000..115d8d5190d --- /dev/null +++ b/drivers/phy/phy-sun4i-usb.c @@ -0,0 +1,336 @@ +/* + * Allwinner sun4i USB phy driver + * + * Copyright (C) 2014 Hans de Goede <hdegoede@redhat.com> + * + * Based on code from + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> + * + * Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki <s.nawrocki@samsung.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. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> + +#define REG_ISCR 0x00 +#define REG_PHYCTL 0x04 +#define REG_PHYBIST 0x08 +#define REG_PHYTUNE 0x0c + +#define PHYCTL_DATA BIT(7) + +#define SUNXI_AHB_ICHR8_EN BIT(10) +#define SUNXI_AHB_INCR4_BURST_EN BIT(9) +#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) +#define SUNXI_ULPI_BYPASS_EN BIT(0) + +/* Common Control Bits for Both PHYs */ +#define PHY_PLL_BW 0x03 +#define PHY_RES45_CAL_EN 0x0c + +/* Private Control Bits for Each PHY */ +#define PHY_TX_AMPLITUDE_TUNE 0x20 +#define PHY_TX_SLEWRATE_TUNE 0x22 +#define PHY_VBUSVALID_TH_SEL 0x25 +#define PHY_PULLUP_RES_SEL 0x27 +#define PHY_OTG_FUNC_EN 0x28 +#define PHY_VBUS_DET_EN 0x29 +#define PHY_DISCON_TH_SEL 0x2a + +#define MAX_PHYS 3 + +struct sun4i_usb_phy_data { + void __iomem *base; + struct mutex mutex; + int num_phys; + u32 disc_thresh; + struct sun4i_usb_phy { + struct phy *phy; + void __iomem *pmu; + struct regulator *vbus; + struct reset_control *reset; + struct clk *clk; + int index; + } phys[MAX_PHYS]; +}; + +#define to_sun4i_usb_phy_data(phy) \ + container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index]) + +static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, + int len) +{ + struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); + u32 temp, usbc_bit = BIT(phy->index * 2); + int i; + + mutex_lock(&phy_data->mutex); + + for (i = 0; i < len; i++) { + temp = readl(phy_data->base + REG_PHYCTL); + + /* clear the address portion */ + temp &= ~(0xff << 8); + + /* set the address */ + temp |= ((addr + i) << 8); + writel(temp, phy_data->base + REG_PHYCTL); + + /* set the data bit and clear usbc bit*/ + temp = readb(phy_data->base + REG_PHYCTL); + if (data & 0x1) + temp |= PHYCTL_DATA; + else + temp &= ~PHYCTL_DATA; + temp &= ~usbc_bit; + writeb(temp, phy_data->base + REG_PHYCTL); + + /* pulse usbc_bit */ + temp = readb(phy_data->base + REG_PHYCTL); + temp |= usbc_bit; + writeb(temp, phy_data->base + REG_PHYCTL); + + temp = readb(phy_data->base + REG_PHYCTL); + temp &= ~usbc_bit; + writeb(temp, phy_data->base + REG_PHYCTL); + + data >>= 1; + } + mutex_unlock(&phy_data->mutex); +} + +static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable) +{ + u32 bits, reg_value; + + if (!phy->pmu) + return; + + bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN | + SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN; + + reg_value = readl(phy->pmu); + + if (enable) + reg_value |= bits; + else + reg_value &= ~bits; + + writel(reg_value, phy->pmu); +} + +static int sun4i_usb_phy_init(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + int ret; + + ret = clk_prepare_enable(phy->clk); + if (ret) + return ret; + + ret = reset_control_deassert(phy->reset); + if (ret) { + clk_disable_unprepare(phy->clk); + return ret; + } + + /* Adjust PHY's magnitude and rate */ + sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); + + /* Disconnect threshold adjustment */ + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2); + + sun4i_usb_phy_passby(phy, 1); + + return 0; +} + +static int sun4i_usb_phy_exit(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + + sun4i_usb_phy_passby(phy, 0); + reset_control_assert(phy->reset); + clk_disable_unprepare(phy->clk); + + return 0; +} + +static int sun4i_usb_phy_power_on(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + int ret = 0; + + if (phy->vbus) + ret = regulator_enable(phy->vbus); + + return ret; +} + +static int sun4i_usb_phy_power_off(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + + if (phy->vbus) + regulator_disable(phy->vbus); + + return 0; +} + +static struct phy_ops sun4i_usb_phy_ops = { + .init = sun4i_usb_phy_init, + .exit = sun4i_usb_phy_exit, + .power_on = sun4i_usb_phy_power_on, + .power_off = sun4i_usb_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct phy *sun4i_usb_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); + + if (WARN_ON(args->args[0] == 0 || args->args[0] >= data->num_phys)) + return ERR_PTR(-ENODEV); + + return data->phys[args->args[0]].phy; +} + +static int sun4i_usb_phy_probe(struct platform_device *pdev) +{ + struct sun4i_usb_phy_data *data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_provider *phy_provider; + bool dedicated_clocks; + struct resource *res; + int i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->mutex); + + if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy")) + data->num_phys = 2; + else + data->num_phys = 3; + + if (of_device_is_compatible(np, "allwinner,sun4i-a10-usb-phy")) + data->disc_thresh = 3; + else + data->disc_thresh = 2; + + if (of_device_is_compatible(np, "allwinner,sun6i-a31-usb-phy")) + dedicated_clocks = true; + else + dedicated_clocks = false; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); + data->base = devm_ioremap_resource(dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + /* Skip 0, 0 is the phy for otg which is not yet supported. */ + for (i = 1; i < data->num_phys; i++) { + struct sun4i_usb_phy *phy = data->phys + i; + char name[16]; + + snprintf(name, sizeof(name), "usb%d_vbus", i); + phy->vbus = devm_regulator_get_optional(dev, name); + if (IS_ERR(phy->vbus)) { + if (PTR_ERR(phy->vbus) == -EPROBE_DEFER) + return -EPROBE_DEFER; + phy->vbus = NULL; + } + + if (dedicated_clocks) + snprintf(name, sizeof(name), "usb%d_phy", i); + else + strlcpy(name, "usb_phy", sizeof(name)); + + phy->clk = devm_clk_get(dev, name); + if (IS_ERR(phy->clk)) { + dev_err(dev, "failed to get clock %s\n", name); + return PTR_ERR(phy->clk); + } + + snprintf(name, sizeof(name), "usb%d_reset", i); + phy->reset = devm_reset_control_get(dev, name); + if (IS_ERR(phy->reset)) { + dev_err(dev, "failed to get reset %s\n", name); + return PTR_ERR(phy->reset); + } + + if (i) { /* No pmu for usbc0 */ + snprintf(name, sizeof(name), "pmu%d", i); + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, name); + phy->pmu = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pmu)) + return PTR_ERR(phy->pmu); + } + + phy->phy = devm_phy_create(dev, &sun4i_usb_phy_ops, NULL); + if (IS_ERR(phy->phy)) { + dev_err(dev, "failed to create PHY %d\n", i); + return PTR_ERR(phy->phy); + } + + phy->index = i; + phy_set_drvdata(phy->phy, &data->phys[i]); + } + + dev_set_drvdata(dev, data); + phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + return 0; +} + +static const struct of_device_id sun4i_usb_phy_of_match[] = { + { .compatible = "allwinner,sun4i-a10-usb-phy" }, + { .compatible = "allwinner,sun5i-a13-usb-phy" }, + { .compatible = "allwinner,sun6i-a31-usb-phy" }, + { .compatible = "allwinner,sun7i-a20-usb-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); + +static struct platform_driver sun4i_usb_phy_driver = { + .probe = sun4i_usb_phy_probe, + .driver = { + .of_match_table = sun4i_usb_phy_of_match, + .name = "sun4i-usb-phy", + .owner = THIS_MODULE, + } +}; +module_platform_driver(sun4i_usb_phy_driver); + +MODULE_DESCRIPTION("Allwinner sun4i USB phy driver"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c new file mode 100644 index 00000000000..59136765461 --- /dev/null +++ b/drivers/phy/phy-ti-pipe3.c @@ -0,0 +1,470 @@ +/* + * 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> + +#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 + +/* + * 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 pipe3_dpll_map *dpll_map; +}; + +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_power_off(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + + omap_control_phy_power(phy->control_dev, 0); + + return 0; +} + +static int ti_pipe3_power_on(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + + omap_control_phy_power(phy->control_dev, 1); + + return 0; +} + +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) + break; + } while (!time_after(jiffies, timeout)); + + if (!(val & PLL_LOCK)) { + dev_err(phy->dev, "DPLL failed to lock\n"); + return -EBUSY; + } + + return 0; +} + +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; + + /* 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); + } + + /* Program the DPLL only if not locked */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if (!(val & PLL_LOCK)) + if (ti_pipe3_dpll_program(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; + + /* SATA DPLL can't be powered down due to Errata i783 */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) + return 0; + + /* 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; + } + + return 0; +} +static 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, +}; + +#ifdef CONFIG_OF +static const struct of_device_id ti_pipe3_id_table[]; +#endif + +static int ti_pipe3_probe(struct platform_device *pdev) +{ + struct ti_pipe3 *phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct resource *res; + struct device_node *node = pdev->dev.of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + const struct of_device_id *match; + + match = of_match_device(of_match_ptr(ti_pipe3_id_table), &pdev->dev); + if (!match) + return -EINVAL; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + dev_err(&pdev->dev, "unable to alloc mem for TI PIPE3 PHY\n"); + return -ENOMEM; + } + + phy->dpll_map = (struct pipe3_dpll_map *)match->data; + if (!phy->dpll_map) { + dev_err(&pdev->dev, "no DPLL data\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->pll_ctrl_base)) + return PTR_ERR(phy->pll_ctrl_base); + + phy->dev = &pdev->dev; + + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + + phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get wkupclk\n"); + return PTR_ERR(phy->wkupclk); + } + + phy->refclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->refclk)) { + dev_err(&pdev->dev, "unable to get refclk\n"); + return PTR_ERR(phy->refclk); + } + } else { + phy->wkupclk = ERR_PTR(-ENODEV); + phy->refclk = ERR_PTR(-ENODEV); + } + + phy->sys_clk = devm_clk_get(phy->dev, "sysclk"); + if (IS_ERR(phy->sys_clk)) { + dev_err(&pdev->dev, "unable to get sysclk\n"); + return -EINVAL; + } + + 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; + + omap_control_phy_power(phy->control_dev, 0); + + platform_set_drvdata(pdev, phy); + pm_runtime_enable(phy->dev); + + generic_phy = devm_phy_create(phy->dev, &ops, NULL); + 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); + + pm_runtime_get(&pdev->dev); + + return 0; +} + +static int ti_pipe3_remove(struct platform_device *pdev) +{ + if (!pm_runtime_suspended(&pdev->dev)) + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME + +static int ti_pipe3_runtime_suspend(struct device *dev) +{ + struct ti_pipe3 *phy = dev_get_drvdata(dev); + + if (!IS_ERR(phy->wkupclk)) + clk_disable_unprepare(phy->wkupclk); + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); + + return 0; +} + +static int ti_pipe3_runtime_resume(struct device *dev) +{ + u32 ret = 0; + struct ti_pipe3 *phy = dev_get_drvdata(dev); + + if (!IS_ERR(phy->refclk)) { + ret = clk_prepare_enable(phy->refclk); + if (ret) { + dev_err(phy->dev, "Failed to enable refclk %d\n", ret); + goto err1; + } + } + + 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 err2; + } + } + + return 0; + +err2: + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); + +err1: + return ret; +} + +static const struct dev_pm_ops ti_pipe3_pm_ops = { + SET_RUNTIME_PM_OPS(ti_pipe3_runtime_suspend, + ti_pipe3_runtime_resume, NULL) +}; + +#define DEV_PM_OPS (&ti_pipe3_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +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, + }, + {} +}; +MODULE_DEVICE_TABLE(of, ti_pipe3_id_table); +#endif + +static struct platform_driver ti_pipe3_driver = { + .probe = ti_pipe3_probe, + .remove = ti_pipe3_remove, + .driver = { + .name = "ti-pipe3", + .owner = THIS_MODULE, + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(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/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c new file mode 100644 index 00000000000..2e0e9b3774c --- /dev/null +++ b/drivers/phy/phy-twl4030-usb.c @@ -0,0 +1,815 @@ +/* + * 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/spinlock.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/usb/musb-omap.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) + +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 */ + spinlock_t lock; + + /* pin configuration */ + enum twl4030_usb_mode usb_mode; + + int irq; + enum omap_musb_vbus_id_status linkstat; + bool vbus_supplied; + u8 asleep; + bool irq_enabled; + + 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 omap_musb_vbus_id_status + twl4030_usb_linkstat(struct twl4030_usb *twl) +{ + int status; + enum omap_musb_vbus_id_status linkstat = OMAP_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 = OMAP_MUSB_ID_GROUND; + else if (status & BIT(7)) + linkstat = OMAP_MUSB_VBUS_VALID; + else + linkstat = OMAP_MUSB_VBUS_OFF; + } else { + if (twl->linkstat != OMAP_MUSB_UNKNOWN) + linkstat = OMAP_MUSB_VBUS_OFF; + } + + 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 void twl4030_phy_power(struct twl4030_usb *twl, int on) +{ + int ret; + + if (on) { + ret = regulator_enable(twl->usb3v1); + if (ret) + dev_err(twl->dev, "Failed to enable usb3v1\n"); + + ret = regulator_enable(twl->usb1v8); + if (ret) + 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); + + ret = regulator_enable(twl->usb1v5); + if (ret) + 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)); + } else { + __twl4030_phy_power(twl, 0); + regulator_disable(twl->usb1v5); + regulator_disable(twl->usb1v8); + regulator_disable(twl->usb3v1); + } +} + +static int twl4030_phy_power_off(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + if (twl->asleep) + return 0; + + twl4030_phy_power(twl, 0); + twl->asleep = 1; + dev_dbg(twl->dev, "%s\n", __func__); + return 0; +} + +static void __twl4030_phy_power_on(struct twl4030_usb *twl) +{ + twl4030_phy_power(twl, 1); + 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); +} + +static int twl4030_phy_power_on(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + if (!twl->asleep) + return 0; + __twl4030_phy_power_on(twl); + twl->asleep = 0; + dev_dbg(twl->dev, "%s\n", __func__); + + /* + * XXX When VBUS gets driven after musb goes to A mode, + * ID_PRES related interrupts no longer arrive, why? + * Register itself is updated fine though, so we must poll. + */ + if (twl->linkstat == OMAP_MUSB_ID_GROUND) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } + 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); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&twl->lock, flags); + ret = sprintf(buf, "%s\n", + twl->vbus_supplied ? "on" : "off"); + spin_unlock_irqrestore(&twl->lock, flags); + + 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 omap_musb_vbus_id_status status; + bool status_changed = false; + + status = twl4030_usb_linkstat(twl); + + spin_lock_irq(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + twl->linkstat = status; + status_changed = true; + } + spin_unlock_irq(&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. + */ + omap_musb_mailbox(status); + } + 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); + enum omap_musb_vbus_id_status status; + bool status_changed = false; + + status = twl4030_usb_linkstat(twl); + + spin_lock_irq(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + twl->linkstat = status; + status_changed = true; + } + spin_unlock_irq(&twl->lock); + + if (status_changed) { + dev_dbg(twl->dev, "handle missing status change to %d\n", + status); + omap_musb_mailbox(status); + } + + /* don't schedule during sleep - irq works right then */ + if (status == OMAP_MUSB_ID_GROUND && !twl->asleep) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } +} + +static int twl4030_phy_init(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + enum omap_musb_vbus_id_status status; + + /* + * Start in sleep state, we'll get called through set_suspend() + * callback when musb is runtime resumed and it's time to start. + */ + __twl4030_phy_power(twl, 0); + twl->asleep = 1; + + status = twl4030_usb_linkstat(twl); + twl->linkstat = status; + + if (status == OMAP_MUSB_ID_GROUND || status == OMAP_MUSB_VBUS_VALID) { + omap_musb_mailbox(twl->linkstat); + twl4030_phy_power_on(phy); + } + + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + 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->phy->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->phy->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 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; + struct phy_init_data *init_data = NULL; + + 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; + init_data = pdata->init_data; + } 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->asleep = 1; + twl->linkstat = OMAP_MUSB_UNKNOWN; + + twl->phy.dev = twl->dev; + twl->phy.label = "twl4030"; + twl->phy.otg = otg; + twl->phy.type = USB_PHY_TYPE_USB2; + + otg->phy = &twl->phy; + otg->set_host = twl4030_set_host; + otg->set_peripheral = twl4030_set_peripheral; + + phy = devm_phy_create(twl->dev, &ops, init_data); + 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 spinlock for workqueue */ + spin_lock_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); + + /* 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. + */ + twl->irq_enabled = true; + 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; + } + + 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; + + 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); + + /* 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); + + if (!twl->asleep) + twl4030_phy_power(twl, 0); + + 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", + .owner = THIS_MODULE, + .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"); diff --git a/drivers/phy/phy-xgene.c b/drivers/phy/phy-xgene.c new file mode 100644 index 00000000000..4aa1ccd1511 --- /dev/null +++ b/drivers/phy/phy-xgene.c @@ -0,0 +1,1750 @@ +/* + * AppliedMicro X-Gene Multi-purpose PHY driver + * + * Copyright (c) 2014, Applied Micro Circuits Corporation + * Author: Loc Ho <lho@apm.com> + * Tuan Phan <tphan@apm.com> + * Suman Tripathi <stripathi@apm.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, see <http://www.gnu.org/licenses/>. + * + * The APM X-Gene PHY consists of two PLL clock macro's (CMU) and lanes. + * The first PLL clock macro is used for internal reference clock. The second + * PLL clock macro is used to generate the clock for the PHY. This driver + * configures the first PLL CMU, the second PLL CMU, and programs the PHY to + * operate according to the mode of operation. The first PLL CMU is only + * required if internal clock is enabled. + * + * Logical Layer Out Of HW module units: + * + * ----------------- + * | Internal | |------| + * | Ref PLL CMU |----| | ------------- --------- + * ------------ ---- | MUX |-----|PHY PLL CMU|----| Serdes| + * | | | | --------- + * External Clock ------| | ------------- + * |------| + * + * The Ref PLL CMU CSR (Configuration System Registers) is accessed + * indirectly from the SDS offset at 0x2000. It is only required for + * internal reference clock. + * The PHY PLL CMU CSR is accessed indirectly from the SDS offset at 0x0000. + * The Serdes CSR is accessed indirectly from the SDS offset at 0x0400. + * + * The Ref PLL CMU can be located within the same PHY IP or outside the PHY IP + * due to shared Ref PLL CMU. For PHY with Ref PLL CMU shared with another IP, + * it is located outside the PHY IP. This is the case for the PHY located + * at 0x1f23a000 (SATA Port 4/5). For such PHY, another resource is required + * to located the SDS/Ref PLL CMU module and its clock for that IP enabled. + * + * Currently, this driver only supports Gen3 SATA mode with external clock. + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/phy/phy.h> +#include <linux/clk.h> + +/* Max 2 lanes per a PHY unit */ +#define MAX_LANE 2 + +/* Register offset inside the PHY */ +#define SERDES_PLL_INDIRECT_OFFSET 0x0000 +#define SERDES_PLL_REF_INDIRECT_OFFSET 0x2000 +#define SERDES_INDIRECT_OFFSET 0x0400 +#define SERDES_LANE_STRIDE 0x0200 + +/* Some default Serdes parameters */ +#define DEFAULT_SATA_TXBOOST_GAIN { 0x1e, 0x1e, 0x1e } +#define DEFAULT_SATA_TXEYEDIRECTION { 0x0, 0x0, 0x0 } +#define DEFAULT_SATA_TXEYETUNING { 0xa, 0xa, 0xa } +#define DEFAULT_SATA_SPD_SEL { 0x1, 0x3, 0x7 } +#define DEFAULT_SATA_TXAMP { 0x8, 0x8, 0x8 } +#define DEFAULT_SATA_TXCN1 { 0x2, 0x2, 0x2 } +#define DEFAULT_SATA_TXCN2 { 0x0, 0x0, 0x0 } +#define DEFAULT_SATA_TXCP1 { 0xa, 0xa, 0xa } + +#define SATA_SPD_SEL_GEN3 0x7 +#define SATA_SPD_SEL_GEN2 0x3 +#define SATA_SPD_SEL_GEN1 0x1 + +#define SSC_DISABLE 0 +#define SSC_ENABLE 1 + +#define FBDIV_VAL_50M 0x77 +#define REFDIV_VAL_50M 0x1 +#define FBDIV_VAL_100M 0x3B +#define REFDIV_VAL_100M 0x0 + +/* SATA Clock/Reset CSR */ +#define SATACLKENREG 0x00000000 +#define SATA0_CORE_CLKEN 0x00000002 +#define SATA1_CORE_CLKEN 0x00000004 +#define SATASRESETREG 0x00000004 +#define SATA_MEM_RESET_MASK 0x00000020 +#define SATA_MEM_RESET_RD(src) (((src) & 0x00000020) >> 5) +#define SATA_SDS_RESET_MASK 0x00000004 +#define SATA_CSR_RESET_MASK 0x00000001 +#define SATA_CORE_RESET_MASK 0x00000002 +#define SATA_PMCLK_RESET_MASK 0x00000010 +#define SATA_PCLK_RESET_MASK 0x00000008 + +/* SDS CSR used for PHY Indirect access */ +#define SATA_ENET_SDS_PCS_CTL0 0x00000000 +#define REGSPEC_CFG_I_TX_WORDMODE0_SET(dst, src) \ + (((dst) & ~0x00070000) | (((u32) (src) << 16) & 0x00070000)) +#define REGSPEC_CFG_I_RX_WORDMODE0_SET(dst, src) \ + (((dst) & ~0x00e00000) | (((u32) (src) << 21) & 0x00e00000)) +#define SATA_ENET_SDS_CTL0 0x0000000c +#define REGSPEC_CFG_I_CUSTOMER_PIN_MODE0_SET(dst, src) \ + (((dst) & ~0x00007fff) | (((u32) (src)) & 0x00007fff)) +#define SATA_ENET_SDS_CTL1 0x00000010 +#define CFG_I_SPD_SEL_CDR_OVR1_SET(dst, src) \ + (((dst) & ~0x0000000f) | (((u32) (src)) & 0x0000000f)) +#define SATA_ENET_SDS_RST_CTL 0x00000024 +#define SATA_ENET_SDS_IND_CMD_REG 0x0000003c +#define CFG_IND_WR_CMD_MASK 0x00000001 +#define CFG_IND_RD_CMD_MASK 0x00000002 +#define CFG_IND_CMD_DONE_MASK 0x00000004 +#define CFG_IND_ADDR_SET(dst, src) \ + (((dst) & ~0x003ffff0) | (((u32) (src) << 4) & 0x003ffff0)) +#define SATA_ENET_SDS_IND_RDATA_REG 0x00000040 +#define SATA_ENET_SDS_IND_WDATA_REG 0x00000044 +#define SATA_ENET_CLK_MACRO_REG 0x0000004c +#define I_RESET_B_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src)) & 0x00000001)) +#define I_PLL_FBDIV_SET(dst, src) \ + (((dst) & ~0x001ff000) | (((u32) (src) << 12) & 0x001ff000)) +#define I_CUSTOMEROV_SET(dst, src) \ + (((dst) & ~0x00000f80) | (((u32) (src) << 7) & 0x00000f80)) +#define O_PLL_LOCK_RD(src) (((src) & 0x40000000) >> 30) +#define O_PLL_READY_RD(src) (((src) & 0x80000000) >> 31) + +/* PLL Clock Macro Unit (CMU) CSR accessing from SDS indirectly */ +#define CMU_REG0 0x00000 +#define CMU_REG0_PLL_REF_SEL_MASK 0x00002000 +#define CMU_REG0_PLL_REF_SEL_SET(dst, src) \ + (((dst) & ~0x00002000) | (((u32) (src) << 13) & 0x00002000)) +#define CMU_REG0_PDOWN_MASK 0x00004000 +#define CMU_REG0_CAL_COUNT_RESOL_SET(dst, src) \ + (((dst) & ~0x000000e0) | (((u32) (src) << 5) & 0x000000e0)) +#define CMU_REG1 0x00002 +#define CMU_REG1_PLL_CP_SET(dst, src) \ + (((dst) & ~0x00003c00) | (((u32) (src) << 10) & 0x00003c00)) +#define CMU_REG1_PLL_MANUALCAL_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define CMU_REG1_PLL_CP_SEL_SET(dst, src) \ + (((dst) & ~0x000003e0) | (((u32) (src) << 5) & 0x000003e0)) +#define CMU_REG1_REFCLK_CMOS_SEL_MASK 0x00000001 +#define CMU_REG1_REFCLK_CMOS_SEL_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define CMU_REG2 0x00004 +#define CMU_REG2_PLL_REFDIV_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define CMU_REG2_PLL_LFRES_SET(dst, src) \ + (((dst) & ~0x0000001e) | (((u32) (src) << 1) & 0x0000001e)) +#define CMU_REG2_PLL_FBDIV_SET(dst, src) \ + (((dst) & ~0x00003fe0) | (((u32) (src) << 5) & 0x00003fe0)) +#define CMU_REG3 0x00006 +#define CMU_REG3_VCOVARSEL_SET(dst, src) \ + (((dst) & ~0x0000000f) | (((u32) (src) << 0) & 0x0000000f)) +#define CMU_REG3_VCO_MOMSEL_INIT_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define CMU_REG3_VCO_MANMOMSEL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define CMU_REG4 0x00008 +#define CMU_REG5 0x0000a +#define CMU_REG5_PLL_LFSMCAP_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define CMU_REG5_PLL_LOCK_RESOLUTION_SET(dst, src) \ + (((dst) & ~0x0000000e) | (((u32) (src) << 1) & 0x0000000e)) +#define CMU_REG5_PLL_LFCAP_SET(dst, src) \ + (((dst) & ~0x00003000) | (((u32) (src) << 12) & 0x00003000)) +#define CMU_REG5_PLL_RESETB_MASK 0x00000001 +#define CMU_REG6 0x0000c +#define CMU_REG6_PLL_VREGTRIM_SET(dst, src) \ + (((dst) & ~0x00000600) | (((u32) (src) << 9) & 0x00000600)) +#define CMU_REG6_MAN_PVT_CAL_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define CMU_REG7 0x0000e +#define CMU_REG7_PLL_CALIB_DONE_RD(src) ((0x00004000 & (u32) (src)) >> 14) +#define CMU_REG7_VCO_CAL_FAIL_RD(src) ((0x00000c00 & (u32) (src)) >> 10) +#define CMU_REG8 0x00010 +#define CMU_REG9 0x00012 +#define CMU_REG9_WORD_LEN_8BIT 0x000 +#define CMU_REG9_WORD_LEN_10BIT 0x001 +#define CMU_REG9_WORD_LEN_16BIT 0x002 +#define CMU_REG9_WORD_LEN_20BIT 0x003 +#define CMU_REG9_WORD_LEN_32BIT 0x004 +#define CMU_REG9_WORD_LEN_40BIT 0x005 +#define CMU_REG9_WORD_LEN_64BIT 0x006 +#define CMU_REG9_WORD_LEN_66BIT 0x007 +#define CMU_REG9_TX_WORD_MODE_CH1_SET(dst, src) \ + (((dst) & ~0x00000380) | (((u32) (src) << 7) & 0x00000380)) +#define CMU_REG9_TX_WORD_MODE_CH0_SET(dst, src) \ + (((dst) & ~0x00000070) | (((u32) (src) << 4) & 0x00000070)) +#define CMU_REG9_PLL_POST_DIVBY2_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define CMU_REG9_VBG_BYPASSB_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define CMU_REG9_IGEN_BYPASS_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define CMU_REG10 0x00014 +#define CMU_REG10_VREG_REFSEL_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define CMU_REG11 0x00016 +#define CMU_REG12 0x00018 +#define CMU_REG12_STATE_DELAY9_SET(dst, src) \ + (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0)) +#define CMU_REG13 0x0001a +#define CMU_REG14 0x0001c +#define CMU_REG15 0x0001e +#define CMU_REG16 0x00020 +#define CMU_REG16_PVT_DN_MAN_ENA_MASK 0x00000001 +#define CMU_REG16_PVT_UP_MAN_ENA_MASK 0x00000002 +#define CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(dst, src) \ + (((dst) & ~0x0000001c) | (((u32) (src) << 2) & 0x0000001c)) +#define CMU_REG16_CALIBRATION_DONE_OVERRIDE_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define CMU_REG16_BYPASS_PLL_LOCK_SET(dst, src) \ + (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020)) +#define CMU_REG17 0x00022 +#define CMU_REG17_PVT_CODE_R2A_SET(dst, src) \ + (((dst) & ~0x00007f00) | (((u32) (src) << 8) & 0x00007f00)) +#define CMU_REG17_RESERVED_7_SET(dst, src) \ + (((dst) & ~0x000000e0) | (((u32) (src) << 5) & 0x000000e0)) +#define CMU_REG17_PVT_TERM_MAN_ENA_MASK 0x00008000 +#define CMU_REG18 0x00024 +#define CMU_REG19 0x00026 +#define CMU_REG20 0x00028 +#define CMU_REG21 0x0002a +#define CMU_REG22 0x0002c +#define CMU_REG23 0x0002e +#define CMU_REG24 0x00030 +#define CMU_REG25 0x00032 +#define CMU_REG26 0x00034 +#define CMU_REG26_FORCE_PLL_LOCK_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define CMU_REG27 0x00036 +#define CMU_REG28 0x00038 +#define CMU_REG29 0x0003a +#define CMU_REG30 0x0003c +#define CMU_REG30_LOCK_COUNT_SET(dst, src) \ + (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006)) +#define CMU_REG30_PCIE_MODE_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define CMU_REG31 0x0003e +#define CMU_REG32 0x00040 +#define CMU_REG32_FORCE_VCOCAL_START_MASK 0x00004000 +#define CMU_REG32_PVT_CAL_WAIT_SEL_SET(dst, src) \ + (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006)) +#define CMU_REG32_IREF_ADJ_SET(dst, src) \ + (((dst) & ~0x00000180) | (((u32) (src) << 7) & 0x00000180)) +#define CMU_REG33 0x00042 +#define CMU_REG34 0x00044 +#define CMU_REG34_VCO_CAL_VTH_LO_MAX_SET(dst, src) \ + (((dst) & ~0x0000000f) | (((u32) (src) << 0) & 0x0000000f)) +#define CMU_REG34_VCO_CAL_VTH_HI_MAX_SET(dst, src) \ + (((dst) & ~0x00000f00) | (((u32) (src) << 8) & 0x00000f00)) +#define CMU_REG34_VCO_CAL_VTH_LO_MIN_SET(dst, src) \ + (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0)) +#define CMU_REG34_VCO_CAL_VTH_HI_MIN_SET(dst, src) \ + (((dst) & ~0x0000f000) | (((u32) (src) << 12) & 0x0000f000)) +#define CMU_REG35 0x00046 +#define CMU_REG35_PLL_SSC_MOD_SET(dst, src) \ + (((dst) & ~0x0000fe00) | (((u32) (src) << 9) & 0x0000fe00)) +#define CMU_REG36 0x00048 +#define CMU_REG36_PLL_SSC_EN_SET(dst, src) \ + (((dst) & ~0x00000010) | (((u32) (src) << 4) & 0x00000010)) +#define CMU_REG36_PLL_SSC_VSTEP_SET(dst, src) \ + (((dst) & ~0x0000ffc0) | (((u32) (src) << 6) & 0x0000ffc0)) +#define CMU_REG36_PLL_SSC_DSMSEL_SET(dst, src) \ + (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020)) +#define CMU_REG37 0x0004a +#define CMU_REG38 0x0004c +#define CMU_REG39 0x0004e + +/* PHY lane CSR accessing from SDS indirectly */ +#define RXTX_REG0 0x000 +#define RXTX_REG0_CTLE_EQ_HR_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG0_CTLE_EQ_QR_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG0_CTLE_EQ_FR_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG1 0x002 +#define RXTX_REG1_RXACVCM_SET(dst, src) \ + (((dst) & ~0x0000f000) | (((u32) (src) << 12) & 0x0000f000)) +#define RXTX_REG1_CTLE_EQ_SET(dst, src) \ + (((dst) & ~0x00000f80) | (((u32) (src) << 7) & 0x00000f80)) +#define RXTX_REG1_RXVREG1_SET(dst, src) \ + (((dst) & ~0x00000060) | (((u32) (src) << 5) & 0x00000060)) +#define RXTX_REG1_RXIREF_ADJ_SET(dst, src) \ + (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006)) +#define RXTX_REG2 0x004 +#define RXTX_REG2_VTT_ENA_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG2_TX_FIFO_ENA_SET(dst, src) \ + (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020)) +#define RXTX_REG2_VTT_SEL_SET(dst, src) \ + (((dst) & ~0x000000c0) | (((u32) (src) << 6) & 0x000000c0)) +#define RXTX_REG4 0x008 +#define RXTX_REG4_TX_LOOPBACK_BUF_EN_MASK 0x00000040 +#define RXTX_REG4_TX_DATA_RATE_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define RXTX_REG4_TX_WORD_MODE_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG5 0x00a +#define RXTX_REG5_TX_CN1_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG5_TX_CP1_SET(dst, src) \ + (((dst) & ~0x000007e0) | (((u32) (src) << 5) & 0x000007e0)) +#define RXTX_REG5_TX_CN2_SET(dst, src) \ + (((dst) & ~0x0000001f) | (((u32) (src) << 0) & 0x0000001f)) +#define RXTX_REG6 0x00c +#define RXTX_REG6_TXAMP_CNTL_SET(dst, src) \ + (((dst) & ~0x00000780) | (((u32) (src) << 7) & 0x00000780)) +#define RXTX_REG6_TXAMP_ENA_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define RXTX_REG6_RX_BIST_ERRCNT_RD_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define RXTX_REG6_TX_IDLE_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG6_RX_BIST_RESYNC_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG7 0x00e +#define RXTX_REG7_RESETB_RXD_MASK 0x00000100 +#define RXTX_REG7_RESETB_RXA_MASK 0x00000080 +#define RXTX_REG7_BIST_ENA_RX_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define RXTX_REG7_RX_WORD_MODE_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG8 0x010 +#define RXTX_REG8_CDR_LOOP_ENA_SET(dst, src) \ + (((dst) & ~0x00004000) | (((u32) (src) << 14) & 0x00004000)) +#define RXTX_REG8_CDR_BYPASS_RXLOS_SET(dst, src) \ + (((dst) & ~0x00000800) | (((u32) (src) << 11) & 0x00000800)) +#define RXTX_REG8_SSC_ENABLE_SET(dst, src) \ + (((dst) & ~0x00000200) | (((u32) (src) << 9) & 0x00000200)) +#define RXTX_REG8_SD_VREF_SET(dst, src) \ + (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0)) +#define RXTX_REG8_SD_DISABLE_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG7 0x00e +#define RXTX_REG7_RESETB_RXD_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG7_RESETB_RXA_SET(dst, src) \ + (((dst) & ~0x00000080) | (((u32) (src) << 7) & 0x00000080)) +#define RXTX_REG7_LOOP_BACK_ENA_CTLE_MASK 0x00004000 +#define RXTX_REG7_LOOP_BACK_ENA_CTLE_SET(dst, src) \ + (((dst) & ~0x00004000) | (((u32) (src) << 14) & 0x00004000)) +#define RXTX_REG11 0x016 +#define RXTX_REG11_PHASE_ADJUST_LIMIT_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG12 0x018 +#define RXTX_REG12_LATCH_OFF_ENA_SET(dst, src) \ + (((dst) & ~0x00002000) | (((u32) (src) << 13) & 0x00002000)) +#define RXTX_REG12_SUMOS_ENABLE_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define RXTX_REG12_RX_DET_TERM_ENABLE_MASK 0x00000002 +#define RXTX_REG12_RX_DET_TERM_ENABLE_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG13 0x01a +#define RXTX_REG14 0x01c +#define RXTX_REG14_CLTE_LATCAL_MAN_PROG_SET(dst, src) \ + (((dst) & ~0x0000003f) | (((u32) (src) << 0) & 0x0000003f)) +#define RXTX_REG14_CTLE_LATCAL_MAN_ENA_SET(dst, src) \ + (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040)) +#define RXTX_REG26 0x034 +#define RXTX_REG26_PERIOD_ERROR_LATCH_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG26_BLWC_ENA_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG21 0x02a +#define RXTX_REG21_DO_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG21_XO_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG21_LATCH_CAL_FAIL_ODD_RD(src) ((0x0000000f & (u32)(src))) +#define RXTX_REG22 0x02c +#define RXTX_REG22_SO_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG22_EO_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG22_LATCH_CAL_FAIL_EVEN_RD(src) ((0x0000000f & (u32)(src))) +#define RXTX_REG23 0x02e +#define RXTX_REG23_DE_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG23_XE_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG24 0x030 +#define RXTX_REG24_EE_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10) +#define RXTX_REG24_SE_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4) +#define RXTX_REG27 0x036 +#define RXTX_REG28 0x038 +#define RXTX_REG31 0x03e +#define RXTX_REG38 0x04c +#define RXTX_REG38_CUSTOMER_PINMODE_INV_SET(dst, src) \ + (((dst) & 0x0000fffe) | (((u32) (src) << 1) & 0x0000fffe)) +#define RXTX_REG39 0x04e +#define RXTX_REG40 0x050 +#define RXTX_REG41 0x052 +#define RXTX_REG42 0x054 +#define RXTX_REG43 0x056 +#define RXTX_REG44 0x058 +#define RXTX_REG45 0x05a +#define RXTX_REG46 0x05c +#define RXTX_REG47 0x05e +#define RXTX_REG48 0x060 +#define RXTX_REG49 0x062 +#define RXTX_REG50 0x064 +#define RXTX_REG51 0x066 +#define RXTX_REG52 0x068 +#define RXTX_REG53 0x06a +#define RXTX_REG54 0x06c +#define RXTX_REG55 0x06e +#define RXTX_REG61 0x07a +#define RXTX_REG61_ISCAN_INBERT_SET(dst, src) \ + (((dst) & ~0x00000010) | (((u32) (src) << 4) & 0x00000010)) +#define RXTX_REG61_LOADFREQ_SHIFT_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG61_EYE_COUNT_WIDTH_SEL_SET(dst, src) \ + (((dst) & ~0x000000c0) | (((u32) (src) << 6) & 0x000000c0)) +#define RXTX_REG61_SPD_SEL_CDR_SET(dst, src) \ + (((dst) & ~0x00003c00) | (((u32) (src) << 10) & 0x00003c00)) +#define RXTX_REG62 0x07c +#define RXTX_REG62_PERIOD_H1_QLATCH_SET(dst, src) \ + (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800)) +#define RXTX_REG81 0x0a2 +#define RXTX_REG89_MU_TH7_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG89_MU_TH8_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG89_MU_TH9_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG96 0x0c0 +#define RXTX_REG96_MU_FREQ1_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG96_MU_FREQ2_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG96_MU_FREQ3_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG99 0x0c6 +#define RXTX_REG99_MU_PHASE1_SET(dst, src) \ + (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800)) +#define RXTX_REG99_MU_PHASE2_SET(dst, src) \ + (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0)) +#define RXTX_REG99_MU_PHASE3_SET(dst, src) \ + (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e)) +#define RXTX_REG102 0x0cc +#define RXTX_REG102_FREQLOOP_LIMIT_SET(dst, src) \ + (((dst) & ~0x00000060) | (((u32) (src) << 5) & 0x00000060)) +#define RXTX_REG114 0x0e4 +#define RXTX_REG121 0x0f2 +#define RXTX_REG121_SUMOS_CAL_CODE_RD(src) ((0x0000003e & (u32)(src)) >> 0x1) +#define RXTX_REG125 0x0fa +#define RXTX_REG125_PQ_REG_SET(dst, src) \ + (((dst) & ~0x0000fe00) | (((u32) (src) << 9) & 0x0000fe00)) +#define RXTX_REG125_SIGN_PQ_SET(dst, src) \ + (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100)) +#define RXTX_REG125_SIGN_PQ_2C_SET(dst, src) \ + (((dst) & ~0x00000080) | (((u32) (src) << 7) & 0x00000080)) +#define RXTX_REG125_PHZ_MANUALCODE_SET(dst, src) \ + (((dst) & ~0x0000007c) | (((u32) (src) << 2) & 0x0000007c)) +#define RXTX_REG125_PHZ_MANUAL_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG127 0x0fe +#define RXTX_REG127_FORCE_SUM_CAL_START_MASK 0x00000002 +#define RXTX_REG127_FORCE_LAT_CAL_START_MASK 0x00000004 +#define RXTX_REG127_FORCE_SUM_CAL_START_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG127_FORCE_LAT_CAL_START_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define RXTX_REG127_LATCH_MAN_CAL_ENA_SET(dst, src) \ + (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008)) +#define RXTX_REG127_DO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG127_XO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG128 0x100 +#define RXTX_REG128_LATCH_CAL_WAIT_SEL_SET(dst, src) \ + (((dst) & ~0x0000000c) | (((u32) (src) << 2) & 0x0000000c)) +#define RXTX_REG128_EO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG128_SO_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG129 0x102 +#define RXTX_REG129_DE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG129_XE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG130 0x104 +#define RXTX_REG130_EE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00)) +#define RXTX_REG130_SE_LATCH_MANCAL_SET(dst, src) \ + (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0)) +#define RXTX_REG145 0x122 +#define RXTX_REG145_TX_IDLE_SATA_SET(dst, src) \ + (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001)) +#define RXTX_REG145_RXES_ENA_SET(dst, src) \ + (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002)) +#define RXTX_REG145_RXDFE_CONFIG_SET(dst, src) \ + (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000)) +#define RXTX_REG145_RXVWES_LATENA_SET(dst, src) \ + (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004)) +#define RXTX_REG147 0x126 +#define RXTX_REG148 0x128 + +/* Clock macro type */ +enum cmu_type_t { + REF_CMU = 0, /* Clock macro is the internal reference clock */ + PHY_CMU = 1, /* Clock macro is the PLL for the Serdes */ +}; + +enum mux_type_t { + MUX_SELECT_ATA = 0, /* Switch the MUX to ATA */ + MUX_SELECT_SGMMII = 0, /* Switch the MUX to SGMII */ +}; + +enum clk_type_t { + CLK_EXT_DIFF = 0, /* External differential */ + CLK_INT_DIFF = 1, /* Internal differential */ + CLK_INT_SING = 2, /* Internal single ended */ +}; + +enum phy_mode { + MODE_SATA = 0, /* List them for simple reference */ + MODE_SGMII = 1, + MODE_PCIE = 2, + MODE_USB = 3, + MODE_XFI = 4, + MODE_MAX +}; + +struct xgene_sata_override_param { + u32 speed[MAX_LANE]; /* Index for override parameter per lane */ + u32 txspeed[3]; /* Tx speed */ + u32 txboostgain[MAX_LANE*3]; /* Tx freq boost and gain control */ + u32 txeyetuning[MAX_LANE*3]; /* Tx eye tuning */ + u32 txeyedirection[MAX_LANE*3]; /* Tx eye tuning direction */ + u32 txamplitude[MAX_LANE*3]; /* Tx amplitude control */ + u32 txprecursor_cn1[MAX_LANE*3]; /* Tx emphasis taps 1st pre-cursor */ + u32 txprecursor_cn2[MAX_LANE*3]; /* Tx emphasis taps 2nd pre-cursor */ + u32 txpostcursor_cp1[MAX_LANE*3]; /* Tx emphasis taps post-cursor */ +}; + +struct xgene_phy_ctx { + struct device *dev; + struct phy *phy; + enum phy_mode mode; /* Mode of operation */ + enum clk_type_t clk_type; /* Input clock selection */ + void __iomem *sds_base; /* PHY CSR base addr */ + struct clk *clk; /* Optional clock */ + + /* Override Serdes parameters */ + struct xgene_sata_override_param sata_param; +}; + +/* + * For chip earlier than A3 version, enable this flag. + * To enable, pass boot argument phy_xgene.preA3Chip=1 + */ +static int preA3Chip; +MODULE_PARM_DESC(preA3Chip, "Enable pre-A3 chip support (1=enable 0=disable)"); +module_param_named(preA3Chip, preA3Chip, int, 0444); + +static void sds_wr(void __iomem *csr_base, u32 indirect_cmd_reg, + u32 indirect_data_reg, u32 addr, u32 data) +{ + unsigned long deadline = jiffies + HZ; + u32 val; + u32 cmd; + + cmd = CFG_IND_WR_CMD_MASK | CFG_IND_CMD_DONE_MASK; + cmd = CFG_IND_ADDR_SET(cmd, addr); + writel(data, csr_base + indirect_data_reg); + readl(csr_base + indirect_data_reg); /* Force a barrier */ + writel(cmd, csr_base + indirect_cmd_reg); + readl(csr_base + indirect_cmd_reg); /* Force a barrier */ + do { + val = readl(csr_base + indirect_cmd_reg); + } while (!(val & CFG_IND_CMD_DONE_MASK) && + time_before(jiffies, deadline)); + if (!(val & CFG_IND_CMD_DONE_MASK)) + pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n", + csr_base + indirect_cmd_reg, addr, data); +} + +static void sds_rd(void __iomem *csr_base, u32 indirect_cmd_reg, + u32 indirect_data_reg, u32 addr, u32 *data) +{ + unsigned long deadline = jiffies + HZ; + u32 val; + u32 cmd; + + cmd = CFG_IND_RD_CMD_MASK | CFG_IND_CMD_DONE_MASK; + cmd = CFG_IND_ADDR_SET(cmd, addr); + writel(cmd, csr_base + indirect_cmd_reg); + readl(csr_base + indirect_cmd_reg); /* Force a barrier */ + do { + val = readl(csr_base + indirect_cmd_reg); + } while (!(val & CFG_IND_CMD_DONE_MASK) && + time_before(jiffies, deadline)); + *data = readl(csr_base + indirect_data_reg); + if (!(val & CFG_IND_CMD_DONE_MASK)) + pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n", + csr_base + indirect_cmd_reg, addr, *data); +} + +static void cmu_wr(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 data) +{ + void __iomem *sds_base = ctx->sds_base; + u32 val; + + if (cmu_type == REF_CMU) + reg += SERDES_PLL_REF_INDIRECT_OFFSET; + else + reg += SERDES_PLL_INDIRECT_OFFSET; + sds_wr(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_WDATA_REG, reg, data); + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, &val); + pr_debug("CMU WR addr 0x%X value 0x%08X <-> 0x%08X\n", reg, data, val); +} + +static void cmu_rd(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 *data) +{ + void __iomem *sds_base = ctx->sds_base; + + if (cmu_type == REF_CMU) + reg += SERDES_PLL_REF_INDIRECT_OFFSET; + else + reg += SERDES_PLL_INDIRECT_OFFSET; + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, data); + pr_debug("CMU RD addr 0x%X value 0x%08X\n", reg, *data); +} + +static void cmu_toggle1to0(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 bits) +{ + u32 val; + + cmu_rd(ctx, cmu_type, reg, &val); + val |= bits; + cmu_wr(ctx, cmu_type, reg, val); + cmu_rd(ctx, cmu_type, reg, &val); + val &= ~bits; + cmu_wr(ctx, cmu_type, reg, val); +} + +static void cmu_clrbits(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 bits) +{ + u32 val; + + cmu_rd(ctx, cmu_type, reg, &val); + val &= ~bits; + cmu_wr(ctx, cmu_type, reg, val); +} + +static void cmu_setbits(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type, + u32 reg, u32 bits) +{ + u32 val; + + cmu_rd(ctx, cmu_type, reg, &val); + val |= bits; + cmu_wr(ctx, cmu_type, reg, val); +} + +static void serdes_wr(struct xgene_phy_ctx *ctx, int lane, u32 reg, u32 data) +{ + void __iomem *sds_base = ctx->sds_base; + u32 val; + + reg += SERDES_INDIRECT_OFFSET; + reg += lane * SERDES_LANE_STRIDE; + sds_wr(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_WDATA_REG, reg, data); + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, &val); + pr_debug("SERDES WR addr 0x%X value 0x%08X <-> 0x%08X\n", reg, data, + val); +} + +static void serdes_rd(struct xgene_phy_ctx *ctx, int lane, u32 reg, u32 *data) +{ + void __iomem *sds_base = ctx->sds_base; + + reg += SERDES_INDIRECT_OFFSET; + reg += lane * SERDES_LANE_STRIDE; + sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG, + SATA_ENET_SDS_IND_RDATA_REG, reg, data); + pr_debug("SERDES RD addr 0x%X value 0x%08X\n", reg, *data); +} + +static void serdes_clrbits(struct xgene_phy_ctx *ctx, int lane, u32 reg, + u32 bits) +{ + u32 val; + + serdes_rd(ctx, lane, reg, &val); + val &= ~bits; + serdes_wr(ctx, lane, reg, val); +} + +static void serdes_setbits(struct xgene_phy_ctx *ctx, int lane, u32 reg, + u32 bits) +{ + u32 val; + + serdes_rd(ctx, lane, reg, &val); + val |= bits; + serdes_wr(ctx, lane, reg, val); +} + +static void xgene_phy_cfg_cmu_clk_type(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + u32 val; + + /* Set the reset sequence delay for TX ready assertion */ + cmu_rd(ctx, cmu_type, CMU_REG12, &val); + val = CMU_REG12_STATE_DELAY9_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG12, val); + /* Set the programmable stage delays between various enable stages */ + cmu_wr(ctx, cmu_type, CMU_REG13, 0x0222); + cmu_wr(ctx, cmu_type, CMU_REG14, 0x2225); + + /* Configure clock type */ + if (clk_type == CLK_EXT_DIFF) { + /* Select external clock mux */ + cmu_rd(ctx, cmu_type, CMU_REG0, &val); + val = CMU_REG0_PLL_REF_SEL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG0, val); + /* Select CMOS as reference clock */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + dev_dbg(ctx->dev, "Set external reference clock\n"); + } else if (clk_type == CLK_INT_DIFF) { + /* Select internal clock mux */ + cmu_rd(ctx, cmu_type, CMU_REG0, &val); + val = CMU_REG0_PLL_REF_SEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG0, val); + /* Select CMOS as reference clock */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + dev_dbg(ctx->dev, "Set internal reference clock\n"); + } else if (clk_type == CLK_INT_SING) { + /* + * NOTE: This clock type is NOT support for controller + * whose internal clock shared in the PCIe controller + * + * Select internal clock mux + */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + /* Select CML as reference clock */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + dev_dbg(ctx->dev, + "Set internal single ended reference clock\n"); + } +} + +static void xgene_phy_sata_cfg_cmu_core(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + u32 val; + int ref_100MHz; + + if (cmu_type == REF_CMU) { + /* Set VCO calibration voltage threshold */ + cmu_rd(ctx, cmu_type, CMU_REG34, &val); + val = CMU_REG34_VCO_CAL_VTH_LO_MAX_SET(val, 0x7); + val = CMU_REG34_VCO_CAL_VTH_HI_MAX_SET(val, 0xc); + val = CMU_REG34_VCO_CAL_VTH_LO_MIN_SET(val, 0x3); + val = CMU_REG34_VCO_CAL_VTH_HI_MIN_SET(val, 0x8); + cmu_wr(ctx, cmu_type, CMU_REG34, val); + } + + /* Set the VCO calibration counter */ + cmu_rd(ctx, cmu_type, CMU_REG0, &val); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG0_CAL_COUNT_RESOL_SET(val, 0x4); + else + val = CMU_REG0_CAL_COUNT_RESOL_SET(val, 0x7); + cmu_wr(ctx, cmu_type, CMU_REG0, val); + + /* Configure PLL for calibration */ + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_PLL_CP_SET(val, 0x1); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG1_PLL_CP_SEL_SET(val, 0x5); + else + val = CMU_REG1_PLL_CP_SEL_SET(val, 0x3); + if (cmu_type == REF_CMU) + val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x0); + else + val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + + if (cmu_type != REF_CMU) + cmu_clrbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + + /* Configure the PLL for either 100MHz or 50MHz */ + cmu_rd(ctx, cmu_type, CMU_REG2, &val); + if (cmu_type == REF_CMU) { + val = CMU_REG2_PLL_LFRES_SET(val, 0xa); + ref_100MHz = 1; + } else { + val = CMU_REG2_PLL_LFRES_SET(val, 0x3); + if (clk_type == CLK_EXT_DIFF) + ref_100MHz = 0; + else + ref_100MHz = 1; + } + if (ref_100MHz) { + val = CMU_REG2_PLL_FBDIV_SET(val, FBDIV_VAL_100M); + val = CMU_REG2_PLL_REFDIV_SET(val, REFDIV_VAL_100M); + } else { + val = CMU_REG2_PLL_FBDIV_SET(val, FBDIV_VAL_50M); + val = CMU_REG2_PLL_REFDIV_SET(val, REFDIV_VAL_50M); + } + cmu_wr(ctx, cmu_type, CMU_REG2, val); + + /* Configure the VCO */ + cmu_rd(ctx, cmu_type, CMU_REG3, &val); + if (cmu_type == REF_CMU) { + val = CMU_REG3_VCOVARSEL_SET(val, 0x3); + val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x10); + } else { + val = CMU_REG3_VCOVARSEL_SET(val, 0xF); + if (preA3Chip) + val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x15); + else + val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x1a); + val = CMU_REG3_VCO_MANMOMSEL_SET(val, 0x15); + } + cmu_wr(ctx, cmu_type, CMU_REG3, val); + + /* Disable force PLL lock */ + cmu_rd(ctx, cmu_type, CMU_REG26, &val); + val = CMU_REG26_FORCE_PLL_LOCK_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG26, val); + + /* Setup PLL loop filter */ + cmu_rd(ctx, cmu_type, CMU_REG5, &val); + val = CMU_REG5_PLL_LFSMCAP_SET(val, 0x3); + val = CMU_REG5_PLL_LFCAP_SET(val, 0x3); + if (cmu_type == REF_CMU || !preA3Chip) + val = CMU_REG5_PLL_LOCK_RESOLUTION_SET(val, 0x7); + else + val = CMU_REG5_PLL_LOCK_RESOLUTION_SET(val, 0x4); + cmu_wr(ctx, cmu_type, CMU_REG5, val); + + /* Enable or disable manual calibration */ + cmu_rd(ctx, cmu_type, CMU_REG6, &val); + val = CMU_REG6_PLL_VREGTRIM_SET(val, preA3Chip ? 0x0 : 0x2); + val = CMU_REG6_MAN_PVT_CAL_SET(val, preA3Chip ? 0x1 : 0x0); + cmu_wr(ctx, cmu_type, CMU_REG6, val); + + /* Configure lane for 20-bits */ + if (cmu_type == PHY_CMU) { + cmu_rd(ctx, cmu_type, CMU_REG9, &val); + val = CMU_REG9_TX_WORD_MODE_CH1_SET(val, + CMU_REG9_WORD_LEN_20BIT); + val = CMU_REG9_TX_WORD_MODE_CH0_SET(val, + CMU_REG9_WORD_LEN_20BIT); + val = CMU_REG9_PLL_POST_DIVBY2_SET(val, 0x1); + if (!preA3Chip) { + val = CMU_REG9_VBG_BYPASSB_SET(val, 0x0); + val = CMU_REG9_IGEN_BYPASS_SET(val , 0x0); + } + cmu_wr(ctx, cmu_type, CMU_REG9, val); + + if (!preA3Chip) { + cmu_rd(ctx, cmu_type, CMU_REG10, &val); + val = CMU_REG10_VREG_REFSEL_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG10, val); + } + } + + cmu_rd(ctx, cmu_type, CMU_REG16, &val); + val = CMU_REG16_CALIBRATION_DONE_OVERRIDE_SET(val, 0x1); + val = CMU_REG16_BYPASS_PLL_LOCK_SET(val, 0x1); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x4); + else + val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x7); + cmu_wr(ctx, cmu_type, CMU_REG16, val); + + /* Configure for SATA */ + cmu_rd(ctx, cmu_type, CMU_REG30, &val); + val = CMU_REG30_PCIE_MODE_SET(val, 0x0); + val = CMU_REG30_LOCK_COUNT_SET(val, 0x3); + cmu_wr(ctx, cmu_type, CMU_REG30, val); + + /* Disable state machine bypass */ + cmu_wr(ctx, cmu_type, CMU_REG31, 0xF); + + cmu_rd(ctx, cmu_type, CMU_REG32, &val); + val = CMU_REG32_PVT_CAL_WAIT_SEL_SET(val, 0x3); + if (cmu_type == REF_CMU || preA3Chip) + val = CMU_REG32_IREF_ADJ_SET(val, 0x3); + else + val = CMU_REG32_IREF_ADJ_SET(val, 0x1); + cmu_wr(ctx, cmu_type, CMU_REG32, val); + + /* Set VCO calibration threshold */ + if (cmu_type != REF_CMU && preA3Chip) + cmu_wr(ctx, cmu_type, CMU_REG34, 0x8d27); + else + cmu_wr(ctx, cmu_type, CMU_REG34, 0x873c); + + /* Set CTLE Override and override waiting from state machine */ + cmu_wr(ctx, cmu_type, CMU_REG37, 0xF00F); +} + +static void xgene_phy_ssc_enable(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type) +{ + u32 val; + + /* Set SSC modulation value */ + cmu_rd(ctx, cmu_type, CMU_REG35, &val); + val = CMU_REG35_PLL_SSC_MOD_SET(val, 98); + cmu_wr(ctx, cmu_type, CMU_REG35, val); + + /* Enable SSC, set vertical step and DSM value */ + cmu_rd(ctx, cmu_type, CMU_REG36, &val); + val = CMU_REG36_PLL_SSC_VSTEP_SET(val, 30); + val = CMU_REG36_PLL_SSC_EN_SET(val, 1); + val = CMU_REG36_PLL_SSC_DSMSEL_SET(val, 1); + cmu_wr(ctx, cmu_type, CMU_REG36, val); + + /* Reset the PLL */ + cmu_clrbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + cmu_setbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + + /* Force VCO calibration to restart */ + cmu_toggle1to0(ctx, cmu_type, CMU_REG32, + CMU_REG32_FORCE_VCOCAL_START_MASK); +} + +static void xgene_phy_sata_cfg_lanes(struct xgene_phy_ctx *ctx) +{ + u32 val; + u32 reg; + int i; + int lane; + + for (lane = 0; lane < MAX_LANE; lane++) { + serdes_wr(ctx, lane, RXTX_REG147, 0x6); + + /* Set boost control for quarter, half, and full rate */ + serdes_rd(ctx, lane, RXTX_REG0, &val); + val = RXTX_REG0_CTLE_EQ_HR_SET(val, 0x10); + val = RXTX_REG0_CTLE_EQ_QR_SET(val, 0x10); + val = RXTX_REG0_CTLE_EQ_FR_SET(val, 0x10); + serdes_wr(ctx, lane, RXTX_REG0, val); + + /* Set boost control value */ + serdes_rd(ctx, lane, RXTX_REG1, &val); + val = RXTX_REG1_RXACVCM_SET(val, 0x7); + val = RXTX_REG1_CTLE_EQ_SET(val, + ctx->sata_param.txboostgain[lane * 3 + + ctx->sata_param.speed[lane]]); + serdes_wr(ctx, lane, RXTX_REG1, val); + + /* Latch VTT value based on the termination to ground and + enable TX FIFO */ + serdes_rd(ctx, lane, RXTX_REG2, &val); + val = RXTX_REG2_VTT_ENA_SET(val, 0x1); + val = RXTX_REG2_VTT_SEL_SET(val, 0x1); + val = RXTX_REG2_TX_FIFO_ENA_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG2, val); + + /* Configure Tx for 20-bits */ + serdes_rd(ctx, lane, RXTX_REG4, &val); + val = RXTX_REG4_TX_WORD_MODE_SET(val, CMU_REG9_WORD_LEN_20BIT); + serdes_wr(ctx, lane, RXTX_REG4, val); + + if (!preA3Chip) { + serdes_rd(ctx, lane, RXTX_REG1, &val); + val = RXTX_REG1_RXVREG1_SET(val, 0x2); + val = RXTX_REG1_RXIREF_ADJ_SET(val, 0x2); + serdes_wr(ctx, lane, RXTX_REG1, val); + } + + /* Set pre-emphasis first 1 and 2, and post-emphasis values */ + serdes_rd(ctx, lane, RXTX_REG5, &val); + val = RXTX_REG5_TX_CN1_SET(val, + ctx->sata_param.txprecursor_cn1[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG5_TX_CP1_SET(val, + ctx->sata_param.txpostcursor_cp1[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG5_TX_CN2_SET(val, + ctx->sata_param.txprecursor_cn2[lane * 3 + + ctx->sata_param.speed[lane]]); + serdes_wr(ctx, lane, RXTX_REG5, val); + + /* Set TX amplitude value */ + serdes_rd(ctx, lane, RXTX_REG6, &val); + val = RXTX_REG6_TXAMP_CNTL_SET(val, + ctx->sata_param.txamplitude[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG6_TXAMP_ENA_SET(val, 0x1); + val = RXTX_REG6_TX_IDLE_SET(val, 0x0); + val = RXTX_REG6_RX_BIST_RESYNC_SET(val, 0x0); + val = RXTX_REG6_RX_BIST_ERRCNT_RD_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG6, val); + + /* Configure Rx for 20-bits */ + serdes_rd(ctx, lane, RXTX_REG7, &val); + val = RXTX_REG7_BIST_ENA_RX_SET(val, 0x0); + val = RXTX_REG7_RX_WORD_MODE_SET(val, CMU_REG9_WORD_LEN_20BIT); + serdes_wr(ctx, lane, RXTX_REG7, val); + + /* Set CDR and LOS values and enable Rx SSC */ + serdes_rd(ctx, lane, RXTX_REG8, &val); + val = RXTX_REG8_CDR_LOOP_ENA_SET(val, 0x1); + val = RXTX_REG8_CDR_BYPASS_RXLOS_SET(val, 0x0); + val = RXTX_REG8_SSC_ENABLE_SET(val, 0x1); + val = RXTX_REG8_SD_DISABLE_SET(val, 0x0); + val = RXTX_REG8_SD_VREF_SET(val, 0x4); + serdes_wr(ctx, lane, RXTX_REG8, val); + + /* Set phase adjust upper/lower limits */ + serdes_rd(ctx, lane, RXTX_REG11, &val); + val = RXTX_REG11_PHASE_ADJUST_LIMIT_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG11, val); + + /* Enable Latch Off; disable SUMOS and Tx termination */ + serdes_rd(ctx, lane, RXTX_REG12, &val); + val = RXTX_REG12_LATCH_OFF_ENA_SET(val, 0x1); + val = RXTX_REG12_SUMOS_ENABLE_SET(val, 0x0); + val = RXTX_REG12_RX_DET_TERM_ENABLE_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG12, val); + + /* Set period error latch to 512T and enable BWL */ + serdes_rd(ctx, lane, RXTX_REG26, &val); + val = RXTX_REG26_PERIOD_ERROR_LATCH_SET(val, 0x0); + val = RXTX_REG26_BLWC_ENA_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG26, val); + + serdes_wr(ctx, lane, RXTX_REG28, 0x0); + + /* Set DFE loop preset value */ + serdes_wr(ctx, lane, RXTX_REG31, 0x0); + + /* Set Eye Monitor counter width to 12-bit */ + serdes_rd(ctx, lane, RXTX_REG61, &val); + val = RXTX_REG61_ISCAN_INBERT_SET(val, 0x1); + val = RXTX_REG61_LOADFREQ_SHIFT_SET(val, 0x0); + val = RXTX_REG61_EYE_COUNT_WIDTH_SEL_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG61, val); + + serdes_rd(ctx, lane, RXTX_REG62, &val); + val = RXTX_REG62_PERIOD_H1_QLATCH_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG62, val); + + /* Set BW select tap X for DFE loop */ + for (i = 0; i < 9; i++) { + reg = RXTX_REG81 + i * 2; + serdes_rd(ctx, lane, reg, &val); + val = RXTX_REG89_MU_TH7_SET(val, 0xe); + val = RXTX_REG89_MU_TH8_SET(val, 0xe); + val = RXTX_REG89_MU_TH9_SET(val, 0xe); + serdes_wr(ctx, lane, reg, val); + } + + /* Set BW select tap X for frequency adjust loop */ + for (i = 0; i < 3; i++) { + reg = RXTX_REG96 + i * 2; + serdes_rd(ctx, lane, reg, &val); + val = RXTX_REG96_MU_FREQ1_SET(val, 0x10); + val = RXTX_REG96_MU_FREQ2_SET(val, 0x10); + val = RXTX_REG96_MU_FREQ3_SET(val, 0x10); + serdes_wr(ctx, lane, reg, val); + } + + /* Set BW select tap X for phase adjust loop */ + for (i = 0; i < 3; i++) { + reg = RXTX_REG99 + i * 2; + serdes_rd(ctx, lane, reg, &val); + val = RXTX_REG99_MU_PHASE1_SET(val, 0x7); + val = RXTX_REG99_MU_PHASE2_SET(val, 0x7); + val = RXTX_REG99_MU_PHASE3_SET(val, 0x7); + serdes_wr(ctx, lane, reg, val); + } + + serdes_rd(ctx, lane, RXTX_REG102, &val); + val = RXTX_REG102_FREQLOOP_LIMIT_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG102, val); + + serdes_wr(ctx, lane, RXTX_REG114, 0xffe0); + + serdes_rd(ctx, lane, RXTX_REG125, &val); + val = RXTX_REG125_SIGN_PQ_SET(val, + ctx->sata_param.txeyedirection[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG125_PQ_REG_SET(val, + ctx->sata_param.txeyetuning[lane * 3 + + ctx->sata_param.speed[lane]]); + val = RXTX_REG125_PHZ_MANUAL_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG125, val); + + serdes_rd(ctx, lane, RXTX_REG127, &val); + val = RXTX_REG127_LATCH_MAN_CAL_ENA_SET(val, 0x0); + serdes_wr(ctx, lane, RXTX_REG127, val); + + serdes_rd(ctx, lane, RXTX_REG128, &val); + val = RXTX_REG128_LATCH_CAL_WAIT_SEL_SET(val, 0x3); + serdes_wr(ctx, lane, RXTX_REG128, val); + + serdes_rd(ctx, lane, RXTX_REG145, &val); + val = RXTX_REG145_RXDFE_CONFIG_SET(val, 0x3); + val = RXTX_REG145_TX_IDLE_SATA_SET(val, 0x0); + if (preA3Chip) { + val = RXTX_REG145_RXES_ENA_SET(val, 0x1); + val = RXTX_REG145_RXVWES_LATENA_SET(val, 0x1); + } else { + val = RXTX_REG145_RXES_ENA_SET(val, 0x0); + val = RXTX_REG145_RXVWES_LATENA_SET(val, 0x0); + } + serdes_wr(ctx, lane, RXTX_REG145, val); + + /* + * Set Rx LOS filter clock rate, sample rate, and threshold + * windows + */ + for (i = 0; i < 4; i++) { + reg = RXTX_REG148 + i * 2; + serdes_wr(ctx, lane, reg, 0xFFFF); + } + } +} + +static int xgene_phy_cal_rdy_chk(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + void __iomem *csr_serdes = ctx->sds_base; + int loop; + u32 val; + + /* Release PHY main reset */ + writel(0xdf, csr_serdes + SATA_ENET_SDS_RST_CTL); + readl(csr_serdes + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + + if (cmu_type != REF_CMU) { + cmu_setbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK); + /* + * As per PHY design spec, the PLL reset requires a minimum + * of 800us. + */ + usleep_range(800, 1000); + + cmu_rd(ctx, cmu_type, CMU_REG1, &val); + val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG1, val); + /* + * As per PHY design spec, the PLL auto calibration requires + * a minimum of 800us. + */ + usleep_range(800, 1000); + + cmu_toggle1to0(ctx, cmu_type, CMU_REG32, + CMU_REG32_FORCE_VCOCAL_START_MASK); + /* + * As per PHY design spec, the PLL requires a minimum of + * 800us to settle. + */ + usleep_range(800, 1000); + } + + if (!preA3Chip) + goto skip_manual_cal; + + /* + * Configure the termination resister calibration + * The serial receive pins, RXP/RXN, have TERMination resistor + * that is required to be calibrated. + */ + cmu_rd(ctx, cmu_type, CMU_REG17, &val); + val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x12); + val = CMU_REG17_RESERVED_7_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG17, val); + cmu_toggle1to0(ctx, cmu_type, CMU_REG17, + CMU_REG17_PVT_TERM_MAN_ENA_MASK); + /* + * The serial transmit pins, TXP/TXN, have Pull-UP and Pull-DOWN + * resistors that are required to the calibrated. + * Configure the pull DOWN calibration + */ + cmu_rd(ctx, cmu_type, CMU_REG17, &val); + val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x29); + val = CMU_REG17_RESERVED_7_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG17, val); + cmu_toggle1to0(ctx, cmu_type, CMU_REG16, + CMU_REG16_PVT_DN_MAN_ENA_MASK); + /* Configure the pull UP calibration */ + cmu_rd(ctx, cmu_type, CMU_REG17, &val); + val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x28); + val = CMU_REG17_RESERVED_7_SET(val, 0x0); + cmu_wr(ctx, cmu_type, CMU_REG17, val); + cmu_toggle1to0(ctx, cmu_type, CMU_REG16, + CMU_REG16_PVT_UP_MAN_ENA_MASK); + +skip_manual_cal: + /* Poll the PLL calibration completion status for at least 1 ms */ + loop = 100; + do { + cmu_rd(ctx, cmu_type, CMU_REG7, &val); + if (CMU_REG7_PLL_CALIB_DONE_RD(val)) + break; + /* + * As per PHY design spec, PLL calibration status requires + * a minimum of 10us to be updated. + */ + usleep_range(10, 100); + } while (--loop > 0); + + cmu_rd(ctx, cmu_type, CMU_REG7, &val); + dev_dbg(ctx->dev, "PLL calibration %s\n", + CMU_REG7_PLL_CALIB_DONE_RD(val) ? "done" : "failed"); + if (CMU_REG7_VCO_CAL_FAIL_RD(val)) { + dev_err(ctx->dev, + "PLL calibration failed due to VCO failure\n"); + return -1; + } + dev_dbg(ctx->dev, "PLL calibration successful\n"); + + cmu_rd(ctx, cmu_type, CMU_REG15, &val); + dev_dbg(ctx->dev, "PHY Tx is %sready\n", val & 0x300 ? "" : "not "); + return 0; +} + +static void xgene_phy_pdwn_force_vco(struct xgene_phy_ctx *ctx, + enum cmu_type_t cmu_type, + enum clk_type_t clk_type) +{ + u32 val; + + dev_dbg(ctx->dev, "Reset VCO and re-start again\n"); + if (cmu_type == PHY_CMU) { + cmu_rd(ctx, cmu_type, CMU_REG16, &val); + val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x7); + cmu_wr(ctx, cmu_type, CMU_REG16, val); + } + + cmu_toggle1to0(ctx, cmu_type, CMU_REG0, CMU_REG0_PDOWN_MASK); + cmu_toggle1to0(ctx, cmu_type, CMU_REG32, + CMU_REG32_FORCE_VCOCAL_START_MASK); +} + +static int xgene_phy_hw_init_sata(struct xgene_phy_ctx *ctx, + enum clk_type_t clk_type, int ssc_enable) +{ + void __iomem *sds_base = ctx->sds_base; + u32 val; + int i; + + /* Configure the PHY for operation */ + dev_dbg(ctx->dev, "Reset PHY\n"); + /* Place PHY into reset */ + writel(0x0, sds_base + SATA_ENET_SDS_RST_CTL); + val = readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + /* Release PHY lane from reset (active high) */ + writel(0x20, sds_base + SATA_ENET_SDS_RST_CTL); + readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + /* Release all PHY module out of reset except PHY main reset */ + writel(0xde, sds_base + SATA_ENET_SDS_RST_CTL); + readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */ + + /* Set the operation speed */ + val = readl(sds_base + SATA_ENET_SDS_CTL1); + val = CFG_I_SPD_SEL_CDR_OVR1_SET(val, + ctx->sata_param.txspeed[ctx->sata_param.speed[0]]); + writel(val, sds_base + SATA_ENET_SDS_CTL1); + + dev_dbg(ctx->dev, "Set the customer pin mode to SATA\n"); + val = readl(sds_base + SATA_ENET_SDS_CTL0); + val = REGSPEC_CFG_I_CUSTOMER_PIN_MODE0_SET(val, 0x4421); + writel(val, sds_base + SATA_ENET_SDS_CTL0); + + /* Configure the clock macro unit (CMU) clock type */ + xgene_phy_cfg_cmu_clk_type(ctx, PHY_CMU, clk_type); + + /* Configure the clock macro */ + xgene_phy_sata_cfg_cmu_core(ctx, PHY_CMU, clk_type); + + /* Enable SSC if enabled */ + if (ssc_enable) + xgene_phy_ssc_enable(ctx, PHY_CMU); + + /* Configure PHY lanes */ + xgene_phy_sata_cfg_lanes(ctx); + + /* Set Rx/Tx 20-bit */ + val = readl(sds_base + SATA_ENET_SDS_PCS_CTL0); + val = REGSPEC_CFG_I_RX_WORDMODE0_SET(val, 0x3); + val = REGSPEC_CFG_I_TX_WORDMODE0_SET(val, 0x3); + writel(val, sds_base + SATA_ENET_SDS_PCS_CTL0); + + /* Start PLL calibration and try for three times */ + i = 10; + do { + if (!xgene_phy_cal_rdy_chk(ctx, PHY_CMU, clk_type)) + break; + /* If failed, toggle the VCO power signal and start again */ + xgene_phy_pdwn_force_vco(ctx, PHY_CMU, clk_type); + } while (--i > 0); + /* Even on failure, allow to continue any way */ + if (i <= 0) + dev_err(ctx->dev, "PLL calibration failed\n"); + + return 0; +} + +static int xgene_phy_hw_initialize(struct xgene_phy_ctx *ctx, + enum clk_type_t clk_type, + int ssc_enable) +{ + int rc; + + dev_dbg(ctx->dev, "PHY init clk type %d\n", clk_type); + + if (ctx->mode == MODE_SATA) { + rc = xgene_phy_hw_init_sata(ctx, clk_type, ssc_enable); + if (rc) + return rc; + } else { + dev_err(ctx->dev, "Un-supported customer pin mode %d\n", + ctx->mode); + return -ENODEV; + } + + return 0; +} + +/* + * Receiver Offset Calibration: + * + * Calibrate the receiver signal path offset in two steps - summar and + * latch calibrations + */ +static void xgene_phy_force_lat_summer_cal(struct xgene_phy_ctx *ctx, int lane) +{ + int i; + struct { + u32 reg; + u32 val; + } serdes_reg[] = { + {RXTX_REG38, 0x0}, + {RXTX_REG39, 0xff00}, + {RXTX_REG40, 0xffff}, + {RXTX_REG41, 0xffff}, + {RXTX_REG42, 0xffff}, + {RXTX_REG43, 0xffff}, + {RXTX_REG44, 0xffff}, + {RXTX_REG45, 0xffff}, + {RXTX_REG46, 0xffff}, + {RXTX_REG47, 0xfffc}, + {RXTX_REG48, 0x0}, + {RXTX_REG49, 0x0}, + {RXTX_REG50, 0x0}, + {RXTX_REG51, 0x0}, + {RXTX_REG52, 0x0}, + {RXTX_REG53, 0x0}, + {RXTX_REG54, 0x0}, + {RXTX_REG55, 0x0}, + }; + + /* Start SUMMER calibration */ + serdes_setbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_SUM_CAL_START_MASK); + /* + * As per PHY design spec, the Summer calibration requires a minimum + * of 100us to complete. + */ + usleep_range(100, 500); + serdes_clrbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_SUM_CAL_START_MASK); + /* + * As per PHY design spec, the auto calibration requires a minimum + * of 100us to complete. + */ + usleep_range(100, 500); + + /* Start latch calibration */ + serdes_setbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_LAT_CAL_START_MASK); + /* + * As per PHY design spec, the latch calibration requires a minimum + * of 100us to complete. + */ + usleep_range(100, 500); + serdes_clrbits(ctx, lane, RXTX_REG127, + RXTX_REG127_FORCE_LAT_CAL_START_MASK); + + /* Configure the PHY lane for calibration */ + serdes_wr(ctx, lane, RXTX_REG28, 0x7); + serdes_wr(ctx, lane, RXTX_REG31, 0x7e00); + serdes_clrbits(ctx, lane, RXTX_REG4, + RXTX_REG4_TX_LOOPBACK_BUF_EN_MASK); + serdes_clrbits(ctx, lane, RXTX_REG7, + RXTX_REG7_LOOP_BACK_ENA_CTLE_MASK); + for (i = 0; i < ARRAY_SIZE(serdes_reg); i++) + serdes_wr(ctx, lane, serdes_reg[i].reg, + serdes_reg[i].val); +} + +static void xgene_phy_reset_rxd(struct xgene_phy_ctx *ctx, int lane) +{ + /* Reset digital Rx */ + serdes_clrbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK); + /* As per PHY design spec, the reset requires a minimum of 100us. */ + usleep_range(100, 150); + serdes_setbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK); +} + +static int xgene_phy_get_avg(int accum, int samples) +{ + return (accum + (samples / 2)) / samples; +} + +static void xgene_phy_gen_avg_val(struct xgene_phy_ctx *ctx, int lane) +{ + int max_loop = 10; + int avg_loop = 0; + int lat_do = 0, lat_xo = 0, lat_eo = 0, lat_so = 0; + int lat_de = 0, lat_xe = 0, lat_ee = 0, lat_se = 0; + int sum_cal = 0; + int lat_do_itr, lat_xo_itr, lat_eo_itr, lat_so_itr; + int lat_de_itr, lat_xe_itr, lat_ee_itr, lat_se_itr; + int sum_cal_itr; + int fail_even; + int fail_odd; + u32 val; + + dev_dbg(ctx->dev, "Generating avg calibration value for lane %d\n", + lane); + + /* Enable RX Hi-Z termination */ + serdes_setbits(ctx, lane, RXTX_REG12, + RXTX_REG12_RX_DET_TERM_ENABLE_MASK); + /* Turn off DFE */ + serdes_wr(ctx, lane, RXTX_REG28, 0x0000); + /* DFE Presets to zero */ + serdes_wr(ctx, lane, RXTX_REG31, 0x0000); + + /* + * Receiver Offset Calibration: + * Calibrate the receiver signal path offset in two steps - summar + * and latch calibration. + * Runs the "Receiver Offset Calibration multiple times to determine + * the average value to use. + */ + while (avg_loop < max_loop) { + /* Start the calibration */ + xgene_phy_force_lat_summer_cal(ctx, lane); + + serdes_rd(ctx, lane, RXTX_REG21, &val); + lat_do_itr = RXTX_REG21_DO_LATCH_CALOUT_RD(val); + lat_xo_itr = RXTX_REG21_XO_LATCH_CALOUT_RD(val); + fail_odd = RXTX_REG21_LATCH_CAL_FAIL_ODD_RD(val); + + serdes_rd(ctx, lane, RXTX_REG22, &val); + lat_eo_itr = RXTX_REG22_EO_LATCH_CALOUT_RD(val); + lat_so_itr = RXTX_REG22_SO_LATCH_CALOUT_RD(val); + fail_even = RXTX_REG22_LATCH_CAL_FAIL_EVEN_RD(val); + + serdes_rd(ctx, lane, RXTX_REG23, &val); + lat_de_itr = RXTX_REG23_DE_LATCH_CALOUT_RD(val); + lat_xe_itr = RXTX_REG23_XE_LATCH_CALOUT_RD(val); + + serdes_rd(ctx, lane, RXTX_REG24, &val); + lat_ee_itr = RXTX_REG24_EE_LATCH_CALOUT_RD(val); + lat_se_itr = RXTX_REG24_SE_LATCH_CALOUT_RD(val); + + serdes_rd(ctx, lane, RXTX_REG121, &val); + sum_cal_itr = RXTX_REG121_SUMOS_CAL_CODE_RD(val); + + /* Check for failure. If passed, sum them for averaging */ + if ((fail_even == 0 || fail_even == 1) && + (fail_odd == 0 || fail_odd == 1)) { + lat_do += lat_do_itr; + lat_xo += lat_xo_itr; + lat_eo += lat_eo_itr; + lat_so += lat_so_itr; + lat_de += lat_de_itr; + lat_xe += lat_xe_itr; + lat_ee += lat_ee_itr; + lat_se += lat_se_itr; + sum_cal += sum_cal_itr; + + dev_dbg(ctx->dev, "Iteration %d:\n", avg_loop); + dev_dbg(ctx->dev, "DO 0x%x XO 0x%x EO 0x%x SO 0x%x\n", + lat_do_itr, lat_xo_itr, lat_eo_itr, + lat_so_itr); + dev_dbg(ctx->dev, "DE 0x%x XE 0x%x EE 0x%x SE 0x%x\n", + lat_de_itr, lat_xe_itr, lat_ee_itr, + lat_se_itr); + dev_dbg(ctx->dev, "SUM 0x%x\n", sum_cal_itr); + ++avg_loop; + } else { + dev_err(ctx->dev, + "Receiver calibration failed at %d loop\n", + avg_loop); + } + xgene_phy_reset_rxd(ctx, lane); + } + + /* Update latch manual calibration with average value */ + serdes_rd(ctx, lane, RXTX_REG127, &val); + val = RXTX_REG127_DO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_do, max_loop)); + val = RXTX_REG127_XO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_xo, max_loop)); + serdes_wr(ctx, lane, RXTX_REG127, val); + + serdes_rd(ctx, lane, RXTX_REG128, &val); + val = RXTX_REG128_EO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_eo, max_loop)); + val = RXTX_REG128_SO_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_so, max_loop)); + serdes_wr(ctx, lane, RXTX_REG128, val); + + serdes_rd(ctx, lane, RXTX_REG129, &val); + val = RXTX_REG129_DE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_de, max_loop)); + val = RXTX_REG129_XE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_xe, max_loop)); + serdes_wr(ctx, lane, RXTX_REG129, val); + + serdes_rd(ctx, lane, RXTX_REG130, &val); + val = RXTX_REG130_EE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_ee, max_loop)); + val = RXTX_REG130_SE_LATCH_MANCAL_SET(val, + xgene_phy_get_avg(lat_se, max_loop)); + serdes_wr(ctx, lane, RXTX_REG130, val); + + /* Update SUMMER calibration with average value */ + serdes_rd(ctx, lane, RXTX_REG14, &val); + val = RXTX_REG14_CLTE_LATCAL_MAN_PROG_SET(val, + xgene_phy_get_avg(sum_cal, max_loop)); + serdes_wr(ctx, lane, RXTX_REG14, val); + + dev_dbg(ctx->dev, "Average Value:\n"); + dev_dbg(ctx->dev, "DO 0x%x XO 0x%x EO 0x%x SO 0x%x\n", + xgene_phy_get_avg(lat_do, max_loop), + xgene_phy_get_avg(lat_xo, max_loop), + xgene_phy_get_avg(lat_eo, max_loop), + xgene_phy_get_avg(lat_so, max_loop)); + dev_dbg(ctx->dev, "DE 0x%x XE 0x%x EE 0x%x SE 0x%x\n", + xgene_phy_get_avg(lat_de, max_loop), + xgene_phy_get_avg(lat_xe, max_loop), + xgene_phy_get_avg(lat_ee, max_loop), + xgene_phy_get_avg(lat_se, max_loop)); + dev_dbg(ctx->dev, "SUM 0x%x\n", + xgene_phy_get_avg(sum_cal, max_loop)); + + serdes_rd(ctx, lane, RXTX_REG14, &val); + val = RXTX_REG14_CTLE_LATCAL_MAN_ENA_SET(val, 0x1); + serdes_wr(ctx, lane, RXTX_REG14, val); + dev_dbg(ctx->dev, "Enable Manual Summer calibration\n"); + + serdes_rd(ctx, lane, RXTX_REG127, &val); + val = RXTX_REG127_LATCH_MAN_CAL_ENA_SET(val, 0x1); + dev_dbg(ctx->dev, "Enable Manual Latch calibration\n"); + serdes_wr(ctx, lane, RXTX_REG127, val); + + /* Disable RX Hi-Z termination */ + serdes_rd(ctx, lane, RXTX_REG12, &val); + val = RXTX_REG12_RX_DET_TERM_ENABLE_SET(val, 0); + serdes_wr(ctx, lane, RXTX_REG12, val); + /* Turn on DFE */ + serdes_wr(ctx, lane, RXTX_REG28, 0x0007); + /* Set DFE preset */ + serdes_wr(ctx, lane, RXTX_REG31, 0x7e00); +} + +static int xgene_phy_hw_init(struct phy *phy) +{ + struct xgene_phy_ctx *ctx = phy_get_drvdata(phy); + int rc; + int i; + + rc = xgene_phy_hw_initialize(ctx, CLK_EXT_DIFF, SSC_DISABLE); + if (rc) { + dev_err(ctx->dev, "PHY initialize failed %d\n", rc); + return rc; + } + + /* Setup clock properly after PHY configuration */ + if (!IS_ERR(ctx->clk)) { + /* HW requires an toggle of the clock */ + clk_prepare_enable(ctx->clk); + clk_disable_unprepare(ctx->clk); + clk_prepare_enable(ctx->clk); + } + + /* Compute average value */ + for (i = 0; i < MAX_LANE; i++) + xgene_phy_gen_avg_val(ctx, i); + + dev_dbg(ctx->dev, "PHY initialized\n"); + return 0; +} + +static const struct phy_ops xgene_phy_ops = { + .init = xgene_phy_hw_init, + .owner = THIS_MODULE, +}; + +static struct phy *xgene_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct xgene_phy_ctx *ctx = dev_get_drvdata(dev); + + if (args->args_count <= 0) + return ERR_PTR(-EINVAL); + if (args->args[0] < MODE_SATA || args->args[0] >= MODE_MAX) + return ERR_PTR(-EINVAL); + + ctx->mode = args->args[0]; + return ctx->phy; +} + +static void xgene_phy_get_param(struct platform_device *pdev, + const char *name, u32 *buffer, + int count, u32 *default_val, + u32 conv_factor) +{ + int i; + + if (!of_property_read_u32_array(pdev->dev.of_node, name, buffer, + count)) { + for (i = 0; i < count; i++) + buffer[i] /= conv_factor; + return; + } + /* Does not exist, load default */ + for (i = 0; i < count; i++) + buffer[i] = default_val[i % 3]; +} + +static int xgene_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct xgene_phy_ctx *ctx; + struct resource *res; + int rc = 0; + u32 default_spd[] = DEFAULT_SATA_SPD_SEL; + u32 default_txboost_gain[] = DEFAULT_SATA_TXBOOST_GAIN; + u32 default_txeye_direction[] = DEFAULT_SATA_TXEYEDIRECTION; + u32 default_txeye_tuning[] = DEFAULT_SATA_TXEYETUNING; + u32 default_txamp[] = DEFAULT_SATA_TXAMP; + u32 default_txcn1[] = DEFAULT_SATA_TXCN1; + u32 default_txcn2[] = DEFAULT_SATA_TXCN2; + u32 default_txcp1[] = DEFAULT_SATA_TXCP1; + int i; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctx->sds_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ctx->sds_base)) { + rc = PTR_ERR(ctx->sds_base); + goto error; + } + + /* Retrieve optional clock */ + ctx->clk = clk_get(&pdev->dev, NULL); + + /* Load override paramaters */ + xgene_phy_get_param(pdev, "apm,tx-eye-tuning", + ctx->sata_param.txeyetuning, 6, default_txeye_tuning, 1); + xgene_phy_get_param(pdev, "apm,tx-eye-direction", + ctx->sata_param.txeyedirection, 6, default_txeye_direction, 1); + xgene_phy_get_param(pdev, "apm,tx-boost-gain", + ctx->sata_param.txboostgain, 6, default_txboost_gain, 1); + xgene_phy_get_param(pdev, "apm,tx-amplitude", + ctx->sata_param.txamplitude, 6, default_txamp, 13300); + xgene_phy_get_param(pdev, "apm,tx-pre-cursor1", + ctx->sata_param.txprecursor_cn1, 6, default_txcn1, 18200); + xgene_phy_get_param(pdev, "apm,tx-pre-cursor2", + ctx->sata_param.txprecursor_cn2, 6, default_txcn2, 18200); + xgene_phy_get_param(pdev, "apm,tx-post-cursor", + ctx->sata_param.txpostcursor_cp1, 6, default_txcp1, 18200); + xgene_phy_get_param(pdev, "apm,tx-speed", + ctx->sata_param.txspeed, 3, default_spd, 1); + for (i = 0; i < MAX_LANE; i++) + ctx->sata_param.speed[i] = 2; /* Default to Gen3 */ + + ctx->dev = &pdev->dev; + platform_set_drvdata(pdev, ctx); + + ctx->phy = devm_phy_create(ctx->dev, &xgene_phy_ops, NULL); + if (IS_ERR(ctx->phy)) { + dev_dbg(&pdev->dev, "Failed to create PHY\n"); + rc = PTR_ERR(ctx->phy); + goto error; + } + phy_set_drvdata(ctx->phy, ctx); + + phy_provider = devm_of_phy_provider_register(ctx->dev, + xgene_phy_xlate); + if (IS_ERR(phy_provider)) { + rc = PTR_ERR(phy_provider); + goto error; + } + + return 0; + +error: + return rc; +} + +static const struct of_device_id xgene_phy_of_match[] = { + {.compatible = "apm,xgene-phy",}, + {}, +}; +MODULE_DEVICE_TABLE(of, xgene_phy_of_match); + +static struct platform_driver xgene_phy_driver = { + .probe = xgene_phy_probe, + .driver = { + .name = "xgene-phy", + .owner = THIS_MODULE, + .of_match_table = xgene_phy_of_match, + }, +}; +module_platform_driver(xgene_phy_driver); + +MODULE_DESCRIPTION("APM X-Gene Multi-Purpose PHY driver"); +MODULE_AUTHOR("Loc Ho <lho@apm.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); |
