forked from openbmc/linux
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathaspeed-chassis.c
More file actions
267 lines (216 loc) · 8.48 KB
/
aspeed-chassis.c
File metadata and controls
267 lines (216 loc) · 8.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 ASPEED Technology Inc.
*
* CHASSIS driver for the Aspeed SoC
*/
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
#include <linux/interrupt.h>
//#define USE_INTERRUPTS
/******************************************************************************/
union chassis_ctrl_register {
u32 value;
struct {
uint32_t intrusion_status_clear : 1; /*[0]*/
uint32_t intrusion_int_enable : 1; /*[1]*/
uint32_t intrusion_status : 1; /*[2]*/
uint32_t battery_power_good : 1; /*[3]*/
uint32_t chassis_raw_status : 1; /*[4]*/
uint32_t reserved0 : 3; /*[5-7]*/
uint32_t io_power_status_clear : 1; /*[8]*/
uint32_t io_power_int_enable : 1; /*[9]*/
uint32_t core_power_status : 1; /*[10]*/
uint32_t reserved1 : 5; /*[11-15]*/
uint32_t core_power_status_clear : 1; /*[16]*/
uint32_t core_power_int_enable : 1; /*[17]*/
uint32_t io_power_status : 1; /*[18]*/
uint32_t reserved2 : 13; /*[19-31]*/
} fields;
};
struct aspeed_chassis {
struct device *dev;
void __iomem *base;
int irq;
/* for hwmon */
const struct attribute_group *groups[2];
/* for workqueue */
struct work_struct chassis_work;
};
static ssize_t
intrusion_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long val;
struct aspeed_chassis *chassis = dev_get_drvdata(dev);
union chassis_ctrl_register chassis_ctrl;
if (kstrtoul(buf, 10, &val) < 0 || val != 0)
return -EINVAL;
chassis_ctrl.value = readl(chassis->base);
chassis_ctrl.fields.intrusion_status_clear = 1;
writel(chassis_ctrl.value, chassis->base);
chassis_ctrl.fields.intrusion_status_clear = 0;
writel(chassis_ctrl.value, chassis->base);
return count;
}
static ssize_t intrusion_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
int index = sensor_attr->index;
struct aspeed_chassis *chassis = dev_get_drvdata(dev);
union chassis_ctrl_register chassis_ctrl;
u8 ret;
chassis_ctrl.value = readl(chassis->base);
switch (index) {
case 0:
ret = chassis_ctrl.fields.core_power_status;
break;
case 1:
ret = chassis_ctrl.fields.io_power_status;
break;
case 2:
ret = chassis_ctrl.fields.intrusion_status;
break;
}
return sprintf(buf, "%d\n", ret);
}
static SENSOR_DEVICE_ATTR_RO(core_power, intrusion, 0);
static SENSOR_DEVICE_ATTR_RO(io_power, intrusion, 1);
static SENSOR_DEVICE_ATTR_RW(intrusion0_alarm, intrusion, 2);
static struct attribute *intrusion_dev_attrs[] = {
&sensor_dev_attr_core_power.dev_attr.attr,
&sensor_dev_attr_io_power.dev_attr.attr,
&sensor_dev_attr_intrusion0_alarm.dev_attr.attr, NULL
};
static const struct attribute_group intrusion_dev_group = {
.attrs = intrusion_dev_attrs,
.is_visible = NULL,
};
#ifdef USE_INTERRUPTS
/*
The reason that I have kept some printing in aspeed_chassis_status_check, (even though there should not be printing in the ISR) is because when you enable interrupts, you get the interrupt storm, and so the workqueue will not execute until after the
interrupt storm has stopped (which occurs when the chassis is closed), and at that point printing out the 'DEBUG' statement would be invalid because the intrusion status might already be cleared.
*/
static void aspeed_chassis_status_print(struct aspeed_chassis *chassis) {
union chassis_ctrl_register chassis_ctrl;
chassis_ctrl.value = readl(chassis->base);
dev_info(chassis->dev, "DEBUG: chassis_ctrl.value=0x%x, intrusion_status_clear=%d, intrusion_int_enable=%d, intrusion_status=%d", chassis_ctrl.value, chassis_ctrl.fields.intrusion_status_clear, chassis_ctrl.fields.intrusion_int_enable, chassis_ctrl.fields.intrusion_status);
if (chassis_ctrl.fields.intrusion_status) {
dev_info(chassis->dev, "CHASSIS pin has been pulled low");
}
if (chassis_ctrl.fields.core_power_status) {
dev_info(chassis->dev, "Core power has been pulled low");
}
if (chassis_ctrl.fields.io_power_status) {
dev_info(chassis->dev, "IO power has been pulled low");
}
}
static void aspeed_chassis_status_check(struct aspeed_chassis *chassis)
{
union chassis_ctrl_register chassis_ctrl;
chassis_ctrl.value = readl(chassis->base);
dev_info(chassis->dev, "DEBUG: chassis_ctrl.value=0x%x, intrusion_status_clear=%d, intrusion_int_enable=%d, intrusion_status=%d", chassis_ctrl.value, chassis_ctrl.fields.intrusion_status_clear, chassis_ctrl.fields.intrusion_int_enable, chassis_ctrl.fields.intrusion_status);
if (chassis_ctrl.fields.intrusion_status) {
chassis_ctrl.fields.intrusion_status_clear = 1;
writel(chassis_ctrl.value, chassis->base);
dev_info(chassis->dev, "clear = 1: chassis_ctrl.value=0x%x, intrusion_status_clear=%d, intrusion_int_enable=%d, intrusion_status=%d", chassis_ctrl.value, chassis_ctrl.fields.intrusion_status_clear, chassis_ctrl.fields.intrusion_int_enable, chassis_ctrl.fields.intrusion_status);
chassis_ctrl.fields.intrusion_status_clear = 0;
writel(chassis_ctrl.value, chassis->base);
dev_info(chassis->dev, "clear = 0: chassis_ctrl.value=0x%x, intrusion_status_clear=%d, intrusion_int_enable=%d, intrusion_status=%d", chassis_ctrl.value, chassis_ctrl.fields.intrusion_status_clear, chassis_ctrl.fields.intrusion_int_enable, chassis_ctrl.fields.intrusion_status);
}
if (chassis_ctrl.fields.core_power_status) {
chassis_ctrl.fields.core_power_status_clear = 1;
writel(chassis_ctrl.value, chassis->base);
chassis_ctrl.fields.core_power_status_clear = 0;
writel(chassis_ctrl.value, chassis->base);
}
if (chassis_ctrl.fields.io_power_status) {
chassis_ctrl.fields.io_power_status_clear = 1;
writel(chassis_ctrl.value, chassis->base);
chassis_ctrl.fields.io_power_status_clear = 0;
writel(chassis_ctrl.value, chassis->base);
}
}
static void chassis_work_handler(struct work_struct *chassis_work) {
struct aspeed_chassis *chassis = container_of(chassis_work, struct aspeed_chassis, chassis_work);
aspeed_chassis_status_print(chassis);
}
static irqreturn_t aspeed_chassis_isr(int this_irq, void *dev_id)
{
struct aspeed_chassis *chassis = dev_id;
aspeed_chassis_status_check(chassis);
schedule_work(&chassis->chassis_work);
return IRQ_HANDLED;
}
#endif
static void aspeed_chassis_int_ctrl(struct aspeed_chassis *chassis, bool ctrl)
{
union chassis_ctrl_register chassis_ctrl;
chassis_ctrl.value = readl(chassis->base);
chassis_ctrl.fields.intrusion_int_enable = ctrl;
chassis_ctrl.fields.io_power_int_enable = ctrl;
chassis_ctrl.fields.core_power_int_enable = ctrl;
writel(chassis_ctrl.value, chassis->base);
}
static const struct of_device_id aspeed_chassis_of_table[] = {
{ .compatible = "aspeed,ast2700-chassis" },
{}
};
MODULE_DEVICE_TABLE(of, aspeed_chassis_of_table);
static int aspeed_chassis_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct aspeed_chassis *priv;
struct device *hwmon;
int __maybe_unused ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
#ifdef USE_INTERRUPTS
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) {
dev_err(dev, "no irq specified\n");
return -ENOENT;
}
ret = devm_request_irq(dev, priv->irq, aspeed_chassis_isr, 0,
dev_name(dev), priv);
if (ret) {
dev_err(dev, "Chassis Unable to get IRQ");
return ret;
}
aspeed_chassis_int_ctrl(priv, true);
#else
aspeed_chassis_int_ctrl(priv, false);
#endif
INIT_WORK(&priv->chassis_work, chassis_work_handler);
priv->groups[0] = &intrusion_dev_group;
priv->groups[1] = NULL;
hwmon = devm_hwmon_device_register_with_groups(dev, "aspeed_chassis",
priv, priv->groups);
return PTR_ERR_OR_ZERO(hwmon);
}
static struct platform_driver aspeed_chassis_driver = {
.probe = aspeed_chassis_probe,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = aspeed_chassis_of_table,
},
};
module_platform_driver(aspeed_chassis_driver);
MODULE_AUTHOR("Billy Tsai<billy_tsai@aspeedtech.com>");
MODULE_DESCRIPTION("ASPEED CHASSIS Driver");
MODULE_LICENSE("GPL");