Commit 5c1c390feac308327eee376845d90355b16615b9
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); | ... | ... |