1 /* 2 * Arizona haptics driver 3 * 4 * Copyright 2012 Wolfson Microelectronics plc 5 * 6 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 */ 12 13 #include <linux/module.h> 14 #include <linux/platform_device.h> 15 #include <linux/input.h> 16 #include <linux/slab.h> 17 18 #include <sound/soc.h> 19 #include <sound/soc-dapm.h> 20 21 #include <linux/mfd/arizona/core.h> 22 #include <linux/mfd/arizona/pdata.h> 23 #include <linux/mfd/arizona/registers.h> 24 25 struct arizona_haptics { 26 struct arizona *arizona; 27 struct input_dev *input_dev; 28 struct work_struct work; 29 30 struct mutex mutex; 31 u8 intensity; 32 }; 33 34 static void arizona_haptics_work(struct work_struct *work) 35 { 36 struct arizona_haptics *haptics = container_of(work, 37 struct arizona_haptics, 38 work); 39 struct arizona *arizona = haptics->arizona; 40 int ret; 41 42 if (!haptics->arizona->dapm) { 43 dev_err(arizona->dev, "No DAPM context\n"); 44 return; 45 } 46 47 if (haptics->intensity) { 48 ret = regmap_update_bits(arizona->regmap, 49 ARIZONA_HAPTICS_PHASE_2_INTENSITY, 50 ARIZONA_PHASE2_INTENSITY_MASK, 51 haptics->intensity); 52 if (ret != 0) { 53 dev_err(arizona->dev, "Failed to set intensity: %d\n", 54 ret); 55 return; 56 } 57 58 /* This enable sequence will be a noop if already enabled */ 59 ret = regmap_update_bits(arizona->regmap, 60 ARIZONA_HAPTICS_CONTROL_1, 61 ARIZONA_HAP_CTRL_MASK, 62 1 << ARIZONA_HAP_CTRL_SHIFT); 63 if (ret != 0) { 64 dev_err(arizona->dev, "Failed to start haptics: %d\n", 65 ret); 66 return; 67 } 68 69 ret = snd_soc_dapm_enable_pin(arizona->dapm, "HAPTICS"); 70 if (ret != 0) { 71 dev_err(arizona->dev, "Failed to start HAPTICS: %d\n", 72 ret); 73 return; 74 } 75 76 ret = snd_soc_dapm_sync(arizona->dapm); 77 if (ret != 0) { 78 dev_err(arizona->dev, "Failed to sync DAPM: %d\n", 79 ret); 80 return; 81 } 82 } else { 83 /* This disable sequence will be a noop if already enabled */ 84 ret = snd_soc_dapm_disable_pin(arizona->dapm, "HAPTICS"); 85 if (ret != 0) { 86 dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n", 87 ret); 88 return; 89 } 90 91 ret = snd_soc_dapm_sync(arizona->dapm); 92 if (ret != 0) { 93 dev_err(arizona->dev, "Failed to sync DAPM: %d\n", 94 ret); 95 return; 96 } 97 98 ret = regmap_update_bits(arizona->regmap, 99 ARIZONA_HAPTICS_CONTROL_1, 100 ARIZONA_HAP_CTRL_MASK, 101 1 << ARIZONA_HAP_CTRL_SHIFT); 102 if (ret != 0) { 103 dev_err(arizona->dev, "Failed to stop haptics: %d\n", 104 ret); 105 return; 106 } 107 } 108 } 109 110 static int arizona_haptics_play(struct input_dev *input, void *data, 111 struct ff_effect *effect) 112 { 113 struct arizona_haptics *haptics = input_get_drvdata(input); 114 struct arizona *arizona = haptics->arizona; 115 116 if (!arizona->dapm) { 117 dev_err(arizona->dev, "No DAPM context\n"); 118 return -EBUSY; 119 } 120 121 if (effect->u.rumble.strong_magnitude) { 122 /* Scale the magnitude into the range the device supports */ 123 if (arizona->pdata.hap_act) { 124 haptics->intensity = 125 effect->u.rumble.strong_magnitude >> 9; 126 if (effect->direction < 0x8000) 127 haptics->intensity += 0x7f; 128 } else { 129 haptics->intensity = 130 effect->u.rumble.strong_magnitude >> 8; 131 } 132 } else { 133 haptics->intensity = 0; 134 } 135 136 schedule_work(&haptics->work); 137 138 return 0; 139 } 140 141 static void arizona_haptics_close(struct input_dev *input) 142 { 143 struct arizona_haptics *haptics = input_get_drvdata(input); 144 145 cancel_work_sync(&haptics->work); 146 147 if (haptics->arizona->dapm) 148 snd_soc_dapm_disable_pin(haptics->arizona->dapm, "HAPTICS"); 149 } 150 151 static int arizona_haptics_probe(struct platform_device *pdev) 152 { 153 struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); 154 struct arizona_haptics *haptics; 155 int ret; 156 157 haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL); 158 if (!haptics) 159 return -ENOMEM; 160 161 haptics->arizona = arizona; 162 163 ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1, 164 ARIZONA_HAP_ACT, arizona->pdata.hap_act); 165 if (ret != 0) { 166 dev_err(arizona->dev, "Failed to set haptics actuator: %d\n", 167 ret); 168 return ret; 169 } 170 171 INIT_WORK(&haptics->work, arizona_haptics_work); 172 173 haptics->input_dev = input_allocate_device(); 174 if (haptics->input_dev == NULL) { 175 dev_err(arizona->dev, "Failed to allocate input device\n"); 176 return -ENOMEM; 177 } 178 179 input_set_drvdata(haptics->input_dev, haptics); 180 181 haptics->input_dev->name = "arizona:haptics"; 182 haptics->input_dev->dev.parent = pdev->dev.parent; 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 goto err_ialloc; 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 goto err_iff; 199 } 200 201 platform_set_drvdata(pdev, haptics); 202 203 return 0; 204 205 err_iff: 206 if (haptics->input_dev) 207 input_ff_destroy(haptics->input_dev); 208 err_ialloc: 209 input_free_device(haptics->input_dev); 210 211 return ret; 212 } 213 214 static int arizona_haptics_remove(struct platform_device *pdev) 215 { 216 struct arizona_haptics *haptics = platform_get_drvdata(pdev); 217 218 input_unregister_device(haptics->input_dev); 219 220 return 0; 221 } 222 223 static struct platform_driver arizona_haptics_driver = { 224 .probe = arizona_haptics_probe, 225 .remove = arizona_haptics_remove, 226 .driver = { 227 .name = "arizona-haptics", 228 .owner = THIS_MODULE, 229 }, 230 }; 231 module_platform_driver(arizona_haptics_driver); 232 233 MODULE_ALIAS("platform:arizona-haptics"); 234 MODULE_DESCRIPTION("Arizona haptics driver"); 235 MODULE_LICENSE("GPL"); 236 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 237