| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * arch/sh/kernel/cpu/sh4a/ubc.c |
| * |
| * On-chip UBC support for SH-4A CPUs. |
| * |
| * Copyright (C) 2009 - 2010 Paul Mundt |
| */ |
| #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); |