162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * COMBPHY driver for HiSilicon STB SoCs 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016-2017 HiSilicon Co., Ltd. http://www.hisilicon.com 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Authors: Jianguo Sun <sunjianguo1@huawei.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/clk.h> 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include <linux/io.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/of.h> 1762306a36Sopenharmony_ci#include <linux/phy/phy.h> 1862306a36Sopenharmony_ci#include <linux/platform_device.h> 1962306a36Sopenharmony_ci#include <linux/regmap.h> 2062306a36Sopenharmony_ci#include <linux/reset.h> 2162306a36Sopenharmony_ci#include <dt-bindings/phy/phy.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define COMBPHY_MODE_PCIE 0 2462306a36Sopenharmony_ci#define COMBPHY_MODE_USB3 1 2562306a36Sopenharmony_ci#define COMBPHY_MODE_SATA 2 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define COMBPHY_CFG_REG 0x0 2862306a36Sopenharmony_ci#define COMBPHY_BYPASS_CODEC BIT(31) 2962306a36Sopenharmony_ci#define COMBPHY_TEST_WRITE BIT(24) 3062306a36Sopenharmony_ci#define COMBPHY_TEST_DATA_SHIFT 20 3162306a36Sopenharmony_ci#define COMBPHY_TEST_DATA_MASK GENMASK(23, 20) 3262306a36Sopenharmony_ci#define COMBPHY_TEST_ADDR_SHIFT 12 3362306a36Sopenharmony_ci#define COMBPHY_TEST_ADDR_MASK GENMASK(16, 12) 3462306a36Sopenharmony_ci#define COMBPHY_CLKREF_OUT_OEN BIT(0) 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistruct histb_combphy_mode { 3762306a36Sopenharmony_ci int fixed; 3862306a36Sopenharmony_ci int select; 3962306a36Sopenharmony_ci u32 reg; 4062306a36Sopenharmony_ci u32 shift; 4162306a36Sopenharmony_ci u32 mask; 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct histb_combphy_priv { 4562306a36Sopenharmony_ci void __iomem *mmio; 4662306a36Sopenharmony_ci struct regmap *syscon; 4762306a36Sopenharmony_ci struct reset_control *por_rst; 4862306a36Sopenharmony_ci struct clk *ref_clk; 4962306a36Sopenharmony_ci struct phy *phy; 5062306a36Sopenharmony_ci struct histb_combphy_mode mode; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic void nano_register_write(struct histb_combphy_priv *priv, 5462306a36Sopenharmony_ci u32 addr, u32 data) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci void __iomem *reg = priv->mmio + COMBPHY_CFG_REG; 5762306a36Sopenharmony_ci u32 val; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci /* Set up address and data for the write */ 6062306a36Sopenharmony_ci val = readl(reg); 6162306a36Sopenharmony_ci val &= ~COMBPHY_TEST_ADDR_MASK; 6262306a36Sopenharmony_ci val |= addr << COMBPHY_TEST_ADDR_SHIFT; 6362306a36Sopenharmony_ci val &= ~COMBPHY_TEST_DATA_MASK; 6462306a36Sopenharmony_ci val |= data << COMBPHY_TEST_DATA_SHIFT; 6562306a36Sopenharmony_ci writel(val, reg); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* Flip strobe control to trigger the write */ 6862306a36Sopenharmony_ci val &= ~COMBPHY_TEST_WRITE; 6962306a36Sopenharmony_ci writel(val, reg); 7062306a36Sopenharmony_ci val |= COMBPHY_TEST_WRITE; 7162306a36Sopenharmony_ci writel(val, reg); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic int is_mode_fixed(struct histb_combphy_mode *mode) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci return (mode->fixed != PHY_NONE) ? true : false; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic int histb_combphy_set_mode(struct histb_combphy_priv *priv) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct histb_combphy_mode *mode = &priv->mode; 8262306a36Sopenharmony_ci struct regmap *syscon = priv->syscon; 8362306a36Sopenharmony_ci u32 hw_sel; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (is_mode_fixed(mode)) 8662306a36Sopenharmony_ci return 0; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci switch (mode->select) { 8962306a36Sopenharmony_ci case PHY_TYPE_SATA: 9062306a36Sopenharmony_ci hw_sel = COMBPHY_MODE_SATA; 9162306a36Sopenharmony_ci break; 9262306a36Sopenharmony_ci case PHY_TYPE_PCIE: 9362306a36Sopenharmony_ci hw_sel = COMBPHY_MODE_PCIE; 9462306a36Sopenharmony_ci break; 9562306a36Sopenharmony_ci case PHY_TYPE_USB3: 9662306a36Sopenharmony_ci hw_sel = COMBPHY_MODE_USB3; 9762306a36Sopenharmony_ci break; 9862306a36Sopenharmony_ci default: 9962306a36Sopenharmony_ci return -EINVAL; 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci return regmap_update_bits(syscon, mode->reg, mode->mask, 10362306a36Sopenharmony_ci hw_sel << mode->shift); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic int histb_combphy_init(struct phy *phy) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci struct histb_combphy_priv *priv = phy_get_drvdata(phy); 10962306a36Sopenharmony_ci u32 val; 11062306a36Sopenharmony_ci int ret; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci ret = histb_combphy_set_mode(priv); 11362306a36Sopenharmony_ci if (ret) 11462306a36Sopenharmony_ci return ret; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* Clear bypass bit to enable encoding/decoding */ 11762306a36Sopenharmony_ci val = readl(priv->mmio + COMBPHY_CFG_REG); 11862306a36Sopenharmony_ci val &= ~COMBPHY_BYPASS_CODEC; 11962306a36Sopenharmony_ci writel(val, priv->mmio + COMBPHY_CFG_REG); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci ret = clk_prepare_enable(priv->ref_clk); 12262306a36Sopenharmony_ci if (ret) 12362306a36Sopenharmony_ci return ret; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci reset_control_deassert(priv->por_rst); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* Enable EP clock */ 12862306a36Sopenharmony_ci val = readl(priv->mmio + COMBPHY_CFG_REG); 12962306a36Sopenharmony_ci val |= COMBPHY_CLKREF_OUT_OEN; 13062306a36Sopenharmony_ci writel(val, priv->mmio + COMBPHY_CFG_REG); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* Need to wait for EP clock stable */ 13362306a36Sopenharmony_ci mdelay(5); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci /* Configure nano phy registers as suggested by vendor */ 13662306a36Sopenharmony_ci nano_register_write(priv, 0x1, 0x8); 13762306a36Sopenharmony_ci nano_register_write(priv, 0xc, 0x9); 13862306a36Sopenharmony_ci nano_register_write(priv, 0x1a, 0x4); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int histb_combphy_exit(struct phy *phy) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci struct histb_combphy_priv *priv = phy_get_drvdata(phy); 14662306a36Sopenharmony_ci u32 val; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* Disable EP clock */ 14962306a36Sopenharmony_ci val = readl(priv->mmio + COMBPHY_CFG_REG); 15062306a36Sopenharmony_ci val &= ~COMBPHY_CLKREF_OUT_OEN; 15162306a36Sopenharmony_ci writel(val, priv->mmio + COMBPHY_CFG_REG); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci reset_control_assert(priv->por_rst); 15462306a36Sopenharmony_ci clk_disable_unprepare(priv->ref_clk); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci return 0; 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic const struct phy_ops histb_combphy_ops = { 16062306a36Sopenharmony_ci .init = histb_combphy_init, 16162306a36Sopenharmony_ci .exit = histb_combphy_exit, 16262306a36Sopenharmony_ci .owner = THIS_MODULE, 16362306a36Sopenharmony_ci}; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic struct phy *histb_combphy_xlate(struct device *dev, 16662306a36Sopenharmony_ci struct of_phandle_args *args) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct histb_combphy_priv *priv = dev_get_drvdata(dev); 16962306a36Sopenharmony_ci struct histb_combphy_mode *mode = &priv->mode; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci if (args->args_count < 1) { 17262306a36Sopenharmony_ci dev_err(dev, "invalid number of arguments\n"); 17362306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci mode->select = args->args[0]; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (mode->select < PHY_TYPE_SATA || mode->select > PHY_TYPE_USB3) { 17962306a36Sopenharmony_ci dev_err(dev, "invalid phy mode select argument\n"); 18062306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci if (is_mode_fixed(mode) && mode->select != mode->fixed) { 18462306a36Sopenharmony_ci dev_err(dev, "mode select %d mismatch fixed phy mode %d\n", 18562306a36Sopenharmony_ci mode->select, mode->fixed); 18662306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return priv->phy; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic int histb_combphy_probe(struct platform_device *pdev) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci struct phy_provider *phy_provider; 19562306a36Sopenharmony_ci struct device *dev = &pdev->dev; 19662306a36Sopenharmony_ci struct histb_combphy_priv *priv; 19762306a36Sopenharmony_ci struct device_node *np = dev->of_node; 19862306a36Sopenharmony_ci struct histb_combphy_mode *mode; 19962306a36Sopenharmony_ci u32 vals[3]; 20062306a36Sopenharmony_ci int ret; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 20362306a36Sopenharmony_ci if (!priv) 20462306a36Sopenharmony_ci return -ENOMEM; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci priv->mmio = devm_platform_ioremap_resource(pdev, 0); 20762306a36Sopenharmony_ci if (IS_ERR(priv->mmio)) { 20862306a36Sopenharmony_ci ret = PTR_ERR(priv->mmio); 20962306a36Sopenharmony_ci return ret; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci priv->syscon = syscon_node_to_regmap(np->parent); 21362306a36Sopenharmony_ci if (IS_ERR(priv->syscon)) { 21462306a36Sopenharmony_ci dev_err(dev, "failed to find peri_ctrl syscon regmap\n"); 21562306a36Sopenharmony_ci return PTR_ERR(priv->syscon); 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci mode = &priv->mode; 21962306a36Sopenharmony_ci mode->fixed = PHY_NONE; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci ret = of_property_read_u32(np, "hisilicon,fixed-mode", &mode->fixed); 22262306a36Sopenharmony_ci if (ret == 0) 22362306a36Sopenharmony_ci dev_dbg(dev, "found fixed phy mode %d\n", mode->fixed); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci ret = of_property_read_u32_array(np, "hisilicon,mode-select-bits", 22662306a36Sopenharmony_ci vals, ARRAY_SIZE(vals)); 22762306a36Sopenharmony_ci if (ret == 0) { 22862306a36Sopenharmony_ci if (is_mode_fixed(mode)) { 22962306a36Sopenharmony_ci dev_err(dev, "found select bits for fixed mode phy\n"); 23062306a36Sopenharmony_ci return -EINVAL; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci mode->reg = vals[0]; 23462306a36Sopenharmony_ci mode->shift = vals[1]; 23562306a36Sopenharmony_ci mode->mask = vals[2]; 23662306a36Sopenharmony_ci dev_dbg(dev, "found mode select bits\n"); 23762306a36Sopenharmony_ci } else { 23862306a36Sopenharmony_ci if (!is_mode_fixed(mode)) { 23962306a36Sopenharmony_ci dev_err(dev, "no valid select bits found for non-fixed phy\n"); 24062306a36Sopenharmony_ci return -ENODEV; 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci priv->ref_clk = devm_clk_get(dev, NULL); 24562306a36Sopenharmony_ci if (IS_ERR(priv->ref_clk)) { 24662306a36Sopenharmony_ci dev_err(dev, "failed to find ref clock\n"); 24762306a36Sopenharmony_ci return PTR_ERR(priv->ref_clk); 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci priv->por_rst = devm_reset_control_get(dev, NULL); 25162306a36Sopenharmony_ci if (IS_ERR(priv->por_rst)) { 25262306a36Sopenharmony_ci dev_err(dev, "failed to get poweron reset\n"); 25362306a36Sopenharmony_ci return PTR_ERR(priv->por_rst); 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci priv->phy = devm_phy_create(dev, NULL, &histb_combphy_ops); 25762306a36Sopenharmony_ci if (IS_ERR(priv->phy)) { 25862306a36Sopenharmony_ci dev_err(dev, "failed to create combphy\n"); 25962306a36Sopenharmony_ci return PTR_ERR(priv->phy); 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci dev_set_drvdata(dev, priv); 26362306a36Sopenharmony_ci phy_set_drvdata(priv->phy, priv); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci phy_provider = devm_of_phy_provider_register(dev, histb_combphy_xlate); 26662306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(phy_provider); 26762306a36Sopenharmony_ci} 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_cistatic const struct of_device_id histb_combphy_of_match[] = { 27062306a36Sopenharmony_ci { .compatible = "hisilicon,hi3798cv200-combphy" }, 27162306a36Sopenharmony_ci { }, 27262306a36Sopenharmony_ci}; 27362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, histb_combphy_of_match); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_cistatic struct platform_driver histb_combphy_driver = { 27662306a36Sopenharmony_ci .probe = histb_combphy_probe, 27762306a36Sopenharmony_ci .driver = { 27862306a36Sopenharmony_ci .name = "combphy", 27962306a36Sopenharmony_ci .of_match_table = histb_combphy_of_match, 28062306a36Sopenharmony_ci }, 28162306a36Sopenharmony_ci}; 28262306a36Sopenharmony_cimodule_platform_driver(histb_combphy_driver); 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ciMODULE_DESCRIPTION("HiSilicon STB COMBPHY driver"); 28562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 286