Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / drivers / input / misc / arizona-haptics.c
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
22 struct 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
31 static 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
108 static 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
139 static 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
152 static 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
204 static struct platform_driver arizona_haptics_driver = {
205         .probe          = arizona_haptics_probe,
206         .driver         = {
207                 .name   = "arizona-haptics",
208         },
209 };
210 module_platform_driver(arizona_haptics_driver);
211
212 MODULE_ALIAS("platform:arizona-haptics");
213 MODULE_DESCRIPTION("Arizona haptics driver");
214 MODULE_LICENSE("GPL");
215 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");