Commit b8ac21d2 authored by David S. Miller's avatar David S. Miller

Merge branch 'tsn-endpoint-driver'

Gerhard Engleder says:

====================
TSN endpoint Ethernet MAC driver

This series adds a driver for my FPGA based TSN endpoint Ethernet MAC.
It also includes the device tree binding.

The device is designed as Ethernet MAC for TSN networks. It will be used
in PLCs with real-time requirements up to isochronous communication with
protocols like OPC UA Pub/Sub.

v3:
 - set MAC mode based on PHY information (Andrew Lunn)
 - remove/postpone loopback mode interface (Andrew Lunn)
 - add suppress_preamble node support (Andrew Lunn)
 - add mdio timeout (Andrew Lunn)
 - no need to call phy_start_aneg (Andrew Lunn)
 - remove unreachable code (Andrew Lunn)
 - move 'struct napi_struct' closer to queues (Vinicius Costa Gomes)
 - remove unused variable (kernel test robot)
 - switch from mdio interrupt to polling
 - mdio register without PHY address flag
 - thread safe interrupt enable register
 - add PTP_1588_CLOCK_OPTIONAL dependency to Kconfig
 - introduce dmadev for DMA allocation
 - mdiobus for platforms without device tree
 - prepare MAC address support for platforms without device tree
 - add missing interrupt disable to probe error path

v2:
 - add C45 check (Andrew Lunn)
 - forward phy_connect_direct() return value (Andrew Lunn)
 - use phy_remove_link_mode() (Andrew Lunn)
 - do not touch PHY directly, use PHY subsystem (Andrew Lunn)
 - remove management data lock (Andrew Lunn)
 - use phy_loopback (Andrew Lunn)
 - remove GMII2RGMII handling, use xgmiitorgmii (Andrew Lunn)
 - remove char device for direct TX/RX queue access (Andrew Lunn)
 - mdio node for mdiobus (Rob Herring)
 - simplify compatible node (Rob Herring)
 - limit number of items of reg and interrupts nodes (Rob Herring)
 - restrict phy-connection-type node (Rob Herring)
 - reference to mdio.yaml under mdio node (Rob Herring)
 - remove device tree (Michal Simek)
 - fix %llx warning (kernel test robot)
 - fix unused tmp variable warning (kernel test robot)
 - add missing of_node_put() for of_parse_phandle()
 - use devm_mdiobus_alloc()
 - simplify mdiobus read/write
 - reduce required nodes
 - ethtool priv flags interface for loopback
 - add missing static for some functions
 - remove obsolete hardware defines
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents a18e6521 403f69bb
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/engleder,tsnep.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: TSN endpoint Ethernet MAC binding
maintainers:
- Gerhard Engleder <gerhard@engleder-embedded.com>
allOf:
- $ref: ethernet-controller.yaml#
properties:
compatible:
const: engleder,tsnep
reg:
maxItems: 1
interrupts:
maxItems: 1
local-mac-address: true
mac-address: true
nvmem-cells: true
nvmem-cells-names: true
phy-connection-type:
enum:
- mii
- gmii
- rgmii
- rgmii-id
phy-mode: true
phy-handle: true
mdio:
type: object
$ref: "mdio.yaml#"
description: optional node for embedded MDIO controller
required:
- compatible
- reg
- interrupts
additionalProperties: false
examples:
- |
axi {
#address-cells = <2>;
#size-cells = <2>;
tnsep0: ethernet@a0000000 {
compatible = "engleder,tsnep";
reg = <0x0 0xa0000000 0x0 0x10000>;
interrupts = <0 89 1>;
interrupt-parent = <&gic>;
local-mac-address = [00 00 00 00 00 00];
phy-mode = "rgmii";
phy-handle = <&phy0>;
mdio {
#address-cells = <1>;
#size-cells = <0>;
suppress-preamble;
phy0: ethernet-phy@1 {
reg = <1>;
rxc-skew-ps = <1080>;
};
};
};
};
......@@ -379,6 +379,8 @@ patternProperties:
description: Silicon Laboratories (formerly Energy Micro AS)
"^engicam,.*":
description: Engicam S.r.l.
"^engleder,.*":
description: Engleder
"^epcos,.*":
description: EPCOS AG
"^epfl,.*":
......
......@@ -73,6 +73,7 @@ config DNET
source "drivers/net/ethernet/dec/Kconfig"
source "drivers/net/ethernet/dlink/Kconfig"
source "drivers/net/ethernet/emulex/Kconfig"
source "drivers/net/ethernet/engleder/Kconfig"
source "drivers/net/ethernet/ezchip/Kconfig"
source "drivers/net/ethernet/faraday/Kconfig"
source "drivers/net/ethernet/freescale/Kconfig"
......
......@@ -36,6 +36,7 @@ obj-$(CONFIG_DNET) += dnet.o
obj-$(CONFIG_NET_VENDOR_DEC) += dec/
obj-$(CONFIG_NET_VENDOR_DLINK) += dlink/
obj-$(CONFIG_NET_VENDOR_EMULEX) += emulex/
obj-$(CONFIG_NET_VENDOR_ENGLEDER) += engleder/
obj-$(CONFIG_NET_VENDOR_EZCHIP) += ezchip/
obj-$(CONFIG_NET_VENDOR_FARADAY) += faraday/
obj-$(CONFIG_NET_VENDOR_FREESCALE) += freescale/
......
# SPDX-License-Identifier: GPL-2.0
#
# Engleder network device configuration
#
config NET_VENDOR_ENGLEDER
bool "Engleder devices"
default y
help
If you have a network (Ethernet) card belonging to this class, say Y.
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 Engleder devices. If you say Y, you will be asked
for your specific card in the following questions.
if NET_VENDOR_ENGLEDER
config TSNEP
tristate "TSN endpoint support"
depends on PTP_1588_CLOCK_OPTIONAL
select PHYLIB
help
Support for the Engleder TSN endpoint Ethernet MAC IP Core.
To compile this driver as a module, choose M here. The module will be
called tsnep.
config TSNEP_SELFTESTS
bool "TSN endpoint self test support"
default n
depends on TSNEP
help
This enables self test support within the TSN endpoint driver.
If unsure, say N.
endif # NET_VENDOR_ENGLEDER
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Engleder Ethernet drivers
#
obj-$(CONFIG_TSNEP) += tsnep.o
tsnep-objs := tsnep_main.o tsnep_ethtool.o tsnep_ptp.o tsnep_tc.o \
$(tsnep-y)
tsnep-$(CONFIG_TSNEP_SELFTESTS) += tsnep_selftests.o
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
#ifndef _TSNEP_H
#define _TSNEP_H
#include "tsnep_hw.h"
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/etherdevice.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#include <linux/net_tstamp.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/miscdevice.h>
#define TSNEP "tsnep"
#define TSNEP_RING_SIZE 256
#define TSNEP_RING_ENTRIES_PER_PAGE (PAGE_SIZE / TSNEP_DESC_SIZE)
#define TSNEP_RING_PAGE_COUNT (TSNEP_RING_SIZE / TSNEP_RING_ENTRIES_PER_PAGE)
#define TSNEP_QUEUES 1
struct tsnep_gcl {
void __iomem *addr;
u64 base_time;
u64 cycle_time;
u64 cycle_time_extension;
struct tsnep_gcl_operation operation[TSNEP_GCL_COUNT];
int count;
u64 change_limit;
u64 start_time;
bool change;
};
struct tsnep_tx_entry {
struct tsnep_tx_desc *desc;
struct tsnep_tx_desc_wb *desc_wb;
dma_addr_t desc_dma;
bool owner_user_flag;
u32 properties;
struct sk_buff *skb;
size_t len;
DEFINE_DMA_UNMAP_ADDR(dma);
};
struct tsnep_tx {
struct tsnep_adapter *adapter;
void __iomem *addr;
void *page[TSNEP_RING_PAGE_COUNT];
dma_addr_t page_dma[TSNEP_RING_PAGE_COUNT];
/* TX ring lock */
spinlock_t lock;
struct tsnep_tx_entry entry[TSNEP_RING_SIZE];
int write;
int read;
u32 owner_counter;
int increment_owner_counter;
u32 packets;
u32 bytes;
u32 dropped;
};
struct tsnep_rx_entry {
struct tsnep_rx_desc *desc;
struct tsnep_rx_desc_wb *desc_wb;
dma_addr_t desc_dma;
u32 properties;
struct sk_buff *skb;
size_t len;
DEFINE_DMA_UNMAP_ADDR(dma);
};
struct tsnep_rx {
struct tsnep_adapter *adapter;
void __iomem *addr;
void *page[TSNEP_RING_PAGE_COUNT];
dma_addr_t page_dma[TSNEP_RING_PAGE_COUNT];
struct tsnep_rx_entry entry[TSNEP_RING_SIZE];
int read;
u32 owner_counter;
int increment_owner_counter;
u32 packets;
u32 bytes;
u32 dropped;
u32 multicast;
};
struct tsnep_queue {
struct tsnep_adapter *adapter;
struct tsnep_tx *tx;
struct tsnep_rx *rx;
struct napi_struct napi;
u32 irq_mask;
};
struct tsnep_adapter {
struct net_device *netdev;
u8 mac_address[ETH_ALEN];
struct mii_bus *mdiobus;
bool suppress_preamble;
phy_interface_t phy_mode;
struct phy_device *phydev;
int msg_enable;
struct platform_device *pdev;
struct device *dmadev;
void __iomem *addr;
unsigned long size;
int irq;
bool gate_control;
/* gate control lock */
struct mutex gate_control_lock;
bool gate_control_active;
struct tsnep_gcl gcl[2];
int next_gcl;
struct hwtstamp_config hwtstamp_config;
struct ptp_clock *ptp_clock;
struct ptp_clock_info ptp_clock_info;
/* ptp clock lock */
spinlock_t ptp_lock;
int num_tx_queues;
struct tsnep_tx tx[TSNEP_MAX_QUEUES];
int num_rx_queues;
struct tsnep_rx rx[TSNEP_MAX_QUEUES];
int num_queues;
struct tsnep_queue queue[TSNEP_MAX_QUEUES];
};
extern const struct ethtool_ops tsnep_ethtool_ops;
int tsnep_ptp_init(struct tsnep_adapter *adapter);
void tsnep_ptp_cleanup(struct tsnep_adapter *adapter);
int tsnep_ptp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd);
int tsnep_tc_init(struct tsnep_adapter *adapter);
void tsnep_tc_cleanup(struct tsnep_adapter *adapter);
int tsnep_tc_setup(struct net_device *netdev, enum tc_setup_type type,
void *type_data);
#if IS_ENABLED(CONFIG_TSNEP_SELFTESTS)
int tsnep_ethtool_get_test_count(void);
void tsnep_ethtool_get_test_strings(u8 *data);
void tsnep_ethtool_self_test(struct net_device *netdev,
struct ethtool_test *eth_test, u64 *data);
#else
static inline int tsnep_ethtool_get_test_count(void)
{
return -EOPNOTSUPP;
}
static inline void tsnep_ethtool_get_test_strings(u8 *data)
{
/* not enabled */
}
static inline void tsnep_ethtool_self_test(struct net_device *dev,
struct ethtool_test *eth_test,
u64 *data)
{
/* not enabled */
}
#endif /* CONFIG_TSNEP_SELFTESTS */
void tsnep_get_system_time(struct tsnep_adapter *adapter, u64 *time);
#endif /* _TSNEP_H */
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
#include "tsnep.h"
static const char tsnep_stats_strings[][ETH_GSTRING_LEN] = {
"rx_packets",
"rx_bytes",
"rx_dropped",
"rx_multicast",
"rx_phy_errors",
"rx_forwarded_phy_errors",
"rx_invalid_frame_errors",
"tx_packets",
"tx_bytes",
"tx_dropped",
};
struct tsnep_stats {
u64 rx_packets;
u64 rx_bytes;
u64 rx_dropped;
u64 rx_multicast;
u64 rx_phy_errors;
u64 rx_forwarded_phy_errors;
u64 rx_invalid_frame_errors;
u64 tx_packets;
u64 tx_bytes;
u64 tx_dropped;
};
#define TSNEP_STATS_COUNT (sizeof(struct tsnep_stats) / sizeof(u64))
static const char tsnep_rx_queue_stats_strings[][ETH_GSTRING_LEN] = {
"rx_%d_packets",
"rx_%d_bytes",
"rx_%d_dropped",
"rx_%d_multicast",
"rx_%d_no_descriptor_errors",
"rx_%d_buffer_too_small_errors",
"rx_%d_fifo_overflow_errors",
"rx_%d_invalid_frame_errors",
};
struct tsnep_rx_queue_stats {
u64 rx_packets;
u64 rx_bytes;
u64 rx_dropped;
u64 rx_multicast;
u64 rx_no_descriptor_errors;
u64 rx_buffer_too_small_errors;
u64 rx_fifo_overflow_errors;
u64 rx_invalid_frame_errors;
};
#define TSNEP_RX_QUEUE_STATS_COUNT (sizeof(struct tsnep_rx_queue_stats) / \
sizeof(u64))
static const char tsnep_tx_queue_stats_strings[][ETH_GSTRING_LEN] = {
"tx_%d_packets",
"tx_%d_bytes",
"tx_%d_dropped",
};
struct tsnep_tx_queue_stats {
u64 tx_packets;
u64 tx_bytes;
u64 tx_dropped;
};
#define TSNEP_TX_QUEUE_STATS_COUNT (sizeof(struct tsnep_tx_queue_stats) / \
sizeof(u64))
static void tsnep_ethtool_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *drvinfo)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
strscpy(drvinfo->driver, TSNEP, sizeof(drvinfo->driver));
strscpy(drvinfo->bus_info, dev_name(&adapter->pdev->dev),
sizeof(drvinfo->bus_info));
}
static int tsnep_ethtool_get_regs_len(struct net_device *netdev)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
int len;
int num_additional_queues;
len = TSNEP_MAC_SIZE;
/* first queue pair is within TSNEP_MAC_SIZE, only queues additional to
* the first queue pair extend the register length by TSNEP_QUEUE_SIZE
*/
num_additional_queues =
max(adapter->num_tx_queues, adapter->num_rx_queues) - 1;
len += TSNEP_QUEUE_SIZE * num_additional_queues;
return len;
}
static void tsnep_ethtool_get_regs(struct net_device *netdev,
struct ethtool_regs *regs,
void *p)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
regs->version = 1;
memcpy_fromio(p, adapter->addr, regs->len);
}
static u32 tsnep_ethtool_get_msglevel(struct net_device *netdev)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
return adapter->msg_enable;
}
static void tsnep_ethtool_set_msglevel(struct net_device *netdev, u32 data)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
adapter->msg_enable = data;
}
static void tsnep_ethtool_get_strings(struct net_device *netdev, u32 stringset,
u8 *data)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
int rx_count = adapter->num_rx_queues;
int tx_count = adapter->num_tx_queues;
int i, j;
switch (stringset) {
case ETH_SS_STATS:
memcpy(data, tsnep_stats_strings, sizeof(tsnep_stats_strings));
data += sizeof(tsnep_stats_strings);
for (i = 0; i < rx_count; i++) {
for (j = 0; j < TSNEP_RX_QUEUE_STATS_COUNT; j++) {
snprintf(data, ETH_GSTRING_LEN,
tsnep_rx_queue_stats_strings[j], i);
data += ETH_GSTRING_LEN;
}
}
for (i = 0; i < tx_count; i++) {
for (j = 0; j < TSNEP_TX_QUEUE_STATS_COUNT; j++) {
snprintf(data, ETH_GSTRING_LEN,
tsnep_tx_queue_stats_strings[j], i);
data += ETH_GSTRING_LEN;
}
}
break;
case ETH_SS_TEST:
tsnep_ethtool_get_test_strings(data);
break;
}
}
static void tsnep_ethtool_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats *stats,
u64 *data)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
int rx_count = adapter->num_rx_queues;
int tx_count = adapter->num_tx_queues;
struct tsnep_stats tsnep_stats;
struct tsnep_rx_queue_stats tsnep_rx_queue_stats;
struct tsnep_tx_queue_stats tsnep_tx_queue_stats;
u32 reg;
int i;
memset(&tsnep_stats, 0, sizeof(tsnep_stats));
for (i = 0; i < adapter->num_rx_queues; i++) {
tsnep_stats.rx_packets += adapter->rx[i].packets;
tsnep_stats.rx_bytes += adapter->rx[i].bytes;
tsnep_stats.rx_dropped += adapter->rx[i].dropped;
tsnep_stats.rx_multicast += adapter->rx[i].multicast;
}
reg = ioread32(adapter->addr + ECM_STAT);
tsnep_stats.rx_phy_errors =
(reg & ECM_STAT_RX_ERR_MASK) >> ECM_STAT_RX_ERR_SHIFT;
tsnep_stats.rx_forwarded_phy_errors =
(reg & ECM_STAT_FWD_RX_ERR_MASK) >> ECM_STAT_FWD_RX_ERR_SHIFT;
tsnep_stats.rx_invalid_frame_errors =
(reg & ECM_STAT_INV_FRM_MASK) >> ECM_STAT_INV_FRM_SHIFT;
for (i = 0; i < adapter->num_tx_queues; i++) {
tsnep_stats.tx_packets += adapter->tx[i].packets;
tsnep_stats.tx_bytes += adapter->tx[i].bytes;
tsnep_stats.tx_dropped += adapter->tx[i].dropped;
}
memcpy(data, &tsnep_stats, sizeof(tsnep_stats));
data += TSNEP_STATS_COUNT;
for (i = 0; i < rx_count; i++) {
memset(&tsnep_rx_queue_stats, 0, sizeof(tsnep_rx_queue_stats));
tsnep_rx_queue_stats.rx_packets = adapter->rx[i].packets;
tsnep_rx_queue_stats.rx_bytes = adapter->rx[i].bytes;
tsnep_rx_queue_stats.rx_dropped = adapter->rx[i].dropped;
tsnep_rx_queue_stats.rx_multicast = adapter->rx[i].multicast;
reg = ioread32(adapter->addr + TSNEP_QUEUE(i) +
TSNEP_RX_STATISTIC);
tsnep_rx_queue_stats.rx_no_descriptor_errors =
(reg & TSNEP_RX_STATISTIC_NO_DESC_MASK) >>
TSNEP_RX_STATISTIC_NO_DESC_SHIFT;
tsnep_rx_queue_stats.rx_buffer_too_small_errors =
(reg & TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_MASK) >>
TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_SHIFT;
tsnep_rx_queue_stats.rx_fifo_overflow_errors =
(reg & TSNEP_RX_STATISTIC_FIFO_OVERFLOW_MASK) >>
TSNEP_RX_STATISTIC_FIFO_OVERFLOW_SHIFT;
tsnep_rx_queue_stats.rx_invalid_frame_errors =
(reg & TSNEP_RX_STATISTIC_INVALID_FRAME_MASK) >>
TSNEP_RX_STATISTIC_INVALID_FRAME_SHIFT;
memcpy(data, &tsnep_rx_queue_stats,
sizeof(tsnep_rx_queue_stats));
data += TSNEP_RX_QUEUE_STATS_COUNT;
}
for (i = 0; i < tx_count; i++) {
memset(&tsnep_tx_queue_stats, 0, sizeof(tsnep_tx_queue_stats));
tsnep_tx_queue_stats.tx_packets += adapter->tx[i].packets;
tsnep_tx_queue_stats.tx_bytes += adapter->tx[i].bytes;
tsnep_tx_queue_stats.tx_dropped += adapter->tx[i].dropped;
memcpy(data, &tsnep_tx_queue_stats,
sizeof(tsnep_tx_queue_stats));
data += TSNEP_TX_QUEUE_STATS_COUNT;
}
}
static int tsnep_ethtool_get_sset_count(struct net_device *netdev, int sset)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
int rx_count;
int tx_count;
switch (sset) {
case ETH_SS_STATS:
rx_count = adapter->num_rx_queues;
tx_count = adapter->num_tx_queues;
return TSNEP_STATS_COUNT +
TSNEP_RX_QUEUE_STATS_COUNT * rx_count +
TSNEP_TX_QUEUE_STATS_COUNT * tx_count;
case ETH_SS_TEST:
return tsnep_ethtool_get_test_count();
default:
return -EOPNOTSUPP;
}
}
static int tsnep_ethtool_get_ts_info(struct net_device *dev,
struct ethtool_ts_info *info)
{
struct tsnep_adapter *adapter = netdev_priv(dev);
info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_RX_SOFTWARE |
SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
if (adapter->ptp_clock)
info->phc_index = ptp_clock_index(adapter->ptp_clock);
else
info->phc_index = -1;
info->tx_types = BIT(HWTSTAMP_TX_OFF) |
BIT(HWTSTAMP_TX_ON);
info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
BIT(HWTSTAMP_FILTER_ALL);
return 0;
}
const struct ethtool_ops tsnep_ethtool_ops = {
.get_drvinfo = tsnep_ethtool_get_drvinfo,
.get_regs_len = tsnep_ethtool_get_regs_len,
.get_regs = tsnep_ethtool_get_regs,
.get_msglevel = tsnep_ethtool_get_msglevel,
.set_msglevel = tsnep_ethtool_set_msglevel,
.nway_reset = phy_ethtool_nway_reset,
.get_link = ethtool_op_get_link,
.self_test = tsnep_ethtool_self_test,
.get_strings = tsnep_ethtool_get_strings,
.get_ethtool_stats = tsnep_ethtool_get_ethtool_stats,
.get_sset_count = tsnep_ethtool_get_sset_count,
.get_ts_info = tsnep_ethtool_get_ts_info,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings,
};
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
/* Hardware definition of TSNEP and EtherCAT MAC device */
#ifndef _TSNEP_HW_H
#define _TSNEP_HW_H
#include <linux/types.h>
/* type */
#define ECM_TYPE 0x0000
#define ECM_REVISION_MASK 0x000000FF
#define ECM_REVISION_SHIFT 0
#define ECM_VERSION_MASK 0x0000FF00
#define ECM_VERSION_SHIFT 8
#define ECM_QUEUE_COUNT_MASK 0x00070000
#define ECM_QUEUE_COUNT_SHIFT 16
#define ECM_GATE_CONTROL 0x02000000
/* system time */
#define ECM_SYSTEM_TIME_LOW 0x0008
#define ECM_SYSTEM_TIME_HIGH 0x000C
/* clock */
#define ECM_CLOCK_RATE 0x0010
#define ECM_CLOCK_RATE_OFFSET_MASK 0x7FFFFFFF
#define ECM_CLOCK_RATE_OFFSET_SIGN 0x80000000
/* interrupt */
#define ECM_INT_ENABLE 0x0018
#define ECM_INT_ACTIVE 0x001C
#define ECM_INT_ACKNOWLEDGE 0x001C
#define ECM_INT_LINK 0x00000020
#define ECM_INT_TX_0 0x00000100
#define ECM_INT_RX_0 0x00000200
#define ECM_INT_ALL 0x7FFFFFFF
#define ECM_INT_DISABLE 0x80000000
/* reset */
#define ECM_RESET 0x0020
#define ECM_RESET_COMMON 0x00000001
#define ECM_RESET_CHANNEL 0x00000100
#define ECM_RESET_TXRX 0x00010000
/* control and status */
#define ECM_STATUS 0x0080
#define ECM_LINK_MODE_OFF 0x01000000
#define ECM_LINK_MODE_100 0x02000000
#define ECM_LINK_MODE_1000 0x04000000
#define ECM_NO_LINK 0x01000000
#define ECM_LINK_MODE_MASK 0x06000000
/* management data */
#define ECM_MD_CONTROL 0x0084
#define ECM_MD_STATUS 0x0084
#define ECM_MD_PREAMBLE 0x00000001
#define ECM_MD_READ 0x00000004
#define ECM_MD_WRITE 0x00000002
#define ECM_MD_ADDR_MASK 0x000000F8
#define ECM_MD_ADDR_SHIFT 3
#define ECM_MD_PHY_ADDR_MASK 0x00001F00
#define ECM_MD_PHY_ADDR_SHIFT 8
#define ECM_MD_BUSY 0x00000001
#define ECM_MD_DATA_MASK 0xFFFF0000
#define ECM_MD_DATA_SHIFT 16
/* statistic */
#define ECM_STAT 0x00B0
#define ECM_STAT_RX_ERR_MASK 0x000000FF
#define ECM_STAT_RX_ERR_SHIFT 0
#define ECM_STAT_INV_FRM_MASK 0x0000FF00
#define ECM_STAT_INV_FRM_SHIFT 8
#define ECM_STAT_FWD_RX_ERR_MASK 0x00FF0000
#define ECM_STAT_FWD_RX_ERR_SHIFT 16
/* tsnep */
#define TSNEP_MAC_SIZE 0x4000
#define TSNEP_QUEUE_SIZE 0x1000
#define TSNEP_QUEUE(n) ({ typeof(n) __n = (n); \
(__n) == 0 ? \
0 : \
TSNEP_MAC_SIZE + TSNEP_QUEUE_SIZE * ((__n) - 1); })
#define TSNEP_MAX_QUEUES 8
#define TSNEP_MAX_FRAME_SIZE (2 * 1024) /* hardware supports actually 16k */
#define TSNEP_DESC_SIZE 256
#define TSNEP_DESC_OFFSET 128
/* tsnep register */
#define TSNEP_INFO 0x0100
#define TSNEP_INFO_RX_ASSIGN 0x00010000
#define TSNEP_INFO_TX_TIME 0x00020000
#define TSNEP_CONTROL 0x0108
#define TSNEP_CONTROL_TX_RESET 0x00000001
#define TSNEP_CONTROL_TX_ENABLE 0x00000002
#define TSNEP_CONTROL_TX_DMA_ERROR 0x00000010
#define TSNEP_CONTROL_TX_DESC_ERROR 0x00000020
#define TSNEP_CONTROL_RX_RESET 0x00000100
#define TSNEP_CONTROL_RX_ENABLE 0x00000200
#define TSNEP_CONTROL_RX_DISABLE 0x00000400
#define TSNEP_CONTROL_RX_DMA_ERROR 0x00001000
#define TSNEP_CONTROL_RX_DESC_ERROR 0x00002000
#define TSNEP_TX_DESC_ADDR_LOW 0x0140
#define TSNEP_TX_DESC_ADDR_HIGH 0x0144
#define TSNEP_RX_DESC_ADDR_LOW 0x0180
#define TSNEP_RX_DESC_ADDR_HIGH 0x0184
#define TSNEP_RESET_OWNER_COUNTER 0x01
#define TSNEP_RX_STATISTIC 0x0190
#define TSNEP_RX_STATISTIC_NO_DESC_MASK 0x000000FF
#define TSNEP_RX_STATISTIC_NO_DESC_SHIFT 0
#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_MASK 0x0000FF00
#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_SHIFT 8
#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW_MASK 0x00FF0000
#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW_SHIFT 16
#define TSNEP_RX_STATISTIC_INVALID_FRAME_MASK 0xFF000000
#define TSNEP_RX_STATISTIC_INVALID_FRAME_SHIFT 24
#define TSNEP_RX_STATISTIC_NO_DESC 0x0190
#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL 0x0191
#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW 0x0192
#define TSNEP_RX_STATISTIC_INVALID_FRAME 0x0193
#define TSNEP_RX_ASSIGN 0x01A0
#define TSNEP_RX_ASSIGN_ETHER_TYPE_ACTIVE 0x00000001
#define TSNEP_RX_ASSIGN_ETHER_TYPE_MASK 0xFFFF0000
#define TSNEP_RX_ASSIGN_ETHER_TYPE_SHIFT 16
#define TSNEP_MAC_ADDRESS_LOW 0x0800
#define TSNEP_MAC_ADDRESS_HIGH 0x0804
#define TSNEP_RX_FILTER 0x0806
#define TSNEP_RX_FILTER_ACCEPT_ALL_MULTICASTS 0x0001
#define TSNEP_RX_FILTER_ACCEPT_ALL_UNICASTS 0x0002
#define TSNEP_GC 0x0808
#define TSNEP_GC_ENABLE_A 0x00000002
#define TSNEP_GC_ENABLE_B 0x00000004
#define TSNEP_GC_DISABLE 0x00000008
#define TSNEP_GC_ENABLE_TIMEOUT 0x00000010
#define TSNEP_GC_ACTIVE_A 0x00000002
#define TSNEP_GC_ACTIVE_B 0x00000004
#define TSNEP_GC_CHANGE_AB 0x00000008
#define TSNEP_GC_TIMEOUT_ACTIVE 0x00000010
#define TSNEP_GC_TIMEOUT_SIGNAL 0x00000020
#define TSNEP_GC_LIST_ERROR 0x00000080
#define TSNEP_GC_OPEN 0x00FF0000
#define TSNEP_GC_OPEN_SHIFT 16
#define TSNEP_GC_NEXT_OPEN 0xFF000000
#define TSNEP_GC_NEXT_OPEN_SHIFT 24
#define TSNEP_GC_TIMEOUT 131072
#define TSNEP_GC_TIME 0x080C
#define TSNEP_GC_CHANGE 0x0810
#define TSNEP_GCL_A 0x2000
#define TSNEP_GCL_B 0x2800
#define TSNEP_GCL_SIZE SZ_2K
/* tsnep gate control list operation */
struct tsnep_gcl_operation {
u32 properties;
u32 interval;
};
#define TSNEP_GCL_COUNT (TSNEP_GCL_SIZE / sizeof(struct tsnep_gcl_operation))
#define TSNEP_GCL_MASK 0x000000FF
#define TSNEP_GCL_INSERT 0x20000000
#define TSNEP_GCL_CHANGE 0x40000000
#define TSNEP_GCL_LAST 0x80000000
#define TSNEP_GCL_MIN_INTERVAL 32
/* tsnep TX/RX descriptor */
#define TSNEP_DESC_SIZE 256
#define TSNEP_DESC_SIZE_DATA_AFTER 2048
#define TSNEP_DESC_OFFSET 128
#define TSNEP_DESC_OWNER_COUNTER_MASK 0xC0000000
#define TSNEP_DESC_OWNER_COUNTER_SHIFT 30
#define TSNEP_DESC_LENGTH_MASK 0x00003FFF
#define TSNEP_DESC_INTERRUPT_FLAG 0x00040000
#define TSNEP_DESC_EXTENDED_WRITEBACK_FLAG 0x00080000
#define TSNEP_DESC_NO_LINK_FLAG 0x01000000
/* tsnep TX descriptor */
struct tsnep_tx_desc {
__le32 properties;
__le32 more_properties;
__le32 reserved[2];
__le64 next;
__le64 tx;
};
#define TSNEP_TX_DESC_OWNER_MASK 0xE0000000
#define TSNEP_TX_DESC_OWNER_USER_FLAG 0x20000000
#define TSNEP_TX_DESC_LAST_FRAGMENT_FLAG 0x00010000
#define TSNEP_TX_DESC_DATA_AFTER_DESC_FLAG 0x00020000
/* tsnep TX descriptor writeback */
struct tsnep_tx_desc_wb {
__le32 properties;
__le32 reserved1[3];
__le64 timestamp;
__le32 dma_delay;
__le32 reserved2;
};
#define TSNEP_TX_DESC_UNDERRUN_ERROR_FLAG 0x00010000
#define TSNEP_TX_DESC_DMA_DELAY_FIRST_DATA_MASK 0x0000FFFC
#define TSNEP_TX_DESC_DMA_DELAY_FIRST_DATA_SHIFT 2
#define TSNEP_TX_DESC_DMA_DELAY_LAST_DATA_MASK 0xFFFC0000
#define TSNEP_TX_DESC_DMA_DELAY_LAST_DATA_SHIFT 18
#define TSNEP_TX_DESC_DMA_DELAY_NS 64
/* tsnep RX descriptor */
struct tsnep_rx_desc {
__le32 properties;
__le32 reserved[3];
__le64 next;
__le64 rx;
};
#define TSNEP_RX_DESC_BUFFER_SIZE_MASK 0x00003FFC
/* tsnep RX descriptor writeback */
struct tsnep_rx_desc_wb {
__le32 properties;
__le32 reserved[7];
};
/* tsnep RX inline meta */
struct tsnep_rx_inline {
__le64 reserved;
__le64 timestamp;
};
#define TSNEP_RX_INLINE_METADATA_SIZE (sizeof(struct tsnep_rx_inline))
#endif /* _TSNEP_HW_H */
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
#include "tsnep.h"
void tsnep_get_system_time(struct tsnep_adapter *adapter, u64 *time)
{
u32 high_before;
u32 low;
u32 high;
/* read high dword twice to detect overrun */
high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
do {
low = ioread32(adapter->addr + ECM_SYSTEM_TIME_LOW);
high_before = high;
high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
} while (high != high_before);
*time = (((u64)high) << 32) | ((u64)low);
}
int tsnep_ptp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
{
struct tsnep_adapter *adapter = netdev_priv(netdev);
struct hwtstamp_config config;
if (!ifr)
return -EINVAL;
if (cmd == SIOCSHWTSTAMP) {
if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
return -EFAULT;
if (config.flags)
return -EINVAL;
switch (config.tx_type) {
case HWTSTAMP_TX_OFF:
case HWTSTAMP_TX_ON:
break;
default:
return -ERANGE;
}
switch (config.rx_filter) {
case HWTSTAMP_FILTER_NONE:
break;
case HWTSTAMP_FILTER_ALL:
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
case HWTSTAMP_FILTER_NTP_ALL:
config.rx_filter = HWTSTAMP_FILTER_ALL;
break;
default:
return -ERANGE;
}
memcpy(&adapter->hwtstamp_config, &config,
sizeof(adapter->hwtstamp_config));
}
if (copy_to_user(ifr->ifr_data, &adapter->hwtstamp_config,
sizeof(adapter->hwtstamp_config)))
return -EFAULT;
return 0;
}
static int tsnep_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
ptp_clock_info);
bool negative = false;
u64 rate_offset;
if (scaled_ppm < 0) {
scaled_ppm = -scaled_ppm;
negative = true;
}
/* convert from 16 bit to 32 bit binary fractional, divide by 1000000 to
* eliminate ppm, multiply with 8 to compensate 8ns clock cycle time,
* simplify calculation because 15625 * 8 = 1000000 / 8
*/
rate_offset = scaled_ppm;
rate_offset <<= 16 - 3;
rate_offset = div_u64(rate_offset, 15625);
rate_offset &= ECM_CLOCK_RATE_OFFSET_MASK;
if (negative)
rate_offset |= ECM_CLOCK_RATE_OFFSET_SIGN;
iowrite32(rate_offset & 0xFFFFFFFF, adapter->addr + ECM_CLOCK_RATE);
return 0;
}
static int tsnep_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
ptp_clock_info);
u64 system_time;
unsigned long flags;
spin_lock_irqsave(&adapter->ptp_lock, flags);
tsnep_get_system_time(adapter, &system_time);
system_time += delta;
/* high dword is buffered in hardware and synchronously written to
* system time when low dword is written
*/
iowrite32(system_time >> 32, adapter->addr + ECM_SYSTEM_TIME_HIGH);
iowrite32(system_time & 0xFFFFFFFF,
adapter->addr + ECM_SYSTEM_TIME_LOW);
spin_unlock_irqrestore(&adapter->ptp_lock, flags);
return 0;
}
static int tsnep_ptp_gettimex64(struct ptp_clock_info *ptp,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
ptp_clock_info);
u32 high_before;
u32 low;
u32 high;
u64 system_time;
/* read high dword twice to detect overrun */
high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
do {
ptp_read_system_prets(sts);
low = ioread32(adapter->addr + ECM_SYSTEM_TIME_LOW);
ptp_read_system_postts(sts);
high_before = high;
high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
} while (high != high_before);
system_time = (((u64)high) << 32) | ((u64)low);
*ts = ns_to_timespec64(system_time);
return 0;
}
static int tsnep_ptp_settime64(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
ptp_clock_info);
u64 system_time = timespec64_to_ns(ts);
unsigned long flags;
spin_lock_irqsave(&adapter->ptp_lock, flags);
/* high dword is buffered in hardware and synchronously written to
* system time when low dword is written
*/
iowrite32(system_time >> 32, adapter->addr + ECM_SYSTEM_TIME_HIGH);
iowrite32(system_time & 0xFFFFFFFF,
adapter->addr + ECM_SYSTEM_TIME_LOW);
spin_unlock_irqrestore(&adapter->ptp_lock, flags);
return 0;
}
int tsnep_ptp_init(struct tsnep_adapter *adapter)
{
int retval = 0;
adapter->hwtstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
adapter->hwtstamp_config.tx_type = HWTSTAMP_TX_OFF;
snprintf(adapter->ptp_clock_info.name, 16, "%s", TSNEP);
adapter->ptp_clock_info.owner = THIS_MODULE;
/* at most 2^-1ns adjustment every clock cycle for 8ns clock cycle time,
* stay slightly below because only bits below 2^-1ns are supported
*/
adapter->ptp_clock_info.max_adj = (500000000 / 8 - 1);
adapter->ptp_clock_info.adjfine = tsnep_ptp_adjfine;
adapter->ptp_clock_info.adjtime = tsnep_ptp_adjtime;
adapter->ptp_clock_info.gettimex64 = tsnep_ptp_gettimex64;
adapter->ptp_clock_info.settime64 = tsnep_ptp_settime64;
spin_lock_init(&adapter->ptp_lock);
adapter->ptp_clock = ptp_clock_register(&adapter->ptp_clock_info,
&adapter->pdev->dev);
if (IS_ERR(adapter->ptp_clock)) {
netdev_err(adapter->netdev, "ptp_clock_register failed\n");
retval = PTR_ERR(adapter->ptp_clock);
adapter->ptp_clock = NULL;
} else if (adapter->ptp_clock) {
netdev_info(adapter->netdev, "PHC added\n");
}
return retval;
}
void tsnep_ptp_cleanup(struct tsnep_adapter *adapter)
{
if (adapter->ptp_clock) {
ptp_clock_unregister(adapter->ptp_clock);
netdev_info(adapter->netdev, "PHC removed\n");
}
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment