xref: /linux/drivers/phy/hisilicon/phy-hisi-inno-usb2.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
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