18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * COMBPHY driver for HiSilicon STB SoCs
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2016-2017 HiSilicon Co., Ltd. http://www.hisilicon.com
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Authors: Jianguo Sun <sunjianguo1@huawei.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/clk.h>
118c2ecf20Sopenharmony_ci#include <linux/delay.h>
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/kernel.h>
148c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/of_device.h>
178c2ecf20Sopenharmony_ci#include <linux/phy/phy.h>
188c2ecf20Sopenharmony_ci#include <linux/regmap.h>
198c2ecf20Sopenharmony_ci#include <linux/reset.h>
208c2ecf20Sopenharmony_ci#include <dt-bindings/phy/phy.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define COMBPHY_MODE_PCIE		0
238c2ecf20Sopenharmony_ci#define COMBPHY_MODE_USB3		1
248c2ecf20Sopenharmony_ci#define COMBPHY_MODE_SATA		2
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#define COMBPHY_CFG_REG			0x0
278c2ecf20Sopenharmony_ci#define COMBPHY_BYPASS_CODEC		BIT(31)
288c2ecf20Sopenharmony_ci#define COMBPHY_TEST_WRITE		BIT(24)
298c2ecf20Sopenharmony_ci#define COMBPHY_TEST_DATA_SHIFT		20
308c2ecf20Sopenharmony_ci#define COMBPHY_TEST_DATA_MASK		GENMASK(23, 20)
318c2ecf20Sopenharmony_ci#define COMBPHY_TEST_ADDR_SHIFT		12
328c2ecf20Sopenharmony_ci#define COMBPHY_TEST_ADDR_MASK		GENMASK(16, 12)
338c2ecf20Sopenharmony_ci#define COMBPHY_CLKREF_OUT_OEN		BIT(0)
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct histb_combphy_mode {
368c2ecf20Sopenharmony_ci	int fixed;
378c2ecf20Sopenharmony_ci	int select;
388c2ecf20Sopenharmony_ci	u32 reg;
398c2ecf20Sopenharmony_ci	u32 shift;
408c2ecf20Sopenharmony_ci	u32 mask;
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistruct histb_combphy_priv {
448c2ecf20Sopenharmony_ci	void __iomem *mmio;
458c2ecf20Sopenharmony_ci	struct regmap *syscon;
468c2ecf20Sopenharmony_ci	struct reset_control *por_rst;
478c2ecf20Sopenharmony_ci	struct clk *ref_clk;
488c2ecf20Sopenharmony_ci	struct phy *phy;
498c2ecf20Sopenharmony_ci	struct histb_combphy_mode mode;
508c2ecf20Sopenharmony_ci};
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic void nano_register_write(struct histb_combphy_priv *priv,
538c2ecf20Sopenharmony_ci				u32 addr, u32 data)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	void __iomem *reg = priv->mmio + COMBPHY_CFG_REG;
568c2ecf20Sopenharmony_ci	u32 val;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	/* Set up address and data for the write */
598c2ecf20Sopenharmony_ci	val = readl(reg);
608c2ecf20Sopenharmony_ci	val &= ~COMBPHY_TEST_ADDR_MASK;
618c2ecf20Sopenharmony_ci	val |= addr << COMBPHY_TEST_ADDR_SHIFT;
628c2ecf20Sopenharmony_ci	val &= ~COMBPHY_TEST_DATA_MASK;
638c2ecf20Sopenharmony_ci	val |= data << COMBPHY_TEST_DATA_SHIFT;
648c2ecf20Sopenharmony_ci	writel(val, reg);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	/* Flip strobe control to trigger the write */
678c2ecf20Sopenharmony_ci	val &= ~COMBPHY_TEST_WRITE;
688c2ecf20Sopenharmony_ci	writel(val, reg);
698c2ecf20Sopenharmony_ci	val |= COMBPHY_TEST_WRITE;
708c2ecf20Sopenharmony_ci	writel(val, reg);
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int is_mode_fixed(struct histb_combphy_mode *mode)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	return (mode->fixed != PHY_NONE) ? true : false;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic int histb_combphy_set_mode(struct histb_combphy_priv *priv)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	struct histb_combphy_mode *mode = &priv->mode;
818c2ecf20Sopenharmony_ci	struct regmap *syscon = priv->syscon;
828c2ecf20Sopenharmony_ci	u32 hw_sel;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	if (is_mode_fixed(mode))
858c2ecf20Sopenharmony_ci		return 0;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	switch (mode->select) {
888c2ecf20Sopenharmony_ci	case PHY_TYPE_SATA:
898c2ecf20Sopenharmony_ci		hw_sel = COMBPHY_MODE_SATA;
908c2ecf20Sopenharmony_ci		break;
918c2ecf20Sopenharmony_ci	case PHY_TYPE_PCIE:
928c2ecf20Sopenharmony_ci		hw_sel = COMBPHY_MODE_PCIE;
938c2ecf20Sopenharmony_ci		break;
948c2ecf20Sopenharmony_ci	case PHY_TYPE_USB3:
958c2ecf20Sopenharmony_ci		hw_sel = COMBPHY_MODE_USB3;
968c2ecf20Sopenharmony_ci		break;
978c2ecf20Sopenharmony_ci	default:
988c2ecf20Sopenharmony_ci		return -EINVAL;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	return regmap_update_bits(syscon, mode->reg, mode->mask,
1028c2ecf20Sopenharmony_ci				  hw_sel << mode->shift);
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic int histb_combphy_init(struct phy *phy)
1068c2ecf20Sopenharmony_ci{
1078c2ecf20Sopenharmony_ci	struct histb_combphy_priv *priv = phy_get_drvdata(phy);
1088c2ecf20Sopenharmony_ci	u32 val;
1098c2ecf20Sopenharmony_ci	int ret;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	ret = histb_combphy_set_mode(priv);
1128c2ecf20Sopenharmony_ci	if (ret)
1138c2ecf20Sopenharmony_ci		return ret;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	/* Clear bypass bit to enable encoding/decoding */
1168c2ecf20Sopenharmony_ci	val = readl(priv->mmio + COMBPHY_CFG_REG);
1178c2ecf20Sopenharmony_ci	val &= ~COMBPHY_BYPASS_CODEC;
1188c2ecf20Sopenharmony_ci	writel(val, priv->mmio + COMBPHY_CFG_REG);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(priv->ref_clk);
1218c2ecf20Sopenharmony_ci	if (ret)
1228c2ecf20Sopenharmony_ci		return ret;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	reset_control_deassert(priv->por_rst);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	/* Enable EP clock */
1278c2ecf20Sopenharmony_ci	val = readl(priv->mmio + COMBPHY_CFG_REG);
1288c2ecf20Sopenharmony_ci	val |= COMBPHY_CLKREF_OUT_OEN;
1298c2ecf20Sopenharmony_ci	writel(val, priv->mmio + COMBPHY_CFG_REG);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* Need to wait for EP clock stable */
1328c2ecf20Sopenharmony_ci	mdelay(5);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	/* Configure nano phy registers as suggested by vendor */
1358c2ecf20Sopenharmony_ci	nano_register_write(priv, 0x1, 0x8);
1368c2ecf20Sopenharmony_ci	nano_register_write(priv, 0xc, 0x9);
1378c2ecf20Sopenharmony_ci	nano_register_write(priv, 0x1a, 0x4);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	return 0;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic int histb_combphy_exit(struct phy *phy)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	struct histb_combphy_priv *priv = phy_get_drvdata(phy);
1458c2ecf20Sopenharmony_ci	u32 val;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	/* Disable EP clock */
1488c2ecf20Sopenharmony_ci	val = readl(priv->mmio + COMBPHY_CFG_REG);
1498c2ecf20Sopenharmony_ci	val &= ~COMBPHY_CLKREF_OUT_OEN;
1508c2ecf20Sopenharmony_ci	writel(val, priv->mmio + COMBPHY_CFG_REG);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	reset_control_assert(priv->por_rst);
1538c2ecf20Sopenharmony_ci	clk_disable_unprepare(priv->ref_clk);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	return 0;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistatic const struct phy_ops histb_combphy_ops = {
1598c2ecf20Sopenharmony_ci	.init = histb_combphy_init,
1608c2ecf20Sopenharmony_ci	.exit = histb_combphy_exit,
1618c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
1628c2ecf20Sopenharmony_ci};
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic struct phy *histb_combphy_xlate(struct device *dev,
1658c2ecf20Sopenharmony_ci				       struct of_phandle_args *args)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	struct histb_combphy_priv *priv = dev_get_drvdata(dev);
1688c2ecf20Sopenharmony_ci	struct histb_combphy_mode *mode = &priv->mode;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	if (args->args_count < 1) {
1718c2ecf20Sopenharmony_ci		dev_err(dev, "invalid number of arguments\n");
1728c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	mode->select = args->args[0];
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	if (mode->select < PHY_TYPE_SATA || mode->select > PHY_TYPE_USB3) {
1788c2ecf20Sopenharmony_ci		dev_err(dev, "invalid phy mode select argument\n");
1798c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	if (is_mode_fixed(mode) && mode->select != mode->fixed) {
1838c2ecf20Sopenharmony_ci		dev_err(dev, "mode select %d mismatch fixed phy mode %d\n",
1848c2ecf20Sopenharmony_ci			mode->select, mode->fixed);
1858c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
1868c2ecf20Sopenharmony_ci	}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	return priv->phy;
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_cistatic int histb_combphy_probe(struct platform_device *pdev)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	struct phy_provider *phy_provider;
1948c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1958c2ecf20Sopenharmony_ci	struct histb_combphy_priv *priv;
1968c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
1978c2ecf20Sopenharmony_ci	struct histb_combphy_mode *mode;
1988c2ecf20Sopenharmony_ci	u32 vals[3];
1998c2ecf20Sopenharmony_ci	int ret;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
2028c2ecf20Sopenharmony_ci	if (!priv)
2038c2ecf20Sopenharmony_ci		return -ENOMEM;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	priv->mmio = devm_platform_ioremap_resource(pdev, 0);
2068c2ecf20Sopenharmony_ci	if (IS_ERR(priv->mmio)) {
2078c2ecf20Sopenharmony_ci		ret = PTR_ERR(priv->mmio);
2088c2ecf20Sopenharmony_ci		return ret;
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	priv->syscon = syscon_node_to_regmap(np->parent);
2128c2ecf20Sopenharmony_ci	if (IS_ERR(priv->syscon)) {
2138c2ecf20Sopenharmony_ci		dev_err(dev, "failed to find peri_ctrl syscon regmap\n");
2148c2ecf20Sopenharmony_ci		return PTR_ERR(priv->syscon);
2158c2ecf20Sopenharmony_ci	}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	mode = &priv->mode;
2188c2ecf20Sopenharmony_ci	mode->fixed = PHY_NONE;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	ret = of_property_read_u32(np, "hisilicon,fixed-mode", &mode->fixed);
2218c2ecf20Sopenharmony_ci	if (ret == 0)
2228c2ecf20Sopenharmony_ci		dev_dbg(dev, "found fixed phy mode %d\n", mode->fixed);
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	ret = of_property_read_u32_array(np, "hisilicon,mode-select-bits",
2258c2ecf20Sopenharmony_ci					 vals, ARRAY_SIZE(vals));
2268c2ecf20Sopenharmony_ci	if (ret == 0) {
2278c2ecf20Sopenharmony_ci		if (is_mode_fixed(mode)) {
2288c2ecf20Sopenharmony_ci			dev_err(dev, "found select bits for fixed mode phy\n");
2298c2ecf20Sopenharmony_ci			return -EINVAL;
2308c2ecf20Sopenharmony_ci		}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci		mode->reg = vals[0];
2338c2ecf20Sopenharmony_ci		mode->shift = vals[1];
2348c2ecf20Sopenharmony_ci		mode->mask = vals[2];
2358c2ecf20Sopenharmony_ci		dev_dbg(dev, "found mode select bits\n");
2368c2ecf20Sopenharmony_ci	} else {
2378c2ecf20Sopenharmony_ci		if (!is_mode_fixed(mode)) {
2388c2ecf20Sopenharmony_ci			dev_err(dev, "no valid select bits found for non-fixed phy\n");
2398c2ecf20Sopenharmony_ci			return -ENODEV;
2408c2ecf20Sopenharmony_ci		}
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	priv->ref_clk = devm_clk_get(dev, NULL);
2448c2ecf20Sopenharmony_ci	if (IS_ERR(priv->ref_clk)) {
2458c2ecf20Sopenharmony_ci		dev_err(dev, "failed to find ref clock\n");
2468c2ecf20Sopenharmony_ci		return PTR_ERR(priv->ref_clk);
2478c2ecf20Sopenharmony_ci	}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	priv->por_rst = devm_reset_control_get(dev, NULL);
2508c2ecf20Sopenharmony_ci	if (IS_ERR(priv->por_rst)) {
2518c2ecf20Sopenharmony_ci		dev_err(dev, "failed to get poweron reset\n");
2528c2ecf20Sopenharmony_ci		return PTR_ERR(priv->por_rst);
2538c2ecf20Sopenharmony_ci	}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	priv->phy = devm_phy_create(dev, NULL, &histb_combphy_ops);
2568c2ecf20Sopenharmony_ci	if (IS_ERR(priv->phy)) {
2578c2ecf20Sopenharmony_ci		dev_err(dev, "failed to create combphy\n");
2588c2ecf20Sopenharmony_ci		return PTR_ERR(priv->phy);
2598c2ecf20Sopenharmony_ci	}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, priv);
2628c2ecf20Sopenharmony_ci	phy_set_drvdata(priv->phy, priv);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	phy_provider = devm_of_phy_provider_register(dev, histb_combphy_xlate);
2658c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(phy_provider);
2668c2ecf20Sopenharmony_ci}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistatic const struct of_device_id histb_combphy_of_match[] = {
2698c2ecf20Sopenharmony_ci	{ .compatible = "hisilicon,hi3798cv200-combphy" },
2708c2ecf20Sopenharmony_ci	{ },
2718c2ecf20Sopenharmony_ci};
2728c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, histb_combphy_of_match);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_cistatic struct platform_driver histb_combphy_driver = {
2758c2ecf20Sopenharmony_ci	.probe	= histb_combphy_probe,
2768c2ecf20Sopenharmony_ci	.driver = {
2778c2ecf20Sopenharmony_ci		.name = "combphy",
2788c2ecf20Sopenharmony_ci		.of_match_table = histb_combphy_of_match,
2798c2ecf20Sopenharmony_ci	},
2808c2ecf20Sopenharmony_ci};
2818c2ecf20Sopenharmony_cimodule_platform_driver(histb_combphy_driver);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HiSilicon STB COMBPHY driver");
2848c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
285