input: misc: Add maxim max77665 haptic driver support
Syed Rafiuddin [Thu, 9 Aug 2012 11:24:51 +0000 (16:24 +0530)]
Maxim max77665 is a companion pmic which includes haptic motor.

This patch adds the haptic motor driver

Bug 1157818
Bug 1043388
Bug 1157811

Change-Id: I5e395ea33426469496945d64df5b31c28ddafdf2
Signed-off-by: Syed Rafiuddin <srafiuddin@nvidia.com>
Reviewed-on: http://git-master/r/159459
Tested-by: Sumit Sharma <sumsharma@nvidia.com>
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>

drivers/input/misc/Kconfig
drivers/input/misc/Makefile
drivers/input/misc/max77665_haptic.c [new file with mode: 0644]
include/linux/input/max77665-haptic.h [new file with mode: 0644]

index b9f26e0..85844a2 100644 (file)
@@ -156,6 +156,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
index 1b766c4..b108616 100644 (file)
@@ -38,6 +38,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 (file)
index 0000000..81ae8dc
--- /dev/null
@@ -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 __devinit 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 __devexit 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         = __devexit_p(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 (file)
index 0000000..783cd79
--- /dev/null
@@ -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