1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Phy provider for USB 3.0 controller on HiSilicon 3660 platform |
4 | * |
5 | * Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd. |
6 | * http://www.huawei.com |
7 | * |
8 | * Authors: Yu Chen <chenyu56@huawei.com> |
9 | */ |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/phy/phy.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/regmap.h> |
18 | |
19 | #define PERI_CRG_CLK_EN4 0x40 |
20 | #define PERI_CRG_CLK_DIS4 0x44 |
21 | #define GT_CLK_USB3OTG_REF BIT(0) |
22 | #define GT_ACLK_USB3OTG BIT(1) |
23 | |
24 | #define PERI_CRG_RSTEN4 0x90 |
25 | #define PERI_CRG_RSTDIS4 0x94 |
26 | #define IP_RST_USB3OTGPHY_POR BIT(3) |
27 | #define IP_RST_USB3OTG BIT(5) |
28 | |
29 | #define PERI_CRG_ISODIS 0x148 |
30 | #define USB_REFCLK_ISO_EN BIT(25) |
31 | |
32 | #define PCTRL_PERI_CTRL3 0x10 |
33 | #define PCTRL_PERI_CTRL3_MSK_START 16 |
34 | #define USB_TCXO_EN BIT(1) |
35 | |
36 | #define PCTRL_PERI_CTRL24 0x64 |
37 | #define SC_CLK_USB3PHY_3MUX1_SEL BIT(25) |
38 | |
39 | #define USBOTG3_CTRL0 0x00 |
40 | #define SC_USB3PHY_ABB_GT_EN BIT(15) |
41 | |
42 | #define USBOTG3_CTRL2 0x08 |
43 | #define USBOTG3CTRL2_POWERDOWN_HSP BIT(0) |
44 | #define USBOTG3CTRL2_POWERDOWN_SSP BIT(1) |
45 | |
46 | #define USBOTG3_CTRL3 0x0C |
47 | #define USBOTG3_CTRL3_VBUSVLDEXT BIT(6) |
48 | #define USBOTG3_CTRL3_VBUSVLDEXTSEL BIT(5) |
49 | |
50 | #define USBOTG3_CTRL4 0x10 |
51 | |
52 | #define USBOTG3_CTRL7 0x1c |
53 | #define REF_SSP_EN BIT(16) |
54 | |
55 | /* This value config the default txtune parameter of the usb 2.0 phy */ |
56 | #define HI3660_USB_DEFAULT_PHY_PARAM 0x1c466e3 |
57 | |
58 | struct hi3660_priv { |
59 | struct device *dev; |
60 | struct regmap *peri_crg; |
61 | struct regmap *pctrl; |
62 | struct regmap *otg_bc; |
63 | u32 eye_diagram_param; |
64 | }; |
65 | |
66 | static int hi3660_phy_init(struct phy *phy) |
67 | { |
68 | struct hi3660_priv *priv = phy_get_drvdata(phy); |
69 | u32 val, mask; |
70 | int ret; |
71 | |
72 | /* usb refclk iso disable */ |
73 | ret = regmap_write(map: priv->peri_crg, PERI_CRG_ISODIS, USB_REFCLK_ISO_EN); |
74 | if (ret) |
75 | goto out; |
76 | |
77 | /* enable usb_tcxo_en */ |
78 | val = USB_TCXO_EN | (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START); |
79 | ret = regmap_write(map: priv->pctrl, PCTRL_PERI_CTRL3, val); |
80 | if (ret) |
81 | goto out; |
82 | |
83 | /* assert phy */ |
84 | val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG; |
85 | ret = regmap_write(map: priv->peri_crg, PERI_CRG_RSTEN4, val); |
86 | if (ret) |
87 | goto out; |
88 | |
89 | /* enable phy ref clk */ |
90 | val = SC_USB3PHY_ABB_GT_EN; |
91 | mask = val; |
92 | ret = regmap_update_bits(map: priv->otg_bc, USBOTG3_CTRL0, mask, val); |
93 | if (ret) |
94 | goto out; |
95 | |
96 | val = REF_SSP_EN; |
97 | mask = val; |
98 | ret = regmap_update_bits(map: priv->otg_bc, USBOTG3_CTRL7, mask, val); |
99 | if (ret) |
100 | goto out; |
101 | |
102 | /* exit from IDDQ mode */ |
103 | mask = USBOTG3CTRL2_POWERDOWN_HSP | USBOTG3CTRL2_POWERDOWN_SSP; |
104 | ret = regmap_update_bits(map: priv->otg_bc, USBOTG3_CTRL2, mask, val: 0); |
105 | if (ret) |
106 | goto out; |
107 | |
108 | /* delay for exit from IDDQ mode */ |
109 | usleep_range(min: 100, max: 120); |
110 | |
111 | /* deassert phy */ |
112 | val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG; |
113 | ret = regmap_write(map: priv->peri_crg, PERI_CRG_RSTDIS4, val); |
114 | if (ret) |
115 | goto out; |
116 | |
117 | /* delay for phy deasserted */ |
118 | usleep_range(min: 10000, max: 15000); |
119 | |
120 | /* fake vbus valid signal */ |
121 | val = USBOTG3_CTRL3_VBUSVLDEXT | USBOTG3_CTRL3_VBUSVLDEXTSEL; |
122 | mask = val; |
123 | ret = regmap_update_bits(map: priv->otg_bc, USBOTG3_CTRL3, mask, val); |
124 | if (ret) |
125 | goto out; |
126 | |
127 | /* delay for vbus valid */ |
128 | usleep_range(min: 100, max: 120); |
129 | |
130 | ret = regmap_write(map: priv->otg_bc, USBOTG3_CTRL4, |
131 | val: priv->eye_diagram_param); |
132 | if (ret) |
133 | goto out; |
134 | |
135 | return 0; |
136 | out: |
137 | dev_err(priv->dev, "failed to init phy ret: %d\n" , ret); |
138 | return ret; |
139 | } |
140 | |
141 | static int hi3660_phy_exit(struct phy *phy) |
142 | { |
143 | struct hi3660_priv *priv = phy_get_drvdata(phy); |
144 | u32 val; |
145 | int ret; |
146 | |
147 | /* assert phy */ |
148 | val = IP_RST_USB3OTGPHY_POR; |
149 | ret = regmap_write(map: priv->peri_crg, PERI_CRG_RSTEN4, val); |
150 | if (ret) |
151 | goto out; |
152 | |
153 | /* disable usb_tcxo_en */ |
154 | val = USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START; |
155 | ret = regmap_write(map: priv->pctrl, PCTRL_PERI_CTRL3, val); |
156 | if (ret) |
157 | goto out; |
158 | |
159 | return 0; |
160 | out: |
161 | dev_err(priv->dev, "failed to exit phy ret: %d\n" , ret); |
162 | return ret; |
163 | } |
164 | |
165 | static const struct phy_ops hi3660_phy_ops = { |
166 | .init = hi3660_phy_init, |
167 | .exit = hi3660_phy_exit, |
168 | .owner = THIS_MODULE, |
169 | }; |
170 | |
171 | static int hi3660_phy_probe(struct platform_device *pdev) |
172 | { |
173 | struct phy_provider *phy_provider; |
174 | struct device *dev = &pdev->dev; |
175 | struct phy *phy; |
176 | struct hi3660_priv *priv; |
177 | |
178 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
179 | if (!priv) |
180 | return -ENOMEM; |
181 | |
182 | priv->dev = dev; |
183 | priv->peri_crg = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
184 | property: "hisilicon,pericrg-syscon" ); |
185 | if (IS_ERR(ptr: priv->peri_crg)) { |
186 | dev_err(dev, "no hisilicon,pericrg-syscon\n" ); |
187 | return PTR_ERR(ptr: priv->peri_crg); |
188 | } |
189 | |
190 | priv->pctrl = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
191 | property: "hisilicon,pctrl-syscon" ); |
192 | if (IS_ERR(ptr: priv->pctrl)) { |
193 | dev_err(dev, "no hisilicon,pctrl-syscon\n" ); |
194 | return PTR_ERR(ptr: priv->pctrl); |
195 | } |
196 | |
197 | /* node of hi3660 phy is a sub-node of usb3_otg_bc */ |
198 | priv->otg_bc = syscon_node_to_regmap(np: dev->parent->of_node); |
199 | if (IS_ERR(ptr: priv->otg_bc)) { |
200 | dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n" ); |
201 | return PTR_ERR(ptr: priv->otg_bc); |
202 | } |
203 | |
204 | if (of_property_read_u32(np: dev->of_node, propname: "hisilicon,eye-diagram-param" , |
205 | out_value: &(priv->eye_diagram_param))) |
206 | priv->eye_diagram_param = HI3660_USB_DEFAULT_PHY_PARAM; |
207 | |
208 | phy = devm_phy_create(dev, NULL, ops: &hi3660_phy_ops); |
209 | if (IS_ERR(ptr: phy)) |
210 | return PTR_ERR(ptr: phy); |
211 | |
212 | phy_set_drvdata(phy, data: priv); |
213 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
214 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
215 | } |
216 | |
217 | static const struct of_device_id hi3660_phy_of_match[] = { |
218 | {.compatible = "hisilicon,hi3660-usb-phy" ,}, |
219 | { } |
220 | }; |
221 | MODULE_DEVICE_TABLE(of, hi3660_phy_of_match); |
222 | |
223 | static struct platform_driver hi3660_phy_driver = { |
224 | .probe = hi3660_phy_probe, |
225 | .driver = { |
226 | .name = "hi3660-usb-phy" , |
227 | .of_match_table = hi3660_phy_of_match, |
228 | } |
229 | }; |
230 | module_platform_driver(hi3660_phy_driver); |
231 | |
232 | MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>" ); |
233 | MODULE_LICENSE("GPL v2" ); |
234 | MODULE_DESCRIPTION("Hilisicon Hi3660 USB3 PHY Driver" ); |
235 | |