1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | /* |
4 | * Sunplus SP7021 USB 2.0 phy driver |
5 | * |
6 | * Copyright (C) 2022 Sunplus Technology Inc., All rights reserved. |
7 | * |
8 | * Note 1 : non-posted write command for the registers accesses of |
9 | * Sunplus SP7021. |
10 | * |
11 | */ |
12 | |
13 | #include <linux/bitfield.h> |
14 | #include <linux/clk.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/io.h> |
17 | #include <linux/module.h> |
18 | #include <linux/nvmem-consumer.h> |
19 | #include <linux/of.h> |
20 | #include <linux/phy/phy.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/reset.h> |
23 | |
24 | #define HIGH_MASK_BITS GENMASK(31, 16) |
25 | #define LOW_MASK_BITS GENMASK(15, 0) |
26 | #define OTP_DISC_LEVEL_DEFAULT 0xd |
27 | |
28 | /* GROUP UPHY */ |
29 | #define CONFIG1 0x4 |
30 | #define J_HS_TX_PWRSAV BIT(5) |
31 | #define CONFIG3 0xc |
32 | #define J_FORCE_DISC_ON BIT(5) |
33 | #define J_DEBUG_CTRL_ADDR_MACRO BIT(0) |
34 | #define CONFIG7 0x1c |
35 | #define J_DISC 0X1f |
36 | #define CONFIG9 0x24 |
37 | #define J_ECO_PATH BIT(6) |
38 | #define CONFIG16 0x40 |
39 | #define J_TBCWAIT_MASK GENMASK(6, 5) |
40 | #define J_TBCWAIT_1P1_MS FIELD_PREP(J_TBCWAIT_MASK, 0) |
41 | #define J_TVDM_SRC_DIS_MASK GENMASK(4, 3) |
42 | #define J_TVDM_SRC_DIS_8P2_MS FIELD_PREP(J_TVDM_SRC_DIS_MASK, 3) |
43 | #define J_TVDM_SRC_EN_MASK GENMASK(2, 1) |
44 | #define J_TVDM_SRC_EN_1P6_MS FIELD_PREP(J_TVDM_SRC_EN_MASK, 0) |
45 | #define J_BC_EN BIT(0) |
46 | #define CONFIG17 0x44 |
47 | #define IBG_TRIM0_MASK GENMASK(7, 5) |
48 | #define IBG_TRIM0_SSLVHT FIELD_PREP(IBG_TRIM0_MASK, 4) |
49 | #define J_VDATREE_TRIM_MASK GENMASK(4, 1) |
50 | #define J_VDATREE_TRIM_DEFAULT FIELD_PREP(J_VDATREE_TRIM_MASK, 9) |
51 | #define CONFIG23 0x5c |
52 | #define PROB_MASK GENMASK(5, 3) |
53 | #define PROB FIELD_PREP(PROB_MASK, 7) |
54 | |
55 | /* GROUP MOON4 */ |
56 | #define UPHY_CONTROL0 0x0 |
57 | #define UPHY_CONTROL1 0x4 |
58 | #define UPHY_CONTROL2 0x8 |
59 | #define MO1_UPHY_RX_CLK_SEL BIT(6) |
60 | #define MASK_MO1_UPHY_RX_CLK_SEL BIT(6 + 16) |
61 | #define UPHY_CONTROL3 0xc |
62 | #define MO1_UPHY_PLL_POWER_OFF_SEL BIT(7) |
63 | #define MASK_MO1_UPHY_PLL_POWER_OFF_SEL BIT(7 + 16) |
64 | #define MO1_UPHY_PLL_POWER_OFF BIT(3) |
65 | #define MASK_UPHY_PLL_POWER_OFF BIT(3 + 16) |
66 | |
67 | struct sp_usbphy { |
68 | struct device *dev; |
69 | struct resource *phy_res_mem; |
70 | struct resource *moon4_res_mem; |
71 | struct reset_control *rstc; |
72 | struct clk *phy_clk; |
73 | void __iomem *phy_regs; |
74 | void __iomem *moon4_regs; |
75 | u32 disc_vol_addr_off; |
76 | }; |
77 | |
78 | static int update_disc_vol(struct sp_usbphy *usbphy) |
79 | { |
80 | struct nvmem_cell *cell; |
81 | char *disc_name = "disc_vol" ; |
82 | ssize_t otp_l = 0; |
83 | char *otp_v; |
84 | u32 val, set; |
85 | |
86 | cell = nvmem_cell_get(dev: usbphy->dev, id: disc_name); |
87 | if (IS_ERR_OR_NULL(ptr: cell)) { |
88 | if (PTR_ERR(ptr: cell) == -EPROBE_DEFER) |
89 | return -EPROBE_DEFER; |
90 | } |
91 | |
92 | otp_v = nvmem_cell_read(cell, len: &otp_l); |
93 | nvmem_cell_put(cell); |
94 | |
95 | if (!IS_ERR(ptr: otp_v)) { |
96 | set = *(otp_v + 1); |
97 | set = (set << (sizeof(char) * 8)) | *otp_v; |
98 | set = (set >> usbphy->disc_vol_addr_off) & J_DISC; |
99 | } |
100 | |
101 | if (IS_ERR(ptr: otp_v) || set == 0) |
102 | set = OTP_DISC_LEVEL_DEFAULT; |
103 | |
104 | val = readl(addr: usbphy->phy_regs + CONFIG7); |
105 | val = (val & ~J_DISC) | set; |
106 | writel(val, addr: usbphy->phy_regs + CONFIG7); |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static int sp_uphy_init(struct phy *phy) |
112 | { |
113 | struct sp_usbphy *usbphy = phy_get_drvdata(phy); |
114 | u32 val; |
115 | int ret; |
116 | |
117 | ret = clk_prepare_enable(clk: usbphy->phy_clk); |
118 | if (ret) |
119 | goto err_clk; |
120 | |
121 | ret = reset_control_deassert(rstc: usbphy->rstc); |
122 | if (ret) |
123 | goto err_reset; |
124 | |
125 | /* Default value modification */ |
126 | writel(HIGH_MASK_BITS | 0x4002, addr: usbphy->moon4_regs + UPHY_CONTROL0); |
127 | writel(HIGH_MASK_BITS | 0x8747, addr: usbphy->moon4_regs + UPHY_CONTROL1); |
128 | |
129 | /* disconnect voltage */ |
130 | ret = update_disc_vol(usbphy); |
131 | if (ret < 0) |
132 | return ret; |
133 | |
134 | /* board uphy 0 internal register modification for tid certification */ |
135 | val = readl(addr: usbphy->phy_regs + CONFIG9); |
136 | val &= ~(J_ECO_PATH); |
137 | writel(val, addr: usbphy->phy_regs + CONFIG9); |
138 | |
139 | val = readl(addr: usbphy->phy_regs + CONFIG1); |
140 | val &= ~(J_HS_TX_PWRSAV); |
141 | writel(val, addr: usbphy->phy_regs + CONFIG1); |
142 | |
143 | val = readl(addr: usbphy->phy_regs + CONFIG23); |
144 | val = (val & ~PROB) | PROB; |
145 | writel(val, addr: usbphy->phy_regs + CONFIG23); |
146 | |
147 | /* port 0 uphy clk fix */ |
148 | writel(MASK_MO1_UPHY_RX_CLK_SEL | MO1_UPHY_RX_CLK_SEL, |
149 | addr: usbphy->moon4_regs + UPHY_CONTROL2); |
150 | |
151 | /* battery charger */ |
152 | writel(J_TBCWAIT_1P1_MS | J_TVDM_SRC_DIS_8P2_MS | J_TVDM_SRC_EN_1P6_MS | J_BC_EN, |
153 | addr: usbphy->phy_regs + CONFIG16); |
154 | writel(IBG_TRIM0_SSLVHT | J_VDATREE_TRIM_DEFAULT, addr: usbphy->phy_regs + CONFIG17); |
155 | |
156 | /* chirp mode */ |
157 | writel(J_FORCE_DISC_ON | J_DEBUG_CTRL_ADDR_MACRO, addr: usbphy->phy_regs + CONFIG3); |
158 | |
159 | return 0; |
160 | |
161 | err_reset: |
162 | reset_control_assert(rstc: usbphy->rstc); |
163 | err_clk: |
164 | clk_disable_unprepare(clk: usbphy->phy_clk); |
165 | |
166 | return ret; |
167 | } |
168 | |
169 | static int sp_uphy_power_on(struct phy *phy) |
170 | { |
171 | struct sp_usbphy *usbphy = phy_get_drvdata(phy); |
172 | u32 pll_pwr_on, pll_pwr_off; |
173 | |
174 | /* PLL power off/on twice */ |
175 | pll_pwr_off = (readl(addr: usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS) |
176 | | MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF; |
177 | pll_pwr_on = (readl(addr: usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS) |
178 | | MO1_UPHY_PLL_POWER_OFF_SEL; |
179 | |
180 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off, |
181 | addr: usbphy->moon4_regs + UPHY_CONTROL3); |
182 | mdelay(1); |
183 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on, |
184 | addr: usbphy->moon4_regs + UPHY_CONTROL3); |
185 | mdelay(1); |
186 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off, |
187 | addr: usbphy->moon4_regs + UPHY_CONTROL3); |
188 | mdelay(1); |
189 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on, |
190 | addr: usbphy->moon4_regs + UPHY_CONTROL3); |
191 | mdelay(1); |
192 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0, |
193 | addr: usbphy->moon4_regs + UPHY_CONTROL3); |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static int sp_uphy_power_off(struct phy *phy) |
199 | { |
200 | struct sp_usbphy *usbphy = phy_get_drvdata(phy); |
201 | u32 pll_pwr_off; |
202 | |
203 | pll_pwr_off = (readl(addr: usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS) |
204 | | MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF; |
205 | |
206 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off, |
207 | addr: usbphy->moon4_regs + UPHY_CONTROL3); |
208 | mdelay(1); |
209 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0, |
210 | addr: usbphy->moon4_regs + UPHY_CONTROL3); |
211 | |
212 | return 0; |
213 | } |
214 | |
215 | static int sp_uphy_exit(struct phy *phy) |
216 | { |
217 | struct sp_usbphy *usbphy = phy_get_drvdata(phy); |
218 | |
219 | reset_control_assert(rstc: usbphy->rstc); |
220 | clk_disable_unprepare(clk: usbphy->phy_clk); |
221 | |
222 | return 0; |
223 | } |
224 | |
225 | static const struct phy_ops sp_uphy_ops = { |
226 | .init = sp_uphy_init, |
227 | .power_on = sp_uphy_power_on, |
228 | .power_off = sp_uphy_power_off, |
229 | .exit = sp_uphy_exit, |
230 | }; |
231 | |
232 | static const struct of_device_id sp_uphy_dt_ids[] = { |
233 | {.compatible = "sunplus,sp7021-usb2-phy" , }, |
234 | { } |
235 | }; |
236 | MODULE_DEVICE_TABLE(of, sp_uphy_dt_ids); |
237 | |
238 | static int sp_usb_phy_probe(struct platform_device *pdev) |
239 | { |
240 | struct sp_usbphy *usbphy; |
241 | struct phy_provider *phy_provider; |
242 | struct phy *phy; |
243 | int ret; |
244 | |
245 | usbphy = devm_kzalloc(dev: &pdev->dev, size: sizeof(*usbphy), GFP_KERNEL); |
246 | if (!usbphy) |
247 | return -ENOMEM; |
248 | |
249 | usbphy->dev = &pdev->dev; |
250 | |
251 | usbphy->phy_res_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy" ); |
252 | usbphy->phy_regs = devm_ioremap_resource(dev: &pdev->dev, res: usbphy->phy_res_mem); |
253 | if (IS_ERR(ptr: usbphy->phy_regs)) |
254 | return PTR_ERR(ptr: usbphy->phy_regs); |
255 | |
256 | usbphy->moon4_res_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "moon4" ); |
257 | if (!usbphy->moon4_res_mem) |
258 | return -EINVAL; |
259 | |
260 | usbphy->moon4_regs = devm_ioremap(dev: &pdev->dev, offset: usbphy->moon4_res_mem->start, |
261 | size: resource_size(res: usbphy->moon4_res_mem)); |
262 | if (!usbphy->moon4_regs) |
263 | return -ENOMEM; |
264 | |
265 | usbphy->phy_clk = devm_clk_get(dev: &pdev->dev, NULL); |
266 | if (IS_ERR(ptr: usbphy->phy_clk)) |
267 | return PTR_ERR(ptr: usbphy->phy_clk); |
268 | |
269 | usbphy->rstc = devm_reset_control_get_exclusive(dev: &pdev->dev, NULL); |
270 | if (IS_ERR(ptr: usbphy->rstc)) |
271 | return PTR_ERR(ptr: usbphy->rstc); |
272 | |
273 | of_property_read_u32(np: pdev->dev.of_node, propname: "sunplus,disc-vol-addr-off" , |
274 | out_value: &usbphy->disc_vol_addr_off); |
275 | |
276 | phy = devm_phy_create(dev: &pdev->dev, NULL, ops: &sp_uphy_ops); |
277 | if (IS_ERR(ptr: phy)) { |
278 | ret = PTR_ERR(ptr: phy); |
279 | return ret; |
280 | } |
281 | |
282 | phy_set_drvdata(phy, data: usbphy); |
283 | phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); |
284 | |
285 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
286 | } |
287 | |
288 | static struct platform_driver sunplus_usb_phy_driver = { |
289 | .probe = sp_usb_phy_probe, |
290 | .driver = { |
291 | .name = "sunplus-usb2-phy" , |
292 | .of_match_table = sp_uphy_dt_ids, |
293 | }, |
294 | }; |
295 | module_platform_driver(sunplus_usb_phy_driver); |
296 | |
297 | MODULE_AUTHOR("Vincent Shih <vincent.shih@sunplus.com>" ); |
298 | MODULE_DESCRIPTION("Sunplus USB 2.0 phy driver" ); |
299 | MODULE_LICENSE("GPL" ); |
300 | |