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");  | 
