accel/ivpu: Move recovery work to system_unbound_wq
[drm/drm-misc.git] / drivers / net / dsa / hirschmann / hellcreek_ptp.c
blobbfe21f9f7dcd36e649d17a514302d13434696217
1 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
2 /*
3 * DSA driver for:
4 * Hirschmann Hellcreek TSN switch.
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>
12 #include <linux/of.h>
13 #include <linux/ptp_clock_kernel.h>
14 #include "hellcreek.h"
15 #include "hellcreek_ptp.h"
16 #include "hellcreek_hwtstamp.h"
18 u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
20 return readw(hellcreek->ptp_base + offset);
23 void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
24 unsigned int offset)
26 writew(data, hellcreek->ptp_base + offset);
29 /* Get nanoseconds from PTP clock */
30 static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek,
31 struct ptp_system_timestamp *sts)
33 u16 nsl, nsh;
35 /* Take a snapshot */
36 hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C);
38 /* The time of the day is saved as 96 bits. However, due to hardware
39 * limitations the seconds are not or only partly kept in the PTP
40 * core. Currently only three bits for the seconds are available. That's
41 * why only the nanoseconds are used and the seconds are tracked in
42 * software. Anyway due to internal locking all five registers should be
43 * read.
45 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
46 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
47 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
48 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
49 ptp_read_system_prets(sts);
50 nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
51 ptp_read_system_postts(sts);
53 return (u64)nsl | ((u64)nsh << 16);
56 static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek,
57 struct ptp_system_timestamp *sts)
59 u64 ns;
61 ns = hellcreek_ptp_clock_read(hellcreek, sts);
62 if (ns < hellcreek->last_ts)
63 hellcreek->seconds++;
64 hellcreek->last_ts = ns;
65 ns += hellcreek->seconds * NSEC_PER_SEC;
67 return ns;
70 /* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns.
71 * There has to be a check whether an overflow occurred between the packet
72 * arrival and now. If so use the correct seconds (-1) for calculating the
73 * packet arrival time.
75 u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns)
77 u64 s;
79 __hellcreek_ptp_gettime(hellcreek, NULL);
80 if (hellcreek->last_ts > ns)
81 s = hellcreek->seconds * NSEC_PER_SEC;
82 else
83 s = (hellcreek->seconds - 1) * NSEC_PER_SEC;
85 return s;
88 static int hellcreek_ptp_gettimex(struct ptp_clock_info *ptp,
89 struct timespec64 *ts,
90 struct ptp_system_timestamp *sts)
92 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
93 u64 ns;
95 mutex_lock(&hellcreek->ptp_lock);
96 ns = __hellcreek_ptp_gettime(hellcreek, sts);
97 mutex_unlock(&hellcreek->ptp_lock);
99 *ts = ns_to_timespec64(ns);
101 return 0;
104 static int hellcreek_ptp_settime(struct ptp_clock_info *ptp,
105 const struct timespec64 *ts)
107 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
108 u16 secl, nsh, nsl;
110 secl = ts->tv_sec & 0xffff;
111 nsh = ((u32)ts->tv_nsec & 0xffff0000) >> 16;
112 nsl = ts->tv_nsec & 0xffff;
114 mutex_lock(&hellcreek->ptp_lock);
116 /* Update overflow data structure */
117 hellcreek->seconds = ts->tv_sec;
118 hellcreek->last_ts = ts->tv_nsec;
120 /* Set time in clock */
121 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
122 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
123 hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C);
124 hellcreek_ptp_write(hellcreek, nsh, PR_CLOCK_WRITE_C);
125 hellcreek_ptp_write(hellcreek, nsl, PR_CLOCK_WRITE_C);
127 mutex_unlock(&hellcreek->ptp_lock);
129 return 0;
132 static int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
134 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
135 u16 negative = 0, addendh, addendl;
136 u32 addend;
137 u64 adj;
139 if (scaled_ppm < 0) {
140 negative = 1;
141 scaled_ppm = -scaled_ppm;
144 /* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns
145 * from the 8 ns (period of the oscillator) every time the accumulator
146 * register overflows. The value stored in the addend register is added
147 * to the accumulator register every 8 ns.
149 * addend value = (2^30 * accumulator_overflow_rate) /
150 * oscillator_frequency
151 * where:
153 * oscillator_frequency = 125 MHz
154 * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8
156 adj = scaled_ppm;
157 adj <<= 11;
158 addend = (u32)div_u64(adj, 15625);
160 addendh = (addend & 0xffff0000) >> 16;
161 addendl = addend & 0xffff;
163 negative = (negative << 15) & 0x8000;
165 mutex_lock(&hellcreek->ptp_lock);
167 /* Set drift register */
168 hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C);
169 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
170 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
171 hellcreek_ptp_write(hellcreek, addendh, PR_CLOCK_DRIFT_C);
172 hellcreek_ptp_write(hellcreek, addendl, PR_CLOCK_DRIFT_C);
174 mutex_unlock(&hellcreek->ptp_lock);
176 return 0;
179 static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
181 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
182 u16 negative = 0, counth, countl;
183 u32 count_val;
185 /* If the offset is larger than IP-Core slow offset resources. Don't
186 * consider slow adjustment. Rather, add the offset directly to the
187 * current time
189 if (abs(delta) > MAX_SLOW_OFFSET_ADJ) {
190 struct timespec64 now, then = ns_to_timespec64(delta);
192 hellcreek_ptp_gettimex(ptp, &now, NULL);
193 now = timespec64_add(now, then);
194 hellcreek_ptp_settime(ptp, &now);
196 return 0;
199 if (delta < 0) {
200 negative = 1;
201 delta = -delta;
204 /* 'count_val' does not exceed the maximum register size (2^30) */
205 count_val = div_s64(delta, MAX_NS_PER_STEP);
207 counth = (count_val & 0xffff0000) >> 16;
208 countl = count_val & 0xffff;
210 negative = (negative << 15) & 0x8000;
212 mutex_lock(&hellcreek->ptp_lock);
214 /* Set offset write register */
215 hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C);
216 hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C);
217 hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS,
218 PR_CLOCK_OFFSET_C);
219 hellcreek_ptp_write(hellcreek, countl, PR_CLOCK_OFFSET_C);
220 hellcreek_ptp_write(hellcreek, counth, PR_CLOCK_OFFSET_C);
222 mutex_unlock(&hellcreek->ptp_lock);
224 return 0;
227 static int hellcreek_ptp_enable(struct ptp_clock_info *ptp,
228 struct ptp_clock_request *rq, int on)
230 return -EOPNOTSUPP;
233 static void hellcreek_ptp_overflow_check(struct work_struct *work)
235 struct delayed_work *dw = to_delayed_work(work);
236 struct hellcreek *hellcreek;
238 hellcreek = dw_overflow_to_hellcreek(dw);
240 mutex_lock(&hellcreek->ptp_lock);
241 __hellcreek_ptp_gettime(hellcreek, NULL);
242 mutex_unlock(&hellcreek->ptp_lock);
244 schedule_delayed_work(&hellcreek->overflow_work,
245 HELLCREEK_OVERFLOW_PERIOD);
248 static enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek,
249 int led)
251 return (hellcreek->status_out & led) ? 1 : 0;
254 static void hellcreek_set_brightness(struct hellcreek *hellcreek, int led,
255 enum led_brightness b)
257 mutex_lock(&hellcreek->ptp_lock);
259 if (b)
260 hellcreek->status_out |= led;
261 else
262 hellcreek->status_out &= ~led;
264 hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT);
266 mutex_unlock(&hellcreek->ptp_lock);
269 static void hellcreek_led_sync_good_set(struct led_classdev *ldev,
270 enum led_brightness b)
272 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
274 hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b);
277 static enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev)
279 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
281 return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
284 static void hellcreek_led_is_gm_set(struct led_classdev *ldev,
285 enum led_brightness b)
287 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
289 hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b);
292 static enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev)
294 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
296 return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
299 /* There two available LEDs internally called sync_good and is_gm. However, the
300 * user might want to use a different label and specify the default state. Take
301 * those properties from device tree.
303 static int hellcreek_led_setup(struct hellcreek *hellcreek)
305 struct device_node *leds, *led = NULL;
306 enum led_default_state state;
307 const char *label;
308 int ret = -EINVAL;
310 of_node_get(hellcreek->dev->of_node);
311 leds = of_find_node_by_name(hellcreek->dev->of_node, "leds");
312 if (!leds) {
313 dev_err(hellcreek->dev, "No LEDs specified in device tree!\n");
314 return ret;
317 hellcreek->status_out = 0;
319 led = of_get_next_available_child(leds, led);
320 if (!led) {
321 dev_err(hellcreek->dev, "First LED not specified!\n");
322 goto out;
325 ret = of_property_read_string(led, "label", &label);
326 hellcreek->led_sync_good.name = ret ? "sync_good" : label;
328 state = led_init_default_state_get(of_fwnode_handle(led));
329 switch (state) {
330 case LEDS_DEFSTATE_ON:
331 hellcreek->led_sync_good.brightness = 1;
332 break;
333 case LEDS_DEFSTATE_KEEP:
334 hellcreek->led_sync_good.brightness =
335 hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
336 break;
337 default:
338 hellcreek->led_sync_good.brightness = 0;
341 hellcreek->led_sync_good.max_brightness = 1;
342 hellcreek->led_sync_good.brightness_set = hellcreek_led_sync_good_set;
343 hellcreek->led_sync_good.brightness_get = hellcreek_led_sync_good_get;
345 led = of_get_next_available_child(leds, led);
346 if (!led) {
347 dev_err(hellcreek->dev, "Second LED not specified!\n");
348 ret = -EINVAL;
349 goto out;
352 ret = of_property_read_string(led, "label", &label);
353 hellcreek->led_is_gm.name = ret ? "is_gm" : label;
355 state = led_init_default_state_get(of_fwnode_handle(led));
356 switch (state) {
357 case LEDS_DEFSTATE_ON:
358 hellcreek->led_is_gm.brightness = 1;
359 break;
360 case LEDS_DEFSTATE_KEEP:
361 hellcreek->led_is_gm.brightness =
362 hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
363 break;
364 default:
365 hellcreek->led_is_gm.brightness = 0;
368 hellcreek->led_is_gm.max_brightness = 1;
369 hellcreek->led_is_gm.brightness_set = hellcreek_led_is_gm_set;
370 hellcreek->led_is_gm.brightness_get = hellcreek_led_is_gm_get;
372 /* Set initial state */
373 if (hellcreek->led_sync_good.brightness == 1)
374 hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, 1);
375 if (hellcreek->led_is_gm.brightness == 1)
376 hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, 1);
378 /* Register both leds */
379 led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good);
380 led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm);
382 ret = 0;
384 out:
385 of_node_put(leds);
387 return ret;
390 int hellcreek_ptp_setup(struct hellcreek *hellcreek)
392 u16 status;
393 int ret;
395 /* Set up the overflow work */
396 INIT_DELAYED_WORK(&hellcreek->overflow_work,
397 hellcreek_ptp_overflow_check);
399 /* Setup PTP clock */
400 hellcreek->ptp_clock_info.owner = THIS_MODULE;
401 snprintf(hellcreek->ptp_clock_info.name,
402 sizeof(hellcreek->ptp_clock_info.name),
403 dev_name(hellcreek->dev));
405 /* IP-Core can add up to 0.5 ns per 8 ns cycle, which means
406 * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts
407 * the nominal frequency by 6.25%)
409 hellcreek->ptp_clock_info.max_adj = 62500000;
410 hellcreek->ptp_clock_info.n_alarm = 0;
411 hellcreek->ptp_clock_info.n_pins = 0;
412 hellcreek->ptp_clock_info.n_ext_ts = 0;
413 hellcreek->ptp_clock_info.n_per_out = 0;
414 hellcreek->ptp_clock_info.pps = 0;
415 hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine;
416 hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime;
417 hellcreek->ptp_clock_info.gettimex64 = hellcreek_ptp_gettimex;
418 hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime;
419 hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable;
420 hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work;
422 hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
423 hellcreek->dev);
424 if (IS_ERR(hellcreek->ptp_clock))
425 return PTR_ERR(hellcreek->ptp_clock);
427 /* Enable the offset correction process, if no offset correction is
428 * already taking place
430 status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C);
431 if (!(status & PR_CLOCK_STATUS_C_OFS_ACT))
432 hellcreek_ptp_write(hellcreek,
433 status | PR_CLOCK_STATUS_C_ENA_OFS,
434 PR_CLOCK_STATUS_C);
436 /* Enable the drift correction process */
437 hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT,
438 PR_CLOCK_STATUS_C);
440 /* LED setup */
441 ret = hellcreek_led_setup(hellcreek);
442 if (ret) {
443 if (hellcreek->ptp_clock)
444 ptp_clock_unregister(hellcreek->ptp_clock);
445 return ret;
448 schedule_delayed_work(&hellcreek->overflow_work,
449 HELLCREEK_OVERFLOW_PERIOD);
451 return 0;
454 void hellcreek_ptp_free(struct hellcreek *hellcreek)
456 led_classdev_unregister(&hellcreek->led_is_gm);
457 led_classdev_unregister(&hellcreek->led_sync_good);
458 cancel_delayed_work_sync(&hellcreek->overflow_work);
459 if (hellcreek->ptp_clock)
460 ptp_clock_unregister(hellcreek->ptp_clock);
461 hellcreek->ptp_clock = NULL;