18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * SMBus driver for ACPI Embedded Controller (v0.1) 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2007 Alexey Starikovskiy 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/acpi.h> 98c2ecf20Sopenharmony_ci#include <linux/wait.h> 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci#include <linux/delay.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 148c2ecf20Sopenharmony_ci#include "sbshc.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define PREFIX "ACPI: " 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define ACPI_SMB_HC_CLASS "smbus_host_ctl" 198c2ecf20Sopenharmony_ci#define ACPI_SMB_HC_DEVICE_NAME "ACPI SMBus HC" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistruct acpi_smb_hc { 228c2ecf20Sopenharmony_ci struct acpi_ec *ec; 238c2ecf20Sopenharmony_ci struct mutex lock; 248c2ecf20Sopenharmony_ci wait_queue_head_t wait; 258c2ecf20Sopenharmony_ci u8 offset; 268c2ecf20Sopenharmony_ci u8 query_bit; 278c2ecf20Sopenharmony_ci smbus_alarm_callback callback; 288c2ecf20Sopenharmony_ci void *context; 298c2ecf20Sopenharmony_ci bool done; 308c2ecf20Sopenharmony_ci}; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic int acpi_smbus_hc_add(struct acpi_device *device); 338c2ecf20Sopenharmony_cistatic int acpi_smbus_hc_remove(struct acpi_device *device); 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic const struct acpi_device_id sbs_device_ids[] = { 368c2ecf20Sopenharmony_ci {"ACPI0001", 0}, 378c2ecf20Sopenharmony_ci {"ACPI0005", 0}, 388c2ecf20Sopenharmony_ci {"", 0}, 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, sbs_device_ids); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic struct acpi_driver acpi_smb_hc_driver = { 448c2ecf20Sopenharmony_ci .name = "smbus_hc", 458c2ecf20Sopenharmony_ci .class = ACPI_SMB_HC_CLASS, 468c2ecf20Sopenharmony_ci .ids = sbs_device_ids, 478c2ecf20Sopenharmony_ci .ops = { 488c2ecf20Sopenharmony_ci .add = acpi_smbus_hc_add, 498c2ecf20Sopenharmony_ci .remove = acpi_smbus_hc_remove, 508c2ecf20Sopenharmony_ci }, 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ciunion acpi_smb_status { 548c2ecf20Sopenharmony_ci u8 raw; 558c2ecf20Sopenharmony_ci struct { 568c2ecf20Sopenharmony_ci u8 status:5; 578c2ecf20Sopenharmony_ci u8 reserved:1; 588c2ecf20Sopenharmony_ci u8 alarm:1; 598c2ecf20Sopenharmony_ci u8 done:1; 608c2ecf20Sopenharmony_ci } fields; 618c2ecf20Sopenharmony_ci}; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cienum acpi_smb_status_codes { 648c2ecf20Sopenharmony_ci SMBUS_OK = 0, 658c2ecf20Sopenharmony_ci SMBUS_UNKNOWN_FAILURE = 0x07, 668c2ecf20Sopenharmony_ci SMBUS_DEVICE_ADDRESS_NACK = 0x10, 678c2ecf20Sopenharmony_ci SMBUS_DEVICE_ERROR = 0x11, 688c2ecf20Sopenharmony_ci SMBUS_DEVICE_COMMAND_ACCESS_DENIED = 0x12, 698c2ecf20Sopenharmony_ci SMBUS_UNKNOWN_ERROR = 0x13, 708c2ecf20Sopenharmony_ci SMBUS_DEVICE_ACCESS_DENIED = 0x17, 718c2ecf20Sopenharmony_ci SMBUS_TIMEOUT = 0x18, 728c2ecf20Sopenharmony_ci SMBUS_HOST_UNSUPPORTED_PROTOCOL = 0x19, 738c2ecf20Sopenharmony_ci SMBUS_BUSY = 0x1a, 748c2ecf20Sopenharmony_ci SMBUS_PEC_ERROR = 0x1f, 758c2ecf20Sopenharmony_ci}; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cienum acpi_smb_offset { 788c2ecf20Sopenharmony_ci ACPI_SMB_PROTOCOL = 0, /* protocol, PEC */ 798c2ecf20Sopenharmony_ci ACPI_SMB_STATUS = 1, /* status */ 808c2ecf20Sopenharmony_ci ACPI_SMB_ADDRESS = 2, /* address */ 818c2ecf20Sopenharmony_ci ACPI_SMB_COMMAND = 3, /* command */ 828c2ecf20Sopenharmony_ci ACPI_SMB_DATA = 4, /* 32 data registers */ 838c2ecf20Sopenharmony_ci ACPI_SMB_BLOCK_COUNT = 0x24, /* number of data bytes */ 848c2ecf20Sopenharmony_ci ACPI_SMB_ALARM_ADDRESS = 0x25, /* alarm address */ 858c2ecf20Sopenharmony_ci ACPI_SMB_ALARM_DATA = 0x26, /* 2 bytes alarm data */ 868c2ecf20Sopenharmony_ci}; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic inline int smb_hc_read(struct acpi_smb_hc *hc, u8 address, u8 *data) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci return ec_read(hc->offset + address, data); 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic inline int smb_hc_write(struct acpi_smb_hc *hc, u8 address, u8 data) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci return ec_write(hc->offset + address, data); 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic int wait_transaction_complete(struct acpi_smb_hc *hc, int timeout) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci if (wait_event_timeout(hc->wait, hc->done, msecs_to_jiffies(timeout))) 1018c2ecf20Sopenharmony_ci return 0; 1028c2ecf20Sopenharmony_ci return -ETIME; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic int acpi_smbus_transaction(struct acpi_smb_hc *hc, u8 protocol, 1068c2ecf20Sopenharmony_ci u8 address, u8 command, u8 *data, u8 length) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci int ret = -EFAULT, i; 1098c2ecf20Sopenharmony_ci u8 temp, sz = 0; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (!hc) { 1128c2ecf20Sopenharmony_ci printk(KERN_ERR PREFIX "host controller is not configured\n"); 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci mutex_lock(&hc->lock); 1178c2ecf20Sopenharmony_ci hc->done = false; 1188c2ecf20Sopenharmony_ci if (smb_hc_read(hc, ACPI_SMB_PROTOCOL, &temp)) 1198c2ecf20Sopenharmony_ci goto end; 1208c2ecf20Sopenharmony_ci if (temp) { 1218c2ecf20Sopenharmony_ci ret = -EBUSY; 1228c2ecf20Sopenharmony_ci goto end; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci smb_hc_write(hc, ACPI_SMB_COMMAND, command); 1258c2ecf20Sopenharmony_ci if (!(protocol & 0x01)) { 1268c2ecf20Sopenharmony_ci smb_hc_write(hc, ACPI_SMB_BLOCK_COUNT, length); 1278c2ecf20Sopenharmony_ci for (i = 0; i < length; ++i) 1288c2ecf20Sopenharmony_ci smb_hc_write(hc, ACPI_SMB_DATA + i, data[i]); 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci smb_hc_write(hc, ACPI_SMB_ADDRESS, address << 1); 1318c2ecf20Sopenharmony_ci smb_hc_write(hc, ACPI_SMB_PROTOCOL, protocol); 1328c2ecf20Sopenharmony_ci /* 1338c2ecf20Sopenharmony_ci * Wait for completion. Save the status code, data size, 1348c2ecf20Sopenharmony_ci * and data into the return package (if required by the protocol). 1358c2ecf20Sopenharmony_ci */ 1368c2ecf20Sopenharmony_ci ret = wait_transaction_complete(hc, 1000); 1378c2ecf20Sopenharmony_ci if (ret || !(protocol & 0x01)) 1388c2ecf20Sopenharmony_ci goto end; 1398c2ecf20Sopenharmony_ci switch (protocol) { 1408c2ecf20Sopenharmony_ci case SMBUS_RECEIVE_BYTE: 1418c2ecf20Sopenharmony_ci case SMBUS_READ_BYTE: 1428c2ecf20Sopenharmony_ci sz = 1; 1438c2ecf20Sopenharmony_ci break; 1448c2ecf20Sopenharmony_ci case SMBUS_READ_WORD: 1458c2ecf20Sopenharmony_ci sz = 2; 1468c2ecf20Sopenharmony_ci break; 1478c2ecf20Sopenharmony_ci case SMBUS_READ_BLOCK: 1488c2ecf20Sopenharmony_ci if (smb_hc_read(hc, ACPI_SMB_BLOCK_COUNT, &sz)) { 1498c2ecf20Sopenharmony_ci ret = -EFAULT; 1508c2ecf20Sopenharmony_ci goto end; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci sz &= 0x1f; 1538c2ecf20Sopenharmony_ci break; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci for (i = 0; i < sz; ++i) 1568c2ecf20Sopenharmony_ci smb_hc_read(hc, ACPI_SMB_DATA + i, &data[i]); 1578c2ecf20Sopenharmony_ci end: 1588c2ecf20Sopenharmony_ci mutex_unlock(&hc->lock); 1598c2ecf20Sopenharmony_ci return ret; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ciint acpi_smbus_read(struct acpi_smb_hc *hc, u8 protocol, u8 address, 1638c2ecf20Sopenharmony_ci u8 command, u8 *data) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci return acpi_smbus_transaction(hc, protocol, address, command, data, 0); 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_smbus_read); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ciint acpi_smbus_write(struct acpi_smb_hc *hc, u8 protocol, u8 address, 1718c2ecf20Sopenharmony_ci u8 command, u8 *data, u8 length) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci return acpi_smbus_transaction(hc, protocol, address, command, data, length); 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_smbus_write); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ciint acpi_smbus_register_callback(struct acpi_smb_hc *hc, 1798c2ecf20Sopenharmony_ci smbus_alarm_callback callback, void *context) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci mutex_lock(&hc->lock); 1828c2ecf20Sopenharmony_ci hc->callback = callback; 1838c2ecf20Sopenharmony_ci hc->context = context; 1848c2ecf20Sopenharmony_ci mutex_unlock(&hc->lock); 1858c2ecf20Sopenharmony_ci return 0; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_smbus_register_callback); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ciint acpi_smbus_unregister_callback(struct acpi_smb_hc *hc) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci mutex_lock(&hc->lock); 1938c2ecf20Sopenharmony_ci hc->callback = NULL; 1948c2ecf20Sopenharmony_ci hc->context = NULL; 1958c2ecf20Sopenharmony_ci mutex_unlock(&hc->lock); 1968c2ecf20Sopenharmony_ci acpi_os_wait_events_complete(); 1978c2ecf20Sopenharmony_ci return 0; 1988c2ecf20Sopenharmony_ci} 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_smbus_unregister_callback); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic inline void acpi_smbus_callback(void *context) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct acpi_smb_hc *hc = context; 2058c2ecf20Sopenharmony_ci if (hc->callback) 2068c2ecf20Sopenharmony_ci hc->callback(hc->context); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cistatic int smbus_alarm(void *context) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci struct acpi_smb_hc *hc = context; 2128c2ecf20Sopenharmony_ci union acpi_smb_status status; 2138c2ecf20Sopenharmony_ci u8 address; 2148c2ecf20Sopenharmony_ci if (smb_hc_read(hc, ACPI_SMB_STATUS, &status.raw)) 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci /* Check if it is only a completion notify */ 2178c2ecf20Sopenharmony_ci if (status.fields.done && status.fields.status == SMBUS_OK) { 2188c2ecf20Sopenharmony_ci hc->done = true; 2198c2ecf20Sopenharmony_ci wake_up(&hc->wait); 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci if (!status.fields.alarm) 2228c2ecf20Sopenharmony_ci return 0; 2238c2ecf20Sopenharmony_ci mutex_lock(&hc->lock); 2248c2ecf20Sopenharmony_ci smb_hc_read(hc, ACPI_SMB_ALARM_ADDRESS, &address); 2258c2ecf20Sopenharmony_ci status.fields.alarm = 0; 2268c2ecf20Sopenharmony_ci smb_hc_write(hc, ACPI_SMB_STATUS, status.raw); 2278c2ecf20Sopenharmony_ci /* We are only interested in events coming from known devices */ 2288c2ecf20Sopenharmony_ci switch (address >> 1) { 2298c2ecf20Sopenharmony_ci case ACPI_SBS_CHARGER: 2308c2ecf20Sopenharmony_ci case ACPI_SBS_MANAGER: 2318c2ecf20Sopenharmony_ci case ACPI_SBS_BATTERY: 2328c2ecf20Sopenharmony_ci acpi_os_execute(OSL_NOTIFY_HANDLER, 2338c2ecf20Sopenharmony_ci acpi_smbus_callback, hc); 2348c2ecf20Sopenharmony_ci default:; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci mutex_unlock(&hc->lock); 2378c2ecf20Sopenharmony_ci return 0; 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_citypedef int (*acpi_ec_query_func) (void *data); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ciextern int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit, 2438c2ecf20Sopenharmony_ci acpi_handle handle, acpi_ec_query_func func, 2448c2ecf20Sopenharmony_ci void *data); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic int acpi_smbus_hc_add(struct acpi_device *device) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci int status; 2498c2ecf20Sopenharmony_ci unsigned long long val; 2508c2ecf20Sopenharmony_ci struct acpi_smb_hc *hc; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (!device) 2538c2ecf20Sopenharmony_ci return -EINVAL; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(device->handle, "_EC", NULL, &val); 2568c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 2578c2ecf20Sopenharmony_ci printk(KERN_ERR PREFIX "error obtaining _EC.\n"); 2588c2ecf20Sopenharmony_ci return -EIO; 2598c2ecf20Sopenharmony_ci } 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci strcpy(acpi_device_name(device), ACPI_SMB_HC_DEVICE_NAME); 2628c2ecf20Sopenharmony_ci strcpy(acpi_device_class(device), ACPI_SMB_HC_CLASS); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci hc = kzalloc(sizeof(struct acpi_smb_hc), GFP_KERNEL); 2658c2ecf20Sopenharmony_ci if (!hc) 2668c2ecf20Sopenharmony_ci return -ENOMEM; 2678c2ecf20Sopenharmony_ci mutex_init(&hc->lock); 2688c2ecf20Sopenharmony_ci init_waitqueue_head(&hc->wait); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci hc->ec = acpi_driver_data(device->parent); 2718c2ecf20Sopenharmony_ci hc->offset = (val >> 8) & 0xff; 2728c2ecf20Sopenharmony_ci hc->query_bit = val & 0xff; 2738c2ecf20Sopenharmony_ci device->driver_data = hc; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci acpi_ec_add_query_handler(hc->ec, hc->query_bit, NULL, smbus_alarm, hc); 2768c2ecf20Sopenharmony_ci dev_info(&device->dev, "SBS HC: offset = 0x%0x, query_bit = 0x%0x\n", 2778c2ecf20Sopenharmony_ci hc->offset, hc->query_bit); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ciextern void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic int acpi_smbus_hc_remove(struct acpi_device *device) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci struct acpi_smb_hc *hc; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci if (!device) 2898c2ecf20Sopenharmony_ci return -EINVAL; 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci hc = acpi_driver_data(device); 2928c2ecf20Sopenharmony_ci acpi_ec_remove_query_handler(hc->ec, hc->query_bit); 2938c2ecf20Sopenharmony_ci acpi_os_wait_events_complete(); 2948c2ecf20Sopenharmony_ci kfree(hc); 2958c2ecf20Sopenharmony_ci device->driver_data = NULL; 2968c2ecf20Sopenharmony_ci return 0; 2978c2ecf20Sopenharmony_ci} 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cimodule_acpi_driver(acpi_smb_hc_driver); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3028c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alexey Starikovskiy"); 3038c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ACPI SMBus HC driver"); 304