| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Oxford Semiconductor OXNAS DWMAC glue layer |
| * |
| * Copyright (C) 2016 Neil Armstrong <narmstrong@baylibre.com> |
| * Copyright (C) 2014 Daniel Golle <daniel@makrotopia.org> |
| * Copyright (C) 2013 Ma Haijun <mahaijuns@gmail.com> |
| * Copyright (C) 2012 John Crispin <blogic@openwrt.org> |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/stmmac.h> |
| |
| #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 device *dev; |
| struct clk *clk; |
| struct regmap *regmap; |
| }; |
| |
| static int oxnas_dwmac_init(struct platform_device *pdev, void *priv) |
| { |
| struct oxnas_dwmac *dwmac = priv; |
| unsigned int value; |
| 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 = regmap_read(dwmac->regmap, OXNAS_DWMAC_CTRL_REGOFFSET, &value); |
| if (ret < 0) { |
| clk_disable_unprepare(dwmac->clk); |
| 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 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->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 of_device_id oxnas_dwmac_match[] = { |
| { .compatible = "oxsemi,ox820-dwmac" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, oxnas_dwmac_match); |
| |
| static struct platform_driver oxnas_dwmac_driver = { |
| .probe = oxnas_dwmac_probe, |
| .remove = 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 <narmstrong@baylibre.com>"); |
| MODULE_DESCRIPTION("Oxford Semiconductor OXNAS DWMAC glue layer"); |
| MODULE_LICENSE("GPL v2"); |