hwmon: (applesmc) Decode and act on read/write status codes
From: Henrik Rydberg <rydberg@euromail.se>
The behavior of the SMC has changed several times over the years,
causing read failures in the driver. It seems the problem can be
explained by a shift in SMC speed combined with improper action on
status codes.
We should first wait for the SMC to settle, which was the most
frequent response on the old slow machines. Then, if the SMC is busy,
we need to try again later by resending the command. This was the most
likely response until 2012. Now, with a shorter wait time, we are
again most likely to poll while the SMC is settling, and as a result
we see high failure rates on many old and new models.
With the distinction between busy and failure, we can also wait longer
before retrying, without sacrificing speed. This seems to bring
failures down to virtually zero on all models.
/*
- * __wait_status - Wait up to 32ms for the status port to get a certain value
- * (masked with 0x0f), returning zero if the value is obtained. Callers must
+ * wait_read - Wait for a byte to appear on SMC port. Callers must
* hold applesmc_lock.
*/
-static int __wait_status(u8 val)
+static int wait_read(void)
{
+ u8 status;
int us;
-
- val = val & APPLESMC_STATUS_MASK;
-
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
udelay(us);
- if ((inb(APPLESMC_CMD_PORT) & APPLESMC_STATUS_MASK) == val)
+ status = inb(APPLESMC_CMD_PORT);
+ /* read: wait for smc to settle */
+ if (status & 0x01)
return 0;
}
/*
- * special treatment of command port - on newer macbooks, it seems necessary
- * to resend the command byte before polling the status again. Callers must
- * hold applesmc_lock.
+ * send_byte - Write to SMC port, retrying when necessary. Callers
+ * must hold applesmc_lock.
*/
-static int send_command(u8 cmd)
+static int send_byte(u8 cmd, u16 port)
{
+ u8 status;
int us;
+
+ outb(cmd, port);
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
- outb(cmd, APPLESMC_CMD_PORT);
udelay(us);
- if ((inb(APPLESMC_CMD_PORT) & APPLESMC_STATUS_MASK) == 0x0c)
+ status = inb(APPLESMC_CMD_PORT);
+ /* write: wait for smc to settle */
+ if (status & 0x02)
+ continue;
+ /* ready: cmd accepted, return */
+ if (status & 0x04)
return 0;
+ /* timeout: give up */
+ if (us << 1 == APPLESMC_MAX_WAIT)
+ break;
+ /* busy: long wait and resend */
+ udelay(APPLESMC_RETRY_WAIT);
+ outb(cmd, port);
}
+
+ pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x
", cmd, port, status);
return -EIO;
}
+static int send_command(u8 cmd)
+{
+ return send_byte(cmd, APPLESMC_CMD_PORT);
+}
+
static int send_argument(const char *key)
{
int i;
- for (i = 0; i < 4; i++) {
- outb(key[i], APPLESMC_DATA_PORT);
- if (__wait_status(0x04))
+ for (i = 0; i < 4; i++)
+ if (send_byte(key[i], APPLESMC_DATA_PORT))
return -EIO;
- }
return 0;
}