@@ -38,6 +38,18 @@ config HWMON_DEBUG_CHIP
comment "Native drivers"
+config SENSORS_AAEON
+ tristate "AAEON hwmon driver"
+ depends on X86
+ depends on UBUNTU_ODM_DRIVERS
+ select MFD_AAEON
+ help
+ This hwmon driver adds support for reporting temperature or fan
+ speed and voltage on Single Board Computers produced by AAEON.
+
+ This driver leverages the ASUS WMI interface to access device
+ resources.
+
config SENSORS_AB8500
tristate "AB8500 thermal monitoring"
depends on AB8500_GPADC && AB8500_BM && (IIO = y)
@@ -79,6 +79,7 @@ obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
+obj-$(CONFIG_SENSORS_AAEON) += hwmon-aaeon.o
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
obj-$(CONFIG_SENSORS_I5500) += i5500_temp.o
obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
new file mode 100644
@@ -0,0 +1,568 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AAEON HWMON driver
+ * Copyright (c) 2021, AAEON Ltd.
+ *
+ * Author: Edward Lin <edward1_lin@aaeon.com.tw>
+ * Author: Kunyang Fan <kunyang_fan@aaeon.com.tw>
+ *
+ * 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.
+ */
+#include <linux/acpi.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/x86/asus-wmi.h>
+#include <linux/platform_device.h>
+
+#define DRVNAME "hwmon-aaeon"
+
+#define AAEON_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66"
+
+#define AAEON_VERSION_METHOD_ID 0x00000000
+#define HWM_INFORMATION_METHOD_ID 0x00030000
+#define HWM_METHOD_ID 0x00030001
+
+#define BITMAP_TEMP_ARG 0x12
+#define BITMAP_FAN_ARG 0x13
+#define BITMAP_VOLTAGE_ARG 0x14
+
+#define SENSOR_TEMP_NUMBER 0
+#define SENSOR_FAN_NUMBER 1
+#define SENSOR_VOLTAGE_NUMBER 2
+#define SENSOR_MAX_NUMBER 2
+
+static ssize_t aaeon_show_sensor(struct device *dev,
+ struct device_attribute *devattr, char *buf);
+static ssize_t aaeon_show_sensor_name(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf);
+static ssize_t aaeon_show_version(struct device *dev,
+ struct device_attribute *devattr, char *buf);
+static ssize_t name_show(struct device *dev, struct device_attribute *devattr,
+ char *buf);
+static int aaeon_get_version(void);
+static int aaeon_hwmon_probe(struct platform_device *pdev);
+static int aaeon_hwmon_remove(struct platform_device *pdev);
+
+static const char * const temp_sensors_name_table[] = {
+ "CPU_Temp",
+ "SYS1_Temp",
+ "SYS2_Temp",
+};
+
+static const char * const temp_sensors_name_table_V3[] = {
+ "SYS_Temp",
+ "CPU_Temp",
+};
+
+static const char * const fan_sensors_name_table[] = {
+ "CPU_FAN",
+ "SYS1_FAN",
+ "SYS2_FAN",
+ "Chasis1_FAN",
+ "Chasis2_FAN",
+};
+
+static const char * const fan_sensors_name_table_V3[] = {
+ "Chasis_FAN",
+ "CPU_FAN",
+};
+
+static const char * const voltage_sensors_name_table[] = {
+ "VCORE_Voltage",
+ "VMEM_Voltage",
+ "+12_Voltage",
+ "+5_Voltage",
+ "+3.3_Voltage",
+ "+1.8_Voltage",
+ "5VSB_Voltage",
+ "3VSB_Voltage",
+ "VBAT_Voltage",
+};
+
+static const char * const voltage_sensors_name_table_V3[] = {
+ "VCORE_Voltage",
+ "+5_Voltage",
+ "AVCC_Voltage",
+ "+3.3_Voltage",
+ "+12_Voltage",
+ "VCOREREFIN_Voltage",
+ "VIN4_Voltage",
+ "3VSB_Voltage",
+ "VBAT_Voltage",
+};
+
+struct aaeon_hwmon_data {
+ struct device *hwmon_dev;
+ int bfpi_version;
+ u32 temp_bitmap;
+ u32 fan_bitmap;
+ u32 voltage_bitmap;
+ unsigned int sensors_number[SENSOR_MAX_NUMBER + 1];
+ const char * const *temp_names;
+ const char * const *fan_names;
+ const char * const *voltage_names;
+};
+
+/* Temperature attributes */
+static struct sensor_device_attribute_2 temp_sys_nodes_atts[] = {
+ SENSOR_ATTR_2(temp1_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_TEMP_NUMBER, 0),
+ SENSOR_ATTR_2(temp1_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_TEMP_NUMBER, 0),
+ SENSOR_ATTR_2(temp2_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_TEMP_NUMBER, 1),
+ SENSOR_ATTR_2(temp2_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_TEMP_NUMBER, 1),
+ SENSOR_ATTR_2(temp3_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_TEMP_NUMBER, 2),
+ SENSOR_ATTR_2(temp3_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_TEMP_NUMBER, 2),
+};
+
+/* Cooler Fan attributes */
+static struct sensor_device_attribute_2 fan_sys_nodes_atts[] = {
+ SENSOR_ATTR_2(fan1_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_FAN_NUMBER, 0),
+ SENSOR_ATTR_2(fan1_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_FAN_NUMBER, 0),
+ SENSOR_ATTR_2(fan2_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_FAN_NUMBER, 1),
+ SENSOR_ATTR_2(fan2_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_FAN_NUMBER, 1),
+ SENSOR_ATTR_2(fan3_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_FAN_NUMBER, 2),
+ SENSOR_ATTR_2(fan3_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_FAN_NUMBER, 2),
+ SENSOR_ATTR_2(fan4_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_FAN_NUMBER, 3),
+ SENSOR_ATTR_2(fan4_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_FAN_NUMBER, 3),
+ SENSOR_ATTR_2(fan5_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_FAN_NUMBER, 4),
+ SENSOR_ATTR_2(fan5_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_FAN_NUMBER, 4),
+};
+
+/* Voltage attributes */
+static struct sensor_device_attribute_2 voltage_sys_nodes_atts[] = {
+ SENSOR_ATTR_2(in1_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 0),
+ SENSOR_ATTR_2(in1_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 0),
+ SENSOR_ATTR_2(in2_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 1),
+ SENSOR_ATTR_2(in2_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 1),
+ SENSOR_ATTR_2(in3_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 2),
+ SENSOR_ATTR_2(in3_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 2),
+ SENSOR_ATTR_2(in4_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 3),
+ SENSOR_ATTR_2(in4_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 3),
+ SENSOR_ATTR_2(in5_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 4),
+ SENSOR_ATTR_2(in5_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 4),
+ SENSOR_ATTR_2(in6_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 5),
+ SENSOR_ATTR_2(in6_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 5),
+ SENSOR_ATTR_2(in7_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 6),
+ SENSOR_ATTR_2(in7_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 6),
+ SENSOR_ATTR_2(in8_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 7),
+ SENSOR_ATTR_2(in8_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 7),
+ SENSOR_ATTR_2(in9_input, 0444, aaeon_show_sensor, NULL,
+ SENSOR_VOLTAGE_NUMBER, 8),
+ SENSOR_ATTR_2(in9_label, 0444, aaeon_show_sensor_name, NULL,
+ SENSOR_VOLTAGE_NUMBER, 8),
+
+};
+
+static struct sensor_device_attribute_2 info_sys_nodes_atts[] = {
+ /* WMI version Information */
+ SENSOR_ATTR_2(AAEON_VERSION, 0444, aaeon_show_version, NULL, 0, 0),
+};
+
+DEVICE_ATTR_RO(name);
+static ssize_t name_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return sprintf(buf, "%s\n", DRVNAME);
+}
+
+static ssize_t aaeon_show_version(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct aaeon_hwmon_data *data =
+ (struct aaeon_hwmon_data *)dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", data->bfpi_version);
+}
+
+static ssize_t aaeon_show_sensor_name(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ u8 nr = to_sensor_dev_attr_2(devattr)->nr;
+ u8 index = to_sensor_dev_attr_2(devattr)->index;
+ struct aaeon_hwmon_data *data =
+ (struct aaeon_hwmon_data *)dev_get_drvdata(dev);
+
+ if (nr > SENSOR_MAX_NUMBER || index >= data->sensors_number[nr]) {
+ pr_debug("Can not check the device");
+ return -1;
+ }
+
+ switch (nr) {
+ case SENSOR_TEMP_NUMBER:
+ return sprintf(buf, "%s\n", data->temp_names[index]);
+ case SENSOR_FAN_NUMBER:
+ return sprintf(buf, "%s\n", data->fan_names[index]);
+ case SENSOR_VOLTAGE_NUMBER:
+ return sprintf(buf, "%s\n", data->voltage_names[index]);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static ssize_t aaeon_show_sensor(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ u8 nr = to_sensor_dev_attr_2(devattr)->nr;
+ u8 index = to_sensor_dev_attr_2(devattr)->index;
+ u32 dev_id;
+ int retval, err;
+ struct aaeon_hwmon_data *data =
+ (struct aaeon_hwmon_data *)dev_get_drvdata(dev);
+
+ if (nr > SENSOR_MAX_NUMBER || index >= data->sensors_number[nr]) {
+ pr_debug("Can not check the device");
+ return -1;
+ }
+
+ /* For the V3 version, index need offset */
+ if (data->bfpi_version == 0x03 && nr != SENSOR_VOLTAGE_NUMBER)
+ index++;
+
+ dev_id = (index << 12) | (nr << 8);
+ err = asus_wmi_evaluate_method(HWM_METHOD_ID, dev_id, 0, &retval);
+ if (err)
+ return err;
+
+ /* For the V3 version, need to convert the raw value*/
+ if (nr == SENSOR_VOLTAGE_NUMBER && data->bfpi_version == 0x03) {
+ switch (index) {
+ case 0: /* VCORE */
+ retval = retval * 16;
+ break;
+ case 1: /* +5V */
+ retval = (retval * 2008) / 50;
+ break;
+ case 2: /* AVCC */
+ retval = retval * 16;
+ break;
+ case 3: /* +3.3V */
+ retval = retval * 16;
+ break;
+ case 4: /* +12V */
+ retval = retval * 96;
+ break;
+ case 5: /* VCOREREFIN */
+ retval = (retval * 552) / 41;
+ break;
+ case 6: /* VIN4 */
+ retval = retval * 8;
+ break;
+ case 7: /* 3VSB */
+ retval = retval * 16;
+ break;
+ case 8: /* VBAT */
+ retval = retval * 16;
+ break;
+ default:
+ break;
+ }
+ } else if (nr == SENSOR_TEMP_NUMBER && data->bfpi_version == 0x03)
+ retval = retval * 1000;
+
+ return sprintf(buf, "%d\n", retval);
+}
+
+static int aaeon_hwmon_create_sub_sysfs_fs(struct platform_device *pdev,
+ struct sensor_device_attribute_2 *attr,
+ int sensor_number,
+ u32 sensor_mask,
+ int bfpi_version)
+{
+ int i, err = 0;
+
+ for (i = 0; i < sensor_number; i++) {
+ if (bfpi_version == 0x03 || sensor_mask & BIT(i)) {
+ err = device_create_file(&pdev->dev, &attr[2 * i].dev_attr);
+ if (err)
+ break;
+ err = device_create_file(&pdev->dev, &attr[2 * i + 1].dev_attr);
+ if (err)
+ break;
+ }
+ }
+
+ return err;
+}
+
+static int
+aaeon_hwmon_create_sysfs_files(struct platform_device *pdev, struct aaeon_hwmon_data *data)
+{
+ int err;
+
+ /* register sysfs interface files */
+ err = device_create_file(&pdev->dev, &dev_attr_name);
+ if (err)
+ return err;
+
+ /* registe sysfs to dump sensors BFPI version */
+ err = device_create_file(&pdev->dev, &info_sys_nodes_atts[0].dev_attr);
+ if (err)
+ return err;
+
+ /* create temperature name and value node */
+ err = aaeon_hwmon_create_sub_sysfs_fs(pdev, temp_sys_nodes_atts,
+ data->sensors_number[SENSOR_TEMP_NUMBER],
+ data->temp_bitmap, data->bfpi_version);
+ if (err)
+ return err;
+
+ /* create fan name and value node */
+ err = aaeon_hwmon_create_sub_sysfs_fs(pdev, fan_sys_nodes_atts,
+ data->sensors_number[SENSOR_FAN_NUMBER],
+ data->fan_bitmap, data->bfpi_version);
+ if (err)
+ return err;
+
+ /* create voltage name and value node */
+ err = aaeon_hwmon_create_sub_sysfs_fs(pdev, voltage_sys_nodes_atts,
+ data->sensors_number[SENSOR_VOLTAGE_NUMBER],
+ data->voltage_bitmap, data->bfpi_version);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static void aaeon_hwmon_remove_sub_sysfs_fs(struct platform_device *pdev,
+ struct sensor_device_attribute_2 *attr,
+ int sensor_number,
+ u32 sensor_mask,
+ int bfpi_version)
+{
+ int i;
+
+ for (i = 0; i < sensor_number; i++) {
+ if (bfpi_version == 0x03 || sensor_mask & BIT(i)) {
+ device_remove_file(&pdev->dev, &attr[2 * i].dev_attr);
+ device_remove_file(&pdev->dev, &attr[2 * i + 1].dev_attr);
+ }
+ }
+}
+
+static void
+aaeon_hwmon_remove_sysfs_files(struct platform_device *pdev,
+ struct aaeon_hwmon_data *data)
+{
+ /* degister sysfs interface files */
+ device_remove_file(&pdev->dev, &dev_attr_name);
+
+ /* degiste sysfs to dump sensors BFPI version */
+ device_remove_file(&pdev->dev, &info_sys_nodes_atts[0].dev_attr);
+
+ /* remove temperature name and value node */
+ aaeon_hwmon_remove_sub_sysfs_fs(pdev, temp_sys_nodes_atts,
+ data->sensors_number[SENSOR_TEMP_NUMBER],
+ data->temp_bitmap,
+ data->bfpi_version);
+
+ /* remove fan name and value node */
+ aaeon_hwmon_remove_sub_sysfs_fs(pdev, fan_sys_nodes_atts,
+ data->sensors_number[SENSOR_FAN_NUMBER],
+ data->fan_bitmap,
+ data->bfpi_version);
+
+ /* remove voltage name and value node */
+ aaeon_hwmon_remove_sub_sysfs_fs(pdev, voltage_sys_nodes_atts,
+ data->sensors_number[SENSOR_VOLTAGE_NUMBER],
+ data->voltage_bitmap,
+ data->bfpi_version);
+}
+
+static int aaeon_hwmon_remove(struct platform_device *pdev)
+{
+ struct aaeon_hwmon_data *data = platform_get_drvdata(pdev);
+
+ if (data->hwmon_dev)
+ hwmon_device_unregister(data->hwmon_dev);
+
+ aaeon_hwmon_remove_sysfs_files(pdev, data);
+
+ return 0;
+}
+
+static int aaeon_get_version(void)
+{
+ int err, retval;
+ u32 dev_id = 0x00;
+
+ err = asus_wmi_evaluate_method(AAEON_VERSION_METHOD_ID, dev_id, 0,
+ &retval);
+ if (err)
+ return err;
+
+ return retval;
+}
+
+static int aaeon_hwmon_init_drv_data(struct aaeon_hwmon_data *data)
+{
+ int err;
+
+ data->bfpi_version = aaeon_get_version();
+ if (data->bfpi_version < 0) {
+ pr_debug("Error BFPI verion\n");
+ return -1;
+ }
+
+ if (data->bfpi_version == 0x03) {
+ /* set the number of bits in temp bitmap */
+ data->sensors_number[SENSOR_TEMP_NUMBER] =
+ ARRAY_SIZE(temp_sensors_name_table_V3);
+ data->temp_names = temp_sensors_name_table_V3;
+
+ /* set the number of bits in fan bitmap */
+ data->sensors_number[SENSOR_FAN_NUMBER] =
+ ARRAY_SIZE(fan_sensors_name_table_V3);
+ data->fan_names = fan_sensors_name_table_V3;
+
+ /* set the number of bits in voltage bitmap */
+ data->sensors_number[SENSOR_VOLTAGE_NUMBER] =
+ ARRAY_SIZE(voltage_sensors_name_table_V3);
+ data->voltage_names = voltage_sensors_name_table_V3;
+ } else {
+ /* set the number of bits in temp bitmap */
+ data->sensors_number[SENSOR_TEMP_NUMBER] =
+ ARRAY_SIZE(temp_sensors_name_table);
+ data->temp_names = temp_sensors_name_table;
+
+ /* set the number of bits in fan bitmap */
+ data->sensors_number[SENSOR_FAN_NUMBER] =
+ ARRAY_SIZE(fan_sensors_name_table);
+ data->fan_names = fan_sensors_name_table;
+
+ /* set the number of bits in voltage bitmap */
+ data->sensors_number[SENSOR_VOLTAGE_NUMBER] =
+ ARRAY_SIZE(voltage_sensors_name_table);
+ data->voltage_names = voltage_sensors_name_table;
+ }
+
+ /* get temp supported bitmap */
+ err = asus_wmi_evaluate_method(HWM_INFORMATION_METHOD_ID,
+ BITMAP_TEMP_ARG, 0, &data->temp_bitmap);
+ if (err)
+ return err;
+
+ /* get fan supported bitmap */
+ err = asus_wmi_evaluate_method(HWM_INFORMATION_METHOD_ID,
+ BITMAP_FAN_ARG, 0, &data->fan_bitmap);
+ if (err)
+ return err;
+
+ /* get voltage supported bitmap */
+ err = asus_wmi_evaluate_method(HWM_INFORMATION_METHOD_ID,
+ BITMAP_VOLTAGE_ARG, 0, &data->voltage_bitmap);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int aaeon_hwmon_probe(struct platform_device *pdev)
+{
+ int err;
+ struct aaeon_hwmon_data *data;
+
+ pr_debug("aaeon hwomon device probe (support V3)!\n");
+ if (!wmi_has_guid(AAEON_WMI_MGMT_GUID)) {
+ pr_info("AAEON Management GUID not found\n");
+ return -ENODEV;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct aaeon_hwmon_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ err = aaeon_hwmon_init_drv_data(data);
+ if (err) {
+ pr_info("Error to get sensor support bitmap\n");
+ goto exit;
+ }
+
+ if (data->bfpi_version != 0x03 && data->temp_bitmap == 0 &&
+ data->fan_bitmap == 0 && data->voltage_bitmap == 0) {
+ pr_debug("No sensors found\n");
+ err = -ENODEV;
+ goto exit;
+ }
+
+ platform_set_drvdata(pdev, data);
+ err = aaeon_hwmon_create_sysfs_files(pdev, data);
+ if (err)
+ goto exit;
+
+ data->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "AAEON_HWM",
+ data,
+ NULL,
+ NULL);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ data->hwmon_dev = NULL;
+ goto exit_unregister_sysfs;
+ }
+
+ return 0;
+
+exit_unregister_sysfs:
+ aaeon_hwmon_remove(pdev);
+exit:
+ return err;
+}
+
+static struct platform_driver aaeon_hwmon_driver = {
+ .driver = {
+ .name = DRVNAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = aaeon_hwmon_probe,
+ .remove = aaeon_hwmon_remove,
+};
+
+module_platform_driver_probe(aaeon_hwmon_driver, aaeon_hwmon_probe);
+
+MODULE_ALIAS("platform:hwmon-aaeon");
+MODULE_DESCRIPTION("AAEON Hardware Monitoring Driver");
+MODULE_AUTHOR("Edward Lin <edward1_lin@aaeon.com.tw>");
+MODULE_AUTHOR("Kunyang Fan <kunyang_fan@aaeon.com.tw>");
+MODULE_LICENSE("GPL v2");