187c0e764SRichard Cochran /* 287c0e764SRichard Cochran * TI Common Platform Time Sync 387c0e764SRichard Cochran * 487c0e764SRichard Cochran * Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com> 587c0e764SRichard Cochran * 687c0e764SRichard Cochran * This program is free software; you can redistribute it and/or modify 787c0e764SRichard Cochran * it under the terms of the GNU General Public License as published by 887c0e764SRichard Cochran * the Free Software Foundation; either version 2 of the License, or 987c0e764SRichard Cochran * (at your option) any later version. 1087c0e764SRichard Cochran * 1187c0e764SRichard Cochran * This program is distributed in the hope that it will be useful, 1287c0e764SRichard Cochran * but WITHOUT ANY WARRANTY; without even the implied warranty of 1387c0e764SRichard Cochran * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1487c0e764SRichard Cochran * GNU General Public License for more details. 1587c0e764SRichard Cochran * 1687c0e764SRichard Cochran * You should have received a copy of the GNU General Public License 1787c0e764SRichard Cochran * along with this program; if not, write to the Free Software 1887c0e764SRichard Cochran * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 1987c0e764SRichard Cochran */ 2087c0e764SRichard Cochran #include <linux/err.h> 2187c0e764SRichard Cochran #include <linux/if.h> 2287c0e764SRichard Cochran #include <linux/hrtimer.h> 2387c0e764SRichard Cochran #include <linux/module.h> 2487c0e764SRichard Cochran #include <linux/net_tstamp.h> 2587c0e764SRichard Cochran #include <linux/ptp_classify.h> 2687c0e764SRichard Cochran #include <linux/time.h> 2787c0e764SRichard Cochran #include <linux/uaccess.h> 2887c0e764SRichard Cochran #include <linux/workqueue.h> 2979eb9d28SAlexei Starovoitov #include <linux/if_ether.h> 3079eb9d28SAlexei Starovoitov #include <linux/if_vlan.h> 3187c0e764SRichard Cochran 3287c0e764SRichard Cochran #include "cpts.h" 3387c0e764SRichard Cochran 340d5f54feSGrygorii Strashko #define CPTS_SKB_TX_WORK_TIMEOUT 1 /* jiffies */ 350d5f54feSGrygorii Strashko 360d5f54feSGrygorii Strashko struct cpts_skb_cb_data { 370d5f54feSGrygorii Strashko unsigned long tmo; 380d5f54feSGrygorii Strashko }; 390d5f54feSGrygorii Strashko 40391fd6caSGrygorii Strashko #define cpts_read32(c, r) readl_relaxed(&c->reg->r) 41391fd6caSGrygorii Strashko #define cpts_write32(c, v, r) writel_relaxed(v, &c->reg->r) 4287c0e764SRichard Cochran 430d5f54feSGrygorii Strashko static int cpts_match(struct sk_buff *skb, unsigned int ptp_class, 440d5f54feSGrygorii Strashko u16 ts_seqid, u8 ts_msgtype); 450d5f54feSGrygorii Strashko 4687c0e764SRichard Cochran static int event_expired(struct cpts_event *event) 4787c0e764SRichard Cochran { 4887c0e764SRichard Cochran return time_after(jiffies, event->tmo); 4987c0e764SRichard Cochran } 5087c0e764SRichard Cochran 5187c0e764SRichard Cochran static int event_type(struct cpts_event *event) 5287c0e764SRichard Cochran { 5387c0e764SRichard Cochran return (event->high >> EVENT_TYPE_SHIFT) & EVENT_TYPE_MASK; 5487c0e764SRichard Cochran } 5587c0e764SRichard Cochran 5687c0e764SRichard Cochran static int cpts_fifo_pop(struct cpts *cpts, u32 *high, u32 *low) 5787c0e764SRichard Cochran { 5887c0e764SRichard Cochran u32 r = cpts_read32(cpts, intstat_raw); 5987c0e764SRichard Cochran 6087c0e764SRichard Cochran if (r & TS_PEND_RAW) { 6187c0e764SRichard Cochran *high = cpts_read32(cpts, event_high); 6287c0e764SRichard Cochran *low = cpts_read32(cpts, event_low); 6387c0e764SRichard Cochran cpts_write32(cpts, EVENT_POP, event_pop); 6487c0e764SRichard Cochran return 0; 6587c0e764SRichard Cochran } 6687c0e764SRichard Cochran return -1; 6787c0e764SRichard Cochran } 6887c0e764SRichard Cochran 69e4439fa8SWingMan Kwok static int cpts_purge_events(struct cpts *cpts) 70e4439fa8SWingMan Kwok { 71e4439fa8SWingMan Kwok struct list_head *this, *next; 72e4439fa8SWingMan Kwok struct cpts_event *event; 73e4439fa8SWingMan Kwok int removed = 0; 74e4439fa8SWingMan Kwok 75e4439fa8SWingMan Kwok list_for_each_safe(this, next, &cpts->events) { 76e4439fa8SWingMan Kwok event = list_entry(this, struct cpts_event, list); 77e4439fa8SWingMan Kwok if (event_expired(event)) { 78e4439fa8SWingMan Kwok list_del_init(&event->list); 79e4439fa8SWingMan Kwok list_add(&event->list, &cpts->pool); 80e4439fa8SWingMan Kwok ++removed; 81e4439fa8SWingMan Kwok } 82e4439fa8SWingMan Kwok } 83e4439fa8SWingMan Kwok 84e4439fa8SWingMan Kwok if (removed) 85e4439fa8SWingMan Kwok pr_debug("cpts: event pool cleaned up %d\n", removed); 86e4439fa8SWingMan Kwok return removed ? 0 : -1; 87e4439fa8SWingMan Kwok } 88e4439fa8SWingMan Kwok 89*f19dcd5fSIvan Khoronzhuk static void cpts_purge_txq(struct cpts *cpts) 90*f19dcd5fSIvan Khoronzhuk { 91*f19dcd5fSIvan Khoronzhuk struct cpts_skb_cb_data *skb_cb; 92*f19dcd5fSIvan Khoronzhuk struct sk_buff *skb, *tmp; 93*f19dcd5fSIvan Khoronzhuk int removed = 0; 94*f19dcd5fSIvan Khoronzhuk 95*f19dcd5fSIvan Khoronzhuk skb_queue_walk_safe(&cpts->txq, skb, tmp) { 96*f19dcd5fSIvan Khoronzhuk skb_cb = (struct cpts_skb_cb_data *)skb->cb; 97*f19dcd5fSIvan Khoronzhuk if (time_after(jiffies, skb_cb->tmo)) { 98*f19dcd5fSIvan Khoronzhuk __skb_unlink(skb, &cpts->txq); 99*f19dcd5fSIvan Khoronzhuk dev_consume_skb_any(skb); 100*f19dcd5fSIvan Khoronzhuk ++removed; 101*f19dcd5fSIvan Khoronzhuk } 102*f19dcd5fSIvan Khoronzhuk } 103*f19dcd5fSIvan Khoronzhuk 104*f19dcd5fSIvan Khoronzhuk if (removed) 105*f19dcd5fSIvan Khoronzhuk dev_dbg(cpts->dev, "txq cleaned up %d\n", removed); 106*f19dcd5fSIvan Khoronzhuk } 107*f19dcd5fSIvan Khoronzhuk 1080d5f54feSGrygorii Strashko static bool cpts_match_tx_ts(struct cpts *cpts, struct cpts_event *event) 1090d5f54feSGrygorii Strashko { 1100d5f54feSGrygorii Strashko struct sk_buff *skb, *tmp; 1110d5f54feSGrygorii Strashko u16 seqid; 1120d5f54feSGrygorii Strashko u8 mtype; 1130d5f54feSGrygorii Strashko bool found = false; 1140d5f54feSGrygorii Strashko 1150d5f54feSGrygorii Strashko mtype = (event->high >> MESSAGE_TYPE_SHIFT) & MESSAGE_TYPE_MASK; 1160d5f54feSGrygorii Strashko seqid = (event->high >> SEQUENCE_ID_SHIFT) & SEQUENCE_ID_MASK; 1170d5f54feSGrygorii Strashko 1180d5f54feSGrygorii Strashko /* no need to grab txq.lock as access is always done under cpts->lock */ 1190d5f54feSGrygorii Strashko skb_queue_walk_safe(&cpts->txq, skb, tmp) { 1200d5f54feSGrygorii Strashko struct skb_shared_hwtstamps ssh; 1210d5f54feSGrygorii Strashko unsigned int class = ptp_classify_raw(skb); 1220d5f54feSGrygorii Strashko struct cpts_skb_cb_data *skb_cb = 1230d5f54feSGrygorii Strashko (struct cpts_skb_cb_data *)skb->cb; 1240d5f54feSGrygorii Strashko 1250d5f54feSGrygorii Strashko if (cpts_match(skb, class, seqid, mtype)) { 1260d5f54feSGrygorii Strashko u64 ns = timecounter_cyc2time(&cpts->tc, event->low); 1270d5f54feSGrygorii Strashko 1280d5f54feSGrygorii Strashko memset(&ssh, 0, sizeof(ssh)); 1290d5f54feSGrygorii Strashko ssh.hwtstamp = ns_to_ktime(ns); 1300d5f54feSGrygorii Strashko skb_tstamp_tx(skb, &ssh); 1310d5f54feSGrygorii Strashko found = true; 1320d5f54feSGrygorii Strashko __skb_unlink(skb, &cpts->txq); 1330d5f54feSGrygorii Strashko dev_consume_skb_any(skb); 1340d5f54feSGrygorii Strashko dev_dbg(cpts->dev, "match tx timestamp mtype %u seqid %04x\n", 1350d5f54feSGrygorii Strashko mtype, seqid); 136d0c694fcSIvan Khoronzhuk break; 137d0c694fcSIvan Khoronzhuk } 138d0c694fcSIvan Khoronzhuk 139d0c694fcSIvan Khoronzhuk if (time_after(jiffies, skb_cb->tmo)) { 1400d5f54feSGrygorii Strashko /* timeout any expired skbs over 1s */ 141d0e14c4dSIvan Khoronzhuk dev_dbg(cpts->dev, "expiring tx timestamp from txq\n"); 1420d5f54feSGrygorii Strashko __skb_unlink(skb, &cpts->txq); 1430d5f54feSGrygorii Strashko dev_consume_skb_any(skb); 1440d5f54feSGrygorii Strashko } 1450d5f54feSGrygorii Strashko } 1460d5f54feSGrygorii Strashko 1470d5f54feSGrygorii Strashko return found; 1480d5f54feSGrygorii Strashko } 1490d5f54feSGrygorii Strashko 15087c0e764SRichard Cochran /* 15187c0e764SRichard Cochran * Returns zero if matching event type was found. 15287c0e764SRichard Cochran */ 15387c0e764SRichard Cochran static int cpts_fifo_read(struct cpts *cpts, int match) 15487c0e764SRichard Cochran { 15587c0e764SRichard Cochran int i, type = -1; 15687c0e764SRichard Cochran u32 hi, lo; 15787c0e764SRichard Cochran struct cpts_event *event; 15887c0e764SRichard Cochran 15987c0e764SRichard Cochran for (i = 0; i < CPTS_FIFO_DEPTH; i++) { 16087c0e764SRichard Cochran if (cpts_fifo_pop(cpts, &hi, &lo)) 16187c0e764SRichard Cochran break; 162e4439fa8SWingMan Kwok 163e4439fa8SWingMan Kwok if (list_empty(&cpts->pool) && cpts_purge_events(cpts)) { 164e4439fa8SWingMan Kwok pr_err("cpts: event pool empty\n"); 16587c0e764SRichard Cochran return -1; 16687c0e764SRichard Cochran } 167e4439fa8SWingMan Kwok 16887c0e764SRichard Cochran event = list_first_entry(&cpts->pool, struct cpts_event, list); 16987c0e764SRichard Cochran event->tmo = jiffies + 2; 17087c0e764SRichard Cochran event->high = hi; 17187c0e764SRichard Cochran event->low = lo; 17287c0e764SRichard Cochran type = event_type(event); 17387c0e764SRichard Cochran switch (type) { 1740d5f54feSGrygorii Strashko case CPTS_EV_TX: 1750d5f54feSGrygorii Strashko if (cpts_match_tx_ts(cpts, event)) { 1760d5f54feSGrygorii Strashko /* if the new event matches an existing skb, 1770d5f54feSGrygorii Strashko * then don't queue it 1780d5f54feSGrygorii Strashko */ 1790d5f54feSGrygorii Strashko break; 1800d5f54feSGrygorii Strashko } 181e38c2e11SGustavo A. R. Silva /* fall through */ 18287c0e764SRichard Cochran case CPTS_EV_PUSH: 18387c0e764SRichard Cochran case CPTS_EV_RX: 18487c0e764SRichard Cochran list_del_init(&event->list); 18587c0e764SRichard Cochran list_add_tail(&event->list, &cpts->events); 18687c0e764SRichard Cochran break; 18787c0e764SRichard Cochran case CPTS_EV_ROLL: 18887c0e764SRichard Cochran case CPTS_EV_HALF: 18987c0e764SRichard Cochran case CPTS_EV_HW: 19087c0e764SRichard Cochran break; 19187c0e764SRichard Cochran default: 19207f42258SMasanari Iida pr_err("cpts: unknown event type\n"); 19387c0e764SRichard Cochran break; 19487c0e764SRichard Cochran } 19587c0e764SRichard Cochran if (type == match) 19687c0e764SRichard Cochran break; 19787c0e764SRichard Cochran } 19887c0e764SRichard Cochran return type == match ? 0 : -1; 19987c0e764SRichard Cochran } 20087c0e764SRichard Cochran 201a5a1d1c2SThomas Gleixner static u64 cpts_systim_read(const struct cyclecounter *cc) 20287c0e764SRichard Cochran { 20387c0e764SRichard Cochran u64 val = 0; 20487c0e764SRichard Cochran struct cpts_event *event; 20587c0e764SRichard Cochran struct list_head *this, *next; 20687c0e764SRichard Cochran struct cpts *cpts = container_of(cc, struct cpts, cc); 20787c0e764SRichard Cochran 20887c0e764SRichard Cochran cpts_write32(cpts, TS_PUSH, ts_push); 20987c0e764SRichard Cochran if (cpts_fifo_read(cpts, CPTS_EV_PUSH)) 21087c0e764SRichard Cochran pr_err("cpts: unable to obtain a time stamp\n"); 21187c0e764SRichard Cochran 21287c0e764SRichard Cochran list_for_each_safe(this, next, &cpts->events) { 21387c0e764SRichard Cochran event = list_entry(this, struct cpts_event, list); 21487c0e764SRichard Cochran if (event_type(event) == CPTS_EV_PUSH) { 21587c0e764SRichard Cochran list_del_init(&event->list); 21687c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 21787c0e764SRichard Cochran val = event->low; 21887c0e764SRichard Cochran break; 21987c0e764SRichard Cochran } 22087c0e764SRichard Cochran } 22187c0e764SRichard Cochran 22287c0e764SRichard Cochran return val; 22387c0e764SRichard Cochran } 22487c0e764SRichard Cochran 22587c0e764SRichard Cochran /* PTP clock operations */ 22687c0e764SRichard Cochran 22787c0e764SRichard Cochran static int cpts_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) 22887c0e764SRichard Cochran { 22987c0e764SRichard Cochran u64 adj; 23087c0e764SRichard Cochran u32 diff, mult; 23187c0e764SRichard Cochran int neg_adj = 0; 23287c0e764SRichard Cochran unsigned long flags; 23387c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 23487c0e764SRichard Cochran 23587c0e764SRichard Cochran if (ppb < 0) { 23687c0e764SRichard Cochran neg_adj = 1; 23787c0e764SRichard Cochran ppb = -ppb; 23887c0e764SRichard Cochran } 23987c0e764SRichard Cochran mult = cpts->cc_mult; 24087c0e764SRichard Cochran adj = mult; 24187c0e764SRichard Cochran adj *= ppb; 24287c0e764SRichard Cochran diff = div_u64(adj, 1000000000ULL); 24387c0e764SRichard Cochran 24487c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 24587c0e764SRichard Cochran 24687c0e764SRichard Cochran timecounter_read(&cpts->tc); 24787c0e764SRichard Cochran 24887c0e764SRichard Cochran cpts->cc.mult = neg_adj ? mult - diff : mult + diff; 24987c0e764SRichard Cochran 25087c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 25187c0e764SRichard Cochran 25287c0e764SRichard Cochran return 0; 25387c0e764SRichard Cochran } 25487c0e764SRichard Cochran 25587c0e764SRichard Cochran static int cpts_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) 25687c0e764SRichard Cochran { 25787c0e764SRichard Cochran unsigned long flags; 25887c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 25987c0e764SRichard Cochran 26087c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 261f25a30beSRichard Cochran timecounter_adjtime(&cpts->tc, delta); 26287c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 26387c0e764SRichard Cochran 26487c0e764SRichard Cochran return 0; 26587c0e764SRichard Cochran } 26687c0e764SRichard Cochran 267a5c79c26SRichard Cochran static int cpts_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) 26887c0e764SRichard Cochran { 26987c0e764SRichard Cochran u64 ns; 27087c0e764SRichard Cochran unsigned long flags; 27187c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 27287c0e764SRichard Cochran 27387c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 27487c0e764SRichard Cochran ns = timecounter_read(&cpts->tc); 27587c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 27687c0e764SRichard Cochran 27784d923ceSRichard Cochran *ts = ns_to_timespec64(ns); 27887c0e764SRichard Cochran 27987c0e764SRichard Cochran return 0; 28087c0e764SRichard Cochran } 28187c0e764SRichard Cochran 28287c0e764SRichard Cochran static int cpts_ptp_settime(struct ptp_clock_info *ptp, 283a5c79c26SRichard Cochran const struct timespec64 *ts) 28487c0e764SRichard Cochran { 28587c0e764SRichard Cochran u64 ns; 28687c0e764SRichard Cochran unsigned long flags; 28787c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 28887c0e764SRichard Cochran 28984d923ceSRichard Cochran ns = timespec64_to_ns(ts); 29087c0e764SRichard Cochran 29187c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 29287c0e764SRichard Cochran timecounter_init(&cpts->tc, &cpts->cc, ns); 29387c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 29487c0e764SRichard Cochran 29587c0e764SRichard Cochran return 0; 29687c0e764SRichard Cochran } 29787c0e764SRichard Cochran 29887c0e764SRichard Cochran static int cpts_ptp_enable(struct ptp_clock_info *ptp, 29987c0e764SRichard Cochran struct ptp_clock_request *rq, int on) 30087c0e764SRichard Cochran { 30187c0e764SRichard Cochran return -EOPNOTSUPP; 30287c0e764SRichard Cochran } 30387c0e764SRichard Cochran 304999f1292SGrygorii Strashko static long cpts_overflow_check(struct ptp_clock_info *ptp) 305999f1292SGrygorii Strashko { 306999f1292SGrygorii Strashko struct cpts *cpts = container_of(ptp, struct cpts, info); 307999f1292SGrygorii Strashko unsigned long delay = cpts->ov_check_period; 308999f1292SGrygorii Strashko struct timespec64 ts; 3090d5f54feSGrygorii Strashko unsigned long flags; 310999f1292SGrygorii Strashko 3110d5f54feSGrygorii Strashko spin_lock_irqsave(&cpts->lock, flags); 3120d5f54feSGrygorii Strashko ts = ns_to_timespec64(timecounter_read(&cpts->tc)); 3130d5f54feSGrygorii Strashko 314*f19dcd5fSIvan Khoronzhuk if (!skb_queue_empty(&cpts->txq)) { 315*f19dcd5fSIvan Khoronzhuk cpts_purge_txq(cpts); 3160d5f54feSGrygorii Strashko if (!skb_queue_empty(&cpts->txq)) 3170d5f54feSGrygorii Strashko delay = CPTS_SKB_TX_WORK_TIMEOUT; 318*f19dcd5fSIvan Khoronzhuk } 3190d5f54feSGrygorii Strashko spin_unlock_irqrestore(&cpts->lock, flags); 3200d5f54feSGrygorii Strashko 321ea5ec9fcSFlorian Fainelli pr_debug("cpts overflow check at %lld.%09ld\n", 322ea5ec9fcSFlorian Fainelli (long long)ts.tv_sec, ts.tv_nsec); 323999f1292SGrygorii Strashko return (long)delay; 324999f1292SGrygorii Strashko } 325999f1292SGrygorii Strashko 326b6d08bd8SBhumika Goyal static const struct ptp_clock_info cpts_info = { 32787c0e764SRichard Cochran .owner = THIS_MODULE, 32887c0e764SRichard Cochran .name = "CTPS timer", 32987c0e764SRichard Cochran .max_adj = 1000000, 33087c0e764SRichard Cochran .n_ext_ts = 0, 3314986b4f0SRichard Cochran .n_pins = 0, 33287c0e764SRichard Cochran .pps = 0, 33387c0e764SRichard Cochran .adjfreq = cpts_ptp_adjfreq, 33487c0e764SRichard Cochran .adjtime = cpts_ptp_adjtime, 335a5c79c26SRichard Cochran .gettime64 = cpts_ptp_gettime, 336a5c79c26SRichard Cochran .settime64 = cpts_ptp_settime, 33787c0e764SRichard Cochran .enable = cpts_ptp_enable, 338999f1292SGrygorii Strashko .do_aux_work = cpts_overflow_check, 33987c0e764SRichard Cochran }; 34087c0e764SRichard Cochran 34187c0e764SRichard Cochran static int cpts_match(struct sk_buff *skb, unsigned int ptp_class, 34287c0e764SRichard Cochran u16 ts_seqid, u8 ts_msgtype) 34387c0e764SRichard Cochran { 34487c0e764SRichard Cochran u16 *seqid; 345ae5c6c6dSStefan Sørensen unsigned int offset = 0; 34687c0e764SRichard Cochran u8 *msgtype, *data = skb->data; 34787c0e764SRichard Cochran 348ae5c6c6dSStefan Sørensen if (ptp_class & PTP_CLASS_VLAN) 349ae5c6c6dSStefan Sørensen offset += VLAN_HLEN; 350ae5c6c6dSStefan Sørensen 351ae5c6c6dSStefan Sørensen switch (ptp_class & PTP_CLASS_PMASK) { 352ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV4: 353cca04b28SRichard Cochran offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN; 35487c0e764SRichard Cochran break; 355ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV6: 356ae5c6c6dSStefan Sørensen offset += ETH_HLEN + IP6_HLEN + UDP_HLEN; 35787c0e764SRichard Cochran break; 358ae5c6c6dSStefan Sørensen case PTP_CLASS_L2: 359ae5c6c6dSStefan Sørensen offset += ETH_HLEN; 36087c0e764SRichard Cochran break; 36187c0e764SRichard Cochran default: 36287c0e764SRichard Cochran return 0; 36387c0e764SRichard Cochran } 36487c0e764SRichard Cochran 36587c0e764SRichard Cochran if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid)) 36687c0e764SRichard Cochran return 0; 36787c0e764SRichard Cochran 36887c0e764SRichard Cochran if (unlikely(ptp_class & PTP_CLASS_V1)) 36987c0e764SRichard Cochran msgtype = data + offset + OFF_PTP_CONTROL; 37087c0e764SRichard Cochran else 37187c0e764SRichard Cochran msgtype = data + offset; 37287c0e764SRichard Cochran 37387c0e764SRichard Cochran seqid = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID); 37487c0e764SRichard Cochran 37587c0e764SRichard Cochran return (ts_msgtype == (*msgtype & 0xf) && ts_seqid == ntohs(*seqid)); 37687c0e764SRichard Cochran } 37787c0e764SRichard Cochran 37887c0e764SRichard Cochran static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, int ev_type) 37987c0e764SRichard Cochran { 38087c0e764SRichard Cochran u64 ns = 0; 38187c0e764SRichard Cochran struct cpts_event *event; 38287c0e764SRichard Cochran struct list_head *this, *next; 383164d8c66SDaniel Borkmann unsigned int class = ptp_classify_raw(skb); 38487c0e764SRichard Cochran unsigned long flags; 38587c0e764SRichard Cochran u16 seqid; 38687c0e764SRichard Cochran u8 mtype; 38787c0e764SRichard Cochran 38887c0e764SRichard Cochran if (class == PTP_CLASS_NONE) 38987c0e764SRichard Cochran return 0; 39087c0e764SRichard Cochran 39187c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 392a93439ccSGrygorii Strashko cpts_fifo_read(cpts, -1); 39387c0e764SRichard Cochran list_for_each_safe(this, next, &cpts->events) { 39487c0e764SRichard Cochran event = list_entry(this, struct cpts_event, list); 39587c0e764SRichard Cochran if (event_expired(event)) { 39687c0e764SRichard Cochran list_del_init(&event->list); 39787c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 39887c0e764SRichard Cochran continue; 39987c0e764SRichard Cochran } 40087c0e764SRichard Cochran mtype = (event->high >> MESSAGE_TYPE_SHIFT) & MESSAGE_TYPE_MASK; 40187c0e764SRichard Cochran seqid = (event->high >> SEQUENCE_ID_SHIFT) & SEQUENCE_ID_MASK; 40287c0e764SRichard Cochran if (ev_type == event_type(event) && 40387c0e764SRichard Cochran cpts_match(skb, class, seqid, mtype)) { 40487c0e764SRichard Cochran ns = timecounter_cyc2time(&cpts->tc, event->low); 40587c0e764SRichard Cochran list_del_init(&event->list); 40687c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 40787c0e764SRichard Cochran break; 40887c0e764SRichard Cochran } 40987c0e764SRichard Cochran } 4100d5f54feSGrygorii Strashko 4110d5f54feSGrygorii Strashko if (ev_type == CPTS_EV_TX && !ns) { 4120d5f54feSGrygorii Strashko struct cpts_skb_cb_data *skb_cb = 4130d5f54feSGrygorii Strashko (struct cpts_skb_cb_data *)skb->cb; 4140d5f54feSGrygorii Strashko /* Not found, add frame to queue for processing later. 4150d5f54feSGrygorii Strashko * The periodic FIFO check will handle this. 4160d5f54feSGrygorii Strashko */ 4170d5f54feSGrygorii Strashko skb_get(skb); 4180d5f54feSGrygorii Strashko /* get the timestamp for timeouts */ 4190d5f54feSGrygorii Strashko skb_cb->tmo = jiffies + msecs_to_jiffies(100); 4200d5f54feSGrygorii Strashko __skb_queue_tail(&cpts->txq, skb); 4210d5f54feSGrygorii Strashko ptp_schedule_worker(cpts->clock, 0); 4220d5f54feSGrygorii Strashko } 42387c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 42487c0e764SRichard Cochran 42587c0e764SRichard Cochran return ns; 42687c0e764SRichard Cochran } 42787c0e764SRichard Cochran 42887c0e764SRichard Cochran void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb) 42987c0e764SRichard Cochran { 43087c0e764SRichard Cochran u64 ns; 43187c0e764SRichard Cochran struct skb_shared_hwtstamps *ssh; 43287c0e764SRichard Cochran 43387c0e764SRichard Cochran if (!cpts->rx_enable) 43487c0e764SRichard Cochran return; 43587c0e764SRichard Cochran ns = cpts_find_ts(cpts, skb, CPTS_EV_RX); 43687c0e764SRichard Cochran if (!ns) 43787c0e764SRichard Cochran return; 43887c0e764SRichard Cochran ssh = skb_hwtstamps(skb); 43987c0e764SRichard Cochran memset(ssh, 0, sizeof(*ssh)); 44087c0e764SRichard Cochran ssh->hwtstamp = ns_to_ktime(ns); 44187c0e764SRichard Cochran } 442c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_rx_timestamp); 44387c0e764SRichard Cochran 44487c0e764SRichard Cochran void cpts_tx_timestamp(struct cpts *cpts, struct sk_buff *skb) 44587c0e764SRichard Cochran { 44687c0e764SRichard Cochran u64 ns; 44787c0e764SRichard Cochran struct skb_shared_hwtstamps ssh; 44887c0e764SRichard Cochran 44987c0e764SRichard Cochran if (!(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)) 45087c0e764SRichard Cochran return; 45187c0e764SRichard Cochran ns = cpts_find_ts(cpts, skb, CPTS_EV_TX); 45287c0e764SRichard Cochran if (!ns) 45387c0e764SRichard Cochran return; 45487c0e764SRichard Cochran memset(&ssh, 0, sizeof(ssh)); 45587c0e764SRichard Cochran ssh.hwtstamp = ns_to_ktime(ns); 45687c0e764SRichard Cochran skb_tstamp_tx(skb, &ssh); 45787c0e764SRichard Cochran } 458c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_tx_timestamp); 45987c0e764SRichard Cochran 4608a2c9a5aSGrygorii Strashko int cpts_register(struct cpts *cpts) 46187c0e764SRichard Cochran { 46287c0e764SRichard Cochran int err, i; 46387c0e764SRichard Cochran 4640d5f54feSGrygorii Strashko skb_queue_head_init(&cpts->txq); 46587c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->events); 46687c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->pool); 46787c0e764SRichard Cochran for (i = 0; i < CPTS_MAX_EVENTS; i++) 46887c0e764SRichard Cochran list_add(&cpts->pool_data[i].list, &cpts->pool); 46987c0e764SRichard Cochran 4708a2c9a5aSGrygorii Strashko clk_enable(cpts->refclk); 4718a2c9a5aSGrygorii Strashko 47287c0e764SRichard Cochran cpts_write32(cpts, CPTS_EN, control); 47387c0e764SRichard Cochran cpts_write32(cpts, TS_PEND_EN, int_enable); 47487c0e764SRichard Cochran 47587c0e764SRichard Cochran timecounter_init(&cpts->tc, &cpts->cc, ktime_to_ns(ktime_get_real())); 47687c0e764SRichard Cochran 4778a2c9a5aSGrygorii Strashko cpts->clock = ptp_clock_register(&cpts->info, cpts->dev); 4786c691405SGrygorii Strashko if (IS_ERR(cpts->clock)) { 4796c691405SGrygorii Strashko err = PTR_ERR(cpts->clock); 4806c691405SGrygorii Strashko cpts->clock = NULL; 4816c691405SGrygorii Strashko goto err_ptp; 4826c691405SGrygorii Strashko } 4836c691405SGrygorii Strashko cpts->phc_index = ptp_clock_index(cpts->clock); 4846c691405SGrygorii Strashko 485999f1292SGrygorii Strashko ptp_schedule_worker(cpts->clock, cpts->ov_check_period); 48687c0e764SRichard Cochran return 0; 4876c691405SGrygorii Strashko 4886c691405SGrygorii Strashko err_ptp: 4898a2c9a5aSGrygorii Strashko clk_disable(cpts->refclk); 4906c691405SGrygorii Strashko return err; 49187c0e764SRichard Cochran } 492c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_register); 49387c0e764SRichard Cochran 49487c0e764SRichard Cochran void cpts_unregister(struct cpts *cpts) 49587c0e764SRichard Cochran { 4968a2c9a5aSGrygorii Strashko if (WARN_ON(!cpts->clock)) 4978a2c9a5aSGrygorii Strashko return; 4988a2c9a5aSGrygorii Strashko 4998a2c9a5aSGrygorii Strashko ptp_clock_unregister(cpts->clock); 5008a2c9a5aSGrygorii Strashko cpts->clock = NULL; 5018fcd6891SGrygorii Strashko 5028fcd6891SGrygorii Strashko cpts_write32(cpts, 0, int_enable); 5038fcd6891SGrygorii Strashko cpts_write32(cpts, 0, control); 5048fcd6891SGrygorii Strashko 5050d5f54feSGrygorii Strashko /* Drop all packet */ 5060d5f54feSGrygorii Strashko skb_queue_purge(&cpts->txq); 5070d5f54feSGrygorii Strashko 5088a2c9a5aSGrygorii Strashko clk_disable(cpts->refclk); 50987c0e764SRichard Cochran } 510c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_unregister); 511c8395d4eSGrygorii Strashko 51288f0f0b0SGrygorii Strashko static void cpts_calc_mult_shift(struct cpts *cpts) 51388f0f0b0SGrygorii Strashko { 51488f0f0b0SGrygorii Strashko u64 frac, maxsec, ns; 51588f0f0b0SGrygorii Strashko u32 freq; 51688f0f0b0SGrygorii Strashko 51788f0f0b0SGrygorii Strashko freq = clk_get_rate(cpts->refclk); 51888f0f0b0SGrygorii Strashko 51988f0f0b0SGrygorii Strashko /* Calc the maximum number of seconds which we can run before 52088f0f0b0SGrygorii Strashko * wrapping around. 52188f0f0b0SGrygorii Strashko */ 52288f0f0b0SGrygorii Strashko maxsec = cpts->cc.mask; 52388f0f0b0SGrygorii Strashko do_div(maxsec, freq); 52488f0f0b0SGrygorii Strashko /* limit conversation rate to 10 sec as higher values will produce 52588f0f0b0SGrygorii Strashko * too small mult factors and so reduce the conversion accuracy 52688f0f0b0SGrygorii Strashko */ 52788f0f0b0SGrygorii Strashko if (maxsec > 10) 52888f0f0b0SGrygorii Strashko maxsec = 10; 52988f0f0b0SGrygorii Strashko 53020138cf9SGrygorii Strashko /* Calc overflow check period (maxsec / 2) */ 53120138cf9SGrygorii Strashko cpts->ov_check_period = (HZ * maxsec) / 2; 53220138cf9SGrygorii Strashko dev_info(cpts->dev, "cpts: overflow check period %lu (jiffies)\n", 53320138cf9SGrygorii Strashko cpts->ov_check_period); 53420138cf9SGrygorii Strashko 53588f0f0b0SGrygorii Strashko if (cpts->cc.mult || cpts->cc.shift) 53688f0f0b0SGrygorii Strashko return; 53788f0f0b0SGrygorii Strashko 53888f0f0b0SGrygorii Strashko clocks_calc_mult_shift(&cpts->cc.mult, &cpts->cc.shift, 53988f0f0b0SGrygorii Strashko freq, NSEC_PER_SEC, maxsec); 54088f0f0b0SGrygorii Strashko 54188f0f0b0SGrygorii Strashko frac = 0; 54288f0f0b0SGrygorii Strashko ns = cyclecounter_cyc2ns(&cpts->cc, freq, cpts->cc.mask, &frac); 54388f0f0b0SGrygorii Strashko 54488f0f0b0SGrygorii Strashko dev_info(cpts->dev, 54588f0f0b0SGrygorii Strashko "CPTS: ref_clk_freq:%u calc_mult:%u calc_shift:%u error:%lld nsec/sec\n", 54688f0f0b0SGrygorii Strashko freq, cpts->cc.mult, cpts->cc.shift, (ns - NSEC_PER_SEC)); 54788f0f0b0SGrygorii Strashko } 54888f0f0b0SGrygorii Strashko 5494a88fb95SGrygorii Strashko static int cpts_of_parse(struct cpts *cpts, struct device_node *node) 5504a88fb95SGrygorii Strashko { 5514a88fb95SGrygorii Strashko int ret = -EINVAL; 5524a88fb95SGrygorii Strashko u32 prop; 5534a88fb95SGrygorii Strashko 55488f0f0b0SGrygorii Strashko if (!of_property_read_u32(node, "cpts_clock_mult", &prop)) 5554a88fb95SGrygorii Strashko cpts->cc.mult = prop; 5564a88fb95SGrygorii Strashko 55788f0f0b0SGrygorii Strashko if (!of_property_read_u32(node, "cpts_clock_shift", &prop)) 5584a88fb95SGrygorii Strashko cpts->cc.shift = prop; 5594a88fb95SGrygorii Strashko 56088f0f0b0SGrygorii Strashko if ((cpts->cc.mult && !cpts->cc.shift) || 56188f0f0b0SGrygorii Strashko (!cpts->cc.mult && cpts->cc.shift)) 56288f0f0b0SGrygorii Strashko goto of_error; 56388f0f0b0SGrygorii Strashko 5644a88fb95SGrygorii Strashko return 0; 5654a88fb95SGrygorii Strashko 5664a88fb95SGrygorii Strashko of_error: 5674a88fb95SGrygorii Strashko dev_err(cpts->dev, "CPTS: Missing property in the DT.\n"); 5684a88fb95SGrygorii Strashko return ret; 5694a88fb95SGrygorii Strashko } 5704a88fb95SGrygorii Strashko 5718a2c9a5aSGrygorii Strashko struct cpts *cpts_create(struct device *dev, void __iomem *regs, 5724a88fb95SGrygorii Strashko struct device_node *node) 5738a2c9a5aSGrygorii Strashko { 5748a2c9a5aSGrygorii Strashko struct cpts *cpts; 5754a88fb95SGrygorii Strashko int ret; 5768a2c9a5aSGrygorii Strashko 5778a2c9a5aSGrygorii Strashko cpts = devm_kzalloc(dev, sizeof(*cpts), GFP_KERNEL); 5788a2c9a5aSGrygorii Strashko if (!cpts) 5798a2c9a5aSGrygorii Strashko return ERR_PTR(-ENOMEM); 5808a2c9a5aSGrygorii Strashko 5818a2c9a5aSGrygorii Strashko cpts->dev = dev; 5828a2c9a5aSGrygorii Strashko cpts->reg = (struct cpsw_cpts __iomem *)regs; 5838a2c9a5aSGrygorii Strashko spin_lock_init(&cpts->lock); 5848a2c9a5aSGrygorii Strashko 5854a88fb95SGrygorii Strashko ret = cpts_of_parse(cpts, node); 5864a88fb95SGrygorii Strashko if (ret) 5874a88fb95SGrygorii Strashko return ERR_PTR(ret); 5884a88fb95SGrygorii Strashko 5898a2c9a5aSGrygorii Strashko cpts->refclk = devm_clk_get(dev, "cpts"); 5908a2c9a5aSGrygorii Strashko if (IS_ERR(cpts->refclk)) { 5918a2c9a5aSGrygorii Strashko dev_err(dev, "Failed to get cpts refclk\n"); 592bde4c563SHernán Gonzalez return ERR_CAST(cpts->refclk); 5938a2c9a5aSGrygorii Strashko } 5948a2c9a5aSGrygorii Strashko 5958a2c9a5aSGrygorii Strashko clk_prepare(cpts->refclk); 5968a2c9a5aSGrygorii Strashko 5978a2c9a5aSGrygorii Strashko cpts->cc.read = cpts_systim_read; 5988a2c9a5aSGrygorii Strashko cpts->cc.mask = CLOCKSOURCE_MASK(32); 59988f0f0b0SGrygorii Strashko cpts->info = cpts_info; 60088f0f0b0SGrygorii Strashko 60188f0f0b0SGrygorii Strashko cpts_calc_mult_shift(cpts); 6024a88fb95SGrygorii Strashko /* save cc.mult original value as it can be modified 6034a88fb95SGrygorii Strashko * by cpts_ptp_adjfreq(). 6044a88fb95SGrygorii Strashko */ 6054a88fb95SGrygorii Strashko cpts->cc_mult = cpts->cc.mult; 6068a2c9a5aSGrygorii Strashko 6078a2c9a5aSGrygorii Strashko return cpts; 6088a2c9a5aSGrygorii Strashko } 6098a2c9a5aSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_create); 6108a2c9a5aSGrygorii Strashko 6118a2c9a5aSGrygorii Strashko void cpts_release(struct cpts *cpts) 6128a2c9a5aSGrygorii Strashko { 6138a2c9a5aSGrygorii Strashko if (!cpts) 6148a2c9a5aSGrygorii Strashko return; 6158a2c9a5aSGrygorii Strashko 6168a2c9a5aSGrygorii Strashko if (WARN_ON(!cpts->refclk)) 6178a2c9a5aSGrygorii Strashko return; 6188a2c9a5aSGrygorii Strashko 6198a2c9a5aSGrygorii Strashko clk_unprepare(cpts->refclk); 6208a2c9a5aSGrygorii Strashko } 6218a2c9a5aSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_release); 6228a2c9a5aSGrygorii Strashko 623c8395d4eSGrygorii Strashko MODULE_LICENSE("GPL v2"); 624c8395d4eSGrygorii Strashko MODULE_DESCRIPTION("TI CPTS driver"); 625c8395d4eSGrygorii Strashko MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>"); 626