// SPDX-License-Identifier: GPL-2.0 /* * phy-uniphier-pcie.c - PHY driver for UniPhier PCIe controller * Copyright 2018, Socionext Inc. * Author: Kunihiko Hayashi <hayashi.kunihiko@socionext.com> */ #include <linux/bitops.h> #include <linux/bitfield.h> #include <linux/clk.h> #include <linux/iopoll.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/reset.h> #include <linux/resource.h> /* PHY */ #define PCL_PHY_CLKCTRL 0x0000 #define PORT_SEL_MASK GENMASK(11, 9) #define PORT_SEL_1 FIELD_PREP(PORT_SEL_MASK, 1) #define PCL_PHY_TEST_I 0x2000 #define TESTI_DAT_MASK GENMASK(13, 6) #define TESTI_ADR_MASK GENMASK(5, 1) #define TESTI_WR_EN BIT(0) #define TESTIO_PHY_SHIFT 16 #define PCL_PHY_TEST_O 0x2004 #define TESTO_DAT_MASK GENMASK(7, 0) #define PCL_PHY_RESET 0x200c #define PCL_PHY_RESET_N_MNMODE BIT(8) /* =1:manual */ #define PCL_PHY_RESET_N BIT(0) /* =1:deasssert */ /* SG */ #define SG_USBPCIESEL 0x590 #define SG_USBPCIESEL_PCIE BIT(0) /* SC */ #define SC_US3SRCSEL 0x2244 #define SC_US3SRCSEL_2LANE GENMASK(9, 8) #define PCL_PHY_R00 0 #define RX_EQ_ADJ_EN BIT(3) /* enable for EQ adjustment */ #define PCL_PHY_R06 6 #define RX_EQ_ADJ GENMASK(5, 0) /* EQ adjustment value */ #define RX_EQ_ADJ_VAL 0 #define PCL_PHY_R26 26 #define VCO_CTRL GENMASK(7, 4) /* Tx VCO adjustment value */ #define VCO_CTRL_INIT_VAL 5 #define PCL_PHY_R28 28 #define VCOPLL_CLMP GENMASK(3, 2) /* Tx VCOPLL clamp mode */ #define VCOPLL_CLMP_VAL 0 struct uniphier_pciephy_priv { void __iomem *base; struct device *dev; struct clk *clk, *clk_gio; struct reset_control *rst, *rst_gio; const struct uniphier_pciephy_soc_data *data; }; struct uniphier_pciephy_soc_data { bool is_legacy; bool is_dual_phy; void (*set_phymode)(struct regmap *regmap); }; static void uniphier_pciephy_testio_write(struct uniphier_pciephy_priv *priv, int id, u32 data) { if (id) data <<= TESTIO_PHY_SHIFT; /* need to read TESTO twice after accessing TESTI */ writel(data, priv->base + PCL_PHY_TEST_I); readl(priv->base + PCL_PHY_TEST_O); readl(priv->base + PCL_PHY_TEST_O); } static u32 uniphier_pciephy_testio_read(struct uniphier_pciephy_priv *priv, int id) { u32 val = readl(priv->base + PCL_PHY_TEST_O); if (id) val >>= TESTIO_PHY_SHIFT; return val & TESTO_DAT_MASK; } static void uniphier_pciephy_set_param(struct uniphier_pciephy_priv *priv, int id, u32 reg, u32 mask, u32 param) { u32 val; /* read previous data */ val = FIELD_PREP(TESTI_DAT_MASK, 1); val |= FIELD_PREP(TESTI_ADR_MASK, reg); uniphier_pciephy_testio_write(priv, id, val); val = uniphier_pciephy_testio_read(priv, id); /* update value */ val &= ~mask; val |= mask & param; val = FIELD_PREP(TESTI_DAT_MASK, val); val |= FIELD_PREP(TESTI_ADR_MASK, reg); uniphier_pciephy_testio_write(priv, id, val); uniphier_pciephy_testio_write(priv, id, val | TESTI_WR_EN); uniphier_pciephy_testio_write(priv, id, val); /* read current data as dummy */ val = FIELD_PREP(TESTI_DAT_MASK, 1); val |= FIELD_PREP(TESTI_ADR_MASK, reg); uniphier_pciephy_testio_write(priv, id, val); uniphier_pciephy_testio_read(priv, id); } static void uniphier_pciephy_assert(struct uniphier_pciephy_priv *priv) { u32 val; val = readl(priv->base + PCL_PHY_RESET); val &= ~PCL_PHY_RESET_N; val |= PCL_PHY_RESET_N_MNMODE; writel(val, priv->base + PCL_PHY_RESET); } static void uniphier_pciephy_deassert(struct uniphier_pciephy_priv *priv) { u32 val; val = readl(priv->base + PCL_PHY_RESET); val |= PCL_PHY_RESET_N_MNMODE | PCL_PHY_RESET_N; writel(val, priv->base + PCL_PHY_RESET); } static int uniphier_pciephy_init(struct phy *phy) { struct uniphier_pciephy_priv *priv = phy_get_drvdata(phy); u32 val; int ret, id; ret = clk_prepare_enable(priv->clk); if (ret) return ret; ret = clk_prepare_enable(priv->clk_gio); if (ret) goto out_clk_disable; ret = reset_control_deassert(priv->rst); if (ret) goto out_clk_gio_disable; ret = reset_control_deassert(priv->rst_gio); if (ret) goto out_rst_assert; /* support only 1 port */ val = readl(priv->base + PCL_PHY_CLKCTRL); val &= ~PORT_SEL_MASK; val |= PORT_SEL_1; writel(val, priv->base + PCL_PHY_CLKCTRL); /* legacy controller doesn't have phy_reset and parameters */ if (priv->data->is_legacy) return 0; for (id = 0; id < (priv->data->is_dual_phy ? 2 : 1); id++) { uniphier_pciephy_set_param(priv, id, PCL_PHY_R00, RX_EQ_ADJ_EN, RX_EQ_ADJ_EN); uniphier_pciephy_set_param(priv, id, PCL_PHY_R06, RX_EQ_ADJ, FIELD_PREP(RX_EQ_ADJ, RX_EQ_ADJ_VAL)); uniphier_pciephy_set_param(priv, id, PCL_PHY_R26, VCO_CTRL, FIELD_PREP(VCO_CTRL, VCO_CTRL_INIT_VAL)); uniphier_pciephy_set_param(priv, id, PCL_PHY_R28, VCOPLL_CLMP, FIELD_PREP(VCOPLL_CLMP, VCOPLL_CLMP_VAL)); } usleep_range(1, 10); uniphier_pciephy_deassert(priv); usleep_range(1, 10); return 0; out_rst_assert: reset_control_assert(priv->rst); out_clk_gio_disable: clk_disable_unprepare(priv->clk_gio); out_clk_disable: clk_disable_unprepare(priv->clk); return ret; } static int uniphier_pciephy_exit(struct phy *phy) { struct uniphier_pciephy_priv *priv = phy_get_drvdata(phy); if (!priv->data->is_legacy) uniphier_pciephy_assert(priv); reset_control_assert(priv->rst_gio); reset_control_assert(priv->rst); clk_disable_unprepare(priv->clk_gio); clk_disable_unprepare(priv->clk); return 0; } static const struct phy_ops uniphier_pciephy_ops = { .init = uniphier_pciephy_init, .exit = uniphier_pciephy_exit, .owner = THIS_MODULE, }; static int uniphier_pciephy_probe(struct platform_device *pdev) { struct uniphier_pciephy_priv *priv; struct phy_provider *phy_provider; struct device *dev = &pdev->dev; struct regmap *regmap; struct phy *phy; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->data = of_device_get_match_data(dev); if (WARN_ON(!priv->data)) return -EINVAL; priv->dev = dev; priv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); if (priv->data->is_legacy) { priv->clk_gio = devm_clk_get(dev, "gio"); if (IS_ERR(priv->clk_gio)) return PTR_ERR(priv->clk_gio); priv->rst_gio = devm_reset_control_get_shared(dev, "gio"); if (IS_ERR(priv->rst_gio)) return PTR_ERR(priv->rst_gio); priv->clk = devm_clk_get(dev, "link"); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); priv->rst = devm_reset_control_get_shared(dev, "link"); if (IS_ERR(priv->rst)) return PTR_ERR(priv->rst); } else { priv->clk = devm_clk_get(dev, NULL); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); priv->rst = devm_reset_control_get_shared(dev, NULL); if (IS_ERR(priv->rst)) return PTR_ERR(priv->rst); } phy = devm_phy_create(dev, dev->of_node, &uniphier_pciephy_ops); if (IS_ERR(phy)) return PTR_ERR(phy); regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "socionext,syscon"); if (!IS_ERR(regmap) && priv->data->set_phymode) priv->data->set_phymode(regmap); 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 void uniphier_pciephy_ld20_setmode(struct regmap *regmap) { regmap_update_bits(regmap, SG_USBPCIESEL, SG_USBPCIESEL_PCIE, SG_USBPCIESEL_PCIE); } static void uniphier_pciephy_nx1_setmode(struct regmap *regmap) { regmap_update_bits(regmap, SC_US3SRCSEL, SC_US3SRCSEL_2LANE, SC_US3SRCSEL_2LANE); } static const struct uniphier_pciephy_soc_data uniphier_pro5_data = { .is_legacy = true, }; static const struct uniphier_pciephy_soc_data uniphier_ld20_data = { .is_legacy = false, .is_dual_phy = false, .set_phymode = uniphier_pciephy_ld20_setmode, }; static const struct uniphier_pciephy_soc_data uniphier_pxs3_data = { .is_legacy = false, .is_dual_phy = false, }; static const struct uniphier_pciephy_soc_data uniphier_nx1_data = { .is_legacy = false, .is_dual_phy = true, .set_phymode = uniphier_pciephy_nx1_setmode, }; static const struct of_device_id uniphier_pciephy_match[] = { { .compatible = "socionext,uniphier-pro5-pcie-phy", .data = &uniphier_pro5_data, }, { .compatible = "socionext,uniphier-ld20-pcie-phy", .data = &uniphier_ld20_data, }, { .compatible = "socionext,uniphier-pxs3-pcie-phy", .data = &uniphier_pxs3_data, }, { .compatible = "socionext,uniphier-nx1-pcie-phy", .data = &uniphier_nx1_data, }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, uniphier_pciephy_match); static struct platform_driver uniphier_pciephy_driver = { .probe = uniphier_pciephy_probe, .driver = { .name = "uniphier-pcie-phy", .of_match_table = uniphier_pciephy_match, }, }; module_platform_driver(uniphier_pciephy_driver); MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>"); MODULE_DESCRIPTION("UniPhier PHY driver for PCIe controller"); MODULE_LICENSE("GPL v2");