Commit c6d9b132 authored by Kunihiko Hayashi's avatar Kunihiko Hayashi Committed by Kishon Vijay Abraham I

phy: socionext: add PCIe PHY driver support

Add a driver for PHY interface built into PCIe controller implemented
in UniPhier SoCs.
Signed-off-by: default avatarKunihiko Hayashi <hayashi.kunihiko@socionext.com>
Signed-off-by: default avatarKishon Vijay Abraham I <kishon@ti.com>
parent 74c60cd9
...@@ -23,3 +23,12 @@ config PHY_UNIPHIER_USB3 ...@@ -23,3 +23,12 @@ config PHY_UNIPHIER_USB3
help help
Enable this to support USB PHY implemented in USB3 controller Enable this to support USB PHY implemented in USB3 controller
on UniPhier SoCs. This controller supports USB3.0 and lower speed. on UniPhier SoCs. This controller supports USB3.0 and lower speed.
config PHY_UNIPHIER_PCIE
tristate "Uniphier PHY driver for PCIe controller"
depends on (ARCH_UNIPHIER || COMPILE_TEST) && OF
default PCIE_UNIPHIER
select GENERIC_PHY
help
Enable this to support PHY implemented in PCIe controller
on UniPhier SoCs. This driver supports LD20 and PXs3 SoCs.
...@@ -5,3 +5,4 @@ ...@@ -5,3 +5,4 @@
obj-$(CONFIG_PHY_UNIPHIER_USB2) += phy-uniphier-usb2.o obj-$(CONFIG_PHY_UNIPHIER_USB2) += phy-uniphier-usb2.o
obj-$(CONFIG_PHY_UNIPHIER_USB3) += phy-uniphier-usb3hs.o phy-uniphier-usb3ss.o obj-$(CONFIG_PHY_UNIPHIER_USB3) += phy-uniphier-usb3hs.o phy-uniphier-usb3ss.o
obj-$(CONFIG_PHY_UNIPHIER_PCIE) += phy-uniphier-pcie.o
// 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_TEST_I 0x2000
#define PCL_PHY_TEST_O 0x2004
#define TESTI_DAT_MASK GENMASK(13, 6)
#define TESTI_ADR_MASK GENMASK(5, 1)
#define TESTI_WR_EN BIT(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)
#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
struct uniphier_pciephy_priv {
void __iomem *base;
struct device *dev;
struct clk *clk;
struct reset_control *rst;
const struct uniphier_pciephy_soc_data *data;
};
struct uniphier_pciephy_soc_data {
bool has_syscon;
};
static void uniphier_pciephy_testio_write(struct uniphier_pciephy_priv *priv,
u32 data)
{
/* 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 void uniphier_pciephy_set_param(struct uniphier_pciephy_priv *priv,
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, val);
val = readl(priv->base + PCL_PHY_TEST_O);
/* update value */
val &= ~FIELD_PREP(TESTI_DAT_MASK, mask);
val = FIELD_PREP(TESTI_DAT_MASK, mask & param);
val |= FIELD_PREP(TESTI_ADR_MASK, reg);
uniphier_pciephy_testio_write(priv, val);
uniphier_pciephy_testio_write(priv, val | TESTI_WR_EN);
uniphier_pciephy_testio_write(priv, 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, val);
readl(priv->base + PCL_PHY_TEST_O);
}
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);
int ret;
ret = clk_prepare_enable(priv->clk);
if (ret)
return ret;
ret = reset_control_deassert(priv->rst);
if (ret)
goto out_clk_disable;
uniphier_pciephy_set_param(priv, PCL_PHY_R00,
RX_EQ_ADJ_EN, RX_EQ_ADJ_EN);
uniphier_pciephy_set_param(priv, PCL_PHY_R06, RX_EQ_ADJ,
FIELD_PREP(RX_EQ_ADJ, RX_EQ_ADJ_VAL));
uniphier_pciephy_set_param(priv, PCL_PHY_R26, VCO_CTRL,
FIELD_PREP(VCO_CTRL, VCO_CTRL_INIT_VAL));
usleep_range(1, 10);
uniphier_pciephy_deassert(priv);
usleep_range(1, 10);
return 0;
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);
uniphier_pciephy_assert(priv);
reset_control_assert(priv->rst);
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 resource *res;
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;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(dev, res);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
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->has_syscon)
regmap_update_bits(regmap, SG_USBPCIESEL,
SG_USBPCIESEL_PCIE, SG_USBPCIESEL_PCIE);
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 uniphier_pciephy_soc_data uniphier_ld20_data = {
.has_syscon = true,
};
static const struct uniphier_pciephy_soc_data uniphier_pxs3_data = {
.has_syscon = false,
};
static const struct of_device_id uniphier_pciephy_match[] = {
{
.compatible = "socionext,uniphier-ld20-pcie-phy",
.data = &uniphier_ld20_data,
},
{
.compatible = "socionext,uniphier-pxs3-pcie-phy",
.data = &uniphier_pxs3_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");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment