162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2002,2003 Broadcom Corporation
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci/*
762306a36Sopenharmony_ci * The Bus Watcher monitors internal bus transactions and maintains
862306a36Sopenharmony_ci * counts of transactions with error status, logging details and
962306a36Sopenharmony_ci * causing one of several interrupts.  This driver provides a handler
1062306a36Sopenharmony_ci * for those interrupts which aggregates the counts (to avoid
1162306a36Sopenharmony_ci * saturating the 8-bit counters) and provides a presence in
1262306a36Sopenharmony_ci * /proc/bus_watcher if PROC_FS is on.
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/kernel.h>
1762306a36Sopenharmony_ci#include <linux/interrupt.h>
1862306a36Sopenharmony_ci#include <linux/sched.h>
1962306a36Sopenharmony_ci#include <linux/proc_fs.h>
2062306a36Sopenharmony_ci#include <linux/seq_file.h>
2162306a36Sopenharmony_ci#include <asm/io.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <asm/sibyte/sb1250.h>
2462306a36Sopenharmony_ci#include <asm/sibyte/sb1250_regs.h>
2562306a36Sopenharmony_ci#include <asm/sibyte/sb1250_int.h>
2662306a36Sopenharmony_ci#include <asm/sibyte/sb1250_scd.h>
2762306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BCM1x80
2862306a36Sopenharmony_ci#include <asm/sibyte/bcm1480_regs.h>
2962306a36Sopenharmony_ci#endif
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistruct bw_stats_struct {
3362306a36Sopenharmony_ci	uint64_t status;
3462306a36Sopenharmony_ci	uint32_t l2_err;
3562306a36Sopenharmony_ci	uint32_t memio_err;
3662306a36Sopenharmony_ci	int status_printed;
3762306a36Sopenharmony_ci	unsigned long l2_cor_d;
3862306a36Sopenharmony_ci	unsigned long l2_bad_d;
3962306a36Sopenharmony_ci	unsigned long l2_cor_t;
4062306a36Sopenharmony_ci	unsigned long l2_bad_t;
4162306a36Sopenharmony_ci	unsigned long mem_cor_d;
4262306a36Sopenharmony_ci	unsigned long mem_bad_d;
4362306a36Sopenharmony_ci	unsigned long bus_error;
4462306a36Sopenharmony_ci} bw_stats;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic void print_summary(uint32_t status, uint32_t l2_err,
4862306a36Sopenharmony_ci			  uint32_t memio_err)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	printk("Bus watcher error counters: %08x %08x\n", l2_err, memio_err);
5162306a36Sopenharmony_ci	printk("\nLast recorded signature:\n");
5262306a36Sopenharmony_ci	printk("Request %02x from %d, answered by %d with Dcode %d\n",
5362306a36Sopenharmony_ci	       (unsigned int)(G_SCD_BERR_TID(status) & 0x3f),
5462306a36Sopenharmony_ci	       (int)(G_SCD_BERR_TID(status) >> 6),
5562306a36Sopenharmony_ci	       (int)G_SCD_BERR_RID(status),
5662306a36Sopenharmony_ci	       (int)G_SCD_BERR_DCODE(status));
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/*
6062306a36Sopenharmony_ci * check_bus_watcher is exported for use in situations where we want
6162306a36Sopenharmony_ci * to see the most recent status of the bus watcher, which might have
6262306a36Sopenharmony_ci * already been destructively read out of the registers.
6362306a36Sopenharmony_ci *
6462306a36Sopenharmony_ci * notes: this is currently used by the cache error handler
6562306a36Sopenharmony_ci *	  should provide locking against the interrupt handler
6662306a36Sopenharmony_ci */
6762306a36Sopenharmony_civoid check_bus_watcher(void)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	u32 status, l2_err, memio_err;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci#if defined(CONFIG_SIBYTE_BCM112X) || defined(CONFIG_SIBYTE_SB1250)
7262306a36Sopenharmony_ci	/* Use non-destructive register */
7362306a36Sopenharmony_ci	status = csr_in32(IOADDR(A_SCD_BUS_ERR_STATUS_DEBUG));
7462306a36Sopenharmony_ci#elif defined(CONFIG_SIBYTE_BCM1x80)
7562306a36Sopenharmony_ci	/* Use non-destructive register */
7662306a36Sopenharmony_ci	/* Same as 1250 except BUS_ERR_STATUS_DEBUG is in a different place. */
7762306a36Sopenharmony_ci	status = csr_in32(IOADDR(A_BCM1480_BUS_ERR_STATUS_DEBUG));
7862306a36Sopenharmony_ci#else
7962306a36Sopenharmony_ci#error bus watcher being built for unknown Sibyte SOC!
8062306a36Sopenharmony_ci#endif
8162306a36Sopenharmony_ci	if (!(status & 0x7fffffff)) {
8262306a36Sopenharmony_ci		printk("Using last values reaped by bus watcher driver\n");
8362306a36Sopenharmony_ci		status = bw_stats.status;
8462306a36Sopenharmony_ci		l2_err = bw_stats.l2_err;
8562306a36Sopenharmony_ci		memio_err = bw_stats.memio_err;
8662306a36Sopenharmony_ci	} else {
8762306a36Sopenharmony_ci		l2_err = csr_in32(IOADDR(A_BUS_L2_ERRORS));
8862306a36Sopenharmony_ci		memio_err = csr_in32(IOADDR(A_BUS_MEM_IO_ERRORS));
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci	if (status & ~(1UL << 31))
9162306a36Sopenharmony_ci		print_summary(status, l2_err, memio_err);
9262306a36Sopenharmony_ci	else
9362306a36Sopenharmony_ci		printk("Bus watcher indicates no error\n");
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci#ifdef CONFIG_PROC_FS
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/* For simplicity, I want to assume a single read is required each
9962306a36Sopenharmony_ci   time */
10062306a36Sopenharmony_cistatic int bw_proc_show(struct seq_file *m, void *v)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct bw_stats_struct *stats = m->private;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	seq_puts(m, "SiByte Bus Watcher statistics\n");
10562306a36Sopenharmony_ci	seq_puts(m, "-----------------------------\n");
10662306a36Sopenharmony_ci	seq_printf(m, "L2-d-cor %8ld\nL2-d-bad %8ld\n",
10762306a36Sopenharmony_ci		   stats->l2_cor_d, stats->l2_bad_d);
10862306a36Sopenharmony_ci	seq_printf(m, "L2-t-cor %8ld\nL2-t-bad %8ld\n",
10962306a36Sopenharmony_ci		   stats->l2_cor_t, stats->l2_bad_t);
11062306a36Sopenharmony_ci	seq_printf(m, "MC-d-cor %8ld\nMC-d-bad %8ld\n",
11162306a36Sopenharmony_ci		   stats->mem_cor_d, stats->mem_bad_d);
11262306a36Sopenharmony_ci	seq_printf(m, "IO-err   %8ld\n", stats->bus_error);
11362306a36Sopenharmony_ci	seq_puts(m, "\nLast recorded signature:\n");
11462306a36Sopenharmony_ci	seq_printf(m, "Request %02x from %d, answered by %d with Dcode %d\n",
11562306a36Sopenharmony_ci		   (unsigned int)(G_SCD_BERR_TID(stats->status) & 0x3f),
11662306a36Sopenharmony_ci		   (int)(G_SCD_BERR_TID(stats->status) >> 6),
11762306a36Sopenharmony_ci		   (int)G_SCD_BERR_RID(stats->status),
11862306a36Sopenharmony_ci		   (int)G_SCD_BERR_DCODE(stats->status));
11962306a36Sopenharmony_ci	/* XXXKW indicate multiple errors between printings, or stats
12062306a36Sopenharmony_ci	   collection (or both)? */
12162306a36Sopenharmony_ci	if (stats->status & M_SCD_BERR_MULTERRS)
12262306a36Sopenharmony_ci		seq_puts(m, "Multiple errors observed since last check.\n");
12362306a36Sopenharmony_ci	if (stats->status_printed) {
12462306a36Sopenharmony_ci		seq_puts(m, "(no change since last printing)\n");
12562306a36Sopenharmony_ci	} else {
12662306a36Sopenharmony_ci		stats->status_printed = 1;
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	return 0;
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic void create_proc_decoder(struct bw_stats_struct *stats)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	struct proc_dir_entry *ent;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	ent = proc_create_single_data("bus_watcher", S_IWUSR | S_IRUGO, NULL,
13762306a36Sopenharmony_ci			bw_proc_show, stats);
13862306a36Sopenharmony_ci	if (!ent) {
13962306a36Sopenharmony_ci		printk(KERN_INFO "Unable to initialize bus_watcher /proc entry\n");
14062306a36Sopenharmony_ci		return;
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci#endif /* CONFIG_PROC_FS */
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci/*
14762306a36Sopenharmony_ci * sibyte_bw_int - handle bus watcher interrupts and accumulate counts
14862306a36Sopenharmony_ci *
14962306a36Sopenharmony_ci * notes: possible re-entry due to multiple sources
15062306a36Sopenharmony_ci *	  should check/indicate saturation
15162306a36Sopenharmony_ci */
15262306a36Sopenharmony_cistatic irqreturn_t sibyte_bw_int(int irq, void *data)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct bw_stats_struct *stats = data;
15562306a36Sopenharmony_ci	unsigned long cntr;
15662306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BW_TRACE
15762306a36Sopenharmony_ci	int i;
15862306a36Sopenharmony_ci#endif
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BW_TRACE
16162306a36Sopenharmony_ci	csr_out32(M_SCD_TRACE_CFG_FREEZE, IOADDR(A_SCD_TRACE_CFG));
16262306a36Sopenharmony_ci	csr_out32(M_SCD_TRACE_CFG_START_READ, IOADDR(A_SCD_TRACE_CFG));
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	for (i=0; i<256*6; i++)
16562306a36Sopenharmony_ci		printk("%016llx\n",
16662306a36Sopenharmony_ci		       (long long)__raw_readq(IOADDR(A_SCD_TRACE_READ)));
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	csr_out32(M_SCD_TRACE_CFG_RESET, IOADDR(A_SCD_TRACE_CFG));
16962306a36Sopenharmony_ci	csr_out32(M_SCD_TRACE_CFG_START, IOADDR(A_SCD_TRACE_CFG));
17062306a36Sopenharmony_ci#endif
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	/* Destructive read, clears register and interrupt */
17362306a36Sopenharmony_ci	stats->status = csr_in32(IOADDR(A_SCD_BUS_ERR_STATUS));
17462306a36Sopenharmony_ci	stats->status_printed = 0;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	stats->l2_err = cntr = csr_in32(IOADDR(A_BUS_L2_ERRORS));
17762306a36Sopenharmony_ci	stats->l2_cor_d += G_SCD_L2ECC_CORR_D(cntr);
17862306a36Sopenharmony_ci	stats->l2_bad_d += G_SCD_L2ECC_BAD_D(cntr);
17962306a36Sopenharmony_ci	stats->l2_cor_t += G_SCD_L2ECC_CORR_T(cntr);
18062306a36Sopenharmony_ci	stats->l2_bad_t += G_SCD_L2ECC_BAD_T(cntr);
18162306a36Sopenharmony_ci	csr_out32(0, IOADDR(A_BUS_L2_ERRORS));
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	stats->memio_err = cntr = csr_in32(IOADDR(A_BUS_MEM_IO_ERRORS));
18462306a36Sopenharmony_ci	stats->mem_cor_d += G_SCD_MEM_ECC_CORR(cntr);
18562306a36Sopenharmony_ci	stats->mem_bad_d += G_SCD_MEM_ECC_BAD(cntr);
18662306a36Sopenharmony_ci	stats->bus_error += G_SCD_MEM_BUSERR(cntr);
18762306a36Sopenharmony_ci	csr_out32(0, IOADDR(A_BUS_MEM_IO_ERRORS));
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return IRQ_HANDLED;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ciint __init sibyte_bus_watcher(void)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	memset(&bw_stats, 0, sizeof(struct bw_stats_struct));
19562306a36Sopenharmony_ci	bw_stats.status_printed = 1;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	if (request_irq(K_INT_BAD_ECC, sibyte_bw_int, 0, "Bus watcher", &bw_stats)) {
19862306a36Sopenharmony_ci		printk("Failed to register bus watcher BAD_ECC irq\n");
19962306a36Sopenharmony_ci		return -1;
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci	if (request_irq(K_INT_COR_ECC, sibyte_bw_int, 0, "Bus watcher", &bw_stats)) {
20262306a36Sopenharmony_ci		free_irq(K_INT_BAD_ECC, &bw_stats);
20362306a36Sopenharmony_ci		printk("Failed to register bus watcher COR_ECC irq\n");
20462306a36Sopenharmony_ci		return -1;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci	if (request_irq(K_INT_IO_BUS, sibyte_bw_int, 0, "Bus watcher", &bw_stats)) {
20762306a36Sopenharmony_ci		free_irq(K_INT_BAD_ECC, &bw_stats);
20862306a36Sopenharmony_ci		free_irq(K_INT_COR_ECC, &bw_stats);
20962306a36Sopenharmony_ci		printk("Failed to register bus watcher IO_BUS irq\n");
21062306a36Sopenharmony_ci		return -1;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci#ifdef CONFIG_PROC_FS
21462306a36Sopenharmony_ci	create_proc_decoder(&bw_stats);
21562306a36Sopenharmony_ci#endif
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BW_TRACE
21862306a36Sopenharmony_ci	csr_out32((M_SCD_TRSEQ_ASAMPLE | M_SCD_TRSEQ_DSAMPLE |
21962306a36Sopenharmony_ci		   K_SCD_TRSEQ_TRIGGER_ALL),
22062306a36Sopenharmony_ci		  IOADDR(A_SCD_TRACE_SEQUENCE_0));
22162306a36Sopenharmony_ci	csr_out32(M_SCD_TRACE_CFG_RESET, IOADDR(A_SCD_TRACE_CFG));
22262306a36Sopenharmony_ci	csr_out32(M_SCD_TRACE_CFG_START, IOADDR(A_SCD_TRACE_CFG));
22362306a36Sopenharmony_ci#endif
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	return 0;
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cidevice_initcall(sibyte_bus_watcher);
229