Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
/* drivers/input/sensors/access/akm8975.c
 *
 * Copyright (C) 2012-2015 ROCKCHIP.
 * Author: luowei <lw@rock-chips.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.
 *
 */
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/freezer.h>
#include <linux/of_gpio.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif

#include <linux/sensor-dev.h>

#define SENSOR_DATA_SIZE		8


/*! \name AK8975 operation mode
 \anchor AK8975_Mode
 Defines an operation mode of the AK8975.*/
/*! @{*/
#define AK8975_MODE_SNG_MEASURE	0x01
#define	AK8975_MODE_SELF_TEST	0x08
#define	AK8975_MODE_FUSE_ACCESS	0x0F
#define	AK8975_MODE_POWERDOWN	0x00
/*! @}*/

#define SENSOR_DATA_SIZE		8	/* Rx buffer size, i.e from ST1 to ST2 */
#define RWBUF_SIZE			16	/* Read/Write buffer size.*/


/*! \name AK8975 register address
\anchor AK8975_REG
Defines a register address of the AK8975.*/
/*! @{*/
#define AK8975_REG_WIA		0x00
#define AK8975_REG_INFO		0x01
#define AK8975_REG_ST1		0x02
#define AK8975_REG_HXL		0x03
#define AK8975_REG_HXH		0x04
#define AK8975_REG_HYL		0x05
#define AK8975_REG_HYH		0x06
#define AK8975_REG_HZL		0x07
#define AK8975_REG_HZH		0x08
#define AK8975_REG_ST2		0x09
#define AK8975_REG_CNTL		0x0A
#define AK8975_REG_RSV		0x0B
#define AK8975_REG_ASTC		0x0C
#define AK8975_REG_TS1		0x0D
#define AK8975_REG_TS2		0x0E
#define AK8975_REG_I2CDIS	0x0F
/*! @}*/

/*! \name AK8975 fuse-rom address
\anchor AK8975_FUSE
Defines a read-only address of the fuse ROM of the AK8975.*/
/*! @{*/
#define AK8975_FUSE_ASAX	0x10
#define AK8975_FUSE_ASAY	0x11
#define AK8975_FUSE_ASAZ	0x12
/*! @}*/

#define AK8975_INFO_DATA	(0x01<<3)


#define COMPASS_IOCTL_MAGIC                   'c'

/* IOCTLs for AKM library */
#define ECS_IOCTL_WRITE                 _IOW(COMPASS_IOCTL_MAGIC, 0x01, char*)
#define ECS_IOCTL_READ                  _IOWR(COMPASS_IOCTL_MAGIC, 0x02, char*)
#define ECS_IOCTL_RESET      	        _IO(COMPASS_IOCTL_MAGIC, 0x03) /* NOT used in AK8975 */
#define ECS_IOCTL_SET_MODE              _IOW(COMPASS_IOCTL_MAGIC, 0x04, short)
#define ECS_IOCTL_GETDATA               _IOR(COMPASS_IOCTL_MAGIC, 0x05, char[SENSOR_DATA_SIZE])
#define ECS_IOCTL_SET_YPR               _IOW(COMPASS_IOCTL_MAGIC, 0x06, short[12])
#define ECS_IOCTL_GET_OPEN_STATUS       _IOR(COMPASS_IOCTL_MAGIC, 0x07, int)
#define ECS_IOCTL_GET_CLOSE_STATUS      _IOR(COMPASS_IOCTL_MAGIC, 0x08, int)
#define ECS_IOCTL_GET_LAYOUT        	_IOR(COMPASS_IOCTL_MAGIC, 0x09, char)
#define ECS_IOCTL_GET_ACCEL         	_IOR(COMPASS_IOCTL_MAGIC, 0x0A, short[3])
#define ECS_IOCTL_GET_OUTBIT        	_IOR(COMPASS_IOCTL_MAGIC, 0x0B, char)
#define ECS_IOCTL_GET_DELAY             _IOR(COMPASS_IOCTL_MAGIC, 0x30, short)
#define ECS_IOCTL_GET_PROJECT_NAME      _IOR(COMPASS_IOCTL_MAGIC, 0x0D, char[64])
#define ECS_IOCTL_GET_MATRIX            _IOR(COMPASS_IOCTL_MAGIC, 0x0E, short [4][3][3])
#define ECS_IOCTL_GET_PLATFORM_DATA     _IOR(COMPASS_IOCTL_MAGIC, 0x0E, struct akm_platform_data)


#define AK8975_DEVICE_ID		0x48
static struct i2c_client *this_client;
static struct miscdevice compass_dev_device;




/****************operate according to sensor chip:start************/

static int sensor_active(struct i2c_client *client, int enable, int rate)
{
	struct sensor_private_data *sensor =
	    (struct sensor_private_data *) i2c_get_clientdata(client);
	int result = 0;

	//sensor->ops->ctrl_data = sensor_read_reg(client, sensor->ops->ctrl_reg);

	//register setting according to chip datasheet
	if(enable)
	{
		sensor->ops->ctrl_data = AK8975_MODE_SNG_MEASURE;
	}
	else
	{
		sensor->ops->ctrl_data = AK8975_MODE_POWERDOWN;
	}

	DBG("%s:reg=0x%x,reg_ctrl=0x%x,enable=%d\n",__func__,sensor->ops->ctrl_reg, sensor->ops->ctrl_data, enable);
	result = sensor_write_reg(client, sensor->ops->ctrl_reg, sensor->ops->ctrl_data);
	if(result)
		printk("%s:fail to active sensor\n",__func__);

	return result;

}

static int sensor_init(struct i2c_client *client)
{
	struct sensor_private_data *sensor =
	    (struct sensor_private_data *) i2c_get_clientdata(client);
	int result = 0;
	int info = 0;

	this_client = client;

	result = sensor->ops->active(client,0,0);
	if(result)
	{
		printk("%s:line=%d,error\n",__func__,__LINE__);
		return result;
	}

	sensor->status_cur = SENSOR_OFF;

	info = sensor_read_reg(client, AK8975_REG_INFO);
	if((info & (0x0f<<3)) != AK8975_INFO_DATA)

	{
		printk("%s:info=0x%x,it is not %s\n",__func__, info, sensor->ops->name);
		return -1;
	}

	result = misc_register(&compass_dev_device);
	if (result < 0) {
		printk("%s:fail to register misc device %s\n", __func__, compass_dev_device.name);
		result = -1;
	}

	DBG("%s:status_cur=%d\n",__func__, sensor->status_cur);
	return result;
}

static int sensor_report_value(struct i2c_client *client)
{
	struct sensor_private_data *sensor =
	    	(struct sensor_private_data *) i2c_get_clientdata(client);
	char buffer[8] = {0};
	unsigned char *stat;
	unsigned char *stat2;
	int ret = 0;
	int i;

	if(sensor->ops->read_len < 8)	//sensor->ops->read_len = 8
	{
		printk("%s:lenth is error,len=%d\n",__func__,sensor->ops->read_len);
		return -1;
	}

	memset(buffer, 0, 8);

	/* Data bytes from hardware xL, xH, yL, yH, zL, zH */
	do {
		*buffer = sensor->ops->read_reg;
		ret = sensor_rx_data(client, buffer, sensor->ops->read_len);
		if (ret < 0)
		return ret;
	} while (0);

	stat = &buffer[0];
	stat2 = &buffer[7];

	/*
	 * ST : data ready -
	 * Measurement has been completed and data is ready to be read.
	 */
	if ((*stat & 0x01) != 0x01) {
		DBG(KERN_ERR "%s:ST is not set\n",__func__);
		return -1;
	}

	/*
	 * ST2 : data error -
	 * occurs when data read is started outside of a readable period;
	 * data read would not be correct.
	 * Valid in continuous measurement mode only.
	 * In single measurement mode this error should not occour but we
	 * stil account for it and return an error, since the data would be
	 * corrupted.
	 * DERR bit is self-clearing when ST2 register is read.
	 */
	if (*stat2 & 0x04)
	{
		DBG(KERN_ERR "%s:compass data error\n",__func__);
		return -2;
	}

	/*
	 * ST2 : overflow -
	 * the sum of the absolute values of all axis |X|+|Y|+|Z| < 2400uT.
	 * This is likely to happen in presence of an external magnetic
	 * disturbance; it indicates, the sensor data is incorrect and should
	 * be ignored.
	 * An error is returned.
	 * HOFL bit clears when a new measurement starts.
	 */
	if (*stat2 & 0x08)
	{
		DBG(KERN_ERR "%s:compass data overflow\n",__func__);
		return -3;
	}

	/* »¥³âµØ»º´æÊý¾Ý. */
	mutex_lock(&sensor->data_mutex);
	memcpy(sensor->sensor_data, buffer, sensor->ops->read_len);
	mutex_unlock(&sensor->data_mutex);
	DBG("%s:",__func__);
	for(i=0; i<sensor->ops->read_len; i++)
		DBG("0x%x,",buffer[i]);
	DBG("\n");

	if((sensor->pdata->irq_enable)&& (sensor->ops->int_status_reg >= 0))	//read sensor intterupt status register
	{

		DBG("%s:sensor int status :0x%x\n", __func__,
			sensor_read_reg(client, sensor->ops->int_status_reg));
	}


	//trigger next measurement
	ret = sensor_write_reg(client, sensor->ops->ctrl_reg, sensor->ops->ctrl_data);
	if(ret)
	{
		printk(KERN_ERR "%s:fail to set ctrl_data:0x%x\n",__func__,sensor->ops->ctrl_data);
		return ret;
	}

	return ret;
}

static void compass_set_YPR(short *rbuf)
{
	struct sensor_private_data *sensor =
	    (struct sensor_private_data *) i2c_get_clientdata(this_client);

	/* Report magnetic sensor information */
	if (atomic_read(&sensor->flags.m_flag)) {
		input_report_abs(sensor->input_dev, ABS_RX, rbuf[0]);
		input_report_abs(sensor->input_dev, ABS_RY, rbuf[1]);
		input_report_abs(sensor->input_dev, ABS_RZ, rbuf[2]);
		input_report_abs(sensor->input_dev, ABS_RUDDER, rbuf[4]);
		DBG("%s:m_flag:x=%d,y=%d,z=%d,RUDDER=%d\n",__func__,rbuf[0], rbuf[1], rbuf[2], rbuf[4]);
	}

	/* Report acceleration sensor information */
	if (atomic_read(&sensor->flags.a_flag)) {
		input_report_abs(sensor->input_dev, ABS_X, rbuf[6]);
		input_report_abs(sensor->input_dev, ABS_Y, rbuf[7]);
		input_report_abs(sensor->input_dev, ABS_Z, rbuf[8]);
		input_report_abs(sensor->input_dev, ABS_WHEEL, rbuf[5]);

		DBG("%s:a_flag:x=%d,y=%d,z=%d,WHEEL=%d\n",__func__,rbuf[6], rbuf[7], rbuf[8], rbuf[5]);
	}

	/* Report magnetic vector information */
	if (atomic_read(&sensor->flags.mv_flag)) {
		input_report_abs(sensor->input_dev, ABS_HAT0X, rbuf[9]);
		input_report_abs(sensor->input_dev, ABS_HAT0Y, rbuf[10]);
		input_report_abs(sensor->input_dev, ABS_BRAKE, rbuf[11]);

		DBG("%s:mv_flag:x=%d,y=%d,BRAKE=%d\n",__func__,rbuf[9], rbuf[10], rbuf[11]);
	}

	input_sync(sensor->input_dev);
}


static int compass_dev_open(struct inode *inode, struct file *file)
{
	int result = 0;
	DBG("%s\n",__func__);

	return result;
}


static int compass_dev_release(struct inode *inode, struct file *file)
{
	int result = 0;
	DBG("%s\n",__func__);

	return result;
}

static int compass_akm_set_mode(struct i2c_client *client, char mode)
{
	struct sensor_private_data* sensor = (struct sensor_private_data *)i2c_get_clientdata(this_client);
	int result = 0;

	switch(mode)
	{
		case AK8975_MODE_SNG_MEASURE:
		case AK8975_MODE_SELF_TEST:
		case AK8975_MODE_FUSE_ACCESS:
			if(sensor->status_cur == SENSOR_OFF)
			{
				sensor->stop_work = 0;
				sensor->status_cur = SENSOR_ON;
				if(sensor->pdata->irq_enable)
				{
					//DBG("%s:enable irq=%d\n",__func__,client->irq);
					//enable_irq(client->irq);
				}
				else
				{
					schedule_delayed_work(&sensor->delaywork, msecs_to_jiffies(sensor->pdata->poll_delay_ms));
				}
			}

			break;

		case AK8975_MODE_POWERDOWN:
			if(sensor->status_cur == SENSOR_ON)
			{
				sensor->stop_work = 1;
				if(sensor->pdata->irq_enable)
				{
					//DBG("%s:disable irq=%d\n",__func__,client->irq);
					//disable_irq_nosync(client->irq);//disable irq
				}
				else
				cancel_delayed_work_sync(&sensor->delaywork);

				sensor->status_cur = SENSOR_OFF;
			}
			break;

	}

	switch(mode)
	{
		case AK8975_MODE_SNG_MEASURE:
			result = sensor_write_reg(client, sensor->ops->ctrl_reg, AK8975_MODE_SNG_MEASURE);
			if(result)
			printk("%s:i2c error,mode=%d\n",__func__,mode);
			break;
		case AK8975_MODE_SELF_TEST:
			result = sensor_write_reg(client, sensor->ops->ctrl_reg, AK8975_MODE_SELF_TEST);
			if(result)
			printk("%s:i2c error,mode=%d\n",__func__,mode);
			break;
		case AK8975_MODE_FUSE_ACCESS:
			result = sensor_write_reg(client, sensor->ops->ctrl_reg, AK8975_MODE_FUSE_ACCESS);
			if(result)
			printk("%s:i2c error,mode=%d\n",__func__,mode);
			break;
		case AK8975_MODE_POWERDOWN:
			/* Set powerdown mode */
			result = sensor_write_reg(client, sensor->ops->ctrl_reg, AK8975_MODE_POWERDOWN);
			if(result)
			printk("%s:i2c error,mode=%d\n",__func__,mode);
			udelay(100);
			break;
		default:
			printk("%s: Unknown mode(%d)", __func__, mode);
			result = -EINVAL;
			break;
	}
	DBG("%s:mode=%d\n",__func__,mode);
	return result;

}


static int compass_akm_get_openstatus(void)
{
	struct sensor_private_data* sensor = (struct sensor_private_data *)i2c_get_clientdata(this_client);
	wait_event_interruptible(sensor->flags.open_wq, (atomic_read(&sensor->flags.open_flag) != 0));
	return atomic_read(&sensor->flags.open_flag);
}

static int compass_akm_get_closestatus(void)
{
	struct sensor_private_data* sensor = (struct sensor_private_data *)i2c_get_clientdata(this_client);
	wait_event_interruptible(sensor->flags.open_wq, (atomic_read(&sensor->flags.open_flag) <= 0));
	return atomic_read(&sensor->flags.open_flag);
}


/* ioctl - I/O control */
static long compass_dev_ioctl(struct file *file,
			  unsigned int cmd, unsigned long arg)
{
	struct sensor_private_data* sensor = (struct sensor_private_data *)i2c_get_clientdata(this_client);
	struct i2c_client *client = this_client;
	void __user *argp = (void __user *)arg;
	int result = 0;

	/* NOTE: In this function the size of "char" should be 1-byte. */
	char compass_data[SENSOR_DATA_SIZE];/* for GETDATA */
	char rwbuf[RWBUF_SIZE];		/* for READ/WRITE */
	char mode;					/* for SET_MODE*/
	short value[12];			/* for SET_YPR */
	short delay;				/* for GET_DELAY */
	int status;					/* for OPEN/CLOSE_STATUS */
	int ret = -1;				/* Return value. */

	switch (cmd) {
		case ECS_IOCTL_WRITE:
		case ECS_IOCTL_READ:
			if (argp == NULL) {
				return -EINVAL;
			}
			if (copy_from_user(&rwbuf, argp, sizeof(rwbuf))) {
				return -EFAULT;
			}
			break;
		case ECS_IOCTL_SET_MODE:
			if (argp == NULL) {
				return -EINVAL;
			}
			if (copy_from_user(&mode, argp, sizeof(mode))) {
				return -EFAULT;
			}
			break;
		case ECS_IOCTL_SET_YPR:
			if (argp == NULL) {
				return -EINVAL;
			}
			if (copy_from_user(&value, argp, sizeof(value))) {
				return -EFAULT;
			}
			break;
		default:
			break;
	}

	switch (cmd) {
		case ECS_IOCTL_WRITE:
			DBG("%s:ECS_IOCTL_WRITE start\n",__func__);
			mutex_lock(&sensor->operation_mutex);
			if ((rwbuf[0] < 2) || (rwbuf[0] > (RWBUF_SIZE-1))) {
				mutex_unlock(&sensor->operation_mutex);
				return -EINVAL;
			}
			ret = sensor_tx_data(client, &rwbuf[1], rwbuf[0]);
			if (ret < 0) {
				mutex_unlock(&sensor->operation_mutex);
				printk("%s:fait to tx data\n",__func__);
				return ret;
			}
			mutex_unlock(&sensor->operation_mutex);
			break;
		case ECS_IOCTL_READ:
			DBG("%s:ECS_IOCTL_READ start\n",__func__);
			mutex_lock(&sensor->operation_mutex);
			if ((rwbuf[0] < 1) || (rwbuf[0] > (RWBUF_SIZE-1))) {
				mutex_unlock(&sensor->operation_mutex);
				printk("%s:data is error\n",__func__);
				return -EINVAL;
			}
			ret = sensor_rx_data(client, &rwbuf[1], rwbuf[0]);
			if (ret < 0) {
				mutex_unlock(&sensor->operation_mutex);
				printk("%s:fait to rx data\n",__func__);
				return ret;
			}
			mutex_unlock(&sensor->operation_mutex);
			break;
		case ECS_IOCTL_SET_MODE:
			DBG("%s:ECS_IOCTL_SET_MODE start\n",__func__);
			mutex_lock(&sensor->operation_mutex);
			if(sensor->ops->ctrl_data != mode)
			{
				ret = compass_akm_set_mode(client, mode);
				if (ret < 0) {
					printk("%s:fait to set mode\n",__func__);
					mutex_unlock(&sensor->operation_mutex);
					return ret;
				}

				sensor->ops->ctrl_data = mode;
			}
			mutex_unlock(&sensor->operation_mutex);
			break;
		case ECS_IOCTL_GETDATA:
			DBG("%s:ECS_IOCTL_GETDATA start\n",__func__);
			mutex_lock(&sensor->data_mutex);
			memcpy(compass_data, sensor->sensor_data, SENSOR_DATA_SIZE);	//get data from buffer
			mutex_unlock(&sensor->data_mutex);
			break;
		case ECS_IOCTL_SET_YPR:
			DBG("%s:ECS_IOCTL_SET_YPR start\n",__func__);
			mutex_lock(&sensor->data_mutex);
			compass_set_YPR(value);
			mutex_unlock(&sensor->data_mutex);
			break;
		case ECS_IOCTL_GET_OPEN_STATUS:
			status = compass_akm_get_openstatus();
			DBG("%s:openstatus=%d\n",__func__,status);
			break;
		case ECS_IOCTL_GET_CLOSE_STATUS:
			status = compass_akm_get_closestatus();
			DBG("%s:closestatus=%d\n",__func__,status);
			break;
		case ECS_IOCTL_GET_DELAY:
			delay = sensor->flags.delay;
			break;
		case ECS_IOCTL_GET_PLATFORM_DATA:
			break;

		default:
			return -ENOTTY;
	}

	switch (cmd) {
		case ECS_IOCTL_READ:
			if (copy_to_user(argp, &rwbuf, rwbuf[0]+1)) {
				return -EFAULT;
			}
			break;
		case ECS_IOCTL_GETDATA:
			if (copy_to_user(argp, &compass_data, sizeof(compass_data))) {
				return -EFAULT;
			}
			break;
		case ECS_IOCTL_GET_OPEN_STATUS:
		case ECS_IOCTL_GET_CLOSE_STATUS:
			if (copy_to_user(argp, &status, sizeof(status))) {
				return -EFAULT;
			}
			break;
		case ECS_IOCTL_GET_DELAY:
			if (copy_to_user(argp, &delay, sizeof(delay))) {
				return -EFAULT;
			}
			break;
		default:
			break;
	}

	return result;
}

static struct file_operations compass_dev_fops =
{
	.owner = THIS_MODULE,
	.open = compass_dev_open,
	.release = compass_dev_release,
	.unlocked_ioctl = compass_dev_ioctl,
};


static struct miscdevice compass_dev_device =
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = "akm8975_dev",
	.fops = &compass_dev_fops,
};

static struct sensor_operate compass_akm8975_ops = {
	.name				= "akm8975",
	.type				= SENSOR_TYPE_COMPASS,	//it is important
	.id_i2c				= COMPASS_ID_AK8975,
	.read_reg			= AK8975_REG_ST1,	//read data
	.read_len			= SENSOR_DATA_SIZE,	//data length
	.id_reg				= AK8975_REG_WIA,	//read id
	.id_data 			= AK8975_DEVICE_ID,
	.precision			= 8,			//12 bits
	.ctrl_reg 			= AK8975_REG_CNTL,	//enable or disable
	.int_status_reg			= SENSOR_UNKNOW_DATA,	//not exist
	.range				= {-0xffff,0xffff},
	.trig				= IRQF_TRIGGER_RISING,	//if LEVEL interrupt then IRQF_ONESHOT
	.active				= sensor_active,
	.init				= sensor_init,
	.report				= sensor_report_value,
	.misc_dev 			= NULL,			//private misc support
};

/****************operate according to sensor chip:end************/
static int compass_akm8975_probe(struct i2c_client *client,
				 const struct i2c_device_id *devid)
{
	return sensor_register_device(client, NULL, devid, &compass_akm8975_ops);
}

static int compass_akm8975_remove(struct i2c_client *client)
{
	return sensor_unregister_device(client, NULL, &compass_akm8975_ops);
}

static const struct i2c_device_id compass_akm8975_id[] = {
	{"ak8975", COMPASS_ID_AK8975},
	{}
};

static struct i2c_driver compass_akm8975_driver = {
	.probe = compass_akm8975_probe,
	.remove = compass_akm8975_remove,
	.shutdown = sensor_shutdown,
	.id_table = compass_akm8975_id,
	.driver = {
		.name = "compass_akm8975",
	#ifdef CONFIG_PM
		.pm = &sensor_pm_ops,
	#endif
	},
};

module_i2c_driver(compass_akm8975_driver);

MODULE_AUTHOR("luowei <lw@rock-chips.com>");
MODULE_DESCRIPTION("akm8975 3-Axis compasss driver");
MODULE_LICENSE("GPL");