Initial commit
This commit is contained in:
commit
ba72ab76e4
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
*.ko
|
||||
*.o
|
||||
*.mod.c
|
||||
.tmp_versions/
|
||||
*.order
|
||||
*.cmd
|
||||
*.symvers
|
29
Makefile
Normal file
29
Makefile
Normal file
|
@ -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)
|
7
dkms.conf
Normal file
7
dkms.conf
Normal file
|
@ -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"
|
328
zenpower.c
Normal file
328
zenpower.c
Normal file
|
@ -0,0 +1,328 @@
|
|||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <asm/amd_nb.h>
|
||||
|
||||
MODULE_DESCRIPTION("Voltage monitor for AMD ZEN family CPUs");
|
||||
MODULE_AUTHOR("Ondrej Čerman");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
// based on k10temp - GPL - (c) 2009 Clemens Ladisch <clemens@ladisch.de>
|
||||
//
|
||||
// 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);
|
Loading…
Reference in a new issue