--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2026 NXP */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+#include <linux/slab.h>
+
+#define DS125DF111_NUM_CH 2
+#define DS125DF111_NUM_VCO_GROUP_REG 5
+
+#define DS125DF111_CH_SELECT 0xff
+#define DS125DF111_CH_SELECT_TARGET_MASK GENMASK(3, 0)
+#define DS125DF111_CH_SELECT_EN BIT(2)
+
+#define DS125DF111_CH_CTRL 0x00
+#define DS125DF111_CH_CTRL_RESET BIT(2) /* self clearing */
+
+#define DS125DF111_CH_RST_SLEEP_US 10
+#define DS125DF111_CH_RST_TIMEOUT_US 10000
+
+#define DS125DF111_VCO_GROUP_BASE 0x60
+
+#define DS125DF111_RATIOS 0x2f
+#define DS125DF111_RATIOS_RATE_MASK GENMASK(7, 6)
+#define DS125DF111_RATIOS_SUBRATE_MASK GENMASK(5, 4)
+#define DS125DF111_RATIOS_MASK GENMASK(7, 4)
+
+struct ds125df111_ch {
+ struct phy *phy;
+ struct ds125df111_priv *priv;
+ int idx;
+};
+
+struct ds125df111_priv {
+ struct ds125df111_ch ch[DS125DF111_NUM_CH];
+ struct i2c_client *client;
+ struct mutex mutex; /* protects access to shared registers */
+};
+
+enum ds125df111_mode {
+ FREQ_1G,
+ FREQ_10G,
+};
+
+static const struct ds125df111_config {
+ u8 vco_group[DS125DF111_NUM_VCO_GROUP_REG];
+ u8 rate;
+ u8 subrate;
+} ds125df111_cfg[] = {
+ [FREQ_1G] = {
+ /* VCO group #0 = 10GHz, VCO group #1 = 10GHz */
+ .vco_group = {0x00, 0xB2, 0x00, 0xB2, 0xCC},
+ /* By using the following combination of rate and subrate we
+ * select divide ratios of 1, 2, 4, 8 on both groups
+ */
+ .rate = 0x1,
+ .subrate = 0x2,
+ },
+
+ [FREQ_10G] = {
+ /* VCO group #0 = 10.3125GHz, VCO group #1 = 10.3125GHz */
+ .vco_group = {0x90, 0xB3, 0x90, 0xB3, 0xCD},
+ /* By using the following combination of rate and subrate we
+ * select divide ratios of 1 on both groups
+ */
+ .rate = 0x1,
+ .subrate = 0x3,
+ },
+};
+
+static int ds125df111_rmw(struct ds125df111_priv *priv, u8 reg, u8 clr, u8 set)
+{
+ struct i2c_client *i2c = priv->client;
+ int err;
+ u8 val;
+
+ err = i2c_smbus_read_byte_data(i2c, reg);
+ if (err < 0)
+ return err;
+
+ val = (u8)err;
+ val &= ~clr;
+ val |= set;
+
+ err = i2c_smbus_write_byte_data(i2c, reg, val);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int ds125df111_configure(struct phy *phy,
+ const struct ds125df111_config *cfg)
+{
+ struct ds125df111_ch *ch = phy_get_drvdata(phy);
+ struct ds125df111_priv *priv = ch->priv;
+ struct i2c_client *i2c = priv->client;
+ struct device *dev = &phy->dev;
+ u8 ratios_val;
+ int err, i;
+ int val;
+
+ mutex_lock(&priv->mutex);
+
+ /* Make sure that any subsequent read/write operation will be directed
+ * only to the registers of the selected channel
+ */
+ err = ds125df111_rmw(priv, DS125DF111_CH_SELECT,
+ DS125DF111_CH_SELECT_TARGET_MASK,
+ DS125DF111_CH_SELECT_EN | ch->idx);
+ if (err < 0) {
+ dev_err(dev, "Unable to select channel: %pe\n", ERR_PTR(err));
+ goto out;
+ }
+
+ /* Reset channel registers and wait until the bit was cleared */
+ err = ds125df111_rmw(priv, DS125DF111_CH_CTRL, 0,
+ DS125DF111_CH_CTRL_RESET);
+ if (err < 0) {
+ dev_err(dev, "Error resetting channel configuration: %pe\n",
+ ERR_PTR(err));
+ goto out;
+ }
+
+ err = read_poll_timeout(i2c_smbus_read_byte_data, val,
+ val < 0 || !(val & DS125DF111_CH_CTRL_RESET),
+ DS125DF111_CH_RST_SLEEP_US,
+ DS125DF111_CH_RST_TIMEOUT_US, false, i2c,
+ DS125DF111_CH_CTRL);
+ if (err) {
+ dev_err(dev, "Timed out waiting for channel reset: %pe\n",
+ ERR_PTR(err));
+ goto out;
+ }
+
+ if (val < 0) {
+ dev_err(dev, "Error reading reset status: %pe\n", ERR_PTR(val));
+ err = val;
+ goto out;
+ }
+
+ /* Program the VCO group frequencies */
+ for (i = 0; i < DS125DF111_NUM_VCO_GROUP_REG; i++) {
+ err = i2c_smbus_write_byte_data(i2c,
+ DS125DF111_VCO_GROUP_BASE + i,
+ cfg->vco_group[i]);
+ if (err < 0) {
+ dev_err(dev, "Error programming VCO group: %pe\n",
+ ERR_PTR(err));
+ goto out;
+ }
+ }
+
+ /* Set the divide ratios for the VCO groups */
+ ratios_val = FIELD_PREP(DS125DF111_RATIOS_RATE_MASK, cfg->rate) |
+ FIELD_PREP(DS125DF111_RATIOS_SUBRATE_MASK, cfg->subrate);
+ err = ds125df111_rmw(priv, DS125DF111_RATIOS, DS125DF111_RATIOS_MASK,
+ ratios_val);
+ if (err < 0) {
+ dev_err(dev, "Error programming the divide ratios: %pe\n",
+ ERR_PTR(err));
+ goto out;
+ }
+
+out:
+ mutex_unlock(&priv->mutex);
+
+ return err;
+}
+
+static int ds125df111_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ const struct ds125df111_config *cfg;
+
+ if (mode != PHY_MODE_ETHERNET)
+ return -EINVAL;
+
+ switch (submode) {
+ case PHY_INTERFACE_MODE_10GBASER:
+ cfg = &ds125df111_cfg[FREQ_10G];
+ break;
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
+ cfg = &ds125df111_cfg[FREQ_1G];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ds125df111_configure(phy, cfg);
+}
+
+static int ds125df111_validate(struct phy *phy, enum phy_mode mode, int submode,
+ union phy_configure_opts *opts __always_unused)
+{
+ if (mode != PHY_MODE_ETHERNET)
+ return -EINVAL;
+
+ switch (submode) {
+ case PHY_INTERFACE_MODE_10GBASER:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct phy_ops ds125df111_ops = {
+ .validate = ds125df111_validate,
+ .set_mode = ds125df111_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *ds125df111_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct ds125df111_priv *priv = dev_get_drvdata(dev);
+ u32 idx;
+
+ if (args->args_count != 1)
+ return ERR_PTR(-EINVAL);
+
+ idx = args->args[0];
+ if (idx >= DS125DF111_NUM_CH) {
+ dev_err(dev, "Maximum number of channels is %d\n",
+ DS125DF111_NUM_CH);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return priv->ch[idx].phy;
+}
+
+static int ds125df111_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct phy_provider *provider;
+ struct ds125df111_priv *priv;
+ int i, err;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->client = client;
+ err = devm_mutex_init(dev, &priv->mutex);
+ if (err)
+ return err;
+
+ i2c_set_clientdata(client, priv);
+
+ for (i = 0; i < DS125DF111_NUM_CH; i++) {
+ struct ds125df111_ch *ch = &priv->ch[i];
+ struct phy *phy;
+
+ phy = devm_phy_create(dev, NULL, &ds125df111_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ ch->idx = i;
+ ch->priv = priv;
+ ch->phy = phy;
+
+ phy_set_drvdata(phy, ch);
+ }
+
+ provider = devm_of_phy_provider_register(dev, ds125df111_xlate);
+
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct of_device_id ds125df111_dt_ids[] = {
+ { .compatible = "ti,ds125df111", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ds125df111_dt_ids);
+
+static struct i2c_driver ds125df111_driver = {
+ .driver = {
+ .name = "ds125df111",
+ .of_match_table = ds125df111_dt_ids,
+ },
+ .probe = ds125df111_probe,
+};
+module_i2c_driver(ds125df111_driver);
+
+MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
+MODULE_DESCRIPTION("TI DS125DF111 Retimer driver");
+MODULE_LICENSE("GPL");