ad6ea4b99918fcf286d5a56d4fc79084bc7dce00
[openwrt/staging/jow.git] /
1 From 83a8df1b7fff284fc3c2277c8051f53acde2e64f Mon Sep 17 00:00:00 2001
2 From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= <noralf@tronnes.org>
3 Date: Sat, 24 Feb 2018 13:41:25 +0100
4 Subject: [PATCH 234/454] firmware/raspberrypi: Add a get_throttled sysfs file
5 MIME-Version: 1.0
6 Content-Type: text/plain; charset=UTF-8
7 Content-Transfer-Encoding: 8bit
8
9 Under-voltage due to inadequate power supplies is a recurring problem for
10 new Raspberry Pi users. There are visual indications that an
11 under-voltage situation is occuring like blinking power led and a
12 lightning icon on the desktop (not shown when using the vc4 driver), but
13 for new users it's not obvious that this signifies a critical situation.
14
15 This patch provides a twofold improvement to the situation:
16
17 Firstly it logs under-voltage events to the kernel log. This provides
18 information also for headless installations.
19
20 Secondly it provides a sysfs file to read the value. This improves on
21 'vcgencmd' by providing change notification. Userspace can poll on the
22 file and be notified of changes to the value.
23 A script can poll the file and use dbus notification to put a windows on
24 the desktop with information about the severity with a recommendation to
25 change the power supply. A link to more information can also be provided.
26 Only changes to the sticky bits are reported (cleared between readings).
27
28 Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
29 ---
30 drivers/firmware/raspberrypi.c | 108 +++++++++++++++++++++
31 include/soc/bcm2835/raspberrypi-firmware.h | 1 +
32 2 files changed, 109 insertions(+)
33
34 --- a/drivers/firmware/raspberrypi.c
35 +++ b/drivers/firmware/raspberrypi.c
36 @@ -14,6 +14,7 @@
37 #include <linux/module.h>
38 #include <linux/of_platform.h>
39 #include <linux/platform_device.h>
40 +#include <linux/workqueue.h>
41 #include <soc/bcm2835/raspberrypi-firmware.h>
42
43 #define MBOX_MSG(chan, data28) (((data28) & ~0xf) | ((chan) & 0xf))
44 @@ -21,11 +22,14 @@
45 #define MBOX_DATA28(msg) ((msg) & ~0xf)
46 #define MBOX_CHAN_PROPERTY 8
47
48 +#define UNDERVOLTAGE_BIT BIT(0)
49 +
50 struct rpi_firmware {
51 struct mbox_client cl;
52 struct mbox_chan *chan; /* The property channel. */
53 struct completion c;
54 u32 enabled;
55 + struct delayed_work get_throttled_poll_work;
56 };
57
58 static struct platform_device *g_pdev;
59 @@ -166,6 +170,101 @@ int rpi_firmware_property(struct rpi_fir
60 }
61 EXPORT_SYMBOL_GPL(rpi_firmware_property);
62
63 +static int rpi_firmware_get_throttled(struct rpi_firmware *fw, u32 *value)
64 +{
65 + static ktime_t old_timestamp;
66 + static u32 old_value;
67 + u32 new_sticky, old_sticky, new_uv, old_uv;
68 + ktime_t new_timestamp;
69 + s64 elapsed_ms;
70 + int ret;
71 +
72 + if (!fw)
73 + return -EBUSY;
74 +
75 + /*
76 + * We can't run faster than the sticky shift (100ms) since we get
77 + * flipping in the sticky bits that are cleared.
78 + * This happens on polling, so just return the previous value.
79 + */
80 + new_timestamp = ktime_get();
81 + elapsed_ms = ktime_ms_delta(new_timestamp, old_timestamp);
82 + if (elapsed_ms < 150) {
83 + *value = old_value;
84 + return 0;
85 + }
86 + old_timestamp = new_timestamp;
87 +
88 + /* Clear sticky bits */
89 + *value = 0xffff;
90 +
91 + ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_THROTTLED,
92 + value, sizeof(*value));
93 + if (ret)
94 + return ret;
95 +
96 + new_sticky = *value >> 16;
97 + old_sticky = old_value >> 16;
98 + old_value = *value;
99 +
100 + /* Only notify about changes in the sticky bits */
101 + if (new_sticky == old_sticky)
102 + return 0;
103 +
104 + new_uv = new_sticky & UNDERVOLTAGE_BIT;
105 + old_uv = old_sticky & UNDERVOLTAGE_BIT;
106 +
107 + if (new_uv != old_uv) {
108 + if (new_uv)
109 + pr_crit("Under-voltage detected! (0x%08x)\n", *value);
110 + else
111 + pr_info("Voltage normalised (0x%08x)\n", *value);
112 + }
113 +
114 + sysfs_notify(&fw->cl.dev->kobj, NULL, "get_throttled");
115 +
116 + return 0;
117 +}
118 +
119 +static void get_throttled_poll(struct work_struct *work)
120 +{
121 + struct rpi_firmware *fw = container_of(work, struct rpi_firmware,
122 + get_throttled_poll_work.work);
123 + u32 dummy;
124 + int ret;
125 +
126 + ret = rpi_firmware_get_throttled(fw, &dummy);
127 + if (ret)
128 + pr_debug("%s: Failed to read value (%d)", __func__, ret);
129 +
130 + schedule_delayed_work(&fw->get_throttled_poll_work, 2 * HZ);
131 +}
132 +
133 +static ssize_t get_throttled_show(struct device *dev,
134 + struct device_attribute *attr, char *buf)
135 +{
136 + struct rpi_firmware *fw = dev_get_drvdata(dev);
137 + u32 value;
138 + int ret;
139 +
140 + ret = rpi_firmware_get_throttled(fw, &value);
141 + if (ret)
142 + return ret;
143 +
144 + return sprintf(buf, "%x\n", value);
145 +}
146 +
147 +static DEVICE_ATTR_RO(get_throttled);
148 +
149 +static struct attribute *rpi_firmware_dev_attrs[] = {
150 + &dev_attr_get_throttled.attr,
151 + NULL,
152 +};
153 +
154 +static const struct attribute_group rpi_firmware_dev_group = {
155 + .attrs = rpi_firmware_dev_attrs,
156 +};
157 +
158 static void
159 rpi_firmware_print_firmware_revision(struct rpi_firmware *fw)
160 {
161 @@ -190,6 +289,11 @@ static int rpi_firmware_probe(struct pla
162 {
163 struct device *dev = &pdev->dev;
164 struct rpi_firmware *fw;
165 + int ret;
166 +
167 + ret = devm_device_add_group(dev, &rpi_firmware_dev_group);
168 + if (ret)
169 + return ret;
170
171 fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL);
172 if (!fw)
173 @@ -208,12 +312,15 @@ static int rpi_firmware_probe(struct pla
174 }
175
176 init_completion(&fw->c);
177 + INIT_DELAYED_WORK(&fw->get_throttled_poll_work, get_throttled_poll);
178
179 platform_set_drvdata(pdev, fw);
180 g_pdev = pdev;
181
182 rpi_firmware_print_firmware_revision(fw);
183
184 + schedule_delayed_work(&fw->get_throttled_poll_work, 0);
185 +
186 return 0;
187 }
188
189 @@ -221,6 +328,7 @@ static int rpi_firmware_remove(struct pl
190 {
191 struct rpi_firmware *fw = platform_get_drvdata(pdev);
192
193 + cancel_delayed_work_sync(&fw->get_throttled_poll_work);
194 mbox_free_channel(fw->chan);
195 g_pdev = NULL;
196
197 --- a/include/soc/bcm2835/raspberrypi-firmware.h
198 +++ b/include/soc/bcm2835/raspberrypi-firmware.h
199 @@ -77,6 +77,7 @@ enum rpi_firmware_property_tag {
200 RPI_FIRMWARE_GET_EDID_BLOCK = 0x00030020,
201 RPI_FIRMWARE_GET_CUSTOMER_OTP = 0x00030021,
202 RPI_FIRMWARE_GET_DOMAIN_STATE = 0x00030030,
203 + RPI_FIRMWARE_GET_THROTTLED = 0x00030046,
204 RPI_FIRMWARE_SET_CLOCK_STATE = 0x00038001,
205 RPI_FIRMWARE_SET_CLOCK_RATE = 0x00038002,
206 RPI_FIRMWARE_SET_VOLTAGE = 0x00038003,