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