/* 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 <string.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;
}