11ccea77eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2ba8b0ee8SPengcheng Li /* 3ba8b0ee8SPengcheng Li * HiSilicon INNO USB2 PHY Driver. 4ba8b0ee8SPengcheng Li * 5ba8b0ee8SPengcheng Li * Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd. 6ba8b0ee8SPengcheng Li */ 7ba8b0ee8SPengcheng Li 8ba8b0ee8SPengcheng Li #include <linux/clk.h> 9ba8b0ee8SPengcheng Li #include <linux/delay.h> 10ba8b0ee8SPengcheng Li #include <linux/io.h> 11ba8b0ee8SPengcheng Li #include <linux/module.h> 12*7559e757SRob Herring #include <linux/of.h> 13ba8b0ee8SPengcheng Li #include <linux/phy/phy.h> 14*7559e757SRob Herring #include <linux/platform_device.h> 15ba8b0ee8SPengcheng Li #include <linux/reset.h> 16ba8b0ee8SPengcheng Li 17ba8b0ee8SPengcheng Li #define INNO_PHY_PORT_NUM 2 18ba8b0ee8SPengcheng Li #define REF_CLK_STABLE_TIME 100 /* unit:us */ 19ba8b0ee8SPengcheng Li #define UTMI_CLK_STABLE_TIME 200 /* unit:us */ 20ba8b0ee8SPengcheng Li #define TEST_CLK_STABLE_TIME 2 /* unit:ms */ 21ba8b0ee8SPengcheng Li #define PHY_CLK_STABLE_TIME 2 /* unit:ms */ 22ba8b0ee8SPengcheng Li #define UTMI_RST_COMPLETE_TIME 2 /* unit:ms */ 23ba8b0ee8SPengcheng Li #define POR_RST_COMPLETE_TIME 300 /* unit:us */ 243940ffc6SDavid Yang 253940ffc6SDavid Yang #define PHY_TYPE_0 0 263940ffc6SDavid Yang #define PHY_TYPE_1 1 273940ffc6SDavid Yang 28ba8b0ee8SPengcheng Li #define PHY_TEST_DATA GENMASK(7, 0) 293940ffc6SDavid Yang #define PHY_TEST_ADDR_OFFSET 8 303940ffc6SDavid Yang #define PHY0_TEST_ADDR GENMASK(15, 8) 313940ffc6SDavid Yang #define PHY0_TEST_PORT_OFFSET 16 323940ffc6SDavid Yang #define PHY0_TEST_PORT GENMASK(18, 16) 333940ffc6SDavid Yang #define PHY0_TEST_WREN BIT(21) 343940ffc6SDavid Yang #define PHY0_TEST_CLK BIT(22) /* rising edge active */ 353940ffc6SDavid Yang #define PHY0_TEST_RST BIT(23) /* low active */ 363940ffc6SDavid Yang #define PHY1_TEST_ADDR GENMASK(11, 8) 373940ffc6SDavid Yang #define PHY1_TEST_PORT_OFFSET 12 383940ffc6SDavid Yang #define PHY1_TEST_PORT BIT(12) 393940ffc6SDavid Yang #define PHY1_TEST_WREN BIT(13) 403940ffc6SDavid Yang #define PHY1_TEST_CLK BIT(14) /* rising edge active */ 413940ffc6SDavid Yang #define PHY1_TEST_RST BIT(15) /* low active */ 423940ffc6SDavid Yang 43ba8b0ee8SPengcheng Li #define PHY_CLK_ENABLE BIT(2) 44ba8b0ee8SPengcheng Li 45ba8b0ee8SPengcheng Li struct hisi_inno_phy_port { 46ba8b0ee8SPengcheng Li struct reset_control *utmi_rst; 47ba8b0ee8SPengcheng Li struct hisi_inno_phy_priv *priv; 48ba8b0ee8SPengcheng Li }; 49ba8b0ee8SPengcheng Li 50ba8b0ee8SPengcheng Li struct hisi_inno_phy_priv { 51ba8b0ee8SPengcheng Li void __iomem *mmio; 52ba8b0ee8SPengcheng Li struct clk *ref_clk; 53ba8b0ee8SPengcheng Li struct reset_control *por_rst; 543940ffc6SDavid Yang unsigned int type; 55ba8b0ee8SPengcheng Li struct hisi_inno_phy_port ports[INNO_PHY_PORT_NUM]; 56ba8b0ee8SPengcheng Li }; 57ba8b0ee8SPengcheng Li 58ba8b0ee8SPengcheng Li static void hisi_inno_phy_write_reg(struct hisi_inno_phy_priv *priv, 59ba8b0ee8SPengcheng Li u8 port, u32 addr, u32 data) 60ba8b0ee8SPengcheng Li { 61ba8b0ee8SPengcheng Li void __iomem *reg = priv->mmio; 62ba8b0ee8SPengcheng Li u32 val; 633940ffc6SDavid Yang u32 value; 64ba8b0ee8SPengcheng Li 653940ffc6SDavid Yang if (priv->type == PHY_TYPE_0) 66ba8b0ee8SPengcheng Li val = (data & PHY_TEST_DATA) | 673940ffc6SDavid Yang ((addr << PHY_TEST_ADDR_OFFSET) & PHY0_TEST_ADDR) | 683940ffc6SDavid Yang ((port << PHY0_TEST_PORT_OFFSET) & PHY0_TEST_PORT) | 693940ffc6SDavid Yang PHY0_TEST_WREN | PHY0_TEST_RST; 703940ffc6SDavid Yang else 713940ffc6SDavid Yang val = (data & PHY_TEST_DATA) | 723940ffc6SDavid Yang ((addr << PHY_TEST_ADDR_OFFSET) & PHY1_TEST_ADDR) | 733940ffc6SDavid Yang ((port << PHY1_TEST_PORT_OFFSET) & PHY1_TEST_PORT) | 743940ffc6SDavid Yang PHY1_TEST_WREN | PHY1_TEST_RST; 75ba8b0ee8SPengcheng Li writel(val, reg); 76ba8b0ee8SPengcheng Li 773940ffc6SDavid Yang value = val; 783940ffc6SDavid Yang if (priv->type == PHY_TYPE_0) 793940ffc6SDavid Yang value |= PHY0_TEST_CLK; 803940ffc6SDavid Yang else 813940ffc6SDavid Yang value |= PHY1_TEST_CLK; 823940ffc6SDavid Yang writel(value, reg); 83ba8b0ee8SPengcheng Li 84ba8b0ee8SPengcheng Li writel(val, reg); 85ba8b0ee8SPengcheng Li } 86ba8b0ee8SPengcheng Li 87ba8b0ee8SPengcheng Li static void hisi_inno_phy_setup(struct hisi_inno_phy_priv *priv) 88ba8b0ee8SPengcheng Li { 89ba8b0ee8SPengcheng Li /* The phy clk is controlled by the port0 register 0x06. */ 90ba8b0ee8SPengcheng Li hisi_inno_phy_write_reg(priv, 0, 0x06, PHY_CLK_ENABLE); 91ba8b0ee8SPengcheng Li msleep(PHY_CLK_STABLE_TIME); 92ba8b0ee8SPengcheng Li } 93ba8b0ee8SPengcheng Li 94ba8b0ee8SPengcheng Li static int hisi_inno_phy_init(struct phy *phy) 95ba8b0ee8SPengcheng Li { 96ba8b0ee8SPengcheng Li struct hisi_inno_phy_port *port = phy_get_drvdata(phy); 97ba8b0ee8SPengcheng Li struct hisi_inno_phy_priv *priv = port->priv; 98ba8b0ee8SPengcheng Li int ret; 99ba8b0ee8SPengcheng Li 100ba8b0ee8SPengcheng Li ret = clk_prepare_enable(priv->ref_clk); 101ba8b0ee8SPengcheng Li if (ret) 102ba8b0ee8SPengcheng Li return ret; 103ba8b0ee8SPengcheng Li udelay(REF_CLK_STABLE_TIME); 104ba8b0ee8SPengcheng Li 105ba8b0ee8SPengcheng Li reset_control_deassert(priv->por_rst); 106ba8b0ee8SPengcheng Li udelay(POR_RST_COMPLETE_TIME); 107ba8b0ee8SPengcheng Li 108ba8b0ee8SPengcheng Li /* Set up phy registers */ 109ba8b0ee8SPengcheng Li hisi_inno_phy_setup(priv); 110ba8b0ee8SPengcheng Li 111ba8b0ee8SPengcheng Li reset_control_deassert(port->utmi_rst); 112ba8b0ee8SPengcheng Li udelay(UTMI_RST_COMPLETE_TIME); 113ba8b0ee8SPengcheng Li 114ba8b0ee8SPengcheng Li return 0; 115ba8b0ee8SPengcheng Li } 116ba8b0ee8SPengcheng Li 117ba8b0ee8SPengcheng Li static int hisi_inno_phy_exit(struct phy *phy) 118ba8b0ee8SPengcheng Li { 119ba8b0ee8SPengcheng Li struct hisi_inno_phy_port *port = phy_get_drvdata(phy); 120ba8b0ee8SPengcheng Li struct hisi_inno_phy_priv *priv = port->priv; 121ba8b0ee8SPengcheng Li 122ba8b0ee8SPengcheng Li reset_control_assert(port->utmi_rst); 123ba8b0ee8SPengcheng Li reset_control_assert(priv->por_rst); 124ba8b0ee8SPengcheng Li clk_disable_unprepare(priv->ref_clk); 125ba8b0ee8SPengcheng Li 126ba8b0ee8SPengcheng Li return 0; 127ba8b0ee8SPengcheng Li } 128ba8b0ee8SPengcheng Li 129ba8b0ee8SPengcheng Li static const struct phy_ops hisi_inno_phy_ops = { 130ba8b0ee8SPengcheng Li .init = hisi_inno_phy_init, 131ba8b0ee8SPengcheng Li .exit = hisi_inno_phy_exit, 132ba8b0ee8SPengcheng Li .owner = THIS_MODULE, 133ba8b0ee8SPengcheng Li }; 134ba8b0ee8SPengcheng Li 135ba8b0ee8SPengcheng Li static int hisi_inno_phy_probe(struct platform_device *pdev) 136ba8b0ee8SPengcheng Li { 137ba8b0ee8SPengcheng Li struct device *dev = &pdev->dev; 138ba8b0ee8SPengcheng Li struct device_node *np = dev->of_node; 139ba8b0ee8SPengcheng Li struct hisi_inno_phy_priv *priv; 140ba8b0ee8SPengcheng Li struct phy_provider *provider; 141ba8b0ee8SPengcheng Li struct device_node *child; 142ba8b0ee8SPengcheng Li int i = 0; 143ba8b0ee8SPengcheng Li int ret; 144ba8b0ee8SPengcheng Li 145ba8b0ee8SPengcheng Li priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 146ba8b0ee8SPengcheng Li if (!priv) 147ba8b0ee8SPengcheng Li return -ENOMEM; 148ba8b0ee8SPengcheng Li 149fa093440SYueHaibing priv->mmio = devm_platform_ioremap_resource(pdev, 0); 150ba8b0ee8SPengcheng Li if (IS_ERR(priv->mmio)) { 151ba8b0ee8SPengcheng Li ret = PTR_ERR(priv->mmio); 152ba8b0ee8SPengcheng Li return ret; 153ba8b0ee8SPengcheng Li } 154ba8b0ee8SPengcheng Li 155ba8b0ee8SPengcheng Li priv->ref_clk = devm_clk_get(dev, NULL); 156ba8b0ee8SPengcheng Li if (IS_ERR(priv->ref_clk)) 157ba8b0ee8SPengcheng Li return PTR_ERR(priv->ref_clk); 158ba8b0ee8SPengcheng Li 159ba8b0ee8SPengcheng Li priv->por_rst = devm_reset_control_get_exclusive(dev, NULL); 160ba8b0ee8SPengcheng Li if (IS_ERR(priv->por_rst)) 161ba8b0ee8SPengcheng Li return PTR_ERR(priv->por_rst); 162ba8b0ee8SPengcheng Li 1633940ffc6SDavid Yang priv->type = (uintptr_t) of_device_get_match_data(dev); 1643940ffc6SDavid Yang 165ba8b0ee8SPengcheng Li for_each_child_of_node(np, child) { 166ba8b0ee8SPengcheng Li struct reset_control *rst; 167ba8b0ee8SPengcheng Li struct phy *phy; 168ba8b0ee8SPengcheng Li 169ba8b0ee8SPengcheng Li rst = of_reset_control_get_exclusive(child, NULL); 17021b89120SWan Jiabing if (IS_ERR(rst)) { 17121b89120SWan Jiabing of_node_put(child); 172ba8b0ee8SPengcheng Li return PTR_ERR(rst); 17321b89120SWan Jiabing } 17421b89120SWan Jiabing 175ba8b0ee8SPengcheng Li priv->ports[i].utmi_rst = rst; 176ba8b0ee8SPengcheng Li priv->ports[i].priv = priv; 177ba8b0ee8SPengcheng Li 178ba8b0ee8SPengcheng Li phy = devm_phy_create(dev, child, &hisi_inno_phy_ops); 17921b89120SWan Jiabing if (IS_ERR(phy)) { 18021b89120SWan Jiabing of_node_put(child); 181ba8b0ee8SPengcheng Li return PTR_ERR(phy); 18221b89120SWan Jiabing } 183ba8b0ee8SPengcheng Li 184ba8b0ee8SPengcheng Li phy_set_bus_width(phy, 8); 185ba8b0ee8SPengcheng Li phy_set_drvdata(phy, &priv->ports[i]); 186ba8b0ee8SPengcheng Li i++; 187ba8b0ee8SPengcheng Li 188ba8b0ee8SPengcheng Li if (i > INNO_PHY_PORT_NUM) { 189ba8b0ee8SPengcheng Li dev_warn(dev, "Support %d ports in maximum\n", i); 19021b89120SWan Jiabing of_node_put(child); 191ba8b0ee8SPengcheng Li break; 192ba8b0ee8SPengcheng Li } 193ba8b0ee8SPengcheng Li } 194ba8b0ee8SPengcheng Li 195ba8b0ee8SPengcheng Li provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 196ba8b0ee8SPengcheng Li return PTR_ERR_OR_ZERO(provider); 197ba8b0ee8SPengcheng Li } 198ba8b0ee8SPengcheng Li 199ba8b0ee8SPengcheng Li static const struct of_device_id hisi_inno_phy_of_match[] = { 2003940ffc6SDavid Yang { .compatible = "hisilicon,inno-usb2-phy", 2013940ffc6SDavid Yang .data = (void *) PHY_TYPE_0 }, 2023940ffc6SDavid Yang { .compatible = "hisilicon,hi3798cv200-usb2-phy", 2033940ffc6SDavid Yang .data = (void *) PHY_TYPE_0 }, 2043940ffc6SDavid Yang { .compatible = "hisilicon,hi3798mv100-usb2-phy", 2053940ffc6SDavid Yang .data = (void *) PHY_TYPE_1 }, 206ba8b0ee8SPengcheng Li { }, 207ba8b0ee8SPengcheng Li }; 208ba8b0ee8SPengcheng Li MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match); 209ba8b0ee8SPengcheng Li 210ba8b0ee8SPengcheng Li static struct platform_driver hisi_inno_phy_driver = { 211ba8b0ee8SPengcheng Li .probe = hisi_inno_phy_probe, 212ba8b0ee8SPengcheng Li .driver = { 213ba8b0ee8SPengcheng Li .name = "hisi-inno-phy", 214ba8b0ee8SPengcheng Li .of_match_table = hisi_inno_phy_of_match, 215ba8b0ee8SPengcheng Li } 216ba8b0ee8SPengcheng Li }; 217ba8b0ee8SPengcheng Li module_platform_driver(hisi_inno_phy_driver); 218ba8b0ee8SPengcheng Li 219ba8b0ee8SPengcheng Li MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver"); 220ba8b0ee8SPengcheng Li MODULE_LICENSE("GPL v2"); 221