2007-10-12 23:04:06 +02:00
/*
* Driver / API for AMD Geode Multi - Function General Purpose Timers ( MFGPT )
*
* Copyright ( C ) 2006 , Advanced Micro Devices , Inc .
* Copyright ( C ) 2007 , Andres Salomon < dilinger @ debian . org >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation .
*
* The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book .
*/
/*
2008-02-09 23:24:08 +01:00
* We are using the 32.768 kHz input clock - it ' s the only one that has the
2007-10-12 23:04:06 +02:00
* ranges we find desirable . The following table lists the suitable
2008-02-09 23:24:08 +01:00
* divisors and the associated Hz , minimum interval and the maximum interval :
2007-10-12 23:04:06 +02:00
*
2008-02-09 23:24:08 +01:00
* Divisor Hz Min Delta ( s ) Max Delta ( s )
* 1 32768 .00048828125 2.000
* 2 16384 .0009765625 4.000
* 4 8192 .001953125 8.000
* 8 4096 .00390625 16.000
* 16 2048 .0078125 32.000
* 32 1024 .015625 64.000
* 64 512 .03125 128.000
* 128 256 .0625 256.000
* 256 128 .125 512.000
2007-10-12 23:04:06 +02:00
*/
# include <linux/kernel.h>
# include <linux/interrupt.h>
2008-03-26 14:13:01 -04:00
# include <linux/module.h>
2007-10-12 23:04:06 +02:00
# include <asm/geode.h>
2008-08-04 14:40:16 +02:00
# define MFGPT_DEFAULT_IRQ 7
2007-10-12 23:04:06 +02:00
static struct mfgpt_timer_t {
2008-02-09 23:24:08 +01:00
unsigned int avail : 1 ;
2007-10-12 23:04:06 +02:00
} mfgpt_timers [ MFGPT_MAX_TIMERS ] ;
/* Selected from the table above */
# define MFGPT_DIVISOR 16
# define MFGPT_SCALE 4 /* divisor = 2^(scale) */
2008-02-09 23:24:08 +01:00
# define MFGPT_HZ (32768 / MFGPT_DIVISOR)
2007-10-12 23:04:06 +02:00
# define MFGPT_PERIODIC (MFGPT_HZ / HZ)
/* Allow for disabling of MFGPTs */
static int disable ;
static int __init mfgpt_disable ( char * s )
{
disable = 1 ;
return 1 ;
}
__setup ( " nomfgpt " , mfgpt_disable ) ;
2008-01-30 13:33:33 +01:00
/* Reset the MFGPT timers. This is required by some broken BIOSes which already
* do the same and leave the system in an unstable state . TinyBIOS 0.98 is
* affected at least ( 0.99 is OK with MFGPT workaround left to off ) .
*/
static int __init mfgpt_fix ( char * s )
{
u32 val , dummy ;
/* The following udocumented bit resets the MFGPT timers */
val = 0xFF ; dummy = 0 ;
2008-04-28 02:14:53 -07:00
wrmsr ( MSR_MFGPT_SETUP , val , dummy ) ;
2008-01-30 13:33:33 +01:00
return 1 ;
}
__setup ( " mfgptfix " , mfgpt_fix ) ;
2007-10-12 23:04:06 +02:00
/*
* Check whether any MFGPTs are available for the kernel to use . In most
* cases , firmware that uses AMD ' s VSA code will claim all timers during
* bootup ; we certainly don ' t want to take them if they ' re already in use .
* In other cases ( such as with VSAless OpenFirmware ) , the system firmware
* leaves timers available for us to use .
*/
2008-02-09 23:24:08 +01:00
static int timers = - 1 ;
static void geode_mfgpt_detect ( void )
2007-10-12 23:04:06 +02:00
{
2008-02-09 23:24:08 +01:00
int i ;
2007-10-12 23:04:06 +02:00
u16 val ;
2008-02-09 23:24:08 +01:00
timers = 0 ;
2007-10-12 23:04:06 +02:00
if ( disable ) {
2008-02-09 23:24:08 +01:00
printk ( KERN_INFO " geode-mfgpt: MFGPT support is disabled \n " ) ;
goto done ;
}
if ( ! geode_get_dev_base ( GEODE_DEV_MFGPT ) ) {
printk ( KERN_INFO " geode-mfgpt: MFGPT LBAR is not set up \n " ) ;
goto done ;
2007-10-12 23:04:06 +02:00
}
for ( i = 0 ; i < MFGPT_MAX_TIMERS ; i + + ) {
val = geode_mfgpt_read ( i , MFGPT_REG_SETUP ) ;
if ( ! ( val & MFGPT_SETUP_SETUP ) ) {
2008-02-09 23:24:08 +01:00
mfgpt_timers [ i ] . avail = 1 ;
2008-02-09 23:24:08 +01:00
timers + + ;
2007-10-12 23:04:06 +02:00
}
}
2008-02-09 23:24:08 +01:00
done :
printk ( KERN_INFO " geode-mfgpt: %d MFGPT timers available. \n " , timers ) ;
2007-10-12 23:04:06 +02:00
}
int geode_mfgpt_toggle_event ( int timer , int cmp , int event , int enable )
{
u32 msr , mask , value , dummy ;
int shift = ( cmp = = MFGPT_CMP1 ) ? 0 : 8 ;
if ( timer < 0 | | timer > = MFGPT_MAX_TIMERS )
return - EIO ;
/*
* The register maps for these are described in sections 6.17 .1 . x of
* the AMD Geode CS5536 Companion Device Data Book .
*/
switch ( event ) {
case MFGPT_EVENT_RESET :
/*
* XXX : According to the docs , we cannot reset timers above
* 6 ; that is , resets for 7 and 8 will be ignored . Is this
* a problem ? - dilinger
*/
2008-04-28 02:14:53 -07:00
msr = MSR_MFGPT_NR ;
2007-10-12 23:04:06 +02:00
mask = 1 < < ( timer + 24 ) ;
break ;
case MFGPT_EVENT_NMI :
2008-04-28 02:14:53 -07:00
msr = MSR_MFGPT_NR ;
2007-10-12 23:04:06 +02:00
mask = 1 < < ( timer + shift ) ;
break ;
case MFGPT_EVENT_IRQ :
2008-04-28 02:14:53 -07:00
msr = MSR_MFGPT_IRQ ;
2007-10-12 23:04:06 +02:00
mask = 1 < < ( timer + shift ) ;
break ;
default :
return - EIO ;
}
rdmsr ( msr , value , dummy ) ;
if ( enable )
value | = mask ;
else
value & = ~ mask ;
wrmsr ( msr , value , dummy ) ;
return 0 ;
}
2008-06-04 18:13:37 +02:00
EXPORT_SYMBOL_GPL ( geode_mfgpt_toggle_event ) ;
2007-10-12 23:04:06 +02:00
2008-08-04 14:40:16 +02:00
int geode_mfgpt_set_irq ( int timer , int cmp , int * irq , int enable )
2007-10-12 23:04:06 +02:00
{
2008-08-04 14:40:16 +02:00
u32 zsel , lpc , dummy ;
int shift ;
2007-10-12 23:04:06 +02:00
if ( timer < 0 | | timer > = MFGPT_MAX_TIMERS )
return - EIO ;
2008-08-04 14:40:16 +02:00
/*
* Unfortunately , MFGPTs come in pairs sharing their IRQ lines . If VSA
* is using the same CMP of the timer ' s Siamese twin , the IRQ is set to
* 2 , and we mustn ' t use nor change it .
* XXX : Likewise , 2 Linux drivers might clash if the 2 nd overwrites the
* IRQ of the 1 st . This can only happen if forcing an IRQ , calling this
* with * irq = = 0 is safe . Currently there _are_ no 2 drivers .
*/
rdmsr ( MSR_PIC_ZSEL_LOW , zsel , dummy ) ;
shift = ( ( cmp = = MFGPT_CMP1 ? 0 : 4 ) + timer % 4 ) * 4 ;
if ( ( ( zsel > > shift ) & 0xF ) = = 2 )
2007-10-12 23:04:06 +02:00
return - EIO ;
2008-08-04 14:40:16 +02:00
/* Choose IRQ: if none supplied, keep IRQ already set or use default */
if ( ! * irq )
* irq = ( zsel > > shift ) & 0xF ;
if ( ! * irq )
* irq = MFGPT_DEFAULT_IRQ ;
2007-10-12 23:04:06 +02:00
2008-08-04 14:40:16 +02:00
/* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */
if ( * irq < 1 | | * irq = = 2 | | * irq > 15 )
return - EIO ;
rdmsr ( MSR_PIC_IRQM_LPC , lpc , dummy ) ;
if ( lpc & ( 1 < < * irq ) )
return - EIO ;
2007-10-12 23:04:06 +02:00
2008-08-04 14:40:16 +02:00
/* All chosen and checked - go for it */
if ( geode_mfgpt_toggle_event ( timer , cmp , MFGPT_EVENT_IRQ , enable ) )
return - EIO ;
2007-10-12 23:04:06 +02:00
if ( enable ) {
2008-08-04 14:40:16 +02:00
zsel = ( zsel & ~ ( 0xF < < shift ) ) | ( * irq < < shift ) ;
wrmsr ( MSR_PIC_ZSEL_LOW , zsel , dummy ) ;
2007-10-12 23:04:06 +02:00
}
return 0 ;
}
2008-02-09 23:24:08 +01:00
static int mfgpt_get ( int timer )
2007-10-12 23:04:06 +02:00
{
2008-02-09 23:24:08 +01:00
mfgpt_timers [ timer ] . avail = 0 ;
2007-10-12 23:04:06 +02:00
printk ( KERN_INFO " geode-mfgpt: Registered timer %d \n " , timer ) ;
return timer ;
}
2008-02-09 23:24:08 +01:00
int geode_mfgpt_alloc_timer ( int timer , int domain )
2007-10-12 23:04:06 +02:00
{
int i ;
2008-02-09 23:24:08 +01:00
if ( timers = = - 1 ) {
/* timers haven't been detected yet */
geode_mfgpt_detect ( ) ;
}
if ( ! timers )
return - 1 ;
2007-10-12 23:04:06 +02:00
if ( timer > = MFGPT_MAX_TIMERS )
2008-02-09 23:24:08 +01:00
return - 1 ;
2007-10-12 23:04:06 +02:00
if ( timer < 0 ) {
/* Try to find an available timer */
for ( i = 0 ; i < MFGPT_MAX_TIMERS ; i + + ) {
2008-02-09 23:24:08 +01:00
if ( mfgpt_timers [ i ] . avail )
2008-02-09 23:24:08 +01:00
return mfgpt_get ( i ) ;
2007-10-12 23:04:06 +02:00
if ( i = = 5 & & domain = = MFGPT_DOMAIN_WORKING )
break ;
}
} else {
/* If they requested a specific timer, try to honor that */
2008-02-09 23:24:08 +01:00
if ( mfgpt_timers [ timer ] . avail )
2008-02-09 23:24:08 +01:00
return mfgpt_get ( timer ) ;
2007-10-12 23:04:06 +02:00
}
/* No timers available - too bad */
return - 1 ;
}
2008-06-04 18:13:37 +02:00
EXPORT_SYMBOL_GPL ( geode_mfgpt_alloc_timer ) ;
2007-10-12 23:04:06 +02:00
2007-10-12 23:04:06 +02:00
# ifdef CONFIG_GEODE_MFGPT_TIMER
/*
* The MFPGT timers on the CS5536 provide us with suitable timers to use
* as clock event sources - not as good as a HPET or APIC , but certainly
2008-10-16 19:02:37 +02:00
* better than the PIT . This isn ' t a general purpose MFGPT driver , but
2007-10-12 23:04:06 +02:00
* a simplified one designed specifically to act as a clock event source .
* For full details about the MFGPT , please consult the CS5536 data sheet .
*/
# include <linux/clocksource.h>
# include <linux/clockchips.h>
static unsigned int mfgpt_tick_mode = CLOCK_EVT_MODE_SHUTDOWN ;
static u16 mfgpt_event_clock ;
2008-08-04 14:40:16 +02:00
static int irq ;
2007-10-12 23:04:06 +02:00
static int __init mfgpt_setup ( char * str )
{
get_option ( & str , & irq ) ;
return 1 ;
}
__setup ( " mfgpt_irq= " , mfgpt_setup ) ;
2008-02-09 23:24:08 +01:00
static void mfgpt_disable_timer ( u16 clock )
2007-10-12 23:04:06 +02:00
{
2008-02-09 23:24:08 +01:00
/* avoid races by clearing CMP1 and CMP2 unconditionally */
geode_mfgpt_write ( clock , MFGPT_REG_SETUP , ( u16 ) ~ MFGPT_SETUP_CNTEN |
MFGPT_SETUP_CMP1 | MFGPT_SETUP_CMP2 ) ;
2007-10-12 23:04:06 +02:00
}
static int mfgpt_next_event ( unsigned long , struct clock_event_device * ) ;
static void mfgpt_set_mode ( enum clock_event_mode , struct clock_event_device * ) ;
static struct clock_event_device mfgpt_clockevent = {
. name = " mfgpt-timer " ,
. features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT ,
. set_mode = mfgpt_set_mode ,
. set_next_event = mfgpt_next_event ,
. rating = 250 ,
2008-12-13 21:20:26 +10:30
. cpumask = cpu_all_mask ,
2007-10-12 23:04:06 +02:00
. shift = 32
} ;
2008-02-09 23:24:08 +01:00
static void mfgpt_start_timer ( u16 delta )
2007-10-12 23:04:06 +02:00
{
geode_mfgpt_write ( mfgpt_event_clock , MFGPT_REG_CMP2 , ( u16 ) delta ) ;
geode_mfgpt_write ( mfgpt_event_clock , MFGPT_REG_COUNTER , 0 ) ;
geode_mfgpt_write ( mfgpt_event_clock , MFGPT_REG_SETUP ,
MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2 ) ;
}
static void mfgpt_set_mode ( enum clock_event_mode mode ,
struct clock_event_device * evt )
{
mfgpt_disable_timer ( mfgpt_event_clock ) ;
if ( mode = = CLOCK_EVT_MODE_PERIODIC )
2008-02-09 23:24:08 +01:00
mfgpt_start_timer ( MFGPT_PERIODIC ) ;
2007-10-12 23:04:06 +02:00
mfgpt_tick_mode = mode ;
}
static int mfgpt_next_event ( unsigned long delta , struct clock_event_device * evt )
{
2008-02-09 23:24:08 +01:00
mfgpt_start_timer ( delta ) ;
2007-10-12 23:04:06 +02:00
return 0 ;
}
static irqreturn_t mfgpt_tick ( int irq , void * dev_id )
{
2008-02-09 23:24:08 +01:00
u16 val = geode_mfgpt_read ( mfgpt_event_clock , MFGPT_REG_SETUP ) ;
/* See if the interrupt was for us */
if ( ! ( val & ( MFGPT_SETUP_SETUP | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1 ) ) )
return IRQ_NONE ;
2008-01-22 23:30:16 +01:00
/* Turn off the clock (and clear the event) */
mfgpt_disable_timer ( mfgpt_event_clock ) ;
2007-10-12 23:04:06 +02:00
if ( mfgpt_tick_mode = = CLOCK_EVT_MODE_SHUTDOWN )
return IRQ_HANDLED ;
/* Clear the counter */
geode_mfgpt_write ( mfgpt_event_clock , MFGPT_REG_COUNTER , 0 ) ;
/* Restart the clock in periodic mode */
if ( mfgpt_tick_mode = = CLOCK_EVT_MODE_PERIODIC ) {
geode_mfgpt_write ( mfgpt_event_clock , MFGPT_REG_SETUP ,
MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2 ) ;
}
mfgpt_clockevent . event_handler ( & mfgpt_clockevent ) ;
return IRQ_HANDLED ;
}
static struct irqaction mfgptirq = {
. handler = mfgpt_tick ,
. flags = IRQF_DISABLED | IRQF_NOBALANCING ,
. mask = CPU_MASK_NONE ,
. name = " mfgpt-timer "
} ;
2008-02-09 23:24:08 +01:00
int __init mfgpt_timer_setup ( void )
2007-10-12 23:04:06 +02:00
{
int timer , ret ;
u16 val ;
2008-02-09 23:24:08 +01:00
timer = geode_mfgpt_alloc_timer ( MFGPT_TIMER_ANY , MFGPT_DOMAIN_WORKING ) ;
2007-10-12 23:04:06 +02:00
if ( timer < 0 ) {
printk ( KERN_ERR
" mfgpt-timer: Could not allocate a MFPGT timer \n " ) ;
return - ENODEV ;
}
mfgpt_event_clock = timer ;
/* Set up the IRQ on the MFGPT side */
2008-08-04 14:40:16 +02:00
if ( geode_mfgpt_setup_irq ( mfgpt_event_clock , MFGPT_CMP2 , & irq ) ) {
2007-10-12 23:04:06 +02:00
printk ( KERN_ERR " mfgpt-timer: Could not set up IRQ %d \n " , irq ) ;
return - EIO ;
}
/* And register it with the kernel */
ret = setup_irq ( irq , & mfgptirq ) ;
if ( ret ) {
printk ( KERN_ERR
" mfgpt-timer: Unable to set up the interrupt. \n " ) ;
goto err ;
}
2008-01-22 23:30:16 +01:00
/* Set the clock scale and enable the event mode for CMP2 */
val = MFGPT_SCALE | ( 3 < < 8 ) ;
geode_mfgpt_write ( mfgpt_event_clock , MFGPT_REG_SETUP , val ) ;
2007-10-12 23:04:06 +02:00
/* Set up the clock event */
2008-04-19 23:55:16 +09:00
mfgpt_clockevent . mult = div_sc ( MFGPT_HZ , NSEC_PER_SEC ,
mfgpt_clockevent . shift ) ;
2007-10-12 23:04:06 +02:00
mfgpt_clockevent . min_delta_ns = clockevent_delta2ns ( 0xF ,
& mfgpt_clockevent ) ;
mfgpt_clockevent . max_delta_ns = clockevent_delta2ns ( 0xFFFE ,
& mfgpt_clockevent ) ;
printk ( KERN_INFO
2008-08-04 14:40:16 +02:00
" mfgpt-timer: Registering MFGPT timer %d as a clock event, using IRQ %d \n " ,
timer , irq ) ;
2007-10-12 23:04:06 +02:00
clockevents_register_device ( & mfgpt_clockevent ) ;
return 0 ;
err :
2008-08-04 14:40:16 +02:00
geode_mfgpt_release_irq ( mfgpt_event_clock , MFGPT_CMP2 , & irq ) ;
2007-10-12 23:04:06 +02:00
printk ( KERN_ERR
" mfgpt-timer: Unable to set up the MFGPT clock source \n " ) ;
return - EIO ;
}
# endif