[PATCH] ACPI: add driver for SMBus Control Method Interface

From: Crane Cai
Date: Wed Jul 15 2009 - 02:18:17 EST


This driver supports the SMBus Control Method Interface. It needs BIOS declare
ACPI control methods via SMBus Control Method Interface Spec.

Please apply

Signed-off-by: Crane Cai <crane.cai@xxxxxxx>
---
drivers/acpi/Kconfig | 11 ++
drivers/acpi/Makefile | 1 +
drivers/acpi/cmi_i2c.c | 391 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 403 insertions(+), 0 deletions(-)
create mode 100644 drivers/acpi/cmi_i2c.c

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 7ec7d88..ce7cf38 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -333,4 +333,15 @@ config ACPI_SBS
To compile this driver as a module, choose M here:
the modules will be called sbs and sbshc.

+config ACPI_I2C
+ tristate "SMBus Control Method Interface"
+ depends on X86
+ help
+ This driver supports the SMBus Control Method Interface. It needs
+ BIOS declare ACPI control methods via SMBus Control Method Interface
+ Spec.
+
+ To compile this driver as a module, choose M here:
+ the modules will be called sbs and sbshc.
+
endif # ACPI
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 03a985b..a76c351 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
obj-$(CONFIG_ACPI_BATTERY) += battery.o
obj-$(CONFIG_ACPI_SBS) += sbshc.o
obj-$(CONFIG_ACPI_SBS) += sbs.o
+obj-$(CONFIG_ACPI_I2C) += cmi_i2c.o

# processor has its own "processor." module_param namespace
processor-y := processor_core.o processor_throttling.o
diff --git a/drivers/acpi/cmi_i2c.c b/drivers/acpi/cmi_i2c.c
new file mode 100644
index 0000000..69f3202
--- /dev/null
+++ b/drivers/acpi/cmi_i2c.c
@@ -0,0 +1,391 @@
+/*
+ * SMBus driver for ACPI SMBus CMI
+ *
+ * Copyright (C) 2009 Crane Cai <crane.cai@xxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/stddef.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/delay.h>
+
+#define ACPI_SMB_HC_COMPONENT 0x00080000
+#define ACPI_SMB_HC_CLASS "smbus"
+#define ACPI_SMB_HC_DEVICE_NAME "smbus cmi"
+#define SMB_HC_DEVICE_NAME "SMBus CMI adapter"
+
+#define _COMPONENT ACPI_SMB_HC_COMPONENT
+
+ACPI_MODULE_NAME("smbus_cmi");
+
+struct smbus_methods {
+ char *mt_info;
+ char *mt_sbr;
+ char *mt_sbw;
+};
+
+struct acpi_smbus_cmi {
+ acpi_handle handle;
+ struct i2c_adapter adapter;
+ struct smbus_methods *methods;
+};
+
+static const struct smbus_methods smb_mtds = {
+ .mt_info = "_SBI",
+ .mt_sbr = "_SBR",
+ .mt_sbw = "_SBW",
+};
+
+static const struct acpi_device_id i2c_device_ids[] = {
+ {"SMBUS01", 0},
+ {"", 0},
+};
+
+static int acpi_smb_cmi_add(struct acpi_device *device);
+static int acpi_smb_cmi_remove(struct acpi_device *device, int type);
+
+static struct acpi_driver acpi_smb_cmi_driver = {
+ .name = ACPI_SMB_HC_DEVICE_NAME,
+ .class = ACPI_SMB_HC_CLASS,
+ .ids = i2c_device_ids,
+ .ops = {
+ .add = acpi_smb_cmi_add,
+ .remove = acpi_smb_cmi_remove,
+ },
+};
+
+#define ACPI_SMB_STATUS_OK 0x00
+#define ACPI_SMB_STATUS_FAIL 0x07
+#define ACPI_SMB_STATUS_DNAK 0x10
+#define ACPI_SMB_STATUS_DERR 0x11
+#define ACPI_SMB_STATUS_CMD_DENY 0x12
+#define ACPI_SMB_STATUS_UNKNOWN 0x13
+#define ACPI_SMB_STATUS_ACC_DENY 0x17
+#define ACPI_SMB_STATUS_TIMEOUT 0x18
+#define ACPI_SMB_STATUS_NOTSUP 0x19
+#define ACPI_SMB_STATUS_BUSY 0x1A
+#define ACPI_SMB_STATUS_PEC 0x1F
+
+#define ACPI_SMB_PRTCL_WRITE 0x0
+#define ACPI_SMB_PRTCL_READ 0x01
+#define ACPI_SMB_PRTCL_QUICK 0x02
+#define ACPI_SMB_PRTCL_BYTE 0x04
+#define ACPI_SMB_PRTCL_BYTE_DATA 0x06
+#define ACPI_SMB_PRTCL_WORD_DATA 0x08
+#define ACPI_SMB_PRTCL_BLOCK_DATA 0x0a
+#define ACPI_SMB_PRTCL_PROC_CALL 0x0c
+#define ACPI_SMB_PRTCL_BLOCK_PROC_CALL 0x0d
+#define ACPI_SMB_PRTCL_PEC 0x80
+
+
+static int
+acpi_smb_cmi_access(struct i2c_adapter *adap, u16 addr, unsigned short flags,
+ char read_write, u8 command, int size,
+ union i2c_smbus_data *data)
+{
+ int result = 0;
+ struct acpi_smbus_cmi *smbus_cmi = adap->algo_data;
+ unsigned char protocol, len = 0;
+ acpi_status status = 0;
+ struct acpi_object_list input;
+ union acpi_object mt_params[5];
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ union acpi_object *pkg;
+ char *mthd;
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ protocol = ACPI_SMB_PRTCL_QUICK;
+ command = 0;
+ if (read_write == I2C_SMBUS_WRITE) {
+ mt_params[3].type = ACPI_TYPE_INTEGER;
+ mt_params[3].integer.value = 0;
+ mt_params[4].type = ACPI_TYPE_INTEGER;
+ mt_params[4].integer.value = 0;
+ }
+ break;
+
+ case I2C_SMBUS_BYTE:
+ protocol = ACPI_SMB_PRTCL_BYTE;
+ if (read_write == I2C_SMBUS_WRITE) {
+ mt_params[3].type = ACPI_TYPE_INTEGER;
+ mt_params[3].integer.value = 0;
+ mt_params[4].type = ACPI_TYPE_INTEGER;
+ mt_params[4].integer.value = 0;
+ } else {
+ command = 0;
+ }
+ break;
+
+ case I2C_SMBUS_BYTE_DATA:
+ protocol = ACPI_SMB_PRTCL_BYTE_DATA;
+ if (read_write == I2C_SMBUS_WRITE) {
+ mt_params[3].type = ACPI_TYPE_INTEGER;
+ mt_params[3].integer.value = 1;
+ mt_params[4].type = ACPI_TYPE_INTEGER;
+ mt_params[4].integer.value = data->byte;
+ }
+ break;
+
+ case I2C_SMBUS_WORD_DATA:
+ protocol = ACPI_SMB_PRTCL_WORD_DATA;
+ if (read_write == I2C_SMBUS_WRITE) {
+ mt_params[3].type = ACPI_TYPE_INTEGER;
+ mt_params[3].integer.value = 2;
+ mt_params[4].type = ACPI_TYPE_INTEGER;
+ mt_params[4].integer.value = data->word;
+ }
+ break;
+
+ case I2C_SMBUS_BLOCK_DATA:
+ protocol = ACPI_SMB_PRTCL_BLOCK_DATA;
+ if (read_write == I2C_SMBUS_WRITE) {
+ len = data->block[0];
+ if (len == 0 || len > I2C_SMBUS_BLOCK_MAX)
+ return -EINVAL;
+ mt_params[3].type = ACPI_TYPE_INTEGER;
+ mt_params[3].integer.value = len;
+ mt_params[4].type = ACPI_TYPE_BUFFER;
+ mt_params[4].buffer.pointer = data->block + 1;
+ }
+ break;
+
+ default:
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "SMBus CMI adapter: "
+ "Unsupported transaction %d\n", size));
+ return -EOPNOTSUPP;
+ }
+
+ if (read_write == I2C_SMBUS_READ) {
+ protocol |= ACPI_SMB_PRTCL_READ;
+ mthd = smbus_cmi->methods->mt_sbr;
+ input.count = 3;
+ } else {
+ protocol |= ACPI_SMB_PRTCL_WRITE;
+ mthd = smbus_cmi->methods->mt_sbw;
+ input.count = 5;
+ }
+
+ input.pointer = mt_params;
+ mt_params[0].type = ACPI_TYPE_INTEGER;
+ mt_params[0].integer.value = protocol;
+ mt_params[1].type = ACPI_TYPE_INTEGER;
+ mt_params[1].integer.value = addr;
+ mt_params[2].type = ACPI_TYPE_INTEGER;
+ mt_params[2].integer.value = command;
+
+ status = acpi_evaluate_object(smbus_cmi->handle, mthd, &input, &buffer);
+ if (ACPI_FAILURE(status)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error evaluate %s\n", mthd));
+ return -EIO;
+ }
+
+ pkg = buffer.pointer;
+ if (pkg && pkg->type == ACPI_TYPE_PACKAGE)
+ obj = pkg->package.elements;
+ else {
+ result = -EIO;
+ goto out;
+ }
+ if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "SMBus status object type \
+ error\n"));
+ result = -EIO;
+ goto out;
+ }
+
+ result = obj->integer.value;
+ switch (result) {
+ case ACPI_SMB_STATUS_OK:
+ break;
+ case ACPI_SMB_STATUS_BUSY:
+ result = -EBUSY;
+ goto out;
+ case ACPI_SMB_STATUS_TIMEOUT:
+ result = -ETIMEDOUT;
+ goto out;
+ case ACPI_SMB_STATUS_DNAK:
+ result = -ENXIO;
+ goto out;
+ default:
+ result = -EIO;
+ goto out;
+ }
+
+ if (read_write == I2C_SMBUS_WRITE)
+ goto out;
+
+ obj = pkg->package.elements + 1;
+ if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "SMBus return package object \
+ type error\n"));
+ result = -EIO;
+ goto out;
+ }
+
+ len = obj->integer.value;
+ obj = pkg->package.elements + 2;
+ switch (size) {
+ case I2C_SMBUS_BYTE:
+ case I2C_SMBUS_BYTE_DATA:
+ case I2C_SMBUS_WORD_DATA:
+ if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "SMBus return package \
+ object type error\n"));
+ result = -EIO;
+ goto out;
+ }
+ if (len == 2)
+ data->word = obj->integer.value & 0xffff;
+ else
+ data->byte = obj->integer.value & 0xff;
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ if (obj == NULL || obj->type != ACPI_TYPE_BUFFER) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "SMBus return package \
+ object type error\n"));
+ result = -EIO;
+ goto out;
+ }
+ data->block[0] = len;
+ if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
+ return -EPROTO;
+ memcpy(data->block + 1, obj->buffer.pointer, len);
+ break;
+ }
+
+out:
+ kfree(buffer.pointer);
+ return result;
+}
+
+static u32 acpi_smb_cmi_func(struct i2c_adapter *adapter)
+{
+
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm acpi_smbus_cmi_algorithm = {
+ .smbus_xfer = acpi_smb_cmi_access,
+ .functionality = acpi_smb_cmi_func,
+};
+
+static int acpi_smb_cmi_add(struct acpi_device *device)
+{
+ int status;
+ struct acpi_smbus_cmi *smb_cmi;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+
+ if (!device)
+ return -EINVAL;
+
+ smb_cmi = kzalloc(sizeof(struct acpi_smbus_cmi), GFP_KERNEL);
+ if (!smb_cmi)
+ return -ENOMEM;
+
+ smb_cmi->handle = device->handle;
+ strcpy(acpi_device_name(device), ACPI_SMB_HC_DEVICE_NAME);
+ strcpy(acpi_device_class(device), ACPI_SMB_HC_CLASS);
+ device->driver_data = smb_cmi;
+ smb_cmi->methods = (struct smbus_methods *)(&smb_mtds);
+
+ status = acpi_evaluate_object(smb_cmi->handle,
+ smb_cmi->methods->mt_info,
+ NULL, &buffer);
+ if (ACPI_FAILURE(status)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error obtaining _SBI\n"));
+ goto err;
+ }
+
+ obj = buffer.pointer;
+ if (obj && obj->type == ACPI_TYPE_PACKAGE)
+ obj = obj->package.elements;
+ else {
+ kfree(buffer.pointer);
+ goto err;
+ }
+
+ if (obj->type != ACPI_TYPE_INTEGER) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "SMBus CMI Version object type \
+ error\n"));
+ } else
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "SMBus CMI Version %0x\n",
+ (int)obj->integer.value));
+ kfree(buffer.pointer);
+
+ snprintf(smb_cmi->adapter.name, sizeof(smb_cmi->adapter.name),
+ "SMBus CMI adapter");
+ smb_cmi->adapter.owner = THIS_MODULE;
+ smb_cmi->adapter.algo = &acpi_smbus_cmi_algorithm;
+ smb_cmi->adapter.algo_data = smb_cmi;
+ smb_cmi->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+ smb_cmi->adapter.dev.parent = &device->dev;
+
+ if (i2c_add_adapter(&smb_cmi->adapter)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN,
+ "SMBus CMI adapter: Failed to register adapter\n"));
+ kfree(smb_cmi);
+ return -EIO;
+ }
+
+ printk(KERN_INFO PREFIX "%s [%s]\n",
+ acpi_device_name(device), acpi_device_bid(device));
+
+ return AE_OK;
+
+err:
+ kfree(smb_cmi);
+ device->driver_data = NULL;
+ return -EIO;
+}
+
+static int acpi_smb_cmi_remove(struct acpi_device *device, int type)
+{
+ struct acpi_smbus_cmi *smbus_cmi;
+
+ if (!device)
+ return -EINVAL;
+
+ smbus_cmi = acpi_driver_data(device);
+
+ i2c_del_adapter(&smbus_cmi->adapter);
+ kfree(smbus_cmi);
+
+ return AE_OK;
+}
+
+static int __init acpi_smb_cmi_init(void)
+{
+ int result;
+
+ result = acpi_bus_register_driver(&acpi_smb_cmi_driver);
+ if (result < 0)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void __exit acpi_smb_cmi_exit(void)
+{
+ acpi_bus_unregister_driver(&acpi_smb_cmi_driver);
+}
+
+module_init(acpi_smb_cmi_init);
+module_exit(acpi_smb_cmi_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Crane Cai");
+MODULE_DESCRIPTION("ACPI SMBus CMI driver");
--
1.6.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/