xref: /openbmc/linux/drivers/input/misc/pwm-beeper.c (revision c4834f4a)
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