* [PATCH v2 2/6] phy/rockchip: inno-combophy: Add initial support
2020-02-13 6:08 [PATCH v2 0/6] Add Rockchip new PCIe controller and combo phy support Shawn Lin
2020-02-13 6:08 ` [PATCH v2 1/6] dt-bindings: add binding for Rockchip combo phy using an Innosilicon IP Shawn Lin
@ 2020-02-13 6:08 ` Shawn Lin
2020-02-13 6:08 ` [PATCH v2 3/6] PCI: dwc: Skip allocating own MSI domain if using external MSI domain Shawn Lin
` (2 subsequent siblings)
4 siblings, 0 replies; 13+ messages in thread
From: Shawn Lin @ 2020-02-13 6:08 UTC (permalink / raw)
To: Heiko Stuebner, Lorenzo Pieralisi, Rob Herring,
Kishon Vijay Abraham I, Bjorn Helgaas
Cc: Jingoo Han, linux-pci, devicetree, William Wu, Simon Xue,
linux-rockchip, Shawn Lin
From: William Wu <william.wu@rock-chips.com>
Add initial support for inno-combophy driver which supports
USB3.0 host/device/otg mode as well as PCIe EP/RC mode.
Signed-off-by: William Wu <william.wu@rock-chips.com>
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
Changes in v2: None
drivers/phy/rockchip/Kconfig | 8 +
drivers/phy/rockchip/Makefile | 1 +
drivers/phy/rockchip/phy-rockchip-inno-combphy.c | 1056 ++++++++++++++++++++++
3 files changed, 1065 insertions(+)
create mode 100644 drivers/phy/rockchip/phy-rockchip-inno-combphy.c
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
index 0824b9d..267306a 100644
--- a/drivers/phy/rockchip/Kconfig
+++ b/drivers/phy/rockchip/Kconfig
@@ -16,6 +16,14 @@ config PHY_ROCKCHIP_EMMC
help
Enable this to support the Rockchip EMMC PHY.
+config PHY_ROCKCHIP_INNO_COMBPHY
+ tristate "Rockchip INNO USB 3.0 and PCIe COMBPHY Driver"
+ depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip SoCs COMBPHY.
+ If unsure, say N.
+
config PHY_ROCKCHIP_INNO_HDMI
tristate "Rockchip INNO HDMI PHY Driver"
depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF
diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile
index 9f59a81..6813b95 100644
--- a/drivers/phy/rockchip/Makefile
+++ b/drivers/phy/rockchip/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o
obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_COMBPHY) += phy-rockchip-inno-combphy.o
obj-$(CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY) += phy-rockchip-inno-dsidphy.o
obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o
obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-combphy.c b/drivers/phy/rockchip/phy-rockchip-inno-combphy.c
new file mode 100644
index 0000000..4656ecc
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-combphy.c
@@ -0,0 +1,1056 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip USB3.0 and PCIE COMBPHY with Innosilicon IP block driver
+ *
+ * Copyright (C) 2018 Fuzhou Rockchip Electronics Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <dt-bindings/phy/phy.h>
+
+#define BIT_WRITEABLE_SHIFT 16
+
+struct rockchip_combphy_priv;
+
+enum rockchip_combphy_rst {
+ OTG_RSTN = 0,
+ PHY_POR_RSTN = 1,
+ PHY_APB_RSTN = 2,
+ PHY_PIPE_RSTN = 3,
+ PHY_GRF_P_RSTN = 4,
+ PHY_RESET_MAX = 5,
+};
+
+struct combphy_reg {
+ u32 offset;
+ u32 bitend;
+ u32 bitstart;
+ u32 disable;
+ u32 enable;
+};
+
+struct rockchip_combphy_grfcfg {
+ struct combphy_reg pipe_l1_sel;
+ struct combphy_reg pipe_l1_set;
+ struct combphy_reg pipe_l1pd_sel;
+ struct combphy_reg pipe_l1pd_p3;
+ struct combphy_reg pipe_l0pd_sel;
+ struct combphy_reg pipe_l0pd_p3;
+ struct combphy_reg pipe_clk_sel;
+ struct combphy_reg pipe_clk_set;
+ struct combphy_reg pipe_rate_sel;
+ struct combphy_reg pipe_rate_set;
+ struct combphy_reg pipe_mode_sel;
+ struct combphy_reg pipe_mode_set;
+ struct combphy_reg pipe_txrx_sel;
+ struct combphy_reg pipe_txrx_set;
+ struct combphy_reg pipe_width_sel;
+ struct combphy_reg pipe_width_set;
+ struct combphy_reg pipe_usb3_sel;
+ struct combphy_reg pipe_pll_lock;
+ struct combphy_reg pipe_status_l0;
+ struct combphy_reg pipe_l0rxterm_sel;
+ struct combphy_reg pipe_l1rxterm_sel;
+ struct combphy_reg pipe_l0rxterm_set;
+ struct combphy_reg pipe_l1rxterm_set;
+ struct combphy_reg pipe_l0rxelec_set;
+ struct combphy_reg u3_port_disable;
+ struct combphy_reg u3_port_num;
+};
+
+struct rockchip_combphy_cfg {
+ const struct rockchip_combphy_grfcfg grfcfg;
+ int (*combphy_cfg)(struct rockchip_combphy_priv *priv);
+ int (*combphy_low_power_ctrl)(struct rockchip_combphy_priv *priv,
+ bool en);
+};
+
+struct rockchip_combphy_priv {
+ bool phy_initialized;
+ bool phy_suspended;
+ u8 phy_type;
+ void __iomem *mmio;
+ struct device *dev;
+ struct clk *ref_clk;
+ struct phy *phy;
+ struct regmap *combphy_grf;
+ struct regmap *usb_pcie_grf;
+ struct reset_control *rsts[PHY_RESET_MAX];
+ const struct rockchip_combphy_cfg *cfg;
+};
+
+static const char *get_reset_name(enum rockchip_combphy_rst rst)
+{
+ switch (rst) {
+ case OTG_RSTN:
+ return "otg-rst";
+ case PHY_POR_RSTN:
+ return "combphy-por";
+ case PHY_APB_RSTN:
+ return "combphy-apb";
+ case PHY_PIPE_RSTN:
+ return "combphy-pipe";
+ case PHY_GRF_P_RSTN:
+ return "usb3phy_grf_p";
+ default:
+ return "invalid";
+ }
+}
+
+static inline bool param_read(struct regmap *base,
+ const struct combphy_reg *reg, u32 val)
+{
+ int ret;
+ u32 mask, orig, tmp;
+
+ ret = regmap_read(base, reg->offset, &orig);
+ if (ret)
+ return false;
+
+ mask = GENMASK(reg->bitend, reg->bitstart);
+ tmp = (orig & mask) >> reg->bitstart;
+
+ return tmp == val;
+}
+
+static inline int param_write(struct regmap *base,
+ const struct combphy_reg *reg, bool en)
+{
+ u32 val, mask, tmp;
+
+ tmp = en ? reg->enable : reg->disable;
+ mask = GENMASK(reg->bitend, reg->bitstart);
+ val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT);
+
+ return regmap_write(base, reg->offset, val);
+}
+
+static inline bool param_exped(void __iomem *base,
+ const struct combphy_reg *reg,
+ unsigned int value)
+{
+ int ret;
+ unsigned int tmp, orig;
+ unsigned int mask = GENMASK(reg->bitend, reg->bitstart);
+
+ ret = regmap_read(base, reg->offset, &orig);
+ if (ret)
+ return false;
+
+ tmp = (orig & mask) >> reg->bitstart;
+
+ return tmp == value;
+}
+
+static ssize_t u3phy_mode_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rockchip_combphy_priv *priv = dev_get_drvdata(device);
+
+ if (param_exped(priv->usb_pcie_grf,
+ &priv->cfg->grfcfg.u3_port_num, 0))
+ return sprintf(buf, "u2\n");
+ else
+ return sprintf(buf, "u3\n");
+}
+
+static ssize_t u3phy_mode_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct rockchip_combphy_priv *priv = dev_get_drvdata(device);
+
+ if (!strncmp(buf, "u3", 2) &&
+ param_exped(priv->usb_pcie_grf,
+ &priv->cfg->grfcfg.u3_port_num, 0)) {
+ /*
+ * Enable USB 3.0 rx termination, need to select
+ * pipe_l0_rxtermination from USB 3.0 controller.
+ */
+ param_write(priv->combphy_grf,
+ &priv->cfg->grfcfg.pipe_l0rxterm_sel, false);
+ /* Set xHCI USB 3.0 port number to 1 */
+ param_write(priv->usb_pcie_grf,
+ &priv->cfg->grfcfg.u3_port_num, true);
+ /* Enable xHCI USB 3.0 port */
+ param_write(priv->usb_pcie_grf,
+ &priv->cfg->grfcfg.u3_port_disable, false);
+ dev_info(priv->dev,
+ "Set usb3.0 and usb2.0 mode successfully\n");
+ } else if (!strncmp(buf, "u2", 2) &&
+ param_exped(priv->usb_pcie_grf,
+ &priv->cfg->grfcfg.u3_port_num, 1)) {
+ /*
+ * Disable USB 3.0 rx termination, need to select
+ * pipe_l0_rxtermination from grf and remove rx
+ * termimation by grf.
+ */
+ param_write(priv->combphy_grf,
+ &priv->cfg->grfcfg.pipe_l0rxterm_set, false);
+ param_write(priv->combphy_grf,
+ &priv->cfg->grfcfg.pipe_l0rxterm_sel, true);
+ /* Set xHCI USB 3.0 port number to 0 */
+ param_write(priv->usb_pcie_grf,
+ &priv->cfg->grfcfg.u3_port_num, false);
+ /* Disable xHCI USB 3.0 port */
+ param_write(priv->usb_pcie_grf,
+ &priv->cfg->grfcfg.u3_port_disable, true);
+ /*
+ * Note:
+ * Don't disable the USB 3.0 PIPE pclk here(set reg
+ * pipe_usb3_sel to false), because USB 3.0 PHY depend
+ * on this clk, if we disable it, we need to reinit
+ * the USB 3.0 PHY when use USB 3.0 mode, in order to
+ * simplify the process, don't disable this PIPE pclk.
+ */
+ dev_info(priv->dev, "Set usb2.0 only mode successfully\n");
+ } else {
+ dev_info(priv->dev, "Same or illegal mode\n");
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(u3phy_mode);
+
+static struct attribute *rockchip_combphy_u3mode_attrs[] = {
+ &dev_attr_u3phy_mode.attr,
+ NULL,
+};
+
+static struct attribute_group rockchip_combphy_u3mode_attr_group = {
+ .name = NULL, /* we want them in the same directory */
+ .attrs = rockchip_combphy_u3mode_attrs,
+};
+
+static u32 rockchip_combphy_pll_lock(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *grfcfg;
+ u32 mask, val;
+
+ grfcfg = &priv->cfg->grfcfg;
+ mask = GENMASK(grfcfg->pipe_pll_lock.bitend,
+ grfcfg->pipe_pll_lock.bitstart);
+
+ regmap_read(priv->combphy_grf, grfcfg->pipe_pll_lock.offset, &val);
+ val = (val & mask) >> grfcfg->pipe_pll_lock.bitstart;
+
+ return val;
+}
+
+static u32 rockchip_combphy_is_ready(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *grfcfg;
+ u32 mask, val;
+
+ grfcfg = &priv->cfg->grfcfg;
+ mask = GENMASK(grfcfg->pipe_status_l0.bitend,
+ grfcfg->pipe_status_l0.bitstart);
+
+ regmap_read(priv->combphy_grf, grfcfg->pipe_status_l0.offset, &val);
+ val = (val & mask) >> grfcfg->pipe_status_l0.bitstart;
+
+ return val;
+}
+
+static int phy_pcie_init(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *grfcfg;
+ u32 val;
+ int ret = 0;
+
+ grfcfg = &priv->cfg->grfcfg;
+
+ /* reset PCIe phy to default configuration */
+ reset_control_assert(priv->rsts[PHY_POR_RSTN]);
+ reset_control_assert(priv->rsts[PHY_APB_RSTN]);
+ reset_control_assert(priv->rsts[PHY_PIPE_RSTN]);
+
+ reset_control_deassert(priv->rsts[PHY_POR_RSTN]);
+ /* Wait PHY power on stable */
+ udelay(5);
+ reset_control_deassert(priv->rsts[PHY_APB_RSTN]);
+ udelay(5);
+
+ /* Set rxtermination for lane0 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0rxterm_set, true);
+ /* Set rxtermination for lane1 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l1rxterm_set, true);
+ /* Select pipe_l0_rxtermination from grf */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0rxterm_sel, true);
+ /* Select pipe_l1_rxtermination from grf */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l1rxterm_sel, true);
+ /* Select rxelecidle_disable and txcommonmode from PCIe controller */
+ param_write(priv->combphy_grf, &grfcfg->pipe_txrx_sel, false);
+
+ /* Start to configurate PHY registers for PCIE. */
+ if (priv->cfg->combphy_cfg) {
+ ret = priv->cfg->combphy_cfg(priv);
+ if (ret)
+ goto error;
+ }
+
+ /* Wait Tx PLL lock */
+ usleep_range(300, 350);
+ ret = readx_poll_timeout_atomic(rockchip_combphy_pll_lock, priv, val,
+ val == grfcfg->pipe_pll_lock.enable,
+ 10, 1000);
+ if (ret) {
+ dev_err(priv->dev, "wait phy PLL lock timeout\n");
+ goto error;
+ }
+
+ reset_control_deassert(priv->rsts[PHY_PIPE_RSTN]);
+error:
+ return ret;
+}
+
+static int phy_u3_init(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *grfcfg;
+ u32 val;
+ int ret = 0;
+
+ grfcfg = &priv->cfg->grfcfg;
+
+ /* Reset the USB3 controller first. */
+ reset_control_assert(priv->rsts[OTG_RSTN]);
+
+ reset_control_deassert(priv->rsts[PHY_POR_RSTN]);
+ /* Wait PHY power on stable. */
+ udelay(5);
+
+ reset_control_deassert(priv->rsts[PHY_APB_RSTN]);
+ udelay(5);
+
+ /*
+ * Start to configurate PHY registers for USB3.
+ * Note: set operation must be done before corresponding
+ * sel operation, otherwise, the PIPE PHY status lane0
+ * may be unable to get ready.
+ */
+
+ /* Disable PHY lane1 which isn't needed for USB3 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l1_set, true);
+ param_write(priv->combphy_grf, &grfcfg->pipe_l1_sel, true);
+
+ /* Set PHY Tx and Rx for USB3 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_txrx_set, true);
+ param_write(priv->combphy_grf, &grfcfg->pipe_txrx_sel, true);
+
+ /* Set PHY PIPE MAC pclk request */
+ param_write(priv->combphy_grf, &grfcfg->pipe_clk_set, true);
+ param_write(priv->combphy_grf, &grfcfg->pipe_clk_sel, true);
+
+ /* Set PHY PIPE rate for USB3 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_rate_set, true);
+ param_write(priv->combphy_grf, &grfcfg->pipe_rate_sel, true);
+
+ /* Set PHY mode for USB3 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_mode_set, true);
+ param_write(priv->combphy_grf, &grfcfg->pipe_mode_sel, true);
+
+ /* Set PHY data bus width for USB3 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_width_set, true);
+ param_write(priv->combphy_grf, &grfcfg->pipe_width_sel, true);
+
+ /* Select PIPE for USB3 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_usb3_sel, true);
+
+ if (priv->cfg->combphy_cfg) {
+ ret = priv->cfg->combphy_cfg(priv);
+ if (ret)
+ goto error;
+ }
+
+ /* Wait Tx PLL lock */
+ usleep_range(300, 350);
+ ret = readx_poll_timeout_atomic(rockchip_combphy_pll_lock, priv, val,
+ val == grfcfg->pipe_pll_lock.enable,
+ 10, 1000);
+ if (ret) {
+ dev_err(priv->dev, "wait phy PLL lock timeout\n");
+ goto error;
+ }
+
+ reset_control_deassert(priv->rsts[PHY_PIPE_RSTN]);
+
+ /* Wait PIPE PHY status lane0 ready */
+ ret = readx_poll_timeout_atomic(rockchip_combphy_is_ready, priv, val,
+ val == grfcfg->pipe_status_l0.enable,
+ 10, 1000);
+ if (ret) {
+ dev_err(priv->dev, "wait phy status lane0 ready timeout\n");
+ goto error;
+ }
+
+ reset_control_deassert(priv->rsts[OTG_RSTN]);
+
+error:
+ return ret;
+}
+
+static int rockchip_combphy_set_phy_type(struct rockchip_combphy_priv *priv)
+{
+ int ret = 0;
+
+ if (priv->phy_initialized)
+ return ret;
+
+ switch (priv->phy_type) {
+ case PHY_TYPE_PCIE:
+ ret = phy_pcie_init(priv);
+ break;
+ case PHY_TYPE_USB3:
+ ret = phy_u3_init(priv);
+ if (ret)
+ return ret;
+
+ /* Attributes */
+ ret = sysfs_create_group(&priv->dev->kobj,
+ &rockchip_combphy_u3mode_attr_group);
+ break;
+ default:
+ dev_err(priv->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int rockchip_combphy_init(struct phy *phy)
+{
+ struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_prepare_enable(priv->ref_clk);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable ref_clk\n");
+ return ret;
+ }
+
+ ret = rockchip_combphy_set_phy_type(priv);
+ if (ret) {
+ dev_err(priv->dev, "failed to set phy type\n");
+ return ret;
+ }
+
+ priv->phy_initialized = true;
+
+ return 0;
+}
+
+static int rockchip_combphy_exit(struct phy *phy)
+{
+ struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
+
+ /*
+ * Note: don't assert PHY reset here, because
+ * we set many phy configurations during phy
+ * init to reduce PHY power consumption, if we
+ * assert PHY reset here, these configurations
+ * will be lost, and increase power consumption.
+ */
+ clk_disable_unprepare(priv->ref_clk);
+
+ /* in case of waiting phy PLL lock timeout */
+ if (priv->phy_type == PHY_TYPE_PCIE) {
+ reset_control_assert(priv->rsts[PHY_GRF_P_RSTN]);
+ udelay(5);
+ reset_control_deassert(priv->rsts[PHY_GRF_P_RSTN]);
+ priv->phy_initialized = false;
+ }
+
+ return 0;
+}
+
+static int rockchip_combphy_power_on(struct phy *phy)
+{
+ struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
+ const struct rockchip_combphy_grfcfg *grfcfg;
+
+ if (!priv->phy_suspended)
+ return 0;
+
+ grfcfg = &priv->cfg->grfcfg;
+
+ if (priv->phy_type == PHY_TYPE_USB3) {
+ if (priv->cfg->combphy_low_power_ctrl)
+ priv->cfg->combphy_low_power_ctrl(priv, false);
+
+ /* Enable lane 0 squelch detection */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0rxelec_set,
+ false);
+
+ /*
+ * Check if lane 0 powerdown is already
+ * controlled by USB 3.0 controller.
+ */
+ if (param_read(priv->combphy_grf,
+ &grfcfg->pipe_l0pd_sel, 0))
+ goto done;
+
+ /* Exit to P0 from P3 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_p3, false);
+ usleep_range(250, 300);
+
+ /*
+ * Set lane 0 powerdown to be controlled
+ * by USB 3.0 controller.
+ */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_sel, false);
+ }
+
+done:
+ priv->phy_suspended = false;
+ return 0;
+}
+
+static int rockchip_combphy_power_off(struct phy *phy)
+{
+ struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
+ const struct rockchip_combphy_grfcfg *grfcfg;
+
+ if (priv->phy_suspended)
+ return 0;
+
+ grfcfg = &priv->cfg->grfcfg;
+
+ if (priv->phy_type != PHY_TYPE_USB3 &&
+ priv->phy_type != PHY_TYPE_PCIE)
+ goto done;
+
+ /*
+ * Check if lane 0 powerdown is already
+ * controlled by grf and in P3 state.
+ */
+ if (param_read(priv->combphy_grf,
+ &grfcfg->pipe_l0pd_sel, 1) &&
+ param_read(priv->combphy_grf,
+ &grfcfg->pipe_l0pd_p3, 3))
+ goto done;
+
+ /* Exit to P0 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_p3, false);
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_sel, true);
+ udelay(1);
+
+ /* Enter to P3 from P0 */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_p3, true);
+ udelay(2);
+
+ /*
+ * Disable lane 0 squelch detection.
+ * Note: if squelch detection is disabled,
+ * the PHY can't detect LFPS.
+ */
+ param_write(priv->combphy_grf, &grfcfg->pipe_l0rxelec_set,
+ true);
+
+ if (priv->cfg->combphy_low_power_ctrl)
+ priv->cfg->combphy_low_power_ctrl(priv, true);
+
+done:
+ priv->phy_suspended = true;
+ return 0;
+}
+
+static int rockchip_combphy_set_mode_ext(struct phy *phy,
+ enum phy_mode mode, int submode)
+{
+ struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
+ u32 reg;
+
+ if (priv->phy_type != PHY_TYPE_PCIE || mode != PHY_MODE_PCIE)
+ return -EINVAL;
+
+ reg = readl(priv->mmio + 0x21a8);
+
+ /*
+ * PCI-e EP/RC runtime special cfg.
+ * submode 1 is for EP, and 0 is for RC.
+ */
+ if (submode == 1)
+ reg |= (0x1 << 2);
+ else if (submode == 0)
+ reg &= ~(0x1 << 2);
+ else
+ return -EINVAL;
+
+ writel(reg, priv->mmio + 0x21a8);
+ return 0;
+}
+
+static const struct phy_ops rockchip_combphy_ops = {
+ .init = rockchip_combphy_init,
+ .exit = rockchip_combphy_exit,
+ .power_on = rockchip_combphy_power_on,
+ .power_off = rockchip_combphy_power_off,
+ .set_mode = rockchip_combphy_set_mode_ext,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *rockchip_combphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct rockchip_combphy_priv *priv = dev_get_drvdata(dev);
+
+ if (args->args_count < 1) {
+ dev_err(dev, "invalid number of arguments\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (priv->phy_type != PHY_NONE &&
+ priv->phy_type != args->args[0]) {
+ dev_err(dev, "type select %d overwriting phy type %d\n",
+ args->args[0], priv->phy_type);
+ return ERR_PTR(-ENODEV);
+ }
+
+ priv->phy_type = args->args[0];
+
+ if (priv->phy_type < PHY_TYPE_SATA ||
+ priv->phy_type > PHY_TYPE_USB3) {
+ dev_err(dev, "invalid phy type select argument\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ return priv->phy;
+}
+
+static int rockchip_combphy_parse_dt(struct device *dev,
+ struct rockchip_combphy_priv *priv)
+{
+ u32 i;
+
+ priv->combphy_grf =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,combphygrf");
+ if (IS_ERR(priv->combphy_grf)) {
+ dev_err(dev, "failed to find combphy grf regmap\n");
+ return PTR_ERR(priv->combphy_grf);
+ }
+
+ priv->usb_pcie_grf =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,usbpciegrf");
+ if (IS_ERR(priv->usb_pcie_grf)) {
+ dev_err(dev, "failed to find usb_pcie_grf regmap\n");
+ return PTR_ERR(priv->usb_pcie_grf);
+ }
+
+ priv->ref_clk = devm_clk_get(dev, "refclk");
+ if (IS_ERR(priv->ref_clk)) {
+ dev_err(dev, "failed to find ref clock\n");
+ return PTR_ERR(priv->ref_clk);
+ }
+
+ for (i = 0; i < PHY_RESET_MAX; i++) {
+ priv->rsts[i] =
+ devm_reset_control_get(dev, get_reset_name(i));
+ if (IS_ERR(priv->rsts[i])) {
+ dev_warn(dev, "no %s reset control specified\n",
+ get_reset_name(i));
+ priv->rsts[i] = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int rockchip_combphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct rockchip_combphy_priv *priv;
+ struct resource *res;
+ const struct rockchip_combphy_cfg *phy_cfg;
+ int ret;
+
+ phy_cfg = of_device_get_match_data(dev);
+ if (!phy_cfg) {
+ dev_err(dev, "No OF match data provided\n");
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ return ret;
+ }
+
+ ret = rockchip_combphy_parse_dt(dev, priv);
+ if (ret) {
+ dev_err(dev, "parse dt failed, ret(%d)\n", ret);
+ return ret;
+ }
+
+ reset_control_assert(priv->rsts[PHY_POR_RSTN]);
+ reset_control_assert(priv->rsts[PHY_APB_RSTN]);
+ reset_control_assert(priv->rsts[PHY_PIPE_RSTN]);
+
+ priv->phy_type = PHY_NONE;
+ priv->dev = dev;
+ priv->cfg = phy_cfg;
+ priv->phy = devm_phy_create(dev, NULL, &rockchip_combphy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create combphy\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+
+ phy_provider =
+ devm_of_phy_provider_register(dev,
+ rockchip_combphy_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static int rockchip_combphy_remove(struct platform_device *pdev)
+{
+ struct rockchip_combphy_priv *priv = platform_get_drvdata(pdev);
+
+ if (priv->phy_type == PHY_TYPE_USB3 &&
+ priv->phy_initialized)
+ sysfs_remove_group(&priv->dev->kobj,
+ &rockchip_combphy_u3mode_attr_group);
+
+ return 0;
+}
+
+static int rk1808_combphy_cfg(struct rockchip_combphy_priv *priv)
+{
+ unsigned long rate;
+ u32 reg;
+ bool ssc_en = false;
+
+ rate = clk_get_rate(priv->ref_clk);
+
+ /* Configure PHY reference clock frequency */
+ switch (rate) {
+ case 24000000:
+ /*
+ * The default PHY refclk frequency
+ * configuration is 24MHz.
+ */
+ break;
+ case 25000000:
+ writel(0x00, priv->mmio + 0x2118);
+ writel(0x64, priv->mmio + 0x211c);
+ writel(0x01, priv->mmio + 0x2020);
+ writel(0x64, priv->mmio + 0x2028);
+ writel(0x21, priv->mmio + 0x2030);
+
+ if (priv->phy_type == PHY_TYPE_PCIE) {
+ writel(0x1, priv->mmio + 0x3020);
+ writel(0x64, priv->mmio + 0x3028);
+ writel(0x21, priv->mmio + 0x3030);
+ }
+
+ break;
+ case 50000000:
+ writel(0x00, priv->mmio + 0x2118);
+ writel(0x32, priv->mmio + 0x211c);
+ writel(0x01, priv->mmio + 0x2020);
+ writel(0x32, priv->mmio + 0x2028);
+ writel(0x21, priv->mmio + 0x2030);
+ break;
+ default:
+ dev_err(priv->dev, "Unsupported rate: %lu\n", rate);
+ return -EINVAL;
+ }
+
+ if (priv->phy_type == PHY_TYPE_PCIE) {
+ /* turn on pcie phy pd */
+ writel(0x08400000, priv->mmio + 0x0);
+ writel(0x03030000, priv->mmio + 0x8);
+
+ /* Adjust Lane 0 Rx interface timing */
+ writel(0x20, priv->mmio + 0x20ac);
+ writel(0x12, priv->mmio + 0x20c8);
+ writel(0x76, priv->mmio + 0x2150);
+
+ /* Adjust Lane 1 Rx interface timing */
+ writel(0x20, priv->mmio + 0x30ac);
+ writel(0x12, priv->mmio + 0x30c8);
+ writel(0x76, priv->mmio + 0x3150);
+ /* Set PHY output refclk path */
+ writel(0x0, priv->mmio + 0x21a4);
+ writel(0x0, priv->mmio + 0x21a8);
+ writel(0xb, priv->mmio + 0x21ec);
+
+ /* Physical ordered set for PCIe */
+ writel(0x02, priv->mmio + 0x45c0);
+ writel(0x83, priv->mmio + 0x45c4);
+ writel(0x03, priv->mmio + 0x45c8);
+ writel(0x43, priv->mmio + 0x45cc);
+ writel(0x00, priv->mmio + 0x45d0);
+ writel(0xbc, priv->mmio + 0x45d4);
+
+ /* Boost pre-emphasis */
+ writel(0xaa, priv->mmio + 0x21b8);
+ writel(0xaa, priv->mmio + 0x31b8);
+ } else if (priv->phy_type == PHY_TYPE_USB3) {
+ /*
+ * Disable PHY Lane 1 which isn't needed
+ * for USB3 to reduce power consumption.
+ */
+ /* Lane 1 cdr power down */
+ writel(0x09, priv->mmio + 0x3148);
+
+ /* Lane 1 rx bias disable */
+ writel(0x01, priv->mmio + 0x21cc);
+
+ /* Lane 1 cdr disable */
+ writel(0x08, priv->mmio + 0x30c4);
+ writel(0x08, priv->mmio + 0x20f4);
+
+ /* Lane 1 rx lock disable and tx bias disable */
+ writel(0x12, priv->mmio + 0x3150);
+
+ /* Lane 1 rx termination disable, and tx_cmenb disable */
+ writel(0x04, priv->mmio + 0x3080);
+
+ /* Lane 1 tx termination disable */
+ writel(0x1d, priv->mmio + 0x3090);
+
+ /* Lane 1 tx driver disable */
+ writel(0x50, priv->mmio + 0x21c4);
+ writel(0x10, priv->mmio + 0x2050);
+
+ /* Lane 1 txldo_refsel disable */
+ writel(0x81, priv->mmio + 0x31a8);
+
+ /* Lane 1 txdetrx_en disable */
+ writel(0x00, priv->mmio + 0x31e8);
+
+ /* Lane 1 rxcm_en disable */
+ writel(0x08, priv->mmio + 0x30c0);
+
+ /* Adjust Lane 0 Rx interface timing */
+ writel(0x20, priv->mmio + 0x20ac);
+
+ /* Set and enable SSC */
+ switch (rate) {
+ case 24000000:
+ /* Set SSC rate to 31.25KHz */
+ reg = readl(priv->mmio + 0x2108);
+ reg = (reg & ~0xf) | 0x1;
+ writel(reg, priv->mmio + 0x2108);
+ ssc_en = true;
+ break;
+ case 25000000:
+ /* Set SSC rate to 32.55KHz */
+ reg = readl(priv->mmio + 0x2108);
+ reg = (reg & ~0xf) | 0x6;
+ writel(reg, priv->mmio + 0x2108);
+ ssc_en = true;
+ break;
+ default:
+ dev_warn(priv->dev,
+ "failed to set SSC on rate: %lu\n", rate);
+ break;
+ }
+
+ if (ssc_en) {
+ /* Enable SSC */
+ reg = readl(priv->mmio + 0x2120);
+ reg &= ~BIT(4);
+ writel(reg, priv->mmio + 0x2120);
+
+ reg = readl(priv->mmio + 0x2000);
+ reg &= ~0x6;
+ writel(reg, priv->mmio + 0x2000);
+ }
+
+ /*
+ * Tuning Tx:
+ * offset 0x21b8 bit[7:4]: lane 0 TX driver swing
+ * tuning bits with weight, "1111" represents the
+ * largest swing and "0000" the smallest.
+ */
+ reg = readl(priv->mmio + 0x21b8);
+ reg = (reg & ~0xf0) | 0xe0;
+ writel(reg, priv->mmio + 0x21b8);
+
+ /*
+ * Tuning Rx for RJTL:
+ * Decrease CDR Chump Bump current.
+ */
+ reg = readl(priv->mmio + 0x20c8);
+ reg = (reg & ~0x6) | BIT(1);
+ writel(reg, priv->mmio + 0x20c8);
+ reg = readl(priv->mmio + 0x2150);
+ reg |= BIT(2);
+ writel(reg, priv->mmio + 0x2150);
+ } else {
+ dev_err(priv->dev, "failed to cfg incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rk1808_combphy_low_power_control(struct rockchip_combphy_priv *priv,
+ bool en)
+{
+ if (priv->phy_type != PHY_TYPE_USB3) {
+ /* turn off pcie phy pd */
+ writel(0x08400840, priv->mmio + 0x0);
+ writel(0x03030303, priv->mmio + 0x8);
+
+ /* enter PCIe phy low power mode */
+ writel(0x36, priv->mmio + 0x2150);
+ writel(0x36, priv->mmio + 0x3150);
+ writel(0x02, priv->mmio + 0x21e8);
+ writel(0x02, priv->mmio + 0x31e8);
+ writel(0x0c, priv->mmio + 0x2080);
+ writel(0x0c, priv->mmio + 0x3080);
+ writel(0x08, priv->mmio + 0x20c0);
+ writel(0x08, priv->mmio + 0x30c0);
+ writel(0x08, priv->mmio + 0x2058);
+
+ writel(0x10, priv->mmio + 0x2044);
+ writel(0x10, priv->mmio + 0x21a8);
+ writel(0x10, priv->mmio + 0x31a8);
+ writel(0x08, priv->mmio + 0x2058);
+ writel(0x08, priv->mmio + 0x3058);
+ writel(0x40, priv->mmio + 0x205c);
+ writel(0x40, priv->mmio + 0x305c);
+ writel(0x08, priv->mmio + 0x2184);
+ writel(0x08, priv->mmio + 0x3184);
+ writel(0x00, priv->mmio + 0x2150);
+ writel(0x00, priv->mmio + 0x3150);
+ writel(0x10, priv->mmio + 0x20e0);
+ writel(0x00, priv->mmio + 0x21e8);
+ writel(0x00, priv->mmio + 0x31e8);
+
+ return 0;
+ }
+
+ if (en) {
+ /* Lane 0 tx_biasen disable */
+ writel(0x36, priv->mmio + 0x2150);
+
+ /* Lane 0 txdetrx_en disable */
+ writel(0x02, priv->mmio + 0x21e8);
+
+ /* Lane 0 tx_cmenb disable */
+ writel(0x0c, priv->mmio + 0x2080);
+
+ /* Lane 0 rxcm_en disable */
+ writel(0x08, priv->mmio + 0x20c0);
+
+ /* Lane 0 and Lane 1 bg_pwrdn */
+ writel(0x10, priv->mmio + 0x2044);
+
+ /* Lane 0 and Lane 1 rcomp_osenseampen disable */
+ writel(0x08, priv->mmio + 0x2058);
+
+ /* Lane 0 txldo_refsel disable and LDO disable */
+ writel(0x91, priv->mmio + 0x21a8);
+
+ /* Lane 1 LDO disable */
+ writel(0x91, priv->mmio + 0x31a8);
+ } else {
+ /* Lane 0 tx_biasen enable */
+ writel(0x76, priv->mmio + 0x2150);
+
+ /* Lane 0 txdetrx_en enable */
+ writel(0x02, priv->mmio + 0x21e8);
+
+ /* Lane 0 tx_cmenb enable */
+ writel(0x08, priv->mmio + 0x2080);
+
+ /* Lane 0 rxcm_en enable */
+ writel(0x18, priv->mmio + 0x20c0);
+
+ /* Lane 0 and Lane 1 bg_pwrdn */
+ writel(0x00, priv->mmio + 0x2044);
+
+ /* Lane 0 and Lane 1 rcomp_osenseampen enable */
+ writel(0x28, priv->mmio + 0x2058);
+
+ /* Lane 0 txldo_refsel enable and LDO enable */
+ writel(0x01, priv->mmio + 0x21a8);
+
+ /* Lane 1 LDO enable */
+ writel(0x81, priv->mmio + 0x31a8);
+ }
+
+ return 0;
+}
+
+static const struct rockchip_combphy_cfg rk1808_combphy_cfgs = {
+ .grfcfg = {
+ .pipe_l1_sel = { 0x0000, 15, 11, 0x00, 0x1f },
+ .pipe_l1_set = { 0x0008, 13, 8, 0x00, 0x13 },
+ .pipe_l1rxterm_sel = { 0x0000, 12, 12, 0x0, 0x1 },
+ .pipe_l1pd_sel = { 0x0000, 11, 11, 0x0, 0x1},
+ .pipe_l1pd_p3 = { 0x0008, 9, 8, 0x0, 0x3 },
+ .pipe_l0rxterm_sel = { 0x0000, 7, 7, 0x0, 0x1 },
+ .pipe_l0pd_sel = { 0x0000, 6, 6, 0x0, 0x1 },
+ .pipe_l0pd_p3 = { 0x0008, 1, 0, 0x0, 0x3 },
+ .pipe_clk_sel = { 0x0000, 3, 3, 0x0, 0x1 },
+ .pipe_clk_set = { 0x0004, 7, 6, 0x1, 0x0 },
+ .pipe_rate_sel = { 0x0000, 2, 2, 0x0, 0x1 },
+ .pipe_rate_set = { 0x0004, 5, 4, 0x0, 0x1 },
+ .pipe_mode_sel = { 0x0000, 1, 1, 0x0, 0x1 },
+ .pipe_mode_set = { 0x0004, 3, 2, 0x0, 0x1 },
+ .pipe_txrx_sel = { 0x0004, 15, 8, 0x10, 0x2f },
+ .pipe_txrx_set = { 0x0008, 15, 14, 0x0, 0x3 },
+ .pipe_l1rxterm_set = { 0x0008, 10, 10, 0x0, 0x1 },
+ .pipe_l0rxterm_set = { 0x0008, 2, 2, 0x0, 0x1 },
+ .pipe_l0rxelec_set = { 0x0008, 6, 6, 0x0, 0x1 },
+ .pipe_width_sel = { 0x0000, 0, 0, 0x0, 0x1 },
+ .pipe_width_set = { 0x0004, 1, 0, 0x2, 0x0 },
+ .pipe_usb3_sel = { 0x000c, 0, 0, 0x0, 0x1 },
+ .pipe_pll_lock = { 0x0034, 14, 14, 0x0, 0x1 },
+ .pipe_status_l0 = { 0x0034, 7, 7, 0x1, 0x0 },
+ .u3_port_disable = { 0x0434, 0, 0, 0, 1},
+ .u3_port_num = { 0x0434, 15, 12, 0, 1},
+ },
+ .combphy_cfg = rk1808_combphy_cfg,
+ .combphy_low_power_ctrl = rk1808_combphy_low_power_control,
+};
+
+static const struct of_device_id rockchip_combphy_of_match[] = {
+ {
+ .compatible = "rockchip,rk1808-combphy",
+ .data = &rk1808_combphy_cfgs,
+ },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_combphy_of_match);
+
+static struct platform_driver rockchip_combphy_driver = {
+ .probe = rockchip_combphy_probe,
+ .remove = rockchip_combphy_remove,
+ .driver = {
+ .name = "rockchip-combphy",
+ .of_match_table = rockchip_combphy_of_match,
+ },
+};
+module_platform_driver(rockchip_combphy_driver);
+
+MODULE_AUTHOR("Rockchip Inc.");
+MODULE_DESCRIPTION("Rockchip USB3.0 and PCIE COMBPHY driver");
+MODULE_LICENSE("GPL v2");
--
1.9.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 5/6] PCI: rockchip: add DesignWare based PCIe controller
2020-02-13 6:08 [PATCH v2 0/6] Add Rockchip new PCIe controller and combo phy support Shawn Lin
` (3 preceding siblings ...)
2020-02-13 6:08 ` [PATCH v2 4/6] dt-bindings: rockchip: Add DesignWare based PCIe controller Shawn Lin
@ 2020-02-13 6:10 ` Shawn Lin
2020-02-13 6:10 ` [PATCH v2 6/6] MAINTAINERS: Update PCIe drivers for Rockchip Shawn Lin
2020-05-07 15:36 ` [PATCH v2 5/6] PCI: rockchip: add DesignWare based PCIe controller Rob Herring
4 siblings, 2 replies; 13+ messages in thread
From: Shawn Lin @ 2020-02-13 6:10 UTC (permalink / raw)
To: Heiko Stuebner, Lorenzo Pieralisi, Rob Herring,
Kishon Vijay Abraham I, Bjorn Helgaas
Cc: Jingoo Han, linux-pci, devicetree, William Wu, Simon Xue,
linux-rockchip, Shawn Lin
From: Simon Xue <xxm@rock-chips.com>
pcie-dw-rockchip is based on DWC IP. But pcie-rockchip-host
is another IP which is only used for RK3399. So all the following
non-RK3399 SoCs should use this driver.
Signed-off-by: Simon Xue <xxm@rock-chips.com>
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
Changes in v2:
- add commit log
- remove dead code
drivers/pci/controller/Kconfig | 4 +-
drivers/pci/controller/dwc/Kconfig | 9 +
drivers/pci/controller/dwc/Makefile | 1 +
drivers/pci/controller/dwc/pcie-dw-rockchip.c | 439 ++++++++++++++++++++++++++
4 files changed, 451 insertions(+), 2 deletions(-)
create mode 100644 drivers/pci/controller/dwc/pcie-dw-rockchip.c
diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
index 20bf00f..d0bc8c5 100644
--- a/drivers/pci/controller/Kconfig
+++ b/drivers/pci/controller/Kconfig
@@ -190,7 +190,7 @@ config PCIE_ROCKCHIP_HOST
help
Say Y here if you want internal PCI support on Rockchip SoC.
There is 1 internal PCIe port available to support GEN2 with
- 4 slots.
+ 4 slots. Only for RK3399.
config PCIE_ROCKCHIP_EP
bool "Rockchip PCIe endpoint controller"
@@ -202,7 +202,7 @@ config PCIE_ROCKCHIP_EP
help
Say Y here if you want to support Rockchip PCIe controller in
endpoint mode on Rockchip SoC. There is 1 internal PCIe port
- available to support GEN2 with 4 slots.
+ available to support GEN2 with 4 slots. Only for RK3399.
config PCIE_MEDIATEK
tristate "MediaTek PCIe controller"
diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index 0830dfc..9e42a2b 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -209,6 +209,15 @@ config PCIE_ARTPEC6_EP
Enables support for the PCIe controller in the ARTPEC-6 SoC to work in
endpoint mode. This uses the DesignWare core.
+config PCIE_DW_ROCKCHIP_HOST
+ bool "Rockchip DesignWare PCIe controller"
+ select PCIE_DW
+ select PCIE_DW_HOST
+ depends on ARCH_ROCKCHIP
+ depends on OF
+ help
+ Enables support for the DW PCIe controller in the Rockchip SoC.
+
config PCIE_INTEL_GW
bool "Intel Gateway PCIe host controller support"
depends on OF && (X86 || COMPILE_TEST)
diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index 8a637cf..1793e81 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_PCI_LAYERSCAPE_EP) += pci-layerscape-ep.o
obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o
obj-$(CONFIG_PCIE_ARMADA_8K) += pcie-armada8k.o
obj-$(CONFIG_PCIE_ARTPEC6) += pcie-artpec6.o
+obj-$(CONFIG_PCIE_DW_ROCKCHIP_HOST) += pcie-dw-rockchip.o
obj-$(CONFIG_PCIE_INTEL_GW) += pcie-intel-gw.o
obj-$(CONFIG_PCIE_KIRIN) += pcie-kirin.o
obj-$(CONFIG_PCIE_HISI_STB) += pcie-histb.o
diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c
new file mode 100644
index 0000000..df413aa
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c
@@ -0,0 +1,439 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCIe host controller driver for Rockchip SoCs
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * http://www.rock-chips.com
+ *
+ * Author: Simon Xue <xxm@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include "pcie-designware.h"
+
+/*
+ * The upper 16 bits of PCIE_CLIENT_CONFIG are a write
+ * mask for the lower 16 bits. This allows atomic updates
+ * of the register without locking.
+ */
+#define HIWORD_UPDATE(mask, val) (((mask) << 16) | (val))
+#define HIWORD_UPDATE_BIT(val) HIWORD_UPDATE(val, val)
+
+#define to_rockchip_pcie(x) dev_get_drvdata((x)->dev)
+
+#define PCIE_CLIENT_RC_MODE HIWORD_UPDATE_BIT(0x40)
+#define PCIE_CLIENT_ENABLE_LTSSM HIWORD_UPDATE_BIT(0xc)
+#define PCIE_PHY_LINKUP BIT(0)
+#define PCIE_DATA_LINKUP BIT(1)
+#define PCIE_LTSSM_STATE_MASK GENMASK(15, 10)
+#define PCIE_LTSSM_STATE_SHIFT 10
+#define PCIE_L0S_ENTRY 0x11
+#define PCIE_CLIENT_GENERAL_CONTROL 0x0
+#define PCIE_CLIENT_GENERAL_DEBUG 0x104
+#define SUB_PHY_MODE_PCIE_RC 0x0
+#define SUB_PHY_MODE_PCIE_EP 0x1
+
+
+struct reset_bulk_data {
+ const char *id;
+ struct reset_control *rst;
+};
+
+struct rockchip_pcie {
+ struct dw_pcie *pci;
+ void __iomem *dbi_base;
+ void __iomem *apb_base;
+ struct phy *phy;
+ struct clk_bulk_data *clks;
+ unsigned int clk_cnt;
+ struct reset_bulk_data *rsts;
+ struct gpio_desc *rst_gpio;
+ struct pcie_port pp;
+ struct regmap *usb_pcie_grf;
+ enum dw_pcie_device_mode mode;
+ int sub_phy_mode;
+};
+
+struct rockchip_pcie_of_data {
+ enum dw_pcie_device_mode mode;
+};
+
+static inline int rockchip_pcie_readl_apb(struct rockchip_pcie *rockchip,
+ u32 reg)
+{
+ return readl(rockchip->apb_base + reg);
+}
+
+static inline void rockchip_pcie_writel_apb(struct rockchip_pcie *rockchip,
+ u32 reg, u32 val)
+{
+ writel(val, rockchip->apb_base + reg);
+}
+
+static inline void rockchip_pcie_set_mode(struct rockchip_pcie *rockchip)
+{
+ rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_GENERAL_CONTROL,
+ PCIE_CLIENT_RC_MODE);
+}
+
+static inline void rockchip_pcie_enable_ltssm(struct rockchip_pcie *rockchip)
+{
+ rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_GENERAL_CONTROL,
+ PCIE_CLIENT_ENABLE_LTSSM);
+}
+
+static int rockchip_pcie_link_up(struct dw_pcie *pci)
+{
+ struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
+ u32 val = rockchip_pcie_readl_apb(rockchip, PCIE_CLIENT_GENERAL_DEBUG);
+ u32 state = (val & PCIE_LTSSM_STATE_MASK) >> PCIE_LTSSM_STATE_SHIFT;
+
+ if ((val & PCIE_PHY_LINKUP) &&
+ (val & PCIE_DATA_LINKUP) &&
+ state == PCIE_L0S_ENTRY)
+ return 1;
+
+ return 0;
+}
+
+static void rockchip_pcie_establish_link(struct dw_pcie *pci)
+{
+ struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
+
+ if (dw_pcie_link_up(pci)) {
+ dev_err(pci->dev, "link already up\n");
+ return;
+ }
+
+ /* Reset device */
+ gpiod_set_value_cansleep(rockchip->rst_gpio, 0);
+ msleep(100);
+ gpiod_set_value_cansleep(rockchip->rst_gpio, 1);
+
+ rockchip_pcie_enable_ltssm(rockchip);
+}
+
+static int rockchip_pcie_host_init(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+
+ dw_pcie_setup_rc(pp);
+
+ rockchip_pcie_establish_link(pci);
+ dw_pcie_wait_for_link(pci);
+
+ return 0;
+}
+
+static const struct dw_pcie_host_ops rockchip_pcie_host_ops = {
+ .host_init = rockchip_pcie_host_init,
+};
+
+static int rockchip_add_pcie_port(struct rockchip_pcie *rockchip)
+{
+ int ret;
+ struct dw_pcie *pci = rockchip->pci;
+ struct pcie_port *pp = &pci->pp;
+ struct device *dev = pci->dev;
+
+ pp->ops = &rockchip_pcie_host_ops;
+
+ if (device_property_read_bool(dev, "msi-map"))
+ pp->msi_ext = 1;
+
+ rockchip_pcie_set_mode(rockchip);
+
+ ret = dw_pcie_host_init(pp);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void rockchip_pcie_clk_deinit(struct rockchip_pcie *rockchip)
+{
+ clk_bulk_disable(rockchip->clk_cnt, rockchip->clks);
+ clk_bulk_unprepare(rockchip->clk_cnt, rockchip->clks);
+}
+
+static int rockchip_pcie_clk_init(struct rockchip_pcie *rockchip)
+{
+ struct device *dev = rockchip->pci->dev;
+ struct property *prop;
+ const char *name;
+ int i = 0, ret, count;
+
+ count = of_property_count_strings(dev->of_node, "clock-names");
+ if (count < 1)
+ return -ENODEV;
+
+ rockchip->clks = devm_kcalloc(dev, count,
+ sizeof(struct clk_bulk_data),
+ GFP_KERNEL);
+ if (!rockchip->clks)
+ return -ENOMEM;
+
+ rockchip->clk_cnt = count;
+
+ of_property_for_each_string(dev->of_node, "clock-names",
+ prop, name) {
+ rockchip->clks[i].id = name;
+ if (!rockchip->clks[i].id)
+ return -ENOMEM;
+ i++;
+ }
+
+ ret = devm_clk_bulk_get(dev, count, rockchip->clks);
+ if (ret)
+ return ret;
+
+ ret = clk_bulk_prepare(count, rockchip->clks);
+ if (ret)
+ return ret;
+
+ ret = clk_bulk_enable(count, rockchip->clks);
+ if (ret) {
+ clk_bulk_unprepare(count, rockchip->clks);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rockchip_pcie_resource_get(struct platform_device *pdev,
+ struct rockchip_pcie *rockchip)
+{
+ struct resource *dbi_base;
+ struct resource *apb_base;
+
+ dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "pcie-dbi");
+ if (!dbi_base)
+ return -ENODEV;
+
+ rockchip->dbi_base = devm_ioremap_resource(&pdev->dev, dbi_base);
+ if (IS_ERR(rockchip->dbi_base))
+ return PTR_ERR(rockchip->dbi_base);
+
+ rockchip->pci->dbi_base = rockchip->dbi_base;
+
+ apb_base = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "pcie-apb");
+ if (!apb_base)
+ return -ENODEV;
+
+ rockchip->apb_base = devm_ioremap_resource(&pdev->dev, apb_base);
+ if (IS_ERR(rockchip->apb_base))
+ return PTR_ERR(rockchip->apb_base);
+
+ rockchip->rst_gpio = devm_gpiod_get_optional(&pdev->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(rockchip->rst_gpio))
+ return PTR_ERR(rockchip->rst_gpio);
+
+ return 0;
+}
+
+static int rockchip_pcie_phy_init(struct rockchip_pcie *rockchip)
+{
+ int ret;
+ struct device *dev = rockchip->pci->dev;
+
+ rockchip->phy = devm_phy_get(dev, "pcie-phy");
+ if (IS_ERR(rockchip->phy)) {
+ if (PTR_ERR(rockchip->phy) != -EPROBE_DEFER)
+ dev_info(dev, "missing phy\n");
+ return PTR_ERR(rockchip->phy);
+ }
+
+ rockchip->sub_phy_mode = rockchip->mode == DW_PCIE_RC_TYPE ?
+ SUB_PHY_MODE_PCIE_RC : SUB_PHY_MODE_PCIE_EP;
+
+ ret = phy_set_mode_ext(rockchip->phy, PHY_MODE_PCIE,
+ rockchip->sub_phy_mode);
+ if (ret)
+ return ret;
+
+ ret = phy_init(rockchip->phy);
+ if (ret < 0)
+ return ret;
+
+ phy_power_on(rockchip->phy);
+
+ return 0;
+}
+
+static int rockchip_pcie_reset_control_release(struct rockchip_pcie *rockchip)
+{
+ struct device *dev = rockchip->pci->dev;
+ struct property *prop;
+ const char *name;
+ int ret, count, i = 0;
+
+ count = of_property_count_strings(dev->of_node, "reset-names");
+ if (count < 1)
+ return -ENODEV;
+
+ rockchip->rsts = devm_kcalloc(dev, count,
+ sizeof(struct reset_bulk_data),
+ GFP_KERNEL);
+ if (!rockchip->rsts)
+ return -ENOMEM;
+
+ of_property_for_each_string(dev->of_node, "reset-names",
+ prop, name) {
+ rockchip->rsts[i].id = name;
+ if (!rockchip->rsts[i].id)
+ return -ENOMEM;
+ i++;
+ }
+
+ for (i = 0; i < count; i++) {
+ rockchip->rsts[i].rst = devm_reset_control_get_exclusive(dev,
+ rockchip->rsts[i].id);
+ if (IS_ERR(rockchip->rsts[i].rst)) {
+ dev_err(dev, "failed to get %s\n",
+ rockchip->clks[i].id);
+ return PTR_ERR(rockchip->rsts[i].rst);
+ }
+ }
+
+ for (i = 0; i < count; i++) {
+ ret = reset_control_deassert(rockchip->rsts[i].rst);
+ if (ret) {
+ dev_err(dev, "failed to release %s\n",
+ rockchip->rsts[i].id);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int rockchip_pcie_reset_grant_ctrl(struct rockchip_pcie *rockchip,
+ bool enable)
+{
+ int ret;
+ u32 val = HIWORD_UPDATE(BIT(2), 0); /* Write mask bit */
+
+ if (enable)
+ val |= BIT(2);
+
+ ret = regmap_write(rockchip->usb_pcie_grf, 0x0, val);
+ return ret;
+}
+
+static const struct rockchip_pcie_of_data rockchip_rc_of_data = {
+ .mode = DW_PCIE_RC_TYPE,
+};
+
+static const struct of_device_id rockchip_pcie_of_match[] = {
+ {
+ .compatible = "rockchip,rk1808-pcie",
+ .data = &rockchip_rc_of_data,
+ },
+ { /* sentinel */ },
+};
+
+static const struct dw_pcie_ops dw_pcie_ops = {
+ .link_up = rockchip_pcie_link_up,
+};
+
+static int rockchip_pcie_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_pcie *rockchip;
+ struct dw_pcie *pci;
+ int ret;
+ const struct of_device_id *match;
+ const struct rockchip_pcie_of_data *data;
+ enum dw_pcie_device_mode mode;
+
+ match = of_match_device(rockchip_pcie_of_match, dev);
+ if (!match)
+ return -EINVAL;
+
+ data = (struct rockchip_pcie_of_data *)match->data;
+ mode = (enum dw_pcie_device_mode)data->mode;
+
+ rockchip = devm_kzalloc(dev, sizeof(*rockchip), GFP_KERNEL);
+ if (!rockchip)
+ return -ENOMEM;
+
+ pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+ if (!pci)
+ return -ENOMEM;
+
+ pci->dev = dev;
+ pci->ops = &dw_pcie_ops;
+
+ rockchip->mode = mode;
+ rockchip->pci = pci;
+
+ ret = rockchip_pcie_resource_get(pdev, rockchip);
+ if (ret)
+ return ret;
+
+ ret = rockchip_pcie_phy_init(rockchip);
+ if (ret)
+ return ret;
+
+ ret = rockchip_pcie_reset_control_release(rockchip);
+ if (ret)
+ return ret;
+
+ rockchip->usb_pcie_grf =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,usbpciegrf");
+ if (IS_ERR(rockchip->usb_pcie_grf))
+ return PTR_ERR(rockchip->usb_pcie_grf);
+
+ ret = rockchip_pcie_clk_init(rockchip);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, rockchip);
+
+ ret = rockchip_pcie_reset_grant_ctrl(rockchip, true);
+ if (ret)
+ goto deinit_clk;
+
+ if (rockchip->mode == DW_PCIE_RC_TYPE)
+ ret = rockchip_add_pcie_port(rockchip);
+
+ if (ret)
+ goto deinit_clk;
+
+ ret = rockchip_pcie_reset_grant_ctrl(rockchip, false);
+ if (ret)
+ goto deinit_clk;
+
+ return 0;
+
+deinit_clk:
+ rockchip_pcie_clk_deinit(rockchip);
+
+ return ret;
+}
+
+MODULE_DEVICE_TABLE(of, rockchip_pcie_of_match);
+
+static struct platform_driver rockchip_pcie_driver = {
+ .driver = {
+ .name = "rk-pcie",
+ .of_match_table = rockchip_pcie_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = rockchip_pcie_probe,
+};
+
+builtin_platform_driver(rockchip_pcie_driver);
--
1.9.1
^ permalink raw reply related [flat|nested] 13+ messages in thread