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 60e4439fa8SWingMan Kwok static int cpts_purge_events(struct cpts *cpts) 61e4439fa8SWingMan Kwok { 62e4439fa8SWingMan Kwok struct list_head *this, *next; 63e4439fa8SWingMan Kwok struct cpts_event *event; 64e4439fa8SWingMan Kwok int removed = 0; 65e4439fa8SWingMan Kwok 66e4439fa8SWingMan Kwok list_for_each_safe(this, next, &cpts->events) { 67e4439fa8SWingMan Kwok event = list_entry(this, struct cpts_event, list); 68e4439fa8SWingMan Kwok if (event_expired(event)) { 69e4439fa8SWingMan Kwok list_del_init(&event->list); 70e4439fa8SWingMan Kwok list_add(&event->list, &cpts->pool); 71e4439fa8SWingMan Kwok ++removed; 72e4439fa8SWingMan Kwok } 73e4439fa8SWingMan Kwok } 74e4439fa8SWingMan Kwok 75e4439fa8SWingMan Kwok if (removed) 76e4439fa8SWingMan Kwok pr_debug("cpts: event pool cleaned up %d\n", removed); 77e4439fa8SWingMan Kwok return removed ? 0 : -1; 78e4439fa8SWingMan Kwok } 79e4439fa8SWingMan 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; 92e4439fa8SWingMan Kwok 93e4439fa8SWingMan Kwok if (list_empty(&cpts->pool) && cpts_purge_events(cpts)) { 94e4439fa8SWingMan Kwok pr_err("cpts: event pool empty\n"); 9587c0e764SRichard Cochran return -1; 9687c0e764SRichard Cochran } 97e4439fa8SWingMan 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_ptp_gettime(&cpts->info, &ts); 247a5c79c26SRichard Cochran pr_debug("cpts overflow check at %lld.%09lu\n", ts.tv_sec, ts.tv_nsec); 24887c0e764SRichard Cochran schedule_delayed_work(&cpts->overflow_work, CPTS_OVERFLOW_PERIOD); 24987c0e764SRichard Cochran } 25087c0e764SRichard Cochran 25187c0e764SRichard Cochran static int cpts_match(struct sk_buff *skb, unsigned int ptp_class, 25287c0e764SRichard Cochran u16 ts_seqid, u8 ts_msgtype) 25387c0e764SRichard Cochran { 25487c0e764SRichard Cochran u16 *seqid; 255ae5c6c6dSStefan Sørensen unsigned int offset = 0; 25687c0e764SRichard Cochran u8 *msgtype, *data = skb->data; 25787c0e764SRichard Cochran 258ae5c6c6dSStefan Sørensen if (ptp_class & PTP_CLASS_VLAN) 259ae5c6c6dSStefan Sørensen offset += VLAN_HLEN; 260ae5c6c6dSStefan Sørensen 261ae5c6c6dSStefan Sørensen switch (ptp_class & PTP_CLASS_PMASK) { 262ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV4: 263cca04b28SRichard Cochran offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN; 26487c0e764SRichard Cochran break; 265ae5c6c6dSStefan Sørensen case PTP_CLASS_IPV6: 266ae5c6c6dSStefan Sørensen offset += ETH_HLEN + IP6_HLEN + UDP_HLEN; 26787c0e764SRichard Cochran break; 268ae5c6c6dSStefan Sørensen case PTP_CLASS_L2: 269ae5c6c6dSStefan Sørensen offset += ETH_HLEN; 27087c0e764SRichard Cochran break; 27187c0e764SRichard Cochran default: 27287c0e764SRichard Cochran return 0; 27387c0e764SRichard Cochran } 27487c0e764SRichard Cochran 27587c0e764SRichard Cochran if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid)) 27687c0e764SRichard Cochran return 0; 27787c0e764SRichard Cochran 27887c0e764SRichard Cochran if (unlikely(ptp_class & PTP_CLASS_V1)) 27987c0e764SRichard Cochran msgtype = data + offset + OFF_PTP_CONTROL; 28087c0e764SRichard Cochran else 28187c0e764SRichard Cochran msgtype = data + offset; 28287c0e764SRichard Cochran 28387c0e764SRichard Cochran seqid = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID); 28487c0e764SRichard Cochran 28587c0e764SRichard Cochran return (ts_msgtype == (*msgtype & 0xf) && ts_seqid == ntohs(*seqid)); 28687c0e764SRichard Cochran } 28787c0e764SRichard Cochran 28887c0e764SRichard Cochran static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, int ev_type) 28987c0e764SRichard Cochran { 29087c0e764SRichard Cochran u64 ns = 0; 29187c0e764SRichard Cochran struct cpts_event *event; 29287c0e764SRichard Cochran struct list_head *this, *next; 293164d8c66SDaniel Borkmann unsigned int class = ptp_classify_raw(skb); 29487c0e764SRichard Cochran unsigned long flags; 29587c0e764SRichard Cochran u16 seqid; 29687c0e764SRichard Cochran u8 mtype; 29787c0e764SRichard Cochran 29887c0e764SRichard Cochran if (class == PTP_CLASS_NONE) 29987c0e764SRichard Cochran return 0; 30087c0e764SRichard Cochran 30187c0e764SRichard Cochran spin_lock_irqsave(&cpts->lock, flags); 30287c0e764SRichard Cochran cpts_fifo_read(cpts, CPTS_EV_PUSH); 30387c0e764SRichard Cochran list_for_each_safe(this, next, &cpts->events) { 30487c0e764SRichard Cochran event = list_entry(this, struct cpts_event, list); 30587c0e764SRichard Cochran if (event_expired(event)) { 30687c0e764SRichard Cochran list_del_init(&event->list); 30787c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 30887c0e764SRichard Cochran continue; 30987c0e764SRichard Cochran } 31087c0e764SRichard Cochran mtype = (event->high >> MESSAGE_TYPE_SHIFT) & MESSAGE_TYPE_MASK; 31187c0e764SRichard Cochran seqid = (event->high >> SEQUENCE_ID_SHIFT) & SEQUENCE_ID_MASK; 31287c0e764SRichard Cochran if (ev_type == event_type(event) && 31387c0e764SRichard Cochran cpts_match(skb, class, seqid, mtype)) { 31487c0e764SRichard Cochran ns = timecounter_cyc2time(&cpts->tc, event->low); 31587c0e764SRichard Cochran list_del_init(&event->list); 31687c0e764SRichard Cochran list_add(&event->list, &cpts->pool); 31787c0e764SRichard Cochran break; 31887c0e764SRichard Cochran } 31987c0e764SRichard Cochran } 32087c0e764SRichard Cochran spin_unlock_irqrestore(&cpts->lock, flags); 32187c0e764SRichard Cochran 32287c0e764SRichard Cochran return ns; 32387c0e764SRichard Cochran } 32487c0e764SRichard Cochran 32587c0e764SRichard Cochran void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb) 32687c0e764SRichard Cochran { 32787c0e764SRichard Cochran u64 ns; 32887c0e764SRichard Cochran struct skb_shared_hwtstamps *ssh; 32987c0e764SRichard Cochran 33087c0e764SRichard Cochran if (!cpts->rx_enable) 33187c0e764SRichard Cochran return; 33287c0e764SRichard Cochran ns = cpts_find_ts(cpts, skb, CPTS_EV_RX); 33387c0e764SRichard Cochran if (!ns) 33487c0e764SRichard Cochran return; 33587c0e764SRichard Cochran ssh = skb_hwtstamps(skb); 33687c0e764SRichard Cochran memset(ssh, 0, sizeof(*ssh)); 33787c0e764SRichard Cochran ssh->hwtstamp = ns_to_ktime(ns); 33887c0e764SRichard Cochran } 339c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_rx_timestamp); 34087c0e764SRichard Cochran 34187c0e764SRichard Cochran void cpts_tx_timestamp(struct cpts *cpts, struct sk_buff *skb) 34287c0e764SRichard Cochran { 34387c0e764SRichard Cochran u64 ns; 34487c0e764SRichard Cochran struct skb_shared_hwtstamps ssh; 34587c0e764SRichard Cochran 34687c0e764SRichard Cochran if (!(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)) 34787c0e764SRichard Cochran return; 34887c0e764SRichard Cochran ns = cpts_find_ts(cpts, skb, CPTS_EV_TX); 34987c0e764SRichard Cochran if (!ns) 35087c0e764SRichard Cochran return; 35187c0e764SRichard Cochran memset(&ssh, 0, sizeof(ssh)); 35287c0e764SRichard Cochran ssh.hwtstamp = ns_to_ktime(ns); 35387c0e764SRichard Cochran skb_tstamp_tx(skb, &ssh); 35487c0e764SRichard Cochran } 355c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_tx_timestamp); 35687c0e764SRichard Cochran 3578a2c9a5aSGrygorii Strashko int cpts_register(struct cpts *cpts) 35887c0e764SRichard Cochran { 35987c0e764SRichard Cochran int err, i; 36087c0e764SRichard Cochran 36187c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->events); 36287c0e764SRichard Cochran INIT_LIST_HEAD(&cpts->pool); 36387c0e764SRichard Cochran for (i = 0; i < CPTS_MAX_EVENTS; i++) 36487c0e764SRichard Cochran list_add(&cpts->pool_data[i].list, &cpts->pool); 36587c0e764SRichard Cochran 3668a2c9a5aSGrygorii Strashko clk_enable(cpts->refclk); 3678a2c9a5aSGrygorii Strashko 36887c0e764SRichard Cochran cpts_write32(cpts, CPTS_EN, control); 36987c0e764SRichard Cochran cpts_write32(cpts, TS_PEND_EN, int_enable); 37087c0e764SRichard Cochran 37187c0e764SRichard Cochran timecounter_init(&cpts->tc, &cpts->cc, ktime_to_ns(ktime_get_real())); 37287c0e764SRichard Cochran 3738a2c9a5aSGrygorii Strashko cpts->clock = ptp_clock_register(&cpts->info, cpts->dev); 3746c691405SGrygorii Strashko if (IS_ERR(cpts->clock)) { 3756c691405SGrygorii Strashko err = PTR_ERR(cpts->clock); 3766c691405SGrygorii Strashko cpts->clock = NULL; 3776c691405SGrygorii Strashko goto err_ptp; 3786c691405SGrygorii Strashko } 3796c691405SGrygorii Strashko cpts->phc_index = ptp_clock_index(cpts->clock); 3806c691405SGrygorii Strashko 38187c0e764SRichard Cochran schedule_delayed_work(&cpts->overflow_work, CPTS_OVERFLOW_PERIOD); 38287c0e764SRichard Cochran 38387c0e764SRichard Cochran return 0; 3846c691405SGrygorii Strashko 3856c691405SGrygorii Strashko err_ptp: 3868a2c9a5aSGrygorii Strashko clk_disable(cpts->refclk); 3876c691405SGrygorii Strashko return err; 38887c0e764SRichard Cochran } 389c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_register); 39087c0e764SRichard Cochran 39187c0e764SRichard Cochran void cpts_unregister(struct cpts *cpts) 39287c0e764SRichard Cochran { 3938a2c9a5aSGrygorii Strashko if (WARN_ON(!cpts->clock)) 3948a2c9a5aSGrygorii Strashko return; 3958a2c9a5aSGrygorii Strashko 39687c0e764SRichard Cochran cancel_delayed_work_sync(&cpts->overflow_work); 3978a2c9a5aSGrygorii Strashko 3988a2c9a5aSGrygorii Strashko ptp_clock_unregister(cpts->clock); 3998a2c9a5aSGrygorii Strashko cpts->clock = NULL; 4008fcd6891SGrygorii Strashko 4018fcd6891SGrygorii Strashko cpts_write32(cpts, 0, int_enable); 4028fcd6891SGrygorii Strashko cpts_write32(cpts, 0, control); 4038fcd6891SGrygorii Strashko 4048a2c9a5aSGrygorii Strashko clk_disable(cpts->refclk); 40587c0e764SRichard Cochran } 406c8395d4eSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_unregister); 407c8395d4eSGrygorii Strashko 408*4a88fb95SGrygorii Strashko static int cpts_of_parse(struct cpts *cpts, struct device_node *node) 409*4a88fb95SGrygorii Strashko { 410*4a88fb95SGrygorii Strashko int ret = -EINVAL; 411*4a88fb95SGrygorii Strashko u32 prop; 412*4a88fb95SGrygorii Strashko 413*4a88fb95SGrygorii Strashko if (of_property_read_u32(node, "cpts_clock_mult", &prop)) 414*4a88fb95SGrygorii Strashko goto of_error; 415*4a88fb95SGrygorii Strashko cpts->cc.mult = prop; 416*4a88fb95SGrygorii Strashko 417*4a88fb95SGrygorii Strashko if (of_property_read_u32(node, "cpts_clock_shift", &prop)) 418*4a88fb95SGrygorii Strashko goto of_error; 419*4a88fb95SGrygorii Strashko cpts->cc.shift = prop; 420*4a88fb95SGrygorii Strashko 421*4a88fb95SGrygorii Strashko return 0; 422*4a88fb95SGrygorii Strashko 423*4a88fb95SGrygorii Strashko of_error: 424*4a88fb95SGrygorii Strashko dev_err(cpts->dev, "CPTS: Missing property in the DT.\n"); 425*4a88fb95SGrygorii Strashko return ret; 426*4a88fb95SGrygorii Strashko } 427*4a88fb95SGrygorii Strashko 4288a2c9a5aSGrygorii Strashko struct cpts *cpts_create(struct device *dev, void __iomem *regs, 429*4a88fb95SGrygorii Strashko struct device_node *node) 4308a2c9a5aSGrygorii Strashko { 4318a2c9a5aSGrygorii Strashko struct cpts *cpts; 432*4a88fb95SGrygorii Strashko int ret; 4338a2c9a5aSGrygorii Strashko 4348a2c9a5aSGrygorii Strashko cpts = devm_kzalloc(dev, sizeof(*cpts), GFP_KERNEL); 4358a2c9a5aSGrygorii Strashko if (!cpts) 4368a2c9a5aSGrygorii Strashko return ERR_PTR(-ENOMEM); 4378a2c9a5aSGrygorii Strashko 4388a2c9a5aSGrygorii Strashko cpts->dev = dev; 4398a2c9a5aSGrygorii Strashko cpts->reg = (struct cpsw_cpts __iomem *)regs; 4408a2c9a5aSGrygorii Strashko spin_lock_init(&cpts->lock); 4418a2c9a5aSGrygorii Strashko INIT_DELAYED_WORK(&cpts->overflow_work, cpts_overflow_check); 4428a2c9a5aSGrygorii Strashko 443*4a88fb95SGrygorii Strashko ret = cpts_of_parse(cpts, node); 444*4a88fb95SGrygorii Strashko if (ret) 445*4a88fb95SGrygorii Strashko return ERR_PTR(ret); 446*4a88fb95SGrygorii Strashko 4478a2c9a5aSGrygorii Strashko cpts->refclk = devm_clk_get(dev, "cpts"); 4488a2c9a5aSGrygorii Strashko if (IS_ERR(cpts->refclk)) { 4498a2c9a5aSGrygorii Strashko dev_err(dev, "Failed to get cpts refclk\n"); 4508a2c9a5aSGrygorii Strashko return ERR_PTR(PTR_ERR(cpts->refclk)); 4518a2c9a5aSGrygorii Strashko } 4528a2c9a5aSGrygorii Strashko 4538a2c9a5aSGrygorii Strashko clk_prepare(cpts->refclk); 4548a2c9a5aSGrygorii Strashko 4558a2c9a5aSGrygorii Strashko cpts->cc.read = cpts_systim_read; 4568a2c9a5aSGrygorii Strashko cpts->cc.mask = CLOCKSOURCE_MASK(32); 457*4a88fb95SGrygorii Strashko /* save cc.mult original value as it can be modified 458*4a88fb95SGrygorii Strashko * by cpts_ptp_adjfreq(). 459*4a88fb95SGrygorii Strashko */ 460*4a88fb95SGrygorii Strashko cpts->cc_mult = cpts->cc.mult; 4618a2c9a5aSGrygorii Strashko cpts->info = cpts_info; 4628a2c9a5aSGrygorii Strashko 4638a2c9a5aSGrygorii Strashko return cpts; 4648a2c9a5aSGrygorii Strashko } 4658a2c9a5aSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_create); 4668a2c9a5aSGrygorii Strashko 4678a2c9a5aSGrygorii Strashko void cpts_release(struct cpts *cpts) 4688a2c9a5aSGrygorii Strashko { 4698a2c9a5aSGrygorii Strashko if (!cpts) 4708a2c9a5aSGrygorii Strashko return; 4718a2c9a5aSGrygorii Strashko 4728a2c9a5aSGrygorii Strashko if (WARN_ON(!cpts->refclk)) 4738a2c9a5aSGrygorii Strashko return; 4748a2c9a5aSGrygorii Strashko 4758a2c9a5aSGrygorii Strashko clk_unprepare(cpts->refclk); 4768a2c9a5aSGrygorii Strashko } 4778a2c9a5aSGrygorii Strashko EXPORT_SYMBOL_GPL(cpts_release); 4788a2c9a5aSGrygorii Strashko 479c8395d4eSGrygorii Strashko MODULE_LICENSE("GPL v2"); 480c8395d4eSGrygorii Strashko MODULE_DESCRIPTION("TI CPTS driver"); 481c8395d4eSGrygorii Strashko MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>"); 482