| /* |
| * RX Interrupt Control Unit |
| * |
| * Warning: Only ICUa is supported. |
| * |
| * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware |
| * (Rev.1.40 R01UH0033EJ0140) |
| * |
| * Copyright (c) 2019 Yoshinori Sato |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2 or later, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu/log.h" |
| #include "qemu/error-report.h" |
| #include "hw/irq.h" |
| #include "hw/registerfields.h" |
| #include "hw/qdev-properties.h" |
| #include "hw/intc/rx_icu.h" |
| #include "migration/vmstate.h" |
| |
| REG8(IR, 0) |
| FIELD(IR, IR, 0, 1) |
| REG8(DTCER, 0x100) |
| FIELD(DTCER, DTCE, 0, 1) |
| REG8(IER, 0x200) |
| REG8(SWINTR, 0x2e0) |
| FIELD(SWINTR, SWINT, 0, 1) |
| REG16(FIR, 0x2f0) |
| FIELD(FIR, FVCT, 0, 8) |
| FIELD(FIR, FIEN, 15, 1) |
| REG8(IPR, 0x300) |
| FIELD(IPR, IPR, 0, 4) |
| REG8(DMRSR, 0x400) |
| REG8(IRQCR, 0x500) |
| FIELD(IRQCR, IRQMD, 2, 2) |
| REG8(NMISR, 0x580) |
| FIELD(NMISR, NMIST, 0, 1) |
| FIELD(NMISR, LVDST, 1, 1) |
| FIELD(NMISR, OSTST, 2, 1) |
| REG8(NMIER, 0x581) |
| FIELD(NMIER, NMIEN, 0, 1) |
| FIELD(NMIER, LVDEN, 1, 1) |
| FIELD(NMIER, OSTEN, 2, 1) |
| REG8(NMICLR, 0x582) |
| FIELD(NMICLR, NMICLR, 0, 1) |
| FIELD(NMICLR, OSTCLR, 2, 1) |
| REG8(NMICR, 0x583) |
| FIELD(NMICR, NMIMD, 3, 1) |
| |
| static void set_irq(RXICUState *icu, int n_IRQ, int req) |
| { |
| if ((icu->fir & R_FIR_FIEN_MASK) && |
| (icu->fir & R_FIR_FVCT_MASK) == n_IRQ) { |
| qemu_set_irq(icu->_fir, req); |
| } else { |
| qemu_set_irq(icu->_irq, req); |
| } |
| } |
| |
| static uint16_t rxicu_level(RXICUState *icu, unsigned n) |
| { |
| return (icu->ipr[icu->map[n]] << 8) | n; |
| } |
| |
| static void rxicu_request(RXICUState *icu, int n_IRQ) |
| { |
| int enable; |
| |
| enable = icu->ier[n_IRQ / 8] & (1 << (n_IRQ & 7)); |
| if (n_IRQ > 0 && enable != 0 && qatomic_read(&icu->req_irq) < 0) { |
| qatomic_set(&icu->req_irq, n_IRQ); |
| set_irq(icu, n_IRQ, rxicu_level(icu, n_IRQ)); |
| } |
| } |
| |
| static void rxicu_set_irq(void *opaque, int n_IRQ, int level) |
| { |
| RXICUState *icu = opaque; |
| struct IRQSource *src; |
| int issue; |
| |
| if (n_IRQ >= NR_IRQS) { |
| error_report("%s: IRQ %d out of range", __func__, n_IRQ); |
| return; |
| } |
| |
| src = &icu->src[n_IRQ]; |
| |
| level = (level != 0); |
| switch (src->sense) { |
| case TRG_LEVEL: |
| /* level-sensitive irq */ |
| issue = level; |
| src->level = level; |
| break; |
| case TRG_NEDGE: |
| issue = (level == 0 && src->level == 1); |
| src->level = level; |
| break; |
| case TRG_PEDGE: |
| issue = (level == 1 && src->level == 0); |
| src->level = level; |
| break; |
| case TRG_BEDGE: |
| issue = ((level ^ src->level) & 1); |
| src->level = level; |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| if (issue == 0 && src->sense == TRG_LEVEL) { |
| icu->ir[n_IRQ] = 0; |
| if (qatomic_read(&icu->req_irq) == n_IRQ) { |
| /* clear request */ |
| set_irq(icu, n_IRQ, 0); |
| qatomic_set(&icu->req_irq, -1); |
| } |
| return; |
| } |
| if (issue) { |
| icu->ir[n_IRQ] = 1; |
| rxicu_request(icu, n_IRQ); |
| } |
| } |
| |
| static void rxicu_ack_irq(void *opaque, int no, int level) |
| { |
| RXICUState *icu = opaque; |
| int i; |
| int n_IRQ; |
| int max_pri; |
| |
| n_IRQ = qatomic_read(&icu->req_irq); |
| if (n_IRQ < 0) { |
| return; |
| } |
| qatomic_set(&icu->req_irq, -1); |
| if (icu->src[n_IRQ].sense != TRG_LEVEL) { |
| icu->ir[n_IRQ] = 0; |
| } |
| |
| max_pri = 0; |
| n_IRQ = -1; |
| for (i = 0; i < NR_IRQS; i++) { |
| if (icu->ir[i]) { |
| if (max_pri < icu->ipr[icu->map[i]]) { |
| n_IRQ = i; |
| max_pri = icu->ipr[icu->map[i]]; |
| } |
| } |
| } |
| |
| if (n_IRQ >= 0) { |
| rxicu_request(icu, n_IRQ); |
| } |
| } |
| |
| static uint64_t icu_read(void *opaque, hwaddr addr, unsigned size) |
| { |
| RXICUState *icu = opaque; |
| int reg = addr & 0xff; |
| |
| if ((addr != A_FIR && size != 1) || |
| (addr == A_FIR && size != 2)) { |
| qemu_log_mask(LOG_GUEST_ERROR, "rx_icu: Invalid read size 0x%" |
| HWADDR_PRIX "\n", |
| addr); |
| return UINT64_MAX; |
| } |
| switch (addr) { |
| case A_IR ... A_IR + 0xff: |
| return icu->ir[reg] & R_IR_IR_MASK; |
| case A_DTCER ... A_DTCER + 0xff: |
| return icu->dtcer[reg] & R_DTCER_DTCE_MASK; |
| case A_IER ... A_IER + 0x1f: |
| return icu->ier[reg]; |
| case A_SWINTR: |
| return 0; |
| case A_FIR: |
| return icu->fir & (R_FIR_FIEN_MASK | R_FIR_FVCT_MASK); |
| case A_IPR ... A_IPR + 0x8f: |
| return icu->ipr[reg] & R_IPR_IPR_MASK; |
| case A_DMRSR: |
| case A_DMRSR + 4: |
| case A_DMRSR + 8: |
| case A_DMRSR + 12: |
| return icu->dmasr[reg >> 2]; |
| case A_IRQCR ... A_IRQCR + 0x1f: |
| return icu->src[64 + reg].sense << R_IRQCR_IRQMD_SHIFT; |
| case A_NMISR: |
| case A_NMICLR: |
| return 0; |
| case A_NMIER: |
| return icu->nmier; |
| case A_NMICR: |
| return icu->nmicr; |
| default: |
| qemu_log_mask(LOG_UNIMP, "rx_icu: Register 0x%" HWADDR_PRIX " " |
| "not implemented.\n", |
| addr); |
| break; |
| } |
| return UINT64_MAX; |
| } |
| |
| static void icu_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) |
| { |
| RXICUState *icu = opaque; |
| int reg = addr & 0xff; |
| |
| if ((addr != A_FIR && size != 1) || |
| (addr == A_FIR && size != 2)) { |
| qemu_log_mask(LOG_GUEST_ERROR, "rx_icu: Invalid write size at " |
| "0x%" HWADDR_PRIX "\n", |
| addr); |
| return; |
| } |
| switch (addr) { |
| case A_IR ... A_IR + 0xff: |
| if (icu->src[reg].sense != TRG_LEVEL && val == 0) { |
| icu->ir[reg] = 0; |
| } |
| break; |
| case A_DTCER ... A_DTCER + 0xff: |
| icu->dtcer[reg] = val & R_DTCER_DTCE_MASK; |
| qemu_log_mask(LOG_UNIMP, "rx_icu: DTC not implemented\n"); |
| break; |
| case A_IER ... A_IER + 0x1f: |
| icu->ier[reg] = val; |
| break; |
| case A_SWINTR: |
| if (val & R_SWINTR_SWINT_MASK) { |
| qemu_irq_pulse(icu->_swi); |
| } |
| break; |
| case A_FIR: |
| icu->fir = val & (R_FIR_FIEN_MASK | R_FIR_FVCT_MASK); |
| break; |
| case A_IPR ... A_IPR + 0x8f: |
| icu->ipr[reg] = val & R_IPR_IPR_MASK; |
| break; |
| case A_DMRSR: |
| case A_DMRSR + 4: |
| case A_DMRSR + 8: |
| case A_DMRSR + 12: |
| icu->dmasr[reg >> 2] = val; |
| qemu_log_mask(LOG_UNIMP, "rx_icu: DMAC not implemented\n"); |
| break; |
| case A_IRQCR ... A_IRQCR + 0x1f: |
| icu->src[64 + reg].sense = val >> R_IRQCR_IRQMD_SHIFT; |
| break; |
| case A_NMICLR: |
| break; |
| case A_NMIER: |
| icu->nmier |= val & (R_NMIER_NMIEN_MASK | |
| R_NMIER_LVDEN_MASK | |
| R_NMIER_OSTEN_MASK); |
| break; |
| case A_NMICR: |
| if ((icu->nmier & R_NMIER_NMIEN_MASK) == 0) { |
| icu->nmicr = val & R_NMICR_NMIMD_MASK; |
| } |
| break; |
| default: |
| qemu_log_mask(LOG_UNIMP, "rx_icu: Register 0x%" HWADDR_PRIX " " |
| "not implemented\n", |
| addr); |
| break; |
| } |
| } |
| |
| static const MemoryRegionOps icu_ops = { |
| .write = icu_write, |
| .read = icu_read, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .impl = { |
| .min_access_size = 1, |
| .max_access_size = 2, |
| }, |
| .valid = { |
| .min_access_size = 1, |
| .max_access_size = 2, |
| }, |
| }; |
| |
| static void rxicu_realize(DeviceState *dev, Error **errp) |
| { |
| RXICUState *icu = RX_ICU(dev); |
| int i; |
| |
| if (icu->init_sense == NULL) { |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "rx_icu: trigger-level property must be set."); |
| return; |
| } |
| |
| for (i = 0; i < NR_IRQS; i++) { |
| icu->src[i].sense = TRG_PEDGE; |
| } |
| for (i = 0; i < icu->nr_sense; i++) { |
| uint8_t irqno = icu->init_sense[i]; |
| icu->src[irqno].sense = TRG_LEVEL; |
| } |
| icu->req_irq = -1; |
| } |
| |
| static void rxicu_init(Object *obj) |
| { |
| SysBusDevice *d = SYS_BUS_DEVICE(obj); |
| RXICUState *icu = RX_ICU(obj); |
| |
| memory_region_init_io(&icu->memory, OBJECT(icu), &icu_ops, |
| icu, "rx-icu", 0x600); |
| sysbus_init_mmio(d, &icu->memory); |
| |
| qdev_init_gpio_in(DEVICE(d), rxicu_set_irq, NR_IRQS); |
| qdev_init_gpio_in_named(DEVICE(d), rxicu_ack_irq, "ack", 1); |
| sysbus_init_irq(d, &icu->_irq); |
| sysbus_init_irq(d, &icu->_fir); |
| sysbus_init_irq(d, &icu->_swi); |
| } |
| |
| static void rxicu_fini(Object *obj) |
| { |
| RXICUState *icu = RX_ICU(obj); |
| g_free(icu->map); |
| g_free(icu->init_sense); |
| } |
| |
| static const VMStateDescription vmstate_rxicu = { |
| .name = "rx-icu", |
| .version_id = 1, |
| .minimum_version_id = 1, |
| .fields = (VMStateField[]) { |
| VMSTATE_UINT8_ARRAY(ir, RXICUState, NR_IRQS), |
| VMSTATE_UINT8_ARRAY(dtcer, RXICUState, NR_IRQS), |
| VMSTATE_UINT8_ARRAY(ier, RXICUState, NR_IRQS / 8), |
| VMSTATE_UINT8_ARRAY(ipr, RXICUState, 142), |
| VMSTATE_UINT8_ARRAY(dmasr, RXICUState, 4), |
| VMSTATE_UINT16(fir, RXICUState), |
| VMSTATE_UINT8(nmisr, RXICUState), |
| VMSTATE_UINT8(nmier, RXICUState), |
| VMSTATE_UINT8(nmiclr, RXICUState), |
| VMSTATE_UINT8(nmicr, RXICUState), |
| VMSTATE_INT16(req_irq, RXICUState), |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| static Property rxicu_properties[] = { |
| DEFINE_PROP_ARRAY("ipr-map", RXICUState, nr_irqs, map, |
| qdev_prop_uint8, uint8_t), |
| DEFINE_PROP_ARRAY("trigger-level", RXICUState, nr_sense, init_sense, |
| qdev_prop_uint8, uint8_t), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void rxicu_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->realize = rxicu_realize; |
| dc->vmsd = &vmstate_rxicu; |
| device_class_set_props(dc, rxicu_properties); |
| } |
| |
| static const TypeInfo rxicu_info = { |
| .name = TYPE_RX_ICU, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(RXICUState), |
| .instance_init = rxicu_init, |
| .instance_finalize = rxicu_fini, |
| .class_init = rxicu_class_init, |
| }; |
| |
| static void rxicu_register_types(void) |
| { |
| type_register_static(&rxicu_info); |
| } |
| |
| type_init(rxicu_register_types) |