aboutsummaryrefslogtreecommitdiff
path: root/drivers/input/misc/lis302dl.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/misc/lis302dl.c')
-rw-r--r--drivers/input/misc/lis302dl.c232
1 files changed, 221 insertions, 11 deletions
diff --git a/drivers/input/misc/lis302dl.c b/drivers/input/misc/lis302dl.c
index 944e5ebcccf..59fc4141263 100644
--- a/drivers/input/misc/lis302dl.c
+++ b/drivers/input/misc/lis302dl.c
@@ -248,10 +248,212 @@ static ssize_t lis302dl_dump(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR(dump, S_IRUGO, lis302dl_dump, NULL);
+static int freefall_ms_to_duration(struct lis302dl_info *lis, int ms)
+{
+ u_int8_t r = reg_read(lis, LIS302DL_REG_CTRL1);
+
+ /* If we have 400 ms sampling rate, the stepping is 2.5 ms,
+ * on 100 ms the stepping is 10ms */
+ if ( r & LIS302DL_CTRL1_DR ) {
+ /* Too large */
+ if (ms > 637)
+ return -1;
+
+ return (ms * 10) / 25;
+ }
+
+ /* Too large value */
+ if (ms > 2550)
+ return -1;
+ return ms / 10;
+}
+
+static int freefall_duration_to_ms(struct lis302dl_info *lis, int duration)
+{
+ u_int8_t r = reg_read(lis, LIS302DL_REG_CTRL1);
+
+ if ( r & LIS302DL_CTRL1_DR )
+ return (duration * 25) / 10;
+
+ return duration * 10;
+}
+
+/* Configure freefall/wakeup interrupts */
+static ssize_t set_freefall_common(int which, struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lis302dl_info *lis = dev_get_drvdata(dev);
+ u_int8_t x_lo, y_lo, z_lo;
+ u_int8_t x_hi, y_hi, z_hi;
+ int duration;
+ int threshold;
+ int and_events;
+ int r_ths = LIS302DL_REG_FF_WU_THS_1; /* registers, assume first pin */
+ int r_duration = LIS302DL_REG_FF_WU_DURATION_1;
+ int r_cfg = LIS302DL_REG_FF_WU_CFG_1;
+ int flag_mask = LIS302DL_F_WUP_FF_1;
+ int intmode = LIS302DL_INTMODE_FF_WU_1;
+ int x, y, z;
+ int ms;
+ unsigned long flags;
+
+ /* Configure for second freefall/wakeup pin */
+ if (which == 2) {
+ r_ths = LIS302DL_REG_FF_WU_THS_2;
+ r_duration = LIS302DL_REG_FF_WU_DURATION_2;
+ r_cfg = LIS302DL_REG_FF_WU_CFG_2;
+ flag_mask = LIS302DL_F_WUP_FF_2;
+ intmode = LIS302DL_INTMODE_FF_WU_2;
+
+ printk(KERN_WARNING "Configuring second freefall / wakeup interrupt\n");
+ }
+
+ /* Parse the input */
+ if (strcmp(buf, "0\n") == 0)
+ {
+ /* Turn off the interrupt */
+ local_save_flags(flags);
+ if ( (lis->flags & LIS302DL_F_IRQ_WAKE) != 0 )
+ disable_irq_wake(lis->spi_dev->irq);
+ lis302dl_int_mode(lis->spi_dev, which, LIS302DL_INTMODE_DATA_READY);
+ lis->flags &= ~(flag_mask | LIS302DL_F_IRQ_WAKE);
+
+ reg_write(lis, r_cfg, 0);
+ reg_write(lis, r_ths, 0);
+ reg_write(lis, r_duration, 0);
+
+ /* Power off unless the input subsystem is using the device */
+ if ( (lis->flags & LIS302DL_F_INPUT_OPEN) == 0 )
+ reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD, 0);
+
+ local_irq_restore(flags);
+
+ return count;
+ }
+
+ if (sscanf(buf, "%d %d %d %d %d %d", &x, &y, &z, &threshold, &ms, &and_events) != 6)
+ return -EINVAL;
+
+ duration = freefall_ms_to_duration(lis, ms);
+ if (duration < 0)
+ return -ERANGE;
+
+ /* 7 bits */
+ if (threshold < 0 || threshold > 127)
+ return -ERANGE;
+
+ /* Interrupt flags */
+ x_lo = x < 0 ? LIS302DL_FFWUCFG_XLIE : 0;
+ y_lo = y < 0 ? LIS302DL_FFWUCFG_YLIE : 0;
+ z_lo = z < 0 ? LIS302DL_FFWUCFG_ZLIE : 0;
+ x_hi = x > 0 ? LIS302DL_FFWUCFG_XHIE : 0;
+ y_hi = y > 0 ? LIS302DL_FFWUCFG_YHIE : 0;
+ z_hi = z > 0 ? LIS302DL_FFWUCFG_ZHIE : 0;
+
+ /* Setup the configuration registers */
+ local_save_flags(flags);
+ reg_write(lis, r_cfg, 0); /* First zero to get to a known state */
+ reg_write(lis, r_cfg,
+ (and_events ? LIS302DL_FFWUCFG_AOI : 0) |
+ x_lo | x_hi | y_lo | y_hi | z_lo | z_hi);
+ reg_write(lis, r_ths, threshold & ~LIS302DL_FFWUTHS_DCRM);
+ reg_write(lis, r_duration, duration);
+
+ /* Route the interrupt for wakeup */
+ lis302dl_int_mode(lis->spi_dev, which, intmode);
+
+ /* Power up the device and note that we want to wake up from
+ * this interrupt */
+ if ( (lis->flags & LIS302DL_F_IRQ_WAKE) == 0 )
+ enable_irq_wake(lis->spi_dev->irq);
+
+ lis->flags |= flag_mask | LIS302DL_F_IRQ_WAKE;
+ reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD,
+ LIS302DL_CTRL1_PD);
+ local_irq_restore(flags);
+
+ return count;
+}
+
+static ssize_t set_freefall_1(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return set_freefall_common(1, dev, attr, buf, count);
+}
+static ssize_t set_freefall_2(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return set_freefall_common(2, dev, attr, buf, count);
+}
+
+
+static ssize_t show_freefall_common(int which, struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lis302dl_info *lis = dev_get_drvdata(dev);
+ u_int8_t duration;
+ u_int8_t threshold;
+ u_int8_t config;
+ u_int8_t r4;
+ u_int8_t r5;
+ int r_ths = LIS302DL_REG_FF_WU_THS_1; /* registers, assume first pin */
+ int r_duration = LIS302DL_REG_FF_WU_DURATION_1;
+ int r_cfg = LIS302DL_REG_FF_WU_CFG_1;
+ int r_src = LIS302DL_REG_FF_WU_SRC_1;
+
+ /* Configure second freefall/wakeup pin */
+ if (which == 2) {
+ r_ths = LIS302DL_REG_FF_WU_THS_2;
+ r_duration = LIS302DL_REG_FF_WU_DURATION_2;
+ r_cfg = LIS302DL_REG_FF_WU_CFG_2;
+ r_src = LIS302DL_REG_FF_WU_SRC_2;
+ }
+ config = reg_read(lis, r_cfg);
+ threshold = reg_read(lis, r_ths);
+ duration = reg_read(lis, r_duration);
+ r4 = reg_read(lis, r_src);
+ r5 = reg_read(lis, LIS302DL_REG_CTRL3);
+
+ /* All events off? */
+ if ((config & (LIS302DL_FFWUCFG_XLIE | LIS302DL_FFWUCFG_XHIE |
+ LIS302DL_FFWUCFG_YLIE | LIS302DL_FFWUCFG_YHIE |
+ LIS302DL_FFWUCFG_ZLIE | LIS302DL_FFWUCFG_ZHIE)) == 0)
+ return sprintf(buf, "off\n");
+
+ return sprintf(buf,"%s events, %s interrupt, duration %d, threshold %d, "
+ "enabled: %s %s %s %s %s %s\n",
+ (config & LIS302DL_FFWUCFG_AOI) == 0 ? "or" : "and",
+ (config & LIS302DL_FFWUCFG_LIR) == 0 ? "don't latch" : "latch",
+ freefall_duration_to_ms(lis, duration), threshold,
+ (config & LIS302DL_FFWUCFG_XLIE) == 0 ? "---" : "xlo",
+ (config & LIS302DL_FFWUCFG_XHIE) == 0 ? "---" : "xhi",
+ (config & LIS302DL_FFWUCFG_YLIE) == 0 ? "---" : "ylo",
+ (config & LIS302DL_FFWUCFG_YHIE) == 0 ? "---" : "yhi",
+ (config & LIS302DL_FFWUCFG_ZLIE) == 0 ? "---" : "zlo",
+ (config & LIS302DL_FFWUCFG_ZHIE) == 0 ? "---" : "zhi");
+}
+
+static ssize_t show_freefall_1(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_freefall_common(1, dev, attr, buf);
+}
+
+static ssize_t show_freefall_2(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_freefall_common(2, dev, attr, buf);
+}
+
+static DEVICE_ATTR(freefall_wakeup_1, S_IRUGO | S_IWUSR, show_freefall_1, set_freefall_1);
+static DEVICE_ATTR(freefall_wakeup_2, S_IRUGO | S_IWUSR, show_freefall_2, set_freefall_2);
+
static struct attribute *lis302dl_sysfs_entries[] = {
&dev_attr_sample_rate.attr,
&dev_attr_full_scale.attr,
&dev_attr_dump.attr,
+ &dev_attr_freefall_wakeup_1.attr,
+ &dev_attr_freefall_wakeup_2.attr,
NULL
};
@@ -274,6 +476,8 @@ static int lis302dl_input_open(struct input_dev *inp)
reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, ctrl1, ctrl1);
local_irq_restore(flags);
+ lis->flags |= LIS302DL_F_INPUT_OPEN;
+
/* kick it off -- since we are edge triggered, if we missed the edge
* permanent low interrupt is death for us */
(lis->pdata->lis302dl_bitbang_read)(lis);
@@ -294,6 +498,7 @@ static void lis302dl_input_close(struct input_dev *inp)
* only see close() for the close of the last user, we can safely
* disable the data ready events */
reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, ctrl1, 0x00);
+ lis->flags &= ~LIS302DL_F_INPUT_OPEN;
/* however, don't power down the whole device if still needed */
if (!(lis->flags & LIS302DL_F_WUP_FF ||
@@ -406,9 +611,9 @@ static int __devinit lis302dl_probe(struct spi_device *spi)
reg_write(lis, LIS302DL_REG_CTRL2, 0);
reg_write(lis, LIS302DL_REG_CTRL3, LIS302DL_CTRL3_PP_OD |
LIS302DL_CTRL3_IHL);
- reg_write(lis, LIS302DL_REG_FF_WU_THS_1, 0x14);
+ reg_write(lis, LIS302DL_REG_FF_WU_THS_1, 0x0);
reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1, 0x00);
- reg_write(lis, LIS302DL_REG_FF_WU_CFG_1, 0x95);
+ reg_write(lis, LIS302DL_REG_FF_WU_CFG_1, 0x0);
/* start off in powered down mode; we power up when someone opens us */
reg_write(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_Xen |
@@ -486,6 +691,12 @@ static int lis302dl_suspend(struct spi_device *spi, pm_message_t state)
{
struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
unsigned long flags;
+ u_int8_t tmp;
+
+ /* determine if we want to wake up from the accel. */
+ if (lis->flags & LIS302DL_F_WUP_FF ||
+ lis->flags & LIS302DL_F_WUP_CLICK)
+ return 0;
disable_irq(lis->spi_dev->irq);
local_save_flags(flags);
@@ -528,15 +739,10 @@ static int lis302dl_suspend(struct spi_device *spi, pm_message_t state)
lis->regs[LIS302DL_REG_CLICK_WINDOW] =
reg_read(lis, LIS302DL_REG_CLICK_WINDOW);
- /* determine if we want to wake up from the accel. */
- if (!(lis->flags & LIS302DL_F_WUP_FF ||
- lis->flags & LIS302DL_F_WUP_CLICK)) {
- /* power down */
- u_int8_t tmp;
- tmp = reg_read(lis, LIS302DL_REG_CTRL1);
- tmp &= ~LIS302DL_CTRL1_PD;
- reg_write(lis, LIS302DL_REG_CTRL1, tmp);
- }
+ /* power down */
+ tmp = reg_read(lis, LIS302DL_REG_CTRL1);
+ tmp &= ~LIS302DL_CTRL1_PD;
+ reg_write(lis, LIS302DL_REG_CTRL1, tmp);
/* place our IO to the device in sleep-compatible states */
(lis->pdata->lis302dl_suspend_io)(lis, 0);
@@ -551,6 +757,10 @@ static int lis302dl_resume(struct spi_device *spi)
struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
unsigned long flags;
+ if (lis->flags & LIS302DL_F_WUP_FF ||
+ lis->flags & LIS302DL_F_WUP_CLICK)
+ return 0;
+
local_save_flags(flags);
/* get our IO to the device back in operational states */