forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
5.2 KiB
215 lines
5.2 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Arizona haptics driver |
|
* |
|
* Copyright 2012 Wolfson Microelectronics plc |
|
* |
|
* Author: Mark Brown <[email protected]> |
|
*/ |
|
|
|
#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 <[email protected]>");
|
|
|