Accelerometer driver for STMicroeletronics-LIS331DL-three-axis-digital

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

 



Kalhan Trisal wrote:
>>From 0d9b356e19170242fcbb746744b7ccb5156211eb Mon Sep 17 00:00:00 2001
> From: Kalhan Trisal <kalhan.trisal at intel.com>
> Date: Tue, 11 Aug 2009 11:12:02 -0400
> Subject: [PATCH] STMicroeletronics LIS331DL three-axis digital accelerometer
> 
> This driver provides support for the LIS3331DL accelerometer
> accelerometer, connected to I2C. The accelerometer data is readable via
> sys/class/hwmon.
> 
> Signed-off-by: Kalhan Trisal <kalhan.trisal at intel.com>
Please read the various previous threads on where accelerometers should
go in the kernel.  One of the biggest elements of them is that hwmon
is not the right place. They typically aren't being used for low
update rate monitoring of hardware.  One option is IIO but thats still
waiting for someone to have time to review the core.  Take a look
at the lis3l02dq driver posted to lkml to see how this sort of device
would be handled (including the interrupts etc).

Anyhow, other than the where to put it issue, I have a few nitpicking
comments and queries below.
 
> ---
>  drivers/hwmon/Kconfig    |    9 ++
>  drivers/hwmon/Makefile   |    1 +
>  drivers/hwmon/lis331dl.c |  283 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 293 insertions(+), 0 deletions(-)
>  create mode 100755 drivers/hwmon/lis331dl.c
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 2d50166..ac6117a 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -975,6 +975,15 @@ config SENSORS_LIS3LV02D
>  	  Say Y here if you have an applicable laptop and want to experience
>  	  the awesome power of lis3lv02d.
>  
> +config SENSORS_LIS331DL
> +	tristate "STMicroeletronics LIS331DL three-axis digital accelerometer"
> +	depends on I2C_MRST
> +	default n
> +	help
> +	  This driver provides support for the LIS3331DL accelerometer
> +	  accelerometer, connected or I2C. The accelerometer data is readable via
> +	  sys/class/hwmon.
> +
>  config SENSORS_LIS3_SPI
>  	tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)"
>  	depends on !ACPI && SPI_MASTER && INPUT
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index b793dce..3b1e424 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -89,6 +89,7 @@ obj-$(CONFIG_SENSORS_VT8231)	+= vt8231.o
>  obj-$(CONFIG_SENSORS_W83627EHF)	+= w83627ehf.o
>  obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
>  obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
> +obj-$(CONFIG_SENSORS_LIS331DL)	+= lis331dl.o
>  
>  ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
>  EXTRA_CFLAGS += -DDEBUG
> diff --git a/drivers/hwmon/lis331dl.c b/drivers/hwmon/lis331dl.c
> new file mode 100755
> index 0000000..9b999a3
> --- /dev/null
> +++ b/drivers/hwmon/lis331dl.c
> @@ -0,0 +1,283 @@
> +/*
> + * lis331dl.c - ST LIS331DL  Accelerometer Driver
> + *
> + * Copyright (C) 2009 Intel Corp
> + *
> + *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *
> + * 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 of the License.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/hwmon-vid.h>
> +#include <linux/err.h>
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/sysfs.h>
> +
> +
> +MODULE_AUTHOR("Kalhan Trisal <kalhan.trisal at intel.com");
> +MODULE_DESCRIPTION("STMacroelectronics LIS331DL Accelerometer Driver");
> +MODULE_LICENSE("GPL v2");
> +
> +#define ACCEL_DATA_RATE_100HZ 0
> +#define ACCEL_DATA_RATE_400HZ 1
> +#define ACCEL_POWER_MODE_DOWN 0
> +#define ACCEL_POWER_MODE_ACTIVE 1
> +#define ACCEL_NORMAL_MODE 0
> +#define ACCEL_MEMORY_REBOOT 1
> +
> +/* internal return values */
> +
> +struct acclero_data {
> +	struct device *hwmon_dev;
> +	struct mutex update_lock;
> +};
The above structure has rather a general name, likely to lead to a clash
in the future. The same is true of a number of the functions.
> +static unsigned int i2c_write_current_data(struct i2c_client *client,
> +					unsigned int reg, unsigned int value)
> +{
> +	int ret_val;
> +
> +	ret_val = i2c_smbus_write_byte_data(client, reg, value);
> +	return ret_val;
> +}
Why bother with above. Doesn't seem to being anything?  Presumably
it was to allow for later inclusion of spi support, but as is you
might as well call the smbus command directly.

> +static ssize_t rate_show(struct device *dev,
> +			struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	int ret_val, val;
> +
> +	val = i2c_smbus_read_byte_data(client, 0x20);
> +	ret_val = (val & 0x80); /* 1= 400HZ 0= 100HZ */
> +	if (ret_val == 0x80)
> +		ret_val = 1;
Having carefully added definitions for this above, why not use them?
> +	return sprintf(buf, "%d\n", ret_val);
> +
odd spacing.
> +}

Not clear what state is refering to. Looking at the data sheet I
can see that it is power down control. Do you want userspace access
to this, or should it be done via the fine grained power management
stuff that is working its way into the kernel?

> +static ssize_t state_show(struct device *dev,
> +			struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	int ret_val, val;
> +
Definitions for register addresses would make this easier to read.
> +	val = i2c_smbus_read_byte_data(client, 0x20);
> +	ret_val = (val & 0x40);
> +	if (ret_val == 0x40)
> +		ret_val = 1;
> +	return sprintf(buf, "%d\n", ret_val);
> +}
> +

Up to you whether you do a single access point for all 3 readings (scan
across them) or individual reads.  Guess which makes sense depends on the
application.

> +static ssize_t xyz_pos_show(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	int x, y, z;
> +	struct i2c_client *client = to_i2c_client(dev);
I haven't read the data sheet thoroughly, but is the device capable of
doing more complex reads? (would be nice to cut down the bus load
of this given the device is running at maybe 400 Hz).
Table 14 of the data sheet seems to hint this can be done.
I guess it would also be dependant on what your i2c adapter supports.

> +	x = i2c_smbus_read_byte_data(client, 0x29);
> +	y = i2c_smbus_read_byte_data(client, 0x2B);
> +	z = i2c_smbus_read_byte_data(client, 0x2D);
> +	return sprintf(buf, "%d, %d, %d \n", x, y, z);
> +}
> +
> +static ssize_t rate_store(struct device *dev,
> +		struct device_attribute *attr, const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct acclero_data *data = i2c_get_clientdata(client);
> +	unsigned int ret_val, set_val;
> +	unsigned long val;
> +
> +	if (strict_strtoul(buf, 10, &val))
> +		return -EINVAL;
Why bother locking for updates without protecting the reads as well?
Given I'm guessing a given i2c write is atomic, I'm not sure you gain
anything.
> +	ret_val = i2c_smbus_read_byte_data(client, 0x20);
> +
> +	mutex_lock(&data->update_lock);
> +	if (val == ACCEL_DATA_RATE_100HZ)
> +		set_val = (ret_val & 0x7F); /* setting the 8th bit to 0 */
> +	else if (val == ACCEL_DATA_RATE_400HZ)
> +		set_val = (ret_val | (1 << 7));
> +	else
> +		goto invarg;
> +
> +	i2c_write_current_data(client, 0x20, set_val);
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +invarg:
> +	mutex_unlock(&data->update_lock);
> +	return -EINVAL;
> +}
> +
> +static ssize_t state_store(struct device *dev,
> +		struct device_attribute *attr, const  char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct acclero_data *data = i2c_get_clientdata(client);
> +	unsigned int ret_val, set_val;
> +	unsigned long val;
> +
> +	if (strict_strtoul(buf, 10, &val))
> +		return -EINVAL;
> +	ret_val = i2c_smbus_read_byte_data(client, 0x20);
> +
> +	mutex_lock(&data->update_lock);
> +	if (val == ACCEL_POWER_MODE_DOWN)
> +		set_val = ret_val & 0xBF; /* if value id 0 */
> +	else if (val == ACCEL_POWER_MODE_ACTIVE)
> +		set_val = (ret_val | (1<<6)); /* if value is 1 */
> +	else
> +		goto invarg;
> +
> +	i2c_write_current_data(client, 0x20, set_val);
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +invarg:
> +	mutex_unlock(&data->update_lock);
> +	return -EINVAL;
> +}

Why do you want this exposed to userspace? To my reading
the primary purpose in doing this is to reset any changes
to the various calibrated parameters. Exporting to userspace
might make sense if you were also exporting interfaces for them
(HPcoeff for example) Even then, I'm not sure providing access
to this isn't more confusing than ideal.

> +static ssize_t reboot_mem_store(struct device *dev,
> +		struct device_attribute *attr, const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct acclero_data *data = i2c_get_clientdata(client);
> +	unsigned int ret_val, set_val;
> +	unsigned long val;
> +
> +	if (strict_strtoul(buf, 10, &val))
> +		return -EINVAL;
> +	ret_val = i2c_smbus_read_byte_data(client, 0x21);
> +	if (val == ACCEL_MEMORY_REBOOT) {
> +		mutex_lock(&data->update_lock);
> +		set_val = (ret_val | (1 << 6)); /* setting the 6th  bit */
> +		i2c_write_current_data(client, 0x21, set_val);
> +		mutex_unlock(&data->update_lock);
> +	} else
> +		return -EINVAL;
> +	return count;
> +}
> +
> +static DEVICE_ATTR(data_rate, S_IRUGO | S_IWUSR, rate_show, rate_store);
> +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, state_show, state_store);
> +static DEVICE_ATTR(reboot_mem, S_IWUSR, NULL, reboot_mem_store);
> +static DEVICE_ATTR(position, S_IRUGO, xyz_pos_show, NULL);
> +
> +static struct attribute *mid_att_acclero[] = {
> +	&dev_attr_data_rate.attr,
> +	&dev_attr_power_state.attr,
> +	&dev_attr_reboot_mem.attr,
> +	&dev_attr_position.attr,
> +	NULL
> +};
This is a little odd, why have a device with an attribute group
of the same name? I think this will lead to a somewhat unusual
structure in sysfs.

> +static struct attribute_group m_acclero_gr = {
> +	.name = "lis331dl",
> +	.attrs = mid_att_acclero
> +};
> +
> +static void accel_set_default_config(struct i2c_client *client)
> +{
This could at very least do with a comment explaining what default
you have selected.
> +	i2c_write_current_data(client, 0x20, 0x47);
> +}
> +
> +static int  lis331dl_probe(struct i2c_client *client,
> +					const struct i2c_device_id *id)
> +{
> +	int res;
> +	struct acclero_data *data;
> +
> +	data = kzalloc(sizeof(struct acclero_data), GFP_KERNEL);
> +	if (data == NULL) {
> +		printk(KERN_WARNING "lis331dl: Memory initi failed \n");
> +		return -ENOMEM;
> +	}
> +	mutex_init(&data->update_lock);
> +	i2c_set_clientdata(client, data);
> +
> +	res = sysfs_create_group(&client->dev.kobj, &m_acclero_gr);
> +	if (res) {
> +		printk(KERN_WARNING "lis331dl: Sysfs  group failed!!\n");
> +		goto acclero_error1;
> +	}
> +	data->hwmon_dev = hwmon_device_register(&client->dev);
> +	if (IS_ERR(data->hwmon_dev)) {
> +		res = PTR_ERR(data->hwmon_dev);
> +		data->hwmon_dev = NULL;
> +		sysfs_remove_group(&client->dev.kobj, &m_acclero_gr);
> +		printk(KERN_WARNING "lis331dl: unable to register \
> +						hwmon device\n");
> +		goto acclero_error1;
> +	}
> +	accel_set_default_config(client);
> +
> +	dev_info(&client->dev, "%s lis331dl:  Accelerometer chip \
> +							foundn", client->name);
> +	return res;
> +
> +acclero_error1:
> +	i2c_set_clientdata(client, NULL);
> +	kfree(data);
> +	return res;
> +}
> +
> +static int lis331dl_remove(struct i2c_client *client)
> +{
> +	struct acclero_data *data = i2c_get_clientdata(client);
> +
> +	hwmon_device_unregister(data->hwmon_dev);
> +	sysfs_remove_group(&client->dev.kobj, &m_acclero_gr);
> +	kfree(data);
> +	return 0;
> +}
That's a very odd name for i2c device.  Shouldn't it be lis33ld1
or similar?

> +static struct i2c_device_id lis331dl_id[] = {
> +	{ "i2c_accel", 0 },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, lis331dl_id);
> +
> +static struct i2c_driver lis331dl_driver = {
> +	.driver = {
> +	.name = "lis331dl",
> +	},
> +	.probe = lis331dl_probe,
> +	.remove = lis331dl_remove,
> +	.id_table = lis331dl_id,
> +};
> +
> +static int __init sensor_lis331dl_init(void)
> +{
> +	int res;
> +
> +	res = i2c_add_driver(&lis331dl_driver);
> +	return res;
> +}
> +
> +static void  __exit sensor_lis331dl_exit(void)
> +{
> +	i2c_del_driver(&lis331dl_driver);
> +}
> +
> +module_init(sensor_lis331dl_init);
> +module_exit(sensor_lis331dl_exit);




[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux