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>
127559e757SRob Herring #include <linux/of.h>
13ba8b0ee8SPengcheng Li #include <linux/phy/phy.h>
147559e757SRob 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
hisi_inno_phy_write_reg(struct hisi_inno_phy_priv * priv,u8 port,u32 addr,u32 data)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
hisi_inno_phy_setup(struct hisi_inno_phy_priv * priv)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
hisi_inno_phy_init(struct phy * phy)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
hisi_inno_phy_exit(struct phy * phy)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
hisi_inno_phy_probe(struct platform_device * pdev)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 int i = 0;
142ba8b0ee8SPengcheng Li int ret;
143ba8b0ee8SPengcheng Li
144ba8b0ee8SPengcheng Li priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
145ba8b0ee8SPengcheng Li if (!priv)
146ba8b0ee8SPengcheng Li return -ENOMEM;
147ba8b0ee8SPengcheng Li
148fa093440SYueHaibing priv->mmio = devm_platform_ioremap_resource(pdev, 0);
149ba8b0ee8SPengcheng Li if (IS_ERR(priv->mmio)) {
150ba8b0ee8SPengcheng Li ret = PTR_ERR(priv->mmio);
151ba8b0ee8SPengcheng Li return ret;
152ba8b0ee8SPengcheng Li }
153ba8b0ee8SPengcheng Li
154ba8b0ee8SPengcheng Li priv->ref_clk = devm_clk_get(dev, NULL);
155ba8b0ee8SPengcheng Li if (IS_ERR(priv->ref_clk))
156ba8b0ee8SPengcheng Li return PTR_ERR(priv->ref_clk);
157ba8b0ee8SPengcheng Li
158ba8b0ee8SPengcheng Li priv->por_rst = devm_reset_control_get_exclusive(dev, NULL);
159ba8b0ee8SPengcheng Li if (IS_ERR(priv->por_rst))
160ba8b0ee8SPengcheng Li return PTR_ERR(priv->por_rst);
161ba8b0ee8SPengcheng Li
1623940ffc6SDavid Yang priv->type = (uintptr_t) of_device_get_match_data(dev);
1633940ffc6SDavid Yang
164*93cab07aSKrzysztof Kozlowski for_each_child_of_node_scoped(np, child) {
165ba8b0ee8SPengcheng Li struct reset_control *rst;
166ba8b0ee8SPengcheng Li struct phy *phy;
167ba8b0ee8SPengcheng Li
168ba8b0ee8SPengcheng Li rst = of_reset_control_get_exclusive(child, NULL);
169*93cab07aSKrzysztof Kozlowski if (IS_ERR(rst))
170ba8b0ee8SPengcheng Li return PTR_ERR(rst);
17121b89120SWan Jiabing
172ba8b0ee8SPengcheng Li priv->ports[i].utmi_rst = rst;
173ba8b0ee8SPengcheng Li priv->ports[i].priv = priv;
174ba8b0ee8SPengcheng Li
175ba8b0ee8SPengcheng Li phy = devm_phy_create(dev, child, &hisi_inno_phy_ops);
176*93cab07aSKrzysztof Kozlowski if (IS_ERR(phy))
177ba8b0ee8SPengcheng Li return PTR_ERR(phy);
178ba8b0ee8SPengcheng Li
179ba8b0ee8SPengcheng Li phy_set_bus_width(phy, 8);
180ba8b0ee8SPengcheng Li phy_set_drvdata(phy, &priv->ports[i]);
181ba8b0ee8SPengcheng Li i++;
182ba8b0ee8SPengcheng Li
18313c088cfSHarshit Mogalapalli if (i >= INNO_PHY_PORT_NUM) {
184ba8b0ee8SPengcheng Li dev_warn(dev, "Support %d ports in maximum\n", i);
185ba8b0ee8SPengcheng Li break;
186ba8b0ee8SPengcheng Li }
187ba8b0ee8SPengcheng Li }
188ba8b0ee8SPengcheng Li
189ba8b0ee8SPengcheng Li provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
190ba8b0ee8SPengcheng Li return PTR_ERR_OR_ZERO(provider);
191ba8b0ee8SPengcheng Li }
192ba8b0ee8SPengcheng Li
193ba8b0ee8SPengcheng Li static const struct of_device_id hisi_inno_phy_of_match[] = {
1943940ffc6SDavid Yang { .compatible = "hisilicon,inno-usb2-phy",
1953940ffc6SDavid Yang .data = (void *) PHY_TYPE_0 },
1963940ffc6SDavid Yang { .compatible = "hisilicon,hi3798cv200-usb2-phy",
1973940ffc6SDavid Yang .data = (void *) PHY_TYPE_0 },
1983940ffc6SDavid Yang { .compatible = "hisilicon,hi3798mv100-usb2-phy",
1993940ffc6SDavid Yang .data = (void *) PHY_TYPE_1 },
200ba8b0ee8SPengcheng Li { },
201ba8b0ee8SPengcheng Li };
202ba8b0ee8SPengcheng Li MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match);
203ba8b0ee8SPengcheng Li
204ba8b0ee8SPengcheng Li static struct platform_driver hisi_inno_phy_driver = {
205ba8b0ee8SPengcheng Li .probe = hisi_inno_phy_probe,
206ba8b0ee8SPengcheng Li .driver = {
207ba8b0ee8SPengcheng Li .name = "hisi-inno-phy",
208ba8b0ee8SPengcheng Li .of_match_table = hisi_inno_phy_of_match,
209ba8b0ee8SPengcheng Li }
210ba8b0ee8SPengcheng Li };
211ba8b0ee8SPengcheng Li module_platform_driver(hisi_inno_phy_driver);
212ba8b0ee8SPengcheng Li
213ba8b0ee8SPengcheng Li MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver");
214ba8b0ee8SPengcheng Li MODULE_LICENSE("GPL v2");
215