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