| /* |
| * 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"); |