[PATCH] auxdisplay: arm-charlcd: propagate ready wait failures
From: Pengpeng Hou
Date: Tue Jun 23 2026 - 09:53:35 EST
The 4-bit read path waits for CHAR_RAW_VALID before reading each nibble,
but the poll/IRQ wait results were ignored. A timeout could therefore
still assemble a byte from CHAR_RD and feed it into the busy-flag path
as if valid data had arrived.
Return errors from the ready waits and propagate them through the local
4-bit helpers. Initialization logs delayed-work failures, while suspend
and resume return the command error to the PM core.
Fixes: ce8962455e90 ("ARM: 6214/2: driver for the character LCD found in ARM refdesigns")
Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
drivers/auxdisplay/arm-charlcd.c | 136 +++++++++++++++++++++----------
1 file changed, 93 insertions(+), 43 deletions(-)
diff --git a/drivers/auxdisplay/arm-charlcd.c b/drivers/auxdisplay/arm-charlcd.c
index 30fd2341c..36a5f396a 100644
--- a/drivers/auxdisplay/arm-charlcd.c
+++ b/drivers/auxdisplay/arm-charlcd.c
@@ -88,7 +88,7 @@ static irqreturn_t charlcd_interrupt(int irq, void *data)
}
-static void charlcd_wait_complete_irq(struct charlcd *lcd)
+static int charlcd_wait_complete_irq(struct charlcd *lcd)
{
int ret;
@@ -101,52 +101,63 @@ static void charlcd_wait_complete_irq(struct charlcd *lcd)
dev_err(lcd->dev,
"wait_for_completion_interruptible_timeout() returned %d waiting for ready\n",
ret);
- return;
+ return ret;
}
if (ret == 0) {
dev_err(lcd->dev, "charlcd controller timed out waiting for ready\n");
- return;
+ return -ETIMEDOUT;
}
+
+ return 0;
}
-static u8 charlcd_4bit_read_char(struct charlcd *lcd)
+static int charlcd_4bit_read_char(struct charlcd *lcd, u8 *data)
{
- u8 data;
u32 val;
+ int ret;
/* If we can, use an IRQ to wait for the data, else poll */
- if (lcd->irq >= 0)
- charlcd_wait_complete_irq(lcd);
- else {
+ if (lcd->irq >= 0) {
+ ret = charlcd_wait_complete_irq(lcd);
+ if (ret)
+ return ret;
+ } else {
udelay(100);
- readl_poll_timeout_atomic(lcd->virtbase + CHAR_RAW, val,
- val & CHAR_RAW_VALID, 100, 1000);
+ ret = readl_poll_timeout_atomic(lcd->virtbase + CHAR_RAW, val,
+ val & CHAR_RAW_VALID, 100, 1000);
writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
+ if (ret)
+ return ret;
}
msleep(1);
/* Read the 4 high bits of the data */
- data = readl(lcd->virtbase + CHAR_RD) & 0xf0;
+ *data = readl(lcd->virtbase + CHAR_RD) & 0xf0;
/*
* The second read for the low bits does not trigger an IRQ
* so in this case we have to poll for the 4 lower bits
*/
udelay(100);
- readl_poll_timeout_atomic(lcd->virtbase + CHAR_RAW, val,
- val & CHAR_RAW_VALID, 100, 1000);
+ ret = readl_poll_timeout_atomic(lcd->virtbase + CHAR_RAW, val,
+ val & CHAR_RAW_VALID, 100, 1000);
writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
+ if (ret)
+ return ret;
msleep(1);
/* Read the 4 low bits of the data */
- data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f;
+ *data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f;
- return data;
+ return 0;
}
-static bool charlcd_4bit_read_bf(struct charlcd *lcd)
+static int charlcd_4bit_read_bf(struct charlcd *lcd, bool *busy)
{
+ u8 data;
+ int ret;
+
if (lcd->irq >= 0) {
/*
* If we'll use IRQs to wait for the busyflag, clear any
@@ -158,21 +169,34 @@ static bool charlcd_4bit_read_bf(struct charlcd *lcd)
}
readl(lcd->virtbase + CHAR_COM);
- return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG;
+ ret = charlcd_4bit_read_char(lcd, &data);
+ if (ret)
+ return ret;
+
+ *busy = data & HD_BUSY_FLAG;
+
+ return 0;
}
-static void charlcd_4bit_wait_busy(struct charlcd *lcd)
+static int charlcd_4bit_wait_busy(struct charlcd *lcd)
{
+ bool busy = true;
int retries = 50;
+ int ret;
udelay(100);
- while (charlcd_4bit_read_bf(lcd) && retries)
+ while (retries--) {
+ ret = charlcd_4bit_read_bf(lcd, &busy);
+ if (ret)
+ return ret;
+ if (!busy)
+ return 0;
- retries--;
- if (!retries)
+ }
dev_err(lcd->dev, "timeout waiting for busyflag\n");
+ return -ETIMEDOUT;
}
-static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd)
+static int charlcd_4bit_command(struct charlcd *lcd, u8 cmd)
{
u32 cmdlo = (cmd << 4) & 0xf0;
u32 cmdhi = (cmd & 0xf0);
@@ -180,10 +207,10 @@ static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd)
writel(cmdhi, lcd->virtbase + CHAR_COM);
udelay(10);
writel(cmdlo, lcd->virtbase + CHAR_COM);
- charlcd_4bit_wait_busy(lcd);
+ return charlcd_4bit_wait_busy(lcd);
}
-static void charlcd_4bit_char(struct charlcd *lcd, u8 ch)
+static int charlcd_4bit_char(struct charlcd *lcd, u8 ch)
{
u32 chlo = (ch << 4) & 0xf0;
u32 chhi = (ch & 0xf0);
@@ -191,13 +218,13 @@ static void charlcd_4bit_char(struct charlcd *lcd, u8 ch)
writel(chhi, lcd->virtbase + CHAR_DAT);
udelay(10);
writel(chlo, lcd->virtbase + CHAR_DAT);
- charlcd_4bit_wait_busy(lcd);
+ return charlcd_4bit_wait_busy(lcd);
}
-static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str)
+static int charlcd_4bit_print(struct charlcd *lcd, int line, const char *str)
{
u8 offset;
- int i;
+ int i, ret;
/*
* We support line 0, 1
@@ -209,18 +236,27 @@ static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str)
else if (line == 1)
offset = 0x28;
else
- return;
+ return -EINVAL;
/* Set offset */
- charlcd_4bit_command(lcd, HD_SET_DDRAM | offset);
+ ret = charlcd_4bit_command(lcd, HD_SET_DDRAM | offset);
+ if (ret)
+ return ret;
/* Send string */
- for (i = 0; i < strlen(str) && i < 0x28; i++)
- charlcd_4bit_char(lcd, str[i]);
+ for (i = 0; i < strlen(str) && i < 0x28; i++) {
+ ret = charlcd_4bit_char(lcd, str[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
-static void charlcd_4bit_init(struct charlcd *lcd)
+static int charlcd_4bit_init(struct charlcd *lcd)
{
+ int ret;
+
/* These commands cannot be checked with the busy flag */
writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM);
msleep(5);
@@ -235,22 +271,38 @@ static void charlcd_4bit_init(struct charlcd *lcd)
* 4bit mode, 2 lines, 5x8 font, after this the number of lines
* and the font cannot be changed until the next initialization sequence
*/
- charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES);
- charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
- charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT);
- charlcd_4bit_command(lcd, HD_CLEAR);
- charlcd_4bit_command(lcd, HD_HOME);
+ ret = charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES);
+ if (ret)
+ return ret;
+ ret = charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
+ if (ret)
+ return ret;
+ ret = charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT);
+ if (ret)
+ return ret;
+ ret = charlcd_4bit_command(lcd, HD_CLEAR);
+ if (ret)
+ return ret;
+ ret = charlcd_4bit_command(lcd, HD_HOME);
+ if (ret)
+ return ret;
/* Put something useful in the display */
- charlcd_4bit_print(lcd, 0, "ARM Linux");
- charlcd_4bit_print(lcd, 1, UTS_RELEASE);
+ ret = charlcd_4bit_print(lcd, 0, "ARM Linux");
+ if (ret)
+ return ret;
+
+ return charlcd_4bit_print(lcd, 1, UTS_RELEASE);
}
static void charlcd_init_work(struct work_struct *work)
{
struct charlcd *lcd =
container_of(work, struct charlcd, init_work.work);
+ int ret;
- charlcd_4bit_init(lcd);
+ ret = charlcd_4bit_init(lcd);
+ if (ret)
+ dev_err(lcd->dev, "failed to initialize LCD: %d\n", ret);
}
static int __init charlcd_probe(struct platform_device *pdev)
@@ -294,8 +346,7 @@ static int charlcd_suspend(struct device *dev)
struct charlcd *lcd = dev_get_drvdata(dev);
/* Power the display off */
- charlcd_4bit_command(lcd, HD_DISPCTRL);
- return 0;
+ return charlcd_4bit_command(lcd, HD_DISPCTRL);
}
static int charlcd_resume(struct device *dev)
@@ -303,8 +354,7 @@ static int charlcd_resume(struct device *dev)
struct charlcd *lcd = dev_get_drvdata(dev);
/* Turn the display back on */
- charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
- return 0;
+ return charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
}
static const struct dev_pm_ops charlcd_pm_ops = {
--
2.50.1 (Apple Git-155)