From 3cffa0818dc82a90d1a3df5ea7111999cb7b8646 Mon Sep 17 00:00:00 2001 From: JC Kuo Date: Thu, 21 Feb 2019 16:46:31 +0100 Subject: phy: tegra: xusb: Skip single function lane programming Tegra186 USB2 pads and USB3 pads do not have hardware mux for changing the pad function. For such "lanes", we can skip the lane mux register programming. Signed-off-by: JC Kuo Signed-off-by: Thierry Reding Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/tegra/xusb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/phy') diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 5b3b8863363e..e3bc60cfe6a1 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -313,6 +313,10 @@ static void tegra_xusb_lane_program(struct tegra_xusb_lane *lane) const struct tegra_xusb_lane_soc *soc = lane->soc; u32 value; + /* skip single function lanes */ + if (soc->num_funcs < 2) + return; + /* choose function */ value = padctl_readl(padctl, soc->offset); value &= ~(soc->mask << soc->shift); -- cgit v1.2.3-55-g7522 From 5311a7b89502592045812f97294f756b1fca132b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 21 Feb 2019 16:46:32 +0100 Subject: phy: tegra: xusb: Parse dual-role mode property The device tree bindings document the "mode" property of "ports" subnodes, but the driver was not parsing the property. In preparation for adding role switching, parse the property at probe time. Based on work by JC Kuo . Reviewed-by: JC Kuo Signed-off-by: Thierry Reding Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/tegra/xusb.c | 21 +++++++++++++++++++++ drivers/phy/tegra/xusb.h | 3 +++ 2 files changed, 24 insertions(+) (limited to 'drivers/phy') diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index e3bc60cfe6a1..57a2d08ef6da 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -546,13 +546,34 @@ static void tegra_xusb_port_unregister(struct tegra_xusb_port *port) device_unregister(&port->dev); } +static const char *const modes[] = { + [USB_DR_MODE_UNKNOWN] = "", + [USB_DR_MODE_HOST] = "host", + [USB_DR_MODE_PERIPHERAL] = "peripheral", + [USB_DR_MODE_OTG] = "otg", +}; + static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2) { struct tegra_xusb_port *port = &usb2->base; struct device_node *np = port->dev.of_node; + const char *mode; usb2->internal = of_property_read_bool(np, "nvidia,internal"); + if (!of_property_read_string(np, "mode", &mode)) { + int err = match_string(modes, ARRAY_SIZE(modes), mode); + if (err < 0) { + dev_err(&port->dev, "invalid value %s for \"mode\"\n", + mode); + usb2->mode = USB_DR_MODE_UNKNOWN; + } else { + usb2->mode = err; + } + } else { + usb2->mode = USB_DR_MODE_HOST; + } + usb2->supply = devm_regulator_get(&port->dev, "vbus"); return PTR_ERR_OR_ZERO(usb2->supply); } diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index b49dbc36efa3..bb60fc09c752 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -19,6 +19,8 @@ #include #include +#include + /* legacy entry points for backwards-compatibility */ int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev); int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev); @@ -271,6 +273,7 @@ struct tegra_xusb_usb2_port { struct tegra_xusb_port base; struct regulator *supply; + enum usb_dr_mode mode; bool internal; }; -- cgit v1.2.3-55-g7522 From a630d54dfa937a937e3faf172ca41b9bd2647c72 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 21 Feb 2019 16:46:33 +0100 Subject: phy: tegra: xusb: Add support for power supplies Support enabling various supplies needed to provide power to the PLLs and logic used to drive the USB, PCI and SATA pads. Reviewed-by: JC Kuo Signed-off-by: Thierry Reding Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/tegra/xusb.c | 34 +++++++++++++++++++++++++++++++++- drivers/phy/tegra/xusb.h | 5 +++++ 2 files changed, 38 insertions(+), 1 deletion(-) (limited to 'drivers/phy') diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 57a2d08ef6da..e510629f4f1c 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -864,6 +864,7 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev) struct tegra_xusb_padctl *padctl; const struct of_device_id *match; struct resource *res; + unsigned int i; int err; /* for backwards compatibility with old device trees */ @@ -901,14 +902,38 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev) goto remove; } + padctl->supplies = devm_kcalloc(&pdev->dev, padctl->soc->num_supplies, + sizeof(*padctl->supplies), GFP_KERNEL); + if (!padctl->supplies) { + err = -ENOMEM; + goto remove; + } + + for (i = 0; i < padctl->soc->num_supplies; i++) + padctl->supplies[i].supply = padctl->soc->supply_names[i]; + + err = devm_regulator_bulk_get(&pdev->dev, padctl->soc->num_supplies, + padctl->supplies); + if (err < 0) { + dev_err(&pdev->dev, "failed to get regulators: %d\n", err); + goto remove; + } + err = reset_control_deassert(padctl->rst); if (err < 0) goto remove; + err = regulator_bulk_enable(padctl->soc->num_supplies, + padctl->supplies); + if (err < 0) { + dev_err(&pdev->dev, "failed to enable supplies: %d\n", err); + goto reset; + } + err = tegra_xusb_setup_pads(padctl); if (err < 0) { dev_err(&pdev->dev, "failed to setup pads: %d\n", err); - goto reset; + goto power_down; } err = tegra_xusb_setup_ports(padctl); @@ -921,6 +946,8 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev) remove_pads: tegra_xusb_remove_pads(padctl); +power_down: + regulator_bulk_disable(padctl->soc->num_supplies, padctl->supplies); reset: reset_control_assert(padctl->rst); remove: @@ -936,6 +963,11 @@ static int tegra_xusb_padctl_remove(struct platform_device *pdev) tegra_xusb_remove_ports(padctl); tegra_xusb_remove_pads(padctl); + err = regulator_bulk_disable(padctl->soc->num_supplies, + padctl->supplies); + if (err < 0) + dev_err(&pdev->dev, "failed to disable supplies: %d\n", err); + err = reset_control_assert(padctl->rst); if (err < 0) dev_err(&pdev->dev, "failed to assert reset: %d\n", err); diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index bb60fc09c752..5d5d22f6cb41 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -370,6 +370,9 @@ struct tegra_xusb_padctl_soc { } ports; const struct tegra_xusb_padctl_ops *ops; + + const char * const *supply_names; + unsigned int num_supplies; }; struct tegra_xusb_padctl { @@ -393,6 +396,8 @@ struct tegra_xusb_padctl { unsigned int enable; struct clk *clk; + + struct regulator_bulk_data *supplies; }; static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value, -- cgit v1.2.3-55-g7522 From bbf711682cd570697086e88388a2c718da918894 Mon Sep 17 00:00:00 2001 From: JC Kuo Date: Thu, 21 Feb 2019 16:46:34 +0100 Subject: phy: tegra: xusb: Add Tegra186 support Add support for the XUSB pad controller found on Tegra186 SoCs. It is mostly similar to the same IP found on earlier chips, but the number of pads exposed differs, as do the programming sequences. Note that the DVDD_PEX, DVDD_PEX_PLL, HVDD_PEX and HVDD_PEX_PLL power supplies of the XUSB pad controller require strict power sequencing and are therefore controlled by the PMIC on Tegra186. Signed-off-by: JC Kuo Signed-off-by: Thierry Reding [dan.carpenter@oracle.com: Fix testing the wrong variable in probe()] Signed-off-by: Dan Carpenter [yuehaibing@huawei.com: Make two functions static to fix sparse warning] Signed-off-by: YueHaibing Signed-off-by: Kishon Vijay Abraham I --- MAINTAINERS | 5 + drivers/phy/tegra/Makefile | 1 + drivers/phy/tegra/xusb-tegra186.c | 899 ++++++++++++++++++++++++++++++++++++++ drivers/phy/tegra/xusb.c | 6 + drivers/phy/tegra/xusb.h | 27 ++ 5 files changed, 938 insertions(+) create mode 100644 drivers/phy/tegra/xusb-tegra186.c (limited to 'drivers/phy') diff --git a/MAINTAINERS b/MAINTAINERS index e17ebf70b548..1d308fd7afaa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15304,6 +15304,11 @@ M: Laxman Dewangan S: Supported F: drivers/spi/spi-tegra* +TEGRA XUSB PADCTL DRIVER +M: JC Kuo +S: Supported +F: drivers/phy/tegra/xusb* + TEHUTI ETHERNET DRIVER M: Andy Gospodarek L: netdev@vger.kernel.org diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index 898589238fd9..a93cd9a499b2 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -4,3 +4,4 @@ phy-tegra-xusb-y += xusb.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c new file mode 100644 index 000000000000..6f3afaf9398f --- /dev/null +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -0,0 +1,899 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2019, NVIDIA CORPORATION. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "xusb.h" + +/* FUSE USB_CALIB registers */ +#define HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? (11 + (x - 1) * 6) : 0) +#define HS_CURR_LEVEL_PAD_MASK 0x3f +#define HS_TERM_RANGE_ADJ_SHIFT 7 +#define HS_TERM_RANGE_ADJ_MASK 0xf +#define HS_SQUELCH_SHIFT 29 +#define HS_SQUELCH_MASK 0x7 + +#define RPD_CTRL_SHIFT 0 +#define RPD_CTRL_MASK 0x1f + +/* XUSB PADCTL registers */ +#define XUSB_PADCTL_USB2_PAD_MUX 0x4 +#define USB2_PORT_SHIFT(x) ((x) * 2) +#define USB2_PORT_MASK 0x3 +#define PORT_XUSB 1 +#define HSIC_PORT_SHIFT(x) ((x) + 20) +#define HSIC_PORT_MASK 0x1 +#define PORT_HSIC 0 + +#define XUSB_PADCTL_USB2_PORT_CAP 0x8 +#define XUSB_PADCTL_SS_PORT_CAP 0xc +#define PORTX_CAP_SHIFT(x) ((x) * 4) +#define PORT_CAP_MASK 0x3 +#define PORT_CAP_DISABLED 0x0 +#define PORT_CAP_HOST 0x1 +#define PORT_CAP_DEVICE 0x2 +#define PORT_CAP_OTG 0x3 + +#define XUSB_PADCTL_ELPG_PROGRAM 0x20 +#define USB2_PORT_WAKE_INTERRUPT_ENABLE(x) BIT(x) +#define USB2_PORT_WAKEUP_EVENT(x) BIT((x) + 7) +#define SS_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 14) +#define SS_PORT_WAKEUP_EVENT(x) BIT((x) + 21) +#define USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 28) +#define USB2_HSIC_PORT_WAKEUP_EVENT(x) BIT((x) + 30) +#define ALL_WAKE_EVENTS \ + (USB2_PORT_WAKEUP_EVENT(0) | USB2_PORT_WAKEUP_EVENT(1) | \ + USB2_PORT_WAKEUP_EVENT(2) | SS_PORT_WAKEUP_EVENT(0) | \ + SS_PORT_WAKEUP_EVENT(1) | SS_PORT_WAKEUP_EVENT(2) | \ + USB2_HSIC_PORT_WAKEUP_EVENT(0)) + +#define XUSB_PADCTL_ELPG_PROGRAM_1 0x24 +#define SSPX_ELPG_CLAMP_EN(x) BIT(0 + (x) * 3) +#define SSPX_ELPG_CLAMP_EN_EARLY(x) BIT(1 + (x) * 3) +#define SSPX_ELPG_VCORE_DOWN(x) BIT(2 + (x) * 3) + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x88 + (x) * 0x40) +#define HS_CURR_LEVEL(x) ((x) & 0x3f) +#define TERM_SEL BIT(25) +#define USB2_OTG_PD BIT(26) +#define USB2_OTG_PD2 BIT(27) +#define USB2_OTG_PD2_OVRD_EN BIT(28) +#define USB2_OTG_PD_ZI BIT(29) + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x8c + (x) * 0x40) +#define USB2_OTG_PD_DR BIT(2) +#define TERM_RANGE_ADJ(x) (((x) & 0xf) << 3) +#define RPD_CTRL(x) (((x) & 0x1f) << 26) + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284 +#define BIAS_PAD_PD BIT(11) +#define HS_SQUELCH_LEVEL(x) (((x) & 0x7) << 0) + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288 +#define USB2_TRK_START_TIMER(x) (((x) & 0x7f) << 12) +#define USB2_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 19) +#define USB2_PD_TRK BIT(26) + +#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20) +#define HSIC_PD_TX_DATA0 BIT(1) +#define HSIC_PD_TX_STROBE BIT(3) +#define HSIC_PD_RX_DATA0 BIT(4) +#define HSIC_PD_RX_STROBE BIT(6) +#define HSIC_PD_ZI_DATA0 BIT(7) +#define HSIC_PD_ZI_STROBE BIT(9) +#define HSIC_RPD_DATA0 BIT(13) +#define HSIC_RPD_STROBE BIT(15) +#define HSIC_RPU_DATA0 BIT(16) +#define HSIC_RPU_STROBE BIT(18) + +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL0 0x340 +#define HSIC_TRK_START_TIMER(x) (((x) & 0x7f) << 5) +#define HSIC_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 12) +#define HSIC_PD_TRK BIT(19) + +#define USB2_VBUS_ID 0x360 +#define VBUS_OVERRIDE BIT(14) +#define ID_OVERRIDE(x) (((x) & 0xf) << 18) +#define ID_OVERRIDE_FLOATING ID_OVERRIDE(8) +#define ID_OVERRIDE_GROUNDED ID_OVERRIDE(0) + +#define TEGRA186_LANE(_name, _offset, _shift, _mask, _type) \ + { \ + .name = _name, \ + .offset = _offset, \ + .shift = _shift, \ + .mask = _mask, \ + .num_funcs = ARRAY_SIZE(tegra186_##_type##_functions), \ + .funcs = tegra186_##_type##_functions, \ + } + +struct tegra_xusb_fuse_calibration { + u32 *hs_curr_level; + u32 hs_squelch; + u32 hs_term_range_adj; + u32 rpd_ctrl; +}; + +struct tegra186_xusb_padctl { + struct tegra_xusb_padctl base; + + struct tegra_xusb_fuse_calibration calib; + + /* UTMI bias and tracking */ + struct clk *usb2_trk_clk; + unsigned int bias_pad_enable; +}; + +static inline struct tegra186_xusb_padctl * +to_tegra186_xusb_padctl(struct tegra_xusb_padctl *padctl) +{ + return container_of(padctl, struct tegra186_xusb_padctl, base); +} + +/* USB 2.0 UTMI PHY support */ +static struct tegra_xusb_lane * +tegra186_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_usb2_lane *usb2; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&usb2->base.list); + usb2->base.soc = &pad->soc->lanes[index]; + usb2->base.index = index; + usb2->base.pad = pad; + usb2->base.np = np; + + err = tegra_xusb_lane_parse_dt(&usb2->base, np); + if (err < 0) { + kfree(usb2); + return ERR_PTR(err); + } + + return &usb2->base; +} + +static void tegra186_usb2_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + + kfree(usb2); +} + +static const struct tegra_xusb_lane_ops tegra186_usb2_lane_ops = { + .probe = tegra186_usb2_lane_probe, + .remove = tegra186_usb2_lane_remove, +}; + +static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl) +{ + struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl); + struct device *dev = padctl->dev; + u32 value; + int err; + + mutex_lock(&padctl->lock); + + if (priv->bias_pad_enable++ > 0) { + mutex_unlock(&padctl->lock); + return; + } + + err = clk_prepare_enable(priv->usb2_trk_clk); + if (err < 0) + dev_warn(dev, "failed to enable USB2 trk clock: %d\n", err); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value &= ~USB2_TRK_START_TIMER(~0); + value |= USB2_TRK_START_TIMER(0x1e); + value &= ~USB2_TRK_DONE_RESET_TIMER(~0); + value |= USB2_TRK_DONE_RESET_TIMER(0xa); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~BIAS_PAD_PD; + value &= ~HS_SQUELCH_LEVEL(~0); + value |= HS_SQUELCH_LEVEL(priv->calib.hs_squelch); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + + udelay(1); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value &= ~USB2_PD_TRK; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + + mutex_unlock(&padctl->lock); +} + +static void tegra186_utmi_bias_pad_power_off(struct tegra_xusb_padctl *padctl) +{ + struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl); + u32 value; + + mutex_lock(&padctl->lock); + + if (WARN_ON(priv->bias_pad_enable == 0)) { + mutex_unlock(&padctl->lock); + return; + } + + if (--priv->bias_pad_enable > 0) { + mutex_unlock(&padctl->lock); + return; + } + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value |= USB2_PD_TRK; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + + clk_disable_unprepare(priv->usb2_trk_clk); + + mutex_unlock(&padctl->lock); +} + +static void tegra_phy_xusb_utmi_pad_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port; + struct device *dev = padctl->dev; + unsigned int index = lane->index; + u32 value; + + if (!phy) + return; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(dev, "no port found for USB2 lane %u\n", index); + return; + } + + tegra186_utmi_bias_pad_power_on(padctl); + + udelay(2); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value &= ~USB2_OTG_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value &= ~USB2_OTG_PD_DR; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); +} + +static void tegra_phy_xusb_utmi_pad_power_down(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + u32 value; + + if (!phy) + return; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value |= USB2_OTG_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value |= USB2_OTG_PD_DR; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + + udelay(2); + + tegra186_utmi_bias_pad_power_off(padctl); +} + +static int tegra186_utmi_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl); + struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + u32 value; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX); + value &= ~(USB2_PORT_MASK << USB2_PORT_SHIFT(index)); + value |= (PORT_XUSB << USB2_PORT_SHIFT(index)); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP); + value &= ~(PORT_CAP_MASK << PORTX_CAP_SHIFT(index)); + + if (port->mode == USB_DR_MODE_UNKNOWN) + value |= (PORT_CAP_DISABLED << PORTX_CAP_SHIFT(index)); + else if (port->mode == USB_DR_MODE_PERIPHERAL) + value |= (PORT_CAP_DEVICE << PORTX_CAP_SHIFT(index)); + else if (port->mode == USB_DR_MODE_HOST) + value |= (PORT_CAP_HOST << PORTX_CAP_SHIFT(index)); + else if (port->mode == USB_DR_MODE_OTG) + value |= (PORT_CAP_OTG << PORTX_CAP_SHIFT(index)); + + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value &= ~USB2_OTG_PD_ZI; + value |= TERM_SEL; + value &= ~HS_CURR_LEVEL(~0); + + if (usb2->hs_curr_level_offset) { + int hs_current_level; + + hs_current_level = (int)priv->calib.hs_curr_level[index] + + usb2->hs_curr_level_offset; + + if (hs_current_level < 0) + hs_current_level = 0; + if (hs_current_level > 0x3f) + hs_current_level = 0x3f; + + value |= HS_CURR_LEVEL(hs_current_level); + } else { + value |= HS_CURR_LEVEL(priv->calib.hs_curr_level[index]); + } + + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value &= ~TERM_RANGE_ADJ(~0); + value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj); + value &= ~RPD_CTRL(~0); + value |= RPD_CTRL(priv->calib.rpd_ctrl); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + + /* TODO: pad power saving */ + tegra_phy_xusb_utmi_pad_power_on(phy); + return 0; +} + +static int tegra186_utmi_phy_power_off(struct phy *phy) +{ + /* TODO: pad power saving */ + tegra_phy_xusb_utmi_pad_power_down(phy); + + return 0; +} + +static int tegra186_utmi_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + int err; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + if (port->supply && port->mode == USB_DR_MODE_HOST) { + err = regulator_enable(port->supply); + if (err) { + dev_err(dev, "failed to enable port %u VBUS: %d\n", + index, err); + return err; + } + } + + return 0; +} + +static int tegra186_utmi_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + int err; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + if (port->supply && port->mode == USB_DR_MODE_HOST) { + err = regulator_disable(port->supply); + if (err) { + dev_err(dev, "failed to disable port %u VBUS: %d\n", + index, err); + return err; + } + } + + return 0; +} + +static const struct phy_ops utmi_phy_ops = { + .init = tegra186_utmi_phy_init, + .exit = tegra186_utmi_phy_exit, + .power_on = tegra186_utmi_phy_power_on, + .power_off = tegra186_utmi_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra186_usb2_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl); + struct tegra_xusb_usb2_pad *usb2; + struct tegra_xusb_pad *pad; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + pad = &usb2->base; + pad->ops = &tegra186_usb2_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(usb2); + goto out; + } + + priv->usb2_trk_clk = devm_clk_get(&pad->dev, "trk"); + if (IS_ERR(priv->usb2_trk_clk)) { + err = PTR_ERR(priv->usb2_trk_clk); + dev_dbg(&pad->dev, "failed to get usb2 trk clock: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &utmi_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra186_usb2_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad); + + kfree(usb2); +} + +static const struct tegra_xusb_pad_ops tegra186_usb2_pad_ops = { + .probe = tegra186_usb2_pad_probe, + .remove = tegra186_usb2_pad_remove, +}; + +static const char * const tegra186_usb2_functions[] = { + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra186_usb2_lanes[] = { + TEGRA186_LANE("usb2-0", 0, 0, 0, usb2), + TEGRA186_LANE("usb2-1", 0, 0, 0, usb2), + TEGRA186_LANE("usb2-2", 0, 0, 0, usb2), +}; + +static const struct tegra_xusb_pad_soc tegra186_usb2_pad = { + .name = "usb2", + .num_lanes = ARRAY_SIZE(tegra186_usb2_lanes), + .lanes = tegra186_usb2_lanes, + .ops = &tegra186_usb2_pad_ops, +}; + +static int tegra186_usb2_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra186_usb2_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra186_usb2_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "usb2", port->index); +} + +static const struct tegra_xusb_port_ops tegra186_usb2_port_ops = { + .enable = tegra186_usb2_port_enable, + .disable = tegra186_usb2_port_disable, + .map = tegra186_usb2_port_map, +}; + +/* SuperSpeed PHY support */ +static struct tegra_xusb_lane * +tegra186_usb3_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_usb3_lane *usb3; + int err; + + usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&usb3->base.list); + usb3->base.soc = &pad->soc->lanes[index]; + usb3->base.index = index; + usb3->base.pad = pad; + usb3->base.np = np; + + err = tegra_xusb_lane_parse_dt(&usb3->base, np); + if (err < 0) { + kfree(usb3); + return ERR_PTR(err); + } + + return &usb3->base; +} + +static void tegra186_usb3_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_usb3_lane *usb3 = to_usb3_lane(lane); + + kfree(usb3); +} + +static const struct tegra_xusb_lane_ops tegra186_usb3_lane_ops = { + .probe = tegra186_usb3_lane_probe, + .remove = tegra186_usb3_lane_remove, +}; +static int tegra186_usb3_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra186_usb3_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra186_usb3_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "usb3", port->index); +} + +static const struct tegra_xusb_port_ops tegra186_usb3_port_ops = { + .enable = tegra186_usb3_port_enable, + .disable = tegra186_usb3_port_disable, + .map = tegra186_usb3_port_map, +}; + +static int tegra186_usb3_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb3_port *port; + struct tegra_xusb_usb2_port *usb2; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + u32 value; + + port = tegra_xusb_find_usb3_port(padctl, index); + if (!port) { + dev_err(dev, "no port found for USB3 lane %u\n", index); + return -ENODEV; + } + + usb2 = tegra_xusb_find_usb2_port(padctl, port->port); + if (!usb2) { + dev_err(dev, "no companion port found for USB3 lane %u\n", + index); + return -ENODEV; + } + + mutex_lock(&padctl->lock); + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_CAP); + value &= ~(PORT_CAP_MASK << PORTX_CAP_SHIFT(index)); + + if (usb2->mode == USB_DR_MODE_UNKNOWN) + value |= (PORT_CAP_DISABLED << PORTX_CAP_SHIFT(index)); + else if (usb2->mode == USB_DR_MODE_PERIPHERAL) + value |= (PORT_CAP_DEVICE << PORTX_CAP_SHIFT(index)); + else if (usb2->mode == USB_DR_MODE_HOST) + value |= (PORT_CAP_HOST << PORTX_CAP_SHIFT(index)); + else if (usb2->mode == USB_DR_MODE_OTG) + value |= (PORT_CAP_OTG << PORTX_CAP_SHIFT(index)); + + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_CAP); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1); + value &= ~SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1); + value &= ~SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1); + value &= ~SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra186_usb3_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb3_port *port; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + u32 value; + + port = tegra_xusb_find_usb3_port(padctl, index); + if (!port) { + dev_err(dev, "no port found for USB3 lane %u\n", index); + return -ENODEV; + } + + mutex_lock(&padctl->lock); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1); + value |= SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1); + value |= SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1); + + usleep_range(250, 350); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1); + value |= SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra186_usb3_phy_init(struct phy *phy) +{ + return 0; +} + +static int tegra186_usb3_phy_exit(struct phy *phy) +{ + return 0; +} + +static const struct phy_ops usb3_phy_ops = { + .init = tegra186_usb3_phy_init, + .exit = tegra186_usb3_phy_exit, + .power_on = tegra186_usb3_phy_power_on, + .power_off = tegra186_usb3_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra186_usb3_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_usb3_pad *usb3; + struct tegra_xusb_pad *pad; + int err; + + usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return ERR_PTR(-ENOMEM); + + pad = &usb3->base; + pad->ops = &tegra186_usb3_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) { + kfree(usb3); + goto out; + } + + err = tegra_xusb_pad_register(pad, &usb3_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +out: + return ERR_PTR(err); +} + +static void tegra186_usb3_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad); + + kfree(usb2); +} + +static const struct tegra_xusb_pad_ops tegra186_usb3_pad_ops = { + .probe = tegra186_usb3_pad_probe, + .remove = tegra186_usb3_pad_remove, +}; + +static const char * const tegra186_usb3_functions[] = { + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra186_usb3_lanes[] = { + TEGRA186_LANE("usb3-0", 0, 0, 0, usb3), + TEGRA186_LANE("usb3-1", 0, 0, 0, usb3), + TEGRA186_LANE("usb3-2", 0, 0, 0, usb3), +}; + +static const struct tegra_xusb_pad_soc tegra186_usb3_pad = { + .name = "usb3", + .num_lanes = ARRAY_SIZE(tegra186_usb3_lanes), + .lanes = tegra186_usb3_lanes, + .ops = &tegra186_usb3_pad_ops, +}; + +static const struct tegra_xusb_pad_soc * const tegra186_pads[] = { + &tegra186_usb2_pad, + &tegra186_usb3_pad, +#if 0 /* TODO implement */ + &tegra186_hsic_pad, +#endif +}; + +static int +tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl) +{ + struct device *dev = padctl->base.dev; + unsigned int i, count; + u32 value, *level; + int err; + + count = padctl->base.soc->ports.usb2.count; + + level = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL); + if (!level) + return -ENOMEM; + + err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value); + if (err) { + dev_err(dev, "failed to read calibration fuse: %d\n", err); + return err; + } + + dev_dbg(dev, "FUSE_USB_CALIB_0 %#x\n", value); + + for (i = 0; i < count; i++) + level[i] = (value >> HS_CURR_LEVEL_PADX_SHIFT(i)) & + HS_CURR_LEVEL_PAD_MASK; + + padctl->calib.hs_curr_level = level; + + padctl->calib.hs_squelch = (value >> HS_SQUELCH_SHIFT) & + HS_SQUELCH_MASK; + padctl->calib.hs_term_range_adj = (value >> HS_TERM_RANGE_ADJ_SHIFT) & + HS_TERM_RANGE_ADJ_MASK; + + err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value); + if (err) { + dev_err(dev, "failed to read calibration fuse: %d\n", err); + return err; + } + + dev_dbg(dev, "FUSE_USB_CALIB_EXT_0 %#x\n", value); + + padctl->calib.rpd_ctrl = (value >> RPD_CTRL_SHIFT) & RPD_CTRL_MASK; + + return 0; +} + +static struct tegra_xusb_padctl * +tegra186_xusb_padctl_probe(struct device *dev, + const struct tegra_xusb_padctl_soc *soc) +{ + struct tegra186_xusb_padctl *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + priv->base.dev = dev; + priv->base.soc = soc; + + err = tegra186_xusb_read_fuse_calibration(priv); + if (err < 0) + return ERR_PTR(err); + + return &priv->base; +} + +static void tegra186_xusb_padctl_remove(struct tegra_xusb_padctl *padctl) +{ +} + +static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = { + .probe = tegra186_xusb_padctl_probe, + .remove = tegra186_xusb_padctl_remove, +}; + +static const char * const tegra186_xusb_padctl_supply_names[] = { + "avdd-pll-erefeut", + "avdd-usb", + "vclamp-usb", + "vddio-hsic", +}; + +const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra186_pads), + .pads = tegra186_pads, + .ports = { + .usb2 = { + .ops = &tegra186_usb2_port_ops, + .count = 3, + }, +#if 0 /* TODO implement */ + .hsic = { + .ops = &tegra186_hsic_port_ops, + .count = 1, + }, +#endif + .usb3 = { + .ops = &tegra186_usb3_port_ops, + .count = 3, + }, + }, + .ops = &tegra186_xusb_padctl_ops, + .supply_names = tegra186_xusb_padctl_supply_names, + .num_supplies = ARRAY_SIZE(tegra186_xusb_padctl_supply_names), +}; +EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc); + +MODULE_AUTHOR("JC Kuo "); +MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index e510629f4f1c..0417213ed68b 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -67,6 +67,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = { .compatible = "nvidia,tegra210-xusb-padctl", .data = &tegra210_xusb_padctl_soc, }, +#endif +#if defined(CONFIG_ARCH_TEGRA_186_SOC) + { + .compatible = "nvidia,tegra186-xusb-padctl", + .data = &tegra186_xusb_padctl_soc, + }, #endif { } }; diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index 5d5d22f6cb41..e0028b9fe702 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -56,10 +56,21 @@ struct tegra_xusb_lane { int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, struct device_node *np); +struct tegra_xusb_usb3_lane { + struct tegra_xusb_lane base; +}; + +static inline struct tegra_xusb_usb3_lane * +to_usb3_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_usb3_lane, base); +} + struct tegra_xusb_usb2_lane { struct tegra_xusb_lane base; u32 hs_curr_level_offset; + bool powered_on; }; static inline struct tegra_xusb_usb2_lane * @@ -170,6 +181,19 @@ int tegra_xusb_pad_register(struct tegra_xusb_pad *pad, const struct phy_ops *ops); void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad); +struct tegra_xusb_usb3_pad { + struct tegra_xusb_pad base; + + unsigned int enable; + struct mutex lock; +}; + +static inline struct tegra_xusb_usb3_pad * +to_usb3_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_usb3_pad, base); +} + struct tegra_xusb_usb2_pad { struct tegra_xusb_pad base; @@ -425,5 +449,8 @@ extern const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc; #if defined(CONFIG_ARCH_TEGRA_210_SOC) extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc; #endif +#if defined(CONFIG_ARCH_TEGRA_186_SOC) +extern const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc; +#endif #endif /* __PHY_TEGRA_XUSB_H */ -- cgit v1.2.3-55-g7522 From 4dcddbb38b640a5c9ecb23e9b9348e36825263f7 Mon Sep 17 00:00:00 2001 From: Srinath Mannam Date: Tue, 19 Mar 2019 14:45:43 +0530 Subject: phy: sr-usb: Add Stingray USB PHY driver USB PHY driver supports two types of stingray USB PHYs - Type 1 is a combo PHY contains two PHYs, one SS and one HS. - Type 2 is a single HS PHY. These two PHY versons support both Generic xHCI host controller driver and BDC Broadcom device controller driver. Signed-off-by: Srinath Mannam Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/Kconfig | 11 + drivers/phy/broadcom/Makefile | 1 + drivers/phy/broadcom/phy-bcm-sr-usb.c | 394 ++++++++++++++++++++++++++++++++++ 3 files changed, 406 insertions(+) create mode 100644 drivers/phy/broadcom/phy-bcm-sr-usb.c (limited to 'drivers/phy') diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index aa917a61071d..f30f4819c3bb 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -10,6 +10,17 @@ config PHY_CYGNUS_PCIE Enable this to support the Broadcom Cygnus PCIe PHY. If unsure, say N. +config PHY_BCM_SR_USB + tristate "Broadcom Stingray USB PHY driver" + depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST) + select GENERIC_PHY + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom Stingray USB PHY + driver. It supports all versions of Superspeed and + Highspeed PHYs. + If unsure, say N. + config BCM_KONA_USB2_PHY tristate "Broadcom Kona USB2 PHY Driver" depends on HAS_IOMEM diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile index 0f60184e6662..f453c7d3ffff 100644 --- a/drivers/phy/broadcom/Makefile +++ b/drivers/phy/broadcom/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_PHY_BRCM_USB) += phy-brcm-usb-dvr.o phy-brcm-usb-dvr-objs := phy-brcm-usb.o phy-brcm-usb-init.o obj-$(CONFIG_PHY_BCM_SR_PCIE) += phy-bcm-sr-pcie.o +obj-$(CONFIG_PHY_BCM_SR_USB) += phy-bcm-sr-usb.o diff --git a/drivers/phy/broadcom/phy-bcm-sr-usb.c b/drivers/phy/broadcom/phy-bcm-sr-usb.c new file mode 100644 index 000000000000..fe6c58910e4c --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-sr-usb.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2018 Broadcom + */ + +#include +#include +#include +#include +#include +#include + +enum bcm_usb_phy_version { + BCM_SR_USB_COMBO_PHY, + BCM_SR_USB_HS_PHY, +}; + +enum bcm_usb_phy_reg { + PLL_NDIV_FRAC, + PLL_NDIV_INT, + PLL_CTRL, + PHY_CTRL, + PHY_PLL_CTRL, +}; + +/* USB PHY registers */ + +static const u8 bcm_usb_combo_phy_ss[] = { + [PLL_CTRL] = 0x18, + [PHY_CTRL] = 0x14, +}; + +static const u8 bcm_usb_combo_phy_hs[] = { + [PLL_NDIV_FRAC] = 0x04, + [PLL_NDIV_INT] = 0x08, + [PLL_CTRL] = 0x0c, + [PHY_CTRL] = 0x10, +}; + +#define HSPLL_NDIV_INT_VAL 0x13 +#define HSPLL_NDIV_FRAC_VAL 0x1005 + +static const u8 bcm_usb_hs_phy[] = { + [PLL_NDIV_FRAC] = 0x0, + [PLL_NDIV_INT] = 0x4, + [PLL_CTRL] = 0x8, + [PHY_CTRL] = 0xc, +}; + +enum pll_ctrl_bits { + PLL_RESETB, + SSPLL_SUSPEND_EN, + PLL_SEQ_START, + PLL_LOCK, + PLL_PDIV, +}; + +static const u8 u3pll_ctrl[] = { + [PLL_RESETB] = 0, + [SSPLL_SUSPEND_EN] = 1, + [PLL_SEQ_START] = 2, + [PLL_LOCK] = 3, +}; + +#define HSPLL_PDIV_MASK 0xF +#define HSPLL_PDIV_VAL 0x1 + +static const u8 u2pll_ctrl[] = { + [PLL_PDIV] = 1, + [PLL_RESETB] = 5, + [PLL_LOCK] = 6, +}; + +enum bcm_usb_phy_ctrl_bits { + CORERDY, + AFE_LDO_PWRDWNB, + AFE_PLL_PWRDWNB, + AFE_BG_PWRDWNB, + PHY_ISO, + PHY_RESETB, + PHY_PCTL, +}; + +#define PHY_PCTL_MASK 0xffff +/* + * 0x0806 of PCTL_VAL has below bits set + * BIT-8 : refclk divider 1 + * BIT-3:2: device mode; mode is not effect + * BIT-1: soft reset active low + */ +#define HSPHY_PCTL_VAL 0x0806 +#define SSPHY_PCTL_VAL 0x0006 + +static const u8 u3phy_ctrl[] = { + [PHY_RESETB] = 1, + [PHY_PCTL] = 2, +}; + +static const u8 u2phy_ctrl[] = { + [CORERDY] = 0, + [AFE_LDO_PWRDWNB] = 1, + [AFE_PLL_PWRDWNB] = 2, + [AFE_BG_PWRDWNB] = 3, + [PHY_ISO] = 4, + [PHY_RESETB] = 5, + [PHY_PCTL] = 6, +}; + +struct bcm_usb_phy_cfg { + uint32_t type; + uint32_t version; + void __iomem *regs; + struct phy *phy; + const u8 *offset; +}; + +#define PLL_LOCK_RETRY_COUNT 1000 + +enum bcm_usb_phy_type { + USB_HS_PHY, + USB_SS_PHY, +}; + +#define NUM_BCM_SR_USB_COMBO_PHYS 2 + +static inline void bcm_usb_reg32_clrbits(void __iomem *addr, uint32_t clear) +{ + writel(readl(addr) & ~clear, addr); +} + +static inline void bcm_usb_reg32_setbits(void __iomem *addr, uint32_t set) +{ + writel(readl(addr) | set, addr); +} + +static int bcm_usb_pll_lock_check(void __iomem *addr, u32 bit) +{ + int retry; + u32 rd_data; + + retry = PLL_LOCK_RETRY_COUNT; + do { + rd_data = readl(addr); + if (rd_data & bit) + return 0; + udelay(1); + } while (--retry > 0); + + pr_err("%s: FAIL\n", __func__); + return -ETIMEDOUT; +} + +static int bcm_usb_ss_phy_init(struct bcm_usb_phy_cfg *phy_cfg) +{ + int ret = 0; + void __iomem *regs = phy_cfg->regs; + const u8 *offset; + u32 rd_data; + + offset = phy_cfg->offset; + + /* Set pctl with mode and soft reset */ + rd_data = readl(regs + offset[PHY_CTRL]); + rd_data &= ~(PHY_PCTL_MASK << u3phy_ctrl[PHY_PCTL]); + rd_data |= (SSPHY_PCTL_VAL << u3phy_ctrl[PHY_PCTL]); + writel(rd_data, regs + offset[PHY_CTRL]); + + bcm_usb_reg32_clrbits(regs + offset[PLL_CTRL], + BIT(u3pll_ctrl[SSPLL_SUSPEND_EN])); + bcm_usb_reg32_setbits(regs + offset[PLL_CTRL], + BIT(u3pll_ctrl[PLL_SEQ_START])); + bcm_usb_reg32_setbits(regs + offset[PLL_CTRL], + BIT(u3pll_ctrl[PLL_RESETB])); + + /* Maximum timeout for PLL reset done */ + msleep(30); + + ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL], + BIT(u3pll_ctrl[PLL_LOCK])); + + return ret; +} + +static int bcm_usb_hs_phy_init(struct bcm_usb_phy_cfg *phy_cfg) +{ + int ret = 0; + void __iomem *regs = phy_cfg->regs; + const u8 *offset; + u32 rd_data; + + offset = phy_cfg->offset; + + writel(HSPLL_NDIV_INT_VAL, regs + offset[PLL_NDIV_INT]); + writel(HSPLL_NDIV_FRAC_VAL, regs + offset[PLL_NDIV_FRAC]); + + rd_data = readl(regs + offset[PLL_CTRL]); + rd_data &= ~(HSPLL_PDIV_MASK << u2pll_ctrl[PLL_PDIV]); + rd_data |= (HSPLL_PDIV_VAL << u2pll_ctrl[PLL_PDIV]); + writel(rd_data, regs + offset[PLL_CTRL]); + + /* Set Core Ready high */ + bcm_usb_reg32_setbits(regs + offset[PHY_CTRL], + BIT(u2phy_ctrl[CORERDY])); + + /* Maximum timeout for Core Ready done */ + msleep(30); + + bcm_usb_reg32_setbits(regs + offset[PLL_CTRL], + BIT(u2pll_ctrl[PLL_RESETB])); + bcm_usb_reg32_setbits(regs + offset[PHY_CTRL], + BIT(u2phy_ctrl[PHY_RESETB])); + + + rd_data = readl(regs + offset[PHY_CTRL]); + rd_data &= ~(PHY_PCTL_MASK << u2phy_ctrl[PHY_PCTL]); + rd_data |= (HSPHY_PCTL_VAL << u2phy_ctrl[PHY_PCTL]); + writel(rd_data, regs + offset[PHY_CTRL]); + + /* Maximum timeout for PLL reset done */ + msleep(30); + + ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL], + BIT(u2pll_ctrl[PLL_LOCK])); + + return ret; +} + +static int bcm_usb_phy_reset(struct phy *phy) +{ + struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy); + void __iomem *regs = phy_cfg->regs; + const u8 *offset; + + offset = phy_cfg->offset; + + if (phy_cfg->type == USB_HS_PHY) { + bcm_usb_reg32_clrbits(regs + offset[PHY_CTRL], + BIT(u2phy_ctrl[CORERDY])); + bcm_usb_reg32_setbits(regs + offset[PHY_CTRL], + BIT(u2phy_ctrl[CORERDY])); + } + + return 0; +} + +static int bcm_usb_phy_init(struct phy *phy) +{ + struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy); + int ret = -EINVAL; + + if (phy_cfg->type == USB_SS_PHY) + ret = bcm_usb_ss_phy_init(phy_cfg); + else if (phy_cfg->type == USB_HS_PHY) + ret = bcm_usb_hs_phy_init(phy_cfg); + + return ret; +} + +static struct phy_ops sr_phy_ops = { + .init = bcm_usb_phy_init, + .reset = bcm_usb_phy_reset, + .owner = THIS_MODULE, +}; + +static struct phy *bcm_usb_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct bcm_usb_phy_cfg *phy_cfg; + int phy_idx; + + phy_cfg = dev_get_drvdata(dev); + if (!phy_cfg) + return ERR_PTR(-EINVAL); + + if (phy_cfg->version == BCM_SR_USB_COMBO_PHY) { + phy_idx = args->args[0]; + + if (WARN_ON(phy_idx > 1)) + return ERR_PTR(-ENODEV); + + return phy_cfg[phy_idx].phy; + } else + return phy_cfg->phy; +} + +static int bcm_usb_phy_create(struct device *dev, struct device_node *node, + void __iomem *regs, uint32_t version) +{ + struct bcm_usb_phy_cfg *phy_cfg; + int idx; + + if (version == BCM_SR_USB_COMBO_PHY) { + phy_cfg = devm_kzalloc(dev, NUM_BCM_SR_USB_COMBO_PHYS * + sizeof(struct bcm_usb_phy_cfg), + GFP_KERNEL); + if (!phy_cfg) + return -ENOMEM; + + for (idx = 0; idx < NUM_BCM_SR_USB_COMBO_PHYS; idx++) { + phy_cfg[idx].regs = regs; + phy_cfg[idx].version = version; + if (idx == 0) { + phy_cfg[idx].offset = bcm_usb_combo_phy_hs; + phy_cfg[idx].type = USB_HS_PHY; + } else { + phy_cfg[idx].offset = bcm_usb_combo_phy_ss; + phy_cfg[idx].type = USB_SS_PHY; + } + phy_cfg[idx].phy = devm_phy_create(dev, node, + &sr_phy_ops); + if (IS_ERR(phy_cfg[idx].phy)) + return PTR_ERR(phy_cfg[idx].phy); + + phy_set_drvdata(phy_cfg[idx].phy, &phy_cfg[idx]); + } + } else if (version == BCM_SR_USB_HS_PHY) { + phy_cfg = devm_kzalloc(dev, sizeof(struct bcm_usb_phy_cfg), + GFP_KERNEL); + if (!phy_cfg) + return -ENOMEM; + + phy_cfg->regs = regs; + phy_cfg->version = version; + phy_cfg->offset = bcm_usb_hs_phy; + phy_cfg->type = USB_HS_PHY; + phy_cfg->phy = devm_phy_create(dev, node, &sr_phy_ops); + if (IS_ERR(phy_cfg->phy)) + return PTR_ERR(phy_cfg->phy); + + phy_set_drvdata(phy_cfg->phy, phy_cfg); + } else + return -ENODEV; + + dev_set_drvdata(dev, phy_cfg); + + return 0; +} + +static const struct of_device_id bcm_usb_phy_of_match[] = { + { + .compatible = "brcm,sr-usb-combo-phy", + .data = (void *)BCM_SR_USB_COMBO_PHY, + }, + { + .compatible = "brcm,sr-usb-hs-phy", + .data = (void *)BCM_SR_USB_HS_PHY, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, bcm_usb_phy_of_match); + +static int bcm_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dn = dev->of_node; + const struct of_device_id *of_id; + struct resource *res; + void __iomem *regs; + int ret; + enum bcm_usb_phy_version version; + struct phy_provider *phy_provider; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + of_id = of_match_node(bcm_usb_phy_of_match, dn); + if (of_id) + version = (enum bcm_usb_phy_version)of_id->data; + else + return -ENODEV; + + ret = bcm_usb_phy_create(dev, dn, regs, version); + if (ret) + return ret; + + phy_provider = devm_of_phy_provider_register(dev, bcm_usb_phy_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver bcm_usb_phy_driver = { + .driver = { + .name = "phy-bcm-sr-usb", + .of_match_table = bcm_usb_phy_of_match, + }, + .probe = bcm_usb_phy_probe, +}; +module_platform_driver(bcm_usb_phy_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom stingray USB Phy driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From 16df8bcb672c45e69a7bf4b37bb6de12c705e195 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Mon, 25 Mar 2019 10:39:40 +0100 Subject: phy: amlogic: add Amlogic G12A USB2 PHY Driver This adds support for the USB2 PHY found in the Amlogic G12A SoC Family. It supports Host and/or Peripheral mode, depending on it's position. The first PHY is only used as Host, but the second supports Dual modes defined by the USB Control Glue HW in front of the USB Controllers. Signed-off-by: Neil Armstrong Reviewed-by: Martin Blumenstingl Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/amlogic/Kconfig | 11 + drivers/phy/amlogic/Makefile | 1 + drivers/phy/amlogic/phy-meson-g12a-usb2.c | 341 ++++++++++++++++++++++++++++++ 3 files changed, 353 insertions(+) create mode 100644 drivers/phy/amlogic/phy-meson-g12a-usb2.c (limited to 'drivers/phy') diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig index 23fe1cda2f70..560ff0f1ed4c 100644 --- a/drivers/phy/amlogic/Kconfig +++ b/drivers/phy/amlogic/Kconfig @@ -36,3 +36,14 @@ config PHY_MESON_GXL_USB3 Enable this to support the Meson USB3 PHY and OTG detection IP block found in Meson GXL and GXM SoCs. If unsure, say N. + +config PHY_MESON_G12A_USB2 + tristate "Meson G12A USB2 PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB2 PHYs found in Meson + G12A SoCs. + If unsure, say N. diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile index 4fd8848c194d..7d4d10f5a6b3 100644 --- a/drivers/phy/amlogic/Makefile +++ b/drivers/phy/amlogic/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o +obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o obj-$(CONFIG_PHY_MESON_GXL_USB3) += phy-meson-gxl-usb3.o diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb2.c b/drivers/phy/amlogic/phy-meson-g12a-usb2.c new file mode 100644 index 000000000000..9065ffc85eb4 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-g12a-usb2.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Meson G12A USB2 PHY driver + * + * Copyright (C) 2017 Martin Blumenstingl + * Copyright (C) 2017 Amlogic, Inc. All rights reserved + * Copyright (C) 2019 BayLibre, SAS + * Author: Neil Armstrong + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY_CTRL_R0 0x0 +#define PHY_CTRL_R1 0x4 +#define PHY_CTRL_R2 0x8 +#define PHY_CTRL_R3 0xc + #define PHY_CTRL_R3_SQUELCH_REF GENMASK(1, 0) + #define PHY_CTRL_R3_HSDIC_REF GENMASK(3, 2) + #define PHY_CTRL_R3_DISC_THRESH GENMASK(7, 4) + +#define PHY_CTRL_R4 0x10 + #define PHY_CTRL_R4_CALIB_CODE_7_0 GENMASK(7, 0) + #define PHY_CTRL_R4_CALIB_CODE_15_8 GENMASK(15, 8) + #define PHY_CTRL_R4_CALIB_CODE_23_16 GENMASK(23, 16) + #define PHY_CTRL_R4_I_C2L_CAL_EN BIT(24) + #define PHY_CTRL_R4_I_C2L_CAL_RESET_N BIT(25) + #define PHY_CTRL_R4_I_C2L_CAL_DONE BIT(26) + #define PHY_CTRL_R4_TEST_BYPASS_MODE_EN BIT(27) + #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0 GENMASK(29, 28) + #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2 GENMASK(31, 30) + +#define PHY_CTRL_R5 0x14 +#define PHY_CTRL_R6 0x18 +#define PHY_CTRL_R7 0x1c +#define PHY_CTRL_R8 0x20 +#define PHY_CTRL_R9 0x24 +#define PHY_CTRL_R10 0x28 +#define PHY_CTRL_R11 0x2c +#define PHY_CTRL_R12 0x30 +#define PHY_CTRL_R13 0x34 + #define PHY_CTRL_R13_CUSTOM_PATTERN_19 GENMASK(7, 0) + #define PHY_CTRL_R13_LOAD_STAT BIT(14) + #define PHY_CTRL_R13_UPDATE_PMA_SIGNALS BIT(15) + #define PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET GENMASK(20, 16) + #define PHY_CTRL_R13_CLEAR_HOLD_HS_DISCONNECT BIT(21) + #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_VAL BIT(22) + #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_EN BIT(23) + #define PHY_CTRL_R13_I_C2L_HS_EN BIT(24) + #define PHY_CTRL_R13_I_C2L_FS_EN BIT(25) + #define PHY_CTRL_R13_I_C2L_LS_EN BIT(26) + #define PHY_CTRL_R13_I_C2L_HS_OE BIT(27) + #define PHY_CTRL_R13_I_C2L_FS_OE BIT(28) + #define PHY_CTRL_R13_I_C2L_HS_RX_EN BIT(29) + #define PHY_CTRL_R13_I_C2L_FSLS_RX_EN BIT(30) + +#define PHY_CTRL_R14 0x38 + #define PHY_CTRL_R14_I_RDP_EN BIT(0) + #define PHY_CTRL_R14_I_RPU_SW1_EN BIT(1) + #define PHY_CTRL_R14_I_RPU_SW2_EN GENMASK(2, 3) + #define PHY_CTRL_R14_PG_RSTN BIT(4) + #define PHY_CTRL_R14_I_C2L_DATA_16_8 BIT(5) + #define PHY_CTRL_R14_I_C2L_ASSERT_SINGLE_EN_ZERO BIT(6) + #define PHY_CTRL_R14_BYPASS_CTRL_7_0 GENMASK(15, 8) + #define PHY_CTRL_R14_BYPASS_CTRL_15_8 GENMASK(23, 16) + +#define PHY_CTRL_R15 0x3c +#define PHY_CTRL_R16 0x40 + #define PHY_CTRL_R16_MPLL_M GENMASK(8, 0) + #define PHY_CTRL_R16_MPLL_N GENMASK(14, 10) + #define PHY_CTRL_R16_MPLL_TDC_MODE BIT(20) + #define PHY_CTRL_R16_MPLL_SDM_EN BIT(21) + #define PHY_CTRL_R16_MPLL_LOAD BIT(22) + #define PHY_CTRL_R16_MPLL_DCO_SDM_EN BIT(23) + #define PHY_CTRL_R16_MPLL_LOCK_LONG GENMASK(25, 24) + #define PHY_CTRL_R16_MPLL_LOCK_F BIT(26) + #define PHY_CTRL_R16_MPLL_FAST_LOCK BIT(27) + #define PHY_CTRL_R16_MPLL_EN BIT(28) + #define PHY_CTRL_R16_MPLL_RESET BIT(29) + #define PHY_CTRL_R16_MPLL_LOCK BIT(30) + #define PHY_CTRL_R16_MPLL_LOCK_DIG BIT(31) + +#define PHY_CTRL_R17 0x44 + #define PHY_CTRL_R17_MPLL_FRAC_IN GENMASK(13, 0) + #define PHY_CTRL_R17_MPLL_FIX_EN BIT(16) + #define PHY_CTRL_R17_MPLL_LAMBDA1 GENMASK(19, 17) + #define PHY_CTRL_R17_MPLL_LAMBDA0 GENMASK(22, 20) + #define PHY_CTRL_R17_MPLL_FILTER_MODE BIT(23) + #define PHY_CTRL_R17_MPLL_FILTER_PVT2 GENMASK(27, 24) + #define PHY_CTRL_R17_MPLL_FILTER_PVT1 GENMASK(31, 28) + +#define PHY_CTRL_R18 0x48 + #define PHY_CTRL_R18_MPLL_LKW_SEL GENMASK(1, 0) + #define PHY_CTRL_R18_MPLL_LK_W GENMASK(5, 2) + #define PHY_CTRL_R18_MPLL_LK_S GENMASK(11, 6) + #define PHY_CTRL_R18_MPLL_DCO_M_EN BIT(12) + #define PHY_CTRL_R18_MPLL_DCO_CLK_SEL BIT(13) + #define PHY_CTRL_R18_MPLL_PFD_GAIN GENMASK(15, 14) + #define PHY_CTRL_R18_MPLL_ROU GENMASK(18, 16) + #define PHY_CTRL_R18_MPLL_DATA_SEL GENMASK(21, 19) + #define PHY_CTRL_R18_MPLL_BIAS_ADJ GENMASK(23, 22) + #define PHY_CTRL_R18_MPLL_BB_MODE GENMASK(25, 24) + #define PHY_CTRL_R18_MPLL_ALPHA GENMASK(28, 26) + #define PHY_CTRL_R18_MPLL_ADJ_LDO GENMASK(30, 29) + #define PHY_CTRL_R18_MPLL_ACG_RANGE BIT(31) + +#define PHY_CTRL_R19 0x4c +#define PHY_CTRL_R20 0x50 + #define PHY_CTRL_R20_USB2_IDDET_EN BIT(0) + #define PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0 GENMASK(3, 1) + #define PHY_CTRL_R20_USB2_OTG_VBUSDET_EN BIT(4) + #define PHY_CTRL_R20_USB2_AMON_EN BIT(5) + #define PHY_CTRL_R20_USB2_CAL_CODE_R5 BIT(6) + #define PHY_CTRL_R20_BYPASS_OTG_DET BIT(7) + #define PHY_CTRL_R20_USB2_DMON_EN BIT(8) + #define PHY_CTRL_R20_USB2_DMON_SEL_3_0 GENMASK(12, 9) + #define PHY_CTRL_R20_USB2_EDGE_DRV_EN BIT(13) + #define PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0 GENMASK(15, 14) + #define PHY_CTRL_R20_USB2_BGR_ADJ_4_0 GENMASK(20, 16) + #define PHY_CTRL_R20_USB2_BGR_START BIT(21) + #define PHY_CTRL_R20_USB2_BGR_VREF_4_0 GENMASK(28, 24) + #define PHY_CTRL_R20_USB2_BGR_DBG_1_0 GENMASK(30, 29) + #define PHY_CTRL_R20_BYPASS_CAL_DONE_R5 BIT(31) + +#define PHY_CTRL_R21 0x54 + #define PHY_CTRL_R21_USB2_BGR_FORCE BIT(0) + #define PHY_CTRL_R21_USB2_CAL_ACK_EN BIT(1) + #define PHY_CTRL_R21_USB2_OTG_ACA_EN BIT(2) + #define PHY_CTRL_R21_USB2_TX_STRG_PD BIT(3) + #define PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0 GENMASK(5, 4) + #define PHY_CTRL_R21_BYPASS_UTMI_CNTR GENMASK(15, 6) + #define PHY_CTRL_R21_BYPASS_UTMI_REG GENMASK(25, 20) + +#define PHY_CTRL_R22 0x58 +#define PHY_CTRL_R23 0x5c + +#define RESET_COMPLETE_TIME 1000 +#define PLL_RESET_COMPLETE_TIME 100 + +struct phy_meson_g12a_usb2_priv { + struct device *dev; + struct regmap *regmap; + struct clk *clk; + struct reset_control *reset; +}; + +static const struct regmap_config phy_meson_g12a_usb2_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = PHY_CTRL_R23, +}; + +static int phy_meson_g12a_usb2_init(struct phy *phy) +{ + struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + udelay(RESET_COMPLETE_TIME); + + /* usb2_otg_aca_en == 0 */ + regmap_update_bits(priv->regmap, PHY_CTRL_R21, + PHY_CTRL_R21_USB2_OTG_ACA_EN, 0); + + /* PLL Setup : 24MHz * 20 / 1 = 480MHz */ + regmap_write(priv->regmap, PHY_CTRL_R16, + FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) | + FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) | + PHY_CTRL_R16_MPLL_LOAD | + FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) | + PHY_CTRL_R16_MPLL_FAST_LOCK | + PHY_CTRL_R16_MPLL_EN | + PHY_CTRL_R16_MPLL_RESET); + + regmap_write(priv->regmap, PHY_CTRL_R17, + FIELD_PREP(PHY_CTRL_R17_MPLL_FRAC_IN, 0) | + FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA1, 7) | + FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA0, 7) | + FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT2, 2) | + FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT1, 9)); + + regmap_write(priv->regmap, PHY_CTRL_R18, + FIELD_PREP(PHY_CTRL_R18_MPLL_LKW_SEL, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_LK_W, 9) | + FIELD_PREP(PHY_CTRL_R18_MPLL_LK_S, 0x27) | + FIELD_PREP(PHY_CTRL_R18_MPLL_PFD_GAIN, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ROU, 7) | + FIELD_PREP(PHY_CTRL_R18_MPLL_DATA_SEL, 3) | + FIELD_PREP(PHY_CTRL_R18_MPLL_BIAS_ADJ, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_BB_MODE, 0) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ALPHA, 3) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ADJ_LDO, 1) | + PHY_CTRL_R18_MPLL_ACG_RANGE); + + udelay(PLL_RESET_COMPLETE_TIME); + + /* UnReset PLL */ + regmap_write(priv->regmap, PHY_CTRL_R16, + FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) | + FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) | + PHY_CTRL_R16_MPLL_LOAD | + FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) | + PHY_CTRL_R16_MPLL_FAST_LOCK | + PHY_CTRL_R16_MPLL_EN); + + /* PHY Tuning */ + regmap_write(priv->regmap, PHY_CTRL_R20, + FIELD_PREP(PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0, 4) | + PHY_CTRL_R20_USB2_OTG_VBUSDET_EN | + FIELD_PREP(PHY_CTRL_R20_USB2_DMON_SEL_3_0, 15) | + PHY_CTRL_R20_USB2_EDGE_DRV_EN | + FIELD_PREP(PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0, 3) | + FIELD_PREP(PHY_CTRL_R20_USB2_BGR_ADJ_4_0, 0) | + FIELD_PREP(PHY_CTRL_R20_USB2_BGR_VREF_4_0, 0) | + FIELD_PREP(PHY_CTRL_R20_USB2_BGR_DBG_1_0, 0)); + + regmap_write(priv->regmap, PHY_CTRL_R4, + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_7_0, 0xf) | + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_15_8, 0xf) | + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_23_16, 0xf) | + PHY_CTRL_R4_TEST_BYPASS_MODE_EN | + FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0, 0) | + FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2, 0)); + + /* Tuning Disconnect Threshold */ + regmap_write(priv->regmap, PHY_CTRL_R3, + FIELD_PREP(PHY_CTRL_R3_SQUELCH_REF, 0) | + FIELD_PREP(PHY_CTRL_R3_HSDIC_REF, 1) | + FIELD_PREP(PHY_CTRL_R3_DISC_THRESH, 3)); + + /* Analog Settings */ + regmap_write(priv->regmap, PHY_CTRL_R14, 0); + regmap_write(priv->regmap, PHY_CTRL_R13, + PHY_CTRL_R13_UPDATE_PMA_SIGNALS | + FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7)); + + return 0; +} + +static int phy_meson_g12a_usb2_exit(struct phy *phy) +{ + struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy); + + return reset_control_reset(priv->reset); +} + +/* set_mode is not needed, mode setting is handled via the UTMI bus */ +static const struct phy_ops phy_meson_g12a_usb2_ops = { + .init = phy_meson_g12a_usb2_init, + .exit = phy_meson_g12a_usb2_exit, + .owner = THIS_MODULE, +}; + +static int phy_meson_g12a_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct resource *res; + struct phy_meson_g12a_usb2_priv *priv; + struct phy *phy; + void __iomem *base; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + platform_set_drvdata(pdev, priv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_meson_g12a_usb2_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clk = devm_clk_get(dev, "xtal"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = devm_reset_control_get(dev, "phy"); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + ret = reset_control_deassert(priv->reset); + if (ret) + return ret; + + phy = devm_phy_create(dev, NULL, &phy_meson_g12a_usb2_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + + return ret; + } + + phy_set_bus_width(phy, 8); + 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 phy_meson_g12a_usb2_of_match[] = { + { .compatible = "amlogic,g12a-usb2-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_meson_g12a_usb2_of_match); + +static struct platform_driver phy_meson_g12a_usb2_driver = { + .probe = phy_meson_g12a_usb2_probe, + .driver = { + .name = "phy-meson-g12a-usb2", + .of_match_table = phy_meson_g12a_usb2_of_match, + }, +}; +module_platform_driver(phy_meson_g12a_usb2_driver); + +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_AUTHOR("Neil Armstrong "); +MODULE_DESCRIPTION("Meson G12A USB2 PHY driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From 36077e16c050d1b063cdfec8c1d38d51d112f86d Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Mon, 25 Mar 2019 10:39:41 +0100 Subject: phy: amlogic: Add Amlogic G12A USB3 + PCIE Combo PHY Driver This adds support for the shared USB3 + PCIE PHY found in the Amlogic G12A SoC Family. It supports USB3 Host mode or PCIE 2.0 mode, depending on the layout of the board. Selection is done by the #phy-cells, making the mode static and exclusive. Signed-off-by: Neil Armstrong Reviewed-by: Martin Blumenstingl Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/amlogic/Kconfig | 11 + drivers/phy/amlogic/Makefile | 1 + drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c | 413 +++++++++++++++++++++++++ 3 files changed, 425 insertions(+) create mode 100644 drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c (limited to 'drivers/phy') diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig index 560ff0f1ed4c..4c08c1ccdd04 100644 --- a/drivers/phy/amlogic/Kconfig +++ b/drivers/phy/amlogic/Kconfig @@ -47,3 +47,14 @@ config PHY_MESON_G12A_USB2 Enable this to support the Meson USB2 PHYs found in Meson G12A SoCs. If unsure, say N. + +config PHY_MESON_G12A_USB3_PCIE + tristate "Meson G12A USB3+PCIE Combo PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB3 + PCIE Combo PHY found + in Meson G12A SoCs. + If unsure, say N. diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile index 7d4d10f5a6b3..fdd008e1b19b 100644 --- a/drivers/phy/amlogic/Makefile +++ b/drivers/phy/amlogic/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o obj-$(CONFIG_PHY_MESON_GXL_USB3) += phy-meson-gxl-usb3.o +obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c new file mode 100644 index 000000000000..6233a7979a93 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Amlogic G12A USB3 + PCIE Combo PHY driver + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved + * Copyright (C) 2019 BayLibre, SAS + * Author: Neil Armstrong + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY_R0 0x00 + #define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0) + #define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5) + +#define PHY_R1 0x04 + #define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0) + #define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5) + #define PHY_R1_PHY_RX1_EQ GENMASK(12, 10) + #define PHY_R1_PHY_RX0_EQ GENMASK(15, 13) + #define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16) + #define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21) + #define PHY_R1_PHY_REF_CLKDIV2 BIT(24) + #define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25) + +#define PHY_R2 0x08 + #define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0) + #define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6) + #define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12) + #define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18) + +#define PHY_R4 0x10 + #define PHY_R4_PHY_CR_WRITE BIT(0) + #define PHY_R4_PHY_CR_READ BIT(1) + #define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2) + #define PHY_R4_PHY_CR_CAP_DATA BIT(18) + #define PHY_R4_PHY_CR_CAP_ADDR BIT(19) + +#define PHY_R5 0x14 + #define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0) + #define PHY_R5_PHY_CR_ACK BIT(16) + #define PHY_R5_PHY_BS_OUT BIT(17) + +struct phy_g12a_usb3_pcie_priv { + struct regmap *regmap; + struct regmap *regmap_cr; + struct clk *clk_ref; + struct reset_control *reset; + struct phy *phy; + unsigned int mode; +}; + +static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = PHY_R5, +}; + +static int phy_g12a_usb3_pcie_cr_bus_addr(struct phy_g12a_usb3_pcie_priv *priv, + unsigned int addr) +{ + unsigned int val, reg; + int ret; + + reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, addr); + + regmap_write(priv->regmap, PHY_R4, reg); + regmap_write(priv->regmap, PHY_R4, reg); + + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_ADDR); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + !(val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + return 0; +} + +static int phy_g12a_usb3_pcie_cr_bus_read(void *context, unsigned int addr, + unsigned int *data) +{ + struct phy_g12a_usb3_pcie_priv *priv = context; + unsigned int val; + int ret; + + ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, 0); + regmap_write(priv->regmap, PHY_R4, PHY_R4_PHY_CR_READ); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + *data = FIELD_GET(PHY_R5_PHY_CR_DATA_OUT, val); + + regmap_write(priv->regmap, PHY_R4, 0); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + !(val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + return 0; +} + +static int phy_g12a_usb3_pcie_cr_bus_write(void *context, unsigned int addr, + unsigned int data) +{ + struct phy_g12a_usb3_pcie_priv *priv = context; + unsigned int val, reg; + int ret; + + ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr); + if (ret) + return ret; + + reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, data); + + regmap_write(priv->regmap, PHY_R4, reg); + regmap_write(priv->regmap, PHY_R4, reg); + + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_DATA); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK) == 0, + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_WRITE); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK) == 0, + 5, 1000); + if (ret) + return ret; + + return 0; +} + +static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = phy_g12a_usb3_pcie_cr_bus_read, + .reg_write = phy_g12a_usb3_pcie_cr_bus_write, + .max_register = 0xffff, + .fast_io = true, +}; + +static int phy_g12a_usb3_init(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + int data, ret; + + /* Switch PHY to USB3 */ + /* TODO figure out how to handle when PCIe was set in the bootloader */ + regmap_update_bits(priv->regmap, PHY_R0, + PHY_R0_PCIE_USB3_SWITCH, + PHY_R0_PCIE_USB3_SWITCH); + + /* + * WORKAROUND: There is SSPHY suspend bug due to + * which USB enumerates + * in HS mode instead of SS mode. Workaround it by asserting + * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus + * mode + */ + ret = regmap_update_bits(priv->regmap_cr, 0x102d, BIT(7), BIT(7)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap_cr, 0x1010, 0xff0, 20); + if (ret) + return ret; + + /* + * Fix RX Equalization setting as follows + * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0 + * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1 + * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3 + * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1 + */ + ret = regmap_read(priv->regmap_cr, 0x1006, &data); + if (ret) + return ret; + + data &= ~BIT(6); + data |= BIT(7); + data &= ~(0x7 << 8); + data |= (0x3 << 8); + data |= (1 << 11); + ret = regmap_write(priv->regmap_cr, 0x1006, data); + if (ret) + return ret; + + /* + * Set EQ and TX launch amplitudes as follows + * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22 + * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127 + * LANE0.TX_OVRD_DRV_LO.EN set to 1. + */ + ret = regmap_read(priv->regmap_cr, 0x1002, &data); + if (ret) + return ret; + + data &= ~0x3f80; + data |= (0x16 << 7); + data &= ~0x7f; + data |= (0x7f | BIT(14)); + ret = regmap_write(priv->regmap_cr, 0x1002, data); + if (ret) + return ret; + + /* MPLL_LOOP_CTL.PROP_CNTRL = 8 */ + ret = regmap_update_bits(priv->regmap_cr, 0x30, 0xf << 4, 8 << 4); + if (ret) + return ret; + + regmap_update_bits(priv->regmap, PHY_R2, + PHY_R2_PHY_TX_VBOOST_LVL, + FIELD_PREP(PHY_R2_PHY_TX_VBOOST_LVL, 0x4)); + + regmap_update_bits(priv->regmap, PHY_R1, + PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL, + FIELD_PREP(PHY_R1_PHY_LOS_BIAS, 4) | + FIELD_PREP(PHY_R1_PHY_LOS_LEVEL, 9)); + + return 0; +} + +static int phy_g12a_usb3_pcie_init(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + if (priv->mode == PHY_TYPE_USB3) + return phy_g12a_usb3_init(phy); + + /* Power UP PCIE */ + /* TODO figure out when the bootloader has set USB3 mode before */ + regmap_update_bits(priv->regmap, PHY_R0, + PHY_R0_PCIE_POWER_STATE, + FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1c)); + + return 0; +} + +static int phy_g12a_usb3_pcie_exit(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + + return reset_control_reset(priv->reset); +} + +static struct phy *phy_g12a_usb3_pcie_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct phy_g12a_usb3_pcie_priv *priv = dev_get_drvdata(dev); + unsigned int mode; + + if (args->args_count < 1) { + dev_err(dev, "invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + mode = args->args[0]; + + if (mode != PHY_TYPE_USB3 && mode != PHY_TYPE_PCIE) { + dev_err(dev, "invalid phy mode select argument\n"); + return ERR_PTR(-EINVAL); + } + + priv->mode = mode; + + return priv->phy; +} + +static const struct phy_ops phy_g12a_usb3_pcie_ops = { + .init = phy_g12a_usb3_pcie_init, + .exit = phy_g12a_usb3_pcie_exit, + .owner = THIS_MODULE, +}; + +static int phy_g12a_usb3_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_g12a_usb3_pcie_priv *priv; + struct resource *res; + struct phy_provider *phy_provider; + void __iomem *base; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_g12a_usb3_pcie_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->regmap_cr = devm_regmap_init(dev, NULL, priv, + &phy_g12a_usb3_pcie_cr_regmap_conf); + if (IS_ERR(priv->regmap_cr)) + return PTR_ERR(priv->regmap_cr); + + priv->clk_ref = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(priv->clk_ref)) + return PTR_ERR(priv->clk_ref); + + ret = clk_prepare_enable(priv->clk_ref); + if (ret) + goto err_disable_clk_ref; + + priv->reset = devm_reset_control_array_get(dev, false, false); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->phy = devm_phy_create(dev, np, &phy_g12a_usb3_pcie_ops); + if (IS_ERR(priv->phy)) { + ret = PTR_ERR(priv->phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + + return ret; + } + + phy_set_drvdata(priv->phy, priv); + dev_set_drvdata(dev, priv); + + phy_provider = devm_of_phy_provider_register(dev, + phy_g12a_usb3_pcie_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); + +err_disable_clk_ref: + clk_disable_unprepare(priv->clk_ref); + + return ret; +} + +static const struct of_device_id phy_g12a_usb3_pcie_of_match[] = { + { .compatible = "amlogic,g12a-usb3-pcie-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_g12a_usb3_pcie_of_match); + +static struct platform_driver phy_g12a_usb3_pcie_driver = { + .probe = phy_g12a_usb3_pcie_probe, + .driver = { + .name = "phy-g12a-usb3-pcie", + .of_match_table = phy_g12a_usb3_pcie_of_match, + }, +}; +module_platform_driver(phy_g12a_usb3_pcie_driver); + +MODULE_AUTHOR("Neil Armstrong "); +MODULE_DESCRIPTION("Amlogic G12A USB3 + PCIE Combo PHY driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From c9b589791fc1b37fdca35b621dae62e98e4c95fc Mon Sep 17 00:00:00 2001 From: Evan Green Date: Thu, 21 Mar 2019 10:17:59 -0700 Subject: phy: qcom: Utilize UFS reset controller Move the PHY reset from ufs-qcom into the respective PHYs. This will allow us to merge the two phases of UFS PHY initialization. Signed-off-by: Evan Green Reviewed-by: Stephen Boyd Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 34 ++++++++++++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-ufs-i.h | 3 +++ drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c | 8 +++++++ drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c | 8 +++++++ drivers/phy/qualcomm/phy-qcom-ufs.c | 23 +++++++++++++++++++ drivers/scsi/ufs/ufs-qcom.c | 18 --------------- 6 files changed, 76 insertions(+), 18 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 08d6f6f7f039..a808887ab4e2 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -897,6 +897,7 @@ struct qmp_phy { * @init_count: phy common block initialization count * @phy_initialized: indicate if PHY has been initialized * @mode: current PHY mode + * @ufs_reset: optional UFS PHY reset handle */ struct qcom_qmp { struct device *dev; @@ -914,6 +915,8 @@ struct qcom_qmp { int init_count; bool phy_initialized; enum phy_mode mode; + + struct reset_control *ufs_reset; }; static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) @@ -1314,6 +1317,7 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp) return 0; } + reset_control_assert(qmp->ufs_reset); if (cfg->has_phy_com_ctrl) { qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL], SERDES_START | PCS_START); @@ -1351,6 +1355,33 @@ static int qcom_qmp_phy_init(struct phy *phy) dev_vdbg(qmp->dev, "Initializing QMP phy\n"); + if (cfg->no_pcs_sw_reset) { + /* + * Get UFS reset, which is delayed until now to avoid a + * circular dependency where UFS needs its PHY, but the PHY + * needs this UFS reset. + */ + if (!qmp->ufs_reset) { + qmp->ufs_reset = + devm_reset_control_get_exclusive(qmp->dev, + "ufsphy"); + + if (IS_ERR(qmp->ufs_reset)) { + ret = PTR_ERR(qmp->ufs_reset); + dev_err(qmp->dev, + "failed to get UFS reset: %d\n", + ret); + + qmp->ufs_reset = NULL; + return ret; + } + } + + ret = reset_control_assert(qmp->ufs_reset); + if (ret) + goto err_lane_rst; + } + ret = qcom_qmp_phy_com_init(qphy); if (ret) return ret; @@ -1383,6 +1414,9 @@ static int qcom_qmp_phy_init(struct phy *phy) cfg->rx_tbl, cfg->rx_tbl_num); qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num); + ret = reset_control_deassert(qmp->ufs_reset); + if (ret) + goto err_lane_rst; /* * UFS PHY requires the deassert of software reset before serdes start. diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h index f798fb64de94..ba77348d807c 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-i.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -101,6 +102,7 @@ struct ufs_qcom_phy { struct ufs_qcom_phy_specific_ops *phy_spec_ops; enum phy_mode mode; + struct reset_control *ufs_reset; }; /** @@ -132,6 +134,7 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev, struct ufs_qcom_phy *common_cfg, const struct phy_ops *ufs_qcom_phy_gen_ops, struct ufs_qcom_phy_specific_ops *phy_spec_ops); +int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common); int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A, struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B, diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c index 1e0d4f2046a4..cc343517a2ca 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c @@ -48,6 +48,14 @@ static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy) bool is_rate_B = false; int ret; + ret = ufs_qcom_phy_get_reset(phy_common); + if (ret) + return ret; + + ret = reset_control_assert(phy_common->ufs_reset); + if (ret) + return ret; + if (phy_common->mode == PHY_MODE_UFS_HS_B) is_rate_B = true; diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c index aef40f7a41d4..54b2af9d8702 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c @@ -67,6 +67,14 @@ static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy) bool is_rate_B = false; int ret; + ret = ufs_qcom_phy_get_reset(phy_common); + if (ret) + return ret; + + ret = reset_control_assert(phy_common->ufs_reset); + if (ret) + return ret; + if (phy_common->mode == PHY_MODE_UFS_HS_B) is_rate_B = true; diff --git a/drivers/phy/qualcomm/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c index f2979ccad00a..fe59785a55f2 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs.c @@ -147,6 +147,22 @@ out: } EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe); +int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common) +{ + struct reset_control *reset; + + if (phy_common->ufs_reset) + return 0; + + reset = devm_reset_control_get_exclusive_by_index(phy_common->dev, 0); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + phy_common->ufs_reset = reset; + return 0; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_get_reset); + static int __ufs_qcom_phy_clk_get(struct device *dev, const char *name, struct clk **clk_out, bool err_print) { @@ -533,6 +549,12 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy) if (phy_common->is_powered_on) return 0; + err = reset_control_deassert(phy_common->ufs_reset); + if (err) { + dev_err(dev, "Failed to assert UFS PHY reset"); + return err; + } + if (!phy_common->is_started) { err = ufs_qcom_phy_start_serdes(phy_common); if (err) @@ -620,6 +642,7 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy) ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll); ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy); + reset_control_assert(phy_common->ufs_reset); phy_common->is_powered_on = false; return 0; diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index ab05ef5cfdcd..1c25b1c82314 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -261,11 +261,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) if (is_rate_B) phy_set_mode(phy, PHY_MODE_UFS_HS_B); - /* Assert PHY reset and apply PHY calibration values */ - ufs_qcom_assert_reset(hba); - /* provide 1ms delay to let the reset pulse propagate */ - usleep_range(1000, 1100); - /* phy initialization - calibrate the phy */ ret = phy_init(phy); if (ret) { @@ -274,15 +269,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) goto out; } - /* De-assert PHY reset and start serdes */ - ufs_qcom_deassert_reset(hba); - - /* - * after reset deassertion, phy will need all ref clocks, - * voltage, current to settle down before starting serdes. - */ - usleep_range(1000, 1100); - /* power on phy - start serdes and phy's power and clocks */ ret = phy_power_on(phy); if (ret) { @@ -296,7 +282,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) return 0; out_disable_phy: - ufs_qcom_assert_reset(hba); phy_exit(phy); out: return ret; @@ -559,9 +544,6 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) */ ufs_qcom_disable_lane_clks(host); phy_power_off(phy); - - /* Assert PHY soft reset */ - ufs_qcom_assert_reset(hba); goto out; } -- cgit v1.2.3-55-g7522 From 3f6d1767b1a011165cb404df1acdf003252df153 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Thu, 21 Mar 2019 10:18:00 -0700 Subject: phy: ufs-qcom: Refactor all init steps into phy_poweron The phy code was using implicit sequencing between the PHY driver and the UFS driver to implement certain hardware requirements. Specifically, the PHY reset register in the UFS controller needs to be deasserted before serdes start occurs in the PHY. Before this change, the code was doing this by utilizing the two phy callbacks, phy_init() and phy_poweron(), as "init step 1" and "init step 2", where the UFS driver would deassert reset between these two steps. This makes it challenging to power off the regulators in suspend, as regulators are initialized in init, not in poweron(), but only poweroff() is called during suspend, not exit(). For UFS, move the actual firing up of the PHY to phy_poweron() and phy_poweroff() callbacks, rather than init()/exit(). UFS calls phy_poweroff() during suspend, so now all clocks and regulators for the phy can be powered down during suspend. QMP is a little tricky because the PHY is also shared with PCIe and USB3, which have their own definitions for init() and poweron(). Rename the meaty functions to _enable() and _disable() to disentangle from the PHY core names, and then create two different ops structures: one for UFS and one for the other PHY types. In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards, as the generic PHY code does the reference counting. The 14/20nm-specific init functions get collapsed into the generic power_on() function, with the addition of a calibrate() callback specific to 14/20nm. Signed-off-by: Evan Green Reviewed-by: Stephen Boyd Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 78 ++++++++-------------------- drivers/phy/qualcomm/phy-qcom-ufs-i.h | 4 +- drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c | 33 +----------- drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c | 33 +----------- drivers/phy/qualcomm/phy-qcom-ufs.c | 42 ++++++++------- drivers/scsi/ufs/ufs-qcom.c | 44 +++++++--------- 6 files changed, 64 insertions(+), 170 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index a808887ab4e2..3b01ebf76f66 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -1339,8 +1339,7 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp) return 0; } -/* PHY Initialization */ -static int qcom_qmp_phy_init(struct phy *phy) +static int qcom_qmp_phy_enable(struct phy *phy) { struct qmp_phy *qphy = phy_get_drvdata(phy); struct qcom_qmp *qmp = qphy->qmp; @@ -1418,14 +1417,6 @@ static int qcom_qmp_phy_init(struct phy *phy) if (ret) goto err_lane_rst; - /* - * UFS PHY requires the deassert of software reset before serdes start. - * For UFS PHYs that do not have software reset control bits, defer - * starting serdes until the power on callback. - */ - if ((cfg->type == PHY_TYPE_UFS) && cfg->no_pcs_sw_reset) - goto out; - /* * Pull out PHY from POWER DOWN state. * This is active low enable signal to power-down PHY. @@ -1437,7 +1428,9 @@ static int qcom_qmp_phy_init(struct phy *phy) usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max); /* Pull PHY out of reset state */ - qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + if (!cfg->no_pcs_sw_reset) + qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + if (cfg->has_phy_dp_com_ctrl) qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET); @@ -1454,11 +1447,10 @@ static int qcom_qmp_phy_init(struct phy *phy) goto err_pcs_ready; } qmp->phy_initialized = true; - -out: - return ret; + return 0; err_pcs_ready: + reset_control_assert(qmp->ufs_reset); clk_disable_unprepare(qphy->pipe_clk); err_clk_enable: if (cfg->has_lane_rst) @@ -1469,7 +1461,7 @@ err_lane_rst: return ret; } -static int qcom_qmp_phy_exit(struct phy *phy) +static int qcom_qmp_phy_disable(struct phy *phy) { struct qmp_phy *qphy = phy_get_drvdata(phy); struct qcom_qmp *qmp = qphy->qmp; @@ -1497,44 +1489,6 @@ static int qcom_qmp_phy_exit(struct phy *phy) return 0; } -static int qcom_qmp_phy_poweron(struct phy *phy) -{ - struct qmp_phy *qphy = phy_get_drvdata(phy); - struct qcom_qmp *qmp = qphy->qmp; - const struct qmp_phy_cfg *cfg = qmp->cfg; - void __iomem *pcs = qphy->pcs; - void __iomem *status; - unsigned int mask, val; - int ret = 0; - - if (cfg->type != PHY_TYPE_UFS) - return 0; - - /* - * For UFS PHY that has not software reset control, serdes start - * should only happen when UFS driver explicitly calls phy_power_on - * after it deasserts software reset. - */ - if (cfg->no_pcs_sw_reset && !qmp->phy_initialized && - (qmp->init_count != 0)) { - /* start SerDes and Phy-Coding-Sublayer */ - qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); - - status = pcs + cfg->regs[QPHY_PCS_READY_STATUS]; - mask = cfg->mask_pcs_ready; - - ret = readl_poll_timeout(status, val, !(val & mask), 1, - PHY_INIT_COMPLETE_TIMEOUT); - if (ret) { - dev_err(qmp->dev, "phy initialization timed-out\n"); - return ret; - } - qmp->phy_initialized = true; - } - - return ret; -} - static int qcom_qmp_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) { @@ -1784,9 +1738,15 @@ static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np) } static const struct phy_ops qcom_qmp_phy_gen_ops = { - .init = qcom_qmp_phy_init, - .exit = qcom_qmp_phy_exit, - .power_on = qcom_qmp_phy_poweron, + .init = qcom_qmp_phy_enable, + .exit = qcom_qmp_phy_disable, + .set_mode = qcom_qmp_phy_set_mode, + .owner = THIS_MODULE, +}; + +static const struct phy_ops qcom_qmp_ufs_ops = { + .power_on = qcom_qmp_phy_enable, + .power_off = qcom_qmp_phy_disable, .set_mode = qcom_qmp_phy_set_mode, .owner = THIS_MODULE, }; @@ -1797,6 +1757,7 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) struct qcom_qmp *qmp = dev_get_drvdata(dev); struct phy *generic_phy; struct qmp_phy *qphy; + const struct phy_ops *ops = &qcom_qmp_phy_gen_ops; char prop_name[MAX_PROP_NAME]; int ret; @@ -1883,7 +1844,10 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) } } - generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops); + if (qmp->cfg->type == PHY_TYPE_UFS) + ops = &qcom_qmp_ufs_ops; + + generic_phy = devm_phy_create(dev, np, ops); if (IS_ERR(generic_phy)) { ret = PTR_ERR(generic_phy); dev_err(dev, "failed to create qphy %d\n", ret); diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h index ba77348d807c..109ddd67be82 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-i.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h @@ -97,8 +97,6 @@ struct ufs_qcom_phy { char name[UFS_QCOM_PHY_NAME_LEN]; struct ufs_qcom_phy_calibration *cached_regs; int cached_regs_table_size; - bool is_powered_on; - bool is_started; struct ufs_qcom_phy_specific_ops *phy_spec_ops; enum phy_mode mode; @@ -117,6 +115,7 @@ struct ufs_qcom_phy { * and writes to QSERDES_RX_SIGDET_CNTRL attribute */ struct ufs_qcom_phy_specific_ops { + int (*calibrate)(struct ufs_qcom_phy *ufs_qcom_phy, bool is_rate_B); void (*start_serdes)(struct ufs_qcom_phy *phy); int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy); void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val); @@ -134,7 +133,6 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev, struct ufs_qcom_phy *common_cfg, const struct phy_ops *ufs_qcom_phy_gen_ops, struct ufs_qcom_phy_specific_ops *phy_spec_ops); -int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common); int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A, struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B, diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c index cc343517a2ca..4815546f228c 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c @@ -42,36 +42,6 @@ void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common) UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; } -static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy) -{ - struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); - bool is_rate_B = false; - int ret; - - ret = ufs_qcom_phy_get_reset(phy_common); - if (ret) - return ret; - - ret = reset_control_assert(phy_common->ufs_reset); - if (ret) - return ret; - - if (phy_common->mode == PHY_MODE_UFS_HS_B) - is_rate_B = true; - - ret = ufs_qcom_phy_qmp_14nm_phy_calibrate(phy_common, is_rate_B); - if (!ret) - /* phy calibrated, but yet to be started */ - phy_common->is_started = false; - - return ret; -} - -static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy) -{ - return 0; -} - static int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy, enum phy_mode mode, int submode) @@ -132,8 +102,6 @@ static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) } static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = { - .init = ufs_qcom_phy_qmp_14nm_init, - .exit = ufs_qcom_phy_qmp_14nm_exit, .power_on = ufs_qcom_phy_power_on, .power_off = ufs_qcom_phy_power_off, .set_mode = ufs_qcom_phy_qmp_14nm_set_mode, @@ -141,6 +109,7 @@ static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = { }; static struct ufs_qcom_phy_specific_ops phy_14nm_ops = { + .calibrate = ufs_qcom_phy_qmp_14nm_phy_calibrate, .start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes, .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready, .set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable, diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c index 54b2af9d8702..f1cf819e12ea 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c @@ -61,36 +61,6 @@ void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common) UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; } -static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy) -{ - struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); - bool is_rate_B = false; - int ret; - - ret = ufs_qcom_phy_get_reset(phy_common); - if (ret) - return ret; - - ret = reset_control_assert(phy_common->ufs_reset); - if (ret) - return ret; - - if (phy_common->mode == PHY_MODE_UFS_HS_B) - is_rate_B = true; - - ret = ufs_qcom_phy_qmp_20nm_phy_calibrate(phy_common, is_rate_B); - if (!ret) - /* phy calibrated, but yet to be started */ - phy_common->is_started = false; - - return ret; -} - -static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy) -{ - return 0; -} - static int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy, enum phy_mode mode, int submode) @@ -190,8 +160,6 @@ static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) } static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = { - .init = ufs_qcom_phy_qmp_20nm_init, - .exit = ufs_qcom_phy_qmp_20nm_exit, .power_on = ufs_qcom_phy_power_on, .power_off = ufs_qcom_phy_power_off, .set_mode = ufs_qcom_phy_qmp_20nm_set_mode, @@ -199,6 +167,7 @@ static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = { }; static struct ufs_qcom_phy_specific_ops phy_20nm_ops = { + .calibrate = ufs_qcom_phy_qmp_20nm_phy_calibrate, .start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes, .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready, .set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable, diff --git a/drivers/phy/qualcomm/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c index fe59785a55f2..0a9f50f086b6 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs.c @@ -147,7 +147,7 @@ out: } EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe); -int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common) +static int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common) { struct reset_control *reset; @@ -161,7 +161,6 @@ int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common) phy_common->ufs_reset = reset; return 0; } -EXPORT_SYMBOL_GPL(ufs_qcom_phy_get_reset); static int __ufs_qcom_phy_clk_get(struct device *dev, const char *name, struct clk **clk_out, bool err_print) @@ -544,10 +543,23 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy) { struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); struct device *dev = phy_common->dev; + bool is_rate_B = false; int err; - if (phy_common->is_powered_on) - return 0; + err = ufs_qcom_phy_get_reset(phy_common); + if (err) + return err; + + err = reset_control_assert(phy_common->ufs_reset); + if (err) + return err; + + if (phy_common->mode == PHY_MODE_UFS_HS_B) + is_rate_B = true; + + err = phy_common->phy_spec_ops->calibrate(phy_common, is_rate_B); + if (err) + return err; err = reset_control_deassert(phy_common->ufs_reset); if (err) { @@ -555,17 +567,13 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy) return err; } - if (!phy_common->is_started) { - err = ufs_qcom_phy_start_serdes(phy_common); - if (err) - return err; - - err = ufs_qcom_phy_is_pcs_ready(phy_common); - if (err) - return err; + err = ufs_qcom_phy_start_serdes(phy_common); + if (err) + return err; - phy_common->is_started = true; - } + err = ufs_qcom_phy_is_pcs_ready(phy_common); + if (err) + return err; err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy); if (err) { @@ -609,7 +617,6 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy) } } - phy_common->is_powered_on = true; goto out; out_disable_ref_clk: @@ -629,9 +636,6 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy) { struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); - if (!phy_common->is_powered_on) - return 0; - phy_common->phy_spec_ops->power_control(phy_common, false); if (phy_common->vddp_ref_clk.reg) @@ -643,8 +647,6 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy) ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll); ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy); reset_control_assert(phy_common->ufs_reset); - phy_common->is_powered_on = false; - return 0; } EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off); diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index 1c25b1c82314..de9d3f56b58c 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -544,19 +544,11 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) */ ufs_qcom_disable_lane_clks(host); phy_power_off(phy); - goto out; - } - /* - * If UniPro link is not active, PHY ref_clk, main PHY analog power - * rail and low noise analog power rail for PLL can be switched off. - */ - if (!ufs_qcom_is_link_active(hba)) { + } else if (!ufs_qcom_is_link_active(hba)) { ufs_qcom_disable_lane_clks(host); - phy_power_off(phy); } -out: return ret; } @@ -566,21 +558,26 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) struct phy *phy = host->generic_phy; int err; - err = phy_power_on(phy); - if (err) { - dev_err(hba->dev, "%s: failed enabling regs, err = %d\n", - __func__, err); - goto out; - } + if (ufs_qcom_is_link_off(hba)) { + err = phy_power_on(phy); + if (err) { + dev_err(hba->dev, "%s: failed PHY power on: %d\n", + __func__, err); + return err; + } - err = ufs_qcom_enable_lane_clks(host); - if (err) - goto out; + err = ufs_qcom_enable_lane_clks(host); + if (err) + return err; - hba->is_sys_suspended = false; + } else if (!ufs_qcom_is_link_active(hba)) { + err = ufs_qcom_enable_lane_clks(host); + if (err) + return err; + } -out: - return err; + hba->is_sys_suspended = false; + return 0; } struct ufs_qcom_dev_params { @@ -1106,8 +1103,6 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on, return 0; if (on && (status == POST_CHANGE)) { - phy_power_on(host->generic_phy); - /* enable the device ref clock for HS mode*/ if (ufshcd_is_hs_mode(&hba->pwr_info)) ufs_qcom_dev_ref_clk_ctrl(host, true); @@ -1119,9 +1114,6 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on, if (!ufs_qcom_is_link_active(hba)) { /* disable device ref_clk */ ufs_qcom_dev_ref_clk_ctrl(host, false); - - /* powering off PHY during aggressive clk gating */ - phy_power_off(host->generic_phy); } vote = host->bus_vote.min_bw_vote; -- cgit v1.2.3-55-g7522 From 043f42edbf2953ea45d0413ac998cff3dae6b268 Mon Sep 17 00:00:00 2001 From: Christoph Muellner Date: Fri, 22 Mar 2019 12:34:50 +0100 Subject: phy: rockchip-emmc: Allow to set drive impedance via DTS. The rockchip-emmc PHY can be configured with different drive impedance values. Currenlty a value of 50 Ohm is hard coded into the driver. This patch introduces the DTS property 'drive-impedance-ohm' for the rockchip-emmc phy node, which uses the value from the DTS to setup the drive impedance accordingly. Signed-off-by: Christoph Muellner Signed-off-by: Philipp Tomsich Reviewed-by: Heiko Stuebner Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/rockchip/phy-rockchip-emmc.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/rockchip/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c index 19bf84f0bc67..b804c6bf4b55 100644 --- a/drivers/phy/rockchip/phy-rockchip-emmc.c +++ b/drivers/phy/rockchip/phy-rockchip-emmc.c @@ -87,6 +87,7 @@ struct rockchip_emmc_phy { unsigned int reg_offset; struct regmap *reg_base; struct clk *emmcclk; + unsigned int drive_impedance; }; static int rockchip_emmc_phy_power(struct phy *phy, bool on_off) @@ -281,10 +282,10 @@ static int rockchip_emmc_phy_power_on(struct phy *phy) { struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); - /* Drive impedance: 50 Ohm */ + /* Drive impedance: from DTS */ regmap_write(rk_phy->reg_base, rk_phy->reg_offset + GRF_EMMCPHY_CON6, - HIWORD_UPDATE(PHYCTRL_DR_50OHM, + HIWORD_UPDATE(rk_phy->drive_impedance, PHYCTRL_DR_MASK, PHYCTRL_DR_SHIFT)); @@ -314,6 +315,26 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +static u32 convert_drive_impedance_ohm(struct platform_device *pdev, u32 dr_ohm) +{ + switch (dr_ohm) { + case 100: + return PHYCTRL_DR_100OHM; + case 66: + return PHYCTRL_DR_66OHM; + case 50: + return PHYCTRL_DR_50OHM; + case 40: + return PHYCTRL_DR_40OHM; + case 33: + return PHYCTRL_DR_33OHM; + } + + dev_warn(&pdev->dev, "Invalid value %u for drive-impedance-ohm.\n", + dr_ohm); + return PHYCTRL_DR_50OHM; +} + static int rockchip_emmc_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -322,6 +343,7 @@ static int rockchip_emmc_phy_probe(struct platform_device *pdev) struct phy_provider *phy_provider; struct regmap *grf; unsigned int reg_offset; + u32 val; if (!dev->parent || !dev->parent->of_node) return -ENODEV; @@ -344,6 +366,10 @@ static int rockchip_emmc_phy_probe(struct platform_device *pdev) rk_phy->reg_offset = reg_offset; rk_phy->reg_base = grf; + rk_phy->drive_impedance = PHYCTRL_DR_50OHM; + + if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val)) + rk_phy->drive_impedance = convert_drive_impedance_ohm(pdev, val); generic_phy = devm_phy_create(dev, dev->of_node, &ops); if (IS_ERR(generic_phy)) { -- cgit v1.2.3-55-g7522 From 54be9c1a03a3337e39f877f1536bbf25c9e8bff3 Mon Sep 17 00:00:00 2001 From: Stanley Chu Date: Sat, 16 Mar 2019 13:04:46 +0800 Subject: phy: mediatek: Add UFS M-PHY driver Add UFS M-PHY driver on MediaTek chipsets. Signed-off-by: Stanley Chu Reviewed-by: Chunfeng Yun Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/mediatek/Kconfig | 10 ++ drivers/phy/mediatek/Makefile | 1 + drivers/phy/mediatek/phy-mtk-ufs.c | 245 +++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 drivers/phy/mediatek/phy-mtk-ufs.c (limited to 'drivers/phy') diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig index 8857d00b3c65..b5a89dbd3fe7 100644 --- a/drivers/phy/mediatek/Kconfig +++ b/drivers/phy/mediatek/Kconfig @@ -13,6 +13,16 @@ config PHY_MTK_TPHY multi-ports is first version, otherwise is second veriosn, so you can easily distinguish them by banks layout. +config PHY_MTK_UFS + tristate "MediaTek UFS M-PHY driver" + depends on ARCH_MEDIATEK && OF + select GENERIC_PHY + help + Support for UFS M-PHY on MediaTek chipsets. + Enable this to provide vendor-specific probing, + initialization, power on and power off flow of + specified M-PHYs. + config PHY_MTK_XSPHY tristate "MediaTek XS-PHY Driver" depends on ARCH_MEDIATEK && OF diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile index ee49edc97ee9..08a8e6a97b1e 100644 --- a/drivers/phy/mediatek/Makefile +++ b/drivers/phy/mediatek/Makefile @@ -4,4 +4,5 @@ # obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o +obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o diff --git a/drivers/phy/mediatek/phy-mtk-ufs.c b/drivers/phy/mediatek/phy-mtk-ufs.c new file mode 100644 index 000000000000..9a80a76d7990 --- /dev/null +++ b/drivers/phy/mediatek/phy-mtk-ufs.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 MediaTek Inc. + * Author: Stanley Chu + */ + +#include +#include +#include +#include +#include +#include + +/* mphy register and offsets */ +#define MP_GLB_DIG_8C 0x008C +#define FRC_PLL_ISO_EN BIT(8) +#define PLL_ISO_EN BIT(9) +#define FRC_FRC_PWR_ON BIT(10) +#define PLL_PWR_ON BIT(11) + +#define MP_LN_DIG_RX_9C 0xA09C +#define FSM_DIFZ_FRC BIT(18) + +#define MP_LN_DIG_RX_AC 0xA0AC +#define FRC_RX_SQ_EN BIT(0) +#define RX_SQ_EN BIT(1) + +#define MP_LN_RX_44 0xB044 +#define FRC_CDR_PWR_ON BIT(17) +#define CDR_PWR_ON BIT(18) +#define FRC_CDR_ISO_EN BIT(19) +#define CDR_ISO_EN BIT(20) + +struct ufs_mtk_phy { + struct device *dev; + void __iomem *mmio; + struct clk *mp_clk; + struct clk *unipro_clk; +}; + +static inline u32 mphy_readl(struct ufs_mtk_phy *phy, u32 reg) +{ + return readl(phy->mmio + reg); +} + +static inline void mphy_writel(struct ufs_mtk_phy *phy, u32 val, u32 reg) +{ + writel(val, phy->mmio + reg); +} + +static void mphy_set_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit) +{ + u32 val; + + val = mphy_readl(phy, reg); + val |= bit; + mphy_writel(phy, val, reg); +} + +static void mphy_clr_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit) +{ + u32 val; + + val = mphy_readl(phy, reg); + val &= ~bit; + mphy_writel(phy, val, reg); +} + +static struct ufs_mtk_phy *get_ufs_mtk_phy(struct phy *generic_phy) +{ + return (struct ufs_mtk_phy *)phy_get_drvdata(generic_phy); +} + +static int ufs_mtk_phy_clk_init(struct ufs_mtk_phy *phy) +{ + struct device *dev = phy->dev; + + phy->unipro_clk = devm_clk_get(dev, "unipro"); + if (IS_ERR(phy->unipro_clk)) { + dev_err(dev, "failed to get clock: unipro"); + return PTR_ERR(phy->unipro_clk); + } + + phy->mp_clk = devm_clk_get(dev, "mp"); + if (IS_ERR(phy->mp_clk)) { + dev_err(dev, "failed to get clock: mp"); + return PTR_ERR(phy->mp_clk); + } + + return 0; +} + +static void ufs_mtk_phy_set_active(struct ufs_mtk_phy *phy) +{ + /* release DA_MP_PLL_PWR_ON */ + mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON); + mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON); + + /* release DA_MP_PLL_ISO_EN */ + mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN); + mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN); + + /* release DA_MP_CDR_PWR_ON */ + mphy_set_bit(phy, MP_LN_RX_44, CDR_PWR_ON); + mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON); + + /* release DA_MP_CDR_ISO_EN */ + mphy_clr_bit(phy, MP_LN_RX_44, CDR_ISO_EN); + mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN); + + /* release DA_MP_RX0_SQ_EN */ + mphy_set_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN); + mphy_clr_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN); + + /* delay 1us to wait DIFZ stable */ + udelay(1); + + /* release DIFZ */ + mphy_clr_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC); +} + +static void ufs_mtk_phy_set_deep_hibern(struct ufs_mtk_phy *phy) +{ + /* force DIFZ */ + mphy_set_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC); + + /* force DA_MP_RX0_SQ_EN */ + mphy_set_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN); + mphy_clr_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN); + + /* force DA_MP_CDR_ISO_EN */ + mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN); + mphy_set_bit(phy, MP_LN_RX_44, CDR_ISO_EN); + + /* force DA_MP_CDR_PWR_ON */ + mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON); + mphy_clr_bit(phy, MP_LN_RX_44, CDR_PWR_ON); + + /* force DA_MP_PLL_ISO_EN */ + mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN); + mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN); + + /* force DA_MP_PLL_PWR_ON */ + mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON); + mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON); +} + +static int ufs_mtk_phy_power_on(struct phy *generic_phy) +{ + struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy); + int ret; + + ret = clk_prepare_enable(phy->unipro_clk); + if (ret) { + dev_err(phy->dev, "unipro_clk enable failed %d\n", ret); + goto out; + } + + ret = clk_prepare_enable(phy->mp_clk); + if (ret) { + dev_err(phy->dev, "mp_clk enable failed %d\n", ret); + goto out_unprepare_unipro_clk; + } + + ufs_mtk_phy_set_active(phy); + + return 0; + +out_unprepare_unipro_clk: + clk_disable_unprepare(phy->unipro_clk); +out: + return ret; +} + +static int ufs_mtk_phy_power_off(struct phy *generic_phy) +{ + struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy); + + ufs_mtk_phy_set_deep_hibern(phy); + + clk_disable_unprepare(phy->unipro_clk); + clk_disable_unprepare(phy->mp_clk); + + return 0; +} + +static const struct phy_ops ufs_mtk_phy_ops = { + .power_on = ufs_mtk_phy_power_on, + .power_off = ufs_mtk_phy_power_off, + .owner = THIS_MODULE, +}; + +static int ufs_mtk_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct resource *res; + struct ufs_mtk_phy *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); + + phy->dev = dev; + + ret = ufs_mtk_phy_clk_init(phy); + if (ret) + return ret; + + generic_phy = devm_phy_create(dev, NULL, &ufs_mtk_phy_ops); + if (IS_ERR(generic_phy)) + return PTR_ERR(generic_phy); + + phy_set_drvdata(generic_phy, 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 ufs_mtk_phy_of_match[] = { + {.compatible = "mediatek,mt8183-ufsphy"}, + {}, +}; +MODULE_DEVICE_TABLE(of, ufs_mtk_phy_of_match); + +static struct platform_driver ufs_mtk_phy_driver = { + .probe = ufs_mtk_phy_probe, + .driver = { + .of_match_table = ufs_mtk_phy_of_match, + .name = "ufs_mtk_phy", + }, +}; +module_platform_driver(ufs_mtk_phy_driver); + +MODULE_DESCRIPTION("Universal Flash Storage (UFS) MediaTek MPHY"); +MODULE_AUTHOR("Stanley Chu "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From e6577cb5103b7ca7c0204c0c86ef4af8aa6288f6 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 19 Feb 2019 14:53:49 +0000 Subject: phy: ti-pipe3: fix missing bit-wise or operator when assigning val There seems to be a missing bit-wise or operator when setting val, fix this by adding it in. Fixes: 2796ceb0c18a ("phy: ti-pipe3: Update pcie phy settings") Cc: stable@vger.kernel.org # v4.19+ Signed-off-by: Colin Ian King Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-ti-pipe3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/phy') diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c index 68ce4a082b9b..693acc167351 100644 --- a/drivers/phy/ti/phy-ti-pipe3.c +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -303,7 +303,7 @@ static void ti_pipe3_calibrate(struct ti_pipe3 *phy) val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY); val &= ~(INTERFACE_MASK | LOSD_MASK | MEM_PLLDIV); - val = (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT); + val |= (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT); ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY, val); val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES); -- cgit v1.2.3-55-g7522 From 22940823f0629b2068c259fb859ed150b8563d8a Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 22 Mar 2019 10:58:04 +0200 Subject: phy: ti-pipe3: Introduce mode property in driver data Introduce a mode property in the driver data so that we don't have to keep using "of_device_is_compatible()" throughtout the driver. No functional change. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-ti-pipe3.c | 93 ++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 36 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c index 693acc167351..c3e0499bed3e 100644 --- a/drivers/phy/ti/phy-ti-pipe3.c +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -110,6 +110,10 @@ #define PLL_IDLE_TIME 100 /* in milliseconds */ #define PLL_LOCK_TIME 100 /* in milliseconds */ +enum pipe3_mode { PIPE3_MODE_PCIE = 1, + PIPE3_MODE_SATA, + PIPE3_MODE_USBSS }; + struct pipe3_dpll_params { u16 m; u8 n; @@ -141,6 +145,7 @@ struct ti_pipe3 { unsigned int power_reg; /* power reg. index within syscon */ unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */ bool sata_refclk_enabled; + enum pipe3_mode mode; }; static struct pipe3_dpll_map dpll_map_usb[] = { @@ -163,6 +168,25 @@ static struct pipe3_dpll_map dpll_map_sata[] = { { }, /* Terminator */ }; +struct pipe3_data { + enum pipe3_mode mode; + struct pipe3_dpll_map *dpll_map; +}; + +static struct pipe3_data data_usb = { + .mode = PIPE3_MODE_USBSS, + .dpll_map = dpll_map_usb, +}; + +static struct pipe3_data data_sata = { + .mode = PIPE3_MODE_SATA, + .dpll_map = dpll_map_sata, +}; + +static struct pipe3_data data_pcie = { + .mode = PIPE3_MODE_PCIE, +}; + static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset) { return __raw_readl(addr + offset); @@ -340,7 +364,7 @@ static int ti_pipe3_init(struct phy *x) * as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table * 18-1804. */ - if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { + if (phy->mode == PIPE3_MODE_PCIE) { if (!phy->pcs_syscon) { omap_control_pcie_pcs(phy->control_dev, 0x96); return 0; @@ -367,8 +391,7 @@ static int ti_pipe3_init(struct phy *x) /* SATA has issues if re-programmed when locked */ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); - if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node, - "ti,phy-pipe3-sata")) + if ((val & PLL_LOCK) && phy->mode == PIPE3_MODE_SATA) return ret; /* Program the DPLL */ @@ -390,12 +413,11 @@ static int ti_pipe3_exit(struct phy *x) /* If dpll_reset_syscon is not present we wont power down SATA DPLL * due to Errata i783 */ - if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") && - !phy->dpll_reset_syscon) + if (phy->mode == PIPE3_MODE_SATA && !phy->dpll_reset_syscon) return 0; /* PCIe doesn't have internal DPLL */ - if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { + if (phy->mode != PIPE3_MODE_PCIE) { /* Put DPLL in IDLE mode */ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); val |= PLL_IDLE; @@ -418,7 +440,7 @@ static int ti_pipe3_exit(struct phy *x) } /* i783: SATA needs control bit toggle after PLL unlock */ - if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) { + if (phy->mode == PIPE3_MODE_SATA) { regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET); regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, @@ -443,7 +465,6 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy) { struct clk *clk; struct device *dev = phy->dev; - struct device_node *node = dev->of_node; phy->refclk = devm_clk_get(dev, "refclk"); if (IS_ERR(phy->refclk)) { @@ -451,11 +472,11 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy) /* older DTBs have missing refclk in SATA PHY * so don't bail out in case of SATA PHY. */ - if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) + if (phy->mode != PIPE3_MODE_SATA) return PTR_ERR(phy->refclk); } - if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + if (phy->mode != PIPE3_MODE_SATA) { phy->wkupclk = devm_clk_get(dev, "wkupclk"); if (IS_ERR(phy->wkupclk)) { dev_err(dev, "unable to get wkupclk\n"); @@ -465,8 +486,7 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy) phy->wkupclk = ERR_PTR(-ENODEV); } - if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") || - phy->phy_power_syscon) { + if (phy->mode != PIPE3_MODE_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"); @@ -474,7 +494,7 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy) } } - if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + if (phy->mode == PIPE3_MODE_PCIE) { clk = devm_clk_get(dev, "dpll_ref"); if (IS_ERR(clk)) { dev_err(dev, "unable to get dpll ref clk\n"); @@ -546,7 +566,7 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) phy->control_dev = &control_pdev->dev; } - if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + if (phy->mode == PIPE3_MODE_PCIE) { phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node, "syscon-pcs"); if (IS_ERR(phy->pcs_syscon)) { @@ -564,7 +584,7 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) } } - if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + if (phy->mode == PIPE3_MODE_SATA) { phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, "syscon-pllreset"); if (IS_ERR(phy->dpll_reset_syscon)) { @@ -589,10 +609,9 @@ static int ti_pipe3_get_tx_rx_base(struct ti_pipe3 *phy) { struct resource *res; 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")) + if (phy->mode != PIPE3_MODE_PCIE) return 0; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, @@ -611,24 +630,12 @@ static int ti_pipe3_get_tx_rx_base(struct ti_pipe3 *phy) 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")) + if (phy->mode == PIPE3_MODE_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; - } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_ctrl"); phy->pll_ctrl_base = devm_ioremap_resource(dev, res); @@ -640,15 +647,28 @@ 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; + const struct of_device_id *match; + struct pipe3_data *data; phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); if (!phy) return -ENOMEM; - phy->dev = dev; + match = of_match_device(ti_pipe3_id_table, dev); + if (!match) + return -EINVAL; + + data = (struct pipe3_data *)match->data; + if (!data) { + dev_err(dev, "no driver data\n"); + return -EINVAL; + } + + phy->dev = dev; + phy->mode = data->mode; + phy->dpll_map = data->dpll_map; ret = ti_pipe3_get_pll_base(phy); if (ret) @@ -672,7 +692,7 @@ static int ti_pipe3_probe(struct platform_device *pdev) /* * Prevent auto-disable of refclk for SATA PHY due to Errata i783 */ - if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + if (phy->mode == PIPE3_MODE_SATA) { if (!IS_ERR(phy->refclk)) { clk_prepare_enable(phy->refclk); phy->sata_refclk_enabled = true; @@ -762,18 +782,19 @@ static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy) static const struct of_device_id ti_pipe3_id_table[] = { { .compatible = "ti,phy-usb3", - .data = dpll_map_usb, + .data = &data_usb, }, { .compatible = "ti,omap-usb3", - .data = dpll_map_usb, + .data = &data_usb, }, { .compatible = "ti,phy-pipe3-sata", - .data = dpll_map_sata, + .data = &data_sata, }, { .compatible = "ti,phy-pipe3-pcie", + .data = &data_pcie, }, {} }; -- cgit v1.2.3-55-g7522 From fdef2f9f700fab55c36d45e7c46607e935886c6c Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 22 Mar 2019 10:58:05 +0200 Subject: phy: ti-pipe3: improve DPLL stability for SATA & USB For increased DPLL stability use the settings recommended in the TRM [1] for PHY_RX registers for SATA and USB. For SATA we need to use spread spectrum settings even though we don't have spread spectrum enabled. The suggested non-spread spectrum settings don't work. [1] DRA75x, DRA74x TRM - http://www.ti.com/lit/ug/sprui30f/sprui30f.pdf Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-ti-pipe3.c | 215 +++++++++++++++++++++++++++++++++--------- 1 file changed, 173 insertions(+), 42 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c index c3e0499bed3e..c596e27bae65 100644 --- a/drivers/phy/ti/phy-ti-pipe3.c +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -68,39 +68,61 @@ #define PCIE_PCS_MASK 0xFF0000 #define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 -#define PCIEPHYRX_ANA_PROGRAMMABILITY 0x0000000C +#define PIPE3_PHY_RX_ANA_PROGRAMMABILITY 0x0000000C #define INTERFACE_MASK GENMASK(31, 27) #define INTERFACE_SHIFT 27 +#define INTERFACE_MODE_USBSS BIT(4) +#define INTERFACE_MODE_SATA_1P5 BIT(3) +#define INTERFACE_MODE_SATA_3P0 BIT(2) +#define INTERFACE_MODE_PCIE BIT(0) + #define LOSD_MASK GENMASK(17, 14) #define LOSD_SHIFT 14 #define MEM_PLLDIV GENMASK(6, 5) -#define PCIEPHYRX_TRIM 0x0000001C -#define MEM_DLL_TRIM_SEL GENMASK(31, 30) +#define PIPE3_PHY_RX_TRIM 0x0000001C +#define MEM_DLL_TRIM_SEL_MASK GENMASK(31, 30) #define MEM_DLL_TRIM_SHIFT 30 -#define PCIEPHYRX_DLL 0x00000024 -#define MEM_DLL_PHINT_RATE GENMASK(31, 30) +#define PIPE3_PHY_RX_DLL 0x00000024 +#define MEM_DLL_PHINT_RATE_MASK GENMASK(31, 30) +#define MEM_DLL_PHINT_RATE_SHIFT 30 -#define PCIEPHYRX_DIGITAL_MODES 0x00000028 +#define PIPE3_PHY_RX_DIGITAL_MODES 0x00000028 +#define MEM_HS_RATE_MASK GENMASK(28, 27) +#define MEM_HS_RATE_SHIFT 27 +#define MEM_OVRD_HS_RATE BIT(26) +#define MEM_OVRD_HS_RATE_SHIFT 26 #define MEM_CDR_FASTLOCK BIT(23) -#define MEM_CDR_LBW GENMASK(22, 21) -#define MEM_CDR_STEPCNT GENMASK(20, 19) +#define MEM_CDR_FASTLOCK_SHIFT 23 +#define MEM_CDR_LBW_MASK GENMASK(22, 21) +#define MEM_CDR_LBW_SHIFT 21 +#define MEM_CDR_STEPCNT_MASK GENMASK(20, 19) +#define MEM_CDR_STEPCNT_SHIFT 19 #define MEM_CDR_STL_MASK GENMASK(18, 16) #define MEM_CDR_STL_SHIFT 16 #define MEM_CDR_THR_MASK GENMASK(15, 13) #define MEM_CDR_THR_SHIFT 13 #define MEM_CDR_THR_MODE BIT(12) -#define MEM_CDR_CDR_2NDO_SDM_MODE BIT(11) -#define MEM_OVRD_HS_RATE BIT(26) - -#define PCIEPHYRX_EQUALIZER 0x00000038 -#define MEM_EQLEV GENMASK(31, 16) -#define MEM_EQFTC GENMASK(15, 11) -#define MEM_EQCTL GENMASK(10, 7) +#define MEM_CDR_THR_MODE_SHIFT 12 +#define MEM_CDR_2NDO_SDM_MODE BIT(11) +#define MEM_CDR_2NDO_SDM_MODE_SHIFT 11 + +#define PIPE3_PHY_RX_EQUALIZER 0x00000038 +#define MEM_EQLEV_MASK GENMASK(31, 16) +#define MEM_EQLEV_SHIFT 16 +#define MEM_EQFTC_MASK GENMASK(15, 11) +#define MEM_EQFTC_SHIFT 11 +#define MEM_EQCTL_MASK GENMASK(10, 7) #define MEM_EQCTL_SHIFT 7 #define MEM_OVRD_EQLEV BIT(2) +#define MEM_OVRD_EQLEV_SHIFT 2 #define MEM_OVRD_EQFTC BIT(1) +#define MEM_OVRD_EQFTC_SHIFT 1 + +#define SATA_PHY_RX_IO_AND_A2D_OVERRIDES 0x44 +#define MEM_CDR_LOS_SOURCE_MASK GENMASK(10, 9) +#define MEM_CDR_LOS_SOURCE_SHIFT 9 /* * This is an Empirical value that works, need to confirm the actual @@ -127,6 +149,27 @@ struct pipe3_dpll_map { struct pipe3_dpll_params params; }; +struct pipe3_settings { + u8 ana_interface; + u8 ana_losd; + u8 dig_fastlock; + u8 dig_lbw; + u8 dig_stepcnt; + u8 dig_stl; + u8 dig_thr; + u8 dig_thr_mode; + u8 dig_2ndo_sdm_mode; + u8 dig_hs_rate; + u8 dig_ovrd_hs_rate; + u8 dll_trim_sel; + u8 dll_phint_rate; + u8 eq_lev; + u8 eq_ftc; + u8 eq_ctl; + u8 eq_ovrd_lev; + u8 eq_ovrd_ftc; +}; + struct ti_pipe3 { void __iomem *pll_ctrl_base; void __iomem *phy_rx; @@ -146,6 +189,7 @@ struct ti_pipe3 { unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */ bool sata_refclk_enabled; enum pipe3_mode mode; + struct pipe3_settings settings; }; static struct pipe3_dpll_map dpll_map_usb[] = { @@ -171,20 +215,84 @@ static struct pipe3_dpll_map dpll_map_sata[] = { struct pipe3_data { enum pipe3_mode mode; struct pipe3_dpll_map *dpll_map; + struct pipe3_settings settings; }; static struct pipe3_data data_usb = { .mode = PIPE3_MODE_USBSS, .dpll_map = dpll_map_usb, + .settings = { + /* DRA75x TRM Table 26-17 Preferred USB3_PHY_RX SCP Register Settings */ + .ana_interface = INTERFACE_MODE_USBSS, + .ana_losd = 0xa, + .dig_fastlock = 1, + .dig_lbw = 3, + .dig_stepcnt = 0, + .dig_stl = 0x3, + .dig_thr = 1, + .dig_thr_mode = 1, + .dig_2ndo_sdm_mode = 0, + .dig_hs_rate = 0, + .dig_ovrd_hs_rate = 1, + .dll_trim_sel = 0x2, + .dll_phint_rate = 0x3, + .eq_lev = 0, + .eq_ftc = 0, + .eq_ctl = 0x9, + .eq_ovrd_lev = 0, + .eq_ovrd_ftc = 0, + }, }; static struct pipe3_data data_sata = { .mode = PIPE3_MODE_SATA, .dpll_map = dpll_map_sata, + .settings = { + /* DRA75x TRM Table 26-9 Preferred SATA_PHY_RX SCP Register Settings */ + .ana_interface = INTERFACE_MODE_SATA_3P0, + .ana_losd = 0x5, + .dig_fastlock = 1, + .dig_lbw = 3, + .dig_stepcnt = 0, + .dig_stl = 0x3, + .dig_thr = 1, + .dig_thr_mode = 1, + .dig_2ndo_sdm_mode = 0, + .dig_hs_rate = 0, /* Not in TRM preferred settings */ + .dig_ovrd_hs_rate = 0, /* Not in TRM preferred settings */ + .dll_trim_sel = 0x1, + .dll_phint_rate = 0x2, /* for 1.5 GHz DPLL clock */ + .eq_lev = 0, + .eq_ftc = 0x1f, + .eq_ctl = 0, + .eq_ovrd_lev = 1, + .eq_ovrd_ftc = 1, + }, }; static struct pipe3_data data_pcie = { .mode = PIPE3_MODE_PCIE, + .settings = { + /* DRA75x TRM Table 26-62 Preferred PCIe_PHY_RX SCP Register Settings */ + .ana_interface = INTERFACE_MODE_PCIE, + .ana_losd = 0xa, + .dig_fastlock = 1, + .dig_lbw = 3, + .dig_stepcnt = 0, + .dig_stl = 0x3, + .dig_thr = 1, + .dig_thr_mode = 1, + .dig_2ndo_sdm_mode = 0, + .dig_hs_rate = 0, + .dig_ovrd_hs_rate = 0, + .dll_trim_sel = 0x2, + .dll_phint_rate = 0x3, + .eq_lev = 0, + .eq_ftc = 0x1f, + .eq_ctl = 1, + .eq_ovrd_lev = 0, + .eq_ovrd_ftc = 0, + }, }; static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset) @@ -324,32 +432,55 @@ static int ti_pipe3_dpll_program(struct ti_pipe3 *phy) static void ti_pipe3_calibrate(struct ti_pipe3 *phy) { u32 val; + struct pipe3_settings *s = &phy->settings; - val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY); + val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_ANA_PROGRAMMABILITY); val &= ~(INTERFACE_MASK | LOSD_MASK | MEM_PLLDIV); - val |= (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT); - ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY, val); - - val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES); - val &= ~(MEM_CDR_STEPCNT | MEM_CDR_STL_MASK | MEM_CDR_THR_MASK | - MEM_CDR_CDR_2NDO_SDM_MODE | MEM_OVRD_HS_RATE); - val |= (MEM_CDR_FASTLOCK | MEM_CDR_LBW | 0x3 << MEM_CDR_STL_SHIFT | - 0x1 << MEM_CDR_THR_SHIFT | MEM_CDR_THR_MODE); - ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES, val); - - val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_TRIM); - val &= ~MEM_DLL_TRIM_SEL; - val |= 0x2 << MEM_DLL_TRIM_SHIFT; - ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_TRIM, val); - - val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DLL); - val |= MEM_DLL_PHINT_RATE; - ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DLL, val); - - val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_EQUALIZER); - val &= ~(MEM_EQLEV | MEM_EQCTL | MEM_OVRD_EQLEV | MEM_OVRD_EQFTC); - val |= MEM_EQFTC | 0x1 << MEM_EQCTL_SHIFT; - ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_EQUALIZER, val); + val |= (s->ana_interface << INTERFACE_SHIFT | s->ana_losd << LOSD_SHIFT); + ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_ANA_PROGRAMMABILITY, val); + + val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DIGITAL_MODES); + val &= ~(MEM_HS_RATE_MASK | MEM_OVRD_HS_RATE | MEM_CDR_FASTLOCK | + MEM_CDR_LBW_MASK | MEM_CDR_STEPCNT_MASK | MEM_CDR_STL_MASK | + MEM_CDR_THR_MASK | MEM_CDR_THR_MODE | MEM_CDR_2NDO_SDM_MODE); + val |= s->dig_hs_rate << MEM_HS_RATE_SHIFT | + s->dig_ovrd_hs_rate << MEM_OVRD_HS_RATE_SHIFT | + s->dig_fastlock << MEM_CDR_FASTLOCK_SHIFT | + s->dig_lbw << MEM_CDR_LBW_SHIFT | + s->dig_stepcnt << MEM_CDR_STEPCNT_SHIFT | + s->dig_stl << MEM_CDR_STL_SHIFT | + s->dig_thr << MEM_CDR_THR_SHIFT | + s->dig_thr_mode << MEM_CDR_THR_MODE_SHIFT | + s->dig_2ndo_sdm_mode << MEM_CDR_2NDO_SDM_MODE_SHIFT; + ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_DIGITAL_MODES, val); + + val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_TRIM); + val &= ~MEM_DLL_TRIM_SEL_MASK; + val |= s->dll_trim_sel << MEM_DLL_TRIM_SHIFT; + ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_TRIM, val); + + val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DLL); + val &= ~MEM_DLL_PHINT_RATE_MASK; + val |= s->dll_phint_rate << MEM_DLL_PHINT_RATE_SHIFT; + ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_DLL, val); + + val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_EQUALIZER); + val &= ~(MEM_EQLEV_MASK | MEM_EQFTC_MASK | MEM_EQCTL_MASK | + MEM_OVRD_EQLEV | MEM_OVRD_EQFTC); + val |= s->eq_lev << MEM_EQLEV_SHIFT | + s->eq_ftc << MEM_EQFTC_SHIFT | + s->eq_ctl << MEM_EQCTL_SHIFT | + s->eq_ovrd_lev << MEM_OVRD_EQLEV_SHIFT | + s->eq_ovrd_ftc << MEM_OVRD_EQFTC_SHIFT; + ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_EQUALIZER, val); + + if (phy->mode == PIPE3_MODE_SATA) { + val = ti_pipe3_readl(phy->phy_rx, + SATA_PHY_RX_IO_AND_A2D_OVERRIDES); + val &= ~MEM_CDR_LOS_SOURCE_MASK; + ti_pipe3_writel(phy->phy_rx, SATA_PHY_RX_IO_AND_A2D_OVERRIDES, + val); + } } static int ti_pipe3_init(struct phy *x) @@ -401,6 +532,8 @@ static int ti_pipe3_init(struct phy *x) return -EINVAL; } + ti_pipe3_calibrate(phy); + return ret; } @@ -611,9 +744,6 @@ static int ti_pipe3_get_tx_rx_base(struct ti_pipe3 *phy) struct device *dev = phy->dev; struct platform_device *pdev = to_platform_device(dev); - if (phy->mode != PIPE3_MODE_PCIE) - return 0; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_rx"); phy->phy_rx = devm_ioremap_resource(dev, res); @@ -669,6 +799,7 @@ static int ti_pipe3_probe(struct platform_device *pdev) phy->dev = dev; phy->mode = data->mode; phy->dpll_map = data->dpll_map; + phy->settings = data->settings; ret = ti_pipe3_get_pll_base(phy); if (ret) -- cgit v1.2.3-55-g7522 From 9d009d9c20624cd8ed2a3ae0e43752c4a34b4893 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 22 Mar 2019 10:58:06 +0200 Subject: phy: ti-pipe3: Fix SATA & USB PHY power up sequence As per "Table 26-7. SATA PHY Subsystem Low-Level Programming Sequence" in TRM [1] we need to turn on SATA_PHY_TX before SATA_PHY_RX. [1] DRA75x, DRA74x TRM - http://www.ti.com/lit/ug/sprui30f/sprui30f.pdf Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-ti-pipe3.c | 44 +++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c index c596e27bae65..aa5eab478039 100644 --- a/drivers/phy/ti/phy-ti-pipe3.c +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -56,14 +56,14 @@ #define SATA_PLL_SOFT_RESET BIT(18) -#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000 +#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK GENMASK(21, 14) #define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14 -#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000 +#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK GENMASK(31, 22) #define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22 -#define PIPE3_PHY_TX_RX_POWERON 0x3 -#define PIPE3_PHY_TX_RX_POWEROFF 0x0 +#define PIPE3_PHY_RX_POWERON (0x1 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT) +#define PIPE3_PHY_TX_POWERON (0x2 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT) #define PCIE_PCS_MASK 0xFF0000 #define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 @@ -328,7 +328,6 @@ 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); @@ -337,10 +336,8 @@ static int ti_pipe3_power_off(struct phy *x) 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); + PIPE3_PHY_PWRCTL_CLK_CMD_MASK, 0); return ret; } @@ -351,6 +348,7 @@ static int ti_pipe3_power_on(struct phy *x) int ret; unsigned long rate; struct ti_pipe3 *phy = phy_get_drvdata(x); + bool rx_pending = false; if (!phy->phy_power_syscon) { omap_control_phy_power(phy->control_dev, 1); @@ -363,14 +361,32 @@ static int ti_pipe3_power_on(struct phy *x) 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; - + mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK; + 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; + /* + * For PCIe, TX and RX must be powered on simultaneously. + * For USB and SATA, TX must be powered on before RX + */ + mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK; + if (phy->mode == PIPE3_MODE_SATA || phy->mode == PIPE3_MODE_USBSS) { + val = PIPE3_PHY_TX_POWERON; + rx_pending = true; + } else { + val = PIPE3_PHY_TX_POWERON | PIPE3_PHY_RX_POWERON; + } + + regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + mask, val); + + if (rx_pending) { + val = PIPE3_PHY_TX_POWERON | PIPE3_PHY_RX_POWERON; + regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + mask, val); + } + + return 0; } static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy) -- cgit v1.2.3-55-g7522 From 1d1bae7250758904ab09458022e2d97c23cf42b7 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 22 Mar 2019 10:58:07 +0200 Subject: phy: ti-pipe3: Fix PCIe power up sequence TRM [1] mentions that we need to power up PCIESS_PHY_TX and PCIESS_PHY_RX before configuring PCIe_PHY_RX SCP settings. See "Table 26-81. PCIePHY Subsystem Low-Level Programming Sequence". [1] DRA75x, DRA74x TRM - http://www.ti.com/lit/ug/sprui30f/sprui30f.pdf Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-ti-pipe3.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c index aa5eab478039..739aaa0eb0ef 100644 --- a/drivers/phy/ti/phy-ti-pipe3.c +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -341,6 +341,8 @@ static int ti_pipe3_power_off(struct phy *x) return ret; } +static void ti_pipe3_calibrate(struct ti_pipe3 *phy); + static int ti_pipe3_power_on(struct phy *x) { u32 val; @@ -386,6 +388,9 @@ static int ti_pipe3_power_on(struct phy *x) mask, val); } + if (phy->mode == PIPE3_MODE_PCIE) + ti_pipe3_calibrate(phy); + return 0; } @@ -520,12 +525,7 @@ static int ti_pipe3_init(struct phy *x) val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT; ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg, PCIE_PCS_MASK, val); - if (ret) - return ret; - - ti_pipe3_calibrate(phy); - - return 0; + return ret; } /* Bring it out of IDLE if it is IDLE */ -- cgit v1.2.3-55-g7522 From b7187e001a103f49ccb118bb2fb62ad31961b666 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 10 Apr 2019 15:48:39 +0100 Subject: phy: renesas: phy-rcar-gen2: Add support for r8a77470 This patch adds support for RZ/G1C (r8a77470) SoC. RZ/G1C SoC has a PLL register shared between hsusb0 and hsusb1. Compared to other RZ/G1 and R-Car Gen2/3, USB Host needs to deassert the pll reset. Signed-off-by: Biju Das Reviewed-and-Tested-by: Yoshihiro Shimoda Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen2.c | 130 ++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 12 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/renesas/phy-rcar-gen2.c b/drivers/phy/renesas/phy-rcar-gen2.c index 72eeb066912d..8dc5710d9c98 100644 --- a/drivers/phy/renesas/phy-rcar-gen2.c +++ b/drivers/phy/renesas/phy-rcar-gen2.c @@ -4,6 +4,7 @@ * * Copyright (C) 2014 Renesas Solutions Corp. * Copyright (C) 2014 Cogent Embedded, Inc. + * Copyright (C) 2019 Renesas Electronics Corp. */ #include @@ -15,6 +16,7 @@ #include #include #include +#include #define USBHS_LPSTS 0x02 #define USBHS_UGCTRL 0x80 @@ -35,6 +37,8 @@ #define USBHS_UGCTRL2_USB0SEL 0x00000030 #define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010 #define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030 +#define USBHS_UGCTRL2_USB0SEL_USB20 0x00000010 +#define USBHS_UGCTRL2_USB0SEL_HS_USB20 0x00000020 /* USB General status register (UGSTS) */ #define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */ @@ -64,6 +68,11 @@ struct rcar_gen2_phy_driver { struct rcar_gen2_channel *channels; }; +struct rcar_gen2_phy_data { + const struct phy_ops *gen2_phy_ops; + const u32 (*select_value)[PHYS_PER_CHANNEL]; +}; + static int rcar_gen2_phy_init(struct phy *p) { struct rcar_gen2_phy *phy = phy_get_drvdata(p); @@ -180,6 +189,60 @@ static int rcar_gen2_phy_power_off(struct phy *p) return 0; } +static int rz_g1c_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; + + spin_lock_irqsave(&drv->lock, flags); + + /* Power on USBHS PHY */ + value = readl(base + USBHS_UGCTRL); + value &= ~USBHS_UGCTRL_PLLRESET; + writel(value, base + USBHS_UGCTRL); + + /* As per the data sheet wait 340 micro sec for power stable */ + udelay(340); + + if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) { + value = readw(base + USBHS_LPSTS); + value |= USBHS_LPSTS_SUSPM; + writew(value, base + USBHS_LPSTS); + } + + spin_unlock_irqrestore(&drv->lock, flags); + + return 0; +} + +static int rz_g1c_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; + + spin_lock_irqsave(&drv->lock, flags); + /* Power off USBHS PHY */ + if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) { + 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 const struct phy_ops rcar_gen2_phy_ops = { .init = rcar_gen2_phy_init, .exit = rcar_gen2_phy_exit, @@ -188,12 +251,55 @@ static const struct phy_ops rcar_gen2_phy_ops = { .owner = THIS_MODULE, }; +static const struct phy_ops rz_g1c_phy_ops = { + .init = rcar_gen2_phy_init, + .exit = rcar_gen2_phy_exit, + .power_on = rz_g1c_phy_power_on, + .power_off = rz_g1c_phy_power_off, + .owner = THIS_MODULE, +}; + +static const u32 pci_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 const u32 usb20_select_value[][PHYS_PER_CHANNEL] = { + { USBHS_UGCTRL2_USB0SEL_USB20, USBHS_UGCTRL2_USB0SEL_HS_USB20 }, +}; + +static const struct rcar_gen2_phy_data rcar_gen2_usb_phy_data = { + .gen2_phy_ops = &rcar_gen2_phy_ops, + .select_value = pci_select_value, +}; + +static const struct rcar_gen2_phy_data rz_g1c_usb_phy_data = { + .gen2_phy_ops = &rz_g1c_phy_ops, + .select_value = usb20_select_value, +}; + static const struct of_device_id rcar_gen2_phy_match_table[] = { - { .compatible = "renesas,usb-phy-r8a7790" }, - { .compatible = "renesas,usb-phy-r8a7791" }, - { .compatible = "renesas,usb-phy-r8a7794" }, - { .compatible = "renesas,rcar-gen2-usb-phy" }, - { } + { + .compatible = "renesas,usb-phy-r8a77470", + .data = &rz_g1c_usb_phy_data, + }, + { + .compatible = "renesas,usb-phy-r8a7790", + .data = &rcar_gen2_usb_phy_data, + }, + { + .compatible = "renesas,usb-phy-r8a7791", + .data = &rcar_gen2_usb_phy_data, + }, + { + .compatible = "renesas,usb-phy-r8a7794", + .data = &rcar_gen2_usb_phy_data, + }, + { + .compatible = "renesas,rcar-gen2-usb-phy", + .data = &rcar_gen2_usb_phy_data, + }, + { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table); @@ -224,11 +330,6 @@ static const u32 select_mask[] = { [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; @@ -238,6 +339,7 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev) struct resource *res; void __iomem *base; struct clk *clk; + const struct rcar_gen2_phy_data *data; int i = 0; if (!dev->of_node) { @@ -266,6 +368,10 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev) drv->clk = clk; drv->base = base; + data = of_device_get_match_data(dev); + if (!data) + return -EINVAL; + drv->num_channels = of_get_child_count(dev->of_node); drv->channels = devm_kcalloc(dev, drv->num_channels, sizeof(struct rcar_gen2_channel), @@ -294,10 +400,10 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev) phy->channel = channel; phy->number = n; - phy->select_value = select_value[channel_num][n]; + phy->select_value = data->select_value[channel_num][n]; phy->phy = devm_phy_create(dev, NULL, - &rcar_gen2_phy_ops); + data->gen2_phy_ops); if (IS_ERR(phy->phy)) { dev_err(dev, "Failed to create PHY\n"); return PTR_ERR(phy->phy); -- cgit v1.2.3-55-g7522 From 5d8042e95fd471d0e342cf14f127194f1a867a01 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 10 Apr 2019 15:48:41 +0100 Subject: phy: rcar-gen3-usb2: Add support for r8a77470 This patch adds support for r8a77470 (RZ/G1C). We can reuse this driver for initializing timing/interrupt generation registers. Signed-off-by: Biju Das Reviewed-by: Yoshihiro Shimoda Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/Kconfig | 2 +- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 39 +++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig index e340a925bbb1..111bdcae775c 100644 --- a/drivers/phy/renesas/Kconfig +++ b/drivers/phy/renesas/Kconfig @@ -19,7 +19,7 @@ config PHY_RCAR_GEN3_PCIE config PHY_RCAR_GEN3_USB2 tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" depends on ARCH_RENESAS - depends on EXTCON + depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in depends on USB_SUPPORT select GENERIC_PHY select USB_COMMON diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 0a34782aaaa2..e3a88b962ee6 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -393,6 +393,12 @@ static const struct phy_ops rcar_gen3_phy_usb2_ops = { .owner = THIS_MODULE, }; +static const struct phy_ops rz_g1c_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .owner = THIS_MODULE, +}; + static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) { struct rcar_gen3_chan *ch = _ch; @@ -411,11 +417,27 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) } static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { - { .compatible = "renesas,usb2-phy-r8a7795" }, - { .compatible = "renesas,usb2-phy-r8a7796" }, - { .compatible = "renesas,usb2-phy-r8a77965" }, - { .compatible = "renesas,rcar-gen3-usb2-phy" }, - { } + { + .compatible = "renesas,usb2-phy-r8a77470", + .data = &rz_g1c_phy_usb2_ops, + }, + { + .compatible = "renesas,usb2-phy-r8a7795", + .data = &rcar_gen3_phy_usb2_ops, + }, + { + .compatible = "renesas,usb2-phy-r8a7796", + .data = &rcar_gen3_phy_usb2_ops, + }, + { + .compatible = "renesas,usb2-phy-r8a77965", + .data = &rcar_gen3_phy_usb2_ops, + }, + { + .compatible = "renesas,rcar-gen3-usb2-phy", + .data = &rcar_gen3_phy_usb2_ops, + }, + { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); @@ -431,6 +453,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) struct rcar_gen3_chan *channel; struct phy_provider *provider; struct resource *res; + const struct phy_ops *phy_usb2_ops; int irq, ret = 0; if (!dev->of_node) { @@ -481,7 +504,11 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) * And then, phy-core will manage runtime pm for this device. */ pm_runtime_enable(dev); - channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops); + phy_usb2_ops = of_device_get_match_data(dev); + if (!phy_usb2_ops) + return -EINVAL; + + channel->phy = devm_phy_create(dev, NULL, phy_usb2_ops); if (IS_ERR(channel->phy)) { dev_err(dev, "Failed to create USB2 PHY\n"); ret = PTR_ERR(channel->phy); -- cgit v1.2.3-55-g7522 From 92fec1c27caa7b6f2a70566625425190e14c2204 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Thu, 11 Apr 2019 19:27:35 +0900 Subject: phy: renesas: rcar-gen3-usb2: Use pdev's device pointer on dev_vdbg() To implement multiple phy instances in the future, this patch uses pdev's device pointer on dev_vdbg() instead of the phy's device pointer. Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Reviewed-by: Fabrizio Castro Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index e3a88b962ee6..9e12d24b800e 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -80,6 +80,7 @@ struct rcar_gen3_chan { void __iomem *base; + struct device *dev; /* platform_device's device */ struct extcon_dev *extcon; struct phy *phy; struct regulator *vbus; @@ -120,7 +121,7 @@ static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) void __iomem *usb2_base = ch->base; u32 val = readl(usb2_base + USB2_COMMCTRL); - dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host); + dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, host); if (host) val &= ~USB2_COMMCTRL_OTG_PERI; else @@ -133,7 +134,7 @@ static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) void __iomem *usb2_base = ch->base; u32 val = readl(usb2_base + USB2_LINECTRL1); - dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); + dev_vdbg(ch->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; @@ -147,7 +148,7 @@ static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) void __iomem *usb2_base = ch->base; u32 val = readl(usb2_base + USB2_ADPCTRL); - dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus); + dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, vbus); if (vbus) val |= USB2_ADPCTRL_DRVVBUS; else @@ -407,7 +408,7 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) irqreturn_t ret = IRQ_NONE; if (status & USB2_OBINT_BITS) { - dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status); + dev_vdbg(ch->dev, "%s: %08x\n", __func__, status); writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); rcar_gen3_device_recognition(ch); ret = IRQ_HANDLED; @@ -526,6 +527,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) platform_set_drvdata(pdev, channel); phy_set_drvdata(channel->phy, channel); + channel->dev = dev; provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(provider)) { -- cgit v1.2.3-55-g7522 From 549b6b55b00558183cef4af2c2bb61d4f2ffe508 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Thu, 11 Apr 2019 19:27:36 +0900 Subject: phy: renesas: rcar-gen3-usb2: enable/disable independent irqs Since the previous code enabled/disabled the irqs both OHCI and EHCI, it is possible to cause unexpected interruptions. To avoid this, this patch creates multiple phy instances from phandle and enables/disables independent irqs by the instances. Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Reviewed-by: Fabrizio Castro Tested-by: Fabrizio Castro Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 185 ++++++++++++++++++++++++++----- 1 file changed, 160 insertions(+), 25 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 9e12d24b800e..1322185a00a2 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -37,11 +37,8 @@ /* 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) +#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) /* For EHCI */ +#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) /* For OHCI */ /* USBCTR */ #define USB2_USBCTR_DIRPD BIT(2) @@ -78,11 +75,35 @@ #define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ #define USB2_ADPCTRL_DRVVBUS BIT(4) +#define NUM_OF_PHYS 4 +enum rcar_gen3_phy_index { + PHY_INDEX_BOTH_HC, + PHY_INDEX_OHCI, + PHY_INDEX_EHCI, + PHY_INDEX_HSUSB +}; + +static const u32 rcar_gen3_int_enable[NUM_OF_PHYS] = { + USB2_INT_ENABLE_USBH_INTB_EN | USB2_INT_ENABLE_USBH_INTA_EN, + USB2_INT_ENABLE_USBH_INTA_EN, + USB2_INT_ENABLE_USBH_INTB_EN, + 0 +}; + +struct rcar_gen3_phy { + struct phy *phy; + struct rcar_gen3_chan *ch; + u32 int_enable_bits; + bool initialized; + bool otg_initialized; + bool powered; +}; + struct rcar_gen3_chan { void __iomem *base; struct device *dev; /* platform_device's device */ struct extcon_dev *extcon; - struct phy *phy; + struct rcar_gen3_phy rphys[NUM_OF_PHYS]; struct regulator *vbus; struct work_struct work; enum usb_dr_mode dr_mode; @@ -250,6 +271,42 @@ static enum phy_mode rcar_gen3_get_phy_mode(struct rcar_gen3_chan *ch) return PHY_MODE_USB_DEVICE; } +static bool rcar_gen3_is_any_rphy_initialized(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].initialized) + return true; + } + + return false; +} + +static bool rcar_gen3_needs_init_otg(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].otg_initialized) + return false; + } + + return true; +} + +static bool rcar_gen3_are_all_rphys_power_off(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].powered) + return false; + } + + return true; +} + static ssize_t role_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -257,7 +314,7 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, bool is_b_device; enum phy_mode cur_mode, new_mode; - if (!ch->is_otg_channel || !ch->phy->init_count) + if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch)) return -EIO; if (!strncmp(buf, "host", strlen("host"))) @@ -295,7 +352,7 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, { struct rcar_gen3_chan *ch = dev_get_drvdata(dev); - if (!ch->is_otg_channel || !ch->phy->init_count) + if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch)) return -EIO; return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : @@ -329,37 +386,62 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) static int rcar_gen3_phy_usb2_init(struct phy *p) { - struct rcar_gen3_chan *channel = phy_get_drvdata(p); + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + u32 val; /* Initialize USB2 part */ - writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE); + val = readl(usb2_base + USB2_INT_ENABLE); + val |= USB2_INT_ENABLE_UCOM_INTEN | rphy->int_enable_bits; + writel(val, 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 otg part */ - if (channel->is_otg_channel) - rcar_gen3_init_otg(channel); + if (channel->is_otg_channel) { + if (rcar_gen3_needs_init_otg(channel)) + rcar_gen3_init_otg(channel); + rphy->otg_initialized = true; + } + + rphy->initialized = true; return 0; } static int rcar_gen3_phy_usb2_exit(struct phy *p) { - struct rcar_gen3_chan *channel = phy_get_drvdata(p); + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + void __iomem *usb2_base = channel->base; + u32 val; + + rphy->initialized = false; - writel(0, channel->base + USB2_INT_ENABLE); + if (channel->is_otg_channel) + rphy->otg_initialized = false; + + val = readl(usb2_base + USB2_INT_ENABLE); + val &= ~rphy->int_enable_bits; + if (!rcar_gen3_is_any_rphy_initialized(channel)) + val &= ~USB2_INT_ENABLE_UCOM_INTEN; + writel(val, 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); + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; u32 val; int ret; + if (!rcar_gen3_are_all_rphys_power_off(channel)) + return 0; + if (channel->vbus) { ret = regulator_enable(channel->vbus); if (ret) @@ -372,14 +454,22 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) val &= ~USB2_USBCTR_PLL_RST; writel(val, usb2_base + USB2_USBCTR); + rphy->powered = true; + return 0; } static int rcar_gen3_phy_usb2_power_off(struct phy *p) { - struct rcar_gen3_chan *channel = phy_get_drvdata(p); + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; int ret = 0; + rphy->powered = false; + + if (!rcar_gen3_are_all_rphys_power_off(channel)) + return 0; + if (channel->vbus) ret = regulator_disable(channel->vbus); @@ -448,6 +538,46 @@ static const unsigned int rcar_gen3_phy_cable[] = { EXTCON_NONE, }; +static struct phy *rcar_gen3_phy_usb2_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + + if (args->args_count == 0) /* For old version dts */ + return ch->rphys[PHY_INDEX_BOTH_HC].phy; + else if (args->args_count > 1) /* Prevent invalid args count */ + return ERR_PTR(-ENODEV); + + if (args->args[0] >= NUM_OF_PHYS) + return ERR_PTR(-ENODEV); + + return ch->rphys[args->args[0]].phy; +} + +static enum usb_dr_mode rcar_gen3_get_dr_mode(struct device_node *np) +{ + enum usb_dr_mode candidate = USB_DR_MODE_UNKNOWN; + int i; + + /* + * If one of device nodes has other dr_mode except UNKNOWN, + * this function returns UNKNOWN. To achieve backward compatibility, + * this loop starts the index as 0. + */ + for (i = 0; i < NUM_OF_PHYS; i++) { + enum usb_dr_mode mode = of_usb_get_dr_mode_by_phy(np, i); + + if (mode != USB_DR_MODE_UNKNOWN) { + if (candidate == USB_DR_MODE_UNKNOWN) + candidate = mode; + else if (candidate != mode) + return USB_DR_MODE_UNKNOWN; + } + } + + return candidate; +} + static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -455,7 +585,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) struct phy_provider *provider; struct resource *res; const struct phy_ops *phy_usb2_ops; - int irq, ret = 0; + int irq, ret = 0, i; if (!dev->of_node) { dev_err(dev, "This driver needs device tree\n"); @@ -481,7 +611,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) dev_err(dev, "No irq handler (%d)\n", irq); } - channel->dr_mode = of_usb_get_dr_mode_by_phy(dev->of_node, 0); + channel->dr_mode = rcar_gen3_get_dr_mode(dev->of_node); if (channel->dr_mode != USB_DR_MODE_UNKNOWN) { int ret; @@ -509,11 +639,17 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) if (!phy_usb2_ops) return -EINVAL; - channel->phy = devm_phy_create(dev, NULL, phy_usb2_ops); - if (IS_ERR(channel->phy)) { - dev_err(dev, "Failed to create USB2 PHY\n"); - ret = PTR_ERR(channel->phy); - goto error; + for (i = 0; i < NUM_OF_PHYS; i++) { + channel->rphys[i].phy = devm_phy_create(dev, NULL, + phy_usb2_ops); + if (IS_ERR(channel->rphys[i].phy)) { + dev_err(dev, "Failed to create USB2 PHY\n"); + ret = PTR_ERR(channel->rphys[i].phy); + goto error; + } + channel->rphys[i].ch = channel; + channel->rphys[i].int_enable_bits = rcar_gen3_int_enable[i]; + phy_set_drvdata(channel->rphys[i].phy, &channel->rphys[i]); } channel->vbus = devm_regulator_get_optional(dev, "vbus"); @@ -526,10 +662,9 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, channel); - phy_set_drvdata(channel->phy, channel); channel->dev = dev; - provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + provider = devm_of_phy_provider_register(dev, rcar_gen3_phy_usb2_xlate); if (IS_ERR(provider)) { dev_err(dev, "Failed to register PHY provider\n"); ret = PTR_ERR(provider); -- cgit v1.2.3-55-g7522 From b603c500d7a5c98089f3d68e4c79297e6aabc4ba Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 10 Apr 2019 14:13:03 +0800 Subject: phy: phy-mtk-tphy: get optional clock by devm_clk_get_optional() Use devm_clk_get_optional() to get optional clock Signed-off-by: Chunfeng Yun Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/mediatek/phy-mtk-tphy.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c index 5b6a470ca145..cb2ed3b25068 100644 --- a/drivers/phy/mediatek/phy-mtk-tphy.c +++ b/drivers/phy/mediatek/phy-mtk-tphy.c @@ -1103,13 +1103,9 @@ static int mtk_tphy_probe(struct platform_device *pdev) } /* it's deprecated, make it optional for backward compatibility */ - tphy->u3phya_ref = devm_clk_get(dev, "u3phya_ref"); - if (IS_ERR(tphy->u3phya_ref)) { - if (PTR_ERR(tphy->u3phya_ref) == -EPROBE_DEFER) - return -EPROBE_DEFER; - - tphy->u3phya_ref = NULL; - } + tphy->u3phya_ref = devm_clk_get_optional(dev, "u3phya_ref"); + if (IS_ERR(tphy->u3phya_ref)) + return PTR_ERR(tphy->u3phya_ref); tphy->src_ref_clk = U3P_REF_CLK; tphy->src_coef = U3P_SLEW_RATE_COEF; -- cgit v1.2.3-55-g7522 From 1039596c90e7443c577bf524feb744879b9f195a Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 10 Apr 2019 14:13:04 +0800 Subject: phy: qcom-qusb2: get optional clock by devm_clk_get_optional() Use devm_clk_get_optional() to get optional clock Cc: Andy Gross Cc: David Brown Cc: Vivek Gautam Signed-off-by: Chunfeng Yun Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qusb2.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index 8fd7ce139772..1cbf1d6f28ce 100644 --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -822,14 +822,9 @@ static int qusb2_phy_probe(struct platform_device *pdev) return ret; } - qphy->iface_clk = devm_clk_get(dev, "iface"); - if (IS_ERR(qphy->iface_clk)) { - ret = PTR_ERR(qphy->iface_clk); - if (ret == -EPROBE_DEFER) - return ret; - qphy->iface_clk = NULL; - dev_dbg(dev, "failed to get iface clk, %d\n", ret); - } + qphy->iface_clk = devm_clk_get_optional(dev, "iface"); + if (IS_ERR(qphy->iface_clk)) + return PTR_ERR(qphy->iface_clk); qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0); if (IS_ERR(qphy->phy_reset)) { -- cgit v1.2.3-55-g7522 From 752d31a3e19251e15ed241c00872ffe4f65bae0b Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 10 Apr 2019 14:13:05 +0800 Subject: phy: socionext: get optional clock by devm_clk_get_optional() Use devm_clk_get_optional() to get optional clock Cc: Kunihiko Hayashi Signed-off-by: Chunfeng Yun Reviewed-by: Kunihiko Hayashi Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/socionext/phy-uniphier-usb3hs.c | 10 +++------- drivers/phy/socionext/phy-uniphier-usb3ss.c | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/socionext/phy-uniphier-usb3hs.c b/drivers/phy/socionext/phy-uniphier-usb3hs.c index b1b048be6166..50f379fc4e06 100644 --- a/drivers/phy/socionext/phy-uniphier-usb3hs.c +++ b/drivers/phy/socionext/phy-uniphier-usb3hs.c @@ -335,13 +335,9 @@ static int uniphier_u3hsphy_probe(struct platform_device *pdev) if (IS_ERR(priv->clk_parent)) return PTR_ERR(priv->clk_parent); - priv->clk_ext = devm_clk_get(dev, "phy-ext"); - if (IS_ERR(priv->clk_ext)) { - if (PTR_ERR(priv->clk_ext) == -ENOENT) - priv->clk_ext = NULL; - else - return PTR_ERR(priv->clk_ext); - } + priv->clk_ext = devm_clk_get_optional(dev, "phy-ext"); + if (IS_ERR(priv->clk_ext)) + return PTR_ERR(priv->clk_ext); priv->rst = devm_reset_control_get_shared(dev, "phy"); if (IS_ERR(priv->rst)) diff --git a/drivers/phy/socionext/phy-uniphier-usb3ss.c b/drivers/phy/socionext/phy-uniphier-usb3ss.c index 4be95679c7d8..ec231e40ef2a 100644 --- a/drivers/phy/socionext/phy-uniphier-usb3ss.c +++ b/drivers/phy/socionext/phy-uniphier-usb3ss.c @@ -238,13 +238,9 @@ static int uniphier_u3ssphy_probe(struct platform_device *pdev) if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); - priv->clk_ext = devm_clk_get(dev, "phy-ext"); - if (IS_ERR(priv->clk_ext)) { - if (PTR_ERR(priv->clk_ext) == -ENOENT) - priv->clk_ext = NULL; - else - return PTR_ERR(priv->clk_ext); - } + priv->clk_ext = devm_clk_get_optional(dev, "phy-ext"); + if (IS_ERR(priv->clk_ext)) + return PTR_ERR(priv->clk_ext); priv->rst = devm_reset_control_get_shared(dev, "phy"); if (IS_ERR(priv->rst)) -- cgit v1.2.3-55-g7522 From 42c7cb71b5c2b014caab7861f2434f3907900d7c Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 10 Apr 2019 14:13:06 +0800 Subject: phy: phy-meson-gxl-usb2: get optional clock by devm_clk_get_optional() Use devm_clk_get_optional() to get optional clock Cc: Martin Blumenstingl Signed-off-by: Chunfeng Yun Acked-by: Martin Blumenstingl Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/amlogic/phy-meson-gxl-usb2.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c index 148ef0bdb9c1..4cbee412f2b0 100644 --- a/drivers/phy/amlogic/phy-meson-gxl-usb2.c +++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c @@ -261,14 +261,9 @@ static int phy_meson_gxl_usb2_probe(struct platform_device *pdev) if (IS_ERR(priv->regmap)) return PTR_ERR(priv->regmap); - priv->clk = devm_clk_get(dev, "phy"); - if (IS_ERR(priv->clk)) { - ret = PTR_ERR(priv->clk); - if (ret == -ENOENT) - priv->clk = NULL; - else - return ret; - } + priv->clk = devm_clk_get_optional(dev, "phy"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); priv->reset = devm_reset_control_get_optional_shared(dev, "phy"); if (IS_ERR(priv->reset)) -- cgit v1.2.3-55-g7522 From fec06b2bc436d1cbc3482becd40f656d46cd22b7 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Fri, 5 Apr 2019 16:38:30 +0530 Subject: phy: core: Add *release* phy_ops invoked when the consumer relinquishes PHY Add a new phy_ops *release* invoked when the consumer relinquishes the PHY using phy_put/devm_phy_put. The initializations done by the PHY driver in of_xlate call back can be can be cleaned up here. Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-core.c | 5 +++++ include/linux/phy/phy.h | 2 ++ 2 files changed, 7 insertions(+) (limited to 'drivers/phy') diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index cb38f6e8614c..b9a4ebf35dd3 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -564,6 +564,11 @@ void phy_put(struct phy *phy) if (!phy || IS_ERR(phy)) return; + mutex_lock(&phy->mutex); + if (phy->ops->release) + phy->ops->release(phy); + mutex_unlock(&phy->mutex); + module_put(phy->ops->owner); put_device(&phy->dev); } diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 3f350e2749fe..ef13aea1d370 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -64,6 +64,7 @@ union phy_configure_opts { * @set_mode: set the mode of the phy * @reset: resetting the phy * @calibrate: calibrate the phy + * @release: ops to be performed while the consumer relinquishes the PHY * @owner: the module owner containing the ops */ struct phy_ops { @@ -105,6 +106,7 @@ struct phy_ops { union phy_configure_opts *opts); int (*reset)(struct phy *phy); int (*calibrate)(struct phy *phy); + void (*release)(struct phy *phy); struct module *owner; }; -- cgit v1.2.3-55-g7522 From 4df614c4ab18ba14892dfa82b88c28203096e951 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Fri, 5 Apr 2019 16:38:31 +0530 Subject: phy: core: Invoke pm_runtime_get_*/pm_runtime_put_* before invoking reset callback PHY drivers may try to access PHY registers in the ->reset() callback. Invoke phy_pm_runtime_get_sync() before invoking the ->reset() callback so that the PHY drivers don't have to enable clocks by themselves before accessing PHY registers. Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-core.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/phy') diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index b9a4ebf35dd3..c147ba843f0b 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -384,10 +384,16 @@ int phy_reset(struct phy *phy) if (!phy || !phy->ops->reset) return 0; + ret = phy_pm_runtime_get_sync(phy); + if (ret < 0 && ret != -ENOTSUPP) + return ret; + mutex_lock(&phy->mutex); ret = phy->ops->reset(phy); mutex_unlock(&phy->mutex); + phy_pm_runtime_put(phy); + return ret; } EXPORT_SYMBOL_GPL(phy_reset); -- cgit v1.2.3-55-g7522 From 71e2f5c5c2249db05dd26b787b56c45f2a890740 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Wed, 17 Apr 2019 11:49:39 +0530 Subject: phy: ti: Add a new SERDES driver for TI's AM654x SoC Add a new SERDES driver for TI's AM654x SoC which configures the SERDES only for PCIe. Support fo USB3 will be added later. SERDES in am654x has three input clocks (left input, externel reference clock and right input) and two output clocks (left output and right output) in addition to a PLL mux clock which the SERDES uses for Clock Multiplier Unit (CMU refclock). The PLL mux clock can select from one of the three input clocks. The right output can select between left input and external reference clock while the left output can select between the right input and external reference clock. The driver has support to select PLL mux and left/right output mux as specified in device tree. [rogerq@ti.com: Fix boot lockup caused by accessing a structure member (hw->init) allocated in stack of probe() and accessed in get_parent] [rogerq@ti.com: Fix "Failed to find the parent" warnings] Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/Kconfig | 12 + drivers/phy/ti/Makefile | 1 + drivers/phy/ti/phy-am654-serdes.c | 624 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 637 insertions(+) create mode 100644 drivers/phy/ti/phy-am654-serdes.c (limited to 'drivers/phy') diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig index 103efc456a12..d658275f6164 100644 --- a/drivers/phy/ti/Kconfig +++ b/drivers/phy/ti/Kconfig @@ -20,6 +20,18 @@ config PHY_DM816X_USB help Enable this for dm816x USB to work. +config PHY_AM654_SERDES + tristate "TI AM654 SERDES support" + depends on OF && ARCH_K3 || COMPILE_TEST + depends on COMMON_CLK + select GENERIC_PHY + select MULTIPLEXER + select REGMAP_MMIO + select MUX_MMIO + help + This option enables support for TI AM654 SerDes PHY used for + PCIe. + config OMAP_CONTROL_PHY tristate "OMAP CONTROL PHY Driver" depends on ARCH_OMAP2PLUS || COMPILE_TEST diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile index bea8f25a137a..bff901eb0ecc 100644 --- a/drivers/phy/ti/Makefile +++ b/drivers/phy/ti/Makefile @@ -6,4 +6,5 @@ obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o +obj-$(CONFIG_PHY_AM654_SERDES) += phy-am654-serdes.o obj-$(CONFIG_PHY_TI_GMII_SEL) += phy-gmii-sel.o diff --git a/drivers/phy/ti/phy-am654-serdes.c b/drivers/phy/ti/phy-am654-serdes.c new file mode 100644 index 000000000000..4817c67abbbb --- /dev/null +++ b/drivers/phy/ti/phy-am654-serdes.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * PCIe SERDES driver for AM654x SoC + * + * Copyright (C) 2018 - 2019 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Kishon Vijay Abraham I + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CMU_R07C 0x7c + +#define COMLANE_R138 0xb38 +#define VERSION 0x70 + +#define COMLANE_R190 0xb90 + +#define COMLANE_R194 0xb94 + +#define SERDES_CTRL 0x1fd0 + +#define WIZ_LANEXCTL_STS 0x1fe0 +#define TX0_DISABLE_STATE 0x4 +#define TX0_SLEEP_STATE 0x5 +#define TX0_SNOOZE_STATE 0x6 +#define TX0_ENABLE_STATE 0x7 + +#define RX0_DISABLE_STATE 0x4 +#define RX0_SLEEP_STATE 0x5 +#define RX0_SNOOZE_STATE 0x6 +#define RX0_ENABLE_STATE 0x7 + +#define WIZ_PLL_CTRL 0x1ff4 +#define PLL_DISABLE_STATE 0x4 +#define PLL_SLEEP_STATE 0x5 +#define PLL_SNOOZE_STATE 0x6 +#define PLL_ENABLE_STATE 0x7 + +#define PLL_LOCK_TIME 100000 /* in microseconds */ +#define SLEEP_TIME 100 /* in microseconds */ + +#define LANE_USB3 0x0 +#define LANE_PCIE0_LANE0 0x1 + +#define LANE_PCIE1_LANE0 0x0 +#define LANE_PCIE0_LANE1 0x1 + +#define SERDES_NUM_CLOCKS 3 + +struct serdes_am654_clk_mux { + struct clk_hw hw; + struct regmap *regmap; + unsigned int reg; + int *table; + u32 mask; + u8 shift; + struct clk_init_data clk_data; +}; + +#define to_serdes_am654_clk_mux(_hw) \ + container_of(_hw, struct serdes_am654_clk_mux, hw) + +static struct regmap_config serdes_am654_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, +}; + +static const struct reg_field cmu_master_cdn_o = REG_FIELD(CMU_R07C, 24, 24); +static const struct reg_field config_version = REG_FIELD(COMLANE_R138, 16, 23); +static const struct reg_field l1_master_cdn_o = REG_FIELD(COMLANE_R190, 9, 9); +static const struct reg_field cmu_ok_i_0 = REG_FIELD(COMLANE_R194, 19, 19); +static const struct reg_field por_en = REG_FIELD(SERDES_CTRL, 29, 29); +static const struct reg_field tx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 29, 31); +static const struct reg_field rx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 13, 15); +static const struct reg_field pll_enable = REG_FIELD(WIZ_PLL_CTRL, 29, 31); +static const struct reg_field pll_ok = REG_FIELD(WIZ_PLL_CTRL, 28, 28); + +struct serdes_am654 { + struct regmap *regmap; + struct regmap_field *cmu_master_cdn_o; + struct regmap_field *config_version; + struct regmap_field *l1_master_cdn_o; + struct regmap_field *cmu_ok_i_0; + struct regmap_field *por_en; + struct regmap_field *tx0_enable; + struct regmap_field *rx0_enable; + struct regmap_field *pll_enable; + struct regmap_field *pll_ok; + + struct device *dev; + struct mux_control *control; + bool busy; + u32 type; + struct device_node *of_node; + struct clk_onecell_data clk_data; + struct clk *clks[SERDES_NUM_CLOCKS]; +}; + +static int serdes_am654_enable_pll(struct serdes_am654 *phy) +{ + int ret; + u32 val; + + ret = regmap_field_write(phy->pll_enable, PLL_ENABLE_STATE); + if (ret) + return ret; + + return regmap_field_read_poll_timeout(phy->pll_ok, val, val, 1000, + PLL_LOCK_TIME); +} + +static void serdes_am654_disable_pll(struct serdes_am654 *phy) +{ + struct device *dev = phy->dev; + int ret; + + ret = regmap_field_write(phy->pll_enable, PLL_DISABLE_STATE); + if (ret) + dev_err(dev, "Failed to disable PLL\n"); +} + +static int serdes_am654_enable_txrx(struct serdes_am654 *phy) +{ + int ret; + + /* Enable TX */ + ret = regmap_field_write(phy->tx0_enable, TX0_ENABLE_STATE); + if (ret) + return ret; + + /* Enable RX */ + ret = regmap_field_write(phy->rx0_enable, RX0_ENABLE_STATE); + if (ret) + return ret; + + return 0; +} + +static int serdes_am654_disable_txrx(struct serdes_am654 *phy) +{ + int ret; + + /* Disable TX */ + ret = regmap_field_write(phy->tx0_enable, TX0_DISABLE_STATE); + if (ret) + return ret; + + /* Disable RX */ + ret = regmap_field_write(phy->rx0_enable, RX0_DISABLE_STATE); + if (ret) + return ret; + + return 0; +} + +static int serdes_am654_power_on(struct phy *x) +{ + struct serdes_am654 *phy = phy_get_drvdata(x); + struct device *dev = phy->dev; + int ret; + u32 val; + + ret = serdes_am654_enable_pll(phy); + if (ret) { + dev_err(dev, "Failed to enable PLL\n"); + return ret; + } + + ret = serdes_am654_enable_txrx(phy); + if (ret) { + dev_err(dev, "Failed to enable TX RX\n"); + return ret; + } + + return regmap_field_read_poll_timeout(phy->cmu_ok_i_0, val, val, + SLEEP_TIME, PLL_LOCK_TIME); +} + +static int serdes_am654_power_off(struct phy *x) +{ + struct serdes_am654 *phy = phy_get_drvdata(x); + + serdes_am654_disable_txrx(phy); + serdes_am654_disable_pll(phy); + + return 0; +} + +static int serdes_am654_init(struct phy *x) +{ + struct serdes_am654 *phy = phy_get_drvdata(x); + int ret; + + ret = regmap_field_write(phy->config_version, VERSION); + if (ret) + return ret; + + ret = regmap_field_write(phy->cmu_master_cdn_o, 0x1); + if (ret) + return ret; + + ret = regmap_field_write(phy->l1_master_cdn_o, 0x1); + if (ret) + return ret; + + return 0; +} + +static int serdes_am654_reset(struct phy *x) +{ + struct serdes_am654 *phy = phy_get_drvdata(x); + int ret; + + ret = regmap_field_write(phy->por_en, 0x1); + if (ret) + return ret; + + mdelay(1); + + ret = regmap_field_write(phy->por_en, 0x0); + if (ret) + return ret; + + return 0; +} + +static void serdes_am654_release(struct phy *x) +{ + struct serdes_am654 *phy = phy_get_drvdata(x); + + phy->type = PHY_NONE; + phy->busy = false; + mux_control_deselect(phy->control); +} + +struct phy *serdes_am654_xlate(struct device *dev, struct of_phandle_args + *args) +{ + struct serdes_am654 *am654_phy; + struct phy *phy; + int ret; + + phy = of_phy_simple_xlate(dev, args); + if (IS_ERR(phy)) + return phy; + + am654_phy = phy_get_drvdata(phy); + if (am654_phy->busy) + return ERR_PTR(-EBUSY); + + ret = mux_control_select(am654_phy->control, args->args[1]); + if (ret) { + dev_err(dev, "Failed to select SERDES Lane Function\n"); + return ERR_PTR(ret); + } + + am654_phy->busy = true; + am654_phy->type = args->args[0]; + + return phy; +} + +static const struct phy_ops ops = { + .reset = serdes_am654_reset, + .init = serdes_am654_init, + .power_on = serdes_am654_power_on, + .power_off = serdes_am654_power_off, + .release = serdes_am654_release, + .owner = THIS_MODULE, +}; + +static u8 serdes_am654_clk_mux_get_parent(struct clk_hw *hw) +{ + struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw); + unsigned int num_parents = clk_hw_get_num_parents(hw); + struct regmap *regmap = mux->regmap; + unsigned int reg = mux->reg; + unsigned int val; + int i; + + regmap_read(regmap, reg, &val); + val >>= mux->shift; + val &= mux->mask; + + for (i = 0; i < num_parents; i++) + if (mux->table[i] == val) + return i; + + /* + * No parent? This should never happen! + * Verify if we set a valid parent in serdes_am654_clk_register() + */ + WARN(1, "Failed to find the parent of %s clock\n", hw->init->name); + + /* Make the parent lookup to fail */ + return num_parents; +} + +static int serdes_am654_clk_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw); + struct regmap *regmap = mux->regmap; + unsigned int reg = mux->reg; + int val; + int ret; + + val = mux->table[index]; + + if (val == -1) + return -EINVAL; + + val <<= mux->shift; + ret = regmap_update_bits(regmap, reg, mux->mask << mux->shift, val); + + return ret; +} + +static const struct clk_ops serdes_am654_clk_mux_ops = { + .set_parent = serdes_am654_clk_mux_set_parent, + .get_parent = serdes_am654_clk_mux_get_parent, +}; + +static int mux_table[SERDES_NUM_CLOCKS][3] = { + /* + * The entries represent values for selecting between + * {left input, external reference clock, right input} + * Only one of Left Output or Right Output should be used since + * both left and right output clock uses the same bits and modifying + * one clock will impact the other. + */ + { BIT(2), 0, BIT(0) }, /* Mux of CMU refclk */ + { -1, BIT(3), BIT(1) }, /* Mux of Left Output */ + { BIT(1), BIT(3) | BIT(1), -1 }, /* Mux of Right Output */ +}; + +static int mux_mask[SERDES_NUM_CLOCKS] = { 0x5, 0xa, 0xa }; + +static int serdes_am654_clk_register(struct serdes_am654 *am654_phy, + const char *clock_name, int clock_num) +{ + struct device_node *node = am654_phy->of_node; + struct device *dev = am654_phy->dev; + struct serdes_am654_clk_mux *mux; + struct device_node *regmap_node; + const char **parent_names; + struct clk_init_data *init; + unsigned int num_parents; + struct regmap *regmap; + const __be32 *addr; + unsigned int reg; + struct clk *clk; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return -ENOMEM; + + init = &mux->clk_data; + + regmap_node = of_parse_phandle(node, "ti,serdes-clk", 0); + of_node_put(regmap_node); + if (!regmap_node) { + dev_err(dev, "Fail to get serdes-clk node\n"); + return -ENODEV; + } + + regmap = syscon_node_to_regmap(regmap_node->parent); + if (IS_ERR(regmap)) { + dev_err(dev, "Fail to get Syscon regmap\n"); + return PTR_ERR(regmap); + } + + num_parents = of_clk_get_parent_count(node); + if (num_parents < 2) { + dev_err(dev, "SERDES clock must have parents\n"); + return -EINVAL; + } + + parent_names = devm_kzalloc(dev, (sizeof(char *) * num_parents), + GFP_KERNEL); + if (!parent_names) + return -ENOMEM; + + of_clk_parent_fill(node, parent_names, num_parents); + + addr = of_get_address(regmap_node, 0, NULL, NULL); + if (!addr) + return -EINVAL; + + reg = be32_to_cpu(*addr); + + init->ops = &serdes_am654_clk_mux_ops; + init->flags = CLK_SET_RATE_NO_REPARENT; + init->parent_names = parent_names; + init->num_parents = num_parents; + init->name = clock_name; + + mux->table = mux_table[clock_num]; + mux->regmap = regmap; + mux->reg = reg; + mux->shift = 4; + mux->mask = mux_mask[clock_num]; + mux->hw.init = init; + + /* + * setup a sane default so get_parent() call evaluates + * to a valid parent. Index 1 is the safest choice as + * the default as it is valid value for all of serdes's + * output clocks. + */ + serdes_am654_clk_mux_set_parent(&mux->hw, 1); + clk = devm_clk_register(dev, &mux->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + am654_phy->clks[clock_num] = clk; + + return 0; +} + +static const struct of_device_id serdes_am654_id_table[] = { + { + .compatible = "ti,phy-am654-serdes", + }, + {} +}; +MODULE_DEVICE_TABLE(of, serdes_am654_id_table); + +static int serdes_am654_regfield_init(struct serdes_am654 *am654_phy) +{ + struct regmap *regmap = am654_phy->regmap; + struct device *dev = am654_phy->dev; + + am654_phy->cmu_master_cdn_o = devm_regmap_field_alloc(dev, regmap, + cmu_master_cdn_o); + if (IS_ERR(am654_phy->cmu_master_cdn_o)) { + dev_err(dev, "CMU_MASTER_CDN_O reg field init failed\n"); + return PTR_ERR(am654_phy->cmu_master_cdn_o); + } + + am654_phy->config_version = devm_regmap_field_alloc(dev, regmap, + config_version); + if (IS_ERR(am654_phy->config_version)) { + dev_err(dev, "CONFIG_VERSION reg field init failed\n"); + return PTR_ERR(am654_phy->config_version); + } + + am654_phy->l1_master_cdn_o = devm_regmap_field_alloc(dev, regmap, + l1_master_cdn_o); + if (IS_ERR(am654_phy->l1_master_cdn_o)) { + dev_err(dev, "L1_MASTER_CDN_O reg field init failed\n"); + return PTR_ERR(am654_phy->l1_master_cdn_o); + } + + am654_phy->cmu_ok_i_0 = devm_regmap_field_alloc(dev, regmap, + cmu_ok_i_0); + if (IS_ERR(am654_phy->cmu_ok_i_0)) { + dev_err(dev, "CMU_OK_I_0 reg field init failed\n"); + return PTR_ERR(am654_phy->cmu_ok_i_0); + } + + am654_phy->por_en = devm_regmap_field_alloc(dev, regmap, por_en); + if (IS_ERR(am654_phy->por_en)) { + dev_err(dev, "POR_EN reg field init failed\n"); + return PTR_ERR(am654_phy->por_en); + } + + am654_phy->tx0_enable = devm_regmap_field_alloc(dev, regmap, + tx0_enable); + if (IS_ERR(am654_phy->tx0_enable)) { + dev_err(dev, "TX0_ENABLE reg field init failed\n"); + return PTR_ERR(am654_phy->tx0_enable); + } + + am654_phy->rx0_enable = devm_regmap_field_alloc(dev, regmap, + rx0_enable); + if (IS_ERR(am654_phy->rx0_enable)) { + dev_err(dev, "RX0_ENABLE reg field init failed\n"); + return PTR_ERR(am654_phy->rx0_enable); + } + + am654_phy->pll_enable = devm_regmap_field_alloc(dev, regmap, + pll_enable); + if (IS_ERR(am654_phy->pll_enable)) { + dev_err(dev, "PLL_ENABLE reg field init failed\n"); + return PTR_ERR(am654_phy->pll_enable); + } + + am654_phy->pll_ok = devm_regmap_field_alloc(dev, regmap, pll_ok); + if (IS_ERR(am654_phy->pll_ok)) { + dev_err(dev, "PLL_OK reg field init failed\n"); + return PTR_ERR(am654_phy->pll_ok); + } + + return 0; +} + +static int serdes_am654_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct clk_onecell_data *clk_data; + struct serdes_am654 *am654_phy; + struct mux_control *control; + const char *clock_name; + struct regmap *regmap; + void __iomem *base; + struct phy *phy; + int ret; + int i; + + am654_phy = devm_kzalloc(dev, sizeof(*am654_phy), GFP_KERNEL); + if (!am654_phy) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(dev, base, &serdes_am654_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to initialize regmap\n"); + return PTR_ERR(regmap); + } + + control = devm_mux_control_get(dev, NULL); + if (IS_ERR(control)) + return PTR_ERR(control); + + am654_phy->dev = dev; + am654_phy->of_node = node; + am654_phy->regmap = regmap; + am654_phy->control = control; + am654_phy->type = PHY_NONE; + + ret = serdes_am654_regfield_init(am654_phy); + if (ret) { + dev_err(dev, "Failed to initialize regfields\n"); + return ret; + } + + platform_set_drvdata(pdev, am654_phy); + + for (i = 0; i < SERDES_NUM_CLOCKS; i++) { + ret = of_property_read_string_index(node, "clock-output-names", + i, &clock_name); + if (ret) { + dev_err(dev, "Failed to get clock name\n"); + return ret; + } + + ret = serdes_am654_clk_register(am654_phy, clock_name, i); + if (ret) { + dev_err(dev, "Failed to initialize clock %s\n", + clock_name); + return ret; + } + } + + clk_data = &am654_phy->clk_data; + clk_data->clks = am654_phy->clks; + clk_data->clk_num = SERDES_NUM_CLOCKS; + ret = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data); + if (ret) + return ret; + + pm_runtime_enable(dev); + + phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, am654_phy); + phy_provider = devm_of_phy_provider_register(dev, serdes_am654_xlate); + if (IS_ERR(phy_provider)) { + ret = PTR_ERR(phy_provider); + goto clk_err; + } + + return 0; + +clk_err: + of_clk_del_provider(node); + + return ret; +} + +static int serdes_am654_remove(struct platform_device *pdev) +{ + struct serdes_am654 *am654_phy = platform_get_drvdata(pdev); + struct device_node *node = am654_phy->of_node; + + pm_runtime_disable(&pdev->dev); + of_clk_del_provider(node); + + return 0; +} + +static struct platform_driver serdes_am654_driver = { + .probe = serdes_am654_probe, + .remove = serdes_am654_remove, + .driver = { + .name = "phy-am654", + .of_match_table = serdes_am654_id_table, + }, +}; +module_platform_driver(serdes_am654_driver); + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TI AM654x SERDES driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From 7e7b8ca66191c5dde9f6521ff8a0180834efa628 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Fri, 5 Apr 2019 16:38:34 +0530 Subject: phy: ti: am654-serdes: Support all clksel values Add support to select all 16 CLKSEL combinations that are shown in "SerDes Reference Clock Distribution" in AM65 TRM. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-am654-serdes.c | 132 ++++++++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 49 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/ti/phy-am654-serdes.c b/drivers/phy/ti/phy-am654-serdes.c index 4817c67abbbb..d3769200cb9b 100644 --- a/drivers/phy/ti/phy-am654-serdes.c +++ b/drivers/phy/ti/phy-am654-serdes.c @@ -58,13 +58,14 @@ #define SERDES_NUM_CLOCKS 3 +#define AM654_SERDES_CTRL_CLKSEL_MASK GENMASK(7, 4) +#define AM654_SERDES_CTRL_CLKSEL_SHIFT 4 + struct serdes_am654_clk_mux { struct clk_hw hw; struct regmap *regmap; unsigned int reg; - int *table; - u32 mask; - u8 shift; + int clk_id; struct clk_init_data clk_data; }; @@ -282,31 +283,52 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +#define SERDES_NUM_MUX_COMBINATIONS 16 + +#define LICLK 0 +#define EXT_REFCLK 1 +#define RICLK 2 + +static const int +serdes_am654_mux_table[SERDES_NUM_MUX_COMBINATIONS][SERDES_NUM_CLOCKS] = { + /* + * Each combination maps to one of + * "Figure 12-1986. SerDes Reference Clock Distribution" + * in TRM. + */ + /* Parent of CMU refclk, Left output, Right output + * either of EXT_REFCLK, LICLK, RICLK + */ + { EXT_REFCLK, EXT_REFCLK, EXT_REFCLK }, /* 0000 */ + { RICLK, EXT_REFCLK, EXT_REFCLK }, /* 0001 */ + { EXT_REFCLK, RICLK, LICLK }, /* 0010 */ + { RICLK, RICLK, EXT_REFCLK }, /* 0011 */ + { LICLK, EXT_REFCLK, EXT_REFCLK }, /* 0100 */ + { EXT_REFCLK, EXT_REFCLK, EXT_REFCLK }, /* 0101 */ + { LICLK, RICLK, LICLK }, /* 0110 */ + { EXT_REFCLK, RICLK, LICLK }, /* 0111 */ + { EXT_REFCLK, EXT_REFCLK, LICLK }, /* 1000 */ + { RICLK, EXT_REFCLK, LICLK }, /* 1001 */ + { EXT_REFCLK, RICLK, EXT_REFCLK }, /* 1010 */ + { RICLK, RICLK, EXT_REFCLK }, /* 1011 */ + { LICLK, EXT_REFCLK, LICLK }, /* 1100 */ + { EXT_REFCLK, EXT_REFCLK, LICLK }, /* 1101 */ + { LICLK, RICLK, EXT_REFCLK }, /* 1110 */ + { EXT_REFCLK, RICLK, EXT_REFCLK }, /* 1111 */ +}; + static u8 serdes_am654_clk_mux_get_parent(struct clk_hw *hw) { struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw); - unsigned int num_parents = clk_hw_get_num_parents(hw); struct regmap *regmap = mux->regmap; unsigned int reg = mux->reg; unsigned int val; - int i; regmap_read(regmap, reg, &val); - val >>= mux->shift; - val &= mux->mask; - - for (i = 0; i < num_parents; i++) - if (mux->table[i] == val) - return i; - - /* - * No parent? This should never happen! - * Verify if we set a valid parent in serdes_am654_clk_register() - */ - WARN(1, "Failed to find the parent of %s clock\n", hw->init->name); + val &= AM654_SERDES_CTRL_CLKSEL_MASK; + val >>= AM654_SERDES_CTRL_CLKSEL_SHIFT; - /* Make the parent lookup to fail */ - return num_parents; + return serdes_am654_mux_table[val][mux->clk_id]; } static int serdes_am654_clk_mux_set_parent(struct clk_hw *hw, u8 index) @@ -314,16 +336,52 @@ static int serdes_am654_clk_mux_set_parent(struct clk_hw *hw, u8 index) struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw); struct regmap *regmap = mux->regmap; unsigned int reg = mux->reg; - int val; + int clk_id = mux->clk_id; + int parents[SERDES_NUM_CLOCKS]; + const int *p; + u32 val; + int found, i; int ret; - val = mux->table[index]; + /* get existing setting */ + regmap_read(regmap, reg, &val); + val &= AM654_SERDES_CTRL_CLKSEL_MASK; + val >>= AM654_SERDES_CTRL_CLKSEL_SHIFT; + + for (i = 0; i < SERDES_NUM_CLOCKS; i++) + parents[i] = serdes_am654_mux_table[val][i]; + + /* change parent of this clock. others left intact */ + parents[clk_id] = index; + + /* Find the match */ + for (val = 0; val < SERDES_NUM_MUX_COMBINATIONS; val++) { + p = serdes_am654_mux_table[val]; + found = 1; + for (i = 0; i < SERDES_NUM_CLOCKS; i++) { + if (parents[i] != p[i]) { + found = 0; + break; + } + } + + if (found) + break; + } - if (val == -1) + if (!found) { + /* + * This can never happen, unless we missed + * a valid combination in serdes_am654_mux_table. + */ + WARN(1, "Failed to find the parent of %s clock\n", + hw->init->name); return -EINVAL; + } - val <<= mux->shift; - ret = regmap_update_bits(regmap, reg, mux->mask << mux->shift, val); + val <<= AM654_SERDES_CTRL_CLKSEL_SHIFT; + ret = regmap_update_bits(regmap, reg, AM654_SERDES_CTRL_CLKSEL_MASK, + val); return ret; } @@ -333,21 +391,6 @@ static const struct clk_ops serdes_am654_clk_mux_ops = { .get_parent = serdes_am654_clk_mux_get_parent, }; -static int mux_table[SERDES_NUM_CLOCKS][3] = { - /* - * The entries represent values for selecting between - * {left input, external reference clock, right input} - * Only one of Left Output or Right Output should be used since - * both left and right output clock uses the same bits and modifying - * one clock will impact the other. - */ - { BIT(2), 0, BIT(0) }, /* Mux of CMU refclk */ - { -1, BIT(3), BIT(1) }, /* Mux of Left Output */ - { BIT(1), BIT(3) | BIT(1), -1 }, /* Mux of Right Output */ -}; - -static int mux_mask[SERDES_NUM_CLOCKS] = { 0x5, 0xa, 0xa }; - static int serdes_am654_clk_register(struct serdes_am654 *am654_phy, const char *clock_name, int clock_num) { @@ -407,20 +450,11 @@ static int serdes_am654_clk_register(struct serdes_am654 *am654_phy, init->num_parents = num_parents; init->name = clock_name; - mux->table = mux_table[clock_num]; mux->regmap = regmap; mux->reg = reg; - mux->shift = 4; - mux->mask = mux_mask[clock_num]; + mux->clk_id = clock_num; mux->hw.init = init; - /* - * setup a sane default so get_parent() call evaluates - * to a valid parent. Index 1 is the safest choice as - * the default as it is valid value for all of serdes's - * output clocks. - */ - serdes_am654_clk_mux_set_parent(&mux->hw, 1); clk = devm_clk_register(dev, &mux->hw); if (IS_ERR(clk)) return PTR_ERR(clk); -- cgit v1.2.3-55-g7522 From 73d7ec899bd8b8aa5ab14781fe5962139840a76e Mon Sep 17 00:00:00 2001 From: Marc Gonzalez Date: Tue, 9 Apr 2019 14:48:22 +0200 Subject: phy: qcom-qmp: Add msm8998 PCIe QMP PHY support Documentation for this PHY, and the proper configuration settings, is *not* publicly available. Therefore the initialization sequence is copied wholesale from downstream: https://source.codeaurora.org/quic/la/kernel/msm-4.4/tree/arch/arm/boot/dts/qcom/msm8998-v2.dtsi?h=LE.UM.1.3.r3.25#n372 Reviewed-by: Vivek Gautam Signed-off-by: Marc Gonzalez Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 110 ++++++++++++++++++++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-qmp.h | 12 ++++ 2 files changed, 122 insertions(+) (limited to 'drivers/phy') diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 3b01ebf76f66..cd91b4179b10 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -242,6 +242,88 @@ static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = { QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e), }; +static const struct qmp_phy_init_tbl msm8998_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER2, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_EP_DIV, 0x19), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_ENABLE1, 0x90), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x0d), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x08), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x34), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x33), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x07), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BG_TIMER, 0x09), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x40), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x7e), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x15), +}; + +static const struct qmp_phy_init_tbl msm8998_pcie_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0x06), +}; + +static const struct qmp_phy_init_tbl msm8998_pcie_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_ENABLES, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x1a), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN_HALF, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_INTERFACE_MODE, 0x40), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_PI_CONTROLS, 0x71), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW, 0x40), +}; + +static const struct qmp_phy_init_tbl msm8998_pcie_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_V3_PCS_ENDPOINT_REFCLK_DRIVE, 0x04), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_OSC_DTCT_ACTIONS, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x01), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB, 0x20), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PLL_LOCK_CHK_DLY_TIME, 0x73), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0x99), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_SIGDET_CNTRL, 0x03), +}; + static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = { QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14), QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), @@ -1149,6 +1231,31 @@ static const struct qmp_phy_cfg sdm845_ufsphy_cfg = { .no_pcs_sw_reset = true, }; +static const struct qmp_phy_cfg msm8998_pciephy_cfg = { + .type = PHY_TYPE_PCIE, + .nlanes = 1, + + .serdes_tbl = msm8998_pcie_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(msm8998_pcie_serdes_tbl), + .tx_tbl = msm8998_pcie_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(msm8998_pcie_tx_tbl), + .rx_tbl = msm8998_pcie_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(msm8998_pcie_rx_tbl), + .pcs_tbl = msm8998_pcie_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(msm8998_pcie_pcs_tbl), + .clk_list = msm8996_phy_clk_l, + .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), + .reset_list = ipq8074_pciephy_reset_l, + .num_resets = ARRAY_SIZE(ipq8074_pciephy_reset_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .regs = pciephy_regs_layout, + + .start_ctrl = SERDES_START | PCS_START, + .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, + .mask_com_pcs_ready = PCS_READY, +}; + static const struct qmp_phy_cfg msm8998_usb3phy_cfg = { .type = PHY_TYPE_USB3, .nlanes = 1, @@ -1870,6 +1977,9 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { }, { .compatible = "qcom,msm8996-qmp-usb3-phy", .data = &msm8996_usb3phy_cfg, + }, { + .compatible = "qcom,msm8998-qmp-pcie-phy", + .data = &msm8998_pciephy_cfg, }, { .compatible = "qcom,msm8998-qmp-ufs-phy", .data = &sdm845_ufsphy_cfg, diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h index a1b6cdee9a08..335ea5d7ef40 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp.h @@ -241,6 +241,7 @@ #define QSERDES_V3_RX_RX_BAND 0x110 #define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c #define QSERDES_V3_RX_RX_MODE_00 0x164 +#define QSERDES_V3_RX_RX_MODE_01 0x168 /* Only for QMP V3 PHY - PCS registers */ #define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004 @@ -280,6 +281,7 @@ #define QPHY_V3_PCS_TSYNC_RSYNC_TIME 0x08c #define QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK 0x0a0 #define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK 0x0a4 +#define QPHY_V3_PCS_PLL_LOCK_CHK_DLY_TIME 0x0a8 #define QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK 0x0b0 #define QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME 0x0b8 #define QPHY_V3_PCS_RXEQTRAINING_RUN_TIME 0x0bc @@ -292,13 +294,23 @@ #define QPHY_V3_PCS_RX_MIN_HIBERN8_TIME 0x138 #define QPHY_V3_PCS_RX_SIGDET_CTRL1 0x13c #define QPHY_V3_PCS_RX_SIGDET_CTRL2 0x140 +#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1a8 +#define QPHY_V3_PCS_OSC_DTCT_ACTIONS 0x1ac +#define QPHY_V3_PCS_SIGDET_CNTRL 0x1b0 #define QPHY_V3_PCS_TX_MID_TERM_CTRL1 0x1bc #define QPHY_V3_PCS_MULTI_LANE_CTRL1 0x1c4 #define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8 +#define QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB 0x1dc +#define QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1e0 #define QPHY_V3_PCS_REFGEN_REQ_CONFIG1 0x20c #define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210 /* Only for QMP V3 PHY - PCS_MISC registers */ #define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c +#define QPHY_V3_PCS_MISC_OSC_DTCT_CONFIG2 0x2c +#define QPHY_V3_PCS_MISC_PCIE_INT_AUX_CLK_CONFIG1 0x44 +#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG2 0x54 +#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG4 0x5c +#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG5 0x60 #endif -- cgit v1.2.3-55-g7522 From eeda879bb54f46c01d7014602aecaae3a2bbeb5d Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Thu, 4 Apr 2019 18:41:48 +0200 Subject: phy: fsl-imx8mq-usb: add support for VBUS power control This adds support to the PHY driver to power up/down the VBUS voltage rail at the appropriate times. Signed-off-by: Lucas Stach Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers/phy') diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c index d6ea5ce8afa5..0c4833da7be0 100644 --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -6,6 +6,7 @@ #include #include #include +#include #define PHY_CTRL0 0x0 #define PHY_CTRL0_REF_SSP_EN BIT(2) @@ -24,6 +25,7 @@ struct imx8mq_usb_phy { struct phy *phy; struct clk *clk; void __iomem *base; + struct regulator *vbus; }; static int imx8mq_usb_phy_init(struct phy *phy) @@ -55,6 +57,11 @@ static int imx8mq_usb_phy_init(struct phy *phy) static int imx8mq_phy_power_on(struct phy *phy) { struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + int ret; + + ret = regulator_enable(imx_phy->vbus); + if (ret) + return ret; return clk_prepare_enable(imx_phy->clk); } @@ -64,6 +71,7 @@ static int imx8mq_phy_power_off(struct phy *phy) struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); clk_disable_unprepare(imx_phy->clk); + regulator_disable(imx_phy->vbus); return 0; } @@ -101,6 +109,10 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev) if (IS_ERR(imx_phy->phy)) return PTR_ERR(imx_phy->phy); + imx_phy->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(imx_phy->vbus)) + return PTR_ERR(imx_phy->vbus); + phy_set_drvdata(imx_phy->phy, imx_phy); phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); -- cgit v1.2.3-55-g7522 From 61c67bfaaae6d555588d39f235aae64b6d7b28de Mon Sep 17 00:00:00 2001 From: Kavya Sree Kotagiri Date: Mon, 25 Mar 2019 10:13:33 +0000 Subject: phy: ocelot-serdes: Add support for SERDES6G muxing Adding support for SERDES6G muxing required for QSGMII mode of operation. Signed-off-by: Kavya Sree Kotagiri Signed-off-by: Quentin Schulz Signed-off-by: Steen Hegelund Co-developed-by: Quentin Schulz Co-developed-by: Steen Hegelund Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/mscc/phy-ocelot-serdes.c | 240 ++++++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 2 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/mscc/phy-ocelot-serdes.c b/drivers/phy/mscc/phy-ocelot-serdes.c index 77c46f639fbf..76f596365176 100644 --- a/drivers/phy/mscc/phy-ocelot-serdes.c +++ b/drivers/phy/mscc/phy-ocelot-serdes.c @@ -31,6 +31,238 @@ struct serdes_macro { struct serdes_ctrl *ctrl; }; +#define MCB_S6G_CFG_TIMEOUT 50 + +static int __serdes_write_mcb_s6g(struct regmap *regmap, u8 macro, u32 op) +{ + unsigned int regval = 0; + + regmap_write(regmap, HSIO_MCB_S6G_ADDR_CFG, op | + HSIO_MCB_S6G_ADDR_CFG_SERDES6G_ADDR(BIT(macro))); + + return regmap_read_poll_timeout(regmap, HSIO_MCB_S6G_ADDR_CFG, regval, + (regval & op) != op, 100, + MCB_S6G_CFG_TIMEOUT * 1000); +} + +static int serdes_commit_mcb_s6g(struct regmap *regmap, u8 macro) +{ + return __serdes_write_mcb_s6g(regmap, macro, + HSIO_MCB_S6G_ADDR_CFG_SERDES6G_WR_ONE_SHOT); +} + +static int serdes_update_mcb_s6g(struct regmap *regmap, u8 macro) +{ + return __serdes_write_mcb_s6g(regmap, macro, + HSIO_MCB_S6G_ADDR_CFG_SERDES6G_RD_ONE_SHOT); +} + +static int serdes_init_s6g(struct regmap *regmap, u8 serdes, int mode) +{ + u32 pll_fsm_ctrl_data; + u32 ob_ena1v_mode; + u32 des_bw_ana; + u32 ob_ena_cas; + u32 if_mode; + u32 ob_lev; + u32 qrate; + int ret; + + if (mode == PHY_INTERFACE_MODE_QSGMII) { + pll_fsm_ctrl_data = 120; + ob_ena1v_mode = 0; + ob_ena_cas = 0; + des_bw_ana = 5; + ob_lev = 24; + if_mode = 3; + qrate = 0; + } else { + pll_fsm_ctrl_data = 60; + ob_ena1v_mode = 1; + ob_ena_cas = 2; + des_bw_ana = 3; + ob_lev = 48; + if_mode = 1; + qrate = 1; + } + + ret = serdes_update_mcb_s6g(regmap, serdes); + if (ret) + return ret; + + /* Test pattern */ + + regmap_update_bits(regmap, HSIO_S6G_COMMON_CFG, + HSIO_S6G_COMMON_CFG_SYS_RST, 0); + + regmap_update_bits(regmap, HSIO_S6G_PLL_CFG, + HSIO_S6G_PLL_CFG_PLL_FSM_ENA, 0); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG, + HSIO_S6G_IB_CFG_IB_SIG_DET_ENA | + HSIO_S6G_IB_CFG_IB_REG_ENA | + HSIO_S6G_IB_CFG_IB_SAM_ENA | + HSIO_S6G_IB_CFG_IB_EQZ_ENA | + HSIO_S6G_IB_CFG_IB_CONCUR | + HSIO_S6G_IB_CFG_IB_CAL_ENA, + HSIO_S6G_IB_CFG_IB_SIG_DET_ENA | + HSIO_S6G_IB_CFG_IB_REG_ENA | + HSIO_S6G_IB_CFG_IB_SAM_ENA | + HSIO_S6G_IB_CFG_IB_EQZ_ENA | + HSIO_S6G_IB_CFG_IB_CONCUR); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG1, + HSIO_S6G_IB_CFG1_IB_FRC_OFFSET | + HSIO_S6G_IB_CFG1_IB_FRC_LP | + HSIO_S6G_IB_CFG1_IB_FRC_MID | + HSIO_S6G_IB_CFG1_IB_FRC_HP | + HSIO_S6G_IB_CFG1_IB_FILT_OFFSET | + HSIO_S6G_IB_CFG1_IB_FILT_LP | + HSIO_S6G_IB_CFG1_IB_FILT_MID | + HSIO_S6G_IB_CFG1_IB_FILT_HP, + HSIO_S6G_IB_CFG1_IB_FILT_OFFSET | + HSIO_S6G_IB_CFG1_IB_FILT_HP | + HSIO_S6G_IB_CFG1_IB_FILT_LP | + HSIO_S6G_IB_CFG1_IB_FILT_MID); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG2, + HSIO_S6G_IB_CFG2_IB_UREG_M, + HSIO_S6G_IB_CFG2_IB_UREG(4)); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG3, + HSIO_S6G_IB_CFG3_IB_INI_OFFSET_M | + HSIO_S6G_IB_CFG3_IB_INI_LP_M | + HSIO_S6G_IB_CFG3_IB_INI_MID_M | + HSIO_S6G_IB_CFG3_IB_INI_HP_M, + HSIO_S6G_IB_CFG3_IB_INI_OFFSET(31) | + HSIO_S6G_IB_CFG3_IB_INI_LP(1) | + HSIO_S6G_IB_CFG3_IB_INI_MID(31) | + HSIO_S6G_IB_CFG3_IB_INI_HP(0)); + + regmap_update_bits(regmap, HSIO_S6G_MISC_CFG, + HSIO_S6G_MISC_CFG_LANE_RST, + HSIO_S6G_MISC_CFG_LANE_RST); + + ret = serdes_commit_mcb_s6g(regmap, serdes); + if (ret) + return ret; + + /* OB + DES + IB + SER CFG */ + regmap_update_bits(regmap, HSIO_S6G_OB_CFG, + HSIO_S6G_OB_CFG_OB_IDLE | + HSIO_S6G_OB_CFG_OB_ENA1V_MODE | + HSIO_S6G_OB_CFG_OB_POST0_M | + HSIO_S6G_OB_CFG_OB_PREC_M, + (ob_ena1v_mode ? HSIO_S6G_OB_CFG_OB_ENA1V_MODE : 0) | + HSIO_S6G_OB_CFG_OB_POST0(0) | + HSIO_S6G_OB_CFG_OB_PREC(0)); + + regmap_update_bits(regmap, HSIO_S6G_OB_CFG1, + HSIO_S6G_OB_CFG1_OB_ENA_CAS_M | + HSIO_S6G_OB_CFG1_OB_LEV_M, + HSIO_S6G_OB_CFG1_OB_LEV(ob_lev) | + HSIO_S6G_OB_CFG1_OB_ENA_CAS(ob_ena_cas)); + + regmap_update_bits(regmap, HSIO_S6G_DES_CFG, + HSIO_S6G_DES_CFG_DES_PHS_CTRL_M | + HSIO_S6G_DES_CFG_DES_CPMD_SEL_M | + HSIO_S6G_DES_CFG_DES_BW_ANA_M, + HSIO_S6G_DES_CFG_DES_PHS_CTRL(2) | + HSIO_S6G_DES_CFG_DES_CPMD_SEL(0) | + HSIO_S6G_DES_CFG_DES_BW_ANA(des_bw_ana)); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG, + HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL_M | + HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET_M, + HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET(0) | + HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL(0)); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG1, + HSIO_S6G_IB_CFG1_IB_TSDET_M, + HSIO_S6G_IB_CFG1_IB_TSDET(16)); + + regmap_update_bits(regmap, HSIO_S6G_SER_CFG, + HSIO_S6G_SER_CFG_SER_ALISEL_M | + HSIO_S6G_SER_CFG_SER_ENALI, + HSIO_S6G_SER_CFG_SER_ALISEL(0)); + + regmap_update_bits(regmap, HSIO_S6G_PLL_CFG, + HSIO_S6G_PLL_CFG_PLL_DIV4 | + HSIO_S6G_PLL_CFG_PLL_ENA_ROT | + HSIO_S6G_PLL_CFG_PLL_FSM_CTRL_DATA_M | + HSIO_S6G_PLL_CFG_PLL_ROT_DIR | + HSIO_S6G_PLL_CFG_PLL_ROT_FRQ, + HSIO_S6G_PLL_CFG_PLL_FSM_CTRL_DATA + (pll_fsm_ctrl_data)); + + regmap_update_bits(regmap, HSIO_S6G_COMMON_CFG, + HSIO_S6G_COMMON_CFG_SYS_RST | + HSIO_S6G_COMMON_CFG_ENA_LANE | + HSIO_S6G_COMMON_CFG_PWD_RX | + HSIO_S6G_COMMON_CFG_PWD_TX | + HSIO_S6G_COMMON_CFG_HRATE | + HSIO_S6G_COMMON_CFG_QRATE | + HSIO_S6G_COMMON_CFG_ENA_ELOOP | + HSIO_S6G_COMMON_CFG_ENA_FLOOP | + HSIO_S6G_COMMON_CFG_IF_MODE_M, + HSIO_S6G_COMMON_CFG_SYS_RST | + HSIO_S6G_COMMON_CFG_ENA_LANE | + (qrate ? HSIO_S6G_COMMON_CFG_QRATE : 0) | + HSIO_S6G_COMMON_CFG_IF_MODE(if_mode)); + + regmap_update_bits(regmap, HSIO_S6G_MISC_CFG, + HSIO_S6G_MISC_CFG_LANE_RST | + HSIO_S6G_MISC_CFG_DES_100FX_CPMD_ENA | + HSIO_S6G_MISC_CFG_RX_LPI_MODE_ENA | + HSIO_S6G_MISC_CFG_TX_LPI_MODE_ENA, + HSIO_S6G_MISC_CFG_LANE_RST | + HSIO_S6G_MISC_CFG_RX_LPI_MODE_ENA); + + + ret = serdes_commit_mcb_s6g(regmap, serdes); + if (ret) + return ret; + + regmap_update_bits(regmap, HSIO_S6G_PLL_CFG, + HSIO_S6G_PLL_CFG_PLL_FSM_ENA, + HSIO_S6G_PLL_CFG_PLL_FSM_ENA); + + ret = serdes_commit_mcb_s6g(regmap, serdes); + if (ret) + return ret; + + /* Wait for PLL bringup */ + msleep(20); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG, + HSIO_S6G_IB_CFG_IB_CAL_ENA, + HSIO_S6G_IB_CFG_IB_CAL_ENA); + + regmap_update_bits(regmap, HSIO_S6G_MISC_CFG, + HSIO_S6G_MISC_CFG_LANE_RST, 0); + + ret = serdes_commit_mcb_s6g(regmap, serdes); + if (ret) + return ret; + + /* Wait for calibration */ + msleep(60); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG, + HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET_M | + HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL_M, + HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET(0) | + HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL(7)); + + regmap_update_bits(regmap, HSIO_S6G_IB_CFG1, + HSIO_S6G_IB_CFG1_IB_TSDET_M, + HSIO_S6G_IB_CFG1_IB_TSDET(3)); + + /* IB CFG */ + + return 0; +} + #define MCB_S1G_CFG_TIMEOUT 50 static int __serdes_write_mcb_s1g(struct regmap *regmap, u8 macro, u32 op) @@ -110,7 +342,7 @@ struct serdes_mux { u32 mux; }; -#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \ +#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \ .idx = _idx, \ .port = _port, \ .mode = _mode, \ @@ -191,8 +423,12 @@ static int serdes_set_mode(struct phy *phy, enum phy_mode mode, int submode) if (macro->idx <= SERDES1G_MAX) return serdes_init_s1g(macro->ctrl->regs, macro->idx); + else if (macro->idx <= SERDES6G_MAX) + return serdes_init_s6g(macro->ctrl->regs, + macro->idx - (SERDES1G_MAX + 1), + ocelot_serdes_muxes[i].submode); - /* SERDES6G and PCIe not supported yet */ + /* PCIe not supported yet */ return -EOPNOTSUPP; } -- cgit v1.2.3-55-g7522 From ea4059fc93fd3235d28463372354db4fd52f93a3 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 20 Mar 2019 21:22:04 +0000 Subject: scsi: phy: mediatek: fix typo in author's email address There is a typo in the module author's email address. Fix this. Signed-off-by: Colin Ian King Acked-by: Chunfeng Yun Reviewed-by: Stanley Chu Acked-by: Gustavo A. R. Silva Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/mediatek/phy-mtk-ufs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/phy') diff --git a/drivers/phy/mediatek/phy-mtk-ufs.c b/drivers/phy/mediatek/phy-mtk-ufs.c index 9a80a76d7990..cf94f5c35dc5 100644 --- a/drivers/phy/mediatek/phy-mtk-ufs.c +++ b/drivers/phy/mediatek/phy-mtk-ufs.c @@ -241,5 +241,5 @@ static struct platform_driver ufs_mtk_phy_driver = { module_platform_driver(ufs_mtk_phy_driver); MODULE_DESCRIPTION("Universal Flash Storage (UFS) MediaTek MPHY"); -MODULE_AUTHOR("Stanley Chu "); +MODULE_AUTHOR("Stanley Chu "); MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From 94e487a41f57ea9f8bdba7e69791076188d8e111 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Fri, 29 Mar 2019 12:14:03 +0800 Subject: phy: Add usb phy support for hi3660 Soc of Hisilicon This driver handles usb phy power on and shutdown for hi3660 Soc of Hisilicon. Cc: Andy Shevchenko Cc: Kishon Vijay Abraham I Cc: "David S. Miller" Cc: Greg Kroah-Hartman Cc: Mauro Carvalho Chehab Cc: Andrew Morton Cc: Arnd Bergmann Cc: Shawn Guo Cc: Pengcheng Li Cc: Jianguo Sun Cc: Masahiro Yamada Cc: Jiancheng Xue Cc: John Stultz Cc: Binghui Wang Reviewed-by: Andy Shevchenko Signed-off-by: Yu Chen Signed-off-by: Kishon Vijay Abraham I --- MAINTAINERS | 8 ++ drivers/phy/hisilicon/Kconfig | 10 ++ drivers/phy/hisilicon/Makefile | 1 + drivers/phy/hisilicon/phy-hi3660-usb3.c | 233 ++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 drivers/phy/hisilicon/phy-hi3660-usb3.c (limited to 'drivers/phy') diff --git a/MAINTAINERS b/MAINTAINERS index 1d308fd7afaa..eec8ef97f3b2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16079,6 +16079,14 @@ L: linux-usb@vger.kernel.org S: Maintained F: drivers/usb/roles/intel-xhci-usb-role-switch.c +USB IP DRIVER FOR HISILICON KIRIN +M: Yu Chen +M: Binghui Wang +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/phy/phy-hi3660-usb3.txt +F: drivers/phy/hisilicon/phy-hi3660-usb3.c + USB ISP116X DRIVER M: Olav Kongas L: linux-usb@vger.kernel.org diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig index b40ee54a1a50..3c142f08987c 100644 --- a/drivers/phy/hisilicon/Kconfig +++ b/drivers/phy/hisilicon/Kconfig @@ -12,6 +12,16 @@ config PHY_HI6220_USB To compile this driver as a module, choose M here. +config PHY_HI3660_USB + tristate "hi3660 USB PHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON HI3660 USB PHY. + + To compile this driver as a module, choose M here. + config PHY_HISTB_COMBPHY tristate "HiSilicon STB SoCs COMBPHY support" depends on (ARCH_HISI && ARM64) || COMPILE_TEST diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile index f662a4fe18d8..75ba64e2faf8 100644 --- a/drivers/phy/hisilicon/Makefile +++ b/drivers/phy/hisilicon/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o +obj-$(CONFIG_PHY_HI3660_USB) += phy-hi3660-usb3.o obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o diff --git a/drivers/phy/hisilicon/phy-hi3660-usb3.c b/drivers/phy/hisilicon/phy-hi3660-usb3.c new file mode 100644 index 000000000000..cc0af2c044d0 --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi3660-usb3.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phy provider for USB 3.0 controller on HiSilicon 3660 platform + * + * Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd. + * http://www.huawei.com + * + * Authors: Yu Chen + */ + +#include +#include +#include +#include +#include +#include + +#define PERI_CRG_CLK_EN4 0x40 +#define PERI_CRG_CLK_DIS4 0x44 +#define GT_CLK_USB3OTG_REF BIT(0) +#define GT_ACLK_USB3OTG BIT(1) + +#define PERI_CRG_RSTEN4 0x90 +#define PERI_CRG_RSTDIS4 0x94 +#define IP_RST_USB3OTGPHY_POR BIT(3) +#define IP_RST_USB3OTG BIT(5) + +#define PERI_CRG_ISODIS 0x148 +#define USB_REFCLK_ISO_EN BIT(25) + +#define PCTRL_PERI_CTRL3 0x10 +#define PCTRL_PERI_CTRL3_MSK_START 16 +#define USB_TCXO_EN BIT(1) + +#define PCTRL_PERI_CTRL24 0x64 +#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25) + +#define USBOTG3_CTRL0 0x00 +#define SC_USB3PHY_ABB_GT_EN BIT(15) + +#define USBOTG3_CTRL2 0x08 +#define USBOTG3CTRL2_POWERDOWN_HSP BIT(0) +#define USBOTG3CTRL2_POWERDOWN_SSP BIT(1) + +#define USBOTG3_CTRL3 0x0C +#define USBOTG3_CTRL3_VBUSVLDEXT BIT(6) +#define USBOTG3_CTRL3_VBUSVLDEXTSEL BIT(5) + +#define USBOTG3_CTRL4 0x10 + +#define USBOTG3_CTRL7 0x1c +#define REF_SSP_EN BIT(16) + +/* This value config the default txtune parameter of the usb 2.0 phy */ +#define HI3660_USB_DEFAULT_PHY_PARAM 0x1c466e3 + +struct hi3660_priv { + struct device *dev; + struct regmap *peri_crg; + struct regmap *pctrl; + struct regmap *otg_bc; + u32 eye_diagram_param; +}; + +static int hi3660_phy_init(struct phy *phy) +{ + struct hi3660_priv *priv = phy_get_drvdata(phy); + u32 val, mask; + int ret; + + /* usb refclk iso disable */ + ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS, USB_REFCLK_ISO_EN); + if (ret) + goto out; + + /* enable usb_tcxo_en */ + val = USB_TCXO_EN | (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START); + ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val); + if (ret) + goto out; + + /* assert phy */ + val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG; + ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val); + if (ret) + goto out; + + /* enable phy ref clk */ + val = SC_USB3PHY_ABB_GT_EN; + mask = val; + ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL0, mask, val); + if (ret) + goto out; + + val = REF_SSP_EN; + mask = val; + ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL7, mask, val); + if (ret) + goto out; + + /* exit from IDDQ mode */ + mask = USBOTG3CTRL2_POWERDOWN_HSP | USBOTG3CTRL2_POWERDOWN_SSP; + ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL2, mask, 0); + if (ret) + goto out; + + /* delay for exit from IDDQ mode */ + usleep_range(100, 120); + + /* deassert phy */ + val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG; + ret = regmap_write(priv->peri_crg, PERI_CRG_RSTDIS4, val); + if (ret) + goto out; + + /* delay for phy deasserted */ + usleep_range(10000, 15000); + + /* fake vbus valid signal */ + val = USBOTG3_CTRL3_VBUSVLDEXT | USBOTG3_CTRL3_VBUSVLDEXTSEL; + mask = val; + ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL3, mask, val); + if (ret) + goto out; + + /* delay for vbus valid */ + usleep_range(100, 120); + + ret = regmap_write(priv->otg_bc, USBOTG3_CTRL4, + priv->eye_diagram_param); + if (ret) + goto out; + + return 0; +out: + dev_err(priv->dev, "failed to init phy ret: %d\n", ret); + return ret; +} + +static int hi3660_phy_exit(struct phy *phy) +{ + struct hi3660_priv *priv = phy_get_drvdata(phy); + u32 val; + int ret; + + /* assert phy */ + val = IP_RST_USB3OTGPHY_POR; + ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val); + if (ret) + goto out; + + /* disable usb_tcxo_en */ + val = USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START; + ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val); + if (ret) + goto out; + + return 0; +out: + dev_err(priv->dev, "failed to exit phy ret: %d\n", ret); + return ret; +} + +static struct phy_ops hi3660_phy_ops = { + .init = hi3660_phy_init, + .exit = hi3660_phy_exit, + .owner = THIS_MODULE, +}; + +static int hi3660_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *phy; + struct hi3660_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,pericrg-syscon"); + if (IS_ERR(priv->peri_crg)) { + dev_err(dev, "no hisilicon,pericrg-syscon\n"); + return PTR_ERR(priv->peri_crg); + } + + priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,pctrl-syscon"); + if (IS_ERR(priv->pctrl)) { + dev_err(dev, "no hisilicon,pctrl-syscon\n"); + return PTR_ERR(priv->pctrl); + } + + /* node of hi3660 phy is a sub-node of usb3_otg_bc */ + priv->otg_bc = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->otg_bc)) { + dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n"); + return PTR_ERR(priv->otg_bc); + } + + if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param", + &(priv->eye_diagram_param))) + priv->eye_diagram_param = HI3660_USB_DEFAULT_PHY_PARAM; + + phy = devm_phy_create(dev, NULL, &hi3660_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 hi3660_phy_of_match[] = { + {.compatible = "hisilicon,hi3660-usb-phy",}, + { } +}; +MODULE_DEVICE_TABLE(of, hi3660_phy_of_match); + +static struct platform_driver hi3660_phy_driver = { + .probe = hi3660_phy_probe, + .driver = { + .name = "hi3660-usb-phy", + .of_match_table = hi3660_phy_of_match, + } +}; +module_platform_driver(hi3660_phy_driver); + +MODULE_AUTHOR("Yu Chen "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hilisicon Hi3660 USB3 PHY Driver"); -- cgit v1.2.3-55-g7522