18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
48c2ecf20Sopenharmony_ci *  PWM beeper driver
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/input.h>
88c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/of.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/property.h>
148c2ecf20Sopenharmony_ci#include <linux/pwm.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistruct pwm_beeper {
198c2ecf20Sopenharmony_ci	struct input_dev *input;
208c2ecf20Sopenharmony_ci	struct pwm_device *pwm;
218c2ecf20Sopenharmony_ci	struct regulator *amplifier;
228c2ecf20Sopenharmony_ci	struct work_struct work;
238c2ecf20Sopenharmony_ci	unsigned long period;
248c2ecf20Sopenharmony_ci	unsigned int bell_frequency;
258c2ecf20Sopenharmony_ci	bool suspended;
268c2ecf20Sopenharmony_ci	bool amplifier_on;
278c2ecf20Sopenharmony_ci};
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	struct pwm_state state;
348c2ecf20Sopenharmony_ci	int error;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	pwm_get_state(beeper->pwm, &state);
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	state.enabled = true;
398c2ecf20Sopenharmony_ci	state.period = period;
408c2ecf20Sopenharmony_ci	pwm_set_relative_duty_cycle(&state, 50, 100);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	error = pwm_apply_state(beeper->pwm, &state);
438c2ecf20Sopenharmony_ci	if (error)
448c2ecf20Sopenharmony_ci		return error;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	if (!beeper->amplifier_on) {
478c2ecf20Sopenharmony_ci		error = regulator_enable(beeper->amplifier);
488c2ecf20Sopenharmony_ci		if (error) {
498c2ecf20Sopenharmony_ci			pwm_disable(beeper->pwm);
508c2ecf20Sopenharmony_ci			return error;
518c2ecf20Sopenharmony_ci		}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci		beeper->amplifier_on = true;
548c2ecf20Sopenharmony_ci	}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	return 0;
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic void pwm_beeper_off(struct pwm_beeper *beeper)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	if (beeper->amplifier_on) {
628c2ecf20Sopenharmony_ci		regulator_disable(beeper->amplifier);
638c2ecf20Sopenharmony_ci		beeper->amplifier_on = false;
648c2ecf20Sopenharmony_ci	}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	pwm_disable(beeper->pwm);
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic void pwm_beeper_work(struct work_struct *work)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct pwm_beeper *beeper = container_of(work, struct pwm_beeper, work);
728c2ecf20Sopenharmony_ci	unsigned long period = READ_ONCE(beeper->period);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	if (period)
758c2ecf20Sopenharmony_ci		pwm_beeper_on(beeper, period);
768c2ecf20Sopenharmony_ci	else
778c2ecf20Sopenharmony_ci		pwm_beeper_off(beeper);
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic int pwm_beeper_event(struct input_dev *input,
818c2ecf20Sopenharmony_ci			    unsigned int type, unsigned int code, int value)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct pwm_beeper *beeper = input_get_drvdata(input);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	if (type != EV_SND || value < 0)
868c2ecf20Sopenharmony_ci		return -EINVAL;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	switch (code) {
898c2ecf20Sopenharmony_ci	case SND_BELL:
908c2ecf20Sopenharmony_ci		value = value ? beeper->bell_frequency : 0;
918c2ecf20Sopenharmony_ci		break;
928c2ecf20Sopenharmony_ci	case SND_TONE:
938c2ecf20Sopenharmony_ci		break;
948c2ecf20Sopenharmony_ci	default:
958c2ecf20Sopenharmony_ci		return -EINVAL;
968c2ecf20Sopenharmony_ci	}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (value == 0)
998c2ecf20Sopenharmony_ci		beeper->period = 0;
1008c2ecf20Sopenharmony_ci	else
1018c2ecf20Sopenharmony_ci		beeper->period = HZ_TO_NANOSECONDS(value);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	if (!beeper->suspended)
1048c2ecf20Sopenharmony_ci		schedule_work(&beeper->work);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	return 0;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic void pwm_beeper_stop(struct pwm_beeper *beeper)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	cancel_work_sync(&beeper->work);
1128c2ecf20Sopenharmony_ci	pwm_beeper_off(beeper);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic void pwm_beeper_close(struct input_dev *input)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	struct pwm_beeper *beeper = input_get_drvdata(input);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	pwm_beeper_stop(beeper);
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic int pwm_beeper_probe(struct platform_device *pdev)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1258c2ecf20Sopenharmony_ci	struct pwm_beeper *beeper;
1268c2ecf20Sopenharmony_ci	struct pwm_state state;
1278c2ecf20Sopenharmony_ci	u32 bell_frequency;
1288c2ecf20Sopenharmony_ci	int error;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	beeper = devm_kzalloc(dev, sizeof(*beeper), GFP_KERNEL);
1318c2ecf20Sopenharmony_ci	if (!beeper)
1328c2ecf20Sopenharmony_ci		return -ENOMEM;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	beeper->pwm = devm_pwm_get(dev, NULL);
1358c2ecf20Sopenharmony_ci	if (IS_ERR(beeper->pwm)) {
1368c2ecf20Sopenharmony_ci		error = PTR_ERR(beeper->pwm);
1378c2ecf20Sopenharmony_ci		if (error != -EPROBE_DEFER)
1388c2ecf20Sopenharmony_ci			dev_err(dev, "Failed to request PWM device: %d\n",
1398c2ecf20Sopenharmony_ci				error);
1408c2ecf20Sopenharmony_ci		return error;
1418c2ecf20Sopenharmony_ci	}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	/* Sync up PWM state and ensure it is off. */
1448c2ecf20Sopenharmony_ci	pwm_init_state(beeper->pwm, &state);
1458c2ecf20Sopenharmony_ci	state.enabled = false;
1468c2ecf20Sopenharmony_ci	error = pwm_apply_state(beeper->pwm, &state);
1478c2ecf20Sopenharmony_ci	if (error) {
1488c2ecf20Sopenharmony_ci		dev_err(dev, "failed to apply initial PWM state: %d\n",
1498c2ecf20Sopenharmony_ci			error);
1508c2ecf20Sopenharmony_ci		return error;
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	beeper->amplifier = devm_regulator_get(dev, "amp");
1548c2ecf20Sopenharmony_ci	if (IS_ERR(beeper->amplifier)) {
1558c2ecf20Sopenharmony_ci		error = PTR_ERR(beeper->amplifier);
1568c2ecf20Sopenharmony_ci		if (error != -EPROBE_DEFER)
1578c2ecf20Sopenharmony_ci			dev_err(dev, "Failed to get 'amp' regulator: %d\n",
1588c2ecf20Sopenharmony_ci				error);
1598c2ecf20Sopenharmony_ci		return error;
1608c2ecf20Sopenharmony_ci	}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	INIT_WORK(&beeper->work, pwm_beeper_work);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	error = device_property_read_u32(dev, "beeper-hz", &bell_frequency);
1658c2ecf20Sopenharmony_ci	if (error) {
1668c2ecf20Sopenharmony_ci		bell_frequency = 1000;
1678c2ecf20Sopenharmony_ci		dev_dbg(dev,
1688c2ecf20Sopenharmony_ci			"failed to parse 'beeper-hz' property, using default: %uHz\n",
1698c2ecf20Sopenharmony_ci			bell_frequency);
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	beeper->bell_frequency = bell_frequency;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	beeper->input = devm_input_allocate_device(dev);
1758c2ecf20Sopenharmony_ci	if (!beeper->input) {
1768c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to allocate input device\n");
1778c2ecf20Sopenharmony_ci		return -ENOMEM;
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	beeper->input->name = "pwm-beeper";
1818c2ecf20Sopenharmony_ci	beeper->input->phys = "pwm/input0";
1828c2ecf20Sopenharmony_ci	beeper->input->id.bustype = BUS_HOST;
1838c2ecf20Sopenharmony_ci	beeper->input->id.vendor = 0x001f;
1848c2ecf20Sopenharmony_ci	beeper->input->id.product = 0x0001;
1858c2ecf20Sopenharmony_ci	beeper->input->id.version = 0x0100;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	input_set_capability(beeper->input, EV_SND, SND_TONE);
1888c2ecf20Sopenharmony_ci	input_set_capability(beeper->input, EV_SND, SND_BELL);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	beeper->input->event = pwm_beeper_event;
1918c2ecf20Sopenharmony_ci	beeper->input->close = pwm_beeper_close;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	input_set_drvdata(beeper->input, beeper);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	error = input_register_device(beeper->input);
1968c2ecf20Sopenharmony_ci	if (error) {
1978c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to register input device: %d\n", error);
1988c2ecf20Sopenharmony_ci		return error;
1998c2ecf20Sopenharmony_ci	}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, beeper);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	return 0;
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic int __maybe_unused pwm_beeper_suspend(struct device *dev)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	struct pwm_beeper *beeper = dev_get_drvdata(dev);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	/*
2118c2ecf20Sopenharmony_ci	 * Spinlock is taken here is not to protect write to
2128c2ecf20Sopenharmony_ci	 * beeper->suspended, but to ensure that pwm_beeper_event
2138c2ecf20Sopenharmony_ci	 * does not re-submit work once flag is set.
2148c2ecf20Sopenharmony_ci	 */
2158c2ecf20Sopenharmony_ci	spin_lock_irq(&beeper->input->event_lock);
2168c2ecf20Sopenharmony_ci	beeper->suspended = true;
2178c2ecf20Sopenharmony_ci	spin_unlock_irq(&beeper->input->event_lock);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	pwm_beeper_stop(beeper);
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	return 0;
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_cistatic int __maybe_unused pwm_beeper_resume(struct device *dev)
2258c2ecf20Sopenharmony_ci{
2268c2ecf20Sopenharmony_ci	struct pwm_beeper *beeper = dev_get_drvdata(dev);
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	spin_lock_irq(&beeper->input->event_lock);
2298c2ecf20Sopenharmony_ci	beeper->suspended = false;
2308c2ecf20Sopenharmony_ci	spin_unlock_irq(&beeper->input->event_lock);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	/* Let worker figure out if we should resume beeping */
2338c2ecf20Sopenharmony_ci	schedule_work(&beeper->work);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	return 0;
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops,
2398c2ecf20Sopenharmony_ci			 pwm_beeper_suspend, pwm_beeper_resume);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
2428c2ecf20Sopenharmony_cistatic const struct of_device_id pwm_beeper_match[] = {
2438c2ecf20Sopenharmony_ci	{ .compatible = "pwm-beeper", },
2448c2ecf20Sopenharmony_ci	{ },
2458c2ecf20Sopenharmony_ci};
2468c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, pwm_beeper_match);
2478c2ecf20Sopenharmony_ci#endif
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cistatic struct platform_driver pwm_beeper_driver = {
2508c2ecf20Sopenharmony_ci	.probe	= pwm_beeper_probe,
2518c2ecf20Sopenharmony_ci	.driver = {
2528c2ecf20Sopenharmony_ci		.name	= "pwm-beeper",
2538c2ecf20Sopenharmony_ci		.pm	= &pwm_beeper_pm_ops,
2548c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(pwm_beeper_match),
2558c2ecf20Sopenharmony_ci	},
2568c2ecf20Sopenharmony_ci};
2578c2ecf20Sopenharmony_cimodule_platform_driver(pwm_beeper_driver);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
2608c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PWM beeper driver");
2618c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2628c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:pwm-beeper");
263