Re: [net-next v5 4/6] net: mtip: The L2 switch driver for imx287

From: Stefan Wahren
Date: Thu Apr 17 2025 - 06:32:43 EST


Hi Lukasz,

Am 14.04.25 um 16:01 schrieb Lukasz Majewski:
This patch series provides support for More Than IP L2 switch embedded
in the imx287 SoC.

This is a two port switch (placed between uDMA[01] and MAC-NET[01]),
which can be used for offloading the network traffic.

It can be used interchangeably with current FEC driver - to be more
specific: one can use either of it, depending on the requirements.

The biggest difference is the usage of DMA - when FEC is used, separate
DMAs are available for each ENET-MAC block.
However, with switch enabled - only the DMA0 is used to send/receive data
to/form switch (and then switch sends them to respecitive ports).

Signed-off-by: Lukasz Majewski <lukma@xxxxxxx>
---
Changes for v2:

- Remove not needed comments
- Restore udelay(10) for switch reset (such delay is explicitly specifed
in the documentation
- Add COMPILE_TEST
- replace pr_* with dev_*
- Use for_each_available_child_of_node_scoped()
- Use devm_* function for memory allocation
- Remove printing information about the HW and SW revision of the driver
- Use devm_regulator_get_optional()
- Change compatible prefix from 'fsl' to more up to date 'nxp'
- Remove .owner = THIS_MODULE
- Use devm_platform_ioremap_resource(pdev, 0);
- Use devm_request_irq()
- Use devm_regulator_get_enable_optional()
- Replace clk_prepare_enable() and devm_clk_get() with single
call to devm_clk_get_optional_enabled()
- Cleanup error patch when function calls in probe fail
- Refactor the mtip_reset_phy() to serve as mdio bus reset callback
- Add myself as the MTIP L2 switch maintainer (squashed the separated
commit)
- More descriptive help paragraphs (> 4 lines)

Changes for v3:
- Remove 'bridge_offloading' module parameter (to bridge ports just after probe)
- Remove forward references
- Fix reverse christmas tree formatting in functions
- Convert eligible comments to kernel doc format
- Remove extra MAC address validation check at esw_mac_addr_static()
- Remove mtip_print_link_status() and replace it with phy_print_status()
- Avoid changing phy device state in the driver (instead use functions
exported by the phy API)
- Do not print extra information regarding PHY (which is printed by phylib) -
e.g. net lan0: lan0: MTIP eth L2 switch 1e:ce:a5:0b:4c:12
- Remove VERSION from the driver - now we rely on the SHA1 in Linux
mainline tree
- Remove zeroing of the net device private area (shall be already done
during allocation)
- Refactor the code to remove mtip_ndev_setup()
- Use -ENOMEM instead of -1 return code when allocation fails
- Replace dev_info() with dev_dbg() to reduce number of information print
on normal operation
- Return ret instead of 0 from mtip_ndev_init()
- Remove fep->mii_timeout flag from the driver
- Remove not used stop_gpr_* fields in mtip_devinfo struct
- Remove platform_device_id description for mtipl2sw driver
- Add MODULE_DEVICE_TABLE() for mtip_of_match
- Remove MODULE_ALIAS()

Changes for v4:
- Rename imx287 with imx28 (as the former is not used in kernel anymore)
- Reorder the place where ENET interface is initialized - without this
change the enet_out clock has default (25 MHz) value, which causes
issues during reset (RMII's 50 MHz is required for proper PHY reset).
- Use PAUR instead of PAUR register to program MAC address
- Replace eth_mac_addr() with eth_hw_addr_set()
- Write to HW the randomly generated MAC address (if required)
- Adjust the reset code
- s/read_atable/mtip_read_atable/g and s/write_atable/mtip_write_atable/g
- Add clk_disable() and netif_napi_del() when errors occur during
mtip_open() - refactor the error handling path.
- Refactor the mtip_set_multicast_list() to write (now) correct values to
ENET-FEC registers.
- Replace dev_warn() with dev_err()
- Use GPIO_ACTIVE_LOW to indicate polarity in DTS
- Refactor code to check if network device is the switch device
- Remove mtip_port_dev_check()
- Refactor mtip_ndev_port_link() avoid starting HW offloading for bridge
when MTIP ports are parts of two distinct bridges
- Replace del_timer() with timer_delete_sync()

Changes for v5:
- Fix spelling in Kconfig
- Replace tmp with reg or register name
- Replace tmpaddr with mac_addr
- Use mac address assignment (from registers) code similar to fec_main.c (as it
shall handle properly generic endianess)
- Add description for fep: in the mtip_update_atable_static() kernel doc
- Replace writel(bdp, &fep->cur_rx) with fep->cur_rx = bdp;
- Fix spelling of transmit
- Remove not needed white spaces in mtipl2sw.h
- Remove '_t' from struct mtip_addr_table_t
- Provide proper alignment in the mtipl2sw.h
- Add blank line after local header in mtipl2sw_br.c
- Use %p instead of %x (and cast) for fep in debug message
- Disable L2 switch in-HW offloading when only one
of eligible ports is removed from the bridge
- Sort includes in the patch set alphabethically
- Introduce FEC_QUIRK_SWAP_FRAME to avoid #ifdef for imx28 proper operation
- Move 'mtip_port_info g_info' to struct switch_enet_private
- Replace some unsigned int with u32 (on data fields with 32 bit size)
- Remove not relevant comments from mtip_enet_init()
- Refactor functions definitions to be void when no other
value than 0 is returned.
- Use capital letters in HEX constants
- Use u32 instead of unsigned int when applicable
- Add error handling code after the dma_map_single() is called
- The MCF_FEC_MSCR register can be written unconditionally
for all supported platforms.
- Use IS_ENABLED() instead of #ifdef in mtip_timeout()
- Replace dev_info() with dev_warn_ratelimited() in mtip_switch_rx()
- Add code to handle situation when there is no memory
- Remove kfree(fep->mii_bus->irq)
- Provide more verbose output of mdio_{read|write} functions
- Handle error when clk_enable() fails in mtip_open()
- Use dev_dbg() at mtip_set_multicast_list()
- Simplify the mtip_is_switch_netdev_port() function to return condition check value
- Add dev_dbg() when of_get_mac_address() fails (as it may not be provided)
- Remove return ret; in mtip_register_notifiers()
- Replace int to bool in mtipl2sw_mgnt.c file's function definitions
- Replace unsigned int/long with u32 where applicable (where access to
32 bit registers is performed)
- Refactor code in mtip_{read|write}_atable() to be more readable
- Remove code added for not (yet) supported IMX's vf610 SoC
- Remove do { } while(); loop from mtip_interrupt() function
- Introduce MTIP_PORT_FORWARDING_INIT to indicate intial value for
port forwarding
- Replace 'unsigned long' to 'u32' in mtipl2sw.h
- Replace 'unsigned short' to 'u16' in mtipl2sw.h
- use %#x in dev_dbg()
- Call SET_NETDEV_DEV() macro to set network device' parent - otherwise
phy_attach_direct() will fail.
---
MAINTAINERS | 7 +
drivers/net/ethernet/freescale/Kconfig | 1 +
drivers/net/ethernet/freescale/Makefile | 1 +
drivers/net/ethernet/freescale/mtipsw/Kconfig | 13 +
.../net/ethernet/freescale/mtipsw/Makefile | 3 +
.../net/ethernet/freescale/mtipsw/mtipl2sw.c | 1990 +++++++++++++++++
.../net/ethernet/freescale/mtipsw/mtipl2sw.h | 788 +++++++
.../ethernet/freescale/mtipsw/mtipl2sw_br.c | 120 +
.../ethernet/freescale/mtipsw/mtipl2sw_mgnt.c | 449 ++++
9 files changed, 3372 insertions(+)
create mode 100644 drivers/net/ethernet/freescale/mtipsw/Kconfig
create mode 100644 drivers/net/ethernet/freescale/mtipsw/Makefile
create mode 100644 drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c
create mode 100644 drivers/net/ethernet/freescale/mtipsw/mtipl2sw.h
create mode 100644 drivers/net/ethernet/freescale/mtipsw/mtipl2sw_br.c
create mode 100644 drivers/net/ethernet/freescale/mtipsw/mtipl2sw_mgnt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 1248443035f4..af4e42a33c99 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9448,6 +9448,13 @@ S: Maintained
F: Documentation/devicetree/bindings/i2c/i2c-mpc.yaml
F: drivers/i2c/busses/i2c-mpc.c
+FREESCALE MTIP ETHERNET SWITCH DRIVER
+M: Lukasz Majewski <lukma@xxxxxxx>
+L: netdev@xxxxxxxxxxxxxxx
+S: Maintained
+F: Documentation/devicetree/bindings/net/nxp,imx28-mtip-switch.yaml
+F: drivers/net/ethernet/freescale/mtipsw/*
+
FREESCALE QORIQ DPAA ETHERNET DRIVER
M: Madalin Bucur <madalin.bucur@xxxxxxx>
L: netdev@xxxxxxxxxxxxxxx
diff --git a/drivers/net/ethernet/freescale/Kconfig b/drivers/net/ethernet/freescale/Kconfig
index a2d7300925a8..056a11c3a74e 100644
--- a/drivers/net/ethernet/freescale/Kconfig
+++ b/drivers/net/ethernet/freescale/Kconfig
@@ -60,6 +60,7 @@ config FEC_MPC52xx_MDIO
source "drivers/net/ethernet/freescale/fs_enet/Kconfig"
source "drivers/net/ethernet/freescale/fman/Kconfig"
+source "drivers/net/ethernet/freescale/mtipsw/Kconfig"
config FSL_PQ_MDIO
tristate "Freescale PQ MDIO"
diff --git a/drivers/net/ethernet/freescale/Makefile b/drivers/net/ethernet/freescale/Makefile
index de7b31842233..0e6cacb0948a 100644
--- a/drivers/net/ethernet/freescale/Makefile
+++ b/drivers/net/ethernet/freescale/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_FSL_DPAA_ETH) += dpaa/
obj-$(CONFIG_FSL_DPAA2_ETH) += dpaa2/
obj-y += enetc/
+obj-y += mtipsw/
diff --git a/drivers/net/ethernet/freescale/mtipsw/Kconfig b/drivers/net/ethernet/freescale/mtipsw/Kconfig
new file mode 100644
index 000000000000..0ae58e7b1ca6
--- /dev/null
+++ b/drivers/net/ethernet/freescale/mtipsw/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config FEC_MTIP_L2SW
+ tristate "MoreThanIP L2 switch support to FEC driver"
+ depends on OF
+ depends on NET_SWITCHDEV
+ depends on BRIDGE
+ depends on ARCH_MXS || COMPILE_TEST
I think we should align the dependencies with FEC:

depends on SOC_IMX28 || COMPILE_TEST
+ help
+ This enables support for the MoreThan IP L2 switch on i.MX
+ SoCs (e.g. iMX287). It offloads bridging to this IP block's
+ hardware and allows switch management with standard Linux tools.
+ This switch driver can be used interchangeable with the already
+ available FEC driver, depending on the use case's requirements.
diff --git a/drivers/net/ethernet/freescale/mtipsw/Makefile b/drivers/net/ethernet/freescale/mtipsw/Makefile
new file mode 100644
index 000000000000..4d69db2226a6
--- /dev/null
+++ b/drivers/net/ethernet/freescale/mtipsw/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_FEC_MTIP_L2SW) += mtipl2sw.o mtipl2sw_mgnt.o mtipl2sw_br.o
diff --git a/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c
new file mode 100644
index 000000000000..50f5a0c1bc8c
--- /dev/null
+++ b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c
@@ -0,0 +1,1990 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * L2 switch Controller (Ethernet L2 switch) driver for MTIP block.
+ *
+ * Copyright (C) 2025 DENX Software Engineering GmbH
+ * Lukasz Majewski <lukma@xxxxxxx>
+ *
+ * Based on a previous work by:
+ *
+ * Copyright 2010-2012 Freescale Semiconductor, Inc.
+ * Alison Wang (b18965@xxxxxxxxxxxxx)
+ * Jason Jin (Jason.jin@xxxxxxxxxxxxx)
+ *
+ * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Shrek Wu (B16972@xxxxxxxxxxxxx)
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/rtnetlink.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include "mtipl2sw.h"
+
+static void swap_buffer(void *bufaddr, int len)
+{
+ int i;
+ unsigned int *buf = bufaddr;
+
+ for (i = 0; i < len; i += 4, buf++)
+ swab32s(buf);
+}
+
+struct mtip_devinfo {
+ u32 quirks;
+};
+
+static void mtip_enet_init(struct switch_enet_private *fep, int port)
+{
+ void __iomem *enet_addr = fep->enet_addr;
+ u32 mii_speed, holdtime, reg;
+
+ if (port == 2)
+ enet_addr += MCF_ESW_ENET_PORT_OFFSET;
+
+ reg = MCF_FEC_RCR_PROM | MCF_FEC_RCR_MII_MODE |
+ MCF_FEC_RCR_MAX_FL(1522);
+
+ if (fep->phy_interface[port - 1] == PHY_INTERFACE_MODE_RMII)
+ reg |= MCF_FEC_RCR_RMII_MODE;
+
+ writel(reg, enet_addr + MCF_FEC_RCR);
+
+ writel(MCF_FEC_TCR_FDEN, enet_addr + MCF_FEC_TCR);
+ writel(MCF_FEC_ECR_ETHER_EN, enet_addr + MCF_FEC_ECR);
+
+ mii_speed = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 5000000);
+ mii_speed--;
+
+ holdtime = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 100000000) - 1;
+
+ fep->phy_speed = mii_speed << 1 | holdtime << 8;
+
+ writel(fep->phy_speed, enet_addr + MCF_FEC_MSCR);
+}
+
+static void mtip_setup_mac(struct net_device *dev)
+{
+ struct mtip_ndev_priv *priv = netdev_priv(dev);
+ struct switch_enet_private *fep = priv->fep;
+ unsigned char *iap, mac_addr[ETH_ALEN];
+
+ /* Use MAC address from DTS */
+ iap = &fep->mac[priv->portnum - 1][0];
+
+ /* Use MAC address set by bootloader */
+ if (!is_valid_ether_addr(iap)) {
+ *((__be32 *)&mac_addr[0]) =
+ cpu_to_be32(readl(fep->enet_addr + MCF_FEC_PALR));
+ *((__be16 *)&mac_addr[4]) =
+ cpu_to_be16(readl(fep->enet_addr +
+ MCF_FEC_PAUR) >> 16);
+ iap = &mac_addr[0];
+ }
+
+ /* Use random MAC address */
+ if (!is_valid_ether_addr(iap)) {
+ eth_hw_addr_random(dev);
+ dev_info(&fep->pdev->dev, "Using random MAC address: %pM\n",
+ dev->dev_addr);
+ iap = (unsigned char *)dev->dev_addr;
+ }
+
+ /* Adjust MAC if using macaddr (and increment if needed) */
+ eth_hw_addr_gen(dev, iap, priv->portnum - 1);
+}
+
+/**
+ * crc8_calc - calculate CRC for MAC storage
+ *
+ * @pmacaddress: A 6-byte array with the MAC address. The first byte is
+ * the first byte transmitted.
+ *
+ * Calculate Galois Field Arithmetic CRC for Polynom x^8+x^2+x+1.
+ * It omits the final shift in of 8 zeroes a "normal" CRC would do
+ * (getting the remainder).
+ *
+ * Examples (hexadecimal values):<br>
+ * 10-11-12-13-14-15 => CRC=0xc2
+ * 10-11-cc-dd-ee-00 => CRC=0xe6
+ *
+ * Return: The 8-bit CRC in bits 7:0
+ */
+static int crc8_calc(unsigned char *pmacaddress)
+{
+ int byt; /* byte index */
+ int bit; /* bit index */
+ int crc = 0x12;
+ int inval;
+
+ for (byt = 0; byt < ETH_ALEN; byt++) {
+ inval = (((int)pmacaddress[byt]) & 0xFF);
+ /* shift bit 0 to bit 8 so all our bits
+ * travel through bit 8
+ * (simplifies below calc)
+ */
+ inval <<= 8;
+
+ for (bit = 0; bit < 8; bit++) {
+ /* next input bit comes into d7 after shift */
+ crc |= inval & 0x100;
+ if (crc & 0x01)
+ /* before shift */
+ crc ^= 0x1C0;
+
+ crc >>= 1;
+ inval >>= 1;
+ }
+ }
+ /* upper bits are clean as we shifted in zeroes! */
+ return crc;
+}
+
+static void mtip_read_atable(struct switch_enet_private *fep, int index,
+ u32 *read_lo, u32 *read_hi)
+{
+ struct addr_table64b_entry *atable_base =
+ fep->hwentry->mtip_table64b_entry;
+
+ *read_lo = readl(&atable_base[index].lo);
+ *read_hi = readl(&atable_base[index].hi);
+}
+
+static void mtip_write_atable(struct switch_enet_private *fep, int index,
+ u32 write_lo, u32 write_hi)
+{
+ struct addr_table64b_entry *atable_base =
+ fep->hwentry->mtip_table64b_entry;
+
+ writel(write_lo, &atable_base[index].lo);
+ writel(write_hi, &atable_base[index].hi);
+}
+
+/**
+ * mtip_portinfofifo_read - Read element from receive FIFO
+ *
+ * @fep: Structure describing switch
+ *
+ * Read one element from the HW receive FIFO (Queue)
+ * if available and return it.
+ *
+ * Return: mtip_port_info or NULL if no data is available.
+ */
+static
+struct mtip_port_info *mtip_portinfofifo_read(struct switch_enet_private *fep)
+{
+ struct mtip_port_info *info = &fep->g_info;
+ struct switch_t *fecp = fep->hwp;
+ u32 reg;
+
+ reg = readl(&fecp->ESW_LSR);
+ if (reg == 0) {
+ dev_dbg(&fep->pdev->dev, "%s: ESW_LSR = 0x%x\n", __func__, reg);
+ return NULL;
+ }
+
+ /* read word from FIFO */
+ info->maclo = readl(&fecp->ESW_LREC0);
+ if (info->maclo == 0) {
+ dev_dbg(&fep->pdev->dev, "%s: mac lo 0x%x\n", __func__,
+ info->maclo);
+ return NULL;
+ }
+
+ /* read 2nd word from FIFO */
+ reg = readl(&fecp->ESW_LREC1);
+ info->machi = reg & 0xFFFF;
+ info->hash = (reg >> 16) & 0xFF;
+ info->port = (reg >> 24) & 0xF;
+
+ return info;
+}
+
+static void mtip_atable_get_entry_port_number(struct switch_enet_private *fep,
+ unsigned char *mac_addr, u8 *port)
+{
+ int block_index, block_index_end, entry;
+ u32 mac_addr_lo, mac_addr_hi;
+ u32 read_lo, read_hi;
+
+ mac_addr_lo = (u32)((mac_addr[3] << 24) | (mac_addr[2] << 16) |
+ (mac_addr[1] << 8) | mac_addr[0]);
+ mac_addr_hi = (u32)((mac_addr[5] << 8) | (mac_addr[4]));
+
+ block_index = GET_BLOCK_PTR(crc8_calc(mac_addr));
+ block_index_end = block_index + ATABLE_ENTRY_PER_SLOT;
+
+ /* now search all the entries in the selected block */
+ for (entry = block_index; entry < block_index_end; entry++) {
+ mtip_read_atable(fep, entry, &read_lo, &read_hi);
+ *port = MTIP_PORT_FORWARDING_INIT;
+
+ if (read_lo == mac_addr_lo &&
+ ((read_hi & 0x0000FFFF) ==
+ (mac_addr_hi & 0x0000FFFF))) {
+ /* found the correct address */
+ if ((read_hi & (1 << 16)) && (!(read_hi & (1 << 17))))
+ *port = AT_EXTRACT_PORT(read_hi);
+ break;
+ }
+ }
+
+ dev_dbg(&fep->pdev->dev, "%s: MAC: %pM PORT: 0x%x\n", __func__,
+ mac_addr, *port);
+}
+
+/* Clear complete MAC Look Up Table */
+void mtip_clear_atable(struct switch_enet_private *fep)
+{
+ int index;
+
+ for (index = 0; index < 2048; index++)
There are a lot of defines for this magic number, please use one of them
+ mtip_write_atable(fep, index, 0, 0);
+}
+
+/**
+ * mtip_update_atable_static - Update switch static address table
+ *
+ * @mac_addr: Pointer to the array containing MAC address to
+ * be put as static entry
+ * @port: Port bitmask numbers to be added in static entry,
+ * valid values are 1-7
+ * @priority: The priority for the static entry in table
+ *
+ * @fep: Pointer to the structure describing the switch
+ *
+ * Updates MAC address lookup table with a static entry.
+ *
+ * Searches if the MAC address is already there in the block and replaces
+ * the older entry with the new one. If MAC address is not there then puts
+ * a new entry in the first empty slot available in the block.
+ *
+ * Return: 0 for a successful update else -ENOSPC when no slot available
+ */
+static int mtip_update_atable_static(unsigned char *mac_addr, unsigned int port,
+ unsigned int priority,
+ struct switch_enet_private *fep)
+{
+ unsigned long block_index, entry, index_end;
+ u32 write_lo, write_hi, read_lo, read_hi;
+
+ write_lo = (u32)((mac_addr[3] << 24) | (mac_addr[2] << 16) |
+ (mac_addr[1] << 8) | mac_addr[0]);
+ write_hi = (u32)(0 | (port << AT_SENTRY_PORTMASK_shift) |
+ (priority << AT_SENTRY_PRIO_shift) |
+ (AT_ENTRY_TYPE_STATIC << AT_ENTRY_TYPE_shift) |
+ (AT_ENTRY_RECORD_VALID << AT_ENTRY_VALID_shift) |
+ (mac_addr[5] << 8) | (mac_addr[4]));
+
+ block_index = GET_BLOCK_PTR(crc8_calc(mac_addr));
+ index_end = block_index + ATABLE_ENTRY_PER_SLOT;
+ /* Now search all the entries in the selected block */
+ for (entry = block_index; entry < index_end; entry++) {
+ mtip_read_atable(fep, entry, &read_lo, &read_hi);
+ /* MAC address matched, so update the
+ * existing entry
+ * even if its a dynamic one
+ */
+ if (read_lo == write_lo &&
+ ((read_hi & 0x0000FFFF) ==
+ (write_hi & 0x0000FFFF))) {
+ mtip_write_atable(fep, entry, write_lo, write_hi);
+ return 0;
+ } else if (!(read_hi & (1 << 16))) {
+ /* Fill this empty slot (valid bit zero),
+ * assuming no holes in the block
+ */
+ mtip_write_atable(fep, entry, write_lo, write_hi);
+ fep->at_curr_entries++;
+ return 0;
+ }
+ }
+
+ /* No space available for this static entry */
+ return -ENOSPC;
+}
+
+static bool mtip_update_atable_dynamic1(u32 write_lo, u32 write_hi,
+ int block_index, unsigned int port,
+ unsigned int curr_time,
+ struct switch_enet_private *fep)
+{
+ unsigned long entry, index_end;
+ int time, timeold, indexold;
+ u32 read_lo, read_hi;
+ unsigned long conf;
+
+ /* prepare update port and timestamp */
+ conf = AT_ENTRY_RECORD_VALID << AT_ENTRY_VALID_shift;
+ conf |= AT_ENTRY_TYPE_DYNAMIC << AT_ENTRY_TYPE_shift;
+ conf |= curr_time << AT_DENTRY_TIME_shift;
+ conf |= port << AT_DENTRY_PORT_shift;
+ conf |= write_hi;
+
+ /* linear search through all slot
+ * entries and update if found
+ */
+ index_end = block_index + ATABLE_ENTRY_PER_SLOT;
+ /* Now search all the entries in the selected block */
+ for (entry = block_index; entry < index_end; entry++) {
+ mtip_read_atable(fep, entry, &read_lo, &read_hi);
+ if (read_lo == write_lo &&
+ ((read_hi & 0x0000FFFF) ==
+ (write_hi & 0x0000FFFF))) {
+ /* found correct address,
+ * update timestamp.
+ */
+ mtip_write_atable(fep, entry, write_lo, conf);
+
+ return false;
+ } else if (!(read_hi & (1 << 16))) {
+ /* slot is empty, then use it
+ * for new entry
+ * Note: There are no holes,
+ * therefore cannot be any
+ * more that need to be compared.
+ */
+ mtip_write_atable(fep, entry, write_lo, conf);
+ /* statistics (we do it between writing
+ * .hi an .lo due to
+ * hardware limitation...
+ */
+ fep->at_curr_entries++;
+ /* newly inserted */
+
+ return true;
+ }
+ }
+
+ /* No more entry available in block overwrite oldest */
+ timeold = 0;
+ indexold = 0;
+ for (entry = block_index; entry < index_end; entry++) {
+ mtip_read_atable(fep, entry, &read_lo, &read_hi);
+ time = AT_EXTRACT_TIMESTAMP(read_hi);
+ dev_dbg(&fep->pdev->dev, "%s : time %x currtime %x\n",
+ __func__, time, curr_time);
+ time = TIMEDELTA(curr_time, time);
+ if (time > timeold) {
+ /* is it older ? */
+ timeold = time;
+ indexold = entry;
+ }
+ }
+
+ mtip_write_atable(fep, indexold, write_lo, conf);
+
+ /* Statistics (do it inbetween writing to .lo and .hi */
+ fep->at_block_overflows++;
+ dev_err(&fep->pdev->dev, "%s update time, at_block_overflows %x\n",
+ __func__, fep->at_block_overflows);
+ /* newly inserted */
+ return true;
+}
+
+/* dynamicms MAC address table learn and migration */
+static void
+mtip_atable_dynamicms_learn_migration(struct switch_enet_private *fep,
+ int curr_time, unsigned char *mac,
+ u8 *rx_port)
+{
+ u8 port = MTIP_PORT_FORWARDING_INIT;
+ struct mtip_port_info *port_info;
+ u32 rx_mac_lo, rx_mac_hi;
+ unsigned long flags;
+ int index;
+
+ spin_lock_irqsave(&fep->learn_lock, flags);
+
+ if (mac && is_valid_ether_addr(mac)) {
+ rx_mac_lo = (u32)((mac[3] << 24) | (mac[2] << 16) |
+ (mac[1] << 8) | mac[0]);
+ rx_mac_hi = (u32)((mac[5] << 8) | (mac[4]));
+ }
+
+ port_info = mtip_portinfofifo_read(fep);
+ while (port_info) {
+ /* get block index from lookup table */
+ index = GET_BLOCK_PTR(port_info->hash);
+ mtip_update_atable_dynamic1(port_info->maclo, port_info->machi,
+ index, port_info->port,
+ curr_time, fep);
+
+ if (mac && is_valid_ether_addr(mac) &&
+ port == MTIP_PORT_FORWARDING_INIT) {
+ if (rx_mac_lo == port_info->maclo &&
+ rx_mac_hi == port_info->machi) {
+ /* The newly learned MAC is the source of
+ * our filtered frame.
+ */
+ port = (u8)port_info->port;
+ }
+ }
+ port_info = mtip_portinfofifo_read(fep);
+ }
+
+ if (rx_port)
+ *rx_port = port;
+
+ spin_unlock_irqrestore(&fep->learn_lock, flags);
+}
+
+static void mtip_aging_timer(struct timer_list *t)
+{
+ struct switch_enet_private *fep = from_timer(fep, t, timer_aging);
+
+ if (fep)
+ fep->curr_time = mtip_timeincrement(fep->curr_time);
+
+ mod_timer(&fep->timer_aging,
+ jiffies + msecs_to_jiffies(LEARNING_AGING_INTERVAL));
I'm not sure that fep can actually be NULL. If it's possible please return in order to avoid a NULL pointer deference here, otherwise drop the check.

Thanks