2021-11-03 11:03:47

by Wells Lu

[permalink] [raw]
Subject: [PATCH 0/2] This is a patch series of ethernet driver for Sunplus SP7021 SoC.

Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
etc.) into a single chip. It is designed for industrial control
applications.

Refer to:
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
https://tibbo.com/store/plus1.html

Wells Lu (2):
devicetree: bindings: net: Add bindings doc for Sunplus SP7021.
net: ethernet: Add driver for Sunplus SP7021

.../bindings/net/sunplus,sp7021-l2sw.yaml | 123 ++++
MAINTAINERS | 8 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/sunplus/Kconfig | 20 +
drivers/net/ethernet/sunplus/Makefile | 6 +
drivers/net/ethernet/sunplus/l2sw_define.h | 221 ++++++
drivers/net/ethernet/sunplus/l2sw_desc.c | 233 ++++++
drivers/net/ethernet/sunplus/l2sw_desc.h | 21 +
drivers/net/ethernet/sunplus/l2sw_driver.c | 779 +++++++++++++++++++++
drivers/net/ethernet/sunplus/l2sw_driver.h | 23 +
drivers/net/ethernet/sunplus/l2sw_hal.c | 422 +++++++++++
drivers/net/ethernet/sunplus/l2sw_hal.h | 47 ++
drivers/net/ethernet/sunplus/l2sw_int.c | 326 +++++++++
drivers/net/ethernet/sunplus/l2sw_int.h | 16 +
drivers/net/ethernet/sunplus/l2sw_mac.c | 68 ++
drivers/net/ethernet/sunplus/l2sw_mac.h | 24 +
drivers/net/ethernet/sunplus/l2sw_mdio.c | 118 ++++
drivers/net/ethernet/sunplus/l2sw_mdio.h | 19 +
drivers/net/ethernet/sunplus/l2sw_register.h | 99 +++
20 files changed, 2575 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml
create mode 100644 drivers/net/ethernet/sunplus/Kconfig
create mode 100644 drivers/net/ethernet/sunplus/Makefile
create mode 100644 drivers/net/ethernet/sunplus/l2sw_define.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_register.h

--
2.7.4



2021-11-03 11:03:50

by Wells Lu

[permalink] [raw]
Subject: [PATCH 1/2] devicetree: bindings: net: Add bindings doc for Sunplus SP7021.

Add bindings documentation for Sunplus SP7021.

Signed-off-by: Wells Lu <[email protected]>
---
.../bindings/net/sunplus,sp7021-l2sw.yaml | 123 +++++++++++++++++++++
MAINTAINERS | 7 ++
2 files changed, 130 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml

diff --git a/Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml b/Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml
new file mode 100644
index 0000000..1fc253d
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/sunplus,sp7021-l2sw.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 Dual Ethernet MAC Device Tree Bindings
+
+maintainers:
+ - Wells Lu <[email protected]>
+
+description: |
+ Sunplus SP7021 dual 10M/100M Ethernet MAC controller with Layer 2 switch.
+ The controller can operate at either dual Ethernet MAC mode or one Ethernet
+ MAC with layer 2 switch (daisy-chain) mode.
+ The device node of Sunplus SP7021 Ethernet (L2SW) MAC controller has
+ following properties.
+
+properties:
+ compatible:
+ const: sunplus,sp7021-l2sw
+
+ reg:
+ items:
+ - description: Base address and length of the L2SW registers.
+ - description: Base address and length of the MOON5 registers.
+
+ reg-names:
+ items:
+ - const: l2sw
+ - const: moon5
+
+ interrupts:
+ description: |
+ Contains number and type of interrupt. Number should be 66.
+ Type should be high-level trigger
+ maxItems: 1
+
+ clocks:
+ description: |
+ Clock controller selector for Ethernet MAC controller.
+ maxItems: 1
+
+ resets:
+ description: |
+ Reset controller selector for Ethernet MAC controller.
+ maxItems: 1
+
+ phy-handle1:
+ description: A handle to node of phy 1 in mdio node
+ maxItems: 1
+
+ phy-handle2:
+ description: A handle to node of phy 2 in mdio node
+ maxItems: 1
+
+ pinctrl-names:
+ description: |
+ Names corresponding to the numbered pinctrl states.
+ A pinctrl state named "default" must be defined.
+ const: default
+
+ pinctrl-0:
+ description: A handle to the 'default' state of pin configuration
+
+ nvmem-cells:
+ items:
+ - description: nvmem cell address of MAC address of MAC 1
+ - description: nvmem cell address of MAC address of MAC 2
+
+ nvmem-cell-names:
+ description: names corresponding to the nvmem cells of MAC address
+ items:
+ - const: mac_addr0
+ - const: mac_addr1
+
+ mode:
+ description: |
+ Set operating modes of Sunplus Dual Ethernet MAC controller.
+ Please set one of the following modes:
+ 0: daisy-chain mode
+ 1: dual NIC mode
+ 2: daisy-chain mode but disable SA learning
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [ 0, 1, 2 ]
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - clocks
+ - resets
+ - phy-handle1
+ - phy-handle2
+ - pinctrl-names
+ - pinctrl-0
+ - nvmem-cells
+ - nvmem-cell-names
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ l2sw: l2sw@9c108000 {
+ compatible = "sunplus,sp7021-l2sw";
+ reg = <0x9c108000 0x400>, <0x9c000280 0x80>;
+ reg-names = "l2sw", "moon5";
+ interrupt-parent = <&intc>;
+ interrupts = <66 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc 0xa7>;
+ resets = <&rstc 0x97>;
+ phy-handle1 = <&eth_phy0>;
+ phy-handle2 = <&eth_phy1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&l2sw_demo_board_v3_pins>;
+ nvmem-cells = <&mac_addr0>, <&mac_addr1>;
+ nvmem-cell-names = "mac_addr0", "mac_addr1";
+ mode = < 1 >;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index dcc1819..4669c16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18000,6 +18000,13 @@ L: [email protected]
S: Maintained
F: drivers/net/ethernet/dlink/sundance.c

+SUNPLUS ETHERNET DRIVER
+M: Wells Lu <[email protected]>
+L: [email protected]
+S: Maintained
+W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F: Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml
+
SUPERH
M: Yoshinori Sato <[email protected]>
M: Rich Felker <[email protected]>
--
2.7.4


2021-11-03 11:03:52

by Wells Lu

[permalink] [raw]
Subject: [PATCH 2/2] net: ethernet: Add driver for Sunplus SP7021

Add Ethernet driver for Sunplus SP7021.

Signed-off-by: Wells Lu <[email protected]>
---
MAINTAINERS | 1 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/sunplus/Kconfig | 20 +
drivers/net/ethernet/sunplus/Makefile | 6 +
drivers/net/ethernet/sunplus/l2sw_define.h | 221 ++++++++
drivers/net/ethernet/sunplus/l2sw_desc.c | 233 ++++++++
drivers/net/ethernet/sunplus/l2sw_desc.h | 21 +
drivers/net/ethernet/sunplus/l2sw_driver.c | 779 +++++++++++++++++++++++++++
drivers/net/ethernet/sunplus/l2sw_driver.h | 23 +
drivers/net/ethernet/sunplus/l2sw_hal.c | 422 +++++++++++++++
drivers/net/ethernet/sunplus/l2sw_hal.h | 47 ++
drivers/net/ethernet/sunplus/l2sw_int.c | 326 +++++++++++
drivers/net/ethernet/sunplus/l2sw_int.h | 16 +
drivers/net/ethernet/sunplus/l2sw_mac.c | 68 +++
drivers/net/ethernet/sunplus/l2sw_mac.h | 24 +
drivers/net/ethernet/sunplus/l2sw_mdio.c | 118 ++++
drivers/net/ethernet/sunplus/l2sw_mdio.h | 19 +
drivers/net/ethernet/sunplus/l2sw_register.h | 99 ++++
19 files changed, 2445 insertions(+)
create mode 100644 drivers/net/ethernet/sunplus/Kconfig
create mode 100644 drivers/net/ethernet/sunplus/Makefile
create mode 100644 drivers/net/ethernet/sunplus/l2sw_define.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.c
create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.h
create mode 100644 drivers/net/ethernet/sunplus/l2sw_register.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 4669c16..ca676ec 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18006,6 +18006,7 @@ L: [email protected]
S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml
+F: drivers/net/ethernet/sunplus/

SUPERH
M: Yoshinori Sato <[email protected]>
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 412ae3e..0084852 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -176,6 +176,7 @@ source "drivers/net/ethernet/smsc/Kconfig"
source "drivers/net/ethernet/socionext/Kconfig"
source "drivers/net/ethernet/stmicro/Kconfig"
source "drivers/net/ethernet/sun/Kconfig"
+source "drivers/net/ethernet/sunplus/Kconfig"
source "drivers/net/ethernet/synopsys/Kconfig"
source "drivers/net/ethernet/tehuti/Kconfig"
source "drivers/net/ethernet/ti/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index aaa5078..e4ce162 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -87,6 +87,7 @@ obj-$(CONFIG_NET_VENDOR_SMSC) += smsc/
obj-$(CONFIG_NET_VENDOR_SOCIONEXT) += socionext/
obj-$(CONFIG_NET_VENDOR_STMICRO) += stmicro/
obj-$(CONFIG_NET_VENDOR_SUN) += sun/
+obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sunplus/
obj-$(CONFIG_NET_VENDOR_TEHUTI) += tehuti/
obj-$(CONFIG_NET_VENDOR_TI) += ti/
obj-$(CONFIG_NET_VENDOR_TOSHIBA) += toshiba/
diff --git a/drivers/net/ethernet/sunplus/Kconfig b/drivers/net/ethernet/sunplus/Kconfig
new file mode 100644
index 0000000..a9e3a4c
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Sunplus Ethernet device configuration
+#
+
+config NET_VENDOR_SUNPLUS
+ tristate "Sunplus Dual 10M/100M Ethernet (with L2 switch) devices"
+ depends on ETHERNET && SOC_SP7021
+ select PHYLIB
+ select PINCTRL_SPPCTL
+ select COMMON_CLK_SP7021
+ select RESET_SUNPLUS
+ select NVMEM_SUNPLUS_OCOTP
+ help
+ If you have Sunplus dual 10M/100M Ethernet (with L2 switch)
+ devices, say Y.
+ The network device supports dual 10M/100M Ethernet interfaces,
+ or one 10/100M Ethernet interface with two LAN ports.
+ To compile this driver as a module, choose M here. The module
+ will be called sp_l2sw.
diff --git a/drivers/net/ethernet/sunplus/Makefile b/drivers/net/ethernet/sunplus/Makefile
new file mode 100644
index 0000000..b401cec
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Sunplus network device drivers.
+#
+obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sp_l2sw.o
+sp_l2sw-objs := l2sw_driver.o l2sw_int.o l2sw_hal.o l2sw_desc.o l2sw_mac.o l2sw_mdio.o
diff --git a/drivers/net/ethernet/sunplus/l2sw_define.h b/drivers/net/ethernet/sunplus/l2sw_define.h
new file mode 100644
index 0000000..c1049c5
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_define.h
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_DEFINE_H__
+#define __L2SW_DEFINE_H__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/in.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/ethtool.h>
+#include <linux/platform_device.h>
+#include <linux/phy.h>
+#include <linux/mii.h>
+#include <linux/if_vlan.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "[L2SW]" fmt
+
+//define MAC interrupt status bit
+#define MAC_INT_DAISY_MODE_CHG BIT(31)
+#define MAC_INT_IP_CHKSUM_ERR BIT(23)
+#define MAC_INT_WDOG_TIMER1_EXP BIT(22)
+#define MAC_INT_WDOG_TIMER0_EXP BIT(21)
+#define MAC_INT_INTRUDER_ALERT BIT(20)
+#define MAC_INT_PORT_ST_CHG BIT(19)
+#define MAC_INT_BC_STORM BIT(18)
+#define MAC_INT_MUST_DROP_LAN BIT(17)
+#define MAC_INT_GLOBAL_QUE_FULL BIT(16)
+#define MAC_INT_TX_SOC_PAUSE_ON BIT(15)
+#define MAC_INT_RX_SOC_QUE_FULL BIT(14)
+#define MAC_INT_TX_LAN1_QUE_FULL BIT(9)
+#define MAC_INT_TX_LAN0_QUE_FULL BIT(8)
+#define MAC_INT_RX_L_DESCF BIT(7)
+#define MAC_INT_RX_H_DESCF BIT(6)
+#define MAC_INT_RX_DONE_L BIT(5)
+#define MAC_INT_RX_DONE_H BIT(4)
+#define MAC_INT_TX_DONE_L BIT(3)
+#define MAC_INT_TX_DONE_H BIT(2)
+#define MAC_INT_TX_DES_ERR BIT(1)
+#define MAC_INT_RX_DES_ERR BIT(0)
+
+#define MAC_INT_RX (MAC_INT_RX_DONE_H | MAC_INT_RX_DONE_L | \
+ MAC_INT_RX_DES_ERR)
+#define MAC_INT_TX (MAC_INT_TX_DONE_L | MAC_INT_TX_DONE_H | \
+ MAC_INT_TX_DES_ERR)
+#define MAC_INT_MASK_DEF (MAC_INT_DAISY_MODE_CHG | MAC_INT_IP_CHKSUM_ERR | \
+ MAC_INT_WDOG_TIMER1_EXP | MAC_INT_WDOG_TIMER0_EXP | \
+ MAC_INT_INTRUDER_ALERT | MAC_INT_BC_STORM | \
+ MAC_INT_MUST_DROP_LAN | MAC_INT_GLOBAL_QUE_FULL | \
+ MAC_INT_TX_SOC_PAUSE_ON | MAC_INT_RX_SOC_QUE_FULL | \
+ MAC_INT_TX_LAN1_QUE_FULL | MAC_INT_TX_LAN0_QUE_FULL | \
+ MAC_INT_RX_L_DESCF | MAC_INT_RX_H_DESCF)
+
+/*define port ability*/
+#define PORT_ABILITY_LINK_ST_P1 BIT(25)
+#define PORT_ABILITY_LINK_ST_P0 BIT(24)
+
+/*define PHY command bit*/
+#define PHY_WT_DATA_MASK 0xffff0000
+#define PHY_RD_CMD 0x00004000
+#define PHY_WT_CMD 0x00002000
+#define PHY_REG_MASK 0x00001f00
+#define PHY_ADR_MASK 0x0000001f
+
+/*define PHY status bit*/
+#define PHY_RD_DATA_MASK 0xffff0000
+#define PHY_RD_RDY BIT(1)
+#define PHY_WT_DONE BIT(0)
+
+/*define other register bit*/
+#define RX_MAX_LEN_MASK 0x00011000
+#define ROUTE_MODE_MASK 0x00000060
+#define POK_INT_THS_MASK 0x000E0000
+#define VLAN_TH_MASK 0x00000007
+
+/*define tx descriptor bit*/
+#define OWN_BIT BIT(31)
+#define FS_BIT BIT(25)
+#define LS_BIT BIT(24)
+#define LEN_MASK 0x000007FF
+#define PKTSP_MASK 0x00007000
+#define PKTSP_PORT1 0x00001000
+#define TO_VLAN_MASK 0x0003F000
+#define TO_VLAN_GROUP1 0x00002000
+
+#define EOR_BIT BIT(31)
+
+/*define rx descriptor bit*/
+#define ERR_CODE (0xf << 26)
+#define RX_TCP_UDP_CHKSUM_BIT BIT(23)
+#define RX_IP_CHKSUM_BIT BIT(18)
+
+#define OWC_BIT BIT(31)
+#define TXOK_BIT BIT(26)
+#define LNKF_BIT BIT(25)
+#define BUR_BIT BIT(22)
+#define TWDE_BIT BIT(20)
+#define CC_MASK 0x000f0000
+#define TBE_MASK 0x00070000
+
+// Address table search
+#define MAC_ADDR_LOOKUP_IDLE BIT(2)
+#define MAC_SEARCH_NEXT_ADDR BIT(1)
+#define MAC_BEGIN_SEARCH_ADDR BIT(0)
+
+// Address table search
+#define MAC_HASK_LOOKUP_ADDR_MASK (0x3ff << 22)
+#define MAC_AT_TABLE_END BIT(1)
+#define MAC_AT_DATA_READY BIT(0)
+
+#define MAC_PHY_ADDR 0x01 /* define by hardware */
+
+/*config descriptor*/
+#define TX_DESC_NUM 16
+#define MAC_GUARD_DESC_NUM 2
+#define RX_QUEUE0_DESC_NUM 16
+#define RX_QUEUE1_DESC_NUM 16
+#define TX_DESC_QUEUE_NUM 1
+#define RX_DESC_QUEUE_NUM 2
+
+#define MAC_TX_BUFF_SIZE 1536
+#define MAC_RX_LEN_MAX 2047
+
+#define DESC_ALIGN_BYTE 32
+#define RX_OFFSET 0
+#define TX_OFFSET 0
+
+#define ETHERNET_MAC_ADDR_LEN 6
+
+struct mac_desc {
+ u32 cmd1;
+ u32 cmd2;
+ u32 addr1;
+ u32 addr2;
+};
+
+struct skb_info {
+ struct sk_buff *skb;
+ u32 mapping;
+ u32 len;
+};
+
+struct l2sw_common {
+ struct net_device *net_dev;
+ struct platform_device *pdev;
+ int dual_nic;
+ int sa_learning;
+
+ void *desc_base;
+ dma_addr_t desc_dma;
+ s32 desc_size;
+ struct clk *clk;
+ struct reset_control *rstc;
+ int irq;
+
+ struct mac_desc *rx_desc[RX_DESC_QUEUE_NUM];
+ struct skb_info *rx_skb_info[RX_DESC_QUEUE_NUM];
+ u32 rx_pos[RX_DESC_QUEUE_NUM];
+ u32 rx_desc_num[RX_DESC_QUEUE_NUM];
+ u32 rx_desc_buff_size;
+
+ struct mac_desc *tx_desc;
+ struct skb_info tx_temp_skb_info[TX_DESC_NUM];
+ u32 tx_done_pos;
+ u32 tx_pos;
+ u32 tx_desc_full;
+
+ struct mii_bus *mii_bus;
+ struct phy_device *phy_dev;
+
+ struct napi_struct rx_napi;
+ struct napi_struct tx_napi;
+
+ spinlock_t rx_lock; // spinlock for accessing rx buffer
+ spinlock_t tx_lock; // spinlock for accessing tx buffer
+ spinlock_t ioctl_lock; // spinlock for ioctl operations
+ struct mutex store_mode; // mutex for dynamic mode change
+
+ struct device_node *phy1_node;
+ struct device_node *phy2_node;
+ u8 phy1_addr;
+ u8 phy2_addr;
+
+ u8 enable;
+};
+
+struct l2sw_mac {
+ struct platform_device *pdev;
+ struct net_device *net_dev;
+ struct l2sw_common *comm;
+ struct net_device *next_netdev;
+
+ struct net_device_stats dev_stats;
+
+ u8 mac_addr[ETHERNET_MAC_ADDR_LEN];
+
+ u8 lan_port;
+ u8 to_vlan;
+ u8 cpu_port;
+ u8 vlan_id;
+};
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/l2sw_desc.c b/drivers/net/ethernet/sunplus/l2sw_desc.c
new file mode 100644
index 0000000..345738f
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_desc.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "l2sw_desc.h"
+#include "l2sw_define.h"
+
+void rx_descs_flush(struct l2sw_common *comm)
+{
+ u32 i, j;
+ struct mac_desc *rx_desc;
+ struct skb_info *rx_skbinfo;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ rx_desc = comm->rx_desc[i];
+ rx_skbinfo = comm->rx_skb_info[i];
+ for (j = 0; j < comm->rx_desc_num[i]; j++) {
+ rx_desc[j].addr1 = rx_skbinfo[j].mapping;
+ rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
+ EOR_BIT | comm->rx_desc_buff_size :
+ comm->rx_desc_buff_size;
+ wmb(); // Set OWN_BIT after other fields are ready.
+ rx_desc[j].cmd1 = OWN_BIT;
+ }
+ }
+}
+
+void tx_descs_clean(struct l2sw_common *comm)
+{
+ u32 i;
+ s32 buflen;
+
+ if (!comm->tx_desc)
+ return;
+
+ for (i = 0; i < TX_DESC_NUM; i++) {
+ comm->tx_desc[i].cmd1 = 0;
+ wmb(); // Clear OWN_BIT and then set other fields.
+ comm->tx_desc[i].cmd2 = 0;
+ comm->tx_desc[i].addr1 = 0;
+ comm->tx_desc[i].addr2 = 0;
+
+ if (comm->tx_temp_skb_info[i].mapping) {
+ buflen = (comm->tx_temp_skb_info[i].skb) ?
+ comm->tx_temp_skb_info[i].skb->len :
+ MAC_TX_BUFF_SIZE;
+ dma_unmap_single(&comm->pdev->dev, comm->tx_temp_skb_info[i].mapping,
+ buflen, DMA_TO_DEVICE);
+ comm->tx_temp_skb_info[i].mapping = 0;
+ }
+
+ if (comm->tx_temp_skb_info[i].skb) {
+ dev_kfree_skb(comm->tx_temp_skb_info[i].skb);
+ comm->tx_temp_skb_info[i].skb = NULL;
+ }
+ }
+}
+
+void rx_descs_clean(struct l2sw_common *comm)
+{
+ u32 i, j;
+ struct mac_desc *rx_desc;
+ struct skb_info *rx_skbinfo;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ if (!comm->rx_skb_info[i])
+ continue;
+
+ rx_desc = comm->rx_desc[i];
+ rx_skbinfo = comm->rx_skb_info[i];
+ for (j = 0; j < comm->rx_desc_num[i]; j++) {
+ rx_desc[j].cmd1 = 0;
+ wmb(); // Clear OWN_BIT and then set other fields.
+ rx_desc[j].cmd2 = 0;
+ rx_desc[j].addr1 = 0;
+
+ if (rx_skbinfo[j].skb) {
+ dma_unmap_single(&comm->pdev->dev, rx_skbinfo[j].mapping,
+ comm->rx_desc_buff_size, DMA_FROM_DEVICE);
+ dev_kfree_skb(rx_skbinfo[j].skb);
+ rx_skbinfo[j].skb = NULL;
+ rx_skbinfo[j].mapping = 0;
+ }
+ }
+
+ kfree(rx_skbinfo);
+ comm->rx_skb_info[i] = NULL;
+ }
+}
+
+void descs_clean(struct l2sw_common *comm)
+{
+ rx_descs_clean(comm);
+ tx_descs_clean(comm);
+}
+
+void descs_free(struct l2sw_common *comm)
+{
+ u32 i;
+
+ descs_clean(comm);
+ comm->tx_desc = NULL;
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ comm->rx_desc[i] = NULL;
+
+ /* Free descriptor area */
+ if (comm->desc_base) {
+ dma_free_coherent(&comm->pdev->dev, comm->desc_size, comm->desc_base,
+ comm->desc_dma);
+ comm->desc_base = NULL;
+ comm->desc_dma = 0;
+ comm->desc_size = 0;
+ }
+}
+
+u32 tx_descs_init(struct l2sw_common *comm)
+{
+ memset(comm->tx_desc, '\0', sizeof(struct mac_desc) * (TX_DESC_NUM + MAC_GUARD_DESC_NUM));
+ return 0;
+}
+
+u32 rx_descs_init(struct l2sw_common *comm)
+{
+ struct sk_buff *skb;
+ u32 i, j;
+ struct mac_desc *rx_desc;
+ struct skb_info *rx_skbinfo;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ comm->rx_skb_info[i] = kmalloc_array(comm->rx_desc_num[i],
+ sizeof(struct skb_info), GFP_KERNEL);
+ if (!comm->rx_skb_info[i])
+ goto MEM_ALLOC_FAIL;
+
+ rx_skbinfo = comm->rx_skb_info[i];
+ rx_desc = comm->rx_desc[i];
+ for (j = 0; j < comm->rx_desc_num[i]; j++) {
+ skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
+ GFP_ATOMIC | GFP_DMA);
+ if (!skb)
+ goto MEM_ALLOC_FAIL;
+
+ skb->dev = comm->net_dev;
+ skb_reserve(skb, RX_OFFSET); /* +data +tail */
+
+ rx_skbinfo[j].skb = skb;
+ rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
+ comm->rx_desc_buff_size,
+ DMA_FROM_DEVICE);
+ rx_desc[j].addr1 = rx_skbinfo[j].mapping;
+ rx_desc[j].addr2 = 0;
+ rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
+ EOR_BIT | comm->rx_desc_buff_size :
+ comm->rx_desc_buff_size;
+ wmb(); // Set OWN_BIT after other fields are effective.
+ rx_desc[j].cmd1 = OWN_BIT;
+ }
+ }
+
+ return 0;
+
+MEM_ALLOC_FAIL:
+ rx_descs_clean(comm);
+ return -ENOMEM;
+}
+
+u32 descs_alloc(struct l2sw_common *comm)
+{
+ u32 i;
+ s32 desc_size;
+
+ /* Alloc descriptor area */
+ desc_size = (TX_DESC_NUM + MAC_GUARD_DESC_NUM) * sizeof(struct mac_desc);
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ desc_size += comm->rx_desc_num[i] * sizeof(struct mac_desc);
+
+ comm->desc_base = dma_alloc_coherent(&comm->pdev->dev, desc_size, &comm->desc_dma,
+ GFP_KERNEL);
+ if (!comm->desc_base)
+ return -ENOMEM;
+
+ comm->desc_size = desc_size;
+
+ /* Setup Tx descriptor */
+ comm->tx_desc = (struct mac_desc *)comm->desc_base;
+
+ /* Setup Rx descriptor */
+ comm->rx_desc[0] = &comm->tx_desc[TX_DESC_NUM + MAC_GUARD_DESC_NUM];
+ for (i = 1; i < RX_DESC_QUEUE_NUM; i++)
+ comm->rx_desc[i] = comm->rx_desc[i - 1] + comm->rx_desc_num[i - 1];
+
+ return 0;
+}
+
+u32 descs_init(struct l2sw_common *comm)
+{
+ u32 i, rc;
+
+ // Initialize rx descriptor's data
+ comm->rx_desc_num[0] = RX_QUEUE0_DESC_NUM;
+#if RX_DESC_QUEUE_NUM > 1
+ comm->rx_desc_num[1] = RX_QUEUE1_DESC_NUM;
+#endif
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ comm->rx_desc[i] = NULL;
+ comm->rx_skb_info[i] = NULL;
+ comm->rx_pos[i] = 0;
+ }
+ comm->rx_desc_buff_size = MAC_RX_LEN_MAX;
+
+ // Initialize tx descriptor's data
+ comm->tx_done_pos = 0;
+ comm->tx_desc = NULL;
+ comm->tx_pos = 0;
+ comm->tx_desc_full = 0;
+ for (i = 0; i < TX_DESC_NUM; i++)
+ comm->tx_temp_skb_info[i].skb = NULL;
+
+ // Allocate tx & rx descriptors.
+ rc = descs_alloc(comm);
+ if (rc) {
+ pr_err(" Failed to allocate tx & rx descriptors!\n");
+ return rc;
+ }
+
+ rc = tx_descs_init(comm);
+ if (rc)
+ return rc;
+
+ return rx_descs_init(comm);
+}
diff --git a/drivers/net/ethernet/sunplus/l2sw_desc.h b/drivers/net/ethernet/sunplus/l2sw_desc.h
new file mode 100644
index 0000000..d0647cb
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_desc.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_DESC_H__
+#define __L2SW_DESC_H__
+
+#include "l2sw_define.h"
+
+void rx_descs_flush(struct l2sw_common *comm);
+void tx_descs_clean(struct l2sw_common *comm);
+void rx_descs_clean(struct l2sw_common *comm);
+void descs_clean(struct l2sw_common *comm);
+void descs_free(struct l2sw_common *comm);
+u32 tx_descs_init(struct l2sw_common *comm);
+u32 rx_descs_init(struct l2sw_common *comm);
+u32 descs_alloc(struct l2sw_common *comm);
+u32 descs_init(struct l2sw_common *comm);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/l2sw_driver.c b/drivers/net/ethernet/sunplus/l2sw_driver.c
new file mode 100644
index 0000000..3dfd0dd
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_driver.c
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/nvmem-consumer.h>
+#include "l2sw_driver.h"
+
+static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x80
+};
+
+/*********************************************************************
+ *
+ * net_device_ops
+ *
+ **********************************************************************/
+static int ethernet_open(struct net_device *net_dev)
+{
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+
+ pr_debug(" Open port = %x\n", mac->lan_port);
+
+ mac->comm->enable |= mac->lan_port;
+
+ mac_hw_start(mac);
+ write_sw_int_mask0(read_sw_int_mask0() & ~(MAC_INT_TX | MAC_INT_RX));
+
+ netif_carrier_on(net_dev);
+ if (netif_carrier_ok(net_dev)) {
+ pr_debug(" Open netif_start_queue.\n");
+ netif_start_queue(net_dev);
+ }
+
+ return 0;
+}
+
+static int ethernet_stop(struct net_device *net_dev)
+{
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+
+ netif_stop_queue(net_dev);
+ netif_carrier_off(net_dev);
+
+ mac->comm->enable &= ~mac->lan_port;
+
+ mac_hw_stop(mac);
+
+ return 0;
+}
+
+/* Transmit a packet (called by the kernel) */
+static int ethernet_start_xmit(struct sk_buff *skb, struct net_device *net_dev)
+{
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+ struct l2sw_common *comm = mac->comm;
+ u32 tx_pos;
+ u32 cmd1;
+ u32 cmd2;
+ struct mac_desc *txdesc;
+ struct skb_info *skbinfo;
+ unsigned long flags;
+
+ if (unlikely(comm->tx_desc_full == 1)) { /* no desc left, wait for tx interrupt */
+ pr_err(" TX descriptor queue full when xmit!\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ /* if skb size shorter than 60, fill it with '\0' */
+ if (unlikely(skb->len < ETH_ZLEN)) {
+ if (skb_tailroom(skb) >= (ETH_ZLEN - skb->len)) {
+ memset(__skb_put(skb, ETH_ZLEN - skb->len), '\0', ETH_ZLEN - skb->len);
+ } else {
+ struct sk_buff *old_skb = skb;
+
+ skb = dev_alloc_skb(ETH_ZLEN + TX_OFFSET);
+ if (skb) {
+ memset(skb->data + old_skb->len, '\0', ETH_ZLEN - old_skb->len);
+ memcpy(skb->data, old_skb->data, old_skb->len);
+ skb_put(skb, ETH_ZLEN); /* add data to an sk_buff */
+ dev_kfree_skb_irq(old_skb);
+ } else {
+ skb = old_skb;
+ }
+ }
+ }
+
+ spin_lock_irqsave(&mac->comm->tx_lock, flags);
+ tx_pos = comm->tx_pos;
+ txdesc = &comm->tx_desc[tx_pos];
+ skbinfo = &comm->tx_temp_skb_info[tx_pos];
+ skbinfo->len = skb->len;
+ skbinfo->skb = skb;
+ skbinfo->mapping = dma_map_single(&mac->pdev->dev, skb->data, skb->len, DMA_TO_DEVICE);
+ cmd1 = (OWN_BIT | FS_BIT | LS_BIT | (mac->to_vlan << 12) | (skb->len & LEN_MASK));
+ cmd2 = skb->len & LEN_MASK;
+
+ if (tx_pos == (TX_DESC_NUM - 1))
+ cmd2 |= EOR_BIT;
+
+ txdesc->addr1 = skbinfo->mapping;
+ txdesc->cmd2 = cmd2;
+ wmb(); // Set OWN_BIT after other fields of descriptor are effective.
+ txdesc->cmd1 = cmd1;
+
+ NEXT_TX(tx_pos);
+
+ if (unlikely(tx_pos == comm->tx_done_pos)) {
+ netif_stop_queue(net_dev);
+ comm->tx_desc_full = 1;
+ //pr_info(" TX Descriptor Queue Full!\n");
+ }
+ comm->tx_pos = tx_pos;
+ wmb(); // make sure settings are effective.
+
+ /* trigger gmac to transmit */
+ tx_trigger();
+
+ spin_unlock_irqrestore(&mac->comm->tx_lock, flags);
+ return NETDEV_TX_OK;
+}
+
+static void ethernet_set_rx_mode(struct net_device *net_dev)
+{
+ if (net_dev) {
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+ struct l2sw_common *comm = mac->comm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&comm->ioctl_lock, flags);
+ rx_mode_set(net_dev);
+ spin_unlock_irqrestore(&comm->ioctl_lock, flags);
+ }
+}
+
+static int ethernet_set_mac_address(struct net_device *net_dev, void *addr)
+{
+ struct sockaddr *hwaddr = (struct sockaddr *)addr;
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+
+ if (netif_running(net_dev)) {
+ pr_err(" Device %s is busy!\n", net_dev->name);
+ return -EBUSY;
+ }
+
+ memcpy(net_dev->dev_addr, hwaddr->sa_data, net_dev->addr_len);
+
+ /* Delete the old Ethernet MAC address */
+ pr_debug(" HW Addr = %pM\n", mac->mac_addr);
+ if (is_valid_ether_addr(mac->mac_addr))
+ mac_hw_addr_del(mac);
+
+ /* Set the Ethernet MAC address */
+ memcpy(mac->mac_addr, hwaddr->sa_data, net_dev->addr_len);
+ mac_hw_addr_set(mac);
+
+ return 0;
+}
+
+static int ethernet_do_ioctl(struct net_device *net_dev, struct ifreq *ifr, int cmd)
+{
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+ struct l2sw_common *comm = mac->comm;
+ struct mii_ioctl_data *data = if_mii(ifr);
+ unsigned long flags;
+
+ pr_debug(" if = %s, cmd = %04x\n", ifr->ifr_ifrn.ifrn_name, cmd);
+ pr_debug(" phy_id = %d, reg_num = %d, val_in = %04x\n", data->phy_id,
+ data->reg_num, data->val_in);
+
+ // Check parameters' range.
+ if ((cmd == SIOCGMIIREG) || (cmd == SIOCSMIIREG)) {
+ if (data->reg_num > 31) {
+ pr_err(" reg_num (= %d) excesses range!\n", (int)data->reg_num);
+ return -EINVAL;
+ }
+ }
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ if (comm->dual_nic && (strcmp(ifr->ifr_ifrn.ifrn_name, "eth1") == 0))
+ return comm->phy2_addr;
+ else
+ return comm->phy1_addr;
+
+ case SIOCGMIIREG:
+ spin_lock_irqsave(&comm->ioctl_lock, flags);
+ data->val_out = mdio_read(data->phy_id, data->reg_num);
+ spin_unlock_irqrestore(&comm->ioctl_lock, flags);
+ pr_debug(" val_out = %04x\n", data->val_out);
+ break;
+
+ case SIOCSMIIREG:
+ spin_lock_irqsave(&comm->ioctl_lock, flags);
+ mdio_write(data->phy_id, data->reg_num, data->val_in);
+ spin_unlock_irqrestore(&comm->ioctl_lock, flags);
+ break;
+
+ default:
+ pr_err(" ioctl #%d has not implemented yet!\n", cmd);
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int ethernet_change_mtu(struct net_device *ndev, int new_mtu)
+{
+ if (netif_running(ndev))
+ return -EBUSY;
+
+ if (new_mtu < 68 || new_mtu > ETH_DATA_LEN)
+ return -EINVAL;
+
+ ndev->mtu = new_mtu;
+
+ return 0;
+}
+
+static void ethernet_tx_timeout(struct net_device *net_dev, unsigned int txqueue)
+{
+}
+
+static struct net_device_stats *ethernet_get_stats(struct net_device *net_dev)
+{
+ struct l2sw_mac *mac;
+
+ mac = netdev_priv(net_dev);
+ return &mac->dev_stats;
+}
+
+static const struct net_device_ops netdev_ops = {
+ .ndo_open = ethernet_open,
+ .ndo_stop = ethernet_stop,
+ .ndo_start_xmit = ethernet_start_xmit,
+ .ndo_set_rx_mode = ethernet_set_rx_mode,
+ .ndo_set_mac_address = ethernet_set_mac_address,
+ .ndo_do_ioctl = ethernet_do_ioctl,
+ .ndo_change_mtu = ethernet_change_mtu,
+ .ndo_tx_timeout = ethernet_tx_timeout,
+ .ndo_get_stats = ethernet_get_stats,
+};
+
+char *sp7021_otp_read_mac(struct device *_d, ssize_t *_l, char *_name)
+{
+ char *ret = NULL;
+ struct nvmem_cell *c = nvmem_cell_get(_d, _name);
+
+ if (IS_ERR_OR_NULL(c)) {
+ pr_err(" OTP %s read failure: %ld", _name, PTR_ERR(c));
+ return NULL;
+ }
+
+ ret = nvmem_cell_read(c, _l);
+ nvmem_cell_put(c);
+ pr_debug(" %zd bytes are read from OTP %s.", *_l, _name);
+
+ return ret;
+}
+
+static void check_mac_vendor_id_and_convert(char *mac_addr)
+{
+ // Byte order of MAC address of some samples are reversed.
+ // Check vendor id and convert byte order if it is wrong.
+ if ((mac_addr[5] == 0xFC) && (mac_addr[4] == 0x4B) && (mac_addr[3] == 0xBC) &&
+ ((mac_addr[0] != 0xFC) || (mac_addr[1] != 0x4B) || (mac_addr[2] != 0xBC))) {
+ char tmp;
+
+ // Swap mac_addr[0] and mac_addr[5]
+ tmp = mac_addr[0];
+ mac_addr[0] = mac_addr[5];
+ mac_addr[5] = tmp;
+
+ // Swap mac_addr[1] and mac_addr[4]
+ tmp = mac_addr[1];
+ mac_addr[1] = mac_addr[4];
+ mac_addr[4] = tmp;
+
+ // Swap mac_addr[2] and mac_addr[3]
+ tmp = mac_addr[2];
+ mac_addr[2] = mac_addr[3];
+ mac_addr[3] = tmp;
+ }
+}
+
+/*********************************************************************
+ *
+ * platform_driver
+ *
+ **********************************************************************/
+static u32 init_netdev(struct platform_device *pdev, int eth_no, struct net_device **r_ndev)
+{
+ u32 ret = -ENODEV;
+ struct l2sw_mac *mac;
+ struct net_device *net_dev;
+ char *m_addr_name = (eth_no == 0) ? "mac_addr0" : "mac_addr1";
+ ssize_t otp_l = 0;
+ char *otp_v;
+
+ /* allocate the devices, and also allocate l2sw_mac, we can get it by netdev_priv() */
+ net_dev = alloc_etherdev(sizeof(struct l2sw_mac));
+ if (!net_dev) {
+ *r_ndev = NULL;
+ return -ENOMEM;
+ }
+ SET_NETDEV_DEV(net_dev, &pdev->dev);
+ net_dev->netdev_ops = &netdev_ops;
+
+ mac = netdev_priv(net_dev);
+ mac->net_dev = net_dev;
+ mac->pdev = pdev;
+ mac->next_netdev = NULL;
+
+ // Get property 'mac-addr0' or 'mac-addr1' from dts.
+ otp_v = sp7021_otp_read_mac(&pdev->dev, &otp_l, m_addr_name);
+ if ((otp_l < 6) || IS_ERR_OR_NULL(otp_v)) {
+ pr_info(" OTP mac %s (len = %zd) is invalid, using default!\n",
+ m_addr_name, otp_l);
+ otp_l = 0;
+ } else {
+ // Check if mac-address is valid or not. If not, copy from default.
+ memcpy(mac->mac_addr, otp_v, 6);
+
+ // Byte order of Some samples are reversed. Convert byte order here.
+ check_mac_vendor_id_and_convert(mac->mac_addr);
+
+ if (!is_valid_ether_addr(mac->mac_addr)) {
+ pr_info(" Invalid mac in OTP[%s] = %pM, use default!\n",
+ m_addr_name, mac->mac_addr);
+ otp_l = 0;
+ }
+ }
+ if (otp_l != 6) {
+ memcpy(mac->mac_addr, def_mac_addr, ETHERNET_MAC_ADDR_LEN);
+ mac->mac_addr[5] += eth_no;
+ }
+
+ pr_info(" HW Addr = %pM\n", mac->mac_addr);
+
+ memcpy(net_dev->dev_addr, mac->mac_addr, ETHERNET_MAC_ADDR_LEN);
+
+ ret = register_netdev(net_dev);
+ if (ret != 0) {
+ pr_err(" Failed to register net device \"%s\" (ret = %d)!\n", net_dev->name, ret);
+ free_netdev(net_dev);
+ *r_ndev = NULL;
+ return ret;
+ }
+ pr_info(" Registered net device \"%s\" successfully.\n", net_dev->name);
+
+ *r_ndev = net_dev;
+ return 0;
+}
+
+static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct net_device *net_dev = dev_get_drvdata(dev);
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+
+ return sprintf(buf, "%d\n", (mac->comm->dual_nic) ? 1 : (mac->comm->sa_learning) ? 0 : 2);
+}
+
+static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct net_device *net_dev = dev_get_drvdata(dev);
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+ struct l2sw_common *comm = mac->comm;
+ struct net_device *net_dev2;
+ struct l2sw_mac *mac2;
+
+ if (buf[0] == '1') {
+ // Switch to dual NIC mode.
+ mutex_lock(&comm->store_mode);
+ if (!comm->dual_nic) {
+ mac_hw_stop(mac);
+
+ comm->dual_nic = 1;
+ comm->sa_learning = 0;
+ mac_disable_port_sa_learning();
+ mac_addr_table_del_all();
+ mac_switch_mode(mac);
+ rx_mode_set(net_dev);
+
+ init_netdev(mac->pdev, 1, &net_dev2); // Initialize the 2nd net device.
+ if (net_dev2) {
+ mac->next_netdev = net_dev2; // Pointed by previous net device.
+ mac2 = netdev_priv(net_dev2);
+ mac2->comm = comm;
+ net_dev2->irq = comm->irq;
+
+ mac_switch_mode(mac);
+ rx_mode_set(net_dev2);
+ mac_hw_addr_set(mac2);
+ }
+
+ comm->enable &= 0x1; // Keep lan 0, but always turn off lan 1.
+ mac_hw_start(mac);
+ }
+ mutex_unlock(&comm->store_mode);
+ } else if ((buf[0] == '0') || (buf[0] == '2')) {
+ // Switch to one NIC with daisy-chain mode.
+ mutex_lock(&comm->store_mode);
+
+ if (buf[0] == '2') {
+ if (comm->sa_learning == 1) {
+ comm->sa_learning = 0;
+ mac_disable_port_sa_learning();
+ mac_addr_table_del_all();
+ }
+ } else {
+ if (comm->sa_learning == 0) {
+ comm->sa_learning = 1;
+ mac_enable_port_sa_learning();
+ }
+ }
+
+ if (comm->dual_nic) {
+ struct net_device *net_dev2 = mac->next_netdev;
+
+ if (!netif_running(net_dev2)) {
+ mac_hw_stop(mac);
+
+ mac2 = netdev_priv(net_dev2);
+
+ // unregister and free net device.
+ unregister_netdev(net_dev2);
+ free_netdev(net_dev2);
+ mac->next_netdev = NULL;
+ pr_info(" Unregistered and freed net device \"eth1\"!\n");
+
+ comm->dual_nic = 0;
+ mac_switch_mode(mac);
+ rx_mode_set(net_dev);
+ mac_hw_addr_del(mac2);
+
+ // If eth0 is up, turn on lan 0 and 1 when
+ // switching to daisy-chain mode.
+ if (comm->enable & 0x1)
+ comm->enable = 0x3;
+ else
+ comm->enable = 0;
+
+ mac_hw_start(mac);
+ } else {
+ pr_err(" Error: Net device \"%s\" is running!\n", net_dev2->name);
+ }
+ }
+ mutex_unlock(&comm->store_mode);
+ } else {
+ pr_err(" Error: Unknown mode \"%c\"!\n", buf[0]);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(mode);
+static struct attribute *l2sw_sysfs_entries[] = {
+ &dev_attr_mode.attr,
+ NULL,
+};
+
+static struct attribute_group l2sw_attribute_group = {
+ .attrs = l2sw_sysfs_entries,
+};
+
+static int soc0_open(struct l2sw_mac *mac)
+{
+ struct l2sw_common *comm = mac->comm;
+ u32 rc;
+
+ mac_hw_stop(mac);
+
+ rc = descs_init(comm);
+ if (rc) {
+ pr_err(" Fail to initialize mac descriptors!\n");
+ goto INIT_DESC_FAIL;
+ }
+
+ /*start hardware port, open interrupt, start system tx queue */
+ mac_init(mac);
+ return 0;
+
+INIT_DESC_FAIL:
+ descs_free(comm);
+ return rc;
+}
+
+static int soc0_stop(struct l2sw_mac *mac)
+{
+ mac_hw_stop(mac);
+
+ descs_free(mac->comm);
+ return 0;
+}
+
+static int l2sw_probe(struct platform_device *pdev)
+{
+ struct l2sw_common *comm;
+ struct resource *r_mem;
+ struct net_device *net_dev, *net_dev2;
+ struct l2sw_mac *mac, *mac2;
+ u32 mode;
+ int ret = 0;
+ int rc;
+
+ if (platform_get_drvdata(pdev))
+ return -ENODEV;
+
+ // Allocate memory for l2sw 'common' area.
+ comm = kmalloc(sizeof(*comm), GFP_KERNEL);
+ if (!comm)
+ return -ENOMEM;
+ pr_debug(" comm = %p\n", comm);
+ memset(comm, '\0', sizeof(struct l2sw_common));
+ comm->pdev = pdev;
+
+ spin_lock_init(&comm->rx_lock);
+ spin_lock_init(&comm->tx_lock);
+ spin_lock_init(&comm->ioctl_lock);
+ mutex_init(&comm->store_mode);
+
+ // Get memory resoruce 0 from dts.
+ r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r_mem) {
+ pr_debug(" res->name = \"%s\", r_mem->start = %pa\n", r_mem->name, &r_mem->start);
+ if (l2sw_reg_base_set(devm_ioremap(&pdev->dev, r_mem->start,
+ (r_mem->end - r_mem->start + 1))) != 0) {
+ pr_err(" ioremap failed!\n");
+ ret = -ENOMEM;
+ goto out_free_comm;
+ }
+ } else {
+ pr_err(" No MEM resource 0 found!\n");
+ ret = -ENXIO;
+ goto out_free_comm;
+ }
+
+ // Get memory resoruce 1 from dts.
+ r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (r_mem) {
+ pr_debug(" res->name = \"%s\", r_mem->start = %pa\n", r_mem->name, &r_mem->start);
+ if (moon5_reg_base_set(devm_ioremap(&pdev->dev, r_mem->start,
+ (r_mem->end - r_mem->start + 1))) != 0) {
+ pr_err(" ioremap failed!\n");
+ ret = -ENOMEM;
+ goto out_free_comm;
+ }
+ } else {
+ pr_err(" No MEM resource 1 found!\n");
+ ret = -ENXIO;
+ goto out_free_comm;
+ }
+
+ // Get irq resource from dts.
+ if (l2sw_get_irq(pdev, comm) != 0) {
+ ret = -ENXIO;
+ goto out_free_comm;
+ }
+
+ // Get L2-switch mode.
+ ret = of_property_read_u32(pdev->dev.of_node, "mode", &mode);
+ if (ret)
+ mode = 0;
+ pr_info(" L2 switch mode = %u\n", mode);
+ if (mode == 2) {
+ comm->dual_nic = 0; // daisy-chain mode 2
+ comm->sa_learning = 0;
+ } else if (mode == 1) {
+ comm->dual_nic = 1; // dual NIC mode
+ comm->sa_learning = 0;
+ } else {
+ comm->dual_nic = 0; // daisy-chain mode
+ comm->sa_learning = 1;
+ }
+
+ // Get resource of clock controller
+ comm->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(comm->clk)) {
+ dev_err(&pdev->dev, "Failed to retrieve clock controller!\n");
+ ret = PTR_ERR(comm->clk);
+ goto out_free_comm;
+ }
+
+ comm->rstc = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(comm->rstc)) {
+ dev_err(&pdev->dev, "Failed to retrieve reset controller!\n");
+ ret = PTR_ERR(comm->rstc);
+ goto out_free_comm;
+ }
+
+ // Enable clock.
+ clk_prepare_enable(comm->clk);
+ udelay(1);
+
+ ret = reset_control_assert(comm->rstc);
+ udelay(1);
+ ret = reset_control_deassert(comm->rstc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to deassert reset line (err = %d)!\n", ret);
+ ret = -ENODEV;
+ goto out_free_comm;
+ }
+ udelay(1);
+
+ ret = init_netdev(pdev, 0, &net_dev); // Initialize the 1st net device (eth0)
+ if (!net_dev)
+ goto out_free_comm;
+
+ platform_set_drvdata(pdev, net_dev); // Pointed by drvdata net device.
+
+ net_dev->irq = comm->irq;
+
+ mac = netdev_priv(net_dev);
+ mac->comm = comm;
+ comm->net_dev = net_dev;
+ pr_debug(" net_dev = %p, mac = %p, comm = %p\n", net_dev, mac, mac->comm);
+
+ comm->phy1_node = of_parse_phandle(pdev->dev.of_node, "phy-handle1", 0);
+ comm->phy2_node = of_parse_phandle(pdev->dev.of_node, "phy-handle2", 0);
+
+ // Get address of phy of ethernet from dts.
+ if (of_property_read_u32(comm->phy1_node, "reg", &rc) == 0) {
+ comm->phy1_addr = rc;
+ } else {
+ comm->phy1_addr = 0;
+ pr_info(" Cannot get address of phy of ethernet 1! Set to 0 by default.\n");
+ }
+
+ if (of_property_read_u32(comm->phy2_node, "reg", &rc) == 0) {
+ comm->phy2_addr = rc;
+ } else {
+ comm->phy2_addr = 1;
+ pr_info(" Cannot get address of phy of ethernet 2! Set to 1 by default.\n");
+ }
+
+ l2sw_enable_port(mac);
+
+ if (comm->phy1_node) {
+ ret = mdio_init(pdev, net_dev);
+ if (ret) {
+ pr_err(" Failed to initialize mdio!\n");
+ goto out_unregister_dev;
+ }
+
+ ret = mac_phy_probe(net_dev);
+ if (ret) {
+ pr_err(" Failed to probe phy!\n");
+ goto out_freemdio;
+ }
+ } else {
+ pr_err(" Failed to get phy-handle!\n");
+ }
+
+ phy_cfg(mac);
+
+ // Register irq to system.
+ if (l2sw_request_irq(pdev, comm, net_dev) != 0) {
+ ret = -ENODEV;
+ goto out_freemdio;
+ }
+
+ netif_napi_add(net_dev, &comm->rx_napi, rx_poll, RX_NAPI_WEIGHT);
+ napi_enable(&comm->rx_napi);
+ netif_napi_add(net_dev, &comm->tx_napi, tx_poll, TX_NAPI_WEIGHT);
+ napi_enable(&comm->tx_napi);
+
+ soc0_open(mac);
+
+ // Set MAC address
+ mac_hw_addr_set(mac);
+
+ /* Add the device attributes */
+ rc = sysfs_create_group(&pdev->dev.kobj, &l2sw_attribute_group);
+ if (rc)
+ dev_err(&pdev->dev, "Error creating sysfs files!\n");
+
+ mac_addr_table_del_all();
+ if (comm->sa_learning)
+ mac_enable_port_sa_learning();
+ else
+ mac_disable_port_sa_learning();
+ rx_mode_set(net_dev);
+
+ if (comm->dual_nic) {
+ init_netdev(pdev, 1, &net_dev2);
+ if (!net_dev2)
+ goto fail_to_init_2nd_port;
+ mac->next_netdev = net_dev2; // Pointed by previous net device.
+
+ net_dev2->irq = comm->irq;
+ mac2 = netdev_priv(net_dev2);
+ mac2->comm = comm;
+ pr_debug(" net_dev = %p, mac = %p, comm = %p\n", net_dev2, mac2, mac2->comm);
+
+ mac_switch_mode(mac);
+ rx_mode_set(net_dev2);
+ mac_hw_addr_set(mac2); // Set MAC address for the second net device.
+ }
+
+fail_to_init_2nd_port:
+ return 0;
+
+out_freemdio:
+ if (comm->mii_bus)
+ mdio_remove(net_dev);
+
+out_unregister_dev:
+ unregister_netdev(net_dev);
+
+out_free_comm:
+ kfree(comm);
+ return ret;
+}
+
+static int l2sw_remove(struct platform_device *pdev)
+{
+ struct net_device *net_dev;
+ struct net_device *net_dev2;
+ struct l2sw_mac *mac;
+
+ net_dev = platform_get_drvdata(pdev);
+ if (!net_dev)
+ return 0;
+ mac = netdev_priv(net_dev);
+
+ // Unregister and free 2nd net device.
+ net_dev2 = mac->next_netdev;
+ if (net_dev2) {
+ unregister_netdev(net_dev2);
+ free_netdev(net_dev2);
+ }
+
+ sysfs_remove_group(&pdev->dev.kobj, &l2sw_attribute_group);
+
+ mac->comm->enable = 0;
+ soc0_stop(mac);
+
+ napi_disable(&mac->comm->rx_napi);
+ netif_napi_del(&mac->comm->rx_napi);
+ napi_disable(&mac->comm->tx_napi);
+ netif_napi_del(&mac->comm->tx_napi);
+
+ mdio_remove(net_dev);
+
+ // Unregister and free 1st net device.
+ unregister_netdev(net_dev);
+ free_netdev(net_dev);
+
+ clk_disable(mac->comm->clk);
+
+ // Free 'common' area.
+ kfree(mac->comm);
+ return 0;
+}
+
+static const struct of_device_id sp_l2sw_of_match[] = {
+ {.compatible = "sunplus,sp7021-l2sw"},
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, sp_l2sw_of_match);
+
+static struct platform_driver l2sw_driver = {
+ .probe = l2sw_probe,
+ .remove = l2sw_remove,
+ .driver = {
+ .name = "sp_l2sw",
+ .owner = THIS_MODULE,
+ .of_match_table = sp_l2sw_of_match,
+ },
+};
+
+module_platform_driver(l2sw_driver);
+
+MODULE_AUTHOR("Wells Lu <[email protected]>");
+MODULE_DESCRIPTION("Sunplus 10M/100M Ethernet (with L2 switch) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/sunplus/l2sw_driver.h b/drivers/net/ethernet/sunplus/l2sw_driver.h
new file mode 100644
index 0000000..a076b33
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_driver.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_DRIVER_H__
+#define __L2SW_DRIVER_H__
+
+#include "l2sw_define.h"
+#include "l2sw_register.h"
+#include "l2sw_hal.h"
+#include "l2sw_int.h"
+#include "l2sw_mdio.h"
+#include "l2sw_mac.h"
+#include "l2sw_desc.h"
+
+#define NEXT_TX(N) ((N) = (((N) + 1) == TX_DESC_NUM) ? 0 : (N) + 1)
+#define NEXT_RX(QUEUE, N) ((N) = (((N) + 1) == mac->comm->rx_desc_num[QUEUE]) ? 0 : (N) + 1)
+
+#define RX_NAPI_WEIGHT 16
+#define TX_NAPI_WEIGHT 16
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/l2sw_hal.c b/drivers/net/ethernet/sunplus/l2sw_hal.c
new file mode 100644
index 0000000..2aac817
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_hal.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "l2sw_hal.h"
+
+static struct l2sw_reg *l2sw_reg_base;
+static struct moon5_reg *moon5_reg_base;
+
+int l2sw_reg_base_set(void __iomem *baseaddr)
+{
+ l2sw_reg_base = (struct l2sw_reg *)baseaddr;
+ pr_debug(" l2sw_reg_base = %p\n", l2sw_reg_base);
+
+ if (!l2sw_reg_base)
+ return -1;
+
+ return 0;
+}
+
+int moon5_reg_base_set(void __iomem *baseaddr)
+{
+ moon5_reg_base = (struct moon5_reg *)baseaddr;
+ pr_debug(" moon5_reg_base = %p\n", moon5_reg_base);
+
+ if (!moon5_reg_base)
+ return -1;
+
+ return 0;
+}
+
+void mac_hw_stop(struct l2sw_mac *mac)
+{
+ struct l2sw_common *comm = mac->comm;
+ u32 reg, disable;
+
+ if (comm->enable == 0) {
+ HWREG_W(sw_int_mask_0, 0xffffffff);
+ HWREG_W(sw_int_status_0, 0xffffffff & (~MAC_INT_PORT_ST_CHG));
+
+ reg = HWREG_R(cpu_cntl);
+ HWREG_W(cpu_cntl, (0x3 << 6) | reg); // Disable cpu 0 and cpu 1.
+ }
+
+ if (comm->dual_nic) {
+ disable = ((~comm->enable) & 0x3) << 24;
+ reg = HWREG_R(port_cntl0);
+ HWREG_W(port_cntl0, disable | reg); // Disable lan 0 and lan 1.
+ wmb(); // make sure settings are effective.
+ }
+}
+
+void mac_hw_reset(struct l2sw_mac *mac)
+{
+}
+
+void mac_hw_start(struct l2sw_mac *mac)
+{
+ struct l2sw_common *comm = mac->comm;
+ u32 reg;
+
+ //enable cpu port 0 (6) & port 0 crc padding (8)
+ reg = HWREG_R(cpu_cntl);
+ HWREG_W(cpu_cntl, (reg & (~(0x1 << 6))) | (0x1 << 8));
+ wmb(); // make sure settings are effective.
+
+ //enable lan 0 & lan 1
+ reg = HWREG_R(port_cntl0);
+ HWREG_W(port_cntl0, reg & (~(comm->enable << 24)));
+ wmb(); // make sure settings are effective.
+}
+
+void mac_hw_addr_set(struct l2sw_mac *mac)
+{
+ u32 reg;
+
+ HWREG_W(w_mac_15_0, mac->mac_addr[0] + (mac->mac_addr[1] << 8));
+ HWREG_W(w_mac_47_16, mac->mac_addr[2] + (mac->mac_addr[3] << 8) +
+ (mac->mac_addr[4] << 16) + (mac->mac_addr[5] << 24));
+ wmb(); // make sure settings are effective.
+
+ // Set aging=1
+ HWREG_W(wt_mac_ad0, (mac->cpu_port << 10) + (mac->vlan_id << 7) + (1 << 4) + 0x1);
+ wmb(); // make sure settings are effective.
+
+ do {
+ reg = HWREG_R(wt_mac_ad0);
+ ndelay(10);
+ pr_debug(" wt_mac_ad0 = %08x\n", reg);
+ } while ((reg & (0x1 << 1)) == 0x0);
+ pr_debug(" mac_ad0 = %08x, mac_ad = %08x%04x\n", HWREG_R(wt_mac_ad0),
+ HWREG_R(w_mac_47_16), HWREG_R(w_mac_15_0) & 0xffff);
+}
+
+void mac_hw_addr_del(struct l2sw_mac *mac)
+{
+ u32 reg;
+
+ HWREG_W(w_mac_15_0, mac->mac_addr[0] + (mac->mac_addr[1] << 8));
+ HWREG_W(w_mac_47_16, mac->mac_addr[2] + (mac->mac_addr[3] << 8) +
+ (mac->mac_addr[4] << 16) + (mac->mac_addr[5] << 24));
+ wmb(); // make sure settings are effective.
+
+ HWREG_W(wt_mac_ad0, (0x1 << 12) + (mac->vlan_id << 7) + 0x1);
+ wmb(); // make sure settings are effective.
+ do {
+ reg = HWREG_R(wt_mac_ad0);
+ ndelay(10);
+ pr_debug(" wt_mac_ad0 = %08x\n", reg);
+ } while ((reg & (0x1 << 1)) == 0x0);
+ pr_debug(" mac_ad0 = %08x, mac_ad = %08x%04x\n", HWREG_R(wt_mac_ad0),
+ HWREG_R(w_mac_47_16), HWREG_R(w_mac_15_0) & 0xffff);
+}
+
+void mac_addr_table_del_all(void)
+{
+ u32 reg;
+
+ // Wait for address table being idle.
+ do {
+ reg = HWREG_R(addr_tbl_srch);
+ ndelay(10);
+ } while (!(reg & MAC_ADDR_LOOKUP_IDLE));
+
+ // Search address table from start.
+ HWREG_W(addr_tbl_srch, HWREG_R(addr_tbl_srch) | MAC_BEGIN_SEARCH_ADDR);
+ mb(); // make sure settings are effective.
+ while (1) {
+ do {
+ reg = HWREG_R(addr_tbl_st);
+ ndelay(10);
+ pr_debug(" addr_tbl_st = %08x\n", reg);
+ } while (!(reg & (MAC_AT_TABLE_END | MAC_AT_DATA_READY)));
+
+ if (reg & MAC_AT_TABLE_END)
+ break;
+
+ pr_debug(" addr_tbl_st = %08x\n", reg);
+ pr_debug(" @AT #%u: port=%01x, cpu=%01x, vid=%u, aging=%u, proxy=%u, mc_ingress=%u\n",
+ (reg >> 22) & 0x3ff, (reg >> 12) & 0x3, (reg >> 10) & 0x3,
+ (reg >> 7) & 0x7, (reg >> 4) & 0x7, (reg >> 3) & 0x1,
+ (reg >> 2) & 0x1);
+
+ // Delete all entries which are learnt from lan ports.
+ if ((reg >> 12) & 0x3) {
+ HWREG_W(w_mac_15_0, HWREG_R(mac_ad_ser0));
+ wmb(); // make sure settings are effective.
+ HWREG_W(w_mac_47_16, HWREG_R(mac_ad_ser1));
+ wmb(); // make sure settings are effective.
+
+ HWREG_W(wt_mac_ad0, (0x1 << 12) + (reg & (0x7 << 7)) + 0x1);
+ wmb(); // make sure settings are effective.
+ do {
+ reg = HWREG_R(wt_mac_ad0);
+ ndelay(10);
+ pr_debug(" wt_mac_ad0 = %08x\n", reg);
+ } while ((reg & (0x1 << 1)) == 0x0);
+ pr_debug(" mac_ad0 = %08x, mac_ad = %08x%04x\n", HWREG_R(wt_mac_ad0),
+ HWREG_R(w_mac_47_16), HWREG_R(w_mac_15_0) & 0xffff);
+ }
+ // Search next.
+ wmb(); // make sure settings are effective.
+
+ HWREG_W(addr_tbl_srch, HWREG_R(addr_tbl_srch) | MAC_SEARCH_NEXT_ADDR);
+ }
+}
+
+void mac_hw_init(struct l2sw_mac *mac)
+{
+ struct l2sw_common *comm = mac->comm;
+ u32 reg;
+
+ reg = HWREG_R(cpu_cntl);
+ HWREG_W(cpu_cntl, (0x3 << 6) | reg); // Disable cpu0 and cpu 1.
+ wmb(); // make sure settings are effective.
+
+ /* descriptor base address */
+ HWREG_W(tx_lbase_addr_0, mac->comm->desc_dma);
+ HWREG_W(tx_hbase_addr_0, mac->comm->desc_dma + sizeof(struct mac_desc) * TX_DESC_NUM);
+ HWREG_W(rx_hbase_addr_0, mac->comm->desc_dma +
+ sizeof(struct mac_desc) * (TX_DESC_NUM + MAC_GUARD_DESC_NUM));
+ HWREG_W(rx_lbase_addr_0, mac->comm->desc_dma +
+ sizeof(struct mac_desc) * (TX_DESC_NUM + MAC_GUARD_DESC_NUM + RX_QUEUE0_DESC_NUM));
+ wmb(); // make sure settings are effective.
+
+ // Threshold values
+ HWREG_W(fl_cntl_th, 0x4a3a2d1d); // Fc_rls_th=0x4a, Fc_set_th=0x3a,
+ // Drop_rls_th=0x2d, Drop_set_th=0x1d
+ HWREG_W(cpu_fl_cntl_th, 0x4a3a1212); // Cpu_rls_th=0x4a, Cpu_set_th=0x3a,
+ // Cpu_th=0x12, Port_th=0x12
+ HWREG_W(pri_fl_cntl, 0xf6680000); // mtcc_lmt=0xf, Pri_th_l=6,
+ // Pri_th_h=6, weigh_8x_en=1
+
+ // High-active LED
+ reg = HWREG_R(led_port0);
+ HWREG_W(led_port0, reg | (1 << 28));
+
+ /* phy address */
+ reg = HWREG_R(mac_force_mode);
+ reg = (reg & (~(0x1f << 16))) | ((mac->comm->phy1_addr & 0x1f) << 16);
+ reg = (reg & (~(0x1f << 24))) | ((mac->comm->phy2_addr & 0x1f) << 24);
+ HWREG_W(mac_force_mode, reg);
+
+ //disable cpu port0 aging (12)
+ //disable cpu port0 learning (14)
+ //enable UC and MC packets
+ reg = HWREG_R(cpu_cntl);
+ HWREG_W(cpu_cntl, (reg & (~((0x1 << 14) | (0x3c << 0)))) | (0x1 << 12));
+
+ mac_switch_mode(mac);
+
+ if (!comm->dual_nic) {
+ //enable lan 0 & lan 1
+ reg = HWREG_R(port_cntl0);
+ HWREG_W(port_cntl0, reg & (~(0x3 << 24)));
+ wmb(); // make sure settings are effective.
+ }
+
+ wmb(); // make sure settings are effective.
+ HWREG_W(sw_int_mask_0, MAC_INT_MASK_DEF);
+}
+
+void mac_switch_mode(struct l2sw_mac *mac)
+{
+ struct l2sw_common *comm = mac->comm;
+ u32 reg;
+
+ if (comm->dual_nic) {
+ mac->cpu_port = 0x1; // soc0
+ mac->lan_port = 0x1; // forward to port 0
+ mac->to_vlan = 0x1; // vlan group: 0
+ mac->vlan_id = 0x0; // vlan group: 0
+
+ if (mac->next_netdev) {
+ struct l2sw_mac *mac2 = netdev_priv(mac->next_netdev);
+
+ mac2->cpu_port = 0x1; // soc0
+ mac2->lan_port = 0x2; // forward to port 1
+ mac2->to_vlan = 0x2; // vlan group: 1
+ mac2->vlan_id = 0x1; // vlan group: 1
+ }
+ //port 0: VLAN group 0
+ //port 1: VLAN group 1
+ HWREG_W(pvid_config0, (1 << 4) + 0);
+
+ //VLAN group 0: cpu0+port0
+ //VLAN group 1: cpu0+port1
+ HWREG_W(vlan_memset_config0, (0xa << 8) + 0x9);
+
+ //RMC forward: to cpu
+ //LED: 60mS
+ //BC storm prev: 31 BC
+ reg = HWREG_R(sw_glb_cntl);
+ HWREG_W(sw_glb_cntl, (reg & (~((0x3 << 25) | (0x3 << 23) | (0x3 << 4)))) |
+ (0x1 << 25) | (0x1 << 23) | (0x1 << 4));
+ } else {
+ mac->cpu_port = 0x1; // soc0
+ mac->lan_port = 0x3; // forward to port 0 and 1
+ mac->to_vlan = 0x1; // vlan group: 0
+ mac->vlan_id = 0x0; // vlan group: 0
+ comm->enable = 0x3; // enable lan 0 and 1
+
+ //port 0: VLAN group 0
+ //port 1: VLAN group 0
+ HWREG_W(pvid_config0, (0 << 4) + 0);
+
+ //VLAN group 0: cpu0+port1+port0
+ HWREG_W(vlan_memset_config0, (0x0 << 8) + 0xb);
+
+ //RMC forward: broadcast
+ //LED: 60mS
+ //BC storm prev: 31 BC
+ reg = HWREG_R(sw_glb_cntl);
+ HWREG_W(sw_glb_cntl, (reg & (~((0x3 << 25) | (0x3 << 23) | (0x3 << 4)))) |
+ (0x0 << 25) | (0x1 << 23) | (0x1 << 4));
+ }
+}
+
+void mac_disable_port_sa_learning(void)
+{
+ u32 reg;
+
+ // Disable lan port SA learning.
+ reg = HWREG_R(port_cntl1);
+ HWREG_W(port_cntl1, reg | (0x3 << 8));
+}
+
+void mac_enable_port_sa_learning(void)
+{
+ u32 reg;
+
+ // Disable lan port SA learning.
+ reg = HWREG_R(port_cntl1);
+ HWREG_W(port_cntl1, reg & (~(0x3 << 8)));
+}
+
+void rx_mode_set(struct net_device *net_dev)
+{
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+ u32 mask, reg, rx_mode;
+
+ pr_debug(" net_dev->flags = %08x\n", net_dev->flags);
+
+ mask = (mac->lan_port << 2) | (mac->lan_port << 0);
+ reg = HWREG_R(cpu_cntl);
+
+ if (net_dev->flags & IFF_PROMISC) { /* Set promiscuous mode */
+ // Allow MC and unknown UC packets
+ rx_mode = (mac->lan_port << 2) | (mac->lan_port << 0);
+ } else if ((!netdev_mc_empty(net_dev) && (net_dev->flags & IFF_MULTICAST)) ||
+ (net_dev->flags & IFF_ALLMULTI)) {
+ // Allow MC packets
+ rx_mode = (mac->lan_port << 2);
+ } else {
+ // Disable MC and unknown UC packets
+ rx_mode = 0;
+ }
+
+ HWREG_W(cpu_cntl, (reg & (~mask)) | ((~rx_mode) & mask));
+ pr_debug(" cpu_cntl = %08x\n", HWREG_R(cpu_cntl));
+}
+
+static int mdio_access(u8 op_cd, u8 dev_reg_addr, u8 phy_addr, u32 wdata)
+{
+ u32 value, time = 0;
+
+ HWREG_W(phy_cntl_reg0, (wdata << 16) | (op_cd << 13) | (dev_reg_addr << 8) | phy_addr);
+ wmb(); // make sure settings are effective.
+ do {
+ if (++time > MDIO_RW_TIMEOUT_RETRY_NUMBERS) {
+ pr_err(" mdio failed to operate!\n");
+ time = 0;
+ }
+
+ value = HWREG_R(phy_cntl_reg1);
+ } while ((value & 0x3) == 0);
+
+ if (time == 0)
+ return -1;
+
+ return value >> 16;
+}
+
+u32 mdio_read(u32 phy_id, u16 regnum)
+{
+ int ret;
+
+ ret = mdio_access(MDIO_READ_CMD, regnum, phy_id, 0);
+ if (ret < 0)
+ return -EIO;
+
+ return ret;
+}
+
+u32 mdio_write(u32 phy_id, u32 regnum, u16 val)
+{
+ int ret;
+
+ ret = mdio_access(MDIO_WRITE_CMD, regnum, phy_id, val);
+ if (ret < 0)
+ return -EIO;
+
+ return 0;
+}
+
+inline void tx_trigger(void)
+{
+ HWREG_W(cpu_tx_trig, (0x1 << 1));
+}
+
+inline u32 read_sw_int_mask0(void)
+{
+ return HWREG_R(sw_int_mask_0);
+}
+
+inline void write_sw_int_mask0(u32 value)
+{
+ HWREG_W(sw_int_mask_0, value);
+}
+
+inline void write_sw_int_status0(u32 value)
+{
+ HWREG_W(sw_int_status_0, value);
+}
+
+inline u32 read_sw_int_status0(void)
+{
+ return HWREG_R(sw_int_status_0);
+}
+
+inline u32 read_port_ability(void)
+{
+ return HWREG_R(port_ability);
+}
+
+void l2sw_enable_port(struct l2sw_mac *mac)
+{
+ u32 reg;
+
+ //set clock
+ reg = MOON5REG_R(mo4_l2sw_clksw_ctl);
+ MOON5REG_W(mo4_l2sw_clksw_ctl, reg | (0xf << 16) | 0xf);
+
+ //phy address
+ reg = HWREG_R(mac_force_mode);
+ reg = (reg & (~(0x1f << 16))) | ((mac->comm->phy1_addr & 0x1f) << 16);
+ reg = (reg & (~(0x1f << 24))) | ((mac->comm->phy2_addr & 0x1f) << 24);
+ HWREG_W(mac_force_mode, reg);
+ wmb(); // make sure settings are effective.
+}
+
+int phy_cfg(struct l2sw_mac *mac)
+{
+ // Bug workaround:
+ // Flow-control of phy should be enabled. L2SW IP flow-control will refer
+ // to the bit to decide to enable or disable flow-control.
+ mdio_write(mac->comm->phy1_addr, 4, mdio_read(mac->comm->phy1_addr, 4) | (1 << 10));
+ mdio_write(mac->comm->phy2_addr, 4, mdio_read(mac->comm->phy2_addr, 4) | (1 << 10));
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/sunplus/l2sw_hal.h b/drivers/net/ethernet/sunplus/l2sw_hal.h
new file mode 100644
index 0000000..7f04659
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_hal.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_HAL_H__
+#define __L2SW_HAL_H__
+
+#include "l2sw_register.h"
+#include "l2sw_define.h"
+#include "l2sw_desc.h"
+
+#define HWREG_W(M, N) (l2sw_reg_base->M = N)
+#define HWREG_R(M) (l2sw_reg_base->M)
+#define MOON5REG_W(M, N) (moon5_reg_base->M = N)
+#define MOON5REG_R(M) (moon5_reg_base->M)
+
+#define MDIO_RW_TIMEOUT_RETRY_NUMBERS 500
+#define MDIO_READ_CMD 0x02
+#define MDIO_WRITE_CMD 0x01
+
+int l2sw_reg_base_set(void __iomem *baseaddr);
+int moon5_reg_base_set(void __iomem *baseaddr);
+void mac_hw_stop(struct l2sw_mac *mac);
+void mac_hw_reset(struct l2sw_mac *mac);
+void mac_hw_start(struct l2sw_mac *mac);
+void mac_hw_addr_set(struct l2sw_mac *mac);
+void mac_hw_addr_del(struct l2sw_mac *mac);
+void mac_addr_table_del_all(void);
+void mac_hw_init(struct l2sw_mac *mac);
+void mac_switch_mode(struct l2sw_mac *mac);
+void mac_disable_port_sa_learning(void);
+void mac_enable_port_sa_learning(void);
+void rx_mode_set(struct net_device *net_dev);
+u32 mdio_read(u32 phy_id, u16 regnum);
+u32 mdio_write(u32 phy_id, u32 regnum, u16 val);
+void tx_trigger(void);
+u32 read_sw_int_mask0(void);
+void write_sw_int_mask0(u32 value);
+void write_sw_int_status0(u32 value);
+u32 read_sw_int_status0(void);
+u32 read_port_ability(void);
+int phy_cfg(struct l2sw_mac *mac);
+void l2sw_enable_port(struct l2sw_mac *mac);
+void regs_print(void);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/l2sw_int.c b/drivers/net/ethernet/sunplus/l2sw_int.c
new file mode 100644
index 0000000..865d2d1
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_int.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "l2sw_define.h"
+#include "l2sw_int.h"
+#include "l2sw_driver.h"
+#include "l2sw_hal.h"
+
+static inline void port_status_change(struct l2sw_mac *mac)
+{
+ u32 reg;
+ struct net_device *net_dev = (struct net_device *)mac->net_dev;
+
+ reg = read_port_ability();
+ if (mac->comm->dual_nic) {
+ if (!netif_carrier_ok(net_dev) && (reg & PORT_ABILITY_LINK_ST_P0)) {
+ netif_carrier_on(net_dev);
+ netif_start_queue(net_dev);
+ } else if (netif_carrier_ok(net_dev) && !(reg & PORT_ABILITY_LINK_ST_P0)) {
+ netif_carrier_off(net_dev);
+ netif_stop_queue(net_dev);
+ }
+
+ if (mac->next_netdev) {
+ struct net_device *ndev2 = mac->next_netdev;
+
+ if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
+ netif_carrier_on(ndev2);
+ netif_start_queue(ndev2);
+ } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
+ netif_carrier_off(ndev2);
+ netif_stop_queue(ndev2);
+ }
+ }
+ } else {
+ if (!netif_carrier_ok(net_dev) &&
+ (reg & (PORT_ABILITY_LINK_ST_P1 | PORT_ABILITY_LINK_ST_P0))) {
+ netif_carrier_on(net_dev);
+ netif_start_queue(net_dev);
+ } else if (netif_carrier_ok(net_dev) &&
+ !(reg & (PORT_ABILITY_LINK_ST_P1 | PORT_ABILITY_LINK_ST_P0))) {
+ netif_carrier_off(net_dev);
+ netif_stop_queue(net_dev);
+ }
+ }
+}
+
+static inline void rx_skb(struct l2sw_mac *mac, struct sk_buff *skb)
+{
+ mac->dev_stats.rx_packets++;
+ mac->dev_stats.rx_bytes += skb->len;
+ netif_receive_skb(skb);
+}
+
+int rx_poll(struct napi_struct *napi, int budget)
+{
+ struct l2sw_common *comm = container_of(napi, struct l2sw_common, rx_napi);
+ struct l2sw_mac *mac = netdev_priv(comm->net_dev);
+ struct sk_buff *skb, *new_skb;
+ struct skb_info *sinfo;
+ struct mac_desc *desc;
+ struct mac_desc *h_desc;
+ u32 rx_pos, pkg_len;
+ u32 cmd;
+ u32 num, rx_count;
+ s32 queue;
+ int ndev2_pkt;
+ struct net_device_stats *dev_stats;
+
+ spin_lock(&comm->rx_lock);
+
+ // Process high-priority queue and then low-priority queue.
+ for (queue = 0; queue < RX_DESC_QUEUE_NUM; queue++) {
+ rx_pos = comm->rx_pos[queue];
+ rx_count = comm->rx_desc_num[queue];
+
+ for (num = 0; num < rx_count; num++) {
+ sinfo = comm->rx_skb_info[queue] + rx_pos;
+ desc = comm->rx_desc[queue] + rx_pos;
+ cmd = desc->cmd1;
+
+ if (cmd & OWN_BIT)
+ break;
+
+ if (comm->dual_nic && ((cmd & PKTSP_MASK) == PKTSP_PORT1)) {
+ struct l2sw_mac *mac2;
+
+ ndev2_pkt = 1;
+ mac2 = (mac->next_netdev) ? netdev_priv(mac->next_netdev) : NULL;
+ dev_stats = (mac2) ? &mac2->dev_stats : &mac->dev_stats;
+ } else {
+ ndev2_pkt = 0;
+ dev_stats = &mac->dev_stats;
+ }
+
+ pkg_len = cmd & LEN_MASK;
+ if (unlikely((cmd & ERR_CODE) || (pkg_len < 64))) {
+ dev_stats->rx_length_errors++;
+ dev_stats->rx_dropped++;
+ goto NEXT;
+ }
+
+ if (unlikely(cmd & RX_IP_CHKSUM_BIT)) {
+ dev_stats->rx_crc_errors++;
+ dev_stats->rx_dropped++;
+ goto NEXT;
+ }
+
+ /* allocate an skbuff for receiving, and it's an inline function */
+ new_skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
+ GFP_ATOMIC | GFP_DMA);
+ if (unlikely(!new_skb)) {
+ dev_stats->rx_dropped++;
+ goto NEXT;
+ }
+ new_skb->dev = mac->net_dev;
+
+ dma_unmap_single(&mac->pdev->dev, sinfo->mapping, comm->rx_desc_buff_size,
+ DMA_FROM_DEVICE);
+
+ skb = sinfo->skb;
+ skb->ip_summed = CHECKSUM_NONE;
+
+ /*skb_put will judge if tail exceeds end, but __skb_put won't */
+ __skb_put(skb, (pkg_len - 4 > comm->rx_desc_buff_size) ?
+ comm->rx_desc_buff_size : pkg_len - 4);
+
+ sinfo->mapping = dma_map_single(&mac->pdev->dev, new_skb->data,
+ comm->rx_desc_buff_size,
+ DMA_FROM_DEVICE);
+ sinfo->skb = new_skb;
+
+ if (ndev2_pkt) {
+ struct net_device *netdev2 = mac->next_netdev;
+
+ if (netdev2) {
+ skb->protocol = eth_type_trans(skb, netdev2);
+ rx_skb(netdev_priv(netdev2), skb);
+ }
+ } else {
+ skb->protocol = eth_type_trans(skb, mac->net_dev);
+ rx_skb(mac, skb);
+ }
+
+ desc->addr1 = sinfo->mapping;
+
+NEXT:
+ desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
+ EOR_BIT | MAC_RX_LEN_MAX : MAC_RX_LEN_MAX;
+ wmb(); // Set OWN_BIT after other fields of descriptor are effective.
+ desc->cmd1 = OWN_BIT | (comm->rx_desc_buff_size & LEN_MASK);
+
+ NEXT_RX(queue, rx_pos);
+
+ // If there are packets in high-priority queue,
+ // stop processing low-priority queue.
+ if ((queue == 1) && ((h_desc->cmd1 & OWN_BIT) == 0))
+ break;
+ }
+
+ comm->rx_pos[queue] = rx_pos;
+
+ // Save pointer to last rx descriptor of high-priority queue.
+ if (queue == 0)
+ h_desc = comm->rx_desc[queue] + rx_pos;
+ }
+
+ spin_unlock(&comm->rx_lock);
+
+ wmb(); // make sure settings are effective.
+ write_sw_int_mask0(read_sw_int_mask0() & ~MAC_INT_RX);
+
+ napi_complete(napi);
+ return 0;
+}
+
+int tx_poll(struct napi_struct *napi, int budget)
+{
+ struct l2sw_common *comm = container_of(napi, struct l2sw_common, tx_napi);
+ struct l2sw_mac *mac = netdev_priv(comm->net_dev);
+ u32 tx_done_pos;
+ u32 cmd;
+ struct skb_info *skbinfo;
+ struct l2sw_mac *smac;
+
+ spin_lock(&comm->tx_lock);
+
+ tx_done_pos = comm->tx_done_pos;
+ while ((tx_done_pos != comm->tx_pos) || (comm->tx_desc_full == 1)) {
+ cmd = comm->tx_desc[tx_done_pos].cmd1;
+ if (cmd & OWN_BIT)
+ break;
+
+ skbinfo = &comm->tx_temp_skb_info[tx_done_pos];
+ if (unlikely(!skbinfo->skb))
+ pr_err(" skb is null!\n");
+
+ smac = mac;
+ if (mac->next_netdev && ((cmd & TO_VLAN_MASK) == TO_VLAN_GROUP1))
+ smac = netdev_priv(mac->next_netdev);
+
+ if (unlikely(cmd & (ERR_CODE))) {
+ smac->dev_stats.tx_errors++;
+ } else {
+ smac->dev_stats.tx_packets++;
+ smac->dev_stats.tx_bytes += skbinfo->len;
+ }
+
+ dma_unmap_single(&mac->pdev->dev, skbinfo->mapping, skbinfo->len, DMA_TO_DEVICE);
+ skbinfo->mapping = 0;
+ dev_kfree_skb_irq(skbinfo->skb);
+ skbinfo->skb = NULL;
+
+ NEXT_TX(tx_done_pos);
+ if (comm->tx_desc_full == 1)
+ comm->tx_desc_full = 0;
+ }
+
+ comm->tx_done_pos = tx_done_pos;
+ if (!comm->tx_desc_full) {
+ if (netif_queue_stopped(mac->net_dev))
+ netif_wake_queue(mac->net_dev);
+
+ if (mac->next_netdev) {
+ if (netif_queue_stopped(mac->next_netdev))
+ netif_wake_queue(mac->next_netdev);
+ }
+ }
+
+ spin_unlock(&comm->tx_lock);
+
+ wmb(); // make sure settings are effective.
+ write_sw_int_mask0(read_sw_int_mask0() & ~MAC_INT_TX);
+
+ napi_complete(napi);
+ return 0;
+}
+
+irqreturn_t ethernet_interrupt(int irq, void *dev_id)
+{
+ struct net_device *net_dev;
+ struct l2sw_mac *mac;
+ struct l2sw_common *comm;
+ u32 status;
+
+ net_dev = (struct net_device *)dev_id;
+ if (unlikely(!net_dev)) {
+ pr_err(" net_dev is null!\n");
+ return -1;
+ }
+
+ mac = netdev_priv(net_dev);
+ comm = mac->comm;
+
+ status = read_sw_int_status0();
+ if (unlikely(status == 0)) {
+ pr_err(" Interrput status is null!\n");
+ goto OUT;
+ }
+ write_sw_int_status0(status);
+
+ if (status & MAC_INT_RX) {
+ // Disable RX interrupts.
+ write_sw_int_mask0(read_sw_int_mask0() | MAC_INT_RX);
+
+ if (unlikely(status & MAC_INT_RX_DES_ERR)) {
+ pr_err(" Illegal RX Descriptor!\n");
+ mac->dev_stats.rx_fifo_errors++;
+ }
+ if (napi_schedule_prep(&comm->rx_napi))
+ __napi_schedule(&comm->rx_napi);
+ }
+
+ if (status & MAC_INT_TX) {
+ // Disable TX interrupts.
+ write_sw_int_mask0(read_sw_int_mask0() | MAC_INT_TX);
+
+ if (unlikely(status & MAC_INT_TX_DES_ERR)) {
+ pr_err(" Illegal TX Descriptor Error\n");
+ mac->dev_stats.tx_fifo_errors++;
+ mac_soft_reset(mac);
+ wmb(); // make sure settings are effective.
+ write_sw_int_mask0(read_sw_int_mask0() & ~MAC_INT_TX);
+ } else {
+ if (napi_schedule_prep(&comm->tx_napi))
+ __napi_schedule(&comm->tx_napi);
+ }
+ }
+
+ if (status & MAC_INT_PORT_ST_CHG) /* link status changed */
+ port_status_change(mac);
+
+OUT:
+ return IRQ_HANDLED;
+}
+
+int l2sw_get_irq(struct platform_device *pdev, struct l2sw_common *comm)
+{
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ pr_err(" No IRQ resource found!\n");
+ return -1;
+ }
+
+ pr_debug(" res->name = \"%s\", res->start = %pa\n", res->name, &res->start);
+ comm->irq = res->start;
+ return 0;
+}
+
+int l2sw_request_irq(struct platform_device *pdev, struct l2sw_common *comm,
+ struct net_device *net_dev)
+{
+ int rc;
+
+ rc = devm_request_irq(&pdev->dev, comm->irq, ethernet_interrupt, 0,
+ net_dev->name, net_dev);
+ if (rc != 0) {
+ pr_err(" Failed to request irq #%d for \"%s\" (rc = %d)!\n",
+ net_dev->irq, net_dev->name, rc);
+ }
+ return rc;
+}
diff --git a/drivers/net/ethernet/sunplus/l2sw_int.h b/drivers/net/ethernet/sunplus/l2sw_int.h
new file mode 100644
index 0000000..b459481
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_int.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_INT_H__
+#define __L2SW_INT_H__
+
+int rx_poll(struct napi_struct *napi, int budget);
+int tx_poll(struct napi_struct *napi, int budget);
+irqreturn_t ethernet_interrupt(int irq, void *dev_id);
+int l2sw_get_irq(struct platform_device *pdev, struct l2sw_common *comm);
+int l2sw_request_irq(struct platform_device *pdev, struct l2sw_common *comm,
+ struct net_device *net_dev);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/l2sw_mac.c b/drivers/net/ethernet/sunplus/l2sw_mac.c
new file mode 100644
index 0000000..4b5b1d7
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_mac.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "l2sw_mac.h"
+
+bool mac_init(struct l2sw_mac *mac)
+{
+ u32 i;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ mac->comm->rx_pos[i] = 0;
+ mb(); // make sure settings are effective.
+
+ mac_hw_init(mac);
+ mb(); // make sure settings are effective.
+
+ return 1;
+}
+
+void mac_soft_reset(struct l2sw_mac *mac)
+{
+ u32 i;
+ struct net_device *net_dev2;
+
+ if (netif_carrier_ok(mac->net_dev)) {
+ netif_carrier_off(mac->net_dev);
+ netif_stop_queue(mac->net_dev);
+ }
+
+ net_dev2 = mac->next_netdev;
+ if (net_dev2) {
+ if (netif_carrier_ok(net_dev2)) {
+ netif_carrier_off(net_dev2);
+ netif_stop_queue(net_dev2);
+ }
+ }
+
+ mac_hw_reset(mac);
+ mac_hw_stop(mac);
+ mb(); // make sure settings are effective.
+
+ rx_descs_flush(mac->comm);
+ mac->comm->tx_pos = 0;
+ mac->comm->tx_done_pos = 0;
+ mac->comm->tx_desc_full = 0;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ mac->comm->rx_pos[i] = 0;
+ mb(); // make sure settings are effective.
+
+ mac_hw_init(mac);
+ mac_hw_start(mac);
+ mb(); // make sure settings are effective.
+
+ if (!netif_carrier_ok(mac->net_dev)) {
+ netif_carrier_on(mac->net_dev);
+ netif_start_queue(mac->net_dev);
+ }
+
+ if (net_dev2) {
+ if (!netif_carrier_ok(net_dev2)) {
+ netif_carrier_on(net_dev2);
+ netif_start_queue(net_dev2);
+ }
+ }
+}
diff --git a/drivers/net/ethernet/sunplus/l2sw_mac.h b/drivers/net/ethernet/sunplus/l2sw_mac.h
new file mode 100644
index 0000000..a053130
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_mac.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_MAC_H__
+#define __L2SW_MAC_H__
+
+#include "l2sw_define.h"
+#include "l2sw_hal.h"
+
+bool mac_init(struct l2sw_mac *mac);
+
+void mac_soft_reset(struct l2sw_mac *mac);
+
+//calculate the empty tx descriptor number
+#define TX_DESC_AVAIL(mac) \
+ (((mac)->tx_pos != (mac)->tx_done_pos) ? \
+ (((mac)->tx_done_pos < (mac)->tx_pos) ? \
+ (TX_DESC_NUM - ((mac)->tx_pos - (mac)->tx_done_pos)) : \
+ ((mac)->tx_done_pos - (mac)->tx_pos)) : \
+ ((mac)->tx_desc_full ? 0 : TX_DESC_NUM))
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/l2sw_mdio.c b/drivers/net/ethernet/sunplus/l2sw_mdio.c
new file mode 100644
index 0000000..9008ab9
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_mdio.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "l2sw_mdio.h"
+
+static int mii_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+ return mdio_read(phy_id, regnum);
+}
+
+static int mii_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
+{
+ return mdio_write(phy_id, regnum, val);
+}
+
+u32 mdio_init(struct platform_device *pdev, struct net_device *net_dev)
+{
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+ struct mii_bus *mii_bus;
+ struct device_node *mdio_node;
+ u32 ret;
+
+ mii_bus = mdiobus_alloc();
+ if (!mii_bus) {
+ pr_err(" Failed to allocate mdio_bus memory!\n");
+ return -ENOMEM;
+ }
+
+ mii_bus->name = "sunplus_mii_bus";
+ mii_bus->parent = &pdev->dev;
+ mii_bus->priv = mac;
+ mii_bus->read = mii_read;
+ mii_bus->write = mii_write;
+ snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
+
+ mdio_node = of_get_parent(mac->comm->phy1_node);
+ ret = of_mdiobus_register(mii_bus, mdio_node);
+ if (ret) {
+ pr_err(" Failed to register mii bus (ret = %d)!\n", ret);
+ mdiobus_free(mii_bus);
+ return ret;
+ }
+
+ mac->comm->mii_bus = mii_bus;
+ return ret;
+}
+
+void mdio_remove(struct net_device *net_dev)
+{
+ struct l2sw_mac *mac = netdev_priv(net_dev);
+
+ if (mac->comm->mii_bus) {
+ mdiobus_unregister(mac->comm->mii_bus);
+ mdiobus_free(mac->comm->mii_bus);
+ mac->comm->mii_bus = NULL;
+ }
+}
+
+static void mii_linkchange(struct net_device *netdev)
+{
+}
+
+int mac_phy_probe(struct net_device *netdev)
+{
+ struct l2sw_mac *mac = netdev_priv(netdev);
+ struct phy_device *phydev;
+ int i;
+
+ phydev = of_phy_connect(mac->net_dev, mac->comm->phy1_node, mii_linkchange,
+ 0, PHY_INTERFACE_MODE_RGMII_ID);
+ if (!phydev) {
+ pr_err(" \"%s\" has no phy found\n", netdev->name);
+ return -1;
+ }
+
+ if (mac->comm->phy2_node) {
+ of_phy_connect(mac->net_dev, mac->comm->phy2_node, mii_linkchange,
+ 0, PHY_INTERFACE_MODE_RGMII_ID);
+ }
+
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported);
+
+ for (i = 0; i < sizeof(phydev->supported) / sizeof(long); i++)
+ phydev->advertising[i] = phydev->supported[i];
+
+ phydev->irq = PHY_MAC_INTERRUPT;
+ mac->comm->phy_dev = phydev;
+
+ return 0;
+}
+
+void mac_phy_start(struct net_device *netdev)
+{
+ struct l2sw_mac *mac = netdev_priv(netdev);
+
+ phy_start(mac->comm->phy_dev);
+}
+
+void mac_phy_stop(struct net_device *netdev)
+{
+ struct l2sw_mac *mac = netdev_priv(netdev);
+
+ if (mac->comm->phy_dev)
+ phy_stop(mac->comm->phy_dev);
+}
+
+void mac_phy_remove(struct net_device *netdev)
+{
+ struct l2sw_mac *mac = netdev_priv(netdev);
+
+ if (mac->comm->phy_dev) {
+ phy_disconnect(mac->comm->phy_dev);
+ mac->comm->phy_dev = NULL;
+ }
+}
diff --git a/drivers/net/ethernet/sunplus/l2sw_mdio.h b/drivers/net/ethernet/sunplus/l2sw_mdio.h
new file mode 100644
index 0000000..698fc0b
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_mdio.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_MDIO_H__
+#define __L2SW_MDIO_H__
+
+#include "l2sw_define.h"
+#include "l2sw_hal.h"
+
+u32 mdio_init(struct platform_device *pdev, struct net_device *net_dev);
+void mdio_remove(struct net_device *net_dev);
+int mac_phy_probe(struct net_device *netdev);
+void mac_phy_start(struct net_device *netdev);
+void mac_phy_stop(struct net_device *netdev);
+void mac_phy_remove(struct net_device *netdev);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/l2sw_register.h b/drivers/net/ethernet/sunplus/l2sw_register.h
new file mode 100644
index 0000000..a54a296
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/l2sw_register.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __L2SW_REGISTER_H__
+#define __L2SW_REGISTER_H__
+
+#include "l2sw_define.h"
+
+/* TYPE: RegisterFile_L2SW */
+struct l2sw_reg {
+ u32 sw_int_status_0;
+ u32 sw_int_mask_0;
+ u32 fl_cntl_th;
+ u32 cpu_fl_cntl_th;
+ u32 pri_fl_cntl;
+ u32 vlan_pri_th;
+ u32 en_tos_bus;
+ u32 tos_map0;
+ u32 tos_map1;
+ u32 tos_map2;
+ u32 tos_map3;
+ u32 tos_map4;
+ u32 tos_map5;
+ u32 tos_map6;
+ u32 tos_map7;
+ u32 global_que_status;
+ u32 addr_tbl_srch;
+ u32 addr_tbl_st;
+ u32 mac_ad_ser0;
+ u32 mac_ad_ser1;
+ u32 wt_mac_ad0;
+ u32 w_mac_15_0;
+ u32 w_mac_47_16;
+ u32 pvid_config0;
+ u32 pvid_config1;
+ u32 vlan_memset_config0;
+ u32 vlan_memset_config1;
+ u32 port_ability;
+ u32 port_st;
+ u32 cpu_cntl;
+ u32 port_cntl0;
+ u32 port_cntl1;
+ u32 port_cntl2;
+ u32 sw_glb_cntl;
+ u32 l2sw_rsv1;
+ u32 led_port0;
+ u32 led_port1;
+ u32 led_port2;
+ u32 led_port3;
+ u32 led_port4;
+ u32 watch_dog_trig_rst;
+ u32 watch_dog_stop_cpu;
+ u32 phy_cntl_reg0;
+ u32 phy_cntl_reg1;
+ u32 mac_force_mode;
+ u32 vlan_group_config0;
+ u32 vlan_group_config1;
+ u32 flow_ctrl_th3;
+ u32 queue_status_0;
+ u32 debug_cntl;
+ u32 l2sw_rsv2;
+ u32 mem_test_info;
+ u32 sw_int_status_1;
+ u32 sw_int_mask_1;
+ u32 l2sw_rsv3[76];
+ u32 cpu_tx_trig;
+ u32 tx_hbase_addr_0;
+ u32 tx_lbase_addr_0;
+ u32 rx_hbase_addr_0;
+ u32 rx_lbase_addr_0;
+ u32 tx_hw_addr_0;
+ u32 tx_lw_addr_0;
+ u32 rx_hw_addr_0;
+ u32 rx_lw_addr_0;
+ u32 cpu_port_cntl_reg_0;
+ u32 tx_hbase_addr_1;
+ u32 tx_lbase_addr_1;
+ u32 rx_hbase_addr_1;
+ u32 rx_lbase_addr_1;
+ u32 tx_hw_addr_1;
+ u32 tx_lw_addr_1;
+ u32 rx_hw_addr_1;
+ u32 rx_lw_addr_1;
+ u32 cpu_port_cntl_reg_1;
+};
+
+/* TYPE: RegisterFile_MOON5 */
+struct moon5_reg {
+ u32 mo5_thermal_ctl_0;
+ u32 mo5_thermal_ctl_1;
+ u32 mo4_thermal_ctl_2;
+ u32 mo4_thermal_ctl_3;
+ u32 mo4_tmds_l2sw_ctl;
+ u32 mo4_l2sw_clksw_ctl;
+};
+
+#endif
--
2.7.4


2021-11-03 11:27:23

by Denis Kirjanov

[permalink] [raw]
Subject: Re: [PATCH 0/2] This is a patch series of ethernet driver for Sunplus SP7021 SoC.



11/3/21 2:02 PM, Wells Lu пишет:
> Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
> many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
> etc.) into a single chip. It is designed for industrial control
> applications.

The series should pe prefixes with net-next

>
> Refer to:
> https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> https://tibbo.com/store/plus1.html
>
> Wells Lu (2):
> devicetree: bindings: net: Add bindings doc for Sunplus SP7021.
> net: ethernet: Add driver for Sunplus SP7021
>
> .../bindings/net/sunplus,sp7021-l2sw.yaml | 123 ++++
> MAINTAINERS | 8 +
> drivers/net/ethernet/Kconfig | 1 +
> drivers/net/ethernet/Makefile | 1 +
> drivers/net/ethernet/sunplus/Kconfig | 20 +
> drivers/net/ethernet/sunplus/Makefile | 6 +
> drivers/net/ethernet/sunplus/l2sw_define.h | 221 ++++++
> drivers/net/ethernet/sunplus/l2sw_desc.c | 233 ++++++
> drivers/net/ethernet/sunplus/l2sw_desc.h | 21 +
> drivers/net/ethernet/sunplus/l2sw_driver.c | 779 +++++++++++++++++++++
> drivers/net/ethernet/sunplus/l2sw_driver.h | 23 +
> drivers/net/ethernet/sunplus/l2sw_hal.c | 422 +++++++++++
> drivers/net/ethernet/sunplus/l2sw_hal.h | 47 ++
> drivers/net/ethernet/sunplus/l2sw_int.c | 326 +++++++++
> drivers/net/ethernet/sunplus/l2sw_int.h | 16 +
> drivers/net/ethernet/sunplus/l2sw_mac.c | 68 ++
> drivers/net/ethernet/sunplus/l2sw_mac.h | 24 +
> drivers/net/ethernet/sunplus/l2sw_mdio.c | 118 ++++
> drivers/net/ethernet/sunplus/l2sw_mdio.h | 19 +
> drivers/net/ethernet/sunplus/l2sw_register.h | 99 +++
> 20 files changed, 2575 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml
> create mode 100644 drivers/net/ethernet/sunplus/Kconfig
> create mode 100644 drivers/net/ethernet/sunplus/Makefile
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_define.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_register.h
>

2021-11-03 12:05:23

by Denis Kirjanov

[permalink] [raw]
Subject: Re: [PATCH 2/2] net: ethernet: Add driver for Sunplus SP7021



11/3/21 2:02 PM, Wells Lu пишет:
> Add Ethernet driver for Sunplus SP7021.
>
> Signed-off-by: Wells Lu <[email protected]>
> ---
> MAINTAINERS | 1 +
> drivers/net/ethernet/Kconfig | 1 +
> drivers/net/ethernet/Makefile | 1 +
> drivers/net/ethernet/sunplus/Kconfig | 20 +
> drivers/net/ethernet/sunplus/Makefile | 6 +
> drivers/net/ethernet/sunplus/l2sw_define.h | 221 ++++++++
> drivers/net/ethernet/sunplus/l2sw_desc.c | 233 ++++++++
> drivers/net/ethernet/sunplus/l2sw_desc.h | 21 +
> drivers/net/ethernet/sunplus/l2sw_driver.c | 779 +++++++++++++++++++++++++++
> drivers/net/ethernet/sunplus/l2sw_driver.h | 23 +
> drivers/net/ethernet/sunplus/l2sw_hal.c | 422 +++++++++++++++
> drivers/net/ethernet/sunplus/l2sw_hal.h | 47 ++
> drivers/net/ethernet/sunplus/l2sw_int.c | 326 +++++++++++
> drivers/net/ethernet/sunplus/l2sw_int.h | 16 +
> drivers/net/ethernet/sunplus/l2sw_mac.c | 68 +++
> drivers/net/ethernet/sunplus/l2sw_mac.h | 24 +
> drivers/net/ethernet/sunplus/l2sw_mdio.c | 118 ++++
> drivers/net/ethernet/sunplus/l2sw_mdio.h | 19 +
> drivers/net/ethernet/sunplus/l2sw_register.h | 99 ++++
> 19 files changed, 2445 insertions(+)
> create mode 100644 drivers/net/ethernet/sunplus/Kconfig
> create mode 100644 drivers/net/ethernet/sunplus/Makefile
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_define.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_desc.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_driver.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_hal.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_int.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mac.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.c
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_mdio.h
> create mode 100644 drivers/net/ethernet/sunplus/l2sw_register.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4669c16..ca676ec 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18006,6 +18006,7 @@ L: [email protected]
> S: Maintained
> W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> F: Documentation/devicetree/bindings/net/sunplus,sp7021-l2sw.yaml
> +F: drivers/net/ethernet/sunplus/
>
> SUPERH
> M: Yoshinori Sato <[email protected]>
> diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
> index 412ae3e..0084852 100644
> --- a/drivers/net/ethernet/Kconfig
> +++ b/drivers/net/ethernet/Kconfig
> @@ -176,6 +176,7 @@ source "drivers/net/ethernet/smsc/Kconfig"
> source "drivers/net/ethernet/socionext/Kconfig"
> source "drivers/net/ethernet/stmicro/Kconfig"
> source "drivers/net/ethernet/sun/Kconfig"
> +source "drivers/net/ethernet/sunplus/Kconfig"
> source "drivers/net/ethernet/synopsys/Kconfig"
> source "drivers/net/ethernet/tehuti/Kconfig"
> source "drivers/net/ethernet/ti/Kconfig"
> diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
> index aaa5078..e4ce162 100644
> --- a/drivers/net/ethernet/Makefile
> +++ b/drivers/net/ethernet/Makefile
> @@ -87,6 +87,7 @@ obj-$(CONFIG_NET_VENDOR_SMSC) += smsc/
> obj-$(CONFIG_NET_VENDOR_SOCIONEXT) += socionext/
> obj-$(CONFIG_NET_VENDOR_STMICRO) += stmicro/
> obj-$(CONFIG_NET_VENDOR_SUN) += sun/
> +obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sunplus/
> obj-$(CONFIG_NET_VENDOR_TEHUTI) += tehuti/
> obj-$(CONFIG_NET_VENDOR_TI) += ti/
> obj-$(CONFIG_NET_VENDOR_TOSHIBA) += toshiba/
> diff --git a/drivers/net/ethernet/sunplus/Kconfig b/drivers/net/ethernet/sunplus/Kconfig
> new file mode 100644
> index 0000000..a9e3a4c
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/Kconfig
> @@ -0,0 +1,20 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Sunplus Ethernet device configuration
> +#
> +
> +config NET_VENDOR_SUNPLUS
> + tristate "Sunplus Dual 10M/100M Ethernet (with L2 switch) devices"
> + depends on ETHERNET && SOC_SP7021
> + select PHYLIB
> + select PINCTRL_SPPCTL
> + select COMMON_CLK_SP7021
> + select RESET_SUNPLUS
> + select NVMEM_SUNPLUS_OCOTP
> + help
> + If you have Sunplus dual 10M/100M Ethernet (with L2 switch)
> + devices, say Y.
> + The network device supports dual 10M/100M Ethernet interfaces,
> + or one 10/100M Ethernet interface with two LAN ports.
> + To compile this driver as a module, choose M here. The module
> + will be called sp_l2sw.
> diff --git a/drivers/net/ethernet/sunplus/Makefile b/drivers/net/ethernet/sunplus/Makefile
> new file mode 100644
> index 0000000..b401cec
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for the Sunplus network device drivers.
> +#
> +obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sp_l2sw.o
> +sp_l2sw-objs := l2sw_driver.o l2sw_int.o l2sw_hal.o l2sw_desc.o l2sw_mac.o l2sw_mdio.o
> diff --git a/drivers/net/ethernet/sunplus/l2sw_define.h b/drivers/net/ethernet/sunplus/l2sw_define.h
> new file mode 100644
> index 0000000..c1049c5
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/l2sw_define.h
> @@ -0,0 +1,221 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __L2SW_DEFINE_H__
> +#define __L2SW_DEFINE_H__
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/sched.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/errno.h>
> +#include <linux/types.h>
> +#include <linux/interrupt.h>
> +#include <linux/kdev_t.h>
> +#include <linux/in.h>
> +#include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
> +#include <linux/ip.h>
> +#include <linux/tcp.h>
> +#include <linux/skbuff.h>
> +#include <linux/ethtool.h>
> +#include <linux/platform_device.h>
> +#include <linux/phy.h>
> +#include <linux/mii.h>
> +#include <linux/if_vlan.h>
> +#include <linux/io.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/of_address.h>
> +#include <linux/of_mdio.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "[L2SW]" fmt
> +
> +//define MAC interrupt status bit
> +#define MAC_INT_DAISY_MODE_CHG BIT(31)
> +#define MAC_INT_IP_CHKSUM_ERR BIT(23)
> +#define MAC_INT_WDOG_TIMER1_EXP BIT(22)
> +#define MAC_INT_WDOG_TIMER0_EXP BIT(21)
> +#define MAC_INT_INTRUDER_ALERT BIT(20)
> +#define MAC_INT_PORT_ST_CHG BIT(19)
> +#define MAC_INT_BC_STORM BIT(18)
> +#define MAC_INT_MUST_DROP_LAN BIT(17)
> +#define MAC_INT_GLOBAL_QUE_FULL BIT(16)
> +#define MAC_INT_TX_SOC_PAUSE_ON BIT(15)
> +#define MAC_INT_RX_SOC_QUE_FULL BIT(14)
> +#define MAC_INT_TX_LAN1_QUE_FULL BIT(9)
> +#define MAC_INT_TX_LAN0_QUE_FULL BIT(8)
> +#define MAC_INT_RX_L_DESCF BIT(7)
> +#define MAC_INT_RX_H_DESCF BIT(6)
> +#define MAC_INT_RX_DONE_L BIT(5)
> +#define MAC_INT_RX_DONE_H BIT(4)
> +#define MAC_INT_TX_DONE_L BIT(3)
> +#define MAC_INT_TX_DONE_H BIT(2)
> +#define MAC_INT_TX_DES_ERR BIT(1)
> +#define MAC_INT_RX_DES_ERR BIT(0)
> +
> +#define MAC_INT_RX (MAC_INT_RX_DONE_H | MAC_INT_RX_DONE_L | \
> + MAC_INT_RX_DES_ERR)
> +#define MAC_INT_TX (MAC_INT_TX_DONE_L | MAC_INT_TX_DONE_H | \
> + MAC_INT_TX_DES_ERR)
> +#define MAC_INT_MASK_DEF (MAC_INT_DAISY_MODE_CHG | MAC_INT_IP_CHKSUM_ERR | \
> + MAC_INT_WDOG_TIMER1_EXP | MAC_INT_WDOG_TIMER0_EXP | \
> + MAC_INT_INTRUDER_ALERT | MAC_INT_BC_STORM | \
> + MAC_INT_MUST_DROP_LAN | MAC_INT_GLOBAL_QUE_FULL | \
> + MAC_INT_TX_SOC_PAUSE_ON | MAC_INT_RX_SOC_QUE_FULL | \
> + MAC_INT_TX_LAN1_QUE_FULL | MAC_INT_TX_LAN0_QUE_FULL | \
> + MAC_INT_RX_L_DESCF | MAC_INT_RX_H_DESCF)
> +
> +/*define port ability*/
> +#define PORT_ABILITY_LINK_ST_P1 BIT(25)
> +#define PORT_ABILITY_LINK_ST_P0 BIT(24)
> +
> +/*define PHY command bit*/
> +#define PHY_WT_DATA_MASK 0xffff0000
> +#define PHY_RD_CMD 0x00004000
> +#define PHY_WT_CMD 0x00002000
> +#define PHY_REG_MASK 0x00001f00
> +#define PHY_ADR_MASK 0x0000001f
> +
> +/*define PHY status bit*/
> +#define PHY_RD_DATA_MASK 0xffff0000
> +#define PHY_RD_RDY BIT(1)
> +#define PHY_WT_DONE BIT(0)
> +
> +/*define other register bit*/
> +#define RX_MAX_LEN_MASK 0x00011000
> +#define ROUTE_MODE_MASK 0x00000060
> +#define POK_INT_THS_MASK 0x000E0000
> +#define VLAN_TH_MASK 0x00000007
> +
> +/*define tx descriptor bit*/
> +#define OWN_BIT BIT(31)
> +#define FS_BIT BIT(25)
> +#define LS_BIT BIT(24)
> +#define LEN_MASK 0x000007FF
> +#define PKTSP_MASK 0x00007000
> +#define PKTSP_PORT1 0x00001000
> +#define TO_VLAN_MASK 0x0003F000
> +#define TO_VLAN_GROUP1 0x00002000
> +
> +#define EOR_BIT BIT(31)
> +
> +/*define rx descriptor bit*/
> +#define ERR_CODE (0xf << 26)
> +#define RX_TCP_UDP_CHKSUM_BIT BIT(23)
> +#define RX_IP_CHKSUM_BIT BIT(18)
> +
> +#define OWC_BIT BIT(31)
> +#define TXOK_BIT BIT(26)
> +#define LNKF_BIT BIT(25)
> +#define BUR_BIT BIT(22)
> +#define TWDE_BIT BIT(20)
> +#define CC_MASK 0x000f0000
> +#define TBE_MASK 0x00070000
> +
> +// Address table search
> +#define MAC_ADDR_LOOKUP_IDLE BIT(2)
> +#define MAC_SEARCH_NEXT_ADDR BIT(1)
> +#define MAC_BEGIN_SEARCH_ADDR BIT(0)
> +
> +// Address table search
> +#define MAC_HASK_LOOKUP_ADDR_MASK (0x3ff << 22)
> +#define MAC_AT_TABLE_END BIT(1)
> +#define MAC_AT_DATA_READY BIT(0)
> +
> +#define MAC_PHY_ADDR 0x01 /* define by hardware */
> +
> +/*config descriptor*/
> +#define TX_DESC_NUM 16
> +#define MAC_GUARD_DESC_NUM 2
> +#define RX_QUEUE0_DESC_NUM 16
> +#define RX_QUEUE1_DESC_NUM 16
> +#define TX_DESC_QUEUE_NUM 1
> +#define RX_DESC_QUEUE_NUM 2
> +
> +#define MAC_TX_BUFF_SIZE 1536
> +#define MAC_RX_LEN_MAX 2047
> +
> +#define DESC_ALIGN_BYTE 32
> +#define RX_OFFSET 0
> +#define TX_OFFSET 0
> +
> +#define ETHERNET_MAC_ADDR_LEN 6
> +
> +struct mac_desc {
> + u32 cmd1;
> + u32 cmd2;
> + u32 addr1;
> + u32 addr2;
> +};
> +
> +struct skb_info {
> + struct sk_buff *skb;
> + u32 mapping;
> + u32 len;
> +};
> +
> +struct l2sw_common {
> + struct net_device *net_dev;
> + struct platform_device *pdev;
> + int dual_nic;
> + int sa_learning;
> +
> + void *desc_base;
> + dma_addr_t desc_dma;
> + s32 desc_size;
> + struct clk *clk;
> + struct reset_control *rstc;
> + int irq;
> +
> + struct mac_desc *rx_desc[RX_DESC_QUEUE_NUM];
> + struct skb_info *rx_skb_info[RX_DESC_QUEUE_NUM];
> + u32 rx_pos[RX_DESC_QUEUE_NUM];
> + u32 rx_desc_num[RX_DESC_QUEUE_NUM];
> + u32 rx_desc_buff_size;
> +
> + struct mac_desc *tx_desc;
> + struct skb_info tx_temp_skb_info[TX_DESC_NUM];
> + u32 tx_done_pos;
> + u32 tx_pos;
> + u32 tx_desc_full;
> +
> + struct mii_bus *mii_bus;
> + struct phy_device *phy_dev;
> +
> + struct napi_struct rx_napi;
> + struct napi_struct tx_napi;
> +
> + spinlock_t rx_lock; // spinlock for accessing rx buffer
> + spinlock_t tx_lock; // spinlock for accessing tx buffer
> + spinlock_t ioctl_lock; // spinlock for ioctl operations
> + struct mutex store_mode; // mutex for dynamic mode change

run checkpatch.pl and fix the errors


2021-11-03 16:51:40

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 2/2] net: ethernet: Add driver for Sunplus SP7021

> +config NET_VENDOR_SUNPLUS
> + tristate "Sunplus Dual 10M/100M Ethernet (with L2 switch) devices"

The "with L2 Switch" is causing lots of warning bells to ring for me.

I don't see any references to switchdev or DSA in this driver. How is
the switch managed? There have been a few examples in the past of
similar two port switches being first supported in Dual MAC
mode. Later trying to actually use the switch in the Linux was always
ran into problems, and basically needed a new driver. So i want to
make sure you don't have this problem.

In the Linux world, Ethernet switches default to having there
ports/interfaces separated. This effectively gives you your dual MAC
mode by default. You then create a Linux bridge, and add the
ports/interfaces to the bridge. switchdev is used to offload the
bridge, telling the hardware to enable the L2 switch between the
ports.

So you don't need the mode parameter in DT. switchdev tells you
this. Switchdev gives user space access to the address table etc.

> +obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sp_l2sw.o
...
> +struct l2sw_common {

Please change your prefix. l2sw is a common prefix, there are other
silicon vendors using l2sw. I would suggest sp_l2sw or spl2sw.

> +static int ethernet_do_ioctl(struct net_device *net_dev, struct ifreq *ifr, int cmd)
> +{
> + struct l2sw_mac *mac = netdev_priv(net_dev);
> + struct l2sw_common *comm = mac->comm;
> + struct mii_ioctl_data *data = if_mii(ifr);
> + unsigned long flags;
> +
> + pr_debug(" if = %s, cmd = %04x\n", ifr->ifr_ifrn.ifrn_name, cmd);
> + pr_debug(" phy_id = %d, reg_num = %d, val_in = %04x\n", data->phy_id,
> + data->reg_num, data->val_in);

You should not be using any of the pr_ functions. You have a net_dev,
so netdev_dbg().

> +
> + // Check parameters' range.
> + if ((cmd == SIOCGMIIREG) || (cmd == SIOCSMIIREG)) {
> + if (data->reg_num > 31) {
> + pr_err(" reg_num (= %d) excesses range!\n", (int)data->reg_num);

Don't spam the kernel log for things like this.

> + return -EINVAL;
> + }
> + }
> +
> + switch (cmd) {
> + case SIOCGMIIPHY:
> + if (comm->dual_nic && (strcmp(ifr->ifr_ifrn.ifrn_name, "eth1") == 0))

You cannot rely on the name, systemd has probably renamed it. If you
have using phylib correctly, net_dev->phydev is what you want.


> + return comm->phy2_addr;
> + else
> + return comm->phy1_addr;
> +
> + case SIOCGMIIREG:
> + spin_lock_irqsave(&comm->ioctl_lock, flags);
> + data->val_out = mdio_read(data->phy_id, data->reg_num);
> + spin_unlock_irqrestore(&comm->ioctl_lock, flags);
> + pr_debug(" val_out = %04x\n", data->val_out);
> + break;
> +
> + case SIOCSMIIREG:
> + spin_lock_irqsave(&comm->ioctl_lock, flags);
> + mdio_write(data->phy_id, data->reg_num, data->val_in);
> + spin_unlock_irqrestore(&comm->ioctl_lock, flags);
> + break;
> +

You should be using phylink_mii_ioctl() or phy_mii_ioctl().

You locking is also suspect.

> +static int ethernet_change_mtu(struct net_device *ndev, int new_mtu)
> +{
> + if (netif_running(ndev))
> + return -EBUSY;
> +
> + if (new_mtu < 68 || new_mtu > ETH_DATA_LEN)
> + return -EINVAL;

The core will do this for you, if you set the values in the ndev
correct at probe time.

> +
> + ndev->mtu = new_mtu;
> +
> + return 0;
> +}
> +


> +static int mdio_access(u8 op_cd, u8 dev_reg_addr, u8 phy_addr, u32 wdata)
> +{
> + u32 value, time = 0;
> +
> + HWREG_W(phy_cntl_reg0, (wdata << 16) | (op_cd << 13) | (dev_reg_addr << 8) | phy_addr);
> + wmb(); // make sure settings are effective.

That suggests you are using the wrong macros to access the registers.

> + do {
> + if (++time > MDIO_RW_TIMEOUT_RETRY_NUMBERS) {
> + pr_err(" mdio failed to operate!\n");
> + time = 0;
> + }
> +
> + value = HWREG_R(phy_cntl_reg1);
> + } while ((value & 0x3) == 0);

include/linux/iopoll.h.


> + if (time == 0)
> + return -1;

-ETIMDEOUT. One of the advantages of iopoll.h is that reusing code
avoids issues like this.

> +u32 mdio_read(u32 phy_id, u16 regnum)
> +{
> + int ret;

Please check for C45 and return -EOPNOTSUPP.

> +
> + ret = mdio_access(MDIO_READ_CMD, regnum, phy_id, 0);
> + if (ret < 0)
> + return -EIO;
> +
> + return ret;
> +}
> +
> +u32 mdio_write(u32 phy_id, u32 regnum, u16 val)
> +{
> + int ret;
> +

Please check for C45 and return -EOPNOTSUPP.

> + ret = mdio_access(MDIO_WRITE_CMD, regnum, phy_id, val);
> + if (ret < 0)
> + return -EIO;
> +
> + return 0;
> +}
> +
> +inline void tx_trigger(void)

No inline functions in C code. Let the compiler decide.

> +int phy_cfg(struct l2sw_mac *mac)
> +{
> + // Bug workaround:
> + // Flow-control of phy should be enabled. L2SW IP flow-control will refer
> + // to the bit to decide to enable or disable flow-control.
> + mdio_write(mac->comm->phy1_addr, 4, mdio_read(mac->comm->phy1_addr, 4) | (1 << 10));
> + mdio_write(mac->comm->phy2_addr, 4, mdio_read(mac->comm->phy2_addr, 4) | (1 << 10));

This should be in the PHY driver. The MAC driver should never need to
touch PHY registers.

> +++ b/drivers/net/ethernet/sunplus/l2sw_mdio.c
> @@ -0,0 +1,118 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "l2sw_mdio.h"
> +
> +static int mii_read(struct mii_bus *bus, int phy_id, int regnum)
> +{
> + return mdio_read(phy_id, regnum);
> +}
> +
> +static int mii_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
> +{
> + return mdio_write(phy_id, regnum, val);
> +}
> +
> +u32 mdio_init(struct platform_device *pdev, struct net_device *net_dev)
> +{
> + struct l2sw_mac *mac = netdev_priv(net_dev);
> + struct mii_bus *mii_bus;
> + struct device_node *mdio_node;
> + u32 ret;
> +
> + mii_bus = mdiobus_alloc();
> + if (!mii_bus) {
> + pr_err(" Failed to allocate mdio_bus memory!\n");
> + return -ENOMEM;
> + }
> +
> + mii_bus->name = "sunplus_mii_bus";
> + mii_bus->parent = &pdev->dev;
> + mii_bus->priv = mac;
> + mii_bus->read = mii_read;
> + mii_bus->write = mii_write;
> + snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
> +
> + mdio_node = of_get_parent(mac->comm->phy1_node);
> + ret = of_mdiobus_register(mii_bus, mdio_node);
> + if (ret) {
> + pr_err(" Failed to register mii bus (ret = %d)!\n", ret);
> + mdiobus_free(mii_bus);
> + return ret;
> + }
> +
> + mac->comm->mii_bus = mii_bus;
> + return ret;
> +}
> +
> +void mdio_remove(struct net_device *net_dev)
> +{
> + struct l2sw_mac *mac = netdev_priv(net_dev);
> +
> + if (mac->comm->mii_bus) {
> + mdiobus_unregister(mac->comm->mii_bus);
> + mdiobus_free(mac->comm->mii_bus);
> + mac->comm->mii_bus = NULL;
> + }
> +}

You MDIO code is pretty scattered around. Please bring it all together
in one file.

> +static void mii_linkchange(struct net_device *netdev)
> +{
> +}

Nothing to do? Seems very odd. Don't you need to tell the MAC it
should do 10Mbps or 100Mbps? What about pause?

> +
> +int mac_phy_probe(struct net_device *netdev)
> +{
> + struct l2sw_mac *mac = netdev_priv(netdev);
> + struct phy_device *phydev;
> + int i;
> +
> + phydev = of_phy_connect(mac->net_dev, mac->comm->phy1_node, mii_linkchange,
> + 0, PHY_INTERFACE_MODE_RGMII_ID);

You should not hard code PHY_INTERFACE_MODE_RGMII_ID. Use the DT
property "phy-mode"

> + if (!phydev) {
> + pr_err(" \"%s\" has no phy found\n", netdev->name);
> + return -1;

-ENODEV;

Never use -1, pick an error code.

> + }
> +
> + if (mac->comm->phy2_node) {
> + of_phy_connect(mac->net_dev, mac->comm->phy2_node, mii_linkchange,
> + 0, PHY_INTERFACE_MODE_RGMII_ID);
> + }
> +
> + linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
> + linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported);

So the MAC does not support pause? I'm then confused about phy_cfg().

Andrew

2021-11-11 09:05:07

by Wells Lu

[permalink] [raw]
Subject: [PATCH v2 0/2] This is a patch series for pinctrl driver for Sunplus SP7021 SoC.

Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
etc.) into a single chip. It is designed for industrial control
applications.

Refer to:
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
https://tibbo.com/store/plus1.html

Wells Lu (2):
devicetree: bindings: net: Add bindings doc for Sunplus SP7021.
net: ethernet: Add driver for Sunplus SP7021

.../bindings/net/sunplus,sp7021-emac.yaml | 152 ++++++
MAINTAINERS | 8 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/sunplus/Kconfig | 36 ++
drivers/net/ethernet/sunplus/Makefile | 6 +
drivers/net/ethernet/sunplus/sp_define.h | 212 +++++++
drivers/net/ethernet/sunplus/sp_desc.c | 231 ++++++++
drivers/net/ethernet/sunplus/sp_desc.h | 21 +
drivers/net/ethernet/sunplus/sp_driver.c | 606 +++++++++++++++++++++
drivers/net/ethernet/sunplus/sp_driver.h | 23 +
drivers/net/ethernet/sunplus/sp_hal.c | 331 +++++++++++
drivers/net/ethernet/sunplus/sp_hal.h | 31 ++
drivers/net/ethernet/sunplus/sp_int.c | 286 ++++++++++
drivers/net/ethernet/sunplus/sp_int.h | 13 +
drivers/net/ethernet/sunplus/sp_mac.c | 63 +++
drivers/net/ethernet/sunplus/sp_mac.h | 23 +
drivers/net/ethernet/sunplus/sp_mdio.c | 90 +++
drivers/net/ethernet/sunplus/sp_mdio.h | 20 +
drivers/net/ethernet/sunplus/sp_phy.c | 64 +++
drivers/net/ethernet/sunplus/sp_phy.h | 16 +
drivers/net/ethernet/sunplus/sp_register.h | 96 ++++
22 files changed, 2330 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
create mode 100644 drivers/net/ethernet/sunplus/Kconfig
create mode 100644 drivers/net/ethernet/sunplus/Makefile
create mode 100644 drivers/net/ethernet/sunplus/sp_define.h
create mode 100644 drivers/net/ethernet/sunplus/sp_desc.c
create mode 100644 drivers/net/ethernet/sunplus/sp_desc.h
create mode 100644 drivers/net/ethernet/sunplus/sp_driver.c
create mode 100644 drivers/net/ethernet/sunplus/sp_driver.h
create mode 100644 drivers/net/ethernet/sunplus/sp_hal.c
create mode 100644 drivers/net/ethernet/sunplus/sp_hal.h
create mode 100644 drivers/net/ethernet/sunplus/sp_int.c
create mode 100644 drivers/net/ethernet/sunplus/sp_int.h
create mode 100644 drivers/net/ethernet/sunplus/sp_mac.c
create mode 100644 drivers/net/ethernet/sunplus/sp_mac.h
create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.c
create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.h
create mode 100644 drivers/net/ethernet/sunplus/sp_phy.c
create mode 100644 drivers/net/ethernet/sunplus/sp_phy.h
create mode 100644 drivers/net/ethernet/sunplus/sp_register.h

--
2.7.4


2021-11-11 09:05:12

by Wells Lu

[permalink] [raw]
Subject: [PATCH v2 1/2] devicetree: bindings: net: Add bindings doc for Sunplus SP7021.

Add bindings documentation for Sunplus SP7021.

Signed-off-by: Wells Lu <[email protected]>
---
Changes in V2
- Added mdio and phy sub-nodes.

.../bindings/net/sunplus,sp7021-emac.yaml | 152 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 159 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml

diff --git a/Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml b/Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
new file mode 100644
index 0000000..9bb0b4a
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
@@ -0,0 +1,152 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/sunplus,sp7021-emac.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 Dual Ethernet MAC Device Tree Bindings
+
+maintainers:
+ - Wells Lu <[email protected]>
+
+description: |
+ Sunplus SP7021 dual 10M/100M Ethernet MAC controller.
+ Device node of the controller has following properties.
+
+properties:
+ compatible:
+ const: sunplus,sp7021-emac
+
+ reg:
+ items:
+ - description: Base address and length of the EMAC registers.
+ - description: Base address and length of the MOON5 registers.
+
+ reg-names:
+ items:
+ - const: emac
+ - const: moon5
+
+ interrupts:
+ description: |
+ Contains number and type of interrupt. Number should be 66.
+ Type should be high-level trigger
+ maxItems: 1
+
+ clocks:
+ description: |
+ Clock controller selector for Ethernet MAC controller.
+ maxItems: 1
+
+ resets:
+ description: |
+ Reset controller selector for Ethernet MAC controller.
+ maxItems: 1
+
+ phy-handle1:
+ description: A handle to node of phy 1 in mdio node
+ maxItems: 1
+
+ phy-handle2:
+ description: A handle to node of phy 2 in mdio node
+ maxItems: 1
+
+ pinctrl-names:
+ description: |
+ Names corresponding to the numbered pinctrl states.
+ A pinctrl state named "default" must be defined.
+ const: default
+
+ pinctrl-0:
+ description: A handle to the 'default' state of pin configuration
+
+ nvmem-cells:
+ items:
+ - description: nvmem cell address of MAC address of MAC 1
+ - description: nvmem cell address of MAC address of MAC 2
+
+ nvmem-cell-names:
+ description: names corresponding to the nvmem cells of MAC address
+ items:
+ - const: mac_addr0
+ - const: mac_addr1
+
+ mdio:
+ type: object
+ description: Internal MDIO Bus
+
+ properties:
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ patternProperties:
+ "^ethernet-phy@[0-9]$":
+ type: object
+
+ description:
+ Integrated PHY node
+
+ properties:
+ reg:
+ maxItems: 1
+
+ phy-mode:
+ maxItems: 1
+
+ required:
+ - reg
+ - phy-mode
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - clocks
+ - resets
+ - phy-handle1
+ - phy-handle2
+ - pinctrl-0
+ - pinctrl-names
+ - nvmem-cells
+ - nvmem-cell-names
+ - mdio
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ emac: emac@9c108000 {
+ compatible = "sunplus,sp7021-emac";
+ reg = <0x9c108000 0x400>, <0x9c000280 0x80>;
+ reg-names = "emac", "moon5";
+ interrupt-parent = <&intc>;
+ interrupts = <66 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc 0xa7>;
+ resets = <&rstc 0x97>;
+ phy-handle1 = <&eth_phy0>;
+ phy-handle2 = <&eth_phy1>;
+ pinctrl-0 = <&emac_demo_board_v3_pins>;
+ pinctrl-names = "default";
+ nvmem-cells = <&mac_addr0>, <&mac_addr1>;
+ nvmem-cell-names = "mac_addr0", "mac_addr1";
+
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ eth_phy0: ethernet-phy@0 {
+ reg = <0>;
+ phy-mode = "rmii";
+ };
+ eth_phy1: ethernet-phy@1 {
+ reg = <1>;
+ phy-mode = "rmii";
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index dcc1819..737b9d0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18000,6 +18000,13 @@ L: [email protected]
S: Maintained
F: drivers/net/ethernet/dlink/sundance.c

+SUNPLUS ETHERNET DRIVER
+M: Wells Lu <[email protected]>
+L: [email protected]
+S: Maintained
+W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F: Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
+
SUPERH
M: Yoshinori Sato <[email protected]>
M: Rich Felker <[email protected]>
--
2.7.4


2021-11-11 09:05:16

by Wells Lu

[permalink] [raw]
Subject: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Add driver for Sunplus SP7021.

Signed-off-by: Wells Lu <[email protected]>
---
Changes in V2
- Addressed all comments from Mr. Philipp Zabel.
- Revised probe function.
- Addressed all comments from Mr. Randy Dunlap.
- Revised Kconfig
- Addressed all comments from Mr. Andrew Lunn.
- Removed daisy-chain (hub) function. Only keep dual NIC mode.
- Removed dynamic mode-switching function using sysfs.
- Removed unnecessary wmb().
- Replaced prefix l2sw_ with sp_ for struct, funciton and file names.
- Modified ethernet_do_ioctl() function.
- Removed ethernet_do_change_mtu() function.
- Revised Kconfig. Added '|| COMPILE_TEST'
- Others
- Replaced HWREG_R() and HWREG_W() macro with readl() and writel().
- Created new file sp_phy.c/sp_phy.h and moved phy-related functions in.
- Revised function name in sp_hal.c/.h to add hal_ prefix.

MAINTAINERS | 1 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/sunplus/Kconfig | 36 ++
drivers/net/ethernet/sunplus/Makefile | 6 +
drivers/net/ethernet/sunplus/sp_define.h | 212 ++++++++++
drivers/net/ethernet/sunplus/sp_desc.c | 231 +++++++++++
drivers/net/ethernet/sunplus/sp_desc.h | 21 +
drivers/net/ethernet/sunplus/sp_driver.c | 606 +++++++++++++++++++++++++++++
drivers/net/ethernet/sunplus/sp_driver.h | 23 ++
drivers/net/ethernet/sunplus/sp_hal.c | 331 ++++++++++++++++
drivers/net/ethernet/sunplus/sp_hal.h | 31 ++
drivers/net/ethernet/sunplus/sp_int.c | 286 ++++++++++++++
drivers/net/ethernet/sunplus/sp_int.h | 13 +
drivers/net/ethernet/sunplus/sp_mac.c | 63 +++
drivers/net/ethernet/sunplus/sp_mac.h | 23 ++
drivers/net/ethernet/sunplus/sp_mdio.c | 90 +++++
drivers/net/ethernet/sunplus/sp_mdio.h | 20 +
drivers/net/ethernet/sunplus/sp_phy.c | 64 +++
drivers/net/ethernet/sunplus/sp_phy.h | 16 +
drivers/net/ethernet/sunplus/sp_register.h | 96 +++++
21 files changed, 2171 insertions(+)
create mode 100644 drivers/net/ethernet/sunplus/Kconfig
create mode 100644 drivers/net/ethernet/sunplus/Makefile
create mode 100644 drivers/net/ethernet/sunplus/sp_define.h
create mode 100644 drivers/net/ethernet/sunplus/sp_desc.c
create mode 100644 drivers/net/ethernet/sunplus/sp_desc.h
create mode 100644 drivers/net/ethernet/sunplus/sp_driver.c
create mode 100644 drivers/net/ethernet/sunplus/sp_driver.h
create mode 100644 drivers/net/ethernet/sunplus/sp_hal.c
create mode 100644 drivers/net/ethernet/sunplus/sp_hal.h
create mode 100644 drivers/net/ethernet/sunplus/sp_int.c
create mode 100644 drivers/net/ethernet/sunplus/sp_int.h
create mode 100644 drivers/net/ethernet/sunplus/sp_mac.c
create mode 100644 drivers/net/ethernet/sunplus/sp_mac.h
create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.c
create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.h
create mode 100644 drivers/net/ethernet/sunplus/sp_phy.c
create mode 100644 drivers/net/ethernet/sunplus/sp_phy.h
create mode 100644 drivers/net/ethernet/sunplus/sp_register.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 737b9d0..ec1ddb1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18006,6 +18006,7 @@ L: [email protected]
S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
+F: drivers/net/ethernet/sunplus/

SUPERH
M: Yoshinori Sato <[email protected]>
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 412ae3e..0084852 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -176,6 +176,7 @@ source "drivers/net/ethernet/smsc/Kconfig"
source "drivers/net/ethernet/socionext/Kconfig"
source "drivers/net/ethernet/stmicro/Kconfig"
source "drivers/net/ethernet/sun/Kconfig"
+source "drivers/net/ethernet/sunplus/Kconfig"
source "drivers/net/ethernet/synopsys/Kconfig"
source "drivers/net/ethernet/tehuti/Kconfig"
source "drivers/net/ethernet/ti/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index aaa5078..e4ce162 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -87,6 +87,7 @@ obj-$(CONFIG_NET_VENDOR_SMSC) += smsc/
obj-$(CONFIG_NET_VENDOR_SOCIONEXT) += socionext/
obj-$(CONFIG_NET_VENDOR_STMICRO) += stmicro/
obj-$(CONFIG_NET_VENDOR_SUN) += sun/
+obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sunplus/
obj-$(CONFIG_NET_VENDOR_TEHUTI) += tehuti/
obj-$(CONFIG_NET_VENDOR_TI) += ti/
obj-$(CONFIG_NET_VENDOR_TOSHIBA) += toshiba/
diff --git a/drivers/net/ethernet/sunplus/Kconfig b/drivers/net/ethernet/sunplus/Kconfig
new file mode 100644
index 0000000..5af2c5b
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/Kconfig
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Sunplus network device configuration
+#
+
+config NET_VENDOR_SUNPLUS
+ bool "Sunplus devices"
+ default y
+ depends on ARCH_SUNPLUS || COMPILE_TEST
+ help
+ If you have a network (Ethernet) card belonging to this
+ class, say Y here.
+
+ Note that the answer to this question doesn't directly
+ affect the kernel: saying N will just cause the configurator
+ to skip all the questions about Sunplus cards. If you say Y,
+ you will be asked for your specific card in the following
+ questions.
+
+if NET_VENDOR_SUNPLUS
+
+config SP7021_EMAC
+ tristate "Sunplus Dual 10M/100M Ethernet devices"
+ depends on SOC_SP7021 || COMPILE_TEST
+ select PHYLIB
+ select PINCTRL_SPPCTL
+ select COMMON_CLK_SP7021
+ select RESET_SUNPLUS
+ select NVMEM_SUNPLUS_OCOTP
+ help
+ If you have Sunplus dual 10M/100M Ethernet devices, say Y.
+ The network device creates two net-device interfaces.
+ To compile this driver as a module, choose M here. The
+ module will be called sp7021_emac.
+
+endif # NET_VENDOR_SUNPLUS
diff --git a/drivers/net/ethernet/sunplus/Makefile b/drivers/net/ethernet/sunplus/Makefile
new file mode 100644
index 0000000..963ba1d
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Sunplus network device drivers.
+#
+obj-$(CONFIG_SP7021_EMAC) += sp7021_emac.o
+sp7021_emac-objs := sp_driver.o sp_int.o sp_hal.o sp_desc.o sp_mac.o sp_mdio.o sp_phy.o
diff --git a/drivers/net/ethernet/sunplus/sp_define.h b/drivers/net/ethernet/sunplus/sp_define.h
new file mode 100644
index 0000000..40e15ba
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_define.h
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_DEFINE_H__
+#define __SP_DEFINE_H__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/in.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/ethtool.h>
+#include <linux/platform_device.h>
+#include <linux/phy.h>
+#include <linux/mii.h>
+#include <linux/if_vlan.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+
+//define MAC interrupt status bit
+#define MAC_INT_DAISY_MODE_CHG BIT(31)
+#define MAC_INT_IP_CHKSUM_ERR BIT(23)
+#define MAC_INT_WDOG_TIMER1_EXP BIT(22)
+#define MAC_INT_WDOG_TIMER0_EXP BIT(21)
+#define MAC_INT_INTRUDER_ALERT BIT(20)
+#define MAC_INT_PORT_ST_CHG BIT(19)
+#define MAC_INT_BC_STORM BIT(18)
+#define MAC_INT_MUST_DROP_LAN BIT(17)
+#define MAC_INT_GLOBAL_QUE_FULL BIT(16)
+#define MAC_INT_TX_SOC_PAUSE_ON BIT(15)
+#define MAC_INT_RX_SOC_QUE_FULL BIT(14)
+#define MAC_INT_TX_LAN1_QUE_FULL BIT(9)
+#define MAC_INT_TX_LAN0_QUE_FULL BIT(8)
+#define MAC_INT_RX_L_DESCF BIT(7)
+#define MAC_INT_RX_H_DESCF BIT(6)
+#define MAC_INT_RX_DONE_L BIT(5)
+#define MAC_INT_RX_DONE_H BIT(4)
+#define MAC_INT_TX_DONE_L BIT(3)
+#define MAC_INT_TX_DONE_H BIT(2)
+#define MAC_INT_TX_DES_ERR BIT(1)
+#define MAC_INT_RX_DES_ERR BIT(0)
+
+#define MAC_INT_RX (MAC_INT_RX_DONE_H | MAC_INT_RX_DONE_L | \
+ MAC_INT_RX_DES_ERR)
+#define MAC_INT_TX (MAC_INT_TX_DONE_L | MAC_INT_TX_DONE_H | \
+ MAC_INT_TX_DES_ERR)
+#define MAC_INT_MASK_DEF (MAC_INT_DAISY_MODE_CHG | MAC_INT_IP_CHKSUM_ERR | \
+ MAC_INT_WDOG_TIMER1_EXP | MAC_INT_WDOG_TIMER0_EXP | \
+ MAC_INT_INTRUDER_ALERT | MAC_INT_BC_STORM | \
+ MAC_INT_MUST_DROP_LAN | MAC_INT_GLOBAL_QUE_FULL | \
+ MAC_INT_TX_SOC_PAUSE_ON | MAC_INT_RX_SOC_QUE_FULL | \
+ MAC_INT_TX_LAN1_QUE_FULL | MAC_INT_TX_LAN0_QUE_FULL | \
+ MAC_INT_RX_L_DESCF | MAC_INT_RX_H_DESCF)
+
+/*define port ability*/
+#define PORT_ABILITY_LINK_ST_P1 BIT(25)
+#define PORT_ABILITY_LINK_ST_P0 BIT(24)
+
+/*define PHY command bit*/
+#define PHY_WT_DATA_MASK 0xffff0000
+#define PHY_RD_CMD 0x00004000
+#define PHY_WT_CMD 0x00002000
+#define PHY_REG_MASK 0x00001f00
+#define PHY_ADR_MASK 0x0000001f
+
+/*define PHY status bit*/
+#define PHY_RD_DATA_MASK 0xffff0000
+#define PHY_RD_RDY BIT(1)
+#define PHY_WT_DONE BIT(0)
+
+/*define other register bit*/
+#define RX_MAX_LEN_MASK 0x00011000
+#define ROUTE_MODE_MASK 0x00000060
+#define POK_INT_THS_MASK 0x000E0000
+#define VLAN_TH_MASK 0x00000007
+
+/*define tx descriptor bit*/
+#define OWN_BIT BIT(31)
+#define FS_BIT BIT(25)
+#define LS_BIT BIT(24)
+#define LEN_MASK 0x000007FF
+#define PKTSP_MASK 0x00007000
+#define PKTSP_PORT1 0x00001000
+#define TO_VLAN_MASK 0x0003F000
+#define TO_VLAN_GROUP1 0x00002000
+
+#define EOR_BIT BIT(31)
+
+/*define rx descriptor bit*/
+#define ERR_CODE (0xf << 26)
+#define RX_TCP_UDP_CHKSUM_BIT BIT(23)
+#define RX_IP_CHKSUM_BIT BIT(18)
+
+#define OWC_BIT BIT(31)
+#define TXOK_BIT BIT(26)
+#define LNKF_BIT BIT(25)
+#define BUR_BIT BIT(22)
+#define TWDE_BIT BIT(20)
+#define CC_MASK 0x000f0000
+#define TBE_MASK 0x00070000
+
+// Address table search
+#define MAC_ADDR_LOOKUP_IDLE BIT(2)
+#define MAC_SEARCH_NEXT_ADDR BIT(1)
+#define MAC_BEGIN_SEARCH_ADDR BIT(0)
+
+// Address table search
+#define MAC_HASK_LOOKUP_ADDR_MASK (0x3ff << 22)
+#define MAC_AT_TABLE_END BIT(1)
+#define MAC_AT_DATA_READY BIT(0)
+
+/*config descriptor*/
+#define TX_DESC_NUM 16
+#define MAC_GUARD_DESC_NUM 2
+#define RX_QUEUE0_DESC_NUM 16
+#define RX_QUEUE1_DESC_NUM 16
+#define TX_DESC_QUEUE_NUM 1
+#define RX_DESC_QUEUE_NUM 2
+
+#define MAC_TX_BUFF_SIZE 1536
+#define MAC_RX_LEN_MAX 2047
+
+#define DESC_ALIGN_BYTE 32
+#define RX_OFFSET 0
+#define TX_OFFSET 0
+
+#define ETHERNET_MAC_ADDR_LEN 6
+
+struct mac_desc {
+ u32 cmd1;
+ u32 cmd2;
+ u32 addr1;
+ u32 addr2;
+};
+
+struct skb_info {
+ struct sk_buff *skb;
+ u32 mapping;
+ u32 len;
+};
+
+struct sp_common {
+ void __iomem *sp_reg_base;
+ void __iomem *moon5_reg_base;
+
+ struct net_device *ndev;
+ struct platform_device *pdev;
+
+ void *desc_base;
+ dma_addr_t desc_dma;
+ s32 desc_size;
+ struct clk *clk;
+ struct reset_control *rstc;
+ int irq;
+
+ struct mac_desc *rx_desc[RX_DESC_QUEUE_NUM];
+ struct skb_info *rx_skb_info[RX_DESC_QUEUE_NUM];
+ u32 rx_pos[RX_DESC_QUEUE_NUM];
+ u32 rx_desc_num[RX_DESC_QUEUE_NUM];
+ u32 rx_desc_buff_size;
+
+ struct mac_desc *tx_desc;
+ struct skb_info tx_temp_skb_info[TX_DESC_NUM];
+ u32 tx_done_pos;
+ u32 tx_pos;
+ u32 tx_desc_full;
+
+ struct mii_bus *mii_bus;
+
+ struct napi_struct rx_napi;
+ struct napi_struct tx_napi;
+
+ spinlock_t rx_lock; // spinlock for accessing rx buffer
+ spinlock_t tx_lock; // spinlock for accessing tx buffer
+ spinlock_t ioctl_lock; // spinlock for ioctl operations
+
+ u8 enable;
+};
+
+struct sp_mac {
+ struct net_device *ndev;
+ struct net_device *next_ndev;
+ struct phy_device *phy_dev;
+ struct sp_common *comm;
+ struct net_device_stats dev_stats;
+ struct device_node *phy_node;
+ phy_interface_t phy_mode;
+ u32 phy_addr;
+
+ u8 mac_addr[ETHERNET_MAC_ADDR_LEN];
+
+ u8 lan_port;
+ u8 to_vlan;
+ u8 cpu_port;
+ u8 vlan_id;
+};
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_desc.c b/drivers/net/ethernet/sunplus/sp_desc.c
new file mode 100644
index 0000000..fed91ec
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_desc.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "sp_desc.h"
+#include "sp_define.h"
+
+void rx_descs_flush(struct sp_common *comm)
+{
+ u32 i, j;
+ struct mac_desc *rx_desc;
+ struct skb_info *rx_skbinfo;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ rx_desc = comm->rx_desc[i];
+ rx_skbinfo = comm->rx_skb_info[i];
+ for (j = 0; j < comm->rx_desc_num[i]; j++) {
+ rx_desc[j].addr1 = rx_skbinfo[j].mapping;
+ rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
+ EOR_BIT | comm->rx_desc_buff_size :
+ comm->rx_desc_buff_size;
+ wmb(); // Set OWN_BIT after other fields are ready.
+ rx_desc[j].cmd1 = OWN_BIT;
+ }
+ }
+}
+
+void tx_descs_clean(struct sp_common *comm)
+{
+ u32 i;
+ s32 buflen;
+
+ if (!comm->tx_desc)
+ return;
+
+ for (i = 0; i < TX_DESC_NUM; i++) {
+ comm->tx_desc[i].cmd1 = 0;
+ wmb(); // Clear OWN_BIT and then set other fields.
+ comm->tx_desc[i].cmd2 = 0;
+ comm->tx_desc[i].addr1 = 0;
+ comm->tx_desc[i].addr2 = 0;
+
+ if (comm->tx_temp_skb_info[i].mapping) {
+ buflen = (comm->tx_temp_skb_info[i].skb) ?
+ comm->tx_temp_skb_info[i].skb->len :
+ MAC_TX_BUFF_SIZE;
+ dma_unmap_single(&comm->pdev->dev, comm->tx_temp_skb_info[i].mapping,
+ buflen, DMA_TO_DEVICE);
+ comm->tx_temp_skb_info[i].mapping = 0;
+ }
+
+ if (comm->tx_temp_skb_info[i].skb) {
+ dev_kfree_skb(comm->tx_temp_skb_info[i].skb);
+ comm->tx_temp_skb_info[i].skb = NULL;
+ }
+ }
+}
+
+void rx_descs_clean(struct sp_common *comm)
+{
+ u32 i, j;
+ struct mac_desc *rx_desc;
+ struct skb_info *rx_skbinfo;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ if (!comm->rx_skb_info[i])
+ continue;
+
+ rx_desc = comm->rx_desc[i];
+ rx_skbinfo = comm->rx_skb_info[i];
+ for (j = 0; j < comm->rx_desc_num[i]; j++) {
+ rx_desc[j].cmd1 = 0;
+ wmb(); // Clear OWN_BIT and then set other fields.
+ rx_desc[j].cmd2 = 0;
+ rx_desc[j].addr1 = 0;
+
+ if (rx_skbinfo[j].skb) {
+ dma_unmap_single(&comm->pdev->dev, rx_skbinfo[j].mapping,
+ comm->rx_desc_buff_size, DMA_FROM_DEVICE);
+ dev_kfree_skb(rx_skbinfo[j].skb);
+ rx_skbinfo[j].skb = NULL;
+ rx_skbinfo[j].mapping = 0;
+ }
+ }
+
+ kfree(rx_skbinfo);
+ comm->rx_skb_info[i] = NULL;
+ }
+}
+
+void descs_clean(struct sp_common *comm)
+{
+ rx_descs_clean(comm);
+ tx_descs_clean(comm);
+}
+
+void descs_free(struct sp_common *comm)
+{
+ u32 i;
+
+ descs_clean(comm);
+ comm->tx_desc = NULL;
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ comm->rx_desc[i] = NULL;
+
+ /* Free descriptor area */
+ if (comm->desc_base) {
+ dma_free_coherent(&comm->pdev->dev, comm->desc_size, comm->desc_base,
+ comm->desc_dma);
+ comm->desc_base = NULL;
+ comm->desc_dma = 0;
+ comm->desc_size = 0;
+ }
+}
+
+void tx_descs_init(struct sp_common *comm)
+{
+ memset(comm->tx_desc, '\0', sizeof(struct mac_desc) *
+ (TX_DESC_NUM + MAC_GUARD_DESC_NUM));
+}
+
+int rx_descs_init(struct sp_common *comm)
+{
+ struct sk_buff *skb;
+ u32 i, j;
+ struct mac_desc *rx_desc;
+ struct skb_info *rx_skbinfo;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ comm->rx_skb_info[i] = kmalloc_array(comm->rx_desc_num[i],
+ sizeof(struct skb_info), GFP_KERNEL);
+ if (!comm->rx_skb_info[i])
+ goto MEM_ALLOC_FAIL;
+
+ rx_skbinfo = comm->rx_skb_info[i];
+ rx_desc = comm->rx_desc[i];
+ for (j = 0; j < comm->rx_desc_num[i]; j++) {
+ skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
+ GFP_ATOMIC | GFP_DMA);
+ if (!skb)
+ goto MEM_ALLOC_FAIL;
+
+ skb->dev = comm->ndev;
+ skb_reserve(skb, RX_OFFSET); /* +data +tail */
+
+ rx_skbinfo[j].skb = skb;
+ rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
+ comm->rx_desc_buff_size,
+ DMA_FROM_DEVICE);
+ rx_desc[j].addr1 = rx_skbinfo[j].mapping;
+ rx_desc[j].addr2 = 0;
+ rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
+ EOR_BIT | comm->rx_desc_buff_size :
+ comm->rx_desc_buff_size;
+ wmb(); // Set OWN_BIT after other fields are effective.
+ rx_desc[j].cmd1 = OWN_BIT;
+ }
+ }
+
+ return 0;
+
+MEM_ALLOC_FAIL:
+ rx_descs_clean(comm);
+ return -ENOMEM;
+}
+
+int descs_alloc(struct sp_common *comm)
+{
+ u32 i;
+ s32 desc_size;
+
+ /* Alloc descriptor area */
+ desc_size = (TX_DESC_NUM + MAC_GUARD_DESC_NUM) * sizeof(struct mac_desc);
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ desc_size += comm->rx_desc_num[i] * sizeof(struct mac_desc);
+
+ comm->desc_base = dma_alloc_coherent(&comm->pdev->dev, desc_size, &comm->desc_dma,
+ GFP_KERNEL);
+ if (!comm->desc_base)
+ return -ENOMEM;
+
+ comm->desc_size = desc_size;
+
+ /* Setup Tx descriptor */
+ comm->tx_desc = (struct mac_desc *)comm->desc_base;
+
+ /* Setup Rx descriptor */
+ comm->rx_desc[0] = &comm->tx_desc[TX_DESC_NUM + MAC_GUARD_DESC_NUM];
+ for (i = 1; i < RX_DESC_QUEUE_NUM; i++)
+ comm->rx_desc[i] = comm->rx_desc[i - 1] + comm->rx_desc_num[i - 1];
+
+ return 0;
+}
+
+int descs_init(struct sp_common *comm)
+{
+ u32 i, ret;
+
+ // Initialize rx descriptor's data
+ comm->rx_desc_num[0] = RX_QUEUE0_DESC_NUM;
+#if RX_DESC_QUEUE_NUM > 1
+ comm->rx_desc_num[1] = RX_QUEUE1_DESC_NUM;
+#endif
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
+ comm->rx_desc[i] = NULL;
+ comm->rx_skb_info[i] = NULL;
+ comm->rx_pos[i] = 0;
+ }
+ comm->rx_desc_buff_size = MAC_RX_LEN_MAX;
+
+ // Initialize tx descriptor's data
+ comm->tx_done_pos = 0;
+ comm->tx_desc = NULL;
+ comm->tx_pos = 0;
+ comm->tx_desc_full = 0;
+ for (i = 0; i < TX_DESC_NUM; i++)
+ comm->tx_temp_skb_info[i].skb = NULL;
+
+ // Allocate tx & rx descriptors.
+ ret = descs_alloc(comm);
+ if (ret) {
+ netdev_err(comm->ndev, "Failed to allocate tx & rx descriptors!\n");
+ return ret;
+ }
+
+ tx_descs_init(comm);
+
+ return rx_descs_init(comm);
+}
diff --git a/drivers/net/ethernet/sunplus/sp_desc.h b/drivers/net/ethernet/sunplus/sp_desc.h
new file mode 100644
index 0000000..20f7519
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_desc.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_DESC_H__
+#define __SP_DESC_H__
+
+#include "sp_define.h"
+
+void rx_descs_flush(struct sp_common *comm);
+void tx_descs_clean(struct sp_common *comm);
+void rx_descs_clean(struct sp_common *comm);
+void descs_clean(struct sp_common *comm);
+void descs_free(struct sp_common *comm);
+void tx_descs_init(struct sp_common *comm);
+int rx_descs_init(struct sp_common *comm);
+int descs_alloc(struct sp_common *comm);
+int descs_init(struct sp_common *comm);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_driver.c b/drivers/net/ethernet/sunplus/sp_driver.c
new file mode 100644
index 0000000..a1c76b9
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_driver.c
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_net.h>
+#include "sp_driver.h"
+#include "sp_phy.h"
+
+static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
+ 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00
+};
+
+/*********************************************************************
+ *
+ * net_device_ops
+ *
+ **********************************************************************/
+static int ethernet_open(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ netdev_dbg(ndev, "Open port = %x\n", mac->lan_port);
+
+ mac->comm->enable |= mac->lan_port;
+
+ hal_mac_start(mac);
+ write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~(MAC_INT_TX | MAC_INT_RX));
+
+ netif_carrier_on(ndev);
+ if (netif_carrier_ok(ndev))
+ netif_start_queue(ndev);
+
+ return 0;
+}
+
+static int ethernet_stop(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ netif_carrier_off(ndev);
+
+ mac->comm->enable &= ~mac->lan_port;
+
+ hal_mac_stop(mac);
+
+ return 0;
+}
+
+/* Transmit a packet (called by the kernel) */
+static int ethernet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+ struct sp_common *comm = mac->comm;
+ u32 tx_pos;
+ u32 cmd1;
+ u32 cmd2;
+ struct mac_desc *txdesc;
+ struct skb_info *skbinfo;
+ unsigned long flags;
+
+ if (unlikely(comm->tx_desc_full == 1)) {
+ // No TX descriptors left. Wait for tx interrupt.
+ netdev_info(ndev, "TX descriptor queue full when xmit!\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ /* if skb size shorter than 60, fill it with '\0' */
+ if (unlikely(skb->len < ETH_ZLEN)) {
+ if (skb_tailroom(skb) >= (ETH_ZLEN - skb->len)) {
+ memset(__skb_put(skb, ETH_ZLEN - skb->len), '\0',
+ ETH_ZLEN - skb->len);
+ } else {
+ struct sk_buff *old_skb = skb;
+
+ skb = dev_alloc_skb(ETH_ZLEN + TX_OFFSET);
+ if (skb) {
+ memset(skb->data + old_skb->len, '\0',
+ ETH_ZLEN - old_skb->len);
+ memcpy(skb->data, old_skb->data, old_skb->len);
+ skb_put(skb, ETH_ZLEN); /* add data to an sk_buff */
+ dev_kfree_skb_irq(old_skb);
+ } else {
+ skb = old_skb;
+ }
+ }
+ }
+
+ spin_lock_irqsave(&comm->tx_lock, flags);
+ tx_pos = comm->tx_pos;
+ txdesc = &comm->tx_desc[tx_pos];
+ skbinfo = &comm->tx_temp_skb_info[tx_pos];
+ skbinfo->len = skb->len;
+ skbinfo->skb = skb;
+ skbinfo->mapping = dma_map_single(&comm->pdev->dev, skb->data,
+ skb->len, DMA_TO_DEVICE);
+ cmd1 = (OWN_BIT | FS_BIT | LS_BIT | (mac->to_vlan << 12) | (skb->len & LEN_MASK));
+ cmd2 = skb->len & LEN_MASK;
+
+ if (tx_pos == (TX_DESC_NUM - 1))
+ cmd2 |= EOR_BIT;
+
+ txdesc->addr1 = skbinfo->mapping;
+ txdesc->cmd2 = cmd2;
+ wmb(); // Set OWN_BIT after other fields of descriptor are effective.
+ txdesc->cmd1 = cmd1;
+
+ NEXT_TX(tx_pos);
+
+ if (unlikely(tx_pos == comm->tx_done_pos)) {
+ netif_stop_queue(ndev);
+ comm->tx_desc_full = 1;
+ }
+ comm->tx_pos = tx_pos;
+ wmb(); // make sure settings are effective.
+
+ /* trigger gmac to transmit */
+ hal_tx_trigger(mac);
+
+ spin_unlock_irqrestore(&mac->comm->tx_lock, flags);
+ return NETDEV_TX_OK;
+}
+
+static void ethernet_set_rx_mode(struct net_device *ndev)
+{
+ if (ndev) {
+ struct sp_mac *mac = netdev_priv(ndev);
+ struct sp_common *comm = mac->comm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&comm->ioctl_lock, flags);
+ hal_rx_mode_set(ndev);
+ spin_unlock_irqrestore(&comm->ioctl_lock, flags);
+ }
+}
+
+static int ethernet_set_mac_address(struct net_device *ndev, void *addr)
+{
+ struct sockaddr *hwaddr = (struct sockaddr *)addr;
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ if (netif_running(ndev))
+ return -EBUSY;
+
+ memcpy(ndev->dev_addr, hwaddr->sa_data, ndev->addr_len);
+
+ /* Delete the old Ethernet MAC address */
+ netdev_dbg(ndev, "HW Addr = %pM\n", mac->mac_addr);
+ if (is_valid_ether_addr(mac->mac_addr))
+ hal_mac_addr_del(mac);
+
+ /* Set the Ethernet MAC address */
+ memcpy(mac->mac_addr, hwaddr->sa_data, ndev->addr_len);
+ hal_mac_addr_set(mac);
+
+ return 0;
+}
+
+static int ethernet_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ case SIOCGMIIREG:
+ case SIOCSMIIREG:
+ return phy_mii_ioctl(mac->phy_dev, ifr, cmd);
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static void ethernet_tx_timeout(struct net_device *ndev, unsigned int txqueue)
+{
+}
+
+static struct net_device_stats *ethernet_get_stats(struct net_device *ndev)
+{
+ struct sp_mac *mac;
+
+ mac = netdev_priv(ndev);
+ return &mac->dev_stats;
+}
+
+static const struct net_device_ops netdev_ops = {
+ .ndo_open = ethernet_open,
+ .ndo_stop = ethernet_stop,
+ .ndo_start_xmit = ethernet_start_xmit,
+ .ndo_set_rx_mode = ethernet_set_rx_mode,
+ .ndo_set_mac_address = ethernet_set_mac_address,
+ .ndo_do_ioctl = ethernet_do_ioctl,
+ .ndo_tx_timeout = ethernet_tx_timeout,
+ .ndo_get_stats = ethernet_get_stats,
+};
+
+char *sp7021_otp_read_mac(struct device *dev, ssize_t *len, char *name)
+{
+ char *ret = NULL;
+ struct nvmem_cell *cell = nvmem_cell_get(dev, name);
+
+ if (IS_ERR_OR_NULL(cell)) {
+ dev_err(dev, "OTP %s read failure: %ld", name, PTR_ERR(cell));
+ return NULL;
+ }
+
+ ret = nvmem_cell_read(cell, len);
+ nvmem_cell_put(cell);
+ dev_dbg(dev, "%zd bytes are read from OTP %s.", *len, name);
+
+ return ret;
+}
+
+static void check_mac_vendor_id_and_convert(char *mac_addr)
+{
+ // Byte order of MAC address of some samples are reversed.
+ // Check vendor id and convert byte order if it is wrong.
+ if ((mac_addr[5] == 0xFC) && (mac_addr[4] == 0x4B) && (mac_addr[3] == 0xBC) &&
+ ((mac_addr[0] != 0xFC) || (mac_addr[1] != 0x4B) || (mac_addr[2] != 0xBC))) {
+ char tmp;
+
+ // Swap mac_addr[0] and mac_addr[5]
+ tmp = mac_addr[0];
+ mac_addr[0] = mac_addr[5];
+ mac_addr[5] = tmp;
+
+ // Swap mac_addr[1] and mac_addr[4]
+ tmp = mac_addr[1];
+ mac_addr[1] = mac_addr[4];
+ mac_addr[4] = tmp;
+
+ // Swap mac_addr[2] and mac_addr[3]
+ tmp = mac_addr[2];
+ mac_addr[2] = mac_addr[3];
+ mac_addr[3] = tmp;
+ }
+}
+
+/*********************************************************************
+ *
+ * platform_driver
+ *
+ **********************************************************************/
+static u32 init_netdev(struct platform_device *pdev, int eth_no, struct net_device **r_ndev)
+{
+ struct sp_mac *mac;
+ struct net_device *ndev;
+ char *m_addr_name = (eth_no == 0) ? "mac_addr0" : "mac_addr1";
+ ssize_t otp_l = 0;
+ char *otp_v;
+ int ret;
+
+ // Allocate the devices, and also allocate sp_mac, we can get it by netdev_priv().
+ ndev = alloc_etherdev(sizeof(*mac));
+ if (!ndev) {
+ *r_ndev = NULL;
+ return -ENOMEM;
+ }
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+ ndev->netdev_ops = &netdev_ops;
+
+ mac = netdev_priv(ndev);
+ mac->ndev = ndev;
+ mac->next_ndev = NULL;
+
+ // Get property 'mac-addr0' or 'mac-addr1' from dts.
+ otp_v = sp7021_otp_read_mac(&pdev->dev, &otp_l, m_addr_name);
+ if ((otp_l < 6) || IS_ERR_OR_NULL(otp_v)) {
+ dev_info(&pdev->dev, "OTP mac %s (len = %zd) is invalid, using default!\n",
+ m_addr_name, otp_l);
+ otp_l = 0;
+ } else {
+ // Check if mac-address is valid or not. If not, copy from default.
+ memcpy(mac->mac_addr, otp_v, 6);
+
+ // Byte order of Some samples are reversed. Convert byte order here.
+ check_mac_vendor_id_and_convert(mac->mac_addr);
+
+ if (!is_valid_ether_addr(mac->mac_addr)) {
+ dev_info(&pdev->dev, "Invalid mac in OTP[%s] = %pM, use default!\n",
+ m_addr_name, mac->mac_addr);
+ otp_l = 0;
+ }
+ }
+ if (otp_l != 6) {
+ memcpy(mac->mac_addr, def_mac_addr, ETHERNET_MAC_ADDR_LEN);
+ mac->mac_addr[5] += eth_no;
+ }
+
+ dev_info(&pdev->dev, "HW Addr = %pM\n", mac->mac_addr);
+
+ memcpy(ndev->dev_addr, mac->mac_addr, ETHERNET_MAC_ADDR_LEN);
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register net device \"%s\"!\n",
+ ndev->name);
+ free_netdev(ndev);
+ *r_ndev = NULL;
+ return ret;
+ }
+ netdev_info(ndev, "Registered net device \"%s\" successfully.\n", ndev->name);
+
+ *r_ndev = ndev;
+ return 0;
+}
+
+static int soc0_open(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 ret;
+
+ hal_mac_stop(mac);
+
+ ret = descs_init(comm);
+ if (ret) {
+ netdev_err(mac->ndev, "Fail to initialize mac descriptors!\n");
+ descs_free(comm);
+ return ret;
+ }
+
+ mac_init(mac);
+ return 0;
+}
+
+static int soc0_stop(struct sp_mac *mac)
+{
+ hal_mac_stop(mac);
+
+ descs_free(mac->comm);
+ return 0;
+}
+
+static int sp_probe(struct platform_device *pdev)
+{
+ struct sp_common *comm;
+ struct resource *rc;
+ struct net_device *ndev, *ndev2;
+ struct device_node *np;
+ struct sp_mac *mac, *mac2;
+ int ret;
+
+ if (platform_get_drvdata(pdev))
+ return -ENODEV;
+
+ // Allocate memory for 'sp_common' area.
+ comm = devm_kzalloc(&pdev->dev, sizeof(*comm), GFP_KERNEL);
+ if (!comm)
+ return -ENOMEM;
+ comm->pdev = pdev;
+
+ spin_lock_init(&comm->rx_lock);
+ spin_lock_init(&comm->tx_lock);
+ spin_lock_init(&comm->ioctl_lock);
+
+ // Get memory resoruce "emac" from dts.
+ rc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "emac");
+ if (!rc) {
+ dev_err(&pdev->dev, "No MEM resource \'emac\' found!\n");
+ return -ENXIO;
+ }
+ dev_dbg(&pdev->dev, "name = \"%s\", start = %pa\n", rc->name, &rc->start);
+
+ comm->sp_reg_base = devm_ioremap_resource(&pdev->dev, rc);
+ if (IS_ERR(comm->sp_reg_base)) {
+ dev_err(&pdev->dev, "ioremap failed!\n");
+ return -ENOMEM;
+ }
+
+ // Get memory resoruce "moon5" from dts.
+ rc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "moon5");
+ if (!rc) {
+ dev_err(&pdev->dev, "No MEM resource \'moon5\' found!\n");
+ return -ENXIO;
+ }
+ dev_dbg(&pdev->dev, "name = \"%s\", start = %pa\n", rc->name, &rc->start);
+
+ // Note that moon5 is shared resource. Don't use devm_ioremap_resource().
+ comm->moon5_reg_base = devm_ioremap(&pdev->dev, rc->start, rc->end - rc->start + 1);
+ if (IS_ERR(comm->moon5_reg_base)) {
+ dev_err(&pdev->dev, "ioremap failed!\n");
+ return -ENOMEM;
+ }
+
+ // Get irq resource from dts.
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+ comm->irq = ret;
+
+ // Get clock controller.
+ comm->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(comm->clk)) {
+ dev_err_probe(&pdev->dev, PTR_ERR(comm->clk),
+ "Failed to retrieve clock controller!\n");
+ return PTR_ERR(comm->clk);
+ }
+
+ // Get reset controller.
+ comm->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(comm->rstc)) {
+ dev_err_probe(&pdev->dev, PTR_ERR(comm->rstc),
+ "Failed to retrieve reset controller!\n");
+ return PTR_ERR(comm->rstc);
+ }
+
+ // Enable clock.
+ clk_prepare_enable(comm->clk);
+ udelay(1);
+
+ reset_control_assert(comm->rstc);
+ udelay(1);
+ reset_control_deassert(comm->rstc);
+ udelay(1);
+
+ // Initialize the 1st net device.
+ ret = init_netdev(pdev, 0, &ndev);
+ if (!ndev)
+ return ret;
+
+ platform_set_drvdata(pdev, ndev);
+
+ ndev->irq = comm->irq;
+ mac = netdev_priv(ndev);
+ mac->comm = comm;
+ comm->ndev = ndev;
+
+ // Get node of phy 1.
+ mac->phy_node = of_parse_phandle(pdev->dev.of_node, "phy-handle1", 0);
+ if (!mac->phy_node) {
+ netdev_info(ndev, "Cannot get node of phy 1!\n");
+ ret = -ENODEV;
+ goto out_unregister_dev;
+ }
+
+ // Get address of phy from dts.
+ if (of_property_read_u32(mac->phy_node, "reg", &mac->phy_addr)) {
+ mac->phy_addr = 0;
+ netdev_info(ndev, "Cannot get address of phy 1! Set to 0.\n");
+ }
+
+ // Get mode of phy from dts.
+ if (of_get_phy_mode(mac->phy_node, &mac->phy_mode)) {
+ mac->phy_mode = PHY_INTERFACE_MODE_RGMII_ID;
+ netdev_info(ndev, "Missing phy-mode of phy 1! Set to \'rgmii-id\'.\n");
+ }
+
+ // Request irq.
+ ret = devm_request_irq(&pdev->dev, comm->irq, ethernet_interrupt, 0,
+ ndev->name, ndev);
+ if (ret) {
+ netdev_err(ndev, "Failed to request irq #%d for \"%s\"!\n",
+ ndev->irq, ndev->name);
+ goto out_unregister_dev;
+ }
+
+ mac->cpu_port = 0x1; // soc0
+ mac->lan_port = 0x1; // forward to port 0
+ mac->to_vlan = 0x1; // vlan group: 0
+ mac->vlan_id = 0x0; // vlan group: 0
+
+ // Set MAC address
+ hal_mac_addr_set(mac);
+ hal_rx_mode_set(ndev);
+ hal_mac_addr_table_del_all(mac);
+
+ ndev2 = NULL;
+ np = of_parse_phandle(pdev->dev.of_node, "phy-handle2", 0);
+ if (np) {
+ init_netdev(pdev, 1, &ndev2);
+ if (ndev2) {
+ mac->next_ndev = ndev2; // Point to the second net device.
+
+ ndev2->irq = comm->irq;
+ mac2 = netdev_priv(ndev2);
+ mac2->comm = comm;
+ mac2->phy_node = np;
+
+ if (of_property_read_u32(mac2->phy_node, "reg", &mac2->phy_addr)) {
+ mac2->phy_addr = 1;
+ netdev_info(ndev2, "Cannot get address of phy 2! Set to 1.\n");
+ }
+
+ if (of_get_phy_mode(mac2->phy_node, &mac2->phy_mode)) {
+ mac2->phy_mode = PHY_INTERFACE_MODE_RGMII_ID;
+ netdev_info(ndev, "Missing phy-mode phy 2! Set to \'rgmii-id\'.\n");
+ }
+
+ mac2->cpu_port = 0x1; // soc0
+ mac2->lan_port = 0x2; // forward to port 1
+ mac2->to_vlan = 0x2; // vlan group: 1
+ mac2->vlan_id = 0x1; // vlan group: 1
+
+ hal_mac_addr_set(mac2); // Set MAC address for the 2nd net device.
+ hal_rx_mode_set(ndev2);
+ }
+ }
+
+ soc0_open(mac);
+ hal_set_rmii_tx_rx_pol(mac);
+ hal_phy_addr(mac);
+
+ ret = mdio_init(pdev, ndev);
+ if (ret) {
+ netdev_err(ndev, "Failed to initialize mdio!\n");
+ goto out_unregister_dev;
+ }
+
+ ret = sp_phy_probe(ndev);
+ if (ret) {
+ netdev_err(ndev, "Failed to probe phy!\n");
+ goto out_freemdio;
+ }
+
+ if (ndev2) {
+ ret = sp_phy_probe(ndev2);
+ if (ret) {
+ netdev_err(ndev2, "Failed to probe phy!\n");
+ unregister_netdev(ndev2);
+ mac->next_ndev = 0;
+ }
+ }
+
+ netif_napi_add(ndev, &comm->rx_napi, rx_poll, RX_NAPI_WEIGHT);
+ napi_enable(&comm->rx_napi);
+ netif_napi_add(ndev, &comm->tx_napi, tx_poll, TX_NAPI_WEIGHT);
+ napi_enable(&comm->tx_napi);
+ return 0;
+
+out_freemdio:
+ if (comm->mii_bus)
+ mdio_remove(ndev);
+
+out_unregister_dev:
+ unregister_netdev(ndev);
+ if (ndev2)
+ unregister_netdev(ndev2);
+
+ return ret;
+}
+
+static int sp_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev, *ndev2;
+ struct sp_mac *mac;
+
+ ndev = platform_get_drvdata(pdev);
+ if (!ndev)
+ return 0;
+
+ mac = netdev_priv(ndev);
+
+ // Unregister and free 2nd net device.
+ ndev2 = mac->next_ndev;
+ if (ndev2) {
+ sp_phy_remove(ndev2);
+ unregister_netdev(ndev2);
+ free_netdev(ndev2);
+ }
+
+ mac->comm->enable = 0;
+ soc0_stop(mac);
+
+ // Disable and delete napi.
+ napi_disable(&mac->comm->rx_napi);
+ netif_napi_del(&mac->comm->rx_napi);
+ napi_disable(&mac->comm->tx_napi);
+ netif_napi_del(&mac->comm->tx_napi);
+
+ sp_phy_remove(ndev);
+ mdio_remove(ndev);
+
+ // Unregister and free 1st net device.
+ unregister_netdev(ndev);
+ free_netdev(ndev);
+
+ clk_disable(mac->comm->clk);
+
+ return 0;
+}
+
+static const struct of_device_id sp_of_match[] = {
+ {.compatible = "sunplus,sp7021-emac"},
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, sp_of_match);
+
+static struct platform_driver sp_driver = {
+ .probe = sp_probe,
+ .remove = sp_remove,
+ .driver = {
+ .name = "sp7021_emac",
+ .owner = THIS_MODULE,
+ .of_match_table = sp_of_match,
+ },
+};
+
+module_platform_driver(sp_driver);
+
+MODULE_AUTHOR("Wells Lu <[email protected]>");
+MODULE_DESCRIPTION("Sunplus Dual 10M/100M Ethernet driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/sunplus/sp_driver.h b/drivers/net/ethernet/sunplus/sp_driver.h
new file mode 100644
index 0000000..ea80a9e
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_driver.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_DRIVER_H__
+#define __SP_DRIVER_H__
+
+#include "sp_define.h"
+#include "sp_register.h"
+#include "sp_hal.h"
+#include "sp_int.h"
+#include "sp_mdio.h"
+#include "sp_mac.h"
+#include "sp_desc.h"
+
+#define NEXT_TX(N) ((N) = (((N) + 1) == TX_DESC_NUM) ? 0 : (N) + 1)
+#define NEXT_RX(QUEUE, N) ((N) = (((N) + 1) == mac->comm->rx_desc_num[QUEUE]) ? 0 : (N) + 1)
+
+#define RX_NAPI_WEIGHT 16
+#define TX_NAPI_WEIGHT 16
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_hal.c b/drivers/net/ethernet/sunplus/sp_hal.c
new file mode 100644
index 0000000..a7f06d7
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_hal.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/iopoll.h>
+#include "sp_hal.h"
+
+void hal_mac_stop(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg, disable;
+
+ if (comm->enable == 0) {
+ // Mask and clear all interrupts, except PORT_ST_CHG.
+ write_sw_int_mask0(mac, 0xffffffff);
+ writel(0xffffffff & (~MAC_INT_PORT_ST_CHG),
+ comm->sp_reg_base + SP_SW_INT_STATUS_0);
+
+ // Disable cpu 0 and cpu 1.
+ reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
+ writel((0x3 << 6) | reg, comm->sp_reg_base + SP_CPU_CNTL);
+ }
+
+ // Disable lan 0 and lan 1.
+ disable = ((~comm->enable) & 0x3) << 24;
+ reg = readl(comm->sp_reg_base + SP_PORT_CNTL0);
+ writel(disable | reg, comm->sp_reg_base + SP_PORT_CNTL0);
+}
+
+void hal_mac_reset(struct sp_mac *mac)
+{
+}
+
+void hal_mac_start(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg;
+
+ // Enable cpu port 0 (6) & port 0 crc padding (8)
+ reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
+ writel((reg & (~(0x1 << 6))) | (0x1 << 8), comm->sp_reg_base + SP_CPU_CNTL);
+
+ // Enable lan 0 & lan 1
+ reg = readl(comm->sp_reg_base + SP_PORT_CNTL0);
+ writel(reg & (~(comm->enable << 24)), comm->sp_reg_base + SP_PORT_CNTL0);
+}
+
+void hal_mac_addr_set(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg;
+
+ // Write MAC address.
+ writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
+ comm->sp_reg_base + SP_W_MAC_15_0);
+ writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
+ (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
+
+ // Set aging=1
+ writel((mac->cpu_port << 10) + (mac->vlan_id << 7) + (1 << 4) + 0x1,
+ comm->sp_reg_base + SP_WT_MAC_AD0);
+
+ // Wait for completing.
+ do {
+ reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
+ ndelay(10);
+ netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
+ } while ((reg & (0x1 << 1)) == 0x0);
+
+ netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
+ readl(comm->sp_reg_base + SP_WT_MAC_AD0),
+ readl(comm->sp_reg_base + SP_W_MAC_47_16),
+ readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
+}
+
+void hal_mac_addr_del(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg;
+
+ // Write MAC address.
+ writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
+ comm->sp_reg_base + SP_W_MAC_15_0);
+ writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
+ (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
+
+ // Wait for completing.
+ writel((0x1 << 12) + (mac->vlan_id << 7) + 0x1,
+ comm->sp_reg_base + SP_WT_MAC_AD0);
+ do {
+ reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
+ ndelay(10);
+ netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
+ } while ((reg & (0x1 << 1)) == 0x0);
+
+ netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
+ readl(comm->sp_reg_base + SP_WT_MAC_AD0),
+ readl(comm->sp_reg_base + SP_W_MAC_47_16),
+ readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
+}
+
+void hal_mac_addr_table_del_all(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg;
+
+ // Wait for address table being idle.
+ do {
+ reg = readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH);
+ ndelay(10);
+ } while (!(reg & MAC_ADDR_LOOKUP_IDLE));
+
+ // Search address table from start.
+ writel(readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH) | MAC_BEGIN_SEARCH_ADDR,
+ comm->sp_reg_base + SP_ADDR_TBL_SRCH);
+ while (1) {
+ do {
+ reg = readl(comm->sp_reg_base + SP_ADDR_TBL_ST);
+ ndelay(10);
+ netdev_dbg(mac->ndev, "addr_tbl_st = %08x\n", reg);
+ } while (!(reg & (MAC_AT_TABLE_END | MAC_AT_DATA_READY)));
+
+ if (reg & MAC_AT_TABLE_END)
+ break;
+
+ netdev_dbg(mac->ndev, "addr_tbl_st = %08x\n", reg);
+ netdev_dbg(mac->ndev, "@AT #%u: port=%01x, cpu=%01x, vid=%u, aging=%u, proxy=%u, mc_ingress=%u\n",
+ (reg >> 22) & 0x3ff, (reg >> 12) & 0x3, (reg >> 10) & 0x3,
+ (reg >> 7) & 0x7, (reg >> 4) & 0x7, (reg >> 3) & 0x1,
+ (reg >> 2) & 0x1);
+
+ // Delete all entries which are learnt from lan ports.
+ if ((reg >> 12) & 0x3) {
+ writel(readl(comm->sp_reg_base + SP_MAC_AD_SER0),
+ comm->sp_reg_base + SP_W_MAC_15_0);
+ writel(readl(comm->sp_reg_base + SP_MAC_AD_SER1),
+ comm->sp_reg_base + SP_W_MAC_47_16);
+
+ writel((0x1 << 12) + (reg & (0x7 << 7)) + 0x1,
+ comm->sp_reg_base + SP_WT_MAC_AD0);
+ do {
+ reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
+ ndelay(10);
+ netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
+ } while ((reg & (0x1 << 1)) == 0x0);
+ netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
+ readl(comm->sp_reg_base + SP_WT_MAC_AD0),
+ readl(comm->sp_reg_base + SP_W_MAC_47_16),
+ readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
+ }
+
+ // Search next.
+ writel(readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH) | MAC_SEARCH_NEXT_ADDR,
+ comm->sp_reg_base + SP_ADDR_TBL_SRCH);
+ }
+}
+
+void hal_mac_init(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg;
+
+ // Disable cpu0 and cpu 1.
+ reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
+ writel((0x3 << 6) | reg, comm->sp_reg_base + SP_CPU_CNTL);
+
+ // Descriptor base address
+ writel(mac->comm->desc_dma, comm->sp_reg_base + SP_TX_LBASE_ADDR_0);
+ writel(mac->comm->desc_dma + sizeof(struct mac_desc) * TX_DESC_NUM,
+ comm->sp_reg_base + SP_TX_HBASE_ADDR_0);
+ writel(mac->comm->desc_dma + sizeof(struct mac_desc) * (TX_DESC_NUM +
+ MAC_GUARD_DESC_NUM), comm->sp_reg_base + SP_RX_HBASE_ADDR_0);
+ writel(mac->comm->desc_dma + sizeof(struct mac_desc) * (TX_DESC_NUM +
+ MAC_GUARD_DESC_NUM + RX_QUEUE0_DESC_NUM),
+ comm->sp_reg_base + SP_RX_LBASE_ADDR_0);
+
+ // Fc_rls_th=0x4a, Fc_set_th=0x3a, Drop_rls_th=0x2d, Drop_set_th=0x1d
+ writel(0x4a3a2d1d, comm->sp_reg_base + SP_FL_CNTL_TH);
+
+ // Cpu_rls_th=0x4a, Cpu_set_th=0x3a, Cpu_th=0x12, Port_th=0x12
+ writel(0x4a3a1212, comm->sp_reg_base + SP_CPU_FL_CNTL_TH);
+
+ // mtcc_lmt=0xf, Pri_th_l=6, Pri_th_h=6, weigh_8x_en=1
+ writel(0xf6680000, comm->sp_reg_base + SP_PRI_FL_CNTL);
+
+ // High-active LED
+ reg = readl(comm->sp_reg_base + SP_LED_PORT0);
+ writel(reg | (1 << 28), comm->sp_reg_base + SP_LED_PORT0);
+
+ // Disable cpu port0 aging (12)
+ // Disable cpu port0 learning (14)
+ // Enable UC and MC packets
+ reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
+ writel((reg & (~((0x1 << 14) | (0x3c << 0)))) | (0x1 << 12),
+ comm->sp_reg_base + SP_CPU_CNTL);
+
+ // Disable lan port SA learning.
+ reg = readl(comm->sp_reg_base + SP_PORT_CNTL1);
+ writel(reg | (0x3 << 8), comm->sp_reg_base + SP_PORT_CNTL1);
+
+ // Port 0: VLAN group 0
+ // Port 1: VLAN group 1
+ writel((1 << 4) + 0, comm->sp_reg_base + SP_PVID_CONFIG0);
+
+ // VLAN group 0: cpu0+port0
+ // VLAN group 1: cpu0+port1
+ writel((0xa << 8) + 0x9, comm->sp_reg_base + SP_VLAN_MEMSET_CONFIG0);
+
+ // RMC forward: to cpu
+ // LED: 60mS
+ // BC storm prev: 31 BC
+ reg = readl(comm->sp_reg_base + SP_SW_GLB_CNTL);
+ writel((reg & (~((0x3 << 25) | (0x3 << 23) | (0x3 << 4)))) |
+ (0x1 << 25) | (0x1 << 23) | (0x1 << 4),
+ comm->sp_reg_base + SP_SW_GLB_CNTL);
+
+ write_sw_int_mask0(mac, MAC_INT_MASK_DEF);
+}
+
+void hal_rx_mode_set(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+ struct sp_common *comm = mac->comm;
+ u32 mask, reg, rx_mode;
+
+ netdev_dbg(ndev, "ndev->flags = %08x\n", ndev->flags);
+
+ mask = (mac->lan_port << 2) | (mac->lan_port << 0);
+ reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
+
+ if (ndev->flags & IFF_PROMISC) { /* Set promiscuous mode */
+ // Allow MC and unknown UC packets
+ rx_mode = (mac->lan_port << 2) | (mac->lan_port << 0);
+ } else if ((!netdev_mc_empty(ndev) && (ndev->flags & IFF_MULTICAST)) ||
+ (ndev->flags & IFF_ALLMULTI)) {
+ // Allow MC packets
+ rx_mode = (mac->lan_port << 2);
+ } else {
+ // Disable MC and unknown UC packets
+ rx_mode = 0;
+ }
+
+ writel((reg & (~mask)) | ((~rx_mode) & mask), comm->sp_reg_base + SP_CPU_CNTL);
+ netdev_dbg(ndev, "cpu_cntl = %08x\n", readl(comm->sp_reg_base + SP_CPU_CNTL));
+}
+
+int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8 reg_addr, u32 wdata)
+{
+ struct sp_common *comm = mac->comm;
+ u32 val, ret;
+
+ writel((wdata << 16) | (op_cd << 13) | (reg_addr << 8) | phy_addr,
+ comm->sp_reg_base + SP_PHY_CNTL_REG0);
+
+ ret = read_poll_timeout(readl, val, val & op_cd, 10, 1000, 1,
+ comm->sp_reg_base + SP_PHY_CNTL_REG1);
+ if (ret == 0)
+ return val >> 16;
+ else
+ return ret;
+}
+
+void hal_tx_trigger(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+
+ writel((0x1 << 1), comm->sp_reg_base + SP_CPU_TX_TRIG);
+}
+
+void hal_set_rmii_tx_rx_pol(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg;
+
+ // Set polarity of RX and TX of RMII signal.
+ reg = readl(comm->moon5_reg_base + MOON5_MO4_L2SW_CLKSW_CTL);
+ writel(reg | (0xf << 16) | 0xf, comm->moon5_reg_base + MOON5_MO4_L2SW_CLKSW_CTL);
+}
+
+void hal_phy_addr(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+ u32 reg;
+
+ // Set address of phy.
+ reg = readl(comm->sp_reg_base + SP_MAC_FORCE_MODE);
+ reg = (reg & (~(0x1f << 16))) | ((mac->phy_addr & 0x1f) << 16);
+ if (mac->next_ndev) {
+ struct net_device *ndev2 = mac->next_ndev;
+ struct sp_mac *mac2 = netdev_priv(ndev2);
+
+ reg = (reg & (~(0x1f << 24))) | ((mac2->phy_addr & 0x1f) << 24);
+ }
+ writel(reg, comm->sp_reg_base + SP_MAC_FORCE_MODE);
+}
+
+u32 read_sw_int_mask0(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+
+ return readl(comm->sp_reg_base + SP_SW_INT_MASK_0);
+}
+
+void write_sw_int_mask0(struct sp_mac *mac, u32 value)
+{
+ struct sp_common *comm = mac->comm;
+
+ writel(value, comm->sp_reg_base + SP_SW_INT_MASK_0);
+}
+
+void write_sw_int_status0(struct sp_mac *mac, u32 value)
+{
+ struct sp_common *comm = mac->comm;
+
+ writel(value, comm->sp_reg_base + SP_SW_INT_STATUS_0);
+}
+
+u32 read_sw_int_status0(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+
+ return readl(comm->sp_reg_base + SP_SW_INT_STATUS_0);
+}
+
+u32 read_port_ability(struct sp_mac *mac)
+{
+ struct sp_common *comm = mac->comm;
+
+ return readl(comm->sp_reg_base + SP_PORT_ABILITY);
+}
diff --git a/drivers/net/ethernet/sunplus/sp_hal.h b/drivers/net/ethernet/sunplus/sp_hal.h
new file mode 100644
index 0000000..f4b1979
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_hal.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_HAL_H__
+#define __SP_HAL_H__
+
+#include "sp_register.h"
+#include "sp_define.h"
+#include "sp_desc.h"
+
+void hal_mac_stop(struct sp_mac *mac);
+void hal_mac_reset(struct sp_mac *mac);
+void hal_mac_start(struct sp_mac *mac);
+void hal_mac_addr_set(struct sp_mac *mac);
+void hal_mac_addr_del(struct sp_mac *mac);
+void hal_mac_addr_table_del_all(struct sp_mac *mac);
+void hal_mac_init(struct sp_mac *mac);
+void hal_rx_mode_set(struct net_device *ndev);
+int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8 reg_addr, u32 wdata);
+void hal_tx_trigger(struct sp_mac *mac);
+void hal_set_rmii_tx_rx_pol(struct sp_mac *mac);
+void hal_phy_addr(struct sp_mac *mac);
+u32 read_sw_int_mask0(struct sp_mac *mac);
+void write_sw_int_mask0(struct sp_mac *mac, u32 value);
+void write_sw_int_status0(struct sp_mac *mac, u32 value);
+u32 read_sw_int_status0(struct sp_mac *mac);
+u32 read_port_ability(struct sp_mac *mac);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_int.c b/drivers/net/ethernet/sunplus/sp_int.c
new file mode 100644
index 0000000..27d81b6
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_int.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "sp_define.h"
+#include "sp_int.h"
+#include "sp_driver.h"
+#include "sp_hal.h"
+
+static void port_status_change(struct sp_mac *mac)
+{
+ u32 reg;
+ struct net_device *ndev = mac->ndev;
+
+ reg = read_port_ability(mac);
+ if (!netif_carrier_ok(ndev) && (reg & PORT_ABILITY_LINK_ST_P0)) {
+ netif_carrier_on(ndev);
+ netif_start_queue(ndev);
+ } else if (netif_carrier_ok(ndev) && !(reg & PORT_ABILITY_LINK_ST_P0)) {
+ netif_carrier_off(ndev);
+ netif_stop_queue(ndev);
+ }
+
+ if (mac->next_ndev) {
+ struct net_device *ndev2 = mac->next_ndev;
+
+ if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
+ netif_carrier_on(ndev2);
+ netif_start_queue(ndev2);
+ } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
+ netif_carrier_off(ndev2);
+ netif_stop_queue(ndev2);
+ }
+ }
+}
+
+static void rx_skb(struct sp_mac *mac, struct sk_buff *skb)
+{
+ mac->dev_stats.rx_packets++;
+ mac->dev_stats.rx_bytes += skb->len;
+ netif_receive_skb(skb);
+}
+
+int rx_poll(struct napi_struct *napi, int budget)
+{
+ struct sp_common *comm = container_of(napi, struct sp_common, rx_napi);
+ struct sp_mac *mac = netdev_priv(comm->ndev);
+ struct sk_buff *skb, *new_skb;
+ struct skb_info *sinfo;
+ struct mac_desc *desc;
+ struct mac_desc *h_desc;
+ u32 rx_pos, pkg_len;
+ u32 cmd;
+ u32 num, rx_count;
+ s32 queue;
+ int ndev2_pkt;
+ struct net_device_stats *dev_stats;
+
+ spin_lock(&comm->rx_lock);
+
+ // Process high-priority queue and then low-priority queue.
+ for (queue = 0; queue < RX_DESC_QUEUE_NUM; queue++) {
+ rx_pos = comm->rx_pos[queue];
+ rx_count = comm->rx_desc_num[queue];
+
+ for (num = 0; num < rx_count; num++) {
+ sinfo = comm->rx_skb_info[queue] + rx_pos;
+ desc = comm->rx_desc[queue] + rx_pos;
+ cmd = desc->cmd1;
+
+ if (cmd & OWN_BIT)
+ break;
+
+ if ((cmd & PKTSP_MASK) == PKTSP_PORT1) {
+ struct sp_mac *mac2;
+
+ ndev2_pkt = 1;
+ mac2 = (mac->next_ndev) ? netdev_priv(mac->next_ndev) : NULL;
+ dev_stats = (mac2) ? &mac2->dev_stats : &mac->dev_stats;
+ } else {
+ ndev2_pkt = 0;
+ dev_stats = &mac->dev_stats;
+ }
+
+ pkg_len = cmd & LEN_MASK;
+ if (unlikely((cmd & ERR_CODE) || (pkg_len < 64))) {
+ dev_stats->rx_length_errors++;
+ dev_stats->rx_dropped++;
+ goto NEXT;
+ }
+
+ if (unlikely(cmd & RX_IP_CHKSUM_BIT)) {
+ dev_stats->rx_crc_errors++;
+ dev_stats->rx_dropped++;
+ goto NEXT;
+ }
+
+ // Allocate an skbuff for receiving.
+ new_skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
+ GFP_ATOMIC | GFP_DMA);
+ if (unlikely(!new_skb)) {
+ dev_stats->rx_dropped++;
+ goto NEXT;
+ }
+ new_skb->dev = mac->ndev;
+
+ dma_unmap_single(&comm->pdev->dev, sinfo->mapping,
+ comm->rx_desc_buff_size, DMA_FROM_DEVICE);
+
+ skb = sinfo->skb;
+ skb->ip_summed = CHECKSUM_NONE;
+
+ /*skb_put will judge if tail exceeds end, but __skb_put won't */
+ __skb_put(skb, (pkg_len - 4 > comm->rx_desc_buff_size) ?
+ comm->rx_desc_buff_size : pkg_len - 4);
+
+ sinfo->mapping = dma_map_single(&comm->pdev->dev, new_skb->data,
+ comm->rx_desc_buff_size,
+ DMA_FROM_DEVICE);
+ sinfo->skb = new_skb;
+
+ if (ndev2_pkt) {
+ struct net_device *netdev2 = mac->next_ndev;
+
+ if (netdev2) {
+ skb->protocol = eth_type_trans(skb, netdev2);
+ rx_skb(netdev_priv(netdev2), skb);
+ }
+ } else {
+ skb->protocol = eth_type_trans(skb, mac->ndev);
+ rx_skb(mac, skb);
+ }
+
+ desc->addr1 = sinfo->mapping;
+
+NEXT:
+ desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
+ EOR_BIT | MAC_RX_LEN_MAX : MAC_RX_LEN_MAX;
+ wmb(); // Set OWN_BIT after other fields of descriptor are effective.
+ desc->cmd1 = OWN_BIT | (comm->rx_desc_buff_size & LEN_MASK);
+
+ NEXT_RX(queue, rx_pos);
+
+ // If there are packets in high-priority queue,
+ // stop processing low-priority queue.
+ if ((queue == 1) && ((h_desc->cmd1 & OWN_BIT) == 0))
+ break;
+ }
+
+ comm->rx_pos[queue] = rx_pos;
+
+ // Save pointer to last rx descriptor of high-priority queue.
+ if (queue == 0)
+ h_desc = comm->rx_desc[queue] + rx_pos;
+ }
+
+ spin_unlock(&comm->rx_lock);
+
+ wmb(); // make sure settings are effective.
+ write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_RX);
+
+ napi_complete(napi);
+ return 0;
+}
+
+int tx_poll(struct napi_struct *napi, int budget)
+{
+ struct sp_common *comm = container_of(napi, struct sp_common, tx_napi);
+ struct sp_mac *mac = netdev_priv(comm->ndev);
+ u32 tx_done_pos;
+ u32 cmd;
+ struct skb_info *skbinfo;
+ struct sp_mac *smac;
+
+ spin_lock(&comm->tx_lock);
+
+ tx_done_pos = comm->tx_done_pos;
+ while ((tx_done_pos != comm->tx_pos) || (comm->tx_desc_full == 1)) {
+ cmd = comm->tx_desc[tx_done_pos].cmd1;
+ if (cmd & OWN_BIT)
+ break;
+
+ skbinfo = &comm->tx_temp_skb_info[tx_done_pos];
+ if (unlikely(!skbinfo->skb))
+ netdev_err(mac->ndev, "skb is null!\n");
+
+ smac = mac;
+ if (mac->next_ndev && ((cmd & TO_VLAN_MASK) == TO_VLAN_GROUP1))
+ smac = netdev_priv(mac->next_ndev);
+
+ if (unlikely(cmd & (ERR_CODE))) {
+ smac->dev_stats.tx_errors++;
+ } else {
+ smac->dev_stats.tx_packets++;
+ smac->dev_stats.tx_bytes += skbinfo->len;
+ }
+
+ dma_unmap_single(&comm->pdev->dev, skbinfo->mapping, skbinfo->len,
+ DMA_TO_DEVICE);
+ skbinfo->mapping = 0;
+ dev_kfree_skb_irq(skbinfo->skb);
+ skbinfo->skb = NULL;
+
+ NEXT_TX(tx_done_pos);
+ if (comm->tx_desc_full == 1)
+ comm->tx_desc_full = 0;
+ }
+
+ comm->tx_done_pos = tx_done_pos;
+ if (!comm->tx_desc_full) {
+ if (netif_queue_stopped(mac->ndev))
+ netif_wake_queue(mac->ndev);
+
+ if (mac->next_ndev) {
+ if (netif_queue_stopped(mac->next_ndev))
+ netif_wake_queue(mac->next_ndev);
+ }
+ }
+
+ spin_unlock(&comm->tx_lock);
+
+ wmb(); // make sure settings are effective.
+ write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_TX);
+
+ napi_complete(napi);
+ return 0;
+}
+
+irqreturn_t ethernet_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev;
+ struct sp_mac *mac;
+ struct sp_common *comm;
+ u32 status;
+
+ ndev = (struct net_device *)dev_id;
+ if (unlikely(!ndev)) {
+ netdev_err(ndev, "ndev is null!\n");
+ goto OUT;
+ }
+
+ mac = netdev_priv(ndev);
+ comm = mac->comm;
+
+ status = read_sw_int_status0(mac);
+ if (unlikely(status == 0)) {
+ netdev_err(ndev, "Interrput status is null!\n");
+ goto OUT;
+ }
+ write_sw_int_status0(mac, status);
+
+ if (status & MAC_INT_RX) {
+ // Disable RX interrupts.
+ write_sw_int_mask0(mac, read_sw_int_mask0(mac) | MAC_INT_RX);
+
+ if (unlikely(status & MAC_INT_RX_DES_ERR)) {
+ netdev_err(ndev, "Illegal RX Descriptor!\n");
+ mac->dev_stats.rx_fifo_errors++;
+ }
+ if (napi_schedule_prep(&comm->rx_napi))
+ __napi_schedule(&comm->rx_napi);
+ }
+
+ if (status & MAC_INT_TX) {
+ // Disable TX interrupts.
+ write_sw_int_mask0(mac, read_sw_int_mask0(mac) | MAC_INT_TX);
+
+ if (unlikely(status & MAC_INT_TX_DES_ERR)) {
+ netdev_err(ndev, "Illegal TX Descriptor Error\n");
+ mac->dev_stats.tx_fifo_errors++;
+ mac_soft_reset(mac);
+ wmb(); // make sure settings are effective.
+ write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_TX);
+ } else {
+ if (napi_schedule_prep(&comm->tx_napi))
+ __napi_schedule(&comm->tx_napi);
+ }
+ }
+
+ if (status & MAC_INT_PORT_ST_CHG) /* link status changed */
+ port_status_change(mac);
+
+OUT:
+ return IRQ_HANDLED;
+}
diff --git a/drivers/net/ethernet/sunplus/sp_int.h b/drivers/net/ethernet/sunplus/sp_int.h
new file mode 100644
index 0000000..7de8df4
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_int.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_INT_H__
+#define __SP_INT_H__
+
+int rx_poll(struct napi_struct *napi, int budget);
+int tx_poll(struct napi_struct *napi, int budget);
+irqreturn_t ethernet_interrupt(int irq, void *dev_id);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_mac.c b/drivers/net/ethernet/sunplus/sp_mac.c
new file mode 100644
index 0000000..6a3dfb1
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_mac.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "sp_mac.h"
+
+void mac_init(struct sp_mac *mac)
+{
+ u32 i;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ mac->comm->rx_pos[i] = 0;
+ mb(); // make sure settings are effective.
+
+ hal_mac_init(mac);
+}
+
+void mac_soft_reset(struct sp_mac *mac)
+{
+ u32 i;
+ struct net_device *ndev2;
+
+ if (netif_carrier_ok(mac->ndev)) {
+ netif_carrier_off(mac->ndev);
+ netif_stop_queue(mac->ndev);
+ }
+
+ ndev2 = mac->next_ndev;
+ if (ndev2) {
+ if (netif_carrier_ok(ndev2)) {
+ netif_carrier_off(ndev2);
+ netif_stop_queue(ndev2);
+ }
+ }
+
+ hal_mac_reset(mac);
+ hal_mac_stop(mac);
+
+ rx_descs_flush(mac->comm);
+ mac->comm->tx_pos = 0;
+ mac->comm->tx_done_pos = 0;
+ mac->comm->tx_desc_full = 0;
+
+ for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
+ mac->comm->rx_pos[i] = 0;
+ mb(); // make sure settings are effective.
+
+ hal_mac_init(mac);
+ hal_mac_start(mac);
+
+ if (!netif_carrier_ok(mac->ndev)) {
+ netif_carrier_on(mac->ndev);
+ netif_start_queue(mac->ndev);
+ }
+
+ if (ndev2) {
+ if (!netif_carrier_ok(ndev2)) {
+ netif_carrier_on(ndev2);
+ netif_start_queue(ndev2);
+ }
+ }
+}
diff --git a/drivers/net/ethernet/sunplus/sp_mac.h b/drivers/net/ethernet/sunplus/sp_mac.h
new file mode 100644
index 0000000..f35f3b7
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_mac.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_MAC_H__
+#define __SP_MAC_H__
+
+#include "sp_define.h"
+#include "sp_hal.h"
+
+void mac_init(struct sp_mac *mac);
+void mac_soft_reset(struct sp_mac *mac);
+
+// Calculate the empty tx descriptor number
+#define TX_DESC_AVAIL(mac) \
+ (((mac)->tx_pos != (mac)->tx_done_pos) ? \
+ (((mac)->tx_done_pos < (mac)->tx_pos) ? \
+ (TX_DESC_NUM - ((mac)->tx_pos - (mac)->tx_done_pos)) : \
+ ((mac)->tx_done_pos - (mac)->tx_pos)) : \
+ ((mac)->tx_desc_full ? 0 : TX_DESC_NUM))
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_mdio.c b/drivers/net/ethernet/sunplus/sp_mdio.c
new file mode 100644
index 0000000..f6a7e64
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_mdio.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "sp_mdio.h"
+
+u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum)
+{
+ int ret;
+
+ ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
+ if (ret < 0)
+ return -EOPNOTSUPP;
+
+ return ret;
+}
+
+u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val)
+{
+ int ret;
+
+ ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
+ if (ret < 0)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int mii_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+ struct sp_mac *mac = bus->priv;
+
+ return mdio_read(mac, phy_id, regnum);
+}
+
+static int mii_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
+{
+ struct sp_mac *mac = bus->priv;
+
+ return mdio_write(mac, phy_id, regnum, val);
+}
+
+u32 mdio_init(struct platform_device *pdev, struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+ struct mii_bus *mii_bus;
+ struct device_node *mdio_node;
+ int ret;
+
+ mii_bus = mdiobus_alloc();
+ if (!mii_bus) {
+ netdev_err(ndev, "Failed to allocate mdio_bus memory!\n");
+ return -ENOMEM;
+ }
+
+ mii_bus->name = "sunplus_mii_bus";
+ mii_bus->parent = &pdev->dev;
+ mii_bus->priv = mac;
+ mii_bus->read = mii_read;
+ mii_bus->write = mii_write;
+ snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
+
+ mdio_node = of_get_parent(mac->phy_node);
+ if (!mdio_node) {
+ netdev_err(ndev, "Failed to get mdio_node!\n");
+ return -ENODATA;
+ }
+
+ ret = of_mdiobus_register(mii_bus, mdio_node);
+ if (ret) {
+ netdev_err(ndev, "Failed to register mii bus!\n");
+ mdiobus_free(mii_bus);
+ return ret;
+ }
+
+ mac->comm->mii_bus = mii_bus;
+ return ret;
+}
+
+void mdio_remove(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ if (mac->comm->mii_bus) {
+ mdiobus_unregister(mac->comm->mii_bus);
+ mdiobus_free(mac->comm->mii_bus);
+ mac->comm->mii_bus = NULL;
+ }
+}
diff --git a/drivers/net/ethernet/sunplus/sp_mdio.h b/drivers/net/ethernet/sunplus/sp_mdio.h
new file mode 100644
index 0000000..d708624
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_mdio.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_MDIO_H__
+#define __SP_MDIO_H__
+
+#include "sp_define.h"
+#include "sp_hal.h"
+
+#define MDIO_READ_CMD 0x02
+#define MDIO_WRITE_CMD 0x01
+
+u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum);
+u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val);
+u32 mdio_init(struct platform_device *pdev, struct net_device *ndev);
+void mdio_remove(struct net_device *ndev);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_phy.c b/drivers/net/ethernet/sunplus/sp_phy.c
new file mode 100644
index 0000000..df6df3a
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_phy.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "sp_phy.h"
+#include "sp_mdio.h"
+
+static void mii_linkchange(struct net_device *netdev)
+{
+}
+
+int sp_phy_probe(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+ struct phy_device *phydev;
+ int i;
+
+ phydev = of_phy_connect(ndev, mac->phy_node, mii_linkchange,
+ 0, mac->phy_mode);
+ if (!phydev) {
+ netdev_err(ndev, "\"%s\" failed to connect to phy!\n", ndev->name);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < sizeof(phydev->supported) / sizeof(long); i++)
+ phydev->advertising[i] = phydev->supported[i];
+
+ phydev->irq = PHY_MAC_INTERRUPT;
+ mac->phy_dev = phydev;
+
+ // Bug workaround:
+ // Flow-control of phy should be enabled. MAC flow-control will refer
+ // to the bit to decide to enable or disable flow-control.
+ mdio_write(mac, mac->phy_addr, 4, mdio_read(mac, mac->phy_addr, 4) | (1 << 10));
+
+ return 0;
+}
+
+void sp_phy_start(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ if (mac->phy_dev)
+ phy_start(mac->phy_dev);
+}
+
+void sp_phy_stop(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ if (mac->phy_dev)
+ phy_stop(mac->phy_dev);
+}
+
+void sp_phy_remove(struct net_device *ndev)
+{
+ struct sp_mac *mac = netdev_priv(ndev);
+
+ if (mac->phy_dev) {
+ phy_disconnect(mac->phy_dev);
+ mac->phy_dev = NULL;
+ }
+}
diff --git a/drivers/net/ethernet/sunplus/sp_phy.h b/drivers/net/ethernet/sunplus/sp_phy.h
new file mode 100644
index 0000000..4ae0351
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_phy.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_PHY_H__
+#define __SP_PHY_H__
+
+#include "sp_define.h"
+
+int sp_phy_probe(struct net_device *netdev);
+void sp_phy_start(struct net_device *netdev);
+void sp_phy_stop(struct net_device *netdev);
+void sp_phy_remove(struct net_device *netdev);
+
+#endif
diff --git a/drivers/net/ethernet/sunplus/sp_register.h b/drivers/net/ethernet/sunplus/sp_register.h
new file mode 100644
index 0000000..690c0dd
--- /dev/null
+++ b/drivers/net/ethernet/sunplus/sp_register.h
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __SP_REGISTER_H__
+#define __SP_REGISTER_H__
+
+#include "sp_define.h"
+
+/* TYPE: RegisterFile_L2SW */
+#define SP_SW_INT_STATUS_0 0x0
+#define SP_SW_INT_MASK_0 0x4
+#define SP_FL_CNTL_TH 0x8
+#define SP_CPU_FL_CNTL_TH 0xc
+#define SP_PRI_FL_CNTL 0x10
+#define SP_VLAN_PRI_TH 0x14
+#define SP_EN_TOS_BUS 0x18
+#define SP_TOS_MAP0 0x1c
+#define SP_TOS_MAP1 0x20
+#define SP_TOS_MAP2 0x24
+#define SP_TOS_MAP3 0x28
+#define SP_TOS_MAP4 0x2c
+#define SP_TOS_MAP5 0x30
+#define SP_TOS_MAP6 0x34
+#define SP_TOS_MAP7 0x38
+#define SP_GLOBAL_QUE_STATUS 0x3c
+#define SP_ADDR_TBL_SRCH 0x40
+#define SP_ADDR_TBL_ST 0x44
+#define SP_MAC_AD_SER0 0x48
+#define SP_MAC_AD_SER1 0x4c
+#define SP_WT_MAC_AD0 0x50
+#define SP_W_MAC_15_0 0x54
+#define SP_W_MAC_47_16 0x58
+#define SP_PVID_CONFIG0 0x5c
+#define SP_PVID_CONFIG1 0x60
+#define SP_VLAN_MEMSET_CONFIG0 0x64
+#define SP_VLAN_MEMSET_CONFIG1 0x68
+#define SP_PORT_ABILITY 0x6c
+#define SP_PORT_ST 0x70
+#define SP_CPU_CNTL 0x74
+#define SP_PORT_CNTL0 0x78
+#define SP_PORT_CNTL1 0x7c
+#define SP_PORT_CNTL2 0x80
+#define SP_SW_GLB_CNTL 0x84
+#define SP_SP_SW_RESET 0x88
+#define SP_LED_PORT0 0x8c
+#define SP_LED_PORT1 0x90
+#define SP_LED_PORT2 0x94
+#define SP_LED_PORT3 0x98
+#define SP_LED_PORT4 0x9c
+#define SP_WATCH_DOG_TRIG_RST 0xa0
+#define SP_WATCH_DOG_STOP_CPU 0xa4
+#define SP_PHY_CNTL_REG0 0xa8
+#define SP_PHY_CNTL_REG1 0xac
+#define SP_MAC_FORCE_MODE 0xb0
+#define SP_VLAN_GROUP_CONFIG0 0xb4
+#define SP_VLAN_GROUP_CONFIG1 0xb8
+#define SP_FLOW_CTRL_TH3 0xbc
+#define SP_QUEUE_STATUS_0 0xc0
+#define SP_DEBUG_CNTL 0xc4
+#define SP_RESERVED_1 0xc8
+#define SP_MEM_TEST_INFO 0xcc
+#define SP_SW_INT_STATUS_1 0xd0
+#define SP_SW_INT_MASK_1 0xd4
+#define SP_SW_GLOBAL_SIGNAL 0xd8
+
+#define SP_CPU_TX_TRIG 0x208
+#define SP_TX_HBASE_ADDR_0 0x20c
+#define SP_TX_LBASE_ADDR_0 0x210
+#define SP_RX_HBASE_ADDR_0 0x214
+#define SP_RX_LBASE_ADDR_0 0x218
+#define SP_TX_HW_ADDR_0 0x21c
+#define SP_TX_LW_ADDR_0 0x220
+#define SP_RX_HW_ADDR_0 0x224
+#define SP_RX_LW_ADDR_0 0x228
+#define SP_CPU_PORT_CNTL_REG_0 0x22c
+#define SP_TX_HBASE_ADDR_1 0x230
+#define SP_TX_LBASE_ADDR_1 0x234
+#define SP_RX_HBASE_ADDR_1 0x238
+#define SP_RX_LBASE_ADDR_1 0x23c
+#define SP_TX_HW_ADDR_1 0x240
+#define SP_TX_LW_ADDR_1 0x244
+#define SP_RX_HW_ADDR_1 0x248
+#define SP_RX_LW_ADDR_1 0x24c
+#define SP_CPU_PORT_CNTL_REG_1 0x250
+
+/* TYPE: RegisterFile_MOON5 */
+#define MOON5_MO5_THERMAL_CTL_0 0x0
+#define MOON5_MO5_THERMAL_CTL_1 0x4
+#define MOON5_MO4_THERMAL_CTL_2 0xc
+#define MOON5_MO4_THERMAL_CTL_3 0x8
+#define MOON5_MO4_TMDS_L2SW_CTL 0x10
+#define MOON5_MO4_L2SW_CLKSW_CTL 0x14
+
+#endif
--
2.7.4


2021-11-11 11:31:56

by Denis Kirjanov

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021



11/11/21 12:04 PM, Wells Lu пишет:
> Add driver for Sunplus SP7021.
>
> Signed-off-by: Wells Lu <[email protected]>
> ---
> Changes in V2
> - Addressed all comments from Mr. Philipp Zabel.
> - Revised probe function.
> - Addressed all comments from Mr. Randy Dunlap.
> - Revised Kconfig
> - Addressed all comments from Mr. Andrew Lunn.
> - Removed daisy-chain (hub) function. Only keep dual NIC mode.
> - Removed dynamic mode-switching function using sysfs.
> - Removed unnecessary wmb().
> - Replaced prefix l2sw_ with sp_ for struct, funciton and file names.
> - Modified ethernet_do_ioctl() function.
> - Removed ethernet_do_change_mtu() function.
> - Revised Kconfig. Added '|| COMPILE_TEST'
> - Others
> - Replaced HWREG_R() and HWREG_W() macro with readl() and writel().
> - Created new file sp_phy.c/sp_phy.h and moved phy-related functions in.
> - Revised function name in sp_hal.c/.h to add hal_ prefix.
>
> MAINTAINERS | 1 +
> drivers/net/ethernet/Kconfig | 1 +
> drivers/net/ethernet/Makefile | 1 +
> drivers/net/ethernet/sunplus/Kconfig | 36 ++
> drivers/net/ethernet/sunplus/Makefile | 6 +
> drivers/net/ethernet/sunplus/sp_define.h | 212 ++++++++++
> drivers/net/ethernet/sunplus/sp_desc.c | 231 +++++++++++
> drivers/net/ethernet/sunplus/sp_desc.h | 21 +
> drivers/net/ethernet/sunplus/sp_driver.c | 606 +++++++++++++++++++++++++++++
> drivers/net/ethernet/sunplus/sp_driver.h | 23 ++
> drivers/net/ethernet/sunplus/sp_hal.c | 331 ++++++++++++++++
> drivers/net/ethernet/sunplus/sp_hal.h | 31 ++
> drivers/net/ethernet/sunplus/sp_int.c | 286 ++++++++++++++
> drivers/net/ethernet/sunplus/sp_int.h | 13 +
> drivers/net/ethernet/sunplus/sp_mac.c | 63 +++
> drivers/net/ethernet/sunplus/sp_mac.h | 23 ++
> drivers/net/ethernet/sunplus/sp_mdio.c | 90 +++++
> drivers/net/ethernet/sunplus/sp_mdio.h | 20 +
> drivers/net/ethernet/sunplus/sp_phy.c | 64 +++
> drivers/net/ethernet/sunplus/sp_phy.h | 16 +
> drivers/net/ethernet/sunplus/sp_register.h | 96 +++++
> 21 files changed, 2171 insertions(+)
> create mode 100644 drivers/net/ethernet/sunplus/Kconfig
> create mode 100644 drivers/net/ethernet/sunplus/Makefile
> create mode 100644 drivers/net/ethernet/sunplus/sp_define.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_desc.c
> create mode 100644 drivers/net/ethernet/sunplus/sp_desc.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_driver.c
> create mode 100644 drivers/net/ethernet/sunplus/sp_driver.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_hal.c
> create mode 100644 drivers/net/ethernet/sunplus/sp_hal.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_int.c
> create mode 100644 drivers/net/ethernet/sunplus/sp_int.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_mac.c
> create mode 100644 drivers/net/ethernet/sunplus/sp_mac.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.c
> create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_phy.c
> create mode 100644 drivers/net/ethernet/sunplus/sp_phy.h
> create mode 100644 drivers/net/ethernet/sunplus/sp_register.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 737b9d0..ec1ddb1 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18006,6 +18006,7 @@ L: [email protected]
> S: Maintained
> W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> F: Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
> +F: drivers/net/ethernet/sunplus/
>
> SUPERH
> M: Yoshinori Sato <[email protected]>
> diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
> index 412ae3e..0084852 100644
> --- a/drivers/net/ethernet/Kconfig
> +++ b/drivers/net/ethernet/Kconfig
> @@ -176,6 +176,7 @@ source "drivers/net/ethernet/smsc/Kconfig"
> source "drivers/net/ethernet/socionext/Kconfig"
> source "drivers/net/ethernet/stmicro/Kconfig"
> source "drivers/net/ethernet/sun/Kconfig"
> +source "drivers/net/ethernet/sunplus/Kconfig"
> source "drivers/net/ethernet/synopsys/Kconfig"
> source "drivers/net/ethernet/tehuti/Kconfig"
> source "drivers/net/ethernet/ti/Kconfig"
> diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
> index aaa5078..e4ce162 100644
> --- a/drivers/net/ethernet/Makefile
> +++ b/drivers/net/ethernet/Makefile
> @@ -87,6 +87,7 @@ obj-$(CONFIG_NET_VENDOR_SMSC) += smsc/
> obj-$(CONFIG_NET_VENDOR_SOCIONEXT) += socionext/
> obj-$(CONFIG_NET_VENDOR_STMICRO) += stmicro/
> obj-$(CONFIG_NET_VENDOR_SUN) += sun/
> +obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sunplus/
> obj-$(CONFIG_NET_VENDOR_TEHUTI) += tehuti/
> obj-$(CONFIG_NET_VENDOR_TI) += ti/
> obj-$(CONFIG_NET_VENDOR_TOSHIBA) += toshiba/
> diff --git a/drivers/net/ethernet/sunplus/Kconfig b/drivers/net/ethernet/sunplus/Kconfig
> new file mode 100644
> index 0000000..5af2c5b
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/Kconfig
> @@ -0,0 +1,36 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Sunplus network device configuration
> +#
> +
> +config NET_VENDOR_SUNPLUS
> + bool "Sunplus devices"
> + default y
> + depends on ARCH_SUNPLUS || COMPILE_TEST
> + help
> + If you have a network (Ethernet) card belonging to this
> + class, say Y here.
> +
> + Note that the answer to this question doesn't directly
> + affect the kernel: saying N will just cause the configurator
> + to skip all the questions about Sunplus cards. If you say Y,
> + you will be asked for your specific card in the following
> + questions.
> +
> +if NET_VENDOR_SUNPLUS
> +
> +config SP7021_EMAC
> + tristate "Sunplus Dual 10M/100M Ethernet devices"
> + depends on SOC_SP7021 || COMPILE_TEST
> + select PHYLIB
> + select PINCTRL_SPPCTL
> + select COMMON_CLK_SP7021
> + select RESET_SUNPLUS
> + select NVMEM_SUNPLUS_OCOTP
> + help
> + If you have Sunplus dual 10M/100M Ethernet devices, say Y.
> + The network device creates two net-device interfaces.
> + To compile this driver as a module, choose M here. The
> + module will be called sp7021_emac.
> +
> +endif # NET_VENDOR_SUNPLUS
> diff --git a/drivers/net/ethernet/sunplus/Makefile b/drivers/net/ethernet/sunplus/Makefile
> new file mode 100644
> index 0000000..963ba1d
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for the Sunplus network device drivers.
> +#
> +obj-$(CONFIG_SP7021_EMAC) += sp7021_emac.o
> +sp7021_emac-objs := sp_driver.o sp_int.o sp_hal.o sp_desc.o sp_mac.o sp_mdio.o sp_phy.o
> diff --git a/drivers/net/ethernet/sunplus/sp_define.h b/drivers/net/ethernet/sunplus/sp_define.h
> new file mode 100644
> index 0000000..40e15ba
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_define.h
> @@ -0,0 +1,212 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_DEFINE_H__
> +#define __SP_DEFINE_H__
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/sched.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/errno.h>
> +#include <linux/types.h>
> +#include <linux/interrupt.h>
> +#include <linux/kdev_t.h>
> +#include <linux/in.h>
> +#include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
> +#include <linux/ip.h>
> +#include <linux/tcp.h>
> +#include <linux/skbuff.h>
> +#include <linux/ethtool.h>
> +#include <linux/platform_device.h>
> +#include <linux/phy.h>
> +#include <linux/mii.h>
> +#include <linux/if_vlan.h>
> +#include <linux/io.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/of_address.h>
> +#include <linux/of_mdio.h>
> +
> +//define MAC interrupt status bit
please embrace all comments with /* */
> +#define MAC_INT_DAISY_MODE_CHG BIT(31)
> +#define MAC_INT_IP_CHKSUM_ERR BIT(23)
> +#define MAC_INT_WDOG_TIMER1_EXP BIT(22)
> +#define MAC_INT_WDOG_TIMER0_EXP BIT(21)
> +#define MAC_INT_INTRUDER_ALERT BIT(20)
> +#define MAC_INT_PORT_ST_CHG BIT(19)
> +#define MAC_INT_BC_STORM BIT(18)
> +#define MAC_INT_MUST_DROP_LAN BIT(17)
> +#define MAC_INT_GLOBAL_QUE_FULL BIT(16)
> +#define MAC_INT_TX_SOC_PAUSE_ON BIT(15)
> +#define MAC_INT_RX_SOC_QUE_FULL BIT(14)
> +#define MAC_INT_TX_LAN1_QUE_FULL BIT(9)
> +#define MAC_INT_TX_LAN0_QUE_FULL BIT(8)
> +#define MAC_INT_RX_L_DESCF BIT(7)
> +#define MAC_INT_RX_H_DESCF BIT(6)
> +#define MAC_INT_RX_DONE_L BIT(5)
> +#define MAC_INT_RX_DONE_H BIT(4)
> +#define MAC_INT_TX_DONE_L BIT(3)
> +#define MAC_INT_TX_DONE_H BIT(2)
> +#define MAC_INT_TX_DES_ERR BIT(1)
> +#define MAC_INT_RX_DES_ERR BIT(0)
> +
> +#define MAC_INT_RX (MAC_INT_RX_DONE_H | MAC_INT_RX_DONE_L | \
> + MAC_INT_RX_DES_ERR)
> +#define MAC_INT_TX (MAC_INT_TX_DONE_L | MAC_INT_TX_DONE_H | \
> + MAC_INT_TX_DES_ERR)
> +#define MAC_INT_MASK_DEF (MAC_INT_DAISY_MODE_CHG | MAC_INT_IP_CHKSUM_ERR | \
> + MAC_INT_WDOG_TIMER1_EXP | MAC_INT_WDOG_TIMER0_EXP | \
> + MAC_INT_INTRUDER_ALERT | MAC_INT_BC_STORM | \
> + MAC_INT_MUST_DROP_LAN | MAC_INT_GLOBAL_QUE_FULL | \
> + MAC_INT_TX_SOC_PAUSE_ON | MAC_INT_RX_SOC_QUE_FULL | \
> + MAC_INT_TX_LAN1_QUE_FULL | MAC_INT_TX_LAN0_QUE_FULL | \
> + MAC_INT_RX_L_DESCF | MAC_INT_RX_H_DESCF)
> +
> +/*define port ability*/
> +#define PORT_ABILITY_LINK_ST_P1 BIT(25)
> +#define PORT_ABILITY_LINK_ST_P0 BIT(24)
> +
> +/*define PHY command bit*/
> +#define PHY_WT_DATA_MASK 0xffff0000
> +#define PHY_RD_CMD 0x00004000
> +#define PHY_WT_CMD 0x00002000
> +#define PHY_REG_MASK 0x00001f00
> +#define PHY_ADR_MASK 0x0000001f
> +
> +/*define PHY status bit*/
> +#define PHY_RD_DATA_MASK 0xffff0000
> +#define PHY_RD_RDY BIT(1)
> +#define PHY_WT_DONE BIT(0)
> +
> +/*define other register bit*/
> +#define RX_MAX_LEN_MASK 0x00011000
> +#define ROUTE_MODE_MASK 0x00000060
> +#define POK_INT_THS_MASK 0x000E0000
> +#define VLAN_TH_MASK 0x00000007
> +
> +/*define tx descriptor bit*/
> +#define OWN_BIT BIT(31)
> +#define FS_BIT BIT(25)
> +#define LS_BIT BIT(24)
> +#define LEN_MASK 0x000007FF
> +#define PKTSP_MASK 0x00007000
> +#define PKTSP_PORT1 0x00001000
> +#define TO_VLAN_MASK 0x0003F000
> +#define TO_VLAN_GROUP1 0x00002000
> +
> +#define EOR_BIT BIT(31)
> +
> +/*define rx descriptor bit*/
> +#define ERR_CODE (0xf << 26)
> +#define RX_TCP_UDP_CHKSUM_BIT BIT(23)
> +#define RX_IP_CHKSUM_BIT BIT(18)
> +
> +#define OWC_BIT BIT(31)
> +#define TXOK_BIT BIT(26)
> +#define LNKF_BIT BIT(25)
> +#define BUR_BIT BIT(22)
> +#define TWDE_BIT BIT(20)
> +#define CC_MASK 0x000f0000
> +#define TBE_MASK 0x00070000
> +
> +// Address table search
> +#define MAC_ADDR_LOOKUP_IDLE BIT(2)
> +#define MAC_SEARCH_NEXT_ADDR BIT(1)
> +#define MAC_BEGIN_SEARCH_ADDR BIT(0)
> +
> +// Address table search
> +#define MAC_HASK_LOOKUP_ADDR_MASK (0x3ff << 22)
> +#define MAC_AT_TABLE_END BIT(1)
> +#define MAC_AT_DATA_READY BIT(0)
> +
> +/*config descriptor*/
> +#define TX_DESC_NUM 16
> +#define MAC_GUARD_DESC_NUM 2
> +#define RX_QUEUE0_DESC_NUM 16
> +#define RX_QUEUE1_DESC_NUM 16
> +#define TX_DESC_QUEUE_NUM 1
> +#define RX_DESC_QUEUE_NUM 2
> +
> +#define MAC_TX_BUFF_SIZE 1536
> +#define MAC_RX_LEN_MAX 2047
> +
> +#define DESC_ALIGN_BYTE 32
> +#define RX_OFFSET 0
> +#define TX_OFFSET 0
> +
> +#define ETHERNET_MAC_ADDR_LEN 6
> +
> +struct mac_desc {
> + u32 cmd1;
> + u32 cmd2;
> + u32 addr1;
> + u32 addr2;
> +};
> +
> +struct skb_info {
> + struct sk_buff *skb;
> + u32 mapping;
> + u32 len;
> +};
> +
> +struct sp_common {
> + void __iomem *sp_reg_base;
> + void __iomem *moon5_reg_base;
> +
> + struct net_device *ndev;
> + struct platform_device *pdev;
> +
> + void *desc_base;
> + dma_addr_t desc_dma;
> + s32 desc_size;
> + struct clk *clk;
> + struct reset_control *rstc;
> + int irq;
> +
> + struct mac_desc *rx_desc[RX_DESC_QUEUE_NUM];
> + struct skb_info *rx_skb_info[RX_DESC_QUEUE_NUM];
> + u32 rx_pos[RX_DESC_QUEUE_NUM];
> + u32 rx_desc_num[RX_DESC_QUEUE_NUM];
> + u32 rx_desc_buff_size;
> +
> + struct mac_desc *tx_desc;
> + struct skb_info tx_temp_skb_info[TX_DESC_NUM];
> + u32 tx_done_pos;
> + u32 tx_pos;
> + u32 tx_desc_full;
> +
> + struct mii_bus *mii_bus;
> +
> + struct napi_struct rx_napi;
> + struct napi_struct tx_napi;
> +
> + spinlock_t rx_lock; // spinlock for accessing rx buffer
> + spinlock_t tx_lock; // spinlock for accessing tx buffer
> + spinlock_t ioctl_lock; // spinlock for ioctl operations
> +
> + u8 enable;
> +};
> +
> +struct sp_mac {
> + struct net_device *ndev;
> + struct net_device *next_ndev;
> + struct phy_device *phy_dev;
> + struct sp_common *comm;
> + struct net_device_stats dev_stats;
> + struct device_node *phy_node;
> + phy_interface_t phy_mode;
> + u32 phy_addr;
> +
> + u8 mac_addr[ETHERNET_MAC_ADDR_LEN];
> +
> + u8 lan_port;
> + u8 to_vlan;
> + u8 cpu_port;
> + u8 vlan_id;
> +};
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_desc.c b/drivers/net/ethernet/sunplus/sp_desc.c
> new file mode 100644
> index 0000000..fed91ec
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_desc.c
> @@ -0,0 +1,231 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "sp_desc.h"
> +#include "sp_define.h"
> +
> +void rx_descs_flush(struct sp_common *comm)
> +{
> + u32 i, j;
> + struct mac_desc *rx_desc;
> + struct skb_info *rx_skbinfo;
> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> + rx_desc = comm->rx_desc[i];
> + rx_skbinfo = comm->rx_skb_info[i];
> + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> + rx_desc[j].addr1 = rx_skbinfo[j].mapping;
> + rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
> + EOR_BIT | comm->rx_desc_buff_size :
> + comm->rx_desc_buff_size;
> + wmb(); // Set OWN_BIT after other fields are ready.
> + rx_desc[j].cmd1 = OWN_BIT;
> + }
> + }
> +}
> +
> +void tx_descs_clean(struct sp_common *comm)
> +{
> + u32 i;
> + s32 buflen;
> +
> + if (!comm->tx_desc)
> + return;
> +
> + for (i = 0; i < TX_DESC_NUM; i++) {
> + comm->tx_desc[i].cmd1 = 0;
> + wmb(); // Clear OWN_BIT and then set other fields.
> + comm->tx_desc[i].cmd2 = 0;
> + comm->tx_desc[i].addr1 = 0;
> + comm->tx_desc[i].addr2 = 0;
> +
> + if (comm->tx_temp_skb_info[i].mapping) {
> + buflen = (comm->tx_temp_skb_info[i].skb) ?
> + comm->tx_temp_skb_info[i].skb->len :
> + MAC_TX_BUFF_SIZE;
> + dma_unmap_single(&comm->pdev->dev, comm->tx_temp_skb_info[i].mapping,
> + buflen, DMA_TO_DEVICE);
> + comm->tx_temp_skb_info[i].mapping = 0;
> + }
> +
> + if (comm->tx_temp_skb_info[i].skb) {
> + dev_kfree_skb(comm->tx_temp_skb_info[i].skb);
> + comm->tx_temp_skb_info[i].skb = NULL;
> + }
> + }
> +}
> +
> +void rx_descs_clean(struct sp_common *comm)
> +{
> + u32 i, j;
> + struct mac_desc *rx_desc;
> + struct skb_info *rx_skbinfo;
> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> + if (!comm->rx_skb_info[i])
> + continue;
> +
> + rx_desc = comm->rx_desc[i];
> + rx_skbinfo = comm->rx_skb_info[i];
> + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> + rx_desc[j].cmd1 = 0;
> + wmb(); // Clear OWN_BIT and then set other fields.
> + rx_desc[j].cmd2 = 0;
> + rx_desc[j].addr1 = 0;
> +
> + if (rx_skbinfo[j].skb) {
> + dma_unmap_single(&comm->pdev->dev, rx_skbinfo[j].mapping,
> + comm->rx_desc_buff_size, DMA_FROM_DEVICE);
> + dev_kfree_skb(rx_skbinfo[j].skb);
> + rx_skbinfo[j].skb = NULL;
> + rx_skbinfo[j].mapping = 0;
> + }
> + }
> +
> + kfree(rx_skbinfo);
> + comm->rx_skb_info[i] = NULL;
> + }
> +}
> +
> +void descs_clean(struct sp_common *comm)
> +{
> + rx_descs_clean(comm);
> + tx_descs_clean(comm);
> +}
> +
> +void descs_free(struct sp_common *comm)
> +{
> + u32 i;
> +
> + descs_clean(comm);
> + comm->tx_desc = NULL;
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> + comm->rx_desc[i] = NULL;
> +
> + /* Free descriptor area */
> + if (comm->desc_base) {
> + dma_free_coherent(&comm->pdev->dev, comm->desc_size, comm->desc_base,
> + comm->desc_dma);
> + comm->desc_base = NULL;
> + comm->desc_dma = 0;
> + comm->desc_size = 0;
> + }
> +}
> +
> +void tx_descs_init(struct sp_common *comm)
> +{
> + memset(comm->tx_desc, '\0', sizeof(struct mac_desc) *
> + (TX_DESC_NUM + MAC_GUARD_DESC_NUM));
> +}
> +
> +int rx_descs_init(struct sp_common *comm)
> +{
> + struct sk_buff *skb;
> + u32 i, j;
> + struct mac_desc *rx_desc;
> + struct skb_info *rx_skbinfo;
> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> + comm->rx_skb_info[i] = kmalloc_array(comm->rx_desc_num[i],
> + sizeof(struct skb_info), GFP_KERNEL);
> + if (!comm->rx_skb_info[i])
> + goto MEM_ALLOC_FAIL;
> +
> + rx_skbinfo = comm->rx_skb_info[i];
> + rx_desc = comm->rx_desc[i];
> + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> + skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
> + GFP_ATOMIC | GFP_DMA);
> + if (!skb)
> + goto MEM_ALLOC_FAIL;
> +
> + skb->dev = comm->ndev;
> + skb_reserve(skb, RX_OFFSET); /* +data +tail */
> +
> + rx_skbinfo[j].skb = skb;
> + rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
> + comm->rx_desc_buff_size,
> + DMA_FROM_DEVICE);
it may fail
> + rx_desc[j].addr1 = rx_skbinfo[j].mapping;
> + rx_desc[j].addr2 = 0;
> + rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
> + EOR_BIT | comm->rx_desc_buff_size :
> + comm->rx_desc_buff_size;
> + wmb(); // Set OWN_BIT after other fields are effective.
> + rx_desc[j].cmd1 = OWN_BIT;
> + }
> + }
> +
> + return 0;
> +
> +MEM_ALLOC_FAIL:
lowercase
> + rx_descs_clean(comm);
> + return -ENOMEM;
> +}
> +
> +int descs_alloc(struct sp_common *comm)
> +{
> + u32 i;
> + s32 desc_size;
> +
> + /* Alloc descriptor area */
> + desc_size = (TX_DESC_NUM + MAC_GUARD_DESC_NUM) * sizeof(struct mac_desc);
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> + desc_size += comm->rx_desc_num[i] * sizeof(struct mac_desc);
> +
> + comm->desc_base = dma_alloc_coherent(&comm->pdev->dev, desc_size, &comm->desc_dma,
> + GFP_KERNEL);
> + if (!comm->desc_base)
> + return -ENOMEM;
> +
> + comm->desc_size = desc_size;
> +
> + /* Setup Tx descriptor */
> + comm->tx_desc = (struct mac_desc *)comm->desc_base;
> +
> + /* Setup Rx descriptor */
> + comm->rx_desc[0] = &comm->tx_desc[TX_DESC_NUM + MAC_GUARD_DESC_NUM];
> + for (i = 1; i < RX_DESC_QUEUE_NUM; i++)
> + comm->rx_desc[i] = comm->rx_desc[i - 1] + comm->rx_desc_num[i - 1];
> +
> + return 0;
> +}
> +
> +int descs_init(struct sp_common *comm)
> +{
> + u32 i, ret;
> +
> + // Initialize rx descriptor's data
> + comm->rx_desc_num[0] = RX_QUEUE0_DESC_NUM;
> +#if RX_DESC_QUEUE_NUM > 1
> + comm->rx_desc_num[1] = RX_QUEUE1_DESC_NUM;
> +#endif
> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> + comm->rx_desc[i] = NULL;
> + comm->rx_skb_info[i] = NULL;
> + comm->rx_pos[i] = 0;
> + }
> + comm->rx_desc_buff_size = MAC_RX_LEN_MAX;
> +
> + // Initialize tx descriptor's data
> + comm->tx_done_pos = 0;
> + comm->tx_desc = NULL;
> + comm->tx_pos = 0;
> + comm->tx_desc_full = 0;
> + for (i = 0; i < TX_DESC_NUM; i++)
> + comm->tx_temp_skb_info[i].skb = NULL;
> +
> + // Allocate tx & rx descriptors.
> + ret = descs_alloc(comm);
> + if (ret) {
> + netdev_err(comm->ndev, "Failed to allocate tx & rx descriptors!\n");
> + return ret;
> + }
> +
> + tx_descs_init(comm);
> +
> + return rx_descs_init(comm);
> +}
> diff --git a/drivers/net/ethernet/sunplus/sp_desc.h b/drivers/net/ethernet/sunplus/sp_desc.h
> new file mode 100644
> index 0000000..20f7519
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_desc.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_DESC_H__
> +#define __SP_DESC_H__
> +
> +#include "sp_define.h"
> +
> +void rx_descs_flush(struct sp_common *comm);
> +void tx_descs_clean(struct sp_common *comm);
> +void rx_descs_clean(struct sp_common *comm);
> +void descs_clean(struct sp_common *comm);
> +void descs_free(struct sp_common *comm);
> +void tx_descs_init(struct sp_common *comm);
> +int rx_descs_init(struct sp_common *comm);
> +int descs_alloc(struct sp_common *comm);
> +int descs_init(struct sp_common *comm);
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_driver.c b/drivers/net/ethernet/sunplus/sp_driver.c
> new file mode 100644
> index 0000000..a1c76b9
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_driver.c
> @@ -0,0 +1,606 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/reset.h>
> +#include <linux/nvmem-consumer.h>
> +#include <linux/of_net.h>
> +#include "sp_driver.h"
> +#include "sp_phy.h"
> +
> +static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
> + 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00
> +};
> +
> +/*********************************************************************
> + *
> + * net_device_ops
> + *
> + **********************************************************************/
> +static int ethernet_open(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + netdev_dbg(ndev, "Open port = %x\n", mac->lan_port);
> +
> + mac->comm->enable |= mac->lan_port;
> +
> + hal_mac_start(mac);
> + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~(MAC_INT_TX | MAC_INT_RX));
> +
> + netif_carrier_on(ndev);
> + if (netif_carrier_ok(ndev))
> + netif_start_queue(ndev);
> +
> + return 0;
> +}
> +
> +static int ethernet_stop(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + netif_stop_queue(ndev);
> + netif_carrier_off(ndev);
> +
> + mac->comm->enable &= ~mac->lan_port;
> +
> + hal_mac_stop(mac);
> +
> + return 0;
> +}
> +
> +/* Transmit a packet (called by the kernel) */
> +static int ethernet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> + struct sp_common *comm = mac->comm;
> + u32 tx_pos;
> + u32 cmd1;
> + u32 cmd2;
> + struct mac_desc *txdesc;
> + struct skb_info *skbinfo;
> + unsigned long flags;
> +
> + if (unlikely(comm->tx_desc_full == 1)) {
> + // No TX descriptors left. Wait for tx interrupt.
> + netdev_info(ndev, "TX descriptor queue full when xmit!\n");
> + return NETDEV_TX_BUSY;
Do you really have to return NETDEV_TX_BUSY?
> + }
> +
> + /* if skb size shorter than 60, fill it with '\0' */
> + if (unlikely(skb->len < ETH_ZLEN)) {
> + if (skb_tailroom(skb) >= (ETH_ZLEN - skb->len)) {
> + memset(__skb_put(skb, ETH_ZLEN - skb->len), '\0',
> + ETH_ZLEN - skb->len);
> + } else {
> + struct sk_buff *old_skb = skb;
> +
> + skb = dev_alloc_skb(ETH_ZLEN + TX_OFFSET);
> + if (skb) {
> + memset(skb->data + old_skb->len, '\0',
> + ETH_ZLEN - old_skb->len);
> + memcpy(skb->data, old_skb->data, old_skb->len);
> + skb_put(skb, ETH_ZLEN); /* add data to an sk_buff */
> + dev_kfree_skb_irq(old_skb);
> + } else {
> + skb = old_skb;
> + }
> + }
> + }
> +
> + spin_lock_irqsave(&comm->tx_lock, flags);
> + tx_pos = comm->tx_pos;
> + txdesc = &comm->tx_desc[tx_pos];
> + skbinfo = &comm->tx_temp_skb_info[tx_pos];
> + skbinfo->len = skb->len;
> + skbinfo->skb = skb;
> + skbinfo->mapping = dma_map_single(&comm->pdev->dev, skb->data,
> + skb->len, DMA_TO_DEVICE);
it may fail
> + cmd1 = (OWN_BIT | FS_BIT | LS_BIT | (mac->to_vlan << 12) | (skb->len & LEN_MASK));
> + cmd2 = skb->len & LEN_MASK;
> +
> + if (tx_pos == (TX_DESC_NUM - 1))
> + cmd2 |= EOR_BIT;
> +
> + txdesc->addr1 = skbinfo->mapping;
> + txdesc->cmd2 = cmd2;
> + wmb(); // Set OWN_BIT after other fields of descriptor are effective.
> + txdesc->cmd1 = cmd1;
> +
> + NEXT_TX(tx_pos);
> +
> + if (unlikely(tx_pos == comm->tx_done_pos)) {
> + netif_stop_queue(ndev);
> + comm->tx_desc_full = 1;
> + }
> + comm->tx_pos = tx_pos;
> + wmb(); // make sure settings are effective.
> +
> + /* trigger gmac to transmit */
> + hal_tx_trigger(mac);
> +
> + spin_unlock_irqrestore(&mac->comm->tx_lock, flags);
> + return NETDEV_TX_OK;
> +}
> +
> +static void ethernet_set_rx_mode(struct net_device *ndev)
> +{
> + if (ndev) {
> + struct sp_mac *mac = netdev_priv(ndev);
> + struct sp_common *comm = mac->comm;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&comm->ioctl_lock, flags);
> + hal_rx_mode_set(ndev);
> + spin_unlock_irqrestore(&comm->ioctl_lock, flags);
> + }
> +}
> +
> +static int ethernet_set_mac_address(struct net_device *ndev, void *addr)
> +{
> + struct sockaddr *hwaddr = (struct sockaddr *)addr;
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (netif_running(ndev))
> + return -EBUSY;
> +
> + memcpy(ndev->dev_addr, hwaddr->sa_data, ndev->addr_len);
> +
> + /* Delete the old Ethernet MAC address */
> + netdev_dbg(ndev, "HW Addr = %pM\n", mac->mac_addr);
> + if (is_valid_ether_addr(mac->mac_addr))
> + hal_mac_addr_del(mac);
> +
> + /* Set the Ethernet MAC address */
> + memcpy(mac->mac_addr, hwaddr->sa_data, ndev->addr_len);
> + hal_mac_addr_set(mac);
> +
> + return 0;
> +}
> +
> +static int ethernet_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + switch (cmd) {
> + case SIOCGMIIPHY:
> + case SIOCGMIIREG:
> + case SIOCSMIIREG:
> + return phy_mii_ioctl(mac->phy_dev, ifr, cmd);
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static void ethernet_tx_timeout(struct net_device *ndev, unsigned int txqueue)
> +{
> +}
the empty function?
> +
> +static struct net_device_stats *ethernet_get_stats(struct net_device *ndev)
> +{
> + struct sp_mac *mac;
> +
> + mac = netdev_priv(ndev);
> + return &mac->dev_stats;
> +}
> +
> +static const struct net_device_ops netdev_ops = {
> + .ndo_open = ethernet_open,
> + .ndo_stop = ethernet_stop,
> + .ndo_start_xmit = ethernet_start_xmit,
> + .ndo_set_rx_mode = ethernet_set_rx_mode,
> + .ndo_set_mac_address = ethernet_set_mac_address,
> + .ndo_do_ioctl = ethernet_do_ioctl,
> + .ndo_tx_timeout = ethernet_tx_timeout,
> + .ndo_get_stats = ethernet_get_stats,
> +};
> +
> +char *sp7021_otp_read_mac(struct device *dev, ssize_t *len, char *name)
> +{
> + char *ret = NULL;
> + struct nvmem_cell *cell = nvmem_cell_get(dev, name);
> +
> + if (IS_ERR_OR_NULL(cell)) {
> + dev_err(dev, "OTP %s read failure: %ld", name, PTR_ERR(cell));
> + return NULL;
> + }
> +
> + ret = nvmem_cell_read(cell, len);
> + nvmem_cell_put(cell);
> + dev_dbg(dev, "%zd bytes are read from OTP %s.", *len, name);
> +
> + return ret;
> +}
> +
> +static void check_mac_vendor_id_and_convert(char *mac_addr)
> +{
> + // Byte order of MAC address of some samples are reversed.
> + // Check vendor id and convert byte order if it is wrong.
> + if ((mac_addr[5] == 0xFC) && (mac_addr[4] == 0x4B) && (mac_addr[3] == 0xBC) &&
> + ((mac_addr[0] != 0xFC) || (mac_addr[1] != 0x4B) || (mac_addr[2] != 0xBC))) {
> + char tmp;
> +
> + // Swap mac_addr[0] and mac_addr[5]
> + tmp = mac_addr[0];
> + mac_addr[0] = mac_addr[5];
> + mac_addr[5] = tmp;
> +
> + // Swap mac_addr[1] and mac_addr[4]
> + tmp = mac_addr[1];
> + mac_addr[1] = mac_addr[4];
> + mac_addr[4] = tmp;
> +
> + // Swap mac_addr[2] and mac_addr[3]
> + tmp = mac_addr[2];
> + mac_addr[2] = mac_addr[3];
> + mac_addr[3] = tmp;
> + }
> +}
> +
> +/*********************************************************************
> + *
> + * platform_driver
> + *
> + **********************************************************************/
> +static u32 init_netdev(struct platform_device *pdev, int eth_no, struct net_device **r_ndev)
> +{
> + struct sp_mac *mac;
> + struct net_device *ndev;
> + char *m_addr_name = (eth_no == 0) ? "mac_addr0" : "mac_addr1";
> + ssize_t otp_l = 0;
> + char *otp_v;
> + int ret;
> +
> + // Allocate the devices, and also allocate sp_mac, we can get it by netdev_priv().
> + ndev = alloc_etherdev(sizeof(*mac));
> + if (!ndev) {
> + *r_ndev = NULL;
> + return -ENOMEM;
please check the function return value
> + }
> + SET_NETDEV_DEV(ndev, &pdev->dev);
> + ndev->netdev_ops = &netdev_ops;
> +
> + mac = netdev_priv(ndev);
> + mac->ndev = ndev;
> + mac->next_ndev = NULL;
> +
> + // Get property 'mac-addr0' or 'mac-addr1' from dts.
> + otp_v = sp7021_otp_read_mac(&pdev->dev, &otp_l, m_addr_name);
> + if ((otp_l < 6) || IS_ERR_OR_NULL(otp_v)) {
> + dev_info(&pdev->dev, "OTP mac %s (len = %zd) is invalid, using default!\n",
> + m_addr_name, otp_l);
> + otp_l = 0;
> + } else {
> + // Check if mac-address is valid or not. If not, copy from default.
> + memcpy(mac->mac_addr, otp_v, 6);
> +
> + // Byte order of Some samples are reversed. Convert byte order here.
> + check_mac_vendor_id_and_convert(mac->mac_addr);
> +
> + if (!is_valid_ether_addr(mac->mac_addr)) {
> + dev_info(&pdev->dev, "Invalid mac in OTP[%s] = %pM, use default!\n",
> + m_addr_name, mac->mac_addr);
> + otp_l = 0;
> + }
> + }
> + if (otp_l != 6) {
> + memcpy(mac->mac_addr, def_mac_addr, ETHERNET_MAC_ADDR_LEN);
> + mac->mac_addr[5] += eth_no;
> + }
> +
> + dev_info(&pdev->dev, "HW Addr = %pM\n", mac->mac_addr);
> +
> + memcpy(ndev->dev_addr, mac->mac_addr, ETHERNET_MAC_ADDR_LEN);
> +
> + ret = register_netdev(ndev);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register net device \"%s\"!\n",
> + ndev->name);
> + free_netdev(ndev);
> + *r_ndev = NULL;
> + return ret;
> + }
> + netdev_info(ndev, "Registered net device \"%s\" successfully.\n", ndev->name);
> +
> + *r_ndev = ndev;
> + return 0;
> +}
> +
> +static int soc0_open(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 ret;
> +
> + hal_mac_stop(mac);
> +
> + ret = descs_init(comm);
> + if (ret) {
> + netdev_err(mac->ndev, "Fail to initialize mac descriptors!\n");
> + descs_free(comm);
> + return ret;
> + }
> +
> + mac_init(mac);
> + return 0;
> +}
> +
> +static int soc0_stop(struct sp_mac *mac)
> +{
> + hal_mac_stop(mac);
> +
> + descs_free(mac->comm);
> + return 0;
> +}
> +
> +static int sp_probe(struct platform_device *pdev)
> +{
> + struct sp_common *comm;
> + struct resource *rc;
> + struct net_device *ndev, *ndev2;
> + struct device_node *np;
> + struct sp_mac *mac, *mac2;
> + int ret;
> +
> + if (platform_get_drvdata(pdev))
> + return -ENODEV;
> +
> + // Allocate memory for 'sp_common' area.
> + comm = devm_kzalloc(&pdev->dev, sizeof(*comm), GFP_KERNEL);
> + if (!comm)
> + return -ENOMEM;
> + comm->pdev = pdev;
> +
> + spin_lock_init(&comm->rx_lock);
> + spin_lock_init(&comm->tx_lock);
> + spin_lock_init(&comm->ioctl_lock);
> +
> + // Get memory resoruce "emac" from dts.
> + rc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "emac");
> + if (!rc) {
> + dev_err(&pdev->dev, "No MEM resource \'emac\' found!\n");
> + return -ENXIO;
> + }
> + dev_dbg(&pdev->dev, "name = \"%s\", start = %pa\n", rc->name, &rc->start);
> +
> + comm->sp_reg_base = devm_ioremap_resource(&pdev->dev, rc);
> + if (IS_ERR(comm->sp_reg_base)) {
> + dev_err(&pdev->dev, "ioremap failed!\n");
> + return -ENOMEM;
> + }
> +
> + // Get memory resoruce "moon5" from dts.
> + rc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "moon5");
> + if (!rc) {
> + dev_err(&pdev->dev, "No MEM resource \'moon5\' found!\n");
> + return -ENXIO;
> + }
> + dev_dbg(&pdev->dev, "name = \"%s\", start = %pa\n", rc->name, &rc->start);
> +
> + // Note that moon5 is shared resource. Don't use devm_ioremap_resource().
> + comm->moon5_reg_base = devm_ioremap(&pdev->dev, rc->start, rc->end - rc->start + 1);
> + if (IS_ERR(comm->moon5_reg_base)) {
> + dev_err(&pdev->dev, "ioremap failed!\n");
> + return -ENOMEM;
> + }
> +
> + // Get irq resource from dts.
> + ret = platform_get_irq(pdev, 0);
> + if (ret < 0)
> + return ret;
> + comm->irq = ret;
> +
> + // Get clock controller.
> + comm->clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(comm->clk)) {
> + dev_err_probe(&pdev->dev, PTR_ERR(comm->clk),
> + "Failed to retrieve clock controller!\n");
> + return PTR_ERR(comm->clk);
> + }
> +
> + // Get reset controller.
> + comm->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
> + if (IS_ERR(comm->rstc)) {
> + dev_err_probe(&pdev->dev, PTR_ERR(comm->rstc),
> + "Failed to retrieve reset controller!\n");
> + return PTR_ERR(comm->rstc);
> + }
> +
> + // Enable clock.
> + clk_prepare_enable(comm->clk);
> + udelay(1);
> +
> + reset_control_assert(comm->rstc);
> + udelay(1);
> + reset_control_deassert(comm->rstc);
> + udelay(1);
> +
> + // Initialize the 1st net device.
> + ret = init_netdev(pdev, 0, &ndev);
> + if (!ndev)
> + return ret;
> +
> + platform_set_drvdata(pdev, ndev);
> +
> + ndev->irq = comm->irq;
> + mac = netdev_priv(ndev);
> + mac->comm = comm;
> + comm->ndev = ndev;
> +
> + // Get node of phy 1.
> + mac->phy_node = of_parse_phandle(pdev->dev.of_node, "phy-handle1", 0);
> + if (!mac->phy_node) {
> + netdev_info(ndev, "Cannot get node of phy 1!\n");
> + ret = -ENODEV;
> + goto out_unregister_dev;
> + }
> +
> + // Get address of phy from dts.
> + if (of_property_read_u32(mac->phy_node, "reg", &mac->phy_addr)) {
> + mac->phy_addr = 0;
> + netdev_info(ndev, "Cannot get address of phy 1! Set to 0.\n");
> + }
> +
> + // Get mode of phy from dts.
> + if (of_get_phy_mode(mac->phy_node, &mac->phy_mode)) {
> + mac->phy_mode = PHY_INTERFACE_MODE_RGMII_ID;
> + netdev_info(ndev, "Missing phy-mode of phy 1! Set to \'rgmii-id\'.\n");
> + }
> +
> + // Request irq.
> + ret = devm_request_irq(&pdev->dev, comm->irq, ethernet_interrupt, 0,
> + ndev->name, ndev);
> + if (ret) {
> + netdev_err(ndev, "Failed to request irq #%d for \"%s\"!\n",
> + ndev->irq, ndev->name);
> + goto out_unregister_dev;
> + }
> +
> + mac->cpu_port = 0x1; // soc0
> + mac->lan_port = 0x1; // forward to port 0
> + mac->to_vlan = 0x1; // vlan group: 0
> + mac->vlan_id = 0x0; // vlan group: 0
> +
> + // Set MAC address
> + hal_mac_addr_set(mac);
> + hal_rx_mode_set(ndev);
> + hal_mac_addr_table_del_all(mac);
> +
> + ndev2 = NULL;
> + np = of_parse_phandle(pdev->dev.of_node, "phy-handle2", 0);
> + if (np) {
> + init_netdev(pdev, 1, &ndev2);
> + if (ndev2) {
> + mac->next_ndev = ndev2; // Point to the second net device.
> +
> + ndev2->irq = comm->irq;
> + mac2 = netdev_priv(ndev2);
> + mac2->comm = comm;
> + mac2->phy_node = np;
> +
> + if (of_property_read_u32(mac2->phy_node, "reg", &mac2->phy_addr)) {
> + mac2->phy_addr = 1;
> + netdev_info(ndev2, "Cannot get address of phy 2! Set to 1.\n");
> + }
> +
> + if (of_get_phy_mode(mac2->phy_node, &mac2->phy_mode)) {
> + mac2->phy_mode = PHY_INTERFACE_MODE_RGMII_ID;
> + netdev_info(ndev, "Missing phy-mode phy 2! Set to \'rgmii-id\'.\n");
> + }
> +
> + mac2->cpu_port = 0x1; // soc0
> + mac2->lan_port = 0x2; // forward to port 1
> + mac2->to_vlan = 0x2; // vlan group: 1
> + mac2->vlan_id = 0x1; // vlan group: 1
> +
> + hal_mac_addr_set(mac2); // Set MAC address for the 2nd net device.
> + hal_rx_mode_set(ndev2);
> + }
> + }
> +
> + soc0_open(mac);
> + hal_set_rmii_tx_rx_pol(mac);
> + hal_phy_addr(mac);
> +
> + ret = mdio_init(pdev, ndev);
> + if (ret) {
> + netdev_err(ndev, "Failed to initialize mdio!\n");
> + goto out_unregister_dev;
> + }
> +
> + ret = sp_phy_probe(ndev);
> + if (ret) {
> + netdev_err(ndev, "Failed to probe phy!\n");
> + goto out_freemdio;
> + }
> +
> + if (ndev2) {
> + ret = sp_phy_probe(ndev2);
> + if (ret) {
> + netdev_err(ndev2, "Failed to probe phy!\n");
> + unregister_netdev(ndev2);
> + mac->next_ndev = 0;
> + }
> + }
> +
> + netif_napi_add(ndev, &comm->rx_napi, rx_poll, RX_NAPI_WEIGHT);
> + napi_enable(&comm->rx_napi);
> + netif_napi_add(ndev, &comm->tx_napi, tx_poll, TX_NAPI_WEIGHT);
> + napi_enable(&comm->tx_napi);
> + return 0;
> +
> +out_freemdio:
> + if (comm->mii_bus)
> + mdio_remove(ndev);
> +
> +out_unregister_dev:
> + unregister_netdev(ndev);
> + if (ndev2)
> + unregister_netdev(ndev2);
> +
> + return ret;
> +}
> +
> +static int sp_remove(struct platform_device *pdev)
> +{
> + struct net_device *ndev, *ndev2;
> + struct sp_mac *mac;
> +
> + ndev = platform_get_drvdata(pdev);
> + if (!ndev)
> + return 0;
> +
> + mac = netdev_priv(ndev);
> +
> + // Unregister and free 2nd net device.
> + ndev2 = mac->next_ndev;
> + if (ndev2) {
> + sp_phy_remove(ndev2);
> + unregister_netdev(ndev2);
> + free_netdev(ndev2);
> + }
> +
> + mac->comm->enable = 0;
> + soc0_stop(mac);
> +
> + // Disable and delete napi.
> + napi_disable(&mac->comm->rx_napi);
> + netif_napi_del(&mac->comm->rx_napi);
> + napi_disable(&mac->comm->tx_napi);
> + netif_napi_del(&mac->comm->tx_napi);
> +
> + sp_phy_remove(ndev);
> + mdio_remove(ndev);
> +
> + // Unregister and free 1st net device.
> + unregister_netdev(ndev);
> + free_netdev(ndev);
> +
> + clk_disable(mac->comm->clk);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id sp_of_match[] = {
> + {.compatible = "sunplus,sp7021-emac"},
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, sp_of_match);
> +
> +static struct platform_driver sp_driver = {
> + .probe = sp_probe,
> + .remove = sp_remove,
> + .driver = {
> + .name = "sp7021_emac",
> + .owner = THIS_MODULE,
> + .of_match_table = sp_of_match,
> + },
> +};
> +
> +module_platform_driver(sp_driver);
> +
> +MODULE_AUTHOR("Wells Lu <[email protected]>");
> +MODULE_DESCRIPTION("Sunplus Dual 10M/100M Ethernet driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/net/ethernet/sunplus/sp_driver.h b/drivers/net/ethernet/sunplus/sp_driver.h
> new file mode 100644
> index 0000000..ea80a9e
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_driver.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_DRIVER_H__
> +#define __SP_DRIVER_H__
> +
> +#include "sp_define.h"
> +#include "sp_register.h"
> +#include "sp_hal.h"
> +#include "sp_int.h"
> +#include "sp_mdio.h"
> +#include "sp_mac.h"
> +#include "sp_desc.h"
> +
> +#define NEXT_TX(N) ((N) = (((N) + 1) == TX_DESC_NUM) ? 0 : (N) + 1)
> +#define NEXT_RX(QUEUE, N) ((N) = (((N) + 1) == mac->comm->rx_desc_num[QUEUE]) ? 0 : (N) + 1)
> +
> +#define RX_NAPI_WEIGHT 16
> +#define TX_NAPI_WEIGHT 16
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_hal.c b/drivers/net/ethernet/sunplus/sp_hal.c
> new file mode 100644
> index 0000000..a7f06d7
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_hal.c
> @@ -0,0 +1,331 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include <linux/iopoll.h>
> +#include "sp_hal.h"
> +
> +void hal_mac_stop(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg, disable;
> +
> + if (comm->enable == 0) {
> + // Mask and clear all interrupts, except PORT_ST_CHG.
> + write_sw_int_mask0(mac, 0xffffffff);
> + writel(0xffffffff & (~MAC_INT_PORT_ST_CHG),
> + comm->sp_reg_base + SP_SW_INT_STATUS_0);
> +
> + // Disable cpu 0 and cpu 1.
> + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> + writel((0x3 << 6) | reg, comm->sp_reg_base + SP_CPU_CNTL);
> + }
> +
> + // Disable lan 0 and lan 1.
> + disable = ((~comm->enable) & 0x3) << 24;
> + reg = readl(comm->sp_reg_base + SP_PORT_CNTL0);
> + writel(disable | reg, comm->sp_reg_base + SP_PORT_CNTL0);
> +}
> +
> +void hal_mac_reset(struct sp_mac *mac)
> +{
> +}
Thre is no need for the empty function
> +
> +void hal_mac_start(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Enable cpu port 0 (6) & port 0 crc padding (8)
> + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> + writel((reg & (~(0x1 << 6))) | (0x1 << 8), comm->sp_reg_base + SP_CPU_CNTL);
> +
> + // Enable lan 0 & lan 1
> + reg = readl(comm->sp_reg_base + SP_PORT_CNTL0);
> + writel(reg & (~(comm->enable << 24)), comm->sp_reg_base + SP_PORT_CNTL0);
> +}
> +
> +void hal_mac_addr_set(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Write MAC address.
> + writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
> + comm->sp_reg_base + SP_W_MAC_15_0);
> + writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
> + (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
> +
> + // Set aging=1
> + writel((mac->cpu_port << 10) + (mac->vlan_id << 7) + (1 << 4) + 0x1,
> + comm->sp_reg_base + SP_WT_MAC_AD0);
> +
> + // Wait for completing.
> + do {
> + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> + ndelay(10);
> + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> + } while ((reg & (0x1 << 1)) == 0x0);
> +
> + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
> +}
> +
> +void hal_mac_addr_del(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Write MAC address.
> + writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
> + comm->sp_reg_base + SP_W_MAC_15_0);
> + writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
> + (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
> +
> + // Wait for completing.
> + writel((0x1 << 12) + (mac->vlan_id << 7) + 0x1,
> + comm->sp_reg_base + SP_WT_MAC_AD0);
> + do {
> + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> + ndelay(10);
> + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> + } while ((reg & (0x1 << 1)) == 0x0);
> +
> + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
> +}
> +
> +void hal_mac_addr_table_del_all(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Wait for address table being idle.
> + do {
> + reg = readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH);
> + ndelay(10);
> + } while (!(reg & MAC_ADDR_LOOKUP_IDLE));
> +
> + // Search address table from start.
> + writel(readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH) | MAC_BEGIN_SEARCH_ADDR,
> + comm->sp_reg_base + SP_ADDR_TBL_SRCH);
> + while (1) {
> + do {
> + reg = readl(comm->sp_reg_base + SP_ADDR_TBL_ST);
> + ndelay(10);
> + netdev_dbg(mac->ndev, "addr_tbl_st = %08x\n", reg);
> + } while (!(reg & (MAC_AT_TABLE_END | MAC_AT_DATA_READY)));
> +
> + if (reg & MAC_AT_TABLE_END)
> + break;
> +
> + netdev_dbg(mac->ndev, "addr_tbl_st = %08x\n", reg);
> + netdev_dbg(mac->ndev, "@AT #%u: port=%01x, cpu=%01x, vid=%u, aging=%u, proxy=%u, mc_ingress=%u\n",
> + (reg >> 22) & 0x3ff, (reg >> 12) & 0x3, (reg >> 10) & 0x3,
> + (reg >> 7) & 0x7, (reg >> 4) & 0x7, (reg >> 3) & 0x1,
> + (reg >> 2) & 0x1);
> +
> + // Delete all entries which are learnt from lan ports.
> + if ((reg >> 12) & 0x3) {
> + writel(readl(comm->sp_reg_base + SP_MAC_AD_SER0),
> + comm->sp_reg_base + SP_W_MAC_15_0);
> + writel(readl(comm->sp_reg_base + SP_MAC_AD_SER1),
> + comm->sp_reg_base + SP_W_MAC_47_16);
> +
> + writel((0x1 << 12) + (reg & (0x7 << 7)) + 0x1,
> + comm->sp_reg_base + SP_WT_MAC_AD0);
> + do {
> + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> + ndelay(10);
> + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> + } while ((reg & (0x1 << 1)) == 0x0);
> + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
> + }
> +
> + // Search next.
> + writel(readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH) | MAC_SEARCH_NEXT_ADDR,
> + comm->sp_reg_base + SP_ADDR_TBL_SRCH);
> + }
> +}
> +
> +void hal_mac_init(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Disable cpu0 and cpu 1.
> + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> + writel((0x3 << 6) | reg, comm->sp_reg_base + SP_CPU_CNTL);
Would be nice to see a constant
> +
> + // Descriptor base address
> + writel(mac->comm->desc_dma, comm->sp_reg_base + SP_TX_LBASE_ADDR_0);
> + writel(mac->comm->desc_dma + sizeof(struct mac_desc) * TX_DESC_NUM,
> + comm->sp_reg_base + SP_TX_HBASE_ADDR_0);
> + writel(mac->comm->desc_dma + sizeof(struct mac_desc) * (TX_DESC_NUM +
> + MAC_GUARD_DESC_NUM), comm->sp_reg_base + SP_RX_HBASE_ADDR_0);
> + writel(mac->comm->desc_dma + sizeof(struct mac_desc) * (TX_DESC_NUM +
> + MAC_GUARD_DESC_NUM + RX_QUEUE0_DESC_NUM),
> + comm->sp_reg_base + SP_RX_LBASE_ADDR_0);
> +
> + // Fc_rls_th=0x4a, Fc_set_th=0x3a, Drop_rls_th=0x2d, Drop_set_th=0x1d
> + writel(0x4a3a2d1d, comm->sp_reg_base + SP_FL_CNTL_TH);
> +
> + // Cpu_rls_th=0x4a, Cpu_set_th=0x3a, Cpu_th=0x12, Port_th=0x12
> + writel(0x4a3a1212, comm->sp_reg_base + SP_CPU_FL_CNTL_TH);
> +
> + // mtcc_lmt=0xf, Pri_th_l=6, Pri_th_h=6, weigh_8x_en=1
> + writel(0xf6680000, comm->sp_reg_base + SP_PRI_FL_CNTL);
> +
> + // High-active LED
> + reg = readl(comm->sp_reg_base + SP_LED_PORT0);
> + writel(reg | (1 << 28), comm->sp_reg_base + SP_LED_PORT0);
ditto
> +
> + // Disable cpu port0 aging (12)
> + // Disable cpu port0 learning (14)
> + // Enable UC and MC packets
> + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> + writel((reg & (~((0x1 << 14) | (0x3c << 0)))) | (0x1 << 12),
ditto
> + comm->sp_reg_base + SP_CPU_CNTL);
> +
> + // Disable lan port SA learning.
> + reg = readl(comm->sp_reg_base + SP_PORT_CNTL1);
> + writel(reg | (0x3 << 8), comm->sp_reg_base + SP_PORT_CNTL1);
ditto
> +
> + // Port 0: VLAN group 0
> + // Port 1: VLAN group 1
> + writel((1 << 4) + 0, comm->sp_reg_base + SP_PVID_CONFIG0);
> +
> + // VLAN group 0: cpu0+port0
> + // VLAN group 1: cpu0+port1
> + writel((0xa << 8) + 0x9, comm->sp_reg_base + SP_VLAN_MEMSET_CONFIG0);
> +
> + // RMC forward: to cpu
> + // LED: 60mS
> + // BC storm prev: 31 BC
> + reg = readl(comm->sp_reg_base + SP_SW_GLB_CNTL);
> + writel((reg & (~((0x3 << 25) | (0x3 << 23) | (0x3 << 4)))) |
> + (0x1 << 25) | (0x1 << 23) | (0x1 << 4),
> + comm->sp_reg_base + SP_SW_GLB_CNTL);
> +
> + write_sw_int_mask0(mac, MAC_INT_MASK_DEF);
> +}
> +
> +void hal_rx_mode_set(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> + struct sp_common *comm = mac->comm;
> + u32 mask, reg, rx_mode;
> +
> + netdev_dbg(ndev, "ndev->flags = %08x\n", ndev->flags);
> +
> + mask = (mac->lan_port << 2) | (mac->lan_port << 0);
> + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> +
> + if (ndev->flags & IFF_PROMISC) { /* Set promiscuous mode */
> + // Allow MC and unknown UC packets
> + rx_mode = (mac->lan_port << 2) | (mac->lan_port << 0);
> + } else if ((!netdev_mc_empty(ndev) && (ndev->flags & IFF_MULTICAST)) ||
> + (ndev->flags & IFF_ALLMULTI)) {
> + // Allow MC packets
> + rx_mode = (mac->lan_port << 2);
> + } else {
> + // Disable MC and unknown UC packets
> + rx_mode = 0;
> + }
> +
> + writel((reg & (~mask)) | ((~rx_mode) & mask), comm->sp_reg_base + SP_CPU_CNTL);
> + netdev_dbg(ndev, "cpu_cntl = %08x\n", readl(comm->sp_reg_base + SP_CPU_CNTL));
> +}
> +
> +int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8 reg_addr, u32 wdata)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 val, ret;
> +
> + writel((wdata << 16) | (op_cd << 13) | (reg_addr << 8) | phy_addr,
> + comm->sp_reg_base + SP_PHY_CNTL_REG0);
> +
> + ret = read_poll_timeout(readl, val, val & op_cd, 10, 1000, 1,
> + comm->sp_reg_base + SP_PHY_CNTL_REG1);
> + if (ret == 0)
> + return val >> 16;
> + else
> + return ret;
> +}
> +
> +void hal_tx_trigger(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> +
> + writel((0x1 << 1), comm->sp_reg_base + SP_CPU_TX_TRIG);
> +}
> +
> +void hal_set_rmii_tx_rx_pol(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Set polarity of RX and TX of RMII signal.
> + reg = readl(comm->moon5_reg_base + MOON5_MO4_L2SW_CLKSW_CTL);
> + writel(reg | (0xf << 16) | 0xf, comm->moon5_reg_base + MOON5_MO4_L2SW_CLKSW_CTL);
> +}
> +
> +void hal_phy_addr(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Set address of phy.
> + reg = readl(comm->sp_reg_base + SP_MAC_FORCE_MODE);
> + reg = (reg & (~(0x1f << 16))) | ((mac->phy_addr & 0x1f) << 16);
> + if (mac->next_ndev) {
> + struct net_device *ndev2 = mac->next_ndev;
> + struct sp_mac *mac2 = netdev_priv(ndev2);
> +
> + reg = (reg & (~(0x1f << 24))) | ((mac2->phy_addr & 0x1f) << 24);
> + }
> + writel(reg, comm->sp_reg_base + SP_MAC_FORCE_MODE);
> +}
> +
> +u32 read_sw_int_mask0(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> +
> + return readl(comm->sp_reg_base + SP_SW_INT_MASK_0);
> +}
> +
> +void write_sw_int_mask0(struct sp_mac *mac, u32 value)
> +{
> + struct sp_common *comm = mac->comm;
> +
> + writel(value, comm->sp_reg_base + SP_SW_INT_MASK_0);
> +}
> +
> +void write_sw_int_status0(struct sp_mac *mac, u32 value)
> +{
> + struct sp_common *comm = mac->comm;
> +
> + writel(value, comm->sp_reg_base + SP_SW_INT_STATUS_0);
> +}
> +
> +u32 read_sw_int_status0(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> +
> + return readl(comm->sp_reg_base + SP_SW_INT_STATUS_0);
> +}
> +
> +u32 read_port_ability(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> +
> + return readl(comm->sp_reg_base + SP_PORT_ABILITY);
> +}
> diff --git a/drivers/net/ethernet/sunplus/sp_hal.h b/drivers/net/ethernet/sunplus/sp_hal.h
> new file mode 100644
> index 0000000..f4b1979
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_hal.h
> @@ -0,0 +1,31 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_HAL_H__
> +#define __SP_HAL_H__
> +
> +#include "sp_register.h"
> +#include "sp_define.h"
> +#include "sp_desc.h"
> +
> +void hal_mac_stop(struct sp_mac *mac);
> +void hal_mac_reset(struct sp_mac *mac);
> +void hal_mac_start(struct sp_mac *mac);
> +void hal_mac_addr_set(struct sp_mac *mac);
> +void hal_mac_addr_del(struct sp_mac *mac);
> +void hal_mac_addr_table_del_all(struct sp_mac *mac);
> +void hal_mac_init(struct sp_mac *mac);
> +void hal_rx_mode_set(struct net_device *ndev);
> +int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8 reg_addr, u32 wdata);
> +void hal_tx_trigger(struct sp_mac *mac);
> +void hal_set_rmii_tx_rx_pol(struct sp_mac *mac);
> +void hal_phy_addr(struct sp_mac *mac);
> +u32 read_sw_int_mask0(struct sp_mac *mac);
> +void write_sw_int_mask0(struct sp_mac *mac, u32 value);
> +void write_sw_int_status0(struct sp_mac *mac, u32 value);
> +u32 read_sw_int_status0(struct sp_mac *mac);
> +u32 read_port_ability(struct sp_mac *mac);
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_int.c b/drivers/net/ethernet/sunplus/sp_int.c
> new file mode 100644
> index 0000000..27d81b6
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_int.c
> @@ -0,0 +1,286 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "sp_define.h"
> +#include "sp_int.h"
> +#include "sp_driver.h"
> +#include "sp_hal.h"
> +
> +static void port_status_change(struct sp_mac *mac)
> +{
> + u32 reg;
> + struct net_device *ndev = mac->ndev;
> +
> + reg = read_port_ability(mac);
> + if (!netif_carrier_ok(ndev) && (reg & PORT_ABILITY_LINK_ST_P0)) {
> + netif_carrier_on(ndev);
> + netif_start_queue(ndev);
> + } else if (netif_carrier_ok(ndev) && !(reg & PORT_ABILITY_LINK_ST_P0)) {
> + netif_carrier_off(ndev);
> + netif_stop_queue(ndev);
> + }
> +
> + if (mac->next_ndev) {
> + struct net_device *ndev2 = mac->next_ndev;
> +
> + if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
> + netif_carrier_on(ndev2);
> + netif_start_queue(ndev2);
> + } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
> + netif_carrier_off(ndev2);
> + netif_stop_queue(ndev2);
> + }
> + }
> +}
> +
> +static void rx_skb(struct sp_mac *mac, struct sk_buff *skb)
> +{
> + mac->dev_stats.rx_packets++;
> + mac->dev_stats.rx_bytes += skb->len;
> + netif_receive_skb(skb);
> +}
> +
> +int rx_poll(struct napi_struct *napi, int budget)
> +{
> + struct sp_common *comm = container_of(napi, struct sp_common, rx_napi);
> + struct sp_mac *mac = netdev_priv(comm->ndev);
> + struct sk_buff *skb, *new_skb;
> + struct skb_info *sinfo;
> + struct mac_desc *desc;
> + struct mac_desc *h_desc;
> + u32 rx_pos, pkg_len;
> + u32 cmd;
> + u32 num, rx_count;
> + s32 queue;
> + int ndev2_pkt;
> + struct net_device_stats *dev_stats;
> +
> + spin_lock(&comm->rx_lock);
> +
> + // Process high-priority queue and then low-priority queue.
> + for (queue = 0; queue < RX_DESC_QUEUE_NUM; queue++) {
> + rx_pos = comm->rx_pos[queue];
> + rx_count = comm->rx_desc_num[queue];
> +
> + for (num = 0; num < rx_count; num++) {
> + sinfo = comm->rx_skb_info[queue] + rx_pos;
> + desc = comm->rx_desc[queue] + rx_pos;
> + cmd = desc->cmd1;
> +
> + if (cmd & OWN_BIT)
> + break;
> +
> + if ((cmd & PKTSP_MASK) == PKTSP_PORT1) {
> + struct sp_mac *mac2;
> +
> + ndev2_pkt = 1;
> + mac2 = (mac->next_ndev) ? netdev_priv(mac->next_ndev) : NULL;
> + dev_stats = (mac2) ? &mac2->dev_stats : &mac->dev_stats;
> + } else {
> + ndev2_pkt = 0;
> + dev_stats = &mac->dev_stats;
> + }
> +
> + pkg_len = cmd & LEN_MASK;
> + if (unlikely((cmd & ERR_CODE) || (pkg_len < 64))) {
> + dev_stats->rx_length_errors++;
> + dev_stats->rx_dropped++;
> + goto NEXT;
> + }
> +
> + if (unlikely(cmd & RX_IP_CHKSUM_BIT)) {
> + dev_stats->rx_crc_errors++;
> + dev_stats->rx_dropped++;
> + goto NEXT;
> + }
> +
> + // Allocate an skbuff for receiving.
> + new_skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
> + GFP_ATOMIC | GFP_DMA);
> + if (unlikely(!new_skb)) {
> + dev_stats->rx_dropped++;
> + goto NEXT;
> + }
> + new_skb->dev = mac->ndev;
> +
> + dma_unmap_single(&comm->pdev->dev, sinfo->mapping,
> + comm->rx_desc_buff_size, DMA_FROM_DEVICE);
> +
> + skb = sinfo->skb;
> + skb->ip_summed = CHECKSUM_NONE;
> +
> + /*skb_put will judge if tail exceeds end, but __skb_put won't */
> + __skb_put(skb, (pkg_len - 4 > comm->rx_desc_buff_size) ?
> + comm->rx_desc_buff_size : pkg_len - 4);
> +
> + sinfo->mapping = dma_map_single(&comm->pdev->dev, new_skb->data,
> + comm->rx_desc_buff_size,
> + DMA_FROM_DEVICE);
may fail
> + sinfo->skb = new_skb;
> +
> + if (ndev2_pkt) {
> + struct net_device *netdev2 = mac->next_ndev;
> +
> + if (netdev2) {
> + skb->protocol = eth_type_trans(skb, netdev2);
> + rx_skb(netdev_priv(netdev2), skb);
> + }
> + } else {
> + skb->protocol = eth_type_trans(skb, mac->ndev);
> + rx_skb(mac, skb);
> + }
> +
> + desc->addr1 = sinfo->mapping;
> +
> +NEXT:
> + desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
> + EOR_BIT | MAC_RX_LEN_MAX : MAC_RX_LEN_MAX;
> + wmb(); // Set OWN_BIT after other fields of descriptor are effective.
> + desc->cmd1 = OWN_BIT | (comm->rx_desc_buff_size & LEN_MASK);
> +
> + NEXT_RX(queue, rx_pos);
> +
> + // If there are packets in high-priority queue,
> + // stop processing low-priority queue.
> + if ((queue == 1) && ((h_desc->cmd1 & OWN_BIT) == 0))
> + break;
> + }
> +
> + comm->rx_pos[queue] = rx_pos;
> +
> + // Save pointer to last rx descriptor of high-priority queue.
> + if (queue == 0)
> + h_desc = comm->rx_desc[queue] + rx_pos;
> + }
> +
> + spin_unlock(&comm->rx_lock);
> +
> + wmb(); // make sure settings are effective.
> + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_RX);
> +
> + napi_complete(napi);
> + return 0;
> +}
> +
> +int tx_poll(struct napi_struct *napi, int budget)
> +{
> + struct sp_common *comm = container_of(napi, struct sp_common, tx_napi);
> + struct sp_mac *mac = netdev_priv(comm->ndev);
> + u32 tx_done_pos;
> + u32 cmd;
> + struct skb_info *skbinfo;
> + struct sp_mac *smac;
> +
> + spin_lock(&comm->tx_lock);
> +
> + tx_done_pos = comm->tx_done_pos;
> + while ((tx_done_pos != comm->tx_pos) || (comm->tx_desc_full == 1)) {
> + cmd = comm->tx_desc[tx_done_pos].cmd1;
> + if (cmd & OWN_BIT)
> + break;
> +
> + skbinfo = &comm->tx_temp_skb_info[tx_done_pos];
> + if (unlikely(!skbinfo->skb))
> + netdev_err(mac->ndev, "skb is null!\n");
> +
> + smac = mac;
> + if (mac->next_ndev && ((cmd & TO_VLAN_MASK) == TO_VLAN_GROUP1))
> + smac = netdev_priv(mac->next_ndev);
> +
> + if (unlikely(cmd & (ERR_CODE))) {
> + smac->dev_stats.tx_errors++;
> + } else {
> + smac->dev_stats.tx_packets++;
> + smac->dev_stats.tx_bytes += skbinfo->len;
> + }
> +
> + dma_unmap_single(&comm->pdev->dev, skbinfo->mapping, skbinfo->len,
> + DMA_TO_DEVICE);
> + skbinfo->mapping = 0;
> + dev_kfree_skb_irq(skbinfo->skb);
> + skbinfo->skb = NULL;
> +
> + NEXT_TX(tx_done_pos);
> + if (comm->tx_desc_full == 1)
> + comm->tx_desc_full = 0;
> + }
> +
> + comm->tx_done_pos = tx_done_pos;
> + if (!comm->tx_desc_full) {
> + if (netif_queue_stopped(mac->ndev))
> + netif_wake_queue(mac->ndev);
> +
> + if (mac->next_ndev) {
> + if (netif_queue_stopped(mac->next_ndev))
> + netif_wake_queue(mac->next_ndev);
> + }
> + }
> +
> + spin_unlock(&comm->tx_lock);
> +
> + wmb(); // make sure settings are effective.
> + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_TX);
> +
> + napi_complete(napi);
> + return 0;
> +}
> +
> +irqreturn_t ethernet_interrupt(int irq, void *dev_id)
> +{
> + struct net_device *ndev;
> + struct sp_mac *mac;
> + struct sp_common *comm;
> + u32 status;
> +
> + ndev = (struct net_device *)dev_id;
> + if (unlikely(!ndev)) {
> + netdev_err(ndev, "ndev is null!\n");
> + goto OUT;
> + }
> +
> + mac = netdev_priv(ndev);
> + comm = mac->comm;
> +
> + status = read_sw_int_status0(mac);
> + if (unlikely(status == 0)) {
> + netdev_err(ndev, "Interrput status is null!\n");
> + goto OUT;
> + }
> + write_sw_int_status0(mac, status);
> +
> + if (status & MAC_INT_RX) {
> + // Disable RX interrupts.
> + write_sw_int_mask0(mac, read_sw_int_mask0(mac) | MAC_INT_RX);
> +
> + if (unlikely(status & MAC_INT_RX_DES_ERR)) {
> + netdev_err(ndev, "Illegal RX Descriptor!\n");
> + mac->dev_stats.rx_fifo_errors++;
> + }
> + if (napi_schedule_prep(&comm->rx_napi))
> + __napi_schedule(&comm->rx_napi);
> + }
> +
> + if (status & MAC_INT_TX) {
> + // Disable TX interrupts.
> + write_sw_int_mask0(mac, read_sw_int_mask0(mac) | MAC_INT_TX);
> +
> + if (unlikely(status & MAC_INT_TX_DES_ERR)) {
> + netdev_err(ndev, "Illegal TX Descriptor Error\n");
> + mac->dev_stats.tx_fifo_errors++;
> + mac_soft_reset(mac);
> + wmb(); // make sure settings are effective.
> + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_TX);
> + } else {
> + if (napi_schedule_prep(&comm->tx_napi))
> + __napi_schedule(&comm->tx_napi);
> + }
> + }
> +
> + if (status & MAC_INT_PORT_ST_CHG) /* link status changed */
> + port_status_change(mac);
> +
> +OUT:
> + return IRQ_HANDLED;
> +}
> diff --git a/drivers/net/ethernet/sunplus/sp_int.h b/drivers/net/ethernet/sunplus/sp_int.h
> new file mode 100644
> index 0000000..7de8df4
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_int.h
> @@ -0,0 +1,13 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_INT_H__
> +#define __SP_INT_H__
> +
> +int rx_poll(struct napi_struct *napi, int budget);
> +int tx_poll(struct napi_struct *napi, int budget);
> +irqreturn_t ethernet_interrupt(int irq, void *dev_id);
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_mac.c b/drivers/net/ethernet/sunplus/sp_mac.c
> new file mode 100644
> index 0000000..6a3dfb1
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_mac.c
> @@ -0,0 +1,63 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "sp_mac.h"
> +
> +void mac_init(struct sp_mac *mac)
> +{
> + u32 i;
> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> + mac->comm->rx_pos[i] = 0;
> + mb(); // make sure settings are effective.
> +
> + hal_mac_init(mac);
> +}
> +
> +void mac_soft_reset(struct sp_mac *mac)
> +{
> + u32 i;
> + struct net_device *ndev2;
> +
> + if (netif_carrier_ok(mac->ndev)) {
> + netif_carrier_off(mac->ndev);
> + netif_stop_queue(mac->ndev);
> + }
> +
> + ndev2 = mac->next_ndev;
> + if (ndev2) {
> + if (netif_carrier_ok(ndev2)) {
> + netif_carrier_off(ndev2);
> + netif_stop_queue(ndev2);
> + }
> + }
> +
> + hal_mac_reset(mac);
> + hal_mac_stop(mac);
> +
> + rx_descs_flush(mac->comm);
> + mac->comm->tx_pos = 0;
> + mac->comm->tx_done_pos = 0;
> + mac->comm->tx_desc_full = 0;
> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> + mac->comm->rx_pos[i] = 0;
> + mb(); // make sure settings are effective.
> +
> + hal_mac_init(mac);
> + hal_mac_start(mac);
> +
> + if (!netif_carrier_ok(mac->ndev)) {
> + netif_carrier_on(mac->ndev);
> + netif_start_queue(mac->ndev);
> + }
> +
> + if (ndev2) {
> + if (!netif_carrier_ok(ndev2)) {
> + netif_carrier_on(ndev2);
> + netif_start_queue(ndev2);
> + }
> + }
> +}
> diff --git a/drivers/net/ethernet/sunplus/sp_mac.h b/drivers/net/ethernet/sunplus/sp_mac.h
> new file mode 100644
> index 0000000..f35f3b7
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_mac.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_MAC_H__
> +#define __SP_MAC_H__
> +
> +#include "sp_define.h"
> +#include "sp_hal.h"
> +
> +void mac_init(struct sp_mac *mac);
> +void mac_soft_reset(struct sp_mac *mac);
> +
> +// Calculate the empty tx descriptor number
> +#define TX_DESC_AVAIL(mac) \
> + (((mac)->tx_pos != (mac)->tx_done_pos) ? \
> + (((mac)->tx_done_pos < (mac)->tx_pos) ? \
> + (TX_DESC_NUM - ((mac)->tx_pos - (mac)->tx_done_pos)) : \
> + ((mac)->tx_done_pos - (mac)->tx_pos)) : \
> + ((mac)->tx_desc_full ? 0 : TX_DESC_NUM))
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_mdio.c b/drivers/net/ethernet/sunplus/sp_mdio.c
> new file mode 100644
> index 0000000..f6a7e64
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_mdio.c
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "sp_mdio.h"
> +
> +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum)
> +{
> + int ret;
> +
> + ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
> + if (ret < 0)
> + return -EOPNOTSUPP;
> +
> + return ret;
> +}
> +
> +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val)
> +{
> + int ret;
> +
> + ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
> + if (ret < 0)
> + return -EOPNOTSUPP;
> +
> + return 0;
> +}
> +
> +static int mii_read(struct mii_bus *bus, int phy_id, int regnum)
> +{
> + struct sp_mac *mac = bus->priv;
> +
> + return mdio_read(mac, phy_id, regnum);
> +}
> +
> +static int mii_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
> +{
> + struct sp_mac *mac = bus->priv;
> +
> + return mdio_write(mac, phy_id, regnum, val);
> +}
> +
> +u32 mdio_init(struct platform_device *pdev, struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> + struct mii_bus *mii_bus;
> + struct device_node *mdio_node;
> + int ret;
> +
> + mii_bus = mdiobus_alloc();
> + if (!mii_bus) {
> + netdev_err(ndev, "Failed to allocate mdio_bus memory!\n");
> + return -ENOMEM;
> + }
> +
> + mii_bus->name = "sunplus_mii_bus";
> + mii_bus->parent = &pdev->dev;
> + mii_bus->priv = mac;
> + mii_bus->read = mii_read;
> + mii_bus->write = mii_write;
> + snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
> +
> + mdio_node = of_get_parent(mac->phy_node);
> + if (!mdio_node) {
> + netdev_err(ndev, "Failed to get mdio_node!\n");
> + return -ENODATA;
> + }
> +
> + ret = of_mdiobus_register(mii_bus, mdio_node);
> + if (ret) {
> + netdev_err(ndev, "Failed to register mii bus!\n");
> + mdiobus_free(mii_bus);
> + return ret;
> + }
> +
> + mac->comm->mii_bus = mii_bus;
> + return ret;
> +}
> +
> +void mdio_remove(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (mac->comm->mii_bus) {
> + mdiobus_unregister(mac->comm->mii_bus);
> + mdiobus_free(mac->comm->mii_bus);
> + mac->comm->mii_bus = NULL;
> + }
> +}
> diff --git a/drivers/net/ethernet/sunplus/sp_mdio.h b/drivers/net/ethernet/sunplus/sp_mdio.h
> new file mode 100644
> index 0000000..d708624
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_mdio.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_MDIO_H__
> +#define __SP_MDIO_H__
> +
> +#include "sp_define.h"
> +#include "sp_hal.h"
> +
> +#define MDIO_READ_CMD 0x02
> +#define MDIO_WRITE_CMD 0x01
> +
> +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum);
> +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val);
> +u32 mdio_init(struct platform_device *pdev, struct net_device *ndev);
> +void mdio_remove(struct net_device *ndev);
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_phy.c b/drivers/net/ethernet/sunplus/sp_phy.c
> new file mode 100644
> index 0000000..df6df3a
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_phy.c
> @@ -0,0 +1,64 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "sp_phy.h"
> +#include "sp_mdio.h"
> +
> +static void mii_linkchange(struct net_device *netdev)
> +{
> +}
> +
> +int sp_phy_probe(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> + struct phy_device *phydev;
> + int i;
> +
> + phydev = of_phy_connect(ndev, mac->phy_node, mii_linkchange,
> + 0, mac->phy_mode);
> + if (!phydev) {
> + netdev_err(ndev, "\"%s\" failed to connect to phy!\n", ndev->name);
> + return -ENODEV;
> + }
> +
> + for (i = 0; i < sizeof(phydev->supported) / sizeof(long); i++)
> + phydev->advertising[i] = phydev->supported[i];
> +
> + phydev->irq = PHY_MAC_INTERRUPT;
> + mac->phy_dev = phydev;
> +
> + // Bug workaround:
> + // Flow-control of phy should be enabled. MAC flow-control will refer
> + // to the bit to decide to enable or disable flow-control.
> + mdio_write(mac, mac->phy_addr, 4, mdio_read(mac, mac->phy_addr, 4) | (1 << 10));
> +
> + return 0;
> +}
> +
> +void sp_phy_start(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (mac->phy_dev)
> + phy_start(mac->phy_dev);
> +}
> +
> +void sp_phy_stop(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (mac->phy_dev)
> + phy_stop(mac->phy_dev);
> +}
> +
> +void sp_phy_remove(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (mac->phy_dev) {
> + phy_disconnect(mac->phy_dev);
> + mac->phy_dev = NULL;
> + }
> +}
> diff --git a/drivers/net/ethernet/sunplus/sp_phy.h b/drivers/net/ethernet/sunplus/sp_phy.h
> new file mode 100644
> index 0000000..4ae0351
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_phy.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_PHY_H__
> +#define __SP_PHY_H__
> +
> +#include "sp_define.h"
> +
> +int sp_phy_probe(struct net_device *netdev);
> +void sp_phy_start(struct net_device *netdev);
> +void sp_phy_stop(struct net_device *netdev);
> +void sp_phy_remove(struct net_device *netdev);
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_register.h b/drivers/net/ethernet/sunplus/sp_register.h
> new file mode 100644
> index 0000000..690c0dd
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_register.h
> @@ -0,0 +1,96 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_REGISTER_H__
> +#define __SP_REGISTER_H__
> +
> +#include "sp_define.h"
> +
> +/* TYPE: RegisterFile_L2SW */
> +#define SP_SW_INT_STATUS_0 0x0
> +#define SP_SW_INT_MASK_0 0x4
> +#define SP_FL_CNTL_TH 0x8
> +#define SP_CPU_FL_CNTL_TH 0xc
> +#define SP_PRI_FL_CNTL 0x10
> +#define SP_VLAN_PRI_TH 0x14
> +#define SP_EN_TOS_BUS 0x18
> +#define SP_TOS_MAP0 0x1c
> +#define SP_TOS_MAP1 0x20
> +#define SP_TOS_MAP2 0x24
> +#define SP_TOS_MAP3 0x28
> +#define SP_TOS_MAP4 0x2c
> +#define SP_TOS_MAP5 0x30
> +#define SP_TOS_MAP6 0x34
> +#define SP_TOS_MAP7 0x38
> +#define SP_GLOBAL_QUE_STATUS 0x3c
> +#define SP_ADDR_TBL_SRCH 0x40
> +#define SP_ADDR_TBL_ST 0x44
> +#define SP_MAC_AD_SER0 0x48
> +#define SP_MAC_AD_SER1 0x4c
> +#define SP_WT_MAC_AD0 0x50
> +#define SP_W_MAC_15_0 0x54
> +#define SP_W_MAC_47_16 0x58
> +#define SP_PVID_CONFIG0 0x5c
> +#define SP_PVID_CONFIG1 0x60
> +#define SP_VLAN_MEMSET_CONFIG0 0x64
> +#define SP_VLAN_MEMSET_CONFIG1 0x68
> +#define SP_PORT_ABILITY 0x6c
> +#define SP_PORT_ST 0x70
> +#define SP_CPU_CNTL 0x74
> +#define SP_PORT_CNTL0 0x78
> +#define SP_PORT_CNTL1 0x7c
> +#define SP_PORT_CNTL2 0x80
> +#define SP_SW_GLB_CNTL 0x84
> +#define SP_SP_SW_RESET 0x88
> +#define SP_LED_PORT0 0x8c
> +#define SP_LED_PORT1 0x90
> +#define SP_LED_PORT2 0x94
> +#define SP_LED_PORT3 0x98
> +#define SP_LED_PORT4 0x9c
> +#define SP_WATCH_DOG_TRIG_RST 0xa0
> +#define SP_WATCH_DOG_STOP_CPU 0xa4
> +#define SP_PHY_CNTL_REG0 0xa8
> +#define SP_PHY_CNTL_REG1 0xac
> +#define SP_MAC_FORCE_MODE 0xb0
> +#define SP_VLAN_GROUP_CONFIG0 0xb4
> +#define SP_VLAN_GROUP_CONFIG1 0xb8
> +#define SP_FLOW_CTRL_TH3 0xbc
> +#define SP_QUEUE_STATUS_0 0xc0
> +#define SP_DEBUG_CNTL 0xc4
> +#define SP_RESERVED_1 0xc8
> +#define SP_MEM_TEST_INFO 0xcc
> +#define SP_SW_INT_STATUS_1 0xd0
> +#define SP_SW_INT_MASK_1 0xd4
> +#define SP_SW_GLOBAL_SIGNAL 0xd8
> +
> +#define SP_CPU_TX_TRIG 0x208
> +#define SP_TX_HBASE_ADDR_0 0x20c
> +#define SP_TX_LBASE_ADDR_0 0x210
> +#define SP_RX_HBASE_ADDR_0 0x214
> +#define SP_RX_LBASE_ADDR_0 0x218
> +#define SP_TX_HW_ADDR_0 0x21c
> +#define SP_TX_LW_ADDR_0 0x220
> +#define SP_RX_HW_ADDR_0 0x224
> +#define SP_RX_LW_ADDR_0 0x228
> +#define SP_CPU_PORT_CNTL_REG_0 0x22c
> +#define SP_TX_HBASE_ADDR_1 0x230
> +#define SP_TX_LBASE_ADDR_1 0x234
> +#define SP_RX_HBASE_ADDR_1 0x238
> +#define SP_RX_LBASE_ADDR_1 0x23c
> +#define SP_TX_HW_ADDR_1 0x240
> +#define SP_TX_LW_ADDR_1 0x244
> +#define SP_RX_HW_ADDR_1 0x248
> +#define SP_RX_LW_ADDR_1 0x24c
> +#define SP_CPU_PORT_CNTL_REG_1 0x250
> +
> +/* TYPE: RegisterFile_MOON5 */
> +#define MOON5_MO5_THERMAL_CTL_0 0x0
> +#define MOON5_MO5_THERMAL_CTL_1 0x4
> +#define MOON5_MO4_THERMAL_CTL_2 0xc
> +#define MOON5_MO4_THERMAL_CTL_3 0x8
> +#define MOON5_MO4_TMDS_L2SW_CTL 0x10
> +#define MOON5_MO4_L2SW_CLKSW_CTL 0x14
> +
> +#endif
>

2021-11-11 14:58:14

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] devicetree: bindings: net: Add bindings doc for Sunplus SP7021.

On Thu, 11 Nov 2021 17:04:20 +0800, Wells Lu wrote:
> Add bindings documentation for Sunplus SP7021.
>
> Signed-off-by: Wells Lu <[email protected]>
> ---
> Changes in V2
> - Added mdio and phy sub-nodes.
>
> .../bindings/net/sunplus,sp7021-emac.yaml | 152 +++++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 159 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
>

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:
./Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml:94:12: [warning] wrong indentation: expected 10 but found 11 (indentation)

dtschema/dtc warnings/errors:

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/patch/1553831

This check can fail if there are any dependencies. The base for a patch
series is generally the most recent rc1.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


2021-11-11 18:23:40

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] devicetree: bindings: net: Add bindings doc for Sunplus SP7021.

> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + emac: emac@9c108000 {
> + compatible = "sunplus,sp7021-emac";
> + reg = <0x9c108000 0x400>, <0x9c000280 0x80>;
> + reg-names = "emac", "moon5";
> + interrupt-parent = <&intc>;
> + interrupts = <66 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clkc 0xa7>;
> + resets = <&rstc 0x97>;
> + phy-handle1 = <&eth_phy0>;
> + phy-handle2 = <&eth_phy1>;
> + pinctrl-0 = <&emac_demo_board_v3_pins>;
> + pinctrl-names = "default";
> + nvmem-cells = <&mac_addr0>, <&mac_addr1>;
> + nvmem-cell-names = "mac_addr0", "mac_addr1";
> +
> + mdio {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + eth_phy0: ethernet-phy@0 {
> + reg = <0>;
> + phy-mode = "rmii";

This is in the wrong place. It is a MAC property. You usually put it
next to phy-handle.

> + };
> + eth_phy1: ethernet-phy@1 {
> + reg = <1>;
> + phy-mode = "rmii";
> + };
> + };

I would suggest you structure this differently to make it clear it is
a two port switch:

ethernet-ports {
#address-cells = <1>;
#size-cells = <0>;

port@0 {
reg = <0>;
phy-handle = <&eth_phy0>;
phy-mode = "rmii";
}

port@1 {
reg = <1>;
phy-handle = <&eth_phy1>;
phy-mode = "rmii";
}
}

Andrew

2021-11-12 02:50:22

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 1/2] devicetree: bindings: net: Add bindings doc for Sunplus SP7021.

Hi,

> > +examples:
> > + - |
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > + emac: emac@9c108000 {
> > + compatible = "sunplus,sp7021-emac";
> > + reg = <0x9c108000 0x400>, <0x9c000280 0x80>;
> > + reg-names = "emac", "moon5";
> > + interrupt-parent = <&intc>;
> > + interrupts = <66 IRQ_TYPE_LEVEL_HIGH>;
> > + clocks = <&clkc 0xa7>;
> > + resets = <&rstc 0x97>;
> > + phy-handle1 = <&eth_phy0>;
> > + phy-handle2 = <&eth_phy1>;
> > + pinctrl-0 = <&emac_demo_board_v3_pins>;
> > + pinctrl-names = "default";
> > + nvmem-cells = <&mac_addr0>, <&mac_addr1>;
> > + nvmem-cell-names = "mac_addr0", "mac_addr1";
> > +
> > + mdio {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + eth_phy0: ethernet-phy@0 {
> > + reg = <0>;
> > + phy-mode = "rmii";
>
> This is in the wrong place. It is a MAC property. You usually put it next to phy-handle.

Yes, I'll move phy-mode to Ethernet-port next patch.


> > + };
> > + eth_phy1: ethernet-phy@1 {
> > + reg = <1>;
> > + phy-mode = "rmii";
> > + };
> > + };
>
> I would suggest you structure this differently to make it clear it is a two port switch:
>
> ethernet-ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@0 {
> reg = <0>;
> phy-handle = <&eth_phy0>;
> phy-mode = "rmii";
> }
>
> port@1 {
> reg = <1>;
> phy-handle = <&eth_phy1>;
> phy-mode = "rmii";
> }
> }
>
> Andrew

Yes, refer to new example:

examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
emac: emac@9c108000 {
compatible = "sunplus,sp7021-emac";
reg = <0x9c108000 0x400>, <0x9c000280 0x80>;
reg-names = "emac", "moon5";
interrupt-parent = <&intc>;
interrupts = <66 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clkc 0xa7>;
resets = <&rstc 0x97>;
pinctrl-0 = <&emac_demo_board_v3_pins>;
pinctrl-names = "default";
nvmem-cells = <&mac_addr0>, <&mac_addr1>;
nvmem-cell-names = "mac_addr0", "mac_addr1";

ethernet-ports {
#address-cells = <1>;
#size-cells = <0>;

port@0 {
reg = <0>;
phy-handle = <&eth_phy0>;
phy-mode = "rmii";
};

port@1 {
reg = <1>;
phy-handle = <&eth_phy1>;
phy-mode = "rmii";
};
};

mdio {
#address-cells = <1>;
#size-cells = <0>;

eth_phy0: ethernet-phy@0 {
reg = <0>;
};

eth_phy1: ethernet-phy@1 {
reg = <1>;
};
};
};

Is it correct?

Thank you very much for your review.

2021-11-12 02:58:05

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 1/2] devicetree: bindings: net: Add bindings doc for Sunplus SP7021.

Hi,


> On Thu, 11 Nov 2021 17:04:20 +0800, Wells Lu wrote:
> > Add bindings documentation for Sunplus SP7021.
> >
> > Signed-off-by: Wells Lu <[email protected]>
> > ---
> > Changes in V2
> > - Added mdio and phy sub-nodes.
> >
> > .../bindings/net/sunplus,sp7021-emac.yaml | 152 +++++++++++++++++++++
> > MAINTAINERS | 7 +
> > 2 files changed, 159 insertions(+)
> > create mode 100644
> > Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
> >
>
> My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
> on your patch (DT_CHECKER_FLAGS is new in v5.13):
>
> yamllint warnings/errors:
> ./Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml:94:12: [warning] wrong
> indentation: expected 10 but found 11 (indentation)
>
> dtschema/dtc warnings/errors:
>
> doc reference errors (make refcheckdocs):
>
> See https://patchwork.ozlabs.org/patch/1553831
>
> This check can fail if there are any dependencies. The base for a patch series is generally the most
> recent rc1.
>
> If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure
> 'yamllint' is installed and dt-schema is up to
> date:
>
> pip3 install dtschema --upgrade
>
> Please check and re-submit.

Thank you very much for review and tests.
I'll fix wrong indentation next patch.

2021-11-12 17:43:13

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Wells,

I love your patch! Yet something to improve:

[auto build test ERROR on net-next/master]
[also build test ERROR on net/master robh/for-next linus/master v5.15 next-20211112]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url: https://github.com/0day-ci/linux/commits/Wells-Lu/devicetree-bindings-net-Add-bindings-doc-for-Sunplus-SP7021/20211111-180647
base: https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git cc0356d6a02e064387c16a83cb96fe43ef33181e
config: m68k-allyesconfig (attached as .config)
compiler: m68k-linux-gcc (GCC) 11.2.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/0day-ci/linux/commit/67fef21f20f9dd81655eb490c5fa41c075f4af3d
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Wells-Lu/devicetree-bindings-net-Add-bindings-doc-for-Sunplus-SP7021/20211111-180647
git checkout 67fef21f20f9dd81655eb490c5fa41c075f4af3d
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross ARCH=m68k

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>

All errors (new ones prefixed by >>):

>> drivers/net/ethernet/sunplus/sp_driver.c:200:7: error: no previous prototype for 'sp7021_otp_read_mac' [-Werror=missing-prototypes]
200 | char *sp7021_otp_read_mac(struct device *dev, ssize_t *len, char *name)
| ^~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors


vim +/sp7021_otp_read_mac +200 drivers/net/ethernet/sunplus/sp_driver.c

199
> 200 char *sp7021_otp_read_mac(struct device *dev, ssize_t *len, char *name)
201 {
202 char *ret = NULL;
203 struct nvmem_cell *cell = nvmem_cell_get(dev, name);
204
205 if (IS_ERR_OR_NULL(cell)) {
206 dev_err(dev, "OTP %s read failure: %ld", name, PTR_ERR(cell));
207 return NULL;
208 }
209
210 ret = nvmem_cell_read(cell, len);
211 nvmem_cell_put(cell);
212 dev_dbg(dev, "%zd bytes are read from OTP %s.", *len, name);
213
214 return ret;
215 }
216

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]


Attachments:
(No filename) (2.47 kB)
.config.gz (60.53 kB)
Download all attachments

2021-11-12 23:16:44

by Florian Fainelli

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

On 11/11/21 1:04 AM, Wells Lu wrote:
> Add driver for Sunplus SP7021.
>
> Signed-off-by: Wells Lu <[email protected]>
> ---

[snip]

> +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum)
> +{
> + int ret;
> +
> + ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
> + if (ret < 0)
> + return -EOPNOTSUPP;
> +
> + return ret;
> +}
> +
> +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val)
> +{
> + int ret;
> +
> + ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
> + if (ret < 0)
> + return -EOPNOTSUPP;
> +
> + return 0;
> +}

You should not be exposing these functions, if you do, that means
another part of your code performs MDIO bus read/write operations
without using the appropriate layer, so no.

> +
> +static int mii_read(struct mii_bus *bus, int phy_id, int regnum)
> +{
> + struct sp_mac *mac = bus->priv;
> +
> + return mdio_read(mac, phy_id, regnum);
> +}
> +
> +static int mii_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
> +{
> + struct sp_mac *mac = bus->priv;
> +
> + return mdio_write(mac, phy_id, regnum, val);
> +}
> +
> +u32 mdio_init(struct platform_device *pdev, struct net_device *ndev)

Those function names need to be prefixed with sp_ to denote the driver
local scope, this applies for your entire patch set.

[snip]

> diff --git a/drivers/net/ethernet/sunplus/sp_mdio.h b/drivers/net/ethernet/sunplus/sp_mdio.h
> new file mode 100644
> index 0000000..d708624
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_mdio.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#ifndef __SP_MDIO_H__
> +#define __SP_MDIO_H__
> +
> +#include "sp_define.h"
> +#include "sp_hal.h"
> +
> +#define MDIO_READ_CMD 0x02
> +#define MDIO_WRITE_CMD 0x01
> +
> +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum);
> +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val);

Please scope your functions better, and name them sp_mdio_read, etc.
because mdio_read() is way too generic. Also, can you please follow the
same prototype as what include/linux/mdio.h has for the mdiobus->read
and ->write calls, that is phy_id is int, regnum is u32, etc.

> +u32 mdio_init(struct platform_device *pdev, struct net_device *ndev);
> +void mdio_remove(struct net_device *ndev);
> +
> +#endif
> diff --git a/drivers/net/ethernet/sunplus/sp_phy.c b/drivers/net/ethernet/sunplus/sp_phy.c
> new file mode 100644
> index 0000000..df6df3a
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_phy.c
> @@ -0,0 +1,64 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "sp_phy.h"
> +#include "sp_mdio.h"
> +
> +static void mii_linkchange(struct net_device *netdev)
> +{
> +}

Does your MAC fully auto-configure based on the PHY's link parameters,
if so, how does it do it? You most certainly need to act on duplex
changes, or speed changes no?

> +
> +int sp_phy_probe(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> + struct phy_device *phydev;
> + int i;
> +
> + phydev = of_phy_connect(ndev, mac->phy_node, mii_linkchange,
> + 0, mac->phy_mode);
> + if (!phydev) {
> + netdev_err(ndev, "\"%s\" failed to connect to phy!\n", ndev->name);
> + return -ENODEV;
> + }
> +
> + for (i = 0; i < sizeof(phydev->supported) / sizeof(long); i++)
> + phydev->advertising[i] = phydev->supported[i];
> +
> + phydev->irq = PHY_MAC_INTERRUPT;
> + mac->phy_dev = phydev;
> +
> + // Bug workaround:
> + // Flow-control of phy should be enabled. MAC flow-control will refer
> + // to the bit to decide to enable or disable flow-control.
> + mdio_write(mac, mac->phy_addr, 4, mdio_read(mac, mac->phy_addr, 4) | (1 << 10));

This is a layering violation, and you should not be doing those things
here, if you need to advertise flow control, then please set
ADVERTISE_PAUSE_CAP and/or ADVERTISE_PAUSE_ASYM accordingly, see whether
phy_set_asym_pause() can do what you need it to.

> +
> + return 0;
> +}
> +
> +void sp_phy_start(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (mac->phy_dev)
> + phy_start(mac->phy_dev);
> +}
> +
> +void sp_phy_stop(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (mac->phy_dev)
> + phy_stop(mac->phy_dev);
> +}
> +
> +void sp_phy_remove(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> +
> + if (mac->phy_dev) {
> + phy_disconnect(mac->phy_dev);
> + mac->phy_dev = NULL;
> + }

The net_device structure already contains a phy_device pointer, you
don't need to have one in your sp_mac structure, too.
--
Florian

2021-11-12 23:24:55

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Florian

You are basically pointing out issues i already pointed out in
previous versions, and have been ignored :-(

Wells, please look at the comments i made on your earlier
versions. Those comments are still valid and need addressing.

Andrew

2021-11-12 23:59:04

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

> +void rx_descs_flush(struct sp_common *comm)

As both Florian and I have said, you need a prefix for all your
functions, structures, etc. sp_ is not the best prefix either, it is
not very unique. spl2sw_ would be better.

> +void rx_descs_clean(struct sp_common *comm)
> +{
> + u32 i, j;
> + struct mac_desc *rx_desc;
> + struct skb_info *rx_skbinfo;

netdev wants reverse christmas tree. You need to change the order of
your local variables, longest lines first, shorted last.

> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> + if (!comm->rx_skb_info[i])
> + continue;
> +
> + rx_desc = comm->rx_desc[i];
> + rx_skbinfo = comm->rx_skb_info[i];
> + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> + rx_desc[j].cmd1 = 0;
> + wmb(); // Clear OWN_BIT and then set other fields.
> + rx_desc[j].cmd2 = 0;
> + rx_desc[j].addr1 = 0;
> +
> + if (rx_skbinfo[j].skb) {
> + dma_unmap_single(&comm->pdev->dev, rx_skbinfo[j].mapping,
> + comm->rx_desc_buff_size, DMA_FROM_DEVICE);
> + dev_kfree_skb(rx_skbinfo[j].skb);
> + rx_skbinfo[j].skb = NULL;
> + rx_skbinfo[j].mapping = 0;
> + }
> + }
> +
> + kfree(rx_skbinfo);
> + comm->rx_skb_info[i] = NULL;
> + }
> +}

> +int rx_descs_init(struct sp_common *comm)
> +{
> + struct sk_buff *skb;
> + u32 i, j;
> + struct mac_desc *rx_desc;
> + struct skb_info *rx_skbinfo;
> +
> + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> + comm->rx_skb_info[i] = kmalloc_array(comm->rx_desc_num[i],
> + sizeof(struct skb_info), GFP_KERNEL);
> + if (!comm->rx_skb_info[i])
> + goto MEM_ALLOC_FAIL;
> +
> + rx_skbinfo = comm->rx_skb_info[i];
> + rx_desc = comm->rx_desc[i];
> + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> + skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
> + GFP_ATOMIC | GFP_DMA);
> + if (!skb)
> + goto MEM_ALLOC_FAIL;
> +
> + skb->dev = comm->ndev;
> + skb_reserve(skb, RX_OFFSET); /* +data +tail */
> +
> + rx_skbinfo[j].skb = skb;
> + rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
> + comm->rx_desc_buff_size,
> + DMA_FROM_DEVICE);
> + rx_desc[j].addr1 = rx_skbinfo[j].mapping;
> + rx_desc[j].addr2 = 0;
> + rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
> + EOR_BIT | comm->rx_desc_buff_size :
> + comm->rx_desc_buff_size;
> + wmb(); // Set OWN_BIT after other fields are effective.
> + rx_desc[j].cmd1 = OWN_BIT;
> + }
> + }
> +
> + return 0;
> +
> +MEM_ALLOC_FAIL:

lower case labels. Didn't somebody already say that?

> +int descs_init(struct sp_common *comm)
> +{
> + u32 i, ret;
> +
> + // Initialize rx descriptor's data
> + comm->rx_desc_num[0] = RX_QUEUE0_DESC_NUM;
> +#if RX_DESC_QUEUE_NUM > 1
> + comm->rx_desc_num[1] = RX_QUEUE1_DESC_NUM;
> +#endif

Avoid #if statements. Why is this needed?

> +++ b/drivers/net/ethernet/sunplus/sp_driver.c
> @@ -0,0 +1,606 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/reset.h>
> +#include <linux/nvmem-consumer.h>
> +#include <linux/of_net.h>
> +#include "sp_driver.h"
> +#include "sp_phy.h"
> +
> +static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
> + 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00

This does not have the locally administered bit set. Should it? Or is
this and address from your OUI?

> +static void ethernet_set_rx_mode(struct net_device *ndev)
> +{
> + if (ndev) {

How can ndev be NULL?

> +++ b/drivers/net/ethernet/sunplus/sp_hal.c
> @@ -0,0 +1,331 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include <linux/iopoll.h>
> +#include "sp_hal.h"
> +
> +void hal_mac_stop(struct sp_mac *mac)

I suggest you avoid any references to hal. It makes people think you
have ported a driver from some other operating system and then put a
layer of code on top of it. That is not how you do it in Linux. This
is a Linux driver, nothing else.

> +void hal_mac_reset(struct sp_mac *mac)
> +{
> +}
> +

Should not exist.

> +void hal_mac_addr_set(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Write MAC address.
> + writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
> + comm->sp_reg_base + SP_W_MAC_15_0);
> + writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
> + (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
> +
> + // Set aging=1
> + writel((mac->cpu_port << 10) + (mac->vlan_id << 7) + (1 << 4) + 0x1,
> + comm->sp_reg_base + SP_WT_MAC_AD0);

Is this actually adding an entry into the address translation table?
If so, make this clear in the function name. You are not setting the
MAC address, you are just adding a static forwarding entry.

> +
> + // Wait for completing.
> + do {
> + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> + ndelay(10);
> + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> + } while ((reg & (0x1 << 1)) == 0x0);
> +
> + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
> +}

> +void hal_rx_mode_set(struct net_device *ndev)
> +{
> + struct sp_mac *mac = netdev_priv(ndev);
> + struct sp_common *comm = mac->comm;
> + u32 mask, reg, rx_mode;
> +
> + netdev_dbg(ndev, "ndev->flags = %08x\n", ndev->flags);
> +
> + mask = (mac->lan_port << 2) | (mac->lan_port << 0);
> + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> +
> + if (ndev->flags & IFF_PROMISC) { /* Set promiscuous mode */
> + // Allow MC and unknown UC packets
> + rx_mode = (mac->lan_port << 2) | (mac->lan_port << 0);
> + } else if ((!netdev_mc_empty(ndev) && (ndev->flags & IFF_MULTICAST)) ||
> + (ndev->flags & IFF_ALLMULTI)) {
> + // Allow MC packets
> + rx_mode = (mac->lan_port << 2);
> + } else {
> + // Disable MC and unknown UC packets
> + rx_mode = 0;
> + }
> +
> + writel((reg & (~mask)) | ((~rx_mode) & mask), comm->sp_reg_base + SP_CPU_CNTL);
> + netdev_dbg(ndev, "cpu_cntl = %08x\n", readl(comm->sp_reg_base + SP_CPU_CNTL));

This looks like it belongs in the ethtool code.

> +int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8 reg_addr, u32 wdata)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 val, ret;
> +
> + writel((wdata << 16) | (op_cd << 13) | (reg_addr << 8) | phy_addr,
> + comm->sp_reg_base + SP_PHY_CNTL_REG0);
> +
> + ret = read_poll_timeout(readl, val, val & op_cd, 10, 1000, 1,
> + comm->sp_reg_base + SP_PHY_CNTL_REG1);
> + if (ret == 0)
> + return val >> 16;
> + else
> + return ret;
> +}

Should go with the other mdio code.

> +void hal_phy_addr(struct sp_mac *mac)
> +{
> + struct sp_common *comm = mac->comm;
> + u32 reg;
> +
> + // Set address of phy.
> + reg = readl(comm->sp_reg_base + SP_MAC_FORCE_MODE);
> + reg = (reg & (~(0x1f << 16))) | ((mac->phy_addr & 0x1f) << 16);
> + if (mac->next_ndev) {
> + struct net_device *ndev2 = mac->next_ndev;
> + struct sp_mac *mac2 = netdev_priv(ndev2);
> +
> + reg = (reg & (~(0x1f << 24))) | ((mac2->phy_addr & 0x1f) << 24);
> + }
> + writel(reg, comm->sp_reg_base + SP_MAC_FORCE_MODE);
> +}

As i said before, the hardware never directly communicates with the
PHY. So you can remove this.

> +static void port_status_change(struct sp_mac *mac)
> +{
> + u32 reg;
> + struct net_device *ndev = mac->ndev;
> +
> + reg = read_port_ability(mac);
> + if (!netif_carrier_ok(ndev) && (reg & PORT_ABILITY_LINK_ST_P0)) {
> + netif_carrier_on(ndev);

phylib should be handling the carrier for you.

> + if (mac->next_ndev) {
> + struct net_device *ndev2 = mac->next_ndev;
> +
> + if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
> + netif_carrier_on(ndev2);
> + netif_start_queue(ndev2);
> + } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
> + netif_carrier_off(ndev2);
> + netif_stop_queue(ndev2);
> + }

Looks very odd. The two netdev should be independent.

> diff --git a/drivers/net/ethernet/sunplus/sp_mdio.c b/drivers/net/ethernet/sunplus/sp_mdio.c
> new file mode 100644
> index 0000000..f6a7e64
> --- /dev/null
> +++ b/drivers/net/ethernet/sunplus/sp_mdio.c
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include "sp_mdio.h"
> +
> +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum)
> +{
> + int ret;
> +
> + ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
> + if (ret < 0)
> + return -EOPNOTSUPP;
> +
> + return ret;
> +}
> +
> +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val)
> +{
> + int ret;
> +
> + ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
> + if (ret < 0)
> + return -EOPNOTSUPP;
> +
> + return 0;
> +}
> +
> +static int mii_read(struct mii_bus *bus, int phy_id, int regnum)
> +{
> + struct sp_mac *mac = bus->priv;

What happened about my request to return -EOPNOTSUPP for C45 requests?

Andrew

2021-11-13 14:24:31

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi,

> 11/11/21 12:04 PM, Wells Lu пишет:
> > Add driver for Sunplus SP7021.
> >
> > Signed-off-by: Wells Lu <[email protected]>
> > ---
> > Changes in V2
> > - Addressed all comments from Mr. Philipp Zabel.
> > - Revised probe function.
> > - Addressed all comments from Mr. Randy Dunlap.
> > - Revised Kconfig
> > - Addressed all comments from Mr. Andrew Lunn.
> > - Removed daisy-chain (hub) function. Only keep dual NIC mode.
> > - Removed dynamic mode-switching function using sysfs.
> > - Removed unnecessary wmb().
> > - Replaced prefix l2sw_ with sp_ for struct, funciton and file names.
> > - Modified ethernet_do_ioctl() function.
> > - Removed ethernet_do_change_mtu() function.
> > - Revised Kconfig. Added '|| COMPILE_TEST'
> > - Others
> > - Replaced HWREG_R() and HWREG_W() macro with readl() and writel().
> > - Created new file sp_phy.c/sp_phy.h and moved phy-related functions in.
> > - Revised function name in sp_hal.c/.h to add hal_ prefix.
> >
> > MAINTAINERS | 1 +
> > drivers/net/ethernet/Kconfig | 1 +
> > drivers/net/ethernet/Makefile | 1 +
> > drivers/net/ethernet/sunplus/Kconfig | 36 ++
> > drivers/net/ethernet/sunplus/Makefile | 6 +
> > drivers/net/ethernet/sunplus/sp_define.h | 212 ++++++++++
> > drivers/net/ethernet/sunplus/sp_desc.c | 231 +++++++++++
> > drivers/net/ethernet/sunplus/sp_desc.h | 21 +
> > drivers/net/ethernet/sunplus/sp_driver.c | 606 +++++++++++++++++++++++++++++
> > drivers/net/ethernet/sunplus/sp_driver.h | 23 ++
> > drivers/net/ethernet/sunplus/sp_hal.c | 331 ++++++++++++++++
> > drivers/net/ethernet/sunplus/sp_hal.h | 31 ++
> > drivers/net/ethernet/sunplus/sp_int.c | 286 ++++++++++++++
> > drivers/net/ethernet/sunplus/sp_int.h | 13 +
> > drivers/net/ethernet/sunplus/sp_mac.c | 63 +++
> > drivers/net/ethernet/sunplus/sp_mac.h | 23 ++
> > drivers/net/ethernet/sunplus/sp_mdio.c | 90 +++++
> > drivers/net/ethernet/sunplus/sp_mdio.h | 20 +
> > drivers/net/ethernet/sunplus/sp_phy.c | 64 +++
> > drivers/net/ethernet/sunplus/sp_phy.h | 16 +
> > drivers/net/ethernet/sunplus/sp_register.h | 96 +++++
> > 21 files changed, 2171 insertions(+)
> > create mode 100644 drivers/net/ethernet/sunplus/Kconfig
> > create mode 100644 drivers/net/ethernet/sunplus/Makefile
> > create mode 100644 drivers/net/ethernet/sunplus/sp_define.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_desc.c
> > create mode 100644 drivers/net/ethernet/sunplus/sp_desc.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_driver.c
> > create mode 100644 drivers/net/ethernet/sunplus/sp_driver.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_hal.c
> > create mode 100644 drivers/net/ethernet/sunplus/sp_hal.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_int.c
> > create mode 100644 drivers/net/ethernet/sunplus/sp_int.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_mac.c
> > create mode 100644 drivers/net/ethernet/sunplus/sp_mac.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.c
> > create mode 100644 drivers/net/ethernet/sunplus/sp_mdio.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_phy.c
> > create mode 100644 drivers/net/ethernet/sunplus/sp_phy.h
> > create mode 100644 drivers/net/ethernet/sunplus/sp_register.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 737b9d0..ec1ddb1 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -18006,6 +18006,7 @@ L: [email protected]
> > S: Maintained
> > W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> > F: Documentation/devicetree/bindings/net/sunplus,sp7021-emac.yaml
> > +F: drivers/net/ethernet/sunplus/
> >
> > SUPERH
> > M: Yoshinori Sato <[email protected]>
> > diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
> > index 412ae3e..0084852 100644
> > --- a/drivers/net/ethernet/Kconfig
> > +++ b/drivers/net/ethernet/Kconfig
> > @@ -176,6 +176,7 @@ source "drivers/net/ethernet/smsc/Kconfig"
> > source "drivers/net/ethernet/socionext/Kconfig"
> > source "drivers/net/ethernet/stmicro/Kconfig"
> > source "drivers/net/ethernet/sun/Kconfig"
> > +source "drivers/net/ethernet/sunplus/Kconfig"
> > source "drivers/net/ethernet/synopsys/Kconfig"
> > source "drivers/net/ethernet/tehuti/Kconfig"
> > source "drivers/net/ethernet/ti/Kconfig"
> > diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
> > index aaa5078..e4ce162 100644
> > --- a/drivers/net/ethernet/Makefile
> > +++ b/drivers/net/ethernet/Makefile
> > @@ -87,6 +87,7 @@ obj-$(CONFIG_NET_VENDOR_SMSC) += smsc/
> > obj-$(CONFIG_NET_VENDOR_SOCIONEXT) += socionext/
> > obj-$(CONFIG_NET_VENDOR_STMICRO) += stmicro/
> > obj-$(CONFIG_NET_VENDOR_SUN) += sun/
> > +obj-$(CONFIG_NET_VENDOR_SUNPLUS) += sunplus/
> > obj-$(CONFIG_NET_VENDOR_TEHUTI) += tehuti/
> > obj-$(CONFIG_NET_VENDOR_TI) += ti/
> > obj-$(CONFIG_NET_VENDOR_TOSHIBA) += toshiba/
> > diff --git a/drivers/net/ethernet/sunplus/Kconfig b/drivers/net/ethernet/sunplus/Kconfig
> > new file mode 100644
> > index 0000000..5af2c5b
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/Kconfig
> > @@ -0,0 +1,36 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# Sunplus network device configuration
> > +#
> > +
> > +config NET_VENDOR_SUNPLUS
> > + bool "Sunplus devices"
> > + default y
> > + depends on ARCH_SUNPLUS || COMPILE_TEST
> > + help
> > + If you have a network (Ethernet) card belonging to this
> > + class, say Y here.
> > +
> > + Note that the answer to this question doesn't directly
> > + affect the kernel: saying N will just cause the configurator
> > + to skip all the questions about Sunplus cards. If you say Y,
> > + you will be asked for your specific card in the following
> > + questions.
> > +
> > +if NET_VENDOR_SUNPLUS
> > +
> > +config SP7021_EMAC
> > + tristate "Sunplus Dual 10M/100M Ethernet devices"
> > + depends on SOC_SP7021 || COMPILE_TEST
> > + select PHYLIB
> > + select PINCTRL_SPPCTL
> > + select COMMON_CLK_SP7021
> > + select RESET_SUNPLUS
> > + select NVMEM_SUNPLUS_OCOTP
> > + help
> > + If you have Sunplus dual 10M/100M Ethernet devices, say Y.
> > + The network device creates two net-device interfaces.
> > + To compile this driver as a module, choose M here. The
> > + module will be called sp7021_emac.
> > +
> > +endif # NET_VENDOR_SUNPLUS
> > diff --git a/drivers/net/ethernet/sunplus/Makefile b/drivers/net/ethernet/sunplus/Makefile
> > new file mode 100644
> > index 0000000..963ba1d
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/Makefile
> > @@ -0,0 +1,6 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# Makefile for the Sunplus network device drivers.
> > +#
> > +obj-$(CONFIG_SP7021_EMAC) += sp7021_emac.o
> > +sp7021_emac-objs := sp_driver.o sp_int.o sp_hal.o sp_desc.o sp_mac.o sp_mdio.o sp_phy.o
> > diff --git a/drivers/net/ethernet/sunplus/sp_define.h b/drivers/net/ethernet/sunplus/sp_define.h
> > new file mode 100644
> > index 0000000..40e15ba
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_define.h
> > @@ -0,0 +1,212 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_DEFINE_H__
> > +#define __SP_DEFINE_H__
> > +
> > +#include <linux/module.h>
> > +#include <linux/init.h>
> > +#include <linux/sched.h>
> > +#include <linux/kernel.h>
> > +#include <linux/slab.h>
> > +#include <linux/errno.h>
> > +#include <linux/types.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kdev_t.h>
> > +#include <linux/in.h>
> > +#include <linux/netdevice.h>
> > +#include <linux/etherdevice.h>
> > +#include <linux/ip.h>
> > +#include <linux/tcp.h>
> > +#include <linux/skbuff.h>
> > +#include <linux/ethtool.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/phy.h>
> > +#include <linux/mii.h>
> > +#include <linux/if_vlan.h>
> > +#include <linux/io.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_mdio.h>
> > +
> > +//define MAC interrupt status bit
> please embrace all comments with /* */

Do you mean to modify comment, for example,

//define MAC interrupt status bit

to

/* define MAC interrupt status bit */

for all commets in .h and .c files?


> > +#define MAC_INT_DAISY_MODE_CHG BIT(31)
> > +#define MAC_INT_IP_CHKSUM_ERR BIT(23)
> > +#define MAC_INT_WDOG_TIMER1_EXP BIT(22)
> > +#define MAC_INT_WDOG_TIMER0_EXP BIT(21)
> > +#define MAC_INT_INTRUDER_ALERT BIT(20)
> > +#define MAC_INT_PORT_ST_CHG BIT(19)
> > +#define MAC_INT_BC_STORM BIT(18)
> > +#define MAC_INT_MUST_DROP_LAN BIT(17)
> > +#define MAC_INT_GLOBAL_QUE_FULL BIT(16)
> > +#define MAC_INT_TX_SOC_PAUSE_ON BIT(15)
> > +#define MAC_INT_RX_SOC_QUE_FULL BIT(14)
> > +#define MAC_INT_TX_LAN1_QUE_FULL BIT(9)
> > +#define MAC_INT_TX_LAN0_QUE_FULL BIT(8)
> > +#define MAC_INT_RX_L_DESCF BIT(7)
> > +#define MAC_INT_RX_H_DESCF BIT(6)
> > +#define MAC_INT_RX_DONE_L BIT(5)
> > +#define MAC_INT_RX_DONE_H BIT(4)
> > +#define MAC_INT_TX_DONE_L BIT(3)
> > +#define MAC_INT_TX_DONE_H BIT(2)
> > +#define MAC_INT_TX_DES_ERR BIT(1)
> > +#define MAC_INT_RX_DES_ERR BIT(0)
> > +
> > +#define MAC_INT_RX (MAC_INT_RX_DONE_H | MAC_INT_RX_DONE_L | \
> > + MAC_INT_RX_DES_ERR)
> > +#define MAC_INT_TX (MAC_INT_TX_DONE_L | MAC_INT_TX_DONE_H | \
> > + MAC_INT_TX_DES_ERR)
> > +#define MAC_INT_MASK_DEF (MAC_INT_DAISY_MODE_CHG |
> MAC_INT_IP_CHKSUM_ERR | \
> > + MAC_INT_WDOG_TIMER1_EXP | MAC_INT_WDOG_TIMER0_EXP | \
> > + MAC_INT_INTRUDER_ALERT | MAC_INT_BC_STORM | \
> > + MAC_INT_MUST_DROP_LAN | MAC_INT_GLOBAL_QUE_FULL | \
> > + MAC_INT_TX_SOC_PAUSE_ON | MAC_INT_RX_SOC_QUE_FULL | \
> > + MAC_INT_TX_LAN1_QUE_FULL | MAC_INT_TX_LAN0_QUE_FULL | \
> > + MAC_INT_RX_L_DESCF | MAC_INT_RX_H_DESCF)
> > +
> > +/*define port ability*/
> > +#define PORT_ABILITY_LINK_ST_P1 BIT(25)
> > +#define PORT_ABILITY_LINK_ST_P0 BIT(24)
> > +
> > +/*define PHY command bit*/
> > +#define PHY_WT_DATA_MASK 0xffff0000
> > +#define PHY_RD_CMD 0x00004000
> > +#define PHY_WT_CMD 0x00002000
> > +#define PHY_REG_MASK 0x00001f00
> > +#define PHY_ADR_MASK 0x0000001f
> > +
> > +/*define PHY status bit*/
> > +#define PHY_RD_DATA_MASK 0xffff0000
> > +#define PHY_RD_RDY BIT(1)
> > +#define PHY_WT_DONE BIT(0)
> > +
> > +/*define other register bit*/
> > +#define RX_MAX_LEN_MASK 0x00011000
> > +#define ROUTE_MODE_MASK 0x00000060
> > +#define POK_INT_THS_MASK 0x000E0000
> > +#define VLAN_TH_MASK 0x00000007
> > +
> > +/*define tx descriptor bit*/
> > +#define OWN_BIT BIT(31)
> > +#define FS_BIT BIT(25)
> > +#define LS_BIT BIT(24)
> > +#define LEN_MASK 0x000007FF
> > +#define PKTSP_MASK 0x00007000
> > +#define PKTSP_PORT1 0x00001000
> > +#define TO_VLAN_MASK 0x0003F000
> > +#define TO_VLAN_GROUP1 0x00002000
> > +
> > +#define EOR_BIT BIT(31)
> > +
> > +/*define rx descriptor bit*/
> > +#define ERR_CODE (0xf << 26)
> > +#define RX_TCP_UDP_CHKSUM_BIT BIT(23)
> > +#define RX_IP_CHKSUM_BIT BIT(18)
> > +
> > +#define OWC_BIT BIT(31)
> > +#define TXOK_BIT BIT(26)
> > +#define LNKF_BIT BIT(25)
> > +#define BUR_BIT BIT(22)
> > +#define TWDE_BIT BIT(20)
> > +#define CC_MASK 0x000f0000
> > +#define TBE_MASK 0x00070000
> > +
> > +// Address table search
> > +#define MAC_ADDR_LOOKUP_IDLE BIT(2)
> > +#define MAC_SEARCH_NEXT_ADDR BIT(1)
> > +#define MAC_BEGIN_SEARCH_ADDR BIT(0)
> > +
> > +// Address table search
> > +#define MAC_HASK_LOOKUP_ADDR_MASK (0x3ff << 22)
> > +#define MAC_AT_TABLE_END BIT(1)
> > +#define MAC_AT_DATA_READY BIT(0)
> > +
> > +/*config descriptor*/
> > +#define TX_DESC_NUM 16
> > +#define MAC_GUARD_DESC_NUM 2
> > +#define RX_QUEUE0_DESC_NUM 16
> > +#define RX_QUEUE1_DESC_NUM 16
> > +#define TX_DESC_QUEUE_NUM 1
> > +#define RX_DESC_QUEUE_NUM 2
> > +
> > +#define MAC_TX_BUFF_SIZE 1536
> > +#define MAC_RX_LEN_MAX 2047
> > +
> > +#define DESC_ALIGN_BYTE 32
> > +#define RX_OFFSET 0
> > +#define TX_OFFSET 0
> > +
> > +#define ETHERNET_MAC_ADDR_LEN 6
> > +
> > +struct mac_desc {
> > + u32 cmd1;
> > + u32 cmd2;
> > + u32 addr1;
> > + u32 addr2;
> > +};
> > +
> > +struct skb_info {
> > + struct sk_buff *skb;
> > + u32 mapping;
> > + u32 len;
> > +};
> > +
> > +struct sp_common {
> > + void __iomem *sp_reg_base;
> > + void __iomem *moon5_reg_base;
> > +
> > + struct net_device *ndev;
> > + struct platform_device *pdev;
> > +
> > + void *desc_base;
> > + dma_addr_t desc_dma;
> > + s32 desc_size;
> > + struct clk *clk;
> > + struct reset_control *rstc;
> > + int irq;
> > +
> > + struct mac_desc *rx_desc[RX_DESC_QUEUE_NUM];
> > + struct skb_info *rx_skb_info[RX_DESC_QUEUE_NUM];
> > + u32 rx_pos[RX_DESC_QUEUE_NUM];
> > + u32 rx_desc_num[RX_DESC_QUEUE_NUM];
> > + u32 rx_desc_buff_size;
> > +
> > + struct mac_desc *tx_desc;
> > + struct skb_info tx_temp_skb_info[TX_DESC_NUM];
> > + u32 tx_done_pos;
> > + u32 tx_pos;
> > + u32 tx_desc_full;
> > +
> > + struct mii_bus *mii_bus;
> > +
> > + struct napi_struct rx_napi;
> > + struct napi_struct tx_napi;
> > +
> > + spinlock_t rx_lock; // spinlock for accessing rx buffer
> > + spinlock_t tx_lock; // spinlock for accessing tx buffer
> > + spinlock_t ioctl_lock; // spinlock for ioctl operations
> > +
> > + u8 enable;
> > +};
> > +
> > +struct sp_mac {
> > + struct net_device *ndev;
> > + struct net_device *next_ndev;
> > + struct phy_device *phy_dev;
> > + struct sp_common *comm;
> > + struct net_device_stats dev_stats;
> > + struct device_node *phy_node;
> > + phy_interface_t phy_mode;
> > + u32 phy_addr;
> > +
> > + u8 mac_addr[ETHERNET_MAC_ADDR_LEN];
> > +
> > + u8 lan_port;
> > + u8 to_vlan;
> > + u8 cpu_port;
> > + u8 vlan_id;
> > +};
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_desc.c b/drivers/net/ethernet/sunplus/sp_desc.c
> > new file mode 100644
> > index 0000000..fed91ec
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_desc.c
> > @@ -0,0 +1,231 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include "sp_desc.h"
> > +#include "sp_define.h"
> > +
> > +void rx_descs_flush(struct sp_common *comm)
> > +{
> > + u32 i, j;
> > + struct mac_desc *rx_desc;
> > + struct skb_info *rx_skbinfo;
> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > + rx_desc = comm->rx_desc[i];
> > + rx_skbinfo = comm->rx_skb_info[i];
> > + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> > + rx_desc[j].addr1 = rx_skbinfo[j].mapping;
> > + rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
> > + EOR_BIT | comm->rx_desc_buff_size :
> > + comm->rx_desc_buff_size;
> > + wmb(); // Set OWN_BIT after other fields are ready.
> > + rx_desc[j].cmd1 = OWN_BIT;
> > + }
> > + }
> > +}
> > +
> > +void tx_descs_clean(struct sp_common *comm)
> > +{
> > + u32 i;
> > + s32 buflen;
> > +
> > + if (!comm->tx_desc)
> > + return;
> > +
> > + for (i = 0; i < TX_DESC_NUM; i++) {
> > + comm->tx_desc[i].cmd1 = 0;
> > + wmb(); // Clear OWN_BIT and then set other fields.
> > + comm->tx_desc[i].cmd2 = 0;
> > + comm->tx_desc[i].addr1 = 0;
> > + comm->tx_desc[i].addr2 = 0;
> > +
> > + if (comm->tx_temp_skb_info[i].mapping) {
> > + buflen = (comm->tx_temp_skb_info[i].skb) ?
> > + comm->tx_temp_skb_info[i].skb->len :
> > + MAC_TX_BUFF_SIZE;
> > + dma_unmap_single(&comm->pdev->dev, comm->tx_temp_skb_info[i].mapping,
> > + buflen, DMA_TO_DEVICE);
> > + comm->tx_temp_skb_info[i].mapping = 0;
> > + }
> > +
> > + if (comm->tx_temp_skb_info[i].skb) {
> > + dev_kfree_skb(comm->tx_temp_skb_info[i].skb);
> > + comm->tx_temp_skb_info[i].skb = NULL;
> > + }
> > + }
> > +}
> > +
> > +void rx_descs_clean(struct sp_common *comm)
> > +{
> > + u32 i, j;
> > + struct mac_desc *rx_desc;
> > + struct skb_info *rx_skbinfo;
> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > + if (!comm->rx_skb_info[i])
> > + continue;
> > +
> > + rx_desc = comm->rx_desc[i];
> > + rx_skbinfo = comm->rx_skb_info[i];
> > + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> > + rx_desc[j].cmd1 = 0;
> > + wmb(); // Clear OWN_BIT and then set other fields.
> > + rx_desc[j].cmd2 = 0;
> > + rx_desc[j].addr1 = 0;
> > +
> > + if (rx_skbinfo[j].skb) {
> > + dma_unmap_single(&comm->pdev->dev, rx_skbinfo[j].mapping,
> > + comm->rx_desc_buff_size, DMA_FROM_DEVICE);
> > + dev_kfree_skb(rx_skbinfo[j].skb);
> > + rx_skbinfo[j].skb = NULL;
> > + rx_skbinfo[j].mapping = 0;
> > + }
> > + }
> > +
> > + kfree(rx_skbinfo);
> > + comm->rx_skb_info[i] = NULL;
> > + }
> > +}
> > +
> > +void descs_clean(struct sp_common *comm)
> > +{
> > + rx_descs_clean(comm);
> > + tx_descs_clean(comm);
> > +}
> > +
> > +void descs_free(struct sp_common *comm)
> > +{
> > + u32 i;
> > +
> > + descs_clean(comm);
> > + comm->tx_desc = NULL;
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> > + comm->rx_desc[i] = NULL;
> > +
> > + /* Free descriptor area */
> > + if (comm->desc_base) {
> > + dma_free_coherent(&comm->pdev->dev, comm->desc_size, comm->desc_base,
> > + comm->desc_dma);
> > + comm->desc_base = NULL;
> > + comm->desc_dma = 0;
> > + comm->desc_size = 0;
> > + }
> > +}
> > +
> > +void tx_descs_init(struct sp_common *comm)
> > +{
> > + memset(comm->tx_desc, '\0', sizeof(struct mac_desc) *
> > + (TX_DESC_NUM + MAC_GUARD_DESC_NUM));
> > +}
> > +
> > +int rx_descs_init(struct sp_common *comm)
> > +{
> > + struct sk_buff *skb;
> > + u32 i, j;
> > + struct mac_desc *rx_desc;
> > + struct skb_info *rx_skbinfo;
> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > + comm->rx_skb_info[i] = kmalloc_array(comm->rx_desc_num[i],
> > + sizeof(struct skb_info), GFP_KERNEL);
> > + if (!comm->rx_skb_info[i])
> > + goto MEM_ALLOC_FAIL;
> > +
> > + rx_skbinfo = comm->rx_skb_info[i];
> > + rx_desc = comm->rx_desc[i];
> > + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> > + skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
> > + GFP_ATOMIC | GFP_DMA);
> > + if (!skb)
> > + goto MEM_ALLOC_FAIL;
> > +
> > + skb->dev = comm->ndev;
> > + skb_reserve(skb, RX_OFFSET); /* +data +tail */
> > +
> > + rx_skbinfo[j].skb = skb;
> > + rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
> > + comm->rx_desc_buff_size,
> > + DMA_FROM_DEVICE);
> it may fail

Yes, I'll add error check in next patch as shown below:

rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
comm->rx_desc_buff_size,
DMA_FROM_DEVICE);
if (dma_mapping_error(&comm->pdev->dev, rx_skbinfo[j].mapping))
goto mem_alloc_fail;

[...]
Return 0;

mem_alloc_fail:
rx_desc_clean(comm);
return -ENOMEM;
}


> > + rx_desc[j].addr1 = rx_skbinfo[j].mapping;
> > + rx_desc[j].addr2 = 0;
> > + rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
> > + EOR_BIT | comm->rx_desc_buff_size :
> > + comm->rx_desc_buff_size;
> > + wmb(); // Set OWN_BIT after other fields are effective.
> > + rx_desc[j].cmd1 = OWN_BIT;
> > + }
> > + }
> > +
> > + return 0;
> > +
> > +MEM_ALLOC_FAIL:
> lowercase

Yes, I'll modify the label and other labels to lowercase in next patch.


> > + rx_descs_clean(comm);
> > + return -ENOMEM;
> > +}
> > +
> > +int descs_alloc(struct sp_common *comm)
> > +{
> > + u32 i;
> > + s32 desc_size;
> > +
> > + /* Alloc descriptor area */
> > + desc_size = (TX_DESC_NUM + MAC_GUARD_DESC_NUM) * sizeof(struct mac_desc);
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> > + desc_size += comm->rx_desc_num[i] * sizeof(struct mac_desc);
> > +
> > + comm->desc_base = dma_alloc_coherent(&comm->pdev->dev, desc_size, &comm->desc_dma,
> > + GFP_KERNEL);
> > + if (!comm->desc_base)
> > + return -ENOMEM;
> > +
> > + comm->desc_size = desc_size;
> > +
> > + /* Setup Tx descriptor */
> > + comm->tx_desc = (struct mac_desc *)comm->desc_base;
> > +
> > + /* Setup Rx descriptor */
> > + comm->rx_desc[0] = &comm->tx_desc[TX_DESC_NUM + MAC_GUARD_DESC_NUM];
> > + for (i = 1; i < RX_DESC_QUEUE_NUM; i++)
> > + comm->rx_desc[i] = comm->rx_desc[i - 1] + comm->rx_desc_num[i - 1];
> > +
> > + return 0;
> > +}
> > +
> > +int descs_init(struct sp_common *comm)
> > +{
> > + u32 i, ret;
> > +
> > + // Initialize rx descriptor's data
> > + comm->rx_desc_num[0] = RX_QUEUE0_DESC_NUM;
> > +#if RX_DESC_QUEUE_NUM > 1
> > + comm->rx_desc_num[1] = RX_QUEUE1_DESC_NUM;
> > +#endif
> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > + comm->rx_desc[i] = NULL;
> > + comm->rx_skb_info[i] = NULL;
> > + comm->rx_pos[i] = 0;
> > + }
> > + comm->rx_desc_buff_size = MAC_RX_LEN_MAX;
> > +
> > + // Initialize tx descriptor's data
> > + comm->tx_done_pos = 0;
> > + comm->tx_desc = NULL;
> > + comm->tx_pos = 0;
> > + comm->tx_desc_full = 0;
> > + for (i = 0; i < TX_DESC_NUM; i++)
> > + comm->tx_temp_skb_info[i].skb = NULL;
> > +
> > + // Allocate tx & rx descriptors.
> > + ret = descs_alloc(comm);
> > + if (ret) {
> > + netdev_err(comm->ndev, "Failed to allocate tx & rx descriptors!\n");
> > + return ret;
> > + }
> > +
> > + tx_descs_init(comm);
> > +
> > + return rx_descs_init(comm);
> > +}
> > diff --git a/drivers/net/ethernet/sunplus/sp_desc.h b/drivers/net/ethernet/sunplus/sp_desc.h
> > new file mode 100644
> > index 0000000..20f7519
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_desc.h
> > @@ -0,0 +1,21 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_DESC_H__
> > +#define __SP_DESC_H__
> > +
> > +#include "sp_define.h"
> > +
> > +void rx_descs_flush(struct sp_common *comm);
> > +void tx_descs_clean(struct sp_common *comm);
> > +void rx_descs_clean(struct sp_common *comm);
> > +void descs_clean(struct sp_common *comm);
> > +void descs_free(struct sp_common *comm);
> > +void tx_descs_init(struct sp_common *comm);
> > +int rx_descs_init(struct sp_common *comm);
> > +int descs_alloc(struct sp_common *comm);
> > +int descs_init(struct sp_common *comm);
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_driver.c b/drivers/net/ethernet/sunplus/sp_driver.c
> > new file mode 100644
> > index 0000000..a1c76b9
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_driver.c
> > @@ -0,0 +1,606 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/reset.h>
> > +#include <linux/nvmem-consumer.h>
> > +#include <linux/of_net.h>
> > +#include "sp_driver.h"
> > +#include "sp_phy.h"
> > +
> > +static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
> > + 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00
> > +};
> > +
> > +/*********************************************************************
> > + *
> > + * net_device_ops
> > + *
> > + **********************************************************************/
> > +static int ethernet_open(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + netdev_dbg(ndev, "Open port = %x\n", mac->lan_port);
> > +
> > + mac->comm->enable |= mac->lan_port;
> > +
> > + hal_mac_start(mac);
> > + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~(MAC_INT_TX | MAC_INT_RX));
> > +
> > + netif_carrier_on(ndev);
> > + if (netif_carrier_ok(ndev))
> > + netif_start_queue(ndev);
> > +
> > + return 0;
> > +}
> > +
> > +static int ethernet_stop(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + netif_stop_queue(ndev);
> > + netif_carrier_off(ndev);
> > +
> > + mac->comm->enable &= ~mac->lan_port;
> > +
> > + hal_mac_stop(mac);
> > +
> > + return 0;
> > +}
> > +
> > +/* Transmit a packet (called by the kernel) */
> > +static int ethernet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > + struct sp_common *comm = mac->comm;
> > + u32 tx_pos;
> > + u32 cmd1;
> > + u32 cmd2;
> > + struct mac_desc *txdesc;
> > + struct skb_info *skbinfo;
> > + unsigned long flags;
> > +
> > + if (unlikely(comm->tx_desc_full == 1)) {
> > + // No TX descriptors left. Wait for tx interrupt.
> > + netdev_info(ndev, "TX descriptor queue full when xmit!\n");
> > + return NETDEV_TX_BUSY;
> Do you really have to return NETDEV_TX_BUSY?

(tx_desc_full == 1) means there is no TX descriptor left in ring buffer.
So there is no way to do new transmit. Return 'busy' directly.
I am not sure if this is a correct process or not.
Could you please teach is there any other way to take care of this case?
Drop directly?


> > + }
> > +
> > + /* if skb size shorter than 60, fill it with '\0' */
> > + if (unlikely(skb->len < ETH_ZLEN)) {
> > + if (skb_tailroom(skb) >= (ETH_ZLEN - skb->len)) {
> > + memset(__skb_put(skb, ETH_ZLEN - skb->len), '\0',
> > + ETH_ZLEN - skb->len);
> > + } else {
> > + struct sk_buff *old_skb = skb;
> > +
> > + skb = dev_alloc_skb(ETH_ZLEN + TX_OFFSET);
> > + if (skb) {
> > + memset(skb->data + old_skb->len, '\0',
> > + ETH_ZLEN - old_skb->len);
> > + memcpy(skb->data, old_skb->data, old_skb->len);
> > + skb_put(skb, ETH_ZLEN); /* add data to an sk_buff */
> > + dev_kfree_skb_irq(old_skb);
> > + } else {
> > + skb = old_skb;
> > + }
> > + }
> > + }
> > +
> > + spin_lock_irqsave(&comm->tx_lock, flags);
> > + tx_pos = comm->tx_pos;
> > + txdesc = &comm->tx_desc[tx_pos];
> > + skbinfo = &comm->tx_temp_skb_info[tx_pos];
> > + skbinfo->len = skb->len;
> > + skbinfo->skb = skb;
> > + skbinfo->mapping = dma_map_single(&comm->pdev->dev, skb->data,
> > + skb->len, DMA_TO_DEVICE);
> it may fail

Yes, I'll add error check in next patch as shown below:

skbinfo->mapping = dma_map_single(&comm->pdev->dev, skb->data,
skb->len, DMA_TO_DEVICE);
if (dma_mapping_error(&comm->pdev->dev, skbinfo->mapping)) {
ndev->stats.tx_errors++;
skbinfo->mapping = 0;
dev_kfree_skb_irq(skb);
skbinfo->skb = NULL;
goto xmit_drop;
}

[...]

/* trigger mac to transmit */
hal_tx_trigger(mac);

xmit_drop:
spin_unlock_irqrestore(&mac->comm->tx_lock, flags);
return NETDEV_TX_OK;
}

> > + cmd1 = (OWN_BIT | FS_BIT | LS_BIT | (mac->to_vlan << 12) | (skb->len & LEN_MASK));
> > + cmd2 = skb->len & LEN_MASK;
> > +
> > + if (tx_pos == (TX_DESC_NUM - 1))
> > + cmd2 |= EOR_BIT;
> > +
> > + txdesc->addr1 = skbinfo->mapping;
> > + txdesc->cmd2 = cmd2;
> > + wmb(); // Set OWN_BIT after other fields of descriptor are effective.
> > + txdesc->cmd1 = cmd1;
> > +
> > + NEXT_TX(tx_pos);
> > +
> > + if (unlikely(tx_pos == comm->tx_done_pos)) {
> > + netif_stop_queue(ndev);
> > + comm->tx_desc_full = 1;
> > + }
> > + comm->tx_pos = tx_pos;
> > + wmb(); // make sure settings are effective.
> > +
> > + /* trigger gmac to transmit */
> > + hal_tx_trigger(mac);
> > +
> > + spin_unlock_irqrestore(&mac->comm->tx_lock, flags);
> > + return NETDEV_TX_OK;
> > +}
> > +
> > +static void ethernet_set_rx_mode(struct net_device *ndev)
> > +{
> > + if (ndev) {
> > + struct sp_mac *mac = netdev_priv(ndev);
> > + struct sp_common *comm = mac->comm;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&comm->ioctl_lock, flags);
> > + hal_rx_mode_set(ndev);
> > + spin_unlock_irqrestore(&comm->ioctl_lock, flags);
> > + }
> > +}
> > +
> > +static int ethernet_set_mac_address(struct net_device *ndev, void *addr)
> > +{
> > + struct sockaddr *hwaddr = (struct sockaddr *)addr;
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (netif_running(ndev))
> > + return -EBUSY;
> > +
> > + memcpy(ndev->dev_addr, hwaddr->sa_data, ndev->addr_len);
> > +
> > + /* Delete the old Ethernet MAC address */
> > + netdev_dbg(ndev, "HW Addr = %pM\n", mac->mac_addr);
> > + if (is_valid_ether_addr(mac->mac_addr))
> > + hal_mac_addr_del(mac);
> > +
> > + /* Set the Ethernet MAC address */
> > + memcpy(mac->mac_addr, hwaddr->sa_data, ndev->addr_len);
> > + hal_mac_addr_set(mac);
> > +
> > + return 0;
> > +}
> > +
> > +static int ethernet_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + switch (cmd) {
> > + case SIOCGMIIPHY:
> > + case SIOCGMIIREG:
> > + case SIOCSMIIREG:
> > + return phy_mii_ioctl(mac->phy_dev, ifr, cmd);
> > + }
> > +
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static void ethernet_tx_timeout(struct net_device *ndev, unsigned int txqueue)
> > +{
> > +}
> the empty function?

I'll add tx_timeout function in next patch as shown below:

static void ethernet_tx_timeout(struct net_device *ndev, unsigned int txqueue)
{
struct sp_mac *mac = netdev_priv(ndev);
struct net_device *ndev2;
unsigned long flags;

netdev_err(ndev, "TX timed out!\n");
ndev->stats.tx_errors++;

spin_lock_irqsave(&mac->comm->tx_lock, flags);
netif_stop_queue(ndev);
ndev2 = mac->next_ndev;
if (ndev2)
netif_stop_queue(ndev2);

hal_mac_stop(mac);
hal_mac_init(mac);
hal_mac_start(mac);

// Accept TX packets again.
netif_trans_update(ndev);
netif_wake_queue(ndev);
if (ndev2) {
netif_trans_update(ndev2);
netif_wake_queue(ndev2);
}

spin_unlock_irqrestore(&mac->comm->tx_lock, flags);
}

Is that ok?


> > +static struct net_device_stats *ethernet_get_stats(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac;
> > +
> > + mac = netdev_priv(ndev);
> > + return &mac->dev_stats;
> > +}
> > +
> > +static const struct net_device_ops netdev_ops = {
> > + .ndo_open = ethernet_open,
> > + .ndo_stop = ethernet_stop,
> > + .ndo_start_xmit = ethernet_start_xmit,
> > + .ndo_set_rx_mode = ethernet_set_rx_mode,
> > + .ndo_set_mac_address = ethernet_set_mac_address,
> > + .ndo_do_ioctl = ethernet_do_ioctl,
> > + .ndo_tx_timeout = ethernet_tx_timeout,
> > + .ndo_get_stats = ethernet_get_stats,
> > +};
> > +
> > +char *sp7021_otp_read_mac(struct device *dev, ssize_t *len, char *name)
> > +{
> > + char *ret = NULL;
> > + struct nvmem_cell *cell = nvmem_cell_get(dev, name);
> > +
> > + if (IS_ERR_OR_NULL(cell)) {
> > + dev_err(dev, "OTP %s read failure: %ld", name, PTR_ERR(cell));
> > + return NULL;
> > + }
> > +
> > + ret = nvmem_cell_read(cell, len);
> > + nvmem_cell_put(cell);
> > + dev_dbg(dev, "%zd bytes are read from OTP %s.", *len, name);
> > +
> > + return ret;
> > +}
> > +
> > +static void check_mac_vendor_id_and_convert(char *mac_addr)
> > +{
> > + // Byte order of MAC address of some samples are reversed.
> > + // Check vendor id and convert byte order if it is wrong.
> > + if ((mac_addr[5] == 0xFC) && (mac_addr[4] == 0x4B) && (mac_addr[3] == 0xBC) &&
> > + ((mac_addr[0] != 0xFC) || (mac_addr[1] != 0x4B) || (mac_addr[2] != 0xBC))) {
> > + char tmp;
> > +
> > + // Swap mac_addr[0] and mac_addr[5]
> > + tmp = mac_addr[0];
> > + mac_addr[0] = mac_addr[5];
> > + mac_addr[5] = tmp;
> > +
> > + // Swap mac_addr[1] and mac_addr[4]
> > + tmp = mac_addr[1];
> > + mac_addr[1] = mac_addr[4];
> > + mac_addr[4] = tmp;
> > +
> > + // Swap mac_addr[2] and mac_addr[3]
> > + tmp = mac_addr[2];
> > + mac_addr[2] = mac_addr[3];
> > + mac_addr[3] = tmp;
> > + }
> > +}
> > +
> > +/*********************************************************************
> > + *
> > + * platform_driver
> > + *
> > + **********************************************************************/
> > +static u32 init_netdev(struct platform_device *pdev, int eth_no, struct net_device **r_ndev)
> > +{
> > + struct sp_mac *mac;
> > + struct net_device *ndev;
> > + char *m_addr_name = (eth_no == 0) ? "mac_addr0" : "mac_addr1";
> > + ssize_t otp_l = 0;
> > + char *otp_v;
> > + int ret;
> > +
> > + // Allocate the devices, and also allocate sp_mac, we can get it by netdev_priv().
> > + ndev = alloc_etherdev(sizeof(*mac));
> > + if (!ndev) {
> > + *r_ndev = NULL;
> > + return -ENOMEM;
> please check the function return value

I am not sure the meaning of this comment.
Do you mean to modify:

// Initialize the 1st net device.
ret = init_netdev(pdev, 0, &ndev);
if (!ndev)
return ret;

to:

// Initialize the 1st net device.
ret = init_netdev(pdev, 0, &ndev);
if (ret)
return ret;

?


> > + }
> > + SET_NETDEV_DEV(ndev, &pdev->dev);
> > + ndev->netdev_ops = &netdev_ops;
> > +
> > + mac = netdev_priv(ndev);
> > + mac->ndev = ndev;
> > + mac->next_ndev = NULL;
> > +
> > + // Get property 'mac-addr0' or 'mac-addr1' from dts.
> > + otp_v = sp7021_otp_read_mac(&pdev->dev, &otp_l, m_addr_name);
> > + if ((otp_l < 6) || IS_ERR_OR_NULL(otp_v)) {
> > + dev_info(&pdev->dev, "OTP mac %s (len = %zd) is invalid, using default!\n",
> > + m_addr_name, otp_l);
> > + otp_l = 0;
> > + } else {
> > + // Check if mac-address is valid or not. If not, copy from default.
> > + memcpy(mac->mac_addr, otp_v, 6);
> > +
> > + // Byte order of Some samples are reversed. Convert byte order here.
> > + check_mac_vendor_id_and_convert(mac->mac_addr);
> > +
> > + if (!is_valid_ether_addr(mac->mac_addr)) {
> > + dev_info(&pdev->dev, "Invalid mac in OTP[%s] = %pM, use default!\n",
> > + m_addr_name, mac->mac_addr);
> > + otp_l = 0;
> > + }
> > + }
> > + if (otp_l != 6) {
> > + memcpy(mac->mac_addr, def_mac_addr, ETHERNET_MAC_ADDR_LEN);
> > + mac->mac_addr[5] += eth_no;
> > + }
> > +
> > + dev_info(&pdev->dev, "HW Addr = %pM\n", mac->mac_addr);
> > +
> > + memcpy(ndev->dev_addr, mac->mac_addr, ETHERNET_MAC_ADDR_LEN);
> > +
> > + ret = register_netdev(ndev);
> > + if (ret) {
> > + dev_err(&pdev->dev, "Failed to register net device \"%s\"!\n",
> > + ndev->name);
> > + free_netdev(ndev);
> > + *r_ndev = NULL;
> > + return ret;
> > + }
> > + netdev_info(ndev, "Registered net device \"%s\" successfully.\n", ndev->name);
> > +
> > + *r_ndev = ndev;
> > + return 0;
> > +}
> > +
> > +static int soc0_open(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 ret;
> > +
> > + hal_mac_stop(mac);
> > +
> > + ret = descs_init(comm);
> > + if (ret) {
> > + netdev_err(mac->ndev, "Fail to initialize mac descriptors!\n");
> > + descs_free(comm);
> > + return ret;
> > + }
> > +
> > + mac_init(mac);
> > + return 0;
> > +}
> > +
> > +static int soc0_stop(struct sp_mac *mac)
> > +{
> > + hal_mac_stop(mac);
> > +
> > + descs_free(mac->comm);
> > + return 0;
> > +}
> > +
> > +static int sp_probe(struct platform_device *pdev)
> > +{
> > + struct sp_common *comm;
> > + struct resource *rc;
> > + struct net_device *ndev, *ndev2;
> > + struct device_node *np;
> > + struct sp_mac *mac, *mac2;
> > + int ret;
> > +
> > + if (platform_get_drvdata(pdev))
> > + return -ENODEV;
> > +
> > + // Allocate memory for 'sp_common' area.
> > + comm = devm_kzalloc(&pdev->dev, sizeof(*comm), GFP_KERNEL);
> > + if (!comm)
> > + return -ENOMEM;
> > + comm->pdev = pdev;
> > +
> > + spin_lock_init(&comm->rx_lock);
> > + spin_lock_init(&comm->tx_lock);
> > + spin_lock_init(&comm->ioctl_lock);
> > +
> > + // Get memory resoruce "emac" from dts.
> > + rc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "emac");
> > + if (!rc) {
> > + dev_err(&pdev->dev, "No MEM resource \'emac\' found!\n");
> > + return -ENXIO;
> > + }
> > + dev_dbg(&pdev->dev, "name = \"%s\", start = %pa\n", rc->name, &rc->start);
> > +
> > + comm->sp_reg_base = devm_ioremap_resource(&pdev->dev, rc);
> > + if (IS_ERR(comm->sp_reg_base)) {
> > + dev_err(&pdev->dev, "ioremap failed!\n");
> > + return -ENOMEM;
> > + }
> > +
> > + // Get memory resoruce "moon5" from dts.
> > + rc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "moon5");
> > + if (!rc) {
> > + dev_err(&pdev->dev, "No MEM resource \'moon5\' found!\n");
> > + return -ENXIO;
> > + }
> > + dev_dbg(&pdev->dev, "name = \"%s\", start = %pa\n", rc->name, &rc->start);
> > +
> > + // Note that moon5 is shared resource. Don't use devm_ioremap_resource().
> > + comm->moon5_reg_base = devm_ioremap(&pdev->dev, rc->start, rc->end - rc->start + 1);
> > + if (IS_ERR(comm->moon5_reg_base)) {
> > + dev_err(&pdev->dev, "ioremap failed!\n");
> > + return -ENOMEM;
> > + }
> > +
> > + // Get irq resource from dts.
> > + ret = platform_get_irq(pdev, 0);
> > + if (ret < 0)
> > + return ret;
> > + comm->irq = ret;
> > +
> > + // Get clock controller.
> > + comm->clk = devm_clk_get(&pdev->dev, NULL);
> > + if (IS_ERR(comm->clk)) {
> > + dev_err_probe(&pdev->dev, PTR_ERR(comm->clk),
> > + "Failed to retrieve clock controller!\n");
> > + return PTR_ERR(comm->clk);
> > + }
> > +
> > + // Get reset controller.
> > + comm->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
> > + if (IS_ERR(comm->rstc)) {
> > + dev_err_probe(&pdev->dev, PTR_ERR(comm->rstc),
> > + "Failed to retrieve reset controller!\n");
> > + return PTR_ERR(comm->rstc);
> > + }
> > +
> > + // Enable clock.
> > + clk_prepare_enable(comm->clk);
> > + udelay(1);
> > +
> > + reset_control_assert(comm->rstc);
> > + udelay(1);
> > + reset_control_deassert(comm->rstc);
> > + udelay(1);
> > +
> > + // Initialize the 1st net device.
> > + ret = init_netdev(pdev, 0, &ndev);
> > + if (!ndev)
> > + return ret;
> > +
> > + platform_set_drvdata(pdev, ndev);
> > +
> > + ndev->irq = comm->irq;
> > + mac = netdev_priv(ndev);
> > + mac->comm = comm;
> > + comm->ndev = ndev;
> > +
> > + // Get node of phy 1.
> > + mac->phy_node = of_parse_phandle(pdev->dev.of_node, "phy-handle1", 0);
> > + if (!mac->phy_node) {
> > + netdev_info(ndev, "Cannot get node of phy 1!\n");
> > + ret = -ENODEV;
> > + goto out_unregister_dev;
> > + }
> > +
> > + // Get address of phy from dts.
> > + if (of_property_read_u32(mac->phy_node, "reg", &mac->phy_addr)) {
> > + mac->phy_addr = 0;
> > + netdev_info(ndev, "Cannot get address of phy 1! Set to 0.\n");
> > + }
> > +
> > + // Get mode of phy from dts.
> > + if (of_get_phy_mode(mac->phy_node, &mac->phy_mode)) {
> > + mac->phy_mode = PHY_INTERFACE_MODE_RGMII_ID;
> > + netdev_info(ndev, "Missing phy-mode of phy 1! Set to \'rgmii-id\'.\n");
> > + }
> > +
> > + // Request irq.
> > + ret = devm_request_irq(&pdev->dev, comm->irq, ethernet_interrupt, 0,
> > + ndev->name, ndev);
> > + if (ret) {
> > + netdev_err(ndev, "Failed to request irq #%d for \"%s\"!\n",
> > + ndev->irq, ndev->name);
> > + goto out_unregister_dev;
> > + }
> > +
> > + mac->cpu_port = 0x1; // soc0
> > + mac->lan_port = 0x1; // forward to port 0
> > + mac->to_vlan = 0x1; // vlan group: 0
> > + mac->vlan_id = 0x0; // vlan group: 0
> > +
> > + // Set MAC address
> > + hal_mac_addr_set(mac);
> > + hal_rx_mode_set(ndev);
> > + hal_mac_addr_table_del_all(mac);
> > +
> > + ndev2 = NULL;
> > + np = of_parse_phandle(pdev->dev.of_node, "phy-handle2", 0);
> > + if (np) {
> > + init_netdev(pdev, 1, &ndev2);
> > + if (ndev2) {
> > + mac->next_ndev = ndev2; // Point to the second net device.
> > +
> > + ndev2->irq = comm->irq;
> > + mac2 = netdev_priv(ndev2);
> > + mac2->comm = comm;
> > + mac2->phy_node = np;
> > +
> > + if (of_property_read_u32(mac2->phy_node, "reg", &mac2->phy_addr)) {
> > + mac2->phy_addr = 1;
> > + netdev_info(ndev2, "Cannot get address of phy 2! Set to 1.\n");
> > + }
> > +
> > + if (of_get_phy_mode(mac2->phy_node, &mac2->phy_mode)) {
> > + mac2->phy_mode = PHY_INTERFACE_MODE_RGMII_ID;
> > + netdev_info(ndev, "Missing phy-mode phy 2! Set to \'rgmii-id\'.\n");
> > + }
> > +
> > + mac2->cpu_port = 0x1; // soc0
> > + mac2->lan_port = 0x2; // forward to port 1
> > + mac2->to_vlan = 0x2; // vlan group: 1
> > + mac2->vlan_id = 0x1; // vlan group: 1
> > +
> > + hal_mac_addr_set(mac2); // Set MAC address for the 2nd net device.
> > + hal_rx_mode_set(ndev2);
> > + }
> > + }
> > +
> > + soc0_open(mac);
> > + hal_set_rmii_tx_rx_pol(mac);
> > + hal_phy_addr(mac);
> > +
> > + ret = mdio_init(pdev, ndev);
> > + if (ret) {
> > + netdev_err(ndev, "Failed to initialize mdio!\n");
> > + goto out_unregister_dev;
> > + }
> > +
> > + ret = sp_phy_probe(ndev);
> > + if (ret) {
> > + netdev_err(ndev, "Failed to probe phy!\n");
> > + goto out_freemdio;
> > + }
> > +
> > + if (ndev2) {
> > + ret = sp_phy_probe(ndev2);
> > + if (ret) {
> > + netdev_err(ndev2, "Failed to probe phy!\n");
> > + unregister_netdev(ndev2);
> > + mac->next_ndev = 0;
> > + }
> > + }
> > +
> > + netif_napi_add(ndev, &comm->rx_napi, rx_poll, RX_NAPI_WEIGHT);
> > + napi_enable(&comm->rx_napi);
> > + netif_napi_add(ndev, &comm->tx_napi, tx_poll, TX_NAPI_WEIGHT);
> > + napi_enable(&comm->tx_napi);
> > + return 0;
> > +
> > +out_freemdio:
> > + if (comm->mii_bus)
> > + mdio_remove(ndev);
> > +
> > +out_unregister_dev:
> > + unregister_netdev(ndev);
> > + if (ndev2)
> > + unregister_netdev(ndev2);
> > +
> > + return ret;
> > +}
> > +
> > +static int sp_remove(struct platform_device *pdev)
> > +{
> > + struct net_device *ndev, *ndev2;
> > + struct sp_mac *mac;
> > +
> > + ndev = platform_get_drvdata(pdev);
> > + if (!ndev)
> > + return 0;
> > +
> > + mac = netdev_priv(ndev);
> > +
> > + // Unregister and free 2nd net device.
> > + ndev2 = mac->next_ndev;
> > + if (ndev2) {
> > + sp_phy_remove(ndev2);
> > + unregister_netdev(ndev2);
> > + free_netdev(ndev2);
> > + }
> > +
> > + mac->comm->enable = 0;
> > + soc0_stop(mac);
> > +
> > + // Disable and delete napi.
> > + napi_disable(&mac->comm->rx_napi);
> > + netif_napi_del(&mac->comm->rx_napi);
> > + napi_disable(&mac->comm->tx_napi);
> > + netif_napi_del(&mac->comm->tx_napi);
> > +
> > + sp_phy_remove(ndev);
> > + mdio_remove(ndev);
> > +
> > + // Unregister and free 1st net device.
> > + unregister_netdev(ndev);
> > + free_netdev(ndev);
> > +
> > + clk_disable(mac->comm->clk);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct of_device_id sp_of_match[] = {
> > + {.compatible = "sunplus,sp7021-emac"},
> > + { /* sentinel */ }
> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, sp_of_match);
> > +
> > +static struct platform_driver sp_driver = {
> > + .probe = sp_probe,
> > + .remove = sp_remove,
> > + .driver = {
> > + .name = "sp7021_emac",
> > + .owner = THIS_MODULE,
> > + .of_match_table = sp_of_match,
> > + },
> > +};
> > +
> > +module_platform_driver(sp_driver);
> > +
> > +MODULE_AUTHOR("Wells Lu <[email protected]>");
> > +MODULE_DESCRIPTION("Sunplus Dual 10M/100M Ethernet driver");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/drivers/net/ethernet/sunplus/sp_driver.h b/drivers/net/ethernet/sunplus/sp_driver.h
> > new file mode 100644
> > index 0000000..ea80a9e
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_driver.h
> > @@ -0,0 +1,23 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_DRIVER_H__
> > +#define __SP_DRIVER_H__
> > +
> > +#include "sp_define.h"
> > +#include "sp_register.h"
> > +#include "sp_hal.h"
> > +#include "sp_int.h"
> > +#include "sp_mdio.h"
> > +#include "sp_mac.h"
> > +#include "sp_desc.h"
> > +
> > +#define NEXT_TX(N) ((N) = (((N) + 1) == TX_DESC_NUM) ? 0 : (N) + 1)
> > +#define NEXT_RX(QUEUE, N) ((N) = (((N) + 1) == mac->comm->rx_desc_num[QUEUE]) ? 0 :
> (N) + 1)
> > +
> > +#define RX_NAPI_WEIGHT 16
> > +#define TX_NAPI_WEIGHT 16
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_hal.c b/drivers/net/ethernet/sunplus/sp_hal.c
> > new file mode 100644
> > index 0000000..a7f06d7
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_hal.c
> > @@ -0,0 +1,331 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include <linux/iopoll.h>
> > +#include "sp_hal.h"
> > +
> > +void hal_mac_stop(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg, disable;
> > +
> > + if (comm->enable == 0) {
> > + // Mask and clear all interrupts, except PORT_ST_CHG.
> > + write_sw_int_mask0(mac, 0xffffffff);
> > + writel(0xffffffff & (~MAC_INT_PORT_ST_CHG),
> > + comm->sp_reg_base + SP_SW_INT_STATUS_0);
> > +
> > + // Disable cpu 0 and cpu 1.
> > + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> > + writel((0x3 << 6) | reg, comm->sp_reg_base + SP_CPU_CNTL);
> > + }
> > +
> > + // Disable lan 0 and lan 1.
> > + disable = ((~comm->enable) & 0x3) << 24;
> > + reg = readl(comm->sp_reg_base + SP_PORT_CNTL0);
> > + writel(disable | reg, comm->sp_reg_base + SP_PORT_CNTL0);
> > +}
> > +
> > +void hal_mac_reset(struct sp_mac *mac)
> > +{
> > +}
> Thre is no need for the empty function

I'll remove the empty function in next patch.


> > +
> > +void hal_mac_start(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Enable cpu port 0 (6) & port 0 crc padding (8)
> > + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> > + writel((reg & (~(0x1 << 6))) | (0x1 << 8), comm->sp_reg_base + SP_CPU_CNTL);
> > +
> > + // Enable lan 0 & lan 1
> > + reg = readl(comm->sp_reg_base + SP_PORT_CNTL0);
> > + writel(reg & (~(comm->enable << 24)), comm->sp_reg_base + SP_PORT_CNTL0);
> > +}
> > +
> > +void hal_mac_addr_set(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Write MAC address.
> > + writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
> > + comm->sp_reg_base + SP_W_MAC_15_0);
> > + writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
> > + (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
> > +
> > + // Set aging=1
> > + writel((mac->cpu_port << 10) + (mac->vlan_id << 7) + (1 << 4) + 0x1,
> > + comm->sp_reg_base + SP_WT_MAC_AD0);
> > +
> > + // Wait for completing.
> > + do {
> > + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> > + ndelay(10);
> > + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> > + } while ((reg & (0x1 << 1)) == 0x0);
> > +
> > + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> > + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> > + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> > + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
> > +}
> > +
> > +void hal_mac_addr_del(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Write MAC address.
> > + writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
> > + comm->sp_reg_base + SP_W_MAC_15_0);
> > + writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
> > + (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
> > +
> > + // Wait for completing.
> > + writel((0x1 << 12) + (mac->vlan_id << 7) + 0x1,
> > + comm->sp_reg_base + SP_WT_MAC_AD0);
> > + do {
> > + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> > + ndelay(10);
> > + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> > + } while ((reg & (0x1 << 1)) == 0x0);
> > +
> > + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> > + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> > + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> > + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
> > +}
> > +
> > +void hal_mac_addr_table_del_all(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Wait for address table being idle.
> > + do {
> > + reg = readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH);
> > + ndelay(10);
> > + } while (!(reg & MAC_ADDR_LOOKUP_IDLE));
> > +
> > + // Search address table from start.
> > + writel(readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH) | MAC_BEGIN_SEARCH_ADDR,
> > + comm->sp_reg_base + SP_ADDR_TBL_SRCH);
> > + while (1) {
> > + do {
> > + reg = readl(comm->sp_reg_base + SP_ADDR_TBL_ST);
> > + ndelay(10);
> > + netdev_dbg(mac->ndev, "addr_tbl_st = %08x\n", reg);
> > + } while (!(reg & (MAC_AT_TABLE_END | MAC_AT_DATA_READY)));
> > +
> > + if (reg & MAC_AT_TABLE_END)
> > + break;
> > +
> > + netdev_dbg(mac->ndev, "addr_tbl_st = %08x\n", reg);
> > + netdev_dbg(mac->ndev, "@AT #%u: port=%01x, cpu=%01x, vid=%u, aging=%u, proxy=%u,
> mc_ingress=%u\n",
> > + (reg >> 22) & 0x3ff, (reg >> 12) & 0x3, (reg >> 10) & 0x3,
> > + (reg >> 7) & 0x7, (reg >> 4) & 0x7, (reg >> 3) & 0x1,
> > + (reg >> 2) & 0x1);
> > +
> > + // Delete all entries which are learnt from lan ports.
> > + if ((reg >> 12) & 0x3) {
> > + writel(readl(comm->sp_reg_base + SP_MAC_AD_SER0),
> > + comm->sp_reg_base + SP_W_MAC_15_0);
> > + writel(readl(comm->sp_reg_base + SP_MAC_AD_SER1),
> > + comm->sp_reg_base + SP_W_MAC_47_16);
> > +
> > + writel((0x1 << 12) + (reg & (0x7 << 7)) + 0x1,
> > + comm->sp_reg_base + SP_WT_MAC_AD0);
> > + do {
> > + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> > + ndelay(10);
> > + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> > + } while ((reg & (0x1 << 1)) == 0x0);
> > + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> > + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> > + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> > + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff);
> > + }
> > +
> > + // Search next.
> > + writel(readl(comm->sp_reg_base + SP_ADDR_TBL_SRCH) | MAC_SEARCH_NEXT_ADDR,
> > + comm->sp_reg_base + SP_ADDR_TBL_SRCH);
> > + }
> > +}
> > +
> > +void hal_mac_init(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Disable cpu0 and cpu 1.
> > + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> > + writel((0x3 << 6) | reg, comm->sp_reg_base + SP_CPU_CNTL);
> Would be nice to see a constant

I'll modify in next patch as shown below:

reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
reg |= MAC_DIS_SOC1_CPU | MAC_DIS_SOC0_CPU;
writel(reg, comm->sp_reg_base + SP_CPU_CNTL);


> > + // Descriptor base address
> > + writel(mac->comm->desc_dma, comm->sp_reg_base + SP_TX_LBASE_ADDR_0);
> > + writel(mac->comm->desc_dma + sizeof(struct mac_desc) * TX_DESC_NUM,
> > + comm->sp_reg_base + SP_TX_HBASE_ADDR_0);
> > + writel(mac->comm->desc_dma + sizeof(struct mac_desc) * (TX_DESC_NUM +
> > + MAC_GUARD_DESC_NUM), comm->sp_reg_base + SP_RX_HBASE_ADDR_0);
> > + writel(mac->comm->desc_dma + sizeof(struct mac_desc) * (TX_DESC_NUM +
> > + MAC_GUARD_DESC_NUM + RX_QUEUE0_DESC_NUM),
> > + comm->sp_reg_base + SP_RX_LBASE_ADDR_0);
> > +
> > + // Fc_rls_th=0x4a, Fc_set_th=0x3a, Drop_rls_th=0x2d, Drop_set_th=0x1d
> > + writel(0x4a3a2d1d, comm->sp_reg_base + SP_FL_CNTL_TH);
> > +
> > + // Cpu_rls_th=0x4a, Cpu_set_th=0x3a, Cpu_th=0x12, Port_th=0x12
> > + writel(0x4a3a1212, comm->sp_reg_base + SP_CPU_FL_CNTL_TH);
> > +
> > + // mtcc_lmt=0xf, Pri_th_l=6, Pri_th_h=6, weigh_8x_en=1
> > + writel(0xf6680000, comm->sp_reg_base + SP_PRI_FL_CNTL);
> > +
> > + // High-active LED
> > + reg = readl(comm->sp_reg_base + SP_LED_PORT0);
> > + writel(reg | (1 << 28), comm->sp_reg_base + SP_LED_PORT0);
> ditto

I'll modify in next patch as shown below:

reg = readl(comm->sp_reg_base + SP_LED_PORT0);
reg |= MAC_LED_ACT_HI;
writel(reg, comm->sp_reg_base + SP_LED_PORT0);


> > + // Disable cpu port0 aging (12)
> > + // Disable cpu port0 learning (14)
> > + // Enable UC and MC packets
> > + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> > + writel((reg & (~((0x1 << 14) | (0x3c << 0)))) | (0x1 << 12),
> ditto

I'll modify in next patch as shown below:

reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
reg &= ~(MAC_EN_SOC0_AGING |
MAC_DIS_BC2CPU_P1 | MAC_DIS_BC2CPU_P0 |
MAC_DIS_MC2CPU_P1 | MAC_DIS_MC2CPU_P0);
reg |= MAC_DIS_LRN_SOC0;
writel(reg, comm->sp_reg_base + SP_CPU_CNTL);


> > + comm->sp_reg_base + SP_CPU_CNTL);
> > +
> > + // Disable lan port SA learning.
> > + reg = readl(comm->sp_reg_base + SP_PORT_CNTL1);
> > + writel(reg | (0x3 << 8), comm->sp_reg_base + SP_PORT_CNTL1);
> ditto

I'll modify in next patch as shown below:

reg = readl(comm->sp_reg_base + SP_PORT_CNTL1);
reg |= MAC_DIS_SA_LRN_P1 | MAC_DIS_SA_LRN_P0;
writel(reg, comm->sp_reg_base + SP_PORT_CNTL1);


> > + // Port 0: VLAN group 0
> > + // Port 1: VLAN group 1
> > + writel((1 << 4) + 0, comm->sp_reg_base + SP_PVID_CONFIG0);
> > +
> > + // VLAN group 0: cpu0+port0
> > + // VLAN group 1: cpu0+port1
> > + writel((0xa << 8) + 0x9, comm->sp_reg_base + SP_VLAN_MEMSET_CONFIG0);
> > +
> > + // RMC forward: to cpu
> > + // LED: 60mS
> > + // BC storm prev: 31 BC
> > + reg = readl(comm->sp_reg_base + SP_SW_GLB_CNTL);
> > + writel((reg & (~((0x3 << 25) | (0x3 << 23) | (0x3 << 4)))) |
> > + (0x1 << 25) | (0x1 << 23) | (0x1 << 4),
> > + comm->sp_reg_base + SP_SW_GLB_CNTL);
> > +
> > + write_sw_int_mask0(mac, MAC_INT_MASK_DEF);
> > +}
> > +
> > +void hal_rx_mode_set(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > + struct sp_common *comm = mac->comm;
> > + u32 mask, reg, rx_mode;
> > +
> > + netdev_dbg(ndev, "ndev->flags = %08x\n", ndev->flags);
> > +
> > + mask = (mac->lan_port << 2) | (mac->lan_port << 0);
> > + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> > +
> > + if (ndev->flags & IFF_PROMISC) { /* Set promiscuous mode */
> > + // Allow MC and unknown UC packets
> > + rx_mode = (mac->lan_port << 2) | (mac->lan_port << 0);
> > + } else if ((!netdev_mc_empty(ndev) && (ndev->flags & IFF_MULTICAST)) ||
> > + (ndev->flags & IFF_ALLMULTI)) {
> > + // Allow MC packets
> > + rx_mode = (mac->lan_port << 2);
> > + } else {
> > + // Disable MC and unknown UC packets
> > + rx_mode = 0;
> > + }
> > +
> > + writel((reg & (~mask)) | ((~rx_mode) & mask), comm->sp_reg_base + SP_CPU_CNTL);
> > + netdev_dbg(ndev, "cpu_cntl = %08x\n", readl(comm->sp_reg_base + SP_CPU_CNTL));
> > +}
> > +
> > +int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8 reg_addr, u32 wdata)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 val, ret;
> > +
> > + writel((wdata << 16) | (op_cd << 13) | (reg_addr << 8) | phy_addr,
> > + comm->sp_reg_base + SP_PHY_CNTL_REG0);
> > +
> > + ret = read_poll_timeout(readl, val, val & op_cd, 10, 1000, 1,
> > + comm->sp_reg_base + SP_PHY_CNTL_REG1);
> > + if (ret == 0)
> > + return val >> 16;
> > + else
> > + return ret;
> > +}
> > +
> > +void hal_tx_trigger(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > +
> > + writel((0x1 << 1), comm->sp_reg_base + SP_CPU_TX_TRIG);
> > +}
> > +
> > +void hal_set_rmii_tx_rx_pol(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Set polarity of RX and TX of RMII signal.
> > + reg = readl(comm->moon5_reg_base + MOON5_MO4_L2SW_CLKSW_CTL);
> > + writel(reg | (0xf << 16) | 0xf, comm->moon5_reg_base + MOON5_MO4_L2SW_CLKSW_CTL);
> > +}
> > +
> > +void hal_phy_addr(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Set address of phy.
> > + reg = readl(comm->sp_reg_base + SP_MAC_FORCE_MODE);
> > + reg = (reg & (~(0x1f << 16))) | ((mac->phy_addr & 0x1f) << 16);
> > + if (mac->next_ndev) {
> > + struct net_device *ndev2 = mac->next_ndev;
> > + struct sp_mac *mac2 = netdev_priv(ndev2);
> > +
> > + reg = (reg & (~(0x1f << 24))) | ((mac2->phy_addr & 0x1f) << 24);
> > + }
> > + writel(reg, comm->sp_reg_base + SP_MAC_FORCE_MODE);
> > +}
> > +
> > +u32 read_sw_int_mask0(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > +
> > + return readl(comm->sp_reg_base + SP_SW_INT_MASK_0);
> > +}
> > +
> > +void write_sw_int_mask0(struct sp_mac *mac, u32 value)
> > +{
> > + struct sp_common *comm = mac->comm;
> > +
> > + writel(value, comm->sp_reg_base + SP_SW_INT_MASK_0);
> > +}
> > +
> > +void write_sw_int_status0(struct sp_mac *mac, u32 value)
> > +{
> > + struct sp_common *comm = mac->comm;
> > +
> > + writel(value, comm->sp_reg_base + SP_SW_INT_STATUS_0);
> > +}
> > +
> > +u32 read_sw_int_status0(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > +
> > + return readl(comm->sp_reg_base + SP_SW_INT_STATUS_0);
> > +}
> > +
> > +u32 read_port_ability(struct sp_mac *mac)
> > +{
> > + struct sp_common *comm = mac->comm;
> > +
> > + return readl(comm->sp_reg_base + SP_PORT_ABILITY);
> > +}
> > diff --git a/drivers/net/ethernet/sunplus/sp_hal.h b/drivers/net/ethernet/sunplus/sp_hal.h
> > new file mode 100644
> > index 0000000..f4b1979
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_hal.h
> > @@ -0,0 +1,31 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_HAL_H__
> > +#define __SP_HAL_H__
> > +
> > +#include "sp_register.h"
> > +#include "sp_define.h"
> > +#include "sp_desc.h"
> > +
> > +void hal_mac_stop(struct sp_mac *mac);
> > +void hal_mac_reset(struct sp_mac *mac);
> > +void hal_mac_start(struct sp_mac *mac);
> > +void hal_mac_addr_set(struct sp_mac *mac);
> > +void hal_mac_addr_del(struct sp_mac *mac);
> > +void hal_mac_addr_table_del_all(struct sp_mac *mac);
> > +void hal_mac_init(struct sp_mac *mac);
> > +void hal_rx_mode_set(struct net_device *ndev);
> > +int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8 reg_addr, u32 wdata);
> > +void hal_tx_trigger(struct sp_mac *mac);
> > +void hal_set_rmii_tx_rx_pol(struct sp_mac *mac);
> > +void hal_phy_addr(struct sp_mac *mac);
> > +u32 read_sw_int_mask0(struct sp_mac *mac);
> > +void write_sw_int_mask0(struct sp_mac *mac, u32 value);
> > +void write_sw_int_status0(struct sp_mac *mac, u32 value);
> > +u32 read_sw_int_status0(struct sp_mac *mac);
> > +u32 read_port_ability(struct sp_mac *mac);
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_int.c b/drivers/net/ethernet/sunplus/sp_int.c
> > new file mode 100644
> > index 0000000..27d81b6
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_int.c
> > @@ -0,0 +1,286 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include "sp_define.h"
> > +#include "sp_int.h"
> > +#include "sp_driver.h"
> > +#include "sp_hal.h"
> > +
> > +static void port_status_change(struct sp_mac *mac)
> > +{
> > + u32 reg;
> > + struct net_device *ndev = mac->ndev;
> > +
> > + reg = read_port_ability(mac);
> > + if (!netif_carrier_ok(ndev) && (reg & PORT_ABILITY_LINK_ST_P0)) {
> > + netif_carrier_on(ndev);
> > + netif_start_queue(ndev);
> > + } else if (netif_carrier_ok(ndev) && !(reg & PORT_ABILITY_LINK_ST_P0)) {
> > + netif_carrier_off(ndev);
> > + netif_stop_queue(ndev);
> > + }
> > +
> > + if (mac->next_ndev) {
> > + struct net_device *ndev2 = mac->next_ndev;
> > +
> > + if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
> > + netif_carrier_on(ndev2);
> > + netif_start_queue(ndev2);
> > + } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
> > + netif_carrier_off(ndev2);
> > + netif_stop_queue(ndev2);
> > + }
> > + }
> > +}
> > +
> > +static void rx_skb(struct sp_mac *mac, struct sk_buff *skb)
> > +{
> > + mac->dev_stats.rx_packets++;
> > + mac->dev_stats.rx_bytes += skb->len;
> > + netif_receive_skb(skb);
> > +}
> > +
> > +int rx_poll(struct napi_struct *napi, int budget)
> > +{
> > + struct sp_common *comm = container_of(napi, struct sp_common, rx_napi);
> > + struct sp_mac *mac = netdev_priv(comm->ndev);
> > + struct sk_buff *skb, *new_skb;
> > + struct skb_info *sinfo;
> > + struct mac_desc *desc;
> > + struct mac_desc *h_desc;
> > + u32 rx_pos, pkg_len;
> > + u32 cmd;
> > + u32 num, rx_count;
> > + s32 queue;
> > + int ndev2_pkt;
> > + struct net_device_stats *dev_stats;
> > +
> > + spin_lock(&comm->rx_lock);
> > +
> > + // Process high-priority queue and then low-priority queue.
> > + for (queue = 0; queue < RX_DESC_QUEUE_NUM; queue++) {
> > + rx_pos = comm->rx_pos[queue];
> > + rx_count = comm->rx_desc_num[queue];
> > +
> > + for (num = 0; num < rx_count; num++) {
> > + sinfo = comm->rx_skb_info[queue] + rx_pos;
> > + desc = comm->rx_desc[queue] + rx_pos;
> > + cmd = desc->cmd1;
> > +
> > + if (cmd & OWN_BIT)
> > + break;
> > +
> > + if ((cmd & PKTSP_MASK) == PKTSP_PORT1) {
> > + struct sp_mac *mac2;
> > +
> > + ndev2_pkt = 1;
> > + mac2 = (mac->next_ndev) ? netdev_priv(mac->next_ndev) : NULL;
> > + dev_stats = (mac2) ? &mac2->dev_stats : &mac->dev_stats;
> > + } else {
> > + ndev2_pkt = 0;
> > + dev_stats = &mac->dev_stats;
> > + }
> > +
> > + pkg_len = cmd & LEN_MASK;
> > + if (unlikely((cmd & ERR_CODE) || (pkg_len < 64))) {
> > + dev_stats->rx_length_errors++;
> > + dev_stats->rx_dropped++;
> > + goto NEXT;
> > + }
> > +
> > + if (unlikely(cmd & RX_IP_CHKSUM_BIT)) {
> > + dev_stats->rx_crc_errors++;
> > + dev_stats->rx_dropped++;
> > + goto NEXT;
> > + }
> > +
> > + // Allocate an skbuff for receiving.
> > + new_skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
> > + GFP_ATOMIC | GFP_DMA);
> > + if (unlikely(!new_skb)) {
> > + dev_stats->rx_dropped++;
> > + goto NEXT;
> > + }
> > + new_skb->dev = mac->ndev;
> > +
> > + dma_unmap_single(&comm->pdev->dev, sinfo->mapping,
> > + comm->rx_desc_buff_size, DMA_FROM_DEVICE);
> > +
> > + skb = sinfo->skb;
> > + skb->ip_summed = CHECKSUM_NONE;
> > +
> > + /*skb_put will judge if tail exceeds end, but __skb_put won't */
> > + __skb_put(skb, (pkg_len - 4 > comm->rx_desc_buff_size) ?
> > + comm->rx_desc_buff_size : pkg_len - 4);
> > +
> > + sinfo->mapping = dma_map_single(&comm->pdev->dev, new_skb->data,
> > + comm->rx_desc_buff_size,
> > + DMA_FROM_DEVICE);
> may fail

Yes, I'll add error check in next patch as shown below:

sinfo->mapping = dma_map_single(&comm->pdev->dev, new_skb->data,
comm->rx_desc_buff_size,
DMA_FROM_DEVICE);
if (dma_mapping_error(&comm->pdev->dev, sinfo->mapping)) {
dev_kfree_skb(new_skb);
dev_stats->rx_errors++;
desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
EOR_BIT : 0;
goto rx_poll_err;
}
sinfo->skb = new_skb;
[...]

desc->addr1 = sinfo->mapping;

rx_poll_next:
desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
EOR_BIT | MAC_RX_LEN_MAX : MAC_RX_LEN_MAX;
rx_poll_err:
wmb(); // Set OWN_BIT after other fields of descriptor are effective.
desc->cmd1 = OWN_BIT;

NEXT_RX(queue, rx_pos);


> > + sinfo->skb = new_skb;
> > +
> > + if (ndev2_pkt) {
> > + struct net_device *netdev2 = mac->next_ndev;
> > +
> > + if (netdev2) {
> > + skb->protocol = eth_type_trans(skb, netdev2);
> > + rx_skb(netdev_priv(netdev2), skb);
> > + }
> > + } else {
> > + skb->protocol = eth_type_trans(skb, mac->ndev);
> > + rx_skb(mac, skb);
> > + }
> > +
> > + desc->addr1 = sinfo->mapping;
> > +
> > +NEXT:
> > + desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
> > + EOR_BIT | MAC_RX_LEN_MAX : MAC_RX_LEN_MAX;
> > + wmb(); // Set OWN_BIT after other fields of descriptor are effective.
> > + desc->cmd1 = OWN_BIT | (comm->rx_desc_buff_size & LEN_MASK);
> > +
> > + NEXT_RX(queue, rx_pos);
> > +
> > + // If there are packets in high-priority queue,
> > + // stop processing low-priority queue.
> > + if ((queue == 1) && ((h_desc->cmd1 & OWN_BIT) == 0))
> > + break;
> > + }
> > +
> > + comm->rx_pos[queue] = rx_pos;
> > +
> > + // Save pointer to last rx descriptor of high-priority queue.
> > + if (queue == 0)
> > + h_desc = comm->rx_desc[queue] + rx_pos;
> > + }
> > +
> > + spin_unlock(&comm->rx_lock);
> > +
> > + wmb(); // make sure settings are effective.
> > + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_RX);
> > +
> > + napi_complete(napi);
> > + return 0;
> > +}
> > +
> > +int tx_poll(struct napi_struct *napi, int budget)
> > +{
> > + struct sp_common *comm = container_of(napi, struct sp_common, tx_napi);
> > + struct sp_mac *mac = netdev_priv(comm->ndev);
> > + u32 tx_done_pos;
> > + u32 cmd;
> > + struct skb_info *skbinfo;
> > + struct sp_mac *smac;
> > +
> > + spin_lock(&comm->tx_lock);
> > +
> > + tx_done_pos = comm->tx_done_pos;
> > + while ((tx_done_pos != comm->tx_pos) || (comm->tx_desc_full == 1)) {
> > + cmd = comm->tx_desc[tx_done_pos].cmd1;
> > + if (cmd & OWN_BIT)
> > + break;
> > +
> > + skbinfo = &comm->tx_temp_skb_info[tx_done_pos];
> > + if (unlikely(!skbinfo->skb))
> > + netdev_err(mac->ndev, "skb is null!\n");
> > +
> > + smac = mac;
> > + if (mac->next_ndev && ((cmd & TO_VLAN_MASK) == TO_VLAN_GROUP1))
> > + smac = netdev_priv(mac->next_ndev);
> > +
> > + if (unlikely(cmd & (ERR_CODE))) {
> > + smac->dev_stats.tx_errors++;
> > + } else {
> > + smac->dev_stats.tx_packets++;
> > + smac->dev_stats.tx_bytes += skbinfo->len;
> > + }
> > +
> > + dma_unmap_single(&comm->pdev->dev, skbinfo->mapping, skbinfo->len,
> > + DMA_TO_DEVICE);
> > + skbinfo->mapping = 0;
> > + dev_kfree_skb_irq(skbinfo->skb);
> > + skbinfo->skb = NULL;
> > +
> > + NEXT_TX(tx_done_pos);
> > + if (comm->tx_desc_full == 1)
> > + comm->tx_desc_full = 0;
> > + }
> > +
> > + comm->tx_done_pos = tx_done_pos;
> > + if (!comm->tx_desc_full) {
> > + if (netif_queue_stopped(mac->ndev))
> > + netif_wake_queue(mac->ndev);
> > +
> > + if (mac->next_ndev) {
> > + if (netif_queue_stopped(mac->next_ndev))
> > + netif_wake_queue(mac->next_ndev);
> > + }
> > + }
> > +
> > + spin_unlock(&comm->tx_lock);
> > +
> > + wmb(); // make sure settings are effective.
> > + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_TX);
> > +
> > + napi_complete(napi);
> > + return 0;
> > +}
> > +
> > +irqreturn_t ethernet_interrupt(int irq, void *dev_id)
> > +{
> > + struct net_device *ndev;
> > + struct sp_mac *mac;
> > + struct sp_common *comm;
> > + u32 status;
> > +
> > + ndev = (struct net_device *)dev_id;
> > + if (unlikely(!ndev)) {
> > + netdev_err(ndev, "ndev is null!\n");
> > + goto OUT;
> > + }
> > +
> > + mac = netdev_priv(ndev);
> > + comm = mac->comm;
> > +
> > + status = read_sw_int_status0(mac);
> > + if (unlikely(status == 0)) {
> > + netdev_err(ndev, "Interrput status is null!\n");
> > + goto OUT;
> > + }
> > + write_sw_int_status0(mac, status);
> > +
> > + if (status & MAC_INT_RX) {
> > + // Disable RX interrupts.
> > + write_sw_int_mask0(mac, read_sw_int_mask0(mac) | MAC_INT_RX);
> > +
> > + if (unlikely(status & MAC_INT_RX_DES_ERR)) {
> > + netdev_err(ndev, "Illegal RX Descriptor!\n");
> > + mac->dev_stats.rx_fifo_errors++;
> > + }
> > + if (napi_schedule_prep(&comm->rx_napi))
> > + __napi_schedule(&comm->rx_napi);
> > + }
> > +
> > + if (status & MAC_INT_TX) {
> > + // Disable TX interrupts.
> > + write_sw_int_mask0(mac, read_sw_int_mask0(mac) | MAC_INT_TX);
> > +
> > + if (unlikely(status & MAC_INT_TX_DES_ERR)) {
> > + netdev_err(ndev, "Illegal TX Descriptor Error\n");
> > + mac->dev_stats.tx_fifo_errors++;
> > + mac_soft_reset(mac);
> > + wmb(); // make sure settings are effective.
> > + write_sw_int_mask0(mac, read_sw_int_mask0(mac) & ~MAC_INT_TX);
> > + } else {
> > + if (napi_schedule_prep(&comm->tx_napi))
> > + __napi_schedule(&comm->tx_napi);
> > + }
> > + }
> > +
> > + if (status & MAC_INT_PORT_ST_CHG) /* link status changed */
> > + port_status_change(mac);
> > +
> > +OUT:
> > + return IRQ_HANDLED;
> > +}
> > diff --git a/drivers/net/ethernet/sunplus/sp_int.h b/drivers/net/ethernet/sunplus/sp_int.h
> > new file mode 100644
> > index 0000000..7de8df4
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_int.h
> > @@ -0,0 +1,13 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_INT_H__
> > +#define __SP_INT_H__
> > +
> > +int rx_poll(struct napi_struct *napi, int budget);
> > +int tx_poll(struct napi_struct *napi, int budget);
> > +irqreturn_t ethernet_interrupt(int irq, void *dev_id);
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_mac.c b/drivers/net/ethernet/sunplus/sp_mac.c
> > new file mode 100644
> > index 0000000..6a3dfb1
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_mac.c
> > @@ -0,0 +1,63 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include "sp_mac.h"
> > +
> > +void mac_init(struct sp_mac *mac)
> > +{
> > + u32 i;
> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> > + mac->comm->rx_pos[i] = 0;
> > + mb(); // make sure settings are effective.
> > +
> > + hal_mac_init(mac);
> > +}
> > +
> > +void mac_soft_reset(struct sp_mac *mac)
> > +{
> > + u32 i;
> > + struct net_device *ndev2;
> > +
> > + if (netif_carrier_ok(mac->ndev)) {
> > + netif_carrier_off(mac->ndev);
> > + netif_stop_queue(mac->ndev);
> > + }
> > +
> > + ndev2 = mac->next_ndev;
> > + if (ndev2) {
> > + if (netif_carrier_ok(ndev2)) {
> > + netif_carrier_off(ndev2);
> > + netif_stop_queue(ndev2);
> > + }
> > + }
> > +
> > + hal_mac_reset(mac);
> > + hal_mac_stop(mac);
> > +
> > + rx_descs_flush(mac->comm);
> > + mac->comm->tx_pos = 0;
> > + mac->comm->tx_done_pos = 0;
> > + mac->comm->tx_desc_full = 0;
> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++)
> > + mac->comm->rx_pos[i] = 0;
> > + mb(); // make sure settings are effective.
> > +
> > + hal_mac_init(mac);
> > + hal_mac_start(mac);
> > +
> > + if (!netif_carrier_ok(mac->ndev)) {
> > + netif_carrier_on(mac->ndev);
> > + netif_start_queue(mac->ndev);
> > + }
> > +
> > + if (ndev2) {
> > + if (!netif_carrier_ok(ndev2)) {
> > + netif_carrier_on(ndev2);
> > + netif_start_queue(ndev2);
> > + }
> > + }
> > +}
> > diff --git a/drivers/net/ethernet/sunplus/sp_mac.h b/drivers/net/ethernet/sunplus/sp_mac.h
> > new file mode 100644
> > index 0000000..f35f3b7
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_mac.h
> > @@ -0,0 +1,23 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_MAC_H__
> > +#define __SP_MAC_H__
> > +
> > +#include "sp_define.h"
> > +#include "sp_hal.h"
> > +
> > +void mac_init(struct sp_mac *mac);
> > +void mac_soft_reset(struct sp_mac *mac);
> > +
> > +// Calculate the empty tx descriptor number
> > +#define TX_DESC_AVAIL(mac) \
> > + (((mac)->tx_pos != (mac)->tx_done_pos) ? \
> > + (((mac)->tx_done_pos < (mac)->tx_pos) ? \
> > + (TX_DESC_NUM - ((mac)->tx_pos - (mac)->tx_done_pos)) : \
> > + ((mac)->tx_done_pos - (mac)->tx_pos)) : \
> > + ((mac)->tx_desc_full ? 0 : TX_DESC_NUM))
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_mdio.c b/drivers/net/ethernet/sunplus/sp_mdio.c
> > new file mode 100644
> > index 0000000..f6a7e64
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_mdio.c
> > @@ -0,0 +1,90 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include "sp_mdio.h"
> > +
> > +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum)
> > +{
> > + int ret;
> > +
> > + ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
> > + if (ret < 0)
> > + return -EOPNOTSUPP;
> > +
> > + return ret;
> > +}
> > +
> > +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val)
> > +{
> > + int ret;
> > +
> > + ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
> > + if (ret < 0)
> > + return -EOPNOTSUPP;
> > +
> > + return 0;
> > +}
> > +
> > +static int mii_read(struct mii_bus *bus, int phy_id, int regnum)
> > +{
> > + struct sp_mac *mac = bus->priv;
> > +
> > + return mdio_read(mac, phy_id, regnum);
> > +}
> > +
> > +static int mii_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
> > +{
> > + struct sp_mac *mac = bus->priv;
> > +
> > + return mdio_write(mac, phy_id, regnum, val);
> > +}
> > +
> > +u32 mdio_init(struct platform_device *pdev, struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > + struct mii_bus *mii_bus;
> > + struct device_node *mdio_node;
> > + int ret;
> > +
> > + mii_bus = mdiobus_alloc();
> > + if (!mii_bus) {
> > + netdev_err(ndev, "Failed to allocate mdio_bus memory!\n");
> > + return -ENOMEM;
> > + }
> > +
> > + mii_bus->name = "sunplus_mii_bus";
> > + mii_bus->parent = &pdev->dev;
> > + mii_bus->priv = mac;
> > + mii_bus->read = mii_read;
> > + mii_bus->write = mii_write;
> > + snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
> > +
> > + mdio_node = of_get_parent(mac->phy_node);
> > + if (!mdio_node) {
> > + netdev_err(ndev, "Failed to get mdio_node!\n");
> > + return -ENODATA;
> > + }
> > +
> > + ret = of_mdiobus_register(mii_bus, mdio_node);
> > + if (ret) {
> > + netdev_err(ndev, "Failed to register mii bus!\n");
> > + mdiobus_free(mii_bus);
> > + return ret;
> > + }
> > +
> > + mac->comm->mii_bus = mii_bus;
> > + return ret;
> > +}
> > +
> > +void mdio_remove(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (mac->comm->mii_bus) {
> > + mdiobus_unregister(mac->comm->mii_bus);
> > + mdiobus_free(mac->comm->mii_bus);
> > + mac->comm->mii_bus = NULL;
> > + }
> > +}
> > diff --git a/drivers/net/ethernet/sunplus/sp_mdio.h b/drivers/net/ethernet/sunplus/sp_mdio.h
> > new file mode 100644
> > index 0000000..d708624
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_mdio.h
> > @@ -0,0 +1,20 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_MDIO_H__
> > +#define __SP_MDIO_H__
> > +
> > +#include "sp_define.h"
> > +#include "sp_hal.h"
> > +
> > +#define MDIO_READ_CMD 0x02
> > +#define MDIO_WRITE_CMD 0x01
> > +
> > +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum);
> > +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val);
> > +u32 mdio_init(struct platform_device *pdev, struct net_device *ndev);
> > +void mdio_remove(struct net_device *ndev);
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_phy.c b/drivers/net/ethernet/sunplus/sp_phy.c
> > new file mode 100644
> > index 0000000..df6df3a
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_phy.c
> > @@ -0,0 +1,64 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include "sp_phy.h"
> > +#include "sp_mdio.h"
> > +
> > +static void mii_linkchange(struct net_device *netdev)
> > +{
> > +}
> > +
> > +int sp_phy_probe(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > + struct phy_device *phydev;
> > + int i;
> > +
> > + phydev = of_phy_connect(ndev, mac->phy_node, mii_linkchange,
> > + 0, mac->phy_mode);
> > + if (!phydev) {
> > + netdev_err(ndev, "\"%s\" failed to connect to phy!\n", ndev->name);
> > + return -ENODEV;
> > + }
> > +
> > + for (i = 0; i < sizeof(phydev->supported) / sizeof(long); i++)
> > + phydev->advertising[i] = phydev->supported[i];
> > +
> > + phydev->irq = PHY_MAC_INTERRUPT;
> > + mac->phy_dev = phydev;
> > +
> > + // Bug workaround:
> > + // Flow-control of phy should be enabled. MAC flow-control will refer
> > + // to the bit to decide to enable or disable flow-control.
> > + mdio_write(mac, mac->phy_addr, 4, mdio_read(mac, mac->phy_addr, 4) | (1 << 10));
> > +
> > + return 0;
> > +}
> > +
> > +void sp_phy_start(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (mac->phy_dev)
> > + phy_start(mac->phy_dev);
> > +}
> > +
> > +void sp_phy_stop(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (mac->phy_dev)
> > + phy_stop(mac->phy_dev);
> > +}
> > +
> > +void sp_phy_remove(struct net_device *ndev)
> > +{
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (mac->phy_dev) {
> > + phy_disconnect(mac->phy_dev);
> > + mac->phy_dev = NULL;
> > + }
> > +}
> > diff --git a/drivers/net/ethernet/sunplus/sp_phy.h b/drivers/net/ethernet/sunplus/sp_phy.h
> > new file mode 100644
> > index 0000000..4ae0351
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_phy.h
> > @@ -0,0 +1,16 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_PHY_H__
> > +#define __SP_PHY_H__
> > +
> > +#include "sp_define.h"
> > +
> > +int sp_phy_probe(struct net_device *netdev);
> > +void sp_phy_start(struct net_device *netdev);
> > +void sp_phy_stop(struct net_device *netdev);
> > +void sp_phy_remove(struct net_device *netdev);
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_register.h b/drivers/net/ethernet/sunplus/sp_register.h
> > new file mode 100644
> > index 0000000..690c0dd
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_register.h
> > @@ -0,0 +1,96 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_REGISTER_H__
> > +#define __SP_REGISTER_H__
> > +
> > +#include "sp_define.h"
> > +
> > +/* TYPE: RegisterFile_L2SW */
> > +#define SP_SW_INT_STATUS_0 0x0
> > +#define SP_SW_INT_MASK_0 0x4
> > +#define SP_FL_CNTL_TH 0x8
> > +#define SP_CPU_FL_CNTL_TH 0xc
> > +#define SP_PRI_FL_CNTL 0x10
> > +#define SP_VLAN_PRI_TH 0x14
> > +#define SP_EN_TOS_BUS 0x18
> > +#define SP_TOS_MAP0 0x1c
> > +#define SP_TOS_MAP1 0x20
> > +#define SP_TOS_MAP2 0x24
> > +#define SP_TOS_MAP3 0x28
> > +#define SP_TOS_MAP4 0x2c
> > +#define SP_TOS_MAP5 0x30
> > +#define SP_TOS_MAP6 0x34
> > +#define SP_TOS_MAP7 0x38
> > +#define SP_GLOBAL_QUE_STATUS 0x3c
> > +#define SP_ADDR_TBL_SRCH 0x40
> > +#define SP_ADDR_TBL_ST 0x44
> > +#define SP_MAC_AD_SER0 0x48
> > +#define SP_MAC_AD_SER1 0x4c
> > +#define SP_WT_MAC_AD0 0x50
> > +#define SP_W_MAC_15_0 0x54
> > +#define SP_W_MAC_47_16 0x58
> > +#define SP_PVID_CONFIG0 0x5c
> > +#define SP_PVID_CONFIG1 0x60
> > +#define SP_VLAN_MEMSET_CONFIG0 0x64
> > +#define SP_VLAN_MEMSET_CONFIG1 0x68
> > +#define SP_PORT_ABILITY 0x6c
> > +#define SP_PORT_ST 0x70
> > +#define SP_CPU_CNTL 0x74
> > +#define SP_PORT_CNTL0 0x78
> > +#define SP_PORT_CNTL1 0x7c
> > +#define SP_PORT_CNTL2 0x80
> > +#define SP_SW_GLB_CNTL 0x84
> > +#define SP_SP_SW_RESET 0x88
> > +#define SP_LED_PORT0 0x8c
> > +#define SP_LED_PORT1 0x90
> > +#define SP_LED_PORT2 0x94
> > +#define SP_LED_PORT3 0x98
> > +#define SP_LED_PORT4 0x9c
> > +#define SP_WATCH_DOG_TRIG_RST 0xa0
> > +#define SP_WATCH_DOG_STOP_CPU 0xa4
> > +#define SP_PHY_CNTL_REG0 0xa8
> > +#define SP_PHY_CNTL_REG1 0xac
> > +#define SP_MAC_FORCE_MODE 0xb0
> > +#define SP_VLAN_GROUP_CONFIG0 0xb4
> > +#define SP_VLAN_GROUP_CONFIG1 0xb8
> > +#define SP_FLOW_CTRL_TH3 0xbc
> > +#define SP_QUEUE_STATUS_0 0xc0
> > +#define SP_DEBUG_CNTL 0xc4
> > +#define SP_RESERVED_1 0xc8
> > +#define SP_MEM_TEST_INFO 0xcc
> > +#define SP_SW_INT_STATUS_1 0xd0
> > +#define SP_SW_INT_MASK_1 0xd4
> > +#define SP_SW_GLOBAL_SIGNAL 0xd8
> > +
> > +#define SP_CPU_TX_TRIG 0x208
> > +#define SP_TX_HBASE_ADDR_0 0x20c
> > +#define SP_TX_LBASE_ADDR_0 0x210
> > +#define SP_RX_HBASE_ADDR_0 0x214
> > +#define SP_RX_LBASE_ADDR_0 0x218
> > +#define SP_TX_HW_ADDR_0 0x21c
> > +#define SP_TX_LW_ADDR_0 0x220
> > +#define SP_RX_HW_ADDR_0 0x224
> > +#define SP_RX_LW_ADDR_0 0x228
> > +#define SP_CPU_PORT_CNTL_REG_0 0x22c
> > +#define SP_TX_HBASE_ADDR_1 0x230
> > +#define SP_TX_LBASE_ADDR_1 0x234
> > +#define SP_RX_HBASE_ADDR_1 0x238
> > +#define SP_RX_LBASE_ADDR_1 0x23c
> > +#define SP_TX_HW_ADDR_1 0x240
> > +#define SP_TX_LW_ADDR_1 0x244
> > +#define SP_RX_HW_ADDR_1 0x248
> > +#define SP_RX_LW_ADDR_1 0x24c
> > +#define SP_CPU_PORT_CNTL_REG_1 0x250
> > +
> > +/* TYPE: RegisterFile_MOON5 */
> > +#define MOON5_MO5_THERMAL_CTL_0 0x0
> > +#define MOON5_MO5_THERMAL_CTL_1 0x4
> > +#define MOON5_MO4_THERMAL_CTL_2 0xc
> > +#define MOON5_MO4_THERMAL_CTL_3 0x8
> > +#define MOON5_MO4_TMDS_L2SW_CTL 0x10
> > +#define MOON5_MO4_L2SW_CLKSW_CTL 0x14
> > +
> > +#endif
> >

Thank you very much for your review.

2021-11-13 15:35:02

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

> > > +//define MAC interrupt status bit
> > please embrace all comments with /* */
>
> Do you mean to modify comment, for example,
>
> //define MAC interrupt status bit
>
> to
>
> /* define MAC interrupt status bit */

Yes. The Kernel is written in C, so C style comments are preferred
over C++ comments, even if later versions of the C standard allow C++
style comments.

You should also read the netdev FAQ, which makes some specific
comments about how multi-line comments should be formatted.

> Yes, I'll add error check in next patch as shown below:
>
> rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
> comm->rx_desc_buff_size,
> DMA_FROM_DEVICE);
> if (dma_mapping_error(&comm->pdev->dev, rx_skbinfo[j].mapping))
> goto mem_alloc_fail;

If it is clear how to fix the code, just do it. No need to tell us
what you are going to do, we will see the change when reviewing the
next version.

> > > +/* Transmit a packet (called by the kernel) */
> > > +static int ethernet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> > > +{
> > > + struct sp_mac *mac = netdev_priv(ndev);
> > > + struct sp_common *comm = mac->comm;
> > > + u32 tx_pos;
> > > + u32 cmd1;
> > > + u32 cmd2;
> > > + struct mac_desc *txdesc;
> > > + struct skb_info *skbinfo;
> > > + unsigned long flags;
> > > +
> > > + if (unlikely(comm->tx_desc_full == 1)) {
> > > + // No TX descriptors left. Wait for tx interrupt.
> > > + netdev_info(ndev, "TX descriptor queue full when xmit!\n");
> > > + return NETDEV_TX_BUSY;
> > Do you really have to return NETDEV_TX_BUSY?
>
> (tx_desc_full == 1) means there is no TX descriptor left in ring buffer.
> So there is no way to do new transmit. Return 'busy' directly.
> I am not sure if this is a correct process or not.
> Could you please teach is there any other way to take care of this case?
> Drop directly?

There are a few hundred examples to follow, other MAC drivers. What do
they do when out of TX buffers? Find the most common pattern, and
follow it.

You should also thinking about the netdev_info(). Do you really want
to spam the kernel log? Say you are connected to a 10/Half link, and
the application is trying to send UDP at 100Mbps, Won't you see a lot
of these messages? change it to _debug(), or rate limit it.

> static void ethernet_tx_timeout(struct net_device *ndev, unsigned int txqueue)
> {
> struct sp_mac *mac = netdev_priv(ndev);
> struct net_device *ndev2;
> unsigned long flags;
>
> netdev_err(ndev, "TX timed out!\n");
> ndev->stats.tx_errors++;
>
> spin_lock_irqsave(&mac->comm->tx_lock, flags);
> netif_stop_queue(ndev);
> ndev2 = mac->next_ndev;
> if (ndev2)
> netif_stop_queue(ndev2);
>
> hal_mac_stop(mac);
> hal_mac_init(mac);
> hal_mac_start(mac);
>
> // Accept TX packets again.
> netif_trans_update(ndev);
> netif_wake_queue(ndev);
> if (ndev2) {
> netif_trans_update(ndev2);
> netif_wake_queue(ndev2);
> }
>
> spin_unlock_irqrestore(&mac->comm->tx_lock, flags);
> }
>
> Is that ok?

This ndev2 stuff is not nice. You probably need a cleaner abstract of
two netdev's sharing one TX and RX ring. See if there are any other
switchdev drivers with a similar structure you can copy. Maybe
cpsw_new.c? But be careful with that driver. cpsw is a bit of a mess
due to an incorrect initial design with respect to its L2 switch. A
lot of my initial comments are to stop you making the same mistakes.

Andrew

2021-11-14 19:00:07

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi,

> On 11/11/21 1:04 AM, Wells Lu wrote:
> > Add driver for Sunplus SP7021.
> >
> > Signed-off-by: Wells Lu <[email protected]>
> > ---
>
> [snip]
>
> > +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum) {
> > + int ret;
> > +
> > + ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
> > + if (ret < 0)
> > + return -EOPNOTSUPP;
> > +
> > + return ret;
> > +}
> > +
> > +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val) {
> > + int ret;
> > +
> > + ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
> > + if (ret < 0)
> > + return -EOPNOTSUPP;
> > +
> > + return 0;
> > +}
>
> You should not be exposing these functions, if you do, that means another part of your
> code performs MDIO bus read/write operations without using the appropriate layer, so no.

Yes, I'll re-declare the two functions as static functions.


> > +
> > +static int mii_read(struct mii_bus *bus, int phy_id, int regnum) {
> > + struct sp_mac *mac = bus->priv;
> > +
> > + return mdio_read(mac, phy_id, regnum); }
> > +
> > +static int mii_write(struct mii_bus *bus, int phy_id, int regnum, u16
> > +val) {
> > + struct sp_mac *mac = bus->priv;
> > +
> > + return mdio_write(mac, phy_id, regnum, val); }
> > +
> > +u32 mdio_init(struct platform_device *pdev, struct net_device *ndev)
>
> Those function names need to be prefixed with sp_ to denote the driver local scope, this
> applies for your entire patch set.

Yes, I'll add vendor-specified prefix to the two functions and all the other functions in
the drivers.


> [snip]
>
> > diff --git a/drivers/net/ethernet/sunplus/sp_mdio.h
> > b/drivers/net/ethernet/sunplus/sp_mdio.h
> > new file mode 100644
> > index 0000000..d708624
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_mdio.h
> > @@ -0,0 +1,20 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#ifndef __SP_MDIO_H__
> > +#define __SP_MDIO_H__
> > +
> > +#include "sp_define.h"
> > +#include "sp_hal.h"
> > +
> > +#define MDIO_READ_CMD 0x02
> > +#define MDIO_WRITE_CMD 0x01
> > +
> > +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum);
> > +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val);
>
> Please scope your functions better, and name them sp_mdio_read, etc.
> because mdio_read() is way too generic. Also, can you please follow the same prototype
> as what include/linux/mdio.h has for the mdiobus->read and ->write calls, that is phy_id
> is int, regnum is u32, etc.

Yes, I'll re-declare the two functions, mdio_read() and mdio_write(), as static
functions and add vendor-specified prefix to them.

The wrong declaration of the two functions will be removed from the header file.


> > +u32 mdio_init(struct platform_device *pdev, struct net_device
> > +*ndev); void mdio_remove(struct net_device *ndev);
> > +
> > +#endif
> > diff --git a/drivers/net/ethernet/sunplus/sp_phy.c
> > b/drivers/net/ethernet/sunplus/sp_phy.c
> > new file mode 100644
> > index 0000000..df6df3a
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_phy.c
> > @@ -0,0 +1,64 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include "sp_phy.h"
> > +#include "sp_mdio.h"
> > +
> > +static void mii_linkchange(struct net_device *netdev) { }
>
> Does your MAC fully auto-configure based on the PHY's link parameters, if so, how does
> it do it? You most certainly need to act on duplex changes, or speed changes no?

Yes, it does. SP7021 MAC communicates with PHY automatically.
It reads link status (half- or full-duplex, 10M or 100M) from PHY
and sets itself automatically.
It also reads port status (link up or down) and generates
interrupt to driver.


> > +
> > +int sp_phy_probe(struct net_device *ndev) {
> > + struct sp_mac *mac = netdev_priv(ndev);
> > + struct phy_device *phydev;
> > + int i;
> > +
> > + phydev = of_phy_connect(ndev, mac->phy_node, mii_linkchange,
> > + 0, mac->phy_mode);
> > + if (!phydev) {
> > + netdev_err(ndev, "\"%s\" failed to connect to phy!\n", ndev->name);
> > + return -ENODEV;
> > + }
> > +
> > + for (i = 0; i < sizeof(phydev->supported) / sizeof(long); i++)
> > + phydev->advertising[i] = phydev->supported[i];
> > +
> > + phydev->irq = PHY_MAC_INTERRUPT;
> > + mac->phy_dev = phydev;
> > +
> > + // Bug workaround:
> > + // Flow-control of phy should be enabled. MAC flow-control will refer
> > + // to the bit to decide to enable or disable flow-control.
> > + mdio_write(mac, mac->phy_addr, 4, mdio_read(mac, mac->phy_addr, 4) |
> > +(1 << 10));
>
> This is a layering violation, and you should not be doing those things here, if you need
> to advertise flow control, then please set ADVERTISE_PAUSE_CAP and/or ADVERTISE_PAUSE_ASYM
> accordingly, see whether
> phy_set_asym_pause() can do what you need it to.

Yes, I'll remove the statement, instead, use phy_set_asym_pause().


> > +
> > + return 0;
> > +}
> > +
> > +void sp_phy_start(struct net_device *ndev) {
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (mac->phy_dev)
> > + phy_start(mac->phy_dev);
> > +}
> > +
> > +void sp_phy_stop(struct net_device *ndev) {
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (mac->phy_dev)
> > + phy_stop(mac->phy_dev);
> > +}
> > +
> > +void sp_phy_remove(struct net_device *ndev) {
> > + struct sp_mac *mac = netdev_priv(ndev);
> > +
> > + if (mac->phy_dev) {
> > + phy_disconnect(mac->phy_dev);
> > + mac->phy_dev = NULL;
> > + }
>
> The net_device structure already contains a phy_device pointer, you don't need to have
> one in your sp_mac structure, too.

Yes, I'll remove phy_device from struct sp_mac.


> --
> Florian

Thank you very much for your review.

2021-11-14 19:19:45

by Pavel Skripkin

[permalink] [raw]
Subject: Re: [PATCH 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi, Wells!

On 11/3/21 14:02, Wells Lu wrote:

[code snip]

> + if (comm->dual_nic) {
> + struct net_device *net_dev2 = mac->next_netdev;
> +
> + if (!netif_running(net_dev2)) {
> + mac_hw_stop(mac);
> +
> + mac2 = netdev_priv(net_dev2);
> +

(*)

> + // unregister and free net device.
> + unregister_netdev(net_dev2);
> + free_netdev(net_dev2);
> + mac->next_netdev = NULL;
> + pr_info(" Unregistered and freed net device \"eth1\"!\n");
> +
> + comm->dual_nic = 0;
> + mac_switch_mode(mac);
> + rx_mode_set(net_dev);
> + mac_hw_addr_del(mac2);
> +

mac2 is net_dev2 private data (*), so it will become freed after
free_netdev() call.

FWIW the latest `smatch` should warn about this type of bugs.

> + // If eth0 is up, turn on lan 0 and 1 when
> + // switching to daisy-chain mode.
> + if (comm->enable & 0x1)
> + comm->enable = 0x3;

[code snip]

> +static int l2sw_remove(struct platform_device *pdev)
> +{
> + struct net_device *net_dev;
> + struct net_device *net_dev2;
> + struct l2sw_mac *mac;
> +
> + net_dev = platform_get_drvdata(pdev);
> + if (!net_dev)
> + return 0;
> + mac = netdev_priv(net_dev);
> +
> + // Unregister and free 2nd net device.
> + net_dev2 = mac->next_netdev;
> + if (net_dev2) {
> + unregister_netdev(net_dev2);
> + free_netdev(net_dev2);
> + }
> +

Is it save here to free mac->next_netdev before unregistering "parent"
netdev? I haven't checked the whole code, just asking :)

> + sysfs_remove_group(&pdev->dev.kobj, &l2sw_attribute_group);
> +
> + mac->comm->enable = 0;
> + soc0_stop(mac);
> +
> + napi_disable(&mac->comm->rx_napi);
> + netif_napi_del(&mac->comm->rx_napi);
> + napi_disable(&mac->comm->tx_napi);
> + netif_napi_del(&mac->comm->tx_napi);
> +
> + mdio_remove(net_dev);
> +
> + // Unregister and free 1st net device.
> + unregister_netdev(net_dev);
> + free_netdev(net_dev);
> +
> + clk_disable(mac->comm->clk);
> +
> + // Free 'common' area.
> + kfree(mac->comm);

Same here with `mac`.

> + return 0;
> +}


I haven't read the whole thread, i am sorry if these questions were
already discussed.



With regards,
Pavel Skripkin

2021-11-15 14:39:55

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Andrew,

Thank you for reminding.

I'll do my best to address all comments.
I fully understand that all reviewers' comments should be addressed.
If I lost addressing any comments, please kindly remind me again.


Best regards,
Wells


> Hi Florian
>
> You are basically pointing out issues i already pointed out in previous versions, and have
> been ignored :-(
>
> Wells, please look at the comments i made on your earlier versions. Those comments are
> still valid and need addressing.
>
> Andrew

2021-11-16 17:10:07

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi,


> > +void rx_descs_flush(struct sp_common *comm)
>
> As both Florian and I have said, you need a prefix for all your functions, structures,
> etc. sp_ is not the best prefix either, it is not very unique. spl2sw_ would be better.

I'll add prefix spl2sw for all functions, structures, file-names in next patch.

I thought comment for revising prefix is only for structures, function and file name
with prefix l2sw_ because 'l2sw_' has been used by other modules.

Now I know prefix is necessary for all in this driver, except local variables and
structure members.


> > +void rx_descs_clean(struct sp_common *comm) {
> > + u32 i, j;
> > + struct mac_desc *rx_desc;
> > + struct skb_info *rx_skbinfo;
>
> netdev wants reverse christmas tree. You need to change the order of your local variables,
> longest lines first, shorted last.

Yes, I'll rearrange local variables to 'reverse Christmas tree' order in next patch.


> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > + if (!comm->rx_skb_info[i])
> > + continue;
> > +
> > + rx_desc = comm->rx_desc[i];
> > + rx_skbinfo = comm->rx_skb_info[i];
> > + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> > + rx_desc[j].cmd1 = 0;
> > + wmb(); // Clear OWN_BIT and then set other fields.
> > + rx_desc[j].cmd2 = 0;
> > + rx_desc[j].addr1 = 0;
> > +
> > + if (rx_skbinfo[j].skb) {
> > + dma_unmap_single(&comm->pdev->dev, rx_skbinfo[j].mapping,
> > + comm->rx_desc_buff_size, DMA_FROM_DEVICE);
> > + dev_kfree_skb(rx_skbinfo[j].skb);
> > + rx_skbinfo[j].skb = NULL;
> > + rx_skbinfo[j].mapping = 0;
> > + }
> > + }
> > +
> > + kfree(rx_skbinfo);
> > + comm->rx_skb_info[i] = NULL;
> > + }
> > +}
>
> > +int rx_descs_init(struct sp_common *comm) {
> > + struct sk_buff *skb;
> > + u32 i, j;
> > + struct mac_desc *rx_desc;
> > + struct skb_info *rx_skbinfo;
> > +
> > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > + comm->rx_skb_info[i] = kmalloc_array(comm->rx_desc_num[i],
> > + sizeof(struct skb_info), GFP_KERNEL);
> > + if (!comm->rx_skb_info[i])
> > + goto MEM_ALLOC_FAIL;
> > +
> > + rx_skbinfo = comm->rx_skb_info[i];
> > + rx_desc = comm->rx_desc[i];
> > + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> > + skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
> > + GFP_ATOMIC | GFP_DMA);
> > + if (!skb)
> > + goto MEM_ALLOC_FAIL;
> > +
> > + skb->dev = comm->ndev;
> > + skb_reserve(skb, RX_OFFSET); /* +data +tail */
> > +
> > + rx_skbinfo[j].skb = skb;
> > + rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
> > + comm->rx_desc_buff_size,
> > + DMA_FROM_DEVICE);
> > + rx_desc[j].addr1 = rx_skbinfo[j].mapping;
> > + rx_desc[j].addr2 = 0;
> > + rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
> > + EOR_BIT | comm->rx_desc_buff_size :
> > + comm->rx_desc_buff_size;
> > + wmb(); // Set OWN_BIT after other fields are effective.
> > + rx_desc[j].cmd1 = OWN_BIT;
> > + }
> > + }
> > +
> > + return 0;
> > +
> > +MEM_ALLOC_FAIL:
>
> lower case labels. Didn't somebody already say that?

I'll modify all labels to lowercase in next patch.
Yes, Denis said that but patch not yet sent out.


> > +int descs_init(struct sp_common *comm) {
> > + u32 i, ret;
> > +
> > + // Initialize rx descriptor's data
> > + comm->rx_desc_num[0] = RX_QUEUE0_DESC_NUM; #if RX_DESC_QUEUE_NUM > 1
> > + comm->rx_desc_num[1] = RX_QUEUE1_DESC_NUM; #endif
>
> Avoid #if statements. Why is this needed?

Yes, I'll remove the #if statement in next patch.
It is indeed not necessary.
RX_DESC_QUEUE_NUM is equal to 2.


> > +++ b/drivers/net/ethernet/sunplus/sp_driver.c
> > @@ -0,0 +1,606 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/reset.h>
> > +#include <linux/nvmem-consumer.h>
> > +#include <linux/of_net.h>
> > +#include "sp_driver.h"
> > +#include "sp_phy.h"
> > +
> > +static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
> > + 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00
>
> This does not have the locally administered bit set. Should it? Or is this and address
> from your OUI?

This is default MAC address when MAC address in NVMEM is not found.
Fc:4b:bc:00:00:00 is OUI of "Sunplus Technology Co., Ltd.".
Can I keep this? or it should be removed?


> > +static void ethernet_set_rx_mode(struct net_device *ndev) {
> > + if (ndev) {
>
> How can ndev be NULL?

Yes, I'll remove 'if (ndev) {' statement in next patch.
It is redundant.


> > +++ b/drivers/net/ethernet/sunplus/sp_hal.c
> > @@ -0,0 +1,331 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include <linux/iopoll.h>
> > +#include "sp_hal.h"
> > +
> > +void hal_mac_stop(struct sp_mac *mac)
>
> I suggest you avoid any references to hal. It makes people think you have ported a driver
> from some other operating system and then put a layer of code on top of it. That is not
> how you do it in Linux. This is a Linux driver, nothing else.


Yes, I'll change file name 'sp_hal.c' to 'spl2sw_hw.c'.
Function name in this file will also be changed, for example:
hal_mac_stop() --> spl2sw_hw_mac_stop()


> > +void hal_mac_reset(struct sp_mac *mac) { }
> > +
>
> Should not exist.

Yes, I'll remove it in next patch.


> > +void hal_mac_addr_set(struct sp_mac *mac) {
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Write MAC address.
> > + writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
> > + comm->sp_reg_base + SP_W_MAC_15_0);
> > + writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
> > + (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
> > +
> > + // Set aging=1
> > + writel((mac->cpu_port << 10) + (mac->vlan_id << 7) + (1 << 4) + 0x1,
> > + comm->sp_reg_base + SP_WT_MAC_AD0);
>
> Is this actually adding an entry into the address translation table?
> If so, make this clear in the function name. You are not setting the MAC address, you are
> just adding a static forwarding entry.

Yes, this is actually adding an entry into address table.
I'll change function name to spl2sw_mac_add_addr() in next patch.
Is the name ok?


> > +
> > + // Wait for completing.
> > + do {
> > + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> > + ndelay(10);
> > + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> > + } while ((reg & (0x1 << 1)) == 0x0);
> > +
> > + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> > + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> > + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> > + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff); }
>
> > +void hal_rx_mode_set(struct net_device *ndev) {
> > + struct sp_mac *mac = netdev_priv(ndev);
> > + struct sp_common *comm = mac->comm;
> > + u32 mask, reg, rx_mode;
> > +
> > + netdev_dbg(ndev, "ndev->flags = %08x\n", ndev->flags);
> > +
> > + mask = (mac->lan_port << 2) | (mac->lan_port << 0);
> > + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> > +
> > + if (ndev->flags & IFF_PROMISC) { /* Set promiscuous mode */
> > + // Allow MC and unknown UC packets
> > + rx_mode = (mac->lan_port << 2) | (mac->lan_port << 0);
> > + } else if ((!netdev_mc_empty(ndev) && (ndev->flags & IFF_MULTICAST)) ||
> > + (ndev->flags & IFF_ALLMULTI)) {
> > + // Allow MC packets
> > + rx_mode = (mac->lan_port << 2);
> > + } else {
> > + // Disable MC and unknown UC packets
> > + rx_mode = 0;
> > + }
> > +
> > + writel((reg & (~mask)) | ((~rx_mode) & mask), comm->sp_reg_base + SP_CPU_CNTL);
> > + netdev_dbg(ndev, "cpu_cntl = %08x\n", readl(comm->sp_reg_base +
> > +SP_CPU_CNTL));
>
> This looks like it belongs in the ethtool code.

This function sets receiving mode.


> > +int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8
> > +reg_addr, u32 wdata) {
> > + struct sp_common *comm = mac->comm;
> > + u32 val, ret;
> > +
> > + writel((wdata << 16) | (op_cd << 13) | (reg_addr << 8) | phy_addr,
> > + comm->sp_reg_base + SP_PHY_CNTL_REG0);
> > +
> > + ret = read_poll_timeout(readl, val, val & op_cd, 10, 1000, 1,
> > + comm->sp_reg_base + SP_PHY_CNTL_REG1);
> > + if (ret == 0)
> > + return val >> 16;
> > + else
> > + return ret;
> > +}
>
> Should go with the other mdio code.

I'll move it into 'spl2sw_mdio.c' in next patch.

I put all hardware-related functions in sp_hal.c (will be changed to spl2sw_hw.c).
All functions in other files won't touch hardware registers.
This seems not Linux driver style.


> > +void hal_phy_addr(struct sp_mac *mac) {
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Set address of phy.
> > + reg = readl(comm->sp_reg_base + SP_MAC_FORCE_MODE);
> > + reg = (reg & (~(0x1f << 16))) | ((mac->phy_addr & 0x1f) << 16);
> > + if (mac->next_ndev) {
> > + struct net_device *ndev2 = mac->next_ndev;
> > + struct sp_mac *mac2 = netdev_priv(ndev2);
> > +
> > + reg = (reg & (~(0x1f << 24))) | ((mac2->phy_addr & 0x1f) << 24);
> > + }
> > + writel(reg, comm->sp_reg_base + SP_MAC_FORCE_MODE); }
>
> As i said before, the hardware never directly communicates with the PHY. So you can remove
> this.

I'll remove this function in next patch.

But now I cannot find a way to disable hardware 'auto rmii' function.
If I remove this function right now, MAC may get wrong status of PHY
from wrong address because SP7021 MAC communicates with PHY
automatically. This may cause more problem.

I am consulting with ASIC engineer. Hopefully, someone can find
a way to disable the auto function.


> > +static void port_status_change(struct sp_mac *mac) {
> > + u32 reg;
> > + struct net_device *ndev = mac->ndev;
> > +
> > + reg = read_port_ability(mac);
> > + if (!netif_carrier_ok(ndev) && (reg & PORT_ABILITY_LINK_ST_P0)) {
> > + netif_carrier_on(ndev);
>
> phylib should be handling the carrier for you.

I'll remove this function in next patch.
If 'auto rmii' function is removed, we no more need this function.


> > + if (mac->next_ndev) {
> > + struct net_device *ndev2 = mac->next_ndev;
> > +
> > + if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
> > + netif_carrier_on(ndev2);
> > + netif_start_queue(ndev2);
> > + } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
> > + netif_carrier_off(ndev2);
> > + netif_stop_queue(ndev2);
> > + }
>
> Looks very odd. The two netdev should be independent.

I don't understand your comment.
ndev checks PORT_ABILITY_LINK_ST_P0
ndev2 checks PORT_ABILITY_LINK_ST_P1
They are independent already.


> > diff --git a/drivers/net/ethernet/sunplus/sp_mdio.c
> > b/drivers/net/ethernet/sunplus/sp_mdio.c
> > new file mode 100644
> > index 0000000..f6a7e64
> > --- /dev/null
> > +++ b/drivers/net/ethernet/sunplus/sp_mdio.c
> > @@ -0,0 +1,90 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright Sunplus Technology Co., Ltd.
> > + * All rights reserved.
> > + */
> > +
> > +#include "sp_mdio.h"
> > +
> > +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum) {
> > + int ret;
> > +
> > + ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
> > + if (ret < 0)
> > + return -EOPNOTSUPP;
> > +
> > + return ret;
> > +}
> > +
> > +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val) {
> > + int ret;
> > +
> > + ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
> > + if (ret < 0)
> > + return -EOPNOTSUPP;
> > +
> > + return 0;
> > +}
> > +
> > +static int mii_read(struct mii_bus *bus, int phy_id, int regnum) {
> > + struct sp_mac *mac = bus->priv;
>
> What happened about my request to return -EOPNOTSUPP for C45 requests?

Sorry for overlooking the comment!
I am not sure how to check C45 request. Should I add statements like:

if (regnum & MII_ADDR_C45)
Return -EOPNOTSUPP;

for mdio_read() and mdio_write()?


> Andrew

Thank you very much for your review!

Best regards,
Wells

2021-11-16 22:15:16

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

> > > +static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
> > > + 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00
> >
> > This does not have the locally administered bit set. Should it? Or is this and address
> > from your OUI?
>
> This is default MAC address when MAC address in NVMEM is not found.
> Fc:4b:bc:00:00:00 is OUI of "Sunplus Technology Co., Ltd.".
> Can I keep this? or it should be removed?

Please add a comment about whos OUI it is.

It is however more normal to use a random MAC address if no other MAC
address is available. That way, you avoid multiple devices on one LAN
using the same default MAC address.

> > > + if (mac->next_ndev) {
> > > + struct net_device *ndev2 = mac->next_ndev;
> > > +
> > > + if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
> > > + netif_carrier_on(ndev2);
> > > + netif_start_queue(ndev2);
> > > + } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
> > > + netif_carrier_off(ndev2);
> > > + netif_stop_queue(ndev2);
> > > + }
> >
> > Looks very odd. The two netdev should be independent.
>
> I don't understand your comment.
> ndev checks PORT_ABILITY_LINK_ST_P0
> ndev2 checks PORT_ABILITY_LINK_ST_P1
> They are independent already.

I would try to remove the mac->next_ndev. I think without that, you
will get a cleaner abstraction. You might want to keep an array of mac
pointers in your top level shared structure.

Andrew

2021-11-17 09:28:43

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Pavel,

> Hi, Wells!
>
> On 11/3/21 14:02, Wells Lu wrote:
>
> [code snip]
>
> > + if (comm->dual_nic) {
> > + struct net_device *net_dev2 = mac->next_netdev;
> > +
> > + if (!netif_running(net_dev2)) {
> > + mac_hw_stop(mac);
> > +
> > + mac2 = netdev_priv(net_dev2);
> > +
>
> (*)
>
> > + // unregister and free net device.
> > + unregister_netdev(net_dev2);
> > + free_netdev(net_dev2);
> > + mac->next_netdev = NULL;
> > + pr_info(" Unregistered and freed net device \"eth1\"!\n");
> > +
> > + comm->dual_nic = 0;
> > + mac_switch_mode(mac);
> > + rx_mode_set(net_dev);
> > + mac_hw_addr_del(mac2);
> > +
>
> mac2 is net_dev2 private data (*), so it will become freed after
> free_netdev() call.
>
> FWIW the latest `smatch` should warn about this type of bugs.

Yes, this is indeed a bug.
But the code paragraph has been removed thoroughly in [PATCH v2].


> > + // If eth0 is up, turn on lan 0 and 1 when
> > + // switching to daisy-chain mode.
> > + if (comm->enable & 0x1)
> > + comm->enable = 0x3;
>
> [code snip]
>
> > +static int l2sw_remove(struct platform_device *pdev) {
> > + struct net_device *net_dev;
> > + struct net_device *net_dev2;
> > + struct l2sw_mac *mac;
> > +
> > + net_dev = platform_get_drvdata(pdev);
> > + if (!net_dev)
> > + return 0;
> > + mac = netdev_priv(net_dev);
> > +
> > + // Unregister and free 2nd net device.
> > + net_dev2 = mac->next_netdev;
> > + if (net_dev2) {
> > + unregister_netdev(net_dev2);
> > + free_netdev(net_dev2);
> > + }
> > +
>
> Is it save here to free mac->next_netdev before unregistering "parent"
> netdev? I haven't checked the whole code, just asking :)

Yes, I think it is save.
netdev2 should be unregistered and freed before net_dev.
If net_dev is unregistered and freed in advance,
mac->next_netdev becomes danger because 'mac' has been freed.


> > + sysfs_remove_group(&pdev->dev.kobj, &l2sw_attribute_group);
> > +
> > + mac->comm->enable = 0;
> > + soc0_stop(mac);
> > +
> > + napi_disable(&mac->comm->rx_napi);
> > + netif_napi_del(&mac->comm->rx_napi);
> > + napi_disable(&mac->comm->tx_napi);
> > + netif_napi_del(&mac->comm->tx_napi);
> > +
> > + mdio_remove(net_dev);
> > +
> > + // Unregister and free 1st net device.
> > + unregister_netdev(net_dev);
> > + free_netdev(net_dev);
> > +
> > + clk_disable(mac->comm->clk);
> > +
> > + // Free 'common' area.
> > + kfree(mac->comm);
>
> Same here with `mac`.

This is indeed a bug.
But the statement, Kfree(mac->comm);, has been removed in [PATCH v2].
In [PATCH v2], structure data 'mac->comm' is allocated by
devm_kzalloc(). No more need to free it here.


> > + return 0;
> > +}
>
>
> I haven't read the whole thread, i am sorry if these questions were already discussed.
>
>
>
> With regards,
> Pavel Skripkin


Thank you very much for your review!

Best regards,
Wells Lu

2021-11-18 08:17:51

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi,


> > > > +//define MAC interrupt status bit
> > > please embrace all comments with /* */
> >
> > Do you mean to modify comment, for example,
> >
> > //define MAC interrupt status bit
> >
> > to
> >
> > /* define MAC interrupt status bit */
>
> Yes. The Kernel is written in C, so C style comments are preferred over C++ comments, even
> if later versions of the C standard allow C++ style comments.

I'll modify all comments to C style in next patch.


> You should also read the netdev FAQ, which makes some specific comments about how multi-line
> comments should be formatted.

Thanks for routing me to the document.
I'll use the new format for multi-line comments.
---
It is requested that you make it look like this:

/* foobar blah blah blah
* another line of text
*/


> > Yes, I'll add error check in next patch as shown below:
> >
> > rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
> > comm->rx_desc_buff_size,
> > DMA_FROM_DEVICE);
> > if (dma_mapping_error(&comm->pdev->dev, rx_skbinfo[j].mapping))
> > goto mem_alloc_fail;
>
> If it is clear how to fix the code, just do it. No need to tell us what you are going to
> do, we will see the change when reviewing the next version.

Thanks, I see.


> > > > +/* Transmit a packet (called by the kernel) */ static int
> > > > +ethernet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> > > > +{
> > > > + struct sp_mac *mac = netdev_priv(ndev);
> > > > + struct sp_common *comm = mac->comm;
> > > > + u32 tx_pos;
> > > > + u32 cmd1;
> > > > + u32 cmd2;
> > > > + struct mac_desc *txdesc;
> > > > + struct skb_info *skbinfo;
> > > > + unsigned long flags;
> > > > +
> > > > + if (unlikely(comm->tx_desc_full == 1)) {
> > > > + // No TX descriptors left. Wait for tx interrupt.
> > > > + netdev_info(ndev, "TX descriptor queue full when xmit!\n");
> > > > + return NETDEV_TX_BUSY;
> > > Do you really have to return NETDEV_TX_BUSY?
> >
> > (tx_desc_full == 1) means there is no TX descriptor left in ring buffer.
> > So there is no way to do new transmit. Return 'busy' directly.
> > I am not sure if this is a correct process or not.
> > Could you please teach is there any other way to take care of this case?
> > Drop directly?
>
> There are a few hundred examples to follow, other MAC drivers. What do they do when out
> of TX buffers? Find the most common pattern, and follow it.

But some drivers return NETDEV_TX_BUSY, some drivers drop packet and return NETDEV_TX_OK
Some drivers seem do not take care this issue. I am not sure.


> You should also thinking about the netdev_info(). Do you really want to spam the kernel
> log? Say you are connected to a 10/Half link, and the application is trying to send UDP
> at 100Mbps, Won't you see a lot of these messages? change it to _debug(), or rate limit
> it.

Yes, I'll modify most netdev_info() to netdev_dbg() in next patch.


> > static void ethernet_tx_timeout(struct net_device *ndev, unsigned int
> > txqueue) {
> > struct sp_mac *mac = netdev_priv(ndev);
> > struct net_device *ndev2;
> > unsigned long flags;
> >
> > netdev_err(ndev, "TX timed out!\n");
> > ndev->stats.tx_errors++;
> >
> > spin_lock_irqsave(&mac->comm->tx_lock, flags);
> > netif_stop_queue(ndev);
> > ndev2 = mac->next_ndev;
> > if (ndev2)
> > netif_stop_queue(ndev2);
> >
> > hal_mac_stop(mac);
> > hal_mac_init(mac);
> > hal_mac_start(mac);
> >
> > // Accept TX packets again.
> > netif_trans_update(ndev);
> > netif_wake_queue(ndev);
> > if (ndev2) {
> > netif_trans_update(ndev2);
> > netif_wake_queue(ndev2);
> > }
> >
> > spin_unlock_irqrestore(&mac->comm->tx_lock, flags); }
> >
> > Is that ok?
>
> This ndev2 stuff is not nice. You probably need a cleaner abstract of two netdev's sharing
> one TX and RX ring. See if there are any other switchdev drivers with a similar structure
> you can copy. Maybe cpsw_new.c? But be careful with that driver. cpsw is a bit of a mess
> due to an incorrect initial design with respect to its L2 switch. A lot of my initial comments
> are to stop you making the same mistakes.

I'll define a array (pointer to struct net_dev) in driver private (shared)
structure to access to all net devices. No more mac->next_ndev;.

> Andrew

Thank you very much for your review.


Best regards,
Wells

2021-11-18 08:22:44

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi,


> > > > +static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
> > > > + 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00
> > >
> > > This does not have the locally administered bit set. Should it? Or
> > > is this and address from your OUI?
> >
> > This is default MAC address when MAC address in NVMEM is not found.
> > Fc:4b:bc:00:00:00 is OUI of "Sunplus Technology Co., Ltd.".
> > Can I keep this? or it should be removed?
>
> Please add a comment about whos OUI it is.
>
> It is however more normal to use a random MAC address if no other MAC address is available.
> That way, you avoid multiple devices on one LAN using the same default MAC address.

Yes, I'll add a comment about the OUI and also use 'get_random_int() % 255'
to generate the latest 3 octets (controller specific).


> > > > + if (mac->next_ndev) {
> > > > + struct net_device *ndev2 = mac->next_ndev;
> > > > +
> > > > + if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
> > > > + netif_carrier_on(ndev2);
> > > > + netif_start_queue(ndev2);
> > > > + } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
> > > > + netif_carrier_off(ndev2);
> > > > + netif_stop_queue(ndev2);
> > > > + }
> > >
> > > Looks very odd. The two netdev should be independent.
> >
> > I don't understand your comment.
> > ndev checks PORT_ABILITY_LINK_ST_P0
> > ndev2 checks PORT_ABILITY_LINK_ST_P1
> > They are independent already.
>
> I would try to remove the mac->next_ndev. I think without that, you will get a cleaner
> abstraction. You might want to keep an array of mac pointers in your top level shared
> structure.

Yes, I'll define a array (pointer to struct net_dev or mac) in driver private (shared)
structure to access to all net devices. No more mac->next_ndev;.


> Andrew

Thank you very much for your review.

Best regards,
Wells

2021-11-25 11:40:20

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Andrew,


Regarding hardware 'auto rmii' function of SP7021, we find a
way to 'disable' it.

We can use 'force' mode of MAC to set link up/down, speeds,
full/half-duplex, flow-control forcibly.

Although MAC still keeps sending MDIO commands to PHY and get
its status, but the read-back status has no use because MAC
is in 'force' mode.

Due to hardware design, we still need to set PHY address,
because MDIO controller of SP7021 only sends out MDIO
commands with the same address listed in PHY address
registers. The function below needs to be kept.

> > +void hal_phy_addr(struct sp_mac *mac) {
> > + struct sp_common *comm = mac->comm;
> > + u32 reg;
> > +
> > + // Set address of phy.
> > + reg = readl(comm->sp_reg_base + SP_MAC_FORCE_MODE);
> > + reg = (reg & (~(0x1f << 16))) | ((mac->phy_addr & 0x1f) << 16);
> > + if (mac->next_ndev) {
> > + struct net_device *ndev2 = mac->next_ndev;
> > + struct sp_mac *mac2 = netdev_priv(ndev2);
> > +
> > + reg = (reg & (~(0x1f << 24))) | ((mac2->phy_addr & 0x1f) << 24);
> > + }
> > + writel(reg, comm->sp_reg_base + SP_MAC_FORCE_MODE); }
>
> As i said before, the hardware never directly communicates with the PHY. So you can remove
> this.

If it is ok, I'll modify code and send a new patch.


Best regards,
Wells


> > > +void rx_descs_flush(struct sp_common *comm)
> >
> > As both Florian and I have said, you need a prefix for all your
> > functions, structures, etc. sp_ is not the best prefix either, it is not very unique.
> spl2sw_ would be better.
>
> I'll add prefix spl2sw for all functions, structures, file-names in next patch.
>
> I thought comment for revising prefix is only for structures, function and file name with
> prefix l2sw_ because 'l2sw_' has been used by other modules.
>
> Now I know prefix is necessary for all in this driver, except local variables and structure
> members.
>
>
> > > +void rx_descs_clean(struct sp_common *comm) {
> > > + u32 i, j;
> > > + struct mac_desc *rx_desc;
> > > + struct skb_info *rx_skbinfo;
> >
> > netdev wants reverse christmas tree. You need to change the order of your local variables,
> > longest lines first, shorted last.
>
> Yes, I'll rearrange local variables to 'reverse Christmas tree' order in next patch.
>
>
> > > +
> > > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > > + if (!comm->rx_skb_info[i])
> > > + continue;
> > > +
> > > + rx_desc = comm->rx_desc[i];
> > > + rx_skbinfo = comm->rx_skb_info[i];
> > > + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> > > + rx_desc[j].cmd1 = 0;
> > > + wmb(); // Clear OWN_BIT and then set other fields.
> > > + rx_desc[j].cmd2 = 0;
> > > + rx_desc[j].addr1 = 0;
> > > +
> > > + if (rx_skbinfo[j].skb) {
> > > + dma_unmap_single(&comm->pdev->dev, rx_skbinfo[j].mapping,
> > > + comm->rx_desc_buff_size, DMA_FROM_DEVICE);
> > > + dev_kfree_skb(rx_skbinfo[j].skb);
> > > + rx_skbinfo[j].skb = NULL;
> > > + rx_skbinfo[j].mapping = 0;
> > > + }
> > > + }
> > > +
> > > + kfree(rx_skbinfo);
> > > + comm->rx_skb_info[i] = NULL;
> > > + }
> > > +}
> >
> > > +int rx_descs_init(struct sp_common *comm) {
> > > + struct sk_buff *skb;
> > > + u32 i, j;
> > > + struct mac_desc *rx_desc;
> > > + struct skb_info *rx_skbinfo;
> > > +
> > > + for (i = 0; i < RX_DESC_QUEUE_NUM; i++) {
> > > + comm->rx_skb_info[i] = kmalloc_array(comm->rx_desc_num[i],
> > > + sizeof(struct skb_info), GFP_KERNEL);
> > > + if (!comm->rx_skb_info[i])
> > > + goto MEM_ALLOC_FAIL;
> > > +
> > > + rx_skbinfo = comm->rx_skb_info[i];
> > > + rx_desc = comm->rx_desc[i];
> > > + for (j = 0; j < comm->rx_desc_num[i]; j++) {
> > > + skb = __dev_alloc_skb(comm->rx_desc_buff_size + RX_OFFSET,
> > > + GFP_ATOMIC | GFP_DMA);
> > > + if (!skb)
> > > + goto MEM_ALLOC_FAIL;
> > > +
> > > + skb->dev = comm->ndev;
> > > + skb_reserve(skb, RX_OFFSET); /* +data +tail */
> > > +
> > > + rx_skbinfo[j].skb = skb;
> > > + rx_skbinfo[j].mapping = dma_map_single(&comm->pdev->dev, skb->data,
> > > + comm->rx_desc_buff_size,
> > > + DMA_FROM_DEVICE);
> > > + rx_desc[j].addr1 = rx_skbinfo[j].mapping;
> > > + rx_desc[j].addr2 = 0;
> > > + rx_desc[j].cmd2 = (j == comm->rx_desc_num[i] - 1) ?
> > > + EOR_BIT | comm->rx_desc_buff_size :
> > > + comm->rx_desc_buff_size;
> > > + wmb(); // Set OWN_BIT after other fields are effective.
> > > + rx_desc[j].cmd1 = OWN_BIT;
> > > + }
> > > + }
> > > +
> > > + return 0;
> > > +
> > > +MEM_ALLOC_FAIL:
> >
> > lower case labels. Didn't somebody already say that?
>
> I'll modify all labels to lowercase in next patch.
> Yes, Denis said that but patch not yet sent out.
>
>
> > > +int descs_init(struct sp_common *comm) {
> > > + u32 i, ret;
> > > +
> > > + // Initialize rx descriptor's data
> > > + comm->rx_desc_num[0] = RX_QUEUE0_DESC_NUM; #if RX_DESC_QUEUE_NUM > 1
> > > + comm->rx_desc_num[1] = RX_QUEUE1_DESC_NUM; #endif
> >
> > Avoid #if statements. Why is this needed?
>
> Yes, I'll remove the #if statement in next patch.
> It is indeed not necessary.
> RX_DESC_QUEUE_NUM is equal to 2.
>
>
> > > +++ b/drivers/net/ethernet/sunplus/sp_driver.c
> > > @@ -0,0 +1,606 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/* Copyright Sunplus Technology Co., Ltd.
> > > + * All rights reserved.
> > > + */
> > > +
> > > +#include <linux/clk.h>
> > > +#include <linux/reset.h>
> > > +#include <linux/nvmem-consumer.h>
> > > +#include <linux/of_net.h>
> > > +#include "sp_driver.h"
> > > +#include "sp_phy.h"
> > > +
> > > +static const char def_mac_addr[ETHERNET_MAC_ADDR_LEN] = {
> > > + 0xfc, 0x4b, 0xbc, 0x00, 0x00, 0x00
> >
> > This does not have the locally administered bit set. Should it? Or is this and address
> > from your OUI?
>
> This is default MAC address when MAC address in NVMEM is not found.
> Fc:4b:bc:00:00:00 is OUI of "Sunplus Technology Co., Ltd.".
> Can I keep this? or it should be removed?
>
>
> > > +static void ethernet_set_rx_mode(struct net_device *ndev) {
> > > + if (ndev) {
> >
> > How can ndev be NULL?
>
> Yes, I'll remove 'if (ndev) {' statement in next patch.
> It is redundant.
>
>
> > > +++ b/drivers/net/ethernet/sunplus/sp_hal.c
> > > @@ -0,0 +1,331 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/* Copyright Sunplus Technology Co., Ltd.
> > > + * All rights reserved.
> > > + */
> > > +
> > > +#include <linux/iopoll.h>
> > > +#include "sp_hal.h"
> > > +
> > > +void hal_mac_stop(struct sp_mac *mac)
> >
> > I suggest you avoid any references to hal. It makes people think you have ported a driver
> > from some other operating system and then put a layer of code on top of it. That is not
> > how you do it in Linux. This is a Linux driver, nothing else.
>
>
> Yes, I'll change file name 'sp_hal.c' to 'spl2sw_hw.c'.
> Function name in this file will also be changed, for example:
> hal_mac_stop() --> spl2sw_hw_mac_stop()
>
>
> > > +void hal_mac_reset(struct sp_mac *mac) { }
> > > +
> >
> > Should not exist.
>
> Yes, I'll remove it in next patch.
>
>
> > > +void hal_mac_addr_set(struct sp_mac *mac) {
> > > + struct sp_common *comm = mac->comm;
> > > + u32 reg;
> > > +
> > > + // Write MAC address.
> > > + writel(mac->mac_addr[0] + (mac->mac_addr[1] << 8),
> > > + comm->sp_reg_base + SP_W_MAC_15_0);
> > > + writel(mac->mac_addr[2] + (mac->mac_addr[3] << 8) + (mac->mac_addr[4] << 16) +
> > > + (mac->mac_addr[5] << 24), comm->sp_reg_base + SP_W_MAC_47_16);
> > > +
> > > + // Set aging=1
> > > + writel((mac->cpu_port << 10) + (mac->vlan_id << 7) + (1 << 4) + 0x1,
> > > + comm->sp_reg_base + SP_WT_MAC_AD0);
> >
> > Is this actually adding an entry into the address translation table?
> > If so, make this clear in the function name. You are not setting the MAC address, you
> are
> > just adding a static forwarding entry.
>
> Yes, this is actually adding an entry into address table.
> I'll change function name to spl2sw_mac_add_addr() in next patch.
> Is the name ok?
>
>
> > > +
> > > + // Wait for completing.
> > > + do {
> > > + reg = readl(comm->sp_reg_base + SP_WT_MAC_AD0);
> > > + ndelay(10);
> > > + netdev_dbg(mac->ndev, "wt_mac_ad0 = %08x\n", reg);
> > > + } while ((reg & (0x1 << 1)) == 0x0);
> > > +
> > > + netdev_dbg(mac->ndev, "mac_ad0 = %08x, mac_ad = %08x%04x\n",
> > > + readl(comm->sp_reg_base + SP_WT_MAC_AD0),
> > > + readl(comm->sp_reg_base + SP_W_MAC_47_16),
> > > + readl(comm->sp_reg_base + SP_W_MAC_15_0) & 0xffff); }
> >
> > > +void hal_rx_mode_set(struct net_device *ndev) {
> > > + struct sp_mac *mac = netdev_priv(ndev);
> > > + struct sp_common *comm = mac->comm;
> > > + u32 mask, reg, rx_mode;
> > > +
> > > + netdev_dbg(ndev, "ndev->flags = %08x\n", ndev->flags);
> > > +
> > > + mask = (mac->lan_port << 2) | (mac->lan_port << 0);
> > > + reg = readl(comm->sp_reg_base + SP_CPU_CNTL);
> > > +
> > > + if (ndev->flags & IFF_PROMISC) { /* Set promiscuous mode */
> > > + // Allow MC and unknown UC packets
> > > + rx_mode = (mac->lan_port << 2) | (mac->lan_port << 0);
> > > + } else if ((!netdev_mc_empty(ndev) && (ndev->flags & IFF_MULTICAST)) ||
> > > + (ndev->flags & IFF_ALLMULTI)) {
> > > + // Allow MC packets
> > > + rx_mode = (mac->lan_port << 2);
> > > + } else {
> > > + // Disable MC and unknown UC packets
> > > + rx_mode = 0;
> > > + }
> > > +
> > > + writel((reg & (~mask)) | ((~rx_mode) & mask), comm->sp_reg_base + SP_CPU_CNTL);
> > > + netdev_dbg(ndev, "cpu_cntl = %08x\n", readl(comm->sp_reg_base +
> > > +SP_CPU_CNTL));
> >
> > This looks like it belongs in the ethtool code.
>
> This function sets receiving mode.
>
>
> > > +int hal_mdio_access(struct sp_mac *mac, u8 op_cd, u8 phy_addr, u8
> > > +reg_addr, u32 wdata) {
> > > + struct sp_common *comm = mac->comm;
> > > + u32 val, ret;
> > > +
> > > + writel((wdata << 16) | (op_cd << 13) | (reg_addr << 8) | phy_addr,
> > > + comm->sp_reg_base + SP_PHY_CNTL_REG0);
> > > +
> > > + ret = read_poll_timeout(readl, val, val & op_cd, 10, 1000, 1,
> > > + comm->sp_reg_base + SP_PHY_CNTL_REG1);
> > > + if (ret == 0)
> > > + return val >> 16;
> > > + else
> > > + return ret;
> > > +}
> >
> > Should go with the other mdio code.
>
> I'll move it into 'spl2sw_mdio.c' in next patch.
>
> I put all hardware-related functions in sp_hal.c (will be changed to spl2sw_hw.c).
> All functions in other files won't touch hardware registers.
> This seems not Linux driver style.
>
>
> > > +void hal_phy_addr(struct sp_mac *mac) {
> > > + struct sp_common *comm = mac->comm;
> > > + u32 reg;
> > > +
> > > + // Set address of phy.
> > > + reg = readl(comm->sp_reg_base + SP_MAC_FORCE_MODE);
> > > + reg = (reg & (~(0x1f << 16))) | ((mac->phy_addr & 0x1f) << 16);
> > > + if (mac->next_ndev) {
> > > + struct net_device *ndev2 = mac->next_ndev;
> > > + struct sp_mac *mac2 = netdev_priv(ndev2);
> > > +
> > > + reg = (reg & (~(0x1f << 24))) | ((mac2->phy_addr & 0x1f) << 24);
> > > + }
> > > + writel(reg, comm->sp_reg_base + SP_MAC_FORCE_MODE); }
> >
> > As i said before, the hardware never directly communicates with the PHY. So you can remove
> > this.
>
> I'll remove this function in next patch.
>
> But now I cannot find a way to disable hardware 'auto rmii' function.
> If I remove this function right now, MAC may get wrong status of PHY
> from wrong address because SP7021 MAC communicates with PHY
> automatically. This may cause more problem.
>
> I am consulting with ASIC engineer. Hopefully, someone can find
> a way to disable the auto function.
>
>
> > > +static void port_status_change(struct sp_mac *mac) {
> > > + u32 reg;
> > > + struct net_device *ndev = mac->ndev;
> > > +
> > > + reg = read_port_ability(mac);
> > > + if (!netif_carrier_ok(ndev) && (reg & PORT_ABILITY_LINK_ST_P0)) {
> > > + netif_carrier_on(ndev);
> >
> > phylib should be handling the carrier for you.
>
> I'll remove this function in next patch.
> If 'auto rmii' function is removed, we no more need this function.
>
>
> > > + if (mac->next_ndev) {
> > > + struct net_device *ndev2 = mac->next_ndev;
> > > +
> > > + if (!netif_carrier_ok(ndev2) && (reg & PORT_ABILITY_LINK_ST_P1)) {
> > > + netif_carrier_on(ndev2);
> > > + netif_start_queue(ndev2);
> > > + } else if (netif_carrier_ok(ndev2) && !(reg & PORT_ABILITY_LINK_ST_P1)) {
> > > + netif_carrier_off(ndev2);
> > > + netif_stop_queue(ndev2);
> > > + }
> >
> > Looks very odd. The two netdev should be independent.
>
> I don't understand your comment.
> ndev checks PORT_ABILITY_LINK_ST_P0
> ndev2 checks PORT_ABILITY_LINK_ST_P1
> They are independent already.
>
>
> > > diff --git a/drivers/net/ethernet/sunplus/sp_mdio.c
> > > b/drivers/net/ethernet/sunplus/sp_mdio.c
> > > new file mode 100644
> > > index 0000000..f6a7e64
> > > --- /dev/null
> > > +++ b/drivers/net/ethernet/sunplus/sp_mdio.c
> > > @@ -0,0 +1,90 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/* Copyright Sunplus Technology Co., Ltd.
> > > + * All rights reserved.
> > > + */
> > > +
> > > +#include "sp_mdio.h"
> > > +
> > > +u32 mdio_read(struct sp_mac *mac, u32 phy_id, u16 regnum) {
> > > + int ret;
> > > +
> > > + ret = hal_mdio_access(mac, MDIO_READ_CMD, phy_id, regnum, 0);
> > > + if (ret < 0)
> > > + return -EOPNOTSUPP;
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +u32 mdio_write(struct sp_mac *mac, u32 phy_id, u32 regnum, u16 val) {
> > > + int ret;
> > > +
> > > + ret = hal_mdio_access(mac, MDIO_WRITE_CMD, phy_id, regnum, val);
> > > + if (ret < 0)
> > > + return -EOPNOTSUPP;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int mii_read(struct mii_bus *bus, int phy_id, int regnum) {
> > > + struct sp_mac *mac = bus->priv;
> >
> > What happened about my request to return -EOPNOTSUPP for C45 requests?
>
> Sorry for overlooking the comment!
> I am not sure how to check C45 request. Should I add statements like:
>
> if (regnum & MII_ADDR_C45)
> Return -EOPNOTSUPP;
>
> for mdio_read() and mdio_write()?
>
>
> > Andrew
>
> Thank you very much for your review!
>
> Best regards,
> Wells

2021-11-25 15:22:50

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

> Due to hardware design, we still need to set PHY address,
> because MDIO controller of SP7021 only sends out MDIO
> commands with the same address listed in PHY address
> registers. The function below needs to be kept.

I suggest you actually set it to some other address. One of the
good/bad things about MDIO is you have no idea if the device is
there. A read to a device which does not exist just returns 0xffff,
not an error. So i would set the address of 0x1f. I've never seen a
PHY actually use that address.

Andrew

2021-11-26 03:58:49

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Andrew,

I set phy-id registers to 30 and 31 and found the read-back
values of mdio read commands from CPU are all 0x0000.

I consulted with an ASIC engineer. She confirmed that if
phy-id of a mdio command from CPU does not match any
phy-id registers, the mdio command will not be sent out.

She explained if phy-id of a mdio command does not match
any phy-id registers (represent addresses of external PHYs),
why MAC needs to send a command to non-existing PHY?


Best regards,

Wells Lu
?f????

?????B???M??/Smart Computing Program
?a?x???x?Ʒ~?s/Home Entertainment Business Unit
?⶧????/Sunplus Technology Co., Ltd.
?a?}?G300?s?ˬ??Ƕ??ϳзs?@??19??
19, Innovation 1st Road,
Science-based Industrial Park
Hsin-Chu, Taiwan 300
TEL?G886-3-5786005 ext. 2580

> -----Original Message-----
> From: Andrew Lunn <[email protected]>
> Sent: Thursday, November 25, 2021 11:21 PM
> To: Wells Lu ?f???? <[email protected]>
> Cc: Wells Lu <[email protected]>; [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; Vincent Shih ?I?C?E
> <[email protected]>
> Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021
>
> > Due to hardware design, we still need to set PHY address, because MDIO
> > controller of SP7021 only sends out MDIO commands with the same
> > address listed in PHY address registers. The function below needs to
> > be kept.
>
> I suggest you actually set it to some other address. One of the good/bad things about MDIO
> is you have no idea if the device is there. A read to a device which does not exist just
> returns 0xffff, not an error. So i would set the address of 0x1f. I've never seen a PHY
> actually use that address.
>
> Andrew

2021-11-26 14:40:56

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

On Fri, Nov 26, 2021 at 03:56:28AM +0000, Wells Lu 呂芳騰 wrote:
> Hi Andrew,
>
> I set phy-id registers to 30 and 31 and found the read-back
> values of mdio read commands from CPU are all 0x0000.
>
> I consulted with an ASIC engineer. She confirmed that if
> phy-id of a mdio command from CPU does not match any
> phy-id registers, the mdio command will not be sent out.
>
> She explained if phy-id of a mdio command does not match
> any phy-id registers (represent addresses of external PHYs),
> why MAC needs to send a command to non-existing PHY?

Reads or writes on a real PHY which Linux is driving can have side
effects. There is a link statue register which latches. Read it once,
you get the last status, read it again, you get the current status. If
the MAC hardware is reading this register as well a Linux, bad things
will happen. A read on the interrupt status register often clears the
interrupts. So Linux will not see the interrupts.

So you need to make sure you hardware is not touching a PHY which
Linux uses. Which is why i suggested using MDIO bus address 31, which
generally does not have a PHY at that address.

Andrew

2021-11-26 16:15:03

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Andrew,


From data provided by ASIC engineer, MAC of SP7021 only
reads the 4 registers of PHY:
0: Control register
1: Status register
4: Auto-negotiation advertisement register
5: Auto-negotiation link partner ability register

It does not read any other registers of PHY.


Best regards,
Wells Lu


> > Hi Andrew,
> >
> > I set phy-id registers to 30 and 31 and found the read-back values of
> > mdio read commands from CPU are all 0x0000.
> >
> > I consulted with an ASIC engineer. She confirmed that if phy-id of a
> > mdio command from CPU does not match any phy-id registers, the mdio
> > command will not be sent out.
> >
> > She explained if phy-id of a mdio command does not match any phy-id
> > registers (represent addresses of external PHYs), why MAC needs to
> > send a command to non-existing PHY?
>
> Reads or writes on a real PHY which Linux is driving can have side effects. There is a
> link statue register which latches. Read it once, you get the last status, read it again,
> you get the current status. If the MAC hardware is reading this register as well a Linux,
> bad things will happen. A read on the interrupt status register often clears the interrupts.
> So Linux will not see the interrupts.
>
> So you need to make sure you hardware is not touching a PHY which Linux uses. Which is
> why i suggested using MDIO bus address 31, which generally does not have a PHY at that
> address.
>
> Andrew

2021-11-26 18:09:13

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

On Fri, Nov 26, 2021 at 04:12:46PM +0000, Wells Lu 呂芳騰 wrote:
> Hi Andrew,
>
>
> From data provided by ASIC engineer, MAC of SP7021 only
> reads the 4 registers of PHY:
> 0: Control register
> 1: Status register

This is the register which has latching of the link
status. genphy_update_link() expects this latching behaviour, and if
the hardware reads the register, that behaviour is not going to
happen.

Andrew

2021-11-26 19:25:46

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Andrew,

I read specification of ICPlus IP101G (10M/100M PHY).
Bits of register 0 (control) and register 1 (status)
are R/W or RO type. They will not be cleared after
read. No matter how many times they are read, the
read-back value is the same.

For example,
Value of register 0 (control) is 0x3100
Value of register 1 (status) is 0x786d

The read-back values are always the same unless you
unplug the cable.

Besides, we use polling mode (phydev->irq = PHY_POLL)
PHY state-machine is triggered by using 1-Hz work-
queue, not interrupt.

We didn't find any problem after many tests after
using 'force' mode.

Can we go with this approach?


Best regards,
Wells


> > Hi Andrew,
> >
> >
> > From data provided by ASIC engineer, MAC of SP7021 only reads the 4
> > registers of PHY:
> > 0: Control register
> > 1: Status register
>
> This is the register which has latching of the link status. genphy_update_link() expects
> this latching behaviour, and if the hardware reads the register, that behaviour is not
> going to happen.
>
> Andrew

2021-11-26 19:34:50

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

On Fri, Nov 26, 2021 at 07:13:23PM +0000, Wells Lu 呂芳騰 wrote:
> Hi Andrew,
>
> I read specification of ICPlus IP101G (10M/100M PHY).
> Bits of register 0 (control) and register 1 (status)
> are R/W or RO type. They will not be cleared after
> read. No matter how many times they are read, the
> read-back value is the same.

Please read 802.3,

Section 22.2.4.2: Status register (Register 1)

Table 22-8 Status register bit definitions

Bit 1.2 Link Status is marked as RO/LL, meaning read only Latching
low.

> Can we go with this approach?

You need to not make any read on the PHY which Linux is driving.
Configure the hardware to read on an address where there is no PHY.

Andrew

2021-11-29 11:19:28

by Wells Lu 呂芳騰

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] net: ethernet: Add driver for Sunplus SP7021

Hi Andrew,


Thanks a lot for explanation!.

> You need to not make any read on the PHY which Linux is driving.
> Configure the hardware to read on an address where there is no PHY.
>

I will modify code to set hardware external PHY address to 31.
In mii_read() or mii_write() functions, set hardware external PHY
address to real PHY address, so that mii read or write command can
be sent out by hardware. Set hardware external PHY address back to
31 after mii read or write command done.


Best regards,
Wells Lu