@@ -20,4 +20,10 @@ config HAPTIC_SAMSUNG_PWM
This options enables support for haptic connected to GPIO lines
controlled by a PWM timer on SAMSUNG CPUs.
+config HAPTIC_ISA1200
+ tristate "ISA1200 haptic support"
+ depends on HAPTIC_CLASS && I2C
+ help
+ The ISA1200 is a high performance enhanced haptic driver.
+
endif # HAPTIC
@@ -3,3 +3,4 @@ obj-$(CONFIG_HAPTIC_CLASS) += haptic-class.o
# Drivers
obj-$(CONFIG_HAPTIC_SAMSUNG_PWM) += haptic-samsung-pwm.o
+obj-$(CONFIG_HAPTIC_ISA1200) += isa1200.o
new file mode 100644
@@ -0,0 +1,429 @@
+/*
+ * isa1200.c - Haptic Motor
+ *
+ * Copyright (C) 2009 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * 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/haptic.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/ctype.h>
+#include <linux/workqueue.h>
+#include <linux/i2c/isa1200.h>
+#include "haptic.h"
+
+struct isa1200_chip {
+ struct i2c_client *client;
+ struct pwm_device *pwm;
+ struct haptic_classdev cdev;
+ struct work_struct work;
+ struct timer_list timer;
+
+ unsigned int len; /* LDO enable */
+ unsigned int hen; /* Haptic enable */
+
+ int enable;
+ int powered;
+
+ int level;
+ int level_max;
+
+ int ldo_level;
+};
+
+static inline struct isa1200_chip *cdev_to_isa1200_chip(
+ struct haptic_classdev *haptic_cdev)
+{
+ return container_of(haptic_cdev, struct isa1200_chip, cdev);
+}
+
+static int isa1200_chip_set_pwm_cycle(struct isa1200_chip *haptic)
+{
+ int duty = PWM_HAPTIC_PERIOD * haptic->level / 100;
+ return pwm_config(haptic->pwm, duty, PWM_HAPTIC_PERIOD);
+}
+
+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_chip_power_on(struct isa1200_chip *haptic)
+{
+ if (haptic->powered)
+ return;
+ haptic->powered = 1;
+ /* Use smart mode enable control */
+ pwm_enable(haptic->pwm);
+}
+
+static void isa1200_chip_power_off(struct isa1200_chip *haptic)
+{
+ if (!haptic->powered)
+ return;
+ haptic->powered = 0;
+ /* Use smart mode enable control */
+ pwm_disable(haptic->pwm);
+}
+
+static void isa1200_chip_work(struct work_struct *work)
+{
+ struct isa1200_chip *haptic;
+ int r;
+
+ haptic = container_of(work, struct isa1200_chip, work);
+ if (haptic->enable) {
+ r = isa1200_chip_set_pwm_cycle(haptic);
+ if (r) {
+ dev_dbg(haptic->cdev.dev, "set_pwm_cycle failed\n");
+ return;
+ }
+ isa1200_chip_power_on(haptic);
+ } else {
+ isa1200_chip_power_off(haptic);
+ }
+}
+
+static void isa1200_chip_timer(unsigned long data)
+{
+ struct isa1200_chip *haptic = (struct isa1200_chip *)data;
+
+ haptic->enable = 0;
+ isa1200_chip_power_off(haptic);
+}
+
+static void isa1200_chip_set(struct haptic_classdev *haptic_cdev,
+ enum haptic_value value)
+{
+ struct isa1200_chip *haptic =
+ cdev_to_isa1200_chip(haptic_cdev);
+
+ switch (value) {
+ case HAPTIC_OFF:
+ haptic->enable = 0;
+ break;
+ case HAPTIC_HALF:
+ case HAPTIC_FULL:
+ default:
+ haptic->enable = 1;
+ break;
+ }
+
+ schedule_work(&haptic->work);
+}
+
+static enum haptic_value isa1200_chip_get(struct haptic_classdev *haptic_cdev)
+{
+ struct isa1200_chip *haptic =
+ cdev_to_isa1200_chip(haptic_cdev);
+
+ if (haptic->enable)
+ return HAPTIC_FULL;
+
+ return HAPTIC_OFF;
+}
+
+#define ATTR_DEF_SHOW(name) \
+static ssize_t isa1200_chip_show_##name(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev); \
+ struct isa1200_chip *haptic = cdev_to_isa1200_chip(haptic_cdev); \
+ \
+ return sprintf(buf, "%u\n", haptic->name) + 1; \
+}
+
+#define ATTR_DEF_STORE(name) \
+static ssize_t isa1200_chip_store_##name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t size) \
+{ \
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev); \
+ struct isa1200_chip *haptic = cdev_to_isa1200_chip(haptic_cdev); \
+ ssize_t ret = -EINVAL; \
+ unsigned long val; \
+ \
+ ret = strict_strtoul(buf, 10, &val); \
+ if (ret == 0) { \
+ ret = size; \
+ haptic->name = val; \
+ schedule_work(&haptic->work); \
+ } \
+ \
+ return ret; \
+}
+
+ATTR_DEF_SHOW(enable);
+ATTR_DEF_STORE(enable);
+static DEVICE_ATTR(enable, 0644, isa1200_chip_show_enable,
+ isa1200_chip_store_enable);
+
+static ssize_t isa1200_chip_store_level(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev);
+ struct isa1200_chip *haptic = cdev_to_isa1200_chip(haptic_cdev);
+ ssize_t ret = -EINVAL;
+ unsigned long val;
+
+ ret = strict_strtoul(buf, 10, &val);
+ if (ret == 0) {
+ ret = size;
+ if (haptic->level_max < val)
+ val = haptic->level_max;
+ haptic->level = val;
+ schedule_work(&haptic->work);
+ }
+
+ return ret;
+}
+ATTR_DEF_SHOW(level);
+static DEVICE_ATTR(level, 0644, isa1200_chip_show_level,
+ isa1200_chip_store_level);
+
+ATTR_DEF_SHOW(level_max);
+static DEVICE_ATTR(level_max, 0444, isa1200_chip_show_level_max, NULL);
+
+static ssize_t isa1200_chip_store_oneshot(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev);
+ struct isa1200_chip *haptic = cdev_to_isa1200_chip(haptic_cdev);
+ ssize_t ret = -EINVAL;
+ unsigned long val;
+
+ ret = strict_strtoul(buf, 10, &val);
+ if (ret == 0) {
+ ret = size;
+ haptic->enable = 1;
+ mod_timer(&haptic->timer, jiffies + val * HZ / 1000);
+ schedule_work(&haptic->work);
+ }
+
+ return ret;
+}
+static DEVICE_ATTR(oneshot, 0200, NULL, isa1200_chip_store_oneshot);
+
+static struct attribute *haptic_attributes[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_level.attr,
+ &dev_attr_level_max.attr,
+ &dev_attr_oneshot.attr,
+ NULL,
+};
+
+static const struct attribute_group haptic_group = {
+ .attrs = haptic_attributes,
+};
+
+static void isa1200_setup(struct i2c_client *client)
+{
+ struct isa1200_chip *chip = i2c_get_clientdata(client);
+ int value;
+
+ gpio_set_value(chip->len, 1);
+ udelay(250);
+ gpio_set_value(chip->len, 1);
+
+ value = isa1200_read_reg(client, ISA1200_SCTRL0);
+ value &= ~ISA1200_LDOADJ_MASK;
+ value |= chip->ldo_level;
+ isa1200_write_reg(client, ISA1200_SCTRL0, value);
+
+ value = ISA1200_HAPDREN | ISA1200_OVERHL | ISA1200_HAPDIGMOD_PWM_IN |
+ ISA1200_PWMMOD_DIVIDER_128;
+ isa1200_write_reg(client, ISA1200_HCTRL0, value);
+
+ value = ISA1200_EXTCLKSEL | ISA1200_BIT6_ON | ISA1200_MOTTYP_LRA |
+ ISA1200_SMARTEN | ISA1200_SMARTOFFT_64;
+ isa1200_write_reg(client, ISA1200_HCTRL1, value);
+
+ value = isa1200_read_reg(client, ISA1200_HCTRL2);
+ value |= ISA1200_SEEN;
+ isa1200_write_reg(client, ISA1200_HCTRL2, value);
+ isa1200_chip_power_off(chip);
+ isa1200_chip_power_on(chip);
+
+ /* TODO */
+}
+
+static int __devinit isa1200_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct isa1200_chip *chip;
+ struct haptic_platform_data *pdata;
+ int ret;
+
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ dev_err(&client->dev, "%s: no platform data\n", __func__);
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->cdev.set = isa1200_chip_set;
+ chip->cdev.get = isa1200_chip_get;
+ chip->cdev.show_enable = isa1200_chip_show_enable;
+ chip->cdev.store_enable = isa1200_chip_store_enable;
+ chip->cdev.store_oneshot = isa1200_chip_store_oneshot;
+ chip->cdev.show_level = isa1200_chip_show_level;
+ chip->cdev.store_level = isa1200_chip_store_level;
+ chip->cdev.show_level_max = isa1200_chip_show_level_max;
+ chip->cdev.name = pdata->name;
+ chip->enable = 0;
+ chip->level = PWM_HAPTIC_DEFAULT_LEVEL;
+ chip->level_max = PWM_HAPTIC_DEFAULT_LEVEL;
+ chip->ldo_level = pdata->ldo_level;
+
+ if (pdata->setup_pin)
+ pdata->setup_pin();
+ chip->len = pdata->gpio;
+ chip->hen = pdata->gpio;
+ chip->pwm = pwm_request(pdata->pwm_timer, "haptic");
+ if (IS_ERR(chip->pwm)) {
+ dev_err(&client->dev, "unable to request PWM for haptic.\n");
+ ret = PTR_ERR(chip->pwm);
+ goto error_pwm;
+ }
+
+ INIT_WORK(&chip->work, isa1200_chip_work);
+
+ /* register our new haptic device */
+ ret = haptic_classdev_register(&client->dev, &chip->cdev);
+ if (ret < 0) {
+ dev_err(&client->dev, "haptic_classdev_register failed\n");
+ goto error_classdev;
+ }
+
+ ret = sysfs_create_group(&chip->cdev.dev->kobj, &haptic_group);
+ if (ret)
+ goto error_enable;
+
+ init_timer(&chip->timer);
+ chip->timer.data = (unsigned long)chip;
+ chip->timer.function = &isa1200_chip_timer;
+
+ i2c_set_clientdata(client, chip);
+
+ if (gpio_is_valid(pdata->gpio)) {
+ ret = gpio_request(pdata->gpio, "haptic enable");
+ if (ret)
+ goto error_gpio;
+ gpio_direction_output(pdata->gpio, 1);
+ }
+
+ isa1200_setup(client);
+
+ printk(KERN_INFO "isa1200 %s registered\n", pdata->name);
+ return 0;
+
+error_gpio:
+ gpio_free(pdata->gpio);
+error_enable:
+ sysfs_remove_group(&chip->cdev.dev->kobj, &haptic_group);
+error_classdev:
+ haptic_classdev_unregister(&chip->cdev);
+error_pwm:
+ pwm_free(chip->pwm);
+ kfree(chip);
+ return ret;
+}
+
+static int __devexit isa1200_remove(struct i2c_client *client)
+{
+ struct isa1200_chip *chip = i2c_get_clientdata(client);
+
+ if (gpio_is_valid(chip->len))
+ gpio_free(chip->len);
+
+ sysfs_remove_group(&chip->cdev.dev->kobj, &haptic_group);
+ haptic_classdev_unregister(&chip->cdev);
+ pwm_free(chip->pwm);
+ kfree(chip);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int isa1200_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct isa1200_chip *chip = i2c_get_clientdata(client);
+ isa1200_chip_power_off(chip);
+ return 0;
+}
+
+static int isa1200_resume(struct i2c_client *client)
+{
+ isa1200_setup(client);
+ return 0;
+}
+#else
+#define isa1200_suspend NULL
+#define isa1200_resume NULL
+#endif
+
+static const struct i2c_device_id isa1200_id[] = {
+ { "isa1200", 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");
new file mode 100644
@@ -0,0 +1,103 @@
+/*
+ * isa1200.h - ISA1200 Haptic Motor driver
+ *
+ * Copyright (C) 2009 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_ISA1200_H
+#define __LINUX_ISA1200_H
+
+#define ISA1200_SCTRL0 0x00
+#define ISA1200_THSWRST (1 << 7)
+#define ISA1200_EXT2DIV (1 << 4)
+#define ISA1200_LDOADJ_24V (0x9 << 0)
+#define ISA1200_LDOADJ_25V (0xa << 0)
+#define ISA1200_LDOADJ_26V (0xb << 0)
+#define ISA1200_LDOADJ_27V (0xc << 0)
+#define ISA1200_LDOADJ_28V (0xd << 0)
+#define ISA1200_LDOADJ_29V (0xe << 0)
+#define ISA1200_LDOADJ_30V (0xf << 0)
+#define ISA1200_LDOADJ_31V (0x0 << 0)
+#define ISA1200_LDOADJ_32V (0x1 << 0)
+#define ISA1200_LDOADJ_33V (0x2 << 0)
+#define ISA1200_LDOADJ_34V (0x3 << 0)
+#define ISA1200_LDOADJ_35V (0x4 << 0)
+#define ISA1200_LDOADJ_36V (0x5 << 0)
+#define ISA1200_LDOADJ_MASK (0xf << 0)
+#define ISA1200_HCTRL0 0x30
+#define ISA1200_HAPDREN (1 << 7)
+#define ISA1200_OVEREN (1 << 6)
+#define ISA1200_OVERHL (1 << 5)
+#define ISA1200_HAPDIGMOD_PWM_IN (1 << 3)
+#define ISA1200_HAPDIGMOD_PWM_GEN (2 << 3)
+#define ISA1200_PLLMOD (1 << 2)
+#define ISA1200_PWMMOD_DIVIDER_128 (0 << 0)
+#define ISA1200_PWMMOD_DIVIDER_256 (1 << 0)
+#define ISA1200_PWMMOD_DIVIDER_512 (2 << 0)
+#define ISA1200_PWMMOD_DIVIDER_1024 (3 << 0)
+#define ISA1200_HCTRL1 0x31
+#define ISA1200_EXTCLKSEL (1 << 7)
+#define ISA1200_BIT6_ON (1 << 6)
+#define ISA1200_MOTTYP_ERM (1 << 5)
+#define ISA1200_MOTTYP_LRA (0 << 5)
+#define ISA1200_PLLEN (1 << 4)
+#define ISA1200_SMARTEN (1 << 3)
+#define ISA1200_SMARTONT (1 << 2)
+#define ISA1200_SMARTOFFT_16 (0 << 0)
+#define ISA1200_SMARTOFFT_32 (1 << 0)
+#define ISA1200_SMARTOFFT_64 (2 << 0)
+#define ISA1200_SMARTOFFT_100 (3 << 0)
+#define ISA1200_HCTRL2 0x32
+#define ISA1200_HSWRST (1 << 7)
+#define ISA1200_SESTMOD (1 << 2)
+#define ISA1200_SEEN (1 << 1)
+#define ISA1200_SEEVENT (1 << 0)
+#define ISA1200_HCTRL3 0x33
+#define ISA1200_PPLLDIV_MASK (0xf0)
+#define ISA1200_PPLLDIV_SHIFT (4)
+#define ISA1200_PPLLDIV_1 (0x0)
+#define ISA1200_PPLLDIV_2 (0x1)
+#define ISA1200_PPLLDIV_4 (0x2)
+#define ISA1200_PPLLDIV_8 (0x3)
+#define ISA1200_PPLLDIV_16 (0x4)
+#define ISA1200_PPLLDIV_32 (0x5)
+#define ISA1200_PPLLDIV_64 (0x6)
+#define ISA1200_PPLLDIV_128 (0x7)
+#define ISA1200_WPLLDIV_MASK (0x0f)
+#define ISA1200_WPLLDIV_SHIFT (0)
+#define ISA1200_WPLLDIV_1 (0x0)
+#define ISA1200_WPPLLDIV_2 (0x1)
+#define ISA1200_WPPLLDIV_4 (0x2)
+#define ISA1200_WPPLLDIV_8 (0x3)
+#define ISA1200_WPPLLDIV_16 (0x4)
+#define ISA1200_WPPLLDIV_32 (0x5)
+#define ISA1200_WPPLLDIV_64 (0x6)
+#define ISA1200_WPPLLDIV_128 (0x7)
+#define ISA1200_HCTRL4 0x34
+#define ISA1200_HCTRL5 0x35
+#define ISA1200_HCTRL6 0x36
+#define ISA1200_HCTRL7 0x37
+#define ISA1200_HCTRL8 0x38
+#define ISA1200_HCTRL9 0x39
+#define ISA1200_PLLP_SHIFT (4)
+#define ISA1200_PLLP_MASK (0xf0)
+#define ISA1200_PLLS_SHIFT (0)
+#define ISA1200_PLLS_MASK (0x0f)
+#define ISA1200_HCTRLA 0x3A
+#define ISA1200_PLLMM_MASK (0xfc)
+#define ISA1200_PLLMM_SHIFT (2)
+#define ISA1200_PLLMS_MASK (0x03)
+#define ISA1200_PLLMS_SHIFT (0)
+#define ISA1200_HCTRLB 0x3B
+#define ISA1200_HCTRLC 0x3C
+#define ISA1200_HCTRLD 0x3D
+
+#define PWM_HAPTIC_PERIOD 44640
+#define PWM_HAPTIC_DEFAULT_LEVEL 99
+
+#endif /* __LINUX_ISA1200_H */