/* drivers/misc/twl6040-vib.c
 *
 * Copyright (C) 2010 Texas Instruments, Inc.
 * Copyright (C) 2008 Google, Inc.
 * Author: Dan Murphy <dmurphy@ti.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 *
 * Derived from: vib-gpio.c
 * Additional derivation from: twl6040-vibra.c
 */

#include <linux/err.h>
#include <linux/hrtimer.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/i2c/twl.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/twl6040-vib.h>
#include <linux/mfd/twl6040-codec.h>
#include "../staging/android/timed_output.h"

/* milliseconds */
#define TWL6040_VIB_POWER_DOWN_DELAY	5000

struct vib_data {
	struct timed_output_dev dev;
	struct work_struct vib_work;
	struct delayed_work power_work;
	struct hrtimer timer;
	spinlock_t lock;
	struct mutex io_mutex;
	struct mutex power_mutex;

	struct twl4030_codec_vibra_data *pdata;
	struct twl6040 *twl6040;

	int vib_power_state;
	int vib_state;
	bool powered;
};

static struct vib_data *misc_data;

static irqreturn_t twl6040_vib_irq_handler(int irq, void *data)
{
	struct vib_data *misc_data = data;
	struct twl6040 *twl6040 = misc_data->twl6040;
	u8 intid = 0, status = 0;

	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);

	if (intid & TWL6040_VIBINT) {
		status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
		if (status & TWL6040_VIBLOCDET) {
			pr_warn("Vibra left overcurrent detected\n");
			twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL,
					   TWL6040_VIBENAL);
		}
		if (status & TWL6040_VIBROCDET) {
			pr_warn("Vibra right overcurrent detected\n");
			twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR,
					   TWL6040_VIBENAR);
		}
	}

	return IRQ_HANDLED;
}

static void twl6040_vib_power_work(struct work_struct *work)
{
	mutex_lock(&misc_data->power_mutex);

	if (misc_data->powered) {
		twl6040_disable(misc_data->twl6040);
		misc_data->powered = false;
	}

	mutex_unlock(&misc_data->power_mutex);
}

static int twl6040_vib_power(bool on)
{
	int ret = 0;

	mutex_lock(&misc_data->power_mutex);

	cancel_delayed_work_sync(&misc_data->power_work);

	if (on == misc_data->powered)
		goto out;

	if (on) {
		/* vibra power-up is immediate */
		ret = twl6040_enable(misc_data->twl6040);
		if (ret)
			goto out;
		misc_data->powered = true;
	} else {
		/* vibra power-down is deferred */
		schedule_delayed_work(&misc_data->power_work,
			msecs_to_jiffies(TWL6040_VIB_POWER_DOWN_DELAY));
	}

out:
	mutex_unlock(&misc_data->power_mutex);
	return ret;
}

static void vib_set(int const new_power_state)
{
	struct twl6040 *twl6040 = misc_data->twl6040;
	u8 speed = misc_data->pdata->voltage_raise_speed;
	int ret;

	mutex_lock(&misc_data->io_mutex);

	/* already in requested state */
	if (new_power_state == misc_data->vib_power_state)
		goto out;

	/**
	 * @warning  VIBDATx registers MUST be setted BEFORE VIBENAx bit
	 *           setted in corresponding VIBCTLx registers
	 */
	if (new_power_state) {
		ret = twl6040_vib_power(true);
		if (ret)
			goto out;

		if (speed == 0x00)
			speed = 0x32;

		twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, speed);
		twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, speed);

		/*
		 * ERRATA: Disable overcurrent protection for at least
		 * 2.5ms when enabling vibrator drivers to avoid false
		 * overcurrent detection
		 */
		twl6040_set_bits(twl6040, TWL6040_REG_VIBCTLL,
				 TWL6040_VIBENAL | TWL6040_VIBCTRLLP);
		twl6040_set_bits(twl6040, TWL6040_REG_VIBCTLR,
				 TWL6040_VIBENAR | TWL6040_VIBCTRLRP);

		mdelay(4);

		twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL,
				 TWL6040_VIBCTRLLP);
		twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR,
				 TWL6040_VIBCTRLRP);
	} else {
		twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, 0x00);
		twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, 0x00);

		twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL,
				   TWL6040_VIBENAL);
		twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR,
				   TWL6040_VIBENAR);

		twl6040_vib_power(false);
	}
	misc_data->vib_power_state = new_power_state;

out:
	mutex_unlock(&misc_data->io_mutex);
}

static void vib_update(struct work_struct *work)
{
	vib_set(misc_data->vib_state);
}

static enum hrtimer_restart vib_timer_func(struct hrtimer *timer)
{
	struct vib_data *data =
	    container_of(timer, struct vib_data, timer);
	data->vib_state = 0;
	schedule_work(&data->vib_work);
	return HRTIMER_NORESTART;
}

static int vib_get_time(struct timed_output_dev *dev)
{
	struct vib_data *data =
	    container_of(dev, struct vib_data, dev);

	if (hrtimer_active(&data->timer)) {
		ktime_t r = hrtimer_get_remaining(&data->timer);
		struct timeval t = ktime_to_timeval(r);
		return t.tv_sec * 1000 + t.tv_usec / 1000;
	} else
		return 0;
}

static void vib_enable(struct timed_output_dev *dev, int value)
{
	struct vib_data *data = container_of(dev, struct vib_data, dev);
	unsigned long flags;

	if (value < 0) {
		pr_err("%s: Invalid vibrator timer value\n", __func__);
		return;
	}

	spin_lock_irqsave(&data->lock, flags);
	hrtimer_cancel(&data->timer);

	if (value == 0)
		data->vib_state = 0;
	else {
		value = (value > data->pdata->max_timeout ?
				 data->pdata->max_timeout : value);

		/* add hardware power-up time to requested timeout */
		if (!misc_data->powered)
			value += TWL6040_POWER_UP_TIME;

		data->vib_state = 1;
		hrtimer_start(&data->timer,
			      ktime_set(value / 1000, (value % 1000) * 1000000),
			      HRTIMER_MODE_REL);
	}

	spin_unlock_irqrestore(&data->lock, flags);

	schedule_work(&data->vib_work);
}

/*
 * This is a temporary solution until a more global haptics soltion is
 * available for haptics that need to occur in any application
 */
void vibrator_haptic_fire(int value)
{
	vib_enable(&misc_data->dev, value);
}

#if CONFIG_PM
static int vib_suspend(struct device *dev)
{
	hrtimer_cancel(&misc_data->timer);
	cancel_work_sync(&misc_data->vib_work);
	vib_set(0);
	flush_delayed_work_sync(&misc_data->power_work);

	return 0;
}
#else
#define vib_suspend NULL
#endif

static const struct dev_pm_ops vib_pm_ops = {
	.suspend = vib_suspend,
};

static int vib_probe(struct platform_device *pdev)
{
	struct twl4030_codec_vibra_data *pdata = pdev->dev.platform_data;
	struct vib_data *data;
	int ret = 0;

	if (!pdata) {
		ret = -EBUSY;
		goto err0;
	}

	data = kzalloc(sizeof(struct vib_data), GFP_KERNEL);
	if (!data) {
		ret = -ENOMEM;
		goto err0;
	}

	data->pdata = pdata;
	data->twl6040 = dev_get_drvdata(pdev->dev.parent);

	INIT_WORK(&data->vib_work, vib_update);
	INIT_DELAYED_WORK(&data->power_work, twl6040_vib_power_work);

	hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);

	data->timer.function = vib_timer_func;
	spin_lock_init(&data->lock);

	data->dev.name = "vibrator";
	data->dev.get_time = vib_get_time;
	data->dev.enable = vib_enable;
	ret = timed_output_dev_register(&data->dev);
	if (ret < 0)
		goto err1;

	if (data->pdata->init)
		ret = data->pdata->init();
	if (ret < 0)
		goto err2;

	misc_data = data;
	platform_set_drvdata(pdev, data);

	mutex_init(&misc_data->io_mutex);
	mutex_init(&misc_data->power_mutex);

	ret = twl6040_request_irq(data->twl6040, TWL6040_IRQ_VIB,
				twl6040_vib_irq_handler, 0,
				"twl6040_irq_vib", data);
	if (ret) {
		pr_err("%s: VIB IRQ request failed: %d\n", __func__, ret);
		goto err2;
	}

	vib_enable(&data->dev, data->pdata->initial_vibrate);

	return 0;

err2:
	timed_output_dev_unregister(&data->dev);
err1:
	kfree(data);
err0:
	return ret;
}

static int vib_remove(struct platform_device *pdev)
{
	struct vib_data *data = platform_get_drvdata(pdev);

	if (data->pdata->exit)
		data->pdata->exit();

	hrtimer_cancel(&data->timer);
	vib_set(0);
	flush_delayed_work_sync(&data->power_work);
	twl6040_free_irq(data->twl6040, TWL6040_IRQ_VIB, data);
	timed_output_dev_unregister(&data->dev);
	kfree(data);

	return 0;
}

/* TO DO: Need to make this drivers own platform data entries */
static struct platform_driver twl6040_vib_driver = {
	.probe = vib_probe,
	.remove = vib_remove,
	.driver = {
		   .name = VIB_NAME,
		   .owner = THIS_MODULE,
		   .pm = &vib_pm_ops,
	},
};

static int __init twl6040_vib_init(void)
{
	return platform_driver_register(&twl6040_vib_driver);
}

static void __exit twl6040_vib_exit(void)
{
	platform_driver_unregister(&twl6040_vib_driver);
}

module_init(twl6040_vib_init);
module_exit(twl6040_vib_exit);

MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_DESCRIPTION("TWL6040 Vibrator Driver");
MODULE_LICENSE("GPL");