aboutsummaryrefslogtreecommitdiff
path: root/smc.c
diff options
context:
space:
mode:
Diffstat (limited to 'smc.c')
-rw-r--r--smc.c156
1 files changed, 156 insertions, 0 deletions
diff --git a/smc.c b/smc.c
new file mode 100644
index 0000000..a875b2c
--- /dev/null
+++ b/smc.c
@@ -0,0 +1,156 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2019 Evgeny Zinoviev <me@ch1p.io>
+ *
+ * SMC interacting code is based on applesmc linux driver by:
+ * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch>
+ * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <sys/io.h>
+#include <errno.h>
+#include <stdio.h>
+#include "smc.h"
+
+/*
+ * wait_read - Wait for a byte to appear on SMC port. Callers must
+ * hold applesmc_lock.
+ */
+int wait_read(void)
+{
+ uint8_t status;
+ int us;
+ for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
+ usleep(us);
+ status = inb(APPLESMC_CMD_PORT);
+
+ /* read: wait for smc to settle */
+ if (status & 0x01)
+ return 0;
+ }
+
+ fprintf(stderr, "%s() fail: 0x%02x\n", __func__, status);
+ return -EIO;
+}
+
+/*
+ * send_byte - Write to SMC port, retrying when necessary. Callers
+ * must hold applesmc_lock.
+ */
+int send_byte(uint8_t cmd, uint16_t port)
+{
+ uint8_t status;
+ int us;
+
+ outb(cmd, port);
+ for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
+ usleep(us);
+ 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 */
+ usleep(APPLESMC_RETRY_WAIT);
+ outb(cmd, port);
+ }
+
+ fprintf(stderr, "%s(0x%02x, 0x%04x) fail: 0x%02x\n",
+ __func__, cmd, port, status);
+ return -EIO;
+}
+
+int send_command(uint8_t cmd)
+{
+ return send_byte(cmd, APPLESMC_CMD_PORT);
+}
+
+int send_argument(const char *key)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ if (send_byte(key[i], APPLESMC_DATA_PORT))
+ return -EIO;
+ return 0;
+}
+
+int read_smc(uint8_t cmd, const char *key, uint8_t *buffer, uint8_t len)
+{
+ uint8_t status, data = 0;
+ int i;
+
+ if (send_command(cmd) || send_argument(key)) {
+ fprintf(stderr, "%.4s: read arg fail\n", key);
+ return -EIO;
+ }
+
+ /* This has no effect on newer (2012) SMCs */
+ if (send_byte(len, APPLESMC_DATA_PORT)) {
+ fprintf(stderr, "%.4s: read len fail\n", key);
+ return -EIO;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (wait_read()) {
+ fprintf(stderr, "%.4s: read data[%d] fail\n", key, i);
+ return -EIO;
+ }
+ buffer[i] = inb(APPLESMC_DATA_PORT);
+ }
+
+ /* Read the data port until bit0 is cleared */
+ for (i = 0; i < 16; i++) {
+ usleep(APPLESMC_MIN_WAIT);
+ status = inb(APPLESMC_CMD_PORT);
+ if (!(status & 0x01))
+ break;
+ data = inb(APPLESMC_DATA_PORT);
+ }
+
+ if (i)
+ fprintf(stderr, "flushed %d bytes, last value is: %d\n",
+ i, data);
+
+ return 0;
+}
+
+int write_smc(uint8_t cmd, const char *key, const uint8_t *buffer, uint8_t len)
+{
+ int i;
+
+ if (send_command(cmd) || send_argument(key)) {
+ fprintf(stderr, "%s: write arg fail\n", key);
+ return -EIO;
+ }
+
+ if (send_byte(len, APPLESMC_DATA_PORT)) {
+ fprintf(stderr, "%.4s: write len fail\n", key);
+ return -EIO;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
+ fprintf(stderr, "%s: write data fail\n", key);
+ return -EIO;
+ }
+ }
+
+ return 0;
+}