162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * twl4030-vibra.c - TWL4030 Vibrator driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2008-2010 Nokia Corporation 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Written by Henrik Saari <henrik.saari@nokia.com> 862306a36Sopenharmony_ci * Updates by Felipe Balbi <felipe.balbi@nokia.com> 962306a36Sopenharmony_ci * Input by Jari Vanhala <ext-jari.vanhala@nokia.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/jiffies.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/of.h> 1662306a36Sopenharmony_ci#include <linux/workqueue.h> 1762306a36Sopenharmony_ci#include <linux/mfd/twl.h> 1862306a36Sopenharmony_ci#include <linux/mfd/twl4030-audio.h> 1962306a36Sopenharmony_ci#include <linux/input.h> 2062306a36Sopenharmony_ci#include <linux/slab.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* MODULE ID2 */ 2362306a36Sopenharmony_ci#define LEDEN 0x00 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* ForceFeedback */ 2662306a36Sopenharmony_ci#define EFFECT_DIR_180_DEG 0x8000 /* range is 0 - 0xFFFF */ 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct vibra_info { 2962306a36Sopenharmony_ci struct device *dev; 3062306a36Sopenharmony_ci struct input_dev *input_dev; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci struct work_struct play_work; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci bool enabled; 3562306a36Sopenharmony_ci int speed; 3662306a36Sopenharmony_ci int direction; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci bool coexist; 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic void vibra_disable_leds(void) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci u8 reg; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* Disable LEDA & LEDB, cannot be used with vibra (PWM) */ 4662306a36Sopenharmony_ci twl_i2c_read_u8(TWL4030_MODULE_LED, ®, LEDEN); 4762306a36Sopenharmony_ci reg &= ~0x03; 4862306a36Sopenharmony_ci twl_i2c_write_u8(TWL4030_MODULE_LED, LEDEN, reg); 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci/* Powers H-Bridge and enables audio clk */ 5262306a36Sopenharmony_cistatic void vibra_enable(struct vibra_info *info) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci u8 reg; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci twl4030_audio_enable_resource(TWL4030_AUDIO_RES_POWER); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* turn H-Bridge on */ 5962306a36Sopenharmony_ci twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 6062306a36Sopenharmony_ci ®, TWL4030_REG_VIBRA_CTL); 6162306a36Sopenharmony_ci twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 6262306a36Sopenharmony_ci (reg | TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci twl4030_audio_enable_resource(TWL4030_AUDIO_RES_APLL); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci info->enabled = true; 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic void vibra_disable(struct vibra_info *info) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci u8 reg; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci /* Power down H-Bridge */ 7462306a36Sopenharmony_ci twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 7562306a36Sopenharmony_ci ®, TWL4030_REG_VIBRA_CTL); 7662306a36Sopenharmony_ci twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 7762306a36Sopenharmony_ci (reg & ~TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci twl4030_audio_disable_resource(TWL4030_AUDIO_RES_APLL); 8062306a36Sopenharmony_ci twl4030_audio_disable_resource(TWL4030_AUDIO_RES_POWER); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci info->enabled = false; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic void vibra_play_work(struct work_struct *work) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct vibra_info *info = container_of(work, 8862306a36Sopenharmony_ci struct vibra_info, play_work); 8962306a36Sopenharmony_ci int dir; 9062306a36Sopenharmony_ci int pwm; 9162306a36Sopenharmony_ci u8 reg; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci dir = info->direction; 9462306a36Sopenharmony_ci pwm = info->speed; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 9762306a36Sopenharmony_ci ®, TWL4030_REG_VIBRA_CTL); 9862306a36Sopenharmony_ci if (pwm && (!info->coexist || !(reg & TWL4030_VIBRA_SEL))) { 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci if (!info->enabled) 10162306a36Sopenharmony_ci vibra_enable(info); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci /* set vibra rotation direction */ 10462306a36Sopenharmony_ci twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, 10562306a36Sopenharmony_ci ®, TWL4030_REG_VIBRA_CTL); 10662306a36Sopenharmony_ci reg = (dir) ? (reg | TWL4030_VIBRA_DIR) : 10762306a36Sopenharmony_ci (reg & ~TWL4030_VIBRA_DIR); 10862306a36Sopenharmony_ci twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 10962306a36Sopenharmony_ci reg, TWL4030_REG_VIBRA_CTL); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* set PWM, 1 = max, 255 = min */ 11262306a36Sopenharmony_ci twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 11362306a36Sopenharmony_ci 256 - pwm, TWL4030_REG_VIBRA_SET); 11462306a36Sopenharmony_ci } else { 11562306a36Sopenharmony_ci if (info->enabled) 11662306a36Sopenharmony_ci vibra_disable(info); 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci/*** Input/ForceFeedback ***/ 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int vibra_play(struct input_dev *input, void *data, 12362306a36Sopenharmony_ci struct ff_effect *effect) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci struct vibra_info *info = input_get_drvdata(input); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci info->speed = effect->u.rumble.strong_magnitude >> 8; 12862306a36Sopenharmony_ci if (!info->speed) 12962306a36Sopenharmony_ci info->speed = effect->u.rumble.weak_magnitude >> 9; 13062306a36Sopenharmony_ci info->direction = effect->direction < EFFECT_DIR_180_DEG ? 0 : 1; 13162306a36Sopenharmony_ci schedule_work(&info->play_work); 13262306a36Sopenharmony_ci return 0; 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic void twl4030_vibra_close(struct input_dev *input) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci struct vibra_info *info = input_get_drvdata(input); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci cancel_work_sync(&info->play_work); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (info->enabled) 14262306a36Sopenharmony_ci vibra_disable(info); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci/*** Module ***/ 14662306a36Sopenharmony_cistatic int twl4030_vibra_suspend(struct device *dev) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 14962306a36Sopenharmony_ci struct vibra_info *info = platform_get_drvdata(pdev); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (info->enabled) 15262306a36Sopenharmony_ci vibra_disable(info); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic int twl4030_vibra_resume(struct device *dev) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci vibra_disable_leds(); 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic DEFINE_SIMPLE_DEV_PM_OPS(twl4030_vibra_pm_ops, 16462306a36Sopenharmony_ci twl4030_vibra_suspend, twl4030_vibra_resume); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic bool twl4030_vibra_check_coexist(struct device_node *parent) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct device_node *node; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci node = of_get_child_by_name(parent, "codec"); 17162306a36Sopenharmony_ci if (node) { 17262306a36Sopenharmony_ci of_node_put(node); 17362306a36Sopenharmony_ci return true; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return false; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic int twl4030_vibra_probe(struct platform_device *pdev) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci struct device_node *twl4030_core_node = pdev->dev.parent->of_node; 18262306a36Sopenharmony_ci struct vibra_info *info; 18362306a36Sopenharmony_ci int ret; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci if (!twl4030_core_node) { 18662306a36Sopenharmony_ci dev_dbg(&pdev->dev, "twl4030 OF node is missing\n"); 18762306a36Sopenharmony_ci return -EINVAL; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 19162306a36Sopenharmony_ci if (!info) 19262306a36Sopenharmony_ci return -ENOMEM; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci info->dev = &pdev->dev; 19562306a36Sopenharmony_ci info->coexist = twl4030_vibra_check_coexist(twl4030_core_node); 19662306a36Sopenharmony_ci INIT_WORK(&info->play_work, vibra_play_work); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci info->input_dev = devm_input_allocate_device(&pdev->dev); 19962306a36Sopenharmony_ci if (info->input_dev == NULL) { 20062306a36Sopenharmony_ci dev_err(&pdev->dev, "couldn't allocate input device\n"); 20162306a36Sopenharmony_ci return -ENOMEM; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci input_set_drvdata(info->input_dev, info); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci info->input_dev->name = "twl4030:vibrator"; 20762306a36Sopenharmony_ci info->input_dev->id.version = 1; 20862306a36Sopenharmony_ci info->input_dev->close = twl4030_vibra_close; 20962306a36Sopenharmony_ci __set_bit(FF_RUMBLE, info->input_dev->ffbit); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci ret = input_ff_create_memless(info->input_dev, NULL, vibra_play); 21262306a36Sopenharmony_ci if (ret < 0) { 21362306a36Sopenharmony_ci dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n"); 21462306a36Sopenharmony_ci return ret; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci ret = input_register_device(info->input_dev); 21862306a36Sopenharmony_ci if (ret < 0) { 21962306a36Sopenharmony_ci dev_dbg(&pdev->dev, "couldn't register input device\n"); 22062306a36Sopenharmony_ci goto err_iff; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci vibra_disable_leds(); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci platform_set_drvdata(pdev, info); 22662306a36Sopenharmony_ci return 0; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cierr_iff: 22962306a36Sopenharmony_ci input_ff_destroy(info->input_dev); 23062306a36Sopenharmony_ci return ret; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic struct platform_driver twl4030_vibra_driver = { 23462306a36Sopenharmony_ci .probe = twl4030_vibra_probe, 23562306a36Sopenharmony_ci .driver = { 23662306a36Sopenharmony_ci .name = "twl4030-vibra", 23762306a36Sopenharmony_ci .pm = pm_sleep_ptr(&twl4030_vibra_pm_ops), 23862306a36Sopenharmony_ci }, 23962306a36Sopenharmony_ci}; 24062306a36Sopenharmony_cimodule_platform_driver(twl4030_vibra_driver); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ciMODULE_ALIAS("platform:twl4030-vibra"); 24362306a36Sopenharmony_ciMODULE_DESCRIPTION("TWL4030 Vibra driver"); 24462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 24562306a36Sopenharmony_ciMODULE_AUTHOR("Nokia Corporation"); 246