~vcs-imports/qemu/git

« back to all changes in this revision

Viewing changes to hw/omap.c

  • Committer: balrog
  • Date: 2007-11-03 12:44:02 UTC
  • Revision ID: git-v1:5c1c390feac308327eee376845d90355b16615b9
Implement OMAP on-chip RTC (Linux guest date/time now matches with host).


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3515 c046a42c-6fe2-441c-8c8c-71466251a162

Show diffs side-by-side

added added

removed removed

Lines of Context:
4018
4018
    return s->bus;
4019
4019
}
4020
4020
 
 
4021
/* Real-time Clock module */
 
4022
struct omap_rtc_s {
 
4023
    target_phys_addr_t base;
 
4024
    qemu_irq irq;
 
4025
    qemu_irq alarm;
 
4026
    QEMUTimer *clk;
 
4027
 
 
4028
    uint8_t interrupts;
 
4029
    uint8_t status;
 
4030
    int16_t comp_reg;
 
4031
    int running;
 
4032
    int pm_am;
 
4033
    int auto_comp;
 
4034
    int round;
 
4035
    struct tm *(*convert)(const time_t *timep, struct tm *result);
 
4036
    struct tm alarm_tm;
 
4037
    time_t alarm_ti;
 
4038
 
 
4039
    struct tm current_tm;
 
4040
    time_t ti;
 
4041
    uint64_t tick;
 
4042
};
 
4043
 
 
4044
static void omap_rtc_interrupts_update(struct omap_rtc_s *s)
 
4045
{
 
4046
    qemu_set_irq(s->alarm, (s->status >> 6) & 1);
 
4047
}
 
4048
 
 
4049
static void omap_rtc_alarm_update(struct omap_rtc_s *s)
 
4050
{
 
4051
    s->alarm_ti = mktime(&s->alarm_tm);
 
4052
    if (s->alarm_ti == -1)
 
4053
        printf("%s: conversion failed\n", __FUNCTION__);
 
4054
}
 
4055
 
 
4056
static inline uint8_t omap_rtc_bcd(int num)
 
4057
{
 
4058
    return ((num / 10) << 4) | (num % 10);
 
4059
}
 
4060
 
 
4061
static inline int omap_rtc_bin(uint8_t num)
 
4062
{
 
4063
    return (num & 15) + 10 * (num >> 4);
 
4064
}
 
4065
 
 
4066
static uint32_t omap_rtc_read(void *opaque, target_phys_addr_t addr)
 
4067
{
 
4068
    struct omap_rtc_s *s = (struct omap_rtc_s *) opaque;
 
4069
    int offset = addr - s->base;
 
4070
    uint8_t i;
 
4071
 
 
4072
    switch (offset) {
 
4073
    case 0x00:  /* SECONDS_REG */
 
4074
        return omap_rtc_bcd(s->current_tm.tm_sec);
 
4075
 
 
4076
    case 0x04:  /* MINUTES_REG */
 
4077
        return omap_rtc_bcd(s->current_tm.tm_min);
 
4078
 
 
4079
    case 0x08:  /* HOURS_REG */
 
4080
        if (s->pm_am)
 
4081
            return ((s->current_tm.tm_hour > 11) << 7) |
 
4082
                    omap_rtc_bcd(((s->current_tm.tm_hour - 1) % 12) + 1);
 
4083
        else
 
4084
            return omap_rtc_bcd(s->current_tm.tm_hour);
 
4085
 
 
4086
    case 0x0c:  /* DAYS_REG */
 
4087
        return omap_rtc_bcd(s->current_tm.tm_mday);
 
4088
 
 
4089
    case 0x10:  /* MONTHS_REG */
 
4090
        return omap_rtc_bcd(s->current_tm.tm_mon + 1);
 
4091
 
 
4092
    case 0x14:  /* YEARS_REG */
 
4093
        return omap_rtc_bcd(s->current_tm.tm_year % 100);
 
4094
 
 
4095
    case 0x18:  /* WEEK_REG */
 
4096
        return s->current_tm.tm_wday;
 
4097
 
 
4098
    case 0x20:  /* ALARM_SECONDS_REG */
 
4099
        return omap_rtc_bcd(s->alarm_tm.tm_sec);
 
4100
 
 
4101
    case 0x24:  /* ALARM_MINUTES_REG */
 
4102
        return omap_rtc_bcd(s->alarm_tm.tm_min);
 
4103
 
 
4104
    case 0x28:  /* ALARM_HOURS_REG */
 
4105
        if (s->pm_am)
 
4106
            return ((s->alarm_tm.tm_hour > 11) << 7) |
 
4107
                    omap_rtc_bcd(((s->alarm_tm.tm_hour - 1) % 12) + 1);
 
4108
        else
 
4109
            return omap_rtc_bcd(s->alarm_tm.tm_hour);
 
4110
 
 
4111
    case 0x2c:  /* ALARM_DAYS_REG */
 
4112
        return omap_rtc_bcd(s->alarm_tm.tm_mday);
 
4113
 
 
4114
    case 0x30:  /* ALARM_MONTHS_REG */
 
4115
        return omap_rtc_bcd(s->alarm_tm.tm_mon + 1);
 
4116
 
 
4117
    case 0x34:  /* ALARM_YEARS_REG */
 
4118
        return omap_rtc_bcd(s->alarm_tm.tm_year % 100);
 
4119
 
 
4120
    case 0x40:  /* RTC_CTRL_REG */
 
4121
        return (s->pm_am << 3) | (s->auto_comp << 2) |
 
4122
                (s->round << 1) | s->running;
 
4123
 
 
4124
    case 0x44:  /* RTC_STATUS_REG */
 
4125
        i = s->status;
 
4126
        s->status &= ~0x3d;
 
4127
        return i;
 
4128
 
 
4129
    case 0x48:  /* RTC_INTERRUPTS_REG */
 
4130
        return s->interrupts;
 
4131
 
 
4132
    case 0x4c:  /* RTC_COMP_LSB_REG */
 
4133
        return ((uint16_t) s->comp_reg) & 0xff;
 
4134
 
 
4135
    case 0x50:  /* RTC_COMP_MSB_REG */
 
4136
        return ((uint16_t) s->comp_reg) >> 8;
 
4137
    }
 
4138
 
 
4139
    OMAP_BAD_REG(addr);
 
4140
    return 0;
 
4141
}
 
4142
 
 
4143
static void omap_rtc_write(void *opaque, target_phys_addr_t addr,
 
4144
                uint32_t value)
 
4145
{
 
4146
    struct omap_rtc_s *s = (struct omap_rtc_s *) opaque;
 
4147
    int offset = addr - s->base;
 
4148
    struct tm new_tm;
 
4149
    time_t ti[2];
 
4150
 
 
4151
    switch (offset) {
 
4152
    case 0x00:  /* SECONDS_REG */
 
4153
#if ALMDEBUG
 
4154
        printf("RTC SEC_REG <-- %02x\n", value);
 
4155
#endif
 
4156
        s->ti -= s->current_tm.tm_sec;
 
4157
        s->ti += omap_rtc_bin(value);
 
4158
        return;
 
4159
 
 
4160
    case 0x04:  /* MINUTES_REG */
 
4161
#if ALMDEBUG
 
4162
        printf("RTC MIN_REG <-- %02x\n", value);
 
4163
#endif
 
4164
        s->ti -= s->current_tm.tm_min * 60;
 
4165
        s->ti += omap_rtc_bin(value) * 60;
 
4166
        return;
 
4167
 
 
4168
    case 0x08:  /* HOURS_REG */
 
4169
#if ALMDEBUG
 
4170
        printf("RTC HRS_REG <-- %02x\n", value);
 
4171
#endif
 
4172
        s->ti -= s->current_tm.tm_hour * 3600;
 
4173
        if (s->pm_am) {
 
4174
            s->ti += (omap_rtc_bin(value & 0x3f) & 12) * 3600;
 
4175
            s->ti += ((value >> 7) & 1) * 43200;
 
4176
        } else
 
4177
            s->ti += omap_rtc_bin(value & 0x3f) * 3600;
 
4178
        return;
 
4179
 
 
4180
    case 0x0c:  /* DAYS_REG */
 
4181
#if ALMDEBUG
 
4182
        printf("RTC DAY_REG <-- %02x\n", value);
 
4183
#endif
 
4184
        s->ti -= s->current_tm.tm_mday * 86400;
 
4185
        s->ti += omap_rtc_bin(value) * 86400;
 
4186
        return;
 
4187
 
 
4188
    case 0x10:  /* MONTHS_REG */
 
4189
#if ALMDEBUG
 
4190
        printf("RTC MTH_REG <-- %02x\n", value);
 
4191
#endif
 
4192
        memcpy(&new_tm, &s->current_tm, sizeof(new_tm));
 
4193
        new_tm.tm_mon = omap_rtc_bin(value);
 
4194
        ti[0] = mktime(&s->current_tm);
 
4195
        ti[1] = mktime(&new_tm);
 
4196
 
 
4197
        if (ti[0] != -1 && ti[1] != -1) {
 
4198
            s->ti -= ti[0];
 
4199
            s->ti += ti[1];
 
4200
        } else {
 
4201
            /* A less accurate version */
 
4202
            s->ti -= s->current_tm.tm_mon * 2592000;
 
4203
            s->ti += omap_rtc_bin(value) * 2592000;
 
4204
        }
 
4205
        return;
 
4206
 
 
4207
    case 0x14:  /* YEARS_REG */
 
4208
#if ALMDEBUG
 
4209
        printf("RTC YRS_REG <-- %02x\n", value);
 
4210
#endif
 
4211
        memcpy(&new_tm, &s->current_tm, sizeof(new_tm));
 
4212
        new_tm.tm_year += omap_rtc_bin(value) - (new_tm.tm_year % 100);
 
4213
        ti[0] = mktime(&s->current_tm);
 
4214
        ti[1] = mktime(&new_tm);
 
4215
 
 
4216
        if (ti[0] != -1 && ti[1] != -1) {
 
4217
            s->ti -= ti[0];
 
4218
            s->ti += ti[1];
 
4219
        } else {
 
4220
            /* A less accurate version */
 
4221
            s->ti -= (s->current_tm.tm_year % 100) * 31536000;
 
4222
            s->ti += omap_rtc_bin(value) * 31536000;
 
4223
        }
 
4224
        return;
 
4225
 
 
4226
    case 0x18:  /* WEEK_REG */
 
4227
        return; /* Ignored */
 
4228
 
 
4229
    case 0x20:  /* ALARM_SECONDS_REG */
 
4230
#if ALMDEBUG
 
4231
        printf("ALM SEC_REG <-- %02x\n", value);
 
4232
#endif
 
4233
        s->alarm_tm.tm_sec = omap_rtc_bin(value);
 
4234
        omap_rtc_alarm_update(s);
 
4235
        return;
 
4236
 
 
4237
    case 0x24:  /* ALARM_MINUTES_REG */
 
4238
#if ALMDEBUG
 
4239
        printf("ALM MIN_REG <-- %02x\n", value);
 
4240
#endif
 
4241
        s->alarm_tm.tm_min = omap_rtc_bin(value);
 
4242
        omap_rtc_alarm_update(s);
 
4243
        return;
 
4244
 
 
4245
    case 0x28:  /* ALARM_HOURS_REG */
 
4246
#if ALMDEBUG
 
4247
        printf("ALM HRS_REG <-- %02x\n", value);
 
4248
#endif
 
4249
        if (s->pm_am)
 
4250
            s->alarm_tm.tm_hour =
 
4251
                    ((omap_rtc_bin(value & 0x3f)) % 12) +
 
4252
                    ((value >> 7) & 1) * 12;
 
4253
        else
 
4254
            s->alarm_tm.tm_hour = omap_rtc_bin(value);
 
4255
        omap_rtc_alarm_update(s);
 
4256
        return;
 
4257
 
 
4258
    case 0x2c:  /* ALARM_DAYS_REG */
 
4259
#if ALMDEBUG
 
4260
        printf("ALM DAY_REG <-- %02x\n", value);
 
4261
#endif
 
4262
        s->alarm_tm.tm_mday = omap_rtc_bin(value);
 
4263
        omap_rtc_alarm_update(s);
 
4264
        return;
 
4265
 
 
4266
    case 0x30:  /* ALARM_MONTHS_REG */
 
4267
#if ALMDEBUG
 
4268
        printf("ALM MON_REG <-- %02x\n", value);
 
4269
#endif
 
4270
        s->alarm_tm.tm_mon = omap_rtc_bin(value);
 
4271
        omap_rtc_alarm_update(s);
 
4272
        return;
 
4273
 
 
4274
    case 0x34:  /* ALARM_YEARS_REG */
 
4275
#if ALMDEBUG
 
4276
        printf("ALM YRS_REG <-- %02x\n", value);
 
4277
#endif
 
4278
        s->alarm_tm.tm_year = omap_rtc_bin(value);
 
4279
        omap_rtc_alarm_update(s);
 
4280
        return;
 
4281
 
 
4282
    case 0x40:  /* RTC_CTRL_REG */
 
4283
#if ALMDEBUG
 
4284
        printf("RTC CONTROL <-- %02x\n", value);
 
4285
#endif
 
4286
        s->pm_am = (value >> 3) & 1;
 
4287
        s->auto_comp = (value >> 2) & 1;
 
4288
        s->round = (value >> 1) & 1;
 
4289
        s->running = value & 1;
 
4290
        s->status &= 0xfd;
 
4291
        s->status |= s->running << 1;
 
4292
        return;
 
4293
 
 
4294
    case 0x44:  /* RTC_STATUS_REG */
 
4295
#if ALMDEBUG
 
4296
        printf("RTC STATUSL <-- %02x\n", value);
 
4297
#endif
 
4298
        s->status &= ~((value & 0xc0) ^ 0x80);
 
4299
        omap_rtc_interrupts_update(s);
 
4300
        return;
 
4301
 
 
4302
    case 0x48:  /* RTC_INTERRUPTS_REG */
 
4303
#if ALMDEBUG
 
4304
        printf("RTC INTRS <-- %02x\n", value);
 
4305
#endif
 
4306
        s->interrupts = value;
 
4307
        return;
 
4308
 
 
4309
    case 0x4c:  /* RTC_COMP_LSB_REG */
 
4310
#if ALMDEBUG
 
4311
        printf("RTC COMPLSB <-- %02x\n", value);
 
4312
#endif
 
4313
        s->comp_reg &= 0xff00;
 
4314
        s->comp_reg |= 0x00ff & value;
 
4315
        return;
 
4316
 
 
4317
    case 0x50:  /* RTC_COMP_MSB_REG */
 
4318
#if ALMDEBUG
 
4319
        printf("RTC COMPMSB <-- %02x\n", value);
 
4320
#endif
 
4321
        s->comp_reg &= 0x00ff;
 
4322
        s->comp_reg |= 0xff00 & (value << 8);
 
4323
        return;
 
4324
 
 
4325
    default:
 
4326
        OMAP_BAD_REG(addr);
 
4327
        return;
 
4328
    }
 
4329
}
 
4330
 
 
4331
static CPUReadMemoryFunc *omap_rtc_readfn[] = {
 
4332
    omap_rtc_read,
 
4333
    omap_badwidth_read8,
 
4334
    omap_badwidth_read8,
 
4335
};
 
4336
 
 
4337
static CPUWriteMemoryFunc *omap_rtc_writefn[] = {
 
4338
    omap_rtc_write,
 
4339
    omap_badwidth_write8,
 
4340
    omap_badwidth_write8,
 
4341
};
 
4342
 
 
4343
static void omap_rtc_tick(void *opaque)
 
4344
{
 
4345
    struct omap_rtc_s *s = opaque;
 
4346
 
 
4347
    if (s->round) {
 
4348
        /* Round to nearest full minute.  */
 
4349
        if (s->current_tm.tm_sec < 30)
 
4350
            s->ti -= s->current_tm.tm_sec;
 
4351
        else
 
4352
            s->ti += 60 - s->current_tm.tm_sec;
 
4353
 
 
4354
        s->round = 0;
 
4355
    }
 
4356
 
 
4357
    localtime_r(&s->ti, &s->current_tm);
 
4358
 
 
4359
    if ((s->interrupts & 0x08) && s->ti == s->alarm_ti) {
 
4360
        s->status |= 0x40;
 
4361
        omap_rtc_interrupts_update(s);
 
4362
    }
 
4363
 
 
4364
    if (s->interrupts & 0x04)
 
4365
        switch (s->interrupts & 3) {
 
4366
        case 0:
 
4367
            s->status |= 0x04;
 
4368
            qemu_irq_raise(s->irq);
 
4369
            break;
 
4370
        case 1:
 
4371
            if (s->current_tm.tm_sec)
 
4372
                break;
 
4373
            s->status |= 0x08;
 
4374
            qemu_irq_raise(s->irq);
 
4375
            break;
 
4376
        case 2:
 
4377
            if (s->current_tm.tm_sec || s->current_tm.tm_min)
 
4378
                break;
 
4379
            s->status |= 0x10;
 
4380
            qemu_irq_raise(s->irq);
 
4381
            break;
 
4382
        case 3:
 
4383
            if (s->current_tm.tm_sec ||
 
4384
                            s->current_tm.tm_min || s->current_tm.tm_hour)
 
4385
                break;
 
4386
            s->status |= 0x20;
 
4387
            qemu_irq_raise(s->irq);
 
4388
            break;
 
4389
        }
 
4390
 
 
4391
    /* Move on */
 
4392
    if (s->running)
 
4393
        s->ti ++;
 
4394
    s->tick += 1000;
 
4395
 
 
4396
    /*
 
4397
     * Every full hour add a rough approximation of the compensation
 
4398
     * register to the 32kHz Timer (which drives the RTC) value. 
 
4399
     */
 
4400
    if (s->auto_comp && !s->current_tm.tm_sec && !s->current_tm.tm_min)
 
4401
        s->tick += s->comp_reg * 1000 / 32768;
 
4402
 
 
4403
    qemu_mod_timer(s->clk, s->tick);
 
4404
}
 
4405
 
 
4406
void omap_rtc_reset(struct omap_rtc_s *s)
 
4407
{
 
4408
    s->interrupts = 0;
 
4409
    s->comp_reg = 0;
 
4410
    s->running = 0;
 
4411
    s->pm_am = 0;
 
4412
    s->auto_comp = 0;
 
4413
    s->round = 0;
 
4414
    s->tick = qemu_get_clock(rt_clock);
 
4415
    memset(&s->alarm_tm, 0, sizeof(s->alarm_tm));
 
4416
    s->alarm_tm.tm_mday = 0x01;
 
4417
    s->status = 1 << 7;
 
4418
    time(&s->ti);
 
4419
    s->ti = mktime(s->convert(&s->ti, &s->current_tm));
 
4420
 
 
4421
    omap_rtc_alarm_update(s);
 
4422
    omap_rtc_tick(s);
 
4423
}
 
4424
 
 
4425
struct omap_rtc_s *omap_rtc_init(target_phys_addr_t base,
 
4426
                qemu_irq *irq, omap_clk clk)
 
4427
{
 
4428
    int iomemtype;
 
4429
    struct omap_rtc_s *s = (struct omap_rtc_s *)
 
4430
            qemu_mallocz(sizeof(struct omap_rtc_s));
 
4431
 
 
4432
    s->base = base;
 
4433
    s->irq = irq[0];
 
4434
    s->alarm = irq[1];
 
4435
    s->clk = qemu_new_timer(rt_clock, omap_rtc_tick, s);
 
4436
    s->convert = rtc_utc ? gmtime_r : localtime_r;
 
4437
 
 
4438
    omap_rtc_reset(s);
 
4439
 
 
4440
    iomemtype = cpu_register_io_memory(0, omap_rtc_readfn,
 
4441
                    omap_rtc_writefn, s);
 
4442
    cpu_register_physical_memory(s->base, 0x800, iomemtype);
 
4443
 
 
4444
    return s;
 
4445
}
 
4446
 
4021
4447
/* General chip reset */
4022
4448
static void omap_mpu_reset(void *opaque)
4023
4449
{
4051
4477
    omap_pwl_reset(mpu);
4052
4478
    omap_pwt_reset(mpu);
4053
4479
    omap_i2c_reset(mpu->i2c);
 
4480
    omap_rtc_reset(mpu->rtc);
4054
4481
    cpu_reset(mpu->env);
4055
4482
}
4056
4483
 
4178
4605
    s->i2c = omap_i2c_init(0xfffb3800, s->irq[1][OMAP_INT_I2C],
4179
4606
                    &s->drq[OMAP_DMA_I2C_RX], omap_findclk(s, "mpuper_ck"));
4180
4607
 
 
4608
    s->rtc = omap_rtc_init(0xfffb4800, &s->irq[1][OMAP_INT_RTC_TIMER],
 
4609
                    omap_findclk(s, "clk32-kHz"));
4181
4610
    qemu_register_reset(omap_mpu_reset, s);
4182
4611
 
4183
4612
    return s;