| /* | |
| * 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 = NULL; | |
| struct max8997_haptic *chip; | |
| struct input_dev *input_dev; | |
| int error; | |
| if (pdata) | |
| haptic_pdata = pdata->haptic_pdata; | |
| 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; | |
| } | |
| /* | |
| * FIXME: pwm_apply_args() should be removed when switching to | |
| * the atomic PWM API. | |
| */ | |
| pwm_apply_args(chip->pwm); | |
| 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; | |
| } | |
| static int __maybe_unused 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; | |
| } | |
| 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(platform, max8997_haptic_id); | |
| static struct platform_driver max8997_haptic_driver = { | |
| .driver = { | |
| .name = "max8997-haptic", | |
| .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_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); | |
| MODULE_DESCRIPTION("max8997_haptic driver"); | |
| MODULE_LICENSE("GPL"); |