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