brintos

brintos / linux-shallow public Read only

0
0
Text · 7.1 KiB · 16f2daa Raw
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