| /* |
| * Fast Ethernet Controller (ENET) PTP driver for MX6x. |
| * |
| * Copyright (C) 2012-2014 Freescale Semiconductor, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/ptrace.h> |
| #include <linux/errno.h> |
| #include <linux/ioport.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/pci.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/skbuff.h> |
| #include <linux/spinlock.h> |
| #include <linux/workqueue.h> |
| #include <linux/bitops.h> |
| #include <linux/io.h> |
| #include <linux/vmalloc.h> |
| #include <linux/ip.h> |
| #include <linux/udp.h> |
| #include <linux/irq.h> |
| #include <linux/clk.h> |
| #include <linux/platform_device.h> |
| #include <linux/phy.h> |
| #include <linux/fec.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_net.h> |
| |
| #include "fec.h" |
| |
| |
| /* FEC 1588 register bits */ |
| #define FEC_T_CTRL_SLAVE 0x00002000 |
| #define FEC_T_CTRL_CAPTURE 0x00000800 |
| #define FEC_T_CTRL_RESTART 0x00000200 |
| #define FEC_T_CTRL_PERIOD_RST 0x00000030 |
| #define FEC_T_CTRL_PERIOD_EN 0x00000010 |
| #define FEC_T_CTRL_ENABLE 0x00000001 |
| |
| #define FEC_T_INC_MASK 0x0000007f |
| #define FEC_T_INC_OFFSET 0 |
| #define FEC_T_INC_CORR_MASK 0x00007f00 |
| #define FEC_T_INC_CORR_OFFSET 8 |
| |
| #define FEC_ATIME_CTRL 0x400 |
| #define FEC_ATIME 0x404 |
| #define FEC_ATIME_EVT_OFFSET 0x408 |
| #define FEC_ATIME_EVT_PERIOD 0x40c |
| #define FEC_ATIME_CORR 0x410 |
| #define FEC_ATIME_INC 0x414 |
| #define FEC_TS_TIMESTAMP 0x418 |
| |
| #define FEC_CC_MULT (1 << 31) |
| |
| /* Alloc the ring resource */ |
| static int fec_ptp_init_circ(struct fec_ptp_circular *buf, int size) |
| { |
| buf->data_buf = (struct fec_ptp_ts_data *) |
| vmalloc(size * sizeof(struct fec_ptp_ts_data)); |
| |
| if (!buf->data_buf) |
| return 1; |
| buf->front = 0; |
| buf->end = 0; |
| buf->size = size; |
| return 0; |
| } |
| |
| static inline int fec_ptp_calc_index(int size, int curr_index, int offset) |
| { |
| return (curr_index + offset) % size; |
| } |
| |
| static int fec_ptp_is_empty(struct fec_ptp_circular *buf) |
| { |
| return (buf->front == buf->end); |
| } |
| |
| static int fec_ptp_nelems(struct fec_ptp_circular *buf) |
| { |
| const int front = buf->front; |
| const int end = buf->end; |
| const int size = buf->size; |
| int n_items; |
| |
| if (end > front) |
| n_items = end - front; |
| else if (end < front) |
| n_items = size - (front - end); |
| else |
| n_items = 0; |
| |
| return n_items; |
| } |
| |
| static int fec_ptp_is_full(struct fec_ptp_circular *buf) |
| { |
| if (fec_ptp_nelems(buf) == (buf->size - 1)) |
| return 1; |
| else |
| return 0; |
| } |
| |
| static int fec_ptp_insert(struct fec_ptp_circular *ptp_buf, |
| struct fec_ptp_ts_data *data) |
| { |
| struct fec_ptp_ts_data *tmp; |
| |
| tmp = (ptp_buf->data_buf + ptp_buf->end); |
| memcpy(tmp, data, sizeof(struct fec_ptp_ts_data)); |
| if (fec_ptp_is_full(ptp_buf)) |
| /* drop one in front */ |
| ptp_buf->front = |
| fec_ptp_calc_index(ptp_buf->size, ptp_buf->front, 1); |
| ptp_buf->end = fec_ptp_calc_index(ptp_buf->size, ptp_buf->end, 1); |
| |
| return 0; |
| } |
| |
| static int fec_ptp_find_and_remove(struct fec_ptp_circular *ptp_buf, |
| struct fec_ptp_ident *ident, struct ptp_time *ts) |
| { |
| int i; |
| int size = ptp_buf->size, end = ptp_buf->end; |
| struct fec_ptp_ident *tmp_ident; |
| |
| if (fec_ptp_is_empty(ptp_buf)) |
| return 1; |
| |
| i = ptp_buf->front; |
| while (i != end) { |
| tmp_ident = &(ptp_buf->data_buf + i)->ident; |
| if (tmp_ident->version == ident->version) { |
| if (tmp_ident->message_type == ident->message_type) { |
| if ((tmp_ident->netw_prot == ident->netw_prot) |
| || (ident->netw_prot == |
| FEC_PTP_PROT_DONTCARE)) { |
| if (tmp_ident->seq_id == |
| ident->seq_id) { |
| int ret = |
| memcmp(tmp_ident->spid, |
| ident->spid, |
| PTP_SOURCE_PORT_LENGTH); |
| if (0 == ret) |
| break; |
| } |
| } |
| } |
| } |
| /* get next */ |
| i = fec_ptp_calc_index(size, i, 1); |
| } |
| |
| /* not found ? */ |
| if (i == end) { |
| /* buffer full ? */ |
| if (fec_ptp_is_full(ptp_buf)) |
| /* drop one in front */ |
| ptp_buf->front = |
| fec_ptp_calc_index(size, ptp_buf->front, 1); |
| |
| return 1; |
| } |
| *ts = (ptp_buf->data_buf + i)->ts; |
| |
| return 0; |
| } |
| |
| /* 1588 Module intialization */ |
| void fec_ptp_start(struct net_device *ndev) |
| { |
| struct fec_enet_private *fep = netdev_priv(ndev); |
| unsigned long flags; |
| int inc; |
| |
| inc = FEC_T_PERIOD_ONE_SEC / clk_get_rate(fep->clk_ptp); |
| |
| spin_lock_irqsave(&fep->tmreg_lock, flags); |
| /* Select 1588 Timer source and enable module for starting Tmr Clock */ |
| writel(FEC_T_CTRL_RESTART, fep->hwp + FEC_ATIME_CTRL); |
| writel(inc << FEC_T_INC_OFFSET, |
| fep->hwp + FEC_ATIME_INC); |
| writel(FEC_T_PERIOD_ONE_SEC, fep->hwp + FEC_ATIME_EVT_PERIOD); |
| /* start counter */ |
| writel(FEC_T_CTRL_PERIOD_RST | FEC_T_CTRL_ENABLE, |
| fep->hwp + FEC_ATIME_CTRL); |
| spin_unlock_irqrestore(&fep->tmreg_lock, flags); |
| } |
| |
| /* Cleanup routine for 1588 module. |
| * When PTP is disabled this routing is called */ |
| void fec_ptp_stop(struct net_device *ndev) |
| { |
| struct fec_enet_private *priv = netdev_priv(ndev); |
| |
| writel(0, priv->hwp + FEC_ATIME_CTRL); |
| writel(FEC_T_CTRL_RESTART, priv->hwp + FEC_ATIME_CTRL); |
| |
| del_timer_sync(&priv->time_keep); |
| } |
| |
| static void fec_get_curr_cnt(struct fec_enet_private *priv, |
| struct ptp_rtc_time *curr_time) |
| { |
| u32 tempval, old_sec; |
| u32 timeout_event, timeout_ts = 0; |
| const struct platform_device_id *id_entry = |
| platform_get_device_id(priv->pdev); |
| |
| do { |
| old_sec = priv->prtc; |
| timeout_event = 0; |
| |
| tempval = readl(priv->hwp + FEC_ATIME_CTRL); |
| tempval |= FEC_T_CTRL_CAPTURE; |
| writel(tempval, priv->hwp + FEC_ATIME_CTRL); |
| if (id_entry->driver_data & FEC_QUIRK_TKT210590) |
| udelay(1); |
| curr_time->rtc_time.nsec = readl(priv->hwp + FEC_ATIME); |
| |
| while (readl(priv->hwp + FEC_IEVENT) & FEC_ENET_TS_TIMER) { |
| timeout_event++; |
| udelay(20); |
| |
| if (timeout_event >= FEC_PTP_TIMEOUT_EVENT) |
| break; |
| } |
| |
| curr_time->rtc_time.sec = priv->prtc; |
| timeout_ts++; |
| |
| if (timeout_event >= FEC_PTP_TIMEOUT_EVENT) |
| pr_err("timeout: TS TIMER event\n"); |
| |
| } while (old_sec != curr_time->rtc_time.sec && |
| timeout_ts < FEC_PTP_TIMEOUT_TS); |
| |
| if (timeout_ts >= FEC_PTP_TIMEOUT_TS) |
| pr_err("timeout: current timestamp unmatched\n"); |
| } |
| |
| /* Set the 1588 timer counter registers */ |
| static void fec_set_1588cnt(struct fec_enet_private *priv, |
| struct ptp_rtc_time *fec_time) |
| { |
| u32 tempval; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&priv->tmreg_lock, flags); |
| priv->prtc = fec_time->rtc_time.sec; |
| |
| tempval = fec_time->rtc_time.nsec; |
| writel(tempval, priv->hwp + FEC_ATIME); |
| spin_unlock_irqrestore(&priv->tmreg_lock, flags); |
| } |
| |
| /** |
| * Parse packets if they are PTP. |
| * The PTP header can be found in an IPv4, IPv6 or in an IEEE802.3 |
| * ethernet frame. The function returns the position of the PTP packet |
| * or NULL, if no PTP found |
| */ |
| u8 *fec_ptp_parse_packet(struct sk_buff *skb, u16 *eth_type) |
| { |
| u8 *position = skb->data + ETH_ALEN + ETH_ALEN; |
| u8 *ptp_loc = NULL; |
| |
| *eth_type = *((u16 *)position); |
| /* Check if outer vlan tag is here */ |
| if (ntohs(*eth_type) == ETH_P_8021Q) { |
| position += FEC_VLAN_TAG_LEN; |
| *eth_type = *((u16 *)position); |
| } |
| |
| /* set position after ethertype */ |
| position += FEC_ETHTYPE_LEN; |
| if (ETH_P_1588 == ntohs(*eth_type)) { |
| ptp_loc = position; |
| /* IEEE1588 event message which needs timestamping */ |
| if ((ptp_loc[0] & 0xF) <= 3) { |
| if (skb->len >= |
| ((ptp_loc - skb->data) + PTP_HEADER_SZE)) |
| return ptp_loc; |
| } |
| } else if (ETH_P_IP == ntohs(*eth_type)) { |
| u8 *ip_header, *prot, *udp_header; |
| u8 ip_version, ip_hlen; |
| ip_header = position; |
| ip_version = ip_header[0] >> 4; /* correct IP version? */ |
| if (0x04 == ip_version) { /* IPv4 */ |
| prot = ip_header + 9; /* protocol */ |
| if (FEC_PACKET_TYPE_UDP == *prot) { |
| u16 udp_dstPort; |
| /* retrieve the size of the ip-header |
| * with the first byte of the ip-header: |
| * version ( 4 bits) + Internet header |
| * length (4 bits) |
| */ |
| ip_hlen = (*ip_header & 0xf) * 4; |
| udp_header = ip_header + ip_hlen; |
| udp_dstPort = *((u16 *)(udp_header + 2)); |
| /* check the destination port address |
| * ( 319 (0x013F) = PTP event port ) |
| */ |
| if (ntohs(udp_dstPort) == PTP_EVENT_PORT) { |
| ptp_loc = udp_header + 8; |
| /* long enough ? */ |
| if (skb->len >= ((ptp_loc - skb->data) |
| + PTP_HEADER_SZE)) |
| return ptp_loc; |
| } |
| } |
| } |
| } else if (ETH_P_IPV6 == ntohs(*eth_type)) { |
| u8 *ip_header, *udp_header, *prot; |
| u8 ip_version; |
| ip_header = position; |
| ip_version = ip_header[0] >> 4; |
| if (0x06 == ip_version) { |
| prot = ip_header + 6; |
| if (FEC_PACKET_TYPE_UDP == *prot) { |
| u16 udp_dstPort; |
| udp_header = ip_header + 40; |
| udp_dstPort = *((u16 *)(udp_header + 2)); |
| /* check the destination port address |
| * ( 319 (0x013F) = PTP event port ) |
| */ |
| if (ntohs(udp_dstPort) == PTP_EVENT_PORT) { |
| ptp_loc = udp_header + 8; |
| /* long enough ? */ |
| if (skb->len >= ((ptp_loc - skb->data) |
| + PTP_HEADER_SZE)) |
| return ptp_loc; |
| } |
| } |
| } |
| } |
| |
| return NULL; /* no PTP frame */ |
| } |
| |
| /* Set the BD to ptp */ |
| int fec_ptp_do_txstamp(struct sk_buff *skb) |
| { |
| u8 *ptp_loc; |
| u16 eth_type; |
| |
| ptp_loc = fec_ptp_parse_packet(skb, ð_type); |
| if (ptp_loc != NULL) |
| return 1; |
| |
| return 0; |
| } |
| |
| void fec_ptp_store_txstamp(struct fec_enet_private *priv, |
| struct sk_buff *skb, |
| struct bufdesc *bdp) |
| { |
| struct fec_ptp_ts_data tmp_tx_time; |
| struct bufdesc_ex *bdp_ex = NULL; |
| struct ptp_rtc_time curr_time; |
| u8 *ptp_loc; |
| u16 eth_type; |
| |
| bdp_ex = container_of(bdp, struct bufdesc_ex, desc); |
| ptp_loc = fec_ptp_parse_packet(skb, ð_type); |
| if (ptp_loc != NULL) { |
| /* store identification data */ |
| switch (ntohs(eth_type)) { |
| case ETH_P_IP: |
| tmp_tx_time.ident.netw_prot = FEC_PTP_PROT_IPV4; |
| break; |
| case ETH_P_IPV6: |
| tmp_tx_time.ident.netw_prot = FEC_PTP_PROT_IPV6; |
| break; |
| case ETH_P_1588: |
| tmp_tx_time.ident.netw_prot = FEC_PTP_PROT_802_3; |
| break; |
| default: |
| return; |
| } |
| tmp_tx_time.ident.version = (*(ptp_loc + 1)) & 0X0F; |
| tmp_tx_time.ident.message_type = (*(ptp_loc)) & 0x0F; |
| tmp_tx_time.ident.seq_id = |
| ntohs(*((u16 *)(ptp_loc + PTP_HEADER_SEQ_OFFS))); |
| memcpy(tmp_tx_time.ident.spid, &ptp_loc[PTP_SPID_OFFS], |
| PTP_SOURCE_PORT_LENGTH); |
| /* store tx timestamp */ |
| fec_get_curr_cnt(priv, &curr_time); |
| if (curr_time.rtc_time.nsec < bdp_ex->ts) |
| tmp_tx_time.ts.sec = curr_time.rtc_time.sec - 1; |
| else |
| tmp_tx_time.ts.sec = curr_time.rtc_time.sec; |
| tmp_tx_time.ts.nsec = bdp_ex->ts; |
| /* insert timestamp in circular buffer */ |
| fec_ptp_insert(&(priv->tx_timestamps), &tmp_tx_time); |
| } |
| } |
| |
| void fec_ptp_store_rxstamp(struct fec_enet_private *priv, |
| struct sk_buff *skb, |
| struct bufdesc *bdp) |
| { |
| struct fec_ptp_ts_data tmp_rx_time; |
| struct bufdesc_ex *bdp_ex = NULL; |
| struct ptp_rtc_time curr_time; |
| u8 *ptp_loc; |
| u16 eth_type; |
| |
| bdp_ex = container_of(bdp, struct bufdesc_ex, desc); |
| ptp_loc = fec_ptp_parse_packet(skb, ð_type); |
| if (ptp_loc != NULL) { |
| /* store identification data */ |
| tmp_rx_time.ident.version = (*(ptp_loc + 1)) & 0X0F; |
| tmp_rx_time.ident.message_type = (*(ptp_loc)) & 0x0F; |
| switch (ntohs(eth_type)) { |
| case ETH_P_IP: |
| tmp_rx_time.ident.netw_prot = FEC_PTP_PROT_IPV4; |
| break; |
| case ETH_P_IPV6: |
| tmp_rx_time.ident.netw_prot = FEC_PTP_PROT_IPV6; |
| break; |
| case ETH_P_1588: |
| tmp_rx_time.ident.netw_prot = FEC_PTP_PROT_802_3; |
| break; |
| default: |
| return; |
| } |
| tmp_rx_time.ident.seq_id = |
| ntohs(*((u16 *)(ptp_loc + PTP_HEADER_SEQ_OFFS))); |
| memcpy(tmp_rx_time.ident.spid, &ptp_loc[PTP_SPID_OFFS], |
| PTP_SOURCE_PORT_LENGTH); |
| /* store rx timestamp */ |
| fec_get_curr_cnt(priv, &curr_time); |
| if (curr_time.rtc_time.nsec < bdp_ex->ts) |
| tmp_rx_time.ts.sec = curr_time.rtc_time.sec - 1; |
| else |
| tmp_rx_time.ts.sec = curr_time.rtc_time.sec; |
| tmp_rx_time.ts.nsec = bdp_ex->ts; |
| |
| /* insert timestamp in circular buffer */ |
| fec_ptp_insert(&(priv->rx_timestamps), &tmp_rx_time); |
| } |
| } |
| |
| |
| static void fec_handle_ptpdrift(struct fec_enet_private *priv, |
| struct ptp_set_comp *comp, struct ptp_time_correct *ptc) |
| { |
| u32 ndrift; |
| u32 i; |
| u32 ptp_ts_clk, ptp_inc; |
| |
| ptp_ts_clk = clk_get_rate(priv->clk_ptp); |
| ptp_inc = FEC_T_PERIOD_ONE_SEC / ptp_ts_clk; |
| |
| ndrift = comp->drift; |
| |
| if (ndrift == 0) { |
| ptc->corr_inc = 0; |
| ptc->corr_period = 0; |
| return; |
| } |
| |
| for (i = 1; i <= ptp_inc; i++) { |
| if (((i * FEC_T_PERIOD_ONE_SEC) / ndrift) > ptp_inc) { |
| ptc->corr_inc = i; |
| ptc->corr_period = ((i * FEC_T_PERIOD_ONE_SEC) / |
| (ptp_inc * ndrift)); |
| break; |
| } |
| } |
| |
| /* not found ? */ |
| if (i > ptp_inc) { |
| /* |
| * set it to high value - double speed |
| * correct in every clock step. |
| */ |
| ptc->corr_inc = ptp_inc; |
| ptc->corr_period = 1; |
| } |
| } |
| |
| static void fec_set_drift(struct fec_enet_private *priv, |
| struct ptp_set_comp *comp) |
| { |
| struct ptp_time_correct tc; |
| u32 tmp, corr_ns; |
| u32 ptp_inc; |
| |
| memset(&tc, 0, sizeof(struct ptp_time_correct)); |
| fec_handle_ptpdrift(priv, comp, &tc); |
| if (tc.corr_inc == 0) |
| return; |
| |
| ptp_inc = FEC_T_PERIOD_ONE_SEC / clk_get_rate(priv->clk_ptp); |
| if (comp->o_ops == TRUE) |
| corr_ns = ptp_inc + tc.corr_inc; |
| else |
| corr_ns = ptp_inc - tc.corr_inc; |
| |
| tmp = readl(priv->hwp + FEC_ATIME_INC) & FEC_T_INC_MASK; |
| tmp |= corr_ns << FEC_T_INC_CORR_OFFSET; |
| writel(tmp, priv->hwp + FEC_ATIME_INC); |
| writel(tc.corr_period, priv->hwp + FEC_ATIME_CORR); |
| } |
| |
| /** |
| * fec_ptp_read - read raw cycle counter (to be used by time counter) |
| * @cc: the cyclecounter structure |
| * |
| * this function reads the cyclecounter registers and is called by the |
| * cyclecounter structure used to construct a ns counter from the |
| * arbitrary fixed point registers |
| */ |
| static cycle_t fec_ptp_read(const struct cyclecounter *cc) |
| { |
| struct fec_enet_private *fep = |
| container_of(cc, struct fec_enet_private, cc); |
| const struct platform_device_id *id_entry = |
| platform_get_device_id(fep->pdev); |
| u32 tempval; |
| |
| tempval = readl(fep->hwp + FEC_ATIME_CTRL); |
| tempval |= FEC_T_CTRL_CAPTURE; |
| writel(tempval, fep->hwp + FEC_ATIME_CTRL); |
| if (id_entry->driver_data & FEC_QUIRK_TKT210590) |
| udelay(1); |
| return readl(fep->hwp + FEC_ATIME); |
| } |
| |
| /** |
| * fec_ptp_start_cyclecounter - create the cycle counter from hw |
| * @ndev: network device |
| * |
| * this function initializes the timecounter and cyclecounter |
| * structures for use in generated a ns counter from the arbitrary |
| * fixed point cycles registers in the hardware. |
| */ |
| void fec_ptp_start_cyclecounter(struct net_device *ndev) |
| { |
| struct fec_enet_private *fep = netdev_priv(ndev); |
| unsigned long flags; |
| int inc; |
| |
| inc = FEC_T_PERIOD_ONE_SEC / fep->cycle_speed; |
| |
| /* grab the ptp lock */ |
| spin_lock_irqsave(&fep->tmreg_lock, flags); |
| writel(FEC_T_CTRL_RESTART, fep->hwp + FEC_ATIME_CTRL); |
| |
| /* 1ns counter */ |
| writel(inc << FEC_T_INC_OFFSET, fep->hwp + FEC_ATIME_INC); |
| |
| if (fep->hwts_rx_en_ioctl || fep->hwts_tx_en_ioctl) { |
| writel(FEC_T_PERIOD_ONE_SEC, fep->hwp + FEC_ATIME_EVT_PERIOD); |
| /* start counter */ |
| writel(FEC_T_CTRL_PERIOD_RST | FEC_T_CTRL_ENABLE, |
| fep->hwp + FEC_ATIME_CTRL); |
| } else if (fep->hwts_tx_en || fep->hwts_tx_en) { |
| /* use free running count */ |
| writel(0, fep->hwp + FEC_ATIME_EVT_PERIOD); |
| writel(FEC_T_CTRL_ENABLE, fep->hwp + FEC_ATIME_CTRL); |
| } |
| |
| memset(&fep->cc, 0, sizeof(fep->cc)); |
| fep->cc.read = fec_ptp_read; |
| fep->cc.mask = CLOCKSOURCE_MASK(32); |
| fep->cc.shift = 31; |
| fep->cc.mult = FEC_CC_MULT; |
| |
| /* reset the ns time counter */ |
| timecounter_init(&fep->tc, &fep->cc, ktime_to_ns(ktime_get_real())); |
| |
| spin_unlock_irqrestore(&fep->tmreg_lock, flags); |
| |
| if (!timer_pending(&fep->time_keep) && fep->opened) |
| add_timer(&fep->time_keep); |
| } |
| |
| /** |
| * fec_ptp_adjfreq - adjust ptp cycle frequency |
| * @ptp: the ptp clock structure |
| * @ppb: parts per billion adjustment from base |
| * |
| * Adjust the frequency of the ptp cycle counter by the |
| * indicated ppb from the base frequency. |
| * |
| * Because ENET hardware frequency adjust is complex, |
| * using software method to do that. |
| */ |
| static int fec_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) |
| { |
| u64 diff; |
| unsigned long flags; |
| int neg_adj = 0; |
| u32 mult = FEC_CC_MULT; |
| |
| struct fec_enet_private *fep = |
| container_of(ptp, struct fec_enet_private, ptp_caps); |
| |
| if (ppb < 0) { |
| ppb = -ppb; |
| neg_adj = 1; |
| } |
| |
| diff = mult; |
| diff *= ppb; |
| diff = div_u64(diff, 1000000000ULL); |
| |
| spin_lock_irqsave(&fep->tmreg_lock, flags); |
| /* |
| * dummy read to set cycle_last in tc to now. |
| * So use adjusted mult to calculate when next call |
| * timercounter_read. |
| */ |
| timecounter_read(&fep->tc); |
| |
| fep->cc.mult = neg_adj ? mult - diff : mult + diff; |
| |
| spin_unlock_irqrestore(&fep->tmreg_lock, flags); |
| |
| return 0; |
| } |
| |
| /** |
| * fec_ptp_adjtime |
| * @ptp: the ptp clock structure |
| * @delta: offset to adjust the cycle counter by |
| * |
| * adjust the timer by resetting the timecounter structure. |
| */ |
| static int fec_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) |
| { |
| struct fec_enet_private *fep = |
| container_of(ptp, struct fec_enet_private, ptp_caps); |
| unsigned long flags; |
| u64 now; |
| |
| spin_lock_irqsave(&fep->tmreg_lock, flags); |
| |
| now = timecounter_read(&fep->tc); |
| now += delta; |
| |
| /* reset the timecounter */ |
| timecounter_init(&fep->tc, &fep->cc, now); |
| |
| spin_unlock_irqrestore(&fep->tmreg_lock, flags); |
| |
| return 0; |
| } |
| |
| /** |
| * fec_ptp_gettime |
| * @ptp: the ptp clock structure |
| * @ts: timespec structure to hold the current time value |
| * |
| * read the timecounter and return the correct value on ns, |
| * after converting it into a struct timespec. |
| */ |
| static int fec_ptp_gettime(struct ptp_clock_info *ptp, struct timespec *ts) |
| { |
| struct fec_enet_private *adapter = |
| container_of(ptp, struct fec_enet_private, ptp_caps); |
| u64 ns; |
| u32 remainder; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&adapter->tmreg_lock, flags); |
| ns = timecounter_read(&adapter->tc); |
| spin_unlock_irqrestore(&adapter->tmreg_lock, flags); |
| |
| ts->tv_sec = div_u64_rem(ns, 1000000000ULL, &remainder); |
| ts->tv_nsec = remainder; |
| |
| return 0; |
| } |
| |
| /** |
| * fec_ptp_settime |
| * @ptp: the ptp clock structure |
| * @ts: the timespec containing the new time for the cycle counter |
| * |
| * reset the timecounter to use a new base value instead of the kernel |
| * wall timer value. |
| */ |
| static int fec_ptp_settime(struct ptp_clock_info *ptp, |
| const struct timespec *ts) |
| { |
| struct fec_enet_private *fep = |
| container_of(ptp, struct fec_enet_private, ptp_caps); |
| |
| u64 ns; |
| unsigned long flags; |
| |
| ns = ts->tv_sec * 1000000000ULL; |
| ns += ts->tv_nsec; |
| |
| spin_lock_irqsave(&fep->tmreg_lock, flags); |
| timecounter_init(&fep->tc, &fep->cc, ns); |
| spin_unlock_irqrestore(&fep->tmreg_lock, flags); |
| return 0; |
| } |
| |
| /** |
| * fec_ptp_enable |
| * @ptp: the ptp clock structure |
| * @rq: the requested feature to change |
| * @on: whether to enable or disable the feature |
| * |
| */ |
| static int fec_ptp_enable(struct ptp_clock_info *ptp, |
| struct ptp_clock_request *rq, int on) |
| { |
| return -EOPNOTSUPP; |
| } |
| |
| /** |
| * fec_ptp_hwtstamp_ioctl - control hardware time stamping |
| * @ndev: pointer to net_device |
| * @ifreq: ioctl data |
| * @cmd: particular ioctl requested |
| */ |
| int fec_ptp_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) |
| { |
| struct fec_enet_private *fep = netdev_priv(ndev); |
| struct hwtstamp_config config; |
| struct ptp_rtc_time curr_time; |
| struct ptp_time rx_time, tx_time; |
| struct fec_ptp_ts_data p_ts; |
| struct fec_ptp_ts_data *p_ts_user; |
| struct ptp_set_comp p_comp; |
| u32 freq_compensation; |
| int retval = 0; |
| |
| switch (cmd) { |
| case SIOCSHWTSTAMP: |
| if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) |
| return -EFAULT; |
| |
| /* reserved for future extensions */ |
| if (config.flags) |
| return -EINVAL; |
| |
| switch (config.tx_type) { |
| case HWTSTAMP_TX_OFF: |
| fep->hwts_tx_en = 0; |
| break; |
| case HWTSTAMP_TX_ON: |
| fep->hwts_tx_en = 1; |
| fep->hwts_tx_en_ioctl = 0; |
| break; |
| default: |
| return -ERANGE; |
| } |
| |
| switch (config.rx_filter) { |
| case HWTSTAMP_FILTER_NONE: |
| if (fep->hwts_rx_en) |
| fep->hwts_rx_en = 0; |
| config.rx_filter = HWTSTAMP_FILTER_NONE; |
| break; |
| |
| default: |
| /* |
| * register RXMTRL must be set in order |
| * to do V1 packets, therefore it is not |
| * possible to time stamp both V1 Sync and |
| * Delay_Req messages and hardware does not support |
| * timestamping all packets => return error |
| */ |
| fep->hwts_rx_en = 1; |
| fep->hwts_rx_en_ioctl = 0; |
| config.rx_filter = HWTSTAMP_FILTER_ALL; |
| break; |
| } |
| |
| fec_ptp_start_cyclecounter(ndev); |
| return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? |
| -EFAULT : 0; |
| break; |
| case PTP_ENBL_TXTS_IOCTL: |
| case PTP_ENBL_RXTS_IOCTL: |
| fep->hwts_rx_en_ioctl = 1; |
| fep->hwts_tx_en_ioctl = 1; |
| fep->hwts_rx_en = 0; |
| fep->hwts_tx_en = 0; |
| fec_ptp_start_cyclecounter(ndev); |
| break; |
| case PTP_DSBL_RXTS_IOCTL: |
| case PTP_DSBL_TXTS_IOCTL: |
| fep->hwts_rx_en_ioctl = 0; |
| fep->hwts_tx_en_ioctl = 0; |
| break; |
| case PTP_GET_RX_TIMESTAMP: |
| p_ts_user = (struct fec_ptp_ts_data *)ifr->ifr_data; |
| if (0 != copy_from_user(&p_ts.ident, |
| &p_ts_user->ident, sizeof(p_ts.ident))) |
| return -EINVAL; |
| |
| if (fec_ptp_find_and_remove(&fep->rx_timestamps, |
| &p_ts.ident, &rx_time)) { |
| usleep_range(4000, 5000); |
| return -EAGAIN; |
| } |
| if (copy_to_user((void __user *)(&p_ts_user->ts), |
| &rx_time, sizeof(rx_time))) |
| return -EFAULT; |
| break; |
| case PTP_GET_TX_TIMESTAMP: |
| p_ts_user = (struct fec_ptp_ts_data *)ifr->ifr_data; |
| if (0 != copy_from_user(&p_ts.ident, |
| &p_ts_user->ident, sizeof(p_ts.ident))) |
| return -EINVAL; |
| |
| if (fec_ptp_find_and_remove(&fep->tx_timestamps, |
| &p_ts.ident, &tx_time)) { |
| usleep_range(4000, 5000); |
| return -EAGAIN; |
| } |
| if (copy_to_user((void __user *)(&p_ts_user->ts), |
| &tx_time, sizeof(tx_time))) |
| return -EFAULT; |
| break; |
| case PTP_GET_CURRENT_TIME: |
| fec_get_curr_cnt(fep, &curr_time); |
| if (0 != copy_to_user(ifr->ifr_data, |
| &(curr_time.rtc_time), |
| sizeof(struct ptp_time))) |
| return -EFAULT; |
| break; |
| case PTP_SET_RTC_TIME: |
| if (0 != copy_from_user(&(curr_time.rtc_time), |
| ifr->ifr_data, |
| sizeof(struct ptp_time))) |
| return -EINVAL; |
| fec_set_1588cnt(fep, &curr_time); |
| break; |
| case PTP_FLUSH_TIMESTAMP: |
| /* reset tx-timestamping buffer */ |
| fep->tx_timestamps.front = 0; |
| fep->tx_timestamps.end = 0; |
| fep->tx_timestamps.size = (DEFAULT_PTP_TX_BUF_SZ + 1); |
| /* reset rx-timestamping buffer */ |
| fep->rx_timestamps.front = 0; |
| fep->rx_timestamps.end = 0; |
| fep->rx_timestamps.size = (DEFAULT_PTP_RX_BUF_SZ + 1); |
| break; |
| case PTP_SET_COMPENSATION: |
| if (0 != copy_from_user(&p_comp, ifr->ifr_data, |
| sizeof(struct ptp_set_comp))) |
| return -EINVAL; |
| fec_set_drift(fep, &p_comp); |
| break; |
| case PTP_GET_ORIG_COMP: |
| freq_compensation = FEC_PTP_ORIG_COMP; |
| if (copy_to_user(ifr->ifr_data, &freq_compensation, |
| sizeof(freq_compensation)) > 0) |
| return -EFAULT; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return retval; |
| } |
| /** |
| * fec_time_keep - call timecounter_read every second to avoid timer overrun |
| * because ENET just support 32bit counter, will timeout in 4s |
| */ |
| static void fec_time_keep(unsigned long _data) |
| { |
| struct fec_enet_private *fep = (struct fec_enet_private *)_data; |
| u64 ns; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&fep->tmreg_lock, flags); |
| ns = timecounter_read(&fep->tc); |
| spin_unlock_irqrestore(&fep->tmreg_lock, flags); |
| |
| mod_timer(&fep->time_keep, jiffies + HZ); |
| } |
| |
| /** |
| * fec_ptp_init |
| * @ndev: The FEC network adapter |
| * |
| * This function performs the required steps for enabling ptp |
| * support. If ptp support has already been loaded it simply calls the |
| * cyclecounter init routine and exits. |
| */ |
| |
| void fec_ptp_init(struct platform_device *pdev) |
| { |
| struct net_device *ndev = platform_get_drvdata(pdev); |
| struct fec_enet_private *fep = netdev_priv(ndev); |
| |
| fep->ptp_caps.owner = THIS_MODULE; |
| snprintf(fep->ptp_caps.name, 16, "fec ptp"); |
| |
| fep->ptp_caps.max_adj = 250000000; |
| fep->ptp_caps.n_alarm = 0; |
| fep->ptp_caps.n_ext_ts = 0; |
| fep->ptp_caps.n_per_out = 0; |
| fep->ptp_caps.pps = 0; |
| fep->ptp_caps.adjfreq = fec_ptp_adjfreq; |
| fep->ptp_caps.adjtime = fec_ptp_adjtime; |
| fep->ptp_caps.gettime = fec_ptp_gettime; |
| fep->ptp_caps.settime = fec_ptp_settime; |
| fep->ptp_caps.enable = fec_ptp_enable; |
| |
| fep->cycle_speed = clk_get_rate(fep->clk_ptp); |
| |
| spin_lock_init(&fep->tmreg_lock); |
| |
| fec_ptp_start_cyclecounter(ndev); |
| |
| init_timer(&fep->time_keep); |
| fep->time_keep.data = (unsigned long)fep; |
| fep->time_keep.function = fec_time_keep; |
| fep->time_keep.expires = jiffies + HZ; |
| |
| fep->ptp_clock = ptp_clock_register(&fep->ptp_caps, &pdev->dev); |
| if (IS_ERR(fep->ptp_clock)) { |
| fep->ptp_clock = NULL; |
| pr_err("ptp_clock_register failed\n"); |
| } |
| |
| /* initialize circular buffer for tx timestamps */ |
| if (fec_ptp_init_circ(&(fep->tx_timestamps), |
| (DEFAULT_PTP_TX_BUF_SZ+1))) |
| pr_err("init tx circular buffer failed\n"); |
| /* initialize circular buffer for rx timestamps */ |
| if (fec_ptp_init_circ(&(fep->rx_timestamps), |
| (DEFAULT_PTP_RX_BUF_SZ+1))) |
| pr_err("init rx curcular buffer failed\n"); |
| } |
| |
| void fec_ptp_cleanup(struct fec_enet_private *priv) |
| { |
| if (priv->tx_timestamps.data_buf) |
| vfree(priv->tx_timestamps.data_buf); |
| if (priv->rx_timestamps.data_buf) |
| vfree(priv->rx_timestamps.data_buf); |
| } |
| |