[RFC PATCH v2 net-next 01/15] phy: introduce phy_get_status() and use it to report CDR lock

From: Vladimir Oltean
Date: Sat Sep 23 2023 - 09:49:37 EST


Some modules, like the MTIP AN/LT block used as a copper backplane PHY
driver, need this extra information from the SerDes PHY as another
source of "link up" information.

Namely, the 25GBase-R PCS does not have a MDIO_CTRL1_LPOWER bit
implemented in its MDIO_MMD_PCS:MDIO_CTRL1 register. That bit is
typically set from phy_suspend() or phylink_pcs_disable() implementations,
and that is supposed to cause a link drop event on the link partner.
But here it does not happen.

By implementing the networking phylink_pcs_disable() as phy_power_off(),
we are able to actually power down the lane in a way that is visible to
the remote end. Where it is visible is the CDR lock, so we introduce
PHY_STATUS_TYPE_CDR_LOCK as an extra link indication, we are able to
detect that condition and signal it to upper layers of the network
stack.

A more high-level and generic phy_get_status() operation was chosen
instead of the more specific phy_get_cdr_lock() alternative, because I
saw this as being more in the spirit of the generic PHY API.
Also, phy_get_status() is more extensible and reusable for other
purposes as well.

Signed-off-by: Vladimir Oltean <vladimir.oltean@xxxxxxx>
---
v1->v2: reimplement phy_check_cdr_lock() as something more generic

drivers/phy/phy-core.c | 31 ++++++++++++++++++++++++++
include/linux/phy/phy.h | 49 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 80 insertions(+)

diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c
index 96a0b1e111f3..3b7e04a59a00 100644
--- a/drivers/phy/phy-core.c
+++ b/drivers/phy/phy-core.c
@@ -553,6 +553,37 @@ int phy_validate(struct phy *phy, enum phy_mode mode, int submode,
}
EXPORT_SYMBOL_GPL(phy_validate);

+/**
+ * phy_get_status() - Query various parameters of a PHY
+ * @phy: the phy returned by phy_get()
+ * @type: type of the status being queried
+ * @opts: pointer to union of status structures, determined by type
+ *
+ * phy_init() must have been called on the phy. The status is relative to the
+ * current phy mode, that can be changed using phy_set_mode(). Not all status
+ * types may be relevant to all phy modes.
+ *
+ * Return: %0 if successful, a negative error code otherwise
+ */
+int phy_get_status(struct phy *phy, enum phy_status_type type,
+ union phy_status_opts *opts)
+{
+ int ret;
+
+ if (!phy)
+ return -EINVAL;
+
+ if (!phy->ops->get_status)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&phy->mutex);
+ ret = phy->ops->get_status(phy, type, opts);
+ mutex_unlock(&phy->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_get_status);
+
/**
* _of_phy_get() - lookup and obtain a reference to a phy by phandle
* @np: device_node for which to get the phy
diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h
index f6d607ef0e80..6be348f1fa0e 100644
--- a/include/linux/phy/phy.h
+++ b/include/linux/phy/phy.h
@@ -51,6 +51,29 @@ enum phy_media {
PHY_MEDIA_DAC,
};

+enum phy_status_type {
+ /* Valid for PHY_MODE_ETHERNET */
+ PHY_STATUS_CDR_LOCK,
+};
+
+/* If the CDR (Clock and Data Recovery) block is able to lock onto the RX bit
+ * stream, it means that the stream contains valid bit transitions for the
+ * configured protocol. This indicates that a link partner is physically
+ * present and powered on.
+ */
+struct phy_status_opts_cdr {
+ bool cdr_locked;
+};
+
+/**
+ * union phy_status_opts - Opaque generic phy status
+ *
+ * @cdr: Configuration set applicable for PHY_STATUS_CDR_LOCK.
+ */
+union phy_status_opts {
+ struct phy_status_opts_cdr cdr;
+};
+
/**
* union phy_configure_opts - Opaque generic phy configuration
*
@@ -78,6 +101,7 @@ union phy_configure_opts {
* @set_speed: set the speed of the phy (optional)
* @reset: resetting the phy
* @calibrate: calibrate the phy
+ * @get_status: get the mode-specific status of the phy
* @release: ops to be performed while the consumer relinquishes the PHY
* @owner: the module owner containing the ops
*/
@@ -122,6 +146,20 @@ struct phy_ops {
union phy_configure_opts *opts);
int (*reset)(struct phy *phy);
int (*calibrate)(struct phy *phy);
+
+ /**
+ * @get_status:
+ *
+ * Optional.
+ *
+ * Used to query the mode-specific status of the phy. Must have no side
+ * effects.
+ *
+ * Returns: 0 if the operation was successful, negative error code
+ * otherwise.
+ */
+ int (*get_status)(struct phy *phy, enum phy_status_type type,
+ union phy_status_opts *opts);
void (*release)(struct phy *phy);
struct module *owner;
};
@@ -236,6 +274,8 @@ int phy_set_speed(struct phy *phy, int speed);
int phy_configure(struct phy *phy, union phy_configure_opts *opts);
int phy_validate(struct phy *phy, enum phy_mode mode, int submode,
union phy_configure_opts *opts);
+int phy_get_status(struct phy *phy, enum phy_status_type type,
+ union phy_status_opts *opts);

static inline enum phy_mode phy_get_mode(struct phy *phy)
{
@@ -414,6 +454,15 @@ static inline int phy_validate(struct phy *phy, enum phy_mode mode, int submode,
return -ENOSYS;
}

+static inline int phy_get_status(struct phy *phy, enum phy_status_type type,
+ union phy_status_opts *opts)
+{
+ if (!phy)
+ return 0;
+
+ return -ENOSYS;
+}
+
static inline int phy_get_bus_width(struct phy *phy)
{
return -ENOSYS;
--
2.34.1