[PATCH 1/2] fpga: altera-hps2fpga: fix HPS2FPGA bridge visibility to L3 masters

From: Anatolij Gustschin
Date: Fri Jul 22 2016 - 11:59:58 EST


While FPGA reconfiguration over device tree overlays the HPS2FPGA
bridge visibility to L3 masters is disabled. This results in abort
errors when accessing the address range of the FPGA devices behind
the HPS2FPGA bridge, i.e.:
...
Unhandled fault: imprecise external abort (0x406) at 0xf0400000
pgd = eef48000
[f0400000] *pgd=2e362811, *pte=00000000, *ppte=00000000
Internal error: : 406 [#1] SMP ARM
...

This visibility configuration error happens in bridge enable
function because the per-bridge 'priv->l3_remap_value' variable
doesn't cache all already written bits to write-only 'remap'
register. After FPGA reconfiguration the HPS2FPGA and LWHPS2FPGA
bridges must be enabled (bits 3 and 4 of the 'remap' register set).
So enable_set function is called for HPS2FPGA and then for LWHPS2FPGA
bridge. In the first call the value 0x9 is written to the 'remap'
register, in the second call the value 0x11 is written, resulting
in disabled HPS2FPGA visibility.

Use remap shadow register common for all bridges to fix the
external abort issue.

Signed-off-by: Anatolij Gustschin <agust@xxxxxxx>
---
drivers/fpga/altera-hps2fpga.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c
index a2f0bd6..476e548 100644
--- a/drivers/fpga/altera-hps2fpga.c
+++ b/drivers/fpga/altera-hps2fpga.c
@@ -34,6 +34,7 @@
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/reset.h>
+#include <linux/spinlock.h>

#define ALT_L3_REMAP_OFST 0x0
#define ALT_L3_REMAP_MPUZERO_MSK 0x00000001
@@ -48,8 +49,6 @@ struct altera_hps2fpga_data {
const char *name;
struct reset_control *bridge_reset;
struct regmap *l3reg;
- /* The L3 REMAP register is write only, so keep a cached value. */
- unsigned int l3_remap_value;
unsigned int remap_mask;
struct clk *clk;
};
@@ -61,9 +60,14 @@ static int alt_hps2fpga_enable_show(struct fpga_bridge *bridge)
return reset_control_status(priv->bridge_reset);
}

+/* The L3 REMAP register is write only, so keep a cached value. */
+static unsigned int l3_remap_shadow;
+static spinlock_t l3_remap_lock;
+
static int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv,
bool enable)
{
+ unsigned long flags;
int ret;

/* bring bridge out of reset */
@@ -76,15 +80,17 @@ static int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv,

/* Allow bridge to be visible to L3 masters or not */
if (priv->remap_mask) {
- priv->l3_remap_value |= ALT_L3_REMAP_MPUZERO_MSK;
+ spin_lock_irqsave(&l3_remap_lock, flags);
+ l3_remap_shadow |= ALT_L3_REMAP_MPUZERO_MSK;

if (enable)
- priv->l3_remap_value |= priv->remap_mask;
+ l3_remap_shadow |= priv->remap_mask;
else
- priv->l3_remap_value &= ~priv->remap_mask;
+ l3_remap_shadow &= ~priv->remap_mask;

ret = regmap_write(priv->l3reg, ALT_L3_REMAP_OFST,
- priv->l3_remap_value);
+ l3_remap_shadow);
+ spin_unlock_irqrestore(&l3_remap_lock, flags);
}

return ret;
@@ -159,6 +165,8 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev)
return -EBUSY;
}

+ spin_lock_init(&l3_remap_lock);
+
ret = fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops,
priv);
if (ret)
--
1.7.9.5