aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorRodolfo Giometti <giometti@linux.it>2009-06-17 19:28:37 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-06-18 16:04:04 -0400
commiteae9d2ba0cfc27a2ad9765f23efb98fb80d80234 (patch)
treef4be40ca528b2f23f97fa9cb6ebe91b8d6696d5b /drivers
parent8820f27ad9a5ad2a62cdcdf425d7921c31831800 (diff)
LinuxPPS: core support
This patch adds the kernel side of the PPS support currently named "LinuxPPS". PPS means "pulse per second" and a PPS source is just a device which provides a high precision signal each second so that an application can use it to adjust system clock time. Common use is the combination of the NTPD as userland program with a GPS receiver as PPS source to obtain a wallclock-time with sub-millisecond synchronisation to UTC. To obtain this goal the userland programs shoud use the PPS API specification (RFC 2783 - Pulse-Per-Second API for UNIX-like Operating Systems, Version 1.0) which in part is implemented by this patch. It provides a set of chars devices, one per PPS source, which can be used to get the time signal. The RFC's functions can be implemented by accessing to these char devices. Signed-off-by: Rodolfo Giometti <giometti@linux.it> Cc: David Woodhouse <dwmw2@infradead.org> Cc: Greg KH <greg@kroah.com> Cc: Randy Dunlap <randy.dunlap@oracle.com> Cc: Kay Sievers <kay.sievers@vrfy.org> Acked-by: Alan Cox <alan@lxorguk.ukuu.org.uk> Cc: Michael Kerrisk <mtk.manpages@googlemail.com> Cc: Christoph Hellwig <hch@infradead.org> Cc: Roman Zippel <zippel@linux-m68k.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig2
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/pps/Kconfig33
-rw-r--r--drivers/pps/Makefile8
-rw-r--r--drivers/pps/kapi.c329
-rw-r--r--drivers/pps/pps.c312
-rw-r--r--drivers/pps/sysfs.c98
7 files changed, 783 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig
index a442c8f29fc1..48bbdbe43e69 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig"
52 52
53source "drivers/spi/Kconfig" 53source "drivers/spi/Kconfig"
54 54
55source "drivers/pps/Kconfig"
56
55source "drivers/gpio/Kconfig" 57source "drivers/gpio/Kconfig"
56 58
57source "drivers/w1/Kconfig" 59source "drivers/w1/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 00b44f4ccf03..bc4205d2fc3c 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_INPUT) += input/
72obj-$(CONFIG_I2O) += message/ 72obj-$(CONFIG_I2O) += message/
73obj-$(CONFIG_RTC_LIB) += rtc/ 73obj-$(CONFIG_RTC_LIB) += rtc/
74obj-y += i2c/ media/ 74obj-y += i2c/ media/
75obj-$(CONFIG_PPS) += pps/
75obj-$(CONFIG_W1) += w1/ 76obj-$(CONFIG_W1) += w1/
76obj-$(CONFIG_POWER_SUPPLY) += power/ 77obj-$(CONFIG_POWER_SUPPLY) += power/
77obj-$(CONFIG_HWMON) += hwmon/ 78obj-$(CONFIG_HWMON) += hwmon/
diff --git a/drivers/pps/Kconfig b/drivers/pps/Kconfig
new file mode 100644
index 000000000000..cc2eb8edb514
--- /dev/null
+++ b/drivers/pps/Kconfig
@@ -0,0 +1,33 @@
1#
2# PPS support configuration
3#
4
5menu "PPS support"
6
7config PPS
8 tristate "PPS support"
9 depends on EXPERIMENTAL
10 ---help---
11 PPS (Pulse Per Second) is a special pulse provided by some GPS
12 antennae. Userland can use it to get a high-precision time
13 reference.
14
15 Some antennae's PPS signals are connected with the CD (Carrier
16 Detect) pin of the serial line they use to communicate with the
17 host. In this case use the SERIAL_LINE client support.
18
19 Some antennae's PPS signals are connected with some special host
20 inputs so you have to enable the corresponding client support.
21
22 To compile this driver as a module, choose M here: the module
23 will be called pps_core.ko.
24
25config PPS_DEBUG
26 bool "PPS debugging messages"
27 depends on PPS
28 help
29 Say Y here if you want the PPS support to produce a bunch of debug
30 messages to the system log. Select this if you are having a
31 problem with PPS support and want to see more of what is going on.
32
33endmenu
diff --git a/drivers/pps/Makefile b/drivers/pps/Makefile
new file mode 100644
index 000000000000..19ea582f431d
--- /dev/null
+++ b/drivers/pps/Makefile
@@ -0,0 +1,8 @@
1#
2# Makefile for the PPS core.
3#
4
5pps_core-y := pps.o kapi.o sysfs.o
6obj-$(CONFIG_PPS) := pps_core.o
7
8ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG
diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c
new file mode 100644
index 000000000000..35a0b192d768
--- /dev/null
+++ b/drivers/pps/kapi.c
@@ -0,0 +1,329 @@
1/*
2 * kernel API
3 *
4 *
5 * Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22
23#include <linux/kernel.h>
24#include <linux/module.h>
25#include <linux/init.h>
26#include <linux/sched.h>
27#include <linux/time.h>
28#include <linux/spinlock.h>
29#include <linux/idr.h>
30#include <linux/fs.h>
31#include <linux/pps_kernel.h>
32
33/*
34 * Global variables
35 */
36
37DEFINE_SPINLOCK(pps_idr_lock);
38DEFINE_IDR(pps_idr);
39
40/*
41 * Local functions
42 */
43
44static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset)
45{
46 ts->nsec += offset->nsec;
47 while (ts->nsec >= NSEC_PER_SEC) {
48 ts->nsec -= NSEC_PER_SEC;
49 ts->sec++;
50 }
51 while (ts->nsec < 0) {
52 ts->nsec += NSEC_PER_SEC;
53 ts->sec--;
54 }
55 ts->sec += offset->sec;
56}
57
58/*
59 * Exported functions
60 */
61
62/* pps_get_source - find a PPS source
63 * @source: the PPS source ID.
64 *
65 * This function is used to find an already registered PPS source into the
66 * system.
67 *
68 * The function returns NULL if found nothing, otherwise it returns a pointer
69 * to the PPS source data struct (the refcounter is incremented by 1).
70 */
71
72struct pps_device *pps_get_source(int source)
73{
74 struct pps_device *pps;
75 unsigned long flags;
76
77 spin_lock_irqsave(&pps_idr_lock, flags);
78
79 pps = idr_find(&pps_idr, source);
80 if (pps != NULL)
81 atomic_inc(&pps->usage);
82
83 spin_unlock_irqrestore(&pps_idr_lock, flags);
84
85 return pps;
86}
87
88/* pps_put_source - free the PPS source data
89 * @pps: a pointer to the PPS source.
90 *
91 * This function is used to free a PPS data struct if its refcount is 0.
92 */
93
94void pps_put_source(struct pps_device *pps)
95{
96 unsigned long flags;
97
98 spin_lock_irqsave(&pps_idr_lock, flags);
99 BUG_ON(atomic_read(&pps->usage) == 0);
100
101 if (!atomic_dec_and_test(&pps->usage)) {
102 pps = NULL;
103 goto exit;
104 }
105
106 /* No more reference to the PPS source. We can safely remove the
107 * PPS data struct.
108 */
109 idr_remove(&pps_idr, pps->id);
110
111exit:
112 spin_unlock_irqrestore(&pps_idr_lock, flags);
113 kfree(pps);
114}
115
116/* pps_register_source - add a PPS source in the system
117 * @info: the PPS info struct
118 * @default_params: the default PPS parameters of the new source
119 *
120 * This function is used to add a new PPS source in the system. The new
121 * source is described by info's fields and it will have, as default PPS
122 * parameters, the ones specified into default_params.
123 *
124 * The function returns, in case of success, the PPS source ID.
125 */
126
127int pps_register_source(struct pps_source_info *info, int default_params)
128{
129 struct pps_device *pps;
130 int id;
131 int err;
132
133 /* Sanity checks */
134 if ((info->mode & default_params) != default_params) {
135 printk(KERN_ERR "pps: %s: unsupported default parameters\n",
136 info->name);
137 err = -EINVAL;
138 goto pps_register_source_exit;
139 }
140 if ((info->mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) != 0 &&
141 info->echo == NULL) {
142 printk(KERN_ERR "pps: %s: echo function is not defined\n",
143 info->name);
144 err = -EINVAL;
145 goto pps_register_source_exit;
146 }
147 if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
148 printk(KERN_ERR "pps: %s: unspecified time format\n",
149 info->name);
150 err = -EINVAL;
151 goto pps_register_source_exit;
152 }
153
154 /* Allocate memory for the new PPS source struct */
155 pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL);
156 if (pps == NULL) {
157 err = -ENOMEM;
158 goto pps_register_source_exit;
159 }
160
161 /* These initializations must be done before calling idr_get_new()
162 * in order to avoid reces into pps_event().
163 */
164 pps->params.api_version = PPS_API_VERS;
165 pps->params.mode = default_params;
166 pps->info = *info;
167
168 init_waitqueue_head(&pps->queue);
169 spin_lock_init(&pps->lock);
170 atomic_set(&pps->usage, 1);
171
172 /* Get new ID for the new PPS source */
173 if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) {
174 err = -ENOMEM;
175 goto kfree_pps;
176 }
177
178 spin_lock_irq(&pps_idr_lock);
179
180 /* Now really allocate the PPS source.
181 * After idr_get_new() calling the new source will be freely available
182 * into the kernel.
183 */
184 err = idr_get_new(&pps_idr, pps, &id);
185 if (err < 0) {
186 spin_unlock_irq(&pps_idr_lock);
187 goto kfree_pps;
188 }
189
190 id = id & MAX_ID_MASK;
191 if (id >= PPS_MAX_SOURCES) {
192 spin_unlock_irq(&pps_idr_lock);
193
194 printk(KERN_ERR "pps: %s: too many PPS sources in the system\n",
195 info->name);
196 err = -EBUSY;
197 goto free_idr;
198 }
199 pps->id = id;
200
201 spin_unlock_irq(&pps_idr_lock);
202
203 /* Create the char device */
204 err = pps_register_cdev(pps);
205 if (err < 0) {
206 printk(KERN_ERR "pps: %s: unable to create char device\n",
207 info->name);
208 goto free_idr;
209 }
210
211 pr_info("new PPS source %s at ID %d\n", info->name, id);
212
213 return id;
214
215free_idr:
216 spin_lock_irq(&pps_idr_lock);
217 idr_remove(&pps_idr, id);
218 spin_unlock_irq(&pps_idr_lock);
219
220kfree_pps:
221 kfree(pps);
222
223pps_register_source_exit:
224 printk(KERN_ERR "pps: %s: unable to register source\n", info->name);
225
226 return err;
227}
228EXPORT_SYMBOL(pps_register_source);
229
230/* pps_unregister_source - remove a PPS source from the system
231 * @source: the PPS source ID
232 *
233 * This function is used to remove a previously registered PPS source from
234 * the system.
235 */
236
237void pps_unregister_source(int source)
238{
239 struct pps_device *pps;
240
241 spin_lock_irq(&pps_idr_lock);
242 pps = idr_find(&pps_idr, source);
243
244 if (!pps) {
245 BUG();
246 spin_unlock_irq(&pps_idr_lock);
247 return;
248 }
249 spin_unlock_irq(&pps_idr_lock);
250
251 pps_unregister_cdev(pps);
252 pps_put_source(pps);
253}
254EXPORT_SYMBOL(pps_unregister_source);
255
256/* pps_event - register a PPS event into the system
257 * @source: the PPS source ID
258 * @ts: the event timestamp
259 * @event: the event type
260 * @data: userdef pointer
261 *
262 * This function is used by each PPS client in order to register a new
263 * PPS event into the system (it's usually called inside an IRQ handler).
264 *
265 * If an echo function is associated with the PPS source it will be called
266 * as:
267 * pps->info.echo(source, event, data);
268 */
269
270void pps_event(int source, struct pps_ktime *ts, int event, void *data)
271{
272 struct pps_device *pps;
273 unsigned long flags;
274
275 if ((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0) {
276 printk(KERN_ERR "pps: unknown event (%x) for source %d\n",
277 event, source);
278 return;
279 }
280
281 pps = pps_get_source(source);
282 if (!pps)
283 return;
284
285 pr_debug("PPS event on source %d at %llu.%06u\n",
286 pps->id, (unsigned long long) ts->sec, ts->nsec);
287
288 spin_lock_irqsave(&pps->lock, flags);
289
290 /* Must call the echo function? */
291 if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)))
292 pps->info.echo(source, event, data);
293
294 /* Check the event */
295 pps->current_mode = pps->params.mode;
296 if (event & PPS_CAPTUREASSERT) {
297 /* We have to add an offset? */
298 if (pps->params.mode & PPS_OFFSETASSERT)
299 pps_add_offset(ts, &pps->params.assert_off_tu);
300
301 /* Save the time stamp */
302 pps->assert_tu = *ts;
303 pps->assert_sequence++;
304 pr_debug("capture assert seq #%u for source %d\n",
305 pps->assert_sequence, source);
306 }
307 if (event & PPS_CAPTURECLEAR) {
308 /* We have to add an offset? */
309 if (pps->params.mode & PPS_OFFSETCLEAR)
310 pps_add_offset(ts, &pps->params.clear_off_tu);
311
312 /* Save the time stamp */
313 pps->clear_tu = *ts;
314 pps->clear_sequence++;
315 pr_debug("capture clear seq #%u for source %d\n",
316 pps->clear_sequence, source);
317 }
318
319 pps->go = ~0;
320 wake_up_interruptible(&pps->queue);
321
322 kill_fasync(&pps->async_queue, SIGIO, POLL_IN);
323
324 spin_unlock_irqrestore(&pps->lock, flags);
325
326 /* Now we can release the PPS source for (possible) deregistration */
327 pps_put_source(pps);
328}
329EXPORT_SYMBOL(pps_event);
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
new file mode 100644
index 000000000000..ac8cc8cea1e3
--- /dev/null
+++ b/drivers/pps/pps.c
@@ -0,0 +1,312 @@
1/*
2 * PPS core file
3 *
4 *
5 * Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22
23#include <linux/kernel.h>
24#include <linux/module.h>
25#include <linux/init.h>
26#include <linux/sched.h>
27#include <linux/uaccess.h>
28#include <linux/idr.h>
29#include <linux/cdev.h>
30#include <linux/poll.h>
31#include <linux/pps_kernel.h>
32
33/*
34 * Local variables
35 */
36
37static dev_t pps_devt;
38static struct class *pps_class;
39
40/*
41 * Char device methods
42 */
43
44static unsigned int pps_cdev_poll(struct file *file, poll_table *wait)
45{
46 struct pps_device *pps = file->private_data;
47
48 poll_wait(file, &pps->queue, wait);
49
50 return POLLIN | POLLRDNORM;
51}
52
53static int pps_cdev_fasync(int fd, struct file *file, int on)
54{
55 struct pps_device *pps = file->private_data;
56 return fasync_helper(fd, file, on, &pps->async_queue);
57}
58
59static long pps_cdev_ioctl(struct file *file,
60 unsigned int cmd, unsigned long arg)
61{
62 struct pps_device *pps = file->private_data;
63 struct pps_kparams params;
64 struct pps_fdata fdata;
65 unsigned long ticks;
66 void __user *uarg = (void __user *) arg;
67 int __user *iuarg = (int __user *) arg;
68 int err;
69
70 switch (cmd) {
71 case PPS_GETPARAMS:
72 pr_debug("PPS_GETPARAMS: source %d\n", pps->id);
73
74 /* Return current parameters */
75 err = copy_to_user(uarg, &pps->params,
76 sizeof(struct pps_kparams));
77 if (err)
78 return -EFAULT;
79
80 break;
81
82 case PPS_SETPARAMS:
83 pr_debug("PPS_SETPARAMS: source %d\n", pps->id);
84
85 /* Check the capabilities */
86 if (!capable(CAP_SYS_TIME))
87 return -EPERM;
88
89 err = copy_from_user(&params, uarg, sizeof(struct pps_kparams));
90 if (err)
91 return -EFAULT;
92 if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
93 pr_debug("capture mode unspecified (%x)\n",
94 params.mode);
95 return -EINVAL;
96 }
97
98 /* Check for supported capabilities */
99 if ((params.mode & ~pps->info.mode) != 0) {
100 pr_debug("unsupported capabilities (%x)\n",
101 params.mode);
102 return -EINVAL;
103 }
104
105 spin_lock_irq(&pps->lock);
106
107 /* Save the new parameters */
108 pps->params = params;
109
110 /* Restore the read only parameters */
111 if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
112 /* section 3.3 of RFC 2783 interpreted */
113 pr_debug("time format unspecified (%x)\n",
114 params.mode);
115 pps->params.mode |= PPS_TSFMT_TSPEC;
116 }
117 if (pps->info.mode & PPS_CANWAIT)
118 pps->params.mode |= PPS_CANWAIT;
119 pps->params.api_version = PPS_API_VERS;
120
121 spin_unlock_irq(&pps->lock);
122
123 break;
124
125 case PPS_GETCAP:
126 pr_debug("PPS_GETCAP: source %d\n", pps->id);
127
128 err = put_user(pps->info.mode, iuarg);
129 if (err)
130 return -EFAULT;
131
132 break;
133
134 case PPS_FETCH:
135 pr_debug("PPS_FETCH: source %d\n", pps->id);
136
137 err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
138 if (err)
139 return -EFAULT;
140
141 pps->go = 0;
142
143 /* Manage the timeout */
144 if (fdata.timeout.flags & PPS_TIME_INVALID)
145 err = wait_event_interruptible(pps->queue, pps->go);
146 else {
147 pr_debug("timeout %lld.%09d\n",
148 (long long) fdata.timeout.sec,
149 fdata.timeout.nsec);
150 ticks = fdata.timeout.sec * HZ;
151 ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ);
152
153 if (ticks != 0) {
154 err = wait_event_interruptible_timeout(
155 pps->queue, pps->go, ticks);
156 if (err == 0)
157 return -ETIMEDOUT;
158 }
159 }
160
161 /* Check for pending signals */
162 if (err == -ERESTARTSYS) {
163 pr_debug("pending signal caught\n");
164 return -EINTR;
165 }
166
167 /* Return the fetched timestamp */
168 spin_lock_irq(&pps->lock);
169
170 fdata.info.assert_sequence = pps->assert_sequence;
171 fdata.info.clear_sequence = pps->clear_sequence;
172 fdata.info.assert_tu = pps->assert_tu;
173 fdata.info.clear_tu = pps->clear_tu;
174 fdata.info.current_mode = pps->current_mode;
175
176 spin_unlock_irq(&pps->lock);
177
178 err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
179 if (err)
180 return -EFAULT;
181
182 break;
183
184 default:
185 return -ENOTTY;
186 break;
187 }
188
189 return 0;
190}
191
192static int pps_cdev_open(struct inode *inode, struct file *file)
193{
194 struct pps_device *pps = container_of(inode->i_cdev,
195 struct pps_device, cdev);
196 int found;
197
198 found = pps_get_source(pps->id) != 0;
199 if (!found)
200 return -ENODEV;
201
202 file->private_data = pps;
203
204 return 0;
205}
206
207static int pps_cdev_release(struct inode *inode, struct file *file)
208{
209 struct pps_device *pps = file->private_data;
210
211 /* Free the PPS source and wake up (possible) deregistration */
212 pps_put_source(pps);
213
214 return 0;
215}
216
217/*
218 * Char device stuff
219 */
220
221static const struct file_operations pps_cdev_fops = {
222 .owner = THIS_MODULE,
223 .llseek = no_llseek,
224 .poll = pps_cdev_poll,
225 .fasync = pps_cdev_fasync,
226 .unlocked_ioctl = pps_cdev_ioctl,
227 .open = pps_cdev_open,
228 .release = pps_cdev_release,
229};
230
231int pps_register_cdev(struct pps_device *pps)
232{
233 int err;
234
235 pps->devno = MKDEV(MAJOR(pps_devt), pps->id);
236 cdev_init(&pps->cdev, &pps_cdev_fops);
237 pps->cdev.owner = pps->info.owner;
238
239 err = cdev_add(&pps->cdev, pps->devno, 1);
240 if (err) {
241 printk(KERN_ERR "pps: %s: failed to add char device %d:%d\n",
242 pps->info.name, MAJOR(pps_devt), pps->id);
243 return err;
244 }
245 pps->dev = device_create(pps_class, pps->info.dev, pps->devno, NULL,
246 "pps%d", pps->id);
247 if (err)
248 goto del_cdev;
249 dev_set_drvdata(pps->dev, pps);
250
251 pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
252 MAJOR(pps_devt), pps->id);
253
254 return 0;
255
256del_cdev:
257 cdev_del(&pps->cdev);
258
259 return err;
260}
261
262void pps_unregister_cdev(struct pps_device *pps)
263{
264 device_destroy(pps_class, pps->devno);
265 cdev_del(&pps->cdev);
266}
267
268/*
269 * Module stuff
270 */
271
272static void __exit pps_exit(void)
273{
274 class_destroy(pps_class);
275 unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
276}
277
278static int __init pps_init(void)
279{
280 int err;
281
282 pps_class = class_create(THIS_MODULE, "pps");
283 if (!pps_class) {
284 printk(KERN_ERR "pps: failed to allocate class\n");
285 return -ENOMEM;
286 }
287 pps_class->dev_attrs = pps_attrs;
288
289 err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
290 if (err < 0) {
291 printk(KERN_ERR "pps: failed to allocate char device region\n");
292 goto remove_class;
293 }
294
295 pr_info("LinuxPPS API ver. %d registered\n", PPS_API_VERS);
296 pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti "
297 "<giometti@linux.it>\n", PPS_VERSION);
298
299 return 0;
300
301remove_class:
302 class_destroy(pps_class);
303
304 return err;
305}
306
307subsys_initcall(pps_init);
308module_exit(pps_exit);
309
310MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
311MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
312MODULE_LICENSE("GPL");
diff --git a/drivers/pps/sysfs.c b/drivers/pps/sysfs.c
new file mode 100644
index 000000000000..ef0978c71eee
--- /dev/null
+++ b/drivers/pps/sysfs.c
@@ -0,0 +1,98 @@
1/*
2 * PPS sysfs support
3 *
4 *
5 * Copyright (C) 2007-2009 Rodolfo Giometti <giometti@linux.it>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22
23#include <linux/device.h>
24#include <linux/module.h>
25#include <linux/string.h>
26#include <linux/pps_kernel.h>
27
28/*
29 * Attribute functions
30 */
31
32static ssize_t pps_show_assert(struct device *dev,
33 struct device_attribute *attr, char *buf)
34{
35 struct pps_device *pps = dev_get_drvdata(dev);
36
37 if (!(pps->info.mode & PPS_CAPTUREASSERT))
38 return 0;
39
40 return sprintf(buf, "%lld.%09d#%d\n",
41 (long long) pps->assert_tu.sec, pps->assert_tu.nsec,
42 pps->assert_sequence);
43}
44
45static ssize_t pps_show_clear(struct device *dev,
46 struct device_attribute *attr, char *buf)
47{
48 struct pps_device *pps = dev_get_drvdata(dev);
49
50 if (!(pps->info.mode & PPS_CAPTURECLEAR))
51 return 0;
52
53 return sprintf(buf, "%lld.%09d#%d\n",
54 (long long) pps->clear_tu.sec, pps->clear_tu.nsec,
55 pps->clear_sequence);
56}
57
58static ssize_t pps_show_mode(struct device *dev,
59 struct device_attribute *attr, char *buf)
60{
61 struct pps_device *pps = dev_get_drvdata(dev);
62
63 return sprintf(buf, "%4x\n", pps->info.mode);
64}
65
66static ssize_t pps_show_echo(struct device *dev,
67 struct device_attribute *attr, char *buf)
68{
69 struct pps_device *pps = dev_get_drvdata(dev);
70
71 return sprintf(buf, "%d\n", !!pps->info.echo);
72}
73
74static ssize_t pps_show_name(struct device *dev,
75 struct device_attribute *attr, char *buf)
76{
77 struct pps_device *pps = dev_get_drvdata(dev);
78
79 return sprintf(buf, "%s\n", pps->info.name);
80}
81
82static ssize_t pps_show_path(struct device *dev,
83 struct device_attribute *attr, char *buf)
84{
85 struct pps_device *pps = dev_get_drvdata(dev);
86
87 return sprintf(buf, "%s\n", pps->info.path);
88}
89
90struct device_attribute pps_attrs[] = {
91 __ATTR(assert, S_IRUGO, pps_show_assert, NULL),
92 __ATTR(clear, S_IRUGO, pps_show_clear, NULL),
93 __ATTR(mode, S_IRUGO, pps_show_mode, NULL),
94 __ATTR(echo, S_IRUGO, pps_show_echo, NULL),
95 __ATTR(name, S_IRUGO, pps_show_name, NULL),
96 __ATTR(path, S_IRUGO, pps_show_path, NULL),
97 __ATTR_NULL,
98};