xref: /linux/drivers/ptp/ptp_clock.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2d94ba80eSRichard Cochran /*
3d94ba80eSRichard Cochran  * PTP 1588 clock support
4d94ba80eSRichard Cochran  *
5d94ba80eSRichard Cochran  * Copyright (C) 2010 OMICRON electronics GmbH
6d94ba80eSRichard Cochran  */
77356a764SJiri Benc #include <linux/idr.h>
8d94ba80eSRichard Cochran #include <linux/device.h>
9d94ba80eSRichard Cochran #include <linux/err.h>
10d94ba80eSRichard Cochran #include <linux/init.h>
11d94ba80eSRichard Cochran #include <linux/kernel.h>
12d94ba80eSRichard Cochran #include <linux/module.h>
13d94ba80eSRichard Cochran #include <linux/posix-clock.h>
14d94ba80eSRichard Cochran #include <linux/pps_kernel.h>
15d94ba80eSRichard Cochran #include <linux/slab.h>
16d94ba80eSRichard Cochran #include <linux/syscalls.h>
17d94ba80eSRichard Cochran #include <linux/uaccess.h>
18d9535cb7SGrygorii Strashko #include <uapi/linux/sched/types.h>
19d94ba80eSRichard Cochran 
20d94ba80eSRichard Cochran #include "ptp_private.h"
21d94ba80eSRichard Cochran 
22d94ba80eSRichard Cochran #define PTP_MAX_ALARMS 4
23d94ba80eSRichard Cochran #define PTP_PPS_DEFAULTS (PPS_CAPTUREASSERT | PPS_OFFSETASSERT)
24d94ba80eSRichard Cochran #define PTP_PPS_EVENT PPS_CAPTUREASSERT
25d94ba80eSRichard Cochran #define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
26d94ba80eSRichard Cochran 
27acb288e8SYangbo Lu struct class *ptp_class;
28acb288e8SYangbo Lu 
29d94ba80eSRichard Cochran /* private globals */
30d94ba80eSRichard Cochran 
31d94ba80eSRichard Cochran static dev_t ptp_devt;
32d94ba80eSRichard Cochran 
337356a764SJiri Benc static DEFINE_IDA(ptp_clocks_map);
34d94ba80eSRichard Cochran 
35d94ba80eSRichard Cochran /* time stamp event queue operations */
36d94ba80eSRichard Cochran 
37d94ba80eSRichard Cochran static inline int queue_free(struct timestamp_event_queue *q)
38d94ba80eSRichard Cochran {
39d94ba80eSRichard Cochran 	return PTP_MAX_TIMESTAMPS - queue_cnt(q) - 1;
40d94ba80eSRichard Cochran }
41d94ba80eSRichard Cochran 
42d94ba80eSRichard Cochran static void enqueue_external_timestamp(struct timestamp_event_queue *queue,
43d94ba80eSRichard Cochran 				       struct ptp_clock_event *src)
44d94ba80eSRichard Cochran {
45d94ba80eSRichard Cochran 	struct ptp_extts_event *dst;
46d94ba80eSRichard Cochran 	unsigned long flags;
47d94ba80eSRichard Cochran 	s64 seconds;
48d94ba80eSRichard Cochran 	u32 remainder;
49d94ba80eSRichard Cochran 
50d94ba80eSRichard Cochran 	seconds = div_u64_rem(src->timestamp, 1000000000, &remainder);
51d94ba80eSRichard Cochran 
52d94ba80eSRichard Cochran 	spin_lock_irqsave(&queue->lock, flags);
53d94ba80eSRichard Cochran 
54d94ba80eSRichard Cochran 	dst = &queue->buf[queue->tail];
55d94ba80eSRichard Cochran 	dst->index = src->index;
56d94ba80eSRichard Cochran 	dst->t.sec = seconds;
57d94ba80eSRichard Cochran 	dst->t.nsec = remainder;
58d94ba80eSRichard Cochran 
59d94ba80eSRichard Cochran 	if (!queue_free(queue))
60d94ba80eSRichard Cochran 		queue->head = (queue->head + 1) % PTP_MAX_TIMESTAMPS;
61d94ba80eSRichard Cochran 
62d94ba80eSRichard Cochran 	queue->tail = (queue->tail + 1) % PTP_MAX_TIMESTAMPS;
63d94ba80eSRichard Cochran 
64d94ba80eSRichard Cochran 	spin_unlock_irqrestore(&queue->lock, flags);
65d94ba80eSRichard Cochran }
66d94ba80eSRichard Cochran 
67d94ba80eSRichard Cochran /* posix clock implementation */
68d94ba80eSRichard Cochran 
69d340266eSDeepa Dinamani static int ptp_clock_getres(struct posix_clock *pc, struct timespec64 *tp)
70d94ba80eSRichard Cochran {
71d68fb11cSThomas Gleixner 	tp->tv_sec = 0;
72d68fb11cSThomas Gleixner 	tp->tv_nsec = 1;
73d68fb11cSThomas Gleixner 	return 0;
74d94ba80eSRichard Cochran }
75d94ba80eSRichard Cochran 
76d340266eSDeepa Dinamani static int ptp_clock_settime(struct posix_clock *pc, const struct timespec64 *tp)
77d94ba80eSRichard Cochran {
78d94ba80eSRichard Cochran 	struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
79d7d38f5bSRichard Cochran 
8073f37068SYangbo Lu 	if (ptp_vclock_in_use(ptp)) {
8173f37068SYangbo Lu 		pr_err("ptp: virtual clock in use\n");
8273f37068SYangbo Lu 		return -EBUSY;
8373f37068SYangbo Lu 	}
8473f37068SYangbo Lu 
85d340266eSDeepa Dinamani 	return  ptp->info->settime64(ptp->info, tp);
86d94ba80eSRichard Cochran }
87d94ba80eSRichard Cochran 
88d340266eSDeepa Dinamani static int ptp_clock_gettime(struct posix_clock *pc, struct timespec64 *tp)
89d94ba80eSRichard Cochran {
90d94ba80eSRichard Cochran 	struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
91d7d38f5bSRichard Cochran 	int err;
92d7d38f5bSRichard Cochran 
93916444dfSMiroslav Lichvar 	if (ptp->info->gettimex64)
94916444dfSMiroslav Lichvar 		err = ptp->info->gettimex64(ptp->info, tp, NULL);
95916444dfSMiroslav Lichvar 	else
96d340266eSDeepa Dinamani 		err = ptp->info->gettime64(ptp->info, tp);
97d7d38f5bSRichard Cochran 	return err;
98d94ba80eSRichard Cochran }
99d94ba80eSRichard Cochran 
100ead25417SDeepa Dinamani static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
101d94ba80eSRichard Cochran {
102d94ba80eSRichard Cochran 	struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
103d94ba80eSRichard Cochran 	struct ptp_clock_info *ops;
104d94ba80eSRichard Cochran 	int err = -EOPNOTSUPP;
105d94ba80eSRichard Cochran 
10673f37068SYangbo Lu 	if (ptp_vclock_in_use(ptp)) {
10773f37068SYangbo Lu 		pr_err("ptp: virtual clock in use\n");
10873f37068SYangbo Lu 		return -EBUSY;
10973f37068SYangbo Lu 	}
11073f37068SYangbo Lu 
111d94ba80eSRichard Cochran 	ops = ptp->info;
112d94ba80eSRichard Cochran 
113d94ba80eSRichard Cochran 	if (tx->modes & ADJ_SETOFFSET) {
114d340266eSDeepa Dinamani 		struct timespec64 ts;
115d94ba80eSRichard Cochran 		ktime_t kt;
116d94ba80eSRichard Cochran 		s64 delta;
117d94ba80eSRichard Cochran 
118d94ba80eSRichard Cochran 		ts.tv_sec  = tx->time.tv_sec;
119d94ba80eSRichard Cochran 		ts.tv_nsec = tx->time.tv_usec;
120d94ba80eSRichard Cochran 
121d94ba80eSRichard Cochran 		if (!(tx->modes & ADJ_NANO))
122d94ba80eSRichard Cochran 			ts.tv_nsec *= 1000;
123d94ba80eSRichard Cochran 
124d94ba80eSRichard Cochran 		if ((unsigned long) ts.tv_nsec >= NSEC_PER_SEC)
125d94ba80eSRichard Cochran 			return -EINVAL;
126d94ba80eSRichard Cochran 
127d340266eSDeepa Dinamani 		kt = timespec64_to_ktime(ts);
128d94ba80eSRichard Cochran 		delta = ktime_to_ns(kt);
129d94ba80eSRichard Cochran 		err = ops->adjtime(ops, delta);
130d94ba80eSRichard Cochran 	} else if (tx->modes & ADJ_FREQUENCY) {
131475b92f9SJakub Kicinski 		long ppb = scaled_ppm_to_ppb(tx->freq);
132d39a7435SRichard Cochran 		if (ppb > ops->max_adj || ppb < -ops->max_adj)
133d39a7435SRichard Cochran 			return -ERANGE;
134d8d26354SRichard Cochran 		if (ops->adjfine)
135d8d26354SRichard Cochran 			err = ops->adjfine(ops, tx->freq);
136d8d26354SRichard Cochran 		else
137d39a7435SRichard Cochran 			err = ops->adjfreq(ops, ppb);
13839a8cbd9SRichard Cochran 		ptp->dialed_frequency = tx->freq;
139184ecc9eSVincent Cheng 	} else if (tx->modes & ADJ_OFFSET) {
140eabd5c9dSRichard Cochran 		if (ops->adjphase) {
141eabd5c9dSRichard Cochran 			s32 offset = tx->offset;
142eabd5c9dSRichard Cochran 
143eabd5c9dSRichard Cochran 			if (!(tx->modes & ADJ_NANO))
144eabd5c9dSRichard Cochran 				offset *= NSEC_PER_USEC;
145eabd5c9dSRichard Cochran 
146eabd5c9dSRichard Cochran 			err = ops->adjphase(ops, offset);
147eabd5c9dSRichard Cochran 		}
1485c35bad5SRichard Cochran 	} else if (tx->modes == 0) {
1495c35bad5SRichard Cochran 		tx->freq = ptp->dialed_frequency;
1505c35bad5SRichard Cochran 		err = 0;
151d94ba80eSRichard Cochran 	}
152d94ba80eSRichard Cochran 
153d94ba80eSRichard Cochran 	return err;
154d94ba80eSRichard Cochran }
155d94ba80eSRichard Cochran 
156d94ba80eSRichard Cochran static struct posix_clock_operations ptp_clock_ops = {
157d94ba80eSRichard Cochran 	.owner		= THIS_MODULE,
158d94ba80eSRichard Cochran 	.clock_adjtime	= ptp_clock_adjtime,
159d94ba80eSRichard Cochran 	.clock_gettime	= ptp_clock_gettime,
160d94ba80eSRichard Cochran 	.clock_getres	= ptp_clock_getres,
161d94ba80eSRichard Cochran 	.clock_settime	= ptp_clock_settime,
162d94ba80eSRichard Cochran 	.ioctl		= ptp_ioctl,
163d94ba80eSRichard Cochran 	.open		= ptp_open,
164d94ba80eSRichard Cochran 	.poll		= ptp_poll,
165d94ba80eSRichard Cochran 	.read		= ptp_read,
166d94ba80eSRichard Cochran };
167d94ba80eSRichard Cochran 
168a33121e5SVladis Dronov static void ptp_clock_release(struct device *dev)
169d94ba80eSRichard Cochran {
170a33121e5SVladis Dronov 	struct ptp_clock *ptp = container_of(dev, struct ptp_clock, dev);
171d94ba80eSRichard Cochran 
17275718584SVladis Dronov 	ptp_cleanup_pin_groups(ptp);
173b6b19a71SYang Yingliang 	kfree(ptp->vclock_index);
174d94ba80eSRichard Cochran 	mutex_destroy(&ptp->tsevq_mux);
1756092315dSRichard Cochran 	mutex_destroy(&ptp->pincfg_mux);
17673f37068SYangbo Lu 	mutex_destroy(&ptp->n_vclocks_mux);
1777356a764SJiri Benc 	ida_simple_remove(&ptp_clocks_map, ptp->index);
178d94ba80eSRichard Cochran 	kfree(ptp);
179d94ba80eSRichard Cochran }
180d94ba80eSRichard Cochran 
181d9535cb7SGrygorii Strashko static void ptp_aux_kworker(struct kthread_work *work)
182d9535cb7SGrygorii Strashko {
183d9535cb7SGrygorii Strashko 	struct ptp_clock *ptp = container_of(work, struct ptp_clock,
184d9535cb7SGrygorii Strashko 					     aux_work.work);
185d9535cb7SGrygorii Strashko 	struct ptp_clock_info *info = ptp->info;
186d9535cb7SGrygorii Strashko 	long delay;
187d9535cb7SGrygorii Strashko 
188d9535cb7SGrygorii Strashko 	delay = info->do_aux_work(info);
189d9535cb7SGrygorii Strashko 
190d9535cb7SGrygorii Strashko 	if (delay >= 0)
191d9535cb7SGrygorii Strashko 		kthread_queue_delayed_work(ptp->kworker, &ptp->aux_work, delay);
192d9535cb7SGrygorii Strashko }
193d9535cb7SGrygorii Strashko 
194d94ba80eSRichard Cochran /* public interface */
195d94ba80eSRichard Cochran 
1961ef76158SRichard Cochran struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
1971ef76158SRichard Cochran 				     struct device *parent)
198d94ba80eSRichard Cochran {
199d94ba80eSRichard Cochran 	struct ptp_clock *ptp;
200d94ba80eSRichard Cochran 	int err = 0, index, major = MAJOR(ptp_devt);
20144c494c8SYangbo Lu 	size_t size;
202d94ba80eSRichard Cochran 
203d94ba80eSRichard Cochran 	if (info->n_alarm > PTP_MAX_ALARMS)
204d94ba80eSRichard Cochran 		return ERR_PTR(-EINVAL);
205d94ba80eSRichard Cochran 
206d94ba80eSRichard Cochran 	/* Initialize a clock structure. */
207d94ba80eSRichard Cochran 	err = -ENOMEM;
208d94ba80eSRichard Cochran 	ptp = kzalloc(sizeof(struct ptp_clock), GFP_KERNEL);
209d94ba80eSRichard Cochran 	if (ptp == NULL)
210d94ba80eSRichard Cochran 		goto no_memory;
211d94ba80eSRichard Cochran 
2127356a764SJiri Benc 	index = ida_simple_get(&ptp_clocks_map, 0, MINORMASK + 1, GFP_KERNEL);
2137356a764SJiri Benc 	if (index < 0) {
2147356a764SJiri Benc 		err = index;
2157356a764SJiri Benc 		goto no_slot;
2167356a764SJiri Benc 	}
2177356a764SJiri Benc 
218d94ba80eSRichard Cochran 	ptp->clock.ops = ptp_clock_ops;
219d94ba80eSRichard Cochran 	ptp->info = info;
220d94ba80eSRichard Cochran 	ptp->devid = MKDEV(major, index);
221d94ba80eSRichard Cochran 	ptp->index = index;
222d94ba80eSRichard Cochran 	spin_lock_init(&ptp->tsevq.lock);
223d94ba80eSRichard Cochran 	mutex_init(&ptp->tsevq_mux);
2246092315dSRichard Cochran 	mutex_init(&ptp->pincfg_mux);
22573f37068SYangbo Lu 	mutex_init(&ptp->n_vclocks_mux);
226d94ba80eSRichard Cochran 	init_waitqueue_head(&ptp->tsev_wq);
227d94ba80eSRichard Cochran 
228d9535cb7SGrygorii Strashko 	if (ptp->info->do_aux_work) {
229d9535cb7SGrygorii Strashko 		kthread_init_delayed_work(&ptp->aux_work, ptp_aux_kworker);
230822c5f73SRasmus Villemoes 		ptp->kworker = kthread_create_worker(0, "ptp%d", ptp->index);
231d9535cb7SGrygorii Strashko 		if (IS_ERR(ptp->kworker)) {
232d9535cb7SGrygorii Strashko 			err = PTR_ERR(ptp->kworker);
233d9535cb7SGrygorii Strashko 			pr_err("failed to create ptp aux_worker %d\n", err);
234d9535cb7SGrygorii Strashko 			goto kworker_err;
235d9535cb7SGrygorii Strashko 		}
236d9535cb7SGrygorii Strashko 	}
237d9535cb7SGrygorii Strashko 
23873f37068SYangbo Lu 	/* PTP virtual clock is being registered under physical clock */
23955eac206SYangbo Lu 	if (parent && parent->class && parent->class->name &&
24073f37068SYangbo Lu 	    strcmp(parent->class->name, "ptp") == 0)
24173f37068SYangbo Lu 		ptp->is_virtual_clock = true;
24273f37068SYangbo Lu 
24344c494c8SYangbo Lu 	if (!ptp->is_virtual_clock) {
24473f37068SYangbo Lu 		ptp->max_vclocks = PTP_DEFAULT_MAX_VCLOCKS;
24573f37068SYangbo Lu 
24644c494c8SYangbo Lu 		size = sizeof(int) * ptp->max_vclocks;
24744c494c8SYangbo Lu 		ptp->vclock_index = kzalloc(size, GFP_KERNEL);
24844c494c8SYangbo Lu 		if (!ptp->vclock_index) {
24944c494c8SYangbo Lu 			err = -ENOMEM;
25044c494c8SYangbo Lu 			goto no_mem_for_vclocks;
25144c494c8SYangbo Lu 		}
25244c494c8SYangbo Lu 	}
25344c494c8SYangbo Lu 
25485a66e55SDmitry Torokhov 	err = ptp_populate_pin_groups(ptp);
25585a66e55SDmitry Torokhov 	if (err)
25685a66e55SDmitry Torokhov 		goto no_pin_groups;
25785a66e55SDmitry Torokhov 
258d94ba80eSRichard Cochran 	/* Register a new PPS source. */
259d94ba80eSRichard Cochran 	if (info->pps) {
260d94ba80eSRichard Cochran 		struct pps_source_info pps;
261d94ba80eSRichard Cochran 		memset(&pps, 0, sizeof(pps));
262d94ba80eSRichard Cochran 		snprintf(pps.name, PPS_MAX_NAME_LEN, "ptp%d", index);
263d94ba80eSRichard Cochran 		pps.mode = PTP_PPS_MODE;
264d94ba80eSRichard Cochran 		pps.owner = info->owner;
265d94ba80eSRichard Cochran 		ptp->pps_source = pps_register_source(&pps, PTP_PPS_DEFAULTS);
266b9d93594SDan Carpenter 		if (IS_ERR(ptp->pps_source)) {
267b9d93594SDan Carpenter 			err = PTR_ERR(ptp->pps_source);
268d94ba80eSRichard Cochran 			pr_err("failed to register pps source\n");
269d94ba80eSRichard Cochran 			goto no_pps;
270d94ba80eSRichard Cochran 		}
271debdd8e3SJonathan Lemon 		ptp->pps_source->lookup_cookie = ptp;
272d94ba80eSRichard Cochran 	}
273d94ba80eSRichard Cochran 
274a33121e5SVladis Dronov 	/* Initialize a new device of our class in our clock structure. */
275a33121e5SVladis Dronov 	device_initialize(&ptp->dev);
276a33121e5SVladis Dronov 	ptp->dev.devt = ptp->devid;
277a33121e5SVladis Dronov 	ptp->dev.class = ptp_class;
278a33121e5SVladis Dronov 	ptp->dev.parent = parent;
279a33121e5SVladis Dronov 	ptp->dev.groups = ptp->pin_attr_groups;
280a33121e5SVladis Dronov 	ptp->dev.release = ptp_clock_release;
281a33121e5SVladis Dronov 	dev_set_drvdata(&ptp->dev, ptp);
282a33121e5SVladis Dronov 	dev_set_name(&ptp->dev, "ptp%d", ptp->index);
283a33121e5SVladis Dronov 
284a33121e5SVladis Dronov 	/* Create a posix clock and link it to the device. */
285a33121e5SVladis Dronov 	err = posix_clock_register(&ptp->clock, &ptp->dev);
286d94ba80eSRichard Cochran 	if (err) {
2874225fea1SYang Yingliang 		if (ptp->pps_source)
2884225fea1SYang Yingliang 			pps_unregister_source(ptp->pps_source);
2894225fea1SYang Yingliang 
2904225fea1SYang Yingliang 		if (ptp->kworker)
2914225fea1SYang Yingliang 			kthread_destroy_worker(ptp->kworker);
2924225fea1SYang Yingliang 
2934225fea1SYang Yingliang 		put_device(&ptp->dev);
2944225fea1SYang Yingliang 
295d94ba80eSRichard Cochran 		pr_err("failed to create posix clock\n");
2964225fea1SYang Yingliang 		return ERR_PTR(err);
297d94ba80eSRichard Cochran 	}
298d94ba80eSRichard Cochran 
299d94ba80eSRichard Cochran 	return ptp;
300d94ba80eSRichard Cochran 
301d94ba80eSRichard Cochran no_pps:
30285a66e55SDmitry Torokhov 	ptp_cleanup_pin_groups(ptp);
30385a66e55SDmitry Torokhov no_pin_groups:
30444c494c8SYangbo Lu 	kfree(ptp->vclock_index);
30544c494c8SYangbo Lu no_mem_for_vclocks:
306d9535cb7SGrygorii Strashko 	if (ptp->kworker)
307d9535cb7SGrygorii Strashko 		kthread_destroy_worker(ptp->kworker);
308d9535cb7SGrygorii Strashko kworker_err:
309d94ba80eSRichard Cochran 	mutex_destroy(&ptp->tsevq_mux);
3106092315dSRichard Cochran 	mutex_destroy(&ptp->pincfg_mux);
31173f37068SYangbo Lu 	mutex_destroy(&ptp->n_vclocks_mux);
312b9118b72SChristophe Jaillet 	ida_simple_remove(&ptp_clocks_map, index);
3137356a764SJiri Benc no_slot:
314d94ba80eSRichard Cochran 	kfree(ptp);
315d94ba80eSRichard Cochran no_memory:
316d94ba80eSRichard Cochran 	return ERR_PTR(err);
317d94ba80eSRichard Cochran }
318d94ba80eSRichard Cochran EXPORT_SYMBOL(ptp_clock_register);
319d94ba80eSRichard Cochran 
320bfcbb76bSMiroslav Lichvar static int unregister_vclock(struct device *dev, void *data)
321bfcbb76bSMiroslav Lichvar {
322bfcbb76bSMiroslav Lichvar 	struct ptp_clock *ptp = dev_get_drvdata(dev);
323bfcbb76bSMiroslav Lichvar 
324bfcbb76bSMiroslav Lichvar 	ptp_vclock_unregister(info_to_vclock(ptp->info));
325bfcbb76bSMiroslav Lichvar 	return 0;
326bfcbb76bSMiroslav Lichvar }
327bfcbb76bSMiroslav Lichvar 
328d94ba80eSRichard Cochran int ptp_clock_unregister(struct ptp_clock *ptp)
329d94ba80eSRichard Cochran {
33073f37068SYangbo Lu 	if (ptp_vclock_in_use(ptp)) {
331bfcbb76bSMiroslav Lichvar 		device_for_each_child(&ptp->dev, NULL, unregister_vclock);
33273f37068SYangbo Lu 	}
33373f37068SYangbo Lu 
334d94ba80eSRichard Cochran 	ptp->defunct = 1;
335d94ba80eSRichard Cochran 	wake_up_interruptible(&ptp->tsev_wq);
336d94ba80eSRichard Cochran 
337d9535cb7SGrygorii Strashko 	if (ptp->kworker) {
338d9535cb7SGrygorii Strashko 		kthread_cancel_delayed_work_sync(&ptp->aux_work);
339d9535cb7SGrygorii Strashko 		kthread_destroy_worker(ptp->kworker);
340d9535cb7SGrygorii Strashko 	}
341d9535cb7SGrygorii Strashko 
342d94ba80eSRichard Cochran 	/* Release the clock's resources. */
343d94ba80eSRichard Cochran 	if (ptp->pps_source)
344d94ba80eSRichard Cochran 		pps_unregister_source(ptp->pps_source);
34585a66e55SDmitry Torokhov 
346d94ba80eSRichard Cochran 	posix_clock_unregister(&ptp->clock);
34775718584SVladis Dronov 
348d94ba80eSRichard Cochran 	return 0;
349d94ba80eSRichard Cochran }
350d94ba80eSRichard Cochran EXPORT_SYMBOL(ptp_clock_unregister);
351d94ba80eSRichard Cochran 
352d94ba80eSRichard Cochran void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
353d94ba80eSRichard Cochran {
354d94ba80eSRichard Cochran 	struct pps_event_time evt;
355d94ba80eSRichard Cochran 
356d94ba80eSRichard Cochran 	switch (event->type) {
357d94ba80eSRichard Cochran 
358d94ba80eSRichard Cochran 	case PTP_CLOCK_ALARM:
359d94ba80eSRichard Cochran 		break;
360d94ba80eSRichard Cochran 
361d94ba80eSRichard Cochran 	case PTP_CLOCK_EXTTS:
362d94ba80eSRichard Cochran 		enqueue_external_timestamp(&ptp->tsevq, event);
363d94ba80eSRichard Cochran 		wake_up_interruptible(&ptp->tsev_wq);
364d94ba80eSRichard Cochran 		break;
365d94ba80eSRichard Cochran 
366d94ba80eSRichard Cochran 	case PTP_CLOCK_PPS:
367d94ba80eSRichard Cochran 		pps_get_ts(&evt);
368d94ba80eSRichard Cochran 		pps_event(ptp->pps_source, &evt, PTP_PPS_EVENT, NULL);
369d94ba80eSRichard Cochran 		break;
370220a60a4SBen Hutchings 
371220a60a4SBen Hutchings 	case PTP_CLOCK_PPSUSR:
372220a60a4SBen Hutchings 		pps_event(ptp->pps_source, &event->pps_times,
373220a60a4SBen Hutchings 			  PTP_PPS_EVENT, NULL);
374220a60a4SBen Hutchings 		break;
375d94ba80eSRichard Cochran 	}
376d94ba80eSRichard Cochran }
377d94ba80eSRichard Cochran EXPORT_SYMBOL(ptp_clock_event);
378d94ba80eSRichard Cochran 
379995a9090SRichard Cochran int ptp_clock_index(struct ptp_clock *ptp)
380995a9090SRichard Cochran {
381995a9090SRichard Cochran 	return ptp->index;
382995a9090SRichard Cochran }
383995a9090SRichard Cochran EXPORT_SYMBOL(ptp_clock_index);
384995a9090SRichard Cochran 
3856092315dSRichard Cochran int ptp_find_pin(struct ptp_clock *ptp,
3866092315dSRichard Cochran 		 enum ptp_pin_function func, unsigned int chan)
3876092315dSRichard Cochran {
3886092315dSRichard Cochran 	struct ptp_pin_desc *pin = NULL;
3896092315dSRichard Cochran 	int i;
3906092315dSRichard Cochran 
3916092315dSRichard Cochran 	for (i = 0; i < ptp->info->n_pins; i++) {
3926092315dSRichard Cochran 		if (ptp->info->pin_config[i].func == func &&
3936092315dSRichard Cochran 		    ptp->info->pin_config[i].chan == chan) {
3946092315dSRichard Cochran 			pin = &ptp->info->pin_config[i];
3956092315dSRichard Cochran 			break;
3966092315dSRichard Cochran 		}
3976092315dSRichard Cochran 	}
3986092315dSRichard Cochran 
3996092315dSRichard Cochran 	return pin ? i : -1;
4006092315dSRichard Cochran }
4016092315dSRichard Cochran EXPORT_SYMBOL(ptp_find_pin);
4026092315dSRichard Cochran 
40362582a7eSRichard Cochran int ptp_find_pin_unlocked(struct ptp_clock *ptp,
40462582a7eSRichard Cochran 			  enum ptp_pin_function func, unsigned int chan)
40562582a7eSRichard Cochran {
40662582a7eSRichard Cochran 	int result;
40762582a7eSRichard Cochran 
40862582a7eSRichard Cochran 	mutex_lock(&ptp->pincfg_mux);
40962582a7eSRichard Cochran 
41062582a7eSRichard Cochran 	result = ptp_find_pin(ptp, func, chan);
41162582a7eSRichard Cochran 
41262582a7eSRichard Cochran 	mutex_unlock(&ptp->pincfg_mux);
41362582a7eSRichard Cochran 
41462582a7eSRichard Cochran 	return result;
41562582a7eSRichard Cochran }
41662582a7eSRichard Cochran EXPORT_SYMBOL(ptp_find_pin_unlocked);
41762582a7eSRichard Cochran 
418d9535cb7SGrygorii Strashko int ptp_schedule_worker(struct ptp_clock *ptp, unsigned long delay)
419d9535cb7SGrygorii Strashko {
420d9535cb7SGrygorii Strashko 	return kthread_mod_delayed_work(ptp->kworker, &ptp->aux_work, delay);
421d9535cb7SGrygorii Strashko }
422d9535cb7SGrygorii Strashko EXPORT_SYMBOL(ptp_schedule_worker);
423d9535cb7SGrygorii Strashko 
424544fed47SVladimir Oltean void ptp_cancel_worker_sync(struct ptp_clock *ptp)
425544fed47SVladimir Oltean {
426544fed47SVladimir Oltean 	kthread_cancel_delayed_work_sync(&ptp->aux_work);
427544fed47SVladimir Oltean }
428544fed47SVladimir Oltean EXPORT_SYMBOL(ptp_cancel_worker_sync);
429544fed47SVladimir Oltean 
430d94ba80eSRichard Cochran /* module operations */
431d94ba80eSRichard Cochran 
432d94ba80eSRichard Cochran static void __exit ptp_exit(void)
433d94ba80eSRichard Cochran {
434d94ba80eSRichard Cochran 	class_destroy(ptp_class);
4357356a764SJiri Benc 	unregister_chrdev_region(ptp_devt, MINORMASK + 1);
4367356a764SJiri Benc 	ida_destroy(&ptp_clocks_map);
437d94ba80eSRichard Cochran }
438d94ba80eSRichard Cochran 
439d94ba80eSRichard Cochran static int __init ptp_init(void)
440d94ba80eSRichard Cochran {
441d94ba80eSRichard Cochran 	int err;
442d94ba80eSRichard Cochran 
443d94ba80eSRichard Cochran 	ptp_class = class_create(THIS_MODULE, "ptp");
444d94ba80eSRichard Cochran 	if (IS_ERR(ptp_class)) {
445d94ba80eSRichard Cochran 		pr_err("ptp: failed to allocate class\n");
446d94ba80eSRichard Cochran 		return PTR_ERR(ptp_class);
447d94ba80eSRichard Cochran 	}
448d94ba80eSRichard Cochran 
4497356a764SJiri Benc 	err = alloc_chrdev_region(&ptp_devt, 0, MINORMASK + 1, "ptp");
450d94ba80eSRichard Cochran 	if (err < 0) {
451d94ba80eSRichard Cochran 		pr_err("ptp: failed to allocate device region\n");
452d94ba80eSRichard Cochran 		goto no_region;
453d94ba80eSRichard Cochran 	}
454d94ba80eSRichard Cochran 
4553499116bSGreg Kroah-Hartman 	ptp_class->dev_groups = ptp_groups;
456d94ba80eSRichard Cochran 	pr_info("PTP clock support registered\n");
457d94ba80eSRichard Cochran 	return 0;
458d94ba80eSRichard Cochran 
459d94ba80eSRichard Cochran no_region:
460d94ba80eSRichard Cochran 	class_destroy(ptp_class);
461d94ba80eSRichard Cochran 	return err;
462d94ba80eSRichard Cochran }
463d94ba80eSRichard Cochran 
464d94ba80eSRichard Cochran subsys_initcall(ptp_init);
465d94ba80eSRichard Cochran module_exit(ptp_exit);
466d94ba80eSRichard Cochran 
467c2ec3ff6SRichard Cochran MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>");
468d94ba80eSRichard Cochran MODULE_DESCRIPTION("PTP clocks support");
469d94ba80eSRichard Cochran MODULE_LICENSE("GPL");
470