18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * PTP 1588 clock support - sysfs interface.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2010 OMICRON electronics GmbH
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci#include <linux/capability.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include "ptp_private.h"
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_cistatic ssize_t clock_name_show(struct device *dev,
138c2ecf20Sopenharmony_ci			       struct device_attribute *attr, char *page)
148c2ecf20Sopenharmony_ci{
158c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
168c2ecf20Sopenharmony_ci	return sysfs_emit(page, "%s\n", ptp->info->name);
178c2ecf20Sopenharmony_ci}
188c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(clock_name);
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define PTP_SHOW_INT(name, var)						\
218c2ecf20Sopenharmony_cistatic ssize_t var##_show(struct device *dev,				\
228c2ecf20Sopenharmony_ci			   struct device_attribute *attr, char *page)	\
238c2ecf20Sopenharmony_ci{									\
248c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);			\
258c2ecf20Sopenharmony_ci	return snprintf(page, PAGE_SIZE-1, "%d\n", ptp->info->var);	\
268c2ecf20Sopenharmony_ci}									\
278c2ecf20Sopenharmony_cistatic DEVICE_ATTR(name, 0444, var##_show, NULL);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ciPTP_SHOW_INT(max_adjustment, max_adj);
308c2ecf20Sopenharmony_ciPTP_SHOW_INT(n_alarms, n_alarm);
318c2ecf20Sopenharmony_ciPTP_SHOW_INT(n_external_timestamps, n_ext_ts);
328c2ecf20Sopenharmony_ciPTP_SHOW_INT(n_periodic_outputs, n_per_out);
338c2ecf20Sopenharmony_ciPTP_SHOW_INT(n_programmable_pins, n_pins);
348c2ecf20Sopenharmony_ciPTP_SHOW_INT(pps_available, pps);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic ssize_t extts_enable_store(struct device *dev,
378c2ecf20Sopenharmony_ci				  struct device_attribute *attr,
388c2ecf20Sopenharmony_ci				  const char *buf, size_t count)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
418c2ecf20Sopenharmony_ci	struct ptp_clock_info *ops = ptp->info;
428c2ecf20Sopenharmony_ci	struct ptp_clock_request req = { .type = PTP_CLK_REQ_EXTTS };
438c2ecf20Sopenharmony_ci	int cnt, enable;
448c2ecf20Sopenharmony_ci	int err = -EINVAL;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	cnt = sscanf(buf, "%u %d", &req.extts.index, &enable);
478c2ecf20Sopenharmony_ci	if (cnt != 2)
488c2ecf20Sopenharmony_ci		goto out;
498c2ecf20Sopenharmony_ci	if (req.extts.index >= ops->n_ext_ts)
508c2ecf20Sopenharmony_ci		goto out;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	err = ops->enable(ops, &req, enable ? 1 : 0);
538c2ecf20Sopenharmony_ci	if (err)
548c2ecf20Sopenharmony_ci		goto out;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	return count;
578c2ecf20Sopenharmony_ciout:
588c2ecf20Sopenharmony_ci	return err;
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_cistatic DEVICE_ATTR(extts_enable, 0220, NULL, extts_enable_store);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic ssize_t extts_fifo_show(struct device *dev,
638c2ecf20Sopenharmony_ci			       struct device_attribute *attr, char *page)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
668c2ecf20Sopenharmony_ci	struct timestamp_event_queue *queue = &ptp->tsevq;
678c2ecf20Sopenharmony_ci	struct ptp_extts_event event;
688c2ecf20Sopenharmony_ci	unsigned long flags;
698c2ecf20Sopenharmony_ci	size_t qcnt;
708c2ecf20Sopenharmony_ci	int cnt = 0;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	memset(&event, 0, sizeof(event));
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	if (mutex_lock_interruptible(&ptp->tsevq_mux))
758c2ecf20Sopenharmony_ci		return -ERESTARTSYS;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	spin_lock_irqsave(&queue->lock, flags);
788c2ecf20Sopenharmony_ci	qcnt = queue_cnt(queue);
798c2ecf20Sopenharmony_ci	if (qcnt) {
808c2ecf20Sopenharmony_ci		event = queue->buf[queue->head];
818c2ecf20Sopenharmony_ci		/* Paired with READ_ONCE() in queue_cnt() */
828c2ecf20Sopenharmony_ci		WRITE_ONCE(queue->head, (queue->head + 1) % PTP_MAX_TIMESTAMPS);
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&queue->lock, flags);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (!qcnt)
878c2ecf20Sopenharmony_ci		goto out;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	cnt = snprintf(page, PAGE_SIZE, "%u %lld %u\n",
908c2ecf20Sopenharmony_ci		       event.index, event.t.sec, event.t.nsec);
918c2ecf20Sopenharmony_ciout:
928c2ecf20Sopenharmony_ci	mutex_unlock(&ptp->tsevq_mux);
938c2ecf20Sopenharmony_ci	return cnt;
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_cistatic DEVICE_ATTR(fifo, 0444, extts_fifo_show, NULL);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic ssize_t period_store(struct device *dev,
988c2ecf20Sopenharmony_ci			    struct device_attribute *attr,
998c2ecf20Sopenharmony_ci			    const char *buf, size_t count)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
1028c2ecf20Sopenharmony_ci	struct ptp_clock_info *ops = ptp->info;
1038c2ecf20Sopenharmony_ci	struct ptp_clock_request req = { .type = PTP_CLK_REQ_PEROUT };
1048c2ecf20Sopenharmony_ci	int cnt, enable, err = -EINVAL;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	cnt = sscanf(buf, "%u %lld %u %lld %u", &req.perout.index,
1078c2ecf20Sopenharmony_ci		     &req.perout.start.sec, &req.perout.start.nsec,
1088c2ecf20Sopenharmony_ci		     &req.perout.period.sec, &req.perout.period.nsec);
1098c2ecf20Sopenharmony_ci	if (cnt != 5)
1108c2ecf20Sopenharmony_ci		goto out;
1118c2ecf20Sopenharmony_ci	if (req.perout.index >= ops->n_per_out)
1128c2ecf20Sopenharmony_ci		goto out;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	enable = req.perout.period.sec || req.perout.period.nsec;
1158c2ecf20Sopenharmony_ci	err = ops->enable(ops, &req, enable);
1168c2ecf20Sopenharmony_ci	if (err)
1178c2ecf20Sopenharmony_ci		goto out;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	return count;
1208c2ecf20Sopenharmony_ciout:
1218c2ecf20Sopenharmony_ci	return err;
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_cistatic DEVICE_ATTR(period, 0220, NULL, period_store);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic ssize_t pps_enable_store(struct device *dev,
1268c2ecf20Sopenharmony_ci				struct device_attribute *attr,
1278c2ecf20Sopenharmony_ci				const char *buf, size_t count)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
1308c2ecf20Sopenharmony_ci	struct ptp_clock_info *ops = ptp->info;
1318c2ecf20Sopenharmony_ci	struct ptp_clock_request req = { .type = PTP_CLK_REQ_PPS };
1328c2ecf20Sopenharmony_ci	int cnt, enable;
1338c2ecf20Sopenharmony_ci	int err = -EINVAL;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	if (!capable(CAP_SYS_TIME))
1368c2ecf20Sopenharmony_ci		return -EPERM;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	cnt = sscanf(buf, "%d", &enable);
1398c2ecf20Sopenharmony_ci	if (cnt != 1)
1408c2ecf20Sopenharmony_ci		goto out;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	err = ops->enable(ops, &req, enable ? 1 : 0);
1438c2ecf20Sopenharmony_ci	if (err)
1448c2ecf20Sopenharmony_ci		goto out;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	return count;
1478c2ecf20Sopenharmony_ciout:
1488c2ecf20Sopenharmony_ci	return err;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_cistatic DEVICE_ATTR(pps_enable, 0220, NULL, pps_enable_store);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_cistatic struct attribute *ptp_attrs[] = {
1538c2ecf20Sopenharmony_ci	&dev_attr_clock_name.attr,
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	&dev_attr_max_adjustment.attr,
1568c2ecf20Sopenharmony_ci	&dev_attr_n_alarms.attr,
1578c2ecf20Sopenharmony_ci	&dev_attr_n_external_timestamps.attr,
1588c2ecf20Sopenharmony_ci	&dev_attr_n_periodic_outputs.attr,
1598c2ecf20Sopenharmony_ci	&dev_attr_n_programmable_pins.attr,
1608c2ecf20Sopenharmony_ci	&dev_attr_pps_available.attr,
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	&dev_attr_extts_enable.attr,
1638c2ecf20Sopenharmony_ci	&dev_attr_fifo.attr,
1648c2ecf20Sopenharmony_ci	&dev_attr_period.attr,
1658c2ecf20Sopenharmony_ci	&dev_attr_pps_enable.attr,
1668c2ecf20Sopenharmony_ci	NULL
1678c2ecf20Sopenharmony_ci};
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_cistatic umode_t ptp_is_attribute_visible(struct kobject *kobj,
1708c2ecf20Sopenharmony_ci					struct attribute *attr, int n)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
1738c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
1748c2ecf20Sopenharmony_ci	struct ptp_clock_info *info = ptp->info;
1758c2ecf20Sopenharmony_ci	umode_t mode = attr->mode;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	if (attr == &dev_attr_extts_enable.attr ||
1788c2ecf20Sopenharmony_ci	    attr == &dev_attr_fifo.attr) {
1798c2ecf20Sopenharmony_ci		if (!info->n_ext_ts)
1808c2ecf20Sopenharmony_ci			mode = 0;
1818c2ecf20Sopenharmony_ci	} else if (attr == &dev_attr_period.attr) {
1828c2ecf20Sopenharmony_ci		if (!info->n_per_out)
1838c2ecf20Sopenharmony_ci			mode = 0;
1848c2ecf20Sopenharmony_ci	} else if (attr == &dev_attr_pps_enable.attr) {
1858c2ecf20Sopenharmony_ci		if (!info->pps)
1868c2ecf20Sopenharmony_ci			mode = 0;
1878c2ecf20Sopenharmony_ci	}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	return mode;
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cistatic const struct attribute_group ptp_group = {
1938c2ecf20Sopenharmony_ci	.is_visible	= ptp_is_attribute_visible,
1948c2ecf20Sopenharmony_ci	.attrs		= ptp_attrs,
1958c2ecf20Sopenharmony_ci};
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ciconst struct attribute_group *ptp_groups[] = {
1988c2ecf20Sopenharmony_ci	&ptp_group,
1998c2ecf20Sopenharmony_ci	NULL
2008c2ecf20Sopenharmony_ci};
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic int ptp_pin_name2index(struct ptp_clock *ptp, const char *name)
2038c2ecf20Sopenharmony_ci{
2048c2ecf20Sopenharmony_ci	int i;
2058c2ecf20Sopenharmony_ci	for (i = 0; i < ptp->info->n_pins; i++) {
2068c2ecf20Sopenharmony_ci		if (!strcmp(ptp->info->pin_config[i].name, name))
2078c2ecf20Sopenharmony_ci			return i;
2088c2ecf20Sopenharmony_ci	}
2098c2ecf20Sopenharmony_ci	return -1;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic ssize_t ptp_pin_show(struct device *dev, struct device_attribute *attr,
2138c2ecf20Sopenharmony_ci			    char *page)
2148c2ecf20Sopenharmony_ci{
2158c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
2168c2ecf20Sopenharmony_ci	unsigned int func, chan;
2178c2ecf20Sopenharmony_ci	int index;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	index = ptp_pin_name2index(ptp, attr->attr.name);
2208c2ecf20Sopenharmony_ci	if (index < 0)
2218c2ecf20Sopenharmony_ci		return -EINVAL;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	if (mutex_lock_interruptible(&ptp->pincfg_mux))
2248c2ecf20Sopenharmony_ci		return -ERESTARTSYS;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	func = ptp->info->pin_config[index].func;
2278c2ecf20Sopenharmony_ci	chan = ptp->info->pin_config[index].chan;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	mutex_unlock(&ptp->pincfg_mux);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	return sysfs_emit(page, "%u %u\n", func, chan);
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cistatic ssize_t ptp_pin_store(struct device *dev, struct device_attribute *attr,
2358c2ecf20Sopenharmony_ci			     const char *buf, size_t count)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	struct ptp_clock *ptp = dev_get_drvdata(dev);
2388c2ecf20Sopenharmony_ci	unsigned int func, chan;
2398c2ecf20Sopenharmony_ci	int cnt, err, index;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	cnt = sscanf(buf, "%u %u", &func, &chan);
2428c2ecf20Sopenharmony_ci	if (cnt != 2)
2438c2ecf20Sopenharmony_ci		return -EINVAL;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	index = ptp_pin_name2index(ptp, attr->attr.name);
2468c2ecf20Sopenharmony_ci	if (index < 0)
2478c2ecf20Sopenharmony_ci		return -EINVAL;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	if (mutex_lock_interruptible(&ptp->pincfg_mux))
2508c2ecf20Sopenharmony_ci		return -ERESTARTSYS;
2518c2ecf20Sopenharmony_ci	err = ptp_set_pinfunc(ptp, index, func, chan);
2528c2ecf20Sopenharmony_ci	mutex_unlock(&ptp->pincfg_mux);
2538c2ecf20Sopenharmony_ci	if (err)
2548c2ecf20Sopenharmony_ci		return err;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	return count;
2578c2ecf20Sopenharmony_ci}
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ciint ptp_populate_pin_groups(struct ptp_clock *ptp)
2608c2ecf20Sopenharmony_ci{
2618c2ecf20Sopenharmony_ci	struct ptp_clock_info *info = ptp->info;
2628c2ecf20Sopenharmony_ci	int err = -ENOMEM, i, n_pins = info->n_pins;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	if (!n_pins)
2658c2ecf20Sopenharmony_ci		return 0;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	ptp->pin_dev_attr = kcalloc(n_pins, sizeof(*ptp->pin_dev_attr),
2688c2ecf20Sopenharmony_ci				    GFP_KERNEL);
2698c2ecf20Sopenharmony_ci	if (!ptp->pin_dev_attr)
2708c2ecf20Sopenharmony_ci		goto no_dev_attr;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	ptp->pin_attr = kcalloc(1 + n_pins, sizeof(*ptp->pin_attr), GFP_KERNEL);
2738c2ecf20Sopenharmony_ci	if (!ptp->pin_attr)
2748c2ecf20Sopenharmony_ci		goto no_pin_attr;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	for (i = 0; i < n_pins; i++) {
2778c2ecf20Sopenharmony_ci		struct device_attribute *da = &ptp->pin_dev_attr[i];
2788c2ecf20Sopenharmony_ci		sysfs_attr_init(&da->attr);
2798c2ecf20Sopenharmony_ci		da->attr.name = info->pin_config[i].name;
2808c2ecf20Sopenharmony_ci		da->attr.mode = 0644;
2818c2ecf20Sopenharmony_ci		da->show = ptp_pin_show;
2828c2ecf20Sopenharmony_ci		da->store = ptp_pin_store;
2838c2ecf20Sopenharmony_ci		ptp->pin_attr[i] = &da->attr;
2848c2ecf20Sopenharmony_ci	}
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	ptp->pin_attr_group.name = "pins";
2878c2ecf20Sopenharmony_ci	ptp->pin_attr_group.attrs = ptp->pin_attr;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	ptp->pin_attr_groups[0] = &ptp->pin_attr_group;
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	return 0;
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_cino_pin_attr:
2948c2ecf20Sopenharmony_ci	kfree(ptp->pin_dev_attr);
2958c2ecf20Sopenharmony_cino_dev_attr:
2968c2ecf20Sopenharmony_ci	return err;
2978c2ecf20Sopenharmony_ci}
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_civoid ptp_cleanup_pin_groups(struct ptp_clock *ptp)
3008c2ecf20Sopenharmony_ci{
3018c2ecf20Sopenharmony_ci	kfree(ptp->pin_attr);
3028c2ecf20Sopenharmony_ci	kfree(ptp->pin_dev_attr);
3038c2ecf20Sopenharmony_ci}
304