root/drivers/staging/media/atomisp/pci/hive_isp_css_common/host/irq.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Support for Intel Camera Imaging ISP subsystem.
 * Copyright (c) 2010-2015, Intel Corporation.
 */

#include "assert_support.h"
#include "irq.h"

#ifndef __INLINE_GP_DEVICE__
#define __INLINE_GP_DEVICE__
#endif
#include "gp_device.h"  /* _REG_GP_IRQ_REQUEST_ADDR */

static inline void irq_wait_for_write_complete(
    const irq_ID_t              ID);

static inline bool any_irq_channel_enabled(
    const irq_ID_t                              ID);

static inline irq_ID_t virq_get_irq_id(const enum virq_id irq_ID,
                                       unsigned int *channel_ID);

#ifndef __INLINE_IRQ__
#include "irq_private.h"
#endif /* __INLINE_IRQ__ */

static unsigned short IRQ_N_CHANNEL[N_IRQ_ID] = {
        IRQ0_ID_N_CHANNEL,
        IRQ1_ID_N_CHANNEL,
        IRQ2_ID_N_CHANNEL,
        IRQ3_ID_N_CHANNEL
};

static unsigned short IRQ_N_ID_OFFSET[N_IRQ_ID + 1] = {
        IRQ0_ID_OFFSET,
        IRQ1_ID_OFFSET,
        IRQ2_ID_OFFSET,
        IRQ3_ID_OFFSET,
        IRQ_END_OFFSET
};

static enum virq_id IRQ_NESTING_ID[N_IRQ_ID] = {
        N_virq_id,
        virq_ifmt,
        virq_isys,
        virq_isel
};

void irq_clear_all(
    const irq_ID_t                              ID)
{
        hrt_data        mask = 0xFFFFFFFF;

        assert(ID < N_IRQ_ID);
        assert(IRQ_N_CHANNEL[ID] <= HRT_DATA_WIDTH);

        if (IRQ_N_CHANNEL[ID] < HRT_DATA_WIDTH) {
                mask = ~((~(hrt_data)0) >> IRQ_N_CHANNEL[ID]);
        }

        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_CLEAR_REG_IDX, mask);
        return;
}

/*
 * Do we want the user to be able to set the signalling method ?
 */
void irq_enable_channel(
    const irq_ID_t                              ID,
    const unsigned int                  irq_id)
{
        unsigned int mask = irq_reg_load(ID,
                                         _HRT_IRQ_CONTROLLER_MASK_REG_IDX);
        unsigned int enable = irq_reg_load(ID,
                                           _HRT_IRQ_CONTROLLER_ENABLE_REG_IDX);
        unsigned int edge_in = irq_reg_load(ID,
                                            _HRT_IRQ_CONTROLLER_EDGE_REG_IDX);
        unsigned int me = 1U << irq_id;

        assert(ID < N_IRQ_ID);
        assert(irq_id < IRQ_N_CHANNEL[ID]);

        mask |= me;
        enable |= me;
        edge_in |= me;  /* rising edge */

        /* to avoid mishaps configuration must follow the following order */

        /* mask this interrupt */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_MASK_REG_IDX, mask & ~me);
        /* rising edge at input */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_EDGE_REG_IDX, edge_in);
        /* enable interrupt to output */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_ENABLE_REG_IDX, enable);
        /* clear current irq only */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_CLEAR_REG_IDX, me);
        /* unmask interrupt from input */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_MASK_REG_IDX, mask);

        irq_wait_for_write_complete(ID);

        return;
}

void irq_enable_pulse(
    const irq_ID_t      ID,
    bool                        pulse)
{
        unsigned int edge_out = 0x0;

        if (pulse) {
                edge_out = 0xffffffff;
        }
        /* output is given as edge, not pulse */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_EDGE_NOT_PULSE_REG_IDX, edge_out);
        return;
}

void irq_disable_channel(
    const irq_ID_t                              ID,
    const unsigned int                  irq_id)
{
        unsigned int mask = irq_reg_load(ID,
                                         _HRT_IRQ_CONTROLLER_MASK_REG_IDX);
        unsigned int enable = irq_reg_load(ID,
                                           _HRT_IRQ_CONTROLLER_ENABLE_REG_IDX);
        unsigned int me = 1U << irq_id;

        assert(ID < N_IRQ_ID);
        assert(irq_id < IRQ_N_CHANNEL[ID]);

        mask &= ~me;
        enable &= ~me;

        /* enable interrupt to output */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_ENABLE_REG_IDX, enable);
        /* unmask interrupt from input */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_MASK_REG_IDX, mask);
        /* clear current irq only */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_CLEAR_REG_IDX, me);

        irq_wait_for_write_complete(ID);

        return;
}

enum hrt_isp_css_irq_status irq_get_channel_id(
    const irq_ID_t                              ID,
    unsigned int                                *irq_id)
{
        unsigned int irq_status = irq_reg_load(ID,
                                               _HRT_IRQ_CONTROLLER_STATUS_REG_IDX);
        unsigned int idx;
        enum hrt_isp_css_irq_status status = hrt_isp_css_irq_status_success;

        assert(ID < N_IRQ_ID);
        assert(irq_id);

        /* find the first irq bit */
        for (idx = 0; idx < IRQ_N_CHANNEL[ID]; idx++) {
                if (irq_status & (1U << idx))
                        break;
        }
        if (idx == IRQ_N_CHANNEL[ID])
                return hrt_isp_css_irq_status_error;

        /* now check whether there are more bits set */
        if (irq_status != (1U << idx))
                status = hrt_isp_css_irq_status_more_irqs;

        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_CLEAR_REG_IDX, 1U << idx);

        irq_wait_for_write_complete(ID);

        if (irq_id)
                *irq_id = (unsigned int)idx;

        return status;
}

static const hrt_address IRQ_REQUEST_ADDR[N_IRQ_SW_CHANNEL_ID] = {
        _REG_GP_IRQ_REQUEST0_ADDR,
        _REG_GP_IRQ_REQUEST1_ADDR
};

void irq_raise(
    const irq_ID_t                              ID,
    const irq_sw_channel_id_t   irq_id)
{
        hrt_address             addr;

        OP___assert(ID == IRQ0_ID);
        OP___assert(IRQ_BASE[ID] != (hrt_address)-1);
        OP___assert(irq_id < N_IRQ_SW_CHANNEL_ID);

        (void)ID;

        addr = IRQ_REQUEST_ADDR[irq_id];
        /* The SW IRQ pins are remapped to offset zero */
        gp_device_reg_store(GP_DEVICE0_ID,
                            (unsigned int)addr, 1);
        gp_device_reg_store(GP_DEVICE0_ID,
                            (unsigned int)addr, 0);
        return;
}

bool any_virq_signal(void)
{
        unsigned int irq_status = irq_reg_load(IRQ0_ID,
                                               _HRT_IRQ_CONTROLLER_STATUS_REG_IDX);

        return (irq_status != 0);
}

void cnd_virq_enable_channel(
    const enum virq_id                          irq_ID,
    const bool                                  en)
{
        irq_ID_t                i;
        unsigned int    channel_ID;
        irq_ID_t                ID = virq_get_irq_id(irq_ID, &channel_ID);

        assert(ID < N_IRQ_ID);

        for (i = IRQ1_ID; i < N_IRQ_ID; i++) {
                /* It is not allowed to enable the pin of a nested IRQ directly */
                assert(irq_ID != IRQ_NESTING_ID[i]);
        }

        if (en) {
                irq_enable_channel(ID, channel_ID);
                if (IRQ_NESTING_ID[ID] != N_virq_id) {
                        /* Single level nesting, otherwise we'd need to recurse */
                        irq_enable_channel(IRQ0_ID, IRQ_NESTING_ID[ID]);
                }
        } else {
                irq_disable_channel(ID, channel_ID);
                if ((IRQ_NESTING_ID[ID] != N_virq_id) && !any_irq_channel_enabled(ID)) {
                        /* Only disable the top if the nested ones are empty */
                        irq_disable_channel(IRQ0_ID, IRQ_NESTING_ID[ID]);
                }
        }
        return;
}

void virq_clear_all(void)
{
        irq_ID_t        irq_id;

        for (irq_id = (irq_ID_t)0; irq_id < N_IRQ_ID; irq_id++) {
                irq_clear_all(irq_id);
        }
        return;
}

enum hrt_isp_css_irq_status
virq_get_channel_signals(struct virq_info *irq_info)
{
        enum hrt_isp_css_irq_status irq_status = hrt_isp_css_irq_status_error;
        irq_ID_t ID;

        assert(irq_info);

        for (ID = (irq_ID_t)0 ; ID < N_IRQ_ID; ID++) {
                if (any_irq_channel_enabled(ID)) {
                        hrt_data        irq_data = irq_reg_load(ID,
                                                            _HRT_IRQ_CONTROLLER_STATUS_REG_IDX);

                        if (irq_data != 0) {
                                /* The error condition is an IRQ pulse received with no IRQ status written */
                                irq_status = hrt_isp_css_irq_status_success;
                        }

                        irq_info->irq_status_reg[ID] |= irq_data;

                        irq_reg_store(ID,
                                      _HRT_IRQ_CONTROLLER_CLEAR_REG_IDX, irq_data);

                        irq_wait_for_write_complete(ID);
                }
        }

        return irq_status;
}

void virq_clear_info(struct virq_info *irq_info)
{
        irq_ID_t ID;

        assert(irq_info);

        for (ID = (irq_ID_t)0 ; ID < N_IRQ_ID; ID++) {
                irq_info->irq_status_reg[ID] = 0;
        }
        return;
}

enum hrt_isp_css_irq_status virq_get_channel_id(
    enum virq_id                                        *irq_id)
{
        unsigned int irq_status = irq_reg_load(IRQ0_ID,
                                               _HRT_IRQ_CONTROLLER_STATUS_REG_IDX);
        unsigned int idx;
        enum hrt_isp_css_irq_status status = hrt_isp_css_irq_status_success;
        irq_ID_t ID;

        assert(irq_id);

        /* find the first irq bit on device 0 */
        for (idx = 0; idx < IRQ_N_CHANNEL[IRQ0_ID]; idx++) {
                if (irq_status & (1U << idx))
                        break;
        }

        if (idx == IRQ_N_CHANNEL[IRQ0_ID]) {
                return hrt_isp_css_irq_status_error;
        }

        /* Check whether there are more bits set on device 0 */
        if (irq_status != (1U << idx)) {
                status = hrt_isp_css_irq_status_more_irqs;
        }

        /* Check whether we have an IRQ on one of the nested devices */
        for (ID = N_IRQ_ID - 1 ; ID > (irq_ID_t)0; ID--) {
                if (IRQ_NESTING_ID[ID] == (enum virq_id)idx) {
                        break;
                }
        }

        /* If we have a nested IRQ, load that state, discard the device 0 state */
        if (ID != IRQ0_ID) {
                irq_status = irq_reg_load(ID,
                                          _HRT_IRQ_CONTROLLER_STATUS_REG_IDX);
                /* find the first irq bit on device "id" */
                for (idx = 0; idx < IRQ_N_CHANNEL[ID]; idx++) {
                        if (irq_status & (1U << idx))
                                break;
                }

                if (idx == IRQ_N_CHANNEL[ID]) {
                        return hrt_isp_css_irq_status_error;
                }

                /* Alternatively check whether there are more bits set on this device */
                if (irq_status != (1U << idx)) {
                        status = hrt_isp_css_irq_status_more_irqs;
                } else {
                        /* If this device is empty, clear the state on device 0 */
                        irq_reg_store(IRQ0_ID,
                                      _HRT_IRQ_CONTROLLER_CLEAR_REG_IDX, 1U << IRQ_NESTING_ID[ID]);
                }
        } /* if (ID != IRQ0_ID) */

        /* Here we proceed to clear the IRQ on detected device, if no nested IRQ, this is device 0 */
        irq_reg_store(ID,
                      _HRT_IRQ_CONTROLLER_CLEAR_REG_IDX, 1U << idx);

        irq_wait_for_write_complete(ID);

        idx += IRQ_N_ID_OFFSET[ID];
        if (irq_id)
                *irq_id = (enum virq_id)idx;

        return status;
}

static inline void irq_wait_for_write_complete(
    const irq_ID_t              ID)
{
        assert(ID < N_IRQ_ID);
        assert(IRQ_BASE[ID] != (hrt_address)-1);
        (void)ia_css_device_load_uint32(IRQ_BASE[ID] +
                                        _HRT_IRQ_CONTROLLER_ENABLE_REG_IDX * sizeof(hrt_data));
}

static inline bool any_irq_channel_enabled(
    const irq_ID_t                              ID)
{
        hrt_data        en_reg;

        assert(ID < N_IRQ_ID);

        en_reg = irq_reg_load(ID,
                              _HRT_IRQ_CONTROLLER_ENABLE_REG_IDX);

        return (en_reg != 0);
}

static inline irq_ID_t virq_get_irq_id(
    const enum virq_id          irq_ID,
    unsigned int                *channel_ID)
{
        irq_ID_t ID;

        assert(channel_ID);

        for (ID = (irq_ID_t)0 ; ID < N_IRQ_ID; ID++) {
                if (irq_ID < IRQ_N_ID_OFFSET[ID + 1]) {
                        break;
                }
        }

        *channel_ID = (unsigned int)irq_ID - IRQ_N_ID_OFFSET[ID];

        return ID;
}