arizona-haptics.c (5283B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Arizona haptics driver 4 * 5 * Copyright 2012 Wolfson Microelectronics plc 6 * 7 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 8 */ 9 10#include <linux/module.h> 11#include <linux/platform_device.h> 12#include <linux/input.h> 13#include <linux/slab.h> 14 15#include <sound/soc.h> 16#include <sound/soc-dapm.h> 17 18#include <linux/mfd/arizona/core.h> 19#include <linux/mfd/arizona/pdata.h> 20#include <linux/mfd/arizona/registers.h> 21 22struct arizona_haptics { 23 struct arizona *arizona; 24 struct input_dev *input_dev; 25 struct work_struct work; 26 27 struct mutex mutex; 28 u8 intensity; 29}; 30 31static void arizona_haptics_work(struct work_struct *work) 32{ 33 struct arizona_haptics *haptics = container_of(work, 34 struct arizona_haptics, 35 work); 36 struct arizona *arizona = haptics->arizona; 37 struct snd_soc_component *component = 38 snd_soc_dapm_to_component(arizona->dapm); 39 int ret; 40 41 if (!haptics->arizona->dapm) { 42 dev_err(arizona->dev, "No DAPM context\n"); 43 return; 44 } 45 46 if (haptics->intensity) { 47 ret = regmap_update_bits(arizona->regmap, 48 ARIZONA_HAPTICS_PHASE_2_INTENSITY, 49 ARIZONA_PHASE2_INTENSITY_MASK, 50 haptics->intensity); 51 if (ret != 0) { 52 dev_err(arizona->dev, "Failed to set intensity: %d\n", 53 ret); 54 return; 55 } 56 57 /* This enable sequence will be a noop if already enabled */ 58 ret = regmap_update_bits(arizona->regmap, 59 ARIZONA_HAPTICS_CONTROL_1, 60 ARIZONA_HAP_CTRL_MASK, 61 1 << ARIZONA_HAP_CTRL_SHIFT); 62 if (ret != 0) { 63 dev_err(arizona->dev, "Failed to start haptics: %d\n", 64 ret); 65 return; 66 } 67 68 ret = snd_soc_component_enable_pin(component, "HAPTICS"); 69 if (ret != 0) { 70 dev_err(arizona->dev, "Failed to start HAPTICS: %d\n", 71 ret); 72 return; 73 } 74 75 ret = snd_soc_dapm_sync(arizona->dapm); 76 if (ret != 0) { 77 dev_err(arizona->dev, "Failed to sync DAPM: %d\n", 78 ret); 79 return; 80 } 81 } else { 82 /* This disable sequence will be a noop if already enabled */ 83 ret = snd_soc_component_disable_pin(component, "HAPTICS"); 84 if (ret != 0) { 85 dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n", 86 ret); 87 return; 88 } 89 90 ret = snd_soc_dapm_sync(arizona->dapm); 91 if (ret != 0) { 92 dev_err(arizona->dev, "Failed to sync DAPM: %d\n", 93 ret); 94 return; 95 } 96 97 ret = regmap_update_bits(arizona->regmap, 98 ARIZONA_HAPTICS_CONTROL_1, 99 ARIZONA_HAP_CTRL_MASK, 0); 100 if (ret != 0) { 101 dev_err(arizona->dev, "Failed to stop haptics: %d\n", 102 ret); 103 return; 104 } 105 } 106} 107 108static int arizona_haptics_play(struct input_dev *input, void *data, 109 struct ff_effect *effect) 110{ 111 struct arizona_haptics *haptics = input_get_drvdata(input); 112 struct arizona *arizona = haptics->arizona; 113 114 if (!arizona->dapm) { 115 dev_err(arizona->dev, "No DAPM context\n"); 116 return -EBUSY; 117 } 118 119 if (effect->u.rumble.strong_magnitude) { 120 /* Scale the magnitude into the range the device supports */ 121 if (arizona->pdata.hap_act) { 122 haptics->intensity = 123 effect->u.rumble.strong_magnitude >> 9; 124 if (effect->direction < 0x8000) 125 haptics->intensity += 0x7f; 126 } else { 127 haptics->intensity = 128 effect->u.rumble.strong_magnitude >> 8; 129 } 130 } else { 131 haptics->intensity = 0; 132 } 133 134 schedule_work(&haptics->work); 135 136 return 0; 137} 138 139static void arizona_haptics_close(struct input_dev *input) 140{ 141 struct arizona_haptics *haptics = input_get_drvdata(input); 142 struct snd_soc_component *component; 143 144 cancel_work_sync(&haptics->work); 145 146 if (haptics->arizona->dapm) { 147 component = snd_soc_dapm_to_component(haptics->arizona->dapm); 148 snd_soc_component_disable_pin(component, "HAPTICS"); 149 } 150} 151 152static int arizona_haptics_probe(struct platform_device *pdev) 153{ 154 struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); 155 struct arizona_haptics *haptics; 156 int ret; 157 158 haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL); 159 if (!haptics) 160 return -ENOMEM; 161 162 haptics->arizona = arizona; 163 164 ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1, 165 ARIZONA_HAP_ACT, arizona->pdata.hap_act); 166 if (ret != 0) { 167 dev_err(arizona->dev, "Failed to set haptics actuator: %d\n", 168 ret); 169 return ret; 170 } 171 172 INIT_WORK(&haptics->work, arizona_haptics_work); 173 174 haptics->input_dev = devm_input_allocate_device(&pdev->dev); 175 if (!haptics->input_dev) { 176 dev_err(arizona->dev, "Failed to allocate input device\n"); 177 return -ENOMEM; 178 } 179 180 input_set_drvdata(haptics->input_dev, haptics); 181 182 haptics->input_dev->name = "arizona:haptics"; 183 haptics->input_dev->close = arizona_haptics_close; 184 __set_bit(FF_RUMBLE, haptics->input_dev->ffbit); 185 186 ret = input_ff_create_memless(haptics->input_dev, NULL, 187 arizona_haptics_play); 188 if (ret < 0) { 189 dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n", 190 ret); 191 return ret; 192 } 193 194 ret = input_register_device(haptics->input_dev); 195 if (ret < 0) { 196 dev_err(arizona->dev, "couldn't register input device: %d\n", 197 ret); 198 return ret; 199 } 200 201 return 0; 202} 203 204static struct platform_driver arizona_haptics_driver = { 205 .probe = arizona_haptics_probe, 206 .driver = { 207 .name = "arizona-haptics", 208 }, 209}; 210module_platform_driver(arizona_haptics_driver); 211 212MODULE_ALIAS("platform:arizona-haptics"); 213MODULE_DESCRIPTION("Arizona haptics driver"); 214MODULE_LICENSE("GPL"); 215MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");