lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250603230422.2553046-3-kyle.swenson@est.tech>
Date: Tue, 3 Jun 2025 23:04:39 +0000
From: Kyle Swenson <kyle.swenson@....tech>
To: "o.rempel@...gutronix.de" <o.rempel@...gutronix.de>,
	"kory.maincent@...tlin.com" <kory.maincent@...tlin.com>,
	"andrew+netdev@...n.ch" <andrew+netdev@...n.ch>, "davem@...emloft.net"
	<davem@...emloft.net>, "edumazet@...gle.com" <edumazet@...gle.com>,
	"kuba@...nel.org" <kuba@...nel.org>, "pabeni@...hat.com" <pabeni@...hat.com>,
	"robh@...nel.org" <robh@...nel.org>, "krzk+dt@...nel.org"
	<krzk+dt@...nel.org>, "conor+dt@...nel.org" <conor+dt@...nel.org>
CC: Kyle Swenson <kyle.swenson@....tech>, "netdev@...r.kernel.org"
	<netdev@...r.kernel.org>, "devicetree@...r.kernel.org"
	<devicetree@...r.kernel.org>
Subject: [RFC PATCH net-next 2/2] net: pse-pd: Add LTC4266 PSE controller
 driver

Add a new driver for the Linear Technology LTC4266 I2C Power Sourcing
Equipment controller.  This driver integrates with the current PSE
controller core, implementing IEEE802.3af and IEEE802.3at PSE standards.
---
 drivers/net/pse-pd/Kconfig   |  10 +
 drivers/net/pse-pd/Makefile  |   1 +
 drivers/net/pse-pd/ltc4266.c | 919 +++++++++++++++++++++++++++++++++++
 3 files changed, 930 insertions(+)
 create mode 100644 drivers/net/pse-pd/ltc4266.c

diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
index 7fab916a7f46..a0f2eaadb4fb 100644
--- a/drivers/net/pse-pd/Kconfig
+++ b/drivers/net/pse-pd/Kconfig
@@ -18,10 +18,20 @@ config PSE_REGULATOR
 	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_LTC4266
+	tristate "LTC4266 PSE controller"
+	depends on I2C
+	help
+	  This module provides support the LTC4266 regulator based Ethernet
+	  Power Sourcing Equipment.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ltc4266.
+
 config PSE_PD692X0
 	tristate "PD692X0 PSE controller"
 	depends on I2C
 	select FW_LOADER
 	select FW_UPLOAD
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
index 9d2898b36737..a17e16467ae2 100644
--- a/drivers/net/pse-pd/Makefile
+++ b/drivers/net/pse-pd/Makefile
@@ -1,8 +1,9 @@
 # SPDX-License-Identifier: GPL-2.0-only
 # Makefile for Linux PSE drivers
 
 obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
 
+obj-$(CONFIG_PSE_LTC4266) += ltc4266.o
 obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
 obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
 obj-$(CONFIG_PSE_TPS23881) += tps23881.o
diff --git a/drivers/net/pse-pd/ltc4266.c b/drivers/net/pse-pd/ltc4266.c
new file mode 100644
index 000000000000..858889c9ab75
--- /dev/null
+++ b/drivers/net/pse-pd/ltc4266.c
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Linear LTC4266 PoE PSE Controller
+ *
+ * Original work:
+ *    Copyright 2019 Cradlepoint Technology, Inc.
+ *    Cradlepoint Technology, Inc.  <source@...dlepoint.com>
+ *
+ * Re-written in 2025:
+ *    Copyright 2025 Ericsson Software Technology
+ *    Kyle Swenson <kyle.swenson@....tech>
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/ethtool.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define LTC4266_REG_ID				0x1B
+#define LTC4266_ID				0x64
+
+#define TWO_BIT_WORD_OFFSET(_v, _pid)		((_v) << ((_pid) * 2))
+#define TWO_BIT_WORD_MASK(_pid)			TWO_BIT_WORD_OFFSET(0x03, (_pid))
+
+#define LTC4266_IPLSB_REG(_p)			(0x30 | ((_p) << 2))
+#define LTC4266_VPLSB_REG(_p)			(LTC4266_IPLSB_REG(_p) + 2)
+
+#define LTC4266_RSTPB_INTCLR			BIT(7)
+#define LTC4266_RSTPB_PINCLR			BIT(6)
+#define LTC4266_RSTPB_RSTALL			BIT(5)
+
+/* Register definitions */
+#define LTC4266_REG_INTSTAT			0x00
+#define LTC4266_REG_INTMASK			0x01
+#define LTC4266_REG_PWREVN_COR			0x03
+#define LTC4266_REG_DETEVN_COR			0x05
+#define LTC4266_REG_FLTEVN_COR			0x07
+#define LTC4266_REG_TSEVN_COR			0x09
+#define LTC4266_REG_SUPEVN_COR			0x0B
+#define LTC4266_REG_STAT(n)			(0x0C + (n))
+#define LTC4266_REG_STATPWR			0x10
+#define LTC4266_REG_OPMD			0x12
+#define LTC4266_REG_DISENA			0x13 /* Disconnect detect enable */
+#define LTC4266_REG_MCONF			0x17
+#define LTC4266_REG_DETPB			0x18
+#define LTC4266_REG_PWRPB			0x19
+#define LTC4266_REG_RSTPB			0x1A
+#define LTC4266_REG_HPEN			0x44
+#define LTC4266_REG_HPMD(_p)			(0x46 + (5 * (_p)))
+#define LTC4266_REG_ILIM(_p)			(LTC4266_REG_HPMD(_p) + 2)
+#define LTC4266_REG_TLIM12			0x1E
+#define LTC4266_REG_TLIM34			0x1F
+
+/* Register field definitions */
+#define LTC4266_HPMD_PONGEN			0x01
+
+/* For LTC4266_REG_TLIM* */
+#define LTC4266_TLIM_VALUE			0x01
+
+/* LTC4266_REG_HPEN, enable "High Power" mode (i.e. Type 2, 25.4W PDs) */
+#define LTC4266_HPEN(_p)			BIT(_p)
+
+/* LTC4266_REG_MCONF */
+#define LTC4266_MCONF_INTERRUPT_ENABLE		BIT(7)
+
+/* LTC4266_REG_STATPWR */
+#define LTC4266_STATPWR_PG(_p)			BIT((_p) + 4)
+#define LTC4266_STATPWR_PE(_p)			BIT(_p)
+#define LTC4266_PORT_CLASS(_stat)		FIELD_GET(GENMASK(6, 4), (_stat))
+
+#define LTC4266_REG_ICUT_HP(_p)			(LTC4266_REG_HPMD(_p) + 1)
+
+/* if R_sense = 0.25 Ohm, this should be set otherwise 0 */
+#define LTC4266_ICUT_RSENSE			BIT(7)
+/* if set, halve the range and double the precision */
+#define LTC4266_ICUT_RANGE			BIT(6)
+
+#define LTC4266_ILIM_AF_RSENSE_025		0x80
+#define LTC4266_ILIM_AF_RSENSE_050		0x00
+#define LTC4266_ILIM_AT_RSENSE_025		0xC0
+#define LTC4266_ILIM_AT_RSENSE_050		0x40
+
+/* LTC4266_REG_INTSTAT and LTC4266_REG_INTMASK */
+#define LTC4266_INT_SUPPLY			BIT(7)
+#define LTC4266_INT_TSTART			BIT(6)
+#define LTC4266_INT_TCUT			BIT(5)
+#define LTC4266_INT_CLASS			BIT(4)
+#define LTC4266_INT_DET				BIT(3)
+#define LTC4266_INT_DIS				BIT(2)
+#define LTC4266_INT_PWRGD			BIT(1)
+#define LTC4266_INT_PWRENA			BIT(0)
+
+#define LTC4266_MAX_PORTS 4
+
+/* Maximum and minimum power limits for a single port */
+#define LTC4266_PW_LIMIT_MAX 25400
+#define LTC4266_PW_LIMIT_MIN 1
+
+enum {
+	READ_CURRENT = 0,
+	READ_VOLTAGE = 2
+};
+
+enum {
+	LTC4266_OPMD_SHUTDOWN = 0,
+	LTC4266_OPMD_MANUAL,
+	LTC4266_OPMD_SEMI,
+	LTC4266_OPMD_AUTO
+};
+
+/* Map LTC4266 Classification result to PD class */
+static int ltc4266_class_map[] = {
+	0, /* Treat as class 3 */
+	1,
+	2,
+	3,
+	4,
+	-EINVAL,
+	3, /* Treat as class 3 */
+	-ERANGE
+};
+
+/* Convert a class 0-4 to icut register value */
+static int ltc4266_class_to_icut[] = {
+	375,
+	112,
+	206,
+	375,
+	638
+};
+
+enum sense_resistor {
+	LTC4266_RSENSE_500, /* Rsense 0.5 Ohm */
+	LTC4266_RSENSE_250 /* Rsense 0.25 Ohm */
+};
+
+struct ltc4266_port {
+	enum sense_resistor rsense;
+	struct device_node *node;
+	int current_limit;
+};
+
+struct ltc4266 {
+	struct i2c_client *client;
+	struct mutex lock; /* Protect Read-Modify-Write Sequences */
+	struct ltc4266_port *ports[LTC4266_MAX_PORTS];
+	struct device *dev;
+	struct device_node *np;
+	struct pse_controller_dev pcdev;
+};
+
+/* Read-modify-write sequence with value and mask.  Mask is expected to be
+ * shifted to the correct spot.
+ */
+static int ltc4266_write_reg(struct ltc4266 *ltc4266, u8 reg, u8 value, u8 mask)
+{
+	int ret;
+	u8 new;
+
+	mutex_lock(&ltc4266->lock);
+	ret = i2c_smbus_read_byte_data(ltc4266->client, reg);
+	if (ret < 0) {
+		dev_warn(ltc4266->dev, "Failed to read register 0x%02x, err=%d\n", reg, ret);
+		mutex_unlock(&ltc4266->lock);
+		return ret;
+	}
+	new = (u8)ret;
+	new &= ~mask;
+	new |= value & mask;
+	ret = i2c_smbus_write_byte_data(ltc4266->client, reg, new);
+	mutex_unlock(&ltc4266->lock);
+
+	return ret;
+}
+
+static int ltc4266_read_iv(struct ltc4266 *ltc4266, int port, u8 iv)
+{
+	int lsb;
+	int msb;
+	int result;
+	int lsb_reg;
+	u64 ivbits = 0;
+
+	if (iv == READ_CURRENT)
+		lsb_reg = LTC4266_IPLSB_REG(port);
+	else if (iv == READ_VOLTAGE)
+		lsb_reg = LTC4266_VPLSB_REG(port);
+	else
+		return -EINVAL;
+
+	result = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_STATPWR);
+	if (result < 0)
+		return result;
+
+	/*  LTC4266 IV readings are only valid if the port is powered. */
+	if (!(result & LTC4266_STATPWR_PG(port)))
+		return -EINVAL;
+
+	/* LTC4266 expects the MSB register to be read immediately following the LSB
+	 * register, so we need to ensure other parts aren't reading other registers in
+	 * this chip while we read the current/voltage regulators.
+	 */
+	mutex_lock(&ltc4266->lock);
+
+	lsb = i2c_smbus_read_byte_data(ltc4266->client, lsb_reg);
+	msb = i2c_smbus_read_byte_data(ltc4266->client, lsb_reg + 1);
+
+	mutex_unlock(&ltc4266->lock);
+
+	if (lsb < 0)
+		return lsb;
+
+	if (msb < 0)
+		return msb;
+
+	ivbits = 0;
+	ivbits |= ((u8)msb) << 8 | ((u8)lsb);
+
+	if (iv == READ_CURRENT)
+		if (ltc4266->ports[port]->rsense == LTC4266_RSENSE_250) /* 122.07 uA/LSB */
+			result = DIV_ROUND_CLOSEST_ULL((ivbits * 122070), 1000);
+		else /* 61.035 uA/LSB */
+			result = DIV_ROUND_CLOSEST_ULL((ivbits * 61035), 1000);
+	else /* 5.835 mV/LSB == 5835 uV/LSB */
+		result = ivbits * 5835;
+
+	return result;
+}
+
+static int ltc4266_port_set_ilim(struct ltc4266 *ltc4266, int port, int class)
+{
+	if (class > 4 || class < 0)
+		return -EINVAL;
+
+	/* We want to set 425 mA for class 3 and lower; 850 mA otherwise for IEEE compliance */
+	if (class < 4) {
+		/* Write 0x80 for 0.25 Ohm sense otherwise 0 */
+		if (ltc4266->ports[port]->rsense == LTC4266_RSENSE_250)
+			return i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_ILIM(port), LTC4266_ILIM_AF_RSENSE_025);
+		return i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_ILIM(port), LTC4266_ILIM_AF_RSENSE_050);
+	}
+
+	/* Class == 4 */
+	if (ltc4266->ports[port]->rsense == LTC4266_RSENSE_250)
+		return i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_ILIM(port), LTC4266_ILIM_AT_RSENSE_025);
+	/* Class == 4 and the sense resistor is 0.5 */
+	return i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_ILIM(port), LTC4266_ILIM_AT_RSENSE_050);
+}
+
+static int ltc4266_port_set_icut(struct ltc4266 *ltc4266, int port, int icut)
+{
+	u8 val;
+
+	if (icut > 850)
+		return -ERANGE;
+
+	val = (u8)(DIV_ROUND_CLOSEST((icut * 1000), 18750) & 0x3F);
+
+	if (ltc4266->ports[port]->rsense == LTC4266_RSENSE_250)
+		val |= LTC4266_ICUT_RSENSE | LTC4266_ICUT_RANGE;
+
+	return i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_ICUT_HP(port), val);
+}
+
+static int ltc4266_port_mode(struct ltc4266 *ltc4266, int port, u8 opmd)
+{
+	if (opmd >= LTC4266_OPMD_AUTO)
+		return -EINVAL;
+
+	return ltc4266_write_reg(ltc4266, LTC4266_REG_OPMD, TWO_BIT_WORD_OFFSET(opmd, port),
+				TWO_BIT_WORD_MASK(port));
+}
+
+static int ltc4266_port_powered(struct ltc4266 *ltc4266, int port)
+{
+	int result = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_STATPWR);
+
+	if (result < 0)
+		return result;
+
+	return !!((result & LTC4266_STATPWR_PG(port)) && (result & LTC4266_STATPWR_PE(port)));
+}
+
+static int ltc4266_port_init(struct ltc4266 *ltc4266, int port)
+{
+	int ret;
+	u8 tlim_reg;
+	u8 tlim_mask;
+
+	/* Reset the port */
+	ret = i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_RSTPB, BIT(port));
+	if (ret < 0)
+		return ret;
+
+	ret = ltc4266_port_mode(ltc4266, port, LTC4266_OPMD_SEMI);
+	if (ret < 0)
+		return ret;
+
+	/* Enable high power mode on the port (802.3at+) */
+	ret = ltc4266_write_reg(ltc4266, LTC4266_REG_HPEN,
+				LTC4266_HPEN(port), LTC4266_HPEN(port));
+	if (ret < 0)
+		return ret;
+
+	/* Enable Ping-Pong Classification */
+	ret = ltc4266_write_reg(ltc4266, LTC4266_REG_HPMD(port),
+				LTC4266_HPMD_PONGEN, LTC4266_HPMD_PONGEN);
+	if (ret < 0)
+		return ret;
+
+	if (ltc4266->ports[port]->rsense == LTC4266_RSENSE_250)
+		ret = ltc4266_write_reg(ltc4266, LTC4266_REG_ICUT_HP(port),
+					LTC4266_ICUT_RSENSE, LTC4266_ICUT_RSENSE);
+	else
+		ret = ltc4266_write_reg(ltc4266, LTC4266_REG_ICUT_HP(port),
+					0, LTC4266_ICUT_RSENSE);
+
+	if (ret < 0)
+		return ret;
+
+	if (port <= 1)
+		tlim_reg = LTC4266_REG_TLIM12;
+	else
+		tlim_reg = LTC4266_REG_TLIM34;
+
+	if (port & BIT(0))
+		tlim_mask = GENMASK(7, 4);
+	else
+		tlim_mask = GENMASK(3, 0);
+
+	ret = ltc4266_write_reg(ltc4266, tlim_reg, LTC4266_TLIM_VALUE, tlim_mask);
+	if (ret < 0)
+		return ret;
+
+	/* Enable disconnect detect. */
+	ret = ltc4266_write_reg(ltc4266, LTC4266_REG_DISENA, BIT(port), BIT(port));
+	if (ret < 0)
+		return ret;
+
+	/* Enable detection (low nibble), classification (high nibble) on the port */
+	ret = i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_DETPB,
+					BIT(port + 4) | BIT(port));
+
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(ltc4266->dev, "Port %d has been initialized\n", port);
+	return 0;
+}
+
+static int ltc4266_get_opmode(struct ltc4266 *ltc4266, int port)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_OPMD);
+	if (ret < 0)
+		return ret;
+
+	switch (port) {
+	case 0:
+		return FIELD_GET(GENMASK(1, 0), ret);
+	case 1:
+		return FIELD_GET(GENMASK(3, 2), ret);
+	case 2:
+		return FIELD_GET(GENMASK(5, 4), ret);
+	case 3:
+		return FIELD_GET(GENMASK(7, 6), ret);
+	}
+	return -EINVAL;
+}
+
+static int ltc4266_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+{
+	int ret;
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	ret = ltc4266_get_opmode(ltc4266, id);
+	if (ret < 0)
+		return ret;
+
+	if (ret == LTC4266_OPMD_SEMI)
+		return 1; /*  If a port is in OPMODE SEMI, we'll just assume admin has it enabled */
+
+	return 0;
+}
+
+static int ltc4266_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
+				    struct pse_pw_status *pw_status)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+	int ret = 0;
+
+	if (!ltc4266_pi_is_enabled(pcdev, id)) {
+		/* The port is disabled by configuration*/
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+		return 0;
+	}
+
+	if (ltc4266_port_powered(ltc4266, id)) {
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+		return 0;
+	}
+
+	ret = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_STAT(id));
+	if (ret < 0) {
+		dev_warn(pcdev->dev, "Failed to read status register, err=%d\n", ret);
+		return ret;
+	}
+
+	pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+	return 0;
+}
+
+/* Allow a port to be powered */
+static int ltc4266_pi_enable(struct pse_controller_dev *pcdev, int port)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	ltc4266_port_init(ltc4266, port);
+	return 0;
+}
+
+static int ltc4266_pi_disable(struct pse_controller_dev *pcdev, int id)
+{
+	int ret = 0;
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	ret = i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_RSTPB, BIT(id));
+	if (ret)
+		return ret;
+
+	ltc4266->ports[id]->current_limit = 0;
+	return ltc4266_port_mode(ltc4266, id, LTC4266_OPMD_SHUTDOWN);
+}
+
+static int ltc4266_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	return ltc4266_read_iv(ltc4266, id, READ_VOLTAGE);
+}
+
+static int ltc4266_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+				      struct pse_admin_state *admin_state)
+{
+	if (ltc4266_pi_is_enabled(pcdev, id))
+		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;
+}
+
+/* Get the PD Classification Result */
+static int ltc4266_pi_get_pw_class(struct pse_controller_dev *pcdev,
+				   int id)
+{
+	int ret;
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	ret = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_STAT(id));
+	if (ret < 0) {
+		dev_warn(ltc4266->dev, "Failed to read status register, err=%d\n", ret);
+		return ret;
+	}
+
+	return ltc4266_class_map[LTC4266_PORT_CLASS(ret)];
+}
+
+static int ltc4266_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+	int uA, uV;
+	u64 uW;
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	uA = ltc4266_read_iv(ltc4266, id, READ_CURRENT);
+	uV = ltc4266_read_iv(ltc4266, id, READ_VOLTAGE);
+
+	if (uA < 0)
+		return uA;
+	if (uV < 0)
+		return uV;
+
+	/* Convert uA to mA and uV to mV; mA * mV = uW */
+	uW = DIV_ROUND_CLOSEST_ULL(uA, 1000) * DIV_ROUND_CLOSEST_ULL(uV, 1000);
+
+	return (int)DIV_ROUND_CLOSEST_ULL(uW, 1000);
+}
+
+static int
+ltc4266_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(sizeof(*c33_pw_limit_ranges),
+				      GFP_KERNEL);
+	if (!c33_pw_limit_ranges)
+		return -ENOMEM;
+
+	c33_pw_limit_ranges[0].min = LTC4266_PW_LIMIT_MIN;
+	c33_pw_limit_ranges[0].max = LTC4266_PW_LIMIT_MAX;
+
+	pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
+	/* Return the number of ranges */
+	return 1;
+}
+
+static int ltc4266_pi_set_pw_limit(struct pse_controller_dev *pcdev,
+				   int id, int max_mW)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+	u64 temp;
+
+	if (max_mW < 1 || max_mW > 25500) {
+		dev_err(ltc4266->dev, "power limit %d is out of range [%d, %d]\n",
+			max_mW, 1, 25500);
+		return -ERANGE;
+	}
+
+	/* Given the range, set a class-specific current limit:
+	 *
+	 * Class	Range (W)	Current Limit
+	 * 0		0			0mA
+	 * 1		4000			112mA
+	 * 2		7000			206mA
+	 * 3		15400			375mA
+	 * 4		25500			638mA
+	 *
+	 * Simple linear regression is probably good enough:
+	 * y = 0.0238856*x + 22.83371414
+	 * scale by 10^7 to get y = 238856 * x + 228337141
+	 */
+
+	temp = DIV_ROUND_CLOSEST_ULL((238856ULL * (uint64_t)max_mW + 22833714ULL), 10000000ULL);
+
+	dev_dbg(ltc4266->dev, "%s passed max_mW=%d, linear regression results in %d\n", __func__, max_mW, (int)temp);
+
+	ltc4266->ports[id]->current_limit = (int)temp;
+	return ltc4266_port_set_icut(ltc4266, id, (int)temp);
+}
+
+static int ltc4266_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+	int ret = 0;
+	int icut = 0;
+	int uA = 0;
+	int mA;
+	int mV;
+	u64 mW = 0;
+
+	/* The LTC4266 offers a "current limit", not a power-limit */
+	ret = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_ICUT_HP(id));
+	if (ret < 0)
+		return ret;
+
+	icut = FIELD_GET(GENMASK(5, 0), ret);
+
+	if (ltc4266->ports[id]->rsense == LTC4266_RSENSE_250) {
+		if (ret & LTC4266_ICUT_RANGE)
+			uA = icut * 18750; /* 18.75 mA/LSB  = 18750 uA/LSB */
+		else
+			uA = icut * 37500;
+	} else {
+		if (ret & LTC4266_ICUT_RANGE)
+			uA = icut * 9380; /* 9.38 mA/LSB */
+		else
+			uA = icut * 18750;
+	}
+
+	mA = DIV_ROUND_CLOSEST(uA, 1000);
+	mV = ltc4266_read_iv(ltc4266, id, READ_VOLTAGE);
+	if (mV < 0)
+		return mV;
+
+	mW = DIV_ROUND_CLOSEST_ULL(((uint64_t)mV * mA), 1000000);
+	dev_dbg(ltc4266->dev, "%s(id=%d) current limit is 0x%02X, power limit is %llu\n", __func__, id, icut, mW);
+	return (int)mW;
+}
+
+static int ltc4266_setup_pi_matrix(struct pse_controller_dev *pcdev)
+{
+	u32 channel_id;
+	struct ltc4266_port *port;
+	u32 sense;
+	struct device_node *channels_node;
+
+	int i = 0;
+	int ret = 0;
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	channels_node = of_find_node_by_name(ltc4266->np, "channels");
+	if (!channels_node)
+		return -EINVAL;
+
+	for_each_child_of_node_scoped(channels_node, port_node) {
+		if (!of_node_name_eq(port_node, "channel"))
+			continue;
+
+		ret = of_property_read_u32(port_node, "reg", &channel_id);
+		if (ret)
+			goto out;
+
+		if (channel_id >= LTC4266_MAX_PORTS) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (ltc4266->ports[channel_id]) {
+			dev_err(ltc4266->dev,
+				"channel_id %d is already used, please check the reg property in node %pOF\n",
+				channel_id, port_node);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		port = devm_kzalloc(ltc4266->dev, sizeof(struct ltc4266_port), GFP_KERNEL);
+		if (!port) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		ltc4266->ports[channel_id] = port;
+
+		ret = of_property_read_u32(port_node, "sense-resistor-micro-ohms", &sense);
+		if (ret)
+			goto out;
+
+		if (sense == 250000) {
+			port->rsense = LTC4266_RSENSE_250;
+		} else if (sense == 500000) {
+			port->rsense = LTC4266_RSENSE_500;
+		} else {
+			dev_err(ltc4266->dev, "sense resistor value of %d is invalid in node %pOF\n", sense, port_node);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		port->node = of_node_get(port_node);
+	}
+	for (i = 0; i < LTC4266_MAX_PORTS; i++) {
+		ret = ltc4266_port_init(ltc4266, i);
+		if (ret < 0) {
+			dev_err(ltc4266->dev, "Failed to initialize port %d\n", i);
+			goto out;
+		}
+	}
+
+	return 0;
+out:
+	for (i = 0; i < LTC4266_MAX_PORTS; i++) {
+		if (!ltc4266->ports[i])
+			continue;
+
+		if (ltc4266->ports[i]->node)
+			of_node_put(ltc4266->ports[i]->node);
+	}
+	return ret;
+}
+
+static const struct pse_controller_ops ltc4266_ops = {
+	.setup_pi_matrix = ltc4266_setup_pi_matrix,
+	.pi_get_admin_state = ltc4266_pi_get_admin_state,
+	.pi_get_pw_status = ltc4266_pi_get_pw_status,
+	.pi_get_pw_class = ltc4266_pi_get_pw_class,
+	.pi_get_actual_pw = ltc4266_pi_get_actual_pw,
+	.pi_enable = ltc4266_pi_enable,
+	.pi_disable = ltc4266_pi_disable,
+	.pi_get_voltage = ltc4266_pi_get_voltage,
+	.pi_get_pw_limit = ltc4266_pi_get_pw_limit,
+	.pi_set_pw_limit = ltc4266_pi_set_pw_limit,
+	.pi_get_pw_limit_ranges = ltc4266_pi_get_pw_limit_ranges,
+};
+
+#define LTC4266_INTERRUPT_SOURCES	(LTC4266_INT_SUPPLY | LTC4266_INT_TSTART | LTC4266_INT_TCUT | LTC4266_INT_CLASS | LTC4266_INT_DIS | LTC4266_INT_PWRENA | LTC4266_INT_PWRGD)
+
+static void ltc4266_enable_interrupts(struct i2c_client *client)
+{
+	i2c_smbus_write_byte_data(client, LTC4266_REG_INTMASK, LTC4266_INTERRUPT_SOURCES); /* Unmask interrupts */
+}
+
+static int ltc4266_disable_interrupts(struct i2c_client *client)
+{
+	int r;
+	/* Mask out the chip's interrupt */
+	r = i2c_smbus_write_byte_data(client, LTC4266_REG_INTMASK, 0x00);
+	if (r < 0) {
+		dev_err(&client->dev, "Failed to disable interrupts, err %d\n", r);
+		return r;
+	}
+	/* Reset the (SMBus Alert) interrupt pin */
+	i2c_smbus_write_byte_data(client, LTC4266_REG_RSTPB, LTC4266_RSTPB_PINCLR);
+	return 0;
+}
+
+static void handle_classification_event(struct ltc4266 *ltc4266, int detect_event)
+{
+	u8 classified_ports;
+	int ret = 0;
+	int i = 0;
+
+	if (detect_event < 0)
+		return;
+
+	for (i = 0, classified_ports = FIELD_GET(GENMASK(7, 4), detect_event);
+			classified_ports && i < LTC4266_MAX_PORTS; classified_ports >>= 1, i++) {
+		if (!(classified_ports & BIT(0)))
+			continue;
+		if (!ltc4266_pi_is_enabled(&ltc4266->pcdev, i))
+			continue;
+
+		ret = ltc4266_pi_get_pw_class(&ltc4266->pcdev, i);
+		if (ret < 0) {
+			dev_warn(&ltc4266->client->dev, "Invalid class %d\n", ret);
+			continue;
+		}
+		dev_dbg(ltc4266->dev, "port %d has a classification result of %d\n", i, ret);
+		ltc4266_port_set_ilim(ltc4266, i, ret);
+
+		/* It is possible we're in this handler before the ports are non-null */
+		if (!ltc4266->ports[i])
+			continue;
+
+		dev_dbg(&ltc4266->client->dev, "%s is powering port %d because it's enabled\n", __func__, i);
+		if (ltc4266->ports[i]->current_limit > 0) {
+			dev_dbg(ltc4266->dev, "Port %d is using a previously set current limit of %d\n", i, ltc4266->ports[i]->current_limit);
+			ltc4266_port_set_icut(ltc4266, i, ltc4266->ports[i]->current_limit);
+		} else {
+			ltc4266_port_set_icut(ltc4266, i, ltc4266_class_to_icut[ret]);
+		}
+		i2c_smbus_write_byte_data(ltc4266->client, LTC4266_REG_PWRPB, BIT(i));
+	}
+}
+
+static irqreturn_t ltc4266_irq_handler_thread(int irq, void *private)
+{
+	struct ltc4266 *ltc4266 = private;
+	int event, intstat;
+
+	ltc4266_disable_interrupts(ltc4266->client);
+
+	intstat = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_INTSTAT);
+	if (intstat < 0) {
+		dev_err(&ltc4266->client->dev, "Error %d reading register 0x%02X", intstat, LTC4266_REG_INTSTAT);
+		goto done;
+	}
+
+	if (!intstat) {
+		dev_dbg(ltc4266->dev, "Intstat is zero yet we're in the interrupt routine...\n");
+		goto done;
+	}
+
+	if (intstat & (LTC4266_INT_SUPPLY)) {
+		event = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_SUPEVN_COR);
+		if (event) {
+			dev_info(ltc4266->dev, "Supply event=0x%02X\n", event);
+			goto done;
+		}
+	}
+
+	/* There isn't anything we need to actually do for the
+	 * Tstart/Tcut/Disconnect events here, except for reading the clear-on-read
+	 * register
+	 */
+	if (intstat & (LTC4266_INT_TCUT | LTC4266_INT_TSTART | LTC4266_INT_DIS)) {
+		i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_TSEVN_COR);
+		i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_FLTEVN_COR);
+	}
+
+	if (intstat & (LTC4266_INT_DET | LTC4266_INT_CLASS))
+		handle_classification_event(ltc4266, i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_DETEVN_COR));
+
+	if (intstat & (LTC4266_INT_PWRGD | LTC4266_INT_PWRENA))
+		i2c_smbus_read_byte_data(ltc4266->client, LTC4266_REG_PWREVN_COR);
+
+done:
+	ltc4266_enable_interrupts(ltc4266->client);
+	return IRQ_HANDLED;
+}
+
+static int ltc4266_probe(struct i2c_client *client)
+{
+	int ret;
+	u8 id_reg;
+	struct ltc4266 *ltc4266;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return dev_err_probe(&client->dev, -ENODEV, "I2C Bus Adapter needs I2C functionality\n");
+
+	/* The id_reg value here is not what was specified in the datasheet, nor
+	 * the include file. Lets make sure we've got something at address 0x2F that
+	 * happens to have a register at 0x1B that returns 0x64
+	 */
+	id_reg = i2c_smbus_read_byte_data(client, LTC4266_REG_ID);
+	if (id_reg != LTC4266_ID)
+		return dev_err_probe(&client->dev, -ENODEV, "Expected an ID of 0x64, saw 0x%02X\n", id_reg);
+
+	/* Reset the chip */
+	i2c_smbus_write_byte_data(client, LTC4266_REG_RSTPB,  LTC4266_RSTPB_INTCLR | LTC4266_RSTPB_RSTALL);
+
+	/* LTC4266 requires approximately 10 ms after reset to be stable; if it
+	 * isn't, then there is typically an undervoltage lockout/something pretty bad
+	 * going on. We give it 50 ms here so we don't need to poll the chip and use I2C bandwidth
+	 */
+	msleep(50);
+
+	/* Let's make sure the chip came out of reset (if not, the chip is probably
+	 * either (no longer?) present, in thermal shutdown, or watchdogged....either
+	 * way, there's nothing we can do in software to fix it)
+	 */
+	id_reg = i2c_smbus_read_byte_data(client, LTC4266_REG_ID);
+	if (id_reg != 0x64)
+		return dev_err_probe(&client->dev, -ENODEV, "Failed to re-read LTC4266 device ID after reset 0x%02X\n", id_reg);
+
+	ltc4266 = devm_kzalloc(&client->dev, sizeof(struct ltc4266), GFP_KERNEL);
+	if (!ltc4266)
+		return -ENOMEM;
+
+	mutex_init(&ltc4266->lock);
+
+	i2c_set_clientdata(client, ltc4266);
+	ltc4266->client = client;
+	ltc4266->np = client->dev.of_node;
+
+	/* After reset, the LTC4266 will interrupt with a (single) supply fault.
+	 * Clear it here and discard the result
+	 */
+	i2c_smbus_read_byte_data(client, LTC4266_REG_SUPEVN_COR);
+
+	ltc4266_disable_interrupts(client);
+
+	ltc4266->pcdev.owner = THIS_MODULE;
+	ltc4266->pcdev.ops = &ltc4266_ops;
+	ltc4266->dev = &client->dev;
+	ltc4266->pcdev.dev = &client->dev;
+	ltc4266->pcdev.types = ETHTOOL_PSE_C33;
+	ltc4266->pcdev.nr_lines = LTC4266_MAX_PORTS;
+
+	ret = devm_pse_controller_register(ltc4266->dev, &ltc4266->pcdev);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+						"Failed to register PSE controller\n");
+
+	if (client->irq) {
+		dev_dbg(ltc4266->dev, "Client IRQ is set!\n");
+
+		/* Enable the interrupt pin */
+		ltc4266_write_reg(ltc4266, LTC4266_REG_MCONF,
+				  LTC4266_MCONF_INTERRUPT_ENABLE, LTC4266_MCONF_INTERRUPT_ENABLE);
+
+		ret = devm_request_threaded_irq(ltc4266->dev, client->irq, NULL,
+						ltc4266_irq_handler_thread,
+						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,  "ltc4266-irq", ltc4266);
+		if (ret)
+			dev_err_probe(&client->dev, ret, "Failed to request threaded IRQ\n");
+
+		ltc4266_enable_interrupts(client);
+	}
+
+	return 0;
+}
+
+static void ltc4266_remove(struct i2c_client *client)
+{
+	struct ltc4266 *ltc4266 = i2c_get_clientdata(client);
+	int i;
+	/*Put the LTC4266 into reset */
+	i2c_smbus_write_byte_data(client, LTC4266_REG_RSTPB, LTC4266_RSTPB_RSTALL);
+
+	for (i = 0; i < LTC4266_MAX_PORTS; i++) {
+		if (!ltc4266->ports[i])
+			continue;
+		if (ltc4266->ports[i]->node)
+			of_node_put(ltc4266->ports[i]->node);
+	}
+}
+
+static const struct i2c_device_id ltc4266_id[] = {
+	{.name = "ltc4266"},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ltc4266_id);
+
+static const struct of_device_id ltc4266_of_match[] = {
+	{ .compatible = "lltc,ltc4266" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ltc4266_of_match);
+
+static struct i2c_driver ltc4266_driver = {
+	.driver		= {
+		.name	= "ltc4266",
+		.of_match_table = ltc4266_of_match,
+	},
+	.probe		= ltc4266_probe,
+	.remove		= ltc4266_remove,
+	.id_table	= ltc4266_id,
+};
+module_i2c_driver(ltc4266_driver);
+
+MODULE_AUTHOR("Kyle Swenson <kyle.swenson@....tech>");
+MODULE_DESCRIPTION("LTC4266 PoE PSE Controller Driver");
+MODULE_LICENSE("GPL");
-- 
2.47.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ