blob: 12ad956abd5cb6e4d2e3a905729cc0bd12d59076 [file] [log] [blame]
Kamil Alkhouriddd56df2020-11-03 08:10:57 +01001// SPDX-License-Identifier: (GPL-2.0 OR MIT)
2/*
3 * DSA driver for:
4 * Hirschmann Hellcreek TSN switch.
5 *
6 * Copyright (C) 2019,2020 Hochschule Offenburg
7 * Copyright (C) 2019,2020 Linutronix GmbH
8 * Authors: Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de>
9 * Kurt Kanzenbach <kurt@linutronix.de>
10 */
11
12#include <linux/ptp_clock_kernel.h>
13#include "hellcreek.h"
14#include "hellcreek_ptp.h"
Kamil Alkhourif0d4ba92020-11-03 08:10:58 +010015#include "hellcreek_hwtstamp.h"
Kamil Alkhouriddd56df2020-11-03 08:10:57 +010016
Kamil Alkhourif0d4ba92020-11-03 08:10:58 +010017u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
Kamil Alkhouriddd56df2020-11-03 08:10:57 +010018{
19 return readw(hellcreek->ptp_base + offset);
20}
21
Kamil Alkhourif0d4ba92020-11-03 08:10:58 +010022void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
23 unsigned int offset)
Kamil Alkhouriddd56df2020-11-03 08:10:57 +010024{
25 writew(data, hellcreek->ptp_base + offset);
26}
27
28/* Get nanoseconds from PTP clock */
29static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek)
30{
31 u16 nsl, nsh;
32
33 /* Take a snapshot */
34 hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C);
35
36 /* The time of the day is saved as 96 bits. However, due to hardware
37 * limitations the seconds are not or only partly kept in the PTP
38 * core. Currently only three bits for the seconds are available. That's
39 * why only the nanoseconds are used and the seconds are tracked in
40 * software. Anyway due to internal locking all five registers should be
41 * read.
42 */
43 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
44 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
45 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
46 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
47 nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
48
49 return (u64)nsl | ((u64)nsh << 16);
50}
51
52static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek)
53{
54 u64 ns;
55
56 ns = hellcreek_ptp_clock_read(hellcreek);
57 if (ns < hellcreek->last_ts)
58 hellcreek->seconds++;
59 hellcreek->last_ts = ns;
60 ns += hellcreek->seconds * NSEC_PER_SEC;
61
62 return ns;
63}
64
Kamil Alkhourif0d4ba92020-11-03 08:10:58 +010065/* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns.
66 * There has to be a check whether an overflow occurred between the packet
67 * arrival and now. If so use the correct seconds (-1) for calculating the
68 * packet arrival time.
69 */
70u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns)
71{
72 u64 s;
73
74 __hellcreek_ptp_gettime(hellcreek);
75 if (hellcreek->last_ts > ns)
76 s = hellcreek->seconds * NSEC_PER_SEC;
77 else
78 s = (hellcreek->seconds - 1) * NSEC_PER_SEC;
79
80 return s;
81}
82
Kamil Alkhouriddd56df2020-11-03 08:10:57 +010083static int hellcreek_ptp_gettime(struct ptp_clock_info *ptp,
84 struct timespec64 *ts)
85{
86 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
87 u64 ns;
88
89 mutex_lock(&hellcreek->ptp_lock);
90 ns = __hellcreek_ptp_gettime(hellcreek);
91 mutex_unlock(&hellcreek->ptp_lock);
92
93 *ts = ns_to_timespec64(ns);
94
95 return 0;
96}
97
98static int hellcreek_ptp_settime(struct ptp_clock_info *ptp,
99 const struct timespec64 *ts)
100{
101 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
102 u16 secl, nsh, nsl;
103
104 secl = ts->tv_sec & 0xffff;
105 nsh = ((u32)ts->tv_nsec & 0xffff0000) >> 16;
106 nsl = ts->tv_nsec & 0xffff;
107
108 mutex_lock(&hellcreek->ptp_lock);
109
110 /* Update overflow data structure */
111 hellcreek->seconds = ts->tv_sec;
112 hellcreek->last_ts = ts->tv_nsec;
113
114 /* Set time in clock */
115 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
116 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
117 hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C);
118 hellcreek_ptp_write(hellcreek, nsh, PR_CLOCK_WRITE_C);
119 hellcreek_ptp_write(hellcreek, nsl, PR_CLOCK_WRITE_C);
120
121 mutex_unlock(&hellcreek->ptp_lock);
122
123 return 0;
124}
125
126static int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
127{
128 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
129 u16 negative = 0, addendh, addendl;
130 u32 addend;
131 u64 adj;
132
133 if (scaled_ppm < 0) {
134 negative = 1;
135 scaled_ppm = -scaled_ppm;
136 }
137
138 /* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns
139 * from the 8 ns (period of the oscillator) every time the accumulator
140 * register overflows. The value stored in the addend register is added
141 * to the accumulator register every 8 ns.
142 *
143 * addend value = (2^30 * accumulator_overflow_rate) /
144 * oscillator_frequency
145 * where:
146 *
147 * oscillator_frequency = 125 MHz
148 * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8
149 */
150 adj = scaled_ppm;
151 adj <<= 11;
152 addend = (u32)div_u64(adj, 15625);
153
154 addendh = (addend & 0xffff0000) >> 16;
155 addendl = addend & 0xffff;
156
157 negative = (negative << 15) & 0x8000;
158
159 mutex_lock(&hellcreek->ptp_lock);
160
161 /* Set drift register */
162 hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C);
163 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
164 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
165 hellcreek_ptp_write(hellcreek, addendh, PR_CLOCK_DRIFT_C);
166 hellcreek_ptp_write(hellcreek, addendl, PR_CLOCK_DRIFT_C);
167
168 mutex_unlock(&hellcreek->ptp_lock);
169
170 return 0;
171}
172
173static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
174{
175 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
176 u16 negative = 0, counth, countl;
177 u32 count_val;
178
179 /* If the offset is larger than IP-Core slow offset resources. Don't
180 * consider slow adjustment. Rather, add the offset directly to the
181 * current time
182 */
183 if (abs(delta) > MAX_SLOW_OFFSET_ADJ) {
184 struct timespec64 now, then = ns_to_timespec64(delta);
185
186 hellcreek_ptp_gettime(ptp, &now);
187 now = timespec64_add(now, then);
188 hellcreek_ptp_settime(ptp, &now);
189
190 return 0;
191 }
192
193 if (delta < 0) {
194 negative = 1;
195 delta = -delta;
196 }
197
198 /* 'count_val' does not exceed the maximum register size (2^30) */
199 count_val = div_s64(delta, MAX_NS_PER_STEP);
200
201 counth = (count_val & 0xffff0000) >> 16;
202 countl = count_val & 0xffff;
203
204 negative = (negative << 15) & 0x8000;
205
206 mutex_lock(&hellcreek->ptp_lock);
207
208 /* Set offset write register */
209 hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C);
210 hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C);
211 hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS,
212 PR_CLOCK_OFFSET_C);
213 hellcreek_ptp_write(hellcreek, countl, PR_CLOCK_OFFSET_C);
214 hellcreek_ptp_write(hellcreek, counth, PR_CLOCK_OFFSET_C);
215
216 mutex_unlock(&hellcreek->ptp_lock);
217
218 return 0;
219}
220
221static int hellcreek_ptp_enable(struct ptp_clock_info *ptp,
222 struct ptp_clock_request *rq, int on)
223{
224 return -EOPNOTSUPP;
225}
226
227static void hellcreek_ptp_overflow_check(struct work_struct *work)
228{
229 struct delayed_work *dw = to_delayed_work(work);
230 struct hellcreek *hellcreek;
231
232 hellcreek = dw_overflow_to_hellcreek(dw);
233
234 mutex_lock(&hellcreek->ptp_lock);
235 __hellcreek_ptp_gettime(hellcreek);
236 mutex_unlock(&hellcreek->ptp_lock);
237
238 schedule_delayed_work(&hellcreek->overflow_work,
239 HELLCREEK_OVERFLOW_PERIOD);
240}
241
242int hellcreek_ptp_setup(struct hellcreek *hellcreek)
243{
244 u16 status;
245
246 /* Set up the overflow work */
247 INIT_DELAYED_WORK(&hellcreek->overflow_work,
248 hellcreek_ptp_overflow_check);
249
250 /* Setup PTP clock */
251 hellcreek->ptp_clock_info.owner = THIS_MODULE;
252 snprintf(hellcreek->ptp_clock_info.name,
253 sizeof(hellcreek->ptp_clock_info.name),
254 dev_name(hellcreek->dev));
255
256 /* IP-Core can add up to 0.5 ns per 8 ns cycle, which means
257 * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts
258 * the nominal frequency by 6.25%)
259 */
Kamil Alkhourif0d4ba92020-11-03 08:10:58 +0100260 hellcreek->ptp_clock_info.max_adj = 62500000;
261 hellcreek->ptp_clock_info.n_alarm = 0;
262 hellcreek->ptp_clock_info.n_pins = 0;
263 hellcreek->ptp_clock_info.n_ext_ts = 0;
264 hellcreek->ptp_clock_info.n_per_out = 0;
265 hellcreek->ptp_clock_info.pps = 0;
266 hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine;
267 hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime;
268 hellcreek->ptp_clock_info.gettime64 = hellcreek_ptp_gettime;
269 hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime;
270 hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable;
271 hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work;
Kamil Alkhouriddd56df2020-11-03 08:10:57 +0100272
273 hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
274 hellcreek->dev);
275 if (IS_ERR(hellcreek->ptp_clock))
276 return PTR_ERR(hellcreek->ptp_clock);
277
278 /* Enable the offset correction process, if no offset correction is
279 * already taking place
280 */
281 status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C);
282 if (!(status & PR_CLOCK_STATUS_C_OFS_ACT))
283 hellcreek_ptp_write(hellcreek,
284 status | PR_CLOCK_STATUS_C_ENA_OFS,
285 PR_CLOCK_STATUS_C);
286
287 /* Enable the drift correction process */
288 hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT,
289 PR_CLOCK_STATUS_C);
290
291 schedule_delayed_work(&hellcreek->overflow_work,
292 HELLCREEK_OVERFLOW_PERIOD);
293
294 return 0;
295}
296
297void hellcreek_ptp_free(struct hellcreek *hellcreek)
298{
299 cancel_delayed_work_sync(&hellcreek->overflow_work);
300 if (hellcreek->ptp_clock)
301 ptp_clock_unregister(hellcreek->ptp_clock);
302 hellcreek->ptp_clock = NULL;
303}