summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/ti/icssg/icssg_config.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/ti/icssg/icssg_config.c')
-rw-r--r--drivers/net/ethernet/ti/icssg/icssg_config.c457
1 files changed, 457 insertions, 0 deletions
diff --git a/drivers/net/ethernet/ti/icssg/icssg_config.c b/drivers/net/ethernet/ti/icssg/icssg_config.c
new file mode 100644
index 000000000000..933b84666574
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssg/icssg_config.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0
+/* ICSSG Ethernet driver
+ *
+ * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com
+ */
+
+#include <linux/iopoll.h>
+#include <linux/regmap.h>
+#include <uapi/linux/if_ether.h>
+#include "icssg_config.h"
+#include "icssg_prueth.h"
+#include "icssg_switch_map.h"
+#include "icssg_mii_rt.h"
+
+/* TX IPG Values to be set for 100M link speed. These values are
+ * in ocp_clk cycles. So need change if ocp_clk is changed for a specific
+ * h/w design.
+ */
+
+/* IPG is in core_clk cycles */
+#define MII_RT_TX_IPG_100M 0x17
+#define MII_RT_TX_IPG_1G 0xb
+
+#define ICSSG_QUEUES_MAX 64
+#define ICSSG_QUEUE_OFFSET 0xd00
+#define ICSSG_QUEUE_PEEK_OFFSET 0xe00
+#define ICSSG_QUEUE_CNT_OFFSET 0xe40
+#define ICSSG_QUEUE_RESET_OFFSET 0xf40
+
+#define ICSSG_NUM_TX_QUEUES 8
+
+#define RECYCLE_Q_SLICE0 16
+#define RECYCLE_Q_SLICE1 17
+
+#define ICSSG_NUM_OTHER_QUEUES 5 /* port, host and special queues */
+
+#define PORT_HI_Q_SLICE0 32
+#define PORT_LO_Q_SLICE0 33
+#define HOST_HI_Q_SLICE0 34
+#define HOST_LO_Q_SLICE0 35
+#define HOST_SPL_Q_SLICE0 40 /* Special Queue */
+
+#define PORT_HI_Q_SLICE1 36
+#define PORT_LO_Q_SLICE1 37
+#define HOST_HI_Q_SLICE1 38
+#define HOST_LO_Q_SLICE1 39
+#define HOST_SPL_Q_SLICE1 41 /* Special Queue */
+
+#define MII_RXCFG_DEFAULT (PRUSS_MII_RT_RXCFG_RX_ENABLE | \
+ PRUSS_MII_RT_RXCFG_RX_DATA_RDY_MODE_DIS | \
+ PRUSS_MII_RT_RXCFG_RX_L2_EN | \
+ PRUSS_MII_RT_RXCFG_RX_L2_EOF_SCLR_DIS)
+
+#define MII_TXCFG_DEFAULT (PRUSS_MII_RT_TXCFG_TX_ENABLE | \
+ PRUSS_MII_RT_TXCFG_TX_AUTO_PREAMBLE | \
+ PRUSS_MII_RT_TXCFG_TX_32_MODE_EN | \
+ PRUSS_MII_RT_TXCFG_TX_IPG_WIRE_CLK_EN)
+
+#define ICSSG_CFG_DEFAULT (ICSSG_CFG_TX_L1_EN | \
+ ICSSG_CFG_TX_L2_EN | ICSSG_CFG_RX_L2_G_EN | \
+ ICSSG_CFG_TX_PRU_EN | \
+ ICSSG_CFG_SGMII_MODE)
+
+#define FDB_GEN_CFG1 0x60
+#define SMEM_VLAN_OFFSET 8
+#define SMEM_VLAN_OFFSET_MASK GENMASK(25, 8)
+
+#define FDB_GEN_CFG2 0x64
+#define FDB_VLAN_EN BIT(6)
+#define FDB_HOST_EN BIT(2)
+#define FDB_PRU1_EN BIT(1)
+#define FDB_PRU0_EN BIT(0)
+#define FDB_EN_ALL (FDB_PRU0_EN | FDB_PRU1_EN | \
+ FDB_HOST_EN | FDB_VLAN_EN)
+
+/**
+ * struct map - ICSSG Queue Map
+ * @queue: Queue number
+ * @pd_addr_start: Packet descriptor queue reserved memory
+ * @flags: Flags
+ * @special: Indicates whether this queue is a special queue or not
+ */
+struct map {
+ int queue;
+ u32 pd_addr_start;
+ u32 flags;
+ bool special;
+};
+
+/* Hardware queue map for ICSSG */
+static const struct map hwq_map[2][ICSSG_NUM_OTHER_QUEUES] = {
+ {
+ { PORT_HI_Q_SLICE0, PORT_DESC0_HI, 0x200000, 0 },
+ { PORT_LO_Q_SLICE0, PORT_DESC0_LO, 0, 0 },
+ { HOST_HI_Q_SLICE0, HOST_DESC0_HI, 0x200000, 0 },
+ { HOST_LO_Q_SLICE0, HOST_DESC0_LO, 0, 0 },
+ { HOST_SPL_Q_SLICE0, HOST_SPPD0, 0x400000, 1 },
+ },
+ {
+ { PORT_HI_Q_SLICE1, PORT_DESC1_HI, 0xa00000, 0 },
+ { PORT_LO_Q_SLICE1, PORT_DESC1_LO, 0x800000, 0 },
+ { HOST_HI_Q_SLICE1, HOST_DESC1_HI, 0xa00000, 0 },
+ { HOST_LO_Q_SLICE1, HOST_DESC1_LO, 0x800000, 0 },
+ { HOST_SPL_Q_SLICE1, HOST_SPPD1, 0xc00000, 1 },
+ },
+};
+
+static void icssg_config_mii_init(struct prueth_emac *emac)
+{
+ u32 rxcfg, txcfg, rxcfg_reg, txcfg_reg, pcnt_reg;
+ struct prueth *prueth = emac->prueth;
+ int slice = prueth_emac_slice(emac);
+ struct regmap *mii_rt;
+
+ mii_rt = prueth->mii_rt;
+
+ rxcfg_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_RXCFG0 :
+ PRUSS_MII_RT_RXCFG1;
+ txcfg_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_TXCFG0 :
+ PRUSS_MII_RT_TXCFG1;
+ pcnt_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_RX_PCNT0 :
+ PRUSS_MII_RT_RX_PCNT1;
+
+ rxcfg = MII_RXCFG_DEFAULT;
+ txcfg = MII_TXCFG_DEFAULT;
+
+ if (slice == ICSS_MII1)
+ rxcfg |= PRUSS_MII_RT_RXCFG_RX_MUX_SEL;
+
+ /* In MII mode TX lines swapped inside ICSSG, so TX_MUX_SEL cfg need
+ * to be swapped also comparing to RGMII mode.
+ */
+ if (emac->phy_if == PHY_INTERFACE_MODE_MII && slice == ICSS_MII0)
+ txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL;
+ else if (emac->phy_if != PHY_INTERFACE_MODE_MII && slice == ICSS_MII1)
+ txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL;
+
+ regmap_write(mii_rt, rxcfg_reg, rxcfg);
+ regmap_write(mii_rt, txcfg_reg, txcfg);
+ regmap_write(mii_rt, pcnt_reg, 0x1);
+}
+
+static void icssg_miig_queues_init(struct prueth *prueth, int slice)
+{
+ struct regmap *miig_rt = prueth->miig_rt;
+ void __iomem *smem = prueth->shram.va;
+ u8 pd[ICSSG_SPECIAL_PD_SIZE];
+ int queue = 0, i, j;
+ u32 *pdword;
+
+ /* reset hwqueues */
+ if (slice)
+ queue = ICSSG_NUM_TX_QUEUES;
+
+ for (i = 0; i < ICSSG_NUM_TX_QUEUES; i++) {
+ regmap_write(miig_rt, ICSSG_QUEUE_RESET_OFFSET, queue);
+ queue++;
+ }
+
+ queue = slice ? RECYCLE_Q_SLICE1 : RECYCLE_Q_SLICE0;
+ regmap_write(miig_rt, ICSSG_QUEUE_RESET_OFFSET, queue);
+
+ for (i = 0; i < ICSSG_NUM_OTHER_QUEUES; i++) {
+ regmap_write(miig_rt, ICSSG_QUEUE_RESET_OFFSET,
+ hwq_map[slice][i].queue);
+ }
+
+ /* initialize packet descriptors in SMEM */
+ /* push pakcet descriptors to hwqueues */
+
+ pdword = (u32 *)pd;
+ for (j = 0; j < ICSSG_NUM_OTHER_QUEUES; j++) {
+ const struct map *mp;
+ int pd_size, num_pds;
+ u32 pdaddr;
+
+ mp = &hwq_map[slice][j];
+ if (mp->special) {
+ pd_size = ICSSG_SPECIAL_PD_SIZE;
+ num_pds = ICSSG_NUM_SPECIAL_PDS;
+ } else {
+ pd_size = ICSSG_NORMAL_PD_SIZE;
+ num_pds = ICSSG_NUM_NORMAL_PDS;
+ }
+
+ for (i = 0; i < num_pds; i++) {
+ memset(pd, 0, pd_size);
+
+ pdword[0] &= ICSSG_FLAG_MASK;
+ pdword[0] |= mp->flags;
+ pdaddr = mp->pd_addr_start + i * pd_size;
+
+ memcpy_toio(smem + pdaddr, pd, pd_size);
+ queue = mp->queue;
+ regmap_write(miig_rt, ICSSG_QUEUE_OFFSET + 4 * queue,
+ pdaddr);
+ }
+ }
+}
+
+void icssg_config_ipg(struct prueth_emac *emac)
+{
+ struct prueth *prueth = emac->prueth;
+ int slice = prueth_emac_slice(emac);
+
+ switch (emac->speed) {
+ case SPEED_1000:
+ icssg_mii_update_ipg(prueth->mii_rt, slice, MII_RT_TX_IPG_1G);
+ break;
+ case SPEED_100:
+ icssg_mii_update_ipg(prueth->mii_rt, slice, MII_RT_TX_IPG_100M);
+ break;
+ case SPEED_10:
+ /* IPG for 10M is same as 100M */
+ icssg_mii_update_ipg(prueth->mii_rt, slice, MII_RT_TX_IPG_100M);
+ break;
+ default:
+ /* Other links speeds not supported */
+ netdev_err(emac->ndev, "Unsupported link speed\n");
+ return;
+ }
+}
+
+static void emac_r30_cmd_init(struct prueth_emac *emac)
+{
+ struct icssg_r30_cmd __iomem *p;
+ int i;
+
+ p = emac->dram.va + MGR_R30_CMD_OFFSET;
+
+ for (i = 0; i < 4; i++)
+ writel(EMAC_NONE, &p->cmd[i]);
+}
+
+static int emac_r30_is_done(struct prueth_emac *emac)
+{
+ const struct icssg_r30_cmd __iomem *p;
+ u32 cmd;
+ int i;
+
+ p = emac->dram.va + MGR_R30_CMD_OFFSET;
+
+ for (i = 0; i < 4; i++) {
+ cmd = readl(&p->cmd[i]);
+ if (cmd != EMAC_NONE)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int prueth_emac_buffer_setup(struct prueth_emac *emac)
+{
+ struct icssg_buffer_pool_cfg __iomem *bpool_cfg;
+ struct icssg_rxq_ctx __iomem *rxq_ctx;
+ struct prueth *prueth = emac->prueth;
+ int slice = prueth_emac_slice(emac);
+ u32 addr;
+ int i;
+
+ /* Layout to have 64KB aligned buffer pool
+ * |BPOOL0|BPOOL1|RX_CTX0|RX_CTX1|
+ */
+
+ addr = lower_32_bits(prueth->msmcram.pa);
+ if (slice)
+ addr += PRUETH_NUM_BUF_POOLS * PRUETH_EMAC_BUF_POOL_SIZE;
+
+ if (addr % SZ_64K) {
+ dev_warn(prueth->dev, "buffer pool needs to be 64KB aligned\n");
+ return -EINVAL;
+ }
+
+ bpool_cfg = emac->dram.va + BUFFER_POOL_0_ADDR_OFFSET;
+ /* workaround for f/w bug. bpool 0 needs to be initilalized */
+ writel(addr, &bpool_cfg[0].addr);
+ writel(0, &bpool_cfg[0].len);
+
+ for (i = PRUETH_EMAC_BUF_POOL_START;
+ i < PRUETH_EMAC_BUF_POOL_START + PRUETH_NUM_BUF_POOLS;
+ i++) {
+ writel(addr, &bpool_cfg[i].addr);
+ writel(PRUETH_EMAC_BUF_POOL_SIZE, &bpool_cfg[i].len);
+ addr += PRUETH_EMAC_BUF_POOL_SIZE;
+ }
+
+ if (!slice)
+ addr += PRUETH_NUM_BUF_POOLS * PRUETH_EMAC_BUF_POOL_SIZE;
+ else
+ addr += PRUETH_EMAC_RX_CTX_BUF_SIZE * 2;
+
+ /* Pre-emptible RX buffer queue */
+ rxq_ctx = emac->dram.va + HOST_RX_Q_PRE_CONTEXT_OFFSET;
+ for (i = 0; i < 3; i++)
+ writel(addr, &rxq_ctx->start[i]);
+
+ addr += PRUETH_EMAC_RX_CTX_BUF_SIZE;
+ writel(addr, &rxq_ctx->end);
+
+ /* Express RX buffer queue */
+ rxq_ctx = emac->dram.va + HOST_RX_Q_EXP_CONTEXT_OFFSET;
+ for (i = 0; i < 3; i++)
+ writel(addr, &rxq_ctx->start[i]);
+
+ addr += PRUETH_EMAC_RX_CTX_BUF_SIZE;
+ writel(addr, &rxq_ctx->end);
+
+ return 0;
+}
+
+static void icssg_init_emac_mode(struct prueth *prueth)
+{
+ /* When the device is configured as a bridge and it is being brought
+ * back to the emac mode, the host mac address has to be set as 0.
+ */
+ u8 mac[ETH_ALEN] = { 0 };
+
+ if (prueth->emacs_initialized)
+ return;
+
+ regmap_update_bits(prueth->miig_rt, FDB_GEN_CFG1,
+ SMEM_VLAN_OFFSET_MASK, 0);
+ regmap_write(prueth->miig_rt, FDB_GEN_CFG2, 0);
+ /* Clear host MAC address */
+ icssg_class_set_host_mac_addr(prueth->miig_rt, mac);
+}
+
+int icssg_config(struct prueth *prueth, struct prueth_emac *emac, int slice)
+{
+ void __iomem *config = emac->dram.va + ICSSG_CONFIG_OFFSET;
+ struct icssg_flow_cfg __iomem *flow_cfg;
+ int ret;
+
+ icssg_init_emac_mode(prueth);
+
+ memset_io(config, 0, TAS_GATE_MASK_LIST0);
+ icssg_miig_queues_init(prueth, slice);
+
+ emac->speed = SPEED_1000;
+ emac->duplex = DUPLEX_FULL;
+ if (!phy_interface_mode_is_rgmii(emac->phy_if)) {
+ emac->speed = SPEED_100;
+ emac->duplex = DUPLEX_FULL;
+ }
+ regmap_update_bits(prueth->miig_rt, ICSSG_CFG_OFFSET,
+ ICSSG_CFG_DEFAULT, ICSSG_CFG_DEFAULT);
+ icssg_miig_set_interface_mode(prueth->miig_rt, slice, emac->phy_if);
+ icssg_config_mii_init(emac);
+ icssg_config_ipg(emac);
+ icssg_update_rgmii_cfg(prueth->miig_rt, emac);
+
+ /* set GPI mode */
+ pruss_cfg_gpimode(prueth->pruss, prueth->pru_id[slice],
+ PRUSS_GPI_MODE_MII);
+
+ /* enable XFR shift for PRU and RTU */
+ pruss_cfg_xfr_enable(prueth->pruss, PRU_TYPE_PRU, true);
+ pruss_cfg_xfr_enable(prueth->pruss, PRU_TYPE_RTU, true);
+
+ /* set C28 to 0x100 */
+ pru_rproc_set_ctable(prueth->pru[slice], PRU_C28, 0x100 << 8);
+ pru_rproc_set_ctable(prueth->rtu[slice], PRU_C28, 0x100 << 8);
+ pru_rproc_set_ctable(prueth->txpru[slice], PRU_C28, 0x100 << 8);
+
+ flow_cfg = config + PSI_L_REGULAR_FLOW_ID_BASE_OFFSET;
+ writew(emac->rx_flow_id_base, &flow_cfg->rx_base_flow);
+ writew(0, &flow_cfg->mgm_base_flow);
+ writeb(0, config + SPL_PKT_DEFAULT_PRIORITY);
+ writeb(0, config + QUEUE_NUM_UNTAGGED);
+
+ ret = prueth_emac_buffer_setup(emac);
+ if (ret)
+ return ret;
+
+ emac_r30_cmd_init(emac);
+
+ return 0;
+}
+
+/* Bitmask for ICSSG r30 commands */
+static const struct icssg_r30_cmd emac_r32_bitmask[] = {
+ {{0xffff0004, 0xffff0100, 0xffff0100, EMAC_NONE}}, /* EMAC_PORT_DISABLE */
+ {{0xfffb0040, 0xfeff0200, 0xfeff0200, EMAC_NONE}}, /* EMAC_PORT_BLOCK */
+ {{0xffbb0000, 0xfcff0000, 0xdcff0000, EMAC_NONE}}, /* EMAC_PORT_FORWARD */
+ {{0xffbb0000, 0xfcff0000, 0xfcff2000, EMAC_NONE}}, /* EMAC_PORT_FORWARD_WO_LEARNING */
+ {{0xffff0001, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT ALL */
+ {{0xfffe0002, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT TAGGED */
+ {{0xfffc0000, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT UNTAGGED and PRIO */
+ {{EMAC_NONE, 0xffff0020, EMAC_NONE, EMAC_NONE}}, /* TAS Trigger List change */
+ {{EMAC_NONE, 0xdfff1000, EMAC_NONE, EMAC_NONE}}, /* TAS set state ENABLE*/
+ {{EMAC_NONE, 0xefff2000, EMAC_NONE, EMAC_NONE}}, /* TAS set state RESET*/
+ {{EMAC_NONE, 0xcfff0000, EMAC_NONE, EMAC_NONE}}, /* TAS set state DISABLE*/
+ {{EMAC_NONE, EMAC_NONE, 0xffff0400, EMAC_NONE}}, /* UC flooding ENABLE*/
+ {{EMAC_NONE, EMAC_NONE, 0xfbff0000, EMAC_NONE}}, /* UC flooding DISABLE*/
+ {{EMAC_NONE, EMAC_NONE, 0xffff0800, EMAC_NONE}}, /* MC flooding ENABLE*/
+ {{EMAC_NONE, EMAC_NONE, 0xf7ff0000, EMAC_NONE}}, /* MC flooding DISABLE*/
+ {{EMAC_NONE, 0xffff4000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx ENABLE*/
+ {{EMAC_NONE, 0xbfff0000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx DISABLE*/
+ {{0xffff0010, EMAC_NONE, 0xffff0010, EMAC_NONE}}, /* VLAN AWARE*/
+ {{0xffef0000, EMAC_NONE, 0xffef0000, EMAC_NONE}} /* VLAN UNWARE*/
+};
+
+int emac_set_port_state(struct prueth_emac *emac,
+ enum icssg_port_state_cmd cmd)
+{
+ struct icssg_r30_cmd __iomem *p;
+ int ret = -ETIMEDOUT;
+ int done = 0;
+ int i;
+
+ p = emac->dram.va + MGR_R30_CMD_OFFSET;
+
+ if (cmd >= ICSSG_EMAC_PORT_MAX_COMMANDS) {
+ netdev_err(emac->ndev, "invalid port command\n");
+ return -EINVAL;
+ }
+
+ /* only one command at a time allowed to firmware */
+ mutex_lock(&emac->cmd_lock);
+
+ for (i = 0; i < 4; i++)
+ writel(emac_r32_bitmask[cmd].cmd[i], &p->cmd[i]);
+
+ /* wait for done */
+ ret = read_poll_timeout(emac_r30_is_done, done, done == 1,
+ 1000, 10000, false, emac);
+
+ if (ret == -ETIMEDOUT)
+ netdev_err(emac->ndev, "timeout waiting for command done\n");
+
+ mutex_unlock(&emac->cmd_lock);
+
+ return ret;
+}
+
+void icssg_config_set_speed(struct prueth_emac *emac)
+{
+ u8 fw_speed;
+
+ switch (emac->speed) {
+ case SPEED_1000:
+ fw_speed = FW_LINK_SPEED_1G;
+ break;
+ case SPEED_100:
+ fw_speed = FW_LINK_SPEED_100M;
+ break;
+ case SPEED_10:
+ fw_speed = FW_LINK_SPEED_10M;
+ break;
+ default:
+ /* Other links speeds not supported */
+ netdev_err(emac->ndev, "Unsupported link speed\n");
+ return;
+ }
+
+ writeb(fw_speed, emac->dram.va + PORT_LINK_SPEED_OFFSET);
+}