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(dapm: 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(map: arizona->regmap, |
48 | ARIZONA_HAPTICS_PHASE_2_INTENSITY, |
49 | ARIZONA_PHASE2_INTENSITY_MASK, |
50 | val: 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(map: arizona->regmap, |
59 | ARIZONA_HAPTICS_CONTROL_1, |
60 | ARIZONA_HAP_CTRL_MASK, |
61 | val: 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, pin: "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(dapm: 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, pin: "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(dapm: 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(map: arizona->regmap, |
98 | ARIZONA_HAPTICS_CONTROL_1, |
99 | ARIZONA_HAP_CTRL_MASK, val: 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(dev: 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(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(dev: input); |
142 | struct snd_soc_component *component; |
143 | |
144 | cancel_work_sync(work: &haptics->work); |
145 | |
146 | if (haptics->arizona->dapm) { |
147 | component = snd_soc_dapm_to_component(dapm: haptics->arizona->dapm); |
148 | snd_soc_component_disable_pin(component, pin: "HAPTICS" ); |
149 | } |
150 | } |
151 | |
152 | static int arizona_haptics_probe(struct platform_device *pdev) |
153 | { |
154 | struct arizona *arizona = dev_get_drvdata(dev: pdev->dev.parent); |
155 | struct arizona_haptics *haptics; |
156 | int ret; |
157 | |
158 | haptics = devm_kzalloc(dev: &pdev->dev, size: sizeof(*haptics), GFP_KERNEL); |
159 | if (!haptics) |
160 | return -ENOMEM; |
161 | |
162 | haptics->arizona = arizona; |
163 | |
164 | ret = regmap_update_bits(map: arizona->regmap, ARIZONA_HAPTICS_CONTROL_1, |
165 | ARIZONA_HAP_ACT, val: 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(dev: haptics->input_dev, data: 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(dev: haptics->input_dev, NULL, |
187 | play_effect: 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>" ); |
216 | |