aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/brcm2708/patches-4.14/950-0234-firmware-raspberrypi-Add-a-get_throttled-sysfs-file.patch
blob: ad6ea4b99918fcf286d5a56d4fc79084bc7dce00 (plain)
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
From 83a8df1b7fff284fc3c2277c8051f53acde2e64f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= <noralf@tronnes.org>
Date: Sat, 24 Feb 2018 13:41:25 +0100
Subject: [PATCH 234/454] firmware/raspberrypi: Add a get_throttled sysfs file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Under-voltage due to inadequate power supplies is a recurring problem for
new Raspberry Pi users. There are visual indications that an
under-voltage situation is occuring like blinking power led and a
lightning icon on the desktop (not shown when using the vc4 driver), but
for new users it's not obvious that this signifies a critical situation.

This patch provides a twofold improvement to the situation:

Firstly it logs under-voltage events to the kernel log. This provides
information also for headless installations.

Secondly it provides a sysfs file to read the value. This improves on
'vcgencmd' by providing change notification. Userspace can poll on the
file and be notified of changes to the value.
A script can poll the file and use dbus notification to put a windows on
the desktop with information about the severity with a recommendation to
change the power supply. A link to more information can also be provided.
Only changes to the sticky bits are reported (cleared between readings).

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/firmware/raspberrypi.c             | 108 +++++++++++++++++++++
 include/soc/bcm2835/raspberrypi-firmware.h |   1 +
 2 files changed, 109 insertions(+)

--- a/drivers/firmware/raspberrypi.c
+++ b/drivers/firmware/raspberrypi.c
@@ -14,6 +14,7 @@
 #include <linux/module.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/workqueue.h>
 #include <soc/bcm2835/raspberrypi-firmware.h>
 
 #define MBOX_MSG(chan, data28)		(((data28) & ~0xf) | ((chan) & 0xf))
@@ -21,11 +22,14 @@
 #define MBOX_DATA28(msg)		((msg) & ~0xf)
 #define MBOX_CHAN_PROPERTY		8
 
+#define UNDERVOLTAGE_BIT		BIT(0)
+
 struct rpi_firmware {
 	struct mbox_client cl;
 	struct mbox_chan *chan; /* The property channel. */
 	struct completion c;
 	u32 enabled;
+	struct delayed_work get_throttled_poll_work;
 };
 
 static struct platform_device *g_pdev;
@@ -166,6 +170,101 @@ int rpi_firmware_property(struct rpi_fir
 }
 EXPORT_SYMBOL_GPL(rpi_firmware_property);
 
+static int rpi_firmware_get_throttled(struct rpi_firmware *fw, u32 *value)
+{
+	static ktime_t old_timestamp;
+	static u32 old_value;
+	u32 new_sticky, old_sticky, new_uv, old_uv;
+	ktime_t new_timestamp;
+	s64 elapsed_ms;
+	int ret;
+
+	if (!fw)
+		return -EBUSY;
+
+	/*
+	 * We can't run faster than the sticky shift (100ms) since we get
+	 * flipping in the sticky bits that are cleared.
+	 * This happens on polling, so just return the previous value.
+	 */
+	new_timestamp = ktime_get();
+	elapsed_ms = ktime_ms_delta(new_timestamp, old_timestamp);
+	if (elapsed_ms < 150) {
+		*value = old_value;
+		return 0;
+	}
+	old_timestamp = new_timestamp;
+
+	/* Clear sticky bits */
+	*value = 0xffff;
+
+	ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_THROTTLED,
+				    value, sizeof(*value));
+	if (ret)
+		return ret;
+
+	new_sticky = *value >> 16;
+	old_sticky = old_value >> 16;
+	old_value = *value;
+
+	/* Only notify about changes in the sticky bits */
+	if (new_sticky == old_sticky)
+		return 0;
+
+	new_uv = new_sticky & UNDERVOLTAGE_BIT;
+	old_uv = old_sticky & UNDERVOLTAGE_BIT;
+
+	if (new_uv != old_uv) {
+		if (new_uv)
+			pr_crit("Under-voltage detected! (0x%08x)\n", *value);
+		else
+			pr_info("Voltage normalised (0x%08x)\n", *value);
+	}
+
+	sysfs_notify(&fw->cl.dev->kobj, NULL, "get_throttled");
+
+	return 0;
+}
+
+static void get_throttled_poll(struct work_struct *work)
+{
+	struct rpi_firmware *fw = container_of(work, struct rpi_firmware,
+					       get_throttled_poll_work.work);
+	u32 dummy;
+	int ret;
+
+	ret = rpi_firmware_get_throttled(fw, &dummy);
+	if (ret)
+		pr_debug("%s: Failed to read value (%d)", __func__, ret);
+
+	schedule_delayed_work(&fw->get_throttled_poll_work, 2 * HZ);
+}
+
+static ssize_t get_throttled_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct rpi_firmware *fw = dev_get_drvdata(dev);
+	u32 value;
+	int ret;
+
+	ret = rpi_firmware_get_throttled(fw, &value);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%x\n", value);
+}
+
+static DEVICE_ATTR_RO(get_throttled);
+
+static struct attribute *rpi_firmware_dev_attrs[] = {
+	&dev_attr_get_throttled.attr,
+	NULL,
+};
+
+static const struct attribute_group rpi_firmware_dev_group = {
+	.attrs = rpi_firmware_dev_attrs,
+};
+
 static void
 rpi_firmware_print_firmware_revision(struct rpi_firmware *fw)
 {
@@ -190,6 +289,11 @@ static int rpi_firmware_probe(struct pla
 {
 	struct device *dev = &pdev->dev;
 	struct rpi_firmware *fw;
+	int ret;
+
+	ret = devm_device_add_group(dev, &rpi_firmware_dev_group);
+	if (ret)
+		return ret;
 
 	fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL);
 	if (!fw)
@@ -208,12 +312,15 @@ static int rpi_firmware_probe(struct pla
 	}
 
 	init_completion(&fw->c);
+	INIT_DELAYED_WORK(&fw->get_throttled_poll_work, get_throttled_poll);
 
 	platform_set_drvdata(pdev, fw);
 	g_pdev = pdev;
 
 	rpi_firmware_print_firmware_revision(fw);
 
+	schedule_delayed_work(&fw->get_throttled_poll_work, 0);
+
 	return 0;
 }
 
@@ -221,6 +328,7 @@ static int rpi_firmware_remove(struct pl
 {
 	struct rpi_firmware *fw = platform_get_drvdata(pdev);
 
+	cancel_delayed_work_sync(&fw->get_throttled_poll_work);
 	mbox_free_channel(fw->chan);
 	g_pdev = NULL;
 
--- a/include/soc/bcm2835/raspberrypi-firmware.h
+++ b/include/soc/bcm2835/raspberrypi-firmware.h
@@ -77,6 +77,7 @@ enum rpi_firmware_property_tag {
 	RPI_FIRMWARE_GET_EDID_BLOCK =                         0x00030020,
 	RPI_FIRMWARE_GET_CUSTOMER_OTP =                       0x00030021,
 	RPI_FIRMWARE_GET_DOMAIN_STATE =                       0x00030030,
+	RPI_FIRMWARE_GET_THROTTLED =                          0x00030046,
 	RPI_FIRMWARE_SET_CLOCK_STATE =                        0x00038001,
 	RPI_FIRMWARE_SET_CLOCK_RATE =                         0x00038002,
 	RPI_FIRMWARE_SET_VOLTAGE =                            0x00038003,