302 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * SMBus driver for ACPI Embedded Controller (v0.1)4 *5 * Copyright (c) 2007 Alexey Starikovskiy6 */7 8#define pr_fmt(fmt) "ACPI: " fmt9 10#include <linux/acpi.h>11#include <linux/wait.h>12#include <linux/slab.h>13#include <linux/delay.h>14#include <linux/module.h>15#include <linux/interrupt.h>16#include "sbshc.h"17 18#define ACPI_SMB_HC_CLASS "smbus_host_ctl"19#define ACPI_SMB_HC_DEVICE_NAME "ACPI SMBus HC"20 21struct acpi_smb_hc {22 struct acpi_ec *ec;23 struct mutex lock;24 wait_queue_head_t wait;25 u8 offset;26 u8 query_bit;27 smbus_alarm_callback callback;28 void *context;29 bool done;30};31 32static int acpi_smbus_hc_add(struct acpi_device *device);33static void acpi_smbus_hc_remove(struct acpi_device *device);34 35static const struct acpi_device_id sbs_device_ids[] = {36 {"ACPI0001", 0},37 {"ACPI0005", 0},38 {"", 0},39};40 41MODULE_DEVICE_TABLE(acpi, sbs_device_ids);42 43static struct acpi_driver acpi_smb_hc_driver = {44 .name = "smbus_hc",45 .class = ACPI_SMB_HC_CLASS,46 .ids = sbs_device_ids,47 .ops = {48 .add = acpi_smbus_hc_add,49 .remove = acpi_smbus_hc_remove,50 },51};52 53union acpi_smb_status {54 u8 raw;55 struct {56 u8 status:5;57 u8 reserved:1;58 u8 alarm:1;59 u8 done:1;60 } fields;61};62 63enum acpi_smb_status_codes {64 SMBUS_OK = 0,65 SMBUS_UNKNOWN_FAILURE = 0x07,66 SMBUS_DEVICE_ADDRESS_NACK = 0x10,67 SMBUS_DEVICE_ERROR = 0x11,68 SMBUS_DEVICE_COMMAND_ACCESS_DENIED = 0x12,69 SMBUS_UNKNOWN_ERROR = 0x13,70 SMBUS_DEVICE_ACCESS_DENIED = 0x17,71 SMBUS_TIMEOUT = 0x18,72 SMBUS_HOST_UNSUPPORTED_PROTOCOL = 0x19,73 SMBUS_BUSY = 0x1a,74 SMBUS_PEC_ERROR = 0x1f,75};76 77enum acpi_smb_offset {78 ACPI_SMB_PROTOCOL = 0, /* protocol, PEC */79 ACPI_SMB_STATUS = 1, /* status */80 ACPI_SMB_ADDRESS = 2, /* address */81 ACPI_SMB_COMMAND = 3, /* command */82 ACPI_SMB_DATA = 4, /* 32 data registers */83 ACPI_SMB_BLOCK_COUNT = 0x24, /* number of data bytes */84 ACPI_SMB_ALARM_ADDRESS = 0x25, /* alarm address */85 ACPI_SMB_ALARM_DATA = 0x26, /* 2 bytes alarm data */86};87 88static inline int smb_hc_read(struct acpi_smb_hc *hc, u8 address, u8 *data)89{90 return ec_read(hc->offset + address, data);91}92 93static inline int smb_hc_write(struct acpi_smb_hc *hc, u8 address, u8 data)94{95 return ec_write(hc->offset + address, data);96}97 98static int wait_transaction_complete(struct acpi_smb_hc *hc, int timeout)99{100 if (wait_event_timeout(hc->wait, hc->done, msecs_to_jiffies(timeout)))101 return 0;102 return -ETIME;103}104 105static int acpi_smbus_transaction(struct acpi_smb_hc *hc, u8 protocol,106 u8 address, u8 command, u8 *data, u8 length)107{108 int ret = -EFAULT, i;109 u8 temp, sz = 0;110 111 if (!hc) {112 pr_err("host controller is not configured\n");113 return ret;114 }115 116 mutex_lock(&hc->lock);117 hc->done = false;118 if (smb_hc_read(hc, ACPI_SMB_PROTOCOL, &temp))119 goto end;120 if (temp) {121 ret = -EBUSY;122 goto end;123 }124 smb_hc_write(hc, ACPI_SMB_COMMAND, command);125 if (!(protocol & 0x01)) {126 smb_hc_write(hc, ACPI_SMB_BLOCK_COUNT, length);127 for (i = 0; i < length; ++i)128 smb_hc_write(hc, ACPI_SMB_DATA + i, data[i]);129 }130 smb_hc_write(hc, ACPI_SMB_ADDRESS, address << 1);131 smb_hc_write(hc, ACPI_SMB_PROTOCOL, protocol);132 /*133 * Wait for completion. Save the status code, data size,134 * and data into the return package (if required by the protocol).135 */136 ret = wait_transaction_complete(hc, 1000);137 if (ret || !(protocol & 0x01))138 goto end;139 switch (protocol) {140 case SMBUS_RECEIVE_BYTE:141 case SMBUS_READ_BYTE:142 sz = 1;143 break;144 case SMBUS_READ_WORD:145 sz = 2;146 break;147 case SMBUS_READ_BLOCK:148 if (smb_hc_read(hc, ACPI_SMB_BLOCK_COUNT, &sz)) {149 ret = -EFAULT;150 goto end;151 }152 sz &= 0x1f;153 break;154 }155 for (i = 0; i < sz; ++i)156 smb_hc_read(hc, ACPI_SMB_DATA + i, &data[i]);157 end:158 mutex_unlock(&hc->lock);159 return ret;160}161 162int acpi_smbus_read(struct acpi_smb_hc *hc, u8 protocol, u8 address,163 u8 command, u8 *data)164{165 return acpi_smbus_transaction(hc, protocol, address, command, data, 0);166}167 168EXPORT_SYMBOL_GPL(acpi_smbus_read);169 170int acpi_smbus_write(struct acpi_smb_hc *hc, u8 protocol, u8 address,171 u8 command, u8 *data, u8 length)172{173 return acpi_smbus_transaction(hc, protocol, address, command, data, length);174}175 176EXPORT_SYMBOL_GPL(acpi_smbus_write);177 178int acpi_smbus_register_callback(struct acpi_smb_hc *hc,179 smbus_alarm_callback callback, void *context)180{181 mutex_lock(&hc->lock);182 hc->callback = callback;183 hc->context = context;184 mutex_unlock(&hc->lock);185 return 0;186}187 188EXPORT_SYMBOL_GPL(acpi_smbus_register_callback);189 190int acpi_smbus_unregister_callback(struct acpi_smb_hc *hc)191{192 mutex_lock(&hc->lock);193 hc->callback = NULL;194 hc->context = NULL;195 mutex_unlock(&hc->lock);196 acpi_os_wait_events_complete();197 return 0;198}199 200EXPORT_SYMBOL_GPL(acpi_smbus_unregister_callback);201 202static inline void acpi_smbus_callback(void *context)203{204 struct acpi_smb_hc *hc = context;205 if (hc->callback)206 hc->callback(hc->context);207}208 209static int smbus_alarm(void *context)210{211 struct acpi_smb_hc *hc = context;212 union acpi_smb_status status;213 u8 address;214 if (smb_hc_read(hc, ACPI_SMB_STATUS, &status.raw))215 return 0;216 /* Check if it is only a completion notify */217 if (status.fields.done && status.fields.status == SMBUS_OK) {218 hc->done = true;219 wake_up(&hc->wait);220 }221 if (!status.fields.alarm)222 return 0;223 mutex_lock(&hc->lock);224 smb_hc_read(hc, ACPI_SMB_ALARM_ADDRESS, &address);225 status.fields.alarm = 0;226 smb_hc_write(hc, ACPI_SMB_STATUS, status.raw);227 /* We are only interested in events coming from known devices */228 switch (address >> 1) {229 case ACPI_SBS_CHARGER:230 case ACPI_SBS_MANAGER:231 case ACPI_SBS_BATTERY:232 acpi_os_execute(OSL_NOTIFY_HANDLER,233 acpi_smbus_callback, hc);234 }235 mutex_unlock(&hc->lock);236 return 0;237}238 239typedef int (*acpi_ec_query_func) (void *data);240 241extern int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit,242 acpi_handle handle, acpi_ec_query_func func,243 void *data);244 245static int acpi_smbus_hc_add(struct acpi_device *device)246{247 int status;248 unsigned long long val;249 struct acpi_smb_hc *hc;250 251 if (!device)252 return -EINVAL;253 254 status = acpi_evaluate_integer(device->handle, "_EC", NULL, &val);255 if (ACPI_FAILURE(status)) {256 pr_err("error obtaining _EC.\n");257 return -EIO;258 }259 260 strcpy(acpi_device_name(device), ACPI_SMB_HC_DEVICE_NAME);261 strcpy(acpi_device_class(device), ACPI_SMB_HC_CLASS);262 263 hc = kzalloc(sizeof(struct acpi_smb_hc), GFP_KERNEL);264 if (!hc)265 return -ENOMEM;266 mutex_init(&hc->lock);267 init_waitqueue_head(&hc->wait);268 269 hc->ec = acpi_driver_data(acpi_dev_parent(device));270 hc->offset = (val >> 8) & 0xff;271 hc->query_bit = val & 0xff;272 device->driver_data = hc;273 274 acpi_ec_add_query_handler(hc->ec, hc->query_bit, NULL, smbus_alarm, hc);275 dev_info(&device->dev, "SBS HC: offset = 0x%0x, query_bit = 0x%0x\n",276 hc->offset, hc->query_bit);277 278 return 0;279}280 281extern void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit);282 283static void acpi_smbus_hc_remove(struct acpi_device *device)284{285 struct acpi_smb_hc *hc;286 287 if (!device)288 return;289 290 hc = acpi_driver_data(device);291 acpi_ec_remove_query_handler(hc->ec, hc->query_bit);292 acpi_os_wait_events_complete();293 kfree(hc);294 device->driver_data = NULL;295}296 297module_acpi_driver(acpi_smb_hc_driver);298 299MODULE_LICENSE("GPL");300MODULE_AUTHOR("Alexey Starikovskiy");301MODULE_DESCRIPTION("ACPI SMBus HC driver");302