| /* |
| * Fast Ethernet Controller (ENET) PTP driver for MX6x. |
| * |
| * Copyright (C) 2012 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/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/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) |
| /** |
| * 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); |
| u32 tempval; |
| |
| tempval = readl(fep->hwp + FEC_ATIME_CTRL); |
| tempval |= FEC_T_CTRL_CAPTURE; |
| writel(tempval, fep->hwp + FEC_ATIME_CTRL); |
| |
| 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 = 1000000000 / fep->cycle_speed; |
| |
| /* grab the ptp lock */ |
| spin_lock_irqsave(&fep->tmreg_lock, flags); |
| |
| /* 1ns counter */ |
| writel(inc << FEC_T_INC_OFFSET, fep->hwp + FEC_ATIME_INC); |
| |
| /* 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); |
| } |
| |
| /** |
| * 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_set(struct net_device *ndev, struct ifreq *ifr) |
| { |
| struct fec_enet_private *fep = netdev_priv(ndev); |
| |
| struct hwtstamp_config config; |
| |
| 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; |
| 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; |
| config.rx_filter = HWTSTAMP_FILTER_ALL; |
| break; |
| } |
| |
| return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? |
| -EFAULT : 0; |
| } |
| |
| int fec_ptp_get(struct net_device *ndev, struct ifreq *ifr) |
| { |
| struct fec_enet_private *fep = netdev_priv(ndev); |
| struct hwtstamp_config config; |
| |
| config.flags = 0; |
| config.tx_type = fep->hwts_tx_en ? HWTSTAMP_TX_ON : HWTSTAMP_TX_OFF; |
| config.rx_filter = (fep->hwts_rx_en ? |
| HWTSTAMP_FILTER_ALL : HWTSTAMP_FILTER_NONE); |
| |
| return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? |
| -EFAULT : 0; |
| } |
| |
| /** |
| * 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; |
| add_timer(&fep->time_keep); |
| |
| 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"); |
| } |
| } |