summaryrefslogtreecommitdiffstats
path: root/drivers/net/pse-pd
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/net/pse-pd
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/net/pse-pd')
-rw-r--r--drivers/net/pse-pd/Kconfig83
-rw-r--r--drivers/net/pse-pd/Makefile12
-rw-r--r--drivers/net/pse-pd/pd692x0.c1889
-rw-r--r--drivers/net/pse-pd/pse_core.c1983
-rw-r--r--drivers/net/pse-pd/pse_regulator.c155
-rw-r--r--drivers/net/pse-pd/realtek-pse-mcu-core.c998
-rw-r--r--drivers/net/pse-pd/realtek-pse-mcu-i2c.c148
-rw-r--r--drivers/net/pse-pd/realtek-pse-mcu-uart.c164
-rw-r--r--drivers/net/pse-pd/realtek-pse-mcu.h94
-rw-r--r--drivers/net/pse-pd/si3474.c578
-rw-r--r--drivers/net/pse-pd/tps23881.c1558
11 files changed, 7662 insertions, 0 deletions
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
new file mode 100644
index 000000000..a0f2ae668
--- /dev/null
+++ b/drivers/net/pse-pd/Kconfig
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Ethernet Power Sourcing Equipment drivers
+#
+
+menuconfig PSE_CONTROLLER
+ bool "Ethernet Power Sourcing Equipment Support"
+ depends on REGULATOR
+ help
+ Generic Power Sourcing Equipment Controller support.
+
+ If unsure, say no.
+
+if PSE_CONTROLLER
+
+config PSE_REALTEK_MCU
+ tristate
+ help
+ Shared core for the Realtek PSE MCU driver. This is selected
+ automatically by the transport options below.
+
+config PSE_REALTEK_MCU_I2C
+ tristate "Realtek PSE MCU driver (I2C transport)"
+ depends on I2C
+ select PSE_REALTEK_MCU
+ help
+ Driver for the microcontroller (MCU) that fronts the PSE
+ hardware on various Realtek-based managed switches, attached
+ via I2C/SMBus. The MCU exposes a message-based protocol; the actual
+ PSE silicon is not accessed directly. To compile this driver as a
+ module, choose M here: the module will be called realtek-pse-mcu-i2c.
+
+config PSE_REALTEK_MCU_UART
+ tristate "Realtek PSE MCU driver (UART transport)"
+ depends on SERIAL_DEV_BUS
+ select PSE_REALTEK_MCU
+ help
+ Driver for the microcontroller (MCU) that fronts the PSE
+ hardware on various Realtek-based managed switches, attached
+ via UART. The MCU exposes a message-based protocol; the actual PSE
+ silicon is not accessed directly. To compile this driver as a
+ module, choose M here: the module will be called realtek-pse-mcu-uart.
+
+config PSE_REGULATOR
+ tristate "Regulator based PSE controller"
+ help
+ This module provides support for simple regulator based Ethernet Power
+ Sourcing Equipment without automatic classification support. For
+ example for basic implementation of PoDL (802.3bu) specification.
+
+config PSE_PD692X0
+ tristate "PD692X0 PSE controller"
+ depends on I2C
+ select FW_LOADER
+ select FW_UPLOAD
+ help
+ This module provides support for PD692x0 regulator based Ethernet
+ Power Sourcing Equipment.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pd692x0.
+
+config PSE_SI3474
+ tristate "Si3474 PSE controller"
+ depends on I2C
+ help
+ This module provides support for Si3474 regulator based Ethernet
+ Power Sourcing Equipment.
+ Only 4-pair PSE configurations are supported.
+
+ To compile this driver as a module, choose M here: the
+ module will be called si3474.
+
+config PSE_TPS23881
+ tristate "TPS23881 PSE controller"
+ depends on I2C
+ help
+ This module provides support for TPS23881 regulator based Ethernet
+ Power Sourcing Equipment.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tps23881.
+endif
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
new file mode 100644
index 000000000..9cca5900f
--- /dev/null
+++ b/drivers/net/pse-pd/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Makefile for Linux PSE drivers
+
+obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
+
+obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o
+obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o
+obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o
+obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
+obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
+obj-$(CONFIG_PSE_SI3474) += si3474.o
+obj-$(CONFIG_PSE_TPS23881) += tps23881.o
diff --git a/drivers/net/pse-pd/pd692x0.c b/drivers/net/pse-pd/pd692x0.c
new file mode 100644
index 000000000..209de9cec
--- /dev/null
+++ b/drivers/net/pse-pd/pd692x0.c
@@ -0,0 +1,1889 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Microchip PD692X0 PoE PSE Controller driver (I2C bus)
+ *
+ * Copyright (c) 2023 Bootlin, Kory Maincent <kory.maincent@bootlin.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+
+#define PD692X0_PSE_NAME "pd692x0_pse"
+
+#define PD692X0_MAX_PIS 48
+#define PD692X0_MAX_MANAGERS 12
+#define PD692X0_MAX_MANAGER_PORTS 8
+#define PD692X0_MAX_HW_PORTS (PD692X0_MAX_MANAGERS * PD692X0_MAX_MANAGER_PORTS)
+
+#define PD69200_BT_PROD_VER 24
+#define PD69210_BT_PROD_VER 26
+#define PD69220_BT_PROD_VER 29
+
+#define PD692X0_FW_MAJ_VER 3
+#define PD692X0_FW_MIN_VER 5
+#define PD692X0_FW_PATCH_VER 5
+
+#define PD692X0_USER_BYTE 42
+
+enum pd692x0_fw_state {
+ PD692X0_FW_UNKNOWN,
+ PD692X0_FW_OK,
+ PD692X0_FW_BROKEN,
+ PD692X0_FW_NEED_UPDATE,
+ PD692X0_FW_PREPARE,
+ PD692X0_FW_WRITE,
+ PD692X0_FW_COMPLETE,
+};
+
+struct pd692x0_msg {
+ u8 key;
+ u8 echo;
+ u8 sub[3];
+ u8 data[8];
+ __be16 chksum;
+} __packed;
+
+struct pd692x0_msg_ver {
+ u8 prod;
+ u8 maj_sw_ver;
+ u8 min_sw_ver;
+ u8 pa_sw_ver;
+ u8 param;
+ u8 build;
+};
+
+enum {
+ PD692X0_KEY_CMD,
+ PD692X0_KEY_PRG,
+ PD692X0_KEY_REQ,
+ PD692X0_KEY_TLM,
+ PD692X0_KEY_TEST,
+ PD692X0_KEY_REPORT = 0x52
+};
+
+enum {
+ PD692X0_MSG_RESET,
+ PD692X0_MSG_GET_SYS_STATUS,
+ PD692X0_MSG_GET_SW_VER,
+ PD692X0_MSG_SET_TMP_PORT_MATRIX,
+ PD692X0_MSG_PRG_PORT_MATRIX,
+ PD692X0_MSG_SET_PORT_PARAM,
+ PD692X0_MSG_GET_PORT_STATUS,
+ PD692X0_MSG_DOWNLOAD_CMD,
+ PD692X0_MSG_GET_PORT_CLASS,
+ PD692X0_MSG_GET_PORT_MEAS,
+ PD692X0_MSG_GET_PORT_PARAM,
+ PD692X0_MSG_GET_POWER_BANK,
+ PD692X0_MSG_SET_POWER_BANK,
+ PD692X0_MSG_SET_USER_BYTE,
+
+ /* add new message above here */
+ PD692X0_MSG_CNT
+};
+
+struct pd692x0_matrix {
+ u8 hw_port_a;
+ u8 hw_port_b;
+};
+
+struct pd692x0_priv {
+ struct i2c_client *client;
+ struct pse_controller_dev pcdev;
+ struct device_node *np;
+
+ enum pd692x0_fw_state fw_state;
+ struct fw_upload *fwl;
+ bool cancel_request;
+
+ u8 msg_id;
+ bool last_cmd_key;
+ unsigned long last_cmd_key_time;
+
+ bool cfg_saved;
+ enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
+ struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
+ int manager_pw_budget[PD692X0_MAX_MANAGERS];
+ int nmanagers;
+ struct pd692x0_matrix *port_matrix;
+};
+
+/* Template list of communication messages. The non-null bytes defined here
+ * constitute the fixed portion of the messages. The remaining bytes will
+ * be configured later within the functions. Refer to the "PD692x0 BT Serial
+ * Communication Protocol User Guide" for comprehensive details on messages
+ * content.
+ */
+static const struct pd692x0_msg pd692x0_msg_template_list[PD692X0_MSG_CNT] = {
+ [PD692X0_MSG_RESET] = {
+ .key = PD692X0_KEY_CMD,
+ .sub = {0x07, 0x55, 0x00},
+ .data = {0x55, 0x00, 0x55, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_GET_SYS_STATUS] = {
+ .key = PD692X0_KEY_REQ,
+ .sub = {0x07, 0xd0, 0x4e},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_GET_SW_VER] = {
+ .key = PD692X0_KEY_REQ,
+ .sub = {0x07, 0x1e, 0x21},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_SET_TMP_PORT_MATRIX] = {
+ .key = PD692X0_KEY_CMD,
+ .sub = {0x05, 0x43},
+ .data = { 0, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_PRG_PORT_MATRIX] = {
+ .key = PD692X0_KEY_CMD,
+ .sub = {0x07, 0x43, 0x4e},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_SET_PORT_PARAM] = {
+ .key = PD692X0_KEY_CMD,
+ .sub = {0x05, 0xc0},
+ .data = { 0xf, 0xff, 0xff, 0xff,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_GET_PORT_STATUS] = {
+ .key = PD692X0_KEY_REQ,
+ .sub = {0x05, 0xc1},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_DOWNLOAD_CMD] = {
+ .key = PD692X0_KEY_PRG,
+ .sub = {0xff, 0x99, 0x15},
+ .data = {0x16, 0x16, 0x99, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_GET_PORT_CLASS] = {
+ .key = PD692X0_KEY_REQ,
+ .sub = {0x05, 0xc4},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_GET_PORT_MEAS] = {
+ .key = PD692X0_KEY_REQ,
+ .sub = {0x05, 0xc5},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_GET_PORT_PARAM] = {
+ .key = PD692X0_KEY_REQ,
+ .sub = {0x05, 0xc0},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_GET_POWER_BANK] = {
+ .key = PD692X0_KEY_REQ,
+ .sub = {0x07, 0x0b, 0x57},
+ .data = { 0, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+ [PD692X0_MSG_SET_POWER_BANK] = {
+ .key = PD692X0_KEY_CMD,
+ .sub = {0x07, 0x0b, 0x57},
+ },
+ [PD692X0_MSG_SET_USER_BYTE] = {
+ .key = PD692X0_KEY_PRG,
+ .sub = {0x41, PD692X0_USER_BYTE, 0x4e},
+ .data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e},
+ },
+};
+
+static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo)
+{
+ u8 *data = (u8 *)msg;
+ u16 chksum = 0;
+ int i;
+
+ msg->echo = echo++;
+ if (echo == 0xff)
+ echo = 0;
+
+ for (i = 0; i < sizeof(*msg) - sizeof(msg->chksum); i++)
+ chksum += data[i];
+
+ msg->chksum = cpu_to_be16(chksum);
+
+ return echo;
+}
+
+static int pd692x0_send_msg(struct pd692x0_priv *priv, struct pd692x0_msg *msg)
+{
+ const struct i2c_client *client = priv->client;
+ int ret;
+
+ if (msg->key == PD692X0_KEY_CMD && priv->last_cmd_key) {
+ int cmd_msleep;
+
+ cmd_msleep = 30 - jiffies_to_msecs(jiffies - priv->last_cmd_key_time);
+ if (cmd_msleep > 0)
+ msleep(cmd_msleep);
+ }
+
+ /* Add echo and checksum bytes to the message */
+ priv->msg_id = pd692x0_build_msg(msg, priv->msg_id);
+
+ ret = i2c_master_send(client, (u8 *)msg, sizeof(*msg));
+ if (ret != sizeof(*msg))
+ return -EIO;
+
+ return 0;
+}
+
+static int pd692x0_reset(struct pd692x0_priv *priv)
+{
+ const struct i2c_client *client = priv->client;
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_RESET];
+ ret = pd692x0_send_msg(priv, &msg);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to reset the controller (%pe)\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ msleep(30);
+
+ ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
+ if (ret != sizeof(buf))
+ return ret < 0 ? ret : -EIO;
+
+ /* Is the reply a successful report message */
+ if (buf.key != PD692X0_KEY_REPORT || buf.sub[0] || buf.sub[1])
+ return -EIO;
+
+ msleep(300);
+
+ ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
+ if (ret != sizeof(buf))
+ return ret < 0 ? ret : -EIO;
+
+ /* Is the boot status without error */
+ if (buf.key != 0x03 || buf.echo != 0xff || buf.sub[0] & 0x1) {
+ dev_err(&client->dev, "PSE controller error\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static bool pd692x0_try_recv_msg(const struct i2c_client *client,
+ struct pd692x0_msg *msg,
+ struct pd692x0_msg *buf)
+{
+ /* Wait 30ms before readback as mandated by the protocol */
+ msleep(30);
+
+ memset(buf, 0, sizeof(*buf));
+ i2c_master_recv(client, (u8 *)buf, sizeof(*buf));
+ if (buf->key)
+ return 0;
+
+ msleep(100);
+
+ memset(buf, 0, sizeof(*buf));
+ i2c_master_recv(client, (u8 *)buf, sizeof(*buf));
+ if (buf->key)
+ return 0;
+
+ return 1;
+}
+
+/* Implementation of I2C communication, specifically addressing scenarios
+ * involving communication loss. Refer to the "Synchronization During
+ * Communication Loss" section in the Communication Protocol document for
+ * further details.
+ */
+static int pd692x0_recv_msg(struct pd692x0_priv *priv,
+ struct pd692x0_msg *msg,
+ struct pd692x0_msg *buf)
+{
+ const struct i2c_client *client = priv->client;
+ int ret;
+
+ ret = pd692x0_try_recv_msg(client, msg, buf);
+ if (!ret)
+ goto out_success;
+
+ dev_warn(&client->dev,
+ "Communication lost, rtnl is locked until communication is back!");
+
+ ret = pd692x0_send_msg(priv, msg);
+ if (ret)
+ return ret;
+
+ ret = pd692x0_try_recv_msg(client, msg, buf);
+ if (!ret)
+ goto out_success2;
+
+ msleep(10000);
+
+ ret = pd692x0_send_msg(priv, msg);
+ if (ret)
+ return ret;
+
+ ret = pd692x0_try_recv_msg(client, msg, buf);
+ if (!ret)
+ goto out_success2;
+
+ return pd692x0_reset(priv);
+
+out_success2:
+ dev_warn(&client->dev, "Communication is back, rtnl is unlocked!");
+out_success:
+ if (msg->key == PD692X0_KEY_CMD) {
+ priv->last_cmd_key = true;
+ priv->last_cmd_key_time = jiffies;
+ } else {
+ priv->last_cmd_key = false;
+ }
+
+ return 0;
+}
+
+static int pd692x0_sendrecv_msg(struct pd692x0_priv *priv,
+ struct pd692x0_msg *msg,
+ struct pd692x0_msg *buf)
+{
+ struct device *dev = &priv->client->dev;
+ int ret;
+
+ ret = pd692x0_send_msg(priv, msg);
+ if (ret)
+ return ret;
+
+ ret = pd692x0_recv_msg(priv, msg, buf);
+ if (ret)
+ return ret;
+
+ if (msg->echo != buf->echo) {
+ dev_err(dev,
+ "Wrong match in message ID, expect %d received %d.\n",
+ msg->echo, buf->echo);
+ return -EIO;
+ }
+
+ /* If the reply is a report message is it successful */
+ if (buf->key == PD692X0_KEY_REPORT &&
+ (buf->sub[0] || buf->sub[1])) {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static struct pd692x0_priv *to_pd692x0_priv(struct pse_controller_dev *pcdev)
+{
+ return container_of(pcdev, struct pd692x0_priv, pcdev);
+}
+
+static int pd692x0_fw_unavailable(struct pd692x0_priv *priv)
+{
+ switch (priv->fw_state) {
+ case PD692X0_FW_OK:
+ return 0;
+ case PD692X0_FW_PREPARE:
+ case PD692X0_FW_WRITE:
+ case PD692X0_FW_COMPLETE:
+ dev_err(&priv->client->dev, "Firmware update in progress!\n");
+ return -EBUSY;
+ case PD692X0_FW_BROKEN:
+ case PD692X0_FW_NEED_UPDATE:
+ default:
+ dev_err(&priv->client->dev,
+ "Firmware issue. Please update it!\n");
+ return -EOPNOTSUPP;
+ }
+}
+
+static int pd692x0_pi_enable(struct pse_controller_dev *pcdev, int id)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ if (priv->admin_state[id] == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
+ return 0;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
+ msg.data[0] = 0x1;
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+
+ return 0;
+}
+
+static int pd692x0_pi_disable(struct pse_controller_dev *pcdev, int id)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ if (priv->admin_state[id] == ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED)
+ return 0;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
+ msg.data[0] = 0x0;
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+
+ return 0;
+}
+
+struct pd692x0_pse_ext_state_mapping {
+ u32 status_code;
+ enum ethtool_c33_pse_ext_state pse_ext_state;
+ u32 pse_ext_substate;
+};
+
+static const struct pd692x0_pse_ext_state_mapping
+pd692x0_pse_ext_state_map[] = {
+ {0x06, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_HIGH_VOLTAGE},
+ {0x07, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_LOW_VOLTAGE},
+ {0x08, ETHTOOL_C33_PSE_EXT_STATE_MR_PSE_ENABLE,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_PSE_ENABLE_DISABLE_PIN_ACTIVE},
+ {0x0C, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_NON_EXISTING_PORT},
+ {0x11, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNDEFINED_PORT},
+ {0x12, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_INTERNAL_HW_FAULT},
+ {0x1B, ETHTOOL_C33_PSE_EXT_STATE_OPTION_DETECT_TED,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_DETECT_TED_DET_IN_PROCESS},
+ {0x1C, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
+ {0x1E, ETHTOOL_C33_PSE_EXT_STATE_MR_MPS_VALID,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_MPS_VALID_DETECTED_UNDERLOAD},
+ {0x1F, ETHTOOL_C33_PSE_EXT_STATE_OVLD_DETECTED,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_OVLD_DETECTED_OVERLOAD},
+ {0x20, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_BUDGET_EXCEEDED},
+ {0x21, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_INTERNAL_HW_FAULT},
+ {0x22, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_CONFIG_CHANGE},
+ {0x24, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_VOLTAGE_INJECTION},
+ {0x25, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
+ {0x34, ETHTOOL_C33_PSE_EXT_STATE_SHORT_DETECTED,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_SHORT_DETECTED_SHORT_CONDITION},
+ {0x35, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_DETECTED_OVER_TEMP},
+ {0x36, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_DETECTED_OVER_TEMP},
+ {0x37, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
+ {0x3C, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_PORT_PW_LIMIT_EXCEEDS_CONTROLLER_BUDGET},
+ {0x3D, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_PD_REQUEST_EXCEEDS_PORT_LIMIT},
+ {0x41, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_HW_PW_LIMIT},
+ {0x43, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
+ {0xA7, ETHTOOL_C33_PSE_EXT_STATE_OPTION_DETECT_TED,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_DETECT_TED_CONNECTION_CHECK_ERROR},
+ {0xA8, ETHTOOL_C33_PSE_EXT_STATE_MR_MPS_VALID,
+ ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_MPS_VALID_CONNECTION_OPEN},
+ { /* sentinel */ }
+};
+
+static int
+pd692x0_pi_get_ext_state(struct pse_controller_dev *pcdev, int id,
+ struct pse_ext_state_info *ext_state_info)
+{
+ struct ethtool_c33_pse_ext_state_info *c33_ext_state_info;
+ const struct pd692x0_pse_ext_state_mapping *ext_state_map;
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ c33_ext_state_info = &ext_state_info->c33_ext_state_info;
+ ext_state_map = pd692x0_pse_ext_state_map;
+ while (ext_state_map->status_code) {
+ if (ext_state_map->status_code == buf.sub[0]) {
+ c33_ext_state_info->c33_pse_ext_state = ext_state_map->pse_ext_state;
+ c33_ext_state_info->__c33_pse_ext_substate = ext_state_map->pse_ext_substate;
+ return 0;
+ }
+ ext_state_map++;
+ }
+
+ return 0;
+}
+
+struct pd692x0_class_pw {
+ int class;
+ int class_cfg_value;
+ int class_pw;
+ int max_added_class_pw;
+};
+
+#define PD692X0_CLASS_PW_TABLE_SIZE 4
+/* 4/2 pairs class configuration power table in compliance mode.
+ * Need to be arranged in ascending order of power support.
+ */
+static const struct pd692x0_class_pw
+pd692x0_class_pw_table[PD692X0_CLASS_PW_TABLE_SIZE] = {
+ {.class = 3, .class_cfg_value = 0x3, .class_pw = 15000, .max_added_class_pw = 3100},
+ {.class = 4, .class_cfg_value = 0x2, .class_pw = 30000, .max_added_class_pw = 8000},
+ {.class = 6, .class_cfg_value = 0x1, .class_pw = 60000, .max_added_class_pw = 5000},
+ {.class = 8, .class_cfg_value = 0x0, .class_pw = 90000, .max_added_class_pw = 7500},
+};
+
+static int pd692x0_pi_get_pw_from_table(int op_mode, int added_pw)
+{
+ const struct pd692x0_class_pw *pw_table;
+ int i;
+
+ pw_table = pd692x0_class_pw_table;
+ for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
+ if (pw_table->class_cfg_value == op_mode)
+ return pw_table->class_pw + added_pw * 100;
+ }
+
+ return -ERANGE;
+}
+
+static int pd692x0_pi_set_pw_from_table(struct device *dev,
+ struct pd692x0_msg *msg, int pw)
+{
+ const struct pd692x0_class_pw *pw_table;
+ int i;
+
+ pw_table = pd692x0_class_pw_table;
+ if (pw < pw_table->class_pw) {
+ dev_err(dev,
+ "Power limit %dmW not supported. Ranges minimal available: [%d-%d]\n",
+ pw,
+ pw_table->class_pw,
+ pw_table->class_pw + pw_table->max_added_class_pw);
+ return -ERANGE;
+ }
+
+ for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
+ if (pw > (pw_table->class_pw + pw_table->max_added_class_pw))
+ continue;
+
+ if (pw < pw_table->class_pw) {
+ dev_err(dev,
+ "Power limit %dmW not supported. Ranges available: [%d-%d] or [%d-%d]\n",
+ pw,
+ (pw_table - 1)->class_pw,
+ (pw_table - 1)->class_pw + (pw_table - 1)->max_added_class_pw,
+ pw_table->class_pw,
+ pw_table->class_pw + pw_table->max_added_class_pw);
+ return -ERANGE;
+ }
+
+ msg->data[2] = pw_table->class_cfg_value;
+ msg->data[3] = (pw - pw_table->class_pw) / 100;
+ return 0;
+ }
+
+ pw_table--;
+ dev_warn(dev,
+ "Power limit %dmW not supported. Set to highest power limit %dmW\n",
+ pw, pw_table->class_pw + pw_table->max_added_class_pw);
+ msg->data[2] = pw_table->class_cfg_value;
+ msg->data[3] = pw_table->max_added_class_pw / 100;
+ return 0;
+}
+
+static int
+pd692x0_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_limit_ranges *pw_limit_ranges)
+{
+ struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
+ const struct pd692x0_class_pw *pw_table;
+ int i;
+
+ pw_table = pd692x0_class_pw_table;
+ c33_pw_limit_ranges = kzalloc_objs(*c33_pw_limit_ranges,
+ PD692X0_CLASS_PW_TABLE_SIZE);
+ if (!c33_pw_limit_ranges)
+ return -ENOMEM;
+
+ for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
+ c33_pw_limit_ranges[i].min = pw_table->class_pw;
+ c33_pw_limit_ranges[i].max = pw_table->class_pw +
+ pw_table->max_added_class_pw;
+ }
+
+ pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
+ return i;
+}
+
+static int
+pd692x0_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ struct pse_admin_state *admin_state)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ if (buf.sub[1])
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+ else
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+
+ priv->admin_state[id] = admin_state->c33_admin_state;
+
+ return 0;
+}
+
+static int
+pd692x0_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_status *pw_status)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ /* Compare Port Status (Communication Protocol Document par. 7.1) */
+ if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90)
+ pw_status->c33_pw_status =
+ ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+ else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22)
+ pw_status->c33_pw_status =
+ ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+ else if (buf.sub[0] == 0x12)
+ pw_status->c33_pw_status =
+ ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
+ else
+ pw_status->c33_pw_status =
+ ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+
+ return 0;
+}
+
+static int
+pd692x0_pi_get_pw_class(struct pse_controller_dev *pcdev, int id)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ u32 class;
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_CLASS];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ class = buf.data[3] >> 4;
+ if (class <= 8)
+ return class;
+
+ return 0;
+}
+
+static int
+pd692x0_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ return (buf.data[0] << 4 | buf.data[1]) * 100;
+}
+
+static int
+pd692x0_pi_get_prio(struct pse_controller_dev *pcdev, int id)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+ if (!buf.data[2] || buf.data[2] > pcdev->pis_prio_max + 1)
+ return -ERANGE;
+
+ /* PSE core priority start at 0 */
+ return buf.data[2] - 1;
+}
+
+static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
+{
+ struct device *dev = &priv->client->dev;
+ struct pd692x0_msg msg, buf = {0};
+ struct pd692x0_msg_ver ver = {0};
+ int ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_SW_VER];
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get PSE version (%pe)\n", ERR_PTR(ret));
+ return ver;
+ }
+
+ /* Extract version from the message */
+ ver.prod = buf.sub[2];
+ ver.maj_sw_ver = (buf.data[0] << 8 | buf.data[1]) / 100;
+ ver.min_sw_ver = ((buf.data[0] << 8 | buf.data[1]) / 10) % 10;
+ ver.pa_sw_ver = (buf.data[0] << 8 | buf.data[1]) % 10;
+ ver.param = buf.data[2];
+ ver.build = buf.data[3];
+
+ return ver;
+}
+
+struct pd692x0_manager {
+ struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS];
+ struct device_node *node;
+ int nports;
+};
+
+static int
+pd692x0_of_get_ports_manager(struct pd692x0_priv *priv,
+ struct pd692x0_manager *manager,
+ struct device_node *np)
+{
+ struct device_node *node;
+ int ret, nports, i;
+
+ nports = 0;
+ for_each_child_of_node(np, node) {
+ u32 port;
+
+ if (!of_node_name_eq(node, "port"))
+ continue;
+
+ ret = of_property_read_u32(node, "reg", &port);
+ if (ret)
+ goto out;
+
+ if (port >= PD692X0_MAX_MANAGER_PORTS || port != nports) {
+ dev_err(&priv->client->dev,
+ "wrong number or order of manager ports (%d)\n",
+ port);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ of_node_get(node);
+ manager->port_node[port] = node;
+ nports++;
+ }
+
+ manager->nports = nports;
+ return 0;
+
+out:
+ for (i = 0; i < nports; i++) {
+ of_node_put(manager->port_node[i]);
+ manager->port_node[i] = NULL;
+ }
+ of_node_put(node);
+ return ret;
+}
+
+static int
+pd692x0_of_get_managers(struct pd692x0_priv *priv,
+ struct pd692x0_manager *manager)
+{
+ struct device_node *managers_node, *node;
+ int ret, nmanagers, i, j;
+
+ if (!priv->np)
+ return -EINVAL;
+
+ nmanagers = 0;
+ managers_node = of_get_child_by_name(priv->np, "managers");
+ if (!managers_node)
+ return -EINVAL;
+
+ for_each_child_of_node(managers_node, node) {
+ u32 manager_id;
+
+ if (!of_node_name_eq(node, "manager"))
+ continue;
+
+ ret = of_property_read_u32(node, "reg", &manager_id);
+ if (ret)
+ goto out;
+
+ if (manager_id >= PD692X0_MAX_MANAGERS ||
+ manager_id != nmanagers) {
+ dev_err(&priv->client->dev,
+ "wrong number or order of managers (%d)\n",
+ manager_id);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = pd692x0_of_get_ports_manager(priv, &manager[manager_id],
+ node);
+ if (ret)
+ goto out;
+
+ of_node_get(node);
+ manager[manager_id].node = node;
+ nmanagers++;
+ }
+
+ of_node_put(managers_node);
+ priv->nmanagers = nmanagers;
+ return 0;
+
+out:
+ for (i = 0; i < nmanagers; i++) {
+ for (j = 0; j < manager[i].nports; j++) {
+ of_node_put(manager[i].port_node[j]);
+ manager[i].port_node[j] = NULL;
+ }
+ of_node_put(manager[i].node);
+ manager[i].node = NULL;
+ }
+
+ of_node_put(node);
+ of_node_put(managers_node);
+ return ret;
+}
+
+static const struct regulator_ops dummy_ops;
+
+static struct regulator_dev *
+pd692x0_register_manager_regulator(struct device *dev, char *reg_name,
+ struct device_node *node)
+{
+ struct regulator_init_data *rinit_data;
+ struct regulator_config rconfig = {0};
+ struct regulator_desc *rdesc;
+ struct regulator_dev *rdev;
+
+ rinit_data = devm_kzalloc(dev, sizeof(*rinit_data),
+ GFP_KERNEL);
+ if (!rinit_data)
+ return ERR_PTR(-ENOMEM);
+
+ rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL);
+ if (!rdesc)
+ return ERR_PTR(-ENOMEM);
+
+ rdesc->name = reg_name;
+ rdesc->type = REGULATOR_VOLTAGE;
+ rdesc->ops = &dummy_ops;
+ rdesc->owner = THIS_MODULE;
+
+ rinit_data->supply_regulator = "vmain";
+
+ rconfig.dev = dev;
+ rconfig.init_data = rinit_data;
+ rconfig.of_node = node;
+
+ rdev = devm_regulator_register(dev, rdesc, &rconfig);
+ if (IS_ERR(rdev)) {
+ dev_err_probe(dev, PTR_ERR(rdev),
+ "Failed to register regulator\n");
+ return rdev;
+ }
+
+ return rdev;
+}
+
+static int
+pd692x0_register_managers_regulator(struct pd692x0_priv *priv,
+ const struct pd692x0_manager *manager)
+{
+ struct device *dev = &priv->client->dev;
+ size_t reg_name_len;
+ int i;
+
+ /* Each regulator name len is dev name + 12 char +
+ * int max digit number (10) + 1
+ */
+ reg_name_len = strlen(dev_name(dev)) + 23;
+
+ for (i = 0; i < priv->nmanagers; i++) {
+ static const char * const regulators[] = { "vaux5", "vaux3p3" };
+ struct regulator_dev *rdev;
+ char *reg_name;
+ int ret;
+
+ reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL);
+ if (!reg_name)
+ return -ENOMEM;
+ snprintf(reg_name, 26, "pse-%s-manager%d", dev_name(dev), i);
+ rdev = pd692x0_register_manager_regulator(dev, reg_name,
+ manager[i].node);
+ if (IS_ERR(rdev))
+ return PTR_ERR(rdev);
+
+ /* VMAIN is described as main supply for the manager.
+ * Add other VAUX power supplies and link them to the
+ * virtual device rdev->dev.
+ */
+ ret = devm_regulator_bulk_get_enable(&rdev->dev,
+ ARRAY_SIZE(regulators),
+ regulators);
+ if (ret)
+ return dev_err_probe(&rdev->dev, ret,
+ "Failed to enable regulators\n");
+
+ priv->manager_reg[i] = rdev;
+ }
+
+ return 0;
+}
+
+static int
+pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id)
+{
+ struct pd692x0_msg msg, buf;
+ int ret, pw_mW;
+
+ pw_mW = priv->manager_pw_budget[id] / 1000;
+ if (!pw_mW)
+ return 0;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK];
+ msg.data[0] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_POWER_BANK];
+ msg.data[0] = id;
+ msg.data[1] = pw_mW >> 8;
+ msg.data[2] = pw_mW & 0xff;
+ msg.data[3] = buf.sub[2];
+ msg.data[4] = buf.data[0];
+ msg.data[5] = buf.data[1];
+ msg.data[6] = buf.data[2];
+ msg.data[7] = buf.data[3];
+ return pd692x0_sendrecv_msg(priv, &msg, &buf);
+}
+
+static int
+pd692x0_req_managers_pw_budget(struct pd692x0_priv *priv)
+{
+ int i, ret;
+
+ for (i = 0; i < priv->nmanagers; i++) {
+ struct regulator *supply = priv->manager_reg[i]->supply;
+ int pw_budget;
+
+ pw_budget = regulator_get_unclaimed_power_budget(supply);
+ if (!pw_budget)
+ /* Do nothing if no power budget */
+ continue;
+
+ /* Max power budget per manager */
+ if (pw_budget > 6000000)
+ pw_budget = 6000000;
+ ret = regulator_request_power_budget(supply, pw_budget);
+ if (ret < 0)
+ return ret;
+
+ priv->manager_pw_budget[i] = pw_budget;
+ }
+
+ return 0;
+}
+
+static int
+pd692x0_configure_managers(struct pd692x0_priv *priv)
+{
+ int i, ret;
+
+ for (i = 0; i < priv->nmanagers; i++) {
+ ret = pd692x0_conf_manager_power_budget(priv, i);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+pd692x0_set_port_matrix(const struct pse_pi_pairset *pairset,
+ const struct pd692x0_manager *manager,
+ int nmanagers, struct pd692x0_matrix *port_matrix)
+{
+ int i, j, port_cnt;
+ bool found = false;
+
+ if (!pairset->np)
+ return 0;
+
+ /* Look on every managers */
+ port_cnt = 0;
+ for (i = 0; i < nmanagers; i++) {
+ /* Look on every ports of the manager */
+ for (j = 0; j < manager[i].nports; j++) {
+ if (pairset->np == manager[i].port_node[j]) {
+ found = true;
+ break;
+ }
+ }
+ port_cnt += j;
+
+ if (found)
+ break;
+ }
+
+ if (!found)
+ return -ENODEV;
+
+ if (pairset->pinout == ALTERNATIVE_A)
+ port_matrix->hw_port_a = port_cnt;
+ else if (pairset->pinout == ALTERNATIVE_B)
+ port_matrix->hw_port_b = port_cnt;
+
+ return 0;
+}
+
+static int
+pd692x0_set_ports_matrix(struct pd692x0_priv *priv,
+ const struct pd692x0_manager *manager)
+{
+ struct pd692x0_matrix *port_matrix = priv->port_matrix;
+ struct pse_controller_dev *pcdev = &priv->pcdev;
+ int i, ret;
+
+ /* Init Matrix */
+ for (i = 0; i < PD692X0_MAX_PIS; i++) {
+ port_matrix[i].hw_port_a = 0xff;
+ port_matrix[i].hw_port_b = 0xff;
+ }
+
+ /* Update with values for every PSE PIs */
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[0],
+ manager, priv->nmanagers,
+ &port_matrix[i]);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "unable to configure pi %d pairset 0", i);
+ return ret;
+ }
+
+ ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[1],
+ manager, priv->nmanagers,
+ &port_matrix[i]);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "unable to configure pi %d pairset 1", i);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int
+pd692x0_write_ports_matrix(struct pd692x0_priv *priv)
+{
+ struct pd692x0_matrix *port_matrix = priv->port_matrix;
+ struct pd692x0_msg msg, buf;
+ int ret, i;
+
+ /* Write temporary Matrix */
+ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_TMP_PORT_MATRIX];
+ for (i = 0; i < PD692X0_MAX_PIS; i++) {
+ msg.sub[2] = i;
+ msg.data[0] = port_matrix[i].hw_port_b;
+ msg.data[1] = port_matrix[i].hw_port_a;
+
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Program Matrix */
+ msg = pd692x0_msg_template_list[PD692X0_MSG_PRG_PORT_MATRIX];
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pd692x0_hw_conf_init(struct pd692x0_priv *priv)
+{
+ int ret;
+
+ /* Is PD692x0 ready to be configured? */
+ if (priv->fw_state != PD692X0_FW_OK &&
+ priv->fw_state != PD692X0_FW_COMPLETE)
+ return 0;
+
+ ret = pd692x0_configure_managers(priv);
+ if (ret)
+ return ret;
+
+ ret = pd692x0_write_ports_matrix(priv);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void pd692x0_of_put_managers(struct pd692x0_priv *priv,
+ struct pd692x0_manager *manager)
+{
+ int i, j;
+
+ for (i = 0; i < priv->nmanagers; i++) {
+ for (j = 0; j < manager[i].nports; j++)
+ of_node_put(manager[i].port_node[j]);
+ of_node_put(manager[i].node);
+ }
+}
+
+static void pd692x0_managers_free_pw_budget(struct pd692x0_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < PD692X0_MAX_MANAGERS; i++) {
+ struct regulator *supply;
+
+ if (!priv->manager_reg[i] || !priv->manager_pw_budget[i])
+ continue;
+
+ supply = priv->manager_reg[i]->supply;
+ if (!supply)
+ continue;
+
+ regulator_free_power_budget(supply,
+ priv->manager_pw_budget[i]);
+ }
+}
+
+static int
+pd692x0_save_user_byte(struct pd692x0_priv *priv)
+{
+ struct pd692x0_msg msg, buf;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_USER_BYTE];
+ return pd692x0_sendrecv_msg(priv, &msg, &buf);
+}
+
+static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_matrix *port_matrix;
+ struct pd692x0_manager *manager;
+ int ret;
+
+ manager = kzalloc_objs(*manager, PD692X0_MAX_MANAGERS);
+ if (!manager)
+ return -ENOMEM;
+
+ port_matrix = devm_kcalloc(&priv->client->dev, PD692X0_MAX_PIS,
+ sizeof(*port_matrix), GFP_KERNEL);
+ if (!port_matrix) {
+ ret = -ENOMEM;
+ goto err_free_manager;
+ }
+ priv->port_matrix = port_matrix;
+
+ ret = pd692x0_of_get_managers(priv, manager);
+ if (ret < 0)
+ goto err_free_manager;
+
+ ret = pd692x0_register_managers_regulator(priv, manager);
+ if (ret)
+ goto err_of_managers;
+
+ ret = pd692x0_req_managers_pw_budget(priv);
+ if (ret)
+ goto err_of_managers;
+
+ ret = pd692x0_set_ports_matrix(priv, manager);
+ if (ret)
+ goto err_managers_req_pw;
+
+ /* Do not init the conf if it is already saved */
+ if (!priv->cfg_saved) {
+ ret = pd692x0_hw_conf_init(priv);
+ if (ret)
+ goto err_managers_req_pw;
+
+ ret = pd692x0_save_user_byte(priv);
+ if (ret)
+ goto err_managers_req_pw;
+ }
+
+ pd692x0_of_put_managers(priv, manager);
+ kfree(manager);
+ return 0;
+
+err_managers_req_pw:
+ pd692x0_managers_free_pw_budget(priv);
+err_of_managers:
+ pd692x0_of_put_managers(priv, manager);
+err_free_manager:
+ kfree(manager);
+ return ret;
+}
+
+static int pd692x0_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_MEAS];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ /* Convert 0.1V unit to uV */
+ return (buf.sub[0] << 8 | buf.sub[1]) * 100000;
+}
+
+static int pd692x0_pi_get_pw_limit(struct pse_controller_dev *pcdev,
+ int id)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
+ msg.sub[2] = id;
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0)
+ return ret;
+
+ return pd692x0_pi_get_pw_from_table(buf.data[0], buf.data[1]);
+}
+
+static int pd692x0_pi_set_pw_limit(struct pse_controller_dev *pcdev,
+ int id, int max_mW)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct device *dev = &priv->client->dev;
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
+ msg.sub[2] = id;
+ ret = pd692x0_pi_set_pw_from_table(dev, &msg, max_mW);
+ if (ret)
+ return ret;
+
+ return pd692x0_sendrecv_msg(priv, &msg, &buf);
+}
+
+static int pd692x0_pi_set_prio(struct pse_controller_dev *pcdev, int id,
+ unsigned int prio)
+{
+ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ struct pd692x0_msg msg, buf = {0};
+ int ret;
+
+ ret = pd692x0_fw_unavailable(priv);
+ if (ret)
+ return ret;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
+ msg.sub[2] = id;
+ /* Controller priority from 1 to 3 */
+ msg.data[4] = prio + 1;
+
+ return pd692x0_sendrecv_msg(priv, &msg, &buf);
+}
+
+static const struct pse_controller_ops pd692x0_ops = {
+ .setup_pi_matrix = pd692x0_setup_pi_matrix,
+ .pi_get_admin_state = pd692x0_pi_get_admin_state,
+ .pi_get_pw_status = pd692x0_pi_get_pw_status,
+ .pi_get_ext_state = pd692x0_pi_get_ext_state,
+ .pi_get_pw_class = pd692x0_pi_get_pw_class,
+ .pi_get_actual_pw = pd692x0_pi_get_actual_pw,
+ .pi_enable = pd692x0_pi_enable,
+ .pi_disable = pd692x0_pi_disable,
+ .pi_get_voltage = pd692x0_pi_get_voltage,
+ .pi_get_pw_limit = pd692x0_pi_get_pw_limit,
+ .pi_set_pw_limit = pd692x0_pi_set_pw_limit,
+ .pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges,
+ .pi_get_prio = pd692x0_pi_get_prio,
+ .pi_set_prio = pd692x0_pi_set_prio,
+};
+
+#define PD692X0_FW_LINE_MAX_SZ 0xff
+static int pd692x0_fw_get_next_line(const u8 *data,
+ char *line, size_t size)
+{
+ size_t line_size;
+ int i;
+
+ line_size = min_t(size_t, size, PD692X0_FW_LINE_MAX_SZ);
+
+ memset(line, 0, PD692X0_FW_LINE_MAX_SZ);
+ for (i = 0; i < line_size - 1; i++) {
+ if (*data == '\r' && *(data + 1) == '\n') {
+ line[i] = '\r';
+ line[i + 1] = '\n';
+ return i + 2;
+ }
+ line[i] = *data;
+ data++;
+ }
+
+ return -EIO;
+}
+
+static enum fw_upload_err
+pd692x0_fw_recv_resp(const struct i2c_client *client, unsigned long ms_timeout,
+ const char *msg_ok, unsigned int msg_size)
+{
+ /* Maximum controller response size */
+ char fw_msg_buf[5] = {0};
+ unsigned long timeout;
+ int ret;
+
+ if (msg_size > sizeof(fw_msg_buf))
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ /* Read until we get something */
+ timeout = msecs_to_jiffies(ms_timeout) + jiffies;
+ while (true) {
+ if (time_is_before_jiffies(timeout))
+ return FW_UPLOAD_ERR_TIMEOUT;
+
+ ret = i2c_master_recv(client, fw_msg_buf, 1);
+ if (ret < 0 || *fw_msg_buf == 0) {
+ usleep_range(1000, 2000);
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ /* Read remaining characters */
+ ret = i2c_master_recv(client, fw_msg_buf + 1, msg_size - 1);
+ if (strncmp(fw_msg_buf, msg_ok, msg_size)) {
+ dev_err(&client->dev,
+ "Wrong FW download process answer (%*pE)\n",
+ msg_size, fw_msg_buf);
+ return FW_UPLOAD_ERR_HW_ERROR;
+ }
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static int pd692x0_fw_write_line(const struct i2c_client *client,
+ const char line[PD692X0_FW_LINE_MAX_SZ],
+ const bool last_line)
+{
+ int ret;
+
+ while (*line != 0) {
+ ret = i2c_master_send(client, line, 1);
+ if (ret < 0)
+ return FW_UPLOAD_ERR_RW_ERROR;
+ line++;
+ }
+
+ if (last_line) {
+ ret = pd692x0_fw_recv_resp(client, 100, "TP\r\n",
+ sizeof("TP\r\n") - 1);
+ if (ret)
+ return ret;
+ } else {
+ ret = pd692x0_fw_recv_resp(client, 100, "T*\r\n",
+ sizeof("T*\r\n") - 1);
+ if (ret)
+ return ret;
+ }
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err pd692x0_fw_reset(const struct i2c_client *client)
+{
+ const struct pd692x0_msg zero = {0};
+ struct pd692x0_msg buf = {0};
+ unsigned long timeout;
+ char cmd[] = "RST";
+ int ret;
+
+ ret = i2c_master_send(client, cmd, strlen(cmd));
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to reset the controller (%pe)\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ timeout = msecs_to_jiffies(10000) + jiffies;
+ while (true) {
+ if (time_is_before_jiffies(timeout))
+ return FW_UPLOAD_ERR_TIMEOUT;
+
+ ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
+ if (ret < 0 ||
+ !memcmp(&buf, &zero, sizeof(buf)))
+ usleep_range(1000, 2000);
+ else
+ break;
+ }
+
+ /* Is the reply a successful report message */
+ if (buf.key != PD692X0_KEY_TLM || buf.echo != 0xff ||
+ buf.sub[0] & 0x01) {
+ dev_err(&client->dev, "PSE controller error\n");
+ return FW_UPLOAD_ERR_HW_ERROR;
+ }
+
+ /* Is the firmware operational */
+ if (buf.sub[0] & 0x02) {
+ dev_err(&client->dev,
+ "PSE firmware error. Please update it.\n");
+ return FW_UPLOAD_ERR_HW_ERROR;
+ }
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err pd692x0_fw_prepare(struct fw_upload *fwl,
+ const u8 *data, u32 size)
+{
+ struct pd692x0_priv *priv = fwl->dd_handle;
+ const struct i2c_client *client = priv->client;
+ enum pd692x0_fw_state last_fw_state;
+ int ret;
+
+ priv->cancel_request = false;
+ last_fw_state = priv->fw_state;
+
+ priv->fw_state = PD692X0_FW_PREPARE;
+
+ /* Enter program mode */
+ if (last_fw_state == PD692X0_FW_BROKEN) {
+ const char *msg = "ENTR";
+ const char *c;
+
+ c = msg;
+ do {
+ ret = i2c_master_send(client, c, 1);
+ if (ret < 0)
+ return FW_UPLOAD_ERR_RW_ERROR;
+ if (*(c + 1))
+ usleep_range(10000, 20000);
+ } while (*(++c));
+ } else {
+ struct pd692x0_msg msg, buf;
+
+ msg = pd692x0_msg_template_list[PD692X0_MSG_DOWNLOAD_CMD];
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to enter programming mode (%pe)\n",
+ ERR_PTR(ret));
+ return FW_UPLOAD_ERR_RW_ERROR;
+ }
+ }
+
+ ret = pd692x0_fw_recv_resp(client, 100, "TPE\r\n", sizeof("TPE\r\n") - 1);
+ if (ret)
+ goto err_out;
+
+ if (priv->cancel_request) {
+ ret = FW_UPLOAD_ERR_CANCELED;
+ goto err_out;
+ }
+
+ return FW_UPLOAD_ERR_NONE;
+
+err_out:
+ pd692x0_fw_reset(priv->client);
+ priv->fw_state = last_fw_state;
+ return ret;
+}
+
+static enum fw_upload_err pd692x0_fw_write(struct fw_upload *fwl,
+ const u8 *data, u32 offset,
+ u32 size, u32 *written)
+{
+ struct pd692x0_priv *priv = fwl->dd_handle;
+ char line[PD692X0_FW_LINE_MAX_SZ];
+ const struct i2c_client *client;
+ int ret, i;
+ char cmd;
+
+ client = priv->client;
+ priv->fw_state = PD692X0_FW_WRITE;
+
+ /* Erase */
+ cmd = 'E';
+ ret = i2c_master_send(client, &cmd, 1);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to boot programming mode (%pe)\n",
+ ERR_PTR(ret));
+ return FW_UPLOAD_ERR_RW_ERROR;
+ }
+
+ ret = pd692x0_fw_recv_resp(client, 100, "TOE\r\n", sizeof("TOE\r\n") - 1);
+ if (ret)
+ return ret;
+
+ ret = pd692x0_fw_recv_resp(client, 5000, "TE\r\n", sizeof("TE\r\n") - 1);
+ if (ret)
+ dev_warn(&client->dev,
+ "Failed to erase internal memory, however still try to write Firmware\n");
+
+ ret = pd692x0_fw_recv_resp(client, 100, "TPE\r\n", sizeof("TPE\r\n") - 1);
+ if (ret)
+ dev_warn(&client->dev,
+ "Failed to erase internal memory, however still try to write Firmware\n");
+
+ if (priv->cancel_request)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ /* Program */
+ cmd = 'P';
+ ret = i2c_master_send(client, &cmd, sizeof(char));
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to boot programming mode (%pe)\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ ret = pd692x0_fw_recv_resp(client, 100, "TOP\r\n", sizeof("TOP\r\n") - 1);
+ if (ret)
+ return ret;
+
+ i = 0;
+ while (i < size) {
+ ret = pd692x0_fw_get_next_line(data, line, size - i);
+ if (ret < 0) {
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto err;
+ }
+
+ i += ret;
+ data += ret;
+ if (line[0] == 'S' && line[1] == '0') {
+ continue;
+ } else if (line[0] == 'S' && line[1] == '7') {
+ ret = pd692x0_fw_write_line(client, line, true);
+ if (ret)
+ goto err;
+ } else {
+ ret = pd692x0_fw_write_line(client, line, false);
+ if (ret)
+ goto err;
+ }
+
+ if (priv->cancel_request) {
+ ret = FW_UPLOAD_ERR_CANCELED;
+ goto err;
+ }
+ }
+ *written = i;
+
+ msleep(400);
+
+ return FW_UPLOAD_ERR_NONE;
+
+err:
+ strscpy_pad(line, "S7\r\n", sizeof(line));
+ pd692x0_fw_write_line(client, line, true);
+ return ret;
+}
+
+static enum fw_upload_err pd692x0_fw_poll_complete(struct fw_upload *fwl)
+{
+ struct pd692x0_priv *priv = fwl->dd_handle;
+ const struct i2c_client *client = priv->client;
+ struct pd692x0_msg_ver ver;
+ int ret;
+
+ priv->fw_state = PD692X0_FW_COMPLETE;
+
+ ret = pd692x0_fw_reset(client);
+ if (ret)
+ return ret;
+
+ ver = pd692x0_get_sw_version(priv);
+ if (ver.maj_sw_ver < PD692X0_FW_MAJ_VER) {
+ dev_err(&client->dev,
+ "Too old firmware version. Please update it\n");
+ priv->fw_state = PD692X0_FW_NEED_UPDATE;
+ return FW_UPLOAD_ERR_FW_INVALID;
+ }
+
+ ret = pd692x0_hw_conf_init(priv);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error configuring ports matrix (%pe)\n",
+ ERR_PTR(ret));
+ priv->fw_state = PD692X0_FW_NEED_UPDATE;
+ return FW_UPLOAD_ERR_HW_ERROR;
+ }
+
+ priv->fw_state = PD692X0_FW_OK;
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static void pd692x0_fw_cancel(struct fw_upload *fwl)
+{
+ struct pd692x0_priv *priv = fwl->dd_handle;
+
+ priv->cancel_request = true;
+}
+
+static void pd692x0_fw_cleanup(struct fw_upload *fwl)
+{
+ struct pd692x0_priv *priv = fwl->dd_handle;
+
+ switch (priv->fw_state) {
+ case PD692X0_FW_WRITE:
+ pd692x0_fw_reset(priv->client);
+ fallthrough;
+ case PD692X0_FW_COMPLETE:
+ priv->fw_state = PD692X0_FW_BROKEN;
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct fw_upload_ops pd692x0_fw_ops = {
+ .prepare = pd692x0_fw_prepare,
+ .write = pd692x0_fw_write,
+ .poll_complete = pd692x0_fw_poll_complete,
+ .cancel = pd692x0_fw_cancel,
+ .cleanup = pd692x0_fw_cleanup,
+};
+
+static int pd692x0_i2c_probe(struct i2c_client *client)
+{
+ static const char * const regulators[] = { "vdd", "vdda" };
+ struct pd692x0_msg msg, buf = {0}, zero = {0};
+ struct device *dev = &client->dev;
+ struct gpio_desc *disable_ports;
+ struct pd692x0_msg_ver ver;
+ struct pd692x0_priv *priv;
+ struct fw_upload *fwl;
+ int ret;
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators),
+ regulators);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to enable regulators\n");
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "i2c check functionality failed\n");
+ return -ENXIO;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+ i2c_set_clientdata(client, priv);
+
+ disable_ports = devm_gpiod_get_optional(dev, "disable-ports", GPIOD_OUT_LOW);
+ if (IS_ERR(disable_ports))
+ return dev_err_probe(&client->dev, PTR_ERR(disable_ports),
+ "Failed to get disable ports GPIO\n");
+
+ ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
+ if (ret != sizeof(buf)) {
+ dev_err(dev, "Failed to get device status\n");
+ return -EIO;
+ }
+
+ /* Probe has been already run and the status dumped */
+ if (!memcmp(&buf, &zero, sizeof(buf))) {
+ /* Ask again the controller status */
+ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_SYS_STATUS];
+ ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get device status\n");
+ return ret;
+ }
+ }
+
+ if (buf.key != 0x03 || buf.sub[0] & 0x01) {
+ dev_err(dev, "PSE controller error\n");
+ return -EIO;
+ }
+ if (buf.sub[0] & 0x02) {
+ dev_err(dev, "PSE firmware error. Please update it.\n");
+ priv->fw_state = PD692X0_FW_BROKEN;
+ } else {
+ ver = pd692x0_get_sw_version(priv);
+ dev_info(&client->dev, "Software version %d.%02d.%d.%d\n",
+ ver.prod, ver.maj_sw_ver, ver.min_sw_ver,
+ ver.pa_sw_ver);
+
+ if (ver.maj_sw_ver < PD692X0_FW_MAJ_VER) {
+ dev_err(dev, "Too old firmware version. Please update it\n");
+ priv->fw_state = PD692X0_FW_NEED_UPDATE;
+ } else {
+ priv->fw_state = PD692X0_FW_OK;
+ }
+ }
+
+ if (buf.data[2] == PD692X0_USER_BYTE)
+ priv->cfg_saved = true;
+
+ priv->np = dev->of_node;
+ priv->pcdev.nr_lines = PD692X0_MAX_PIS;
+ priv->pcdev.owner = THIS_MODULE;
+ priv->pcdev.ops = &pd692x0_ops;
+ priv->pcdev.dev = dev;
+ priv->pcdev.types = ETHTOOL_PSE_C33;
+ priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
+ priv->pcdev.pis_prio_max = 2;
+ ret = devm_pse_controller_register(dev, &priv->pcdev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register PSE controller\n");
+
+ fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev),
+ &pd692x0_fw_ops, priv);
+ if (IS_ERR(fwl))
+ return dev_err_probe(dev, PTR_ERR(fwl),
+ "failed to register to the Firmware Upload API\n");
+ priv->fwl = fwl;
+
+ return 0;
+}
+
+static void pd692x0_i2c_remove(struct i2c_client *client)
+{
+ struct pd692x0_priv *priv = i2c_get_clientdata(client);
+
+ pd692x0_managers_free_pw_budget(priv);
+ firmware_upload_unregister(priv->fwl);
+}
+
+static const struct i2c_device_id pd692x0_id[] = {
+ { .name = PD692X0_PSE_NAME },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pd692x0_id);
+
+static const struct of_device_id pd692x0_of_match[] = {
+ { .compatible = "microchip,pd69200", },
+ { .compatible = "microchip,pd69210", },
+ { .compatible = "microchip,pd69220", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pd692x0_of_match);
+
+static struct i2c_driver pd692x0_driver = {
+ .probe = pd692x0_i2c_probe,
+ .remove = pd692x0_i2c_remove,
+ .id_table = pd692x0_id,
+ .driver = {
+ .name = PD692X0_PSE_NAME,
+ .of_match_table = pd692x0_of_match,
+ },
+};
+module_i2c_driver(pd692x0_driver);
+
+MODULE_AUTHOR("Kory Maincent <kory.maincent@bootlin.com>");
+MODULE_DESCRIPTION("Microchip PD692x0 PoE PSE Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
new file mode 100644
index 000000000..a5e6d7b26
--- /dev/null
+++ b/drivers/net/pse-pd/pse_core.c
@@ -0,0 +1,1983 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Framework for Ethernet Power Sourcing Equipment
+//
+// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+//
+
+#include <linux/device.h>
+#include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/rtnetlink.h>
+#include <net/net_trackers.h>
+
+#define PSE_PW_D_LIMIT INT_MAX
+
+static DEFINE_MUTEX(pse_list_mutex);
+static LIST_HEAD(pse_controller_list);
+static DEFINE_XARRAY_ALLOC(pse_pw_d_map);
+static DEFINE_MUTEX(pse_pw_d_mutex);
+
+/**
+ * struct pse_control - a PSE control
+ * @pcdev: a pointer to the PSE controller device
+ * this PSE control belongs to
+ * @ps: PSE PI supply of the PSE control
+ * @list: list entry for the pcdev's PSE controller list
+ * @id: ID of the PSE line in the PSE controller device
+ * @refcnt: Number of gets of this pse_control
+ * @attached_phydev: PHY device pointer attached by the PSE control
+ */
+struct pse_control {
+ struct pse_controller_dev *pcdev;
+ struct regulator *ps;
+ struct list_head list;
+ unsigned int id;
+ struct kref refcnt;
+ struct phy_device *attached_phydev;
+};
+
+/**
+ * struct pse_power_domain - a PSE power domain
+ * @id: ID of the power domain
+ * @supply: Power supply the Power Domain
+ * @refcnt: Number of gets of this pse_power_domain
+ * @budget_eval_strategy: Current power budget evaluation strategy of the
+ * power domain
+ */
+struct pse_power_domain {
+ int id;
+ struct regulator *supply;
+ struct kref refcnt;
+ u32 budget_eval_strategy;
+};
+
+static int of_load_single_pse_pi_pairset(struct device_node *node,
+ struct pse_pi *pi,
+ int pairset_num)
+{
+ struct device_node *pairset_np;
+ const char *name;
+ int ret;
+
+ ret = of_property_read_string_index(node, "pairset-names",
+ pairset_num, &name);
+ if (ret)
+ return ret;
+
+ if (!strcmp(name, "alternative-a")) {
+ pi->pairset[pairset_num].pinout = ALTERNATIVE_A;
+ } else if (!strcmp(name, "alternative-b")) {
+ pi->pairset[pairset_num].pinout = ALTERNATIVE_B;
+ } else {
+ pr_err("pse: wrong pairset-names value %s (%pOF)\n",
+ name, node);
+ return -EINVAL;
+ }
+
+ pairset_np = of_parse_phandle(node, "pairsets", pairset_num);
+ if (!pairset_np)
+ return -ENODEV;
+
+ pi->pairset[pairset_num].np = pairset_np;
+
+ return 0;
+}
+
+/**
+ * of_load_pse_pi_pairsets - load PSE PI pairsets pinout and polarity
+ * @node: a pointer of the device node
+ * @pi: a pointer of the PSE PI to fill
+ * @npairsets: the number of pairsets (1 or 2) used by the PI
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int of_load_pse_pi_pairsets(struct device_node *node,
+ struct pse_pi *pi,
+ int npairsets)
+{
+ int i, ret;
+
+ ret = of_property_count_strings(node, "pairset-names");
+ if (ret != npairsets) {
+ pr_err("pse: amount of pairsets and pairset-names is not equal %d != %d (%pOF)\n",
+ npairsets, ret, node);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < npairsets; i++) {
+ ret = of_load_single_pse_pi_pairset(node, pi, i);
+ if (ret)
+ goto out;
+ }
+
+ if (npairsets == 2 &&
+ pi->pairset[0].pinout == pi->pairset[1].pinout) {
+ pr_err("pse: two PI pairsets can not have identical pinout (%pOF)",
+ node);
+ ret = -EINVAL;
+ }
+
+out:
+ /* If an error appears, release all the pairset device node kref */
+ if (ret) {
+ of_node_put(pi->pairset[0].np);
+ pi->pairset[0].np = NULL;
+ of_node_put(pi->pairset[1].np);
+ pi->pairset[1].np = NULL;
+ }
+
+ return ret;
+}
+
+static void pse_release_pis(struct pse_controller_dev *pcdev)
+{
+ int i;
+
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ of_node_put(pcdev->pi[i].pairset[0].np);
+ of_node_put(pcdev->pi[i].pairset[1].np);
+ of_node_put(pcdev->pi[i].np);
+ }
+ kfree(pcdev->pi);
+}
+
+/**
+ * of_load_pse_pis - load all the PSE PIs
+ * @pcdev: a pointer to the PSE controller device
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int of_load_pse_pis(struct pse_controller_dev *pcdev)
+{
+ struct device_node *np = pcdev->dev->of_node;
+ struct device_node *node, *pis;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ pcdev->pi = kzalloc_objs(*pcdev->pi, pcdev->nr_lines);
+ if (!pcdev->pi)
+ return -ENOMEM;
+
+ pis = of_get_child_by_name(np, "pse-pis");
+ if (!pis) {
+ /* no description of PSE PIs */
+ pcdev->no_of_pse_pi = true;
+ return 0;
+ }
+
+ for_each_child_of_node(pis, node) {
+ struct pse_pi pi = {0};
+ u32 id;
+
+ if (!of_node_name_eq(node, "pse-pi"))
+ continue;
+
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret) {
+ dev_err(pcdev->dev,
+ "can't get reg property for node '%pOF'",
+ node);
+ goto out;
+ }
+
+ if (id >= pcdev->nr_lines) {
+ dev_err(pcdev->dev,
+ "reg value (%u) is out of range (%u) (%pOF)\n",
+ id, pcdev->nr_lines, node);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (pcdev->pi[id].np) {
+ dev_err(pcdev->dev,
+ "other node with same reg value was already registered. %pOF : %pOF\n",
+ pcdev->pi[id].np, node);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = of_count_phandle_with_args(node, "pairsets", NULL);
+ /* npairsets is limited to value one or two */
+ if (ret == 1 || ret == 2) {
+ ret = of_load_pse_pi_pairsets(node, &pi, ret);
+ if (ret)
+ goto out;
+ } else if (ret != -ENOENT) {
+ dev_err(pcdev->dev,
+ "error: wrong number of pairsets. Should be 1 or 2, got %d (%pOF)\n",
+ ret, node);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ of_node_get(node);
+ pi.np = node;
+ memcpy(&pcdev->pi[id], &pi, sizeof(pi));
+ }
+
+ of_node_put(pis);
+ return 0;
+
+out:
+ pse_release_pis(pcdev);
+ of_node_put(node);
+ of_node_put(pis);
+ return ret;
+}
+
+/**
+ * pse_control_find_by_id - Find pse_control from an id
+ * @pcdev: a pointer to the PSE
+ * @id: index of the PSE control
+ *
+ * Return: pse_control pointer or NULL. The device returned has had a
+ * reference added and the pointer is safe until the user calls
+ * pse_control_put() to indicate they have finished with it.
+ */
+static struct pse_control *
+pse_control_find_by_id(struct pse_controller_dev *pcdev, int id)
+{
+ struct pse_control *psec;
+
+ mutex_lock(&pse_list_mutex);
+ list_for_each_entry(psec, &pcdev->pse_control_head, list) {
+ if (psec->id == id) {
+ kref_get(&psec->refcnt);
+ mutex_unlock(&pse_list_mutex);
+ return psec;
+ }
+ }
+ mutex_unlock(&pse_list_mutex);
+ return NULL;
+}
+
+/**
+ * pse_control_get_netdev - Return netdev associated to a PSE control
+ * @psec: PSE control pointer
+ *
+ * Return: netdev pointer or NULL
+ */
+static struct net_device *pse_control_get_netdev(struct pse_control *psec)
+{
+ ASSERT_RTNL();
+
+ if (!psec || !psec->attached_phydev)
+ return NULL;
+
+ return psec->attached_phydev->attached_dev;
+}
+
+/**
+ * pse_pi_is_hw_enabled - Is PI enabled at the hardware level
+ * @pcdev: a pointer to the PSE controller device
+ * @id: Index of the PI
+ *
+ * Return: 1 if the PI is enabled at the hardware level, 0 if not, and
+ * a failure value on error
+ */
+static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id)
+{
+ struct pse_admin_state admin_state = {0};
+ int ret;
+
+ ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state);
+ if (ret < 0)
+ return ret;
+
+ /* PI is well enabled at the hardware level */
+ if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
+ admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
+ return 1;
+
+ return 0;
+}
+
+/**
+ * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state
+ * which mean the power is not yet being
+ * delivered
+ * @pcdev: a pointer to the PSE controller device
+ * @id: Index of the PI
+ *
+ * Detects if a PI is enabled in software with a PD detected, but the hardware
+ * admin state hasn't been applied yet.
+ *
+ * This function is used in the power delivery and retry mechanisms to determine
+ * which PIs need to have power delivery attempted again.
+ *
+ * Return: true if the PI has admin enable flag set in software but not yet
+ * reflected in the hardware admin state, false otherwise.
+ */
+static bool
+pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id)
+{
+ int ret;
+
+ /* PI not enabled or nothing is plugged */
+ if (!pcdev->pi[id].admin_state_enabled ||
+ !pcdev->pi[id].isr_pd_detected)
+ return false;
+
+ ret = pse_pi_is_hw_enabled(pcdev, id);
+ /* PSE PI is already enabled at hardware level */
+ if (ret == 1)
+ return false;
+
+ return true;
+}
+
+static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
+ int id,
+ struct netlink_ext_ack *extack);
+
+/**
+ * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a
+ * PSE power domain
+ * @pcdev: a pointer to the PSE controller device
+ * @pw_d: a pointer to the PSE power domain
+ *
+ * Scans all ports in the specified power domain and attempts to enable power
+ * delivery to any ports that have admin enable state set but don't yet have
+ * hardware power enabled. Used when there are changes in connection status,
+ * admin state, or priority that might allow previously unpowered ports to
+ * receive power, especially in over-budget conditions.
+ */
+static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev,
+ struct pse_power_domain *pw_d)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ int prio_max = pcdev->nr_lines;
+ struct netlink_ext_ack extack;
+
+ if (pcdev->pi[i].pw_d != pw_d)
+ continue;
+
+ if (!pse_pi_is_admin_enable_pending(pcdev, i))
+ continue;
+
+ /* Do not try to enable PI with a lower prio (higher value)
+ * than one which already can't be enabled.
+ */
+ if (pcdev->pi[i].prio > prio_max)
+ continue;
+
+ ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack);
+ if (ret == -ERANGE)
+ prio_max = pcdev->pi[i].prio;
+ }
+}
+
+/**
+ * pse_pw_d_is_sw_pw_control - Determine if power control is software managed
+ * @pcdev: a pointer to the PSE controller device
+ * @pw_d: a pointer to the PSE power domain
+ *
+ * This function determines whether the power control for a specific power
+ * domain is managed by software in the interrupt handler rather than directly
+ * by hardware.
+ *
+ * Software power control is active in the following cases:
+ * - When the budget evaluation strategy is set to static
+ * - When the budget evaluation strategy is disabled but the PSE controller
+ * has an interrupt handler that can report if a Powered Device is connected
+ *
+ * Return: true if the power control of the power domain is managed by software,
+ * false otherwise
+ */
+static bool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev,
+ struct pse_power_domain *pw_d)
+{
+ if (!pw_d)
+ return false;
+
+ if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
+ return true;
+ if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED &&
+ pcdev->ops->pi_enable && pcdev->irq)
+ return true;
+
+ return false;
+}
+
+static int pse_pi_is_enabled(struct regulator_dev *rdev)
+{
+ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ const struct pse_controller_ops *ops;
+ int id, ret;
+
+ ops = pcdev->ops;
+ if (!ops->pi_get_admin_state)
+ return -EOPNOTSUPP;
+
+ id = rdev_get_id(rdev);
+ mutex_lock(&pcdev->lock);
+ if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
+ ret = pcdev->pi[id].admin_state_enabled;
+ goto out;
+ }
+
+ ret = pse_pi_is_hw_enabled(pcdev, id);
+
+out:
+ mutex_unlock(&pcdev->lock);
+
+ return ret;
+}
+
+/**
+ * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI
+ * @pi: a pointer to the PSE PI
+ */
+static void pse_pi_deallocate_pw_budget(struct pse_pi *pi)
+{
+ if (!pi->pw_d || !pi->pw_allocated_mW)
+ return;
+
+ regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW);
+ pi->pw_allocated_mW = 0;
+}
+
+/**
+ * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been
+ * acquired.
+ * @pcdev: a pointer to the PSE
+ * @id: index of the PSE control
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id)
+{
+ const struct pse_controller_ops *ops = pcdev->ops;
+ int ret;
+
+ if (!ops->pi_disable)
+ return -EOPNOTSUPP;
+
+ ret = ops->pi_disable(pcdev, id);
+ if (ret)
+ return ret;
+
+ pse_pi_deallocate_pw_budget(&pcdev->pi[id]);
+
+ if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d))
+ pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d);
+
+ return 0;
+}
+
+/**
+ * pse_disable_pi_pol - Disable a PI on a power budget policy
+ * @pcdev: a pointer to the PSE
+ * @id: index of the PSE PI
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id)
+{
+ unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET;
+ struct pse_ntf ntf = {};
+ int ret;
+
+ dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id);
+
+ ret = _pse_pi_disable(pcdev, id);
+ if (ret)
+ notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
+
+ ntf.notifs = notifs;
+ ntf.id = id;
+ kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock);
+ schedule_work(&pcdev->ntf_work);
+
+ return ret;
+}
+
+/**
+ * pse_disable_pi_prio - Disable all PIs of a given priority inside a PSE
+ * power domain
+ * @pcdev: a pointer to the PSE
+ * @pw_d: a pointer to the PSE power domain
+ * @prio: priority
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int pse_disable_pi_prio(struct pse_controller_dev *pcdev,
+ struct pse_power_domain *pw_d,
+ int prio)
+{
+ int i;
+
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ int ret;
+
+ if (pcdev->pi[i].prio != prio ||
+ pcdev->pi[i].pw_d != pw_d ||
+ pse_pi_is_hw_enabled(pcdev, i) <= 0)
+ continue;
+
+ ret = pse_disable_pi_pol(pcdev, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * pse_pi_allocate_pw_budget_static_prio - Allocate power budget for the PI
+ * when the budget eval strategy is
+ * static
+ * @pcdev: a pointer to the PSE
+ * @id: index of the PSE control
+ * @pw_req: power requested in mW
+ * @extack: extack for error reporting
+ *
+ * Allocates power using static budget evaluation strategy, where allocation
+ * is based on PD classification. When insufficient budget is available,
+ * lower-priority ports (higher priority numbers) are turned off first.
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int
+pse_pi_allocate_pw_budget_static_prio(struct pse_controller_dev *pcdev, int id,
+ int pw_req, struct netlink_ext_ack *extack)
+{
+ struct pse_pi *pi = &pcdev->pi[id];
+ int ret, _prio;
+
+ _prio = pcdev->nr_lines;
+ while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) {
+ if (_prio <= pi->prio) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: not enough power budget available",
+ id);
+ return -ERANGE;
+ }
+
+ ret = pse_disable_pi_prio(pcdev, pi->pw_d, _prio);
+ if (ret < 0)
+ return ret;
+
+ _prio--;
+ }
+
+ pi->pw_allocated_mW = pw_req;
+ return 0;
+}
+
+/**
+ * pse_pi_allocate_pw_budget - Allocate power budget for the PI
+ * @pcdev: a pointer to the PSE
+ * @id: index of the PSE control
+ * @pw_req: power requested in mW
+ * @extack: extack for error reporting
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int pse_pi_allocate_pw_budget(struct pse_controller_dev *pcdev, int id,
+ int pw_req, struct netlink_ext_ack *extack)
+{
+ struct pse_pi *pi = &pcdev->pi[id];
+
+ if (!pi->pw_d)
+ return 0;
+
+ /* PSE_BUDGET_EVAL_STRAT_STATIC */
+ if (pi->pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
+ return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req,
+ extack);
+
+ return 0;
+}
+
+/**
+ * _pse_pi_delivery_power_sw_pw_ctrl - Enable PSE PI in case of software power
+ * control. Assumes the PSE lock has been
+ * acquired.
+ * @pcdev: a pointer to the PSE
+ * @id: index of the PSE control
+ * @extack: extack for error reporting
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
+ int id,
+ struct netlink_ext_ack *extack)
+{
+ const struct pse_controller_ops *ops = pcdev->ops;
+ struct pse_pi *pi = &pcdev->pi[id];
+ int ret, pw_req;
+
+ if (!ops->pi_get_pw_req) {
+ /* No power allocation management */
+ ret = ops->pi_enable(pcdev, id);
+ if (ret)
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: enable error %d",
+ id, ret);
+ return ret;
+ }
+
+ ret = ops->pi_get_pw_req(pcdev, id);
+ if (ret < 0)
+ return ret;
+
+ pw_req = ret;
+
+ /* Compare requested power with port power limit and use the lowest
+ * one.
+ */
+ if (ops->pi_get_pw_limit) {
+ ret = ops->pi_get_pw_limit(pcdev, id);
+ if (ret < 0)
+ return ret;
+
+ if (ret < pw_req)
+ pw_req = ret;
+ }
+
+ ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack);
+ if (ret)
+ return ret;
+
+ ret = ops->pi_enable(pcdev, id);
+ if (ret) {
+ pse_pi_deallocate_pw_budget(pi);
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: enable error %d",
+ id, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pse_pi_enable(struct regulator_dev *rdev)
+{
+ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ const struct pse_controller_ops *ops;
+ int id, ret = 0;
+
+ ops = pcdev->ops;
+ if (!ops->pi_enable)
+ return -EOPNOTSUPP;
+
+ id = rdev_get_id(rdev);
+ mutex_lock(&pcdev->lock);
+ if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
+ /* Manage enabled status by software.
+ * Real enable process will happen if a port is connected.
+ */
+ if (pcdev->pi[id].isr_pd_detected) {
+ struct netlink_ext_ack extack;
+
+ ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, &extack);
+ }
+ if (!ret || ret == -ERANGE) {
+ pcdev->pi[id].admin_state_enabled = 1;
+ ret = 0;
+ }
+ mutex_unlock(&pcdev->lock);
+ return ret;
+ }
+
+ ret = ops->pi_enable(pcdev, id);
+ if (!ret)
+ pcdev->pi[id].admin_state_enabled = 1;
+ mutex_unlock(&pcdev->lock);
+
+ return ret;
+}
+
+static int pse_pi_disable(struct regulator_dev *rdev)
+{
+ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ struct pse_pi *pi;
+ int id, ret;
+
+ id = rdev_get_id(rdev);
+ pi = &pcdev->pi[id];
+ mutex_lock(&pcdev->lock);
+ ret = _pse_pi_disable(pcdev, id);
+ if (!ret)
+ pi->admin_state_enabled = 0;
+
+ mutex_unlock(&pcdev->lock);
+ return 0;
+}
+
+static int _pse_pi_get_voltage(struct regulator_dev *rdev)
+{
+ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ const struct pse_controller_ops *ops;
+ int id;
+
+ ops = pcdev->ops;
+ if (!ops->pi_get_voltage)
+ return -EOPNOTSUPP;
+
+ id = rdev_get_id(rdev);
+ return ops->pi_get_voltage(pcdev, id);
+}
+
+static int pse_pi_get_voltage(struct regulator_dev *rdev)
+{
+ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ int ret;
+
+ mutex_lock(&pcdev->lock);
+ ret = _pse_pi_get_voltage(rdev);
+ mutex_unlock(&pcdev->lock);
+
+ return ret;
+}
+
+static int pse_pi_get_current_limit(struct regulator_dev *rdev)
+{
+ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ const struct pse_controller_ops *ops;
+ int id, uV, mW, ret;
+ s64 tmp_64;
+
+ ops = pcdev->ops;
+ id = rdev_get_id(rdev);
+ if (!ops->pi_get_pw_limit || !ops->pi_get_voltage)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&pcdev->lock);
+ ret = ops->pi_get_pw_limit(pcdev, id);
+ if (ret < 0)
+ goto out;
+ mW = ret;
+
+ ret = _pse_pi_get_voltage(rdev);
+ if (!ret) {
+ dev_err(pcdev->dev, "Voltage null\n");
+ ret = -ERANGE;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+ uV = ret;
+
+ tmp_64 = mW;
+ tmp_64 *= 1000000000ull;
+ /* uA = mW * 1000000000 / uV */
+ ret = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
+
+out:
+ mutex_unlock(&pcdev->lock);
+ return ret;
+}
+
+static int pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA,
+ int max_uA)
+{
+ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ const struct pse_controller_ops *ops;
+ int id, mW, ret;
+ s64 tmp_64;
+
+ ops = pcdev->ops;
+ if (!ops->pi_set_pw_limit || !ops->pi_get_voltage)
+ return -EOPNOTSUPP;
+
+ if (max_uA > MAX_PI_CURRENT)
+ return -ERANGE;
+
+ id = rdev_get_id(rdev);
+ mutex_lock(&pcdev->lock);
+ ret = _pse_pi_get_voltage(rdev);
+ if (!ret) {
+ dev_err(pcdev->dev, "Voltage null\n");
+ ret = -ERANGE;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+
+ tmp_64 = ret;
+ tmp_64 *= max_uA;
+ /* mW = uA * uV / 1000000000 */
+ mW = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
+ ret = ops->pi_set_pw_limit(pcdev, id, mW);
+out:
+ mutex_unlock(&pcdev->lock);
+
+ return ret;
+}
+
+static const struct regulator_ops pse_pi_ops = {
+ .is_enabled = pse_pi_is_enabled,
+ .enable = pse_pi_enable,
+ .disable = pse_pi_disable,
+ .get_voltage = pse_pi_get_voltage,
+ .get_current_limit = pse_pi_get_current_limit,
+ .set_current_limit = pse_pi_set_current_limit,
+};
+
+static int
+devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev,
+ char *name, int id)
+{
+ struct regulator_init_data *rinit_data;
+ struct regulator_config rconfig = {0};
+ struct regulator_desc *rdesc;
+ struct regulator_dev *rdev;
+
+ rinit_data = devm_kzalloc(pcdev->dev, sizeof(*rinit_data),
+ GFP_KERNEL);
+ if (!rinit_data)
+ return -ENOMEM;
+
+ rdesc = devm_kzalloc(pcdev->dev, sizeof(*rdesc), GFP_KERNEL);
+ if (!rdesc)
+ return -ENOMEM;
+
+ /* Regulator descriptor id have to be the same as its associated
+ * PSE PI id for the well functioning of the PSE controls.
+ */
+ rdesc->id = id;
+ rdesc->name = name;
+ rdesc->type = REGULATOR_VOLTAGE;
+ rdesc->ops = &pse_pi_ops;
+ rdesc->owner = pcdev->owner;
+
+ rinit_data->constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
+
+ if (pcdev->ops->pi_set_pw_limit)
+ rinit_data->constraints.valid_ops_mask |=
+ REGULATOR_CHANGE_CURRENT;
+
+ rinit_data->supply_regulator = "vpwr";
+
+ rconfig.dev = pcdev->dev;
+ rconfig.driver_data = pcdev;
+ rconfig.init_data = rinit_data;
+ rconfig.of_node = pcdev->pi[id].np;
+
+ rdev = devm_regulator_register(pcdev->dev, rdesc, &rconfig);
+ if (IS_ERR(rdev)) {
+ dev_err_probe(pcdev->dev, PTR_ERR(rdev),
+ "Failed to register regulator\n");
+ return PTR_ERR(rdev);
+ }
+
+ pcdev->pi[id].rdev = rdev;
+
+ return 0;
+}
+
+static void __pse_pw_d_release(struct kref *kref)
+{
+ struct pse_power_domain *pw_d = container_of(kref,
+ struct pse_power_domain,
+ refcnt);
+
+ regulator_put(pw_d->supply);
+ xa_erase(&pse_pw_d_map, pw_d->id);
+ mutex_unlock(&pse_pw_d_mutex);
+}
+
+/**
+ * pse_flush_pw_ds - flush all PSE power domains of a PSE
+ * @pcdev: a pointer to the initialized PSE controller device
+ */
+static void pse_flush_pw_ds(struct pse_controller_dev *pcdev)
+{
+ struct pse_power_domain *pw_d;
+ int i;
+
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ if (!pcdev->pi[i].pw_d)
+ continue;
+
+ pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id);
+ if (!pw_d)
+ continue;
+
+ kref_put_mutex(&pw_d->refcnt, __pse_pw_d_release,
+ &pse_pw_d_mutex);
+ }
+}
+
+/**
+ * devm_pse_alloc_pw_d - allocate a new PSE power domain for a device
+ * @dev: device that is registering this PSE power domain
+ *
+ * Return: Pointer to the newly allocated PSE power domain or error pointers
+ */
+static struct pse_power_domain *devm_pse_alloc_pw_d(struct device *dev)
+{
+ struct pse_power_domain *pw_d;
+ int index, ret;
+
+ pw_d = devm_kzalloc(dev, sizeof(*pw_d), GFP_KERNEL);
+ if (!pw_d)
+ return ERR_PTR(-ENOMEM);
+
+ ret = xa_alloc(&pse_pw_d_map, &index, pw_d, XA_LIMIT(1, PSE_PW_D_LIMIT),
+ GFP_KERNEL);
+ if (ret)
+ return ERR_PTR(ret);
+
+ kref_init(&pw_d->refcnt);
+ pw_d->id = index;
+ return pw_d;
+}
+
+/**
+ * pse_register_pw_ds - register the PSE power domains for a PSE
+ * @pcdev: a pointer to the PSE controller device
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int pse_register_pw_ds(struct pse_controller_dev *pcdev)
+{
+ int i, ret = 0;
+
+ mutex_lock(&pse_pw_d_mutex);
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ struct regulator_dev *rdev = pcdev->pi[i].rdev;
+ struct pse_power_domain *pw_d;
+ struct regulator *supply;
+ bool present = false;
+ unsigned long index;
+
+ /* No regulator or regulator parent supply registered.
+ * We need a regulator parent to register a PSE power domain
+ */
+ if (!rdev || !rdev->supply)
+ continue;
+
+ xa_for_each(&pse_pw_d_map, index, pw_d) {
+ /* Power supply already registered as a PSE power
+ * domain.
+ */
+ if (regulator_is_equal(pw_d->supply, rdev->supply)) {
+ present = true;
+ pcdev->pi[i].pw_d = pw_d;
+ break;
+ }
+ }
+ if (present) {
+ kref_get(&pw_d->refcnt);
+ continue;
+ }
+
+ pw_d = devm_pse_alloc_pw_d(pcdev->dev);
+ if (IS_ERR(pw_d)) {
+ ret = PTR_ERR(pw_d);
+ goto out;
+ }
+
+ supply = regulator_get(&rdev->dev, rdev->supply_name);
+ if (IS_ERR(supply)) {
+ xa_erase(&pse_pw_d_map, pw_d->id);
+ ret = PTR_ERR(supply);
+ goto out;
+ }
+
+ pw_d->supply = supply;
+ if (pcdev->supp_budget_eval_strategies)
+ pw_d->budget_eval_strategy = pcdev->supp_budget_eval_strategies;
+ else
+ pw_d->budget_eval_strategy = PSE_BUDGET_EVAL_STRAT_DISABLED;
+ kref_init(&pw_d->refcnt);
+ pcdev->pi[i].pw_d = pw_d;
+ }
+
+out:
+ mutex_unlock(&pse_pw_d_mutex);
+ return ret;
+}
+
+/**
+ * pse_send_ntf_worker - Worker to send PSE notifications
+ * @work: work object
+ *
+ * Manage and send PSE netlink notifications using a workqueue to avoid
+ * deadlock between pcdev_lock and pse_list_mutex.
+ */
+static void pse_send_ntf_worker(struct work_struct *work)
+{
+ struct pse_controller_dev *pcdev;
+ struct pse_ntf ntf;
+
+ pcdev = container_of(work, struct pse_controller_dev, ntf_work);
+
+ while (kfifo_out(&pcdev->ntf_fifo, &ntf, 1)) {
+ struct net_device *netdev;
+ struct pse_control *psec;
+
+ psec = pse_control_find_by_id(pcdev, ntf.id);
+ rtnl_lock();
+ netdev = pse_control_get_netdev(psec);
+ if (netdev)
+ ethnl_pse_send_ntf(netdev, ntf.notifs);
+ rtnl_unlock();
+ pse_control_put(psec);
+ }
+}
+
+/**
+ * pse_controller_register - register a PSE controller device
+ * @pcdev: a pointer to the initialized PSE controller device
+ *
+ * Return: 0 on success and failure value on error
+ */
+int pse_controller_register(struct pse_controller_dev *pcdev)
+{
+ size_t reg_name_len;
+ int ret, i;
+
+ mutex_init(&pcdev->lock);
+ INIT_LIST_HEAD(&pcdev->pse_control_head);
+ spin_lock_init(&pcdev->ntf_fifo_lock);
+ ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL);
+ if (ret) {
+ dev_err(pcdev->dev, "failed to allocate kfifo notifications\n");
+ return ret;
+ }
+ INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker);
+
+ if (!pcdev->nr_lines)
+ pcdev->nr_lines = 1;
+
+ if (!pcdev->ops->pi_get_admin_state ||
+ !pcdev->ops->pi_get_pw_status) {
+ dev_err(pcdev->dev,
+ "Mandatory status report callbacks are missing");
+ return -EINVAL;
+ }
+
+ ret = of_load_pse_pis(pcdev);
+ if (ret)
+ return ret;
+
+ if (pcdev->ops->setup_pi_matrix) {
+ ret = pcdev->ops->setup_pi_matrix(pcdev);
+ if (ret)
+ return ret;
+ }
+
+ /* Each regulator name len is pcdev dev name + 7 char +
+ * int max digit number (10) + 1
+ */
+ reg_name_len = strlen(dev_name(pcdev->dev)) + 18;
+
+ /* Register PI regulators */
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ char *reg_name;
+
+ /* Do not register regulator for PIs not described */
+ if (!pcdev->no_of_pse_pi && !pcdev->pi[i].np)
+ continue;
+
+ reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL);
+ if (!reg_name)
+ return -ENOMEM;
+
+ snprintf(reg_name, reg_name_len, "pse-%s_pi%d",
+ dev_name(pcdev->dev), i);
+
+ ret = devm_pse_pi_regulator_register(pcdev, reg_name, i);
+ if (ret)
+ return ret;
+ }
+
+ ret = pse_register_pw_ds(pcdev);
+ if (ret)
+ return ret;
+
+ mutex_lock(&pse_list_mutex);
+ list_add(&pcdev->list, &pse_controller_list);
+ mutex_unlock(&pse_list_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pse_controller_register);
+
+/**
+ * pse_controller_unregister - unregister a PSE controller device
+ * @pcdev: a pointer to the PSE controller device
+ */
+void pse_controller_unregister(struct pse_controller_dev *pcdev)
+{
+ pse_flush_pw_ds(pcdev);
+ pse_release_pis(pcdev);
+ if (pcdev->irq)
+ disable_irq(pcdev->irq);
+ cancel_work_sync(&pcdev->ntf_work);
+ kfifo_free(&pcdev->ntf_fifo);
+ mutex_lock(&pse_list_mutex);
+ list_del(&pcdev->list);
+ mutex_unlock(&pse_list_mutex);
+}
+EXPORT_SYMBOL_GPL(pse_controller_unregister);
+
+static void devm_pse_controller_release(struct device *dev, void *res)
+{
+ pse_controller_unregister(*(struct pse_controller_dev **)res);
+}
+
+/**
+ * devm_pse_controller_register - resource managed pse_controller_register()
+ * @dev: device that is registering this PSE controller
+ * @pcdev: a pointer to the initialized PSE controller device
+ *
+ * Managed pse_controller_register(). For PSE controllers registered by
+ * this function, pse_controller_unregister() is automatically called on
+ * driver detach. See pse_controller_register() for more information.
+ *
+ * Return: 0 on success and failure value on error
+ */
+int devm_pse_controller_register(struct device *dev,
+ struct pse_controller_dev *pcdev)
+{
+ struct pse_controller_dev **pcdevp;
+ int ret;
+
+ pcdevp = devres_alloc(devm_pse_controller_release, sizeof(*pcdevp),
+ GFP_KERNEL);
+ if (!pcdevp)
+ return -ENOMEM;
+
+ ret = pse_controller_register(pcdev);
+ if (ret) {
+ devres_free(pcdevp);
+ return ret;
+ }
+
+ *pcdevp = pcdev;
+ devres_add(dev, pcdevp);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_pse_controller_register);
+
+struct pse_irq {
+ struct pse_controller_dev *pcdev;
+ struct pse_irq_desc desc;
+ unsigned long *notifs;
+ unsigned long *notifs_mask;
+};
+
+/**
+ * pse_to_regulator_notifs - Convert PSE notifications to Regulator
+ * notifications
+ * @notifs: PSE notifications
+ *
+ * Return: Regulator notifications
+ */
+static unsigned long pse_to_regulator_notifs(unsigned long notifs)
+{
+ unsigned long rnotifs = 0;
+
+ if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT)
+ rnotifs |= REGULATOR_EVENT_OVER_CURRENT;
+ if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP)
+ rnotifs |= REGULATOR_EVENT_OVER_TEMP;
+
+ return rnotifs;
+}
+
+/**
+ * pse_set_config_isr - Set PSE control config according to the PSE
+ * notifications
+ * @pcdev: a pointer to the PSE
+ * @id: index of the PSE control
+ * @notifs: PSE event notifications
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id,
+ unsigned long notifs)
+{
+ int ret = 0;
+
+ if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC)
+ return 0;
+
+ if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) &&
+ ((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) ||
+ (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) {
+ dev_dbg(pcdev->dev,
+ "PI %d: error, connection and disconnection reported simultaneously",
+ id);
+ return -EINVAL;
+ }
+
+ if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION) {
+ struct netlink_ext_ack extack;
+
+ pcdev->pi[id].isr_pd_detected = true;
+ if (pcdev->pi[id].admin_state_enabled) {
+ ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id,
+ &extack);
+ if (ret == -ERANGE)
+ ret = 0;
+ }
+ } else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) {
+ if (pcdev->pi[id].admin_state_enabled &&
+ pcdev->pi[id].isr_pd_detected)
+ ret = _pse_pi_disable(pcdev, id);
+ pcdev->pi[id].isr_pd_detected = false;
+ }
+
+ return ret;
+}
+
+/**
+ * pse_isr - IRQ handler for PSE
+ * @irq: irq number
+ * @data: pointer to user interrupt structure
+ *
+ * Return: irqreturn_t - status of IRQ
+ */
+static irqreturn_t pse_isr(int irq, void *data)
+{
+ struct pse_controller_dev *pcdev;
+ struct pse_irq_desc *desc;
+ struct pse_irq *h = data;
+ int ret, i;
+
+ desc = &h->desc;
+ pcdev = h->pcdev;
+
+ /* Clear notifs mask */
+ memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
+ bitmap_zero(h->notifs_mask, pcdev->nr_lines);
+ mutex_lock(&pcdev->lock);
+ ret = desc->map_event(irq, pcdev, h->notifs, h->notifs_mask);
+ if (ret || bitmap_empty(h->notifs_mask, pcdev->nr_lines)) {
+ mutex_unlock(&pcdev->lock);
+ return IRQ_NONE;
+ }
+
+ for_each_set_bit(i, h->notifs_mask, pcdev->nr_lines) {
+ unsigned long notifs, rnotifs;
+ struct pse_ntf ntf = {};
+
+ /* Do nothing PI not described */
+ if (!pcdev->pi[i].rdev)
+ continue;
+
+ notifs = h->notifs[i];
+ if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[i].pw_d)) {
+ ret = pse_set_config_isr(pcdev, i, notifs);
+ if (ret)
+ notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
+ }
+
+ dev_dbg(h->pcdev->dev,
+ "Sending PSE notification EVT 0x%lx\n", notifs);
+
+ ntf.notifs = notifs;
+ ntf.id = i;
+ kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1,
+ &pcdev->ntf_fifo_lock);
+ schedule_work(&pcdev->ntf_work);
+
+ rnotifs = pse_to_regulator_notifs(notifs);
+ regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs,
+ NULL);
+ }
+
+ mutex_unlock(&pcdev->lock);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * devm_pse_irq_helper - Register IRQ based PSE event notifier
+ * @pcdev: a pointer to the PSE
+ * @irq: the irq value to be passed to request_irq
+ * @irq_flags: the flags to be passed to request_irq
+ * @d: PSE interrupt description
+ *
+ * Return: 0 on success and errno on failure
+ */
+int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
+ int irq_flags, const struct pse_irq_desc *d)
+{
+ struct device *dev = pcdev->dev;
+ size_t irq_name_len;
+ struct pse_irq *h;
+ char *irq_name;
+ int ret;
+
+ if (!d || !d->map_event || !d->name)
+ return -EINVAL;
+
+ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return -ENOMEM;
+
+ h->pcdev = pcdev;
+ h->desc = *d;
+
+ /* IRQ name len is pcdev dev name + 5 char + irq desc name + 1 */
+ irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1;
+ irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL);
+ if (!irq_name)
+ return -ENOMEM;
+
+ snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev),
+ d->name);
+
+ h->notifs = devm_kcalloc(dev, pcdev->nr_lines,
+ sizeof(*h->notifs), GFP_KERNEL);
+ if (!h->notifs)
+ return -ENOMEM;
+
+ h->notifs_mask = devm_bitmap_zalloc(dev, pcdev->nr_lines, GFP_KERNEL);
+ if (!h->notifs_mask)
+ return -ENOMEM;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr,
+ IRQF_ONESHOT | irq_flags,
+ irq_name, h);
+ if (ret)
+ dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq);
+
+ pcdev->irq = irq;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(devm_pse_irq_helper);
+
+/* PSE control section */
+
+static void __pse_control_release(struct kref *kref)
+{
+ struct pse_control *psec = container_of(kref, struct pse_control,
+ refcnt);
+
+ lockdep_assert_held(&pse_list_mutex);
+
+ if (psec->pcdev->pi[psec->id].admin_state_enabled)
+ regulator_disable(psec->ps);
+ regulator_put(psec->ps);
+
+ module_put(psec->pcdev->owner);
+
+ list_del(&psec->list);
+ kfree(psec);
+}
+
+static void __pse_control_put_internal(struct pse_control *psec)
+{
+ lockdep_assert_held(&pse_list_mutex);
+
+ kref_put(&psec->refcnt, __pse_control_release);
+}
+
+/**
+ * pse_control_put - free the PSE control
+ * @psec: PSE control pointer
+ */
+void pse_control_put(struct pse_control *psec)
+{
+ if (IS_ERR_OR_NULL(psec))
+ return;
+
+ mutex_lock(&pse_list_mutex);
+ __pse_control_put_internal(psec);
+ mutex_unlock(&pse_list_mutex);
+}
+EXPORT_SYMBOL_GPL(pse_control_put);
+
+static struct pse_control *
+pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index,
+ struct phy_device *phydev)
+{
+ struct pse_control *psec;
+ int ret;
+
+ lockdep_assert_held(&pse_list_mutex);
+
+ list_for_each_entry(psec, &pcdev->pse_control_head, list) {
+ if (psec->id == index) {
+ kref_get(&psec->refcnt);
+ return psec;
+ }
+ }
+
+ psec = kzalloc_obj(*psec);
+ if (!psec)
+ return ERR_PTR(-ENOMEM);
+
+ if (!try_module_get(pcdev->owner)) {
+ ret = -ENODEV;
+ goto free_psec;
+ }
+
+ if (!pcdev->ops->pi_get_admin_state) {
+ ret = -EOPNOTSUPP;
+ goto free_psec;
+ }
+
+ /* Initialize admin_state_enabled before the regulator_get. This
+ * aims to have the right value reported in the first is_enabled
+ * call in case of control managed by software.
+ */
+ ret = pse_pi_is_hw_enabled(pcdev, index);
+ if (ret < 0)
+ goto free_psec;
+
+ pcdev->pi[index].admin_state_enabled = ret;
+ psec->ps = regulator_get_exclusive(pcdev->dev,
+ rdev_get_name(pcdev->pi[index].rdev));
+ if (IS_ERR(psec->ps)) {
+ ret = PTR_ERR(psec->ps);
+ goto put_module;
+ }
+
+ psec->pcdev = pcdev;
+ list_add(&psec->list, &pcdev->pse_control_head);
+ psec->id = index;
+ psec->attached_phydev = phydev;
+ kref_init(&psec->refcnt);
+
+ return psec;
+
+put_module:
+ module_put(pcdev->owner);
+free_psec:
+ kfree(psec);
+
+ return ERR_PTR(ret);
+}
+
+/**
+ * of_pse_match_pi - Find the PSE PI id matching the device node phandle
+ * @pcdev: a pointer to the PSE controller device
+ * @np: a pointer to the device node
+ *
+ * Return: id of the PSE PI, -EINVAL if not found
+ */
+static int of_pse_match_pi(struct pse_controller_dev *pcdev,
+ struct device_node *np)
+{
+ int i;
+
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ if (pcdev->pi[i].np == np)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * psec_id_xlate - translate pse_spec to the PSE line number according
+ * to the number of pse-cells in case of no pse_pi node
+ * @pcdev: a pointer to the PSE controller device
+ * @pse_spec: PSE line specifier as found in the device tree
+ *
+ * Return: 0 if #pse-cells = <0>. Return PSE line number otherwise.
+ */
+static int psec_id_xlate(struct pse_controller_dev *pcdev,
+ const struct of_phandle_args *pse_spec)
+{
+ if (!pcdev->of_pse_n_cells)
+ return 0;
+
+ if (pcdev->of_pse_n_cells > 1 ||
+ pse_spec->args[0] >= pcdev->nr_lines)
+ return -EINVAL;
+
+ return pse_spec->args[0];
+}
+
+struct pse_control *of_pse_control_get(struct device_node *node,
+ struct phy_device *phydev)
+{
+ struct pse_controller_dev *r, *pcdev;
+ struct of_phandle_args args;
+ struct pse_control *psec;
+ int psec_id;
+ int ret;
+
+ if (!node)
+ return ERR_PTR(-EINVAL);
+
+ ret = of_parse_phandle_with_args(node, "pses", "#pse-cells", 0, &args);
+ if (ret)
+ return ERR_PTR(ret);
+
+ mutex_lock(&pse_list_mutex);
+ pcdev = NULL;
+ list_for_each_entry(r, &pse_controller_list, list) {
+ if (!r->no_of_pse_pi) {
+ ret = of_pse_match_pi(r, args.np);
+ if (ret >= 0) {
+ pcdev = r;
+ psec_id = ret;
+ break;
+ }
+ } else if (args.np == r->dev->of_node) {
+ pcdev = r;
+ break;
+ }
+ }
+
+ if (!pcdev) {
+ psec = ERR_PTR(-EPROBE_DEFER);
+ goto out;
+ }
+
+ if (WARN_ON(args.args_count != pcdev->of_pse_n_cells)) {
+ psec = ERR_PTR(-EINVAL);
+ goto out;
+ }
+
+ if (pcdev->no_of_pse_pi) {
+ psec_id = psec_id_xlate(pcdev, &args);
+ if (psec_id < 0) {
+ psec = ERR_PTR(psec_id);
+ goto out;
+ }
+ }
+
+ /* pse_list_mutex also protects the pcdev's pse_control list */
+ psec = pse_control_get_internal(pcdev, psec_id, phydev);
+
+out:
+ mutex_unlock(&pse_list_mutex);
+ of_node_put(args.np);
+
+ return psec;
+}
+EXPORT_SYMBOL_GPL(of_pse_control_get);
+
+/**
+ * pse_get_sw_admin_state - Convert the software admin state to c33 or podl
+ * admin state value used in the standard
+ * @psec: PSE control pointer
+ * @admin_state: a pointer to the admin_state structure
+ */
+static void pse_get_sw_admin_state(struct pse_control *psec,
+ struct pse_admin_state *admin_state)
+{
+ struct pse_pi *pi = &psec->pcdev->pi[psec->id];
+
+ if (pse_has_podl(psec)) {
+ if (pi->admin_state_enabled)
+ admin_state->podl_admin_state =
+ ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
+ else
+ admin_state->podl_admin_state =
+ ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
+ }
+ if (pse_has_c33(psec)) {
+ if (pi->admin_state_enabled)
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+ else
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+ }
+}
+
+/**
+ * pse_ethtool_get_status - get status of PSE control
+ * @psec: PSE control pointer
+ * @extack: extack for reporting useful error messages
+ * @status: struct to store PSE status
+ *
+ * Return: 0 on success and failure value on error
+ */
+int pse_ethtool_get_status(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ struct ethtool_pse_control_status *status)
+{
+ struct pse_admin_state admin_state = {0};
+ struct pse_pw_status pw_status = {0};
+ const struct pse_controller_ops *ops;
+ struct pse_controller_dev *pcdev;
+ struct pse_pi *pi;
+ int ret;
+
+ pcdev = psec->pcdev;
+ ops = pcdev->ops;
+
+ pi = &pcdev->pi[psec->id];
+ mutex_lock(&pcdev->lock);
+ if (pi->pw_d) {
+ status->pw_d_id = pi->pw_d->id;
+ if (pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d)) {
+ pse_get_sw_admin_state(psec, &admin_state);
+ } else {
+ ret = ops->pi_get_admin_state(pcdev, psec->id,
+ &admin_state);
+ if (ret)
+ goto out;
+ }
+ status->podl_admin_state = admin_state.podl_admin_state;
+ status->c33_admin_state = admin_state.c33_admin_state;
+
+ switch (pi->pw_d->budget_eval_strategy) {
+ case PSE_BUDGET_EVAL_STRAT_STATIC:
+ status->prio_max = pcdev->nr_lines - 1;
+ status->prio = pi->prio;
+ break;
+ case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
+ status->prio_max = pcdev->pis_prio_max;
+ if (ops->pi_get_prio) {
+ ret = ops->pi_get_prio(pcdev, psec->id);
+ if (ret < 0)
+ goto out;
+
+ status->prio = ret;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status);
+ if (ret)
+ goto out;
+ status->podl_pw_status = pw_status.podl_pw_status;
+ status->c33_pw_status = pw_status.c33_pw_status;
+
+ if (ops->pi_get_ext_state) {
+ struct pse_ext_state_info ext_state_info = {0};
+
+ ret = ops->pi_get_ext_state(pcdev, psec->id,
+ &ext_state_info);
+ if (ret)
+ goto out;
+
+ memcpy(&status->c33_ext_state_info,
+ &ext_state_info.c33_ext_state_info,
+ sizeof(status->c33_ext_state_info));
+ }
+
+ if (ops->pi_get_pw_class) {
+ ret = ops->pi_get_pw_class(pcdev, psec->id);
+ if (ret < 0)
+ goto out;
+
+ status->c33_pw_class = ret;
+ }
+
+ if (ops->pi_get_actual_pw) {
+ ret = ops->pi_get_actual_pw(pcdev, psec->id);
+ if (ret < 0)
+ goto out;
+
+ status->c33_actual_pw = ret;
+ }
+
+ if (ops->pi_get_pw_limit) {
+ ret = ops->pi_get_pw_limit(pcdev, psec->id);
+ if (ret < 0)
+ goto out;
+
+ status->c33_avail_pw_limit = ret;
+ }
+
+ if (ops->pi_get_pw_limit_ranges) {
+ struct pse_pw_limit_ranges pw_limit_ranges = {0};
+
+ ret = ops->pi_get_pw_limit_ranges(pcdev, psec->id,
+ &pw_limit_ranges);
+ if (ret < 0)
+ goto out;
+
+ status->c33_pw_limit_ranges =
+ pw_limit_ranges.c33_pw_limit_ranges;
+ status->c33_pw_limit_nb_ranges = ret;
+ }
+out:
+ mutex_unlock(&psec->pcdev->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pse_ethtool_get_status);
+
+static int pse_ethtool_c33_set_config(struct pse_control *psec,
+ const struct pse_control_config *config)
+{
+ int err = 0;
+
+ /* Look at admin_state_enabled status to not call regulator_enable
+ * or regulator_disable twice creating a regulator counter mismatch
+ */
+ switch (config->c33_admin_control) {
+ case ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED:
+ /* We could have mismatch between admin_state_enabled and
+ * state reported by regulator_is_enabled. This can occur when
+ * the PI is forcibly turn off by the controller. Call
+ * regulator_disable on that case to fix the counters state.
+ */
+ if (psec->pcdev->pi[psec->id].admin_state_enabled &&
+ !regulator_is_enabled(psec->ps)) {
+ err = regulator_disable(psec->ps);
+ if (err)
+ break;
+ }
+ if (!psec->pcdev->pi[psec->id].admin_state_enabled)
+ err = regulator_enable(psec->ps);
+ break;
+ case ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED:
+ if (psec->pcdev->pi[psec->id].admin_state_enabled)
+ err = regulator_disable(psec->ps);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ }
+
+ return err;
+}
+
+static int pse_ethtool_podl_set_config(struct pse_control *psec,
+ const struct pse_control_config *config)
+{
+ int err = 0;
+
+ /* Look at admin_state_enabled status to not call regulator_enable
+ * or regulator_disable twice creating a regulator counter mismatch
+ */
+ switch (config->podl_admin_control) {
+ case ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED:
+ if (!psec->pcdev->pi[psec->id].admin_state_enabled)
+ err = regulator_enable(psec->ps);
+ break;
+ case ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED:
+ if (psec->pcdev->pi[psec->id].admin_state_enabled)
+ err = regulator_disable(psec->ps);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ }
+
+ return err;
+}
+
+/**
+ * pse_ethtool_set_config - set PSE control configuration
+ * @psec: PSE control pointer
+ * @extack: extack for reporting useful error messages
+ * @config: Configuration of the test to run
+ *
+ * Return: 0 on success and failure value on error
+ */
+int pse_ethtool_set_config(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ const struct pse_control_config *config)
+{
+ int err = 0;
+
+ if (pse_has_c33(psec) && config->c33_admin_control) {
+ err = pse_ethtool_c33_set_config(psec, config);
+ if (err)
+ return err;
+ }
+
+ if (pse_has_podl(psec) && config->podl_admin_control)
+ err = pse_ethtool_podl_set_config(psec, config);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(pse_ethtool_set_config);
+
+/**
+ * pse_pi_update_pw_budget - Update PSE power budget allocated with new
+ * power in mW
+ * @pcdev: a pointer to the PSE controller device
+ * @id: index of the PSE PI
+ * @pw_req: power requested
+ * @extack: extack for reporting useful error messages
+ *
+ * Return: Previous power allocated on success and failure value on error
+ */
+static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id,
+ const unsigned int pw_req,
+ struct netlink_ext_ack *extack)
+{
+ struct pse_pi *pi = &pcdev->pi[id];
+ int previous_pw_allocated;
+ int pw_diff, ret = 0;
+
+ /* We don't want pw_allocated_mW value change in the middle of an
+ * power budget update
+ */
+ mutex_lock(&pcdev->lock);
+ previous_pw_allocated = pi->pw_allocated_mW;
+ pw_diff = pw_req - previous_pw_allocated;
+ if (!pw_diff) {
+ goto out;
+ } else if (pw_diff > 0) {
+ ret = regulator_request_power_budget(pi->pw_d->supply, pw_diff);
+ if (ret) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: not enough power budget available",
+ id);
+ goto out;
+ }
+
+ } else {
+ regulator_free_power_budget(pi->pw_d->supply, -pw_diff);
+ }
+ pi->pw_allocated_mW = pw_req;
+ ret = previous_pw_allocated;
+
+out:
+ mutex_unlock(&pcdev->lock);
+ return ret;
+}
+
+/**
+ * pse_ethtool_set_pw_limit - set PSE control power limit
+ * @psec: PSE control pointer
+ * @extack: extack for reporting useful error messages
+ * @pw_limit: power limit value in mW
+ *
+ * Return: 0 on success and failure value on error
+ */
+int pse_ethtool_set_pw_limit(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ const unsigned int pw_limit)
+{
+ int uV, uA, ret, previous_pw_allocated = 0;
+ s64 tmp_64;
+
+ if (pw_limit > MAX_PI_PW)
+ return -ERANGE;
+
+ ret = regulator_get_voltage(psec->ps);
+ if (!ret) {
+ NL_SET_ERR_MSG(extack,
+ "Can't calculate the current, PSE voltage read is 0");
+ return -ERANGE;
+ }
+ if (ret < 0) {
+ NL_SET_ERR_MSG(extack,
+ "Error reading PSE voltage");
+ return ret;
+ }
+ uV = ret;
+
+ tmp_64 = pw_limit;
+ tmp_64 *= 1000000000ull;
+ /* uA = mW * 1000000000 / uV */
+ uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
+
+ /* Update power budget only in software power control case and
+ * if a Power Device is powered.
+ */
+ if (pse_pw_d_is_sw_pw_control(psec->pcdev,
+ psec->pcdev->pi[psec->id].pw_d) &&
+ psec->pcdev->pi[psec->id].admin_state_enabled &&
+ psec->pcdev->pi[psec->id].isr_pd_detected) {
+ ret = pse_pi_update_pw_budget(psec->pcdev, psec->id,
+ pw_limit, extack);
+ if (ret < 0)
+ return ret;
+ previous_pw_allocated = ret;
+ }
+
+ ret = regulator_set_current_limit(psec->ps, 0, uA);
+ if (ret < 0 && previous_pw_allocated) {
+ pse_pi_update_pw_budget(psec->pcdev, psec->id,
+ previous_pw_allocated, extack);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit);
+
+/**
+ * pse_ethtool_set_prio - Set PSE PI priority according to the budget
+ * evaluation strategy
+ * @psec: PSE control pointer
+ * @extack: extack for reporting useful error messages
+ * @prio: priovity value
+ *
+ * Return: 0 on success and failure value on error
+ */
+int pse_ethtool_set_prio(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ unsigned int prio)
+{
+ struct pse_controller_dev *pcdev = psec->pcdev;
+ const struct pse_controller_ops *ops;
+ int ret = 0;
+
+ if (!pcdev->pi[psec->id].pw_d) {
+ NL_SET_ERR_MSG(extack, "no power domain attached");
+ return -EOPNOTSUPP;
+ }
+
+ /* We don't want priority change in the middle of an
+ * enable/disable call or a priority mode change
+ */
+ mutex_lock(&pcdev->lock);
+ switch (pcdev->pi[psec->id].pw_d->budget_eval_strategy) {
+ case PSE_BUDGET_EVAL_STRAT_STATIC:
+ if (prio >= pcdev->nr_lines) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "priority %d exceed priority max %d",
+ prio, pcdev->nr_lines);
+ ret = -ERANGE;
+ goto out;
+ }
+
+ pcdev->pi[psec->id].prio = prio;
+ pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[psec->id].pw_d);
+ break;
+
+ case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
+ ops = psec->pcdev->ops;
+ if (!ops->pi_set_prio) {
+ NL_SET_ERR_MSG(extack,
+ "pse driver does not support setting port priority");
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (prio > pcdev->pis_prio_max) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "priority %d exceed priority max %d",
+ prio, pcdev->pis_prio_max);
+ ret = -ERANGE;
+ goto out;
+ }
+
+ ret = ops->pi_set_prio(pcdev, psec->id, prio);
+ break;
+
+ default:
+ ret = -EOPNOTSUPP;
+ }
+
+out:
+ mutex_unlock(&pcdev->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pse_ethtool_set_prio);
+
+bool pse_has_podl(struct pse_control *psec)
+{
+ return psec->pcdev->types & ETHTOOL_PSE_PODL;
+}
+EXPORT_SYMBOL_GPL(pse_has_podl);
+
+bool pse_has_c33(struct pse_control *psec)
+{
+ return psec->pcdev->types & ETHTOOL_PSE_C33;
+}
+EXPORT_SYMBOL_GPL(pse_has_c33);
diff --git a/drivers/net/pse-pd/pse_regulator.c b/drivers/net/pse-pd/pse_regulator.c
new file mode 100644
index 000000000..6ce6773ff
--- /dev/null
+++ b/drivers/net/pse-pd/pse_regulator.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Driver for the regulator based Ethernet Power Sourcing Equipment, without
+// auto classification support.
+//
+// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+//
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/regulator/consumer.h>
+
+struct pse_reg_priv {
+ struct pse_controller_dev pcdev;
+ struct regulator *ps; /*power source */
+ enum ethtool_podl_pse_admin_state admin_state;
+};
+
+static struct pse_reg_priv *to_pse_reg(struct pse_controller_dev *pcdev)
+{
+ return container_of(pcdev, struct pse_reg_priv, pcdev);
+}
+
+static int
+pse_reg_pi_enable(struct pse_controller_dev *pcdev, int id)
+{
+ struct pse_reg_priv *priv = to_pse_reg(pcdev);
+ int ret;
+
+ ret = regulator_enable(priv->ps);
+ if (ret)
+ return ret;
+
+ priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
+ return 0;
+}
+
+static int
+pse_reg_pi_disable(struct pse_controller_dev *pcdev, int id)
+{
+ struct pse_reg_priv *priv = to_pse_reg(pcdev);
+ int ret;
+
+ ret = regulator_disable(priv->ps);
+ if (ret)
+ return ret;
+
+ priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
+ return 0;
+}
+
+static int
+pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ struct pse_admin_state *admin_state)
+{
+ struct pse_reg_priv *priv = to_pse_reg(pcdev);
+
+ admin_state->podl_admin_state = priv->admin_state;
+
+ return 0;
+}
+
+static int
+pse_reg_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_status *pw_status)
+{
+ struct pse_reg_priv *priv = to_pse_reg(pcdev);
+ int ret;
+
+ ret = regulator_is_enabled(priv->ps);
+ if (ret < 0)
+ return ret;
+
+ if (!ret)
+ pw_status->podl_pw_status =
+ ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED;
+ else
+ pw_status->podl_pw_status =
+ ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING;
+
+ return 0;
+}
+
+static const struct pse_controller_ops pse_reg_ops = {
+ .pi_get_admin_state = pse_reg_pi_get_admin_state,
+ .pi_get_pw_status = pse_reg_pi_get_pw_status,
+ .pi_enable = pse_reg_pi_enable,
+ .pi_disable = pse_reg_pi_disable,
+};
+
+static int
+pse_reg_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pse_reg_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ if (!pdev->dev.of_node)
+ return -ENOENT;
+
+ priv->ps = devm_regulator_get_exclusive(dev, "pse");
+ if (IS_ERR(priv->ps))
+ return dev_err_probe(dev, PTR_ERR(priv->ps),
+ "failed to get PSE regulator.\n");
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = regulator_is_enabled(priv->ps);
+ if (ret < 0)
+ return ret;
+
+ if (ret)
+ priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
+ else
+ priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
+
+ priv->pcdev.owner = THIS_MODULE;
+ priv->pcdev.ops = &pse_reg_ops;
+ priv->pcdev.dev = dev;
+ priv->pcdev.types = ETHTOOL_PSE_PODL;
+ ret = devm_pse_controller_register(dev, &priv->pcdev);
+ if (ret) {
+ dev_err(dev, "failed to register PSE controller (%pe)\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static const __maybe_unused struct of_device_id pse_reg_of_match[] = {
+ { .compatible = "podl-pse-regulator", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pse_reg_of_match);
+
+static struct platform_driver pse_reg_driver = {
+ .probe = pse_reg_probe,
+ .driver = {
+ .name = "PSE regulator",
+ .of_match_table = of_match_ptr(pse_reg_of_match),
+ },
+};
+module_platform_driver(pse_reg_driver);
+
+MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>");
+MODULE_DESCRIPTION("regulator based Ethernet Power Sourcing Equipment");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:pse-regulator");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c
new file mode 100644
index 000000000..e64f13ea6
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c
@@ -0,0 +1,998 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for the microcontroller (MCU) fronting PSE silicon on various
+ * Realtek-based managed switches. The MCU speaks a 12-byte fixed-frame
+ * management protocol; this driver covers two generations of the
+ * protocol via a per-dialect opcode table and response parsers.
+ *
+ * Many PoE switch designs put a dedicated microcontroller in front of the
+ * actual PSE silicon: the host CPU talks to the MCU over I2C/SMBus or
+ * UART, and the MCU in turn manages the PSE chips on the board. The MCU
+ * speaks a small message-based protocol. The PSE chips themselves are not
+ * accessed directly; everything goes through MCU commands.
+ *
+ * This driver targets that architecture for the Realtek-family protocol.
+ * Two generations are supported: Gen1 being used on older switches where
+ * the MCU fronts and manages Broadcom PSE silicon; Gen2 being used with
+ * Realtek PSE silicon. The two share frame format and a sum-mod-256
+ * checksum but diverge on opcode numbers and on a few response layouts;
+ * this is handled by the per-dialect opcode table and parser hooks.
+ *
+ * Out of scope: PSE chips that are interfaced directly from the host
+ * without a management MCU, MCU designs that speak an unrelated protocol
+ * family, and "dumb PSE" modes where no host control is wired up at all.
+ *
+ * This core module implements the protocol, decoding/encoding of MCU
+ * responses, and the pse_controller_ops integration. Transport modules
+ * (realtek-pse-mcu-i2c, realtek-pse-mcu-uart) provide the send/recv
+ * callbacks.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/jiffies.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/unaligned.h>
+
+#include "realtek-pse-mcu.h"
+
+#define RTPSE_MCU_DEVICE_ID_RTL8238B 0x0138
+#define RTPSE_MCU_DEVICE_ID_RTL8239 0x0039
+#define RTPSE_MCU_DEVICE_ID_RTL8239C 0x0139
+#define RTPSE_MCU_DEVICE_ID_BCM59111 0xe111
+#define RTPSE_MCU_DEVICE_ID_BCM59121 0xe121
+
+#define RTPSE_MCU_PORT_STS_DISABLED 0x00
+#define RTPSE_MCU_PORT_STS_SEARCHING 0x01
+#define RTPSE_MCU_PORT_STS_DELIVERING 0x02
+#define RTPSE_MCU_PORT_STS_TEST 0x03 /* Gen1-only; reserved on Gen2 */
+#define RTPSE_MCU_PORT_STS_FAULT 0x04
+#define RTPSE_MCU_PORT_STS_OTHER_FAULT 0x05 /* Gen1-only; reserved on Gen2 */
+#define RTPSE_MCU_PORT_STS_REQUESTING 0x06
+
+/* RTPSE_MCU_PORT_SET_POWER_LIMIT_TYPE values */
+#define RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER 0x02
+
+#define RTPSE_MCU_MAX_PORTS 48
+#define RTPSE_MCU_PORT_MAX_PRIORITY 3
+
+/* Bounded resends when the MCU replies NOT_READY (busy). */
+#define RTPSE_MCU_NOT_READY_RETRIES 3
+
+/* Nominal PSE rail; 802.3at/bt operating range. */
+#define RTPSE_MCU_PSE_VOLTAGE_UV 54000000
+
+enum rtpse_mcu_cmd {
+ RTPSE_MCU_CMD_SET_GLOBAL_STATE,
+ RTPSE_MCU_CMD_GET_SYSTEM_INFO,
+ RTPSE_MCU_CMD_GET_EXT_CONFIG,
+
+ RTPSE_MCU_CMD_PORT_ENABLE,
+ RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE,
+ RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+ RTPSE_MCU_CMD_PORT_SET_PRIORITY,
+ RTPSE_MCU_CMD_PORT_GET_STATUS,
+ RTPSE_MCU_CMD_PORT_GET_POWER_STATS,
+ RTPSE_MCU_CMD_PORT_GET_CONFIG,
+ RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG,
+
+ RTPSE_MCU_NUM_CMDS,
+};
+
+struct rtpse_mcu_opcode {
+ u8 op;
+ bool valid;
+};
+
+/* Shorthand for the designated-initializer entries in dialect opcode tables. */
+#define RTPSE_MCU_OP(opc) { .op = (opc), .valid = true }
+
+/* Parsed MCU response structures (decoded from rtpse_mcu_msg replies) */
+
+struct rtpse_mcu_info {
+ u8 max_ports;
+ bool system_enable;
+ u16 device_id;
+ u8 mcu_type;
+};
+
+struct rtpse_mcu_ext_config {
+ u8 num_of_pses;
+};
+
+struct rtpse_mcu_port_status {
+ u8 sts1;
+ u8 sts2;
+ u8 sts3;
+};
+
+struct rtpse_mcu_port_measurement {
+ u16 voltage_raw; /* 64.45mV/LSB */
+ u16 current_raw; /* 1mA/LSB */
+ u16 temperature_raw; /* T(mC) = 1250 * (220 - raw) */
+ u16 power_raw; /* 100mW/LSB */
+};
+
+struct rtpse_mcu_port_config {
+ bool enable;
+};
+
+struct rtpse_mcu_port_ext_config {
+ u8 max_power;
+ u8 priority;
+};
+
+struct rtpse_mcu_dialect {
+ struct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS];
+
+ /*
+ * Response parsers for the fields that differ between dialects; each
+ * dialect supplies its own. Other responses share one layout and are
+ * decoded directly - a dialect that diverges there must add a hook,
+ * as a mismatched layout cannot be detected (the checksum still passes).
+ */
+ void (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info);
+ int (*parse_port_class)(const struct rtpse_mcu_port_status *status);
+ const char *(*mcu_type_str)(unsigned int mcu_type);
+};
+
+struct rtpse_mcu_chip_info {
+ const char *name;
+ u32 max_mW_per_port;
+ enum rtpse_mcu_cmd pw_set_cmd; /* command used by set_pw_limit */
+ u32 pw_set_lsb_mW; /* LSB of pw_set_cmd value, in mW */
+ u32 pw_read_lsb_mW; /* LSB of ext_config.max_power read-back, in mW */
+};
+
+static const struct rtpse_mcu_chip_info rtl8238b_info = {
+ .max_mW_per_port = 30000,
+ .name = "RTL8238B",
+ .pw_read_lsb_mW = 200,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ .pw_set_lsb_mW = 200,
+};
+
+static const struct rtpse_mcu_chip_info rtl8239_info = {
+ .max_mW_per_port = 90000,
+ .name = "RTL8239",
+ .pw_read_lsb_mW = 400,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+ .pw_set_lsb_mW = 400,
+};
+
+static const struct rtpse_mcu_chip_info rtl8239c_info = {
+ .max_mW_per_port = 90000,
+ .name = "RTL8239C",
+ .pw_read_lsb_mW = 400,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+ .pw_set_lsb_mW = 400,
+};
+
+static const struct rtpse_mcu_chip_info bcm59111_info = {
+ .max_mW_per_port = 30000,
+ .name = "BCM59111",
+ .pw_read_lsb_mW = 200,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ .pw_set_lsb_mW = 200,
+};
+
+static const struct rtpse_mcu_chip_info bcm59121_info = {
+ /*
+ * BCM59121 is a 60W Type-3 part, but known boards run it at 802.3at
+ * and the Gen1 dialect has only the 8-bit/0.2W set command (<=51W);
+ * cap at the 30W the hardware actually offers.
+ */
+ .max_mW_per_port = 30000,
+ .name = "BCM59121",
+ .pw_read_lsb_mW = 200,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ .pw_set_lsb_mW = 200,
+};
+
+/* Helpers and basic functions */
+
+static struct rtpse_mcu_ctrl *to_rtpse_mcu_ctrl(struct pse_controller_dev *pcdev)
+{
+ return container_of(pcdev, struct rtpse_mcu_ctrl, pcdev);
+}
+
+static void rtpse_mcu_msg_init(struct rtpse_mcu_msg *msg, u8 opcode)
+{
+ memset(msg, 0xff, sizeof(*msg));
+ msg->opcode = opcode;
+}
+
+static u8 rtpse_mcu_checksum(const u8 *buf, size_t len)
+{
+ u8 sum = 0;
+
+ while (len--)
+ sum += *buf++;
+ return sum;
+}
+
+static int rtpse_mcu_do_xfer(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp)
+{
+ unsigned int tries;
+ int ret;
+
+ for (tries = 0; ; tries++) {
+ scoped_guard(mutex, &pse->mutex) {
+ /* Rolling seq_num (skip 0) so a stale/all-zero reply can't match. */
+ if (++pse->seq == 0)
+ pse->seq = 1;
+ req->seq_num = pse->seq;
+ req->checksum = rtpse_mcu_checksum((u8 *)req, RTPSE_MCU_MSG_SIZE - 1);
+
+ ret = pse->transport->send(pse, req);
+ if (ret)
+ return ret;
+
+ /* Pace the base reply delay; the transport waits its own way. */
+ msleep(RTPSE_MCU_RESPONSE_MS);
+
+ memset(resp, 0, sizeof(*resp));
+ ret = pse->transport->recv(pse, req, resp);
+ if (ret)
+ return ret;
+ }
+
+ /* NOT_READY: MCU busy, wants the command resent; bounded retry. */
+ if (resp->opcode != RTPSE_MCU_OPCODE_NOT_READY ||
+ tries >= RTPSE_MCU_NOT_READY_RETRIES)
+ break;
+ msleep(RTPSE_MCU_RESPONSE_MS);
+ }
+
+ /* Explicit MCU error opcodes (Gen1); map to a meaningful errno. */
+ switch (resp->opcode) {
+ case RTPSE_MCU_OPCODE_INCOMPLETE:
+ return -EBADE;
+ case RTPSE_MCU_OPCODE_BAD_CSUM:
+ return -EBADMSG;
+ case RTPSE_MCU_OPCODE_NOT_READY:
+ return -EAGAIN;
+ }
+
+ if (resp->opcode != req->opcode ||
+ resp->seq_num != req->seq_num ||
+ resp->checksum != rtpse_mcu_checksum((u8 *)resp, RTPSE_MCU_MSG_SIZE - 1))
+ return -EBADMSG;
+
+ return 0;
+}
+
+static int rtpse_mcu_port_query(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode,
+ struct rtpse_mcu_msg *resp)
+{
+ struct rtpse_mcu_msg req;
+ int ret;
+
+ rtpse_mcu_msg_init(&req, opcode);
+ req.payload[0] = port;
+
+ ret = rtpse_mcu_do_xfer(pse, &req, resp);
+ if (ret)
+ return ret;
+
+ if (resp->payload[0] != port)
+ return -EIO;
+
+ return 0;
+}
+
+static int rtpse_mcu_port_cmd(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, u8 arg)
+{
+ struct rtpse_mcu_msg req, resp;
+ int ret;
+
+ rtpse_mcu_msg_init(&req, opcode);
+ req.payload[0] = port;
+ req.payload[1] = arg;
+
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ if (resp.payload[0] != port || resp.payload[1] != 0)
+ return -EIO;
+
+ return 0;
+}
+
+/* Global operations */
+
+static int rtpse_mcu_get_info(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
+{
+ struct rtpse_mcu_msg req, resp;
+ const struct rtpse_mcu_opcode *opc;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_SYSTEM_INFO];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ pse->dialect->parse_system_info(resp.payload, info);
+ return 0;
+}
+
+static int rtpse_mcu_get_ext_config(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_ext_config *config)
+{
+ struct rtpse_mcu_msg req, resp;
+ const struct rtpse_mcu_opcode *opc;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_EXT_CONFIG];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ config->num_of_pses = resp.payload[6];
+
+ return 0;
+}
+
+static int rtpse_mcu_set_global_state(struct rtpse_mcu_ctrl *pse, bool enable)
+{
+ struct rtpse_mcu_msg req, resp;
+ const struct rtpse_mcu_opcode *opc;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_SET_GLOBAL_STATE];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ req.payload[0] = enable ? 0x1 : 0x0;
+
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ return (resp.payload[0] == 0x0) ? 0 : -EIO;
+}
+
+/* Port operations */
+
+static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_status *status)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_STATUS];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ status->sts1 = resp.payload[1];
+ status->sts2 = resp.payload[2];
+ status->sts3 = resp.payload[3];
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_measurement(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_measurement *measurement)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_POWER_STATS];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ measurement->voltage_raw = get_unaligned_be16(&resp.payload[1]);
+ measurement->current_raw = get_unaligned_be16(&resp.payload[3]);
+ measurement->temperature_raw = get_unaligned_be16(&resp.payload[5]);
+ measurement->power_raw = get_unaligned_be16(&resp.payload[7]);
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_config(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_config *config)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_CONFIG];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ config->enable = (resp.payload[1] == 1);
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_ext_config(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_ext_config *config)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ config->max_power = resp.payload[3];
+ config->priority = resp.payload[4];
+
+ return 0;
+}
+
+static int rtpse_mcu_port_set_state(struct rtpse_mcu_ctrl *pse, unsigned int port, bool enable)
+{
+ const struct rtpse_mcu_opcode *opc;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_ENABLE];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ return rtpse_mcu_port_cmd(pse, port, opc->op, enable ? 0x1 : 0x0);
+}
+
+/* PSE controller ops */
+
+static int rtpse_mcu_port_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ struct pse_admin_state *admin_state)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_config config;
+ int ret;
+
+ ret = rtpse_mcu_port_get_config(pse, id, &config);
+ if (ret)
+ return ret;
+
+ admin_state->c33_admin_state = config.enable ? ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED :
+ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+ return 0;
+}
+
+static int rtpse_mcu_port_get_pw_status(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_status *pw_status)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_status status;
+ int ret;
+
+ ret = rtpse_mcu_port_get_status(pse, id, &status);
+ if (ret)
+ return ret;
+
+ switch (status.sts1) {
+ case RTPSE_MCU_PORT_STS_DISABLED:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+ break;
+ case RTPSE_MCU_PORT_STS_SEARCHING:
+ case RTPSE_MCU_PORT_STS_REQUESTING:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+ break;
+ case RTPSE_MCU_PORT_STS_DELIVERING:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+ break;
+ case RTPSE_MCU_PORT_STS_TEST:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_TEST;
+ break;
+ case RTPSE_MCU_PORT_STS_FAULT:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
+ break;
+ case RTPSE_MCU_PORT_STS_OTHER_FAULT:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT;
+ break;
+ default:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_pw_class(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_status status;
+ int ret;
+
+ ret = rtpse_mcu_port_get_status(pse, id, &status);
+ if (ret)
+ return ret;
+
+ /*
+ * As per datasheet, the classification result is only valid when in
+ * one of those operational modes, otherwise not.
+ */
+ switch (status.sts1) {
+ case RTPSE_MCU_PORT_STS_DISABLED:
+ case RTPSE_MCU_PORT_STS_SEARCHING:
+ case RTPSE_MCU_PORT_STS_DELIVERING:
+ case RTPSE_MCU_PORT_STS_REQUESTING:
+ return pse->dialect->parse_port_class(&status);
+ default:
+ /*
+ * No class to report, return 0 instead. This is indistinguishable
+ * from a real class-0 PD but userspace disambiguates via the
+ * power status.
+ */
+ return 0;
+ }
+}
+
+static int rtpse_mcu_port_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_measurement measurement;
+ int ret;
+
+ ret = rtpse_mcu_port_get_measurement(pse, id, &measurement);
+ if (ret)
+ return ret;
+
+ /* 100mW per LSB */
+ return measurement.power_raw * 100U;
+}
+
+static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_measurement measurement;
+ int ret;
+ u32 uV;
+
+ ret = rtpse_mcu_port_get_measurement(pse, id, &measurement);
+ if (ret)
+ return ret;
+
+ /* 64.45mV per LSB */
+ uV = measurement.voltage_raw * 64450U;
+
+ /*
+ * Idle ports measure 0V, which the core rejects when turning a power
+ * limit into a current limit. Fall back to the nominal rail so a limit
+ * can be set before a PD is attached.
+ */
+ if (!uV)
+ return RTPSE_MCU_PSE_VOLTAGE_UV;
+
+ return min_t(u32, uV, INT_MAX);
+}
+
+static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id)
+{
+ return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true);
+}
+
+static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id)
+{
+ return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false);
+}
+
+static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_ext_config config;
+ int ret;
+
+ ret = rtpse_mcu_port_get_ext_config(pse, id, &config);
+ if (ret)
+ return ret;
+
+ /*
+ * The MCU's raw max_power byte can scale above the chip's rated cap;
+ * clamp to the same bound set_pw_limit() and the advertised range use.
+ */
+ return min_t(u32, config.max_power * pse->chip->pw_read_lsb_mW,
+ pse->chip->max_mW_per_port);
+}
+
+static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW)
+{
+ const struct rtpse_mcu_opcode *type_opc, *val_opc;
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ const struct rtpse_mcu_chip_info *chip = pse->chip;
+ u8 prg_val;
+ int ret;
+
+ if (max_mW < 0 || max_mW > chip->max_mW_per_port)
+ return -ERANGE;
+
+ type_opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE];
+ val_opc = &pse->dialect->opcode[chip->pw_set_cmd];
+ /* pw_set_lsb_mW is the divisor below; reject a chip that lacks it. */
+ if (!type_opc->valid || !val_opc->valid || !chip->pw_set_lsb_mW)
+ return -EOPNOTSUPP;
+
+ /*
+ * Round up so a sub-LSB request maps to one LSB, not silently to 0;
+ * an explicit 0 still yields 0, and LSB-aligned maxima can't overshoot.
+ */
+ prg_val = min_t(unsigned int, DIV_ROUND_UP(max_mW, chip->pw_set_lsb_mW), U8_MAX);
+
+ /*
+ * Program the value before switching to user-defined mode. The two
+ * commands aren't atomic, but this order never leaves a stale cap: a
+ * failure keeps the previous cap, or (already user mode) the requested.
+ */
+ ret = rtpse_mcu_port_cmd(pse, id, val_opc->op, prg_val);
+ if (ret)
+ return ret;
+
+ return rtpse_mcu_port_cmd(pse, id, type_opc->op, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER);
+}
+
+static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_limit_ranges *out)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct ethtool_c33_pse_pw_limit_range *range;
+
+ range = kzalloc_obj(*range);
+ if (!range)
+ return -ENOMEM;
+
+ range[0].min = 0;
+ range[0].max = pse->chip->max_mW_per_port;
+
+ out->c33_pw_limit_ranges = range;
+ return 1;
+}
+
+static int rtpse_mcu_port_get_prio(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_ext_config config;
+ int ret;
+
+ ret = rtpse_mcu_port_get_ext_config(pse, id, &config);
+ if (ret)
+ return ret;
+
+ /* Clamp to the advertised max; set_prio() and pis_prio_max use the same bound. */
+ return min_t(u8, config.priority, RTPSE_MCU_PORT_MAX_PRIORITY);
+}
+
+static int rtpse_mcu_port_set_prio(struct pse_controller_dev *pcdev, int id, unsigned int prio)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ const struct rtpse_mcu_opcode *opc;
+
+ if (prio > RTPSE_MCU_PORT_MAX_PRIORITY)
+ return -ERANGE;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_PRIORITY];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ return rtpse_mcu_port_cmd(pse, id, opc->op, prio);
+}
+
+static const struct pse_controller_ops rtpse_mcu_ops = {
+ .pi_get_admin_state = rtpse_mcu_port_get_admin_state,
+ .pi_get_pw_status = rtpse_mcu_port_get_pw_status,
+ .pi_get_pw_class = rtpse_mcu_port_get_pw_class,
+ .pi_get_actual_pw = rtpse_mcu_port_get_actual_pw,
+ .pi_enable = rtpse_mcu_port_enable,
+ .pi_disable = rtpse_mcu_port_disable,
+ .pi_get_voltage = rtpse_mcu_port_get_voltage,
+ .pi_get_pw_limit = rtpse_mcu_port_get_pw_limit,
+ .pi_set_pw_limit = rtpse_mcu_port_set_pw_limit,
+ .pi_get_pw_limit_ranges = rtpse_mcu_port_get_pw_limit_ranges,
+ .pi_get_prio = rtpse_mcu_port_get_prio,
+ .pi_set_prio = rtpse_mcu_port_set_prio,
+};
+
+static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
+{
+ struct rtpse_mcu_ext_config ext_config;
+ unsigned long deadline;
+ int ret;
+
+ /*
+ * A booting MCU may stay silent (-ETIMEDOUT), not ACK its address
+ * (-ENXIO / -EREMOTEIO), report not-ready (-EAGAIN), or emit a
+ * corrupt/partial frame (-EBADMSG / -EBADE). Retry those within a
+ * bounded window; other errors (e.g. -EOPNOTSUPP) are fatal and fail
+ * immediately.
+ */
+ deadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS);
+ do {
+ ret = rtpse_mcu_get_info(pse, info);
+ if (ret != -ETIMEDOUT && ret != -ENXIO && ret != -EREMOTEIO &&
+ ret != -EAGAIN && ret != -EBADMSG && ret != -EBADE)
+ break;
+ msleep(RTPSE_MCU_BOOT_RETRY_MS);
+ } while (time_before(jiffies, deadline));
+ if (ret)
+ return dev_err_probe(pse->dev, ret, "failed to read MCU info\n");
+
+ switch (info->device_id) {
+ case RTPSE_MCU_DEVICE_ID_RTL8238B:
+ pse->chip = &rtl8238b_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_RTL8239:
+ pse->chip = &rtl8239_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_RTL8239C:
+ pse->chip = &rtl8239c_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_BCM59111:
+ pse->chip = &bcm59111_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_BCM59121:
+ pse->chip = &bcm59121_info;
+ break;
+ default:
+ return dev_err_probe(pse->dev, -EINVAL, "unknown PSE id 0x%x\n",
+ info->device_id);
+ }
+
+ if (!info->max_ports || info->max_ports > RTPSE_MCU_MAX_PORTS)
+ return dev_err_probe(pse->dev, -EINVAL,
+ "MCU reports invalid port count %u\n", info->max_ports);
+
+ ret = rtpse_mcu_get_ext_config(pse, &ext_config);
+ if (ret)
+ return dev_err_probe(pse->dev, ret, "failed to read MCU ext config\n");
+
+ dev_info(pse->dev, "%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\n",
+ pse->dialect->mcu_type_str(info->mcu_type), pse->chip->name,
+ info->device_id, info->max_ports, ext_config.num_of_pses);
+ return 0;
+}
+
+static void rtpse_mcu_global_disable(void *data)
+{
+ struct rtpse_mcu_ctrl *pse = data;
+
+ rtpse_mcu_set_global_state(pse, false);
+}
+
+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)
+{
+ const struct rtpse_mcu_match_data *match;
+ struct rtpse_mcu_info info;
+ struct gpio_desc *gpiod;
+ int ret;
+
+ BUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE);
+
+ ret = devm_mutex_init(pse->dev, &pse->mutex);
+ if (ret)
+ return ret;
+
+ match = device_get_match_data(pse->dev);
+ if (!match)
+ return dev_err_probe(pse->dev, -ENODEV, "missing match data\n");
+ pse->dialect = match->dialect;
+
+ /*
+ * Catch a dialect that forgot to set one of the required hooks at
+ * probe time, rather than NULL-deref'ing later from a fast path.
+ */
+ if (!pse->dialect ||
+ !pse->dialect->parse_system_info ||
+ !pse->dialect->parse_port_class ||
+ !pse->dialect->mcu_type_str)
+ return dev_err_probe(pse->dev, -EINVAL,
+ "dialect for chip is incomplete\n");
+
+ /*
+ * Release the MCU from reset before the first transaction; the
+ * boot-retry loop in discover() waits for it to answer.
+ */
+ gpiod = devm_gpiod_get_optional(pse->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ return dev_err_probe(pse->dev, PTR_ERR(gpiod),
+ "failed to get reset gpio\n");
+
+ ret = rtpse_mcu_discover(pse, &info);
+ if (ret)
+ return ret;
+
+ /*
+ * Some boards gate all ports through a hardware line; deassert it only
+ * after the MCU is confirmed, so a discover failure never ungates the
+ * ports. It is then left to the MCU - not re-gated on unbind or a later
+ * probe error - so a driver reload doesn't black out PoE.
+ */
+ gpiod = devm_gpiod_get_optional(pse->dev, "disable-ports", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ return dev_err_probe(pse->dev, PTR_ERR(gpiod),
+ "failed to get disable-ports gpio\n");
+
+ if (!info.system_enable) {
+ ret = rtpse_mcu_set_global_state(pse, true);
+ /* Dialects without a global-state concept (e.g. Gen1) return
+ * -EOPNOTSUPP; treat that as "no separate enable required".
+ */
+ if (ret && ret != -EOPNOTSUPP)
+ return dev_err_probe(pse->dev, ret,
+ "failed to enable PSE system\n");
+ if (!ret) {
+ ret = devm_add_action_or_reset(pse->dev,
+ rtpse_mcu_global_disable, pse);
+ if (ret)
+ return ret;
+ }
+ }
+
+ /*
+ * Depending on the MCU firmware configuration (which might be different
+ * for every board), it isn't known whether the PoE subsystem is active or
+ * inactive by default. At this stage, the PSE chips might already deliver
+ * power to PDs without any explicit enable.
+ */
+
+ /* pcdev.owner is set by the transport, so the registered controller
+ * pins the transport module that owns the live device, not the core.
+ */
+ pse->pcdev.ops = &rtpse_mcu_ops;
+ pse->pcdev.dev = pse->dev;
+ pse->pcdev.types = ETHTOOL_PSE_C33;
+ pse->pcdev.nr_lines = info.max_ports;
+ pse->pcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY;
+ pse->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
+
+ return devm_pse_controller_register(pse->dev, &pse->pcdev);
+}
+EXPORT_SYMBOL_GPL(rtpse_mcu_register);
+
+static void rtpse_mcu_gen2_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)
+{
+ info->max_ports = payload[1];
+ info->system_enable = (payload[2] == 0x1);
+ info->device_id = get_unaligned_be16(&payload[3]);
+ info->mcu_type = payload[6];
+}
+
+static int rtpse_mcu_gen2_parse_port_class(const struct rtpse_mcu_port_status *status)
+{
+ /* Class lives in the upper nibble of sts2. */
+ return FIELD_GET(GENMASK(7, 4), status->sts2);
+}
+
+static const char *rtpse_mcu_gen2_mcu_type_str(unsigned int mcu_type)
+{
+ switch (mcu_type) {
+ case 0x00: return "GigaDevice GD32F310";
+ case 0x01: return "GigaDevice GD32F230";
+ case 0x02: return "GigaDevice GD32F303";
+ case 0x03: return "GigaDevice GD32F103";
+ case 0x04: return "GigaDevice GD32E103";
+ case 0x10: return "Nuvoton M0516";
+ case 0x11: return "Nuvoton M0564";
+ case 0x12: return "Nuvoton NUC029";
+ default: return "unknown";
+ }
+}
+
+static void rtpse_mcu_gen1_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)
+{
+ info->max_ports = payload[1];
+ /* Gen1 has no explicit system_enable byte; the closest analog is the
+ * "remote enable" bit in the system-status flags at payload[7].
+ */
+ info->system_enable = !!(payload[7] & BIT(2));
+ info->device_id = get_unaligned_be16(&payload[3]);
+ info->mcu_type = payload[6];
+}
+
+static int rtpse_mcu_gen1_parse_port_class(const struct rtpse_mcu_port_status *status)
+{
+ /* Gen1 puts the detected class in payload[3] (== sts3) directly.
+ * Mask to the low nibble; class is 0..8 and any high bits would be
+ * noise.
+ */
+ return status->sts3 & 0x0f;
+}
+
+static const char *rtpse_mcu_gen1_mcu_type_str(unsigned int mcu_type)
+{
+ switch (mcu_type) {
+ case 0x00: return "ST Micro ST32F100";
+ case 0x01: return "Nuvoton M05xx LAN";
+ case 0x02: return "ST Micro STF030C8";
+ case 0x03: return "Nuvoton M058SAN";
+ case 0x04: return "Nuvoton NUC122";
+ default: return "unknown";
+ }
+}
+
+/* Map each logical command the core issues to its per-dialect opcode. */
+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = {
+ .parse_system_info = rtpse_mcu_gen2_parse_system_info,
+ .parse_port_class = rtpse_mcu_gen2_parse_port_class,
+ .mcu_type_str = rtpse_mcu_gen2_mcu_type_str,
+ .opcode = {
+ [RTPSE_MCU_CMD_SET_GLOBAL_STATE] = RTPSE_MCU_OP(0x00),
+ [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x40),
+ [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x4a),
+
+ [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x01),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x13),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14),
+ [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x15),
+ [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x42),
+ [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x44),
+ [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x48),
+ [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x49),
+ },
+};
+
+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = {
+ .parse_system_info = rtpse_mcu_gen1_parse_system_info,
+ .parse_port_class = rtpse_mcu_gen1_parse_port_class,
+ .mcu_type_str = rtpse_mcu_gen1_mcu_type_str,
+ .opcode = {
+ [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x20),
+ [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x2b),
+
+ [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x00),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x16),
+ [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x1a),
+ [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x21),
+ [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x30),
+ [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x25),
+ [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x26),
+ },
+};
+
+const struct rtpse_mcu_match_data rtpse_mcu_gen1_data = {
+ .dialect = &rtpse_mcu_dialect_gen1,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen1_data);
+
+const struct rtpse_mcu_match_data rtpse_mcu_gen2_data = {
+ .dialect = &rtpse_mcu_dialect_gen2,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_data);
+
+/* Same dialect as gen2, but the MCU expects raw-I2C framing. */
+const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data = {
+ .dialect = &rtpse_mcu_dialect_gen2,
+ .native_i2c = true,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_i2c_data);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (core)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-i2c.c b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c
new file mode 100644
index 000000000..b6769ac1d
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+
+#include "realtek-pse-mcu.h"
+
+/*
+ * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so
+ * the response is normally ready on the very first read. For commands the
+ * MCU produces more slowly, keep polling at the typical response cadence
+ * up to the worst-case ceiling.
+ */
+#define RTPSE_MCU_I2C_RETRY_MS RTPSE_MCU_RESPONSE_MS
+#define RTPSE_MCU_I2C_MAX_TRIES (RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS)
+
+static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+
+ /* Send opcode as SMBus command byte; remaining 11 bytes as block data */
+ return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1,
+ (const u8 *)req + 1);
+}
+
+static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+ int tries, ret;
+
+ for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
+ if (tries > 0)
+ msleep(RTPSE_MCU_I2C_RETRY_MS);
+
+ /* MCU needs 0x00 as command byte for read */
+ ret = i2c_smbus_read_i2c_block_data(client, 0x00,
+ RTPSE_MCU_MSG_SIZE,
+ (u8 *)resp);
+ if (ret < 0)
+ return ret;
+ if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
+ return 0;
+ }
+
+ return -ETIMEDOUT;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = {
+ .send = rtpse_mcu_i2c_smbus_send,
+ .recv = rtpse_mcu_i2c_smbus_recv,
+};
+
+static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+ int ret;
+
+ ret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE);
+ if (ret < 0)
+ return ret;
+ return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO;
+}
+
+static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+ int tries, ret;
+
+ for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
+ if (tries > 0)
+ msleep(RTPSE_MCU_I2C_RETRY_MS);
+
+ ret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE);
+ if (ret < 0)
+ return ret;
+ if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
+ return 0;
+ }
+
+ return -ETIMEDOUT;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = {
+ .send = rtpse_mcu_i2c_native_send,
+ .recv = rtpse_mcu_i2c_native_recv,
+};
+
+static int rtpse_mcu_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const struct rtpse_mcu_match_data *match;
+ struct rtpse_mcu_ctrl *pse;
+ bool use_native;
+
+ match = device_get_match_data(dev);
+ if (!match)
+ return dev_err_probe(dev, -ENODEV, "missing match data\n");
+
+ /* The framing (raw I2C vs SMBus) is carried by the match data. */
+ use_native = match->native_i2c;
+ if (use_native) {
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return dev_err_probe(dev, -EOPNOTSUPP,
+ "plain-I2C MCU protocol requires I2C-capable adapter\n");
+ } else {
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+ return dev_err_probe(dev, -EOPNOTSUPP,
+ "SMBus MCU protocol requires SMBus I2C-block support\n");
+ }
+
+ pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL);
+ if (!pse)
+ return -ENOMEM;
+
+ pse->dev = dev;
+ pse->pcdev.owner = THIS_MODULE;
+ pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops;
+
+ return rtpse_mcu_register(pse);
+}
+
+static const struct of_device_id rtpse_mcu_i2c_of_match[] = {
+ { .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data },
+ { .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data },
+ { .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match);
+
+static struct i2c_driver rtpse_mcu_i2c_driver = {
+ .driver = {
+ .name = "realtek-pse-mcu-i2c",
+ .of_match_table = rtpse_mcu_i2c_of_match,
+ },
+ .probe = rtpse_mcu_i2c_probe,
+};
+module_i2c_driver(rtpse_mcu_i2c_driver);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-uart.c b/drivers/net/pse-pd/realtek-pse-mcu-uart.c
new file mode 100644
index 000000000..9baa17d8d
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/serdev.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include "realtek-pse-mcu.h"
+
+#define RTPSE_MCU_UART_BAUD_DEFAULT 19200
+#define RTPSE_MCU_UART_TX_TIMEOUT msecs_to_jiffies(100)
+#define RTPSE_MCU_UART_RX_TIMEOUT msecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS)
+
+struct rtpse_mcu_uart {
+ struct rtpse_mcu_ctrl pse;
+ struct serdev_device *serdev;
+ struct completion rx_done;
+ spinlock_t rx_lock; /* protects rx_buf and rx_len */
+ size_t rx_len;
+ u8 rx_buf[RTPSE_MCU_MSG_SIZE];
+};
+
+#define to_rtpse_mcu_uart(p) container_of(p, struct rtpse_mcu_uart, pse)
+
+/*
+ * No framing is done here: a glitched frame costs one transaction, then
+ * the next _send re-frames from rx_len 0. Resync works by returning count
+ * (not take), dropping any overflow so serdev keeps no leftover to bleed
+ * into the next frame.
+ */
+static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev,
+ const u8 *buf, size_t count)
+{
+ struct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev);
+ size_t take;
+
+ scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+ take = min(count, sizeof(ctx->rx_buf) - ctx->rx_len);
+ if (take) {
+ memcpy(ctx->rx_buf + ctx->rx_len, buf, take);
+ ctx->rx_len += take;
+ if (ctx->rx_len == sizeof(ctx->rx_buf))
+ complete(&ctx->rx_done);
+ }
+ }
+
+ /* consume all to avoid desync/misalignment */
+ return count;
+}
+
+static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = {
+ .receive_buf = rtpse_mcu_uart_receive,
+ .write_wakeup = serdev_device_write_wakeup,
+};
+
+static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+ struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
+ int written;
+
+ /* clear any leftover rx state before transmitting */
+ scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+ reinit_completion(&ctx->rx_done);
+ ctx->rx_len = 0;
+ }
+
+ written = serdev_device_write(ctx->serdev, (const u8 *)req, sizeof(*req),
+ RTPSE_MCU_UART_TX_TIMEOUT);
+ if (written < 0)
+ return written;
+ if (written != sizeof(*req))
+ return -EIO;
+
+ return 0;
+}
+
+static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse,
+ const struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp)
+{
+ struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
+
+ if (!wait_for_completion_timeout(&ctx->rx_done, RTPSE_MCU_UART_RX_TIMEOUT))
+ return -ETIMEDOUT;
+
+ scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+ if (ctx->rx_len != sizeof(*resp))
+ return -EIO;
+
+ memcpy(resp, ctx->rx_buf, sizeof(*resp));
+ }
+ return 0;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = {
+ .send = rtpse_mcu_uart_send,
+ .recv = rtpse_mcu_uart_recv,
+};
+
+static int rtpse_mcu_uart_probe(struct serdev_device *serdev)
+{
+ u32 speed = RTPSE_MCU_UART_BAUD_DEFAULT;
+ struct device *dev = &serdev->dev;
+ struct rtpse_mcu_uart *ctx;
+ unsigned int baud;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->serdev = serdev;
+ ctx->pse.dev = dev;
+ ctx->pse.pcdev.owner = THIS_MODULE;
+ ctx->pse.transport = &rtpse_mcu_uart_transport_ops;
+ init_completion(&ctx->rx_done);
+ spin_lock_init(&ctx->rx_lock);
+
+ serdev_device_set_drvdata(serdev, ctx);
+ serdev_device_set_client_ops(serdev, &rtpse_mcu_uart_serdev_ops);
+
+ ret = devm_serdev_device_open(dev, serdev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to open serdev\n");
+
+ fwnode_property_read_u32(dev_fwnode(dev), "current-speed", &speed);
+
+ baud = serdev_device_set_baudrate(serdev, speed);
+ if (baud != speed)
+ dev_warn(dev, "could not set baudrate %u, controller uses %u\n",
+ speed, baud);
+
+ serdev_device_set_flow_control(serdev, false);
+
+ ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+ if (ret)
+ dev_warn(dev, "could not set parity to none: %d\n", ret);
+
+ return rtpse_mcu_register(&ctx->pse);
+}
+
+static const struct of_device_id rtpse_mcu_uart_of_match[] = {
+ { .compatible = "realtek,pse-mcu-gen1", .data = &rtpse_mcu_gen1_data },
+ { .compatible = "realtek,pse-mcu-gen2", .data = &rtpse_mcu_gen2_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match);
+
+static struct serdev_device_driver rtpse_mcu_uart_driver = {
+ .driver = {
+ .name = "realtek-pse-mcu-uart",
+ .of_match_table = rtpse_mcu_uart_of_match,
+ },
+ .probe = rtpse_mcu_uart_probe,
+};
+module_serdev_device_driver(rtpse_mcu_uart_driver);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (UART transport)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h
new file mode 100644
index 000000000..d75e91669
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _REALTEK_PSE_MCU_H
+#define _REALTEK_PSE_MCU_H
+
+#include <linux/mutex.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/types.h>
+
+/*
+ * Time the MCU itself needs between accepting a request and having a
+ * response ready. These are properties of the MCU firmware, not of the
+ * underlying transport: the core paces transactions by RTPSE_MCU_RESPONSE_MS
+ * and both transports size their per-transaction recv ceiling from
+ * RTPSE_MCU_RESPONSE_MAX_MS, since some commands are documented as
+ * needing up to ~1s to produce a reply.
+ */
+#define RTPSE_MCU_RESPONSE_MS 25
+#define RTPSE_MCU_RESPONSE_MAX_MS 1000
+
+/*
+ * Total time to keep retrying the first MCU read at probe, and the pause
+ * between attempts. Right after reset-gpios is deasserted the MCU may not
+ * answer on the bus yet; give it a bounded window to come up before
+ * declaring the probe failed.
+ */
+#define RTPSE_MCU_BOOT_TIMEOUT_MS 3000
+#define RTPSE_MCU_BOOT_RETRY_MS 100
+
+#define RTPSE_MCU_MSG_SIZE 12
+
+struct rtpse_mcu_msg {
+ u8 opcode;
+ u8 seq_num;
+ u8 payload[9];
+ u8 checksum;
+} __packed;
+
+/*
+ * MCU status opcodes (seen on the Gen1 dialect; Gen2 never emits them).
+ * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient.
+ */
+#define RTPSE_MCU_OPCODE_INCOMPLETE 0xfd /* -EBADE */
+#define RTPSE_MCU_OPCODE_BAD_CSUM 0xfe /* -EBADMSG */
+#define RTPSE_MCU_OPCODE_NOT_READY 0xff /* -EAGAIN */
+
+/*
+ * A polling transport can stop here: the reply to this request (opcode and
+ * seq_num) or a terminal error. The seq_num rejects a stale normal reply; the
+ * terminal errors match unconditionally, as a request the MCU couldn't parse
+ * carries no seq_num to correlate against.
+ */
+static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req,
+ const struct rtpse_mcu_msg *resp)
+{
+ return (resp->opcode == req->opcode && resp->seq_num == req->seq_num) ||
+ resp->opcode == RTPSE_MCU_OPCODE_INCOMPLETE ||
+ resp->opcode == RTPSE_MCU_OPCODE_BAD_CSUM;
+}
+
+/* Opaque to transports; defined in realtek-pse-mcu-core.c. */
+struct rtpse_mcu_dialect;
+struct rtpse_mcu_chip_info;
+struct rtpse_mcu_ctrl;
+
+/* Per-compatible match data (the of_match .data). */
+struct rtpse_mcu_match_data {
+ const struct rtpse_mcu_dialect *dialect;
+ bool native_i2c; /* raw-I2C framing (vs SMBus); I2C transport only */
+};
+
+struct rtpse_mcu_transport_ops {
+ int (*send)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req);
+ int (*recv)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp);
+};
+
+struct rtpse_mcu_ctrl {
+ struct device *dev;
+ struct pse_controller_dev pcdev;
+ struct mutex mutex; /* serializes MCU request/response transactions */
+ const struct rtpse_mcu_dialect *dialect;
+ const struct rtpse_mcu_chip_info *chip;
+ const struct rtpse_mcu_transport_ops *transport;
+ u8 seq; /* rolling request seq_num, echoed by the MCU */
+};
+
+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse);
+
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen1_data;
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_data;
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data;
+
+#endif
diff --git a/drivers/net/pse-pd/si3474.c b/drivers/net/pse-pd/si3474.c
new file mode 100644
index 000000000..1845b9c51
--- /dev/null
+++ b/drivers/net/pse-pd/si3474.c
@@ -0,0 +1,578 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Skyworks Si3474 PoE PSE Controller
+ *
+ * Chip Architecture & Terminology:
+ *
+ * The Si3474 is a single-chip PoE PSE controller managing 8 physical power
+ * delivery channels. Internally, it's structured into two logical "Quads".
+ *
+ * Quad 0: Manages physical channels ('ports' in datasheet) 0, 1, 2, 3
+ * Quad 1: Manages physical channels ('ports' in datasheet) 4, 5, 6, 7
+ *
+ * Each Quad is accessed via a separate I2C address. The base address range is
+ * set by hardware pins A1-A4, and the specific address selects Quad 0 (usually
+ * the lower/even address) or Quad 1 (usually the higher/odd address).
+ * See datasheet Table 2.2 for the address mapping.
+ *
+ * While the Quads manage channel-specific operations, the Si3474 package has
+ * several resources shared across the entire chip:
+ * - Single RESETb input pin.
+ * - Single INTb output pin (signals interrupts from *either* Quad).
+ * - Single OSS input pin (Emergency Shutdown).
+ * - Global I2C Address (0x7F) used for firmware updates.
+ * - Global status monitoring (Temperature, VDD/VPWR Undervoltage Lockout).
+ *
+ * Driver Architecture:
+ *
+ * To handle the mix of per-Quad access and shared resources correctly, this
+ * driver treats the entire Si3474 package as one logical device. The driver
+ * instance associated with the primary I2C address (Quad 0) takes ownership.
+ * It discovers and manages the I2C client for the secondary address (Quad 1).
+ * This primary instance handles shared resources like IRQ management and
+ * registers a single PSE controller device representing all logical PIs.
+ * Internal functions route I2C commands to the appropriate Quad's i2c_client
+ * based on the target channel or PI.
+ *
+ * Terminology Mapping:
+ *
+ * - "PI" (Power Interface): Refers to the logical PSE port as defined by
+ * IEEE 802.3 (typically corresponds to an RJ45 connector). This is the
+ * `id` (0-7) used in the pse_controller_ops.
+ * - "Channel": Refers to one of the 8 physical power control paths within
+ * the Si3474 chip itself (hardware channels 0-7). This terminology is
+ * used internally within the driver to avoid confusion with 'ports'.
+ * - "Quad": One of the two internal 4-channel management units within the
+ * Si3474, each accessed via its own I2C address.
+ *
+ * Relationship:
+ * - A 2-Pair PoE PI uses 1 Channel.
+ * - A 4-Pair PoE PI uses 2 Channels.
+ *
+ * ASCII Schematic:
+ *
+ * +-----------------------------------------------------+
+ * | Si3474 Chip |
+ * | |
+ * | +---------------------+ +---------------------+ |
+ * | | Quad 0 | | Quad 1 | |
+ * | | Channels 0, 1, 2, 3 | | Channels 4, 5, 6, 7 | |
+ * | +----------^----------+ +-------^-------------+ |
+ * | I2C Addr 0 | | I2C Addr 1 |
+ * | +------------------------+ |
+ * | (Primary Driver Instance) (Managed by Primary) |
+ * | |
+ * | Shared Resources (affect whole chip): |
+ * | - Single INTb Output -> Handled by Primary |
+ * | - Single RESETb Input |
+ * | - Single OSS Input -> Handled by Primary |
+ * | - Global I2C Addr (0x7F) for Firmware Update |
+ * | - Global Status (Temp, VDD/VPWR UVLO) |
+ * +-----------------------------------------------------+
+ * | | | | | | | |
+ * Ch0 Ch1 Ch2 Ch3 Ch4 Ch5 Ch6 Ch7 (Physical Channels)
+ *
+ * Example Mapping (Logical PI to Physical Channel(s)):
+ * * 2-Pair Mode (8 PIs):
+ * PI 0 -> Ch 0
+ * PI 1 -> Ch 1
+ * ...
+ * PI 7 -> Ch 7
+ * * 4-Pair Mode (4 PIs):
+ * PI 0 -> Ch 0 + Ch 1 (Managed via Quad 0 Addr)
+ * PI 1 -> Ch 2 + Ch 3 (Managed via Quad 0 Addr)
+ * PI 2 -> Ch 4 + Ch 5 (Managed via Quad 1 Addr)
+ * PI 3 -> Ch 6 + Ch 7 (Managed via Quad 1 Addr)
+ * (Note: Actual mapping depends on Device Tree and PORT_REMAP config)
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pse-pd/pse.h>
+
+#define SI3474_MAX_CHANS 8
+
+#define MANUFACTURER_ID 0x08
+#define IC_ID 0x05
+#define SI3474_DEVICE_ID (MANUFACTURER_ID << 3 | IC_ID)
+
+/* Misc registers */
+#define VENDOR_IC_ID_REG 0x1B
+#define TEMPERATURE_REG 0x2C
+#define FIRMWARE_REVISION_REG 0x41
+#define CHIP_REVISION_REG 0x43
+
+/* Main status registers */
+#define POWER_STATUS_REG 0x10
+#define PORT_MODE_REG 0x12
+#define DETECT_CLASS_ENABLE_REG 0x14
+
+/* PORTn Current */
+#define PORT1_CURRENT_LSB_REG 0x30
+
+/* PORTn Current [mA], return in [nA] */
+/* 1000 * ((PORTn_CURRENT_MSB << 8) + PORTn_CURRENT_LSB) / 16384 */
+#define SI3474_NA_STEP (1000 * 1000 * 1000 / 16384)
+
+/* VPWR Voltage */
+#define VPWR_LSB_REG 0x2E
+#define VPWR_MSB_REG 0x2F
+
+/* PORTn Voltage */
+#define PORT1_VOLTAGE_LSB_REG 0x32
+
+/* VPWR Voltage [V], return in [uV] */
+/* 60 * (( VPWR_MSB << 8) + VPWR_LSB) / 16384 */
+#define SI3474_UV_STEP (1000 * 1000 * 60 / 16384)
+
+/* Helper macros */
+#define CHAN_IDX(chan) ((chan) % 4)
+#define CHAN_BIT(chan) BIT(CHAN_IDX(chan))
+#define CHAN_UPPER_BIT(chan) BIT(CHAN_IDX(chan) + 4)
+
+#define CHAN_MASK(chan) (0x03U << (2 * CHAN_IDX(chan)))
+#define CHAN_REG(base, chan) ((base) + (CHAN_IDX(chan) * 4))
+
+struct si3474_pi_desc {
+ u8 chan[2];
+ bool is_4p;
+};
+
+struct si3474_priv {
+ struct i2c_client *client[2];
+ struct pse_controller_dev pcdev;
+ struct device_node *np;
+ struct si3474_pi_desc pi[SI3474_MAX_CHANS];
+};
+
+static struct si3474_priv *to_si3474_priv(struct pse_controller_dev *pcdev)
+{
+ return container_of(pcdev, struct si3474_priv, pcdev);
+}
+
+static void si3474_get_channels(struct si3474_priv *priv, int id,
+ u8 *chan0, u8 *chan1)
+{
+ *chan0 = priv->pi[id].chan[0];
+ *chan1 = priv->pi[id].chan[1];
+}
+
+static struct i2c_client *si3474_get_chan_client(struct si3474_priv *priv,
+ u8 chan)
+{
+ return (chan < 4) ? priv->client[0] : priv->client[1];
+}
+
+static int si3474_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ struct pse_admin_state *admin_state)
+{
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
+ struct i2c_client *client;
+ bool is_enabled;
+ u8 chan0, chan1;
+ s32 ret;
+
+ si3474_get_channels(priv, id, &chan0, &chan1);
+ client = si3474_get_chan_client(priv, chan0);
+
+ ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
+ if (ret < 0) {
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_UNKNOWN;
+ return ret;
+ }
+
+ is_enabled = ret & (CHAN_MASK(chan0) | CHAN_MASK(chan1));
+
+ if (is_enabled)
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+ else
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+
+ return 0;
+}
+
+static int si3474_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_status *pw_status)
+{
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
+ struct i2c_client *client;
+ bool delivering;
+ u8 chan0, chan1;
+ s32 ret;
+
+ si3474_get_channels(priv, id, &chan0, &chan1);
+ client = si3474_get_chan_client(priv, chan0);
+
+ ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG);
+ if (ret < 0) {
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
+ return ret;
+ }
+
+ delivering = ret & (CHAN_UPPER_BIT(chan0) | CHAN_UPPER_BIT(chan1));
+
+ if (delivering)
+ pw_status->c33_pw_status =
+ ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+ else
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+
+ return 0;
+}
+
+static int si3474_get_of_channels(struct si3474_priv *priv)
+{
+ struct pse_pi *pi;
+ u32 chan_id;
+ u8 pi_no;
+ s32 ret;
+
+ for (pi_no = 0; pi_no < SI3474_MAX_CHANS; pi_no++) {
+ pi = &priv->pcdev.pi[pi_no];
+ bool pairset_found = false;
+ u8 pairset_no;
+
+ for (pairset_no = 0; pairset_no < 2; pairset_no++) {
+ if (!pi->pairset[pairset_no].np)
+ continue;
+
+ pairset_found = true;
+
+ ret = of_property_read_u32(pi->pairset[pairset_no].np,
+ "reg", &chan_id);
+ if (ret) {
+ dev_err(&priv->client[0]->dev,
+ "Failed to read channel reg property\n");
+ return ret;
+ }
+ if (chan_id > SI3474_MAX_CHANS) {
+ dev_err(&priv->client[0]->dev,
+ "Incorrect channel number: %d\n", chan_id);
+ return -EINVAL;
+ }
+
+ priv->pi[pi_no].chan[pairset_no] = chan_id;
+ /* Mark as 4-pair if second pairset is present */
+ priv->pi[pi_no].is_4p = (pairset_no == 1);
+ }
+
+ if (pairset_found && !priv->pi[pi_no].is_4p) {
+ dev_err(&priv->client[0]->dev,
+ "Second pairset is missing for PI %pOF, only 4p configs are supported\n",
+ pi->np);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int si3474_setup_pi_matrix(struct pse_controller_dev *pcdev)
+{
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
+ s32 ret;
+
+ ret = si3474_get_of_channels(priv);
+ if (ret < 0)
+ dev_warn(&priv->client[0]->dev,
+ "Unable to parse DT PSE power interface matrix\n");
+
+ return ret;
+}
+
+static int si3474_pi_enable(struct pse_controller_dev *pcdev, int id)
+{
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
+ struct i2c_client *client;
+ u8 chan0, chan1;
+ s32 ret;
+ u8 val;
+
+ si3474_get_channels(priv, id, &chan0, &chan1);
+ client = si3474_get_chan_client(priv, chan0);
+
+ /* Release PI from shutdown */
+ ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
+ if (ret < 0)
+ return ret;
+
+ val = (u8)ret;
+ val |= CHAN_MASK(chan0);
+ val |= CHAN_MASK(chan1);
+
+ ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val);
+ if (ret)
+ return ret;
+
+ /* DETECT_CLASS_ENABLE must be set when using AUTO mode,
+ * otherwise PI does not power up - datasheet section 2.10.2
+ */
+ val = CHAN_BIT(chan0) | CHAN_UPPER_BIT(chan0) |
+ CHAN_BIT(chan1) | CHAN_UPPER_BIT(chan1);
+
+ ret = i2c_smbus_write_byte_data(client, DETECT_CLASS_ENABLE_REG, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int si3474_pi_disable(struct pse_controller_dev *pcdev, int id)
+{
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
+ struct i2c_client *client;
+ u8 chan0, chan1;
+ s32 ret;
+ u8 val;
+
+ si3474_get_channels(priv, id, &chan0, &chan1);
+ client = si3474_get_chan_client(priv, chan0);
+
+ /* Set PI in shutdown mode */
+ ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
+ if (ret < 0)
+ return ret;
+
+ val = (u8)ret;
+ val &= ~CHAN_MASK(chan0);
+ val &= ~CHAN_MASK(chan1);
+
+ ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int si3474_pi_get_chan_current(struct si3474_priv *priv, u8 chan)
+{
+ struct i2c_client *client;
+ u64 tmp_64;
+ s32 ret;
+ u8 reg;
+
+ client = si3474_get_chan_client(priv, chan);
+
+ /* Registers 0x30 to 0x3d */
+ reg = CHAN_REG(PORT1_CURRENT_LSB_REG, chan);
+
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ tmp_64 = ret * SI3474_NA_STEP;
+
+ /* uA = nA / 1000 */
+ tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000);
+ return (int)tmp_64;
+}
+
+static int si3474_pi_get_chan_voltage(struct si3474_priv *priv, u8 chan)
+{
+ struct i2c_client *client;
+ s32 ret;
+ u32 val;
+ u8 reg;
+
+ client = si3474_get_chan_client(priv, chan);
+
+ /* Registers 0x32 to 0x3f */
+ reg = CHAN_REG(PORT1_VOLTAGE_LSB_REG, chan);
+
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ val = ret * SI3474_UV_STEP;
+
+ return (int)val;
+}
+
+static int si3474_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
+ struct i2c_client *client;
+ u8 chan0, chan1;
+ s32 ret;
+
+ si3474_get_channels(priv, id, &chan0, &chan1);
+ client = si3474_get_chan_client(priv, chan0);
+
+ /* Check which channels are enabled*/
+ ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG);
+ if (ret < 0)
+ return ret;
+
+ /* Take voltage from the first enabled channel */
+ if (ret & CHAN_BIT(chan0))
+ ret = si3474_pi_get_chan_voltage(priv, chan0);
+ else if (ret & CHAN_BIT(chan1))
+ ret = si3474_pi_get_chan_voltage(priv, chan1);
+ else
+ /* 'should' be no voltage in this case */
+ return 0;
+
+ return ret;
+}
+
+static int si3474_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
+ u8 chan0, chan1;
+ u32 uV, uA;
+ u64 tmp_64;
+ s32 ret;
+
+ ret = si3474_pi_get_voltage(&priv->pcdev, id);
+
+ /* Do not read currents if voltage is 0 */
+ if (ret <= 0)
+ return ret;
+ uV = ret;
+
+ si3474_get_channels(priv, id, &chan0, &chan1);
+
+ ret = si3474_pi_get_chan_current(priv, chan0);
+ if (ret < 0)
+ return ret;
+ uA = ret;
+
+ ret = si3474_pi_get_chan_current(priv, chan1);
+ if (ret < 0)
+ return ret;
+ uA += ret;
+
+ tmp_64 = uV;
+ tmp_64 *= uA;
+ /* mW = uV * uA / 1000000000 */
+ return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
+}
+
+static const struct pse_controller_ops si3474_ops = {
+ .setup_pi_matrix = si3474_setup_pi_matrix,
+ .pi_enable = si3474_pi_enable,
+ .pi_disable = si3474_pi_disable,
+ .pi_get_actual_pw = si3474_pi_get_actual_pw,
+ .pi_get_voltage = si3474_pi_get_voltage,
+ .pi_get_admin_state = si3474_pi_get_admin_state,
+ .pi_get_pw_status = si3474_pi_get_pw_status,
+};
+
+static void si3474_ancillary_i2c_remove(void *data)
+{
+ struct i2c_client *client = data;
+
+ i2c_unregister_device(client);
+}
+
+static int si3474_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct si3474_priv *priv;
+ u8 fw_version;
+ s32 ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "i2c check functionality failed\n");
+ return -ENXIO;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ret = i2c_smbus_read_byte_data(client, VENDOR_IC_ID_REG);
+ if (ret < 0)
+ return ret;
+
+ if (ret != SI3474_DEVICE_ID) {
+ dev_err(dev, "Wrong device ID: 0x%x\n", ret);
+ return -ENXIO;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, FIRMWARE_REVISION_REG);
+ if (ret < 0)
+ return ret;
+ fw_version = ret;
+
+ ret = i2c_smbus_read_byte_data(client, CHIP_REVISION_REG);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "Chip revision: 0x%x, firmware version: 0x%x\n",
+ ret, fw_version);
+
+ priv->client[0] = client;
+ i2c_set_clientdata(client, priv);
+
+ priv->client[1] = i2c_new_ancillary_device(priv->client[0], "secondary",
+ priv->client[0]->addr + 1);
+ if (IS_ERR(priv->client[1]))
+ return PTR_ERR(priv->client[1]);
+
+ ret = devm_add_action_or_reset(dev, si3474_ancillary_i2c_remove, priv->client[1]);
+ if (ret < 0) {
+ dev_err(&priv->client[1]->dev, "Cannot register remove callback\n");
+ return ret;
+ }
+
+ ret = i2c_smbus_read_byte_data(priv->client[1], VENDOR_IC_ID_REG);
+ if (ret < 0) {
+ dev_err(&priv->client[1]->dev, "Cannot access secondary PSE controller\n");
+ return ret;
+ }
+
+ if (ret != SI3474_DEVICE_ID) {
+ dev_err(&priv->client[1]->dev,
+ "Wrong device ID for secondary PSE controller: 0x%x\n", ret);
+ return -ENXIO;
+ }
+
+ priv->np = dev->of_node;
+ priv->pcdev.owner = THIS_MODULE;
+ priv->pcdev.ops = &si3474_ops;
+ priv->pcdev.dev = dev;
+ priv->pcdev.types = ETHTOOL_PSE_C33;
+ priv->pcdev.nr_lines = SI3474_MAX_CHANS;
+
+ ret = devm_pse_controller_register(dev, &priv->pcdev);
+ if (ret) {
+ dev_err(dev, "Failed to register PSE controller: 0x%x\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id si3474_id[] = {
+ { .name = "si3474" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, si3474_id);
+
+static const struct of_device_id si3474_of_match[] = {
+ {
+ .compatible = "skyworks,si3474",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, si3474_of_match);
+
+static struct i2c_driver si3474_driver = {
+ .probe = si3474_i2c_probe,
+ .id_table = si3474_id,
+ .driver = {
+ .name = "si3474",
+ .of_match_table = si3474_of_match,
+ },
+};
+module_i2c_driver(si3474_driver);
+
+MODULE_AUTHOR("Piotr Kubik <piotr.kubik@adtran.com>");
+MODULE_DESCRIPTION("Skyworks Si3474 PoE PSE Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c
new file mode 100644
index 000000000..8a3bbd623
--- /dev/null
+++ b/drivers/net/pse-pd/tps23881.c
@@ -0,0 +1,1558 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the TI TPS23881 PoE PSE Controller driver (I2C bus)
+ *
+ * Copyright (c) 2023 Bootlin, Kory Maincent <kory.maincent@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pse-pd/pse.h>
+
+#define TPS23881_MAX_CHANS 8
+#define TPS23881_MAX_IRQ_RETRIES 10
+
+#define TPS23881_REG_IT 0x0
+#define TPS23881_REG_IT_MASK 0x1
+#define TPS23881_REG_IT_DISF BIT(2)
+#define TPS23881_REG_IT_DETC BIT(3)
+#define TPS23881_REG_IT_CLASC BIT(4)
+#define TPS23881_REG_IT_IFAULT BIT(5)
+#define TPS23881_REG_IT_SUPF BIT(7)
+#define TPS23881_REG_DET_EVENT 0x5
+#define TPS23881_REG_FAULT 0x7
+#define TPS23881_REG_SUPF_EVENT 0xb
+#define TPS23881_REG_TSD BIT(7)
+#define TPS23881_REG_DISC 0xc
+#define TPS23881_REG_PW_STATUS 0x10
+#define TPS23881_REG_OP_MODE 0x12
+#define TPS23881_REG_DISC_EN 0x13
+#define TPS23881_OP_MODE_SEMIAUTO 0xaaaa
+#define TPS23881_REG_DIS_EN 0x13
+#define TPS23881_REG_DET_CLA_EN 0x14
+#define TPS23881_REG_GEN_MASK 0x17
+#define TPS23881_REG_CLCHE BIT(2)
+#define TPS23881_REG_DECHE BIT(3)
+#define TPS23881_REG_NBITACC BIT(5)
+#define TPS23881_REG_INTEN BIT(7)
+#define TPS23881_REG_PW_EN 0x19
+#define TPS23881_REG_RESET 0x1a
+#define TPS23881_REG_CLRAIN BIT(7)
+#define TPS23881_REG_2PAIR_POL1 0x1e
+#define TPS23881_REG_PORT_MAP 0x26
+#define TPS23881_REG_PORT_POWER 0x29
+#define TPS23881_REG_4PAIR_POL1 0x2a
+#define TPS23881_REG_INPUT_V 0x2e
+#define TPS23881_REG_CHAN1_A 0x30
+#define TPS23881_REG_CHAN1_V 0x32
+#define TPS23881_REG_FOLDBACK 0x40
+#define TPS23881_REG_TPON BIT(0)
+#define TPS23881_REG_FWREV 0x41
+#define TPS23881_REG_DEVID 0x43
+#define TPS23881_REG_CHAN1_CLASS 0x4c
+#define TPS23881_REG_SRAM_CTRL 0x60
+#define TPS23881_REG_SRAM_DATA 0x61
+
+#define TPS23881_UV_STEP 3662
+#define TPS23881_NA_STEP 89500
+#define TPS23881_MW_STEP 500
+#define TPS23881_MIN_PI_PW_LIMIT_MW 2000
+
+struct tps23881_port_desc {
+ u8 chan[2];
+ bool is_4p;
+ int pw_pol;
+ bool exist;
+};
+
+struct tps23881_priv {
+ struct i2c_client *client;
+ struct pse_controller_dev pcdev;
+ struct device_node *np;
+ struct tps23881_port_desc port[TPS23881_MAX_CHANS];
+};
+
+static struct tps23881_priv *to_tps23881_priv(struct pse_controller_dev *pcdev)
+{
+ return container_of(pcdev, struct tps23881_priv, pcdev);
+}
+
+/*
+ * Helper to extract a value from a u16 register value, which is made of two
+ * u8 registers. The function calculates the bit offset based on the channel
+ * and extracts the relevant bits using a provided field mask.
+ *
+ * @param reg_val: The u16 register value (composed of two u8 registers).
+ * @param chan: The channel number (0-7).
+ * @param field_offset: The base bit offset to apply (e.g., 0 or 4).
+ * @param field_mask: The mask to apply to extract the required bits.
+ * @return: The extracted value for the specific channel.
+ */
+static u16 tps23881_calc_val(u16 reg_val, u8 chan, u8 field_offset,
+ u16 field_mask)
+{
+ if (chan >= 4)
+ reg_val >>= 8;
+
+ return (reg_val >> field_offset) & field_mask;
+}
+
+/*
+ * Helper to combine individual channel values into a u16 register value.
+ * The function sets the value for a specific channel in the appropriate
+ * position.
+ *
+ * @param reg_val: The current u16 register value.
+ * @param chan: The channel number (0-7).
+ * @param field_offset: The base bit offset to apply (e.g., 0 or 4).
+ * @param field_mask: The mask to apply for the field (e.g., 0x0F).
+ * @param field_val: The value to set for the specific channel (masked by
+ * field_mask).
+ * @return: The updated u16 register value with the channel value set.
+ */
+static u16 tps23881_set_val(u16 reg_val, u8 chan, u8 field_offset,
+ u16 field_mask, u16 field_val)
+{
+ field_val &= field_mask;
+
+ if (chan < 4) {
+ reg_val &= ~(field_mask << field_offset);
+ reg_val |= (field_val << field_offset);
+ } else {
+ reg_val &= ~(field_mask << (field_offset + 8));
+ reg_val |= (field_val << (field_offset + 8));
+ }
+
+ return reg_val;
+}
+
+static int
+tps23881_pi_set_pw_pol_limit(struct tps23881_priv *priv, int id, u8 pw_pol,
+ bool is_4p)
+{
+ struct i2c_client *client = priv->client;
+ int ret, reg;
+ u16 val;
+ u8 chan;
+
+ chan = priv->port[id].chan[0];
+ if (!is_4p) {
+ reg = TPS23881_REG_2PAIR_POL1 + (chan % 4);
+ } else {
+ /* One chan is enough to configure the 4p PI power limit */
+ if ((chan % 4) < 2)
+ reg = TPS23881_REG_4PAIR_POL1;
+ else
+ reg = TPS23881_REG_4PAIR_POL1 + 1;
+ }
+
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ val = tps23881_set_val(ret, chan, 0, 0xff, pw_pol);
+ return i2c_smbus_write_word_data(client, reg, val);
+}
+
+static int tps23881_pi_enable_manual_pol(struct tps23881_priv *priv, int id)
+{
+ struct i2c_client *client = priv->client;
+ int ret;
+ u8 chan;
+ u16 val;
+
+ ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FOLDBACK);
+ if (ret < 0)
+ return ret;
+
+ /* No need to test if the chan is PoE4 as setting either bit for a
+ * 4P configured port disables the automatic configuration on both
+ * channels.
+ */
+ chan = priv->port[id].chan[0];
+ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
+ return i2c_smbus_write_byte_data(client, TPS23881_REG_FOLDBACK, val);
+}
+
+static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ u8 chan;
+ u16 val;
+ int ret;
+
+ if (id >= TPS23881_MAX_CHANS)
+ return -ERANGE;
+
+ chan = priv->port[id].chan[0];
+ val = tps23881_set_val(0, chan, 0, BIT(chan % 4), BIT(chan % 4));
+
+ if (priv->port[id].is_4p) {
+ chan = priv->port[id].chan[1];
+ val = tps23881_set_val(val, chan, 0, BIT(chan % 4),
+ BIT(chan % 4));
+ }
+
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+ if (ret)
+ return ret;
+
+ /* Enable DC disconnect*/
+ chan = priv->port[id].chan[0];
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
+ if (ret < 0)
+ return ret;
+
+ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ u8 chan;
+ u16 val;
+ int ret;
+
+ if (id >= TPS23881_MAX_CHANS)
+ return -ERANGE;
+
+ chan = priv->port[id].chan[0];
+ val = tps23881_set_val(0, chan, 4, BIT(chan % 4), BIT(chan % 4));
+
+ if (priv->port[id].is_4p) {
+ chan = priv->port[id].chan[1];
+ val = tps23881_set_val(val, chan, 4, BIT(chan % 4),
+ BIT(chan % 4));
+ }
+
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+ if (ret)
+ return ret;
+
+ /* PWOFF command resets lots of register which need to be
+ * configured again. According to the datasheet "It may take upwards
+ * of 5ms after PWOFFn command for all register values to be updated"
+ */
+ mdelay(5);
+
+ /* Disable DC disconnect*/
+ chan = priv->port[id].chan[0];
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
+ if (ret < 0)
+ return ret;
+
+ val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4));
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
+ if (ret)
+ return ret;
+
+ /* Enable detection and classification */
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN);
+ if (ret < 0)
+ return ret;
+
+ chan = priv->port[id].chan[0];
+ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
+ val = tps23881_set_val(val, chan, 4, BIT(chan % 4), BIT(chan % 4));
+
+ if (priv->port[id].is_4p) {
+ chan = priv->port[id].chan[1];
+ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4),
+ BIT(chan % 4));
+ val = tps23881_set_val(val, chan, 4, BIT(chan % 4),
+ BIT(chan % 4));
+ }
+
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+ if (ret)
+ return ret;
+
+ /* No power policy */
+ if (priv->port[id].pw_pol < 0)
+ return 0;
+
+ ret = tps23881_pi_enable_manual_pol(priv, id);
+ if (ret < 0)
+ return ret;
+
+ /* Set power policy */
+ return tps23881_pi_set_pw_pol_limit(priv, id, priv->port[id].pw_pol,
+ priv->port[id].is_4p);
+}
+
+static int
+tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ struct pse_admin_state *admin_state)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ bool enabled;
+ u8 chan;
+ u16 val;
+ int ret;
+
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+ if (ret < 0)
+ return ret;
+
+ chan = priv->port[id].chan[0];
+ val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+ enabled = !!(val);
+
+ if (priv->port[id].is_4p) {
+ chan = priv->port[id].chan[1];
+ val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+ enabled &= !!(val);
+ }
+
+ /* Return enabled status only if both channel are on this state */
+ if (enabled)
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+ else
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+
+ return 0;
+}
+
+static int
+tps23881_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_status *pw_status)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ bool delivering;
+ u8 chan;
+ u16 val;
+ int ret;
+
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+ if (ret < 0)
+ return ret;
+
+ chan = priv->port[id].chan[0];
+ val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
+ delivering = !!(val);
+
+ if (priv->port[id].is_4p) {
+ chan = priv->port[id].chan[1];
+ val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
+ delivering &= !!(val);
+ }
+
+ /* Return delivering status only if both channel are on this state */
+ if (delivering)
+ pw_status->c33_pw_status =
+ ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+ else
+ pw_status->c33_pw_status =
+ ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+
+ return 0;
+}
+
+static int tps23881_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ int ret;
+ u64 uV;
+
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_INPUT_V);
+ if (ret < 0)
+ return ret;
+
+ uV = ret & 0x3fff;
+ uV *= TPS23881_UV_STEP;
+
+ return (int)uV;
+}
+
+static int
+tps23881_pi_get_chan_current(struct tps23881_priv *priv, u8 chan)
+{
+ struct i2c_client *client = priv->client;
+ int reg, ret;
+ u64 tmp_64;
+
+ /* Registers 0x30 to 0x3d */
+ reg = TPS23881_REG_CHAN1_A + (chan % 4) * 4 + (chan >= 4);
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ tmp_64 = ret & 0x3fff;
+ tmp_64 *= TPS23881_NA_STEP;
+ /* uA = nA / 1000 */
+ tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000);
+ return (int)tmp_64;
+}
+
+static int tps23881_pi_get_pw_class(struct pse_controller_dev *pcdev,
+ int id)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ int ret, reg;
+ u8 chan;
+
+ chan = priv->port[id].chan[0];
+ reg = TPS23881_REG_CHAN1_CLASS + (chan % 4);
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ return tps23881_calc_val(ret, chan, 4, 0x0f);
+}
+
+static int
+tps23881_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ int ret, uV, uA;
+ u64 tmp_64;
+ u8 chan;
+
+ ret = tps23881_pi_get_voltage(&priv->pcdev, id);
+ if (ret < 0)
+ return ret;
+ uV = ret;
+
+ chan = priv->port[id].chan[0];
+ ret = tps23881_pi_get_chan_current(priv, chan);
+ if (ret < 0)
+ return ret;
+ uA = ret;
+
+ if (priv->port[id].is_4p) {
+ chan = priv->port[id].chan[1];
+ ret = tps23881_pi_get_chan_current(priv, chan);
+ if (ret < 0)
+ return ret;
+ uA += ret;
+ }
+
+ tmp_64 = uV;
+ tmp_64 *= uA;
+ /* mW = uV * uA / 1000000000 */
+ return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
+}
+
+static int
+tps23881_pi_get_pw_limit_chan(struct tps23881_priv *priv, u8 chan)
+{
+ struct i2c_client *client = priv->client;
+ int ret, reg;
+ u16 val;
+
+ reg = TPS23881_REG_2PAIR_POL1 + (chan % 4);
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ val = tps23881_calc_val(ret, chan, 0, 0xff);
+ return val * TPS23881_MW_STEP;
+}
+
+static int tps23881_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ int ret, mW;
+ u8 chan;
+
+ chan = priv->port[id].chan[0];
+ ret = tps23881_pi_get_pw_limit_chan(priv, chan);
+ if (ret < 0)
+ return ret;
+
+ mW = ret;
+ if (priv->port[id].is_4p) {
+ chan = priv->port[id].chan[1];
+ ret = tps23881_pi_get_pw_limit_chan(priv, chan);
+ if (ret < 0)
+ return ret;
+ mW += ret;
+ }
+
+ return mW;
+}
+
+static int tps23881_pi_set_pw_limit(struct pse_controller_dev *pcdev,
+ int id, int max_mW)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ u8 pw_pol;
+ int ret;
+
+ if (max_mW < TPS23881_MIN_PI_PW_LIMIT_MW || MAX_PI_PW < max_mW) {
+ dev_err(&priv->client->dev,
+ "power limit %d out of ranges [%d,%d]",
+ max_mW, TPS23881_MIN_PI_PW_LIMIT_MW, MAX_PI_PW);
+ return -ERANGE;
+ }
+
+ ret = tps23881_pi_enable_manual_pol(priv, id);
+ if (ret < 0)
+ return ret;
+
+ pw_pol = DIV_ROUND_CLOSEST_ULL(max_mW, TPS23881_MW_STEP);
+
+ /* Save power policy to reconfigure it after a disabled call */
+ priv->port[id].pw_pol = pw_pol;
+ return tps23881_pi_set_pw_pol_limit(priv, id, pw_pol,
+ priv->port[id].is_4p);
+}
+
+static int
+tps23881_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_limit_ranges *pw_limit_ranges)
+{
+ struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
+
+ c33_pw_limit_ranges = kzalloc_obj(*c33_pw_limit_ranges);
+ if (!c33_pw_limit_ranges)
+ return -ENOMEM;
+
+ c33_pw_limit_ranges->min = TPS23881_MIN_PI_PW_LIMIT_MW;
+ c33_pw_limit_ranges->max = MAX_PI_PW;
+ pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
+
+ /* Return the number of ranges */
+ return 1;
+}
+
+/* Parse managers subnode into a array of device node */
+static int
+tps23881_get_of_channels(struct tps23881_priv *priv,
+ struct device_node *chan_node[TPS23881_MAX_CHANS])
+{
+ struct device_node *channels_node, *node;
+ int i, ret;
+
+ if (!priv->np)
+ return -EINVAL;
+
+ channels_node = of_find_node_by_name(priv->np, "channels");
+ if (!channels_node)
+ return -EINVAL;
+
+ for_each_child_of_node(channels_node, node) {
+ u32 chan_id;
+
+ if (!of_node_name_eq(node, "channel"))
+ continue;
+
+ ret = of_property_read_u32(node, "reg", &chan_id);
+ if (ret) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (chan_id >= TPS23881_MAX_CHANS || chan_node[chan_id]) {
+ dev_err(&priv->client->dev,
+ "wrong number of port (%d)\n", chan_id);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ of_node_get(node);
+ chan_node[chan_id] = node;
+ }
+
+ of_node_put(channels_node);
+ return 0;
+
+out:
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ of_node_put(chan_node[i]);
+ chan_node[i] = NULL;
+ }
+
+ of_node_put(node);
+ of_node_put(channels_node);
+ return ret;
+}
+
+struct tps23881_port_matrix {
+ u8 pi_id;
+ u8 lgcl_chan[2];
+ u8 hw_chan[2];
+ bool is_4p;
+ bool exist;
+};
+
+static int
+tps23881_match_channel(const struct pse_pi_pairset *pairset,
+ struct device_node *chan_node[TPS23881_MAX_CHANS])
+{
+ int i;
+
+ /* Look on every channels */
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ if (pairset->np == chan_node[i])
+ return i;
+ }
+
+ return -ENODEV;
+}
+
+static bool
+tps23881_is_chan_free(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS],
+ int chan)
+{
+ int i;
+
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ if (port_matrix[i].exist &&
+ (port_matrix[i].hw_chan[0] == chan ||
+ port_matrix[i].hw_chan[1] == chan))
+ return false;
+ }
+
+ return true;
+}
+
+/* Fill port matrix with the matching channels */
+static int
+tps23881_match_port_matrix(struct pse_pi *pi, int pi_id,
+ struct device_node *chan_node[TPS23881_MAX_CHANS],
+ struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS])
+{
+ int ret;
+
+ if (!pi->pairset[0].np)
+ return 0;
+
+ ret = tps23881_match_channel(&pi->pairset[0], chan_node);
+ if (ret < 0)
+ return ret;
+
+ if (!tps23881_is_chan_free(port_matrix, ret)) {
+ pr_err("tps23881: channel %d already used\n", ret);
+ return -ENODEV;
+ }
+
+ port_matrix[pi_id].hw_chan[0] = ret;
+ port_matrix[pi_id].exist = true;
+
+ if (!pi->pairset[1].np)
+ return 0;
+
+ ret = tps23881_match_channel(&pi->pairset[1], chan_node);
+ if (ret < 0)
+ return ret;
+
+ if (!tps23881_is_chan_free(port_matrix, ret)) {
+ pr_err("tps23881: channel %d already used\n", ret);
+ return -ENODEV;
+ }
+
+ if (port_matrix[pi_id].hw_chan[0] / 4 != ret / 4) {
+ pr_err("tps23881: 4-pair PSE can only be set within the same 4 ports group");
+ return -ENODEV;
+ }
+
+ port_matrix[pi_id].hw_chan[1] = ret;
+ port_matrix[pi_id].is_4p = true;
+
+ return 0;
+}
+
+static int
+tps23881_get_unused_chan(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS],
+ int port_cnt)
+{
+ bool used;
+ int i, j;
+
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ used = false;
+
+ for (j = 0; j < port_cnt; j++) {
+ if (port_matrix[j].hw_chan[0] == i) {
+ used = true;
+ break;
+ }
+
+ if (port_matrix[j].is_4p &&
+ port_matrix[j].hw_chan[1] == i) {
+ used = true;
+ break;
+ }
+ }
+
+ if (!used)
+ return i;
+ }
+
+ return -ENODEV;
+}
+
+/* Sort the port matrix to following particular hardware ports matrix
+ * specification of the tps23881. The device has two 4-ports groups and
+ * each 4-pair powered device has to be configured to use two consecutive
+ * logical channel in each 4 ports group (1 and 2 or 3 and 4). Also the
+ * hardware matrix has to be fully configured even with unused chan to be
+ * valid.
+ */
+static int
+tps23881_sort_port_matrix(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS])
+{
+ struct tps23881_port_matrix tmp_port_matrix[TPS23881_MAX_CHANS] = {0};
+ int i, ret, port_cnt = 0, cnt_4ch_grp1 = 0, cnt_4ch_grp2 = 4;
+
+ /* Configure 4p port matrix */
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ int *cnt;
+
+ if (!port_matrix[i].exist || !port_matrix[i].is_4p)
+ continue;
+
+ if (port_matrix[i].hw_chan[0] < 4)
+ cnt = &cnt_4ch_grp1;
+ else
+ cnt = &cnt_4ch_grp2;
+
+ tmp_port_matrix[port_cnt].exist = true;
+ tmp_port_matrix[port_cnt].is_4p = true;
+ tmp_port_matrix[port_cnt].pi_id = i;
+ tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0];
+ tmp_port_matrix[port_cnt].hw_chan[1] = port_matrix[i].hw_chan[1];
+
+ /* 4-pair ports have to be configured with consecutive
+ * logical channels 0 and 1, 2 and 3.
+ */
+ tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++;
+ tmp_port_matrix[port_cnt].lgcl_chan[1] = (*cnt)++;
+
+ port_cnt++;
+ }
+
+ /* Configure 2p port matrix */
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ int *cnt;
+
+ if (!port_matrix[i].exist || port_matrix[i].is_4p)
+ continue;
+
+ if (port_matrix[i].hw_chan[0] < 4)
+ cnt = &cnt_4ch_grp1;
+ else
+ cnt = &cnt_4ch_grp2;
+
+ tmp_port_matrix[port_cnt].exist = true;
+ tmp_port_matrix[port_cnt].pi_id = i;
+ tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++;
+ tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0];
+
+ port_cnt++;
+ }
+
+ /* Complete the rest of the first 4 port group matrix even if
+ * channels are unused
+ */
+ while (cnt_4ch_grp1 < 4) {
+ ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt);
+ if (ret < 0) {
+ pr_err("tps23881: port matrix issue, no chan available\n");
+ return ret;
+ }
+
+ if (port_cnt >= TPS23881_MAX_CHANS) {
+ pr_err("tps23881: wrong number of channels\n");
+ return -ENODEV;
+ }
+ tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp1;
+ tmp_port_matrix[port_cnt].hw_chan[0] = ret;
+ cnt_4ch_grp1++;
+ port_cnt++;
+ }
+
+ /* Complete the rest of the second 4 port group matrix even if
+ * channels are unused
+ */
+ while (cnt_4ch_grp2 < 8) {
+ ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt);
+ if (ret < 0) {
+ pr_err("tps23881: port matrix issue, no chan available\n");
+ return -ENODEV;
+ }
+
+ if (port_cnt >= TPS23881_MAX_CHANS) {
+ pr_err("tps23881: wrong number of channels\n");
+ return -ENODEV;
+ }
+ tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp2;
+ tmp_port_matrix[port_cnt].hw_chan[0] = ret;
+ cnt_4ch_grp2++;
+ port_cnt++;
+ }
+
+ memcpy(port_matrix, tmp_port_matrix, sizeof(tmp_port_matrix));
+
+ return port_cnt;
+}
+
+/* Write port matrix to the hardware port matrix and the software port
+ * matrix.
+ */
+static int
+tps23881_write_port_matrix(struct tps23881_priv *priv,
+ struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS],
+ int port_cnt)
+{
+ struct i2c_client *client = priv->client;
+ u8 pi_id, lgcl_chan, hw_chan;
+ u16 val = 0;
+ int i;
+
+ for (i = 0; i < port_cnt; i++) {
+ pi_id = port_matrix[i].pi_id;
+ lgcl_chan = port_matrix[i].lgcl_chan[0];
+ hw_chan = port_matrix[i].hw_chan[0] % 4;
+
+ /* Set software port matrix for existing ports */
+ if (port_matrix[i].exist) {
+ priv->port[pi_id].chan[0] = lgcl_chan;
+ priv->port[pi_id].exist = true;
+ }
+
+ /* Initialize power policy internal value */
+ priv->port[pi_id].pw_pol = -1;
+
+ /* Set hardware port matrix for all ports */
+ val |= hw_chan << (lgcl_chan * 2);
+
+ if (!port_matrix[i].is_4p)
+ continue;
+
+ lgcl_chan = port_matrix[i].lgcl_chan[1];
+ hw_chan = port_matrix[i].hw_chan[1] % 4;
+
+ /* Set software port matrix for existing ports */
+ if (port_matrix[i].exist) {
+ priv->port[pi_id].is_4p = true;
+ priv->port[pi_id].chan[1] = lgcl_chan;
+ }
+
+ /* Set hardware port matrix for all ports */
+ val |= hw_chan << (lgcl_chan * 2);
+ }
+
+ /* Write hardware ports matrix */
+ return i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val);
+}
+
+static int
+tps23881_set_ports_conf(struct tps23881_priv *priv,
+ struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS])
+{
+ struct i2c_client *client = priv->client;
+ int i, ret;
+ u16 val;
+
+ /* Set operating mode */
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_OP_MODE,
+ TPS23881_OP_MODE_SEMIAUTO);
+ if (ret)
+ return ret;
+
+ /* Disable DC disconnect */
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_DIS_EN, 0x0);
+ if (ret)
+ return ret;
+
+ /* Set port power allocation */
+ val = 0;
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ if (!port_matrix[i].exist)
+ continue;
+
+ if (port_matrix[i].is_4p)
+ val |= 0xf << ((port_matrix[i].lgcl_chan[0] / 2) * 4);
+ else
+ val |= 0x3 << ((port_matrix[i].lgcl_chan[0] / 2) * 4);
+ }
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_POWER, val);
+ if (ret)
+ return ret;
+
+ /* Enable detection and classification */
+ val = 0;
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ if (!port_matrix[i].exist)
+ continue;
+
+ val |= BIT(port_matrix[i].lgcl_chan[0]) |
+ BIT(port_matrix[i].lgcl_chan[0] + 4);
+ if (port_matrix[i].is_4p)
+ val |= BIT(port_matrix[i].lgcl_chan[1]) |
+ BIT(port_matrix[i].lgcl_chan[1] + 4);
+ }
+ return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+}
+
+static int
+tps23881_set_ports_matrix(struct tps23881_priv *priv,
+ struct device_node *chan_node[TPS23881_MAX_CHANS])
+{
+ struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS] = {0};
+ int i, ret;
+
+ /* Update with values for every PSE PIs */
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ ret = tps23881_match_port_matrix(&priv->pcdev.pi[i], i,
+ chan_node, port_matrix);
+ if (ret)
+ return ret;
+ }
+
+ ret = tps23881_sort_port_matrix(port_matrix);
+ if (ret < 0)
+ return ret;
+
+ ret = tps23881_write_port_matrix(priv, port_matrix, ret);
+ if (ret)
+ return ret;
+
+ return tps23881_set_ports_conf(priv, port_matrix);
+}
+
+static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev)
+{
+ struct device_node *chan_node[TPS23881_MAX_CHANS] = {NULL};
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ int ret, i;
+
+ ret = tps23881_get_of_channels(priv, chan_node);
+ if (ret < 0) {
+ dev_warn(&priv->client->dev,
+ "Unable to parse port-matrix, default matrix will be used\n");
+ return 0;
+ }
+
+ ret = tps23881_set_ports_matrix(priv, chan_node);
+
+ for (i = 0; i < TPS23881_MAX_CHANS; i++)
+ of_node_put(chan_node[i]);
+
+ return ret;
+}
+
+static int tps23881_power_class_table[] = {
+ -ERANGE,
+ 4000,
+ 7000,
+ 15500,
+ 30000,
+ 15500,
+ 15500,
+ -ERANGE,
+ 45000,
+ 60000,
+ 75000,
+ 90000,
+ 15500,
+ 45000,
+ -ERANGE,
+ -ERANGE,
+};
+
+static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ u8 reg, chan;
+ int ret;
+ u16 val;
+
+ /* For a 4-pair the classification need 5ms to be completed */
+ if (priv->port[id].is_4p)
+ mdelay(5);
+
+ chan = priv->port[id].chan[0];
+ reg = TPS23881_REG_DISC + (chan % 4);
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ val = tps23881_calc_val(ret, chan, 4, 0xf);
+ return tps23881_power_class_table[val];
+}
+
+static const struct pse_controller_ops tps23881_ops = {
+ .setup_pi_matrix = tps23881_setup_pi_matrix,
+ .pi_enable = tps23881_pi_enable,
+ .pi_disable = tps23881_pi_disable,
+ .pi_get_admin_state = tps23881_pi_get_admin_state,
+ .pi_get_pw_status = tps23881_pi_get_pw_status,
+ .pi_get_pw_class = tps23881_pi_get_pw_class,
+ .pi_get_actual_pw = tps23881_pi_get_actual_pw,
+ .pi_get_voltage = tps23881_pi_get_voltage,
+ .pi_get_pw_limit = tps23881_pi_get_pw_limit,
+ .pi_set_pw_limit = tps23881_pi_set_pw_limit,
+ .pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges,
+ .pi_get_pw_req = tps23881_pi_get_pw_req,
+};
+
+struct tps23881_info {
+ u8 dev_id; /* device ID and silicon revision */
+ const char *fw_parity_name; /* parity code firmware file name */
+ const char *fw_sram_name; /* SRAM code firmware file name */
+};
+
+enum tps23881_model {
+ TPS23881,
+ TPS23881B,
+};
+
+static const struct tps23881_info tps23881_info[] = {
+ [TPS23881] = {
+ .dev_id = 0x22,
+ .fw_parity_name = "ti/tps23881/tps23881-parity-14.bin",
+ .fw_sram_name = "ti/tps23881/tps23881-sram-14.bin",
+ },
+ [TPS23881B] = {
+ .dev_id = 0x24,
+ /* skip SRAM load, ROM provides Clause 145 hardware-level support */
+ },
+};
+
+struct tps23881_fw_conf {
+ u8 reg;
+ u8 val;
+};
+
+static const struct tps23881_fw_conf tps23881_fw_parity_conf[] = {
+ {.reg = 0x60, .val = 0x01},
+ {.reg = 0x62, .val = 0x00},
+ {.reg = 0x63, .val = 0x80},
+ {.reg = 0x60, .val = 0xC4},
+ {.reg = 0x1D, .val = 0xBC},
+ {.reg = 0xD7, .val = 0x02},
+ {.reg = 0x91, .val = 0x00},
+ {.reg = 0x90, .val = 0x00},
+ {.reg = 0xD7, .val = 0x00},
+ {.reg = 0x1D, .val = 0x00},
+ { /* sentinel */ }
+};
+
+static const struct tps23881_fw_conf tps23881_fw_sram_conf[] = {
+ {.reg = 0x60, .val = 0xC5},
+ {.reg = 0x62, .val = 0x00},
+ {.reg = 0x63, .val = 0x80},
+ {.reg = 0x60, .val = 0xC0},
+ {.reg = 0x1D, .val = 0xBC},
+ {.reg = 0xD7, .val = 0x02},
+ {.reg = 0x91, .val = 0x00},
+ {.reg = 0x90, .val = 0x00},
+ {.reg = 0xD7, .val = 0x00},
+ {.reg = 0x1D, .val = 0x00},
+ { /* sentinel */ }
+};
+
+static int tps23881_flash_sram_fw_part(struct i2c_client *client,
+ const char *fw_name,
+ const struct tps23881_fw_conf *fw_conf)
+{
+ const struct firmware *fw = NULL;
+ int i, ret;
+
+ ret = request_firmware(&fw, fw_name, &client->dev);
+ if (ret)
+ return ret;
+
+ dev_dbg(&client->dev, "Flashing %s\n", fw_name);
+
+ /* Prepare device for RAM download */
+ while (fw_conf->reg) {
+ ret = i2c_smbus_write_byte_data(client, fw_conf->reg,
+ fw_conf->val);
+ if (ret)
+ goto out;
+
+ fw_conf++;
+ }
+
+ /* Flash the firmware file */
+ for (i = 0; i < fw->size; i++) {
+ ret = i2c_smbus_write_byte_data(client,
+ TPS23881_REG_SRAM_DATA,
+ fw->data[i]);
+ if (ret)
+ goto out;
+ }
+
+out:
+ release_firmware(fw);
+ return ret;
+}
+
+static int tps23881_flash_sram_fw(struct i2c_client *client,
+ const struct tps23881_info *info)
+{
+ int ret;
+
+ ret = tps23881_flash_sram_fw_part(client, info->fw_parity_name,
+ tps23881_fw_parity_conf);
+ if (ret)
+ return ret;
+
+ ret = tps23881_flash_sram_fw_part(client, info->fw_sram_name,
+ tps23881_fw_sram_conf);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(client, TPS23881_REG_SRAM_CTRL, 0x18);
+ if (ret)
+ return ret;
+
+ mdelay(12);
+
+ return 0;
+}
+
+/* Convert interrupt events to 0xff to be aligned with the chan
+ * number.
+ */
+static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset)
+{
+ u8 val;
+
+ val = (reg_val >> (4 + field_offset) & 0xf0) |
+ (reg_val >> field_offset & 0x0f);
+
+ return val;
+}
+
+/* Convert chan number to port number */
+static void tps23881_set_notifs_helper(struct tps23881_priv *priv,
+ u8 chans,
+ unsigned long *notifs,
+ unsigned long *notifs_mask,
+ enum ethtool_pse_event event)
+{
+ u8 chan;
+ int i;
+
+ if (!chans)
+ return;
+
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ if (!priv->port[i].exist)
+ continue;
+ /* No need to look at the 2nd channel in case of PoE4 as
+ * both registers are set.
+ */
+ chan = priv->port[i].chan[0];
+
+ if (BIT(chan) & chans) {
+ *notifs_mask |= BIT(i);
+ notifs[i] |= event;
+ }
+ }
+}
+
+static void tps23881_irq_event_over_temp(struct tps23881_priv *priv,
+ u16 reg_val,
+ unsigned long *notifs,
+ unsigned long *notifs_mask)
+{
+ int i;
+
+ if (reg_val & TPS23881_REG_TSD) {
+ for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+ if (!priv->port[i].exist)
+ continue;
+
+ *notifs_mask |= BIT(i);
+ notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP;
+ }
+ }
+}
+
+static int tps23881_irq_event_over_current(struct tps23881_priv *priv,
+ u16 reg_val,
+ unsigned long *notifs,
+ unsigned long *notifs_mask)
+{
+ int i, ret;
+ u8 chans;
+
+ chans = tps23881_irq_export_chans_helper(reg_val, 0);
+ if (!chans)
+ return 0;
+
+ tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
+ ETHTOOL_PSE_EVENT_OVER_CURRENT |
+ ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
+
+ /* Over Current event resets the power limit registers so we need
+ * to configured it again.
+ */
+ for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) {
+ if (priv->port[i].pw_pol < 0)
+ continue;
+
+ ret = tps23881_pi_enable_manual_pol(priv, i);
+ if (ret < 0)
+ return ret;
+
+ /* Set power policy */
+ ret = tps23881_pi_set_pw_pol_limit(priv, i,
+ priv->port[i].pw_pol,
+ priv->port[i].is_4p);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void tps23881_irq_event_disconnection(struct tps23881_priv *priv,
+ u16 reg_val,
+ unsigned long *notifs,
+ unsigned long *notifs_mask)
+{
+ u8 chans;
+
+ chans = tps23881_irq_export_chans_helper(reg_val, 4);
+ if (chans)
+ tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
+ ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
+}
+
+static int tps23881_irq_event_detection(struct tps23881_priv *priv,
+ u16 reg_val,
+ unsigned long *notifs,
+ unsigned long *notifs_mask)
+{
+ enum ethtool_pse_event event;
+ int reg, ret, i, val;
+ unsigned long chans;
+
+ chans = tps23881_irq_export_chans_helper(reg_val, 0);
+ for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
+ reg = TPS23881_REG_DISC + (i % 4);
+ ret = i2c_smbus_read_word_data(priv->client, reg);
+ if (ret < 0)
+ return ret;
+
+ val = tps23881_calc_val(ret, i, 0, 0xf);
+ /* If detection valid */
+ if (val == 0x4)
+ event = ETHTOOL_C33_PSE_EVENT_DETECTION;
+ else
+ event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
+
+ tps23881_set_notifs_helper(priv, BIT(i), notifs,
+ notifs_mask, event);
+ }
+
+ return 0;
+}
+
+static int tps23881_irq_event_classification(struct tps23881_priv *priv,
+ u16 reg_val,
+ unsigned long *notifs,
+ unsigned long *notifs_mask)
+{
+ int reg, ret, val, i;
+ unsigned long chans;
+
+ chans = tps23881_irq_export_chans_helper(reg_val, 4);
+ for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
+ reg = TPS23881_REG_DISC + (i % 4);
+ ret = i2c_smbus_read_word_data(priv->client, reg);
+ if (ret < 0)
+ return ret;
+
+ val = tps23881_calc_val(ret, i, 4, 0xf);
+ /* Do not report classification event for unknown class */
+ if (!val || val == 0x8 || val == 0xf)
+ continue;
+
+ tps23881_set_notifs_helper(priv, BIT(i), notifs,
+ notifs_mask,
+ ETHTOOL_C33_PSE_EVENT_CLASSIFICATION);
+ }
+
+ return 0;
+}
+
+static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
+ unsigned long *notifs,
+ unsigned long *notifs_mask)
+{
+ struct i2c_client *client = priv->client;
+ int ret, val;
+
+ /* The Supply event bit is repeated twice so we only need to read
+ * the one from the first byte.
+ */
+ if (reg & TPS23881_REG_IT_SUPF) {
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT);
+ if (ret < 0)
+ return ret;
+ tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
+ }
+
+ if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 |
+ TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 8)) {
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT);
+ if (ret < 0)
+ return ret;
+ ret = tps23881_irq_event_over_current(priv, ret, notifs,
+ notifs_mask);
+ if (ret)
+ return ret;
+
+ tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask);
+ }
+
+ if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 |
+ TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) {
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT);
+ if (ret < 0)
+ return ret;
+
+ val = ret;
+ ret = tps23881_irq_event_detection(priv, val, notifs,
+ notifs_mask);
+ if (ret)
+ return ret;
+
+ ret = tps23881_irq_event_classification(priv, val, notifs,
+ notifs_mask);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev,
+ unsigned long *notifs,
+ unsigned long *notifs_mask)
+{
+ struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ struct i2c_client *client = priv->client;
+ int ret, it_mask, retry;
+
+ /* Get interruption mask */
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK);
+ if (ret < 0)
+ return ret;
+ it_mask = ret;
+
+ /* Read interrupt register until it frees the interruption pin. */
+ retry = 0;
+ while (true) {
+ if (retry > TPS23881_MAX_IRQ_RETRIES) {
+ dev_err(&client->dev, "interrupt never freed");
+ return -ETIMEDOUT;
+ }
+
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT);
+ if (ret < 0)
+ return ret;
+
+ /* No more relevant interruption */
+ if (!(ret & it_mask))
+ return 0;
+
+ ret = tps23881_irq_event_handler(priv, (u16)ret, notifs,
+ notifs_mask);
+ if (ret)
+ return ret;
+
+ retry++;
+ }
+ return 0;
+}
+
+static int tps23881_setup_irq(struct tps23881_priv *priv, int irq)
+{
+ struct i2c_client *client = priv->client;
+ struct pse_irq_desc irq_desc = {
+ .name = "tps23881-irq",
+ .map_event = tps23881_irq_handler,
+ };
+ int ret;
+ u16 val;
+
+ if (!irq) {
+ dev_err(&client->dev, "interrupt is missing");
+ return -EINVAL;
+ }
+
+ val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF |
+ TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC |
+ TPS23881_REG_IT_DISF;
+ val |= val << 8;
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK);
+ if (ret < 0)
+ return ret;
+
+ val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE;
+ val |= val << 8;
+ val |= (u16)ret;
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ /* Reset interrupts registers */
+ ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET,
+ TPS23881_REG_CLRAIN);
+ if (ret < 0)
+ return ret;
+
+ return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
+}
+
+static int tps23881_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const struct tps23881_info *info;
+ struct tps23881_priv *priv;
+ struct gpio_desc *reset;
+ int ret;
+ u8 val;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "i2c check functionality failed\n");
+ return -ENXIO;
+ }
+
+ info = i2c_get_match_data(client);
+ if (!info)
+ return -EINVAL;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset))
+ return dev_err_probe(&client->dev, PTR_ERR(reset), "Failed to get reset GPIO\n");
+
+ if (reset) {
+ /* TPS23880 datasheet (Rev G) indicates minimum reset pulse is 5us */
+ usleep_range(5, 10);
+ gpiod_set_value_cansleep(reset, 0); /* De-assert reset */
+
+ /* TPS23880 datasheet indicates the minimum time after power on reset
+ * should be 20ms, but the document describing how to load SRAM ("How
+ * to Load TPS2388x SRAM and Parity Code over I2C" (Rev E))
+ * indicates we should delay that programming by at least 50ms. So
+ * we'll wait the entire 50ms here to ensure we're safe to go to the
+ * SRAM loading procedure.
+ */
+ msleep(50);
+ }
+
+ ret = i2c_smbus_read_byte_data(client, TPS23881_REG_DEVID);
+ if (ret < 0)
+ return ret;
+
+ if (ret != info->dev_id) {
+ dev_err(dev, "Wrong device ID\n");
+ return -ENXIO;
+ }
+
+ if (info->fw_sram_name) {
+ ret = tps23881_flash_sram_fw(client, info);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV);
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0xFF) {
+ dev_err(&client->dev, "Device entered safe mode\n");
+ return -ENXIO;
+ }
+ dev_info(&client->dev, "Firmware revision 0x%x%s\n", ret,
+ ret == 0x00 ? " (ROM firmware)" : "");
+
+ /* Set configuration B, 16 bit access on a single device address */
+ ret = i2c_smbus_read_byte_data(client, TPS23881_REG_GEN_MASK);
+ if (ret < 0)
+ return ret;
+
+ val = ret | TPS23881_REG_NBITACC;
+ ret = i2c_smbus_write_byte_data(client, TPS23881_REG_GEN_MASK, val);
+ if (ret)
+ return ret;
+
+ priv->client = client;
+ i2c_set_clientdata(client, priv);
+ priv->np = dev->of_node;
+
+ priv->pcdev.owner = THIS_MODULE;
+ priv->pcdev.ops = &tps23881_ops;
+ priv->pcdev.dev = dev;
+ priv->pcdev.types = ETHTOOL_PSE_C33;
+ priv->pcdev.nr_lines = TPS23881_MAX_CHANS;
+ priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC;
+ ret = devm_pse_controller_register(dev, &priv->pcdev);
+ if (ret) {
+ return dev_err_probe(dev, ret,
+ "failed to register PSE controller\n");
+ }
+
+ return tps23881_setup_irq(priv, client->irq);
+}
+
+static const struct i2c_device_id tps23881_id[] = {
+ { .name = "tps23881", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881] },
+ { .name = "tps23881b", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881B] },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tps23881_id);
+
+static const struct of_device_id tps23881_of_match[] = {
+ {
+ .compatible = "ti,tps23881",
+ .data = &tps23881_info[TPS23881]
+ },
+ {
+ .compatible = "ti,tps23881b",
+ .data = &tps23881_info[TPS23881B]
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tps23881_of_match);
+
+static struct i2c_driver tps23881_driver = {
+ .probe = tps23881_i2c_probe,
+ .id_table = tps23881_id,
+ .driver = {
+ .name = "tps23881",
+ .of_match_table = tps23881_of_match,
+ },
+};
+module_i2c_driver(tps23881_driver);
+
+MODULE_AUTHOR("Kory Maincent <kory.maincent@bootlin.com>");
+MODULE_DESCRIPTION("TI TPS23881 PoE PSE Controller driver");
+MODULE_LICENSE("GPL");