linux/arch/mips/loongson64/loongson-3/hpet.c
<<
>>
Prefs
   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
  16static DEFINE_SPINLOCK(hpet_lock);
  17DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device);
  18
  19static unsigned int smbus_read(int offset)
  20{
  21        return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset);
  22}
  23
  24static void smbus_write(int offset, int data)
  25{
  26        *(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data;
  27}
  28
  29static 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
  37static int hpet_read(int offset)
  38{
  39        return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset);
  40}
  41
  42static void hpet_write(int offset, int data)
  43{
  44        *(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data;
  45}
  46
  47static 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
  55static 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
  63static void hpet_reset_counter(void)
  64{
  65        hpet_write(HPET_COUNTER, 0);
  66        hpet_write(HPET_COUNTER + 4, 0);
  67}
  68
  69static void hpet_restart_counter(void)
  70{
  71        hpet_stop_counter();
  72        hpet_reset_counter();
  73        hpet_start_counter();
  74}
  75
  76static void hpet_enable_legacy_int(void)
  77{
  78        /* Do nothing on Loongson-3 */
  79}
  80
  81static int hpet_set_state_periodic(struct clock_event_device *evt)
  82{
  83        int cfg;
  84
  85        spin_lock(&hpet_lock);
  86
  87        pr_info("set clock event to periodic mode!\n");
  88        /* stop counter */
  89        hpet_stop_counter();
  90
  91        /* enables the timer0 to generate a periodic interrupt */
  92        cfg = hpet_read(HPET_T0_CFG);
  93        cfg &= ~HPET_TN_LEVEL;
  94        cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL |
  95                HPET_TN_32BIT;
  96        hpet_write(HPET_T0_CFG, cfg);
  97
  98        /* set the comparator */
  99        hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
 100        udelay(1);
 101        hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
 102
 103        /* start counter */
 104        hpet_start_counter();
 105
 106        spin_unlock(&hpet_lock);
 107        return 0;
 108}
 109
 110static int hpet_set_state_shutdown(struct clock_event_device *evt)
 111{
 112        int cfg;
 113
 114        spin_lock(&hpet_lock);
 115
 116        cfg = hpet_read(HPET_T0_CFG);
 117        cfg &= ~HPET_TN_ENABLE;
 118        hpet_write(HPET_T0_CFG, cfg);
 119
 120        spin_unlock(&hpet_lock);
 121        return 0;
 122}
 123
 124static int hpet_set_state_oneshot(struct clock_event_device *evt)
 125{
 126        int cfg;
 127
 128        spin_lock(&hpet_lock);
 129
 130        pr_info("set clock event to one shot mode!\n");
 131        cfg = hpet_read(HPET_T0_CFG);
 132        /*
 133         * set timer0 type
 134         * 1 : periodic interrupt
 135         * 0 : non-periodic(oneshot) interrupt
 136         */
 137        cfg &= ~HPET_TN_PERIODIC;
 138        cfg |= HPET_TN_ENABLE | HPET_TN_32BIT;
 139        hpet_write(HPET_T0_CFG, cfg);
 140
 141        spin_unlock(&hpet_lock);
 142        return 0;
 143}
 144
 145static int hpet_tick_resume(struct clock_event_device *evt)
 146{
 147        spin_lock(&hpet_lock);
 148        hpet_enable_legacy_int();
 149        spin_unlock(&hpet_lock);
 150
 151        return 0;
 152}
 153
 154static int hpet_next_event(unsigned long delta,
 155                struct clock_event_device *evt)
 156{
 157        unsigned int cnt;
 158        int res;
 159
 160        cnt = hpet_read(HPET_COUNTER);
 161        cnt += delta;
 162        hpet_write(HPET_T0_CMP, cnt);
 163
 164        res = ((int)(hpet_read(HPET_COUNTER) - cnt) > 0) ? -ETIME : 0;
 165        return res;
 166}
 167
 168static irqreturn_t hpet_irq_handler(int irq, void *data)
 169{
 170        int is_irq;
 171        struct clock_event_device *cd;
 172        unsigned int cpu = smp_processor_id();
 173
 174        is_irq = hpet_read(HPET_STATUS);
 175        if (is_irq & HPET_T0_IRS) {
 176                /* clear the TIMER0 irq status register */
 177                hpet_write(HPET_STATUS, HPET_T0_IRS);
 178                cd = &per_cpu(hpet_clockevent_device, cpu);
 179                cd->event_handler(cd);
 180                return IRQ_HANDLED;
 181        }
 182        return IRQ_NONE;
 183}
 184
 185static struct irqaction hpet_irq = {
 186        .handler = hpet_irq_handler,
 187        .flags = IRQF_NOBALANCING | IRQF_TIMER,
 188        .name = "hpet",
 189};
 190
 191/*
 192 * hpet address assignation and irq setting should be done in bios.
 193 * but pmon don't do this, we just setup here directly.
 194 * The operation under is normal. unfortunately, hpet_setup process
 195 * is before pci initialize.
 196 *
 197 * {
 198 *      struct pci_dev *pdev;
 199 *
 200 *      pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
 201 *      pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR);
 202 *
 203 *      ...
 204 * }
 205 */
 206static void hpet_setup(void)
 207{
 208        /* set hpet base address */
 209        smbus_write(SMBUS_PCI_REGB4, HPET_ADDR);
 210
 211        /* enable decodeing of access to HPET MMIO*/
 212        smbus_enable(SMBUS_PCI_REG40, (1 << 28));
 213
 214        /* HPET irq enable */
 215        smbus_enable(SMBUS_PCI_REG64, (1 << 10));
 216
 217        hpet_enable_legacy_int();
 218}
 219
 220void __init setup_hpet_timer(void)
 221{
 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 = 320;
 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->min_delta_ns = 5000;
 241
 242        clockevents_register_device(cd);
 243        setup_irq(HPET_T0_IRQ, &hpet_irq);
 244        pr_info("hpet clock event device register\n");
 245}
 246
 247static cycle_t hpet_read_counter(struct clocksource *cs)
 248{
 249        return (cycle_t)hpet_read(HPET_COUNTER);
 250}
 251
 252static void hpet_suspend(struct clocksource *cs)
 253{
 254}
 255
 256static void hpet_resume(struct clocksource *cs)
 257{
 258        hpet_setup();
 259        hpet_restart_counter();
 260}
 261
 262static struct clocksource csrc_hpet = {
 263        .name = "hpet",
 264        /* mips clocksource rating is less than 300, so hpet is better. */
 265        .rating = 300,
 266        .read = hpet_read_counter,
 267        .mask = CLOCKSOURCE_MASK(32),
 268        /* oneshot mode work normal with this flag */
 269        .flags = CLOCK_SOURCE_IS_CONTINUOUS,
 270        .suspend = hpet_suspend,
 271        .resume = hpet_resume,
 272        .mult = 0,
 273        .shift = 10,
 274};
 275
 276int __init init_hpet_clocksource(void)
 277{
 278        csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift);
 279        return clocksource_register_hz(&csrc_hpet, HPET_FREQ);
 280}
 281
 282arch_initcall(init_hpet_clocksource);
 283