]>
Commit | Line | Data |
---|---|---|
74ba9207 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
eae9d2ba RG |
2 | /* |
3 | * kernel API | |
4 | * | |
eae9d2ba | 5 | * Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it> |
eae9d2ba RG |
6 | */ |
7 | ||
7f7cce74 | 8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
eae9d2ba RG |
9 | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/init.h> | |
13 | #include <linux/sched.h> | |
14 | #include <linux/time.h> | |
717c0336 | 15 | #include <linux/timex.h> |
eae9d2ba | 16 | #include <linux/spinlock.h> |
eae9d2ba RG |
17 | #include <linux/fs.h> |
18 | #include <linux/pps_kernel.h> | |
5a0e3ad6 | 19 | #include <linux/slab.h> |
eae9d2ba | 20 | |
717c0336 AG |
21 | #include "kc.h" |
22 | ||
eae9d2ba RG |
23 | /* |
24 | * Local functions | |
25 | */ | |
26 | ||
27 | static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset) | |
28 | { | |
29 | ts->nsec += offset->nsec; | |
30 | while (ts->nsec >= NSEC_PER_SEC) { | |
31 | ts->nsec -= NSEC_PER_SEC; | |
32 | ts->sec++; | |
33 | } | |
34 | while (ts->nsec < 0) { | |
35 | ts->nsec += NSEC_PER_SEC; | |
36 | ts->sec--; | |
37 | } | |
38 | ts->sec += offset->sec; | |
39 | } | |
40 | ||
437c5341 JN |
41 | static void pps_echo_client_default(struct pps_device *pps, int event, |
42 | void *data) | |
43 | { | |
44 | dev_info(pps->dev, "echo %s %s\n", | |
45 | event & PPS_CAPTUREASSERT ? "assert" : "", | |
46 | event & PPS_CAPTURECLEAR ? "clear" : ""); | |
47 | } | |
48 | ||
eae9d2ba RG |
49 | /* |
50 | * Exported functions | |
51 | */ | |
52 | ||
eae9d2ba RG |
53 | /* pps_register_source - add a PPS source in the system |
54 | * @info: the PPS info struct | |
55 | * @default_params: the default PPS parameters of the new source | |
56 | * | |
57 | * This function is used to add a new PPS source in the system. The new | |
58 | * source is described by info's fields and it will have, as default PPS | |
59 | * parameters, the ones specified into default_params. | |
60 | * | |
3b1ad360 Y |
61 | * The function returns, in case of success, the PPS device. Otherwise |
62 | * ERR_PTR(errno). | |
eae9d2ba RG |
63 | */ |
64 | ||
5e196d34 AG |
65 | struct pps_device *pps_register_source(struct pps_source_info *info, |
66 | int default_params) | |
eae9d2ba RG |
67 | { |
68 | struct pps_device *pps; | |
eae9d2ba RG |
69 | int err; |
70 | ||
71 | /* Sanity checks */ | |
72 | if ((info->mode & default_params) != default_params) { | |
7f7cce74 | 73 | pr_err("%s: unsupported default parameters\n", |
eae9d2ba RG |
74 | info->name); |
75 | err = -EINVAL; | |
76 | goto pps_register_source_exit; | |
77 | } | |
eae9d2ba | 78 | if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { |
7f7cce74 | 79 | pr_err("%s: unspecified time format\n", |
eae9d2ba RG |
80 | info->name); |
81 | err = -EINVAL; | |
82 | goto pps_register_source_exit; | |
83 | } | |
84 | ||
85 | /* Allocate memory for the new PPS source struct */ | |
86 | pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL); | |
87 | if (pps == NULL) { | |
88 | err = -ENOMEM; | |
89 | goto pps_register_source_exit; | |
90 | } | |
91 | ||
19dd2da3 | 92 | /* These initializations must be done before calling idr_alloc() |
eae9d2ba RG |
93 | * in order to avoid reces into pps_event(). |
94 | */ | |
95 | pps->params.api_version = PPS_API_VERS; | |
96 | pps->params.mode = default_params; | |
97 | pps->info = *info; | |
98 | ||
437c5341 JN |
99 | /* check for default echo function */ |
100 | if ((pps->info.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) && | |
101 | pps->info.echo == NULL) | |
102 | pps->info.echo = pps_echo_client_default; | |
103 | ||
eae9d2ba RG |
104 | init_waitqueue_head(&pps->queue); |
105 | spin_lock_init(&pps->lock); | |
eae9d2ba | 106 | |
eae9d2ba RG |
107 | /* Create the char device */ |
108 | err = pps_register_cdev(pps); | |
109 | if (err < 0) { | |
7f7cce74 | 110 | pr_err("%s: unable to create char device\n", |
eae9d2ba | 111 | info->name); |
083e5866 | 112 | goto kfree_pps; |
eae9d2ba RG |
113 | } |
114 | ||
7f7cce74 | 115 | dev_info(pps->dev, "new PPS source %s\n", info->name); |
eae9d2ba | 116 | |
5e196d34 | 117 | return pps; |
eae9d2ba | 118 | |
eae9d2ba RG |
119 | kfree_pps: |
120 | kfree(pps); | |
121 | ||
122 | pps_register_source_exit: | |
7f7cce74 | 123 | pr_err("%s: unable to register source\n", info->name); |
eae9d2ba | 124 | |
3b1ad360 | 125 | return ERR_PTR(err); |
eae9d2ba RG |
126 | } |
127 | EXPORT_SYMBOL(pps_register_source); | |
128 | ||
129 | /* pps_unregister_source - remove a PPS source from the system | |
5e196d34 | 130 | * @pps: the PPS source |
eae9d2ba RG |
131 | * |
132 | * This function is used to remove a previously registered PPS source from | |
133 | * the system. | |
134 | */ | |
135 | ||
5e196d34 | 136 | void pps_unregister_source(struct pps_device *pps) |
eae9d2ba | 137 | { |
717c0336 | 138 | pps_kc_remove(pps); |
5e196d34 | 139 | pps_unregister_cdev(pps); |
eae9d2ba | 140 | |
083e5866 AG |
141 | /* don't have to kfree(pps) here because it will be done on |
142 | * device destruction */ | |
eae9d2ba RG |
143 | } |
144 | EXPORT_SYMBOL(pps_unregister_source); | |
145 | ||
146 | /* pps_event - register a PPS event into the system | |
5e196d34 | 147 | * @pps: the PPS device |
eae9d2ba RG |
148 | * @ts: the event timestamp |
149 | * @event: the event type | |
150 | * @data: userdef pointer | |
151 | * | |
152 | * This function is used by each PPS client in order to register a new | |
153 | * PPS event into the system (it's usually called inside an IRQ handler). | |
154 | * | |
5e196d34 | 155 | * If an echo function is associated with the PPS device it will be called |
eae9d2ba | 156 | * as: |
5e196d34 | 157 | * pps->info.echo(pps, event, data); |
eae9d2ba | 158 | */ |
5e196d34 AG |
159 | void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, |
160 | void *data) | |
eae9d2ba | 161 | { |
eae9d2ba | 162 | unsigned long flags; |
276b282e | 163 | int captured = 0; |
99b0d365 | 164 | struct pps_ktime ts_real = { .sec = 0, .nsec = 0, .flags = 0 }; |
eae9d2ba | 165 | |
29f347c9 AG |
166 | /* check event type */ |
167 | BUG_ON((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0); | |
eae9d2ba | 168 | |
ade1bdff AB |
169 | dev_dbg(pps->dev, "PPS event at %lld.%09ld\n", |
170 | (s64)ts->ts_real.tv_sec, ts->ts_real.tv_nsec); | |
6f4229b5 AG |
171 | |
172 | timespec_to_pps_ktime(&ts_real, ts->ts_real); | |
eae9d2ba RG |
173 | |
174 | spin_lock_irqsave(&pps->lock, flags); | |
175 | ||
176 | /* Must call the echo function? */ | |
177 | if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR))) | |
5e196d34 | 178 | pps->info.echo(pps, event, data); |
eae9d2ba RG |
179 | |
180 | /* Check the event */ | |
181 | pps->current_mode = pps->params.mode; | |
818b9eef | 182 | if (event & pps->params.mode & PPS_CAPTUREASSERT) { |
eae9d2ba RG |
183 | /* We have to add an offset? */ |
184 | if (pps->params.mode & PPS_OFFSETASSERT) | |
6f4229b5 AG |
185 | pps_add_offset(&ts_real, |
186 | &pps->params.assert_off_tu); | |
eae9d2ba RG |
187 | |
188 | /* Save the time stamp */ | |
6f4229b5 | 189 | pps->assert_tu = ts_real; |
eae9d2ba | 190 | pps->assert_sequence++; |
5e196d34 AG |
191 | dev_dbg(pps->dev, "capture assert seq #%u\n", |
192 | pps->assert_sequence); | |
276b282e RG |
193 | |
194 | captured = ~0; | |
eae9d2ba | 195 | } |
818b9eef | 196 | if (event & pps->params.mode & PPS_CAPTURECLEAR) { |
eae9d2ba RG |
197 | /* We have to add an offset? */ |
198 | if (pps->params.mode & PPS_OFFSETCLEAR) | |
6f4229b5 AG |
199 | pps_add_offset(&ts_real, |
200 | &pps->params.clear_off_tu); | |
eae9d2ba RG |
201 | |
202 | /* Save the time stamp */ | |
6f4229b5 | 203 | pps->clear_tu = ts_real; |
eae9d2ba | 204 | pps->clear_sequence++; |
5e196d34 AG |
205 | dev_dbg(pps->dev, "capture clear seq #%u\n", |
206 | pps->clear_sequence); | |
276b282e RG |
207 | |
208 | captured = ~0; | |
eae9d2ba RG |
209 | } |
210 | ||
717c0336 AG |
211 | pps_kc_event(pps, ts, event); |
212 | ||
7a21a3cc | 213 | /* Wake up if captured something */ |
276b282e | 214 | if (captured) { |
3003d55b AG |
215 | pps->last_ev++; |
216 | wake_up_interruptible_all(&pps->queue); | |
eae9d2ba | 217 | |
276b282e RG |
218 | kill_fasync(&pps->async_queue, SIGIO, POLL_IN); |
219 | } | |
eae9d2ba RG |
220 | |
221 | spin_unlock_irqrestore(&pps->lock, flags); | |
eae9d2ba RG |
222 | } |
223 | EXPORT_SYMBOL(pps_event); |