2009-09-23 15:57:15 -07:00
/*
* Timer device implementation for SGI UV platform .
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( c ) 2009 Silicon Graphics , Inc . All rights reserved .
*
*/
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/ioctl.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/mm.h>
# include <linux/fs.h>
# include <linux/mmtimer.h>
# include <linux/miscdevice.h>
# include <linux/posix-timers.h>
# include <linux/interrupt.h>
# include <linux/time.h>
# include <linux/math64.h>
# include <linux/smp_lock.h>
# include <asm/genapic.h>
# include <asm/uv/uv_hub.h>
# include <asm/uv/bios.h>
# include <asm/uv/uv.h>
MODULE_AUTHOR ( " Dimitri Sivanich <sivanich@sgi.com> " ) ;
MODULE_DESCRIPTION ( " SGI UV Memory Mapped RTC Timer " ) ;
MODULE_LICENSE ( " GPL " ) ;
/* name of the device, usually in /dev */
# define UV_MMTIMER_NAME "mmtimer"
# define UV_MMTIMER_DESC "SGI UV Memory Mapped RTC Timer"
# define UV_MMTIMER_VERSION "1.0"
static long uv_mmtimer_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg ) ;
static int uv_mmtimer_mmap ( struct file * file , struct vm_area_struct * vma ) ;
/*
* Period in femtoseconds ( 10 ^ - 15 s )
*/
static unsigned long uv_mmtimer_femtoperiod ;
static const struct file_operations uv_mmtimer_fops = {
. owner = THIS_MODULE ,
. mmap = uv_mmtimer_mmap ,
. unlocked_ioctl = uv_mmtimer_ioctl ,
} ;
/**
* uv_mmtimer_ioctl - ioctl interface for / dev / uv_mmtimer
* @ file : file structure for the device
* @ cmd : command to execute
* @ arg : optional argument to command
*
* Executes the command specified by @ cmd . Returns 0 for success , < 0 for
* failure .
*
* Valid commands :
*
* % MMTIMER_GETOFFSET - Should return the offset ( relative to the start
* of the page where the registers are mapped ) for the counter in question .
*
* % MMTIMER_GETRES - Returns the resolution of the clock in femto ( 10 ^ - 15 )
* seconds
*
* % MMTIMER_GETFREQ - Copies the frequency of the clock in Hz to the address
* specified by @ arg
*
* % MMTIMER_GETBITS - Returns the number of bits in the clock ' s counter
*
* % MMTIMER_MMAPAVAIL - Returns 1 if registers can be mmap ' d into userspace
*
* % MMTIMER_GETCOUNTER - Gets the current value in the counter and places it
* in the address specified by @ arg .
*/
static long uv_mmtimer_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
int ret = 0 ;
switch ( cmd ) {
case MMTIMER_GETOFFSET : /* offset of the counter */
/*
2010-01-22 09:41:40 -06:00
* Starting with HUB rev 2.0 , the UV RTC register is
* replicated across all cachelines of it ' s own page .
* This allows faster simultaneous reads from a given socket .
*
* The offset returned is in 64 bit units .
2009-09-23 15:57:15 -07:00
*/
2010-01-22 09:41:40 -06:00
if ( uv_get_min_hub_revision_id ( ) = = 1 )
ret = 0 ;
2009-09-23 15:57:15 -07:00
else
2010-01-22 09:41:40 -06:00
ret = ( ( uv_blade_processor_id ( ) * L1_CACHE_BYTES ) %
PAGE_SIZE ) / 8 ;
2009-09-23 15:57:15 -07:00
break ;
case MMTIMER_GETRES : /* resolution of the clock in 10^-15 s */
if ( copy_to_user ( ( unsigned long __user * ) arg ,
& uv_mmtimer_femtoperiod , sizeof ( unsigned long ) ) )
ret = - EFAULT ;
break ;
case MMTIMER_GETFREQ : /* frequency in Hz */
if ( copy_to_user ( ( unsigned long __user * ) arg ,
& sn_rtc_cycles_per_second ,
sizeof ( unsigned long ) ) )
ret = - EFAULT ;
break ;
case MMTIMER_GETBITS : /* number of bits in the clock */
ret = hweight64 ( UVH_RTC_REAL_TIME_CLOCK_MASK ) ;
break ;
2010-01-22 09:41:40 -06:00
case MMTIMER_MMAPAVAIL :
ret = 1 ;
2009-09-23 15:57:15 -07:00
break ;
case MMTIMER_GETCOUNTER :
if ( copy_to_user ( ( unsigned long __user * ) arg ,
( unsigned long * ) uv_local_mmr_address ( UVH_RTC ) ,
sizeof ( unsigned long ) ) )
ret = - EFAULT ;
break ;
default :
ret = - ENOTTY ;
break ;
}
return ret ;
}
/**
* uv_mmtimer_mmap - maps the clock ' s registers into userspace
* @ file : file structure for the device
* @ vma : VMA to map the registers into
*
* Calls remap_pfn_range ( ) to map the clock ' s registers into
* the calling process ' address space .
*/
static int uv_mmtimer_mmap ( struct file * file , struct vm_area_struct * vma )
{
unsigned long uv_mmtimer_addr ;
if ( vma - > vm_end - vma - > vm_start ! = PAGE_SIZE )
return - EINVAL ;
if ( vma - > vm_flags & VM_WRITE )
return - EPERM ;
if ( PAGE_SIZE > ( 1 < < 16 ) )
return - ENOSYS ;
vma - > vm_page_prot = pgprot_noncached ( vma - > vm_page_prot ) ;
uv_mmtimer_addr = UV_LOCAL_MMR_BASE | UVH_RTC ;
uv_mmtimer_addr & = ~ ( PAGE_SIZE - 1 ) ;
uv_mmtimer_addr & = 0xfffffffffffffffUL ;
if ( remap_pfn_range ( vma , vma - > vm_start , uv_mmtimer_addr > > PAGE_SHIFT ,
PAGE_SIZE , vma - > vm_page_prot ) ) {
printk ( KERN_ERR " remap_pfn_range failed in uv_mmtimer_mmap \n " ) ;
return - EAGAIN ;
}
return 0 ;
}
static struct miscdevice uv_mmtimer_miscdev = {
MISC_DYNAMIC_MINOR ,
UV_MMTIMER_NAME ,
& uv_mmtimer_fops
} ;
/**
* uv_mmtimer_init - device initialization routine
*
* Does initial setup for the uv_mmtimer device .
*/
static int __init uv_mmtimer_init ( void )
{
if ( ! is_uv_system ( ) ) {
printk ( KERN_ERR " %s: Hardware unsupported \n " , UV_MMTIMER_NAME ) ;
return - 1 ;
}
/*
* Sanity check the cycles / sec variable
*/
if ( sn_rtc_cycles_per_second < 100000 ) {
printk ( KERN_ERR " %s: unable to determine clock frequency \n " ,
UV_MMTIMER_NAME ) ;
return - 1 ;
}
uv_mmtimer_femtoperiod = ( ( unsigned long ) 1E15 +
sn_rtc_cycles_per_second / 2 ) /
sn_rtc_cycles_per_second ;
if ( misc_register ( & uv_mmtimer_miscdev ) ) {
printk ( KERN_ERR " %s: failed to register device \n " ,
UV_MMTIMER_NAME ) ;
return - 1 ;
}
printk ( KERN_INFO " %s: v%s, %ld MHz \n " , UV_MMTIMER_DESC ,
UV_MMTIMER_VERSION ,
sn_rtc_cycles_per_second / ( unsigned long ) 1E6 ) ;
return 0 ;
}
module_init ( uv_mmtimer_init ) ;