1 | /* Memory Scrubber register driver |
---|
2 | * |
---|
3 | * COPYRIGHT (c) 2017. |
---|
4 | * Cobham Gaisler AB. |
---|
5 | * |
---|
6 | * The license and distribution terms for this file may be |
---|
7 | * found in the file LICENSE in this distribution or at |
---|
8 | * http://www.rtems.org/license/LICENSE. |
---|
9 | */ |
---|
10 | |
---|
11 | #include <stdint.h> |
---|
12 | #include <stdlib.h> |
---|
13 | #include <stdio.h> |
---|
14 | #include <string.h> |
---|
15 | #include <rtems/bspIo.h> |
---|
16 | #include <drvmgr/drvmgr.h> |
---|
17 | #include <drvmgr/ambapp_bus.h> |
---|
18 | |
---|
19 | #include <bsp/memscrub.h> |
---|
20 | |
---|
21 | /*#define STATIC*/ |
---|
22 | #define STATIC static |
---|
23 | |
---|
24 | #define UNUSED __attribute__((unused)) |
---|
25 | |
---|
26 | /*#define DEBUG 1*/ |
---|
27 | |
---|
28 | #ifdef DEBUG |
---|
29 | #define DBG(x...) printf(x) |
---|
30 | #else |
---|
31 | #define DBG(x...) |
---|
32 | #endif |
---|
33 | |
---|
34 | #define REG_WRITE(addr, val) (*(volatile uint32_t *)(addr) = (uint32_t)(val)) |
---|
35 | #define REG_READ(addr) (*(volatile uint32_t *)(addr)) |
---|
36 | |
---|
37 | /* |
---|
38 | * MEMORYSCRUBBER AHBS register fields |
---|
39 | * DEFINED IN HEADER FILE |
---|
40 | */ |
---|
41 | |
---|
42 | /* |
---|
43 | * MEMORYSCRUBBER AHBERC register fields |
---|
44 | * DEFINED IN HEADER FILE |
---|
45 | */ |
---|
46 | |
---|
47 | /* |
---|
48 | * MEMORYSCRUBBER STAT register fields |
---|
49 | * DEFINED IN HEADER FILE |
---|
50 | */ |
---|
51 | |
---|
52 | /* |
---|
53 | * MEMORYSCRUBBER CONFIG register fields |
---|
54 | * DEFINED IN HEADER FILE |
---|
55 | */ |
---|
56 | |
---|
57 | /* |
---|
58 | * MEMORYSCRUBBER ETHRES register fields |
---|
59 | * DEFINED IN HEADER FILE |
---|
60 | */ |
---|
61 | |
---|
62 | /* MEMORYSCRUBBER Registers layout */ |
---|
63 | struct memscrub_regs { |
---|
64 | volatile uint32_t ahbstatus; /* 0x00 */ |
---|
65 | volatile uint32_t ahbfailing; /* 0x04 */ |
---|
66 | volatile uint32_t ahberc; /* 0x08 */ |
---|
67 | volatile uint32_t resv1; /* 0x0c */ |
---|
68 | volatile uint32_t status; /* 0x10 */ |
---|
69 | volatile uint32_t config; /* 0x14 */ |
---|
70 | volatile uint32_t rangel; /* 0x18 */ |
---|
71 | volatile uint32_t rangeh; /* 0x1c */ |
---|
72 | volatile uint32_t pos; /* 0x20 */ |
---|
73 | volatile uint32_t ethres; /* 0x24 */ |
---|
74 | volatile uint32_t init; /* 0x28 */ |
---|
75 | volatile uint32_t rangel2; /* 0x2c */ |
---|
76 | volatile uint32_t rangeh2; /* 0x30 */ |
---|
77 | }; |
---|
78 | |
---|
79 | #define DEVNAME_LEN 10 |
---|
80 | struct memscrub_priv { |
---|
81 | struct drvmgr_dev *dev; |
---|
82 | char devname[DEVNAME_LEN]; |
---|
83 | struct memscrub_regs *regs; |
---|
84 | int minor; |
---|
85 | int burstlen; |
---|
86 | int blockmask; |
---|
87 | /* Cached error */ |
---|
88 | uint32_t last_status; |
---|
89 | uint32_t last_address; |
---|
90 | /* User defined ISR */ |
---|
91 | memscrub_isr_t isr; |
---|
92 | void *isr_arg; |
---|
93 | }; |
---|
94 | static struct memscrub_priv * memscrubpriv = NULL; |
---|
95 | |
---|
96 | STATIC int memscrub_init2(struct drvmgr_dev *dev); |
---|
97 | STATIC int memscrub_init(struct memscrub_priv *priv); |
---|
98 | |
---|
99 | void memscrub_isr(void *arg); |
---|
100 | |
---|
101 | struct drvmgr_drv_ops memscrub_ops = |
---|
102 | { |
---|
103 | .init = {NULL, memscrub_init2, NULL, NULL}, |
---|
104 | .remove = NULL, |
---|
105 | .info = NULL |
---|
106 | }; |
---|
107 | |
---|
108 | struct amba_dev_id memscrub_ids[] = |
---|
109 | { |
---|
110 | {VENDOR_GAISLER, GAISLER_MEMSCRUB}, |
---|
111 | {0, 0} /* Mark end of table */ |
---|
112 | }; |
---|
113 | |
---|
114 | struct amba_drv_info memscrub_drv_info = |
---|
115 | { |
---|
116 | { |
---|
117 | DRVMGR_OBJ_DRV, /* Driver */ |
---|
118 | NULL, /* Next driver */ |
---|
119 | NULL, /* Device list */ |
---|
120 | DRIVER_AMBAPP_GAISLER_MEMSCRUB_ID,/* Driver ID */ |
---|
121 | "MEMSCRUB_DRV", /* Driver Name */ |
---|
122 | DRVMGR_BUS_TYPE_AMBAPP, /* Bus Type */ |
---|
123 | &memscrub_ops, |
---|
124 | NULL, /* Funcs */ |
---|
125 | 0, /* No devices yet */ |
---|
126 | sizeof(struct memscrub_priv), |
---|
127 | }, |
---|
128 | &memscrub_ids[0] |
---|
129 | }; |
---|
130 | |
---|
131 | void memscrub_register_drv (void) |
---|
132 | { |
---|
133 | drvmgr_drv_register(&memscrub_drv_info.general); |
---|
134 | } |
---|
135 | |
---|
136 | STATIC int memscrub_init(struct memscrub_priv *priv) |
---|
137 | { |
---|
138 | struct ambapp_ahb_info *ahb; |
---|
139 | struct amba_dev_info *ainfo = priv->dev->businfo; |
---|
140 | unsigned int tmp; |
---|
141 | int i,j; |
---|
142 | |
---|
143 | /* Get device information from AMBA PnP information */ |
---|
144 | if (ainfo == NULL){ |
---|
145 | return MEMSCRUB_ERR_ERROR; |
---|
146 | } |
---|
147 | |
---|
148 | /* Find MEMSCRUB core from Plug&Play information */ |
---|
149 | ahb = ainfo->info.ahb_slv; |
---|
150 | priv->regs = (struct memscrub_regs *)ahb->start[0]; |
---|
151 | |
---|
152 | DBG("MEMSCRUB regs 0x%08x\n", (unsigned int) priv->regs); |
---|
153 | |
---|
154 | /* Find MEMSCRUB capabilities */ |
---|
155 | tmp = REG_READ(&priv->regs->status); |
---|
156 | i = (tmp & STAT_BURSTLEN) >> STAT_BURSTLEN_BIT; |
---|
157 | for (j=1; i>0; i--) j <<= 1; |
---|
158 | priv->burstlen = j; |
---|
159 | |
---|
160 | |
---|
161 | /* If scrubber is active, we cannot stop it to read blockmask value */ |
---|
162 | if (tmp & STAT_ACTIVE){ |
---|
163 | priv->blockmask = 0; |
---|
164 | }else{ |
---|
165 | /* Detect block size in bytes and burst length */ |
---|
166 | tmp = REG_READ(&priv->regs->rangeh); |
---|
167 | REG_WRITE(&priv->regs->rangeh, 0); |
---|
168 | priv->blockmask = REG_READ(&priv->regs->rangeh); |
---|
169 | REG_WRITE(&priv->regs->rangeh, tmp); |
---|
170 | } |
---|
171 | |
---|
172 | /* DEBUG print */ |
---|
173 | DBG("MEMSCRUB with following capabilities:\n"); |
---|
174 | DBG(" -Burstlength: %d\n", priv->burstlen); |
---|
175 | |
---|
176 | return MEMSCRUB_ERR_OK; |
---|
177 | } |
---|
178 | |
---|
179 | STATIC int memscrub_init2(struct drvmgr_dev *dev) |
---|
180 | { |
---|
181 | struct memscrub_priv *priv = dev->priv; |
---|
182 | |
---|
183 | DBG("MEMSCRUB[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name); |
---|
184 | |
---|
185 | if (memscrubpriv) { |
---|
186 | DBG("Driver only supports one MEMSCRUB core\n"); |
---|
187 | return DRVMGR_FAIL; |
---|
188 | } |
---|
189 | |
---|
190 | if (priv==NULL){ |
---|
191 | return DRVMGR_NOMEM; |
---|
192 | } |
---|
193 | |
---|
194 | /* Assign priv */ |
---|
195 | priv->dev = dev; |
---|
196 | strncpy(&priv->devname[0], "memscrub0", DEVNAME_LEN); |
---|
197 | memscrubpriv=priv; |
---|
198 | |
---|
199 | /* Initilize driver struct */ |
---|
200 | if (memscrub_init(priv) != MEMSCRUB_ERR_OK){ |
---|
201 | return DRVMGR_FAIL; |
---|
202 | } |
---|
203 | |
---|
204 | /* Startup Action: |
---|
205 | * - Clear status |
---|
206 | * - Register ISR |
---|
207 | */ |
---|
208 | |
---|
209 | /* Initialize hardware by clearing its status */ |
---|
210 | REG_WRITE(&priv->regs->ahbstatus, 0); |
---|
211 | REG_WRITE(&priv->regs->status, 0); |
---|
212 | |
---|
213 | return DRVMGR_OK; |
---|
214 | } |
---|
215 | |
---|
216 | |
---|
217 | int memscrub_init_start(uint32_t value, uint8_t delay, int options) |
---|
218 | { |
---|
219 | struct memscrub_priv *priv = memscrubpriv; |
---|
220 | uint32_t sts, tmp; |
---|
221 | int i; |
---|
222 | |
---|
223 | if (priv==NULL){ |
---|
224 | DBG("MEMSCRUB not init.\n"); |
---|
225 | return MEMSCRUB_ERR_ERROR; |
---|
226 | } |
---|
227 | |
---|
228 | /* Check if scrubber is active */ |
---|
229 | sts = REG_READ(&priv->regs->status); |
---|
230 | if (sts & STAT_ACTIVE){ |
---|
231 | DBG("MEMSCRUB running.\n"); |
---|
232 | return MEMSCRUB_ERR_ERROR; |
---|
233 | } |
---|
234 | |
---|
235 | /* Check if we need to probe blockmask */ |
---|
236 | if (priv->blockmask == 0){ |
---|
237 | /* Detect block size in bytes and burst length */ |
---|
238 | tmp = REG_READ(&priv->regs->rangeh); |
---|
239 | REG_WRITE(&priv->regs->rangeh, 0); |
---|
240 | priv->blockmask = REG_READ(&priv->regs->rangeh); |
---|
241 | REG_WRITE(&priv->regs->rangeh, tmp); |
---|
242 | } |
---|
243 | |
---|
244 | /* Set data value */ |
---|
245 | for (i=0; i<priv->blockmask; i+=4){ |
---|
246 | REG_WRITE(&priv->regs->init,value); |
---|
247 | } |
---|
248 | |
---|
249 | /* Clear unused bits */ |
---|
250 | options = options & ~(CONFIG_MODE | CONFIG_DELAY); |
---|
251 | |
---|
252 | /* Enable scrubber */ |
---|
253 | REG_WRITE(&priv->regs->config, options | |
---|
254 | ((delay << CONFIG_DELAY_BIT) & CONFIG_DELAY) | |
---|
255 | CONFIG_MODE_INIT | CONFIG_SCEN); |
---|
256 | |
---|
257 | DBG("MEMSCRUB INIT STARTED\n"); |
---|
258 | |
---|
259 | return MEMSCRUB_ERR_OK; |
---|
260 | } |
---|
261 | |
---|
262 | int memscrub_scrub_start(uint8_t delay, int options) |
---|
263 | { |
---|
264 | struct memscrub_priv *priv = memscrubpriv; |
---|
265 | uint32_t ctrl,sts; |
---|
266 | |
---|
267 | if (priv==NULL){ |
---|
268 | DBG("MEMSCRUB not init.\n"); |
---|
269 | return MEMSCRUB_ERR_ERROR; |
---|
270 | } |
---|
271 | |
---|
272 | /* Check if scrubber is active */ |
---|
273 | sts = REG_READ(&priv->regs->status); |
---|
274 | if (sts & STAT_ACTIVE){ |
---|
275 | /* Check if mode is not init */ |
---|
276 | ctrl = REG_READ(&priv->regs->config); |
---|
277 | if ((ctrl & CONFIG_MODE)==CONFIG_MODE_INIT){ |
---|
278 | DBG("MEMSCRUB init running.\n"); |
---|
279 | return MEMSCRUB_ERR_ERROR; |
---|
280 | } |
---|
281 | } |
---|
282 | |
---|
283 | /* Clear unused bits */ |
---|
284 | options = options & ~(CONFIG_MODE | CONFIG_DELAY); |
---|
285 | |
---|
286 | /* Enable scrubber */ |
---|
287 | REG_WRITE(&priv->regs->config, options | |
---|
288 | ((delay << CONFIG_DELAY_BIT) & CONFIG_DELAY) | |
---|
289 | CONFIG_MODE_SCRUB | CONFIG_SCEN); |
---|
290 | |
---|
291 | DBG("MEMSCRUB SCRUB STARTED\n"); |
---|
292 | |
---|
293 | return MEMSCRUB_ERR_OK; |
---|
294 | } |
---|
295 | |
---|
296 | int memscrub_regen_start(uint8_t delay, int options) |
---|
297 | { |
---|
298 | struct memscrub_priv *priv = memscrubpriv; |
---|
299 | uint32_t ctrl,sts; |
---|
300 | |
---|
301 | if (priv==NULL){ |
---|
302 | DBG("MEMSCRUB not init.\n"); |
---|
303 | return MEMSCRUB_ERR_ERROR; |
---|
304 | } |
---|
305 | |
---|
306 | /* Check if scrubber is active */ |
---|
307 | sts = REG_READ(&priv->regs->status); |
---|
308 | if (sts & STAT_ACTIVE){ |
---|
309 | /* Check if mode is not init */ |
---|
310 | ctrl = REG_READ(&priv->regs->config); |
---|
311 | if ((ctrl & CONFIG_MODE)==CONFIG_MODE_INIT){ |
---|
312 | DBG("MEMSCRUB init running.\n"); |
---|
313 | return MEMSCRUB_ERR_ERROR; |
---|
314 | } |
---|
315 | } |
---|
316 | |
---|
317 | /* Clear unused bits */ |
---|
318 | options = options & ~(CONFIG_MODE | CONFIG_DELAY); |
---|
319 | |
---|
320 | /* Enable scrubber */ |
---|
321 | REG_WRITE(&priv->regs->config, options | |
---|
322 | ((delay << CONFIG_DELAY_BIT) & CONFIG_DELAY) | |
---|
323 | CONFIG_MODE_REGEN | CONFIG_SCEN); |
---|
324 | |
---|
325 | DBG("MEMSCRUB REGEN STARTED\n"); |
---|
326 | |
---|
327 | return MEMSCRUB_ERR_OK; |
---|
328 | } |
---|
329 | |
---|
330 | int memscrub_stop(void) |
---|
331 | { |
---|
332 | struct memscrub_priv *priv = memscrubpriv; |
---|
333 | |
---|
334 | if (priv==NULL){ |
---|
335 | DBG("MEMSCRUB not init.\n"); |
---|
336 | return MEMSCRUB_ERR_ERROR; |
---|
337 | } |
---|
338 | |
---|
339 | /* Disable scrubber */ |
---|
340 | REG_WRITE(&priv->regs->config, 0); |
---|
341 | |
---|
342 | /* Wait until finished */ |
---|
343 | while(REG_READ(&priv->regs->status) & STAT_ACTIVE){}; |
---|
344 | |
---|
345 | DBG("MEMSCRUB STOPPED\n"); |
---|
346 | |
---|
347 | return MEMSCRUB_ERR_OK; |
---|
348 | } |
---|
349 | |
---|
350 | int memscrub_range_set(uint32_t start, uint32_t end) |
---|
351 | { |
---|
352 | struct memscrub_priv *priv = memscrubpriv; |
---|
353 | |
---|
354 | if (priv==NULL){ |
---|
355 | DBG("MEMSCRUB not init.\n"); |
---|
356 | return MEMSCRUB_ERR_ERROR; |
---|
357 | } |
---|
358 | |
---|
359 | if (end <= start){ |
---|
360 | DBG("MEMSCRUB wrong address.\n"); |
---|
361 | return MEMSCRUB_ERR_EINVAL; |
---|
362 | } |
---|
363 | |
---|
364 | /* Check if scrubber is active */ |
---|
365 | if (REG_READ(&priv->regs->status) & STAT_ACTIVE){ |
---|
366 | DBG("MEMSCRUB running.\n"); |
---|
367 | return MEMSCRUB_ERR_ERROR; |
---|
368 | } |
---|
369 | |
---|
370 | /* Set range */ |
---|
371 | REG_WRITE(&priv->regs->rangel, start); |
---|
372 | REG_WRITE(&priv->regs->rangeh, end); |
---|
373 | |
---|
374 | DBG("MEMSCRUB range: 0x%08x-0x%08x\n", |
---|
375 | (unsigned int) start, |
---|
376 | (unsigned int) end); |
---|
377 | |
---|
378 | return MEMSCRUB_ERR_OK; |
---|
379 | } |
---|
380 | |
---|
381 | int memscrub_secondary_range_set(uint32_t start, uint32_t end) |
---|
382 | { |
---|
383 | struct memscrub_priv *priv = memscrubpriv; |
---|
384 | |
---|
385 | if (priv==NULL){ |
---|
386 | DBG("MEMSCRUB not init.\n"); |
---|
387 | return MEMSCRUB_ERR_ERROR; |
---|
388 | } |
---|
389 | |
---|
390 | if (end <= start){ |
---|
391 | DBG("MEMSCRUB wrong address.\n"); |
---|
392 | return MEMSCRUB_ERR_EINVAL; |
---|
393 | } |
---|
394 | |
---|
395 | /* Check if scrubber is active */ |
---|
396 | if (REG_READ(&priv->regs->status) & STAT_ACTIVE){ |
---|
397 | DBG("MEMSCRUB running.\n"); |
---|
398 | return MEMSCRUB_ERR_ERROR; |
---|
399 | } |
---|
400 | |
---|
401 | /* Set range */ |
---|
402 | REG_WRITE(&priv->regs->rangel2, start); |
---|
403 | REG_WRITE(&priv->regs->rangeh2, end); |
---|
404 | |
---|
405 | DBG("MEMSCRUB 2nd range: 0x%08x-0x%08x\n", |
---|
406 | (unsigned int) start, |
---|
407 | (unsigned int) end); |
---|
408 | |
---|
409 | return MEMSCRUB_ERR_OK; |
---|
410 | } |
---|
411 | |
---|
412 | int memscrub_range_get(uint32_t * start, uint32_t * end) |
---|
413 | { |
---|
414 | struct memscrub_priv *priv = memscrubpriv; |
---|
415 | |
---|
416 | if (priv==NULL){ |
---|
417 | DBG("MEMSCRUB not init.\n"); |
---|
418 | return MEMSCRUB_ERR_ERROR; |
---|
419 | } |
---|
420 | |
---|
421 | if ((start==NULL) || (end == NULL)){ |
---|
422 | DBG("MEMSCRUB wrong pointer.\n"); |
---|
423 | return MEMSCRUB_ERR_EINVAL; |
---|
424 | } |
---|
425 | |
---|
426 | /* Get range */ |
---|
427 | *start = REG_READ(&priv->regs->rangel); |
---|
428 | *end = REG_READ(&priv->regs->rangeh); |
---|
429 | |
---|
430 | return MEMSCRUB_ERR_OK; |
---|
431 | } |
---|
432 | |
---|
433 | int memscrub_secondary_range_get(uint32_t * start, uint32_t * end) |
---|
434 | { |
---|
435 | struct memscrub_priv *priv = memscrubpriv; |
---|
436 | |
---|
437 | if (priv==NULL){ |
---|
438 | DBG("MEMSCRUB not init.\n"); |
---|
439 | return MEMSCRUB_ERR_ERROR; |
---|
440 | } |
---|
441 | |
---|
442 | if ((start==NULL) || (end == NULL)){ |
---|
443 | DBG("MEMSCRUB wrong pointer.\n"); |
---|
444 | return MEMSCRUB_ERR_EINVAL; |
---|
445 | } |
---|
446 | |
---|
447 | /* Get range */ |
---|
448 | *start = REG_READ(&priv->regs->rangel2); |
---|
449 | *end = REG_READ(&priv->regs->rangeh2); |
---|
450 | |
---|
451 | return MEMSCRUB_ERR_OK; |
---|
452 | } |
---|
453 | |
---|
454 | int memscrub_ahberror_setup(int uethres, int cethres, int options) |
---|
455 | { |
---|
456 | struct memscrub_priv *priv = memscrubpriv; |
---|
457 | |
---|
458 | if (priv==NULL){ |
---|
459 | DBG("MEMSCRUB not init.\n"); |
---|
460 | return MEMSCRUB_ERR_ERROR; |
---|
461 | } |
---|
462 | |
---|
463 | /* Set AHBERR */ |
---|
464 | REG_WRITE(&priv->regs->ahberc, |
---|
465 | ((cethres << AHBERC_CECNTT_BIT) & AHBERC_CECNTT) | |
---|
466 | ((uethres << AHBERC_UECNTT_BIT) & AHBERC_UECNTT) | |
---|
467 | (options & (AHBERC_CECTE | AHBERC_UECTE))); |
---|
468 | |
---|
469 | DBG("MEMSCRUB ahb err: UE[%d]:%s, CE[%d]:%s\n", |
---|
470 | (unsigned int) uethres, |
---|
471 | (options & AHBERC_UECTE)? "enabled":"disabled", |
---|
472 | (unsigned int) cethres, |
---|
473 | (options & AHBERC_CECTE)? "enabled":"disabled" |
---|
474 | ); |
---|
475 | |
---|
476 | return MEMSCRUB_ERR_OK; |
---|
477 | } |
---|
478 | |
---|
479 | int memscrub_scruberror_setup(int blkthres, int runthres, int options) |
---|
480 | { |
---|
481 | struct memscrub_priv *priv = memscrubpriv; |
---|
482 | |
---|
483 | if (priv==NULL){ |
---|
484 | DBG("MEMSCRUB not init.\n"); |
---|
485 | return MEMSCRUB_ERR_ERROR; |
---|
486 | } |
---|
487 | |
---|
488 | /* Set ETHRES */ |
---|
489 | REG_WRITE(&priv->regs->ethres, |
---|
490 | ((blkthres << ETHRES_BECT_BIT) & ETHRES_BECT) | |
---|
491 | ((runthres << ETHRES_RECT_BIT) & ETHRES_RECT) | |
---|
492 | (options & (ETHRES_RECTE | ETHRES_BECTE))); |
---|
493 | |
---|
494 | DBG("MEMSCRUB scrub err: BLK[%d]:%s, RUN[%d]:%s\n", |
---|
495 | (unsigned int) blkthres, |
---|
496 | (options & ETHRES_BECTE)? "enabled":"disabled", |
---|
497 | (unsigned int) runthres, |
---|
498 | (options & ETHRES_RECTE)? "enabled":"disabled" |
---|
499 | ); |
---|
500 | |
---|
501 | return MEMSCRUB_ERR_OK; |
---|
502 | } |
---|
503 | |
---|
504 | int memscrub_scrub_position(uint32_t * position) |
---|
505 | { |
---|
506 | struct memscrub_priv *priv = memscrubpriv; |
---|
507 | |
---|
508 | if (priv==NULL){ |
---|
509 | DBG("MEMSCRUB not init.\n"); |
---|
510 | return MEMSCRUB_ERR_ERROR; |
---|
511 | } |
---|
512 | |
---|
513 | if (position==NULL){ |
---|
514 | DBG("MEMSCRUB wrong pointer.\n"); |
---|
515 | return MEMSCRUB_ERR_EINVAL; |
---|
516 | } |
---|
517 | |
---|
518 | *position = REG_READ(&priv->regs->pos); |
---|
519 | |
---|
520 | return MEMSCRUB_ERR_OK; |
---|
521 | } |
---|
522 | |
---|
523 | int memscrub_isr_register(memscrub_isr_t isr, void * data) |
---|
524 | { |
---|
525 | struct memscrub_priv *priv = memscrubpriv; |
---|
526 | unsigned int ethres, ahberc, config; |
---|
527 | |
---|
528 | if (priv==NULL){ |
---|
529 | DBG("MEMSCRUB not init.\n"); |
---|
530 | return MEMSCRUB_ERR_ERROR; |
---|
531 | } |
---|
532 | |
---|
533 | if (isr==NULL){ |
---|
534 | DBG("MEMSCRUB wrong pointer.\n"); |
---|
535 | return MEMSCRUB_ERR_EINVAL; |
---|
536 | } |
---|
537 | |
---|
538 | /* Mask interrupts */ |
---|
539 | ethres = REG_READ(&priv->regs->ethres); |
---|
540 | REG_WRITE(&priv->regs->ethres, ethres & ~(ETHRES_RECTE | ETHRES_BECTE)); |
---|
541 | |
---|
542 | ahberc = REG_READ(&priv->regs->ahberc); |
---|
543 | REG_WRITE(&priv->regs->ahberc, ahberc & ~(AHBERC_CECTE | AHBERC_UECTE)); |
---|
544 | |
---|
545 | config = REG_READ(&priv->regs->config); |
---|
546 | REG_WRITE(&priv->regs->config, config & ~(CONFIG_IRQD)); |
---|
547 | |
---|
548 | /* Install IRQ handler if needed */ |
---|
549 | if (priv->isr == NULL){ |
---|
550 | drvmgr_interrupt_register(priv->dev, 0, priv->devname, memscrub_isr, |
---|
551 | priv); |
---|
552 | } |
---|
553 | |
---|
554 | /* Install user ISR */ |
---|
555 | priv->isr=isr; |
---|
556 | priv->isr_arg=data; |
---|
557 | |
---|
558 | /* Unmask interrupts */ |
---|
559 | REG_WRITE(&priv->regs->ethres, ethres); |
---|
560 | |
---|
561 | REG_WRITE(&priv->regs->ahberc, ahberc); |
---|
562 | |
---|
563 | REG_WRITE(&priv->regs->config, config); |
---|
564 | |
---|
565 | return MEMSCRUB_ERR_OK; |
---|
566 | } |
---|
567 | |
---|
568 | int memscrub_isr_unregister(void) |
---|
569 | { |
---|
570 | struct memscrub_priv *priv = memscrubpriv; |
---|
571 | unsigned int ethres, ahberc, config; |
---|
572 | |
---|
573 | if (priv==NULL){ |
---|
574 | DBG("MEMSCRUB not init.\n"); |
---|
575 | return MEMSCRUB_ERR_ERROR; |
---|
576 | } |
---|
577 | |
---|
578 | if (priv->isr==NULL){ |
---|
579 | DBG("MEMSCRUB wrong pointer.\n"); |
---|
580 | return MEMSCRUB_ERR_EINVAL; |
---|
581 | } |
---|
582 | |
---|
583 | /* Mask interrupts */ |
---|
584 | ethres = REG_READ(&priv->regs->ethres); |
---|
585 | REG_WRITE(&priv->regs->ethres, ethres & ~(ETHRES_RECTE | ETHRES_BECTE)); |
---|
586 | |
---|
587 | ahberc = REG_READ(&priv->regs->ahberc); |
---|
588 | REG_WRITE(&priv->regs->ahberc, ahberc & ~(AHBERC_CECTE | AHBERC_UECTE)); |
---|
589 | |
---|
590 | config = REG_READ(&priv->regs->config); |
---|
591 | REG_WRITE(&priv->regs->config, config & ~(CONFIG_IRQD)); |
---|
592 | |
---|
593 | /* Uninstall IRQ handler if needed */ |
---|
594 | drvmgr_interrupt_unregister(priv->dev, 0, memscrub_isr, priv); |
---|
595 | |
---|
596 | /* Uninstall user ISR */ |
---|
597 | priv->isr=NULL; |
---|
598 | priv->isr_arg=NULL; |
---|
599 | |
---|
600 | return MEMSCRUB_ERR_OK; |
---|
601 | } |
---|
602 | |
---|
603 | int memscrub_error_status(uint32_t *ahbaccess, uint32_t *ahbstatus, |
---|
604 | uint32_t *scrubstatus) |
---|
605 | { |
---|
606 | struct memscrub_priv *priv = memscrubpriv; |
---|
607 | uint32_t mask, ahbstatus_val; |
---|
608 | |
---|
609 | if (priv==NULL){ |
---|
610 | DBG("MEMSCRUB not init.\n"); |
---|
611 | return MEMSCRUB_ERR_ERROR; |
---|
612 | } |
---|
613 | |
---|
614 | if ((ahbaccess==NULL) || (ahbstatus==NULL) || (scrubstatus == NULL)){ |
---|
615 | DBG("MEMSCRUB wrong pointer.\n"); |
---|
616 | return MEMSCRUB_ERR_EINVAL; |
---|
617 | } |
---|
618 | |
---|
619 | /* Get hardware status */ |
---|
620 | *ahbaccess = REG_READ(&priv->regs->ahbfailing); |
---|
621 | *ahbstatus = ahbstatus_val = REG_READ(&priv->regs->ahbstatus); |
---|
622 | *scrubstatus = REG_READ(&priv->regs->status); |
---|
623 | |
---|
624 | /* Clear error status */ |
---|
625 | mask = 0; |
---|
626 | /* Clear CECNT only if we crossed the CE threshold*/ |
---|
627 | if ((ahbstatus_val & AHBS_CE) == 0){ |
---|
628 | /* Don't clear the CECNT */ |
---|
629 | mask |= AHBS_CECNT; |
---|
630 | } |
---|
631 | /* Clear UECNT only if we crossed the UE threshold*/ |
---|
632 | if ((ahbstatus_val & (AHBS_NE|AHBS_CE|AHBS_SBC|AHBS_SEC)) != AHBS_NE){ |
---|
633 | /* Don't clear the UECNT */ |
---|
634 | mask |= AHBS_UECNT; |
---|
635 | } |
---|
636 | REG_WRITE(&priv->regs->ahbstatus, ahbstatus_val & mask); |
---|
637 | REG_WRITE(&priv->regs->status,0); |
---|
638 | |
---|
639 | return MEMSCRUB_ERR_OK; |
---|
640 | } |
---|
641 | |
---|
642 | int memscrub_active(void) |
---|
643 | { |
---|
644 | struct memscrub_priv *priv = memscrubpriv; |
---|
645 | |
---|
646 | if (priv==NULL){ |
---|
647 | DBG("MEMSCRUB not init.\n"); |
---|
648 | return MEMSCRUB_ERR_ERROR; |
---|
649 | } |
---|
650 | |
---|
651 | return REG_READ(&priv->regs->status) & STAT_ACTIVE; |
---|
652 | } |
---|
653 | |
---|
654 | void memscrub_isr(void *arg) |
---|
655 | { |
---|
656 | struct memscrub_priv *priv = arg; |
---|
657 | uint32_t fadr, ahbstatus, status, mask; |
---|
658 | |
---|
659 | /* Get hardware status */ |
---|
660 | ahbstatus = REG_READ(&priv->regs->ahbstatus); |
---|
661 | if ((ahbstatus & (AHBS_NE|AHBS_DONE)) == 0){ |
---|
662 | return; |
---|
663 | } |
---|
664 | |
---|
665 | /* IRQ generated by MEMSCRUB core... handle it here */ |
---|
666 | |
---|
667 | /* Get Failing address */ |
---|
668 | fadr = REG_READ(&priv->regs->ahbfailing); |
---|
669 | |
---|
670 | /* Get Status */ |
---|
671 | status = REG_READ(&priv->regs->status); |
---|
672 | |
---|
673 | /* Clear error status */ |
---|
674 | mask = 0; |
---|
675 | /* Clear CECNT only if we crossed the CE threshold*/ |
---|
676 | if ((ahbstatus & AHBS_CE) == 0){ |
---|
677 | /* Don't clear the CECNT */ |
---|
678 | mask |= AHBS_CECNT; |
---|
679 | } |
---|
680 | /* Clear UECNT only if we crossed the UE threshold*/ |
---|
681 | if ((ahbstatus & (AHBS_NE|AHBS_CE|AHBS_SBC|AHBS_SEC)) != AHBS_NE){ |
---|
682 | /* Don't clear the UECNT */ |
---|
683 | mask |= AHBS_UECNT; |
---|
684 | } |
---|
685 | REG_WRITE(&priv->regs->ahbstatus, ahbstatus & mask); |
---|
686 | REG_WRITE(&priv->regs->status,0); |
---|
687 | |
---|
688 | /* Let user handle error */ |
---|
689 | (priv->isr)(priv->isr_arg, fadr, ahbstatus, status); |
---|
690 | |
---|
691 | return; |
---|
692 | } |
---|