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