Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: dsa: hellcreek: Add PTP clock support

The switch has internal PTP hardware clocks. Add support for it. There are three
clocks:

* Synchronized
* Syntonized
* Free running

Currently the synchronized clock is exported to user space which is a good
default for the beginning. The free running clock might be exported later
e.g. for implementing 802.1AS-2011/2020 Time Aware Bridges (TAB). The switch
also supports cross time stamping for that purpose.

The implementation adds support setting/getting the time as well as offset and
frequency adjustments. However, the clock only holds a partial timeofday
timestamp. This is why we track the seconds completely in software (see overflow
work and last_ts).

Furthermore, add the PTP multicast addresses into the FDB to forward that
packages only to the CPU port where they are processed by a PTP program.

Signed-off-by: Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de>
Signed-off-by: Kurt Kanzenbach <kurt@linutronix.de>
Acked-by: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Kamil Alkhouri and committed by
Jakub Kicinski
ddd56dfe e4b27ebc

+435 -1
+1
drivers/net/dsa/hirschmann/Kconfig
··· 3 3 tristate "Hirschmann Hellcreek TSN Switch support" 4 4 depends on HAS_IOMEM 5 5 depends on NET_DSA 6 + depends on PTP_1588_CLOCK 6 7 select NET_DSA_TAG_HELLCREEK 7 8 help 8 9 This driver adds support for Hirschmann Hellcreek TSN switches.
+3 -1
drivers/net/dsa/hirschmann/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 - obj-$(CONFIG_NET_DSA_HIRSCHMANN_HELLCREEK) += hellcreek.o 2 + obj-$(CONFIG_NET_DSA_HIRSCHMANN_HELLCREEK) += hellcreek_sw.o 3 + hellcreek_sw-objs := hellcreek.o 4 + hellcreek_sw-objs += hellcreek_ptp.o
+71
drivers/net/dsa/hirschmann/hellcreek.c
··· 25 25 #include <net/dsa.h> 26 26 27 27 #include "hellcreek.h" 28 + #include "hellcreek_ptp.h" 28 29 29 30 static const struct hellcreek_counter hellcreek_counter[] = { 30 31 { 0x00, "RxFiltered", }, ··· 960 959 } 961 960 } 962 961 962 + static int hellcreek_setup_fdb(struct hellcreek *hellcreek) 963 + { 964 + static struct hellcreek_fdb_entry ptp = { 965 + /* MAC: 01-1B-19-00-00-00 */ 966 + .mac = { 0x01, 0x1b, 0x19, 0x00, 0x00, 0x00 }, 967 + .portmask = 0x03, /* Management ports */ 968 + .age = 0, 969 + .is_obt = 0, 970 + .pass_blocked = 0, 971 + .is_static = 1, 972 + .reprio_tc = 6, /* TC: 6 as per IEEE 802.1AS */ 973 + .reprio_en = 1, 974 + }; 975 + static struct hellcreek_fdb_entry p2p = { 976 + /* MAC: 01-80-C2-00-00-0E */ 977 + .mac = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }, 978 + .portmask = 0x03, /* Management ports */ 979 + .age = 0, 980 + .is_obt = 0, 981 + .pass_blocked = 0, 982 + .is_static = 1, 983 + .reprio_tc = 6, /* TC: 6 as per IEEE 802.1AS */ 984 + .reprio_en = 1, 985 + }; 986 + int ret; 987 + 988 + mutex_lock(&hellcreek->reg_lock); 989 + ret = __hellcreek_fdb_add(hellcreek, &ptp); 990 + if (ret) 991 + goto out; 992 + ret = __hellcreek_fdb_add(hellcreek, &p2p); 993 + out: 994 + mutex_unlock(&hellcreek->reg_lock); 995 + 996 + return ret; 997 + } 998 + 963 999 static int hellcreek_setup(struct dsa_switch *ds) 964 1000 { 965 1001 struct hellcreek *hellcreek = ds->priv; ··· 1046 1008 * filtering setups are not supported. 1047 1009 */ 1048 1010 ds->vlan_filtering_is_global = true; 1011 + 1012 + /* Intercept _all_ PTP multicast traffic */ 1013 + ret = hellcreek_setup_fdb(hellcreek); 1014 + if (ret) { 1015 + dev_err(hellcreek->dev, 1016 + "Failed to insert static PTP FDB entries\n"); 1017 + return ret; 1018 + } 1049 1019 1050 1020 return 0; 1051 1021 } ··· 1206 1160 1207 1161 mutex_init(&hellcreek->reg_lock); 1208 1162 mutex_init(&hellcreek->vlan_lock); 1163 + mutex_init(&hellcreek->ptp_lock); 1209 1164 1210 1165 hellcreek->dev = dev; 1211 1166 ··· 1220 1173 if (IS_ERR(hellcreek->base)) { 1221 1174 dev_err(dev, "No memory available!\n"); 1222 1175 return PTR_ERR(hellcreek->base); 1176 + } 1177 + 1178 + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ptp"); 1179 + if (!res) { 1180 + dev_err(dev, "No PTP memory region provided!\n"); 1181 + return -ENODEV; 1182 + } 1183 + 1184 + hellcreek->ptp_base = devm_ioremap_resource(dev, res); 1185 + if (IS_ERR(hellcreek->ptp_base)) { 1186 + dev_err(dev, "No memory available!\n"); 1187 + return PTR_ERR(hellcreek->ptp_base); 1223 1188 } 1224 1189 1225 1190 ret = hellcreek_detect(hellcreek); ··· 1264 1205 return ret; 1265 1206 } 1266 1207 1208 + ret = hellcreek_ptp_setup(hellcreek); 1209 + if (ret) { 1210 + dev_err(dev, "Failed to setup PTP!\n"); 1211 + goto err_ptp_setup; 1212 + } 1213 + 1267 1214 platform_set_drvdata(pdev, hellcreek); 1268 1215 1269 1216 return 0; 1217 + 1218 + err_ptp_setup: 1219 + dsa_unregister_switch(hellcreek->ds); 1220 + 1221 + return ret; 1270 1222 } 1271 1223 1272 1224 static int hellcreek_remove(struct platform_device *pdev) 1273 1225 { 1274 1226 struct hellcreek *hellcreek = platform_get_drvdata(pdev); 1275 1227 1228 + hellcreek_ptp_free(hellcreek); 1276 1229 dsa_unregister_switch(hellcreek->ds); 1277 1230 platform_set_drvdata(pdev, NULL); 1278 1231
+8
drivers/net/dsa/hirschmann/hellcreek.h
··· 15 15 #include <linux/device.h> 16 16 #include <linux/kernel.h> 17 17 #include <linux/mutex.h> 18 + #include <linux/workqueue.h> 18 19 #include <linux/platform_data/hirschmann-hellcreek.h> 19 20 #include <linux/ptp_clock_kernel.h> 20 21 #include <linux/timecounter.h> ··· 238 237 const struct hellcreek_platform_data *pdata; 239 238 struct device *dev; 240 239 struct dsa_switch *ds; 240 + struct ptp_clock *ptp_clock; 241 + struct ptp_clock_info ptp_clock_info; 241 242 struct hellcreek_port *ports; 243 + struct delayed_work overflow_work; 242 244 struct mutex reg_lock; /* Switch IP register lock */ 243 245 struct mutex vlan_lock; /* VLAN bitmaps lock */ 246 + struct mutex ptp_lock; /* PTP IP register lock */ 244 247 void __iomem *base; 248 + void __iomem *ptp_base; 245 249 u16 swcfg; /* swcfg shadow */ 246 250 u8 *vidmbrcfg; /* vidmbrcfg shadow */ 251 + u64 seconds; /* PTP seconds */ 252 + u64 last_ts; /* Used for overflow detection */ 247 253 size_t fdb_entries; 248 254 }; 249 255
+283
drivers/net/dsa/hirschmann/hellcreek_ptp.c
··· 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 + 16 + static u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset) 17 + { 18 + return readw(hellcreek->ptp_base + offset); 19 + } 20 + 21 + static void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data, 22 + unsigned int offset) 23 + { 24 + writew(data, hellcreek->ptp_base + offset); 25 + } 26 + 27 + /* Get nanoseconds from PTP clock */ 28 + static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek) 29 + { 30 + u16 nsl, nsh; 31 + 32 + /* Take a snapshot */ 33 + hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C); 34 + 35 + /* The time of the day is saved as 96 bits. However, due to hardware 36 + * limitations the seconds are not or only partly kept in the PTP 37 + * core. Currently only three bits for the seconds are available. That's 38 + * why only the nanoseconds are used and the seconds are tracked in 39 + * software. Anyway due to internal locking all five registers should be 40 + * read. 41 + */ 42 + nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); 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 + nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); 47 + 48 + return (u64)nsl | ((u64)nsh << 16); 49 + } 50 + 51 + static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek) 52 + { 53 + u64 ns; 54 + 55 + ns = hellcreek_ptp_clock_read(hellcreek); 56 + if (ns < hellcreek->last_ts) 57 + hellcreek->seconds++; 58 + hellcreek->last_ts = ns; 59 + ns += hellcreek->seconds * NSEC_PER_SEC; 60 + 61 + return ns; 62 + } 63 + 64 + static int hellcreek_ptp_gettime(struct ptp_clock_info *ptp, 65 + struct timespec64 *ts) 66 + { 67 + struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 68 + u64 ns; 69 + 70 + mutex_lock(&hellcreek->ptp_lock); 71 + ns = __hellcreek_ptp_gettime(hellcreek); 72 + mutex_unlock(&hellcreek->ptp_lock); 73 + 74 + *ts = ns_to_timespec64(ns); 75 + 76 + return 0; 77 + } 78 + 79 + static int hellcreek_ptp_settime(struct ptp_clock_info *ptp, 80 + const struct timespec64 *ts) 81 + { 82 + struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 83 + u16 secl, nsh, nsl; 84 + 85 + secl = ts->tv_sec & 0xffff; 86 + nsh = ((u32)ts->tv_nsec & 0xffff0000) >> 16; 87 + nsl = ts->tv_nsec & 0xffff; 88 + 89 + mutex_lock(&hellcreek->ptp_lock); 90 + 91 + /* Update overflow data structure */ 92 + hellcreek->seconds = ts->tv_sec; 93 + hellcreek->last_ts = ts->tv_nsec; 94 + 95 + /* Set time in clock */ 96 + hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C); 97 + hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C); 98 + hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C); 99 + hellcreek_ptp_write(hellcreek, nsh, PR_CLOCK_WRITE_C); 100 + hellcreek_ptp_write(hellcreek, nsl, PR_CLOCK_WRITE_C); 101 + 102 + mutex_unlock(&hellcreek->ptp_lock); 103 + 104 + return 0; 105 + } 106 + 107 + static int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) 108 + { 109 + struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 110 + u16 negative = 0, addendh, addendl; 111 + u32 addend; 112 + u64 adj; 113 + 114 + if (scaled_ppm < 0) { 115 + negative = 1; 116 + scaled_ppm = -scaled_ppm; 117 + } 118 + 119 + /* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns 120 + * from the 8 ns (period of the oscillator) every time the accumulator 121 + * register overflows. The value stored in the addend register is added 122 + * to the accumulator register every 8 ns. 123 + * 124 + * addend value = (2^30 * accumulator_overflow_rate) / 125 + * oscillator_frequency 126 + * where: 127 + * 128 + * oscillator_frequency = 125 MHz 129 + * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8 130 + */ 131 + adj = scaled_ppm; 132 + adj <<= 11; 133 + addend = (u32)div_u64(adj, 15625); 134 + 135 + addendh = (addend & 0xffff0000) >> 16; 136 + addendl = addend & 0xffff; 137 + 138 + negative = (negative << 15) & 0x8000; 139 + 140 + mutex_lock(&hellcreek->ptp_lock); 141 + 142 + /* Set drift register */ 143 + hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C); 144 + hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C); 145 + hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C); 146 + hellcreek_ptp_write(hellcreek, addendh, PR_CLOCK_DRIFT_C); 147 + hellcreek_ptp_write(hellcreek, addendl, PR_CLOCK_DRIFT_C); 148 + 149 + mutex_unlock(&hellcreek->ptp_lock); 150 + 151 + return 0; 152 + } 153 + 154 + static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) 155 + { 156 + struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 157 + u16 negative = 0, counth, countl; 158 + u32 count_val; 159 + 160 + /* If the offset is larger than IP-Core slow offset resources. Don't 161 + * consider slow adjustment. Rather, add the offset directly to the 162 + * current time 163 + */ 164 + if (abs(delta) > MAX_SLOW_OFFSET_ADJ) { 165 + struct timespec64 now, then = ns_to_timespec64(delta); 166 + 167 + hellcreek_ptp_gettime(ptp, &now); 168 + now = timespec64_add(now, then); 169 + hellcreek_ptp_settime(ptp, &now); 170 + 171 + return 0; 172 + } 173 + 174 + if (delta < 0) { 175 + negative = 1; 176 + delta = -delta; 177 + } 178 + 179 + /* 'count_val' does not exceed the maximum register size (2^30) */ 180 + count_val = div_s64(delta, MAX_NS_PER_STEP); 181 + 182 + counth = (count_val & 0xffff0000) >> 16; 183 + countl = count_val & 0xffff; 184 + 185 + negative = (negative << 15) & 0x8000; 186 + 187 + mutex_lock(&hellcreek->ptp_lock); 188 + 189 + /* Set offset write register */ 190 + hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C); 191 + hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C); 192 + hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS, 193 + PR_CLOCK_OFFSET_C); 194 + hellcreek_ptp_write(hellcreek, countl, PR_CLOCK_OFFSET_C); 195 + hellcreek_ptp_write(hellcreek, counth, PR_CLOCK_OFFSET_C); 196 + 197 + mutex_unlock(&hellcreek->ptp_lock); 198 + 199 + return 0; 200 + } 201 + 202 + static int hellcreek_ptp_enable(struct ptp_clock_info *ptp, 203 + struct ptp_clock_request *rq, int on) 204 + { 205 + return -EOPNOTSUPP; 206 + } 207 + 208 + static void hellcreek_ptp_overflow_check(struct work_struct *work) 209 + { 210 + struct delayed_work *dw = to_delayed_work(work); 211 + struct hellcreek *hellcreek; 212 + 213 + hellcreek = dw_overflow_to_hellcreek(dw); 214 + 215 + mutex_lock(&hellcreek->ptp_lock); 216 + __hellcreek_ptp_gettime(hellcreek); 217 + mutex_unlock(&hellcreek->ptp_lock); 218 + 219 + schedule_delayed_work(&hellcreek->overflow_work, 220 + HELLCREEK_OVERFLOW_PERIOD); 221 + } 222 + 223 + int hellcreek_ptp_setup(struct hellcreek *hellcreek) 224 + { 225 + u16 status; 226 + 227 + /* Set up the overflow work */ 228 + INIT_DELAYED_WORK(&hellcreek->overflow_work, 229 + hellcreek_ptp_overflow_check); 230 + 231 + /* Setup PTP clock */ 232 + hellcreek->ptp_clock_info.owner = THIS_MODULE; 233 + snprintf(hellcreek->ptp_clock_info.name, 234 + sizeof(hellcreek->ptp_clock_info.name), 235 + dev_name(hellcreek->dev)); 236 + 237 + /* IP-Core can add up to 0.5 ns per 8 ns cycle, which means 238 + * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts 239 + * the nominal frequency by 6.25%) 240 + */ 241 + hellcreek->ptp_clock_info.max_adj = 62500000; 242 + hellcreek->ptp_clock_info.n_alarm = 0; 243 + hellcreek->ptp_clock_info.n_pins = 0; 244 + hellcreek->ptp_clock_info.n_ext_ts = 0; 245 + hellcreek->ptp_clock_info.n_per_out = 0; 246 + hellcreek->ptp_clock_info.pps = 0; 247 + hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine; 248 + hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime; 249 + hellcreek->ptp_clock_info.gettime64 = hellcreek_ptp_gettime; 250 + hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime; 251 + hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable; 252 + 253 + hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info, 254 + hellcreek->dev); 255 + if (IS_ERR(hellcreek->ptp_clock)) 256 + return PTR_ERR(hellcreek->ptp_clock); 257 + 258 + /* Enable the offset correction process, if no offset correction is 259 + * already taking place 260 + */ 261 + status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C); 262 + if (!(status & PR_CLOCK_STATUS_C_OFS_ACT)) 263 + hellcreek_ptp_write(hellcreek, 264 + status | PR_CLOCK_STATUS_C_ENA_OFS, 265 + PR_CLOCK_STATUS_C); 266 + 267 + /* Enable the drift correction process */ 268 + hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT, 269 + PR_CLOCK_STATUS_C); 270 + 271 + schedule_delayed_work(&hellcreek->overflow_work, 272 + HELLCREEK_OVERFLOW_PERIOD); 273 + 274 + return 0; 275 + } 276 + 277 + void hellcreek_ptp_free(struct hellcreek *hellcreek) 278 + { 279 + cancel_delayed_work_sync(&hellcreek->overflow_work); 280 + if (hellcreek->ptp_clock) 281 + ptp_clock_unregister(hellcreek->ptp_clock); 282 + hellcreek->ptp_clock = NULL; 283 + }
+69
drivers/net/dsa/hirschmann/hellcreek_ptp.h
··· 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: Kurt Kanzenbach <kurt@linutronix.de> 9 + * Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de> 10 + */ 11 + 12 + #ifndef _HELLCREEK_PTP_H_ 13 + #define _HELLCREEK_PTP_H_ 14 + 15 + #include <linux/bitops.h> 16 + #include <linux/ptp_clock_kernel.h> 17 + 18 + #include "hellcreek.h" 19 + 20 + /* Every jump in time is 7 ns */ 21 + #define MAX_NS_PER_STEP 7L 22 + 23 + /* Correct offset at every clock cycle */ 24 + #define MIN_CLK_CYCLES_BETWEEN_STEPS 0 25 + 26 + /* Maximum available slow offset resources */ 27 + #define MAX_SLOW_OFFSET_ADJ \ 28 + ((unsigned long long)((1 << 30) - 1) * MAX_NS_PER_STEP) 29 + 30 + /* four times a second overflow check */ 31 + #define HELLCREEK_OVERFLOW_PERIOD (HZ / 4) 32 + 33 + /* PTP Register */ 34 + #define PR_SETTINGS_C (0x09 * 2) 35 + #define PR_SETTINGS_C_RES3TS BIT(4) 36 + #define PR_SETTINGS_C_TS_SRC_TK_SHIFT 8 37 + #define PR_SETTINGS_C_TS_SRC_TK_MASK GENMASK(9, 8) 38 + #define PR_COMMAND_C (0x0a * 2) 39 + #define PR_COMMAND_C_SS BIT(0) 40 + 41 + #define PR_CLOCK_STATUS_C (0x0c * 2) 42 + #define PR_CLOCK_STATUS_C_ENA_DRIFT BIT(12) 43 + #define PR_CLOCK_STATUS_C_OFS_ACT BIT(13) 44 + #define PR_CLOCK_STATUS_C_ENA_OFS BIT(14) 45 + 46 + #define PR_CLOCK_READ_C (0x0d * 2) 47 + #define PR_CLOCK_WRITE_C (0x0e * 2) 48 + #define PR_CLOCK_OFFSET_C (0x0f * 2) 49 + #define PR_CLOCK_DRIFT_C (0x10 * 2) 50 + 51 + #define PR_SS_FREE_DATA_C (0x12 * 2) 52 + #define PR_SS_SYNT_DATA_C (0x14 * 2) 53 + #define PR_SS_SYNC_DATA_C (0x16 * 2) 54 + #define PR_SS_DRAC_DATA_C (0x18 * 2) 55 + 56 + #define STATUS_OUT (0x60 * 2) 57 + #define STATUS_OUT_SYNC_GOOD BIT(0) 58 + #define STATUS_OUT_IS_GM BIT(1) 59 + 60 + int hellcreek_ptp_setup(struct hellcreek *hellcreek); 61 + void hellcreek_ptp_free(struct hellcreek *hellcreek); 62 + 63 + #define ptp_to_hellcreek(ptp) \ 64 + container_of(ptp, struct hellcreek, ptp_clock_info) 65 + 66 + #define dw_overflow_to_hellcreek(dw) \ 67 + container_of(dw, struct hellcreek, overflow_work) 68 + 69 + #endif /* _HELLCREEK_PTP_H_ */