Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

irqchip/sun6i-r: Add wakeup support

Maintain bitmaps of wake-enabled IRQs and mux inputs, and program them
to the hardware during the syscore phase of suspend and shutdown. Then
restore the original set of enabled IRQs (only the NMI) during resume.

This serves two purposes. First, it lets power management firmware
running on the ARISC coprocessor know which wakeup sources Linux wants
to have enabled. That way, it can avoid turning them off when it shuts
down the remainder of the clock tree. Second, it preconfigures the
coprocessor's interrupt controller, so the firmware's wakeup logic
is as simple as waiting for an interrupt to arrive.

The suspend/resume logic is not conditional on PM_SLEEP because it is
identical to the init/shutdown logic. Wake IRQs may be enabled during
shutdown to allow powering the board back on. As an example, see
commit a5c5e50cce9d ("Input: gpio-keys - add shutdown callback").

Acked-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Samuel Holland <samuel@sholland.org>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20210118055040.21910-5-samuel@sholland.org

authored by

Samuel Holland and committed by
Marc Zyngier
7ab365f6 4e346146

+101 -6
+101 -6
drivers/irqchip/irq-sun6i-r.c
··· 39 39 * set of 128 mux bits. This requires a second set of top-level registers. 40 40 */ 41 41 42 + #include <linux/bitmap.h> 42 43 #include <linux/interrupt.h> 43 44 #include <linux/irq.h> 44 45 #include <linux/irqchip.h> ··· 47 46 #include <linux/of.h> 48 47 #include <linux/of_address.h> 49 48 #include <linux/of_irq.h> 49 + #include <linux/syscore_ops.h> 50 50 51 51 #include <dt-bindings/interrupt-controller/arm-gic.h> 52 52 ··· 69 67 #define SUN6I_NR_DIRECT_IRQS 16 70 68 #define SUN6I_NR_MUX_BITS 128 71 69 70 + struct sun6i_r_intc_variant { 71 + u32 first_mux_irq; 72 + u32 nr_mux_irqs; 73 + u32 mux_valid[BITS_TO_U32(SUN6I_NR_MUX_BITS)]; 74 + }; 75 + 72 76 static void __iomem *base; 73 77 static irq_hw_number_t nmi_hwirq; 78 + static DECLARE_BITMAP(wake_irq_enabled, SUN6I_NR_TOP_LEVEL_IRQS); 79 + static DECLARE_BITMAP(wake_mux_enabled, SUN6I_NR_MUX_BITS); 80 + static DECLARE_BITMAP(wake_mux_valid, SUN6I_NR_MUX_BITS); 74 81 75 82 static void sun6i_r_intc_ack_nmi(void) 76 83 { ··· 156 145 return irq_chip_set_parent_state(data, which, state); 157 146 } 158 147 148 + static int sun6i_r_intc_irq_set_wake(struct irq_data *data, unsigned int on) 149 + { 150 + unsigned long offset_from_nmi = data->hwirq - nmi_hwirq; 151 + 152 + if (offset_from_nmi < SUN6I_NR_DIRECT_IRQS) 153 + assign_bit(offset_from_nmi, wake_irq_enabled, on); 154 + else if (test_bit(data->hwirq, wake_mux_valid)) 155 + assign_bit(data->hwirq, wake_mux_enabled, on); 156 + else 157 + /* Not wakeup capable. */ 158 + return -EPERM; 159 + 160 + return 0; 161 + } 162 + 159 163 static struct irq_chip sun6i_r_intc_nmi_chip = { 160 164 .name = "sun6i-r-intc", 161 165 .irq_ack = sun6i_r_intc_nmi_ack, ··· 180 154 .irq_set_affinity = irq_chip_set_affinity_parent, 181 155 .irq_set_type = sun6i_r_intc_nmi_set_type, 182 156 .irq_set_irqchip_state = sun6i_r_intc_nmi_set_irqchip_state, 183 - .flags = IRQCHIP_SET_TYPE_MASKED | 184 - IRQCHIP_SKIP_SET_WAKE, 157 + .irq_set_wake = sun6i_r_intc_irq_set_wake, 158 + .flags = IRQCHIP_SET_TYPE_MASKED, 159 + }; 160 + 161 + static struct irq_chip sun6i_r_intc_wakeup_chip = { 162 + .name = "sun6i-r-intc", 163 + .irq_mask = irq_chip_mask_parent, 164 + .irq_unmask = irq_chip_unmask_parent, 165 + .irq_eoi = irq_chip_eoi_parent, 166 + .irq_set_affinity = irq_chip_set_affinity_parent, 167 + .irq_set_type = irq_chip_set_type_parent, 168 + .irq_set_wake = sun6i_r_intc_irq_set_wake, 169 + .flags = IRQCHIP_SET_TYPE_MASKED, 185 170 }; 186 171 187 172 static int sun6i_r_intc_domain_translate(struct irq_domain *domain, ··· 252 215 &sun6i_r_intc_nmi_chip, 0); 253 216 irq_set_handler(virq, handle_fasteoi_ack_irq); 254 217 } else { 255 - /* Only the NMI is currently supported. */ 256 - return -EINVAL; 218 + irq_domain_set_hwirq_and_chip(domain, virq, hwirq, 219 + &sun6i_r_intc_wakeup_chip, 0); 257 220 } 258 221 } 259 222 ··· 266 229 .free = irq_domain_free_irqs_common, 267 230 }; 268 231 232 + static int sun6i_r_intc_suspend(void) 233 + { 234 + u32 buf[BITS_TO_U32(max(SUN6I_NR_TOP_LEVEL_IRQS, SUN6I_NR_MUX_BITS))]; 235 + int i; 236 + 237 + /* Wake IRQs are enabled during system sleep and shutdown. */ 238 + bitmap_to_arr32(buf, wake_irq_enabled, SUN6I_NR_TOP_LEVEL_IRQS); 239 + for (i = 0; i < BITS_TO_U32(SUN6I_NR_TOP_LEVEL_IRQS); ++i) 240 + writel_relaxed(buf[i], base + SUN6I_IRQ_ENABLE(i)); 241 + bitmap_to_arr32(buf, wake_mux_enabled, SUN6I_NR_MUX_BITS); 242 + for (i = 0; i < BITS_TO_U32(SUN6I_NR_MUX_BITS); ++i) 243 + writel_relaxed(buf[i], base + SUN6I_MUX_ENABLE(i)); 244 + 245 + return 0; 246 + } 247 + 269 248 static void sun6i_r_intc_resume(void) 270 249 { 271 250 int i; ··· 292 239 writel_relaxed(0, base + SUN6I_IRQ_ENABLE(i)); 293 240 } 294 241 242 + static void sun6i_r_intc_shutdown(void) 243 + { 244 + sun6i_r_intc_suspend(); 245 + } 246 + 247 + static struct syscore_ops sun6i_r_intc_syscore_ops = { 248 + .suspend = sun6i_r_intc_suspend, 249 + .resume = sun6i_r_intc_resume, 250 + .shutdown = sun6i_r_intc_shutdown, 251 + }; 252 + 295 253 static int __init sun6i_r_intc_init(struct device_node *node, 296 - struct device_node *parent) 254 + struct device_node *parent, 255 + const struct sun6i_r_intc_variant *v) 297 256 { 298 257 struct irq_domain *domain, *parent_domain; 299 258 struct of_phandle_args nmi_parent; ··· 320 255 nmi_parent.args[2] != IRQ_TYPE_LEVEL_HIGH) 321 256 return -EINVAL; 322 257 nmi_hwirq = nmi_parent.args[1]; 258 + 259 + bitmap_set(wake_irq_enabled, v->first_mux_irq, v->nr_mux_irqs); 260 + bitmap_from_arr32(wake_mux_valid, v->mux_valid, SUN6I_NR_MUX_BITS); 323 261 324 262 parent_domain = irq_find_host(parent); 325 263 if (!parent_domain) { ··· 344 276 return -ENOMEM; 345 277 } 346 278 279 + register_syscore_ops(&sun6i_r_intc_syscore_ops); 280 + 347 281 sun6i_r_intc_ack_nmi(); 348 282 sun6i_r_intc_resume(); 349 283 350 284 return 0; 351 285 } 352 - IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_r_intc_init); 286 + 287 + static const struct sun6i_r_intc_variant sun6i_a31_r_intc_variant __initconst = { 288 + .first_mux_irq = 19, 289 + .nr_mux_irqs = 13, 290 + .mux_valid = { 0xffffffff, 0xfff80000, 0xffffffff, 0x0000000f }, 291 + }; 292 + 293 + static int __init sun6i_a31_r_intc_init(struct device_node *node, 294 + struct device_node *parent) 295 + { 296 + return sun6i_r_intc_init(node, parent, &sun6i_a31_r_intc_variant); 297 + } 298 + IRQCHIP_DECLARE(sun6i_a31_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_a31_r_intc_init); 299 + 300 + static const struct sun6i_r_intc_variant sun50i_h6_r_intc_variant __initconst = { 301 + .first_mux_irq = 21, 302 + .nr_mux_irqs = 16, 303 + .mux_valid = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }, 304 + }; 305 + 306 + static int __init sun50i_h6_r_intc_init(struct device_node *node, 307 + struct device_node *parent) 308 + { 309 + return sun6i_r_intc_init(node, parent, &sun50i_h6_r_intc_variant); 310 + } 311 + IRQCHIP_DECLARE(sun50i_h6_r_intc, "allwinner,sun50i-h6-r-intc", sun50i_h6_r_intc_init);