Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/misc/isa1200.c b/drivers/misc/isa1200.c
new file mode 100644
index 0000000..bb3f9a8
--- /dev/null
+++ b/drivers/misc/isa1200.c
@@ -0,0 +1,440 @@
+/*
+ *  isa1200.c - Haptic Motor
+ *
+ *  Copyright (C) 2009 Samsung Electronics
+ *  Kyungmin Park <kyungmin.park@samsung.com>
+ *  Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/i2c/isa1200.h>
+#include "../staging/android/timed_output.h"
+
+#define ISA1200_HCTRL0		0x30
+#define ISA1200_HCTRL1		0x31
+#define ISA1200_HCTRL5		0x35
+
+#define ISA1200_HCTRL0_RESET	0x01
+#define ISA1200_HCTRL1_RESET	0x4B
+
+#define ISA1200_HCTRL5_VIB_STRT	0xD5
+#define ISA1200_HCTRL5_VIB_STOP	0x6B
+
+struct isa1200_chip {
+	struct i2c_client *client;
+	struct isa1200_platform_data *pdata;
+	struct pwm_device *pwm;
+	struct hrtimer timer;
+	struct timed_output_dev dev;
+	struct work_struct work;
+	spinlock_t lock;
+	unsigned int enable;
+	unsigned int period_ns;
+};
+
+static int isa1200_read_reg(struct i2c_client *client, int reg)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client, reg);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int isa1200_write_reg(struct i2c_client *client, int reg, u8 value)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, reg, value);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+	return ret;
+}
+
+static void isa1200_vib_set(struct isa1200_chip *haptic, int enable)
+{
+	int rc = 0;
+
+	if (enable) {
+		if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) {
+			int period_us = haptic->period_ns / 1000;
+			rc = pwm_config(haptic->pwm,
+				(period_us * haptic->pdata->duty) / 100,
+				period_us);
+			if (rc < 0)
+				pr_err("%s: pwm_config fail\n", __func__);
+			rc = pwm_enable(haptic->pwm);
+			if (rc < 0)
+				pr_err("%s: pwm_enable fail\n", __func__);
+		} else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) {
+			rc = isa1200_write_reg(haptic->client,
+						ISA1200_HCTRL5,
+						ISA1200_HCTRL5_VIB_STRT);
+			if (rc < 0)
+				pr_err("%s: start vibartion fail\n", __func__);
+		}
+	} else {
+		if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE)
+			pwm_disable(haptic->pwm);
+		else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) {
+			rc = isa1200_write_reg(haptic->client,
+						ISA1200_HCTRL5,
+						ISA1200_HCTRL5_VIB_STOP);
+			if (rc < 0)
+				pr_err("%s: stop vibartion fail\n", __func__);
+		}
+	}
+}
+
+static void isa1200_chip_work(struct work_struct *work)
+{
+	struct isa1200_chip *haptic;
+
+	haptic = container_of(work, struct isa1200_chip, work);
+	isa1200_vib_set(haptic, haptic->enable);
+}
+
+static void isa1200_chip_enable(struct timed_output_dev *dev, int value)
+{
+	struct isa1200_chip *haptic = container_of(dev, struct isa1200_chip,
+					dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&haptic->lock, flags);
+	hrtimer_cancel(&haptic->timer);
+	if (value == 0)
+		haptic->enable = 0;
+	else {
+		value = (value > haptic->pdata->max_timeout ?
+				haptic->pdata->max_timeout : value);
+		haptic->enable = 1;
+		hrtimer_start(&haptic->timer,
+			ktime_set(value / 1000, (value % 1000) * 1000000),
+			HRTIMER_MODE_REL);
+	}
+	spin_unlock_irqrestore(&haptic->lock, flags);
+	schedule_work(&haptic->work);
+}
+
+static int isa1200_chip_get_time(struct timed_output_dev *dev)
+{
+	struct isa1200_chip *haptic = container_of(dev, struct isa1200_chip,
+					dev);
+
+	if (hrtimer_active(&haptic->timer)) {
+		ktime_t r = hrtimer_get_remaining(&haptic->timer);
+		struct timeval t = ktime_to_timeval(r);
+		return t.tv_sec * 1000 + t.tv_usec / 1000;
+	} else
+		return 0;
+}
+
+static enum hrtimer_restart isa1200_vib_timer_func(struct hrtimer *timer)
+{
+	struct isa1200_chip *haptic = container_of(timer, struct isa1200_chip,
+					timer);
+	haptic->enable = 0;
+	schedule_work(&haptic->work);
+
+	return HRTIMER_NORESTART;
+}
+
+static void dump_isa1200_reg(char *str, struct i2c_client *client)
+{
+	pr_debug("%s reg0x%x=0x%x, reg0x%x=0x%x, reg0x%x=0x%x\n", str,
+		ISA1200_HCTRL0, isa1200_read_reg(client, ISA1200_HCTRL0),
+		ISA1200_HCTRL1, isa1200_read_reg(client, ISA1200_HCTRL1),
+		ISA1200_HCTRL5, isa1200_read_reg(client, ISA1200_HCTRL5));
+}
+
+static int isa1200_setup(struct i2c_client *client)
+{
+	struct isa1200_chip *haptic = i2c_get_clientdata(client);
+	int value, temp, rc;
+
+	gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0);
+	udelay(250);
+	gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1);
+
+	value =	(haptic->pdata->smart_en << 3) |
+		(haptic->pdata->is_erm << 5) |
+		(haptic->pdata->ext_clk_en << 7);
+
+	rc = isa1200_write_reg(client, ISA1200_HCTRL1, value);
+	if (rc < 0) {
+		pr_err("%s: i2c write failure\n", __func__);
+		return rc;
+	}
+
+	if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) {
+		temp = haptic->pdata->pwm_fd.pwm_div;
+		if (temp < 128 || temp > 1024 || temp % 128) {
+			pr_err("%s: Invalid divider\n", __func__);
+			goto reset_hctrl1;
+		}
+		value = ((temp >> 7) - 1);
+	} else if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) {
+		temp = haptic->pdata->pwm_fd.pwm_freq;
+		if (temp < 22400 || temp > 172600 || temp % 22400) {
+			pr_err("%s: Invalid frequency\n", __func__);
+			goto reset_hctrl1;
+		}
+		value = ((temp / 22400) - 1);
+		haptic->period_ns = NSEC_PER_SEC / temp;
+	}
+
+	value |= (haptic->pdata->mode_ctrl << 3) |
+		(haptic->pdata->overdrive_high << 5) |
+		(haptic->pdata->overdrive_en << 5) |
+		(haptic->pdata->chip_en << 7);
+
+	rc = isa1200_write_reg(client, ISA1200_HCTRL0, value);
+	if (rc < 0) {
+		pr_err("%s: i2c write failure\n", __func__);
+		goto reset_hctrl1;
+	}
+
+	dump_isa1200_reg("new:", client);
+	return 0;
+
+reset_hctrl1:
+	i2c_smbus_write_byte_data(client, ISA1200_HCTRL1,
+				ISA1200_HCTRL1_RESET);
+	return rc;
+}
+
+static int __devinit isa1200_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct isa1200_chip *haptic;
+	struct isa1200_platform_data *pdata;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter,
+			I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(&client->dev, "%s: no support for i2c read/write"
+				"byte data\n", __func__);
+		return -EIO;
+	}
+
+	pdata = client->dev.platform_data;
+	if (!pdata) {
+		dev_err(&client->dev, "%s: no platform data\n", __func__);
+		return -EINVAL;
+	}
+
+	if (pdata->dev_setup) {
+		ret = pdata->dev_setup(true);
+		if (ret < 0) {
+			dev_err(&client->dev, "dev setup failed\n");
+			return -EINVAL;
+		}
+	}
+
+	haptic = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL);
+	if (!haptic) {
+		ret = -ENOMEM;
+		goto mem_alloc_fail;
+	}
+	haptic->client = client;
+	haptic->enable = 0;
+	haptic->pdata = pdata;
+
+	if (pdata->power_on) {
+		ret = pdata->power_on(1);
+		if (ret) {
+			dev_err(&client->dev, "%s: power-up failed\n",
+							__func__);
+			goto pwr_up_fail;
+		}
+	}
+
+	spin_lock_init(&haptic->lock);
+	INIT_WORK(&haptic->work, isa1200_chip_work);
+
+	hrtimer_init(&haptic->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	haptic->timer.function = isa1200_vib_timer_func;
+
+	/*register with timed output class*/
+	haptic->dev.name = pdata->name;
+	haptic->dev.get_time = isa1200_chip_get_time;
+	haptic->dev.enable = isa1200_chip_enable;
+	ret = timed_output_dev_register(&haptic->dev);
+	if (ret < 0)
+		goto timed_reg_fail;
+
+	i2c_set_clientdata(client, haptic);
+
+	ret = gpio_is_valid(pdata->hap_en_gpio);
+	if (ret) {
+		ret = gpio_request(pdata->hap_en_gpio, "haptic_gpio");
+		if (ret) {
+			dev_err(&client->dev, "%s: gpio %d request failed\n",
+					__func__, pdata->hap_en_gpio);
+			goto gpio_fail;
+		}
+	} else {
+		dev_err(&client->dev, "%s: Invalid gpio %d\n", __func__,
+					pdata->hap_en_gpio);
+		goto gpio_fail;
+	}
+
+	ret = isa1200_setup(client);
+	if (ret) {
+		dev_err(&client->dev, "%s: setup fail %d\n", __func__, ret);
+		goto gpio_fail;
+	}
+
+	if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) {
+		haptic->pwm = pwm_request(pdata->pwm_ch_id, id->name);
+		if (IS_ERR(haptic->pwm)) {
+			dev_err(&client->dev, "%s: pwm request failed\n",
+							__func__);
+			ret = PTR_ERR(haptic->pwm);
+			goto reset_hctrl0;
+		}
+	}
+
+	printk(KERN_INFO "%s: %s registered\n", __func__, id->name);
+	return 0;
+
+reset_hctrl0:
+	i2c_smbus_write_byte_data(client, ISA1200_HCTRL0,
+					ISA1200_HCTRL0_RESET);
+gpio_fail:
+	timed_output_dev_unregister(&haptic->dev);
+timed_reg_fail:
+	if (pdata->power_on)
+		pdata->power_on(0);
+pwr_up_fail:
+	kfree(haptic);
+mem_alloc_fail:
+	if (pdata->dev_setup)
+		pdata->dev_setup(false);
+	return ret;
+}
+
+static int __devexit isa1200_remove(struct i2c_client *client)
+{
+	struct isa1200_chip *haptic = i2c_get_clientdata(client);
+
+	hrtimer_cancel(&haptic->timer);
+	cancel_work_sync(&haptic->work);
+
+	/* turn-off current vibration */
+	isa1200_vib_set(haptic, 0);
+
+	if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE)
+		pwm_free(haptic->pwm);
+
+	timed_output_dev_unregister(&haptic->dev);
+	gpio_free(haptic->pdata->hap_en_gpio);
+
+	/* reset hardware registers */
+	i2c_smbus_write_byte_data(client, ISA1200_HCTRL0,
+				ISA1200_HCTRL0_RESET);
+	i2c_smbus_write_byte_data(client, ISA1200_HCTRL1,
+				ISA1200_HCTRL1_RESET);
+
+	if (haptic->pdata->dev_setup)
+		haptic->pdata->dev_setup(false);
+
+	/* power-off the chip */
+	if (haptic->pdata->power_on)
+		haptic->pdata->power_on(0);
+
+	kfree(haptic);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isa1200_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct isa1200_chip *haptic = i2c_get_clientdata(client);
+	int ret;
+
+	hrtimer_cancel(&haptic->timer);
+	cancel_work_sync(&haptic->work);
+	/* turn-off current vibration */
+	isa1200_vib_set(haptic, 0);
+
+	if (haptic->pdata->power_on) {
+		ret = haptic->pdata->power_on(0);
+		if (ret) {
+			dev_err(&client->dev, "power-down failed\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int isa1200_resume(struct i2c_client *client)
+{
+	struct isa1200_chip *haptic = i2c_get_clientdata(client);
+	int ret;
+
+	if (haptic->pdata->power_on) {
+		ret = haptic->pdata->power_on(1);
+		if (ret) {
+			dev_err(&client->dev, "power-up failed\n");
+			return ret;
+		}
+	}
+
+	isa1200_setup(client);
+	return 0;
+}
+#else
+#define isa1200_suspend		NULL
+#define isa1200_resume		NULL
+#endif
+
+static const struct i2c_device_id isa1200_id[] = {
+	{ "isa1200_1", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, isa1200_id);
+
+static struct i2c_driver isa1200_driver = {
+	.driver	= {
+		.name	= "isa1200",
+	},
+	.probe		= isa1200_probe,
+	.remove		= __devexit_p(isa1200_remove),
+	.suspend	= isa1200_suspend,
+	.resume		= isa1200_resume,
+	.id_table	= isa1200_id,
+};
+
+static int __init isa1200_init(void)
+{
+	return i2c_add_driver(&isa1200_driver);
+}
+
+static void __exit isa1200_exit(void)
+{
+	i2c_del_driver(&isa1200_driver);
+}
+
+module_init(isa1200_init);
+module_exit(isa1200_exit);
+
+MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("ISA1200 Haptic Motor driver");
+MODULE_LICENSE("GPL");