[PATCH 3/4] misc: Add MLX90609 angular rate sensor driver

Stefan Schmidt stefan at datenfreihafen.org
Mon Apr 26 09:25:05 CEST 2010


Signed-off-by: Stefan Schmidt <stefan at datenfreihafen.org>
---
 drivers/misc/Kconfig    |    7 +
 drivers/misc/Makefile   |    1 +
 drivers/misc/mlx90609.c |  387 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 395 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/mlx90609.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 50d413c..aed9c8c 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -498,4 +498,11 @@ config MACH_NEO1973
 	help
 	  Common machine code for Openmoko GTAxx hardware
 
+config MLX90609
+	tristate "MLX90609 Angular Rate Sensor (gyroscope)"
+	depends on SPI
+	help
+	  This option enabled the driver for the MLX90609 Angular Rate Sensor
+	  (gyroscope).
+
 endif # MISC_DEVICES
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 77efd7a..d211639 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -36,4 +36,5 @@ obj-$(CONFIG_LOW_MEMORY_KILLER)	+= lowmemorykiller.o
 obj-$(CONFIG_MACH_NEO1973)      += neo1973_version.o \
                                    neo1973_pm_host.o \
                                    neo1973_pm_resume_reason.o
+obj-$(CONFIG_MLX90609)		+= mlx90609.o
 
diff --git a/drivers/misc/mlx90609.c b/drivers/misc/mlx90609.c
new file mode 100644
index 0000000..694324a
--- /dev/null
+++ b/drivers/misc/mlx90609.c
@@ -0,0 +1,387 @@
+/* Linux kernel driver for the Melexis MLX90609 Angular Rate Sensor
+ *
+ * Copyright (C) 2010 DLR
+ * Author: Stefan Schmidt <stefan at datenfreihafen.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; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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/kernel.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+
+#define BUSY_BIT	1 << 10
+#define EOC_BIT		1 << 13
+#define OPC_BIT		1 << 14
+#define REFUSAL_BIT	1 << 15
+
+#define AD_MASK		0x07FF
+
+#define STATR_CMD	0x88
+#define ADCC_TEMP	0x9C
+#define ADCC_RATE	0x94
+#define ADCR		0x80
+
+#define ADCC_ENABLE	0x94
+#define ADCC_DISABLE	0x90
+
+/* Relaying on the fact that the reg bufer is called ret is quite ugly */
+#define bit0	(ret & 0x01)
+#define bit1	(ret & 0x02)
+#define bit2	(ret & 0x04)
+#define bit3	(ret & 0x08)
+#define bit4	(ret & 0x10)
+#define bit5	(ret & 0x20)
+#define bit6	(ret & 0x40)
+
+/* Global error counter */
+int err_count = 0;
+int spi_cmds = 0;
+
+struct mlx90609_info {
+	struct spi_device *spi_dev;
+	struct device *dev;
+	int version;
+	int range;
+	u16 tc[3];	/* Temperatur coefficients */
+	u8 ee_zro[3];	/* Zero rate output adjustments */
+	u8 ee_sf[3];	/* Scale factor adjustments */
+	struct mutex lock;
+};
+
+static int mlx_spi(struct mlx90609_info *mlx, u8 cmd)
+{
+	int ret;
+
+	mutex_lock(&mlx->lock);
+	ret = spi_w8r16(mlx->spi_dev, cmd);
+	mutex_unlock(&mlx->lock);
+	spi_cmds++;
+	if (ret < 0)
+		return ret;
+
+	ret = swab16(ret);
+
+	/* Check if it is a refusal answer */
+	if ((ret & REFUSAL_BIT) >> 15) {
+		err_count++;
+		if ((ret & OPC_BIT) >> 14)
+			dev_info(&mlx->spi_dev->dev, "Refusal answer with OPC bit set on %i\n", cmd);
+		if ((ret & BUSY_BIT) >> 10)
+			dev_info(&mlx->spi_dev->dev, "Refusal answer with BUSY bit set on %i\n", cmd);
+		return -EIO;
+	}
+
+	return ret;
+}
+
+static void adc_enable(struct mlx90609_info *mlx)
+{
+	mlx_spi(mlx, ADCC_ENABLE);
+	udelay(120); /* Everything > 115 us should be fine */
+}
+
+static void adc_disable(struct mlx90609_info *mlx)
+{
+	mlx_spi(mlx, ADCC_DISABLE);
+}
+
+/* Not used maybe interesting fo debugging */
+static int dump_eeprom(struct mlx90609_info *mlx)
+{
+	int ret, tmp;
+	u8 addr;
+
+	for (addr = 0x00; addr < 0x80; addr++) {
+		mutex_lock(&mlx->lock);
+		ret = spi_w8r16(mlx->spi_dev, addr);
+		mutex_unlock(&mlx->lock);
+		/* return negative errno we got from spi */
+		if (ret < 0)
+			return ret;
+		tmp = swab16(ret);
+		printk("Address 0x%x value %i\n",addr, tmp);
+	}
+	return 0;
+}
+
+static int read_temp(struct mlx90609_info *mlx)
+{
+	int ret;
+
+	/* Start conversion */
+	ret = mlx_spi(mlx, ADCC_TEMP);
+
+	/* Wait until conversion is ready */
+	udelay(170); /* Everything > 115 us should be fine */
+
+	/* Obtain result */
+	ret = mlx_spi(mlx, ADCR);
+
+	/* Check for EOC */
+	if ((ret & EOC_BIT) < 1)
+		return -EIO;
+
+	/* Cut away everything but the AD values */
+	ret >>= 1;
+	ret &= AD_MASK;
+
+	return ret;
+}
+
+static int read_rate(struct mlx90609_info *mlx)
+{
+	int ret;
+
+	/* Start conversion */
+	ret = mlx_spi(mlx, ADCC_RATE);
+
+	/* Wait until conversion is ready */
+	udelay(170); /* Everything > 115 us should be fine */
+
+	/* Obtain result */
+	ret = mlx_spi(mlx, ADCR);
+
+	/* Check for EOC */
+	if ((ret & EOC_BIT) < 1)
+		return -EIO;
+
+	/* Cut away everything but the AD values */
+	ret >>= 1;
+	ret &= AD_MASK;
+
+	return ret;
+}
+
+static int read_tc(struct mlx90609_info *mlx)
+{
+	int ret;
+	u16 tc;
+	u8 addr, i;
+
+	for (i = 0; i < 3; i++) {
+		tc = 0;
+		for (addr = 0x38 + i * 0x10; addr < 0x47 + i * 0x10; addr++) {
+			ret = spi_w8r16(mlx->spi_dev, addr);
+			/* return negative errno we got from spi */
+			if (ret < 0)
+				return ret;
+			ret = swab16(ret);
+			tc >>= 1;
+			/* Do majority voting on bit 0 to 2 */
+			if ((bit0 && bit1) || (bit0 && bit2) || (bit1 && bit2))
+				tc |= 0x80;
+		}
+		mlx->tc[i] = tc;
+	}
+
+	return 0;
+}
+
+static int read_zro_sf(struct mlx90609_info *mlx)
+{
+	int ret;
+	u16 ee;
+	u8 addr;
+
+	for (addr = 0x20 ; addr < 0x50; addr++) {
+		ret = spi_w8r16(mlx->spi_dev, addr);
+		/* return negative errno we got from spi */
+		if (ret < 0)
+			return ret;
+		ret = swab16(ret);
+		ee >>= 1;
+		if (ret & 0x40)
+			ee |= 0x80;
+		if (addr == 0x27)
+			mlx->ee_zro[0] = ee;
+		if (addr == 0x2F)
+			mlx->ee_zro[1] = ee;
+		if (addr == 0x37)
+			mlx->ee_zro[2] = ee;
+		if (addr == 0x3F)
+			mlx->ee_sf[0] = ee;
+		if (addr == 0x47)
+			mlx->ee_sf[1] = ee;
+		if (addr == 0x4F)
+			mlx->ee_sf[2] = ee;
+	}
+
+	return 0;
+}
+
+static int read_range(struct mlx90609_info *mlx)
+{
+	int ret;
+	u8 range, addr;
+
+	range = 0;
+
+	for (addr = 0x68; addr < 0x70; addr++) {
+		ret = spi_w8r16(mlx->spi_dev, addr);
+		/* return negative errno we got from spi */
+		if (ret < 0)
+			return ret;
+		ret = swab16(ret);
+		range >>= 1;
+		/* Do majority voting on bit 0 to 2 */
+		if ((bit0 && bit1) || (bit0 && bit2) || (bit1 && bit2))
+			range |= 0x80;
+	}
+	return range * 5;
+}
+
+static int read_version(struct mlx90609_info *mlx)
+{
+	int ret;
+	u8 version, addr;
+
+	version = 0;
+
+	for (addr = 0x60; addr < 0x68; addr++) {
+		ret = spi_w8r16(mlx->spi_dev, addr);
+		/* return negative errno we got from spi */
+		if (ret < 0)
+			return ret;
+		ret = swab16(ret);
+		version >>= 1;
+		/* Do majority voting on bit 3 to 5 */
+		if ((bit3 && bit4) || (bit3 && bit5) || (bit4 && bit5))
+			version |= 0x80;
+	}
+	return version;
+}
+
+/* Sysfs interface for temperature and angular rate */
+static ssize_t show_temperature(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct mlx90609_info *mlx = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%i\n", read_temp(mlx));
+}
+
+static DEVICE_ATTR(temperature, S_IRUGO, show_temperature, NULL);
+
+static ssize_t show_angular_rate(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct mlx90609_info *mlx = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%i\n", read_rate(mlx));
+}
+
+static DEVICE_ATTR(angular_rate, S_IRUGO, show_angular_rate, NULL);
+
+static struct attribute *mlx90609_sysfs_entries[] = {
+	&dev_attr_angular_rate.attr,
+	&dev_attr_temperature.attr,
+	NULL
+};
+
+static struct attribute_group mlx90609_attr_group = {
+	.name	= NULL,
+	.attrs	= mlx90609_sysfs_entries,
+};
+
+/* Device driver infrastructure */
+static int __devinit mlx90609_probe(struct spi_device *spi)
+{
+	int rc;
+	struct mlx90609_info *mlx;
+
+	mlx = kzalloc(sizeof(*mlx), GFP_KERNEL);
+	if (!mlx)
+		return -ENOMEM;
+
+	mlx->spi_dev = spi;
+	dev_set_drvdata(&spi->dev, mlx);
+	mutex_init(&mlx->lock);
+
+	rc = sysfs_create_group(&spi->dev.kobj, &mlx90609_attr_group);
+	if (rc) {
+		dev_err(&spi->dev, "Error creating sysfs group\n");
+		goto err_out;
+	}
+
+	/* Wake up the chip and read in data from the EEPROM */
+	udelay(120); /* Everything > 115 us should be fine */
+	adc_enable(mlx);
+	mlx->version = read_version(mlx);
+	mlx->range = read_range(mlx);
+	read_tc(mlx);
+	read_zro_sf(mlx);
+
+	dev_info(&spi->dev, "Found MLX90609 chip.\n");
+	dev_info(&spi->dev, "Version %i, full scale range %i\n", mlx->version, mlx->range);
+	dev_info(&spi->dev, "TC0: %i, TC1: %i, TC2: %i\n", mlx->tc[0], mlx->tc[1], mlx->tc[2]);
+	dev_info(&spi->dev, "ZRO0: %i, ZRO1: %i, ZRO2: %i\n", mlx->ee_zro[0], mlx->ee_zro[1], mlx->ee_zro[2]);
+	dev_info(&spi->dev, "SF0: %i, SF1: %i, SF2: %i\n", mlx->ee_sf[0], mlx->ee_sf[1], mlx->ee_sf[2]);
+
+	return 0;
+
+err_out:
+	dev_set_drvdata(&spi->dev, NULL);
+	kfree(mlx);
+	return rc;
+}
+
+static int __devexit mlx90609_remove(struct spi_device *spi)
+{
+	struct mlx90609_info *mlx = dev_get_drvdata(&spi->dev);
+
+	/* Sent chip back to sleep */
+	adc_disable(mlx);
+
+	printk("Number of SPI commands: %i, invalid answers %i\n", spi_cmds, err_count);
+	sysfs_remove_group(&spi->dev.kobj, &mlx90609_attr_group);
+	dev_set_drvdata(&spi->dev, NULL);
+	kfree(mlx);
+
+	return 0;
+}
+
+static struct spi_driver mlx90609_driver = {
+	.driver = {
+		.name	= "mlx90609",
+		.owner	= THIS_MODULE,
+	},
+	.probe	 = mlx90609_probe,
+	.remove	 = __devexit_p(mlx90609_remove),
+};
+
+static int __init mlx90609_init(void)
+{
+	return spi_register_driver(&mlx90609_driver);
+}
+
+static void __exit mlx90609_exit(void)
+{
+	spi_unregister_driver(&mlx90609_driver);
+}
+
+MODULE_DESCRIPTION("SPI driver for the MLX90609 angular rate sensor");
+MODULE_AUTHOR("Stefan Schmidt <stefan at datenfreihafen.org>");
+MODULE_LICENSE("GPL");
+
+module_init(mlx90609_init);
+module_exit(mlx90609_exit);
-- 
1.7.0.5




More information about the openmoko-kernel mailing list