phy: for 4.5

*) new PHY driver for hi6220 usb and rcar gen3 usb2
 *) deprecate phy-omap-control driver. phy-omap-control driver was added
    when there was no proper infrastructure for doing control module
    initialization. The phy-omap-control driver is not an 'actual' PHY
    driver and it was just a hack to do PHY related control module
    initialization. Now with SYSCON framework in the kernel, control
    module setttings can be done using APIs provided by syscon.
 *) usbphy-internal pll creates the needed 480MHz and is also a
    supply-clock back to the core clock-controller in Rockchip SoCs.
    This is now modeled as a real clock.
 *) calibrate mt65xx usb3 PHY for better eye diagram and receiver
    sensitivity.
 *) Miscellaneous cleanups.
 
 Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1.4.11 (GNU/Linux)
 
 iQIcBAABAgAGBQJWeR+CAAoJEA5ceFyATYLZG8QP/jlux4ejRKhaO/BYd2gSEOij
 H6CO+3O+oQqgKHdDvgGrE5vAqRfxBZI9UErOEBUcPOY+Qdp0s5Wg657WuJR1sSDK
 iNwho1AS2ZuQHqxVDaDToBCTIwliDVkT0+eIgq5tGZaHBh0/iOUEzjnODcaR36a/
 VFPPTTFmkeX9Y0qzyWx/eVASBNguKo6/8/h9PV0zIFkunaE28rDvR2F9x4QSfBaV
 737iivHmVxe+/dQ3EwLymrVu1LMwporEqctWUFzUVHsSZfJH7lCErzCs5qPNCoOw
 OqqHJXmk+FtYix3GMj+sGSD3qR83ml031EeCxpmHQ4OVNfTTIodF/po9K9NRkM8K
 4rA9EkV5xjgWZFw/38ANvozcUXDbVOKMqhwMAH8hLWvIDzO4HfPY/hpRpXzTc+vo
 c7QowH+SP/8lXWGEVvFNjDWaowC7ajkAq94RFXQLTucySP9jgaDDD9nK2onMPD2S
 Z2t7QXgdu9zuLziWeigriSIsjQIJ7gCfAHpUAaeX9VDocgzTqOoweYFTQuU89S9S
 FrfE/B3k+ZI+RDwvsbAShzmMxoYSpct5fhwjA+6D0L1v7piU0GSAT5wMGYFumC32
 BQWi41I2o5ory3rOOfzFa5GUxBEG8dJNjIgJKvcMT/2QH0zinY6c03a1wN+V7iRV
 i1YqBUUvHCEnbsNHrxmN
 =Bu7H
 -----END PGP SIGNATURE-----

Merge tag 'phy-for-4.5' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy into usb-next

Kishon writes:

phy: for 4.5

*) new PHY driver for hi6220 usb and rcar gen3 usb2
*) deprecate phy-omap-control driver. phy-omap-control driver was added
   when there was no proper infrastructure for doing control module
   initialization. The phy-omap-control driver is not an 'actual' PHY
   driver and it was just a hack to do PHY related control module
   initialization. Now with SYSCON framework in the kernel, control
   module setttings can be done using APIs provided by syscon.
*) usbphy-internal pll creates the needed 480MHz and is also a
   supply-clock back to the core clock-controller in Rockchip SoCs.
   This is now modeled as a real clock.
*) calibrate mt65xx usb3 PHY for better eye diagram and receiver
   sensitivity.
*) Miscellaneous cleanups.

Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
This commit is contained in:
Greg Kroah-Hartman 2015-12-26 17:01:18 -08:00
commit daf273350d
19 changed files with 1441 additions and 240 deletions

View file

@ -2,6 +2,7 @@
Required properties:
- compatible: should be one or more of
"brcm,bcm7425-sata-phy"
"brcm,bcm7445-sata-phy"
"brcm,phy-sata3"
- address-cells: should be 1

View file

@ -0,0 +1,16 @@
Hisilicon hi6220 usb PHY
-----------------------
Required properties:
- compatible: should be "hisilicon,hi6220-usb-phy"
- #phy-cells: must be 0
- hisilicon,peripheral-syscon: phandle of syscon used to control phy.
Refer to phy/phy-bindings.txt for the generic PHY binding properties
Example:
usb_phy: usbphy {
compatible = "hisilicon,hi6220-usb-phy";
#phy-cells = <0>;
phy-supply = <&fixed_5v_hub>;
hisilicon,peripheral-syscon = <&sys_ctrl>;
};

View file

@ -0,0 +1,39 @@
* Renesas R-Car generation 3 USB 2.0 PHY
This file provides information on what the device node for the R-Car generation
3 USB 2.0 PHY contains.
Required properties:
- compatible: "renesas,usb2-phy-r8a7795" if the device is a part of an R8A7795
SoC.
- reg: offset and length of the partial USB 2.0 Host register block.
- reg-names: must be "usb2_host".
- clocks: clock phandle and specifier pair(s).
- #phy-cells: see phy-bindings.txt in the same directory, must be <0>.
Optional properties:
To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are
combined, the device tree node should set HSUSB properties to reg and reg-names
properties. This is because HSUSB has registers to select USB 2.0 host or
peripheral at that channel:
- reg: offset and length of the partial HSUSB register block.
- reg-names: must be "hsusb".
- interrupts: interrupt specifier for the PHY.
Example (R-Car H3):
usb-phy@ee080200 {
compatible = "renesas,usb2-phy-r8a7795";
reg = <0 0xee080200 0 0x700>, <0 0xe6590100 0 0x100>;
reg-names = "usb2_host", "hsusb";
interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&mstp7_clks R8A7795_CLK_EHCI0>,
<&mstp7_clks R8A7795_CLK_HSUSB>;
};
usb-phy@ee0a0200 {
compatible = "renesas,usb2-phy-r8a7795";
reg = <0 0xee0a0200 0 0x700>;
reg-names = "usb2_host";
clocks = <&mstp7_clks R8A7795_CLK_EHCI0>;
};

View file

@ -1,7 +1,10 @@
ROCKCHIP USB2 PHY
Required properties:
- compatible: rockchip,rk3288-usb-phy
- compatible: matching the soc type, one of
"rockchip,rk3066a-usb-phy"
"rockchip,rk3188-usb-phy"
"rockchip,rk3288-usb-phy"
- rockchip,grf : phandle to the syscon managing the "general
register files"
- #address-cells: should be 1
@ -21,6 +24,7 @@ required properties:
Optional Properties:
- clocks : phandle + clock specifier for the phy clocks
- clock-names: string, clock name, must be "phyclk"
- #clock-cells: for users of the phy-pll, should be 0
Example:

View file

@ -9,6 +9,7 @@ Required properties:
* allwinner,sun7i-a20-usb-phy
* allwinner,sun8i-a23-usb-phy
* allwinner,sun8i-a33-usb-phy
* allwinner,sun8i-h3-usb-phy
- reg : a list of offset + length pairs
- reg-names :
* "phy_ctrl"

View file

@ -31,6 +31,8 @@ OMAP USB2 PHY
Required properties:
- compatible: Should be "ti,omap-usb2"
Should be "ti,dra7x-usb2-phy2" for the 2nd instance of USB2 PHY
in DRA7x
- reg : Address and length of the register set for the device.
- #phy-cells: determine the number of cells that should be given in the
phandle while referencing this phy.
@ -40,10 +42,14 @@ Required properties:
* "wkupclk" - wakeup clock.
* "refclk" - reference clock (optional).
Optional properties:
Deprecated properties:
- ctrl-module : phandle of the control module used by PHY driver to power on
the PHY.
Recommended properies:
- syscon-phy-power : phandle/offset pair. Phandle to the system control
module and the register offset to power on/off the PHY.
This is usually a subnode of ocp2scp to which it is connected.
usb2phy@4a0ad080 {
@ -77,14 +83,22 @@ Required properties:
* "div-clk" - apll clock
Optional properties:
- ctrl-module : phandle of the control module used by PHY driver to power on
the PHY.
- id: If there are multiple instance of the same type, in order to
differentiate between each instance "id" can be used (e.g., multi-lane PCIe
PHY). If "id" is not provided, it is set to default value of '1'.
- syscon-pllreset: Handle to system control region that contains the
CTRL_CORE_SMA_SW_0 register and register offset to the CTRL_CORE_SMA_SW_0
register that contains the SATA_PLL_SOFT_RESET bit. Only valid for sata_phy.
- syscon-pcs : phandle/offset pair. Phandle to the system control module and the
register offset to write the PCS delay value.
Deprecated properties:
- ctrl-module : phandle of the control module used by PHY driver to power on
the PHY.
Recommended properies:
- syscon-phy-power : phandle/offset pair. Phandle to the system control
module and the register offset to power on/off the PHY.
This is usually a subnode of ocp2scp to which it is connected.

View file

@ -8961,6 +8961,12 @@ L: linux-sh@vger.kernel.org
F: drivers/net/ethernet/renesas/
F: include/linux/sh_eth.h
RENESAS USB2 PHY DRIVER
M: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
L: linux-sh@vger.kernel.org
S: Maintained
F: drivers/phy/phy-rcar-gen3-usb2.c
RESET CONTROLLER FRAMEWORK
M: Philipp Zabel <p.zabel@pengutronix.de>
S: Maintained

View file

@ -118,6 +118,13 @@ config PHY_RCAR_GEN2
help
Support for USB PHY found on Renesas R-Car generation 2 SoCs.
config PHY_RCAR_GEN3_USB2
tristate "Renesas R-Car generation 3 USB 2.0 PHY driver"
depends on OF && ARCH_SHMOBILE
select GENERIC_PHY
help
Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs.
config OMAP_CONTROL_PHY
tristate "OMAP CONTROL PHY Driver"
depends on ARCH_OMAP2PLUS || COMPILE_TEST
@ -215,6 +222,15 @@ config PHY_MT65XX_USB3
for mt65xx SoCs. it supports two usb2.0 ports and
one usb3.0 port.
config PHY_HI6220_USB
tristate "hi6220 USB PHY support"
select GENERIC_PHY
select MFD_SYSCON
help
Enable this to support the HISILICON HI6220 USB PHY.
To compile this driver as a module, choose M here.
config PHY_SUN4I_USB
tristate "Allwinner sunxi SoC USB PHY driver"
depends on ARCH_SUNXI && HAS_IOMEM && OF
@ -374,11 +390,11 @@ config PHY_TUSB1210
config PHY_BRCMSTB_SATA
tristate "Broadcom STB SATA PHY driver"
depends on ARCH_BRCMSTB
depends on ARCH_BRCMSTB || BMIPS_GENERIC
depends on OF
select GENERIC_PHY
help
Enable this to support the SATA3 PHY on 28nm Broadcom STB SoCs.
Enable this to support the SATA3 PHY on 28nm or 40nm Broadcom STB SoCs.
Likely useful only with CONFIG_SATA_BRCMSTB enabled.
config PHY_CYGNUS_PCIE

View file

@ -17,12 +17,14 @@ obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o
obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o
obj-$(CONFIG_PHY_MIPHY365X) += phy-miphy365x.o
obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o
obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.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_HI6220_USB) += phy-hi6220-usb.o
obj-$(CONFIG_PHY_MT65XX_USB3) += phy-mt65xx-usb3.o
obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o
obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o

View file

@ -9,11 +9,9 @@
* warranty of any kind, whether express or implied.
*/
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
@ -195,7 +193,6 @@ static int phy_berlin_usb_probe(struct platform_device *pdev)
return PTR_ERR(phy);
}
platform_set_drvdata(pdev, priv);
phy_set_drvdata(phy, priv);
phy_provider =

View file

@ -26,13 +26,21 @@
#define SATA_MDIO_BANK_OFFSET 0x23c
#define SATA_MDIO_REG_OFFSET(ofs) ((ofs) * 4)
#define SATA_MDIO_REG_SPACE_SIZE 0x1000
#define SATA_MDIO_REG_LENGTH 0x1f00
#define MAX_PORTS 2
/* Register offset between PHYs in PCB space */
#define SATA_MDIO_REG_SPACE_SIZE 0x1000
#define SATA_MDIO_REG_28NM_SPACE_SIZE 0x1000
/* The older SATA PHY registers duplicated per port registers within the map,
* rather than having a separate map per port.
*/
#define SATA_MDIO_REG_40NM_SPACE_SIZE 0x10
enum brcm_sata_phy_version {
BRCM_SATA_PHY_28NM,
BRCM_SATA_PHY_40NM,
};
struct brcm_sata_port {
int portnum;
@ -44,11 +52,12 @@ struct brcm_sata_port {
struct brcm_sata_phy {
struct device *dev;
void __iomem *phy_base;
enum brcm_sata_phy_version version;
struct brcm_sata_port phys[MAX_PORTS];
};
enum sata_mdio_phy_regs_28nm {
enum sata_mdio_phy_regs {
PLL_REG_BANK_0 = 0x50,
PLL_REG_BANK_0_PLLCONTROL_0 = 0x81,
@ -66,8 +75,16 @@ enum sata_mdio_phy_regs_28nm {
static inline void __iomem *brcm_sata_phy_base(struct brcm_sata_port *port)
{
struct brcm_sata_phy *priv = port->phy_priv;
u32 offset = 0;
return priv->phy_base + (port->portnum * SATA_MDIO_REG_SPACE_SIZE);
if (priv->version == BRCM_SATA_PHY_28NM)
offset = SATA_MDIO_REG_28NM_SPACE_SIZE;
else if (priv->version == BRCM_SATA_PHY_40NM)
offset = SATA_MDIO_REG_40NM_SPACE_SIZE;
else
dev_err(priv->dev, "invalid phy version\n");
return priv->phy_base + (port->portnum * offset);
}
static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs,
@ -86,7 +103,7 @@ static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs,
#define FMAX_VAL_DEFAULT 0x3df
#define FMAX_VAL_SSC 0x83
static void brcm_sata_cfg_ssc_28nm(struct brcm_sata_port *port)
static void brcm_sata_cfg_ssc(struct brcm_sata_port *port)
{
void __iomem *base = brcm_sata_phy_base(port);
struct brcm_sata_phy *priv = port->phy_priv;
@ -117,18 +134,21 @@ static int brcm_sata_phy_init(struct phy *phy)
{
struct brcm_sata_port *port = phy_get_drvdata(phy);
brcm_sata_cfg_ssc_28nm(port);
brcm_sata_cfg_ssc(port);
return 0;
}
static const struct phy_ops phy_ops_28nm = {
static const struct phy_ops phy_ops = {
.init = brcm_sata_phy_init,
.owner = THIS_MODULE,
};
static const struct of_device_id brcm_sata_phy_of_match[] = {
{ .compatible = "brcm,bcm7445-sata-phy" },
{ .compatible = "brcm,bcm7445-sata-phy",
.data = (void *)BRCM_SATA_PHY_28NM },
{ .compatible = "brcm,bcm7425-sata-phy",
.data = (void *)BRCM_SATA_PHY_40NM },
{},
};
MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match);
@ -137,6 +157,7 @@ static int brcm_sata_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *dn = dev->of_node, *child;
const struct of_device_id *of_id;
struct brcm_sata_phy *priv;
struct resource *res;
struct phy_provider *provider;
@ -156,6 +177,12 @@ static int brcm_sata_phy_probe(struct platform_device *pdev)
if (IS_ERR(priv->phy_base))
return PTR_ERR(priv->phy_base);
of_id = of_match_node(brcm_sata_phy_of_match, dn);
if (of_id)
priv->version = (enum brcm_sata_phy_version)of_id->data;
else
priv->version = BRCM_SATA_PHY_28NM;
for_each_available_child_of_node(dn, child) {
unsigned int id;
struct brcm_sata_port *port;
@ -181,7 +208,7 @@ static int brcm_sata_phy_probe(struct platform_device *pdev)
port = &priv->phys[id];
port->portnum = id;
port->phy_priv = priv;
port->phy = devm_phy_create(dev, child, &phy_ops_28nm);
port->phy = devm_phy_create(dev, child, &phy_ops);
port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc");
if (IS_ERR(port->phy)) {
dev_err(dev, "failed to create PHY\n");

View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2015 Linaro Ltd.
* Copyright (c) 2015 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/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
#define SC_PERIPH_CTRL4 0x00c
#define CTRL4_PICO_SIDDQ BIT(6)
#define CTRL4_PICO_OGDISABLE BIT(8)
#define CTRL4_PICO_VBUSVLDEXT BIT(10)
#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11)
#define CTRL4_OTG_PHY_SEL BIT(21)
#define SC_PERIPH_CTRL5 0x010
#define CTRL5_USBOTG_RES_SEL BIT(3)
#define CTRL5_PICOPHY_ACAENB BIT(4)
#define CTRL5_PICOPHY_BC_MODE BIT(5)
#define CTRL5_PICOPHY_CHRGSEL BIT(6)
#define CTRL5_PICOPHY_VDATSRCEND BIT(7)
#define CTRL5_PICOPHY_VDATDETENB BIT(8)
#define CTRL5_PICOPHY_DCDENB BIT(9)
#define CTRL5_PICOPHY_IDDIG BIT(10)
#define SC_PERIPH_CTRL8 0x018
#define SC_PERIPH_RSTEN0 0x300
#define SC_PERIPH_RSTDIS0 0x304
#define RST0_USBOTG_BUS BIT(4)
#define RST0_POR_PICOPHY BIT(5)
#define RST0_USBOTG BIT(6)
#define RST0_USBOTG_32K BIT(7)
#define EYE_PATTERN_PARA 0x7053348c
struct hi6220_priv {
struct regmap *reg;
struct device *dev;
};
static void hi6220_phy_init(struct hi6220_priv *priv)
{
struct regmap *reg = priv->reg;
u32 val, mask;
val = RST0_USBOTG_BUS | RST0_POR_PICOPHY |
RST0_USBOTG | RST0_USBOTG_32K;
mask = val;
regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val);
regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val);
}
static int hi6220_phy_setup(struct hi6220_priv *priv, bool on)
{
struct regmap *reg = priv->reg;
u32 val, mask;
int ret;
if (on) {
val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB;
mask = val | CTRL5_PICOPHY_BC_MODE;
ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val);
if (ret)
goto out;
val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL |
CTRL4_OTG_PHY_SEL;
mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE;
ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val);
if (ret)
goto out;
ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA);
if (ret)
goto out;
} else {
val = CTRL4_PICO_SIDDQ;
mask = val;
ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val);
if (ret)
goto out;
}
return 0;
out:
dev_err(priv->dev, "failed to setup phy ret: %d\n", ret);
return ret;
}
static int hi6220_phy_start(struct phy *phy)
{
struct hi6220_priv *priv = phy_get_drvdata(phy);
return hi6220_phy_setup(priv, true);
}
static int hi6220_phy_exit(struct phy *phy)
{
struct hi6220_priv *priv = phy_get_drvdata(phy);
return hi6220_phy_setup(priv, false);
}
static struct phy_ops hi6220_phy_ops = {
.init = hi6220_phy_start,
.exit = hi6220_phy_exit,
.owner = THIS_MODULE,
};
static int hi6220_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct phy *phy;
struct hi6220_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,peripheral-syscon");
if (IS_ERR(priv->reg)) {
dev_err(dev, "no hisilicon,peripheral-syscon\n");
return PTR_ERR(priv->reg);
}
hi6220_phy_init(priv);
phy = devm_phy_create(dev, NULL, &hi6220_phy_ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
phy_set_drvdata(phy, priv);
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 hi6220_phy_of_match[] = {
{.compatible = "hisilicon,hi6220-usb-phy",},
{ },
};
MODULE_DEVICE_TABLE(of, hi6220_phy_of_match);
static struct platform_driver hi6220_phy_driver = {
.probe = hi6220_phy_probe,
.driver = {
.name = "hi6220-usb-phy",
.of_match_table = hi6220_phy_of_match,
}
};
module_platform_driver(hi6220_phy_driver);
MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver");
MODULE_ALIAS("platform:hi6220-usb-phy");
MODULE_LICENSE("GPL");

View file

@ -17,6 +17,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/phy/phy.h>
@ -27,6 +28,7 @@
* relative to USB3_SIF2_BASE base address
*/
#define SSUSB_SIFSLV_SPLLC 0x0000
#define SSUSB_SIFSLV_U2FREQ 0x0100
/* offsets of sub-segment in each port registers */
#define SSUSB_SIFSLV_U2PHY_COM_BASE 0x0000
@ -41,6 +43,7 @@
#define PA2_RG_SIF_U2PLL_FORCE_EN BIT(18)
#define U3P_USBPHYACR5 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0014)
#define PA5_RG_U2_HSTX_SRCAL_EN BIT(15)
#define PA5_RG_U2_HSTX_SRCTRL GENMASK(14, 12)
#define PA5_RG_U2_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12)
#define PA5_RG_U2_HS_100U_U3_EN BIT(11)
@ -49,6 +52,8 @@
#define PA6_RG_U2_ISO_EN BIT(31)
#define PA6_RG_U2_BC11_SW_EN BIT(23)
#define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20)
#define PA6_RG_U2_SQTH GENMASK(3, 0)
#define PA6_RG_U2_SQTH_VAL(x) (0xf & (x))
#define U3P_U2PHYACR4 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0020)
#define P2C_RG_USB20_GPIO_CTL BIT(9)
@ -111,6 +116,24 @@
#define XC3_RG_U3_XTAL_RX_PWD BIT(9)
#define XC3_RG_U3_FRC_XTAL_RX_PWD BIT(8)
#define U3P_U2FREQ_FMCR0 (SSUSB_SIFSLV_U2FREQ + 0x00)
#define P2F_RG_MONCLK_SEL GENMASK(27, 26)
#define P2F_RG_MONCLK_SEL_VAL(x) ((0x3 & (x)) << 26)
#define P2F_RG_FREQDET_EN BIT(24)
#define P2F_RG_CYCLECNT GENMASK(23, 0)
#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x))
#define U3P_U2FREQ_VALUE (SSUSB_SIFSLV_U2FREQ + 0x0c)
#define U3P_U2FREQ_FMMONR1 (SSUSB_SIFSLV_U2FREQ + 0x10)
#define P2F_USB_FM_VALID BIT(0)
#define P2F_RG_FRCK_EN BIT(8)
#define U3P_REF_CLK 26 /* MHZ */
#define U3P_SLEW_RATE_COEF 28
#define U3P_SR_COEF_DIVISOR 1000
#define U3P_FM_DET_CYCLE_CNT 1024
struct mt65xx_phy_instance {
struct phy *phy;
void __iomem *port_base;
@ -126,6 +149,77 @@ struct mt65xx_u3phy {
int nphys;
};
static void hs_slew_rate_calibrate(struct mt65xx_u3phy *u3phy,
struct mt65xx_phy_instance *instance)
{
void __iomem *sif_base = u3phy->sif_base;
int calibration_val;
int fm_out;
u32 tmp;
/* enable USB ring oscillator */
tmp = readl(instance->port_base + U3P_USBPHYACR5);
tmp |= PA5_RG_U2_HSTX_SRCAL_EN;
writel(tmp, instance->port_base + U3P_USBPHYACR5);
udelay(1);
/*enable free run clock */
tmp = readl(sif_base + U3P_U2FREQ_FMMONR1);
tmp |= P2F_RG_FRCK_EN;
writel(tmp, sif_base + U3P_U2FREQ_FMMONR1);
/* set cycle count as 1024, and select u2 channel */
tmp = readl(sif_base + U3P_U2FREQ_FMCR0);
tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL);
tmp |= P2F_RG_CYCLECNT_VAL(U3P_FM_DET_CYCLE_CNT);
tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index);
writel(tmp, sif_base + U3P_U2FREQ_FMCR0);
/* enable frequency meter */
tmp = readl(sif_base + U3P_U2FREQ_FMCR0);
tmp |= P2F_RG_FREQDET_EN;
writel(tmp, sif_base + U3P_U2FREQ_FMCR0);
/* ignore return value */
readl_poll_timeout(sif_base + U3P_U2FREQ_FMMONR1, tmp,
(tmp & P2F_USB_FM_VALID), 10, 200);
fm_out = readl(sif_base + U3P_U2FREQ_VALUE);
/* disable frequency meter */
tmp = readl(sif_base + U3P_U2FREQ_FMCR0);
tmp &= ~P2F_RG_FREQDET_EN;
writel(tmp, sif_base + U3P_U2FREQ_FMCR0);
/*disable free run clock */
tmp = readl(sif_base + U3P_U2FREQ_FMMONR1);
tmp &= ~P2F_RG_FRCK_EN;
writel(tmp, sif_base + U3P_U2FREQ_FMMONR1);
if (fm_out) {
/* ( 1024 / FM_OUT ) x reference clock frequency x 0.028 */
tmp = U3P_FM_DET_CYCLE_CNT * U3P_REF_CLK * U3P_SLEW_RATE_COEF;
tmp /= fm_out;
calibration_val = DIV_ROUND_CLOSEST(tmp, U3P_SR_COEF_DIVISOR);
} else {
/* if FM detection fail, set default value */
calibration_val = 4;
}
dev_dbg(u3phy->dev, "phy:%d, fm_out:%d, calib:%d\n",
instance->index, fm_out, calibration_val);
/* set HS slew rate */
tmp = readl(instance->port_base + U3P_USBPHYACR5);
tmp &= ~PA5_RG_U2_HSTX_SRCTRL;
tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(calibration_val);
writel(tmp, instance->port_base + U3P_USBPHYACR5);
/* disable USB ring oscillator */
tmp = readl(instance->port_base + U3P_USBPHYACR5);
tmp &= ~PA5_RG_U2_HSTX_SRCAL_EN;
writel(tmp, instance->port_base + U3P_USBPHYACR5);
}
static void phy_instance_init(struct mt65xx_u3phy *u3phy,
struct mt65xx_phy_instance *instance)
{
@ -165,9 +259,10 @@ static void phy_instance_init(struct mt65xx_u3phy *u3phy,
writel(tmp, port_base + U3P_U2PHYDTM0);
}
/* DP/DM BC1.1 path Disable */
tmp = readl(port_base + U3P_USBPHYACR6);
tmp &= ~PA6_RG_U2_BC11_SW_EN;
tmp &= ~PA6_RG_U2_BC11_SW_EN; /* DP/DM BC1.1 path Disable */
tmp &= ~PA6_RG_U2_SQTH;
tmp |= PA6_RG_U2_SQTH_VAL(2);
writel(tmp, port_base + U3P_USBPHYACR6);
tmp = readl(port_base + U3P_U3PHYA_DA_REG0);
@ -223,9 +318,9 @@ static void phy_instance_power_on(struct mt65xx_u3phy *u3phy,
tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD;
writel(tmp, u3phy->sif_base + U3P_XTALCTL3);
/* [mt8173]disable Change 100uA current from SSUSB */
/* [mt8173]switch 100uA current to SSUSB */
tmp = readl(port_base + U3P_USBPHYACR5);
tmp &= ~PA5_RG_U2_HS_100U_U3_EN;
tmp |= PA5_RG_U2_HS_100U_U3_EN;
writel(tmp, port_base + U3P_USBPHYACR5);
}
@ -270,7 +365,7 @@ static void phy_instance_power_off(struct mt65xx_u3phy *u3phy,
writel(tmp, port_base + U3P_USBPHYACR6);
if (!index) {
/* (also disable)Change 100uA current switch to USB2.0 */
/* switch 100uA current back to USB2.0 */
tmp = readl(port_base + U3P_USBPHYACR5);
tmp &= ~PA5_RG_U2_HS_100U_U3_EN;
writel(tmp, port_base + U3P_USBPHYACR5);
@ -340,6 +435,7 @@ static int mt65xx_phy_power_on(struct phy *phy)
struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent);
phy_instance_power_on(u3phy, instance);
hs_slew_rate_calibrate(u3phy, instance);
return 0;
}

View file

@ -29,6 +29,8 @@
#include <linux/delay.h>
#include <linux/phy/omap_control_phy.h>
#include <linux/phy/phy.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/of_platform.h>
#define USB2PHY_DISCON_BYP_LATCH (1 << 31)
@ -97,22 +99,38 @@ static int omap_usb_set_peripheral(struct usb_otg *otg,
return 0;
}
static int omap_usb_phy_power(struct omap_usb *phy, int on)
{
u32 val;
int ret;
if (!phy->syscon_phy_power) {
omap_control_phy_power(phy->control_dev, on);
return 0;
}
if (on)
val = phy->power_on;
else
val = phy->power_off;
ret = regmap_update_bits(phy->syscon_phy_power, phy->power_reg,
phy->mask, val);
return ret;
}
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;
return omap_usb_phy_power(phy, false);
}
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;
return omap_usb_phy_power(phy, true);
}
static int omap_usb_init(struct phy *x)
@ -147,21 +165,38 @@ static const struct phy_ops ops = {
static const struct usb_phy_data omap_usb2_data = {
.label = "omap_usb2",
.flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS,
.mask = OMAP_DEV_PHY_PD,
.power_off = OMAP_DEV_PHY_PD,
};
static const struct usb_phy_data omap5_usb2_data = {
.label = "omap5_usb2",
.flags = 0,
.mask = OMAP_DEV_PHY_PD,
.power_off = OMAP_DEV_PHY_PD,
};
static const struct usb_phy_data dra7x_usb2_data = {
.label = "dra7x_usb2",
.flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
.mask = OMAP_DEV_PHY_PD,
.power_off = OMAP_DEV_PHY_PD,
};
static const struct usb_phy_data dra7x_usb2_phy2_data = {
.label = "dra7x_usb2_phy2",
.flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
.mask = OMAP_USB2_PHY_PD,
.power_off = OMAP_USB2_PHY_PD,
};
static const struct usb_phy_data am437x_usb2_data = {
.label = "am437x_usb2",
.flags = 0,
.mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD |
AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN,
.power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN,
.power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD,
};
static const struct of_device_id omap_usb2_id_table[] = {
@ -177,6 +212,10 @@ static const struct of_device_id omap_usb2_id_table[] = {
.compatible = "ti,dra7x-usb2",
.data = &dra7x_usb2_data,
},
{
.compatible = "ti,dra7x-usb2-phy2",
.data = &dra7x_usb2_phy2_data,
},
{
.compatible = "ti,am437x-usb2",
.data = &am437x_usb2_data,
@ -219,6 +258,9 @@ static int omap_usb2_probe(struct platform_device *pdev)
phy->phy.label = phy_data->label;
phy->phy.otg = otg;
phy->phy.type = USB_PHY_TYPE_USB2;
phy->mask = phy_data->mask;
phy->power_on = phy_data->power_on;
phy->power_off = phy_data->power_off;
if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@ -228,20 +270,35 @@ static int omap_usb2_probe(struct platform_device *pdev)
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;
}
phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node,
"syscon-phy-power");
if (IS_ERR(phy->syscon_phy_power)) {
dev_dbg(&pdev->dev,
"can't get syscon-phy-power, using control device\n");
phy->syscon_phy_power = NULL;
control_pdev = of_find_device_by_node(control_node);
if (!control_pdev) {
dev_err(&pdev->dev, "Failed to get control device\n");
return -EINVAL;
}
control_node = of_parse_phandle(node, "ctrl-module", 0);
if (!control_node) {
dev_err(&pdev->dev,
"Failed to get control device phandle\n");
return -EINVAL;
}
phy->control_dev = &control_pdev->dev;
omap_control_phy_power(phy->control_dev, 0);
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;
} else {
if (of_property_read_u32_index(node,
"syscon-phy-power", 1,
&phy->power_reg)) {
dev_err(&pdev->dev,
"couldn't get power reg. offset\n");
return -EINVAL;
}
}
otg->set_host = omap_usb_set_host;
otg->set_peripheral = omap_usb_set_peripheral;
@ -261,6 +318,7 @@ static int omap_usb2_probe(struct platform_device *pdev)
}
phy_set_drvdata(generic_phy, phy);
omap_usb_power_off(generic_phy);
phy_provider = devm_of_phy_provider_register(phy->dev,
of_phy_simple_xlate);

View file

@ -0,0 +1,378 @@
/*
* Renesas R-Car Gen3 for USB2.0 PHY driver
*
* Copyright (C) 2015 Renesas Electronics Corporation
*
* This is based on the phy-rcar-gen2 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/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
/******* USB2.0 Host registers (original offset is +0x200) *******/
#define USB2_INT_ENABLE 0x000
#define USB2_USBCTR 0x00c
#define USB2_SPD_RSM_TIMSET 0x10c
#define USB2_OC_TIMSET 0x110
#define USB2_COMMCTRL 0x600
#define USB2_OBINTSTA 0x604
#define USB2_OBINTEN 0x608
#define USB2_VBCTRL 0x60c
#define USB2_LINECTRL1 0x610
#define USB2_ADPCTRL 0x630
/* INT_ENABLE */
#define USB2_INT_ENABLE_UCOM_INTEN BIT(3)
#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2)
#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1)
#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \
USB2_INT_ENABLE_USBH_INTB_EN | \
USB2_INT_ENABLE_USBH_INTA_EN)
/* USBCTR */
#define USB2_USBCTR_DIRPD BIT(2)
#define USB2_USBCTR_PLL_RST BIT(1)
/* SPD_RSM_TIMSET */
#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b
/* OC_TIMSET */
#define USB2_OC_TIMSET_INIT 0x000209ab
/* COMMCTRL */
#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */
/* OBINTSTA and OBINTEN */
#define USB2_OBINT_SESSVLDCHG BIT(12)
#define USB2_OBINT_IDDIGCHG BIT(11)
#define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \
USB2_OBINT_IDDIGCHG)
/* VBCTRL */
#define USB2_VBCTRL_DRVVBUSSEL BIT(8)
/* LINECTRL1 */
#define USB2_LINECTRL1_DPRPD_EN BIT(19)
#define USB2_LINECTRL1_DP_RPD BIT(18)
#define USB2_LINECTRL1_DMRPD_EN BIT(17)
#define USB2_LINECTRL1_DM_RPD BIT(16)
/* ADPCTRL */
#define USB2_ADPCTRL_OTGSESSVLD BIT(20)
#define USB2_ADPCTRL_IDDIG BIT(19)
#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
#define USB2_ADPCTRL_DRVVBUS BIT(4)
/******* HSUSB registers (original offset is +0x100) *******/
#define HSUSB_LPSTS 0x02
#define HSUSB_UGCTRL2 0x84
/* Low Power Status register (LPSTS) */
#define HSUSB_LPSTS_SUSPM 0x4000
/* USB General control register 2 (UGCTRL2) */
#define HSUSB_UGCTRL2_MASK 0x00000031 /* bit[31:6] should be 0 */
#define HSUSB_UGCTRL2_USB0SEL 0x00000030
#define HSUSB_UGCTRL2_USB0SEL_HOST 0x00000010
#define HSUSB_UGCTRL2_USB0SEL_HS_USB 0x00000020
#define HSUSB_UGCTRL2_USB0SEL_OTG 0x00000030
struct rcar_gen3_data {
void __iomem *base;
struct clk *clk;
};
struct rcar_gen3_chan {
struct rcar_gen3_data usb2;
struct rcar_gen3_data hsusb;
struct phy *phy;
};
static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host)
{
void __iomem *usb2_base = ch->usb2.base;
u32 val = readl(usb2_base + USB2_COMMCTRL);
dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host);
if (host)
val &= ~USB2_COMMCTRL_OTG_PERI;
else
val |= USB2_COMMCTRL_OTG_PERI;
writel(val, usb2_base + USB2_COMMCTRL);
}
static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm)
{
void __iomem *usb2_base = ch->usb2.base;
u32 val = readl(usb2_base + USB2_LINECTRL1);
dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm);
val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD);
if (dp)
val |= USB2_LINECTRL1_DP_RPD;
if (dm)
val |= USB2_LINECTRL1_DM_RPD;
writel(val, usb2_base + USB2_LINECTRL1);
}
static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus)
{
void __iomem *usb2_base = ch->usb2.base;
u32 val = readl(usb2_base + USB2_ADPCTRL);
dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus);
if (vbus)
val |= USB2_ADPCTRL_DRVVBUS;
else
val &= ~USB2_ADPCTRL_DRVVBUS;
writel(val, usb2_base + USB2_ADPCTRL);
}
static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch)
{
rcar_gen3_set_linectrl(ch, 1, 1);
rcar_gen3_set_host_mode(ch, 1);
rcar_gen3_enable_vbus_ctrl(ch, 1);
}
static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch)
{
rcar_gen3_set_linectrl(ch, 0, 1);
rcar_gen3_set_host_mode(ch, 0);
rcar_gen3_enable_vbus_ctrl(ch, 0);
}
static bool rcar_gen3_check_vbus(struct rcar_gen3_chan *ch)
{
return !!(readl(ch->usb2.base + USB2_ADPCTRL) &
USB2_ADPCTRL_OTGSESSVLD);
}
static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch)
{
return !!(readl(ch->usb2.base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG);
}
static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch)
{
bool is_host = true;
/* B-device? */
if (rcar_gen3_check_id(ch) && rcar_gen3_check_vbus(ch))
is_host = false;
if (is_host)
rcar_gen3_init_for_host(ch);
else
rcar_gen3_init_for_peri(ch);
}
static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch)
{
void __iomem *usb2_base = ch->usb2.base;
u32 val;
val = readl(usb2_base + USB2_VBCTRL);
writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL);
writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
val = readl(usb2_base + USB2_OBINTEN);
writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN);
val = readl(usb2_base + USB2_ADPCTRL);
writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL);
val = readl(usb2_base + USB2_LINECTRL1);
rcar_gen3_set_linectrl(ch, 0, 0);
writel(val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN,
usb2_base + USB2_LINECTRL1);
rcar_gen3_device_recognition(ch);
}
static int rcar_gen3_phy_usb2_init(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
void __iomem *usb2_base = channel->usb2.base;
void __iomem *hsusb_base = channel->hsusb.base;
u32 val;
/* Initialize USB2 part */
writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE);
writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET);
writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET);
/* Initialize HSUSB part */
if (hsusb_base) {
val = readl(hsusb_base + HSUSB_UGCTRL2);
val = (val & ~HSUSB_UGCTRL2_USB0SEL) |
HSUSB_UGCTRL2_USB0SEL_OTG;
writel(val & HSUSB_UGCTRL2_MASK, hsusb_base + HSUSB_UGCTRL2);
/* Initialize otg part */
rcar_gen3_init_otg(channel);
}
return 0;
}
static int rcar_gen3_phy_usb2_exit(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
writel(0, channel->usb2.base + USB2_INT_ENABLE);
return 0;
}
static int rcar_gen3_phy_usb2_power_on(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
void __iomem *usb2_base = channel->usb2.base;
void __iomem *hsusb_base = channel->hsusb.base;
u32 val;
val = readl(usb2_base + USB2_USBCTR);
val |= USB2_USBCTR_PLL_RST;
writel(val, usb2_base + USB2_USBCTR);
val &= ~USB2_USBCTR_PLL_RST;
writel(val, usb2_base + USB2_USBCTR);
/*
* TODO: To reduce power consuming, this driver should set the SUSPM
* after the PHY detects ID pin as peripheral.
*/
if (hsusb_base) {
/* Power on HSUSB PHY */
val = readw(hsusb_base + HSUSB_LPSTS);
val |= HSUSB_LPSTS_SUSPM;
writew(val, hsusb_base + HSUSB_LPSTS);
}
return 0;
}
static int rcar_gen3_phy_usb2_power_off(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
void __iomem *hsusb_base = channel->hsusb.base;
u32 val;
if (hsusb_base) {
/* Power off HSUSB PHY */
val = readw(hsusb_base + HSUSB_LPSTS);
val &= ~HSUSB_LPSTS_SUSPM;
writew(val, hsusb_base + HSUSB_LPSTS);
}
return 0;
}
static struct phy_ops rcar_gen3_phy_usb2_ops = {
.init = rcar_gen3_phy_usb2_init,
.exit = rcar_gen3_phy_usb2_exit,
.power_on = rcar_gen3_phy_usb2_power_on,
.power_off = rcar_gen3_phy_usb2_power_off,
.owner = THIS_MODULE,
};
static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
{
struct rcar_gen3_chan *ch = _ch;
void __iomem *usb2_base = ch->usb2.base;
u32 status = readl(usb2_base + USB2_OBINTSTA);
irqreturn_t ret = IRQ_NONE;
if (status & USB2_OBINT_BITS) {
dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status);
writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
rcar_gen3_device_recognition(ch);
ret = IRQ_HANDLED;
}
return ret;
}
static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = {
{ .compatible = "renesas,usb2-phy-r8a7795" },
{ }
};
MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table);
static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rcar_gen3_chan *channel;
struct phy_provider *provider;
struct resource *res;
if (!dev->of_node) {
dev_err(dev, "This driver needs device tree\n");
return -EINVAL;
}
channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL);
if (!channel)
return -ENOMEM;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2_host");
channel->usb2.base = devm_ioremap_resource(dev, res);
if (IS_ERR(channel->usb2.base))
return PTR_ERR(channel->usb2.base);
/* "hsusb" memory resource is optional */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hsusb");
/* To avoid error message by devm_ioremap_resource() */
if (res) {
int irq;
channel->hsusb.base = devm_ioremap_resource(dev, res);
if (IS_ERR(channel->hsusb.base))
channel->hsusb.base = NULL;
/* call request_irq for OTG */
irq = platform_get_irq(pdev, 0);
if (irq >= 0)
irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq,
IRQF_SHARED, dev_name(dev),
channel);
if (irq < 0)
dev_err(dev, "No irq handler (%d)\n", irq);
}
/* devm_phy_create() will call pm_runtime_enable(dev); */
channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops);
if (IS_ERR(channel->phy)) {
dev_err(dev, "Failed to create USB2 PHY\n");
return PTR_ERR(channel->phy);
}
phy_set_drvdata(channel->phy, channel);
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(provider))
dev_err(dev, "Failed to register PHY provider\n");
return PTR_ERR_OR_ZERO(provider);
}
static struct platform_driver rcar_gen3_phy_usb2_driver = {
.driver = {
.name = "phy_rcar_gen3_usb2",
.of_match_table = rcar_gen3_phy_usb2_match_table,
},
.probe = rcar_gen3_phy_usb2_probe,
};
module_platform_driver(rcar_gen3_phy_usb2_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY");
MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");

View file

@ -15,12 +15,14 @@
*/
#include <linux/clk.h>
#include <linux/clk-provider.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/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
@ -36,31 +38,91 @@
#define SIDDQ_ON BIT(13)
#define SIDDQ_OFF (0 << 13)
struct rockchip_usb_phys {
int reg;
const char *pll_name;
};
struct rockchip_usb_phy_pdata {
struct rockchip_usb_phys *phys;
};
struct rockchip_usb_phy_base {
struct device *dev;
struct regmap *reg_base;
const struct rockchip_usb_phy_pdata *pdata;
};
struct rockchip_usb_phy {
struct rockchip_usb_phy_base *base;
struct device_node *np;
unsigned int reg_offset;
struct regmap *reg_base;
struct clk *clk;
struct clk *clk480m;
struct clk_hw clk480m_hw;
struct phy *phy;
};
static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy,
bool siddq)
{
return regmap_write(phy->reg_base, phy->reg_offset,
return regmap_write(phy->base->reg_base, phy->reg_offset,
SIDDQ_WRITE_ENA | (siddq ? SIDDQ_ON : SIDDQ_OFF));
}
static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return 480000000;
}
static void rockchip_usb_phy480m_disable(struct clk_hw *hw)
{
struct rockchip_usb_phy *phy = container_of(hw,
struct rockchip_usb_phy,
clk480m_hw);
/* Power down usb phy analog blocks by set siddq 1 */
rockchip_usb_phy_power(phy, 1);
}
static int rockchip_usb_phy480m_enable(struct clk_hw *hw)
{
struct rockchip_usb_phy *phy = container_of(hw,
struct rockchip_usb_phy,
clk480m_hw);
/* Power up usb phy analog blocks by set siddq 0 */
return rockchip_usb_phy_power(phy, 0);
}
static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw)
{
struct rockchip_usb_phy *phy = container_of(hw,
struct rockchip_usb_phy,
clk480m_hw);
int ret;
u32 val;
ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val);
if (ret < 0)
return ret;
return (val & SIDDQ_ON) ? 0 : 1;
}
static const struct clk_ops rockchip_usb_phy480m_ops = {
.enable = rockchip_usb_phy480m_enable,
.disable = rockchip_usb_phy480m_disable,
.is_enabled = rockchip_usb_phy480m_is_enabled,
.recalc_rate = rockchip_usb_phy480m_recalc_rate,
};
static int rockchip_usb_phy_power_off(struct phy *_phy)
{
struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
int ret = 0;
/* Power down usb phy analog blocks by set siddq 1 */
ret = rockchip_usb_phy_power(phy, 1);
if (ret)
return ret;
clk_disable_unprepare(phy->clk);
clk_disable_unprepare(phy->clk480m);
return 0;
}
@ -68,20 +130,8 @@ static int rockchip_usb_phy_power_off(struct phy *_phy)
static int rockchip_usb_phy_power_on(struct phy *_phy)
{
struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
int ret = 0;
ret = clk_prepare_enable(phy->clk);
if (ret)
return ret;
/* Power up usb phy analog blocks by set siddq 0 */
ret = rockchip_usb_phy_power(phy, 0);
if (ret) {
clk_disable_unprepare(phy->clk);
return ret;
}
return 0;
return clk_prepare_enable(phy->clk480m);
}
static const struct phy_ops ops = {
@ -90,66 +140,179 @@ static const struct phy_ops ops = {
.owner = THIS_MODULE,
};
static void rockchip_usb_phy_action(void *data)
{
struct rockchip_usb_phy *rk_phy = data;
of_clk_del_provider(rk_phy->np);
clk_unregister(rk_phy->clk480m);
if (rk_phy->clk)
clk_put(rk_phy->clk);
}
static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base,
struct device_node *child)
{
struct rockchip_usb_phy *rk_phy;
unsigned int reg_offset;
const char *clk_name;
struct clk_init_data init;
int err, i;
rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL);
if (!rk_phy)
return -ENOMEM;
rk_phy->base = base;
rk_phy->np = child;
if (of_property_read_u32(child, "reg", &reg_offset)) {
dev_err(base->dev, "missing reg property in node %s\n",
child->name);
return -EINVAL;
}
rk_phy->reg_offset = reg_offset;
rk_phy->clk = of_clk_get_by_name(child, "phyclk");
if (IS_ERR(rk_phy->clk))
rk_phy->clk = NULL;
i = 0;
init.name = NULL;
while (base->pdata->phys[i].reg) {
if (base->pdata->phys[i].reg == reg_offset) {
init.name = base->pdata->phys[i].pll_name;
break;
}
i++;
}
if (!init.name) {
dev_err(base->dev, "phy data not found\n");
return -EINVAL;
}
if (rk_phy->clk) {
clk_name = __clk_get_name(rk_phy->clk);
init.flags = 0;
init.parent_names = &clk_name;
init.num_parents = 1;
} else {
init.flags = CLK_IS_ROOT;
init.parent_names = NULL;
init.num_parents = 0;
}
init.ops = &rockchip_usb_phy480m_ops;
rk_phy->clk480m_hw.init = &init;
rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw);
if (IS_ERR(rk_phy->clk480m)) {
err = PTR_ERR(rk_phy->clk480m);
goto err_clk;
}
err = of_clk_add_provider(child, of_clk_src_simple_get,
rk_phy->clk480m);
if (err < 0)
goto err_clk_prov;
err = devm_add_action(base->dev, rockchip_usb_phy_action, rk_phy);
if (err)
goto err_devm_action;
rk_phy->phy = devm_phy_create(base->dev, child, &ops);
if (IS_ERR(rk_phy->phy)) {
dev_err(base->dev, "failed to create PHY\n");
return PTR_ERR(rk_phy->phy);
}
phy_set_drvdata(rk_phy->phy, rk_phy);
/* only power up usb phy when it use, so disable it when init*/
return rockchip_usb_phy_power(rk_phy, 1);
err_devm_action:
of_clk_del_provider(child);
err_clk_prov:
clk_unregister(rk_phy->clk480m);
err_clk:
if (rk_phy->clk)
clk_put(rk_phy->clk);
return err;
}
static const struct rockchip_usb_phy_pdata rk3066a_pdata = {
.phys = (struct rockchip_usb_phys[]){
{ .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" },
{ .reg = 0x188, .pll_name = "sclk_otgphy1_480m" },
{ /* sentinel */ }
},
};
static const struct rockchip_usb_phy_pdata rk3188_pdata = {
.phys = (struct rockchip_usb_phys[]){
{ .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" },
{ .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" },
{ /* sentinel */ }
},
};
static const struct rockchip_usb_phy_pdata rk3288_pdata = {
.phys = (struct rockchip_usb_phys[]){
{ .reg = 0x320, .pll_name = "sclk_otgphy0_480m" },
{ .reg = 0x334, .pll_name = "sclk_otgphy1_480m" },
{ .reg = 0x348, .pll_name = "sclk_otgphy2_480m" },
{ /* sentinel */ }
},
};
static int rockchip_usb_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rockchip_usb_phy *rk_phy;
struct rockchip_usb_phy_base *phy_base;
struct phy_provider *phy_provider;
const struct of_device_id *match;
struct device_node *child;
struct regmap *grf;
unsigned int reg_offset;
int err;
grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf");
if (IS_ERR(grf)) {
phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL);
if (!phy_base)
return -ENOMEM;
match = of_match_device(dev->driver->of_match_table, dev);
if (!match || !match->data) {
dev_err(dev, "missing phy data\n");
return -EINVAL;
}
phy_base->pdata = match->data;
phy_base->dev = dev;
phy_base->reg_base = syscon_regmap_lookup_by_phandle(dev->of_node,
"rockchip,grf");
if (IS_ERR(phy_base->reg_base)) {
dev_err(&pdev->dev, "Missing rockchip,grf property\n");
return PTR_ERR(grf);
return PTR_ERR(phy_base->reg_base);
}
for_each_available_child_of_node(dev->of_node, child) {
rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL);
if (!rk_phy) {
err = -ENOMEM;
goto put_child;
err = rockchip_usb_phy_init(phy_base, child);
if (err) {
of_node_put(child);
return err;
}
if (of_property_read_u32(child, "reg", &reg_offset)) {
dev_err(dev, "missing reg property in node %s\n",
child->name);
err = -EINVAL;
goto put_child;
}
rk_phy->reg_offset = reg_offset;
rk_phy->reg_base = grf;
rk_phy->clk = of_clk_get_by_name(child, "phyclk");
if (IS_ERR(rk_phy->clk))
rk_phy->clk = NULL;
rk_phy->phy = devm_phy_create(dev, child, &ops);
if (IS_ERR(rk_phy->phy)) {
dev_err(dev, "failed to create PHY\n");
err = PTR_ERR(rk_phy->phy);
goto put_child;
}
phy_set_drvdata(rk_phy->phy, rk_phy);
/* only power up usb phy when it use, so disable it when init*/
err = rockchip_usb_phy_power(rk_phy, 1);
if (err)
goto put_child;
}
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
put_child:
of_node_put(child);
return err;
}
static const struct of_device_id rockchip_usb_phy_dt_ids[] = {
{ .compatible = "rockchip,rk3288-usb-phy" },
{ .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata },
{ .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata },
{ .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata },
{}
};

View file

@ -32,6 +32,7 @@
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-sun4i-usb.h>
@ -46,6 +47,9 @@
#define REG_PHYBIST 0x08
#define REG_PHYTUNE 0x0c
#define REG_PHYCTL_A33 0x10
#define REG_PHY_UNK_H3 0x20
#define REG_PMU_UNK_H3 0x10
#define PHYCTL_DATA BIT(7)
@ -79,7 +83,7 @@
#define PHY_DISCON_TH_SEL 0x2a
#define PHY_SQUELCH_DETECT 0x3c
#define MAX_PHYS 3
#define MAX_PHYS 4
/*
* Note do not raise the debounce time, we must report Vusb high within 100ms
@ -88,12 +92,24 @@
#define DEBOUNCE_TIME msecs_to_jiffies(50)
#define POLL_TIME msecs_to_jiffies(250)
enum sun4i_usb_phy_type {
sun4i_a10_phy,
sun8i_a33_phy,
sun8i_h3_phy,
};
struct sun4i_usb_phy_cfg {
int num_phys;
enum sun4i_usb_phy_type type;
u32 disc_thresh;
u8 phyctl_offset;
bool dedicated_clocks;
};
struct sun4i_usb_phy_data {
void __iomem *base;
const struct sun4i_usb_phy_cfg *cfg;
struct mutex mutex;
int num_phys;
u32 disc_thresh;
bool has_a33_phyctl;
struct sun4i_usb_phy {
struct phy *phy;
void __iomem *pmu;
@ -159,17 +175,14 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data,
{
struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy);
u32 temp, usbc_bit = BIT(phy->index * 2);
void *phyctl;
void *phyctl = phy_data->base + phy_data->cfg->phyctl_offset;
int i;
mutex_lock(&phy_data->mutex);
if (phy_data->has_a33_phyctl) {
phyctl = phy_data->base + REG_PHYCTL_A33;
if (phy_data->cfg->type == sun8i_a33_phy) {
/* A33 needs us to set phyctl to 0 explicitly */
writel(0, phyctl);
} else {
phyctl = phy_data->base + REG_PHYCTL_A10;
}
for (i = 0; i < len; i++) {
@ -230,6 +243,7 @@ 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;
u32 val;
ret = clk_prepare_enable(phy->clk);
if (ret)
@ -241,15 +255,26 @@ static int sun4i_usb_phy_init(struct phy *_phy)
return ret;
}
/* Enable USB 45 Ohm resistor calibration */
if (phy->index == 0)
sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1);
if (data->cfg->type == sun8i_h3_phy) {
if (phy->index == 0) {
val = readl(data->base + REG_PHY_UNK_H3);
writel(val & ~1, data->base + REG_PHY_UNK_H3);
}
/* Adjust PHY's magnitude and rate */
sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5);
val = readl(phy->pmu + REG_PMU_UNK_H3);
writel(val & ~2, phy->pmu + REG_PMU_UNK_H3);
} else {
/* Enable USB 45 Ohm resistor calibration */
if (phy->index == 0)
sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1);
/* Disconnect threshold adjustment */
sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2);
/* 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->cfg->disc_thresh, 2);
}
sun4i_usb_phy_passby(phy, 1);
@ -476,7 +501,7 @@ static struct phy *sun4i_usb_phy_xlate(struct device *dev,
{
struct sun4i_usb_phy_data *data = dev_get_drvdata(dev);
if (args->args[0] >= data->num_phys)
if (args->args[0] >= data->cfg->num_phys)
return ERR_PTR(-ENODEV);
return data->phys[args->args[0]].phy;
@ -511,7 +536,6 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
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, ret;
@ -522,29 +546,9 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
mutex_init(&data->mutex);
INIT_DELAYED_WORK(&data->detect, sun4i_usb_phy0_id_vbus_det_scan);
dev_set_drvdata(dev, data);
if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy") ||
of_device_is_compatible(np, "allwinner,sun8i-a23-usb-phy") ||
of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy"))
data->num_phys = 2;
else
data->num_phys = 3;
if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy") ||
of_device_is_compatible(np, "allwinner,sun7i-a20-usb-phy"))
data->disc_thresh = 2;
else
data->disc_thresh = 3;
if (of_device_is_compatible(np, "allwinner,sun6i-a31-usb-phy") ||
of_device_is_compatible(np, "allwinner,sun8i-a23-usb-phy") ||
of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy"))
dedicated_clocks = true;
else
dedicated_clocks = false;
if (of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy"))
data->has_a33_phyctl = true;
data->cfg = of_device_get_match_data(dev);
if (!data->cfg)
return -EINVAL;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl");
data->base = devm_ioremap_resource(dev, res);
@ -590,7 +594,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
}
}
for (i = 0; i < data->num_phys; i++) {
for (i = 0; i < data->cfg->num_phys; i++) {
struct sun4i_usb_phy *phy = data->phys + i;
char name[16];
@ -602,7 +606,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
phy->vbus = NULL;
}
if (dedicated_clocks)
if (data->cfg->dedicated_clocks)
snprintf(name, sizeof(name), "usb%d_phy", i);
else
strlcpy(name, "usb_phy", sizeof(name));
@ -689,13 +693,69 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
return 0;
}
static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = {
.num_phys = 3,
.type = sun4i_a10_phy,
.disc_thresh = 3,
.phyctl_offset = REG_PHYCTL_A10,
.dedicated_clocks = false,
};
static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = {
.num_phys = 2,
.type = sun4i_a10_phy,
.disc_thresh = 2,
.phyctl_offset = REG_PHYCTL_A10,
.dedicated_clocks = false,
};
static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = {
.num_phys = 3,
.type = sun4i_a10_phy,
.disc_thresh = 3,
.phyctl_offset = REG_PHYCTL_A10,
.dedicated_clocks = true,
};
static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = {
.num_phys = 3,
.type = sun4i_a10_phy,
.disc_thresh = 2,
.phyctl_offset = REG_PHYCTL_A10,
.dedicated_clocks = false,
};
static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = {
.num_phys = 2,
.type = sun4i_a10_phy,
.disc_thresh = 3,
.phyctl_offset = REG_PHYCTL_A10,
.dedicated_clocks = true,
};
static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = {
.num_phys = 2,
.type = sun8i_a33_phy,
.disc_thresh = 3,
.phyctl_offset = REG_PHYCTL_A33,
.dedicated_clocks = true,
};
static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = {
.num_phys = 4,
.type = sun8i_h3_phy,
.disc_thresh = 3,
.dedicated_clocks = true,
};
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" },
{ .compatible = "allwinner,sun8i-a23-usb-phy" },
{ .compatible = "allwinner,sun8i-a33-usb-phy" },
{ .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg },
{ .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg },
{ .compatible = "allwinner,sun6i-a31-usb-phy", .data = &sun6i_a31_cfg },
{ .compatible = "allwinner,sun7i-a20-usb-phy", .data = &sun7i_a20_cfg },
{ .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg },
{ .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg },
{ .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg },
{ },
};
MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match);

View file

@ -56,6 +56,18 @@
#define SATA_PLL_SOFT_RESET BIT(18)
#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000
#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14
#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000
#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22
#define PIPE3_PHY_TX_RX_POWERON 0x3
#define PIPE3_PHY_TX_RX_POWEROFF 0x0
#define PCIE_PCS_MASK 0xFF0000
#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10
/*
* This is an Empirical value that works, need to confirm the actual
* value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status
@ -86,8 +98,12 @@ struct ti_pipe3 {
struct clk *refclk;
struct clk *div_clk;
struct pipe3_dpll_map *dpll_map;
struct regmap *phy_power_syscon; /* ctrl. reg. acces */
struct regmap *pcs_syscon; /* ctrl. reg. acces */
struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */
unsigned int dpll_reset_reg; /* reg. index within syscon */
unsigned int power_reg; /* power reg. index within syscon */
unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */
bool sata_refclk_enabled;
};
@ -144,20 +160,49 @@ static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy);
static int ti_pipe3_power_off(struct phy *x)
{
u32 val;
int ret;
struct ti_pipe3 *phy = phy_get_drvdata(x);
omap_control_phy_power(phy->control_dev, 0);
if (!phy->phy_power_syscon) {
omap_control_phy_power(phy->control_dev, 0);
return 0;
}
return 0;
val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val);
return ret;
}
static int ti_pipe3_power_on(struct phy *x)
{
u32 val;
u32 mask;
int ret;
unsigned long rate;
struct ti_pipe3 *phy = phy_get_drvdata(x);
omap_control_phy_power(phy->control_dev, 1);
if (!phy->phy_power_syscon) {
omap_control_phy_power(phy->control_dev, 1);
return 0;
}
return 0;
rate = clk_get_rate(phy->sys_clk);
if (!rate) {
dev_err(phy->dev, "Invalid clock rate\n");
return -EINVAL;
}
rate = rate / 1000000;
mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK |
OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
mask, val);
return ret;
}
static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy)
@ -229,8 +274,15 @@ static int ti_pipe3_init(struct phy *x)
* 18-1804.
*/
if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
omap_control_pcie_pcs(phy->control_dev, 0x96);
return 0;
if (!phy->pcs_syscon) {
omap_control_pcie_pcs(phy->control_dev, 0x96);
return 0;
}
val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT;
ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg,
PCIE_PCS_MASK, val);
return ret;
}
/* Bring it out of IDLE if it is IDLE */
@ -308,51 +360,15 @@ static const struct phy_ops ops = {
static const struct of_device_id ti_pipe3_id_table[];
static int ti_pipe3_probe(struct platform_device *pdev)
static int ti_pipe3_get_clk(struct ti_pipe3 *phy)
{
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;
struct device *dev = phy->dev;
struct device_node *node = dev->of_node;
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(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;
}
}
phy->refclk = devm_clk_get(phy->dev, "refclk");
phy->refclk = devm_clk_get(dev, "refclk");
if (IS_ERR(phy->refclk)) {
dev_err(&pdev->dev, "unable to get refclk\n");
dev_err(dev, "unable to get refclk\n");
/* older DTBs have missing refclk in SATA PHY
* so don't bail out in case of SATA PHY.
*/
@ -361,80 +377,194 @@ static int ti_pipe3_probe(struct platform_device *pdev)
}
if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
phy->wkupclk = devm_clk_get(phy->dev, "wkupclk");
phy->wkupclk = devm_clk_get(dev, "wkupclk");
if (IS_ERR(phy->wkupclk)) {
dev_err(&pdev->dev, "unable to get wkupclk\n");
dev_err(dev, "unable to get wkupclk\n");
return PTR_ERR(phy->wkupclk);
}
} else {
phy->wkupclk = ERR_PTR(-ENODEV);
phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-pllreset");
if (IS_ERR(phy->dpll_reset_syscon)) {
dev_info(&pdev->dev,
"can't get syscon-pllreset, sata dpll won't idle\n");
phy->dpll_reset_syscon = NULL;
} else {
if (of_property_read_u32_index(node,
"syscon-pllreset", 1,
&phy->dpll_reset_reg)) {
dev_err(&pdev->dev,
"couldn't get pllreset reg. offset\n");
return -EINVAL;
}
}
if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") ||
phy->phy_power_syscon) {
phy->sys_clk = devm_clk_get(dev, "sysclk");
if (IS_ERR(phy->sys_clk)) {
dev_err(dev, "unable to get sysclk\n");
return -EINVAL;
}
}
if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
clk = devm_clk_get(phy->dev, "dpll_ref");
clk = devm_clk_get(dev, "dpll_ref");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get dpll ref clk\n");
dev_err(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");
clk = devm_clk_get(dev, "dpll_ref_m2");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get dpll ref m2 clk\n");
dev_err(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");
clk = devm_clk_get(dev, "phy-div");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get phy-div clk\n");
dev_err(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");
phy->div_clk = devm_clk_get(dev, "div-clk");
if (IS_ERR(phy->div_clk)) {
dev_err(&pdev->dev, "unable to get div-clk\n");
dev_err(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 0;
}
static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy)
{
struct device *dev = phy->dev;
struct device_node *node = dev->of_node;
struct device_node *control_node;
struct platform_device *control_pdev;
phy->phy_power_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-phy-power");
if (IS_ERR(phy->phy_power_syscon)) {
dev_dbg(dev,
"can't get syscon-phy-power, using control device\n");
phy->phy_power_syscon = NULL;
} else {
if (of_property_read_u32_index(node,
"syscon-phy-power", 1,
&phy->power_reg)) {
dev_err(dev, "couldn't get power reg. offset\n");
return -EINVAL;
}
}
if (!phy->phy_power_syscon) {
control_node = of_parse_phandle(node, "ctrl-module", 0);
if (!control_node) {
dev_err(dev, "Failed to get control device phandle\n");
return -EINVAL;
}
control_pdev = of_find_device_by_node(control_node);
if (!control_pdev) {
dev_err(dev, "Failed to get control device\n");
return -EINVAL;
}
phy->control_dev = &control_pdev->dev;
}
if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-pcs");
if (IS_ERR(phy->pcs_syscon)) {
dev_dbg(dev,
"can't get syscon-pcs, using omap control\n");
phy->pcs_syscon = NULL;
} else {
if (of_property_read_u32_index(node,
"syscon-pcs", 1,
&phy->pcie_pcs_reg)) {
dev_err(dev,
"couldn't get pcie pcs reg. offset\n");
return -EINVAL;
}
}
}
if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-pllreset");
if (IS_ERR(phy->dpll_reset_syscon)) {
dev_info(dev,
"can't get syscon-pllreset, sata dpll won't idle\n");
phy->dpll_reset_syscon = NULL;
} else {
if (of_property_read_u32_index(node,
"syscon-pllreset", 1,
&phy->dpll_reset_reg)) {
dev_err(dev,
"couldn't get pllreset reg. offset\n");
return -EINVAL;
}
}
}
return 0;
}
static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy)
{
struct resource *res;
const struct of_device_id *match;
struct device *dev = phy->dev;
struct device_node *node = dev->of_node;
struct platform_device *pdev = to_platform_device(dev);
if (of_device_is_compatible(node, "ti,phy-pipe3-pcie"))
return 0;
match = of_match_device(ti_pipe3_id_table, dev);
if (!match)
return -EINVAL;
phy->dpll_map = (struct pipe3_dpll_map *)match->data;
if (!phy->dpll_map) {
dev_err(dev, "no DPLL data\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;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"pll_ctrl");
phy->pll_ctrl_base = devm_ioremap_resource(dev, res);
if (IS_ERR(phy->pll_ctrl_base))
return PTR_ERR(phy->pll_ctrl_base);
phy->control_dev = &control_pdev->dev;
return 0;
}
omap_control_phy_power(phy->control_dev, 0);
static int ti_pipe3_probe(struct platform_device *pdev)
{
struct ti_pipe3 *phy;
struct phy *generic_phy;
struct phy_provider *phy_provider;
struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
int ret;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->dev = dev;
ret = ti_pipe3_get_pll_base(phy);
if (ret)
return ret;
ret = ti_pipe3_get_sysctrl(phy);
if (ret)
return ret;
ret = ti_pipe3_get_clk(phy);
if (ret)
return ret;
platform_set_drvdata(pdev, phy);
pm_runtime_enable(phy->dev);
pm_runtime_enable(dev);
/*
* Prevent auto-disable of refclk for SATA PHY due to Errata i783
@ -446,13 +576,15 @@ static int ti_pipe3_probe(struct platform_device *pdev)
}
}
generic_phy = devm_phy_create(phy->dev, NULL, &ops);
generic_phy = devm_phy_create(dev, NULL, &ops);
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);
ti_pipe3_power_off(generic_phy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(phy_provider))
return PTR_ERR(phy_provider);

View file

@ -30,6 +30,12 @@ struct usb_dpll_params {
u32 mf;
};
enum omap_usb_phy_type {
TYPE_USB2, /* USB2_PHY, power down in CONTROL_DEV_CONF */
TYPE_DRA7USB2, /* USB2 PHY, power and power_aux e.g. DRA7 */
TYPE_AM437USB2, /* USB2 PHY, power e.g. AM437x */
};
struct omap_usb {
struct usb_phy phy;
struct phy_companion *comparator;
@ -40,11 +46,20 @@ struct omap_usb {
struct clk *wkupclk;
struct clk *optclk;
u8 flags;
enum omap_usb_phy_type type;
struct regmap *syscon_phy_power; /* ctrl. reg. acces */
unsigned int power_reg; /* power reg. index within syscon */
u32 mask;
u32 power_on;
u32 power_off;
};
struct usb_phy_data {
const char *label;
u8 flags;
u32 mask;
u32 power_on;
u32 power_off;
};
/* Driver Flags */
@ -52,6 +67,14 @@ struct usb_phy_data {
#define OMAP_USB2_HAS_SET_VBUS (1 << 1)
#define OMAP_USB2_CALIBRATE_FALSE_DISCONNECT (1 << 2)
#define OMAP_DEV_PHY_PD BIT(0)
#define OMAP_USB2_PHY_PD BIT(28)
#define AM437X_USB2_PHY_PD BIT(0)
#define AM437X_USB2_OTG_PD BIT(1)
#define AM437X_USB2_OTGVDET_EN BIT(19)
#define AM437X_USB2_OTGSESSEND_EN BIT(20)
#define phy_to_omapusb(x) container_of((x), struct omap_usb, phy)
#if defined(CONFIG_OMAP_USB2) || defined(CONFIG_OMAP_USB2_MODULE)