1 | // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) |
2 | /* |
3 | * Copyright 2013-2016 Freescale Semiconductor Inc. |
4 | * |
5 | * I/O services to send MC commands to the MC hardware |
6 | * |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/ioport.h> |
12 | #include <linux/device.h> |
13 | #include <linux/io.h> |
14 | #include <linux/io-64-nonatomic-hi-lo.h> |
15 | #include <linux/fsl/mc.h> |
16 | |
17 | #include "fsl-mc-private.h" |
18 | |
19 | /* |
20 | * Timeout in milliseconds to wait for the completion of an MC command |
21 | */ |
22 | #define MC_CMD_COMPLETION_TIMEOUT_MS 500 |
23 | |
24 | /* |
25 | * usleep_range() min and max values used to throttle down polling |
26 | * iterations while waiting for MC command completion |
27 | */ |
28 | #define MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS 10 |
29 | #define MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS 500 |
30 | |
31 | static enum mc_cmd_status mc_cmd_hdr_read_status(struct fsl_mc_command *cmd) |
32 | { |
33 | struct mc_cmd_header *hdr = (struct mc_cmd_header *)&cmd->header; |
34 | |
35 | return (enum mc_cmd_status)hdr->status; |
36 | } |
37 | |
38 | u16 mc_cmd_hdr_read_cmdid(struct fsl_mc_command *cmd) |
39 | { |
40 | struct mc_cmd_header *hdr = (struct mc_cmd_header *)&cmd->header; |
41 | u16 cmd_id = le16_to_cpu(hdr->cmd_id); |
42 | |
43 | return cmd_id; |
44 | } |
45 | |
46 | static int mc_status_to_error(enum mc_cmd_status status) |
47 | { |
48 | static const int mc_status_to_error_map[] = { |
49 | [MC_CMD_STATUS_OK] = 0, |
50 | [MC_CMD_STATUS_AUTH_ERR] = -EACCES, |
51 | [MC_CMD_STATUS_NO_PRIVILEGE] = -EPERM, |
52 | [MC_CMD_STATUS_DMA_ERR] = -EIO, |
53 | [MC_CMD_STATUS_CONFIG_ERR] = -ENXIO, |
54 | [MC_CMD_STATUS_TIMEOUT] = -ETIMEDOUT, |
55 | [MC_CMD_STATUS_NO_RESOURCE] = -ENAVAIL, |
56 | [MC_CMD_STATUS_NO_MEMORY] = -ENOMEM, |
57 | [MC_CMD_STATUS_BUSY] = -EBUSY, |
58 | [MC_CMD_STATUS_UNSUPPORTED_OP] = -ENOTSUPP, |
59 | [MC_CMD_STATUS_INVALID_STATE] = -ENODEV, |
60 | }; |
61 | |
62 | if ((u32)status >= ARRAY_SIZE(mc_status_to_error_map)) |
63 | return -EINVAL; |
64 | |
65 | return mc_status_to_error_map[status]; |
66 | } |
67 | |
68 | static const char *mc_status_to_string(enum mc_cmd_status status) |
69 | { |
70 | static const char *const status_strings[] = { |
71 | [MC_CMD_STATUS_OK] = "Command completed successfully" , |
72 | [MC_CMD_STATUS_READY] = "Command ready to be processed" , |
73 | [MC_CMD_STATUS_AUTH_ERR] = "Authentication error" , |
74 | [MC_CMD_STATUS_NO_PRIVILEGE] = "No privilege" , |
75 | [MC_CMD_STATUS_DMA_ERR] = "DMA or I/O error" , |
76 | [MC_CMD_STATUS_CONFIG_ERR] = "Configuration error" , |
77 | [MC_CMD_STATUS_TIMEOUT] = "Operation timed out" , |
78 | [MC_CMD_STATUS_NO_RESOURCE] = "No resources" , |
79 | [MC_CMD_STATUS_NO_MEMORY] = "No memory available" , |
80 | [MC_CMD_STATUS_BUSY] = "Device is busy" , |
81 | [MC_CMD_STATUS_UNSUPPORTED_OP] = "Unsupported operation" , |
82 | [MC_CMD_STATUS_INVALID_STATE] = "Invalid state" |
83 | }; |
84 | |
85 | if ((unsigned int)status >= ARRAY_SIZE(status_strings)) |
86 | return "Unknown MC error" ; |
87 | |
88 | return status_strings[status]; |
89 | } |
90 | |
91 | /** |
92 | * mc_write_command - writes a command to a Management Complex (MC) portal |
93 | * |
94 | * @portal: pointer to an MC portal |
95 | * @cmd: pointer to a filled command |
96 | */ |
97 | static inline void mc_write_command(struct fsl_mc_command __iomem *portal, |
98 | struct fsl_mc_command *cmd) |
99 | { |
100 | int i; |
101 | |
102 | /* copy command parameters into the portal */ |
103 | for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++) |
104 | /* |
105 | * Data is already in the expected LE byte-order. Do an |
106 | * extra LE -> CPU conversion so that the CPU -> LE done in |
107 | * the device io write api puts it back in the right order. |
108 | */ |
109 | writeq_relaxed(le64_to_cpu(cmd->params[i]), &portal->params[i]); |
110 | |
111 | /* submit the command by writing the header */ |
112 | writeq(le64_to_cpu(cmd->header), addr: &portal->header); |
113 | } |
114 | |
115 | /** |
116 | * mc_read_response - reads the response for the last MC command from a |
117 | * Management Complex (MC) portal |
118 | * |
119 | * @portal: pointer to an MC portal |
120 | * @resp: pointer to command response buffer |
121 | * |
122 | * Returns MC_CMD_STATUS_OK on Success; Error code otherwise. |
123 | */ |
124 | static inline enum mc_cmd_status mc_read_response(struct fsl_mc_command __iomem |
125 | *portal, |
126 | struct fsl_mc_command *resp) |
127 | { |
128 | int i; |
129 | enum mc_cmd_status status; |
130 | |
131 | /* Copy command response header from MC portal: */ |
132 | resp->header = cpu_to_le64(readq_relaxed(&portal->header)); |
133 | status = mc_cmd_hdr_read_status(cmd: resp); |
134 | if (status != MC_CMD_STATUS_OK) |
135 | return status; |
136 | |
137 | /* Copy command response data from MC portal: */ |
138 | for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++) |
139 | /* |
140 | * Data is expected to be in LE byte-order. Do an |
141 | * extra CPU -> LE to revert the LE -> CPU done in |
142 | * the device io read api. |
143 | */ |
144 | resp->params[i] = |
145 | cpu_to_le64(readq_relaxed(&portal->params[i])); |
146 | |
147 | return status; |
148 | } |
149 | |
150 | /** |
151 | * mc_polling_wait_preemptible() - Waits for the completion of an MC |
152 | * command doing preemptible polling. |
153 | * uslepp_range() is called between |
154 | * polling iterations. |
155 | * @mc_io: MC I/O object to be used |
156 | * @cmd: command buffer to receive MC response |
157 | * @mc_status: MC command completion status |
158 | */ |
159 | static int mc_polling_wait_preemptible(struct fsl_mc_io *mc_io, |
160 | struct fsl_mc_command *cmd, |
161 | enum mc_cmd_status *mc_status) |
162 | { |
163 | enum mc_cmd_status status; |
164 | unsigned long jiffies_until_timeout = |
165 | jiffies + msecs_to_jiffies(MC_CMD_COMPLETION_TIMEOUT_MS); |
166 | |
167 | /* |
168 | * Wait for response from the MC hardware: |
169 | */ |
170 | for (;;) { |
171 | status = mc_read_response(portal: mc_io->portal_virt_addr, resp: cmd); |
172 | if (status != MC_CMD_STATUS_READY) |
173 | break; |
174 | |
175 | /* |
176 | * TODO: When MC command completion interrupts are supported |
177 | * call wait function here instead of usleep_range() |
178 | */ |
179 | usleep_range(MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS, |
180 | MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS); |
181 | |
182 | if (time_after_eq(jiffies, jiffies_until_timeout)) { |
183 | dev_dbg(mc_io->dev, |
184 | "MC command timed out (portal: %pa, dprc handle: %#x, command: %#x)\n" , |
185 | &mc_io->portal_phys_addr, |
186 | (unsigned int)mc_cmd_hdr_read_token(cmd), |
187 | (unsigned int)mc_cmd_hdr_read_cmdid(cmd)); |
188 | |
189 | return -ETIMEDOUT; |
190 | } |
191 | } |
192 | |
193 | *mc_status = status; |
194 | return 0; |
195 | } |
196 | |
197 | /** |
198 | * mc_polling_wait_atomic() - Waits for the completion of an MC command |
199 | * doing atomic polling. udelay() is called |
200 | * between polling iterations. |
201 | * @mc_io: MC I/O object to be used |
202 | * @cmd: command buffer to receive MC response |
203 | * @mc_status: MC command completion status |
204 | */ |
205 | static int mc_polling_wait_atomic(struct fsl_mc_io *mc_io, |
206 | struct fsl_mc_command *cmd, |
207 | enum mc_cmd_status *mc_status) |
208 | { |
209 | enum mc_cmd_status status; |
210 | unsigned long timeout_usecs = MC_CMD_COMPLETION_TIMEOUT_MS * 1000; |
211 | |
212 | BUILD_BUG_ON((MC_CMD_COMPLETION_TIMEOUT_MS * 1000) % |
213 | MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS != 0); |
214 | |
215 | for (;;) { |
216 | status = mc_read_response(portal: mc_io->portal_virt_addr, resp: cmd); |
217 | if (status != MC_CMD_STATUS_READY) |
218 | break; |
219 | |
220 | udelay(MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS); |
221 | timeout_usecs -= MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS; |
222 | if (timeout_usecs == 0) { |
223 | dev_dbg(mc_io->dev, |
224 | "MC command timed out (portal: %pa, dprc handle: %#x, command: %#x)\n" , |
225 | &mc_io->portal_phys_addr, |
226 | (unsigned int)mc_cmd_hdr_read_token(cmd), |
227 | (unsigned int)mc_cmd_hdr_read_cmdid(cmd)); |
228 | |
229 | return -ETIMEDOUT; |
230 | } |
231 | } |
232 | |
233 | *mc_status = status; |
234 | return 0; |
235 | } |
236 | |
237 | /** |
238 | * mc_send_command() - Sends a command to the MC device using the given |
239 | * MC I/O object |
240 | * @mc_io: MC I/O object to be used |
241 | * @cmd: command to be sent |
242 | * |
243 | * Returns '0' on Success; Error code otherwise. |
244 | */ |
245 | int mc_send_command(struct fsl_mc_io *mc_io, struct fsl_mc_command *cmd) |
246 | { |
247 | int error; |
248 | enum mc_cmd_status status; |
249 | unsigned long irq_flags = 0; |
250 | |
251 | if (in_irq() && !(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)) |
252 | return -EINVAL; |
253 | |
254 | if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL) |
255 | raw_spin_lock_irqsave(&mc_io->spinlock, irq_flags); |
256 | else |
257 | mutex_lock(&mc_io->mutex); |
258 | |
259 | /* |
260 | * Send command to the MC hardware: |
261 | */ |
262 | mc_write_command(portal: mc_io->portal_virt_addr, cmd); |
263 | |
264 | /* |
265 | * Wait for response from the MC hardware: |
266 | */ |
267 | if (!(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)) |
268 | error = mc_polling_wait_preemptible(mc_io, cmd, mc_status: &status); |
269 | else |
270 | error = mc_polling_wait_atomic(mc_io, cmd, mc_status: &status); |
271 | |
272 | if (error < 0) |
273 | goto common_exit; |
274 | |
275 | if (status != MC_CMD_STATUS_OK) { |
276 | dev_dbg(mc_io->dev, |
277 | "MC command failed: portal: %pa, dprc handle: %#x, command: %#x, status: %s (%#x)\n" , |
278 | &mc_io->portal_phys_addr, |
279 | (unsigned int)mc_cmd_hdr_read_token(cmd), |
280 | (unsigned int)mc_cmd_hdr_read_cmdid(cmd), |
281 | mc_status_to_string(status), |
282 | (unsigned int)status); |
283 | |
284 | error = mc_status_to_error(status); |
285 | goto common_exit; |
286 | } |
287 | |
288 | error = 0; |
289 | common_exit: |
290 | if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL) |
291 | raw_spin_unlock_irqrestore(&mc_io->spinlock, irq_flags); |
292 | else |
293 | mutex_unlock(lock: &mc_io->mutex); |
294 | |
295 | return error; |
296 | } |
297 | EXPORT_SYMBOL_GPL(mc_send_command); |
298 | |