diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/fxas2100x.c | 618 |
3 files changed, 629 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 6cb388e8fb7d..42e56ee2975f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -333,6 +333,16 @@ config ISL29020 This driver can also be built as a module. If so, the module will be called isl29020. +config SENSORS_FXAS2100X + tristate "Freescale FXAS2100X gyroscope sensor" + depends on I2C && SYSFS + help + If you say yes here you get support for the Freescale FXAS2100X + gyroscope sensor. + + This driver can also be built as a module. If so, the module + will be called fxas2100x. + config SENSORS_TSL2550 tristate "Taos TSL2550 ambient light sensor" depends on I2C && SYSFS diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 99b9424ce31d..a6258bb4a197 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_LKDTM) += lkdtm.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o +obj-$(CONFIG_SENSORS_FXAS2100X) += fxas2100x.o obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o diff --git a/drivers/misc/fxas2100x.c b/drivers/misc/fxas2100x.c new file mode 100644 index 000000000000..b5b008211c85 --- /dev/null +++ b/drivers/misc/fxas2100x.c @@ -0,0 +1,618 @@ +/* + * Copyright (C) 2012-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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.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> + +#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 FXAS2100X_I2C_ADDR 0x20 +#define FXAS21000_CHIP_ID 0xD1 +#define FXAS21002_CHID_ID_1 0xD6 +#define FXAS21002_CHID_ID_2 0xD7 + +#define FXAS2100X_POSITION_DEFAULT 2 +#define FXAS2100X_DELAY_DEFAULT 200 + +#define FXAS2100X_STATUS_ZYXDR 0x08 +#define FXAS2100X_BUF_SIZE 6 + +#define FXAS2100X_POLL_INTERVAL 400 +#define FXAS2100X_POLL_MAX 800 +#define FXAS2100X_POLL_MIN 200 +#define ABSMIN_GYRO_VAL -32768 +#define ABSMAX_GYRO_VAL 32768 + +#define FXAS2100X_DRIVER "fxas2100x" + +/* register enum for fxas2100x registers */ +enum { + FXAS2100X_STATUS = 0x00, + FXAS2100X_OUT_X_MSB, + FXAS2100X_OUT_X_LSB, + FXAS2100X_OUT_Y_MSB, + FXAS2100X_OUT_Y_LSB, + FXAS2100X_OUT_Z_MSB, + FXAS2100X_OUT_Z_LSB, + FXAS2100X_DR_STATUS, + FXAS2100X_F_STATUS, + FXAS2100X_F_SETUP, + FXAS2100X_F_EVENT, + FXAS2100X_INT_SRC_FLAG, + FXAS2100X_WHO_AM_I, + FXAS2100X_CTRL_REG0, + FXAS2100X_RT_CFG, + FXAS2100X_RT_SRC, + FXAS2100X_RT_THS, + FXAS2100X_RT_COUNT, + FXAS2100X_TEMP, + FXAS2100X_CTRL_REG1, + FXAS2100X_CTRL_REG2, + FXAS2100X_CTRL_REG3, /* fxos21002 special */ + FXAS2100X_REG_END, +}; + +enum { + STANDBY = 0, + ACTIVED, +}; + +struct fxas2100x_data_axis { + short x; + short y; + short z; +}; + +struct fxas2100x_data { + struct i2c_client *client; + struct input_polled_dev *input_polled; + atomic_t active; + atomic_t active_poll; + atomic_t delay; + atomic_t position; + u8 chip_id; +}; + +static struct fxas2100x_data *g_fxas2100x_data; + +static int fxas2100x_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 fxas2100x_data_convert(struct fxas2100x_data *pdata, + struct fxas2100x_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] * fxas2100x_position_setting[position][i][j]; + } + axis_data->x = data[0]; + axis_data->y = data[1]; + axis_data->z = data[2]; + return 0; +} + +static int fxas2100x_device_init(struct i2c_client *client) +{ + int result; + u8 val; + struct fxas2100x_data *pdata = i2c_get_clientdata(client); + if (pdata->chip_id == FXAS21000_CHIP_ID) + val = (0x01 << 2); /* fxas21000 dr 200HZ */ + else + val = (0x02 << 2); /* fxas21002 dr 200HZ */ + result = i2c_smbus_write_byte_data(client, FXAS2100X_CTRL_REG1, val); + if (result < 0) + goto out; + atomic_set(&pdata->active, STANDBY); + return 0; +out: + dev_err(&client->dev, "error when init fxas2100x:(%d)", result); + return result; +} + +static int fxas2100x_change_mode(struct i2c_client *client, int mode) +{ + u8 val; + int ret; + if (mode == ACTIVED) { + val = i2c_smbus_read_byte_data(client, FXAS2100X_CTRL_REG1); + val &= ~0x03; + val |= 0x02; + /* set bit 1 */ + ret = i2c_smbus_write_byte_data(client, FXAS2100X_CTRL_REG1, val); + } else { + val = i2c_smbus_read_byte_data(client, FXAS2100X_CTRL_REG1); + val &= (~0x03); + /* clear bit 0,1 */ + ret = i2c_smbus_write_byte_data(client, FXAS2100X_CTRL_REG1, val); + } + return ret; +} + +static int fxas2100x_set_delay(struct i2c_client *client, int delay) +{ + return 0; +} + +static int fxas2100x_device_stop(struct i2c_client *client) +{ + u8 val; + val = i2c_smbus_read_byte_data(client, FXAS2100X_CTRL_REG1); + val &= ~0x03; + i2c_smbus_write_byte_data(client, FXAS2100X_CTRL_REG1, val); + return 0; +} + +static int fxas2100x_read_data(struct fxas2100x_data *pdata, + struct fxas2100x_data_axis *data) +{ + struct i2c_client * client = pdata->client; + int x, y, z; + u8 tmp_data[FXAS2100X_BUF_SIZE]; + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, FXAS2100X_OUT_X_MSB, + FXAS2100X_BUF_SIZE, tmp_data); + if (ret < FXAS2100X_BUF_SIZE) { + dev_err(&client->dev, "i2c block read failed\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]; + if (pdata->chip_id == FXAS21000_CHIP_ID) { + x = data->x; + y = data->y; + z = data->z; + x = x * 4 / 5; + y = y * 4 / 5; + z = z * 4 / 5; + data->x = x; + data->y = y; + data->z = z; + } + + return 0; +} + +/* fxas2100x miscdevice */ +static long fxas2100x_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct fxas2100x_data *pdata = file->private_data; + void __user *argp = (void __user *)arg; + struct fxas2100x_data_axis data; + long ret = 0; + short sdata[3]; + int enable; + int delay; + + if (!pdata) { + printk(KERN_ERR "FXAS2100X struct datt point is NULL."); + return -EFAULT; + } + + switch (cmd) { + case SENSOR_GET_MODEL_NAME: + if (copy_to_user(argp, "FXAS2100X GYRO", strlen("FXAS2100X GYRO") + 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->client) { + ret = fxas2100x_change_mode(pdata->client, 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->client && delay > 0 && delay <= 500) { + ret = fxas2100x_set_delay(pdata->client, delay); + if (!ret) + atomic_set(&pdata->delay, delay); + } + break; + case SENSOR_GET_RAW_DATA: + ret = fxas2100x_read_data(pdata, &data); + if (!ret) { + fxas2100x_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 fxas2100x_open(struct inode *inode, struct file *file) +{ + file->private_data = g_fxas2100x_data; + return nonseekable_open(inode, file); +} + +static int fxas2100x_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 fxas2100x_fops = { + .owner = THIS_MODULE, + .open = fxas2100x_open, + .release = fxas2100x_release, + .unlocked_ioctl = fxas2100x_ioctl, +}; + +static struct miscdevice fxas2100x_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "FreescaleGyroscope", + .fops = &fxas2100x_fops, +}; + +static ssize_t fxas2100x_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fxas2100x_data *pdata = g_fxas2100x_data; + int enable = 0; + enable = atomic_read(&pdata->active); + return sprintf(buf, "%d\n", enable); +} + +static ssize_t fxas2100x_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fxas2100x_data *pdata = g_fxas2100x_data; + struct i2c_client *client = pdata->client; + int ret; + unsigned long enable; + + if (kstrtoul(buf, 10, &enable) < 0) + return -EINVAL; + + enable = (enable > 0) ? 1 : 0; + ret = fxas2100x_change_mode(client, (enable > 0 ? ACTIVED : STANDBY)); + if (!ret) { + atomic_set(&pdata->active, enable); + atomic_set(&pdata->active_poll, enable); + dev_err(dev, "mma enable setting active \n"); + } + return count; +} + +static ssize_t fxas2100x_poll_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fxas2100x_data *pdata = g_fxas2100x_data; + int delay = 0; + + delay = atomic_read(&pdata->delay); + return sprintf(buf, "%d\n", delay); +} + +static ssize_t fxas2100x_poll_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fxas2100x_data *pdata = g_fxas2100x_data; + struct i2c_client *client = pdata->client; + int ret; + int delay; + + delay = simple_strtoul(buf, NULL, 10); + ret = fxas2100x_set_delay(client, delay); + if (!ret) + atomic_set(&pdata->delay, delay); + return count; +} + +static ssize_t fxas2100x_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fxas2100x_data *pdata = g_fxas2100x_data; + int position = 0; + + position = atomic_read(&pdata->position); + return sprintf(buf, "%d\n", position); +} + +static ssize_t fxas2100x_position_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fxas2100x_data *pdata = g_fxas2100x_data; + int position; + + position = simple_strtoul(buf, NULL, 10); + atomic_set(&pdata->position, position); + return count; +} + +static DEVICE_ATTR(enable, 0666, fxas2100x_enable_show, fxas2100x_enable_store); +static DEVICE_ATTR(poll_delay, 0666, fxas2100x_poll_delay_show, fxas2100x_poll_delay_store); +static DEVICE_ATTR(position, 0666, fxas2100x_position_show, fxas2100x_position_store); + +static struct attribute *fxas2100x_attributes[] = { + &dev_attr_enable.attr, + &dev_attr_poll_delay.attr, + &dev_attr_position.attr, + NULL +}; + +static const struct attribute_group fxas2100x_attr_group = { + .attrs = fxas2100x_attributes, +}; + +static void fxas2100x_poll(struct input_polled_dev *dev) +{ + struct fxas2100x_data *pdata = g_fxas2100x_data; + struct input_dev *idev = pdata->input_polled->input; + struct fxas2100x_data_axis data; + int ret; + + if (!(atomic_read(&pdata->active_poll))) + return; + + ret = fxas2100x_read_data(pdata, &data); + if (!ret) { + fxas2100x_data_convert(pdata, &data); + input_report_abs(idev, ABS_X, data.x); + input_report_abs(idev, ABS_Y, data.y); + input_report_abs(idev, ABS_Z, data.z); + input_sync(idev); + } +} + +static int fxas2100x_register_polled_device(struct fxas2100x_data *pdata) +{ + struct input_polled_dev *ipoll_dev; + struct input_dev *idev; + int error; + + ipoll_dev = input_allocate_polled_device(); + if (!ipoll_dev) + return -ENOMEM; + + ipoll_dev->private = pdata; + ipoll_dev->poll = fxas2100x_poll; + ipoll_dev->poll_interval = FXAS2100X_POLL_INTERVAL; + ipoll_dev->poll_interval_min = FXAS2100X_POLL_MIN; + ipoll_dev->poll_interval_max = FXAS2100X_POLL_MAX; + idev = ipoll_dev->input; + idev->name = FXAS2100X_DRIVER; + idev->id.bustype = BUS_I2C; + idev->dev.parent = &pdata->client->dev; + + idev->evbit[0] = BIT_MASK(EV_ABS); + input_set_abs_params(idev, ABS_X, ABSMIN_GYRO_VAL, ABSMAX_GYRO_VAL, 0, 0); + input_set_abs_params(idev, ABS_Y, ABSMIN_GYRO_VAL, ABSMAX_GYRO_VAL, 0, 0); + input_set_abs_params(idev, ABS_Z, ABSMIN_GYRO_VAL, ABSMAX_GYRO_VAL, 0, 0); + + error = input_register_polled_device(ipoll_dev); + if (error) { + input_free_polled_device(ipoll_dev); + return error; + } + + pdata->input_polled = ipoll_dev; + return 0; +} + +static int fxas2100x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int result, chip_id; + struct fxas2100x_data *pdata; + struct i2c_adapter *adapter; + + adapter = to_i2c_adapter(client->dev.parent); + result = i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA); + if (!result) + goto err_out; + + chip_id = i2c_smbus_read_byte_data(client, FXAS2100X_WHO_AM_I); + if (chip_id != FXAS21000_CHIP_ID && chip_id != FXAS21002_CHID_ID_1 && + chip_id != FXAS21002_CHID_ID_2) { + dev_err(&client->dev, + "read chip ID 0x%x is not equal to 0x%x for fxas21000 or 0x%x/0x%x fxas21002!\n", + chip_id, FXAS21000_CHIP_ID, FXAS21002_CHID_ID_1, FXAS21002_CHID_ID_2); + result = -EINVAL; + goto err_out; + } + + pdata = kzalloc(sizeof(struct fxas2100x_data), GFP_KERNEL); + if (!pdata) { + result = -ENOMEM; + dev_err(&client->dev, "alloc data memory error!\n"); + goto err_out; + } + + /* Initialize the FXAS2100X chip */ + g_fxas2100x_data = pdata; + pdata->client = client; + pdata->chip_id = chip_id; + atomic_set(&pdata->delay, FXAS2100X_DELAY_DEFAULT); + atomic_set(&pdata->position, FXAS2100X_POSITION_DEFAULT); + i2c_set_clientdata(client, pdata); + result = misc_register(&fxas2100x_device); + if (result != 0) { + dev_err(&client->dev, "register acc miscdevice error"); + goto err_regsiter_misc; + } + + /* for debug */ + if (client->irq <= 0) { + result = fxas2100x_register_polled_device(g_fxas2100x_data); + if (result) + dev_err(&client->dev, + "IRQ GPIO conf. error %d, error %d\n", + client->irq, result); + } + + result = sysfs_create_group(&fxas2100x_device.this_device->kobj, + &fxas2100x_attr_group); + if (result) { + dev_err(&client->dev, "create device file failed!\n"); + result = -EINVAL; + goto err_create_sysfs; + } + fxas2100x_device_init(client); + dev_info(&client->dev, "fxas2100x device driver probe successfully\n"); + return 0; +err_create_sysfs: + misc_deregister(&fxas2100x_device); +err_regsiter_misc: + kfree(pdata); +err_out: + return result; +} + +static int fxas2100x_remove(struct i2c_client *client) +{ + struct fxas2100x_data *pdata = i2c_get_clientdata(client); + fxas2100x_device_stop(client); + if (client->irq <= 0) { + input_unregister_polled_device(pdata->input_polled); + input_free_polled_device(pdata->input_polled); + } + misc_deregister(&fxas2100x_device); + if (pdata != NULL) + kfree(pdata); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int fxas2100x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fxas2100x_data *pdata = i2c_get_clientdata(client); + + if (atomic_read(&pdata->active)) + fxas2100x_device_stop(client); + return 0; +} + +static int fxas2100x_resume(struct device *dev) +{ + int val = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fxas2100x_data *pdata = i2c_get_clientdata(client); + + if (atomic_read(&pdata->active)) { + val = i2c_smbus_read_byte_data(client, FXAS2100X_CTRL_REG1); + val &= ~0x03; + val |= 0x02; + i2c_smbus_write_byte_data(client, FXAS2100X_CTRL_REG1, val); + } + return 0; + +} +#endif + +static const struct i2c_device_id fxas2100x_id[] = { + { "fxas2100x", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, fxas2100x_id); + +static SIMPLE_DEV_PM_OPS(fxas2100x_pm_ops, fxas2100x_suspend, fxas2100x_resume); +static struct i2c_driver fxas2100x_driver = { + .driver = { + .name = FXAS2100X_DRIVER, + .owner = THIS_MODULE, + .pm = &fxas2100x_pm_ops, + }, + .probe = fxas2100x_probe, + .remove = fxas2100x_remove, + .id_table = fxas2100x_id, +}; + +module_i2c_driver(fxas2100x_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("FXAS2100X 3-Axis Gyrosope Sensor driver"); +MODULE_LICENSE("GPL"); |