summaryrefslogtreecommitdiff
path: root/drivers/net/phy/xpowers/ac200.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy/xpowers/ac200.c')
-rw-r--r--drivers/net/phy/xpowers/ac200.c314
1 files changed, 314 insertions, 0 deletions
diff --git a/drivers/net/phy/xpowers/ac200.c b/drivers/net/phy/xpowers/ac200.c
new file mode 100644
index 000000000000..afdd2a3dfbe4
--- /dev/null
+++ b/drivers/net/phy/xpowers/ac200.c
@@ -0,0 +1,314 @@
+// 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(dev, sizeof(*priv), GFP_KERNEL);
+ 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);
+}