android_kernel_cmhtcleo/drivers/i2c/chips/akm8973.c
Jon Benson 2dbf06e5df Revert "Removed compass offset hack. It only "worked" if the phone was lying flat anyway, and only for some people."
Rajko assures me this works for most people, so I'll leave it until I get more feedback one way or another.

This reverts commit c60f5846d6.
2010-09-29 15:31:51 +10:00

809 lines
18 KiB
C

/* drivers/i2c/chips/akm8973.c - akm8973 compass driver
*
* Copyright (C) 2008-2009 HTC Corporation.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <asm/gpio.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/freezer.h>
#include <linux/akm8973.h>
#include<linux/earlysuspend.h>
#define DEBUG 0
#define MAX_FAILURE_COUNT 3
static struct i2c_client *this_client;
struct akm8973_data {
struct input_dev *input_dev;
struct work_struct work;
struct early_suspend early_suspend_akm;
};
/* Addresses to scan -- protected by sense_data_mutex */
static char sense_data[RBUFF_SIZE + 1];
static struct mutex sense_data_mutex;
#define AKM8973_RETRY_COUNT 10
static DECLARE_WAIT_QUEUE_HEAD(data_ready_wq);
static DECLARE_WAIT_QUEUE_HEAD(open_wq);
static atomic_t data_ready;
static atomic_t open_count;
static atomic_t open_flag;
static atomic_t reserve_open_flag;
static atomic_t m_flag;
static atomic_t a_flag;
static atomic_t t_flag;
static atomic_t mv_flag;
static int failure_count = 0;
static short akmd_delay = 0;
static atomic_t suspend_flag = ATOMIC_INIT(0);
static struct akm8973_platform_data *pdata;
static int AKI2C_RxData(char *rxData, int length)
{
uint8_t loop_i;
struct i2c_msg msgs[] = {
{
.addr = this_client->addr,
.flags = 0,
.len = 1,
.buf = rxData,
},
{
.addr = this_client->addr,
.flags = I2C_M_RD,
.len = length,
.buf = rxData,
},
};
for (loop_i = 0; loop_i < AKM8973_RETRY_COUNT; loop_i++) {
if (i2c_transfer(this_client->adapter, msgs, 2) > 0) {
break;
}
mdelay(10);
}
if (loop_i >= AKM8973_RETRY_COUNT) {
printk(KERN_ERR "%s retry over %d\n", __func__, AKM8973_RETRY_COUNT);
return -EIO;
}
return 0;
}
static int AKI2C_TxData(char *txData, int length)
{
uint8_t loop_i;
struct i2c_msg msg[] = {
{
.addr = this_client->addr,
.flags = 0,
.len = length,
.buf = txData,
},
};
for (loop_i = 0; loop_i < AKM8973_RETRY_COUNT; loop_i++) {
if (i2c_transfer(this_client->adapter, msg, 1) > 0) {
break;
}
mdelay(10);
}
if (loop_i >= AKM8973_RETRY_COUNT) {
printk(KERN_ERR "%s retry over %d\n", __func__, AKM8973_RETRY_COUNT);
return -EIO;
}
return 0;
}
static void AKECS_Reset(void)
{
gpio_set_value(pdata->reset, 0);
udelay(120);
gpio_set_value(pdata->reset, 1);
}
static int AKECS_StartMeasure(void)
{
char buffer[2];
atomic_set(&data_ready, 0);
/* Set measure mode */
buffer[0] = AKECS_REG_MS1;
buffer[1] = AKECS_MODE_MEASURE;
/* Set data */
return AKI2C_TxData(buffer, 2);
}
static int AKECS_PowerDown(void)
{
char buffer[2];
int ret;
/* Set powerdown mode */
buffer[0] = AKECS_REG_MS1;
buffer[1] = AKECS_MODE_POWERDOWN;
/* Set data */
ret = AKI2C_TxData(buffer, 2);
if (ret < 0)
return ret;
/* Dummy read for clearing INT pin */
buffer[0] = AKECS_REG_TMPS;
/* Read data */
ret = AKI2C_RxData(buffer, 1);
if (ret < 0)
return ret;
return ret;
}
static int AKECS_StartE2PRead(void)
{
char buffer[2];
/* Set measure mode */
buffer[0] = AKECS_REG_MS1;
buffer[1] = AKECS_MODE_E2P_READ;
/* Set data */
return AKI2C_TxData(buffer, 2);
}
static int AKECS_GetData(void)
{
char buffer[RBUFF_SIZE + 1];
int ret;
memset(buffer, 0, RBUFF_SIZE + 1);
buffer[0] = AKECS_REG_ST;
ret = AKI2C_RxData(buffer, RBUFF_SIZE+1);
if (ret < 0)
return ret;
mutex_lock(&sense_data_mutex);
memcpy(sense_data, buffer, sizeof(buffer));
atomic_set(&data_ready, 1);
wake_up(&data_ready_wq);
mutex_unlock(&sense_data_mutex);
return 0;
}
static int AKECS_SetMode(char mode)
{
int ret;
switch (mode) {
case AKECS_MODE_MEASURE:
ret = AKECS_StartMeasure();
break;
case AKECS_MODE_E2P_READ:
ret = AKECS_StartE2PRead();
break;
case AKECS_MODE_POWERDOWN:
ret = AKECS_PowerDown();
break;
default:
return -EINVAL;
}
/* wait at least 300us after changing mode */
mdelay(1);
return ret;
}
static int AKECS_TransRBuff(char *rbuf, int size)
{
wait_event_interruptible_timeout(data_ready_wq,
atomic_read(&data_ready), 1000);
if (!atomic_read(&data_ready)) {
if (!atomic_read(&suspend_flag)) {
printk(KERN_ERR
"AKM8973 AKECS_TransRBUFF: Data not ready\n");
failure_count++;
if (failure_count >= MAX_FAILURE_COUNT) {
printk(KERN_ERR
"AKM8973 AKECS_TransRBUFF: successive %d failure.\n",
failure_count);
atomic_set(&open_flag, -1);
wake_up(&open_wq);
failure_count = 0;
}
}
return -1;
}
mutex_lock(&sense_data_mutex);
memcpy(&rbuf[1], &sense_data[1], size);
atomic_set(&data_ready, 0);
mutex_unlock(&sense_data_mutex);
failure_count = 0;
return 0;
}
static void AKECS_Report_Value(short *rbuf)
{
struct akm8973_data *data = i2c_get_clientdata(this_client);
#if DEBUG
printk(KERN_INFO"AKECS_Report_Value: yaw = %d, pitch = %d, roll = %d\n", rbuf[0],
rbuf[1], rbuf[2]);
printk(KERN_INFO" tmp = %d, m_stat= %d, g_stat=%d\n", rbuf[3],
rbuf[4], rbuf[5]);
printk(KERN_INFO" G_Sensor: x = %d LSB, y = %d LSB, z = %d LSB\n",
rbuf[6], rbuf[7], rbuf[8]);
#endif
/* Offset compass hack taken from michyprimas kernel */
short valueint = rbuf[0];
if ((valueint + 85) < 360)
{
rbuf[0] = valueint + 85;
}
else
{
short diff = 359 - valueint;
rbuf[0] = 85 - diff;
}
/* Report magnetic sensor information */
if (atomic_read(&m_flag)) {
input_report_abs(data->input_dev, ABS_RX, rbuf[0]);
input_report_abs(data->input_dev, ABS_RY, rbuf[1]);
input_report_abs(data->input_dev, ABS_RZ, rbuf[2]);
input_report_abs(data->input_dev, ABS_RUDDER, rbuf[4]);
}
/* Report acceleration sensor information */
if (atomic_read(&a_flag)) {
input_report_abs(data->input_dev, ABS_X, rbuf[6]);
input_report_abs(data->input_dev, ABS_Y, rbuf[7]);
input_report_abs(data->input_dev, ABS_Z, rbuf[8]);
input_report_abs(data->input_dev, ABS_WHEEL, rbuf[5]);
}
/* Report temperature information */
if (atomic_read(&t_flag))
input_report_abs(data->input_dev, ABS_THROTTLE, rbuf[3]);
if (atomic_read(&mv_flag)) {
input_report_abs(data->input_dev, ABS_HAT0X, rbuf[9]);
input_report_abs(data->input_dev, ABS_HAT0Y, rbuf[10]);
input_report_abs(data->input_dev, ABS_BRAKE, rbuf[11]);
}
input_sync(data->input_dev);
}
static int AKECS_GetOpenStatus(void)
{
wait_event_interruptible(open_wq, (atomic_read(&open_flag) != 0));
return atomic_read(&open_flag);
}
static int AKECS_GetCloseStatus(void)
{
wait_event_interruptible(open_wq, (atomic_read(&open_flag) <= 0));
return atomic_read(&open_flag);
}
static void AKECS_CloseDone(void)
{
atomic_set(&m_flag, 0);
atomic_set(&a_flag, 0);
atomic_set(&t_flag, 0);
atomic_set(&mv_flag, 0);
}
static int akm_aot_open(struct inode *inode, struct file *file)
{
int ret = -1;
if (atomic_cmpxchg(&open_count, 0, 1) == 0) {
if (atomic_cmpxchg(&open_flag, 0, 1) == 0) {
atomic_set(&reserve_open_flag, 1);
wake_up(&open_wq);
ret = 0;
}
}
return ret;
}
static int akm_aot_release(struct inode *inode, struct file *file)
{
atomic_set(&reserve_open_flag, 0);
atomic_set(&open_flag, 0);
atomic_set(&open_count, 0);
wake_up(&open_wq);
return 0;
}
static int
akm_aot_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
short flag;
switch (cmd) {
case ECS_IOCTL_APP_SET_MFLAG:
case ECS_IOCTL_APP_SET_AFLAG:
case ECS_IOCTL_APP_SET_TFLAG:
case ECS_IOCTL_APP_SET_MVFLAG:
if (copy_from_user(&flag, argp, sizeof(flag)))
return -EFAULT;
if (flag < 0 || flag > 1)
return -EINVAL;
break;
case ECS_IOCTL_APP_SET_DELAY:
if (copy_from_user(&flag, argp, sizeof(flag)))
return -EFAULT;
break;
default:
break;
}
switch (cmd) {
case ECS_IOCTL_APP_SET_MFLAG:
atomic_set(&m_flag, flag);
break;
case ECS_IOCTL_APP_GET_MFLAG:
flag = atomic_read(&m_flag);
break;
case ECS_IOCTL_APP_SET_AFLAG:
atomic_set(&a_flag, flag);
break;
case ECS_IOCTL_APP_GET_AFLAG:
flag = atomic_read(&a_flag);
break;
case ECS_IOCTL_APP_SET_TFLAG:
atomic_set(&t_flag, flag);
break;
case ECS_IOCTL_APP_GET_TFLAG:
flag = atomic_read(&t_flag);
break;
case ECS_IOCTL_APP_SET_MVFLAG:
atomic_set(&mv_flag, flag);
break;
case ECS_IOCTL_APP_GET_MVFLAG:
flag = atomic_read(&mv_flag);
break;
case ECS_IOCTL_APP_SET_DELAY:
akmd_delay = flag;
break;
case ECS_IOCTL_APP_GET_DELAY:
flag = akmd_delay;
break;
default:
return -ENOTTY;
}
switch (cmd) {
case ECS_IOCTL_APP_GET_MFLAG:
case ECS_IOCTL_APP_GET_AFLAG:
case ECS_IOCTL_APP_GET_TFLAG:
case ECS_IOCTL_APP_GET_MVFLAG:
case ECS_IOCTL_APP_GET_DELAY:
if (copy_to_user(argp, &flag, sizeof(flag)))
return -EFAULT;
break;
default:
break;
}
return 0;
}
static int akmd_open(struct inode *inode, struct file *file)
{
return nonseekable_open(inode, file);
}
static int akmd_release(struct inode *inode, struct file *file)
{
AKECS_CloseDone();
return 0;
}
static int
akmd_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
char msg[RBUFF_SIZE + 1], rwbuf[5];
int ret = -1, status;
short mode, value[12], delay;
char project_name[64];
short layouts[4][3][3];
int i, j, k;
switch (cmd) {
case ECS_IOCTL_WRITE:
case ECS_IOCTL_READ:
if (copy_from_user(&rwbuf, argp, sizeof(rwbuf)))
return -EFAULT;
break;
case ECS_IOCTL_SET_MODE:
if (copy_from_user(&mode, argp, sizeof(mode)))
return -EFAULT;
break;
case ECS_IOCTL_SET_YPR:
if (copy_from_user(&value, argp, sizeof(value)))
return -EFAULT;
break;
default:
break;
}
switch (cmd) {
case ECS_IOCTL_WRITE:
if (rwbuf[0] < 2)
return -EINVAL;
ret = AKI2C_TxData(&rwbuf[1], rwbuf[0]);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_READ:
if (rwbuf[0] < 1)
return -EINVAL;
ret = AKI2C_RxData(&rwbuf[1], rwbuf[0]);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_RESET:
AKECS_Reset();
break;
case ECS_IOCTL_SET_MODE:
ret = AKECS_SetMode((char)mode);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_GETDATA:
ret = AKECS_TransRBuff(msg, RBUFF_SIZE);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_SET_YPR:
AKECS_Report_Value(value);
break;
case ECS_IOCTL_GET_OPEN_STATUS:
status = AKECS_GetOpenStatus();
break;
case ECS_IOCTL_GET_CLOSE_STATUS:
status = AKECS_GetCloseStatus();
break;
case ECS_IOCTL_GET_DELAY:
delay = akmd_delay;
break;
case ECS_IOCTL_GET_PROJECT_NAME:
strncpy(project_name, pdata->project_name, 64);
break;
case ECS_IOCTL_GET_MATRIX:
for (i = 0; i < 4; i++)
for (j = 0; j < 3; j++)
for (k = 0; k < 3; k++)
layouts[i][j][k] =
pdata->layouts[i][j][k];
break;
default:
return -ENOTTY;
}
switch (cmd) {
case ECS_IOCTL_READ:
if (copy_to_user(argp, &rwbuf, sizeof(rwbuf)))
return -EFAULT;
break;
case ECS_IOCTL_GETDATA:
if (copy_to_user(argp, &msg, sizeof(msg)))
return -EFAULT;
break;
case ECS_IOCTL_GET_OPEN_STATUS:
case ECS_IOCTL_GET_CLOSE_STATUS:
if (copy_to_user(argp, &status, sizeof(status)))
return -EFAULT;
break;
case ECS_IOCTL_GET_DELAY:
if (copy_to_user(argp, &delay, sizeof(delay)))
return -EFAULT;
break;
case ECS_IOCTL_GET_PROJECT_NAME:
if (copy_to_user(argp, project_name, sizeof(project_name)))
return -EFAULT;
break;
case ECS_IOCTL_GET_MATRIX:
if (copy_to_user(argp, layouts, sizeof(layouts)))
return -EFAULT;
break;
default:
break;
}
return 0;
}
static void akm_work_func(struct work_struct *work)
{
if (AKECS_GetData() < 0)
printk(KERN_ERR "AKM8973 akm_work_func: Get data failed\n");
enable_irq(this_client->irq);
}
static irqreturn_t akm8973_interrupt(int irq, void *dev_id)
{
struct akm8973_data *data = dev_id;
disable_irq_nosync(this_client->irq);
schedule_work(&data->work);
return IRQ_HANDLED;
}
static void akm8973_early_suspend(struct early_suspend *handler)
{
atomic_set(&suspend_flag, 1);
atomic_set(&reserve_open_flag, atomic_read(&open_flag));
atomic_set(&open_flag, 0);
wake_up(&open_wq);
disable_irq(this_client->irq);
}
static void akm8973_early_resume(struct early_suspend *handler)
{
enable_irq(this_client->irq);
atomic_set(&suspend_flag, 0);
atomic_set(&open_flag, atomic_read(&reserve_open_flag));
wake_up(&open_wq);
}
static struct file_operations akmd_fops = {
.owner = THIS_MODULE,
.open = akmd_open,
.release = akmd_release,
.ioctl = akmd_ioctl,
};
static struct file_operations akm_aot_fops = {
.owner = THIS_MODULE,
.open = akm_aot_open,
.release = akm_aot_release,
.ioctl = akm_aot_ioctl,
};
static struct miscdevice akm_aot_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "akm8973_aot",
.fops = &akm_aot_fops,
};
static struct miscdevice akmd_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "akm8973_daemon",
.fops = &akmd_fops,
};
int akm8973_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct akm8973_data *akm;
int err = 0;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
err = -ENODEV;
goto exit_check_functionality_failed;
}
akm = kzalloc(sizeof(struct akm8973_data), GFP_KERNEL);
if (!akm) {
err = -ENOMEM;
goto exit_alloc_data_failed;
}
INIT_WORK(&akm->work, akm_work_func);
i2c_set_clientdata(client, akm);
pdata = client->dev.platform_data;
if (pdata == NULL) {
printk(KERN_ERR"AKM8973 akm8973_probe: platform data is NULL\n");
goto exit_platform_data_null;
}
this_client = client;
if (pdata && pdata->reset) {
err = gpio_request(pdata->reset, "akm8973");
if (err < 0) {
printk(KERN_ERR "%s: request reset gpio failed\n",
__func__);
goto err_request_reset_gpio;
}
err = gpio_direction_output(pdata->reset, 1);
if (err < 0) {
printk(KERN_ERR
"%s: request reset gpio failed\n", __func__);
goto err_set_reset_gpio;
}
} else {
printk(KERN_ERR "%s: pdata or pdata->reset is NULL\n",
__func__);
goto err_request_reset_gpio;
}
err = AKECS_PowerDown();
if (err < 0) {
printk(KERN_ERR"AKM8973 akm8973_probe: set power down mode error\n");
goto exit_set_mode_failed;
}
err = request_irq(client->irq, akm8973_interrupt, IRQF_TRIGGER_HIGH,
"akm8973", akm);
if (err < 0) {
printk(KERN_ERR"AKM8973 akm8973_probe: request irq failed\n");
goto exit_irq_request_failed;
}
akm->input_dev = input_allocate_device();
if (!akm->input_dev) {
err = -ENOMEM;
printk(KERN_ERR
"AKM8973 akm8973_probe: Failed to allocate input device\n");
goto exit_input_dev_alloc_failed;
}
set_bit(EV_ABS, akm->input_dev->evbit);
/* yaw */
input_set_abs_params(akm->input_dev, ABS_RX, 0, 360, 0, 0);
/* pitch */
input_set_abs_params(akm->input_dev, ABS_RY, -180, 180, 0, 0);
/* roll */
input_set_abs_params(akm->input_dev, ABS_RZ, -90, 90, 0, 0);
/* x-axis acceleration */
input_set_abs_params(akm->input_dev, ABS_X, -1872, 1872, 0, 0);
/* y-axis acceleration */
input_set_abs_params(akm->input_dev, ABS_Y, -1872, 1872, 0, 0);
/* z-axis acceleration */
input_set_abs_params(akm->input_dev, ABS_Z, -1872, 1872, 0, 0);
/* temparature */
input_set_abs_params(akm->input_dev, ABS_THROTTLE, -30, 85, 0, 0);
/* status of magnetic sensor */
input_set_abs_params(akm->input_dev, ABS_RUDDER, -32768, 3, 0, 0);
/* status of acceleration sensor */
input_set_abs_params(akm->input_dev, ABS_WHEEL, -32768, 3, 0, 0);
/* step count */
input_set_abs_params(akm->input_dev, ABS_GAS, 0, 65535, 0, 0);
/* x-axis of raw magnetic vector */
input_set_abs_params(akm->input_dev, ABS_HAT0X, -2048, 2032, 0, 0);
/* y-axis of raw magnetic vector */
input_set_abs_params(akm->input_dev, ABS_HAT0Y, -2048, 2032, 0, 0);
/* z-axis of raw magnetic vector */
input_set_abs_params(akm->input_dev, ABS_BRAKE, -2048, 2032, 0, 0);
akm->input_dev->name = "compass";
err = input_register_device(akm->input_dev);
if (err) {
printk(KERN_ERR
"AKM8973 akm8973_probe: Unable to register input device: %s\n",
akm->input_dev->name);
goto exit_input_register_device_failed;
}
err = misc_register(&akmd_device);
if (err) {
printk(KERN_ERR "AKM8973 akm8973_probe: akmd_device register failed\n");
goto exit_misc_device_register_failed;
}
err = misc_register(&akm_aot_device);
if (err) {
printk(KERN_ERR
"AKM8973 akm8973_probe: akm_aot_device register failed\n");
goto exit_misc_device_register_failed;
}
mutex_init(&sense_data_mutex);
init_waitqueue_head(&data_ready_wq);
init_waitqueue_head(&open_wq);
/* As default, do not report all information */
atomic_set(&m_flag, 0);
atomic_set(&a_flag, 0);
atomic_set(&t_flag, 0);
atomic_set(&mv_flag, 0);
akm->early_suspend_akm.suspend = akm8973_early_suspend;
akm->early_suspend_akm.resume = akm8973_early_resume;
register_early_suspend(&akm->early_suspend_akm);
return 0;
exit_misc_device_register_failed:
exit_input_register_device_failed:
input_free_device(akm->input_dev);
exit_input_dev_alloc_failed:
free_irq(client->irq, akm);
exit_irq_request_failed:
exit_set_mode_failed:
err_set_reset_gpio:
gpio_free(pdata->reset);
err_request_reset_gpio:
exit_platform_data_null:
kfree(akm);
exit_alloc_data_failed:
exit_check_functionality_failed:
return err;
}
static int akm8973_remove(struct i2c_client *client)
{
struct akm8973_data *akm = i2c_get_clientdata(client);
free_irq(client->irq, akm);
input_unregister_device(akm->input_dev);
kfree(akm);
if (pdata && pdata->reset)
gpio_free(pdata->reset);
return 0;
}
static const struct i2c_device_id akm8973_id[] = {
{ AKM8973_I2C_NAME, 0 },
{ }
};
static struct i2c_driver akm8973_driver = {
.probe = akm8973_probe,
.remove = akm8973_remove,
.id_table = akm8973_id,
.driver = {
.name = AKM8973_I2C_NAME,
},
};
static int __init akm8973_init(void)
{
printk(KERN_INFO "AKM8973 compass driver: init\n");
return i2c_add_driver(&akm8973_driver);
}
static void __exit akm8973_exit(void)
{
i2c_del_driver(&akm8973_driver);
}
module_init(akm8973_init);
module_exit(akm8973_exit);
MODULE_AUTHOR("viral wang <viral_wang@htc.com>");
MODULE_DESCRIPTION("AKM8973 compass driver");
MODULE_LICENSE("GPL");