sh: Abstracted SH-4A UBC support on hw-breakpoint core.
[pandora-kernel.git] / arch / sh / kernel / cpu / sh4a / ubc.c
diff --git a/arch/sh/kernel/cpu/sh4a/ubc.c b/arch/sh/kernel/cpu/sh4a/ubc.c
new file mode 100644 (file)
index 0000000..efb2745
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * arch/sh/kernel/cpu/sh4a/ubc.c
+ *
+ * On-chip UBC support for SH-4A CPUs.
+ *
+ * Copyright (C) 2009 - 2010  Paul Mundt
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <asm/hw_breakpoint.h>
+
+#define UBC_CBR(idx)   (0xff200000 + (0x20 * idx))
+#define UBC_CRR(idx)   (0xff200004 + (0x20 * idx))
+#define UBC_CAR(idx)   (0xff200008 + (0x20 * idx))
+#define UBC_CAMR(idx)  (0xff20000c + (0x20 * idx))
+
+#define UBC_CCMFR      0xff200600
+#define UBC_CBCR       0xff200620
+
+/* CRR */
+#define UBC_CRR_PCB    (1 << 1)
+#define UBC_CRR_BIE    (1 << 0)
+
+/* CBR */
+#define UBC_CBR_CE     (1 << 0)
+
+static struct sh_ubc sh4a_ubc;
+
+static void sh4a_ubc_enable(struct arch_hw_breakpoint *info, int idx)
+{
+       __raw_writel(UBC_CBR_CE | info->len | info->type, UBC_CBR(idx));
+       __raw_writel(info->address, UBC_CAR(idx));
+}
+
+static void sh4a_ubc_disable(struct arch_hw_breakpoint *info, int idx)
+{
+       __raw_writel(0, UBC_CBR(idx));
+       __raw_writel(0, UBC_CAR(idx));
+}
+
+static void sh4a_ubc_enable_all(unsigned long mask)
+{
+       int i;
+
+       for (i = 0; i < sh4a_ubc.num_events; i++)
+               if (mask & (1 << i))
+                       __raw_writel(__raw_readl(UBC_CBR(i)) | UBC_CBR_CE,
+                                    UBC_CBR(i));
+}
+
+static void sh4a_ubc_disable_all(void)
+{
+       int i;
+
+       for (i = 0; i < sh4a_ubc.num_events; i++)
+               __raw_writel(__raw_readl(UBC_CBR(i)) & ~UBC_CBR_CE,
+                            UBC_CBR(i));
+}
+
+static unsigned long sh4a_ubc_active_mask(void)
+{
+       unsigned long active = 0;
+       int i;
+
+       for (i = 0; i < sh4a_ubc.num_events; i++)
+               if (__raw_readl(UBC_CBR(i)) & UBC_CBR_CE)
+                       active |= (1 << i);
+
+       return active;
+}
+
+static unsigned long sh4a_ubc_triggered_mask(void)
+{
+       return __raw_readl(UBC_CCMFR);
+}
+
+static void sh4a_ubc_clear_triggered_mask(unsigned long mask)
+{
+       __raw_writel(__raw_readl(UBC_CCMFR) & ~mask, UBC_CCMFR);
+}
+
+static struct sh_ubc sh4a_ubc = {
+       .name                   = "SH-4A",
+       .num_events             = 2,
+       .trap_nr                = 0x1e0,
+       .enable                 = sh4a_ubc_enable,
+       .disable                = sh4a_ubc_disable,
+       .enable_all             = sh4a_ubc_enable_all,
+       .disable_all            = sh4a_ubc_disable_all,
+       .active_mask            = sh4a_ubc_active_mask,
+       .triggered_mask         = sh4a_ubc_triggered_mask,
+       .clear_triggered_mask   = sh4a_ubc_clear_triggered_mask,
+};
+
+static int __init sh4a_ubc_init(void)
+{
+       struct clk *ubc_iclk = clk_get(NULL, "ubc0");
+       int i;
+
+       /*
+        * The UBC MSTP bit is optional, as not all platforms will have
+        * it. Just ignore it if we can't find it.
+        */
+       if (IS_ERR(ubc_iclk))
+               ubc_iclk = NULL;
+
+       clk_enable(ubc_iclk);
+
+       __raw_writel(0, UBC_CBCR);
+
+       for (i = 0; i < sh4a_ubc.num_events; i++) {
+               __raw_writel(0, UBC_CAMR(i));
+               __raw_writel(0, UBC_CBR(i));
+
+               __raw_writel(UBC_CRR_BIE | UBC_CRR_PCB, UBC_CRR(i));
+
+               /* dummy read for write posting */
+               (void)__raw_readl(UBC_CRR(i));
+       }
+
+       clk_disable(ubc_iclk);
+
+       sh4a_ubc.clk = ubc_iclk;
+
+       return register_sh_ubc(&sh4a_ubc);
+}
+arch_initcall(sh4a_ubc_init);