[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <93d3bbf0-742c-41d4-83c6-6d94a0dd779c@adtran.com>
Date: Wed, 16 Apr 2025 10:47:13 +0000
From: Piotr Kubik <piotr.kubik@...ran.com>
To: Oleksij Rempel <o.rempel@...gutronix.de>, Kory Maincent
<kory.maincent@...tlin.com>, Andrew Lunn <andrew+netdev@...n.ch>, "David S.
Miller" <davem@...emloft.net>, Eric Dumazet <edumazet@...gle.com>, Jakub
Kicinski <kuba@...nel.org>, Paolo Abeni <pabeni@...hat.com>, Rob Herring
<robh@...nel.org>, Krzysztof Kozlowski <krzk+dt@...nel.org>, Conor Dooley
<conor+dt@...nel.org>, "netdev@...r.kernel.org" <netdev@...r.kernel.org>,
"devicetree@...r.kernel.org" <devicetree@...r.kernel.org>,
"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Subject: [PATCH 1/2] net: pse-pd: Add Si3474 PSE controller driver
From: Piotr Kubik <piotr.kubik@...ran.com>
Add a driver for the Skyworks Si3474 I2C Power Sourcing Equipment
controller.
Based on the TPS23881 driver code.
Driver supports basic features of Si3474 IC:
- get port status,
- get port power,
- get port voltage,
- enable/disable port power.
Only 4p configurations are supported at this moment.
Signed-off-by: Piotr Kubik <piotr.kubik@...ran.com>
---
drivers/net/pse-pd/Kconfig | 10 +
drivers/net/pse-pd/Makefile | 1 +
drivers/net/pse-pd/si3474.c | 477 ++++++++++++++++++++++++++++++++++++
3 files changed, 488 insertions(+)
create mode 100644 drivers/net/pse-pd/si3474.c
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
index 7fab916a7f46..6d2fef6c2602 100644
--- a/drivers/net/pse-pd/Kconfig
+++ b/drivers/net/pse-pd/Kconfig
@@ -41,4 +41,14 @@ config PSE_TPS23881
To compile this driver as a module, choose M here: the
module will be called tps23881.
+
+config PSE_SI3474
+ tristate "Si3474 PSE controller"
+ depends on I2C
+ help
+ This module provides support for Si3474 regulator based Ethernet
+ Power Sourcing Equipment.
+
+ To compile this driver as a module, choose M here: the
+ module will be called si3474.
endif
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
index 9d2898b36737..b33b4d905cd5 100644
--- a/drivers/net/pse-pd/Makefile
+++ b/drivers/net/pse-pd/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
obj-$(CONFIG_PSE_TPS23881) += tps23881.o
+obj-$(CONFIG_PSE_SI3474) += si3474.o
\ No newline at end of file
diff --git a/drivers/net/pse-pd/si3474.c b/drivers/net/pse-pd/si3474.c
new file mode 100644
index 000000000000..a2b4b8bff393
--- /dev/null
+++ b/drivers/net/pse-pd/si3474.c
@@ -0,0 +1,477 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Skyworks Si3474 PoE PSE Controller
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.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 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 PB_POWER_ENABLE_REG 0x19
+
+/* 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)
+
+struct si3474_port_desc {
+ u8 chan[2];
+ bool is_4p;
+};
+
+struct si3474_priv {
+ struct i2c_client *client;
+ struct pse_controller_dev pcdev;
+ struct device_node *np;
+ struct si3474_port_desc port[SI3474_MAX_CHANS];
+};
+
+static struct si3474_priv *to_si3474_priv(struct pse_controller_dev *pcdev)
+{
+ return container_of(pcdev, struct si3474_priv, pcdev);
+}
+
+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 = priv->client;
+ bool enabled = FALSE;
+ u8 chan0, chan1;
+ s32 ret;
+
+ ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG);
+ if (ret < 0) {
+ admin_state->c33_admin_state =
+ ETHTOOL_C33_PSE_ADMIN_STATE_UNKNOWN;
+ return ret;
+ }
+
+ chan0 = priv->port[id].chan[0];
+ chan1 = priv->port[id].chan[1];
+
+ if (chan0 < 4 && chan1 < 4)
+ enabled = (ret & (BIT(chan0) | BIT(chan1))) != 0;
+
+ 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 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 = priv->client;
+ bool delivering = FALSE;
+ u8 chan0, chan1;
+ s32 ret;
+
+ 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;
+ }
+
+ chan0 = priv->port[id].chan[0];
+ chan1 = priv->port[id].chan[1];
+
+ if (chan0 < 4 && chan1 < 4)
+ delivering = (ret & (BIT(chan0 + 4) | BIT(chan1 + 4))) != 0;
+
+ 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;
+}
+
+/* Parse pse-pis subnode into chan array of si3474_priv */
+static int si3474_get_of_channels(struct si3474_priv *priv)
+{
+ struct device_node *pse_node, *node;
+ struct pse_pi *pi;
+ us32 port_no, chan_id;
+ s8 pairset_cnt;
+ s32 ret = 0;
+
+ pse_node = of_get_child_by_name(priv->np, "pse-pis");
+ if (!pse_node) {
+ dev_warn(&priv->client->dev,
+ "Unable to parse DT PSE port-matrix, no pse-pis node\n");
+ return -EINVAL;
+ }
+
+ for_each_child_of_node(pse_node, node) {
+ if (!of_node_name_eq(node, "pse-pi"))
+ continue;
+
+ ret = of_property_read_u32(node, "reg", &port_no);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Failed to read pse-pi reg property\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ if (port_no >= SI3474_MAX_CHANS) {
+ dev_err(&priv->client->dev, "Invalid port number %u\n",
+ port_no);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ pairset_cnt = of_property_count_elems_of_size(node, "pairsets",
+ sizeof(u32));
+ if (!pairset_cnt) {
+ dev_err(&priv->client->dev,
+ "Failed to get pairsets property\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ pi = &priv->pcdev.pi[port_no];
+ if (!pi->pairset[0].np) {
+ dev_err(&priv->client->dev,
+ "Missing pairset reference, port: %u\n",
+ port_no);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = of_property_read_u32(pi->pairset[0].np, "reg", &chan_id);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Failed to read channel reg property, ret:%d\n",
+ ret);
+ ret = -EINVAL;
+ goto out;
+ }
+ priv->port[port_no].chan[0] = chan_id;
+ priv->port[port_no].is_4p = FALSE;
+
+ if (pairset_cnt == 2) {
+ if (!pi->pairset[1].np) {
+ dev_err(&priv->client->dev,
+ "Missing pairset reference, port: %u\n",
+ port_no);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = of_property_read_u32(pi->pairset[1].np, "reg",
+ &chan_id);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Failed to read channel reg property\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ priv->port[port_no].chan[1] = chan_id;
+ priv->port[port_no].is_4p = TRUE;
+ } else {
+ dev_err(&priv->client->dev,
+ "Number of pairsets incorrect - only 4p configurations supported\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+out:
+ of_node_put(pse_node);
+ of_node_put(node);
+ return ret;
+}
+
+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->dev,
+ "Unable to parse DT PSE port-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 = priv->client;
+ u8 chan0, chan1;
+ u16 val = 0;
+ s32 ret;
+
+ if (id >= SI3474_MAX_CHANS)
+ return -ERANGE;
+
+ chan0 = priv->port[id].chan[0];
+ chan1 = priv->port[id].chan[1];
+
+ if (chan0 >= 4 || chan1 >= 4)
+ return -ERANGE;
+
+ val = (BIT(chan0) | BIT(chan1));
+ ret = i2c_smbus_write_word_data(client, PB_POWER_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 = priv->client;
+ u8 chan0, chan1;
+ u16 val = 0;
+ s32 ret;
+
+ if (id >= SI3474_MAX_CHANS)
+ return -ERANGE;
+
+ chan0 = priv->port[id].chan[0];
+ chan1 = priv->port[id].chan[1];
+
+ if (chan0 >= 4 || chan1 >= 4)
+ return -ERANGE;
+
+ val = (BIT(chan0 + 4) | BIT(chan1 + 4));
+ ret = i2c_smbus_write_word_data(client, PB_POWER_ENABLE_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 = priv->client;
+ s32 ret;
+ u8 reg;
+ u64 tmp_64;
+
+ /* Registers 0x30 to 0x3d */
+ reg = PORT1_CURRENT_LSB_REG + (chan % 4) * 4;
+
+ 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 = priv->client;
+ s32 ret;
+ u8 reg;
+ us32 val;
+
+ /* Registers 0x32 to 0x3f */
+ reg = PORT1_VOLTAGE_LSB_REG + (chan % 4) * 4;
+
+ 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 = priv->client;
+
+ u8 chan0, chan1;
+ s32 ret;
+
+ chan0 = priv->port[id].chan[0];
+ chan1 = priv->port[id].chan[1];
+
+ /* 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 & BIT(chan0))
+ ret = si3474_pi_get_chan_voltage(priv, chan0);
+ else if (ret & 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);
+ s32 ret;
+ us32 uV, uA;
+ u64 tmp_64;
+ u8 chan0, chan1;
+
+ ret = si3474_pi_get_voltage(&priv->pcdev, id);
+ if (ret < 0)
+ return ret;
+ uV = ret;
+
+ chan0 = priv->port[id].chan[0];
+ chan1 = priv->port[id].chan[1];
+
+ 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 int si3474_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct si3474_priv *priv;
+ s32 ret;
+ u8 fw_version;
+
+ 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_info(&client->dev, "Chip revision: 0x%x, firmware version: 0x%x\n",
+ ret, fw_version);
+
+ priv->client = client;
+ i2c_set_clientdata(client, priv);
+ 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) {
+ return dev_err_probe(dev, ret,
+ "Failed to register PSE controller\n");
+ }
+
+ return ret;
+}
+
+static const struct i2c_device_id si3474_id[] = {{"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@...ran.com>");
+MODULE_DESCRIPTION("Skyworks Si3474 PoE PSE Controller driver");
+MODULE_LICENSE("GPL");
--
2.43.0
General Business
Powered by blists - more mailing lists