mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 17:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
289
drivers/phy/Kconfig
Normal file
289
drivers/phy/Kconfig
Normal file
|
@ -0,0 +1,289 @@
|
|||
#
|
||||
# PHY
|
||||
#
|
||||
|
||||
menu "PHY Subsystem"
|
||||
|
||||
config GENERIC_PHY
|
||||
bool "PHY Core"
|
||||
help
|
||||
Generic PHY support.
|
||||
|
||||
This framework is designed to provide a generic interface for PHY
|
||||
devices present in the kernel. This layer will have the generic
|
||||
API by which phy drivers can create PHY using the phy framework and
|
||||
phy users can obtain reference to the PHY. All the users of this
|
||||
framework should select this config.
|
||||
|
||||
config PHY_BERLIN_SATA
|
||||
tristate "Marvell Berlin SATA PHY driver"
|
||||
depends on ARCH_BERLIN && HAS_IOMEM && OF
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the SATA PHY on Marvell Berlin 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_MIPI
|
||||
tristate "Samsung EXYNOS SoC MIPI CSI/DSI PHY driver"
|
||||
depends on HAS_IOMEM
|
||||
depends on ARCH_EXYNOS && OF || COMPILE_TEST
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Support for MIPI CSI and MIPI DSI DPHY found on Samsung
|
||||
and EXYNOS SoCs.
|
||||
|
||||
config PHY_MVEBU_SATA
|
||||
def_bool y
|
||||
depends on ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD
|
||||
depends on OF
|
||||
select GENERIC_PHY
|
||||
|
||||
config PHY_MIPHY365X
|
||||
tristate "STMicroelectronics MIPHY365X PHY driver for STiH41x series"
|
||||
depends on ARCH_STI
|
||||
depends on HAS_IOMEM
|
||||
depends on OF
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the miphy transceiver (for SATA/PCIE)
|
||||
that is part of STMicroelectronics STiH41x SoC series.
|
||||
|
||||
config PHY_RCAR_GEN2
|
||||
tristate "Renesas R-Car generation 2 USB PHY driver"
|
||||
depends on ARCH_SHMOBILE
|
||||
depends on GENERIC_PHY
|
||||
help
|
||||
Support for USB PHY found on Renesas R-Car generation 2 SoCs.
|
||||
|
||||
config OMAP_CONTROL_PHY
|
||||
tristate "OMAP CONTROL PHY Driver"
|
||||
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
||||
help
|
||||
Enable this to add support for the PHY part present in the control
|
||||
module. This driver has API to power on the USB2 PHY and to write to
|
||||
the mailbox. The mailbox is present only in omap4 and the register to
|
||||
power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an
|
||||
additional register to power on USB3 PHY/SATA PHY/PCIE PHY
|
||||
(PIPE3 PHY).
|
||||
|
||||
config OMAP_USB2
|
||||
tristate "OMAP USB2 PHY Driver"
|
||||
depends on ARCH_OMAP2PLUS
|
||||
depends on USB_PHY
|
||||
select GENERIC_PHY
|
||||
select OMAP_CONTROL_PHY
|
||||
depends on OMAP_OCP2SCP
|
||||
help
|
||||
Enable this to support the transceiver that is part of SOC. This
|
||||
driver takes care of all the PHY functionality apart from comparator.
|
||||
The USB OTG controller communicates with the comparator using this
|
||||
driver.
|
||||
|
||||
config TI_PIPE3
|
||||
tristate "TI PIPE3 PHY Driver"
|
||||
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
||||
select GENERIC_PHY
|
||||
select OMAP_CONTROL_PHY
|
||||
depends on OMAP_OCP2SCP
|
||||
help
|
||||
Enable this to support the PIPE3 PHY that is part of TI SOCs. This
|
||||
driver takes care of all the PHY functionality apart from comparator.
|
||||
This driver interacts with the "OMAP Control PHY Driver" to power
|
||||
on/off the PHY.
|
||||
|
||||
config TWL4030_USB
|
||||
tristate "TWL4030 USB Transceiver Driver"
|
||||
depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS
|
||||
depends on USB_PHY
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the USB OTG transceiver on TWL4030
|
||||
family chips (including the TWL5030 and TPS659x0 devices).
|
||||
This transceiver supports high and full speed devices plus,
|
||||
in host mode, low speed.
|
||||
|
||||
config PHY_EXYNOS_DP_VIDEO
|
||||
tristate "EXYNOS SoC series Display Port PHY driver"
|
||||
depends on OF
|
||||
depends on ARCH_EXYNOS || COMPILE_TEST
|
||||
default ARCH_EXYNOS
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Support for Display Port PHY found on Samsung EXYNOS SoCs.
|
||||
|
||||
config BCM_KONA_USB2_PHY
|
||||
tristate "Broadcom Kona USB2 PHY Driver"
|
||||
depends on HAS_IOMEM
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the Broadcom Kona USB 2.0 PHY.
|
||||
|
||||
config PHY_EXYNOS5250_SATA
|
||||
tristate "Exynos5250 Sata SerDes/PHY driver"
|
||||
depends on SOC_EXYNOS5250
|
||||
depends on HAS_IOMEM
|
||||
depends on OF
|
||||
select GENERIC_PHY
|
||||
select I2C
|
||||
select I2C_S3C2410
|
||||
select MFD_SYSCON
|
||||
help
|
||||
Enable this to support SATA SerDes/Phy found on Samsung's
|
||||
Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s,
|
||||
SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host
|
||||
port to accept one SATA device.
|
||||
|
||||
config PHY_HIX5HD2_SATA
|
||||
tristate "HIX5HD2 SATA PHY Driver"
|
||||
depends on ARCH_HIX5HD2 && OF && HAS_IOMEM
|
||||
select GENERIC_PHY
|
||||
select MFD_SYSCON
|
||||
help
|
||||
Support for SATA PHY on Hisilicon hix5hd2 Soc.
|
||||
|
||||
config PHY_SUN4I_USB
|
||||
tristate "Allwinner sunxi SoC USB PHY driver"
|
||||
depends on ARCH_SUNXI && HAS_IOMEM && OF
|
||||
depends on RESET_CONTROLLER
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the transceiver that is part of Allwinner
|
||||
sunxi SoCs.
|
||||
|
||||
This driver controls the entire USB PHY block, both the USB OTG
|
||||
parts, as well as the 2 regular USB 2 host PHYs.
|
||||
|
||||
config PHY_SAMSUNG_USB2
|
||||
tristate "Samsung USB 2.0 PHY driver"
|
||||
depends on HAS_IOMEM
|
||||
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_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_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_EXYNOS5_USBDRD
|
||||
tristate "Exynos5 SoC series USB DRD PHY driver"
|
||||
depends on ARCH_EXYNOS5 && 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_EXYNOS_USBDRD
|
||||
tristate "Exynos 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 SoC series.
|
||||
This driver provides PHY interface for USB 3.0 DRD controller
|
||||
present on Exynos SoC series.
|
||||
|
||||
config PHY_SAMSUNG_USB_CAL
|
||||
bool "Samsung USB PHY CAL"
|
||||
depends on PHY_EXYNOS_USBDRD
|
||||
default y
|
||||
help
|
||||
Enable this to support CAL (Chip Abstraction Layer)
|
||||
for Samsung USB PHY controller.
|
||||
|
||||
config PHY_QCOM_APQ8064_SATA
|
||||
tristate "Qualcomm APQ8064 SATA SerDes/PHY driver"
|
||||
depends on ARCH_QCOM
|
||||
depends on HAS_IOMEM
|
||||
depends on OF
|
||||
select GENERIC_PHY
|
||||
|
||||
config PHY_QCOM_IPQ806X_SATA
|
||||
tristate "Qualcomm IPQ806x SATA SerDes/PHY driver"
|
||||
depends on ARCH_QCOM
|
||||
depends on HAS_IOMEM
|
||||
depends on OF
|
||||
select GENERIC_PHY
|
||||
|
||||
config PHY_ST_SPEAR1310_MIPHY
|
||||
tristate "ST SPEAR1310-MIPHY driver"
|
||||
select GENERIC_PHY
|
||||
depends on MACH_SPEAR1310 || COMPILE_TEST
|
||||
help
|
||||
Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA.
|
||||
|
||||
config PHY_ST_SPEAR1340_MIPHY
|
||||
tristate "ST SPEAR1340-MIPHY driver"
|
||||
select GENERIC_PHY
|
||||
depends on MACH_SPEAR1340 || COMPILE_TEST
|
||||
help
|
||||
Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA.
|
||||
|
||||
config PHY_XGENE
|
||||
tristate "APM X-Gene 15Gbps PHY support"
|
||||
depends on HAS_IOMEM && OF && (ARM64 || COMPILE_TEST)
|
||||
select GENERIC_PHY
|
||||
help
|
||||
This option enables support for APM X-Gene SoC multi-purpose PHY.
|
||||
|
||||
config PHY_STIH407_USB
|
||||
tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family"
|
||||
depends on RESET_CONTROLLER
|
||||
depends on ARCH_STI || COMPILE_TEST
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this support to enable the picoPHY device used by USB2
|
||||
and USB3 controllers on STMicroelectronics STiH407 SoC families.
|
||||
|
||||
config PHY_STIH41X_USB
|
||||
tristate "STMicroelectronics USB2 PHY driver for STiH41x series"
|
||||
depends on ARCH_STI
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the USB transceiver that is part of
|
||||
STMicroelectronics STiH41x SoC series.
|
||||
|
||||
endmenu
|
36
drivers/phy/Makefile
Normal file
36
drivers/phy/Makefile
Normal file
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# Makefile for the phy drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_GENERIC_PHY) += phy-core.o
|
||||
obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o
|
||||
obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
|
||||
obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o
|
||||
obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o
|
||||
obj-$(CONFIG_PHY_EXYNOS_MIPI) += phy-exynos-mipi.o
|
||||
obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o
|
||||
obj-$(CONFIG_PHY_MIPHY365X) += phy-miphy365x.o
|
||||
obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o
|
||||
obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o
|
||||
obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o
|
||||
obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
|
||||
obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o
|
||||
obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o
|
||||
obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o
|
||||
obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o
|
||||
obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o
|
||||
phy-exynos-usb2-y += phy-samsung-usb2.o
|
||||
phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o
|
||||
phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o
|
||||
phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o
|
||||
phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o
|
||||
obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o
|
||||
obj-$(CONFIG_PHY_EXYNOS_USBDRD) += phy-exynos-usbdrd.o
|
||||
obj-$(CONFIG_PHY_SAMSUNG_USB_CAL) += phy-samsung-usb3-cal.o
|
||||
obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o
|
||||
obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
|
||||
obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o
|
||||
obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o
|
||||
obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
|
||||
obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o
|
||||
obj-$(CONFIG_PHY_STIH41X_USB) += phy-stih41x-usb.o
|
155
drivers/phy/phy-bcm-kona-usb2.c
Normal file
155
drivers/phy/phy-bcm-kona-usb2.c
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* phy-bcm-kona-usb2.c - Broadcom Kona USB2 Phy Driver
|
||||
*
|
||||
* Copyright (C) 2013 Linaro Limited
|
||||
* Matt Porter <mporter@linaro.org>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define OTGCTL (0)
|
||||
#define OTGCTL_OTGSTAT2 BIT(31)
|
||||
#define OTGCTL_OTGSTAT1 BIT(30)
|
||||
#define OTGCTL_PRST_N_SW BIT(11)
|
||||
#define OTGCTL_HRESET_N BIT(10)
|
||||
#define OTGCTL_UTMI_LINE_STATE1 BIT(9)
|
||||
#define OTGCTL_UTMI_LINE_STATE0 BIT(8)
|
||||
|
||||
#define P1CTL (8)
|
||||
#define P1CTL_SOFT_RESET BIT(1)
|
||||
#define P1CTL_NON_DRIVING BIT(0)
|
||||
|
||||
struct bcm_kona_usb {
|
||||
void __iomem *regs;
|
||||
};
|
||||
|
||||
static void bcm_kona_usb_phy_power(struct bcm_kona_usb *phy, int on)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(phy->regs + OTGCTL);
|
||||
if (on) {
|
||||
/* Configure and power PHY */
|
||||
val &= ~(OTGCTL_OTGSTAT2 | OTGCTL_OTGSTAT1 |
|
||||
OTGCTL_UTMI_LINE_STATE1 | OTGCTL_UTMI_LINE_STATE0);
|
||||
val |= OTGCTL_PRST_N_SW | OTGCTL_HRESET_N;
|
||||
} else {
|
||||
val &= ~(OTGCTL_PRST_N_SW | OTGCTL_HRESET_N);
|
||||
}
|
||||
writel(val, phy->regs + OTGCTL);
|
||||
}
|
||||
|
||||
static int bcm_kona_usb_phy_init(struct phy *gphy)
|
||||
{
|
||||
struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
|
||||
u32 val;
|
||||
|
||||
/* Soft reset PHY */
|
||||
val = readl(phy->regs + P1CTL);
|
||||
val &= ~P1CTL_NON_DRIVING;
|
||||
val |= P1CTL_SOFT_RESET;
|
||||
writel(val, phy->regs + P1CTL);
|
||||
writel(val & ~P1CTL_SOFT_RESET, phy->regs + P1CTL);
|
||||
/* Reset needs to be asserted for 2ms */
|
||||
mdelay(2);
|
||||
writel(val | P1CTL_SOFT_RESET, phy->regs + P1CTL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm_kona_usb_phy_power_on(struct phy *gphy)
|
||||
{
|
||||
struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
|
||||
|
||||
bcm_kona_usb_phy_power(phy, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm_kona_usb_phy_power_off(struct phy *gphy)
|
||||
{
|
||||
struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
|
||||
|
||||
bcm_kona_usb_phy_power(phy, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops ops = {
|
||||
.init = bcm_kona_usb_phy_init,
|
||||
.power_on = bcm_kona_usb_phy_power_on,
|
||||
.power_off = bcm_kona_usb_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int bcm_kona_usb2_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct bcm_kona_usb *phy;
|
||||
struct resource *res;
|
||||
struct phy *gphy;
|
||||
struct phy_provider *phy_provider;
|
||||
|
||||
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
phy->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(phy->regs))
|
||||
return PTR_ERR(phy->regs);
|
||||
|
||||
platform_set_drvdata(pdev, phy);
|
||||
|
||||
gphy = devm_phy_create(dev, NULL, &ops, NULL);
|
||||
if (IS_ERR(gphy))
|
||||
return PTR_ERR(gphy);
|
||||
|
||||
/* The Kona PHY supports an 8-bit wide UTMI interface */
|
||||
phy_set_bus_width(gphy, 8);
|
||||
|
||||
phy_set_drvdata(gphy, phy);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev,
|
||||
of_phy_simple_xlate);
|
||||
|
||||
return PTR_ERR_OR_ZERO(phy_provider);
|
||||
}
|
||||
|
||||
static const struct of_device_id bcm_kona_usb2_dt_ids[] = {
|
||||
{ .compatible = "brcm,kona-usb2-phy" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, bcm_kona_usb2_dt_ids);
|
||||
|
||||
static struct platform_driver bcm_kona_usb2_driver = {
|
||||
.probe = bcm_kona_usb2_probe,
|
||||
.driver = {
|
||||
.name = "bcm-kona-usb2",
|
||||
.of_match_table = bcm_kona_usb2_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(bcm_kona_usb2_driver);
|
||||
|
||||
MODULE_ALIAS("platform:bcm-kona-usb2");
|
||||
MODULE_AUTHOR("Matt Porter <mporter@linaro.org>");
|
||||
MODULE_DESCRIPTION("BCM Kona USB 2.0 PHY driver");
|
||||
MODULE_LICENSE("GPL v2");
|
283
drivers/phy/phy-berlin-sata.c
Normal file
283
drivers/phy/phy-berlin-sata.c
Normal file
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Marvell Berlin SATA PHY driver
|
||||
*
|
||||
* Copyright (C) 2014 Marvell Technology Group Ltd.
|
||||
*
|
||||
* Antoine Ténart <antoine.tenart@free-electrons.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define HOST_VSA_ADDR 0x0
|
||||
#define HOST_VSA_DATA 0x4
|
||||
#define PORT_SCR_CTL 0x2c
|
||||
#define PORT_VSR_ADDR 0x78
|
||||
#define PORT_VSR_DATA 0x7c
|
||||
|
||||
#define CONTROL_REGISTER 0x0
|
||||
#define MBUS_SIZE_CONTROL 0x4
|
||||
|
||||
#define POWER_DOWN_PHY0 BIT(6)
|
||||
#define POWER_DOWN_PHY1 BIT(14)
|
||||
#define MBUS_WRITE_REQUEST_SIZE_128 (BIT(2) << 16)
|
||||
#define MBUS_READ_REQUEST_SIZE_128 (BIT(2) << 19)
|
||||
|
||||
#define PHY_BASE 0x200
|
||||
|
||||
/* register 0x01 */
|
||||
#define REF_FREF_SEL_25 BIT(0)
|
||||
#define PHY_MODE_SATA (0x0 << 5)
|
||||
|
||||
/* register 0x02 */
|
||||
#define USE_MAX_PLL_RATE BIT(12)
|
||||
|
||||
/* register 0x23 */
|
||||
#define DATA_BIT_WIDTH_10 (0x0 << 10)
|
||||
#define DATA_BIT_WIDTH_20 (0x1 << 10)
|
||||
#define DATA_BIT_WIDTH_40 (0x2 << 10)
|
||||
|
||||
/* register 0x25 */
|
||||
#define PHY_GEN_MAX_1_5 (0x0 << 10)
|
||||
#define PHY_GEN_MAX_3_0 (0x1 << 10)
|
||||
#define PHY_GEN_MAX_6_0 (0x2 << 10)
|
||||
|
||||
struct phy_berlin_desc {
|
||||
struct phy *phy;
|
||||
u32 power_bit;
|
||||
unsigned index;
|
||||
};
|
||||
|
||||
struct phy_berlin_priv {
|
||||
void __iomem *base;
|
||||
spinlock_t lock;
|
||||
struct clk *clk;
|
||||
struct phy_berlin_desc **phys;
|
||||
unsigned nphys;
|
||||
};
|
||||
|
||||
static inline void phy_berlin_sata_reg_setbits(void __iomem *ctrl_reg, u32 reg,
|
||||
u32 mask, u32 val)
|
||||
{
|
||||
u32 regval;
|
||||
|
||||
/* select register */
|
||||
writel(PHY_BASE + reg, ctrl_reg + PORT_VSR_ADDR);
|
||||
|
||||
/* set bits */
|
||||
regval = readl(ctrl_reg + PORT_VSR_DATA);
|
||||
regval &= ~mask;
|
||||
regval |= val;
|
||||
writel(regval, ctrl_reg + PORT_VSR_DATA);
|
||||
}
|
||||
|
||||
static int phy_berlin_sata_power_on(struct phy *phy)
|
||||
{
|
||||
struct phy_berlin_desc *desc = phy_get_drvdata(phy);
|
||||
struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent);
|
||||
void __iomem *ctrl_reg = priv->base + 0x60 + (desc->index * 0x80);
|
||||
int ret = 0;
|
||||
u32 regval;
|
||||
|
||||
clk_prepare_enable(priv->clk);
|
||||
|
||||
spin_lock(&priv->lock);
|
||||
|
||||
/* Power on PHY */
|
||||
writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR);
|
||||
regval = readl(priv->base + HOST_VSA_DATA);
|
||||
regval &= ~desc->power_bit;
|
||||
writel(regval, priv->base + HOST_VSA_DATA);
|
||||
|
||||
/* Configure MBus */
|
||||
writel(MBUS_SIZE_CONTROL, priv->base + HOST_VSA_ADDR);
|
||||
regval = readl(priv->base + HOST_VSA_DATA);
|
||||
regval |= MBUS_WRITE_REQUEST_SIZE_128 | MBUS_READ_REQUEST_SIZE_128;
|
||||
writel(regval, priv->base + HOST_VSA_DATA);
|
||||
|
||||
/* set PHY mode and ref freq to 25 MHz */
|
||||
phy_berlin_sata_reg_setbits(ctrl_reg, 0x1, 0xff,
|
||||
REF_FREF_SEL_25 | PHY_MODE_SATA);
|
||||
|
||||
/* set PHY up to 6 Gbps */
|
||||
phy_berlin_sata_reg_setbits(ctrl_reg, 0x25, 0xc00, PHY_GEN_MAX_6_0);
|
||||
|
||||
/* set 40 bits width */
|
||||
phy_berlin_sata_reg_setbits(ctrl_reg, 0x23, 0xc00, DATA_BIT_WIDTH_40);
|
||||
|
||||
/* use max pll rate */
|
||||
phy_berlin_sata_reg_setbits(ctrl_reg, 0x2, 0x0, USE_MAX_PLL_RATE);
|
||||
|
||||
/* set Gen3 controller speed */
|
||||
regval = readl(ctrl_reg + PORT_SCR_CTL);
|
||||
regval &= ~GENMASK(7, 4);
|
||||
regval |= 0x30;
|
||||
writel(regval, ctrl_reg + PORT_SCR_CTL);
|
||||
|
||||
spin_unlock(&priv->lock);
|
||||
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int phy_berlin_sata_power_off(struct phy *phy)
|
||||
{
|
||||
struct phy_berlin_desc *desc = phy_get_drvdata(phy);
|
||||
struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent);
|
||||
u32 regval;
|
||||
|
||||
clk_prepare_enable(priv->clk);
|
||||
|
||||
spin_lock(&priv->lock);
|
||||
|
||||
/* Power down PHY */
|
||||
writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR);
|
||||
regval = readl(priv->base + HOST_VSA_DATA);
|
||||
regval |= desc->power_bit;
|
||||
writel(regval, priv->base + HOST_VSA_DATA);
|
||||
|
||||
spin_unlock(&priv->lock);
|
||||
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy *phy_berlin_sata_phy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct phy_berlin_priv *priv = dev_get_drvdata(dev);
|
||||
int i;
|
||||
|
||||
if (WARN_ON(args->args[0] >= priv->nphys))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
for (i = 0; i < priv->nphys; i++) {
|
||||
if (priv->phys[i]->index == args->args[0])
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == priv->nphys)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return priv->phys[i]->phy;
|
||||
}
|
||||
|
||||
static struct phy_ops phy_berlin_sata_ops = {
|
||||
.power_on = phy_berlin_sata_power_on,
|
||||
.power_off = phy_berlin_sata_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static u32 phy_berlin_power_down_bits[] = {
|
||||
POWER_DOWN_PHY0,
|
||||
POWER_DOWN_PHY1,
|
||||
};
|
||||
|
||||
static int phy_berlin_sata_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *child;
|
||||
struct phy *phy;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy_berlin_priv *priv;
|
||||
struct resource *res;
|
||||
int i = 0;
|
||||
u32 phy_id;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -EINVAL;
|
||||
|
||||
priv->base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!priv->base)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(priv->clk))
|
||||
return PTR_ERR(priv->clk);
|
||||
|
||||
priv->nphys = of_get_child_count(dev->of_node);
|
||||
if (priv->nphys == 0)
|
||||
return -ENODEV;
|
||||
|
||||
priv->phys = devm_kzalloc(dev, priv->nphys * sizeof(*priv->phys),
|
||||
GFP_KERNEL);
|
||||
if (!priv->phys)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, priv);
|
||||
spin_lock_init(&priv->lock);
|
||||
|
||||
for_each_available_child_of_node(dev->of_node, child) {
|
||||
struct phy_berlin_desc *phy_desc;
|
||||
|
||||
if (of_property_read_u32(child, "reg", &phy_id)) {
|
||||
dev_err(dev, "missing reg property in node %s\n",
|
||||
child->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (phy_id >= ARRAY_SIZE(phy_berlin_power_down_bits)) {
|
||||
dev_err(dev, "invalid reg in node %s\n", child->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL);
|
||||
if (!phy_desc)
|
||||
return -ENOMEM;
|
||||
|
||||
phy = devm_phy_create(dev, NULL, &phy_berlin_sata_ops, NULL);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create PHY %d\n", phy_id);
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_desc->phy = phy;
|
||||
phy_desc->power_bit = phy_berlin_power_down_bits[phy_id];
|
||||
phy_desc->index = phy_id;
|
||||
phy_set_drvdata(phy, phy_desc);
|
||||
|
||||
priv->phys[i++] = phy_desc;
|
||||
|
||||
/* Make sure the PHY is off */
|
||||
phy_berlin_sata_power_off(phy);
|
||||
}
|
||||
|
||||
phy_provider =
|
||||
devm_of_phy_provider_register(dev, phy_berlin_sata_phy_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id phy_berlin_sata_of_match[] = {
|
||||
{ .compatible = "marvell,berlin2q-sata-phy" },
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver phy_berlin_sata_driver = {
|
||||
.probe = phy_berlin_sata_probe,
|
||||
.driver = {
|
||||
.name = "phy-berlin-sata",
|
||||
.of_match_table = phy_berlin_sata_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(phy_berlin_sata_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Marvell Berlin SATA PHY driver");
|
||||
MODULE_AUTHOR("Antoine Ténart <antoine.tenart@free-electrons.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
906
drivers/phy/phy-core.c
Normal file
906
drivers/phy/phy-core.c
Normal file
|
@ -0,0 +1,906 @@
|
|||
/*
|
||||
* phy-core.c -- Generic Phy framework.
|
||||
*
|
||||
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
|
||||
*
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
static struct class *phy_class;
|
||||
static DEFINE_MUTEX(phy_provider_mutex);
|
||||
static LIST_HEAD(phy_provider_list);
|
||||
static DEFINE_IDA(phy_ida);
|
||||
|
||||
static void devm_phy_release(struct device *dev, void *res)
|
||||
{
|
||||
struct phy *phy = *(struct phy **)res;
|
||||
|
||||
phy_put(phy);
|
||||
}
|
||||
|
||||
static void devm_phy_provider_release(struct device *dev, void *res)
|
||||
{
|
||||
struct phy_provider *phy_provider = *(struct phy_provider **)res;
|
||||
|
||||
of_phy_provider_unregister(phy_provider);
|
||||
}
|
||||
|
||||
static void devm_phy_consume(struct device *dev, void *res)
|
||||
{
|
||||
struct phy *phy = *(struct phy **)res;
|
||||
|
||||
phy_destroy(phy);
|
||||
}
|
||||
|
||||
static int devm_phy_match(struct device *dev, void *res, void *match_data)
|
||||
{
|
||||
struct phy **phy = res;
|
||||
|
||||
return *phy == match_data;
|
||||
}
|
||||
|
||||
static struct phy *phy_lookup(struct device *device, const char *port)
|
||||
{
|
||||
unsigned int count;
|
||||
struct phy *phy;
|
||||
struct device *dev;
|
||||
struct phy_consumer *consumers;
|
||||
struct class_dev_iter iter;
|
||||
|
||||
class_dev_iter_init(&iter, phy_class, NULL, NULL);
|
||||
while ((dev = class_dev_iter_next(&iter))) {
|
||||
phy = to_phy(dev);
|
||||
|
||||
if (!phy->init_data)
|
||||
continue;
|
||||
count = phy->init_data->num_consumers;
|
||||
consumers = phy->init_data->consumers;
|
||||
while (count--) {
|
||||
if (!strcmp(consumers->dev_name, dev_name(device)) &&
|
||||
!strcmp(consumers->port, port)) {
|
||||
class_dev_iter_exit(&iter);
|
||||
return phy;
|
||||
}
|
||||
consumers++;
|
||||
}
|
||||
}
|
||||
|
||||
class_dev_iter_exit(&iter);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static struct phy_provider *of_phy_provider_lookup(struct device_node *node)
|
||||
{
|
||||
struct phy_provider *phy_provider;
|
||||
struct device_node *child;
|
||||
|
||||
list_for_each_entry(phy_provider, &phy_provider_list, list) {
|
||||
if (phy_provider->dev->of_node == node)
|
||||
return phy_provider;
|
||||
|
||||
for_each_child_of_node(phy_provider->dev->of_node, child)
|
||||
if (child == node)
|
||||
return phy_provider;
|
||||
}
|
||||
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
|
||||
int phy_pm_runtime_get(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!pm_runtime_enabled(&phy->dev))
|
||||
return -ENOTSUPP;
|
||||
|
||||
ret = pm_runtime_get(&phy->dev);
|
||||
if (ret < 0 && ret != -EINPROGRESS)
|
||||
pm_runtime_put_noidle(&phy->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_pm_runtime_get);
|
||||
|
||||
int phy_pm_runtime_get_sync(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!pm_runtime_enabled(&phy->dev))
|
||||
return -ENOTSUPP;
|
||||
|
||||
ret = pm_runtime_get_sync(&phy->dev);
|
||||
if (ret < 0)
|
||||
pm_runtime_put_sync(&phy->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_pm_runtime_get_sync);
|
||||
|
||||
int phy_pm_runtime_put(struct phy *phy)
|
||||
{
|
||||
if (!pm_runtime_enabled(&phy->dev))
|
||||
return -ENOTSUPP;
|
||||
|
||||
return pm_runtime_put(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_pm_runtime_put);
|
||||
|
||||
int phy_pm_runtime_put_sync(struct phy *phy)
|
||||
{
|
||||
if (!pm_runtime_enabled(&phy->dev))
|
||||
return -ENOTSUPP;
|
||||
|
||||
return pm_runtime_put_sync(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_pm_runtime_put_sync);
|
||||
|
||||
void phy_pm_runtime_allow(struct phy *phy)
|
||||
{
|
||||
if (!pm_runtime_enabled(&phy->dev))
|
||||
return;
|
||||
|
||||
pm_runtime_allow(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_pm_runtime_allow);
|
||||
|
||||
void phy_pm_runtime_forbid(struct phy *phy)
|
||||
{
|
||||
if (!pm_runtime_enabled(&phy->dev))
|
||||
return;
|
||||
|
||||
pm_runtime_forbid(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_pm_runtime_forbid);
|
||||
|
||||
int phy_init(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!phy)
|
||||
return 0;
|
||||
|
||||
ret = phy_pm_runtime_get_sync(phy);
|
||||
if (ret < 0 && ret != -ENOTSUPP)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&phy->mutex);
|
||||
if (phy->init_count == 0 && phy->ops->init) {
|
||||
ret = phy->ops->init(phy);
|
||||
if (ret < 0) {
|
||||
dev_err(&phy->dev, "phy init failed --> %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
ret = 0; /* Override possible ret == -ENOTSUPP */
|
||||
}
|
||||
++phy->init_count;
|
||||
|
||||
out:
|
||||
mutex_unlock(&phy->mutex);
|
||||
phy_pm_runtime_put(phy);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_init);
|
||||
|
||||
int phy_exit(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!phy)
|
||||
return 0;
|
||||
|
||||
ret = phy_pm_runtime_get_sync(phy);
|
||||
if (ret < 0 && ret != -ENOTSUPP)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&phy->mutex);
|
||||
if (phy->init_count == 1 && phy->ops->exit) {
|
||||
ret = phy->ops->exit(phy);
|
||||
if (ret < 0) {
|
||||
dev_err(&phy->dev, "phy exit failed --> %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
--phy->init_count;
|
||||
|
||||
out:
|
||||
mutex_unlock(&phy->mutex);
|
||||
phy_pm_runtime_put(phy);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_exit);
|
||||
|
||||
int phy_tune(struct phy *phy, int phy_state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!phy || !phy->ops->tune)
|
||||
return 0;
|
||||
|
||||
ret = phy->ops->tune(phy, phy_state);
|
||||
if (ret < 0) {
|
||||
dev_err(&phy->dev, "phy tune failed --> %d\n", ret);
|
||||
} else {
|
||||
ret = 0; /* Override possible ret == -ENOTSUPP */
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_tune);
|
||||
|
||||
int phy_set(struct phy *phy, int option, void *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!phy || !phy->ops->set)
|
||||
return 0;
|
||||
|
||||
ret = phy->ops->set(phy, option, info);
|
||||
if (ret < 0)
|
||||
dev_err(&phy->dev, "phy set failed --> %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_set);
|
||||
|
||||
int phy_power_on(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!phy)
|
||||
return 0;
|
||||
|
||||
if (phy->pwr) {
|
||||
ret = regulator_enable(phy->pwr);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = phy_pm_runtime_get_sync(phy);
|
||||
if (ret < 0 && ret != -ENOTSUPP)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&phy->mutex);
|
||||
if (phy->power_count == 0 && phy->ops->power_on) {
|
||||
ret = phy->ops->power_on(phy);
|
||||
if (ret < 0) {
|
||||
dev_err(&phy->dev, "phy poweron failed --> %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
ret = 0; /* Override possible ret == -ENOTSUPP */
|
||||
}
|
||||
++phy->power_count;
|
||||
mutex_unlock(&phy->mutex);
|
||||
return 0;
|
||||
|
||||
out:
|
||||
mutex_unlock(&phy->mutex);
|
||||
phy_pm_runtime_put_sync(phy);
|
||||
if (phy->pwr)
|
||||
regulator_disable(phy->pwr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_power_on);
|
||||
|
||||
int phy_power_off(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!phy)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&phy->mutex);
|
||||
if (phy->power_count == 1 && phy->ops->power_off) {
|
||||
ret = phy->ops->power_off(phy);
|
||||
if (ret < 0) {
|
||||
dev_err(&phy->dev, "phy poweroff failed --> %d\n", ret);
|
||||
mutex_unlock(&phy->mutex);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
--phy->power_count;
|
||||
mutex_unlock(&phy->mutex);
|
||||
phy_pm_runtime_put(phy);
|
||||
|
||||
if (phy->pwr)
|
||||
regulator_disable(phy->pwr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_power_off);
|
||||
|
||||
/**
|
||||
* _of_phy_get() - lookup and obtain a reference to a phy by phandle
|
||||
* @np: device_node for which to get the phy
|
||||
* @index: the index of the phy
|
||||
*
|
||||
* Returns the phy associated with the given phandle value,
|
||||
* after getting a refcount to it or -ENODEV if there is no such phy or
|
||||
* -EPROBE_DEFER if there is a phandle to the phy, but the device is
|
||||
* not yet loaded. This function uses of_xlate call back function provided
|
||||
* while registering the phy_provider to find the phy instance.
|
||||
*/
|
||||
static struct phy *_of_phy_get(struct device_node *np, int index)
|
||||
{
|
||||
int ret;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy *phy = NULL;
|
||||
struct of_phandle_args args;
|
||||
|
||||
ret = of_parse_phandle_with_args(np, "phys", "#phy-cells",
|
||||
index, &args);
|
||||
if (ret)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
mutex_lock(&phy_provider_mutex);
|
||||
phy_provider = of_phy_provider_lookup(args.np);
|
||||
if (IS_ERR(phy_provider) || !try_module_get(phy_provider->owner)) {
|
||||
phy = ERR_PTR(-EPROBE_DEFER);
|
||||
goto err0;
|
||||
}
|
||||
|
||||
phy = phy_provider->of_xlate(phy_provider->dev, &args);
|
||||
module_put(phy_provider->owner);
|
||||
|
||||
err0:
|
||||
mutex_unlock(&phy_provider_mutex);
|
||||
of_node_put(args.np);
|
||||
|
||||
return phy;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_phy_get() - lookup and obtain a reference to a phy using a device_node.
|
||||
* @np: device_node for which to get the phy
|
||||
* @con_id: name of the phy from device's point of view
|
||||
*
|
||||
* Returns the phy driver, after getting a refcount to it; or
|
||||
* -ENODEV if there is no such phy. The caller is responsible for
|
||||
* calling phy_put() to release that count.
|
||||
*/
|
||||
struct phy *of_phy_get(struct device_node *np, const char *con_id)
|
||||
{
|
||||
struct phy *phy = NULL;
|
||||
int index = 0;
|
||||
|
||||
if (con_id)
|
||||
index = of_property_match_string(np, "phy-names", con_id);
|
||||
|
||||
phy = _of_phy_get(np, index);
|
||||
if (IS_ERR(phy))
|
||||
return phy;
|
||||
|
||||
if (!try_module_get(phy->ops->owner))
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
get_device(&phy->dev);
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_phy_get);
|
||||
|
||||
/**
|
||||
* phy_put() - release the PHY
|
||||
* @phy: the phy returned by phy_get()
|
||||
*
|
||||
* Releases a refcount the caller received from phy_get().
|
||||
*/
|
||||
void phy_put(struct phy *phy)
|
||||
{
|
||||
if (!phy || IS_ERR(phy))
|
||||
return;
|
||||
|
||||
module_put(phy->ops->owner);
|
||||
put_device(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_put);
|
||||
|
||||
/**
|
||||
* devm_phy_put() - release the PHY
|
||||
* @dev: device that wants to release this phy
|
||||
* @phy: the phy returned by devm_phy_get()
|
||||
*
|
||||
* destroys the devres associated with this phy and invokes phy_put
|
||||
* to release the phy.
|
||||
*/
|
||||
void devm_phy_put(struct device *dev, struct phy *phy)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (!phy)
|
||||
return;
|
||||
|
||||
r = devres_destroy(dev, devm_phy_release, devm_phy_match, phy);
|
||||
dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_phy_put);
|
||||
|
||||
/**
|
||||
* of_phy_simple_xlate() - returns the phy instance from phy provider
|
||||
* @dev: the PHY provider device
|
||||
* @args: of_phandle_args (not used here)
|
||||
*
|
||||
* Intended to be used by phy provider for the common case where #phy-cells is
|
||||
* 0. For other cases where #phy-cells is greater than '0', the phy provider
|
||||
* should provide a custom of_xlate function that reads the *args* and returns
|
||||
* the appropriate phy.
|
||||
*/
|
||||
struct phy *of_phy_simple_xlate(struct device *dev, struct of_phandle_args
|
||||
*args)
|
||||
{
|
||||
struct phy *phy;
|
||||
struct class_dev_iter iter;
|
||||
struct device_node *node = dev->of_node;
|
||||
struct device_node *child;
|
||||
|
||||
class_dev_iter_init(&iter, phy_class, NULL, NULL);
|
||||
while ((dev = class_dev_iter_next(&iter))) {
|
||||
phy = to_phy(dev);
|
||||
if (node != phy->dev.of_node) {
|
||||
for_each_child_of_node(node, child) {
|
||||
if (child == phy->dev.of_node)
|
||||
goto phy_found;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
phy_found:
|
||||
class_dev_iter_exit(&iter);
|
||||
return phy;
|
||||
}
|
||||
|
||||
class_dev_iter_exit(&iter);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_phy_simple_xlate);
|
||||
|
||||
/**
|
||||
* phy_get() - lookup and obtain a reference to a phy.
|
||||
* @dev: device that requests this phy
|
||||
* @string: the phy name as given in the dt data or the name of the controller
|
||||
* port for non-dt case
|
||||
*
|
||||
* Returns the phy driver, after getting a refcount to it; or
|
||||
* -ENODEV if there is no such phy. The caller is responsible for
|
||||
* calling phy_put() to release that count.
|
||||
*/
|
||||
struct phy *phy_get(struct device *dev, const char *string)
|
||||
{
|
||||
int index = 0;
|
||||
struct phy *phy;
|
||||
|
||||
if (string == NULL) {
|
||||
dev_WARN(dev, "missing string\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
if (dev->of_node) {
|
||||
index = of_property_match_string(dev->of_node, "phy-names",
|
||||
string);
|
||||
phy = _of_phy_get(dev->of_node, index);
|
||||
} else {
|
||||
phy = phy_lookup(dev, string);
|
||||
}
|
||||
if (IS_ERR(phy))
|
||||
return phy;
|
||||
|
||||
if (!try_module_get(phy->ops->owner))
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
get_device(&phy->dev);
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_get);
|
||||
|
||||
/**
|
||||
* phy_optional_get() - lookup and obtain a reference to an optional phy.
|
||||
* @dev: device that requests this phy
|
||||
* @string: the phy name as given in the dt data or the name of the controller
|
||||
* port for non-dt case
|
||||
*
|
||||
* Returns the phy driver, after getting a refcount to it; or
|
||||
* NULL if there is no such phy. The caller is responsible for
|
||||
* calling phy_put() to release that count.
|
||||
*/
|
||||
struct phy *phy_optional_get(struct device *dev, const char *string)
|
||||
{
|
||||
struct phy *phy = phy_get(dev, string);
|
||||
|
||||
if (PTR_ERR(phy) == -ENODEV)
|
||||
phy = NULL;
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_optional_get);
|
||||
|
||||
/**
|
||||
* devm_phy_get() - lookup and obtain a reference to a phy.
|
||||
* @dev: device that requests this phy
|
||||
* @string: the phy name as given in the dt data or phy device name
|
||||
* for non-dt case
|
||||
*
|
||||
* Gets the phy using phy_get(), and associates a device with it using
|
||||
* devres. On driver detach, release function is invoked on the devres data,
|
||||
* then, devres data is freed.
|
||||
*/
|
||||
struct phy *devm_phy_get(struct device *dev, const char *string)
|
||||
{
|
||||
struct phy **ptr, *phy;
|
||||
|
||||
ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
phy = phy_get(dev, string);
|
||||
if (!IS_ERR(phy)) {
|
||||
*ptr = phy;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_phy_get);
|
||||
|
||||
/**
|
||||
* devm_phy_optional_get() - lookup and obtain a reference to an optional phy.
|
||||
* @dev: device that requests this phy
|
||||
* @string: the phy name as given in the dt data or phy device name
|
||||
* for non-dt case
|
||||
*
|
||||
* Gets the phy using phy_get(), and associates a device with it using
|
||||
* devres. On driver detach, release function is invoked on the devres
|
||||
* data, then, devres data is freed. This differs to devm_phy_get() in
|
||||
* that if the phy does not exist, it is not considered an error and
|
||||
* -ENODEV will not be returned. Instead the NULL phy is returned,
|
||||
* which can be passed to all other phy consumer calls.
|
||||
*/
|
||||
struct phy *devm_phy_optional_get(struct device *dev, const char *string)
|
||||
{
|
||||
struct phy *phy = devm_phy_get(dev, string);
|
||||
|
||||
if (PTR_ERR(phy) == -ENODEV)
|
||||
phy = NULL;
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_phy_optional_get);
|
||||
|
||||
/**
|
||||
* devm_of_phy_get() - lookup and obtain a reference to a phy.
|
||||
* @dev: device that requests this phy
|
||||
* @np: node containing the phy
|
||||
* @con_id: name of the phy from device's point of view
|
||||
*
|
||||
* Gets the phy using of_phy_get(), and associates a device with it using
|
||||
* devres. On driver detach, release function is invoked on the devres data,
|
||||
* then, devres data is freed.
|
||||
*/
|
||||
struct phy *devm_of_phy_get(struct device *dev, struct device_node *np,
|
||||
const char *con_id)
|
||||
{
|
||||
struct phy **ptr, *phy;
|
||||
|
||||
ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
phy = of_phy_get(np, con_id);
|
||||
if (!IS_ERR(phy)) {
|
||||
*ptr = phy;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_of_phy_get);
|
||||
|
||||
/**
|
||||
* phy_create() - create a new phy
|
||||
* @dev: device that is creating the new phy
|
||||
* @node: device node of the phy
|
||||
* @ops: function pointers for performing phy operations
|
||||
* @init_data: contains the list of PHY consumers or NULL
|
||||
*
|
||||
* Called to create a phy using phy framework.
|
||||
*/
|
||||
struct phy *phy_create(struct device *dev, struct device_node *node,
|
||||
const struct phy_ops *ops,
|
||||
struct phy_init_data *init_data)
|
||||
{
|
||||
int ret;
|
||||
int id;
|
||||
struct phy *phy;
|
||||
|
||||
if (WARN_ON(!dev))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
phy = kzalloc(sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
id = ida_simple_get(&phy_ida, 0, 0, GFP_KERNEL);
|
||||
if (id < 0) {
|
||||
dev_err(dev, "unable to get id\n");
|
||||
ret = id;
|
||||
goto free_phy;
|
||||
}
|
||||
|
||||
/* phy-supply */
|
||||
phy->pwr = regulator_get_optional(dev, "phy");
|
||||
if (IS_ERR(phy->pwr)) {
|
||||
if (PTR_ERR(phy->pwr) == -EPROBE_DEFER) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto free_ida;
|
||||
}
|
||||
phy->pwr = NULL;
|
||||
}
|
||||
|
||||
device_initialize(&phy->dev);
|
||||
mutex_init(&phy->mutex);
|
||||
|
||||
phy->dev.class = phy_class;
|
||||
phy->dev.parent = dev;
|
||||
phy->dev.of_node = node ?: dev->of_node;
|
||||
phy->id = id;
|
||||
phy->ops = ops;
|
||||
phy->init_data = init_data;
|
||||
|
||||
ret = dev_set_name(&phy->dev, "phy-%s.%d", dev_name(dev), id);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
ret = device_add(&phy->dev);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
if (pm_runtime_enabled(dev)) {
|
||||
pm_runtime_enable(&phy->dev);
|
||||
pm_runtime_no_callbacks(&phy->dev);
|
||||
}
|
||||
|
||||
return phy;
|
||||
|
||||
put_dev:
|
||||
put_device(&phy->dev); /* calls phy_release() which frees resources */
|
||||
return ERR_PTR(ret);
|
||||
|
||||
free_ida:
|
||||
ida_simple_remove(&phy_ida, phy->id);
|
||||
|
||||
free_phy:
|
||||
kfree(phy);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_create);
|
||||
|
||||
/**
|
||||
* devm_phy_create() - create a new phy
|
||||
* @dev: device that is creating the new phy
|
||||
* @node: device node of the phy
|
||||
* @ops: function pointers for performing phy operations
|
||||
* @init_data: contains the list of PHY consumers or NULL
|
||||
*
|
||||
* Creates a new PHY device adding it to the PHY class.
|
||||
* While at that, it also associates the device with the phy using devres.
|
||||
* On driver detach, release function is invoked on the devres data,
|
||||
* then, devres data is freed.
|
||||
*/
|
||||
struct phy *devm_phy_create(struct device *dev, struct device_node *node,
|
||||
const struct phy_ops *ops,
|
||||
struct phy_init_data *init_data)
|
||||
{
|
||||
struct phy **ptr, *phy;
|
||||
|
||||
ptr = devres_alloc(devm_phy_consume, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
phy = phy_create(dev, node, ops, init_data);
|
||||
if (!IS_ERR(phy)) {
|
||||
*ptr = phy;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_phy_create);
|
||||
|
||||
/**
|
||||
* phy_destroy() - destroy the phy
|
||||
* @phy: the phy to be destroyed
|
||||
*
|
||||
* Called to destroy the phy.
|
||||
*/
|
||||
void phy_destroy(struct phy *phy)
|
||||
{
|
||||
pm_runtime_disable(&phy->dev);
|
||||
device_unregister(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(phy_destroy);
|
||||
|
||||
/**
|
||||
* devm_phy_destroy() - destroy the PHY
|
||||
* @dev: device that wants to release this phy
|
||||
* @phy: the phy returned by devm_phy_get()
|
||||
*
|
||||
* destroys the devres associated with this phy and invokes phy_destroy
|
||||
* to destroy the phy.
|
||||
*/
|
||||
void devm_phy_destroy(struct device *dev, struct phy *phy)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = devres_destroy(dev, devm_phy_consume, devm_phy_match, phy);
|
||||
dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_phy_destroy);
|
||||
|
||||
/**
|
||||
* __of_phy_provider_register() - create/register phy provider with the framework
|
||||
* @dev: struct device of the phy provider
|
||||
* @owner: the module owner containing of_xlate
|
||||
* @of_xlate: function pointer to obtain phy instance from phy provider
|
||||
*
|
||||
* Creates struct phy_provider from dev and of_xlate function pointer.
|
||||
* This is used in the case of dt boot for finding the phy instance from
|
||||
* phy provider.
|
||||
*/
|
||||
struct phy_provider *__of_phy_provider_register(struct device *dev,
|
||||
struct module *owner, struct phy * (*of_xlate)(struct device *dev,
|
||||
struct of_phandle_args *args))
|
||||
{
|
||||
struct phy_provider *phy_provider;
|
||||
|
||||
phy_provider = kzalloc(sizeof(*phy_provider), GFP_KERNEL);
|
||||
if (!phy_provider)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
phy_provider->dev = dev;
|
||||
phy_provider->owner = owner;
|
||||
phy_provider->of_xlate = of_xlate;
|
||||
|
||||
mutex_lock(&phy_provider_mutex);
|
||||
list_add_tail(&phy_provider->list, &phy_provider_list);
|
||||
mutex_unlock(&phy_provider_mutex);
|
||||
|
||||
return phy_provider;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__of_phy_provider_register);
|
||||
|
||||
/**
|
||||
* __devm_of_phy_provider_register() - create/register phy provider with the
|
||||
* framework
|
||||
* @dev: struct device of the phy provider
|
||||
* @owner: the module owner containing of_xlate
|
||||
* @of_xlate: function pointer to obtain phy instance from phy provider
|
||||
*
|
||||
* Creates struct phy_provider from dev and of_xlate function pointer.
|
||||
* This is used in the case of dt boot for finding the phy instance from
|
||||
* phy provider. While at that, it also associates the device with the
|
||||
* phy provider using devres. On driver detach, release function is invoked
|
||||
* on the devres data, then, devres data is freed.
|
||||
*/
|
||||
struct phy_provider *__devm_of_phy_provider_register(struct device *dev,
|
||||
struct module *owner, struct phy * (*of_xlate)(struct device *dev,
|
||||
struct of_phandle_args *args))
|
||||
{
|
||||
struct phy_provider **ptr, *phy_provider;
|
||||
|
||||
ptr = devres_alloc(devm_phy_provider_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
phy_provider = __of_phy_provider_register(dev, owner, of_xlate);
|
||||
if (!IS_ERR(phy_provider)) {
|
||||
*ptr = phy_provider;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return phy_provider;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__devm_of_phy_provider_register);
|
||||
|
||||
/**
|
||||
* of_phy_provider_unregister() - unregister phy provider from the framework
|
||||
* @phy_provider: phy provider returned by of_phy_provider_register()
|
||||
*
|
||||
* Removes the phy_provider created using of_phy_provider_register().
|
||||
*/
|
||||
void of_phy_provider_unregister(struct phy_provider *phy_provider)
|
||||
{
|
||||
if (IS_ERR(phy_provider))
|
||||
return;
|
||||
|
||||
mutex_lock(&phy_provider_mutex);
|
||||
list_del(&phy_provider->list);
|
||||
kfree(phy_provider);
|
||||
mutex_unlock(&phy_provider_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_phy_provider_unregister);
|
||||
|
||||
/**
|
||||
* devm_of_phy_provider_unregister() - remove phy provider from the framework
|
||||
* @dev: struct device of the phy provider
|
||||
*
|
||||
* destroys the devres associated with this phy provider and invokes
|
||||
* of_phy_provider_unregister to unregister the phy provider.
|
||||
*/
|
||||
void devm_of_phy_provider_unregister(struct device *dev,
|
||||
struct phy_provider *phy_provider) {
|
||||
int r;
|
||||
|
||||
r = devres_destroy(dev, devm_phy_provider_release, devm_phy_match,
|
||||
phy_provider);
|
||||
dev_WARN_ONCE(dev, r, "couldn't find PHY provider device resource\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_of_phy_provider_unregister);
|
||||
|
||||
/**
|
||||
* phy_release() - release the phy
|
||||
* @dev: the dev member within phy
|
||||
*
|
||||
* When the last reference to the device is removed, it is called
|
||||
* from the embedded kobject as release method.
|
||||
*/
|
||||
static void phy_release(struct device *dev)
|
||||
{
|
||||
struct phy *phy;
|
||||
|
||||
phy = to_phy(dev);
|
||||
dev_vdbg(dev, "releasing '%s'\n", dev_name(dev));
|
||||
regulator_put(phy->pwr);
|
||||
ida_simple_remove(&phy_ida, phy->id);
|
||||
kfree(phy);
|
||||
}
|
||||
|
||||
static int __init phy_core_init(void)
|
||||
{
|
||||
phy_class = class_create(THIS_MODULE, "phy");
|
||||
if (IS_ERR(phy_class)) {
|
||||
pr_err("failed to create phy class --> %ld\n",
|
||||
PTR_ERR(phy_class));
|
||||
return PTR_ERR(phy_class);
|
||||
}
|
||||
|
||||
phy_class->dev_release = phy_release;
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(phy_core_init);
|
||||
|
||||
static void __exit phy_core_exit(void)
|
||||
{
|
||||
class_destroy(phy_class);
|
||||
}
|
||||
module_exit(phy_core_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Generic PHY Framework");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
138
drivers/phy/phy-exynos-dp-video.c
Normal file
138
drivers/phy/phy-exynos-dp-video.c
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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/mfd/syscon/exynos5-pmu.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.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 void exynos_dp_video_phy_pwr_isol(struct exynos_dp_video_phy *state,
|
||||
unsigned int on)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (IS_ERR(state->regs))
|
||||
return;
|
||||
|
||||
val = on ? 0 : EXYNOS5_PHY_ENABLE;
|
||||
|
||||
regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset,
|
||||
EXYNOS5_PHY_ENABLE, val);
|
||||
}
|
||||
|
||||
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 */
|
||||
exynos_dp_video_phy_pwr_isol(state, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 */
|
||||
exynos_dp_video_phy_pwr_isol(state, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops exynos_dp_video_phy_ops = {
|
||||
.power_on = exynos_dp_video_phy_power_on,
|
||||
.power_off = exynos_dp_video_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static 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, NULL);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create Display Port PHY\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
phy_set_drvdata(phy, state);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
|
||||
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");
|
174
drivers/phy/phy-exynos-mipi-video.c
Normal file
174
drivers/phy/phy-exynos-mipi-video.c
Normal file
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
||||
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/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/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
/* MIPI_PHYn_CONTROL register offset: n = 0..1 */
|
||||
#define EXYNOS_MIPI_PHY_CONTROL(n) ((n) * 4)
|
||||
#define EXYNOS_MIPI_PHY_ENABLE (1 << 0)
|
||||
#define EXYNOS_MIPI_PHY_SRESETN (1 << 1)
|
||||
#define EXYNOS_MIPI_PHY_MRESETN (1 << 2)
|
||||
#define EXYNOS_MIPI_PHY_RESET_MASK (3 << 1)
|
||||
|
||||
enum exynos_mipi_phy_id {
|
||||
EXYNOS_MIPI_PHY_ID_CSIS0,
|
||||
EXYNOS_MIPI_PHY_ID_DSIM0,
|
||||
EXYNOS_MIPI_PHY_ID_CSIS1,
|
||||
EXYNOS_MIPI_PHY_ID_DSIM1,
|
||||
EXYNOS_MIPI_PHYS_NUM
|
||||
};
|
||||
|
||||
#define is_mipi_dsim_phy_id(id) \
|
||||
((id) == EXYNOS_MIPI_PHY_ID_DSIM0 || (id) == EXYNOS_MIPI_PHY_ID_DSIM1)
|
||||
|
||||
struct exynos_mipi_video_phy {
|
||||
spinlock_t slock;
|
||||
struct video_phy_desc {
|
||||
struct phy *phy;
|
||||
unsigned int index;
|
||||
} phys[EXYNOS_MIPI_PHYS_NUM];
|
||||
void __iomem *regs;
|
||||
};
|
||||
|
||||
static int __set_phy_state(struct exynos_mipi_video_phy *state,
|
||||
enum exynos_mipi_phy_id id, unsigned int on)
|
||||
{
|
||||
void __iomem *addr;
|
||||
u32 reg, reset;
|
||||
|
||||
addr = state->regs + EXYNOS_MIPI_PHY_CONTROL(id / 2);
|
||||
|
||||
if (is_mipi_dsim_phy_id(id))
|
||||
reset = EXYNOS_MIPI_PHY_MRESETN;
|
||||
else
|
||||
reset = EXYNOS_MIPI_PHY_SRESETN;
|
||||
|
||||
spin_lock(&state->slock);
|
||||
reg = readl(addr);
|
||||
if (on)
|
||||
reg |= reset;
|
||||
else
|
||||
reg &= ~reset;
|
||||
writel(reg, addr);
|
||||
|
||||
/* Clear ENABLE bit only if MRESETN, SRESETN bits are not set. */
|
||||
if (on)
|
||||
reg |= EXYNOS_MIPI_PHY_ENABLE;
|
||||
else if (!(reg & EXYNOS_MIPI_PHY_RESET_MASK))
|
||||
reg &= ~EXYNOS_MIPI_PHY_ENABLE;
|
||||
|
||||
writel(reg, addr);
|
||||
spin_unlock(&state->slock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define to_mipi_video_phy(desc) \
|
||||
container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index]);
|
||||
|
||||
static int exynos_mipi_video_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct video_phy_desc *phy_desc = phy_get_drvdata(phy);
|
||||
struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc);
|
||||
|
||||
return __set_phy_state(state, phy_desc->index, 1);
|
||||
}
|
||||
|
||||
static int exynos_mipi_video_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct video_phy_desc *phy_desc = phy_get_drvdata(phy);
|
||||
struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc);
|
||||
|
||||
return __set_phy_state(state, phy_desc->index, 0);
|
||||
}
|
||||
|
||||
static struct phy *exynos_mipi_video_phy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct exynos_mipi_video_phy *state = dev_get_drvdata(dev);
|
||||
|
||||
if (WARN_ON(args->args[0] >= EXYNOS_MIPI_PHYS_NUM))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return state->phys[args->args[0]].phy;
|
||||
}
|
||||
|
||||
static struct phy_ops exynos_mipi_video_phy_ops = {
|
||||
.power_on = exynos_mipi_video_phy_power_on,
|
||||
.power_off = exynos_mipi_video_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int exynos_mipi_video_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_mipi_video_phy *state;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct phy_provider *phy_provider;
|
||||
unsigned int i;
|
||||
|
||||
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
state->regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(state->regs))
|
||||
return PTR_ERR(state->regs);
|
||||
|
||||
dev_set_drvdata(dev, state);
|
||||
spin_lock_init(&state->slock);
|
||||
|
||||
for (i = 0; i < EXYNOS_MIPI_PHYS_NUM; i++) {
|
||||
struct phy *phy = devm_phy_create(dev, NULL,
|
||||
&exynos_mipi_video_phy_ops, NULL);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create PHY %d\n", i);
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
state->phys[i].phy = phy;
|
||||
state->phys[i].index = i;
|
||||
phy_set_drvdata(phy, &state->phys[i]);
|
||||
}
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev,
|
||||
exynos_mipi_video_phy_xlate);
|
||||
|
||||
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" },
|
||||
{ },
|
||||
};
|
||||
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");
|
411
drivers/phy/phy-exynos-mipi.c
Normal file
411
drivers/phy/phy-exynos-mipi.c
Normal file
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* Samsung EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
|
||||
*
|
||||
* Copyright (C) 2015 Samsung Electronics Co., Ltd.
|
||||
* Author: Sewoon Park <seuni.park@samsung.com>
|
||||
* Author: Wooki Min <wooki.min@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/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define EXYNOS_MIPI_PHY_ISO_BYPASS BIT(0)
|
||||
#define EXYNOS_MIPI_PHYS_NUM 4
|
||||
|
||||
#define MIPI_PHY_MxSx_UNIQUE (0 << 1)
|
||||
#define MIPI_PHY_MxSx_SHARED (1 << 1)
|
||||
#define MIPI_PHY_MxSx_INIT_DONE (2 << 1)
|
||||
|
||||
/* reference count for phy-m4s4 */
|
||||
static int phy_m4s4_count;
|
||||
|
||||
enum exynos_mipi_phy_type {
|
||||
EXYNOS_MIPI_PHY_FOR_DSIM,
|
||||
EXYNOS_MIPI_PHY_FOR_CSIS,
|
||||
};
|
||||
|
||||
struct mipi_phy_data {
|
||||
enum exynos_mipi_phy_type type;
|
||||
u8 flags;
|
||||
};
|
||||
|
||||
struct exynos_mipi_phy {
|
||||
struct device *dev;
|
||||
spinlock_t slock;
|
||||
void __iomem *regs;
|
||||
struct regmap *reg_pmu;
|
||||
struct mipi_phy_desc {
|
||||
struct phy *phy;
|
||||
unsigned int index;
|
||||
enum exynos_mipi_phy_type type;
|
||||
unsigned int iso_offset;
|
||||
unsigned int rst_bit;
|
||||
u8 flags;
|
||||
} phys[EXYNOS_MIPI_PHYS_NUM];
|
||||
};
|
||||
|
||||
/* 1: Isolation bypass, 0: Isolation enable */
|
||||
static int __set_phy_isolation(struct regmap *reg_pmu,
|
||||
unsigned int offset, unsigned int on)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
val = on ? EXYNOS_MIPI_PHY_ISO_BYPASS : 0;
|
||||
|
||||
ret = regmap_update_bits(reg_pmu, offset,
|
||||
EXYNOS_MIPI_PHY_ISO_BYPASS, val);
|
||||
|
||||
pr_debug("%s off=0x%x, val=0x%x\n", __func__, offset, val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 1: Enable reset -> release reset, 0: Enable reset */
|
||||
static int __set_phy_reset(struct exynos_mipi_phy *state,
|
||||
unsigned int bit, unsigned int on)
|
||||
{
|
||||
void __iomem *addr = state->regs;
|
||||
unsigned int cfg;
|
||||
|
||||
if (IS_ERR_OR_NULL(addr)) {
|
||||
dev_err(state->dev, "%s Invalid address\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cfg = readl(addr);
|
||||
cfg &= ~(1 << bit);
|
||||
writel(cfg, addr);
|
||||
|
||||
/* release a reset before using a PHY */
|
||||
if (on) {
|
||||
cfg |= (1 << bit);
|
||||
writel(cfg, addr);
|
||||
}
|
||||
|
||||
pr_debug("%s bit=%d, val=0x%x\n", __func__, bit, cfg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __set_phy_init(struct exynos_mipi_phy *state,
|
||||
struct mipi_phy_desc *phy_desc, unsigned int on)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned int cfg;
|
||||
|
||||
ret = regmap_read(state->reg_pmu,
|
||||
phy_desc->iso_offset, &cfg);
|
||||
if (ret) {
|
||||
dev_err(state->dev, "%s Can't read 0x%x\n",
|
||||
__func__, phy_desc->iso_offset);
|
||||
ret = -EINVAL;
|
||||
goto phy_exit;
|
||||
}
|
||||
|
||||
/* Add INIT_DONE flag when ISO is already bypass(LCD_ON_UBOOT) */
|
||||
if (cfg && EXYNOS_MIPI_PHY_ISO_BYPASS)
|
||||
phy_desc->flags |= MIPI_PHY_MxSx_INIT_DONE;
|
||||
|
||||
phy_exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __set_phy_alone(struct exynos_mipi_phy *state,
|
||||
struct mipi_phy_desc *phy_desc, unsigned int on)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&state->slock, flags);
|
||||
if (on) {
|
||||
ret = __set_phy_isolation(state->reg_pmu,
|
||||
phy_desc->iso_offset, on);
|
||||
|
||||
__set_phy_reset(state, phy_desc->rst_bit, on);
|
||||
|
||||
} else {
|
||||
__set_phy_reset(state, phy_desc->rst_bit, on);
|
||||
|
||||
ret = __set_phy_isolation(state->reg_pmu,
|
||||
phy_desc->iso_offset, on);
|
||||
|
||||
}
|
||||
pr_debug("%s: isolation 0x%x, reset 0x%x\n", __func__,
|
||||
phy_desc->iso_offset, phy_desc->rst_bit);
|
||||
spin_unlock_irqrestore(&state->slock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DEFINE_SPINLOCK(lock_share);
|
||||
static int __set_phy_share(struct exynos_mipi_phy *state,
|
||||
struct mipi_phy_desc *phy_desc, unsigned int on)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&lock_share, flags);
|
||||
|
||||
on ? ++phy_m4s4_count : --phy_m4s4_count;
|
||||
|
||||
/* If phy is already initialization(power_on) */
|
||||
if (phy_desc->flags & MIPI_PHY_MxSx_INIT_DONE) {
|
||||
phy_desc->flags &= (~MIPI_PHY_MxSx_INIT_DONE);
|
||||
spin_unlock_irqrestore(&lock_share, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (on) {
|
||||
/* Isolation bypass when reference count is 1 */
|
||||
if (phy_m4s4_count == 1)
|
||||
ret = __set_phy_isolation(state->reg_pmu,
|
||||
phy_desc->iso_offset, on);
|
||||
|
||||
__set_phy_reset(state, phy_desc->rst_bit, on);
|
||||
|
||||
} else {
|
||||
__set_phy_reset(state, phy_desc->rst_bit, on);
|
||||
|
||||
/* Isolation enabled when reference count is zero */
|
||||
if (phy_m4s4_count == 0)
|
||||
ret = __set_phy_isolation(state->reg_pmu,
|
||||
phy_desc->iso_offset, on);
|
||||
}
|
||||
|
||||
pr_debug("%s: isolation 0x%x, reset 0x%x\n", __func__,
|
||||
phy_desc->iso_offset, phy_desc->rst_bit);
|
||||
spin_unlock_irqrestore(&lock_share, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __set_phy_state(struct exynos_mipi_phy *state,
|
||||
struct mipi_phy_desc *phy_desc, unsigned int on)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (phy_desc->flags & MIPI_PHY_MxSx_SHARED)
|
||||
ret = __set_phy_share(state, phy_desc, on);
|
||||
else
|
||||
ret = __set_phy_alone(state, phy_desc, on);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct mipi_phy_data mipi_phy_m4sx = {
|
||||
.type = EXYNOS_MIPI_PHY_FOR_DSIM,
|
||||
.flags = MIPI_PHY_MxSx_SHARED,
|
||||
};
|
||||
|
||||
static const struct mipi_phy_data mipi_phy_mxs4 = {
|
||||
.type = EXYNOS_MIPI_PHY_FOR_CSIS,
|
||||
.flags = MIPI_PHY_MxSx_SHARED,
|
||||
};
|
||||
|
||||
static const struct mipi_phy_data mipi_phy_mxs0 = {
|
||||
.type = EXYNOS_MIPI_PHY_FOR_DSIM,
|
||||
.flags = MIPI_PHY_MxSx_UNIQUE,
|
||||
};
|
||||
|
||||
static const struct mipi_phy_data mipi_phy_m0sx = {
|
||||
.type = EXYNOS_MIPI_PHY_FOR_CSIS,
|
||||
.flags = MIPI_PHY_MxSx_UNIQUE,
|
||||
};
|
||||
|
||||
static const struct of_device_id exynos_mipi_phy_of_table[] = {
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-dsim",
|
||||
.data = &mipi_phy_m4sx,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-m4",
|
||||
.data = &mipi_phy_mxs0,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-m2",
|
||||
.data = &mipi_phy_mxs0,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-m1",
|
||||
.data = &mipi_phy_mxs0,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-csis",
|
||||
.data = &mipi_phy_mxs4,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-s4",
|
||||
.data = &mipi_phy_m0sx,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-s2",
|
||||
.data = &mipi_phy_m0sx,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,mipi-phy-s1",
|
||||
.data = &mipi_phy_m0sx,
|
||||
},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, exynos_mipi_phy_of_table);
|
||||
|
||||
#define to_mipi_video_phy(desc) \
|
||||
container_of((desc), struct exynos_mipi_phy, phys[(desc)->index])
|
||||
|
||||
static int exynos_mipi_phy_init(struct phy *phy)
|
||||
{
|
||||
struct mipi_phy_desc *phy_desc = phy_get_drvdata(phy);
|
||||
struct exynos_mipi_phy *state = to_mipi_video_phy(phy_desc);
|
||||
|
||||
return __set_phy_init(state, phy_desc, 1);
|
||||
}
|
||||
|
||||
|
||||
static int exynos_mipi_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct mipi_phy_desc *phy_desc = phy_get_drvdata(phy);
|
||||
struct exynos_mipi_phy *state = to_mipi_video_phy(phy_desc);
|
||||
|
||||
return __set_phy_state(state, phy_desc, 1);
|
||||
}
|
||||
|
||||
static int exynos_mipi_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct mipi_phy_desc *phy_desc = phy_get_drvdata(phy);
|
||||
struct exynos_mipi_phy *state = to_mipi_video_phy(phy_desc);
|
||||
|
||||
return __set_phy_state(state, phy_desc, 0);
|
||||
}
|
||||
|
||||
static struct phy *exynos_mipi_phy_of_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct exynos_mipi_phy *state = dev_get_drvdata(dev);
|
||||
|
||||
if (WARN_ON(args->args[0] >= EXYNOS_MIPI_PHYS_NUM))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return state->phys[args->args[0]].phy;
|
||||
}
|
||||
|
||||
static struct phy_ops exynos_mipi_phy_ops = {
|
||||
.init = exynos_mipi_phy_init,
|
||||
.power_on = exynos_mipi_phy_power_on,
|
||||
.power_off = exynos_mipi_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int exynos_mipi_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *node = dev->of_node;
|
||||
struct resource *res;
|
||||
struct exynos_mipi_phy *state;
|
||||
struct phy_provider *phy_provider;
|
||||
struct mipi_phy_data *phy_data;
|
||||
const struct of_device_id *of_id;
|
||||
unsigned int iso[EXYNOS_MIPI_PHYS_NUM];
|
||||
unsigned int rst[EXYNOS_MIPI_PHYS_NUM];
|
||||
unsigned int i, elements;
|
||||
int ret = 0;
|
||||
|
||||
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
state->dev = &pdev->dev;
|
||||
state->regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(state->regs))
|
||||
return PTR_ERR(state->regs);
|
||||
|
||||
of_id = of_match_device(of_match_ptr(exynos_mipi_phy_of_table), dev);
|
||||
if (!of_id)
|
||||
return -EINVAL;
|
||||
|
||||
phy_data = (struct mipi_phy_data *)of_id->data;
|
||||
phy_m4s4_count = 0;
|
||||
|
||||
dev_set_drvdata(dev, state);
|
||||
spin_lock_init(&state->slock);
|
||||
|
||||
state->reg_pmu = syscon_regmap_lookup_by_phandle(node,
|
||||
"samsung,pmu-syscon");
|
||||
if (IS_ERR(state->reg_pmu)) {
|
||||
dev_err(dev, "Failed to lookup PMU regmap\n");
|
||||
return PTR_ERR(state->reg_pmu);
|
||||
}
|
||||
|
||||
elements = of_property_count_u32_elems(node, "isolation");
|
||||
ret = of_property_read_u32_array(node, "isolation", iso,
|
||||
elements);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot get mipi-phy isolation!!!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32_array(node, "reset", rst,
|
||||
elements);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot get mipi-phy reset!!!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < elements; i++) {
|
||||
state->phys[i].iso_offset = iso[i];
|
||||
state->phys[i].rst_bit = rst[i];
|
||||
dev_dbg(dev, "%s: iso 0x%x, reset %d\n", __func__,
|
||||
state->phys[i].iso_offset, state->phys[i].rst_bit);
|
||||
}
|
||||
|
||||
for (i = 0; i < elements; i++) {
|
||||
struct phy *generic_phy = devm_phy_create(dev, NULL,
|
||||
&exynos_mipi_phy_ops, NULL);
|
||||
if (IS_ERR(generic_phy)) {
|
||||
dev_err(dev, "failed to create PHY\n");
|
||||
return PTR_ERR(generic_phy);
|
||||
}
|
||||
|
||||
state->phys[i].index = i;
|
||||
state->phys[i].phy = generic_phy;
|
||||
state->phys[i].type = phy_data->type;
|
||||
if (i == 0)
|
||||
state->phys[i].flags = phy_data->flags;
|
||||
else /* 0 index only can support MIPI_PHY_MxSx_SHARED */
|
||||
state->phys[i].flags = MIPI_PHY_MxSx_UNIQUE;
|
||||
phy_set_drvdata(generic_phy, &state->phys[i]);
|
||||
}
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev,
|
||||
exynos_mipi_phy_of_xlate);
|
||||
|
||||
if (IS_ERR(phy_provider))
|
||||
dev_err(dev, "failed to create exynos mipi-phy\n");
|
||||
else
|
||||
dev_err(dev, "Creating exynos-mipi-phy\n");
|
||||
|
||||
return PTR_ERR_OR_ZERO(phy_provider);
|
||||
}
|
||||
|
||||
static struct platform_driver exynos_mipi_phy_driver = {
|
||||
.probe = exynos_mipi_phy_probe,
|
||||
.driver = {
|
||||
.name = "exynos-mipi-phy",
|
||||
.of_match_table = of_match_ptr(exynos_mipi_phy_of_table),
|
||||
}
|
||||
};
|
||||
module_platform_driver(exynos_mipi_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Samsung EXYNOS SoC MIPI CSI/DSI PHY driver");
|
||||
MODULE_LICENSE("GPL v2");
|
1236
drivers/phy/phy-exynos-usbdrd.c
Normal file
1236
drivers/phy/phy-exynos-usbdrd.c
Normal file
File diff suppressed because it is too large
Load diff
178
drivers/phy/phy-exynos-usbdrd.h
Normal file
178
drivers/phy/phy-exynos-usbdrd.h
Normal file
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Samsung Electronics Co., Ltd.
|
||||
*
|
||||
* 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_USBDRD_H__
|
||||
#define __PHY_EXYNOS_USBDRD_H__
|
||||
|
||||
#include "phy-samsung-usb-cal.h"
|
||||
#include "phy-samsung-usb3-cal.h"
|
||||
|
||||
/* PMU register offset for USB */
|
||||
#define EXYNOS_USBDEV_PHY_CONTROL (0x704)
|
||||
#define EXYNOS_USB3PHY_ENABLE BIT(0)
|
||||
#define EXYNOS_USB2PHY_ENABLE BIT(1)
|
||||
|
||||
/* Exynos USB PHY registers */
|
||||
#define EXYNOS_FSEL_9MHZ6 0x0
|
||||
#define EXYNOS_FSEL_10MHZ 0x1
|
||||
#define EXYNOS_FSEL_12MHZ 0x2
|
||||
#define EXYNOS_FSEL_19MHZ2 0x3
|
||||
#define EXYNOS_FSEL_20MHZ 0x4
|
||||
#define EXYNOS_FSEL_24MHZ 0x5
|
||||
#define EXYNOS_FSEL_26MHZ 0x82
|
||||
#define EXYNOS_FSEL_50MHZ 0x7
|
||||
|
||||
/* EXYNOS: USB DRD PHY registers */
|
||||
#define EXYNOS_DRD_LINKSYSTEM 0x04
|
||||
|
||||
#define LINKSYSTEM_FLADJ_MASK (0x3f << 1)
|
||||
#define LINKSYSTEM_FLADJ(_x) ((_x) << 1)
|
||||
|
||||
#define EXYNOS_DRD_PHYUTMI 0x08
|
||||
|
||||
#define EXYNOS_DRD_PHYPIPE 0x0c
|
||||
|
||||
#define PHY_CLOCK_SEL (0x1 << 4)
|
||||
|
||||
#define EXYNOS_DRD_PHYCLKRST 0x10
|
||||
|
||||
#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_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_REFCLKSEL_MASK (0x03 << 2)
|
||||
#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2)
|
||||
#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2)
|
||||
|
||||
#define EXYNOS_DRD_PHYREG0 0x14
|
||||
#define EXYNOS_DRD_PHYREG1 0x18
|
||||
|
||||
#define EXYNOS_DRD_PHYPARAM0 0x1c
|
||||
|
||||
#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26)
|
||||
#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26)
|
||||
|
||||
#define EXYNOS_DRD_PHYPARAM1 0x20
|
||||
|
||||
#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0)
|
||||
#define PHYPARAM1_PCS_TXDEEMPH (0x1c)
|
||||
|
||||
#define EXYNOS_DRD_PHYTERM 0x24
|
||||
|
||||
#define EXYNOS_DRD_PHYTEST 0x28
|
||||
|
||||
#define EXYNOS_DRD_PHYADP 0x2c
|
||||
|
||||
#define EXYNOS_DRD_PHYUTMICLKSEL 0x30
|
||||
|
||||
#define PHYUTMICLKSEL_UTMI_CLKSEL BIT(2)
|
||||
|
||||
#define EXYNOS_DRD_PHYRESUME 0x34
|
||||
#define EXYNOS_DRD_LINKPORT 0x44
|
||||
|
||||
#define KHZ 1000
|
||||
#define MHZ (KHZ * KHZ)
|
||||
|
||||
enum exynos_usbdrd_phy_id {
|
||||
EXYNOS_DRDPHY_UTMI,
|
||||
EXYNOS_DRDPHY_PIPE3,
|
||||
EXYNOS_DRDPHYS_NUM,
|
||||
};
|
||||
|
||||
enum exynos_usbdrd_ext_refclk_state {
|
||||
EXYNOS_EXTCLK_SUCCESS = 0,
|
||||
EXYNOS_EXTCLK_STARTED,
|
||||
EXYNOS_EXTCLK_STOPPED,
|
||||
EXYNOS_NOT_STARTED,
|
||||
EXYNOS_NOT_STOPPED,
|
||||
};
|
||||
|
||||
struct phy_usb_instance;
|
||||
struct exynos_usbdrd_phy;
|
||||
|
||||
struct exynos_usbdrd_phy_config {
|
||||
u32 id;
|
||||
void (*phy_isol)(struct phy_usb_instance *inst, u32 on, unsigned int);
|
||||
void (*phy_init)(struct exynos_usbdrd_phy *phy_drd);
|
||||
void (*phy_exit)(struct exynos_usbdrd_phy *phy_drd);
|
||||
void (*phy_tune)(struct exynos_usbdrd_phy *phy_drd, int);
|
||||
void (*phy_set)(struct exynos_usbdrd_phy *phy_drd, int, void *);
|
||||
unsigned int (*set_refclk)(struct phy_usb_instance *inst);
|
||||
};
|
||||
|
||||
struct exynos_usbdrd_phy_drvdata {
|
||||
const struct exynos_usbdrd_phy_config *phy_cfg;
|
||||
bool phy_usermux;
|
||||
u32 pmu_offset_usbdrd0_phy;
|
||||
u32 pmu_offset_usbdrd1_phy;
|
||||
u32 cpu_type;
|
||||
u32 ip_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos_usbdrd_phy - driver data for USB DRD PHY
|
||||
* @dev: pointer to device instance of this platform device
|
||||
* @reg_phy: usb phy controller register memory base
|
||||
* @clk: phy clock for register access
|
||||
* @drv_data: pointer to SoC level driver data structure
|
||||
* @phys[]: array for 'EXYNOS_DRDPHYS_NUM' number of PHY
|
||||
* instances each with its 'phy' and 'phy_cfg'.
|
||||
* @extrefclk: frequency select settings when using 'separate
|
||||
* reference clocks' for SS and HS operations
|
||||
* @ref_clk: reference clock to PHY block from which PHY's
|
||||
* operational clocks are derived
|
||||
* @ref_rate: rate of above reference clock
|
||||
*/
|
||||
struct exynos_usbdrd_phy {
|
||||
struct device *dev;
|
||||
void __iomem *reg_phy;
|
||||
struct clk **clocks;
|
||||
struct clk **phy_clocks;
|
||||
const struct exynos_usbdrd_phy_drvdata *drv_data;
|
||||
struct phy_usb_instance {
|
||||
struct phy *phy;
|
||||
u32 index;
|
||||
struct regmap *reg_pmu;
|
||||
u32 pmu_offset;
|
||||
u32 uart_io_share_en;
|
||||
u32 uart_io_share_offset;
|
||||
u32 uart_io_share_mask;
|
||||
const struct exynos_usbdrd_phy_config *phy_cfg;
|
||||
} phys[EXYNOS_DRDPHYS_NUM];
|
||||
u32 extrefclk;
|
||||
u32 use_additional_tuning;
|
||||
u32 request_extrefclk;
|
||||
bool extrefclk_requested;
|
||||
struct completion can_use_extrefclk;
|
||||
int (*request_extrefclk_cb)(void);
|
||||
int (*release_extrefclk_cb)(void);
|
||||
struct clk *ref_clk;
|
||||
struct regulator *vbus;
|
||||
struct exynos_usbphy_info usbphy_info;
|
||||
struct exynos_usbphy_ss_tune ss_value[2];
|
||||
struct exynos_usbphy_hs_tune hs_value[2];
|
||||
};
|
||||
|
||||
#endif /* __PHY_EXYNOS_USBDRD_H__ */
|
261
drivers/phy/phy-exynos4210-usb2.c
Normal file
261
drivers/phy/phy-exynos4210-usb2.c
Normal file
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4210 support
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
||||
* Author: Kamil Debski <k.debski@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/regmap.h>
|
||||
#include "phy-samsung-usb2.h"
|
||||
|
||||
/* Exynos USB PHY registers */
|
||||
|
||||
/* PHY power control */
|
||||
#define EXYNOS_4210_UPHYPWR 0x0
|
||||
|
||||
#define EXYNOS_4210_UPHYPWR_PHY0_SUSPEND BIT(0)
|
||||
#define EXYNOS_4210_UPHYPWR_PHY0_PWR BIT(3)
|
||||
#define EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR BIT(4)
|
||||
#define EXYNOS_4210_UPHYPWR_PHY0_SLEEP BIT(5)
|
||||
#define EXYNOS_4210_UPHYPWR_PHY0 ( \
|
||||
EXYNOS_4210_UPHYPWR_PHY0_SUSPEND | \
|
||||
EXYNOS_4210_UPHYPWR_PHY0_PWR | \
|
||||
EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR | \
|
||||
EXYNOS_4210_UPHYPWR_PHY0_SLEEP)
|
||||
|
||||
#define EXYNOS_4210_UPHYPWR_PHY1_SUSPEND BIT(6)
|
||||
#define EXYNOS_4210_UPHYPWR_PHY1_PWR BIT(7)
|
||||
#define EXYNOS_4210_UPHYPWR_PHY1_SLEEP BIT(8)
|
||||
#define EXYNOS_4210_UPHYPWR_PHY1 ( \
|
||||
EXYNOS_4210_UPHYPWR_PHY1_SUSPEND | \
|
||||
EXYNOS_4210_UPHYPWR_PHY1_PWR | \
|
||||
EXYNOS_4210_UPHYPWR_PHY1_SLEEP)
|
||||
|
||||
#define EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND BIT(9)
|
||||
#define EXYNOS_4210_UPHYPWR_HSIC0_SLEEP BIT(10)
|
||||
#define EXYNOS_4210_UPHYPWR_HSIC0 ( \
|
||||
EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND | \
|
||||
EXYNOS_4210_UPHYPWR_HSIC0_SLEEP)
|
||||
|
||||
#define EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND BIT(11)
|
||||
#define EXYNOS_4210_UPHYPWR_HSIC1_SLEEP BIT(12)
|
||||
#define EXYNOS_4210_UPHYPWR_HSIC1 ( \
|
||||
EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND | \
|
||||
EXYNOS_4210_UPHYPWR_HSIC1_SLEEP)
|
||||
|
||||
/* PHY clock control */
|
||||
#define EXYNOS_4210_UPHYCLK 0x4
|
||||
|
||||
#define EXYNOS_4210_UPHYCLK_PHYFSEL_MASK (0x3 << 0)
|
||||
#define EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET 0
|
||||
#define EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0)
|
||||
#define EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0)
|
||||
#define EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0)
|
||||
|
||||
#define EXYNOS_4210_UPHYCLK_PHY0_ID_PULLUP BIT(2)
|
||||
#define EXYNOS_4210_UPHYCLK_PHY0_COMMON_ON BIT(4)
|
||||
#define EXYNOS_4210_UPHYCLK_PHY1_COMMON_ON BIT(7)
|
||||
|
||||
/* PHY reset control */
|
||||
#define EXYNOS_4210_UPHYRST 0x8
|
||||
|
||||
#define EXYNOS_4210_URSTCON_PHY0 BIT(0)
|
||||
#define EXYNOS_4210_URSTCON_OTG_HLINK BIT(1)
|
||||
#define EXYNOS_4210_URSTCON_OTG_PHYLINK BIT(2)
|
||||
#define EXYNOS_4210_URSTCON_PHY1_ALL BIT(3)
|
||||
#define EXYNOS_4210_URSTCON_PHY1_P0 BIT(4)
|
||||
#define EXYNOS_4210_URSTCON_PHY1_P1P2 BIT(5)
|
||||
#define EXYNOS_4210_URSTCON_HOST_LINK_ALL BIT(6)
|
||||
#define EXYNOS_4210_URSTCON_HOST_LINK_P0 BIT(7)
|
||||
#define EXYNOS_4210_URSTCON_HOST_LINK_P1 BIT(8)
|
||||
#define EXYNOS_4210_URSTCON_HOST_LINK_P2 BIT(9)
|
||||
|
||||
/* Isolation, configured in the power management unit */
|
||||
#define EXYNOS_4210_USB_ISOL_DEVICE_OFFSET 0x704
|
||||
#define EXYNOS_4210_USB_ISOL_DEVICE BIT(0)
|
||||
#define EXYNOS_4210_USB_ISOL_HOST_OFFSET 0x708
|
||||
#define EXYNOS_4210_USB_ISOL_HOST BIT(0)
|
||||
|
||||
/* USBYPHY1 Floating prevention */
|
||||
#define EXYNOS_4210_UPHY1CON 0x34
|
||||
#define EXYNOS_4210_UPHY1CON_FLOAT_PREVENTION 0x1
|
||||
|
||||
/* Mode switching SUB Device <-> Host */
|
||||
#define EXYNOS_4210_MODE_SWITCH_OFFSET 0x21c
|
||||
#define EXYNOS_4210_MODE_SWITCH_MASK 1
|
||||
#define EXYNOS_4210_MODE_SWITCH_DEVICE 0
|
||||
#define EXYNOS_4210_MODE_SWITCH_HOST 1
|
||||
|
||||
enum exynos4210_phy_id {
|
||||
EXYNOS4210_DEVICE,
|
||||
EXYNOS4210_HOST,
|
||||
EXYNOS4210_HSIC0,
|
||||
EXYNOS4210_HSIC1,
|
||||
EXYNOS4210_NUM_PHYS,
|
||||
};
|
||||
|
||||
/*
|
||||
* exynos4210_rate_to_clk() converts the supplied clock rate to the value that
|
||||
* can be written to the phy register.
|
||||
*/
|
||||
static int exynos4210_rate_to_clk(unsigned long rate, u32 *reg)
|
||||
{
|
||||
switch (rate) {
|
||||
case 12 * MHZ:
|
||||
*reg = EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ;
|
||||
break;
|
||||
case 24 * MHZ:
|
||||
*reg = EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ;
|
||||
break;
|
||||
case 48 * MHZ:
|
||||
*reg = EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exynos4210_isol(struct samsung_usb2_phy_instance *inst, bool on)
|
||||
{
|
||||
struct samsung_usb2_phy_driver *drv = inst->drv;
|
||||
u32 offset;
|
||||
u32 mask;
|
||||
|
||||
switch (inst->cfg->id) {
|
||||
case EXYNOS4210_DEVICE:
|
||||
offset = EXYNOS_4210_USB_ISOL_DEVICE_OFFSET;
|
||||
mask = EXYNOS_4210_USB_ISOL_DEVICE;
|
||||
break;
|
||||
case EXYNOS4210_HOST:
|
||||
offset = EXYNOS_4210_USB_ISOL_HOST_OFFSET;
|
||||
mask = EXYNOS_4210_USB_ISOL_HOST;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
};
|
||||
|
||||
regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask);
|
||||
}
|
||||
|
||||
static void exynos4210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on)
|
||||
{
|
||||
struct samsung_usb2_phy_driver *drv = inst->drv;
|
||||
u32 rstbits = 0;
|
||||
u32 phypwr = 0;
|
||||
u32 rst;
|
||||
u32 pwr;
|
||||
u32 clk;
|
||||
|
||||
switch (inst->cfg->id) {
|
||||
case EXYNOS4210_DEVICE:
|
||||
phypwr = EXYNOS_4210_UPHYPWR_PHY0;
|
||||
rstbits = EXYNOS_4210_URSTCON_PHY0;
|
||||
break;
|
||||
case EXYNOS4210_HOST:
|
||||
phypwr = EXYNOS_4210_UPHYPWR_PHY1;
|
||||
rstbits = EXYNOS_4210_URSTCON_PHY1_ALL |
|
||||
EXYNOS_4210_URSTCON_PHY1_P0 |
|
||||
EXYNOS_4210_URSTCON_PHY1_P1P2 |
|
||||
EXYNOS_4210_URSTCON_HOST_LINK_ALL |
|
||||
EXYNOS_4210_URSTCON_HOST_LINK_P0;
|
||||
writel(on, drv->reg_phy + EXYNOS_4210_UPHY1CON);
|
||||
break;
|
||||
case EXYNOS4210_HSIC0:
|
||||
phypwr = EXYNOS_4210_UPHYPWR_HSIC0;
|
||||
rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 |
|
||||
EXYNOS_4210_URSTCON_HOST_LINK_P1;
|
||||
break;
|
||||
case EXYNOS4210_HSIC1:
|
||||
phypwr = EXYNOS_4210_UPHYPWR_HSIC1;
|
||||
rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 |
|
||||
EXYNOS_4210_URSTCON_HOST_LINK_P2;
|
||||
break;
|
||||
};
|
||||
|
||||
if (on) {
|
||||
clk = readl(drv->reg_phy + EXYNOS_4210_UPHYCLK);
|
||||
clk &= ~EXYNOS_4210_UPHYCLK_PHYFSEL_MASK;
|
||||
clk |= drv->ref_reg_val << EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET;
|
||||
writel(clk, drv->reg_phy + EXYNOS_4210_UPHYCLK);
|
||||
|
||||
pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR);
|
||||
pwr &= ~phypwr;
|
||||
writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR);
|
||||
|
||||
rst = readl(drv->reg_phy + EXYNOS_4210_UPHYRST);
|
||||
rst |= rstbits;
|
||||
writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST);
|
||||
udelay(10);
|
||||
rst &= ~rstbits;
|
||||
writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST);
|
||||
/* The following delay is necessary for the reset sequence to be
|
||||
* completed */
|
||||
udelay(80);
|
||||
} else {
|
||||
pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR);
|
||||
pwr |= phypwr;
|
||||
writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR);
|
||||
}
|
||||
}
|
||||
|
||||
static int exynos4210_power_on(struct samsung_usb2_phy_instance *inst)
|
||||
{
|
||||
/* Order of initialisation is important - first power then isolation */
|
||||
exynos4210_phy_pwr(inst, 1);
|
||||
exynos4210_isol(inst, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos4210_power_off(struct samsung_usb2_phy_instance *inst)
|
||||
{
|
||||
exynos4210_isol(inst, 1);
|
||||
exynos4210_phy_pwr(inst, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct samsung_usb2_common_phy exynos4210_phys[] = {
|
||||
{
|
||||
.label = "device",
|
||||
.id = EXYNOS4210_DEVICE,
|
||||
.power_on = exynos4210_power_on,
|
||||
.power_off = exynos4210_power_off,
|
||||
},
|
||||
{
|
||||
.label = "host",
|
||||
.id = EXYNOS4210_HOST,
|
||||
.power_on = exynos4210_power_on,
|
||||
.power_off = exynos4210_power_off,
|
||||
},
|
||||
{
|
||||
.label = "hsic0",
|
||||
.id = EXYNOS4210_HSIC0,
|
||||
.power_on = exynos4210_power_on,
|
||||
.power_off = exynos4210_power_off,
|
||||
},
|
||||
{
|
||||
.label = "hsic1",
|
||||
.id = EXYNOS4210_HSIC1,
|
||||
.power_on = exynos4210_power_on,
|
||||
.power_off = exynos4210_power_off,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = {
|
||||
.has_mode_switch = 0,
|
||||
.num_phys = EXYNOS4210_NUM_PHYS,
|
||||
.phys = exynos4210_phys,
|
||||
.rate_to_clk = exynos4210_rate_to_clk,
|
||||
};
|
379
drivers/phy/phy-exynos4x12-usb2.c
Normal file
379
drivers/phy/phy-exynos4x12-usb2.c
Normal file
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
677
drivers/phy/phy-exynos5-usbdrd.c
Normal file
677
drivers/phy/phy-exynos5-usbdrd.c
Normal file
|
@ -0,0 +1,677 @@
|
|||
/*
|
||||
* Samsung EXYNOS5 SoC series USB DRD PHY driver
|
||||
*
|
||||
* Phy provider for USB 3.0 DRD controller on Exynos5 SoC series
|
||||
*
|
||||
* Copyright (C) 2014 Samsung Electronics Co., Ltd.
|
||||
* Author: Vivek Gautam <gautam.vivek@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/mfd/syscon/exynos5-pmu.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
/* Exynos USB PHY registers */
|
||||
#define EXYNOS5_FSEL_9MHZ6 0x0
|
||||
#define EXYNOS5_FSEL_10MHZ 0x1
|
||||
#define EXYNOS5_FSEL_12MHZ 0x2
|
||||
#define EXYNOS5_FSEL_19MHZ2 0x3
|
||||
#define EXYNOS5_FSEL_20MHZ 0x4
|
||||
#define EXYNOS5_FSEL_24MHZ 0x5
|
||||
#define EXYNOS5_FSEL_50MHZ 0x7
|
||||
|
||||
/* EXYNOS5: USB 3.0 DRD PHY registers */
|
||||
#define EXYNOS5_DRD_LINKSYSTEM 0x04
|
||||
|
||||
#define LINKSYSTEM_FLADJ_MASK (0x3f << 1)
|
||||
#define LINKSYSTEM_FLADJ(_x) ((_x) << 1)
|
||||
#define LINKSYSTEM_XHCI_VERSION_CONTROL BIT(27)
|
||||
|
||||
#define EXYNOS5_DRD_PHYUTMI 0x08
|
||||
|
||||
#define PHYUTMI_OTGDISABLE BIT(6)
|
||||
#define PHYUTMI_FORCESUSPEND BIT(1)
|
||||
#define PHYUTMI_FORCESLEEP BIT(0)
|
||||
|
||||
#define EXYNOS5_DRD_PHYPIPE 0x0c
|
||||
|
||||
#define EXYNOS5_DRD_PHYCLKRST 0x10
|
||||
|
||||
#define PHYCLKRST_EN_UTMISUSPEND BIT(31)
|
||||
|
||||
#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23)
|
||||
#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23)
|
||||
|
||||
#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21)
|
||||
#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21)
|
||||
|
||||
#define PHYCLKRST_SSC_EN BIT(20)
|
||||
#define PHYCLKRST_REF_SSP_EN BIT(19)
|
||||
#define PHYCLKRST_REF_CLKDIV2 BIT(18)
|
||||
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x32 << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11)
|
||||
|
||||
#define PHYCLKRST_FSEL_UTMI_MASK (0x7 << 5)
|
||||
#define PHYCLKRST_FSEL_PIPE_MASK (0x7 << 8)
|
||||
#define PHYCLKRST_FSEL(_x) ((_x) << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5)
|
||||
|
||||
#define PHYCLKRST_RETENABLEN BIT(4)
|
||||
|
||||
#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2)
|
||||
#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2)
|
||||
#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2)
|
||||
|
||||
#define PHYCLKRST_PORTRESET BIT(1)
|
||||
#define PHYCLKRST_COMMONONN BIT(0)
|
||||
|
||||
#define EXYNOS5_DRD_PHYREG0 0x14
|
||||
#define EXYNOS5_DRD_PHYREG1 0x18
|
||||
|
||||
#define EXYNOS5_DRD_PHYPARAM0 0x1c
|
||||
|
||||
#define PHYPARAM0_REF_USE_PAD BIT(31)
|
||||
#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26)
|
||||
#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26)
|
||||
|
||||
#define EXYNOS5_DRD_PHYPARAM1 0x20
|
||||
|
||||
#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0)
|
||||
#define PHYPARAM1_PCS_TXDEEMPH (0x1c)
|
||||
|
||||
#define EXYNOS5_DRD_PHYTERM 0x24
|
||||
|
||||
#define EXYNOS5_DRD_PHYTEST 0x28
|
||||
|
||||
#define PHYTEST_POWERDOWN_SSP BIT(3)
|
||||
#define PHYTEST_POWERDOWN_HSP BIT(2)
|
||||
|
||||
#define EXYNOS5_DRD_PHYADP 0x2c
|
||||
|
||||
#define EXYNOS5_DRD_PHYUTMICLKSEL 0x30
|
||||
|
||||
#define PHYUTMICLKSEL_UTMI_CLKSEL BIT(2)
|
||||
|
||||
#define EXYNOS5_DRD_PHYRESUME 0x34
|
||||
#define EXYNOS5_DRD_LINKPORT 0x44
|
||||
|
||||
#define KHZ 1000
|
||||
#define MHZ (KHZ * KHZ)
|
||||
|
||||
enum exynos5_usbdrd_phy_id {
|
||||
EXYNOS5_DRDPHY_UTMI,
|
||||
EXYNOS5_DRDPHY_PIPE3,
|
||||
EXYNOS5_DRDPHYS_NUM,
|
||||
};
|
||||
|
||||
struct phy_usb_instance;
|
||||
struct exynos5_usbdrd_phy;
|
||||
|
||||
struct exynos5_usbdrd_phy_config {
|
||||
u32 id;
|
||||
void (*phy_isol)(struct phy_usb_instance *inst, u32 on);
|
||||
void (*phy_init)(struct exynos5_usbdrd_phy *phy_drd);
|
||||
unsigned int (*set_refclk)(struct phy_usb_instance *inst);
|
||||
};
|
||||
|
||||
struct exynos5_usbdrd_phy_drvdata {
|
||||
const struct exynos5_usbdrd_phy_config *phy_cfg;
|
||||
u32 pmu_offset_usbdrd0_phy;
|
||||
u32 pmu_offset_usbdrd1_phy;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos5_usbdrd_phy - driver data for USB 3.0 PHY
|
||||
* @dev: pointer to device instance of this platform device
|
||||
* @reg_phy: usb phy controller register memory base
|
||||
* @clk: phy clock for register access
|
||||
* @drv_data: pointer to SoC level driver data structure
|
||||
* @phys[]: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY
|
||||
* instances each with its 'phy' and 'phy_cfg'.
|
||||
* @extrefclk: frequency select settings when using 'separate
|
||||
* reference clocks' for SS and HS operations
|
||||
* @ref_clk: reference clock to PHY block from which PHY's
|
||||
* operational clocks are derived
|
||||
* @ref_rate: rate of above reference clock
|
||||
*/
|
||||
struct exynos5_usbdrd_phy {
|
||||
struct device *dev;
|
||||
void __iomem *reg_phy;
|
||||
struct clk *clk;
|
||||
const struct exynos5_usbdrd_phy_drvdata *drv_data;
|
||||
struct phy_usb_instance {
|
||||
struct phy *phy;
|
||||
u32 index;
|
||||
struct regmap *reg_pmu;
|
||||
u32 pmu_offset;
|
||||
const struct exynos5_usbdrd_phy_config *phy_cfg;
|
||||
} phys[EXYNOS5_DRDPHYS_NUM];
|
||||
u32 extrefclk;
|
||||
struct clk *ref_clk;
|
||||
struct regulator *vbus;
|
||||
};
|
||||
|
||||
static inline
|
||||
struct exynos5_usbdrd_phy *to_usbdrd_phy(struct phy_usb_instance *inst)
|
||||
{
|
||||
return container_of((inst), struct exynos5_usbdrd_phy,
|
||||
phys[(inst)->index]);
|
||||
}
|
||||
|
||||
/*
|
||||
* exynos5_rate_to_clk() converts the supplied clock rate to the value that
|
||||
* can be written to the phy register.
|
||||
*/
|
||||
static unsigned int exynos5_rate_to_clk(unsigned long rate, u32 *reg)
|
||||
{
|
||||
/* EXYNOS5_FSEL_MASK */
|
||||
|
||||
switch (rate) {
|
||||
case 9600 * KHZ:
|
||||
*reg = EXYNOS5_FSEL_9MHZ6;
|
||||
break;
|
||||
case 10 * MHZ:
|
||||
*reg = EXYNOS5_FSEL_10MHZ;
|
||||
break;
|
||||
case 12 * MHZ:
|
||||
*reg = EXYNOS5_FSEL_12MHZ;
|
||||
break;
|
||||
case 19200 * KHZ:
|
||||
*reg = EXYNOS5_FSEL_19MHZ2;
|
||||
break;
|
||||
case 20 * MHZ:
|
||||
*reg = EXYNOS5_FSEL_20MHZ;
|
||||
break;
|
||||
case 24 * MHZ:
|
||||
*reg = EXYNOS5_FSEL_24MHZ;
|
||||
break;
|
||||
case 50 * MHZ:
|
||||
*reg = EXYNOS5_FSEL_50MHZ;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst,
|
||||
unsigned int on)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (!inst->reg_pmu)
|
||||
return;
|
||||
|
||||
val = on ? 0 : EXYNOS5_PHY_ENABLE;
|
||||
|
||||
regmap_update_bits(inst->reg_pmu, inst->pmu_offset,
|
||||
EXYNOS5_PHY_ENABLE, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the pipe3 phy's clk as EXTREFCLK (XXTI) which is internal clock
|
||||
* from clock core. Further sets multiplier values and spread spectrum
|
||||
* clock settings for SuperSpeed operations.
|
||||
*/
|
||||
static unsigned int
|
||||
exynos5_usbdrd_pipe3_set_refclk(struct phy_usb_instance *inst)
|
||||
{
|
||||
static u32 reg;
|
||||
struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
|
||||
|
||||
/* restore any previous reference clock settings */
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
/* Use EXTREFCLK as ref clock */
|
||||
reg &= ~PHYCLKRST_REFCLKSEL_MASK;
|
||||
reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK;
|
||||
|
||||
/* FSEL settings corresponding to reference clock */
|
||||
reg &= ~PHYCLKRST_FSEL_PIPE_MASK |
|
||||
PHYCLKRST_MPLL_MULTIPLIER_MASK |
|
||||
PHYCLKRST_SSC_REFCLKSEL_MASK;
|
||||
switch (phy_drd->extrefclk) {
|
||||
case EXYNOS5_FSEL_50MHZ:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x00));
|
||||
break;
|
||||
case EXYNOS5_FSEL_24MHZ:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x88));
|
||||
break;
|
||||
case EXYNOS5_FSEL_20MHZ:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x00));
|
||||
break;
|
||||
case EXYNOS5_FSEL_19MHZ2:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x88));
|
||||
break;
|
||||
default:
|
||||
dev_dbg(phy_drd->dev, "unsupported ref clk\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the utmi phy's clk as EXTREFCLK (XXTI) which is internal clock
|
||||
* from clock core. Further sets the FSEL values for HighSpeed operations.
|
||||
*/
|
||||
static unsigned int
|
||||
exynos5_usbdrd_utmi_set_refclk(struct phy_usb_instance *inst)
|
||||
{
|
||||
static u32 reg;
|
||||
struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
|
||||
|
||||
/* restore any previous reference clock settings */
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
reg &= ~PHYCLKRST_REFCLKSEL_MASK;
|
||||
reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK;
|
||||
|
||||
reg &= ~PHYCLKRST_FSEL_UTMI_MASK |
|
||||
PHYCLKRST_MPLL_MULTIPLIER_MASK |
|
||||
PHYCLKRST_SSC_REFCLKSEL_MASK;
|
||||
reg |= PHYCLKRST_FSEL(phy_drd->extrefclk);
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
static void exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
|
||||
/* Set Tx De-Emphasis level */
|
||||
reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK;
|
||||
reg |= PHYPARAM1_PCS_TXDEEMPH;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
|
||||
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
|
||||
reg &= ~PHYTEST_POWERDOWN_SSP;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
|
||||
}
|
||||
|
||||
static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
|
||||
/* Set Loss-of-Signal Detector sensitivity */
|
||||
reg &= ~PHYPARAM0_REF_LOSLEVEL_MASK;
|
||||
reg |= PHYPARAM0_REF_LOSLEVEL;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
|
||||
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
|
||||
/* Set Tx De-Emphasis level */
|
||||
reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK;
|
||||
reg |= PHYPARAM1_PCS_TXDEEMPH;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
|
||||
|
||||
/* UTMI Power Control */
|
||||
writel(PHYUTMI_OTGDISABLE, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI);
|
||||
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
|
||||
reg &= ~PHYTEST_POWERDOWN_HSP;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
|
||||
}
|
||||
|
||||
static int exynos5_usbdrd_phy_init(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
u32 reg;
|
||||
struct phy_usb_instance *inst = phy_get_drvdata(phy);
|
||||
struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
|
||||
|
||||
ret = clk_prepare_enable(phy_drd->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Reset USB 3.0 PHY */
|
||||
writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
|
||||
writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYRESUME);
|
||||
|
||||
/*
|
||||
* Setting the Frame length Adj value[6:1] to default 0x20
|
||||
* See xHCI 1.0 spec, 5.2.4
|
||||
*/
|
||||
reg = LINKSYSTEM_XHCI_VERSION_CONTROL |
|
||||
LINKSYSTEM_FLADJ(0x20);
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_LINKSYSTEM);
|
||||
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
|
||||
/* Select PHY CLK source */
|
||||
reg &= ~PHYPARAM0_REF_USE_PAD;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
|
||||
|
||||
/* This bit must be set for both HS and SS operations */
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL);
|
||||
reg |= PHYUTMICLKSEL_UTMI_CLKSEL;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL);
|
||||
|
||||
/* UTMI or PIPE3 specific init */
|
||||
inst->phy_cfg->phy_init(phy_drd);
|
||||
|
||||
/* reference clock settings */
|
||||
reg = inst->phy_cfg->set_refclk(inst);
|
||||
|
||||
/* Digital power supply in normal operating mode */
|
||||
reg |= PHYCLKRST_RETENABLEN |
|
||||
/* Enable ref clock for SS function */
|
||||
PHYCLKRST_REF_SSP_EN |
|
||||
/* Enable spread spectrum */
|
||||
PHYCLKRST_SSC_EN |
|
||||
/* Power down HS Bias and PLL blocks in suspend mode */
|
||||
PHYCLKRST_COMMONONN |
|
||||
/* Reset the port */
|
||||
PHYCLKRST_PORTRESET;
|
||||
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
udelay(10);
|
||||
|
||||
reg &= ~PHYCLKRST_PORTRESET;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
clk_disable_unprepare(phy_drd->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos5_usbdrd_phy_exit(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
u32 reg;
|
||||
struct phy_usb_instance *inst = phy_get_drvdata(phy);
|
||||
struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
|
||||
|
||||
ret = clk_prepare_enable(phy_drd->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg = PHYUTMI_OTGDISABLE |
|
||||
PHYUTMI_FORCESUSPEND |
|
||||
PHYUTMI_FORCESLEEP;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI);
|
||||
|
||||
/* Resetting the PHYCLKRST enable bits to reduce leakage current */
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
|
||||
reg &= ~(PHYCLKRST_REF_SSP_EN |
|
||||
PHYCLKRST_SSC_EN |
|
||||
PHYCLKRST_COMMONONN);
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
/* Control PHYTEST to remove leakage current */
|
||||
reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
|
||||
reg |= PHYTEST_POWERDOWN_SSP |
|
||||
PHYTEST_POWERDOWN_HSP;
|
||||
writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
|
||||
|
||||
clk_disable_unprepare(phy_drd->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos5_usbdrd_phy_power_on(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
struct phy_usb_instance *inst = phy_get_drvdata(phy);
|
||||
struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
|
||||
|
||||
dev_dbg(phy_drd->dev, "Request to power_on usbdrd_phy phy\n");
|
||||
|
||||
clk_prepare_enable(phy_drd->ref_clk);
|
||||
|
||||
/* Enable VBUS supply */
|
||||
if (phy_drd->vbus) {
|
||||
ret = regulator_enable(phy_drd->vbus);
|
||||
if (ret) {
|
||||
dev_err(phy_drd->dev, "Failed to enable VBUS supply\n");
|
||||
goto fail_vbus;
|
||||
}
|
||||
}
|
||||
|
||||
/* Power-on PHY*/
|
||||
inst->phy_cfg->phy_isol(inst, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_vbus:
|
||||
clk_disable_unprepare(phy_drd->ref_clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos5_usbdrd_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct phy_usb_instance *inst = phy_get_drvdata(phy);
|
||||
struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
|
||||
|
||||
dev_dbg(phy_drd->dev, "Request to power_off usbdrd_phy phy\n");
|
||||
|
||||
/* Power-off the PHY */
|
||||
inst->phy_cfg->phy_isol(inst, 1);
|
||||
|
||||
/* Disable VBUS supply */
|
||||
if (phy_drd->vbus)
|
||||
regulator_disable(phy_drd->vbus);
|
||||
|
||||
clk_disable_unprepare(phy_drd->ref_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy *exynos5_usbdrd_phy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct exynos5_usbdrd_phy *phy_drd = dev_get_drvdata(dev);
|
||||
|
||||
if (WARN_ON(args->args[0] > EXYNOS5_DRDPHYS_NUM))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return phy_drd->phys[args->args[0]].phy;
|
||||
}
|
||||
|
||||
static struct phy_ops exynos5_usbdrd_phy_ops = {
|
||||
.init = exynos5_usbdrd_phy_init,
|
||||
.exit = exynos5_usbdrd_phy_exit,
|
||||
.power_on = exynos5_usbdrd_phy_power_on,
|
||||
.power_off = exynos5_usbdrd_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static const struct exynos5_usbdrd_phy_drvdata exynos5250_usbdrd_phy = {
|
||||
.phy_cfg = phy_cfg_exynos5,
|
||||
.pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL,
|
||||
};
|
||||
|
||||
static const struct of_device_id exynos5_usbdrd_phy_of_match[] = {
|
||||
{
|
||||
.compatible = "samsung,exynos5250-usbdrd-phy",
|
||||
.data = &exynos5250_usbdrd_phy
|
||||
}, {
|
||||
.compatible = "samsung,exynos5420-usbdrd-phy",
|
||||
.data = &exynos5420_usbdrd_phy
|
||||
},
|
||||
{ },
|
||||
};
|
||||
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;
|
||||
unsigned long ref_rate;
|
||||
int i, ret;
|
||||
int channel;
|
||||
|
||||
phy_drd = devm_kzalloc(dev, sizeof(*phy_drd), GFP_KERNEL);
|
||||
if (!phy_drd)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, phy_drd);
|
||||
phy_drd->dev = dev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
phy_drd->reg_phy = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(phy_drd->reg_phy))
|
||||
return PTR_ERR(phy_drd->reg_phy);
|
||||
|
||||
match = of_match_node(exynos5_usbdrd_phy_of_match, pdev->dev.of_node);
|
||||
|
||||
drv_data = match->data;
|
||||
phy_drd->drv_data = drv_data;
|
||||
|
||||
phy_drd->clk = devm_clk_get(dev, "phy");
|
||||
if (IS_ERR(phy_drd->clk)) {
|
||||
dev_err(dev, "Failed to get clock of phy controller\n");
|
||||
return PTR_ERR(phy_drd->clk);
|
||||
}
|
||||
|
||||
phy_drd->ref_clk = devm_clk_get(dev, "ref");
|
||||
if (IS_ERR(phy_drd->ref_clk)) {
|
||||
dev_err(dev, "Failed to get reference clock of usbdrd phy\n");
|
||||
return PTR_ERR(phy_drd->ref_clk);
|
||||
}
|
||||
ref_rate = clk_get_rate(phy_drd->ref_clk);
|
||||
|
||||
ret = exynos5_rate_to_clk(ref_rate, &phy_drd->extrefclk);
|
||||
if (ret) {
|
||||
dev_err(phy_drd->dev, "Clock rate (%ld) not supported\n",
|
||||
ref_rate);
|
||||
return ret;
|
||||
}
|
||||
|
||||
reg_pmu = syscon_regmap_lookup_by_phandle(dev->of_node,
|
||||
"samsung,pmu-syscon");
|
||||
if (IS_ERR(reg_pmu)) {
|
||||
dev_err(dev, "Failed to lookup PMU regmap\n");
|
||||
return PTR_ERR(reg_pmu);
|
||||
}
|
||||
|
||||
/*
|
||||
* Exynos5420 SoC has multiple channels for USB 3.0 PHY, with
|
||||
* each having separate power control registers.
|
||||
* 'channel' facilitates to set such registers.
|
||||
*/
|
||||
channel = of_alias_get_id(node, "usbdrdphy");
|
||||
if (channel < 0)
|
||||
dev_dbg(dev, "Not a multi-controller usbdrd phy\n");
|
||||
|
||||
switch (channel) {
|
||||
case 1:
|
||||
pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd1_phy;
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd0_phy;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get Vbus regulator */
|
||||
phy_drd->vbus = devm_regulator_get(dev, "vbus");
|
||||
if (IS_ERR(phy_drd->vbus)) {
|
||||
ret = PTR_ERR(phy_drd->vbus);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
return ret;
|
||||
|
||||
dev_warn(dev, "Failed to get VBUS supply regulator\n");
|
||||
phy_drd->vbus = NULL;
|
||||
}
|
||||
|
||||
dev_vdbg(dev, "Creating usbdrd_phy phy\n");
|
||||
|
||||
for (i = 0; i < EXYNOS5_DRDPHYS_NUM; i++) {
|
||||
struct phy *phy = devm_phy_create(dev, NULL,
|
||||
&exynos5_usbdrd_phy_ops,
|
||||
NULL);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "Failed to create usbdrd_phy phy\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_drd->phys[i].phy = phy;
|
||||
phy_drd->phys[i].index = i;
|
||||
phy_drd->phys[i].reg_pmu = reg_pmu;
|
||||
phy_drd->phys[i].pmu_offset = pmu_offset;
|
||||
phy_drd->phys[i].phy_cfg = &drv_data->phy_cfg[i];
|
||||
phy_set_drvdata(phy, &phy_drd->phys[i]);
|
||||
}
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev,
|
||||
exynos5_usbdrd_phy_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
dev_err(phy_drd->dev, "Failed to register phy provider\n");
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver exynos5_usb3drd_phy = {
|
||||
.probe = exynos5_usbdrd_phy_probe,
|
||||
.driver = {
|
||||
.of_match_table = exynos5_usbdrd_phy_of_match,
|
||||
.name = "exynos5_usb3drd_phy",
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
250
drivers/phy/phy-exynos5250-sata.c
Normal file
250
drivers/phy/phy-exynos5250-sata.c
Normal file
|
@ -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 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, NULL);
|
||||
if (IS_ERR(sata_phy->phy)) {
|
||||
clk_disable_unprepare(sata_phy->phyclk);
|
||||
dev_err(dev, "failed to create PHY\n");
|
||||
return PTR_ERR(sata_phy->phy);
|
||||
}
|
||||
|
||||
phy_set_drvdata(sata_phy->phy, sata_phy);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev,
|
||||
of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
clk_disable_unprepare(sata_phy->phyclk);
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id exynos_sata_phy_of_match[] = {
|
||||
{ .compatible = "samsung,exynos5250-sata-phy" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, exynos_sata_phy_of_match);
|
||||
|
||||
static struct platform_driver exynos_sata_phy_driver = {
|
||||
.probe = exynos_sata_phy_probe,
|
||||
.driver = {
|
||||
.of_match_table = exynos_sata_phy_of_match,
|
||||
.name = "samsung,sata-phy",
|
||||
}
|
||||
};
|
||||
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>");
|
402
drivers/phy/phy-exynos5250-usb2.c
Normal file
402
drivers/phy/phy-exynos5250-usb2.c
Normal file
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
191
drivers/phy/phy-hix5hd2-sata.c
Normal file
191
drivers/phy/phy-hix5hd2-sata.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Linaro Ltd.
|
||||
* Copyright (c) 2014 Hisilicon Limited.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define SATA_PHY0_CTLL 0xa0
|
||||
#define MPLL_MULTIPLIER_SHIFT 1
|
||||
#define MPLL_MULTIPLIER_MASK 0xfe
|
||||
#define MPLL_MULTIPLIER_50M 0x3c
|
||||
#define MPLL_MULTIPLIER_100M 0x1e
|
||||
#define PHY_RESET BIT(0)
|
||||
#define REF_SSP_EN BIT(9)
|
||||
#define SSC_EN BIT(10)
|
||||
#define REF_USE_PAD BIT(23)
|
||||
|
||||
#define SATA_PORT_PHYCTL 0x174
|
||||
#define SPEED_MODE_MASK 0x6f0000
|
||||
#define HALF_RATE_SHIFT 16
|
||||
#define PHY_CONFIG_SHIFT 18
|
||||
#define GEN2_EN_SHIFT 21
|
||||
#define SPEED_CTRL BIT(20)
|
||||
|
||||
#define SATA_PORT_PHYCTL1 0x148
|
||||
#define AMPLITUDE_MASK 0x3ffffe
|
||||
#define AMPLITUDE_GEN3 0x68
|
||||
#define AMPLITUDE_GEN3_SHIFT 15
|
||||
#define AMPLITUDE_GEN2 0x56
|
||||
#define AMPLITUDE_GEN2_SHIFT 8
|
||||
#define AMPLITUDE_GEN1 0x56
|
||||
#define AMPLITUDE_GEN1_SHIFT 1
|
||||
|
||||
#define SATA_PORT_PHYCTL2 0x14c
|
||||
#define PREEMPH_MASK 0x3ffff
|
||||
#define PREEMPH_GEN3 0x20
|
||||
#define PREEMPH_GEN3_SHIFT 12
|
||||
#define PREEMPH_GEN2 0x15
|
||||
#define PREEMPH_GEN2_SHIFT 6
|
||||
#define PREEMPH_GEN1 0x5
|
||||
#define PREEMPH_GEN1_SHIFT 0
|
||||
|
||||
struct hix5hd2_priv {
|
||||
void __iomem *base;
|
||||
struct regmap *peri_ctrl;
|
||||
};
|
||||
|
||||
enum phy_speed_mode {
|
||||
SPEED_MODE_GEN1 = 0,
|
||||
SPEED_MODE_GEN2 = 1,
|
||||
SPEED_MODE_GEN3 = 2,
|
||||
};
|
||||
|
||||
static int hix5hd2_sata_phy_init(struct phy *phy)
|
||||
{
|
||||
struct hix5hd2_priv *priv = phy_get_drvdata(phy);
|
||||
u32 val, data[2];
|
||||
int ret;
|
||||
|
||||
if (priv->peri_ctrl) {
|
||||
ret = of_property_read_u32_array(phy->dev.of_node,
|
||||
"hisilicon,power-reg",
|
||||
&data[0], 2);
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "Fail read hisilicon,power-reg\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap_update_bits(priv->peri_ctrl, data[0],
|
||||
BIT(data[1]), BIT(data[1]));
|
||||
}
|
||||
|
||||
/* reset phy */
|
||||
val = readl_relaxed(priv->base + SATA_PHY0_CTLL);
|
||||
val &= ~(MPLL_MULTIPLIER_MASK | REF_USE_PAD);
|
||||
val |= MPLL_MULTIPLIER_50M << MPLL_MULTIPLIER_SHIFT |
|
||||
REF_SSP_EN | PHY_RESET;
|
||||
writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
|
||||
msleep(20);
|
||||
val &= ~PHY_RESET;
|
||||
writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
|
||||
|
||||
val = readl_relaxed(priv->base + SATA_PORT_PHYCTL1);
|
||||
val &= ~AMPLITUDE_MASK;
|
||||
val |= AMPLITUDE_GEN3 << AMPLITUDE_GEN3_SHIFT |
|
||||
AMPLITUDE_GEN2 << AMPLITUDE_GEN2_SHIFT |
|
||||
AMPLITUDE_GEN1 << AMPLITUDE_GEN1_SHIFT;
|
||||
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL1);
|
||||
|
||||
val = readl_relaxed(priv->base + SATA_PORT_PHYCTL2);
|
||||
val &= ~PREEMPH_MASK;
|
||||
val |= PREEMPH_GEN3 << PREEMPH_GEN3_SHIFT |
|
||||
PREEMPH_GEN2 << PREEMPH_GEN2_SHIFT |
|
||||
PREEMPH_GEN1 << PREEMPH_GEN1_SHIFT;
|
||||
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL2);
|
||||
|
||||
/* ensure PHYCTRL setting takes effect */
|
||||
val = readl_relaxed(priv->base + SATA_PORT_PHYCTL);
|
||||
val &= ~SPEED_MODE_MASK;
|
||||
val |= SPEED_MODE_GEN1 << HALF_RATE_SHIFT |
|
||||
SPEED_MODE_GEN1 << PHY_CONFIG_SHIFT |
|
||||
SPEED_MODE_GEN1 << GEN2_EN_SHIFT | SPEED_CTRL;
|
||||
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
|
||||
|
||||
msleep(20);
|
||||
val &= ~SPEED_MODE_MASK;
|
||||
val |= SPEED_MODE_GEN3 << HALF_RATE_SHIFT |
|
||||
SPEED_MODE_GEN3 << PHY_CONFIG_SHIFT |
|
||||
SPEED_MODE_GEN3 << GEN2_EN_SHIFT | SPEED_CTRL;
|
||||
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
|
||||
|
||||
val &= ~(SPEED_MODE_MASK | SPEED_CTRL);
|
||||
val |= SPEED_MODE_GEN2 << HALF_RATE_SHIFT |
|
||||
SPEED_MODE_GEN2 << PHY_CONFIG_SHIFT |
|
||||
SPEED_MODE_GEN2 << GEN2_EN_SHIFT;
|
||||
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops hix5hd2_sata_phy_ops = {
|
||||
.init = hix5hd2_sata_phy_init,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int hix5hd2_sata_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct phy_provider *phy_provider;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct phy *phy;
|
||||
struct hix5hd2_priv *priv;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
priv->base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!priv->base)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->peri_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
|
||||
"hisilicon,peripheral-syscon");
|
||||
if (IS_ERR(priv->peri_ctrl))
|
||||
priv->peri_ctrl = NULL;
|
||||
|
||||
phy = devm_phy_create(dev, NULL, &hix5hd2_sata_phy_ops, NULL);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create PHY\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_set_drvdata(phy, priv);
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id hix5hd2_sata_phy_of_match[] = {
|
||||
{.compatible = "hisilicon,hix5hd2-sata-phy",},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, hix5hd2_sata_phy_of_match);
|
||||
|
||||
static struct platform_driver hix5hd2_sata_phy_driver = {
|
||||
.probe = hix5hd2_sata_phy_probe,
|
||||
.driver = {
|
||||
.name = "hix5hd2-sata-phy",
|
||||
.of_match_table = hix5hd2_sata_phy_of_match,
|
||||
}
|
||||
};
|
||||
module_platform_driver(hix5hd2_sata_phy_driver);
|
||||
|
||||
MODULE_AUTHOR("Jiancheng Xue <xuejiancheng@huawei.com>");
|
||||
MODULE_DESCRIPTION("HISILICON HIX5HD2 SATA PHY driver");
|
||||
MODULE_ALIAS("platform:hix5hd2-sata-phy");
|
||||
MODULE_LICENSE("GPL v2");
|
636
drivers/phy/phy-miphy365x.c
Normal file
636
drivers/phy/phy-miphy365x.c
Normal file
|
@ -0,0 +1,636 @@
|
|||
/*
|
||||
* Copyright (C) 2014 STMicroelectronics – All Rights Reserved
|
||||
*
|
||||
* STMicroelectronics PHY driver MiPHY365 (for SoC STiH416).
|
||||
*
|
||||
* Authors: Alexandre Torgue <alexandre.torgue@st.com>
|
||||
* Lee Jones <lee.jones@linaro.org>
|
||||
*
|
||||
* 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/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <dt-bindings/phy/phy-miphy365x.h>
|
||||
|
||||
#define HFC_TIMEOUT 100
|
||||
|
||||
#define SYSCFG_SELECT_SATA_MASK BIT(1)
|
||||
#define SYSCFG_SELECT_SATA_POS 1
|
||||
|
||||
/* MiPHY365x register definitions */
|
||||
#define RESET_REG 0x00
|
||||
#define RST_PLL BIT(1)
|
||||
#define RST_PLL_CAL BIT(2)
|
||||
#define RST_RX BIT(4)
|
||||
#define RST_MACRO BIT(7)
|
||||
|
||||
#define STATUS_REG 0x01
|
||||
#define IDLL_RDY BIT(0)
|
||||
#define PLL_RDY BIT(1)
|
||||
#define DES_BIT_LOCK BIT(2)
|
||||
#define DES_SYMBOL_LOCK BIT(3)
|
||||
|
||||
#define CTRL_REG 0x02
|
||||
#define TERM_EN BIT(0)
|
||||
#define PCI_EN BIT(2)
|
||||
#define DES_BIT_LOCK_EN BIT(3)
|
||||
#define TX_POL BIT(5)
|
||||
|
||||
#define INT_CTRL_REG 0x03
|
||||
|
||||
#define BOUNDARY1_REG 0x10
|
||||
#define SPDSEL_SEL BIT(0)
|
||||
|
||||
#define BOUNDARY3_REG 0x12
|
||||
#define TX_SPDSEL_GEN1_VAL 0
|
||||
#define TX_SPDSEL_GEN2_VAL 0x01
|
||||
#define TX_SPDSEL_GEN3_VAL 0x02
|
||||
#define RX_SPDSEL_GEN1_VAL 0
|
||||
#define RX_SPDSEL_GEN2_VAL (0x01 << 3)
|
||||
#define RX_SPDSEL_GEN3_VAL (0x02 << 3)
|
||||
|
||||
#define PCIE_REG 0x16
|
||||
|
||||
#define BUF_SEL_REG 0x20
|
||||
#define CONF_GEN_SEL_GEN3 0x02
|
||||
#define CONF_GEN_SEL_GEN2 0x01
|
||||
#define PD_VDDTFILTER BIT(4)
|
||||
|
||||
#define TXBUF1_REG 0x21
|
||||
#define SWING_VAL 0x04
|
||||
#define SWING_VAL_GEN1 0x03
|
||||
#define PREEMPH_VAL (0x3 << 5)
|
||||
|
||||
#define TXBUF2_REG 0x22
|
||||
#define TXSLEW_VAL 0x2
|
||||
#define TXSLEW_VAL_GEN1 0x4
|
||||
|
||||
#define RXBUF_OFFSET_CTRL_REG 0x23
|
||||
|
||||
#define RXBUF_REG 0x25
|
||||
#define SDTHRES_VAL 0x01
|
||||
#define EQ_ON3 (0x03 << 4)
|
||||
#define EQ_ON1 (0x01 << 4)
|
||||
|
||||
#define COMP_CTRL1_REG 0x40
|
||||
#define START_COMSR BIT(0)
|
||||
#define START_COMZC BIT(1)
|
||||
#define COMSR_DONE BIT(2)
|
||||
#define COMZC_DONE BIT(3)
|
||||
#define COMP_AUTO_LOAD BIT(4)
|
||||
|
||||
#define COMP_CTRL2_REG 0x41
|
||||
#define COMP_2MHZ_RAT_GEN1 0x1e
|
||||
#define COMP_2MHZ_RAT 0xf
|
||||
|
||||
#define COMP_CTRL3_REG 0x42
|
||||
#define COMSR_COMP_REF 0x33
|
||||
|
||||
#define COMP_IDLL_REG 0x47
|
||||
#define COMZC_IDLL 0x2a
|
||||
|
||||
#define PLL_CTRL1_REG 0x50
|
||||
#define PLL_START_CAL BIT(0)
|
||||
#define BUF_EN BIT(2)
|
||||
#define SYNCHRO_TX BIT(3)
|
||||
#define SSC_EN BIT(6)
|
||||
#define CONFIG_PLL BIT(7)
|
||||
|
||||
#define PLL_CTRL2_REG 0x51
|
||||
#define BYPASS_PLL_CAL BIT(1)
|
||||
|
||||
#define PLL_RAT_REG 0x52
|
||||
|
||||
#define PLL_SSC_STEP_MSB_REG 0x56
|
||||
#define PLL_SSC_STEP_MSB_VAL 0x03
|
||||
|
||||
#define PLL_SSC_STEP_LSB_REG 0x57
|
||||
#define PLL_SSC_STEP_LSB_VAL 0x63
|
||||
|
||||
#define PLL_SSC_PER_MSB_REG 0x58
|
||||
#define PLL_SSC_PER_MSB_VAL 0
|
||||
|
||||
#define PLL_SSC_PER_LSB_REG 0x59
|
||||
#define PLL_SSC_PER_LSB_VAL 0xf1
|
||||
|
||||
#define IDLL_TEST_REG 0x72
|
||||
#define START_CLK_HF BIT(6)
|
||||
|
||||
#define DES_BITLOCK_REG 0x86
|
||||
#define BIT_LOCK_LEVEL 0x01
|
||||
#define BIT_LOCK_CNT_512 (0x03 << 5)
|
||||
|
||||
struct miphy365x_phy {
|
||||
struct phy *phy;
|
||||
void __iomem *base;
|
||||
bool pcie_tx_pol_inv;
|
||||
bool sata_tx_pol_inv;
|
||||
u32 sata_gen;
|
||||
u64 ctrlreg;
|
||||
u8 type;
|
||||
};
|
||||
|
||||
struct miphy365x_dev {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct mutex miphy_mutex;
|
||||
struct miphy365x_phy **phys;
|
||||
};
|
||||
|
||||
/*
|
||||
* These values are represented in Device tree. They are considered to be ABI
|
||||
* and although they can be extended any existing values must not change.
|
||||
*/
|
||||
enum miphy_sata_gen {
|
||||
SATA_GEN1 = 1,
|
||||
SATA_GEN2,
|
||||
SATA_GEN3
|
||||
};
|
||||
|
||||
static u8 rx_tx_spd[] = {
|
||||
0, /* GEN0 doesn't exist. */
|
||||
TX_SPDSEL_GEN1_VAL | RX_SPDSEL_GEN1_VAL,
|
||||
TX_SPDSEL_GEN2_VAL | RX_SPDSEL_GEN2_VAL,
|
||||
TX_SPDSEL_GEN3_VAL | RX_SPDSEL_GEN3_VAL
|
||||
};
|
||||
|
||||
/*
|
||||
* This function selects the system configuration,
|
||||
* either two SATA, one SATA and one PCIe, or two PCIe lanes.
|
||||
*/
|
||||
static int miphy365x_set_path(struct miphy365x_phy *miphy_phy,
|
||||
struct miphy365x_dev *miphy_dev)
|
||||
{
|
||||
bool sata = (miphy_phy->type == MIPHY_TYPE_SATA);
|
||||
|
||||
return regmap_update_bits(miphy_dev->regmap,
|
||||
(unsigned int)miphy_phy->ctrlreg,
|
||||
SYSCFG_SELECT_SATA_MASK,
|
||||
sata << SYSCFG_SELECT_SATA_POS);
|
||||
}
|
||||
|
||||
static int miphy365x_init_pcie_port(struct miphy365x_phy *miphy_phy,
|
||||
struct miphy365x_dev *miphy_dev)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
if (miphy_phy->pcie_tx_pol_inv) {
|
||||
/* Invert Tx polarity and clear pci_txdetect_pol bit */
|
||||
val = TERM_EN | PCI_EN | DES_BIT_LOCK_EN | TX_POL;
|
||||
writeb_relaxed(val, miphy_phy->base + CTRL_REG);
|
||||
writeb_relaxed(0x00, miphy_phy->base + PCIE_REG);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int miphy365x_hfc_not_rdy(struct miphy365x_phy *miphy_phy,
|
||||
struct miphy365x_dev *miphy_dev)
|
||||
{
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(HFC_TIMEOUT);
|
||||
u8 mask = IDLL_RDY | PLL_RDY;
|
||||
u8 regval;
|
||||
|
||||
do {
|
||||
regval = readb_relaxed(miphy_phy->base + STATUS_REG);
|
||||
if (!(regval & mask))
|
||||
return 0;
|
||||
|
||||
usleep_range(2000, 2500);
|
||||
} while (time_before(jiffies, timeout));
|
||||
|
||||
dev_err(miphy_dev->dev, "HFC ready timeout!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static inline int miphy365x_rdy(struct miphy365x_phy *miphy_phy,
|
||||
struct miphy365x_dev *miphy_dev)
|
||||
{
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(HFC_TIMEOUT);
|
||||
u8 mask = IDLL_RDY | PLL_RDY;
|
||||
u8 regval;
|
||||
|
||||
do {
|
||||
regval = readb_relaxed(miphy_phy->base + STATUS_REG);
|
||||
if ((regval & mask) == mask)
|
||||
return 0;
|
||||
|
||||
usleep_range(2000, 2500);
|
||||
} while (time_before(jiffies, timeout));
|
||||
|
||||
dev_err(miphy_dev->dev, "PHY not ready timeout!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static inline void miphy365x_set_comp(struct miphy365x_phy *miphy_phy,
|
||||
struct miphy365x_dev *miphy_dev)
|
||||
{
|
||||
u8 val, mask;
|
||||
|
||||
if (miphy_phy->sata_gen == SATA_GEN1)
|
||||
writeb_relaxed(COMP_2MHZ_RAT_GEN1,
|
||||
miphy_phy->base + COMP_CTRL2_REG);
|
||||
else
|
||||
writeb_relaxed(COMP_2MHZ_RAT,
|
||||
miphy_phy->base + COMP_CTRL2_REG);
|
||||
|
||||
if (miphy_phy->sata_gen != SATA_GEN3) {
|
||||
writeb_relaxed(COMSR_COMP_REF,
|
||||
miphy_phy->base + COMP_CTRL3_REG);
|
||||
/*
|
||||
* Force VCO current to value defined by address 0x5A
|
||||
* and disable PCIe100Mref bit
|
||||
* Enable auto load compensation for pll_i_bias
|
||||
*/
|
||||
writeb_relaxed(BYPASS_PLL_CAL, miphy_phy->base + PLL_CTRL2_REG);
|
||||
writeb_relaxed(COMZC_IDLL, miphy_phy->base + COMP_IDLL_REG);
|
||||
}
|
||||
|
||||
/*
|
||||
* Force restart compensation and enable auto load
|
||||
* for Comzc_Tx, Comzc_Rx and Comsr on macro
|
||||
*/
|
||||
val = START_COMSR | START_COMZC | COMP_AUTO_LOAD;
|
||||
writeb_relaxed(val, miphy_phy->base + COMP_CTRL1_REG);
|
||||
|
||||
mask = COMSR_DONE | COMZC_DONE;
|
||||
while ((readb_relaxed(miphy_phy->base + COMP_CTRL1_REG) & mask) != mask)
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static inline void miphy365x_set_ssc(struct miphy365x_phy *miphy_phy,
|
||||
struct miphy365x_dev *miphy_dev)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
/*
|
||||
* SSC Settings. SSC will be enabled through Link
|
||||
* SSC Ampl. = 0.4%
|
||||
* SSC Freq = 31KHz
|
||||
*/
|
||||
writeb_relaxed(PLL_SSC_STEP_MSB_VAL,
|
||||
miphy_phy->base + PLL_SSC_STEP_MSB_REG);
|
||||
writeb_relaxed(PLL_SSC_STEP_LSB_VAL,
|
||||
miphy_phy->base + PLL_SSC_STEP_LSB_REG);
|
||||
writeb_relaxed(PLL_SSC_PER_MSB_VAL,
|
||||
miphy_phy->base + PLL_SSC_PER_MSB_REG);
|
||||
writeb_relaxed(PLL_SSC_PER_LSB_VAL,
|
||||
miphy_phy->base + PLL_SSC_PER_LSB_REG);
|
||||
|
||||
/* SSC Settings complete */
|
||||
if (miphy_phy->sata_gen == SATA_GEN1) {
|
||||
val = PLL_START_CAL | BUF_EN | SYNCHRO_TX | CONFIG_PLL;
|
||||
writeb_relaxed(val, miphy_phy->base + PLL_CTRL1_REG);
|
||||
} else {
|
||||
val = SSC_EN | PLL_START_CAL | BUF_EN | SYNCHRO_TX | CONFIG_PLL;
|
||||
writeb_relaxed(val, miphy_phy->base + PLL_CTRL1_REG);
|
||||
}
|
||||
}
|
||||
|
||||
static int miphy365x_init_sata_port(struct miphy365x_phy *miphy_phy,
|
||||
struct miphy365x_dev *miphy_dev)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
/*
|
||||
* Force PHY macro reset, PLL calibration reset, PLL reset
|
||||
* and assert Deserializer Reset
|
||||
*/
|
||||
val = RST_PLL | RST_PLL_CAL | RST_RX | RST_MACRO;
|
||||
writeb_relaxed(val, miphy_phy->base + RESET_REG);
|
||||
|
||||
if (miphy_phy->sata_tx_pol_inv)
|
||||
writeb_relaxed(TX_POL, miphy_phy->base + CTRL_REG);
|
||||
|
||||
/*
|
||||
* Force macro1 to use rx_lspd, tx_lspd
|
||||
* Force Rx_Clock on first I-DLL phase
|
||||
* Force Des in HP mode on macro, rx_lspd, tx_lspd for Gen2/3
|
||||
*/
|
||||
writeb_relaxed(SPDSEL_SEL, miphy_phy->base + BOUNDARY1_REG);
|
||||
writeb_relaxed(START_CLK_HF, miphy_phy->base + IDLL_TEST_REG);
|
||||
val = rx_tx_spd[miphy_phy->sata_gen];
|
||||
writeb_relaxed(val, miphy_phy->base + BOUNDARY3_REG);
|
||||
|
||||
/* Wait for HFC_READY = 0 */
|
||||
ret = miphy365x_hfc_not_rdy(miphy_phy, miphy_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Compensation Recalibration */
|
||||
miphy365x_set_comp(miphy_phy, miphy_dev);
|
||||
|
||||
switch (miphy_phy->sata_gen) {
|
||||
case SATA_GEN3:
|
||||
/*
|
||||
* TX Swing target 550-600mv peak to peak diff
|
||||
* Tx Slew target 90-110ps rising/falling time
|
||||
* Rx Eq ON3, Sigdet threshold SDTH1
|
||||
*/
|
||||
val = PD_VDDTFILTER | CONF_GEN_SEL_GEN3;
|
||||
writeb_relaxed(val, miphy_phy->base + BUF_SEL_REG);
|
||||
val = SWING_VAL | PREEMPH_VAL;
|
||||
writeb_relaxed(val, miphy_phy->base + TXBUF1_REG);
|
||||
writeb_relaxed(TXSLEW_VAL, miphy_phy->base + TXBUF2_REG);
|
||||
writeb_relaxed(0x00, miphy_phy->base + RXBUF_OFFSET_CTRL_REG);
|
||||
val = SDTHRES_VAL | EQ_ON3;
|
||||
writeb_relaxed(val, miphy_phy->base + RXBUF_REG);
|
||||
break;
|
||||
case SATA_GEN2:
|
||||
/*
|
||||
* conf gen sel=0x1 to program Gen2 banked registers
|
||||
* VDDT filter ON
|
||||
* Tx Swing target 550-600mV peak-to-peak diff
|
||||
* Tx Slew target 90-110 ps rising/falling time
|
||||
* RX Equalization ON1, Sigdet threshold SDTH1
|
||||
*/
|
||||
writeb_relaxed(CONF_GEN_SEL_GEN2,
|
||||
miphy_phy->base + BUF_SEL_REG);
|
||||
writeb_relaxed(SWING_VAL, miphy_phy->base + TXBUF1_REG);
|
||||
writeb_relaxed(TXSLEW_VAL, miphy_phy->base + TXBUF2_REG);
|
||||
val = SDTHRES_VAL | EQ_ON1;
|
||||
writeb_relaxed(val, miphy_phy->base + RXBUF_REG);
|
||||
break;
|
||||
case SATA_GEN1:
|
||||
/*
|
||||
* conf gen sel = 00b to program Gen1 banked registers
|
||||
* VDDT filter ON
|
||||
* Tx Swing target 500-550mV peak-to-peak diff
|
||||
* Tx Slew target120-140 ps rising/falling time
|
||||
*/
|
||||
writeb_relaxed(PD_VDDTFILTER, miphy_phy->base + BUF_SEL_REG);
|
||||
writeb_relaxed(SWING_VAL_GEN1, miphy_phy->base + TXBUF1_REG);
|
||||
writeb_relaxed(TXSLEW_VAL_GEN1, miphy_phy->base + TXBUF2_REG);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Force Macro1 in partial mode & release pll cal reset */
|
||||
writeb_relaxed(RST_RX, miphy_phy->base + RESET_REG);
|
||||
usleep_range(100, 150);
|
||||
|
||||
miphy365x_set_ssc(miphy_phy, miphy_dev);
|
||||
|
||||
/* Wait for phy_ready */
|
||||
ret = miphy365x_rdy(miphy_phy, miphy_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Enable macro1 to use rx_lspd & tx_lspd
|
||||
* Release Rx_Clock on first I-DLL phase on macro1
|
||||
* Assert deserializer reset
|
||||
* des_bit_lock_en is set
|
||||
* bit lock detection strength
|
||||
* Deassert deserializer reset
|
||||
*/
|
||||
writeb_relaxed(0x00, miphy_phy->base + BOUNDARY1_REG);
|
||||
writeb_relaxed(0x00, miphy_phy->base + IDLL_TEST_REG);
|
||||
writeb_relaxed(RST_RX, miphy_phy->base + RESET_REG);
|
||||
val = miphy_phy->sata_tx_pol_inv ?
|
||||
(TX_POL | DES_BIT_LOCK_EN) : DES_BIT_LOCK_EN;
|
||||
writeb_relaxed(val, miphy_phy->base + CTRL_REG);
|
||||
|
||||
val = BIT_LOCK_CNT_512 | BIT_LOCK_LEVEL;
|
||||
writeb_relaxed(val, miphy_phy->base + DES_BITLOCK_REG);
|
||||
writeb_relaxed(0x00, miphy_phy->base + RESET_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int miphy365x_init(struct phy *phy)
|
||||
{
|
||||
struct miphy365x_phy *miphy_phy = phy_get_drvdata(phy);
|
||||
struct miphy365x_dev *miphy_dev = dev_get_drvdata(phy->dev.parent);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&miphy_dev->miphy_mutex);
|
||||
|
||||
ret = miphy365x_set_path(miphy_phy, miphy_dev);
|
||||
if (ret) {
|
||||
mutex_unlock(&miphy_dev->miphy_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Initialise Miphy for PCIe or SATA */
|
||||
if (miphy_phy->type == MIPHY_TYPE_PCIE)
|
||||
ret = miphy365x_init_pcie_port(miphy_phy, miphy_dev);
|
||||
else
|
||||
ret = miphy365x_init_sata_port(miphy_phy, miphy_dev);
|
||||
|
||||
mutex_unlock(&miphy_dev->miphy_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int miphy365x_get_addr(struct device *dev, struct miphy365x_phy *miphy_phy,
|
||||
int index)
|
||||
{
|
||||
struct device_node *phynode = miphy_phy->phy->dev.of_node;
|
||||
const char *name;
|
||||
const __be32 *taddr;
|
||||
int type = miphy_phy->type;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_string_index(phynode, "reg-names", index, &name);
|
||||
if (ret) {
|
||||
dev_err(dev, "no reg-names property not found\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!strncmp(name, "syscfg", 6)) {
|
||||
taddr = of_get_address(phynode, index, NULL, NULL);
|
||||
if (!taddr) {
|
||||
dev_err(dev, "failed to fetch syscfg address\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
miphy_phy->ctrlreg = of_translate_address(phynode, taddr);
|
||||
if (miphy_phy->ctrlreg == OF_BAD_ADDR) {
|
||||
dev_err(dev, "failed to translate syscfg address\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!((!strncmp(name, "sata", 4) && type == MIPHY_TYPE_SATA) ||
|
||||
(!strncmp(name, "pcie", 4) && type == MIPHY_TYPE_PCIE)))
|
||||
return 0;
|
||||
|
||||
miphy_phy->base = of_iomap(phynode, index);
|
||||
if (!miphy_phy->base) {
|
||||
dev_err(dev, "Failed to map %s\n", phynode->full_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy *miphy365x_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct miphy365x_dev *miphy_dev = dev_get_drvdata(dev);
|
||||
struct miphy365x_phy *miphy_phy = NULL;
|
||||
struct device_node *phynode = args->np;
|
||||
int ret, index;
|
||||
|
||||
if (!of_device_is_available(phynode)) {
|
||||
dev_warn(dev, "Requested PHY is disabled\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
if (args->args_count != 1) {
|
||||
dev_err(dev, "Invalid number of cells in 'phy' property\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
for (index = 0; index < of_get_child_count(dev->of_node); index++)
|
||||
if (phynode == miphy_dev->phys[index]->phy->dev.of_node) {
|
||||
miphy_phy = miphy_dev->phys[index];
|
||||
break;
|
||||
}
|
||||
|
||||
if (!miphy_phy) {
|
||||
dev_err(dev, "Failed to find appropriate phy\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
miphy_phy->type = args->args[0];
|
||||
|
||||
if (!(miphy_phy->type == MIPHY_TYPE_SATA ||
|
||||
miphy_phy->type == MIPHY_TYPE_PCIE)) {
|
||||
dev_err(dev, "Unsupported device type: %d\n", miphy_phy->type);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/* Each port handles SATA and PCIE - third entry is always sysconf. */
|
||||
for (index = 0; index < 3; index++) {
|
||||
ret = miphy365x_get_addr(dev, miphy_phy, index);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return miphy_phy->phy;
|
||||
}
|
||||
|
||||
static struct phy_ops miphy365x_ops = {
|
||||
.init = miphy365x_init,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int miphy365x_of_probe(struct device_node *phynode,
|
||||
struct miphy365x_phy *miphy_phy)
|
||||
{
|
||||
of_property_read_u32(phynode, "st,sata-gen", &miphy_phy->sata_gen);
|
||||
if (!miphy_phy->sata_gen)
|
||||
miphy_phy->sata_gen = SATA_GEN1;
|
||||
|
||||
miphy_phy->pcie_tx_pol_inv =
|
||||
of_property_read_bool(phynode, "st,pcie-tx-pol-inv");
|
||||
|
||||
miphy_phy->sata_tx_pol_inv =
|
||||
of_property_read_bool(phynode, "st,sata-tx-pol-inv");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int miphy365x_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *child, *np = pdev->dev.of_node;
|
||||
struct miphy365x_dev *miphy_dev;
|
||||
struct phy_provider *provider;
|
||||
struct phy *phy;
|
||||
int chancount, port = 0;
|
||||
int ret;
|
||||
|
||||
miphy_dev = devm_kzalloc(&pdev->dev, sizeof(*miphy_dev), GFP_KERNEL);
|
||||
if (!miphy_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
chancount = of_get_child_count(np);
|
||||
miphy_dev->phys = devm_kzalloc(&pdev->dev, sizeof(phy) * chancount,
|
||||
GFP_KERNEL);
|
||||
if (!miphy_dev->phys)
|
||||
return -ENOMEM;
|
||||
|
||||
miphy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
|
||||
if (IS_ERR(miphy_dev->regmap)) {
|
||||
dev_err(miphy_dev->dev, "No syscfg phandle specified\n");
|
||||
return PTR_ERR(miphy_dev->regmap);
|
||||
}
|
||||
|
||||
miphy_dev->dev = &pdev->dev;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, miphy_dev);
|
||||
|
||||
mutex_init(&miphy_dev->miphy_mutex);
|
||||
|
||||
for_each_child_of_node(np, child) {
|
||||
struct miphy365x_phy *miphy_phy;
|
||||
|
||||
miphy_phy = devm_kzalloc(&pdev->dev, sizeof(*miphy_phy),
|
||||
GFP_KERNEL);
|
||||
if (!miphy_phy)
|
||||
return -ENOMEM;
|
||||
|
||||
miphy_dev->phys[port] = miphy_phy;
|
||||
|
||||
phy = devm_phy_create(&pdev->dev, child, &miphy365x_ops, NULL);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(&pdev->dev, "failed to create PHY\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
miphy_dev->phys[port]->phy = phy;
|
||||
|
||||
ret = miphy365x_of_probe(child, miphy_phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
phy_set_drvdata(phy, miphy_dev->phys[port]);
|
||||
port++;
|
||||
}
|
||||
|
||||
provider = devm_of_phy_provider_register(&pdev->dev, miphy365x_xlate);
|
||||
if (IS_ERR(provider))
|
||||
return PTR_ERR(provider);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id miphy365x_of_match[] = {
|
||||
{ .compatible = "st,miphy365x-phy", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, miphy365x_of_match);
|
||||
|
||||
static struct platform_driver miphy365x_driver = {
|
||||
.probe = miphy365x_probe,
|
||||
.driver = {
|
||||
.name = "miphy365x-phy",
|
||||
.of_match_table = miphy365x_of_match,
|
||||
}
|
||||
};
|
||||
module_platform_driver(miphy365x_driver);
|
||||
|
||||
MODULE_AUTHOR("Alexandre Torgue <alexandre.torgue@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics miphy365x driver");
|
||||
MODULE_LICENSE("GPL v2");
|
138
drivers/phy/phy-mvebu-sata.c
Normal file
138
drivers/phy/phy-mvebu-sata.c
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* phy-mvebu-sata.c: SATA Phy driver for the Marvell mvebu SoCs.
|
||||
*
|
||||
* Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct priv {
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
#define SATA_PHY_MODE_2 0x0330
|
||||
#define MODE_2_FORCE_PU_TX BIT(0)
|
||||
#define MODE_2_FORCE_PU_RX BIT(1)
|
||||
#define MODE_2_PU_PLL BIT(2)
|
||||
#define MODE_2_PU_IVREF BIT(3)
|
||||
#define SATA_IF_CTRL 0x0050
|
||||
#define CTRL_PHY_SHUTDOWN BIT(9)
|
||||
|
||||
static int phy_mvebu_sata_power_on(struct phy *phy)
|
||||
{
|
||||
struct priv *priv = phy_get_drvdata(phy);
|
||||
u32 reg;
|
||||
|
||||
clk_prepare_enable(priv->clk);
|
||||
|
||||
/* Enable PLL and IVREF */
|
||||
reg = readl(priv->base + SATA_PHY_MODE_2);
|
||||
reg |= (MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX |
|
||||
MODE_2_PU_PLL | MODE_2_PU_IVREF);
|
||||
writel(reg , priv->base + SATA_PHY_MODE_2);
|
||||
|
||||
/* Enable PHY */
|
||||
reg = readl(priv->base + SATA_IF_CTRL);
|
||||
reg &= ~CTRL_PHY_SHUTDOWN;
|
||||
writel(reg, priv->base + SATA_IF_CTRL);
|
||||
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phy_mvebu_sata_power_off(struct phy *phy)
|
||||
{
|
||||
struct priv *priv = phy_get_drvdata(phy);
|
||||
u32 reg;
|
||||
|
||||
clk_prepare_enable(priv->clk);
|
||||
|
||||
/* Disable PLL and IVREF */
|
||||
reg = readl(priv->base + SATA_PHY_MODE_2);
|
||||
reg &= ~(MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX |
|
||||
MODE_2_PU_PLL | MODE_2_PU_IVREF);
|
||||
writel(reg, priv->base + SATA_PHY_MODE_2);
|
||||
|
||||
/* Disable PHY */
|
||||
reg = readl(priv->base + SATA_IF_CTRL);
|
||||
reg |= CTRL_PHY_SHUTDOWN;
|
||||
writel(reg, priv->base + SATA_IF_CTRL);
|
||||
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops phy_mvebu_sata_ops = {
|
||||
.power_on = phy_mvebu_sata_power_on,
|
||||
.power_off = phy_mvebu_sata_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int phy_mvebu_sata_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct phy_provider *phy_provider;
|
||||
struct resource *res;
|
||||
struct priv *priv;
|
||||
struct phy *phy;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
priv->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(priv->base))
|
||||
return PTR_ERR(priv->base);
|
||||
|
||||
priv->clk = devm_clk_get(&pdev->dev, "sata");
|
||||
if (IS_ERR(priv->clk))
|
||||
return PTR_ERR(priv->clk);
|
||||
|
||||
phy = devm_phy_create(&pdev->dev, NULL, &phy_mvebu_sata_ops, NULL);
|
||||
if (IS_ERR(phy))
|
||||
return PTR_ERR(phy);
|
||||
|
||||
phy_set_drvdata(phy, priv);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(&pdev->dev,
|
||||
of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
/* The boot loader may of left it on. Turn it off. */
|
||||
phy_mvebu_sata_power_off(phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id phy_mvebu_sata_of_match[] = {
|
||||
{ .compatible = "marvell,mvebu-sata-phy" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, phy_mvebu_sata_of_match);
|
||||
|
||||
static struct platform_driver phy_mvebu_sata_driver = {
|
||||
.probe = phy_mvebu_sata_probe,
|
||||
.driver = {
|
||||
.name = "phy-mvebu-sata",
|
||||
.of_match_table = phy_mvebu_sata_of_match,
|
||||
}
|
||||
};
|
||||
module_platform_driver(phy_mvebu_sata_driver);
|
||||
|
||||
MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
|
||||
MODULE_DESCRIPTION("Marvell MVEBU SATA PHY driver");
|
||||
MODULE_LICENSE("GPL v2");
|
367
drivers/phy/phy-omap-control.c
Normal file
367
drivers/phy/phy-omap-control.c
Normal file
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* omap-control-phy.c - The PHY part of control module.
|
||||
*
|
||||
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/phy/omap_control_phy.h>
|
||||
|
||||
/**
|
||||
* omap_control_pcie_pcs - set the PCS delay count
|
||||
* @dev: the control module device
|
||||
* @id: index of the pcie PHY (should be 1 or 2)
|
||||
* @delay: 8 bit delay value
|
||||
*/
|
||||
void omap_control_pcie_pcs(struct device *dev, u8 id, u8 delay)
|
||||
{
|
||||
u32 val;
|
||||
struct omap_control_phy *control_phy;
|
||||
|
||||
if (IS_ERR(dev) || !dev) {
|
||||
pr_err("%s: invalid device\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
control_phy = dev_get_drvdata(dev);
|
||||
if (!control_phy) {
|
||||
dev_err(dev, "%s: invalid control phy device\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (control_phy->type != OMAP_CTRL_TYPE_PCIE) {
|
||||
dev_err(dev, "%s: unsupported operation\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
val = readl(control_phy->pcie_pcs);
|
||||
val &= ~(OMAP_CTRL_PCIE_PCS_MASK <<
|
||||
(id * OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT));
|
||||
val |= delay << (id * OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT);
|
||||
writel(val, control_phy->pcie_pcs);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_control_pcie_pcs);
|
||||
|
||||
/**
|
||||
* omap_control_phy_power - power on/off the phy using control module reg
|
||||
* @dev: the control module device
|
||||
* @on: 0 or 1, based on powering on or off the PHY
|
||||
*/
|
||||
void omap_control_phy_power(struct device *dev, int on)
|
||||
{
|
||||
u32 val;
|
||||
unsigned long rate;
|
||||
struct omap_control_phy *control_phy;
|
||||
|
||||
if (IS_ERR(dev) || !dev) {
|
||||
pr_err("%s: invalid device\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
control_phy = dev_get_drvdata(dev);
|
||||
if (!control_phy) {
|
||||
dev_err(dev, "%s: invalid control phy device\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (control_phy->type == OMAP_CTRL_TYPE_OTGHS)
|
||||
return;
|
||||
|
||||
val = readl(control_phy->power);
|
||||
|
||||
switch (control_phy->type) {
|
||||
case OMAP_CTRL_TYPE_USB2:
|
||||
if (on)
|
||||
val &= ~OMAP_CTRL_DEV_PHY_PD;
|
||||
else
|
||||
val |= OMAP_CTRL_DEV_PHY_PD;
|
||||
break;
|
||||
|
||||
case OMAP_CTRL_TYPE_PCIE:
|
||||
case OMAP_CTRL_TYPE_PIPE3:
|
||||
rate = clk_get_rate(control_phy->sys_clk);
|
||||
rate = rate/1000000;
|
||||
|
||||
if (on) {
|
||||
val &= ~(OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK |
|
||||
OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK);
|
||||
val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWERON <<
|
||||
OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
|
||||
val |= rate <<
|
||||
OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
|
||||
} else {
|
||||
val &= ~OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK;
|
||||
val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWEROFF <<
|
||||
OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
|
||||
}
|
||||
break;
|
||||
|
||||
case OMAP_CTRL_TYPE_DRA7USB2:
|
||||
if (on)
|
||||
val &= ~OMAP_CTRL_USB2_PHY_PD;
|
||||
else
|
||||
val |= OMAP_CTRL_USB2_PHY_PD;
|
||||
break;
|
||||
|
||||
case OMAP_CTRL_TYPE_AM437USB2:
|
||||
if (on) {
|
||||
val &= ~(AM437X_CTRL_USB2_PHY_PD |
|
||||
AM437X_CTRL_USB2_OTG_PD);
|
||||
val |= (AM437X_CTRL_USB2_OTGVDET_EN |
|
||||
AM437X_CTRL_USB2_OTGSESSEND_EN);
|
||||
} else {
|
||||
val &= ~(AM437X_CTRL_USB2_OTGVDET_EN |
|
||||
AM437X_CTRL_USB2_OTGSESSEND_EN);
|
||||
val |= (AM437X_CTRL_USB2_PHY_PD |
|
||||
AM437X_CTRL_USB2_OTG_PD);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "%s: type %d not recognized\n",
|
||||
__func__, control_phy->type);
|
||||
break;
|
||||
}
|
||||
|
||||
writel(val, control_phy->power);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_control_phy_power);
|
||||
|
||||
/**
|
||||
* omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded
|
||||
* @ctrl_phy: struct omap_control_phy *
|
||||
*
|
||||
* Writes to the mailbox register to notify the usb core that a usb
|
||||
* device has been connected.
|
||||
*/
|
||||
static void omap_control_usb_host_mode(struct omap_control_phy *ctrl_phy)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(ctrl_phy->otghs_control);
|
||||
val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND);
|
||||
val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID;
|
||||
writel(val, ctrl_phy->otghs_control);
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high
|
||||
* impedance
|
||||
* @ctrl_phy: struct omap_control_phy *
|
||||
*
|
||||
* Writes to the mailbox register to notify the usb core that it has been
|
||||
* connected to a usb host.
|
||||
*/
|
||||
static void omap_control_usb_device_mode(struct omap_control_phy *ctrl_phy)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(ctrl_phy->otghs_control);
|
||||
val &= ~OMAP_CTRL_DEV_SESSEND;
|
||||
val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID |
|
||||
OMAP_CTRL_DEV_VBUSVALID;
|
||||
writel(val, ctrl_phy->otghs_control);
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high
|
||||
* impedance
|
||||
* @ctrl_phy: struct omap_control_phy *
|
||||
*
|
||||
* Writes to the mailbox register to notify the usb core it's now in
|
||||
* disconnected state.
|
||||
*/
|
||||
static void omap_control_usb_set_sessionend(struct omap_control_phy *ctrl_phy)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(ctrl_phy->otghs_control);
|
||||
val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID);
|
||||
val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND;
|
||||
writel(val, ctrl_phy->otghs_control);
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_control_usb_set_mode - Calls to functions to set USB in one of host mode
|
||||
* or device mode or to denote disconnected state
|
||||
* @dev: the control module device
|
||||
* @mode: The mode to which usb should be configured
|
||||
*
|
||||
* This is an API to write to the mailbox register to notify the usb core that
|
||||
* a usb device has been connected.
|
||||
*/
|
||||
void omap_control_usb_set_mode(struct device *dev,
|
||||
enum omap_control_usb_mode mode)
|
||||
{
|
||||
struct omap_control_phy *ctrl_phy;
|
||||
|
||||
if (IS_ERR(dev) || !dev)
|
||||
return;
|
||||
|
||||
ctrl_phy = dev_get_drvdata(dev);
|
||||
|
||||
if (!ctrl_phy) {
|
||||
dev_err(dev, "Invalid control phy device\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctrl_phy->type != OMAP_CTRL_TYPE_OTGHS)
|
||||
return;
|
||||
|
||||
switch (mode) {
|
||||
case USB_MODE_HOST:
|
||||
omap_control_usb_host_mode(ctrl_phy);
|
||||
break;
|
||||
case USB_MODE_DEVICE:
|
||||
omap_control_usb_device_mode(ctrl_phy);
|
||||
break;
|
||||
case USB_MODE_DISCONNECT:
|
||||
omap_control_usb_set_sessionend(ctrl_phy);
|
||||
break;
|
||||
default:
|
||||
dev_vdbg(dev, "invalid omap control usb mode\n");
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_control_usb_set_mode);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
|
||||
static const enum omap_control_phy_type otghs_data = OMAP_CTRL_TYPE_OTGHS;
|
||||
static const enum omap_control_phy_type usb2_data = OMAP_CTRL_TYPE_USB2;
|
||||
static const enum omap_control_phy_type pipe3_data = OMAP_CTRL_TYPE_PIPE3;
|
||||
static const enum omap_control_phy_type pcie_data = OMAP_CTRL_TYPE_PCIE;
|
||||
static const enum omap_control_phy_type dra7usb2_data = OMAP_CTRL_TYPE_DRA7USB2;
|
||||
static const enum omap_control_phy_type am437usb2_data = OMAP_CTRL_TYPE_AM437USB2;
|
||||
|
||||
static const struct of_device_id omap_control_phy_id_table[] = {
|
||||
{
|
||||
.compatible = "ti,control-phy-otghs",
|
||||
.data = &otghs_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,control-phy-usb2",
|
||||
.data = &usb2_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,control-phy-pipe3",
|
||||
.data = &pipe3_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,control-phy-pcie",
|
||||
.data = &pcie_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,control-phy-usb2-dra7",
|
||||
.data = &dra7usb2_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,control-phy-usb2-am437",
|
||||
.data = &am437usb2_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_control_phy_id_table);
|
||||
#endif
|
||||
|
||||
|
||||
static int omap_control_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
const struct of_device_id *of_id;
|
||||
struct omap_control_phy *control_phy;
|
||||
|
||||
of_id = of_match_device(of_match_ptr(omap_control_phy_id_table),
|
||||
&pdev->dev);
|
||||
if (!of_id)
|
||||
return -EINVAL;
|
||||
|
||||
control_phy = devm_kzalloc(&pdev->dev, sizeof(*control_phy),
|
||||
GFP_KERNEL);
|
||||
if (!control_phy)
|
||||
return -ENOMEM;
|
||||
|
||||
control_phy->dev = &pdev->dev;
|
||||
control_phy->type = *(enum omap_control_phy_type *)of_id->data;
|
||||
|
||||
if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) {
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"otghs_control");
|
||||
control_phy->otghs_control = devm_ioremap_resource(
|
||||
&pdev->dev, res);
|
||||
if (IS_ERR(control_phy->otghs_control))
|
||||
return PTR_ERR(control_phy->otghs_control);
|
||||
} else {
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"power");
|
||||
control_phy->power = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(control_phy->power)) {
|
||||
dev_err(&pdev->dev, "Couldn't get power register\n");
|
||||
return PTR_ERR(control_phy->power);
|
||||
}
|
||||
}
|
||||
|
||||
if (control_phy->type == OMAP_CTRL_TYPE_PIPE3 ||
|
||||
control_phy->type == OMAP_CTRL_TYPE_PCIE) {
|
||||
control_phy->sys_clk = devm_clk_get(control_phy->dev,
|
||||
"sys_clkin");
|
||||
if (IS_ERR(control_phy->sys_clk)) {
|
||||
pr_err("%s: unable to get sys_clkin\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (control_phy->type == OMAP_CTRL_TYPE_PCIE) {
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"pcie_pcs");
|
||||
control_phy->pcie_pcs = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(control_phy->pcie_pcs))
|
||||
return PTR_ERR(control_phy->pcie_pcs);
|
||||
}
|
||||
|
||||
dev_set_drvdata(control_phy->dev, control_phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap_control_phy_driver = {
|
||||
.probe = omap_control_phy_probe,
|
||||
.driver = {
|
||||
.name = "omap-control-phy",
|
||||
.of_match_table = of_match_ptr(omap_control_phy_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init omap_control_phy_init(void)
|
||||
{
|
||||
return platform_driver_register(&omap_control_phy_driver);
|
||||
}
|
||||
subsys_initcall(omap_control_phy_init);
|
||||
|
||||
static void __exit omap_control_phy_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&omap_control_phy_driver);
|
||||
}
|
||||
module_exit(omap_control_phy_exit);
|
||||
|
||||
MODULE_ALIAS("platform: omap_control_phy");
|
||||
MODULE_AUTHOR("Texas Instruments Inc.");
|
||||
MODULE_DESCRIPTION("OMAP Control Module PHY Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
393
drivers/phy/phy-omap-usb2.c
Normal file
393
drivers/phy/phy-omap-usb2.c
Normal file
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* omap-usb2.c - USB PHY, talking to musb controller in OMAP.
|
||||
*
|
||||
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/phy/omap_usb.h>
|
||||
#include <linux/usb/phy_companion.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/phy/omap_control_phy.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#define USB2PHY_DISCON_BYP_LATCH (1 << 31)
|
||||
#define USB2PHY_ANA_CONFIG1 0x4c
|
||||
|
||||
/**
|
||||
* omap_usb2_set_comparator - links the comparator present in the sytem with
|
||||
* this phy
|
||||
* @comparator - the companion phy(comparator) for this phy
|
||||
*
|
||||
* The phy companion driver should call this API passing the phy_companion
|
||||
* filled with set_vbus and start_srp to be used by usb phy.
|
||||
*
|
||||
* For use by phy companion driver
|
||||
*/
|
||||
int omap_usb2_set_comparator(struct phy_companion *comparator)
|
||||
{
|
||||
struct omap_usb *phy;
|
||||
struct usb_phy *x = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
|
||||
if (IS_ERR(x))
|
||||
return -ENODEV;
|
||||
|
||||
phy = phy_to_omapusb(x);
|
||||
phy->comparator = comparator;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_usb2_set_comparator);
|
||||
|
||||
static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled)
|
||||
{
|
||||
struct omap_usb *phy = phy_to_omapusb(otg->phy);
|
||||
|
||||
if (!phy->comparator)
|
||||
return -ENODEV;
|
||||
|
||||
return phy->comparator->set_vbus(phy->comparator, enabled);
|
||||
}
|
||||
|
||||
static int omap_usb_start_srp(struct usb_otg *otg)
|
||||
{
|
||||
struct omap_usb *phy = phy_to_omapusb(otg->phy);
|
||||
|
||||
if (!phy->comparator)
|
||||
return -ENODEV;
|
||||
|
||||
return phy->comparator->start_srp(phy->comparator);
|
||||
}
|
||||
|
||||
static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
struct usb_phy *phy = otg->phy;
|
||||
|
||||
otg->host = host;
|
||||
if (!host)
|
||||
phy->state = OTG_STATE_UNDEFINED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_usb_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
struct usb_phy *phy = otg->phy;
|
||||
|
||||
otg->gadget = gadget;
|
||||
if (!gadget)
|
||||
phy->state = OTG_STATE_UNDEFINED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_usb_power_off(struct phy *x)
|
||||
{
|
||||
struct omap_usb *phy = phy_get_drvdata(x);
|
||||
|
||||
omap_control_phy_power(phy->control_dev, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_usb_power_on(struct phy *x)
|
||||
{
|
||||
struct omap_usb *phy = phy_get_drvdata(x);
|
||||
|
||||
omap_control_phy_power(phy->control_dev, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_usb_init(struct phy *x)
|
||||
{
|
||||
struct omap_usb *phy = phy_get_drvdata(x);
|
||||
u32 val;
|
||||
|
||||
if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
|
||||
/*
|
||||
*
|
||||
* Reduce the sensitivity of internal PHY by enabling the
|
||||
* DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This
|
||||
* resolves issues with certain devices which can otherwise
|
||||
* be prone to false disconnects.
|
||||
*
|
||||
*/
|
||||
val = omap_usb_readl(phy->phy_base, USB2PHY_ANA_CONFIG1);
|
||||
val |= USB2PHY_DISCON_BYP_LATCH;
|
||||
omap_usb_writel(phy->phy_base, USB2PHY_ANA_CONFIG1, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops ops = {
|
||||
.init = omap_usb_init,
|
||||
.power_on = omap_usb_power_on,
|
||||
.power_off = omap_usb_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct usb_phy_data omap_usb2_data = {
|
||||
.label = "omap_usb2",
|
||||
.flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS,
|
||||
};
|
||||
|
||||
static const struct usb_phy_data omap5_usb2_data = {
|
||||
.label = "omap5_usb2",
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
static const struct usb_phy_data dra7x_usb2_data = {
|
||||
.label = "dra7x_usb2",
|
||||
.flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
|
||||
};
|
||||
|
||||
static const struct usb_phy_data am437x_usb2_data = {
|
||||
.label = "am437x_usb2",
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
static const struct of_device_id omap_usb2_id_table[] = {
|
||||
{
|
||||
.compatible = "ti,omap-usb2",
|
||||
.data = &omap_usb2_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,omap5-usb2",
|
||||
.data = &omap5_usb2_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,dra7x-usb2",
|
||||
.data = &dra7x_usb2_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,am437x-usb2",
|
||||
.data = &am437x_usb2_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_usb2_id_table);
|
||||
#endif
|
||||
|
||||
static int omap_usb2_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_usb *phy;
|
||||
struct phy *generic_phy;
|
||||
struct resource *res;
|
||||
struct phy_provider *phy_provider;
|
||||
struct usb_otg *otg;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *control_node;
|
||||
struct platform_device *control_pdev;
|
||||
const struct of_device_id *of_id;
|
||||
struct usb_phy_data *phy_data;
|
||||
|
||||
of_id = of_match_device(of_match_ptr(omap_usb2_id_table), &pdev->dev);
|
||||
|
||||
if (!of_id)
|
||||
return -EINVAL;
|
||||
|
||||
phy_data = (struct usb_phy_data *)of_id->data;
|
||||
|
||||
phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return -ENOMEM;
|
||||
|
||||
otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
|
||||
if (!otg)
|
||||
return -ENOMEM;
|
||||
|
||||
phy->dev = &pdev->dev;
|
||||
|
||||
phy->phy.dev = phy->dev;
|
||||
phy->phy.label = phy_data->label;
|
||||
phy->phy.otg = otg;
|
||||
phy->phy.type = USB_PHY_TYPE_USB2;
|
||||
|
||||
if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
phy->phy_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(phy->phy_base))
|
||||
return PTR_ERR(phy->phy_base);
|
||||
phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT;
|
||||
}
|
||||
|
||||
control_node = of_parse_phandle(node, "ctrl-module", 0);
|
||||
if (!control_node) {
|
||||
dev_err(&pdev->dev, "Failed to get control device phandle\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
control_pdev = of_find_device_by_node(control_node);
|
||||
if (!control_pdev) {
|
||||
dev_err(&pdev->dev, "Failed to get control device\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
phy->control_dev = &control_pdev->dev;
|
||||
omap_control_phy_power(phy->control_dev, 0);
|
||||
|
||||
otg->set_host = omap_usb_set_host;
|
||||
otg->set_peripheral = omap_usb_set_peripheral;
|
||||
if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS)
|
||||
otg->set_vbus = omap_usb_set_vbus;
|
||||
if (phy_data->flags & OMAP_USB2_HAS_START_SRP)
|
||||
otg->start_srp = omap_usb_start_srp;
|
||||
otg->phy = &phy->phy;
|
||||
|
||||
platform_set_drvdata(pdev, phy);
|
||||
pm_runtime_enable(phy->dev);
|
||||
|
||||
generic_phy = devm_phy_create(phy->dev, NULL, &ops, NULL);
|
||||
if (IS_ERR(generic_phy)) {
|
||||
pm_runtime_disable(phy->dev);
|
||||
return PTR_ERR(generic_phy);
|
||||
}
|
||||
|
||||
phy_set_drvdata(generic_phy, phy);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(phy->dev,
|
||||
of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
pm_runtime_disable(phy->dev);
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
phy->wkupclk = devm_clk_get(phy->dev, "wkupclk");
|
||||
if (IS_ERR(phy->wkupclk)) {
|
||||
dev_warn(&pdev->dev, "unable to get wkupclk, trying old name\n");
|
||||
phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k");
|
||||
if (IS_ERR(phy->wkupclk)) {
|
||||
dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n");
|
||||
return PTR_ERR(phy->wkupclk);
|
||||
} else {
|
||||
dev_warn(&pdev->dev,
|
||||
"found usb_phy_cm_clk32k, please fix DTS\n");
|
||||
}
|
||||
}
|
||||
clk_prepare(phy->wkupclk);
|
||||
|
||||
phy->optclk = devm_clk_get(phy->dev, "refclk");
|
||||
if (IS_ERR(phy->optclk)) {
|
||||
dev_dbg(&pdev->dev, "unable to get refclk, trying old name\n");
|
||||
phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m");
|
||||
if (IS_ERR(phy->optclk)) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"unable to get usb_otg_ss_refclk960m\n");
|
||||
} else {
|
||||
dev_warn(&pdev->dev,
|
||||
"found usb_otg_ss_refclk960m, please fix DTS\n");
|
||||
}
|
||||
} else {
|
||||
clk_prepare(phy->optclk);
|
||||
}
|
||||
|
||||
usb_add_phy_dev(&phy->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_usb2_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_usb *phy = platform_get_drvdata(pdev);
|
||||
|
||||
clk_unprepare(phy->wkupclk);
|
||||
if (!IS_ERR(phy->optclk))
|
||||
clk_unprepare(phy->optclk);
|
||||
usb_remove_phy(&phy->phy);
|
||||
pm_runtime_disable(phy->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
|
||||
static int omap_usb2_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct omap_usb *phy = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable(phy->wkupclk);
|
||||
if (!IS_ERR(phy->optclk))
|
||||
clk_disable(phy->optclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_usb2_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct omap_usb *phy = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = clk_enable(phy->wkupclk);
|
||||
if (ret < 0) {
|
||||
dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret);
|
||||
goto err0;
|
||||
}
|
||||
|
||||
if (!IS_ERR(phy->optclk)) {
|
||||
ret = clk_enable(phy->optclk);
|
||||
if (ret < 0) {
|
||||
dev_err(phy->dev, "Failed to enable optclk %d\n", ret);
|
||||
goto err1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
clk_disable(phy->wkupclk);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops omap_usb2_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(omap_usb2_runtime_suspend, omap_usb2_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
|
||||
#define DEV_PM_OPS (&omap_usb2_pm_ops)
|
||||
#else
|
||||
#define DEV_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver omap_usb2_driver = {
|
||||
.probe = omap_usb2_probe,
|
||||
.remove = omap_usb2_remove,
|
||||
.driver = {
|
||||
.name = "omap-usb2",
|
||||
.pm = DEV_PM_OPS,
|
||||
.of_match_table = of_match_ptr(omap_usb2_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(omap_usb2_driver);
|
||||
|
||||
MODULE_ALIAS("platform: omap_usb2");
|
||||
MODULE_AUTHOR("Texas Instruments Inc.");
|
||||
MODULE_DESCRIPTION("OMAP USB2 phy driver");
|
||||
MODULE_LICENSE("GPL v2");
|
288
drivers/phy/phy-qcom-apq8064-sata.c
Normal file
288
drivers/phy/phy-qcom-apq8064-sata.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
|
||||
/* PHY registers */
|
||||
#define UNIPHY_PLL_REFCLK_CFG 0x000
|
||||
#define UNIPHY_PLL_PWRGEN_CFG 0x014
|
||||
#define UNIPHY_PLL_GLB_CFG 0x020
|
||||
#define UNIPHY_PLL_SDM_CFG0 0x038
|
||||
#define UNIPHY_PLL_SDM_CFG1 0x03C
|
||||
#define UNIPHY_PLL_SDM_CFG2 0x040
|
||||
#define UNIPHY_PLL_SDM_CFG3 0x044
|
||||
#define UNIPHY_PLL_SDM_CFG4 0x048
|
||||
#define UNIPHY_PLL_SSC_CFG0 0x04C
|
||||
#define UNIPHY_PLL_SSC_CFG1 0x050
|
||||
#define UNIPHY_PLL_SSC_CFG2 0x054
|
||||
#define UNIPHY_PLL_SSC_CFG3 0x058
|
||||
#define UNIPHY_PLL_LKDET_CFG0 0x05C
|
||||
#define UNIPHY_PLL_LKDET_CFG1 0x060
|
||||
#define UNIPHY_PLL_LKDET_CFG2 0x064
|
||||
#define UNIPHY_PLL_CAL_CFG0 0x06C
|
||||
#define UNIPHY_PLL_CAL_CFG8 0x08C
|
||||
#define UNIPHY_PLL_CAL_CFG9 0x090
|
||||
#define UNIPHY_PLL_CAL_CFG10 0x094
|
||||
#define UNIPHY_PLL_CAL_CFG11 0x098
|
||||
#define UNIPHY_PLL_STATUS 0x0C0
|
||||
|
||||
#define SATA_PHY_SER_CTRL 0x100
|
||||
#define SATA_PHY_TX_DRIV_CTRL0 0x104
|
||||
#define SATA_PHY_TX_DRIV_CTRL1 0x108
|
||||
#define SATA_PHY_TX_IMCAL0 0x11C
|
||||
#define SATA_PHY_TX_IMCAL2 0x124
|
||||
#define SATA_PHY_RX_IMCAL0 0x128
|
||||
#define SATA_PHY_EQUAL 0x13C
|
||||
#define SATA_PHY_OOB_TERM 0x144
|
||||
#define SATA_PHY_CDR_CTRL0 0x148
|
||||
#define SATA_PHY_CDR_CTRL1 0x14C
|
||||
#define SATA_PHY_CDR_CTRL2 0x150
|
||||
#define SATA_PHY_CDR_CTRL3 0x154
|
||||
#define SATA_PHY_PI_CTRL0 0x168
|
||||
#define SATA_PHY_POW_DWN_CTRL0 0x180
|
||||
#define SATA_PHY_POW_DWN_CTRL1 0x184
|
||||
#define SATA_PHY_TX_DATA_CTRL 0x188
|
||||
#define SATA_PHY_ALIGNP 0x1A4
|
||||
#define SATA_PHY_TX_IMCAL_STAT 0x1E4
|
||||
#define SATA_PHY_RX_IMCAL_STAT 0x1E8
|
||||
|
||||
#define UNIPHY_PLL_LOCK BIT(0)
|
||||
#define SATA_PHY_TX_CAL BIT(0)
|
||||
#define SATA_PHY_RX_CAL BIT(0)
|
||||
|
||||
/* default timeout set to 1 sec */
|
||||
#define TIMEOUT_MS 10000
|
||||
#define DELAY_INTERVAL_US 100
|
||||
|
||||
struct qcom_apq8064_sata_phy {
|
||||
void __iomem *mmio;
|
||||
struct clk *cfg_clk;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
/* Helper function to do poll and timeout */
|
||||
static int read_poll_timeout(void __iomem *addr, u32 mask)
|
||||
{
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(TIMEOUT_MS);
|
||||
|
||||
do {
|
||||
if (readl_relaxed(addr) & mask)
|
||||
return 0;
|
||||
|
||||
usleep_range(DELAY_INTERVAL_US, DELAY_INTERVAL_US + 50);
|
||||
} while (!time_after(jiffies, timeout));
|
||||
|
||||
return (readl_relaxed(addr) & mask) ? 0 : -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int qcom_apq8064_sata_phy_init(struct phy *generic_phy)
|
||||
{
|
||||
struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy);
|
||||
void __iomem *base = phy->mmio;
|
||||
int ret = 0;
|
||||
|
||||
/* SATA phy initialization */
|
||||
writel_relaxed(0x01, base + SATA_PHY_SER_CTRL);
|
||||
writel_relaxed(0xB1, base + SATA_PHY_POW_DWN_CTRL0);
|
||||
/* Make sure the power down happens before power up */
|
||||
mb();
|
||||
usleep_range(10, 60);
|
||||
|
||||
writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0);
|
||||
writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1);
|
||||
writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0);
|
||||
writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0);
|
||||
writel_relaxed(0x02, base + SATA_PHY_TX_IMCAL2);
|
||||
|
||||
/* Write UNIPHYPLL registers to configure PLL */
|
||||
writel_relaxed(0x04, base + UNIPHY_PLL_REFCLK_CFG);
|
||||
writel_relaxed(0x00, base + UNIPHY_PLL_PWRGEN_CFG);
|
||||
|
||||
writel_relaxed(0x0A, base + UNIPHY_PLL_CAL_CFG0);
|
||||
writel_relaxed(0xF3, base + UNIPHY_PLL_CAL_CFG8);
|
||||
writel_relaxed(0x01, base + UNIPHY_PLL_CAL_CFG9);
|
||||
writel_relaxed(0xED, base + UNIPHY_PLL_CAL_CFG10);
|
||||
writel_relaxed(0x02, base + UNIPHY_PLL_CAL_CFG11);
|
||||
|
||||
writel_relaxed(0x36, base + UNIPHY_PLL_SDM_CFG0);
|
||||
writel_relaxed(0x0D, base + UNIPHY_PLL_SDM_CFG1);
|
||||
writel_relaxed(0xA3, base + UNIPHY_PLL_SDM_CFG2);
|
||||
writel_relaxed(0xF0, base + UNIPHY_PLL_SDM_CFG3);
|
||||
writel_relaxed(0x00, base + UNIPHY_PLL_SDM_CFG4);
|
||||
|
||||
writel_relaxed(0x19, base + UNIPHY_PLL_SSC_CFG0);
|
||||
writel_relaxed(0xE1, base + UNIPHY_PLL_SSC_CFG1);
|
||||
writel_relaxed(0x00, base + UNIPHY_PLL_SSC_CFG2);
|
||||
writel_relaxed(0x11, base + UNIPHY_PLL_SSC_CFG3);
|
||||
|
||||
writel_relaxed(0x04, base + UNIPHY_PLL_LKDET_CFG0);
|
||||
writel_relaxed(0xFF, base + UNIPHY_PLL_LKDET_CFG1);
|
||||
|
||||
writel_relaxed(0x02, base + UNIPHY_PLL_GLB_CFG);
|
||||
/* make sure global config LDO power down happens before power up */
|
||||
mb();
|
||||
|
||||
writel_relaxed(0x03, base + UNIPHY_PLL_GLB_CFG);
|
||||
writel_relaxed(0x05, base + UNIPHY_PLL_LKDET_CFG2);
|
||||
|
||||
/* PLL Lock wait */
|
||||
ret = read_poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "poll timeout UNIPHY_PLL_STATUS\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TX Calibration */
|
||||
ret = read_poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "poll timeout SATA_PHY_TX_IMCAL_STAT\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* RX Calibration */
|
||||
ret = read_poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "poll timeout SATA_PHY_RX_IMCAL_STAT\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* SATA phy calibrated succesfully, power up to functional mode */
|
||||
writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1);
|
||||
writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0);
|
||||
writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0);
|
||||
|
||||
writel_relaxed(0x00, base + SATA_PHY_POW_DWN_CTRL1);
|
||||
writel_relaxed(0x59, base + SATA_PHY_CDR_CTRL0);
|
||||
writel_relaxed(0x04, base + SATA_PHY_CDR_CTRL1);
|
||||
writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL2);
|
||||
writel_relaxed(0x00, base + SATA_PHY_PI_CTRL0);
|
||||
writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL3);
|
||||
writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0);
|
||||
|
||||
writel_relaxed(0x11, base + SATA_PHY_TX_DATA_CTRL);
|
||||
writel_relaxed(0x43, base + SATA_PHY_ALIGNP);
|
||||
writel_relaxed(0x04, base + SATA_PHY_OOB_TERM);
|
||||
|
||||
writel_relaxed(0x01, base + SATA_PHY_EQUAL);
|
||||
writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL0);
|
||||
writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_apq8064_sata_phy_exit(struct phy *generic_phy)
|
||||
{
|
||||
struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy);
|
||||
void __iomem *base = phy->mmio;
|
||||
|
||||
/* Power down PHY */
|
||||
writel_relaxed(0xF8, base + SATA_PHY_POW_DWN_CTRL0);
|
||||
writel_relaxed(0xFE, base + SATA_PHY_POW_DWN_CTRL1);
|
||||
|
||||
/* Power down PLL block */
|
||||
writel_relaxed(0x00, base + UNIPHY_PLL_GLB_CFG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops qcom_apq8064_sata_phy_ops = {
|
||||
.init = qcom_apq8064_sata_phy_init,
|
||||
.exit = qcom_apq8064_sata_phy_exit,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int qcom_apq8064_sata_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_apq8064_sata_phy *phy;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy *generic_phy;
|
||||
int ret;
|
||||
|
||||
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
phy->mmio = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(phy->mmio))
|
||||
return PTR_ERR(phy->mmio);
|
||||
|
||||
generic_phy = devm_phy_create(dev, NULL, &qcom_apq8064_sata_phy_ops,
|
||||
NULL);
|
||||
if (IS_ERR(generic_phy)) {
|
||||
dev_err(dev, "%s: failed to create phy\n", __func__);
|
||||
return PTR_ERR(generic_phy);
|
||||
}
|
||||
|
||||
phy->dev = dev;
|
||||
phy_set_drvdata(generic_phy, phy);
|
||||
platform_set_drvdata(pdev, phy);
|
||||
|
||||
phy->cfg_clk = devm_clk_get(dev, "cfg");
|
||||
if (IS_ERR(phy->cfg_clk)) {
|
||||
dev_err(dev, "Failed to get sata cfg clock\n");
|
||||
return PTR_ERR(phy->cfg_clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(phy->cfg_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
clk_disable_unprepare(phy->cfg_clk);
|
||||
dev_err(dev, "%s: failed to register phy\n", __func__);
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_apq8064_sata_phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_apq8064_sata_phy *phy = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable_unprepare(phy->cfg_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_apq8064_sata_phy_of_match[] = {
|
||||
{ .compatible = "qcom,apq8064-sata-phy" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qcom_apq8064_sata_phy_of_match);
|
||||
|
||||
static struct platform_driver qcom_apq8064_sata_phy_driver = {
|
||||
.probe = qcom_apq8064_sata_phy_probe,
|
||||
.remove = qcom_apq8064_sata_phy_remove,
|
||||
.driver = {
|
||||
.name = "qcom-apq8064-sata-phy",
|
||||
.of_match_table = qcom_apq8064_sata_phy_of_match,
|
||||
}
|
||||
};
|
||||
module_platform_driver(qcom_apq8064_sata_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QCOM apq8064 SATA PHY driver");
|
||||
MODULE_LICENSE("GPL v2");
|
210
drivers/phy/phy-qcom-ipq806x-sata.c
Normal file
210
drivers/phy/phy-qcom-ipq806x-sata.c
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
|
||||
struct qcom_ipq806x_sata_phy {
|
||||
void __iomem *mmio;
|
||||
struct clk *cfg_clk;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b))
|
||||
|
||||
#define SATA_PHY_P0_PARAM0 0x200
|
||||
#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(x) __set(x, 17, 12)
|
||||
#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK GENMASK(17, 12)
|
||||
#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2(x) __set(x, 11, 6)
|
||||
#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK GENMASK(11, 6)
|
||||
#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1(x) __set(x, 5, 0)
|
||||
#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK GENMASK(5, 0)
|
||||
|
||||
#define SATA_PHY_P0_PARAM1 0x204
|
||||
#define SATA_PHY_P0_PARAM1_RESERVED_BITS31_21(x) __set(x, 31, 21)
|
||||
#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(x) __set(x, 20, 14)
|
||||
#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK GENMASK(20, 14)
|
||||
#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(x) __set(x, 13, 7)
|
||||
#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK GENMASK(13, 7)
|
||||
#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(x) __set(x, 6, 0)
|
||||
#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK GENMASK(6, 0)
|
||||
|
||||
#define SATA_PHY_P0_PARAM2 0x208
|
||||
#define SATA_PHY_P0_PARAM2_RX_EQ(x) __set(x, 20, 18)
|
||||
#define SATA_PHY_P0_PARAM2_RX_EQ_MASK GENMASK(20, 18)
|
||||
|
||||
#define SATA_PHY_P0_PARAM3 0x20C
|
||||
#define SATA_PHY_SSC_EN 0x8
|
||||
#define SATA_PHY_P0_PARAM4 0x210
|
||||
#define SATA_PHY_REF_SSP_EN 0x2
|
||||
#define SATA_PHY_RESET 0x1
|
||||
|
||||
static int qcom_ipq806x_sata_phy_init(struct phy *generic_phy)
|
||||
{
|
||||
struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy);
|
||||
u32 reg;
|
||||
|
||||
/* Setting SSC_EN to 1 */
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM3);
|
||||
reg = reg | SATA_PHY_SSC_EN;
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM3);
|
||||
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM0) &
|
||||
~(SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK |
|
||||
SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK |
|
||||
SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK);
|
||||
reg |= SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(0xf);
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM0);
|
||||
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM1) &
|
||||
~(SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK |
|
||||
SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK |
|
||||
SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK);
|
||||
reg |= SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(0x55) |
|
||||
SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(0x55) |
|
||||
SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(0x55);
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM1);
|
||||
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM2) &
|
||||
~SATA_PHY_P0_PARAM2_RX_EQ_MASK;
|
||||
reg |= SATA_PHY_P0_PARAM2_RX_EQ(0x3);
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM2);
|
||||
|
||||
/* Setting PHY_RESET to 1 */
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
reg = reg | SATA_PHY_RESET;
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
|
||||
/* Setting REF_SSP_EN to 1 */
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
reg = reg | SATA_PHY_REF_SSP_EN | SATA_PHY_RESET;
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
|
||||
/* make sure all changes complete before we let the PHY out of reset */
|
||||
mb();
|
||||
|
||||
/* sleep for max. 50us more to combine processor wakeups */
|
||||
usleep_range(20, 20 + 50);
|
||||
|
||||
/* Clearing PHY_RESET to 0 */
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
reg = reg & ~SATA_PHY_RESET;
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_ipq806x_sata_phy_exit(struct phy *generic_phy)
|
||||
{
|
||||
struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy);
|
||||
u32 reg;
|
||||
|
||||
/* Setting PHY_RESET to 1 */
|
||||
reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
reg = reg | SATA_PHY_RESET;
|
||||
writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops qcom_ipq806x_sata_phy_ops = {
|
||||
.init = qcom_ipq806x_sata_phy_init,
|
||||
.exit = qcom_ipq806x_sata_phy_exit,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int qcom_ipq806x_sata_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_ipq806x_sata_phy *phy;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy *generic_phy;
|
||||
int ret;
|
||||
|
||||
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
phy->mmio = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(phy->mmio))
|
||||
return PTR_ERR(phy->mmio);
|
||||
|
||||
generic_phy = devm_phy_create(dev, NULL, &qcom_ipq806x_sata_phy_ops,
|
||||
NULL);
|
||||
if (IS_ERR(generic_phy)) {
|
||||
dev_err(dev, "%s: failed to create phy\n", __func__);
|
||||
return PTR_ERR(generic_phy);
|
||||
}
|
||||
|
||||
phy->dev = dev;
|
||||
phy_set_drvdata(generic_phy, phy);
|
||||
platform_set_drvdata(pdev, phy);
|
||||
|
||||
phy->cfg_clk = devm_clk_get(dev, "cfg");
|
||||
if (IS_ERR(phy->cfg_clk)) {
|
||||
dev_err(dev, "Failed to get sata cfg clock\n");
|
||||
return PTR_ERR(phy->cfg_clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(phy->cfg_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
clk_disable_unprepare(phy->cfg_clk);
|
||||
dev_err(dev, "%s: failed to register phy\n", __func__);
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_ipq806x_sata_phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_ipq806x_sata_phy *phy = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable_unprepare(phy->cfg_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_ipq806x_sata_phy_of_match[] = {
|
||||
{ .compatible = "qcom,ipq806x-sata-phy" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qcom_ipq806x_sata_phy_of_match);
|
||||
|
||||
static struct platform_driver qcom_ipq806x_sata_phy_driver = {
|
||||
.probe = qcom_ipq806x_sata_phy_probe,
|
||||
.remove = qcom_ipq806x_sata_phy_remove,
|
||||
.driver = {
|
||||
.name = "qcom-ipq806x-sata-phy",
|
||||
.of_match_table = qcom_ipq806x_sata_phy_of_match,
|
||||
}
|
||||
};
|
||||
module_platform_driver(qcom_ipq806x_sata_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QCOM IPQ806x SATA PHY driver");
|
||||
MODULE_LICENSE("GPL v2");
|
341
drivers/phy/phy-rcar-gen2.c
Normal file
341
drivers/phy/phy-rcar-gen2.c
Normal file
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Renesas R-Car Gen2 PHY driver
|
||||
*
|
||||
* Copyright (C) 2014 Renesas Solutions Corp.
|
||||
* Copyright (C) 2014 Cogent Embedded, Inc.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include <asm/cmpxchg.h>
|
||||
|
||||
#define USBHS_LPSTS 0x02
|
||||
#define USBHS_UGCTRL 0x80
|
||||
#define USBHS_UGCTRL2 0x84
|
||||
#define USBHS_UGSTS 0x88 /* The manuals have 0x90 */
|
||||
|
||||
/* Low Power Status register (LPSTS) */
|
||||
#define USBHS_LPSTS_SUSPM 0x4000
|
||||
|
||||
/* USB General control register (UGCTRL) */
|
||||
#define USBHS_UGCTRL_CONNECT 0x00000004
|
||||
#define USBHS_UGCTRL_PLLRESET 0x00000001
|
||||
|
||||
/* USB General control register 2 (UGCTRL2) */
|
||||
#define USBHS_UGCTRL2_USB2SEL 0x80000000
|
||||
#define USBHS_UGCTRL2_USB2SEL_PCI 0x00000000
|
||||
#define USBHS_UGCTRL2_USB2SEL_USB30 0x80000000
|
||||
#define USBHS_UGCTRL2_USB0SEL 0x00000030
|
||||
#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010
|
||||
#define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030
|
||||
|
||||
/* USB General status register (UGSTS) */
|
||||
#define USBHS_UGSTS_LOCK 0x00000300 /* The manuals have 0x3 */
|
||||
|
||||
#define PHYS_PER_CHANNEL 2
|
||||
|
||||
struct rcar_gen2_phy {
|
||||
struct phy *phy;
|
||||
struct rcar_gen2_channel *channel;
|
||||
int number;
|
||||
u32 select_value;
|
||||
};
|
||||
|
||||
struct rcar_gen2_channel {
|
||||
struct device_node *of_node;
|
||||
struct rcar_gen2_phy_driver *drv;
|
||||
struct rcar_gen2_phy phys[PHYS_PER_CHANNEL];
|
||||
int selected_phy;
|
||||
u32 select_mask;
|
||||
};
|
||||
|
||||
struct rcar_gen2_phy_driver {
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
spinlock_t lock;
|
||||
int num_channels;
|
||||
struct rcar_gen2_channel *channels;
|
||||
};
|
||||
|
||||
static int rcar_gen2_phy_init(struct phy *p)
|
||||
{
|
||||
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
|
||||
struct rcar_gen2_channel *channel = phy->channel;
|
||||
struct rcar_gen2_phy_driver *drv = channel->drv;
|
||||
unsigned long flags;
|
||||
u32 ugctrl2;
|
||||
|
||||
/*
|
||||
* Try to acquire exclusive access to PHY. The first driver calling
|
||||
* phy_init() on a given channel wins, and all attempts to use another
|
||||
* PHY on this channel will fail until phy_exit() is called by the first
|
||||
* driver. Achieving this with cmpxcgh() should be SMP-safe.
|
||||
*/
|
||||
if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
|
||||
return -EBUSY;
|
||||
|
||||
clk_prepare_enable(drv->clk);
|
||||
|
||||
spin_lock_irqsave(&drv->lock, flags);
|
||||
ugctrl2 = readl(drv->base + USBHS_UGCTRL2);
|
||||
ugctrl2 &= ~channel->select_mask;
|
||||
ugctrl2 |= phy->select_value;
|
||||
writel(ugctrl2, drv->base + USBHS_UGCTRL2);
|
||||
spin_unlock_irqrestore(&drv->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcar_gen2_phy_exit(struct phy *p)
|
||||
{
|
||||
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
|
||||
struct rcar_gen2_channel *channel = phy->channel;
|
||||
|
||||
clk_disable_unprepare(channel->drv->clk);
|
||||
|
||||
channel->selected_phy = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcar_gen2_phy_power_on(struct phy *p)
|
||||
{
|
||||
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
|
||||
struct rcar_gen2_phy_driver *drv = phy->channel->drv;
|
||||
void __iomem *base = drv->base;
|
||||
unsigned long flags;
|
||||
u32 value;
|
||||
int err = 0, i;
|
||||
|
||||
/* Skip if it's not USBHS */
|
||||
if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&drv->lock, flags);
|
||||
|
||||
/* Power on USBHS PHY */
|
||||
value = readl(base + USBHS_UGCTRL);
|
||||
value &= ~USBHS_UGCTRL_PLLRESET;
|
||||
writel(value, base + USBHS_UGCTRL);
|
||||
|
||||
value = readw(base + USBHS_LPSTS);
|
||||
value |= USBHS_LPSTS_SUSPM;
|
||||
writew(value, base + USBHS_LPSTS);
|
||||
|
||||
for (i = 0; i < 20; i++) {
|
||||
value = readl(base + USBHS_UGSTS);
|
||||
if ((value & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) {
|
||||
value = readl(base + USBHS_UGCTRL);
|
||||
value |= USBHS_UGCTRL_CONNECT;
|
||||
writel(value, base + USBHS_UGCTRL);
|
||||
goto out;
|
||||
}
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
/* Timed out waiting for the PLL lock */
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&drv->lock, flags);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rcar_gen2_phy_power_off(struct phy *p)
|
||||
{
|
||||
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
|
||||
struct rcar_gen2_phy_driver *drv = phy->channel->drv;
|
||||
void __iomem *base = drv->base;
|
||||
unsigned long flags;
|
||||
u32 value;
|
||||
|
||||
/* Skip if it's not USBHS */
|
||||
if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&drv->lock, flags);
|
||||
|
||||
/* Power off USBHS PHY */
|
||||
value = readl(base + USBHS_UGCTRL);
|
||||
value &= ~USBHS_UGCTRL_CONNECT;
|
||||
writel(value, base + USBHS_UGCTRL);
|
||||
|
||||
value = readw(base + USBHS_LPSTS);
|
||||
value &= ~USBHS_LPSTS_SUSPM;
|
||||
writew(value, base + USBHS_LPSTS);
|
||||
|
||||
value = readl(base + USBHS_UGCTRL);
|
||||
value |= USBHS_UGCTRL_PLLRESET;
|
||||
writel(value, base + USBHS_UGCTRL);
|
||||
|
||||
spin_unlock_irqrestore(&drv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops rcar_gen2_phy_ops = {
|
||||
.init = rcar_gen2_phy_init,
|
||||
.exit = rcar_gen2_phy_exit,
|
||||
.power_on = rcar_gen2_phy_power_on,
|
||||
.power_off = rcar_gen2_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct of_device_id rcar_gen2_phy_match_table[] = {
|
||||
{ .compatible = "renesas,usb-phy-r8a7790" },
|
||||
{ .compatible = "renesas,usb-phy-r8a7791" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table);
|
||||
|
||||
static struct phy *rcar_gen2_phy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct rcar_gen2_phy_driver *drv;
|
||||
struct device_node *np = args->np;
|
||||
int i;
|
||||
|
||||
if (!of_device_is_available(np)) {
|
||||
dev_warn(dev, "Requested PHY is disabled\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
drv = dev_get_drvdata(dev);
|
||||
if (!drv)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
for (i = 0; i < drv->num_channels; i++) {
|
||||
if (np == drv->channels[i].of_node)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= drv->num_channels || args->args[0] >= 2)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return drv->channels[i].phys[args->args[0]].phy;
|
||||
}
|
||||
|
||||
static const u32 select_mask[] = {
|
||||
[0] = USBHS_UGCTRL2_USB0SEL,
|
||||
[2] = USBHS_UGCTRL2_USB2SEL,
|
||||
};
|
||||
|
||||
static const u32 select_value[][PHYS_PER_CHANNEL] = {
|
||||
[0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB },
|
||||
[2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
|
||||
};
|
||||
|
||||
static int rcar_gen2_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rcar_gen2_phy_driver *drv;
|
||||
struct phy_provider *provider;
|
||||
struct device_node *np;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
int i = 0;
|
||||
|
||||
if (!dev->of_node) {
|
||||
dev_err(dev,
|
||||
"This driver is required to be instantiated from device tree\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk = devm_clk_get(dev, "usbhs");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "Can't get USBHS clock\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
|
||||
if (!drv)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&drv->lock);
|
||||
|
||||
drv->clk = clk;
|
||||
drv->base = base;
|
||||
|
||||
drv->num_channels = of_get_child_count(dev->of_node);
|
||||
drv->channels = devm_kcalloc(dev, drv->num_channels,
|
||||
sizeof(struct rcar_gen2_channel),
|
||||
GFP_KERNEL);
|
||||
if (!drv->channels)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_child_of_node(dev->of_node, np) {
|
||||
struct rcar_gen2_channel *channel = drv->channels + i;
|
||||
u32 channel_num;
|
||||
int error, n;
|
||||
|
||||
channel->of_node = np;
|
||||
channel->drv = drv;
|
||||
channel->selected_phy = -1;
|
||||
|
||||
error = of_property_read_u32(np, "reg", &channel_num);
|
||||
if (error || channel_num > 2) {
|
||||
dev_err(dev, "Invalid \"reg\" property\n");
|
||||
return error;
|
||||
}
|
||||
channel->select_mask = select_mask[channel_num];
|
||||
|
||||
for (n = 0; n < PHYS_PER_CHANNEL; n++) {
|
||||
struct rcar_gen2_phy *phy = &channel->phys[n];
|
||||
|
||||
phy->channel = channel;
|
||||
phy->number = n;
|
||||
phy->select_value = select_value[channel_num][n];
|
||||
|
||||
phy->phy = devm_phy_create(dev, NULL,
|
||||
&rcar_gen2_phy_ops, NULL);
|
||||
if (IS_ERR(phy->phy)) {
|
||||
dev_err(dev, "Failed to create PHY\n");
|
||||
return PTR_ERR(phy->phy);
|
||||
}
|
||||
phy_set_drvdata(phy->phy, phy);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
provider = devm_of_phy_provider_register(dev, rcar_gen2_phy_xlate);
|
||||
if (IS_ERR(provider)) {
|
||||
dev_err(dev, "Failed to register PHY provider\n");
|
||||
return PTR_ERR(provider);
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, drv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rcar_gen2_phy_driver = {
|
||||
.driver = {
|
||||
.name = "phy_rcar_gen2",
|
||||
.of_match_table = rcar_gen2_phy_match_table,
|
||||
},
|
||||
.probe = rcar_gen2_phy_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(rcar_gen2_phy_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Renesas R-Car Gen2 PHY");
|
||||
MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>");
|
187
drivers/phy/phy-s5pv210-usb2.c
Normal file
187
drivers/phy/phy-s5pv210-usb2.c
Normal file
|
@ -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,
|
||||
};
|
144
drivers/phy/phy-samsung-usb-cal.h
Normal file
144
drivers/phy/phy-samsung-usb-cal.h
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Sung-Hyun Na <sunghyun.na@samsung.com>
|
||||
*
|
||||
* USBPHY configuration definitions for Samsung USB PHY CAL
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __PHY_SAMSUNG_USB_FW_CAL_H__
|
||||
#define __PHY_SAMSUNG_USB_FW_CAL_H__
|
||||
|
||||
#define EXYNOS_USBCON_VER_01_0_0 0x0100 /* Istor */
|
||||
#define EXYNOS_USBCON_VER_01_0_1 0x0101 /* JF 3.0 */
|
||||
#define EXYNOS_USBCON_VER_01_MAX 0x01FF
|
||||
|
||||
#define EXYNOS_USBCON_VER_02_0_0 0x0200 /* Insel-D, Island */
|
||||
#define EXYNOS_USBCON_VER_02_0_1 0x0201 /* JF EVT0 2.0 Host */
|
||||
#define EXYNOS_USBCON_VER_02_1_0 0x0210
|
||||
#define EXYNOS_USBCON_VER_02_1_1 0x0211 /* JF EVT1 2.0 Host */
|
||||
#define EXYNOS_USBCON_VER_02_MAX 0x02FF
|
||||
|
||||
#define EXYNOS_USBCON_VER_F2_0_0 0xF200
|
||||
#define EXYNOS_USBCON_VER_F2_MAX 0xF2FF
|
||||
|
||||
enum exynos_usbphy_mode {
|
||||
USBPHY_MODE_DEV = 0,
|
||||
USBPHY_MODE_HOST = 1,
|
||||
|
||||
/* usb phy for uart bypass mode */
|
||||
USBPHY_MODE_BYPASS = 0x10,
|
||||
};
|
||||
|
||||
enum exynos_usbphy_refclk {
|
||||
USBPHY_REFCLK_DIFF_100MHZ = 0x80 | 0x27,
|
||||
USBPHY_REFCLK_DIFF_26MHZ = 0x80 | 0x02,
|
||||
USBPHY_REFCLK_DIFF_24MHZ = 0x80 | 0x2a,
|
||||
|
||||
USBPHY_REFCLK_EXT_50MHZ = 0x07,
|
||||
USBPHY_REFCLK_EXT_24MHZ = 0x05,
|
||||
USBPHY_REFCLK_EXT_12MHZ = 0x02,
|
||||
};
|
||||
|
||||
enum exynos_usbphy_refsel {
|
||||
USBPHY_REFSEL_CLKCORE = 0x2,
|
||||
USBPHY_REFSEL_EXT_OSC = 0x1,
|
||||
USBPHY_REFSEL_EXT_XTAL = 0x0,
|
||||
|
||||
USBPHY_REFSEL_DIFF_PAD = 0x6,
|
||||
USBPHY_REFSEL_DIFF_INTERNAL = 0x4,
|
||||
USBPHY_REFSEL_DIFF_SINGLE = 0x3,
|
||||
};
|
||||
|
||||
enum exynos_usbphy_utmi {
|
||||
USBPHY_UTMI_FREECLOCK, USBPHY_UTMI_PHYCLOCK,
|
||||
};
|
||||
|
||||
/* HS PHY tune parameter */
|
||||
struct exynos_usbphy_hs_tune {
|
||||
u8 tx_vref;
|
||||
u8 tx_pre_emp;
|
||||
u8 tx_pre_emp_puls;
|
||||
u8 tx_res;
|
||||
u8 tx_rise;
|
||||
u8 tx_hsxv;
|
||||
u8 tx_fsls;
|
||||
u8 rx_sqrx;
|
||||
u8 compdis;
|
||||
u8 otg;
|
||||
bool enable_user_imp;
|
||||
u8 user_imp_value;
|
||||
enum exynos_usbphy_utmi utmi_clk;
|
||||
};
|
||||
|
||||
/* SS PHY tune parameter */
|
||||
struct exynos_usbphy_ss_tune {
|
||||
/* TX Swing Level*/
|
||||
u8 tx_boost_level;
|
||||
u8 tx_swing_level;
|
||||
u8 tx_swing_full;
|
||||
u8 tx_swing_low;
|
||||
/* TX De-Emphasis */
|
||||
u8 tx_deemphasis_mode;
|
||||
u8 tx_deemphasis_3p5db;
|
||||
u8 tx_deemphasis_6db;
|
||||
/* SSC Operation*/
|
||||
u8 enable_ssc;
|
||||
u8 ssc_range;
|
||||
/* Loss-of-Signal detector threshold level */
|
||||
u8 los_bias;
|
||||
/* Loss-of-Signal mask width */
|
||||
u16 los_mask_val;
|
||||
/* RX equalizer mode */
|
||||
u8 enable_fixed_rxeq_mode;
|
||||
u8 fix_rxeq_value;
|
||||
|
||||
u8 set_crport_level_en;
|
||||
u8 set_crport_mpll_charge_pump;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos_usbphy_info : USBPHY information to share USBPHY CAL code
|
||||
* @version: PHY controller version
|
||||
* 0x0100 - for EXYNOS_USB3 : EXYNOS7420, EXYNOS7890
|
||||
* 0x0101 - EXYNOS8890
|
||||
* 0x0200 - for EXYNOS_USB2 : EXYNOS7580, EXYNOS3475
|
||||
* 0x0210 - EXYNOS8890_EVT1
|
||||
* 0xF200 - for EXT : EXYNOS7420_HSIC
|
||||
* @refclk: reference clock frequency for USBPHY
|
||||
* @refsrc: reference clock source path for USBPHY
|
||||
* @use_io_for_ovc: use over-current notification io for USBLINK
|
||||
* @regs_base: base address of PHY control register *
|
||||
*/
|
||||
|
||||
struct exynos_usbphy_info {
|
||||
u32 version;
|
||||
enum exynos_usbphy_refclk refclk;
|
||||
enum exynos_usbphy_refsel refsel;
|
||||
|
||||
bool use_io_for_ovc;
|
||||
bool common_block_enable;
|
||||
bool not_used_vbus_pad;
|
||||
|
||||
void __iomem *regs_base;
|
||||
|
||||
/* HS PHY tune parameter */
|
||||
struct exynos_usbphy_hs_tune *hs_tune;
|
||||
|
||||
/* SS PHY tune parameter */
|
||||
struct exynos_usbphy_ss_tune *ss_tune;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* __PHY_SAMSUNG_USB_FW_CAL_H__ */
|
241
drivers/phy/phy-samsung-usb2.c
Normal file
241
drivers/phy/phy-samsung-usb2.c
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Samsung SoC USB 1.1/2.0 PHY driver
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
||||
* Author: Kamil Debski <k.debski@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include "phy-samsung-usb2.h"
|
||||
|
||||
static int samsung_usb2_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy);
|
||||
struct samsung_usb2_phy_driver *drv = inst->drv;
|
||||
int ret;
|
||||
|
||||
dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n",
|
||||
inst->cfg->label);
|
||||
ret = clk_prepare_enable(drv->clk);
|
||||
if (ret)
|
||||
goto err_main_clk;
|
||||
ret = clk_prepare_enable(drv->ref_clk);
|
||||
if (ret)
|
||||
goto err_instance_clk;
|
||||
if (inst->cfg->power_on) {
|
||||
spin_lock(&drv->lock);
|
||||
ret = inst->cfg->power_on(inst);
|
||||
spin_unlock(&drv->lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_instance_clk:
|
||||
clk_disable_unprepare(drv->clk);
|
||||
err_main_clk:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int samsung_usb2_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy);
|
||||
struct samsung_usb2_phy_driver *drv = inst->drv;
|
||||
int ret = 0;
|
||||
|
||||
dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n",
|
||||
inst->cfg->label);
|
||||
if (inst->cfg->power_off) {
|
||||
spin_lock(&drv->lock);
|
||||
ret = inst->cfg->power_off(inst);
|
||||
spin_unlock(&drv->lock);
|
||||
}
|
||||
clk_disable_unprepare(drv->ref_clk);
|
||||
clk_disable_unprepare(drv->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct phy_ops samsung_usb2_phy_ops = {
|
||||
.power_on = samsung_usb2_phy_power_on,
|
||||
.power_off = samsung_usb2_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct phy *samsung_usb2_phy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct samsung_usb2_phy_driver *drv;
|
||||
|
||||
drv = dev_get_drvdata(dev);
|
||||
if (!drv)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (WARN_ON(args->args[0] >= drv->cfg->num_phys))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return drv->instances[args->args[0]].phy;
|
||||
}
|
||||
|
||||
static const struct of_device_id samsung_usb2_phy_of_match[] = {
|
||||
#ifdef CONFIG_PHY_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;
|
||||
}
|
||||
|
||||
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,
|
||||
NULL);
|
||||
if (IS_ERR(p->phy)) {
|
||||
dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n",
|
||||
label);
|
||||
return PTR_ERR(p->phy);
|
||||
}
|
||||
|
||||
p->cfg = &drv->cfg->phys[i];
|
||||
p->drv = drv;
|
||||
phy_set_bus_width(p->phy, 8);
|
||||
phy_set_drvdata(p->phy, p);
|
||||
}
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev,
|
||||
samsung_usb2_phy_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
dev_err(drv->dev, "Failed to register phy provider\n");
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver samsung_usb2_phy_driver = {
|
||||
.probe = samsung_usb2_phy_probe,
|
||||
.driver = {
|
||||
.of_match_table = samsung_usb2_phy_of_match,
|
||||
.name = "samsung-usb2-phy",
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
71
drivers/phy/phy-samsung-usb2.h
Normal file
71
drivers/phy/phy-samsung-usb2.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Samsung SoC USB 1.1/2.0 PHY driver
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
||||
* Author: Kamil Debski <k.debski@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _PHY_EXYNOS_USB2_H
|
||||
#define _PHY_EXYNOS_USB2_H
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define KHZ 1000
|
||||
#define MHZ (KHZ * KHZ)
|
||||
|
||||
struct samsung_usb2_phy_driver;
|
||||
struct samsung_usb2_phy_instance;
|
||||
struct samsung_usb2_phy_config;
|
||||
|
||||
struct samsung_usb2_phy_instance {
|
||||
const struct samsung_usb2_common_phy *cfg;
|
||||
struct phy *phy;
|
||||
struct samsung_usb2_phy_driver *drv;
|
||||
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;
|
||||
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
|
683
drivers/phy/phy-samsung-usb3-cal.c
Normal file
683
drivers/phy/phy-samsung-usb3-cal.c
Normal file
|
@ -0,0 +1,683 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Sung-Hyun Na <sunghyun.na@samsung.com>
|
||||
* Author: Minho Lee <minho55.lee@samsung.com>
|
||||
*
|
||||
* Chip Abstraction Layer for USB PHY
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __EXCITE__
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#endif
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "phy-samsung-usb-cal.h"
|
||||
#include "phy-samsung-usb3-cal.h"
|
||||
|
||||
enum exynos_usbcon_cr {
|
||||
USBCON_CR_ADDR = 0,
|
||||
USBCON_CR_DATA = 1,
|
||||
USBCON_CR_READ = 18,
|
||||
USBCON_CR_WRITE = 19,
|
||||
};
|
||||
|
||||
static u16 samsung_exynos_cal_cr_access(struct exynos_usbphy_info *usbphy_info,
|
||||
enum exynos_usbcon_cr cr_bit, u16 data)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 phyreg0;
|
||||
u32 phyreg1;
|
||||
u32 loop;
|
||||
u32 loop_cnt;
|
||||
|
||||
/* Clear CR port register */
|
||||
phyreg0 = readl(regs_base + EXYNOS_USBCON_PHYREG0);
|
||||
phyreg0 &= ~(0xfffff);
|
||||
writel(phyreg0, regs_base + EXYNOS_USBCON_PHYREG0);
|
||||
|
||||
/* Set Data for cr port */
|
||||
phyreg0 &= ~PHYREG0_CR_DATA_MASK;
|
||||
phyreg0 |= PHYREG0_CR_DATA_IN(data);
|
||||
writel(phyreg0, regs_base + EXYNOS_USBCON_PHYREG0);
|
||||
|
||||
if (cr_bit == USBCON_CR_ADDR)
|
||||
loop = 1;
|
||||
else
|
||||
loop = 2;
|
||||
|
||||
for (loop_cnt = 0; loop_cnt < loop; loop_cnt++) {
|
||||
u32 trigger_bit = 0;
|
||||
u32 handshake_cnt = 2;
|
||||
/* Trigger cr port */
|
||||
if (cr_bit == USBCON_CR_ADDR)
|
||||
trigger_bit = PHYREG0_CR_CR_CAP_ADDR;
|
||||
else {
|
||||
if (loop_cnt == 0)
|
||||
trigger_bit = PHYREG0_CR_CR_CAP_DATA;
|
||||
else {
|
||||
if (cr_bit == USBCON_CR_READ)
|
||||
trigger_bit = PHYREG0_CR_READ;
|
||||
else
|
||||
trigger_bit = PHYREG0_CR_WRITE;
|
||||
}
|
||||
}
|
||||
/* Handshake Procedure */
|
||||
do {
|
||||
u32 usec = 100;
|
||||
if (handshake_cnt == 2)
|
||||
phyreg0 |= trigger_bit;
|
||||
else
|
||||
phyreg0 &= ~trigger_bit;
|
||||
writel(phyreg0, regs_base + EXYNOS_USBCON_PHYREG0);
|
||||
|
||||
/* Handshake */
|
||||
do {
|
||||
phyreg1 = readl(regs_base + EXYNOS_USBCON_PHYREG1);
|
||||
if ((handshake_cnt == 2)
|
||||
&& (phyreg1 & PHYREG1_CR_ACK))
|
||||
break;
|
||||
else if ((handshake_cnt == 1)
|
||||
&& !(phyreg1 & PHYREG1_CR_ACK))
|
||||
break;
|
||||
|
||||
udelay(1);
|
||||
} while (usec-- > 0);
|
||||
|
||||
if (!usec)
|
||||
pr_err("CRPORT handshake timeout1 (0x%08x)\n",
|
||||
phyreg0);
|
||||
|
||||
udelay(5);
|
||||
handshake_cnt--;
|
||||
} while (handshake_cnt != 0);
|
||||
udelay(50);
|
||||
}
|
||||
return (u16) ((phyreg1 & PHYREG1_CR_DATA_OUT_MASK) >> 1);
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_cr_write(struct exynos_usbphy_info *usbphy_info,
|
||||
u16 addr, u16 data)
|
||||
{
|
||||
samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_ADDR, addr);
|
||||
samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_WRITE, data);
|
||||
}
|
||||
|
||||
u16 samsung_exynos_cal_cr_read(struct exynos_usbphy_info *usbphy_info, u16 addr)
|
||||
{
|
||||
samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_ADDR, addr);
|
||||
return samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_READ, 0);
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_enable(struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 version = usbphy_info->version;
|
||||
enum exynos_usbphy_refclk refclkfreq = usbphy_info->refclk;
|
||||
u32 phyutmi;
|
||||
u32 phyclkrst;
|
||||
u32 phyparam0;
|
||||
u32 phyreg0;
|
||||
u32 phypipe;
|
||||
u32 linkport;
|
||||
|
||||
/* Set force q-channel */
|
||||
if ((version & 0xf) >= 0x01) {
|
||||
u32 phy_resume;
|
||||
|
||||
/* WA for Q-channel: disable all q-act from usb */
|
||||
phy_resume = readl(regs_base + EXYNOS_USBCON_PHYRESUME);
|
||||
phy_resume |= PHYRESUME_DIS_ID0_QACT;
|
||||
phy_resume |= PHYRESUME_DIS_VBUSVALID_QACT;
|
||||
phy_resume |= PHYRESUME_DIS_BVALID_QACT;
|
||||
phy_resume |= PHYRESUME_DIS_LINKGATE_QACT;
|
||||
phy_resume &= ~PHYRESUME_FORCE_QACT;
|
||||
udelay(500);
|
||||
writel(phy_resume, regs_base + EXYNOS_USBCON_PHYRESUME);
|
||||
|
||||
udelay(500);
|
||||
|
||||
phy_resume = readl(regs_base + EXYNOS_USBCON_PHYRESUME);
|
||||
phy_resume |= PHYRESUME_FORCE_QACT;
|
||||
udelay(500);
|
||||
writel(phy_resume, regs_base + EXYNOS_USBCON_PHYRESUME);
|
||||
}
|
||||
|
||||
/* set phy clock & control HS phy */
|
||||
phyclkrst = readl(regs_base + EXYNOS_USBCON_PHYCLKRST);
|
||||
|
||||
/* assert port_reset */
|
||||
phyclkrst |= PHYCLKRST_PORTRESET;
|
||||
|
||||
/* Select Reference clock source path */
|
||||
phyclkrst &= ~PHYCLKRST_REFCLKSEL_MASK;
|
||||
phyclkrst |= PHYCLKRST_REFCLKSEL(usbphy_info->refsel);
|
||||
|
||||
/* Select ref clk */
|
||||
phyclkrst &= ~PHYCLKRST_FSEL_MASK;
|
||||
phyclkrst |= PHYCLKRST_FSEL(refclkfreq & 0x3f);
|
||||
|
||||
/* Enable Common Block in suspend/sleep mode */
|
||||
phyclkrst &= ~PHYCLKRST_COMMONONN;
|
||||
|
||||
/* Additional control for 3.0 PHY */
|
||||
if ((EXYNOS_USBCON_VER_01_0_0 <= version)
|
||||
&& (version <= EXYNOS_USBCON_VER_01_MAX)) {
|
||||
if (version < EXYNOS_USBCON_VER_01_0_1)
|
||||
phyclkrst |= PHYCLKRST_COMMONONN;
|
||||
|
||||
/* Disable Common block control by link */
|
||||
phyclkrst &= ~PHYCLKRST_EN_UTMISUSPEND;
|
||||
|
||||
/* Digital Supply Mode : normal operating mode */
|
||||
phyclkrst |= PHYCLKRST_RETENABLEN;
|
||||
|
||||
/* ref. clock enable for ss function */
|
||||
phyclkrst |= PHYCLKRST_REF_SSP_EN;
|
||||
|
||||
phyparam0 = readl(regs_base + EXYNOS_USBCON_PHYPARAM0);
|
||||
if (usbphy_info->refsel == USBPHY_REFSEL_DIFF_PAD)
|
||||
phyparam0 |= PHYPARAM0_REF_USE_PAD;
|
||||
else
|
||||
phyparam0 &= ~PHYPARAM0_REF_USE_PAD;
|
||||
writel(phyparam0, regs_base + EXYNOS_USBCON_PHYPARAM0);
|
||||
|
||||
if (version >= EXYNOS_USBCON_VER_01_0_1)
|
||||
phyreg0 = readl(regs_base + EXYNOS_USBCON_PHYREG0);
|
||||
|
||||
switch (refclkfreq) {
|
||||
case USBPHY_REFCLK_DIFF_100MHZ:
|
||||
phyclkrst &= ~PHYCLKRST_MPLL_MULTIPLIER_MASK;
|
||||
phyclkrst |= PHYCLKRST_MPLL_MULTIPLIER(0x00);
|
||||
phyclkrst &= ~PHYCLKRST_REF_CLKDIV2;
|
||||
if (version == EXYNOS_USBCON_VER_01_0_1) {
|
||||
phyreg0 &= ~PHYREG0_SSC_REFCLKSEL_MASK;
|
||||
phyreg0 |= PHYREG0_SSC_REFCLKSEL(0x00);
|
||||
} else {
|
||||
phyclkrst &= ~PHYCLKRST_SSC_REFCLKSEL_MASK;
|
||||
phyclkrst |= PHYCLKRST_SSC_REFCLKSEL(0x00);
|
||||
}
|
||||
break;
|
||||
case USBPHY_REFCLK_DIFF_26MHZ:
|
||||
phyclkrst &= ~PHYCLKRST_MPLL_MULTIPLIER_MASK;
|
||||
phyclkrst |= PHYCLKRST_MPLL_MULTIPLIER(0x60);
|
||||
phyclkrst &= ~PHYCLKRST_REF_CLKDIV2;
|
||||
if (version == EXYNOS_USBCON_VER_01_0_1) {
|
||||
phyreg0 &= ~PHYREG0_SSC_REFCLKSEL_MASK;
|
||||
phyreg0 |= PHYREG0_SSC_REFCLKSEL(0x108);
|
||||
} else {
|
||||
phyclkrst &= ~PHYCLKRST_SSC_REFCLKSEL_MASK;
|
||||
phyclkrst |= PHYCLKRST_SSC_REFCLKSEL(0x108);
|
||||
}
|
||||
break;
|
||||
case USBPHY_REFCLK_DIFF_24MHZ:
|
||||
case USBPHY_REFCLK_EXT_24MHZ:
|
||||
phyclkrst &= ~PHYCLKRST_MPLL_MULTIPLIER_MASK;
|
||||
phyclkrst |= PHYCLKRST_MPLL_MULTIPLIER(0x00);
|
||||
phyclkrst &= ~PHYCLKRST_REF_CLKDIV2;
|
||||
if (version != EXYNOS_USBCON_VER_01_0_1) {
|
||||
phyclkrst &= ~PHYCLKRST_SSC_REFCLKSEL_MASK;
|
||||
phyclkrst |= PHYCLKRST_SSC_REFCLKSEL(0x88);
|
||||
} else {
|
||||
phyreg0 &= ~PHYREG0_SSC_REFCLKSEL_MASK;
|
||||
phyreg0 |= PHYREG0_SSC_REFCLKSEL(0x88);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* SSC Enable */
|
||||
phyclkrst |= PHYCLKRST_SSC_EN;
|
||||
if (version != EXYNOS_USBCON_VER_01_0_1) {
|
||||
phyclkrst &= ~PHYCLKRST_SSC_RANGE_MASK;
|
||||
phyclkrst |= PHYCLKRST_SSC_RANGE(0x0);
|
||||
} else {
|
||||
phyreg0 &= ~PHYREG0_SSC_RANGE_MASK;
|
||||
phyreg0 |= PHYREG0_SSC_RAMGE(0x0);
|
||||
}
|
||||
|
||||
/* Select UTMI CLOCK 0 : PHY CLOCK, 1 : FREE CLOCK */
|
||||
phypipe = readl(regs_base + EXYNOS_USBCON_PHYPIPE);
|
||||
phypipe |= PHY_CLOCK_SEL;
|
||||
writel(phypipe, regs_base + EXYNOS_USBCON_PHYPIPE);
|
||||
|
||||
if (version >= EXYNOS_USBCON_VER_01_0_1)
|
||||
writel(phyreg0, regs_base + EXYNOS_USBCON_PHYREG0);
|
||||
} else if ((EXYNOS_USBCON_VER_02_0_0 <= version)
|
||||
&& (version <= EXYNOS_USBCON_VER_02_MAX)) {
|
||||
u32 hsphyplltune = readl(
|
||||
regs_base + EXYNOS_USBCON_HSPHYPLLTUNE);
|
||||
|
||||
if ((version & 0xf0) >= 0x10) {
|
||||
/* Disable Common block control by link */
|
||||
phyclkrst |= PHYCLKRST_EN_UTMISUSPEND;
|
||||
phyclkrst |= PHYCLKRST_COMMONONN;
|
||||
} else {
|
||||
phyclkrst |= PHYCLKRST_EN_UTMISUSPEND;
|
||||
phyclkrst |= PHYCLKRST_COMMONONN;
|
||||
}
|
||||
|
||||
/* Change PHY PLL Tune value */
|
||||
if (refclkfreq == USBPHY_REFCLK_EXT_24MHZ)
|
||||
hsphyplltune |= HSPHYPLLTUNE_PLL_B_TUNE;
|
||||
else
|
||||
hsphyplltune &= ~HSPHYPLLTUNE_PLL_B_TUNE;
|
||||
hsphyplltune |= HSPHYPLLTUNE_PLL_P_TUNE(0xe);
|
||||
writel(hsphyplltune, regs_base + EXYNOS_USBCON_HSPHYPLLTUNE);
|
||||
}
|
||||
writel(phyclkrst, regs_base + EXYNOS_USBCON_PHYCLKRST);
|
||||
udelay(10);
|
||||
|
||||
phyclkrst &= ~PHYCLKRST_PORTRESET;
|
||||
writel(phyclkrst, regs_base + EXYNOS_USBCON_PHYCLKRST);
|
||||
|
||||
if ((EXYNOS_USBCON_VER_01_0_0 <= version)
|
||||
&& (version <= EXYNOS_USBCON_VER_01_MAX)) {
|
||||
u32 phytest;
|
||||
|
||||
phytest = readl(regs_base + EXYNOS_USBCON_PHYTEST);
|
||||
phytest &= ~PHYTEST_POWERDOWN_HSP;
|
||||
phytest &= ~PHYTEST_POWERDOWN_SSP;
|
||||
writel(phytest, regs_base + EXYNOS_USBCON_PHYTEST);
|
||||
} else if (version >= EXYNOS_USBCON_VER_02_0_0
|
||||
&& version <= EXYNOS_USBCON_VER_02_MAX) {
|
||||
u32 hsphyctrl;
|
||||
|
||||
hsphyctrl = readl(regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
hsphyctrl &= ~HSPHYCTRL_SIDDQ;
|
||||
hsphyctrl &= ~HSPHYCTRL_PHYSWRST;
|
||||
hsphyctrl &= ~HSPHYCTRL_PHYSWRSTALL;
|
||||
writel(hsphyctrl, regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
}
|
||||
udelay(500);
|
||||
|
||||
|
||||
/* Set VBUSVALID signal if VBUS pad is not used */
|
||||
if (usbphy_info->not_used_vbus_pad) {
|
||||
u32 linksystem;
|
||||
|
||||
linksystem = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
linksystem |= LINKSYSTEM_FORCE_BVALID;
|
||||
linksystem |= LINKSYSTEM_FORCE_VBUSVALID;
|
||||
writel(linksystem, regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
}
|
||||
|
||||
/* release force_sleep & force_suspend */
|
||||
phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
phyutmi &= ~PHYUTMI_FORCESLEEP;
|
||||
phyutmi &= ~PHYUTMI_FORCESUSPEND;
|
||||
|
||||
/* DP/DM Pull Down Control */
|
||||
phyutmi &= ~PHYUTMI_DMPULLDOWN;
|
||||
phyutmi &= ~PHYUTMI_DPPULLDOWN;
|
||||
|
||||
/* Set DP-Pull up if VBUS pad is not used */
|
||||
if (usbphy_info->not_used_vbus_pad) {
|
||||
phyutmi |= PHYUTMI_VBUSVLDEXTSEL;
|
||||
phyutmi |= PHYUTMI_VBUSVLDEXT;
|
||||
}
|
||||
|
||||
/* disable OTG block and VBUS valid comparator */
|
||||
phyutmi &= ~PHYUTMI_DRVVBUS;
|
||||
phyutmi |= PHYUTMI_OTGDISABLE;
|
||||
writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
|
||||
/* OVC io usage */
|
||||
linkport = readl(regs_base + EXYNOS_USBCON_LINKPORT);
|
||||
if (usbphy_info->use_io_for_ovc) {
|
||||
linkport &= ~LINKPORT_HOST_PORT_OVCR_U3_SEL;
|
||||
linkport &= ~LINKPORT_HOST_PORT_OVCR_U2_SEL;
|
||||
} else {
|
||||
linkport |= LINKPORT_HOST_PORT_OVCR_U3_SEL;
|
||||
linkport |= LINKPORT_HOST_PORT_OVCR_U2_SEL;
|
||||
}
|
||||
writel(linkport, regs_base + EXYNOS_USBCON_LINKPORT);
|
||||
|
||||
if ((EXYNOS_USBCON_VER_02_0_0 <= version)
|
||||
&& (version <= EXYNOS_USBCON_VER_02_MAX)) {
|
||||
|
||||
u32 hsphyctrl;
|
||||
|
||||
hsphyctrl = readl(regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
hsphyctrl |= HSPHYCTRL_PHYSWRST;
|
||||
writel(hsphyctrl, regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
udelay(20);
|
||||
hsphyctrl = readl(regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
hsphyctrl &= ~HSPHYCTRL_PHYSWRST;
|
||||
writel(hsphyctrl, regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
}
|
||||
|
||||
if (EXYNOS_USBCON_VER_01_0_0 <= version && version <= EXYNOS_USBCON_VER_01_MAX)
|
||||
samsung_exynos_cal_usb3phy_set_cr_port(usbphy_info);
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_disable(struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 version = usbphy_info->version;
|
||||
u32 phyutmi;
|
||||
u32 phyclkrst;
|
||||
|
||||
phyclkrst = readl(regs_base + EXYNOS_USBCON_PHYCLKRST);
|
||||
phyclkrst |= PHYCLKRST_EN_UTMISUSPEND;
|
||||
phyclkrst |= PHYCLKRST_COMMONONN;
|
||||
phyclkrst |= PHYCLKRST_RETENABLEN;
|
||||
phyclkrst &= ~PHYCLKRST_REF_SSP_EN;
|
||||
phyclkrst &= ~PHYCLKRST_SSC_EN;
|
||||
/* Select Reference clock source path */
|
||||
phyclkrst &= ~PHYCLKRST_REFCLKSEL_MASK;
|
||||
phyclkrst |= PHYCLKRST_REFCLKSEL(usbphy_info->refsel);
|
||||
|
||||
/* Select ref clk */
|
||||
phyclkrst &= ~PHYCLKRST_FSEL_MASK;
|
||||
phyclkrst |= PHYCLKRST_FSEL(usbphy_info->refclk & 0x3f);
|
||||
writel(phyclkrst, regs_base + EXYNOS_USBCON_PHYCLKRST);
|
||||
|
||||
phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
phyutmi &= ~PHYUTMI_IDPULLUP;
|
||||
phyutmi &= ~PHYUTMI_DRVVBUS;
|
||||
phyutmi |= PHYUTMI_FORCESUSPEND;
|
||||
phyutmi |= PHYUTMI_FORCESLEEP;
|
||||
if (usbphy_info->not_used_vbus_pad) {
|
||||
phyutmi &= ~PHYUTMI_VBUSVLDEXTSEL;
|
||||
phyutmi &= ~PHYUTMI_VBUSVLDEXT;
|
||||
}
|
||||
writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
|
||||
if ((EXYNOS_USBCON_VER_01_0_0 <= version)
|
||||
&& (version <= EXYNOS_USBCON_VER_01_MAX)) {
|
||||
u32 phytest;
|
||||
|
||||
phytest = readl(regs_base + EXYNOS_USBCON_PHYTEST);
|
||||
phytest |= PHYTEST_POWERDOWN_HSP;
|
||||
phytest |= PHYTEST_POWERDOWN_SSP;
|
||||
writel(phytest, regs_base + EXYNOS_USBCON_PHYTEST);
|
||||
} else if (version >= EXYNOS_USBCON_VER_02_0_0
|
||||
&& version <= EXYNOS_USBCON_VER_02_MAX) {
|
||||
u32 hsphyctrl;
|
||||
|
||||
hsphyctrl = readl(regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
hsphyctrl |= HSPHYCTRL_SIDDQ;
|
||||
writel(hsphyctrl, regs_base + EXYNOS_USBCON_HSPHYCTRL);
|
||||
}
|
||||
|
||||
/* Clear VBUSVALID signal if VBUS pad is not used */
|
||||
if (usbphy_info->not_used_vbus_pad) {
|
||||
u32 linksystem;
|
||||
|
||||
linksystem = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
linksystem &= ~LINKSYSTEM_FORCE_BVALID;
|
||||
linksystem &= ~LINKSYSTEM_FORCE_VBUSVALID;
|
||||
writel(linksystem, regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
}
|
||||
|
||||
/* Set force q-channel */
|
||||
if ((version & 0xf) >= 0x01) {
|
||||
u32 phy_resume;
|
||||
|
||||
phy_resume = readl(regs_base + EXYNOS_USBCON_PHYRESUME);
|
||||
phy_resume &= ~PHYRESUME_FORCE_QACT;
|
||||
phy_resume |= PHYRESUME_DIS_ID0_QACT;
|
||||
phy_resume |= PHYRESUME_DIS_VBUSVALID_QACT;
|
||||
phy_resume |= PHYRESUME_DIS_BVALID_QACT;
|
||||
phy_resume |= PHYRESUME_DIS_LINKGATE_QACT;
|
||||
writel(phy_resume, regs_base + EXYNOS_USBCON_PHYRESUME);
|
||||
}
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_config_host_mode(
|
||||
struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 phyutmi;
|
||||
|
||||
phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
phyutmi |= PHYUTMI_DMPULLDOWN;
|
||||
phyutmi |= PHYUTMI_DPPULLDOWN;
|
||||
phyutmi &= ~PHYUTMI_VBUSVLDEXTSEL;
|
||||
phyutmi &= ~PHYUTMI_VBUSVLDEXT;
|
||||
writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_enable_dp_pullup(
|
||||
struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 phyutmi;
|
||||
|
||||
phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
phyutmi |= PHYUTMI_VBUSVLDEXT;
|
||||
writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_disable_dp_pullup(
|
||||
struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 phyutmi;
|
||||
|
||||
phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
phyutmi &= ~PHYUTMI_VBUSVLDEXT;
|
||||
writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_set_cr_port(
|
||||
struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
if (usbphy_info->ss_tune) {
|
||||
if (usbphy_info->ss_tune->set_crport_level_en) {
|
||||
/* Enable override los_bias, los_level and
|
||||
* tx_vboost_lvl, Set los_bias to 0x5 and
|
||||
* los_level to 0x9 */
|
||||
samsung_exynos_cal_cr_write(usbphy_info, 0x15, 0xA409);
|
||||
/* Set TX_VBOOST_LEVLE to default Value (0x4) */
|
||||
samsung_exynos_cal_cr_write(usbphy_info, 0x12, 0x8000);
|
||||
}
|
||||
/* to set the charge pump proportional current */
|
||||
if (usbphy_info->ss_tune->set_crport_mpll_charge_pump)
|
||||
samsung_exynos_cal_cr_write(usbphy_info, 0x30, 0xC0);
|
||||
}
|
||||
/* Set RXDET_MEAS_TIME[11:4] each reference clock */
|
||||
samsung_exynos_cal_cr_write(usbphy_info, 0x1010, 0x80);
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_tune_dev(struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 linksystem;
|
||||
u32 phyparam0;
|
||||
u32 phyparam1;
|
||||
u32 phyparam2;
|
||||
u32 phypcsval;
|
||||
|
||||
/* Set the LINK Version Control and Frame Adjust Value */
|
||||
linksystem = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
linksystem &= ~LINKSYSTEM_FLADJ_MASK;
|
||||
linksystem |= LINKSYSTEM_FLADJ(0x20);
|
||||
linksystem |= LINKSYSTEM_XHCI_VERSION_CONTROL;
|
||||
writel(linksystem, regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
|
||||
/* Tuning the HS Block of phy */
|
||||
if (usbphy_info->hs_tune) {
|
||||
struct exynos_usbphy_hs_tune *tune = usbphy_info->hs_tune;
|
||||
|
||||
/* Set tune value for 2.0(HS/FS) function */
|
||||
phyparam0 = readl(regs_base + EXYNOS_USBCON_PHYPARAM0);
|
||||
/* TX VREF TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXVREFTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXVREFTUNE(tune->tx_vref);
|
||||
/* TX RISE TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXRISETUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXRISETUNE(tune->tx_rise);
|
||||
/* TX RES TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXRESTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXRESTUNE(tune->tx_res);
|
||||
/* TX PRE EMPHASIS PULS */
|
||||
if (tune->tx_pre_emp_puls)
|
||||
phyparam0 |= PHYPARAM0_TXPREEMPPULSETUNE;
|
||||
else
|
||||
phyparam0 &= ~PHYPARAM0_TXPREEMPPULSETUNE;
|
||||
/* TX PRE EMPHASIS */
|
||||
phyparam0 &= ~PHYPARAM0_TXPREEMPAMPTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXPREEMPAMPTUNE(tune->tx_pre_emp);
|
||||
/* TX HS XV TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXHSXVTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXHSXVTUNE(tune->tx_hsxv);
|
||||
/* TX FSLS TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXFSLSTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXFSLSTUNE(tune->tx_fsls);
|
||||
/* RX SQ TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_SQRXTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_SQRXTUNE(tune->rx_sqrx);
|
||||
/* OTG TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_OTGTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_OTGTUNE(tune->otg);
|
||||
/* COM DIS TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_COMPDISTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_COMPDISTUNE(tune->compdis);
|
||||
|
||||
writel(phyparam0, regs_base + EXYNOS_USBCON_PHYPARAM0);
|
||||
}
|
||||
|
||||
/* Tuning the SS Block of phy */
|
||||
if (usbphy_info->ss_tune) {
|
||||
struct exynos_usbphy_ss_tune *tune = usbphy_info->ss_tune;
|
||||
|
||||
/* Set the PHY Signal Quality Tuning Value */
|
||||
phyparam1 = readl(regs_base + EXYNOS5_USBCON_PHYPARAM1);
|
||||
/* TX SWING FULL */
|
||||
phyparam1 &= ~PHYPARAM1_PCS_TXSWING_FULL_MASK;
|
||||
phyparam1 |= PHYPARAM1_PCS_TXSWING_FULL(tune->tx_swing_full);
|
||||
/* TX DE EMPHASIS 3.5 dB */
|
||||
phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_3P5DB_MASK;
|
||||
phyparam1 |= PHYPARAM1_PCS_TXDEEMPH_3P5DB(
|
||||
tune->tx_deemphasis_3p5db);
|
||||
writel(phyparam1, regs_base + EXYNOS5_USBCON_PHYPARAM1);
|
||||
|
||||
/* Set vboost value for eye diagram */
|
||||
phyparam2 = readl(regs_base + EXYNOS_USBCON_PHYPARAM2);
|
||||
/* TX VBOOST Value */
|
||||
phyparam2 &= ~PHYPARAM2_TX_VBOOST_LVL_MASK;
|
||||
phyparam2 |= PHYPARAM2_TX_VBOOST_LVL(tune->tx_boost_level);
|
||||
/* LOS BIAS */
|
||||
phyparam2 &= ~PHYPARAM2_LOS_BIAS_MASK;
|
||||
phyparam2 |= PHYPARAM2_LOS_BIAS(tune->los_bias);
|
||||
writel(phyparam2, regs_base + EXYNOS_USBCON_PHYPARAM2);
|
||||
|
||||
/*
|
||||
* Set pcs_rx_los_mask_val for 14nm PHY to mask the abnormal
|
||||
* LFPS and glitches
|
||||
*/
|
||||
phypcsval = readl(regs_base + EXYNOS_USBCON_PHYPCSVAL);
|
||||
phypcsval &= ~PHYPCSVAL_PCS_RX_LOS_MASK_VAL_MASK;
|
||||
phypcsval |= PHYPCSVAL_PCS_RX_LOS_MASK_VAL(tune->los_mask_val);
|
||||
writel(phypcsval, regs_base + EXYNOS_USBCON_PHYPCSVAL);
|
||||
}
|
||||
}
|
||||
|
||||
void samsung_exynos_cal_usb3phy_tune_host(
|
||||
struct exynos_usbphy_info *usbphy_info)
|
||||
{
|
||||
void __iomem *regs_base = usbphy_info->regs_base;
|
||||
u32 linksystem;
|
||||
u32 phyparam0;
|
||||
u32 phyparam1;
|
||||
u32 phyparam2;
|
||||
u32 phypcsval;
|
||||
|
||||
/* Set the LINK Version Control and Frame Adjust Value */
|
||||
linksystem = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
linksystem &= ~LINKSYSTEM_FLADJ_MASK;
|
||||
linksystem |= LINKSYSTEM_FLADJ(0x20);
|
||||
linksystem |= LINKSYSTEM_XHCI_VERSION_CONTROL;
|
||||
writel(linksystem, regs_base + EXYNOS_USBCON_LINKSYSTEM);
|
||||
|
||||
/* Tuning the HS Block of phy */
|
||||
if (usbphy_info->hs_tune) {
|
||||
struct exynos_usbphy_hs_tune *tune = usbphy_info->hs_tune;
|
||||
|
||||
/* Set tune value for 2.0(HS/FS) function */
|
||||
phyparam0 = readl(regs_base + EXYNOS_USBCON_PHYPARAM0);
|
||||
/* TX VREF TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXVREFTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXVREFTUNE(tune->tx_vref);
|
||||
/* TX RISE TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXRISETUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXRISETUNE(tune->tx_rise);
|
||||
/* TX RES TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXRESTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXRESTUNE(tune->tx_res);
|
||||
/* TX PRE EMPHASIS PULS */
|
||||
if (tune->tx_pre_emp_puls)
|
||||
phyparam0 |= PHYPARAM0_TXPREEMPPULSETUNE;
|
||||
else
|
||||
phyparam0 &= ~PHYPARAM0_TXPREEMPPULSETUNE;
|
||||
/* TX PRE EMPHASIS */
|
||||
phyparam0 &= ~PHYPARAM0_TXPREEMPAMPTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXPREEMPAMPTUNE(tune->tx_pre_emp);
|
||||
/* TX HS XV TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXHSXVTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXHSXVTUNE(tune->tx_hsxv);
|
||||
/* TX FSLS TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_TXFSLSTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_TXFSLSTUNE(tune->tx_fsls);
|
||||
/* RX SQ TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_SQRXTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_SQRXTUNE(tune->rx_sqrx);
|
||||
/* OTG TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_OTGTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_OTGTUNE(tune->otg);
|
||||
/* COM DIS TUNE */
|
||||
phyparam0 &= ~PHYPARAM0_COMPDISTUNE_MASK;
|
||||
phyparam0 |= PHYPARAM0_COMPDISTUNE(tune->compdis);
|
||||
|
||||
writel(phyparam0, regs_base + EXYNOS_USBCON_PHYPARAM0);
|
||||
}
|
||||
|
||||
/* Tuning the SS Block of phy */
|
||||
if (usbphy_info->ss_tune) {
|
||||
struct exynos_usbphy_ss_tune *tune = usbphy_info->ss_tune;
|
||||
|
||||
/* Set the PHY Signal Quality Tuning Value */
|
||||
phyparam1 = readl(regs_base + EXYNOS5_USBCON_PHYPARAM1);
|
||||
/* TX SWING FULL */
|
||||
phyparam1 &= ~PHYPARAM1_PCS_TXSWING_FULL_MASK;
|
||||
phyparam1 |= PHYPARAM1_PCS_TXSWING_FULL(tune->tx_swing_full);
|
||||
/* TX DE EMPHASIS 3.5 dB */
|
||||
phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_3P5DB_MASK;
|
||||
phyparam1 |= PHYPARAM1_PCS_TXDEEMPH_3P5DB(
|
||||
tune->tx_deemphasis_3p5db);
|
||||
writel(phyparam1, regs_base + EXYNOS5_USBCON_PHYPARAM1);
|
||||
|
||||
/* Set vboost value for eye diagram */
|
||||
phyparam2 = readl(regs_base + EXYNOS_USBCON_PHYPARAM2);
|
||||
/* TX VBOOST Value */
|
||||
phyparam2 &= ~PHYPARAM2_TX_VBOOST_LVL_MASK;
|
||||
phyparam2 |= PHYPARAM2_TX_VBOOST_LVL(tune->tx_boost_level);
|
||||
/* LOS BIAS */
|
||||
phyparam2 &= ~PHYPARAM2_LOS_BIAS_MASK;
|
||||
phyparam2 |= PHYPARAM2_LOS_BIAS(tune->los_bias);
|
||||
writel(phyparam2, regs_base + EXYNOS_USBCON_PHYPARAM2);
|
||||
|
||||
/*
|
||||
* Set pcs_rx_los_mask_val for 14nm PHY to mask the abnormal
|
||||
* LFPS and glitches
|
||||
*/
|
||||
phypcsval = readl(regs_base + EXYNOS_USBCON_PHYPCSVAL);
|
||||
phypcsval &= ~PHYPCSVAL_PCS_RX_LOS_MASK_VAL_MASK;
|
||||
phypcsval |= PHYPCSVAL_PCS_RX_LOS_MASK_VAL(tune->los_mask_val);
|
||||
writel(phypcsval, regs_base + EXYNOS_USBCON_PHYPCSVAL);
|
||||
}
|
||||
}
|
247
drivers/phy/phy-samsung-usb3-cal.h
Normal file
247
drivers/phy/phy-samsung-usb3-cal.h
Normal file
|
@ -0,0 +1,247 @@
|
|||
#ifndef __PHY_SAMSUNG_USB3_FW_CAL_H__
|
||||
#define __PHY_SAMSUNG_USB3_FW_CAL_H__
|
||||
|
||||
#define EXYNOS_USBCON_LINKSYSTEM (0x04)
|
||||
#define LINKSYSTEM_HOST_SYSTEM_ERR (0x1 << 31)
|
||||
#define LINKSYSTEM_PHY_POWER_DOWN (0x1 << 30)
|
||||
#define LINKSYSTEM_PHY_SW_RESET (0x1 << 29)
|
||||
#define LINKSYSTEM_LINK_SW_RESET (0x1 << 28)
|
||||
#define LINKSYSTEM_XHCI_VERSION_CONTROL (0x1 << 27)
|
||||
#define LINKSYSTEM_FLADJ_MASK (0x3f << 1)
|
||||
#define LINKSYSTEM_FLADJ(_x) ((_x) << 1)
|
||||
#define LINKSYSTEM_FORCE_BVALID (0x1 << 7)
|
||||
#define LINKSYSTEM_FORCE_VBUSVALID (0x1 << 8)
|
||||
|
||||
#define EXYNOS_USBCON_PHYUTMI (0x08)
|
||||
#define PHYUTMI_UTMI_SUSPEND_COM_N (0x1 << 12)
|
||||
#define PHYUTMI_UTMI_L1_SUSPEND_COM_N (0x1 << 11)
|
||||
#define PHYUTMI_VBUSVLDEXTSEL (0x1 << 10)
|
||||
#define PHYUTMI_VBUSVLDEXT (0x1 << 9)
|
||||
#define PHYUTMI_TXBITSTUFFENH (0x1 << 8)
|
||||
#define PHYUTMI_TXBITSTUFFEN (0x1 << 7)
|
||||
#define PHYUTMI_OTGDISABLE (0x1 << 6)
|
||||
#define PHYUTMI_OTGDISABLE (0x1 << 6)
|
||||
#define PHYUTMI_IDPULLUP (0x1 << 5)
|
||||
#define PHYUTMI_DRVVBUS (0x1 << 4)
|
||||
#define PHYUTMI_DPPULLDOWN (0x1 << 3)
|
||||
#define PHYUTMI_DMPULLDOWN (0x1 << 2)
|
||||
#define PHYUTMI_FORCESUSPEND (0x1 << 1)
|
||||
#define PHYUTMI_FORCESLEEP (0x1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_PHYCLKRST (0x10)
|
||||
#define PHYCLKRST_EN_UTMISUSPEND (0x1 << 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 (0x1 << 20)
|
||||
#define PHYCLKRST_REF_SSP_EN (0x1 << 19)
|
||||
#define PHYCLKRST_REF_CLKDIV2 (0x1 << 18)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER(_x) ((_x) << 11)
|
||||
#define PHYCLKRST_FSEL_MASK (0x3f << 5)
|
||||
#define PHYCLKRST_FSEL(_x) ((_x) << 5)
|
||||
#define PHYCLKRST_RETENABLEN (0x1 << 4)
|
||||
#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2)
|
||||
#define PHYCLKRST_REFCLKSEL(_x) ((_x) << 2)
|
||||
#define PHYCLKRST_PORTRESET (0x1 << 1)
|
||||
#define PHYCLKRST_COMMONONN (0x1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_PHYPIPE (0x0c)
|
||||
#define PHY_CLOCK_SEL (0x1 << 4)
|
||||
|
||||
#define EXYNOS_USBCON_PHYREG0 (0x14)
|
||||
#define PHYREG0_SSC_REFCLKSEL_MASK (0x1ff << 23)
|
||||
#define PHYREG0_SSC_REFCLKSEL(_x) ((_x) << 23)
|
||||
#define PHYREG0_SSC_RANGE_MASK (0x7 << 20)
|
||||
#define PHYREG0_SSC_RAMGE(_x) ((_x) << 20)
|
||||
#define PHYREG0_CR_WRITE (1 << 19)
|
||||
#define PHYREG0_CR_READ (1 << 18)
|
||||
#define PHYREG0_CR_DATA_MASK (0xffff << 2)
|
||||
#define PHYREG0_CR_DATA_IN(_x) ((_x) << 2)
|
||||
#define PHYREG0_CR_CR_CAP_DATA (1 << 1)
|
||||
#define PHYREG0_CR_CR_CAP_ADDR (1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_PHYREG1 (0x18)
|
||||
#define PHYREG1_CR_DATA_OUT_MASK (0xffff << 1)
|
||||
#define PHYREG1_CR_DATA_OUT(_x) ((_x) << 1)
|
||||
#define PHYREG1_CR_ACK (1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_PHYPARAM0 (0x1c)
|
||||
#define PHYPARAM0_REF_USE_PAD (0x1 << 31)
|
||||
#define PHYPARAM0_VDATAREFTUNE_MASK (0x3 << 26)
|
||||
#define PHYPARAM0_VDATAREFTUNE(_x) ((_x) << 26)
|
||||
#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26)
|
||||
#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26)
|
||||
#define PHYPARAM0_TXVREFTUNE_MASK (0xf << 22)
|
||||
#define PHYPARAM0_TXVREFTUNE(_x) ((_x) << 22)
|
||||
#define PHYPARAM0_TXRISETUNE_MASK (0x3 << 20)
|
||||
#define PHYPARAM0_TXRISETUNE(_x) ((_x) << 20)
|
||||
#define PHYPARAM0_TXRESTUNE_MASK (0x3 << 18)
|
||||
#define PHYPARAM0_TXRESTUNE(_x) ((_x) << 18)
|
||||
#define PHYPARAM0_TXPREEMPPULSETUNE (0x1 << 17)
|
||||
#define PHYPARAM0_TXPREEMPAMPTUNE_MASK (0x3 << 15)
|
||||
#define PHYPARAM0_TXPREEMPAMPTUNE(_x) ((_x) << 15)
|
||||
#define PHYPARAM0_TXHSXVTUNE_MASK (0x3 << 13)
|
||||
#define PHYPARAM0_TXHSXVTUNE(_x) ((_x) << 13)
|
||||
#define PHYPARAM0_TXFSLSTUNE_MASK (0xf << 9)
|
||||
#define PHYPARAM0_TXFSLSTUNE(_x) ((_x) << 9)
|
||||
#define PHYPARAM0_SQRXTUNE_MASK (0x7 << 6)
|
||||
#define PHYPARAM0_SQRXTUNE(_x) ((_x) << 6)
|
||||
#define PHYPARAM0_OTGTUNE_MASK (0x7 << 3)
|
||||
#define PHYPARAM0_OTGTUNE(_x) ((_x) << 3)
|
||||
#define PHYPARAM0_COMPDISTUNE_MASK (0x7 << 0)
|
||||
#define PHYPARAM0_COMPDISTUNE(_x) ((_x) << 0)
|
||||
|
||||
#define EXYNOS5_USBCON_PHYPARAM1 (0x20)
|
||||
#define PHYPARAM1_TX0_TERM_OFFSET_MASK (0x1f << 26)
|
||||
#define PHYPARAM1_TX0_TERM_OFFSET(_x) ((_x) << 26)
|
||||
#define PHYPARAM1_PCS_TXSWING_FULL_MASK (0x7f << 12)
|
||||
#define PHYPARAM1_PCS_TXSWING_FULL(_x) ((_x) << 12)
|
||||
#define PHYPARAM1_PCS_TXDEEMPH_3P5DB_MASK (0x3f << 0)
|
||||
#define PHYPARAM1_PCS_TXDEEMPH_3P5DB(_x) ((_x) << 0)
|
||||
|
||||
#define EXYNOS_USBCON_PHYTEST (0x28)
|
||||
#define PHYTEST_POWERDOWN_SSP (0x1 << 3)
|
||||
#define PHYTEST_POWERDOWN_HSP (0x1 << 2)
|
||||
|
||||
#define EXYNOS_USBCON_PHYBATCHG (0x30)
|
||||
#define PHYBATCHG_CHGDET (0x1 << 10)
|
||||
#define PHYBATCHG_RIDC (0x1 << 9)
|
||||
#define PHYBATCHG_RIDB (0x1 << 8)
|
||||
#define PHYBATCHG_RIDA (0x1 << 7)
|
||||
#define PHYBATCHG_RIDGND (0x1 << 6)
|
||||
#define PHYBATCHG_RIDFLOAT (0x1 << 5)
|
||||
#define PHYBATCHG_VDATSRCENB (0x1 << 4)
|
||||
#define PHYBATCHG_VDATDETENB (0x1 << 3)
|
||||
#define PHYBATCHG_CHRGSEL (0x1 << 2)
|
||||
#define PHYBATCHG_ACAENB (0x1 << 1)
|
||||
#define PHYBATCHG_DCDENB (0x1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_PHYRESUME (0x34)
|
||||
#define PHYRESUME_DIS_BUSPEND_QACT (0x1 << 14)
|
||||
#define PHYRESUME_DIS_LINKGATE_QACT (0x1 << 13)
|
||||
#define PHYRESUME_DIS_ID0_QACT (0x1 << 12)
|
||||
#define PHYRESUME_DIS_VBUSVALID_QACT (0x1 << 11)
|
||||
#define PHYRESUME_DIS_BVALID_QACT (0x1 << 10)
|
||||
#define PHYRESUME_FORCE_QACT (0x1 << 9)
|
||||
#define PHYRESUME_FORCE_OPMODE_MASK (0x3 << 7)
|
||||
#define PHYRESUME_FORCE_OPMODE(_x) ((_x) << 7)
|
||||
#define PHYRESUME_OPMODE_EN (0x1 << 6)
|
||||
#define PHYRESUME_AUTORESUME_EN (0x1 << 5)
|
||||
#define PHYRESUME_BYPASS_SEL (0x1 << 4)
|
||||
#define PHYRESUME_BYPASS_DM_EN (0x1 << 3)
|
||||
#define PHYRESUME_BYPASS_DP_EN (0x1 << 2)
|
||||
#define PHYRESUME_BYPASS_DM_DATA (0x1 << 1)
|
||||
#define PHYRESUME_BYPASS_DP_DATA (0x1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_PHYPCSVAL (0x3C)
|
||||
#define PHYPCSVAL_PCS_RX_LOS_MASK_VAL_MASK (0x3FF << 0)
|
||||
#define PHYPCSVAL_PCS_RX_LOS_MASK_VAL(_x) ((_x & 0x3FF) << 0)
|
||||
|
||||
#define EXYNOS_USBCON_LINKPORT (0x44)
|
||||
#define LINKPORT_HOST_U3_PORT_DISABLE (1 << 8)
|
||||
#define LINKPORT_HOST_U2_PORT_DISABLE (1 << 7)
|
||||
#define LINKPORT_HOST_PORT_OVCR_U3 (1 << 5)
|
||||
#define LINKPORT_HOST_PORT_OVCR_U2 (1 << 4)
|
||||
#define LINKPORT_HOST_PORT_OVCR_U3_SEL (1 << 3)
|
||||
#define LINKPORT_HOST_PORT_OVCR_U2_SEL (1 << 2)
|
||||
|
||||
#define EXYNOS_USBCON_PHYPARAM2 (0x50)
|
||||
#define PHYPARAM2_TX_VBOOST_LVL_MASK (0x7 << 4)
|
||||
#define PHYPARAM2_TX_VBOOST_LVL(_x) ((_x) << 4)
|
||||
#define PHYPARAM2_LOS_BIAS_MASK (0x7 << 0)
|
||||
#define PHYPARAM2_LOS_BIAS(_x) ((_x) << 0)
|
||||
|
||||
#define EXYNOS_USBCON_HSPHYCTRL (0x54)
|
||||
#define HSPHYCTRL_PHYSWRSTALL (0x1 << 31)
|
||||
#define HSPHYCTRL_SIDDQ (0x1 << 6)
|
||||
#define HSPHYCTRL_PHYSWRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_HSPHYTESTIF (0x5c)
|
||||
#define HSPHYTESTIF_LINESTATE_MASK (0x3 << 20)
|
||||
#define HSPHYTESTIF_DATAOUT_MASK (0xf << 16)
|
||||
#define HSPHYTESTIF_CLKEN (0x1 << 13)
|
||||
#define HSPHYTESTIF_DATAOUTSEL (0x2 << 12)
|
||||
#define HSPHYTESTIF_ADDR_MASK (0xf << 8)
|
||||
#define HSPHYTESTIF_ADDR(_x) ((_x) << 8)
|
||||
#define HSPHYTESTIF_DATAIN_MASK (0xff << 0)
|
||||
#define HSPHYTESTIF_DATAIN(_x) ((_x) << 0)
|
||||
|
||||
#define EXYNOS_USBCON_HSPHYPLLTUNE (0x70)
|
||||
#define HSPHYPLLTUNE_PLL_B_TUNE (0x1 << 6)
|
||||
#define HSPHYPLLTUNE_PLL_I_TUNE(_x) ((_x) << 4)
|
||||
#define HSPHYPLLTUNE_PLL_P_TUNE(_x) ((_x) << 0)
|
||||
|
||||
#define EXYNOS_USBCON_EHCICTRL (0x80)
|
||||
#define EHCICTRL_EHCI64BITEN (0x1 << 31)
|
||||
#define EHCICTRL_EN_INCRX_ALIGN (0x1 << 30)
|
||||
#define EHCICTRL_EN_INCR4 (0x1 << 29)
|
||||
#define EHCICTRL_EN_INCR8 (0x1 << 28)
|
||||
#define EHCICTRL_EN_INCR16 (0x1 << 26)
|
||||
#define EHCICTRL_EN_AUTO_PPDON_OVRCUR (0x1 << 25)
|
||||
#define EHCICTRL_FLADJVAL0_MASK (0x3f << 19)
|
||||
#define EHCICTRL_FLADJVAL0(_x) ((_x) << 19)
|
||||
#define EHCICTRL_FLADJVAL1_MASK (0x3f << 13)
|
||||
#define EHCICTRL_FLADJVAL1(_x) ((_x) << 13)
|
||||
#define EHCICTRL_FLADJVAL2_MASK (0x3f << 7)
|
||||
#define EHCICTRL_FLADJVAL2(_x) ((_x) << 7)
|
||||
#define EHCICTRL_FLADJVALHOST_MASK (0x3f << 1)
|
||||
#define EHCICTRL_FLADJVALHOST(_x) ((_x) << 1)
|
||||
|
||||
#define EXYNOS_USBCON_OHCICTRL (0x84)
|
||||
#define OHCICTRL_RESET_PORT0 (0x1 << 29)
|
||||
#define OHCICTRL_HUBSETUP_MIN (0x1 << 4)
|
||||
#define OHCICTRL_OHCISUSPLGCY (0x1 << 3)
|
||||
#define OHCICTRL_APPSTARTCLK (0x1 << 2)
|
||||
#define OHCICTRL_OHCICNTSELN (0x1 << 1)
|
||||
#define OHCICTRL_OHCICLKCKTRSTN (0x1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_HOSTLINKCTRL (0x88)
|
||||
#define HOSTLINKCTRL_EN_SLEEP_HOST_P2 (0x1 << 31)
|
||||
#define HOSTLINKCTRL_EN_SLEEP_HOST_P1 (0x1 << 30)
|
||||
#define HOSTLINKCTRL_EN_SLEEP_HOST_P0 (0x1 << 29)
|
||||
#define HOSTLINKCTRL_EN_SLEEP_OTG (0x1 << 28)
|
||||
#define HOSTLINKCTRL_EN_SUSPEND_HOST_P2 (0x1 << 27)
|
||||
#define HOSTLINKCTRL_EN_SUSPEND_HOST_P1 (0x1 << 26)
|
||||
#define HOSTLINKCTRL_EN_SUSPEND_HOST_P0 (0x1 << 25)
|
||||
#define HOSTLINKCTRL_EN_SUSPEND_OTG (0x1 << 24)
|
||||
#define HOSTLINKCTRL_FORCE_HOST_OVRCUR_P2 (0x1 << 17)
|
||||
#define HOSTLINKCTRL_FORCE_HOST_OVRCUR (0x1 << 16)
|
||||
#define HOSTLINKCTRL_SW_RESET_PORT2 (0x1 << 3)
|
||||
#define HOSTLINKCTRL_SW_RESET_PORT1 (0x1 << 2)
|
||||
#define HOSTLINKCTRL_SW_RESET_PORT0 (0x1 << 1)
|
||||
#define HOSTLINKCTRL_LINKSWRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS_USBCON_OTGLINKCTRL (0x8C)
|
||||
#define OTGINKCTRL_AVALID (0x1 << 14)
|
||||
#define OTGINKCTRL_BVALID (0x1 << 13)
|
||||
#define OTGINKCTRL_IDDIG (0x1 << 12)
|
||||
#define OTGINKCTRL_VBUSDETECT (0x1 << 11)
|
||||
#define OTGINKCTRL_VBUSVLDSEL_MASK (0x3 << 9)
|
||||
#define OTGINKCTRL_VBUSVLDSEL(_x) ((_x) << 9)
|
||||
#define OTGINKCTRL_LINK_PRST (0x1 << 4)
|
||||
#define OTGINKCTRL_SW_RESET_ALL (0x1 << 3)
|
||||
#define OTGINKCTRL_SW_RESET (0x1 << 2)
|
||||
|
||||
void samsung_exynos_cal_usb3phy_enable(struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_usb3phy_disable(struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_usb3phy_set_cr_port(
|
||||
struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_usb3phy_tune_dev(
|
||||
struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_usb3phy_tune_host(
|
||||
struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_cr_write(struct exynos_usbphy_info *usbphy_info,
|
||||
u16 addr, u16 data);
|
||||
u16 samsung_exynos_cal_cr_read(struct exynos_usbphy_info *usbphy_info,
|
||||
u16 addr);
|
||||
void samsung_cal_usb3phy_tune(struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_usb3phy_config_host_mode(
|
||||
struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_usb3phy_enable_dp_pullup(
|
||||
struct exynos_usbphy_info *usbphy_info);
|
||||
void samsung_exynos_cal_usb3phy_disable_dp_pullup(
|
||||
struct exynos_usbphy_info *usbphy_info);
|
||||
|
||||
#endif
|
261
drivers/phy/phy-spear1310-miphy.c
Normal file
261
drivers/phy/phy-spear1310-miphy.c
Normal file
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* ST SPEAr1310-miphy driver
|
||||
*
|
||||
* Copyright (C) 2014 ST Microelectronics
|
||||
* Pratyush Anand <pratyush.anand@st.com>
|
||||
* Mohit Kumar <mohit.kumar@st.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/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/* SPEAr1310 Registers */
|
||||
#define SPEAR1310_PCIE_SATA_CFG 0x3A4
|
||||
#define SPEAR1310_PCIE_SATA2_SEL_PCIE (0 << 31)
|
||||
#define SPEAR1310_PCIE_SATA1_SEL_PCIE (0 << 30)
|
||||
#define SPEAR1310_PCIE_SATA0_SEL_PCIE (0 << 29)
|
||||
#define SPEAR1310_PCIE_SATA2_SEL_SATA BIT(31)
|
||||
#define SPEAR1310_PCIE_SATA1_SEL_SATA BIT(30)
|
||||
#define SPEAR1310_PCIE_SATA0_SEL_SATA BIT(29)
|
||||
#define SPEAR1310_SATA2_CFG_TX_CLK_EN BIT(27)
|
||||
#define SPEAR1310_SATA2_CFG_RX_CLK_EN BIT(26)
|
||||
#define SPEAR1310_SATA2_CFG_POWERUP_RESET BIT(25)
|
||||
#define SPEAR1310_SATA2_CFG_PM_CLK_EN BIT(24)
|
||||
#define SPEAR1310_SATA1_CFG_TX_CLK_EN BIT(23)
|
||||
#define SPEAR1310_SATA1_CFG_RX_CLK_EN BIT(22)
|
||||
#define SPEAR1310_SATA1_CFG_POWERUP_RESET BIT(21)
|
||||
#define SPEAR1310_SATA1_CFG_PM_CLK_EN BIT(20)
|
||||
#define SPEAR1310_SATA0_CFG_TX_CLK_EN BIT(19)
|
||||
#define SPEAR1310_SATA0_CFG_RX_CLK_EN BIT(18)
|
||||
#define SPEAR1310_SATA0_CFG_POWERUP_RESET BIT(17)
|
||||
#define SPEAR1310_SATA0_CFG_PM_CLK_EN BIT(16)
|
||||
#define SPEAR1310_PCIE2_CFG_DEVICE_PRESENT BIT(11)
|
||||
#define SPEAR1310_PCIE2_CFG_POWERUP_RESET BIT(10)
|
||||
#define SPEAR1310_PCIE2_CFG_CORE_CLK_EN BIT(9)
|
||||
#define SPEAR1310_PCIE2_CFG_AUX_CLK_EN BIT(8)
|
||||
#define SPEAR1310_PCIE1_CFG_DEVICE_PRESENT BIT(7)
|
||||
#define SPEAR1310_PCIE1_CFG_POWERUP_RESET BIT(6)
|
||||
#define SPEAR1310_PCIE1_CFG_CORE_CLK_EN BIT(5)
|
||||
#define SPEAR1310_PCIE1_CFG_AUX_CLK_EN BIT(4)
|
||||
#define SPEAR1310_PCIE0_CFG_DEVICE_PRESENT BIT(3)
|
||||
#define SPEAR1310_PCIE0_CFG_POWERUP_RESET BIT(2)
|
||||
#define SPEAR1310_PCIE0_CFG_CORE_CLK_EN BIT(1)
|
||||
#define SPEAR1310_PCIE0_CFG_AUX_CLK_EN BIT(0)
|
||||
|
||||
#define SPEAR1310_PCIE_CFG_MASK(x) ((0xF << (x * 4)) | BIT((x + 29)))
|
||||
#define SPEAR1310_SATA_CFG_MASK(x) ((0xF << (x * 4 + 16)) | \
|
||||
BIT((x + 29)))
|
||||
#define SPEAR1310_PCIE_CFG_VAL(x) \
|
||||
(SPEAR1310_PCIE_SATA##x##_SEL_PCIE | \
|
||||
SPEAR1310_PCIE##x##_CFG_AUX_CLK_EN | \
|
||||
SPEAR1310_PCIE##x##_CFG_CORE_CLK_EN | \
|
||||
SPEAR1310_PCIE##x##_CFG_POWERUP_RESET | \
|
||||
SPEAR1310_PCIE##x##_CFG_DEVICE_PRESENT)
|
||||
#define SPEAR1310_SATA_CFG_VAL(x) \
|
||||
(SPEAR1310_PCIE_SATA##x##_SEL_SATA | \
|
||||
SPEAR1310_SATA##x##_CFG_PM_CLK_EN | \
|
||||
SPEAR1310_SATA##x##_CFG_POWERUP_RESET | \
|
||||
SPEAR1310_SATA##x##_CFG_RX_CLK_EN | \
|
||||
SPEAR1310_SATA##x##_CFG_TX_CLK_EN)
|
||||
|
||||
#define SPEAR1310_PCIE_MIPHY_CFG_1 0x3A8
|
||||
#define SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT BIT(31)
|
||||
#define SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 BIT(28)
|
||||
#define SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(x) (x << 16)
|
||||
#define SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT BIT(15)
|
||||
#define SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 BIT(12)
|
||||
#define SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(x) (x << 0)
|
||||
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_MASK (0xFFFF)
|
||||
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK (0xFFFF << 16)
|
||||
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA \
|
||||
(SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
|
||||
SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 | \
|
||||
SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(60) | \
|
||||
SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
|
||||
SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 | \
|
||||
SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(60))
|
||||
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
|
||||
(SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(120))
|
||||
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE \
|
||||
(SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
|
||||
SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(25) | \
|
||||
SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
|
||||
SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(25))
|
||||
|
||||
#define SPEAR1310_PCIE_MIPHY_CFG_2 0x3AC
|
||||
|
||||
enum spear1310_miphy_mode {
|
||||
SATA,
|
||||
PCIE,
|
||||
};
|
||||
|
||||
struct spear1310_miphy_priv {
|
||||
/* instance id of this phy */
|
||||
u32 id;
|
||||
/* phy mode: 0 for SATA 1 for PCIe */
|
||||
enum spear1310_miphy_mode mode;
|
||||
/* regmap for any soc specific misc registers */
|
||||
struct regmap *misc;
|
||||
/* phy struct pointer */
|
||||
struct phy *phy;
|
||||
};
|
||||
|
||||
static int spear1310_miphy_pcie_init(struct spear1310_miphy_priv *priv)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
|
||||
SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK,
|
||||
SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE);
|
||||
|
||||
switch (priv->id) {
|
||||
case 0:
|
||||
val = SPEAR1310_PCIE_CFG_VAL(0);
|
||||
break;
|
||||
case 1:
|
||||
val = SPEAR1310_PCIE_CFG_VAL(1);
|
||||
break;
|
||||
case 2:
|
||||
val = SPEAR1310_PCIE_CFG_VAL(2);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
|
||||
SPEAR1310_PCIE_CFG_MASK(priv->id), val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spear1310_miphy_pcie_exit(struct spear1310_miphy_priv *priv)
|
||||
{
|
||||
regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
|
||||
SPEAR1310_PCIE_CFG_MASK(priv->id), 0);
|
||||
|
||||
regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
|
||||
SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spear1310_miphy_init(struct phy *phy)
|
||||
{
|
||||
struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
|
||||
int ret = 0;
|
||||
|
||||
if (priv->mode == PCIE)
|
||||
ret = spear1310_miphy_pcie_init(priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spear1310_miphy_exit(struct phy *phy)
|
||||
{
|
||||
struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
|
||||
int ret = 0;
|
||||
|
||||
if (priv->mode == PCIE)
|
||||
ret = spear1310_miphy_pcie_exit(priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id spear1310_miphy_of_match[] = {
|
||||
{ .compatible = "st,spear1310-miphy" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, spear1310_miphy_of_match);
|
||||
|
||||
static struct phy_ops spear1310_miphy_ops = {
|
||||
.init = spear1310_miphy_init,
|
||||
.exit = spear1310_miphy_exit,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct phy *spear1310_miphy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct spear1310_miphy_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (args->args_count < 1) {
|
||||
dev_err(dev, "DT did not pass correct no of args\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
priv->mode = args->args[0];
|
||||
|
||||
if (priv->mode != SATA && priv->mode != PCIE) {
|
||||
dev_err(dev, "DT did not pass correct phy mode\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return priv->phy;
|
||||
}
|
||||
|
||||
static int spear1310_miphy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct spear1310_miphy_priv *priv;
|
||||
struct phy_provider *phy_provider;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->misc =
|
||||
syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
|
||||
if (IS_ERR(priv->misc)) {
|
||||
dev_err(dev, "failed to find misc regmap\n");
|
||||
return PTR_ERR(priv->misc);
|
||||
}
|
||||
|
||||
if (of_property_read_u32(dev->of_node, "phy-id", &priv->id)) {
|
||||
dev_err(dev, "failed to find phy id\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->phy = devm_phy_create(dev, NULL, &spear1310_miphy_ops, NULL);
|
||||
if (IS_ERR(priv->phy)) {
|
||||
dev_err(dev, "failed to create SATA PCIe PHY\n");
|
||||
return PTR_ERR(priv->phy);
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, priv);
|
||||
phy_set_drvdata(priv->phy, priv);
|
||||
|
||||
phy_provider =
|
||||
devm_of_phy_provider_register(dev, spear1310_miphy_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
dev_err(dev, "failed to register phy provider\n");
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver spear1310_miphy_driver = {
|
||||
.probe = spear1310_miphy_probe,
|
||||
.driver = {
|
||||
.name = "spear1310-miphy",
|
||||
.of_match_table = of_match_ptr(spear1310_miphy_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(spear1310_miphy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ST SPEAR1310-MIPHY driver");
|
||||
MODULE_AUTHOR("Pratyush Anand <pratyush.anand@st.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
294
drivers/phy/phy-spear1340-miphy.c
Normal file
294
drivers/phy/phy-spear1340-miphy.c
Normal file
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* ST spear1340-miphy driver
|
||||
*
|
||||
* Copyright (C) 2014 ST Microelectronics
|
||||
* Pratyush Anand <pratyush.anand@st.com>
|
||||
* Mohit Kumar <mohit.kumar@st.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/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/* SPEAr1340 Registers */
|
||||
/* Power Management Registers */
|
||||
#define SPEAR1340_PCM_CFG 0x100
|
||||
#define SPEAR1340_PCM_CFG_SATA_POWER_EN BIT(11)
|
||||
#define SPEAR1340_PCM_WKUP_CFG 0x104
|
||||
#define SPEAR1340_SWITCH_CTR 0x108
|
||||
|
||||
#define SPEAR1340_PERIP1_SW_RST 0x318
|
||||
#define SPEAR1340_PERIP1_SW_RSATA BIT(12)
|
||||
#define SPEAR1340_PERIP2_SW_RST 0x31C
|
||||
#define SPEAR1340_PERIP3_SW_RST 0x320
|
||||
|
||||
/* PCIE - SATA configuration registers */
|
||||
#define SPEAR1340_PCIE_SATA_CFG 0x424
|
||||
/* PCIE CFG MASks */
|
||||
#define SPEAR1340_PCIE_CFG_DEVICE_PRESENT BIT(11)
|
||||
#define SPEAR1340_PCIE_CFG_POWERUP_RESET BIT(10)
|
||||
#define SPEAR1340_PCIE_CFG_CORE_CLK_EN BIT(9)
|
||||
#define SPEAR1340_PCIE_CFG_AUX_CLK_EN BIT(8)
|
||||
#define SPEAR1340_SATA_CFG_TX_CLK_EN BIT(4)
|
||||
#define SPEAR1340_SATA_CFG_RX_CLK_EN BIT(3)
|
||||
#define SPEAR1340_SATA_CFG_POWERUP_RESET BIT(2)
|
||||
#define SPEAR1340_SATA_CFG_PM_CLK_EN BIT(1)
|
||||
#define SPEAR1340_PCIE_SATA_SEL_PCIE (0)
|
||||
#define SPEAR1340_PCIE_SATA_SEL_SATA (1)
|
||||
#define SPEAR1340_PCIE_SATA_CFG_MASK 0xF1F
|
||||
#define SPEAR1340_PCIE_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_PCIE | \
|
||||
SPEAR1340_PCIE_CFG_AUX_CLK_EN | \
|
||||
SPEAR1340_PCIE_CFG_CORE_CLK_EN | \
|
||||
SPEAR1340_PCIE_CFG_POWERUP_RESET | \
|
||||
SPEAR1340_PCIE_CFG_DEVICE_PRESENT)
|
||||
#define SPEAR1340_SATA_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_SATA | \
|
||||
SPEAR1340_SATA_CFG_PM_CLK_EN | \
|
||||
SPEAR1340_SATA_CFG_POWERUP_RESET | \
|
||||
SPEAR1340_SATA_CFG_RX_CLK_EN | \
|
||||
SPEAR1340_SATA_CFG_TX_CLK_EN)
|
||||
|
||||
#define SPEAR1340_PCIE_MIPHY_CFG 0x428
|
||||
#define SPEAR1340_MIPHY_OSC_BYPASS_EXT BIT(31)
|
||||
#define SPEAR1340_MIPHY_CLK_REF_DIV2 BIT(27)
|
||||
#define SPEAR1340_MIPHY_CLK_REF_DIV4 (2 << 27)
|
||||
#define SPEAR1340_MIPHY_CLK_REF_DIV8 (3 << 27)
|
||||
#define SPEAR1340_MIPHY_PLL_RATIO_TOP(x) (x << 0)
|
||||
#define SPEAR1340_PCIE_MIPHY_CFG_MASK 0xF80000FF
|
||||
#define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA \
|
||||
(SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
|
||||
SPEAR1340_MIPHY_CLK_REF_DIV2 | \
|
||||
SPEAR1340_MIPHY_PLL_RATIO_TOP(60))
|
||||
#define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
|
||||
(SPEAR1340_MIPHY_PLL_RATIO_TOP(120))
|
||||
#define SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE \
|
||||
(SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
|
||||
SPEAR1340_MIPHY_PLL_RATIO_TOP(25))
|
||||
|
||||
enum spear1340_miphy_mode {
|
||||
SATA,
|
||||
PCIE,
|
||||
};
|
||||
|
||||
struct spear1340_miphy_priv {
|
||||
/* phy mode: 0 for SATA 1 for PCIe */
|
||||
enum spear1340_miphy_mode mode;
|
||||
/* regmap for any soc specific misc registers */
|
||||
struct regmap *misc;
|
||||
/* phy struct pointer */
|
||||
struct phy *phy;
|
||||
};
|
||||
|
||||
static int spear1340_miphy_sata_init(struct spear1340_miphy_priv *priv)
|
||||
{
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
|
||||
SPEAR1340_PCIE_SATA_CFG_MASK,
|
||||
SPEAR1340_SATA_CFG_VAL);
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
|
||||
SPEAR1340_PCIE_MIPHY_CFG_MASK,
|
||||
SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK);
|
||||
/* Switch on sata power domain */
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
|
||||
SPEAR1340_PCM_CFG_SATA_POWER_EN,
|
||||
SPEAR1340_PCM_CFG_SATA_POWER_EN);
|
||||
/* Wait for SATA power domain on */
|
||||
msleep(20);
|
||||
|
||||
/* Disable PCIE SATA Controller reset */
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
|
||||
SPEAR1340_PERIP1_SW_RSATA, 0);
|
||||
/* Wait for SATA reset de-assert completion */
|
||||
msleep(20);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spear1340_miphy_sata_exit(struct spear1340_miphy_priv *priv)
|
||||
{
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
|
||||
SPEAR1340_PCIE_SATA_CFG_MASK, 0);
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
|
||||
SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
|
||||
|
||||
/* Enable PCIE SATA Controller reset */
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
|
||||
SPEAR1340_PERIP1_SW_RSATA,
|
||||
SPEAR1340_PERIP1_SW_RSATA);
|
||||
/* Wait for SATA power domain off */
|
||||
msleep(20);
|
||||
/* Switch off sata power domain */
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
|
||||
SPEAR1340_PCM_CFG_SATA_POWER_EN, 0);
|
||||
/* Wait for SATA reset assert completion */
|
||||
msleep(20);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spear1340_miphy_pcie_init(struct spear1340_miphy_priv *priv)
|
||||
{
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
|
||||
SPEAR1340_PCIE_MIPHY_CFG_MASK,
|
||||
SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE);
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
|
||||
SPEAR1340_PCIE_SATA_CFG_MASK,
|
||||
SPEAR1340_PCIE_CFG_VAL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spear1340_miphy_pcie_exit(struct spear1340_miphy_priv *priv)
|
||||
{
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
|
||||
SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
|
||||
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
|
||||
SPEAR1340_PCIE_SATA_CFG_MASK, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spear1340_miphy_init(struct phy *phy)
|
||||
{
|
||||
struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
|
||||
int ret = 0;
|
||||
|
||||
if (priv->mode == SATA)
|
||||
ret = spear1340_miphy_sata_init(priv);
|
||||
else if (priv->mode == PCIE)
|
||||
ret = spear1340_miphy_pcie_init(priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spear1340_miphy_exit(struct phy *phy)
|
||||
{
|
||||
struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
|
||||
int ret = 0;
|
||||
|
||||
if (priv->mode == SATA)
|
||||
ret = spear1340_miphy_sata_exit(priv);
|
||||
else if (priv->mode == PCIE)
|
||||
ret = spear1340_miphy_pcie_exit(priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id spear1340_miphy_of_match[] = {
|
||||
{ .compatible = "st,spear1340-miphy" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, spear1340_miphy_of_match);
|
||||
|
||||
static struct phy_ops spear1340_miphy_ops = {
|
||||
.init = spear1340_miphy_init,
|
||||
.exit = spear1340_miphy_exit,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int spear1340_miphy_suspend(struct device *dev)
|
||||
{
|
||||
struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (priv->mode == SATA)
|
||||
ret = spear1340_miphy_sata_exit(priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spear1340_miphy_resume(struct device *dev)
|
||||
{
|
||||
struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (priv->mode == SATA)
|
||||
ret = spear1340_miphy_sata_init(priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(spear1340_miphy_pm_ops, spear1340_miphy_suspend,
|
||||
spear1340_miphy_resume);
|
||||
|
||||
static struct phy *spear1340_miphy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (args->args_count < 1) {
|
||||
dev_err(dev, "DT did not pass correct no of args\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
priv->mode = args->args[0];
|
||||
|
||||
if (priv->mode != SATA && priv->mode != PCIE) {
|
||||
dev_err(dev, "DT did not pass correct phy mode\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return priv->phy;
|
||||
}
|
||||
|
||||
static int spear1340_miphy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct spear1340_miphy_priv *priv;
|
||||
struct phy_provider *phy_provider;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->misc =
|
||||
syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
|
||||
if (IS_ERR(priv->misc)) {
|
||||
dev_err(dev, "failed to find misc regmap\n");
|
||||
return PTR_ERR(priv->misc);
|
||||
}
|
||||
|
||||
priv->phy = devm_phy_create(dev, NULL, &spear1340_miphy_ops, NULL);
|
||||
if (IS_ERR(priv->phy)) {
|
||||
dev_err(dev, "failed to create SATA PCIe PHY\n");
|
||||
return PTR_ERR(priv->phy);
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, priv);
|
||||
phy_set_drvdata(priv->phy, priv);
|
||||
|
||||
phy_provider =
|
||||
devm_of_phy_provider_register(dev, spear1340_miphy_xlate);
|
||||
if (IS_ERR(phy_provider)) {
|
||||
dev_err(dev, "failed to register phy provider\n");
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver spear1340_miphy_driver = {
|
||||
.probe = spear1340_miphy_probe,
|
||||
.driver = {
|
||||
.name = "spear1340-miphy",
|
||||
.pm = &spear1340_miphy_pm_ops,
|
||||
.of_match_table = of_match_ptr(spear1340_miphy_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(spear1340_miphy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ST SPEAR1340-MIPHY driver");
|
||||
MODULE_AUTHOR("Pratyush Anand <pratyush.anand@st.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
177
drivers/phy/phy-stih407-usb.c
Normal file
177
drivers/phy/phy-stih407-usb.c
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright (C) 2014 STMicroelectronics
|
||||
*
|
||||
* STMicroelectronics Generic PHY driver for STiH407 USB2.
|
||||
*
|
||||
* Author: Giuseppe Cavallaro <peppe.cavallaro@st.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/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/phy/phy.h>
|
||||
|
||||
/* Default PHY_SEL and REFCLKSEL configuration */
|
||||
#define STIH407_USB_PICOPHY_CTRL_PORT_CONF 0x6
|
||||
#define STIH407_USB_PICOPHY_CTRL_PORT_MASK 0x1f
|
||||
|
||||
/* ports parameters overriding */
|
||||
#define STIH407_USB_PICOPHY_PARAM_DEF 0x39a4dc
|
||||
#define STIH407_USB_PICOPHY_PARAM_MASK 0xffffffff
|
||||
|
||||
struct stih407_usb2_picophy {
|
||||
struct phy *phy;
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
struct reset_control *rstc;
|
||||
struct reset_control *rstport;
|
||||
int ctrl;
|
||||
int param;
|
||||
};
|
||||
|
||||
static int stih407_usb2_pico_ctrl(struct stih407_usb2_picophy *phy_dev)
|
||||
{
|
||||
reset_control_deassert(phy_dev->rstc);
|
||||
|
||||
return regmap_update_bits(phy_dev->regmap, phy_dev->ctrl,
|
||||
STIH407_USB_PICOPHY_CTRL_PORT_MASK,
|
||||
STIH407_USB_PICOPHY_CTRL_PORT_CONF);
|
||||
}
|
||||
|
||||
static int stih407_usb2_init_port(struct phy *phy)
|
||||
{
|
||||
int ret;
|
||||
struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
|
||||
|
||||
stih407_usb2_pico_ctrl(phy_dev);
|
||||
|
||||
ret = regmap_update_bits(phy_dev->regmap,
|
||||
phy_dev->param,
|
||||
STIH407_USB_PICOPHY_PARAM_MASK,
|
||||
STIH407_USB_PICOPHY_PARAM_DEF);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return reset_control_deassert(phy_dev->rstport);
|
||||
}
|
||||
|
||||
static int stih407_usb2_exit_port(struct phy *phy)
|
||||
{
|
||||
struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
|
||||
|
||||
/*
|
||||
* Only port reset is asserted, phy global reset is kept untouched
|
||||
* as other ports may still be active. When all ports are in reset
|
||||
* state, assumption is made that power will be cut off on the phy, in
|
||||
* case of suspend for instance. Theoretically, asserting individual
|
||||
* reset (like here) or global reset should be equivalent.
|
||||
*/
|
||||
return reset_control_assert(phy_dev->rstport);
|
||||
}
|
||||
|
||||
static const struct phy_ops stih407_usb2_picophy_data = {
|
||||
.init = stih407_usb2_init_port,
|
||||
.exit = stih407_usb2_exit_port,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int stih407_usb2_picophy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct stih407_usb2_picophy *phy_dev;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy *phy;
|
||||
struct resource *res;
|
||||
|
||||
phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL);
|
||||
if (!phy_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
phy_dev->dev = dev;
|
||||
dev_set_drvdata(dev, phy_dev);
|
||||
|
||||
phy_dev->rstc = devm_reset_control_get(dev, "global");
|
||||
if (IS_ERR(phy_dev->rstc)) {
|
||||
dev_err(dev, "failed to ctrl picoPHY reset\n");
|
||||
return PTR_ERR(phy_dev->rstc);
|
||||
}
|
||||
|
||||
phy_dev->rstport = devm_reset_control_get(dev, "port");
|
||||
if (IS_ERR(phy_dev->rstport)) {
|
||||
dev_err(dev, "failed to ctrl picoPHY reset\n");
|
||||
return PTR_ERR(phy_dev->rstport);
|
||||
}
|
||||
|
||||
/* Reset port by default: only deassert it in phy init */
|
||||
reset_control_assert(phy_dev->rstport);
|
||||
|
||||
phy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
|
||||
if (IS_ERR(phy_dev->regmap)) {
|
||||
dev_err(dev, "No syscfg phandle specified\n");
|
||||
return PTR_ERR(phy_dev->regmap);
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ctrl");
|
||||
if (!res) {
|
||||
dev_err(dev, "No ctrl reg found\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
phy_dev->ctrl = res->start;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "param");
|
||||
if (!res) {
|
||||
dev_err(dev, "No param reg found\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
phy_dev->param = res->start;
|
||||
|
||||
phy = devm_phy_create(dev, NULL, &stih407_usb2_picophy_data, NULL);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create Display Port PHY\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_dev->phy = phy;
|
||||
phy_set_drvdata(phy, phy_dev);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
dev_info(dev, "STiH407 USB Generic picoPHY driver probed!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id stih407_usb2_picophy_of_match[] = {
|
||||
{ .compatible = "st,stih407-usb2-phy" },
|
||||
{ /*sentinel */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, stih407_usb2_picophy_of_match);
|
||||
|
||||
static struct platform_driver stih407_usb2_picophy_driver = {
|
||||
.probe = stih407_usb2_picophy_probe,
|
||||
.driver = {
|
||||
.name = "stih407-usb-genphy",
|
||||
.of_match_table = stih407_usb2_picophy_of_match,
|
||||
}
|
||||
};
|
||||
|
||||
module_platform_driver(stih407_usb2_picophy_driver);
|
||||
|
||||
MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics Generic picoPHY driver for STiH407");
|
||||
MODULE_LICENSE("GPL v2");
|
187
drivers/phy/phy-stih41x-usb.c
Normal file
187
drivers/phy/phy-stih41x-usb.c
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright (C) 2014 STMicroelectronics
|
||||
*
|
||||
* STMicroelectronics PHY driver for STiH41x USB.
|
||||
*
|
||||
* Author: Maxime Coquelin <maxime.coquelin@st.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/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
|
||||
#define SYSCFG332 0x80
|
||||
#define SYSCFG2520 0x820
|
||||
|
||||
/**
|
||||
* struct stih41x_usb_cfg - SoC specific PHY register mapping
|
||||
* @syscfg: Offset in syscfg registers bank
|
||||
* @cfg_mask: Bits mask for PHY configuration
|
||||
* @cfg: Static configuration value for PHY
|
||||
* @oscok: Notify the PHY oscillator clock is ready
|
||||
* Setting this bit enable the PHY
|
||||
*/
|
||||
struct stih41x_usb_cfg {
|
||||
u32 syscfg;
|
||||
u32 cfg_mask;
|
||||
u32 cfg;
|
||||
u32 oscok;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct stih41x_usb_phy - Private data for the PHY
|
||||
* @dev: device for this controller
|
||||
* @regmap: Syscfg registers bank in which PHY is configured
|
||||
* @cfg: SoC specific PHY register mapping
|
||||
* @clk: Oscillator used by the PHY
|
||||
*/
|
||||
struct stih41x_usb_phy {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
const struct stih41x_usb_cfg *cfg;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static struct stih41x_usb_cfg stih415_usb_phy_cfg = {
|
||||
.syscfg = SYSCFG332,
|
||||
.cfg_mask = 0x3f,
|
||||
.cfg = 0x38,
|
||||
.oscok = BIT(6),
|
||||
};
|
||||
|
||||
static struct stih41x_usb_cfg stih416_usb_phy_cfg = {
|
||||
.syscfg = SYSCFG2520,
|
||||
.cfg_mask = 0x33f,
|
||||
.cfg = 0x238,
|
||||
.oscok = BIT(6),
|
||||
};
|
||||
|
||||
static int stih41x_usb_phy_init(struct phy *phy)
|
||||
{
|
||||
struct stih41x_usb_phy *phy_dev = phy_get_drvdata(phy);
|
||||
|
||||
return regmap_update_bits(phy_dev->regmap, phy_dev->cfg->syscfg,
|
||||
phy_dev->cfg->cfg_mask, phy_dev->cfg->cfg);
|
||||
}
|
||||
|
||||
static int stih41x_usb_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct stih41x_usb_phy *phy_dev = phy_get_drvdata(phy);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(phy_dev->clk);
|
||||
if (ret) {
|
||||
dev_err(phy_dev->dev, "Failed to enable osc_phy clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_update_bits(phy_dev->regmap, phy_dev->cfg->syscfg,
|
||||
phy_dev->cfg->oscok, phy_dev->cfg->oscok);
|
||||
}
|
||||
|
||||
static int stih41x_usb_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct stih41x_usb_phy *phy_dev = phy_get_drvdata(phy);
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(phy_dev->regmap, phy_dev->cfg->syscfg,
|
||||
phy_dev->cfg->oscok, 0);
|
||||
if (ret) {
|
||||
dev_err(phy_dev->dev, "Failed to clear oscok bit\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
clk_disable_unprepare(phy_dev->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops stih41x_usb_phy_ops = {
|
||||
.init = stih41x_usb_phy_init,
|
||||
.power_on = stih41x_usb_phy_power_on,
|
||||
.power_off = stih41x_usb_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct of_device_id stih41x_usb_phy_of_match[];
|
||||
|
||||
static int stih41x_usb_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
const struct of_device_id *match;
|
||||
struct stih41x_usb_phy *phy_dev;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy *phy;
|
||||
|
||||
phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL);
|
||||
if (!phy_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
match = of_match_device(stih41x_usb_phy_of_match, &pdev->dev);
|
||||
if (!match)
|
||||
return -ENODEV;
|
||||
|
||||
phy_dev->cfg = match->data;
|
||||
|
||||
phy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
|
||||
if (IS_ERR(phy_dev->regmap)) {
|
||||
dev_err(dev, "No syscfg phandle specified\n");
|
||||
return PTR_ERR(phy_dev->regmap);
|
||||
}
|
||||
|
||||
phy_dev->clk = devm_clk_get(dev, "osc_phy");
|
||||
if (IS_ERR(phy_dev->clk)) {
|
||||
dev_err(dev, "osc_phy clk not found\n");
|
||||
return PTR_ERR(phy_dev->clk);
|
||||
}
|
||||
|
||||
phy = devm_phy_create(dev, NULL, &stih41x_usb_phy_ops, NULL);
|
||||
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create phy\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_dev->dev = dev;
|
||||
|
||||
phy_set_drvdata(phy, phy_dev);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id stih41x_usb_phy_of_match[] = {
|
||||
{ .compatible = "st,stih415-usb-phy", .data = &stih415_usb_phy_cfg },
|
||||
{ .compatible = "st,stih416-usb-phy", .data = &stih416_usb_phy_cfg },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stih41x_usb_phy_of_match);
|
||||
|
||||
static struct platform_driver stih41x_usb_phy_driver = {
|
||||
.probe = stih41x_usb_phy_probe,
|
||||
.driver = {
|
||||
.name = "stih41x-usb-phy",
|
||||
.of_match_table = stih41x_usb_phy_of_match,
|
||||
}
|
||||
};
|
||||
module_platform_driver(stih41x_usb_phy_driver);
|
||||
|
||||
MODULE_AUTHOR("Maxime Coquelin <maxime.coquelin@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics USB PHY driver for STiH41x series");
|
||||
MODULE_LICENSE("GPL v2");
|
334
drivers/phy/phy-sun4i-usb.c
Normal file
334
drivers/phy/phy-sun4i-usb.c
Normal file
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
* Allwinner sun4i USB phy driver
|
||||
*
|
||||
* Copyright (C) 2014 Hans de Goede <hdegoede@redhat.com>
|
||||
*
|
||||
* Based on code from
|
||||
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
||||
*
|
||||
* Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
|
||||
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
||||
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#define REG_ISCR 0x00
|
||||
#define REG_PHYCTL 0x04
|
||||
#define REG_PHYBIST 0x08
|
||||
#define REG_PHYTUNE 0x0c
|
||||
|
||||
#define PHYCTL_DATA BIT(7)
|
||||
|
||||
#define SUNXI_AHB_ICHR8_EN BIT(10)
|
||||
#define SUNXI_AHB_INCR4_BURST_EN BIT(9)
|
||||
#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8)
|
||||
#define SUNXI_ULPI_BYPASS_EN BIT(0)
|
||||
|
||||
/* Common Control Bits for Both PHYs */
|
||||
#define PHY_PLL_BW 0x03
|
||||
#define PHY_RES45_CAL_EN 0x0c
|
||||
|
||||
/* Private Control Bits for Each PHY */
|
||||
#define PHY_TX_AMPLITUDE_TUNE 0x20
|
||||
#define PHY_TX_SLEWRATE_TUNE 0x22
|
||||
#define PHY_VBUSVALID_TH_SEL 0x25
|
||||
#define PHY_PULLUP_RES_SEL 0x27
|
||||
#define PHY_OTG_FUNC_EN 0x28
|
||||
#define PHY_VBUS_DET_EN 0x29
|
||||
#define PHY_DISCON_TH_SEL 0x2a
|
||||
|
||||
#define MAX_PHYS 3
|
||||
|
||||
struct sun4i_usb_phy_data {
|
||||
void __iomem *base;
|
||||
struct mutex mutex;
|
||||
int num_phys;
|
||||
u32 disc_thresh;
|
||||
struct sun4i_usb_phy {
|
||||
struct phy *phy;
|
||||
void __iomem *pmu;
|
||||
struct regulator *vbus;
|
||||
struct reset_control *reset;
|
||||
struct clk *clk;
|
||||
int index;
|
||||
} phys[MAX_PHYS];
|
||||
};
|
||||
|
||||
#define to_sun4i_usb_phy_data(phy) \
|
||||
container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index])
|
||||
|
||||
static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data,
|
||||
int len)
|
||||
{
|
||||
struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy);
|
||||
u32 temp, usbc_bit = BIT(phy->index * 2);
|
||||
int i;
|
||||
|
||||
mutex_lock(&phy_data->mutex);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
temp = readl(phy_data->base + REG_PHYCTL);
|
||||
|
||||
/* clear the address portion */
|
||||
temp &= ~(0xff << 8);
|
||||
|
||||
/* set the address */
|
||||
temp |= ((addr + i) << 8);
|
||||
writel(temp, phy_data->base + REG_PHYCTL);
|
||||
|
||||
/* set the data bit and clear usbc bit*/
|
||||
temp = readb(phy_data->base + REG_PHYCTL);
|
||||
if (data & 0x1)
|
||||
temp |= PHYCTL_DATA;
|
||||
else
|
||||
temp &= ~PHYCTL_DATA;
|
||||
temp &= ~usbc_bit;
|
||||
writeb(temp, phy_data->base + REG_PHYCTL);
|
||||
|
||||
/* pulse usbc_bit */
|
||||
temp = readb(phy_data->base + REG_PHYCTL);
|
||||
temp |= usbc_bit;
|
||||
writeb(temp, phy_data->base + REG_PHYCTL);
|
||||
|
||||
temp = readb(phy_data->base + REG_PHYCTL);
|
||||
temp &= ~usbc_bit;
|
||||
writeb(temp, phy_data->base + REG_PHYCTL);
|
||||
|
||||
data >>= 1;
|
||||
}
|
||||
mutex_unlock(&phy_data->mutex);
|
||||
}
|
||||
|
||||
static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable)
|
||||
{
|
||||
u32 bits, reg_value;
|
||||
|
||||
if (!phy->pmu)
|
||||
return;
|
||||
|
||||
bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN |
|
||||
SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN;
|
||||
|
||||
reg_value = readl(phy->pmu);
|
||||
|
||||
if (enable)
|
||||
reg_value |= bits;
|
||||
else
|
||||
reg_value &= ~bits;
|
||||
|
||||
writel(reg_value, phy->pmu);
|
||||
}
|
||||
|
||||
static int sun4i_usb_phy_init(struct phy *_phy)
|
||||
{
|
||||
struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
|
||||
struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(phy->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_control_deassert(phy->reset);
|
||||
if (ret) {
|
||||
clk_disable_unprepare(phy->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Adjust PHY's magnitude and rate */
|
||||
sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5);
|
||||
|
||||
/* Disconnect threshold adjustment */
|
||||
sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2);
|
||||
|
||||
sun4i_usb_phy_passby(phy, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_usb_phy_exit(struct phy *_phy)
|
||||
{
|
||||
struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
|
||||
|
||||
sun4i_usb_phy_passby(phy, 0);
|
||||
reset_control_assert(phy->reset);
|
||||
clk_disable_unprepare(phy->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_usb_phy_power_on(struct phy *_phy)
|
||||
{
|
||||
struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
|
||||
int ret = 0;
|
||||
|
||||
if (phy->vbus)
|
||||
ret = regulator_enable(phy->vbus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sun4i_usb_phy_power_off(struct phy *_phy)
|
||||
{
|
||||
struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
|
||||
|
||||
if (phy->vbus)
|
||||
regulator_disable(phy->vbus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops sun4i_usb_phy_ops = {
|
||||
.init = sun4i_usb_phy_init,
|
||||
.exit = sun4i_usb_phy_exit,
|
||||
.power_on = sun4i_usb_phy_power_on,
|
||||
.power_off = sun4i_usb_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct phy *sun4i_usb_phy_xlate(struct device *dev,
|
||||
struct of_phandle_args *args)
|
||||
{
|
||||
struct sun4i_usb_phy_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (WARN_ON(args->args[0] == 0 || args->args[0] >= data->num_phys))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return data->phys[args->args[0]].phy;
|
||||
}
|
||||
|
||||
static int sun4i_usb_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sun4i_usb_phy_data *data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct phy_provider *phy_provider;
|
||||
bool dedicated_clocks;
|
||||
struct resource *res;
|
||||
int i;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&data->mutex);
|
||||
|
||||
if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy"))
|
||||
data->num_phys = 2;
|
||||
else
|
||||
data->num_phys = 3;
|
||||
|
||||
if (of_device_is_compatible(np, "allwinner,sun4i-a10-usb-phy"))
|
||||
data->disc_thresh = 3;
|
||||
else
|
||||
data->disc_thresh = 2;
|
||||
|
||||
if (of_device_is_compatible(np, "allwinner,sun6i-a31-usb-phy"))
|
||||
dedicated_clocks = true;
|
||||
else
|
||||
dedicated_clocks = false;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl");
|
||||
data->base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(data->base))
|
||||
return PTR_ERR(data->base);
|
||||
|
||||
/* Skip 0, 0 is the phy for otg which is not yet supported. */
|
||||
for (i = 1; i < data->num_phys; i++) {
|
||||
struct sun4i_usb_phy *phy = data->phys + i;
|
||||
char name[16];
|
||||
|
||||
snprintf(name, sizeof(name), "usb%d_vbus", i);
|
||||
phy->vbus = devm_regulator_get_optional(dev, name);
|
||||
if (IS_ERR(phy->vbus)) {
|
||||
if (PTR_ERR(phy->vbus) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
phy->vbus = NULL;
|
||||
}
|
||||
|
||||
if (dedicated_clocks)
|
||||
snprintf(name, sizeof(name), "usb%d_phy", i);
|
||||
else
|
||||
strlcpy(name, "usb_phy", sizeof(name));
|
||||
|
||||
phy->clk = devm_clk_get(dev, name);
|
||||
if (IS_ERR(phy->clk)) {
|
||||
dev_err(dev, "failed to get clock %s\n", name);
|
||||
return PTR_ERR(phy->clk);
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "usb%d_reset", i);
|
||||
phy->reset = devm_reset_control_get(dev, name);
|
||||
if (IS_ERR(phy->reset)) {
|
||||
dev_err(dev, "failed to get reset %s\n", name);
|
||||
return PTR_ERR(phy->reset);
|
||||
}
|
||||
|
||||
if (i) { /* No pmu for usbc0 */
|
||||
snprintf(name, sizeof(name), "pmu%d", i);
|
||||
res = platform_get_resource_byname(pdev,
|
||||
IORESOURCE_MEM, name);
|
||||
phy->pmu = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(phy->pmu))
|
||||
return PTR_ERR(phy->pmu);
|
||||
}
|
||||
|
||||
phy->phy = devm_phy_create(dev, NULL, &sun4i_usb_phy_ops, NULL);
|
||||
if (IS_ERR(phy->phy)) {
|
||||
dev_err(dev, "failed to create PHY %d\n", i);
|
||||
return PTR_ERR(phy->phy);
|
||||
}
|
||||
|
||||
phy->index = i;
|
||||
phy_set_drvdata(phy->phy, &data->phys[i]);
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, data);
|
||||
phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate);
|
||||
|
||||
return PTR_ERR_OR_ZERO(phy_provider);
|
||||
}
|
||||
|
||||
static const struct of_device_id sun4i_usb_phy_of_match[] = {
|
||||
{ .compatible = "allwinner,sun4i-a10-usb-phy" },
|
||||
{ .compatible = "allwinner,sun5i-a13-usb-phy" },
|
||||
{ .compatible = "allwinner,sun6i-a31-usb-phy" },
|
||||
{ .compatible = "allwinner,sun7i-a20-usb-phy" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match);
|
||||
|
||||
static struct platform_driver sun4i_usb_phy_driver = {
|
||||
.probe = sun4i_usb_phy_probe,
|
||||
.driver = {
|
||||
.of_match_table = sun4i_usb_phy_of_match,
|
||||
.name = "sun4i-usb-phy",
|
||||
}
|
||||
};
|
||||
module_platform_driver(sun4i_usb_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Allwinner sun4i USB phy driver");
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
531
drivers/phy/phy-ti-pipe3.c
Normal file
531
drivers/phy/phy-ti-pipe3.c
Normal file
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* phy-ti-pipe3 - PIPE3 PHY driver.
|
||||
*
|
||||
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/phy/omap_control_phy.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#define PLL_STATUS 0x00000004
|
||||
#define PLL_GO 0x00000008
|
||||
#define PLL_CONFIGURATION1 0x0000000C
|
||||
#define PLL_CONFIGURATION2 0x00000010
|
||||
#define PLL_CONFIGURATION3 0x00000014
|
||||
#define PLL_CONFIGURATION4 0x00000020
|
||||
|
||||
#define PLL_REGM_MASK 0x001FFE00
|
||||
#define PLL_REGM_SHIFT 0x9
|
||||
#define PLL_REGM_F_MASK 0x0003FFFF
|
||||
#define PLL_REGM_F_SHIFT 0x0
|
||||
#define PLL_REGN_MASK 0x000001FE
|
||||
#define PLL_REGN_SHIFT 0x1
|
||||
#define PLL_SELFREQDCO_MASK 0x0000000E
|
||||
#define PLL_SELFREQDCO_SHIFT 0x1
|
||||
#define PLL_SD_MASK 0x0003FC00
|
||||
#define PLL_SD_SHIFT 10
|
||||
#define SET_PLL_GO 0x1
|
||||
#define PLL_LDOPWDN BIT(15)
|
||||
#define PLL_TICOPWDN BIT(16)
|
||||
#define PLL_LOCK 0x2
|
||||
#define PLL_IDLE 0x1
|
||||
|
||||
/*
|
||||
* This is an Empirical value that works, need to confirm the actual
|
||||
* value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status
|
||||
* to be correctly reflected in the PIPE3PHY_PLL_STATUS register.
|
||||
*/
|
||||
#define PLL_IDLE_TIME 100 /* in milliseconds */
|
||||
#define PLL_LOCK_TIME 100 /* in milliseconds */
|
||||
|
||||
struct pipe3_dpll_params {
|
||||
u16 m;
|
||||
u8 n;
|
||||
u8 freq:3;
|
||||
u8 sd;
|
||||
u32 mf;
|
||||
};
|
||||
|
||||
struct pipe3_dpll_map {
|
||||
unsigned long rate;
|
||||
struct pipe3_dpll_params params;
|
||||
};
|
||||
|
||||
struct ti_pipe3 {
|
||||
void __iomem *pll_ctrl_base;
|
||||
struct device *dev;
|
||||
struct device *control_dev;
|
||||
struct clk *wkupclk;
|
||||
struct clk *sys_clk;
|
||||
struct clk *refclk;
|
||||
struct clk *div_clk;
|
||||
struct pipe3_dpll_map *dpll_map;
|
||||
u8 id;
|
||||
};
|
||||
|
||||
static struct pipe3_dpll_map dpll_map_usb[] = {
|
||||
{12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */
|
||||
{16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */
|
||||
{19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */
|
||||
{20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */
|
||||
{26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */
|
||||
{38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */
|
||||
{ }, /* Terminator */
|
||||
};
|
||||
|
||||
static struct pipe3_dpll_map dpll_map_sata[] = {
|
||||
{12000000, {1000, 7, 4, 6, 0} }, /* 12 MHz */
|
||||
{16800000, {714, 7, 4, 6, 0} }, /* 16.8 MHz */
|
||||
{19200000, {625, 7, 4, 6, 0} }, /* 19.2 MHz */
|
||||
{20000000, {600, 7, 4, 6, 0} }, /* 20 MHz */
|
||||
{26000000, {461, 7, 4, 6, 0} }, /* 26 MHz */
|
||||
{38400000, {312, 7, 4, 6, 0} }, /* 38.4 MHz */
|
||||
{ }, /* Terminator */
|
||||
};
|
||||
|
||||
static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset)
|
||||
{
|
||||
return __raw_readl(addr + offset);
|
||||
}
|
||||
|
||||
static inline void ti_pipe3_writel(void __iomem *addr, unsigned offset,
|
||||
u32 data)
|
||||
{
|
||||
__raw_writel(data, addr + offset);
|
||||
}
|
||||
|
||||
static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(struct ti_pipe3 *phy)
|
||||
{
|
||||
unsigned long rate;
|
||||
struct pipe3_dpll_map *dpll_map = phy->dpll_map;
|
||||
|
||||
rate = clk_get_rate(phy->sys_clk);
|
||||
|
||||
for (; dpll_map->rate; dpll_map++) {
|
||||
if (rate == dpll_map->rate)
|
||||
return &dpll_map->params;
|
||||
}
|
||||
|
||||
dev_err(phy->dev, "No DPLL configuration for %lu Hz SYS CLK\n", rate);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ti_pipe3_power_off(struct phy *x)
|
||||
{
|
||||
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
||||
|
||||
omap_control_phy_power(phy->control_dev, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_pipe3_power_on(struct phy *x)
|
||||
{
|
||||
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
||||
|
||||
omap_control_phy_power(phy->control_dev, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy)
|
||||
{
|
||||
u32 val;
|
||||
unsigned long timeout;
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(PLL_LOCK_TIME);
|
||||
do {
|
||||
cpu_relax();
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
|
||||
if (val & PLL_LOCK)
|
||||
break;
|
||||
} while (!time_after(jiffies, timeout));
|
||||
|
||||
if (!(val & PLL_LOCK)) {
|
||||
dev_err(phy->dev, "DPLL failed to lock\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_pipe3_dpll_program(struct ti_pipe3 *phy)
|
||||
{
|
||||
u32 val;
|
||||
struct pipe3_dpll_params *dpll_params;
|
||||
|
||||
dpll_params = ti_pipe3_get_dpll_params(phy);
|
||||
if (!dpll_params)
|
||||
return -EINVAL;
|
||||
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1);
|
||||
val &= ~PLL_REGN_MASK;
|
||||
val |= dpll_params->n << PLL_REGN_SHIFT;
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val);
|
||||
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
|
||||
val &= ~PLL_SELFREQDCO_MASK;
|
||||
val |= dpll_params->freq << PLL_SELFREQDCO_SHIFT;
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val);
|
||||
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1);
|
||||
val &= ~PLL_REGM_MASK;
|
||||
val |= dpll_params->m << PLL_REGM_SHIFT;
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val);
|
||||
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4);
|
||||
val &= ~PLL_REGM_F_MASK;
|
||||
val |= dpll_params->mf << PLL_REGM_F_SHIFT;
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val);
|
||||
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3);
|
||||
val &= ~PLL_SD_MASK;
|
||||
val |= dpll_params->sd << PLL_SD_SHIFT;
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val);
|
||||
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO);
|
||||
|
||||
return ti_pipe3_dpll_wait_lock(phy);
|
||||
}
|
||||
|
||||
static int ti_pipe3_init(struct phy *x)
|
||||
{
|
||||
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
||||
u32 val;
|
||||
int ret = 0;
|
||||
|
||||
if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
|
||||
omap_control_pcie_pcs(phy->control_dev, phy->id, 0xF1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Bring it out of IDLE if it is IDLE */
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
|
||||
if (val & PLL_IDLE) {
|
||||
val &= ~PLL_IDLE;
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val);
|
||||
ret = ti_pipe3_dpll_wait_lock(phy);
|
||||
}
|
||||
|
||||
/* Program the DPLL only if not locked */
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
|
||||
if (!(val & PLL_LOCK))
|
||||
if (ti_pipe3_dpll_program(phy))
|
||||
return -EINVAL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ti_pipe3_exit(struct phy *x)
|
||||
{
|
||||
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
||||
u32 val;
|
||||
unsigned long timeout;
|
||||
|
||||
/* SATA DPLL can't be powered down due to Errata i783 and PCIe
|
||||
* does not have internal DPLL
|
||||
*/
|
||||
if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") ||
|
||||
of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie"))
|
||||
return 0;
|
||||
|
||||
/* Put DPLL in IDLE mode */
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
|
||||
val |= PLL_IDLE;
|
||||
ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val);
|
||||
|
||||
/* wait for LDO and Oscillator to power down */
|
||||
timeout = jiffies + msecs_to_jiffies(PLL_IDLE_TIME);
|
||||
do {
|
||||
cpu_relax();
|
||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
|
||||
if ((val & PLL_TICOPWDN) && (val & PLL_LDOPWDN))
|
||||
break;
|
||||
} while (!time_after(jiffies, timeout));
|
||||
|
||||
if (!(val & PLL_TICOPWDN) || !(val & PLL_LDOPWDN)) {
|
||||
dev_err(phy->dev, "Failed to power down: PLL_STATUS 0x%x\n",
|
||||
val);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static struct phy_ops ops = {
|
||||
.init = ti_pipe3_init,
|
||||
.exit = ti_pipe3_exit,
|
||||
.power_on = ti_pipe3_power_on,
|
||||
.power_off = ti_pipe3_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id ti_pipe3_id_table[];
|
||||
#endif
|
||||
|
||||
static int ti_pipe3_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ti_pipe3 *phy;
|
||||
struct phy *generic_phy;
|
||||
struct phy_provider *phy_provider;
|
||||
struct resource *res;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *control_node;
|
||||
struct platform_device *control_pdev;
|
||||
const struct of_device_id *match;
|
||||
struct clk *clk;
|
||||
|
||||
phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return -ENOMEM;
|
||||
|
||||
phy->dev = &pdev->dev;
|
||||
|
||||
if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
|
||||
match = of_match_device(of_match_ptr(ti_pipe3_id_table),
|
||||
&pdev->dev);
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
|
||||
phy->dpll_map = (struct pipe3_dpll_map *)match->data;
|
||||
if (!phy->dpll_map) {
|
||||
dev_err(&pdev->dev, "no DPLL data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"pll_ctrl");
|
||||
phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(phy->pll_ctrl_base))
|
||||
return PTR_ERR(phy->pll_ctrl_base);
|
||||
|
||||
phy->sys_clk = devm_clk_get(phy->dev, "sysclk");
|
||||
if (IS_ERR(phy->sys_clk)) {
|
||||
dev_err(&pdev->dev, "unable to get sysclk\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
|
||||
phy->wkupclk = devm_clk_get(phy->dev, "wkupclk");
|
||||
if (IS_ERR(phy->wkupclk)) {
|
||||
dev_err(&pdev->dev, "unable to get wkupclk\n");
|
||||
return PTR_ERR(phy->wkupclk);
|
||||
}
|
||||
|
||||
phy->refclk = devm_clk_get(phy->dev, "refclk");
|
||||
if (IS_ERR(phy->refclk)) {
|
||||
dev_err(&pdev->dev, "unable to get refclk\n");
|
||||
return PTR_ERR(phy->refclk);
|
||||
}
|
||||
} else {
|
||||
phy->wkupclk = ERR_PTR(-ENODEV);
|
||||
phy->refclk = ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
|
||||
if (of_property_read_u8(node, "id", &phy->id) < 0)
|
||||
phy->id = 1;
|
||||
|
||||
clk = devm_clk_get(phy->dev, "dpll_ref");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "unable to get dpll ref clk\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
clk_set_rate(clk, 1500000000);
|
||||
|
||||
clk = devm_clk_get(phy->dev, "dpll_ref_m2");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "unable to get dpll ref m2 clk\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
clk_set_rate(clk, 100000000);
|
||||
|
||||
clk = devm_clk_get(phy->dev, "phy-div");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "unable to get phy-div clk\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
clk_set_rate(clk, 100000000);
|
||||
|
||||
phy->div_clk = devm_clk_get(phy->dev, "div-clk");
|
||||
if (IS_ERR(phy->div_clk)) {
|
||||
dev_err(&pdev->dev, "unable to get div-clk\n");
|
||||
return PTR_ERR(phy->div_clk);
|
||||
}
|
||||
} else {
|
||||
phy->div_clk = ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
control_node = of_parse_phandle(node, "ctrl-module", 0);
|
||||
if (!control_node) {
|
||||
dev_err(&pdev->dev, "Failed to get control device phandle\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
control_pdev = of_find_device_by_node(control_node);
|
||||
if (!control_pdev) {
|
||||
dev_err(&pdev->dev, "Failed to get control device\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
phy->control_dev = &control_pdev->dev;
|
||||
|
||||
omap_control_phy_power(phy->control_dev, 0);
|
||||
|
||||
platform_set_drvdata(pdev, phy);
|
||||
pm_runtime_enable(phy->dev);
|
||||
|
||||
generic_phy = devm_phy_create(phy->dev, NULL, &ops, NULL);
|
||||
if (IS_ERR(generic_phy))
|
||||
return PTR_ERR(generic_phy);
|
||||
|
||||
phy_set_drvdata(generic_phy, phy);
|
||||
phy_provider = devm_of_phy_provider_register(phy->dev,
|
||||
of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
pm_runtime_get(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_pipe3_remove(struct platform_device *pdev)
|
||||
{
|
||||
if (!pm_runtime_suspended(&pdev->dev))
|
||||
pm_runtime_put(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
|
||||
static int ti_pipe3_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct ti_pipe3 *phy = dev_get_drvdata(dev);
|
||||
|
||||
if (!IS_ERR(phy->wkupclk))
|
||||
clk_disable_unprepare(phy->wkupclk);
|
||||
if (!IS_ERR(phy->refclk))
|
||||
clk_disable_unprepare(phy->refclk);
|
||||
if (!IS_ERR(phy->div_clk))
|
||||
clk_disable_unprepare(phy->div_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_pipe3_runtime_resume(struct device *dev)
|
||||
{
|
||||
u32 ret = 0;
|
||||
struct ti_pipe3 *phy = dev_get_drvdata(dev);
|
||||
|
||||
if (!IS_ERR(phy->refclk)) {
|
||||
ret = clk_prepare_enable(phy->refclk);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "Failed to enable refclk %d\n", ret);
|
||||
goto err1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IS_ERR(phy->wkupclk)) {
|
||||
ret = clk_prepare_enable(phy->wkupclk);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret);
|
||||
goto err2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IS_ERR(phy->div_clk)) {
|
||||
ret = clk_prepare_enable(phy->div_clk);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "Failed to enable div_clk %d\n", ret);
|
||||
goto err3;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
err3:
|
||||
if (!IS_ERR(phy->wkupclk))
|
||||
clk_disable_unprepare(phy->wkupclk);
|
||||
|
||||
err2:
|
||||
if (!IS_ERR(phy->refclk))
|
||||
clk_disable_unprepare(phy->refclk);
|
||||
|
||||
err1:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ti_pipe3_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(ti_pipe3_runtime_suspend,
|
||||
ti_pipe3_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
#define DEV_PM_OPS (&ti_pipe3_pm_ops)
|
||||
#else
|
||||
#define DEV_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id ti_pipe3_id_table[] = {
|
||||
{
|
||||
.compatible = "ti,phy-usb3",
|
||||
.data = dpll_map_usb,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,omap-usb3",
|
||||
.data = dpll_map_usb,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,phy-pipe3-sata",
|
||||
.data = dpll_map_sata,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,phy-pipe3-pcie",
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ti_pipe3_id_table);
|
||||
#endif
|
||||
|
||||
static struct platform_driver ti_pipe3_driver = {
|
||||
.probe = ti_pipe3_probe,
|
||||
.remove = ti_pipe3_remove,
|
||||
.driver = {
|
||||
.name = "ti-pipe3",
|
||||
.pm = DEV_PM_OPS,
|
||||
.of_match_table = of_match_ptr(ti_pipe3_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(ti_pipe3_driver);
|
||||
|
||||
MODULE_ALIAS("platform: ti_pipe3");
|
||||
MODULE_AUTHOR("Texas Instruments Inc.");
|
||||
MODULE_DESCRIPTION("TI PIPE3 phy driver");
|
||||
MODULE_LICENSE("GPL v2");
|
807
drivers/phy/phy-twl4030-usb.c
Normal file
807
drivers/phy/phy-twl4030-usb.c
Normal file
|
@ -0,0 +1,807 @@
|
|||
/*
|
||||
* twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller
|
||||
*
|
||||
* Copyright (C) 2004-2007 Texas Instruments
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
* Contact: Felipe Balbi <felipe.balbi@nokia.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* Current status:
|
||||
* - HS USB ULPI mode works.
|
||||
* - 3-pin mode support may be added in future.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/usb/musb-omap.h>
|
||||
#include <linux/usb/ulpi.h>
|
||||
#include <linux/i2c/twl.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* Register defines */
|
||||
|
||||
#define MCPC_CTRL 0x30
|
||||
#define MCPC_CTRL_RTSOL (1 << 7)
|
||||
#define MCPC_CTRL_EXTSWR (1 << 6)
|
||||
#define MCPC_CTRL_EXTSWC (1 << 5)
|
||||
#define MCPC_CTRL_VOICESW (1 << 4)
|
||||
#define MCPC_CTRL_OUT64K (1 << 3)
|
||||
#define MCPC_CTRL_RTSCTSSW (1 << 2)
|
||||
#define MCPC_CTRL_HS_UART (1 << 0)
|
||||
|
||||
#define MCPC_IO_CTRL 0x33
|
||||
#define MCPC_IO_CTRL_MICBIASEN (1 << 5)
|
||||
#define MCPC_IO_CTRL_CTS_NPU (1 << 4)
|
||||
#define MCPC_IO_CTRL_RXD_PU (1 << 3)
|
||||
#define MCPC_IO_CTRL_TXDTYP (1 << 2)
|
||||
#define MCPC_IO_CTRL_CTSTYP (1 << 1)
|
||||
#define MCPC_IO_CTRL_RTSTYP (1 << 0)
|
||||
|
||||
#define MCPC_CTRL2 0x36
|
||||
#define MCPC_CTRL2_MCPC_CK_EN (1 << 0)
|
||||
|
||||
#define OTHER_FUNC_CTRL 0x80
|
||||
#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4)
|
||||
#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2)
|
||||
|
||||
#define OTHER_IFC_CTRL 0x83
|
||||
#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6)
|
||||
#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5)
|
||||
#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4)
|
||||
#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3)
|
||||
#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2)
|
||||
#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0)
|
||||
|
||||
#define OTHER_INT_EN_RISE 0x86
|
||||
#define OTHER_INT_EN_FALL 0x89
|
||||
#define OTHER_INT_STS 0x8C
|
||||
#define OTHER_INT_LATCH 0x8D
|
||||
#define OTHER_INT_VB_SESS_VLD (1 << 7)
|
||||
#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */
|
||||
#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */
|
||||
#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */
|
||||
#define OTHER_INT_MANU (1 << 1)
|
||||
#define OTHER_INT_ABNORMAL_STRESS (1 << 0)
|
||||
|
||||
#define ID_STATUS 0x96
|
||||
#define ID_RES_FLOAT (1 << 4)
|
||||
#define ID_RES_440K (1 << 3)
|
||||
#define ID_RES_200K (1 << 2)
|
||||
#define ID_RES_102K (1 << 1)
|
||||
#define ID_RES_GND (1 << 0)
|
||||
|
||||
#define POWER_CTRL 0xAC
|
||||
#define POWER_CTRL_OTG_ENAB (1 << 5)
|
||||
|
||||
#define OTHER_IFC_CTRL2 0xAF
|
||||
#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4)
|
||||
#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3)
|
||||
#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2)
|
||||
#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */
|
||||
#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0)
|
||||
#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0)
|
||||
|
||||
#define REG_CTRL_EN 0xB2
|
||||
#define REG_CTRL_ERROR 0xB5
|
||||
#define ULPI_I2C_CONFLICT_INTEN (1 << 0)
|
||||
|
||||
#define OTHER_FUNC_CTRL2 0xB8
|
||||
#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0)
|
||||
|
||||
/* following registers do not have separate _clr and _set registers */
|
||||
#define VBUS_DEBOUNCE 0xC0
|
||||
#define ID_DEBOUNCE 0xC1
|
||||
#define VBAT_TIMER 0xD3
|
||||
#define PHY_PWR_CTRL 0xFD
|
||||
#define PHY_PWR_PHYPWD (1 << 0)
|
||||
#define PHY_CLK_CTRL 0xFE
|
||||
#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2)
|
||||
#define PHY_CLK_CTRL_CLK32K_EN (1 << 1)
|
||||
#define REQ_PHY_DPLL_CLK (1 << 0)
|
||||
#define PHY_CLK_CTRL_STS 0xFF
|
||||
#define PHY_DPLL_CLK (1 << 0)
|
||||
|
||||
/* In module TWL_MODULE_PM_MASTER */
|
||||
#define STS_HW_CONDITIONS 0x0F
|
||||
|
||||
/* In module TWL_MODULE_PM_RECEIVER */
|
||||
#define VUSB_DEDICATED1 0x7D
|
||||
#define VUSB_DEDICATED2 0x7E
|
||||
#define VUSB1V5_DEV_GRP 0x71
|
||||
#define VUSB1V5_TYPE 0x72
|
||||
#define VUSB1V5_REMAP 0x73
|
||||
#define VUSB1V8_DEV_GRP 0x74
|
||||
#define VUSB1V8_TYPE 0x75
|
||||
#define VUSB1V8_REMAP 0x76
|
||||
#define VUSB3V1_DEV_GRP 0x77
|
||||
#define VUSB3V1_TYPE 0x78
|
||||
#define VUSB3V1_REMAP 0x79
|
||||
|
||||
/* In module TWL4030_MODULE_INTBR */
|
||||
#define PMBR1 0x0D
|
||||
#define GPIO_USB_4PIN_ULPI_2430C (3 << 0)
|
||||
|
||||
struct twl4030_usb {
|
||||
struct usb_phy phy;
|
||||
struct device *dev;
|
||||
|
||||
/* TWL4030 internal USB regulator supplies */
|
||||
struct regulator *usb1v5;
|
||||
struct regulator *usb1v8;
|
||||
struct regulator *usb3v1;
|
||||
|
||||
/* for vbus reporting with irqs disabled */
|
||||
struct mutex lock;
|
||||
|
||||
/* pin configuration */
|
||||
enum twl4030_usb_mode usb_mode;
|
||||
|
||||
int irq;
|
||||
enum omap_musb_vbus_id_status linkstat;
|
||||
bool vbus_supplied;
|
||||
|
||||
struct delayed_work id_workaround_work;
|
||||
};
|
||||
|
||||
/* internal define on top of container_of */
|
||||
#define phy_to_twl(x) container_of((x), struct twl4030_usb, phy)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl,
|
||||
u8 module, u8 data, u8 address)
|
||||
{
|
||||
u8 check;
|
||||
|
||||
if ((twl_i2c_write_u8(module, data, address) >= 0) &&
|
||||
(twl_i2c_read_u8(module, &check, address) >= 0) &&
|
||||
(check == data))
|
||||
return 0;
|
||||
dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n",
|
||||
1, module, address, check, data);
|
||||
|
||||
/* Failed once: Try again */
|
||||
if ((twl_i2c_write_u8(module, data, address) >= 0) &&
|
||||
(twl_i2c_read_u8(module, &check, address) >= 0) &&
|
||||
(check == data))
|
||||
return 0;
|
||||
dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n",
|
||||
2, module, address, check, data);
|
||||
|
||||
/* Failed again: Return error */
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
#define twl4030_usb_write_verify(twl, address, data) \
|
||||
twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address))
|
||||
|
||||
static inline int twl4030_usb_write(struct twl4030_usb *twl,
|
||||
u8 address, u8 data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address);
|
||||
if (ret < 0)
|
||||
dev_dbg(twl->dev,
|
||||
"TWL4030:USB:Write[0x%x] Error %d\n", address, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address)
|
||||
{
|
||||
u8 data;
|
||||
int ret = 0;
|
||||
|
||||
ret = twl_i2c_read_u8(module, &data, address);
|
||||
if (ret >= 0)
|
||||
ret = data;
|
||||
else
|
||||
dev_dbg(twl->dev,
|
||||
"TWL4030:readb[0x%x,0x%x] Error %d\n",
|
||||
module, address, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address)
|
||||
{
|
||||
return twl4030_readb(twl, TWL_MODULE_USB, address);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static inline int
|
||||
twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
|
||||
{
|
||||
return twl4030_usb_write(twl, ULPI_SET(reg), bits);
|
||||
}
|
||||
|
||||
static inline int
|
||||
twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
|
||||
{
|
||||
return twl4030_usb_write(twl, ULPI_CLR(reg), bits);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static bool twl4030_is_driving_vbus(struct twl4030_usb *twl)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS);
|
||||
if (ret < 0 || !(ret & PHY_DPLL_CLK))
|
||||
/*
|
||||
* if clocks are off, registers are not updated,
|
||||
* but we can assume we don't drive VBUS in this case
|
||||
*/
|
||||
return false;
|
||||
|
||||
ret = twl4030_usb_read(twl, ULPI_OTG_CTRL);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false;
|
||||
}
|
||||
|
||||
static enum omap_musb_vbus_id_status
|
||||
twl4030_usb_linkstat(struct twl4030_usb *twl)
|
||||
{
|
||||
int status;
|
||||
enum omap_musb_vbus_id_status linkstat = OMAP_MUSB_UNKNOWN;
|
||||
|
||||
twl->vbus_supplied = false;
|
||||
|
||||
/*
|
||||
* For ID/VBUS sensing, see manual section 15.4.8 ...
|
||||
* except when using only battery backup power, two
|
||||
* comparators produce VBUS_PRES and ID_PRES signals,
|
||||
* which don't match docs elsewhere. But ... BIT(7)
|
||||
* and BIT(2) of STS_HW_CONDITIONS, respectively, do
|
||||
* seem to match up. If either is true the USB_PRES
|
||||
* signal is active, the OTG module is activated, and
|
||||
* its interrupt may be raised (may wake the system).
|
||||
*/
|
||||
status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS);
|
||||
if (status < 0)
|
||||
dev_err(twl->dev, "USB link status err %d\n", status);
|
||||
else if (status & (BIT(7) | BIT(2))) {
|
||||
if (status & BIT(7)) {
|
||||
if (twl4030_is_driving_vbus(twl))
|
||||
status &= ~BIT(7);
|
||||
else
|
||||
twl->vbus_supplied = true;
|
||||
}
|
||||
|
||||
if (status & BIT(2))
|
||||
linkstat = OMAP_MUSB_ID_GROUND;
|
||||
else if (status & BIT(7))
|
||||
linkstat = OMAP_MUSB_VBUS_VALID;
|
||||
else
|
||||
linkstat = OMAP_MUSB_VBUS_OFF;
|
||||
} else {
|
||||
if (twl->linkstat != OMAP_MUSB_UNKNOWN)
|
||||
linkstat = OMAP_MUSB_VBUS_OFF;
|
||||
}
|
||||
|
||||
dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n",
|
||||
status, status, linkstat);
|
||||
|
||||
/* REVISIT this assumes host and peripheral controllers
|
||||
* are registered, and that both are active...
|
||||
*/
|
||||
|
||||
return linkstat;
|
||||
}
|
||||
|
||||
static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode)
|
||||
{
|
||||
twl->usb_mode = mode;
|
||||
|
||||
switch (mode) {
|
||||
case T2_USB_MODE_ULPI:
|
||||
twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL,
|
||||
ULPI_IFC_CTRL_CARKITMODE);
|
||||
twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
|
||||
twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL,
|
||||
ULPI_FUNC_CTRL_XCVRSEL_MASK |
|
||||
ULPI_FUNC_CTRL_OPMODE_MASK);
|
||||
break;
|
||||
case -1:
|
||||
/* FIXME: power on defaults */
|
||||
break;
|
||||
default:
|
||||
dev_err(twl->dev, "unsupported T2 transceiver mode %d\n",
|
||||
mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void twl4030_i2c_access(struct twl4030_usb *twl, int on)
|
||||
{
|
||||
unsigned long timeout;
|
||||
int val = twl4030_usb_read(twl, PHY_CLK_CTRL);
|
||||
|
||||
if (val >= 0) {
|
||||
if (on) {
|
||||
/* enable DPLL to access PHY registers over I2C */
|
||||
val |= REQ_PHY_DPLL_CLK;
|
||||
WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL,
|
||||
(u8)val) < 0);
|
||||
|
||||
timeout = jiffies + HZ;
|
||||
while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) &
|
||||
PHY_DPLL_CLK)
|
||||
&& time_before(jiffies, timeout))
|
||||
udelay(10);
|
||||
if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) &
|
||||
PHY_DPLL_CLK))
|
||||
dev_err(twl->dev, "Timeout setting T2 HSUSB "
|
||||
"PHY DPLL clock\n");
|
||||
} else {
|
||||
/* let ULPI control the DPLL clock */
|
||||
val &= ~REQ_PHY_DPLL_CLK;
|
||||
WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL,
|
||||
(u8)val) < 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __twl4030_phy_power(struct twl4030_usb *twl, int on)
|
||||
{
|
||||
u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL);
|
||||
|
||||
if (on)
|
||||
pwr &= ~PHY_PWR_PHYPWD;
|
||||
else
|
||||
pwr |= PHY_PWR_PHYPWD;
|
||||
|
||||
WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0);
|
||||
}
|
||||
|
||||
static int twl4030_usb_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct twl4030_usb *twl = dev_get_drvdata(dev);
|
||||
|
||||
dev_dbg(twl->dev, "%s\n", __func__);
|
||||
if (pm_runtime_suspended(dev))
|
||||
return 0;
|
||||
|
||||
__twl4030_phy_power(twl, 0);
|
||||
regulator_disable(twl->usb1v5);
|
||||
regulator_disable(twl->usb1v8);
|
||||
regulator_disable(twl->usb3v1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_usb_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct twl4030_usb *twl = dev_get_drvdata(dev);
|
||||
int res;
|
||||
|
||||
dev_dbg(twl->dev, "%s\n", __func__);
|
||||
if (pm_runtime_active(dev))
|
||||
return 0;
|
||||
|
||||
res = regulator_enable(twl->usb3v1);
|
||||
if (res)
|
||||
dev_err(twl->dev, "Failed to enable usb3v1\n");
|
||||
|
||||
res = regulator_enable(twl->usb1v8);
|
||||
if (res)
|
||||
dev_err(twl->dev, "Failed to enable usb1v8\n");
|
||||
|
||||
/*
|
||||
* Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP
|
||||
* in twl4030) resets the VUSB_DEDICATED2 register. This reset
|
||||
* enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to
|
||||
* SLEEP. We work around this by clearing the bit after usv3v1
|
||||
* is re-activated. This ensures that VUSB3V1 is really active.
|
||||
*/
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);
|
||||
|
||||
res = regulator_enable(twl->usb1v5);
|
||||
if (res)
|
||||
dev_err(twl->dev, "Failed to enable usb1v5\n");
|
||||
|
||||
__twl4030_phy_power(twl, 1);
|
||||
twl4030_usb_write(twl, PHY_CLK_CTRL,
|
||||
twl4030_usb_read(twl, PHY_CLK_CTRL) |
|
||||
(PHY_CLK_CTRL_CLOCKGATING_EN |
|
||||
PHY_CLK_CTRL_CLK32K_EN));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct twl4030_usb *twl = phy_get_drvdata(phy);
|
||||
|
||||
dev_dbg(twl->dev, "%s\n", __func__);
|
||||
pm_runtime_mark_last_busy(twl->dev);
|
||||
pm_runtime_put_autosuspend(twl->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct twl4030_usb *twl = phy_get_drvdata(phy);
|
||||
|
||||
dev_dbg(twl->dev, "%s\n", __func__);
|
||||
pm_runtime_get_sync(twl->dev);
|
||||
twl4030_i2c_access(twl, 1);
|
||||
twl4030_usb_set_mode(twl, twl->usb_mode);
|
||||
if (twl->usb_mode == T2_USB_MODE_ULPI)
|
||||
twl4030_i2c_access(twl, 0);
|
||||
schedule_delayed_work(&twl->id_workaround_work, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_usb_ldo_init(struct twl4030_usb *twl)
|
||||
{
|
||||
/* Enable writing to power configuration registers */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1,
|
||||
TWL4030_PM_MASTER_PROTECT_KEY);
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2,
|
||||
TWL4030_PM_MASTER_PROTECT_KEY);
|
||||
|
||||
/* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/
|
||||
/*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/
|
||||
|
||||
/* input to VUSB3V1 LDO is from VBAT, not VBUS */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1);
|
||||
|
||||
/* Initialize 3.1V regulator */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP);
|
||||
|
||||
twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1");
|
||||
if (IS_ERR(twl->usb3v1))
|
||||
return -ENODEV;
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE);
|
||||
|
||||
/* Initialize 1.5V regulator */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP);
|
||||
|
||||
twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5");
|
||||
if (IS_ERR(twl->usb1v5))
|
||||
return -ENODEV;
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE);
|
||||
|
||||
/* Initialize 1.8V regulator */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP);
|
||||
|
||||
twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8");
|
||||
if (IS_ERR(twl->usb1v8))
|
||||
return -ENODEV;
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE);
|
||||
|
||||
/* disable access to power configuration registers */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0,
|
||||
TWL4030_PM_MASTER_PROTECT_KEY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t twl4030_usb_vbus_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct twl4030_usb *twl = dev_get_drvdata(dev);
|
||||
int ret = -EINVAL;
|
||||
|
||||
mutex_lock(&twl->lock);
|
||||
ret = sprintf(buf, "%s\n",
|
||||
twl->vbus_supplied ? "on" : "off");
|
||||
mutex_unlock(&twl->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
|
||||
|
||||
static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
|
||||
{
|
||||
struct twl4030_usb *twl = _twl;
|
||||
enum omap_musb_vbus_id_status status;
|
||||
bool status_changed = false;
|
||||
|
||||
status = twl4030_usb_linkstat(twl);
|
||||
|
||||
mutex_lock(&twl->lock);
|
||||
if (status >= 0 && status != twl->linkstat) {
|
||||
twl->linkstat = status;
|
||||
status_changed = true;
|
||||
}
|
||||
mutex_unlock(&twl->lock);
|
||||
|
||||
if (status_changed) {
|
||||
/* FIXME add a set_power() method so that B-devices can
|
||||
* configure the charger appropriately. It's not always
|
||||
* correct to consume VBUS power, and how much current to
|
||||
* consume is a function of the USB configuration chosen
|
||||
* by the host.
|
||||
*
|
||||
* REVISIT usb_gadget_vbus_connect(...) as needed, ditto
|
||||
* its disconnect() sibling, when changing to/from the
|
||||
* USB_LINK_VBUS state. musb_hdrc won't care until it
|
||||
* starts to handle softconnect right.
|
||||
*/
|
||||
if ((status == OMAP_MUSB_VBUS_VALID) ||
|
||||
(status == OMAP_MUSB_ID_GROUND)) {
|
||||
if (pm_runtime_suspended(twl->dev))
|
||||
pm_runtime_get_sync(twl->dev);
|
||||
} else {
|
||||
if (pm_runtime_active(twl->dev)) {
|
||||
pm_runtime_mark_last_busy(twl->dev);
|
||||
pm_runtime_put_autosuspend(twl->dev);
|
||||
}
|
||||
}
|
||||
omap_musb_mailbox(status);
|
||||
}
|
||||
|
||||
/* don't schedule during sleep - irq works right then */
|
||||
if (status == OMAP_MUSB_ID_GROUND && pm_runtime_active(twl->dev)) {
|
||||
cancel_delayed_work(&twl->id_workaround_work);
|
||||
schedule_delayed_work(&twl->id_workaround_work, HZ);
|
||||
}
|
||||
|
||||
if (irq)
|
||||
sysfs_notify(&twl->dev->kobj, NULL, "vbus");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void twl4030_id_workaround_work(struct work_struct *work)
|
||||
{
|
||||
struct twl4030_usb *twl = container_of(work, struct twl4030_usb,
|
||||
id_workaround_work.work);
|
||||
|
||||
twl4030_usb_irq(0, twl);
|
||||
}
|
||||
|
||||
static int twl4030_phy_init(struct phy *phy)
|
||||
{
|
||||
struct twl4030_usb *twl = phy_get_drvdata(phy);
|
||||
|
||||
pm_runtime_get_sync(twl->dev);
|
||||
schedule_delayed_work(&twl->id_workaround_work, 0);
|
||||
pm_runtime_mark_last_busy(twl->dev);
|
||||
pm_runtime_put_autosuspend(twl->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
otg->gadget = gadget;
|
||||
if (!gadget)
|
||||
otg->phy->state = OTG_STATE_UNDEFINED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
otg->host = host;
|
||||
if (!host)
|
||||
otg->phy->state = OTG_STATE_UNDEFINED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct phy_ops ops = {
|
||||
.init = twl4030_phy_init,
|
||||
.power_on = twl4030_phy_power_on,
|
||||
.power_off = twl4030_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct dev_pm_ops twl4030_usb_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend,
|
||||
twl4030_usb_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static int twl4030_usb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct twl4030_usb_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct twl4030_usb *twl;
|
||||
struct phy *phy;
|
||||
int status, err;
|
||||
struct usb_otg *otg;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy_init_data *init_data = NULL;
|
||||
|
||||
twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
|
||||
if (!twl)
|
||||
return -ENOMEM;
|
||||
|
||||
if (np)
|
||||
of_property_read_u32(np, "usb_mode",
|
||||
(enum twl4030_usb_mode *)&twl->usb_mode);
|
||||
else if (pdata) {
|
||||
twl->usb_mode = pdata->usb_mode;
|
||||
init_data = pdata->init_data;
|
||||
} else {
|
||||
dev_err(&pdev->dev, "twl4030 initialized without pdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
|
||||
if (!otg)
|
||||
return -ENOMEM;
|
||||
|
||||
twl->dev = &pdev->dev;
|
||||
twl->irq = platform_get_irq(pdev, 0);
|
||||
twl->vbus_supplied = false;
|
||||
twl->linkstat = -EINVAL;
|
||||
twl->linkstat = OMAP_MUSB_UNKNOWN;
|
||||
|
||||
twl->phy.dev = twl->dev;
|
||||
twl->phy.label = "twl4030";
|
||||
twl->phy.otg = otg;
|
||||
twl->phy.type = USB_PHY_TYPE_USB2;
|
||||
|
||||
otg->phy = &twl->phy;
|
||||
otg->set_host = twl4030_set_host;
|
||||
otg->set_peripheral = twl4030_set_peripheral;
|
||||
|
||||
phy = devm_phy_create(twl->dev, NULL, &ops, init_data);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_dbg(&pdev->dev, "Failed to create PHY\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_set_drvdata(phy, twl);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(twl->dev,
|
||||
of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
/* init mutex for workqueue */
|
||||
mutex_init(&twl->lock);
|
||||
|
||||
INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work);
|
||||
|
||||
err = twl4030_usb_ldo_init(twl);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "ldo init failed\n");
|
||||
return err;
|
||||
}
|
||||
usb_add_phy_dev(&twl->phy);
|
||||
|
||||
platform_set_drvdata(pdev, twl);
|
||||
if (device_create_file(&pdev->dev, &dev_attr_vbus))
|
||||
dev_warn(&pdev->dev, "could not create sysfs file\n");
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier);
|
||||
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
|
||||
/* Our job is to use irqs and status from the power module
|
||||
* to keep the transceiver disabled when nothing's connected.
|
||||
*
|
||||
* FIXME we actually shouldn't start enabling it until the
|
||||
* USB controller drivers have said they're ready, by calling
|
||||
* set_host() and/or set_peripheral() ... OTG_capable boards
|
||||
* need both handles, otherwise just one suffices.
|
||||
*/
|
||||
status = devm_request_threaded_irq(twl->dev, twl->irq, NULL,
|
||||
twl4030_usb_irq, IRQF_TRIGGER_FALLING |
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl);
|
||||
if (status < 0) {
|
||||
dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
|
||||
twl->irq, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
pm_runtime_mark_last_busy(&pdev->dev);
|
||||
pm_runtime_put_autosuspend(twl->dev);
|
||||
|
||||
dev_info(&pdev->dev, "Initialized TWL4030 USB module\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_usb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct twl4030_usb *twl = platform_get_drvdata(pdev);
|
||||
int val;
|
||||
|
||||
pm_runtime_get_sync(twl->dev);
|
||||
cancel_delayed_work(&twl->id_workaround_work);
|
||||
device_remove_file(twl->dev, &dev_attr_vbus);
|
||||
|
||||
/* set transceiver mode to power on defaults */
|
||||
twl4030_usb_set_mode(twl, -1);
|
||||
|
||||
/* autogate 60MHz ULPI clock,
|
||||
* clear dpll clock request for i2c access,
|
||||
* disable 32KHz
|
||||
*/
|
||||
val = twl4030_usb_read(twl, PHY_CLK_CTRL);
|
||||
if (val >= 0) {
|
||||
val |= PHY_CLK_CTRL_CLOCKGATING_EN;
|
||||
val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK);
|
||||
twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val);
|
||||
}
|
||||
|
||||
/* disable complete OTG block */
|
||||
twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
|
||||
pm_runtime_mark_last_busy(twl->dev);
|
||||
pm_runtime_put(twl->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id twl4030_usb_id_table[] = {
|
||||
{ .compatible = "ti,twl4030-usb" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, twl4030_usb_id_table);
|
||||
#endif
|
||||
|
||||
static struct platform_driver twl4030_usb_driver = {
|
||||
.probe = twl4030_usb_probe,
|
||||
.remove = twl4030_usb_remove,
|
||||
.driver = {
|
||||
.name = "twl4030_usb",
|
||||
.pm = &twl4030_usb_pm_ops,
|
||||
.of_match_table = of_match_ptr(twl4030_usb_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init twl4030_usb_init(void)
|
||||
{
|
||||
return platform_driver_register(&twl4030_usb_driver);
|
||||
}
|
||||
subsys_initcall(twl4030_usb_init);
|
||||
|
||||
static void __exit twl4030_usb_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&twl4030_usb_driver);
|
||||
}
|
||||
module_exit(twl4030_usb_exit);
|
||||
|
||||
MODULE_ALIAS("platform:twl4030_usb");
|
||||
MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation");
|
||||
MODULE_DESCRIPTION("TWL4030 USB transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
1749
drivers/phy/phy-xgene.c
Normal file
1749
drivers/phy/phy-xgene.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue