Path: blob/master/drivers/clocksource/cs5535-clockevt.c
15109 views
/*1* Clock event driver for the CS5535/CS55362*3* Copyright (C) 2006, Advanced Micro Devices, Inc.4* Copyright (C) 2007 Andres Salomon <[email protected]>5* Copyright (C) 2009 Andres Salomon <[email protected]>6*7* This program is free software; you can redistribute it and/or8* modify it under the terms of version 2 of the GNU General Public License9* as published by the Free Software Foundation.10*11* The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book.12*/1314#include <linux/kernel.h>15#include <linux/irq.h>16#include <linux/interrupt.h>17#include <linux/module.h>18#include <linux/cs5535.h>19#include <linux/clockchips.h>2021#define DRV_NAME "cs5535-clockevt"2223static int timer_irq;24module_param_named(irq, timer_irq, int, 0644);25MODULE_PARM_DESC(irq, "Which IRQ to use for the clock source MFGPT ticks.");2627/*28* We are using the 32.768kHz input clock - it's the only one that has the29* ranges we find desirable. The following table lists the suitable30* divisors and the associated Hz, minimum interval and the maximum interval:31*32* Divisor Hz Min Delta (s) Max Delta (s)33* 1 32768 .00048828125 2.00034* 2 16384 .0009765625 4.00035* 4 8192 .001953125 8.00036* 8 4096 .00390625 16.00037* 16 2048 .0078125 32.00038* 32 1024 .015625 64.00039* 64 512 .03125 128.00040* 128 256 .0625 256.00041* 256 128 .125 512.00042*/4344static unsigned int cs5535_tick_mode = CLOCK_EVT_MODE_SHUTDOWN;45static struct cs5535_mfgpt_timer *cs5535_event_clock;4647/* Selected from the table above */4849#define MFGPT_DIVISOR 1650#define MFGPT_SCALE 4 /* divisor = 2^(scale) */51#define MFGPT_HZ (32768 / MFGPT_DIVISOR)52#define MFGPT_PERIODIC (MFGPT_HZ / HZ)5354/*55* The MFPGT timers on the CS5536 provide us with suitable timers to use56* as clock event sources - not as good as a HPET or APIC, but certainly57* better than the PIT. This isn't a general purpose MFGPT driver, but58* a simplified one designed specifically to act as a clock event source.59* For full details about the MFGPT, please consult the CS5536 data sheet.60*/6162static void disable_timer(struct cs5535_mfgpt_timer *timer)63{64/* avoid races by clearing CMP1 and CMP2 unconditionally */65cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,66(uint16_t) ~MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP1 |67MFGPT_SETUP_CMP2);68}6970static void start_timer(struct cs5535_mfgpt_timer *timer, uint16_t delta)71{72cs5535_mfgpt_write(timer, MFGPT_REG_CMP2, delta);73cs5535_mfgpt_write(timer, MFGPT_REG_COUNTER, 0);7475cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,76MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);77}7879static void mfgpt_set_mode(enum clock_event_mode mode,80struct clock_event_device *evt)81{82disable_timer(cs5535_event_clock);8384if (mode == CLOCK_EVT_MODE_PERIODIC)85start_timer(cs5535_event_clock, MFGPT_PERIODIC);8687cs5535_tick_mode = mode;88}8990static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt)91{92start_timer(cs5535_event_clock, delta);93return 0;94}9596static struct clock_event_device cs5535_clockevent = {97.name = DRV_NAME,98.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,99.set_mode = mfgpt_set_mode,100.set_next_event = mfgpt_next_event,101.rating = 250,102.cpumask = cpu_all_mask,103.shift = 32104};105106static irqreturn_t mfgpt_tick(int irq, void *dev_id)107{108uint16_t val = cs5535_mfgpt_read(cs5535_event_clock, MFGPT_REG_SETUP);109110/* See if the interrupt was for us */111if (!(val & (MFGPT_SETUP_SETUP | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1)))112return IRQ_NONE;113114/* Turn off the clock (and clear the event) */115disable_timer(cs5535_event_clock);116117if (cs5535_tick_mode == CLOCK_EVT_MODE_SHUTDOWN)118return IRQ_HANDLED;119120/* Clear the counter */121cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_COUNTER, 0);122123/* Restart the clock in periodic mode */124125if (cs5535_tick_mode == CLOCK_EVT_MODE_PERIODIC)126cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP,127MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);128129cs5535_clockevent.event_handler(&cs5535_clockevent);130return IRQ_HANDLED;131}132133static struct irqaction mfgptirq = {134.handler = mfgpt_tick,135.flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_TIMER,136.name = DRV_NAME,137};138139static int __init cs5535_mfgpt_init(void)140{141struct cs5535_mfgpt_timer *timer;142int ret;143uint16_t val;144145timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);146if (!timer) {147printk(KERN_ERR DRV_NAME ": Could not allocate MFPGT timer\n");148return -ENODEV;149}150cs5535_event_clock = timer;151152/* Set up the IRQ on the MFGPT side */153if (cs5535_mfgpt_setup_irq(timer, MFGPT_CMP2, &timer_irq)) {154printk(KERN_ERR DRV_NAME ": Could not set up IRQ %d\n",155timer_irq);156goto err_timer;157}158159/* And register it with the kernel */160ret = setup_irq(timer_irq, &mfgptirq);161if (ret) {162printk(KERN_ERR DRV_NAME ": Unable to set up the interrupt.\n");163goto err_irq;164}165166/* Set the clock scale and enable the event mode for CMP2 */167val = MFGPT_SCALE | (3 << 8);168169cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP, val);170171/* Set up the clock event */172cs5535_clockevent.mult = div_sc(MFGPT_HZ, NSEC_PER_SEC,173cs5535_clockevent.shift);174cs5535_clockevent.min_delta_ns = clockevent_delta2ns(0xF,175&cs5535_clockevent);176cs5535_clockevent.max_delta_ns = clockevent_delta2ns(0xFFFE,177&cs5535_clockevent);178179printk(KERN_INFO DRV_NAME180": Registering MFGPT timer as a clock event, using IRQ %d\n",181timer_irq);182clockevents_register_device(&cs5535_clockevent);183184return 0;185186err_irq:187cs5535_mfgpt_release_irq(cs5535_event_clock, MFGPT_CMP2, &timer_irq);188err_timer:189cs5535_mfgpt_free_timer(cs5535_event_clock);190printk(KERN_ERR DRV_NAME ": Unable to set up the MFGPT clock source\n");191return -EIO;192}193194module_init(cs5535_mfgpt_init);195196MODULE_AUTHOR("Andres Salomon <[email protected]>");197MODULE_DESCRIPTION("CS5535/CS5536 MFGPT clock event driver");198MODULE_LICENSE("GPL");199200201