Commit 5c1c390feac308327eee376845d90355b16615b9

Authored by balrog
1 parent 4a2c8ac2

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
Showing 2 changed files with 433 additions and 0 deletions
hw/omap.c
... ... @@ -4018,6 +4018,432 @@ i2c_bus *omap_i2c_bus(struct omap_i2c_s *s)
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,6 +4477,7 @@ static void omap_mpu_reset(void *opaque)
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,6 +4605,8 @@ struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size,
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;
... ...
hw/omap.h
... ... @@ -480,6 +480,10 @@ struct omap_i2c_s *omap_i2c_init(target_phys_addr_t base,
480 480 qemu_irq irq, qemu_irq *dma, omap_clk clk);
481 481 i2c_bus *omap_i2c_bus(struct omap_i2c_s *s);
482 482  
  483 +struct omap_rtc_s;
  484 +struct omap_rtc_s *omap_rtc_init(target_phys_addr_t base,
  485 + qemu_irq *irq, omap_clk clk);
  486 +
483 487 /* omap_lcdc.c */
484 488 struct omap_lcd_panel_s;
485 489 void omap_lcdc_reset(struct omap_lcd_panel_s *s);
... ...