From: Misael Lopez Cruz misael.lopez@ti.com
Add twl6040_vibra as a child of MFD device twl6040_codec. This implementation covers the PCM-to-PWM mode of TWL6040 vibrator module.
Signed-off-by: Jorge Eduardo Candelaria jorge.candelaria@ti.com Signed-off-by: Misael Lopez Cruz misael.lopez@ti.com --- drivers/input/misc/Kconfig | 11 ++ drivers/input/misc/Makefile | 1 + drivers/input/misc/twl6040-vibra.c | 333 ++++++++++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 0 deletions(-) create mode 100644 drivers/input/misc/twl6040-vibra.c
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index f9cf088..9f78f42 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -275,6 +275,17 @@ config INPUT_TWL4030_VIBRA To compile this driver as a module, choose M here. The module will be called twl4030_vibra.
+config INPUT_TWL6040_VIBRA + tristate "Support for TWL6040 Vibrator" + depends on TWL4030_CORE + select TWL6040_CODEC + select INPUT_FF_MEMLESS + help + This option enables support for TWL6040 Vibrator Driver. + + To compile this driver as a module, choose M here. The module will + be called twl6040_vibra. + config INPUT_UINPUT tristate "User level driver support" help diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index e3f7984..3ad998d 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o +obj-$(CONFIG_INPUT_TWL6040_VIBRA) += twl6040-vibra.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c new file mode 100644 index 0000000..fb46bf4 --- /dev/null +++ b/drivers/input/misc/twl6040-vibra.c @@ -0,0 +1,333 @@ +/* + * twl6040-vibra.c - TWL6040 Vibrator driver + * + * Author: Jorge Eduardo Candelaria jorge.candelaria@ti.com + * Author: Misael Lopez Cruz misael.lopez@ti.com + * + * Copyright: (C) 2011 Texas Instruments, Inc. + * + * Based on twl4030-vibra.c by Henrik Saari henrik.saari@nokia.com + * Felipe Balbi felipe.balbi@nokia.com + * Jari Vanhala ext-javi.vanhala@nokia.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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/i2c/twl.h> +#include <linux/mfd/twl6040-codec.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#define EFFECT_DIR_180_DEG 0x8000 + +struct vibra_info { + struct device *dev; + struct input_dev *input_dev; + struct workqueue_struct *workqueue; + struct work_struct play_work; + + bool enabled; + int speed; + int direction; + + struct twl6040 *twl6040; +}; + +static irqreturn_t twl6040_vib_irq_handler(int irq, void *data) +{ + struct vibra_info *info = data; + struct twl6040 *twl6040 = info->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) { + dev_warn(info->dev, + "Vibra left overcurrent detected\n"); + twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL, + TWL6040_VIBENAL); + } + if (status & TWL6040_VIBROCDET) { + dev_warn(info->dev, + "Vibra right overcurrent detected\n"); + twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR, + TWL6040_VIBENAR); + } + } + + return IRQ_HANDLED; +} + +static void twl6040_vibra_enable(struct vibra_info *info) +{ + struct twl6040 *twl6040 = info->twl6040; + + /* + * ERRATA: Disable overcurrent protection for at least + * 2.5ms when enabling vibrator drivers to avoid false + * overcurrent detection + */ + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, + TWL6040_VIBENAL | TWL6040_VIBCTRLL); + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, + TWL6040_VIBENAR | TWL6040_VIBCTRLR); + mdelay(3); + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, + TWL6040_VIBENAL); + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, + TWL6040_VIBENAR); + + info->enabled = true; +} + +static void twl6040_vibra_disable(struct vibra_info *info) +{ + struct twl6040 *twl6040 = info->twl6040; + + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, 0x00); + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, 0x00); + + info->enabled = false; +} + +static void twl6040_vibra_set_effect(struct vibra_info *info) +{ + struct twl6040 *twl6040 = info->twl6040; + u8 vibdat = (u8)(info->speed); + + /* 2's complement for direction > 180 degrees */ + vibdat *= info->direction; + + twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, vibdat); + twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, vibdat); +} + +static void vibra_play_work(struct work_struct *work) +{ + struct vibra_info *info = container_of(work, + struct vibra_info, play_work); + + if (!info->enabled) + twl6040_vibra_enable(info); + + twl6040_vibra_set_effect(info); +} + +static int vibra_play(struct input_dev *input, void *data, + struct ff_effect *effect) +{ + struct vibra_info *info = input_get_drvdata(input); + int ret; + + info->speed = effect->u.rumble.strong_magnitude; + if (!info->speed) + info->speed = effect->u.rumble.weak_magnitude >> 1; + + /* scale to VIBDAT allowed limits */ + info->speed = (info->speed * TWL6040_VIBDAT_MAX) / USHRT_MAX; + info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1; + + ret = queue_work(info->workqueue, &info->play_work); + if (!ret) { + dev_err(&input->dev, "work is already on queue\n"); + return ret; + } + + return 0; +} + +static int twl6040_vibra_open(struct input_dev *input) +{ + struct vibra_info *info = input_get_drvdata(input); + + info->workqueue = create_singlethread_workqueue("vibra"); + if (info->workqueue == NULL) { + dev_err(&input->dev, "couldn't create workqueue\n"); + return -ENOMEM; + } + + return 0; +} + +static void twl6040_vibra_close(struct input_dev *input) +{ + struct vibra_info *info = input_get_drvdata(input); + + cancel_work_sync(&info->play_work); + INIT_WORK(&info->play_work, vibra_play_work); + destroy_workqueue(info->workqueue); + info->workqueue = NULL; + + info->direction = 0; + info->speed = 0; + twl6040_vibra_set_effect(info); + + if (info->enabled) + twl6040_vibra_disable(info); +} + +#if CONFIG_PM +static int twl6040_vibra_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vibra_info *info = platform_get_drvdata(pdev); + + twl6040_power(info->twl6040, 0); + + return 0; +} + +static int twl6040_vibra_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vibra_info *info = platform_get_drvdata(pdev); + + twl6040_power(info->twl6040, 1); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(twl6040_vibra_pm_ops, + twl6040_vibra_suspend, twl6040_vibra_resume); +#endif + +static int __devinit twl6040_vibra_probe(struct platform_device *pdev) +{ + struct twl4030_codec_vibra_data *pdata = pdev->dev.platform_data; + struct vibra_info *info; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "platform_data not available\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "couldn't allocate memory\n"); + return -ENOMEM; + } + + info->dev = &pdev->dev; + info->twl6040 = dev_get_drvdata(pdev->dev.parent); + INIT_WORK(&info->play_work, vibra_play_work); + + info->input_dev = input_allocate_device(); + if (info->input_dev == NULL) { + dev_err(&pdev->dev, "couldn't allocate input device\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + + input_set_drvdata(info->input_dev, info); + + info->input_dev->name = "twl6040:vibrator"; + info->input_dev->id.version = 1; + info->input_dev->dev.parent = pdev->dev.parent; + info->input_dev->open = twl6040_vibra_open; + info->input_dev->close = twl6040_vibra_close; + __set_bit(FF_RUMBLE, info->input_dev->ffbit); + + ret = input_ff_create_memless(info->input_dev, NULL, vibra_play); + if (ret < 0) { + dev_err(&pdev->dev, "couldn't register vibrator to FF\n"); + goto err_ialloc; + } + + ret = input_register_device(info->input_dev); + if (ret < 0) { + dev_err(&pdev->dev, "couldn't register input device\n"); + goto err_iff; + } + + platform_set_drvdata(pdev, info); + + ret = twl6040_request_irq(info->twl6040, TWL6040_IRQ_VIB, + twl6040_vib_irq_handler, 0, + "twl6040_irq_vib", info); + if (ret) { + dev_err(&pdev->dev, "VIB IRQ request failed: %d\n", ret); + goto err_irq; + } + + printk(KERN_ERR "%s:powering twl6040\n", __func__); + ret = twl6040_power(info->twl6040, 1); + if (ret < 0) + goto err_pwr; + + printk(KERN_ERR "%s:powered\n", __func__); + return 0; + +err_pwr: + twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info); +err_irq: + input_unregister_device(info->input_dev); + info->input_dev = NULL; +err_iff: + if (info->input_dev) + input_ff_destroy(info->input_dev); +err_ialloc: + input_free_device(info->input_dev); +err_kzalloc: + kfree(info); + return ret; +} + +static int __devexit twl6040_vibra_remove(struct platform_device *pdev) +{ + struct vibra_info *info = platform_get_drvdata(pdev); + + twl6040_power(info->twl6040, 0); + twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info); + input_unregister_device(info->input_dev); + kfree(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver twl6040_vibra_driver = { + .probe = twl6040_vibra_probe, + .remove = __devexit_p(twl6040_vibra_remove), + .driver = { + .name = "twl6040-vibra", + .owner = THIS_MODULE, +#if CONFIG_PM + .pm = &twl6040_vibra_pm_ops, +#endif + }, +}; + +static int __init twl6040_vibra_init(void) +{ + return platform_driver_register(&twl6040_vibra_driver); +} +module_init(twl6040_vibra_init); + +static void __exit twl6040_vibra_exit(void) +{ + platform_driver_unregister(&twl6040_vibra_driver); +} +module_exit(twl6040_vibra_exit); + +MODULE_ALIAS("platform:twl6040-vibra"); +MODULE_DESCRIPTION("TWL6040 Vibra driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jorge Eduardo Candelaria jorge.candelaria@ti.com"); +MODULE_AUTHOR("Misael Lopez Cruz misael.lopez@ti.com");