blob: 3718878e3994a445493890de3cb93b237bd2feb8 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* X-Powers AC200 Ethernet PHY package backend
*
* Copyright (c) 2022 Arm Ltd. (Andre Przywara <andre.przywara@arm.com>)
* Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include "acx00.h"
#define AC200_EPHY_BPS_EFFUSE_OFFSET 3
#define AC200_EPHY_CLK_RATE_24_MHZ 24000000
#define AC200_EPHY_CLK_RATE_27_MHZ 27000000
#define AC200_SYS_EPHY_CTL0_REG 0x0014
#define AC200_EPHY_RESET_DEASSERT BIT(0)
#define AC200_EPHY_SYSCLK_ENABLE BIT(1)
#define AC200_SYS_EPHY_CTL1_REG 0x0016
#define AC200_EPHY_MII_IO_ENABLE BIT(0)
/* AC200-internal copy of the Ethernet PHY calibration eFuse. */
#define AC200_EFUSE_EPHY_REG 0x8004
#define AC200_EPHY_CTL_REG 0x6000
#define AC200_EPHY_SHUTDOWN BIT(0)
#define AC200_EPHY_CLK_SEL_24_MHZ BIT(2)
#define AC200_EPHY_PHY_ADDR_MASK GENMASK(8, 4)
#define AC200_EPHY_RMII_SEL BIT(11)
#define AC200_EPHY_BPS_EFFUSE_MASK GENMASK(15, 12)
struct ac200_ephy_ctl {
struct acx00_ephy_control control;
struct regmap *regmap;
struct regulator *vcc;
struct device *dev;
u16 ephy_ctl;
unsigned int phy_addr;
phy_interface_t interface;
bool supply_enabled;
bool powered;
};
static u16 ac200_ephy_ctl_config(const struct ac200_ephy_ctl *priv)
{
return priv->ephy_ctl |
(priv->interface == PHY_INTERFACE_MODE_RMII ?
AC200_EPHY_RMII_SEL : 0) |
FIELD_PREP(AC200_EPHY_PHY_ADDR_MASK, priv->phy_addr);
}
static int ac200_ephy_ctl_write(struct ac200_ephy_ctl *priv,
unsigned int reg, u16 value)
{
int ret;
ret = regmap_write(priv->regmap, reg, value);
if (ret)
dev_err(priv->dev, "failed to write register %#x: %pe\n",
reg, ERR_PTR(ret));
return ret;
}
static int ac200_ephy_ctl_disable(struct ac200_ephy_ctl *priv)
{
int err;
int ret = 0;
if (priv->powered) {
ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,
ac200_ephy_ctl_config(priv) |
AC200_EPHY_SHUTDOWN);
err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, 0);
if (!ret)
ret = err;
err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);
if (!ret)
ret = err;
priv->powered = false;
}
if (priv->supply_enabled) {
err = regulator_disable(priv->vcc);
if (!err)
priv->supply_enabled = false;
else if (!ret)
ret = err;
}
return ret;
}
static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)
{
struct ac200_ephy_ctl *priv =
container_of(control, struct ac200_ephy_ctl, control);
return ac200_ephy_ctl_disable(priv);
}
static int
ac200_ephy_ctl_set_interface(struct acx00_ephy_control *control,
phy_interface_t interface)
{
struct ac200_ephy_ctl *priv =
container_of(control, struct ac200_ephy_ctl, control);
int ret = 0;
if (interface != PHY_INTERFACE_MODE_MII &&
interface != PHY_INTERFACE_MODE_RMII)
return -EINVAL;
if (priv->interface == interface)
return 0;
if (priv->powered)
ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG,
AC200_EPHY_RMII_SEL,
interface == PHY_INTERFACE_MODE_RMII ?
AC200_EPHY_RMII_SEL : 0);
if (!ret)
priv->interface = interface;
return ret;
}
static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,
unsigned int phy_addr)
{
struct ac200_ephy_ctl *priv =
container_of(control, struct ac200_ephy_ctl, control);
u16 ephy_ctl;
int ret;
if (phy_addr > FIELD_MAX(AC200_EPHY_PHY_ADDR_MASK))
return -EINVAL;
if (priv->powered && priv->phy_addr == phy_addr)
return 0;
if (priv->powered) {
ret = ac200_ephy_ctl_disable(priv);
if (ret)
return ret;
}
priv->phy_addr = phy_addr;
if (!priv->supply_enabled) {
ret = regulator_enable(priv->vcc);
if (ret)
return ret;
priv->supply_enabled = true;
}
ephy_ctl = ac200_ephy_ctl_config(priv);
/*
* Start from a disabled state before applying the configuration. AC200
* specifies no additional delay between these controls. The companion
* MFD provider has completed its input-clock startup delay before
* publishing the regmap.
*/
ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);
if (ret)
goto err_disable;
ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG,
AC200_EPHY_MII_IO_ENABLE);
if (ret)
goto err_disable;
ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,
ephy_ctl | AC200_EPHY_SHUTDOWN);
if (ret)
goto err_disable;
ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG,
AC200_EPHY_RESET_DEASSERT |
AC200_EPHY_SYSCLK_ENABLE);
if (ret)
goto err_disable;
ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, ephy_ctl);
if (ret)
goto err_disable;
priv->powered = true;
return 0;
err_disable:
/* Attempt every step of the shutdown sequence after a partial start. */
priv->powered = true;
ac200_ephy_ctl_disable(priv);
return ret;
}
struct acx00_ephy_control *
ac200_ephy_ctl_create(struct phy_device *phydev,
struct device_node *package_node,
bool has_calibration, u8 calibration)
{
struct device *dev = &phydev->mdio.dev;
unsigned int internal_calibration;
struct device_node *ac200_node;
struct ac200_ephy_ctl *priv;
struct i2c_client *client;
unsigned long clk_rate;
u8 bps_effuse_code;
struct clk *clk;
int ret;
priv = devm_kzalloc_obj(dev, *priv);
if (!priv)
return ERR_PTR(-ENOMEM);
priv->dev = dev;
priv->vcc = devm_of_regulator_get(dev, package_node, "vcc");
if (IS_ERR(priv->vcc))
return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->vcc),
"failed to get package supply\n"));
ac200_node = of_parse_phandle(package_node, "x-powers,ac200", 0);
if (!ac200_node)
return ERR_PTR(dev_err_probe(dev, -EINVAL,
"missing x-powers,ac200 reference\n"));
client = of_find_i2c_device_by_node(ac200_node);
of_node_put(ac200_node);
if (!client) {
ret = IS_ENABLED(CONFIG_I2C) ? -EPROBE_DEFER : -ENODEV;
return ERR_PTR(dev_err_probe(dev, ret,
"AC200 device is not registered\n"));
}
if (!device_link_add(dev, &client->dev,
DL_FLAG_AUTOREMOVE_CONSUMER)) {
ret = dev_err_probe(dev, -EINVAL,
"failed to link AC200 device\n");
goto out_put_client;
}
device_lock(&client->dev);
if (device_is_bound(&client->dev))
priv->regmap = dev_get_regmap(&client->dev, NULL);
device_unlock(&client->dev);
if (!priv->regmap) {
ret = dev_err_probe(dev, -EPROBE_DEFER,
"AC200 driver is not ready\n");
goto out_put_client;
}
if (!has_calibration) {
ret = regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG,
&internal_calibration);
if (ret)
goto out_error;
calibration = internal_calibration;
}
/* The vendor driver supplies no transfer function beyond this offset. */
bps_effuse_code = (calibration + AC200_EPHY_BPS_EFFUSE_OFFSET) &
FIELD_MAX(AC200_EPHY_BPS_EFFUSE_MASK);
priv->ephy_ctl =
FIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code);
/* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
clk = clk_get(&client->dev, NULL);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
goto out_error;
}
/* The bound AC200 provider enables and exclusively pins this rate. */
clk_rate = clk_get_rate(clk);
clk_put(clk);
switch (clk_rate) {
case AC200_EPHY_CLK_RATE_24_MHZ:
priv->ephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ;
break;
case AC200_EPHY_CLK_RATE_27_MHZ:
break;
default:
ret = dev_err_probe(dev, -EINVAL,
"unsupported AC200 clock rate %lu Hz\n",
clk_rate);
goto out_put_client;
}
priv->control.power_on = ac200_ephy_ctl_power_on;
priv->control.power_off = ac200_ephy_ctl_power_off;
priv->control.set_interface = ac200_ephy_ctl_set_interface;
/* MII is the reset default used until the MAC supplies its interface. */
priv->interface = PHY_INTERFACE_MODE_MII;
put_device(&client->dev);
return &priv->control;
out_error:
ret = dev_err_probe(dev, ret, "failed to initialize AC200 control\n");
out_put_client:
put_device(&client->dev);
return ERR_PTR(ret);
}