1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * UFS PHY driver for Samsung SoC |
4 | * |
5 | * Copyright (C) 2020 Samsung Electronics Co., Ltd. |
6 | * Author: Seungwon Jeon <essuuj@gmail.com> |
7 | * Author: Alim Akhtar <alim.akhtar@samsung.com> |
8 | * |
9 | */ |
10 | #include <linux/clk.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/err.h> |
13 | #include <linux/of.h> |
14 | #include <linux/io.h> |
15 | #include <linux/iopoll.h> |
16 | #include <linux/mfd/syscon.h> |
17 | #include <linux/module.h> |
18 | #include <linux/phy/phy.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/regmap.h> |
21 | |
22 | #include "phy-samsung-ufs.h" |
23 | |
24 | #define for_each_phy_lane(phy, i) \ |
25 | for (i = 0; i < (phy)->lane_cnt; i++) |
26 | #define for_each_phy_cfg(cfg) \ |
27 | for (; (cfg)->id; (cfg)++) |
28 | |
29 | #define PHY_DEF_LANE_CNT 1 |
30 | |
31 | static void samsung_ufs_phy_config(struct samsung_ufs_phy *phy, |
32 | const struct samsung_ufs_phy_cfg *cfg, |
33 | u8 lane) |
34 | { |
35 | enum {LANE_0, LANE_1}; /* lane index */ |
36 | |
37 | switch (lane) { |
38 | case LANE_0: |
39 | writel(val: cfg->val, addr: (phy)->reg_pma + cfg->off_0); |
40 | break; |
41 | case LANE_1: |
42 | if (cfg->id == PHY_TRSV_BLK) |
43 | writel(val: cfg->val, addr: (phy)->reg_pma + cfg->off_1); |
44 | break; |
45 | } |
46 | } |
47 | |
48 | static int samsung_ufs_phy_wait_for_lock_acq(struct phy *phy) |
49 | { |
50 | struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); |
51 | const unsigned int timeout_us = 100000; |
52 | const unsigned int sleep_us = 10; |
53 | u32 val; |
54 | int err; |
55 | |
56 | err = readl_poll_timeout( |
57 | ufs_phy->reg_pma + PHY_APB_ADDR(PHY_PLL_LOCK_STATUS), |
58 | val, (val & PHY_PLL_LOCK_BIT), sleep_us, timeout_us); |
59 | if (err) { |
60 | dev_err(ufs_phy->dev, |
61 | "failed to get phy pll lock acquisition %d\n" , err); |
62 | goto out; |
63 | } |
64 | |
65 | err = readl_poll_timeout( |
66 | ufs_phy->reg_pma + |
67 | PHY_APB_ADDR(ufs_phy->drvdata->cdr_lock_status_offset), |
68 | val, (val & PHY_CDR_LOCK_BIT), sleep_us, timeout_us); |
69 | if (err) |
70 | dev_err(ufs_phy->dev, |
71 | "failed to get phy cdr lock acquisition %d\n" , err); |
72 | out: |
73 | return err; |
74 | } |
75 | |
76 | static int samsung_ufs_phy_calibrate(struct phy *phy) |
77 | { |
78 | struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); |
79 | const struct samsung_ufs_phy_cfg * const *cfgs = ufs_phy->cfgs; |
80 | const struct samsung_ufs_phy_cfg *cfg; |
81 | int err = 0; |
82 | int i; |
83 | |
84 | if (unlikely(ufs_phy->ufs_phy_state < CFG_PRE_INIT || |
85 | ufs_phy->ufs_phy_state >= CFG_TAG_MAX)) { |
86 | dev_err(ufs_phy->dev, "invalid phy config index %d\n" , ufs_phy->ufs_phy_state); |
87 | return -EINVAL; |
88 | } |
89 | |
90 | cfg = cfgs[ufs_phy->ufs_phy_state]; |
91 | if (!cfg) |
92 | goto out; |
93 | |
94 | for_each_phy_cfg(cfg) { |
95 | for_each_phy_lane(ufs_phy, i) { |
96 | samsung_ufs_phy_config(phy: ufs_phy, cfg, lane: i); |
97 | } |
98 | } |
99 | |
100 | if (ufs_phy->ufs_phy_state == CFG_POST_PWR_HS) |
101 | err = samsung_ufs_phy_wait_for_lock_acq(phy); |
102 | |
103 | /** |
104 | * In Samsung ufshci, PHY need to be calibrated at different |
105 | * stages / state mainly before Linkstartup, after Linkstartup, |
106 | * before power mode change and after power mode change. |
107 | * Below state machine to make sure to calibrate PHY in each |
108 | * state. Here after configuring PHY in a given state, will |
109 | * change the state to next state so that next state phy |
110 | * calibration value can be programed |
111 | */ |
112 | out: |
113 | switch (ufs_phy->ufs_phy_state) { |
114 | case CFG_PRE_INIT: |
115 | ufs_phy->ufs_phy_state = CFG_POST_INIT; |
116 | break; |
117 | case CFG_POST_INIT: |
118 | ufs_phy->ufs_phy_state = CFG_PRE_PWR_HS; |
119 | break; |
120 | case CFG_PRE_PWR_HS: |
121 | ufs_phy->ufs_phy_state = CFG_POST_PWR_HS; |
122 | break; |
123 | case CFG_POST_PWR_HS: |
124 | /* Change back to INIT state */ |
125 | ufs_phy->ufs_phy_state = CFG_PRE_INIT; |
126 | break; |
127 | default: |
128 | dev_err(ufs_phy->dev, "wrong state for phy calibration\n" ); |
129 | } |
130 | |
131 | return err; |
132 | } |
133 | |
134 | static int samsung_ufs_phy_clks_init(struct samsung_ufs_phy *phy) |
135 | { |
136 | int i; |
137 | const struct samsung_ufs_phy_drvdata *drvdata = phy->drvdata; |
138 | int num_clks = drvdata->num_clks; |
139 | |
140 | phy->clks = devm_kcalloc(dev: phy->dev, n: num_clks, size: sizeof(*phy->clks), |
141 | GFP_KERNEL); |
142 | if (!phy->clks) |
143 | return -ENOMEM; |
144 | |
145 | for (i = 0; i < num_clks; i++) |
146 | phy->clks[i].id = drvdata->clk_list[i]; |
147 | |
148 | return devm_clk_bulk_get(dev: phy->dev, num_clks, clks: phy->clks); |
149 | } |
150 | |
151 | static int samsung_ufs_phy_init(struct phy *phy) |
152 | { |
153 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); |
154 | |
155 | ss_phy->lane_cnt = phy->attrs.bus_width; |
156 | ss_phy->ufs_phy_state = CFG_PRE_INIT; |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | static int samsung_ufs_phy_power_on(struct phy *phy) |
162 | { |
163 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); |
164 | int ret; |
165 | |
166 | samsung_ufs_phy_ctrl_isol(phy: ss_phy, isol: false); |
167 | |
168 | ret = clk_bulk_prepare_enable(num_clks: ss_phy->drvdata->num_clks, clks: ss_phy->clks); |
169 | if (ret) { |
170 | dev_err(ss_phy->dev, "failed to enable ufs phy clocks\n" ); |
171 | return ret; |
172 | } |
173 | |
174 | if (ss_phy->ufs_phy_state == CFG_PRE_INIT) { |
175 | ret = samsung_ufs_phy_calibrate(phy); |
176 | if (ret) |
177 | dev_err(ss_phy->dev, "ufs phy calibration failed\n" ); |
178 | } |
179 | |
180 | return ret; |
181 | } |
182 | |
183 | static int samsung_ufs_phy_power_off(struct phy *phy) |
184 | { |
185 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); |
186 | |
187 | clk_bulk_disable_unprepare(num_clks: ss_phy->drvdata->num_clks, clks: ss_phy->clks); |
188 | |
189 | samsung_ufs_phy_ctrl_isol(phy: ss_phy, isol: true); |
190 | |
191 | return 0; |
192 | } |
193 | |
194 | static int samsung_ufs_phy_set_mode(struct phy *generic_phy, |
195 | enum phy_mode mode, int submode) |
196 | { |
197 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy: generic_phy); |
198 | |
199 | ss_phy->mode = PHY_MODE_INVALID; |
200 | |
201 | if (mode > 0) |
202 | ss_phy->mode = mode; |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static int samsung_ufs_phy_exit(struct phy *phy) |
208 | { |
209 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); |
210 | |
211 | ss_phy->ufs_phy_state = CFG_TAG_MAX; |
212 | |
213 | return 0; |
214 | } |
215 | |
216 | static const struct phy_ops samsung_ufs_phy_ops = { |
217 | .init = samsung_ufs_phy_init, |
218 | .exit = samsung_ufs_phy_exit, |
219 | .power_on = samsung_ufs_phy_power_on, |
220 | .power_off = samsung_ufs_phy_power_off, |
221 | .calibrate = samsung_ufs_phy_calibrate, |
222 | .set_mode = samsung_ufs_phy_set_mode, |
223 | .owner = THIS_MODULE, |
224 | }; |
225 | |
226 | static const struct of_device_id samsung_ufs_phy_match[]; |
227 | |
228 | static int samsung_ufs_phy_probe(struct platform_device *pdev) |
229 | { |
230 | struct device *dev = &pdev->dev; |
231 | const struct of_device_id *match; |
232 | struct samsung_ufs_phy *phy; |
233 | struct phy *gen_phy; |
234 | struct phy_provider *phy_provider; |
235 | const struct samsung_ufs_phy_drvdata *drvdata; |
236 | u32 isol_offset; |
237 | int err = 0; |
238 | |
239 | match = of_match_node(matches: samsung_ufs_phy_match, node: dev->of_node); |
240 | if (!match) { |
241 | err = -EINVAL; |
242 | dev_err(dev, "failed to get match_node\n" ); |
243 | goto out; |
244 | } |
245 | |
246 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
247 | if (!phy) { |
248 | err = -ENOMEM; |
249 | goto out; |
250 | } |
251 | |
252 | phy->reg_pma = devm_platform_ioremap_resource_byname(pdev, name: "phy-pma" ); |
253 | if (IS_ERR(ptr: phy->reg_pma)) { |
254 | err = PTR_ERR(ptr: phy->reg_pma); |
255 | goto out; |
256 | } |
257 | |
258 | phy->reg_pmu = syscon_regmap_lookup_by_phandle( |
259 | np: dev->of_node, property: "samsung,pmu-syscon" ); |
260 | if (IS_ERR(ptr: phy->reg_pmu)) { |
261 | err = PTR_ERR(ptr: phy->reg_pmu); |
262 | dev_err(dev, "failed syscon remap for pmu\n" ); |
263 | goto out; |
264 | } |
265 | |
266 | gen_phy = devm_phy_create(dev, NULL, ops: &samsung_ufs_phy_ops); |
267 | if (IS_ERR(ptr: gen_phy)) { |
268 | err = PTR_ERR(ptr: gen_phy); |
269 | dev_err(dev, "failed to create PHY for ufs-phy\n" ); |
270 | goto out; |
271 | } |
272 | |
273 | drvdata = match->data; |
274 | phy->dev = dev; |
275 | phy->drvdata = drvdata; |
276 | phy->cfgs = drvdata->cfgs; |
277 | memcpy(&phy->isol, &drvdata->isol, sizeof(phy->isol)); |
278 | |
279 | if (!of_property_read_u32_index(np: dev->of_node, propname: "samsung,pmu-syscon" , index: 1, |
280 | out_value: &isol_offset)) |
281 | phy->isol.offset = isol_offset; |
282 | |
283 | phy->lane_cnt = PHY_DEF_LANE_CNT; |
284 | |
285 | err = samsung_ufs_phy_clks_init(phy); |
286 | if (err) { |
287 | dev_err(dev, "failed to get phy clocks\n" ); |
288 | goto out; |
289 | } |
290 | |
291 | phy_set_drvdata(phy: gen_phy, data: phy); |
292 | |
293 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
294 | if (IS_ERR(ptr: phy_provider)) { |
295 | err = PTR_ERR(ptr: phy_provider); |
296 | dev_err(dev, "failed to register phy-provider\n" ); |
297 | goto out; |
298 | } |
299 | out: |
300 | return err; |
301 | } |
302 | |
303 | static const struct of_device_id samsung_ufs_phy_match[] = { |
304 | { |
305 | .compatible = "samsung,exynos7-ufs-phy" , |
306 | .data = &exynos7_ufs_phy, |
307 | }, { |
308 | .compatible = "samsung,exynosautov9-ufs-phy" , |
309 | .data = &exynosautov9_ufs_phy, |
310 | }, { |
311 | .compatible = "tesla,fsd-ufs-phy" , |
312 | .data = &fsd_ufs_phy, |
313 | }, |
314 | {}, |
315 | }; |
316 | MODULE_DEVICE_TABLE(of, samsung_ufs_phy_match); |
317 | |
318 | static struct platform_driver samsung_ufs_phy_driver = { |
319 | .probe = samsung_ufs_phy_probe, |
320 | .driver = { |
321 | .name = "samsung-ufs-phy" , |
322 | .of_match_table = samsung_ufs_phy_match, |
323 | }, |
324 | }; |
325 | module_platform_driver(samsung_ufs_phy_driver); |
326 | MODULE_DESCRIPTION("Samsung SoC UFS PHY Driver" ); |
327 | MODULE_AUTHOR("Seungwon Jeon <essuuj@gmail.com>" ); |
328 | MODULE_AUTHOR("Alim Akhtar <alim.akhtar@samsung.com>" ); |
329 | MODULE_LICENSE("GPL v2" ); |
330 | |