--- /dev/null
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ *
+ * Authors:
+ * Anup Patel <anup.patel@wdc.com>
+ */
+
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_error.h>
+#include <sbi_utils/irqchip/aplic.h>
+
+#define APLIC_MAX_IDC (1UL << 14)
+#define APLIC_MAX_SOURCE 1024
+
+#define APLIC_DOMAINCFG 0x0000
+#define APLIC_DOMAINCFG_IE (1 << 8)
+#define APLIC_DOMAINCFG_DM (1 << 2)
+#define APLIC_DOMAINCFG_BE (1 << 0)
+
+#define APLIC_SOURCECFG_BASE 0x0004
+#define APLIC_SOURCECFG_D (1 << 10)
+#define APLIC_SOURCECFG_CHILDIDX_MASK 0x000003ff
+#define APLIC_SOURCECFG_SM_MASK 0x00000007
+#define APLIC_SOURCECFG_SM_INACTIVE 0x0
+#define APLIC_SOURCECFG_SM_DETACH 0x1
+#define APLIC_SOURCECFG_SM_EDGE_RISE 0x4
+#define APLIC_SOURCECFG_SM_EDGE_FALL 0x5
+#define APLIC_SOURCECFG_SM_LEVEL_HIGH 0x6
+#define APLIC_SOURCECFG_SM_LEVEL_LOW 0x7
+
+#define APLIC_MMSICFGADDR 0x1bc0
+#define APLIC_MMSICFGADDRH 0x1bc4
+#define APLIC_SMSICFGADDR 0x1bc8
+#define APLIC_SMSICFGADDRH 0x1bcc
+
+#define APLIC_xMSICFGADDRH_L (1UL << 31)
+#define APLIC_xMSICFGADDRH_HHXS_MASK 0x1f
+#define APLIC_xMSICFGADDRH_HHXS_SHIFT 24
+#define APLIC_xMSICFGADDRH_LHXS_MASK 0x7
+#define APLIC_xMSICFGADDRH_LHXS_SHIFT 20
+#define APLIC_xMSICFGADDRH_HHXW_MASK 0x7
+#define APLIC_xMSICFGADDRH_HHXW_SHIFT 16
+#define APLIC_xMSICFGADDRH_LHXW_MASK 0xf
+#define APLIC_xMSICFGADDRH_LHXW_SHIFT 12
+#define APLIC_xMSICFGADDRH_BAPPN_MASK 0xfff
+
+#define APLIC_xMSICFGADDR_PPN_SHIFT 12
+
+#define APLIC_xMSICFGADDR_PPN_HART(__lhxs) \
+ ((1UL << (__lhxs)) - 1)
+
+#define APLIC_xMSICFGADDR_PPN_LHX_MASK(__lhxw) \
+ ((1UL << (__lhxw)) - 1)
+#define APLIC_xMSICFGADDR_PPN_LHX_SHIFT(__lhxs) \
+ ((__lhxs))
+#define APLIC_xMSICFGADDR_PPN_LHX(__lhxw, __lhxs) \
+ (APLIC_xMSICFGADDR_PPN_LHX_MASK(__lhxw) << \
+ APLIC_xMSICFGADDR_PPN_LHX_SHIFT(__lhxs))
+
+#define APLIC_xMSICFGADDR_PPN_HHX_MASK(__hhxw) \
+ ((1UL << (__hhxw)) - 1)
+#define APLIC_xMSICFGADDR_PPN_HHX_SHIFT(__hhxs) \
+ ((__hhxs) + APLIC_xMSICFGADDR_PPN_SHIFT)
+#define APLIC_xMSICFGADDR_PPN_HHX(__hhxw, __hhxs) \
+ (APLIC_xMSICFGADDR_PPN_HHX_MASK(__hhxw) << \
+ APLIC_xMSICFGADDR_PPN_HHX_SHIFT(__hhxs))
+
+#define APLIC_SETIP_BASE 0x1c00
+#define APLIC_SETIPNUM 0x1cdc
+
+#define APLIC_CLRIP_BASE 0x1d00
+#define APLIC_CLRIPNUM 0x1ddc
+
+#define APLIC_SETIE_BASE 0x1e00
+#define APLIC_SETIENUM 0x1edc
+
+#define APLIC_CLRIE_BASE 0x1f00
+#define APLIC_CLRIENUM 0x1fdc
+
+#define APLIC_SETIPNUM_LE 0x2000
+#define APLIC_SETIPNUM_BE 0x2004
+
+#define APLIC_TARGET_BASE 0x3004
+#define APLIC_TARGET_HART_IDX_SHIFT 18
+#define APLIC_TARGET_HART_IDX_MASK 0x3fff
+#define APLIC_TARGET_GUEST_IDX_SHIFT 12
+#define APLIC_TARGET_GUEST_IDX_MASK 0x3f
+#define APLIC_TARGET_IPRIO_MASK 0xff
+#define APLIC_TARGET_EIID_MASK 0x7ff
+
+#define APLIC_IDC_BASE 0x4000
+#define APLIC_IDC_SIZE 32
+
+#define APLIC_IDC_IDELIVERY 0x00
+
+#define APLIC_IDC_IFORCE 0x04
+
+#define APLIC_IDC_ITHRESHOLD 0x08
+
+#define APLIC_IDC_TOPI 0x18
+#define APLIC_IDC_TOPI_ID_SHIFT 16
+#define APLIC_IDC_TOPI_ID_MASK 0x3ff
+#define APLIC_IDC_TOPI_PRIO_MASK 0xff
+
+#define APLIC_IDC_CLAIMI 0x1c
+
+#define APLIC_DEFAULT_PRIORITY 1
+#define APLIC_DISABLE_IDELIVERY 0
+#define APLIC_ENABLE_IDELIVERY 1
+#define APLIC_DISABLE_ITHRESHOLD 1
+#define APLIC_ENABLE_ITHRESHOLD 0
+
+static void aplic_writel_msicfg(struct aplic_msicfg_data *msicfg,
+ void *msicfgaddr, void *msicfgaddrH)
+{
+ u32 val;
+ unsigned long base_ppn;
+
+ /* Check if MSI config is already locked */
+ if (readl(msicfgaddrH) & APLIC_xMSICFGADDRH_L)
+ return;
+
+ /* Compute the MSI base PPN */
+ base_ppn = msicfg->base_addr >> APLIC_xMSICFGADDR_PPN_SHIFT;
+ base_ppn &= ~APLIC_xMSICFGADDR_PPN_HART(msicfg->lhxs);
+ base_ppn &= ~APLIC_xMSICFGADDR_PPN_LHX(msicfg->lhxw, msicfg->lhxs);
+ base_ppn &= ~APLIC_xMSICFGADDR_PPN_HHX(msicfg->hhxw, msicfg->hhxs);
+
+ /* Write the lower MSI config register */
+ writel((u32)base_ppn, msicfgaddr);
+
+ /* Write the upper MSI config register */
+ val = (((u64)base_ppn) >> 32) &
+ APLIC_xMSICFGADDRH_BAPPN_MASK;
+ val |= (msicfg->lhxw & APLIC_xMSICFGADDRH_LHXW_MASK)
+ << APLIC_xMSICFGADDRH_LHXW_SHIFT;
+ val |= (msicfg->hhxw & APLIC_xMSICFGADDRH_HHXW_MASK)
+ << APLIC_xMSICFGADDRH_HHXW_SHIFT;
+ val |= (msicfg->lhxs & APLIC_xMSICFGADDRH_LHXS_MASK)
+ << APLIC_xMSICFGADDRH_LHXS_SHIFT;
+ val |= (msicfg->hhxs & APLIC_xMSICFGADDRH_HHXS_MASK)
+ << APLIC_xMSICFGADDRH_HHXS_SHIFT;
+ writel(val, msicfgaddrH);
+}
+
+static int aplic_check_msicfg(struct aplic_msicfg_data *msicfg)
+{
+ if (APLIC_xMSICFGADDRH_LHXS_MASK < msicfg->lhxs)
+ return SBI_EINVAL;
+
+ if (APLIC_xMSICFGADDRH_LHXW_MASK < msicfg->lhxw)
+ return SBI_EINVAL;
+
+ if (APLIC_xMSICFGADDRH_HHXS_MASK < msicfg->hhxs)
+ return SBI_EINVAL;
+
+ if (APLIC_xMSICFGADDRH_HHXW_MASK < msicfg->hhxw)
+ return SBI_EINVAL;
+
+ return 0;
+}
+
+int aplic_cold_irqchip_init(struct aplic_data *aplic)
+{
+ int rc;
+ u32 i, j, tmp;
+ struct sbi_domain_memregion reg;
+ struct aplic_delegate_data *deleg;
+ u32 first_deleg_irq, last_deleg_irq;
+
+ /* Sanity checks */
+ if (!aplic ||
+ !aplic->num_source || APLIC_MAX_SOURCE <= aplic->num_source ||
+ APLIC_MAX_IDC <= aplic->num_idc)
+ return SBI_EINVAL;
+ if (aplic->targets_mmode && aplic->has_msicfg_mmode) {
+ rc = aplic_check_msicfg(&aplic->msicfg_mmode);
+ if (rc)
+ return rc;
+ }
+ if (aplic->targets_mmode && aplic->has_msicfg_smode) {
+ rc = aplic_check_msicfg(&aplic->msicfg_smode);
+ if (rc)
+ return rc;
+ }
+
+ /* Set domain configuration to 0 */
+ writel(0, (void *)(aplic->addr + APLIC_DOMAINCFG));
+
+ /* Disable all interrupts */
+ for (i = 0; i <= aplic->num_source; i++)
+ writel(-1U, (void *)(aplic->addr + APLIC_CLRIE_BASE +
+ (i / 32) * sizeof(u32)));
+
+ /* Set interrupt type and priority for all interrupts */
+ for (i = 1; i <= aplic->num_source; i++) {
+ /* Set IRQ source configuration to 0 */
+ writel(0, (void *)(aplic->addr + APLIC_SOURCECFG_BASE +
+ (i - 1) * sizeof(u32)));
+ /* Set IRQ target hart index and priority to 1 */
+ writel(APLIC_DEFAULT_PRIORITY, (void *)(aplic->addr +
+ APLIC_TARGET_BASE +
+ (i - 1) * sizeof(u32)));
+ }
+
+ /* Configure IRQ delegation */
+ first_deleg_irq = -1U;
+ last_deleg_irq = 0;
+ for (i = 0; i < APLIC_MAX_DELEGATE; i++) {
+ deleg = &aplic->delegate[i];
+ if (!deleg->first_irq || !deleg->last_irq)
+ continue;
+ if (aplic->num_source < deleg->first_irq ||
+ aplic->num_source < deleg->last_irq)
+ continue;
+ if (APLIC_SOURCECFG_CHILDIDX_MASK < deleg->child_index)
+ continue;
+ if (deleg->first_irq > deleg->last_irq) {
+ tmp = deleg->first_irq;
+ deleg->first_irq = deleg->last_irq;
+ deleg->last_irq = tmp;
+ }
+ if (deleg->first_irq < first_deleg_irq)
+ first_deleg_irq = deleg->first_irq;
+ if (last_deleg_irq < deleg->last_irq)
+ last_deleg_irq = deleg->last_irq;
+ for (j = deleg->first_irq; j <= deleg->last_irq; j++)
+ writel(APLIC_SOURCECFG_D | deleg->child_index,
+ (void *)(aplic->addr + APLIC_SOURCECFG_BASE +
+ (j - 1) * sizeof(u32)));
+ }
+
+ /* Default initialization of IDC structures */
+ for (i = 0; i < aplic->num_idc; i++) {
+ writel(0, (void *)(aplic->addr + APLIC_IDC_BASE +
+ i * APLIC_IDC_SIZE + APLIC_IDC_IDELIVERY));
+ writel(0, (void *)(aplic->addr + APLIC_IDC_BASE +
+ i * APLIC_IDC_SIZE + APLIC_IDC_IFORCE));
+ writel(APLIC_DISABLE_ITHRESHOLD, (void *)(aplic->addr +
+ APLIC_IDC_BASE +
+ (i * APLIC_IDC_SIZE) +
+ APLIC_IDC_ITHRESHOLD));
+ }
+
+ /* MSI configuration */
+ if (aplic->targets_mmode && aplic->has_msicfg_mmode) {
+ aplic_writel_msicfg(&aplic->msicfg_mmode,
+ (void *)(aplic->addr + APLIC_MMSICFGADDR),
+ (void *)(aplic->addr + APLIC_MMSICFGADDRH));
+ }
+ if (aplic->targets_mmode && aplic->has_msicfg_smode) {
+ aplic_writel_msicfg(&aplic->msicfg_smode,
+ (void *)(aplic->addr + APLIC_SMSICFGADDR),
+ (void *)(aplic->addr + APLIC_SMSICFGADDRH));
+ }
+
+ /*
+ * Add APLIC region to the root domain if:
+ * 1) It targets M-mode of any HART directly or via MSIs
+ * 2) All interrupts are delegated to some child APLIC
+ */
+ if (aplic->targets_mmode ||
+ ((first_deleg_irq < last_deleg_irq) &&
+ (last_deleg_irq == aplic->num_source) &&
+ (first_deleg_irq == 1))) {
+ sbi_domain_memregion_init(aplic->addr, aplic->size,
+ SBI_DOMAIN_MEMREGION_MMIO, ®);
+ rc = sbi_domain_root_add_memregion(®);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}