]> Git Repo - linux.git/blob - drivers/input/misc/pwm-beeper.c
Input: pwm-beeper - fix race when suspending
[linux.git] / drivers / input / misc / pwm-beeper.c
1 /*
2  *  Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
3  *  PWM beeper driver
4  *
5  *  This program is free software; you can redistribute it and/or modify it
6  *  under  the terms of the GNU General  Public License as published by the
7  *  Free Software Foundation;  either version 2 of the License, or (at your
8  *  option) any later version.
9  *
10  *  You should have received a copy of the GNU General Public License along
11  *  with this program; if not, write to the Free Software Foundation, Inc.,
12  *  675 Mass Ave, Cambridge, MA 02139, USA.
13  *
14  */
15
16 #include <linux/input.h>
17 #include <linux/module.h>
18 #include <linux/kernel.h>
19 #include <linux/of.h>
20 #include <linux/platform_device.h>
21 #include <linux/pwm.h>
22 #include <linux/slab.h>
23 #include <linux/workqueue.h>
24
25 struct pwm_beeper {
26         struct input_dev *input;
27         struct pwm_device *pwm;
28         struct work_struct work;
29         unsigned long period;
30         bool suspended;
31 };
32
33 #define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
34
35 static void __pwm_beeper_set(struct pwm_beeper *beeper)
36 {
37         unsigned long period = beeper->period;
38
39         if (period) {
40                 pwm_config(beeper->pwm, period / 2, period);
41                 pwm_enable(beeper->pwm);
42         } else
43                 pwm_disable(beeper->pwm);
44 }
45
46 static void pwm_beeper_work(struct work_struct *work)
47 {
48         struct pwm_beeper *beeper =
49                 container_of(work, struct pwm_beeper, work);
50
51         __pwm_beeper_set(beeper);
52 }
53
54 static int pwm_beeper_event(struct input_dev *input,
55                             unsigned int type, unsigned int code, int value)
56 {
57         struct pwm_beeper *beeper = input_get_drvdata(input);
58
59         if (type != EV_SND || value < 0)
60                 return -EINVAL;
61
62         switch (code) {
63         case SND_BELL:
64                 value = value ? 1000 : 0;
65                 break;
66         case SND_TONE:
67                 break;
68         default:
69                 return -EINVAL;
70         }
71
72         if (value == 0)
73                 beeper->period = 0;
74         else
75                 beeper->period = HZ_TO_NANOSECONDS(value);
76
77         if (!beeper->suspended)
78                 schedule_work(&beeper->work);
79
80         return 0;
81 }
82
83 static void pwm_beeper_stop(struct pwm_beeper *beeper)
84 {
85         cancel_work_sync(&beeper->work);
86
87         if (beeper->period)
88                 pwm_disable(beeper->pwm);
89 }
90
91 static void pwm_beeper_close(struct input_dev *input)
92 {
93         struct pwm_beeper *beeper = input_get_drvdata(input);
94
95         pwm_beeper_stop(beeper);
96 }
97
98 static int pwm_beeper_probe(struct platform_device *pdev)
99 {
100         struct device *dev = &pdev->dev;
101         struct pwm_beeper *beeper;
102         int error;
103
104         beeper = devm_kzalloc(dev, sizeof(*beeper), GFP_KERNEL);
105         if (!beeper)
106                 return -ENOMEM;
107
108         beeper->pwm = devm_pwm_get(dev, NULL);
109         if (IS_ERR(beeper->pwm)) {
110                 error = PTR_ERR(beeper->pwm);
111                 dev_err(dev, "Failed to request PWM device: %d\n", error);
112                 return error;
113         }
114
115         /*
116          * FIXME: pwm_apply_args() should be removed when switching to
117          * the atomic PWM API.
118          */
119         pwm_apply_args(beeper->pwm);
120
121         INIT_WORK(&beeper->work, pwm_beeper_work);
122
123         beeper->input = devm_input_allocate_device(dev);
124         if (!beeper->input) {
125                 dev_err(dev, "Failed to allocate input device\n");
126                 return -ENOMEM;
127         }
128
129         beeper->input->name = "pwm-beeper";
130         beeper->input->phys = "pwm/input0";
131         beeper->input->id.bustype = BUS_HOST;
132         beeper->input->id.vendor = 0x001f;
133         beeper->input->id.product = 0x0001;
134         beeper->input->id.version = 0x0100;
135
136         input_set_capability(beeper->input, EV_SND, SND_TONE);
137         input_set_capability(beeper->input, EV_SND, SND_BELL);
138
139         beeper->input->event = pwm_beeper_event;
140         beeper->input->close = pwm_beeper_close;
141
142         input_set_drvdata(beeper->input, beeper);
143
144         error = input_register_device(beeper->input);
145         if (error) {
146                 dev_err(dev, "Failed to register input device: %d\n", error);
147                 return error;
148         }
149
150         platform_set_drvdata(pdev, beeper);
151
152         return 0;
153 }
154
155 static int __maybe_unused pwm_beeper_suspend(struct device *dev)
156 {
157         struct pwm_beeper *beeper = dev_get_drvdata(dev);
158
159         /*
160          * Spinlock is taken here is not to protect write to
161          * beeper->suspended, but to ensure that pwm_beeper_event
162          * does not re-submit work once flag is set.
163          */
164         spin_lock_irq(&beeper->input->event_lock);
165         beeper->suspended = true;
166         spin_unlock_irq(&beeper->input->event_lock);
167
168         pwm_beeper_stop(beeper);
169
170         return 0;
171 }
172
173 static int __maybe_unused pwm_beeper_resume(struct device *dev)
174 {
175         struct pwm_beeper *beeper = dev_get_drvdata(dev);
176
177         spin_lock_irq(&beeper->input->event_lock);
178         beeper->suspended = false;
179         spin_unlock_irq(&beeper->input->event_lock);
180
181         /* Let worker figure out if we should resume beeping */
182         schedule_work(&beeper->work);
183
184         return 0;
185 }
186
187 static SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops,
188                          pwm_beeper_suspend, pwm_beeper_resume);
189
190 #ifdef CONFIG_OF
191 static const struct of_device_id pwm_beeper_match[] = {
192         { .compatible = "pwm-beeper", },
193         { },
194 };
195 MODULE_DEVICE_TABLE(of, pwm_beeper_match);
196 #endif
197
198 static struct platform_driver pwm_beeper_driver = {
199         .probe  = pwm_beeper_probe,
200         .driver = {
201                 .name   = "pwm-beeper",
202                 .pm     = &pwm_beeper_pm_ops,
203                 .of_match_table = of_match_ptr(pwm_beeper_match),
204         },
205 };
206 module_platform_driver(pwm_beeper_driver);
207
208 MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
209 MODULE_DESCRIPTION("PWM beeper driver");
210 MODULE_LICENSE("GPL");
211 MODULE_ALIAS("platform:pwm-beeper");
This page took 0.045665 seconds and 4 git commands to generate.