| /* |
| * Copyright (c) 2014-2015, Linux Foundation. All rights reserved. |
| * Linux Foundation chooses to take subject only to the GPLv2 license |
| * terms, and distributes only under these terms. |
| * Copyright (C) 2010 MEMSIC, Inc. |
| * |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/jiffies.h> |
| #include <linux/i2c.h> |
| #include <linux/i2c-dev.h> |
| #include <linux/miscdevice.h> |
| #include <linux/mutex.h> |
| #include <linux/mm.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> |
| #include <linux/delay.h> |
| #include <linux/sysctl.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/input.h> |
| #include <linux/regmap.h> |
| #include <linux/sensors.h> |
| #include <asm/uaccess.h> |
| |
| #include "mmc3416x.h" |
| |
| #define MMC3416X_DELAY_TM_MS 10 |
| |
| #define MMC3416X_DELAY_SET_MS 75 |
| #define MMC3416X_DELAY_RESET_MS 75 |
| |
| #define MMC3416X_RETRY_COUNT 10 |
| #define MMC3416X_DEFAULT_INTERVAL_MS 100 |
| #define MMC3416X_TIMEOUT_SET_MS 15000 |
| |
| #define MMC3416X_PRODUCT_ID 0x06 |
| |
| /* POWER SUPPLY VOLTAGE RANGE */ |
| #define MMC3416X_VDD_MIN_UV 2000000 |
| #define MMC3416X_VDD_MAX_UV 3300000 |
| #define MMC3416X_VIO_MIN_UV 1750000 |
| #define MMC3416X_VIO_MAX_UV 1950000 |
| |
| enum { |
| OBVERSE_X_AXIS_FORWARD = 0, |
| OBVERSE_X_AXIS_RIGHTWARD, |
| OBVERSE_X_AXIS_BACKWARD, |
| OBVERSE_X_AXIS_LEFTWARD, |
| REVERSE_X_AXIS_FORWARD, |
| REVERSE_X_AXIS_RIGHTWARD, |
| REVERSE_X_AXIS_BACKWARD, |
| REVERSE_X_AXIS_LEFTWARD, |
| MMC3416X_DIR_COUNT, |
| }; |
| |
| static char *mmc3416x_dir[MMC3416X_DIR_COUNT] = { |
| [OBVERSE_X_AXIS_FORWARD] = "obverse-x-axis-forward", |
| [OBVERSE_X_AXIS_RIGHTWARD] = "obverse-x-axis-rightward", |
| [OBVERSE_X_AXIS_BACKWARD] = "obverse-x-axis-backward", |
| [OBVERSE_X_AXIS_LEFTWARD] = "obverse-x-axis-leftward", |
| [REVERSE_X_AXIS_FORWARD] = "reverse-x-axis-forward", |
| [REVERSE_X_AXIS_RIGHTWARD] = "reverse-x-axis-rightward", |
| [REVERSE_X_AXIS_BACKWARD] = "reverse-x-axis-backward", |
| [REVERSE_X_AXIS_LEFTWARD] = "reverse-x-axis-leftward", |
| }; |
| |
| static s8 mmc3416x_rotation_matrix[MMC3416X_DIR_COUNT][9] = { |
| [OBVERSE_X_AXIS_FORWARD] = {0, -1, 0, 1, 0, 0, 0, 0, 1}, |
| [OBVERSE_X_AXIS_RIGHTWARD] = {1, 0, 0, 0, 1, 0, 0, 0, 1}, |
| [OBVERSE_X_AXIS_BACKWARD] = {0, 1, 0, -1, 0, 0, 0, 0, 1}, |
| [OBVERSE_X_AXIS_LEFTWARD] = {-1, 0, 0, 0, -1, 0, 0, 0, 1}, |
| [REVERSE_X_AXIS_FORWARD] = {0, 1, 0, 1, 0, 0, 0, 0, -1}, |
| [REVERSE_X_AXIS_RIGHTWARD] = {1, 0, 0, 0, -1, 0, 0, 0, -1}, |
| [REVERSE_X_AXIS_BACKWARD] = {0, -1, 0, -1, 0, 0, 0, 0, -1}, |
| [REVERSE_X_AXIS_LEFTWARD] = {-1, 0, 0, 0, 1, 0, 0, 0, -1}, |
| }; |
| |
| struct mmc3416x_vec { |
| int x; |
| int y; |
| int z; |
| }; |
| |
| struct mmc3416x_data { |
| struct mutex ecompass_lock; |
| struct mutex ops_lock; |
| struct workqueue_struct *data_wq; |
| struct delayed_work dwork; |
| struct sensors_classdev cdev; |
| struct mmc3416x_vec last; |
| |
| struct i2c_client *i2c; |
| struct input_dev *idev; |
| struct regulator *vdd; |
| struct regulator *vio; |
| struct regmap *regmap; |
| |
| int dir; |
| int auto_report; |
| int enable; |
| int poll_interval; |
| int power_enabled; |
| unsigned long timeout; |
| }; |
| |
| static struct sensors_classdev sensors_cdev = { |
| .name = "mmc3416x-mag", |
| .vendor = "MEMSIC, Inc", |
| .version = 1, |
| .handle = SENSORS_MAGNETIC_FIELD_HANDLE, |
| .type = SENSOR_TYPE_MAGNETIC_FIELD, |
| .max_range = "1228.8", |
| .resolution = "0.0488228125", |
| .sensor_power = "0.35", |
| .min_delay = 10000, |
| .max_delay = 10000, |
| .fifo_reserved_event_count = 0, |
| .fifo_max_event_count = 0, |
| .enabled = 0, |
| .delay_msec = MMC3416X_DEFAULT_INTERVAL_MS, |
| .sensors_enable = NULL, |
| .sensors_poll_delay = NULL, |
| }; |
| |
| static int mmc3416x_read_xyz(struct mmc3416x_data *memsic, |
| struct mmc3416x_vec *vec) |
| { |
| int count = 0; |
| unsigned char data[6]; |
| unsigned int status; |
| struct mmc3416x_vec tmp; |
| int rc = 0; |
| |
| mutex_lock(&memsic->ecompass_lock); |
| |
| /* mmc3416x need to be set periodly to avoid overflow */ |
| if (time_after(jiffies, memsic->timeout)) { |
| rc = regmap_write(memsic->regmap, MMC3416X_REG_CTRL, |
| MMC3416X_CTRL_REFILL); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "write reg %d failed at %d.(%d)\n", |
| MMC3416X_REG_CTRL, __LINE__, rc); |
| goto exit; |
| } |
| |
| /* Time from refill cap to SET */ |
| msleep(MMC3416X_DELAY_SET_MS); |
| |
| rc = regmap_write(memsic->regmap, MMC3416X_REG_CTRL, |
| MMC3416X_CTRL_SET); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "write reg %d failed at %d.(%d)\n", |
| MMC3416X_REG_CTRL, __LINE__, rc); |
| goto exit; |
| } |
| |
| /* Wait time to complete SET/RESET */ |
| usleep_range(1000, 1500); |
| memsic->timeout = jiffies + |
| msecs_to_jiffies(MMC3416X_TIMEOUT_SET_MS); |
| |
| dev_dbg(&memsic->i2c->dev, "mmc3416x reset is done\n"); |
| |
| /* Re-send the TM command */ |
| rc = regmap_write(memsic->regmap, MMC3416X_REG_CTRL, |
| MMC3416X_CTRL_TM); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "write reg %d failed at %d.(%d)\n", |
| MMC3416X_REG_CTRL, __LINE__, rc); |
| goto exit; |
| } |
| } |
| |
| /* Read MD */ |
| rc = regmap_read(memsic->regmap, MMC3416X_REG_DS, &status); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "read reg %d failed at %d.(%d)\n", |
| MMC3416X_REG_DS, __LINE__, rc); |
| goto exit; |
| |
| } |
| |
| while ((!(status & 0x01)) && (count < MMC3416X_RETRY_COUNT)) { |
| /* Read MD again*/ |
| rc = regmap_read(memsic->regmap, MMC3416X_REG_DS, &status); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "read reg %d failed at %d.(%d)\n", |
| MMC3416X_REG_DS, __LINE__, rc); |
| goto exit; |
| |
| } |
| |
| /* Wait more time to get valid data */ |
| usleep_range(1000, 1500); |
| count++; |
| } |
| |
| if (count >= MMC3416X_RETRY_COUNT) { |
| dev_err(&memsic->i2c->dev, "TM not work!!"); |
| rc = -EFAULT; |
| goto exit; |
| } |
| |
| /* read xyz raw data */ |
| rc = regmap_bulk_read(memsic->regmap, MMC3416X_REG_DATA, data, 6); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "read reg %d failed at %d.(%d)\n", |
| MMC3416X_REG_DS, __LINE__, rc); |
| goto exit; |
| } |
| |
| tmp.x = (((u8)data[1]) << 8 | (u8)data[0]) - 32768; |
| tmp.y = (((u8)data[3]) << 8 | (u8)data[2]) - 32768; |
| tmp.z = (((u8)data[5]) << 8 | (u8)data[4]) - 32768; |
| |
| dev_dbg(&memsic->i2c->dev, "raw data:%d %d %d %d %d %d", |
| data[0], data[1], data[2], data[3], data[4], data[5]); |
| dev_dbg(&memsic->i2c->dev, "raw x:%d y:%d z:%d\n", tmp.x, tmp.y, tmp.z); |
| |
| vec->x = tmp.x; |
| vec->y = tmp.y; |
| vec->z = -tmp.z; |
| |
| exit: |
| /* send TM cmd before read */ |
| if (regmap_write(memsic->regmap, MMC3416X_REG_CTRL, MMC3416X_CTRL_TM)) { |
| dev_warn(&memsic->i2c->dev, "write reg %d failed at %d.(%d)\n", |
| MMC3416X_REG_CTRL, __LINE__, rc); |
| } |
| |
| mutex_unlock(&memsic->ecompass_lock); |
| return rc; |
| } |
| |
| static void mmc3416x_poll(struct work_struct *work) |
| { |
| int ret; |
| s8 *tmp; |
| struct mmc3416x_vec vec; |
| struct mmc3416x_vec report; |
| struct mmc3416x_data *memsic = container_of((struct delayed_work *)work, |
| struct mmc3416x_data, dwork); |
| |
| vec.x = vec.y = vec.z = 0; |
| |
| ret = mmc3416x_read_xyz(memsic, &vec); |
| if (ret) { |
| dev_warn(&memsic->i2c->dev, "read xyz failed\n"); |
| goto exit; |
| } |
| |
| tmp = &mmc3416x_rotation_matrix[memsic->dir][0]; |
| report.x = tmp[0] * vec.x + tmp[1] * vec.y + tmp[2] * vec.z; |
| report.y = tmp[3] * vec.x + tmp[4] * vec.y + tmp[5] * vec.z; |
| report.z = tmp[6] * vec.x + tmp[7] * vec.y + tmp[8] * vec.z; |
| |
| input_report_abs(memsic->idev, ABS_X, report.x); |
| input_report_abs(memsic->idev, ABS_Y, report.y); |
| input_report_abs(memsic->idev, ABS_Z, report.z); |
| input_sync(memsic->idev); |
| |
| exit: |
| queue_delayed_work(memsic->data_wq, |
| &memsic->dwork, |
| msecs_to_jiffies(memsic->poll_interval)); |
| } |
| |
| static struct input_dev *mmc3416x_init_input(struct i2c_client *client) |
| { |
| int status; |
| struct input_dev *input = NULL; |
| |
| input = devm_input_allocate_device(&client->dev); |
| if (!input) |
| return NULL; |
| |
| input->name = "compass"; |
| input->phys = "mmc3416x/input0"; |
| input->id.bustype = BUS_I2C; |
| |
| __set_bit(EV_ABS, input->evbit); |
| |
| input_set_abs_params(input, ABS_X, -2047, 2047, 0, 0); |
| input_set_abs_params(input, ABS_Y, -2047, 2047, 0, 0); |
| input_set_abs_params(input, ABS_Z, -2047, 2047, 0, 0); |
| |
| input_set_capability(input, EV_REL, REL_X); |
| input_set_capability(input, EV_REL, REL_Y); |
| input_set_capability(input, EV_REL, REL_Z); |
| |
| status = input_register_device(input); |
| if (status) { |
| dev_err(&client->dev, |
| "error registering input device\n"); |
| return NULL; |
| } |
| |
| return input; |
| } |
| |
| static int mmc3416x_power_init(struct mmc3416x_data *data) |
| { |
| int rc; |
| |
| data->vdd = devm_regulator_get(&data->i2c->dev, "vdd"); |
| if (IS_ERR(data->vdd)) { |
| rc = PTR_ERR(data->vdd); |
| dev_err(&data->i2c->dev, |
| "Regualtor get failed vdd rc=%d\n", rc); |
| return rc; |
| } |
| if (regulator_count_voltages(data->vdd) > 0) { |
| rc = regulator_set_voltage(data->vdd, |
| MMC3416X_VDD_MIN_UV, MMC3416X_VDD_MAX_UV); |
| if (rc) { |
| dev_err(&data->i2c->dev, |
| "Regulator set failed vdd rc=%d\n", |
| rc); |
| goto exit; |
| } |
| } |
| |
| rc = regulator_enable(data->vdd); |
| if (rc) { |
| dev_err(&data->i2c->dev, |
| "Regulator enable vdd failed rc=%d\n", rc); |
| goto exit; |
| } |
| data->vio = devm_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, |
| MMC3416X_VIO_MIN_UV, MMC3416X_VIO_MAX_UV); |
| if (rc) { |
| dev_err(&data->i2c->dev, |
| "Regulator set failed vio rc=%d\n", rc); |
| goto reg_vdd_set; |
| } |
| } |
| rc = regulator_enable(data->vio); |
| if (rc) { |
| dev_err(&data->i2c->dev, |
| "Regulator enable vio failed rc=%d\n", rc); |
| goto reg_vdd_set; |
| } |
| |
| /* The minimum time to operate device after VDD valid is 10 ms. */ |
| usleep_range(15000, 20000); |
| |
| data->power_enabled = true; |
| |
| return 0; |
| |
| reg_vdd_set: |
| regulator_disable(data->vdd); |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, MMC3416X_VDD_MAX_UV); |
| exit: |
| return rc; |
| |
| } |
| |
| static int mmc3416x_power_deinit(struct mmc3416x_data *data) |
| { |
| if (!IS_ERR_OR_NULL(data->vio)) { |
| if (regulator_count_voltages(data->vio) > 0) |
| regulator_set_voltage(data->vio, 0, |
| MMC3416X_VIO_MAX_UV); |
| |
| regulator_disable(data->vio); |
| } |
| |
| if (!IS_ERR_OR_NULL(data->vdd)) { |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, |
| MMC3416X_VDD_MAX_UV); |
| |
| regulator_disable(data->vdd); |
| } |
| |
| data->power_enabled = false; |
| |
| return 0; |
| } |
| |
| static int mmc3416x_power_set(struct mmc3416x_data *memsic, bool on) |
| { |
| int rc = 0; |
| |
| if (!on && memsic->power_enabled) { |
| mutex_lock(&memsic->ecompass_lock); |
| |
| rc = regulator_disable(memsic->vdd); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, |
| "Regulator vdd disable failed rc=%d\n", rc); |
| goto err_vdd_disable; |
| } |
| |
| rc = regulator_disable(memsic->vio); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, |
| "Regulator vio disable failed rc=%d\n", rc); |
| goto err_vio_disable; |
| } |
| memsic->power_enabled = false; |
| |
| mutex_unlock(&memsic->ecompass_lock); |
| return rc; |
| } else if (on && !memsic->power_enabled) { |
| mutex_lock(&memsic->ecompass_lock); |
| |
| rc = regulator_enable(memsic->vdd); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, |
| "Regulator vdd enable failed rc=%d\n", rc); |
| goto err_vdd_enable; |
| } |
| |
| rc = regulator_enable(memsic->vio); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, |
| "Regulator vio enable failed rc=%d\n", rc); |
| goto err_vio_enable; |
| } |
| memsic->power_enabled = true; |
| |
| mutex_unlock(&memsic->ecompass_lock); |
| |
| /* The minimum time to operate after VDD valid is 10 ms */ |
| usleep_range(15000, 20000); |
| |
| return rc; |
| } else { |
| dev_warn(&memsic->i2c->dev, |
| "Power on=%d. enabled=%d\n", |
| on, memsic->power_enabled); |
| return rc; |
| } |
| |
| err_vio_enable: |
| regulator_disable(memsic->vio); |
| err_vdd_enable: |
| mutex_unlock(&memsic->ecompass_lock); |
| return rc; |
| |
| err_vio_disable: |
| if (regulator_enable(memsic->vdd)) |
| dev_warn(&memsic->i2c->dev, "Regulator vdd enable failed\n"); |
| err_vdd_disable: |
| mutex_unlock(&memsic->ecompass_lock); |
| return rc; |
| } |
| static int mmc3416x_check_device(struct mmc3416x_data *memsic) |
| { |
| unsigned int data; |
| int rc; |
| |
| rc = regmap_read(memsic->regmap, MMC3416X_REG_PRODUCTID_1, &data); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "read reg %d failed.(%d)\n", |
| MMC3416X_REG_DS, rc); |
| return rc; |
| |
| } |
| |
| if (data != MMC3416X_PRODUCT_ID) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| static int mmc3416x_parse_dt(struct i2c_client *client, |
| struct mmc3416x_data *memsic) |
| { |
| struct device_node *np = client->dev.of_node; |
| const char *tmp; |
| int rc; |
| int i; |
| |
| rc = of_property_read_string(np, "memsic,dir", &tmp); |
| |
| /* does not have a value or the string is not null-terminated */ |
| if (rc && (rc != -EINVAL)) { |
| dev_err(&client->dev, "Unable to read memsic,dir\n"); |
| return rc; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(mmc3416x_dir); i++) { |
| if (strcmp(mmc3416x_dir[i], tmp) == 0) |
| break; |
| } |
| |
| if (i >= ARRAY_SIZE(mmc3416x_dir)) { |
| dev_err(&client->dev, "Invalid memsic,dir property"); |
| return -EINVAL; |
| } |
| |
| memsic->dir = i; |
| |
| if (of_property_read_bool(np, "memsic,auto-report")) |
| memsic->auto_report = 1; |
| else |
| memsic->auto_report = 0; |
| |
| return 0; |
| } |
| |
| static int mmc3416x_set_enable(struct sensors_classdev *sensors_cdev, |
| unsigned int enable) |
| { |
| int rc = 0; |
| struct mmc3416x_data *memsic = container_of(sensors_cdev, |
| struct mmc3416x_data, cdev); |
| |
| mutex_lock(&memsic->ops_lock); |
| |
| if (enable && (!memsic->enable)) { |
| rc = mmc3416x_power_set(memsic, true); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "Power up failed\n"); |
| goto exit; |
| } |
| |
| /* send TM cmd before read */ |
| rc = regmap_write(memsic->regmap, MMC3416X_REG_CTRL, |
| MMC3416X_CTRL_TM); |
| if (rc) { |
| dev_err(&memsic->i2c->dev, "write reg %d failed.(%d)\n", |
| MMC3416X_REG_CTRL, rc); |
| goto exit; |
| } |
| |
| memsic->timeout = jiffies; |
| if (memsic->auto_report) |
| queue_delayed_work(memsic->data_wq, |
| &memsic->dwork, |
| msecs_to_jiffies(memsic->poll_interval)); |
| } else if ((!enable) && memsic->enable) { |
| if (memsic->auto_report) |
| cancel_delayed_work_sync(&memsic->dwork); |
| |
| if (mmc3416x_power_set(memsic, false)) |
| dev_warn(&memsic->i2c->dev, "Power off failed\n"); |
| } else { |
| dev_warn(&memsic->i2c->dev, |
| "ignore enable state change from %d to %d\n", |
| memsic->enable, enable); |
| } |
| memsic->enable = enable; |
| |
| exit: |
| mutex_unlock(&memsic->ops_lock); |
| return rc; |
| } |
| |
| static int mmc3416x_set_poll_delay(struct sensors_classdev *sensors_cdev, |
| unsigned int delay_msec) |
| { |
| struct mmc3416x_data *memsic = container_of(sensors_cdev, |
| struct mmc3416x_data, cdev); |
| |
| mutex_lock(&memsic->ops_lock); |
| if (memsic->poll_interval != delay_msec) |
| memsic->poll_interval = delay_msec; |
| |
| if (memsic->auto_report && memsic->enable) |
| mod_delayed_work(system_wq, &memsic->dwork, |
| msecs_to_jiffies(delay_msec)); |
| mutex_unlock(&memsic->ops_lock); |
| |
| return 0; |
| } |
| |
| static struct regmap_config mmc3416x_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| }; |
| |
| static int mmc3416x_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| int res = 0; |
| struct mmc3416x_data *memsic; |
| |
| dev_dbg(&client->dev, "probing mmc3416x\n"); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| pr_err("mmc3416x i2c functionality check failed.\n"); |
| res = -ENODEV; |
| goto out; |
| } |
| |
| memsic = devm_kzalloc(&client->dev, sizeof(struct mmc3416x_data), |
| GFP_KERNEL); |
| if (!memsic) { |
| dev_err(&client->dev, "memory allocation failed.\n"); |
| res = -ENOMEM; |
| goto out; |
| } |
| |
| if (client->dev.of_node) { |
| res = mmc3416x_parse_dt(client, memsic); |
| if (res) { |
| dev_err(&client->dev, |
| "Unable to parse platform data.(%d)", res); |
| goto out; |
| } |
| } else { |
| memsic->dir = 0; |
| memsic->auto_report = 1; |
| } |
| |
| memsic->i2c = client; |
| dev_set_drvdata(&client->dev, memsic); |
| |
| mutex_init(&memsic->ecompass_lock); |
| mutex_init(&memsic->ops_lock); |
| |
| memsic->regmap = devm_regmap_init_i2c(client, &mmc3416x_regmap_config); |
| if (IS_ERR(memsic->regmap)) { |
| dev_err(&client->dev, "Init regmap failed.(%ld)", |
| PTR_ERR(memsic->regmap)); |
| res = PTR_ERR(memsic->regmap); |
| goto out; |
| } |
| |
| res = mmc3416x_power_init(memsic); |
| if (res) { |
| dev_err(&client->dev, "Power up mmc3416x failed\n"); |
| goto out; |
| } |
| |
| res = mmc3416x_check_device(memsic); |
| if (res) { |
| dev_err(&client->dev, "Check device failed\n"); |
| goto out_check_device; |
| } |
| |
| memsic->idev = mmc3416x_init_input(client); |
| if (!memsic->idev) { |
| dev_err(&client->dev, "init input device failed\n"); |
| res = -ENODEV; |
| goto out_init_input; |
| } |
| |
| memsic->data_wq = NULL; |
| if (memsic->auto_report) { |
| dev_dbg(&client->dev, "auto report is enabled\n"); |
| INIT_DELAYED_WORK(&memsic->dwork, mmc3416x_poll); |
| memsic->data_wq = |
| create_freezable_workqueue("mmc3416_data_work"); |
| if (!memsic->data_wq) { |
| dev_err(&client->dev, "Cannot create workqueue.\n"); |
| goto out_create_workqueue; |
| } |
| } |
| |
| memsic->cdev = sensors_cdev; |
| memsic->cdev.sensors_enable = mmc3416x_set_enable; |
| memsic->cdev.sensors_poll_delay = mmc3416x_set_poll_delay; |
| res = sensors_classdev_register(&client->dev, &memsic->cdev); |
| if (res) { |
| dev_err(&client->dev, "sensors class register failed.\n"); |
| goto out_register_classdev; |
| } |
| |
| res = mmc3416x_power_set(memsic, false); |
| if (res) { |
| dev_err(&client->dev, "Power off failed\n"); |
| goto out_power_set; |
| } |
| |
| memsic->poll_interval = MMC3416X_DEFAULT_INTERVAL_MS; |
| |
| dev_info(&client->dev, "mmc3416x successfully probed\n"); |
| |
| return 0; |
| |
| out_power_set: |
| sensors_classdev_unregister(&memsic->cdev); |
| out_register_classdev: |
| if (memsic->data_wq) |
| destroy_workqueue(memsic->data_wq); |
| out_create_workqueue: |
| input_unregister_device(memsic->idev); |
| out_init_input: |
| out_check_device: |
| mmc3416x_power_deinit(memsic); |
| out: |
| return res; |
| } |
| |
| static int mmc3416x_remove(struct i2c_client *client) |
| { |
| struct mmc3416x_data *memsic = dev_get_drvdata(&client->dev); |
| |
| sensors_classdev_unregister(&memsic->cdev); |
| if (memsic->data_wq) |
| destroy_workqueue(memsic->data_wq); |
| mmc3416x_power_deinit(memsic); |
| |
| if (memsic->idev) |
| input_unregister_device(memsic->idev); |
| |
| return 0; |
| } |
| |
| static int mmc3416x_suspend(struct device *dev) |
| { |
| int res = 0; |
| struct mmc3416x_data *memsic = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "suspended\n"); |
| mutex_lock(&memsic->ops_lock); |
| |
| if (memsic->enable) { |
| if (memsic->auto_report) |
| cancel_delayed_work_sync(&memsic->dwork); |
| |
| res = mmc3416x_power_set(memsic, false); |
| if (res) { |
| dev_err(dev, "failed to suspend mmc3416x\n"); |
| goto exit; |
| } |
| } |
| exit: |
| mutex_unlock(&memsic->ops_lock); |
| return res; |
| } |
| |
| static int mmc3416x_resume(struct device *dev) |
| { |
| int res = 0; |
| struct mmc3416x_data *memsic = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "resumed\n"); |
| |
| if (memsic->enable) { |
| res = mmc3416x_power_set(memsic, true); |
| if (res) { |
| dev_err(&memsic->i2c->dev, "Power enable failed\n"); |
| goto exit; |
| } |
| |
| if (memsic->auto_report) |
| queue_delayed_work(memsic->data_wq, |
| &memsic->dwork, |
| msecs_to_jiffies(memsic->poll_interval)); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| static const struct i2c_device_id mmc3416x_id[] = { |
| { MMC3416X_I2C_NAME, 0 }, |
| { } |
| }; |
| |
| static struct of_device_id mmc3416x_match_table[] = { |
| { .compatible = "memsic,mmc3416x", }, |
| { }, |
| }; |
| |
| static const struct dev_pm_ops mmc3416x_pm_ops = { |
| .suspend = mmc3416x_suspend, |
| .resume = mmc3416x_resume, |
| }; |
| |
| static struct i2c_driver mmc3416x_driver = { |
| .probe = mmc3416x_probe, |
| .remove = mmc3416x_remove, |
| .id_table = mmc3416x_id, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = MMC3416X_I2C_NAME, |
| .of_match_table = mmc3416x_match_table, |
| .pm = &mmc3416x_pm_ops, |
| }, |
| }; |
| |
| module_i2c_driver(mmc3416x_driver); |
| |
| MODULE_DESCRIPTION("MEMSIC MMC3416X Magnetic Sensor Driver"); |
| MODULE_LICENSE("GPL"); |
| |