mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-11-01 02:21:16 +00:00
fc9499e55a
* Package version: T713XXU2BQCO Change-Id: I293d9e7f2df458c512d59b7a06f8ca6add610c99
1127 lines
28 KiB
C
1127 lines
28 KiB
C
/*
|
|
* Copyright (C) 2013 Samsung Electronics. 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
|
|
*/
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/module.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/input.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <linux/sensor/sensors_core.h>
|
|
#include "ak09916c_reg.h"
|
|
#include <linux/pm_qos.h>
|
|
|
|
/* Rx buffer size. i.e ST,TMPS,H1X,H1Y,H1Z*/
|
|
#define SENSOR_DATA_SIZE 9
|
|
#define AK09916C_DEFAULT_DELAY 200000000LL
|
|
#define AK09916C_DRDY_TIMEOUT_MS 100
|
|
#define AK09916C_WIA1_VALUE 0x48
|
|
#define AK09916C_WIA2_VALUE 0x09
|
|
|
|
#define I2C_M_WR 0 /* for i2c Write */
|
|
#define I2c_M_RD 1 /* for i2c Read */
|
|
|
|
#define VENDOR_NAME "AKM"
|
|
#define MODEL_NAME "AK09916C"
|
|
#define MODULE_NAME "magnetic_sensor"
|
|
|
|
#define AK09916C_TOP_LOWER_RIGHT 0
|
|
#define AK09916C_TOP_LOWER_LEFT 1
|
|
#define AK09916C_TOP_UPPER_LEFT 2
|
|
#define AK09916C_TOP_UPPER_RIGHT 3
|
|
#define AK09916C_BOTTOM_LOWER_RIGHT 4
|
|
#define AK09916C_BOTTOM_LOWER_LEFT 5
|
|
#define AK09916C_BOTTOM_UPPER_LEFT 6
|
|
#define AK09916C_BOTTOM_UPPER_RIGHT 7
|
|
|
|
#define AK09916C_MAX_DELAY 200000000LL
|
|
#define AK09916C_MIN_DELAY 10000000LL
|
|
|
|
#define PC_ON 1
|
|
#define PC_OFF 0
|
|
|
|
struct ak09916c_v {
|
|
union {
|
|
s16 v[3];
|
|
struct {
|
|
s16 x;
|
|
s16 y;
|
|
s16 z;
|
|
};
|
|
};
|
|
};
|
|
|
|
struct ak09916c_p {
|
|
struct i2c_client *client;
|
|
struct input_dev *input;
|
|
struct device *factory_device;
|
|
struct ak09916c_v magdata;
|
|
struct mutex lock;
|
|
struct mutex enable_lock;
|
|
struct delayed_work work;
|
|
struct pm_qos_request pm_qos_req;
|
|
|
|
atomic_t delay;
|
|
atomic_t enable;
|
|
atomic_t i2c_cancel;
|
|
#if defined(CONFIG_SENSORS_SW_RESET)
|
|
int reset_state;
|
|
#endif
|
|
|
|
u8 asa[3];
|
|
u32 chip_pos;
|
|
u64 timestamp;
|
|
u64 old_timestamp;
|
|
};
|
|
|
|
static int ak09916c_i2c_read(struct i2c_client *client,
|
|
unsigned char reg_addr, unsigned char *buf)
|
|
{
|
|
int ret;
|
|
struct i2c_msg msg[2];
|
|
#if defined(CONFIG_SENSORS_SW_RESET)
|
|
struct ak09916c_p *data =
|
|
(struct ak09916c_p *)i2c_get_clientdata(client);
|
|
|
|
if (data->reset_state)
|
|
return -1;
|
|
#endif
|
|
|
|
msg[0].addr = client->addr;
|
|
msg[0].flags = I2C_M_WR;
|
|
msg[0].len = 1;
|
|
msg[0].buf = ®_addr;
|
|
|
|
msg[1].addr = client->addr;
|
|
msg[1].flags = I2C_M_RD;
|
|
msg[1].len = 1;
|
|
msg[1].buf = buf;
|
|
|
|
ret = i2c_transfer(client->adapter, msg, 2);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - i2c bus read error %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ak09916c_i2c_write(struct i2c_client *client,
|
|
unsigned char reg_addr, unsigned char buf)
|
|
{
|
|
int ret;
|
|
struct i2c_msg msg;
|
|
unsigned char w_buf[2];
|
|
|
|
#if defined(CONFIG_SENSORS_SW_RESET)
|
|
struct ak09916c_p *data =
|
|
(struct ak09916c_p *)i2c_get_clientdata(client);
|
|
|
|
if (data->reset_state) {
|
|
pr_info("[SENSOR]: %s - reset_state %d start!!!\n", __func__,data->reset_state);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
w_buf[0] = reg_addr;
|
|
w_buf[1] = buf;
|
|
|
|
msg.addr = client->addr;
|
|
msg.flags = I2C_M_WR;
|
|
msg.len = 2;
|
|
msg.buf = (char *)w_buf;
|
|
|
|
ret = i2c_transfer(client->adapter, &msg, 1);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - i2c bus write error %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ak09916c_i2c_read_block(struct i2c_client *client,
|
|
unsigned char reg_addr, unsigned char *buf, unsigned char len)
|
|
{
|
|
#if 1
|
|
int ret;
|
|
struct i2c_msg msg[2];
|
|
|
|
#if defined(CONFIG_SENSORS_SW_RESET)
|
|
struct ak09916c_p *data =
|
|
(struct ak09916c_p *)i2c_get_clientdata(client);
|
|
|
|
if (data->reset_state)
|
|
return -1;
|
|
#endif
|
|
|
|
|
|
msg[0].addr = client->addr;
|
|
msg[0].flags = I2C_M_WR;
|
|
msg[0].len = 1;
|
|
msg[0].buf = ®_addr;
|
|
|
|
msg[1].addr = client->addr;
|
|
msg[1].flags = I2C_M_RD;
|
|
msg[1].len = len;
|
|
msg[1].buf = buf;
|
|
|
|
ret = i2c_transfer(client->adapter, msg, 2);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - i2c bus read error %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
#else
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < len; i++)
|
|
ret += ak09916c_i2c_read(client, reg_addr + i, &buf[i]);
|
|
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
static int ak09916c_ecs_set_mode_power_down(struct ak09916c_p *data)
|
|
{
|
|
unsigned char reg;
|
|
int ret;
|
|
|
|
data->old_timestamp = 0LL;
|
|
reg = AK09916C_MODE_POWERDOWN;
|
|
ret = ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ak09916c_ecs_set_mode(struct ak09916c_p *data, char mode)
|
|
{
|
|
u8 reg;
|
|
int ret;
|
|
|
|
switch (mode & 0x1F) {
|
|
case AK09916C_MODE_SNG_MEASURE:
|
|
reg = AK09916C_MODE_SNG_MEASURE;
|
|
ret = ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
break;
|
|
case AK09916C_MODE_POWERDOWN:
|
|
reg = AK09916C_MODE_SNG_MEASURE;
|
|
ret = ak09916c_ecs_set_mode_power_down(data);
|
|
break;
|
|
case AK09916C_MODE_SELF_TEST:
|
|
reg = AK09916C_MODE_SELF_TEST;
|
|
ret = ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Wait at least 300us after changing mode. */
|
|
udelay(100);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ak09916c_read_mag_xyz(struct ak09916c_p *data,
|
|
struct ak09916c_v *mag)
|
|
{
|
|
u8 temp[SENSOR_DATA_SIZE] = {0, };
|
|
int ret = 0, retries = 0;
|
|
|
|
mutex_lock(&data->lock);
|
|
if (atomic_read(&data->i2c_cancel) == 1)
|
|
goto exit;
|
|
|
|
ret = ak09916c_ecs_set_mode(data, AK09916C_MODE_SNG_MEASURE);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - ak09916c_ecs_set_mode error\n",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
|
|
again:
|
|
ret = ak09916c_i2c_read(data->client, AK09916C_REG_ST1, &temp[0]);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - ak09916c_i2c_read error\n",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
|
|
/* Check ST bit */
|
|
if (!(temp[0] & 0x01)) {
|
|
if ((retries++ < 5) && (temp[0] == 0)) {
|
|
usleep_range(2000, 2100);
|
|
goto again;
|
|
} else {
|
|
ret = -1;
|
|
pr_err("[SENSOR]: %s - ST1 = %u, ST2 = %u\n",
|
|
__func__, temp[0], temp[8]);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
ret = ak09916c_i2c_read_block(data->client, AK09916C_REG_ST1 + 1,
|
|
&temp[1], SENSOR_DATA_SIZE - 1);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - ak09916c_i2c_read_block error\n",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
mag->x = temp[1] | (temp[2] << 8);
|
|
mag->y = temp[3] | (temp[4] << 8);
|
|
mag->z = temp[5] | (temp[6] << 8);
|
|
|
|
remap_sensor_data(mag->v, data->chip_pos);
|
|
|
|
exit:
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void ak09916c_work_func(struct work_struct *work)
|
|
{
|
|
int ret;
|
|
struct ak09916c_v mag;
|
|
struct timespec ts;
|
|
int time_hi, time_lo;
|
|
|
|
struct ak09916c_p *data = container_of((struct delayed_work *)work,
|
|
struct ak09916c_p, work);
|
|
unsigned long delay = nsecs_to_jiffies(atomic_read(&data->delay));
|
|
unsigned long pdelay = atomic_read(&data->delay);
|
|
|
|
#ifdef CONFIG_SENSORS_SW_RESET
|
|
if(data->reset_state)
|
|
{
|
|
pr_err("[SENSOR]:ak09916c_work_func: reset state\n");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
ts = ktime_to_timespec(ktime_get_boottime());
|
|
data->timestamp = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
|
|
|
|
ret = ak09916c_read_mag_xyz(data, &mag);
|
|
|
|
if (ret >= 0) {
|
|
if (data->old_timestamp != 0 &&
|
|
((data->timestamp - data->old_timestamp) * 10
|
|
> (pdelay) * 18)) {
|
|
u64 shift_timestamp = pdelay >> 1;
|
|
u64 timestamp = 0ULL;
|
|
|
|
for (timestamp = data->old_timestamp + pdelay; timestamp
|
|
< data->timestamp - shift_timestamp; timestamp += pdelay) {
|
|
time_hi = (int)((timestamp & TIME_HI_MASK) >> TIME_HI_SHIFT);
|
|
time_lo = (int)(timestamp & TIME_LO_MASK);
|
|
|
|
input_report_rel(data->input, REL_X, mag.x);
|
|
input_report_rel(data->input, REL_Y, mag.y);
|
|
input_report_rel(data->input, REL_Z, mag.z);
|
|
input_report_rel(data->input, REL_RX, time_hi);
|
|
input_report_rel(data->input, REL_RY, time_lo);
|
|
input_sync(data->input);
|
|
data->magdata = mag;
|
|
}
|
|
}
|
|
time_hi = (int)((data->timestamp & TIME_HI_MASK)
|
|
>> TIME_HI_SHIFT);
|
|
time_lo = (int)(data->timestamp & TIME_LO_MASK);
|
|
|
|
input_report_rel(data->input, REL_X, mag.x);
|
|
input_report_rel(data->input, REL_Y, mag.y);
|
|
input_report_rel(data->input, REL_Z, mag.z);
|
|
input_report_rel(data->input, REL_RX, time_hi);
|
|
input_report_rel(data->input, REL_RY, time_lo);
|
|
input_sync(data->input);
|
|
data->magdata = mag;
|
|
data->old_timestamp = data->timestamp;
|
|
}
|
|
|
|
schedule_delayed_work(&data->work, delay);
|
|
}
|
|
|
|
static void ak09916c_power_collapse(struct ak09916c_p *data, int enable)
|
|
{
|
|
if (enable) {
|
|
pr_info("%s - enable\n", __func__);
|
|
if (atomic_read(&data->delay) == 66666000 || atomic_read(&data->delay) == 20000000){
|
|
pr_info("[SENSOR]: %s - delay :%d\n", __func__, atomic_read(&data->delay));
|
|
pm_qos_update_request(&data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
|
|
}
|
|
else
|
|
pm_qos_update_request(&data->pm_qos_req, 1);
|
|
} else {
|
|
pr_info("%s - disable\n", __func__);
|
|
pm_qos_update_request(&data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
|
|
}
|
|
}
|
|
|
|
|
|
static void ak09916c_set_enable(struct ak09916c_p *data, int enable)
|
|
{
|
|
int pre_enable = atomic_read(&data->enable);
|
|
|
|
if (enable) {
|
|
ak09916c_power_collapse(data, PC_ON);
|
|
if (pre_enable == 0) {
|
|
data->old_timestamp = 0LL;
|
|
atomic_set(&data->i2c_cancel, 0);
|
|
pr_info("[SENSOR]: %s - i2c_cancel: %d\n", __func__,atomic_read(&data->i2c_cancel));
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_SNG_MEASURE);
|
|
schedule_delayed_work(&data->work,
|
|
nsecs_to_jiffies(atomic_read(&data->delay)));
|
|
atomic_set(&data->enable, 1);
|
|
}
|
|
} else {
|
|
if (pre_enable == 1) {
|
|
atomic_set(&data->i2c_cancel, 1);
|
|
pr_info("[SENSOR]: %s - i2c_cancel: %d\n", __func__,atomic_read(&data->i2c_cancel));
|
|
cancel_delayed_work_sync(&data->work);
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_POWERDOWN);
|
|
atomic_set(&data->enable, 0);
|
|
}
|
|
ak09916c_power_collapse(data, PC_OFF);
|
|
}
|
|
}
|
|
|
|
static ssize_t ak09916c_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&data->enable));
|
|
}
|
|
|
|
static ssize_t ak09916c_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
u8 enable;
|
|
int ret;
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
ret = kstrtou8(buf, 2, &enable);
|
|
if (ret) {
|
|
pr_err("[SENSOR]: %s - Invalid Argument\n", __func__);
|
|
return ret;
|
|
}
|
|
pr_info("[SENSOR]: %s - new_value = %u\n", __func__, enable);
|
|
#if defined(CONFIG_SENSORS_SW_RESET)
|
|
if (data->reset_state) {
|
|
pr_info("[SENSOR]: %s - sw reset come enable is = %u\n", __func__, enable);
|
|
atomic_set(&data->enable, enable);
|
|
return size;
|
|
}
|
|
#endif
|
|
mutex_lock(&data->enable_lock);
|
|
if ((enable == 0) || (enable == 1))
|
|
ak09916c_set_enable(data, (int)enable);
|
|
|
|
mutex_unlock(&data->enable_lock);
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t ak09916c_delay_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&data->delay));
|
|
}
|
|
|
|
static ssize_t ak09916c_delay_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int ret;
|
|
int64_t delay;
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
ret = kstrtoll(buf, 10, &delay);
|
|
if (ret) {
|
|
pr_err("[SENSOR]: %s - Invalid Argument\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
if (delay > AK09916C_MAX_DELAY) {
|
|
pr_info("[SENSOR]: %s - %lld > AK09916C_MAX_DELAY\n", __func__, delay);
|
|
delay = AK09916C_MAX_DELAY;
|
|
} else if (delay < AK09916C_MIN_DELAY) {
|
|
pr_info("[SENSOR]: %s - %lld < AK09916C_MAX_DELAY\n", __func__, delay);
|
|
delay = AK09916C_MIN_DELAY;
|
|
}
|
|
|
|
mutex_lock(&data->enable_lock);
|
|
atomic_set(&data->delay, (int64_t)delay);
|
|
pr_info("[SENSOR]: %s - poll_delay = %lld\n", __func__, delay);
|
|
mutex_unlock(&data->enable_lock);
|
|
|
|
return size;
|
|
}
|
|
|
|
static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
ak09916c_delay_show, ak09916c_delay_store);
|
|
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
ak09916c_enable_show, ak09916c_enable_store);
|
|
|
|
static struct attribute *ak09916c_attributes[] = {
|
|
&dev_attr_poll_delay.attr,
|
|
&dev_attr_enable.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group ak09916c_attribute_group = {
|
|
.attrs = ak09916c_attributes
|
|
};
|
|
|
|
static int ak09916c_selftest(struct ak09916c_p *data, int *dac_ret, int *sf)
|
|
{
|
|
u8 temp[6], reg;
|
|
s16 x, y, z;
|
|
int retry_count = 0;
|
|
int ready_count = 0;
|
|
int ret = -1;
|
|
retry:
|
|
mutex_lock(&data->lock);
|
|
/* power down */
|
|
reg = AK09916C_MODE_POWERDOWN;
|
|
*dac_ret = ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
udelay(100);
|
|
*dac_ret += ak09916c_i2c_read(data->client, AK09916C_REG_CNTL2, ®);
|
|
|
|
/* read device info */
|
|
ak09916c_i2c_read_block(data->client, AK09916C_REG_WIA1, temp, 2);
|
|
pr_info("[SENSOR]: %s - device id = 0x%x, info = 0x%x\n",
|
|
__func__, temp[0], temp[1]);
|
|
|
|
/* start self test */
|
|
reg = AK09916C_MODE_SELF_TEST;
|
|
ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
|
|
/* wait for data ready */
|
|
while (ready_count < 10) {
|
|
usleep_range(20000, 21000);
|
|
ret = ak09916c_i2c_read(data->client, AK09916C_REG_ST1, ®);
|
|
if ((reg == 1) && (ret == 0))
|
|
break;
|
|
ready_count++;
|
|
}
|
|
|
|
ak09916c_i2c_read_block(data->client, AK09916C_REG_HXL,
|
|
temp, sizeof(temp));
|
|
mutex_unlock(&data->lock);
|
|
|
|
x = temp[0] | (temp[1] << 8);
|
|
y = temp[2] | (temp[3] << 8);
|
|
z = temp[4] | (temp[5] << 8);
|
|
|
|
pr_info("[SENSOR]: %s - self test x = %d, y = %d, z = %d\n",
|
|
__func__, x, y, z);
|
|
if ((x >= -200) && (x <= 200))
|
|
pr_info("[SENSOR]: %s - x passed self test, -200<=x<=200\n",
|
|
__func__);
|
|
else
|
|
pr_info("[SENSOR]: %s - x failed self test, -200<=x<=200\n",
|
|
__func__);
|
|
if ((y >= -200) && (y <= 200))
|
|
pr_info("[SENSOR]: %s - y passed self test, -200<=y<=200\n",
|
|
__func__);
|
|
else
|
|
pr_info("[SENSOR]: %s - y failed self test, -200<=y<=200\n",
|
|
__func__);
|
|
if ((z >= -1000) && (z <= -200))
|
|
pr_info("[SENSOR]: %s - z passed self test, -1000<=z<=-200\n",
|
|
__func__);
|
|
else
|
|
pr_info("[SENSOR]: %s - z failed self test, -1000<=z<=-200\n",
|
|
__func__);
|
|
|
|
sf[0] = x;
|
|
sf[1] = y;
|
|
sf[2] = z;
|
|
|
|
if (((x >= -200) && (x <= 200)) &&
|
|
((y >= -200) && (y <= 200)) &&
|
|
((z >= -1000) && (z <= -200))) {
|
|
pr_info("%s, Selftest is successful.\n", __func__);
|
|
return 0;
|
|
} else {
|
|
if (retry_count < 5) {
|
|
retry_count++;
|
|
pr_warn("############################################");
|
|
pr_warn("%s, retry_count=%d\n", __func__, retry_count);
|
|
pr_warn("############################################");
|
|
goto retry;
|
|
} else {
|
|
pr_err("[SENSOR]: %s - Selftest is failed.\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
static ssize_t ak09916c_vendor_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR_NAME);
|
|
}
|
|
|
|
static ssize_t ak09916c_name_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", MODEL_NAME);
|
|
}
|
|
|
|
static ssize_t ak09916c_get_asa(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u,%u,%u\n",
|
|
data->asa[0], data->asa[1], data->asa[2]);
|
|
}
|
|
|
|
static ssize_t ak09916c_get_selftest(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int status, dac_ret = -1, adc_ret = -1;
|
|
int sf_ret, sf[3] = {0,}, retries;
|
|
struct ak09916c_v mag;
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
/* STATUS */
|
|
if ((data->asa[0] == 0) | (data->asa[0] == 0xff)
|
|
| (data->asa[1] == 0) | (data->asa[1] == 0xff)
|
|
| (data->asa[2] == 0) | (data->asa[2] == 0xff))
|
|
status = -1;
|
|
else
|
|
status = 0;
|
|
|
|
if (atomic_read(&data->enable) == 1) {
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_POWERDOWN);
|
|
cancel_delayed_work_sync(&data->work);
|
|
}
|
|
|
|
sf_ret = ak09916c_selftest(data, &dac_ret, sf);
|
|
|
|
for (retries = 0; retries < 5; retries++) {
|
|
if (ak09916c_read_mag_xyz(data, &mag) == 0) {
|
|
if ((mag.x < 6500) && (mag.x > -6500)
|
|
&& (mag.y < 6500) && (mag.y > -6500)
|
|
&& (mag.z < 6500) && (mag.z > -6500))
|
|
adc_ret = 0;
|
|
else
|
|
pr_err("[SENSOR]: %s adc specout %d, %d, %d\n",
|
|
__func__, mag.x, mag.y, mag.z);
|
|
break;
|
|
}
|
|
|
|
usleep_range(20000, 21000);
|
|
pr_err("[SENSOR]: %s - adc retries %d", __func__, retries);
|
|
}
|
|
|
|
if (atomic_read(&data->enable) == 1) {
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_SNG_MEASURE);
|
|
schedule_delayed_work(&data->work,
|
|
nsecs_to_jiffies(atomic_read(&data->delay)));
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
|
|
status, sf_ret, sf[0], sf[1], sf[2], dac_ret,
|
|
adc_ret, mag.x, mag.y, mag.z);
|
|
}
|
|
|
|
static ssize_t ak09916c_check_registers(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 temp[13], reg;
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
mutex_lock(&data->lock);
|
|
/* power down */
|
|
reg = AK09916C_MODE_POWERDOWN;
|
|
ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
/* get the value */
|
|
ak09916c_i2c_read_block(data->client, AK09916C_REG_WIA1, temp, 13);
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
|
|
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5],
|
|
temp[6], temp[7], temp[8], temp[9], temp[10], temp[11],
|
|
temp[12]);
|
|
}
|
|
|
|
static ssize_t ak09916c_check_cntl(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 reg;
|
|
int ret = 0;
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
mutex_lock(&data->lock);
|
|
/* power down */
|
|
reg = AK09916C_MODE_POWERDOWN;
|
|
|
|
ret = ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
udelay(100);
|
|
ret += ak09916c_i2c_read(data->client, AK09916C_REG_CNTL2, ®);
|
|
mutex_unlock(&data->lock);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
|
(((reg == AK09916C_MODE_POWERDOWN) &&
|
|
(ret == 0)) ? "OK" : "NG"));
|
|
}
|
|
|
|
static ssize_t ak09916c_get_status(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
bool success;
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
if ((data->asa[0] == 0) | (data->asa[0] == 0xff)
|
|
| (data->asa[1] == 0) | (data->asa[1] == 0xff)
|
|
| (data->asa[2] == 0) | (data->asa[2] == 0xff))
|
|
success = false;
|
|
else
|
|
success = true;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", (success ? "OK" : "NG"));
|
|
}
|
|
|
|
static ssize_t ak09916c_adc(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
bool success = false;
|
|
int ret;
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
struct ak09916c_v mag = data->magdata;
|
|
|
|
if (atomic_read(&data->enable) == 1) {
|
|
success = true;
|
|
usleep_range(20000, 21000);
|
|
goto exit;
|
|
}
|
|
|
|
ret = ak09916c_read_mag_xyz(data, &mag);
|
|
if (ret < 0)
|
|
success = false;
|
|
else
|
|
success = true;
|
|
|
|
data->magdata = mag;
|
|
|
|
exit:
|
|
return snprintf(buf, PAGE_SIZE, "%s,%d,%d,%d\n",
|
|
(success ? "OK" : "NG"), mag.x, mag.y, mag.z);
|
|
}
|
|
|
|
static ssize_t ak09916c_raw_data_read(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
struct ak09916c_v mag = data->magdata;
|
|
|
|
if (atomic_read(&data->enable) == 1) {
|
|
usleep_range(20000, 21000);
|
|
goto exit;
|
|
}
|
|
|
|
ak09916c_read_mag_xyz(data, &mag);
|
|
data->magdata = mag;
|
|
|
|
exit:
|
|
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", mag.x, mag.y, mag.z);
|
|
}
|
|
|
|
static int ak09916c_check_device(struct ak09916c_p *data)
|
|
{
|
|
unsigned char reg, buf[2];
|
|
int ret;
|
|
|
|
ret = ak09916c_i2c_read_block(data->client, AK09916C_REG_WIA1, buf, 2);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - unable to read AK09916C_REG_WIA1\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
reg = AK09916C_MODE_POWERDOWN;
|
|
ret = ak09916c_i2c_write(data->client, AK09916C_REG_CNTL2, reg);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - Error in setting power down mode\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
if ((buf[0] != AK09916C_WIA1_VALUE)
|
|
|| (buf[1] != AK09916C_WIA2_VALUE)) {
|
|
pr_err("[SENSOR]: %s - The device is not AKM Compass. %u, %u",
|
|
__func__, buf[0], buf[1]);
|
|
return -ENXIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_SENSORS_SW_RESET
|
|
static ssize_t ak09916c_power_reset_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
int enabled = atomic_read(&data->enable);
|
|
|
|
pr_info("[SENSOR]: %s - magnetic power reset start!!!\n", __func__);
|
|
|
|
data->reset_state = 1;
|
|
mutex_lock(&data->enable_lock);
|
|
|
|
if (enabled) {
|
|
cancel_delayed_work_sync(&data->work);
|
|
pr_info("[SENSOR]: %s - cancel_delayed_work_sync done!!!\n", __func__);
|
|
}
|
|
|
|
mutex_unlock(&data->enable_lock);
|
|
|
|
pr_info("[SENSOR]: %s - magnetic power reset end!!!\n", __func__);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", enabled);
|
|
}
|
|
|
|
|
|
static ssize_t ak09916c_sw_reset_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
int enabled = atomic_read(&data->enable);
|
|
|
|
pr_info("[SENSOR]: %s - magnetic sw reset start!!!\n", __func__);
|
|
|
|
mutex_lock(&data->enable_lock);
|
|
data->reset_state = 0;
|
|
data->old_timestamp = 0;
|
|
if (enabled == 1) {
|
|
pr_info("[SENSOR]: %s - magnetic was enabled so make enable!!!\n", __func__);
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_SNG_MEASURE);
|
|
schedule_delayed_work(&data->work,
|
|
nsecs_to_jiffies(atomic_read(&data->delay)));
|
|
}
|
|
else {
|
|
pr_info("[SENSOR]: %s - magnetic was disabled so make disable!!!\n", __func__);
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_POWERDOWN);
|
|
}
|
|
|
|
mutex_unlock(&data->enable_lock);
|
|
|
|
pr_info("[SENSOR]: %s - magnetic sw reset end!!!\n", __func__);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", enabled);
|
|
}
|
|
#endif
|
|
|
|
static DEVICE_ATTR(name, S_IRUGO, ak09916c_name_show, NULL);
|
|
static DEVICE_ATTR(vendor, S_IRUGO, ak09916c_vendor_show, NULL);
|
|
static DEVICE_ATTR(raw_data, S_IRUGO, ak09916c_raw_data_read, NULL);
|
|
static DEVICE_ATTR(adc, S_IRUGO, ak09916c_adc, NULL);
|
|
static DEVICE_ATTR(dac, S_IRUGO, ak09916c_check_cntl, NULL);
|
|
static DEVICE_ATTR(chk_registers, S_IRUGO, ak09916c_check_registers, NULL);
|
|
static DEVICE_ATTR(selftest, S_IRUGO, ak09916c_get_selftest, NULL);
|
|
static DEVICE_ATTR(asa, S_IRUGO, ak09916c_get_asa, NULL);
|
|
static DEVICE_ATTR(status, S_IRUGO, ak09916c_get_status, NULL);
|
|
#ifdef CONFIG_SENSORS_SW_RESET
|
|
static DEVICE_ATTR(power_reset, S_IRUSR | S_IRGRP, ak09916c_power_reset_show, NULL);
|
|
static DEVICE_ATTR(sw_reset, S_IRUSR | S_IRGRP, ak09916c_sw_reset_show, NULL);
|
|
#endif
|
|
|
|
static struct device_attribute *sensor_attrs[] = {
|
|
&dev_attr_name,
|
|
&dev_attr_vendor,
|
|
&dev_attr_raw_data,
|
|
&dev_attr_adc,
|
|
&dev_attr_dac,
|
|
&dev_attr_chk_registers,
|
|
&dev_attr_selftest,
|
|
&dev_attr_asa,
|
|
&dev_attr_status,
|
|
#ifdef CONFIG_SENSORS_SW_RESET
|
|
&dev_attr_power_reset,
|
|
&dev_attr_sw_reset,
|
|
#endif
|
|
NULL,
|
|
};
|
|
|
|
static int ak09916c_input_init(struct ak09916c_p *data)
|
|
{
|
|
int ret = 0;
|
|
struct input_dev *dev;
|
|
|
|
dev = input_allocate_device();
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
dev->name = MODULE_NAME;
|
|
dev->id.bustype = BUS_I2C;
|
|
|
|
input_set_capability(dev, EV_REL, REL_X);
|
|
input_set_capability(dev, EV_REL, REL_Y);
|
|
input_set_capability(dev, EV_REL, REL_Z);
|
|
input_set_capability(dev, EV_REL, REL_RX); /* time_hi */
|
|
input_set_capability(dev, EV_REL, REL_RY); /* time_lo */
|
|
input_set_drvdata(dev, data);
|
|
|
|
ret = input_register_device(dev);
|
|
if (ret < 0) {
|
|
input_free_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
ret = sensors_create_symlink(&dev->dev.kobj, dev->name);
|
|
if (ret < 0) {
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
/* sysfs node creation */
|
|
ret = sysfs_create_group(&dev->dev.kobj, &ak09916c_attribute_group);
|
|
if (ret < 0) {
|
|
sensors_remove_symlink(&data->input->dev.kobj,
|
|
data->input->name);
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
data->input = dev;
|
|
return 0;
|
|
}
|
|
|
|
static int ak09916c_parse_dt(struct ak09916c_p *data, struct device *dev)
|
|
{
|
|
struct device_node *dNode = dev->of_node;
|
|
if (dNode == NULL)
|
|
return -ENODEV;
|
|
if (of_property_read_u32(dNode,
|
|
"ak09916c-i2c,chip_pos", &data->chip_pos) < 0)
|
|
data->chip_pos = AK09916C_TOP_LOWER_RIGHT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ak09916c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int ret = -ENODEV;
|
|
struct ak09916c_p *data = NULL;
|
|
|
|
pr_info("[SENSOR]: %s - Probe Start!\n", __func__);
|
|
|
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
|
pr_err("[SENSOR]: %s - i2c_check_functionality error\n",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
|
|
data = kzalloc(sizeof(struct ak09916c_p), GFP_KERNEL);
|
|
if (data == NULL) {
|
|
pr_err("[SENSOR]: %s - kzalloc error\n", __func__);
|
|
ret = -ENOMEM;
|
|
goto exit_kzalloc;
|
|
}
|
|
|
|
ret = ak09916c_parse_dt(data, &client->dev);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR]: %s - of_node error\n", __func__);
|
|
ret = -ENODEV;
|
|
goto exit_of_node;
|
|
}
|
|
|
|
i2c_set_clientdata(client, data);
|
|
data->client = client;
|
|
|
|
ret = ak09916c_check_device(data);
|
|
if (ret < 0) {
|
|
pr_err("[SENSOR] %s - check_device fail (err=%d)\n",
|
|
__func__, ret);
|
|
goto exit_set_mode_check_device;
|
|
}
|
|
|
|
pm_qos_add_request(&data->pm_qos_req,
|
|
PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE);
|
|
|
|
/* input device init */
|
|
ret = ak09916c_input_init(data);
|
|
if (ret < 0)
|
|
goto exit_input_init;
|
|
|
|
sensors_register(data->factory_device, data, sensor_attrs,
|
|
MODULE_NAME);
|
|
|
|
/* workqueue init */
|
|
INIT_DELAYED_WORK(&data->work, ak09916c_work_func);
|
|
mutex_init(&data->lock);
|
|
#ifdef CONFIG_SENSORS_SW_RESET
|
|
data->reset_state = 0;
|
|
#endif
|
|
mutex_init(&data->enable_lock);
|
|
|
|
atomic_set(&data->delay, AK09916C_DEFAULT_DELAY);
|
|
atomic_set(&data->enable, 0);
|
|
atomic_set(&data->i2c_cancel, 0);
|
|
|
|
data->asa[0] = 128;
|
|
data->asa[1] = 128;
|
|
data->asa[2] = 128;
|
|
|
|
pr_info("[SENSOR]: %s - Probe done!(chip pos : %d)\n",
|
|
__func__, data->chip_pos);
|
|
|
|
return 0;
|
|
|
|
exit_input_init:
|
|
pm_qos_remove_request(&data->pm_qos_req);
|
|
exit_set_mode_check_device:
|
|
exit_of_node:
|
|
kfree(data);
|
|
exit_kzalloc:
|
|
exit:
|
|
pr_err("[SENSOR]: %s - Probe fail!\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static void ak09916c_shutdown(struct i2c_client *client)
|
|
{
|
|
struct ak09916c_p *data =
|
|
(struct ak09916c_p *)i2c_get_clientdata(client);
|
|
|
|
pr_info("[SENSOR]: %s\n", __func__);
|
|
if (atomic_read(&data->enable) == 1) {
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_POWERDOWN);
|
|
cancel_delayed_work_sync(&data->work);
|
|
}
|
|
}
|
|
|
|
static int ak09916c_remove(struct i2c_client *client)
|
|
{
|
|
struct ak09916c_p *data =
|
|
(struct ak09916c_p *)i2c_get_clientdata(client);
|
|
|
|
if (atomic_read(&data->enable) == 1)
|
|
ak09916c_set_enable(data, 0);
|
|
mutex_destroy(&data->lock);
|
|
mutex_destroy(&data->enable_lock);
|
|
|
|
sensors_unregister(data->factory_device, sensor_attrs);
|
|
sensors_remove_symlink(&data->input->dev.kobj, data->input->name);
|
|
pm_qos_remove_request(&data->pm_qos_req);
|
|
|
|
sysfs_remove_group(&data->input->dev.kobj, &ak09916c_attribute_group);
|
|
input_unregister_device(data->input);
|
|
kfree(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ak09916c_suspend(struct device *dev)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
if (atomic_read(&data->enable) == 1) {
|
|
pr_info("[SENSOR]: %s - i2c_cancel: %d\n", __func__,atomic_read(&data->i2c_cancel));
|
|
atomic_set(&data->i2c_cancel, 1);
|
|
cancel_delayed_work(&data->work);
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_POWERDOWN);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ak09916c_resume(struct device *dev)
|
|
{
|
|
struct ak09916c_p *data = dev_get_drvdata(dev);
|
|
|
|
if (atomic_read(&data->enable) == 1) {
|
|
atomic_set(&data->i2c_cancel, 0);
|
|
pr_info("[SENSOR]: %s - i2c_cancel: %d\n", __func__,atomic_read(&data->i2c_cancel));
|
|
ak09916c_ecs_set_mode(data, AK09916C_MODE_SNG_MEASURE);
|
|
schedule_delayed_work(&data->work,
|
|
nsecs_to_jiffies(atomic_read(&data->delay)));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct of_device_id ak09916c_match_table[] = {
|
|
{ .compatible = "ak09916c-i2c",},
|
|
{},
|
|
};
|
|
|
|
static const struct i2c_device_id ak09916c_id[] = {
|
|
{ "ak09916c_match_table", 0 },
|
|
{ }
|
|
};
|
|
|
|
static const struct dev_pm_ops ak09916c_pm_ops = {
|
|
.suspend = ak09916c_suspend,
|
|
.resume = ak09916c_resume
|
|
};
|
|
|
|
static struct i2c_driver ak09916c_driver = {
|
|
.driver = {
|
|
.name = MODEL_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = ak09916c_match_table,
|
|
.pm = &ak09916c_pm_ops
|
|
},
|
|
.probe = ak09916c_probe,
|
|
.shutdown = ak09916c_shutdown,
|
|
.remove = ak09916c_remove,
|
|
.id_table = ak09916c_id,
|
|
};
|
|
|
|
static int __init ak09916c_init(void)
|
|
{
|
|
return i2c_add_driver(&ak09916c_driver);
|
|
}
|
|
|
|
static void __exit ak09916c_exit(void)
|
|
{
|
|
i2c_del_driver(&ak09916c_driver);
|
|
}
|
|
|
|
module_init(ak09916c_init);
|
|
module_exit(ak09916c_exit);
|
|
|
|
MODULE_DESCRIPTION("AK09916C compass driver");
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_LICENSE("GPL");
|