From ba72ab76e44fe0582c63469e23ac0c5788a22398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20=C4=8Cerman?= Date: Sat, 10 Nov 2018 23:17:51 +0100 Subject: [PATCH] Initial commit --- .gitignore | 7 ++ Makefile | 29 +++++ dkms.conf | 7 ++ zenpower.c | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 371 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 dkms.conf create mode 100644 zenpower.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1815dd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.ko +*.o +*.mod.c +.tmp_versions/ +*.order +*.cmd +*.symvers diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e78339a --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +TARGET := $(shell uname -r) +KERNEL_BUILD := /usr/src/linux-headers-$(TARGET) +DKMS_ROOT_PATH := /usr/src/zenpower-0.1.0 + +obj-m := $(patsubst %,%.o,zenpower) +obj-ko := $(patsubst %,%.ko,zenpower) + +.PHONY: all modules clean dkms-install dkms-uninstall + +all: modules + +modules: + @$(MAKE) -C $(KERNEL_BUILD) M=$(CURDIR) modules + +clean: + @$(MAKE) -C $(KERNEL_BUILD) M=$(CURDIR) clean + +dkms-install: + mkdir $(DKMS_ROOT_PATH) + cp $(CURDIR)/dkms.conf $(DKMS_ROOT_PATH) + cp $(CURDIR)/Makefile $(DKMS_ROOT_PATH) + cp $(CURDIR)/zenpower.c $(DKMS_ROOT_PATH) + dkms add zenpower/0.1.0 + dkms build zenpower/0.1.0 + dkms install zenpower/0.1.0 + +dkms-uninstall: + dkms remove zenpower/0.1.0 --all + rm -rf $(DKMS_ROOT_PATH) diff --git a/dkms.conf b/dkms.conf new file mode 100644 index 0000000..d3cda75 --- /dev/null +++ b/dkms.conf @@ -0,0 +1,7 @@ +MAKE="make TARGET=${kernelver}" +CLEAN="make clean" +PACKAGE_NAME="zenpower" +PACKAGE_VERSION="0.1.0" +BUILT_MODULE_NAME[0]="zenpower" +DEST_MODULE_LOCATION[0]="/kernel/drivers/hwmon/zenpower" +AUTOINSTALL="yes" diff --git a/zenpower.c b/zenpower.c new file mode 100644 index 0000000..f56b64e --- /dev/null +++ b/zenpower.c @@ -0,0 +1,328 @@ +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Voltage monitor for AMD ZEN family CPUs"); +MODULE_AUTHOR("Ondrej Čerman"); +MODULE_LICENSE("GPL"); + +// based on k10temp - GPL - (c) 2009 Clemens Ladisch +// +// Docs: +// - https://www.kernel.org/doc/Documentation/hwmon/hwmon-kernel-api.txt +// - https://developer.amd.com/wp-content/resources/56255_3_03.PDF +// +// Sources: +// - Temp monitoring is from k10temp +// - SVI address and voltage formula is from LibreHardwareMonitor +// - Current formulas were discovered experimentally + + +#ifndef PCI_DEVICE_ID_AMD_17H_DF_F3 +#define PCI_DEVICE_ID_AMD_17H_DF_F3 0x1463 +#endif + +#ifndef PCI_DEVICE_ID_AMD_17H_M10H_DF_F3 +#define PCI_DEVICE_ID_AMD_17H_M10H_DF_F3 0x15eb +#endif + +/* F17h M01h Access througn SMN */ +#define F17H_M01H_REPORTED_TEMP_CTRL_OFFSET 0x00059800 +#define F17H_M01H_SVI 0x0005A000 + +#define F17H_TEMP_ADJUST_MASK 0x80000 + +struct zenpower_data { + struct pci_dev *pdev; + void (*read_smusvi0_tel_plane0)(struct pci_dev *pdev, u32 *regval); + void (*read_smusvi0_tel_plane1)(struct pci_dev *pdev, u32 *regval); + void (*read_tempreg)(struct pci_dev *pdev, u32 *regval); + int temp_offset; +}; + +struct tctl_offset { + u8 model; + char const *id; + int offset; +}; + +static const struct tctl_offset tctl_offset_table[] = { + { 0x17, "AMD Ryzen 5 1600X", 20000 }, + { 0x17, "AMD Ryzen 7 1700X", 20000 }, + { 0x17, "AMD Ryzen 7 1800X", 20000 }, + { 0x17, "AMD Ryzen 7 2700X", 10000 }, + { 0x17, "AMD Ryzen Threadripper 19", 27000 }, /* 19{00,20,50}X */ + { 0x17, "AMD Ryzen Threadripper 29", 27000 }, /* 29{20,50,70,90}[W]X */ +}; + +static umode_t zenpower_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + return attr->mode; +} + +static u32 plane_to_vcc(u32 p) +{ + u32 vdd_cor; + vdd_cor = (p >> 16) & 0xff; + // U = 1550 - 6.25 * cdd_cor + + return 1550 - ((625 * vdd_cor) / 100); +} + +static u32 get_core_current(u32 plane) +{ + u32 idd_cor; + idd_cor = plane & 0xff; + // I = 1039.211 * iddcor + + return (1039211 * idd_cor) / 1000; +} + +static u32 get_soc_current(u32 plane) +{ + u32 idd_cor; + idd_cor = plane & 0xff; + // I = 360.772 * iddcor + + return (360772 * idd_cor) / 1000; +} + +static unsigned int get_raw_temp(struct zenpower_data *data) +{ + unsigned int temp; + u32 regval; + + data->read_tempreg(data->pdev, ®val); + temp = (regval >> 21) * 125; + if (regval & F17H_TEMP_ADJUST_MASK) + temp -= 49000; + return temp; +} + +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + unsigned int temp = get_raw_temp(data); + + if (temp > data->temp_offset) + temp -= data->temp_offset; + else + temp = 0; + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t temp1_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", 70 * 1000); +} + +static ssize_t temp2_input_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + unsigned int temp = get_raw_temp(data); + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t in1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + u32 plane, vcc; + + data->read_smusvi0_tel_plane0(data->pdev, &plane); + vcc = plane_to_vcc(plane); + + return sprintf(buf, "%d\n", vcc); +} + +static ssize_t in2_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + u32 plane, vcc; + + data->read_smusvi0_tel_plane1(data->pdev, &plane); + vcc = plane_to_vcc(plane); + + return sprintf(buf, "%d\n", vcc); +} + +static ssize_t curr1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + u32 plane; + + data->read_smusvi0_tel_plane0(data->pdev, &plane); + return sprintf(buf, "%d\n", get_core_current(plane) ); +} + +static ssize_t curr2_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + u32 plane; + + data->read_smusvi0_tel_plane1(data->pdev, &plane); + return sprintf(buf, "%d\n", get_soc_current(plane) ); +} + +static ssize_t power1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + u32 plane; + + data->read_smusvi0_tel_plane0(data->pdev, &plane); + return sprintf(buf, "%d\n", get_core_current(plane) * plane_to_vcc(plane) ); +} + +static ssize_t power2_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + u32 plane; + + data->read_smusvi0_tel_plane1(data->pdev, &plane); + return sprintf(buf, "%d\n", get_soc_current(plane) * plane_to_vcc(plane) ); +} + +static ssize_t zen_label_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + switch (attr->index){ + case 1: + return sprintf(buf, "SVI2_Core\n"); + case 2: + return sprintf(buf, "SVI2_SoC\n"); + case 11: + return sprintf(buf, "SVI2_C_Core\n"); + case 12: + return sprintf(buf, "SVI2_C_SoC\n"); + case 21: + return sprintf(buf, "SVI2_P_Core\n"); + case 22: + return sprintf(buf, "SVI2_P_SoC\n"); + case 31: + return sprintf(buf, "Tdie\n"); + case 32: + return sprintf(buf, "Tctl\n"); + } + + return 0; +} + +static DEVICE_ATTR_RO(in1_input); +static SENSOR_DEVICE_ATTR(in1_label, 0444, zen_label_show, NULL, 1); +static DEVICE_ATTR_RO(in2_input); +static SENSOR_DEVICE_ATTR(in2_label, 0444, zen_label_show, NULL, 2); +static DEVICE_ATTR_RO(curr1_input); +static SENSOR_DEVICE_ATTR(curr1_label, 0444, zen_label_show, NULL, 11); +static DEVICE_ATTR_RO(curr2_input); +static SENSOR_DEVICE_ATTR(curr2_label, 0444, zen_label_show, NULL, 12); +static DEVICE_ATTR_RO(power1_input); +static SENSOR_DEVICE_ATTR(power1_label, 0444, zen_label_show, NULL, 21); +static DEVICE_ATTR_RO(power2_input); +static SENSOR_DEVICE_ATTR(power2_label, 0444, zen_label_show, NULL, 22); +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RO(temp1_max); +static SENSOR_DEVICE_ATTR(temp1_label, 0444, zen_label_show, NULL, 31); +static DEVICE_ATTR_RO(temp2_input); +static SENSOR_DEVICE_ATTR(temp2_label, 0444, zen_label_show, NULL, 32); + +static struct attribute *zenpower_attrs[] = { + &dev_attr_in1_input.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &dev_attr_in2_input.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &dev_attr_curr1_input.attr, + &sensor_dev_attr_curr1_label.dev_attr.attr, + &dev_attr_curr2_input.attr, + &sensor_dev_attr_curr2_label.dev_attr.attr, + &dev_attr_power1_input.attr, + &sensor_dev_attr_power1_label.dev_attr.attr, + &dev_attr_power2_input.attr, + &sensor_dev_attr_power2_label.dev_attr.attr, + &dev_attr_temp1_input.attr, + &dev_attr_temp1_max.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &dev_attr_temp2_input.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + NULL +}; + +static const struct attribute_group zenpower_group = { + .attrs = zenpower_attrs, + .is_visible = zenpower_is_visible, +}; +__ATTRIBUTE_GROUPS(zenpower); + +static void read_smusvi0_tel_plane0_nb_f17(struct pci_dev *pdev, u32 *regval) +{ + amd_smn_read(amd_pci_dev_to_node_id(pdev), F17H_M01H_SVI + 0xc, regval); +} + +static void read_smusvi0_tel_plane1_nb_f17(struct pci_dev *pdev, u32 *regval) +{ + amd_smn_read(amd_pci_dev_to_node_id(pdev), F17H_M01H_SVI + 0x10, regval); +} + +static void read_tempreg_nb_f17(struct pci_dev *pdev, u32 *regval) +{ + amd_smn_read(amd_pci_dev_to_node_id(pdev), F17H_M01H_REPORTED_TEMP_CTRL_OFFSET, regval); +} + +static int zenpower_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct zenpower_data *data; + struct device *hwmon_dev; + int i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + data->read_smusvi0_tel_plane0 = read_smusvi0_tel_plane0_nb_f17; + data->read_smusvi0_tel_plane1 = read_smusvi0_tel_plane1_nb_f17; + data->read_tempreg = read_tempreg_nb_f17; + + for (i = 0; i < ARRAY_SIZE(tctl_offset_table); i++) { + const struct tctl_offset *entry = &tctl_offset_table[i]; + + if (boot_cpu_data.x86 == entry->model && + strstr(boot_cpu_data.x86_model_id, entry->id)) { + data->temp_offset = entry->offset; + break; + } + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "zenpower", data, zenpower_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct pci_device_id zenpower_id_table[] = { + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M10H_DF_F3) }, + {} +}; +MODULE_DEVICE_TABLE(pci, zenpower_id_table); + +static struct pci_driver zenpower_driver = { + .name = "zenpower", + .id_table = zenpower_id_table, + .probe = zenpower_probe, +}; + +module_pci_driver(zenpower_driver);