| /* |
| * rtc.c, RTC(has only timer function) routines for NEC VR4100 series. |
| * |
| * Copyright (C) 2003-2004 Yoichi Yuasa <yuasa@hh.iij4u.or.jp> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| #include <linux/init.h> |
| #include <linux/irq.h> |
| #include <linux/smp.h> |
| #include <linux/types.h> |
| |
| #include <asm/io.h> |
| #include <asm/time.h> |
| #include <asm/vr41xx/vr41xx.h> |
| |
| static uint32_t rtc1_base; |
| static uint32_t rtc2_base; |
| |
| static uint64_t previous_elapsedtime; |
| static unsigned int remainder_per_sec; |
| static unsigned int cycles_per_sec; |
| static unsigned int cycles_per_jiffy; |
| static unsigned long epoch_time; |
| |
| #define CYCLES_PER_JIFFY (CLOCK_TICK_RATE / HZ) |
| #define REMAINDER_PER_SEC (CLOCK_TICK_RATE - (CYCLES_PER_JIFFY * HZ)) |
| #define CYCLES_PER_100USEC ((CLOCK_TICK_RATE + (10000 / 2)) / 10000) |
| |
| #define ETIMELREG_TYPE1 KSEG1ADDR(0x0b0000c0) |
| #define TCLKLREG_TYPE1 KSEG1ADDR(0x0b0001c0) |
| |
| #define ETIMELREG_TYPE2 KSEG1ADDR(0x0f000100) |
| #define TCLKLREG_TYPE2 KSEG1ADDR(0x0f000120) |
| |
| /* RTC 1 registers */ |
| #define ETIMELREG 0x00 |
| #define ETIMEMREG 0x02 |
| #define ETIMEHREG 0x04 |
| /* RFU */ |
| #define ECMPLREG 0x08 |
| #define ECMPMREG 0x0a |
| #define ECMPHREG 0x0c |
| /* RFU */ |
| #define RTCL1LREG 0x10 |
| #define RTCL1HREG 0x12 |
| #define RTCL1CNTLREG 0x14 |
| #define RTCL1CNTHREG 0x16 |
| #define RTCL2LREG 0x18 |
| #define RTCL2HREG 0x1a |
| #define RTCL2CNTLREG 0x1c |
| #define RTCL2CNTHREG 0x1e |
| |
| /* RTC 2 registers */ |
| #define TCLKLREG 0x00 |
| #define TCLKHREG 0x02 |
| #define TCLKCNTLREG 0x04 |
| #define TCLKCNTHREG 0x06 |
| /* RFU */ |
| #define RTCINTREG 0x1e |
| #define TCLOCK_INT 0x08 |
| #define RTCLONG2_INT 0x04 |
| #define RTCLONG1_INT 0x02 |
| #define ELAPSEDTIME_INT 0x01 |
| |
| #define read_rtc1(offset) readw(rtc1_base + (offset)) |
| #define write_rtc1(val, offset) writew((val), rtc1_base + (offset)) |
| |
| #define read_rtc2(offset) readw(rtc2_base + (offset)) |
| #define write_rtc2(val, offset) writew((val), rtc2_base + (offset)) |
| |
| static inline uint64_t read_elapsedtime_counter(void) |
| { |
| uint64_t first, second; |
| uint32_t first_mid, first_low; |
| uint32_t second_mid, second_low; |
| |
| do { |
| first_low = (uint32_t)read_rtc1(ETIMELREG); |
| first_mid = (uint32_t)read_rtc1(ETIMEMREG); |
| first = (uint64_t)read_rtc1(ETIMEHREG); |
| second_low = (uint32_t)read_rtc1(ETIMELREG); |
| second_mid = (uint32_t)read_rtc1(ETIMEMREG); |
| second = (uint64_t)read_rtc1(ETIMEHREG); |
| } while (first_low != second_low || first_mid != second_mid || |
| first != second); |
| |
| return (first << 32) | (uint64_t)((first_mid << 16) | first_low); |
| } |
| |
| static inline void write_elapsedtime_counter(uint64_t time) |
| { |
| write_rtc1((uint16_t)time, ETIMELREG); |
| write_rtc1((uint16_t)(time >> 16), ETIMEMREG); |
| write_rtc1((uint16_t)(time >> 32), ETIMEHREG); |
| } |
| |
| static inline void write_elapsedtime_compare(uint64_t time) |
| { |
| write_rtc1((uint16_t)time, ECMPLREG); |
| write_rtc1((uint16_t)(time >> 16), ECMPMREG); |
| write_rtc1((uint16_t)(time >> 32), ECMPHREG); |
| } |
| |
| void vr41xx_set_rtclong1_cycle(uint32_t cycles) |
| { |
| write_rtc1((uint16_t)cycles, RTCL1LREG); |
| write_rtc1((uint16_t)(cycles >> 16), RTCL1HREG); |
| } |
| |
| uint32_t vr41xx_read_rtclong1_counter(void) |
| { |
| uint32_t first_high, first_low; |
| uint32_t second_high, second_low; |
| |
| do { |
| first_low = (uint32_t)read_rtc1(RTCL1CNTLREG); |
| first_high = (uint32_t)read_rtc1(RTCL1CNTHREG); |
| second_low = (uint32_t)read_rtc1(RTCL1CNTLREG); |
| second_high = (uint32_t)read_rtc1(RTCL1CNTHREG); |
| } while (first_low != second_low || first_high != second_high); |
| |
| return (first_high << 16) | first_low; |
| } |
| |
| void vr41xx_set_rtclong2_cycle(uint32_t cycles) |
| { |
| write_rtc1((uint16_t)cycles, RTCL2LREG); |
| write_rtc1((uint16_t)(cycles >> 16), RTCL2HREG); |
| } |
| |
| uint32_t vr41xx_read_rtclong2_counter(void) |
| { |
| uint32_t first_high, first_low; |
| uint32_t second_high, second_low; |
| |
| do { |
| first_low = (uint32_t)read_rtc1(RTCL2CNTLREG); |
| first_high = (uint32_t)read_rtc1(RTCL2CNTHREG); |
| second_low = (uint32_t)read_rtc1(RTCL2CNTLREG); |
| second_high = (uint32_t)read_rtc1(RTCL2CNTHREG); |
| } while (first_low != second_low || first_high != second_high); |
| |
| return (first_high << 16) | first_low; |
| } |
| |
| void vr41xx_set_tclock_cycle(uint32_t cycles) |
| { |
| write_rtc2((uint16_t)cycles, TCLKLREG); |
| write_rtc2((uint16_t)(cycles >> 16), TCLKHREG); |
| } |
| |
| uint32_t vr41xx_read_tclock_counter(void) |
| { |
| uint32_t first_high, first_low; |
| uint32_t second_high, second_low; |
| |
| do { |
| first_low = (uint32_t)read_rtc2(TCLKCNTLREG); |
| first_high = (uint32_t)read_rtc2(TCLKCNTHREG); |
| second_low = (uint32_t)read_rtc2(TCLKCNTLREG); |
| second_high = (uint32_t)read_rtc2(TCLKCNTHREG); |
| } while (first_low != second_low || first_high != second_high); |
| |
| return (first_high << 16) | first_low; |
| } |
| |
| static void vr41xx_timer_ack(void) |
| { |
| uint64_t cur; |
| |
| write_rtc2(ELAPSEDTIME_INT, RTCINTREG); |
| |
| previous_elapsedtime += (uint64_t)cycles_per_jiffy; |
| cycles_per_sec += cycles_per_jiffy; |
| |
| if (cycles_per_sec >= CLOCK_TICK_RATE) { |
| cycles_per_sec = 0; |
| remainder_per_sec = REMAINDER_PER_SEC; |
| } |
| |
| cycles_per_jiffy = 0; |
| |
| do { |
| cycles_per_jiffy += CYCLES_PER_JIFFY; |
| if (remainder_per_sec > 0) { |
| cycles_per_jiffy++; |
| remainder_per_sec--; |
| } |
| |
| cur = read_elapsedtime_counter(); |
| } while (cur >= previous_elapsedtime + (uint64_t)cycles_per_jiffy); |
| |
| write_elapsedtime_compare(previous_elapsedtime + (uint64_t)cycles_per_jiffy); |
| } |
| |
| static void vr41xx_hpt_init(unsigned int count) |
| { |
| } |
| |
| static unsigned int vr41xx_hpt_read(void) |
| { |
| uint64_t cur; |
| |
| cur = read_elapsedtime_counter(); |
| |
| return (unsigned int)cur; |
| } |
| |
| static unsigned long vr41xx_gettimeoffset(void) |
| { |
| uint64_t cur; |
| unsigned long gap; |
| |
| cur = read_elapsedtime_counter(); |
| gap = (unsigned long)(cur - previous_elapsedtime); |
| gap = gap / CYCLES_PER_100USEC * 100; /* usec */ |
| |
| return gap; |
| } |
| |
| static unsigned long vr41xx_get_time(void) |
| { |
| uint64_t counts; |
| |
| counts = read_elapsedtime_counter(); |
| counts >>= 15; |
| |
| return epoch_time + (unsigned long)counts; |
| |
| } |
| |
| static int vr41xx_set_time(unsigned long sec) |
| { |
| if (sec < epoch_time) |
| return -EINVAL; |
| |
| sec -= epoch_time; |
| |
| write_elapsedtime_counter((uint64_t)sec << 15); |
| |
| return 0; |
| } |
| |
| void vr41xx_set_epoch_time(unsigned long time) |
| { |
| epoch_time = time; |
| } |
| |
| static void __init vr41xx_time_init(void) |
| { |
| switch (current_cpu_data.cputype) { |
| case CPU_VR4111: |
| case CPU_VR4121: |
| rtc1_base = ETIMELREG_TYPE1; |
| rtc2_base = TCLKLREG_TYPE1; |
| break; |
| case CPU_VR4122: |
| case CPU_VR4131: |
| case CPU_VR4133: |
| rtc1_base = ETIMELREG_TYPE2; |
| rtc2_base = TCLKLREG_TYPE2; |
| break; |
| default: |
| panic("Unexpected CPU of NEC VR4100 series"); |
| break; |
| } |
| |
| mips_timer_ack = vr41xx_timer_ack; |
| |
| mips_hpt_init = vr41xx_hpt_init; |
| mips_hpt_read = vr41xx_hpt_read; |
| mips_hpt_frequency = CLOCK_TICK_RATE; |
| |
| if (epoch_time == 0) |
| epoch_time = mktime(1970, 1, 1, 0, 0, 0); |
| |
| rtc_get_time = vr41xx_get_time; |
| rtc_set_time = vr41xx_set_time; |
| } |
| |
| static void __init vr41xx_timer_setup(struct irqaction *irq) |
| { |
| do_gettimeoffset = vr41xx_gettimeoffset; |
| |
| remainder_per_sec = REMAINDER_PER_SEC; |
| cycles_per_jiffy = CYCLES_PER_JIFFY; |
| |
| if (remainder_per_sec > 0) { |
| cycles_per_jiffy++; |
| remainder_per_sec--; |
| } |
| |
| previous_elapsedtime = read_elapsedtime_counter(); |
| write_elapsedtime_compare(previous_elapsedtime + (uint64_t)cycles_per_jiffy); |
| write_rtc2(ELAPSEDTIME_INT, RTCINTREG); |
| |
| setup_irq(ELAPSEDTIME_IRQ, irq); |
| } |
| |
| static int __init vr41xx_rtc_init(void) |
| { |
| board_time_init = vr41xx_time_init; |
| board_timer_setup = vr41xx_timer_setup; |
| |
| return 0; |
| } |
| |
| early_initcall(vr41xx_rtc_init); |