// SPDX-License-Identifier: GPL-2.0 /* ICSSG Ethernet driver * * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com */ #include #include #include #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 MII_RT_TX_IPG_100M_SR1 0x166 #define MII_RT_TX_IPG_1G_SR1 0x1a #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); u32 ipg; switch (emac->speed) { case SPEED_1000: ipg = emac->is_sr1 ? MII_RT_TX_IPG_1G_SR1 : MII_RT_TX_IPG_1G; break; case SPEED_100: ipg = emac->is_sr1 ? MII_RT_TX_IPG_100M_SR1 : MII_RT_TX_IPG_100M; break; case SPEED_10: /* Firmware hardcodes IPG for SR1.0 */ if (emac->is_sr1) return; /* IPG for 10M is same as 100M */ ipg = MII_RT_TX_IPG_100M; break; default: /* Other links speeds not supported */ netdev_err(emac->ndev, "Unsupported link speed\n"); return; } icssg_mii_update_ipg(prueth->mii_rt, slice, ipg); } 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, 0xffff0004, EMAC_NONE}}, /* EMAC_PORT_DISABLE */ {{0xfffb0040, 0xfeff0200, 0xfeff0200, EMAC_NONE}}, /* EMAC_PORT_BLOCK */ {{0xffbb0000, 0xfcff0000, 0xdcfb0000, 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_half_duplex(struct prueth_emac *emac) { u32 val; if (!emac->half_duplex) return; val = get_random_u32(); writel(val, emac->dram.va + HD_RAND_SEED_OFFSET); } 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; } if (emac->duplex == DUPLEX_HALF) fw_speed |= FW_LINK_SPEED_HD; writeb(fw_speed, emac->dram.va + PORT_LINK_SPEED_OFFSET); }