diff options
-rw-r--r-- | drivers/input/misc/Kconfig | 9 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/misc/max77665_haptic.c | 422 | ||||
-rw-r--r-- | include/linux/input/max77665-haptic.h | 110 |
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 |