This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / drivers / char / mmtimer.c
1 /*
2  * Intel Multimedia Timer device implementation for SGI SN platforms.
3  *
4  * This file is subject to the terms and conditions of the GNU General Public
5  * License.  See the file "COPYING" in the main directory of this archive
6  * for more details.
7  *
8  * Copyright (c) 2001-2004 Silicon Graphics, Inc.  All rights reserved.
9  *
10  * This driver exports an API that should be supportable by any HPET or IA-PC
11  * multimedia timer.  The code below is currently specific to the SGI Altix
12  * SHub RTC, however.
13  *
14  * 11/01/01 - jbarnes - initial revision
15  * 9/10/04 - Christoph Lameter - remove interrupt support for kernel inclusion
16  */
17
18 #include <linux/types.h>
19 #include <linux/kernel.h>
20 #include <linux/ioctl.h>
21 #include <linux/module.h>
22 #include <linux/init.h>
23 #include <linux/errno.h>
24 #include <linux/mm.h>
25 #include <linux/devfs_fs_kernel.h>
26 #include <linux/mmtimer.h>
27 #include <linux/miscdevice.h>
28 #include <asm/uaccess.h>
29 #include <asm/sn/addrs.h>
30 #include <asm/sn/clksupport.h>
31
32 MODULE_AUTHOR("Jesse Barnes <jbarnes@sgi.com>");
33 MODULE_DESCRIPTION("Multimedia timer support");
34 MODULE_LICENSE("GPL");
35
36 /* name of the device, usually in /dev */
37 #define MMTIMER_NAME "mmtimer"
38 #define MMTIMER_DESC "IA-PC Multimedia Timer"
39 #define MMTIMER_VERSION "1.0"
40
41 #define RTC_BITS 55 /* 55 bits for this implementation */
42
43 static int mmtimer_ioctl(struct inode *inode, struct file *file,
44                          unsigned int cmd, unsigned long arg);
45 static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma);
46
47 /*
48  * Period in femtoseconds (10^-15 s)
49  */
50 static unsigned long mmtimer_femtoperiod = 0;
51
52 static struct file_operations mmtimer_fops = {
53         .owner =        THIS_MODULE,
54         .mmap =         mmtimer_mmap,
55         .ioctl =        mmtimer_ioctl,
56 };
57
58 /**
59  * mmtimer_ioctl - ioctl interface for /dev/mmtimer
60  * @inode: inode of the device
61  * @file: file structure for the device
62  * @cmd: command to execute
63  * @arg: optional argument to command
64  *
65  * Executes the command specified by @cmd.  Returns 0 for success, < 0 for
66  * failure.
67  *
68  * Valid commands:
69  *
70  * %MMTIMER_GETOFFSET - Should return the offset (relative to the start
71  * of the page where the registers are mapped) for the counter in question.
72  *
73  * %MMTIMER_GETRES - Returns the resolution of the clock in femto (10^-15)
74  * seconds
75  *
76  * %MMTIMER_GETFREQ - Copies the frequency of the clock in Hz to the address
77  * specified by @arg
78  *
79  * %MMTIMER_GETBITS - Returns the number of bits in the clock's counter
80  *
81  * %MMTIMER_MMAPAVAIL - Returns 1 if the registers can be mmap'd into userspace
82  *
83  * %MMTIMER_GETCOUNTER - Gets the current value in the counter and places it
84  * in the address specified by @arg.
85  */
86 static int mmtimer_ioctl(struct inode *inode, struct file *file,
87                          unsigned int cmd, unsigned long arg)
88 {
89         int ret = 0;
90
91         switch (cmd) {
92         case MMTIMER_GETOFFSET: /* offset of the counter */
93                 /*
94                  * SN RTC registers are on their own 64k page
95                  */
96                 if(PAGE_SIZE <= (1 << 16))
97                         ret = (((long)RTC_COUNTER_ADDR) & (PAGE_SIZE-1)) / 8;
98                 else
99                         ret = -ENOSYS;
100                 break;
101
102         case MMTIMER_GETRES: /* resolution of the clock in 10^-15 s */
103                 if(copy_to_user((unsigned long *)arg, &mmtimer_femtoperiod,
104                                 sizeof(unsigned long)))
105                         return -EFAULT;
106                 break;
107
108         case MMTIMER_GETFREQ: /* frequency in Hz */
109                 if(copy_to_user((unsigned long *)arg,
110                                 &sn_rtc_cycles_per_second,
111                                 sizeof(unsigned long)))
112                         return -EFAULT;
113                 ret = 0;
114                 break;
115
116         case MMTIMER_GETBITS: /* number of bits in the clock */
117                 ret = RTC_BITS;
118                 break;
119
120         case MMTIMER_MMAPAVAIL: /* can we mmap the clock into userspace? */
121                 ret = (PAGE_SIZE <= (1 << 16)) ? 1 : 0;
122                 break;
123
124         case MMTIMER_GETCOUNTER:
125                 if(copy_to_user((unsigned long *)arg, RTC_COUNTER_ADDR,
126                                 sizeof(unsigned long)))
127                         return -EFAULT;
128                 break;
129         default:
130                 ret = -ENOSYS;
131                 break;
132         }
133
134         return ret;
135 }
136
137 /**
138  * mmtimer_mmap - maps the clock's registers into userspace
139  * @file: file structure for the device
140  * @vma: VMA to map the registers into
141  *
142  * Calls remap_page_range() to map the clock's registers into
143  * the calling process' address space.
144  */
145 static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma)
146 {
147         unsigned long mmtimer_addr;
148
149         if (vma->vm_end - vma->vm_start != PAGE_SIZE)
150                 return -EINVAL;
151
152         if (vma->vm_flags & VM_WRITE)
153                 return -EPERM;
154
155         if (PAGE_SIZE > (1 << 16))
156                 return -ENOSYS;
157
158         vma->vm_flags |= (VM_IO | VM_SHM | VM_LOCKED );
159         vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
160
161         mmtimer_addr = __pa(RTC_COUNTER_ADDR);
162         mmtimer_addr &= ~(PAGE_SIZE - 1);
163         mmtimer_addr &= 0xfffffffffffffffUL;
164
165         if (remap_page_range(vma, vma->vm_start, mmtimer_addr, PAGE_SIZE,
166                              vma->vm_page_prot)) {
167                 printk(KERN_ERR "remap_page_range failed in mmtimer.c\n");
168                 return -EAGAIN;
169         }
170
171         return 0;
172 }
173
174 static struct miscdevice mmtimer_miscdev = {
175         SGI_MMTIMER,
176         MMTIMER_NAME,
177         &mmtimer_fops
178 };
179
180 /**
181  * mmtimer_init - device initialization routine
182  *
183  * Does initial setup for the mmtimer device.
184  */
185 static int __init mmtimer_init(void)
186 {
187         if (!ia64_platform_is("sn2"))
188                 return -1;
189
190         /*
191          * Sanity check the cycles/sec variable
192          */
193         if (sn_rtc_cycles_per_second < 100000) {
194                 printk(KERN_ERR "%s: unable to determine clock frequency\n",
195                        MMTIMER_NAME);
196                 return -1;
197         }
198
199         mmtimer_femtoperiod = ((unsigned long)1E15 + sn_rtc_cycles_per_second /
200                                2) / sn_rtc_cycles_per_second;
201
202         strcpy(mmtimer_miscdev.devfs_name, MMTIMER_NAME);
203         if (misc_register(&mmtimer_miscdev)) {
204                 printk(KERN_ERR "%s: failed to register device\n",
205                        MMTIMER_NAME);
206                 return -1;
207         }
208
209         printk(KERN_INFO "%s: v%s, %ld MHz\n", MMTIMER_DESC, MMTIMER_VERSION,
210                sn_rtc_cycles_per_second/(unsigned long)1E6);
211
212         return 0;
213 }
214
215 module_init(mmtimer_init);
216