summaryrefslogtreecommitdiff
path: root/drivers/ptp/ptp_clock.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ptp/ptp_clock.c')
-rw-r--r--drivers/ptp/ptp_clock.c172
1 files changed, 165 insertions, 7 deletions
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index c56cd0f63909..b0e167c0b3eb 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/posix-clock.h>
#include <linux/pps_kernel.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
@@ -96,10 +97,13 @@ static int ptp_clock_settime(struct posix_clock *pc, const struct timespec64 *tp
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
if (ptp_clock_freerun(ptp)) {
- pr_err("ptp: physical clock is free running\n");
+ pr_err_ratelimited("ptp: physical clock is free running\n");
return -EBUSY;
}
+ if (!timespec64_valid_settod(tp))
+ return -EINVAL;
+
return ptp->info->settime64(ptp->info, tp);
}
@@ -121,7 +125,8 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
struct ptp_clock_info *ops;
int err = -EOPNOTSUPP;
- if (ptp_clock_freerun(ptp)) {
+ if (tx->modes & (ADJ_SETOFFSET | ADJ_FREQUENCY | ADJ_OFFSET) &&
+ ptp_clock_freerun(ptp)) {
pr_err("ptp: physical clock is free running\n");
return -EBUSY;
}
@@ -129,7 +134,7 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
ops = ptp->info;
if (tx->modes & ADJ_SETOFFSET) {
- struct timespec64 ts;
+ struct timespec64 ts, ts2;
ktime_t kt;
s64 delta;
@@ -142,6 +147,14 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
if ((unsigned long) ts.tv_nsec >= NSEC_PER_SEC)
return -EINVAL;
+ /* Make sure the offset is valid */
+ err = ptp_clock_gettime(pc, &ts2);
+ if (err)
+ return err;
+ ts2 = timespec64_add(ts2, ts);
+ if (!timespec64_valid_settod(&ts2))
+ return -EINVAL;
+
kt = timespec64_to_ktime(ts);
delta = ktime_to_ns(kt);
err = ops->adjtime(ops, delta);
@@ -150,7 +163,8 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
if (ppb > ops->max_adj || ppb < -ops->max_adj)
return -ERANGE;
err = ops->adjfine(ops, tx->freq);
- ptp->dialed_frequency = tx->freq;
+ if (!err)
+ ptp->dialed_frequency = tx->freq;
} else if (tx->modes & ADJ_OFFSET) {
if (ops->adjphase) {
s32 max_phase_adj = ops->getmaxphase(ops);
@@ -216,6 +230,11 @@ static int ptp_getcycles64(struct ptp_clock_info *info, struct timespec64 *ts)
return info->gettime64(info, ts);
}
+static int ptp_enable(struct ptp_clock_info *ptp, struct ptp_clock_request *request, int on)
+{
+ return -EOPNOTSUPP;
+}
+
static void ptp_aux_kworker(struct kthread_work *work)
{
struct ptp_clock *ptp = container_of(work, struct ptp_clock,
@@ -229,6 +248,69 @@ static void ptp_aux_kworker(struct kthread_work *work)
kthread_queue_delayed_work(ptp->kworker, &ptp->aux_work, delay);
}
+static ssize_t ptp_n_perout_loopback_read(struct file *filep,
+ char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ struct ptp_clock *ptp = filep->private_data;
+ char buf[12] = {};
+
+ snprintf(buf, sizeof(buf), "%d\n", ptp->info->n_per_lp);
+
+ return simple_read_from_buffer(buffer, count, pos, buf, strlen(buf));
+}
+
+static const struct file_operations ptp_n_perout_loopback_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = ptp_n_perout_loopback_read,
+};
+
+static ssize_t ptp_perout_loopback_write(struct file *filep,
+ const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct ptp_clock *ptp = filep->private_data;
+ struct ptp_clock_info *ops = ptp->info;
+ unsigned int index, enable;
+ int len, cnt, err;
+ char buf[32] = {};
+
+ if (*ppos || !count)
+ return -EINVAL;
+
+ if (count >= sizeof(buf))
+ return -ENOSPC;
+
+ len = simple_write_to_buffer(buf, sizeof(buf) - 1,
+ ppos, buffer, count);
+ if (len < 0)
+ return len;
+
+ buf[len] = '\0';
+ cnt = sscanf(buf, "%u %u", &index, &enable);
+ if (cnt != 2)
+ return -EINVAL;
+
+ if (index >= ops->n_per_lp)
+ return -EINVAL;
+
+ if (enable != 0 && enable != 1)
+ return -EINVAL;
+
+ err = ops->perout_loopback(ops, index, enable);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static const struct file_operations ptp_perout_loopback_ops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .write = ptp_perout_loopback_write,
+};
+
/* public interface */
struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
@@ -240,7 +322,9 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
char debugfsname[16];
size_t size;
- if (info->n_alarm > PTP_MAX_ALARMS)
+ if (WARN_ON_ONCE(info->n_alarm > PTP_MAX_ALARMS ||
+ (!info->gettimex64 && !info->gettime64) ||
+ !info->settime64))
return ERR_PTR(-EINVAL);
/* Initialize a clock structure. */
@@ -293,9 +377,12 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
ptp->info->getcrosscycles = ptp->info->getcrosststamp;
}
+ if (!ptp->info->enable)
+ ptp->info->enable = ptp_enable;
+
if (ptp->info->do_aux_work) {
kthread_init_delayed_work(&ptp->aux_work, ptp_aux_kworker);
- ptp->kworker = kthread_create_worker(0, "ptp%d", ptp->index);
+ ptp->kworker = kthread_run_worker(0, "ptp%d", ptp->index);
if (IS_ERR(ptp->kworker)) {
err = PTR_ERR(ptp->kworker);
pr_err("failed to create ptp aux_worker %d\n", err);
@@ -367,6 +454,12 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
/* Debugfs initialization */
snprintf(debugfsname, sizeof(debugfsname), "ptp%d", ptp->index);
ptp->debugfs_root = debugfs_create_dir(debugfsname, NULL);
+ if (info->n_per_lp > 0 && info->perout_loopback) {
+ debugfs_create_file("n_perout_loopback", 0400, ptp->debugfs_root,
+ ptp, &ptp_n_perout_loopback_fops);
+ debugfs_create_file("perout_loopback", 0200, ptp->debugfs_root,
+ ptp, &ptp_perout_loopback_ops);
+ }
return ptp;
@@ -407,9 +500,21 @@ int ptp_clock_unregister(struct ptp_clock *ptp)
device_for_each_child(&ptp->dev, NULL, unregister_vclock);
}
+ /* Get the device to stop posix_clock_unregister() doing the last put
+ * and freeing the structure(s)
+ */
+ get_device(&ptp->dev);
+
+ /* Wake up any userspace waiting for an event. */
ptp->defunct = 1;
wake_up_interruptible(&ptp->tsev_wq);
+ /* Tear down the POSIX clock, which removes the user interface. */
+ posix_clock_unregister(&ptp->clock);
+
+ /* Disable all sources of event generation. */
+ ptp_disable_all_events(ptp);
+
if (ptp->kworker) {
kthread_cancel_delayed_work_sync(&ptp->aux_work);
kthread_destroy_worker(ptp->kworker);
@@ -419,7 +524,8 @@ int ptp_clock_unregister(struct ptp_clock *ptp)
if (ptp->pps_source)
pps_unregister_source(ptp->pps_source);
- posix_clock_unregister(&ptp->clock);
+ /* The final put, normally here, will invoke ptp_clock_release(). */
+ put_device(&ptp->dev);
return 0;
}
@@ -467,6 +573,58 @@ int ptp_clock_index(struct ptp_clock *ptp)
}
EXPORT_SYMBOL(ptp_clock_index);
+static int ptp_clock_of_node_match(struct device *dev, const void *data)
+{
+ const struct device_node *parent_np = data;
+
+ return (dev->parent && dev_of_node(dev->parent) == parent_np);
+}
+
+int ptp_clock_index_by_of_node(struct device_node *np)
+{
+ struct ptp_clock *ptp;
+ struct device *dev;
+ int phc_index;
+
+ dev = class_find_device(&ptp_class, NULL, np,
+ ptp_clock_of_node_match);
+ if (!dev)
+ return -1;
+
+ ptp = dev_get_drvdata(dev);
+ phc_index = ptp_clock_index(ptp);
+ put_device(dev);
+
+ return phc_index;
+}
+EXPORT_SYMBOL_GPL(ptp_clock_index_by_of_node);
+
+static int ptp_clock_dev_match(struct device *dev, const void *data)
+{
+ const struct device *parent = data;
+
+ return dev->parent == parent;
+}
+
+int ptp_clock_index_by_dev(struct device *parent)
+{
+ struct ptp_clock *ptp;
+ struct device *dev;
+ int phc_index;
+
+ dev = class_find_device(&ptp_class, NULL, parent,
+ ptp_clock_dev_match);
+ if (!dev)
+ return -1;
+
+ ptp = dev_get_drvdata(dev);
+ phc_index = ptp_clock_index(ptp);
+ put_device(dev);
+
+ return phc_index;
+}
+EXPORT_SYMBOL_GPL(ptp_clock_index_by_dev);
+
int ptp_find_pin(struct ptp_clock *ptp,
enum ptp_pin_function func, unsigned int chan)
{