| // 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"); |