diff options
Diffstat (limited to 'drivers/net/designware_stm32.c')
-rw-r--r-- | drivers/net/designware_stm32.c | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/drivers/net/designware_stm32.c b/drivers/net/designware_stm32.c new file mode 100644 index 0000000000..5b087ad5a3 --- /dev/null +++ b/drivers/net/designware_stm32.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * Copyright (c) 2019, Ahmad Fatoum, Pengutronix + * + * Portions based on U-Boot's rtl8169.c and dwc_eth_qos. + */ + +#include <common.h> +#include <init.h> +#include <net.h> +#include <linux/clk.h> +#include <mfd/syscon.h> + +#include "designware_eqos.h" + +#define SYSCFG_PMCR_ETH_CLK_SEL BIT(16) +#define SYSCFG_PMCR_ETH_REF_CLK_SEL BIT(17) + +/* Ethernet PHY interface selection in register SYSCFG Configuration + *------------------------------------------ + * src |BIT(23)| BIT(22)| BIT(21)|BIT(20)| + *------------------------------------------ + * MII | 0 | 0 | 0 | 1 | + *------------------------------------------ + * GMII | 0 | 0 | 0 | 0 | + *------------------------------------------ + * RGMII | 0 | 0 | 1 | n/a | + *------------------------------------------ + * RMII | 1 | 0 | 0 | n/a | + *------------------------------------------ + */ +#define SYSCFG_PMCR_ETH_SEL_MII BIT(20) +#define SYSCFG_PMCR_ETH_SEL_RGMII BIT(21) +#define SYSCFG_PMCR_ETH_SEL_RMII BIT(23) +#define SYSCFG_PMCR_ETH_SEL_GMII 0 +#define SYSCFG_MCU_ETH_SEL_MII 0 +#define SYSCFG_MCU_ETH_SEL_RMII 1 + +/* Descriptors */ + +#define SYSCFG_MCU_ETH_MASK BIT(23) +#define SYSCFG_MP1_ETH_MASK GENMASK(23, 16) +#define SYSCFG_PMCCLRR_OFFSET 0x40 + +struct eqos_stm32 { + struct clk_bulk_data *clks; + int num_clks; + struct regmap *regmap; + u32 mode_reg; + int eth_clk_sel_reg; + int eth_ref_clk_sel_reg; +}; + +static inline struct eqos_stm32 *to_stm32(struct eqos *eqos) +{ + return eqos->priv; +} + +enum { CLK_STMMACETH, CLK_MAX_RX, CLK_MAX_TX, CLK_SYSCFG, }; +static const struct clk_bulk_data stm32_clks[] = { + [CLK_STMMACETH] = { .id = "stmmaceth" }, + [CLK_MAX_RX] = { .id = "mac-clk-rx" }, + [CLK_MAX_TX] = { .id = "mac-clk-tx" }, + [CLK_SYSCFG] = { .id = "syscfg-clk" }, +}; + +static unsigned long eqos_get_csr_clk_rate_stm32(struct eqos *eqos) +{ + return clk_get_rate(to_stm32(eqos)->clks[CLK_STMMACETH].clk); +} + +static int eqos_set_mode_stm32(struct eqos_stm32 *priv, phy_interface_t interface) +{ + u32 val, reg = priv->mode_reg; + int ret; + + switch (interface) { + case PHY_INTERFACE_MODE_MII: + val = SYSCFG_PMCR_ETH_SEL_MII; + break; + case PHY_INTERFACE_MODE_GMII: + val = SYSCFG_PMCR_ETH_SEL_GMII; + if (priv->eth_clk_sel_reg) + val |= SYSCFG_PMCR_ETH_CLK_SEL; + break; + case PHY_INTERFACE_MODE_RMII: + val = SYSCFG_PMCR_ETH_SEL_RMII; + if (priv->eth_ref_clk_sel_reg) + val |= SYSCFG_PMCR_ETH_REF_CLK_SEL; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + val = SYSCFG_PMCR_ETH_SEL_RGMII; + if (priv->eth_clk_sel_reg) + val |= SYSCFG_PMCR_ETH_CLK_SEL; + break; + default: + return -EINVAL; + } + + /* Need to update PMCCLRR (clear register) */ + ret = regmap_write(priv->regmap, reg + SYSCFG_PMCCLRR_OFFSET, + SYSCFG_MP1_ETH_MASK); + if (ret) + return -EIO; + + /* Update PMCSETR (set register) */ + regmap_update_bits(priv->regmap, reg, GENMASK(23, 16), val); + + return 0; +} + +static int eqos_init_stm32(struct device_d *dev, struct eqos *eqos) +{ + struct device_node *np = dev->device_node; + struct eqos_stm32 *priv = to_stm32(eqos); + struct clk_bulk_data *eth_ck; + int ret; + + /* Gigabit Ethernet 125MHz clock selection. */ + priv->eth_clk_sel_reg = of_property_read_bool(np, "st,eth-clk-sel"); + + /* Ethernet 50Mhz RMII clock selection */ + priv->eth_ref_clk_sel_reg = + of_property_read_bool(np, "st,eth-ref-clk-sel"); + + priv->regmap = syscon_regmap_lookup_by_phandle(dev->device_node, + "st,syscon"); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Could not get st,syscon node\n"); + return PTR_ERR(priv->regmap); + } + + ret = of_property_read_u32_index(dev->device_node, "st,syscon", + 1, &priv->mode_reg); + if (ret) { + dev_err(dev, "Can't get sysconfig mode offset (%s)\n", + strerror(-ret)); + return -EINVAL; + } + + ret = eqos_set_mode_stm32(priv, eqos->interface); + if (ret) + dev_warn(dev, "Configuring syscfg failed: %s\n", strerror(-ret)); + + priv->num_clks = ARRAY_SIZE(stm32_clks) + 1; + priv->clks = xmalloc(priv->num_clks * sizeof(*priv->clks)); + memcpy(priv->clks, stm32_clks, sizeof stm32_clks); + + ret = clk_bulk_get(dev, ARRAY_SIZE(stm32_clks), priv->clks); + if (ret) { + dev_err(dev, "Failed to get clks: %s\n", strerror(-ret)); + return ret; + } + + eth_ck = &priv->clks[ARRAY_SIZE(stm32_clks)]; + eth_ck->id = "eth-ck"; + eth_ck->clk = clk_get(dev, eth_ck->id); + if (IS_ERR(eth_ck->clk)) { + priv->num_clks--; + dev_dbg(dev, "No phy clock provided. Continuing without.\n"); + } + + return 0; + +} + +static int eqos_start_stm32(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + struct eqos_stm32 *priv = to_stm32(eqos); + int ret; + + ret = clk_bulk_enable(priv->num_clks, priv->clks); + if (ret < 0) { + eqos_err(eqos, "clk_bulk_enable() failed: %s\n", + strerror(-ret)); + return ret; + } + + udelay(10); + + ret = eqos_start(edev); + if (ret) + goto err_stop_clks; + + return 0; + +err_stop_clks: + clk_bulk_disable(priv->num_clks, priv->clks); + + return ret; +} + +static void eqos_stop_stm32(struct eth_device *edev) +{ + struct eqos_stm32 *priv = to_stm32(edev->priv); + + clk_bulk_disable(priv->num_clks, priv->clks); +} + +// todo split! +static struct eqos_ops stm32_ops = { + .init = eqos_init_stm32, + .get_ethaddr = eqos_get_ethaddr, + .set_ethaddr = eqos_set_ethaddr, + .start = eqos_start_stm32, + .stop = eqos_stop_stm32, + .adjust_link = eqos_adjust_link, + .get_csr_clk_rate = eqos_get_csr_clk_rate_stm32, + + .mdio_wait_us = 10 * USEC_PER_MSEC, + .clk_csr = EQOS_MDIO_ADDR_CR_250_300, + .config_mac = EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_AV, +}; + +static int eqos_probe_stm32(struct device_d *dev) +{ + return eqos_probe(dev, &stm32_ops, xzalloc(sizeof(struct eqos_stm32))); +} + +static void eqos_remove_stm32(struct device_d *dev) +{ + struct eqos_stm32 *priv = to_stm32(dev->priv); + + eqos_remove(dev); + + clk_bulk_put(priv->num_clks, priv->clks); +} + +static const struct of_device_id eqos_stm32_ids[] = { + { .compatible = "st,stm32mp1-dwmac" }, + { /* sentinel */ } +}; + +static struct driver_d eqos_stm32_driver = { + .name = "eqos-stm32", + .probe = eqos_probe_stm32, + .remove = eqos_remove_stm32, + .of_compatible = DRV_OF_COMPAT(eqos_stm32_ids), +}; +device_platform_driver(eqos_stm32_driver); |