mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 05:54:26 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			958 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			958 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Linux kernel driver for the ST LIS302D 3-axis accelerometer
 | |
|  *
 | |
|  * Copyright (C) 2007-2008 by Openmoko, Inc.
 | |
|  * Author: Harald Welte <laforge@openmoko.org>
 | |
|  *         converted to private bitbang by:
 | |
|  *         Andy Green <andy@openmoko.com>
 | |
|  *         ability to set acceleration threshold added by:
 | |
|  *         Simon Kagstrom <simon.kagstrom@gmail.com>
 | |
|  * All rights reserved.
 | |
|  *
 | |
|  * 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
 | |
|  *
 | |
|  * TODO
 | |
|  * 	* statistics for overflow events
 | |
|  * 	* configuration interface (sysfs) for
 | |
|  * 		* enable/disable x/y/z axis data ready
 | |
|  * 		* enable/disable resume from freee fall / click
 | |
|  * 		* free fall / click parameters
 | |
|  * 		* high pass filter parameters
 | |
|  */
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/types.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/irq.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/sysfs.h>
 | |
| #include <linux/spi/spi.h>
 | |
| 
 | |
| #include <linux/lis302dl.h>
 | |
| 
 | |
| /* Utility functions */
 | |
| static u8 __reg_read(struct lis302dl_info *lis, u8 reg)
 | |
| {
 | |
| 	struct spi_message msg;
 | |
| 	struct spi_transfer t;
 | |
| 	u8 data[2] = {0xc0 | reg};
 | |
| 	int rc;
 | |
| 
 | |
| 	spi_message_init(&msg);
 | |
| 	memset(&t, 0, sizeof t);
 | |
| 	t.len = 2;
 | |
| 	spi_message_add_tail(&t, &msg);
 | |
| 	t.tx_buf = &data[0];
 | |
| 	t.rx_buf = &data[0];
 | |
| 
 | |
| 	/* Should complete without blocking */
 | |
| 	rc = spi_non_blocking_transfer(lis->spi, &msg);
 | |
| 	if (rc < 0) {
 | |
| 		dev_err(lis->dev, "Error reading register\n");
 | |
| 		return rc;
 | |
| 	}
 | |
| 
 | |
| 	return data[1];
 | |
| }
 | |
| 
 | |
| static void __reg_write(struct lis302dl_info *lis, u8 reg, u8 val)
 | |
| {
 | |
| 	struct spi_message msg;
 | |
| 	struct spi_transfer t;
 | |
| 	u8 data[2] = {reg, val};
 | |
| 
 | |
| 	spi_message_init(&msg);
 | |
| 	memset(&t, 0, sizeof t);
 | |
| 	t.len = 2;
 | |
| 	spi_message_add_tail(&t, &msg);
 | |
| 	t.tx_buf = &data[0];
 | |
| 	t.rx_buf = &data[0];
 | |
| 
 | |
| 	/* Completes without blocking */
 | |
| 	if (spi_non_blocking_transfer(lis->spi, &msg) < 0)
 | |
| 		dev_err(lis->dev, "Error writing register\n");
 | |
| }
 | |
| 
 | |
| static void __reg_set_bit_mask(struct lis302dl_info *lis, u8 reg, u8 mask,
 | |
| 		u8 val)
 | |
| {
 | |
| 	u_int8_t tmp;
 | |
| 
 | |
| 	val &= mask;
 | |
| 
 | |
| 	tmp = __reg_read(lis, reg);
 | |
| 	tmp &= ~mask;
 | |
| 	tmp |= val;
 | |
| 	__reg_write(lis, reg, tmp);
 | |
| }
 | |
| 
 | |
| static int __ms_to_duration(struct lis302dl_info *lis, int ms)
 | |
| {
 | |
| 	/* If we have 400 ms sampling rate, the stepping is 2.5 ms,
 | |
| 	 * on 100 ms the stepping is 10ms */
 | |
| 	if (lis->flags & LIS302DL_F_DR)
 | |
| 		return min((ms * 10) / 25, 637);
 | |
| 
 | |
| 	return min(ms / 10, 2550);
 | |
| }
 | |
| 
 | |
| static int __duration_to_ms(struct lis302dl_info *lis, int duration)
 | |
| {
 | |
| 	if (lis->flags & LIS302DL_F_DR)
 | |
| 		return (duration * 25) / 10;
 | |
| 
 | |
| 	return duration * 10;
 | |
| }
 | |
| 
 | |
| static u8 __mg_to_threshold(struct lis302dl_info *lis, int mg)
 | |
| {
 | |
| 	/* If FS is set each bit is 71mg, otherwise 18mg. The THS register
 | |
| 	 * has 7 bits for the threshold value */
 | |
| 	if (lis->flags & LIS302DL_F_FS)
 | |
| 		return min(mg / 71, 127);
 | |
| 
 | |
| 	return min(mg / 18, 127);
 | |
| }
 | |
| 
 | |
| static int __threshold_to_mg(struct lis302dl_info *lis, u8 threshold)
 | |
| {
 | |
| 	if (lis->flags & LIS302DL_F_FS)
 | |
| 		return threshold * 71;
 | |
| 
 | |
| 	return threshold * 18;
 | |
| }
 | |
| 
 | |
| /* interrupt handling related */
 | |
| 
 | |
| enum lis302dl_intmode {
 | |
| 	LIS302DL_INTMODE_GND		= 0x00,
 | |
| 	LIS302DL_INTMODE_FF_WU_1	= 0x01,
 | |
| 	LIS302DL_INTMODE_FF_WU_2	= 0x02,
 | |
| 	LIS302DL_INTMODE_FF_WU_12	= 0x03,
 | |
| 	LIS302DL_INTMODE_DATA_READY	= 0x04,
 | |
| 	LIS302DL_INTMODE_CLICK		= 0x07,
 | |
| };
 | |
| 
 | |
| static void __lis302dl_int_mode(struct device *dev, int int_pin,
 | |
| 			      enum lis302dl_intmode mode)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 
 | |
| 	switch (int_pin) {
 | |
| 	case 1:
 | |
| 		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL3, 0x07, mode);
 | |
| 		break;
 | |
| 	case 2:
 | |
| 		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL3, 0x38, mode << 3);
 | |
| 		break;
 | |
| 	default:
 | |
| 		BUG();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void __enable_wakeup(struct lis302dl_info *lis)
 | |
| {
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL1, 0);
 | |
| 
 | |
| 	/* First zero to get to a known state */
 | |
| 	__reg_write(lis, LIS302DL_REG_FF_WU_CFG_1, LIS302DL_FFWUCFG_XHIE |
 | |
| 			LIS302DL_FFWUCFG_YHIE | LIS302DL_FFWUCFG_ZHIE |
 | |
| 			LIS302DL_FFWUCFG_LIR);
 | |
| 	__reg_write(lis, LIS302DL_REG_FF_WU_THS_1,
 | |
| 			__mg_to_threshold(lis, lis->wakeup.threshold));
 | |
| 	__reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1,
 | |
| 			__ms_to_duration(lis, lis->wakeup.duration));
 | |
| 
 | |
| 	/* Route the interrupt for wakeup */
 | |
| 	__lis302dl_int_mode(lis->dev, 1,
 | |
| 			LIS302DL_INTMODE_FF_WU_1);
 | |
| 
 | |
| 	__reg_read(lis, LIS302DL_REG_HP_FILTER_RESET);
 | |
| 	__reg_read(lis, LIS302DL_REG_OUT_X);
 | |
| 	__reg_read(lis, LIS302DL_REG_OUT_Y);
 | |
| 	__reg_read(lis, LIS302DL_REG_OUT_Z);
 | |
| 	__reg_read(lis, LIS302DL_REG_STATUS);
 | |
| 	__reg_read(lis, LIS302DL_REG_FF_WU_SRC_1);
 | |
| 	__reg_read(lis, LIS302DL_REG_FF_WU_SRC_2);
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD | 7);
 | |
| }
 | |
| 
 | |
| static void __enable_data_collection(struct lis302dl_info *lis)
 | |
| {
 | |
| 	u_int8_t ctrl1 = LIS302DL_CTRL1_PD | LIS302DL_CTRL1_Xen |
 | |
| 			 LIS302DL_CTRL1_Yen | LIS302DL_CTRL1_Zen;
 | |
| 
 | |
| 	/* 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);
 | |
| 	} else {
 | |
| 		__reg_write(lis, LIS302DL_REG_CTRL2,
 | |
| 				LIS302DL_CTRL2_HPFF1);
 | |
| 		__reg_write(lis, LIS302DL_REG_FF_WU_THS_1,
 | |
| 				__mg_to_threshold(lis, lis->threshold));
 | |
| 		__reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1,
 | |
| 				__ms_to_duration(lis, lis->duration));
 | |
| 
 | |
| 		/* Clear the HP filter "starting point" */
 | |
| 		__reg_read(lis, LIS302DL_REG_HP_FILTER_RESET);
 | |
| 		__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);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #if 0
 | |
| static void _report_btn_single(struct input_dev *inp, int btn)
 | |
| {
 | |
| 	input_report_key(inp, btn, 1);
 | |
| 	input_sync(inp);
 | |
| 	input_report_key(inp, btn, 0);
 | |
| }
 | |
| 
 | |
| static void _report_btn_double(struct input_dev *inp, int btn)
 | |
| {
 | |
| 	input_report_key(inp, btn, 1);
 | |
| 	input_sync(inp);
 | |
| 	input_report_key(inp, btn, 0);
 | |
| 	input_sync(inp);
 | |
| 	input_report_key(inp, btn, 1);
 | |
| 	input_sync(inp);
 | |
| 	input_report_key(inp, btn, 0);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| static void lis302dl_bitbang_read_sample(struct lis302dl_info *lis)
 | |
| {
 | |
| 	u8 data[(LIS302DL_REG_OUT_Z - LIS302DL_REG_STATUS) + 2] = {0xC0 | LIS302DL_REG_STATUS};
 | |
| 	u8 *read = data + 1;
 | |
| 	unsigned long flags;
 | |
| 	int mg_per_sample = __threshold_to_mg(lis, 1);
 | |
| 	struct spi_message msg;
 | |
| 	struct spi_transfer t;
 | |
| 
 | |
| 	spi_message_init(&msg);
 | |
| 	memset(&t, 0, sizeof t);
 | |
| 	t.len = sizeof(data);
 | |
| 	spi_message_add_tail(&t, &msg);
 | |
| 	t.tx_buf = &data[0];
 | |
| 	t.rx_buf = &data[0];
 | |
| 
 | |
| 	/* grab the set of register containing status and XYZ data */
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	/* Should complete without blocking */
 | |
| 	if (spi_non_blocking_transfer(lis->spi, &msg) < 0)
 | |
| 		dev_err(lis->dev, "Error reading registers\n");
 | |
| 
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	/*
 | |
| 	 * at the minute the test below fails 50% of the time due to
 | |
| 	 * a problem with level interrupts causing ISRs to get called twice.
 | |
| 	 * This is a workaround for that, but actually this test is still
 | |
| 	 * valid and the information can be used for overrrun stats.
 | |
| 	 */
 | |
| 
 | |
| 	/* has any kind of overrun been observed by the lis302dl? */
 | |
| 	if (read[0] & (LIS302DL_STATUS_XOR |
 | |
| 		       LIS302DL_STATUS_YOR |
 | |
| 		       LIS302DL_STATUS_ZOR))
 | |
| 		lis->overruns++;
 | |
| 
 | |
| 	/* we have a valid sample set? */
 | |
| 	if (read[0] & LIS302DL_STATUS_XYZDA) {
 | |
| 		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 *
 | |
| 			    (s8)read[LIS302DL_REG_OUT_Y - LIS302DL_REG_STATUS]);
 | |
| 		input_report_abs(lis->input_dev, ABS_Z, mg_per_sample *
 | |
| 			    (s8)read[LIS302DL_REG_OUT_Z - LIS302DL_REG_STATUS]);
 | |
| 
 | |
| 		input_sync(lis->input_dev);
 | |
| 	}
 | |
| 
 | |
| 	if (lis->threshold)
 | |
| 		/* acknowledge the wakeup source */
 | |
| 		__reg_read(lis,	LIS302DL_REG_FF_WU_SRC_1);
 | |
| }
 | |
| 
 | |
| static irqreturn_t lis302dl_interrupt(int irq, void *_lis)
 | |
| {
 | |
| 	struct lis302dl_info *lis = _lis;
 | |
| 
 | |
| 	lis302dl_bitbang_read_sample(lis);
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| /* sysfs */
 | |
| 
 | |
| static ssize_t show_overruns(struct device *dev, struct device_attribute *attr,
 | |
| 			 char *buf)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 
 | |
| 	return sprintf(buf, "%u\n", lis->overruns);
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(overruns, S_IRUGO, show_overruns, NULL);
 | |
| 
 | |
| 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);
 | |
| 
 | |
| 	return sprintf(buf, "%d\n", ctrl1 & LIS302DL_CTRL1_DR ? 400 : 100);
 | |
| }
 | |
| 
 | |
| static ssize_t set_rate(struct device *dev, struct device_attribute *attr,
 | |
| 			const char *buf, size_t count)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	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;
 | |
| 	}
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(sample_rate, S_IRUGO | S_IWUSR, show_rate, set_rate);
 | |
| 
 | |
| static ssize_t show_scale(struct device *dev, struct device_attribute *attr,
 | |
| 			  char *buf)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	u_int8_t ctrl1;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 	ctrl1 = __reg_read(lis, LIS302DL_REG_CTRL1);
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	return sprintf(buf, "%s\n", ctrl1 & LIS302DL_CTRL1_FS ? "9.2" : "2.3");
 | |
| }
 | |
| 
 | |
| static ssize_t set_scale(struct device *dev, struct device_attribute *attr,
 | |
| 			 const char *buf, size_t count)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	if (!strcmp(buf, "9.2\n")) {
 | |
| 		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_FS,
 | |
| 				 LIS302DL_CTRL1_FS);
 | |
| 		lis->flags |= LIS302DL_F_FS;
 | |
| 	} else {
 | |
| 		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_FS,
 | |
| 				0);
 | |
| 		lis->flags &= ~LIS302DL_F_FS;
 | |
| 	}
 | |
| 
 | |
| 	if (lis->flags & LIS302DL_F_INPUT_OPEN)
 | |
| 		__enable_data_collection(lis);
 | |
| 
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(full_scale, S_IRUGO | S_IWUSR, show_scale, set_scale);
 | |
| 
 | |
| static ssize_t show_threshold(struct device *dev, struct device_attribute *attr,
 | |
| 		 char *buf)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 
 | |
| 	/* Display the device view of the threshold setting */
 | |
| 	return sprintf(buf, "%d\n", __threshold_to_mg(lis,
 | |
| 			__mg_to_threshold(lis, lis->threshold)));
 | |
| }
 | |
| 
 | |
| static ssize_t set_threshold(struct device *dev, struct device_attribute *attr,
 | |
| 		 const char *buf, size_t count)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	unsigned int val;
 | |
| 
 | |
| 	if (sscanf(buf, "%u\n", &val) != 1)
 | |
| 		return -EINVAL;
 | |
| 	/* 8g is the maximum if FS is 1 */
 | |
| 	if (val > 8000)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	/* Set the threshold and write it out if the device is used */
 | |
| 	lis->threshold = val;
 | |
| 
 | |
| 	if (lis->flags & LIS302DL_F_INPUT_OPEN) {
 | |
| 		unsigned long flags;
 | |
| 
 | |
| 		local_irq_save(flags);
 | |
| 		__enable_data_collection(lis);
 | |
| 		local_irq_restore(flags);
 | |
| 	}
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(threshold, S_IRUGO | S_IWUSR, show_threshold, set_threshold);
 | |
| 
 | |
| static ssize_t show_duration(struct device *dev, struct device_attribute *attr,
 | |
| 		 char *buf)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 
 | |
| 	return sprintf(buf, "%d\n", __duration_to_ms(lis,
 | |
| 			__ms_to_duration(lis, lis->duration)));
 | |
| }
 | |
| 
 | |
| static ssize_t set_duration(struct device *dev, struct device_attribute *attr,
 | |
| 		 const char *buf, size_t count)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	unsigned int val;
 | |
| 
 | |
| 	if (sscanf(buf, "%u\n", &val) != 1)
 | |
| 		return -EINVAL;
 | |
| 	if (val > 2550)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	lis->duration = val;
 | |
| 	if (lis->flags & LIS302DL_F_INPUT_OPEN)
 | |
| 		__reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1,
 | |
| 				__ms_to_duration(lis, lis->duration));
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(duration, S_IRUGO | S_IWUSR, show_duration, set_duration);
 | |
| 
 | |
| static ssize_t lis302dl_dump(struct device *dev, struct device_attribute *attr,
 | |
| 								      char *buf)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	int n = 0;
 | |
| 	u8 reg[0x40];
 | |
| 	char *end = buf;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	for (n = 0; n < sizeof(reg); n++)
 | |
| 		reg[n] = __reg_read(lis, n);
 | |
| 
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	for (n = 0; n < sizeof(reg); n += 16) {
 | |
| 		hex_dump_to_buffer(reg + n, 16, 16, 1, end, 128, 0);
 | |
| 		end += strlen(end);
 | |
| 		*end++ = '\n';
 | |
| 		*end++ = '\0';
 | |
| 	}
 | |
| 
 | |
| 	return end - buf;
 | |
| }
 | |
| static DEVICE_ATTR(dump, S_IRUGO, lis302dl_dump, NULL);
 | |
| 
 | |
| /* Configure freefall/wakeup interrupts */
 | |
| static ssize_t set_wakeup_threshold(struct device *dev,
 | |
| 		struct device_attribute *attr, const char *buf, size_t count)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	unsigned int threshold;
 | |
| 
 | |
| 	if (sscanf(buf, "%u\n", &threshold) != 1)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (threshold > 8000)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	/* Zero turns the feature off */
 | |
| 	if (threshold == 0) {
 | |
| 		if (lis->flags & LIS302DL_F_IRQ_WAKE) {
 | |
| 			disable_irq_wake(lis->pdata->interrupt);
 | |
| 			lis->flags &= ~LIS302DL_F_IRQ_WAKE;
 | |
| 		}
 | |
| 
 | |
| 		return count;
 | |
| 	}
 | |
| 
 | |
| 	lis->wakeup.threshold = threshold;
 | |
| 
 | |
| 	if (!(lis->flags & LIS302DL_F_IRQ_WAKE)) {
 | |
| 		enable_irq_wake(lis->pdata->interrupt);
 | |
| 		lis->flags |= LIS302DL_F_IRQ_WAKE;
 | |
| 	}
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static ssize_t show_wakeup_threshold(struct device *dev,
 | |
| 		struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 
 | |
| 	/* All events off? */
 | |
| 	if (lis->wakeup.threshold == 0)
 | |
| 		return sprintf(buf, "off\n");
 | |
| 
 | |
| 	return sprintf(buf, "%u\n", lis->wakeup.threshold);
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(wakeup_threshold, S_IRUGO | S_IWUSR, show_wakeup_threshold,
 | |
| 		set_wakeup_threshold);
 | |
| 
 | |
| static ssize_t set_wakeup_duration(struct device *dev,
 | |
| 		struct device_attribute *attr, const char *buf, size_t count)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 	unsigned int duration;
 | |
| 
 | |
| 	if (sscanf(buf, "%u\n", &duration) != 1)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (duration > 2550)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	lis->wakeup.duration = duration;
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static ssize_t show_wakeup_duration(struct device *dev,
 | |
| 		struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(dev);
 | |
| 
 | |
| 	return sprintf(buf, "%u\n", lis->wakeup.duration);
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(wakeup_duration, S_IRUGO | S_IWUSR, show_wakeup_duration,
 | |
| 		set_wakeup_duration);
 | |
| 
 | |
| static struct attribute *lis302dl_sysfs_entries[] = {
 | |
| 	&dev_attr_sample_rate.attr,
 | |
| 	&dev_attr_full_scale.attr,
 | |
| 	&dev_attr_threshold.attr,
 | |
| 	&dev_attr_duration.attr,
 | |
| 	&dev_attr_dump.attr,
 | |
| 	&dev_attr_wakeup_threshold.attr,
 | |
| 	&dev_attr_wakeup_duration.attr,
 | |
| 	&dev_attr_overruns.attr,
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| static struct attribute_group lis302dl_attr_group = {
 | |
| 	.name	= NULL,
 | |
| 	.attrs	= lis302dl_sysfs_entries,
 | |
| };
 | |
| 
 | |
| /* input device handling and driver core interaction */
 | |
| 
 | |
| static int lis302dl_input_open(struct input_dev *inp)
 | |
| {
 | |
| 	struct lis302dl_info *lis = input_get_drvdata(inp);
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	__enable_data_collection(lis);
 | |
| 	lis->flags |= LIS302DL_F_INPUT_OPEN;
 | |
| 
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void lis302dl_input_close(struct input_dev *inp)
 | |
| {
 | |
| 	struct lis302dl_info *lis = input_get_drvdata(inp);
 | |
| 	u_int8_t ctrl1 = LIS302DL_CTRL1_Xen | LIS302DL_CTRL1_Yen |
 | |
| 			 LIS302DL_CTRL1_Zen;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	/* since the input core already serializes access and makes sure we
 | |
| 	 * 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 ||
 | |
| 	      lis->flags & LIS302DL_F_WUP_CLICK)) {
 | |
| 		__reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD,
 | |
| 				 0x00);
 | |
| 	}
 | |
| 	local_irq_restore(flags);
 | |
| }
 | |
| 
 | |
| /* get the device to reload its coefficients from EEPROM and wait for it
 | |
|  * to complete
 | |
|  */
 | |
| 
 | |
| static int __lis302dl_reset_device(struct lis302dl_info *lis)
 | |
| {
 | |
| 	int timeout = 10;
 | |
| 
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL2,
 | |
| 			LIS302DL_CTRL2_BOOT | LIS302DL_CTRL2_FDS);
 | |
| 
 | |
| 	while ((__reg_read(lis, LIS302DL_REG_CTRL2)
 | |
| 			& LIS302DL_CTRL2_BOOT) && (timeout--))
 | |
| 		mdelay(1);
 | |
| 
 | |
| 	return !!(timeout < 0);
 | |
| }
 | |
| 
 | |
| static int __devinit lis302dl_probe(struct spi_device *spi)
 | |
| {
 | |
| 	int rc;
 | |
| 	struct lis302dl_info *lis;
 | |
| 	u_int8_t wai;
 | |
| 	unsigned long flags;
 | |
| 	struct lis302dl_platform_data *pdata = spi->dev.platform_data;
 | |
| 
 | |
| 	spi->mode = SPI_MODE_3;
 | |
| 	rc = spi_setup(spi);
 | |
| 	if (rc < 0) {
 | |
| 		dev_err(&spi->dev, "spi_setup failed\n");
 | |
| 		return rc;
 | |
| 	}
 | |
| 
 | |
| 	lis = kzalloc(sizeof(*lis), GFP_KERNEL);
 | |
| 	if (!lis)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	lis->dev = &spi->dev;
 | |
| 	lis->spi = spi;
 | |
| 
 | |
| 	dev_set_drvdata(lis->dev, lis);
 | |
| 
 | |
| 	lis->pdata = pdata;
 | |
| 
 | |
| 	rc = sysfs_create_group(&lis->dev->kobj, &lis302dl_attr_group);
 | |
| 	if (rc) {
 | |
| 		dev_err(lis->dev, "error creating sysfs group\n");
 | |
| 		goto bail_free_lis;
 | |
| 	}
 | |
| 
 | |
| 	/* initialize input layer details */
 | |
| 	lis->input_dev = input_allocate_device();
 | |
| 	if (!lis->input_dev) {
 | |
| 		dev_err(lis->dev, "Unable to allocate input device\n");
 | |
| 		goto bail_sysfs;
 | |
| 	}
 | |
| 
 | |
| 	input_set_drvdata(lis->input_dev, lis);
 | |
| 	lis->input_dev->name = pdata->name;
 | |
| 	 /* SPI Bus not defined as a valid bus for input subsystem*/
 | |
| 	lis->input_dev->id.bustype = BUS_I2C; /* lie about it */
 | |
| 	lis->input_dev->open = lis302dl_input_open;
 | |
| 	lis->input_dev->close = lis302dl_input_close;
 | |
| 
 | |
| 	rc = input_register_device(lis->input_dev);
 | |
| 	if (rc) {
 | |
| 		dev_err(lis->dev, "error %d registering input device\n", rc);
 | |
| 		goto bail_inp_dev;
 | |
| 	}
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 	/* Configure our IO */
 | |
| 	(lis->pdata->lis302dl_suspend_io)(lis, 1);
 | |
| 
 | |
| 	wai = __reg_read(lis, LIS302DL_REG_WHO_AM_I);
 | |
| 	if (wai != LIS302DL_WHO_AM_I_MAGIC) {
 | |
| 		dev_err(lis->dev, "unknown who_am_i signature 0x%02x\n", wai);
 | |
| 		dev_set_drvdata(lis->dev, NULL);
 | |
| 		rc = -ENODEV;
 | |
| 		local_irq_restore(flags);
 | |
| 		goto bail_inp_reg;
 | |
| 	}
 | |
| 
 | |
| 	set_bit(EV_ABS, lis->input_dev->evbit);
 | |
| 	input_set_abs_params(lis->input_dev, ABS_X, 0, 0, 0, 0);
 | |
| 	input_set_abs_params(lis->input_dev, ABS_Y, 0, 0, 0, 0);
 | |
| 	input_set_abs_params(lis->input_dev, ABS_Z, 0, 0, 0, 0);
 | |
| 
 | |
| 	lis->threshold = 0;
 | |
| 	lis->duration = 0;
 | |
| 	memset(&lis->wakeup, 0, sizeof(lis->wakeup));
 | |
| 
 | |
| 	if (__lis302dl_reset_device(lis))
 | |
| 		dev_err(lis->dev, "device BOOT reload failed\n");
 | |
| 
 | |
| 	/* force us powered */
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD |
 | |
| 			LIS302DL_CTRL1_Xen |
 | |
| 			LIS302DL_CTRL1_Yen |
 | |
| 			LIS302DL_CTRL1_Zen);
 | |
| 	mdelay(1);
 | |
| 
 | |
| 	__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, 0x0);
 | |
| 	__reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1, 0x00);
 | |
| 	__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 |
 | |
| 			LIS302DL_CTRL1_Yen | LIS302DL_CTRL1_Zen);
 | |
| 
 | |
| 	if (pdata->open_drain)
 | |
| 		/* switch interrupt to open collector, active-low */
 | |
| 		__reg_write(lis, LIS302DL_REG_CTRL3,
 | |
| 				LIS302DL_CTRL3_PP_OD | LIS302DL_CTRL3_IHL);
 | |
| 	else
 | |
| 		/* push-pull, active-low */
 | |
| 		__reg_write(lis, LIS302DL_REG_CTRL3, LIS302DL_CTRL3_IHL);
 | |
| 
 | |
| 	__lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_GND);
 | |
| 	__lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_GND);
 | |
| 
 | |
| 	__reg_read(lis, LIS302DL_REG_STATUS);
 | |
| 	__reg_read(lis, LIS302DL_REG_FF_WU_SRC_1);
 | |
| 	__reg_read(lis, LIS302DL_REG_FF_WU_SRC_2);
 | |
| 	__reg_read(lis, LIS302DL_REG_CLICK_SRC);
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	dev_info(lis->dev, "Found %s\n", pdata->name);
 | |
| 
 | |
| 	lis->pdata = pdata;
 | |
| 
 | |
| 	set_irq_handler(lis->pdata->interrupt, handle_level_irq);
 | |
| 
 | |
| 	rc = request_irq(lis->pdata->interrupt, lis302dl_interrupt,
 | |
| 			 IRQF_TRIGGER_LOW, "lis302dl", lis);
 | |
| 
 | |
| 	if (rc < 0) {
 | |
| 		dev_err(lis->dev, "error requesting IRQ %d\n",
 | |
| 			lis->pdata->interrupt);
 | |
| 		goto bail_inp_reg;
 | |
| 	}
 | |
| 	return 0;
 | |
| 
 | |
| bail_inp_reg:
 | |
| 	input_unregister_device(lis->input_dev);
 | |
| bail_inp_dev:
 | |
| 	input_free_device(lis->input_dev);
 | |
| bail_sysfs:
 | |
| 	sysfs_remove_group(&lis->dev->kobj, &lis302dl_attr_group);
 | |
| bail_free_lis:
 | |
| 	kfree(lis);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int __devexit lis302dl_remove(struct spi_device *spi)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	/* Disable interrupts */
 | |
| 	if (lis->flags & LIS302DL_F_IRQ_WAKE)
 | |
| 		disable_irq_wake(lis->pdata->interrupt);
 | |
| 	free_irq(lis->pdata->interrupt, lis);
 | |
| 
 | |
| 	/* Reset and power down the device */
 | |
| 	local_irq_save(flags);
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL3, 0x00);
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL2, 0x00);
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL1, 0x00);
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	/* Cleanup resources */
 | |
| 	sysfs_remove_group(&spi->dev.kobj, &lis302dl_attr_group);
 | |
| 	input_unregister_device(lis->input_dev);
 | |
| 	if (lis->input_dev)
 | |
| 		input_free_device(lis->input_dev);
 | |
| 	dev_set_drvdata(lis->dev, NULL);
 | |
| 	kfree(lis);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_PM
 | |
| 
 | |
| static u8 regs_to_save[] = {
 | |
| 	LIS302DL_REG_CTRL1,
 | |
| 	LIS302DL_REG_CTRL2,
 | |
| 	LIS302DL_REG_CTRL3,
 | |
| 	LIS302DL_REG_FF_WU_CFG_1,
 | |
| 	LIS302DL_REG_FF_WU_THS_1,
 | |
| 	LIS302DL_REG_FF_WU_DURATION_1,
 | |
| 	LIS302DL_REG_FF_WU_CFG_2,
 | |
| 	LIS302DL_REG_FF_WU_THS_2,
 | |
| 	LIS302DL_REG_FF_WU_DURATION_2,
 | |
| 	LIS302DL_REG_CLICK_CFG,
 | |
| 	LIS302DL_REG_CLICK_THSY_X,
 | |
| 	LIS302DL_REG_CLICK_THSZ,
 | |
| 	LIS302DL_REG_CLICK_TIME_LIMIT,
 | |
| 	LIS302DL_REG_CLICK_LATENCY,
 | |
| 	LIS302DL_REG_CLICK_WINDOW,
 | |
| 
 | |
| };
 | |
| 
 | |
| 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;
 | |
| 	int n;
 | |
| 
 | |
| 	/* determine if we want to wake up from the accel. */
 | |
| 	if (lis->flags & LIS302DL_F_WUP_CLICK)
 | |
| 		return 0;
 | |
| 
 | |
| 	disable_irq(lis->pdata->interrupt);
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	/*
 | |
| 	 * When we share SPI over multiple sensors, there is a race here
 | |
| 	 * that one or more sensors will lose.  In that case, the shared
 | |
| 	 * SPI bus GPIO will be in sleep mode and partially pulled down.  So
 | |
| 	 * we explicitly put our IO into "wake" mode here before the final
 | |
| 	 * traffic to the sensor.
 | |
| 	 */
 | |
| 	(lis->pdata->lis302dl_suspend_io)(lis, 1);
 | |
| 
 | |
| 	/* save registers */
 | |
| 	for (n = 0; n < ARRAY_SIZE(regs_to_save); n++)
 | |
| 		lis->regs[regs_to_save[n]] =
 | |
| 			__reg_read(lis, regs_to_save[n]);
 | |
| 
 | |
| 	/* power down or enable wakeup */
 | |
| 
 | |
| 	if (lis->wakeup.threshold == 0) {
 | |
| 		tmp = __reg_read(lis, LIS302DL_REG_CTRL1);
 | |
| 		tmp &= ~LIS302DL_CTRL1_PD;
 | |
| 		__reg_write(lis, LIS302DL_REG_CTRL1, tmp);
 | |
| 	} else
 | |
| 		__enable_wakeup(lis);
 | |
| 
 | |
| 	/* place our IO to the device in sleep-compatible states */
 | |
| 	(lis->pdata->lis302dl_suspend_io)(lis, 0);
 | |
| 
 | |
| 	local_irq_restore(flags);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int lis302dl_resume(struct spi_device *spi)
 | |
| {
 | |
| 	struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
 | |
| 	unsigned long flags;
 | |
| 	int n;
 | |
| 
 | |
| 	if (lis->flags & LIS302DL_F_WUP_CLICK)
 | |
| 		return 0;
 | |
| 
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	/* get our IO to the device back in operational states */
 | |
| 	(lis->pdata->lis302dl_suspend_io)(lis, 1);
 | |
| 
 | |
| 	/* resume from powerdown first! */
 | |
| 	__reg_write(lis, LIS302DL_REG_CTRL1,
 | |
| 			LIS302DL_CTRL1_PD |
 | |
| 			LIS302DL_CTRL1_Xen |
 | |
| 			LIS302DL_CTRL1_Yen |
 | |
| 			LIS302DL_CTRL1_Zen);
 | |
| 	mdelay(1);
 | |
| 
 | |
| 	if (__lis302dl_reset_device(lis))
 | |
| 		dev_err(&spi->dev, "device BOOT reload failed\n");
 | |
| 
 | |
| 	lis->regs[LIS302DL_REG_CTRL1] |=	LIS302DL_CTRL1_PD |
 | |
| 						LIS302DL_CTRL1_Xen |
 | |
| 						LIS302DL_CTRL1_Yen |
 | |
| 						LIS302DL_CTRL1_Zen;
 | |
| 
 | |
| 	/* restore registers after resume */
 | |
| 	for (n = 0; n < ARRAY_SIZE(regs_to_save); n++)
 | |
| 		__reg_write(lis, regs_to_save[n], lis->regs[regs_to_save[n]]);
 | |
| 
 | |
| 	/* if someone had us open, reset the non-wake threshold stuff */
 | |
| 	if (lis->flags & LIS302DL_F_INPUT_OPEN)
 | |
| 		__enable_data_collection(lis);
 | |
| 
 | |
| 	local_irq_restore(flags);
 | |
| 	enable_irq(lis->pdata->interrupt);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| #else
 | |
| #define lis302dl_suspend	NULL
 | |
| #define lis302dl_resume		NULL
 | |
| #endif
 | |
| 
 | |
| static struct spi_driver lis302dl_spi_driver = {
 | |
| 	.driver = {
 | |
| 		.name = "lis302dl",
 | |
| 		.owner = THIS_MODULE,
 | |
| 	},
 | |
| 
 | |
| 	.probe 	= lis302dl_probe,
 | |
| 	.remove	= __devexit_p(lis302dl_remove),
 | |
| 	.suspend = lis302dl_suspend,
 | |
| 	.resume	 = lis302dl_resume,
 | |
| };
 | |
| 
 | |
| static int __devinit lis302dl_init(void)
 | |
| {
 | |
| 	return spi_register_driver(&lis302dl_spi_driver);
 | |
| }
 | |
| 
 | |
| static void __exit lis302dl_exit(void)
 | |
| {
 | |
| 	spi_unregister_driver(&lis302dl_spi_driver);
 | |
| }
 | |
| 
 | |
| MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
 | |
| MODULE_LICENSE("GPL");
 | |
| 
 | |
| module_init(lis302dl_init);
 | |
| module_exit(lis302dl_exit);
 |