* [PATCH v3 2/2] input: add driver support for MAX8997-haptic @ 2012-03-12 8:31 Chanwoo Choi 2012-03-14 8:46 ` Dmitry Torokhov 0 siblings, 1 reply; 9+ messages in thread From: Chanwoo Choi @ 2012-03-12 8:31 UTC (permalink / raw) To: linux-input, Dmitry Torokhov Cc: sameo, broonie, linux-kernel, kyungmin.park, myungjoo.ham The MAX8997-haptic function can be used to control motor. User can control the haptic driver by using force feedback framework. This patch supports the haptic function in MAX8997. Signed-off-by: Donggeun Kim <dg77.kim@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Tested-by: Chanwoo Choi <cw00.choi@samsung.com> --- v2 - code clean and remove unnecessary code according to comment of Dmitry Torokhov v3 - use work_struct to enable/disable max8997-haptic instead of using mutex in irq handler statck of forced feedbacky drivers/input/misc/Kconfig | 12 + drivers/input/misc/Makefile | 1 + drivers/input/misc/max8997_haptic.c | 377 +++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+), 0 deletions(-) create mode 100644 drivers/input/misc/max8997_haptic.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 7b46781..c828c2e 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -134,6 +134,18 @@ config INPUT_MAX8925_ONKEY To compile this driver as a module, choose M here: the module will be called max8925_onkey. +config INPUT_MAX8997_HAPTIC + tristate "MAXIM MAX8997 haptic controller support" + depends on HAVE_PWM && MFD_MAX8997 + select INPUT_FF_MEMLESS + help + This option enables device driver support for the haptic controller + on MAXIM MAX8997 chip. This driver supports ff-memless interface + from input framework. + + To compile this driver as module, choose M here: the + module will be called max8997-haptic. + config INPUT_MC13783_PWRBUTTON tristate "MC13783 ON buttons" depends on MFD_MC13783 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 46671a8..f0574a6 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o 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_MMA8450) += mma8450.o obj-$(CONFIG_INPUT_MPU3050) += mpu3050.o diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c new file mode 100644 index 0000000..ee02d7e --- /dev/null +++ b/drivers/input/misc/max8997_haptic.c @@ -0,0 +1,377 @@ +/* + * MAX8997-haptic controller driver + * + * Copyright (C) 2012 Samsung Electronics + * Donggeun Kim <dg77.kim@samsung.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/max8997-private.h> +#include <linux/mfd/max8997.h> +#include <linux/regulator/consumer.h> + +/* Haptic configuration 2 register */ +#define MAX8997_MOTOR_TYPE_SHIFT 7 +#define MAX8997_ENABLE_SHIFT 6 +#define MAX8997_MODE_SHIFT 5 + +/* Haptic driver configuration register */ +#define MAX8997_CYCLE_SHIFT 6 +#define MAX8997_SIG_PERIOD_SHIFT 4 +#define MAX8997_SIG_DUTY_SHIFT 2 +#define MAX8997_PWM_DUTY_SHIFT 0 + +struct max8997_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 max8997_haptic_pwm_divisor pwm_divisor; + + enum max8997_haptic_motor_type type; + enum max8997_haptic_pulse_mode mode; + + int internal_mode_pattern; + int pattern_cycle; + int pattern_signal_period; +}; + +static int max8997_haptic_set_duty_cycle(struct max8997_haptic *chip) +{ + int duty, i; + int ret; + u8 duty_index; + + if (chip->mode == MAX8997_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 * 100 / 64) { + duty_index = i; + break; + } + } + switch (chip->internal_mode_pattern) { + case 0: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC1, duty_index); + break; + case 1: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC2, duty_index); + break; + case 2: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC3, duty_index); + break; + case 3: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC4, duty_index); + break; + default: + break; + } + } + return ret; +} + +static void max8997_haptic_configure(struct max8997_haptic *chip) +{ + u8 value; + + value = chip->type << MAX8997_MOTOR_TYPE_SHIFT | + chip->enabled << MAX8997_ENABLE_SHIFT | + chip->mode << MAX8997_MODE_SHIFT | chip->pwm_divisor; + max8997_write_reg(chip->client, MAX8997_HAPTIC_REG_CONF2, value); + + if (chip->mode == MAX8997_INTERNAL_MODE && chip->enabled) { + value = chip->internal_mode_pattern << MAX8997_CYCLE_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_PERIOD_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_DUTY_SHIFT | + chip->internal_mode_pattern << MAX8997_PWM_DUTY_SHIFT; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_DRVCONF, value); + + switch (chip->internal_mode_pattern) { + case 0: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF1, value); + break; + case 1: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF2, value); + break; + case 2: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF3, value); + break; + case 3: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF4, value); + break; + default: + break; + } + } +} + +static void max8997_haptic_enable(struct max8997_haptic *chip, bool enable) +{ + if (chip->enabled == enable) + return; + + chip->enabled = enable; + + if (enable) { + regulator_enable(chip->regulator); + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_enable(chip->pwm); + } else { + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_disable(chip->pwm); + regulator_disable(chip->regulator); + } +} + +static void max8997_haptic_play_effect_work(struct work_struct *work) +{ + struct max8997_haptic *chip = + container_of(work, struct max8997_haptic, work); + int ret; + + if (chip->level) { + ret = max8997_haptic_set_duty_cycle(chip); + if (ret) { + dev_err(chip->dev, "set_pwm_cycle failed\n"); + return; + } + max8997_haptic_enable(chip, true); + } else + max8997_haptic_enable(chip, false); +} + + +static int max8997_haptic_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct max8997_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 max8997_haptic_probe(struct platform_device *pdev) +{ + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); + struct max8997_haptic_platform_data *haptic_pdata = + pdata->haptic_pdata; + struct max8997_haptic *chip; + struct input_dev *input_dev; + int ret; + + if (!haptic_pdata) { + dev_err(&pdev->dev, "no haptic platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct max8997_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->client = iodev->haptic; + 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 == MAX8997_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; + } + + if (chip->mode == MAX8997_EXTERNAL_MODE) { + chip->pwm = pwm_request(haptic_pdata->pwm_channel_id, + "max8997-haptic"); + 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, "inmotor"); + if (IS_ERR(chip->regulator)) { + dev_err(&pdev->dev, "unable to get regulator\n"); + ret = PTR_ERR(chip->regulator); + goto err_regulator; + } + + platform_set_drvdata(pdev, chip); + + input_dev->name = "max8997-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, + max8997_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, + max8997_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 == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); +err_pwm: + input_free_device(input_dev); +err_input_alloc: + kfree(chip); + + return ret; +} + +static int __devexit max8997_haptic_remove(struct platform_device *pdev) +{ + struct max8997_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 == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); + + kfree(chip); + + return 0; +} + +static int max8997_haptic_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct max8997_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); + max8997_haptic_enable(chip, false); + spin_unlock_irqrestore(&input_dev->event_lock, flags); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(max8997_haptic_pm_ops, max8997_haptic_suspend, NULL); + +static const struct platform_device_id max8997_haptic_id[] = { + { "max8997-haptic", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max8997_haptic_id); + +static struct platform_driver max8997_haptic_driver = { + .driver = { + .name = "max8997-haptic", + .owner = THIS_MODULE, + .pm = &max8997_haptic_pm_ops, + }, + .probe = max8997_haptic_probe, + .remove = __devexit_p(max8997_haptic_remove), + .id_table = max8997_haptic_id, +}; + +module_platform_driver(max8997_haptic_driver); + +MODULE_ALIAS("platform:max8997-haptic"); +MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); +MODULE_DESCRIPTION("max8997_haptic driver"); +MODULE_LICENSE("GPL"); -- 1.7.0.4 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-12 8:31 [PATCH v3 2/2] input: add driver support for MAX8997-haptic Chanwoo Choi @ 2012-03-14 8:46 ` Dmitry Torokhov 2012-03-14 11:54 ` Chanwoo Choi 2012-03-14 14:47 ` Chanwoo Choi 0 siblings, 2 replies; 9+ messages in thread From: Dmitry Torokhov @ 2012-03-14 8:46 UTC (permalink / raw) To: Chanwoo Choi Cc: linux-input, sameo, broonie, linux-kernel, kyungmin.park, myungjoo.ham Hi Chanwoo, On Mon, Mar 12, 2012 at 05:31:21PM +0900, Chanwoo Choi wrote: > + > +static int __devexit max8997_haptic_remove(struct platform_device *pdev) > +{ > + struct max8997_haptic *chip = platform_get_drvdata(pdev); > + > + destroy_work_on_stack(&chip->work); This does not make sense because: - chip->work is not on stack - even if you "destroy" work nothing stops play effect to schedule a new one - you need to cancel work in close method. > + input_unregister_device(chip->input_dev); > + regulator_put(chip->regulator); > + > + if (chip->mode == MAX8997_EXTERNAL_MODE) > + pwm_free(chip->pwm); > + > + kfree(chip); > + > + return 0; > +} > + > +static int max8997_haptic_suspend(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct max8997_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); > + max8997_haptic_enable(chip, false); > + spin_unlock_irqrestore(&input_dev->event_lock, flags); > + This is not proper locking between playing effect and suspend. Now that you are using workqueue you need to synchronize access with it, not with play_effect method. Does the following patch work for you? Thanks. -- Dmitry Input: add driver support for MAX8997-haptic From: Donggeun Kim <dg77.kim@samsung.com> The MAX8997-haptic function can be used to control motor. User can control the haptic driver by using force feedback framework. Signed-off-by: Donggeun Kim <dg77.kim@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru> --- drivers/input/misc/Kconfig | 12 + drivers/input/misc/Makefile | 1 drivers/input/misc/max8997_haptic.c | 407 +++++++++++++++++++++++++++++++++++ include/linux/mfd/max8997.h | 53 ++++- 4 files changed, 472 insertions(+), 1 deletions(-) create mode 100644 drivers/input/misc/max8997_haptic.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 2337c3e..ee077a4 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -134,6 +134,18 @@ config INPUT_MAX8925_ONKEY To compile this driver as a module, choose M here: the module will be called max8925_onkey. +config INPUT_MAX8997_HAPTIC + tristate "MAXIM MAX8997 haptic controller support" + depends on HAVE_PWM && MFD_MAX8997 + select INPUT_FF_MEMLESS + help + This option enables device driver support for the haptic controller + on MAXIM MAX8997 chip. This driver supports ff-memless interface + from input framework. + + To compile this driver as module, choose M here: the + module will be called max8997-haptic. + config INPUT_MC13783_PWRBUTTON tristate "MC13783 ON buttons" depends on MFD_MC13783 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index a6d8de0..f55cdf4 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o 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_MMA8450) += mma8450.o obj-$(CONFIG_INPUT_MPU3050) += mpu3050.o diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c new file mode 100644 index 0000000..d968c37 --- /dev/null +++ b/drivers/input/misc/max8997_haptic.c @@ -0,0 +1,407 @@ +/* + * MAX8997-haptic controller driver + * + * Copyright (C) 2012 Samsung Electronics + * Donggeun Kim <dg77.kim@samsung.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/max8997-private.h> +#include <linux/mfd/max8997.h> +#include <linux/regulator/consumer.h> + +/* Haptic configuration 2 register */ +#define MAX8997_MOTOR_TYPE_SHIFT 7 +#define MAX8997_ENABLE_SHIFT 6 +#define MAX8997_MODE_SHIFT 5 + +/* Haptic driver configuration register */ +#define MAX8997_CYCLE_SHIFT 6 +#define MAX8997_SIG_PERIOD_SHIFT 4 +#define MAX8997_SIG_DUTY_SHIFT 2 +#define MAX8997_PWM_DUTY_SHIFT 0 + +struct max8997_haptic { + struct device *dev; + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *regulator; + + struct work_struct work; + struct mutex mutex; + + bool enabled; + unsigned int level; + + struct pwm_device *pwm; + unsigned int pwm_period; + enum max8997_haptic_pwm_divisor pwm_divisor; + + enum max8997_haptic_motor_type type; + enum max8997_haptic_pulse_mode mode; + + unsigned int internal_mode_pattern; + unsigned int pattern_cycle; + unsigned int pattern_signal_period; +}; + +static int max8997_haptic_set_duty_cycle(struct max8997_haptic *chip) +{ + int ret = 0; + + if (chip->mode == MAX8997_EXTERNAL_MODE) { + unsigned int duty = chip->pwm_period * chip->level / 100; + ret = pwm_config(chip->pwm, duty, chip->pwm_period); + } else { + int i; + u8 duty_index = 0; + + for (i = 0; i <= 64; i++) { + if (chip->level <= i * 100 / 64) { + duty_index = i; + break; + } + } + switch (chip->internal_mode_pattern) { + case 0: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC1, duty_index); + break; + case 1: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC2, duty_index); + break; + case 2: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC3, duty_index); + break; + case 3: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC4, duty_index); + break; + default: + break; + } + } + return ret; +} + +static void max8997_haptic_configure(struct max8997_haptic *chip) +{ + u8 value; + + value = chip->type << MAX8997_MOTOR_TYPE_SHIFT | + chip->enabled << MAX8997_ENABLE_SHIFT | + chip->mode << MAX8997_MODE_SHIFT | chip->pwm_divisor; + max8997_write_reg(chip->client, MAX8997_HAPTIC_REG_CONF2, value); + + if (chip->mode == MAX8997_INTERNAL_MODE && chip->enabled) { + value = chip->internal_mode_pattern << MAX8997_CYCLE_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_PERIOD_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_DUTY_SHIFT | + chip->internal_mode_pattern << MAX8997_PWM_DUTY_SHIFT; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_DRVCONF, value); + + switch (chip->internal_mode_pattern) { + case 0: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF1, value); + break; + + case 1: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF2, value); + break; + + case 2: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF3, value); + break; + + case 3: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF4, value); + break; + + default: + break; + } + } +} + +static void max8997_haptic_enable(struct max8997_haptic *chip) +{ + int error; + + mutex_lock(&chip->mutex); + + error = max8997_haptic_set_duty_cycle(chip); + if (error) { + dev_err(chip->dev, "set_pwm_cycle failed, error: %d\n", error); + goto out; + } + + if (!chip->enabled) { + regulator_enable(chip->regulator); + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_enable(chip->pwm); + chip->enabled = true; + } + +out: + mutex_unlock(&chip->mutex); +} + +static void max8997_haptic_disable(struct max8997_haptic *chip) +{ + mutex_lock(&chip->mutex); + + if (chip->enabled) { + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_disable(chip->pwm); + regulator_disable(chip->regulator); + chip->enabled = false; + } + + mutex_unlock(&chip->mutex); +} + +static void max8997_haptic_play_effect_work(struct work_struct *work) +{ + struct max8997_haptic *chip = + container_of(work, struct max8997_haptic, work); + + if (chip->level) + max8997_haptic_enable(chip); + else + max8997_haptic_disable(chip); +} + +static int max8997_haptic_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct max8997_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 void max8997_haptic_close(struct input_dev *dev) +{ + struct max8997_haptic *chip = input_get_drvdata(dev); + + cancel_work_sync(&chip->work); + max8997_haptic_disable(chip); +} + +static int __devinit max8997_haptic_probe(struct platform_device *pdev) +{ + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + const struct max8997_platform_data *pdata = + dev_get_platdata(iodev->dev); + const struct max8997_haptic_platform_data *haptic_pdata = + pdata->haptic_pdata; + struct max8997_haptic *chip; + struct input_dev *input_dev; + int error; + + if (!haptic_pdata) { + dev_err(&pdev->dev, "no haptic platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct max8997_haptic), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!chip || !input_dev) { + dev_err(&pdev->dev, "unable to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + INIT_WORK(&chip->work, max8997_haptic_play_effect_work); + mutex_init(&chip->mutex); + + chip->client = iodev->haptic; + 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; + + switch (chip->mode) { + case MAX8997_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; + break; + + case MAX8997_EXTERNAL_MODE: + chip->pwm = pwm_request(haptic_pdata->pwm_channel_id, + "max8997-haptic"); + if (IS_ERR(chip->pwm)) { + error = PTR_ERR(chip->pwm); + dev_err(&pdev->dev, + "unable to request PWM for haptic, error: %d\n", + error); + goto err_free_mem; + } + break; + + default: + dev_err(&pdev->dev, + "Invalid chip mode specified (%d)\n", chip->mode); + error = -EINVAL; + goto err_free_mem; + } + + chip->regulator = regulator_get(&pdev->dev, "inmotor"); + if (IS_ERR(chip->regulator)) { + error = PTR_ERR(chip->regulator); + dev_err(&pdev->dev, + "unable to get regulator, error: %d\n", + error); + goto err_free_pwm; + } + + input_dev->name = "max8997-haptic"; + input_dev->id.version = 1; + input_dev->dev.parent = &pdev->dev; + input_dev->close = max8997_haptic_close; + input_set_drvdata(input_dev, chip); + input_set_capability(input_dev, EV_FF, FF_RUMBLE); + + error = input_ff_create_memless(input_dev, NULL, + max8997_haptic_play_effect); + if (error) { + dev_err(&pdev->dev, + "unable to create FF device, error: %d\n", + error); + goto err_put_regulator; + } + + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, + "unable to register input device, error: %d\n", + error); + goto err_destroy_ff; + } + + platform_set_drvdata(pdev, chip); + return 0; + +err_destroy_ff: + input_ff_destroy(input_dev); +err_put_regulator: + regulator_put(chip->regulator); +err_free_pwm: + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); +err_free_mem: + input_free_device(input_dev); + kfree(chip); + + return error; +} + +static int __devexit max8997_haptic_remove(struct platform_device *pdev) +{ + struct max8997_haptic *chip = platform_get_drvdata(pdev); + + input_unregister_device(chip->input_dev); + regulator_put(chip->regulator); + + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); + + kfree(chip); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max8997_haptic_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct max8997_haptic *chip = platform_get_drvdata(pdev); + + max8997_haptic_disable(chip); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max8997_haptic_pm_ops, max8997_haptic_suspend, NULL); + +static const struct platform_device_id max8997_haptic_id[] = { + { "max8997-haptic", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max8997_haptic_id); + +static struct platform_driver max8997_haptic_driver = { + .driver = { + .name = "max8997-haptic", + .owner = THIS_MODULE, + .pm = &max8997_haptic_pm_ops, + }, + .probe = max8997_haptic_probe, + .remove = __devexit_p(max8997_haptic_remove), + .id_table = max8997_haptic_id, +}; +module_platform_driver(max8997_haptic_driver); + +MODULE_ALIAS("platform:max8997-haptic"); +MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); +MODULE_DESCRIPTION("max8997_haptic driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index fff5905..28726dd 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -131,6 +131,55 @@ struct max8997_muic_platform_data { int num_init_data; }; +enum max8997_haptic_motor_type { + MAX8997_HAPTIC_ERM, + MAX8997_HAPTIC_LRA, +}; + +enum max8997_haptic_pulse_mode { + MAX8997_EXTERNAL_MODE, + MAX8997_INTERNAL_MODE, +}; + +enum max8997_haptic_pwm_divisor { + MAX8997_PWM_DIVISOR_32, + MAX8997_PWM_DIVISOR_64, + MAX8997_PWM_DIVISOR_128, + MAX8997_PWM_DIVISOR_256, +}; + +/** + * max8997_haptic_platform_data + * @pwm_channel_id: channel number of PWM device + * valid for MAX8997_EXTERNAL_MODE + * @pwm_period: period in nano second for PWM device + * valid for MAX8997_EXTERNAL_MODE + * @type: motor type + * @mode: pulse mode + * MAX8997_EXTERNAL_MODE: external PWM device is used to control motor + * MAX8997_INTERNAL_MODE: internal pulse generator is used to control motor + * @pwm_divisor: divisor for external PWM device + * @internal_mode_pattern: internal mode pattern for internal mode + * [0 - 3]: valid pattern number + * @pattern_cycle: the number of cycles of the waveform + * for the internal mode pattern + * [0 - 15]: available cycles + * @pattern_signal_period: period of the waveform for the internal mode pattern + * [0 - 255]: available period + */ +struct max8997_haptic_platform_data { + unsigned int pwm_channel_id; + unsigned int pwm_period; + + enum max8997_haptic_motor_type type; + enum max8997_haptic_pulse_mode mode; + enum max8997_haptic_pwm_divisor pwm_divisor; + + unsigned int internal_mode_pattern; + unsigned int pattern_cycle; + unsigned int pattern_signal_period; +}; + enum max8997_led_mode { MAX8997_NONE, MAX8997_FLASH_MODE, @@ -192,7 +241,9 @@ struct max8997_platform_data { /* ---- MUIC ---- */ struct max8997_muic_platform_data *muic_pdata; - /* HAPTIC: Not implemented */ + /* ---- HAPTIC ---- */ + struct max8997_haptic_platform_data *haptic_pdata; + /* RTC: Not implemented */ /* ---- LED ---- */ struct max8997_led_platform_data *led_pdata; ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-14 8:46 ` Dmitry Torokhov @ 2012-03-14 11:54 ` Chanwoo Choi 2012-03-14 14:47 ` Chanwoo Choi 1 sibling, 0 replies; 9+ messages in thread From: Chanwoo Choi @ 2012-03-14 11:54 UTC (permalink / raw) To: Dmitry Torokhov Cc: linux-input, sameo, broonie, linux-kernel, kyungmin.park, myungjoo.ham On 03/14/2012 05:46 PM, Dmitry Torokhov wrote: > Hi Chanwoo, > > On Mon, Mar 12, 2012 at 05:31:21PM +0900, Chanwoo Choi wrote: >> + >> +static int __devexit max8997_haptic_remove(struct platform_device *pdev) >> +{ >> + struct max8997_haptic *chip = platform_get_drvdata(pdev); >> + >> + destroy_work_on_stack(&chip->work); > > This does not make sense because: > > - chip->work is not on stack > - even if you "destroy" work nothing stops play effect to schedule a new > one - you need to cancel work in close method. > Agreed, I will fix it. >> + input_unregister_device(chip->input_dev); >> + regulator_put(chip->regulator); >> + >> + if (chip->mode == MAX8997_EXTERNAL_MODE) >> + pwm_free(chip->pwm); >> + >> + kfree(chip); >> + >> + return 0; >> +} >> + >> +static int max8997_haptic_suspend(struct device *dev) >> +{ >> + struct platform_device *pdev = to_platform_device(dev); >> + struct max8997_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); >> + max8997_haptic_enable(chip, false); >> + spin_unlock_irqrestore(&input_dev->event_lock, flags); >> + > > This is not proper locking between playing effect and suspend. Now that > you are using workqueue you need to synchronize access with it, not with > play_effect method. > > Does the following patch work for you? You're right. As you said, I face with the kernel panic when enter suspend state while testing max8997-haptic by using fftest at once. Previously, I tested for each operation of max8997-haptic and suspend/resume. I will fix it according to your comment. Thank you for your comment. Best Regards, Chanwoo Choi ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-14 8:46 ` Dmitry Torokhov 2012-03-14 11:54 ` Chanwoo Choi @ 2012-03-14 14:47 ` Chanwoo Choi 2012-03-14 15:48 ` Dmitry Torokhov 1 sibling, 1 reply; 9+ messages in thread From: Chanwoo Choi @ 2012-03-14 14:47 UTC (permalink / raw) To: Dmitry Torokhov Cc: linux-input, sameo, broonie, linux-kernel, kyungmin.park, myungjoo.ham Hi Dmitry, > This is not proper locking between playing effect and suspend. Now that > you are using workqueue you need to synchronize access with it, not with > play_effect method. > > Does the following patch work for you? > Your patch is worked on TRATS board based on EXYNOS4210 but, you should apply following patch because 'chip->enabled' is used on max8997_haptic_configure() function to enable/disable haptic motor. diff --git a/drivers/haptic/max8997_haptic.c b/drivers/haptic/max8997_haptic.c index 806bcf3..0347404 100644 --- a/drivers/haptic/max8997_haptic.c +++ b/drivers/haptic/max8997_haptic.c @@ -182,11 +182,11 @@ static void max8997_haptic_enable(struct max8997_haptic *chip) } if (!chip->enabled) { + chip->enabled = true; regulator_enable(chip->regulator); max8997_haptic_configure(chip); if (chip->mode == MAX8997_EXTERNAL_MODE) pwm_enable(chip->pwm); - chip->enabled = true; } out: @@ -198,11 +198,11 @@ static void max8997_haptic_disable(struct max8997_haptic *chip) mutex_lock(&chip->mutex); if (chip->enabled) { + chip->enabled = false; max8997_haptic_configure(chip); if (chip->mode == MAX8997_EXTERNAL_MODE) pwm_disable(chip->pwm); regulator_disable(chip->regulator); - chip->enabled = false; } mutex_unlock(&chip->mutex); -- 1.7.0.4 Thank you, Chanwoo Choi ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-14 14:47 ` Chanwoo Choi @ 2012-03-14 15:48 ` Dmitry Torokhov 2012-03-16 17:48 ` Samuel Ortiz 0 siblings, 1 reply; 9+ messages in thread From: Dmitry Torokhov @ 2012-03-14 15:48 UTC (permalink / raw) To: Chanwoo Choi Cc: linux-input, sameo, broonie, linux-kernel, kyungmin.park, myungjoo.ham On Wed, Mar 14, 2012 at 11:47:42PM +0900, Chanwoo Choi wrote: > Hi Dmitry, > > > This is not proper locking between playing effect and suspend. Now that > > you are using workqueue you need to synchronize access with it, not with > > play_effect method. > > > > Does the following patch work for you? > > > > Your patch is worked on TRATS board based on EXYNOS4210 but, you should > apply following patch because 'chip->enabled' is used on > max8997_haptic_configure() function to enable/disable haptic motor. OK, excellent. I'll pick up this fix and apply the driver for 3.4 queue, thanks! -- Dmitry ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-14 15:48 ` Dmitry Torokhov @ 2012-03-16 17:48 ` Samuel Ortiz 2012-03-16 17:55 ` Dmitry Torokhov 0 siblings, 1 reply; 9+ messages in thread From: Samuel Ortiz @ 2012-03-16 17:48 UTC (permalink / raw) To: Dmitry Torokhov Cc: Chanwoo Choi, linux-input, broonie, linux-kernel, kyungmin.park, myungjoo.ham Hi Dmitry, On Wed, Mar 14, 2012 at 08:48:28AM -0700, Dmitry Torokhov wrote: > On Wed, Mar 14, 2012 at 11:47:42PM +0900, Chanwoo Choi wrote: > > Hi Dmitry, > > > > > This is not proper locking between playing effect and suspend. Now that > > > you are using workqueue you need to synchronize access with it, not with > > > play_effect method. > > > > > > Does the following patch work for you? > > > > > > > Your patch is worked on TRATS board based on EXYNOS4210 but, you should > > apply following patch because 'chip->enabled' is used on > > max8997_haptic_configure() function to enable/disable haptic motor. > > OK, excellent. I'll pick up this fix and apply the driver for 3.4 queue, > thanks! Your patch depends on the MFD changes Choi sent along the input driver. I think it would make more sense for me to take it. What do you think ? Cheers, Samuel. -- Intel Open Source Technology Centre http://oss.intel.com/ ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-16 17:48 ` Samuel Ortiz @ 2012-03-16 17:55 ` Dmitry Torokhov 2012-03-16 18:56 ` Samuel Ortiz 0 siblings, 1 reply; 9+ messages in thread From: Dmitry Torokhov @ 2012-03-16 17:55 UTC (permalink / raw) To: Samuel Ortiz Cc: Chanwoo Choi, linux-input, broonie, linux-kernel, kyungmin.park, myungjoo.ham On Fri, Mar 16, 2012 at 06:48:02PM +0100, Samuel Ortiz wrote: > Hi Dmitry, > > On Wed, Mar 14, 2012 at 08:48:28AM -0700, Dmitry Torokhov wrote: > > On Wed, Mar 14, 2012 at 11:47:42PM +0900, Chanwoo Choi wrote: > > > Hi Dmitry, > > > > > > > This is not proper locking between playing effect and suspend. Now that > > > > you are using workqueue you need to synchronize access with it, not with > > > > play_effect method. > > > > > > > > Does the following patch work for you? > > > > > > > > > > Your patch is worked on TRATS board based on EXYNOS4210 but, you should > > > apply following patch because 'chip->enabled' is used on > > > max8997_haptic_configure() function to enable/disable haptic motor. > > > > OK, excellent. I'll pick up this fix and apply the driver for 3.4 queue, > > thanks! > Your patch depends on the MFD changes Choi sent along the input driver. I > think it would make more sense for me to take it. What do you think ? I think that splitting required platform data definitions from the driver into separate patch is quite pointless. I often see a driver split in 3+ patches - headers, the driver itself and Kconfig/makefile changes and I wonder what made them do it... Anyway, below is the consolidated patch I was going to apply to my tree, but if you want to go through yours - feel free to apply it. Thanks. -- Dmitry Input: add driver support for MAX8997-haptic From: Donggeun Kim <dg77.kim@samsung.com> The MAX8997-haptic function can be used to control motor. User can control the haptic driver by using force feedback framework. Signed-off-by: Donggeun Kim <dg77.kim@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru> --- drivers/input/misc/Kconfig | 12 + drivers/input/misc/Makefile | 1 drivers/input/misc/max8997_haptic.c | 407 +++++++++++++++++++++++++++++++++++ include/linux/mfd/max8997.h | 53 ++++- 4 files changed, 472 insertions(+), 1 deletions(-) create mode 100644 drivers/input/misc/max8997_haptic.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 2337c3e..ee077a4 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -134,6 +134,18 @@ config INPUT_MAX8925_ONKEY To compile this driver as a module, choose M here: the module will be called max8925_onkey. +config INPUT_MAX8997_HAPTIC + tristate "MAXIM MAX8997 haptic controller support" + depends on HAVE_PWM && MFD_MAX8997 + select INPUT_FF_MEMLESS + help + This option enables device driver support for the haptic controller + on MAXIM MAX8997 chip. This driver supports ff-memless interface + from input framework. + + To compile this driver as module, choose M here: the + module will be called max8997-haptic. + config INPUT_MC13783_PWRBUTTON tristate "MC13783 ON buttons" depends on MFD_MC13783 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index a6d8de0..f55cdf4 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o 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_MMA8450) += mma8450.o obj-$(CONFIG_INPUT_MPU3050) += mpu3050.o diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c new file mode 100644 index 0000000..05b7b8b --- /dev/null +++ b/drivers/input/misc/max8997_haptic.c @@ -0,0 +1,407 @@ +/* + * MAX8997-haptic controller driver + * + * Copyright (C) 2012 Samsung Electronics + * Donggeun Kim <dg77.kim@samsung.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/max8997-private.h> +#include <linux/mfd/max8997.h> +#include <linux/regulator/consumer.h> + +/* Haptic configuration 2 register */ +#define MAX8997_MOTOR_TYPE_SHIFT 7 +#define MAX8997_ENABLE_SHIFT 6 +#define MAX8997_MODE_SHIFT 5 + +/* Haptic driver configuration register */ +#define MAX8997_CYCLE_SHIFT 6 +#define MAX8997_SIG_PERIOD_SHIFT 4 +#define MAX8997_SIG_DUTY_SHIFT 2 +#define MAX8997_PWM_DUTY_SHIFT 0 + +struct max8997_haptic { + struct device *dev; + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *regulator; + + struct work_struct work; + struct mutex mutex; + + bool enabled; + unsigned int level; + + struct pwm_device *pwm; + unsigned int pwm_period; + enum max8997_haptic_pwm_divisor pwm_divisor; + + enum max8997_haptic_motor_type type; + enum max8997_haptic_pulse_mode mode; + + unsigned int internal_mode_pattern; + unsigned int pattern_cycle; + unsigned int pattern_signal_period; +}; + +static int max8997_haptic_set_duty_cycle(struct max8997_haptic *chip) +{ + int ret = 0; + + if (chip->mode == MAX8997_EXTERNAL_MODE) { + unsigned int duty = chip->pwm_period * chip->level / 100; + ret = pwm_config(chip->pwm, duty, chip->pwm_period); + } else { + int i; + u8 duty_index = 0; + + for (i = 0; i <= 64; i++) { + if (chip->level <= i * 100 / 64) { + duty_index = i; + break; + } + } + switch (chip->internal_mode_pattern) { + case 0: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC1, duty_index); + break; + case 1: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC2, duty_index); + break; + case 2: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC3, duty_index); + break; + case 3: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC4, duty_index); + break; + default: + break; + } + } + return ret; +} + +static void max8997_haptic_configure(struct max8997_haptic *chip) +{ + u8 value; + + value = chip->type << MAX8997_MOTOR_TYPE_SHIFT | + chip->enabled << MAX8997_ENABLE_SHIFT | + chip->mode << MAX8997_MODE_SHIFT | chip->pwm_divisor; + max8997_write_reg(chip->client, MAX8997_HAPTIC_REG_CONF2, value); + + if (chip->mode == MAX8997_INTERNAL_MODE && chip->enabled) { + value = chip->internal_mode_pattern << MAX8997_CYCLE_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_PERIOD_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_DUTY_SHIFT | + chip->internal_mode_pattern << MAX8997_PWM_DUTY_SHIFT; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_DRVCONF, value); + + switch (chip->internal_mode_pattern) { + case 0: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF1, value); + break; + + case 1: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF2, value); + break; + + case 2: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF3, value); + break; + + case 3: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF4, value); + break; + + default: + break; + } + } +} + +static void max8997_haptic_enable(struct max8997_haptic *chip) +{ + int error; + + mutex_lock(&chip->mutex); + + error = max8997_haptic_set_duty_cycle(chip); + if (error) { + dev_err(chip->dev, "set_pwm_cycle failed, error: %d\n", error); + goto out; + } + + if (!chip->enabled) { + chip->enabled = true; + regulator_enable(chip->regulator); + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_enable(chip->pwm); + } + +out: + mutex_unlock(&chip->mutex); +} + +static void max8997_haptic_disable(struct max8997_haptic *chip) +{ + mutex_lock(&chip->mutex); + + if (chip->enabled) { + chip->enabled = false; + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_disable(chip->pwm); + regulator_disable(chip->regulator); + } + + mutex_unlock(&chip->mutex); +} + +static void max8997_haptic_play_effect_work(struct work_struct *work) +{ + struct max8997_haptic *chip = + container_of(work, struct max8997_haptic, work); + + if (chip->level) + max8997_haptic_enable(chip); + else + max8997_haptic_disable(chip); +} + +static int max8997_haptic_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct max8997_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 void max8997_haptic_close(struct input_dev *dev) +{ + struct max8997_haptic *chip = input_get_drvdata(dev); + + cancel_work_sync(&chip->work); + max8997_haptic_disable(chip); +} + +static int __devinit max8997_haptic_probe(struct platform_device *pdev) +{ + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + const struct max8997_platform_data *pdata = + dev_get_platdata(iodev->dev); + const struct max8997_haptic_platform_data *haptic_pdata = + pdata->haptic_pdata; + struct max8997_haptic *chip; + struct input_dev *input_dev; + int error; + + if (!haptic_pdata) { + dev_err(&pdev->dev, "no haptic platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct max8997_haptic), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!chip || !input_dev) { + dev_err(&pdev->dev, "unable to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + INIT_WORK(&chip->work, max8997_haptic_play_effect_work); + mutex_init(&chip->mutex); + + chip->client = iodev->haptic; + 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; + + switch (chip->mode) { + case MAX8997_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; + break; + + case MAX8997_EXTERNAL_MODE: + chip->pwm = pwm_request(haptic_pdata->pwm_channel_id, + "max8997-haptic"); + if (IS_ERR(chip->pwm)) { + error = PTR_ERR(chip->pwm); + dev_err(&pdev->dev, + "unable to request PWM for haptic, error: %d\n", + error); + goto err_free_mem; + } + break; + + default: + dev_err(&pdev->dev, + "Invalid chip mode specified (%d)\n", chip->mode); + error = -EINVAL; + goto err_free_mem; + } + + chip->regulator = regulator_get(&pdev->dev, "inmotor"); + if (IS_ERR(chip->regulator)) { + error = PTR_ERR(chip->regulator); + dev_err(&pdev->dev, + "unable to get regulator, error: %d\n", + error); + goto err_free_pwm; + } + + input_dev->name = "max8997-haptic"; + input_dev->id.version = 1; + input_dev->dev.parent = &pdev->dev; + input_dev->close = max8997_haptic_close; + input_set_drvdata(input_dev, chip); + input_set_capability(input_dev, EV_FF, FF_RUMBLE); + + error = input_ff_create_memless(input_dev, NULL, + max8997_haptic_play_effect); + if (error) { + dev_err(&pdev->dev, + "unable to create FF device, error: %d\n", + error); + goto err_put_regulator; + } + + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, + "unable to register input device, error: %d\n", + error); + goto err_destroy_ff; + } + + platform_set_drvdata(pdev, chip); + return 0; + +err_destroy_ff: + input_ff_destroy(input_dev); +err_put_regulator: + regulator_put(chip->regulator); +err_free_pwm: + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); +err_free_mem: + input_free_device(input_dev); + kfree(chip); + + return error; +} + +static int __devexit max8997_haptic_remove(struct platform_device *pdev) +{ + struct max8997_haptic *chip = platform_get_drvdata(pdev); + + input_unregister_device(chip->input_dev); + regulator_put(chip->regulator); + + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); + + kfree(chip); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max8997_haptic_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct max8997_haptic *chip = platform_get_drvdata(pdev); + + max8997_haptic_disable(chip); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max8997_haptic_pm_ops, max8997_haptic_suspend, NULL); + +static const struct platform_device_id max8997_haptic_id[] = { + { "max8997-haptic", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max8997_haptic_id); + +static struct platform_driver max8997_haptic_driver = { + .driver = { + .name = "max8997-haptic", + .owner = THIS_MODULE, + .pm = &max8997_haptic_pm_ops, + }, + .probe = max8997_haptic_probe, + .remove = __devexit_p(max8997_haptic_remove), + .id_table = max8997_haptic_id, +}; +module_platform_driver(max8997_haptic_driver); + +MODULE_ALIAS("platform:max8997-haptic"); +MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); +MODULE_DESCRIPTION("max8997_haptic driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index fff5905..28726dd 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -131,6 +131,55 @@ struct max8997_muic_platform_data { int num_init_data; }; +enum max8997_haptic_motor_type { + MAX8997_HAPTIC_ERM, + MAX8997_HAPTIC_LRA, +}; + +enum max8997_haptic_pulse_mode { + MAX8997_EXTERNAL_MODE, + MAX8997_INTERNAL_MODE, +}; + +enum max8997_haptic_pwm_divisor { + MAX8997_PWM_DIVISOR_32, + MAX8997_PWM_DIVISOR_64, + MAX8997_PWM_DIVISOR_128, + MAX8997_PWM_DIVISOR_256, +}; + +/** + * max8997_haptic_platform_data + * @pwm_channel_id: channel number of PWM device + * valid for MAX8997_EXTERNAL_MODE + * @pwm_period: period in nano second for PWM device + * valid for MAX8997_EXTERNAL_MODE + * @type: motor type + * @mode: pulse mode + * MAX8997_EXTERNAL_MODE: external PWM device is used to control motor + * MAX8997_INTERNAL_MODE: internal pulse generator is used to control motor + * @pwm_divisor: divisor for external PWM device + * @internal_mode_pattern: internal mode pattern for internal mode + * [0 - 3]: valid pattern number + * @pattern_cycle: the number of cycles of the waveform + * for the internal mode pattern + * [0 - 15]: available cycles + * @pattern_signal_period: period of the waveform for the internal mode pattern + * [0 - 255]: available period + */ +struct max8997_haptic_platform_data { + unsigned int pwm_channel_id; + unsigned int pwm_period; + + enum max8997_haptic_motor_type type; + enum max8997_haptic_pulse_mode mode; + enum max8997_haptic_pwm_divisor pwm_divisor; + + unsigned int internal_mode_pattern; + unsigned int pattern_cycle; + unsigned int pattern_signal_period; +}; + enum max8997_led_mode { MAX8997_NONE, MAX8997_FLASH_MODE, @@ -192,7 +241,9 @@ struct max8997_platform_data { /* ---- MUIC ---- */ struct max8997_muic_platform_data *muic_pdata; - /* HAPTIC: Not implemented */ + /* ---- HAPTIC ---- */ + struct max8997_haptic_platform_data *haptic_pdata; + /* RTC: Not implemented */ /* ---- LED ---- */ struct max8997_led_platform_data *led_pdata; ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-16 17:55 ` Dmitry Torokhov @ 2012-03-16 18:56 ` Samuel Ortiz 2012-03-16 19:27 ` Dmitry Torokhov 0 siblings, 1 reply; 9+ messages in thread From: Samuel Ortiz @ 2012-03-16 18:56 UTC (permalink / raw) To: Dmitry Torokhov Cc: Chanwoo Choi, linux-input, broonie, linux-kernel, kyungmin.park, myungjoo.ham Hi Dmitry, On Fri, Mar 16, 2012 at 10:55:24AM -0700, Dmitry Torokhov wrote: > > > OK, excellent. I'll pick up this fix and apply the driver for 3.4 queue, > > > thanks! > > Your patch depends on the MFD changes Choi sent along the input driver. I > > think it would make more sense for me to take it. What do you think ? > > I think that splitting required platform data definitions from the > driver into separate patch is quite pointless. Hmm, you're right on this one. > I often see a driver > split in 3+ patches - headers, the driver itself and Kconfig/makefile > changes and I wonder what made them do it... > > Anyway, below is the consolidated patch I was going to apply to my tree, > but if you want to go through yours - feel free to apply it. No, I think it makes sense for you to take it. Add my: Acked-by: Samuel Ortiz <sameo@linux.intel.com> if you feel it's needed. Cheers, Samuel. -- Intel Open Source Technology Centre http://oss.intel.com/ ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] input: add driver support for MAX8997-haptic 2012-03-16 18:56 ` Samuel Ortiz @ 2012-03-16 19:27 ` Dmitry Torokhov 0 siblings, 0 replies; 9+ messages in thread From: Dmitry Torokhov @ 2012-03-16 19:27 UTC (permalink / raw) To: Samuel Ortiz Cc: Chanwoo Choi, linux-input, broonie, linux-kernel, kyungmin.park, myungjoo.ham On Fri, Mar 16, 2012 at 07:56:26PM +0100, Samuel Ortiz wrote: > Hi Dmitry, > > On Fri, Mar 16, 2012 at 10:55:24AM -0700, Dmitry Torokhov wrote: > > > > OK, excellent. I'll pick up this fix and apply the driver for 3.4 queue, > > > > thanks! > > > Your patch depends on the MFD changes Choi sent along the input driver. I > > > think it would make more sense for me to take it. What do you think ? > > > > I think that splitting required platform data definitions from the > > driver into separate patch is quite pointless. > Hmm, you're right on this one. > > > I often see a driver > > split in 3+ patches - headers, the driver itself and Kconfig/makefile > > changes and I wonder what made them do it... > > > > Anyway, below is the consolidated patch I was going to apply to my tree, > > but if you want to go through yours - feel free to apply it. > No, I think it makes sense for you to take it. Add my: > Acked-by: Samuel Ortiz <sameo@linux.intel.com> > > if you feel it's needed. I will, thank you. -- Dmitry ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2012-03-16 19:27 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2012-03-12 8:31 [PATCH v3 2/2] input: add driver support for MAX8997-haptic Chanwoo Choi 2012-03-14 8:46 ` Dmitry Torokhov 2012-03-14 11:54 ` Chanwoo Choi 2012-03-14 14:47 ` Chanwoo Choi 2012-03-14 15:48 ` Dmitry Torokhov 2012-03-16 17:48 ` Samuel Ortiz 2012-03-16 17:55 ` Dmitry Torokhov 2012-03-16 18:56 ` Samuel Ortiz 2012-03-16 19:27 ` Dmitry Torokhov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).