// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Western Digital Corporation or its affiliates.
* Copyright (C) 2022 Ventana Micro Systems Inc.
*/
#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/acpi.h>
#include <linux/cpu.h>
#include <linux/bitmap.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/smp.h>
#include <asm/hwcap.h>
#include "irq-riscv-imsic-state.h"
#define IMSIC_DISABLE_EIDELIVERY 0
#define IMSIC_ENABLE_EIDELIVERY 1
#define IMSIC_DISABLE_EITHRESHOLD 1
#define IMSIC_ENABLE_EITHRESHOLD 0
static inline void imsic_csr_write(unsigned long reg, unsigned long val)
{
csr_write(CSR_ISELECT, reg);
csr_write(CSR_IREG, val);
}
static inline unsigned long imsic_csr_read(unsigned long reg)
{
csr_write(CSR_ISELECT, reg);
return csr_read(CSR_IREG);
}
static inline unsigned long imsic_csr_read_clear(unsigned long reg, unsigned long val)
{
csr_write(CSR_ISELECT, reg);
return csr_read_clear(CSR_IREG, val);
}
static inline void imsic_csr_set(unsigned long reg, unsigned long val)
{
csr_write(CSR_ISELECT, reg);
csr_set(CSR_IREG, val);
}
static inline void imsic_csr_clear(unsigned long reg, unsigned long val)
{
csr_write(CSR_ISELECT, reg);
csr_clear(CSR_IREG, val);
}
struct imsic_priv *imsic;
const struct imsic_global_config *imsic_get_global_config(void)
{
return imsic ? &imsic->global : NULL;
}
EXPORT_SYMBOL_GPL(imsic_get_global_config);
static bool __imsic_eix_read_clear(unsigned long id, bool pend)
{
unsigned long isel, imask;
isel = id / BITS_PER_LONG;
isel *= BITS_PER_LONG / IMSIC_EIPx_BITS;
isel += pend ? IMSIC_EIP0 : IMSIC_EIE0;
imask = BIT(id & (__riscv_xlen - 1));
return !!(imsic_csr_read_clear(isel, imask) & imask);
}
static inline bool __imsic_id_read_clear_enabled(unsigned long id)
{
return __imsic_eix_read_clear(id, false);
}
static inline bool __imsic_id_read_clear_pending(unsigned long id)
{
return __imsic_eix_read_clear(id, true);
}
void __imsic_eix_update(unsigned long base_id, unsigned long num_id, bool pend, bool val)
{
unsigned long id = base_id, last_id = base_id + num_id;
unsigned long i, isel, ireg;
while (id < last_id) {
isel = id / BITS_PER_LONG;
isel *= BITS_PER_LONG / IMSIC_EIPx_BITS;
isel += pend ? IMSIC_EIP0 : IMSIC_EIE0;
/*
* Prepare the ID mask to be programmed in the
* IMSIC EIEx and EIPx registers. These registers
* are XLEN-wide and we must not touch IDs which
* are < base_id and >= (base_id + num_id).
*/
ireg = 0;
for (i = id & (__riscv_xlen - 1); id < last_id && i < __riscv_xlen; i++) {
ireg |= BIT(i);
id++;
}
/*
* The IMSIC EIEx and EIPx registers are indirectly
* accessed via using ISELECT and IREG CSRs so we
* need to access these CSRs without getting preempted.
*
* All existing users of this function call this
* function with local IRQs disabled so we don't
* need to do anything special here.
*/
if (val)
imsic_csr_set(isel, ireg);
else
imsic_csr_clear(isel, ireg);
}
}
static bool __imsic_local_sync(struct imsic_local_priv *lpriv)
{
struct imsic_local_config *tlocal, *mlocal;
struct imsic_vector *vec, *tvec, *mvec;
bool ret = true;
int i;
lockdep_assert_held(&lpriv->lock);
for_each_set_bit(i, lpriv->dirty_bitmap, imsic->global.nr_ids + 1) {
if (!i || (!imsic_noipi && i == IMSIC_IPI_ID))
goto skip;
vec = &lpriv->vectors[i];
if (READ_ONCE(vec->enable))
__imsic_id_set_enable(i);
else
__imsic_id_clear_enable(i);
/*
* Clear the previous vector pointer of the new vector only
* after the movement is complete on the old CPU.
*/
mvec = READ_ONCE(vec->move_prev);
if (mvec) {
/*
* If the old vector has