diff options
Diffstat (limited to 'c/src/lib/libbsp/sparc/shared/scrub/memscrub.c')
-rw-r--r-- | c/src/lib/libbsp/sparc/shared/scrub/memscrub.c | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/c/src/lib/libbsp/sparc/shared/scrub/memscrub.c b/c/src/lib/libbsp/sparc/shared/scrub/memscrub.c new file mode 100644 index 0000000000..cf026898c0 --- /dev/null +++ b/c/src/lib/libbsp/sparc/shared/scrub/memscrub.c @@ -0,0 +1,691 @@ +/* Memory Scrubber register driver + * + * COPYRIGHT (c) 2017. + * Cobham Gaisler AB. + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <rtems/bspIo.h> +#include <drvmgr/drvmgr.h> +#include <drvmgr/ambapp_bus.h> + +#include <bsp/memscrub.h> + +/*#define STATIC*/ +#define STATIC static + +#define UNUSED __attribute__((unused)) + +/*#define DEBUG 1*/ + +#ifdef DEBUG +#define DBG(x...) printf(x) +#else +#define DBG(x...) +#endif + +#define REG_WRITE(addr, val) (*(volatile uint32_t *)(addr) = (uint32_t)(val)) +#define REG_READ(addr) (*(volatile uint32_t *)(addr)) + +/* + * MEMORYSCRUBBER AHBS register fields + * DEFINED IN HEADER FILE + */ + +/* + * MEMORYSCRUBBER AHBERC register fields + * DEFINED IN HEADER FILE + */ + +/* + * MEMORYSCRUBBER STAT register fields + * DEFINED IN HEADER FILE + */ + +/* + * MEMORYSCRUBBER CONFIG register fields + * DEFINED IN HEADER FILE + */ + +/* + * MEMORYSCRUBBER ETHRES register fields + * DEFINED IN HEADER FILE + */ + +/* MEMORYSCRUBBER Registers layout */ +struct memscrub_regs { + volatile uint32_t ahbstatus; /* 0x00 */ + volatile uint32_t ahbfailing; /* 0x04 */ + volatile uint32_t ahberc; /* 0x08 */ + volatile uint32_t resv1; /* 0x0c */ + volatile uint32_t status; /* 0x10 */ + volatile uint32_t config; /* 0x14 */ + volatile uint32_t rangel; /* 0x18 */ + volatile uint32_t rangeh; /* 0x1c */ + volatile uint32_t pos; /* 0x20 */ + volatile uint32_t ethres; /* 0x24 */ + volatile uint32_t init; /* 0x28 */ + volatile uint32_t rangel2; /* 0x2c */ + volatile uint32_t rangeh2; /* 0x30 */ +}; + +#define DEVNAME_LEN 10 +struct memscrub_priv { + struct drvmgr_dev *dev; + char devname[DEVNAME_LEN]; + struct memscrub_regs *regs; + int minor; + int burstlen; + int blockmask; + /* Cached error */ + uint32_t last_status; + uint32_t last_address; + /* User defined ISR */ + memscrub_isr_t isr; + void *isr_arg; +}; +static struct memscrub_priv * memscrubpriv = NULL; + +STATIC int memscrub_init2(struct drvmgr_dev *dev); +STATIC int memscrub_init(struct memscrub_priv *priv); + +void memscrub_isr(void *arg); + +struct drvmgr_drv_ops memscrub_ops = +{ + .init = {NULL, memscrub_init2, NULL, NULL}, + .remove = NULL, + .info = NULL +}; + +struct amba_dev_id memscrub_ids[] = +{ + {VENDOR_GAISLER, GAISLER_MEMSCRUB}, + {0, 0} /* Mark end of table */ +}; + +struct amba_drv_info memscrub_drv_info = +{ + { + DRVMGR_OBJ_DRV, /* Driver */ + NULL, /* Next driver */ + NULL, /* Device list */ + DRIVER_AMBAPP_GAISLER_MEMSCRUB_ID,/* Driver ID */ + "MEMSCRUB_DRV", /* Driver Name */ + DRVMGR_BUS_TYPE_AMBAPP, /* Bus Type */ + &memscrub_ops, + NULL, /* Funcs */ + 0, /* No devices yet */ + sizeof(struct memscrub_priv), + }, + &memscrub_ids[0] +}; + +void memscrub_register_drv (void) +{ + drvmgr_drv_register(&memscrub_drv_info.general); +} + +STATIC int memscrub_init(struct memscrub_priv *priv) +{ + struct ambapp_ahb_info *ahb; + struct amba_dev_info *ainfo = priv->dev->businfo; + unsigned int tmp; + int i,j; + + /* Get device information from AMBA PnP information */ + if (ainfo == NULL){ + return MEMSCRUB_ERR_ERROR; + } + + /* Find MEMSCRUB core from Plug&Play information */ + ahb = ainfo->info.ahb_slv; + priv->regs = (struct memscrub_regs *)ahb->start[0]; + + DBG("MEMSCRUB regs 0x%08x\n", (unsigned int) priv->regs); + + /* Find MEMSCRUB capabilities */ + tmp = REG_READ(&priv->regs->status); + i = (tmp & STAT_BURSTLEN) >> STAT_BURSTLEN_BIT; + for (j=1; i>0; i--) j <<= 1; + priv->burstlen = j; + + + /* If scrubber is active, we cannot stop it to read blockmask value */ + if (tmp & STAT_ACTIVE){ + priv->blockmask = 0; + }else{ + /* Detect block size in bytes and burst length */ + tmp = REG_READ(&priv->regs->rangeh); + REG_WRITE(&priv->regs->rangeh, 0); + priv->blockmask = REG_READ(&priv->regs->rangeh); + REG_WRITE(&priv->regs->rangeh, tmp); + } + + /* DEBUG print */ + DBG("MEMSCRUB with following capabilities:\n"); + DBG(" -Burstlength: %d\n", priv->burstlen); + + return MEMSCRUB_ERR_OK; +} + +STATIC int memscrub_init2(struct drvmgr_dev *dev) +{ + struct memscrub_priv *priv = dev->priv; + + DBG("MEMSCRUB[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name); + + if (memscrubpriv) { + DBG("Driver only supports one MEMSCRUB core\n"); + return DRVMGR_FAIL; + } + + if (priv==NULL){ + return DRVMGR_NOMEM; + } + + /* Assign priv */ + priv->dev = dev; + strncpy(&priv->devname[0], "memscrub0", DEVNAME_LEN); + memscrubpriv=priv; + + /* Initilize driver struct */ + if (memscrub_init(priv) != MEMSCRUB_ERR_OK){ + return DRVMGR_FAIL; + } + + /* Startup Action: + * - Clear status + * - Register ISR + */ + + /* Initialize hardware by clearing its status */ + REG_WRITE(&priv->regs->ahbstatus, 0); + REG_WRITE(&priv->regs->status, 0); + + return DRVMGR_OK; +} + + +int memscrub_init_start(uint32_t value, uint8_t delay, int options) +{ + struct memscrub_priv *priv = memscrubpriv; + uint32_t sts, tmp; + int i; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Check if scrubber is active */ + sts = REG_READ(&priv->regs->status); + if (sts & STAT_ACTIVE){ + DBG("MEMSCRUB running.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Check if we need to probe blockmask */ + if (priv->blockmask == 0){ + /* Detect block size in bytes and burst length */ + tmp = REG_READ(&priv->regs->rangeh); + REG_WRITE(&priv->regs->rangeh, 0); + priv->blockmask = REG_READ(&priv->regs->rangeh); + REG_WRITE(&priv->regs->rangeh, tmp); + } + + /* Set data value */ + for (i=0; i<priv->blockmask; i+=4){ + REG_WRITE(&priv->regs->init,value); + } + + /* Clear unused bits */ + options = options & ~(CONFIG_MODE | CONFIG_DELAY); + + /* Enable scrubber */ + REG_WRITE(&priv->regs->config, options | + ((delay << CONFIG_DELAY_BIT) & CONFIG_DELAY) | + CONFIG_MODE_INIT | CONFIG_SCEN); + + DBG("MEMSCRUB INIT STARTED\n"); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_scrub_start(uint8_t delay, int options) +{ + struct memscrub_priv *priv = memscrubpriv; + uint32_t ctrl,sts; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Check if scrubber is active */ + sts = REG_READ(&priv->regs->status); + if (sts & STAT_ACTIVE){ + /* Check if mode is not init */ + ctrl = REG_READ(&priv->regs->config); + if ((ctrl & CONFIG_MODE)==CONFIG_MODE_INIT){ + DBG("MEMSCRUB init running.\n"); + return MEMSCRUB_ERR_ERROR; + } + } + + /* Clear unused bits */ + options = options & ~(CONFIG_MODE | CONFIG_DELAY); + + /* Enable scrubber */ + REG_WRITE(&priv->regs->config, options | + ((delay << CONFIG_DELAY_BIT) & CONFIG_DELAY) | + CONFIG_MODE_SCRUB | CONFIG_SCEN); + + DBG("MEMSCRUB SCRUB STARTED\n"); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_regen_start(uint8_t delay, int options) +{ + struct memscrub_priv *priv = memscrubpriv; + uint32_t ctrl,sts; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Check if scrubber is active */ + sts = REG_READ(&priv->regs->status); + if (sts & STAT_ACTIVE){ + /* Check if mode is not init */ + ctrl = REG_READ(&priv->regs->config); + if ((ctrl & CONFIG_MODE)==CONFIG_MODE_INIT){ + DBG("MEMSCRUB init running.\n"); + return MEMSCRUB_ERR_ERROR; + } + } + + /* Clear unused bits */ + options = options & ~(CONFIG_MODE | CONFIG_DELAY); + + /* Enable scrubber */ + REG_WRITE(&priv->regs->config, options | + ((delay << CONFIG_DELAY_BIT) & CONFIG_DELAY) | + CONFIG_MODE_REGEN | CONFIG_SCEN); + + DBG("MEMSCRUB REGEN STARTED\n"); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_stop(void) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Disable scrubber */ + REG_WRITE(&priv->regs->config, 0); + + /* Wait until finished */ + while(REG_READ(&priv->regs->status) & STAT_ACTIVE){}; + + DBG("MEMSCRUB STOPPED\n"); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_range_set(uint32_t start, uint32_t end) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if (end <= start){ + DBG("MEMSCRUB wrong address.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + /* Check if scrubber is active */ + if (REG_READ(&priv->regs->status) & STAT_ACTIVE){ + DBG("MEMSCRUB running.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Set range */ + REG_WRITE(&priv->regs->rangel, start); + REG_WRITE(&priv->regs->rangeh, end); + + DBG("MEMSCRUB range: 0x%08x-0x%08x\n", + (unsigned int) start, + (unsigned int) end); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_secondary_range_set(uint32_t start, uint32_t end) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if (end <= start){ + DBG("MEMSCRUB wrong address.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + /* Check if scrubber is active */ + if (REG_READ(&priv->regs->status) & STAT_ACTIVE){ + DBG("MEMSCRUB running.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Set range */ + REG_WRITE(&priv->regs->rangel2, start); + REG_WRITE(&priv->regs->rangeh2, end); + + DBG("MEMSCRUB 2nd range: 0x%08x-0x%08x\n", + (unsigned int) start, + (unsigned int) end); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_range_get(uint32_t * start, uint32_t * end) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if ((start==NULL) || (end == NULL)){ + DBG("MEMSCRUB wrong pointer.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + /* Get range */ + *start = REG_READ(&priv->regs->rangel); + *end = REG_READ(&priv->regs->rangeh); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_secondary_range_get(uint32_t * start, uint32_t * end) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if ((start==NULL) || (end == NULL)){ + DBG("MEMSCRUB wrong pointer.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + /* Get range */ + *start = REG_READ(&priv->regs->rangel2); + *end = REG_READ(&priv->regs->rangeh2); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_ahberror_setup(int uethres, int cethres, int options) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Set AHBERR */ + REG_WRITE(&priv->regs->ahberc, + ((cethres << AHBERC_CECNTT_BIT) & AHBERC_CECNTT) | + ((uethres << AHBERC_UECNTT_BIT) & AHBERC_UECNTT) | + (options & (AHBERC_CECTE | AHBERC_UECTE))); + + DBG("MEMSCRUB ahb err: UE[%d]:%s, CE[%d]:%s\n", + (unsigned int) uethres, + (options & AHBERC_UECTE)? "enabled":"disabled", + (unsigned int) cethres, + (options & AHBERC_CECTE)? "enabled":"disabled" + ); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_scruberror_setup(int blkthres, int runthres, int options) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + /* Set ETHRES */ + REG_WRITE(&priv->regs->ethres, + ((blkthres << ETHRES_BECT_BIT) & ETHRES_BECT) | + ((runthres << ETHRES_RECT_BIT) & ETHRES_RECT) | + (options & (ETHRES_RECTE | ETHRES_BECTE))); + + DBG("MEMSCRUB scrub err: BLK[%d]:%s, RUN[%d]:%s\n", + (unsigned int) blkthres, + (options & ETHRES_BECTE)? "enabled":"disabled", + (unsigned int) runthres, + (options & ETHRES_RECTE)? "enabled":"disabled" + ); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_scrub_position(uint32_t * position) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if (position==NULL){ + DBG("MEMSCRUB wrong pointer.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + *position = REG_READ(&priv->regs->pos); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_isr_register(memscrub_isr_t isr, void * data) +{ + struct memscrub_priv *priv = memscrubpriv; + unsigned int ethres, ahberc, config; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if (isr==NULL){ + DBG("MEMSCRUB wrong pointer.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + /* Mask interrupts */ + ethres = REG_READ(&priv->regs->ethres); + REG_WRITE(&priv->regs->ethres, ethres & ~(ETHRES_RECTE | ETHRES_BECTE)); + + ahberc = REG_READ(&priv->regs->ahberc); + REG_WRITE(&priv->regs->ahberc, ahberc & ~(AHBERC_CECTE | AHBERC_UECTE)); + + config = REG_READ(&priv->regs->config); + REG_WRITE(&priv->regs->config, config & ~(CONFIG_IRQD)); + + /* Install IRQ handler if needed */ + if (priv->isr == NULL){ + drvmgr_interrupt_register(priv->dev, 0, priv->devname, memscrub_isr, + priv); + } + + /* Install user ISR */ + priv->isr=isr; + priv->isr_arg=data; + + /* Unmask interrupts */ + REG_WRITE(&priv->regs->ethres, ethres); + + REG_WRITE(&priv->regs->ahberc, ahberc); + + REG_WRITE(&priv->regs->config, config); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_isr_unregister(void) +{ + struct memscrub_priv *priv = memscrubpriv; + unsigned int ethres, ahberc, config; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if (priv->isr==NULL){ + DBG("MEMSCRUB wrong pointer.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + /* Mask interrupts */ + ethres = REG_READ(&priv->regs->ethres); + REG_WRITE(&priv->regs->ethres, ethres & ~(ETHRES_RECTE | ETHRES_BECTE)); + + ahberc = REG_READ(&priv->regs->ahberc); + REG_WRITE(&priv->regs->ahberc, ahberc & ~(AHBERC_CECTE | AHBERC_UECTE)); + + config = REG_READ(&priv->regs->config); + REG_WRITE(&priv->regs->config, config & ~(CONFIG_IRQD)); + + /* Uninstall IRQ handler if needed */ + drvmgr_interrupt_unregister(priv->dev, 0, memscrub_isr, priv); + + /* Uninstall user ISR */ + priv->isr=NULL; + priv->isr_arg=NULL; + + return MEMSCRUB_ERR_OK; +} + +int memscrub_error_status(uint32_t *ahbaccess, uint32_t *ahbstatus, + uint32_t *scrubstatus) +{ + struct memscrub_priv *priv = memscrubpriv; + uint32_t mask, ahbstatus_val; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + if ((ahbaccess==NULL) || (ahbstatus==NULL) || (scrubstatus == NULL)){ + DBG("MEMSCRUB wrong pointer.\n"); + return MEMSCRUB_ERR_EINVAL; + } + + /* Get hardware status */ + *ahbaccess = REG_READ(&priv->regs->ahbfailing); + *ahbstatus = ahbstatus_val = REG_READ(&priv->regs->ahbstatus); + *scrubstatus = REG_READ(&priv->regs->status); + + /* Clear error status */ + mask = 0; + /* Clear CECNT only if we crossed the CE threshold*/ + if ((ahbstatus_val & AHBS_CE) == 0){ + /* Don't clear the CECNT */ + mask |= AHBS_CECNT; + } + /* Clear UECNT only if we crossed the UE threshold*/ + if ((ahbstatus_val & (AHBS_NE|AHBS_CE|AHBS_SBC|AHBS_SEC)) != AHBS_NE){ + /* Don't clear the UECNT */ + mask |= AHBS_UECNT; + } + REG_WRITE(&priv->regs->ahbstatus, ahbstatus_val & mask); + REG_WRITE(&priv->regs->status,0); + + return MEMSCRUB_ERR_OK; +} + +int memscrub_active(void) +{ + struct memscrub_priv *priv = memscrubpriv; + + if (priv==NULL){ + DBG("MEMSCRUB not init.\n"); + return MEMSCRUB_ERR_ERROR; + } + + return REG_READ(&priv->regs->status) & STAT_ACTIVE; +} + +void memscrub_isr(void *arg) +{ + struct memscrub_priv *priv = arg; + uint32_t fadr, ahbstatus, status, mask; + + /* Get hardware status */ + ahbstatus = REG_READ(&priv->regs->ahbstatus); + if ((ahbstatus & (AHBS_NE|AHBS_DONE)) == 0){ + return; + } + + /* IRQ generated by MEMSCRUB core... handle it here */ + + /* Get Failing address */ + fadr = REG_READ(&priv->regs->ahbfailing); + + /* Get Status */ + status = REG_READ(&priv->regs->status); + + /* Clear error status */ + mask = 0; + /* Clear CECNT only if we crossed the CE threshold*/ + if ((ahbstatus & AHBS_CE) == 0){ + /* Don't clear the CECNT */ + mask |= AHBS_CECNT; + } + /* Clear UECNT only if we crossed the UE threshold*/ + if ((ahbstatus & (AHBS_NE|AHBS_CE|AHBS_SBC|AHBS_SEC)) != AHBS_NE){ + /* Don't clear the UECNT */ + mask |= AHBS_UECNT; + } + REG_WRITE(&priv->regs->ahbstatus, ahbstatus & mask); + REG_WRITE(&priv->regs->status,0); + + /* Let user handle error */ + (priv->isr)(priv->isr_arg, fadr, ahbstatus, status); + + return; +} |