Skip to content

Instantly share code, notes, and snippets.

@AdityaGarg8
Created April 20, 2025 07:19
Show Gist options
  • Save AdityaGarg8/8524d5b4379f8a1b212a4c4322119ed1 to your computer and use it in GitHub Desktop.
Save AdityaGarg8/8524d5b4379f8a1b212a4c4322119ed1 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* Apple SMC ACPI backend
* Copyright (C) Subu Dwevedi <[email protected]>
*/
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include "smc.h"
#define SMC_ENDPOINT 0x20
//PORT BASED I/O ADDRESSES
#define SMC_DATA_PORT 0
#define SMC_CMD_PORT 4 //this is also status port
#define SMC_NR_PORTS 32
#define SMC_STATUS_AWAITING_DATA BIT(0)
#define SMC_STATUS_IB_CLOSED BIT(1)
#define SMC_STATUS_BUSY BIT(2)
//MEMORY-BASED I/O ADDRESSES
#define SMC_MMIO_KEY_DATA 0x0
#define SMC_MMIO_KEY_STATUS 0x4005
#define SMC_MMIO_KEY_NAME 0x78
#define SMC_MMIO_KEY_DATA_LEN 0x7D
#define SMC_MMIO_KEY_SMC_ID 0x7E
#define SMC_MMIO_KEY_CMD 0x7F
#define SMC_MMIO_MIN_SIZE 0x4006
//GENERAL ADDRESSES
#define SMC_MSG_READ_KEY 0x10
#define SMC_MSG_WRITE_KEY 0x11
#define SMC_MSG_GET_KEY_BY_INDEX 0x12
#define SMC_MSG_GET_KEY_INFO 0x13
#define SMC_MSG_NOTIFICATION 0x18
#define SMC_RECV_TIMEOUT 1000
struct apple_smc_acpi {
struct acpi_device *adev;
struct completion cmd_done;
struct device *dev;
u16 port_base;
u8 *__iomem iomem_base;
u32 iomem_base_addr, iomem_base_size;
bool is_mmio;
bool has_port;
};
/*
* PORT IMPLEMENTATION
*/
static int apple_smc_acpi_port_wait(struct apple_smc_acpi *smc, u8 val, u8 mask)
{
do {
if (wait_for_completion_timeout(&smc->cmd_done, msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) {
dev_err(smc->dev, "port/wait: timeout value:%u, mask:%u\n", val, mask);
return -ETIMEDOUT;
}
if ((inb(smc->port_base + SMC_CMD_PORT) & mask) == val)
break;
dev_warn(smc->dev, "port/wait: sequence missed\n");
} while (1);
return 0;
}
static int apple_smc_acpi_port_write_byte(struct apple_smc_acpi *smc, u8 buf)
{
int ret;
ret = apple_smc_acpi_port_wait(smc, SMC_STATUS_BUSY, SMC_STATUS_IB_CLOSED | SMC_STATUS_BUSY);
if (ret)
return ret;
outb(buf, smc->port_base + SMC_DATA_PORT);
return 0;
}
static int apple_smc_acpi_port_write_cmd(struct apple_smc_acpi *smc, u8 buf)
{
int ret;
ret = apple_smc_acpi_port_wait(smc, 0, SMC_STATUS_IB_CLOSED);
if (ret)
return ret;
outb(buf, smc->port_base + SMC_CMD_PORT);
return 0;
}
static int apple_smc_acpi_port_write_key(struct apple_smc_acpi *smc, smc_key key)
{
if (!apple_smc_acpi_port_write_byte(smc, (u8)(key >> 24))
&& !apple_smc_acpi_port_write_byte(smc, (u8)(key >> 16))
&& !apple_smc_acpi_port_write_byte(smc, (u8)(key >> 8))
&& !apple_smc_acpi_port_write_byte(smc, (u8)key))
return 0;
return -EIO;
}
static int apple_smc_acpi_port_read_byte(struct apple_smc_acpi *smc, u8 *byte)
{
if (apple_smc_acpi_port_wait(smc, SMC_STATUS_BUSY, 5))
return -EIO;
if (apple_smc_acpi_port_wait(smc, 0, SMC_STATUS_BUSY))
return -EIO;
*byte = inb(smc->port_base + SMC_DATA_PORT);
return 0;
}
static int apple_smc_acpi_port_read_key(struct apple_smc_acpi *smc, smc_key *key)
{
//this function is also used to read other 32-bit values
u8 buf[4] = {0}, i;
for (i = 0; i < 4; i++) {
apple_smc_acpi_port_read_byte(smc, &buf[i]);
if (!buf[i])
return i;
}
*key = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
return 0;
}
static int apple_smc_acpi_port_is_ok(struct apple_smc_acpi *smc)
{
int ret;
ret = apple_smc_acpi_port_wait(smc, 0, SMC_STATUS_BUSY);
if (!ret)
return ret;
ret = apple_smc_acpi_write_cmd(smc, SMC_MSG_READ_CMD);
if (ret)
return ret;
return apple_smc_acpi_port_wait(smc, 0, SMC_STATUS_BUSY);
}
static int apple_smc_acpi_port_read_smc(struct apple_smc_acpi *smc, u8 cmd, smc_key key, void *buffer, size_t len)
{
int ret;
u8 buf;
if (!smc_key || !cmd || !len)
return -EIO;
ret = apple_smc_acpi_port_is_ok(smc);
if (ret)
return ret;
if (apple_smc_acpi_port_write_cmd(smc, cmd) || apple_smc_acpi_port_write_key(smc, key)) {
dev_err(smc->dev, "port/read_smc: read argument failed, key: %u, cmd: %u\n", key, cmd);
return -EIO;
}
if (apple_smc_acpi_port_write_byte(smc, len)) {
dev_err(smc->dev, "port/read_smc: read len failed, len:%zu\n", len);
return -EIO;
}
//read the data until fully complete (this is not necessary but good for safety)
while (!apple_smc_acpi_port_read_byte(smc, &buf)) {
if (ret < len) {
buffer[ret] = buf;
}
ret++;
}
return apple_smc_acpi_port_wait(smc, 0, SMC_STATUS_BUSY);
}
static int apple_smc_acpi_port_write_smc(struct apple_smc_acpi *smc, u8 cmd, smc_key key, const void *buffer, size_t len)
{
int ret;
if (!smc_key || !cmd || !len)
return -EIO;
ret = apple_smc_acpi_port_is_ok(smc);
if (ret)
return ret;
if (apple_smc_acpi_port_write_cmd(smc, cmd) || apple_smc_acpi_port_write_key(smc, key)) {
dev_err(smc->dev, "port/write_smc: write argument failed key:%u, cmd:%u\n", key, cmd);
return -EIO;
}
if (apple_smc_acpi_port_write_byte(smc, len)) {
dev_err(smc->dev, "port/write_smc: write len failed, len:%zu\n", len);
return -EIO;
}
for (ret = 0; ret < len; ret++) {
if (apple_smc_acpi_port_write_byte(smc, buffer[ret])) {
dev_err(smc->dev, "port/write_smc: data copy failed, key:%u, cmd:%u, len:%zu\n", key,
cmd, len);
return -EIO;
}
}
return apple_smc_acpi_port_wait(smc, 0, SMC_STATUS_BUSY);
}
static int apple_smc_acpi_port_get_key_by_index(struct apple_smc_acpi *smc, int index, smc_key *key_out)
{
__be32 be_index = cpu_to_be32(index);
return apple_smc_acpi_port_read_smc(smc, SMC_MSG_GET_KEY_BY_INDEX, (smc_key)be_index,
(void *) &key_out, 4);
}
static int apple_smc_acpi_port_get_key_type(smc_key key, struct apple_smc_key_info *info)
{
int ret;
if (!key)
return -EIO;
ret = apple_smc_acpi_port_is_ok(smc);
if (ret)
return ret;
if (apple_smc_acpi_port_write_cmd(smc, SMC_MSG_GET_KEY_TYPE) || apple_smc_acpi_port_write_key(smc, key))
goto err;
if (apple_smc_acpi_port_read_byte(smc, &info->size) || apple_smc_acpi_port_read_key(smc, &info->type_code))
goto data_err;
if (apple_smc_acpi_port_read_byte(smc, &info->flags))
goto data_err;
return 0;
data_err:
info->size = NULL;
info->type_code = NULL;
info->flags = NULL;
err:
dev_err(smc->dev, "port/get_key_type: failed key:%u\n", key);
return -EIO;
}
/*
* MMIO IMPLEMENTATION
*/
static void mmio_clear_smc(struct apple_smc_acpi *smc)
{
if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS))
return iowrite(0, smc->iomem_base + SMC_MMIO_KEY_STATUS);
}
static int apple_smc_acpi_mmio_wait(struct apple_smc_acpi *smc)
{
do {
if (wait_for_completion_timeout(&smc->cmd_done,
msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) {
dev_err(smc->dev, "%s: timeout\n", __func__);
return -ETIMEDOUT;
}
if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS) & 0x20)
break;
dev_warn(smc->dev, "%s: sequence missed\n", __func__);
} while (1);
return 0;
}
static int apple_smc_acpi_mmio_read_smc(struct apple_smc_acpi *smc, u64 cmd, smc_key key,
void *buffer, size_t len)
{
u8 ret;
mmio_clear_smc(smc);
iowrite32(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
iowrite8(cmd, smc->iomem_base + SMC_MMIO_KEY_CMD);
if (apple_smc_acpi_mmio_wait(smc))
return -EIO;
ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
if (ret) {
dev_err(smc->dev, "mmio/read_smc: read failed, cmd: %llx, key: %u, len: %zu\n",
cmd, key, len);
return ret;
}
memcpy_fromio(buffer, smc->iomem_base + SMC_MMIO_KEY_DATA, len);
return 0;
}
static int apple_smc_acpi_mmio_write_smc(struct apple_smc_acpi *smc, u64 cmd, smc_key key,
const void *buffer, size_t len)
{
u8 ret;
mmio_clear_smc(smc);
iowrite32(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
memcpy_toio(smc->iomem_base + SMC_MMIO_KEY_DATA, buffer, len);
iowrite8(len, smc->iomem_base + SMC_MMIO_KEY_DATA_LEN);
iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
iowrite8(cmd, smc->iomem_base + SMC_MMIO_KEY_CMD);
if (apple_smc_acpi_mmio_wait(smc))
return -EIO;
ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
if (ret) {
dev_err(smc->dev, "mmio/write_smc: write failed, cmd: %llx, key: %u, len: %zu\n",
cmd, key, len);
return ret;
}
return 0;
}
static int apple_smc_acpi_mmio_get_key_by_index(struct apple_smc_acpi *smc, int index, smc_key *key_out)
{
__be32 be = cpu_to_be32(index);
return apple_smc_acpi_mmio_read_smc(smc, SMC_MSG_GET_KEY_BY_INDEX, (smc_key)be, (void *) &key_out, 4)
}
static int apple_smc_acpi_mmio_get_key_info(struct apple_smc_acpi *smc, smc_key key,
struct apple_smc_key_info *info)
{
u8 ret;
mmio_clear_smc(smc);
iowrite32(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
iowrite8(SMC_MSG_GET_KEY_INFO, smc->iomem_base + SMC_MMIO_KEY_CMD);
if (apple_smc_acpi_mmio_wait(smc))
return -EIO;
ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
if (ret) {
dev_err(smc->dev, "mmio/get_key_info failed: cmd: 0x13, key: %u\n", key);
return ret;
}
info->type_code = ioread32(smc->iomem_base + SMC_MMIO_KEY_CMD);
info->size = ioread8(smc->iomem_base + 5);
info->flags = ioread8(smc->iomem_base + 6);
return 0;
}
static int apple_smc_acpi_try_enable_mmio(struct apple_smc_acpi *smc)
{
if (!smc->is_mmio)
return -ENXIO;
void *ldkn = kmalloc(sizeof(u8), GFP_KERNEL);
smc->iomem_base = ioremap(smc->iomem_base_addr, iomem_base_size);
if (!smc->iomem_base)
goto out;
if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS) == 0xFF) {
dev_warn(smc->dev, "mmio enable failed: init status is 0xff\n");
goto out_iomem;
}
//the hex value in the following code is "LDKN" in hex
if (apple_smc_acpi_mmio_read_smc(smc, SMC_MSG_READ_KEY, 0x4C444B4B, ldkn, 1)) {
dev_warn(smc->dev, "mmio enable failed: ldkn read failed\n");
goto out_iomem;
}
if (*(u8 *)ldkn < 2) {
dev_warn(smc->dev, "mmio enable failed: ldkn version less than 2\n");
goto out_iomem;
}
return 0;
out_iomem:
iounmap(smc->iomem_base);
out:
kfree(ldkn);
return -ENXIO;
}
/*
* GENERAL STUFF
*/
static int apple_smc_acpi_read_smc(void *cookie, smc_key key, void *buf, size_t size)
{
struct apple_smc_acpi *smc = cookie;
if (smc->is_mmio)
return apple_smc_acpi_mmio_read_smc(smc, SMC_MSG_READ_KEY,
key, buf, size);
return apple_smc_acpi_port_read_smc(smc, SMC_MSG_READ_KEY, key,
buf, size);
}
static int apple_smc_acpi_write_smc(void *cookie, smc_key key, void *buf, size_t size)
{
struct apple_smc_acpi *smc = cookie;
if (smc->is_mmio)
return apple_smc_acpi_mmio_write_smc(smc, SMC_MSG_WRITE_KEY,
key, (const void *) buf, size);
return apple_smc_acpi_port_write_smc(smc, SMC_MSG_WRITE_KEY, key,
(const void *)buf, size);
}
static int apple_smc_acpi_get_key_by_index(void *cookie, int index, smc_key *key)
{
struct apple_smc_acpi *smc = cookie;
if (smc->is_mmio)
return apple_smc_acpi_mmio_get_key_by_index(smc, index, key);
return apple_smc_acpi_port_get_key_by_index(smc, index, key);
}
static int apple_smc_acpi_get_key_info(void *cookie, smc_key key, struct apple_smc_key_info *info)
{
struct apple_smc_acpi *smc = cookie;
if (smc->is_mmio)
return apple_smc_acpi_mmio_get_key_info(smc, key, info);
return apple_smc_acpi_port_get_key_info(smc, key, info);
}
static int apple_smc_acpi_write_atomic(void *cookie, smc_key key, void *buf, size_t size)
{
return 0;
}
static int apple_smc_acpi_rw_smc(void *cookie, smc_key key, void *wbuf, size_t wsize,
void *rbuf, size_t rsize)
{
return 0;
}
static const struct apple_smc_backend_ops apple_smc_acpi_be_ops = {
.read_key = apple_smc_acpi_read_smc,
.write_key = apple_smc_acpi_write_smc,
.get_key_by_index = apple_smc_acpi_get_key_by_index,
.get_key_info = apple_smc_acpi_get_key_info,
.write_key_atomic = apple_smc_acpi_write_atomic,
.rw_key = apple_smc_acpi_rw_smc,
};
static acpi_status apple_smc_acpi_walk_resources(struct acpi_resource *res
void *data)
{
struct apple_smc_acpi *smc = data;
switch (res->type) {
case ACPI_RESOURCE_TYPE_IO:
if (!smc->has_port) {
if (res->data.io.address_length < SMC_NR_PORTS)
return AE_ERROR;
smc->port_base = res->data.io.minimum;
smc->has_port = true;
}
return AE_OK;
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
if (!smc->is_mmio) {
if (res->data.fixed_memory32.address_length < SMC_MMIO_MIN_SIZE){
dev_warn(smc->dev, "found mmio support but its too small: %u\n",
res->data.fixed_memory32.address_length);
return AE_OK;
}
smc->iomem_base_addr = res->data.fixed_memory32.address;
smc->iomem_base_size = res->data.fixed_memory32.address_length;
smc->is_mmio = true;
}
case ACPI_RESOURCE_TYPE_END_TAG:
if (smc->has_port)
return AE_OK;
else
return AE_NOT_FOUND;
default:
return AE_OK;
}
}
static int apple_smc_acpi_add(struct acpi_device *adev)
{
struct device *dev = &adev->dev;
struct apple_smc_acpi *smc;
int ret;
acpi_status status;
smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
if (!smc)
return -ENOMEM;
smc->dev = dev;
status = acpi_walk_resources(smc->adev->handle, METHOD_NAME__CRS,
apple_smc_acpi_walk_resources, smc);
if (ACPI_FAILURE(status))
return -ENODEV;
apple_smc_acpi_try_enable_mmio(smc);
init_completion(&smc->cmd_done);
ret = apple_smc_probe(dev, &apple_smc_acpi_be_ops, smc);
if (ret)
return ret;
return 0;
}
static void apple_smc_acpi_remove(struct acpi_device *adev)
{
struct apple_smc *core = dev_get_drvdata(&adev->dev);
apple_smc_remove(core);
}
static const struct of_device_id apple_smc_acpi_of_match[] = {
{ .compatible = "apple,smc" },
{}
};
static const struct acpi_device_id apple_smc_acpi_ids[] = {
{"APP0001", 0},
{"", 0},
};
static struct acpi_driver apple_smc_acpi_driver = {
.name = "apple_smc_acpi",
.class = "apple_smc_acpi",
.ids = apple_smc_acpi_ids,
.ops = {
.add = apple_smc_acpi_add,
.remove = apple_smc_acpi_remove
},
};
static int __init apple_smc_acpi_init(void)
{
return acpi_bus_register_driver(&apple_smc_acpi_driver);
}
static void __exit apple_smc_acpi_exit(void)
{
acpi_bus_unregister_driver(&apple_smc_acpi_driver);
}
module_init(apple_smc_acpi_init);
module_exit(apple_smc_acpi_exit);
MODULE_DEVICE_TABLE(of, apple_smc_acpi_of_match);
MODULE_AUTHOR("Subu Dwevedi <[email protected]>");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("Apple SMC ACPI backend driver");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment