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 "hw/sysbus.h" 31 32 #include "hw/arm/exynos4210.h" 33 34 //#define DEBUG_COMBINER 35 36 #ifdef DEBUG_COMBINER 37 #define DPRINTF(fmt, ...) \ 38 do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \ 39 ## __VA_ARGS__); } while (0) 40 #else 41 #define DPRINTF(fmt, ...) do {} while (0) 42 #endif 43 44 #define IIC_NGRP 64 /* Internal Interrupt Combiner 45 Groups number */ 46 #define IIC_NIRQ (IIC_NGRP * 8)/* Internal Interrupt Combiner 47 Interrupts number */ 48 #define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */ 49 #define IIC_REGSET_SIZE 0x41 50 51 /* 52 * State for each output signal of internal combiner 53 */ 54 typedef struct CombinerGroupState { 55 uint8_t src_mask; /* 1 - source enabled, 0 - disabled */ 56 uint8_t src_pending; /* Pending source interrupts before masking */ 57 } CombinerGroupState; 58 59 typedef struct Exynos4210CombinerState { 60 SysBusDevice busdev; 61 MemoryRegion iomem; 62 63 struct CombinerGroupState group[IIC_NGRP]; 64 uint32_t reg_set[IIC_REGSET_SIZE]; 65 uint32_t icipsr[2]; 66 uint32_t external; /* 1 means that this combiner is external */ 67 68 qemu_irq output_irq[IIC_NGRP]; 69 } Exynos4210CombinerState; 70 71 static const VMStateDescription vmstate_exynos4210_combiner_group_state = { 72 .name = "exynos4210.combiner.groupstate", 73 .version_id = 1, 74 .minimum_version_id = 1, 75 .minimum_version_id_old = 1, 76 .fields = (VMStateField[]) { 77 VMSTATE_UINT8(src_mask, CombinerGroupState), 78 VMSTATE_UINT8(src_pending, CombinerGroupState), 79 VMSTATE_END_OF_LIST() 80 } 81 }; 82 83 static const VMStateDescription vmstate_exynos4210_combiner = { 84 .name = "exynos4210.combiner", 85 .version_id = 1, 86 .minimum_version_id = 1, 87 .minimum_version_id_old = 1, 88 .fields = (VMStateField[]) { 89 VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0, 90 vmstate_exynos4210_combiner_group_state, CombinerGroupState), 91 VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState, 92 IIC_REGSET_SIZE), 93 VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2), 94 VMSTATE_UINT32(external, Exynos4210CombinerState), 95 VMSTATE_END_OF_LIST() 96 } 97 }; 98 99 /* 100 * Get Combiner input GPIO into irqs structure 101 */ 102 void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev, 103 int ext) 104 { 105 int n; 106 int bit; 107 int max; 108 qemu_irq *irq; 109 110 max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ : 111 EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; 112 irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq; 113 114 /* 115 * Some IRQs of Int/External Combiner are going to two Combiners groups, 116 * so let split them. 117 */ 118 for (n = 0; n < max; n++) { 119 120 bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n); 121 122 switch (n) { 123 /* MDNIE_LCD1 INTG1 */ 124 case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ... 125 EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3): 126 irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), 127 irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]); 128 continue; 129 130 /* TMU INTG3 */ 131 case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4): 132 irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), 133 irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]); 134 continue; 135 136 /* LCD1 INTG12 */ 137 case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ... 138 EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3): 139 irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), 140 irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]); 141 continue; 142 143 /* Multi-Core Timer INTG12 */ 144 case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ... 145 EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8): 146 irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), 147 irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); 148 continue; 149 150 /* Multi-Core Timer INTG35 */ 151 case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ... 152 EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8): 153 irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), 154 irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); 155 continue; 156 157 /* Multi-Core Timer INTG51 */ 158 case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ... 159 EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8): 160 irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), 161 irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); 162 continue; 163 164 /* Multi-Core Timer INTG53 */ 165 case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ... 166 EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8): 167 irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), 168 irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); 169 continue; 170 } 171 172 irq[n] = qdev_get_gpio_in(dev, n); 173 } 174 } 175 176 static uint64_t 177 exynos4210_combiner_read(void *opaque, hwaddr offset, unsigned size) 178 { 179 struct Exynos4210CombinerState *s = 180 (struct Exynos4210CombinerState *)opaque; 181 uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and 182 get a start of corresponding group quad */ 183 uint32_t grp_quad_base_n; /* Base of group quad */ 184 uint32_t reg_n; /* Register number inside the quad */ 185 uint32_t val; 186 187 req_quad_base_n = offset >> 4; 188 grp_quad_base_n = req_quad_base_n << 2; 189 reg_n = (offset - (req_quad_base_n << 4)) >> 2; 190 191 if (req_quad_base_n >= IIC_NGRP) { 192 /* Read of ICIPSR register */ 193 return s->icipsr[reg_n]; 194 } 195 196 val = 0; 197 198 switch (reg_n) { 199 /* IISTR */ 200 case 2: 201 val |= s->group[grp_quad_base_n].src_pending; 202 val |= s->group[grp_quad_base_n + 1].src_pending << 8; 203 val |= s->group[grp_quad_base_n + 2].src_pending << 16; 204 val |= s->group[grp_quad_base_n + 3].src_pending << 24; 205 break; 206 /* IIMSR */ 207 case 3: 208 val |= s->group[grp_quad_base_n].src_mask & 209 s->group[grp_quad_base_n].src_pending; 210 val |= (s->group[grp_quad_base_n + 1].src_mask & 211 s->group[grp_quad_base_n + 1].src_pending) << 8; 212 val |= (s->group[grp_quad_base_n + 2].src_mask & 213 s->group[grp_quad_base_n + 2].src_pending) << 16; 214 val |= (s->group[grp_quad_base_n + 3].src_mask & 215 s->group[grp_quad_base_n + 3].src_pending) << 24; 216 break; 217 default: 218 if (offset >> 2 >= IIC_REGSET_SIZE) { 219 hw_error("exynos4210.combiner: overflow of reg_set by 0x" 220 TARGET_FMT_plx "offset\n", offset); 221 } 222 val = s->reg_set[offset >> 2]; 223 return 0; 224 } 225 return val; 226 } 227 228 static void exynos4210_combiner_update(void *opaque, uint8_t group_n) 229 { 230 struct Exynos4210CombinerState *s = 231 (struct Exynos4210CombinerState *)opaque; 232 233 /* Send interrupt if needed */ 234 if (s->group[group_n].src_mask & s->group[group_n].src_pending) { 235 #ifdef DEBUG_COMBINER 236 if (group_n != 26) { 237 /* skip uart */ 238 DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); 239 } 240 #endif 241 242 /* Set Combiner interrupt pending status after masking */ 243 if (group_n >= 32) { 244 s->icipsr[1] |= 1 << (group_n - 32); 245 } else { 246 s->icipsr[0] |= 1 << group_n; 247 } 248 249 qemu_irq_raise(s->output_irq[group_n]); 250 } else { 251 #ifdef DEBUG_COMBINER 252 if (group_n != 26) { 253 /* skip uart */ 254 DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); 255 } 256 #endif 257 258 /* Set Combiner interrupt pending status after masking */ 259 if (group_n >= 32) { 260 s->icipsr[1] &= ~(1 << (group_n - 32)); 261 } else { 262 s->icipsr[0] &= ~(1 << group_n); 263 } 264 265 qemu_irq_lower(s->output_irq[group_n]); 266 } 267 } 268 269 static void exynos4210_combiner_write(void *opaque, hwaddr offset, 270 uint64_t val, unsigned size) 271 { 272 struct Exynos4210CombinerState *s = 273 (struct Exynos4210CombinerState *)opaque; 274 uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and 275 get a start of corresponding group quad */ 276 uint32_t grp_quad_base_n; /* Base of group quad */ 277 uint32_t reg_n; /* Register number inside the quad */ 278 279 req_quad_base_n = offset >> 4; 280 grp_quad_base_n = req_quad_base_n << 2; 281 reg_n = (offset - (req_quad_base_n << 4)) >> 2; 282 283 if (req_quad_base_n >= IIC_NGRP) { 284 hw_error("exynos4210.combiner: unallowed write access at offset 0x" 285 TARGET_FMT_plx "\n", offset); 286 return; 287 } 288 289 if (reg_n > 1) { 290 hw_error("exynos4210.combiner: unallowed write access at offset 0x" 291 TARGET_FMT_plx "\n", offset); 292 return; 293 } 294 295 if (offset >> 2 >= IIC_REGSET_SIZE) { 296 hw_error("exynos4210.combiner: overflow of reg_set by 0x" 297 TARGET_FMT_plx "offset\n", offset); 298 } 299 s->reg_set[offset >> 2] = val; 300 301 switch (reg_n) { 302 /* IIESR */ 303 case 0: 304 /* FIXME: what if irq is pending, allowed by mask, and we allow it 305 * again. Interrupt will rise again! */ 306 307 DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n", 308 s->external ? "EXT" : "INT", 309 grp_quad_base_n, 310 grp_quad_base_n + 1, 311 grp_quad_base_n + 2, 312 grp_quad_base_n + 3); 313 314 /* Enable interrupt sources */ 315 s->group[grp_quad_base_n].src_mask |= val & 0xFF; 316 s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8; 317 s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16; 318 s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24; 319 320 exynos4210_combiner_update(s, grp_quad_base_n); 321 exynos4210_combiner_update(s, grp_quad_base_n + 1); 322 exynos4210_combiner_update(s, grp_quad_base_n + 2); 323 exynos4210_combiner_update(s, grp_quad_base_n + 3); 324 break; 325 /* IIECR */ 326 case 1: 327 DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n", 328 s->external ? "EXT" : "INT", 329 grp_quad_base_n, 330 grp_quad_base_n + 1, 331 grp_quad_base_n + 2, 332 grp_quad_base_n + 3); 333 334 /* Disable interrupt sources */ 335 s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF); 336 s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8); 337 s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16); 338 s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24); 339 340 exynos4210_combiner_update(s, grp_quad_base_n); 341 exynos4210_combiner_update(s, grp_quad_base_n + 1); 342 exynos4210_combiner_update(s, grp_quad_base_n + 2); 343 exynos4210_combiner_update(s, grp_quad_base_n + 3); 344 break; 345 default: 346 hw_error("exynos4210.combiner: unallowed write access at offset 0x" 347 TARGET_FMT_plx "\n", offset); 348 break; 349 } 350 } 351 352 /* Get combiner group and bit from irq number */ 353 static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit) 354 { 355 *bit = irq - ((irq >> 3) << 3); 356 return irq >> 3; 357 } 358 359 /* Process a change in an external IRQ input. */ 360 static void exynos4210_combiner_handler(void *opaque, int irq, int level) 361 { 362 struct Exynos4210CombinerState *s = 363 (struct Exynos4210CombinerState *)opaque; 364 uint8_t bit_n, group_n; 365 366 group_n = get_combiner_group_and_bit(irq, &bit_n); 367 368 if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) { 369 DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT" 370 , group_n); 371 return; 372 } 373 374 if (level) { 375 s->group[group_n].src_pending |= 1 << bit_n; 376 } else { 377 s->group[group_n].src_pending &= ~(1 << bit_n); 378 } 379 380 exynos4210_combiner_update(s, group_n); 381 } 382 383 static void exynos4210_combiner_reset(DeviceState *d) 384 { 385 struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d; 386 387 memset(&s->group, 0, sizeof(s->group)); 388 memset(&s->reg_set, 0, sizeof(s->reg_set)); 389 390 s->reg_set[0xC0 >> 2] = 0x01010101; 391 s->reg_set[0xC4 >> 2] = 0x01010101; 392 s->reg_set[0xD0 >> 2] = 0x01010101; 393 s->reg_set[0xD4 >> 2] = 0x01010101; 394 } 395 396 static const MemoryRegionOps exynos4210_combiner_ops = { 397 .read = exynos4210_combiner_read, 398 .write = exynos4210_combiner_write, 399 .endianness = DEVICE_NATIVE_ENDIAN, 400 }; 401 402 /* 403 * Internal Combiner initialization. 404 */ 405 static int exynos4210_combiner_init(SysBusDevice *dev) 406 { 407 unsigned int i; 408 struct Exynos4210CombinerState *s = 409 FROM_SYSBUS(struct Exynos4210CombinerState, dev); 410 411 /* Allocate general purpose input signals and connect a handler to each of 412 * them */ 413 qdev_init_gpio_in(&s->busdev.qdev, exynos4210_combiner_handler, IIC_NIRQ); 414 415 /* Connect SysBusDev irqs to device specific irqs */ 416 for (i = 0; i < IIC_NIRQ; i++) { 417 sysbus_init_irq(dev, &s->output_irq[i]); 418 } 419 420 memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_combiner_ops, s, 421 "exynos4210-combiner", IIC_REGION_SIZE); 422 sysbus_init_mmio(dev, &s->iomem); 423 424 return 0; 425 } 426 427 static Property exynos4210_combiner_properties[] = { 428 DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0), 429 DEFINE_PROP_END_OF_LIST(), 430 }; 431 432 static void exynos4210_combiner_class_init(ObjectClass *klass, void *data) 433 { 434 DeviceClass *dc = DEVICE_CLASS(klass); 435 SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); 436 437 k->init = exynos4210_combiner_init; 438 dc->reset = exynos4210_combiner_reset; 439 dc->props = exynos4210_combiner_properties; 440 dc->vmsd = &vmstate_exynos4210_combiner; 441 } 442 443 static const TypeInfo exynos4210_combiner_info = { 444 .name = "exynos4210.combiner", 445 .parent = TYPE_SYS_BUS_DEVICE, 446 .instance_size = sizeof(Exynos4210CombinerState), 447 .class_init = exynos4210_combiner_class_init, 448 }; 449 450 static void exynos4210_combiner_register_types(void) 451 { 452 type_register_static(&exynos4210_combiner_info); 453 } 454 455 type_init(exynos4210_combiner_register_types) 456