@@ -15,6 +15,8 @@
#include <linux/firmware/samsung/exynos-acpm-protocol.h>
#include <linux/io.h>
#include <linux/iopoll.h>
+#include <linux/irqflags.h>
+#include <linux/kernel.h>
#include <linux/mailbox/exynos-message.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
@@ -24,6 +26,7 @@
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
+#include <linux/preempt.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -272,6 +275,17 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
return 0;
}
+/*
+ * When ACPM transfers happen very late, e.g. to access a PMIC when powering
+ * down, we can not sleep. We do want to sleep in the normal case, though, to
+ * avoid wasting CPU cycles!
+ */
+static bool acpm_may_sleep(void)
+{
+ return system_state <= SYSTEM_RUNNING ||
+ (IS_ENABLED(CONFIG_PREEMPT_COUNT) ? preemptible() : !irqs_disabled());
+}
+
/**
* acpm_dequeue_by_polling() - RX dequeue by polling.
* @achan: ACPM channel info.
@@ -299,7 +313,10 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan,
return 0;
/* Determined experimentally. */
- usleep_range(20, 30);
+ if (!acpm_may_sleep())
+ udelay(10);
+ else
+ usleep_range(20, 30);
} while (!ktime_after(ktime_get(), timeout));
dev_err(dev, "Timeout! ch:%u s:%u bitmap:%lx.\n",
We need to access the PMIC during late system shutdown and at that time we are not allowed to sleep anymore. To make this case work, detect this condition and use busy waiting via udelay() instead of usleep_range() in that situation. The code isn't switched over to udelay() unconditionally so as to not waste resources during normal operation. acpm_may_sleep() was heavily inspired by the I2C subsystem's i2c_in_atomic_xfer_mode(). Signed-off-by: André Draszik <andre.draszik@linaro.org> --- udelay(10) causes a checkpatch warning (it suggests to use usleep_range() instead for usec >= 10), but that's exactly what we can not do. Reducing the udelay to be smaller will generally cause the loop to be iterated more than once, which I wanted to avoid. I could reflow the code to hide the actual value from checkpatch, e.g. with the help of a local variable if that is preferred to ignoring the checkpatch warning. --- drivers/firmware/samsung/exynos-acpm.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-)