xref: /linux/drivers/platform/mips/cpu_hwmon.c (revision 0ea8a56de21be24cb79abb03dee79aabcd60a316)
109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
264f09aa9SHuacai Chen #include <linux/err.h>
364f09aa9SHuacai Chen #include <linux/module.h>
464f09aa9SHuacai Chen #include <linux/reboot.h>
564f09aa9SHuacai Chen #include <linux/jiffies.h>
664f09aa9SHuacai Chen #include <linux/hwmon.h>
764f09aa9SHuacai Chen #include <linux/hwmon-sysfs.h>
864f09aa9SHuacai Chen 
964f09aa9SHuacai Chen #include <loongson.h>
1064f09aa9SHuacai Chen #include <boot_param.h>
1164f09aa9SHuacai Chen #include <loongson_hwmon.h>
127507445bSHuacai Chen #include <loongson_regs.h>
137507445bSHuacai Chen 
14f17d3f21STiezhu Yang static int csr_temp_enable;
1564f09aa9SHuacai Chen 
1664f09aa9SHuacai Chen /*
1764f09aa9SHuacai Chen  * Loongson-3 series cpu has two sensors inside,
1864f09aa9SHuacai Chen  * each of them from 0 to 255,
1964f09aa9SHuacai Chen  * if more than 127, that is dangerous.
2064f09aa9SHuacai Chen  * here only provide sensor1 data, because it always hot than sensor0
2164f09aa9SHuacai Chen  */
2264f09aa9SHuacai Chen int loongson3_cpu_temp(int cpu)
2364f09aa9SHuacai Chen {
240a00024dSHuacai Chen 	u32 reg, prid_rev;
2564f09aa9SHuacai Chen 
267507445bSHuacai Chen 	if (csr_temp_enable) {
277507445bSHuacai Chen 		reg = (csr_readl(LOONGSON_CSR_CPUTEMP) & 0xff);
287507445bSHuacai Chen 		goto out;
297507445bSHuacai Chen 	}
307507445bSHuacai Chen 
3164f09aa9SHuacai Chen 	reg = LOONGSON_CHIPTEMP(cpu);
320a00024dSHuacai Chen 	prid_rev = read_c0_prid() & PRID_REV_MASK;
337507445bSHuacai Chen 
340a00024dSHuacai Chen 	switch (prid_rev) {
350a00024dSHuacai Chen 	case PRID_REV_LOONGSON3A_R1:
3664f09aa9SHuacai Chen 		reg = (reg >> 8) & 0xff;
370a00024dSHuacai Chen 		break;
380a00024dSHuacai Chen 	case PRID_REV_LOONGSON3B_R1:
390a00024dSHuacai Chen 	case PRID_REV_LOONGSON3B_R2:
40f3ade253SHuacai Chen 	case PRID_REV_LOONGSON3A_R2_0:
41f3ade253SHuacai Chen 	case PRID_REV_LOONGSON3A_R2_1:
4264f09aa9SHuacai Chen 		reg = ((reg >> 8) & 0xff) - 100;
430a00024dSHuacai Chen 		break;
447cff3f16SHuacai Chen 	case PRID_REV_LOONGSON3A_R3_0:
457cff3f16SHuacai Chen 	case PRID_REV_LOONGSON3A_R3_1:
467507445bSHuacai Chen 	default:
470a00024dSHuacai Chen 		reg = (reg & 0xffff) * 731 / 0x4000 - 273;
480a00024dSHuacai Chen 		break;
490a00024dSHuacai Chen 	}
507507445bSHuacai Chen 
517507445bSHuacai Chen out:
5264f09aa9SHuacai Chen 	return (int)reg * 1000;
5364f09aa9SHuacai Chen }
5464f09aa9SHuacai Chen 
5599b0b5a3SHuacai Chen static int nr_packages;
5664f09aa9SHuacai Chen static struct device *cpu_hwmon_dev;
5764f09aa9SHuacai Chen 
58*f59dc511SZhi Li static SENSOR_DEVICE_ATTR(name, 0444, NULL, NULL, 0);
5964f09aa9SHuacai Chen 
6064f09aa9SHuacai Chen static struct attribute *cpu_hwmon_attributes[] = {
6164f09aa9SHuacai Chen 	&sensor_dev_attr_name.dev_attr.attr,
6264f09aa9SHuacai Chen 	NULL
6364f09aa9SHuacai Chen };
6464f09aa9SHuacai Chen 
6564f09aa9SHuacai Chen /* Hwmon device attribute group */
6664f09aa9SHuacai Chen static struct attribute_group cpu_hwmon_attribute_group = {
6764f09aa9SHuacai Chen 	.attrs = cpu_hwmon_attributes,
6864f09aa9SHuacai Chen };
6964f09aa9SHuacai Chen 
7099b0b5a3SHuacai Chen static ssize_t get_cpu_temp(struct device *dev,
7164f09aa9SHuacai Chen 			struct device_attribute *attr, char *buf);
7299b0b5a3SHuacai Chen static ssize_t cpu_temp_label(struct device *dev,
7364f09aa9SHuacai Chen 			struct device_attribute *attr, char *buf);
7464f09aa9SHuacai Chen 
75f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp1_input, 0444, get_cpu_temp, NULL, 1);
76f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp1_label, 0444, cpu_temp_label, NULL, 1);
77f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp2_input, 0444, get_cpu_temp, NULL, 2);
78f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp2_label, 0444, cpu_temp_label, NULL, 2);
79f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp3_input, 0444, get_cpu_temp, NULL, 3);
80f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp3_label, 0444, cpu_temp_label, NULL, 3);
81f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp4_input, 0444, get_cpu_temp, NULL, 4);
82f17d3f21STiezhu Yang static SENSOR_DEVICE_ATTR(temp4_label, 0444, cpu_temp_label, NULL, 4);
8364f09aa9SHuacai Chen 
8499b0b5a3SHuacai Chen static const struct attribute *hwmon_cputemp[4][3] = {
8599b0b5a3SHuacai Chen 	{
8664f09aa9SHuacai Chen 		&sensor_dev_attr_temp1_input.dev_attr.attr,
8764f09aa9SHuacai Chen 		&sensor_dev_attr_temp1_label.dev_attr.attr,
8864f09aa9SHuacai Chen 		NULL
8999b0b5a3SHuacai Chen 	},
9099b0b5a3SHuacai Chen 	{
9164f09aa9SHuacai Chen 		&sensor_dev_attr_temp2_input.dev_attr.attr,
9264f09aa9SHuacai Chen 		&sensor_dev_attr_temp2_label.dev_attr.attr,
9364f09aa9SHuacai Chen 		NULL
9499b0b5a3SHuacai Chen 	},
9599b0b5a3SHuacai Chen 	{
9699b0b5a3SHuacai Chen 		&sensor_dev_attr_temp3_input.dev_attr.attr,
9799b0b5a3SHuacai Chen 		&sensor_dev_attr_temp3_label.dev_attr.attr,
9899b0b5a3SHuacai Chen 		NULL
9999b0b5a3SHuacai Chen 	},
10099b0b5a3SHuacai Chen 	{
10199b0b5a3SHuacai Chen 		&sensor_dev_attr_temp4_input.dev_attr.attr,
10299b0b5a3SHuacai Chen 		&sensor_dev_attr_temp4_label.dev_attr.attr,
10399b0b5a3SHuacai Chen 		NULL
10499b0b5a3SHuacai Chen 	}
10564f09aa9SHuacai Chen };
10664f09aa9SHuacai Chen 
10799b0b5a3SHuacai Chen static ssize_t cpu_temp_label(struct device *dev,
10864f09aa9SHuacai Chen 			struct device_attribute *attr, char *buf)
10964f09aa9SHuacai Chen {
11099b0b5a3SHuacai Chen 	int id = (to_sensor_dev_attr(attr))->index - 1;
111f17d3f21STiezhu Yang 
11299b0b5a3SHuacai Chen 	return sprintf(buf, "CPU %d Temperature\n", id);
11364f09aa9SHuacai Chen }
11464f09aa9SHuacai Chen 
11599b0b5a3SHuacai Chen static ssize_t get_cpu_temp(struct device *dev,
11664f09aa9SHuacai Chen 			struct device_attribute *attr, char *buf)
11764f09aa9SHuacai Chen {
11899b0b5a3SHuacai Chen 	int id = (to_sensor_dev_attr(attr))->index - 1;
11999b0b5a3SHuacai Chen 	int value = loongson3_cpu_temp(id);
120f17d3f21STiezhu Yang 
12164f09aa9SHuacai Chen 	return sprintf(buf, "%d\n", value);
12264f09aa9SHuacai Chen }
12364f09aa9SHuacai Chen 
12464f09aa9SHuacai Chen static int create_sysfs_cputemp_files(struct kobject *kobj)
12564f09aa9SHuacai Chen {
12699b0b5a3SHuacai Chen 	int i, ret = 0;
12764f09aa9SHuacai Chen 
12899b0b5a3SHuacai Chen 	for (i = 0; i < nr_packages; i++)
12999b0b5a3SHuacai Chen 		ret = sysfs_create_files(kobj, hwmon_cputemp[i]);
13064f09aa9SHuacai Chen 
13199b0b5a3SHuacai Chen 	return ret;
13264f09aa9SHuacai Chen }
13364f09aa9SHuacai Chen 
13464f09aa9SHuacai Chen static void remove_sysfs_cputemp_files(struct kobject *kobj)
13564f09aa9SHuacai Chen {
13699b0b5a3SHuacai Chen 	int i;
13764f09aa9SHuacai Chen 
13899b0b5a3SHuacai Chen 	for (i = 0; i < nr_packages; i++)
13999b0b5a3SHuacai Chen 		sysfs_remove_files(kobj, hwmon_cputemp[i]);
14064f09aa9SHuacai Chen }
14164f09aa9SHuacai Chen 
14264f09aa9SHuacai Chen #define CPU_THERMAL_THRESHOLD 90000
14364f09aa9SHuacai Chen static struct delayed_work thermal_work;
14464f09aa9SHuacai Chen 
14564f09aa9SHuacai Chen static void do_thermal_timer(struct work_struct *work)
14664f09aa9SHuacai Chen {
14717cbb070STiezhu Yang 	int i, value;
14899b0b5a3SHuacai Chen 
14999b0b5a3SHuacai Chen 	for (i = 0; i < nr_packages; i++) {
15099b0b5a3SHuacai Chen 		value = loongson3_cpu_temp(i);
15117cbb070STiezhu Yang 		if (value > CPU_THERMAL_THRESHOLD) {
15217cbb070STiezhu Yang 			pr_emerg("Power off due to high temp: %d\n", value);
15317cbb070STiezhu Yang 			orderly_poweroff(true);
15417cbb070STiezhu Yang 		}
15599b0b5a3SHuacai Chen 	}
15699b0b5a3SHuacai Chen 
15764f09aa9SHuacai Chen 	schedule_delayed_work(&thermal_work, msecs_to_jiffies(5000));
15864f09aa9SHuacai Chen }
15964f09aa9SHuacai Chen 
16064f09aa9SHuacai Chen static int __init loongson_hwmon_init(void)
16164f09aa9SHuacai Chen {
16264f09aa9SHuacai Chen 	int ret;
16364f09aa9SHuacai Chen 
16464f09aa9SHuacai Chen 	pr_info("Loongson Hwmon Enter...\n");
16564f09aa9SHuacai Chen 
1667507445bSHuacai Chen 	if (cpu_has_csr())
167f17d3f21STiezhu Yang 		csr_temp_enable = csr_readl(LOONGSON_CSR_FEATURES) &
168f17d3f21STiezhu Yang 				  LOONGSON_CSRF_TEMP;
1697507445bSHuacai Chen 
170*f59dc511SZhi Li 	cpu_hwmon_dev = hwmon_device_register_with_info(NULL, "cpu_hwmon", NULL, NULL, NULL);
17164f09aa9SHuacai Chen 	if (IS_ERR(cpu_hwmon_dev)) {
172dece3c2aSTiezhu Yang 		ret = PTR_ERR(cpu_hwmon_dev);
17364f09aa9SHuacai Chen 		pr_err("hwmon_device_register fail!\n");
17464f09aa9SHuacai Chen 		goto fail_hwmon_device_register;
17564f09aa9SHuacai Chen 	}
17664f09aa9SHuacai Chen 
17799b0b5a3SHuacai Chen 	nr_packages = loongson_sysconf.nr_cpus /
17899b0b5a3SHuacai Chen 		loongson_sysconf.cores_per_package;
17999b0b5a3SHuacai Chen 
18064f09aa9SHuacai Chen 	ret = create_sysfs_cputemp_files(&cpu_hwmon_dev->kobj);
18164f09aa9SHuacai Chen 	if (ret) {
182c01e0159SMasanari Iida 		pr_err("fail to create cpu temperature interface!\n");
18364f09aa9SHuacai Chen 		goto fail_create_sysfs_cputemp_files;
18464f09aa9SHuacai Chen 	}
18564f09aa9SHuacai Chen 
18664f09aa9SHuacai Chen 	INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer);
18764f09aa9SHuacai Chen 	schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000));
18864f09aa9SHuacai Chen 
18964f09aa9SHuacai Chen 	return ret;
19064f09aa9SHuacai Chen 
19164f09aa9SHuacai Chen fail_create_sysfs_cputemp_files:
19264f09aa9SHuacai Chen 	sysfs_remove_group(&cpu_hwmon_dev->kobj,
19364f09aa9SHuacai Chen 				&cpu_hwmon_attribute_group);
19464f09aa9SHuacai Chen 	hwmon_device_unregister(cpu_hwmon_dev);
19564f09aa9SHuacai Chen 
19664f09aa9SHuacai Chen fail_hwmon_device_register:
19764f09aa9SHuacai Chen 	return ret;
19864f09aa9SHuacai Chen }
19964f09aa9SHuacai Chen 
20064f09aa9SHuacai Chen static void __exit loongson_hwmon_exit(void)
20164f09aa9SHuacai Chen {
20264f09aa9SHuacai Chen 	cancel_delayed_work_sync(&thermal_work);
20364f09aa9SHuacai Chen 	remove_sysfs_cputemp_files(&cpu_hwmon_dev->kobj);
20464f09aa9SHuacai Chen 	sysfs_remove_group(&cpu_hwmon_dev->kobj,
20564f09aa9SHuacai Chen 				&cpu_hwmon_attribute_group);
20664f09aa9SHuacai Chen 	hwmon_device_unregister(cpu_hwmon_dev);
20764f09aa9SHuacai Chen }
20864f09aa9SHuacai Chen 
20964f09aa9SHuacai Chen module_init(loongson_hwmon_init);
21064f09aa9SHuacai Chen module_exit(loongson_hwmon_exit);
21164f09aa9SHuacai Chen 
21264f09aa9SHuacai Chen MODULE_AUTHOR("Yu Xiang <xiangy@lemote.com>");
21364f09aa9SHuacai Chen MODULE_AUTHOR("Huacai Chen <chenhc@lemote.com>");
21464f09aa9SHuacai Chen MODULE_DESCRIPTION("Loongson CPU Hwmon driver");
21564f09aa9SHuacai Chen MODULE_LICENSE("GPL");
216