1 /* 2 * Samsung exynos4210 Interrupt Combiner 3 * 4 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. 5 * All rights reserved. 6 * 7 * Evgeny Voevodin <e.voevodin@samsung.com> 8 * 9 * This program is free software; you can redistribute it and/or modify it 10 * under the terms of the GNU General Public License as published by the 11 * Free Software Foundation; either version 2 of the License, or (at your 12 * option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 * See the GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, see <http://www.gnu.org/licenses/>. 21 */ 22 23 /* 24 * Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines 25 * IRQ sources into groups and provides signal output to GIC from each group. It 26 * is driven by common mask and enable/disable logic. Take a note that not all 27 * IRQs are passed to GIC through Combiner. 28 */ 29 30 #include "qemu/osdep.h" 31 #include "hw/sysbus.h" 32 #include "migration/vmstate.h" 33 #include "qemu/module.h" 34 35 #include "hw/arm/exynos4210.h" 36 #include "hw/hw.h" 37 #include "hw/irq.h" 38 #include "hw/qdev-properties.h" 39 #include "qom/object.h" 40 41 //#define DEBUG_COMBINER 42 43 #ifdef DEBUG_COMBINER 44 #define DPRINTF(fmt, ...) \ 45 do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \ 46 ## __VA_ARGS__); } while (0) 47 #else 48 #define DPRINTF(fmt, ...) do {} while (0) 49 #endif 50 51 #define IIC_NGRP 64 /* Internal Interrupt Combiner 52 Groups number */ 53 #define IIC_NIRQ (IIC_NGRP * 8)/* Internal Interrupt Combiner 54 Interrupts number */ 55 #define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */ 56 #define IIC_REGSET_SIZE 0x41 57 58 /* 59 * State for each output signal of internal combiner 60 */ 61 typedef struct CombinerGroupState { 62 uint8_t src_mask; /* 1 - source enabled, 0 - disabled */ 63 uint8_t src_pending; /* Pending source interrupts before masking */ 64 } CombinerGroupState; 65 66 #define TYPE_EXYNOS4210_COMBINER "exynos4210.combiner" 67 OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210CombinerState, EXYNOS4210_COMBINER) 68 69 struct Exynos4210CombinerState { 70 SysBusDevice parent_obj; 71 72 MemoryRegion iomem; 73 74 struct CombinerGroupState group[IIC_NGRP]; 75 uint32_t reg_set[IIC_REGSET_SIZE]; 76 uint32_t icipsr[2]; 77 uint32_t external; /* 1 means that this combiner is external */ 78 79 qemu_irq output_irq[IIC_NGRP]; 80 }; 81 82 static const VMStateDescription vmstate_exynos4210_combiner_group_state = { 83 .name = "exynos4210.combiner.groupstate", 84 .version_id = 1, 85 .minimum_version_id = 1, 86 .fields = (VMStateField[]) { 87 VMSTATE_UINT8(src_mask, CombinerGroupState), 88 VMSTATE_UINT8(src_pending, CombinerGroupState), 89 VMSTATE_END_OF_LIST() 90 } 91 }; 92 93 static const VMStateDescription vmstate_exynos4210_combiner = { 94 .name = "exynos4210.combiner", 95 .version_id = 1, 96 .minimum_version_id = 1, 97 .fields = (VMStateField[]) { 98 VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0, 99 vmstate_exynos4210_combiner_group_state, CombinerGroupState), 100 VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState, 101 IIC_REGSET_SIZE), 102 VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2), 103 VMSTATE_UINT32(external, Exynos4210CombinerState), 104 VMSTATE_END_OF_LIST() 105 } 106 }; 107 108 static uint64_t 109 exynos4210_combiner_read(void *opaque, hwaddr offset, unsigned size) 110 { 111 struct Exynos4210CombinerState *s = 112 (struct Exynos4210CombinerState *)opaque; 113 uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and 114 get a start of corresponding group quad */ 115 uint32_t grp_quad_base_n; /* Base of group quad */ 116 uint32_t reg_n; /* Register number inside the quad */ 117 uint32_t val; 118 119 req_quad_base_n = offset >> 4; 120 grp_quad_base_n = req_quad_base_n << 2; 121 reg_n = (offset - (req_quad_base_n << 4)) >> 2; 122 123 if (req_quad_base_n >= IIC_NGRP) { 124 /* Read of ICIPSR register */ 125 return s->icipsr[reg_n]; 126 } 127 128 val = 0; 129 130 switch (reg_n) { 131 /* IISTR */ 132 case 2: 133 val |= s->group[grp_quad_base_n].src_pending; 134 val |= s->group[grp_quad_base_n + 1].src_pending << 8; 135 val |= s->group[grp_quad_base_n + 2].src_pending << 16; 136 val |= s->group[grp_quad_base_n + 3].src_pending << 24; 137 break; 138 /* IIMSR */ 139 case 3: 140 val |= s->group[grp_quad_base_n].src_mask & 141 s->group[grp_quad_base_n].src_pending; 142 val |= (s->group[grp_quad_base_n + 1].src_mask & 143 s->group[grp_quad_base_n + 1].src_pending) << 8; 144 val |= (s->group[grp_quad_base_n + 2].src_mask & 145 s->group[grp_quad_base_n + 2].src_pending) << 16; 146 val |= (s->group[grp_quad_base_n + 3].src_mask & 147 s->group[grp_quad_base_n + 3].src_pending) << 24; 148 break; 149 default: 150 if (offset >> 2 >= IIC_REGSET_SIZE) { 151 hw_error("exynos4210.combiner: overflow of reg_set by 0x" 152 TARGET_FMT_plx "offset\n", offset); 153 } 154 val = s->reg_set[offset >> 2]; 155 } 156 return val; 157 } 158 159 static void exynos4210_combiner_update(void *opaque, uint8_t group_n) 160 { 161 struct Exynos4210CombinerState *s = 162 (struct Exynos4210CombinerState *)opaque; 163 164 /* Send interrupt if needed */ 165 if (s->group[group_n].src_mask & s->group[group_n].src_pending) { 166 #ifdef DEBUG_COMBINER 167 if (group_n != 26) { 168 /* skip uart */ 169 DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); 170 } 171 #endif 172 173 /* Set Combiner interrupt pending status after masking */ 174 if (group_n >= 32) { 175 s->icipsr[1] |= 1 << (group_n - 32); 176 } else { 177 s->icipsr[0] |= 1 << group_n; 178 } 179 180 qemu_irq_raise(s->output_irq[group_n]); 181 } else { 182 #ifdef DEBUG_COMBINER 183 if (group_n != 26) { 184 /* skip uart */ 185 DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); 186 } 187 #endif 188 189 /* Set Combiner interrupt pending status after masking */ 190 if (group_n >= 32) { 191 s->icipsr[1] &= ~(1 << (group_n - 32)); 192 } else { 193 s->icipsr[0] &= ~(1 << group_n); 194 } 195 196 qemu_irq_lower(s->output_irq[group_n]); 197 } 198 } 199 200 static void exynos4210_combiner_write(void *opaque, hwaddr offset, 201 uint64_t val, unsigned size) 202 { 203 struct Exynos4210CombinerState *s = 204 (struct Exynos4210CombinerState *)opaque; 205 uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and 206 get a start of corresponding group quad */ 207 uint32_t grp_quad_base_n; /* Base of group quad */ 208 uint32_t reg_n; /* Register number inside the quad */ 209 210 req_quad_base_n = offset >> 4; 211 grp_quad_base_n = req_quad_base_n << 2; 212 reg_n = (offset - (req_quad_base_n << 4)) >> 2; 213 214 if (req_quad_base_n >= IIC_NGRP) { 215 hw_error("exynos4210.combiner: unallowed write access at offset 0x" 216 TARGET_FMT_plx "\n", offset); 217 return; 218 } 219 220 if (reg_n > 1) { 221 hw_error("exynos4210.combiner: unallowed write access at offset 0x" 222 TARGET_FMT_plx "\n", offset); 223 return; 224 } 225 226 if (offset >> 2 >= IIC_REGSET_SIZE) { 227 hw_error("exynos4210.combiner: overflow of reg_set by 0x" 228 TARGET_FMT_plx "offset\n", offset); 229 } 230 s->reg_set[offset >> 2] = val; 231 232 switch (reg_n) { 233 /* IIESR */ 234 case 0: 235 /* FIXME: what if irq is pending, allowed by mask, and we allow it 236 * again. Interrupt will rise again! */ 237 238 DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n", 239 s->external ? "EXT" : "INT", 240 grp_quad_base_n, 241 grp_quad_base_n + 1, 242 grp_quad_base_n + 2, 243 grp_quad_base_n + 3); 244 245 /* Enable interrupt sources */ 246 s->group[grp_quad_base_n].src_mask |= val & 0xFF; 247 s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8; 248 s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16; 249 s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24; 250 251 exynos4210_combiner_update(s, grp_quad_base_n); 252 exynos4210_combiner_update(s, grp_quad_base_n + 1); 253 exynos4210_combiner_update(s, grp_quad_base_n + 2); 254 exynos4210_combiner_update(s, grp_quad_base_n + 3); 255 break; 256 /* IIECR */ 257 case 1: 258 DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n", 259 s->external ? "EXT" : "INT", 260 grp_quad_base_n, 261 grp_quad_base_n + 1, 262 grp_quad_base_n + 2, 263 grp_quad_base_n + 3); 264 265 /* Disable interrupt sources */ 266 s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF); 267 s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8); 268 s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16); 269 s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24); 270 271 exynos4210_combiner_update(s, grp_quad_base_n); 272 exynos4210_combiner_update(s, grp_quad_base_n + 1); 273 exynos4210_combiner_update(s, grp_quad_base_n + 2); 274 exynos4210_combiner_update(s, grp_quad_base_n + 3); 275 break; 276 default: 277 hw_error("exynos4210.combiner: unallowed write access at offset 0x" 278 TARGET_FMT_plx "\n", offset); 279 break; 280 } 281 } 282 283 /* Get combiner group and bit from irq number */ 284 static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit) 285 { 286 *bit = irq - ((irq >> 3) << 3); 287 return irq >> 3; 288 } 289 290 /* Process a change in an external IRQ input. */ 291 static void exynos4210_combiner_handler(void *opaque, int irq, int level) 292 { 293 struct Exynos4210CombinerState *s = 294 (struct Exynos4210CombinerState *)opaque; 295 uint8_t bit_n, group_n; 296 297 group_n = get_combiner_group_and_bit(irq, &bit_n); 298 299 if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) { 300 DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT" 301 , group_n); 302 return; 303 } 304 305 if (level) { 306 s->group[group_n].src_pending |= 1 << bit_n; 307 } else { 308 s->group[group_n].src_pending &= ~(1 << bit_n); 309 } 310 311 exynos4210_combiner_update(s, group_n); 312 } 313 314 static void exynos4210_combiner_reset(DeviceState *d) 315 { 316 struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d; 317 318 memset(&s->group, 0, sizeof(s->group)); 319 memset(&s->reg_set, 0, sizeof(s->reg_set)); 320 321 s->reg_set[0xC0 >> 2] = 0x01010101; 322 s->reg_set[0xC4 >> 2] = 0x01010101; 323 s->reg_set[0xD0 >> 2] = 0x01010101; 324 s->reg_set[0xD4 >> 2] = 0x01010101; 325 } 326 327 static const MemoryRegionOps exynos4210_combiner_ops = { 328 .read = exynos4210_combiner_read, 329 .write = exynos4210_combiner_write, 330 .endianness = DEVICE_NATIVE_ENDIAN, 331 }; 332 333 /* 334 * Internal Combiner initialization. 335 */ 336 static void exynos4210_combiner_init(Object *obj) 337 { 338 DeviceState *dev = DEVICE(obj); 339 Exynos4210CombinerState *s = EXYNOS4210_COMBINER(obj); 340 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 341 unsigned int i; 342 343 /* Allocate general purpose input signals and connect a handler to each of 344 * them */ 345 qdev_init_gpio_in(dev, exynos4210_combiner_handler, IIC_NIRQ); 346 347 /* Connect SysBusDev irqs to device specific irqs */ 348 for (i = 0; i < IIC_NGRP; i++) { 349 sysbus_init_irq(sbd, &s->output_irq[i]); 350 } 351 352 memory_region_init_io(&s->iomem, obj, &exynos4210_combiner_ops, s, 353 "exynos4210-combiner", IIC_REGION_SIZE); 354 sysbus_init_mmio(sbd, &s->iomem); 355 } 356 357 static Property exynos4210_combiner_properties[] = { 358 DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0), 359 DEFINE_PROP_END_OF_LIST(), 360 }; 361 362 static void exynos4210_combiner_class_init(ObjectClass *klass, void *data) 363 { 364 DeviceClass *dc = DEVICE_CLASS(klass); 365 366 dc->reset = exynos4210_combiner_reset; 367 device_class_set_props(dc, exynos4210_combiner_properties); 368 dc->vmsd = &vmstate_exynos4210_combiner; 369 } 370 371 static const TypeInfo exynos4210_combiner_info = { 372 .name = TYPE_EXYNOS4210_COMBINER, 373 .parent = TYPE_SYS_BUS_DEVICE, 374 .instance_size = sizeof(Exynos4210CombinerState), 375 .instance_init = exynos4210_combiner_init, 376 .class_init = exynos4210_combiner_class_init, 377 }; 378 379 static void exynos4210_combiner_register_types(void) 380 { 381 type_register_static(&exynos4210_combiner_info); 382 } 383 384 type_init(exynos4210_combiner_register_types) 385