Created
April 20, 2025 07:19
-
-
Save AdityaGarg8/8524d5b4379f8a1b212a4c4322119ed1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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