From patchwork Tue Aug 2 11:28:43 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Ujfalusi X-Patchwork-Id: 1029212 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.4) with ESMTP id p72BT6NV019914 for ; Tue, 2 Aug 2011 11:29:07 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753933Ab1HBL3F (ORCPT ); Tue, 2 Aug 2011 07:29:05 -0400 Received: from comal.ext.ti.com ([198.47.26.152]:53080 "EHLO comal.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753879Ab1HBL3A (ORCPT ); Tue, 2 Aug 2011 07:29:00 -0400 Received: from dlep36.itg.ti.com ([157.170.170.91]) by comal.ext.ti.com (8.13.7/8.13.7) with ESMTP id p72BSuuJ009569 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Tue, 2 Aug 2011 06:28:56 -0500 Received: from dlep26.itg.ti.com (smtp-le.itg.ti.com [157.170.170.27]) by dlep36.itg.ti.com (8.13.8/8.13.8) with ESMTP id p72BStrH007005; Tue, 2 Aug 2011 06:28:55 -0500 (CDT) Received: from dlee73.ent.ti.com (localhost [127.0.0.1]) by dlep26.itg.ti.com (8.13.8/8.13.8) with ESMTP id p72BSt3R024582; Tue, 2 Aug 2011 06:28:55 -0500 (CDT) Received: from dlelxv22.itg.ti.com (172.17.1.197) by DLEE73.ent.ti.com (157.170.170.88) with Microsoft SMTP Server id 8.3.106.1; Tue, 2 Aug 2011 06:28:55 -0500 Received: from barack.norway.design.ti.com (h88-3.vpn.ti.com [172.24.88.3]) by dlelxv22.itg.ti.com (8.13.8/8.13.8) with ESMTP id p72BSl3E010921; Tue, 2 Aug 2011 06:28:53 -0500 From: Peter Ujfalusi To: Liam Girdwood , Tony Lindgren , Mark Brown CC: linux-omap@vger.kernel.org, alsa-devel@alsa-project.org, Misael Lopez Cruz , Benoit Cousson , Sebastien Guiriec Subject: [PATCH 2/3] input: Add initial support for TWL6040 vibrator Date: Tue, 2 Aug 2011 14:28:43 +0300 Message-ID: <1312284526-1656-3-git-send-email-peter.ujfalusi@ti.com> X-Mailer: git-send-email 1.7.6 In-Reply-To: <1312284526-1656-1-git-send-email-peter.ujfalusi@ti.com> References: <1312284526-1656-1-git-send-email-peter.ujfalusi@ti.com> MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Tue, 02 Aug 2011 11:29:25 +0000 (UTC) From: Misael Lopez Cruz 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 Signed-off-by: Misael Lopez Cruz --- 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 + * Author: Misael Lopez Cruz + * + * Copyright: (C) 2011 Texas Instruments, Inc. + * + * Based on twl4030-vibra.c by Henrik Saari + * Felipe Balbi + * Jari Vanhala + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_AUTHOR("Misael Lopez Cruz ");