1 /***************************************************************************//**
2 * \file cyhal_irq_impl.c
3 *
4 * \brief
5 * Provides internal utility functions for working with interrupts on CAT1/CAT2.
6 *
7 ********************************************************************************
8 * \copyright
9 * Copyright 2018-2022 Cypress Semiconductor Corporation (an Infineon company) or
10 * an affiliate of Cypress Semiconductor Corporation
11 *
12 * SPDX-License-Identifier: Apache-2.0
13 *
14 * Licensed under the Apache License, Version 2.0 (the "License");
15 * you may not use this file except in compliance with the License.
16 * You may obtain a copy of the License at
17 *
18 *     http://www.apache.org/licenses/LICENSE-2.0
19 *
20 * Unless required by applicable law or agreed to in writing, software
21 * distributed under the License is distributed on an "AS IS" BASIS,
22 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 * See the License for the specific language governing permissions and
24 * limitations under the License.
25 *******************************************************************************/
26 
27 #include "cyhal_irq_impl.h"
28 
29 /**
30  * \addtogroup group_hal_impl_irq IRQ Muxing (Interrupt muxing)
31  * \ingroup group_hal_impl
32  * \{
33  * There are two situations where system interrupts do not correlate 1:1 to CPU interrupts.
34  * ("System interrupt" refers to a signal on a peripheral that can request an interrupt.
35  * "CPU interrupt" refers to an IRQ input line on the cpu). Each has a different potential
36  * impact on application behavior.
37  * - When running on the CM0+ on PSoC 6 S1 devices, there are 32 CPU interrupts available.
38  *   Each CPU interrupt can be associatedd with exactly one system interrupt. This means that
39  *   if the application attempts to initialize more than 32 HAL driver instances which require
40  *   unique interrupt handlers, the initialization will fail because there are no CPU interrupt
41  *   slots remaining.
42  * - When running on the CM0+ on all other CAT1 devices, or when running on the CM7 on CAT3 devices,
43  *   there are 8 CPU interrupts. Any system interrupt can be assigned to any CPU interrupt. In the event
44  *   that more than one system interrupt fires at the same time for a given CPU interrupt, the interrupts
45  *   are serviced in ascending numerical order (see the device datasheet for numeric IRQ values). This
46  *   means that the above error case where all CPU interrupts have been consumed does not apply. The HAL
47  *   automatically divides the system interrupts across the CPU interrupts.
48  *   However, it is only possible to assign one priority per CPU interrupt, even though the HAL APIs expose
49  *   the interrupt priority per system interrupt. The CAT1 HAL handles this situation by tracking the requested
50  *   priority for each system interrupt, then setting the priority for each CPU interrupt as the lowest
51  *   (i.e. most important) value requested across all of its associated system interrupts.
52  * \}
53  */
54 
55 #if _CYHAL_IRQ_LEGACY_M0
56 #include "cyhal_hwmgr.h"
57 
58 static const uint8_t _CYHAL_IRQ_COUNT_M0 = 32; /* Fixed in the IP definition */
59 #elif _CYHAL_IRQ_MUXING
60 #if defined(COMPONENT_CAT1A)
61 #include "cyhal_hwmgr.h" // TODO: This is a temporary workaround to assign 1:1 CPU to system mapping.
62 #endif
63 
64 static uint8_t _cyhal_system_irq_priority[((_CYHAL_IRQ_PRIO_BITS * _CYHAL_IRQ_COUNT) + 7) / 8]; /* Round up to nearest byte */
65 
_cyhal_system_irq_lookup_priority(cy_en_intr_t system_irq)66 uint8_t _cyhal_system_irq_lookup_priority(cy_en_intr_t system_irq)
67 {
68     uint16_t start_bit = ((uint16_t)system_irq) * _CYHAL_IRQ_PRIO_BITS;
69     uint8_t result = 0u;
70     for(uint16_t bit = start_bit; bit < start_bit + _CYHAL_IRQ_PRIO_BITS; ++bit)
71     {
72         uint16_t byte = bit / 8u;
73         uint8_t bit_in_byte = bit % 8u;
74         uint8_t offset = bit - start_bit;
75         uint8_t bit_value = ((_cyhal_system_irq_priority[byte] >> bit_in_byte) & 1u);
76         result |= (bit_value << offset);
77     }
78 
79     return result;
80 }
81 
_cyhal_system_irq_store_priority(cy_en_intr_t system_irq,uint8_t priority)82 void _cyhal_system_irq_store_priority(cy_en_intr_t system_irq, uint8_t priority)
83 {
84     uint16_t start_bit = ((uint16_t)system_irq) * _CYHAL_IRQ_PRIO_BITS;
85     for(uint16_t bit = start_bit; bit < start_bit + _CYHAL_IRQ_PRIO_BITS; ++bit)
86     {
87         uint16_t byte = bit / 8u;
88         uint8_t bit_in_byte = bit % 8u;
89         uint8_t offset = bit - start_bit;
90         uint8_t bit_value = priority & (1u << offset);
91         if(0u != bit_value)
92         {
93             _cyhal_system_irq_priority[byte] |= (1u << bit_in_byte);
94         }
95         else
96         {
97             _cyhal_system_irq_priority[byte] &= ~(1u << bit_in_byte);
98         }
99     }
100 }
101 
_cyhal_system_irq_lowest_priority(IRQn_Type cpu_irq)102 uint8_t _cyhal_system_irq_lowest_priority(IRQn_Type cpu_irq)
103 {
104     uint8_t lowest_priority = 0xFF;
105     for(uint32_t i = 0; i < _CYHAL_IRQ_COUNT; ++i)
106     {
107         /* No reverse mapping from CPU interrupt to system interrupt, we have to look
108          * through all of the system interrupts to see which ones correspond to this one */
109         if(cpu_irq == Cy_SysInt_GetNvicConnection((cy_en_intr_t)i))
110         {
111             uint8_t priority = _cyhal_system_irq_lookup_priority((cy_en_intr_t)i);
112             if(priority < lowest_priority)
113             {
114                 lowest_priority = priority;
115             }
116         }
117     }
118 
119     return lowest_priority;
120 }
121 
122 #endif
123 
124 #if (_CYHAL_IRQ_MUXING) && defined(COMPONENT_CAT1A) && (!_CYHAL_IRQ_LEGACY_M0) && !defined(CY_DEVICE_TVIIBE)
125 uint8_t _cpu_irq_tracker = 0u; // TODO: This is a temporary workaround to assign 1:1 CPU to system mapping.
126 #endif
127 
_cyhal_irq_register(_cyhal_system_irq_t system_intr,uint8_t intr_priority,cy_israddress irq_handler)128 cy_rslt_t _cyhal_irq_register(_cyhal_system_irq_t system_intr, uint8_t intr_priority, cy_israddress irq_handler)
129 {
130 #if _CYHAL_IRQ_MUXING
131     #if _CYHAL_IRQ_LEGACY_M0
132         /* Find a free CM0 slot */
133         IRQn_Type chosen_irq = unconnected_IRQn;
134         bool deepsleep_irq = (uint16_t)system_intr < CPUSS_DPSLP_IRQ_NR;
135         uint8_t max_cm0_irq = deepsleep_irq ? CPUSS_CM0_DPSLP_IRQ_NR : _CYHAL_IRQ_COUNT_M0;
136         for(int i = 0; i < max_cm0_irq; ++i)
137         {
138             IRQn_Type irqn = (IRQn_Type)i;
139             if(disconnected_IRQn == Cy_SysInt_GetInterruptSource(irqn))
140             {
141                 chosen_irq = irqn;
142                 break;
143             }
144         }
145         if(unconnected_IRQn == chosen_irq)
146         {
147             /* This is a highly device specific situation. "None free" is the closest matching generic error */
148             return CYHAL_HWMGR_RSLT_ERR_NONE_FREE;
149         }
150         cy_stc_sysint_t intr_cfg = { chosen_irq, system_intr, intr_priority };
151     #else /* CM0+ on CPUSSv2, or CM4/CM7 on CPUSSv2 with SYSTEM_IRQ_PRESENT */
152         /* Any system interrupt can go to any CPU interrupt. Map the system interrupts evenly across the CPU interrupts. Cluster
153          * adjacent system interrupts together to make it more likely that interrupts from the same FFB type, which are reasonably
154          * likely to want similar priorities. */
155         #if defined(CY_IP_M4CPUSS)
156         const uint8_t NUM_CPU_INTERRUPTS = 8u; /* Value fixed in the IP */
157         #else /* M7CPUSS */
158         #if (CY_CPU_CORTEX_M0P)
159         /* There are 8 general purpose interrupts on the CM0+, but per comments in Cy_SysInt_Init, the first two
160          * CPU interrupts  are reserved for ROM */
161         const uint8_t NUM_CPU_INTERRUPTS = 6u;
162         #else
163         const uint8_t NUM_CPU_INTERRUPTS = CPUSS_CM7_INT_NR;
164         #endif
165         #endif
166         // TODO: This is a temporary workaround to assign 1:1 CPU to system mapping.
167         // When the PDL allows more than one mapping, remove this logic.
168         #if defined(COMPONENT_CAT1A) && !defined(CY_DEVICE_TVIIBE)
169         CY_UNUSED_PARAMETER(system_intr);
170         cy_rslt_t status = CYHAL_HWMGR_RSLT_ERR_NONE_FREE;
171         uint8_t cpu_irq;
172         for (int idx = 0; idx < NUM_CPU_INTERRUPTS; idx++)
173         {
174             if ((_cpu_irq_tracker & (1 << idx)) == 0)
175             {
176                 cpu_irq = idx;
177                 _cpu_irq_tracker |= (1 << idx);
178                 status = CY_RSLT_SUCCESS;
179                 break;
180             }
181         }
182         if (status != CY_RSLT_SUCCESS)
183         {
184             return status;
185         }
186         uint32_t intr_src = (uint32_t)system_intr;
187         intr_src |= cpu_irq << CY_SYSINT_INTRSRC_MUXIRQ_SHIFT;
188         #if !(CY_CPU_CORTEX_M0P)
189         _cyhal_system_irq_store_priority(system_intr, intr_priority);
190         /* We can avoid a "lowest priority" search here, because the currently set value is the lowest except
191          * possibly for the one we're just now registering */
192         uint8_t existing_priority = NVIC_GetPriority((IRQn_Type)cpu_irq);
193         uint8_t lowest_priority = (existing_priority < intr_priority) ? existing_priority : intr_priority;
194         #endif /* !(CY_CPU_CORTEX_M0P) */
195         #else
196         /* We want to always round up to ensure that even the highest numbered system IRQ still falls into
197          * the top indexed CPU IRQ */
198         const uint8_t SYSTEM_IRQ_PER_CPU_IRQ = (_CYHAL_IRQ_COUNT + (NUM_CPU_INTERRUPTS - 1)) / NUM_CPU_INTERRUPTS;
199         uint8_t cpu_irq = ((uint32_t)system_intr) / SYSTEM_IRQ_PER_CPU_IRQ;
200         #endif
201         #if defined(CY_IP_M7CPUSS) || (defined(CY_IP_M4CPUSS) && (CY_IP_M4CPUSS_VERSION == 2u) && (CPUSS_SYSTEM_IRQ_PRESENT))
202         #if (CY_CPU_CORTEX_M0P)
203         cpu_irq += 2u; /* Handle offset from interrupts reserved for ROM */
204         #endif
205         /* For CM7, the upper 20 bits of the intrSrc field are the CPU interrupt number */
206         uint32_t intr_src = (uint32_t)system_intr;
207         intr_src |= cpu_irq << CY_SYSINT_INTRSRC_MUXIRQ_SHIFT;
208         _cyhal_system_irq_store_priority(system_intr, intr_priority);
209         /* Find the lowest priority between all of the configured interrupts
210            and the interrupt that is currently being registered */
211         uint8_t existing_priority = _cyhal_system_irq_lowest_priority((IRQn_Type)cpu_irq);
212         uint8_t lowest_priority = (existing_priority < intr_priority) ? existing_priority : intr_priority;
213             #if (CY_CPU_CORTEX_M0P) && defined(CY_IP_M4CPUSS)
214                 CY_UNUSED_PARAMETER(lowest_priority);
215                 cy_stc_sysint_t intr_cfg = { (IRQn_Type)cpu_irq, system_intr, intr_priority };
216             #elif defined(CY_IP_M7CPUSS)
217                 cy_stc_sysint_t intr_cfg = { intr_src, lowest_priority };
218             #else
219                 cy_stc_sysint_t intr_cfg = { (IRQn_Type)intr_src, lowest_priority };
220             #endif
221         #else /* M4CPUSS and CY_IP_M4CPUSS_VERSION is 1 */
222             #if (CY_CPU_CORTEX_M0P)
223                 cy_stc_sysint_t intr_cfg = { (IRQn_Type)cpu_irq, system_intr, intr_priority };
224             #else
225                 cy_stc_sysint_t intr_cfg = { (IRQn_Type)intr_src, lowest_priority };
226             #endif
227         #endif
228     #endif
229 #else
230     cy_stc_sysint_t intr_cfg = { system_intr, intr_priority };
231 #endif
232     return Cy_SysInt_Init(&intr_cfg, irq_handler);
233 }
234 
235 #if _CYHAL_IRQ_LEGACY_M0
_cyhal_irq_find_cm0(cy_en_intr_t system_irq)236 IRQn_Type _cyhal_irq_find_cm0(cy_en_intr_t system_irq)
237 {
238     /* Need to look through all of the enabled CPU interrupts and see if any of them
239      * is connected to this IRQn */
240     for(int i = 0; i < _CYHAL_IRQ_COUNT_M0; ++i)
241     {
242         IRQn_Type irqn = (IRQn_Type)i;
243         if(system_irq == Cy_SysInt_GetInterruptSource(irqn))
244         {
245             return irqn;
246         }
247     }
248 
249     return unconnected_IRQn;
250 }
251 #endif
252