[SRU][G/linux-oracle][PATCH 07/18] UBUNTU: SAUCE: hwmon: Add Ampere Altra HW monitor driver

Khalid Elmously khalid.elmously at canonical.com
Fri May 21 07:00:31 UTC 2021


From: lho <loc.ho at amperecomputing.com>

BugLink: https://bugs.launchpad.net/bugs/1925421

Add Ampere Altra HW monitor driver to support per core energy
and SoC temperature reporting.

Signed-off-by: lho <loc.ho at os.amperecomputing.com>
Signed-off-by: lho <hoan at os.amperecomputing.com>
(backported from commit f2e6dee522cc4be9dd094c30dc88273f44d5ffe2 https://github.com/AmpereComputing/ampere-centos-kernel)
[ kmously: context adjustment in drivers/hwmon/Makefile ]
Signed-off-by: Khalid Elmously <khalid.elmously at canonical.com>
---
 drivers/hwmon/Kconfig       |  10 +
 drivers/hwmon/Makefile      |   1 +
 drivers/hwmon/altra-hwmon.c | 435 ++++++++++++++++++++++++++++++++++++
 3 files changed, 446 insertions(+)
 create mode 100644 drivers/hwmon/altra-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 288ae9f63588..e94d68d1dcc3 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -334,6 +334,16 @@ config SENSORS_AMD_ENERGY
 	  This driver can also be built as a module. If so, the module
 	  will be called as amd_energy.
 
+config SENSORS_ALTRA
+	tristate "Altra sensors driver"
+	depends on ARM64
+	help
+	  If you say yes here you get support for Ampere SoC core and package
+	  sensors for Ampere Altra CPUs.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called as altra-hwmon.
+
 config SENSORS_APPLESMC
 	tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
 	depends on INPUT && X86
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 3e32c21f5efe..f4b8367cde40 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SENSORS_ADT7462)	+= adt7462.o
 obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470.o
 obj-$(CONFIG_SENSORS_ADT7475)	+= adt7475.o
 obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
+obj-$(CONFIG_SENSORS_ALTRA)	+= altra-hwmon.o
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_ARM_SCMI)	+= scmi-hwmon.o
 obj-$(CONFIG_SENSORS_ARM_SCPI)	+= scpi-hwmon.o
diff --git a/drivers/hwmon/altra-hwmon.c b/drivers/hwmon/altra-hwmon.c
new file mode 100644
index 000000000000..12460edd5f18
--- /dev/null
+++ b/drivers/hwmon/altra-hwmon.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Ampere Altra SoC Hardware Monitoring Driver
+ *
+ * Copyright (C) 2020 Ampere Computing LLC
+ * Author: Loc Ho <loc.ho at os.amperecompting.com>
+ *         Hoan Tran <hoan at os.amperecomputing.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#define DRVNAME				"altra_hwmon"
+#define ALTRA_HWMON_VER1		1
+#define ALTRA_HWMON_VER2		2
+
+#define HW_SUPPORTED_VER		1
+
+#define UNIT_DEGREE_CELSIUS		0x0001
+#define UNIT_JOULE			0x0010
+#define UNIT_MILLI_JOULE		0x0011
+#define UNIT_MICRO_JOULE		0x0012
+
+#define HW_METRIC_LABEL_REG		0x0000
+#define HW_METRIC_LABEL_SIZE		16
+#define HW_METRIC_INFO_REG		0x0010
+#define HW_METRIC_INFO_UNIT_RD(x)	((x) & 0xFF)
+#define HW_METRIC_INFO_DATASIZE_RD(x)	(((x) >> 8) & 0xFF)
+#define HW_METRIC_INFO_DATACNT_RD(x)	(((x) >> 16) & 0xFFFF)
+#define HW_METRIC_DATA_REG		0x0018
+#define HW_METRIC_HDRSIZE		24
+
+#define HW_METRICS_ID_REG		0x0000
+#define HW_METRICS_ID			0x304D5748 /* HWM0 */
+#define HW_METRICS_INFO_REG		0x0004
+#define HW_METRICS_INFO_VER_RD(x)	((x) & 0xFFFF)
+#define HW_METRICS_INFO_CNT_RD(x)	(((x) >> 16) & 0xFFFF)
+#define HW_METRICS_DATA_REG		0x0008
+#define HW_METRICS_HDRSIZE		8
+
+#define SENSOR_ITEM_LABEL_SIZE		(HW_METRIC_LABEL_SIZE + 3 + 1)
+
+struct sensor_item {
+	char label[SENSOR_ITEM_LABEL_SIZE]; /* NULL terminator label */
+	u32 scale_factor; /* Convert HW unit to HWmon unnt */
+	u8 data_size;	  /* 4 or 8 bytes */
+	u32 hw_reg;	  /* Registor offset to data */
+};
+
+struct altra_hwmon_context {
+	struct hwmon_channel_info *channel_info;
+	const struct hwmon_channel_info **info;
+	struct hwmon_chip_info chip;
+	struct sensor_item *sensor_list[hwmon_max];
+	u32 sensor_list_cnt[hwmon_max];
+	struct device *dev;
+	struct device *hwmon_dev;
+	void __iomem *base;
+	u32 base_size;
+};
+
+static u32 altra_hwmon_read32(struct altra_hwmon_context *ctx, u32 reg)
+{
+	return readl_relaxed(ctx->base + reg);
+}
+
+static u64 altra_hwmon_read64(struct altra_hwmon_context *ctx, u32 reg)
+{
+	return readq_relaxed(ctx->base + reg);
+}
+
+static int altra_hwmon_read_labels(struct device *dev,
+				   enum hwmon_sensor_types type, u32 attr,
+				   int channel, const char **str)
+{
+	struct altra_hwmon_context *ctx = dev_get_drvdata(dev);
+	struct sensor_item *item;
+
+	if (type >= hwmon_max)
+		return -EINVAL;
+	if (channel >= ctx->sensor_list_cnt[type])
+		return -EINVAL;
+
+	item = ctx->sensor_list[type];
+	*str = item[channel].label;
+	return 0;
+}
+
+static int altra_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+			    u32 attr, int channel, long *val)
+{
+	struct altra_hwmon_context *ctx = dev_get_drvdata(dev);
+	struct sensor_item *item;
+
+	if (type >= hwmon_max)
+		return -EINVAL;
+	if (channel >= ctx->sensor_list_cnt[type])
+		return -EINVAL;
+
+	item = ctx->sensor_list[type];
+	if (item[channel].data_size == 4)
+		*val = altra_hwmon_read32(ctx, item[channel].hw_reg);
+	else
+		*val = altra_hwmon_read64(ctx, item[channel].hw_reg);
+	*val *= item[channel].scale_factor;
+
+	return 0;
+}
+
+static umode_t altra_hwmon_is_visible(const void *_data,
+				      enum hwmon_sensor_types type,
+				      u32 attr, int channel)
+{
+	return 0444;
+}
+
+static const struct hwmon_ops altra_hwmon_ops = {
+	.is_visible = altra_hwmon_is_visible,
+	.read = altra_hwmon_read,
+	.read_string = altra_hwmon_read_labels,
+};
+
+static enum hwmon_sensor_types altra_hwmon_unit2type(u8 unit)
+{
+	switch (unit) {
+	case UNIT_DEGREE_CELSIUS:
+		return hwmon_temp;
+	case UNIT_JOULE:
+	case UNIT_MILLI_JOULE:
+	case UNIT_MICRO_JOULE:
+		return hwmon_energy;
+	}
+	return hwmon_max;
+}
+
+static u32 altra_hwmon_scale_factor(u8 unit)
+{
+	switch (unit) {
+	case UNIT_DEGREE_CELSIUS:
+		return 1000;
+	case UNIT_JOULE:
+		return 1000000;
+	case UNIT_MILLI_JOULE:
+		return 1000;
+	case UNIT_MICRO_JOULE:
+		return 1;
+	}
+	return 1;
+}
+
+static u32 altra_hwmon_type2flag(enum hwmon_sensor_types type)
+{
+	switch (type) {
+	case hwmon_energy:
+		return HWMON_E_INPUT | HWMON_E_LABEL;
+	case hwmon_temp:
+		return HWMON_T_LABEL | HWMON_T_INPUT;
+	default:
+		return 0;
+	}
+}
+
+static int altra_sensor_is_valid(struct altra_hwmon_context *ctx, u32 reg, u32 data_size)
+{
+	int val;
+
+	if (data_size == 4)
+		val = altra_hwmon_read32(ctx, reg);
+	else
+		val = altra_hwmon_read64(ctx, reg);
+
+	return val ? 1 : 0;
+}
+
+static int altra_create_sensor(struct altra_hwmon_context *ctx,
+			       u32 metric_info,
+			       struct hwmon_channel_info *info)
+{
+	enum hwmon_sensor_types type;
+	char label[SENSOR_ITEM_LABEL_SIZE];
+	struct sensor_item *item_list;
+	struct sensor_item *item;
+	int data_size;
+	u32 *s_config;
+	u32 hw_info;
+	u32 total;
+	int i, j;
+
+	/* Check for supported type */
+	hw_info = altra_hwmon_read32(ctx, metric_info + HW_METRIC_INFO_REG);
+	type = altra_hwmon_unit2type(HW_METRIC_INFO_UNIT_RD(hw_info));
+	if (type == hwmon_max) {
+		dev_err(ctx->dev,
+			"malform info header @ 0x%x value 0x%x. Ignore remaining\n",
+			metric_info + HW_METRIC_INFO_REG, hw_info);
+		return -ENODEV;
+	}
+
+	/* Label */
+	for (i = 0; i < HW_METRIC_LABEL_SIZE; i += 4)
+		*(u32 *)&label[i] = altra_hwmon_read32(ctx,
+					metric_info + HW_METRIC_LABEL_REG + i);
+	label[sizeof(label) - 1] = '\0';
+	if (strlen(label) <= 0) {
+		dev_err(ctx->dev,
+			"malform label header 0x%x. Ignore remaining\n",
+			metric_info + HW_METRIC_LABEL_REG);
+		return -ENODEV;
+	}
+
+	total = HW_METRIC_INFO_DATACNT_RD(hw_info);
+	data_size = HW_METRIC_INFO_DATASIZE_RD(hw_info);
+	/* Get the total valid sensors */
+	j = 0;
+	for (i = 0; i < total; i++) {
+		if (altra_sensor_is_valid(ctx, metric_info + HW_METRIC_DATA_REG +
+					  i * data_size, data_size))
+			j++;
+	}
+	total = j;
+
+	if (!ctx->sensor_list[type]) {
+		ctx->sensor_list[type] = devm_kzalloc(ctx->dev,
+						      sizeof(struct sensor_item) * total,
+						      GFP_KERNEL);
+	} else {
+		item_list = devm_kzalloc(ctx->dev,
+					 sizeof(*item) * (ctx->sensor_list_cnt[type] + total),
+					 GFP_KERNEL);
+		if (!item_list)
+			return -ENOMEM;
+		memcpy(item_list, ctx->sensor_list[type],
+		       sizeof(*item) * ctx->sensor_list_cnt[type]);
+		devm_kfree(ctx->dev, ctx->sensor_list[type]);
+		ctx->sensor_list[type] = item_list;
+	}
+
+	s_config = devm_kcalloc(ctx->dev, total, sizeof(u32), GFP_KERNEL);
+	if (!s_config)
+		return -ENOMEM;
+	info->type = type;
+	info->config = s_config;
+
+	/* Set up sensor entry */
+	item_list = ctx->sensor_list[type];
+	j = 0;
+	for (i = 0; i < HW_METRIC_INFO_DATACNT_RD(hw_info); i++) {
+		/* Check if sensor is valid */
+		if (!altra_sensor_is_valid(ctx, metric_info + HW_METRIC_DATA_REG +
+					   i * data_size, data_size))
+			continue;
+
+		item = &item_list[ctx->sensor_list_cnt[type]];
+		item->hw_reg = metric_info + HW_METRIC_DATA_REG + i * data_size;
+		scnprintf(item->label, SENSOR_ITEM_LABEL_SIZE, "%s %03u", label, j);
+		item->scale_factor = altra_hwmon_scale_factor(HW_METRIC_INFO_UNIT_RD(hw_info));
+		item->data_size = data_size;
+		s_config[j] = altra_hwmon_type2flag(type);
+		ctx->sensor_list_cnt[type]++;
+		j++;
+	}
+
+	return 0;
+}
+
+static int altra_hwmon_create_sensors(struct altra_hwmon_context *ctx)
+{
+	u32 metrics_info;
+	u32 total_metric;
+	u32 hw_reg;
+	u32 hw_end_reg;
+	int ret;
+	u32 val;
+	int i;
+	int used;
+
+	if (altra_hwmon_read32(ctx, HW_METRICS_ID_REG) != HW_METRICS_ID)
+		return -ENODEV;
+
+	metrics_info = altra_hwmon_read32(ctx, HW_METRICS_INFO_REG);
+	if (HW_METRICS_INFO_VER_RD(metrics_info) != HW_SUPPORTED_VER)
+		return -ENODEV;
+
+	total_metric = HW_METRICS_INFO_CNT_RD(metrics_info);
+	ctx->channel_info = devm_kzalloc(ctx->dev,
+					 sizeof(struct hwmon_channel_info) * total_metric,
+					 GFP_KERNEL);
+	if (!ctx->channel_info)
+		return -ENOMEM;
+	ctx->info = devm_kzalloc(ctx->dev,
+				 sizeof(struct hwmon_channel_info *) * (total_metric + 1),
+				 GFP_KERNEL);
+	if (!ctx->info)
+		return -ENOMEM;
+
+	hw_reg = HW_METRICS_HDRSIZE;
+	for (used = 0, i = 0; i < total_metric; i++) {
+		/* Check for out of bound */
+		if ((hw_reg + HW_METRIC_HDRSIZE) > ctx->base_size) {
+			dev_err(ctx->dev,
+				"malform metric header 0x%x (exceeded range). Ignore remaining\n",
+				hw_reg);
+			break;
+		}
+
+		/*
+		 * At least a metric header. Check with data.
+		 */
+		val = altra_hwmon_read32(ctx, hw_reg + HW_METRIC_INFO_REG);
+		hw_end_reg = hw_reg + HW_METRIC_HDRSIZE +
+			   HW_METRIC_INFO_DATASIZE_RD(val) *
+			   HW_METRIC_INFO_DATACNT_RD(val);
+		if (hw_end_reg > ctx->base_size) {
+			dev_err(ctx->dev,
+				"malform metric data 0x%x (exceeded range). Ignore remaining\n",
+				hw_reg);
+			break;
+		}
+		ret = altra_create_sensor(ctx, hw_reg, &ctx->channel_info[used]);
+
+		/* 64-bit alignment */
+		hw_reg = hw_end_reg;
+		hw_reg = ((hw_reg + 7) / 8) * 8;
+		if (ret == -ENODEV)
+			continue;
+		if (ret < 0)
+			return ret;
+		ctx->info[used] = &ctx->channel_info[used];
+		used++;
+	}
+	ctx->info[used] = NULL;
+	return 0;
+}
+
+static int altra_hwmon_probe(struct platform_device *pdev)
+{
+	const struct acpi_device_id *acpi_id;
+	struct altra_hwmon_context *ctx;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	int version;
+	int err;
+
+	ctx = devm_kzalloc(dev, sizeof(struct altra_hwmon_context), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, ctx);
+	ctx->dev = dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+
+	acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev);
+	if (!acpi_id)
+		return -EINVAL;
+
+	version = (int)acpi_id->driver_data;
+
+	ctx->base_size = resource_size(res);
+	if (version == ALTRA_HWMON_VER1)
+		ctx->base = devm_ioremap_resource(dev, res);
+	else
+		ctx->base = memremap(res->start, ctx->base_size, MEMREMAP_WB);
+	if (IS_ERR(ctx->base))
+		return PTR_ERR(ctx->base);
+
+	/* Create sensors */
+	err = altra_hwmon_create_sensors(ctx);
+	if (err != 0) {
+		if (err == -ENODEV)
+			dev_err(dev, "No sensor\n");
+		else
+			dev_err(dev, "Failed to create sensors error %d\n", err);
+		return err;
+	}
+
+	ctx->chip.ops = &altra_hwmon_ops;
+	ctx->chip.info = ctx->info;
+	ctx->hwmon_dev = devm_hwmon_device_register_with_info(dev, DRVNAME, ctx,
+							      &ctx->chip, NULL);
+	if (IS_ERR(ctx->hwmon_dev)) {
+		dev_err(dev, "Fail to register with HWmon\n");
+		err = PTR_ERR(ctx->hwmon_dev);
+		return err;
+	}
+
+	return 0;
+}
+
+static int altra_hwmon_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id altra_hwmon_acpi_match[] = {
+	{"AMPC0005", ALTRA_HWMON_VER1},
+	{"AMPC0006", ALTRA_HWMON_VER2},
+	{ },
+};
+MODULE_DEVICE_TABLE(acpi, altra_hwmon_acpi_match);
+#endif
+
+static struct platform_driver altra_hwmon_driver = {
+	.probe = altra_hwmon_probe,
+	.remove = altra_hwmon_remove,
+	.driver = {
+		.name = "altra-hwmon",
+		.acpi_match_table = ACPI_PTR(altra_hwmon_acpi_match),
+	},
+};
+module_platform_driver(altra_hwmon_driver);
+
+MODULE_DESCRIPTION("Altra SoC hardware sensor monitor");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1




More information about the kernel-team mailing list