13dd1b394SJari Vanhala /* 23dd1b394SJari Vanhala * twl4030-vibra.c - TWL4030 Vibrator driver 33dd1b394SJari Vanhala * 43dd1b394SJari Vanhala * Copyright (C) 2008-2010 Nokia Corporation 53dd1b394SJari Vanhala * 63dd1b394SJari Vanhala * Written by Henrik Saari <henrik.saari@nokia.com> 73dd1b394SJari Vanhala * Updates by Felipe Balbi <felipe.balbi@nokia.com> 83dd1b394SJari Vanhala * Input by Jari Vanhala <ext-jari.vanhala@nokia.com> 93dd1b394SJari Vanhala * 103dd1b394SJari Vanhala * This program is free software; you can redistribute it and/or modify 113dd1b394SJari Vanhala * it under the terms of the GNU General Public License version 2 as 123dd1b394SJari Vanhala * published by the Free Software Foundation. 133dd1b394SJari Vanhala * 143dd1b394SJari Vanhala * This program is distributed in the hope that it will be useful, but 153dd1b394SJari Vanhala * WITHOUT ANY WARRANTY; without even the implied warranty of 163dd1b394SJari Vanhala * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 173dd1b394SJari Vanhala * General Public License for more details. 183dd1b394SJari Vanhala * 193dd1b394SJari Vanhala * You should have received a copy of the GNU General Public License 203dd1b394SJari Vanhala * along with this program; if not, write to the Free Software 213dd1b394SJari Vanhala * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 223dd1b394SJari Vanhala * 02110-1301 USA 233dd1b394SJari Vanhala * 243dd1b394SJari Vanhala */ 253dd1b394SJari Vanhala 263dd1b394SJari Vanhala #include <linux/module.h> 273dd1b394SJari Vanhala #include <linux/jiffies.h> 283dd1b394SJari Vanhala #include <linux/platform_device.h> 293dd1b394SJari Vanhala #include <linux/workqueue.h> 303dd1b394SJari Vanhala #include <linux/i2c/twl.h> 3157fe7251SPeter Ujfalusi #include <linux/mfd/twl4030-audio.h> 323dd1b394SJari Vanhala #include <linux/input.h> 335a0e3ad6STejun Heo #include <linux/slab.h> 343dd1b394SJari Vanhala 353dd1b394SJari Vanhala /* MODULE ID2 */ 363dd1b394SJari Vanhala #define LEDEN 0x00 373dd1b394SJari Vanhala 383dd1b394SJari Vanhala /* ForceFeedback */ 393dd1b394SJari Vanhala #define EFFECT_DIR_180_DEG 0x8000 /* range is 0 - 0xFFFF */ 403dd1b394SJari Vanhala 413dd1b394SJari Vanhala struct vibra_info { 423dd1b394SJari Vanhala struct device *dev; 433dd1b394SJari Vanhala struct input_dev *input_dev; 443dd1b394SJari Vanhala 453dd1b394SJari Vanhala struct workqueue_struct *workqueue; 463dd1b394SJari Vanhala struct work_struct play_work; 473dd1b394SJari Vanhala 483dd1b394SJari Vanhala bool enabled; 493dd1b394SJari Vanhala int speed; 503dd1b394SJari Vanhala int direction; 513dd1b394SJari Vanhala 523dd1b394SJari Vanhala bool coexist; 533dd1b394SJari Vanhala }; 543dd1b394SJari Vanhala 553dd1b394SJari Vanhala static void vibra_disable_leds(void) 563dd1b394SJari Vanhala { 573dd1b394SJari Vanhala u8 reg; 583dd1b394SJari Vanhala 593dd1b394SJari Vanhala /* Disable LEDA & LEDB, cannot be used with vibra (PWM) */ 603dd1b394SJari Vanhala twl_i2c_read_u8(TWL4030_MODULE_LED, ®, LEDEN); 613dd1b394SJari Vanhala reg &= ~0x03; 623dd1b394SJari Vanhala twl_i2c_write_u8(TWL4030_MODULE_LED, LEDEN, reg); 633dd1b394SJari Vanhala } 643dd1b394SJari Vanhala 653dd1b394SJari Vanhala /* Powers H-Bridge and enables audio clk */ 663dd1b394SJari Vanhala static void vibra_enable(struct vibra_info *info) 673dd1b394SJari Vanhala { 683dd1b394SJari Vanhala u8 reg; 693dd1b394SJari Vanhala 7057fe7251SPeter Ujfalusi twl4030_audio_enable_resource(TWL4030_AUDIO_RES_POWER); 713dd1b394SJari Vanhala 723dd1b394SJari Vanhala /* turn H-Bridge on */ 733dd1b394SJari Vanhala twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 743dd1b394SJari Vanhala ®, TWL4030_REG_VIBRA_CTL); 753dd1b394SJari Vanhala twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 763dd1b394SJari Vanhala (reg | TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL); 773dd1b394SJari Vanhala 7857fe7251SPeter Ujfalusi twl4030_audio_enable_resource(TWL4030_AUDIO_RES_APLL); 793dd1b394SJari Vanhala 803dd1b394SJari Vanhala info->enabled = true; 813dd1b394SJari Vanhala } 823dd1b394SJari Vanhala 833dd1b394SJari Vanhala static void vibra_disable(struct vibra_info *info) 843dd1b394SJari Vanhala { 853dd1b394SJari Vanhala u8 reg; 863dd1b394SJari Vanhala 873dd1b394SJari Vanhala /* Power down H-Bridge */ 883dd1b394SJari Vanhala twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 893dd1b394SJari Vanhala ®, TWL4030_REG_VIBRA_CTL); 903dd1b394SJari Vanhala twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 913dd1b394SJari Vanhala (reg & ~TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL); 923dd1b394SJari Vanhala 9357fe7251SPeter Ujfalusi twl4030_audio_disable_resource(TWL4030_AUDIO_RES_APLL); 9457fe7251SPeter Ujfalusi twl4030_audio_disable_resource(TWL4030_AUDIO_RES_POWER); 953dd1b394SJari Vanhala 963dd1b394SJari Vanhala info->enabled = false; 973dd1b394SJari Vanhala } 983dd1b394SJari Vanhala 993dd1b394SJari Vanhala static void vibra_play_work(struct work_struct *work) 1003dd1b394SJari Vanhala { 1013dd1b394SJari Vanhala struct vibra_info *info = container_of(work, 1023dd1b394SJari Vanhala struct vibra_info, play_work); 1033dd1b394SJari Vanhala int dir; 1043dd1b394SJari Vanhala int pwm; 1053dd1b394SJari Vanhala u8 reg; 1063dd1b394SJari Vanhala 1073dd1b394SJari Vanhala dir = info->direction; 1083dd1b394SJari Vanhala pwm = info->speed; 1093dd1b394SJari Vanhala 1103dd1b394SJari Vanhala twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 1113dd1b394SJari Vanhala ®, TWL4030_REG_VIBRA_CTL); 1123dd1b394SJari Vanhala if (pwm && (!info->coexist || !(reg & TWL4030_VIBRA_SEL))) { 1133dd1b394SJari Vanhala 1143dd1b394SJari Vanhala if (!info->enabled) 1153dd1b394SJari Vanhala vibra_enable(info); 1163dd1b394SJari Vanhala 1173dd1b394SJari Vanhala /* set vibra rotation direction */ 1183dd1b394SJari Vanhala twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 1193dd1b394SJari Vanhala ®, TWL4030_REG_VIBRA_CTL); 1203dd1b394SJari Vanhala reg = (dir) ? (reg | TWL4030_VIBRA_DIR) : 1213dd1b394SJari Vanhala (reg & ~TWL4030_VIBRA_DIR); 1223dd1b394SJari Vanhala twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 1233dd1b394SJari Vanhala reg, TWL4030_REG_VIBRA_CTL); 1243dd1b394SJari Vanhala 1253dd1b394SJari Vanhala /* set PWM, 1 = max, 255 = min */ 1263dd1b394SJari Vanhala twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 1273dd1b394SJari Vanhala 256 - pwm, TWL4030_REG_VIBRA_SET); 1283dd1b394SJari Vanhala } else { 1293dd1b394SJari Vanhala if (info->enabled) 1303dd1b394SJari Vanhala vibra_disable(info); 1313dd1b394SJari Vanhala } 1323dd1b394SJari Vanhala } 1333dd1b394SJari Vanhala 1343dd1b394SJari Vanhala /*** Input/ForceFeedback ***/ 1353dd1b394SJari Vanhala 1363dd1b394SJari Vanhala static int vibra_play(struct input_dev *input, void *data, 1373dd1b394SJari Vanhala struct ff_effect *effect) 1383dd1b394SJari Vanhala { 1393dd1b394SJari Vanhala struct vibra_info *info = input_get_drvdata(input); 1403dd1b394SJari Vanhala 1413dd1b394SJari Vanhala info->speed = effect->u.rumble.strong_magnitude >> 8; 1423dd1b394SJari Vanhala if (!info->speed) 1433dd1b394SJari Vanhala info->speed = effect->u.rumble.weak_magnitude >> 9; 1443dd1b394SJari Vanhala info->direction = effect->direction < EFFECT_DIR_180_DEG ? 0 : 1; 1453dd1b394SJari Vanhala queue_work(info->workqueue, &info->play_work); 1463dd1b394SJari Vanhala return 0; 1473dd1b394SJari Vanhala } 1483dd1b394SJari Vanhala 1493dd1b394SJari Vanhala static int twl4030_vibra_open(struct input_dev *input) 1503dd1b394SJari Vanhala { 1513dd1b394SJari Vanhala struct vibra_info *info = input_get_drvdata(input); 1523dd1b394SJari Vanhala 1533dd1b394SJari Vanhala info->workqueue = create_singlethread_workqueue("vibra"); 1543dd1b394SJari Vanhala if (info->workqueue == NULL) { 1553dd1b394SJari Vanhala dev_err(&input->dev, "couldn't create workqueue\n"); 1563dd1b394SJari Vanhala return -ENOMEM; 1573dd1b394SJari Vanhala } 1583dd1b394SJari Vanhala return 0; 1593dd1b394SJari Vanhala } 1603dd1b394SJari Vanhala 1613dd1b394SJari Vanhala static void twl4030_vibra_close(struct input_dev *input) 1623dd1b394SJari Vanhala { 1633dd1b394SJari Vanhala struct vibra_info *info = input_get_drvdata(input); 1643dd1b394SJari Vanhala 1653dd1b394SJari Vanhala cancel_work_sync(&info->play_work); 1663dd1b394SJari Vanhala INIT_WORK(&info->play_work, vibra_play_work); /* cleanup */ 1673dd1b394SJari Vanhala destroy_workqueue(info->workqueue); 1683dd1b394SJari Vanhala info->workqueue = NULL; 1693dd1b394SJari Vanhala 1703dd1b394SJari Vanhala if (info->enabled) 1713dd1b394SJari Vanhala vibra_disable(info); 1723dd1b394SJari Vanhala } 1733dd1b394SJari Vanhala 1743dd1b394SJari Vanhala /*** Module ***/ 1753dd1b394SJari Vanhala #if CONFIG_PM 1763dd1b394SJari Vanhala static int twl4030_vibra_suspend(struct device *dev) 1773dd1b394SJari Vanhala { 1783dd1b394SJari Vanhala struct platform_device *pdev = to_platform_device(dev); 1793dd1b394SJari Vanhala struct vibra_info *info = platform_get_drvdata(pdev); 1803dd1b394SJari Vanhala 1813dd1b394SJari Vanhala if (info->enabled) 1823dd1b394SJari Vanhala vibra_disable(info); 1833dd1b394SJari Vanhala 1843dd1b394SJari Vanhala return 0; 1853dd1b394SJari Vanhala } 1863dd1b394SJari Vanhala 1873dd1b394SJari Vanhala static int twl4030_vibra_resume(struct device *dev) 1883dd1b394SJari Vanhala { 1893dd1b394SJari Vanhala vibra_disable_leds(); 1903dd1b394SJari Vanhala return 0; 1913dd1b394SJari Vanhala } 1923dd1b394SJari Vanhala 1933dd1b394SJari Vanhala static SIMPLE_DEV_PM_OPS(twl4030_vibra_pm_ops, 1943dd1b394SJari Vanhala twl4030_vibra_suspend, twl4030_vibra_resume); 1953dd1b394SJari Vanhala #endif 1963dd1b394SJari Vanhala 1973dd1b394SJari Vanhala static int __devinit twl4030_vibra_probe(struct platform_device *pdev) 1983dd1b394SJari Vanhala { 1994ae6df5eSPeter Ujfalusi struct twl4030_vibra_data *pdata = pdev->dev.platform_data; 2003dd1b394SJari Vanhala struct vibra_info *info; 2013dd1b394SJari Vanhala int ret; 2023dd1b394SJari Vanhala 2033dd1b394SJari Vanhala if (!pdata) { 2043dd1b394SJari Vanhala dev_dbg(&pdev->dev, "platform_data not available\n"); 2053dd1b394SJari Vanhala return -EINVAL; 2063dd1b394SJari Vanhala } 2073dd1b394SJari Vanhala 2083dd1b394SJari Vanhala info = kzalloc(sizeof(*info), GFP_KERNEL); 2093dd1b394SJari Vanhala if (!info) 2103dd1b394SJari Vanhala return -ENOMEM; 2113dd1b394SJari Vanhala 2123dd1b394SJari Vanhala info->dev = &pdev->dev; 2133dd1b394SJari Vanhala info->coexist = pdata->coexist; 2143dd1b394SJari Vanhala INIT_WORK(&info->play_work, vibra_play_work); 2153dd1b394SJari Vanhala 2163dd1b394SJari Vanhala info->input_dev = input_allocate_device(); 2173dd1b394SJari Vanhala if (info->input_dev == NULL) { 2183dd1b394SJari Vanhala dev_err(&pdev->dev, "couldn't allocate input device\n"); 2193dd1b394SJari Vanhala ret = -ENOMEM; 2203dd1b394SJari Vanhala goto err_kzalloc; 2213dd1b394SJari Vanhala } 2223dd1b394SJari Vanhala 2233dd1b394SJari Vanhala input_set_drvdata(info->input_dev, info); 2243dd1b394SJari Vanhala 2253dd1b394SJari Vanhala info->input_dev->name = "twl4030:vibrator"; 2263dd1b394SJari Vanhala info->input_dev->id.version = 1; 2273dd1b394SJari Vanhala info->input_dev->dev.parent = pdev->dev.parent; 2283dd1b394SJari Vanhala info->input_dev->open = twl4030_vibra_open; 2293dd1b394SJari Vanhala info->input_dev->close = twl4030_vibra_close; 2303dd1b394SJari Vanhala __set_bit(FF_RUMBLE, info->input_dev->ffbit); 2313dd1b394SJari Vanhala 2323dd1b394SJari Vanhala ret = input_ff_create_memless(info->input_dev, NULL, vibra_play); 2333dd1b394SJari Vanhala if (ret < 0) { 2343dd1b394SJari Vanhala dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n"); 2353dd1b394SJari Vanhala goto err_ialloc; 2363dd1b394SJari Vanhala } 2373dd1b394SJari Vanhala 2383dd1b394SJari Vanhala ret = input_register_device(info->input_dev); 2393dd1b394SJari Vanhala if (ret < 0) { 2403dd1b394SJari Vanhala dev_dbg(&pdev->dev, "couldn't register input device\n"); 2413dd1b394SJari Vanhala goto err_iff; 2423dd1b394SJari Vanhala } 2433dd1b394SJari Vanhala 2443dd1b394SJari Vanhala vibra_disable_leds(); 2453dd1b394SJari Vanhala 2463dd1b394SJari Vanhala platform_set_drvdata(pdev, info); 2473dd1b394SJari Vanhala return 0; 2483dd1b394SJari Vanhala 2493dd1b394SJari Vanhala err_iff: 2503dd1b394SJari Vanhala input_ff_destroy(info->input_dev); 2513dd1b394SJari Vanhala err_ialloc: 2523dd1b394SJari Vanhala input_free_device(info->input_dev); 2533dd1b394SJari Vanhala err_kzalloc: 2543dd1b394SJari Vanhala kfree(info); 2553dd1b394SJari Vanhala return ret; 2563dd1b394SJari Vanhala } 2573dd1b394SJari Vanhala 2583dd1b394SJari Vanhala static int __devexit twl4030_vibra_remove(struct platform_device *pdev) 2593dd1b394SJari Vanhala { 2603dd1b394SJari Vanhala struct vibra_info *info = platform_get_drvdata(pdev); 2613dd1b394SJari Vanhala 2623dd1b394SJari Vanhala /* this also free ff-memless and calls close if needed */ 2633dd1b394SJari Vanhala input_unregister_device(info->input_dev); 2643dd1b394SJari Vanhala kfree(info); 2653dd1b394SJari Vanhala platform_set_drvdata(pdev, NULL); 2663dd1b394SJari Vanhala 2673dd1b394SJari Vanhala return 0; 2683dd1b394SJari Vanhala } 2693dd1b394SJari Vanhala 2703dd1b394SJari Vanhala static struct platform_driver twl4030_vibra_driver = { 2713dd1b394SJari Vanhala .probe = twl4030_vibra_probe, 2723dd1b394SJari Vanhala .remove = __devexit_p(twl4030_vibra_remove), 2733dd1b394SJari Vanhala .driver = { 274f0fba2adSLiam Girdwood .name = "twl4030-vibra", 2753dd1b394SJari Vanhala .owner = THIS_MODULE, 2763dd1b394SJari Vanhala #ifdef CONFIG_PM 2773dd1b394SJari Vanhala .pm = &twl4030_vibra_pm_ops, 2783dd1b394SJari Vanhala #endif 2793dd1b394SJari Vanhala }, 2803dd1b394SJari Vanhala }; 2813dd1b394SJari Vanhala 2823dd1b394SJari Vanhala static int __init twl4030_vibra_init(void) 2833dd1b394SJari Vanhala { 2843dd1b394SJari Vanhala return platform_driver_register(&twl4030_vibra_driver); 2853dd1b394SJari Vanhala } 2863dd1b394SJari Vanhala module_init(twl4030_vibra_init); 2873dd1b394SJari Vanhala 2883dd1b394SJari Vanhala static void __exit twl4030_vibra_exit(void) 2893dd1b394SJari Vanhala { 2903dd1b394SJari Vanhala platform_driver_unregister(&twl4030_vibra_driver); 2913dd1b394SJari Vanhala } 2923dd1b394SJari Vanhala module_exit(twl4030_vibra_exit); 2933dd1b394SJari Vanhala 294f0fba2adSLiam Girdwood MODULE_ALIAS("platform:twl4030-vibra"); 2953dd1b394SJari Vanhala 2963dd1b394SJari Vanhala MODULE_DESCRIPTION("TWL4030 Vibra driver"); 2973dd1b394SJari Vanhala MODULE_LICENSE("GPL"); 2983dd1b394SJari Vanhala MODULE_AUTHOR("Nokia Corporation"); 299