diff options
Diffstat (limited to 'drivers/input/misc/max8997_haptic.c')
| -rw-r--r-- | drivers/input/misc/max8997_haptic.c | 416 | 
1 files changed, 416 insertions, 0 deletions
diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c new file mode 100644 index 00000000000..a363ebbd9cc --- /dev/null +++ b/drivers/input/misc/max8997_haptic.c @@ -0,0 +1,416 @@ +/* + * 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/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) { +		error = regulator_enable(chip->regulator); +		if (error) { +			dev_err(chip->dev, "Failed to enable regulator\n"); +			goto out; +		} +		max8997_haptic_configure(chip); +		if (chip->mode == MAX8997_EXTERNAL_MODE) { +			error = pwm_enable(chip->pwm); +			if (error) { +				dev_err(chip->dev, "Failed to enable PWM\n"); +				regulator_disable(chip->regulator); +				goto out; +			} +		} +		chip->enabled = true; +	} + +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 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 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		= 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");  | 
