Message ID | 1302697533-20456-3-git-send-email-mpallaka@codeaurora.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Dmitry, I've addressed your comments from previous revision. Please review and let me know if you have comments. Thanks, Mohan. On 4/13/2011 5:55 PM, Mohan Pallaka wrote: > From: Kyungmin Park <kyungmin.park@samsung.com> > > isa1200 chip can be used to generate vibrations to indicate > silent alerts, providing gaming haptic actions or vibrations > in response to touches. It can operate in two modes, pwm > generation and pwm input mode. Pwm generation mode requires > external clock and pwm input mode needs the pwm signal to be > given as input to the chip. > > This patch has been derived based on Kyungmin Park's haptic > framework patches at http://lkml.org/lkml/2009/10/7/50 . Park's > isa1200 driver registers to the haptic class, which is also > introduced as part of the same patch series, and userspace will > operate the vibrator through exported sysfs entries. This version > supports only pwm input mode. > > This derived patch converts the driver from haptic framework to > the Linux supported force feeback framework. It also supports the > chip operation in both pwm input and generation modes. > > Change-Id: If253c71e833e629431a35864e33824d8d6dadb35 > Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> > Signed-off-by: Mohan Pallaka <mpallaka@codeaurora.org> > --- > drivers/input/misc/Kconfig | 12 + > drivers/input/misc/Makefile | 1 + > drivers/input/misc/isa1200.c | 442 +++++++++++++++++++++++++++++++++++++++++ > include/linux/input/isa1200.h | 46 +++++ > 4 files changed, 501 insertions(+), 0 deletions(-) > create mode 100644 drivers/input/misc/isa1200.c > create mode 100644 include/linux/input/isa1200.h > > diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig > index f9cf088..9fd762d 100644 > --- a/drivers/input/misc/Kconfig > +++ b/drivers/input/misc/Kconfig > @@ -302,6 +302,18 @@ config HP_SDC_RTC > Say Y here if you want to support the built-in real time clock > of the HP SDC controller. > > +config INPUT_ISA1200 > + tristate "ISA1200 haptic support" > + depends on I2C > + select INPUT_FF_MEMLESS > + help > + ISA1200 is a high performance enhanced haptic chip. > + Say Y here if you want to support ISA1200 connected via I2C, > + and select N if you are unsure. > + > + To compile this driver as a module, choose M here: the > + module will be called isa1200. > + > config INPUT_PCF50633_PMU > tristate "PCF50633 PMU events" > depends on MFD_PCF50633 > diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile > index e3f7984..47f74f7 100644 > --- a/drivers/input/misc/Makefile > +++ b/drivers/input/misc/Makefile > @@ -23,6 +23,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o > obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o > obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o > obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o > +obj-$(CONFIG_INPUT_ISA1200) += isa1200.o > obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o > obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o > obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o > diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c > new file mode 100644 > index 0000000..0f5448a > --- /dev/null > +++ b/drivers/input/misc/isa1200.c > @@ -0,0 +1,442 @@ > +/* > + * Copyright (C) 2009 Samsung Electronics > + * Kyungmin Park <kyungmin.park@samsung.com> > + * > + * Copyright (c) 2011, 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. > + */ > + > +#define pr_fmt(fmt) "%s: " fmt, __func__ > + > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/i2c.h> > +#include <linux/gpio.h> > +#include <linux/workqueue.h> > +#include <linux/delay.h> > +#include <linux/pwm.h> > +#include <linux/input.h> > +#include <linux/slab.h> > +#include <linux/pm.h> > +#include <linux/input/isa1200.h> > + > +#define ISA1200_HCTRL0 0x30 > +#define HCTRL0_MODE_CTRL_BIT (3) > +#define HCTRL0_OVERDRIVE_HIGH_BIT (5) > +#define HCTRL0_OVERDRIVE_EN_BIT (6) > +#define HCTRL0_HAP_EN (7) > +#define HCTRL0_RESET 0x01 > +#define HCTRL1_RESET 0x4B > + > +#define ISA1200_HCTRL1 0x31 > +#define HCTRL1_SMART_ENABLE_BIT (3) > +#define HCTRL1_ERM_BIT (5) > +#define HCTRL1_EXT_CLK_ENABLE_BIT (7) > + > +#define ISA1200_HCTRL5 0x35 > +#define HCTRL5_VIB_STRT 0xD5 > +#define HCTRL5_VIB_STOP 0x6B > + > +#define DIVIDER_128 (128) > +#define DIVIDER_1024 (1024) > +#define DIVIDE_SHIFTER_128 (7) > + > +#define FREQ_22400 (22400) > +#define FREQ_172600 (172600) > + > +#define POR_DELAY_USEC (250) > + > +struct isa1200_chip { > + const struct isa1200_platform_data *pdata; > + struct i2c_client *client; > + struct input_dev *input_device; > + struct pwm_device *pwm; > + unsigned int period_ns; > + unsigned int state; > + struct work_struct work; > +}; > + > +static void isa1200_vib_set(struct isa1200_chip *haptic, bool enable) > +{ > + int rc; > + > + if (enable) { > + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { > + rc = pwm_config(haptic->pwm, > + (haptic->period_ns * haptic->pdata->duty) / 100, > + haptic->period_ns); > + if (rc < 0) > + pr_err("pwm_config fail\n"); > + rc = pwm_enable(haptic->pwm); > + if (rc < 0) > + pr_err("pwm_enable fail\n"); > + } else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { > + rc = i2c_smbus_write_byte_data(haptic->client, > + ISA1200_HCTRL5, > + HCTRL5_VIB_STRT); > + if (rc < 0) > + pr_err("start vibration fail\n"); > + } > + } else { > + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) > + pwm_disable(haptic->pwm); > + else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { > + rc = i2c_smbus_write_byte_data(haptic->client, > + ISA1200_HCTRL5, > + HCTRL5_VIB_STOP); > + if (rc < 0) > + pr_err("stop vibration fail\n"); > + } > + } > +} > + > +static int isa1200_setup(struct i2c_client *client) > +{ > + struct isa1200_chip *haptic = i2c_get_clientdata(client); > + int value, temp, rc; > + > + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) > + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 1); > + > + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); > + udelay(POR_DELAY_USEC); > + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1); > + > + value = (haptic->pdata->smart_en << HCTRL1_SMART_ENABLE_BIT) | > + (haptic->pdata->is_erm << HCTRL1_ERM_BIT) | > + (haptic->pdata->ext_clk_en << HCTRL1_EXT_CLK_ENABLE_BIT); > + > + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, value); > + if (rc < 0) { > + pr_err("i2c write failure\n"); > + return rc; > + } > + > + if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { > + temp = haptic->pdata->pwm_fd.pwm_div; > + if (temp < DIVIDER_128 || temp > DIVIDER_1024 || > + temp % DIVIDER_128) { > + pr_err("Invalid divisor\n"); > + rc = -EINVAL; > + goto reset_hctrl1; > + } > + value = ((temp >> DIVIDE_SHIFTER_128) - 1); > + } else if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { > + temp = haptic->pdata->pwm_fd.pwm_freq; > + if (temp < FREQ_22400 || temp > FREQ_172600 || > + temp % FREQ_22400) { > + pr_err("Invalid frequency\n"); > + rc = -EINVAL; > + goto reset_hctrl1; > + } > + value = ((temp / FREQ_22400) - 1); > + haptic->period_ns = NSEC_PER_SEC / temp; > + } > + value |= (haptic->pdata->mode_ctrl << HCTRL0_MODE_CTRL_BIT) | > + (haptic->pdata->overdrive_high << HCTRL0_OVERDRIVE_HIGH_BIT) | > + (haptic->pdata->overdrive_en << HCTRL0_OVERDRIVE_EN_BIT) | > + (haptic->pdata->chip_en << HCTRL0_HAP_EN); > + > + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, value); > + if (rc < 0) { > + pr_err("i2c write failure\n"); > + goto reset_hctrl1; > + } > + > + return 0; > + > +reset_hctrl1: > + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, > + HCTRL1_RESET); > + return rc; > +} > + > +static void isa1200_worker(struct work_struct *work) > +{ > + struct isa1200_chip *haptic; > + > + haptic = container_of(work, struct isa1200_chip, work); > + isa1200_vib_set(haptic, !!haptic->state); > +} > + > +static int isa1200_play_effect(struct input_dev *dev, void *data, > + struct ff_effect *effect) > +{ > + struct isa1200_chip *haptic = input_get_drvdata(dev); > + > + /* support basic vibration */ > + haptic->state = effect->u.rumble.strong_magnitude >> 8; > + if (!haptic->state) > + haptic->state = effect->u.rumble.weak_magnitude >> 9; > + > + schedule_work(&haptic->work); > + return 0; > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int isa1200_suspend(struct device *dev) > +{ > + struct isa1200_chip *haptic = dev_get_drvdata(dev); > + int rc; > + > + cancel_work_sync(&haptic->work); > + /* turn-off current vibration */ > + isa1200_vib_set(haptic, false); > + > + if (haptic->pdata->power_on) { > + rc = haptic->pdata->power_on(false); > + if (rc) { > + pr_err("power-down failed\n"); > + return rc; > + } > + } > + return 0; > +} > + > +static int isa1200_resume(struct device *dev) > +{ > + struct isa1200_chip *haptic = dev_get_drvdata(dev); > + int rc; > + > + if (haptic->pdata->power_on) { > + rc = haptic->pdata->power_on(true); > + if (rc) { > + pr_err("power-up failed\n"); > + return rc; > + } > + } > + > + isa1200_setup(haptic->client); > + return 0; > +} > +#else > +#define isa1200_suspend NULL > +#define isa1200_resume NULL > +#endif > + > +static int __devinit isa1200_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct isa1200_chip *haptic; > + int rc; > + > + if (!i2c_check_functionality(client->adapter, > + I2C_FUNC_SMBUS_BYTE_DATA)) { > + pr_err("i2c is not supported\n"); > + return -EIO; > + } > + > + if (!client->dev.platform_data) { > + pr_err("pdata is not avaiable\n"); > + return -EINVAL; > + } > + > + haptic = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL); > + if (!haptic) { > + pr_err("no memory\n"); > + return -ENOMEM; > + } > + > + haptic->pdata = client->dev.platform_data; > + haptic->client = client; > + > + i2c_set_clientdata(client, haptic); > + > + /* device setup */ > + if (haptic->pdata->dev_setup) { > + rc = haptic->pdata->dev_setup(true); > + if (rc < 0) { > + pr_err("setup failed!\n"); > + goto err_mem_alloc; > + } > + } > + > + /* power on */ > + if (haptic->pdata->power_on) { > + rc = haptic->pdata->power_on(true); > + if (rc < 0) { > + pr_err("power failed\n"); > + goto err_setup; > + } > + } > + > + /* request gpio */ > + rc = gpio_is_valid(haptic->pdata->hap_en_gpio); > + if (rc) { > + rc = gpio_request(haptic->pdata->hap_en_gpio, "haptic_gpio"); > + if (rc) { > + pr_err("gpio %d request failed\n", > + haptic->pdata->hap_en_gpio); > + goto err_power_on; > + } > + } else { > + pr_err("Invalid gpio %d\n", > + haptic->pdata->hap_en_gpio); > + goto err_power_on; > + } > + > + rc = gpio_direction_output(haptic->pdata->hap_en_gpio, 0); > + if (rc) { > + pr_err("gpio %d set direction failed\n", > + haptic->pdata->hap_en_gpio); > + goto err_hap_en_gpio_free; > + } > + > + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) { > + rc = gpio_request(haptic->pdata->hap_len_gpio, > + "haptic_len_gpio"); > + if (rc) { > + pr_err("gpio %d request failed\n", > + haptic->pdata->hap_len_gpio); > + goto err_hap_en_gpio_free; > + } > + } > + > + /* setup registers */ > + rc = isa1200_setup(haptic->client); > + if (rc < 0) { > + pr_err("setup fail %d\n", rc); > + goto err_ldo_en_gpio_free; > + } > + > + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { > + haptic->pwm = pwm_request(haptic->pdata->pwm_ch_id, > + haptic->client->driver->id_table->name); > + if (IS_ERR(haptic->pwm)) { > + pr_err("pwm request failed\n"); > + rc = PTR_ERR(haptic->pwm); > + goto err_reset_hctrl0; > + } > + } > + > + /* init workqeueue */ > + INIT_WORK(&haptic->work, isa1200_worker); > + > + haptic->input_device = input_allocate_device(); > + if (!haptic->input_device) { > + pr_err("input device alloc failed\n"); > + rc = -ENOMEM; > + goto err_pwm_free ; > + } > + > + input_set_drvdata(haptic->input_device, haptic); > + haptic->input_device->name = haptic->pdata->name ? : > + "isa1200-haptic-device"; > + > + haptic->input_device->dev.parent = &client->dev; > + haptic->input_device->id.bustype = BUS_I2C; > + > + input_set_capability(haptic->input_device, EV_FF, FF_RUMBLE); > + > + rc = input_ff_create_memless(haptic->input_device, NULL, > + isa1200_play_effect); > + if (rc < 0) { > + pr_err("unable to register with ff\n"); > + goto err_free_dev; > + } > + > + rc = input_register_device(haptic->input_device); > + if (rc < 0) { > + pr_err("unable to register input device\n"); > + goto err_ff_destroy; > + } > + return 0; > + > +err_ff_destroy: > + input_ff_destroy(haptic->input_device); > +err_free_dev: > + input_free_device(haptic->input_device); > +err_pwm_free: > + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) > + pwm_free(haptic->pwm); > +err_reset_hctrl0: > + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, > + HCTRL0_RESET); > +err_ldo_en_gpio_free: > + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) > + gpio_free(haptic->pdata->hap_len_gpio); > +err_hap_en_gpio_free: > + gpio_free(haptic->pdata->hap_en_gpio); > +err_power_on: > + if (haptic->pdata->power_on) > + haptic->pdata->power_on(false); > +err_setup: > + if (haptic->pdata->dev_setup) > + haptic->pdata->dev_setup(false); > +err_mem_alloc: > + kfree(haptic); > + return rc; > +} > + > +static int __devexit isa1200_remove(struct i2c_client *client) > +{ > + struct isa1200_chip *haptic = i2c_get_clientdata(client); > + > + cancel_work_sync(&haptic->work); > + /* turn-off current vibration */ > + isa1200_vib_set(haptic, false); > + > + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) > + pwm_free(haptic->pwm); > + > + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) > + gpio_free(haptic->pdata->hap_len_gpio); > + gpio_free(haptic->pdata->hap_en_gpio); > + > + /* reset hardware registers */ > + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, > + HCTRL0_RESET); > + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL1, > + 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(false); > + > + input_unregister_device(haptic->input_device); > + kfree(haptic); > + > + return 0; > +} > + > +static const struct i2c_device_id isa1200_id_table[] = { > + {"isa1200", 0}, > + { }, > +}; > +MODULE_DEVICE_TABLE(i2c, isa1200_id_table); > + > +static SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); > + > +static struct i2c_driver isa1200_driver = { > + .driver = { > + .name = "isa1200", > + .owner = THIS_MODULE, > + .pm = &isa1200_pm_ops, > + }, > + .probe = isa1200_probe, > + .remove = __devexit_p(isa1200_remove), > + .id_table = isa1200_id_table, > +}; > + > +static int __init isa1200_init(void) > +{ > + return i2c_add_driver(&isa1200_driver); > +} > +module_init(isa1200_init); > + > +static void __exit isa1200_exit(void) > +{ > + i2c_del_driver(&isa1200_driver); > +} > +module_exit(isa1200_exit); > + > +MODULE_DESCRIPTION("isa1200 based vibrator chip driver"); > +MODULE_LICENSE("GPL v2"); > +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); > +MODULE_AUTHOR("Mohan Pallaka <mpallaka@codeaurora.org>"); > diff --git a/include/linux/input/isa1200.h b/include/linux/input/isa1200.h > new file mode 100644 > index 0000000..a1b43d5 > --- /dev/null > +++ b/include/linux/input/isa1200.h > @@ -0,0 +1,46 @@ > +/* > + * Copyright (C) 2009 Samsung Electronics > + * Kyungmin Park <kyungmin.park@samsung.com> > + * > + * Copyright (c) 2011, 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. > + */ > + > +#ifndef __LINUX_ISA1200_H > +#define __LINUX_ISA1200_H > + > +enum mode_control { > + POWER_DOWN_MODE = 0, > + PWM_INPUT_MODE, > + PWM_GEN_MODE, > + WAVE_GEN_MODE > +}; > + > +union pwm_div_freq { > + unsigned int pwm_div; /* PWM gen mode */ > + unsigned int pwm_freq; /* PWM input mode */ > +}; > + > +struct isa1200_platform_data { > + const char *name; > + unsigned int pwm_ch_id; /* pwm channel id */ > + unsigned int max_timeout; > + unsigned int hap_en_gpio; > + unsigned int hap_len_gpio; /* GPIO to enable the internal LDO */ > + bool overdrive_high; /* high/low overdrive */ > + bool overdrive_en; /* enable/disable overdrive */ > + enum mode_control mode_ctrl; /* input/generation/wave */ > + union pwm_div_freq pwm_fd; > + bool smart_en; /* smart mode enable/disable */ > + bool is_erm; > + bool ext_clk_en; > + unsigned int chip_en; > + unsigned int duty; > + int (*power_on)(bool on); > + int (*dev_setup)(bool on); > +}; > + > +#endif /* __LINUX_ISA1200_H */
Ping. Hi Dmitry, Please review this patch. Regards, Mohan. On 4/14/2011 12:24 PM, Mohan Pallaka wrote: > Hi Dmitry, > > I've addressed your comments from previous revision. Please review and let me know if you have comments. > > Thanks, > Mohan. > > On 4/13/2011 5:55 PM, Mohan Pallaka wrote: >> From: Kyungmin Park <kyungmin.park@samsung.com> >> >> isa1200 chip can be used to generate vibrations to indicate >> silent alerts, providing gaming haptic actions or vibrations >> in response to touches. It can operate in two modes, pwm >> generation and pwm input mode. Pwm generation mode requires >> external clock and pwm input mode needs the pwm signal to be >> given as input to the chip. >> >> This patch has been derived based on Kyungmin Park's haptic >> framework patches at http://lkml.org/lkml/2009/10/7/50 . Park's >> isa1200 driver registers to the haptic class, which is also >> introduced as part of the same patch series, and userspace will >> operate the vibrator through exported sysfs entries. This version >> supports only pwm input mode. >> >> This derived patch converts the driver from haptic framework to >> the Linux supported force feeback framework. It also supports the >> chip operation in both pwm input and generation modes. >> >> Change-Id: If253c71e833e629431a35864e33824d8d6dadb35 >> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> >> Signed-off-by: Mohan Pallaka <mpallaka@codeaurora.org> >> --- >> drivers/input/misc/Kconfig | 12 + >> drivers/input/misc/Makefile | 1 + >> drivers/input/misc/isa1200.c | 442 +++++++++++++++++++++++++++++++++++++++++ >> include/linux/input/isa1200.h | 46 +++++ >> 4 files changed, 501 insertions(+), 0 deletions(-) >> create mode 100644 drivers/input/misc/isa1200.c >> create mode 100644 include/linux/input/isa1200.h >> >> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig >> index f9cf088..9fd762d 100644 >> --- a/drivers/input/misc/Kconfig >> +++ b/drivers/input/misc/Kconfig >> @@ -302,6 +302,18 @@ config HP_SDC_RTC >> Say Y here if you want to support the built-in real time clock >> of the HP SDC controller. >> >> +config INPUT_ISA1200 >> + tristate "ISA1200 haptic support" >> + depends on I2C >> + select INPUT_FF_MEMLESS >> + help >> + ISA1200 is a high performance enhanced haptic chip. >> + Say Y here if you want to support ISA1200 connected via I2C, >> + and select N if you are unsure. >> + >> + To compile this driver as a module, choose M here: the >> + module will be called isa1200. >> + >> config INPUT_PCF50633_PMU >> tristate "PCF50633 PMU events" >> depends on MFD_PCF50633 >> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile >> index e3f7984..47f74f7 100644 >> --- a/drivers/input/misc/Makefile >> +++ b/drivers/input/misc/Makefile >> @@ -23,6 +23,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o >> obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o >> obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o >> obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o >> +obj-$(CONFIG_INPUT_ISA1200) += isa1200.o >> obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o >> obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o >> obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o >> diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c >> new file mode 100644 >> index 0000000..0f5448a >> --- /dev/null >> +++ b/drivers/input/misc/isa1200.c >> @@ -0,0 +1,442 @@ >> +/* >> + * Copyright (C) 2009 Samsung Electronics >> + * Kyungmin Park <kyungmin.park@samsung.com> >> + * >> + * Copyright (c) 2011, 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. >> + */ >> + >> +#define pr_fmt(fmt) "%s: " fmt, __func__ >> + >> +#include <linux/module.h> >> +#include <linux/init.h> >> +#include <linux/i2c.h> >> +#include <linux/gpio.h> >> +#include <linux/workqueue.h> >> +#include <linux/delay.h> >> +#include <linux/pwm.h> >> +#include <linux/input.h> >> +#include <linux/slab.h> >> +#include <linux/pm.h> >> +#include <linux/input/isa1200.h> >> + >> +#define ISA1200_HCTRL0 0x30 >> +#define HCTRL0_MODE_CTRL_BIT (3) >> +#define HCTRL0_OVERDRIVE_HIGH_BIT (5) >> +#define HCTRL0_OVERDRIVE_EN_BIT (6) >> +#define HCTRL0_HAP_EN (7) >> +#define HCTRL0_RESET 0x01 >> +#define HCTRL1_RESET 0x4B >> + >> +#define ISA1200_HCTRL1 0x31 >> +#define HCTRL1_SMART_ENABLE_BIT (3) >> +#define HCTRL1_ERM_BIT (5) >> +#define HCTRL1_EXT_CLK_ENABLE_BIT (7) >> + >> +#define ISA1200_HCTRL5 0x35 >> +#define HCTRL5_VIB_STRT 0xD5 >> +#define HCTRL5_VIB_STOP 0x6B >> + >> +#define DIVIDER_128 (128) >> +#define DIVIDER_1024 (1024) >> +#define DIVIDE_SHIFTER_128 (7) >> + >> +#define FREQ_22400 (22400) >> +#define FREQ_172600 (172600) >> + >> +#define POR_DELAY_USEC (250) >> + >> +struct isa1200_chip { >> + const struct isa1200_platform_data *pdata; >> + struct i2c_client *client; >> + struct input_dev *input_device; >> + struct pwm_device *pwm; >> + unsigned int period_ns; >> + unsigned int state; >> + struct work_struct work; >> +}; >> + >> +static void isa1200_vib_set(struct isa1200_chip *haptic, bool enable) >> +{ >> + int rc; >> + >> + if (enable) { >> + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { >> + rc = pwm_config(haptic->pwm, >> + (haptic->period_ns * haptic->pdata->duty) / 100, >> + haptic->period_ns); >> + if (rc < 0) >> + pr_err("pwm_config fail\n"); >> + rc = pwm_enable(haptic->pwm); >> + if (rc < 0) >> + pr_err("pwm_enable fail\n"); >> + } else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { >> + rc = i2c_smbus_write_byte_data(haptic->client, >> + ISA1200_HCTRL5, >> + HCTRL5_VIB_STRT); >> + if (rc < 0) >> + pr_err("start vibration fail\n"); >> + } >> + } else { >> + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) >> + pwm_disable(haptic->pwm); >> + else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { >> + rc = i2c_smbus_write_byte_data(haptic->client, >> + ISA1200_HCTRL5, >> + HCTRL5_VIB_STOP); >> + if (rc < 0) >> + pr_err("stop vibration fail\n"); >> + } >> + } >> +} >> + >> +static int isa1200_setup(struct i2c_client *client) >> +{ >> + struct isa1200_chip *haptic = i2c_get_clientdata(client); >> + int value, temp, rc; >> + >> + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) >> + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 1); >> + >> + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); >> + udelay(POR_DELAY_USEC); >> + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1); >> + >> + value = (haptic->pdata->smart_en << HCTRL1_SMART_ENABLE_BIT) | >> + (haptic->pdata->is_erm << HCTRL1_ERM_BIT) | >> + (haptic->pdata->ext_clk_en << HCTRL1_EXT_CLK_ENABLE_BIT); >> + >> + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, value); >> + if (rc < 0) { >> + pr_err("i2c write failure\n"); >> + return rc; >> + } >> + >> + if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { >> + temp = haptic->pdata->pwm_fd.pwm_div; >> + if (temp < DIVIDER_128 || temp > DIVIDER_1024 || >> + temp % DIVIDER_128) { >> + pr_err("Invalid divisor\n"); >> + rc = -EINVAL; >> + goto reset_hctrl1; >> + } >> + value = ((temp >> DIVIDE_SHIFTER_128) - 1); >> + } else if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { >> + temp = haptic->pdata->pwm_fd.pwm_freq; >> + if (temp < FREQ_22400 || temp > FREQ_172600 || >> + temp % FREQ_22400) { >> + pr_err("Invalid frequency\n"); >> + rc = -EINVAL; >> + goto reset_hctrl1; >> + } >> + value = ((temp / FREQ_22400) - 1); >> + haptic->period_ns = NSEC_PER_SEC / temp; >> + } >> + value |= (haptic->pdata->mode_ctrl << HCTRL0_MODE_CTRL_BIT) | >> + (haptic->pdata->overdrive_high << HCTRL0_OVERDRIVE_HIGH_BIT) | >> + (haptic->pdata->overdrive_en << HCTRL0_OVERDRIVE_EN_BIT) | >> + (haptic->pdata->chip_en << HCTRL0_HAP_EN); >> + >> + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, value); >> + if (rc < 0) { >> + pr_err("i2c write failure\n"); >> + goto reset_hctrl1; >> + } >> + >> + return 0; >> + >> +reset_hctrl1: >> + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, >> + HCTRL1_RESET); >> + return rc; >> +} >> + >> +static void isa1200_worker(struct work_struct *work) >> +{ >> + struct isa1200_chip *haptic; >> + >> + haptic = container_of(work, struct isa1200_chip, work); >> + isa1200_vib_set(haptic, !!haptic->state); >> +} >> + >> +static int isa1200_play_effect(struct input_dev *dev, void *data, >> + struct ff_effect *effect) >> +{ >> + struct isa1200_chip *haptic = input_get_drvdata(dev); >> + >> + /* support basic vibration */ >> + haptic->state = effect->u.rumble.strong_magnitude >> 8; >> + if (!haptic->state) >> + haptic->state = effect->u.rumble.weak_magnitude >> 9; >> + >> + schedule_work(&haptic->work); >> + return 0; >> +} >> + >> +#ifdef CONFIG_PM_SLEEP >> +static int isa1200_suspend(struct device *dev) >> +{ >> + struct isa1200_chip *haptic = dev_get_drvdata(dev); >> + int rc; >> + >> + cancel_work_sync(&haptic->work); >> + /* turn-off current vibration */ >> + isa1200_vib_set(haptic, false); >> + >> + if (haptic->pdata->power_on) { >> + rc = haptic->pdata->power_on(false); >> + if (rc) { >> + pr_err("power-down failed\n"); >> + return rc; >> + } >> + } >> + return 0; >> +} >> + >> +static int isa1200_resume(struct device *dev) >> +{ >> + struct isa1200_chip *haptic = dev_get_drvdata(dev); >> + int rc; >> + >> + if (haptic->pdata->power_on) { >> + rc = haptic->pdata->power_on(true); >> + if (rc) { >> + pr_err("power-up failed\n"); >> + return rc; >> + } >> + } >> + >> + isa1200_setup(haptic->client); >> + return 0; >> +} >> +#else >> +#define isa1200_suspend NULL >> +#define isa1200_resume NULL >> +#endif >> + >> +static int __devinit isa1200_probe(struct i2c_client *client, >> + const struct i2c_device_id *id) >> +{ >> + struct isa1200_chip *haptic; >> + int rc; >> + >> + if (!i2c_check_functionality(client->adapter, >> + I2C_FUNC_SMBUS_BYTE_DATA)) { >> + pr_err("i2c is not supported\n"); >> + return -EIO; >> + } >> + >> + if (!client->dev.platform_data) { >> + pr_err("pdata is not avaiable\n"); >> + return -EINVAL; >> + } >> + >> + haptic = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL); >> + if (!haptic) { >> + pr_err("no memory\n"); >> + return -ENOMEM; >> + } >> + >> + haptic->pdata = client->dev.platform_data; >> + haptic->client = client; >> + >> + i2c_set_clientdata(client, haptic); >> + >> + /* device setup */ >> + if (haptic->pdata->dev_setup) { >> + rc = haptic->pdata->dev_setup(true); >> + if (rc < 0) { >> + pr_err("setup failed!\n"); >> + goto err_mem_alloc; >> + } >> + } >> + >> + /* power on */ >> + if (haptic->pdata->power_on) { >> + rc = haptic->pdata->power_on(true); >> + if (rc < 0) { >> + pr_err("power failed\n"); >> + goto err_setup; >> + } >> + } >> + >> + /* request gpio */ >> + rc = gpio_is_valid(haptic->pdata->hap_en_gpio); >> + if (rc) { >> + rc = gpio_request(haptic->pdata->hap_en_gpio, "haptic_gpio"); >> + if (rc) { >> + pr_err("gpio %d request failed\n", >> + haptic->pdata->hap_en_gpio); >> + goto err_power_on; >> + } >> + } else { >> + pr_err("Invalid gpio %d\n", >> + haptic->pdata->hap_en_gpio); >> + goto err_power_on; >> + } >> + >> + rc = gpio_direction_output(haptic->pdata->hap_en_gpio, 0); >> + if (rc) { >> + pr_err("gpio %d set direction failed\n", >> + haptic->pdata->hap_en_gpio); >> + goto err_hap_en_gpio_free; >> + } >> + >> + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) { >> + rc = gpio_request(haptic->pdata->hap_len_gpio, >> + "haptic_len_gpio"); >> + if (rc) { >> + pr_err("gpio %d request failed\n", >> + haptic->pdata->hap_len_gpio); >> + goto err_hap_en_gpio_free; >> + } >> + } >> + >> + /* setup registers */ >> + rc = isa1200_setup(haptic->client); >> + if (rc < 0) { >> + pr_err("setup fail %d\n", rc); >> + goto err_ldo_en_gpio_free; >> + } >> + >> + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { >> + haptic->pwm = pwm_request(haptic->pdata->pwm_ch_id, >> + haptic->client->driver->id_table->name); >> + if (IS_ERR(haptic->pwm)) { >> + pr_err("pwm request failed\n"); >> + rc = PTR_ERR(haptic->pwm); >> + goto err_reset_hctrl0; >> + } >> + } >> + >> + /* init workqeueue */ >> + INIT_WORK(&haptic->work, isa1200_worker); >> + >> + haptic->input_device = input_allocate_device(); >> + if (!haptic->input_device) { >> + pr_err("input device alloc failed\n"); >> + rc = -ENOMEM; >> + goto err_pwm_free ; >> + } >> + >> + input_set_drvdata(haptic->input_device, haptic); >> + haptic->input_device->name = haptic->pdata->name ? : >> + "isa1200-haptic-device"; >> + >> + haptic->input_device->dev.parent = &client->dev; >> + haptic->input_device->id.bustype = BUS_I2C; >> + >> + input_set_capability(haptic->input_device, EV_FF, FF_RUMBLE); >> + >> + rc = input_ff_create_memless(haptic->input_device, NULL, >> + isa1200_play_effect); >> + if (rc < 0) { >> + pr_err("unable to register with ff\n"); >> + goto err_free_dev; >> + } >> + >> + rc = input_register_device(haptic->input_device); >> + if (rc < 0) { >> + pr_err("unable to register input device\n"); >> + goto err_ff_destroy; >> + } >> + return 0; >> + >> +err_ff_destroy: >> + input_ff_destroy(haptic->input_device); >> +err_free_dev: >> + input_free_device(haptic->input_device); >> +err_pwm_free: >> + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) >> + pwm_free(haptic->pwm); >> +err_reset_hctrl0: >> + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, >> + HCTRL0_RESET); >> +err_ldo_en_gpio_free: >> + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) >> + gpio_free(haptic->pdata->hap_len_gpio); >> +err_hap_en_gpio_free: >> + gpio_free(haptic->pdata->hap_en_gpio); >> +err_power_on: >> + if (haptic->pdata->power_on) >> + haptic->pdata->power_on(false); >> +err_setup: >> + if (haptic->pdata->dev_setup) >> + haptic->pdata->dev_setup(false); >> +err_mem_alloc: >> + kfree(haptic); >> + return rc; >> +} >> + >> +static int __devexit isa1200_remove(struct i2c_client *client) >> +{ >> + struct isa1200_chip *haptic = i2c_get_clientdata(client); >> + >> + cancel_work_sync(&haptic->work); >> + /* turn-off current vibration */ >> + isa1200_vib_set(haptic, false); >> + >> + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) >> + pwm_free(haptic->pwm); >> + >> + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) >> + gpio_free(haptic->pdata->hap_len_gpio); >> + gpio_free(haptic->pdata->hap_en_gpio); >> + >> + /* reset hardware registers */ >> + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, >> + HCTRL0_RESET); >> + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL1, >> + 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(false); >> + >> + input_unregister_device(haptic->input_device); >> + kfree(haptic); >> + >> + return 0; >> +} >> + >> +static const struct i2c_device_id isa1200_id_table[] = { >> + {"isa1200", 0}, >> + { }, >> +}; >> +MODULE_DEVICE_TABLE(i2c, isa1200_id_table); >> + >> +static SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); >> + >> +static struct i2c_driver isa1200_driver = { >> + .driver = { >> + .name = "isa1200", >> + .owner = THIS_MODULE, >> + .pm = &isa1200_pm_ops, >> + }, >> + .probe = isa1200_probe, >> + .remove = __devexit_p(isa1200_remove), >> + .id_table = isa1200_id_table, >> +}; >> + >> +static int __init isa1200_init(void) >> +{ >> + return i2c_add_driver(&isa1200_driver); >> +} >> +module_init(isa1200_init); >> + >> +static void __exit isa1200_exit(void) >> +{ >> + i2c_del_driver(&isa1200_driver); >> +} >> +module_exit(isa1200_exit); >> + >> +MODULE_DESCRIPTION("isa1200 based vibrator chip driver"); >> +MODULE_LICENSE("GPL v2"); >> +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); >> +MODULE_AUTHOR("Mohan Pallaka <mpallaka@codeaurora.org>"); >> diff --git a/include/linux/input/isa1200.h b/include/linux/input/isa1200.h >> new file mode 100644 >> index 0000000..a1b43d5 >> --- /dev/null >> +++ b/include/linux/input/isa1200.h >> @@ -0,0 +1,46 @@ >> +/* >> + * Copyright (C) 2009 Samsung Electronics >> + * Kyungmin Park <kyungmin.park@samsung.com> >> + * >> + * Copyright (c) 2011, 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. >> + */ >> + >> +#ifndef __LINUX_ISA1200_H >> +#define __LINUX_ISA1200_H >> + >> +enum mode_control { >> + POWER_DOWN_MODE = 0, >> + PWM_INPUT_MODE, >> + PWM_GEN_MODE, >> + WAVE_GEN_MODE >> +}; >> + >> +union pwm_div_freq { >> + unsigned int pwm_div; /* PWM gen mode */ >> + unsigned int pwm_freq; /* PWM input mode */ >> +}; >> + >> +struct isa1200_platform_data { >> + const char *name; >> + unsigned int pwm_ch_id; /* pwm channel id */ >> + unsigned int max_timeout; >> + unsigned int hap_en_gpio; >> + unsigned int hap_len_gpio; /* GPIO to enable the internal LDO */ >> + bool overdrive_high; /* high/low overdrive */ >> + bool overdrive_en; /* enable/disable overdrive */ >> + enum mode_control mode_ctrl; /* input/generation/wave */ >> + union pwm_div_freq pwm_fd; >> + bool smart_en; /* smart mode enable/disable */ >> + bool is_erm; >> + bool ext_clk_en; >> + unsigned int chip_en; >> + unsigned int duty; >> + int (*power_on)(bool on); >> + int (*dev_setup)(bool on); >> +}; >> + >> +#endif /* __LINUX_ISA1200_H */ > >
Hi Mohan, On Fri, Apr 29, 2011 at 06:18:34PM +0530, Mohan Pallaka wrote: > Ping. > Sorry for the delay. The patch looks good, but could you tell me if you got the ack for the "weak" annotation for PWM symbols? Thanks.
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index f9cf088..9fd762d 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -302,6 +302,18 @@ config HP_SDC_RTC Say Y here if you want to support the built-in real time clock of the HP SDC controller. +config INPUT_ISA1200 + tristate "ISA1200 haptic support" + depends on I2C + select INPUT_FF_MEMLESS + help + ISA1200 is a high performance enhanced haptic chip. + Say Y here if you want to support ISA1200 connected via I2C, + and select N if you are unsure. + + To compile this driver as a module, choose M here: the + module will be called isa1200. + config INPUT_PCF50633_PMU tristate "PCF50633 PMU events" depends on MFD_PCF50633 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index e3f7984..47f74f7 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o +obj-$(CONFIG_INPUT_ISA1200) += isa1200.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c new file mode 100644 index 0000000..0f5448a --- /dev/null +++ b/drivers/input/misc/isa1200.c @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2009 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * + * Copyright (c) 2011, 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/pwm.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/input/isa1200.h> + +#define ISA1200_HCTRL0 0x30 +#define HCTRL0_MODE_CTRL_BIT (3) +#define HCTRL0_OVERDRIVE_HIGH_BIT (5) +#define HCTRL0_OVERDRIVE_EN_BIT (6) +#define HCTRL0_HAP_EN (7) +#define HCTRL0_RESET 0x01 +#define HCTRL1_RESET 0x4B + +#define ISA1200_HCTRL1 0x31 +#define HCTRL1_SMART_ENABLE_BIT (3) +#define HCTRL1_ERM_BIT (5) +#define HCTRL1_EXT_CLK_ENABLE_BIT (7) + +#define ISA1200_HCTRL5 0x35 +#define HCTRL5_VIB_STRT 0xD5 +#define HCTRL5_VIB_STOP 0x6B + +#define DIVIDER_128 (128) +#define DIVIDER_1024 (1024) +#define DIVIDE_SHIFTER_128 (7) + +#define FREQ_22400 (22400) +#define FREQ_172600 (172600) + +#define POR_DELAY_USEC (250) + +struct isa1200_chip { + const struct isa1200_platform_data *pdata; + struct i2c_client *client; + struct input_dev *input_device; + struct pwm_device *pwm; + unsigned int period_ns; + unsigned int state; + struct work_struct work; +}; + +static void isa1200_vib_set(struct isa1200_chip *haptic, bool enable) +{ + int rc; + + if (enable) { + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + rc = pwm_config(haptic->pwm, + (haptic->period_ns * haptic->pdata->duty) / 100, + haptic->period_ns); + if (rc < 0) + pr_err("pwm_config fail\n"); + rc = pwm_enable(haptic->pwm); + if (rc < 0) + pr_err("pwm_enable fail\n"); + } else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + rc = i2c_smbus_write_byte_data(haptic->client, + ISA1200_HCTRL5, + HCTRL5_VIB_STRT); + if (rc < 0) + pr_err("start vibration fail\n"); + } + } else { + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) + pwm_disable(haptic->pwm); + else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + rc = i2c_smbus_write_byte_data(haptic->client, + ISA1200_HCTRL5, + HCTRL5_VIB_STOP); + if (rc < 0) + pr_err("stop vibration fail\n"); + } + } +} + +static int isa1200_setup(struct i2c_client *client) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + int value, temp, rc; + + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 1); + + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + udelay(POR_DELAY_USEC); + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1); + + value = (haptic->pdata->smart_en << HCTRL1_SMART_ENABLE_BIT) | + (haptic->pdata->is_erm << HCTRL1_ERM_BIT) | + (haptic->pdata->ext_clk_en << HCTRL1_EXT_CLK_ENABLE_BIT); + + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, value); + if (rc < 0) { + pr_err("i2c write failure\n"); + return rc; + } + + if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + temp = haptic->pdata->pwm_fd.pwm_div; + if (temp < DIVIDER_128 || temp > DIVIDER_1024 || + temp % DIVIDER_128) { + pr_err("Invalid divisor\n"); + rc = -EINVAL; + goto reset_hctrl1; + } + value = ((temp >> DIVIDE_SHIFTER_128) - 1); + } else if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + temp = haptic->pdata->pwm_fd.pwm_freq; + if (temp < FREQ_22400 || temp > FREQ_172600 || + temp % FREQ_22400) { + pr_err("Invalid frequency\n"); + rc = -EINVAL; + goto reset_hctrl1; + } + value = ((temp / FREQ_22400) - 1); + haptic->period_ns = NSEC_PER_SEC / temp; + } + value |= (haptic->pdata->mode_ctrl << HCTRL0_MODE_CTRL_BIT) | + (haptic->pdata->overdrive_high << HCTRL0_OVERDRIVE_HIGH_BIT) | + (haptic->pdata->overdrive_en << HCTRL0_OVERDRIVE_EN_BIT) | + (haptic->pdata->chip_en << HCTRL0_HAP_EN); + + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, value); + if (rc < 0) { + pr_err("i2c write failure\n"); + goto reset_hctrl1; + } + + return 0; + +reset_hctrl1: + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, + HCTRL1_RESET); + return rc; +} + +static void isa1200_worker(struct work_struct *work) +{ + struct isa1200_chip *haptic; + + haptic = container_of(work, struct isa1200_chip, work); + isa1200_vib_set(haptic, !!haptic->state); +} + +static int isa1200_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct isa1200_chip *haptic = input_get_drvdata(dev); + + /* support basic vibration */ + haptic->state = effect->u.rumble.strong_magnitude >> 8; + if (!haptic->state) + haptic->state = effect->u.rumble.weak_magnitude >> 9; + + schedule_work(&haptic->work); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int isa1200_suspend(struct device *dev) +{ + struct isa1200_chip *haptic = dev_get_drvdata(dev); + int rc; + + cancel_work_sync(&haptic->work); + /* turn-off current vibration */ + isa1200_vib_set(haptic, false); + + if (haptic->pdata->power_on) { + rc = haptic->pdata->power_on(false); + if (rc) { + pr_err("power-down failed\n"); + return rc; + } + } + return 0; +} + +static int isa1200_resume(struct device *dev) +{ + struct isa1200_chip *haptic = dev_get_drvdata(dev); + int rc; + + if (haptic->pdata->power_on) { + rc = haptic->pdata->power_on(true); + if (rc) { + pr_err("power-up failed\n"); + return rc; + } + } + + isa1200_setup(haptic->client); + return 0; +} +#else +#define isa1200_suspend NULL +#define isa1200_resume NULL +#endif + +static int __devinit isa1200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isa1200_chip *haptic; + int rc; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + pr_err("i2c is not supported\n"); + return -EIO; + } + + if (!client->dev.platform_data) { + pr_err("pdata is not avaiable\n"); + return -EINVAL; + } + + haptic = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL); + if (!haptic) { + pr_err("no memory\n"); + return -ENOMEM; + } + + haptic->pdata = client->dev.platform_data; + haptic->client = client; + + i2c_set_clientdata(client, haptic); + + /* device setup */ + if (haptic->pdata->dev_setup) { + rc = haptic->pdata->dev_setup(true); + if (rc < 0) { + pr_err("setup failed!\n"); + goto err_mem_alloc; + } + } + + /* power on */ + if (haptic->pdata->power_on) { + rc = haptic->pdata->power_on(true); + if (rc < 0) { + pr_err("power failed\n"); + goto err_setup; + } + } + + /* request gpio */ + rc = gpio_is_valid(haptic->pdata->hap_en_gpio); + if (rc) { + rc = gpio_request(haptic->pdata->hap_en_gpio, "haptic_gpio"); + if (rc) { + pr_err("gpio %d request failed\n", + haptic->pdata->hap_en_gpio); + goto err_power_on; + } + } else { + pr_err("Invalid gpio %d\n", + haptic->pdata->hap_en_gpio); + goto err_power_on; + } + + rc = gpio_direction_output(haptic->pdata->hap_en_gpio, 0); + if (rc) { + pr_err("gpio %d set direction failed\n", + haptic->pdata->hap_en_gpio); + goto err_hap_en_gpio_free; + } + + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) { + rc = gpio_request(haptic->pdata->hap_len_gpio, + "haptic_len_gpio"); + if (rc) { + pr_err("gpio %d request failed\n", + haptic->pdata->hap_len_gpio); + goto err_hap_en_gpio_free; + } + } + + /* setup registers */ + rc = isa1200_setup(haptic->client); + if (rc < 0) { + pr_err("setup fail %d\n", rc); + goto err_ldo_en_gpio_free; + } + + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + haptic->pwm = pwm_request(haptic->pdata->pwm_ch_id, + haptic->client->driver->id_table->name); + if (IS_ERR(haptic->pwm)) { + pr_err("pwm request failed\n"); + rc = PTR_ERR(haptic->pwm); + goto err_reset_hctrl0; + } + } + + /* init workqeueue */ + INIT_WORK(&haptic->work, isa1200_worker); + + haptic->input_device = input_allocate_device(); + if (!haptic->input_device) { + pr_err("input device alloc failed\n"); + rc = -ENOMEM; + goto err_pwm_free ; + } + + input_set_drvdata(haptic->input_device, haptic); + haptic->input_device->name = haptic->pdata->name ? : + "isa1200-haptic-device"; + + haptic->input_device->dev.parent = &client->dev; + haptic->input_device->id.bustype = BUS_I2C; + + input_set_capability(haptic->input_device, EV_FF, FF_RUMBLE); + + rc = input_ff_create_memless(haptic->input_device, NULL, + isa1200_play_effect); + if (rc < 0) { + pr_err("unable to register with ff\n"); + goto err_free_dev; + } + + rc = input_register_device(haptic->input_device); + if (rc < 0) { + pr_err("unable to register input device\n"); + goto err_ff_destroy; + } + return 0; + +err_ff_destroy: + input_ff_destroy(haptic->input_device); +err_free_dev: + input_free_device(haptic->input_device); +err_pwm_free: + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) + pwm_free(haptic->pwm); +err_reset_hctrl0: + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, + HCTRL0_RESET); +err_ldo_en_gpio_free: + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) + gpio_free(haptic->pdata->hap_len_gpio); +err_hap_en_gpio_free: + gpio_free(haptic->pdata->hap_en_gpio); +err_power_on: + if (haptic->pdata->power_on) + haptic->pdata->power_on(false); +err_setup: + if (haptic->pdata->dev_setup) + haptic->pdata->dev_setup(false); +err_mem_alloc: + kfree(haptic); + return rc; +} + +static int __devexit isa1200_remove(struct i2c_client *client) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + + cancel_work_sync(&haptic->work); + /* turn-off current vibration */ + isa1200_vib_set(haptic, false); + + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) + pwm_free(haptic->pwm); + + if (haptic->pdata->hap_en_gpio != haptic->pdata->hap_len_gpio) + gpio_free(haptic->pdata->hap_len_gpio); + gpio_free(haptic->pdata->hap_en_gpio); + + /* reset hardware registers */ + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, + HCTRL0_RESET); + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL1, + 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(false); + + input_unregister_device(haptic->input_device); + kfree(haptic); + + return 0; +} + +static const struct i2c_device_id isa1200_id_table[] = { + {"isa1200", 0}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, isa1200_id_table); + +static SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); + +static struct i2c_driver isa1200_driver = { + .driver = { + .name = "isa1200", + .owner = THIS_MODULE, + .pm = &isa1200_pm_ops, + }, + .probe = isa1200_probe, + .remove = __devexit_p(isa1200_remove), + .id_table = isa1200_id_table, +}; + +static int __init isa1200_init(void) +{ + return i2c_add_driver(&isa1200_driver); +} +module_init(isa1200_init); + +static void __exit isa1200_exit(void) +{ + i2c_del_driver(&isa1200_driver); +} +module_exit(isa1200_exit); + +MODULE_DESCRIPTION("isa1200 based vibrator chip driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_AUTHOR("Mohan Pallaka <mpallaka@codeaurora.org>"); diff --git a/include/linux/input/isa1200.h b/include/linux/input/isa1200.h new file mode 100644 index 0000000..a1b43d5 --- /dev/null +++ b/include/linux/input/isa1200.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * + * Copyright (c) 2011, 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. + */ + +#ifndef __LINUX_ISA1200_H +#define __LINUX_ISA1200_H + +enum mode_control { + POWER_DOWN_MODE = 0, + PWM_INPUT_MODE, + PWM_GEN_MODE, + WAVE_GEN_MODE +}; + +union pwm_div_freq { + unsigned int pwm_div; /* PWM gen mode */ + unsigned int pwm_freq; /* PWM input mode */ +}; + +struct isa1200_platform_data { + const char *name; + unsigned int pwm_ch_id; /* pwm channel id */ + unsigned int max_timeout; + unsigned int hap_en_gpio; + unsigned int hap_len_gpio; /* GPIO to enable the internal LDO */ + bool overdrive_high; /* high/low overdrive */ + bool overdrive_en; /* enable/disable overdrive */ + enum mode_control mode_ctrl; /* input/generation/wave */ + union pwm_div_freq pwm_fd; + bool smart_en; /* smart mode enable/disable */ + bool is_erm; + bool ext_clk_en; + unsigned int chip_en; + unsigned int duty; + int (*power_on)(bool on); + int (*dev_setup)(bool on); +}; + +#endif /* __LINUX_ISA1200_H */