Add support for LTC4296-1 is an IEEE 802.3cg-compliant,
five port, single-pair power over Ethernet (SPoE), power
sourcing equipment (PSE) controller.
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/ltc4296-1.c | 644 ++++++++++++++++++++++++++++++++++++++
3 files changed, 655 insertions(+)
create mode 100644 drivers/hwmon/ltc4296-1.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 08a3c863f80a..fc6c2a4f6029 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1095,6 +1095,16 @@ config SENSORS_LTC4282
This driver can also be built as a module. If so, the module will
be called ltc4282.
+config SENSORS_LTC4296_1
+ tristate "Analog Devices LTC4296-1"
+ depends on SPI
+ help
+ If you say yes here you get support for Analog Devices LTC4296-1
+ 5-Port SPoE PSE Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called ltc4296-1.
+
config SENSORS_LTQ_CPUTEMP
bool "Lantiq cpu temperature sensor driver"
depends on SOC_XWAY
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 9554d2fdcf7b..943c6858dded 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -141,6 +141,7 @@ obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o
+obj-$(CONFIG_SENSORS_LTC4296_1) += ltc4296-1.o
obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
obj-$(CONFIG_SENSORS_MAX127) += max127.o
diff --git a/drivers/hwmon/ltc4296-1.c b/drivers/hwmon/ltc4296-1.c
new file mode 100644
index 000000000000..22651f166a36
--- /dev/null
+++ b/drivers/hwmon/ltc4296-1.c
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Analog Devices Inc.
+ *
+ * Driver for the LTC4296_1 SPoE PSE.
+ *
+ * Author: Antoniu Miclaus <antoniu.miclaus@analog.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/spi/spi.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+
+#include <linux/unaligned.h>
+
+#define LTC4296_1_REG_GFLTEV 0x02
+#define LTC4296_1_REG_GFLTMSK 0x03
+#define LTC4296_1_REG_GCAP 0x06
+#define LTC4296_1_REG_GIOST 0x07
+#define LTC4296_1_REG_GCMD 0x08
+#define LTC4296_1_REG_GCFG 0x09
+#define LTC4296_1_REG_GADCCFG 0x0A
+#define LTC4296_1_REG_GADCDAT 0x0B
+#define LTC4296_1_REG_P0EV 0x10
+#define LTC4296_1_REG_P0ST 0x12
+#define LTC4296_1_REG_P0CFG0 0x13
+#define LTC4296_1_REG_P0CFG1 0x14
+#define LTC4296_1_REG_P0ADCCFG 0x15
+#define LTC4296_1_REG_P0ADCDAT 0x16
+#define LTC4296_1_REG_P0SELFTEST 0x17
+#define LTC4296_1_REG_P1EV 0x20
+#define LTC4296_1_REG_P1ST 0x22
+#define LTC4296_1_REG_P1CFG0 0x23
+#define LTC4296_1_REG_P1CFG1 0x24
+#define LTC4296_1_REG_P1ADCCFG 0x25
+#define LTC4296_1_REG_P1ADCDAT 0x26
+#define LTC4296_1_REG_P1SELFTEST 0x27
+#define LTC4296_1_REG_P2EV 0x30
+#define LTC4296_1_REG_P2ST 0x32
+#define LTC4296_1_REG_P2CFG0 0x33
+#define LTC4296_1_REG_P2CFG1 0x34
+#define LTC4296_1_REG_P2ADCCFG 0x35
+#define LTC4296_1_REG_P2ADCDAT 0x36
+#define LTC4296_1_REG_P2SELFTEST 0x37
+#define LTC4296_1_REG_P3EV 0x40
+#define LTC4296_1_REG_P3ST 0x42
+#define LTC4296_1_REG_P3CFG0 0x43
+#define LTC4296_1_REG_P3CFG1 0x44
+#define LTC4296_1_REG_P3ADCCFG 0x45
+#define LTC4296_1_REG_P3ADCDAT 0x46
+#define LTC4296_1_REG_P3SELFTEST 0x47
+#define LTC4296_1_REG_P4EV 0x50
+#define LTC4296_1_REG_P4ST 0x52
+#define LTC4296_1_REG_P4CFG0 0x53
+#define LTC4296_1_REG_P4CFG1 0x54
+#define LTC4296_1_REG_P4ADCCFG 0x55
+#define LTC4296_1_REG_P4ADCDAT 0x56
+#define LTC4296_1_REG_P4SELFTEST 0x57
+
+/* LTC4296_1_REG_GFLTEV */
+#define LTC4296_1_UVLO_DIGITAL_MSK BIT(4)
+#define LTC4296_1_COMMAND_FAULT_MSK BIT(3)
+#define LTC4296_1_PEC_FAULT_MSK BIT(2)
+#define LTC4296_1_MEMORY_FAULT_MSK BIT(1)
+#define LTC4296_1_LOW_CKT_BRK_FAULT_MSK BIT(0)
+
+/* LTC4296_1_REG_GCAP */
+#define LTC4296_1_SCCP_SUPPORT_MSK BIT(6)
+#define LTC4296_1_WAKE_FWD_SUPPORT_MSK BIT(5)
+#define LTC4296_1_NUMPORTS_MSK GENMASK(4, 0)
+
+/* LTC4296_1_REG_GIOST */
+#define LTC4296_1_PG_OUT4_MSK BIT(8)
+#define LTC4296_1_PG_OUT3_MSK BIT(7)
+#define LTC4296_1_PG_OUT2_MSK BIT(6)
+#define LTC4296_1_PG_OUT1_MSK BIT(5)
+#define LTC4296_1_PG_OUT0_MSK BIT(4)
+#define LTC4296_1_PAD_AUTO_MSK BIT(3)
+#define LTC4296_1_PAD_WAKEUP_MSK BIT(2)
+#define LTC4296_1_PAD_WAKEUP_DRIVE_MSK BIT(1)
+
+/* LTC4296_1_REG_GCMD */
+#define LTC4296_1_SW_RESET_MSK GENMASK(15, 8)
+#define LTC4296_1_WRITE_PROTECT_MSK GENMASK(7, 0)
+
+/* LTC4296_1_REG_GCFG */
+#define LTC4296_1_MASK_LOWFAULT_MSK BIT(5)
+#define LTC4296_1_TLIM_DISABLE_MSK BIT(4)
+#define LTC4296_1_TLIM_TIMER_SLEEP_MSK GENMASK(3, 2)
+#define LTC4296_1_REFRESH_MSK BIT(1)
+#define LTC4296_1_SW_VIN_PGOOD_MSK BIT(0)
+
+/* LTC4296_1_REG_GADCCFG */
+#define LTC4296_1_GADC_SAMPLE_MODE_MSK GENMASK(6, 5)
+#define LTC4296_1_GADC_SEL_MSK GENMASK(4, 0)
+
+/* LTC4296_1_REG_GADCDAT */
+#define LTC4296_1_GADC_MISSED_MSK BIT(13)
+#define LTC4296_1_GADC_NEW_MSK BIT(12)
+#define LTC4296_1_GADC_MSK GENMASK(11, 0)
+
+/* LTC4296_1_REG_PXEV */
+#define LTC4296_1_VALID_SIGNATURE_MSK BIT(9)
+#define LTC4296_1_INVALID_SIGNATURE_MSK BIT(8)
+#define LTC4296_1_TOFF_TIMER_DONE_MSK BIT(7)
+#define LTC4296_1_OVERLOAD_DETECTED_ISLEEP_MSK BIT(6)
+#define LTC4296_1_OVERLOAD_DETECTED_IPOWER_MSK BIT(5)
+#define LTC4296_1_MFVS_TIMEOUT_MSK BIT(4)
+#define LTC4296_1_TINRUSH_TIMER_DONE_MSK BIT(3)
+#define LTC4296_1_PD_WAKEUP_MSK BIT(2)
+#define LTC4296_1_LSNS_FORWARD_FAULT_MSK BIT(1)
+#define LTC4296_1_LSNS_REVERSE_FAULT_MSK BIT(0)
+
+/* LTC4296_1_REG_PXST */
+#define LTC4296_1_DET_VHIGH_MSK BIT(13)
+#define LTC4296_1_DET_VLOW_MSK BIT(12)
+#define LTC4296_1_POWER_STABLE_HI_MSK BIT(11)
+#define LTC4296_1_POWER_STABLE_LO_MSK BIT(10)
+#define LTC4296_1_POWER_STABLE_MSK BIT(9)
+#define LTC4296_1_OVERLOAD_HELD_MSK BIT(8)
+#define LTC4296_1_PI_SLEEPING_MSK BIT(7)
+#define LTC4296_1_PI_PREBIASED_MSK BIT(6)
+#define LTC4296_1_PI_DETECTING_MSK BIT(5)
+#define LTC4296_1_PI_POWERED_MSK BIT(4)
+#define LTC4296_1_PI_DISCHARGE_EN_MSK BIT(3)
+#define LTC4296_1_PSE_STATUS_MSK GENMASK(2, 0)
+
+/* LTC4296_1_REG_PXCFG0 */
+#define LTC4296_1_SW_INRUSH_MSK BIT(15)
+#define LTC4296_1_END_CLASSIFICATION_MSK BIT(14)
+#define LTC4296_1_SET_CLASSIFICATION_MODE_MSK BIT(13)
+#define LTC4296_1_DISABLE_DETECTION_PULLUP_MSK BIT(12)
+#define LTC4296_1_TDET_DISABLE_MSK BIT(11)
+#define LTC4296_1_FOLDBACK_DISABLE_MSK BIT(10)
+#define LTC4296_1_SOFT_START_DISABLE_MSK BIT(9)
+#define LTC4296_1_TOFF_TIMER_DISABLE_MSK BIT(8)
+#define LTC4296_1_TMFVDO_TIMER_DISABLE_MSK BIT(7)
+#define LTC4296_1_SW_PSE_READY_MSK BIT(6)
+#define LTC4296_1_SW_POWER_AVAILABLE_MSK BIT(5)
+#define LTC4296_1_UPSTREAM_WAKEUP_DISABLE_MSK BIT(4)
+#define LTC4296_1_DOWNSTREAM_WAKEUP_DISABLE_MSK BIT(3)
+#define LTC4296_1_SW_PSE_WAKEUP_MSK BIT(2)
+#define LTC4296_1_HW_EN_MASK_MSK BIT(1)
+#define LTC4296_1_SW_EN_MSK BIT(0)
+
+/* LTC4296_1_REG_PXCFG1 */
+#define LTC4296_1_PREBIAS_OVERRIDE_GOOD_MSK BIT(8)
+#define LTC4296_1_TLIM_TIMER_TOP_MSK GENMASK(7, 6)
+#define LTC4296_1_TOD_TRESTART_TIMER_MSK GENMASK(5, 4)
+#define LTC4296_1_TINRUSH_TIMER_MSK GENMASK(3, 2)
+#define LTC4296_1_SIG_OVERRIDE_BAD_MSK BIT(1)
+#define LTC4296_1_SIG_OVERRIDE_GOOD_MSK BIT(0)
+
+/* LTC4296_1_REG_PXADCCFG */
+#define LTC4296_1_MFVS_THRESHOLD_MSK GENMASK(7, 0)
+
+/* LTC4296_1_REG_PXADCDAT */
+#define LTC4296_1_MISSED_MSK BIT(13)
+#define LTC4296_1_NEW_MSK BIT(12)
+#define LTC4296_1_SOURCE_CURRENT_MSK GENMASK(11, 0)
+
+/* Miscellaneous Definitions*/
+#define LTC4296_1_RESET_CODE 0x73
+#define LTC4296_1_UNLOCK_KEY 0x05
+#define LTC4296_1_LOCK_KEY 0xA0
+
+#define LTC4296_1_ADC_OFFSET 2049
+
+#define LTC4296_1_VGAIN (35230 / 1000)
+#define LTC4296_1_IGAIN (1 / 10)
+
+#define LTC4296_1_VMAX 1
+#define LTC4296_1_VMIN 0
+
+#define LTC4296_1_MAX_PORTS 5
+
+enum ltc4296_1_port {
+ LTC_PORT0,
+ LTC_PORT1,
+ LTC_PORT2,
+ LTC_PORT3,
+ LTC_PORT4,
+ LTC_NO_PORT
+};
+
+enum ltc4296_1_port_status {
+ LTC_PORT_DISABLED,
+ LTC_PORT_ENABLED
+};
+
+enum ltc4296_1_port_reg_offset_e {
+ LTC_PORT_EVENTS = 0,
+ LTC_PORT_STATUS = 2,
+ LTC_PORT_CFG0 = 3,
+ LTC_PORT_CFG1 = 4,
+ LTC_PORT_ADCCFG = 5,
+ LTC_PORT_ADCDAT = 6,
+ LTC_PORT_SELFTEST = 7
+};
+
+static u8 set_port_vout[LTC4296_1_MAX_PORTS] = {0x04, 0x06, 0x08, 0x0A, 0x0C};
+
+struct ltc4296_1_state {
+ struct spi_device *spi;
+ u32 r_sense_mohm[LTC4296_1_MAX_PORTS];
+ u8 data[5];
+};
+
+static u8 ltc4296_1_get_pec_byte(u8 data, u8 seed)
+{
+ u8 pec = seed;
+ u8 din, in0, in1, in2;
+ int bit;
+
+ for (bit = 7; bit >= 0; bit--) {
+ din = (data >> bit) & 0x01;
+ in0 = din ^ ((pec >> 7) & 0x01);
+ in1 = in0 ^ (pec & 0x01);
+ in2 = in0 ^ ((pec >> 1) & 0x01);
+ pec = (pec << 1);
+ pec &= ~(0x07);
+ pec = pec | in0 | (in1 << 1) | (in2 << 2);
+ }
+
+ return pec;
+}
+
+static int ltc4296_1_spi_write(struct ltc4296_1_state *st, unsigned int reg, u16 val)
+{
+ st->data[0] = reg << 1 | 0x0;
+ st->data[1] = ltc4296_1_get_pec_byte(st->data[0], 0x41);
+ st->data[2] = val >> 8;
+ st->data[3] = val & 0xFF;
+ st->data[4] = ltc4296_1_get_pec_byte(st->data[3],
+ ltc4296_1_get_pec_byte(st->data[2], 0x41));
+
+ return spi_write(st->spi, &st->data[0], 5);
+}
+
+static int ltc4296_1_spi_read(struct ltc4296_1_state *st, unsigned int reg,
+ u16 *val)
+{
+ int ret;
+ struct spi_transfer t = {0};
+
+ t.tx_buf = &st->data[0];
+ t.rx_buf = &st->data[0];
+ t.len = 5;
+
+ st->data[0] = reg << 1 | 0x1;
+ st->data[1] = ltc4296_1_get_pec_byte(st->data[0], 0x41);
+
+ ret = spi_sync_transfer(st->spi, &t, 1);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be16(&st->data[2]);
+
+ return 0;
+}
+
+static int ltc4296_1_reset(struct ltc4296_1_state *st)
+{
+ int ret;
+
+ ret = ltc4296_1_spi_write(st, LTC4296_1_REG_GCMD, LTC4296_1_RESET_CODE);
+ if (ret)
+ return ret;
+
+ fsleep(10000);
+
+ return 0;
+}
+
+static int ltc4296_1_get_port_addr(enum ltc4296_1_port port_no,
+ enum ltc4296_1_port_reg_offset_e port_offset,
+ u8 *port_addr)
+{
+ if (!port_addr)
+ return -EINVAL;
+
+ *port_addr = (((port_no + 1) << 4) + port_offset);
+
+ return 0;
+}
+
+static int ltc4296_1_read_gadc(struct ltc4296_1_state *st, int *port_voltage_mv)
+{
+ int ret;
+ u16 val16;
+
+ if (!st || !port_voltage_mv)
+ return -EINVAL;
+
+ ret = ltc4296_1_spi_read(st, LTC4296_1_REG_GADCDAT, &val16);
+ if (ret)
+ return ret;
+ if ((val16 & LTC4296_1_GADC_NEW_MSK) != LTC4296_1_GADC_NEW_MSK)
+ return -EINVAL;
+
+ /* A new ADC value is available */
+ *port_voltage_mv = (((val16 & LTC4296_1_GADC_MSK) - LTC4296_1_ADC_OFFSET) *
+ LTC4296_1_VGAIN);
+
+ return 0;
+}
+
+static int ltc4296_1_read_port_current(struct ltc4296_1_state *st, enum ltc4296_1_port port_no,
+ int *port_i_out_ma)
+{
+ int ret;
+ u8 port_addr = 0;
+ u16 val16;
+
+ if (!st || !port_i_out_ma || !st->r_sense_mohm[port_no])
+ return -EINVAL;
+
+ ret = ltc4296_1_get_port_addr(port_no, LTC_PORT_ADCDAT, &port_addr);
+ if (ret)
+ return ret;
+
+ ret = ltc4296_1_spi_read(st, port_addr, &val16);
+ if (ret)
+ return ret;
+
+ if ((val16 & LTC4296_1_NEW_MSK) == LTC4296_1_NEW_MSK)
+ /* A new ADC value is available */
+ *port_i_out_ma = (FIELD_GET(LTC4296_1_SOURCE_CURRENT_MSK, val16) - LTC4296_1_ADC_OFFSET)
+ * 100 / st->r_sense_mohm[port_no];
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ltc4296_1_port_prebias(struct ltc4296_1_state *st, enum ltc4296_1_port port_no)
+{
+ int ret;
+ u8 port_addr = 0;
+
+ ret = ltc4296_1_get_port_addr(port_no, LTC_PORT_CFG1, &port_addr);
+ if (ret)
+ return ret;
+
+ return ltc4296_1_spi_write(st, port_addr, LTC4296_1_PREBIAS_OVERRIDE_GOOD_MSK |
+ FIELD_PREP(LTC4296_1_TINRUSH_TIMER_MSK, 2) |
+ LTC4296_1_SIG_OVERRIDE_GOOD_MSK);
+}
+
+static int ltc4296_1_port_en(struct ltc4296_1_state *st, enum ltc4296_1_port port_no)
+{
+ int ret;
+ u8 port_addr = 0;
+
+ ret = ltc4296_1_get_port_addr(port_no, LTC_PORT_CFG0, &port_addr);
+ if (ret)
+ return ret;
+
+ return ltc4296_1_spi_write(st, port_addr, (LTC4296_1_SW_EN_MSK |
+ LTC4296_1_SW_POWER_AVAILABLE_MSK |
+ LTC4296_1_SW_PSE_READY_MSK |
+ LTC4296_1_TMFVDO_TIMER_DISABLE_MSK));
+}
+
+static int ltc4296_1_port_dis(struct ltc4296_1_state *st, enum ltc4296_1_port port_no)
+{
+ int ret;
+ u8 port_addr = 0;
+
+ ret = ltc4296_1_get_port_addr(port_no, LTC_PORT_CFG0, &port_addr);
+ if (ret)
+ return ret;
+
+ return ltc4296_1_spi_write(st, port_addr, 0);
+}
+
+static int ltc4296_1_force_port_pwr(struct ltc4296_1_state *st, enum ltc4296_1_port port_no)
+{
+ int ret;
+ u8 port_addr = 0;
+
+ if (!st)
+ return -EINVAL;
+
+ ret = ltc4296_1_get_port_addr(port_no, LTC_PORT_CFG0, &port_addr);
+ if (ret)
+ return ret;
+
+ return ltc4296_1_spi_write(st, port_addr,
+ (LTC4296_1_SW_EN_MSK | LTC4296_1_SW_POWER_AVAILABLE_MSK |
+ LTC4296_1_SW_PSE_READY_MSK | LTC4296_1_TMFVDO_TIMER_DISABLE_MSK));
+}
+
+static int ltc4296_1_set_gadc_vout(struct ltc4296_1_state *st, enum ltc4296_1_port port_no)
+{
+ u16 val16;
+
+ if (!st)
+ return -EINVAL;
+
+ /* LTC4296_1-1 Set global ADC to measure Port Vout GADCCFG=ContModeLowGain|VoutPort */
+ val16 = FIELD_PREP(LTC4296_1_GADC_SEL_MSK, set_port_vout[port_no]);
+ /* Set Continuous mode with LOW GAIN bit */
+ val16 = val16 | FIELD_PREP(LTC4296_1_GADC_SAMPLE_MODE_MSK, 2);
+
+ return ltc4296_1_spi_write(st, LTC4296_1_REG_GADCCFG, val16);
+}
+
+static int ltc4296_1_init(struct ltc4296_1_state *st)
+{
+ int ret;
+ u16 value;
+
+ ret = ltc4296_1_reset(st);
+ if (ret)
+ return ret;
+
+ ret = ltc4296_1_spi_write(st, LTC4296_1_REG_GCMD, LTC4296_1_UNLOCK_KEY);
+ if (ret)
+ return ret;
+
+ ret = ltc4296_1_spi_read(st, LTC4296_1_REG_GCMD, &value);
+ if (ret)
+ return ret;
+
+ if (value != LTC4296_1_UNLOCK_KEY) {
+ dev_err_probe(&st->spi->dev, -EINVAL, "Device locked. Write Access is disabled\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static ssize_t input_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int channel = to_sensor_dev_attr(attr)->index;
+ struct ltc4296_1_state *st = dev_get_drvdata(dev);
+ int ret;
+ bool val;
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ if (!val) {
+ ret = ltc4296_1_port_dis(st, channel);
+ } else {
+ ret = ltc4296_1_port_prebias(st, channel);
+ if (ret)
+ return ret;
+
+ ret = ltc4296_1_port_en(st, channel);
+ }
+
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t input_enable_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int channel = to_sensor_dev_attr(attr)->index;
+ struct ltc4296_1_state *st = dev_get_drvdata(dev);
+ int ret;
+ u8 port_addr = 0;
+ u16 val = 0;
+
+ ret = ltc4296_1_get_port_addr(channel, LTC_PORT_CFG0, &port_addr);
+ if (ret)
+ return ret;
+
+ ret = ltc4296_1_spi_read(st, port_addr, &val);
+ if (ret)
+ return ret;
+
+ if (val & LTC4296_1_SW_EN_MSK)
+ return sprintf(buf, "Port %d enabled.\n", channel);
+
+ return sprintf(buf, "Port %d disabled.\n", channel);
+}
+
+static ssize_t input_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int channel = to_sensor_dev_attr(attr)->index;
+ struct ltc4296_1_state *st = dev_get_drvdata(dev);
+ int ret, data;
+
+ ret = ltc4296_1_port_prebias(st, channel);
+ if (ret)
+ return ret;
+
+ ret = ltc4296_1_force_port_pwr(st, channel);
+ if (ret)
+ return ret;
+
+ ret = ltc4296_1_set_gadc_vout(st, channel);
+ if (ret)
+ return ret;
+
+ fsleep(10000);
+
+ ret = ltc4296_1_read_gadc(st, &data);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d mV\n", data);
+}
+
+static ssize_t curr_input_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int channel = to_sensor_dev_attr(attr)->index;
+ struct ltc4296_1_state *st = dev_get_drvdata(dev);
+ int ret, data;
+
+ ret = ltc4296_1_read_port_current(st, channel, &data);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d mA\n", data);
+}
+
+static SENSOR_DEVICE_ATTR_RW(in0_input_enable, input_enable, 0);
+static SENSOR_DEVICE_ATTR_RW(in1_input_enable, input_enable, 1);
+static SENSOR_DEVICE_ATTR_RW(in2_input_enable, input_enable, 2);
+static SENSOR_DEVICE_ATTR_RW(in3_input_enable, input_enable, 3);
+static SENSOR_DEVICE_ATTR_RW(in4_input_enable, input_enable, 4);
+
+static SENSOR_DEVICE_ATTR_RO(in0_input, input, 0);
+static SENSOR_DEVICE_ATTR_RO(in1_input, input, 1);
+static SENSOR_DEVICE_ATTR_RO(in2_input, input, 2);
+static SENSOR_DEVICE_ATTR_RO(in3_input, input, 3);
+static SENSOR_DEVICE_ATTR_RO(in4_input, input, 4);
+
+static SENSOR_DEVICE_ATTR_RO(curr0_input, curr_input, 0);
+static SENSOR_DEVICE_ATTR_RO(curr1_input, curr_input, 1);
+static SENSOR_DEVICE_ATTR_RO(curr2_input, curr_input, 2);
+static SENSOR_DEVICE_ATTR_RO(curr3_input, curr_input, 3);
+static SENSOR_DEVICE_ATTR_RO(curr4_input, curr_input, 4);
+
+static struct attribute *ltc4296_1_attrs[] = {
+ &sensor_dev_attr_in0_input_enable.dev_attr.attr,
+ &sensor_dev_attr_in1_input_enable.dev_attr.attr,
+ &sensor_dev_attr_in2_input_enable.dev_attr.attr,
+ &sensor_dev_attr_in3_input_enable.dev_attr.attr,
+ &sensor_dev_attr_in4_input_enable.dev_attr.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in3_input.dev_attr.attr,
+ &sensor_dev_attr_in4_input.dev_attr.attr,
+ &sensor_dev_attr_curr0_input.dev_attr.attr,
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_curr2_input.dev_attr.attr,
+ &sensor_dev_attr_curr3_input.dev_attr.attr,
+ &sensor_dev_attr_curr4_input.dev_attr.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ltc4296_1);
+
+static int ltc4296_1_probe(struct spi_device *spi)
+{
+ struct ltc4296_1_state *st;
+ struct device *hwmon_dev;
+ u32 val, addr;
+ int ret;
+
+ st = devm_kzalloc(&spi->dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->spi = spi;
+
+ ret = devm_regulator_get_enable(dev, "vin");
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to enable regulator\n");
+
+ device_for_each_child_node_scoped(&spi->dev, child) {
+ ret = fwnode_property_read_u32(child, "reg", &addr);
+ if (ret < 0)
+ return ret;
+
+ if (addr > 4)
+ return -EINVAL;
+
+ ret = fwnode_property_read_u32(child,
+ "shunt-resistor-micro-ohms",
+ &val);
+
+ if (!ret) {
+ if (!val)
+ return dev_err_probe(&spi->dev, -EINVAL,
+ "shunt resistor value cannot be zero\n");
+
+ st->r_sense_mohm[addr] = val;
+ }
+ }
+
+ ret = ltc4296_1_init(st);
+ if (ret)
+ return ret;
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(&spi->dev,
+ spi->modalias,
+ st, ltc4296_1_groups);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct spi_device_id ltc4296_1_id[] = {
+ { "ltc4296-1", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ltc4296_1_id);
+
+static const struct of_device_id ltc4296_1_of_match[] = {
+ { .compatible = "adi,ltc4296-1", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ltc4296_1_of_match);
+
+static struct spi_driver ltc4296_1_driver = {
+ .probe = ltc4296_1_probe,
+ .driver = {
+ .name = "ltc4296-1",
+ .of_match_table = ltc4296_1_of_match,
+ },
+ .id_table = ltc4296_1_id,
+};
+module_spi_driver(ltc4296_1_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("LTC4296_1 SPoE PSE");
--
2.46.2
Hi, On 10/25/24 04:56, Antoniu Miclaus wrote: > Add support for LTC4296-1 is an IEEE 802.3cg-compliant, > five port, single-pair power over Ethernet (SPoE), power > sourcing equipment (PSE) controller. > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > --- ... > + hwmon_dev = devm_hwmon_device_register_with_groups(&spi->dev, > + spi->modalias, > + st, ltc4296_1_groups); New drivers must use the the with_info() hardware monitoring API. The API use is inappropriate: _enable attributes are supposed to enable monitoring, not a power source. The hardware monitoring subsystem is responsible for hardware _monitoring_, not control. It can be tied to the regulator subsystem, but even that seems to be be inappropriate here. I think the driver should probably reside in drivers/net/pse-pd/. That doesn't mean it can not support hardware monitoring, but that isn't really the chip's primary functionality. Yes, I see that we already have ti,tps23861 in the hardware monitoring subsystem, but that may be just as wrong. I am copying the PSE subsystem maintainers and mailing list for advice. Thanks, Guenter
Hi Guenter, On Fri, Oct 25, 2024 at 07:22:08AM -0700, Guenter Roeck wrote: > Hi, > > On 10/25/24 04:56, Antoniu Miclaus wrote: > > Add support for LTC4296-1 is an IEEE 802.3cg-compliant, > > five port, single-pair power over Ethernet (SPoE), power > > sourcing equipment (PSE) controller. > > > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > > --- > > ... > > > + hwmon_dev = devm_hwmon_device_register_with_groups(&spi->dev, > > + spi->modalias, > > + st, ltc4296_1_groups); > > New drivers must use the the with_info() hardware monitoring API. > > The API use is inappropriate: _enable attributes are supposed to enable > monitoring, not a power source. The hardware monitoring subsystem is > responsible for hardware _monitoring_, not control. It can be tied to > the regulator subsystem, but even that seems to be be inappropriate here. > I think the driver should probably reside in drivers/net/pse-pd/. > That doesn't mean it can not support hardware monitoring, but that > isn't really the chip's primary functionality. > > Yes, I see that we already have ti,tps23861 in the hardware monitoring > subsystem, but that may be just as wrong. > > I am copying the PSE subsystem maintainers and mailing list for advice. Thank you! Yes, the PSE subsystem is the proper location for this chip. Regards, Oleksij -- Pengutronix e.K. | | Steuerwalder Str. 21 | http://www.pengutronix.de/ | 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
© 2016 - 2024 Red Hat, Inc.