Commit | Line | Data |
---|---|---|
bca21e93 AA |
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> | |
bca21e93 AA |
16 | #include <linux/module.h> |
17 | #include <linux/phy/phy.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/regmap.h> | |
f2c6d0fa | 20 | #include <linux/soc/samsung/exynos-pmu.h> |
bca21e93 AA |
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(cfg->val, (phy)->reg_pma + cfg->off_0); | |
40 | break; | |
41 | case LANE_1: | |
42 | if (cfg->id == PHY_TRSV_BLK) | |
43 | writel(cfg->val, (phy)->reg_pma + cfg->off_1); | |
44 | break; | |
45 | } | |
46 | } | |
47 | ||
a4de58a9 | 48 | int samsung_ufs_phy_wait_for_lock_acq(struct phy *phy, u8 lane) |
bca21e93 AA |
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( | |
e313216b AA |
66 | ufs_phy->reg_pma + |
67 | PHY_APB_ADDR(ufs_phy->drvdata->cdr_lock_status_offset), | |
bca21e93 AA |
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); | |
521f88bf | 79 | const struct samsung_ufs_phy_cfg * const *cfgs = ufs_phy->cfgs; |
bca21e93 AA |
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(ufs_phy, cfg, i); | |
97 | } | |
98 | } | |
99 | ||
a4de58a9 PG |
100 | for_each_phy_lane(ufs_phy, i) { |
101 | if (ufs_phy->ufs_phy_state == CFG_PRE_INIT && | |
2ff6365e | 102 | ufs_phy->drvdata->wait_for_cal) { |
a4de58a9 | 103 | err = ufs_phy->drvdata->wait_for_cal(phy, i); |
2ff6365e PG |
104 | if (err) |
105 | goto out; | |
106 | } | |
a4de58a9 PG |
107 | |
108 | if (ufs_phy->ufs_phy_state == CFG_POST_PWR_HS && | |
2ff6365e | 109 | ufs_phy->drvdata->wait_for_cdr) { |
a4de58a9 | 110 | err = ufs_phy->drvdata->wait_for_cdr(phy, i); |
2ff6365e PG |
111 | if (err) |
112 | goto out; | |
113 | } | |
a4de58a9 | 114 | } |
bca21e93 AA |
115 | |
116 | /** | |
117 | * In Samsung ufshci, PHY need to be calibrated at different | |
118 | * stages / state mainly before Linkstartup, after Linkstartup, | |
119 | * before power mode change and after power mode change. | |
120 | * Below state machine to make sure to calibrate PHY in each | |
121 | * state. Here after configuring PHY in a given state, will | |
122 | * change the state to next state so that next state phy | |
123 | * calibration value can be programed | |
124 | */ | |
125 | out: | |
126 | switch (ufs_phy->ufs_phy_state) { | |
127 | case CFG_PRE_INIT: | |
128 | ufs_phy->ufs_phy_state = CFG_POST_INIT; | |
129 | break; | |
130 | case CFG_POST_INIT: | |
131 | ufs_phy->ufs_phy_state = CFG_PRE_PWR_HS; | |
132 | break; | |
133 | case CFG_PRE_PWR_HS: | |
134 | ufs_phy->ufs_phy_state = CFG_POST_PWR_HS; | |
135 | break; | |
136 | case CFG_POST_PWR_HS: | |
137 | /* Change back to INIT state */ | |
138 | ufs_phy->ufs_phy_state = CFG_PRE_INIT; | |
139 | break; | |
140 | default: | |
141 | dev_err(ufs_phy->dev, "wrong state for phy calibration\n"); | |
142 | } | |
143 | ||
144 | return err; | |
145 | } | |
146 | ||
bca21e93 AA |
147 | static int samsung_ufs_phy_clks_init(struct samsung_ufs_phy *phy) |
148 | { | |
8d5bb683 CP |
149 | int i; |
150 | const struct samsung_ufs_phy_drvdata *drvdata = phy->drvdata; | |
151 | int num_clks = drvdata->num_clks; | |
bca21e93 | 152 | |
8d5bb683 CP |
153 | phy->clks = devm_kcalloc(phy->dev, num_clks, sizeof(*phy->clks), |
154 | GFP_KERNEL); | |
155 | if (!phy->clks) | |
156 | return -ENOMEM; | |
bca21e93 | 157 | |
8d5bb683 CP |
158 | for (i = 0; i < num_clks; i++) |
159 | phy->clks[i].id = drvdata->clk_list[i]; | |
bca21e93 | 160 | |
8d5bb683 | 161 | return devm_clk_bulk_get(phy->dev, num_clks, phy->clks); |
bca21e93 AA |
162 | } |
163 | ||
164 | static int samsung_ufs_phy_init(struct phy *phy) | |
165 | { | |
166 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); | |
bca21e93 AA |
167 | |
168 | ss_phy->lane_cnt = phy->attrs.bus_width; | |
169 | ss_phy->ufs_phy_state = CFG_PRE_INIT; | |
170 | ||
4e123efa CP |
171 | return 0; |
172 | } | |
173 | ||
174 | static int samsung_ufs_phy_power_on(struct phy *phy) | |
175 | { | |
176 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); | |
177 | int ret; | |
178 | ||
179 | samsung_ufs_phy_ctrl_isol(ss_phy, false); | |
180 | ||
8d5bb683 CP |
181 | ret = clk_bulk_prepare_enable(ss_phy->drvdata->num_clks, ss_phy->clks); |
182 | if (ret) { | |
183 | dev_err(ss_phy->dev, "failed to enable ufs phy clocks\n"); | |
184 | return ret; | |
bca21e93 AA |
185 | } |
186 | ||
4e123efa CP |
187 | if (ss_phy->ufs_phy_state == CFG_PRE_INIT) { |
188 | ret = samsung_ufs_phy_calibrate(phy); | |
189 | if (ret) | |
190 | dev_err(ss_phy->dev, "ufs phy calibration failed\n"); | |
191 | } | |
bca21e93 AA |
192 | |
193 | return ret; | |
194 | } | |
195 | ||
bca21e93 AA |
196 | static int samsung_ufs_phy_power_off(struct phy *phy) |
197 | { | |
198 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); | |
199 | ||
4e123efa CP |
200 | clk_bulk_disable_unprepare(ss_phy->drvdata->num_clks, ss_phy->clks); |
201 | ||
bca21e93 | 202 | samsung_ufs_phy_ctrl_isol(ss_phy, true); |
4e123efa | 203 | |
bca21e93 AA |
204 | return 0; |
205 | } | |
206 | ||
207 | static int samsung_ufs_phy_set_mode(struct phy *generic_phy, | |
208 | enum phy_mode mode, int submode) | |
209 | { | |
210 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(generic_phy); | |
211 | ||
212 | ss_phy->mode = PHY_MODE_INVALID; | |
213 | ||
214 | if (mode > 0) | |
215 | ss_phy->mode = mode; | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static int samsung_ufs_phy_exit(struct phy *phy) | |
221 | { | |
222 | struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); | |
223 | ||
4e123efa | 224 | ss_phy->ufs_phy_state = CFG_TAG_MAX; |
bca21e93 AA |
225 | |
226 | return 0; | |
227 | } | |
228 | ||
f9781f7f | 229 | static const struct phy_ops samsung_ufs_phy_ops = { |
bca21e93 AA |
230 | .init = samsung_ufs_phy_init, |
231 | .exit = samsung_ufs_phy_exit, | |
232 | .power_on = samsung_ufs_phy_power_on, | |
233 | .power_off = samsung_ufs_phy_power_off, | |
234 | .calibrate = samsung_ufs_phy_calibrate, | |
235 | .set_mode = samsung_ufs_phy_set_mode, | |
236 | .owner = THIS_MODULE, | |
237 | }; | |
238 | ||
239 | static const struct of_device_id samsung_ufs_phy_match[]; | |
240 | ||
241 | static int samsung_ufs_phy_probe(struct platform_device *pdev) | |
242 | { | |
243 | struct device *dev = &pdev->dev; | |
244 | const struct of_device_id *match; | |
245 | struct samsung_ufs_phy *phy; | |
246 | struct phy *gen_phy; | |
247 | struct phy_provider *phy_provider; | |
248 | const struct samsung_ufs_phy_drvdata *drvdata; | |
2aecaf6c | 249 | u32 isol_offset; |
bca21e93 AA |
250 | int err = 0; |
251 | ||
252 | match = of_match_node(samsung_ufs_phy_match, dev->of_node); | |
253 | if (!match) { | |
254 | err = -EINVAL; | |
255 | dev_err(dev, "failed to get match_node\n"); | |
256 | goto out; | |
257 | } | |
258 | ||
259 | phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); | |
260 | if (!phy) { | |
261 | err = -ENOMEM; | |
262 | goto out; | |
263 | } | |
264 | ||
265 | phy->reg_pma = devm_platform_ioremap_resource_byname(pdev, "phy-pma"); | |
266 | if (IS_ERR(phy->reg_pma)) { | |
267 | err = PTR_ERR(phy->reg_pma); | |
268 | goto out; | |
269 | } | |
270 | ||
f2c6d0fa PG |
271 | phy->reg_pmu = exynos_get_pmu_regmap_by_phandle(dev->of_node, |
272 | "samsung,pmu-syscon"); | |
bca21e93 AA |
273 | if (IS_ERR(phy->reg_pmu)) { |
274 | err = PTR_ERR(phy->reg_pmu); | |
275 | dev_err(dev, "failed syscon remap for pmu\n"); | |
276 | goto out; | |
277 | } | |
278 | ||
279 | gen_phy = devm_phy_create(dev, NULL, &samsung_ufs_phy_ops); | |
280 | if (IS_ERR(gen_phy)) { | |
281 | err = PTR_ERR(gen_phy); | |
282 | dev_err(dev, "failed to create PHY for ufs-phy\n"); | |
283 | goto out; | |
284 | } | |
285 | ||
286 | drvdata = match->data; | |
287 | phy->dev = dev; | |
e313216b | 288 | phy->drvdata = drvdata; |
521f88bf | 289 | phy->cfgs = drvdata->cfgs; |
f86c1d0a CP |
290 | memcpy(&phy->isol, &drvdata->isol, sizeof(phy->isol)); |
291 | ||
2aecaf6c CP |
292 | if (!of_property_read_u32_index(dev->of_node, "samsung,pmu-syscon", 1, |
293 | &isol_offset)) | |
294 | phy->isol.offset = isol_offset; | |
295 | ||
bca21e93 AA |
296 | phy->lane_cnt = PHY_DEF_LANE_CNT; |
297 | ||
8d5bb683 CP |
298 | err = samsung_ufs_phy_clks_init(phy); |
299 | if (err) { | |
300 | dev_err(dev, "failed to get phy clocks\n"); | |
301 | goto out; | |
302 | } | |
303 | ||
bca21e93 AA |
304 | phy_set_drvdata(gen_phy, phy); |
305 | ||
306 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | |
307 | if (IS_ERR(phy_provider)) { | |
308 | err = PTR_ERR(phy_provider); | |
309 | dev_err(dev, "failed to register phy-provider\n"); | |
310 | goto out; | |
311 | } | |
312 | out: | |
313 | return err; | |
314 | } | |
315 | ||
316 | static const struct of_device_id samsung_ufs_phy_match[] = { | |
317 | { | |
c1cf725d PG |
318 | .compatible = "google,gs101-ufs-phy", |
319 | .data = &tensor_gs101_ufs_phy, | |
320 | }, { | |
bca21e93 AA |
321 | .compatible = "samsung,exynos7-ufs-phy", |
322 | .data = &exynos7_ufs_phy, | |
d6451924 CP |
323 | }, { |
324 | .compatible = "samsung,exynosautov9-ufs-phy", | |
325 | .data = &exynosautov9_ufs_phy, | |
f1b2d06d AA |
326 | }, { |
327 | .compatible = "tesla,fsd-ufs-phy", | |
328 | .data = &fsd_ufs_phy, | |
bca21e93 AA |
329 | }, |
330 | {}, | |
331 | }; | |
332 | MODULE_DEVICE_TABLE(of, samsung_ufs_phy_match); | |
333 | ||
334 | static struct platform_driver samsung_ufs_phy_driver = { | |
335 | .probe = samsung_ufs_phy_probe, | |
336 | .driver = { | |
337 | .name = "samsung-ufs-phy", | |
338 | .of_match_table = samsung_ufs_phy_match, | |
339 | }, | |
340 | }; | |
341 | module_platform_driver(samsung_ufs_phy_driver); | |
342 | MODULE_DESCRIPTION("Samsung SoC UFS PHY Driver"); | |
343 | MODULE_AUTHOR("Seungwon Jeon <essuuj@gmail.com>"); | |
344 | MODULE_AUTHOR("Alim Akhtar <alim.akhtar@samsung.com>"); | |
345 | MODULE_LICENSE("GPL v2"); |