diff options
author | Richard Cochran <richardcochran@gmail.com> | 2011-04-22 06:03:08 -0400 |
---|---|---|
committer | John Stultz <john.stultz@linaro.org> | 2011-05-23 16:01:00 -0400 |
commit | d94ba80ebbea17f036cecb104398fbcd788aa742 (patch) | |
tree | 7fe40228c5ea2bb77f2892b722d27155df8c1157 /drivers/ptp/ptp_sysfs.c | |
parent | caebc160ce3f76761cc62ad96ef6d6f30f54e3dd (diff) |
ptp: Added a brand new class driver for ptp clocks.
This patch adds an infrastructure for hardware clocks that implement
IEEE 1588, the Precision Time Protocol (PTP). A class driver offers a
registration method to particular hardware clock drivers. Each clock is
presented as a standard POSIX clock.
The ancillary clock features are exposed in two different ways, via
the sysfs and by a character device.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: David S. Miller <davem@davemloft.net>
Signed-off-by: John Stultz <john.stultz@linaro.org>
Diffstat (limited to 'drivers/ptp/ptp_sysfs.c')
-rw-r--r-- | drivers/ptp/ptp_sysfs.c | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/drivers/ptp/ptp_sysfs.c b/drivers/ptp/ptp_sysfs.c new file mode 100644 index 000000000000..2f93926ac976 --- /dev/null +++ b/drivers/ptp/ptp_sysfs.c | |||
@@ -0,0 +1,230 @@ | |||
1 | /* | ||
2 | * PTP 1588 clock support - sysfs interface. | ||
3 | * | ||
4 | * Copyright (C) 2010 OMICRON electronics GmbH | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | */ | ||
20 | #include <linux/capability.h> | ||
21 | |||
22 | #include "ptp_private.h" | ||
23 | |||
24 | static ssize_t clock_name_show(struct device *dev, | ||
25 | struct device_attribute *attr, char *page) | ||
26 | { | ||
27 | struct ptp_clock *ptp = dev_get_drvdata(dev); | ||
28 | return snprintf(page, PAGE_SIZE-1, "%s\n", ptp->info->name); | ||
29 | } | ||
30 | |||
31 | #define PTP_SHOW_INT(name) \ | ||
32 | static ssize_t name##_show(struct device *dev, \ | ||
33 | struct device_attribute *attr, char *page) \ | ||
34 | { \ | ||
35 | struct ptp_clock *ptp = dev_get_drvdata(dev); \ | ||
36 | return snprintf(page, PAGE_SIZE-1, "%d\n", ptp->info->name); \ | ||
37 | } | ||
38 | |||
39 | PTP_SHOW_INT(max_adj); | ||
40 | PTP_SHOW_INT(n_alarm); | ||
41 | PTP_SHOW_INT(n_ext_ts); | ||
42 | PTP_SHOW_INT(n_per_out); | ||
43 | PTP_SHOW_INT(pps); | ||
44 | |||
45 | #define PTP_RO_ATTR(_var, _name) { \ | ||
46 | .attr = { .name = __stringify(_name), .mode = 0444 }, \ | ||
47 | .show = _var##_show, \ | ||
48 | } | ||
49 | |||
50 | struct device_attribute ptp_dev_attrs[] = { | ||
51 | PTP_RO_ATTR(clock_name, clock_name), | ||
52 | PTP_RO_ATTR(max_adj, max_adjustment), | ||
53 | PTP_RO_ATTR(n_alarm, n_alarms), | ||
54 | PTP_RO_ATTR(n_ext_ts, n_external_timestamps), | ||
55 | PTP_RO_ATTR(n_per_out, n_periodic_outputs), | ||
56 | PTP_RO_ATTR(pps, pps_available), | ||
57 | __ATTR_NULL, | ||
58 | }; | ||
59 | |||
60 | static ssize_t extts_enable_store(struct device *dev, | ||
61 | struct device_attribute *attr, | ||
62 | const char *buf, size_t count) | ||
63 | { | ||
64 | struct ptp_clock *ptp = dev_get_drvdata(dev); | ||
65 | struct ptp_clock_info *ops = ptp->info; | ||
66 | struct ptp_clock_request req = { .type = PTP_CLK_REQ_EXTTS }; | ||
67 | int cnt, enable; | ||
68 | int err = -EINVAL; | ||
69 | |||
70 | cnt = sscanf(buf, "%u %d", &req.extts.index, &enable); | ||
71 | if (cnt != 2) | ||
72 | goto out; | ||
73 | if (req.extts.index >= ops->n_ext_ts) | ||
74 | goto out; | ||
75 | |||
76 | err = ops->enable(ops, &req, enable ? 1 : 0); | ||
77 | if (err) | ||
78 | goto out; | ||
79 | |||
80 | return count; | ||
81 | out: | ||
82 | return err; | ||
83 | } | ||
84 | |||
85 | static ssize_t extts_fifo_show(struct device *dev, | ||
86 | struct device_attribute *attr, char *page) | ||
87 | { | ||
88 | struct ptp_clock *ptp = dev_get_drvdata(dev); | ||
89 | struct timestamp_event_queue *queue = &ptp->tsevq; | ||
90 | struct ptp_extts_event event; | ||
91 | unsigned long flags; | ||
92 | size_t qcnt; | ||
93 | int cnt = 0; | ||
94 | |||
95 | memset(&event, 0, sizeof(event)); | ||
96 | |||
97 | if (mutex_lock_interruptible(&ptp->tsevq_mux)) | ||
98 | return -ERESTARTSYS; | ||
99 | |||
100 | spin_lock_irqsave(&queue->lock, flags); | ||
101 | qcnt = queue_cnt(queue); | ||
102 | if (qcnt) { | ||
103 | event = queue->buf[queue->head]; | ||
104 | queue->head = (queue->head + 1) % PTP_MAX_TIMESTAMPS; | ||
105 | } | ||
106 | spin_unlock_irqrestore(&queue->lock, flags); | ||
107 | |||
108 | if (!qcnt) | ||
109 | goto out; | ||
110 | |||
111 | cnt = snprintf(page, PAGE_SIZE, "%u %lld %u\n", | ||
112 | event.index, event.t.sec, event.t.nsec); | ||
113 | out: | ||
114 | mutex_unlock(&ptp->tsevq_mux); | ||
115 | return cnt; | ||
116 | } | ||
117 | |||
118 | static ssize_t period_store(struct device *dev, | ||
119 | struct device_attribute *attr, | ||
120 | const char *buf, size_t count) | ||
121 | { | ||
122 | struct ptp_clock *ptp = dev_get_drvdata(dev); | ||
123 | struct ptp_clock_info *ops = ptp->info; | ||
124 | struct ptp_clock_request req = { .type = PTP_CLK_REQ_PEROUT }; | ||
125 | int cnt, enable, err = -EINVAL; | ||
126 | |||
127 | cnt = sscanf(buf, "%u %lld %u %lld %u", &req.perout.index, | ||
128 | &req.perout.start.sec, &req.perout.start.nsec, | ||
129 | &req.perout.period.sec, &req.perout.period.nsec); | ||
130 | if (cnt != 5) | ||
131 | goto out; | ||
132 | if (req.perout.index >= ops->n_per_out) | ||
133 | goto out; | ||
134 | |||
135 | enable = req.perout.period.sec || req.perout.period.nsec; | ||
136 | err = ops->enable(ops, &req, enable); | ||
137 | if (err) | ||
138 | goto out; | ||
139 | |||
140 | return count; | ||
141 | out: | ||
142 | return err; | ||
143 | } | ||
144 | |||
145 | static ssize_t pps_enable_store(struct device *dev, | ||
146 | struct device_attribute *attr, | ||
147 | const char *buf, size_t count) | ||
148 | { | ||
149 | struct ptp_clock *ptp = dev_get_drvdata(dev); | ||
150 | struct ptp_clock_info *ops = ptp->info; | ||
151 | struct ptp_clock_request req = { .type = PTP_CLK_REQ_PPS }; | ||
152 | int cnt, enable; | ||
153 | int err = -EINVAL; | ||
154 | |||
155 | if (!capable(CAP_SYS_TIME)) | ||
156 | return -EPERM; | ||
157 | |||
158 | cnt = sscanf(buf, "%d", &enable); | ||
159 | if (cnt != 1) | ||
160 | goto out; | ||
161 | |||
162 | err = ops->enable(ops, &req, enable ? 1 : 0); | ||
163 | if (err) | ||
164 | goto out; | ||
165 | |||
166 | return count; | ||
167 | out: | ||
168 | return err; | ||
169 | } | ||
170 | |||
171 | static DEVICE_ATTR(extts_enable, 0220, NULL, extts_enable_store); | ||
172 | static DEVICE_ATTR(fifo, 0444, extts_fifo_show, NULL); | ||
173 | static DEVICE_ATTR(period, 0220, NULL, period_store); | ||
174 | static DEVICE_ATTR(pps_enable, 0220, NULL, pps_enable_store); | ||
175 | |||
176 | int ptp_cleanup_sysfs(struct ptp_clock *ptp) | ||
177 | { | ||
178 | struct device *dev = ptp->dev; | ||
179 | struct ptp_clock_info *info = ptp->info; | ||
180 | |||
181 | if (info->n_ext_ts) { | ||
182 | device_remove_file(dev, &dev_attr_extts_enable); | ||
183 | device_remove_file(dev, &dev_attr_fifo); | ||
184 | } | ||
185 | if (info->n_per_out) | ||
186 | device_remove_file(dev, &dev_attr_period); | ||
187 | |||
188 | if (info->pps) | ||
189 | device_remove_file(dev, &dev_attr_pps_enable); | ||
190 | |||
191 | return 0; | ||
192 | } | ||
193 | |||
194 | int ptp_populate_sysfs(struct ptp_clock *ptp) | ||
195 | { | ||
196 | struct device *dev = ptp->dev; | ||
197 | struct ptp_clock_info *info = ptp->info; | ||
198 | int err; | ||
199 | |||
200 | if (info->n_ext_ts) { | ||
201 | err = device_create_file(dev, &dev_attr_extts_enable); | ||
202 | if (err) | ||
203 | goto out1; | ||
204 | err = device_create_file(dev, &dev_attr_fifo); | ||
205 | if (err) | ||
206 | goto out2; | ||
207 | } | ||
208 | if (info->n_per_out) { | ||
209 | err = device_create_file(dev, &dev_attr_period); | ||
210 | if (err) | ||
211 | goto out3; | ||
212 | } | ||
213 | if (info->pps) { | ||
214 | err = device_create_file(dev, &dev_attr_pps_enable); | ||
215 | if (err) | ||
216 | goto out4; | ||
217 | } | ||
218 | return 0; | ||
219 | out4: | ||
220 | if (info->n_per_out) | ||
221 | device_remove_file(dev, &dev_attr_period); | ||
222 | out3: | ||
223 | if (info->n_ext_ts) | ||
224 | device_remove_file(dev, &dev_attr_fifo); | ||
225 | out2: | ||
226 | if (info->n_ext_ts) | ||
227 | device_remove_file(dev, &dev_attr_extts_enable); | ||
228 | out1: | ||
229 | return err; | ||
230 | } | ||