Added macb (Cadence GEM / RP1) driver for kernel 6.18.

Native EtherCAT device driver for the Raspberry Pi 5 on-RP1 gigabit MAC
(Cadence GEM, "macb"), ported from the genet recipe. Lets the IgH kernel
master poll-drive the NIC directly instead of via the generic driver.

Derived from the in-tree macb driver at Linux 6.18.33
(drivers/net/ethernet/cadence: macb_main.c, macb.h, macb_ptp.c); the
-orig files are verbatim, the -ethercat files add the ecdev hooks
(ecdev_offer/open/close/withdraw, ec_poll, gem_rx -> ecdev_receive,
a persistent TX skb ring, IRQ/NAPI/PTP gated by get_ecdev()).

macb-specific points vs the genet recipe:
- ecdev_offer() before macb_init() (macb requests its IRQs there).
- phylink (not MDIO adjust_link); phylink_start/stop wrapped in rtnl_lock
  in EtherCAT mode (ecdev_open runs without the RTNL lock).
- MAC PHC / PTP init gated off in EtherCAT mode.
- macb_pad_and_fcs(): the master reuses one persistent TX skb and rewrites
  skb->len each cycle without resetting the tail, so re-sync the tail and
  append the software FCS in place; a plain skb_put() walked the tail past
  the buffer end -> skb_over_panic().

Tested on a Raspberry Pi 5 (BCM2712, 6.18.33-rt) against an A6-EC servo
drive: binds the RP1 GEM, reaches OP, 0 lost frames at up to 4 kHz.

Build: ./configure --enable-macb [--with-macb-kernel=6.18]

Known limitations: RX allocates an skb per frame in the poll path (a future
revision can reuse the buffer in place); only the GEM RX path carries the
hook (RP1 is GEM); PM suspend/resume is not specially handled.
This commit is contained in:
Danila Dergachev
2026-06-26 17:58:43 +02:00
parent beb2bf07df
commit a40c85c03a
12 changed files with 16366 additions and 0 deletions
+48
View File
@@ -491,6 +491,52 @@ fi
AC_SUBST(KERNEL_GENET, [$kernelgenet])
#-----------------------------------------------------------------------------
# macb driver
#-----------------------------------------------------------------------------
AC_ARG_ENABLE([macb],
AS_HELP_STRING([--enable-macb],
[Build macb driver for Raspi 5 [default=no]]),
[
case "${enableval}" in
yes) enablemacb=1
;;
no) enablemacb=0
;;
*) AC_MSG_ERROR([Invalid value for --enable-macb])
;;
esac
],
[enablemacb=0]
)
AM_CONDITIONAL(ENABLE_MACB, test "x$enablemacb" = "x1")
AC_SUBST(ENABLE_MACB, [$enablemacb])
AC_ARG_WITH([macb-kernel],
AS_HELP_STRING([--with-macb-kernel=<X.Y.Z>],
[macb kernel (only if differing)]),
[
kernelmacb=[$withval]
],
[
kernelmacb=$linuxversion
]
)
if test "x${enablemacb}" = "x1"; then
AC_MSG_CHECKING([for kernel for macb driver])
if test ! -f "${srcdir}/devices/macb/macb_main-${kernelmacb}-orig.c"; then
AC_MSG_ERROR([kernel $kernelmacb not available for macb driver!])
fi
AC_MSG_RESULT([$kernelmacb])
fi
AC_SUBST(KERNEL_MACB, [$kernelmacb])
#-----------------------------------------------------------------------------
# igb driver
#-----------------------------------------------------------------------------
@@ -1443,6 +1489,8 @@ AC_CONFIG_FILES([
devices/igb/Makefile
devices/igc/Kbuild
devices/igc/Makefile
devices/macb/Kbuild
devices/macb/Makefile
devices/r8169/Kbuild
devices/r8169/Makefile
devices/stmmac/Kbuild
+4
View File
@@ -82,6 +82,10 @@ ifeq (@ENABLE_IGC@,1)
obj-m += igc/
endif
ifeq (@ENABLE_MACB@,1)
obj-m += macb/
endif
ifeq (@ENABLE_R8169@,1)
ifeq (@R8169_IN_SUBDIR@,1)
obj-m += r8169/
+1
View File
@@ -28,6 +28,7 @@ SUBDIRS = \
genet \
igb \
igc \
macb \
r8169 \
stmmac
+54
View File
@@ -0,0 +1,54 @@
#-----------------------------------------------------------------------------
#
# Copyright (C) 2006-2017 Florian Pose, Ingenieurgemeinschaft IgH
#
# This file is part of the IgH EtherCAT Master.
#
# The IgH EtherCAT Master is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 2, as
# published by the Free Software Foundation.
#
# The IgH EtherCAT Master is distributed in the hope that 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 the IgH EtherCAT Master; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ---
#
# vim: syntax=make
#
#-----------------------------------------------------------------------------
src := @abs_srcdir@
ccflags-y := -I@abs_top_builddir@
TOPDIR := $(src)/../..
REV := $(shell if test -s $(TOPDIR)/revision; then \
cat $(TOPDIR)/revision; \
else \
git -C $(TOPDIR) describe 2>/dev/null || echo "unknown"; \
fi)
ifeq (@ENABLE_MACB@,1)
obj-m += ec_macb.o
ec_macb-objs := \
macb_main-@KERNEL_MACB@-ethercat.o \
macb_ptp-@KERNEL_MACB@-ethercat.o
ifeq (@ENABLE_DRIVER_RESOURCE_VERIFYING@,1)
ccflags-y := -DEC_ENABLE_DRIVER_RESOURCE_VERIFYING
endif
CFLAGS_macb_main-@KERNEL_MACB@-ethercat.o = -DREV="\"$(REV)\""
endif
KBUILD_EXTRA_SYMBOLS := \
@abs_top_builddir@/$(LINUX_SYMVERS) \
@abs_top_builddir@/master/$(LINUX_SYMVERS)
#-----------------------------------------------------------------------------
+33
View File
@@ -0,0 +1,33 @@
#------------------------------------------------------------------------------
#
# Copyright (C) 2006-2021 Florian Pose, Ingenieurgemeinschaft IgH
#
# This file is part of the IgH EtherCAT Master.
#
# The IgH EtherCAT Master is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 2, as
# published by the Free Software Foundation.
#
# The IgH EtherCAT Master is distributed in the hope that 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 the IgH EtherCAT Master; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#------------------------------------------------------------------------------
include $(top_srcdir)/Makefile.kbuild
EXTRA_DIST = \
macb_main-6.18-ethercat.c \
macb_main-6.18-orig.c \
macb-6.18-ethercat.h \
macb-6.18-orig.h \
macb_ptp-6.18-ethercat.c \
macb_ptp-6.18-orig.c \
update.sh
#------------------------------------------------------------------------------
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+468
View File
@@ -0,0 +1,468 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 1588 PTP support for Cadence GEM device.
*
* Copyright (C) 2017 Cadence Design Systems - https://www.cadence.com
*
* Authors: Rafal Ozieblo <rafalo@cadence.com>
* Bartosz Folta <bfolta@cadence.com>
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/time64.h>
#include <linux/ptp_classify.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
#include <linux/circ_buf.h>
#include <linux/spinlock.h>
#include "macb-6.18-ethercat.h"
#define GEM_PTP_TIMER_NAME "gem-ptp-timer"
static struct macb_dma_desc_ptp *macb_ptp_desc(struct macb *bp,
struct macb_dma_desc *desc)
{
if (bp->hw_dma_cap == HW_DMA_CAP_PTP)
return (struct macb_dma_desc_ptp *)
((u8 *)desc + sizeof(struct macb_dma_desc));
if (bp->hw_dma_cap == HW_DMA_CAP_64B_PTP)
return (struct macb_dma_desc_ptp *)
((u8 *)desc + sizeof(struct macb_dma_desc)
+ sizeof(struct macb_dma_desc_64));
return NULL;
}
static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
unsigned long flags;
long first, second;
u32 secl, sech;
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
ptp_read_system_prets(sts);
first = gem_readl(bp, TN);
ptp_read_system_postts(sts);
secl = gem_readl(bp, TSL);
sech = gem_readl(bp, TSH);
second = gem_readl(bp, TN);
/* test for nsec rollover */
if (first > second) {
/* if so, use later read & re-read seconds
* (assume all done within 1s)
*/
ptp_read_system_prets(sts);
ts->tv_nsec = gem_readl(bp, TN);
ptp_read_system_postts(sts);
secl = gem_readl(bp, TSL);
sech = gem_readl(bp, TSH);
} else {
ts->tv_nsec = first;
}
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
ts->tv_sec = (((u64)sech << GEM_TSL_SIZE) | secl)
& TSU_SEC_MAX_VAL;
return 0;
}
static int gem_tsu_set_time(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
unsigned long flags;
u32 ns, sech, secl;
secl = (u32)ts->tv_sec;
sech = (ts->tv_sec >> GEM_TSL_SIZE) & ((1 << GEM_TSH_SIZE) - 1);
ns = ts->tv_nsec;
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
/* TSH doesn't latch the time and no atomicity! */
gem_writel(bp, TN, 0); /* clear to avoid overflow */
gem_writel(bp, TSH, sech);
/* write lower bits 2nd, for synchronized secs update */
gem_writel(bp, TSL, secl);
gem_writel(bp, TN, ns);
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
return 0;
}
static int gem_tsu_incr_set(struct macb *bp, struct tsu_incr *incr_spec)
{
unsigned long flags;
/* tsu_timer_incr register must be written after
* the tsu_timer_incr_sub_ns register and the write operation
* will cause the value written to the tsu_timer_incr_sub_ns register
* to take effect.
*/
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
/* RegBit[15:0] = Subns[23:8]; RegBit[31:24] = Subns[7:0] */
gem_writel(bp, TISUBN, GEM_BF(SUBNSINCRL, incr_spec->sub_ns) |
GEM_BF(SUBNSINCRH, (incr_spec->sub_ns >>
GEM_SUBNSINCRL_SIZE)));
gem_writel(bp, TI, GEM_BF(NSINCR, incr_spec->ns));
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
return 0;
}
static int gem_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
struct tsu_incr incr_spec;
bool neg_adj = false;
u32 word;
u64 adj;
if (scaled_ppm < 0) {
neg_adj = true;
scaled_ppm = -scaled_ppm;
}
/* Adjustment is relative to base frequency */
incr_spec.sub_ns = bp->tsu_incr.sub_ns;
incr_spec.ns = bp->tsu_incr.ns;
/* scaling: unused(8bit) | ns(8bit) | fractions(16bit) */
word = ((u64)incr_spec.ns << GEM_SUBNSINCR_SIZE) + incr_spec.sub_ns;
adj = (u64)scaled_ppm * word;
/* Divide with rounding, equivalent to floating dividing:
* (temp / USEC_PER_SEC) + 0.5
*/
adj += (USEC_PER_SEC >> 1);
adj >>= PPM_FRACTION; /* remove fractions */
adj = div_u64(adj, USEC_PER_SEC);
adj = neg_adj ? (word - adj) : (word + adj);
incr_spec.ns = (adj >> GEM_SUBNSINCR_SIZE)
& ((1 << GEM_NSINCR_SIZE) - 1);
incr_spec.sub_ns = adj & ((1 << GEM_SUBNSINCR_SIZE) - 1);
gem_tsu_incr_set(bp, &incr_spec);
return 0;
}
static int gem_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
struct timespec64 now, then = ns_to_timespec64(delta);
u32 adj, sign = 0;
if (delta < 0) {
sign = 1;
delta = -delta;
}
if (delta > TSU_NSEC_MAX_VAL) {
gem_tsu_get_time(&bp->ptp_clock_info, &now, NULL);
now = timespec64_add(now, then);
gem_tsu_set_time(&bp->ptp_clock_info,
(const struct timespec64 *)&now);
} else {
adj = (sign << GEM_ADDSUB_OFFSET) | delta;
gem_writel(bp, TA, adj);
}
return 0;
}
static int gem_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
return -EOPNOTSUPP;
}
static const struct ptp_clock_info gem_ptp_caps_template = {
.owner = THIS_MODULE,
.name = GEM_PTP_TIMER_NAME,
.max_adj = 0,
.n_alarm = 0,
.n_ext_ts = 0,
.n_per_out = 0,
.n_pins = 0,
.pps = 1,
.adjfine = gem_ptp_adjfine,
.adjtime = gem_ptp_adjtime,
.gettimex64 = gem_tsu_get_time,
.settime64 = gem_tsu_set_time,
.enable = gem_ptp_enable,
};
static void gem_ptp_init_timer(struct macb *bp)
{
u32 rem = 0;
u64 adj;
bp->tsu_incr.ns = div_u64_rem(NSEC_PER_SEC, bp->tsu_rate, &rem);
if (rem) {
adj = rem;
adj <<= GEM_SUBNSINCR_SIZE;
bp->tsu_incr.sub_ns = div_u64(adj, bp->tsu_rate);
} else {
bp->tsu_incr.sub_ns = 0;
}
}
static void gem_ptp_init_tsu(struct macb *bp)
{
struct timespec64 ts;
/* 1. get current system time */
ts = ns_to_timespec64(ktime_to_ns(ktime_get_real()));
/* 2. set ptp timer */
gem_tsu_set_time(&bp->ptp_clock_info, &ts);
/* 3. set PTP timer increment value to BASE_INCREMENT */
gem_tsu_incr_set(bp, &bp->tsu_incr);
gem_writel(bp, TA, 0);
}
static void gem_ptp_clear_timer(struct macb *bp)
{
bp->tsu_incr.sub_ns = 0;
bp->tsu_incr.ns = 0;
gem_writel(bp, TISUBN, GEM_BF(SUBNSINCR, 0));
gem_writel(bp, TI, GEM_BF(NSINCR, 0));
gem_writel(bp, TA, 0);
}
static int gem_hw_timestamp(struct macb *bp, u32 dma_desc_ts_1,
u32 dma_desc_ts_2, struct timespec64 *ts)
{
struct timespec64 tsu;
ts->tv_sec = (GEM_BFEXT(DMA_SECH, dma_desc_ts_2) << GEM_DMA_SECL_SIZE) |
GEM_BFEXT(DMA_SECL, dma_desc_ts_1);
ts->tv_nsec = GEM_BFEXT(DMA_NSEC, dma_desc_ts_1);
/* TSU overlapping workaround
* The timestamp only contains lower few bits of seconds,
* so add value from 1588 timer
*/
gem_tsu_get_time(&bp->ptp_clock_info, &tsu, NULL);
ts->tv_sec |= ((~GEM_DMA_SEC_MASK) & tsu.tv_sec);
/* If the top bit is set in the timestamp,
* but not in 1588 timer, it has rolled over,
* so subtract max size
*/
if ((ts->tv_sec & (GEM_DMA_SEC_TOP >> 1)) &&
!(tsu.tv_sec & (GEM_DMA_SEC_TOP >> 1)))
ts->tv_sec -= GEM_DMA_SEC_TOP;
return 0;
}
void gem_ptp_rxstamp(struct macb *bp, struct sk_buff *skb,
struct macb_dma_desc *desc)
{
struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb);
struct macb_dma_desc_ptp *desc_ptp;
struct timespec64 ts;
if (GEM_BFEXT(DMA_RXVALID, desc->addr)) {
desc_ptp = macb_ptp_desc(bp, desc);
/* Unlikely but check */
if (!desc_ptp) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not supported in BD\n");
return;
}
gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
shhwtstamps->hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
}
}
void gem_ptp_txstamp(struct macb *bp, struct sk_buff *skb,
struct macb_dma_desc *desc)
{
struct skb_shared_hwtstamps shhwtstamps;
struct macb_dma_desc_ptp *desc_ptp;
struct timespec64 ts;
if (!GEM_BFEXT(DMA_TXVALID, desc->ctrl)) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not set in TX BD as expected\n");
return;
}
desc_ptp = macb_ptp_desc(bp, desc);
/* Unlikely but check */
if (!desc_ptp) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not supported in BD\n");
return;
}
/* ensure ts_1/ts_2 is loaded after ctrl (TX_USED check) */
dma_rmb();
gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
skb_tstamp_tx(skb, &shhwtstamps);
}
void gem_ptp_init(struct net_device *dev)
{
struct macb *bp = netdev_priv(dev);
bp->ptp_clock_info = gem_ptp_caps_template;
/* nominal frequency and maximum adjustment in ppb */
bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp);
bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj();
gem_ptp_init_timer(bp);
bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &dev->dev);
if (IS_ERR(bp->ptp_clock)) {
pr_err("ptp clock register failed: %ld\n",
PTR_ERR(bp->ptp_clock));
bp->ptp_clock = NULL;
return;
} else if (bp->ptp_clock == NULL) {
pr_err("ptp clock register failed\n");
return;
}
spin_lock_init(&bp->tsu_clk_lock);
gem_ptp_init_tsu(bp);
dev_info(&bp->pdev->dev, "%s ptp clock registered.\n",
GEM_PTP_TIMER_NAME);
}
void gem_ptp_remove(struct net_device *ndev)
{
struct macb *bp = netdev_priv(ndev);
if (bp->ptp_clock) {
ptp_clock_unregister(bp->ptp_clock);
bp->ptp_clock = NULL;
}
gem_ptp_clear_timer(bp);
dev_info(&bp->pdev->dev, "%s ptp clock unregistered.\n",
GEM_PTP_TIMER_NAME);
}
static int gem_ptp_set_ts_mode(struct macb *bp,
enum macb_bd_control tx_bd_control,
enum macb_bd_control rx_bd_control)
{
gem_writel(bp, TXBDCTRL, GEM_BF(TXTSMODE, tx_bd_control));
gem_writel(bp, RXBDCTRL, GEM_BF(RXTSMODE, rx_bd_control));
return 0;
}
int gem_get_hwtst(struct net_device *dev,
struct kernel_hwtstamp_config *tstamp_config)
{
struct macb *bp = netdev_priv(dev);
*tstamp_config = bp->tstamp_config;
if ((bp->hw_dma_cap & HW_DMA_CAP_PTP) == 0)
return -EOPNOTSUPP;
return 0;
}
static void gem_ptp_set_one_step_sync(struct macb *bp, u8 enable)
{
u32 reg_val;
reg_val = macb_readl(bp, NCR);
if (enable)
macb_writel(bp, NCR, reg_val | MACB_BIT(OSSMODE));
else
macb_writel(bp, NCR, reg_val & ~MACB_BIT(OSSMODE));
}
int gem_set_hwtst(struct net_device *dev,
struct kernel_hwtstamp_config *tstamp_config,
struct netlink_ext_ack *extack)
{
enum macb_bd_control tx_bd_control = TSTAMP_DISABLED;
enum macb_bd_control rx_bd_control = TSTAMP_DISABLED;
struct macb *bp = netdev_priv(dev);
u32 regval;
if ((bp->hw_dma_cap & HW_DMA_CAP_PTP) == 0)
return -EOPNOTSUPP;
switch (tstamp_config->tx_type) {
case HWTSTAMP_TX_OFF:
break;
case HWTSTAMP_TX_ONESTEP_SYNC:
gem_ptp_set_one_step_sync(bp, 1);
tx_bd_control = TSTAMP_ALL_FRAMES;
break;
case HWTSTAMP_TX_ON:
gem_ptp_set_one_step_sync(bp, 0);
tx_bd_control = TSTAMP_ALL_FRAMES;
break;
default:
return -ERANGE;
}
switch (tstamp_config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
break;
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
break;
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
break;
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
rx_bd_control = TSTAMP_ALL_PTP_FRAMES;
tstamp_config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
regval = macb_readl(bp, NCR);
macb_writel(bp, NCR, (regval | MACB_BIT(SRTSM)));
break;
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_ALL:
rx_bd_control = TSTAMP_ALL_FRAMES;
tstamp_config->rx_filter = HWTSTAMP_FILTER_ALL;
break;
default:
tstamp_config->rx_filter = HWTSTAMP_FILTER_NONE;
return -ERANGE;
}
bp->tstamp_config = *tstamp_config;
if (gem_ptp_set_ts_mode(bp, tx_bd_control, rx_bd_control) != 0)
return -ERANGE;
return 0;
}
+468
View File
@@ -0,0 +1,468 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 1588 PTP support for Cadence GEM device.
*
* Copyright (C) 2017 Cadence Design Systems - https://www.cadence.com
*
* Authors: Rafal Ozieblo <rafalo@cadence.com>
* Bartosz Folta <bfolta@cadence.com>
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/time64.h>
#include <linux/ptp_classify.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
#include <linux/circ_buf.h>
#include <linux/spinlock.h>
#include "macb.h"
#define GEM_PTP_TIMER_NAME "gem-ptp-timer"
static struct macb_dma_desc_ptp *macb_ptp_desc(struct macb *bp,
struct macb_dma_desc *desc)
{
if (bp->hw_dma_cap == HW_DMA_CAP_PTP)
return (struct macb_dma_desc_ptp *)
((u8 *)desc + sizeof(struct macb_dma_desc));
if (bp->hw_dma_cap == HW_DMA_CAP_64B_PTP)
return (struct macb_dma_desc_ptp *)
((u8 *)desc + sizeof(struct macb_dma_desc)
+ sizeof(struct macb_dma_desc_64));
return NULL;
}
static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
unsigned long flags;
long first, second;
u32 secl, sech;
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
ptp_read_system_prets(sts);
first = gem_readl(bp, TN);
ptp_read_system_postts(sts);
secl = gem_readl(bp, TSL);
sech = gem_readl(bp, TSH);
second = gem_readl(bp, TN);
/* test for nsec rollover */
if (first > second) {
/* if so, use later read & re-read seconds
* (assume all done within 1s)
*/
ptp_read_system_prets(sts);
ts->tv_nsec = gem_readl(bp, TN);
ptp_read_system_postts(sts);
secl = gem_readl(bp, TSL);
sech = gem_readl(bp, TSH);
} else {
ts->tv_nsec = first;
}
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
ts->tv_sec = (((u64)sech << GEM_TSL_SIZE) | secl)
& TSU_SEC_MAX_VAL;
return 0;
}
static int gem_tsu_set_time(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
unsigned long flags;
u32 ns, sech, secl;
secl = (u32)ts->tv_sec;
sech = (ts->tv_sec >> GEM_TSL_SIZE) & ((1 << GEM_TSH_SIZE) - 1);
ns = ts->tv_nsec;
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
/* TSH doesn't latch the time and no atomicity! */
gem_writel(bp, TN, 0); /* clear to avoid overflow */
gem_writel(bp, TSH, sech);
/* write lower bits 2nd, for synchronized secs update */
gem_writel(bp, TSL, secl);
gem_writel(bp, TN, ns);
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
return 0;
}
static int gem_tsu_incr_set(struct macb *bp, struct tsu_incr *incr_spec)
{
unsigned long flags;
/* tsu_timer_incr register must be written after
* the tsu_timer_incr_sub_ns register and the write operation
* will cause the value written to the tsu_timer_incr_sub_ns register
* to take effect.
*/
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
/* RegBit[15:0] = Subns[23:8]; RegBit[31:24] = Subns[7:0] */
gem_writel(bp, TISUBN, GEM_BF(SUBNSINCRL, incr_spec->sub_ns) |
GEM_BF(SUBNSINCRH, (incr_spec->sub_ns >>
GEM_SUBNSINCRL_SIZE)));
gem_writel(bp, TI, GEM_BF(NSINCR, incr_spec->ns));
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
return 0;
}
static int gem_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
struct tsu_incr incr_spec;
bool neg_adj = false;
u32 word;
u64 adj;
if (scaled_ppm < 0) {
neg_adj = true;
scaled_ppm = -scaled_ppm;
}
/* Adjustment is relative to base frequency */
incr_spec.sub_ns = bp->tsu_incr.sub_ns;
incr_spec.ns = bp->tsu_incr.ns;
/* scaling: unused(8bit) | ns(8bit) | fractions(16bit) */
word = ((u64)incr_spec.ns << GEM_SUBNSINCR_SIZE) + incr_spec.sub_ns;
adj = (u64)scaled_ppm * word;
/* Divide with rounding, equivalent to floating dividing:
* (temp / USEC_PER_SEC) + 0.5
*/
adj += (USEC_PER_SEC >> 1);
adj >>= PPM_FRACTION; /* remove fractions */
adj = div_u64(adj, USEC_PER_SEC);
adj = neg_adj ? (word - adj) : (word + adj);
incr_spec.ns = (adj >> GEM_SUBNSINCR_SIZE)
& ((1 << GEM_NSINCR_SIZE) - 1);
incr_spec.sub_ns = adj & ((1 << GEM_SUBNSINCR_SIZE) - 1);
gem_tsu_incr_set(bp, &incr_spec);
return 0;
}
static int gem_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
struct timespec64 now, then = ns_to_timespec64(delta);
u32 adj, sign = 0;
if (delta < 0) {
sign = 1;
delta = -delta;
}
if (delta > TSU_NSEC_MAX_VAL) {
gem_tsu_get_time(&bp->ptp_clock_info, &now, NULL);
now = timespec64_add(now, then);
gem_tsu_set_time(&bp->ptp_clock_info,
(const struct timespec64 *)&now);
} else {
adj = (sign << GEM_ADDSUB_OFFSET) | delta;
gem_writel(bp, TA, adj);
}
return 0;
}
static int gem_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
return -EOPNOTSUPP;
}
static const struct ptp_clock_info gem_ptp_caps_template = {
.owner = THIS_MODULE,
.name = GEM_PTP_TIMER_NAME,
.max_adj = 0,
.n_alarm = 0,
.n_ext_ts = 0,
.n_per_out = 0,
.n_pins = 0,
.pps = 1,
.adjfine = gem_ptp_adjfine,
.adjtime = gem_ptp_adjtime,
.gettimex64 = gem_tsu_get_time,
.settime64 = gem_tsu_set_time,
.enable = gem_ptp_enable,
};
static void gem_ptp_init_timer(struct macb *bp)
{
u32 rem = 0;
u64 adj;
bp->tsu_incr.ns = div_u64_rem(NSEC_PER_SEC, bp->tsu_rate, &rem);
if (rem) {
adj = rem;
adj <<= GEM_SUBNSINCR_SIZE;
bp->tsu_incr.sub_ns = div_u64(adj, bp->tsu_rate);
} else {
bp->tsu_incr.sub_ns = 0;
}
}
static void gem_ptp_init_tsu(struct macb *bp)
{
struct timespec64 ts;
/* 1. get current system time */
ts = ns_to_timespec64(ktime_to_ns(ktime_get_real()));
/* 2. set ptp timer */
gem_tsu_set_time(&bp->ptp_clock_info, &ts);
/* 3. set PTP timer increment value to BASE_INCREMENT */
gem_tsu_incr_set(bp, &bp->tsu_incr);
gem_writel(bp, TA, 0);
}
static void gem_ptp_clear_timer(struct macb *bp)
{
bp->tsu_incr.sub_ns = 0;
bp->tsu_incr.ns = 0;
gem_writel(bp, TISUBN, GEM_BF(SUBNSINCR, 0));
gem_writel(bp, TI, GEM_BF(NSINCR, 0));
gem_writel(bp, TA, 0);
}
static int gem_hw_timestamp(struct macb *bp, u32 dma_desc_ts_1,
u32 dma_desc_ts_2, struct timespec64 *ts)
{
struct timespec64 tsu;
ts->tv_sec = (GEM_BFEXT(DMA_SECH, dma_desc_ts_2) << GEM_DMA_SECL_SIZE) |
GEM_BFEXT(DMA_SECL, dma_desc_ts_1);
ts->tv_nsec = GEM_BFEXT(DMA_NSEC, dma_desc_ts_1);
/* TSU overlapping workaround
* The timestamp only contains lower few bits of seconds,
* so add value from 1588 timer
*/
gem_tsu_get_time(&bp->ptp_clock_info, &tsu, NULL);
ts->tv_sec |= ((~GEM_DMA_SEC_MASK) & tsu.tv_sec);
/* If the top bit is set in the timestamp,
* but not in 1588 timer, it has rolled over,
* so subtract max size
*/
if ((ts->tv_sec & (GEM_DMA_SEC_TOP >> 1)) &&
!(tsu.tv_sec & (GEM_DMA_SEC_TOP >> 1)))
ts->tv_sec -= GEM_DMA_SEC_TOP;
return 0;
}
void gem_ptp_rxstamp(struct macb *bp, struct sk_buff *skb,
struct macb_dma_desc *desc)
{
struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb);
struct macb_dma_desc_ptp *desc_ptp;
struct timespec64 ts;
if (GEM_BFEXT(DMA_RXVALID, desc->addr)) {
desc_ptp = macb_ptp_desc(bp, desc);
/* Unlikely but check */
if (!desc_ptp) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not supported in BD\n");
return;
}
gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
shhwtstamps->hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
}
}
void gem_ptp_txstamp(struct macb *bp, struct sk_buff *skb,
struct macb_dma_desc *desc)
{
struct skb_shared_hwtstamps shhwtstamps;
struct macb_dma_desc_ptp *desc_ptp;
struct timespec64 ts;
if (!GEM_BFEXT(DMA_TXVALID, desc->ctrl)) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not set in TX BD as expected\n");
return;
}
desc_ptp = macb_ptp_desc(bp, desc);
/* Unlikely but check */
if (!desc_ptp) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not supported in BD\n");
return;
}
/* ensure ts_1/ts_2 is loaded after ctrl (TX_USED check) */
dma_rmb();
gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
skb_tstamp_tx(skb, &shhwtstamps);
}
void gem_ptp_init(struct net_device *dev)
{
struct macb *bp = netdev_priv(dev);
bp->ptp_clock_info = gem_ptp_caps_template;
/* nominal frequency and maximum adjustment in ppb */
bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp);
bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj();
gem_ptp_init_timer(bp);
bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &dev->dev);
if (IS_ERR(bp->ptp_clock)) {
pr_err("ptp clock register failed: %ld\n",
PTR_ERR(bp->ptp_clock));
bp->ptp_clock = NULL;
return;
} else if (bp->ptp_clock == NULL) {
pr_err("ptp clock register failed\n");
return;
}
spin_lock_init(&bp->tsu_clk_lock);
gem_ptp_init_tsu(bp);
dev_info(&bp->pdev->dev, "%s ptp clock registered.\n",
GEM_PTP_TIMER_NAME);
}
void gem_ptp_remove(struct net_device *ndev)
{
struct macb *bp = netdev_priv(ndev);
if (bp->ptp_clock) {
ptp_clock_unregister(bp->ptp_clock);
bp->ptp_clock = NULL;
}
gem_ptp_clear_timer(bp);
dev_info(&bp->pdev->dev, "%s ptp clock unregistered.\n",
GEM_PTP_TIMER_NAME);
}
static int gem_ptp_set_ts_mode(struct macb *bp,
enum macb_bd_control tx_bd_control,
enum macb_bd_control rx_bd_control)
{
gem_writel(bp, TXBDCTRL, GEM_BF(TXTSMODE, tx_bd_control));
gem_writel(bp, RXBDCTRL, GEM_BF(RXTSMODE, rx_bd_control));
return 0;
}
int gem_get_hwtst(struct net_device *dev,
struct kernel_hwtstamp_config *tstamp_config)
{
struct macb *bp = netdev_priv(dev);
*tstamp_config = bp->tstamp_config;
if ((bp->hw_dma_cap & HW_DMA_CAP_PTP) == 0)
return -EOPNOTSUPP;
return 0;
}
static void gem_ptp_set_one_step_sync(struct macb *bp, u8 enable)
{
u32 reg_val;
reg_val = macb_readl(bp, NCR);
if (enable)
macb_writel(bp, NCR, reg_val | MACB_BIT(OSSMODE));
else
macb_writel(bp, NCR, reg_val & ~MACB_BIT(OSSMODE));
}
int gem_set_hwtst(struct net_device *dev,
struct kernel_hwtstamp_config *tstamp_config,
struct netlink_ext_ack *extack)
{
enum macb_bd_control tx_bd_control = TSTAMP_DISABLED;
enum macb_bd_control rx_bd_control = TSTAMP_DISABLED;
struct macb *bp = netdev_priv(dev);
u32 regval;
if ((bp->hw_dma_cap & HW_DMA_CAP_PTP) == 0)
return -EOPNOTSUPP;
switch (tstamp_config->tx_type) {
case HWTSTAMP_TX_OFF:
break;
case HWTSTAMP_TX_ONESTEP_SYNC:
gem_ptp_set_one_step_sync(bp, 1);
tx_bd_control = TSTAMP_ALL_FRAMES;
break;
case HWTSTAMP_TX_ON:
gem_ptp_set_one_step_sync(bp, 0);
tx_bd_control = TSTAMP_ALL_FRAMES;
break;
default:
return -ERANGE;
}
switch (tstamp_config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
break;
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
break;
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
break;
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
rx_bd_control = TSTAMP_ALL_PTP_FRAMES;
tstamp_config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
regval = macb_readl(bp, NCR);
macb_writel(bp, NCR, (regval | MACB_BIT(SRTSM)));
break;
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_ALL:
rx_bd_control = TSTAMP_ALL_FRAMES;
tstamp_config->rx_filter = HWTSTAMP_FILTER_ALL;
break;
default:
tstamp_config->rx_filter = HWTSTAMP_FILTER_NONE;
return -ERANGE;
}
bp->tstamp_config = *tstamp_config;
if (gem_ptp_set_ts_mode(bp, tx_bd_control, rx_bd_control) != 0)
return -ERANGE;
return 0;
}
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
if [ $# -ne 3 ]; then
echo "Need 3 arguments: 1) kernel source dir, 2) previous version, 3) version to add"
exit 1
fi
KERNELDIR=$1
PREVER=$2
KERNELVER=$3
MACBDIR=drivers/net/ethernet/cadence
FILES="macb_main.c macb.h macb_ptp.c"
for f in $FILES; do
echo $f
o=${f/\./-$KERNELVER-orig.}
e=${f/\./-$KERNELVER-ethercat.}
cp -v $KERNELDIR/$MACBDIR/$f $o
chmod 644 $o
cp -v $o $e
op=${f/\./-$PREVER-orig.}
ep=${f/\./-$PREVER-ethercat.}
diff -up $op $ep | patch -p1 --no-backup-if-mismatch $e
sed -i s/$PREVER-ethercat.h/$KERNELVER-ethercat.h/ $e
git add $o $e
echo -e "\t$e \\" >> Makefile.am
echo -e "\t$o \\" >> Makefile.am
done
echo "Don't forget to update Makefile.am!"