@@ -227,6 +227,18 @@ config TI_ICSS_IEP
To compile this driver as a module, choose M here. The module
will be called icss_iep.
+config TI_PRUETH_ECAP
+ tristate "TI PRUETH ECAP driver"
+ depends on TI_PRUSS
+ default TI_PRUSS
+ help
+ This enables support for the PRU-ICSS Enhanced Capture (eCAP) driver
+ used for rx interrupt pacing support in PRU Driver/firmwares
+ (Dual EMAC, HSR, PRP).
+
+ To compile this driver as a module, choose M here. The module
+ will be called prueth_ecap.
+
config TI_PRUETH
tristate "TI PRU Ethernet EMAC driver"
depends on PRU_REMOTEPROC
@@ -49,3 +49,5 @@ icssg-y := icssg/icssg_common.o \
icssg/icssg_ethtool.o
obj-$(CONFIG_TI_ICSS_IEP) += icssg/icss_iep.o
+
+obj-$(CONFIG_TI_PRUETH_ECAP) += icssm/icssm_prueth_ecap.o
@@ -7,6 +7,7 @@
#include <linux/if_bridge.h>
#include "icssm_prueth.h"
+#include "icssm_prueth_ecap.h"
#include "icssm_vlan_mcast_filter_mmap.h"
#include "../icssg/icss_iep.h"
@@ -252,6 +253,40 @@ static int icssm_emac_get_ts_info(struct net_device *ndev,
return 0;
}
+static int icssm_emac_get_coalesce(struct net_device *ndev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth *prueth = emac->prueth;
+ struct prueth_ecap *ecap;
+
+ ecap = prueth->ecap;
+ if (IS_ERR(ecap))
+ return -EOPNOTSUPP;
+
+ return ecap->get_coalesce(emac, &coal->use_adaptive_rx_coalesce,
+ &coal->rx_coalesce_usecs);
+}
+
+static int icssm_emac_set_coalesce(struct net_device *ndev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth *prueth = emac->prueth;
+ struct prueth_ecap *ecap;
+
+ ecap = prueth->ecap;
+ if (IS_ERR(ecap))
+ return -EOPNOTSUPP;
+
+ return ecap->set_coalesce(emac, coal->use_adaptive_rx_coalesce,
+ coal->rx_coalesce_usecs);
+}
+
/* Ethtool support for EMAC adapter */
const struct ethtool_ops emac_ethtool_ops = {
.get_drvinfo = icssm_emac_get_drvinfo,
@@ -264,5 +299,8 @@ const struct ethtool_ops emac_ethtool_ops = {
.get_ethtool_stats = icssm_emac_get_ethtool_stats,
.get_regs = icssm_emac_get_regs,
.get_regs_len = icssm_emac_get_regs_len,
+ .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS,
+ .get_coalesce = icssm_emac_get_coalesce,
+ .set_coalesce = icssm_emac_set_coalesce,
};
EXPORT_SYMBOL_GPL(emac_ethtool_ops);
@@ -30,6 +30,7 @@
#include "icssm_prueth.h"
#include "icssm_vlan_mcast_filter_mmap.h"
+#include "icssm_prueth_ecap.h"
#include "../icssg/icssg_mii_rt.h"
#include "../icssg/icss_iep.h"
@@ -1182,8 +1183,10 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
+ struct prueth_ecap *ecap;
int ret;
+ ecap = prueth->ecap;
/* set h/w MAC as user might have re-configured */
ether_addr_copy(emac->mac_addr, ndev->dev_addr);
@@ -1193,6 +1196,9 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
icssm_prueth_emac_config(emac);
icssm_emac_set_stats(emac, &emac->stats);
+ /* initialize ecap for interrupt pacing */
+ if (!IS_ERR(ecap))
+ ecap->init(emac);
if (!prueth->emac_configured) {
icssm_iptp_dram_init(emac);
@@ -2177,12 +2183,25 @@ static int icssm_prueth_probe(struct platform_device *pdev)
goto netdev_exit;
}
+ /* Make rx interrupt pacing optional so that users can use ECAP for
+ * other use cases if needed
+ */
+ prueth->ecap = icssm_prueth_ecap_get(np);
+ if (IS_ERR(prueth->ecap)) {
+ ret = PTR_ERR(prueth->ecap);
+ if (ret != -EPROBE_DEFER)
+ dev_info(dev,
+ "No ECAP. Rx interrupt pacing disabled\n");
+ else
+ goto iep_put;
+ }
+
/* register the network devices */
if (eth0_node) {
ret = register_netdev(prueth->emac[PRUETH_MAC0]->ndev);
if (ret) {
dev_err(dev, "can't register netdev for port MII0");
- goto iep_put;
+ goto ecap_put;
}
prueth->registered_netdevs[PRUETH_MAC0] =
@@ -2218,6 +2237,10 @@ static int icssm_prueth_probe(struct platform_device *pdev)
unregister_netdev(prueth->registered_netdevs[i]);
}
+ecap_put:
+ if (!IS_ERR(prueth->ecap))
+ icssm_prueth_ecap_put(prueth->ecap);
+
iep_put:
icss_iep_put(prueth->iep);
@@ -12,6 +12,7 @@
#include <linux/types.h>
#include <linux/pruss_driver.h>
#include <linux/remoteproc/pruss.h>
+#include <linux/netdevice.h>
#include "icssm_switch.h"
#include "icssm_prueth_ptp.h"
@@ -384,6 +385,7 @@ struct prueth {
struct regmap *mii_rt;
struct icss_iep *iep;
+ struct prueth_ecap *ecap;
const struct prueth_private_data *fw_data;
struct prueth_fw_offsets *fw_offsets;
@@ -392,6 +394,7 @@ struct prueth {
struct net_device *registered_netdevs[PRUETH_NUM_MACS];
unsigned int eth_type;
+ struct mutex mlock; /* serialize access */
u8 emac_configured;
u8 base_mac[ETH_ALEN];
};
new file mode 100644
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* PRUETH Ecap driver for Interrupt pacing support. eCAP is used by
+ * firmware to implement Rx Interrupt pacing for PRUETH driver using
+ * ECAP1 and ECAP2. Firmware uses ECAP as a timer to implement
+ * interrupt pacing logic. For HSR/PRP, the interrupt pacing can
+ * be enabled/disabled for both ports together as there is a common
+ * control for both ports, where as for Dual EMAC, interrupt pacing
+ * can be enabled or disabled independently for both Ethernet ports.
+ * SRAM memory location stores the configuration for interrupt pacing
+ * such as enable/disable flag and timeout values.
+ *
+ * TODO: This is marked as a HACK driver since the correct solution
+ * is to move the initialization of the ECAP registers to firmware.
+ * Driver has nothing to do ECAP as it is used by firmware and it
+ * is expected that firmware does the initialization.
+ *
+ * Copyright (C) 2018-2020 Texas Instruments Incorporated - https://www.ti.com
+ * Murali Karicheri <m-karicheri2@ti.com>
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include "icssm_switch.h"
+#include "icssm_prueth_ecap.h"
+
+/* ECAP registers */
+#define ECAP_CAP1 8
+#define ECAP_CAP2 0xC
+#define ECAP_ECCTL2 0x2A
+
+#define ECAP_ECCTL2_TSCTRSTOP_MASK BIT(4)
+#define ECAP_ECCTL2_CAP_APWM_MASK BIT(9)
+
+#define ECAP_ECCTL2_INIT_VAL (ECAP_ECCTL2_TSCTRSTOP_MASK | \
+ ECAP_ECCTL2_CAP_APWM_MASK)
+#define ECAP_CAP2_MAX_COUNT 0xFFFFFFFF
+
+/* TODO: Driver assumes that ECAP runs at 200Mhz clock. But on some
+ * platforms, PRU ICSS clock rate may be changed by user in which case
+ * the pacing logic will not work as expected. Update the driver and
+ * firmware if ECAP/PRUSS clock rate is ever changed. Based on this
+ * assumption each tick is 5 nsec. i.e 1000/200
+ */
+#define ECAP_TICK_NSEC 5
+
+/* in usec */
+/* Duration of 3 frames of 1528 bytes each. If we go beyond this,
+ * receive buffer overflow may happen assuming 4 MTU buffer. So
+ * set this as the limit
+ */
+#define MAX_RX_TIMEOUT_USEC (123 * 3)
+
+/* Dual EMAC defaults */
+static struct rx_int_pacing_offsets pacing_offsets_defaults[PRUETH_NUM_MACS] = {
+ { INTR_PAC_STATUS_OFFSET_PRU0, INTR_PAC_TMR_EXP_OFFSET_PRU0,
+ INTR_PAC_PREV_TS_OFFSET_PRU0 },
+ { INTR_PAC_STATUS_OFFSET_PRU1, INTR_PAC_TMR_EXP_OFFSET_PRU1,
+ INTR_PAC_PREV_TS_OFFSET_PRU1 },
+};
+
+static int icssm_prueth_ecap_config_pacing(struct prueth_emac *emac,
+ u32 use_adaptive,
+ u32 new_timeout_val)
+{
+ struct rx_int_pacing_offsets *offsets;
+ struct prueth *prueth = emac->prueth;
+ u8 val = INTR_PAC_DIS_ADP_LGC_DIS;
+ struct prueth_ecap *ecap;
+ void __iomem *sram;
+ u32 pacing_ctrl;
+ int port;
+
+ ecap = prueth->ecap;
+ sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+ port = (emac->port_id == PRUETH_PORT_MII0) ?
+ PRUETH_MAC0 : PRUETH_MAC1;
+ offsets = &ecap->int_pacing_offsets[port];
+ pacing_ctrl = offsets->rx_int_pacing_ctrl;
+
+ if (!new_timeout_val) {
+ /* disable pacing */
+ writeb_relaxed(val, sram + pacing_ctrl);
+ /* Timeout separate */
+ ecap->timeout[port] = new_timeout_val;
+ return 0;
+ }
+
+ if (use_adaptive)
+ val = INTR_PAC_ENA_ADP_LGC_ENA;
+ else
+ val = INTR_PAC_ENA_ADP_LGC_DIS;
+
+ if (!ecap->timeout[port]) {
+ /* disable to enable transition */
+ writeb_relaxed(INTR_PAC_DIS_ADP_LGC_DIS, sram + pacing_ctrl);
+ /* For EMAC set timeout for specific port and for
+ * LRE for both ports
+ */
+ if (PRUETH_IS_EMAC(prueth)) {
+ if (!port) {
+ offsets =
+ &ecap->int_pacing_offsets[PRUETH_MAC0];
+ writel_relaxed(new_timeout_val *
+ NSEC_PER_USEC / ECAP_TICK_NSEC,
+ sram +
+ offsets->rx_int_pacing_exp);
+ writel_relaxed(INTR_PAC_PREV_TS_RESET_VAL,
+ sram +
+ offsets->rx_int_pacing_prev);
+ ecap->timeout[PRUETH_MAC0] = new_timeout_val;
+ } else {
+ offsets =
+ &ecap->int_pacing_offsets[PRUETH_MAC1];
+ writel_relaxed(new_timeout_val *
+ NSEC_PER_USEC / ECAP_TICK_NSEC,
+ sram +
+ offsets->rx_int_pacing_exp);
+ writel_relaxed(INTR_PAC_PREV_TS_RESET_VAL,
+ sram +
+ offsets->rx_int_pacing_prev);
+ ecap->timeout[PRUETH_MAC1] = new_timeout_val;
+ }
+ }
+ } else {
+ /* update */
+ if (PRUETH_IS_EMAC(prueth)) {
+ if (!port) {
+ offsets =
+ &ecap->int_pacing_offsets[PRUETH_MAC0];
+ writel_relaxed(new_timeout_val *
+ NSEC_PER_USEC / ECAP_TICK_NSEC,
+ sram +
+ offsets->rx_int_pacing_exp);
+ ecap->timeout[PRUETH_MAC0] = new_timeout_val;
+ } else {
+ offsets =
+ &ecap->int_pacing_offsets[PRUETH_MAC1];
+ writel_relaxed(new_timeout_val *
+ NSEC_PER_USEC / ECAP_TICK_NSEC,
+ sram +
+ offsets->rx_int_pacing_exp);
+ ecap->timeout[PRUETH_MAC1] = new_timeout_val;
+ }
+ }
+ }
+
+ writeb_relaxed(val, sram + pacing_ctrl);
+
+ return 0;
+}
+
+/**
+ * icssm_prueth_ecap_init - ecap driver init
+ *
+ * @emac: EMAC data structure
+ *
+ */
+static void icssm_prueth_ecap_init(struct prueth_emac *emac)
+{
+ struct prueth *prueth = emac->prueth;
+
+ if (!prueth->emac_configured || PRUETH_IS_EMAC(prueth))
+ icssm_prueth_ecap_config_pacing(emac, 0, 0);
+}
+
+static int icssm_prueth_ecap_get_coalesce(struct prueth_emac *emac,
+ u32 *use_adaptive_rx_coalesce,
+ u32 *rx_coalesce_usecs)
+{
+ struct rx_int_pacing_offsets *pacing_offsets;
+ struct prueth *prueth = emac->prueth;
+ struct prueth_ecap *ecap;
+ void __iomem *sram;
+ int port;
+ u32 val;
+
+ ecap = prueth->ecap;
+ sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+ port = (emac->port_id == PRUETH_PORT_MII0) ?
+ PRUETH_MAC0 : PRUETH_MAC1;
+ pacing_offsets = &ecap->int_pacing_offsets[port];
+ val = readb_relaxed(sram + pacing_offsets->rx_int_pacing_ctrl);
+ *use_adaptive_rx_coalesce = (val == INTR_PAC_ENA_ADP_LGC_ENA);
+ *rx_coalesce_usecs = ecap->timeout[port];
+
+ return 0;
+}
+
+static int icssm_prueth_ecap_set_coalesce(struct prueth_emac *emac,
+ u32 use_adaptive_rx_coalesce,
+ u32 rx_coalesce_usecs)
+{
+ struct prueth *prueth = emac->prueth;
+ int ret;
+
+ if (rx_coalesce_usecs > MAX_RX_TIMEOUT_USEC)
+ return -EINVAL;
+
+ mutex_lock(&prueth->mlock);
+ /* Start or restart the pacing timer. */
+ ret = icssm_prueth_ecap_config_pacing(emac, use_adaptive_rx_coalesce,
+ rx_coalesce_usecs);
+ mutex_unlock(&prueth->mlock);
+
+ return ret;
+}
+
+void icssm_prueth_ecap_put(struct prueth_ecap *ecap)
+{
+ device_lock(ecap->dev);
+ ecap->client_np = NULL;
+ device_unlock(ecap->dev);
+ put_device(ecap->dev);
+}
+EXPORT_SYMBOL_GPL(icssm_prueth_ecap_put);
+
+struct prueth_ecap *icssm_prueth_ecap_get(struct device_node *np)
+{
+ struct platform_device *pdev;
+ struct device_node *ecap_np;
+ struct prueth_ecap *ecap;
+
+ ecap_np = of_parse_phandle(np, "ecap", 0);
+ if (!ecap_np || !of_device_is_available(ecap_np))
+ return ERR_PTR(-ENODEV);
+
+ pdev = of_find_device_by_node(ecap_np);
+ of_node_put(ecap_np);
+
+ if (!pdev)
+ /* probably IEP not yet probed */
+ return ERR_PTR(-EPROBE_DEFER);
+
+ ecap = platform_get_drvdata(pdev);
+ if (!ecap)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ device_lock(ecap->dev);
+ if (ecap->client_np) {
+ device_unlock(ecap->dev);
+ dev_err(ecap->dev, "ECAP is already acquired by %s",
+ ecap->client_np->name);
+ return ERR_PTR(-EBUSY);
+ }
+ ecap->client_np = np;
+ device_unlock(ecap->dev);
+ get_device(ecap->dev);
+
+ return ecap;
+}
+EXPORT_SYMBOL_GPL(icssm_prueth_ecap_get);
+
+static int icssm_prueth_ecap_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct prueth_ecap *ecap;
+ struct resource *res;
+ int i;
+
+ ecap = devm_kzalloc(dev, sizeof(*ecap), GFP_KERNEL);
+ if (!ecap)
+ return -ENOMEM;
+
+ ecap->dev = dev;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ecap->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(ecap->base))
+ return -ENODEV;
+
+ /* Initialize the ECAP timer. It is a common timer used
+ * by firmware for rx interrupt pacing.
+ */
+ writew_relaxed(ECAP_ECCTL2_INIT_VAL, ecap->base + ECAP_ECCTL2);
+ writel_relaxed(ECAP_CAP2_MAX_COUNT, ecap->base + ECAP_CAP1);
+ writel_relaxed(ECAP_CAP2_MAX_COUNT, ecap->base + ECAP_CAP2);
+
+ /* initialize SRAM memory offsets for rx pace time control */
+ for (i = 0; i < PRUETH_NUM_MACS; i++)
+ ecap->int_pacing_offsets[i] = pacing_offsets_defaults[i];
+ ecap->get_coalesce = icssm_prueth_ecap_get_coalesce;
+ ecap->set_coalesce = icssm_prueth_ecap_set_coalesce;
+ ecap->init = icssm_prueth_ecap_init;
+
+ dev_set_drvdata(dev, ecap);
+
+ return 0;
+}
+
+static const struct of_device_id prueth_ecap_of_match[] = {
+ { .compatible = "ti,pruss-ecap", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, prueth_ecap_of_match);
+
+static struct platform_driver prueth_ecap_driver = {
+ .driver = {
+ .name = "prueth-ecap",
+ .of_match_table = prueth_ecap_of_match,
+ },
+ .probe = icssm_prueth_ecap_probe,
+};
+module_platform_driver(prueth_ecap_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TI PRUETH ECAP driver for Rx Interrupt pacing");
+MODULE_AUTHOR("Murali Karicheri <m-karicheri2@ti.com>");
new file mode 100644
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Texas Instruments ICSS Enhanced Capture (eCAP) Driver
+ *
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ */
+#ifndef __NET_TI_PRUETH_ECAP_H
+#define __NET_TI_PRUETH_ECAP_H
+
+#include "icssm_prueth.h"
+
+/* SRAM offsets for firmware pacing timer configuration */
+struct rx_int_pacing_offsets {
+ u32 rx_int_pacing_ctrl;
+ u32 rx_int_pacing_exp;
+ u32 rx_int_pacing_prev;
+};
+
+struct prueth_ecap {
+ struct device *dev;
+ void __iomem *base;
+ struct device_node *client_np;
+ struct rx_int_pacing_offsets int_pacing_offsets[PRUETH_NUM_MACS];
+ u32 timeout[PRUETH_NUM_MACS];
+ void (*init)(struct prueth_emac *emac);
+ int (*get_coalesce)(struct prueth_emac *emac,
+ u32 *use_adaptive_rx_coalesce,
+ u32 *rx_coalesce_usecs);
+ int (*set_coalesce)(struct prueth_emac *emac,
+ u32 use_adaptive_rx_coalesce,
+ u32 rx_coalesce_usecs);
+};
+
+#if IS_ENABLED(CONFIG_TI_PRUETH_ECAP)
+struct prueth_ecap *icssm_prueth_ecap_get(struct device_node *np);
+void icssm_prueth_ecap_put(struct prueth_ecap *ecap);
+#else
+static inline struct prueth_ecap *icssm_prueth_ecap_get(struct device_node *np)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline void icssm_prueth_ecap_put(struct prueth_ecap *ecap)
+{};
+#endif
+
+#endif /* __NET_TI_PRUETH_ECAP_H */
@@ -259,4 +259,27 @@
#define P0_COL_BUFFER_OFFSET 0xEE00
#define P0_Q1_BUFFER_OFFSET 0x0000
+/* Below Rx Interrupt pacing defines. */
+/* shared RAM */
+/* 1 byte for pace control */
+#define INTR_PAC_STATUS_OFFSET 0x1FAF
+#define INTR_PAC_STATUS_OFFSET_PRU1 0x1FAE
+#define INTR_PAC_STATUS_OFFSET_PRU0 0x1FAF
+/* Interrupt Pacing disabled, Adaptive logic disabled */
+#define INTR_PAC_DIS_ADP_LGC_DIS 0x0
+/* Interrupt Pacing enabled, Adaptive logic disabled */
+#define INTR_PAC_ENA_ADP_LGC_DIS 0x1
+/* Interrupt Pacing enabled, Adaptive logic enabled */
+#define INTR_PAC_ENA_ADP_LGC_ENA 0x2
+
+/* 4 bytes | previous TS from eCAP TSCNT for PRU 0 */
+#define INTR_PAC_PREV_TS_OFFSET_PRU0 0x1FB0
+/* 4 bytes | timer expiration value for PRU 0 */
+#define INTR_PAC_TMR_EXP_OFFSET_PRU0 0x1FB4
+/* 4 bytes | previous TS from eCAP TSCNT for PRU 1 */
+#define INTR_PAC_PREV_TS_OFFSET_PRU1 0x1FB8
+/* 4 bytes | timer expiration value for PRU 1 */
+#define INTR_PAC_TMR_EXP_OFFSET_PRU1 0x1FBC
+#define INTR_PAC_PREV_TS_RESET_VAL 0x0
+
#endif /* __ICSS_SWITCH_H */