This driver can be used to drive a piezo-buzzer attached to a PWM.

Signed-off-by: Ahmad Fatoum <[email protected]>
---
 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 <[email protected]>
+ *  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


_______________________________________________
barebox mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to