| /* |
| * fxls8471.c - Linux kernel modules for 3-Axis Accel sensor |
| * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/pm.h> |
| #include <linux/mutex.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/hwmon-sysfs.h> |
| #include <linux/err.h> |
| #include <linux/hwmon.h> |
| #include <linux/input-polldev.h> |
| #include <linux/miscdevice.h> |
| #include <linux/poll.h> |
| #include "fxls8471.h" |
| |
| #define SENSOR_IOCTL_BASE 'S' |
| #define SENSOR_GET_MODEL_NAME _IOR(SENSOR_IOCTL_BASE, 0, char *) |
| #define SENSOR_GET_POWER_STATUS _IOR(SENSOR_IOCTL_BASE, 2, int) |
| #define SENSOR_SET_POWER_STATUS _IOR(SENSOR_IOCTL_BASE, 3, int) |
| #define SENSOR_GET_DELAY_TIME _IOR(SENSOR_IOCTL_BASE, 4, int) |
| #define SENSOR_SET_DELAY_TIME _IOR(SENSOR_IOCTL_BASE, 5, int) |
| #define SENSOR_GET_RAW_DATA _IOR(SENSOR_IOCTL_BASE, 6, short[3]) |
| |
| #define FXLS8471_POSITION_DEFAULT 2 |
| #define FXLS8471_DELAY_DEFAULT 200 |
| |
| #define FXLS8471_STATUS_ZYXDR 0x08 |
| #define FXLS8471_BUF_SIZE 6 |
| |
| struct fxls8471_data fxls8471_dev; |
| EXPORT_SYMBOL(fxls8471_dev); |
| |
| static int fxls8471_position_setting[8][3][3] = { |
| {{0, -1, 0}, {1, 0, 0}, {0, 0, 1} }, |
| {{-1, 0, 0}, {0, -1, 0}, {0, 0, 1} }, |
| {{0, 1, 0}, {-1, 0, 0}, {0, 0, 1} }, |
| {{1, 0, 0}, {0, 1, 0}, {0, 0, 1} }, |
| |
| {{0, -1, 0}, {-1, 0, 0}, {0, 0, -1} }, |
| {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1} }, |
| {{0, 1, 0}, {1, 0, 0}, {0, 0, -1} }, |
| {{1, 0, 0}, {0, -1, 0}, {0, 0, -1} }, |
| }; |
| |
| static int fxls8471_bus_write(struct fxls8471_data *pdata, u8 reg, u8 val) |
| { |
| if (pdata && pdata->write) |
| return pdata->write(pdata, reg, val); |
| return -EIO; |
| } |
| |
| static int fxls8471_bus_read(struct fxls8471_data *pdata, u8 reg) |
| { |
| if (pdata && pdata->read) |
| return pdata->read(pdata, reg); |
| return -EIO; |
| } |
| |
| static int fxls8471_bus_read_block(struct fxls8471_data *pdata, u8 reg, u8 len, |
| u8 *val) |
| { |
| if (pdata && pdata->read_block) |
| return pdata->read_block(pdata, reg, len, val); |
| return -EIO; |
| } |
| |
| static int fxls8471_data_convert(struct fxls8471_data *pdata, |
| struct fxls8471_data_axis *axis_data) |
| { |
| short rawdata[3], data[3]; |
| int i, j; |
| int position = atomic_read(&pdata->position); |
| |
| if (position < 0 || position > 7) |
| position = 0; |
| rawdata[0] = axis_data->x; |
| rawdata[1] = axis_data->y; |
| rawdata[2] = axis_data->z; |
| for (i = 0; i < 3; i++) { |
| data[i] = 0; |
| for (j = 0; j < 3; j++) |
| data[i] += |
| rawdata[j] * |
| fxls8471_position_setting[position][i][j]; |
| } |
| axis_data->x = data[0]; |
| axis_data->y = data[1]; |
| axis_data->z = data[2]; |
| return 0; |
| } |
| |
| static int fxls8471_device_init(struct fxls8471_data *pdata) |
| { |
| int result; |
| result = fxls8471_bus_write(pdata, FXLS8471_CTRL_REG1, 0); |
| if (result < 0) |
| goto out; |
| |
| result = fxls8471_bus_write(pdata, FXLS8471_XYZ_DATA_CFG, MODE_2G); |
| if (result < 0) |
| goto out; |
| |
| if (pdata->irq) { |
| result = fxls8471_bus_write(pdata, FXLS8471_CTRL_REG5, 0x01); |
| if (result < 0) |
| goto out; |
| result = fxls8471_bus_write(pdata, FXLS8471_CTRL_REG4, 0x01); |
| if (result < 0) |
| goto out; |
| } |
| atomic_set(&pdata->active, STANDBY); |
| return 0; |
| out: |
| printk("FXLS8471 device init error\n"); |
| return result; |
| |
| } |
| |
| static int fxls8471_change_mode(struct fxls8471_data *pdata, int mode) |
| { |
| u8 val; |
| int ret; |
| val = fxls8471_bus_read(pdata, FXLS8471_CTRL_REG1); |
| if (mode == ACTIVED) |
| val |= 0x01; |
| else |
| val &= (~0x01); |
| ret = fxls8471_bus_write(pdata, FXLS8471_CTRL_REG1, val); |
| return ret; |
| } |
| |
| static int fxls8471_set_delay(struct fxls8471_data *pdata, int delay) |
| { |
| u8 val; |
| val = fxls8471_bus_read(pdata, FXLS8471_CTRL_REG1); |
| /* set sensor standby */ |
| fxls8471_bus_write(pdata, FXLS8471_CTRL_REG1, (val & ~0x01)); |
| val &= ~(0x7 << 3); |
| if (delay <= 10) |
| val |= 0x02 << 3; |
| else if (delay <= 20) |
| val |= 0x03 << 3; |
| else if (delay <= 67) |
| val |= 0x04 << 3; |
| else |
| val |= 0x05 << 3; |
| /* set sensor standby */ |
| fxls8471_bus_write(pdata, FXLS8471_CTRL_REG1, val); |
| return 0; |
| } |
| |
| static int fxls8471_change_range(struct fxls8471_data *pdata, int range) |
| { |
| int ret; |
| |
| ret = fxls8471_bus_write(pdata, FXLS8471_XYZ_DATA_CFG, range); |
| |
| return ret; |
| } |
| |
| static int fxls8471_read_data(struct fxls8471_data *pdata, |
| struct fxls8471_data_axis *data) |
| { |
| u8 tmp_data[FXLS8471_BUF_SIZE]; |
| int ret; |
| ret = fxls8471_bus_read_block(pdata, FXLS8471_OUT_X_MSB, |
| FXLS8471_BUF_SIZE, tmp_data); |
| if (ret < FXLS8471_BUF_SIZE) { |
| printk(KERN_ERR "FXLS8471 read sensor block data error\n"); |
| return -EIO; |
| } |
| data->x = ((tmp_data[0] << 8) & 0xff00) | tmp_data[1]; |
| data->y = ((tmp_data[2] << 8) & 0xff00) | tmp_data[3]; |
| data->z = ((tmp_data[4] << 8) & 0xff00) | tmp_data[5]; |
| return 0; |
| } |
| |
| /* fxls8471 miscdevice */ |
| static long fxls8471_ioctl(struct file *file, unsigned int reg, |
| unsigned long arg) |
| { |
| struct fxls8471_data *pdata = file->private_data; |
| void __user *argp = (void __user *)arg; |
| long ret = 0; |
| short sdata[3]; |
| int enable; |
| int delay; |
| struct fxls8471_data_axis data; |
| if (!pdata) { |
| printk(KERN_ERR "FXLS8471 struct datt point is NULL."); |
| return -EFAULT; |
| } |
| switch (reg) { |
| case SENSOR_GET_MODEL_NAME: |
| if (copy_to_user(argp, "fxls8471", strlen("fxls8471") + 1)) { |
| printk(KERN_ERR |
| "SENSOR_GET_MODEL_NAME copy_to_user failed."); |
| ret = -EFAULT; |
| } |
| break; |
| case SENSOR_GET_POWER_STATUS: |
| enable = atomic_read(&pdata->active); |
| if (copy_to_user(argp, &enable, sizeof(int))) { |
| printk(KERN_ERR |
| "SENSOR_SET_POWER_STATUS copy_to_user failed."); |
| ret = -EFAULT; |
| } |
| break; |
| case SENSOR_SET_POWER_STATUS: |
| if (copy_from_user(&enable, argp, sizeof(int))) { |
| printk(KERN_ERR |
| "SENSOR_SET_POWER_STATUS copy_to_user failed."); |
| ret = -EFAULT; |
| } |
| if (pdata) { |
| ret = |
| fxls8471_change_mode(pdata, |
| enable ? ACTIVED : STANDBY); |
| if (!ret) |
| atomic_set(&pdata->active, enable); |
| } |
| break; |
| case SENSOR_GET_DELAY_TIME: |
| delay = atomic_read(&pdata->delay); |
| if (copy_to_user(argp, &delay, sizeof(delay))) { |
| printk(KERN_ERR |
| "SENSOR_GET_DELAY_TIME copy_to_user failed."); |
| return -EFAULT; |
| } |
| break; |
| case SENSOR_SET_DELAY_TIME: |
| if (copy_from_user(&delay, argp, sizeof(int))) { |
| printk(KERN_ERR |
| "SENSOR_GET_DELAY_TIME copy_to_user failed."); |
| ret = -EFAULT; |
| } |
| if (pdata && delay > 0 && delay <= 500) { |
| ret = fxls8471_set_delay(pdata, delay); |
| if (!ret) |
| atomic_set(&pdata->delay, delay); |
| } |
| break; |
| case SENSOR_GET_RAW_DATA: |
| ret = fxls8471_read_data(pdata, &data); |
| if (!ret) { |
| fxls8471_data_convert(pdata, &data); |
| sdata[0] = data.x; |
| sdata[1] = data.y; |
| sdata[2] = data.z; |
| if (copy_to_user(argp, sdata, sizeof(sdata))) { |
| printk(KERN_ERR |
| "SENSOR_GET_RAW_DATA copy_to_user failed."); |
| ret = -EFAULT; |
| } |
| } |
| break; |
| default: |
| ret = -1; |
| } |
| return ret; |
| } |
| |
| static int fxls8471_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = &fxls8471_dev; |
| return nonseekable_open(inode, file); |
| } |
| |
| static int fxls8471_release(struct inode *inode, struct file *file) |
| { |
| /* note: releasing the wdt in NOWAYOUT-mode does not stop it */ |
| return 0; |
| } |
| |
| static const struct file_operations fxls8471_fops = { |
| .owner = THIS_MODULE, |
| .open = fxls8471_open, |
| .release = fxls8471_release, |
| .unlocked_ioctl = fxls8471_ioctl, |
| }; |
| |
| static struct miscdevice fxls8471_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "FreescaleAccelerometer", |
| .fops = &fxls8471_fops, |
| }; |
| |
| static ssize_t fxls8471_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int enable = 0; |
| enable = atomic_read(&pdata->active); |
| return sprintf(buf, "%d\n", enable); |
| } |
| |
| static ssize_t fxls8471_enable_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int ret; |
| unsigned long enable; |
| |
| if (kstrtoul(buf, 10, &enable) < 0) |
| return -EINVAL; |
| enable = (enable > 0) ? 1 : 0; |
| ret = fxls8471_change_mode(pdata, (enable > 0 ? ACTIVED : STANDBY)); |
| if (!ret) { |
| atomic_set(&pdata->active, enable); |
| if (enable) |
| printk(KERN_INFO "mma enable setting actived\n"); |
| else |
| printk(KERN_INFO "mma enable setting standby\n"); |
| } |
| return count; |
| } |
| |
| static ssize_t fxls8471_poll_delay_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int delay = 0; |
| delay = atomic_read(&pdata->delay); |
| return sprintf(buf, "%d\n", delay); |
| } |
| |
| static ssize_t fxls8471_poll_delay_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int ret; |
| unsigned long delay; |
| |
| if (kstrtoul(buf, 10, &delay) < 0) |
| return -EINVAL; |
| ret = fxls8471_set_delay(pdata, delay); |
| if (!ret) |
| atomic_set(&pdata->delay, delay); |
| |
| return count; |
| } |
| |
| static ssize_t fxls8471_position_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int position = 0; |
| position = atomic_read(&pdata->position); |
| return sprintf(buf, "%d\n", position); |
| } |
| |
| static ssize_t fxls8471_position_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| unsigned long position; |
| |
| if (kstrtoul(buf, 10, &position) < 0) |
| return -EINVAL; |
| atomic_set(&pdata->position, position); |
| |
| return count; |
| } |
| |
| static ssize_t fxls8471_data_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int ret = 0; |
| struct fxls8471_data_axis data; |
| ret = fxls8471_read_data(pdata, &data); |
| if (!ret) |
| fxls8471_data_convert(pdata, &data); |
| return sprintf(buf, "%d,%d,%d\n", data.x, data.y, data.z); |
| } |
| |
| static ssize_t fxls8471_range_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int range = 0; |
| |
| range = atomic_read(&pdata->range); |
| return sprintf(buf, "%d\n", range); |
| } |
| |
| static ssize_t fxls8471_range_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct fxls8471_data *pdata = &fxls8471_dev; |
| int ret; |
| unsigned long range; |
| |
| if (kstrtoul(buf, 10, &range) < 0) |
| return -EINVAL; |
| |
| if (range == atomic_read(&pdata->range)) |
| return count; |
| |
| if (atomic_read(&pdata->active)) |
| printk(KERN_INFO "Pls set the sensor standby and then actived\n"); |
| ret = fxls8471_change_range(pdata, range); |
| if (!ret) |
| atomic_set(&pdata->range, range); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, fxls8471_enable_show, fxls8471_enable_store); |
| static DEVICE_ATTR(poll_delay, S_IWUSR | S_IRUGO, fxls8471_poll_delay_show, |
| fxls8471_poll_delay_store); |
| |
| static DEVICE_ATTR(position, S_IWUSR | S_IRUGO, fxls8471_position_show, |
| fxls8471_position_store); |
| |
| static DEVICE_ATTR(data, S_IWUSR | S_IRUGO, fxls8471_data_show, NULL); |
| |
| static DEVICE_ATTR(range, S_IWUSR | S_IRUGO, fxls8471_range_show, fxls8471_range_store); |
| |
| static struct attribute *fxls8471_attributes[] = { |
| &dev_attr_enable.attr, |
| &dev_attr_poll_delay.attr, |
| &dev_attr_position.attr, |
| &dev_attr_data.attr, |
| &dev_attr_range.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group fxls8471_attr_group = { |
| .attrs = fxls8471_attributes, |
| }; |
| |
| static irqreturn_t fxls8471_irq_handler(int irq, void *dev) |
| { |
| int ret; |
| u8 int_src; |
| struct fxls8471_data *pdata = (struct fxls8471_data *)dev; |
| struct fxls8471_data_axis data; |
| int_src = fxls8471_bus_read(pdata, FXLS8471_INT_SOURCE); |
| /* data ready interrupt */ |
| if (int_src & 0x01) { |
| ret = fxls8471_read_data(pdata, &data); |
| if (!ret) { |
| fxls8471_data_convert(pdata, &data); |
| input_report_abs(pdata->idev, ABS_X, data.x); |
| input_report_abs(pdata->idev, ABS_Y, data.y); |
| input_report_abs(pdata->idev, ABS_Z, data.z); |
| input_sync(pdata->idev); |
| } |
| |
| } |
| return IRQ_HANDLED; |
| } |
| |
| int fxls8471_driver_init(struct fxls8471_data *pdata) |
| { |
| int result, chip_id; |
| |
| chip_id = fxls8471_bus_read(pdata, FXLS8471_WHO_AM_I); |
| |
| if (chip_id != FXSL8471_ID) { |
| printk(KERN_ERR "read sensor who am i (0x%x)error !\n", |
| chip_id); |
| result = -EINVAL; |
| goto err_out; |
| } |
| /* Initialize the FXLS8471 chip */ |
| pdata->chip_id = chip_id; |
| atomic_set(&pdata->delay, FXLS8471_DELAY_DEFAULT); |
| atomic_set(&pdata->position, FXLS8471_POSITION_DEFAULT); |
| result = misc_register(&fxls8471_device); |
| if (result != 0) { |
| printk(KERN_ERR "register acc miscdevice error"); |
| goto err_out; |
| } |
| |
| result = |
| sysfs_create_group(&fxls8471_device.this_device->kobj, |
| &fxls8471_attr_group); |
| if (result) { |
| printk(KERN_ERR "create device file failed!\n"); |
| result = -EINVAL; |
| goto err_create_sysfs; |
| } |
| /*create data input device */ |
| pdata->idev = input_allocate_device(); |
| if (!pdata->idev) { |
| result = -ENOMEM; |
| printk(KERN_ERR "alloc fxls8471 input device failed!\n"); |
| goto err_alloc_input_device; |
| } |
| pdata->idev->name = "FreescaleAccelerometer"; |
| pdata->idev->id.bustype = BUS_I2C; |
| pdata->idev->evbit[0] = BIT_MASK(EV_ABS); |
| input_set_abs_params(pdata->idev, ABS_X, -0x7fff, 0x7fff, 0, 0); |
| input_set_abs_params(pdata->idev, ABS_Y, -0x7fff, 0x7fff, 0, 0); |
| input_set_abs_params(pdata->idev, ABS_Z, -0x7fff, 0x7fff, 0, 0); |
| result = input_register_device(pdata->idev); |
| if (result) { |
| printk(KERN_ERR "register fxls8471 input device failed!\n"); |
| goto err_register_input_device; |
| } |
| if (pdata->irq) { |
| result = |
| request_threaded_irq(pdata->irq, NULL, fxls8471_irq_handler, |
| IRQF_TRIGGER_LOW | IRQF_ONESHOT, |
| pdata->idev->name, pdata); |
| if (result < 0) { |
| printk(KERN_ERR "failed to register MMA8x5x irq %d!\n", |
| pdata->irq); |
| goto err_register_irq; |
| } |
| } |
| fxls8471_device_init(pdata); |
| printk("fxls8471 device driver probe successfully\n"); |
| return 0; |
| err_register_irq: |
| input_unregister_device(pdata->idev); |
| err_register_input_device: |
| input_free_device(pdata->idev); |
| err_alloc_input_device: |
| sysfs_remove_group(&fxls8471_device.this_device->kobj, |
| &fxls8471_attr_group); |
| err_create_sysfs: |
| misc_deregister(&fxls8471_device); |
| err_out: |
| return result; |
| } |
| EXPORT_SYMBOL_GPL(fxls8471_driver_init); |
| |
| int fxls8471_driver_remove(struct fxls8471_data *pdata) |
| { |
| fxls8471_change_mode(pdata, STANDBY); |
| misc_deregister(&fxls8471_device); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(fxls8471_driver_remove); |
| |
| #ifdef CONFIG_PM_SLEEP |
| int fxls8471_driver_suspend(struct fxls8471_data *pdata) |
| { |
| if (atomic_read(&pdata->active)) |
| fxls8471_change_mode(pdata, STANDBY); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(fxls8471_driver_suspend); |
| |
| int fxls8471_driver_resume(struct fxls8471_data *pdata) |
| { |
| if (atomic_read(&pdata->active)) |
| fxls8471_change_mode(pdata, ACTIVED); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(fxls8471_driver_resume); |
| |
| #endif |