summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/input/misc/Kconfig9
-rw-r--r--drivers/input/misc/Makefile1
-rw-r--r--drivers/input/misc/max77665_haptic.c422
-rw-r--r--include/linux/input/max77665-haptic.h110
4 files changed, 542 insertions, 0 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index a3e20ec08917..eb38e66ac358 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -176,6 +176,15 @@ config INPUT_MC13783_PWRBUTTON
To compile this driver as a module, choose M here: the module
will be called mc13783-pwrbutton.
+config INPUT_MAX77665_HAPTIC
+ tristate "MAXIM MAX77665 haptic controller support"
+ depends on HAVE_PWM && MFD_MAX77665
+ select INPUT_FF_MEMLESS
+ help
+ This option enables device driver support for the haptic controller
+ on MAXIM MAX77665 chip. This driver supports ff-memless interface
+ from input framework.
+
config INPUT_MMA8450
tristate "MMA8450 - Freescale's 3-Axis, 8/12-bit Digital Accelerometer"
depends on I2C
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 35c841ee8fb5..75aaade04854 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
obj-$(CONFIG_INPUT_MAX8997_HAPTIC) += max8997_haptic.o
obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o
+obj-$(CONFIG_INPUT_MAX77665_HAPTIC) += max77665_haptic.o
obj-$(CONFIG_INPUT_MMA8450) += mma8450.o
obj-$(CONFIG_INPUT_MPU3050) += mpu3050.o
obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o
diff --git a/drivers/input/misc/max77665_haptic.c b/drivers/input/misc/max77665_haptic.c
new file mode 100644
index 000000000000..2d4fc6988eb0
--- /dev/null
+++ b/drivers/input/misc/max77665_haptic.c
@@ -0,0 +1,422 @@
+/*
+ * MAX77665-haptic controller driver
+ *
+ * Copyright (c) 2012, NVIDIA Corporation, All Rights Reserved.
+ *
+ * Based on driver max8997_haptic.c
+ * Copyright (c) 2012 Samsung Electronics
+ *
+ * This program is not provided / owned by Maxim Integrated Products.
+ *
+ * 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/platform_device.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/input.h>
+#include <linux/mfd/max77665.h>
+#include <linux/input/max77665-haptic.h>
+#include <linux/regulator/consumer.h>
+
+struct max77665_haptic {
+ struct device *dev;
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct regulator *regulator;
+ struct work_struct work;
+
+ bool enabled;
+
+ unsigned int level;
+
+ struct pwm_device *pwm;
+ int pwm_period;
+ enum max77665_haptic_pwm_divisor pwm_divisor;
+
+ enum max77665_haptic_motor_type type;
+ enum max77665_haptic_pulse_mode mode;
+ enum max77665_haptic_invert invert;
+ enum max77665_haptic_continous_mode cont_mode;
+
+ int internal_mode_pattern;
+ int pattern_cycle;
+ int pattern_signal_period;
+ int feedback_duty_cycle;
+ int motor_startup_val;
+ int scf_val;
+};
+
+static int max77665_read_reg(struct max77665_haptic *chip, u8 reg, u8 *val)
+{
+ int ret;
+ struct device *dev = chip->dev;
+
+ ret = max77665_read(dev->parent, MAX77665_I2C_SLAVE_HAPTIC, reg, val);
+ return ret;
+}
+
+static int max77665_write_reg(struct max77665_haptic *chip, u8 reg, u8 value)
+{
+ int ret;
+ struct device *dev = chip->dev;
+
+ ret = max77665_write(dev->parent, MAX77665_I2C_SLAVE_HAPTIC,
+ reg, value);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int max77665_haptic_set_duty_cycle(struct max77665_haptic *chip)
+{
+ int duty, i;
+ u8 duty_index = 0;
+ int ret = 0;
+ int reg1, reg2;
+ bool internal_mode_valid = true;
+
+ if (chip->mode == MAX77665_EXTERNAL_MODE) {
+ duty = chip->pwm_period * chip->level / 100;
+ ret = pwm_config(chip->pwm, duty, chip->pwm_period);
+ } else {
+ for (i = 0; i < 64; i++) {
+ if (chip->level <= (i + 1) * 100 / 64) {
+ duty_index = i;
+ break;
+ }
+ }
+ switch (chip->internal_mode_pattern) {
+ case 0:
+ reg1 = MAX77665_HAPTIC_REG_SIGDC1;
+ reg2 = MAX77665_HAPTIC_REG_SIGPWMDC1;
+ break;
+ case 1:
+ reg1 = MAX77665_HAPTIC_REG_SIGDC1;
+ reg2 = MAX77665_HAPTIC_REG_SIGPWMDC2;
+ break;
+ case 2:
+ reg1 = MAX77665_HAPTIC_REG_SIGDC2;
+ reg2 = MAX77665_HAPTIC_REG_SIGPWMDC3;
+ break;
+ case 3:
+ reg1 = MAX77665_HAPTIC_REG_SIGDC1;
+ reg2 = MAX77665_HAPTIC_REG_SIGPWMDC4;
+ break;
+ default:
+ internal_mode_valid = false;
+ break;
+ }
+
+ if (internal_mode_valid) {
+ if (chip->internal_mode_pattern % 2 == 1)
+ max77665_write_reg(chip, reg1,
+ chip->feedback_duty_cycle);
+ else
+ max77665_write_reg(chip, reg1,
+ chip->feedback_duty_cycle << 4);
+
+ max77665_write_reg(chip, reg2, duty_index);
+ }
+ }
+ return ret;
+}
+
+static void max77665_haptic_configure(struct max77665_haptic *chip)
+{
+ u8 value1, value2, reg1, reg2;
+ bool internal_mode_valid = true;
+
+ value1 = chip->type << MAX77665_MOTOR_TYPE_SHIFT |
+ chip->enabled << MAX77665_ENABLE_SHIFT |
+ chip->mode << MAX77665_MODE_SHIFT | chip->pwm_divisor;
+ max77665_write_reg(chip, MAX77665_HAPTIC_REG_CONF2, value1);
+
+ if (chip->mode == MAX77665_INTERNAL_MODE && chip->enabled) {
+ max77665_write(chip->dev->parent, MAX77665_I2C_SLAVE_PMIC,
+ MAX77665_PMIC_REG_LSCNFG, 0xAA);
+ value1 = chip->invert << MAX77665_INVERT_SHIFT |
+ chip->cont_mode << MAX77665_CONT_MODE_SHIFT |
+ chip->motor_startup_val << MAX77665_MOTOR_STRT_SHIFT |
+ chip->scf_val;
+
+ max77665_write_reg(chip, MAX77665_HAPTIC_REG_CONF1, value1);
+
+ switch (chip->internal_mode_pattern) {
+ case 0:
+ value1 = chip->pattern_cycle << 4;
+ reg1 = MAX77665_HAPTIC_REG_CYCLECONF1;
+ value2 = chip->pattern_signal_period;
+ reg2 = MAX77665_HAPTIC_REG_SIGCONF1;
+ break;
+ case 1:
+ value1 = chip->pattern_cycle;
+ reg1 = MAX77665_HAPTIC_REG_CYCLECONF1;
+ value2 = chip->pattern_signal_period;
+ reg2 = MAX77665_HAPTIC_REG_SIGCONF2;
+ break;
+ case 2:
+ value1 = chip->pattern_cycle << 4;
+ reg1 = MAX77665_HAPTIC_REG_CYCLECONF2;
+ value2 = chip->pattern_signal_period;
+ reg2 = MAX77665_HAPTIC_REG_SIGCONF3;
+ break;
+ case 3:
+ value1 = chip->pattern_cycle;
+ reg1 = MAX77665_HAPTIC_REG_CYCLECONF2;
+ value2 = chip->pattern_signal_period;
+ reg2 = MAX77665_HAPTIC_REG_SIGCONF4;
+ break;
+ default:
+ internal_mode_valid = false;
+ break;
+ }
+
+ if (internal_mode_valid) {
+ max77665_write_reg(chip, reg1, value1);
+ max77665_write_reg(chip, reg2, value2);
+ value1 = chip->internal_mode_pattern
+ << MAX77665_CYCLE_SHIFT |
+ chip->internal_mode_pattern
+ << MAX77665_SIG_PERIOD_SHIFT |
+ chip->internal_mode_pattern
+ << MAX77665_SIG_DUTY_SHIFT |
+ chip->internal_mode_pattern
+ << MAX77665_PWM_DUTY_SHIFT;
+ max77665_write_reg(chip,
+ MAX77665_HAPTIC_REG_DRVCONF, value1);
+ }
+ }
+}
+
+static void max77665_haptic_enable(struct max77665_haptic *chip, bool enable)
+{
+ if (chip->enabled == enable)
+ return;
+
+ chip->enabled = enable;
+
+ if (enable) {
+ regulator_enable(chip->regulator);
+ max77665_haptic_configure(chip);
+ if (chip->mode == MAX77665_EXTERNAL_MODE)
+ pwm_enable(chip->pwm);
+ } else {
+ max77665_haptic_configure(chip);
+ if (chip->mode == MAX77665_EXTERNAL_MODE)
+ pwm_disable(chip->pwm);
+ regulator_disable(chip->regulator);
+ }
+}
+
+static void max77665_haptic_play_effect_work(struct work_struct *work)
+{
+ struct max77665_haptic *chip =
+ container_of(work, struct max77665_haptic, work);
+ int ret;
+
+ if (chip->level) {
+ ret = max77665_haptic_set_duty_cycle(chip);
+ if (ret) {
+ dev_err(chip->dev, "set_pwm_cycle failed\n");
+ return;
+ }
+ max77665_haptic_enable(chip, true);
+ } else
+ max77665_haptic_enable(chip, false);
+}
+
+
+static int max77665_haptic_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct max77665_haptic *chip = input_get_drvdata(dev);
+
+ chip->level = effect->u.rumble.strong_magnitude;
+ if (!chip->level)
+ chip->level = effect->u.rumble.weak_magnitude;
+
+ schedule_work(&chip->work);
+
+ return 0;
+}
+
+static int max77665_haptic_probe(struct platform_device *pdev)
+{
+ struct max77665_haptic_platform_data *haptic_pdata =
+ pdev->dev.platform_data;
+ struct max77665_haptic *chip;
+ struct input_dev *input_dev;
+ int ret;
+
+ if (!haptic_pdata) {
+ dev_err(&pdev->dev, "no haptic platform data\n");
+ return -EINVAL;
+ }
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(struct max77665_haptic),
+ GFP_KERNEL);
+ if (!chip) {
+ dev_err(&pdev->dev, "unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(&pdev->dev,
+ "unable to allocate memory for input dev\n");
+ ret = -ENOMEM;
+ goto err_input_alloc;
+ }
+
+ chip->dev = &pdev->dev;
+ chip->input_dev = input_dev;
+ chip->pwm_period = haptic_pdata->pwm_period;
+ chip->type = haptic_pdata->type;
+ chip->mode = haptic_pdata->mode;
+ chip->pwm_divisor = haptic_pdata->pwm_divisor;
+
+ if (chip->mode == MAX77665_INTERNAL_MODE) {
+ chip->internal_mode_pattern =
+ haptic_pdata->internal_mode_pattern;
+ chip->pattern_cycle = haptic_pdata->pattern_cycle;
+ chip->pattern_signal_period =
+ haptic_pdata->pattern_signal_period;
+ chip->feedback_duty_cycle =
+ haptic_pdata->feedback_duty_cycle;
+ chip->invert = haptic_pdata->invert;
+ chip->cont_mode = haptic_pdata->cont_mode;
+ chip->motor_startup_val = haptic_pdata->motor_startup_val;
+ chip->scf_val = haptic_pdata->scf_val;
+ }
+
+ if (chip->mode == MAX77665_EXTERNAL_MODE) {
+ chip->pwm = pwm_request(haptic_pdata->pwm_channel_id,
+ "max-vbrtr");
+ if (IS_ERR(chip->pwm)) {
+ dev_err(&pdev->dev,
+ "unable to request PWM for haptic\n");
+ ret = PTR_ERR(chip->pwm);
+ goto err_pwm;
+ }
+ }
+
+ chip->regulator = regulator_get(&pdev->dev, "vdd_vbrtr");
+ if (IS_ERR(chip->regulator)) {
+ dev_err(&pdev->dev, "unable to get regulator\n");
+ ret = PTR_ERR(chip->regulator);
+ goto err_regulator;
+ }
+
+ dev_set_drvdata(&pdev->dev, chip);
+
+ input_dev->name = "max77665-haptic";
+ input_dev->id.version = 1;
+ input_dev->dev.parent = &pdev->dev;
+ input_set_drvdata(input_dev, chip);
+ input_set_capability(input_dev, EV_FF, FF_RUMBLE);
+
+ ret = input_ff_create_memless(input_dev, NULL,
+ max77665_haptic_play_effect);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "unable to create FF device(ret : %d)\n", ret);
+ goto err_ff_memless;
+ }
+ INIT_WORK(&chip->work,
+ max77665_haptic_play_effect_work);
+
+ ret = input_register_device(input_dev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "unable to register input device(ret : %d)\n", ret);
+ goto err_input_register;
+ }
+
+ return 0;
+
+err_input_register:
+ destroy_work_on_stack(&chip->work);
+ input_ff_destroy(input_dev);
+err_ff_memless:
+ regulator_put(chip->regulator);
+err_regulator:
+ if (chip->mode == MAX77665_EXTERNAL_MODE)
+ pwm_free(chip->pwm);
+err_pwm:
+ input_free_device(input_dev);
+err_input_alloc:
+ kfree(chip);
+
+ return ret;
+}
+
+static int max77665_haptic_remove(struct platform_device *pdev)
+{
+ struct max77665_haptic *chip = platform_get_drvdata(pdev);
+
+ destroy_work_on_stack(&chip->work);
+ input_unregister_device(chip->input_dev);
+ regulator_put(chip->regulator);
+
+ if (chip->mode == MAX77665_EXTERNAL_MODE)
+ pwm_free(chip->pwm);
+
+ kfree(chip);
+
+ return 0;
+}
+
+static int max77665_haptic_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct max77665_haptic *chip = platform_get_drvdata(pdev);
+ struct input_dev *input_dev = chip->input_dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&input_dev->event_lock, flags);
+ max77665_haptic_enable(chip, false);
+ spin_unlock_irqrestore(&input_dev->event_lock, flags);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max77665_haptic_pm_ops, max77665_haptic_suspend, NULL);
+
+static const struct platform_device_id max77665_haptic_id[] = {
+ { "max77665-haptic", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, max77665_haptic_id);
+
+static struct platform_driver max77665_haptic_driver = {
+ .driver = {
+ .name = "max77665-haptic",
+ .owner = THIS_MODULE,
+ .pm = &max77665_haptic_pm_ops,
+ },
+ .probe = max77665_haptic_probe,
+ .remove = max77665_haptic_remove,
+ .id_table = max77665_haptic_id,
+};
+
+module_platform_driver(max77665_haptic_driver);
+
+MODULE_ALIAS("platform:max77665-haptic");
+MODULE_DESCRIPTION("max77665_haptic driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/input/max77665-haptic.h b/include/linux/input/max77665-haptic.h
new file mode 100644
index 000000000000..783cd7906f8b
--- /dev/null
+++ b/include/linux/input/max77665-haptic.h
@@ -0,0 +1,110 @@
+/*
+ * MAX77665 Haptic Driver
+ *
+ * Copyright (c) 2012, NVIDIA Corporation, All Rights Reserved
+ * Author: Syed Rafiuddin <srafiuddin@nvidia.com>
+ *
+ * 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.
+ */
+
+#ifndef _LINUX_INPUT_MAX77665_HAPTIC_H
+#define _LINUX_INPUT_MAX77665_HAPTIC_H
+
+#include <linux/platform_device.h>
+#include <linux/mfd/max77665.h>
+
+#define MAX77665_HAPTIC_REG_GENERAL 0x00
+#define MAX77665_HAPTIC_REG_CONF1 0x01
+#define MAX77665_HAPTIC_REG_CONF2 0x02
+#define MAX77665_HAPTIC_REG_DRVCONF 0x03
+#define MAX77665_HAPTIC_REG_CYCLECONF1 0x04
+#define MAX77665_HAPTIC_REG_CYCLECONF2 0x05
+#define MAX77665_HAPTIC_REG_SIGCONF1 0x06
+#define MAX77665_HAPTIC_REG_SIGCONF2 0x07
+#define MAX77665_HAPTIC_REG_SIGCONF3 0x08
+#define MAX77665_HAPTIC_REG_SIGCONF4 0x09
+#define MAX77665_HAPTIC_REG_SIGDC1 0x0a
+#define MAX77665_HAPTIC_REG_SIGDC2 0x0b
+#define MAX77665_HAPTIC_REG_SIGPWMDC1 0x0c
+#define MAX77665_HAPTIC_REG_SIGPWMDC2 0x0d
+#define MAX77665_HAPTIC_REG_SIGPWMDC3 0x0e
+#define MAX77665_HAPTIC_REG_SIGPWMDC4 0x0f
+#define MAX77665_HAPTIC_REG_MTR_REV 0x10
+#define MAX77665_HAPTIC_REG_END 0x11
+
+#define MAX77665_PMIC_REG_LSCNFG 0x2B
+
+/* Haptic configuration 1 register */
+#define MAX77665_INVERT_SHIFT 7
+#define MAX77665_CONT_MODE_SHIFT 6
+#define MAX77665_MOTOR_STRT_SHIFT 3
+
+/* Haptic configuration 2 register */
+#define MAX77665_MOTOR_TYPE_SHIFT 7
+#define MAX77665_ENABLE_SHIFT 6
+#define MAX77665_MODE_SHIFT 5
+
+/* Haptic driver configuration register */
+#define MAX77665_CYCLE_SHIFT 6
+#define MAX77665_SIG_PERIOD_SHIFT 4
+#define MAX77665_SIG_DUTY_SHIFT 2
+#define MAX77665_PWM_DUTY_SHIFT 0
+
+enum max77665_haptic_motor_type {
+ MAX77665_HAPTIC_ERM,
+ MAX77665_HAPTIC_LRA,
+};
+
+enum max77665_haptic_pulse_mode {
+ MAX77665_EXTERNAL_MODE,
+ MAX77665_INTERNAL_MODE,
+};
+
+enum max77665_haptic_pwm_divisor {
+ MAX77665_PWM_DIVISOR_32,
+ MAX77665_PWM_DIVISOR_64,
+ MAX77665_PWM_DIVISOR_128,
+ MAX77665_PWM_DIVISOR_256,
+};
+
+enum max77665_haptic_invert {
+ MAX77665_INVERT_OFF,
+ MAX77665_INVERT_ON,
+};
+
+enum max77665_haptic_continous_mode {
+ MAX77665_NORMAL_MODE,
+ MAX77665_CONT_MODE,
+};
+
+struct max77665_haptic_platform_data {
+ int pwm_channel_id;
+ int pwm_period;
+
+ enum max77665_haptic_motor_type type;
+ enum max77665_haptic_pulse_mode mode;
+ enum max77665_haptic_pwm_divisor pwm_divisor;
+ enum max77665_haptic_invert invert;
+ enum max77665_haptic_continous_mode cont_mode;
+
+ int internal_mode_pattern;
+ int pattern_cycle;
+ int pattern_signal_period;
+ int feedback_duty_cycle;
+ int motor_startup_val;
+ int scf_val;
+};
+
+#endif