Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2014 Wind River Systems, Inc.
 * Copyright (c) 2020 Synopsys.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief ARCv2 Interrupt Unit device driver
 *
 * The ARCv2 interrupt unit has 16 allocated exceptions associated with
 * vectors 0 to 15 and 240 interrupts associated with vectors 16 to 255.
 * The interrupt unit is optional in the ARCv2-based processors. When
 * building a processor, you can configure the processor to include an
 * interrupt unit. The ARCv2 interrupt unit is highly programmable.
 */

#include <kernel.h>
#include <arch/cpu.h>
#include <device.h>
#include <init.h>

extern void *_VectorTable;

#ifdef CONFIG_PM_DEVICE
#include <pm/device.h>
#include <kernel_structs.h>

#ifdef CONFIG_ARC_SECURE_FIRMWARE
#undef _ARC_V2_IRQ_VECT_BASE
#define _ARC_V2_IRQ_VECT_BASE _ARC_V2_IRQ_VECT_BASE_S
#endif

struct arc_v2_irq_unit_ctx {
	uint32_t irq_ctrl; /* Interrupt Context Saving Control Register. */
	uint32_t irq_vect_base; /* Interrupt Vector Base. */

	/*
	 * IRQ configuration:
	 * - IRQ Priority:BIT(6):BIT(2)
	 * - IRQ Trigger:BIT(1)
	 * - IRQ Enable:BIT(0)
	 */
	uint8_t irq_config[CONFIG_NUM_IRQS - 16];
};
static struct arc_v2_irq_unit_ctx ctx;
#endif

/*
 * @brief Initialize the interrupt unit device driver
 *
 * Initializes the interrupt unit device driver and the device
 * itself.
 *
 * Interrupts are still locked at this point, so there is no need to protect
 * the window between a write to IRQ_SELECT and subsequent writes to the
 * selected IRQ's registers.
 *
 * @return 0 for success
 */
static int arc_v2_irq_unit_init(const struct device *unused)
{
	ARG_UNUSED(unused);
	int irq; /* the interrupt index */

	/* Interrupts from 0 to 15 are exceptions and they are ignored
	 * by IRQ auxiliary registers. For that reason we skip those
	 * values in this loop.
	 */
	for (irq = 16; irq < CONFIG_NUM_IRQS; irq++) {

		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_SELECT, irq);
#ifdef CONFIG_ARC_SECURE_FIRMWARE
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_PRIORITY,
			 (CONFIG_NUM_IRQ_PRIO_LEVELS-1) |
			 _ARC_V2_IRQ_PRIORITY_SECURE); /* lowest priority */
#else
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_PRIORITY,
			 (CONFIG_NUM_IRQ_PRIO_LEVELS-1)); /* lowest priority */
#endif
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_ENABLE, _ARC_V2_INT_DISABLE);
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_TRIGGER, _ARC_V2_INT_LEVEL);
	}

	return 0;
}

#ifdef CONFIG_PM_DEVICE

/*
 * @brief Suspend the interrupt unit device driver
 *
 * Suspends the interrupt unit device driver and the device
 * itself.
 *
 * @return 0 for success
 */
static int arc_v2_irq_unit_suspend(const struct device *dev)
{
	uint8_t irq;

	ARG_UNUSED(dev);

	/* Interrupts from 0 to 15 are exceptions and they are ignored
	 * by IRQ auxiliary registers. For that reason we skip those
	 * values in this loop.
	 */
	for (irq = 16U; irq < CONFIG_NUM_IRQS; irq++) {
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_SELECT, irq);
		ctx.irq_config[irq - 16] =
			z_arc_v2_aux_reg_read(_ARC_V2_IRQ_PRIORITY) << 2;
		ctx.irq_config[irq - 16] |=
			z_arc_v2_aux_reg_read(_ARC_V2_IRQ_TRIGGER) << 1;
		ctx.irq_config[irq - 16] |=
			z_arc_v2_aux_reg_read(_ARC_V2_IRQ_ENABLE);
	}

	ctx.irq_ctrl = z_arc_v2_aux_reg_read(_ARC_V2_AUX_IRQ_CTRL);
	ctx.irq_vect_base = z_arc_v2_aux_reg_read(_ARC_V2_IRQ_VECT_BASE);

	return 0;
}

/*
 * @brief Resume the interrupt unit device driver
 *
 * Resume the interrupt unit device driver and the device
 * itself.
 *
 * @return 0 for success
 */
static int arc_v2_irq_unit_resume(const struct device *dev)
{
	uint8_t irq;

	ARG_UNUSED(dev);

	/* Interrupts from 0 to 15 are exceptions and they are ignored
	 * by IRQ auxiliary registers. For that reason we skip those
	 * values in this loop.
	 */
	for (irq = 16U; irq < CONFIG_NUM_IRQS; irq++) {
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_SELECT, irq);
#ifdef CONFIG_ARC_SECURE_FIRMWARE
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_PRIORITY,
				ctx.irq_config[irq - 16] >> 2 |
				_ARC_V2_IRQ_PRIORITY_SECURE);
#else
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_PRIORITY,
				ctx.irq_config[irq - 16] >> 2);
#endif
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_TRIGGER,
				(ctx.irq_config[irq - 16] >> 1) & BIT(0));
		z_arc_v2_aux_reg_write(_ARC_V2_IRQ_ENABLE,
				ctx.irq_config[irq - 16] & BIT(0));
	}

#ifdef CONFIG_ARC_NORMAL_FIRMWARE
	/* \todo use sjli instruction to access irq_ctrl */
#else
	z_arc_v2_aux_reg_write(_ARC_V2_AUX_IRQ_CTRL, ctx.irq_ctrl);
#endif
	z_arc_v2_aux_reg_write(_ARC_V2_IRQ_VECT_BASE, ctx.irq_vect_base);

	return 0;
}

/*
 * @brief  Implement the driver control of interrupt unit
 *
 * The operation on interrupt unit requires interrupt lock.
 * The *context may include IN data or/and OUT data
 *
 * @return operation result
 */
static int arc_v2_irq_unit_device_ctrl(const struct device *dev,
				       enum pm_device_action action)
{
	int ret = 0;
	unsigned int key = arch_irq_lock();

	switch (action) {
	case PM_DEVICE_ACTION_SUSPEND:
		ret = arc_v2_irq_unit_suspend(dev);
		break;
	case PM_DEVICE_ACTION_RESUME:
		ret = arc_v2_irq_unit_resume(dev);
		break;
	default:
		ret = -ENOTSUP;
		break;
	}

	arch_irq_unlock(key);

	return ret;
}

SYS_DEVICE_DEFINE("arc_v2_irq_unit", arc_v2_irq_unit_init,
		  arc_v2_irq_unit_device_ctrl, PRE_KERNEL_1, 0);
#else
SYS_INIT(arc_v2_irq_unit_init, PRE_KERNEL_1, 0);
#endif   /* CONFIG_PM_DEVICE */