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