linux/drivers/net/dsa/hirschmann/hellcreek_ptp.c
<<
>>
Prefs
   1// 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"
  15#include "hellcreek_hwtstamp.h"
  16
  17u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
  18{
  19        return readw(hellcreek->ptp_base + offset);
  20}
  21
  22void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
  23                         unsigned int offset)
  24{
  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
  65/* 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
  83static 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
 242static enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek,
 243                                                    int led)
 244{
 245        return (hellcreek->status_out & led) ? 1 : 0;
 246}
 247
 248static void hellcreek_set_brightness(struct hellcreek *hellcreek, int led,
 249                                     enum led_brightness b)
 250{
 251        mutex_lock(&hellcreek->ptp_lock);
 252
 253        if (b)
 254                hellcreek->status_out |= led;
 255        else
 256                hellcreek->status_out &= ~led;
 257
 258        hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT);
 259
 260        mutex_unlock(&hellcreek->ptp_lock);
 261}
 262
 263static void hellcreek_led_sync_good_set(struct led_classdev *ldev,
 264                                        enum led_brightness b)
 265{
 266        struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
 267
 268        hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b);
 269}
 270
 271static enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev)
 272{
 273        struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
 274
 275        return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
 276}
 277
 278static void hellcreek_led_is_gm_set(struct led_classdev *ldev,
 279                                    enum led_brightness b)
 280{
 281        struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
 282
 283        hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b);
 284}
 285
 286static enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev)
 287{
 288        struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
 289
 290        return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
 291}
 292
 293/* There two available LEDs internally called sync_good and is_gm. However, the
 294 * user might want to use a different label and specify the default state. Take
 295 * those properties from device tree.
 296 */
 297static int hellcreek_led_setup(struct hellcreek *hellcreek)
 298{
 299        struct device_node *leds, *led = NULL;
 300        const char *label, *state;
 301        int ret = -EINVAL;
 302
 303        leds = of_find_node_by_name(hellcreek->dev->of_node, "leds");
 304        if (!leds) {
 305                dev_err(hellcreek->dev, "No LEDs specified in device tree!\n");
 306                return ret;
 307        }
 308
 309        hellcreek->status_out = 0;
 310
 311        led = of_get_next_available_child(leds, led);
 312        if (!led) {
 313                dev_err(hellcreek->dev, "First LED not specified!\n");
 314                goto out;
 315        }
 316
 317        ret = of_property_read_string(led, "label", &label);
 318        hellcreek->led_sync_good.name = ret ? "sync_good" : label;
 319
 320        ret = of_property_read_string(led, "default-state", &state);
 321        if (!ret) {
 322                if (!strcmp(state, "on"))
 323                        hellcreek->led_sync_good.brightness = 1;
 324                else if (!strcmp(state, "off"))
 325                        hellcreek->led_sync_good.brightness = 0;
 326                else if (!strcmp(state, "keep"))
 327                        hellcreek->led_sync_good.brightness =
 328                                hellcreek_get_brightness(hellcreek,
 329                                                         STATUS_OUT_SYNC_GOOD);
 330        }
 331
 332        hellcreek->led_sync_good.max_brightness = 1;
 333        hellcreek->led_sync_good.brightness_set = hellcreek_led_sync_good_set;
 334        hellcreek->led_sync_good.brightness_get = hellcreek_led_sync_good_get;
 335
 336        led = of_get_next_available_child(leds, led);
 337        if (!led) {
 338                dev_err(hellcreek->dev, "Second LED not specified!\n");
 339                ret = -EINVAL;
 340                goto out;
 341        }
 342
 343        ret = of_property_read_string(led, "label", &label);
 344        hellcreek->led_is_gm.name = ret ? "is_gm" : label;
 345
 346        ret = of_property_read_string(led, "default-state", &state);
 347        if (!ret) {
 348                if (!strcmp(state, "on"))
 349                        hellcreek->led_is_gm.brightness = 1;
 350                else if (!strcmp(state, "off"))
 351                        hellcreek->led_is_gm.brightness = 0;
 352                else if (!strcmp(state, "keep"))
 353                        hellcreek->led_is_gm.brightness =
 354                                hellcreek_get_brightness(hellcreek,
 355                                                         STATUS_OUT_IS_GM);
 356        }
 357
 358        hellcreek->led_is_gm.max_brightness = 1;
 359        hellcreek->led_is_gm.brightness_set = hellcreek_led_is_gm_set;
 360        hellcreek->led_is_gm.brightness_get = hellcreek_led_is_gm_get;
 361
 362        /* Set initial state */
 363        if (hellcreek->led_sync_good.brightness == 1)
 364                hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, 1);
 365        if (hellcreek->led_is_gm.brightness == 1)
 366                hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, 1);
 367
 368        /* Register both leds */
 369        led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good);
 370        led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm);
 371
 372        ret = 0;
 373
 374out:
 375        of_node_put(leds);
 376
 377        return ret;
 378}
 379
 380int hellcreek_ptp_setup(struct hellcreek *hellcreek)
 381{
 382        u16 status;
 383        int ret;
 384
 385        /* Set up the overflow work */
 386        INIT_DELAYED_WORK(&hellcreek->overflow_work,
 387                          hellcreek_ptp_overflow_check);
 388
 389        /* Setup PTP clock */
 390        hellcreek->ptp_clock_info.owner = THIS_MODULE;
 391        snprintf(hellcreek->ptp_clock_info.name,
 392                 sizeof(hellcreek->ptp_clock_info.name),
 393                 dev_name(hellcreek->dev));
 394
 395        /* IP-Core can add up to 0.5 ns per 8 ns cycle, which means
 396         * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts
 397         * the nominal frequency by 6.25%)
 398         */
 399        hellcreek->ptp_clock_info.max_adj     = 62500000;
 400        hellcreek->ptp_clock_info.n_alarm     = 0;
 401        hellcreek->ptp_clock_info.n_pins      = 0;
 402        hellcreek->ptp_clock_info.n_ext_ts    = 0;
 403        hellcreek->ptp_clock_info.n_per_out   = 0;
 404        hellcreek->ptp_clock_info.pps         = 0;
 405        hellcreek->ptp_clock_info.adjfine     = hellcreek_ptp_adjfine;
 406        hellcreek->ptp_clock_info.adjtime     = hellcreek_ptp_adjtime;
 407        hellcreek->ptp_clock_info.gettime64   = hellcreek_ptp_gettime;
 408        hellcreek->ptp_clock_info.settime64   = hellcreek_ptp_settime;
 409        hellcreek->ptp_clock_info.enable      = hellcreek_ptp_enable;
 410        hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work;
 411
 412        hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
 413                                                  hellcreek->dev);
 414        if (IS_ERR(hellcreek->ptp_clock))
 415                return PTR_ERR(hellcreek->ptp_clock);
 416
 417        /* Enable the offset correction process, if no offset correction is
 418         * already taking place
 419         */
 420        status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C);
 421        if (!(status & PR_CLOCK_STATUS_C_OFS_ACT))
 422                hellcreek_ptp_write(hellcreek,
 423                                    status | PR_CLOCK_STATUS_C_ENA_OFS,
 424                                    PR_CLOCK_STATUS_C);
 425
 426        /* Enable the drift correction process */
 427        hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT,
 428                            PR_CLOCK_STATUS_C);
 429
 430        /* LED setup */
 431        ret = hellcreek_led_setup(hellcreek);
 432        if (ret) {
 433                if (hellcreek->ptp_clock)
 434                        ptp_clock_unregister(hellcreek->ptp_clock);
 435                return ret;
 436        }
 437
 438        schedule_delayed_work(&hellcreek->overflow_work,
 439                              HELLCREEK_OVERFLOW_PERIOD);
 440
 441        return 0;
 442}
 443
 444void hellcreek_ptp_free(struct hellcreek *hellcreek)
 445{
 446        led_classdev_unregister(&hellcreek->led_is_gm);
 447        led_classdev_unregister(&hellcreek->led_sync_good);
 448        cancel_delayed_work_sync(&hellcreek->overflow_work);
 449        if (hellcreek->ptp_clock)
 450                ptp_clock_unregister(hellcreek->ptp_clock);
 451        hellcreek->ptp_clock = NULL;
 452}
 453