ad6ea4b99918fcf286d5a56d4fc79084bc7dce00
[oweals/openwrt.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,