diff options
author | Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> | 2015-06-11 01:02:30 +0300 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2015-06-11 00:24:12 -0700 |
commit | a0d2f20650e81407d8e51ad2cbdc492861c74e9c (patch) | |
tree | 56c9c1c4f4fcf26d246f3a0a8649729e496edf22 | |
parent | c156633f1353264634135dea86ffcae74f2122fc (diff) |
Renesas Ethernet AVB PTP clock driver
Ethernet AVB device includes the gPTP timer, so we can implement a PTP clock
driver. We're doing that in a separate file, with the main Ethernet driver
calling the PTP driver's [de]initialization and interrupt handler functions.
Unfortunately, the clock seems tightly coupled with the AVB-DMAC, so when that
one leaves the operation mode, we have to unregister the PTP clock... :-(
Based on the original patches by Masaru Nagai.
Signed-off-by: Masaru Nagai <masaru.nagai.vx@renesas.com>
Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/ethernet/renesas/Makefile | 3 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/ravb.h | 26 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/ravb_main.c (renamed from drivers/net/ethernet/renesas/ravb.c) | 33 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/ravb_ptp.c | 357 |
4 files changed, 414 insertions, 5 deletions
diff --git a/drivers/net/ethernet/renesas/Makefile b/drivers/net/ethernet/renesas/Makefile index aa354a666fac..a05102a7df02 100644 --- a/drivers/net/ethernet/renesas/Makefile +++ b/drivers/net/ethernet/renesas/Makefile @@ -3,4 +3,7 @@ # obj-$(CONFIG_SH_ETH) += sh_eth.o + +ravb-objs := ravb_main.o ravb_ptp.o + obj-$(CONFIG_RAVB) += ravb.o diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h index f2656ebba084..8aa50ac4e2d6 100644 --- a/drivers/net/ethernet/renesas/ravb.h +++ b/drivers/net/ethernet/renesas/ravb.h @@ -20,6 +20,8 @@ #include <linux/mdio-bitbang.h> #include <linux/netdevice.h> #include <linux/phy.h> +#include <linux/platform_device.h> +#include <linux/ptp_clock_kernel.h> #define BE_TX_RING_SIZE 64 /* TX ring size for Best Effort */ #define BE_RX_RING_SIZE 1024 /* RX ring size for Best Effort */ @@ -744,6 +746,23 @@ struct ravb_tstamp_skb { u16 tag; }; +struct ravb_ptp_perout { + u32 target; + u32 period; +}; + +#define N_EXT_TS 1 +#define N_PER_OUT 1 + +struct ravb_ptp { + struct ptp_clock *clock; + struct ptp_clock_info info; + u32 default_addend; + u32 current_addend; + int extts[N_EXT_TS]; + struct ravb_ptp_perout perout[N_PER_OUT]; +}; + struct ravb_private { struct net_device *ndev; struct platform_device *pdev; @@ -768,6 +787,7 @@ struct ravb_private { u32 tstamp_rx_ctrl; struct list_head ts_skb_list; u32 ts_skb_tag; + struct ravb_ptp ptp; spinlock_t lock; /* Register access lock */ u32 cur_rx[NUM_RX_QUEUE]; /* Consumer ring indices */ u32 dirty_rx[NUM_RX_QUEUE]; /* Producer ring indices */ @@ -803,4 +823,10 @@ static inline void ravb_write(struct net_device *ndev, u32 data, iowrite32(data, priv->addr + reg); } +int ravb_wait(struct net_device *ndev, enum ravb_reg reg, u32 mask, u32 value); + +irqreturn_t ravb_ptp_interrupt(struct net_device *ndev); +void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev); +void ravb_ptp_stop(struct net_device *ndev); + #endif /* #ifndef __RAVB_H__ */ diff --git a/drivers/net/ethernet/renesas/ravb.c b/drivers/net/ethernet/renesas/ravb_main.c index 1fa9be203790..fd9745714d90 100644 --- a/drivers/net/ethernet/renesas/ravb.c +++ b/drivers/net/ethernet/renesas/ravb_main.c @@ -28,7 +28,6 @@ #include <linux/of_irq.h> #include <linux/of_mdio.h> #include <linux/of_net.h> -#include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/spinlock.h> @@ -41,8 +40,7 @@ NETIF_MSG_RX_ERR | \ NETIF_MSG_TX_ERR) -static int ravb_wait(struct net_device *ndev, enum ravb_reg reg, u32 mask, - u32 value) +int ravb_wait(struct net_device *ndev, enum ravb_reg reg, u32 mask, u32 value) { int i; @@ -763,6 +761,9 @@ static irqreturn_t ravb_interrupt(int irq, void *dev_id) result = IRQ_HANDLED; } + if (iss & ISS_CGIS) + result = ravb_ptp_interrupt(ndev); + mmiowb(); spin_unlock(&priv->lock); return result; @@ -1103,6 +1104,8 @@ static int ravb_set_ringparam(struct net_device *ndev, if (netif_running(ndev)) { netif_device_detach(ndev); + /* Stop PTP Clock driver */ + ravb_ptp_stop(ndev); /* Wait for DMA stopping */ error = ravb_stop_dma(ndev); if (error) { @@ -1132,6 +1135,9 @@ static int ravb_set_ringparam(struct net_device *ndev, ravb_emac_init(ndev); + /* Initialise PTP Clock driver */ + ravb_ptp_init(ndev, priv->pdev); + netif_device_attach(ndev); } @@ -1141,6 +1147,8 @@ static int ravb_set_ringparam(struct net_device *ndev, static int ravb_get_ts_info(struct net_device *ndev, struct ethtool_ts_info *info) { + struct ravb_private *priv = netdev_priv(ndev); + info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE | @@ -1153,7 +1161,7 @@ static int ravb_get_ts_info(struct net_device *ndev, (1 << HWTSTAMP_FILTER_NONE) | (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | (1 << HWTSTAMP_FILTER_ALL); - info->phc_index = -1; + info->phc_index = ptp_clock_index(priv->ptp.clock); return 0; } @@ -1195,15 +1203,21 @@ static int ravb_open(struct net_device *ndev) goto out_free_irq; ravb_emac_init(ndev); + /* Initialise PTP Clock driver */ + ravb_ptp_init(ndev, priv->pdev); + netif_tx_start_all_queues(ndev); /* PHY control start */ error = ravb_phy_start(ndev); if (error) - goto out_free_irq; + goto out_ptp_stop; return 0; +out_ptp_stop: + /* Stop PTP Clock driver */ + ravb_ptp_stop(ndev); out_free_irq: free_irq(ndev->irq, ndev); out_napi_off: @@ -1235,6 +1249,9 @@ static void ravb_tx_timeout_work(struct work_struct *work) netif_tx_stop_all_queues(ndev); + /* Stop PTP Clock driver */ + ravb_ptp_stop(ndev); + /* Wait for DMA stopping */ ravb_stop_dma(ndev); @@ -1245,6 +1262,9 @@ static void ravb_tx_timeout_work(struct work_struct *work) ravb_dmac_init(ndev); ravb_emac_init(ndev); + /* Initialise PTP Clock driver */ + ravb_ptp_init(ndev, priv->pdev); + netif_tx_start_all_queues(ndev); } @@ -1409,6 +1429,9 @@ static int ravb_close(struct net_device *ndev) ravb_write(ndev, 0, RIC2); ravb_write(ndev, 0, TIC); + /* Stop PTP Clock driver */ + ravb_ptp_stop(ndev); + /* Set the config mode to stop the AVB-DMAC's processes */ if (ravb_stop_dma(ndev) < 0) netdev_err(ndev, diff --git a/drivers/net/ethernet/renesas/ravb_ptp.c b/drivers/net/ethernet/renesas/ravb_ptp.c new file mode 100644 index 000000000000..42656da50500 --- /dev/null +++ b/drivers/net/ethernet/renesas/ravb_ptp.c @@ -0,0 +1,357 @@ +/* PTP 1588 clock using the Renesas Ethernet AVB + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * Copyright (C) 2015 Renesas Solutions Corp. + * Copyright (C) 2015 Cogent Embedded, Inc. <source@cogentembedded.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "ravb.h" + +static int ravb_ptp_tcr_request(struct ravb_private *priv, u32 request) +{ + struct net_device *ndev = priv->ndev; + int error; + + error = ravb_wait(ndev, GCCR, GCCR_TCR, GCCR_TCR_NOREQ); + if (error) + return error; + + ravb_write(ndev, ravb_read(ndev, GCCR) | request, GCCR); + return ravb_wait(ndev, GCCR, GCCR_TCR, GCCR_TCR_NOREQ); +} + +/* Caller must hold the lock */ +static int ravb_ptp_time_read(struct ravb_private *priv, struct timespec64 *ts) +{ + struct net_device *ndev = priv->ndev; + int error; + + error = ravb_ptp_tcr_request(priv, GCCR_TCR_CAPTURE); + if (error) + return error; + + ts->tv_nsec = ravb_read(ndev, GCT0); + ts->tv_sec = ravb_read(ndev, GCT1) | + ((s64)ravb_read(ndev, GCT2) << 32); + + return 0; +} + +/* Caller must hold the lock */ +static int ravb_ptp_time_write(struct ravb_private *priv, + const struct timespec64 *ts) +{ + struct net_device *ndev = priv->ndev; + int error; + u32 gccr; + + error = ravb_ptp_tcr_request(priv, GCCR_TCR_RESET); + if (error) + return error; + + gccr = ravb_read(ndev, GCCR); + if (gccr & GCCR_LTO) + return -EBUSY; + ravb_write(ndev, ts->tv_nsec, GTO0); + ravb_write(ndev, ts->tv_sec, GTO1); + ravb_write(ndev, (ts->tv_sec >> 32) & 0xffff, GTO2); + ravb_write(ndev, gccr | GCCR_LTO, GCCR); + + return 0; +} + +/* Caller must hold the lock */ +static int ravb_ptp_update_compare(struct ravb_private *priv, u32 ns) +{ + struct net_device *ndev = priv->ndev; + /* When the comparison value (GPTC.PTCV) is in range of + * [x-1 to x+1] (x is the configured increment value in + * GTI.TIV), it may happen that a comparison match is + * not detected when the timer wraps around. + */ + u32 gti_ns_plus_1 = (priv->ptp.current_addend >> 20) + 1; + u32 gccr; + + if (ns < gti_ns_plus_1) + ns = gti_ns_plus_1; + else if (ns > 0 - gti_ns_plus_1) + ns = 0 - gti_ns_plus_1; + + gccr = ravb_read(ndev, GCCR); + if (gccr & GCCR_LPTC) + return -EBUSY; + ravb_write(ndev, ns, GPTC); + ravb_write(ndev, gccr | GCCR_LPTC, GCCR); + + return 0; +} + +/* PTP clock operations */ +static int ravb_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) +{ + struct ravb_private *priv = container_of(ptp, struct ravb_private, + ptp.info); + struct net_device *ndev = priv->ndev; + unsigned long flags; + u32 diff, addend; + bool neg_adj = false; + u32 gccr; + + if (ppb < 0) { + neg_adj = true; + ppb = -ppb; + } + addend = priv->ptp.default_addend; + diff = div_u64((u64)addend * ppb, NSEC_PER_SEC); + + addend = neg_adj ? addend - diff : addend + diff; + + spin_lock_irqsave(&priv->lock, flags); + + priv->ptp.current_addend = addend; + + gccr = ravb_read(ndev, GCCR); + if (gccr & GCCR_LTI) + return -EBUSY; + ravb_write(ndev, addend & GTI_TIV, GTI); + ravb_write(ndev, gccr | GCCR_LTI, GCCR); + + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int ravb_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct ravb_private *priv = container_of(ptp, struct ravb_private, + ptp.info); + struct timespec64 ts; + unsigned long flags; + int error; + + spin_lock_irqsave(&priv->lock, flags); + error = ravb_ptp_time_read(priv, &ts); + if (!error) { + u64 now = ktime_to_ns(timespec64_to_ktime(ts)); + + ts = ns_to_timespec64(now + delta); + error = ravb_ptp_time_write(priv, &ts); + } + spin_unlock_irqrestore(&priv->lock, flags); + + return error; +} + +static int ravb_ptp_gettime64(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + struct ravb_private *priv = container_of(ptp, struct ravb_private, + ptp.info); + unsigned long flags; + int error; + + spin_lock_irqsave(&priv->lock, flags); + error = ravb_ptp_time_read(priv, ts); + spin_unlock_irqrestore(&priv->lock, flags); + + return error; +} + +static int ravb_ptp_settime64(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct ravb_private *priv = container_of(ptp, struct ravb_private, + ptp.info); + unsigned long flags; + int error; + + spin_lock_irqsave(&priv->lock, flags); + error = ravb_ptp_time_write(priv, ts); + spin_unlock_irqrestore(&priv->lock, flags); + + return error; +} + +static int ravb_ptp_extts(struct ptp_clock_info *ptp, + struct ptp_extts_request *req, int on) +{ + struct ravb_private *priv = container_of(ptp, struct ravb_private, + ptp.info); + struct net_device *ndev = priv->ndev; + unsigned long flags; + u32 gic; + + if (req->index) + return -EINVAL; + + if (priv->ptp.extts[req->index] == on) + return 0; + priv->ptp.extts[req->index] = on; + + spin_lock_irqsave(&priv->lock, flags); + gic = ravb_read(ndev, GIC); + if (on) + gic |= GIC_PTCE; + else + gic &= ~GIC_PTCE; + ravb_write(ndev, gic, GIC); + mmiowb(); + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int ravb_ptp_perout(struct ptp_clock_info *ptp, + struct ptp_perout_request *req, int on) +{ + struct ravb_private *priv = container_of(ptp, struct ravb_private, + ptp.info); + struct net_device *ndev = priv->ndev; + struct ravb_ptp_perout *perout; + unsigned long flags; + int error = 0; + u32 gic; + + if (req->index) + return -EINVAL; + + if (on) { + u64 start_ns; + u64 period_ns; + + start_ns = req->start.sec * NSEC_PER_SEC + req->start.nsec; + period_ns = req->period.sec * NSEC_PER_SEC + req->period.nsec; + + if (start_ns > U32_MAX) { + netdev_warn(ndev, + "ptp: start value (nsec) is over limit. Maximum size of start is only 32 bits\n"); + return -ERANGE; + } + + if (period_ns > U32_MAX) { + netdev_warn(ndev, + "ptp: period value (nsec) is over limit. Maximum size of period is only 32 bits\n"); + return -ERANGE; + } + + spin_lock_irqsave(&priv->lock, flags); + + perout = &priv->ptp.perout[req->index]; + perout->target = (u32)start_ns; + perout->period = (u32)period_ns; + error = ravb_ptp_update_compare(priv, (u32)start_ns); + if (!error) { + /* Unmask interrupt */ + gic = ravb_read(ndev, GIC); + gic |= GIC_PTME; + ravb_write(ndev, gic, GIC); + } + } else { + spin_lock_irqsave(&priv->lock, flags); + + perout = &priv->ptp.perout[req->index]; + perout->period = 0; + + /* Mask interrupt */ + gic = ravb_read(ndev, GIC); + gic &= ~GIC_PTME; + ravb_write(ndev, gic, GIC); + } + mmiowb(); + spin_unlock_irqrestore(&priv->lock, flags); + + return error; +} + +static int ravb_ptp_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *req, int on) +{ + switch (req->type) { + case PTP_CLK_REQ_EXTTS: + return ravb_ptp_extts(ptp, &req->extts, on); + case PTP_CLK_REQ_PEROUT: + return ravb_ptp_perout(ptp, &req->perout, on); + default: + return -EOPNOTSUPP; + } +} + +static const struct ptp_clock_info ravb_ptp_info = { + .owner = THIS_MODULE, + .name = "ravb clock", + .max_adj = 50000000, + .n_ext_ts = N_EXT_TS, + .n_per_out = N_PER_OUT, + .adjfreq = ravb_ptp_adjfreq, + .adjtime = ravb_ptp_adjtime, + .gettime64 = ravb_ptp_gettime64, + .settime64 = ravb_ptp_settime64, + .enable = ravb_ptp_enable, +}; + +/* Caller must hold the lock */ +irqreturn_t ravb_ptp_interrupt(struct net_device *ndev) +{ + struct ravb_private *priv = netdev_priv(ndev); + u32 gis = ravb_read(ndev, GIS); + + gis &= ravb_read(ndev, GIC); + if (gis & GIS_PTCF) { + struct ptp_clock_event event; + + event.type = PTP_CLOCK_EXTTS; + event.index = 0; + event.timestamp = ravb_read(ndev, GCPT); + ptp_clock_event(priv->ptp.clock, &event); + } + if (gis & GIS_PTMF) { + struct ravb_ptp_perout *perout = priv->ptp.perout; + + if (perout->period) { + perout->target += perout->period; + ravb_ptp_update_compare(priv, perout->target); + } + } + + if (gis) { + ravb_write(ndev, ~gis, GIS); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) +{ + struct ravb_private *priv = netdev_priv(ndev); + unsigned long flags; + u32 gccr; + + priv->ptp.info = ravb_ptp_info; + + priv->ptp.default_addend = ravb_read(ndev, GTI); + priv->ptp.current_addend = priv->ptp.default_addend; + + spin_lock_irqsave(&priv->lock, flags); + ravb_wait(ndev, GCCR, GCCR_TCR, GCCR_TCR_NOREQ); + gccr = ravb_read(ndev, GCCR) & ~GCCR_TCSS; + ravb_write(ndev, gccr | GCCR_TCSS_ADJGPTP, GCCR); + mmiowb(); + spin_unlock_irqrestore(&priv->lock, flags); + + priv->ptp.clock = ptp_clock_register(&priv->ptp.info, &pdev->dev); +} + +void ravb_ptp_stop(struct net_device *ndev) +{ + struct ravb_private *priv = netdev_priv(ndev); + + ravb_write(ndev, 0, GIC); + ravb_write(ndev, 0, GIS); + + ptp_clock_unregister(priv->ptp.clock); +} |