[PATCH 1/4] lis302dl: allow accelerometer sample_rate to be other values including fractions.

NeilBrown neilb at suse.de
Mon Nov 2 02:32:45 CET 2009


This patch enhances the 'sample_rate' sysfs attribute so that it can
take on a wide range of "HZ" values including fractions.

If the HZ rate is 50 or larger, the lis302dl still generates
interrupts on each sample, but possibly not all of them are reported
to user-space.
If the HZ rate is below 50, the data-ready interrupt is disabled and
a timer is used to read the latest sample at the required rate.

The device is only set to take samples at the high 400HZ rate if
sample_rate exceeds 100. (i.e. if it is 200 or 400).

The actual sample rates that are supported at 50 or above are
50, 100, 200, 400.

Below 50, sample rates that are a submultiple of the kernel's internal
HZ rates are supported, including fractions down to 0.0001 or there
abouts.

To maintain backwards compatibility, if the threshold is set, the
sample_rate is automatically set to 0, so samples are only reported
when there are threshold crossing.  If the sample_rate is set after
the threshold, then samples are reported at that rate, or when
threshold crossings happen.

Signed-off-by: NeilBrown <neilb at suse.de>
---

 drivers/input/misc/lis302dl.c |  187 +++++++++++++++++++++++++++++++++++------
 include/linux/lis302dl.h      |   15 +++
 2 files changed, 173 insertions(+), 29 deletions(-)

diff --git a/drivers/input/misc/lis302dl.c b/drivers/input/misc/lis302dl.c
index f31e548..894f928 100644
--- a/drivers/input/misc/lis302dl.c
+++ b/drivers/input/misc/lis302dl.c
@@ -41,6 +41,7 @@
 #include <linux/interrupt.h>
 #include <linux/sysfs.h>
 #include <linux/spi/spi.h>
+#include <linux/ctype.h>
 
 #include <linux/lis302dl.h>
 
@@ -199,13 +200,21 @@ static void __enable_data_collection(struct lis302dl_info *lis)
 	/* make sure we're powered up and generate data ready */
 	__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, ctrl1, ctrl1);
 
-	/* If the threshold is zero, let the device generated an interrupt
-	 * on each datum */
-	if (lis->threshold == 0) {
-		__reg_write(lis, LIS302DL_REG_CTRL2, 0);
-		__lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_DATA_READY);
-		__lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_DATA_READY);
+	/* set an appropriate sample rate */
+	if (lis->use_jiffies == 0 && lis->sample_time < 4) {
+		/* high-rate samples - 400Hz */
+		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR,
+				   LIS302DL_CTRL1_DR);
+		lis->flags |= LIS302DL_F_DR;
 	} else {
+		/* low-rate samples - 100Hz */
+		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR,
+				   0);
+		lis->flags &= ~LIS302DL_F_DR;
+	}
+
+	if (lis->threshold) {
+		/* Enable WU detection, with high-pass filter engaged. */
 		__reg_write(lis, LIS302DL_REG_CTRL2,
 				LIS302DL_CTRL2_HPFF1);
 		__reg_write(lis, LIS302DL_REG_FF_WU_THS_1,
@@ -218,8 +227,22 @@ static void __enable_data_collection(struct lis302dl_info *lis)
 		__reg_write(lis, LIS302DL_REG_FF_WU_CFG_1,
 				LIS302DL_FFWUCFG_XHIE | LIS302DL_FFWUCFG_YHIE |
 				LIS302DL_FFWUCFG_ZHIE | LIS302DL_FFWUCFG_LIR);
-		__lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_FF_WU_12);
-		__lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_FF_WU_12);
+	} else {
+		/* Disable WU */
+		__reg_write(lis, LIS302DL_REG_FF_WU_CFG_1, 0);
+	}
+
+	if (!lis->use_jiffies) {
+		/* Interrupt on data-ready */
+		__lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_DATA_READY);
+		__lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_DATA_READY);
+	} else {
+		/* Interrupt on WU1, which may well be disabled */
+		__lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_FF_WU_1);
+		__lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_FF_WU_1);
+
+		if (lis->sample_time)
+			mod_timer(&lis->timer, jiffies + lis->sample_time);
 	}
 }
 
@@ -252,6 +275,8 @@ static void lis302dl_bitbang_read_sample(struct lis302dl_info *lis)
 	int mg_per_sample = __threshold_to_mg(lis, 1);
 	struct spi_message msg;
 	struct spi_transfer t;
+	u8 wakeup;
+	int need_sample = 0;
 
 	spi_message_init(&msg);
 	memset(&t, 0, sizeof t);
@@ -283,8 +308,29 @@ static void lis302dl_bitbang_read_sample(struct lis302dl_info *lis)
 		       LIS302DL_STATUS_ZOR))
 		lis->overruns++;
 
-	/* we have a valid sample set? */
-	if (read[0] & LIS302DL_STATUS_XYZDA) {
+	wakeup = __reg_read(lis, LIS302DL_REG_FF_WU_SRC_1);
+
+	if (!(read[0] & LIS302DL_STATUS_XYZDA))
+		return;
+
+	if (lis->use_jiffies == 0 && (read[0] & LIS302DL_STATUS_XYZDA)) {
+		if (lis->flags & LIS302DL_F_DR)
+			lis->last_sample++;
+		else
+			lis->last_sample += 4;
+		if (lis->last_sample >= lis->sample_time)
+			need_sample = 1;
+	}
+	if (lis->use_jiffies && lis->sample_time &&
+	    time_before_eq(lis->last_sample + lis->sample_time, jiffies)) {
+		need_sample = 1;
+		lis->last_sample = jiffies;
+	}
+
+	if (lis->threshold && (wakeup & LIS302DL_FFWUCFG_AOI))
+		need_sample = 1;
+
+	if (need_sample) {
 		input_report_abs(lis->input_dev, ABS_X, mg_per_sample *
 			    (s8)read[LIS302DL_REG_OUT_X - LIS302DL_REG_STATUS]);
 		input_report_abs(lis->input_dev, ABS_Y, mg_per_sample *
@@ -295,9 +341,8 @@ static void lis302dl_bitbang_read_sample(struct lis302dl_info *lis)
 		input_sync(lis->input_dev);
 	}
 
-	if (lis->threshold)
-		/* acknowledge the wakeup source */
-		__reg_read(lis,	LIS302DL_REG_FF_WU_SRC_1);
+	if (lis->use_jiffies && lis->sample_time)
+		mod_timer(&lis->timer, jiffies + lis->sample_time);
 }
 
 static irqreturn_t lis302dl_interrupt(int irq, void *_lis)
@@ -308,6 +353,12 @@ static irqreturn_t lis302dl_interrupt(int irq, void *_lis)
 	return IRQ_HANDLED;
 }
 
+static void lis302dl_timer_read_sample(unsigned long data)
+{
+	struct lis302dl_info *lis = (struct lis302dl_info *)data;
+	lis302dl_bitbang_read_sample(lis);
+}
+
 /* sysfs */
 
 static ssize_t show_overruns(struct device *dev, struct device_attribute *attr,
@@ -324,14 +375,25 @@ static ssize_t show_rate(struct device *dev, struct device_attribute *attr,
 			 char *buf)
 {
 	struct lis302dl_info *lis = dev_get_drvdata(dev);
-	u8 ctrl1;
-	unsigned long flags;
-
-	local_irq_save(flags);
-	ctrl1 = __reg_read(lis, LIS302DL_REG_CTRL1);
-	local_irq_restore(flags);
+	char *e;
+	int rate;
 
-	return sprintf(buf, "%d\n", ctrl1 & LIS302DL_CTRL1_DR ? 400 : 100);
+	if (lis->sample_time == 0)
+		rate = 0;
+	else if (lis->use_jiffies)
+		rate = HZ*10000 / lis->sample_time;
+	else
+		rate = 4000000 / lis->sample_time;
+	sprintf(buf, "%u.%04u", rate/10000, rate%10000);
+	/* remove trailing zeros and possibly a '.' */
+	e = buf + strlen(buf);
+	while (e[-1] == '0')
+		*--e = 0;
+	if (e[-1] == '.')
+		*--e = 0;
+	*e++ = '\n';
+	*e++ = 0;
+	return strlen(buf);
 }
 
 static ssize_t set_rate(struct device *dev, struct device_attribute *attr,
@@ -339,18 +401,70 @@ static ssize_t set_rate(struct device *dev, struct device_attribute *attr,
 {
 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 	unsigned long flags;
+	char nbuf[30];
+	int i;
+	int dot = 0;
+	unsigned long hz;
+	int scale = 1;
+
+	/* remove a period, and count digits after it */
+	if (count >= sizeof(nbuf))
+		return -EINVAL;
+	strlcpy(nbuf, buf, sizeof(nbuf));
+	for (i = 0; i < count; i++) {
+		if (dot) {
+			if (isdigit(nbuf[i])) {
+				nbuf[i-1] = nbuf[i];
+				scale *= 10;
+			} else
+				break;
+			nbuf[i] = 0;
+		} else if (nbuf[i] == '.') {
+			dot = 1;
+			nbuf[i] = 0;
+		} else if (!isdigit(nbuf[i]))
+			break;
+	}
+	if (i < count && nbuf[i] != '\n')
+		return -EINVAL;
+	nbuf[i] = 0;
+	if (strict_strtoul(nbuf, 10, &hz) < 0)
+		return -EINVAL;
+	if (hz) {
+		while (scale > 10000) {
+			hz /= 10;
+			scale /= 10;
+		}
+		while (scale < 10000) {
+			hz *= 10;
+			scale *= 10;
+		}
+		if (hz == 0)
+			hz = 1;
+	}
+	/* hz is now scaled so 10000 is 1Hz */
+	if (hz == 0) {
+		lis->use_jiffies = 1;
+		lis->sample_time = 0;
+	} else if (hz < 500000) {
+		/* below 50Hz, use jiffies */
+		lis->use_jiffies = 1;
+		lis->sample_time = (HZ * 10000 + hz/2) / hz;
+	} else {
+		/* Use interrupts */
+		lis->use_jiffies = 0;
+		lis->sample_time = (400 * 10000 + hz/2) / hz;
+	}
+	if (lis->use_jiffies)
+		lis->last_sample = jiffies - lis->sample_time;
+	else
+		lis->last_sample = 0;
 
 	local_irq_save(flags);
 
-	if (!strcmp(buf, "400\n")) {
-		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR,
-				 LIS302DL_CTRL1_DR);
-		lis->flags |= LIS302DL_F_DR;
-	} else {
-		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR,
-				0);
-		lis->flags &= ~LIS302DL_F_DR;
-	}
+	if (lis->flags & LIS302DL_F_INPUT_OPEN)
+		__enable_data_collection(lis);
+
 	local_irq_restore(flags);
 
 	return count;
@@ -425,6 +539,12 @@ static ssize_t set_threshold(struct device *dev, struct device_attribute *attr,
 	/* Set the threshold and write it out if the device is used */
 	lis->threshold = val;
 
+	/* for backwards compatibility, we clear sample_rate when setting
+	 * threshold
+	 */
+	lis->sample_time = 0;
+	lis->use_jiffies = 1;
+
 	if (lis->flags & LIS302DL_F_INPUT_OPEN) {
 		unsigned long flags;
 
@@ -595,6 +715,10 @@ static int lis302dl_input_open(struct input_dev *inp)
 	struct lis302dl_info *lis = input_get_drvdata(inp);
 	unsigned long flags;
 
+	lis->timer.function = lis302dl_timer_read_sample;
+	lis->timer.data = (unsigned long)lis;
+	init_timer(&lis->timer);
+
 	local_irq_save(flags);
 
 	__enable_data_collection(lis);
@@ -612,6 +736,7 @@ static void lis302dl_input_close(struct input_dev *inp)
 			 LIS302DL_CTRL1_Zen;
 	unsigned long flags;
 
+	del_timer_sync(&lis->timer);
 	local_irq_save(flags);
 
 	/* since the input core already serializes access and makes sure we
@@ -720,6 +845,10 @@ static int __devinit lis302dl_probe(struct spi_device *spi)
 
 	lis->threshold = 0;
 	lis->duration = 0;
+	/* default to 100HZ sampling */
+	lis->use_jiffies = 0;
+	lis->sample_time = 4;
+	lis->last_sample = 0;
 	memset(&lis->wakeup, 0, sizeof(lis->wakeup));
 
 	if (__lis302dl_reset_device(lis))
diff --git a/include/linux/lis302dl.h b/include/linux/lis302dl.h
index 0c1fc30..de47a37 100644
--- a/include/linux/lis302dl.h
+++ b/include/linux/lis302dl.h
@@ -26,6 +26,21 @@ struct lis302dl_info {
 	unsigned int flags;
 	unsigned int threshold;
 	unsigned int duration;
+
+	/* We store sample_rate as time-between-samples.
+	 * For rates above 50Hz, the unit of time is 2.5ms, the
+	 * time of the fastest interrupt from the device.
+	 * For lower rates, the unit of time is one jiffy.
+	 * When using jiffies, 'last_sample' is the value of 'jiffies'
+	 * at the time of the last sample.
+	 * When using 2.5ms, 'last_sample' is set to 0 when
+	 * a sample is taken, and incremented on each interrupt.  So it
+	 * is effectively 'time since last_sample'.
+	 */
+	int use_jiffies;
+	unsigned int sample_time;
+	unsigned long last_sample;
+	struct timer_list timer;
 	u32 overruns;
 	struct {
 		unsigned int threshold; /* mg */





More information about the openmoko-kernel mailing list