mma7660(G-senser) 驱动

/*
 * Copyright 2011 Freescale Semiconductor, Inc. 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
 * version 2 as published by the Free Software Foundation.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef __LINUX_MMA7660_H
#define __LINUX_MMA7660_H

#include <linux/types.h>

#ifdef __KERNEL__

/* MMA7760 Registers */
#define MMA7660_XOUT   0x00 // 6-bit output value X
#define MMA7660_YOUT   0x01 // 6-bit output value Y
#define MMA7660_ZOUT   0x02 // 6-bit output value Z
#define MMA7660_TILT   0x03 // Tilt status
#define MMA7660_SRST   0x04 // Sampling Rate Status
#define MMA7660_SPCNT   0x05 // Sleep Count
#define MMA7660_INTSU   0x06 // Interrupt Setup
#define MMA7660_MODE   0x07 // Mode
#define MMA7660_SR    0x08 // Auto-Wake/Sleep and Debounce Filter
#define MMA7660_PDET   0x09 // Tap Detection
#define MMA7660_PD    0x0a // Tap Debounce Count

struct mma7660_platform_data {
 /* eint connected to chip */
 int irq;

 /* parameters for input device */
 int poll_interval;
 int input_fuzz;
 int input_flat;
};

#endif /* __KERNEL__ */

#endif /* __LINUX_MMA7660_H */


 

/*
 * linux/drivers/hwmon/mma7660.c
 *
 * 3-Axis Orientation/Motion Detection Sensor support
 *
 * Copyright (C) 2009-2010 Freescale Semiconductor Ltd.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/input-polldev.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/mma7660.h>
#include <mach/hardware.h>


#define MMA7660_NAME		"mma7660"

#define POLL_INTERVAL		100
#define INPUT_FUZZ			4
#define INPUT_FLAT			4


static struct i2c_client	*mma7660_client;

static struct device		*hwmon_dev;
static struct input_polled_dev	*mma7660_idev;
static struct mma7660_platform_data	*plat_data;

static int					last_tilt = 0;
static int					oper_mode;


/*-----------------------------------------------------------------------------
 * MMA7660 operations
 */

#define __need_retry(__v)	(__v & (1 << 6))
#define __is_negative(__v)	(__v & (1 << 5))

static const char *mma7660_bafro[] = {
	"Unknown", "Front", "Back"
};
static const char *mma7660_pola[] = {
	"Unknown",
	"Left", "Right",
	"Rsvd", "Rsvd",
	"Down", "Up",
	"Rsvd",
};

static int mma7660_read_xyz(struct i2c_client *client, int idx, int *xyz)
{
	int val;

	do {
		val = i2c_smbus_read_byte_data(client, idx + MMA7660_XOUT);
		if (val < 0) {
			dev_err(&client->dev, "Read register %02x failed, %d\n",
					idx + MMA7660_XOUT, val);
			return -EIO;
		}
	} while (__need_retry(val));

	*xyz = __is_negative(val) ? (val | ~0x3f) : (val & 0x3f);

	return 0;
}

static int mma7660_read_tilt(struct i2c_client *client, int *tilt)
{
	int val;

	do {
		val = i2c_smbus_read_byte_data(client, MMA7660_TILT);
		if (val < 0) {
			dev_err(&client->dev, "Read register %02x failed, %d\n",
					MMA7660_TILT, val);
			return -EIO;
		}
	} while (__need_retry(val));

	*tilt = (val & 0xff);

	return 0;
}

static int mma7660_initialize(struct i2c_client *client)
{
	int val;

	/* Using test mode to probe chip */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
	mdelay(10);
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04);
	mdelay(10);
	i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f);
	i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x01);
	i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x15);
	val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT);
	if (val != 0x15) {
		dev_err(&client->dev, "no device\n");
		return -ENODEV;
	}

	/* Goto standby mode for configuration */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
	mdelay(10);

	/* Sample rate: 64Hz / 16Hz; Filt: 3 samples  */
	i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1));

	/* Sleep count */
	i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0);

	/* Tap detect and debounce ~4ms */
	i2c_smbus_write_byte_data(client, MMA7660_PDET, 4);
	i2c_smbus_write_byte_data(client, MMA7660_PD, 15);

	/* Enable interrupt except exiting Auto-Sleep */
	i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7);

	/* IPP, Auto-wake, auto-sleep and standby */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59);
	mdelay(10);

	/* Save current tilt status */
	mma7660_read_tilt(client, &last_tilt);

	mma7660_client = client;
	return 0;
}


/*-----------------------------------------------------------------------------
 * sysfs group support
 */

static ssize_t mma7660_show_regs(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int reg, val;
	int i, len = 0;

	for (reg = 0; reg < 0x0b; reg++) {
		val = i2c_smbus_read_byte_data(mma7660_client, reg);
		len += sprintf(buf + len, "REG: 0x%02x = 0x%02x ...... [ ", reg, val);
		for (i = 7; i >= 0; i--) {
			len += sprintf(buf + len, "%d", (val >> i) & 1);
			if ((i % 4) == 0) {
				len += sprintf(buf + len, " ");
			}
		}
		len += sprintf(buf + len, "]\n");
	}

	return len;
}

static ssize_t mma7660_write_reg(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	unsigned int reg, val;
	int ret;

	ret = sscanf(buf, "%x %x", ®, &val);
	if (ret == 2) {
		if (reg >= 0 && reg <= 0x0a) {
			i2c_smbus_write_byte_data(mma7660_client, reg, val);
			val = i2c_smbus_read_byte_data(mma7660_client, reg);
			printk("REG: 0x%02x = 0x%02x\n", reg, val);
		}
	}

	return count;
}

static ssize_t mma7660_show_xyz_g(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int axis[3];
	int i;

	for (i = 0; i < 3; i++) {
		mma7660_read_xyz(mma7660_client, i, &axis[i]);
	}

	return sprintf(buf, "%3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}

static ssize_t mma7660_show_axis_g(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int n = to_sensor_dev_attr(attr)->index;
	int val;

	mma7660_read_xyz(mma7660_client, n, &val);

	return sprintf(buf, "%3d\n", val);
}

static ssize_t mma7660_show_tilt(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int val = 0, len = 0;

	mma7660_read_tilt(mma7660_client, &val);

	len += sprintf(buf + len, "%s", mma7660_bafro[val & 0x03]);
	len += sprintf(buf + len, ", %s", mma7660_pola[(val >> 2) & 0x07]);
	if (val & (1 << 5)) {
		len += sprintf(buf + len, ", Tap");
	}
	if (val & (1 << 7)) {
		len += sprintf(buf + len, ", Shake");
	}
	len += sprintf(buf + len, "\n");

	return len;
}

static SENSOR_DEVICE_ATTR(registers, S_IRUGO | S_IWUGO,
		mma7660_show_regs, mma7660_write_reg, 0);
static SENSOR_DEVICE_ATTR(x_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 0);
static SENSOR_DEVICE_ATTR(y_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 1);
static SENSOR_DEVICE_ATTR(z_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 2);
static SENSOR_DEVICE_ATTR(all_axis_g, S_IRUGO, mma7660_show_xyz_g, NULL, 0);
static SENSOR_DEVICE_ATTR(tilt_status, S_IRUGO, mma7660_show_tilt, NULL, 0);

static struct attribute* mma7660_attrs[] = {
	&sensor_dev_attr_registers.dev_attr.attr,
	&sensor_dev_attr_x_axis_g.dev_attr.attr,
	&sensor_dev_attr_y_axis_g.dev_attr.attr,
	&sensor_dev_attr_z_axis_g.dev_attr.attr,
	&sensor_dev_attr_all_axis_g.dev_attr.attr,
	&sensor_dev_attr_tilt_status.dev_attr.attr,
	NULL
};

static const struct attribute_group mma7660_group = {
	.attrs		= mma7660_attrs,
};


/*-----------------------------------------------------------------------------
 * Input interfaces
 */
static void mma7660_report_abs(void)
{
	int axis[3];
	int i;

	for (i = 0; i < 3; i++) {
		mma7660_read_xyz(mma7660_client, i, &axis[i]);
	}

	input_report_abs(mma7660_idev->input, ABS_X, axis[0]);
	input_report_abs(mma7660_idev->input, ABS_Y, axis[1]);
	input_report_abs(mma7660_idev->input, ABS_Z, axis[2]);
	input_sync(mma7660_idev->input);

	//printk("3-Axis ... %3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}

static void mma7660_dev_poll(struct input_polled_dev *dev)
{
	mma7660_report_abs();
}


/*-----------------------------------------------------------------------------
 * Interrupt handler
 */

static void mma7660_worker(struct work_struct *work)
{
	int bafro, pola, shake, tap;
	int val = 0;

	mma7660_read_tilt(mma7660_client, &val);

	/* TODO: report it ? */

	bafro = val & 0x03;
	if (bafro != (last_tilt & 0x03)) {
		printk("%s\n", mma7660_bafro[bafro]);
	}

	pola = (val >> 2) & 0x07;
	if (pola != ((last_tilt >> 2) & 0x07)) {
		printk("%s\n", mma7660_pola[pola]);
	}

	shake = (val >> 5) & 0x01;
	if (shake && shake != ((last_tilt >> 5) & 0x01)) {
		printk("Shake\n");
	}

	tap = (val >> 7) & 0x01;
	if (tap && tap != ((last_tilt >> 7) & 0x01)) {
		printk("Tap\n");
	}

	/* Save current status */
	last_tilt = val;
}

DECLARE_WORK(mma7660_work, mma7660_worker);

static irqreturn_t mma7660_interrupt(int irq, void *dev_id)
{
	schedule_work(&mma7660_work);

	return IRQ_HANDLED;
}


/*-----------------------------------------------------------------------------
 * I2C client driver interfaces
 */

static int __devinit mma7660_probe(struct i2c_client *client,
		const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	struct input_dev *idev;
	int poll_interval = POLL_INTERVAL;
	int input_fuzz = INPUT_FUZZ;
	int input_flat = INPUT_FLAT;
	int ret;

	ret = i2c_check_functionality(adapter,
			I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA);
	if (!ret) {
		dev_err(&client->dev, "I2C check functionality failed\n");
		return -ENXIO;
	}

	plat_data = (struct mma7660_platform_data *)client->dev.platform_data;
	if (plat_data == NULL) {
		dev_err(&client->dev, "lack of platform data!\n");
		return -ENODEV;
	}

	/* Get parameters from platfrom data */
	if (plat_data->poll_interval > 0)
		poll_interval = plat_data->poll_interval;
	if (plat_data->input_fuzz > 0)
		input_fuzz = plat_data->input_fuzz;
	if (plat_data->input_flat > 0)
		input_flat = plat_data->input_flat;

	if (mma7660_initialize(client) < 0) {
		goto error_init_client;
	}

	ret = sysfs_create_group(&client->dev.kobj, &mma7660_group);
	if (ret) {
		dev_err(&client->dev, "create sysfs group failed!\n");
		goto error_init_client;
	}

	/* register to hwmon device */
	hwmon_dev = hwmon_device_register(&client->dev);
	if (IS_ERR(hwmon_dev)) {
		dev_err(&client->dev, "hwmon register failed!\n");
		ret = PTR_ERR(hwmon_dev);
		goto error_rm_dev_file;
	}

	/* input poll device register */
	mma7660_idev = input_allocate_polled_device();
	if (!mma7660_idev) {
		dev_err(&client->dev, "alloc poll device failed!\n");
		ret = -ENOMEM;
		goto error_rm_hwmon_dev;
	}

	mma7660_idev->poll = mma7660_dev_poll;
	mma7660_idev->poll_interval = plat_data->poll_interval;

	idev = mma7660_idev->input;
	idev->name = MMA7660_NAME;
	idev->id.bustype = BUS_I2C;
	idev->id.vendor = 0x12FA;
	idev->id.product = 0x7660;
	idev->id.version = 0x0100;
	idev->dev.parent = &client->dev;

	set_bit(EV_ABS, idev->evbit);

	set_bit(ABS_X, idev->absbit);
	set_bit(ABS_Y, idev->absbit);
	set_bit(ABS_Z, idev->absbit);

	input_set_abs_params(idev, ABS_X, -512, 512, input_fuzz, input_flat);
	input_set_abs_params(idev, ABS_Y, -512, 512, input_fuzz, input_flat);
	input_set_abs_params(idev, ABS_Z, -512, 512, input_fuzz, input_flat);
	ret = input_register_polled_device(mma7660_idev);
	if (ret) {
		dev_err(&client->dev, "register poll device failed!\n");
		goto error_free_poll_dev;
	}

	/* register interrupt handle */
	ret = request_irq(plat_data->irq, mma7660_interrupt,
			IRQF_TRIGGER_FALLING, MMA7660_NAME, idev);
	if (ret) {
		dev_err(&client->dev, "request irq (%d) failed %d\n", plat_data->irq, ret);
		goto error_rm_poll_dev;
	}

	dev_info(&client->dev, "MMA7660 device is probed successfully.\n");

#if	0
	set_mod(1);
#endif

	return 0;

error_rm_poll_dev:
	input_unregister_polled_device(mma7660_idev);
error_free_poll_dev:
	input_free_polled_device(mma7660_idev);
error_rm_hwmon_dev:
	hwmon_device_unregister(hwmon_dev);
error_rm_dev_file:
	sysfs_remove_group(&client->dev.kobj, &mma7660_group);
error_init_client:
	mma7660_client = NULL;

	return 0;
}

static int __devexit mma7660_remove(struct i2c_client *client)
{
	free_irq(plat_data->irq, mma7660_idev->input);

	input_unregister_polled_device(mma7660_idev);
	input_free_polled_device(mma7660_idev);
	hwmon_device_unregister(hwmon_dev);
	sysfs_remove_group(&client->dev.kobj, &mma7660_group);
	mma7660_client = NULL;

	return 0;
}

static int mma7660_suspend(struct i2c_client *client, pm_message_t state)
{
	int ret;

	oper_mode = i2c_smbus_read_byte_data(client, MMA7660_MODE);

	ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, 0);
	if (ret) {
		printk("%s: set mode (0) for suspend failed, ret = %d\n",
				MMA7660_NAME, ret);
	}

	return 0;
}

static int mma7660_resume(struct i2c_client *client)
{
	int ret;

	ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, oper_mode);
	if (ret) {
		printk("%s: set mode (%d) for resume failed, ret = %d\n",
				MMA7660_NAME, oper_mode, ret);
	}

	return 0;
}

static const struct i2c_device_id mma7660_ids[] = {
	{ "mma7660", 0 },
	{ },
};
MODULE_DEVICE_TABLE(i2c, mma7660_ids);

static struct i2c_driver i2c_mma7660_driver = {
	.driver		= {
		.name	= MMA7660_NAME,
	},

	.probe		= mma7660_probe,
	.remove		= __devexit_p(mma7660_remove),
	.suspend	= mma7660_suspend,
	.resume		= mma7660_resume,
	.id_table	= mma7660_ids,
};

static int __init init_mma7660(void)
{
	int ret;

	ret = i2c_add_driver(&i2c_mma7660_driver);
	printk(KERN_INFO "MMA7660 sensor driver registered.\n");

	return ret;
}

static void __exit exit_mma7660(void)
{
	i2c_del_driver(&i2c_mma7660_driver);
	printk(KERN_INFO "MMA7660 sensor driver removed.\n");
}

module_init(init_mma7660);
module_exit(exit_mma7660);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MMA7660 sensor driver");
MODULE_LICENSE("GPL");


 

static struct mma7660_platform_data mma7660_pdata = {
	.irq			= IRQ_EINT9,
	.poll_interval	= 100,
	.input_fuzz		= 4,
	.input_flat		= 4,
};


static struct i2c_board_info mini210_i2c_devs0[] __initdata = {
	{	I2C_BOARD_INFO("24c08", 0x50), },     /* Samsung S524AD0XD1 */
#ifdef CONFIG_SND_SOC_WM8580
	{	I2C_BOARD_INFO("wm8580", 0x1b), },
#endif
#ifdef CONFIG_SND_SOC_WM8960_MINI210
	{
		I2C_BOARD_INFO("wm8960", 0x1a),
		.platform_data = &wm8960_pdata,
	},
#endif
#ifdef CONFIG_SENSORS_MMA7660
	{
		I2C_BOARD_INFO("mma7660", 0x4c),
		.platform_data = &mma7660_pdata,
	},
#endif
};


你可能感兴趣的:(mma7660(G-senser) 驱动)