Commit | Line | Data |
---|---|---|
64f09aa9 HC |
1 | #include <linux/err.h> |
2 | #include <linux/module.h> | |
3 | #include <linux/reboot.h> | |
4 | #include <linux/jiffies.h> | |
5 | #include <linux/hwmon.h> | |
6 | #include <linux/hwmon-sysfs.h> | |
7 | ||
8 | #include <loongson.h> | |
9 | #include <boot_param.h> | |
10 | #include <loongson_hwmon.h> | |
11 | ||
12 | /* | |
13 | * Loongson-3 series cpu has two sensors inside, | |
14 | * each of them from 0 to 255, | |
15 | * if more than 127, that is dangerous. | |
16 | * here only provide sensor1 data, because it always hot than sensor0 | |
17 | */ | |
18 | int loongson3_cpu_temp(int cpu) | |
19 | { | |
20 | u32 reg; | |
21 | ||
22 | reg = LOONGSON_CHIPTEMP(cpu); | |
b2edcfc8 | 23 | if ((read_c0_prid() & PRID_REV_MASK) == PRID_REV_LOONGSON3A_R1) |
64f09aa9 | 24 | reg = (reg >> 8) & 0xff; |
b2edcfc8 | 25 | else |
64f09aa9 HC |
26 | reg = ((reg >> 8) & 0xff) - 100; |
27 | ||
28 | return (int)reg * 1000; | |
29 | } | |
30 | ||
31 | static struct device *cpu_hwmon_dev; | |
32 | ||
33 | static ssize_t get_hwmon_name(struct device *dev, | |
34 | struct device_attribute *attr, char *buf); | |
35 | static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0); | |
36 | ||
37 | static struct attribute *cpu_hwmon_attributes[] = { | |
38 | &sensor_dev_attr_name.dev_attr.attr, | |
39 | NULL | |
40 | }; | |
41 | ||
42 | /* Hwmon device attribute group */ | |
43 | static struct attribute_group cpu_hwmon_attribute_group = { | |
44 | .attrs = cpu_hwmon_attributes, | |
45 | }; | |
46 | ||
47 | /* Hwmon device get name */ | |
48 | static ssize_t get_hwmon_name(struct device *dev, | |
49 | struct device_attribute *attr, char *buf) | |
50 | { | |
51 | return sprintf(buf, "cpu-hwmon\n"); | |
52 | } | |
53 | ||
54 | static ssize_t get_cpu0_temp(struct device *dev, | |
55 | struct device_attribute *attr, char *buf); | |
56 | static ssize_t get_cpu1_temp(struct device *dev, | |
57 | struct device_attribute *attr, char *buf); | |
58 | static ssize_t cpu0_temp_label(struct device *dev, | |
59 | struct device_attribute *attr, char *buf); | |
60 | static ssize_t cpu1_temp_label(struct device *dev, | |
61 | struct device_attribute *attr, char *buf); | |
62 | ||
63 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_cpu0_temp, NULL, 1); | |
64 | static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, cpu0_temp_label, NULL, 1); | |
65 | static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, get_cpu1_temp, NULL, 2); | |
66 | static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, cpu1_temp_label, NULL, 2); | |
67 | ||
68 | static const struct attribute *hwmon_cputemp1[] = { | |
69 | &sensor_dev_attr_temp1_input.dev_attr.attr, | |
70 | &sensor_dev_attr_temp1_label.dev_attr.attr, | |
71 | NULL | |
72 | }; | |
73 | ||
74 | static const struct attribute *hwmon_cputemp2[] = { | |
75 | &sensor_dev_attr_temp2_input.dev_attr.attr, | |
76 | &sensor_dev_attr_temp2_label.dev_attr.attr, | |
77 | NULL | |
78 | }; | |
79 | ||
80 | static ssize_t cpu0_temp_label(struct device *dev, | |
81 | struct device_attribute *attr, char *buf) | |
82 | { | |
c01e0159 | 83 | return sprintf(buf, "CPU 0 Temperature\n"); |
64f09aa9 HC |
84 | } |
85 | ||
86 | static ssize_t cpu1_temp_label(struct device *dev, | |
87 | struct device_attribute *attr, char *buf) | |
88 | { | |
c01e0159 | 89 | return sprintf(buf, "CPU 1 Temperature\n"); |
64f09aa9 HC |
90 | } |
91 | ||
92 | static ssize_t get_cpu0_temp(struct device *dev, | |
93 | struct device_attribute *attr, char *buf) | |
94 | { | |
95 | int value = loongson3_cpu_temp(0); | |
96 | return sprintf(buf, "%d\n", value); | |
97 | } | |
98 | ||
99 | static ssize_t get_cpu1_temp(struct device *dev, | |
100 | struct device_attribute *attr, char *buf) | |
101 | { | |
102 | int value = loongson3_cpu_temp(1); | |
103 | return sprintf(buf, "%d\n", value); | |
104 | } | |
105 | ||
106 | static int create_sysfs_cputemp_files(struct kobject *kobj) | |
107 | { | |
108 | int ret; | |
109 | ||
110 | ret = sysfs_create_files(kobj, hwmon_cputemp1); | |
111 | if (ret) | |
112 | goto sysfs_create_temp1_fail; | |
113 | ||
114 | if (loongson_sysconf.nr_cpus <= loongson_sysconf.cores_per_package) | |
115 | return 0; | |
116 | ||
117 | ret = sysfs_create_files(kobj, hwmon_cputemp2); | |
118 | if (ret) | |
119 | goto sysfs_create_temp2_fail; | |
120 | ||
121 | return 0; | |
122 | ||
123 | sysfs_create_temp2_fail: | |
124 | sysfs_remove_files(kobj, hwmon_cputemp1); | |
125 | ||
126 | sysfs_create_temp1_fail: | |
127 | return -1; | |
128 | } | |
129 | ||
130 | static void remove_sysfs_cputemp_files(struct kobject *kobj) | |
131 | { | |
132 | sysfs_remove_files(&cpu_hwmon_dev->kobj, hwmon_cputemp1); | |
133 | ||
134 | if (loongson_sysconf.nr_cpus > loongson_sysconf.cores_per_package) | |
135 | sysfs_remove_files(&cpu_hwmon_dev->kobj, hwmon_cputemp2); | |
136 | } | |
137 | ||
138 | #define CPU_THERMAL_THRESHOLD 90000 | |
139 | static struct delayed_work thermal_work; | |
140 | ||
141 | static void do_thermal_timer(struct work_struct *work) | |
142 | { | |
143 | int value = loongson3_cpu_temp(0); | |
144 | if (value <= CPU_THERMAL_THRESHOLD) | |
145 | schedule_delayed_work(&thermal_work, msecs_to_jiffies(5000)); | |
146 | else | |
147 | orderly_poweroff(true); | |
148 | } | |
149 | ||
150 | static int __init loongson_hwmon_init(void) | |
151 | { | |
152 | int ret; | |
153 | ||
154 | pr_info("Loongson Hwmon Enter...\n"); | |
155 | ||
156 | cpu_hwmon_dev = hwmon_device_register(NULL); | |
157 | if (IS_ERR(cpu_hwmon_dev)) { | |
158 | ret = -ENOMEM; | |
159 | pr_err("hwmon_device_register fail!\n"); | |
160 | goto fail_hwmon_device_register; | |
161 | } | |
162 | ||
163 | ret = sysfs_create_group(&cpu_hwmon_dev->kobj, | |
164 | &cpu_hwmon_attribute_group); | |
165 | if (ret) { | |
166 | pr_err("fail to create loongson hwmon!\n"); | |
167 | goto fail_sysfs_create_group_hwmon; | |
168 | } | |
169 | ||
170 | ret = create_sysfs_cputemp_files(&cpu_hwmon_dev->kobj); | |
171 | if (ret) { | |
c01e0159 | 172 | pr_err("fail to create cpu temperature interface!\n"); |
64f09aa9 HC |
173 | goto fail_create_sysfs_cputemp_files; |
174 | } | |
175 | ||
176 | INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer); | |
177 | schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000)); | |
178 | ||
179 | return ret; | |
180 | ||
181 | fail_create_sysfs_cputemp_files: | |
182 | sysfs_remove_group(&cpu_hwmon_dev->kobj, | |
183 | &cpu_hwmon_attribute_group); | |
184 | ||
185 | fail_sysfs_create_group_hwmon: | |
186 | hwmon_device_unregister(cpu_hwmon_dev); | |
187 | ||
188 | fail_hwmon_device_register: | |
189 | return ret; | |
190 | } | |
191 | ||
192 | static void __exit loongson_hwmon_exit(void) | |
193 | { | |
194 | cancel_delayed_work_sync(&thermal_work); | |
195 | remove_sysfs_cputemp_files(&cpu_hwmon_dev->kobj); | |
196 | sysfs_remove_group(&cpu_hwmon_dev->kobj, | |
197 | &cpu_hwmon_attribute_group); | |
198 | hwmon_device_unregister(cpu_hwmon_dev); | |
199 | } | |
200 | ||
201 | module_init(loongson_hwmon_init); | |
202 | module_exit(loongson_hwmon_exit); | |
203 | ||
204 | MODULE_AUTHOR("Yu Xiang <xiangy@lemote.com>"); | |
205 | MODULE_AUTHOR("Huacai Chen <chenhc@lemote.com>"); | |
206 | MODULE_DESCRIPTION("Loongson CPU Hwmon driver"); | |
207 | MODULE_LICENSE("GPL"); |