blob: 271d0dce8c1db62c64aac0beba892f5e6493f09c [file] [log] [blame]
/* drivers/input/misc/akm8963.c - akm8963 compass driver
*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* Copyright (C) 2007-2008 HTC Corporation.
* Author: Hou-Kun Chen <houkun.chen@gmail.com>
*
* 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.
*
*/
/*#define DEBUG*/
/*#define VERBOSE_DEBUG*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/freezer.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/of_gpio.h>
#include <linux/sensors.h>
#include <linux/akm8963.h>
#define AKM_DEBUG_IF 0
#define AKM_HAS_RESET 0
#define AKM_INPUT_DEVICE_NAME "compass"
#define AKM_DRDY_TIMEOUT_MS 100
#define AKM_BASE_NUM 10
#define AKM_IS_MAG_DATA_ENABLED() (akm->enable_flag & (1 << MAG_DATA_FLAG))
/* POWER SUPPLY VOLTAGE RANGE */
#define AKM8963_VDD_MIN_UV 2000000
#define AKM8963_VDD_MAX_UV 3300000
#define AKM8963_VIO_MIN_UV 1750000
#define AKM8963_VIO_MAX_UV 1950000
#define STATUS_ERROR(st) (((st)&0x08) != 0x0)
#define REG_CNTL1_MODE(reg_cntl1) (reg_cntl1 & 0x0F)
/* Save last device state for power down */
struct akm_sensor_state {
bool power_on;
uint8_t mode;
};
struct akm_compass_data {
struct i2c_client *i2c;
struct input_dev *input;
struct device *class_dev;
struct class *compass;
struct pinctrl *pinctrl;
struct pinctrl_state *pin_default;
struct pinctrl_state *pin_sleep;
struct sensors_classdev cdev;
struct workqueue_struct *data_wq;
struct delayed_work dwork;
struct mutex op_mutex;
wait_queue_head_t drdy_wq;
wait_queue_head_t open_wq;
/* These two buffers are initialized at start up.
After that, the value is not changed */
uint8_t sense_info[AKM_SENSOR_INFO_SIZE];
uint8_t sense_conf[AKM_SENSOR_CONF_SIZE];
struct mutex sensor_mutex;
uint8_t sense_data[AKM_SENSOR_DATA_SIZE];
struct mutex accel_mutex;
int16_t accel_data[3];
/* Positive value means the device is working.
0 or negative value means the device is not woking,
i.e. in power-down mode. */
int8_t is_busy;
struct mutex val_mutex;
uint32_t enable_flag;
int32_t delay[AKM_NUM_SENSORS];
atomic_t active;
atomic_t drdy;
int gpio_rstn;
bool power_enabled;
bool use_poll;
struct regulator *vdd;
struct regulator *vio;
struct akm_sensor_state state;
struct akm8963_platform_data *pdata;
};
static struct sensors_classdev sensors_cdev = {
.name = "akm8963-mag",
.vendor = "Asahi Kasei Microdevices Corporation",
.version = 1,
.handle = SENSORS_MAGNETIC_FIELD_HANDLE,
.type = SENSOR_TYPE_MAGNETIC_FIELD,
.max_range = "1228.8",
.resolution = "0.15",
.sensor_power = "0.35",
.min_delay = 10000,
.max_delay = 10000,
.fifo_reserved_event_count = 0,
.fifo_max_event_count = 0,
.enabled = 0,
.delay_msec = 10,
.sensors_enable = NULL,
.sensors_poll_delay = NULL,
};
static struct akm_compass_data *s_akm;
static int akm_compass_power_set(struct akm_compass_data *data, bool on);
/***** I2C I/O function ***********************************************/
static int akm_i2c_rxdata(
struct i2c_client *i2c,
uint8_t *rxData,
int length)
{
int ret;
struct i2c_msg msgs[] = {
{
.addr = i2c->addr,
.flags = 0,
.len = 1,
.buf = rxData,
},
{
.addr = i2c->addr,
.flags = I2C_M_RD,
.len = length,
.buf = rxData,
},
};
uint8_t addr = rxData[0];
ret = i2c_transfer(i2c->adapter, msgs, ARRAY_SIZE(msgs));
if (ret < 0) {
dev_err(&i2c->dev, "%s: transfer failed.", __func__);
return ret;
} else if (ret != ARRAY_SIZE(msgs)) {
dev_err(&i2c->dev, "%s: transfer failed(size error).\n",
__func__);
return -ENXIO;
}
dev_vdbg(&i2c->dev, "RxData: len=%02x, addr=%02x, data=%02x",
length, addr, rxData[0]);
return 0;
}
static int akm_i2c_txdata(
struct i2c_client *i2c,
uint8_t *txData,
int length)
{
int ret;
struct i2c_msg msg[] = {
{
.addr = i2c->addr,
.flags = 0,
.len = length,
.buf = txData,
},
};
ret = i2c_transfer(i2c->adapter, msg, ARRAY_SIZE(msg));
if (ret < 0) {
dev_err(&i2c->dev, "%s: transfer failed.", __func__);
return ret;
} else if (ret != ARRAY_SIZE(msg)) {
dev_err(&i2c->dev, "%s: transfer failed(size error).",
__func__);
return -ENXIO;
}
dev_vdbg(&i2c->dev, "TxData: len=%02x, addr=%02x data=%02x",
length, txData[0], txData[1]);
return 0;
}
/***** akm miscdevice functions *************************************/
static int AKECS_Set_CNTL(
struct akm_compass_data *akm,
uint8_t mode)
{
uint8_t buffer[2];
int err;
/***** lock *****/
mutex_lock(&akm->sensor_mutex);
/* Busy check */
if (akm->is_busy > 0) {
dev_err(&akm->i2c->dev,
"%s: device is busy.", __func__);
err = -EBUSY;
} else {
/* Set measure mode */
buffer[0] = AKM_REG_MODE;
buffer[1] = mode;
err = akm_i2c_txdata(akm->i2c, buffer, 2);
if (err < 0) {
dev_err(&akm->i2c->dev,
"%s: Can not set CNTL.", __func__);
} else {
dev_vdbg(&akm->i2c->dev,
"Mode is set to (%d).", mode);
/* Set flag */
akm->is_busy = 1;
atomic_set(&akm->drdy, 0);
/* wait at least 100us after changing mode */
udelay(100);
}
}
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
return err;
}
static int AKECS_Set_PowerDown(
struct akm_compass_data *akm)
{
uint8_t buffer[2];
int err;
/***** lock *****/
mutex_lock(&akm->sensor_mutex);
/* Set powerdown mode */
buffer[0] = AKM_REG_MODE;
buffer[1] = AKM_MODE_POWERDOWN;
err = akm_i2c_txdata(akm->i2c, buffer, 2);
if (err < 0) {
dev_err(&akm->i2c->dev,
"%s: Can not set to powerdown mode.", __func__);
} else {
dev_dbg(&akm->i2c->dev, "Powerdown mode is set.");
/* wait at least 100us after changing mode */
udelay(100);
}
/* Clear status */
akm->is_busy = 0;
atomic_set(&akm->drdy, 0);
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
return err;
}
static int AKECS_Reset(
struct akm_compass_data *akm,
int hard)
{
int err;
#if AKM_HAS_RESET
uint8_t buffer[2];
/***** lock *****/
mutex_lock(&akm->sensor_mutex);
if (hard != 0) {
gpio_set_value(akm->gpio_rstn, 0);
udelay(5);
gpio_set_value(akm->gpio_rstn, 1);
/* No error is returned */
err = 0;
} else {
buffer[0] = AKM_REG_RESET;
buffer[1] = AKM_RESET_DATA;
err = akm_i2c_txdata(akm->i2c, buffer, 2);
if (err < 0) {
dev_err(&akm->i2c->dev,
"%s: Can not set SRST bit.", __func__);
} else {
dev_dbg(&akm->i2c->dev, "Soft reset is done.");
}
}
/* Device will be accessible 100 us after */
udelay(100);
/* Clear status */
akm->is_busy = 0;
atomic_set(&akm->drdy, 0);
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
#else
err = AKECS_Set_PowerDown(akm);
#endif
return err;
}
static int AKECS_SetMode(
struct akm_compass_data *akm,
uint8_t mode)
{
int err;
switch (REG_CNTL1_MODE(mode)) {
case AKM_MODE_SNG_MEASURE:
case AKM_MODE_SELF_TEST:
case AK8963_MODE_CONT1_MEASURE:
case AK8963_MODE_CONT2_MEASURE:
case AK8963_MODE_EXT_TRIG_MEASURE:
case AKM_MODE_FUSE_ACCESS:
err = AKECS_Set_CNTL(akm, mode);
break;
case AKM_MODE_POWERDOWN:
err = AKECS_Set_PowerDown(akm);
break;
default:
dev_err(&akm->i2c->dev,
"%s: Unknown mode(%d).", __func__, mode);
return -EINVAL;
}
akm->state.mode = mode;
return err;
}
static void AKECS_SetYPR(
struct akm_compass_data *akm,
int *rbuf)
{
uint32_t ready;
dev_vdbg(&akm->i2c->dev, "%s: flag =0x%X", __func__, rbuf[0]);
dev_vdbg(&akm->input->dev, " Acc [LSB] : %6d,%6d,%6d stat=%d",
rbuf[1], rbuf[2], rbuf[3], rbuf[4]);
dev_vdbg(&akm->input->dev, " Geo [LSB] : %6d,%6d,%6d stat=%d",
rbuf[5], rbuf[6], rbuf[7], rbuf[8]);
dev_vdbg(&akm->input->dev, " Orientation : %6d,%6d,%6d",
rbuf[9], rbuf[10], rbuf[11]);
dev_vdbg(&akm->input->dev, " Rotation V : %6d,%6d,%6d,%6d",
rbuf[12], rbuf[13], rbuf[14], rbuf[15]);
/* No events are reported */
if (!rbuf[0]) {
dev_dbg(&akm->i2c->dev, "Don't waste a time.");
return;
}
mutex_lock(&akm->val_mutex);
ready = (akm->enable_flag & (uint32_t)rbuf[0]);
mutex_unlock(&akm->val_mutex);
/* Report acceleration sensor information */
if (ready & ACC_DATA_READY) {
input_report_abs(akm->input, ABS_X, rbuf[1]);
input_report_abs(akm->input, ABS_Y, rbuf[2]);
input_report_abs(akm->input, ABS_Z, rbuf[3]);
input_report_abs(akm->input, ABS_RX, rbuf[4]);
}
/* Report magnetic vector information */
if (ready & MAG_DATA_READY) {
input_report_abs(akm->input, ABS_X, rbuf[5]);
input_report_abs(akm->input, ABS_Y, rbuf[6]);
input_report_abs(akm->input, ABS_Z, rbuf[7]);
}
/* Report fusion sensor information */
if (ready & FUSION_DATA_READY) {
/* Orientation */
input_report_abs(akm->input, ABS_HAT0Y, rbuf[9]);
input_report_abs(akm->input, ABS_HAT1X, rbuf[10]);
input_report_abs(akm->input, ABS_HAT1Y, rbuf[11]);
/* Rotation Vector */
input_report_abs(akm->input, ABS_TILT_X, rbuf[12]);
input_report_abs(akm->input, ABS_TILT_Y, rbuf[13]);
input_report_abs(akm->input, ABS_TOOL_WIDTH, rbuf[14]);
input_report_abs(akm->input, ABS_VOLUME, rbuf[15]);
}
input_sync(akm->input);
}
/* This function will block a process until the latest measurement
* data is available.
*/
static int AKECS_GetData(
struct akm_compass_data *akm,
uint8_t *rbuf,
int size)
{
int err;
/* Block! */
err = wait_event_interruptible_timeout(
akm->drdy_wq,
atomic_read(&akm->drdy),
msecs_to_jiffies(AKM_DRDY_TIMEOUT_MS));
if (err < 0) {
dev_err(&akm->i2c->dev,
"%s: wait_event failed (%d).", __func__, err);
return err;
}
if (!atomic_read(&akm->drdy)) {
dev_err(&akm->i2c->dev,
"%s: DRDY is not set.", __func__);
return -ENODATA;
}
/***** lock *****/
mutex_lock(&akm->sensor_mutex);
memcpy(rbuf, akm->sense_data, size);
atomic_set(&akm->drdy, 0);
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
return 0;
}
static int AKECS_GetData_Poll(
struct akm_compass_data *akm,
uint8_t *rbuf,
int size)
{
uint8_t buffer[AKM_SENSOR_DATA_SIZE];
int err;
/* Read status */
buffer[0] = AKM_REG_STATUS;
err = akm_i2c_rxdata(akm->i2c, buffer, 1);
if (err < 0) {
dev_err(&akm->i2c->dev, "%s failed.", __func__);
return err;
}
/* Check ST bit */
if (!(AKM_DRDY_IS_HIGH(buffer[0])))
return -EAGAIN;
/* Read rest data */
buffer[1] = AKM_REG_STATUS + 1;
err = akm_i2c_rxdata(akm->i2c, &(buffer[1]), AKM_SENSOR_DATA_SIZE-1);
if (err < 0) {
dev_err(&akm->i2c->dev, "%s failed.", __func__);
return err;
}
memcpy(rbuf, buffer, size);
atomic_set(&akm->drdy, 0);
/***** lock *****/
mutex_lock(&akm->sensor_mutex);
akm->is_busy = 0;
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
return 0;
}
static int AKECS_GetOpenStatus(
struct akm_compass_data *akm)
{
return wait_event_interruptible(
akm->open_wq, (atomic_read(&akm->active) > 0));
}
static int AKECS_GetCloseStatus(
struct akm_compass_data *akm)
{
return wait_event_interruptible(
akm->open_wq, (atomic_read(&akm->active) <= 0));
}
static int AKECS_Open(struct inode *inode, struct file *file)
{
file->private_data = s_akm;
return nonseekable_open(inode, file);
}
static int AKECS_Release(struct inode *inode, struct file *file)
{
return 0;
}
static long
AKECS_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
struct akm_compass_data *akm = file->private_data;
/* NOTE: In this function the size of "char" should be 1-byte. */
uint8_t i2c_buf[AKM_RWBUF_SIZE]; /* for READ/WRITE */
uint8_t dat_buf[AKM_SENSOR_DATA_SIZE];/* for GET_DATA */
int32_t ypr_buf[AKM_YPR_DATA_SIZE]; /* for SET_YPR */
int32_t delay[AKM_NUM_SENSORS]; /* for GET_DELAY */
int16_t acc_buf[3]; /* for GET_ACCEL */
uint8_t mode; /* for SET_MODE*/
int status; /* for OPEN/CLOSE_STATUS */
int ret = 0; /* Return value. */
switch (cmd) {
case ECS_IOCTL_READ:
case ECS_IOCTL_WRITE:
if (argp == NULL) {
dev_err(&akm->i2c->dev, "invalid argument.");
return -EINVAL;
}
if (copy_from_user(&i2c_buf, argp, sizeof(i2c_buf))) {
dev_err(&akm->i2c->dev, "copy_from_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_SET_MODE:
if (argp == NULL) {
dev_err(&akm->i2c->dev, "invalid argument.");
return -EINVAL;
}
if (copy_from_user(&mode, argp, sizeof(mode))) {
dev_err(&akm->i2c->dev, "copy_from_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_SET_YPR:
if (argp == NULL) {
dev_err(&akm->i2c->dev, "invalid argument.");
return -EINVAL;
}
if (copy_from_user(&ypr_buf, argp, sizeof(ypr_buf))) {
dev_err(&akm->i2c->dev, "copy_from_user failed.");
return -EFAULT;
}
case ECS_IOCTL_GET_INFO:
case ECS_IOCTL_GET_CONF:
case ECS_IOCTL_GET_DATA:
case ECS_IOCTL_GET_OPEN_STATUS:
case ECS_IOCTL_GET_CLOSE_STATUS:
case ECS_IOCTL_GET_DELAY:
case ECS_IOCTL_GET_LAYOUT:
case ECS_IOCTL_GET_ACCEL:
/* Check buffer pointer for writing a data later. */
if (argp == NULL) {
dev_err(&akm->i2c->dev, "invalid argument.");
return -EINVAL;
}
break;
default:
break;
}
switch (cmd) {
case ECS_IOCTL_READ:
dev_vdbg(&akm->i2c->dev, "IOCTL_READ called.");
if ((i2c_buf[0] < 1) || (i2c_buf[0] > (AKM_RWBUF_SIZE-1))) {
dev_err(&akm->i2c->dev, "invalid argument.");
return -EINVAL;
}
ret = akm_i2c_rxdata(akm->i2c, &i2c_buf[1], i2c_buf[0]);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_WRITE:
dev_vdbg(&akm->i2c->dev, "IOCTL_WRITE called.");
if ((i2c_buf[0] < 2) || (i2c_buf[0] > (AKM_RWBUF_SIZE-1))) {
dev_err(&akm->i2c->dev, "invalid argument.");
return -EINVAL;
}
ret = akm_i2c_txdata(akm->i2c, &i2c_buf[1], i2c_buf[0]);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_RESET:
dev_vdbg(&akm->i2c->dev, "IOCTL_RESET called.");
ret = AKECS_Reset(akm, akm->gpio_rstn);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_SET_MODE:
dev_vdbg(&akm->i2c->dev, "IOCTL_SET_MODE called.");
ret = AKECS_SetMode(akm, mode);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_SET_YPR:
dev_vdbg(&akm->i2c->dev, "IOCTL_SET_YPR called.");
AKECS_SetYPR(akm, ypr_buf);
break;
case ECS_IOCTL_GET_DATA:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_DATA called.");
if (akm->i2c->irq)
ret = AKECS_GetData(akm, dat_buf, AKM_SENSOR_DATA_SIZE);
else
ret = AKECS_GetData_Poll(
akm, dat_buf, AKM_SENSOR_DATA_SIZE);
if (ret < 0)
return ret;
break;
case ECS_IOCTL_GET_OPEN_STATUS:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_OPEN_STATUS called.");
ret = AKECS_GetOpenStatus(akm);
if (ret < 0) {
dev_err(&akm->i2c->dev,
"Get Open returns error (%d).", ret);
return ret;
}
break;
case ECS_IOCTL_GET_CLOSE_STATUS:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_CLOSE_STATUS called.");
ret = AKECS_GetCloseStatus(akm);
if (ret < 0) {
dev_err(&akm->i2c->dev,
"Get Close returns error (%d).", ret);
return ret;
}
break;
case ECS_IOCTL_GET_DELAY:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_DELAY called.");
mutex_lock(&akm->val_mutex);
delay[0] = ((akm->enable_flag & ACC_DATA_READY) ?
akm->delay[0] : -1);
delay[1] = ((akm->enable_flag & MAG_DATA_READY) ?
akm->delay[1] : -1);
delay[2] = ((akm->enable_flag & FUSION_DATA_READY) ?
akm->delay[2] : -1);
mutex_unlock(&akm->val_mutex);
break;
case ECS_IOCTL_GET_INFO:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_INFO called.");
break;
case ECS_IOCTL_GET_CONF:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_CONF called.");
break;
case ECS_IOCTL_GET_LAYOUT:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_LAYOUT called.");
break;
case ECS_IOCTL_GET_ACCEL:
dev_vdbg(&akm->i2c->dev, "IOCTL_GET_ACCEL called.");
mutex_lock(&akm->accel_mutex);
acc_buf[0] = akm->accel_data[0];
acc_buf[1] = akm->accel_data[1];
acc_buf[2] = akm->accel_data[2];
mutex_unlock(&akm->accel_mutex);
break;
default:
return -ENOTTY;
}
switch (cmd) {
case ECS_IOCTL_READ:
/* +1 is for the first byte */
if (copy_to_user(argp, &i2c_buf, i2c_buf[0]+1)) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_GET_INFO:
if (copy_to_user(argp, &akm->sense_info,
sizeof(akm->sense_info))) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_GET_CONF:
if (copy_to_user(argp, &akm->sense_conf,
sizeof(akm->sense_conf))) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_GET_DATA:
if (copy_to_user(argp, &dat_buf, sizeof(dat_buf))) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_GET_OPEN_STATUS:
case ECS_IOCTL_GET_CLOSE_STATUS:
status = atomic_read(&akm->active);
if (copy_to_user(argp, &status, sizeof(status))) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_GET_DELAY:
if (copy_to_user(argp, &delay, sizeof(delay))) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_GET_LAYOUT:
if (copy_to_user(argp, &akm->pdata->layout,
sizeof(akm->pdata->layout))) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
case ECS_IOCTL_GET_ACCEL:
if (copy_to_user(argp, &acc_buf, sizeof(acc_buf))) {
dev_err(&akm->i2c->dev, "copy_to_user failed.");
return -EFAULT;
}
break;
default:
break;
}
return 0;
}
static const struct file_operations AKECS_fops = {
.owner = THIS_MODULE,
.open = AKECS_Open,
.release = AKECS_Release,
.unlocked_ioctl = AKECS_ioctl,
};
/***** akm sysfs functions ******************************************/
static int create_device_attributes(
struct device *dev,
struct device_attribute *attrs)
{
int i;
int err = 0;
for (i = 0 ; NULL != attrs[i].attr.name ; ++i) {
err = device_create_file(dev, &attrs[i]);
if (err)
break;
}
if (err) {
for (--i; i >= 0 ; --i)
device_remove_file(dev, &attrs[i]);
}
return err;
}
static void remove_device_attributes(
struct device *dev,
struct device_attribute *attrs)
{
int i;
for (i = 0 ; NULL != attrs[i].attr.name ; ++i)
device_remove_file(dev, &attrs[i]);
}
static int create_device_binary_attributes(
struct kobject *kobj,
struct bin_attribute *attrs)
{
int i;
int err = 0;
err = 0;
for (i = 0 ; NULL != attrs[i].attr.name ; ++i) {
err = sysfs_create_bin_file(kobj, &attrs[i]);
if (0 != err)
break;
}
if (0 != err) {
for (--i; i >= 0 ; --i)
sysfs_remove_bin_file(kobj, &attrs[i]);
}
return err;
}
static void remove_device_binary_attributes(
struct kobject *kobj,
struct bin_attribute *attrs)
{
int i;
for (i = 0 ; NULL != attrs[i].attr.name ; ++i)
sysfs_remove_bin_file(kobj, &attrs[i]);
}
/*********************************************************************
*
* SysFS attribute functions
*
* directory : /sys/class/compass/akmXXXX/
* files :
* - enable_acc [rw] [t] : enable flag for accelerometer
* - enable_mag [rw] [t] : enable flag for magnetometer
* - enable_fusion [rw] [t] : enable flag for fusion sensor
* - delay_acc [rw] [t] : delay in nanosecond for accelerometer
* - delay_mag [rw] [t] : delay in nanosecond for magnetometer
* - delay_fusion [rw] [t] : delay in nanosecond for fusion sensor
*
* debug :
* - mode [w] [t] : E-Compass mode
* - bdata [r] [t] : buffered raw data
* - asa [r] [t] : FUSEROM data
* - regs [r] [t] : read all registers
*
* [b] = binary format
* [t] = text format
*/
/***** sysfs enable *************************************************/
static void akm_compass_sysfs_update_status(
struct akm_compass_data *akm)
{
uint32_t en;
mutex_lock(&akm->val_mutex);
en = akm->enable_flag;
mutex_unlock(&akm->val_mutex);
if (en == 0) {
if (atomic_cmpxchg(&akm->active, 1, 0) == 1) {
wake_up(&akm->open_wq);
dev_dbg(akm->class_dev, "Deactivated");
}
} else {
if (atomic_cmpxchg(&akm->active, 0, 1) == 0) {
wake_up(&akm->open_wq);
dev_dbg(akm->class_dev, "Activated");
}
}
dev_dbg(&akm->i2c->dev,
"Status updated: enable=0x%X, active=%d",
en, atomic_read(&akm->active));
}
static int akm_enable_set(struct sensors_classdev *sensors_cdev,
unsigned int enable)
{
int ret = 0;
struct akm_compass_data *akm = container_of(sensors_cdev,
struct akm_compass_data, cdev);
mutex_lock(&akm->val_mutex);
akm->enable_flag &= ~(1<<MAG_DATA_FLAG);
akm->enable_flag |= ((uint32_t)(enable))<<MAG_DATA_FLAG;
mutex_unlock(&akm->val_mutex);
akm_compass_sysfs_update_status(akm);
mutex_lock(&akm->op_mutex);
if (enable) {
ret = akm_compass_power_set(akm, true);
if (ret) {
dev_err(&akm->i2c->dev,
"Power on fail! ret=%d\n", ret);
goto exit;
}
}
if (akm->use_poll && akm->pdata->auto_report) {
if (enable) {
AKECS_SetMode(akm,
AKM_MODE_SNG_MEASURE | AKM8963_BIT_OP_16);
queue_delayed_work(akm->data_wq,
&akm->dwork,
msecs_to_jiffies
(akm->delay[MAG_DATA_FLAG]));
} else {
cancel_delayed_work_sync(&akm->dwork);
AKECS_SetMode(akm, AKM_MODE_POWERDOWN);
}
} else {
if (enable)
enable_irq(akm->i2c->irq);
else
disable_irq(akm->i2c->irq);
}
if (!enable) {
ret = akm_compass_power_set(akm, false);
if (ret) {
dev_err(&akm->i2c->dev,
"Power off fail! ret=%d\n", ret);
goto exit;
}
}
exit:
mutex_unlock(&akm->op_mutex);
return ret;
}
static ssize_t akm_compass_sysfs_enable_show(
struct akm_compass_data *akm, char *buf, int pos)
{
int flag;
mutex_lock(&akm->val_mutex);
flag = ((akm->enable_flag >> pos) & 1);
mutex_unlock(&akm->val_mutex);
return scnprintf(buf, PAGE_SIZE, "%d\n", flag);
}
static ssize_t akm_compass_sysfs_enable_store(
struct akm_compass_data *akm, char const *buf, size_t count, int pos)
{
long en = 0;
int ret = 0;
if (NULL == buf)
return -EINVAL;
if (0 == count)
return 0;
if (strict_strtol(buf, AKM_BASE_NUM, &en))
return -EINVAL;
en = en ? 1 : 0;
mutex_lock(&akm->op_mutex);
ret = akm_compass_power_set(akm, en);
if (ret) {
dev_err(&akm->i2c->dev,
"Fail to configure device power!\n");
goto exit;
}
mutex_lock(&akm->val_mutex);
akm->enable_flag &= ~(1<<pos);
akm->enable_flag |= ((uint32_t)(en))<<pos;
mutex_unlock(&akm->val_mutex);
akm_compass_sysfs_update_status(akm);
exit:
mutex_unlock(&akm->op_mutex);
return ret ? ret : count;
}
/***** Acceleration ***/
static ssize_t akm_enable_acc_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
return akm_compass_sysfs_enable_show(
dev_get_drvdata(dev), buf, ACC_DATA_FLAG);
}
static ssize_t akm_enable_acc_store(
struct device *dev, struct device_attribute *attr,
char const *buf, size_t count)
{
return akm_compass_sysfs_enable_store(
dev_get_drvdata(dev), buf, count, ACC_DATA_FLAG);
}
/***** Magnetic field ***/
static ssize_t akm_enable_mag_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
return akm_compass_sysfs_enable_show(
dev_get_drvdata(dev), buf, MAG_DATA_FLAG);
}
static ssize_t akm_enable_mag_store(
struct device *dev, struct device_attribute *attr,
char const *buf, size_t count)
{
return akm_compass_sysfs_enable_store(
dev_get_drvdata(dev), buf, count, MAG_DATA_FLAG);
}
/***** Fusion ***/
static ssize_t akm_enable_fusion_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
return akm_compass_sysfs_enable_show(
dev_get_drvdata(dev), buf, FUSION_DATA_FLAG);
}
static ssize_t akm_enable_fusion_store(
struct device *dev, struct device_attribute *attr,
char const *buf, size_t count)
{
return akm_compass_sysfs_enable_store(
dev_get_drvdata(dev), buf, count, FUSION_DATA_FLAG);
}
/***** sysfs delay **************************************************/
static int akm_poll_delay_set(struct sensors_classdev *sensors_cdev,
unsigned int delay_msec)
{
struct akm_compass_data *akm = container_of(sensors_cdev,
struct akm_compass_data, cdev);
mutex_lock(&akm->val_mutex);
akm->delay[MAG_DATA_FLAG] = delay_msec;
mutex_unlock(&akm->val_mutex);
return 0;
}
static ssize_t akm_compass_sysfs_delay_show(
struct akm_compass_data *akm, char *buf, int pos)
{
unsigned long val;
mutex_lock(&akm->val_mutex);
val = akm->delay[pos];
mutex_unlock(&akm->val_mutex);
return scnprintf(buf, PAGE_SIZE, "%lu\n", val);
}
static ssize_t akm_compass_sysfs_delay_store(
struct akm_compass_data *akm, char const *buf, size_t count, int pos)
{
unsigned long val = 0;
if (NULL == buf)
return -EINVAL;
if (0 == count)
return 0;
if (kstrtoul(buf, AKM_BASE_NUM, &val))
return -EINVAL;
mutex_lock(&akm->val_mutex);
akm->delay[pos] = val;
mutex_unlock(&akm->val_mutex);
return count;
}
/***** Accelerometer ***/
static ssize_t akm_delay_acc_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
return akm_compass_sysfs_delay_show(
dev_get_drvdata(dev), buf, ACC_DATA_FLAG);
}
static ssize_t akm_delay_acc_store(
struct device *dev, struct device_attribute *attr,
char const *buf, size_t count)
{
return akm_compass_sysfs_delay_store(
dev_get_drvdata(dev), buf, count, ACC_DATA_FLAG);
}
/***** Magnetic field ***/
static ssize_t akm_delay_mag_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
return akm_compass_sysfs_delay_show(
dev_get_drvdata(dev), buf, MAG_DATA_FLAG);
}
static ssize_t akm_delay_mag_store(
struct device *dev, struct device_attribute *attr,
char const *buf, size_t count)
{
return akm_compass_sysfs_delay_store(
dev_get_drvdata(dev), buf, count, MAG_DATA_FLAG);
}
/***** Fusion ***/
static ssize_t akm_delay_fusion_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
return akm_compass_sysfs_delay_show(
dev_get_drvdata(dev), buf, FUSION_DATA_FLAG);
}
static ssize_t akm_delay_fusion_store(
struct device *dev, struct device_attribute *attr,
char const *buf, size_t count)
{
return akm_compass_sysfs_delay_store(
dev_get_drvdata(dev), buf, count, FUSION_DATA_FLAG);
}
/***** accel (binary) ***/
static ssize_t akm_bin_accel_write(
struct file *file,
struct kobject *kobj,
struct bin_attribute *attr,
char *buf,
loff_t pos,
size_t size)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct akm_compass_data *akm = dev_get_drvdata(dev);
int16_t *accel_data;
if (size == 0)
return 0;
accel_data = (int16_t *)buf;
mutex_lock(&akm->accel_mutex);
akm->accel_data[0] = accel_data[0];
akm->accel_data[1] = accel_data[1];
akm->accel_data[2] = accel_data[2];
mutex_unlock(&akm->accel_mutex);
dev_vdbg(&akm->i2c->dev, "accel:%d,%d,%d\n",
accel_data[0], accel_data[1], accel_data[2]);
return size;
}
#if AKM_DEBUG_IF
static ssize_t akm_sysfs_mode_store(
struct device *dev, struct device_attribute *attr,
char const *buf, size_t count)
{
struct akm_compass_data *akm = dev_get_drvdata(dev);
long mode = 0;
if (NULL == buf)
return -EINVAL;
if (0 == count)
return 0;
if (strict_strtol(buf, AKM_BASE_NUM, &mode))
return -EINVAL;
if (AKECS_SetMode(akm, (uint8_t)mode) < 0)
return -EINVAL;
return 1;
}
static ssize_t akm_buf_print(
char *buf, uint8_t *data, size_t num)
{
int sz, i;
char *cur;
size_t cur_len;
cur = buf;
cur_len = PAGE_SIZE;
sz = snprintf(cur, cur_len, "(HEX):");
if (sz < 0)
return sz;
cur += sz;
cur_len -= sz;
for (i = 0; i < num; i++) {
sz = snprintf(cur, cur_len, "%02X,", *data);
if (sz < 0)
return sz;
cur += sz;
cur_len -= sz;
data++;
}
sz = snprintf(cur, cur_len, "\n");
if (sz < 0)
return sz;
cur += sz;
return (ssize_t)(cur - buf);
}
static ssize_t akm_sysfs_bdata_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
struct akm_compass_data *akm = dev_get_drvdata(dev);
uint8_t rbuf[AKM_SENSOR_DATA_SIZE];
mutex_lock(&akm->sensor_mutex);
memcpy(&rbuf, akm->sense_data, sizeof(rbuf));
mutex_unlock(&akm->sensor_mutex);
return akm_buf_print(buf, rbuf, AKM_SENSOR_DATA_SIZE);
}
static ssize_t akm_sysfs_asa_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
struct akm_compass_data *akm = dev_get_drvdata(dev);
int err;
uint8_t asa[3];
err = AKECS_SetMode(akm, AKM_MODE_FUSE_ACCESS);
if (err < 0)
return err;
asa[0] = AKM_FUSE_1ST_ADDR;
err = akm_i2c_rxdata(akm->i2c, asa, 3);
if (err < 0)
return err;
err = AKECS_SetMode(akm, AKM_MODE_POWERDOWN);
if (err < 0)
return err;
return akm_buf_print(buf, asa, 3);
}
static ssize_t akm_sysfs_regs_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
/* The total number of registers depends on the device. */
struct akm_compass_data *akm = dev_get_drvdata(dev);
int err;
uint8_t regs[AKM_REGS_SIZE];
/* This function does not lock mutex obj */
regs[0] = AKM_REGS_1ST_ADDR;
err = akm_i2c_rxdata(akm->i2c, regs, AKM_REGS_SIZE);
if (err < 0)
return err;
return akm_buf_print(buf, regs, AKM_REGS_SIZE);
}
#endif
static struct device_attribute akm_compass_attributes[] = {
__ATTR(enable_acc, 0660, akm_enable_acc_show, akm_enable_acc_store),
__ATTR(enable_mag, 0660, akm_enable_mag_show, akm_enable_mag_store),
__ATTR(enable_fusion, 0660, akm_enable_fusion_show,
akm_enable_fusion_store),
__ATTR(delay_acc, 0660, akm_delay_acc_show, akm_delay_acc_store),
__ATTR(delay_mag, 0660, akm_delay_mag_show, akm_delay_mag_store),
__ATTR(delay_fusion, 0660, akm_delay_fusion_show,
akm_delay_fusion_store),
#if AKM_DEBUG_IF
__ATTR(mode, 0220, NULL, akm_sysfs_mode_store),
__ATTR(bdata, 0440, akm_sysfs_bdata_show, NULL),
__ATTR(asa, 0440, akm_sysfs_asa_show, NULL),
__ATTR(regs, 0440, akm_sysfs_regs_show, NULL),
#endif
__ATTR_NULL,
};
#define __BIN_ATTR(name_, mode_, size_, private_, read_, write_) \
{ \
.attr = { .name = __stringify(name_), .mode = mode_ }, \
.size = size_, \
.private = private_, \
.read = read_, \
.write = write_, \
}
#define __BIN_ATTR_NULL \
{ \
.attr = { .name = NULL }, \
}
static struct bin_attribute akm_compass_bin_attributes[] = {
__BIN_ATTR(accel, 0220, 6, NULL,
NULL, akm_bin_accel_write),
__BIN_ATTR_NULL
};
static char const *const device_link_name = "i2c";
static dev_t const akm_compass_device_dev_t = MKDEV(MISC_MAJOR, 240);
static int create_sysfs_interfaces(struct akm_compass_data *akm)
{
int err;
if (NULL == akm)
return -EINVAL;
err = 0;
akm->compass = class_create(THIS_MODULE, AKM_SYSCLS_NAME);
if (IS_ERR(akm->compass)) {
err = PTR_ERR(akm->compass);
goto exit_class_create_failed;
}
akm->class_dev = device_create(
akm->compass,
NULL,
akm_compass_device_dev_t,
akm,
AKM_SYSDEV_NAME);
if (IS_ERR(akm->class_dev)) {
err = PTR_ERR(akm->class_dev);
goto exit_class_device_create_failed;
}
err = sysfs_create_link(
&akm->class_dev->kobj,
&akm->i2c->dev.kobj,
device_link_name);
if (0 > err)
goto exit_sysfs_create_link_failed;
err = create_device_attributes(
akm->class_dev,
akm_compass_attributes);
if (0 > err)
goto exit_device_attributes_create_failed;
err = create_device_binary_attributes(
&akm->class_dev->kobj,
akm_compass_bin_attributes);
if (0 > err)
goto exit_device_binary_attributes_create_failed;
return err;
exit_device_binary_attributes_create_failed:
remove_device_attributes(akm->class_dev, akm_compass_attributes);
exit_device_attributes_create_failed:
sysfs_remove_link(&akm->class_dev->kobj, device_link_name);
exit_sysfs_create_link_failed:
device_destroy(akm->compass, akm_compass_device_dev_t);
exit_class_device_create_failed:
akm->class_dev = NULL;
class_destroy(akm->compass);
exit_class_create_failed:
akm->compass = NULL;
return err;
}
static void remove_sysfs_interfaces(struct akm_compass_data *akm)
{
if (NULL == akm)
return;
if (NULL != akm->class_dev) {
remove_device_binary_attributes(
&akm->class_dev->kobj,
akm_compass_bin_attributes);
remove_device_attributes(
akm->class_dev,
akm_compass_attributes);
sysfs_remove_link(
&akm->class_dev->kobj,
device_link_name);
akm->class_dev = NULL;
}
if (NULL != akm->compass) {
device_destroy(
akm->compass,
akm_compass_device_dev_t);
class_destroy(akm->compass);
akm->compass = NULL;
}
}
/***** akm input device functions ***********************************/
static int akm_compass_input_init(
struct input_dev **input)
{
int err = 0;
/* Declare input device */
*input = input_allocate_device();
if (!*input)
return -ENOMEM;
/* Setup input device */
set_bit(EV_ABS, (*input)->evbit);
/* Magnetic field (limited to 16bit) */
input_set_abs_params(*input, ABS_X,
-32768, 32767, 0, 0);
input_set_abs_params(*input, ABS_Y,
-32768, 32767, 0, 0);
input_set_abs_params(*input, ABS_Z,
-32768, 32767, 0, 0);
/* Set name */
(*input)->name = AKM_INPUT_DEVICE_NAME;
/* Register */
err = input_register_device(*input);
if (err) {
input_free_device(*input);
return err;
}
return err;
}
/***** akm functions ************************************************/
static irqreturn_t akm_compass_irq(int irq, void *handle)
{
struct akm_compass_data *akm = handle;
uint8_t buffer[AKM_SENSOR_DATA_SIZE];
int err;
memset(buffer, 0, sizeof(buffer));
/***** lock *****/
mutex_lock(&akm->sensor_mutex);
/* Read whole data */
buffer[0] = AKM_REG_STATUS;
err = akm_i2c_rxdata(akm->i2c, buffer, AKM_SENSOR_DATA_SIZE);
if (err < 0) {
dev_err(&akm->i2c->dev, "IRQ I2C error.");
akm->is_busy = 0;
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
return IRQ_HANDLED;
}
/* Check ST bit */
if (!(AKM_DRDY_IS_HIGH(buffer[0])))
goto work_func_none;
memcpy(akm->sense_data, buffer, AKM_SENSOR_DATA_SIZE);
akm->is_busy = 0;
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
atomic_set(&akm->drdy, 1);
wake_up(&akm->drdy_wq);
dev_vdbg(&akm->i2c->dev, "IRQ handled.");
return IRQ_HANDLED;
work_func_none:
mutex_unlock(&akm->sensor_mutex);
/***** unlock *****/
dev_vdbg(&akm->i2c->dev, "IRQ not handled.");
return IRQ_NONE;
}
static int akm_compass_suspend(struct device *dev)
{
struct akm_compass_data *akm = dev_get_drvdata(dev);
int ret = 0;
if (AKM_IS_MAG_DATA_ENABLED() &&
akm->use_poll &&
akm->pdata->auto_report)
cancel_delayed_work_sync(&akm->dwork);
akm->state.power_on = akm->power_enabled;
if (akm->state.power_on) {
akm_compass_power_set(akm, false);
/* Clear status */
akm->is_busy = 0;
atomic_set(&akm->drdy, 0);
}
ret = pinctrl_select_state(akm->pinctrl, akm->pin_sleep);
if (ret)
dev_err(dev, "Can't select pinctrl state\n");
dev_dbg(&akm->i2c->dev, "suspended\n");
return 0;
}
static int akm_compass_resume(struct device *dev)
{
struct akm_compass_data *akm = dev_get_drvdata(dev);
int ret = 0;
ret = pinctrl_select_state(akm->pinctrl, akm->pin_default);
if (ret)
dev_err(dev, "Can't select pinctrl state\n");
if (akm->state.power_on) {
ret = akm_compass_power_set(akm, true);
if (ret) {
dev_err(dev, "Sensor power resume fail!\n");
goto exit;
}
ret = AKECS_SetMode(akm, akm->state.mode);
if (ret) {
dev_err(dev, "Sensor state resume fail!\n");
goto exit;
}
if (AKM_IS_MAG_DATA_ENABLED() &&
akm->use_poll &&
akm->pdata->auto_report)
queue_delayed_work(akm->data_wq,
&akm->dwork,
(unsigned long)nsecs_to_jiffies64(
akm->delay[MAG_DATA_FLAG]));
}
dev_dbg(&akm->i2c->dev, "resumed\n");
exit:
return 0;
}
static int akm8963_i2c_check_device(
struct i2c_client *client)
{
/* AK8963 specific function */
struct akm_compass_data *akm = i2c_get_clientdata(client);
int err;
akm->sense_info[0] = AK8963_REG_WIA;
err = akm_i2c_rxdata(client, akm->sense_info, AKM_SENSOR_INFO_SIZE);
if (err < 0)
return err;
/* Set FUSE access mode */
err = AKECS_SetMode(akm, AK8963_MODE_FUSE_ACCESS);
if (err < 0)
return err;
akm->sense_conf[0] = AK8963_FUSE_ASAX;
err = akm_i2c_rxdata(client, akm->sense_conf, AKM_SENSOR_CONF_SIZE);
if (err < 0)
return err;
err = AKECS_SetMode(akm, AK8963_MODE_POWERDOWN);
if (err < 0)
return err;
/* Check read data */
if (akm->sense_info[0] != AK8963_WIA_VALUE) {
dev_err(&client->dev,
"%s: The device is not AKM Compass.", __func__);
return -ENXIO;
}
return err;
}
static int akm_compass_power_set(struct akm_compass_data *data, bool on)
{
int rc = 0;
if (!on && data->power_enabled) {
rc = regulator_disable(data->vdd);
if (rc) {
dev_err(&data->i2c->dev,
"Regulator vdd disable failed rc=%d\n", rc);
goto err_vdd_disable;
}
rc = regulator_disable(data->vio);
if (rc) {
dev_err(&data->i2c->dev,
"Regulator vio disable failed rc=%d\n", rc);
goto err_vio_disable;
}
data->power_enabled = false;
return rc;
} else if (on && !data->power_enabled) {
rc = regulator_enable(data->vdd);
if (rc) {
dev_err(&data->i2c->dev,
"Regulator vdd enable failed rc=%d\n", rc);
goto err_vdd_enable;
}
rc = regulator_enable(data->vio);
if (rc) {
dev_err(&data->i2c->dev,
"Regulator vio enable failed rc=%d\n", rc);
goto err_vio_enable;
}
data->power_enabled = true;
/*
* The max time for the power supply rise time is 50ms.
* Use 80ms to make sure it meets the requirements.
*/
msleep(80);
return rc;
} else {
dev_warn(&data->i2c->dev,
"Power on=%d. enabled=%d\n",
on, data->power_enabled);
return rc;
}
err_vio_enable:
regulator_disable(data->vio);
err_vdd_enable:
return rc;
err_vio_disable:
if (regulator_enable(data->vdd))
dev_warn(&data->i2c->dev, "Regulator vdd enable failed\n");
err_vdd_disable:
return rc;
}
static int akm_compass_power_init(struct akm_compass_data *data, bool on)
{
int rc;
if (!on) {
if (regulator_count_voltages(data->vdd) > 0)
regulator_set_voltage(data->vdd, 0,
AKM8963_VDD_MAX_UV);
regulator_put(data->vdd);
if (regulator_count_voltages(data->vio) > 0)
regulator_set_voltage(data->vio, 0,
AKM8963_VIO_MAX_UV);
regulator_put(data->vio);
} else {
data->vdd = regulator_get(&data->i2c->dev, "vdd");
if (IS_ERR(data->vdd)) {
rc = PTR_ERR(data->vdd);
dev_err(&data->i2c->dev,
"Regulator get failed vdd rc=%d\n", rc);
return rc;
}
if (regulator_count_voltages(data->vdd) > 0) {
rc = regulator_set_voltage(data->vdd,
AKM8963_VDD_MIN_UV, AKM8963_VDD_MAX_UV);
if (rc) {
dev_err(&data->i2c->dev,
"Regulator set failed vdd rc=%d\n",
rc);
goto reg_vdd_put;
}
}
data->vio = regulator_get(&data->i2c->dev, "vio");
if (IS_ERR(data->vio)) {
rc = PTR_ERR(data->vio);
dev_err(&data->i2c->dev,
"Regulator get failed vio rc=%d\n", rc);
goto reg_vdd_set;
}
if (regulator_count_voltages(data->vio) > 0) {
rc = regulator_set_voltage(data->vio,
AKM8963_VIO_MIN_UV, AKM8963_VIO_MAX_UV);
if (rc) {
dev_err(&data->i2c->dev,
"Regulator set failed vio rc=%d\n", rc);
goto reg_vio_put;
}
}
}
return 0;
reg_vio_put:
regulator_put(data->vio);
reg_vdd_set:
if (regulator_count_voltages(data->vdd) > 0)
regulator_set_voltage(data->vdd, 0, AKM8963_VDD_MAX_UV);
reg_vdd_put:
regulator_put(data->vdd);
return rc;
}
#ifdef CONFIG_OF
static int akm_compass_parse_dt(struct device *dev,
struct akm8963_platform_data *pdata)
{
struct device_node *np = dev->of_node;
u32 temp_val;
int rc;
rc = of_property_read_u32(np, "ak,layout", &temp_val);
if (rc && (rc != -EINVAL)) {
dev_err(dev, "Unable to read akm,layout\n");
return rc;
} else {
pdata->layout = temp_val;
}
if (of_property_read_bool(np, "ak,auto-report")) {
pdata->auto_report = 1;
pdata->use_int = 0;
} else {
pdata->auto_report = 0;
if (of_property_read_bool(dev->of_node, "ak,use-interrupt")) {
pdata->use_int = 1;
/* check gpio_int later, if it is invalid,
* just use poll */
pdata->gpio_int = of_get_named_gpio_flags(dev->of_node,
"ak,gpio-int", 0, &pdata->int_flags);
} else {
pdata->use_int = 0;
}
}
pdata->gpio_rstn = of_get_named_gpio_flags(dev->of_node,
"ak,gpio-rstn", 0, NULL);
return 0;
}
#else
static int akm_compass_parse_dt(struct device *dev,
struct akm8963_platform_data *pdata)
{
return -EINVAL;
}
#endif /* !CONFIG_OF */
static int akm8963_pinctrl_init(struct akm_compass_data *s_akm)
{
struct i2c_client *client = s_akm->i2c;
s_akm->pinctrl = devm_pinctrl_get(&client->dev);
if (IS_ERR_OR_NULL(s_akm->pinctrl)) {
dev_err(&client->dev, "Failed to get pinctrl\n");
return PTR_ERR(s_akm->pinctrl);
}
s_akm->pin_default = pinctrl_lookup_state(s_akm->pinctrl,
"ak8963_default");
if (IS_ERR_OR_NULL(s_akm->pin_default)) {
dev_err(&client->dev, "Failed to look up default state\n");
return PTR_ERR(s_akm->pin_default);
}
s_akm->pin_sleep = pinctrl_lookup_state(s_akm->pinctrl,
"ak8963_sleep");
if (IS_ERR_OR_NULL(s_akm->pin_sleep)) {
dev_err(&client->dev, "Failed to look up sleep state\n");
return PTR_ERR(s_akm->pin_sleep);
}
return 0;
}
static void akm_dev_poll(struct work_struct *work)
{
struct akm_compass_data *akm;
uint8_t dat_buf[AKM_SENSOR_DATA_SIZE];/* for GET_DATA */
int ret;
int mag_x, mag_y, mag_z;
int tmp;
akm = container_of((struct delayed_work *)work,
struct akm_compass_data, dwork);
ret = AKECS_GetData_Poll(akm, dat_buf, AKM_SENSOR_DATA_SIZE);
if (ret < 0) {
dev_warn(&s_akm->i2c->dev, "Get data failed\n");
goto exit;
}
tmp = 0xF & (dat_buf[7] + dat_buf[0]);
if (STATUS_ERROR(tmp)) {
dev_warn(&akm->i2c->dev, "Status error(0x%x). Reset...\n",
tmp);
AKECS_Reset(akm, 0);
goto exit;
}
tmp = (int)((int16_t)(dat_buf[2]<<8)+((int16_t)dat_buf[1]));
tmp = tmp * akm->sense_conf[0] / 256 + tmp / 2;
mag_x = tmp;
tmp = (int)((int16_t)(dat_buf[4]<<8)+((int16_t)dat_buf[3]));
tmp = tmp * akm->sense_conf[1] / 256 + tmp / 2;
mag_y = tmp;
tmp = (int)((int16_t)(dat_buf[6]<<8)+((int16_t)dat_buf[5]));
tmp = tmp * akm->sense_conf[2] / 256 + tmp / 2;
mag_z = tmp;
switch (akm->pdata->layout) {
case 0:
case 1:
/* Fall into the default direction */
break;
case 2:
tmp = mag_x;
mag_x = mag_y;
mag_y = -tmp;
break;
case 3:
mag_x = -mag_x;
mag_y = -mag_y;
break;
case 4:
tmp = mag_x;
mag_x = -mag_y;
mag_y = tmp;
break;
case 5:
mag_x = -mag_x;
mag_z = -mag_z;
break;
case 6:
tmp = mag_x;
mag_x = mag_y;
mag_y = tmp;
mag_z = -mag_z;
break;
case 7:
mag_y = -mag_y;
mag_z = -mag_z;
break;
case 8:
tmp = mag_x;
mag_x = -mag_y;
mag_y = -tmp;
mag_z = -mag_z;
break;
}
input_report_abs(akm->input, ABS_X, mag_x);
input_report_abs(akm->input, ABS_Y, mag_y);
input_report_abs(akm->input, ABS_Z, mag_z);
input_sync(akm->input);
dev_vdbg(&s_akm->i2c->dev,
"input report: mag_x=%02x, mag_y=%02x, mag_z=%02x",
mag_x, mag_y, mag_z);
exit:
ret = AKECS_SetMode(akm, AKM_MODE_SNG_MEASURE | AKM8963_BIT_OP_16);
if (ret < 0)
dev_warn(&akm->i2c->dev, "Failed to set mode\n");
if (akm->use_poll)
queue_delayed_work(akm->data_wq,
&akm->dwork,
msecs_to_jiffies(akm->delay[MAG_DATA_FLAG]));
}
int akm8963_compass_probe(
struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct akm8963_platform_data *pdata;
int err = 0;
int i;
dev_dbg(&i2c->dev, "start probing.");
if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) {
dev_err(&i2c->dev,
"%s: check_functionality failed.", __func__);
err = -ENODEV;
goto err_i2c_check;
}
/* Allocate memory for driver data */
s_akm = devm_kzalloc(&i2c->dev, sizeof(struct akm_compass_data),
GFP_KERNEL);
if (!s_akm) {
dev_err(&i2c->dev, "Failed to allocate driver data\n");
return -ENOMEM;
}
/***** I2C initialization *****/
s_akm->i2c = i2c;
/* set i2c data */
i2c_set_clientdata(i2c, s_akm);
/**** initialize variables in akm_compass_data *****/
init_waitqueue_head(&s_akm->drdy_wq);
init_waitqueue_head(&s_akm->open_wq);
mutex_init(&s_akm->sensor_mutex);
mutex_init(&s_akm->accel_mutex);
mutex_init(&s_akm->val_mutex);
mutex_init(&s_akm->op_mutex);
atomic_set(&s_akm->active, 0);
atomic_set(&s_akm->drdy, 0);
s_akm->is_busy = 0;
s_akm->enable_flag = 0;
/* Set to 1G in Android coordination, AKSC format */
s_akm->accel_data[0] = 0;
s_akm->accel_data[1] = 0;
s_akm->accel_data[2] = 720;
for (i = 0; i < AKM_NUM_SENSORS; i++)
s_akm->delay[i] = 0;
if (i2c->dev.of_node) {
pdata = devm_kzalloc(
&i2c->dev,
sizeof(struct akm8963_platform_data),
GFP_KERNEL);
if (!pdata) {
dev_err(&i2c->dev, "Failed to allcated memory\n");
err = -ENOMEM;
goto err_devm;
}
err = akm_compass_parse_dt(&i2c->dev, pdata);
if (err) {
dev_err(
&i2c->dev,
"Unable to parse platfrom data err=%d\n",
err);
goto err_devm;
}
} else {
if (i2c->dev.platform_data) {
/* Copy platform data to local. */
pdata = i2c->dev.platform_data;
} else {
/* Platform data is not available.
Layout and information should be
set by each application. */
s_akm->pdata->layout = 0;
s_akm->gpio_rstn = 0;
dev_warn(&i2c->dev, "%s: No platform data.",
__func__);
}
}
s_akm->pdata = pdata;
/* check connection */
err = akm_compass_power_init(s_akm, true);
if (err < 0)
goto err_devm;
err = akm_compass_power_set(s_akm, true);
if (err < 0)
goto err_compass_pwr_init;
/* Pull up the reset pin */
AKECS_Reset(s_akm, 1);
err = akm8963_i2c_check_device(i2c);
if (err < 0)
goto err_compass_pwr_off;
/***** input *****/
err = akm_compass_input_init(&s_akm->input);
if (err) {
dev_err(&i2c->dev,
"%s: input_dev register failed", __func__);
goto err_compass_pwr_off;
}
input_set_drvdata(s_akm->input, s_akm);
/* initialize pinctrl */
if (!akm8963_pinctrl_init(s_akm)) {
err = pinctrl_select_state(s_akm->pinctrl, s_akm->pin_default);
if (err) {
dev_err(&i2c->dev, "Can't select pinctrl state\n");
goto err_unregister_device;
}
}
s_akm->data_wq = NULL;
if ((s_akm->pdata->use_int) &&
gpio_is_valid(s_akm->pdata->gpio_int)) {
s_akm->use_poll = false;
/* configure interrupt gpio */
err = gpio_request(s_akm->pdata->gpio_int,
"akm8963_gpio_int");
if (err) {
dev_err(
&i2c->dev,
"Unable to request interrupt gpio %d\n",
s_akm->pdata->gpio_int);
goto err_unregister_device;
}
err = gpio_direction_input(s_akm->pdata->gpio_int);
if (err) {
dev_err(
&i2c->dev,
"Unable to set direction for gpio %d\n",
s_akm->pdata->gpio_int);
goto err_gpio_free;
}
i2c->irq = gpio_to_irq(s_akm->pdata->gpio_int);
/***** IRQ setup *****/
s_akm->i2c->irq = i2c->irq;
dev_dbg(&i2c->dev, "%s: IRQ is #%d.",
__func__, s_akm->i2c->irq);
err = request_threaded_irq(
s_akm->i2c->irq,
NULL,
akm_compass_irq,
IRQF_TRIGGER_HIGH|IRQF_ONESHOT,
dev_name(&i2c->dev),
s_akm);
if (err) {
dev_err(&i2c->dev,
"%s: request irq failed.", __func__);
goto err_gpio_free;
}
} else if (s_akm->pdata->auto_report) {
s_akm->use_poll = true;
s_akm->data_wq =
create_freezable_workqueue("akm8963_data_work");
if (!s_akm->data_wq) {
dev_err(&i2c->dev, "Cannot create workqueue!\n");
goto err_unregister_device;
}
INIT_DELAYED_WORK(&s_akm->dwork, akm_dev_poll);
}
/***** sysfs *****/
err = create_sysfs_interfaces(s_akm);
if (0 > err) {
dev_err(&i2c->dev,
"%s: create sysfs failed.", __func__);
goto err_destroy_workqueue;
}
s_akm->cdev = sensors_cdev;
s_akm->cdev.sensors_enable = akm_enable_set;
s_akm->cdev.sensors_poll_delay = akm_poll_delay_set;
s_akm->delay[MAG_DATA_FLAG] = sensors_cdev.delay_msec;
err = sensors_classdev_register(&i2c->dev, &s_akm->cdev);
if (err) {
dev_err(&i2c->dev, "class device create failed: %d\n", err);
goto remove_sysfs;
}
err = akm_compass_power_set(s_akm, false);
if (err)
dev_err(&i2c->dev,
"Fail to disable power after probe: %d\n", err);
dev_info(&i2c->dev, "successfully probed.");
return 0;
remove_sysfs:
remove_sysfs_interfaces(s_akm);
err_destroy_workqueue:
if (s_akm->data_wq)
destroy_workqueue(s_akm->data_wq);
if (s_akm->i2c->irq)
free_irq(s_akm->i2c->irq, s_akm);
err_gpio_free:
if ((s_akm->pdata->use_int) &&
(gpio_is_valid(s_akm->pdata->gpio_int)))
gpio_free(s_akm->pdata->gpio_int);
err_unregister_device:
input_unregister_device(s_akm->input);
err_compass_pwr_off:
akm_compass_power_set(s_akm, false);
err_compass_pwr_init:
akm_compass_power_init(s_akm, false);
err_devm:
devm_kfree(&i2c->dev, s_akm);
err_i2c_check:
return err;
}
static int akm8963_compass_remove(struct i2c_client *i2c)
{
struct akm_compass_data *akm = i2c_get_clientdata(i2c);
if (akm_compass_power_set(akm, false))
dev_err(&i2c->dev, "power off failed.\n");
if (akm_compass_power_init(akm, false))
dev_err(&i2c->dev, "power deinit failed.\n");
remove_sysfs_interfaces(akm);
if (akm->data_wq)
destroy_workqueue(s_akm->data_wq);
if (akm->i2c->irq)
free_irq(akm->i2c->irq, akm);
if ((s_akm->pdata->use_int) &&
(gpio_is_valid(s_akm->pdata->gpio_int)))
gpio_free(s_akm->pdata->gpio_int);
input_unregister_device(akm->input);
devm_kfree(&i2c->dev, akm);
dev_info(&i2c->dev, "successfully removed.");
return 0;
}
static const struct i2c_device_id akm8963_compass_id[] = {
{AKM_I2C_NAME, 0 },
{ }
};
static const struct dev_pm_ops akm_compass_pm_ops = {
.suspend = akm_compass_suspend,
.resume = akm_compass_resume,
};
static struct of_device_id akm8963_match_table[] = {
{ .compatible = "ak,ak8963", },
{ .compatible = "akm,akm8963", },
{ },
};
static struct i2c_driver akm_compass_driver = {
.probe = akm8963_compass_probe,
.remove = akm8963_compass_remove,
.id_table = akm8963_compass_id,
.driver = {
.name = AKM_I2C_NAME,
.owner = THIS_MODULE,
.of_match_table = akm8963_match_table,
.pm = &akm_compass_pm_ops,
},
};
static int __init akm_compass_init(void)
{
pr_info("AKM compass driver: initialize.");
return i2c_add_driver(&akm_compass_driver);
}
static void __exit akm_compass_exit(void)
{
pr_info("AKM compass driver: release.");
i2c_del_driver(&akm_compass_driver);
}
module_init(akm_compass_init);
module_exit(akm_compass_exit);
MODULE_AUTHOR("viral wang <viral_wang@htc.com>");
MODULE_DESCRIPTION("AKM compass driver");
MODULE_LICENSE("GPL");