diff options
author | Vivek Gautam <vivek.gautam@codeaurora.org> | 2017-05-11 08:47:42 +0200 |
---|---|---|
committer | Kishon Vijay Abraham I <kishon@ti.com> | 2017-06-01 11:58:33 +0200 |
commit | 0b56e9a7e8358e59b21d8a425e463072bfae523c (patch) | |
tree | a1bd94fb04cd66967e7d2a0f620a37c87da61cf9 /drivers/phy/samsung | |
parent | phy: Move ULPI phy header out of drivers to include path (diff) | |
download | linux-0b56e9a7e8358e59b21d8a425e463072bfae523c.tar.xz linux-0b56e9a7e8358e59b21d8a425e463072bfae523c.zip |
phy: Group vendor specific phy drivers
Adding vendor specific directories in phy to group
phy drivers under their respective vendor umbrella.
Also updated the MAINTAINERS file to reflect the correct
directory structure for phy drivers.
Signed-off-by: Vivek Gautam <vivek.gautam@codeaurora.org>
Acked-by: Heiko Stuebner <heiko@sntech.de>
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Acked-by: Krzysztof Kozlowski <krzk@kernel.org>
Acked-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Reviewed-by: Jaehoon Chung <jh80.chung@samsung.com>
Cc: Kishon Vijay Abraham I <kishon@ti.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Heiko Stuebner <heiko@sntech.de>
Cc: Viresh Kumar <viresh.kumar@linaro.org>
Cc: Maxime Ripard <maxime.ripard@free-electrons.com>
Cc: Chen-Yu Tsai <wens@csie.org>
Cc: Sylwester Nawrocki <s.nawrocki@samsung.com>
Cc: Krzysztof Kozlowski <krzk@kernel.org>
Cc: Jaehoon Chung <jh80.chung@samsung.com>
Cc: Stephen Boyd <stephen.boyd@linaro.org>
Cc: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-omap@vger.kernel.org
Cc: linux-renesas-soc@vger.kernel.org
Cc: linux-rockchip@lists.infradead.org
Cc: linux-samsung-soc@vger.kernel.org
Cc: linux-usb@vger.kernel.org
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Diffstat (limited to 'drivers/phy/samsung')
-rw-r--r-- | drivers/phy/samsung/Kconfig | 95 | ||||
-rw-r--r-- | drivers/phy/samsung/Makefile | 11 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos-dp-video.c | 122 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos-mipi-video.c | 377 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos-pcie.c | 281 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos4210-usb2.c | 260 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos4x12-usb2.c | 378 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos5-usbdrd.c | 782 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos5250-sata.c | 250 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos5250-usb2.c | 401 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-s5pv210-usb2.c | 187 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-samsung-usb2.c | 267 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-samsung-usb2.h | 73 |
13 files changed, 3484 insertions, 0 deletions
diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig new file mode 100644 index 000000000000..b7e0645a7bd9 --- /dev/null +++ b/drivers/phy/samsung/Kconfig @@ -0,0 +1,95 @@ +# +# Phy drivers for Samsung platforms +# +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 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_EXYNOS_PCIE + bool "Exynos PCIe PHY driver" + depends on OF && (ARCH_EXYNOS || COMPILE_TEST) + select GENERIC_PHY + help + Enable PCIe PHY support for Exynos SoC series. + This driver provides PHY interface for Exynos PCIe controller. + +config PHY_SAMSUNG_USB2 + tristate "Samsung USB 2.0 PHY driver" + depends on HAS_IOMEM + depends on USB_EHCI_EXYNOS || USB_OHCI_EXYNOS || USB_DWC2 + select GENERIC_PHY + select MFD_SYSCON + default ARCH_EXYNOS + 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 PHYs will be enabled based on the SoC type in addition + to this driver. + +config PHY_EXYNOS4210_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default CPU_EXYNOS4210 + +config PHY_EXYNOS4X12_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default SOC_EXYNOS3250 || SOC_EXYNOS4212 || SOC_EXYNOS4412 + +config PHY_EXYNOS5250_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default SOC_EXYNOS5250 || SOC_EXYNOS5420 + +config PHY_S5PV210_USB2 + bool "Support for S5PV210" + depends on PHY_SAMSUNG_USB2 + depends on ARCH_S5PV210 + help + Enable USB PHY support for S5PV210. 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 S5PV210 two phys + are available - device and host. + +config PHY_EXYNOS5_USBDRD + tristate "Exynos5 SoC series USB DRD PHY driver" + depends on ARCH_EXYNOS && OF + depends on HAS_IOMEM + depends on USB_DWC3_EXYNOS + select GENERIC_PHY + select MFD_SYSCON + default y + 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_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. diff --git a/drivers/phy/samsung/Makefile b/drivers/phy/samsung/Makefile new file mode 100644 index 000000000000..20d7f2424772 --- /dev/null +++ b/drivers/phy/samsung/Makefile @@ -0,0 +1,11 @@ +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_EXYNOS_PCIE) += phy-exynos-pcie.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 +phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o +obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o +obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o diff --git a/drivers/phy/samsung/phy-exynos-dp-video.c b/drivers/phy/samsung/phy-exynos-dp-video.c new file mode 100644 index 000000000000..bb3279dbf88c --- /dev/null +++ b/drivers/phy/samsung/phy-exynos-dp-video.c @@ -0,0 +1,122 @@ +/* + * 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/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mfd/syscon.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/soc/samsung/exynos-regs-pmu.h> + +struct exynos_dp_video_phy_drvdata { + u32 phy_ctrl_offset; +}; + +struct exynos_dp_video_phy { + struct regmap *regs; + const struct exynos_dp_video_phy_drvdata *drvdata; +}; + +static int exynos_dp_video_phy_power_on(struct phy *phy) +{ + struct exynos_dp_video_phy *state = phy_get_drvdata(phy); + + /* Disable power isolation on DP-PHY */ + return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, + EXYNOS4_PHY_ENABLE, EXYNOS4_PHY_ENABLE); +} + +static int exynos_dp_video_phy_power_off(struct phy *phy) +{ + struct exynos_dp_video_phy *state = phy_get_drvdata(phy); + + /* Enable power isolation on DP-PHY */ + return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, + EXYNOS4_PHY_ENABLE, 0); +} + +static const 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 const struct exynos_dp_video_phy_drvdata exynos5250_dp_video_phy = { + .phy_ctrl_offset = EXYNOS5_DPTX_PHY_CONTROL, +}; + +static const struct exynos_dp_video_phy_drvdata exynos5420_dp_video_phy = { + .phy_ctrl_offset = EXYNOS5420_DPTX_PHY_CONTROL, +}; + +static const struct of_device_id exynos_dp_video_phy_of_match[] = { + { + .compatible = "samsung,exynos5250-dp-video-phy", + .data = &exynos5250_dp_video_phy, + }, { + .compatible = "samsung,exynos5420-dp-video-phy", + .data = &exynos5420_dp_video_phy, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_dp_video_phy_of_match); + +static int exynos_dp_video_phy_probe(struct platform_device *pdev) +{ + struct exynos_dp_video_phy *state; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + struct phy_provider *phy_provider; + struct phy *phy; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->regs = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,pmu-syscon"); + if (IS_ERR(state->regs)) { + dev_err(dev, "Failed to lookup PMU regmap\n"); + return PTR_ERR(state->regs); + } + + match = of_match_node(exynos_dp_video_phy_of_match, dev->of_node); + state->drvdata = match->data; + + phy = devm_phy_create(dev, NULL, &exynos_dp_video_phy_ops); + 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); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver exynos_dp_video_phy_driver = { + .probe = exynos_dp_video_phy_probe, + .driver = { + .name = "exynos-dp-video-phy", + .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/samsung/phy-exynos-mipi-video.c b/drivers/phy/samsung/phy-exynos-mipi-video.c new file mode 100644 index 000000000000..c198886f80a3 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos-mipi-video.c @@ -0,0 +1,377 @@ +/* + * Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver + * + * Copyright (C) 2013,2016 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/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/spinlock.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> +#include <linux/mfd/syscon.h> + +enum exynos_mipi_phy_id { + EXYNOS_MIPI_PHY_ID_NONE = -1, + EXYNOS_MIPI_PHY_ID_CSIS0, + EXYNOS_MIPI_PHY_ID_DSIM0, + EXYNOS_MIPI_PHY_ID_CSIS1, + EXYNOS_MIPI_PHY_ID_DSIM1, + EXYNOS_MIPI_PHY_ID_CSIS2, + EXYNOS_MIPI_PHYS_NUM +}; + +enum exynos_mipi_phy_regmap_id { + EXYNOS_MIPI_REGMAP_PMU, + EXYNOS_MIPI_REGMAP_DISP, + EXYNOS_MIPI_REGMAP_CAM0, + EXYNOS_MIPI_REGMAP_CAM1, + EXYNOS_MIPI_REGMAPS_NUM +}; + +struct mipi_phy_device_desc { + int num_phys; + int num_regmaps; + const char *regmap_names[EXYNOS_MIPI_REGMAPS_NUM]; + struct exynos_mipi_phy_desc { + enum exynos_mipi_phy_id coupled_phy_id; + u32 enable_val; + unsigned int enable_reg; + enum exynos_mipi_phy_regmap_id enable_map; + u32 resetn_val; + unsigned int resetn_reg; + enum exynos_mipi_phy_regmap_id resetn_map; + } phys[EXYNOS_MIPI_PHYS_NUM]; +}; + +static const struct mipi_phy_device_desc s5pv210_mipi_phy = { + .num_regmaps = 1, + .regmap_names = {"syscon"}, + .num_phys = 4, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, + }, +}; + +static const struct mipi_phy_device_desc exynos5420_mipi_phy = { + .num_regmaps = 1, + .regmap_names = {"syscon"}, + .num_phys = 5, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS2 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, + }, +}; + +#define EXYNOS5433_SYSREG_DISP_MIPI_PHY 0x100C +#define EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON 0x1014 +#define EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON 0x1020 + +static const struct mipi_phy_device_desc exynos5433_mipi_phy = { + .num_regmaps = 4, + .regmap_names = { + "samsung,pmu-syscon", + "samsung,disp-sysreg", + "samsung,cam0-sysreg", + "samsung,cam1-sysreg" + }, + .num_phys = 5, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, + .resetn_map = EXYNOS_MIPI_REGMAP_DISP, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(1), + .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(1), + .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, + .resetn_map = EXYNOS_MIPI_REGMAP_DISP, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS2 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(2), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM1, + }, + }, +}; + +struct exynos_mipi_video_phy { + struct regmap *regmaps[EXYNOS_MIPI_REGMAPS_NUM]; + int num_phys; + struct video_phy_desc { + struct phy *phy; + unsigned int index; + const struct exynos_mipi_phy_desc *data; + } phys[EXYNOS_MIPI_PHYS_NUM]; + spinlock_t slock; +}; + +static int __set_phy_state(const struct exynos_mipi_phy_desc *data, + struct exynos_mipi_video_phy *state, unsigned int on) +{ + u32 val; + + spin_lock(&state->slock); + + /* disable in PMU sysreg */ + if (!on && data->coupled_phy_id >= 0 && + state->phys[data->coupled_phy_id].phy->power_count == 0) { + regmap_read(state->regmaps[data->enable_map], data->enable_reg, + &val); + val &= ~data->enable_val; + regmap_write(state->regmaps[data->enable_map], data->enable_reg, + val); + } + + /* PHY reset */ + regmap_read(state->regmaps[data->resetn_map], data->resetn_reg, &val); + val = on ? (val | data->resetn_val) : (val & ~data->resetn_val); + regmap_write(state->regmaps[data->resetn_map], data->resetn_reg, val); + + /* enable in PMU sysreg */ + if (on) { + regmap_read(state->regmaps[data->enable_map], data->enable_reg, + &val); + val |= data->enable_val; + regmap_write(state->regmaps[data->enable_map], data->enable_reg, + val); + } + + 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(phy_desc->data, state, 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(phy_desc->data, state, 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] >= state->num_phys)) + return ERR_PTR(-ENODEV); + + return state->phys[args->args[0]].phy; +} + +static const 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) +{ + const struct mipi_phy_device_desc *phy_dev; + struct exynos_mipi_video_phy *state; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_provider *phy_provider; + unsigned int i; + + phy_dev = of_device_get_match_data(dev); + if (!phy_dev) + return -ENODEV; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + for (i = 0; i < phy_dev->num_regmaps; i++) { + state->regmaps[i] = syscon_regmap_lookup_by_phandle(np, + phy_dev->regmap_names[i]); + if (IS_ERR(state->regmaps[i])) + return PTR_ERR(state->regmaps[i]); + } + state->num_phys = phy_dev->num_phys; + spin_lock_init(&state->slock); + + dev_set_drvdata(dev, state); + + for (i = 0; i < state->num_phys; i++) { + struct phy *phy = devm_phy_create(dev, NULL, + &exynos_mipi_video_phy_ops); + 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; + state->phys[i].data = &phy_dev->phys[i]; + phy_set_drvdata(phy, &state->phys[i]); + } + + phy_provider = devm_of_phy_provider_register(dev, + exynos_mipi_video_phy_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id exynos_mipi_video_phy_of_match[] = { + { + .compatible = "samsung,s5pv210-mipi-video-phy", + .data = &s5pv210_mipi_phy, + }, { + .compatible = "samsung,exynos5420-mipi-video-phy", + .data = &exynos5420_mipi_phy, + }, { + .compatible = "samsung,exynos5433-mipi-video-phy", + .data = &exynos5433_mipi_phy, + }, + { /* sentinel */ }, +}; +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", + } +}; +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/samsung/phy-exynos-pcie.c b/drivers/phy/samsung/phy-exynos-pcie.c new file mode 100644 index 000000000000..a89c12faff39 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos-pcie.c @@ -0,0 +1,281 @@ +/* + * Samsung EXYNOS SoC series PCIe PHY driver + * + * Phy provider for PCIe controller on Exynos SoC series + * + * Copyright (C) 2017 Samsung Electronics Co., Ltd. + * Jaehoon Chung <jh80.chung@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/iopoll.h> +#include <linux/init.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> + +/* PCIe Purple registers */ +#define PCIE_PHY_GLOBAL_RESET 0x000 +#define PCIE_PHY_COMMON_RESET 0x004 +#define PCIE_PHY_CMN_REG 0x008 +#define PCIE_PHY_MAC_RESET 0x00c +#define PCIE_PHY_PLL_LOCKED 0x010 +#define PCIE_PHY_TRSVREG_RESET 0x020 +#define PCIE_PHY_TRSV_RESET 0x024 + +/* PCIe PHY registers */ +#define PCIE_PHY_IMPEDANCE 0x004 +#define PCIE_PHY_PLL_DIV_0 0x008 +#define PCIE_PHY_PLL_BIAS 0x00c +#define PCIE_PHY_DCC_FEEDBACK 0x014 +#define PCIE_PHY_PLL_DIV_1 0x05c +#define PCIE_PHY_COMMON_POWER 0x064 +#define PCIE_PHY_COMMON_PD_CMN BIT(3) +#define PCIE_PHY_TRSV0_EMP_LVL 0x084 +#define PCIE_PHY_TRSV0_DRV_LVL 0x088 +#define PCIE_PHY_TRSV0_RXCDR 0x0ac +#define PCIE_PHY_TRSV0_POWER 0x0c4 +#define PCIE_PHY_TRSV0_PD_TSV BIT(7) +#define PCIE_PHY_TRSV0_LVCC 0x0dc +#define PCIE_PHY_TRSV1_EMP_LVL 0x144 +#define PCIE_PHY_TRSV1_RXCDR 0x16c +#define PCIE_PHY_TRSV1_POWER 0x184 +#define PCIE_PHY_TRSV1_PD_TSV BIT(7) +#define PCIE_PHY_TRSV1_LVCC 0x19c +#define PCIE_PHY_TRSV2_EMP_LVL 0x204 +#define PCIE_PHY_TRSV2_RXCDR 0x22c +#define PCIE_PHY_TRSV2_POWER 0x244 +#define PCIE_PHY_TRSV2_PD_TSV BIT(7) +#define PCIE_PHY_TRSV2_LVCC 0x25c +#define PCIE_PHY_TRSV3_EMP_LVL 0x2c4 +#define PCIE_PHY_TRSV3_RXCDR 0x2ec +#define PCIE_PHY_TRSV3_POWER 0x304 +#define PCIE_PHY_TRSV3_PD_TSV BIT(7) +#define PCIE_PHY_TRSV3_LVCC 0x31c + +struct exynos_pcie_phy_data { + const struct phy_ops *ops; +}; + +/* For Exynos pcie phy */ +struct exynos_pcie_phy { + const struct exynos_pcie_phy_data *drv_data; + void __iomem *phy_base; + void __iomem *blk_base; /* For exynos5440 */ +}; + +static void exynos_pcie_phy_writel(void __iomem *base, u32 val, u32 offset) +{ + writel(val, base + offset); +} + +static u32 exynos_pcie_phy_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +/* For Exynos5440 specific functions */ +static int exynos5440_pcie_phy_init(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + + /* DCC feedback control off */ + exynos_pcie_phy_writel(ep->phy_base, 0x29, PCIE_PHY_DCC_FEEDBACK); + + /* set TX/RX impedance */ + exynos_pcie_phy_writel(ep->phy_base, 0xd5, PCIE_PHY_IMPEDANCE); + + /* set 50Mhz PHY clock */ + exynos_pcie_phy_writel(ep->phy_base, 0x14, PCIE_PHY_PLL_DIV_0); + exynos_pcie_phy_writel(ep->phy_base, 0x12, PCIE_PHY_PLL_DIV_1); + + /* set TX Differential output for lane 0 */ + exynos_pcie_phy_writel(ep->phy_base, 0x7f, PCIE_PHY_TRSV0_DRV_LVL); + + /* set TX Pre-emphasis Level Control for lane 0 to minimum */ + exynos_pcie_phy_writel(ep->phy_base, 0x0, PCIE_PHY_TRSV0_EMP_LVL); + + /* set RX clock and data recovery bandwidth */ + exynos_pcie_phy_writel(ep->phy_base, 0xe7, PCIE_PHY_PLL_BIAS); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV0_RXCDR); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV1_RXCDR); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV2_RXCDR); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV3_RXCDR); + + /* change TX Pre-emphasis Level Control for lanes */ + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV0_EMP_LVL); + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV1_EMP_LVL); + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV2_EMP_LVL); + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV3_EMP_LVL); + + /* set LVCC */ + exynos_pcie_phy_writel(ep->phy_base, 0x20, PCIE_PHY_TRSV0_LVCC); + exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV1_LVCC); + exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV2_LVCC); + exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV3_LVCC); + + /* pulse for common reset */ + exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_COMMON_RESET); + udelay(500); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET); + + return 0; +} + +static int exynos5440_pcie_phy_power_on(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + u32 val; + + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_CMN_REG); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSVREG_RESET); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSV_RESET); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER); + val &= ~PCIE_PHY_COMMON_PD_CMN; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER); + val &= ~PCIE_PHY_TRSV0_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER); + val &= ~PCIE_PHY_TRSV1_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER); + val &= ~PCIE_PHY_TRSV2_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER); + val &= ~PCIE_PHY_TRSV3_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER); + + return 0; +} + +static int exynos5440_pcie_phy_power_off(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + u32 val; + + if (readl_poll_timeout(ep->phy_base + PCIE_PHY_PLL_LOCKED, val, + (val != 0), 1, 500)) { + dev_err(&phy->dev, "PLL Locked: 0x%x\n", val); + return -ETIMEDOUT; + } + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER); + val |= PCIE_PHY_COMMON_PD_CMN; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER); + val |= PCIE_PHY_TRSV0_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER); + val |= PCIE_PHY_TRSV1_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER); + val |= PCIE_PHY_TRSV2_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER); + val |= PCIE_PHY_TRSV3_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER); + + return 0; +} + +static int exynos5440_pcie_phy_reset(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_MAC_RESET); + exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_GLOBAL_RESET); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_GLOBAL_RESET); + + return 0; +} + +static const struct phy_ops exynos5440_phy_ops = { + .init = exynos5440_pcie_phy_init, + .power_on = exynos5440_pcie_phy_power_on, + .power_off = exynos5440_pcie_phy_power_off, + .reset = exynos5440_pcie_phy_reset, + .owner = THIS_MODULE, +}; + +static const struct exynos_pcie_phy_data exynos5440_pcie_phy_data = { + .ops = &exynos5440_phy_ops, +}; + +static const struct of_device_id exynos_pcie_phy_match[] = { + { + .compatible = "samsung,exynos5440-pcie-phy", + .data = &exynos5440_pcie_phy_data, + }, + {}, +}; + +static int exynos_pcie_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct exynos_pcie_phy *exynos_phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct resource *res; + const struct exynos_pcie_phy_data *drv_data; + + drv_data = of_device_get_match_data(dev); + if (!drv_data) + return -ENODEV; + + exynos_phy = devm_kzalloc(dev, sizeof(*exynos_phy), GFP_KERNEL); + if (!exynos_phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + exynos_phy->phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(exynos_phy->phy_base)) + return PTR_ERR(exynos_phy->phy_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + exynos_phy->blk_base = devm_ioremap_resource(dev, res); + if (IS_ERR(exynos_phy->blk_base)) + return PTR_ERR(exynos_phy->blk_base); + + exynos_phy->drv_data = drv_data; + + generic_phy = devm_phy_create(dev, dev->of_node, drv_data->ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, exynos_phy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver exynos_pcie_phy_driver = { + .probe = exynos_pcie_phy_probe, + .driver = { + .of_match_table = exynos_pcie_phy_match, + .name = "exynos_pcie_phy", + } +}; + +builtin_platform_driver(exynos_pcie_phy_driver); diff --git a/drivers/phy/samsung/phy-exynos4210-usb2.c b/drivers/phy/samsung/phy-exynos4210-usb2.c new file mode 100644 index 000000000000..1f50e1004828 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos4210-usb2.c @@ -0,0 +1,260 @@ +/* + * 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/samsung/phy-exynos4x12-usb2.c b/drivers/phy/samsung/phy-exynos4x12-usb2.c new file mode 100644 index 000000000000..7f27a91acf87 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos4x12-usb2.c @@ -0,0 +1,378 @@ +/* + * 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_3250_UPHYCLK_REFCLKSEL (0x2 << 8) + +#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) +/* The following bit defines are presented in the + * order taken from the Exynos4412 reference manual. + * + * During experiments with the hardware and debugging + * it was determined that the hardware behaves contrary + * to the manual. + * + * The following bit values were chaned accordingly to the + * results of real hardware experiments. + */ +#define EXYNOS_4x12_URSTCON_PHY1 BIT(4) +#define EXYNOS_4x12_URSTCON_HSIC0 BIT(6) +#define EXYNOS_4x12_URSTCON_HSIC1 BIT(5) +#define EXYNOS_4x12_URSTCON_HOST_LINK_ALL BIT(7) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P0 BIT(10) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P1 BIT(9) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P2 BIT(8) + +/* 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; + + if (drv->cfg->has_refclk_sel) + clk = EXYNOS_3250_UPHYCLK_REFCLKSEL; + + clk |= drv->ref_reg_val << EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET; + clk |= EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON; + 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; + + switch (inst->cfg->id) { + case EXYNOS4x12_DEVICE: + phypwr = EXYNOS_4x12_UPHYPWR_PHY0; + rstbits = EXYNOS_4x12_URSTCON_PHY0; + break; + case EXYNOS4x12_HOST: + phypwr = EXYNOS_4x12_UPHYPWR_PHY1; + rstbits = EXYNOS_4x12_URSTCON_HOST_PHY | + EXYNOS_4x12_URSTCON_PHY1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P0; + break; + case EXYNOS4x12_HSIC0: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC0; + rstbits = EXYNOS_4x12_URSTCON_HSIC0 | + EXYNOS_4x12_URSTCON_HOST_LINK_P1; + break; + case EXYNOS4x12_HSIC1: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC1; + rstbits = EXYNOS_4x12_URSTCON_HSIC1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P1; + break; + } + + if (on) { + 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 void exynos4x12_power_on_int(struct samsung_usb2_phy_instance *inst) +{ + if (inst->int_cnt++ > 0) + return; + + exynos4x12_setup_clk(inst); + exynos4x12_isol(inst, 0); + exynos4x12_phy_pwr(inst, 1); +} + +static int exynos4x12_power_on(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + + if (inst->ext_cnt++ > 0) + return 0; + + if (inst->cfg->id == EXYNOS4x12_HOST) { + regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, + EXYNOS_4x12_MODE_SWITCH_HOST); + exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]); + } + + if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch) + regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, + EXYNOS_4x12_MODE_SWITCH_DEVICE); + + if (inst->cfg->id == EXYNOS4x12_HSIC0 || + inst->cfg->id == EXYNOS4x12_HSIC1) { + exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]); + exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_HOST]); + } + + exynos4x12_power_on_int(inst); + + return 0; +} + +static void exynos4x12_power_off_int(struct samsung_usb2_phy_instance *inst) +{ + if (inst->int_cnt-- > 1) + return; + + exynos4x12_isol(inst, 1); + exynos4x12_phy_pwr(inst, 0); +} + +static int exynos4x12_power_off(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + + if (inst->ext_cnt-- > 1) + return 0; + + if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch) + regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, + EXYNOS_4x12_MODE_SWITCH_HOST); + + if (inst->cfg->id == EXYNOS4x12_HOST) + exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]); + + if (inst->cfg->id == EXYNOS4x12_HSIC0 || + inst->cfg->id == EXYNOS4x12_HSIC1) { + exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]); + exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_HOST]); + } + + exynos4x12_power_off_int(inst); + + 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 exynos3250_usb2_phy_config = { + .has_refclk_sel = 1, + .num_phys = 1, + .phys = exynos4x12_phys, + .rate_to_clk = exynos4x12_rate_to_clk, +}; + +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/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c new file mode 100644 index 000000000000..7c41daa2c625 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -0,0 +1,782 @@ +/* + * 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/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/soc/samsung/exynos-regs-pmu.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; + bool has_common_clk_gate; +}; + +/** + * 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 + * @pipeclk: clock for pipe3 phy + * @utmiclk: clock for utmi+ phy + * @itpclk: clock for ITP generation + * @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 + * vbus: VBUS regulator for phy + * vbus_boost: Boost regulator for VBUS present on few Exynos boards + */ +struct exynos5_usbdrd_phy { + struct device *dev; + void __iomem *reg_phy; + struct clk *clk; + struct clk *pipeclk; + struct clk *utmiclk; + struct clk *itpclk; + 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; + struct regulator *vbus_boost; +}; + +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 : EXYNOS4_PHY_ENABLE; + + regmap_update_bits(inst->reg_pmu, inst->pmu_offset, + EXYNOS4_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) +{ + 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) +{ + 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); + if (!phy_drd->drv_data->has_common_clk_gate) { + clk_prepare_enable(phy_drd->pipeclk); + clk_prepare_enable(phy_drd->utmiclk); + clk_prepare_enable(phy_drd->itpclk); + } + + /* Enable VBUS supply */ + if (phy_drd->vbus_boost) { + ret = regulator_enable(phy_drd->vbus_boost); + if (ret) { + dev_err(phy_drd->dev, + "Failed to enable VBUS boost supply\n"); + goto fail_vbus; + } + } + + 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_boost; + } + } + + /* Power-on PHY*/ + inst->phy_cfg->phy_isol(inst, 0); + + return 0; + +fail_vbus_boost: + if (phy_drd->vbus_boost) + regulator_disable(phy_drd->vbus_boost); + +fail_vbus: + clk_disable_unprepare(phy_drd->ref_clk); + if (!phy_drd->drv_data->has_common_clk_gate) { + clk_disable_unprepare(phy_drd->itpclk); + clk_disable_unprepare(phy_drd->utmiclk); + clk_disable_unprepare(phy_drd->pipeclk); + } + + 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); + if (phy_drd->vbus_boost) + regulator_disable(phy_drd->vbus_boost); + + clk_disable_unprepare(phy_drd->ref_clk); + if (!phy_drd->drv_data->has_common_clk_gate) { + clk_disable_unprepare(phy_drd->itpclk); + clk_disable_unprepare(phy_drd->pipeclk); + clk_disable_unprepare(phy_drd->utmiclk); + } + + 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 const 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, +}; + +static int exynos5_usbdrd_phy_clk_handle(struct exynos5_usbdrd_phy *phy_drd) +{ + unsigned long ref_rate; + int ret; + + phy_drd->clk = devm_clk_get(phy_drd->dev, "phy"); + if (IS_ERR(phy_drd->clk)) { + dev_err(phy_drd->dev, "Failed to get phy clock\n"); + return PTR_ERR(phy_drd->clk); + } + + phy_drd->ref_clk = devm_clk_get(phy_drd->dev, "ref"); + if (IS_ERR(phy_drd->ref_clk)) { + dev_err(phy_drd->dev, "Failed to get phy reference clock\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; + } + + if (!phy_drd->drv_data->has_common_clk_gate) { + phy_drd->pipeclk = devm_clk_get(phy_drd->dev, "phy_pipe"); + if (IS_ERR(phy_drd->pipeclk)) { + dev_info(phy_drd->dev, + "PIPE3 phy operational clock not specified\n"); + phy_drd->pipeclk = NULL; + } + + phy_drd->utmiclk = devm_clk_get(phy_drd->dev, "phy_utmi"); + if (IS_ERR(phy_drd->utmiclk)) { + dev_info(phy_drd->dev, + "UTMI phy operational clock not specified\n"); + phy_drd->utmiclk = NULL; + } + + phy_drd->itpclk = devm_clk_get(phy_drd->dev, "itp"); + if (IS_ERR(phy_drd->itpclk)) { + dev_info(phy_drd->dev, + "ITP clock from main OSC not specified\n"); + phy_drd->itpclk = NULL; + } + } + + return 0; +} + +static 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, + }, +}; + +static 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, + .has_common_clk_gate = true, +}; + +static const struct exynos5_usbdrd_phy_drvdata exynos5250_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .has_common_clk_gate = true, +}; + +static const struct exynos5_usbdrd_phy_drvdata exynos5433_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .pmu_offset_usbdrd1_phy = EXYNOS5433_USBHOST30_PHY_CONTROL, + .has_common_clk_gate = false, +}; + +static const struct exynos5_usbdrd_phy_drvdata exynos7_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .has_common_clk_gate = false, +}; + +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 + }, { + .compatible = "samsung,exynos5433-usbdrd-phy", + .data = &exynos5433_usbdrd_phy + }, { + .compatible = "samsung,exynos7-usbdrd-phy", + .data = &exynos7_usbdrd_phy + }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos5_usbdrd_phy_of_match); + +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; + 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; + + ret = exynos5_usbdrd_phy_clk_handle(phy_drd); + if (ret) { + dev_err(dev, "Failed to initialize clocks\n"); + 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 regulators */ + 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; + } + + phy_drd->vbus_boost = devm_regulator_get(dev, "vbus-boost"); + if (IS_ERR(phy_drd->vbus_boost)) { + ret = PTR_ERR(phy_drd->vbus_boost); + if (ret == -EPROBE_DEFER) + return ret; + + dev_warn(dev, "Failed to get VBUS boost supply regulator\n"); + phy_drd->vbus_boost = NULL; + } + + dev_vdbg(dev, "Creating usbdrd_phy phy\n"); + + for (i = 0; i < EXYNOS5_DRDPHYS_NUM; i++) { + struct phy *phy = devm_phy_create(dev, NULL, + &exynos5_usbdrd_phy_ops); + 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", + } +}; + +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/samsung/phy-exynos5250-sata.c b/drivers/phy/samsung/phy-exynos5250-sata.c new file mode 100644 index 000000000000..60e13afcd9b8 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos5250-sata.c @@ -0,0 +1,250 @@ +/* + * 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 const 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, NULL, &exynos_sata_phy_ops); + 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", + } +}; +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/samsung/phy-exynos5250-usb2.c b/drivers/phy/samsung/phy-exynos5250-usb2.c new file mode 100644 index 000000000000..aad806272305 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos5250-usb2.c @@ -0,0 +1,401 @@ +/* + * 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; + } + 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; + + 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/samsung/phy-s5pv210-usb2.c b/drivers/phy/samsung/phy-s5pv210-usb2.c new file mode 100644 index 000000000000..f6f72339bbc3 --- /dev/null +++ b/drivers/phy/samsung/phy-s5pv210-usb2.c @@ -0,0 +1,187 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - S5PV210 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Authors: 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 "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define S5PV210_UPHYPWR 0x0 + +#define S5PV210_UPHYPWR_PHY0_SUSPEND BIT(0) +#define S5PV210_UPHYPWR_PHY0_PWR BIT(3) +#define S5PV210_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define S5PV210_UPHYPWR_PHY0 ( \ + S5PV210_UPHYPWR_PHY0_SUSPEND | \ + S5PV210_UPHYPWR_PHY0_PWR | \ + S5PV210_UPHYPWR_PHY0_OTG_PWR) + +#define S5PV210_UPHYPWR_PHY1_SUSPEND BIT(6) +#define S5PV210_UPHYPWR_PHY1_PWR BIT(7) +#define S5PV210_UPHYPWR_PHY1 ( \ + S5PV210_UPHYPWR_PHY1_SUSPEND | \ + S5PV210_UPHYPWR_PHY1_PWR) + +/* PHY clock control */ +#define S5PV210_UPHYCLK 0x4 + +#define S5PV210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) +#define S5PV210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) +#define S5PV210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) +#define S5PV210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) + +#define S5PV210_UPHYCLK_PHY0_ID_PULLUP BIT(2) +#define S5PV210_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define S5PV210_UPHYCLK_PHY1_COMMON_ON BIT(7) + +/* PHY reset control */ +#define S5PV210_UPHYRST 0x8 + +#define S5PV210_URSTCON_PHY0 BIT(0) +#define S5PV210_URSTCON_OTG_HLINK BIT(1) +#define S5PV210_URSTCON_OTG_PHYLINK BIT(2) +#define S5PV210_URSTCON_PHY1_ALL BIT(3) +#define S5PV210_URSTCON_HOST_LINK_ALL BIT(4) + +/* Isolation, configured in the power management unit */ +#define S5PV210_USB_ISOL_OFFSET 0x680c +#define S5PV210_USB_ISOL_DEVICE BIT(0) +#define S5PV210_USB_ISOL_HOST BIT(1) + + +enum s5pv210_phy_id { + S5PV210_DEVICE, + S5PV210_HOST, + S5PV210_NUM_PHYS, +}; + +/* + * s5pv210_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int s5pv210_rate_to_clk(unsigned long rate, u32 *reg) +{ + switch (rate) { + case 12 * MHZ: + *reg = S5PV210_UPHYCLK_PHYFSEL_12MHZ; + break; + case 24 * MHZ: + *reg = S5PV210_UPHYCLK_PHYFSEL_24MHZ; + break; + case 48 * MHZ: + *reg = S5PV210_UPHYCLK_PHYFSEL_48MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void s5pv210_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 mask; + + switch (inst->cfg->id) { + case S5PV210_DEVICE: + mask = S5PV210_USB_ISOL_DEVICE; + break; + case S5PV210_HOST: + mask = S5PV210_USB_ISOL_HOST; + break; + default: + return; + } + + regmap_update_bits(drv->reg_pmu, S5PV210_USB_ISOL_OFFSET, + mask, on ? 0 : mask); +} + +static void s5pv210_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; + + switch (inst->cfg->id) { + case S5PV210_DEVICE: + phypwr = S5PV210_UPHYPWR_PHY0; + rstbits = S5PV210_URSTCON_PHY0; + break; + case S5PV210_HOST: + phypwr = S5PV210_UPHYPWR_PHY1; + rstbits = S5PV210_URSTCON_PHY1_ALL | + S5PV210_URSTCON_HOST_LINK_ALL; + break; + } + + if (on) { + writel(drv->ref_reg_val, drv->reg_phy + S5PV210_UPHYCLK); + + pwr = readl(drv->reg_phy + S5PV210_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + S5PV210_UPHYPWR); + + rst = readl(drv->reg_phy + S5PV210_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + S5PV210_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + S5PV210_UPHYRST); + } else { + pwr = readl(drv->reg_phy + S5PV210_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + S5PV210_UPHYPWR); + } +} + +static int s5pv210_power_on(struct samsung_usb2_phy_instance *inst) +{ + s5pv210_isol(inst, 0); + s5pv210_phy_pwr(inst, 1); + + return 0; +} + +static int s5pv210_power_off(struct samsung_usb2_phy_instance *inst) +{ + s5pv210_phy_pwr(inst, 0); + s5pv210_isol(inst, 1); + + return 0; +} + +static const struct samsung_usb2_common_phy s5pv210_phys[S5PV210_NUM_PHYS] = { + [S5PV210_DEVICE] = { + .label = "device", + .id = S5PV210_DEVICE, + .power_on = s5pv210_power_on, + .power_off = s5pv210_power_off, + }, + [S5PV210_HOST] = { + .label = "host", + .id = S5PV210_HOST, + .power_on = s5pv210_power_on, + .power_off = s5pv210_power_off, + }, +}; + +const struct samsung_usb2_phy_config s5pv210_usb2_phy_config = { + .num_phys = ARRAY_SIZE(s5pv210_phys), + .phys = s5pv210_phys, + .rate_to_clk = s5pv210_rate_to_clk, +}; diff --git a/drivers/phy/samsung/phy-samsung-usb2.c b/drivers/phy/samsung/phy-samsung-usb2.c new file mode 100644 index 000000000000..1d22d93b552d --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-usb2.c @@ -0,0 +1,267 @@ +/* + * 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); + + if (drv->vbus) { + ret = regulator_enable(drv->vbus); + if (ret) + goto err_regulator; + } + + 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); + if (ret) + goto err_power_on; + } + + return 0; + +err_power_on: + clk_disable_unprepare(drv->ref_clk); +err_instance_clk: + clk_disable_unprepare(drv->clk); +err_main_clk: + if (drv->vbus) + regulator_disable(drv->vbus); +err_regulator: + 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); + if (ret) + return ret; + } + clk_disable_unprepare(drv->ref_clk); + clk_disable_unprepare(drv->clk); + if (drv->vbus) + ret = regulator_disable(drv->vbus); + + return ret; +} + +static const 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_EXYNOS4X12_USB2 + { + .compatible = "samsung,exynos3250-usb2-phy", + .data = &exynos3250_usb2_phy_config, + }, +#endif +#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 +#ifdef CONFIG_PHY_S5PV210_USB2 + { + .compatible = "samsung,s5pv210-usb2-phy", + .data = &s5pv210_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; + } + + drv->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(drv->vbus)) { + ret = PTR_ERR(drv->vbus); + if (ret == -EPROBE_DEFER) + return ret; + drv->vbus = NULL; + } + + 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, NULL, &samsung_usb2_phy_ops); + 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", + } +}; + +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/samsung/phy-samsung-usb2.h b/drivers/phy/samsung/phy-samsung-usb2.h new file mode 100644 index 000000000000..6563e7ca0ac4 --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-usb2.h @@ -0,0 +1,73 @@ +/* + * 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> +#include <linux/regulator/consumer.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; + int int_cnt; + int ext_cnt; +}; + +struct samsung_usb2_phy_driver { + const struct samsung_usb2_phy_config *cfg; + struct clk *clk; + struct clk *ref_clk; + struct regulator *vbus; + 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; + bool has_refclk_sel; +}; + +extern const struct samsung_usb2_phy_config exynos3250_usb2_phy_config; +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; +extern const struct samsung_usb2_phy_config s5pv210_usb2_phy_config; +#endif |