Re: [PATCH] I2C: add driver for SMBus Control Method Interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Crane,

On Thu, 27 Aug 2009 10:29:58 +0800, Crane Cai wrote:
> This driver supports the SMBus Control Method Interface. It needs BIOS declare
> ACPI control methods via SMBus Control Method Interface Spec.
> http://smbus.org/specs/smbus_cmi10.pdf
> 
> Hi Jean,
> This driver can give BIOS a chance to avoid SMBus access conflicts on runtime.
> And it obeys the SMBus CMI spec.
> Please apply.

This is very interesting. Do you happen to have, or know, systems which
actually implement this? Were you able to test your code?

How can I check if any of my systems do implement this?

Full review below, inline. I had many comments but they are really
small things, overall your code is very good and getting the driver in
shape for 2.6.32 seems totally feasible.

> 
> Signed-off-by: Crane Cai <crane.cai@xxxxxxx>
> ---
>  drivers/i2c/busses/Kconfig   |   11 ++
>  drivers/i2c/busses/Makefile  |    1 +
>  drivers/i2c/busses/cmi_i2c.c |  391 ++++++++++++++++++++++++++++++++++++++++++

To comply with the naming scheme of all other drivers, the driver file
should be named i2c-cmi.c.

>  3 files changed, 403 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/i2c/busses/cmi_i2c.c
> 
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 8206442..c4a5d6c 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 CMI_I2C

I2C_CMI

> +	tristate "SMBus Control Method Interface"
> +	depends on X86 && ACPI

Why depend on X86? As far as I know, IA64 systems can support ACPI too.
And this dependency is already handled by CONFIG_ACPI so drivers
shouldn't have to care.

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

I suggest rewording the second sentence as follows: "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 modules will be called cmi_i2c.

module (no s)

> +
>  endmenu
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index e654263..12806df 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_I2C_SIBYTE)	+= i2c-sibyte.o
>  obj-$(CONFIG_I2C_STUB)		+= i2c-stub.o
>  obj-$(CONFIG_SCx200_ACB)	+= scx200_acb.o
>  obj-$(CONFIG_SCx200_I2C)	+= scx200_i2c.o
> +obj-$(CONFIG_CMI_I2C)		+= cmi_i2c.o

The i2c-cmi driver should probably be listed first: if built into the
kernel, it should be loaded before native drivers.

>  
>  ifeq ($(CONFIG_I2C_DEBUG_BUS),y)
>  EXTRA_CFLAGS += -DDEBUG
> diff --git a/drivers/i2c/busses/cmi_i2c.c b/drivers/i2c/busses/cmi_i2c.c
> new file mode 100644
> index 0000000..69f3202
> --- /dev/null
> +++ b/drivers/i2c/busses/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>

This header file should never be included by in-tree drivers.

> +#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>

Your driver doesn't seem to delay or sleep, so not needed.

> +
> +#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"

This last define is not used anywhere.

> +
> +#define _COMPONENT		ACPI_SMB_HC_COMPONENT

Nor this one.

> +
> +ACPI_MODULE_NAME("smbus_cmi");
> +
> +struct smbus_methods {
> +	char *mt_info;
> +	char *mt_sbr;
> +	char *mt_sbw;

Shouldn't these be const char*?

> +};
> +
> +struct acpi_smbus_cmi {
> +	acpi_handle handle;
> +	struct i2c_adapter adapter;
> +	struct smbus_methods *methods;
> +};
> +
> +static const struct smbus_methods smb_mtds = {

I suggest that you never use "smb" as an abbreviation for "SMBus":
"smb" stands for a number of different things in computer science, so
it can easily get confusing. And "smbus" isn't much longer.

Likewise, "mtds" as an abbreviation for "methods" doesn't save much
space and somewhat hurts readability IMHO.

> +	.mt_info = "_SBI",
> +	.mt_sbr = "_SBR",
> +	.mt_sbw = "_SBW",
> +};

What's the point of having a per-device structure for this if all
devices end up using the same hard-coded structure?

> +
> +static const struct acpi_device_id i2c_device_ids[] = {

I would suggest a different name than i2c_device_ids, because it's
almost similar to an existing type (i2c_device_id). What about
"acpi_smbus_cmi_ids"?

> +	{"SMBUS01", 0},
> +	{"", 0},

Trailing comma isn't needed, as you have a list terminator.

> +};
> +
> +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,
> +		},

Bad indentation for the last curly brace (should be aligned with ".ops".

> +};

What about moving this driver definition later in the file, so that you
no longer need to forward-declare the _add and _remove methods?

> +
> +#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

0x00 for consistency.

> +#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

Can't see this one in the CMI specification, and your driver doesn't
use it anyway.

> +#define ACPI_SMB_PRTCL_PEC			0x80

It would also look better if the two blocks above had the same
indentation, and if you stuck to capitals or not capitals for
hexadecimal numbers.

> +
> +
> +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:

This assumes that ACPI_SMB_STATUS_OK == 0, which is true but is only a
coincidence.

> +		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;
> +	}

You might want to log error messages for unknown errors, and maybe
debug messages for known errors too.

> +
> +	if (read_write == I2C_SMBUS_WRITE)

I think you also want to quit here on size == SMBUS_QUICK. What the CMI
specification names "quick read" is really a write from a functional
perspective.

> +		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"));

Not the right way to split a string.

> +		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"));

Same here.

> +			result = -EIO;
> +			goto out;
> +		}
> +		if (len == 2)
> +			data->word = obj->integer.value & 0xffff;
> +		else
> +			data->byte = obj->integer.value & 0xff;

Masking is a no-op.

> +		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"));

Not the right way to split a string.

> +			result = -EIO;
> +			goto out;
> +		}
> +		data->block[0] = len;
> +		if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
> +			return -EPROTO;

You should check the value of len before copying it to data->block[0].
data->block[0] is a u8 so you might get wrapping issues.

> +		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;
> +}

It doesn't look correct to hard-code this. As I read the CMI
specification, each SMBus segment exposed may or may not support each
type of SMBus transaction. Unfortunately there doesn't seem to be any
way to know the exact subset of transactions that are supported. But at
least you could use the presence or absence of methods _SBR and _SBW to
determine whether reading, writing or both are supported.

I hope this limitation won't cause too much trouble... some drivers
really test for adapter functionality, and use different methods
depending on the result.

> +
> +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;

How could this ever happen?

> +
> +	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"));

This isn't how strings are split in C. Instead, do:

		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",

How does %0x differ from %x? This might be displayed nicer anyway, as
we know this byte is encoded in BCD.

> +					(int)obj->integer.value));
> +	kfree(buffer.pointer);

Maybe list some more information from the SMB_INFO object, in
particular the version of SMBus that is supported, and the number of
devices on the bus? Icing on the cake would be the complete list of
devices, but this can be added later.

> +
> +	snprintf(smb_cmi->adapter.name, sizeof(smb_cmi->adapter.name),
> +		"SMBus CMI adapter");

Isn't it possible for a given systems to have more than one SMBus
adapter of this type? The CMI specification suggests so. The i2c_adapter
name is supposed to be unique within a given system. We usually add the
device address or any other unique identifier at the end of the name.
As I read the CMI specification, we should be able to use _UID for this
purpose?

> +	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;

Why not just "goto err"?

> +	}
> +
> +	printk(KERN_INFO PREFIX "%s [%s]\n",
> +	       acpi_device_name(device), acpi_device_bid(device));

That's a little rough IMHO.

> +
> +	return AE_OK;

I am surprised that you mix regular error codes with ACPI-specific ones.

> +
> +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;

I fail to see how this would be possible.

> +
> +	smbus_cmi = acpi_driver_data(device);
> +
> +	i2c_del_adapter(&smbus_cmi->adapter);
> +	kfree(smbus_cmi);

Care to reset driver data to NULL?

> +
> +	return AE_OK;

Here again, I am surprised by the mix of regular error codes and ACPI
ones.

> +}
> +
> +static int __init acpi_smb_cmi_init(void)
> +{
> +	int result;
> +
> +	result = acpi_bus_register_driver(&acpi_smb_cmi_driver);
> +	if (result < 0)
> +		return -ENODEV;

Why don't you simply return the error code? Would be simpler and more
informative too.

> +
> +	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");

No e-mail address?

> +MODULE_DESCRIPTION("ACPI SMBus CMI driver");

And lastly a general comment: is it OK that you always use
ACPI_DEBUG_PRINT((ACPI_DB_WARN for all kernel messages, be they errors,
warning or informational?

-- 
Jean Delvare
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux