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