Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

289
drivers/phy/Kconfig Normal file
View 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
View 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

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

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

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

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

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

File diff suppressed because it is too large Load diff

View 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__ */

View 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,
};

View 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,
};

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

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

View 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,
};

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

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

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

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

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

View 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,
};

View 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__ */

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

View 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

View 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);
}
}

View 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

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

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

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

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

View 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

File diff suppressed because it is too large Load diff