From: Jean Delvare <khali-PUYAD+kWke1g9hUCZPvPmw@public.gmane.org>
To: Crane Cai <crane.cai-5C7GfCeVMHo@public.gmane.org>
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Subject: Re: [PATCH v2] I2C: add driver for SMBus Control Method Interface
Date: Wed, 16 Sep 2009 15:48:46 +0200 [thread overview]
Message-ID: <20090916154846.70413d42@hyperion.delvare> (raw)
In-Reply-To: <20090916091825.GA29707@crane-desktop>
Hi Crane,
On Wed, 16 Sep 2009 17:18:25 +0800, Crane Cai wrote:
> This driver supports the SMBus Control Method Interface. It needs BIOS declare
> ACPI control methods which described in SMBus Control Method Interface Spec.
> http://smbus.org/specs/smbus_cmi10.pdf
>
> Hi Jean,
> This driver is modified as you commented last time and it is verified OK. Could
> you put some time to review and apply it?
Here you go:
> Thanks.
>
> Signed-off-by: Crane Cai <crane.cai-5C7GfCeVMHo@public.gmane.org>
> ---
> drivers/i2c/busses/Kconfig | 11 +
> drivers/i2c/busses/Makefile | 3 +
> drivers/i2c/busses/i2c-cmi.c | 442 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 456 insertions(+), 0 deletions(-)
> create mode 100644 drivers/i2c/busses/i2c-cmi.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 8206442..afbc468 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -761,4 +761,15 @@ config SCx200_ACB
> This support is also available as a module. If so, the module
> will be called scx200_acb.
>
> +config I2C_CMI
> + tristate "SMBus Control Method Interface"
> + depends on ACPI
> + help
> + This driver supports the SMBus Control Method Interface. It needs the
> + BIOS to declare ACPI control methods as described in the SMBus Control
> + Method Interface specification.
> +
> + To compile this driver as a module, choose M here:
> + the module will be called i2c-cmi.
> +
I believe we should create a specific part in the Kconfig menu for this
driver, as we just did for ACPI-based hardware monitoring drivers.
> endmenu
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index e654263..4e83162 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -2,6 +2,9 @@
> # Makefile for the i2c bus drivers.
> #
>
> +# SMBus CMI driver
> +obj-$(CONFIG_I2C_CMI) += i2c-cmi.o
> +
> # PC SMBus host controller drivers
> obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o
> obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o
> diff --git a/drivers/i2c/busses/i2c-cmi.c b/drivers/i2c/busses/i2c-cmi.c
> new file mode 100644
> index 0000000..555b482
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-cmi.c
> @@ -0,0 +1,442 @@
> +/*
> + * SMBus driver for ACPI SMBus CMI
> + *
> + * Copyright (C) 2009 Crane Cai <crane.cai-5C7GfCeVMHo@public.gmane.org>
> + *
> + * 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/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>
> +
> +#define ACPI_SMBUS_HC_COMPONENT 0x00080000
This one isn't used anywhere. Is this OK?
> +#define ACPI_SMBUS_HC_CLASS "smbus"
> +#define ACPI_SMBUS_HC_DEVICE_NAME "cmi"
> +
> +ACPI_MODULE_NAME("smbus_cmi");
> +
> +struct smbus_methods_t {
> + const char *mt_info;
> + const char *mt_sbr;
> + const char *mt_sbw;
I can see that adding const here required casts elsewhere in the code.
I didn't expect that, so it's probably better to remove the const here
to avoid the ugly casts.
> +};
> +
> +struct acpi_smbus_cmi {
> + acpi_handle handle;
> + struct i2c_adapter adapter;
> + u8 cap_info:1;
> + u8 cap_read:1;
> + u8 cap_write:1;
> +};
> +
> +static const struct smbus_methods_t smbus_methods = {
> + .mt_info = "_SBI",
> + .mt_sbr = "_SBR",
> + .mt_sbw = "_SBW",
> +};
> +
> +static const struct acpi_device_id acpi_smbus_cmi_ids[] = {
> + {"SMBUS01", 0},
> + {"", 0}
> +};
> +
> +#define ACPI_SMBUS_STATUS_OK 0x00
> +#define ACPI_SMBUS_STATUS_FAIL 0x07
> +#define ACPI_SMBUS_STATUS_DNAK 0x10
> +#define ACPI_SMBUS_STATUS_DERR 0x11
> +#define ACPI_SMBUS_STATUS_CMD_DENY 0x12
> +#define ACPI_SMBUS_STATUS_UNKNOWN 0x13
> +#define ACPI_SMBUS_STATUS_ACC_DENY 0x17
> +#define ACPI_SMBUS_STATUS_TIMEOUT 0x18
> +#define ACPI_SMBUS_STATUS_NOTSUP 0x19
> +#define ACPI_SMBUS_STATUS_BUSY 0x1A
> +#define ACPI_SMBUS_STATUS_PEC 0x1F
> +
> +#define ACPI_SMBUS_PRTCL_WRITE 0x00
> +#define ACPI_SMBUS_PRTCL_READ 0x01
> +#define ACPI_SMBUS_PRTCL_QUICK 0x02
> +#define ACPI_SMBUS_PRTCL_BYTE 0x04
> +#define ACPI_SMBUS_PRTCL_BYTE_DATA 0x06
> +#define ACPI_SMBUS_PRTCL_WORD_DATA 0x08
> +#define ACPI_SMBUS_PRTCL_BLOCK_DATA 0x0a
> +
> +
> +static int
> +acpi_smbus_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;
> + 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 *method;
> + int len = 0;
> +
> + dev_dbg(&adap->dev, "access size: %d %s\n", size,
> + (read_write) ? "READ" : "WRITE");
> + switch (size) {
> + case I2C_SMBUS_QUICK:
> + protocol = ACPI_SMBUS_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_SMBUS_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_SMBUS_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_SMBUS_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_SMBUS_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:
> + dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
> + return -EOPNOTSUPP;
> + }
> +
> + if (read_write == I2C_SMBUS_READ) {
> + protocol |= ACPI_SMBUS_PRTCL_READ;
> + method = (char *)smbus_methods.mt_sbr;
> + input.count = 3;
> + } else {
> + protocol |= ACPI_SMBUS_PRTCL_WRITE;
> + method = (char *)smbus_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, method, &input,
> + &buffer);
> + if (ACPI_FAILURE(status)) {
> + ACPI_ERROR((AE_INFO, "Evaluating %s: %i", method, status));
> + return -EIO;
> + }
> +
> + pkg = buffer.pointer;
> + if (pkg && pkg->type == ACPI_TYPE_PACKAGE)
> + obj = pkg->package.elements;
> + else {
> + ACPI_ERROR((AE_INFO, "Invalid argument type"));
> + result = -EIO;
> + goto out;
> + }
> + if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
> + ACPI_ERROR((AE_INFO, "Invalid argument type"));
> + result = -EIO;
> + goto out;
> + }
> +
> + result = obj->integer.value;
> + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s return status: %i\n",
> + method, result));
> +
> + switch (result) {
> + case ACPI_SMBUS_STATUS_OK:
> + break;
> + case ACPI_SMBUS_STATUS_BUSY:
> + result = -EBUSY;
> + goto out;
> + case ACPI_SMBUS_STATUS_TIMEOUT:
> + result = -ETIMEDOUT;
> + goto out;
> + case ACPI_SMBUS_STATUS_DNAK:
> + result = -ENXIO;
> + goto out;
> + default:
> + result = -EIO;
> + goto out;
> + }
> +
> + if (read_write == I2C_SMBUS_WRITE || size == I2C_SMBUS_QUICK)
> + goto out;
> +
> + obj = pkg->package.elements + 1;
> + if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
> + ACPI_ERROR((AE_INFO, "Invalid argument type"));
> + 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_ERROR((AE_INFO, "Invalid argument type"));
> + result = -EIO;
> + goto out;
> + }
> + if (len == 2)
> + data->word = obj->integer.value;
> + else
> + data->byte = obj->integer.value;
> + break;
> + case I2C_SMBUS_BLOCK_DATA:
> + if (obj == NULL || obj->type != ACPI_TYPE_BUFFER) {
> + ACPI_ERROR((AE_INFO, "Invalid argument type"));
> + result = -EIO;
> + goto out;
> + }
> + if (len == 0 || len > I2C_SMBUS_BLOCK_MAX)
> + return -EPROTO;
> + data->block[0] = len;
> + memcpy(data->block + 1, obj->buffer.pointer, len);
> + break;
> + }
> +
> +out:
> + kfree(buffer.pointer);
> + dev_dbg(&adap->dev, "Transaction status: %i\n", result);
> + return result;
> +}
> +
> +static u32 acpi_smbus_cmi_func(struct i2c_adapter *adapter)
> +{
> + struct acpi_smbus_cmi *smbus_cmi = adapter->algo_data;
> + u32 ret;
> +
> + ret = smbus_cmi->cap_read | smbus_cmi->cap_write ?
> + I2C_FUNC_SMBUS_QUICK : 0;
> +
> + ret |= smbus_cmi->cap_read ?
> + (I2C_FUNC_SMBUS_READ_BYTE |
> + I2C_FUNC_SMBUS_READ_BYTE_DATA |
> + I2C_FUNC_SMBUS_READ_WORD_DATA |
> + I2C_FUNC_SMBUS_READ_BLOCK_DATA) : 0;
> +
> + ret |= smbus_cmi->cap_write ?
> + (I2C_FUNC_SMBUS_WRITE_BYTE |
> + I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
> + I2C_FUNC_SMBUS_WRITE_WORD_DATA |
> + I2C_FUNC_SMBUS_WRITE_BLOCK_DATA) : 0;
> +
> + return ret;
> +}
> +
> +static const struct i2c_algorithm acpi_smbus_cmi_algorithm = {
> + .smbus_xfer = acpi_smbus_cmi_access,
> + .functionality = acpi_smbus_cmi_func,
> +};
> +
> +static int acpi_smbus_cmi_add_cap(struct acpi_smbus_cmi *smbus_cmi,
> + const char *name)
> +{
> + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> + union acpi_object *obj;
> + int i;
> + acpi_status status;
> +
> + if (!strcmp(name, smbus_methods.mt_info)) {
> + status = acpi_evaluate_object(smbus_cmi->handle,
> + (char *)smbus_methods.mt_info,
> + NULL, &buffer);
> + if (ACPI_FAILURE(status)) {
> + ACPI_ERROR((AE_INFO, "Evaluating %s: %i",
> + smbus_methods.mt_info, status));
> + return -EIO;
> + }
> +
> + obj = buffer.pointer;
> + if (obj && obj->type == ACPI_TYPE_PACKAGE)
> + obj = obj->package.elements;
> + else {
> + ACPI_ERROR((AE_INFO, "Invalid argument type"));
> + kfree(buffer.pointer);
> + return -EIO;
> + }
> +
> + if (obj->type != ACPI_TYPE_INTEGER) {
> + ACPI_ERROR((AE_INFO, "Invalid argument type"));
Shouldn't you free buffer.pointer here?
> + return -EIO;
> + } else
> + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "SMBus CMI Version %x"
> + "\n", (int)obj->integer.value));
> +
> + obj = obj->package.elements + 1;
> + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "SMB_INFO:\n"));
> + for (i = 0; i < obj->buffer.length; i++) {
> + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%02x ",
> + *(obj->buffer.pointer + i)));
> + if (i > 0 && (i % 16) == 0 && i != obj->buffer.length-1)
> + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "\n"));
Did you try this? I suspect this will insert log level codes all around
the place and the output will be a mess. Fragmented logging is strongly
discouraged.
> + }
> + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "\n"));
> +
> + kfree(buffer.pointer);
> + smbus_cmi->cap_info = 1;
> + } else if (!strcmp(name, smbus_methods.mt_sbr))
> + smbus_cmi->cap_read = 1;
> + else if (!strcmp(name, smbus_methods.mt_sbw))
> + smbus_cmi->cap_write = 1;
> + else
> + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "CMI Methods: %s\n", name));
It won't be clear why this is printed. What about: "Unsupported CMI
method\n"?
> +
> + return 0;
> +}
> +
> +static acpi_status acpi_smbus_cmi_query_methods(acpi_handle handle, u32 level,
> + void *context, void **return_value)
> +{
> + char node_name[5];
> + struct acpi_buffer buffer = { sizeof(node_name), node_name };
> + struct acpi_smbus_cmi *smbus_cmi = context;
> + acpi_status status;
> +
> + status = acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer);
> +
> + if (ACPI_SUCCESS(status))
> + acpi_smbus_cmi_add_cap(smbus_cmi, node_name);
> +
> + return AE_OK;
> +}
> +
> +static int acpi_smbus_cmi_add(struct acpi_device *device)
> +{
> + struct acpi_smbus_cmi *smbus_cmi;
> +
> + smbus_cmi = kzalloc(sizeof(struct acpi_smbus_cmi), GFP_KERNEL);
> + if (!smbus_cmi)
> + return -ENOMEM;
> +
> + smbus_cmi->handle = device->handle;
> + strcpy(acpi_device_name(device), ACPI_SMBUS_HC_DEVICE_NAME);
> + strcpy(acpi_device_class(device), ACPI_SMBUS_HC_CLASS);
> + device->driver_data = smbus_cmi;
> + smbus_cmi->cap_info = 0;
> + smbus_cmi->cap_read = 0;
> + smbus_cmi->cap_write = 0;
> +
> + acpi_walk_namespace(ACPI_TYPE_METHOD, smbus_cmi->handle, 1,
> + acpi_smbus_cmi_query_methods, smbus_cmi, NULL);
> +
> + if (smbus_cmi->cap_info == 0)
> + goto err;
> +
> + snprintf(smbus_cmi->adapter.name, sizeof(smbus_cmi->adapter.name),
> + "SMBus CMI adapter at %s-%s",
> + acpi_device_name(device),
> + acpi_device_uid(device));
"at" sounds strange, as what follows isn't an address. I'd rather use:
"SMBus CMI adapter %s (%s)",
acpi_device_name(device),
acpi_device_uid(device)
> + smbus_cmi->adapter.owner = THIS_MODULE;
> + smbus_cmi->adapter.algo = &acpi_smbus_cmi_algorithm;
> + smbus_cmi->adapter.algo_data = smbus_cmi;
> + smbus_cmi->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> + smbus_cmi->adapter.dev.parent = &device->dev;
> +
> + if (i2c_add_adapter(&smbus_cmi->adapter)) {
> + dev_err(&device->dev, "Couldn't register adapter!\n");
> + goto err;
> + }
> +
> + return 0;
> +
> +err:
> + kfree(smbus_cmi);
> + device->driver_data = NULL;
> + return -EIO;
> +}
> +
> +static int acpi_smbus_cmi_remove(struct acpi_device *device, int type)
> +{
> + struct acpi_smbus_cmi *smbus_cmi = acpi_driver_data(device);
> +
> + i2c_del_adapter(&smbus_cmi->adapter);
> + kfree(smbus_cmi);
> + smbus_cmi = NULL;
This is a no-op, smbus_cmi is a local variable. What you really want to
do is:
device->driver_data = NULL;
> +
> + return 0;
> +}
> +
> +static struct acpi_driver acpi_smbus_cmi_driver = {
> + .name = ACPI_SMBUS_HC_DEVICE_NAME,
> + .class = ACPI_SMBUS_HC_CLASS,
> + .ids = acpi_smbus_cmi_ids,
> + .ops = {
> + .add = acpi_smbus_cmi_add,
> + .remove = acpi_smbus_cmi_remove,
> + },
> +};
> +
> +static int __init acpi_smbus_cmi_init(void)
> +{
> + int result;
> +
> + result = acpi_bus_register_driver(&acpi_smbus_cmi_driver);
> +
> + return result;
This can be simplified to:
return acpi_bus_register_driver(&acpi_smbus_cmi_driver);
> +}
> +
> +static void __exit acpi_smbus_cmi_exit(void)
> +{
> + acpi_bus_unregister_driver(&acpi_smbus_cmi_driver);
> +}
> +
> +module_init(acpi_smbus_cmi_init);
> +module_exit(acpi_smbus_cmi_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Crane Cai <crane.cai-5C7GfCeVMHo@public.gmane.org>");
> +MODULE_DESCRIPTION("ACPI SMBus CMI driver");
If you are fine with my latest proposals, I'll implement them myself,
no need to resend a patch. Thanks a lot for writing the driver!
I am considering renaming the driver to i2c-scmi, too. "i2c-cmi"
seems somewhat short and I fear name collisions in the future.
--
Jean Delvare
next prev parent reply other threads:[~2009-09-16 13:48 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2009-09-16 9:18 [PATCH v2] I2C: add driver for SMBus Control Method Interface Crane Cai
2009-09-16 13:48 ` Jean Delvare [this message]
[not found] ` <20090916154846.70413d42-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-09-17 4:46 ` Crane Cai
2009-09-17 9:45 ` Jean Delvare
[not found] ` <20090917114553.7abddd1e-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-09-18 0:59 ` Crane Cai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20090916154846.70413d42@hyperion.delvare \
--to=khali-puyad+kwke1g9huczpvpmw@public.gmane.org \
--cc=crane.cai-5C7GfCeVMHo@public.gmane.org \
--cc=linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).