mirror of
				https://source.denx.de/u-boot/u-boot.git
				synced 2025-11-04 10:21:25 +01:00 
			
		
		
		
	The ADIN1300 supports generating certain clocks on its GP_CLK pin, as well as providing the reference clock on CLK25_REF. Add support for selecting the clock via device-tree properties. This patch is based on the Linux implementation for this feature, which has been added to netdev/net-next.git [1]. [2] https://patchwork.kernel.org/project/netdevbpf/cover/20220517085143.3749-1-josua@solid-run.com/ Signed-off-by: Josua Mayer <josua@solid-run.com>
		
			
				
	
	
		
			271 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0+
 | 
						|
/**
 | 
						|
 *  Driver for Analog Devices Industrial Ethernet PHYs
 | 
						|
 *
 | 
						|
 * Copyright 2019 Analog Devices Inc.
 | 
						|
 * Copyright 2022 Variscite Ltd.
 | 
						|
 * Copyright 2022 Josua Mayer <josua@solid-run.com>
 | 
						|
 */
 | 
						|
#include <common.h>
 | 
						|
#include <phy.h>
 | 
						|
#include <linux/bitops.h>
 | 
						|
#include <linux/bitfield.h>
 | 
						|
 | 
						|
#define PHY_ID_ADIN1300				0x0283bc30
 | 
						|
#define ADIN1300_EXT_REG_PTR			0x10
 | 
						|
#define ADIN1300_EXT_REG_DATA			0x11
 | 
						|
 | 
						|
#define ADIN1300_GE_CLK_CFG_REG			0xff1f
 | 
						|
#define   ADIN1300_GE_CLK_CFG_MASK		GENMASK(5, 0)
 | 
						|
#define   ADIN1300_GE_CLK_CFG_RCVR_125		BIT(5)
 | 
						|
#define   ADIN1300_GE_CLK_CFG_FREE_125		BIT(4)
 | 
						|
#define   ADIN1300_GE_CLK_CFG_REF_EN		BIT(3)
 | 
						|
#define   ADIN1300_GE_CLK_CFG_HRT_RCVR		BIT(2)
 | 
						|
#define   ADIN1300_GE_CLK_CFG_HRT_FREE		BIT(1)
 | 
						|
#define   ADIN1300_GE_CLK_CFG_25		BIT(0)
 | 
						|
 | 
						|
#define ADIN1300_GE_RGMII_CFG			0xff23
 | 
						|
#define ADIN1300_GE_RGMII_RX_MSK		GENMASK(8, 6)
 | 
						|
#define ADIN1300_GE_RGMII_RX_SEL(x)		\
 | 
						|
		FIELD_PREP(ADIN1300_GE_RGMII_RX_MSK, x)
 | 
						|
#define ADIN1300_GE_RGMII_GTX_MSK		GENMASK(5, 3)
 | 
						|
#define ADIN1300_GE_RGMII_GTX_SEL(x)		\
 | 
						|
		FIELD_PREP(ADIN1300_GE_RGMII_GTX_MSK, x)
 | 
						|
#define ADIN1300_GE_RGMII_RXID_EN		BIT(2)
 | 
						|
#define ADIN1300_GE_RGMII_TXID_EN		BIT(1)
 | 
						|
#define ADIN1300_GE_RGMII_EN			BIT(0)
 | 
						|
 | 
						|
/* RGMII internal delay settings for rx and tx for ADIN1300 */
 | 
						|
#define ADIN1300_RGMII_1_60_NS			0x0001
 | 
						|
#define ADIN1300_RGMII_1_80_NS			0x0002
 | 
						|
#define	ADIN1300_RGMII_2_00_NS			0x0000
 | 
						|
#define	ADIN1300_RGMII_2_20_NS			0x0006
 | 
						|
#define	ADIN1300_RGMII_2_40_NS			0x0007
 | 
						|
 | 
						|
/**
 | 
						|
 * struct adin_cfg_reg_map - map a config value to aregister value
 | 
						|
 * @cfg		value in device configuration
 | 
						|
 * @reg		value in the register
 | 
						|
 */
 | 
						|
struct adin_cfg_reg_map {
 | 
						|
	int cfg;
 | 
						|
	int reg;
 | 
						|
};
 | 
						|
 | 
						|
static const struct adin_cfg_reg_map adin_rgmii_delays[] = {
 | 
						|
	{ 1600, ADIN1300_RGMII_1_60_NS },
 | 
						|
	{ 1800, ADIN1300_RGMII_1_80_NS },
 | 
						|
	{ 2000, ADIN1300_RGMII_2_00_NS },
 | 
						|
	{ 2200, ADIN1300_RGMII_2_20_NS },
 | 
						|
	{ 2400, ADIN1300_RGMII_2_40_NS },
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
static int adin_lookup_reg_value(const struct adin_cfg_reg_map *tbl, int cfg)
 | 
						|
{
 | 
						|
	size_t i;
 | 
						|
 | 
						|
	for (i = 0; tbl[i].cfg; i++) {
 | 
						|
		if (tbl[i].cfg == cfg)
 | 
						|
			return tbl[i].reg;
 | 
						|
	}
 | 
						|
 | 
						|
	return -EINVAL;
 | 
						|
}
 | 
						|
 | 
						|
static u32 adin_get_reg_value(struct phy_device *phydev,
 | 
						|
			      const char *prop_name,
 | 
						|
			      const struct adin_cfg_reg_map *tbl,
 | 
						|
			      u32 dflt)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	ofnode node = phy_get_ofnode(phydev);
 | 
						|
	if (!ofnode_valid(node)) {
 | 
						|
		printf("%s: failed to get node\n", __func__);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ofnode_read_u32(node, prop_name, &val)) {
 | 
						|
		printf("%s: failed to find %s, using default %d\n",
 | 
						|
		       __func__, prop_name, dflt);
 | 
						|
		return dflt;
 | 
						|
	}
 | 
						|
 | 
						|
	debug("%s: %s = '%d'\n", __func__, prop_name, val);
 | 
						|
 | 
						|
	rc = adin_lookup_reg_value(tbl, val);
 | 
						|
	if (rc < 0) {
 | 
						|
		printf("%s: Unsupported value %u for %s using default (%u)\n",
 | 
						|
		      __func__, val, prop_name, dflt);
 | 
						|
		return dflt;
 | 
						|
	}
 | 
						|
 | 
						|
	return rc;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * adin_get_phy_mode_override - Get phy-mode override for adin PHY
 | 
						|
 *
 | 
						|
 * The function gets phy-mode string from property 'adi,phy-mode-override'
 | 
						|
 * and return its index in phy_interface_strings table, or -1 in error case.
 | 
						|
 */
 | 
						|
phy_interface_t adin_get_phy_mode_override(struct phy_device *phydev)
 | 
						|
{
 | 
						|
	ofnode node = phy_get_ofnode(phydev);
 | 
						|
	const char *phy_mode_override;
 | 
						|
	const char *prop_phy_mode_override = "adi,phy-mode-override";
 | 
						|
	int i;
 | 
						|
 | 
						|
	phy_mode_override = ofnode_read_string(node, prop_phy_mode_override);
 | 
						|
	if (!phy_mode_override)
 | 
						|
		return PHY_INTERFACE_MODE_NA;
 | 
						|
 | 
						|
	debug("%s: %s = '%s'\n",
 | 
						|
	      __func__, prop_phy_mode_override, phy_mode_override);
 | 
						|
 | 
						|
	for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++)
 | 
						|
		if (!strcmp(phy_mode_override, phy_interface_strings[i]))
 | 
						|
			return (phy_interface_t) i;
 | 
						|
 | 
						|
	printf("%s: Invalid PHY interface '%s'\n", __func__, phy_mode_override);
 | 
						|
 | 
						|
	return PHY_INTERFACE_MODE_NA;
 | 
						|
}
 | 
						|
 | 
						|
static u16 adin_ext_read(struct phy_device *phydev, const u32 regnum)
 | 
						|
{
 | 
						|
	u16 val;
 | 
						|
 | 
						|
	phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum);
 | 
						|
	val = phy_read(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA);
 | 
						|
 | 
						|
	debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val);
 | 
						|
 | 
						|
	return val;
 | 
						|
}
 | 
						|
 | 
						|
static int adin_ext_write(struct phy_device *phydev, const u32 regnum, const u16 val)
 | 
						|
{
 | 
						|
	debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val);
 | 
						|
 | 
						|
	phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum);
 | 
						|
 | 
						|
	return phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA, val);
 | 
						|
}
 | 
						|
 | 
						|
static int adin_config_clk_out(struct phy_device *phydev)
 | 
						|
{
 | 
						|
	ofnode node = phy_get_ofnode(phydev);
 | 
						|
	const char *val = NULL;
 | 
						|
	u8 sel = 0;
 | 
						|
 | 
						|
	val = ofnode_read_string(node, "adi,phy-output-clock");
 | 
						|
	if (!val) {
 | 
						|
		/* property not present, do not enable GP_CLK pin */
 | 
						|
	} else if (strcmp(val, "25mhz-reference") == 0) {
 | 
						|
		sel |= ADIN1300_GE_CLK_CFG_25;
 | 
						|
	} else if (strcmp(val, "125mhz-free-running") == 0) {
 | 
						|
		sel |= ADIN1300_GE_CLK_CFG_FREE_125;
 | 
						|
	} else if (strcmp(val, "adaptive-free-running") == 0) {
 | 
						|
		sel |= ADIN1300_GE_CLK_CFG_HRT_FREE;
 | 
						|
	} else {
 | 
						|
		pr_err("%s: invalid adi,phy-output-clock\n", __func__);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ofnode_read_bool(node, "adi,phy-output-reference-clock"))
 | 
						|
		sel |= ADIN1300_GE_CLK_CFG_REF_EN;
 | 
						|
 | 
						|
	return adin_ext_write(phydev, ADIN1300_GE_CLK_CFG_REG,
 | 
						|
			      ADIN1300_GE_CLK_CFG_MASK & sel);
 | 
						|
}
 | 
						|
 | 
						|
static int adin_config_rgmii_mode(struct phy_device *phydev)
 | 
						|
{
 | 
						|
	u16 reg_val;
 | 
						|
	u32 val;
 | 
						|
	phy_interface_t phy_mode_override = adin_get_phy_mode_override(phydev);
 | 
						|
 | 
						|
	if (phy_mode_override != PHY_INTERFACE_MODE_NA) {
 | 
						|
		phydev->interface = phy_mode_override;
 | 
						|
	}
 | 
						|
 | 
						|
	reg_val = adin_ext_read(phydev, ADIN1300_GE_RGMII_CFG);
 | 
						|
 | 
						|
	if (!phy_interface_is_rgmii(phydev)) {
 | 
						|
		/* Disable RGMII */
 | 
						|
		reg_val &= ~ADIN1300_GE_RGMII_EN;
 | 
						|
		return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Enable RGMII */
 | 
						|
	reg_val |= ADIN1300_GE_RGMII_EN;
 | 
						|
 | 
						|
	/* Enable / Disable RGMII RX Delay */
 | 
						|
	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
 | 
						|
	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
 | 
						|
		reg_val |= ADIN1300_GE_RGMII_RXID_EN;
 | 
						|
 | 
						|
		val = adin_get_reg_value(phydev, "adi,rx-internal-delay-ps",
 | 
						|
					 adin_rgmii_delays,
 | 
						|
					 ADIN1300_RGMII_2_00_NS);
 | 
						|
		reg_val &= ~ADIN1300_GE_RGMII_RX_MSK;
 | 
						|
		reg_val |= ADIN1300_GE_RGMII_RX_SEL(val);
 | 
						|
	} else {
 | 
						|
		reg_val &= ~ADIN1300_GE_RGMII_RXID_EN;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Enable / Disable RGMII RX Delay */
 | 
						|
	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
 | 
						|
	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
 | 
						|
		reg_val |= ADIN1300_GE_RGMII_TXID_EN;
 | 
						|
 | 
						|
		val = adin_get_reg_value(phydev, "adi,tx-internal-delay-ps",
 | 
						|
					 adin_rgmii_delays,
 | 
						|
					 ADIN1300_RGMII_2_00_NS);
 | 
						|
		reg_val &= ~ADIN1300_GE_RGMII_GTX_MSK;
 | 
						|
		reg_val |= ADIN1300_GE_RGMII_GTX_SEL(val);
 | 
						|
	} else {
 | 
						|
		reg_val &= ~ADIN1300_GE_RGMII_TXID_EN;
 | 
						|
	}
 | 
						|
 | 
						|
	return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val);
 | 
						|
}
 | 
						|
 | 
						|
static int adin1300_config(struct phy_device *phydev)
 | 
						|
{
 | 
						|
	int ret;
 | 
						|
 | 
						|
	printf("ADIN1300 PHY detected at addr %d\n", phydev->addr);
 | 
						|
 | 
						|
	ret = adin_config_clk_out(phydev);
 | 
						|
	if (ret < 0)
 | 
						|
		return ret;
 | 
						|
 | 
						|
	ret = adin_config_rgmii_mode(phydev);
 | 
						|
 | 
						|
	if (ret < 0)
 | 
						|
		return ret;
 | 
						|
 | 
						|
	return genphy_config(phydev);
 | 
						|
}
 | 
						|
 | 
						|
static struct phy_driver ADIN1300_driver =  {
 | 
						|
	.name = "ADIN1300",
 | 
						|
	.uid = PHY_ID_ADIN1300,
 | 
						|
	.mask = 0xffffffff,
 | 
						|
	.features = PHY_GBIT_FEATURES,
 | 
						|
	.config = adin1300_config,
 | 
						|
	.startup = genphy_startup,
 | 
						|
	.shutdown = genphy_shutdown,
 | 
						|
};
 | 
						|
 | 
						|
int phy_adin_init(void)
 | 
						|
{
 | 
						|
	phy_register(&ADIN1300_driver);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 |