162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for Microchip 10BASE-T1S PHYs
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Support: Microchip Phys:
662306a36Sopenharmony_ci *  lan8670/1/2 Rev.B1
762306a36Sopenharmony_ci *  lan8650/1 Rev.B0 Internal PHYs
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/phy.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define PHY_ID_LAN867X_REVB1 0x0007C162
1562306a36Sopenharmony_ci#define PHY_ID_LAN865X_REVB0 0x0007C1B3
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define LAN867X_REG_STS2 0x0019
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define LAN867x_RESET_COMPLETE_STS BIT(11)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define LAN865X_REG_CFGPARAM_ADDR 0x00D8
2262306a36Sopenharmony_ci#define LAN865X_REG_CFGPARAM_DATA 0x00D9
2362306a36Sopenharmony_ci#define LAN865X_REG_CFGPARAM_CTRL 0x00DA
2462306a36Sopenharmony_ci#define LAN865X_REG_STS2 0x0019
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define LAN865X_CFGPARAM_READ_ENABLE BIT(1)
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci/* The arrays below are pulled from the following table from AN1699
2962306a36Sopenharmony_ci * Access MMD Address Value Mask
3062306a36Sopenharmony_ci * RMW 0x1F 0x00D0 0x0002 0x0E03
3162306a36Sopenharmony_ci * RMW 0x1F 0x00D1 0x0000 0x0300
3262306a36Sopenharmony_ci * RMW 0x1F 0x0084 0x3380 0xFFC0
3362306a36Sopenharmony_ci * RMW 0x1F 0x0085 0x0006 0x000F
3462306a36Sopenharmony_ci * RMW 0x1F 0x008A 0xC000 0xF800
3562306a36Sopenharmony_ci * RMW 0x1F 0x0087 0x801C 0x801C
3662306a36Sopenharmony_ci * RMW 0x1F 0x0088 0x033F 0x1FFF
3762306a36Sopenharmony_ci * W   0x1F 0x008B 0x0404 ------
3862306a36Sopenharmony_ci * RMW 0x1F 0x0080 0x0600 0x0600
3962306a36Sopenharmony_ci * RMW 0x1F 0x00F1 0x2400 0x7F00
4062306a36Sopenharmony_ci * RMW 0x1F 0x0096 0x2000 0x2000
4162306a36Sopenharmony_ci * W   0x1F 0x0099 0x7F80 ------
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic const u32 lan867x_revb1_fixup_registers[12] = {
4562306a36Sopenharmony_ci	0x00D0, 0x00D1, 0x0084, 0x0085,
4662306a36Sopenharmony_ci	0x008A, 0x0087, 0x0088, 0x008B,
4762306a36Sopenharmony_ci	0x0080, 0x00F1, 0x0096, 0x0099,
4862306a36Sopenharmony_ci};
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic const u16 lan867x_revb1_fixup_values[12] = {
5162306a36Sopenharmony_ci	0x0002, 0x0000, 0x3380, 0x0006,
5262306a36Sopenharmony_ci	0xC000, 0x801C, 0x033F, 0x0404,
5362306a36Sopenharmony_ci	0x0600, 0x2400, 0x2000, 0x7F80,
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic const u16 lan867x_revb1_fixup_masks[12] = {
5762306a36Sopenharmony_ci	0x0E03, 0x0300, 0xFFC0, 0x000F,
5862306a36Sopenharmony_ci	0xF800, 0x801C, 0x1FFF, 0xFFFF,
5962306a36Sopenharmony_ci	0x0600, 0x7F00, 0x2000, 0xFFFF,
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/* LAN865x Rev.B0 configuration parameters from AN1760 */
6362306a36Sopenharmony_cistatic const u32 lan865x_revb0_fixup_registers[28] = {
6462306a36Sopenharmony_ci	0x0091, 0x0081, 0x0043, 0x0044,
6562306a36Sopenharmony_ci	0x0045, 0x0053, 0x0054, 0x0055,
6662306a36Sopenharmony_ci	0x0040, 0x0050, 0x00D0, 0x00E9,
6762306a36Sopenharmony_ci	0x00F5, 0x00F4, 0x00F8, 0x00F9,
6862306a36Sopenharmony_ci	0x00B0, 0x00B1, 0x00B2, 0x00B3,
6962306a36Sopenharmony_ci	0x00B4, 0x00B5, 0x00B6, 0x00B7,
7062306a36Sopenharmony_ci	0x00B8, 0x00B9, 0x00BA, 0x00BB,
7162306a36Sopenharmony_ci};
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic const u16 lan865x_revb0_fixup_values[28] = {
7462306a36Sopenharmony_ci	0x9660, 0x00C0, 0x00FF, 0xFFFF,
7562306a36Sopenharmony_ci	0x0000, 0x00FF, 0xFFFF, 0x0000,
7662306a36Sopenharmony_ci	0x0002, 0x0002, 0x5F21, 0x9E50,
7762306a36Sopenharmony_ci	0x1CF8, 0xC020, 0x9B00, 0x4E53,
7862306a36Sopenharmony_ci	0x0103, 0x0910, 0x1D26, 0x002A,
7962306a36Sopenharmony_ci	0x0103, 0x070D, 0x1720, 0x0027,
8062306a36Sopenharmony_ci	0x0509, 0x0E13, 0x1C25, 0x002B,
8162306a36Sopenharmony_ci};
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic const u16 lan865x_revb0_fixup_cfg_regs[5] = {
8462306a36Sopenharmony_ci	0x0084, 0x008A, 0x00AD, 0x00AE, 0x00AF
8562306a36Sopenharmony_ci};
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci/* Pulled from AN1760 describing 'indirect read'
8862306a36Sopenharmony_ci *
8962306a36Sopenharmony_ci * write_register(0x4, 0x00D8, addr)
9062306a36Sopenharmony_ci * write_register(0x4, 0x00DA, 0x2)
9162306a36Sopenharmony_ci * return (int8)(read_register(0x4, 0x00D9))
9262306a36Sopenharmony_ci *
9362306a36Sopenharmony_ci * 0x4 refers to memory map selector 4, which maps to MDIO_MMD_VEND2
9462306a36Sopenharmony_ci */
9562306a36Sopenharmony_cistatic int lan865x_revb0_indirect_read(struct phy_device *phydev, u16 addr)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	int ret;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_ADDR,
10062306a36Sopenharmony_ci			    addr);
10162306a36Sopenharmony_ci	if (ret)
10262306a36Sopenharmony_ci		return ret;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_CTRL,
10562306a36Sopenharmony_ci			    LAN865X_CFGPARAM_READ_ENABLE);
10662306a36Sopenharmony_ci	if (ret)
10762306a36Sopenharmony_ci		return ret;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	return phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_DATA);
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci/* This is pulled straight from AN1760 from 'calculation of offset 1' &
11362306a36Sopenharmony_ci * 'calculation of offset 2'
11462306a36Sopenharmony_ci */
11562306a36Sopenharmony_cistatic int lan865x_generate_cfg_offsets(struct phy_device *phydev, s8 offsets[2])
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	const u16 fixup_regs[2] = {0x0004, 0x0008};
11862306a36Sopenharmony_ci	int ret;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	for (int i = 0; i < ARRAY_SIZE(fixup_regs); i++) {
12162306a36Sopenharmony_ci		ret = lan865x_revb0_indirect_read(phydev, fixup_regs[i]);
12262306a36Sopenharmony_ci		if (ret < 0)
12362306a36Sopenharmony_ci			return ret;
12462306a36Sopenharmony_ci		if (ret & BIT(4))
12562306a36Sopenharmony_ci			offsets[i] = ret | 0xE0;
12662306a36Sopenharmony_ci		else
12762306a36Sopenharmony_ci			offsets[i] = ret;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return 0;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic int lan865x_read_cfg_params(struct phy_device *phydev, u16 cfg_params[])
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	int ret;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) {
13862306a36Sopenharmony_ci		ret = phy_read_mmd(phydev, MDIO_MMD_VEND2,
13962306a36Sopenharmony_ci				   lan865x_revb0_fixup_cfg_regs[i]);
14062306a36Sopenharmony_ci		if (ret < 0)
14162306a36Sopenharmony_ci			return ret;
14262306a36Sopenharmony_ci		cfg_params[i] = (u16)ret;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic int lan865x_write_cfg_params(struct phy_device *phydev, u16 cfg_params[])
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	int ret;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) {
15362306a36Sopenharmony_ci		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
15462306a36Sopenharmony_ci				    lan865x_revb0_fixup_cfg_regs[i],
15562306a36Sopenharmony_ci				    cfg_params[i]);
15662306a36Sopenharmony_ci		if (ret)
15762306a36Sopenharmony_ci			return ret;
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	return 0;
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic int lan865x_setup_cfgparam(struct phy_device *phydev)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	u16 cfg_params[ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs)];
16662306a36Sopenharmony_ci	u16 cfg_results[5];
16762306a36Sopenharmony_ci	s8 offsets[2];
16862306a36Sopenharmony_ci	int ret;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	ret = lan865x_generate_cfg_offsets(phydev, offsets);
17162306a36Sopenharmony_ci	if (ret)
17262306a36Sopenharmony_ci		return ret;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	ret = lan865x_read_cfg_params(phydev, cfg_params);
17562306a36Sopenharmony_ci	if (ret)
17662306a36Sopenharmony_ci		return ret;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	cfg_results[0] = (cfg_params[0] & 0x000F) |
17962306a36Sopenharmony_ci			  FIELD_PREP(GENMASK(15, 10), 9 + offsets[0]) |
18062306a36Sopenharmony_ci			  FIELD_PREP(GENMASK(15, 4), 14 + offsets[0]);
18162306a36Sopenharmony_ci	cfg_results[1] = (cfg_params[1] & 0x03FF) |
18262306a36Sopenharmony_ci			  FIELD_PREP(GENMASK(15, 10), 40 + offsets[1]);
18362306a36Sopenharmony_ci	cfg_results[2] = (cfg_params[2] & 0xC0C0) |
18462306a36Sopenharmony_ci			  FIELD_PREP(GENMASK(15, 8), 5 + offsets[0]) |
18562306a36Sopenharmony_ci			  (9 + offsets[0]);
18662306a36Sopenharmony_ci	cfg_results[3] = (cfg_params[3] & 0xC0C0) |
18762306a36Sopenharmony_ci			  FIELD_PREP(GENMASK(15, 8), 9 + offsets[0]) |
18862306a36Sopenharmony_ci			  (14 + offsets[0]);
18962306a36Sopenharmony_ci	cfg_results[4] = (cfg_params[4] & 0xC0C0) |
19062306a36Sopenharmony_ci			  FIELD_PREP(GENMASK(15, 8), 17 + offsets[0]) |
19162306a36Sopenharmony_ci			  (22 + offsets[0]);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return lan865x_write_cfg_params(phydev, cfg_results);
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic int lan865x_revb0_config_init(struct phy_device *phydev)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	int ret;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	/* Reference to AN1760
20162306a36Sopenharmony_ci	 * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8650-1-Configuration-60001760.pdf
20262306a36Sopenharmony_ci	 */
20362306a36Sopenharmony_ci	for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_registers); i++) {
20462306a36Sopenharmony_ci		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
20562306a36Sopenharmony_ci				    lan865x_revb0_fixup_registers[i],
20662306a36Sopenharmony_ci				    lan865x_revb0_fixup_values[i]);
20762306a36Sopenharmony_ci		if (ret)
20862306a36Sopenharmony_ci			return ret;
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci	/* Function to calculate and write the configuration parameters in the
21162306a36Sopenharmony_ci	 * 0x0084, 0x008A, 0x00AD, 0x00AE and 0x00AF registers (from AN1760)
21262306a36Sopenharmony_ci	 */
21362306a36Sopenharmony_ci	return lan865x_setup_cfgparam(phydev);
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic int lan867x_revb1_config_init(struct phy_device *phydev)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	int err;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* The chip completes a reset in 3us, we might get here earlier than
22162306a36Sopenharmony_ci	 * that, as an added margin we'll conditionally sleep 5us.
22262306a36Sopenharmony_ci	 */
22362306a36Sopenharmony_ci	err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2);
22462306a36Sopenharmony_ci	if (err < 0)
22562306a36Sopenharmony_ci		return err;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (!(err & LAN867x_RESET_COMPLETE_STS)) {
22862306a36Sopenharmony_ci		udelay(5);
22962306a36Sopenharmony_ci		err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2);
23062306a36Sopenharmony_ci		if (err < 0)
23162306a36Sopenharmony_ci			return err;
23262306a36Sopenharmony_ci		if (!(err & LAN867x_RESET_COMPLETE_STS)) {
23362306a36Sopenharmony_ci			phydev_err(phydev, "PHY reset failed\n");
23462306a36Sopenharmony_ci			return -ENODEV;
23562306a36Sopenharmony_ci		}
23662306a36Sopenharmony_ci	}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	/* Reference to AN1699
23962306a36Sopenharmony_ci	 * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8670-1-2-config-60001699.pdf
24062306a36Sopenharmony_ci	 * AN1699 says Read, Modify, Write, but the Write is not required if the
24162306a36Sopenharmony_ci	 * register already has the required value. So it is safe to use
24262306a36Sopenharmony_ci	 * phy_modify_mmd here.
24362306a36Sopenharmony_ci	 */
24462306a36Sopenharmony_ci	for (int i = 0; i < ARRAY_SIZE(lan867x_revb1_fixup_registers); i++) {
24562306a36Sopenharmony_ci		err = phy_modify_mmd(phydev, MDIO_MMD_VEND2,
24662306a36Sopenharmony_ci				     lan867x_revb1_fixup_registers[i],
24762306a36Sopenharmony_ci				     lan867x_revb1_fixup_masks[i],
24862306a36Sopenharmony_ci				     lan867x_revb1_fixup_values[i]);
24962306a36Sopenharmony_ci		if (err)
25062306a36Sopenharmony_ci			return err;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return 0;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic int lan86xx_read_status(struct phy_device *phydev)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	/* The phy has some limitations, namely:
25962306a36Sopenharmony_ci	 *  - always reports link up
26062306a36Sopenharmony_ci	 *  - only supports 10MBit half duplex
26162306a36Sopenharmony_ci	 *  - does not support auto negotiate
26262306a36Sopenharmony_ci	 */
26362306a36Sopenharmony_ci	phydev->link = 1;
26462306a36Sopenharmony_ci	phydev->duplex = DUPLEX_HALF;
26562306a36Sopenharmony_ci	phydev->speed = SPEED_10;
26662306a36Sopenharmony_ci	phydev->autoneg = AUTONEG_DISABLE;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return 0;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cistatic struct phy_driver microchip_t1s_driver[] = {
27262306a36Sopenharmony_ci	{
27362306a36Sopenharmony_ci		PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1),
27462306a36Sopenharmony_ci		.name               = "LAN867X Rev.B1",
27562306a36Sopenharmony_ci		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
27662306a36Sopenharmony_ci		.config_init        = lan867x_revb1_config_init,
27762306a36Sopenharmony_ci		.read_status        = lan86xx_read_status,
27862306a36Sopenharmony_ci		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
27962306a36Sopenharmony_ci		.set_plca_cfg	    = genphy_c45_plca_set_cfg,
28062306a36Sopenharmony_ci		.get_plca_status    = genphy_c45_plca_get_status,
28162306a36Sopenharmony_ci	},
28262306a36Sopenharmony_ci	{
28362306a36Sopenharmony_ci		PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0),
28462306a36Sopenharmony_ci		.name               = "LAN865X Rev.B0 Internal Phy",
28562306a36Sopenharmony_ci		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
28662306a36Sopenharmony_ci		.config_init        = lan865x_revb0_config_init,
28762306a36Sopenharmony_ci		.read_status        = lan86xx_read_status,
28862306a36Sopenharmony_ci		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
28962306a36Sopenharmony_ci		.set_plca_cfg	    = genphy_c45_plca_set_cfg,
29062306a36Sopenharmony_ci		.get_plca_status    = genphy_c45_plca_get_status,
29162306a36Sopenharmony_ci	},
29262306a36Sopenharmony_ci};
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cimodule_phy_driver(microchip_t1s_driver);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused tbl[] = {
29762306a36Sopenharmony_ci	{ PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) },
29862306a36Sopenharmony_ci	{ PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0) },
29962306a36Sopenharmony_ci	{ }
30062306a36Sopenharmony_ci};
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, tbl);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ciMODULE_DESCRIPTION("Microchip 10BASE-T1S PHYs driver");
30562306a36Sopenharmony_ciMODULE_AUTHOR("Ramón Nordin Rodriguez");
30662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
307