]>
Commit | Line | Data |
---|---|---|
14723ed5 | 1 | // SPDX-License-Identifier: GPL-2.0 |
1327d167 IE |
2 | /* |
3 | * Xilinx Zynq MPSoC Firmware driver | |
4 | * | |
5 | * Copyright (C) 2018-2019 Xilinx, Inc. | |
6 | */ | |
14723ed5 | 7 | |
1327d167 | 8 | #include <common.h> |
380bd083 | 9 | #include <cpu_func.h> |
14723ed5 | 10 | #include <dm.h> |
e0283cbd | 11 | #include <dm/lists.h> |
f7ae49fc | 12 | #include <log.h> |
866225f3 | 13 | #include <zynqmp_firmware.h> |
90526e9f | 14 | #include <asm/cache.h> |
25a5818f | 15 | #include <asm/ptrace.h> |
14723ed5 | 16 | |
1327d167 IE |
17 | #if defined(CONFIG_ZYNQMP_IPI) |
18 | #include <mailbox.h> | |
19 | #include <asm/arch/sys_proto.h> | |
20 | ||
490f6273 IE |
21 | #define PMUFW_PAYLOAD_ARG_CNT 8 |
22 | ||
4c86e083 | 23 | #define XST_PM_NO_ACCESS 2002L |
11c07719 | 24 | #define XST_PM_ALREADY_CONFIGURED 2009L |
4c86e083 | 25 | |
1327d167 IE |
26 | struct zynqmp_power { |
27 | struct mbox_chan tx_chan; | |
28 | struct mbox_chan rx_chan; | |
29 | } zynqmp_power; | |
30 | ||
c750c6db MS |
31 | #define NODE_ID_LOCATION 5 |
32 | ||
33 | static unsigned int xpm_configobject[] = { | |
34 | /**********************************************************************/ | |
35 | /* HEADER */ | |
36 | 2, /* Number of remaining words in the header */ | |
37 | 1, /* Number of sections included in config object */ | |
38 | PM_CONFIG_OBJECT_TYPE_OVERLAY, /* Type of Config object as overlay */ | |
39 | /**********************************************************************/ | |
40 | /* SLAVE SECTION */ | |
41 | ||
42 | PM_CONFIG_SLAVE_SECTION_ID, /* Section ID */ | |
43 | 1, /* Number of slaves */ | |
44 | ||
45 | 0, /* Node ID which will be changed below */ | |
46 | PM_SLAVE_FLAG_IS_SHAREABLE, | |
47 | PM_CONFIG_IPI_PSU_CORTEXA53_0_MASK | | |
48 | PM_CONFIG_IPI_PSU_CORTEXR5_0_MASK | | |
49 | PM_CONFIG_IPI_PSU_CORTEXR5_1_MASK, /* IPI Mask */ | |
50 | }; | |
51 | ||
fac46bc4 MS |
52 | static unsigned int xpm_configobject_close[] = { |
53 | /**********************************************************************/ | |
54 | /* HEADER */ | |
55 | 2, /* Number of remaining words in the header */ | |
56 | 1, /* Number of sections included in config object */ | |
57 | PM_CONFIG_OBJECT_TYPE_OVERLAY, /* Type of Config object as overlay */ | |
58 | /**********************************************************************/ | |
59 | /* SET CONFIG SECTION */ | |
60 | PM_CONFIG_SET_CONFIG_SECTION_ID, | |
61 | 0U, /* Loading permission to Overlay config object */ | |
62 | }; | |
63 | ||
64 | int zynqmp_pmufw_config_close(void) | |
65 | { | |
66 | zynqmp_pmufw_load_config_object(xpm_configobject_close, | |
67 | sizeof(xpm_configobject_close)); | |
68 | return 0; | |
69 | } | |
70 | ||
c750c6db MS |
71 | int zynqmp_pmufw_node(u32 id) |
72 | { | |
73 | /* Record power domain id */ | |
74 | xpm_configobject[NODE_ID_LOCATION] = id; | |
75 | ||
76 | zynqmp_pmufw_load_config_object(xpm_configobject, | |
77 | sizeof(xpm_configobject)); | |
78 | ||
79 | return 0; | |
80 | } | |
81 | ||
490f6273 IE |
82 | static int ipi_req(const u32 *req, size_t req_len, u32 *res, size_t res_maxlen) |
83 | { | |
84 | struct zynqmp_ipi_msg msg; | |
85 | int ret; | |
53f5d168 MS |
86 | u32 buffer[PAYLOAD_ARG_CNT]; |
87 | ||
88 | if (!res) | |
89 | res = buffer; | |
490f6273 IE |
90 | |
91 | if (req_len > PMUFW_PAYLOAD_ARG_CNT || | |
92 | res_maxlen > PMUFW_PAYLOAD_ARG_CNT) | |
93 | return -EINVAL; | |
94 | ||
95 | if (!(zynqmp_power.tx_chan.dev) || !(&zynqmp_power.rx_chan.dev)) | |
96 | return -EINVAL; | |
97 | ||
2eabb6bf | 98 | debug("%s, Sending IPI message with ID: 0x%0x\n", __func__, req[0]); |
490f6273 IE |
99 | msg.buf = (u32 *)req; |
100 | msg.len = req_len; | |
101 | ret = mbox_send(&zynqmp_power.tx_chan, &msg); | |
102 | if (ret) { | |
103 | debug("%s: Sending message failed\n", __func__); | |
104 | return ret; | |
105 | } | |
106 | ||
107 | msg.buf = res; | |
108 | msg.len = res_maxlen; | |
109 | ret = mbox_recv(&zynqmp_power.rx_chan, &msg, 100); | |
110 | if (ret) | |
111 | debug("%s: Receiving message failed\n", __func__); | |
112 | ||
113 | return ret; | |
114 | } | |
115 | ||
116 | unsigned int zynqmp_firmware_version(void) | |
117 | { | |
118 | int ret; | |
119 | u32 ret_payload[PAYLOAD_ARG_CNT]; | |
120 | static u32 pm_api_version = ZYNQMP_PM_VERSION_INVALID; | |
121 | ||
122 | /* | |
123 | * Get PMU version only once and later | |
124 | * just return stored values instead of | |
125 | * asking PMUFW again. | |
126 | **/ | |
127 | if (pm_api_version == ZYNQMP_PM_VERSION_INVALID) { | |
490f6273 | 128 | |
2eabb6bf IE |
129 | ret = xilinx_pm_request(PM_GET_API_VERSION, 0, 0, 0, 0, |
130 | ret_payload); | |
490f6273 IE |
131 | if (ret) |
132 | panic("PMUFW is not found - Please load it!\n"); | |
133 | ||
134 | pm_api_version = ret_payload[1]; | |
135 | if (pm_api_version < ZYNQMP_PM_VERSION) | |
136 | panic("PMUFW version error. Expected: v%d.%d\n", | |
137 | ZYNQMP_PM_VERSION_MAJOR, ZYNQMP_PM_VERSION_MINOR); | |
138 | } | |
139 | ||
140 | return pm_api_version; | |
141 | }; | |
142 | ||
7d9ee466 ARS |
143 | int zynqmp_pm_set_sd_config(u32 node, enum pm_sd_config_type config, u32 value) |
144 | { | |
145 | int ret; | |
146 | ||
147 | ret = xilinx_pm_request(PM_IOCTL, node, IOCTL_SET_SD_CONFIG, | |
148 | config, value, NULL); | |
149 | if (ret) | |
150 | printf("%s: node %d: set_sd_config %d failed\n", | |
151 | __func__, node, config); | |
152 | ||
153 | return ret; | |
154 | } | |
155 | ||
156 | int zynqmp_pm_is_function_supported(const u32 api_id, const u32 id) | |
157 | { | |
158 | int ret; | |
159 | u32 *bit_mask; | |
160 | u32 ret_payload[PAYLOAD_ARG_CNT]; | |
161 | ||
162 | /* Input arguments validation */ | |
163 | if (id >= 64 || (api_id != PM_IOCTL && api_id != PM_QUERY_DATA)) | |
164 | return -EINVAL; | |
165 | ||
166 | /* Check feature check API version */ | |
167 | ret = xilinx_pm_request(PM_FEATURE_CHECK, PM_FEATURE_CHECK, 0, 0, 0, | |
168 | ret_payload); | |
169 | if (ret) | |
170 | return ret; | |
171 | ||
172 | /* Check if feature check version 2 is supported or not */ | |
173 | if ((ret_payload[1] & FIRMWARE_VERSION_MASK) == PM_API_VERSION_2) { | |
174 | /* | |
175 | * Call feature check for IOCTL/QUERY API to get IOCTL ID or | |
176 | * QUERY ID feature status. | |
177 | */ | |
178 | ||
179 | ret = xilinx_pm_request(PM_FEATURE_CHECK, api_id, 0, 0, 0, | |
180 | ret_payload); | |
181 | if (ret) | |
182 | return ret; | |
183 | ||
184 | bit_mask = &ret_payload[2]; | |
185 | if ((bit_mask[(id / 32)] & BIT((id % 32))) == 0) | |
186 | return -EOPNOTSUPP; | |
187 | } else { | |
188 | return -ENODATA; | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
a3e552b5 MS |
194 | /** |
195 | * Send a configuration object to the PMU firmware. | |
196 | * | |
197 | * @cfg_obj: Pointer to the configuration object | |
198 | * @size: Size of @cfg_obj in bytes | |
199 | */ | |
200 | void zynqmp_pmufw_load_config_object(const void *cfg_obj, size_t size) | |
201 | { | |
a3e552b5 | 202 | int err; |
2eabb6bf | 203 | u32 ret_payload[PAYLOAD_ARG_CNT]; |
a3e552b5 | 204 | |
12662e70 MS |
205 | if (IS_ENABLED(CONFIG_SPL_BUILD)) |
206 | printf("Loading new PMUFW cfg obj (%ld bytes)\n", size); | |
a3e552b5 | 207 | |
380bd083 MS |
208 | flush_dcache_range((ulong)cfg_obj, (ulong)(cfg_obj + size)); |
209 | ||
2eabb6bf IE |
210 | err = xilinx_pm_request(PM_SET_CONFIGURATION, (u32)(u64)cfg_obj, 0, 0, |
211 | 0, ret_payload); | |
4c86e083 MS |
212 | if (err == XST_PM_NO_ACCESS) { |
213 | printf("PMUFW no permission to change config object\n"); | |
214 | return; | |
215 | } | |
216 | ||
11c07719 MS |
217 | if (err == XST_PM_ALREADY_CONFIGURED) { |
218 | debug("PMUFW Node is already configured\n"); | |
219 | return; | |
220 | } | |
221 | ||
a3e552b5 | 222 | if (err) |
4c86e083 MS |
223 | printf("Cannot load PMUFW configuration object (%d)\n", err); |
224 | ||
2eabb6bf IE |
225 | if (ret_payload[0]) |
226 | printf("PMUFW returned 0x%08x status!\n", ret_payload[0]); | |
4c86e083 | 227 | |
2eabb6bf | 228 | if ((err || ret_payload[0]) && IS_ENABLED(CONFIG_SPL_BUILD)) |
4c86e083 | 229 | panic("PMUFW config object loading failed in EL3\n"); |
a3e552b5 MS |
230 | } |
231 | ||
1327d167 IE |
232 | static int zynqmp_power_probe(struct udevice *dev) |
233 | { | |
44dccd59 | 234 | int ret; |
1327d167 IE |
235 | |
236 | debug("%s, (dev=%p)\n", __func__, dev); | |
237 | ||
238 | ret = mbox_get_by_name(dev, "tx", &zynqmp_power.tx_chan); | |
239 | if (ret) { | |
44dccd59 | 240 | debug("%s: Cannot find tx mailbox\n", __func__); |
1327d167 IE |
241 | return ret; |
242 | } | |
243 | ||
244 | ret = mbox_get_by_name(dev, "rx", &zynqmp_power.rx_chan); | |
490f6273 | 245 | if (ret) { |
44dccd59 | 246 | debug("%s: Cannot find rx mailbox\n", __func__); |
490f6273 IE |
247 | return ret; |
248 | } | |
1327d167 | 249 | |
490f6273 IE |
250 | ret = zynqmp_firmware_version(); |
251 | printf("PMUFW:\tv%d.%d\n", | |
252 | ret >> ZYNQMP_PM_VERSION_MAJOR_SHIFT, | |
253 | ret & ZYNQMP_PM_VERSION_MINOR_MASK); | |
254 | ||
255 | return 0; | |
1327d167 IE |
256 | }; |
257 | ||
258 | static const struct udevice_id zynqmp_power_ids[] = { | |
259 | { .compatible = "xlnx,zynqmp-power" }, | |
260 | { } | |
261 | }; | |
262 | ||
263 | U_BOOT_DRIVER(zynqmp_power) = { | |
264 | .name = "zynqmp_power", | |
265 | .id = UCLASS_FIRMWARE, | |
266 | .of_match = zynqmp_power_ids, | |
267 | .probe = zynqmp_power_probe, | |
268 | }; | |
269 | #endif | |
270 | ||
40361951 MS |
271 | int __maybe_unused xilinx_pm_request(u32 api_id, u32 arg0, u32 arg1, u32 arg2, |
272 | u32 arg3, u32 *ret_payload) | |
866225f3 | 273 | { |
2eabb6bf | 274 | debug("%s at EL%d, API ID: 0x%0x\n", __func__, current_el(), api_id); |
866225f3 | 275 | |
2eabb6bf IE |
276 | if (IS_ENABLED(CONFIG_SPL_BUILD) || current_el() == 3) { |
277 | #if defined(CONFIG_ZYNQMP_IPI) | |
278 | /* | |
279 | * Use fixed payload and arg size as the EL2 call. The firmware | |
280 | * is capable to handle PMUFW_PAYLOAD_ARG_CNT bytes but the | |
281 | * firmware API is limited by the SMC call size | |
282 | */ | |
283 | u32 regs[] = {api_id, arg0, arg1, arg2, arg3}; | |
b05cc389 | 284 | int ret; |
2eabb6bf | 285 | |
5690128f MS |
286 | if (api_id == PM_FPGA_LOAD) { |
287 | /* Swap addr_hi/low because of incompatibility */ | |
288 | u32 temp = regs[1]; | |
289 | ||
290 | regs[1] = regs[2]; | |
291 | regs[2] = temp; | |
292 | } | |
293 | ||
b05cc389 MS |
294 | ret = ipi_req(regs, PAYLOAD_ARG_CNT, ret_payload, |
295 | PAYLOAD_ARG_CNT); | |
296 | if (ret) | |
297 | return ret; | |
2eabb6bf | 298 | #else |
9bed8a63 | 299 | return -EPERM; |
2eabb6bf IE |
300 | #endif |
301 | } else { | |
302 | /* | |
303 | * Added SIP service call Function Identifier | |
304 | * Make sure to stay in x0 register | |
305 | */ | |
306 | struct pt_regs regs; | |
307 | ||
308 | regs.regs[0] = PM_SIP_SVC | api_id; | |
309 | regs.regs[1] = ((u64)arg1 << 32) | arg0; | |
310 | regs.regs[2] = ((u64)arg3 << 32) | arg2; | |
311 | ||
312 | smc_call(®s); | |
313 | ||
314 | if (ret_payload) { | |
315 | ret_payload[0] = (u32)regs.regs[0]; | |
316 | ret_payload[1] = upper_32_bits(regs.regs[0]); | |
317 | ret_payload[2] = (u32)regs.regs[1]; | |
318 | ret_payload[3] = upper_32_bits(regs.regs[1]); | |
319 | ret_payload[4] = (u32)regs.regs[2]; | |
320 | } | |
866225f3 | 321 | |
866225f3 | 322 | } |
2eabb6bf | 323 | return (ret_payload) ? ret_payload[0] : 0; |
866225f3 MS |
324 | } |
325 | ||
14723ed5 RV |
326 | static const struct udevice_id zynqmp_firmware_ids[] = { |
327 | { .compatible = "xlnx,zynqmp-firmware" }, | |
95105089 | 328 | { .compatible = "xlnx,versal-firmware"}, |
14723ed5 RV |
329 | { } |
330 | }; | |
331 | ||
e0283cbd MS |
332 | static int zynqmp_firmware_bind(struct udevice *dev) |
333 | { | |
334 | int ret; | |
335 | struct udevice *child; | |
336 | ||
337 | if (IS_ENABLED(CONFIG_ZYNQMP_POWER_DOMAIN)) { | |
338 | ret = device_bind_driver_to_node(dev, "zynqmp_power_domain", | |
339 | "zynqmp_power_domain", | |
340 | dev_ofnode(dev), &child); | |
341 | if (ret) { | |
342 | printf("zynqmp power domain driver is not bound: %d\n", ret); | |
343 | return ret; | |
344 | } | |
345 | } | |
346 | ||
347 | return dm_scan_fdt_dev(dev); | |
348 | } | |
349 | ||
14723ed5 RV |
350 | U_BOOT_DRIVER(zynqmp_firmware) = { |
351 | .id = UCLASS_FIRMWARE, | |
6c0e59fc | 352 | .name = "zynqmp_firmware", |
14723ed5 | 353 | .of_match = zynqmp_firmware_ids, |
e0283cbd | 354 | .bind = zynqmp_firmware_bind, |
14723ed5 | 355 | }; |