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