1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Platform UFS Host driver for Cadence controller |
4 | * |
5 | * Copyright (C) 2018 Cadence Design Systems, Inc. |
6 | * |
7 | * Authors: |
8 | * Jan Kotas <jank@cadence.com> |
9 | * |
10 | */ |
11 | |
12 | #include <linux/clk.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/of.h> |
17 | #include <linux/time.h> |
18 | |
19 | #include "ufshcd-pltfrm.h" |
20 | |
21 | #define CDNS_UFS_REG_HCLKDIV 0xFC |
22 | #define CDNS_UFS_REG_PHY_XCFGD1 0x113C |
23 | #define CDNS_UFS_MAX_L4_ATTRS 12 |
24 | |
25 | struct cdns_ufs_host { |
26 | /** |
27 | * cdns_ufs_dme_attr_val - for storing L4 attributes |
28 | */ |
29 | u32 cdns_ufs_dme_attr_val[CDNS_UFS_MAX_L4_ATTRS]; |
30 | }; |
31 | |
32 | /** |
33 | * cdns_ufs_get_l4_attr - get L4 attributes on local side |
34 | * @hba: per adapter instance |
35 | * |
36 | */ |
37 | static void cdns_ufs_get_l4_attr(struct ufs_hba *hba) |
38 | { |
39 | struct cdns_ufs_host *host = ufshcd_get_variant(hba); |
40 | |
41 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERDEVICEID), |
42 | mib_val: &host->cdns_ufs_dme_attr_val[0]); |
43 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERCPORTID), |
44 | mib_val: &host->cdns_ufs_dme_attr_val[1]); |
45 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_TRAFFICCLASS), |
46 | mib_val: &host->cdns_ufs_dme_attr_val[2]); |
47 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_PROTOCOLID), |
48 | mib_val: &host->cdns_ufs_dme_attr_val[3]); |
49 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTFLAGS), |
50 | mib_val: &host->cdns_ufs_dme_attr_val[4]); |
51 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_TXTOKENVALUE), |
52 | mib_val: &host->cdns_ufs_dme_attr_val[5]); |
53 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_RXTOKENVALUE), |
54 | mib_val: &host->cdns_ufs_dme_attr_val[6]); |
55 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE), |
56 | mib_val: &host->cdns_ufs_dme_attr_val[7]); |
57 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE), |
58 | mib_val: &host->cdns_ufs_dme_attr_val[8]); |
59 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_CREDITSTOSEND), |
60 | mib_val: &host->cdns_ufs_dme_attr_val[9]); |
61 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTMODE), |
62 | mib_val: &host->cdns_ufs_dme_attr_val[10]); |
63 | ufshcd_dme_get(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), |
64 | mib_val: &host->cdns_ufs_dme_attr_val[11]); |
65 | } |
66 | |
67 | /** |
68 | * cdns_ufs_set_l4_attr - set L4 attributes on local side |
69 | * @hba: per adapter instance |
70 | * |
71 | */ |
72 | static void cdns_ufs_set_l4_attr(struct ufs_hba *hba) |
73 | { |
74 | struct cdns_ufs_host *host = ufshcd_get_variant(hba); |
75 | |
76 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), mib_val: 0); |
77 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERDEVICEID), |
78 | mib_val: host->cdns_ufs_dme_attr_val[0]); |
79 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERCPORTID), |
80 | mib_val: host->cdns_ufs_dme_attr_val[1]); |
81 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_TRAFFICCLASS), |
82 | mib_val: host->cdns_ufs_dme_attr_val[2]); |
83 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_PROTOCOLID), |
84 | mib_val: host->cdns_ufs_dme_attr_val[3]); |
85 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTFLAGS), |
86 | mib_val: host->cdns_ufs_dme_attr_val[4]); |
87 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_TXTOKENVALUE), |
88 | mib_val: host->cdns_ufs_dme_attr_val[5]); |
89 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_RXTOKENVALUE), |
90 | mib_val: host->cdns_ufs_dme_attr_val[6]); |
91 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE), |
92 | mib_val: host->cdns_ufs_dme_attr_val[7]); |
93 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE), |
94 | mib_val: host->cdns_ufs_dme_attr_val[8]); |
95 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_CREDITSTOSEND), |
96 | mib_val: host->cdns_ufs_dme_attr_val[9]); |
97 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTMODE), |
98 | mib_val: host->cdns_ufs_dme_attr_val[10]); |
99 | ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), |
100 | mib_val: host->cdns_ufs_dme_attr_val[11]); |
101 | } |
102 | |
103 | /** |
104 | * cdns_ufs_set_hclkdiv() - set HCLKDIV register value based on the core_clk. |
105 | * @hba: host controller instance |
106 | * |
107 | * Return: zero for success and non-zero for failure. |
108 | */ |
109 | static int cdns_ufs_set_hclkdiv(struct ufs_hba *hba) |
110 | { |
111 | struct ufs_clk_info *clki; |
112 | struct list_head *head = &hba->clk_list_head; |
113 | unsigned long core_clk_rate = 0; |
114 | u32 core_clk_div = 0; |
115 | |
116 | if (list_empty(head)) |
117 | return 0; |
118 | |
119 | list_for_each_entry(clki, head, list) { |
120 | if (IS_ERR_OR_NULL(ptr: clki->clk)) |
121 | continue; |
122 | if (!strcmp(clki->name, "core_clk" )) |
123 | core_clk_rate = clk_get_rate(clk: clki->clk); |
124 | } |
125 | |
126 | if (!core_clk_rate) { |
127 | dev_err(hba->dev, "%s: unable to find core_clk rate\n" , |
128 | __func__); |
129 | return -EINVAL; |
130 | } |
131 | |
132 | core_clk_div = core_clk_rate / USEC_PER_SEC; |
133 | |
134 | ufshcd_writel(hba, core_clk_div, CDNS_UFS_REG_HCLKDIV); |
135 | /** |
136 | * Make sure the register was updated, |
137 | * UniPro layer will not work with an incorrect value. |
138 | */ |
139 | mb(); |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | /** |
145 | * cdns_ufs_hce_enable_notify() - set HCLKDIV register |
146 | * @hba: host controller instance |
147 | * @status: notify stage (pre, post change) |
148 | * |
149 | * Return: zero for success and non-zero for failure. |
150 | */ |
151 | static int cdns_ufs_hce_enable_notify(struct ufs_hba *hba, |
152 | enum ufs_notify_change_status status) |
153 | { |
154 | if (status != PRE_CHANGE) |
155 | return 0; |
156 | |
157 | return cdns_ufs_set_hclkdiv(hba); |
158 | } |
159 | |
160 | /** |
161 | * cdns_ufs_hibern8_notify() - save and restore L4 attributes. |
162 | * @hba: host controller instance |
163 | * @cmd: UIC Command |
164 | * @status: notify stage (pre, post change) |
165 | */ |
166 | static void cdns_ufs_hibern8_notify(struct ufs_hba *hba, enum uic_cmd_dme cmd, |
167 | enum ufs_notify_change_status status) |
168 | { |
169 | if (status == PRE_CHANGE && cmd == UIC_CMD_DME_HIBER_ENTER) |
170 | cdns_ufs_get_l4_attr(hba); |
171 | if (status == POST_CHANGE && cmd == UIC_CMD_DME_HIBER_EXIT) |
172 | cdns_ufs_set_l4_attr(hba); |
173 | } |
174 | |
175 | /** |
176 | * cdns_ufs_link_startup_notify() - handle link startup. |
177 | * @hba: host controller instance |
178 | * @status: notify stage (pre, post change) |
179 | * |
180 | * Return: zero for success and non-zero for failure. |
181 | */ |
182 | static int cdns_ufs_link_startup_notify(struct ufs_hba *hba, |
183 | enum ufs_notify_change_status status) |
184 | { |
185 | if (status != PRE_CHANGE) |
186 | return 0; |
187 | |
188 | /* |
189 | * Some UFS devices have issues if LCC is enabled. |
190 | * So we are setting PA_Local_TX_LCC_Enable to 0 |
191 | * before link startup which will make sure that both host |
192 | * and device TX LCC are disabled once link startup is |
193 | * completed. |
194 | */ |
195 | ufshcd_disable_host_tx_lcc(hba); |
196 | |
197 | /* |
198 | * Disabling Autohibern8 feature in cadence UFS |
199 | * to mask unexpected interrupt trigger. |
200 | */ |
201 | hba->ahit = 0; |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | /** |
207 | * cdns_ufs_init - performs additional ufs initialization |
208 | * @hba: host controller instance |
209 | * |
210 | * Return: status of initialization. |
211 | */ |
212 | static int cdns_ufs_init(struct ufs_hba *hba) |
213 | { |
214 | int status = 0; |
215 | struct cdns_ufs_host *host; |
216 | struct device *dev = hba->dev; |
217 | |
218 | host = devm_kzalloc(dev, size: sizeof(*host), GFP_KERNEL); |
219 | |
220 | if (!host) |
221 | return -ENOMEM; |
222 | ufshcd_set_variant(hba, variant: host); |
223 | |
224 | status = ufshcd_vops_phy_initialization(hba); |
225 | |
226 | return status; |
227 | } |
228 | |
229 | /** |
230 | * cdns_ufs_m31_16nm_phy_initialization - performs m31 phy initialization |
231 | * @hba: host controller instance |
232 | * |
233 | * Return: 0 (success). |
234 | */ |
235 | static int cdns_ufs_m31_16nm_phy_initialization(struct ufs_hba *hba) |
236 | { |
237 | u32 data; |
238 | |
239 | /* Increase RX_Advanced_Min_ActivateTime_Capability */ |
240 | data = ufshcd_readl(hba, CDNS_UFS_REG_PHY_XCFGD1); |
241 | data |= BIT(24); |
242 | ufshcd_writel(hba, data, CDNS_UFS_REG_PHY_XCFGD1); |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static const struct ufs_hba_variant_ops cdns_ufs_pltfm_hba_vops = { |
248 | .name = "cdns-ufs-pltfm" , |
249 | .init = cdns_ufs_init, |
250 | .hce_enable_notify = cdns_ufs_hce_enable_notify, |
251 | .link_startup_notify = cdns_ufs_link_startup_notify, |
252 | .hibern8_notify = cdns_ufs_hibern8_notify, |
253 | }; |
254 | |
255 | static const struct ufs_hba_variant_ops cdns_ufs_m31_16nm_pltfm_hba_vops = { |
256 | .name = "cdns-ufs-pltfm" , |
257 | .init = cdns_ufs_init, |
258 | .hce_enable_notify = cdns_ufs_hce_enable_notify, |
259 | .link_startup_notify = cdns_ufs_link_startup_notify, |
260 | .phy_initialization = cdns_ufs_m31_16nm_phy_initialization, |
261 | .hibern8_notify = cdns_ufs_hibern8_notify, |
262 | }; |
263 | |
264 | static const struct of_device_id cdns_ufs_of_match[] = { |
265 | { |
266 | .compatible = "cdns,ufshc" , |
267 | .data = &cdns_ufs_pltfm_hba_vops, |
268 | }, |
269 | { |
270 | .compatible = "cdns,ufshc-m31-16nm" , |
271 | .data = &cdns_ufs_m31_16nm_pltfm_hba_vops, |
272 | }, |
273 | { }, |
274 | }; |
275 | |
276 | MODULE_DEVICE_TABLE(of, cdns_ufs_of_match); |
277 | |
278 | /** |
279 | * cdns_ufs_pltfrm_probe - probe routine of the driver |
280 | * @pdev: pointer to platform device handle |
281 | * |
282 | * Return: zero for success and non-zero for failure. |
283 | */ |
284 | static int cdns_ufs_pltfrm_probe(struct platform_device *pdev) |
285 | { |
286 | int err; |
287 | const struct of_device_id *of_id; |
288 | struct ufs_hba_variant_ops *vops; |
289 | struct device *dev = &pdev->dev; |
290 | |
291 | of_id = of_match_node(matches: cdns_ufs_of_match, node: dev->of_node); |
292 | vops = (struct ufs_hba_variant_ops *)of_id->data; |
293 | |
294 | /* Perform generic probe */ |
295 | err = ufshcd_pltfrm_init(pdev, vops); |
296 | if (err) |
297 | dev_err(dev, "ufshcd_pltfrm_init() failed %d\n" , err); |
298 | |
299 | return err; |
300 | } |
301 | |
302 | /** |
303 | * cdns_ufs_pltfrm_remove - removes the ufs driver |
304 | * @pdev: pointer to platform device handle |
305 | * |
306 | * Return: 0 (success). |
307 | */ |
308 | static void cdns_ufs_pltfrm_remove(struct platform_device *pdev) |
309 | { |
310 | struct ufs_hba *hba = platform_get_drvdata(pdev); |
311 | |
312 | ufshcd_remove(hba); |
313 | } |
314 | |
315 | static const struct dev_pm_ops cdns_ufs_dev_pm_ops = { |
316 | SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume) |
317 | SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL) |
318 | .prepare = ufshcd_suspend_prepare, |
319 | .complete = ufshcd_resume_complete, |
320 | }; |
321 | |
322 | static struct platform_driver cdns_ufs_pltfrm_driver = { |
323 | .probe = cdns_ufs_pltfrm_probe, |
324 | .remove_new = cdns_ufs_pltfrm_remove, |
325 | .driver = { |
326 | .name = "cdns-ufshcd" , |
327 | .pm = &cdns_ufs_dev_pm_ops, |
328 | .of_match_table = cdns_ufs_of_match, |
329 | }, |
330 | }; |
331 | |
332 | module_platform_driver(cdns_ufs_pltfrm_driver); |
333 | |
334 | MODULE_AUTHOR("Jan Kotas <jank@cadence.com>" ); |
335 | MODULE_DESCRIPTION("Cadence UFS host controller platform driver" ); |
336 | MODULE_LICENSE("GPL v2" ); |
337 | |