diff --git a/MAINTAINERS b/MAINTAINERS index 8ae7578a0532..e00b43bd3444 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26684,6 +26684,13 @@ T: git git://linuxtv.org/mhadli/v4l-dvb-davinci_devices.git F: drivers/media/platform/ti/davinci/ F: include/media/davinci/ +TI DS125DF111 RETIMER PHY DRIVER +M: Ioana Ciornei +L: linux-phy@lists.infradead.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/phy/ti,ds125df111.yaml +F: drivers/phy/ti/phy-ds125df111.c + TI ENHANCED CAPTURE (eCAP) DRIVER M: Vignesh Raghavendra R: Julien Panis diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig index dbe65500f20c..b52c7eabe24c 100644 --- a/drivers/phy/ti/Kconfig +++ b/drivers/phy/ti/Kconfig @@ -73,6 +73,16 @@ config PHY_J721E_WIZ three clock selects (pll0, pll1, dig) and resets for each of the lanes. +config PHY_TI_DS125DF111 + tristate "TI DS125DF111 2-Channel Retimer Driver" + depends on OF && I2C + select GENERIC_PHY + help + Enable this to add support for configuration and runtime management + of the TI DS125DF111 Multi-Protocol 2-Channel Retimer. + The retimer is modeled as a Generic PHY and supports both 10G and 1G + link speeds. + config PHY_TI_GMII_SEL tristate select GENERIC_PHY diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile index 975fb8448ba5..a002ef8764a2 100644 --- a/drivers/phy/ti/Makefile +++ b/drivers/phy/ti/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_PHY_AM654_SERDES) += phy-am654-serdes.o obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o obj-$(CONFIG_PHY_J721E_WIZ) += phy-j721e-wiz.o +obj-$(CONFIG_PHY_TI_DS125DF111) += phy-ds125df111.o obj-$(CONFIG_PHY_TI_GMII_SEL) += phy-gmii-sel.o obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o diff --git a/drivers/phy/ti/phy-ds125df111.c b/drivers/phy/ti/phy-ds125df111.c new file mode 100644 index 000000000000..84ff96d7d589 --- /dev/null +++ b/drivers/phy/ti/phy-ds125df111.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2026 NXP */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("TI DS125DF111 Retimer driver"); +MODULE_LICENSE("GPL");