| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  * SolidRun DPU driver for control plane | 
 |  * | 
 |  * Copyright (C) 2022-2023 SolidRun | 
 |  * | 
 |  * Author: Alvaro Karsz <alvaro.karsz@solid-run.com> | 
 |  * | 
 |  */ | 
 | #include <linux/hwmon.h> | 
 |  | 
 | #include "snet_vdpa.h" | 
 |  | 
 | /* Monitor offsets */ | 
 | #define SNET_MON_TMP0_IN_OFF      0x00 | 
 | #define SNET_MON_TMP0_MAX_OFF     0x08 | 
 | #define SNET_MON_TMP0_CRIT_OFF    0x10 | 
 | #define SNET_MON_TMP1_IN_OFF      0x18 | 
 | #define SNET_MON_TMP1_CRIT_OFF    0x20 | 
 | #define SNET_MON_CURR_IN_OFF      0x28 | 
 | #define SNET_MON_CURR_MAX_OFF     0x30 | 
 | #define SNET_MON_CURR_CRIT_OFF    0x38 | 
 | #define SNET_MON_PWR_IN_OFF       0x40 | 
 | #define SNET_MON_VOLT_IN_OFF      0x48 | 
 | #define SNET_MON_VOLT_CRIT_OFF    0x50 | 
 | #define SNET_MON_VOLT_LCRIT_OFF   0x58 | 
 |  | 
 | static void snet_hwmon_read_reg(struct psnet *psnet, u32 reg, long *out) | 
 | { | 
 | 	*out = psnet_read64(psnet, psnet->cfg.hwmon_off + reg); | 
 | } | 
 |  | 
 | static umode_t snet_howmon_is_visible(const void *data, | 
 | 				      enum hwmon_sensor_types type, | 
 | 				      u32 attr, int channel) | 
 | { | 
 | 	return 0444; | 
 | } | 
 |  | 
 | static int snet_howmon_read(struct device *dev, enum hwmon_sensor_types type, | 
 | 			    u32 attr, int channel, long *val) | 
 | { | 
 | 	struct psnet *psnet = dev_get_drvdata(dev); | 
 | 	int ret = 0; | 
 |  | 
 | 	switch (type) { | 
 | 	case hwmon_in: | 
 | 		switch (attr) { | 
 | 		case hwmon_in_lcrit: | 
 | 			snet_hwmon_read_reg(psnet, SNET_MON_VOLT_LCRIT_OFF, val); | 
 | 			break; | 
 | 		case hwmon_in_crit: | 
 | 			snet_hwmon_read_reg(psnet, SNET_MON_VOLT_CRIT_OFF, val); | 
 | 			break; | 
 | 		case hwmon_in_input: | 
 | 			snet_hwmon_read_reg(psnet, SNET_MON_VOLT_IN_OFF, val); | 
 | 			break; | 
 | 		default: | 
 | 			ret = -EOPNOTSUPP; | 
 | 			break; | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case hwmon_power: | 
 | 		switch (attr) { | 
 | 		case hwmon_power_input: | 
 | 			snet_hwmon_read_reg(psnet, SNET_MON_PWR_IN_OFF, val); | 
 | 			break; | 
 |  | 
 | 		default: | 
 | 			ret = -EOPNOTSUPP; | 
 | 			break; | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case hwmon_curr: | 
 | 		switch (attr) { | 
 | 		case hwmon_curr_input: | 
 | 			snet_hwmon_read_reg(psnet, SNET_MON_CURR_IN_OFF, val); | 
 | 			break; | 
 | 		case hwmon_curr_max: | 
 | 			snet_hwmon_read_reg(psnet, SNET_MON_CURR_MAX_OFF, val); | 
 | 			break; | 
 | 		case hwmon_curr_crit: | 
 | 			snet_hwmon_read_reg(psnet, SNET_MON_CURR_CRIT_OFF, val); | 
 | 			break; | 
 | 		default: | 
 | 			ret = -EOPNOTSUPP; | 
 | 			break; | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case hwmon_temp: | 
 | 		switch (attr) { | 
 | 		case hwmon_temp_input: | 
 | 			if (channel == 0) | 
 | 				snet_hwmon_read_reg(psnet, SNET_MON_TMP0_IN_OFF, val); | 
 | 			else | 
 | 				snet_hwmon_read_reg(psnet, SNET_MON_TMP1_IN_OFF, val); | 
 | 			break; | 
 | 		case hwmon_temp_max: | 
 | 			if (channel == 0) | 
 | 				snet_hwmon_read_reg(psnet, SNET_MON_TMP0_MAX_OFF, val); | 
 | 			else | 
 | 				ret = -EOPNOTSUPP; | 
 | 			break; | 
 | 		case hwmon_temp_crit: | 
 | 			if (channel == 0) | 
 | 				snet_hwmon_read_reg(psnet, SNET_MON_TMP0_CRIT_OFF, val); | 
 | 			else | 
 | 				snet_hwmon_read_reg(psnet, SNET_MON_TMP1_CRIT_OFF, val); | 
 | 			break; | 
 |  | 
 | 		default: | 
 | 			ret = -EOPNOTSUPP; | 
 | 			break; | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		ret = -EOPNOTSUPP; | 
 | 		break; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int snet_hwmon_read_string(struct device *dev, | 
 | 				  enum hwmon_sensor_types type, u32 attr, | 
 | 				  int channel, const char **str) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	switch (type) { | 
 | 	case hwmon_in: | 
 | 		*str = "main_vin"; | 
 | 		break; | 
 | 	case hwmon_power: | 
 | 		*str = "soc_pin"; | 
 | 		break; | 
 | 	case hwmon_curr: | 
 | 		*str = "soc_iin"; | 
 | 		break; | 
 | 	case hwmon_temp: | 
 | 		if (channel == 0) | 
 | 			*str = "power_stage_temp"; | 
 | 		else | 
 | 			*str = "ic_junction_temp"; | 
 | 		break; | 
 | 	default: | 
 | 		ret = -EOPNOTSUPP; | 
 | 		break; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static const struct hwmon_ops snet_hwmon_ops = { | 
 | 	.is_visible = snet_howmon_is_visible, | 
 | 	.read = snet_howmon_read, | 
 | 	.read_string = snet_hwmon_read_string | 
 | }; | 
 |  | 
 | static const struct hwmon_channel_info * const snet_hwmon_info[] = { | 
 | 	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL, | 
 | 			   HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL), | 
 | 	HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL), | 
 | 	HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_CRIT | HWMON_C_LABEL), | 
 | 	HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_LCRIT | HWMON_I_LABEL), | 
 | 			   NULL | 
 | }; | 
 |  | 
 | static const struct hwmon_chip_info snet_hwmono_info = { | 
 | 	.ops = &snet_hwmon_ops, | 
 | 	.info = snet_hwmon_info, | 
 | }; | 
 |  | 
 | /* Create an HW monitor device */ | 
 | void psnet_create_hwmon(struct pci_dev *pdev) | 
 | { | 
 | 	struct device *hwmon; | 
 | 	struct psnet *psnet = pci_get_drvdata(pdev); | 
 |  | 
 | 	snprintf(psnet->hwmon_name, SNET_NAME_SIZE, "snet_%s", pci_name(pdev)); | 
 | 	hwmon = devm_hwmon_device_register_with_info(&pdev->dev, psnet->hwmon_name, psnet, | 
 | 						     &snet_hwmono_info, NULL); | 
 | 	/* The monitor is not mandatory, Just alert user in case of an error */ | 
 | 	if (IS_ERR(hwmon)) | 
 | 		SNET_WARN(pdev, "Failed to create SNET hwmon, error %ld\n", PTR_ERR(hwmon)); | 
 | } |