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