/*
* 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
};