// SPDX-License-Identifier: GPL-2.0-only /* * Oxford Semiconductor OXNAS DWMAC glue layer * * Copyright (C) 2016 Neil Armstrong * Copyright (C) 2014 Daniel Golle * Copyright (C) 2013 Ma Haijun * Copyright (C) 2012 John Crispin */ #include #include #include #include #include #include #include #include #include #include "stmmac_platform.h" /* System Control regmap offsets */ #define OXNAS_DWMAC_CTRL_REGOFFSET 0x78 #define OXNAS_DWMAC_DELAY_REGOFFSET 0x100 /* Control Register */ #define DWMAC_CKEN_RX_IN 14 #define DWMAC_CKEN_RXN_OUT 13 #define DWMAC_CKEN_RX_OUT 12 #define DWMAC_CKEN_TX_IN 10 #define DWMAC_CKEN_TXN_OUT 9 #define DWMAC_CKEN_TX_OUT 8 #define DWMAC_RX_SOURCE 7 #define DWMAC_TX_SOURCE 6 #define DWMAC_LOW_TX_SOURCE 4 #define DWMAC_AUTO_TX_SOURCE 3 #define DWMAC_RGMII 2 #define DWMAC_SIMPLE_MUX 1 #define DWMAC_CKEN_GTX 0 /* Delay register */ #define DWMAC_TX_VARDELAY_SHIFT 0 #define DWMAC_TXN_VARDELAY_SHIFT 8 #define DWMAC_RX_VARDELAY_SHIFT 16 #define DWMAC_RXN_VARDELAY_SHIFT 24 #define DWMAC_TX_VARDELAY(d) ((d) << DWMAC_TX_VARDELAY_SHIFT) #define DWMAC_TXN_VARDELAY(d) ((d) << DWMAC_TXN_VARDELAY_SHIFT) #define DWMAC_RX_VARDELAY(d) ((d) << DWMAC_RX_VARDELAY_SHIFT) #define DWMAC_RXN_VARDELAY(d) ((d) << DWMAC_RXN_VARDELAY_SHIFT) struct oxnas_dwmac; struct oxnas_dwmac_data { int (*setup)(struct oxnas_dwmac *dwmac); }; struct oxnas_dwmac { struct device *dev; struct clk *clk; struct regmap *regmap; const struct oxnas_dwmac_data *data; }; static int oxnas_dwmac_setup_ox810se(struct oxnas_dwmac *dwmac) { unsigned int value; int ret; ret = regmap_read(dwmac->regmap, OXNAS_DWMAC_CTRL_REGOFFSET, &value); if (ret < 0) return ret; /* Enable GMII_GTXCLK to follow GMII_REFCLK, required for gigabit PHY */ value |= BIT(DWMAC_CKEN_GTX) | /* Use simple mux for 25/125 Mhz clock switching */ BIT(DWMAC_SIMPLE_MUX); regmap_write(dwmac->regmap, OXNAS_DWMAC_CTRL_REGOFFSET, value); return 0; } static int oxnas_dwmac_setup_ox820(struct oxnas_dwmac *dwmac) { unsigned int value; int ret; ret = regmap_read(dwmac->regmap, OXNAS_DWMAC_CTRL_REGOFFSET, &value); if (ret < 0) return ret; /* Enable GMII_GTXCLK to follow GMII_REFCLK, required for gigabit PHY */ value |= BIT(DWMAC_CKEN_GTX) | /* Use simple mux for 25/125 Mhz clock switching */ BIT(DWMAC_SIMPLE_MUX) | /* set auto switch tx clock source */ BIT(DWMAC_AUTO_TX_SOURCE) | /* enable tx & rx vardelay */ BIT(DWMAC_CKEN_TX_OUT) | BIT(DWMAC_CKEN_TXN_OUT) | BIT(DWMAC_CKEN_TX_IN) | BIT(DWMAC_CKEN_RX_OUT) | BIT(DWMAC_CKEN_RXN_OUT) | BIT(DWMAC_CKEN_RX_IN); regmap_write(dwmac->regmap, OXNAS_DWMAC_CTRL_REGOFFSET, value); /* set tx & rx vardelay */ value = DWMAC_TX_VARDELAY(4) | DWMAC_TXN_VARDELAY(2) | DWMAC_RX_VARDELAY(10) | DWMAC_RXN_VARDELAY(8); regmap_write(dwmac->regmap, OXNAS_DWMAC_DELAY_REGOFFSET, value); return 0; } static int oxnas_dwmac_init(struct platform_device *pdev, void *priv) { struct oxnas_dwmac *dwmac = priv; int ret; /* Reset HW here before changing the glue configuration */ ret = device_reset(dwmac->dev); if (ret) return ret; ret = clk_prepare_enable(dwmac->clk); if (ret) return ret; ret = dwmac->data->setup(dwmac); if (ret) clk_disable_unprepare(dwmac->clk); return ret; } static void oxnas_dwmac_exit(struct platform_device *pdev, void *priv) { struct oxnas_dwmac *dwmac = priv; clk_disable_unprepare(dwmac->clk); } static int oxnas_dwmac_probe(struct platform_device *pdev) { struct plat_stmmacenet_data *plat_dat; struct stmmac_resources stmmac_res; struct oxnas_dwmac *dwmac; int ret; ret = stmmac_get_platform_resources(pdev, &stmmac_res); if (ret) return ret; plat_dat = stmmac_probe_config_dt(pdev, stmmac_res.mac); if (IS_ERR(plat_dat)) return PTR_ERR(plat_dat); dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL); if (!dwmac) { ret = -ENOMEM; goto err_remove_config_dt; } dwmac->data = (const struct oxnas_dwmac_data *)of_device_get_match_data(&pdev->dev); if (!dwmac->data) { ret = -EINVAL; goto err_remove_config_dt; } dwmac->dev = &pdev->dev; plat_dat->bsp_priv = dwmac; plat_dat->init = oxnas_dwmac_init; plat_dat->exit = oxnas_dwmac_exit; dwmac->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "oxsemi,sys-ctrl"); if (IS_ERR(dwmac->regmap)) { dev_err(&pdev->dev, "failed to have sysctrl regmap\n"); ret = PTR_ERR(dwmac->regmap); goto err_remove_config_dt; } dwmac->clk = devm_clk_get(&pdev->dev, "gmac"); if (IS_ERR(dwmac->clk)) { ret = PTR_ERR(dwmac->clk); goto err_remove_config_dt; } ret = oxnas_dwmac_init(pdev, plat_dat->bsp_priv); if (ret) goto err_remove_config_dt; ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); if (ret) goto err_dwmac_exit; return 0; err_dwmac_exit: oxnas_dwmac_exit(pdev, plat_dat->bsp_priv); err_remove_config_dt: stmmac_remove_config_dt(pdev, plat_dat); return ret; } static const struct oxnas_dwmac_data ox810se_dwmac_data = { .setup = oxnas_dwmac_setup_ox810se, }; static const struct oxnas_dwmac_data ox820_dwmac_data = { .setup = oxnas_dwmac_setup_ox820, }; static const struct of_device_id oxnas_dwmac_match[] = { { .compatible = "oxsemi,ox810se-dwmac", .data = &ox810se_dwmac_data, }, { .compatible = "oxsemi,ox820-dwmac", .data = &ox820_dwmac_data, }, { } }; MODULE_DEVICE_TABLE(of, oxnas_dwmac_match); static struct platform_driver oxnas_dwmac_driver = { .probe = oxnas_dwmac_probe, .remove_new = stmmac_pltfr_remove, .driver = { .name = "oxnas-dwmac", .pm = &stmmac_pltfr_pm_ops, .of_match_table = oxnas_dwmac_match, }, }; module_platform_driver(oxnas_dwmac_driver); MODULE_AUTHOR("Neil Armstrong "); MODULE_DESCRIPTION("Oxford Semiconductor OXNAS DWMAC glue layer"); MODULE_LICENSE("GPL v2");