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