summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmad Fatoum <ahmad@a3f.at>2021-01-31 21:18:42 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2021-02-08 10:57:05 +0100
commit01a55d345dbb8d861cda0d430d42c3bbbaff718a (patch)
treee22881f24ddb8ae83eb7211137ccb92ff331be44
parent78dd61b47e5d93240b3d112c51fa3b1896c4f6e6 (diff)
downloadbarebox-01a55d345dbb8d861cda0d430d42c3bbbaff718a.tar.gz
barebox-01a55d345dbb8d861cda0d430d42c3bbbaff718a.tar.xz
sound: add basic synthesizers for PCM beeper use
For beeping on PCM sound cards, barebox will need to synthesize samples. Add basic sine and square wave synthesizers to achieve this. Client code can either call __synth_F to explicitly select synth F or synth_F, which depending on CONFIG_SYNTH_SQUARES may expand to either __synth_F or a gain-adjusted __synth_generate_square. The latter is mainly useful for slow systems that can't synthesize enough sine samples in a poller without impacting boot performance. Signed-off-by: Ahmad Fatoum <ahmad@a3f.at> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
-rw-r--r--drivers/sound/Kconfig13
-rw-r--r--drivers/sound/Makefile2
-rw-r--r--drivers/sound/synth.c43
-rw-r--r--include/linux/fixp-arith.h144
-rw-r--r--include/sound.h32
5 files changed, 233 insertions, 1 deletions
diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig
index cce5c760af..d9f63a5f3c 100644
--- a/drivers/sound/Kconfig
+++ b/drivers/sound/Kconfig
@@ -6,3 +6,16 @@ menuconfig SOUND
Say Y here for sound support. At the moment that's just beep tones.
Tones are played asynchronously in a poller. Check the beep command
for how to exercise the API.
+
+if SOUND
+
+config SYNTH_SQUARES
+ bool "Synthesize square waves only"
+ help
+ For beeping on PCM sound cards, barebox needs to synthesize samples,
+ which can take too much poller time for crisp playback and/or quick
+ booting. If your playback stutters, say Y here. This will have all
+ synthesizers output a gain-adjusted square wave instead, which is
+ less time-consuming to compute.
+
+endif
diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile
index e343e0fa72..69873faab9 100644
--- a/drivers/sound/Makefile
+++ b/drivers/sound/Makefile
@@ -1,2 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
-obj-y += core.o
+obj-y += core.o synth.o
diff --git a/drivers/sound/synth.c b/drivers/sound/synth.c
new file mode 100644
index 0000000000..c9de62b516
--- /dev/null
+++ b/drivers/sound/synth.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2012 Samsung Electronics, R. Chandrasekar <rcsekar@samsung.com>
+ * Copyright (C) 2021 Ahmad Fatoum
+ */
+
+#include <common.h>
+#include <linux/fixp-arith.h>
+#include <linux/math64.h>
+#include <sound.h>
+
+void __synth_sin(unsigned freq, s16 amplitude, s16 *stream,
+ unsigned sample_rate, unsigned nsamples)
+{
+ int64_t v = 0;
+ int i = 0;
+
+ for (i = 0; i < nsamples; i++) {
+ /* Assume RHS sign extension, true for GCC */
+ stream[i] = (fixp_sin32(div_s64(v * 360, sample_rate)) * (int64_t)amplitude) >> 31;
+ v += freq;
+ }
+}
+
+void __synth_square(unsigned freq, s16 amplitude, s16 *stream,
+ unsigned sample_rate, unsigned nsamples)
+{
+ unsigned period = freq ? sample_rate / freq : 0;
+ int half = period / 2;
+
+ while (nsamples) {
+ int i;
+
+ for (i = 0; nsamples && i < half; i++) {
+ nsamples--;
+ *stream++ = amplitude;
+ }
+ for (i = 0; nsamples && i < period - half; i++) {
+ nsamples--;
+ *stream++ = -amplitude;
+ }
+ }
+}
diff --git a/include/linux/fixp-arith.h b/include/linux/fixp-arith.h
new file mode 100644
index 0000000000..8396013785
--- /dev/null
+++ b/include/linux/fixp-arith.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _FIXP_ARITH_H
+#define _FIXP_ARITH_H
+
+#include <linux/math64.h>
+
+/*
+ * Simplistic fixed-point arithmetics.
+ * Hmm, I'm probably duplicating some code :(
+ *
+ * Copyright (c) 2002 Johann Deneux
+ */
+
+/*
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <johann.deneux@gmail.com>
+ */
+
+#include <linux/types.h>
+
+static const s32 sin_table[] = {
+ 0x00000000, 0x023be165, 0x04779632, 0x06b2f1d2, 0x08edc7b6, 0x0b27eb5c,
+ 0x0d61304d, 0x0f996a26, 0x11d06c96, 0x14060b67, 0x163a1a7d, 0x186c6ddd,
+ 0x1a9cd9ac, 0x1ccb3236, 0x1ef74bf2, 0x2120fb82, 0x234815ba, 0x256c6f9e,
+ 0x278dde6e, 0x29ac379f, 0x2bc750e8, 0x2ddf003f, 0x2ff31bdd, 0x32037a44,
+ 0x340ff241, 0x36185aee, 0x381c8bb5, 0x3a1c5c56, 0x3c17a4e7, 0x3e0e3ddb,
+ 0x3fffffff, 0x41ecc483, 0x43d464fa, 0x45b6bb5d, 0x4793a20f, 0x496af3e1,
+ 0x4b3c8c11, 0x4d084650, 0x4ecdfec6, 0x508d9210, 0x5246dd48, 0x53f9be04,
+ 0x55a6125a, 0x574bb8e5, 0x58ea90c2, 0x5a827999, 0x5c135399, 0x5d9cff82,
+ 0x5f1f5ea0, 0x609a52d1, 0x620dbe8a, 0x637984d3, 0x64dd894f, 0x6639b039,
+ 0x678dde6d, 0x68d9f963, 0x6a1de735, 0x6b598ea1, 0x6c8cd70a, 0x6db7a879,
+ 0x6ed9eba0, 0x6ff389de, 0x71046d3c, 0x720c8074, 0x730baeec, 0x7401e4bf,
+ 0x74ef0ebb, 0x75d31a5f, 0x76adf5e5, 0x777f903b, 0x7847d908, 0x7906c0af,
+ 0x79bc384c, 0x7a6831b8, 0x7b0a9f8c, 0x7ba3751c, 0x7c32a67c, 0x7cb82884,
+ 0x7d33f0c8, 0x7da5f5a3, 0x7e0e2e31, 0x7e6c924f, 0x7ec11aa3, 0x7f0bc095,
+ 0x7f4c7e52, 0x7f834ecf, 0x7fb02dc4, 0x7fd317b3, 0x7fec09e1, 0x7ffb025e,
+ 0x7fffffff
+};
+
+/**
+ * __fixp_sin32() returns the sin of an angle in degrees
+ *
+ * @degrees: angle, in degrees, from 0 to 360.
+ *
+ * The returned value ranges from -0x7fffffff to +0x7fffffff.
+ */
+static inline s32 __fixp_sin32(int degrees)
+{
+ s32 ret;
+ bool negative = false;
+
+ if (degrees > 180) {
+ negative = true;
+ degrees -= 180;
+ }
+ if (degrees > 90)
+ degrees = 180 - degrees;
+
+ ret = sin_table[degrees];
+
+ return negative ? -ret : ret;
+}
+
+/**
+ * fixp_sin32() returns the sin of an angle in degrees
+ *
+ * @degrees: angle, in degrees. The angle can be positive or negative
+ *
+ * The returned value ranges from -0x7fffffff to +0x7fffffff.
+ */
+static inline s32 fixp_sin32(int degrees)
+{
+ degrees = (degrees % 360 + 360) % 360;
+
+ return __fixp_sin32(degrees);
+}
+
+/* cos(x) = sin(x + 90 degrees) */
+#define fixp_cos32(v) fixp_sin32((v) + 90)
+
+/*
+ * 16 bits variants
+ *
+ * The returned value ranges from -0x7fff to 0x7fff
+ */
+
+#define fixp_sin16(v) (fixp_sin32(v) >> 16)
+#define fixp_cos16(v) (fixp_cos32(v) >> 16)
+
+/**
+ * fixp_sin32_rad() - calculates the sin of an angle in radians
+ *
+ * @radians: angle, in radians
+ * @twopi: value to be used for 2*pi
+ *
+ * Provides a variant for the cases where just 360
+ * values is not enough. This function uses linear
+ * interpolation to a wider range of values given by
+ * twopi var.
+ *
+ * Experimental tests gave a maximum difference of
+ * 0.000038 between the value calculated by sin() and
+ * the one produced by this function, when twopi is
+ * equal to 360000. That seems to be enough precision
+ * for practical purposes.
+ *
+ * Please notice that two high numbers for twopi could cause
+ * overflows, so the routine will not allow values of twopi
+ * bigger than 1^18.
+ */
+static inline s32 fixp_sin32_rad(u32 radians, u32 twopi)
+{
+ int degrees;
+ s32 v1, v2, dx, dy;
+ s64 tmp;
+
+ /*
+ * Avoid too large values for twopi, as we don't want overflows.
+ */
+ BUG_ON(twopi > 1 << 18);
+
+ degrees = (radians * 360) / twopi;
+ tmp = radians - (degrees * twopi) / 360;
+
+ degrees = (degrees % 360 + 360) % 360;
+ v1 = __fixp_sin32(degrees);
+
+ v2 = fixp_sin32(degrees + 1);
+
+ dx = twopi / 360;
+ dy = v2 - v1;
+
+ tmp *= dy;
+
+ return v1 + div_s64(tmp, dx);
+}
+
+/* cos(x) = sin(x + pi/2 radians) */
+
+#define fixp_cos32_rad(rad, twopi) \
+ fixp_sin32_rad(rad + twopi / 4, twopi)
+
+#endif
diff --git a/include/sound.h b/include/sound.h
index f5124aebbd..62fef106ee 100644
--- a/include/sound.h
+++ b/include/sound.h
@@ -42,4 +42,36 @@ static inline int beep_cancel(void)
return sound_card_beep_cancel(sound_card_get_default());
}
+
+/*
+ * Synthesizers all have the same prototype, but their implementation
+ * is replaced with a gain-adjusted square wave if CONFIG_SYNTH_SQUARES=y.
+ * This is to support PCM beeping on systems, where sine generation may
+ * spend to much time in the poller.
+ */
+typedef void synth_t(unsigned freq, s16 amplitude, s16 *stream,
+ unsigned sample_rate, unsigned nsamples);
+
+#ifdef CONFIG_SYNTH_SQUARES
+#define SYNTH(fn, volume_percent) \
+ static inline void fn(unsigned f, s16 amplitude, s16 *s, \
+ unsigned r, unsigned n) { \
+ synth_t __synth_square; \
+ amplitude = amplitude * volume_percent / 100; \
+ __synth_square(f, amplitude, s, r, n); \
+ } \
+ synth_t __##fn
+#else
+#define SYNTH(fn, volume_percent) \
+ static inline void fn(unsigned f, s16 a, s16 *s, \
+ unsigned r, unsigned n) { \
+ synth_t __##fn; \
+ __##fn(f, a, s, r, n); \
+ } \
+ synth_t __##fn
+#endif
+
+SYNTH(synth_square, 100); /* square wave always has full amplitude */
+SYNTH(synth_sin, 64); /* ∫₀¹ sin(πx) dx ≈ 64% */
+
#endif