295 lines
6.8 KiB
C
295 lines
6.8 KiB
C
// 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");
|