162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Marvell 88E6xxx System Management Interface (SMI) support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2008 Marvell Semiconductor
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (c) 2019 Vivien Didelot <vivien.didelot@gmail.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "chip.h"
1162306a36Sopenharmony_ci#include "smi.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci/* The switch ADDR[4:1] configuration pins define the chip SMI device address
1462306a36Sopenharmony_ci * (ADDR[0] is always zero, thus only even SMI addresses can be strapped).
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it
1762306a36Sopenharmony_ci * is the only device connected to the SMI master. In this mode it responds to
1862306a36Sopenharmony_ci * all 32 possible SMI addresses, and thus maps directly the internal devices.
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing
2162306a36Sopenharmony_ci * multiple devices to share the SMI interface. In this mode it responds to only
2262306a36Sopenharmony_ci * 2 registers, used to indirectly access the internal SMI devices.
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci * Some chips use a different scheme: Only the ADDR4 pin is used for
2562306a36Sopenharmony_ci * configuration, and the device responds to 16 of the 32 SMI
2662306a36Sopenharmony_ci * addresses, allowing two to coexist on the same SMI interface.
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic int mv88e6xxx_smi_direct_read(struct mv88e6xxx_chip *chip,
3062306a36Sopenharmony_ci				     int dev, int reg, u16 *data)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	int ret;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	ret = mdiobus_read_nested(chip->bus, dev, reg);
3562306a36Sopenharmony_ci	if (ret < 0)
3662306a36Sopenharmony_ci		return ret;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	*data = ret & 0xffff;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	return 0;
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic int mv88e6xxx_smi_direct_write(struct mv88e6xxx_chip *chip,
4462306a36Sopenharmony_ci				      int dev, int reg, u16 data)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	int ret;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	ret = mdiobus_write_nested(chip->bus, dev, reg, data);
4962306a36Sopenharmony_ci	if (ret < 0)
5062306a36Sopenharmony_ci		return ret;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	return 0;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int mv88e6xxx_smi_direct_wait(struct mv88e6xxx_chip *chip,
5662306a36Sopenharmony_ci				     int dev, int reg, int bit, int val)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	const unsigned long timeout = jiffies + msecs_to_jiffies(50);
5962306a36Sopenharmony_ci	u16 data;
6062306a36Sopenharmony_ci	int err;
6162306a36Sopenharmony_ci	int i;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	/* Even if the initial poll takes longer than 50ms, always do
6462306a36Sopenharmony_ci	 * at least one more attempt.
6562306a36Sopenharmony_ci	 */
6662306a36Sopenharmony_ci	for (i = 0; time_before(jiffies, timeout) || (i < 2); i++) {
6762306a36Sopenharmony_ci		err = mv88e6xxx_smi_direct_read(chip, dev, reg, &data);
6862306a36Sopenharmony_ci		if (err)
6962306a36Sopenharmony_ci			return err;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		if (!!(data & BIT(bit)) == !!val)
7262306a36Sopenharmony_ci			return 0;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci		if (i < 2)
7562306a36Sopenharmony_ci			cpu_relax();
7662306a36Sopenharmony_ci		else
7762306a36Sopenharmony_ci			usleep_range(1000, 2000);
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return -ETIMEDOUT;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic const struct mv88e6xxx_bus_ops mv88e6xxx_smi_direct_ops = {
8462306a36Sopenharmony_ci	.read = mv88e6xxx_smi_direct_read,
8562306a36Sopenharmony_ci	.write = mv88e6xxx_smi_direct_write,
8662306a36Sopenharmony_ci};
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int mv88e6xxx_smi_dual_direct_read(struct mv88e6xxx_chip *chip,
8962306a36Sopenharmony_ci					  int dev, int reg, u16 *data)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	return mv88e6xxx_smi_direct_read(chip, chip->sw_addr + dev, reg, data);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic int mv88e6xxx_smi_dual_direct_write(struct mv88e6xxx_chip *chip,
9562306a36Sopenharmony_ci					   int dev, int reg, u16 data)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	return mv88e6xxx_smi_direct_write(chip, chip->sw_addr + dev, reg, data);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic const struct mv88e6xxx_bus_ops mv88e6xxx_smi_dual_direct_ops = {
10162306a36Sopenharmony_ci	.read = mv88e6xxx_smi_dual_direct_read,
10262306a36Sopenharmony_ci	.write = mv88e6xxx_smi_dual_direct_write,
10362306a36Sopenharmony_ci};
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/* Offset 0x00: SMI Command Register
10662306a36Sopenharmony_ci * Offset 0x01: SMI Data Register
10762306a36Sopenharmony_ci */
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic int mv88e6xxx_smi_indirect_read(struct mv88e6xxx_chip *chip,
11062306a36Sopenharmony_ci				       int dev, int reg, u16 *data)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	int err;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr,
11562306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD,
11662306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD_BUSY |
11762306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD_MODE_22 |
11862306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD_OP_22_READ |
11962306a36Sopenharmony_ci					 (dev << 5) | reg);
12062306a36Sopenharmony_ci	if (err)
12162306a36Sopenharmony_ci		return err;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	err = mv88e6xxx_smi_direct_wait(chip, chip->sw_addr,
12462306a36Sopenharmony_ci					MV88E6XXX_SMI_CMD, 15, 0);
12562306a36Sopenharmony_ci	if (err)
12662306a36Sopenharmony_ci		return err;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return mv88e6xxx_smi_direct_read(chip, chip->sw_addr,
12962306a36Sopenharmony_ci					 MV88E6XXX_SMI_DATA, data);
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic int mv88e6xxx_smi_indirect_write(struct mv88e6xxx_chip *chip,
13362306a36Sopenharmony_ci					int dev, int reg, u16 data)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	int err;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr,
13862306a36Sopenharmony_ci					 MV88E6XXX_SMI_DATA, data);
13962306a36Sopenharmony_ci	if (err)
14062306a36Sopenharmony_ci		return err;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr,
14362306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD,
14462306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD_BUSY |
14562306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD_MODE_22 |
14662306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD_OP_22_WRITE |
14762306a36Sopenharmony_ci					 (dev << 5) | reg);
14862306a36Sopenharmony_ci	if (err)
14962306a36Sopenharmony_ci		return err;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return mv88e6xxx_smi_direct_wait(chip, chip->sw_addr,
15262306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD, 15, 0);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic int mv88e6xxx_smi_indirect_init(struct mv88e6xxx_chip *chip)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	/* Ensure that the chip starts out in the ready state. As both
15862306a36Sopenharmony_ci	 * reads and writes always ensure this on return, they can
15962306a36Sopenharmony_ci	 * safely depend on the chip not being busy on entry.
16062306a36Sopenharmony_ci	 */
16162306a36Sopenharmony_ci	return mv88e6xxx_smi_direct_wait(chip, chip->sw_addr,
16262306a36Sopenharmony_ci					 MV88E6XXX_SMI_CMD, 15, 0);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic const struct mv88e6xxx_bus_ops mv88e6xxx_smi_indirect_ops = {
16662306a36Sopenharmony_ci	.read = mv88e6xxx_smi_indirect_read,
16762306a36Sopenharmony_ci	.write = mv88e6xxx_smi_indirect_write,
16862306a36Sopenharmony_ci	.init = mv88e6xxx_smi_indirect_init,
16962306a36Sopenharmony_ci};
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ciint mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip,
17262306a36Sopenharmony_ci		       struct mii_bus *bus, int sw_addr)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	if (chip->info->dual_chip)
17562306a36Sopenharmony_ci		chip->smi_ops = &mv88e6xxx_smi_dual_direct_ops;
17662306a36Sopenharmony_ci	else if (sw_addr == 0)
17762306a36Sopenharmony_ci		chip->smi_ops = &mv88e6xxx_smi_direct_ops;
17862306a36Sopenharmony_ci	else if (chip->info->multi_chip)
17962306a36Sopenharmony_ci		chip->smi_ops = &mv88e6xxx_smi_indirect_ops;
18062306a36Sopenharmony_ci	else
18162306a36Sopenharmony_ci		return -EINVAL;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	chip->bus = bus;
18462306a36Sopenharmony_ci	chip->sw_addr = sw_addr;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (chip->smi_ops->init)
18762306a36Sopenharmony_ci		return chip->smi_ops->init(chip);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
191