1 | // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) |
2 | /* |
3 | * Freescale DPAA2 Platforms Console Driver |
4 | * |
5 | * Copyright 2015-2016 Freescale Semiconductor Inc. |
6 | * Copyright 2018 NXP |
7 | */ |
8 | |
9 | #define pr_fmt(fmt) "dpaa2-console: " fmt |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/miscdevice.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/uaccess.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/fs.h> |
19 | #include <linux/io.h> |
20 | |
21 | /* MC firmware base low/high registers indexes */ |
22 | #define MCFBALR_OFFSET 0 |
23 | #define MCFBAHR_OFFSET 1 |
24 | |
25 | /* Bit masks used to get the most/least significant part of the MC base addr */ |
26 | #define MC_FW_ADDR_MASK_HIGH 0x1FFFF |
27 | #define MC_FW_ADDR_MASK_LOW 0xE0000000 |
28 | |
29 | #define MC_BUFFER_OFFSET 0x01000000 |
30 | #define MC_BUFFER_SIZE (1024 * 1024 * 16) |
31 | #define MC_OFFSET_DELTA MC_BUFFER_OFFSET |
32 | |
33 | #define AIOP_BUFFER_OFFSET 0x06000000 |
34 | #define AIOP_BUFFER_SIZE (1024 * 1024 * 16) |
35 | #define AIOP_OFFSET_DELTA 0 |
36 | |
37 | #define 0x80000000 |
38 | #define LAST_BYTE(a) ((a) & ~(LOG_HEADER_FLAG_BUFFER_WRAPAROUND)) |
39 | |
40 | /* MC and AIOP Magic words */ |
41 | #define MAGIC_MC 0x4d430100 |
42 | #define MAGIC_AIOP 0x41494F50 |
43 | |
44 | struct { |
45 | __le32 ; |
46 | char [4]; |
47 | __le32 ; |
48 | __le32 ; |
49 | __le32 ; |
50 | }; |
51 | |
52 | struct console_data { |
53 | void __iomem *map_addr; |
54 | struct log_header __iomem *hdr; |
55 | void __iomem *start_addr; |
56 | void __iomem *end_addr; |
57 | void __iomem *end_of_data; |
58 | void __iomem *cur_ptr; |
59 | }; |
60 | |
61 | static struct resource mc_base_addr; |
62 | |
63 | static inline void adjust_end(struct console_data *cd) |
64 | { |
65 | u32 last_byte = readl(addr: &cd->hdr->last_byte); |
66 | |
67 | cd->end_of_data = cd->start_addr + LAST_BYTE(last_byte); |
68 | } |
69 | |
70 | static u64 get_mc_fw_base_address(void) |
71 | { |
72 | u64 mcfwbase = 0ULL; |
73 | u32 __iomem *mcfbaregs; |
74 | |
75 | mcfbaregs = ioremap(offset: mc_base_addr.start, size: resource_size(res: &mc_base_addr)); |
76 | if (!mcfbaregs) { |
77 | pr_err("could not map MC Firmware Base registers\n" ); |
78 | return 0; |
79 | } |
80 | |
81 | mcfwbase = readl(addr: mcfbaregs + MCFBAHR_OFFSET) & |
82 | MC_FW_ADDR_MASK_HIGH; |
83 | mcfwbase <<= 32; |
84 | mcfwbase |= readl(addr: mcfbaregs + MCFBALR_OFFSET) & MC_FW_ADDR_MASK_LOW; |
85 | iounmap(addr: mcfbaregs); |
86 | |
87 | pr_debug("MC base address at 0x%016llx\n" , mcfwbase); |
88 | return mcfwbase; |
89 | } |
90 | |
91 | static ssize_t dpaa2_console_size(struct console_data *cd) |
92 | { |
93 | ssize_t size; |
94 | |
95 | if (cd->cur_ptr <= cd->end_of_data) |
96 | size = cd->end_of_data - cd->cur_ptr; |
97 | else |
98 | size = (cd->end_addr - cd->cur_ptr) + |
99 | (cd->end_of_data - cd->start_addr); |
100 | |
101 | return size; |
102 | } |
103 | |
104 | static int dpaa2_generic_console_open(struct inode *node, struct file *fp, |
105 | u64 offset, u64 size, |
106 | u32 expected_magic, |
107 | u32 offset_delta) |
108 | { |
109 | u32 read_magic, wrapped, last_byte, buf_start, buf_length; |
110 | struct console_data *cd; |
111 | u64 base_addr; |
112 | int err; |
113 | |
114 | cd = kmalloc(size: sizeof(*cd), GFP_KERNEL); |
115 | if (!cd) |
116 | return -ENOMEM; |
117 | |
118 | base_addr = get_mc_fw_base_address(); |
119 | if (!base_addr) { |
120 | err = -EIO; |
121 | goto err_fwba; |
122 | } |
123 | |
124 | cd->map_addr = ioremap(offset: base_addr + offset, size); |
125 | if (!cd->map_addr) { |
126 | pr_err("cannot map console log memory\n" ); |
127 | err = -EIO; |
128 | goto err_ioremap; |
129 | } |
130 | |
131 | cd->hdr = (struct log_header __iomem *)cd->map_addr; |
132 | read_magic = readl(addr: &cd->hdr->magic_word); |
133 | last_byte = readl(addr: &cd->hdr->last_byte); |
134 | buf_start = readl(addr: &cd->hdr->buf_start); |
135 | buf_length = readl(addr: &cd->hdr->buf_length); |
136 | |
137 | if (read_magic != expected_magic) { |
138 | pr_warn("expected = %08x, read = %08x\n" , |
139 | expected_magic, read_magic); |
140 | err = -EIO; |
141 | goto err_magic; |
142 | } |
143 | |
144 | cd->start_addr = cd->map_addr + buf_start - offset_delta; |
145 | cd->end_addr = cd->start_addr + buf_length; |
146 | |
147 | wrapped = last_byte & LOG_HEADER_FLAG_BUFFER_WRAPAROUND; |
148 | |
149 | adjust_end(cd); |
150 | if (wrapped && cd->end_of_data != cd->end_addr) |
151 | cd->cur_ptr = cd->end_of_data + 1; |
152 | else |
153 | cd->cur_ptr = cd->start_addr; |
154 | |
155 | fp->private_data = cd; |
156 | |
157 | return 0; |
158 | |
159 | err_magic: |
160 | iounmap(addr: cd->map_addr); |
161 | |
162 | err_ioremap: |
163 | err_fwba: |
164 | kfree(objp: cd); |
165 | |
166 | return err; |
167 | } |
168 | |
169 | static int dpaa2_mc_console_open(struct inode *node, struct file *fp) |
170 | { |
171 | return dpaa2_generic_console_open(node, fp, |
172 | MC_BUFFER_OFFSET, MC_BUFFER_SIZE, |
173 | MAGIC_MC, MC_OFFSET_DELTA); |
174 | } |
175 | |
176 | static int dpaa2_aiop_console_open(struct inode *node, struct file *fp) |
177 | { |
178 | return dpaa2_generic_console_open(node, fp, |
179 | AIOP_BUFFER_OFFSET, AIOP_BUFFER_SIZE, |
180 | MAGIC_AIOP, AIOP_OFFSET_DELTA); |
181 | } |
182 | |
183 | static int dpaa2_console_close(struct inode *node, struct file *fp) |
184 | { |
185 | struct console_data *cd = fp->private_data; |
186 | |
187 | iounmap(addr: cd->map_addr); |
188 | kfree(objp: cd); |
189 | return 0; |
190 | } |
191 | |
192 | static ssize_t dpaa2_console_read(struct file *fp, char __user *buf, |
193 | size_t count, loff_t *f_pos) |
194 | { |
195 | struct console_data *cd = fp->private_data; |
196 | size_t bytes = dpaa2_console_size(cd); |
197 | size_t bytes_end = cd->end_addr - cd->cur_ptr; |
198 | size_t written = 0; |
199 | void *kbuf; |
200 | int err; |
201 | |
202 | /* Check if we need to adjust the end of data addr */ |
203 | adjust_end(cd); |
204 | |
205 | if (cd->end_of_data == cd->cur_ptr) |
206 | return 0; |
207 | |
208 | if (count < bytes) |
209 | bytes = count; |
210 | |
211 | kbuf = kmalloc(size: bytes, GFP_KERNEL); |
212 | if (!kbuf) |
213 | return -ENOMEM; |
214 | |
215 | if (bytes > bytes_end) { |
216 | memcpy_fromio(kbuf, cd->cur_ptr, bytes_end); |
217 | if (copy_to_user(to: buf, from: kbuf, n: bytes_end)) { |
218 | err = -EFAULT; |
219 | goto err_free_buf; |
220 | } |
221 | buf += bytes_end; |
222 | cd->cur_ptr = cd->start_addr; |
223 | bytes -= bytes_end; |
224 | written += bytes_end; |
225 | } |
226 | |
227 | memcpy_fromio(kbuf, cd->cur_ptr, bytes); |
228 | if (copy_to_user(to: buf, from: kbuf, n: bytes)) { |
229 | err = -EFAULT; |
230 | goto err_free_buf; |
231 | } |
232 | cd->cur_ptr += bytes; |
233 | written += bytes; |
234 | |
235 | kfree(objp: kbuf); |
236 | return written; |
237 | |
238 | err_free_buf: |
239 | kfree(objp: kbuf); |
240 | |
241 | return err; |
242 | } |
243 | |
244 | static const struct file_operations dpaa2_mc_console_fops = { |
245 | .owner = THIS_MODULE, |
246 | .open = dpaa2_mc_console_open, |
247 | .release = dpaa2_console_close, |
248 | .read = dpaa2_console_read, |
249 | }; |
250 | |
251 | static struct miscdevice dpaa2_mc_console_dev = { |
252 | .minor = MISC_DYNAMIC_MINOR, |
253 | .name = "dpaa2_mc_console" , |
254 | .fops = &dpaa2_mc_console_fops |
255 | }; |
256 | |
257 | static const struct file_operations dpaa2_aiop_console_fops = { |
258 | .owner = THIS_MODULE, |
259 | .open = dpaa2_aiop_console_open, |
260 | .release = dpaa2_console_close, |
261 | .read = dpaa2_console_read, |
262 | }; |
263 | |
264 | static struct miscdevice dpaa2_aiop_console_dev = { |
265 | .minor = MISC_DYNAMIC_MINOR, |
266 | .name = "dpaa2_aiop_console" , |
267 | .fops = &dpaa2_aiop_console_fops |
268 | }; |
269 | |
270 | static int dpaa2_console_probe(struct platform_device *pdev) |
271 | { |
272 | int error; |
273 | |
274 | error = of_address_to_resource(dev: pdev->dev.of_node, index: 0, r: &mc_base_addr); |
275 | if (error < 0) { |
276 | pr_err("of_address_to_resource() failed for %pOF with %d\n" , |
277 | pdev->dev.of_node, error); |
278 | return error; |
279 | } |
280 | |
281 | error = misc_register(misc: &dpaa2_mc_console_dev); |
282 | if (error) { |
283 | pr_err("cannot register device %s\n" , |
284 | dpaa2_mc_console_dev.name); |
285 | goto err_register_mc; |
286 | } |
287 | |
288 | error = misc_register(misc: &dpaa2_aiop_console_dev); |
289 | if (error) { |
290 | pr_err("cannot register device %s\n" , |
291 | dpaa2_aiop_console_dev.name); |
292 | goto err_register_aiop; |
293 | } |
294 | |
295 | return 0; |
296 | |
297 | err_register_aiop: |
298 | misc_deregister(misc: &dpaa2_mc_console_dev); |
299 | err_register_mc: |
300 | return error; |
301 | } |
302 | |
303 | static void dpaa2_console_remove(struct platform_device *pdev) |
304 | { |
305 | misc_deregister(misc: &dpaa2_mc_console_dev); |
306 | misc_deregister(misc: &dpaa2_aiop_console_dev); |
307 | } |
308 | |
309 | static const struct of_device_id dpaa2_console_match_table[] = { |
310 | { .compatible = "fsl,dpaa2-console" ,}, |
311 | {}, |
312 | }; |
313 | |
314 | MODULE_DEVICE_TABLE(of, dpaa2_console_match_table); |
315 | |
316 | static struct platform_driver dpaa2_console_driver = { |
317 | .driver = { |
318 | .name = "dpaa2-console" , |
319 | .pm = NULL, |
320 | .of_match_table = dpaa2_console_match_table, |
321 | }, |
322 | .probe = dpaa2_console_probe, |
323 | .remove_new = dpaa2_console_remove, |
324 | }; |
325 | module_platform_driver(dpaa2_console_driver); |
326 | |
327 | MODULE_LICENSE("Dual BSD/GPL" ); |
328 | MODULE_AUTHOR("Roy Pledge <roy.pledge@nxp.com>" ); |
329 | MODULE_DESCRIPTION("DPAA2 console driver" ); |
330 | |