hellcreek_ptp.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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. #include <linux/of.h>
  12. #include <linux/ptp_clock_kernel.h>
  13. #include "hellcreek.h"
  14. #include "hellcreek_ptp.h"
  15. #include "hellcreek_hwtstamp.h"
  16. u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
  17. {
  18. return readw(hellcreek->ptp_base + offset);
  19. }
  20. void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
  21. unsigned int offset)
  22. {
  23. writew(data, hellcreek->ptp_base + offset);
  24. }
  25. /* Get nanoseconds from PTP clock */
  26. static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek,
  27. struct ptp_system_timestamp *sts)
  28. {
  29. u16 nsl, nsh;
  30. /* Take a snapshot */
  31. hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C);
  32. /* The time of the day is saved as 96 bits. However, due to hardware
  33. * limitations the seconds are not or only partly kept in the PTP
  34. * core. Currently only three bits for the seconds are available. That's
  35. * why only the nanoseconds are used and the seconds are tracked in
  36. * software. Anyway due to internal locking all five registers should be
  37. * read.
  38. */
  39. nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
  40. nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
  41. nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
  42. nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
  43. ptp_read_system_prets(sts);
  44. nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
  45. ptp_read_system_postts(sts);
  46. return (u64)nsl | ((u64)nsh << 16);
  47. }
  48. static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek,
  49. struct ptp_system_timestamp *sts)
  50. {
  51. u64 ns;
  52. ns = hellcreek_ptp_clock_read(hellcreek, sts);
  53. if (ns < hellcreek->last_ts)
  54. hellcreek->seconds++;
  55. hellcreek->last_ts = ns;
  56. ns += hellcreek->seconds * NSEC_PER_SEC;
  57. return ns;
  58. }
  59. /* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns.
  60. * There has to be a check whether an overflow occurred between the packet
  61. * arrival and now. If so use the correct seconds (-1) for calculating the
  62. * packet arrival time.
  63. */
  64. u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns)
  65. {
  66. u64 s;
  67. __hellcreek_ptp_gettime(hellcreek, NULL);
  68. if (hellcreek->last_ts > ns)
  69. s = hellcreek->seconds * NSEC_PER_SEC;
  70. else
  71. s = (hellcreek->seconds - 1) * NSEC_PER_SEC;
  72. return s;
  73. }
  74. static int hellcreek_ptp_gettimex(struct ptp_clock_info *ptp,
  75. struct timespec64 *ts,
  76. struct ptp_system_timestamp *sts)
  77. {
  78. struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
  79. u64 ns;
  80. mutex_lock(&hellcreek->ptp_lock);
  81. ns = __hellcreek_ptp_gettime(hellcreek, sts);
  82. mutex_unlock(&hellcreek->ptp_lock);
  83. *ts = ns_to_timespec64(ns);
  84. return 0;
  85. }
  86. static int hellcreek_ptp_settime(struct ptp_clock_info *ptp,
  87. const struct timespec64 *ts)
  88. {
  89. struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
  90. u16 secl, nsh, nsl;
  91. secl = ts->tv_sec & 0xffff;
  92. nsh = ((u32)ts->tv_nsec & 0xffff0000) >> 16;
  93. nsl = ts->tv_nsec & 0xffff;
  94. mutex_lock(&hellcreek->ptp_lock);
  95. /* Update overflow data structure */
  96. hellcreek->seconds = ts->tv_sec;
  97. hellcreek->last_ts = ts->tv_nsec;
  98. /* Set time in clock */
  99. hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
  100. hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
  101. hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C);
  102. hellcreek_ptp_write(hellcreek, nsh, PR_CLOCK_WRITE_C);
  103. hellcreek_ptp_write(hellcreek, nsl, PR_CLOCK_WRITE_C);
  104. mutex_unlock(&hellcreek->ptp_lock);
  105. return 0;
  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. if (scaled_ppm < 0) {
  114. negative = 1;
  115. scaled_ppm = -scaled_ppm;
  116. }
  117. /* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns
  118. * from the 8 ns (period of the oscillator) every time the accumulator
  119. * register overflows. The value stored in the addend register is added
  120. * to the accumulator register every 8 ns.
  121. *
  122. * addend value = (2^30 * accumulator_overflow_rate) /
  123. * oscillator_frequency
  124. * where:
  125. *
  126. * oscillator_frequency = 125 MHz
  127. * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8
  128. */
  129. adj = scaled_ppm;
  130. adj <<= 11;
  131. addend = (u32)div_u64(adj, 15625);
  132. addendh = (addend & 0xffff0000) >> 16;
  133. addendl = addend & 0xffff;
  134. negative = (negative << 15) & 0x8000;
  135. mutex_lock(&hellcreek->ptp_lock);
  136. /* Set drift register */
  137. hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C);
  138. hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
  139. hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
  140. hellcreek_ptp_write(hellcreek, addendh, PR_CLOCK_DRIFT_C);
  141. hellcreek_ptp_write(hellcreek, addendl, PR_CLOCK_DRIFT_C);
  142. mutex_unlock(&hellcreek->ptp_lock);
  143. return 0;
  144. }
  145. static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
  146. {
  147. struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
  148. u16 negative = 0, counth, countl;
  149. u32 count_val;
  150. /* If the offset is larger than IP-Core slow offset resources. Don't
  151. * consider slow adjustment. Rather, add the offset directly to the
  152. * current time
  153. */
  154. if (abs(delta) > MAX_SLOW_OFFSET_ADJ) {
  155. struct timespec64 now, then = ns_to_timespec64(delta);
  156. hellcreek_ptp_gettimex(ptp, &now, NULL);
  157. now = timespec64_add(now, then);
  158. hellcreek_ptp_settime(ptp, &now);
  159. return 0;
  160. }
  161. if (delta < 0) {
  162. negative = 1;
  163. delta = -delta;
  164. }
  165. /* 'count_val' does not exceed the maximum register size (2^30) */
  166. count_val = div_s64(delta, MAX_NS_PER_STEP);
  167. counth = (count_val & 0xffff0000) >> 16;
  168. countl = count_val & 0xffff;
  169. negative = (negative << 15) & 0x8000;
  170. mutex_lock(&hellcreek->ptp_lock);
  171. /* Set offset write register */
  172. hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C);
  173. hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C);
  174. hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS,
  175. PR_CLOCK_OFFSET_C);
  176. hellcreek_ptp_write(hellcreek, countl, PR_CLOCK_OFFSET_C);
  177. hellcreek_ptp_write(hellcreek, counth, PR_CLOCK_OFFSET_C);
  178. mutex_unlock(&hellcreek->ptp_lock);
  179. return 0;
  180. }
  181. static int hellcreek_ptp_enable(struct ptp_clock_info *ptp,
  182. struct ptp_clock_request *rq, int on)
  183. {
  184. return -EOPNOTSUPP;
  185. }
  186. static void hellcreek_ptp_overflow_check(struct work_struct *work)
  187. {
  188. struct delayed_work *dw = to_delayed_work(work);
  189. struct hellcreek *hellcreek;
  190. hellcreek = dw_overflow_to_hellcreek(dw);
  191. mutex_lock(&hellcreek->ptp_lock);
  192. __hellcreek_ptp_gettime(hellcreek, NULL);
  193. mutex_unlock(&hellcreek->ptp_lock);
  194. schedule_delayed_work(&hellcreek->overflow_work,
  195. HELLCREEK_OVERFLOW_PERIOD);
  196. }
  197. static enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek,
  198. int led)
  199. {
  200. return (hellcreek->status_out & led) ? 1 : 0;
  201. }
  202. static void hellcreek_set_brightness(struct hellcreek *hellcreek, int led,
  203. enum led_brightness b)
  204. {
  205. mutex_lock(&hellcreek->ptp_lock);
  206. if (b)
  207. hellcreek->status_out |= led;
  208. else
  209. hellcreek->status_out &= ~led;
  210. hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT);
  211. mutex_unlock(&hellcreek->ptp_lock);
  212. }
  213. static void hellcreek_led_sync_good_set(struct led_classdev *ldev,
  214. enum led_brightness b)
  215. {
  216. struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
  217. hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b);
  218. }
  219. static enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev)
  220. {
  221. struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
  222. return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
  223. }
  224. static void hellcreek_led_is_gm_set(struct led_classdev *ldev,
  225. enum led_brightness b)
  226. {
  227. struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
  228. hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b);
  229. }
  230. static enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev)
  231. {
  232. struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
  233. return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
  234. }
  235. /* There two available LEDs internally called sync_good and is_gm. However, the
  236. * user might want to use a different label and specify the default state. Take
  237. * those properties from device tree.
  238. */
  239. static int hellcreek_led_setup(struct hellcreek *hellcreek)
  240. {
  241. struct device_node *leds, *led = NULL;
  242. enum led_default_state state;
  243. const char *label;
  244. int ret = -EINVAL;
  245. of_node_get(hellcreek->dev->of_node);
  246. leds = of_find_node_by_name(hellcreek->dev->of_node, "leds");
  247. if (!leds) {
  248. dev_err(hellcreek->dev, "No LEDs specified in device tree!\n");
  249. return ret;
  250. }
  251. hellcreek->status_out = 0;
  252. led = of_get_next_available_child(leds, led);
  253. if (!led) {
  254. dev_err(hellcreek->dev, "First LED not specified!\n");
  255. goto out;
  256. }
  257. ret = of_property_read_string(led, "label", &label);
  258. hellcreek->led_sync_good.name = ret ? "sync_good" : label;
  259. state = led_init_default_state_get(of_fwnode_handle(led));
  260. switch (state) {
  261. case LEDS_DEFSTATE_ON:
  262. hellcreek->led_sync_good.brightness = 1;
  263. break;
  264. case LEDS_DEFSTATE_KEEP:
  265. hellcreek->led_sync_good.brightness =
  266. hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
  267. break;
  268. default:
  269. hellcreek->led_sync_good.brightness = 0;
  270. }
  271. hellcreek->led_sync_good.max_brightness = 1;
  272. hellcreek->led_sync_good.brightness_set = hellcreek_led_sync_good_set;
  273. hellcreek->led_sync_good.brightness_get = hellcreek_led_sync_good_get;
  274. led = of_get_next_available_child(leds, led);
  275. if (!led) {
  276. dev_err(hellcreek->dev, "Second LED not specified!\n");
  277. ret = -EINVAL;
  278. goto out;
  279. }
  280. ret = of_property_read_string(led, "label", &label);
  281. hellcreek->led_is_gm.name = ret ? "is_gm" : label;
  282. state = led_init_default_state_get(of_fwnode_handle(led));
  283. switch (state) {
  284. case LEDS_DEFSTATE_ON:
  285. hellcreek->led_is_gm.brightness = 1;
  286. break;
  287. case LEDS_DEFSTATE_KEEP:
  288. hellcreek->led_is_gm.brightness =
  289. hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
  290. break;
  291. default:
  292. hellcreek->led_is_gm.brightness = 0;
  293. }
  294. hellcreek->led_is_gm.max_brightness = 1;
  295. hellcreek->led_is_gm.brightness_set = hellcreek_led_is_gm_set;
  296. hellcreek->led_is_gm.brightness_get = hellcreek_led_is_gm_get;
  297. /* Set initial state */
  298. if (hellcreek->led_sync_good.brightness == 1)
  299. hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, 1);
  300. if (hellcreek->led_is_gm.brightness == 1)
  301. hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, 1);
  302. /* Register both leds */
  303. led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good);
  304. led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm);
  305. ret = 0;
  306. out:
  307. of_node_put(leds);
  308. return ret;
  309. }
  310. int hellcreek_ptp_setup(struct hellcreek *hellcreek)
  311. {
  312. u16 status;
  313. int ret;
  314. /* Set up the overflow work */
  315. INIT_DELAYED_WORK(&hellcreek->overflow_work,
  316. hellcreek_ptp_overflow_check);
  317. /* Setup PTP clock */
  318. hellcreek->ptp_clock_info.owner = THIS_MODULE;
  319. snprintf(hellcreek->ptp_clock_info.name,
  320. sizeof(hellcreek->ptp_clock_info.name),
  321. dev_name(hellcreek->dev));
  322. /* IP-Core can add up to 0.5 ns per 8 ns cycle, which means
  323. * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts
  324. * the nominal frequency by 6.25%)
  325. */
  326. hellcreek->ptp_clock_info.max_adj = 62500000;
  327. hellcreek->ptp_clock_info.n_alarm = 0;
  328. hellcreek->ptp_clock_info.n_pins = 0;
  329. hellcreek->ptp_clock_info.n_ext_ts = 0;
  330. hellcreek->ptp_clock_info.n_per_out = 0;
  331. hellcreek->ptp_clock_info.pps = 0;
  332. hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine;
  333. hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime;
  334. hellcreek->ptp_clock_info.gettimex64 = hellcreek_ptp_gettimex;
  335. hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime;
  336. hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable;
  337. hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work;
  338. hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
  339. hellcreek->dev);
  340. if (IS_ERR(hellcreek->ptp_clock))
  341. return PTR_ERR(hellcreek->ptp_clock);
  342. /* Enable the offset correction process, if no offset correction is
  343. * already taking place
  344. */
  345. status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C);
  346. if (!(status & PR_CLOCK_STATUS_C_OFS_ACT))
  347. hellcreek_ptp_write(hellcreek,
  348. status | PR_CLOCK_STATUS_C_ENA_OFS,
  349. PR_CLOCK_STATUS_C);
  350. /* Enable the drift correction process */
  351. hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT,
  352. PR_CLOCK_STATUS_C);
  353. /* LED setup */
  354. ret = hellcreek_led_setup(hellcreek);
  355. if (ret) {
  356. if (hellcreek->ptp_clock)
  357. ptp_clock_unregister(hellcreek->ptp_clock);
  358. return ret;
  359. }
  360. schedule_delayed_work(&hellcreek->overflow_work,
  361. HELLCREEK_OVERFLOW_PERIOD);
  362. return 0;
  363. }
  364. void hellcreek_ptp_free(struct hellcreek *hellcreek)
  365. {
  366. led_classdev_unregister(&hellcreek->led_is_gm);
  367. led_classdev_unregister(&hellcreek->led_sync_good);
  368. cancel_delayed_work_sync(&hellcreek->overflow_work);
  369. if (hellcreek->ptp_clock)
  370. ptp_clock_unregister(hellcreek->ptp_clock);
  371. hellcreek->ptp_clock = NULL;
  372. }