]>
Commit | Line | Data |
---|---|---|
1ccea77e | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
ba8b0ee8 PL |
2 | /* |
3 | * HiSilicon INNO USB2 PHY Driver. | |
4 | * | |
5 | * Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd. | |
ba8b0ee8 PL |
6 | */ |
7 | ||
8 | #include <linux/clk.h> | |
9 | #include <linux/delay.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/phy/phy.h> | |
14 | #include <linux/reset.h> | |
15 | ||
16 | #define INNO_PHY_PORT_NUM 2 | |
17 | #define REF_CLK_STABLE_TIME 100 /* unit:us */ | |
18 | #define UTMI_CLK_STABLE_TIME 200 /* unit:us */ | |
19 | #define TEST_CLK_STABLE_TIME 2 /* unit:ms */ | |
20 | #define PHY_CLK_STABLE_TIME 2 /* unit:ms */ | |
21 | #define UTMI_RST_COMPLETE_TIME 2 /* unit:ms */ | |
22 | #define POR_RST_COMPLETE_TIME 300 /* unit:us */ | |
23 | #define PHY_TEST_DATA GENMASK(7, 0) | |
24 | #define PHY_TEST_ADDR GENMASK(15, 8) | |
25 | #define PHY_TEST_PORT GENMASK(18, 16) | |
26 | #define PHY_TEST_WREN BIT(21) | |
27 | #define PHY_TEST_CLK BIT(22) /* rising edge active */ | |
28 | #define PHY_TEST_RST BIT(23) /* low active */ | |
29 | #define PHY_CLK_ENABLE BIT(2) | |
30 | ||
31 | struct hisi_inno_phy_port { | |
32 | struct reset_control *utmi_rst; | |
33 | struct hisi_inno_phy_priv *priv; | |
34 | }; | |
35 | ||
36 | struct hisi_inno_phy_priv { | |
37 | void __iomem *mmio; | |
38 | struct clk *ref_clk; | |
39 | struct reset_control *por_rst; | |
40 | struct hisi_inno_phy_port ports[INNO_PHY_PORT_NUM]; | |
41 | }; | |
42 | ||
43 | static void hisi_inno_phy_write_reg(struct hisi_inno_phy_priv *priv, | |
44 | u8 port, u32 addr, u32 data) | |
45 | { | |
46 | void __iomem *reg = priv->mmio; | |
47 | u32 val; | |
48 | ||
49 | val = (data & PHY_TEST_DATA) | | |
50 | ((addr << 8) & PHY_TEST_ADDR) | | |
51 | ((port << 16) & PHY_TEST_PORT) | | |
52 | PHY_TEST_WREN | PHY_TEST_RST; | |
53 | writel(val, reg); | |
54 | ||
55 | val |= PHY_TEST_CLK; | |
56 | writel(val, reg); | |
57 | ||
58 | val &= ~PHY_TEST_CLK; | |
59 | writel(val, reg); | |
60 | } | |
61 | ||
62 | static void hisi_inno_phy_setup(struct hisi_inno_phy_priv *priv) | |
63 | { | |
64 | /* The phy clk is controlled by the port0 register 0x06. */ | |
65 | hisi_inno_phy_write_reg(priv, 0, 0x06, PHY_CLK_ENABLE); | |
66 | msleep(PHY_CLK_STABLE_TIME); | |
67 | } | |
68 | ||
69 | static int hisi_inno_phy_init(struct phy *phy) | |
70 | { | |
71 | struct hisi_inno_phy_port *port = phy_get_drvdata(phy); | |
72 | struct hisi_inno_phy_priv *priv = port->priv; | |
73 | int ret; | |
74 | ||
75 | ret = clk_prepare_enable(priv->ref_clk); | |
76 | if (ret) | |
77 | return ret; | |
78 | udelay(REF_CLK_STABLE_TIME); | |
79 | ||
80 | reset_control_deassert(priv->por_rst); | |
81 | udelay(POR_RST_COMPLETE_TIME); | |
82 | ||
83 | /* Set up phy registers */ | |
84 | hisi_inno_phy_setup(priv); | |
85 | ||
86 | reset_control_deassert(port->utmi_rst); | |
87 | udelay(UTMI_RST_COMPLETE_TIME); | |
88 | ||
89 | return 0; | |
90 | } | |
91 | ||
92 | static int hisi_inno_phy_exit(struct phy *phy) | |
93 | { | |
94 | struct hisi_inno_phy_port *port = phy_get_drvdata(phy); | |
95 | struct hisi_inno_phy_priv *priv = port->priv; | |
96 | ||
97 | reset_control_assert(port->utmi_rst); | |
98 | reset_control_assert(priv->por_rst); | |
99 | clk_disable_unprepare(priv->ref_clk); | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | static const struct phy_ops hisi_inno_phy_ops = { | |
105 | .init = hisi_inno_phy_init, | |
106 | .exit = hisi_inno_phy_exit, | |
107 | .owner = THIS_MODULE, | |
108 | }; | |
109 | ||
110 | static int hisi_inno_phy_probe(struct platform_device *pdev) | |
111 | { | |
112 | struct device *dev = &pdev->dev; | |
113 | struct device_node *np = dev->of_node; | |
114 | struct hisi_inno_phy_priv *priv; | |
115 | struct phy_provider *provider; | |
116 | struct device_node *child; | |
ba8b0ee8 PL |
117 | int i = 0; |
118 | int ret; | |
119 | ||
120 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
121 | if (!priv) | |
122 | return -ENOMEM; | |
123 | ||
fa093440 | 124 | priv->mmio = devm_platform_ioremap_resource(pdev, 0); |
ba8b0ee8 PL |
125 | if (IS_ERR(priv->mmio)) { |
126 | ret = PTR_ERR(priv->mmio); | |
127 | return ret; | |
128 | } | |
129 | ||
130 | priv->ref_clk = devm_clk_get(dev, NULL); | |
131 | if (IS_ERR(priv->ref_clk)) | |
132 | return PTR_ERR(priv->ref_clk); | |
133 | ||
134 | priv->por_rst = devm_reset_control_get_exclusive(dev, NULL); | |
135 | if (IS_ERR(priv->por_rst)) | |
136 | return PTR_ERR(priv->por_rst); | |
137 | ||
138 | for_each_child_of_node(np, child) { | |
139 | struct reset_control *rst; | |
140 | struct phy *phy; | |
141 | ||
142 | rst = of_reset_control_get_exclusive(child, NULL); | |
143 | if (IS_ERR(rst)) | |
144 | return PTR_ERR(rst); | |
145 | priv->ports[i].utmi_rst = rst; | |
146 | priv->ports[i].priv = priv; | |
147 | ||
148 | phy = devm_phy_create(dev, child, &hisi_inno_phy_ops); | |
149 | if (IS_ERR(phy)) | |
150 | return PTR_ERR(phy); | |
151 | ||
152 | phy_set_bus_width(phy, 8); | |
153 | phy_set_drvdata(phy, &priv->ports[i]); | |
154 | i++; | |
155 | ||
156 | if (i > INNO_PHY_PORT_NUM) { | |
157 | dev_warn(dev, "Support %d ports in maximum\n", i); | |
158 | break; | |
159 | } | |
160 | } | |
161 | ||
162 | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | |
163 | return PTR_ERR_OR_ZERO(provider); | |
164 | } | |
165 | ||
166 | static const struct of_device_id hisi_inno_phy_of_match[] = { | |
167 | { .compatible = "hisilicon,inno-usb2-phy", }, | |
168 | { .compatible = "hisilicon,hi3798cv200-usb2-phy", }, | |
169 | { }, | |
170 | }; | |
171 | MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match); | |
172 | ||
173 | static struct platform_driver hisi_inno_phy_driver = { | |
174 | .probe = hisi_inno_phy_probe, | |
175 | .driver = { | |
176 | .name = "hisi-inno-phy", | |
177 | .of_match_table = hisi_inno_phy_of_match, | |
178 | } | |
179 | }; | |
180 | module_platform_driver(hisi_inno_phy_driver); | |
181 | ||
182 | MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver"); | |
183 | MODULE_LICENSE("GPL v2"); |