1/* 2 * Copyright 2011 IBM Corporation. 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 7 * 2 of the License, or (at your option) any later version. 8 * 9 */ 10#include <linux/types.h> 11#include <linux/kernel.h> 12#include <linux/irq.h> 13#include <linux/smp.h> 14#include <linux/interrupt.h> 15#include <linux/cpu.h> 16#include <linux/of.h> 17 18#include <asm/smp.h> 19#include <asm/irq.h> 20#include <asm/errno.h> 21#include <asm/xics.h> 22#include <asm/io.h> 23#include <asm/hvcall.h> 24 25static inline unsigned int icp_hv_get_xirr(unsigned char cppr) 26{ 27 unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; 28 long rc; 29 unsigned int ret = XICS_IRQ_SPURIOUS; 30 31 rc = plpar_hcall(H_XIRR, retbuf, cppr); 32 if (rc == H_SUCCESS) { 33 ret = (unsigned int)retbuf[0]; 34 } else { 35 pr_err("%s: bad return code xirr cppr=0x%x returned %ld\n", 36 __func__, cppr, rc); 37 WARN_ON_ONCE(1); 38 } 39 40 return ret; 41} 42 43static inline void icp_hv_set_cppr(u8 value) 44{ 45 long rc = plpar_hcall_norets(H_CPPR, value); 46 if (rc != H_SUCCESS) { 47 pr_err("%s: bad return code cppr cppr=0x%x returned %ld\n", 48 __func__, value, rc); 49 WARN_ON_ONCE(1); 50 } 51} 52 53static inline void icp_hv_set_xirr(unsigned int value) 54{ 55 long rc = plpar_hcall_norets(H_EOI, value); 56 if (rc != H_SUCCESS) { 57 pr_err("%s: bad return code eoi xirr=0x%x returned %ld\n", 58 __func__, value, rc); 59 WARN_ON_ONCE(1); 60 icp_hv_set_cppr(value >> 24); 61 } 62} 63 64static inline void icp_hv_set_qirr(int n_cpu , u8 value) 65{ 66 int hw_cpu = get_hard_smp_processor_id(n_cpu); 67 long rc; 68 69 /* Make sure all previous accesses are ordered before IPI sending */ 70 mb(); 71 rc = plpar_hcall_norets(H_IPI, hw_cpu, value); 72 if (rc != H_SUCCESS) { 73 pr_err("%s: bad return code qirr cpu=%d hw_cpu=%d mfrr=0x%x " 74 "returned %ld\n", __func__, n_cpu, hw_cpu, value, rc); 75 WARN_ON_ONCE(1); 76 } 77} 78 79static void icp_hv_eoi(struct irq_data *d) 80{ 81 unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); 82 83 iosync(); 84 icp_hv_set_xirr((xics_pop_cppr() << 24) | hw_irq); 85} 86 87static void icp_hv_teardown_cpu(void) 88{ 89 int cpu = smp_processor_id(); 90 91 /* Clear any pending IPI */ 92 icp_hv_set_qirr(cpu, 0xff); 93} 94 95static void icp_hv_flush_ipi(void) 96{ 97 /* We take the ipi irq but and never return so we 98 * need to EOI the IPI, but want to leave our priority 0 99 * 100 * should we check all the other interrupts too? 101 * should we be flagging idle loop instead? 102 * or creating some task to be scheduled? 103 */ 104 105 icp_hv_set_xirr((0x00 << 24) | XICS_IPI); 106} 107 108static unsigned int icp_hv_get_irq(void) 109{ 110 unsigned int xirr = icp_hv_get_xirr(xics_cppr_top()); 111 unsigned int vec = xirr & 0x00ffffff; 112 unsigned int irq; 113 114 if (vec == XICS_IRQ_SPURIOUS) 115 return NO_IRQ; 116 117 irq = irq_find_mapping(xics_host, vec); 118 if (likely(irq != NO_IRQ)) { 119 xics_push_cppr(vec); 120 return irq; 121 } 122 123 /* We don't have a linux mapping, so have rtas mask it. */ 124 xics_mask_unknown_vec(vec); 125 126 /* We might learn about it later, so EOI it */ 127 icp_hv_set_xirr(xirr); 128 129 return NO_IRQ; 130} 131 132static void icp_hv_set_cpu_priority(unsigned char cppr) 133{ 134 xics_set_base_cppr(cppr); 135 icp_hv_set_cppr(cppr); 136 iosync(); 137} 138 139#ifdef CONFIG_SMP 140 141static void icp_hv_cause_ipi(int cpu, unsigned long data) 142{ 143 icp_hv_set_qirr(cpu, IPI_PRIORITY); 144} 145 146static irqreturn_t icp_hv_ipi_action(int irq, void *dev_id) 147{ 148 int cpu = smp_processor_id(); 149 150 icp_hv_set_qirr(cpu, 0xff); 151 152 return smp_ipi_demux(); 153} 154 155#endif /* CONFIG_SMP */ 156 157static const struct icp_ops icp_hv_ops = { 158 .get_irq = icp_hv_get_irq, 159 .eoi = icp_hv_eoi, 160 .set_priority = icp_hv_set_cpu_priority, 161 .teardown_cpu = icp_hv_teardown_cpu, 162 .flush_ipi = icp_hv_flush_ipi, 163#ifdef CONFIG_SMP 164 .ipi_action = icp_hv_ipi_action, 165 .cause_ipi = icp_hv_cause_ipi, 166#endif 167}; 168 169int icp_hv_init(void) 170{ 171 struct device_node *np; 172 173 np = of_find_compatible_node(NULL, NULL, "ibm,ppc-xicp"); 174 if (!np) 175 np = of_find_node_by_type(NULL, 176 "PowerPC-External-Interrupt-Presentation"); 177 if (!np) 178 return -ENODEV; 179 180 icp_ops = &icp_hv_ops; 181 182 return 0; 183} 184 185