| /* drivers/misc/akm09911.c - akm09911 compass driver |
| * |
| * Copyright (c) 2014-2015, 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/akm09911.h> |
| #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> |
| |
| #define AKM_DEBUG_IF 0 |
| #define AKM_HAS_RESET 1 |
| #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 AKM09911_VDD_MIN_UV 2000000 |
| #define AKM09911_VDD_MAX_UV 3300000 |
| #define AKM09911_VIO_MIN_UV 1750000 |
| #define AKM09911_VIO_MAX_UV 1950000 |
| |
| #define STATUS_ERROR(st) (((st)&0x08) != 0x0) |
| |
| #define AKM09911_RETRY_COUNT 10 |
| |
| enum { |
| AKM09911_AXIS_X = 0, |
| AKM09911_AXIS_Y, |
| AKM09911_AXIS_Z, |
| AKM09911_AXIS_COUNT, |
| }; |
| |
| /* 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 delayed_work dwork; |
| struct workqueue_struct *work_queue; |
| 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]; |
| |
| struct mutex val_mutex; |
| uint32_t enable_flag; |
| int64_t delay[AKM_NUM_SENSORS]; |
| |
| atomic_t active; |
| atomic_t drdy; |
| |
| char layout; |
| int irq; |
| int gpio_rstn; |
| int power_enabled; |
| int auto_report; |
| int use_hrtimer; |
| |
| /* The input event last time */ |
| int last_x; |
| int last_y; |
| int last_z; |
| |
| /* dummy value to avoid sensor event get eaten */ |
| int rep_cnt; |
| |
| struct regulator *vdd; |
| struct regulator *vio; |
| struct akm_sensor_state state; |
| struct hrtimer poll_timer; |
| }; |
| |
| static struct sensors_classdev sensors_cdev = { |
| .name = "akm09911-mag", |
| .vendor = "Asahi Kasei Microdevices Corporation", |
| .version = 1, |
| .handle = SENSORS_MAGNETIC_FIELD_HANDLE, |
| .type = SENSOR_TYPE_MAGNETIC_FIELD, |
| .max_range = "1228.8", |
| .resolution = "0.6", |
| .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); |
| /* 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); |
| 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); |
| } |
| 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); |
| 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 (mode & 0x1F) { |
| case AKM_MODE_SNG_MEASURE: |
| case AKM_MODE_SELF_TEST: |
| case AKM_MODE_FUSE_ACCESS: |
| case AKM_MODE_CONTINUOUS_10HZ: |
| case AKM_MODE_CONTINUOUS_20HZ: |
| case AKM_MODE_CONTINUOUS_50HZ: |
| case AKM_MODE_CONTINUOUS_100HZ: |
| 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; |
| } |
| |
| 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]); |
| input_report_abs(akm->input, ABS_MISC, rbuf[8]); |
| } |
| /* 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 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, "%s failed.", __func__); |
| return err; |
| } |
| |
| /* Check ST bit */ |
| if (!(AKM_DRDY_IS_HIGH(buffer[0]))) |
| dev_dbg(&akm->i2c->dev, "DRDY is low. Use last value.\n"); |
| |
| /* Data is over run is */ |
| if (AKM_DOR_IS_HIGH(buffer[0])) |
| dev_dbg(&akm->i2c->dev, "Data over run!\n"); |
| |
| memcpy(rbuf, buffer, size); |
| atomic_set(&akm->drdy, 0); |
| |
| 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 */ |
| int64_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->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->layout, sizeof(akm->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, |
| }; |
| |
| static struct miscdevice akm_compass_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = AKM_MISCDEV_NAME, |
| .fops = &AKECS_fops, |
| }; |
| |
| /***** 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 inline uint8_t akm_select_frequency(int64_t delay_ns) |
| { |
| if (delay_ns >= 100000000LL) |
| return AKM_MODE_CONTINUOUS_10HZ; |
| else if (delay_ns >= 50000000LL) |
| return AKM_MODE_CONTINUOUS_20HZ; |
| else if (delay_ns >= 20000000LL) |
| return AKM_MODE_CONTINUOUS_50HZ; |
| else |
| return AKM_MODE_CONTINUOUS_100HZ; |
| } |
| |
| 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); |
| uint8_t mode; |
| |
| 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, |
| "Fail to power on the device!\n"); |
| goto exit; |
| } |
| |
| if (akm->auto_report) { |
| mode = akm_select_frequency(akm->delay[MAG_DATA_FLAG]); |
| AKECS_SetMode(akm, mode); |
| if (akm->use_hrtimer) |
| hrtimer_start(&akm->poll_timer, |
| ns_to_ktime(akm->delay[MAG_DATA_FLAG]), |
| HRTIMER_MODE_REL); |
| else |
| queue_delayed_work(akm->work_queue, &akm->dwork, |
| (unsigned long)nsecs_to_jiffies64( |
| akm->delay[MAG_DATA_FLAG])); |
| } |
| } else { |
| if (akm->auto_report) { |
| if (akm->use_hrtimer) { |
| hrtimer_cancel(&akm->poll_timer); |
| cancel_work_sync(&akm->dwork.work); |
| } else { |
| cancel_delayed_work_sync(&akm->dwork); |
| } |
| AKECS_SetMode(akm, AKM_MODE_POWERDOWN); |
| } |
| ret = akm_compass_power_set(akm, false); |
| if (ret) { |
| dev_err(&akm->i2c->dev, |
| "Fail to power off the device!\n"); |
| 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); |
| uint8_t mode; |
| int ret; |
| |
| mutex_lock(&akm->val_mutex); |
| |
| akm->delay[MAG_DATA_FLAG] = delay_msec * 1000000; |
| mode = akm_select_frequency(akm->delay[MAG_DATA_FLAG]); |
| ret = AKECS_SetMode(akm, mode); |
| if (ret < 0) |
| dev_err(&akm->i2c->dev, "Failed to set to mode(%x)\n", mode); |
| |
| mutex_unlock(&akm->val_mutex); |
| return ret; |
| } |
| |
| static ssize_t akm_compass_sysfs_delay_show( |
| struct akm_compass_data *akm, char *buf, int pos) |
| { |
| int64_t val; |
| |
| mutex_lock(&akm->val_mutex); |
| val = akm->delay[pos]; |
| mutex_unlock(&akm->val_mutex); |
| |
| return scnprintf(buf, PAGE_SIZE, "%lld\n", val); |
| } |
| |
| static ssize_t akm_compass_sysfs_delay_store( |
| struct akm_compass_data *akm, char const *buf, size_t count, int pos) |
| { |
| long long val = 0; |
| |
| if (NULL == buf) |
| return -EINVAL; |
| |
| if (0 == count) |
| return 0; |
| |
| if (strict_strtoll(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); |
| /* Accelerometer (720 x 16G)*/ |
| input_set_abs_params(*input, ABS_X, |
| -11520, 11520, 0, 0); |
| input_set_abs_params(*input, ABS_Y, |
| -11520, 11520, 0, 0); |
| input_set_abs_params(*input, ABS_Z, |
| -11520, 11520, 0, 0); |
| input_set_abs_params(*input, ABS_RX, |
| 0, 3, 0, 0); |
| /* Magnetic field (limited to 16bit) */ |
| input_set_abs_params(*input, ABS_RY, |
| -32768, 32767, 0, 0); |
| input_set_abs_params(*input, ABS_RZ, |
| -32768, 32767, 0, 0); |
| input_set_abs_params(*input, ABS_THROTTLE, |
| -32768, 32767, 0, 0); |
| input_set_abs_params(*input, ABS_RUDDER, |
| 0, 3, 0, 0); |
| |
| /* Orientation (degree in Q6 format) */ |
| /* yaw[0,360) pitch[-180,180) roll[-90,90) */ |
| input_set_abs_params(*input, ABS_HAT0Y, |
| 0, 23040, 0, 0); |
| input_set_abs_params(*input, ABS_HAT1X, |
| -11520, 11520, 0, 0); |
| input_set_abs_params(*input, ABS_HAT1Y, |
| -5760, 5760, 0, 0); |
| /* Rotation Vector [-1,+1] in Q14 format */ |
| input_set_abs_params(*input, ABS_TILT_X, |
| -16384, 16384, 0, 0); |
| input_set_abs_params(*input, ABS_TILT_Y, |
| -16384, 16384, 0, 0); |
| input_set_abs_params(*input, ABS_TOOL_WIDTH, |
| -16384, 16384, 0, 0); |
| input_set_abs_params(*input, ABS_VOLUME, |
| -16384, 16384, 0, 0); |
| |
| /* Report the dummy value */ |
| input_set_abs_params(*input, ABS_MISC, |
| INT_MIN, INT_MAX, 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."); |
| 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); |
| |
| 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->auto_report) { |
| if (akm->use_hrtimer) |
| hrtimer_cancel(&akm->poll_timer); |
| else |
| cancel_delayed_work_sync(&akm->dwork); |
| } |
| |
| ret = AKECS_SetMode(akm, AKM_MODE_POWERDOWN); |
| if (ret) |
| dev_warn(&akm->i2c->dev, "Failed to set to POWERDOWN mode.\n"); |
| |
| akm->state.power_on = akm->power_enabled; |
| if (akm->state.power_on) |
| akm_compass_power_set(akm, false); |
| |
| 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 ret; |
| } |
| |
| static int akm_compass_resume(struct device *dev) |
| { |
| struct akm_compass_data *akm = dev_get_drvdata(dev); |
| int ret = 0; |
| uint8_t mode; |
| |
| 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; |
| } |
| |
| if (AKM_IS_MAG_DATA_ENABLED() && akm->auto_report) { |
| mode = akm_select_frequency(akm->delay[MAG_DATA_FLAG]); |
| ret = AKECS_SetMode(akm, mode); |
| if (ret < 0) { |
| dev_err(&akm->i2c->dev, "Failed to set to mode(%d)\n", |
| mode); |
| goto exit; |
| } |
| if (akm->use_hrtimer) |
| hrtimer_start(&akm->poll_timer, |
| ns_to_ktime(akm->delay[MAG_DATA_FLAG]), |
| HRTIMER_MODE_REL); |
| else |
| queue_delayed_work(akm->work_queue, &akm->dwork, |
| (unsigned long)nsecs_to_jiffies64( |
| akm->delay[MAG_DATA_FLAG])); |
| } |
| } |
| |
| dev_dbg(&akm->i2c->dev, "resumed\n"); |
| |
| exit: |
| return ret; |
| } |
| |
| static int akm09911_i2c_check_device( |
| struct i2c_client *client) |
| { |
| /* AK09911 specific function */ |
| struct akm_compass_data *akm = i2c_get_clientdata(client); |
| int err; |
| |
| akm->sense_info[0] = AK09911_REG_WIA1; |
| 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, AK09911_MODE_FUSE_ACCESS); |
| if (err < 0) |
| return err; |
| |
| akm->sense_conf[0] = AK09911_FUSE_ASAX; |
| err = akm_i2c_rxdata(client, akm->sense_conf, AKM_SENSOR_CONF_SIZE); |
| if (err < 0) |
| return err; |
| |
| err = AKECS_SetMode(akm, AK09911_MODE_POWERDOWN); |
| if (err < 0) |
| return err; |
| |
| /* Check read data */ |
| if ((akm->sense_info[0] != AK09911_WIA1_VALUE) || |
| (akm->sense_info[1] != AK09911_WIA2_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, |
| AKM09911_VDD_MAX_UV); |
| |
| regulator_put(data->vdd); |
| |
| if (regulator_count_voltages(data->vio) > 0) |
| regulator_set_voltage(data->vio, 0, |
| AKM09911_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, |
| AKM09911_VDD_MIN_UV, AKM09911_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, |
| AKM09911_VIO_MIN_UV, AKM09911_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, AKM09911_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 akm_compass_data *akm) |
| { |
| struct device_node *np = dev->of_node; |
| u32 temp_val; |
| int rc; |
| |
| rc = of_property_read_u32(np, "akm,layout", &temp_val); |
| if (rc && (rc != -EINVAL)) { |
| dev_err(dev, "Unable to read akm,layout\n"); |
| return rc; |
| } else { |
| akm->layout = temp_val; |
| } |
| |
| akm->auto_report = of_property_read_bool(np, "akm,auto-report"); |
| akm->use_hrtimer = of_property_read_bool(np, "akm,use-hrtimer"); |
| akm->gpio_rstn = of_get_named_gpio_flags(dev->of_node, |
| "akm,gpio_rstn", 0, NULL); |
| |
| if (!gpio_is_valid(akm->gpio_rstn)) { |
| dev_err(dev, "gpio reset pin %d is invalid.\n", |
| akm->gpio_rstn); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| #else |
| static int akm_compass_parse_dt(struct device *dev, |
| struct akm_compass_data *akm) |
| { |
| return -EINVAL; |
| } |
| #endif /* !CONFIG_OF */ |
| |
| static int akm_pinctrl_init(struct akm_compass_data *akm) |
| { |
| struct i2c_client *client = akm->i2c; |
| |
| akm->pinctrl = devm_pinctrl_get(&client->dev); |
| if (IS_ERR_OR_NULL(akm->pinctrl)) { |
| dev_err(&client->dev, "Failed to get pinctrl\n"); |
| return PTR_ERR(akm->pinctrl); |
| } |
| |
| akm->pin_default = pinctrl_lookup_state(akm->pinctrl, "default"); |
| if (IS_ERR_OR_NULL(akm->pin_default)) { |
| dev_err(&client->dev, "Failed to look up default state\n"); |
| return PTR_ERR(akm->pin_default); |
| } |
| |
| akm->pin_sleep = pinctrl_lookup_state(akm->pinctrl, "sleep"); |
| if (IS_ERR_OR_NULL(akm->pin_sleep)) { |
| dev_err(&client->dev, "Failed to look up sleep state\n"); |
| return PTR_ERR(akm->pin_sleep); |
| } |
| |
| return 0; |
| } |
| |
| static int akm_report_data(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; |
| |
| ret = AKECS_GetData_Poll(akm, dat_buf, AKM_SENSOR_DATA_SIZE); |
| if (ret) { |
| dev_err(&akm->i2c->dev, "Get data failed.\n"); |
| return -EIO; |
| } |
| |
| if (STATUS_ERROR(dat_buf[8])) { |
| dev_warn(&akm->i2c->dev, "Status error. Reset...\n"); |
| AKECS_Reset(akm, 0); |
| return -EIO; |
| } |
| |
| tmp = (int)((int16_t)(dat_buf[2]<<8)+((int16_t)dat_buf[1])); |
| tmp = tmp * akm->sense_conf[0] / 128 + tmp; |
| mag_x = tmp; |
| |
| tmp = (int)((int16_t)(dat_buf[4]<<8)+((int16_t)dat_buf[3])); |
| tmp = tmp * akm->sense_conf[1] / 128 + tmp; |
| mag_y = tmp; |
| |
| tmp = (int)((int16_t)(dat_buf[6]<<8)+((int16_t)dat_buf[5])); |
| tmp = tmp * akm->sense_conf[2] / 128 + tmp; |
| mag_z = tmp; |
| |
| dev_dbg(&akm->i2c->dev, "mag_x:%d mag_y:%d mag_z:%d\n", |
| mag_x, mag_y, mag_z); |
| dev_dbg(&akm->i2c->dev, "raw data: %d %d %d %d %d %d %d %d\n", |
| dat_buf[0], dat_buf[1], dat_buf[2], dat_buf[3], |
| dat_buf[4], dat_buf[5], dat_buf[6], dat_buf[7]); |
| dev_dbg(&akm->i2c->dev, "asa: %d %d %d\n", akm->sense_conf[0], |
| akm->sense_conf[1], akm->sense_conf[2]); |
| |
| switch (akm->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); |
| |
| /* avoid eaten by input subsystem framework */ |
| if ((mag_x == akm->last_x) && (mag_y == akm->last_y) && |
| (mag_z == akm->last_z)) |
| input_report_abs(akm->input, ABS_MISC, akm->rep_cnt++); |
| |
| akm->last_x = mag_x; |
| akm->last_y = mag_y; |
| akm->last_z = mag_z; |
| |
| input_sync(akm->input); |
| |
| return 0; |
| } |
| |
| static void akm_dev_poll(struct work_struct *work) |
| { |
| struct akm_compass_data *akm; |
| int ret; |
| |
| akm = container_of((struct delayed_work *)work, |
| struct akm_compass_data, dwork); |
| |
| ret = akm_report_data(akm); |
| if (ret < 0) |
| dev_warn(&akm->i2c->dev, "Failed to report data\n"); |
| |
| if (!akm->use_hrtimer) |
| queue_delayed_work(akm->work_queue, &akm->dwork, |
| (unsigned long)nsecs_to_jiffies64( |
| akm->delay[MAG_DATA_FLAG])); |
| } |
| |
| static enum hrtimer_restart akm_timer_func(struct hrtimer *timer) |
| { |
| struct akm_compass_data *akm; |
| |
| akm = container_of(timer, struct akm_compass_data, poll_timer); |
| |
| queue_work(akm->work_queue, &akm->dwork.work); |
| hrtimer_forward_now(&akm->poll_timer, |
| ns_to_ktime(akm->delay[MAG_DATA_FLAG])); |
| |
| return HRTIMER_RESTART; |
| } |
| |
| static int case_test(struct akm_compass_data *akm, const char test_name[], |
| const int testdata, const int lo_limit, const int hi_limit, |
| int *fail_total) |
| { |
| /* Pass:0, Fail:-1 */ |
| int result = 0; |
| |
| if (fail_total == NULL) |
| return -EINVAL; |
| |
| if (strcmp(test_name, "START") == 0) { |
| dev_dbg(&akm->i2c->dev, "----------------------------------------------------------\n"); |
| dev_dbg(&akm->i2c->dev, "Test Name Fail Test Data [ Low High]\n"); |
| dev_dbg(&akm->i2c->dev, "----------------------------------------------------------\n"); |
| } else if (strcmp(test_name, "END") == 0) { |
| dev_dbg(&akm->i2c->dev, "----------------------------------------------------------\n"); |
| if (*fail_total == 0) |
| dev_dbg(&akm->i2c->dev, "Factory shipment test passed.\n\n"); |
| else |
| dev_dbg(&akm->i2c->dev, "%d test cases failed.\n\n", |
| *fail_total); |
| } else { |
| if ((testdata < lo_limit) || (testdata > hi_limit)) { |
| result = -1; |
| *fail_total += 1; |
| } |
| |
| dev_dbg(&akm->i2c->dev, " %-10s %c %9d [%9d %9d]\n", |
| test_name, ((result == 0) ? ('.') : ('F')), |
| testdata, lo_limit, hi_limit); |
| } |
| |
| return result; |
| } |
| |
| static int akm_self_test(struct sensors_classdev *sensors_cdev) |
| { |
| struct akm_compass_data *akm = container_of(sensors_cdev, |
| struct akm_compass_data, cdev); |
| uint8_t i2c_data[AKM_SENSOR_DATA_SIZE]; |
| int hdata[AKM09911_AXIS_COUNT]; |
| int asax, asay, asaz; |
| int count; |
| int ret; |
| int fail_total = 0; |
| uint8_t mode; |
| bool power_enabled = akm->power_enabled ? true : false; |
| |
| mutex_lock(&akm->op_mutex); |
| |
| asax = akm->sense_conf[AKM09911_AXIS_X]; |
| asay = akm->sense_conf[AKM09911_AXIS_Y]; |
| asaz = akm->sense_conf[AKM09911_AXIS_Z]; |
| |
| if (!power_enabled) { |
| ret = akm_compass_power_set(akm, true); |
| if (ret) { |
| dev_err(&akm->i2c->dev, "Power up failed.\n"); |
| goto exit; |
| } |
| } else { |
| i2c_data[0] = AKM_REG_MODE; |
| ret = akm_i2c_rxdata(akm->i2c, i2c_data, 1); |
| if (ret < 0) { |
| dev_err(&akm->i2c->dev, "Get mode failed.\n"); |
| goto exit; |
| } |
| mode = i2c_data[1]; |
| } |
| |
| ret = AKECS_Reset(akm, 0); |
| if (ret < 0) { |
| dev_err(&akm->i2c->dev, "Reset failed.\n"); |
| goto exit; |
| } |
| |
| /* start test */ |
| case_test(akm, "START", 0, 0, 0, &fail_total); |
| |
| case_test(akm, TLIMIT_TN_ASAX_09911, asax, TLIMIT_LO_ASAX_09911, |
| TLIMIT_HI_ASAX_09911, &fail_total); |
| case_test(akm, TLIMIT_TN_ASAY_09911, asay, TLIMIT_LO_ASAY_09911, |
| TLIMIT_HI_ASAY_09911, &fail_total); |
| case_test(akm, TLIMIT_TN_ASAZ_09911, asaz, TLIMIT_LO_ASAZ_09911, |
| TLIMIT_HI_ASAZ_09911, &fail_total); |
| |
| ret = AKECS_SetMode(akm, AK09911_MODE_SNG_MEASURE); |
| if (ret < 0) { |
| dev_err(&akm->i2c->dev, "Set to single measurement failed.\n"); |
| goto exit; |
| } |
| |
| count = AKM09911_RETRY_COUNT; |
| do { |
| /* The typical time for single measurement is 7.2ms */ |
| ret = AKECS_GetData_Poll(akm, i2c_data, AKM_SENSOR_DATA_SIZE); |
| if (ret == -EAGAIN) |
| usleep_range(1000, 10000); |
| } while ((ret == -EAGAIN) && (--count)); |
| |
| if (!count) { |
| dev_err(&akm->i2c->dev, "Timeout get valid data.\n"); |
| goto exit; |
| } |
| |
| hdata[AKM09911_AXIS_X] = (s16)(i2c_data[1] | (i2c_data[2] << 8)); |
| hdata[AKM09911_AXIS_Y] = (s16)(i2c_data[3] | (i2c_data[4] << 8)); |
| hdata[AKM09911_AXIS_Z] = (s16)(i2c_data[5] | (i2c_data[6] << 8)); |
| |
| i2c_data[0] &= 0x7F; |
| case_test(akm, TLIMIT_TN_SNG_ST1_09911, |
| (int)i2c_data[0], TLIMIT_LO_SNG_ST1_09911, |
| TLIMIT_HI_SNG_ST1_09911, &fail_total); |
| |
| case_test(akm, TLIMIT_TN_SNG_HX_09911, hdata[0], TLIMIT_LO_SNG_HX_09911, |
| TLIMIT_HI_SNG_HX_09911, &fail_total); |
| case_test(akm, TLIMIT_TN_SNG_HY_09911, hdata[1], TLIMIT_LO_SNG_HY_09911, |
| TLIMIT_HI_SNG_HY_09911, &fail_total); |
| case_test(akm, TLIMIT_TN_SNG_HZ_09911, hdata[2], TLIMIT_LO_SNG_HZ_09911, |
| TLIMIT_HI_SNG_HZ_09911, &fail_total); |
| |
| case_test(akm, TLIMIT_TN_SNG_ST2_09911, (int)i2c_data[8], |
| TLIMIT_LO_SNG_ST2_09911, TLIMIT_HI_SNG_ST2_09911, |
| &fail_total); |
| |
| /* self-test mode */ |
| ret = AKECS_SetMode(akm, AK09911_MODE_SELF_TEST); |
| if (ret < 0) { |
| dev_err(&akm->i2c->dev, "Set to self test mode failed\n"); |
| goto exit; |
| } |
| |
| count = AKM09911_RETRY_COUNT; |
| do { |
| /* The typical time for single measurement is 7.2ms */ |
| ret = AKECS_GetData_Poll(akm, i2c_data, AKM_SENSOR_DATA_SIZE); |
| if (ret == -EAGAIN) |
| usleep_range(1000, 10000); |
| } while ((ret == -EAGAIN) && (--count)); |
| |
| if (!count) { |
| dev_err(&akm->i2c->dev, "Timeout get valid data.\n"); |
| goto exit; |
| } |
| |
| i2c_data[0] &= 0x7F; |
| |
| case_test(akm, TLIMIT_TN_SLF_ST1_09911, (int)i2c_data[0], |
| TLIMIT_LO_SLF_ST1_09911, TLIMIT_HI_SLF_ST1_09911, |
| &fail_total); |
| |
| hdata[AKM09911_AXIS_X] = (s16)(i2c_data[1] | (i2c_data[2] << 8)); |
| hdata[AKM09911_AXIS_Y] = (s16)(i2c_data[3] | (i2c_data[4] << 8)); |
| hdata[AKM09911_AXIS_Z] = (s16)(i2c_data[5] | (i2c_data[6] << 8)); |
| |
| case_test(akm, TLIMIT_TN_SLF_RVHX_09911, (hdata[0])*(asax/128 + 1), |
| TLIMIT_LO_SLF_RVHX_09911, TLIMIT_HI_SLF_RVHX_09911, |
| &fail_total); |
| |
| case_test(akm, TLIMIT_TN_SLF_RVHY_09911, (hdata[1])*(asay/128 + 1), |
| TLIMIT_LO_SLF_RVHY_09911, TLIMIT_HI_SLF_RVHY_09911, |
| &fail_total); |
| |
| case_test(akm, TLIMIT_TN_SLF_RVHZ_09911, (hdata[2])*(asaz/128 + 1), |
| TLIMIT_LO_SLF_RVHZ_09911, TLIMIT_HI_SLF_RVHZ_09911, |
| &fail_total); |
| |
| case_test(akm, TLIMIT_TN_SLF_ST2_09911, (int)i2c_data[8], |
| TLIMIT_LO_SLF_ST2_09911, TLIMIT_HI_SLF_ST2_09911, |
| &fail_total); |
| |
| case_test(akm, "END", 0, 0, 0, &fail_total); |
| /* clean up */ |
| if (!power_enabled) { |
| ret = akm_compass_power_set(akm, false); |
| if (ret) { |
| dev_err(&akm->i2c->dev, "Power down failed.\n"); |
| goto exit; |
| } |
| } else { |
| /* Set measure mode */ |
| i2c_data[0] = AKM_REG_MODE; |
| i2c_data[1] = mode; |
| ret = akm_i2c_txdata(akm->i2c, i2c_data, 2); |
| if (ret < 0) { |
| dev_err(&akm->i2c->dev, "restore mode failed\n"); |
| goto exit; |
| } |
| } |
| |
| exit: |
| mutex_unlock(&akm->op_mutex); |
| return ((fail_total > 0) || ret) ? -EIO : 0; |
| } |
| |
| int akm_compass_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| struct akm09911_platform_data *pdata; |
| int err = 0; |
| int i; |
| |
| dev_dbg(&client->dev, "start probing."); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(&client->dev, |
| "%s: check_functionality failed.", __func__); |
| err = -ENODEV; |
| goto exit0; |
| } |
| |
| /* Allocate memory for driver data */ |
| s_akm = kzalloc(sizeof(struct akm_compass_data), GFP_KERNEL); |
| if (!s_akm) { |
| dev_err(&client->dev, |
| "%s: memory allocation failed.", __func__); |
| err = -ENOMEM; |
| goto exit1; |
| } |
| |
| /**** 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->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] = -1; |
| |
| if (client->dev.of_node) { |
| err = akm_compass_parse_dt(&client->dev, s_akm); |
| if (err) { |
| dev_err(&client->dev, |
| "Unable to parse platfrom data err=%d\n", err); |
| goto exit2; |
| } |
| } else { |
| if (client->dev.platform_data) { |
| /* Copy platform data to local. */ |
| pdata = client->dev.platform_data; |
| s_akm->layout = pdata->layout; |
| s_akm->gpio_rstn = pdata->gpio_RSTN; |
| } else { |
| /* Platform data is not available. |
| Layout and information should be set by each application. */ |
| s_akm->layout = 0; |
| s_akm->gpio_rstn = 0; |
| dev_warn(&client->dev, "%s: No platform data.", |
| __func__); |
| } |
| } |
| |
| /***** I2C initialization *****/ |
| s_akm->i2c = client; |
| /* set client data */ |
| i2c_set_clientdata(client, s_akm); |
| |
| /* initialize pinctrl */ |
| if (!akm_pinctrl_init(s_akm)) { |
| err = pinctrl_select_state(s_akm->pinctrl, s_akm->pin_default); |
| if (err) { |
| dev_err(&client->dev, "Can't select pinctrl state\n"); |
| goto exit2; |
| } |
| } |
| |
| /* Pull up the reset pin */ |
| AKECS_Reset(s_akm, 1); |
| |
| /* check connection */ |
| err = akm_compass_power_init(s_akm, 1); |
| if (err < 0) |
| goto exit2; |
| err = akm_compass_power_set(s_akm, 1); |
| if (err < 0) |
| goto exit3; |
| |
| err = akm09911_i2c_check_device(client); |
| if (err < 0) |
| goto exit4; |
| |
| /***** input *****/ |
| err = akm_compass_input_init(&s_akm->input); |
| if (err) { |
| dev_err(&client->dev, |
| "%s: input_dev register failed", __func__); |
| goto exit4; |
| } |
| input_set_drvdata(s_akm->input, s_akm); |
| |
| /***** IRQ setup *****/ |
| s_akm->irq = client->irq; |
| |
| dev_dbg(&client->dev, "%s: IRQ is #%d.", |
| __func__, s_akm->irq); |
| |
| if (s_akm->irq) { |
| err = request_threaded_irq( |
| s_akm->irq, |
| NULL, |
| akm_compass_irq, |
| IRQF_TRIGGER_HIGH|IRQF_ONESHOT, |
| dev_name(&client->dev), |
| s_akm); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "%s: request irq failed.", __func__); |
| goto exit5; |
| } |
| } else if (s_akm->auto_report) { |
| if (s_akm->use_hrtimer) { |
| hrtimer_init(&s_akm->poll_timer, CLOCK_MONOTONIC, |
| HRTIMER_MODE_REL); |
| s_akm->poll_timer.function = akm_timer_func; |
| s_akm->work_queue = alloc_workqueue("akm_poll_work", |
| WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI, 1); |
| INIT_WORK(&s_akm->dwork.work, akm_dev_poll); |
| } else { |
| s_akm->work_queue = alloc_workqueue("akm_poll_work", |
| WQ_NON_REENTRANT, 0); |
| INIT_DELAYED_WORK(&s_akm->dwork, akm_dev_poll); |
| } |
| } |
| |
| /***** misc *****/ |
| err = misc_register(&akm_compass_dev); |
| if (err) { |
| dev_err(&client->dev, |
| "%s: akm_compass_dev register failed", __func__); |
| goto exit6; |
| } |
| |
| /***** sysfs *****/ |
| err = create_sysfs_interfaces(s_akm); |
| if (0 > err) { |
| dev_err(&client->dev, |
| "%s: create sysfs failed.", __func__); |
| goto exit7; |
| } |
| |
| 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->cdev.sensors_self_test = akm_self_test; |
| |
| s_akm->delay[MAG_DATA_FLAG] = sensors_cdev.delay_msec * 1000000; |
| |
| err = sensors_classdev_register(&client->dev, &s_akm->cdev); |
| |
| if (err) { |
| dev_err(&client->dev, "class device create failed: %d\n", err); |
| goto exit8; |
| } |
| |
| akm_compass_power_set(s_akm, false); |
| |
| dev_info(&client->dev, "successfully probed."); |
| return 0; |
| |
| exit8: |
| remove_sysfs_interfaces(s_akm); |
| exit7: |
| misc_deregister(&akm_compass_dev); |
| exit6: |
| if (s_akm->irq) |
| free_irq(s_akm->irq, s_akm); |
| exit5: |
| input_unregister_device(s_akm->input); |
| exit4: |
| akm_compass_power_set(s_akm, 0); |
| exit3: |
| akm_compass_power_init(s_akm, 0); |
| exit2: |
| kfree(s_akm); |
| exit1: |
| exit0: |
| return err; |
| } |
| |
| static int akm_compass_remove(struct i2c_client *client) |
| { |
| struct akm_compass_data *akm = i2c_get_clientdata(client); |
| |
| if (akm->auto_report) { |
| if (akm->use_hrtimer) { |
| hrtimer_cancel(&akm->poll_timer); |
| cancel_work_sync(&akm->dwork.work); |
| } else { |
| cancel_delayed_work_sync(&akm->dwork); |
| } |
| destroy_workqueue(akm->work_queue); |
| } |
| |
| if (akm_compass_power_set(akm, 0)) |
| dev_err(&client->dev, "power set failed."); |
| if (akm_compass_power_init(akm, 0)) |
| dev_err(&client->dev, "power deinit failed."); |
| remove_sysfs_interfaces(akm); |
| sensors_classdev_unregister(&akm->cdev); |
| if (misc_deregister(&akm_compass_dev) < 0) |
| dev_err(&client->dev, "misc deregister failed."); |
| if (akm->irq) |
| free_irq(akm->irq, akm); |
| input_unregister_device(akm->input); |
| kfree(akm); |
| dev_info(&client->dev, "successfully removed."); |
| return 0; |
| } |
| |
| static const struct i2c_device_id akm_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 akm09911_match_table[] = { |
| { .compatible = "ak,ak09911", }, |
| { .compatible = "akm,akm09911", }, |
| { }, |
| }; |
| |
| static struct i2c_driver akm_compass_driver = { |
| .probe = akm_compass_probe, |
| .remove = akm_compass_remove, |
| .id_table = akm_compass_id, |
| .driver = { |
| .name = AKM_I2C_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = akm09911_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"); |
| |