Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

MIPS: Loongson-3: Add RS780/SBX00 HPET support

CPUFreq driver need external timer, so add hpet at first.

In Loongson 3, only Core-0 can receive external interrupt. As a result,
timekeeping cannot absolutely use HPET timer. We use a hybrid solution:
Core-0 use HPET as its clock event device, but other cores still use
MIPS; clock source is global and doesn't need interrupt, so use HPET.

Signed-off-by: Huacai Chen <chenhc@lemote.com>
Signed-off-by: Hongliang Tao <taohl@lemote.com>
Cc: John Crispin <john@phrozen.org>
Cc: Steven J. Hill <Steven.Hill@imgtec.com>
Cc: linux-mips@linux-mips.org
Cc: Fuxin Zhang <zhangfx@lemote.com>
Cc: Zhangjin Wu <wuzhangjin@gmail.com>
Patchwork: https://patchwork.linux-mips.org/patch/8329/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>

authored by

Huacai Chen and committed by
Ralf Baechle
e292ccde 89467e73

+350 -1
+73
arch/mips/include/asm/hpet.h
··· 1 + #ifndef _ASM_HPET_H 2 + #define _ASM_HPET_H 3 + 4 + #ifdef CONFIG_RS780_HPET 5 + 6 + #define HPET_MMAP_SIZE 1024 7 + 8 + #define HPET_ID 0x000 9 + #define HPET_PERIOD 0x004 10 + #define HPET_CFG 0x010 11 + #define HPET_STATUS 0x020 12 + #define HPET_COUNTER 0x0f0 13 + 14 + #define HPET_Tn_CFG(n) (0x100 + 0x20 * n) 15 + #define HPET_Tn_CMP(n) (0x108 + 0x20 * n) 16 + #define HPET_Tn_ROUTE(n) (0x110 + 0x20 * n) 17 + 18 + #define HPET_T0_IRS 0x001 19 + #define HPET_T1_IRS 0x002 20 + #define HPET_T3_IRS 0x004 21 + 22 + #define HPET_T0_CFG 0x100 23 + #define HPET_T0_CMP 0x108 24 + #define HPET_T0_ROUTE 0x110 25 + #define HPET_T1_CFG 0x120 26 + #define HPET_T1_CMP 0x128 27 + #define HPET_T1_ROUTE 0x130 28 + #define HPET_T2_CFG 0x140 29 + #define HPET_T2_CMP 0x148 30 + #define HPET_T2_ROUTE 0x150 31 + 32 + #define HPET_ID_REV 0x000000ff 33 + #define HPET_ID_NUMBER 0x00001f00 34 + #define HPET_ID_64BIT 0x00002000 35 + #define HPET_ID_LEGSUP 0x00008000 36 + #define HPET_ID_VENDOR 0xffff0000 37 + #define HPET_ID_NUMBER_SHIFT 8 38 + #define HPET_ID_VENDOR_SHIFT 16 39 + 40 + #define HPET_CFG_ENABLE 0x001 41 + #define HPET_CFG_LEGACY 0x002 42 + #define HPET_LEGACY_8254 2 43 + #define HPET_LEGACY_RTC 8 44 + 45 + #define HPET_TN_LEVEL 0x0002 46 + #define HPET_TN_ENABLE 0x0004 47 + #define HPET_TN_PERIODIC 0x0008 48 + #define HPET_TN_PERIODIC_CAP 0x0010 49 + #define HPET_TN_64BIT_CAP 0x0020 50 + #define HPET_TN_SETVAL 0x0040 51 + #define HPET_TN_32BIT 0x0100 52 + #define HPET_TN_ROUTE 0x3e00 53 + #define HPET_TN_FSB 0x4000 54 + #define HPET_TN_FSB_CAP 0x8000 55 + #define HPET_TN_ROUTE_SHIFT 9 56 + 57 + /* Max HPET Period is 10^8 femto sec as in HPET spec */ 58 + #define HPET_MAX_PERIOD 100000000UL 59 + /* 60 + * Min HPET period is 10^5 femto sec just for safety. If it is less than this, 61 + * then 32 bit HPET counter wrapsaround in less than 0.5 sec. 62 + */ 63 + #define HPET_MIN_PERIOD 100000UL 64 + 65 + #define HPET_ADDR 0x20000 66 + #define HPET_MMIO_ADDR 0x90000e0000020000 67 + #define HPET_FREQ 14318780 68 + #define HPET_COMPARE_VAL ((HPET_FREQ + HZ / 2) / HZ) 69 + #define HPET_T0_IRQ 0 70 + 71 + extern void __init setup_hpet_timer(void); 72 + #endif /* CONFIG_RS780_HPET */ 73 + #endif /* _ASM_HPET_H */
+12
arch/mips/loongson/Kconfig
··· 108 108 109 109 If unsure, say Yes. 110 110 111 + config RS780_HPET 112 + bool "RS780/SBX00 HPET Timer" 113 + depends on LOONGSON_MACH3X 114 + select MIPS_EXTERNAL_TIMER 115 + help 116 + This option enables the hpet timer of AMD RS780/SBX00. 117 + 118 + If you want to enable the Loongson3 CPUFreq Driver, Please enable 119 + this option at first, otherwise, You will get wrong system time. 120 + 121 + If unsure, say Yes. 122 + 111 123 config LOONGSON_SUSPEND 112 124 bool 113 125 default y
+5
arch/mips/loongson/common/time.c
··· 12 12 */ 13 13 #include <asm/mc146818-time.h> 14 14 #include <asm/time.h> 15 + #include <asm/hpet.h> 15 16 16 17 #include <loongson.h> 17 18 #include <cs5536/cs5536_mfgpt.h> ··· 22 21 /* setup mips r4k timer */ 23 22 mips_hpt_frequency = cpu_clock_freq / 2; 24 23 24 + #ifdef CONFIG_RS780_HPET 25 + setup_hpet_timer(); 26 + #else 25 27 setup_mfgpt0_timer(); 28 + #endif 26 29 } 27 30 28 31 void read_persistent_clock(struct timespec *ts)
+2
arch/mips/loongson/loongson-3/Makefile
··· 6 6 obj-$(CONFIG_SMP) += smp.o 7 7 8 8 obj-$(CONFIG_NUMA) += numa.o 9 + 10 + obj-$(CONFIG_RS780_HPET) += hpet.o
+257
arch/mips/loongson/loongson-3/hpet.c
··· 1 + #include <linux/init.h> 2 + #include <linux/pci.h> 3 + #include <linux/percpu.h> 4 + #include <linux/delay.h> 5 + #include <linux/spinlock.h> 6 + #include <linux/interrupt.h> 7 + 8 + #include <asm/hpet.h> 9 + #include <asm/time.h> 10 + 11 + #define SMBUS_CFG_BASE (loongson_sysconf.ht_control_base + 0x0300a000) 12 + #define SMBUS_PCI_REG40 0x40 13 + #define SMBUS_PCI_REG64 0x64 14 + #define SMBUS_PCI_REGB4 0xb4 15 + 16 + static DEFINE_SPINLOCK(hpet_lock); 17 + DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device); 18 + 19 + static unsigned int smbus_read(int offset) 20 + { 21 + return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset); 22 + } 23 + 24 + static void smbus_write(int offset, int data) 25 + { 26 + *(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data; 27 + } 28 + 29 + static void smbus_enable(int offset, int bit) 30 + { 31 + unsigned int cfg = smbus_read(offset); 32 + 33 + cfg |= bit; 34 + smbus_write(offset, cfg); 35 + } 36 + 37 + static int hpet_read(int offset) 38 + { 39 + return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset); 40 + } 41 + 42 + static void hpet_write(int offset, int data) 43 + { 44 + *(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data; 45 + } 46 + 47 + static void hpet_start_counter(void) 48 + { 49 + unsigned int cfg = hpet_read(HPET_CFG); 50 + 51 + cfg |= HPET_CFG_ENABLE; 52 + hpet_write(HPET_CFG, cfg); 53 + } 54 + 55 + static void hpet_stop_counter(void) 56 + { 57 + unsigned int cfg = hpet_read(HPET_CFG); 58 + 59 + cfg &= ~HPET_CFG_ENABLE; 60 + hpet_write(HPET_CFG, cfg); 61 + } 62 + 63 + static void hpet_reset_counter(void) 64 + { 65 + hpet_write(HPET_COUNTER, 0); 66 + hpet_write(HPET_COUNTER + 4, 0); 67 + } 68 + 69 + static void hpet_restart_counter(void) 70 + { 71 + hpet_stop_counter(); 72 + hpet_reset_counter(); 73 + hpet_start_counter(); 74 + } 75 + 76 + static void hpet_enable_legacy_int(void) 77 + { 78 + /* Do nothing on Loongson-3 */ 79 + } 80 + 81 + static void hpet_set_mode(enum clock_event_mode mode, 82 + struct clock_event_device *evt) 83 + { 84 + int cfg = 0; 85 + 86 + spin_lock(&hpet_lock); 87 + switch (mode) { 88 + case CLOCK_EVT_MODE_PERIODIC: 89 + pr_info("set clock event to periodic mode!\n"); 90 + /* stop counter */ 91 + hpet_stop_counter(); 92 + 93 + /* enables the timer0 to generate a periodic interrupt */ 94 + cfg = hpet_read(HPET_T0_CFG); 95 + cfg &= ~HPET_TN_LEVEL; 96 + cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | 97 + HPET_TN_SETVAL | HPET_TN_32BIT; 98 + hpet_write(HPET_T0_CFG, cfg); 99 + 100 + /* set the comparator */ 101 + hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); 102 + udelay(1); 103 + hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); 104 + 105 + /* start counter */ 106 + hpet_start_counter(); 107 + break; 108 + case CLOCK_EVT_MODE_SHUTDOWN: 109 + case CLOCK_EVT_MODE_UNUSED: 110 + cfg = hpet_read(HPET_T0_CFG); 111 + cfg &= ~HPET_TN_ENABLE; 112 + hpet_write(HPET_T0_CFG, cfg); 113 + break; 114 + case CLOCK_EVT_MODE_ONESHOT: 115 + pr_info("set clock event to one shot mode!\n"); 116 + cfg = hpet_read(HPET_T0_CFG); 117 + /* set timer0 type 118 + * 1 : periodic interrupt 119 + * 0 : non-periodic(oneshot) interrupt 120 + */ 121 + cfg &= ~HPET_TN_PERIODIC; 122 + cfg |= HPET_TN_ENABLE | HPET_TN_32BIT; 123 + hpet_write(HPET_T0_CFG, cfg); 124 + break; 125 + case CLOCK_EVT_MODE_RESUME: 126 + hpet_enable_legacy_int(); 127 + break; 128 + } 129 + spin_unlock(&hpet_lock); 130 + } 131 + 132 + static int hpet_next_event(unsigned long delta, 133 + struct clock_event_device *evt) 134 + { 135 + unsigned int cnt; 136 + int res; 137 + 138 + cnt = hpet_read(HPET_COUNTER); 139 + cnt += delta; 140 + hpet_write(HPET_T0_CMP, cnt); 141 + 142 + res = ((int)(hpet_read(HPET_COUNTER) - cnt) > 0) ? -ETIME : 0; 143 + return res; 144 + } 145 + 146 + static irqreturn_t hpet_irq_handler(int irq, void *data) 147 + { 148 + int is_irq; 149 + struct clock_event_device *cd; 150 + unsigned int cpu = smp_processor_id(); 151 + 152 + is_irq = hpet_read(HPET_STATUS); 153 + if (is_irq & HPET_T0_IRS) { 154 + /* clear the TIMER0 irq status register */ 155 + hpet_write(HPET_STATUS, HPET_T0_IRS); 156 + cd = &per_cpu(hpet_clockevent_device, cpu); 157 + cd->event_handler(cd); 158 + return IRQ_HANDLED; 159 + } 160 + return IRQ_NONE; 161 + } 162 + 163 + static struct irqaction hpet_irq = { 164 + .handler = hpet_irq_handler, 165 + .flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_TIMER, 166 + .name = "hpet", 167 + }; 168 + 169 + /* 170 + * hpet address assignation and irq setting should be done in bios. 171 + * but pmon don't do this, we just setup here directly. 172 + * The operation under is normal. unfortunately, hpet_setup process 173 + * is before pci initialize. 174 + * 175 + * { 176 + * struct pci_dev *pdev; 177 + * 178 + * pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); 179 + * pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR); 180 + * 181 + * ... 182 + * } 183 + */ 184 + static void hpet_setup(void) 185 + { 186 + /* set hpet base address */ 187 + smbus_write(SMBUS_PCI_REGB4, HPET_ADDR); 188 + 189 + /* enable decodeing of access to HPET MMIO*/ 190 + smbus_enable(SMBUS_PCI_REG40, (1 << 28)); 191 + 192 + /* HPET irq enable */ 193 + smbus_enable(SMBUS_PCI_REG64, (1 << 10)); 194 + 195 + hpet_enable_legacy_int(); 196 + } 197 + 198 + void __init setup_hpet_timer(void) 199 + { 200 + unsigned int cpu = smp_processor_id(); 201 + struct clock_event_device *cd; 202 + 203 + hpet_setup(); 204 + 205 + cd = &per_cpu(hpet_clockevent_device, cpu); 206 + cd->name = "hpet"; 207 + cd->rating = 320; 208 + cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; 209 + cd->set_mode = hpet_set_mode; 210 + cd->set_next_event = hpet_next_event; 211 + cd->irq = HPET_T0_IRQ; 212 + cd->cpumask = cpumask_of(cpu); 213 + clockevent_set_clock(cd, HPET_FREQ); 214 + cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd); 215 + cd->min_delta_ns = 5000; 216 + 217 + clockevents_register_device(cd); 218 + setup_irq(HPET_T0_IRQ, &hpet_irq); 219 + pr_info("hpet clock event device register\n"); 220 + } 221 + 222 + static cycle_t hpet_read_counter(struct clocksource *cs) 223 + { 224 + return (cycle_t)hpet_read(HPET_COUNTER); 225 + } 226 + 227 + static void hpet_suspend(struct clocksource *cs) 228 + { 229 + } 230 + 231 + static void hpet_resume(struct clocksource *cs) 232 + { 233 + hpet_setup(); 234 + hpet_restart_counter(); 235 + } 236 + 237 + static struct clocksource csrc_hpet = { 238 + .name = "hpet", 239 + /* mips clocksource rating is less than 300, so hpet is better. */ 240 + .rating = 300, 241 + .read = hpet_read_counter, 242 + .mask = CLOCKSOURCE_MASK(32), 243 + /* oneshot mode work normal with this flag */ 244 + .flags = CLOCK_SOURCE_IS_CONTINUOUS, 245 + .suspend = hpet_suspend, 246 + .resume = hpet_resume, 247 + .mult = 0, 248 + .shift = 10, 249 + }; 250 + 251 + int __init init_hpet_clocksource(void) 252 + { 253 + csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift); 254 + return clocksource_register_hz(&csrc_hpet, HPET_FREQ); 255 + } 256 + 257 + arch_initcall(init_hpet_clocksource);
+1 -1
arch/mips/loongson/loongson-3/irq.c
··· 9 9 10 10 #include "smp.h" 11 11 12 - unsigned int ht_irq[] = {1, 3, 4, 5, 6, 7, 8, 12, 14, 15}; 12 + unsigned int ht_irq[] = {0, 1, 3, 4, 5, 6, 7, 8, 12, 14, 15}; 13 13 14 14 static void ht_irqdispatch(void) 15 15 {