| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Support for Intel Camera Imaging ISP subsystem. |
| * Copyright (c) 2010-2015, Intel Corporation. |
| * |
| * 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, 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. |
| */ |
| |
| #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; |
| } |
| |
| void irq_controller_get_state(const irq_ID_t ID, |
| struct irq_controller_state *state) |
| { |
| assert(ID < N_IRQ_ID); |
| assert(state); |
| |
| state->irq_edge = irq_reg_load(ID, |
| _HRT_IRQ_CONTROLLER_EDGE_REG_IDX); |
| state->irq_mask = irq_reg_load(ID, |
| _HRT_IRQ_CONTROLLER_MASK_REG_IDX); |
| state->irq_status = irq_reg_load(ID, |
| _HRT_IRQ_CONTROLLER_STATUS_REG_IDX); |
| state->irq_enable = irq_reg_load(ID, |
| _HRT_IRQ_CONTROLLER_ENABLE_REG_IDX); |
| state->irq_level_not_pulse = irq_reg_load(ID, |
| _HRT_IRQ_CONTROLLER_EDGE_NOT_PULSE_REG_IDX); |
| 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; |
| } |