From 8109a6c885a4a638b63b07fa6f48e7db358f8ea2 Mon Sep 17 00:00:00 2001 From: lho Date: Mon, 22 Jun 2020 21:29:02 -0700 Subject: [PATCH] hwmon: Add Ampere Altra HW monitor driver Add Ampere Altra HW monitor driver to support per core energy and SoC temperature reporting. cherry-pick from: https://github.com/AmpereComputing/ampere-centos-kernel/commit/f2e6dee522cc4be9dd094c30dc88273f44d5ffe2 Signed-off-by: lho Signed-off-by: lho --- 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 13a6b4afb4b3..1f0b52e56a56 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -304,6 +304,16 @@ config SENSORS_FAM15H_POWER This driver can also be built as a module. If so, the module will be called fam15h_power. +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 40c036ea45e6..01edf299bdd5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.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 + * Hoan Tran + * + * 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 . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#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");