| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * dwmac-ingenic.c - Ingenic SoCs DWMAC specific glue layer | 
 |  * | 
 |  * Copyright (c) 2021 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com> | 
 |  */ | 
 |  | 
 | #include <linux/bitfield.h> | 
 | #include <linux/clk.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/mfd/syscon.h> | 
 | #include <linux/module.h> | 
 | #include <linux/of.h> | 
 | #include <linux/of_device.h> | 
 | #include <linux/of_net.h> | 
 | #include <linux/phy.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/stmmac.h> | 
 |  | 
 | #include "stmmac_platform.h" | 
 |  | 
 | #define MACPHYC_TXCLK_SEL_MASK		GENMASK(31, 31) | 
 | #define MACPHYC_TXCLK_SEL_OUTPUT	0x1 | 
 | #define MACPHYC_TXCLK_SEL_INPUT		0x0 | 
 | #define MACPHYC_MODE_SEL_MASK		GENMASK(31, 31) | 
 | #define MACPHYC_MODE_SEL_RMII		0x0 | 
 | #define MACPHYC_TX_SEL_MASK			GENMASK(19, 19) | 
 | #define MACPHYC_TX_SEL_ORIGIN		0x0 | 
 | #define MACPHYC_TX_SEL_DELAY		0x1 | 
 | #define MACPHYC_TX_DELAY_MASK		GENMASK(18, 12) | 
 | #define MACPHYC_RX_SEL_MASK			GENMASK(11, 11) | 
 | #define MACPHYC_RX_SEL_ORIGIN		0x0 | 
 | #define MACPHYC_RX_SEL_DELAY		0x1 | 
 | #define MACPHYC_RX_DELAY_MASK		GENMASK(10, 4) | 
 | #define MACPHYC_SOFT_RST_MASK		GENMASK(3, 3) | 
 | #define MACPHYC_PHY_INFT_MASK		GENMASK(2, 0) | 
 | #define MACPHYC_PHY_INFT_RMII		0x4 | 
 | #define MACPHYC_PHY_INFT_RGMII		0x1 | 
 | #define MACPHYC_PHY_INFT_GMII		0x0 | 
 | #define MACPHYC_PHY_INFT_MII		0x0 | 
 |  | 
 | #define MACPHYC_TX_DELAY_PS_MAX		2496 | 
 | #define MACPHYC_TX_DELAY_PS_MIN		20 | 
 |  | 
 | #define MACPHYC_RX_DELAY_PS_MAX		2496 | 
 | #define MACPHYC_RX_DELAY_PS_MIN		20 | 
 |  | 
 | enum ingenic_mac_version { | 
 | 	ID_JZ4775, | 
 | 	ID_X1000, | 
 | 	ID_X1600, | 
 | 	ID_X1830, | 
 | 	ID_X2000, | 
 | }; | 
 |  | 
 | struct ingenic_mac { | 
 | 	const struct ingenic_soc_info *soc_info; | 
 | 	struct device *dev; | 
 | 	struct regmap *regmap; | 
 |  | 
 | 	int rx_delay; | 
 | 	int tx_delay; | 
 | }; | 
 |  | 
 | struct ingenic_soc_info { | 
 | 	enum ingenic_mac_version version; | 
 | 	u32 mask; | 
 |  | 
 | 	int (*set_mode)(struct plat_stmmacenet_data *plat_dat); | 
 | }; | 
 |  | 
 | static int ingenic_mac_init(struct plat_stmmacenet_data *plat_dat) | 
 | { | 
 | 	struct ingenic_mac *mac = plat_dat->bsp_priv; | 
 | 	int ret; | 
 |  | 
 | 	if (mac->soc_info->set_mode) { | 
 | 		ret = mac->soc_info->set_mode(plat_dat); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int jz4775_mac_set_mode(struct plat_stmmacenet_data *plat_dat) | 
 | { | 
 | 	struct ingenic_mac *mac = plat_dat->bsp_priv; | 
 | 	unsigned int val; | 
 |  | 
 | 	switch (plat_dat->interface) { | 
 | 	case PHY_INTERFACE_MODE_MII: | 
 | 		val = FIELD_PREP(MACPHYC_TXCLK_SEL_MASK, MACPHYC_TXCLK_SEL_INPUT) | | 
 | 			  FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_MII); | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_MII\n"); | 
 | 		break; | 
 |  | 
 | 	case PHY_INTERFACE_MODE_GMII: | 
 | 		val = FIELD_PREP(MACPHYC_TXCLK_SEL_MASK, MACPHYC_TXCLK_SEL_INPUT) | | 
 | 			  FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_GMII); | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_GMII\n"); | 
 | 		break; | 
 |  | 
 | 	case PHY_INTERFACE_MODE_RMII: | 
 | 		val = FIELD_PREP(MACPHYC_TXCLK_SEL_MASK, MACPHYC_TXCLK_SEL_INPUT) | | 
 | 			  FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_RMII); | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_RMII\n"); | 
 | 		break; | 
 |  | 
 | 	case PHY_INTERFACE_MODE_RGMII: | 
 | 	case PHY_INTERFACE_MODE_RGMII_ID: | 
 | 	case PHY_INTERFACE_MODE_RGMII_TXID: | 
 | 	case PHY_INTERFACE_MODE_RGMII_RXID: | 
 | 		val = FIELD_PREP(MACPHYC_TXCLK_SEL_MASK, MACPHYC_TXCLK_SEL_INPUT) | | 
 | 			  FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_RGMII); | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_RGMII\n"); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		dev_err(mac->dev, "Unsupported interface %d", plat_dat->interface); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Update MAC PHY control register */ | 
 | 	return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val); | 
 | } | 
 |  | 
 | static int x1000_mac_set_mode(struct plat_stmmacenet_data *plat_dat) | 
 | { | 
 | 	struct ingenic_mac *mac = plat_dat->bsp_priv; | 
 |  | 
 | 	switch (plat_dat->interface) { | 
 | 	case PHY_INTERFACE_MODE_RMII: | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_RMII\n"); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		dev_err(mac->dev, "Unsupported interface %d", plat_dat->interface); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Update MAC PHY control register */ | 
 | 	return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, 0); | 
 | } | 
 |  | 
 | static int x1600_mac_set_mode(struct plat_stmmacenet_data *plat_dat) | 
 | { | 
 | 	struct ingenic_mac *mac = plat_dat->bsp_priv; | 
 | 	unsigned int val; | 
 |  | 
 | 	switch (plat_dat->interface) { | 
 | 	case PHY_INTERFACE_MODE_RMII: | 
 | 		val = FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_RMII); | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_RMII\n"); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		dev_err(mac->dev, "Unsupported interface %d", plat_dat->interface); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Update MAC PHY control register */ | 
 | 	return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val); | 
 | } | 
 |  | 
 | static int x1830_mac_set_mode(struct plat_stmmacenet_data *plat_dat) | 
 | { | 
 | 	struct ingenic_mac *mac = plat_dat->bsp_priv; | 
 | 	unsigned int val; | 
 |  | 
 | 	switch (plat_dat->interface) { | 
 | 	case PHY_INTERFACE_MODE_RMII: | 
 | 		val = FIELD_PREP(MACPHYC_MODE_SEL_MASK, MACPHYC_MODE_SEL_RMII) | | 
 | 			  FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_RMII); | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_RMII\n"); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		dev_err(mac->dev, "Unsupported interface %d", plat_dat->interface); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Update MAC PHY control register */ | 
 | 	return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val); | 
 | } | 
 |  | 
 | static int x2000_mac_set_mode(struct plat_stmmacenet_data *plat_dat) | 
 | { | 
 | 	struct ingenic_mac *mac = plat_dat->bsp_priv; | 
 | 	unsigned int val; | 
 |  | 
 | 	switch (plat_dat->interface) { | 
 | 	case PHY_INTERFACE_MODE_RMII: | 
 | 		val = FIELD_PREP(MACPHYC_TX_SEL_MASK, MACPHYC_TX_SEL_ORIGIN) | | 
 | 			  FIELD_PREP(MACPHYC_RX_SEL_MASK, MACPHYC_RX_SEL_ORIGIN) | | 
 | 			  FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_RMII); | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_RMII\n"); | 
 | 		break; | 
 |  | 
 | 	case PHY_INTERFACE_MODE_RGMII: | 
 | 	case PHY_INTERFACE_MODE_RGMII_ID: | 
 | 	case PHY_INTERFACE_MODE_RGMII_TXID: | 
 | 	case PHY_INTERFACE_MODE_RGMII_RXID: | 
 | 		val = FIELD_PREP(MACPHYC_PHY_INFT_MASK, MACPHYC_PHY_INFT_RGMII); | 
 |  | 
 | 		if (mac->tx_delay == 0) | 
 | 			val |= FIELD_PREP(MACPHYC_TX_SEL_MASK, MACPHYC_TX_SEL_ORIGIN); | 
 | 		else | 
 | 			val |= FIELD_PREP(MACPHYC_TX_SEL_MASK, MACPHYC_TX_SEL_DELAY) | | 
 | 				   FIELD_PREP(MACPHYC_TX_DELAY_MASK, (mac->tx_delay + 9750) / 19500 - 1); | 
 |  | 
 | 		if (mac->rx_delay == 0) | 
 | 			val |= FIELD_PREP(MACPHYC_RX_SEL_MASK, MACPHYC_RX_SEL_ORIGIN); | 
 | 		else | 
 | 			val |= FIELD_PREP(MACPHYC_RX_SEL_MASK, MACPHYC_RX_SEL_DELAY) | | 
 | 				   FIELD_PREP(MACPHYC_RX_DELAY_MASK, (mac->rx_delay + 9750) / 19500 - 1); | 
 |  | 
 | 		dev_dbg(mac->dev, "MAC PHY Control Register: PHY_INTERFACE_MODE_RGMII\n"); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		dev_err(mac->dev, "Unsupported interface %d", plat_dat->interface); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Update MAC PHY control register */ | 
 | 	return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val); | 
 | } | 
 |  | 
 | static int ingenic_mac_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct plat_stmmacenet_data *plat_dat; | 
 | 	struct stmmac_resources stmmac_res; | 
 | 	struct ingenic_mac *mac; | 
 | 	const struct ingenic_soc_info *data; | 
 | 	u32 tx_delay_ps, rx_delay_ps; | 
 | 	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); | 
 |  | 
 | 	mac = devm_kzalloc(&pdev->dev, sizeof(*mac), GFP_KERNEL); | 
 | 	if (!mac) { | 
 | 		ret = -ENOMEM; | 
 | 		goto err_remove_config_dt; | 
 | 	} | 
 |  | 
 | 	data = of_device_get_match_data(&pdev->dev); | 
 | 	if (!data) { | 
 | 		dev_err(&pdev->dev, "No of match data provided\n"); | 
 | 		ret = -EINVAL; | 
 | 		goto err_remove_config_dt; | 
 | 	} | 
 |  | 
 | 	/* Get MAC PHY control register */ | 
 | 	mac->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "mode-reg"); | 
 | 	if (IS_ERR(mac->regmap)) { | 
 | 		dev_err(&pdev->dev, "%s: Failed to get syscon regmap\n", __func__); | 
 | 		ret = PTR_ERR(mac->regmap); | 
 | 		goto err_remove_config_dt; | 
 | 	} | 
 |  | 
 | 	if (!of_property_read_u32(pdev->dev.of_node, "tx-clk-delay-ps", &tx_delay_ps)) { | 
 | 		if (tx_delay_ps >= MACPHYC_TX_DELAY_PS_MIN && | 
 | 			tx_delay_ps <= MACPHYC_TX_DELAY_PS_MAX) { | 
 | 			mac->tx_delay = tx_delay_ps * 1000; | 
 | 		} else { | 
 | 			dev_err(&pdev->dev, "Invalid TX clock delay: %dps\n", tx_delay_ps); | 
 | 			ret = -EINVAL; | 
 | 			goto err_remove_config_dt; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (!of_property_read_u32(pdev->dev.of_node, "rx-clk-delay-ps", &rx_delay_ps)) { | 
 | 		if (rx_delay_ps >= MACPHYC_RX_DELAY_PS_MIN && | 
 | 			rx_delay_ps <= MACPHYC_RX_DELAY_PS_MAX) { | 
 | 			mac->rx_delay = rx_delay_ps * 1000; | 
 | 		} else { | 
 | 			dev_err(&pdev->dev, "Invalid RX clock delay: %dps\n", rx_delay_ps); | 
 | 			ret = -EINVAL; | 
 | 			goto err_remove_config_dt; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	mac->soc_info = data; | 
 | 	mac->dev = &pdev->dev; | 
 |  | 
 | 	plat_dat->bsp_priv = mac; | 
 |  | 
 | 	ret = ingenic_mac_init(plat_dat); | 
 | 	if (ret) | 
 | 		goto err_remove_config_dt; | 
 |  | 
 | 	ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); | 
 | 	if (ret) | 
 | 		goto err_remove_config_dt; | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_remove_config_dt: | 
 | 	stmmac_remove_config_dt(pdev, plat_dat); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | #ifdef CONFIG_PM_SLEEP | 
 | static int ingenic_mac_suspend(struct device *dev) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = stmmac_suspend(dev); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int ingenic_mac_resume(struct device *dev) | 
 | { | 
 | 	struct net_device *ndev = dev_get_drvdata(dev); | 
 | 	struct stmmac_priv *priv = netdev_priv(ndev); | 
 | 	int ret; | 
 |  | 
 | 	ret = ingenic_mac_init(priv->plat); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = stmmac_resume(dev); | 
 |  | 
 | 	return ret; | 
 | } | 
 | #endif /* CONFIG_PM_SLEEP */ | 
 |  | 
 | static SIMPLE_DEV_PM_OPS(ingenic_mac_pm_ops, ingenic_mac_suspend, ingenic_mac_resume); | 
 |  | 
 | static struct ingenic_soc_info jz4775_soc_info = { | 
 | 	.version = ID_JZ4775, | 
 | 	.mask = MACPHYC_TXCLK_SEL_MASK | MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK, | 
 |  | 
 | 	.set_mode = jz4775_mac_set_mode, | 
 | }; | 
 |  | 
 | static struct ingenic_soc_info x1000_soc_info = { | 
 | 	.version = ID_X1000, | 
 | 	.mask = MACPHYC_SOFT_RST_MASK, | 
 |  | 
 | 	.set_mode = x1000_mac_set_mode, | 
 | }; | 
 |  | 
 | static struct ingenic_soc_info x1600_soc_info = { | 
 | 	.version = ID_X1600, | 
 | 	.mask = MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK, | 
 |  | 
 | 	.set_mode = x1600_mac_set_mode, | 
 | }; | 
 |  | 
 | static struct ingenic_soc_info x1830_soc_info = { | 
 | 	.version = ID_X1830, | 
 | 	.mask = MACPHYC_MODE_SEL_MASK | MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK, | 
 |  | 
 | 	.set_mode = x1830_mac_set_mode, | 
 | }; | 
 |  | 
 | static struct ingenic_soc_info x2000_soc_info = { | 
 | 	.version = ID_X2000, | 
 | 	.mask = MACPHYC_TX_SEL_MASK | MACPHYC_TX_DELAY_MASK | MACPHYC_RX_SEL_MASK | | 
 | 			MACPHYC_RX_DELAY_MASK | MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK, | 
 |  | 
 | 	.set_mode = x2000_mac_set_mode, | 
 | }; | 
 |  | 
 | static const struct of_device_id ingenic_mac_of_matches[] = { | 
 | 	{ .compatible = "ingenic,jz4775-mac", .data = &jz4775_soc_info }, | 
 | 	{ .compatible = "ingenic,x1000-mac", .data = &x1000_soc_info }, | 
 | 	{ .compatible = "ingenic,x1600-mac", .data = &x1600_soc_info }, | 
 | 	{ .compatible = "ingenic,x1830-mac", .data = &x1830_soc_info }, | 
 | 	{ .compatible = "ingenic,x2000-mac", .data = &x2000_soc_info }, | 
 | 	{ } | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, ingenic_mac_of_matches); | 
 |  | 
 | static struct platform_driver ingenic_mac_driver = { | 
 | 	.probe		= ingenic_mac_probe, | 
 | 	.remove		= stmmac_pltfr_remove, | 
 | 	.driver		= { | 
 | 		.name	= "ingenic-mac", | 
 | 		.pm		= pm_ptr(&ingenic_mac_pm_ops), | 
 | 		.of_match_table = ingenic_mac_of_matches, | 
 | 	}, | 
 | }; | 
 | module_platform_driver(ingenic_mac_driver); | 
 |  | 
 | MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>"); | 
 | MODULE_DESCRIPTION("Ingenic SoCs DWMAC specific glue layer"); | 
 | MODULE_LICENSE("GPL v2"); |