summaryrefslogtreecommitdiff
path: root/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/riscv_clint0.c
blob: d0488b317317780abbb1cd14d7d2e38951f2d994 (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/* Copyright 2018 SiFive, Inc */
/* SPDX-License-Identifier: Apache-2.0 */

#include <metal/machine/platform.h>

#ifdef METAL_RISCV_CLINT0

#include <metal/io.h>
#include <metal/cpu.h>
#include <metal/drivers/riscv_clint0.h>
#include <metal/machine.h>

unsigned long long __metal_clint0_mtime_get (struct __metal_driver_riscv_clint0 *clint)
{
    __metal_io_u32 lo, hi;
    unsigned long control_base = __metal_driver_sifive_clint0_control_base(&clint->controller);

    /* Guard against rollover when reading */
    do {
	hi = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_RISCV_CLINT0_MTIME + 4));
	lo = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_RISCV_CLINT0_MTIME));
    } while (__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_RISCV_CLINT0_MTIME + 4)) != hi);

    return (((unsigned long long)hi) << 32) | lo;
}

int __metal_driver_riscv_clint0_mtimecmp_set(struct metal_interrupt *controller,
                                             int hartid,
                                             unsigned long long time)
{   
    struct __metal_driver_riscv_clint0 *clint =
                              (struct __metal_driver_riscv_clint0 *)(controller);
    unsigned long control_base = __metal_driver_sifive_clint0_control_base(&clint->controller);
    /* Per spec, the RISC-V MTIME/MTIMECMP registers are 64 bit,
     * and are NOT internally latched for multiword transfers.
     * Need to be careful about sequencing to avoid triggering
     * spurious interrupts: For that set the high word to a max
     * value first.
     */
    __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_RISCV_CLINT0_MTIMECMP_BASE + 4)) = 0xFFFFFFFF;
    __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_RISCV_CLINT0_MTIMECMP_BASE)) = (__metal_io_u32)time;
    __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_RISCV_CLINT0_MTIMECMP_BASE + 4)) = (__metal_io_u32)(time >> 32);
    return 0;
}

static struct metal_interrupt *_get_cpu_intc()
{
    int hartid = 0;
    __asm__ volatile("csrr %[hartid], mhartid"
                     : [hartid] "=r" (hartid) :: "memory");

    struct metal_cpu *cpu = metal_cpu_get(hartid);

    return metal_cpu_interrupt_controller(cpu);
}

void __metal_driver_riscv_clint0_init (struct metal_interrupt *controller)
{
    int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);
    struct __metal_driver_riscv_clint0 *clint =
                              (struct __metal_driver_riscv_clint0 *)(controller);

    if ( !clint->init_done ) {
	/* Register its interrupts with with parent controller, aka sw and timerto its default isr */
        for (int i = 0; i < num_interrupts; i++) {
	  struct metal_interrupt *intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
	  int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
            intc->vtable->interrupt_register(intc, line, NULL, controller);
	}
	clint->init_done = 1;
    }	
}

int __metal_driver_riscv_clint0_register (struct metal_interrupt *controller,
                                        int id, metal_interrupt_handler_t isr,
                                        void *priv)
{
    int rc = -1;
    metal_vector_mode mode = __metal_controller_interrupt_vector_mode();
    struct metal_interrupt *intc = NULL;
    struct metal_interrupt *cpu_intc = _get_cpu_intc();
    int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);

    if ( (mode != METAL_VECTOR_MODE) && (mode != METAL_DIRECT_MODE) ) {
        return rc;
    }

    for(int i = 0; i < num_interrupts; i++) {
	int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
        intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
        if (cpu_intc == intc && id == line) {
            break;
        }
	intc = NULL;
    }

    /* Register its interrupts with parent controller */
    if (intc) {
        rc = intc->vtable->interrupt_register(intc, id, isr, priv);
    }
    return rc;
}

int __metal_driver_riscv_clint0_vector_register (struct metal_interrupt *controller,
                                                 int id, metal_interrupt_vector_handler_t isr,
                                                 void *priv)
{   
    /* Not supported. User can override the 'weak' handler with their own */
    int rc = -1;
    return rc;
}

metal_vector_mode __metal_driver_riscv_clint0_get_vector_mode (struct metal_interrupt *controller)
{
    return __metal_controller_interrupt_vector_mode();
}

int __metal_driver_riscv_clint0_set_vector_mode (struct metal_interrupt *controller, metal_vector_mode mode)
{
    int rc = -1;
    struct metal_interrupt *intc = _get_cpu_intc();

    if (intc) {
	/* Valid vector modes are VECTOR and DIRECT, anything else is invalid (-1) */
        switch (mode) {
        case METAL_VECTOR_MODE:
        case METAL_DIRECT_MODE:
            rc = intc->vtable->interrupt_set_vector_mode(intc, mode);
            break;
        case METAL_HARDWARE_VECTOR_MODE:
        case METAL_SELECTIVE_NONVECTOR_MODE:
        case METAL_SELECTIVE_VECTOR_MODE:
        break;
        }
    }
    return rc;
}

int __metal_driver_riscv_clint0_enable (struct metal_interrupt *controller, int id)
{
    int rc = -1;

    if ( id ) {
        struct metal_interrupt *intc = NULL;
        struct metal_interrupt *cpu_intc = _get_cpu_intc();
	int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);

        for(int i = 0; i < num_interrupts; i++) {
	    int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
	    intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
            if(cpu_intc == intc && id == line) {
                break;
            }
	    intc = NULL;
        }
        
        /* Enable its interrupts with parent controller */
        if (intc) {
            rc = intc->vtable->interrupt_enable(intc, id);
        }
    }

    return rc;
}

int __metal_driver_riscv_clint0_disable (struct metal_interrupt *controller, int id)
{
    int rc = -1;

    if ( id ) {
        struct metal_interrupt *intc = NULL;
        struct metal_interrupt *cpu_intc = _get_cpu_intc();
	int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);

        for(int i = 0; i < num_interrupts; i++) {
	    int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
	    intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
            if(cpu_intc == intc && id == line) {
                break;
            }
	    intc = NULL;
        }
        
        /* Disable its interrupts with parent controller */
        if (intc) {
            rc = intc->vtable->interrupt_disable(intc, id);
        }
    }

    return rc;
}

int __metal_driver_riscv_clint0_command_request (struct metal_interrupt *controller,
                                               int command, void *data)
{
    int hartid;
    int rc = -1;
    struct __metal_driver_riscv_clint0 *clint =
                              (struct __metal_driver_riscv_clint0 *)(controller);
    unsigned long control_base = __metal_driver_sifive_clint0_control_base(controller);

    switch (command) {
    case METAL_TIMER_MTIME_GET:
        if (data) {
	    *(unsigned long long *)data = __metal_clint0_mtime_get(clint);
            rc = 0;
        }
        break;
    case METAL_SOFTWARE_IPI_CLEAR:
	if (data) {
	    hartid = *(int *)data;
            __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
					       (hartid * 4))) = METAL_DISABLE;
            rc = 0;
        }
        break;
    case METAL_SOFTWARE_IPI_SET:
	if (data) {
	    hartid = *(int *)data;
            __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
					       (hartid * 4))) = METAL_ENABLE;
	    /* Callers of this function assume it's blocking, in the sense that
	     * the IPI is guarnteed to have been delivered before the function
	     * returns.  We can't really guarnteed it's delivered, but we can
	     * read back the control register after writing it in at least an
	     * attempt to provide some semblence of ordering here.  The fence
	     * ensures the read is order after the write -- it wouldn't be
	     * necessary under RVWMO because this is the same address, but we
	     * don't have an IO memory model so I'm being a bit overkill here.
	     */
	    __METAL_IO_FENCE(o,i);
            rc = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
					       (hartid * 4)));
            rc = 0;
        }
        break;
    case METAL_SOFTWARE_MSIP_GET:
        rc = 0;
	if (data) {
	    hartid = *(int *)data;
            rc = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
						    (hartid * 4)));
        }
        break;
    default:
	break;
    }

    return rc;
}

int __metal_driver_riscv_clint0_clear_interrupt (struct metal_interrupt *controller, int id)
{
    int hartid = metal_cpu_get_current_hartid();
    return __metal_driver_riscv_clint0_command_request(controller,
						METAL_SOFTWARE_IPI_CLEAR, &hartid);
}

int __metal_driver_riscv_clint0_set_interrupt (struct metal_interrupt *controller, int id)
{
    int hartid = metal_cpu_get_current_hartid();
    return __metal_driver_riscv_clint0_command_request(controller,
						METAL_SOFTWARE_IPI_SET, &hartid);
}


__METAL_DEFINE_VTABLE(__metal_driver_vtable_riscv_clint0) = {
    .clint_vtable.interrupt_init     = __metal_driver_riscv_clint0_init,
    .clint_vtable.interrupt_register = __metal_driver_riscv_clint0_register,
    .clint_vtable.interrupt_vector_register = __metal_driver_riscv_clint0_vector_register,
    .clint_vtable.interrupt_enable   = __metal_driver_riscv_clint0_enable,
    .clint_vtable.interrupt_disable  = __metal_driver_riscv_clint0_disable,
    .clint_vtable.interrupt_get_vector_mode = __metal_driver_riscv_clint0_get_vector_mode,
    .clint_vtable.interrupt_set_vector_mode = __metal_driver_riscv_clint0_set_vector_mode,
    .clint_vtable.interrupt_clear    = __metal_driver_riscv_clint0_clear_interrupt,
    .clint_vtable.interrupt_set      = __metal_driver_riscv_clint0_set_interrupt,
    .clint_vtable.command_request    = __metal_driver_riscv_clint0_command_request,
    .clint_vtable.mtimecmp_set       = __metal_driver_riscv_clint0_mtimecmp_set,
};

#endif /* METAL_RISCV_CLINT0 */

typedef int no_empty_translation_units;