summaryrefslogtreecommitdiff
path: root/lib/utils/timer/fdt_timer_mtimer.c
blob: e140567e9c76427b9bdf48b533862b7460e9266b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2021 Western Digital Corporation or its affiliates.
 *
 * Authors:
 *   Anup Patel <anup.patel@wdc.com>
 */

#include <libfdt.h>
#include <sbi/sbi_error.h>
#include <sbi_utils/fdt/fdt_helper.h>
#include <sbi_utils/timer/fdt_timer.h>
#include <sbi_utils/timer/aclint_mtimer.h>

#define MTIMER_MAX_NR			16

struct timer_mtimer_quirks {
	unsigned int	mtime_offset;
	bool		has_64bit_mmio;
};

static unsigned long mtimer_count = 0;
static struct aclint_mtimer_data mtimer[MTIMER_MAX_NR];
static struct aclint_mtimer_data *mt_reference = NULL;

static int timer_mtimer_cold_init(void *fdt, int nodeoff,
				  const struct fdt_match *match)
{
	int i, rc;
	unsigned long addr[2], size[2];
	struct aclint_mtimer_data *mt;

	if (MTIMER_MAX_NR <= mtimer_count)
		return SBI_ENOSPC;
	mt = &mtimer[mtimer_count];

	rc = fdt_parse_aclint_node(fdt, nodeoff, true,
				   &addr[0], &size[0], &addr[1], &size[1],
				   &mt->first_hartid, &mt->hart_count);
	if (rc)
		return rc;
	mt->has_64bit_mmio = true;
	mt->has_shared_mtime = false;

	rc = fdt_parse_timebase_frequency(fdt, &mt->mtime_freq);
	if (rc)
		return rc;

	if (match->data) { /* SiFive CLINT */
		const struct timer_mtimer_quirks *quirks = match->data;

		/* Set CLINT addresses */
		mt->mtimecmp_addr = addr[0] + ACLINT_DEFAULT_MTIMECMP_OFFSET;
		mt->mtimecmp_size = ACLINT_DEFAULT_MTIMECMP_SIZE;
		mt->mtime_addr = addr[0] + ACLINT_DEFAULT_MTIME_OFFSET;
		mt->mtime_size = size[0] - mt->mtimecmp_size;
		/* Adjust MTIMER address and size for CLINT device */
		mt->mtime_addr += quirks->mtime_offset;
		mt->mtimecmp_addr += quirks->mtime_offset;
		mt->mtime_size -= quirks->mtime_offset;
		/* Apply additional CLINT quirks */
		mt->has_64bit_mmio = quirks->has_64bit_mmio;
	} else { /* RISC-V ACLINT MTIMER */
		/* Set ACLINT MTIMER addresses */
		mt->mtime_addr = addr[0];
		mt->mtime_size = size[0];
		mt->mtimecmp_addr = addr[1];
		mt->mtimecmp_size = size[1];
	}

	/* Check if MTIMER device has shared MTIME address */
	mt->has_shared_mtime = false;
	for (i = 0; i < mtimer_count; i++) {
		if (mtimer[i].mtime_addr == mt->mtime_addr) {
			mt->has_shared_mtime = true;
			break;
		}
	}

	/* Initialize the MTIMER device */
	rc = aclint_mtimer_cold_init(mt, mt_reference);
	if (rc)
		return rc;

	/*
	 * Select first MTIMER device with no associated HARTs as our
	 * reference MTIMER device. This is only a temporary strategy
	 * of selecting reference MTIMER device. In future, we might
	 * define an optional DT property or some other mechanism to
	 * help us select the reference MTIMER device.
	 */
	if (!mt->hart_count && !mt_reference) {
		mt_reference = mt;
		/*
		 * Set reference for already propbed MTIMER devices
		 * with non-shared MTIME
		 */
		for (i = 0; i < mtimer_count; i++)
			if (!mtimer[i].has_shared_mtime)
				aclint_mtimer_set_reference(&mtimer[i], mt);
	}

	/* Explicitly sync-up MTIMER devices not associated with any HARTs */
	if (!mt->hart_count)
		aclint_mtimer_sync(mt);

	mtimer_count++;
	return 0;
}

static const struct timer_mtimer_quirks d1_clint_quirks = {
	.mtime_offset	= CLINT_MTIMER_OFFSET,
	.has_64bit_mmio	= false,
};

static const struct timer_mtimer_quirks sifive_clint_quirks = {
	.mtime_offset	= CLINT_MTIMER_OFFSET,
	.has_64bit_mmio	= true,
};

static const struct fdt_match timer_mtimer_match[] = {
	{ .compatible = "allwinner,sun20i-d1-clint", .data = &d1_clint_quirks },
	{ .compatible = "riscv,clint0", .data = &sifive_clint_quirks },
	{ .compatible = "sifive,clint0", .data = &sifive_clint_quirks },
	{ .compatible = "riscv,aclint-mtimer" },
	{ },
};

struct fdt_timer fdt_timer_mtimer = {
	.match_table = timer_mtimer_match,
	.cold_init = timer_mtimer_cold_init,
	.warm_init = aclint_mtimer_warm_init,
	.exit = NULL,
};