From 5c1c390feac308327eee376845d90355b16615b9 Mon Sep 17 00:00:00 2001 From: balrog Date: Sat, 3 Nov 2007 12:44:02 +0000 Subject: [PATCH] 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 --- hw/omap.c | 429 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/omap.h | 4 + 2 files changed, 433 insertions(+) diff --git a/hw/omap.c b/hw/omap.c index ef03868384..eb80b0a3af 100644 --- a/hw/omap.c +++ b/hw/omap.c @@ -4018,6 +4018,432 @@ i2c_bus *omap_i2c_bus(struct omap_i2c_s *s) return s->bus; } +/* Real-time Clock module */ +struct omap_rtc_s { + target_phys_addr_t base; + qemu_irq irq; + qemu_irq alarm; + QEMUTimer *clk; + + uint8_t interrupts; + uint8_t status; + int16_t comp_reg; + int running; + int pm_am; + int auto_comp; + int round; + struct tm *(*convert)(const time_t *timep, struct tm *result); + struct tm alarm_tm; + time_t alarm_ti; + + struct tm current_tm; + time_t ti; + uint64_t tick; +}; + +static void omap_rtc_interrupts_update(struct omap_rtc_s *s) +{ + qemu_set_irq(s->alarm, (s->status >> 6) & 1); +} + +static void omap_rtc_alarm_update(struct omap_rtc_s *s) +{ + s->alarm_ti = mktime(&s->alarm_tm); + if (s->alarm_ti == -1) + printf("%s: conversion failed\n", __FUNCTION__); +} + +static inline uint8_t omap_rtc_bcd(int num) +{ + return ((num / 10) << 4) | (num % 10); +} + +static inline int omap_rtc_bin(uint8_t num) +{ + return (num & 15) + 10 * (num >> 4); +} + +static uint32_t omap_rtc_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_rtc_s *s = (struct omap_rtc_s *) opaque; + int offset = addr - s->base; + uint8_t i; + + switch (offset) { + case 0x00: /* SECONDS_REG */ + return omap_rtc_bcd(s->current_tm.tm_sec); + + case 0x04: /* MINUTES_REG */ + return omap_rtc_bcd(s->current_tm.tm_min); + + case 0x08: /* HOURS_REG */ + if (s->pm_am) + return ((s->current_tm.tm_hour > 11) << 7) | + omap_rtc_bcd(((s->current_tm.tm_hour - 1) % 12) + 1); + else + return omap_rtc_bcd(s->current_tm.tm_hour); + + case 0x0c: /* DAYS_REG */ + return omap_rtc_bcd(s->current_tm.tm_mday); + + case 0x10: /* MONTHS_REG */ + return omap_rtc_bcd(s->current_tm.tm_mon + 1); + + case 0x14: /* YEARS_REG */ + return omap_rtc_bcd(s->current_tm.tm_year % 100); + + case 0x18: /* WEEK_REG */ + return s->current_tm.tm_wday; + + case 0x20: /* ALARM_SECONDS_REG */ + return omap_rtc_bcd(s->alarm_tm.tm_sec); + + case 0x24: /* ALARM_MINUTES_REG */ + return omap_rtc_bcd(s->alarm_tm.tm_min); + + case 0x28: /* ALARM_HOURS_REG */ + if (s->pm_am) + return ((s->alarm_tm.tm_hour > 11) << 7) | + omap_rtc_bcd(((s->alarm_tm.tm_hour - 1) % 12) + 1); + else + return omap_rtc_bcd(s->alarm_tm.tm_hour); + + case 0x2c: /* ALARM_DAYS_REG */ + return omap_rtc_bcd(s->alarm_tm.tm_mday); + + case 0x30: /* ALARM_MONTHS_REG */ + return omap_rtc_bcd(s->alarm_tm.tm_mon + 1); + + case 0x34: /* ALARM_YEARS_REG */ + return omap_rtc_bcd(s->alarm_tm.tm_year % 100); + + case 0x40: /* RTC_CTRL_REG */ + return (s->pm_am << 3) | (s->auto_comp << 2) | + (s->round << 1) | s->running; + + case 0x44: /* RTC_STATUS_REG */ + i = s->status; + s->status &= ~0x3d; + return i; + + case 0x48: /* RTC_INTERRUPTS_REG */ + return s->interrupts; + + case 0x4c: /* RTC_COMP_LSB_REG */ + return ((uint16_t) s->comp_reg) & 0xff; + + case 0x50: /* RTC_COMP_MSB_REG */ + return ((uint16_t) s->comp_reg) >> 8; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_rtc_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_rtc_s *s = (struct omap_rtc_s *) opaque; + int offset = addr - s->base; + struct tm new_tm; + time_t ti[2]; + + switch (offset) { + case 0x00: /* SECONDS_REG */ +#if ALMDEBUG + printf("RTC SEC_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_sec; + s->ti += omap_rtc_bin(value); + return; + + case 0x04: /* MINUTES_REG */ +#if ALMDEBUG + printf("RTC MIN_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_min * 60; + s->ti += omap_rtc_bin(value) * 60; + return; + + case 0x08: /* HOURS_REG */ +#if ALMDEBUG + printf("RTC HRS_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_hour * 3600; + if (s->pm_am) { + s->ti += (omap_rtc_bin(value & 0x3f) & 12) * 3600; + s->ti += ((value >> 7) & 1) * 43200; + } else + s->ti += omap_rtc_bin(value & 0x3f) * 3600; + return; + + case 0x0c: /* DAYS_REG */ +#if ALMDEBUG + printf("RTC DAY_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_mday * 86400; + s->ti += omap_rtc_bin(value) * 86400; + return; + + case 0x10: /* MONTHS_REG */ +#if ALMDEBUG + printf("RTC MTH_REG <-- %02x\n", value); +#endif + memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); + new_tm.tm_mon = omap_rtc_bin(value); + ti[0] = mktime(&s->current_tm); + ti[1] = mktime(&new_tm); + + if (ti[0] != -1 && ti[1] != -1) { + s->ti -= ti[0]; + s->ti += ti[1]; + } else { + /* A less accurate version */ + s->ti -= s->current_tm.tm_mon * 2592000; + s->ti += omap_rtc_bin(value) * 2592000; + } + return; + + case 0x14: /* YEARS_REG */ +#if ALMDEBUG + printf("RTC YRS_REG <-- %02x\n", value); +#endif + memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); + new_tm.tm_year += omap_rtc_bin(value) - (new_tm.tm_year % 100); + ti[0] = mktime(&s->current_tm); + ti[1] = mktime(&new_tm); + + if (ti[0] != -1 && ti[1] != -1) { + s->ti -= ti[0]; + s->ti += ti[1]; + } else { + /* A less accurate version */ + s->ti -= (s->current_tm.tm_year % 100) * 31536000; + s->ti += omap_rtc_bin(value) * 31536000; + } + return; + + case 0x18: /* WEEK_REG */ + return; /* Ignored */ + + case 0x20: /* ALARM_SECONDS_REG */ +#if ALMDEBUG + printf("ALM SEC_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_sec = omap_rtc_bin(value); + omap_rtc_alarm_update(s); + return; + + case 0x24: /* ALARM_MINUTES_REG */ +#if ALMDEBUG + printf("ALM MIN_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_min = omap_rtc_bin(value); + omap_rtc_alarm_update(s); + return; + + case 0x28: /* ALARM_HOURS_REG */ +#if ALMDEBUG + printf("ALM HRS_REG <-- %02x\n", value); +#endif + if (s->pm_am) + s->alarm_tm.tm_hour = + ((omap_rtc_bin(value & 0x3f)) % 12) + + ((value >> 7) & 1) * 12; + else + s->alarm_tm.tm_hour = omap_rtc_bin(value); + omap_rtc_alarm_update(s); + return; + + case 0x2c: /* ALARM_DAYS_REG */ +#if ALMDEBUG + printf("ALM DAY_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_mday = omap_rtc_bin(value); + omap_rtc_alarm_update(s); + return; + + case 0x30: /* ALARM_MONTHS_REG */ +#if ALMDEBUG + printf("ALM MON_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_mon = omap_rtc_bin(value); + omap_rtc_alarm_update(s); + return; + + case 0x34: /* ALARM_YEARS_REG */ +#if ALMDEBUG + printf("ALM YRS_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_year = omap_rtc_bin(value); + omap_rtc_alarm_update(s); + return; + + case 0x40: /* RTC_CTRL_REG */ +#if ALMDEBUG + printf("RTC CONTROL <-- %02x\n", value); +#endif + s->pm_am = (value >> 3) & 1; + s->auto_comp = (value >> 2) & 1; + s->round = (value >> 1) & 1; + s->running = value & 1; + s->status &= 0xfd; + s->status |= s->running << 1; + return; + + case 0x44: /* RTC_STATUS_REG */ +#if ALMDEBUG + printf("RTC STATUSL <-- %02x\n", value); +#endif + s->status &= ~((value & 0xc0) ^ 0x80); + omap_rtc_interrupts_update(s); + return; + + case 0x48: /* RTC_INTERRUPTS_REG */ +#if ALMDEBUG + printf("RTC INTRS <-- %02x\n", value); +#endif + s->interrupts = value; + return; + + case 0x4c: /* RTC_COMP_LSB_REG */ +#if ALMDEBUG + printf("RTC COMPLSB <-- %02x\n", value); +#endif + s->comp_reg &= 0xff00; + s->comp_reg |= 0x00ff & value; + return; + + case 0x50: /* RTC_COMP_MSB_REG */ +#if ALMDEBUG + printf("RTC COMPMSB <-- %02x\n", value); +#endif + s->comp_reg &= 0x00ff; + s->comp_reg |= 0xff00 & (value << 8); + return; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static CPUReadMemoryFunc *omap_rtc_readfn[] = { + omap_rtc_read, + omap_badwidth_read8, + omap_badwidth_read8, +}; + +static CPUWriteMemoryFunc *omap_rtc_writefn[] = { + omap_rtc_write, + omap_badwidth_write8, + omap_badwidth_write8, +}; + +static void omap_rtc_tick(void *opaque) +{ + struct omap_rtc_s *s = opaque; + + if (s->round) { + /* Round to nearest full minute. */ + if (s->current_tm.tm_sec < 30) + s->ti -= s->current_tm.tm_sec; + else + s->ti += 60 - s->current_tm.tm_sec; + + s->round = 0; + } + + localtime_r(&s->ti, &s->current_tm); + + if ((s->interrupts & 0x08) && s->ti == s->alarm_ti) { + s->status |= 0x40; + omap_rtc_interrupts_update(s); + } + + if (s->interrupts & 0x04) + switch (s->interrupts & 3) { + case 0: + s->status |= 0x04; + qemu_irq_raise(s->irq); + break; + case 1: + if (s->current_tm.tm_sec) + break; + s->status |= 0x08; + qemu_irq_raise(s->irq); + break; + case 2: + if (s->current_tm.tm_sec || s->current_tm.tm_min) + break; + s->status |= 0x10; + qemu_irq_raise(s->irq); + break; + case 3: + if (s->current_tm.tm_sec || + s->current_tm.tm_min || s->current_tm.tm_hour) + break; + s->status |= 0x20; + qemu_irq_raise(s->irq); + break; + } + + /* Move on */ + if (s->running) + s->ti ++; + s->tick += 1000; + + /* + * Every full hour add a rough approximation of the compensation + * register to the 32kHz Timer (which drives the RTC) value. + */ + if (s->auto_comp && !s->current_tm.tm_sec && !s->current_tm.tm_min) + s->tick += s->comp_reg * 1000 / 32768; + + qemu_mod_timer(s->clk, s->tick); +} + +void omap_rtc_reset(struct omap_rtc_s *s) +{ + s->interrupts = 0; + s->comp_reg = 0; + s->running = 0; + s->pm_am = 0; + s->auto_comp = 0; + s->round = 0; + s->tick = qemu_get_clock(rt_clock); + memset(&s->alarm_tm, 0, sizeof(s->alarm_tm)); + s->alarm_tm.tm_mday = 0x01; + s->status = 1 << 7; + time(&s->ti); + s->ti = mktime(s->convert(&s->ti, &s->current_tm)); + + omap_rtc_alarm_update(s); + omap_rtc_tick(s); +} + +struct omap_rtc_s *omap_rtc_init(target_phys_addr_t base, + qemu_irq *irq, omap_clk clk) +{ + int iomemtype; + struct omap_rtc_s *s = (struct omap_rtc_s *) + qemu_mallocz(sizeof(struct omap_rtc_s)); + + s->base = base; + s->irq = irq[0]; + s->alarm = irq[1]; + s->clk = qemu_new_timer(rt_clock, omap_rtc_tick, s); + s->convert = rtc_utc ? gmtime_r : localtime_r; + + omap_rtc_reset(s); + + iomemtype = cpu_register_io_memory(0, omap_rtc_readfn, + omap_rtc_writefn, s); + cpu_register_physical_memory(s->base, 0x800, iomemtype); + + return s; +} + /* General chip reset */ static void omap_mpu_reset(void *opaque) { @@ -4051,6 +4477,7 @@ static void omap_mpu_reset(void *opaque) omap_pwl_reset(mpu); omap_pwt_reset(mpu); omap_i2c_reset(mpu->i2c); + omap_rtc_reset(mpu->rtc); cpu_reset(mpu->env); } @@ -4178,6 +4605,8 @@ struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size, s->i2c = omap_i2c_init(0xfffb3800, s->irq[1][OMAP_INT_I2C], &s->drq[OMAP_DMA_I2C_RX], omap_findclk(s, "mpuper_ck")); + s->rtc = omap_rtc_init(0xfffb4800, &s->irq[1][OMAP_INT_RTC_TIMER], + omap_findclk(s, "clk32-kHz")); qemu_register_reset(omap_mpu_reset, s); return s; diff --git a/hw/omap.h b/hw/omap.h index 4b5b579736..9b8da8f31b 100644 --- a/hw/omap.h +++ b/hw/omap.h @@ -480,6 +480,10 @@ struct omap_i2c_s *omap_i2c_init(target_phys_addr_t base, qemu_irq irq, qemu_irq *dma, omap_clk clk); i2c_bus *omap_i2c_bus(struct omap_i2c_s *s); +struct omap_rtc_s; +struct omap_rtc_s *omap_rtc_init(target_phys_addr_t base, + qemu_irq *irq, omap_clk clk); + /* omap_lcdc.c */ struct omap_lcd_panel_s; void omap_lcdc_reset(struct omap_lcd_panel_s *s);