From: Nikita Shubin <n.shu...@yadro.com> Add gpiodev support for:
- getting line info - getting/setting lines - monitoring line events - monitoting config events Binding is done via id, i.e.: ... -gpiodev chardev,id=aspeed-gpio0 Signed-off-by: Nikita Shubin <n.shu...@yadro.com> --- hw/gpio/aspeed_gpio.c | 127 ++++++++++++++++++++++++++++++++++++++++++ include/hw/gpio/aspeed_gpio.h | 3 + 2 files changed, 130 insertions(+) diff --git a/hw/gpio/aspeed_gpio.c b/hw/gpio/aspeed_gpio.c index a5b3f454e800dc9440600562b6295b069cb536fb..0c8b27c9a0cbef313254405dde3f1e1910f9bf57 100644 --- a/hw/gpio/aspeed_gpio.c +++ b/hw/gpio/aspeed_gpio.c @@ -291,6 +291,8 @@ static int aspeed_evaluate_irq(GPIOSets *regs, int gpio_prev_high, int gpio) return 0; } +static void aspeed_gpio_line_event(AspeedGPIOState *s, uint32_t set_idx, uint32_t pin_idx); + #define nested_struct_index(ta, pa, m, tb, pb) \ (pb - ((tb *)(((char *)pa) + offsetof(ta, m)))) @@ -337,6 +339,9 @@ static void aspeed_gpio_update(AspeedGPIOState *s, GPIOSets *regs, /* ...trigger the line-state IRQ */ ptrdiff_t set = aspeed_gpio_set_idx(s, regs); qemu_set_irq(s->gpios[set][gpio], !!(new & mask)); + + /* ...notify gpio backend if any */ + aspeed_gpio_line_event(s, set, gpio); } else { /* ...otherwise if we meet the line's current IRQ policy... */ if (aspeed_evaluate_irq(regs, old & mask, gpio)) { @@ -360,6 +365,18 @@ static bool aspeed_gpio_get_pin_level(AspeedGPIOState *s, uint32_t set_idx, return !!(reg_val & pin_mask); } +static void aspeed_gpio_line_event(AspeedGPIOState *s, uint32_t set_idx, uint32_t pin_idx) +{ + uint32_t offset = set_idx * ASPEED_GPIOS_PER_SET + pin_idx; + QEMUGpioLineEvent event = GPIO_EVENT_FALLING_EDGE; + + if (aspeed_gpio_get_pin_level(s, set_idx, pin_idx)) { + event = GPIO_EVENT_RISING_EDGE; + } + + qemu_gpio_fe_line_event(&s->gpiodev, offset, event); +} + static void aspeed_gpio_set_pin_level(AspeedGPIOState *s, uint32_t set_idx, uint32_t pin, bool level) { @@ -659,6 +676,13 @@ static uint64_t aspeed_gpio_read(void *opaque, hwaddr offset, uint32_t size) return value; } +static void aspeed_gpio_config_event(AspeedGPIOState *s, uint32_t set_idx, uint32_t pin_idx) +{ + uint32_t offset = set_idx * ASPEED_GPIOS_PER_SET + pin_idx; + + qemu_gpio_fe_config_event(&s->gpiodev, offset, GPIO_LINE_CHANGED_CONFIG); +} + static void aspeed_gpio_write_index_mode(void *opaque, hwaddr offset, uint64_t data, uint32_t size) { @@ -674,6 +698,7 @@ static void aspeed_gpio_write_index_mode(void *opaque, hwaddr offset, uint32_t group_idx = pin_idx / GPIOS_PER_GROUP; uint32_t reg_value = 0; uint32_t pending = 0; + uint32_t old_direction; set = &s->sets[set_idx]; props = &agc->props[set_idx]; @@ -711,8 +736,12 @@ static void aspeed_gpio_write_index_mode(void *opaque, hwaddr offset, * data = ( data | ~input) & output; */ reg_value = (reg_value | ~props->input) & props->output; + old_direction = set->direction; set->direction = update_value_control_source(set, set->direction, reg_value); + if (set->direction != old_direction) { + aspeed_gpio_config_event(s, set_idx, pin_idx); + } break; case gpio_reg_idx_interrupt: reg_value = set->int_enable; @@ -813,6 +842,7 @@ static void aspeed_gpio_write(void *opaque, hwaddr offset, uint64_t data, const AspeedGPIOReg *reg; GPIOSets *set; uint32_t cleared; + uint32_t old_direction; trace_aspeed_gpio_write(offset, data); @@ -867,7 +897,17 @@ static void aspeed_gpio_write(void *opaque, hwaddr offset, uint64_t data, * data = ( data | ~input) & output; */ data = (data | ~props->input) & props->output; + old_direction = set->direction; set->direction = update_value_control_source(set, set->direction, data); + qemu_log("gpio_reg_direction: 0x%x 0x%x\n", set->direction, old_direction); + if (set->direction != old_direction) { + unsigned long changed = set->direction ^ old_direction; + int idx = find_first_bit(&changed, ASPEED_GPIOS_PER_SET); + while (idx < ASPEED_GPIOS_PER_SET) { + aspeed_gpio_config_event(s, reg->set_idx, idx); + idx = find_next_bit(&changed, ASPEED_GPIOS_PER_SET, idx + 1); + } + } break; case gpio_reg_int_enable: set->int_enable = update_value_control_source(set, set->int_enable, @@ -1392,11 +1432,85 @@ static void aspeed_gpio_reset(DeviceState *dev) memset(s->sets, 0, sizeof(s->sets)); } +static void aspeed_gpio_line_info(void *opaque, gpio_line_info *info) +{ + AspeedGPIOState *s = ASPEED_GPIO(opaque); + AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s); + uint32_t group_idx = 0, pin_idx = 0, idx = 0; + uint32_t offset = info->offset; + const GPIOSetProperties *props; + bool direction; + const char *group; + int i, set_idx, grp_idx, pin; + + for (i = 0; i < ASPEED_GPIO_MAX_NR_SETS; i++) { + props = &agc->props[i]; + uint32_t skip = ~(props->input | props->output); + for (int j = 0; j < ASPEED_GPIOS_PER_SET; j++) { + if (skip >> j & 1) { + continue; + } + + group_idx = j / GPIOS_PER_GROUP; + pin_idx = j % GPIOS_PER_GROUP; + if (idx == offset) { + goto found; + } + + idx++; + } + } + + return; + +found: + group = &props->group_label[group_idx][0]; + set_idx = get_set_idx(s, group, &grp_idx); + snprintf(info->name, sizeof(info->name), "gpio%s%d", group, pin_idx); + pin = pin_idx + group_idx * GPIOS_PER_GROUP; + direction = !!(s->sets[set_idx].direction & BIT_ULL(pin)); + + if (direction) { + info->flags |= GPIO_LINE_FLAG_OUTPUT; + } else { + info->flags |= GPIO_LINE_FLAG_INPUT; + } + + qemu_log("%u: %s: set_idx=%d, grp_idx=%d, group_idx=%u, pin_idx=%u\n", offset, info->name, set_idx, grp_idx, group_idx, pin_idx); +} + +static int aspeed_gpio_get_line(void *opaque, uint32_t offset) +{ + AspeedGPIOState *s = ASPEED_GPIO(opaque); + int set_idx, pin_idx; + + set_idx = offset / ASPEED_GPIOS_PER_SET; + pin_idx = offset % ASPEED_GPIOS_PER_SET; + + return aspeed_gpio_get_pin_level(s, set_idx, pin_idx); +} + +static int aspeed_gpio_set_line(void *opaque, uint32_t offset, uint8_t value) +{ + AspeedGPIOState *s = ASPEED_GPIO(opaque); + int set_idx, pin_idx; + + set_idx = offset / ASPEED_GPIOS_PER_SET; + pin_idx = offset % ASPEED_GPIOS_PER_SET; + + aspeed_gpio_set_pin_level(s, set_idx, pin_idx, value); + + return 0; +} + static void aspeed_gpio_realize(DeviceState *dev, Error **errp) { AspeedGPIOState *s = ASPEED_GPIO(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s); + DeviceState *d = DEVICE(s); + Object *backend; + Gpiodev *gpio; /* Interrupt parent line */ sysbus_init_irq(sbd, &s->irq); @@ -1417,6 +1531,19 @@ static void aspeed_gpio_realize(DeviceState *dev, Error **errp) TYPE_ASPEED_GPIO, agc->mem_size); sysbus_init_mmio(sbd, &s->iomem); + + /* NOTE: or we can create one per set */ + if (d->id) { + backend = object_resolve_path_type(d->id, TYPE_GPIODEV, NULL); + if (backend) { + gpio = GPIODEV(backend); + qemu_gpio_fe_init(&s->gpiodev, gpio, agc->nr_gpio_pins, d->id, + "ASPEED GPIO", NULL); + qemu_gpio_fe_set_handlers(&s->gpiodev, aspeed_gpio_line_info, + aspeed_gpio_get_line, + aspeed_gpio_set_line, s); + } + } } static void aspeed_gpio_init(Object *obj) diff --git a/include/hw/gpio/aspeed_gpio.h b/include/hw/gpio/aspeed_gpio.h index e1e6c543339ef9ddff99124d7a267e9c8544c556..f0a1207b24421f4129182fbcbb88650604313a6c 100644 --- a/include/hw/gpio/aspeed_gpio.h +++ b/include/hw/gpio/aspeed_gpio.h @@ -12,6 +12,8 @@ #include "hw/sysbus.h" #include "qom/object.h" +#include "gpiodev/gpio.h" +#include "gpiodev/gpio-fe.h" #define TYPE_ASPEED_GPIO "aspeed.gpio" OBJECT_DECLARE_TYPE(AspeedGPIOState, AspeedGPIOClass, ASPEED_GPIO) @@ -85,6 +87,7 @@ struct AspeedGPIOState { SysBusDevice parent; /*< public >*/ + GpioBackend gpiodev; MemoryRegion iomem; int pending; qemu_irq irq; -- 2.45.2