kernel: add upstream patches for tps23861 PoE controller
These patches support the tps23861 PoE controller found on a number of managed switches. The TPS23861 is an I2C-based quad IEEE 802.3at (PoE+) Power-over-Ethernet PSE controller. It's also found on some Realtek based switches, where we expect the bulk of the users to reside. Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com> [Disable driver in generic/config-5.10] Signed-off-by: Sander Vanheule <sander@svanheule.net>
This commit is contained in:
		 Alexandru Gagniuc
					Alexandru Gagniuc
				
			
				
					committed by
					
						 Sander Vanheule
						Sander Vanheule
					
				
			
			
				
	
			
			
			 Sander Vanheule
						Sander Vanheule
					
				
			
						parent
						
							7bba7ccde9
						
					
				
				
					commit
					c5fbd49d3f
				
			| @@ -0,0 +1,711 @@ | ||||
| From 97c95dbbba64dbd6e98e033e396695f328033966 Mon Sep 17 00:00:00 2001 | ||||
| From: Robert Marko <robert.marko@sartura.hr> | ||||
| Date: Thu, 21 Jan 2021 14:44:33 +0100 | ||||
| Subject: [PATCH 1/4] hwmon: add Texas Instruments TPS23861 driver | ||||
|  | ||||
| Add basic monitoring support as well as port on/off control for Texas | ||||
| Instruments TPS23861 PoE PSE IC. | ||||
|  | ||||
| Signed-off-by: Robert Marko <robert.marko@sartura.hr> | ||||
| Cc: Luka Perkov <luka.perkov@sartura.hr> | ||||
| Reviewed-by: Guenter Roeck <linux@roeck-us.net> | ||||
| Link: https://lore.kernel.org/r/20210121134434.2782405-2-robert.marko@sartura.hr | ||||
| Signed-off-by: Guenter Roeck <linux@roeck-us.net> | ||||
| --- | ||||
|  Documentation/hwmon/index.rst    |   1 + | ||||
|  Documentation/hwmon/tps23861.rst |  41 +++ | ||||
|  drivers/hwmon/Kconfig            |  11 + | ||||
|  drivers/hwmon/Makefile           |   1 + | ||||
|  drivers/hwmon/tps23861.c         | 601 +++++++++++++++++++++++++++++++ | ||||
|  5 files changed, 655 insertions(+) | ||||
|  create mode 100644 Documentation/hwmon/tps23861.rst | ||||
|  create mode 100644 drivers/hwmon/tps23861.c | ||||
|  | ||||
| --- a/Documentation/hwmon/index.rst | ||||
| +++ b/Documentation/hwmon/index.rst | ||||
| @@ -172,6 +172,7 @@ Hardware Monitoring Kernel Drivers | ||||
|     tmp401 | ||||
|     tmp421 | ||||
|     tmp513 | ||||
| +   tps23861 | ||||
|     tps40422 | ||||
|     tps53679 | ||||
|     twl4030-madc-hwmon | ||||
| --- /dev/null | ||||
| +++ b/Documentation/hwmon/tps23861.rst | ||||
| @@ -0,0 +1,41 @@ | ||||
| +.. SPDX-License-Identifier: GPL-2.0-only | ||||
| + | ||||
| +Kernel driver tps23861 | ||||
| +====================== | ||||
| + | ||||
| +Supported chips: | ||||
| +  * Texas Instruments TPS23861 | ||||
| + | ||||
| +    Prefix: 'tps23861' | ||||
| + | ||||
| +    Datasheet: https://www.ti.com/lit/gpn/tps23861 | ||||
| + | ||||
| +Author: Robert Marko <robert.marko@sartura.hr> | ||||
| + | ||||
| +Description | ||||
| +----------- | ||||
| + | ||||
| +This driver supports hardware monitoring for Texas Instruments TPS23861 PoE PSE. | ||||
| + | ||||
| +TPS23861 is a quad port IEEE802.3at PSE controller with optional I2C control | ||||
| +and monitoring capabilities. | ||||
| + | ||||
| +TPS23861 offers three modes of operation: Auto, Semi-Auto and Manual. | ||||
| + | ||||
| +This driver only supports the Auto mode of operation providing monitoring | ||||
| +as well as enabling/disabling the four ports. | ||||
| + | ||||
| +Sysfs entries | ||||
| +------------- | ||||
| + | ||||
| +======================= ===================================================================== | ||||
| +in[0-3]_input		Voltage on ports [1-4] | ||||
| +in[0-3]_label		"Port[1-4]" | ||||
| +in4_input		IC input voltage | ||||
| +in4_label		"Input" | ||||
| +temp1_input		IC die temperature | ||||
| +temp1_label		"Die" | ||||
| +curr[1-4]_input		Current on ports [1-4] | ||||
| +in[1-4]_label		"Port[1-4]" | ||||
| +in[0-3]_enable		Enable/disable ports [1-4] | ||||
| +======================= ===================================================================== | ||||
| --- a/drivers/hwmon/Kconfig | ||||
| +++ b/drivers/hwmon/Kconfig | ||||
| @@ -1102,6 +1102,17 @@ config SENSORS_TC654 | ||||
|  	  This driver can also be built as a module. If so, the module | ||||
|  	  will be called tc654. | ||||
|   | ||||
| +config SENSORS_TPS23861 | ||||
| +	tristate "Texas Instruments TPS23861 PoE PSE" | ||||
| +	depends on I2C | ||||
| +	select REGMAP_I2C | ||||
| +	help | ||||
| +	  If you say yes here you get support for Texas Instruments | ||||
| +	  TPS23861 802.3at PoE PSE chips. | ||||
| + | ||||
| +	  This driver can also be built as a module. If so, the module | ||||
| +	  will be called tps23861. | ||||
| + | ||||
|  config SENSORS_MENF21BMC_HWMON | ||||
|  	tristate "MEN 14F021P00 BMC Hardware Monitoring" | ||||
|  	depends on MFD_MENF21BMC | ||||
| --- a/drivers/hwmon/Makefile | ||||
| +++ b/drivers/hwmon/Makefile | ||||
| @@ -141,6 +141,7 @@ obj-$(CONFIG_SENSORS_MAX31790)	+= max317 | ||||
|  obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o | ||||
|  obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o | ||||
|  obj-$(CONFIG_SENSORS_TC654)	+= tc654.o | ||||
| +obj-$(CONFIG_SENSORS_TPS23861)	+= tps23861.o | ||||
|  obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o | ||||
|  obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o | ||||
|  obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o | ||||
| --- /dev/null | ||||
| +++ b/drivers/hwmon/tps23861.c | ||||
| @@ -0,0 +1,601 @@ | ||||
| +// SPDX-License-Identifier: GPL-2.0-only | ||||
| +/* | ||||
| + * Copyright (c) 2020 Sartura Ltd. | ||||
| + * | ||||
| + * Driver for the TI TPS23861 PoE PSE. | ||||
| + * | ||||
| + * Author: Robert Marko <robert.marko@sartura.hr> | ||||
| + */ | ||||
| + | ||||
| +#include <linux/bitfield.h> | ||||
| +#include <linux/debugfs.h> | ||||
| +#include <linux/delay.h> | ||||
| +#include <linux/hwmon-sysfs.h> | ||||
| +#include <linux/hwmon.h> | ||||
| +#include <linux/i2c.h> | ||||
| +#include <linux/module.h> | ||||
| +#include <linux/of_device.h> | ||||
| +#include <linux/regmap.h> | ||||
| + | ||||
| +#define TEMPERATURE			0x2c | ||||
| +#define INPUT_VOLTAGE_LSB		0x2e | ||||
| +#define INPUT_VOLTAGE_MSB		0x2f | ||||
| +#define PORT_1_CURRENT_LSB		0x30 | ||||
| +#define PORT_1_CURRENT_MSB		0x31 | ||||
| +#define PORT_1_VOLTAGE_LSB		0x32 | ||||
| +#define PORT_1_VOLTAGE_MSB		0x33 | ||||
| +#define PORT_2_CURRENT_LSB		0x34 | ||||
| +#define PORT_2_CURRENT_MSB		0x35 | ||||
| +#define PORT_2_VOLTAGE_LSB		0x36 | ||||
| +#define PORT_2_VOLTAGE_MSB		0x37 | ||||
| +#define PORT_3_CURRENT_LSB		0x38 | ||||
| +#define PORT_3_CURRENT_MSB		0x39 | ||||
| +#define PORT_3_VOLTAGE_LSB		0x3a | ||||
| +#define PORT_3_VOLTAGE_MSB		0x3b | ||||
| +#define PORT_4_CURRENT_LSB		0x3c | ||||
| +#define PORT_4_CURRENT_MSB		0x3d | ||||
| +#define PORT_4_VOLTAGE_LSB		0x3e | ||||
| +#define PORT_4_VOLTAGE_MSB		0x3f | ||||
| +#define PORT_N_CURRENT_LSB_OFFSET	0x04 | ||||
| +#define PORT_N_VOLTAGE_LSB_OFFSET	0x04 | ||||
| +#define VOLTAGE_CURRENT_MASK		GENMASK(13, 0) | ||||
| +#define PORT_1_RESISTANCE_LSB		0x60 | ||||
| +#define PORT_1_RESISTANCE_MSB		0x61 | ||||
| +#define PORT_2_RESISTANCE_LSB		0x62 | ||||
| +#define PORT_2_RESISTANCE_MSB		0x63 | ||||
| +#define PORT_3_RESISTANCE_LSB		0x64 | ||||
| +#define PORT_3_RESISTANCE_MSB		0x65 | ||||
| +#define PORT_4_RESISTANCE_LSB		0x66 | ||||
| +#define PORT_4_RESISTANCE_MSB		0x67 | ||||
| +#define PORT_N_RESISTANCE_LSB_OFFSET	0x02 | ||||
| +#define PORT_RESISTANCE_MASK		GENMASK(13, 0) | ||||
| +#define PORT_RESISTANCE_RSN_MASK	GENMASK(15, 14) | ||||
| +#define PORT_RESISTANCE_RSN_OTHER	0 | ||||
| +#define PORT_RESISTANCE_RSN_LOW		1 | ||||
| +#define PORT_RESISTANCE_RSN_OPEN	2 | ||||
| +#define PORT_RESISTANCE_RSN_SHORT	3 | ||||
| +#define PORT_1_STATUS			0x0c | ||||
| +#define PORT_2_STATUS			0x0d | ||||
| +#define PORT_3_STATUS			0x0e | ||||
| +#define PORT_4_STATUS			0x0f | ||||
| +#define PORT_STATUS_CLASS_MASK		GENMASK(7, 4) | ||||
| +#define PORT_STATUS_DETECT_MASK		GENMASK(3, 0) | ||||
| +#define PORT_CLASS_UNKNOWN		0 | ||||
| +#define PORT_CLASS_1			1 | ||||
| +#define PORT_CLASS_2			2 | ||||
| +#define PORT_CLASS_3			3 | ||||
| +#define PORT_CLASS_4			4 | ||||
| +#define PORT_CLASS_RESERVED		5 | ||||
| +#define PORT_CLASS_0			6 | ||||
| +#define PORT_CLASS_OVERCURRENT		7 | ||||
| +#define PORT_CLASS_MISMATCH		8 | ||||
| +#define PORT_DETECT_UNKNOWN		0 | ||||
| +#define PORT_DETECT_SHORT		1 | ||||
| +#define PORT_DETECT_RESERVED		2 | ||||
| +#define PORT_DETECT_RESISTANCE_LOW	3 | ||||
| +#define PORT_DETECT_RESISTANCE_OK	4 | ||||
| +#define PORT_DETECT_RESISTANCE_HIGH	5 | ||||
| +#define PORT_DETECT_OPEN_CIRCUIT	6 | ||||
| +#define PORT_DETECT_RESERVED_2		7 | ||||
| +#define PORT_DETECT_MOSFET_FAULT	8 | ||||
| +#define PORT_DETECT_LEGACY		9 | ||||
| +/* Measurment beyond clamp voltage */ | ||||
| +#define PORT_DETECT_CAPACITANCE_INVALID_BEYOND	10 | ||||
| +/* Insufficient voltage delta */ | ||||
| +#define PORT_DETECT_CAPACITANCE_INVALID_DELTA	11 | ||||
| +#define PORT_DETECT_CAPACITANCE_OUT_OF_RANGE	12 | ||||
| +#define POE_PLUS			0x40 | ||||
| +#define OPERATING_MODE			0x12 | ||||
| +#define OPERATING_MODE_OFF		0 | ||||
| +#define OPERATING_MODE_MANUAL		1 | ||||
| +#define OPERATING_MODE_SEMI		2 | ||||
| +#define OPERATING_MODE_AUTO		3 | ||||
| +#define OPERATING_MODE_PORT_1_MASK	GENMASK(1, 0) | ||||
| +#define OPERATING_MODE_PORT_2_MASK	GENMASK(3, 2) | ||||
| +#define OPERATING_MODE_PORT_3_MASK	GENMASK(5, 4) | ||||
| +#define OPERATING_MODE_PORT_4_MASK	GENMASK(7, 6) | ||||
| + | ||||
| +#define DETECT_CLASS_RESTART		0x18 | ||||
| +#define POWER_ENABLE			0x19 | ||||
| +#define TPS23861_NUM_PORTS		4 | ||||
| + | ||||
| +#define TEMPERATURE_LSB			652 /* 0.652 degrees Celsius */ | ||||
| +#define VOLTAGE_LSB			3662 /* 3.662 mV */ | ||||
| +#define SHUNT_RESISTOR_DEFAULT		255000 /* 255 mOhm */ | ||||
| +#define CURRENT_LSB_255			62260 /* 62.260 uA */ | ||||
| +#define CURRENT_LSB_250			61039 /* 61.039 uA */ | ||||
| +#define RESISTANCE_LSB			110966 /* 11.0966 Ohm*/ | ||||
| +#define RESISTANCE_LSB_LOW		157216 /* 15.7216 Ohm*/ | ||||
| + | ||||
| +struct tps23861_data { | ||||
| +	struct regmap *regmap; | ||||
| +	u32 shunt_resistor; | ||||
| +	struct i2c_client *client; | ||||
| +	struct dentry *debugfs_dir; | ||||
| +}; | ||||
| + | ||||
| +static struct regmap_config tps23861_regmap_config = { | ||||
| +	.reg_bits = 8, | ||||
| +	.val_bits = 8, | ||||
| +}; | ||||
| + | ||||
| +static int tps23861_read_temp(struct tps23861_data *data, long *val) | ||||
| +{ | ||||
| +	unsigned int regval; | ||||
| +	int err; | ||||
| + | ||||
| +	err = regmap_read(data->regmap, TEMPERATURE, ®val); | ||||
| +	if (err < 0) | ||||
| +		return err; | ||||
| + | ||||
| +	*val = (regval * TEMPERATURE_LSB) - 20000; | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static int tps23861_read_voltage(struct tps23861_data *data, int channel, | ||||
| +				 long *val) | ||||
| +{ | ||||
| +	unsigned int regval; | ||||
| +	int err; | ||||
| + | ||||
| +	if (channel < TPS23861_NUM_PORTS) { | ||||
| +		err = regmap_bulk_read(data->regmap, | ||||
| +				       PORT_1_VOLTAGE_LSB + channel * PORT_N_VOLTAGE_LSB_OFFSET, | ||||
| +				       ®val, 2); | ||||
| +	} else { | ||||
| +		err = regmap_bulk_read(data->regmap, | ||||
| +				       INPUT_VOLTAGE_LSB, | ||||
| +				       ®val, 2); | ||||
| +	} | ||||
| +	if (err < 0) | ||||
| +		return err; | ||||
| + | ||||
| +	*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * VOLTAGE_LSB) / 1000; | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static int tps23861_read_current(struct tps23861_data *data, int channel, | ||||
| +				 long *val) | ||||
| +{ | ||||
| +	unsigned int current_lsb; | ||||
| +	unsigned int regval; | ||||
| +	int err; | ||||
| + | ||||
| +	if (data->shunt_resistor == SHUNT_RESISTOR_DEFAULT) | ||||
| +		current_lsb = CURRENT_LSB_255; | ||||
| +	else | ||||
| +		current_lsb = CURRENT_LSB_250; | ||||
| + | ||||
| +	err = regmap_bulk_read(data->regmap, | ||||
| +			       PORT_1_CURRENT_LSB + channel * PORT_N_CURRENT_LSB_OFFSET, | ||||
| +			       ®val, 2); | ||||
| +	if (err < 0) | ||||
| +		return err; | ||||
| + | ||||
| +	*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * current_lsb) / 1000000; | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static int tps23861_port_disable(struct tps23861_data *data, int channel) | ||||
| +{ | ||||
| +	unsigned int regval = 0; | ||||
| +	int err; | ||||
| + | ||||
| +	regval |= BIT(channel + 4); | ||||
| +	err = regmap_write(data->regmap, POWER_ENABLE, regval); | ||||
| + | ||||
| +	return err; | ||||
| +} | ||||
| + | ||||
| +static int tps23861_port_enable(struct tps23861_data *data, int channel) | ||||
| +{ | ||||
| +	unsigned int regval = 0; | ||||
| +	int err; | ||||
| + | ||||
| +	regval |= BIT(channel); | ||||
| +	regval |= BIT(channel + 4); | ||||
| +	err = regmap_write(data->regmap, DETECT_CLASS_RESTART, regval); | ||||
| + | ||||
| +	return err; | ||||
| +} | ||||
| + | ||||
| +static umode_t tps23861_is_visible(const void *data, enum hwmon_sensor_types type, | ||||
| +				   u32 attr, int channel) | ||||
| +{ | ||||
| +	switch (type) { | ||||
| +	case hwmon_temp: | ||||
| +		switch (attr) { | ||||
| +		case hwmon_temp_input: | ||||
| +		case hwmon_temp_label: | ||||
| +			return 0444; | ||||
| +		default: | ||||
| +			return 0; | ||||
| +		} | ||||
| +	case hwmon_in: | ||||
| +		switch (attr) { | ||||
| +		case hwmon_in_input: | ||||
| +		case hwmon_in_label: | ||||
| +			return 0444; | ||||
| +		case hwmon_in_enable: | ||||
| +			return 0200; | ||||
| +		default: | ||||
| +			return 0; | ||||
| +		} | ||||
| +	case hwmon_curr: | ||||
| +		switch (attr) { | ||||
| +		case hwmon_curr_input: | ||||
| +		case hwmon_curr_label: | ||||
| +			return 0444; | ||||
| +		default: | ||||
| +			return 0; | ||||
| +		} | ||||
| +	default: | ||||
| +		return 0; | ||||
| +	} | ||||
| +} | ||||
| + | ||||
| +static int tps23861_write(struct device *dev, enum hwmon_sensor_types type, | ||||
| +			  u32 attr, int channel, long val) | ||||
| +{ | ||||
| +	struct tps23861_data *data = dev_get_drvdata(dev); | ||||
| +	int err; | ||||
| + | ||||
| +	switch (type) { | ||||
| +	case hwmon_in: | ||||
| +		switch (attr) { | ||||
| +		case hwmon_in_enable: | ||||
| +			if (val == 0) | ||||
| +				err = tps23861_port_disable(data, channel); | ||||
| +			else if (val == 1) | ||||
| +				err = tps23861_port_enable(data, channel); | ||||
| +			else | ||||
| +				err = -EINVAL; | ||||
| +			break; | ||||
| +		default: | ||||
| +			return -EOPNOTSUPP; | ||||
| +		} | ||||
| +		break; | ||||
| +	default: | ||||
| +		return -EOPNOTSUPP; | ||||
| +	} | ||||
| + | ||||
| +	return err; | ||||
| +} | ||||
| + | ||||
| +static int tps23861_read(struct device *dev, enum hwmon_sensor_types type, | ||||
| +			 u32 attr, int channel, long *val) | ||||
| +{ | ||||
| +	struct tps23861_data *data = dev_get_drvdata(dev); | ||||
| +	int err; | ||||
| + | ||||
| +	switch (type) { | ||||
| +	case hwmon_temp: | ||||
| +		switch (attr) { | ||||
| +		case hwmon_temp_input: | ||||
| +			err = tps23861_read_temp(data, val); | ||||
| +			break; | ||||
| +		default: | ||||
| +			return -EOPNOTSUPP; | ||||
| +		} | ||||
| +		break; | ||||
| +	case hwmon_in: | ||||
| +		switch (attr) { | ||||
| +		case hwmon_in_input: | ||||
| +			err = tps23861_read_voltage(data, channel, val); | ||||
| +			break; | ||||
| +		default: | ||||
| +			return -EOPNOTSUPP; | ||||
| +		} | ||||
| +		break; | ||||
| +	case hwmon_curr: | ||||
| +		switch (attr) { | ||||
| +		case hwmon_curr_input: | ||||
| +			err = tps23861_read_current(data, channel, val); | ||||
| +			break; | ||||
| +		default: | ||||
| +			return -EOPNOTSUPP; | ||||
| +		} | ||||
| +		break; | ||||
| +	default: | ||||
| +		return -EOPNOTSUPP; | ||||
| +	} | ||||
| + | ||||
| +	return err; | ||||
| +} | ||||
| + | ||||
| +static const char * const tps23861_port_label[] = { | ||||
| +	"Port1", | ||||
| +	"Port2", | ||||
| +	"Port3", | ||||
| +	"Port4", | ||||
| +	"Input", | ||||
| +}; | ||||
| + | ||||
| +static int tps23861_read_string(struct device *dev, | ||||
| +				enum hwmon_sensor_types type, | ||||
| +				u32 attr, int channel, const char **str) | ||||
| +{ | ||||
| +	switch (type) { | ||||
| +	case hwmon_in: | ||||
| +	case hwmon_curr: | ||||
| +		*str = tps23861_port_label[channel]; | ||||
| +		break; | ||||
| +	case hwmon_temp: | ||||
| +		*str = "Die"; | ||||
| +		break; | ||||
| +	default: | ||||
| +		return -EOPNOTSUPP; | ||||
| +	} | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static const struct hwmon_channel_info *tps23861_info[] = { | ||||
| +	HWMON_CHANNEL_INFO(chip, | ||||
| +			   HWMON_C_REGISTER_TZ), | ||||
| +	HWMON_CHANNEL_INFO(temp, | ||||
| +			   HWMON_T_INPUT | HWMON_T_LABEL), | ||||
| +	HWMON_CHANNEL_INFO(in, | ||||
| +			   HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, | ||||
| +			   HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, | ||||
| +			   HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, | ||||
| +			   HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, | ||||
| +			   HWMON_I_INPUT | HWMON_I_LABEL), | ||||
| +	HWMON_CHANNEL_INFO(curr, | ||||
| +			   HWMON_C_INPUT | HWMON_C_LABEL, | ||||
| +			   HWMON_C_INPUT | HWMON_C_LABEL, | ||||
| +			   HWMON_C_INPUT | HWMON_C_LABEL, | ||||
| +			   HWMON_C_INPUT | HWMON_C_LABEL), | ||||
| +	NULL | ||||
| +}; | ||||
| + | ||||
| +static const struct hwmon_ops tps23861_hwmon_ops = { | ||||
| +	.is_visible = tps23861_is_visible, | ||||
| +	.write = tps23861_write, | ||||
| +	.read = tps23861_read, | ||||
| +	.read_string = tps23861_read_string, | ||||
| +}; | ||||
| + | ||||
| +static const struct hwmon_chip_info tps23861_chip_info = { | ||||
| +	.ops = &tps23861_hwmon_ops, | ||||
| +	.info = tps23861_info, | ||||
| +}; | ||||
| + | ||||
| +static char *tps23861_port_operating_mode(struct tps23861_data *data, int port) | ||||
| +{ | ||||
| +	unsigned int regval; | ||||
| +	int mode; | ||||
| + | ||||
| +	regmap_read(data->regmap, OPERATING_MODE, ®val); | ||||
| + | ||||
| +	switch (port) { | ||||
| +	case 1: | ||||
| +		mode = FIELD_GET(OPERATING_MODE_PORT_1_MASK, regval); | ||||
| +		break; | ||||
| +	case 2: | ||||
| +		mode = FIELD_GET(OPERATING_MODE_PORT_2_MASK, regval); | ||||
| +		break; | ||||
| +	case 3: | ||||
| +		mode = FIELD_GET(OPERATING_MODE_PORT_3_MASK, regval); | ||||
| +		break; | ||||
| +	case 4: | ||||
| +		mode = FIELD_GET(OPERATING_MODE_PORT_4_MASK, regval); | ||||
| +		break; | ||||
| +	default: | ||||
| +		mode = -EINVAL; | ||||
| +	} | ||||
| + | ||||
| +	switch (mode) { | ||||
| +	case OPERATING_MODE_OFF: | ||||
| +		return "Off"; | ||||
| +	case OPERATING_MODE_MANUAL: | ||||
| +		return "Manual"; | ||||
| +	case OPERATING_MODE_SEMI: | ||||
| +		return "Semi-Auto"; | ||||
| +	case OPERATING_MODE_AUTO: | ||||
| +		return "Auto"; | ||||
| +	default: | ||||
| +		return "Invalid"; | ||||
| +	} | ||||
| +} | ||||
| + | ||||
| +static char *tps23861_port_detect_status(struct tps23861_data *data, int port) | ||||
| +{ | ||||
| +	unsigned int regval; | ||||
| + | ||||
| +	regmap_read(data->regmap, | ||||
| +		    PORT_1_STATUS + (port - 1), | ||||
| +		    ®val); | ||||
| + | ||||
| +	switch (FIELD_GET(PORT_STATUS_DETECT_MASK, regval)) { | ||||
| +	case PORT_DETECT_UNKNOWN: | ||||
| +		return "Unknown device"; | ||||
| +	case PORT_DETECT_SHORT: | ||||
| +		return "Short circuit"; | ||||
| +	case PORT_DETECT_RESISTANCE_LOW: | ||||
| +		return "Too low resistance"; | ||||
| +	case PORT_DETECT_RESISTANCE_OK: | ||||
| +		return "Valid resistance"; | ||||
| +	case PORT_DETECT_RESISTANCE_HIGH: | ||||
| +		return "Too high resistance"; | ||||
| +	case PORT_DETECT_OPEN_CIRCUIT: | ||||
| +		return "Open circuit"; | ||||
| +	case PORT_DETECT_MOSFET_FAULT: | ||||
| +		return "MOSFET fault"; | ||||
| +	case PORT_DETECT_LEGACY: | ||||
| +		return "Legacy device"; | ||||
| +	case PORT_DETECT_CAPACITANCE_INVALID_BEYOND: | ||||
| +		return "Invalid capacitance, beyond clamp voltage"; | ||||
| +	case PORT_DETECT_CAPACITANCE_INVALID_DELTA: | ||||
| +		return "Invalid capacitance, insufficient voltage delta"; | ||||
| +	case PORT_DETECT_CAPACITANCE_OUT_OF_RANGE: | ||||
| +		return "Valid capacitance, outside of legacy range"; | ||||
| +	case PORT_DETECT_RESERVED: | ||||
| +	case PORT_DETECT_RESERVED_2: | ||||
| +	default: | ||||
| +		return "Invalid"; | ||||
| +	} | ||||
| +} | ||||
| + | ||||
| +static char *tps23861_port_class_status(struct tps23861_data *data, int port) | ||||
| +{ | ||||
| +	unsigned int regval; | ||||
| + | ||||
| +	regmap_read(data->regmap, | ||||
| +		    PORT_1_STATUS + (port - 1), | ||||
| +		    ®val); | ||||
| + | ||||
| +	switch (FIELD_GET(PORT_STATUS_CLASS_MASK, regval)) { | ||||
| +	case PORT_CLASS_UNKNOWN: | ||||
| +		return "Unknown"; | ||||
| +	case PORT_CLASS_RESERVED: | ||||
| +	case PORT_CLASS_0: | ||||
| +		return "0"; | ||||
| +	case PORT_CLASS_1: | ||||
| +		return "1"; | ||||
| +	case PORT_CLASS_2: | ||||
| +		return "2"; | ||||
| +	case PORT_CLASS_3: | ||||
| +		return "3"; | ||||
| +	case PORT_CLASS_4: | ||||
| +		return "4"; | ||||
| +	case PORT_CLASS_OVERCURRENT: | ||||
| +		return "Overcurrent"; | ||||
| +	case PORT_CLASS_MISMATCH: | ||||
| +		return "Mismatch"; | ||||
| +	default: | ||||
| +		return "Invalid"; | ||||
| +	} | ||||
| +} | ||||
| + | ||||
| +static char *tps23861_port_poe_plus_status(struct tps23861_data *data, int port) | ||||
| +{ | ||||
| +	unsigned int regval; | ||||
| + | ||||
| +	regmap_read(data->regmap, POE_PLUS, ®val); | ||||
| + | ||||
| +	if (BIT(port + 3) & regval) | ||||
| +		return "Yes"; | ||||
| +	else | ||||
| +		return "No"; | ||||
| +} | ||||
| + | ||||
| +static int tps23861_port_resistance(struct tps23861_data *data, int port) | ||||
| +{ | ||||
| +	u16 regval; | ||||
| + | ||||
| +	regmap_bulk_read(data->regmap, | ||||
| +			 PORT_1_RESISTANCE_LSB + PORT_N_RESISTANCE_LSB_OFFSET * (port - 1), | ||||
| +			 ®val, | ||||
| +			 2); | ||||
| + | ||||
| +	switch (FIELD_GET(PORT_RESISTANCE_RSN_MASK, regval)) { | ||||
| +	case PORT_RESISTANCE_RSN_OTHER: | ||||
| +		return (FIELD_GET(PORT_RESISTANCE_MASK, regval) * RESISTANCE_LSB) / 10000; | ||||
| +	case PORT_RESISTANCE_RSN_LOW: | ||||
| +		return (FIELD_GET(PORT_RESISTANCE_MASK, regval) * RESISTANCE_LSB_LOW) / 10000; | ||||
| +	case PORT_RESISTANCE_RSN_SHORT: | ||||
| +	case PORT_RESISTANCE_RSN_OPEN: | ||||
| +	default: | ||||
| +		return 0; | ||||
| +	} | ||||
| +} | ||||
| + | ||||
| +static int tps23861_port_status_show(struct seq_file *s, void *data) | ||||
| +{ | ||||
| +	struct tps23861_data *priv = s->private; | ||||
| +	int i; | ||||
| + | ||||
| +	for (i = 1; i < TPS23861_NUM_PORTS + 1; i++) { | ||||
| +		seq_printf(s, "Port: \t\t%d\n", i); | ||||
| +		seq_printf(s, "Operating mode: %s\n", tps23861_port_operating_mode(priv, i)); | ||||
| +		seq_printf(s, "Detected: \t%s\n", tps23861_port_detect_status(priv, i)); | ||||
| +		seq_printf(s, "Class: \t\t%s\n", tps23861_port_class_status(priv, i)); | ||||
| +		seq_printf(s, "PoE Plus: \t%s\n", tps23861_port_poe_plus_status(priv, i)); | ||||
| +		seq_printf(s, "Resistance: \t%d\n", tps23861_port_resistance(priv, i)); | ||||
| +		seq_putc(s, '\n'); | ||||
| +	} | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +DEFINE_SHOW_ATTRIBUTE(tps23861_port_status); | ||||
| + | ||||
| +static void tps23861_init_debugfs(struct tps23861_data *data) | ||||
| +{ | ||||
| +	data->debugfs_dir = debugfs_create_dir(data->client->name, NULL); | ||||
| + | ||||
| +	debugfs_create_file("port_status", | ||||
| +			    0400, | ||||
| +			    data->debugfs_dir, | ||||
| +			    data, | ||||
| +			    &tps23861_port_status_fops); | ||||
| +} | ||||
| + | ||||
| +static int tps23861_probe(struct i2c_client *client) | ||||
| +{ | ||||
| +	struct device *dev = &client->dev; | ||||
| +	struct tps23861_data *data; | ||||
| +	struct device *hwmon_dev; | ||||
| +	u32 shunt_resistor; | ||||
| + | ||||
| +	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | ||||
| +	if (!data) | ||||
| +		return -ENOMEM; | ||||
| + | ||||
| +	data->client = client; | ||||
| +	i2c_set_clientdata(client, data); | ||||
| + | ||||
| +	data->regmap = devm_regmap_init_i2c(client, &tps23861_regmap_config); | ||||
| +	if (IS_ERR(data->regmap)) { | ||||
| +		dev_err(dev, "failed to allocate register map\n"); | ||||
| +		return PTR_ERR(data->regmap); | ||||
| +	} | ||||
| + | ||||
| +	if (!of_property_read_u32(dev->of_node, "shunt-resistor-micro-ohms", &shunt_resistor)) | ||||
| +		data->shunt_resistor = shunt_resistor; | ||||
| +	else | ||||
| +		data->shunt_resistor = SHUNT_RESISTOR_DEFAULT; | ||||
| + | ||||
| +	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, | ||||
| +							 data, &tps23861_chip_info, | ||||
| +							 NULL); | ||||
| +	if (IS_ERR(hwmon_dev)) | ||||
| +		return PTR_ERR(hwmon_dev); | ||||
| + | ||||
| +	tps23861_init_debugfs(data); | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static int tps23861_remove(struct i2c_client *client) | ||||
| +{ | ||||
| +	struct tps23861_data *data = i2c_get_clientdata(client); | ||||
| + | ||||
| +	debugfs_remove_recursive(data->debugfs_dir); | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static const struct of_device_id __maybe_unused tps23861_of_match[] = { | ||||
| +	{ .compatible = "ti,tps23861", }, | ||||
| +	{ }, | ||||
| +}; | ||||
| +MODULE_DEVICE_TABLE(of, tps23861_of_match); | ||||
| + | ||||
| +static struct i2c_driver tps23861_driver = { | ||||
| +	.probe_new		= tps23861_probe, | ||||
| +	.remove			= tps23861_remove, | ||||
| +	.driver = { | ||||
| +		.name		= "tps23861", | ||||
| +		.of_match_table	= of_match_ptr(tps23861_of_match), | ||||
| +	}, | ||||
| +}; | ||||
| +module_i2c_driver(tps23861_driver); | ||||
| + | ||||
| +MODULE_LICENSE("GPL"); | ||||
| +MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>"); | ||||
| +MODULE_DESCRIPTION("TI TPS23861 PoE PSE"); | ||||
| @@ -0,0 +1,29 @@ | ||||
| From 3d61a7b3a714eb3ef1777e3c576576aca2b85365 Mon Sep 17 00:00:00 2001 | ||||
| From: Robert Marko <robert.marko@sartura.hr> | ||||
| Date: Thu, 10 Jun 2021 00:07:26 +0200 | ||||
| Subject: [PATCH 2/4] hwmon: (tps23861) define regmap max register | ||||
|  | ||||
| Define the max register address the device supports. | ||||
| This allows reading the whole register space via | ||||
| regmap debugfs, without it only register 0x0 is visible. | ||||
|  | ||||
| This was forgotten in the original driver commit. | ||||
|  | ||||
| Fixes: fff7b8ab2255 ("hwmon: add Texas Instruments TPS23861 driver") | ||||
| Signed-off-by: Robert Marko <robert.marko@sartura.hr> | ||||
| Link: https://lore.kernel.org/r/20210609220728.499879-1-robert.marko@sartura.hr | ||||
| Signed-off-by: Guenter Roeck <linux@roeck-us.net> | ||||
| --- | ||||
|  drivers/hwmon/tps23861.c | 1 + | ||||
|  1 file changed, 1 insertion(+) | ||||
|  | ||||
| --- a/drivers/hwmon/tps23861.c | ||||
| +++ b/drivers/hwmon/tps23861.c | ||||
| @@ -117,6 +117,7 @@ struct tps23861_data { | ||||
|  static struct regmap_config tps23861_regmap_config = { | ||||
|  	.reg_bits = 8, | ||||
|  	.val_bits = 8, | ||||
| +	.max_register = 0x6f, | ||||
|  }; | ||||
|   | ||||
|  static int tps23861_read_temp(struct tps23861_data *data, long *val) | ||||
| @@ -0,0 +1,57 @@ | ||||
| From 9bca598d4a86e88afb29fdb516c68b2519bd0fb9 Mon Sep 17 00:00:00 2001 | ||||
| From: Robert Marko <robert.marko@sartura.hr> | ||||
| Date: Thu, 10 Jun 2021 00:07:27 +0200 | ||||
| Subject: [PATCH 3/4] hwmon: (tps23861) set current shunt value | ||||
|  | ||||
| TPS23861 has a configuration bit for setting of the | ||||
| current shunt value used on the board. | ||||
| Its bit 0 of the General Mask 1 register. | ||||
|  | ||||
| According to the datasheet bit values are: | ||||
| 0 for 255 mOhm (Default) | ||||
| 1 for 250 mOhm | ||||
|  | ||||
| So, configure the bit before registering the hwmon | ||||
| device according to the value passed in the DTS or | ||||
| default one if none is passed. | ||||
|  | ||||
| This caused potentially reading slightly skewed values | ||||
| due to max current value being 1.02A when 250mOhm shunt | ||||
| is used instead of 1.0A when 255mOhm is used. | ||||
|  | ||||
| Fixes: fff7b8ab2255 ("hwmon: add Texas Instruments TPS23861 driver") | ||||
| Signed-off-by: Robert Marko <robert.marko@sartura.hr> | ||||
| Link: https://lore.kernel.org/r/20210609220728.499879-2-robert.marko@sartura.hr | ||||
| Signed-off-by: Guenter Roeck <linux@roeck-us.net> | ||||
| --- | ||||
|  drivers/hwmon/tps23861.c | 12 ++++++++++++ | ||||
|  1 file changed, 12 insertions(+) | ||||
|  | ||||
| --- a/drivers/hwmon/tps23861.c | ||||
| +++ b/drivers/hwmon/tps23861.c | ||||
| @@ -99,6 +99,9 @@ | ||||
|  #define POWER_ENABLE			0x19 | ||||
|  #define TPS23861_NUM_PORTS		4 | ||||
|   | ||||
| +#define TPS23861_GENERAL_MASK_1		0x17 | ||||
| +#define TPS23861_CURRENT_SHUNT_MASK	BIT(0) | ||||
| + | ||||
|  #define TEMPERATURE_LSB			652 /* 0.652 degrees Celsius */ | ||||
|  #define VOLTAGE_LSB			3662 /* 3.662 mV */ | ||||
|  #define SHUNT_RESISTOR_DEFAULT		255000 /* 255 mOhm */ | ||||
| @@ -561,6 +564,15 @@ static int tps23861_probe(struct i2c_cli | ||||
|  	else | ||||
|  		data->shunt_resistor = SHUNT_RESISTOR_DEFAULT; | ||||
|   | ||||
| +	if (data->shunt_resistor == SHUNT_RESISTOR_DEFAULT) | ||||
| +		regmap_clear_bits(data->regmap, | ||||
| +				  TPS23861_GENERAL_MASK_1, | ||||
| +				  TPS23861_CURRENT_SHUNT_MASK); | ||||
| +	else | ||||
| +		regmap_set_bits(data->regmap, | ||||
| +				TPS23861_GENERAL_MASK_1, | ||||
| +				TPS23861_CURRENT_SHUNT_MASK); | ||||
| + | ||||
|  	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, | ||||
|  							 data, &tps23861_chip_info, | ||||
|  							 NULL); | ||||
| @@ -0,0 +1,34 @@ | ||||
| From b447e689a26614ce08a431e8000e8a650a63dcb3 Mon Sep 17 00:00:00 2001 | ||||
| From: Robert Marko <robert.marko@sartura.hr> | ||||
| Date: Thu, 10 Jun 2021 00:07:28 +0200 | ||||
| Subject: [PATCH 4/4] hwmon: (tps23861) correct shunt LSB values | ||||
|  | ||||
| Current shunt LSB values got reversed during in the | ||||
| original driver commit. | ||||
|  | ||||
| So, correct the current shunt LSB values according to | ||||
| the datasheet. | ||||
|  | ||||
| This caused reading slightly skewed current values. | ||||
|  | ||||
| Fixes: fff7b8ab2255 ("hwmon: add Texas Instruments TPS23861 driver") | ||||
| Signed-off-by: Robert Marko <robert.marko@sartura.hr> | ||||
| Link: https://lore.kernel.org/r/20210609220728.499879-3-robert.marko@sartura.hr | ||||
| Signed-off-by: Guenter Roeck <linux@roeck-us.net> | ||||
| --- | ||||
|  drivers/hwmon/tps23861.c | 4 ++-- | ||||
|  1 file changed, 2 insertions(+), 2 deletions(-) | ||||
|  | ||||
| --- a/drivers/hwmon/tps23861.c | ||||
| +++ b/drivers/hwmon/tps23861.c | ||||
| @@ -105,8 +105,8 @@ | ||||
|  #define TEMPERATURE_LSB			652 /* 0.652 degrees Celsius */ | ||||
|  #define VOLTAGE_LSB			3662 /* 3.662 mV */ | ||||
|  #define SHUNT_RESISTOR_DEFAULT		255000 /* 255 mOhm */ | ||||
| -#define CURRENT_LSB_255			62260 /* 62.260 uA */ | ||||
| -#define CURRENT_LSB_250			61039 /* 61.039 uA */ | ||||
| +#define CURRENT_LSB_250			62260 /* 62.260 uA */ | ||||
| +#define CURRENT_LSB_255			61039 /* 61.039 uA */ | ||||
|  #define RESISTANCE_LSB			110966 /* 11.0966 Ohm*/ | ||||
|  #define RESISTANCE_LSB_LOW		157216 /* 15.7216 Ohm*/ | ||||
|   | ||||
| @@ -0,0 +1,66 @@ | ||||
| From 0eabb1396656f215a5333a9444158b17b0fd3247 Mon Sep 17 00:00:00 2001 | ||||
| From: Alexandru Gagniuc <mr.nuke.me@gmail.com> | ||||
| Date: Wed, 20 Jul 2022 22:22:55 -0500 | ||||
| Subject: hwmon: (tps23861) fix byte order in current and voltage registers | ||||
|  | ||||
| Trying to use this driver on a big-endian machine results in garbage | ||||
| values for voltage and current. The tps23861 registers are little- | ||||
| endian, and regmap_read_bulk() does not do byte order conversion. Thus | ||||
| on BE machines, the most significant bytes got modified, and were | ||||
| trimmed by the VOLTAGE_CURRENT_MASK. | ||||
|  | ||||
| To resolve this use uint16_t values, and convert them to host byte | ||||
| order using le16_to_cpu(). This results in correct readings on MIPS. | ||||
|  | ||||
| Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com> | ||||
| Link: https://lore.kernel.org/r/20220721032255.2850647-1-mr.nuke.me@gmail.com | ||||
| [groeck: Use __le16 instead of uint16_t] | ||||
| Signed-off-by: Guenter Roeck <linux@roeck-us.net> | ||||
| --- | ||||
|  drivers/hwmon/tps23861.c | 14 +++++++++----- | ||||
|  1 file changed, 9 insertions(+), 5 deletions(-) | ||||
|  | ||||
| --- a/drivers/hwmon/tps23861.c | ||||
| +++ b/drivers/hwmon/tps23861.c | ||||
| @@ -140,7 +140,8 @@ static int tps23861_read_temp(struct tps | ||||
|  static int tps23861_read_voltage(struct tps23861_data *data, int channel, | ||||
|  				 long *val) | ||||
|  { | ||||
| -	unsigned int regval; | ||||
| +	__le16 regval; | ||||
| +	long raw_val; | ||||
|  	int err; | ||||
|   | ||||
|  	if (channel < TPS23861_NUM_PORTS) { | ||||
| @@ -155,7 +156,8 @@ static int tps23861_read_voltage(struct | ||||
|  	if (err < 0) | ||||
|  		return err; | ||||
|   | ||||
| -	*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * VOLTAGE_LSB) / 1000; | ||||
| +	raw_val = le16_to_cpu(regval); | ||||
| +	*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, raw_val) * VOLTAGE_LSB) / 1000; | ||||
|   | ||||
|  	return 0; | ||||
|  } | ||||
| @@ -163,8 +165,9 @@ static int tps23861_read_voltage(struct | ||||
|  static int tps23861_read_current(struct tps23861_data *data, int channel, | ||||
|  				 long *val) | ||||
|  { | ||||
| -	unsigned int current_lsb; | ||||
| -	unsigned int regval; | ||||
| +	long raw_val, current_lsb; | ||||
| +	__le16 regval; | ||||
| + | ||||
|  	int err; | ||||
|   | ||||
|  	if (data->shunt_resistor == SHUNT_RESISTOR_DEFAULT) | ||||
| @@ -178,7 +181,8 @@ static int tps23861_read_current(struct | ||||
|  	if (err < 0) | ||||
|  		return err; | ||||
|   | ||||
| -	*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * current_lsb) / 1000000; | ||||
| +	raw_val = le16_to_cpu(regval); | ||||
| +	*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, raw_val) * current_lsb) / 1000000; | ||||
|   | ||||
|  	return 0; | ||||
|  } | ||||
| @@ -5304,6 +5304,7 @@ CONFIG_SELECT_MEMORY_MODEL=y | ||||
| # CONFIG_SENSORS_TMP401 is not set | ||||
| # CONFIG_SENSORS_TMP421 is not set | ||||
| # CONFIG_SENSORS_TMP513 is not set | ||||
| # CONFIG_SENSORS_TPS23861 is not set | ||||
| # CONFIG_SENSORS_TPS40422 is not set | ||||
| # CONFIG_SENSORS_TPS53679 is not set | ||||
| # CONFIG_SENSORS_TSL2550 is not set | ||||
|   | ||||
		Reference in New Issue
	
	Block a user