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 34391fd6caSGrygorii Strashko #define cpts_read32(c, r) readl_relaxed(&c->reg->r) 35391fd6caSGrygorii Strashko #define cpts_write32(c, v, r) writel_relaxed(v, &c->reg->r) 3687c0e764SRichard Cochran 3787c0e764SRichard Cochran static int event_expired(struct cpts_event *event) 3887c0e764SRichard Cochran { 3987c0e764SRichard Cochran return time_after(jiffies, event->tmo); 4087c0e764SRichard Cochran } 4187c0e764SRichard Cochran 4287c0e764SRichard Cochran static int event_type(struct cpts_event *event) 4387c0e764SRichard Cochran { 4487c0e764SRichard Cochran return (event->high >> EVENT_TYPE_SHIFT) & EVENT_TYPE_MASK; 4587c0e764SRichard Cochran } 4687c0e764SRichard Cochran 4787c0e764SRichard Cochran static int cpts_fifo_pop(struct cpts *cpts, u32 *high, u32 *low) 4887c0e764SRichard Cochran { 4987c0e764SRichard Cochran u32 r = cpts_read32(cpts, intstat_raw); 5087c0e764SRichard Cochran 5187c0e764SRichard Cochran if (r & TS_PEND_RAW) { 5287c0e764SRichard Cochran *high = cpts_read32(cpts, event_high); 5387c0e764SRichard Cochran *low = cpts_read32(cpts, event_low); 5487c0e764SRichard Cochran cpts_write32(cpts, EVENT_POP, event_pop); 5587c0e764SRichard Cochran return 0; 5687c0e764SRichard Cochran } 5787c0e764SRichard Cochran return -1; 5887c0e764SRichard Cochran } 5987c0e764SRichard Cochran 60*e4439fa8SWingMan Kwok static int cpts_purge_events(struct cpts *cpts) 61*e4439fa8SWingMan Kwok { 62*e4439fa8SWingMan Kwok struct list_head *this, *next; 63*e4439fa8SWingMan Kwok struct cpts_event *event; 64*e4439fa8SWingMan Kwok int removed = 0; 65*e4439fa8SWingMan Kwok 66*e4439fa8SWingMan Kwok list_for_each_safe(this, next, &cpts->events) { 67*e4439fa8SWingMan Kwok event = list_entry(this, struct cpts_event, list); 68*e4439fa8SWingMan Kwok if (event_expired(event)) { 69*e4439fa8SWingMan Kwok list_del_init(&event->list); 70*e4439fa8SWingMan Kwok list_add(&event->list, &cpts->pool); 71*e4439fa8SWingMan Kwok ++removed; 72*e4439fa8SWingMan Kwok } 73*e4439fa8SWingMan Kwok } 74*e4439fa8SWingMan Kwok 75*e4439fa8SWingMan Kwok if (removed) 76*e4439fa8SWingMan Kwok pr_debug("cpts: event pool cleaned up %d\n", removed); 77*e4439fa8SWingMan Kwok return removed ? 0 : -1; 78*e4439fa8SWingMan Kwok } 79*e4439fa8SWingMan Kwok 8087c0e764SRichard Cochran /* 8187c0e764SRichard Cochran * Returns zero if matching event type was found. 8287c0e764SRichard Cochran */ 8387c0e764SRichard Cochran static int cpts_fifo_read(struct cpts *cpts, int match) 8487c0e764SRichard Cochran { 8587c0e764SRichard Cochran int i, type = -1; 8687c0e764SRichard Cochran u32 hi, lo; 8787c0e764SRichard Cochran struct cpts_event *event; 8887c0e764SRichard Cochran 8987c0e764SRichard Cochran for (i = 0; i < CPTS_FIFO_DEPTH; i++) { 9087c0e764SRichard Cochran if (cpts_fifo_pop(cpts, &hi, &lo)) 9187c0e764SRichard Cochran break; 92*e4439fa8SWingMan Kwok 93*e4439fa8SWingMan Kwok if (list_empty(&cpts->pool) && cpts_purge_events(cpts)) { 94*e4439fa8SWingMan Kwok pr_err("cpts: event pool empty\n"); 9587c0e764SRichard Cochran return -1; 9687c0e764SRichard Cochran } 97*e4439fa8SWingMan Kwok 9887c0e764SRichard Cochran event = list_first_entry(&cpts->pool, struct cpts_event, list); 9987c0e764SRichard Cochran event->tmo = jiffies + 2; 10087c0e764SRichard Cochran event->high = hi; 10187c0e764SRichard Cochran event->low = lo; 10287c0e764SRichard Cochran type = event_type(event); 10387c0e764SRichard Cochran switch (type) { 10487c0e764SRichard Cochran case CPTS_EV_PUSH: 10587c0e764SRichard Cochran case CPTS_EV_RX: 10687c0e764SRichard Cochran case CPTS_EV_TX: 10787c0e764SRichard Cochran list_del_init(&event->list); 10887c0e764SRichard Cochran list_add_tail(&event->list, &cpts->events); 10987c0e764SRichard Cochran break; 11087c0e764SRichard Cochran case CPTS_EV_ROLL: 11187c0e764SRichard Cochran case CPTS_EV_HALF: 11287c0e764SRichard Cochran case CPTS_EV_HW: 11387c0e764SRichard Cochran break; 11487c0e764SRichard Cochran default: 11507f42258SMasanari Iida pr_err("cpts: unknown event type\n"); 11687c0e764SRichard Cochran break; 11787c0e764SRichard Cochran } 11887c0e764SRichard Cochran if (type == match) 11987c0e764SRichard Cochran break; 12087c0e764SRichard Cochran } 12187c0e764SRichard Cochran return type == match ? 0 : -1; 12287c0e764SRichard Cochran } 12387c0e764SRichard Cochran 12487c0e764SRichard Cochran static cycle_t cpts_systim_read(const struct cyclecounter *cc) 12587c0e764SRichard Cochran { 12687c0e764SRichard Cochran u64 val = 0; 12787c0e764SRichard Cochran struct cpts_event *event; 12887c0e764SRichard Cochran struct list_head *this, *next; 12987c0e764SRichard Cochran struct cpts *cpts = container_of(cc, struct cpts, cc); 13087c0e764SRichard Cochran 13187c0e764SRichard Cochran cpts_write32(cpts, TS_PUSH, ts_push); 13287c0e764SRichard Cochran if (cpts_fifo_read(cpts, CPTS_EV_PUSH)) 13387c0e764SRichard Cochran pr_err("cpts: unable to obtain a time stamp\n"); 13487c0e764SRichard Cochran 13587c0e764SRichard Cochran list_for_each_safe(this, next, &cpts->events) { 13687c0e764SRichard Cochran event = list_entry(this, struct cpts_event, list); 13787c0e764SRichard Cochran if (event_type(event) == CPTS_EV_PUSH) { 13887c0e764SRichard Cochran list_del_init(&event->list); 13987c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 14087c0e764SRichard Cochran val = event->low; 14187c0e764SRichard Cochran break; 14287c0e764SRichard Cochran } 14387c0e764SRichard Cochran } 14487c0e764SRichard Cochran 14587c0e764SRichard Cochran return val; 14687c0e764SRichard Cochran } 14787c0e764SRichard Cochran 14887c0e764SRichard Cochran /* PTP clock operations */ 14987c0e764SRichard Cochran 15087c0e764SRichard Cochran static int cpts_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) 15187c0e764SRichard Cochran { 15287c0e764SRichard Cochran u64 adj; 15387c0e764SRichard Cochran u32 diff, mult; 15487c0e764SRichard Cochran int neg_adj = 0; 15587c0e764SRichard Cochran unsigned long flags; 15687c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 15787c0e764SRichard Cochran 15887c0e764SRichard Cochran if (ppb < 0) { 15987c0e764SRichard Cochran neg_adj = 1; 16087c0e764SRichard Cochran ppb = -ppb; 16187c0e764SRichard Cochran } 16287c0e764SRichard Cochran mult = cpts->cc_mult; 16387c0e764SRichard Cochran adj = mult; 16487c0e764SRichard Cochran adj *= ppb; 16587c0e764SRichard Cochran diff = div_u64(adj, 1000000000ULL); 16687c0e764SRichard Cochran 16787c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 16887c0e764SRichard Cochran 16987c0e764SRichard Cochran timecounter_read(&cpts->tc); 17087c0e764SRichard Cochran 17187c0e764SRichard Cochran cpts->cc.mult = neg_adj ? mult - diff : mult + diff; 17287c0e764SRichard Cochran 17387c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 17487c0e764SRichard Cochran 17587c0e764SRichard Cochran return 0; 17687c0e764SRichard Cochran } 17787c0e764SRichard Cochran 17887c0e764SRichard Cochran static int cpts_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) 17987c0e764SRichard Cochran { 18087c0e764SRichard Cochran unsigned long flags; 18187c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 18287c0e764SRichard Cochran 18387c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 184f25a30beSRichard Cochran timecounter_adjtime(&cpts->tc, delta); 18587c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 18687c0e764SRichard Cochran 18787c0e764SRichard Cochran return 0; 18887c0e764SRichard Cochran } 18987c0e764SRichard Cochran 190a5c79c26SRichard Cochran static int cpts_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) 19187c0e764SRichard Cochran { 19287c0e764SRichard Cochran u64 ns; 19387c0e764SRichard Cochran unsigned long flags; 19487c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 19587c0e764SRichard Cochran 19687c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 19787c0e764SRichard Cochran ns = timecounter_read(&cpts->tc); 19887c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 19987c0e764SRichard Cochran 20084d923ceSRichard Cochran *ts = ns_to_timespec64(ns); 20187c0e764SRichard Cochran 20287c0e764SRichard Cochran return 0; 20387c0e764SRichard Cochran } 20487c0e764SRichard Cochran 20587c0e764SRichard Cochran static int cpts_ptp_settime(struct ptp_clock_info *ptp, 206a5c79c26SRichard Cochran const struct timespec64 *ts) 20787c0e764SRichard Cochran { 20887c0e764SRichard Cochran u64 ns; 20987c0e764SRichard Cochran unsigned long flags; 21087c0e764SRichard Cochran struct cpts *cpts = container_of(ptp, struct cpts, info); 21187c0e764SRichard Cochran 21284d923ceSRichard Cochran ns = timespec64_to_ns(ts); 21387c0e764SRichard Cochran 21487c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 21587c0e764SRichard Cochran timecounter_init(&cpts->tc, &cpts->cc, ns); 21687c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 21787c0e764SRichard Cochran 21887c0e764SRichard Cochran return 0; 21987c0e764SRichard Cochran } 22087c0e764SRichard Cochran 22187c0e764SRichard Cochran static int cpts_ptp_enable(struct ptp_clock_info *ptp, 22287c0e764SRichard Cochran struct ptp_clock_request *rq, int on) 22387c0e764SRichard Cochran { 22487c0e764SRichard Cochran return -EOPNOTSUPP; 22587c0e764SRichard Cochran } 22687c0e764SRichard Cochran 22787c0e764SRichard Cochran static struct ptp_clock_info cpts_info = { 22887c0e764SRichard Cochran .owner = THIS_MODULE, 22987c0e764SRichard Cochran .name = "CTPS timer", 23087c0e764SRichard Cochran .max_adj = 1000000, 23187c0e764SRichard Cochran .n_ext_ts = 0, 2324986b4f0SRichard Cochran .n_pins = 0, 23387c0e764SRichard Cochran .pps = 0, 23487c0e764SRichard Cochran .adjfreq = cpts_ptp_adjfreq, 23587c0e764SRichard Cochran .adjtime = cpts_ptp_adjtime, 236a5c79c26SRichard Cochran .gettime64 = cpts_ptp_gettime, 237a5c79c26SRichard Cochran .settime64 = cpts_ptp_settime, 23887c0e764SRichard Cochran .enable = cpts_ptp_enable, 23987c0e764SRichard Cochran }; 24087c0e764SRichard Cochran 24187c0e764SRichard Cochran static void cpts_overflow_check(struct work_struct *work) 24287c0e764SRichard Cochran { 243a5c79c26SRichard Cochran struct timespec64 ts; 24487c0e764SRichard Cochran struct cpts *cpts = container_of(work, struct cpts, overflow_work.work); 24587c0e764SRichard Cochran 24687c0e764SRichard Cochran cpts_write32(cpts, CPTS_EN, control); 24787c0e764SRichard Cochran cpts_write32(cpts, TS_PEND_EN, int_enable); 24887c0e764SRichard Cochran cpts_ptp_gettime(&cpts->info, &ts); 249a5c79c26SRichard Cochran pr_debug("cpts overflow check at %lld.%09lu\n", ts.tv_sec, ts.tv_nsec); 25087c0e764SRichard Cochran schedule_delayed_work(&cpts->overflow_work, CPTS_OVERFLOW_PERIOD); 25187c0e764SRichard Cochran } 25287c0e764SRichard Cochran 253d0415e7cSGeorge Cherian static void cpts_clk_init(struct device *dev, struct cpts *cpts) 25487c0e764SRichard Cochran { 255fd123a94SGrygorii Strashko if (!cpts->refclk) { 256d0415e7cSGeorge Cherian cpts->refclk = devm_clk_get(dev, "cpts"); 25787c0e764SRichard Cochran if (IS_ERR(cpts->refclk)) { 258d0415e7cSGeorge Cherian dev_err(dev, "Failed to get cpts refclk\n"); 25987c0e764SRichard Cochran cpts->refclk = NULL; 26087c0e764SRichard Cochran return; 26187c0e764SRichard Cochran } 262fd123a94SGrygorii Strashko } 263ccb6e984SRichard Cochran clk_prepare_enable(cpts->refclk); 26487c0e764SRichard Cochran } 26587c0e764SRichard Cochran 26687c0e764SRichard Cochran static void cpts_clk_release(struct cpts *cpts) 26787c0e764SRichard Cochran { 268fd123a94SGrygorii Strashko clk_disable_unprepare(cpts->refclk); 26987c0e764SRichard Cochran } 27087c0e764SRichard Cochran 27187c0e764SRichard Cochran static int cpts_match(struct sk_buff *skb, unsigned int ptp_class, 27287c0e764SRichard Cochran u16 ts_seqid, u8 ts_msgtype) 27387c0e764SRichard Cochran { 27487c0e764SRichard Cochran u16 *seqid; 275ae5c6c6dSStefan Sørensen unsigned int offset = 0; 27687c0e764SRichard Cochran u8 *msgtype, *data = skb->data; 27787c0e764SRichard Cochran 278ae5c6c6dSStefan Sørensen if (ptp_class & PTP_CLASS_VLAN) 279ae5c6c6dSStefan Sørensen offset += VLAN_HLEN; 280ae5c6c6dSStefan Sørensen 281ae5c6c6dSStefan Sørensen switch (ptp_class & PTP_CLASS_PMASK) { 282ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV4: 283cca04b28SRichard Cochran offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN; 28487c0e764SRichard Cochran break; 285ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV6: 286ae5c6c6dSStefan Sørensen offset += ETH_HLEN + IP6_HLEN + UDP_HLEN; 28787c0e764SRichard Cochran break; 288ae5c6c6dSStefan Sørensen case PTP_CLASS_L2: 289ae5c6c6dSStefan Sørensen offset += ETH_HLEN; 29087c0e764SRichard Cochran break; 29187c0e764SRichard Cochran default: 29287c0e764SRichard Cochran return 0; 29387c0e764SRichard Cochran } 29487c0e764SRichard Cochran 29587c0e764SRichard Cochran if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid)) 29687c0e764SRichard Cochran return 0; 29787c0e764SRichard Cochran 29887c0e764SRichard Cochran if (unlikely(ptp_class & PTP_CLASS_V1)) 29987c0e764SRichard Cochran msgtype = data + offset + OFF_PTP_CONTROL; 30087c0e764SRichard Cochran else 30187c0e764SRichard Cochran msgtype = data + offset; 30287c0e764SRichard Cochran 30387c0e764SRichard Cochran seqid = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID); 30487c0e764SRichard Cochran 30587c0e764SRichard Cochran return (ts_msgtype == (*msgtype & 0xf) && ts_seqid == ntohs(*seqid)); 30687c0e764SRichard Cochran } 30787c0e764SRichard Cochran 30887c0e764SRichard Cochran static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, int ev_type) 30987c0e764SRichard Cochran { 31087c0e764SRichard Cochran u64 ns = 0; 31187c0e764SRichard Cochran struct cpts_event *event; 31287c0e764SRichard Cochran struct list_head *this, *next; 313164d8c66SDaniel Borkmann unsigned int class = ptp_classify_raw(skb); 31487c0e764SRichard Cochran unsigned long flags; 31587c0e764SRichard Cochran u16 seqid; 31687c0e764SRichard Cochran u8 mtype; 31787c0e764SRichard Cochran 31887c0e764SRichard Cochran if (class == PTP_CLASS_NONE) 31987c0e764SRichard Cochran return 0; 32087c0e764SRichard Cochran 32187c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 32287c0e764SRichard Cochran cpts_fifo_read(cpts, CPTS_EV_PUSH); 32387c0e764SRichard Cochran list_for_each_safe(this, next, &cpts->events) { 32487c0e764SRichard Cochran event = list_entry(this, struct cpts_event, list); 32587c0e764SRichard Cochran if (event_expired(event)) { 32687c0e764SRichard Cochran list_del_init(&event->list); 32787c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 32887c0e764SRichard Cochran continue; 32987c0e764SRichard Cochran } 33087c0e764SRichard Cochran mtype = (event->high >> MESSAGE_TYPE_SHIFT) & MESSAGE_TYPE_MASK; 33187c0e764SRichard Cochran seqid = (event->high >> SEQUENCE_ID_SHIFT) & SEQUENCE_ID_MASK; 33287c0e764SRichard Cochran if (ev_type == event_type(event) && 33387c0e764SRichard Cochran cpts_match(skb, class, seqid, mtype)) { 33487c0e764SRichard Cochran ns = timecounter_cyc2time(&cpts->tc, event->low); 33587c0e764SRichard Cochran list_del_init(&event->list); 33687c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 33787c0e764SRichard Cochran break; 33887c0e764SRichard Cochran } 33987c0e764SRichard Cochran } 34087c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 34187c0e764SRichard Cochran 34287c0e764SRichard Cochran return ns; 34387c0e764SRichard Cochran } 34487c0e764SRichard Cochran 34587c0e764SRichard Cochran void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb) 34687c0e764SRichard Cochran { 34787c0e764SRichard Cochran u64 ns; 34887c0e764SRichard Cochran struct skb_shared_hwtstamps *ssh; 34987c0e764SRichard Cochran 35087c0e764SRichard Cochran if (!cpts->rx_enable) 35187c0e764SRichard Cochran return; 35287c0e764SRichard Cochran ns = cpts_find_ts(cpts, skb, CPTS_EV_RX); 35387c0e764SRichard Cochran if (!ns) 35487c0e764SRichard Cochran return; 35587c0e764SRichard Cochran ssh = skb_hwtstamps(skb); 35687c0e764SRichard Cochran memset(ssh, 0, sizeof(*ssh)); 35787c0e764SRichard Cochran ssh->hwtstamp = ns_to_ktime(ns); 35887c0e764SRichard Cochran } 359c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_rx_timestamp); 36087c0e764SRichard Cochran 36187c0e764SRichard Cochran void cpts_tx_timestamp(struct cpts *cpts, struct sk_buff *skb) 36287c0e764SRichard Cochran { 36387c0e764SRichard Cochran u64 ns; 36487c0e764SRichard Cochran struct skb_shared_hwtstamps ssh; 36587c0e764SRichard Cochran 36687c0e764SRichard Cochran if (!(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)) 36787c0e764SRichard Cochran return; 36887c0e764SRichard Cochran ns = cpts_find_ts(cpts, skb, CPTS_EV_TX); 36987c0e764SRichard Cochran if (!ns) 37087c0e764SRichard Cochran return; 37187c0e764SRichard Cochran memset(&ssh, 0, sizeof(ssh)); 37287c0e764SRichard Cochran ssh.hwtstamp = ns_to_ktime(ns); 37387c0e764SRichard Cochran skb_tstamp_tx(skb, &ssh); 37487c0e764SRichard Cochran } 375c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_tx_timestamp); 37687c0e764SRichard Cochran 37787c0e764SRichard Cochran int cpts_register(struct device *dev, struct cpts *cpts, 37887c0e764SRichard Cochran u32 mult, u32 shift) 37987c0e764SRichard Cochran { 38087c0e764SRichard Cochran int err, i; 38187c0e764SRichard Cochran 38287c0e764SRichard Cochran cpts->info = cpts_info; 38387c0e764SRichard Cochran spin_lock_init(&cpts->lock); 38487c0e764SRichard Cochran 38587c0e764SRichard Cochran cpts->cc.read = cpts_systim_read; 38687c0e764SRichard Cochran cpts->cc.mask = CLOCKSOURCE_MASK(32); 38787c0e764SRichard Cochran cpts->cc_mult = mult; 38887c0e764SRichard Cochran cpts->cc.mult = mult; 38987c0e764SRichard Cochran cpts->cc.shift = shift; 39087c0e764SRichard Cochran 39187c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->events); 39287c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->pool); 39387c0e764SRichard Cochran for (i = 0; i < CPTS_MAX_EVENTS; i++) 39487c0e764SRichard Cochran list_add(&cpts->pool_data[i].list, &cpts->pool); 39587c0e764SRichard Cochran 396d0415e7cSGeorge Cherian cpts_clk_init(dev, cpts); 39787c0e764SRichard Cochran cpts_write32(cpts, CPTS_EN, control); 39887c0e764SRichard Cochran cpts_write32(cpts, TS_PEND_EN, int_enable); 39987c0e764SRichard Cochran 40087c0e764SRichard Cochran timecounter_init(&cpts->tc, &cpts->cc, ktime_to_ns(ktime_get_real())); 40187c0e764SRichard Cochran 40287c0e764SRichard Cochran INIT_DELAYED_WORK(&cpts->overflow_work, cpts_overflow_check); 4036c691405SGrygorii Strashko 4046c691405SGrygorii Strashko cpts->clock = ptp_clock_register(&cpts->info, dev); 4056c691405SGrygorii Strashko if (IS_ERR(cpts->clock)) { 4066c691405SGrygorii Strashko err = PTR_ERR(cpts->clock); 4076c691405SGrygorii Strashko cpts->clock = NULL; 4086c691405SGrygorii Strashko goto err_ptp; 4096c691405SGrygorii Strashko } 4106c691405SGrygorii Strashko cpts->phc_index = ptp_clock_index(cpts->clock); 4116c691405SGrygorii Strashko 41287c0e764SRichard Cochran schedule_delayed_work(&cpts->overflow_work, CPTS_OVERFLOW_PERIOD); 41387c0e764SRichard Cochran 41487c0e764SRichard Cochran return 0; 4156c691405SGrygorii Strashko 4166c691405SGrygorii Strashko err_ptp: 4176c691405SGrygorii Strashko if (cpts->refclk) 4186c691405SGrygorii Strashko cpts_clk_release(cpts); 4196c691405SGrygorii Strashko return err; 42087c0e764SRichard Cochran } 421c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_register); 42287c0e764SRichard Cochran 42387c0e764SRichard Cochran void cpts_unregister(struct cpts *cpts) 42487c0e764SRichard Cochran { 42587c0e764SRichard Cochran if (cpts->clock) { 42687c0e764SRichard Cochran ptp_clock_unregister(cpts->clock); 42787c0e764SRichard Cochran cancel_delayed_work_sync(&cpts->overflow_work); 42887c0e764SRichard Cochran } 4298fcd6891SGrygorii Strashko 4308fcd6891SGrygorii Strashko cpts_write32(cpts, 0, int_enable); 4318fcd6891SGrygorii Strashko cpts_write32(cpts, 0, control); 4328fcd6891SGrygorii Strashko 43387c0e764SRichard Cochran if (cpts->refclk) 43487c0e764SRichard Cochran cpts_clk_release(cpts); 43587c0e764SRichard Cochran } 436c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_unregister); 437c8395d4eSGrygorii Strashko 438c8395d4eSGrygorii Strashko MODULE_LICENSE("GPL v2"); 439c8395d4eSGrygorii Strashko MODULE_DESCRIPTION("TI CPTS driver"); 440c8395d4eSGrygorii Strashko MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>"); 441