Skip to content

Commit

Permalink
gpio-sifive: Implement SiFive GPIO controller
Browse files Browse the repository at this point in the history
- Supports 32 pins, route all pin interrupts to single PLIC IRQ
- Interrupt on input rise/fall/high/low
- Lock-free implementation
  • Loading branch information
LekKit authored Mar 7, 2024
1 parent 2b526a0 commit 1845948
Show file tree
Hide file tree
Showing 2 changed files with 339 additions and 0 deletions.
308 changes: 308 additions & 0 deletions src/devices/gpio-sifive.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
/*
gpio-sifive.c - SiFive GPIO Controller
Copyright (C) 2024 LekKit <github.com/LekKit>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include "gpio-sifive.h"
#include "plic.h"
#include "mem_ops.h"
#include "utils.h"
#include "fdtlib.h"

// See https://static.dev.sifive.com/FU540-C000-v1.0.pdf

#define GPIO_SIFIVE_REG_INPUT 0x00 // Pin input value
#define GPIO_SIFIVE_REG_INPUT_EN 0x04 // Pin input enable
#define GPIO_SIFIVE_REG_OUTPUT_EN 0x08 // Pin output enable
#define GPIO_SIFIVE_REG_OUTPUT 0x0C // Pin output value
#define GPIO_SIFIVE_REG_PUE 0x10 // Pull-up enable
#define GPIO_SIFIVE_REG_DS 0x14 // Drive strength
#define GPIO_SIFIVE_REG_RISE_IE 0x18 // Rise interrupt enable
#define GPIO_SIFIVE_REG_RISE_IP 0x1C // Rise interrupt pending
#define GPIO_SIFIVE_REG_FALL_IE 0x20 // Rise interrupt enable
#define GPIO_SIFIVE_REG_FALL_IP 0x24 // Fall interrupt pending
#define GPIO_SIFIVE_REG_HIGH_IE 0x28 // High interrupt enable
#define GPIO_SIFIVE_REG_HIGH_IP 0x2C // High interrupt pending
#define GPIO_SIFIVE_REG_LOW_IE 0x30 // Low interrupt enable
#define GPIO_SIFIVE_REG_LOW_IP 0x34 // Low interrupt pending
#define GPIO_SIFIVE_REG_OUT_XOR 0x40 // Output XOR (Invert)

#define GPIO_SIFIVE_MMIO_SIZE 0x44

typedef struct {
rvvm_gpio_dev_t* gpio;
plic_ctx_t* plic;
uint32_t irq;
uint32_t pins;
uint32_t input_en;
uint32_t output_en;
uint32_t output;
uint32_t pue;
uint32_t ds;
uint32_t rise_ie;
uint32_t rise_ip;
uint32_t fall_ie;
uint32_t fall_ip;
uint32_t high_ie;
uint32_t high_ip;
uint32_t low_ie;
uint32_t low_ip;
uint32_t out_xor;
} gpio_sifive_dev_t;

static void gpio_sifive_update_irqs(gpio_sifive_dev_t* bus)
{
if ((atomic_load_uint32(&bus->rise_ip) & atomic_load_uint32(&bus->rise_ie))
|| (atomic_load_uint32(&bus->fall_ip) & atomic_load_uint32(&bus->fall_ie))
|| (atomic_load_uint32(&bus->high_ip) & atomic_load_uint32(&bus->high_ie))
|| (atomic_load_uint32(&bus->low_ip) & atomic_load_uint32(&bus->low_ie))) {
plic_raise_irq(bus->plic, bus->irq);
} else {
plic_lower_irq(bus->plic, bus->irq);
}
}

static void gpio_sifive_update_pins(gpio_sifive_dev_t* bus, uint32_t pins)
{
uint32_t old_pins = atomic_swap_uint32(&bus->pins, pins);
uint32_t enable = atomic_load_uint32(&bus->input_en);
uint32_t pins_rise = (pins & ~old_pins);
uint32_t pins_fall = (~pins & old_pins);
atomic_or_uint32(&bus->rise_ip, pins_rise & enable);
atomic_or_uint32(&bus->fall_ip, pins_fall & enable);
atomic_or_uint32(&bus->high_ip, pins & enable);
atomic_or_uint32(&bus->low_ip, ~pins & enable);
gpio_sifive_update_irqs(bus);
}

static void gpio_sifive_update_out(gpio_sifive_dev_t* bus)
{
uint32_t out = atomic_load_uint32(&bus->output);
out &= atomic_load_uint32(&bus->output_en);
out ^= atomic_load_uint32(&bus->out_xor);
gpio_pins_out(bus->gpio, 0, out);
}

static bool gpio_sifive_pins_in(rvvm_gpio_dev_t* gpio, size_t off, uint32_t pins)
{
if (off == 0) {
gpio_sifive_dev_t* bus = gpio->io_dev;
gpio_sifive_update_pins(bus, pins);
return true;
}
return false;
}

static uint32_t gpio_sifive_pins_read(rvvm_gpio_dev_t* gpio, size_t off)
{
if (off == 0) {
gpio_sifive_dev_t* bus = gpio->io_dev;
uint32_t out = atomic_load_uint32(&bus->output);
out &= atomic_load_uint32(&bus->output_en);
out ^= atomic_load_uint32(&bus->out_xor);
return out;
}
return 0;
}

static bool gpio_sifive_mmio_read(rvvm_mmio_dev_t* dev, void* data, size_t offset, uint8_t size)
{
gpio_sifive_dev_t* bus = dev->data;
memset(data, 0, size);
switch (offset) {
case GPIO_SIFIVE_REG_INPUT:
write_uint32_le_m(data, atomic_load_uint32(&bus->pins) & atomic_load_uint32(&bus->input_en));
break;
case GPIO_SIFIVE_REG_INPUT_EN:
write_uint32_le_m(data, atomic_load_uint32(&bus->input_en));
break;
case GPIO_SIFIVE_REG_OUTPUT_EN:
write_uint32_le_m(data, atomic_load_uint32(&bus->output_en));
break;
case GPIO_SIFIVE_REG_OUTPUT:
write_uint32_le_m(data, atomic_load_uint32(&bus->output));
break;
case GPIO_SIFIVE_REG_PUE:
write_uint32_le_m(data, atomic_load_uint32(&bus->pue));
break;
case GPIO_SIFIVE_REG_DS:
write_uint32_le_m(data, atomic_load_uint32(&bus->ds));
break;
case GPIO_SIFIVE_REG_RISE_IE:
write_uint32_le_m(data, atomic_load_uint32(&bus->rise_ie));
break;
case GPIO_SIFIVE_REG_RISE_IP:
write_uint32_le_m(data, atomic_load_uint32(&bus->rise_ip));
break;
case GPIO_SIFIVE_REG_FALL_IE:
write_uint32_le_m(data, atomic_load_uint32(&bus->fall_ie));
break;
case GPIO_SIFIVE_REG_FALL_IP:
write_uint32_le_m(data, atomic_load_uint32(&bus->fall_ip));
break;
case GPIO_SIFIVE_REG_HIGH_IE:
write_uint32_le_m(data, atomic_load_uint32(&bus->high_ie));
break;
case GPIO_SIFIVE_REG_HIGH_IP:
write_uint32_le_m(data, atomic_load_uint32(&bus->high_ip));
break;
case GPIO_SIFIVE_REG_LOW_IE:
write_uint32_le_m(data, atomic_load_uint32(&bus->low_ie));
break;
case GPIO_SIFIVE_REG_LOW_IP:
write_uint32_le_m(data, atomic_load_uint32(&bus->low_ip));
break;
case GPIO_SIFIVE_REG_OUT_XOR:
write_uint32_le_m(data, atomic_load_uint32(&bus->out_xor));
break;
}
//rvvm_warn("gpio read %08x from %08zx", read_uint32_le_m(data), offset);
return true;
}

static bool gpio_sifive_mmio_write(rvvm_mmio_dev_t* dev, void* data, size_t offset, uint8_t size)
{
gpio_sifive_dev_t* bus = dev->data;
UNUSED(size);
switch (offset) {
case GPIO_SIFIVE_REG_INPUT_EN:
atomic_store_uint32(&bus->input_en, read_uint32_le_m(data));
break;
case GPIO_SIFIVE_REG_OUTPUT_EN:
atomic_store_uint32(&bus->output_en, read_uint32_le_m(data));
gpio_sifive_update_out(bus);
break;
case GPIO_SIFIVE_REG_OUTPUT:
atomic_store_uint32(&bus->output, read_uint32_le_m(data));
gpio_sifive_update_out(bus);
break;
case GPIO_SIFIVE_REG_PUE:
atomic_store_uint32(&bus->pue, read_uint32_le_m(data));
break;
case GPIO_SIFIVE_REG_DS:
atomic_store_uint32(&bus->ds, read_uint32_le_m(data));
break;
case GPIO_SIFIVE_REG_RISE_IE:
atomic_store_uint32(&bus->rise_ie, read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_RISE_IP:
atomic_and_uint32(&bus->rise_ip, ~read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_FALL_IE:
atomic_store_uint32(&bus->fall_ie, read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_FALL_IP:
atomic_and_uint32(&bus->fall_ip, ~read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_HIGH_IE:
atomic_store_uint32(&bus->high_ie, read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_HIGH_IP:
atomic_and_uint32(&bus->high_ip, ~read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_LOW_IE:
atomic_store_uint32(&bus->low_ie, read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_LOW_IP:
atomic_and_uint32(&bus->low_ip, ~read_uint32_le_m(data));
gpio_sifive_update_irqs(bus);
break;
case GPIO_SIFIVE_REG_OUT_XOR:
atomic_store_uint32(&bus->out_xor, read_uint32_le_m(data));
gpio_sifive_update_out(bus);
break;
}
//rvvm_warn("gpio write %08x to %08zx", read_uint32_le_m(data), offset);
return true;
}

static void gpio_sifive_remove(rvvm_mmio_dev_t* dev)
{
gpio_sifive_dev_t* bus = dev->data;
gpio_free(bus->gpio);
free(bus);
}

static void gpio_sifive_update(rvvm_mmio_dev_t* dev)
{
gpio_sifive_dev_t* bus = dev->data;
gpio_update(bus->gpio);
}

static rvvm_mmio_type_t gpio_sifive_dev_type = {
.name = "gpio_sifive",
.remove = gpio_sifive_remove,
.update = gpio_sifive_update,
};

PUBLIC rvvm_mmio_handle_t gpio_sifive_init(rvvm_machine_t* machine, rvvm_gpio_dev_t* gpio,
rvvm_addr_t base_addr, plic_ctx_t* plic, uint32_t irq)
{
gpio_sifive_dev_t* bus = safe_new_obj(gpio_sifive_dev_t);
bus->gpio = gpio;
bus->plic = plic;
bus->irq = irq;
if (gpio) {
gpio->io_dev = bus;
gpio->pins_in = gpio_sifive_pins_in;
gpio->pins_read = gpio_sifive_pins_read;
}
rvvm_mmio_dev_t gpio_sifive = {
.addr = base_addr,
.size = GPIO_SIFIVE_MMIO_SIZE,
.data = bus,
.read = gpio_sifive_mmio_read,
.write = gpio_sifive_mmio_write,
.type = &gpio_sifive_dev_type,
.min_op_size = 4,
.max_op_size = 4,
};
rvvm_mmio_handle_t handle = rvvm_attach_mmio(machine, &gpio_sifive);
if (handle == RVVM_INVALID_MMIO) return handle;
#ifdef USE_FDT
struct fdt_node* gpio_fdt = fdt_node_create_reg("gpio", base_addr);
fdt_node_add_prop_reg(gpio_fdt, "reg", base_addr, GPIO_SIFIVE_MMIO_SIZE);
fdt_node_add_prop_str(gpio_fdt, "compatible", "sifive,gpio0");
fdt_node_add_prop_u32(gpio_fdt, "interrupt-parent", plic_get_phandle(plic));
// Amount of IRQs controlls amount of GPIO pins, but they may overlap
uint32_t irqs[32] = {0};
for (size_t i=0; i<32; ++i) irqs[i] = irq;
fdt_node_add_prop_cells(gpio_fdt, "interrupts", irqs, 32);
//fdt_node_add_prop_u32(gpio_fdt, "interrupts", irq);
fdt_node_add_prop(gpio_fdt, "gpio-controller", NULL, 0);
fdt_node_add_prop_u32(gpio_fdt, "#gpio-cells", 2);
fdt_node_add_prop(gpio_fdt, "interrupt-controller", NULL, 0);
fdt_node_add_prop_u32(gpio_fdt, "#interrupt-cells", 2);
fdt_node_add_prop_u32(gpio_fdt, "ngpios", 32);
fdt_node_add_prop_str(gpio_fdt, "status", "okay");
fdt_node_add_child(rvvm_get_fdt_soc(machine), gpio_fdt);
#endif
return handle;
}

PUBLIC rvvm_mmio_handle_t gpio_sifive_init_auto(rvvm_machine_t* machine, rvvm_gpio_dev_t* gpio)
{
plic_ctx_t* plic = rvvm_get_plic(machine);
rvvm_addr_t addr = rvvm_mmio_zone_auto(machine, GPIO_SIFIVE_DEFAULT_MMIO, GPIO_SIFIVE_MMIO_SIZE);
return gpio_sifive_init(machine, gpio, addr, plic, plic_alloc_irq(plic));
}
31 changes: 31 additions & 0 deletions src/devices/gpio-sifive.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
gpio-sifive.h - SiFive GPIO Controller
Copyright (C) 2024 LekKit <github.com/LekKit>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#ifndef RVVM_GPIO_SIFIVE_H
#define RVVM_GPIO_SIFIVE_H

#include "rvvmlib.h"
#include "gpio_api.h"

#define GPIO_SIFIVE_DEFAULT_MMIO 0x10060000

PUBLIC rvvm_mmio_handle_t gpio_sifive_init(rvvm_machine_t* machine, rvvm_gpio_dev_t* gpio,
rvvm_addr_t base_addr, plic_ctx_t* plic, uint32_t irq);
PUBLIC rvvm_mmio_handle_t gpio_sifive_init_auto(rvvm_machine_t* machine, rvvm_gpio_dev_t* gpio);

#endif

0 comments on commit 1845948

Please sign in to comment.