| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Arizona haptics driver |
| * |
| * Copyright 2012 Wolfson Microelectronics plc |
| * |
| * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/input.h> |
| #include <linux/slab.h> |
| |
| #include <sound/soc.h> |
| #include <sound/soc-dapm.h> |
| |
| #include <linux/mfd/arizona/core.h> |
| #include <linux/mfd/arizona/pdata.h> |
| #include <linux/mfd/arizona/registers.h> |
| |
| struct arizona_haptics { |
| struct arizona *arizona; |
| struct input_dev *input_dev; |
| struct work_struct work; |
| |
| struct mutex mutex; |
| u8 intensity; |
| }; |
| |
| static void arizona_haptics_work(struct work_struct *work) |
| { |
| struct arizona_haptics *haptics = container_of(work, |
| struct arizona_haptics, |
| work); |
| struct arizona *arizona = haptics->arizona; |
| struct snd_soc_component *component = |
| snd_soc_dapm_to_component(arizona->dapm); |
| int ret; |
| |
| if (!haptics->arizona->dapm) { |
| dev_err(arizona->dev, "No DAPM context\n"); |
| return; |
| } |
| |
| if (haptics->intensity) { |
| ret = regmap_update_bits(arizona->regmap, |
| ARIZONA_HAPTICS_PHASE_2_INTENSITY, |
| ARIZONA_PHASE2_INTENSITY_MASK, |
| haptics->intensity); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to set intensity: %d\n", |
| ret); |
| return; |
| } |
| |
| /* This enable sequence will be a noop if already enabled */ |
| ret = regmap_update_bits(arizona->regmap, |
| ARIZONA_HAPTICS_CONTROL_1, |
| ARIZONA_HAP_CTRL_MASK, |
| 1 << ARIZONA_HAP_CTRL_SHIFT); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to start haptics: %d\n", |
| ret); |
| return; |
| } |
| |
| ret = snd_soc_component_enable_pin(component, "HAPTICS"); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to start HAPTICS: %d\n", |
| ret); |
| return; |
| } |
| |
| ret = snd_soc_dapm_sync(arizona->dapm); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to sync DAPM: %d\n", |
| ret); |
| return; |
| } |
| } else { |
| /* This disable sequence will be a noop if already enabled */ |
| ret = snd_soc_component_disable_pin(component, "HAPTICS"); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n", |
| ret); |
| return; |
| } |
| |
| ret = snd_soc_dapm_sync(arizona->dapm); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to sync DAPM: %d\n", |
| ret); |
| return; |
| } |
| |
| ret = regmap_update_bits(arizona->regmap, |
| ARIZONA_HAPTICS_CONTROL_1, |
| ARIZONA_HAP_CTRL_MASK, 0); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to stop haptics: %d\n", |
| ret); |
| return; |
| } |
| } |
| } |
| |
| static int arizona_haptics_play(struct input_dev *input, void *data, |
| struct ff_effect *effect) |
| { |
| struct arizona_haptics *haptics = input_get_drvdata(input); |
| struct arizona *arizona = haptics->arizona; |
| |
| if (!arizona->dapm) { |
| dev_err(arizona->dev, "No DAPM context\n"); |
| return -EBUSY; |
| } |
| |
| if (effect->u.rumble.strong_magnitude) { |
| /* Scale the magnitude into the range the device supports */ |
| if (arizona->pdata.hap_act) { |
| haptics->intensity = |
| effect->u.rumble.strong_magnitude >> 9; |
| if (effect->direction < 0x8000) |
| haptics->intensity += 0x7f; |
| } else { |
| haptics->intensity = |
| effect->u.rumble.strong_magnitude >> 8; |
| } |
| } else { |
| haptics->intensity = 0; |
| } |
| |
| schedule_work(&haptics->work); |
| |
| return 0; |
| } |
| |
| static void arizona_haptics_close(struct input_dev *input) |
| { |
| struct arizona_haptics *haptics = input_get_drvdata(input); |
| struct snd_soc_component *component; |
| |
| cancel_work_sync(&haptics->work); |
| |
| if (haptics->arizona->dapm) { |
| component = snd_soc_dapm_to_component(haptics->arizona->dapm); |
| snd_soc_component_disable_pin(component, "HAPTICS"); |
| } |
| } |
| |
| static int arizona_haptics_probe(struct platform_device *pdev) |
| { |
| struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); |
| struct arizona_haptics *haptics; |
| int ret; |
| |
| haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL); |
| if (!haptics) |
| return -ENOMEM; |
| |
| haptics->arizona = arizona; |
| |
| ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1, |
| ARIZONA_HAP_ACT, arizona->pdata.hap_act); |
| if (ret != 0) { |
| dev_err(arizona->dev, "Failed to set haptics actuator: %d\n", |
| ret); |
| return ret; |
| } |
| |
| INIT_WORK(&haptics->work, arizona_haptics_work); |
| |
| haptics->input_dev = devm_input_allocate_device(&pdev->dev); |
| if (!haptics->input_dev) { |
| dev_err(arizona->dev, "Failed to allocate input device\n"); |
| return -ENOMEM; |
| } |
| |
| input_set_drvdata(haptics->input_dev, haptics); |
| |
| haptics->input_dev->name = "arizona:haptics"; |
| haptics->input_dev->close = arizona_haptics_close; |
| __set_bit(FF_RUMBLE, haptics->input_dev->ffbit); |
| |
| ret = input_ff_create_memless(haptics->input_dev, NULL, |
| arizona_haptics_play); |
| if (ret < 0) { |
| dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n", |
| ret); |
| return ret; |
| } |
| |
| ret = input_register_device(haptics->input_dev); |
| if (ret < 0) { |
| dev_err(arizona->dev, "couldn't register input device: %d\n", |
| ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static struct platform_driver arizona_haptics_driver = { |
| .probe = arizona_haptics_probe, |
| .driver = { |
| .name = "arizona-haptics", |
| }, |
| }; |
| module_platform_driver(arizona_haptics_driver); |
| |
| MODULE_ALIAS("platform:arizona-haptics"); |
| MODULE_DESCRIPTION("Arizona haptics driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); |