hellcreek_ptp.c 13 KB

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