[PATCH 6/7] sound: add PWM beeper support
Ahmad Fatoum
ahmad at a3f.at
Sun Jan 31 15:18:45 EST 2021
This driver can be used to drive a piezo-buzzer attached to a PWM.
Signed-off-by: Ahmad Fatoum <ahmad at a3f.at>
---
drivers/sound/Kconfig | 6 ++
drivers/sound/Makefile | 1 +
drivers/sound/pwm-beeper.c | 124 +++++++++++++++++++++++++++++++++++++
include/pwm.h | 33 ++++++++++
4 files changed, 164 insertions(+)
create mode 100644 drivers/sound/pwm-beeper.c
diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig
index 889657305b0b..9b7bbd7e7a33 100644
--- a/drivers/sound/Kconfig
+++ b/drivers/sound/Kconfig
@@ -14,6 +14,12 @@ config SOUND_SDL
depends on SANDBOX && OFDEVICE
select SDL
+config PWM_BEEPER
+ bool "PWM beeper support"
+ depends on PWM && OFDEVICE
+ help
+ Say Y here to get support for PWM based beeper devices.
+
config SYNTH_SQUARES
bool "Synthesize square waves only"
help
diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile
index 692105fd6b59..468e5bee838d 100644
--- a/drivers/sound/Makefile
+++ b/drivers/sound/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += core.o synth.o
obj-$(CONFIG_SOUND_SDL) += sdl.o
+obj-$(CONFIG_PWM_BEEPER) += pwm-beeper.o
diff --git a/drivers/sound/pwm-beeper.c b/drivers/sound/pwm-beeper.c
new file mode 100644
index 000000000000..ef053f97cf47
--- /dev/null
+++ b/drivers/sound/pwm-beeper.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <lars at metafoo.de>
+ * Copyright (C) 2021, Ahmad Fatoum
+ */
+
+#include <common.h>
+#include <regulator.h>
+#include <sound.h>
+#include <of.h>
+#include <pwm.h>
+
+struct pwm_beeper {
+ struct pwm_device *pwm;
+ struct regulator *amplifier;
+ struct sound_card card;
+};
+
+#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
+
+static int pwm_beeper_beep(struct sound_card *card, unsigned freq, unsigned duration)
+{
+ struct pwm_beeper *beeper = container_of(card, struct pwm_beeper, card);
+ struct pwm_state state;
+ int error = 0;
+
+ if (!freq) {
+ regulator_disable(beeper->amplifier);
+ goto pwm_disable;
+ }
+
+ pwm_get_state(beeper->pwm, &state);
+
+ state.p_enable = true;
+ state.period_ns = HZ_TO_NANOSECONDS(freq);
+ pwm_set_relative_duty_cycle(&state, 50, 100);
+
+ error = pwm_apply_state(beeper->pwm, &state);
+ if (error)
+ return error;
+
+ error = regulator_enable(beeper->amplifier);
+ if (error)
+ goto pwm_disable;
+
+ return 0;
+pwm_disable:
+ pwm_disable(beeper->pwm);
+ return error;
+}
+
+static int pwm_beeper_probe(struct device_d *dev)
+{
+ struct pwm_beeper *beeper;
+ struct sound_card *card;
+ struct pwm_state state;
+ u32 bell_frequency;
+ int error;
+
+ beeper = xzalloc(sizeof(*beeper));
+ dev->priv = beeper;
+
+ beeper->pwm = of_pwm_request(dev->device_node, NULL);
+ if (IS_ERR(beeper->pwm)) {
+ error = PTR_ERR(beeper->pwm);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to request PWM device: %d\n",
+ error);
+ return error;
+ }
+
+ /* Sync up PWM state and ensure it is off. */
+ pwm_init_state(beeper->pwm, &state);
+ state.p_enable = false;
+ error = pwm_apply_state(beeper->pwm, &state);
+ if (error) {
+ dev_err(dev, "failed to apply initial PWM state: %d\n",
+ error);
+ return error;
+ }
+
+ beeper->amplifier = regulator_get(dev, "amp");
+ if (IS_ERR(beeper->amplifier)) {
+ error = PTR_ERR(beeper->amplifier);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get 'amp' regulator: %d\n",
+ error);
+ return error;
+ }
+
+ error = of_property_read_u32(dev->device_node, "beeper-hz", &bell_frequency);
+ if (error) {
+ bell_frequency = 1000;
+ dev_dbg(dev, "failed to parse 'beeper-hz' property, using default: %uHz\n",
+ bell_frequency);
+ }
+
+ card = &beeper->card;
+ card->name = dev->device_node->full_name;
+ card->bell_frequency = bell_frequency;
+ card->beep = pwm_beeper_beep;
+
+ return sound_card_register(card);
+}
+
+static void pwm_beeper_suspend(struct device_d *dev)
+{
+ struct pwm_beeper *beeper = dev->priv;
+
+ pwm_beeper_beep(&beeper->card, 0, 0);
+}
+
+static const struct of_device_id pwm_beeper_match[] = {
+ { .compatible = "pwm-beeper", },
+ { },
+};
+
+static struct driver_d pwm_beeper_driver = {
+ .name = "pwm-beeper",
+ .probe = pwm_beeper_probe,
+ .remove = pwm_beeper_suspend,
+ .of_compatible = pwm_beeper_match,
+};
+device_platform_driver(pwm_beeper_driver);
diff --git a/include/pwm.h b/include/pwm.h
index b67ab13d2e2d..2bd59fb8d3b6 100644
--- a/include/pwm.h
+++ b/include/pwm.h
@@ -3,6 +3,7 @@
#define __PWM_H
#include <dt-bindings/pwm/pwm.h>
+#include <errno.h>
struct pwm_device;
struct device_d;
@@ -63,6 +64,38 @@ void pwm_disable(struct pwm_device *pwm);
unsigned int pwm_get_period(struct pwm_device *pwm);
+/**
+ * pwm_set_relative_duty_cycle() - Set a relative duty cycle value
+ * @state: PWM state to fill
+ * @duty_cycle: relative duty cycle value
+ * @scale: scale in which @duty_cycle is expressed
+ *
+ * This functions converts a relative into an absolute duty cycle (expressed
+ * in nanoseconds), and puts the result in state->duty_cycle.
+ *
+ * For example if you want to configure a 50% duty cycle, call:
+ *
+ * pwm_init_state(pwm, &state);
+ * pwm_set_relative_duty_cycle(&state, 50, 100);
+ * pwm_apply_state(pwm, &state);
+ *
+ * This functions returns -EINVAL if @duty_cycle and/or @scale are
+ * inconsistent (@scale == 0 or @duty_cycle > @scale).
+ */
+static inline int
+pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle,
+ unsigned int scale)
+{
+ if (!scale || duty_cycle > scale)
+ return -EINVAL;
+
+ state->duty_ns = DIV_ROUND_CLOSEST_ULL((u64)duty_cycle *
+ state->period_ns,
+ scale);
+
+ return 0;
+}
+
struct pwm_chip;
/**
--
2.30.0
More information about the barebox
mailing list