linux/arch/mips/loongson64/hpet.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#include <linux/init.h>
   3#include <linux/pci.h>
   4#include <linux/percpu.h>
   5#include <linux/delay.h>
   6#include <linux/spinlock.h>
   7#include <linux/interrupt.h>
   8
   9#include <asm/hpet.h>
  10#include <asm/time.h>
  11
  12#define SMBUS_CFG_BASE          (loongson_sysconf.ht_control_base + 0x0300a000)
  13#define SMBUS_PCI_REG40         0x40
  14#define SMBUS_PCI_REG64         0x64
  15#define SMBUS_PCI_REGB4         0xb4
  16
  17#define HPET_MIN_CYCLES         16
  18#define HPET_MIN_PROG_DELTA     (HPET_MIN_CYCLES * 12)
  19
  20static DEFINE_SPINLOCK(hpet_lock);
  21DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device);
  22
  23static unsigned int smbus_read(int offset)
  24{
  25        return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset);
  26}
  27
  28static void smbus_write(int offset, int data)
  29{
  30        *(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data;
  31}
  32
  33static void smbus_enable(int offset, int bit)
  34{
  35        unsigned int cfg = smbus_read(offset);
  36
  37        cfg |= bit;
  38        smbus_write(offset, cfg);
  39}
  40
  41static int hpet_read(int offset)
  42{
  43        return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset);
  44}
  45
  46static void hpet_write(int offset, int data)
  47{
  48        *(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data;
  49}
  50
  51static void hpet_start_counter(void)
  52{
  53        unsigned int cfg = hpet_read(HPET_CFG);
  54
  55        cfg |= HPET_CFG_ENABLE;
  56        hpet_write(HPET_CFG, cfg);
  57}
  58
  59static void hpet_stop_counter(void)
  60{
  61        unsigned int cfg = hpet_read(HPET_CFG);
  62
  63        cfg &= ~HPET_CFG_ENABLE;
  64        hpet_write(HPET_CFG, cfg);
  65}
  66
  67static void hpet_reset_counter(void)
  68{
  69        hpet_write(HPET_COUNTER, 0);
  70        hpet_write(HPET_COUNTER + 4, 0);
  71}
  72
  73static void hpet_restart_counter(void)
  74{
  75        hpet_stop_counter();
  76        hpet_reset_counter();
  77        hpet_start_counter();
  78}
  79
  80static void hpet_enable_legacy_int(void)
  81{
  82        /* Do nothing on Loongson-3 */
  83}
  84
  85static int hpet_set_state_periodic(struct clock_event_device *evt)
  86{
  87        int cfg;
  88
  89        spin_lock(&hpet_lock);
  90
  91        pr_info("set clock event to periodic mode!\n");
  92        /* stop counter */
  93        hpet_stop_counter();
  94
  95        /* enables the timer0 to generate a periodic interrupt */
  96        cfg = hpet_read(HPET_T0_CFG);
  97        cfg &= ~HPET_TN_LEVEL;
  98        cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL |
  99                HPET_TN_32BIT;
 100        hpet_write(HPET_T0_CFG, cfg);
 101
 102        /* set the comparator */
 103        hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
 104        udelay(1);
 105        hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
 106
 107        /* start counter */
 108        hpet_start_counter();
 109
 110        spin_unlock(&hpet_lock);
 111        return 0;
 112}
 113
 114static int hpet_set_state_shutdown(struct clock_event_device *evt)
 115{
 116        int cfg;
 117
 118        spin_lock(&hpet_lock);
 119
 120        cfg = hpet_read(HPET_T0_CFG);
 121        cfg &= ~HPET_TN_ENABLE;
 122        hpet_write(HPET_T0_CFG, cfg);
 123
 124        spin_unlock(&hpet_lock);
 125        return 0;
 126}
 127
 128static int hpet_set_state_oneshot(struct clock_event_device *evt)
 129{
 130        int cfg;
 131
 132        spin_lock(&hpet_lock);
 133
 134        pr_info("set clock event to one shot mode!\n");
 135        cfg = hpet_read(HPET_T0_CFG);
 136        /*
 137         * set timer0 type
 138         * 1 : periodic interrupt
 139         * 0 : non-periodic(oneshot) interrupt
 140         */
 141        cfg &= ~HPET_TN_PERIODIC;
 142        cfg |= HPET_TN_ENABLE | HPET_TN_32BIT;
 143        hpet_write(HPET_T0_CFG, cfg);
 144
 145        spin_unlock(&hpet_lock);
 146        return 0;
 147}
 148
 149static int hpet_tick_resume(struct clock_event_device *evt)
 150{
 151        spin_lock(&hpet_lock);
 152        hpet_enable_legacy_int();
 153        spin_unlock(&hpet_lock);
 154
 155        return 0;
 156}
 157
 158static int hpet_next_event(unsigned long delta,
 159                struct clock_event_device *evt)
 160{
 161        u32 cnt;
 162        s32 res;
 163
 164        cnt = hpet_read(HPET_COUNTER);
 165        cnt += (u32) delta;
 166        hpet_write(HPET_T0_CMP, cnt);
 167
 168        res = (s32)(cnt - hpet_read(HPET_COUNTER));
 169
 170        return res < HPET_MIN_CYCLES ? -ETIME : 0;
 171}
 172
 173static irqreturn_t hpet_irq_handler(int irq, void *data)
 174{
 175        int is_irq;
 176        struct clock_event_device *cd;
 177        unsigned int cpu = smp_processor_id();
 178
 179        is_irq = hpet_read(HPET_STATUS);
 180        if (is_irq & HPET_T0_IRS) {
 181                /* clear the TIMER0 irq status register */
 182                hpet_write(HPET_STATUS, HPET_T0_IRS);
 183                cd = &per_cpu(hpet_clockevent_device, cpu);
 184                cd->event_handler(cd);
 185                return IRQ_HANDLED;
 186        }
 187        return IRQ_NONE;
 188}
 189
 190/*
 191 * hpet address assignation and irq setting should be done in bios.
 192 * but pmon don't do this, we just setup here directly.
 193 * The operation under is normal. unfortunately, hpet_setup process
 194 * is before pci initialize.
 195 *
 196 * {
 197 *      struct pci_dev *pdev;
 198 *
 199 *      pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
 200 *      pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR);
 201 *
 202 *      ...
 203 * }
 204 */
 205static void hpet_setup(void)
 206{
 207        /* set hpet base address */
 208        smbus_write(SMBUS_PCI_REGB4, HPET_ADDR);
 209
 210        /* enable decoding of access to HPET MMIO*/
 211        smbus_enable(SMBUS_PCI_REG40, (1 << 28));
 212
 213        /* HPET irq enable */
 214        smbus_enable(SMBUS_PCI_REG64, (1 << 10));
 215
 216        hpet_enable_legacy_int();
 217}
 218
 219void __init setup_hpet_timer(void)
 220{
 221        unsigned long flags = IRQF_NOBALANCING | IRQF_TIMER;
 222        unsigned int cpu = smp_processor_id();
 223        struct clock_event_device *cd;
 224
 225        hpet_setup();
 226
 227        cd = &per_cpu(hpet_clockevent_device, cpu);
 228        cd->name = "hpet";
 229        cd->rating = 100;
 230        cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
 231        cd->set_state_shutdown = hpet_set_state_shutdown;
 232        cd->set_state_periodic = hpet_set_state_periodic;
 233        cd->set_state_oneshot = hpet_set_state_oneshot;
 234        cd->tick_resume = hpet_tick_resume;
 235        cd->set_next_event = hpet_next_event;
 236        cd->irq = HPET_T0_IRQ;
 237        cd->cpumask = cpumask_of(cpu);
 238        clockevent_set_clock(cd, HPET_FREQ);
 239        cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd);
 240        cd->max_delta_ticks = 0x7fffffff;
 241        cd->min_delta_ns = clockevent_delta2ns(HPET_MIN_PROG_DELTA, cd);
 242        cd->min_delta_ticks = HPET_MIN_PROG_DELTA;
 243
 244        clockevents_register_device(cd);
 245        if (request_irq(HPET_T0_IRQ, hpet_irq_handler, flags, "hpet", NULL))
 246                pr_err("Failed to request irq %d (hpet)\n", HPET_T0_IRQ);
 247        pr_info("hpet clock event device register\n");
 248}
 249
 250static u64 hpet_read_counter(struct clocksource *cs)
 251{
 252        return (u64)hpet_read(HPET_COUNTER);
 253}
 254
 255static void hpet_suspend(struct clocksource *cs)
 256{
 257}
 258
 259static void hpet_resume(struct clocksource *cs)
 260{
 261        hpet_setup();
 262        hpet_restart_counter();
 263}
 264
 265static struct clocksource csrc_hpet = {
 266        .name = "hpet",
 267        /* mips clocksource rating is less than 300, so hpet is better. */
 268        .rating = 300,
 269        .read = hpet_read_counter,
 270        .mask = CLOCKSOURCE_MASK(32),
 271        /* oneshot mode work normal with this flag */
 272        .flags = CLOCK_SOURCE_IS_CONTINUOUS,
 273        .suspend = hpet_suspend,
 274        .resume = hpet_resume,
 275        .mult = 0,
 276        .shift = 10,
 277};
 278
 279int __init init_hpet_clocksource(void)
 280{
 281        csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift);
 282        return clocksource_register_hz(&csrc_hpet, HPET_FREQ);
 283}
 284
 285arch_initcall(init_hpet_clocksource);
 286