1a912e80bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2e22739d0SLars-Peter Clausen /*
3e22739d0SLars-Peter Clausen * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
4e22739d0SLars-Peter Clausen * PWM beeper driver
5e22739d0SLars-Peter Clausen */
6e22739d0SLars-Peter Clausen
7e22739d0SLars-Peter Clausen #include <linux/input.h>
89e549244SDavid Lechner #include <linux/regulator/consumer.h>
9e22739d0SLars-Peter Clausen #include <linux/module.h>
10e22739d0SLars-Peter Clausen #include <linux/kernel.h>
119dbe4c32SSachin Kamat #include <linux/of.h>
12e22739d0SLars-Peter Clausen #include <linux/platform_device.h>
13fad358a0SGuan Ben #include <linux/property.h>
14e22739d0SLars-Peter Clausen #include <linux/pwm.h>
15e22739d0SLars-Peter Clausen #include <linux/slab.h>
16f49cf3b8SManfred Schlaegl #include <linux/workqueue.h>
17e22739d0SLars-Peter Clausen
18e22739d0SLars-Peter Clausen struct pwm_beeper {
19e22739d0SLars-Peter Clausen struct input_dev *input;
20e22739d0SLars-Peter Clausen struct pwm_device *pwm;
219e549244SDavid Lechner struct regulator *amplifier;
22f49cf3b8SManfred Schlaegl struct work_struct work;
23e22739d0SLars-Peter Clausen unsigned long period;
24fad358a0SGuan Ben unsigned int bell_frequency;
25e9728f0dSDmitry Torokhov bool suspended;
269e549244SDavid Lechner bool amplifier_on;
27e22739d0SLars-Peter Clausen };
28e22739d0SLars-Peter Clausen
29e22739d0SLars-Peter Clausen #define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
30e22739d0SLars-Peter Clausen
pwm_beeper_on(struct pwm_beeper * beeper,unsigned long period)319e549244SDavid Lechner static int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period)
32f49cf3b8SManfred Schlaegl {
332de8b411SDmitry Torokhov struct pwm_state state;
349e549244SDavid Lechner int error;
35f49cf3b8SManfred Schlaegl
362de8b411SDmitry Torokhov pwm_get_state(beeper->pwm, &state);
379e549244SDavid Lechner
382de8b411SDmitry Torokhov state.enabled = true;
392de8b411SDmitry Torokhov state.period = period;
402de8b411SDmitry Torokhov pwm_set_relative_duty_cycle(&state, 50, 100);
412de8b411SDmitry Torokhov
422de8b411SDmitry Torokhov error = pwm_apply_state(beeper->pwm, &state);
439e549244SDavid Lechner if (error)
449e549244SDavid Lechner return error;
459e549244SDavid Lechner
469e549244SDavid Lechner if (!beeper->amplifier_on) {
479e549244SDavid Lechner error = regulator_enable(beeper->amplifier);
489e549244SDavid Lechner if (error) {
499e549244SDavid Lechner pwm_disable(beeper->pwm);
509e549244SDavid Lechner return error;
519e549244SDavid Lechner }
529e549244SDavid Lechner
539e549244SDavid Lechner beeper->amplifier_on = true;
549e549244SDavid Lechner }
559e549244SDavid Lechner
569e549244SDavid Lechner return 0;
579e549244SDavid Lechner }
589e549244SDavid Lechner
pwm_beeper_off(struct pwm_beeper * beeper)599e549244SDavid Lechner static void pwm_beeper_off(struct pwm_beeper *beeper)
609e549244SDavid Lechner {
619e549244SDavid Lechner if (beeper->amplifier_on) {
629e549244SDavid Lechner regulator_disable(beeper->amplifier);
639e549244SDavid Lechner beeper->amplifier_on = false;
649e549244SDavid Lechner }
659e549244SDavid Lechner
66f49cf3b8SManfred Schlaegl pwm_disable(beeper->pwm);
67f49cf3b8SManfred Schlaegl }
68f49cf3b8SManfred Schlaegl
pwm_beeper_work(struct work_struct * work)69f49cf3b8SManfred Schlaegl static void pwm_beeper_work(struct work_struct *work)
70f49cf3b8SManfred Schlaegl {
719e549244SDavid Lechner struct pwm_beeper *beeper = container_of(work, struct pwm_beeper, work);
729e549244SDavid Lechner unsigned long period = READ_ONCE(beeper->period);
73f49cf3b8SManfred Schlaegl
749e549244SDavid Lechner if (period)
759e549244SDavid Lechner pwm_beeper_on(beeper, period);
769e549244SDavid Lechner else
779e549244SDavid Lechner pwm_beeper_off(beeper);
78f49cf3b8SManfred Schlaegl }
79f49cf3b8SManfred Schlaegl
pwm_beeper_event(struct input_dev * input,unsigned int type,unsigned int code,int value)80e22739d0SLars-Peter Clausen static int pwm_beeper_event(struct input_dev *input,
81e22739d0SLars-Peter Clausen unsigned int type, unsigned int code, int value)
82e22739d0SLars-Peter Clausen {
83e22739d0SLars-Peter Clausen struct pwm_beeper *beeper = input_get_drvdata(input);
84e22739d0SLars-Peter Clausen
85e22739d0SLars-Peter Clausen if (type != EV_SND || value < 0)
86e22739d0SLars-Peter Clausen return -EINVAL;
87e22739d0SLars-Peter Clausen
88e22739d0SLars-Peter Clausen switch (code) {
89e22739d0SLars-Peter Clausen case SND_BELL:
90fad358a0SGuan Ben value = value ? beeper->bell_frequency : 0;
91e22739d0SLars-Peter Clausen break;
92e22739d0SLars-Peter Clausen case SND_TONE:
93e22739d0SLars-Peter Clausen break;
94e22739d0SLars-Peter Clausen default:
95e22739d0SLars-Peter Clausen return -EINVAL;
96e22739d0SLars-Peter Clausen }
97e22739d0SLars-Peter Clausen
98f49cf3b8SManfred Schlaegl if (value == 0)
99f49cf3b8SManfred Schlaegl beeper->period = 0;
100f49cf3b8SManfred Schlaegl else
101f49cf3b8SManfred Schlaegl beeper->period = HZ_TO_NANOSECONDS(value);
102f49cf3b8SManfred Schlaegl
103e9728f0dSDmitry Torokhov if (!beeper->suspended)
104f49cf3b8SManfred Schlaegl schedule_work(&beeper->work);
105e22739d0SLars-Peter Clausen
106e22739d0SLars-Peter Clausen return 0;
107e22739d0SLars-Peter Clausen }
108e22739d0SLars-Peter Clausen
pwm_beeper_stop(struct pwm_beeper * beeper)109f49cf3b8SManfred Schlaegl static void pwm_beeper_stop(struct pwm_beeper *beeper)
110f49cf3b8SManfred Schlaegl {
111f49cf3b8SManfred Schlaegl cancel_work_sync(&beeper->work);
1129e549244SDavid Lechner pwm_beeper_off(beeper);
113f49cf3b8SManfred Schlaegl }
114f49cf3b8SManfred Schlaegl
pwm_beeper_close(struct input_dev * input)115f49cf3b8SManfred Schlaegl static void pwm_beeper_close(struct input_dev *input)
116f49cf3b8SManfred Schlaegl {
117f49cf3b8SManfred Schlaegl struct pwm_beeper *beeper = input_get_drvdata(input);
118f49cf3b8SManfred Schlaegl
119f49cf3b8SManfred Schlaegl pwm_beeper_stop(beeper);
120f49cf3b8SManfred Schlaegl }
121f49cf3b8SManfred Schlaegl
pwm_beeper_probe(struct platform_device * pdev)1225298cc4cSBill Pemberton static int pwm_beeper_probe(struct platform_device *pdev)
123e22739d0SLars-Peter Clausen {
124bcf4b046SDmitry Torokhov struct device *dev = &pdev->dev;
125e22739d0SLars-Peter Clausen struct pwm_beeper *beeper;
1262de8b411SDmitry Torokhov struct pwm_state state;
127fad358a0SGuan Ben u32 bell_frequency;
128e22739d0SLars-Peter Clausen int error;
129e22739d0SLars-Peter Clausen
130bcf4b046SDmitry Torokhov beeper = devm_kzalloc(dev, sizeof(*beeper), GFP_KERNEL);
131e22739d0SLars-Peter Clausen if (!beeper)
132e22739d0SLars-Peter Clausen return -ENOMEM;
133e22739d0SLars-Peter Clausen
134bcf4b046SDmitry Torokhov beeper->pwm = devm_pwm_get(dev, NULL);
135*c4834f4aSKrzysztof Kozlowski if (IS_ERR(beeper->pwm))
136*c4834f4aSKrzysztof Kozlowski return dev_err_probe(dev, PTR_ERR(beeper->pwm), "Failed to request PWM device\n");
137e22739d0SLars-Peter Clausen
1382de8b411SDmitry Torokhov /* Sync up PWM state and ensure it is off. */
1392de8b411SDmitry Torokhov pwm_init_state(beeper->pwm, &state);
1402de8b411SDmitry Torokhov state.enabled = false;
1412de8b411SDmitry Torokhov error = pwm_apply_state(beeper->pwm, &state);
1422de8b411SDmitry Torokhov if (error) {
1432de8b411SDmitry Torokhov dev_err(dev, "failed to apply initial PWM state: %d\n",
1442de8b411SDmitry Torokhov error);
1452de8b411SDmitry Torokhov return error;
1462de8b411SDmitry Torokhov }
147cfae56f1SBoris Brezillon
1489e549244SDavid Lechner beeper->amplifier = devm_regulator_get(dev, "amp");
149*c4834f4aSKrzysztof Kozlowski if (IS_ERR(beeper->amplifier))
150*c4834f4aSKrzysztof Kozlowski return dev_err_probe(dev, PTR_ERR(beeper->amplifier),
151*c4834f4aSKrzysztof Kozlowski "Failed to get 'amp' regulator\n");
1529e549244SDavid Lechner
153f49cf3b8SManfred Schlaegl INIT_WORK(&beeper->work, pwm_beeper_work);
154f49cf3b8SManfred Schlaegl
155fad358a0SGuan Ben error = device_property_read_u32(dev, "beeper-hz", &bell_frequency);
156fad358a0SGuan Ben if (error) {
157fad358a0SGuan Ben bell_frequency = 1000;
158fad358a0SGuan Ben dev_dbg(dev,
159fad358a0SGuan Ben "failed to parse 'beeper-hz' property, using default: %uHz\n",
160fad358a0SGuan Ben bell_frequency);
161fad358a0SGuan Ben }
162fad358a0SGuan Ben
163fad358a0SGuan Ben beeper->bell_frequency = bell_frequency;
164fad358a0SGuan Ben
165bcf4b046SDmitry Torokhov beeper->input = devm_input_allocate_device(dev);
166e22739d0SLars-Peter Clausen if (!beeper->input) {
167bcf4b046SDmitry Torokhov dev_err(dev, "Failed to allocate input device\n");
168bcf4b046SDmitry Torokhov return -ENOMEM;
169e22739d0SLars-Peter Clausen }
170e22739d0SLars-Peter Clausen
171e22739d0SLars-Peter Clausen beeper->input->name = "pwm-beeper";
172e22739d0SLars-Peter Clausen beeper->input->phys = "pwm/input0";
173e22739d0SLars-Peter Clausen beeper->input->id.bustype = BUS_HOST;
174e22739d0SLars-Peter Clausen beeper->input->id.vendor = 0x001f;
175e22739d0SLars-Peter Clausen beeper->input->id.product = 0x0001;
176e22739d0SLars-Peter Clausen beeper->input->id.version = 0x0100;
177e22739d0SLars-Peter Clausen
17848a55d7dSDmitry Torokhov input_set_capability(beeper->input, EV_SND, SND_TONE);
17948a55d7dSDmitry Torokhov input_set_capability(beeper->input, EV_SND, SND_BELL);
180e22739d0SLars-Peter Clausen
181e22739d0SLars-Peter Clausen beeper->input->event = pwm_beeper_event;
182f49cf3b8SManfred Schlaegl beeper->input->close = pwm_beeper_close;
183e22739d0SLars-Peter Clausen
184e22739d0SLars-Peter Clausen input_set_drvdata(beeper->input, beeper);
185e22739d0SLars-Peter Clausen
186e22739d0SLars-Peter Clausen error = input_register_device(beeper->input);
187e22739d0SLars-Peter Clausen if (error) {
188bcf4b046SDmitry Torokhov dev_err(dev, "Failed to register input device: %d\n", error);
189e22739d0SLars-Peter Clausen return error;
190e22739d0SLars-Peter Clausen }
191e22739d0SLars-Peter Clausen
192bcf4b046SDmitry Torokhov platform_set_drvdata(pdev, beeper);
193e22739d0SLars-Peter Clausen
194e22739d0SLars-Peter Clausen return 0;
195e22739d0SLars-Peter Clausen }
196e22739d0SLars-Peter Clausen
pwm_beeper_suspend(struct device * dev)197349fe1e4SJonathan Cameron static int pwm_beeper_suspend(struct device *dev)
198e22739d0SLars-Peter Clausen {
199e22739d0SLars-Peter Clausen struct pwm_beeper *beeper = dev_get_drvdata(dev);
200e22739d0SLars-Peter Clausen
201e9728f0dSDmitry Torokhov /*
202e9728f0dSDmitry Torokhov * Spinlock is taken here is not to protect write to
203e9728f0dSDmitry Torokhov * beeper->suspended, but to ensure that pwm_beeper_event
204e9728f0dSDmitry Torokhov * does not re-submit work once flag is set.
205e9728f0dSDmitry Torokhov */
206e9728f0dSDmitry Torokhov spin_lock_irq(&beeper->input->event_lock);
207e9728f0dSDmitry Torokhov beeper->suspended = true;
208e9728f0dSDmitry Torokhov spin_unlock_irq(&beeper->input->event_lock);
209e9728f0dSDmitry Torokhov
210f49cf3b8SManfred Schlaegl pwm_beeper_stop(beeper);
211e22739d0SLars-Peter Clausen
212e22739d0SLars-Peter Clausen return 0;
213e22739d0SLars-Peter Clausen }
214e22739d0SLars-Peter Clausen
pwm_beeper_resume(struct device * dev)215349fe1e4SJonathan Cameron static int pwm_beeper_resume(struct device *dev)
216e22739d0SLars-Peter Clausen {
217e22739d0SLars-Peter Clausen struct pwm_beeper *beeper = dev_get_drvdata(dev);
218e22739d0SLars-Peter Clausen
219e9728f0dSDmitry Torokhov spin_lock_irq(&beeper->input->event_lock);
220e9728f0dSDmitry Torokhov beeper->suspended = false;
221e9728f0dSDmitry Torokhov spin_unlock_irq(&beeper->input->event_lock);
222e9728f0dSDmitry Torokhov
223e9728f0dSDmitry Torokhov /* Let worker figure out if we should resume beeping */
224e9728f0dSDmitry Torokhov schedule_work(&beeper->work);
225e22739d0SLars-Peter Clausen
226e22739d0SLars-Peter Clausen return 0;
227e22739d0SLars-Peter Clausen }
228e22739d0SLars-Peter Clausen
229349fe1e4SJonathan Cameron static DEFINE_SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops,
230e22739d0SLars-Peter Clausen pwm_beeper_suspend, pwm_beeper_resume);
231e22739d0SLars-Peter Clausen
232d56a289bSSascha Hauer #ifdef CONFIG_OF
233d56a289bSSascha Hauer static const struct of_device_id pwm_beeper_match[] = {
234d56a289bSSascha Hauer { .compatible = "pwm-beeper", },
235d56a289bSSascha Hauer { },
236d56a289bSSascha Hauer };
237544edf56SLuis de Bethencourt MODULE_DEVICE_TABLE(of, pwm_beeper_match);
238d56a289bSSascha Hauer #endif
239d56a289bSSascha Hauer
240e22739d0SLars-Peter Clausen static struct platform_driver pwm_beeper_driver = {
241e22739d0SLars-Peter Clausen .probe = pwm_beeper_probe,
242e22739d0SLars-Peter Clausen .driver = {
243e22739d0SLars-Peter Clausen .name = "pwm-beeper",
244349fe1e4SJonathan Cameron .pm = pm_sleep_ptr(&pwm_beeper_pm_ops),
245d56a289bSSascha Hauer .of_match_table = of_match_ptr(pwm_beeper_match),
246e22739d0SLars-Peter Clausen },
247e22739d0SLars-Peter Clausen };
248840a746bSJJ Ding module_platform_driver(pwm_beeper_driver);
249e22739d0SLars-Peter Clausen
250e22739d0SLars-Peter Clausen MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
251e22739d0SLars-Peter Clausen MODULE_DESCRIPTION("PWM beeper driver");
252e22739d0SLars-Peter Clausen MODULE_LICENSE("GPL");
253e22739d0SLars-Peter Clausen MODULE_ALIAS("platform:pwm-beeper");
254