168cf027fSGrygorii Strashko // SPDX-License-Identifier: GPL-2.0+ 287c0e764SRichard Cochran /* 387c0e764SRichard Cochran * TI Common Platform Time Sync 487c0e764SRichard Cochran * 587c0e764SRichard Cochran * Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com> 687c0e764SRichard Cochran * 787c0e764SRichard Cochran */ 8a3047a81SGrygorii Strashko #include <linux/clk-provider.h> 987c0e764SRichard Cochran #include <linux/err.h> 1087c0e764SRichard Cochran #include <linux/if.h> 1187c0e764SRichard Cochran #include <linux/hrtimer.h> 1287c0e764SRichard Cochran #include <linux/module.h> 1387c0e764SRichard Cochran #include <linux/net_tstamp.h> 1487c0e764SRichard Cochran #include <linux/ptp_classify.h> 1587c0e764SRichard Cochran #include <linux/time.h> 1687c0e764SRichard Cochran #include <linux/uaccess.h> 1787c0e764SRichard Cochran #include <linux/workqueue.h> 1879eb9d28SAlexei Starovoitov #include <linux/if_ether.h> 1979eb9d28SAlexei Starovoitov #include <linux/if_vlan.h> 2087c0e764SRichard Cochran 2187c0e764SRichard Cochran #include "cpts.h" 2287c0e764SRichard Cochran 230d5f54feSGrygorii Strashko #define CPTS_SKB_TX_WORK_TIMEOUT 1 /* jiffies */ 240d5f54feSGrygorii Strashko 250d5f54feSGrygorii Strashko struct cpts_skb_cb_data { 26*3bfd41b5SGrygorii Strashko u32 skb_mtype_seqid; 270d5f54feSGrygorii Strashko unsigned long tmo; 280d5f54feSGrygorii Strashko }; 290d5f54feSGrygorii Strashko 30391fd6caSGrygorii Strashko #define cpts_read32(c, r) readl_relaxed(&c->reg->r) 31391fd6caSGrygorii Strashko #define cpts_write32(c, v, r) writel_relaxed(v, &c->reg->r) 3287c0e764SRichard Cochran 3387c0e764SRichard Cochran static int event_expired(struct cpts_event *event) 3487c0e764SRichard Cochran { 3587c0e764SRichard Cochran return time_after(jiffies, event->tmo); 3687c0e764SRichard Cochran } 3787c0e764SRichard Cochran 3887c0e764SRichard Cochran static int event_type(struct cpts_event *event) 3987c0e764SRichard Cochran { 4087c0e764SRichard Cochran return (event->high >> EVENT_TYPE_SHIFT) & EVENT_TYPE_MASK; 4187c0e764SRichard Cochran } 4287c0e764SRichard Cochran 4387c0e764SRichard Cochran static int cpts_fifo_pop(struct cpts *cpts, u32 *high, u32 *low) 4487c0e764SRichard Cochran { 4587c0e764SRichard Cochran u32 r = cpts_read32(cpts, intstat_raw); 4687c0e764SRichard Cochran 4787c0e764SRichard Cochran if (r & TS_PEND_RAW) { 4887c0e764SRichard Cochran *high = cpts_read32(cpts, event_high); 4987c0e764SRichard Cochran *low = cpts_read32(cpts, event_low); 5087c0e764SRichard Cochran cpts_write32(cpts, EVENT_POP, event_pop); 5187c0e764SRichard Cochran return 0; 5287c0e764SRichard Cochran } 5387c0e764SRichard Cochran return -1; 5487c0e764SRichard Cochran } 5587c0e764SRichard Cochran 56e4439fa8SWingMan Kwok static int cpts_purge_events(struct cpts *cpts) 57e4439fa8SWingMan Kwok { 58e4439fa8SWingMan Kwok struct list_head *this, *next; 59e4439fa8SWingMan Kwok struct cpts_event *event; 60e4439fa8SWingMan Kwok int removed = 0; 61e4439fa8SWingMan Kwok 62e4439fa8SWingMan Kwok list_for_each_safe(this, next, &cpts->events) { 63e4439fa8SWingMan Kwok event = list_entry(this, struct cpts_event, list); 64e4439fa8SWingMan Kwok if (event_expired(event)) { 65e4439fa8SWingMan Kwok list_del_init(&event->list); 66e4439fa8SWingMan Kwok list_add(&event->list, &cpts->pool); 67e4439fa8SWingMan Kwok ++removed; 68e4439fa8SWingMan Kwok } 69e4439fa8SWingMan Kwok } 70e4439fa8SWingMan Kwok 71e4439fa8SWingMan Kwok if (removed) 7279d6e755SGrygorii Strashko dev_dbg(cpts->dev, "cpts: event pool cleaned up %d\n", removed); 73e4439fa8SWingMan Kwok return removed ? 0 : -1; 74e4439fa8SWingMan Kwok } 75e4439fa8SWingMan Kwok 76f19dcd5fSIvan Khoronzhuk static void cpts_purge_txq(struct cpts *cpts) 77f19dcd5fSIvan Khoronzhuk { 78f19dcd5fSIvan Khoronzhuk struct cpts_skb_cb_data *skb_cb; 79f19dcd5fSIvan Khoronzhuk struct sk_buff *skb, *tmp; 80f19dcd5fSIvan Khoronzhuk int removed = 0; 81f19dcd5fSIvan Khoronzhuk 82f19dcd5fSIvan Khoronzhuk skb_queue_walk_safe(&cpts->txq, skb, tmp) { 83f19dcd5fSIvan Khoronzhuk skb_cb = (struct cpts_skb_cb_data *)skb->cb; 84f19dcd5fSIvan Khoronzhuk if (time_after(jiffies, skb_cb->tmo)) { 85f19dcd5fSIvan Khoronzhuk __skb_unlink(skb, &cpts->txq); 86f19dcd5fSIvan Khoronzhuk dev_consume_skb_any(skb); 87f19dcd5fSIvan Khoronzhuk ++removed; 88f19dcd5fSIvan Khoronzhuk } 89f19dcd5fSIvan Khoronzhuk } 90f19dcd5fSIvan Khoronzhuk 91f19dcd5fSIvan Khoronzhuk if (removed) 92f19dcd5fSIvan Khoronzhuk dev_dbg(cpts->dev, "txq cleaned up %d\n", removed); 93f19dcd5fSIvan Khoronzhuk } 94f19dcd5fSIvan Khoronzhuk 950d5f54feSGrygorii Strashko static bool cpts_match_tx_ts(struct cpts *cpts, struct cpts_event *event) 960d5f54feSGrygorii Strashko { 970d5f54feSGrygorii Strashko struct sk_buff *skb, *tmp; 980d5f54feSGrygorii Strashko bool found = false; 99*3bfd41b5SGrygorii Strashko u32 mtype_seqid; 1000d5f54feSGrygorii Strashko 101*3bfd41b5SGrygorii Strashko mtype_seqid = event->high & 102*3bfd41b5SGrygorii Strashko ((MESSAGE_TYPE_MASK << MESSAGE_TYPE_SHIFT) | 103*3bfd41b5SGrygorii Strashko (SEQUENCE_ID_MASK << SEQUENCE_ID_SHIFT) | 104*3bfd41b5SGrygorii Strashko (EVENT_TYPE_MASK << EVENT_TYPE_SHIFT)); 1050d5f54feSGrygorii Strashko 1060d5f54feSGrygorii Strashko /* no need to grab txq.lock as access is always done under cpts->lock */ 1070d5f54feSGrygorii Strashko skb_queue_walk_safe(&cpts->txq, skb, tmp) { 1080d5f54feSGrygorii Strashko struct skb_shared_hwtstamps ssh; 1090d5f54feSGrygorii Strashko struct cpts_skb_cb_data *skb_cb = 1100d5f54feSGrygorii Strashko (struct cpts_skb_cb_data *)skb->cb; 1110d5f54feSGrygorii Strashko 112*3bfd41b5SGrygorii Strashko if (mtype_seqid == skb_cb->skb_mtype_seqid) { 1130d5f54feSGrygorii Strashko memset(&ssh, 0, sizeof(ssh)); 114e66dccceSGrygorii Strashko ssh.hwtstamp = ns_to_ktime(event->timestamp); 1150d5f54feSGrygorii Strashko skb_tstamp_tx(skb, &ssh); 1160d5f54feSGrygorii Strashko found = true; 1170d5f54feSGrygorii Strashko __skb_unlink(skb, &cpts->txq); 1180d5f54feSGrygorii Strashko dev_consume_skb_any(skb); 119*3bfd41b5SGrygorii Strashko dev_dbg(cpts->dev, "match tx timestamp mtype_seqid %08x\n", 120*3bfd41b5SGrygorii Strashko mtype_seqid); 121d0c694fcSIvan Khoronzhuk break; 122d0c694fcSIvan Khoronzhuk } 123d0c694fcSIvan Khoronzhuk 124d0c694fcSIvan Khoronzhuk if (time_after(jiffies, skb_cb->tmo)) { 1250d5f54feSGrygorii Strashko /* timeout any expired skbs over 1s */ 126d0e14c4dSIvan Khoronzhuk dev_dbg(cpts->dev, "expiring tx timestamp from txq\n"); 1270d5f54feSGrygorii Strashko __skb_unlink(skb, &cpts->txq); 1280d5f54feSGrygorii Strashko dev_consume_skb_any(skb); 1290d5f54feSGrygorii Strashko } 1300d5f54feSGrygorii Strashko } 1310d5f54feSGrygorii Strashko 1320d5f54feSGrygorii Strashko return found; 1330d5f54feSGrygorii Strashko } 1340d5f54feSGrygorii Strashko 13587c0e764SRichard Cochran /* 13687c0e764SRichard Cochran * Returns zero if matching event type was found. 13787c0e764SRichard Cochran */ 13887c0e764SRichard Cochran static int cpts_fifo_read(struct cpts *cpts, int match) 13987c0e764SRichard Cochran { 14087c0e764SRichard Cochran int i, type = -1; 14187c0e764SRichard Cochran u32 hi, lo; 14287c0e764SRichard Cochran struct cpts_event *event; 14387c0e764SRichard Cochran 14487c0e764SRichard Cochran for (i = 0; i < CPTS_FIFO_DEPTH; i++) { 14587c0e764SRichard Cochran if (cpts_fifo_pop(cpts, &hi, &lo)) 14687c0e764SRichard Cochran break; 147e4439fa8SWingMan Kwok 148e4439fa8SWingMan Kwok if (list_empty(&cpts->pool) && cpts_purge_events(cpts)) { 14979d6e755SGrygorii Strashko dev_warn(cpts->dev, "cpts: event pool empty\n"); 15087c0e764SRichard Cochran return -1; 15187c0e764SRichard Cochran } 152e4439fa8SWingMan Kwok 15387c0e764SRichard Cochran event = list_first_entry(&cpts->pool, struct cpts_event, list); 15487c0e764SRichard Cochran event->tmo = jiffies + 2; 15587c0e764SRichard Cochran event->high = hi; 15687c0e764SRichard Cochran event->low = lo; 157e66dccceSGrygorii Strashko event->timestamp = timecounter_cyc2time(&cpts->tc, event->low); 15887c0e764SRichard Cochran type = event_type(event); 159e66dccceSGrygorii Strashko 160e66dccceSGrygorii Strashko dev_dbg(cpts->dev, "CPTS_EV: %d high:%08X low:%08x\n", 161e66dccceSGrygorii Strashko type, event->high, event->low); 16287c0e764SRichard Cochran switch (type) { 163e66dccceSGrygorii Strashko case CPTS_EV_PUSH: 164e66dccceSGrygorii Strashko WRITE_ONCE(cpts->cur_timestamp, lo); 165e66dccceSGrygorii Strashko timecounter_read(&cpts->tc); 1660d6df3e6SGrygorii Strashko if (cpts->mult_new) { 1670d6df3e6SGrygorii Strashko cpts->cc.mult = cpts->mult_new; 1680d6df3e6SGrygorii Strashko cpts->mult_new = 0; 1690d6df3e6SGrygorii Strashko } 170e66dccceSGrygorii Strashko break; 1710d5f54feSGrygorii Strashko case CPTS_EV_TX: 1720d5f54feSGrygorii Strashko if (cpts_match_tx_ts(cpts, event)) { 1730d5f54feSGrygorii Strashko /* if the new event matches an existing skb, 1740d5f54feSGrygorii Strashko * then don't queue it 1750d5f54feSGrygorii Strashko */ 1760d5f54feSGrygorii Strashko break; 1770d5f54feSGrygorii Strashko } 178e38c2e11SGustavo A. R. Silva /* fall through */ 17987c0e764SRichard Cochran case CPTS_EV_RX: 18087c0e764SRichard Cochran list_del_init(&event->list); 18187c0e764SRichard Cochran list_add_tail(&event->list, &cpts->events); 18287c0e764SRichard Cochran break; 18387c0e764SRichard Cochran case CPTS_EV_ROLL: 18487c0e764SRichard Cochran case CPTS_EV_HALF: 18587c0e764SRichard Cochran case CPTS_EV_HW: 18687c0e764SRichard Cochran break; 18787c0e764SRichard Cochran default: 18879d6e755SGrygorii Strashko dev_err(cpts->dev, "cpts: unknown event type\n"); 18987c0e764SRichard Cochran break; 19087c0e764SRichard Cochran } 19187c0e764SRichard Cochran if (type == match) 19287c0e764SRichard Cochran break; 19387c0e764SRichard Cochran } 19487c0e764SRichard Cochran return type == match ? 0 : -1; 19587c0e764SRichard Cochran } 19687c0e764SRichard Cochran 197a5a1d1c2SThomas Gleixner static u64 cpts_systim_read(const struct cyclecounter *cc) 19887c0e764SRichard Cochran { 19987c0e764SRichard Cochran struct cpts *cpts = container_of(cc, struct cpts, cc); 20087c0e764SRichard Cochran 201e66dccceSGrygorii Strashko return READ_ONCE(cpts->cur_timestamp); 202e66dccceSGrygorii Strashko } 203e66dccceSGrygorii Strashko 204856e59abSGrygorii Strashko static void cpts_update_cur_time(struct cpts *cpts, int match, 205856e59abSGrygorii Strashko struct ptp_system_timestamp *sts) 206e66dccceSGrygorii Strashko { 207856e59abSGrygorii Strashko ptp_read_system_prets(sts); 20887c0e764SRichard Cochran cpts_write32(cpts, TS_PUSH, ts_push); 209856e59abSGrygorii Strashko cpts_read32(cpts, ts_push); 210856e59abSGrygorii Strashko ptp_read_system_postts(sts); 211e66dccceSGrygorii Strashko 212e66dccceSGrygorii Strashko if (cpts_fifo_read(cpts, match) && match != -1) 21379d6e755SGrygorii Strashko dev_err(cpts->dev, "cpts: unable to obtain a time stamp\n"); 21487c0e764SRichard Cochran } 21587c0e764SRichard Cochran 21687c0e764SRichard Cochran /* PTP clock operations */ 21787c0e764SRichard Cochran 21887c0e764SRichard Cochran static int cpts_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) 21987c0e764SRichard Cochran { 22087c0e764SRichard Cochran u64 adj; 22187c0e764SRichard Cochran u32 diff, mult; 22287c0e764SRichard Cochran int neg_adj = 0; 22387c0e764SRichard Cochran unsigned long flags; 22487c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 22587c0e764SRichard Cochran 22687c0e764SRichard Cochran if (ppb < 0) { 22787c0e764SRichard Cochran neg_adj = 1; 22887c0e764SRichard Cochran ppb = -ppb; 22987c0e764SRichard Cochran } 23087c0e764SRichard Cochran mult = cpts->cc_mult; 23187c0e764SRichard Cochran adj = mult; 23287c0e764SRichard Cochran adj *= ppb; 23387c0e764SRichard Cochran diff = div_u64(adj, 1000000000ULL); 23487c0e764SRichard Cochran 23587c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 23687c0e764SRichard Cochran 2370d6df3e6SGrygorii Strashko cpts->mult_new = neg_adj ? mult - diff : mult + diff; 23887c0e764SRichard Cochran 239856e59abSGrygorii Strashko cpts_update_cur_time(cpts, CPTS_EV_PUSH, NULL); 24087c0e764SRichard Cochran 24187c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 24287c0e764SRichard Cochran 24387c0e764SRichard Cochran return 0; 24487c0e764SRichard Cochran } 24587c0e764SRichard Cochran 24687c0e764SRichard Cochran static int cpts_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) 24787c0e764SRichard Cochran { 24887c0e764SRichard Cochran unsigned long flags; 24987c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 25087c0e764SRichard Cochran 25187c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 252f25a30beSRichard Cochran timecounter_adjtime(&cpts->tc, delta); 25387c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 25487c0e764SRichard Cochran 25587c0e764SRichard Cochran return 0; 25687c0e764SRichard Cochran } 25787c0e764SRichard Cochran 258856e59abSGrygorii Strashko static int cpts_ptp_gettimeex(struct ptp_clock_info *ptp, 259856e59abSGrygorii Strashko struct timespec64 *ts, 260856e59abSGrygorii Strashko struct ptp_system_timestamp *sts) 26187c0e764SRichard Cochran { 26287c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 263856e59abSGrygorii Strashko unsigned long flags; 264856e59abSGrygorii Strashko u64 ns; 26587c0e764SRichard Cochran 26687c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 267e66dccceSGrygorii Strashko 268856e59abSGrygorii Strashko cpts_update_cur_time(cpts, CPTS_EV_PUSH, sts); 269e66dccceSGrygorii Strashko 27087c0e764SRichard Cochran ns = timecounter_read(&cpts->tc); 27187c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 27287c0e764SRichard Cochran 27384d923ceSRichard Cochran *ts = ns_to_timespec64(ns); 27487c0e764SRichard Cochran 27587c0e764SRichard Cochran return 0; 27687c0e764SRichard Cochran } 27787c0e764SRichard Cochran 27887c0e764SRichard Cochran static int cpts_ptp_settime(struct ptp_clock_info *ptp, 279a5c79c26SRichard Cochran const struct timespec64 *ts) 28087c0e764SRichard Cochran { 28187c0e764SRichard Cochran u64 ns; 28287c0e764SRichard Cochran unsigned long flags; 28387c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 28487c0e764SRichard Cochran 28584d923ceSRichard Cochran ns = timespec64_to_ns(ts); 28687c0e764SRichard Cochran 28787c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 28887c0e764SRichard Cochran timecounter_init(&cpts->tc, &cpts->cc, ns); 28987c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 29087c0e764SRichard Cochran 29187c0e764SRichard Cochran return 0; 29287c0e764SRichard Cochran } 29387c0e764SRichard Cochran 29487c0e764SRichard Cochran static int cpts_ptp_enable(struct ptp_clock_info *ptp, 29587c0e764SRichard Cochran struct ptp_clock_request *rq, int on) 29687c0e764SRichard Cochran { 29787c0e764SRichard Cochran return -EOPNOTSUPP; 29887c0e764SRichard Cochran } 29987c0e764SRichard Cochran 300999f1292SGrygorii Strashko static long cpts_overflow_check(struct ptp_clock_info *ptp) 301999f1292SGrygorii Strashko { 302999f1292SGrygorii Strashko struct cpts *cpts = container_of(ptp, struct cpts, info); 303999f1292SGrygorii Strashko unsigned long delay = cpts->ov_check_period; 3040d5f54feSGrygorii Strashko unsigned long flags; 305e66dccceSGrygorii Strashko u64 ns; 306999f1292SGrygorii Strashko 3070d5f54feSGrygorii Strashko spin_lock_irqsave(&cpts->lock, flags); 308e66dccceSGrygorii Strashko 309856e59abSGrygorii Strashko cpts_update_cur_time(cpts, -1, NULL); 310e66dccceSGrygorii Strashko 311e66dccceSGrygorii Strashko ns = timecounter_read(&cpts->tc); 3120d5f54feSGrygorii Strashko 313f19dcd5fSIvan Khoronzhuk if (!skb_queue_empty(&cpts->txq)) { 314f19dcd5fSIvan Khoronzhuk cpts_purge_txq(cpts); 3150d5f54feSGrygorii Strashko if (!skb_queue_empty(&cpts->txq)) 3160d5f54feSGrygorii Strashko delay = CPTS_SKB_TX_WORK_TIMEOUT; 317f19dcd5fSIvan Khoronzhuk } 3180d5f54feSGrygorii Strashko spin_unlock_irqrestore(&cpts->lock, flags); 3190d5f54feSGrygorii Strashko 320e66dccceSGrygorii Strashko dev_dbg(cpts->dev, "cpts overflow check at %lld\n", ns); 321999f1292SGrygorii Strashko return (long)delay; 322999f1292SGrygorii Strashko } 323999f1292SGrygorii Strashko 324b6d08bd8SBhumika Goyal static const struct ptp_clock_info cpts_info = { 32587c0e764SRichard Cochran .owner = THIS_MODULE, 32687c0e764SRichard Cochran .name = "CTPS timer", 32787c0e764SRichard Cochran .max_adj = 1000000, 32887c0e764SRichard Cochran .n_ext_ts = 0, 3294986b4f0SRichard Cochran .n_pins = 0, 33087c0e764SRichard Cochran .pps = 0, 33187c0e764SRichard Cochran .adjfreq = cpts_ptp_adjfreq, 33287c0e764SRichard Cochran .adjtime = cpts_ptp_adjtime, 333856e59abSGrygorii Strashko .gettimex64 = cpts_ptp_gettimeex, 334a5c79c26SRichard Cochran .settime64 = cpts_ptp_settime, 33587c0e764SRichard Cochran .enable = cpts_ptp_enable, 336999f1292SGrygorii Strashko .do_aux_work = cpts_overflow_check, 33787c0e764SRichard Cochran }; 33887c0e764SRichard Cochran 339*3bfd41b5SGrygorii Strashko static int cpts_skb_get_mtype_seqid(struct sk_buff *skb, u32 *mtype_seqid) 34087c0e764SRichard Cochran { 341*3bfd41b5SGrygorii Strashko unsigned int ptp_class = ptp_classify_raw(skb); 34287c0e764SRichard Cochran u8 *msgtype, *data = skb->data; 343*3bfd41b5SGrygorii Strashko unsigned int offset = 0; 344*3bfd41b5SGrygorii Strashko u16 *seqid; 345*3bfd41b5SGrygorii Strashko 346*3bfd41b5SGrygorii Strashko if (ptp_class == PTP_CLASS_NONE) 347*3bfd41b5SGrygorii Strashko return 0; 34887c0e764SRichard Cochran 349ae5c6c6dSStefan Sørensen if (ptp_class & PTP_CLASS_VLAN) 350ae5c6c6dSStefan Sørensen offset += VLAN_HLEN; 351ae5c6c6dSStefan Sørensen 352ae5c6c6dSStefan Sørensen switch (ptp_class & PTP_CLASS_PMASK) { 353ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV4: 354cca04b28SRichard Cochran offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN; 35587c0e764SRichard Cochran break; 356ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV6: 357ae5c6c6dSStefan Sørensen offset += ETH_HLEN + IP6_HLEN + UDP_HLEN; 35887c0e764SRichard Cochran break; 359ae5c6c6dSStefan Sørensen case PTP_CLASS_L2: 360ae5c6c6dSStefan Sørensen offset += ETH_HLEN; 36187c0e764SRichard Cochran break; 36287c0e764SRichard Cochran default: 36387c0e764SRichard Cochran return 0; 36487c0e764SRichard Cochran } 36587c0e764SRichard Cochran 36687c0e764SRichard Cochran if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid)) 36787c0e764SRichard Cochran return 0; 36887c0e764SRichard Cochran 36987c0e764SRichard Cochran if (unlikely(ptp_class & PTP_CLASS_V1)) 37087c0e764SRichard Cochran msgtype = data + offset + OFF_PTP_CONTROL; 37187c0e764SRichard Cochran else 37287c0e764SRichard Cochran msgtype = data + offset; 37387c0e764SRichard Cochran 37487c0e764SRichard Cochran seqid = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID); 375*3bfd41b5SGrygorii Strashko *mtype_seqid = (*msgtype & MESSAGE_TYPE_MASK) << MESSAGE_TYPE_SHIFT; 376*3bfd41b5SGrygorii Strashko *mtype_seqid |= (ntohs(*seqid) & SEQUENCE_ID_MASK) << SEQUENCE_ID_SHIFT; 37787c0e764SRichard Cochran 378*3bfd41b5SGrygorii Strashko return 1; 37987c0e764SRichard Cochran } 38087c0e764SRichard Cochran 381*3bfd41b5SGrygorii Strashko static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, 382*3bfd41b5SGrygorii Strashko int ev_type, u32 skb_mtype_seqid) 38387c0e764SRichard Cochran { 38487c0e764SRichard Cochran struct list_head *this, *next; 385*3bfd41b5SGrygorii Strashko struct cpts_event *event; 38687c0e764SRichard Cochran unsigned long flags; 387*3bfd41b5SGrygorii Strashko u32 mtype_seqid; 388*3bfd41b5SGrygorii Strashko u64 ns = 0; 38987c0e764SRichard Cochran 39087c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 391a93439ccSGrygorii Strashko cpts_fifo_read(cpts, -1); 39287c0e764SRichard Cochran list_for_each_safe(this, next, &cpts->events) { 39387c0e764SRichard Cochran event = list_entry(this, struct cpts_event, list); 39487c0e764SRichard Cochran if (event_expired(event)) { 39587c0e764SRichard Cochran list_del_init(&event->list); 39687c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 39787c0e764SRichard Cochran continue; 39887c0e764SRichard Cochran } 399*3bfd41b5SGrygorii Strashko 400*3bfd41b5SGrygorii Strashko mtype_seqid = event->high & 401*3bfd41b5SGrygorii Strashko ((MESSAGE_TYPE_MASK << MESSAGE_TYPE_SHIFT) | 402*3bfd41b5SGrygorii Strashko (SEQUENCE_ID_MASK << SEQUENCE_ID_SHIFT) | 403*3bfd41b5SGrygorii Strashko (EVENT_TYPE_MASK << EVENT_TYPE_SHIFT)); 404*3bfd41b5SGrygorii Strashko 405*3bfd41b5SGrygorii Strashko if (mtype_seqid == skb_mtype_seqid) { 406e66dccceSGrygorii Strashko ns = event->timestamp; 40787c0e764SRichard Cochran list_del_init(&event->list); 40887c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 40987c0e764SRichard Cochran break; 41087c0e764SRichard Cochran } 41187c0e764SRichard Cochran } 4120d5f54feSGrygorii Strashko 4130d5f54feSGrygorii Strashko if (ev_type == CPTS_EV_TX && !ns) { 4140d5f54feSGrygorii Strashko struct cpts_skb_cb_data *skb_cb = 4150d5f54feSGrygorii Strashko (struct cpts_skb_cb_data *)skb->cb; 4160d5f54feSGrygorii Strashko /* Not found, add frame to queue for processing later. 4170d5f54feSGrygorii Strashko * The periodic FIFO check will handle this. 4180d5f54feSGrygorii Strashko */ 4190d5f54feSGrygorii Strashko skb_get(skb); 4200d5f54feSGrygorii Strashko /* get the timestamp for timeouts */ 4210d5f54feSGrygorii Strashko skb_cb->tmo = jiffies + msecs_to_jiffies(100); 4220d5f54feSGrygorii Strashko __skb_queue_tail(&cpts->txq, skb); 4230d5f54feSGrygorii Strashko ptp_schedule_worker(cpts->clock, 0); 4240d5f54feSGrygorii Strashko } 42587c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 42687c0e764SRichard Cochran 42787c0e764SRichard Cochran return ns; 42887c0e764SRichard Cochran } 42987c0e764SRichard Cochran 43087c0e764SRichard Cochran void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb) 43187c0e764SRichard Cochran { 432*3bfd41b5SGrygorii Strashko struct cpts_skb_cb_data *skb_cb = (struct cpts_skb_cb_data *)skb->cb; 43387c0e764SRichard Cochran struct skb_shared_hwtstamps *ssh; 434*3bfd41b5SGrygorii Strashko int ret; 435*3bfd41b5SGrygorii Strashko u64 ns; 43687c0e764SRichard Cochran 437*3bfd41b5SGrygorii Strashko ret = cpts_skb_get_mtype_seqid(skb, &skb_cb->skb_mtype_seqid); 438*3bfd41b5SGrygorii Strashko if (!ret) 439*3bfd41b5SGrygorii Strashko return; 440*3bfd41b5SGrygorii Strashko 441*3bfd41b5SGrygorii Strashko skb_cb->skb_mtype_seqid |= (CPTS_EV_RX << EVENT_TYPE_SHIFT); 442*3bfd41b5SGrygorii Strashko 443*3bfd41b5SGrygorii Strashko dev_dbg(cpts->dev, "%s mtype seqid %08x\n", 444*3bfd41b5SGrygorii Strashko __func__, skb_cb->skb_mtype_seqid); 445*3bfd41b5SGrygorii Strashko 446*3bfd41b5SGrygorii Strashko ns = cpts_find_ts(cpts, skb, CPTS_EV_RX, skb_cb->skb_mtype_seqid); 44787c0e764SRichard Cochran if (!ns) 44887c0e764SRichard Cochran return; 44987c0e764SRichard Cochran ssh = skb_hwtstamps(skb); 45087c0e764SRichard Cochran memset(ssh, 0, sizeof(*ssh)); 45187c0e764SRichard Cochran ssh->hwtstamp = ns_to_ktime(ns); 45287c0e764SRichard Cochran } 453c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_rx_timestamp); 45487c0e764SRichard Cochran 45587c0e764SRichard Cochran void cpts_tx_timestamp(struct cpts *cpts, struct sk_buff *skb) 45687c0e764SRichard Cochran { 457*3bfd41b5SGrygorii Strashko struct cpts_skb_cb_data *skb_cb = (struct cpts_skb_cb_data *)skb->cb; 45887c0e764SRichard Cochran struct skb_shared_hwtstamps ssh; 459*3bfd41b5SGrygorii Strashko int ret; 460*3bfd41b5SGrygorii Strashko u64 ns; 46187c0e764SRichard Cochran 46287c0e764SRichard Cochran if (!(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)) 46387c0e764SRichard Cochran return; 464*3bfd41b5SGrygorii Strashko 465*3bfd41b5SGrygorii Strashko ret = cpts_skb_get_mtype_seqid(skb, &skb_cb->skb_mtype_seqid); 466*3bfd41b5SGrygorii Strashko if (!ret) 467*3bfd41b5SGrygorii Strashko return; 468*3bfd41b5SGrygorii Strashko 469*3bfd41b5SGrygorii Strashko skb_cb->skb_mtype_seqid |= (CPTS_EV_TX << EVENT_TYPE_SHIFT); 470*3bfd41b5SGrygorii Strashko 471*3bfd41b5SGrygorii Strashko dev_dbg(cpts->dev, "%s mtype seqid %08x\n", 472*3bfd41b5SGrygorii Strashko __func__, skb_cb->skb_mtype_seqid); 473*3bfd41b5SGrygorii Strashko 474*3bfd41b5SGrygorii Strashko ns = cpts_find_ts(cpts, skb, CPTS_EV_TX, skb_cb->skb_mtype_seqid); 47587c0e764SRichard Cochran if (!ns) 47687c0e764SRichard Cochran return; 47787c0e764SRichard Cochran memset(&ssh, 0, sizeof(ssh)); 47887c0e764SRichard Cochran ssh.hwtstamp = ns_to_ktime(ns); 47987c0e764SRichard Cochran skb_tstamp_tx(skb, &ssh); 48087c0e764SRichard Cochran } 481c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_tx_timestamp); 48287c0e764SRichard Cochran 4838a2c9a5aSGrygorii Strashko int cpts_register(struct cpts *cpts) 48487c0e764SRichard Cochran { 48587c0e764SRichard Cochran int err, i; 48687c0e764SRichard Cochran 4870d5f54feSGrygorii Strashko skb_queue_head_init(&cpts->txq); 48887c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->events); 48987c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->pool); 49087c0e764SRichard Cochran for (i = 0; i < CPTS_MAX_EVENTS; i++) 49187c0e764SRichard Cochran list_add(&cpts->pool_data[i].list, &cpts->pool); 49287c0e764SRichard Cochran 4938a2c9a5aSGrygorii Strashko clk_enable(cpts->refclk); 4948a2c9a5aSGrygorii Strashko 49587c0e764SRichard Cochran cpts_write32(cpts, CPTS_EN, control); 49687c0e764SRichard Cochran cpts_write32(cpts, TS_PEND_EN, int_enable); 49787c0e764SRichard Cochran 498693bd8b7SIvan Khoronzhuk timecounter_init(&cpts->tc, &cpts->cc, ktime_get_real_ns()); 49987c0e764SRichard Cochran 5008a2c9a5aSGrygorii Strashko cpts->clock = ptp_clock_register(&cpts->info, cpts->dev); 5016c691405SGrygorii Strashko if (IS_ERR(cpts->clock)) { 5026c691405SGrygorii Strashko err = PTR_ERR(cpts->clock); 5036c691405SGrygorii Strashko cpts->clock = NULL; 5046c691405SGrygorii Strashko goto err_ptp; 5056c691405SGrygorii Strashko } 5066c691405SGrygorii Strashko cpts->phc_index = ptp_clock_index(cpts->clock); 5076c691405SGrygorii Strashko 508999f1292SGrygorii Strashko ptp_schedule_worker(cpts->clock, cpts->ov_check_period); 50987c0e764SRichard Cochran return 0; 5106c691405SGrygorii Strashko 5116c691405SGrygorii Strashko err_ptp: 5128a2c9a5aSGrygorii Strashko clk_disable(cpts->refclk); 5136c691405SGrygorii Strashko return err; 51487c0e764SRichard Cochran } 515c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_register); 51687c0e764SRichard Cochran 51787c0e764SRichard Cochran void cpts_unregister(struct cpts *cpts) 51887c0e764SRichard Cochran { 5198a2c9a5aSGrygorii Strashko if (WARN_ON(!cpts->clock)) 5208a2c9a5aSGrygorii Strashko return; 5218a2c9a5aSGrygorii Strashko 5228a2c9a5aSGrygorii Strashko ptp_clock_unregister(cpts->clock); 5238a2c9a5aSGrygorii Strashko cpts->clock = NULL; 5248fcd6891SGrygorii Strashko 5258fcd6891SGrygorii Strashko cpts_write32(cpts, 0, int_enable); 5268fcd6891SGrygorii Strashko cpts_write32(cpts, 0, control); 5278fcd6891SGrygorii Strashko 5280d5f54feSGrygorii Strashko /* Drop all packet */ 5290d5f54feSGrygorii Strashko skb_queue_purge(&cpts->txq); 5300d5f54feSGrygorii Strashko 5318a2c9a5aSGrygorii Strashko clk_disable(cpts->refclk); 53287c0e764SRichard Cochran } 533c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_unregister); 534c8395d4eSGrygorii Strashko 53588f0f0b0SGrygorii Strashko static void cpts_calc_mult_shift(struct cpts *cpts) 53688f0f0b0SGrygorii Strashko { 53788f0f0b0SGrygorii Strashko u64 frac, maxsec, ns; 53888f0f0b0SGrygorii Strashko u32 freq; 53988f0f0b0SGrygorii Strashko 54088f0f0b0SGrygorii Strashko freq = clk_get_rate(cpts->refclk); 54188f0f0b0SGrygorii Strashko 54288f0f0b0SGrygorii Strashko /* Calc the maximum number of seconds which we can run before 54388f0f0b0SGrygorii Strashko * wrapping around. 54488f0f0b0SGrygorii Strashko */ 54588f0f0b0SGrygorii Strashko maxsec = cpts->cc.mask; 54688f0f0b0SGrygorii Strashko do_div(maxsec, freq); 54788f0f0b0SGrygorii Strashko /* limit conversation rate to 10 sec as higher values will produce 54888f0f0b0SGrygorii Strashko * too small mult factors and so reduce the conversion accuracy 54988f0f0b0SGrygorii Strashko */ 55088f0f0b0SGrygorii Strashko if (maxsec > 10) 55188f0f0b0SGrygorii Strashko maxsec = 10; 55288f0f0b0SGrygorii Strashko 55320138cf9SGrygorii Strashko /* Calc overflow check period (maxsec / 2) */ 55420138cf9SGrygorii Strashko cpts->ov_check_period = (HZ * maxsec) / 2; 55520138cf9SGrygorii Strashko dev_info(cpts->dev, "cpts: overflow check period %lu (jiffies)\n", 55620138cf9SGrygorii Strashko cpts->ov_check_period); 55720138cf9SGrygorii Strashko 55888f0f0b0SGrygorii Strashko if (cpts->cc.mult || cpts->cc.shift) 55988f0f0b0SGrygorii Strashko return; 56088f0f0b0SGrygorii Strashko 56188f0f0b0SGrygorii Strashko clocks_calc_mult_shift(&cpts->cc.mult, &cpts->cc.shift, 56288f0f0b0SGrygorii Strashko freq, NSEC_PER_SEC, maxsec); 56388f0f0b0SGrygorii Strashko 56488f0f0b0SGrygorii Strashko frac = 0; 56588f0f0b0SGrygorii Strashko ns = cyclecounter_cyc2ns(&cpts->cc, freq, cpts->cc.mask, &frac); 56688f0f0b0SGrygorii Strashko 56788f0f0b0SGrygorii Strashko dev_info(cpts->dev, 56888f0f0b0SGrygorii Strashko "CPTS: ref_clk_freq:%u calc_mult:%u calc_shift:%u error:%lld nsec/sec\n", 56988f0f0b0SGrygorii Strashko freq, cpts->cc.mult, cpts->cc.shift, (ns - NSEC_PER_SEC)); 57088f0f0b0SGrygorii Strashko } 57188f0f0b0SGrygorii Strashko 572a3047a81SGrygorii Strashko static int cpts_of_mux_clk_setup(struct cpts *cpts, struct device_node *node) 573a3047a81SGrygorii Strashko { 574a3047a81SGrygorii Strashko struct device_node *refclk_np; 575a3047a81SGrygorii Strashko const char **parent_names; 576a3047a81SGrygorii Strashko unsigned int num_parents; 577a3047a81SGrygorii Strashko struct clk_hw *clk_hw; 578a3047a81SGrygorii Strashko int ret = -EINVAL; 579a3047a81SGrygorii Strashko u32 *mux_table; 580a3047a81SGrygorii Strashko 581a3047a81SGrygorii Strashko refclk_np = of_get_child_by_name(node, "cpts-refclk-mux"); 582a3047a81SGrygorii Strashko if (!refclk_np) 583a3047a81SGrygorii Strashko /* refclk selection supported not for all SoCs */ 584a3047a81SGrygorii Strashko return 0; 585a3047a81SGrygorii Strashko 586a3047a81SGrygorii Strashko num_parents = of_clk_get_parent_count(refclk_np); 587a3047a81SGrygorii Strashko if (num_parents < 1) { 588a3047a81SGrygorii Strashko dev_err(cpts->dev, "mux-clock %s must have parents\n", 589a3047a81SGrygorii Strashko refclk_np->name); 590a3047a81SGrygorii Strashko goto mux_fail; 591a3047a81SGrygorii Strashko } 592a3047a81SGrygorii Strashko 593a3047a81SGrygorii Strashko parent_names = devm_kzalloc(cpts->dev, (sizeof(char *) * num_parents), 594a3047a81SGrygorii Strashko GFP_KERNEL); 595a3047a81SGrygorii Strashko 596a3047a81SGrygorii Strashko mux_table = devm_kzalloc(cpts->dev, sizeof(*mux_table) * num_parents, 597a3047a81SGrygorii Strashko GFP_KERNEL); 598a3047a81SGrygorii Strashko if (!mux_table || !parent_names) { 599a3047a81SGrygorii Strashko ret = -ENOMEM; 600a3047a81SGrygorii Strashko goto mux_fail; 601a3047a81SGrygorii Strashko } 602a3047a81SGrygorii Strashko 603a3047a81SGrygorii Strashko of_clk_parent_fill(refclk_np, parent_names, num_parents); 604a3047a81SGrygorii Strashko 605a3047a81SGrygorii Strashko ret = of_property_read_variable_u32_array(refclk_np, "ti,mux-tbl", 606a3047a81SGrygorii Strashko mux_table, 607a3047a81SGrygorii Strashko num_parents, num_parents); 608a3047a81SGrygorii Strashko if (ret < 0) 609a3047a81SGrygorii Strashko goto mux_fail; 610a3047a81SGrygorii Strashko 611a3047a81SGrygorii Strashko clk_hw = clk_hw_register_mux_table(cpts->dev, refclk_np->name, 612a3047a81SGrygorii Strashko parent_names, num_parents, 613a3047a81SGrygorii Strashko 0, 614a3047a81SGrygorii Strashko &cpts->reg->rftclk_sel, 0, 0x1F, 615a3047a81SGrygorii Strashko 0, mux_table, NULL); 616a3047a81SGrygorii Strashko if (IS_ERR(clk_hw)) { 617a3047a81SGrygorii Strashko ret = PTR_ERR(clk_hw); 618a3047a81SGrygorii Strashko goto mux_fail; 619a3047a81SGrygorii Strashko } 620a3047a81SGrygorii Strashko 621a3047a81SGrygorii Strashko ret = devm_add_action_or_reset(cpts->dev, 622a3047a81SGrygorii Strashko (void(*)(void *))clk_hw_unregister_mux, 623a3047a81SGrygorii Strashko clk_hw); 624a3047a81SGrygorii Strashko if (ret) { 625a3047a81SGrygorii Strashko dev_err(cpts->dev, "add clkmux unreg action %d", ret); 626a3047a81SGrygorii Strashko goto mux_fail; 627a3047a81SGrygorii Strashko } 628a3047a81SGrygorii Strashko 629a3047a81SGrygorii Strashko ret = of_clk_add_hw_provider(refclk_np, of_clk_hw_simple_get, clk_hw); 630a3047a81SGrygorii Strashko if (ret) 631a3047a81SGrygorii Strashko goto mux_fail; 632a3047a81SGrygorii Strashko 633a3047a81SGrygorii Strashko ret = devm_add_action_or_reset(cpts->dev, 634a3047a81SGrygorii Strashko (void(*)(void *))of_clk_del_provider, 635a3047a81SGrygorii Strashko refclk_np); 636a3047a81SGrygorii Strashko if (ret) { 637a3047a81SGrygorii Strashko dev_err(cpts->dev, "add clkmux provider unreg action %d", ret); 638a3047a81SGrygorii Strashko goto mux_fail; 639a3047a81SGrygorii Strashko } 640a3047a81SGrygorii Strashko 641a3047a81SGrygorii Strashko return ret; 642a3047a81SGrygorii Strashko 643a3047a81SGrygorii Strashko mux_fail: 644a3047a81SGrygorii Strashko of_node_put(refclk_np); 645a3047a81SGrygorii Strashko return ret; 646a3047a81SGrygorii Strashko } 647a3047a81SGrygorii Strashko 6484a88fb95SGrygorii Strashko static int cpts_of_parse(struct cpts *cpts, struct device_node *node) 6494a88fb95SGrygorii Strashko { 6504a88fb95SGrygorii Strashko int ret = -EINVAL; 6514a88fb95SGrygorii Strashko u32 prop; 6524a88fb95SGrygorii Strashko 65388f0f0b0SGrygorii Strashko if (!of_property_read_u32(node, "cpts_clock_mult", &prop)) 6544a88fb95SGrygorii Strashko cpts->cc.mult = prop; 6554a88fb95SGrygorii Strashko 65688f0f0b0SGrygorii Strashko if (!of_property_read_u32(node, "cpts_clock_shift", &prop)) 6574a88fb95SGrygorii Strashko cpts->cc.shift = prop; 6584a88fb95SGrygorii Strashko 65988f0f0b0SGrygorii Strashko if ((cpts->cc.mult && !cpts->cc.shift) || 66088f0f0b0SGrygorii Strashko (!cpts->cc.mult && cpts->cc.shift)) 66188f0f0b0SGrygorii Strashko goto of_error; 66288f0f0b0SGrygorii Strashko 663a3047a81SGrygorii Strashko return cpts_of_mux_clk_setup(cpts, node); 6644a88fb95SGrygorii Strashko 6654a88fb95SGrygorii Strashko of_error: 6664a88fb95SGrygorii Strashko dev_err(cpts->dev, "CPTS: Missing property in the DT.\n"); 6674a88fb95SGrygorii Strashko return ret; 6684a88fb95SGrygorii Strashko } 6694a88fb95SGrygorii Strashko 6708a2c9a5aSGrygorii Strashko struct cpts *cpts_create(struct device *dev, void __iomem *regs, 6714a88fb95SGrygorii Strashko struct device_node *node) 6728a2c9a5aSGrygorii Strashko { 6738a2c9a5aSGrygorii Strashko struct cpts *cpts; 6744a88fb95SGrygorii Strashko int ret; 6758a2c9a5aSGrygorii Strashko 6768a2c9a5aSGrygorii Strashko cpts = devm_kzalloc(dev, sizeof(*cpts), GFP_KERNEL); 6778a2c9a5aSGrygorii Strashko if (!cpts) 6788a2c9a5aSGrygorii Strashko return ERR_PTR(-ENOMEM); 6798a2c9a5aSGrygorii Strashko 6808a2c9a5aSGrygorii Strashko cpts->dev = dev; 6818a2c9a5aSGrygorii Strashko cpts->reg = (struct cpsw_cpts __iomem *)regs; 6828a2c9a5aSGrygorii Strashko spin_lock_init(&cpts->lock); 6838a2c9a5aSGrygorii Strashko 6844a88fb95SGrygorii Strashko ret = cpts_of_parse(cpts, node); 6854a88fb95SGrygorii Strashko if (ret) 6864a88fb95SGrygorii Strashko return ERR_PTR(ret); 6874a88fb95SGrygorii Strashko 6888a6389a5SGrygorii Strashko cpts->refclk = devm_get_clk_from_child(dev, node, "cpts"); 6898a6389a5SGrygorii Strashko if (IS_ERR(cpts->refclk)) 6908a6389a5SGrygorii Strashko /* try get clk from dev node for compatibility */ 6918a2c9a5aSGrygorii Strashko cpts->refclk = devm_clk_get(dev, "cpts"); 6928a6389a5SGrygorii Strashko 6938a2c9a5aSGrygorii Strashko if (IS_ERR(cpts->refclk)) { 6948a6389a5SGrygorii Strashko dev_err(dev, "Failed to get cpts refclk %ld\n", 6958a6389a5SGrygorii Strashko PTR_ERR(cpts->refclk)); 696bde4c563SHernán Gonzalez return ERR_CAST(cpts->refclk); 6978a2c9a5aSGrygorii Strashko } 6988a2c9a5aSGrygorii Strashko 6992d822f2dSKangjie Lu ret = clk_prepare(cpts->refclk); 7002d822f2dSKangjie Lu if (ret) 7012d822f2dSKangjie Lu return ERR_PTR(ret); 7028a2c9a5aSGrygorii Strashko 7038a2c9a5aSGrygorii Strashko cpts->cc.read = cpts_systim_read; 7048a2c9a5aSGrygorii Strashko cpts->cc.mask = CLOCKSOURCE_MASK(32); 70588f0f0b0SGrygorii Strashko cpts->info = cpts_info; 70688f0f0b0SGrygorii Strashko 70788f0f0b0SGrygorii Strashko cpts_calc_mult_shift(cpts); 7084a88fb95SGrygorii Strashko /* save cc.mult original value as it can be modified 7094a88fb95SGrygorii Strashko * by cpts_ptp_adjfreq(). 7104a88fb95SGrygorii Strashko */ 7114a88fb95SGrygorii Strashko cpts->cc_mult = cpts->cc.mult; 7128a2c9a5aSGrygorii Strashko 7138a2c9a5aSGrygorii Strashko return cpts; 7148a2c9a5aSGrygorii Strashko } 7158a2c9a5aSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_create); 7168a2c9a5aSGrygorii Strashko 7178a2c9a5aSGrygorii Strashko void cpts_release(struct cpts *cpts) 7188a2c9a5aSGrygorii Strashko { 7198a2c9a5aSGrygorii Strashko if (!cpts) 7208a2c9a5aSGrygorii Strashko return; 7218a2c9a5aSGrygorii Strashko 7228a2c9a5aSGrygorii Strashko if (WARN_ON(!cpts->refclk)) 7238a2c9a5aSGrygorii Strashko return; 7248a2c9a5aSGrygorii Strashko 7258a2c9a5aSGrygorii Strashko clk_unprepare(cpts->refclk); 7268a2c9a5aSGrygorii Strashko } 7278a2c9a5aSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_release); 7288a2c9a5aSGrygorii Strashko 729c8395d4eSGrygorii Strashko MODULE_LICENSE("GPL v2"); 730c8395d4eSGrygorii Strashko MODULE_DESCRIPTION("TI CPTS driver"); 731c8395d4eSGrygorii Strashko MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>"); 732