|
|
1.1 ! root 1: /* ! 2: * Samsung exynos4210 Pulse Width Modulation Timer ! 3: * ! 4: * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. ! 5: * All rights reserved. ! 6: * ! 7: * Evgeny Voevodin <[email protected]> ! 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: #include "sysbus.h" ! 24: #include "qemu-timer.h" ! 25: #include "qemu-common.h" ! 26: #include "ptimer.h" ! 27: ! 28: #include "exynos4210.h" ! 29: ! 30: //#define DEBUG_PWM ! 31: ! 32: #ifdef DEBUG_PWM ! 33: #define DPRINTF(fmt, ...) \ ! 34: do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \ ! 35: ## __VA_ARGS__); } while (0) ! 36: #else ! 37: #define DPRINTF(fmt, ...) do {} while (0) ! 38: #endif ! 39: ! 40: #define EXYNOS4210_PWM_TIMERS_NUM 5 ! 41: #define EXYNOS4210_PWM_REG_MEM_SIZE 0x50 ! 42: ! 43: #define TCFG0 0x0000 ! 44: #define TCFG1 0x0004 ! 45: #define TCON 0x0008 ! 46: #define TCNTB0 0x000C ! 47: #define TCMPB0 0x0010 ! 48: #define TCNTO0 0x0014 ! 49: #define TCNTB1 0x0018 ! 50: #define TCMPB1 0x001C ! 51: #define TCNTO1 0x0020 ! 52: #define TCNTB2 0x0024 ! 53: #define TCMPB2 0x0028 ! 54: #define TCNTO2 0x002C ! 55: #define TCNTB3 0x0030 ! 56: #define TCMPB3 0x0034 ! 57: #define TCNTO3 0x0038 ! 58: #define TCNTB4 0x003C ! 59: #define TCNTO4 0x0040 ! 60: #define TINT_CSTAT 0x0044 ! 61: ! 62: #define TCNTB(x) (0xC * (x)) ! 63: #define TCMPB(x) (0xC * (x) + 1) ! 64: #define TCNTO(x) (0xC * (x) + 2) ! 65: ! 66: #define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x)) ! 67: #define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x)))) ! 68: ! 69: /* ! 70: * Attention! Timer4 doesn't have OUTPUT_INVERTER, ! 71: * so Auto Reload bit is not accessible by macros! ! 72: */ ! 73: #define TCON_TIMER_BASE(x) (((x) ? 1 : 0) * 4 + 4 * (x)) ! 74: #define TCON_TIMER_START(x) (1 << (TCON_TIMER_BASE(x) + 0)) ! 75: #define TCON_TIMER_MANUAL_UPD(x) (1 << (TCON_TIMER_BASE(x) + 1)) ! 76: #define TCON_TIMER_OUTPUT_INV(x) (1 << (TCON_TIMER_BASE(x) + 2)) ! 77: #define TCON_TIMER_AUTO_RELOAD(x) (1 << (TCON_TIMER_BASE(x) + 3)) ! 78: #define TCON_TIMER4_AUTO_RELOAD (1 << 22) ! 79: ! 80: #define TINT_CSTAT_STATUS(x) (1 << (5 + (x))) ! 81: #define TINT_CSTAT_ENABLE(x) (1 << (x)) ! 82: ! 83: /* timer struct */ ! 84: typedef struct { ! 85: uint32_t id; /* timer id */ ! 86: qemu_irq irq; /* local timer irq */ ! 87: uint32_t freq; /* timer frequency */ ! 88: ! 89: /* use ptimer.c to represent count down timer */ ! 90: ptimer_state *ptimer; /* timer */ ! 91: ! 92: /* registers */ ! 93: uint32_t reg_tcntb; /* counter register buffer */ ! 94: uint32_t reg_tcmpb; /* compare register buffer */ ! 95: ! 96: struct Exynos4210PWMState *parent; ! 97: ! 98: } Exynos4210PWM; ! 99: ! 100: ! 101: typedef struct Exynos4210PWMState { ! 102: SysBusDevice busdev; ! 103: MemoryRegion iomem; ! 104: ! 105: uint32_t reg_tcfg[2]; ! 106: uint32_t reg_tcon; ! 107: uint32_t reg_tint_cstat; ! 108: ! 109: Exynos4210PWM timer[EXYNOS4210_PWM_TIMERS_NUM]; ! 110: ! 111: } Exynos4210PWMState; ! 112: ! 113: /*** VMState ***/ ! 114: static const VMStateDescription vmstate_exynos4210_pwm = { ! 115: .name = "exynos4210.pwm.pwm", ! 116: .version_id = 1, ! 117: .minimum_version_id = 1, ! 118: .minimum_version_id_old = 1, ! 119: .fields = (VMStateField[]) { ! 120: VMSTATE_UINT32(id, Exynos4210PWM), ! 121: VMSTATE_UINT32(freq, Exynos4210PWM), ! 122: VMSTATE_PTIMER(ptimer, Exynos4210PWM), ! 123: VMSTATE_UINT32(reg_tcntb, Exynos4210PWM), ! 124: VMSTATE_UINT32(reg_tcmpb, Exynos4210PWM), ! 125: VMSTATE_END_OF_LIST() ! 126: } ! 127: }; ! 128: ! 129: static const VMStateDescription vmstate_exynos4210_pwm_state = { ! 130: .name = "exynos4210.pwm", ! 131: .version_id = 1, ! 132: .minimum_version_id = 1, ! 133: .minimum_version_id_old = 1, ! 134: .fields = (VMStateField[]) { ! 135: VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2), ! 136: VMSTATE_UINT32(reg_tcon, Exynos4210PWMState), ! 137: VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState), ! 138: VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState, ! 139: EXYNOS4210_PWM_TIMERS_NUM, 0, ! 140: vmstate_exynos4210_pwm, Exynos4210PWM), ! 141: VMSTATE_END_OF_LIST() ! 142: } ! 143: }; ! 144: ! 145: /* ! 146: * PWM update frequency ! 147: */ ! 148: static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id) ! 149: { ! 150: uint32_t freq; ! 151: freq = s->timer[id].freq; ! 152: if (id > 1) { ! 153: s->timer[id].freq = 24000000 / ! 154: ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) * ! 155: (GET_DIVIDER(s->reg_tcfg[1], id))); ! 156: } else { ! 157: s->timer[id].freq = 24000000 / ! 158: ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) * ! 159: (GET_DIVIDER(s->reg_tcfg[1], id))); ! 160: } ! 161: ! 162: if (freq != s->timer[id].freq) { ! 163: ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq); ! 164: DPRINTF("freq=%dHz\n", s->timer[id].freq); ! 165: } ! 166: } ! 167: ! 168: /* ! 169: * Counter tick handler ! 170: */ ! 171: static void exynos4210_pwm_tick(void *opaque) ! 172: { ! 173: Exynos4210PWM *s = (Exynos4210PWM *)opaque; ! 174: Exynos4210PWMState *p = (Exynos4210PWMState *)s->parent; ! 175: uint32_t id = s->id; ! 176: bool cmp; ! 177: ! 178: DPRINTF("timer %d tick\n", id); ! 179: ! 180: /* set irq status */ ! 181: p->reg_tint_cstat |= TINT_CSTAT_STATUS(id); ! 182: ! 183: /* raise IRQ */ ! 184: if (p->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) { ! 185: DPRINTF("timer %d IRQ\n", id); ! 186: qemu_irq_raise(p->timer[id].irq); ! 187: } ! 188: ! 189: /* reload timer */ ! 190: if (id != 4) { ! 191: cmp = p->reg_tcon & TCON_TIMER_AUTO_RELOAD(id); ! 192: } else { ! 193: cmp = p->reg_tcon & TCON_TIMER4_AUTO_RELOAD; ! 194: } ! 195: ! 196: if (cmp) { ! 197: DPRINTF("auto reload timer %d count to %x\n", id, ! 198: p->timer[id].reg_tcntb); ! 199: ptimer_set_count(p->timer[id].ptimer, p->timer[id].reg_tcntb); ! 200: ptimer_run(p->timer[id].ptimer, 1); ! 201: } else { ! 202: /* stop timer, set status to STOP, see Basic Timer Operation */ ! 203: p->reg_tcon = ~TCON_TIMER_START(id); ! 204: ptimer_stop(p->timer[id].ptimer); ! 205: } ! 206: } ! 207: ! 208: /* ! 209: * PWM Read ! 210: */ ! 211: static uint64_t exynos4210_pwm_read(void *opaque, target_phys_addr_t offset, ! 212: unsigned size) ! 213: { ! 214: Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; ! 215: uint32_t value = 0; ! 216: int index; ! 217: ! 218: switch (offset) { ! 219: case TCFG0: case TCFG1: ! 220: index = (offset - TCFG0) >> 2; ! 221: value = s->reg_tcfg[index]; ! 222: break; ! 223: ! 224: case TCON: ! 225: value = s->reg_tcon; ! 226: break; ! 227: ! 228: case TCNTB0: case TCNTB1: ! 229: case TCNTB2: case TCNTB3: case TCNTB4: ! 230: index = (offset - TCNTB0) / 0xC; ! 231: value = s->timer[index].reg_tcntb; ! 232: break; ! 233: ! 234: case TCMPB0: case TCMPB1: ! 235: case TCMPB2: case TCMPB3: ! 236: index = (offset - TCMPB0) / 0xC; ! 237: value = s->timer[index].reg_tcmpb; ! 238: break; ! 239: ! 240: case TCNTO0: case TCNTO1: ! 241: case TCNTO2: case TCNTO3: case TCNTO4: ! 242: index = (offset == TCNTO4) ? 4 : (offset - TCNTO0) / 0xC; ! 243: value = ptimer_get_count(s->timer[index].ptimer); ! 244: break; ! 245: ! 246: case TINT_CSTAT: ! 247: value = s->reg_tint_cstat; ! 248: break; ! 249: ! 250: default: ! 251: fprintf(stderr, ! 252: "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n", ! 253: offset); ! 254: break; ! 255: } ! 256: return value; ! 257: } ! 258: ! 259: /* ! 260: * PWM Write ! 261: */ ! 262: static void exynos4210_pwm_write(void *opaque, target_phys_addr_t offset, ! 263: uint64_t value, unsigned size) ! 264: { ! 265: Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; ! 266: int index; ! 267: uint32_t new_val; ! 268: int i; ! 269: ! 270: switch (offset) { ! 271: case TCFG0: case TCFG1: ! 272: index = (offset - TCFG0) >> 2; ! 273: s->reg_tcfg[index] = value; ! 274: ! 275: /* update timers frequencies */ ! 276: for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { ! 277: exynos4210_pwm_update_freq(s, s->timer[i].id); ! 278: } ! 279: break; ! 280: ! 281: case TCON: ! 282: for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { ! 283: if ((value & TCON_TIMER_MANUAL_UPD(i)) > ! 284: (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) { ! 285: /* ! 286: * TCNTB and TCMPB are loaded into TCNT and TCMP. ! 287: * Update timers. ! 288: */ ! 289: ! 290: /* this will start timer to run, this ok, because ! 291: * during processing start bit timer will be stopped ! 292: * if needed */ ! 293: ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb); ! 294: DPRINTF("set timer %d count to %x\n", i, ! 295: s->timer[i].reg_tcntb); ! 296: } ! 297: ! 298: if ((value & TCON_TIMER_START(i)) > ! 299: (s->reg_tcon & TCON_TIMER_START(i))) { ! 300: /* changed to start */ ! 301: ptimer_run(s->timer[i].ptimer, 1); ! 302: DPRINTF("run timer %d\n", i); ! 303: } ! 304: ! 305: if ((value & TCON_TIMER_START(i)) < ! 306: (s->reg_tcon & TCON_TIMER_START(i))) { ! 307: /* changed to stop */ ! 308: ptimer_stop(s->timer[i].ptimer); ! 309: DPRINTF("stop timer %d\n", i); ! 310: } ! 311: } ! 312: s->reg_tcon = value; ! 313: break; ! 314: ! 315: case TCNTB0: case TCNTB1: ! 316: case TCNTB2: case TCNTB3: case TCNTB4: ! 317: index = (offset - TCNTB0) / 0xC; ! 318: s->timer[index].reg_tcntb = value; ! 319: break; ! 320: ! 321: case TCMPB0: case TCMPB1: ! 322: case TCMPB2: case TCMPB3: ! 323: index = (offset - TCMPB0) / 0xC; ! 324: s->timer[index].reg_tcmpb = value; ! 325: break; ! 326: ! 327: case TINT_CSTAT: ! 328: new_val = (s->reg_tint_cstat & 0x3E0) + (0x1F & value); ! 329: new_val &= ~(0x3E0 & value); ! 330: ! 331: for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { ! 332: if ((new_val & TINT_CSTAT_STATUS(i)) < ! 333: (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) { ! 334: qemu_irq_lower(s->timer[i].irq); ! 335: } ! 336: } ! 337: ! 338: s->reg_tint_cstat = new_val; ! 339: break; ! 340: ! 341: default: ! 342: fprintf(stderr, ! 343: "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n", ! 344: offset); ! 345: break; ! 346: ! 347: } ! 348: } ! 349: ! 350: /* ! 351: * Set default values to timer fields and registers ! 352: */ ! 353: static void exynos4210_pwm_reset(DeviceState *d) ! 354: { ! 355: Exynos4210PWMState *s = (Exynos4210PWMState *)d; ! 356: int i; ! 357: s->reg_tcfg[0] = 0x0101; ! 358: s->reg_tcfg[1] = 0x0; ! 359: s->reg_tcon = 0; ! 360: s->reg_tint_cstat = 0; ! 361: for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { ! 362: s->timer[i].reg_tcmpb = 0; ! 363: s->timer[i].reg_tcntb = 0; ! 364: ! 365: exynos4210_pwm_update_freq(s, s->timer[i].id); ! 366: ptimer_stop(s->timer[i].ptimer); ! 367: } ! 368: } ! 369: ! 370: static const MemoryRegionOps exynos4210_pwm_ops = { ! 371: .read = exynos4210_pwm_read, ! 372: .write = exynos4210_pwm_write, ! 373: .endianness = DEVICE_NATIVE_ENDIAN, ! 374: }; ! 375: ! 376: /* ! 377: * PWM timer initialization ! 378: */ ! 379: static int exynos4210_pwm_init(SysBusDevice *dev) ! 380: { ! 381: Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev); ! 382: int i; ! 383: QEMUBH *bh; ! 384: ! 385: for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { ! 386: bh = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]); ! 387: sysbus_init_irq(dev, &s->timer[i].irq); ! 388: s->timer[i].ptimer = ptimer_init(bh); ! 389: s->timer[i].id = i; ! 390: s->timer[i].parent = s; ! 391: } ! 392: ! 393: memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, "exynos4210-pwm", ! 394: EXYNOS4210_PWM_REG_MEM_SIZE); ! 395: sysbus_init_mmio(dev, &s->iomem); ! 396: ! 397: return 0; ! 398: } ! 399: ! 400: static void exynos4210_pwm_class_init(ObjectClass *klass, void *data) ! 401: { ! 402: DeviceClass *dc = DEVICE_CLASS(klass); ! 403: SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); ! 404: ! 405: k->init = exynos4210_pwm_init; ! 406: dc->reset = exynos4210_pwm_reset; ! 407: dc->vmsd = &vmstate_exynos4210_pwm_state; ! 408: } ! 409: ! 410: static TypeInfo exynos4210_pwm_info = { ! 411: .name = "exynos4210.pwm", ! 412: .parent = TYPE_SYS_BUS_DEVICE, ! 413: .instance_size = sizeof(Exynos4210PWMState), ! 414: .class_init = exynos4210_pwm_class_init, ! 415: }; ! 416: ! 417: static void exynos4210_pwm_register_types(void) ! 418: { ! 419: type_register_static(&exynos4210_pwm_info); ! 420: } ! 421: ! 422: type_init(exynos4210_pwm_register_types)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.