diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2021-02-22 10:39:39 +0100 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2021-02-22 10:39:39 +0100 |
commit | 2b1f426e511bf5b41c84d05ec09949ebd5dc216e (patch) | |
tree | fcf06dba00432686c8c0bd5137357d2aabf356f8 | |
parent | 774f731a63500deec5d529bb17a75a983f3a4629 (diff) | |
parent | fdb315c4a0764c43916236363a0060972108f5d5 (diff) | |
download | barebox-2b1f426e511bf5b41c84d05ec09949ebd5dc216e.tar.gz barebox-2b1f426e511bf5b41c84d05ec09949ebd5dc216e.tar.xz |
Merge branch 'for-next/sound'
-rw-r--r-- | arch/sandbox/Kconfig | 3 | ||||
-rw-r--r-- | arch/sandbox/Makefile | 6 | ||||
-rw-r--r-- | arch/sandbox/configs/sandbox_defconfig | 1 | ||||
-rw-r--r-- | arch/sandbox/dts/sandbox.dts | 4 | ||||
-rw-r--r-- | arch/sandbox/mach-sandbox/include/mach/linux.h | 22 | ||||
-rw-r--r-- | arch/sandbox/os/Makefile | 4 | ||||
-rw-r--r-- | arch/sandbox/os/sdl.c | 184 | ||||
-rw-r--r-- | commands/Kconfig | 7 | ||||
-rw-r--r-- | commands/Makefile | 1 | ||||
-rw-r--r-- | commands/beep.c | 99 | ||||
-rw-r--r-- | drivers/Kconfig | 1 | ||||
-rw-r--r-- | drivers/Makefile | 1 | ||||
-rw-r--r-- | drivers/sound/Kconfig | 38 | ||||
-rw-r--r-- | drivers/sound/Makefile | 5 | ||||
-rw-r--r-- | drivers/sound/core.c | 121 | ||||
-rw-r--r-- | drivers/sound/gpio-beeper.c | 77 | ||||
-rw-r--r-- | drivers/sound/pwm-beeper.c | 124 | ||||
-rw-r--r-- | drivers/sound/sdl.c | 87 | ||||
-rw-r--r-- | drivers/sound/synth.c | 43 | ||||
-rw-r--r-- | drivers/video/Kconfig | 1 | ||||
-rw-r--r-- | drivers/video/sdl.c | 37 | ||||
-rw-r--r-- | include/linux/fixp-arith.h | 144 | ||||
-rw-r--r-- | include/poller.h | 4 | ||||
-rw-r--r-- | include/pwm.h | 33 | ||||
-rw-r--r-- | include/sound.h | 77 |
25 files changed, 1033 insertions, 91 deletions
diff --git a/arch/sandbox/Kconfig b/arch/sandbox/Kconfig index 5d1702480f..1a4e3bacf6 100644 --- a/arch/sandbox/Kconfig +++ b/arch/sandbox/Kconfig @@ -44,3 +44,6 @@ config 64BIT config SANDBOX_LINUX_I386 bool "32-bit x86 barebox" if CC_HAS_LINUX_I386_SUPPORT + +config SDL + bool diff --git a/arch/sandbox/Makefile b/arch/sandbox/Makefile index 17f9a298d7..ea594944e4 100644 --- a/arch/sandbox/Makefile +++ b/arch/sandbox/Makefile @@ -37,8 +37,8 @@ archprepare: maketools PHONY += maketools -ifeq ($(CONFIG_DRIVER_VIDEO_SDL),y) -SDL_LIBS := $(shell pkg-config sdl --libs) +ifeq ($(CONFIG_SDL),y) +SDL_LIBS := $(shell pkg-config sdl2 --libs) endif ifeq ($(CONFIG_GPIO_LIBFTDI1),y) @@ -64,7 +64,7 @@ endif BAREBOX_LDFLAGS += \ -Wl,-T,$(BAREBOX_LDS) \ -Wl,--whole-archive $(BAREBOX_OBJS) -Wl,--no-whole-archive \ - -lrt -lpthread $(SDL_LIBS) $(FTDI1_LIBS) \ + -lrt $(SDL_LIBS) $(FTDI1_LIBS) \ $(SANITIZER_LIBS) cmd_barebox__ = $(CC) -o $@ $(BAREBOX_LDFLAGS) diff --git a/arch/sandbox/configs/sandbox_defconfig b/arch/sandbox/configs/sandbox_defconfig index dcd0557326..d9d96d9481 100644 --- a/arch/sandbox/configs/sandbox_defconfig +++ b/arch/sandbox/configs/sandbox_defconfig @@ -108,6 +108,7 @@ CONFIG_MTD=y CONFIG_MTD_M25P80=y CONFIG_VIDEO=y CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_SOUND=y CONFIG_MFD_SYSCON=y CONFIG_STATE_DRV=y CONFIG_UBOOTVAR=y diff --git a/arch/sandbox/dts/sandbox.dts b/arch/sandbox/dts/sandbox.dts index afe48154c4..ef1fa7b866 100644 --- a/arch/sandbox/dts/sandbox.dts +++ b/arch/sandbox/dts/sandbox.dts @@ -94,4 +94,8 @@ compatible = "barebox,sandbox-watchdog"; barebox,reset-source = <&stickypage 0>; }; + + sound { + compatible = "barebox,sandbox-sound"; + }; }; diff --git a/arch/sandbox/mach-sandbox/include/mach/linux.h b/arch/sandbox/mach-sandbox/include/mach/linux.h index f047c83e17..831e170d90 100644 --- a/arch/sandbox/mach-sandbox/include/mach/linux.h +++ b/arch/sandbox/mach-sandbox/include/mach/linux.h @@ -38,13 +38,21 @@ struct linux_console_data { extern int sdl_xres; extern int sdl_yres; -void sdl_close(void); -int sdl_open(int xres, int yres, int bpp, void* buf); -void sdl_stop_timer(void); -void sdl_start_timer(void); -void sdl_get_bitfield_rgba(struct fb_bitfield *r, struct fb_bitfield *g, - struct fb_bitfield *b, struct fb_bitfield *a); -void sdl_setpixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +struct sdl_fb_info { + void *screen_base; + int xres; + int yres; + int bpp; + int rmask, gmask, bmask, amask; +}; +int sdl_video_open(const struct sdl_fb_info *); +void sdl_video_pause(void); +void sdl_video_close(void); + +int sdl_sound_init(unsigned sample_rate); +int sdl_sound_play(const void *data, unsigned nsamples); +void sdl_sound_stop(void); +void sdl_sound_close(void); struct ft2232_bitbang; struct ft2232_bitbang *barebox_libftdi1_open(int vendor_id, int device_id, diff --git a/arch/sandbox/os/Makefile b/arch/sandbox/os/Makefile index 9a264ca314..fb2c3cfd86 100644 --- a/arch/sandbox/os/Makefile +++ b/arch/sandbox/os/Makefile @@ -17,8 +17,8 @@ endif obj-y = common.o tap.o obj-$(CONFIG_MALLOC_LIBC) += libc_malloc.o -CFLAGS_sdl.o = $(shell pkg-config sdl --cflags) -obj-$(CONFIG_DRIVER_VIDEO_SDL) += sdl.o +CFLAGS_sdl.o = $(shell pkg-config sdl2 --cflags) +obj-$(CONFIG_SDL) += sdl.o CFLAGS_ftdi.o = $(shell pkg-config libftdi1 --cflags) obj-$(CONFIG_GPIO_LIBFTDI1) += ftdi.o diff --git a/arch/sandbox/os/sdl.c b/arch/sandbox/os/sdl.c index 9a35279eb7..13178abfc0 100644 --- a/arch/sandbox/os/sdl.c +++ b/arch/sandbox/os/sdl.c @@ -1,103 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> - * - * GPL v2 + * Copyright (c) 2021 Ahmad Fatoum */ #include <stdio.h> +#include <stdbool.h> #include <SDL.h> -#include <time.h> -#include <signal.h> #include <mach/linux.h> -#include <unistd.h> -#include <pthread.h> -struct fb_bitfield { - uint32_t offset; /* beginning of bitfield */ - uint32_t length; /* length of bitfield */ - uint32_t msb_right; /* != 0 : Most significant bit is */ - /* right */ -}; +static void sdl_perror(const char *what) +{ + printf("SDL: Could not %s: %s.\n", what, SDL_GetError()); +} -static SDL_Surface *real_screen; -static void *buffer = NULL; -pthread_t th; +static struct sdl_fb_info info; +static SDL_atomic_t shutdown; +SDL_Window *window; -static void sdl_copy_buffer(SDL_Surface *screen) +static int scanout(void *ptr) { - if (SDL_MUSTLOCK(screen)) { - if (SDL_LockSurface(screen) < 0) - return; + SDL_Renderer *renderer; + SDL_Surface *surface; + SDL_Texture *texture; + void *buf = info.screen_base; + int ret = -1; + + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (!renderer) { + sdl_perror("create renderer"); + return -1; } - memcpy(screen->pixels, buffer, screen->pitch * screen->h); + surface = SDL_CreateRGBSurface(0, info.xres, info.yres, info.bpp, + info.rmask, info.gmask, info.bmask, info.amask); + if (!surface) { + sdl_perror("create surface"); + goto destroy_renderer; + } - if(SDL_MUSTLOCK(screen)) - SDL_UnlockSurface(screen); -} + texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { + sdl_perror("create texture"); + goto free_surface; + } -static void *threadStart(void *ptr) -{ - while (1) { - usleep(1000 * 100); + while (!SDL_AtomicGet(&shutdown)) { + SDL_Delay(100); - sdl_copy_buffer(real_screen); - SDL_Flip(real_screen); + SDL_UpdateTexture(texture, NULL, buf, surface->pitch); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); } - return 0; -} + ret = 0; -void sdl_start_timer(void) -{ - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_create(&th, &attr, threadStart, NULL); -} + SDL_DestroyTexture(texture); +free_surface: + SDL_FreeSurface(surface); +destroy_renderer: + SDL_DestroyRenderer(renderer); -void sdl_stop_timer(void) -{ - pthread_cancel(th); + return ret; } -void sdl_get_bitfield_rgba(struct fb_bitfield *r, struct fb_bitfield *g, - struct fb_bitfield *b, struct fb_bitfield *a) +static SDL_Thread *thread; + +void sdl_video_close(void) { - SDL_Surface *screen = real_screen; - - r->length = 8 - screen->format->Rloss; - r->offset = screen->format->Rshift; - g->length = 8 - screen->format->Gloss; - g->offset = screen->format->Gshift; - b->length = 8 - screen->format->Bloss; - b->offset = screen->format->Bshift; - a->length = 8 - screen->format->Aloss; - a->offset = screen->format->Ashift; + SDL_AtomicSet(&shutdown, true); /* implies full memory barrier */ + SDL_WaitThread(thread, NULL); + SDL_AtomicSet(&shutdown, false); + SDL_DestroyWindow(window); + SDL_QuitSubSystem(SDL_INIT_VIDEO); } -int sdl_open(int xres, int yres, int bpp, void* buf) +int sdl_video_open(const struct sdl_fb_info *_info) { - int flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL; + info = *_info; - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE) < 0) { - printf("Could not initialize SDL: %s.\n", SDL_GetError()); + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { + sdl_perror("initialize SDL Video"); return -1; } - real_screen = SDL_SetVideoMode(xres, yres, bpp, flags); - if (!real_screen) { - sdl_close(); - fprintf(stderr, "Couldn't create renderer: %s\n", SDL_GetError()); + window = SDL_CreateWindow("barebox", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + info.xres, info.yres, 0); + if (!window) { + sdl_perror("create window"); + goto quit_subsystem; + } + + /* All scanout needs to happen in the same thread, because not all + * graphic backends are thread-safe. The window is created in the main + * thread though to work around libEGL crashing with SDL_VIDEODRIVER=wayland + */ + + thread = SDL_CreateThread(scanout, "video-scanout", NULL); + if (!thread) { + sdl_perror("start scanout thread"); + goto destroy_window; + } + + return 0; + +destroy_window: + SDL_DestroyWindow(window); +quit_subsystem: + SDL_QuitSubSystem(SDL_INIT_VIDEO); + + return -1; +} + +static SDL_AudioDeviceID dev; + +int sdl_sound_init(unsigned sample_rate) +{ + SDL_AudioSpec audiospec = { + .freq = sample_rate, + .format = AUDIO_S16, + .channels = 1, + .samples = 2048, + }; + + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + sdl_perror("initialize SDL Audio"); return -1; } - buffer = buf; + dev = SDL_OpenAudioDevice(NULL, 0, &audiospec, NULL, 0); + if (!dev) { + sdl_perror("initialize open audio device"); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return -1; + } + SDL_PauseAudioDevice(dev, 0); return 0; } -void sdl_close(void) +void sdl_sound_close(void) +{ + SDL_QuitSubSystem(SDL_INIT_AUDIO); +} + +int sdl_sound_play(const void *data, unsigned nsamples) +{ + /* core sound support handles all the queueing for us */ + SDL_ClearQueuedAudio(dev); + return SDL_QueueAudio(dev, data, nsamples * sizeof(uint16_t)); +} + +void sdl_sound_stop(void) { - sdl_stop_timer(); - SDL_Quit(); + SDL_ClearQueuedAudio(dev); } diff --git a/commands/Kconfig b/commands/Kconfig index 1667e50cc6..520ad4b1de 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -1474,6 +1474,13 @@ config CMD_FBTEST Framebuffer test command that allows to produce a number of test patterns on a screen. +config CMD_BEEP + def_bool y + depends on SOUND + prompt "Beep" + help + Play beeps. Accepts same format as GRUB play + config CMD_READLINE tristate prompt "readline" diff --git a/commands/Makefile b/commands/Makefile index dc285cd00e..034c0e6383 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_CMD_LSMOD) += lsmod.o obj-$(CONFIG_CMD_INSMOD) += insmod.o obj-$(CONFIG_CMD_SPLASH) += splash.o obj-$(CONFIG_CMD_FBTEST) += fbtest.o +obj-$(CONFIG_CMD_BEEP) += beep.o obj-$(CONFIG_USB_GADGET_DFU) += dfu.o obj-$(CONFIG_USB_GADGET_SERIAL) += usbserial.o obj-$(CONFIG_CMD_GPIO) += gpio.o diff --git a/commands/beep.c b/commands/beep.c new file mode 100644 index 0000000000..29569fb51a --- /dev/null +++ b/commands/beep.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: © 2021 Ahmad Fatoum + +#include <common.h> +#include <command.h> +#include <sound.h> +#include <getopt.h> + +static int do_beep(int argc, char *argv[]) +{ + int ret, i, opt; + u32 tempo, total_us = 0; + bool wait = false; + + while((opt = getopt(argc, argv, "wc")) > 0) { + switch(opt) { + case 'w': + wait = true; + break; + case 'c': + return beep_cancel(); + default: + return COMMAND_ERROR_USAGE; + } + } + + argc -= optind; + argv += optind; + + if (argc == 0 || argc % 2 != 1) + return COMMAND_ERROR_USAGE; + + ret = kstrtou32(argv[0], 0, &tempo); + if (ret || tempo == 0) + return COMMAND_ERROR_USAGE; + + tempo = 60 * USEC_PER_SEC / tempo; + + if (argc == 1) { + ret = beep(BELL_DEFAULT_FREQUENCY, tempo); + if (ret) + return ret; + + total_us += tempo; + goto out; + } + + for (i = 1; i < argc; i += 2) { + u32 pitch = 0, duration; + u16 val; + + ret = kstrtou16(argv[i], 0, &val); + if (ret) + return COMMAND_ERROR_USAGE; + + if (val) + pitch = clamp_t(unsigned, val, 20, 20000); + + ret = kstrtou16(argv[i+1], 0, &val); + if (ret) + return COMMAND_ERROR_USAGE; + + duration = val * tempo; + + ret = beep(pitch, duration); + if (ret) + return ret; + + total_us += duration; + } + +out: + if (wait) + beep_wait(total_us); + + return 0; +} + +/* https://www.gnu.org/software/grub/manual/grub/html_node/play.html */ +BAREBOX_CMD_HELP_START(beep) +BAREBOX_CMD_HELP_TEXT("Tempo is an unsigned 32bit number. It's followed by pairs of unsigned") +BAREBOX_CMD_HELP_TEXT("16bit numbers for pitch and duration.") +BAREBOX_CMD_HELP_TEXT("The tempo is the base for all note durations. 60 gives a 1-second base,") +BAREBOX_CMD_HELP_TEXT("120 gives a half-second base, etc. Pitches are Hz.") +BAREBOX_CMD_HELP_TEXT("Set pitch to 0 to produce a rest.") +BAREBOX_CMD_HELP_TEXT("When only tempo is given, a beep of duration 1 at bell frequency results.") +BAREBOX_CMD_HELP_TEXT("") +BAREBOX_CMD_HELP_TEXT("Options:") +BAREBOX_CMD_HELP_OPT ("-c", "cancel pending beeps") +BAREBOX_CMD_HELP_OPT ("-w", "wait until beep is over") +BAREBOX_CMD_HELP_END + +BAREBOX_CMD_START(beep) + .cmd = do_beep, + BAREBOX_CMD_DESC("play a GRUB beep tune") + BAREBOX_CMD_OPTS("tempo [pitch1 duration1 [pitch2 duration2] ...]") + BAREBOX_CMD_HELP(cmd_beep_help) + BAREBOX_CMD_GROUP(CMD_GRP_CONSOLE) +BAREBOX_CMD_END diff --git a/drivers/Kconfig b/drivers/Kconfig index dda2405780..0b87c2af2a 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -13,6 +13,7 @@ source "drivers/mtd/Kconfig" source "drivers/ata/Kconfig" source "drivers/usb/Kconfig" source "drivers/video/Kconfig" +source "drivers/sound/Kconfig" source "drivers/mci/Kconfig" source "drivers/clk/Kconfig" source "drivers/clocksource/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 5a03bdceab..fab3790288 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -43,3 +43,4 @@ obj-y += soc/imx/ obj-y += nvme/ obj-y += ddr/ obj-y += power/ +obj-$(CONFIG_SOUND) += sound/ diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig new file mode 100644 index 0000000000..bf6f715200 --- /dev/null +++ b/drivers/sound/Kconfig @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig SOUND + bool "Sound drivers" + select POLLER + help + 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 SOUND_SDL + bool "SDL sound driver for sandbox" + 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 GPIO_BEEPER + bool "GPIO beeper support" + depends on GPIOLIB && OFDEVICE + help + Say Y here to get support for GPIO based beeper devices. + +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 new file mode 100644 index 0000000000..57d9cbd332 --- /dev/null +++ b/drivers/sound/Makefile @@ -0,0 +1,5 @@ +# 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 +obj-$(CONFIG_GPIO_BEEPER) += gpio-beeper.o diff --git a/drivers/sound/core.c b/drivers/sound/core.c new file mode 100644 index 0000000000..801b1fade5 --- /dev/null +++ b/drivers/sound/core.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: © 2021 Ahmad Fatoum + +#include <common.h> +#include <linux/list.h> +#include <sound.h> +#include <poller.h> +#include <linux/iopoll.h> + +static LIST_HEAD(card_list); + +struct beep { + int freq; + unsigned int us; + struct list_head list; +}; + +static int sound_card_do_beep(struct sound_card *card, + int freq, unsigned int us) +{ + if (freq == -1) + freq = card->bell_frequency; + + return card->beep(card, freq, us); +} + +static void sound_card_poller_cb(void *_card) +{ + struct sound_card *card = _card; + struct beep *beep; + + beep = list_first_entry_or_null(&card->tune, struct beep, list); + if (!beep) { + sound_card_do_beep(card, 0, 0); + return; + } + + list_del(&beep->list); + + poller_call_async(&card->poller, beep->us * 1000ULL, + sound_card_poller_cb, card); + sound_card_do_beep(card, beep->freq, beep->us); + + free(beep); +} + +int sound_card_register(struct sound_card *card) +{ + if (!card->name) + return -EINVAL; + + if (card->bell_frequency <= 0) + card->bell_frequency = 1000; + + poller_async_register(&card->poller, card->name); + INIT_LIST_HEAD(&card->tune); + + list_add_tail(&card->list, &card_list); + return 0; +} + +struct sound_card *sound_card_get_default(void) +{ + return list_first_entry_or_null(&card_list, struct sound_card, list); +} + +int sound_card_beep(struct sound_card *card, int freq, unsigned int us) +{ + struct beep *beep; + int ret; + + if (!card) + return -ENODEV; + + if (!poller_async_active(&card->poller)) { + ret = sound_card_do_beep(card, freq, us); + if (!ret) + poller_call_async(&card->poller, us * 1000ULL, + sound_card_poller_cb, card); + + return ret; + } + + beep = malloc(sizeof(*beep)); + if (!beep) + return -ENOMEM; + + beep->freq = freq; + beep->us = us; + + list_add_tail(&beep->list, &card->tune); + + return 0; +} + +int sound_card_beep_wait(struct sound_card *card, unsigned timeout) +{ + bool active; + return read_poll_timeout(poller_async_active, active, + !active, timeout, &card->poller); +} + +int sound_card_beep_cancel(struct sound_card *card) +{ + struct beep *beep, *tmp; + int ret; + + if (!card) + return -ENODEV; + + poller_async_cancel(&card->poller); + + ret = card->beep(card, 0, 0); + + list_for_each_entry_safe(beep, tmp, &card->tune, list) { + list_del(&beep->list); + free(beep); + } + + return ret; +} diff --git a/drivers/sound/gpio-beeper.c b/drivers/sound/gpio-beeper.c new file mode 100644 index 0000000000..86fd4a4ee6 --- /dev/null +++ b/drivers/sound/gpio-beeper.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021, Ahmad Fatoum + */ + +#include <common.h> +#include <regulator.h> +#include <sound.h> +#include <of.h> +#include <gpio.h> +#include <of_gpio.h> + +struct gpio_beeper { + int gpio; + struct sound_card card; +}; + +static int gpio_beeper_beep(struct sound_card *card, unsigned freq, unsigned duration) +{ + struct gpio_beeper *beeper = container_of(card, struct gpio_beeper, card); + + gpio_set_active(beeper->gpio, freq); + return 0; +} + +static int gpio_beeper_probe(struct device_d *dev) +{ + struct device_node *np = dev->device_node; + struct gpio_beeper *beeper; + struct sound_card *card; + enum of_gpio_flags of_flags; + unsigned long gpio_flags = GPIOF_OUT_INIT_ACTIVE; + int ret, gpio; + + gpio = of_get_named_gpio_flags(np, "gpios", 0, &of_flags); + if (!gpio_is_valid(gpio)) + return gpio; + + if (of_flags & OF_GPIO_ACTIVE_LOW) + gpio_flags |= GPIOF_ACTIVE_LOW; + + ret = gpio_request_one(gpio, gpio_flags, "gpio-beeper"); + if (ret) { + dev_err(dev, "failed to request gpio %d: %d\n", gpio, ret); + return ret; + } + + beeper = xzalloc(sizeof(*beeper)); + beeper->gpio = gpio; + dev->priv = beeper; + + card = &beeper->card; + card->name = np->full_name; + card->beep = gpio_beeper_beep; + + return sound_card_register(card); +} + +static void gpio_beeper_suspend(struct device_d *dev) +{ + struct gpio_beeper *beeper = dev->priv; + + gpio_beeper_beep(&beeper->card, 0, 0); +} + +static const struct of_device_id gpio_beeper_match[] = { + { .compatible = "gpio-beeper", }, + { }, +}; + +static struct driver_d gpio_beeper_driver = { + .name = "gpio-beeper", + .probe = gpio_beeper_probe, + .remove = gpio_beeper_suspend, + .of_compatible = gpio_beeper_match, +}; +device_platform_driver(gpio_beeper_driver); diff --git a/drivers/sound/pwm-beeper.c b/drivers/sound/pwm-beeper.c new file mode 100644 index 0000000000..ef053f97cf --- /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@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/drivers/sound/sdl.c b/drivers/sound/sdl.c new file mode 100644 index 0000000000..118d774295 --- /dev/null +++ b/drivers/sound/sdl.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <common.h> +#include <errno.h> +#include <driver.h> +#include <mach/linux.h> +#include <linux/time.h> +#include <linux/math64.h> +#include <of.h> +#include <sound.h> + +#define AMPLITUDE 28000 +#define SAMPLERATE 44000ULL + +struct sandbox_sound { + struct sound_card card; +}; + +static int sandbox_sound_beep(struct sound_card *card, unsigned freq, unsigned duration) +{ + size_t nsamples = div_s64(SAMPLERATE * duration, USEC_PER_SEC); + int16_t *data; + int ret; + + if (!freq) { + sdl_sound_stop(); + return 0; + } + + data = malloc(nsamples * sizeof(*data)); + if (!data) + return -ENOMEM; + + synth_sin(freq, AMPLITUDE, data, SAMPLERATE, nsamples); + ret = sdl_sound_play(data, nsamples); + if (ret) + ret = -EIO; + free(data); + + return ret; +} + +static int sandbox_sound_probe(struct device_d *dev) +{ + struct sandbox_sound *priv; + struct sound_card *card; + int ret; + + priv = xzalloc(sizeof(*priv)); + + card = &priv->card; + card->name = "SDL-Audio"; + card->beep = sandbox_sound_beep; + + ret = sdl_sound_init(SAMPLERATE); + if (ret) { + ret = -ENODEV; + goto free_priv; + } + + ret = sound_card_register(card); + if (ret) + goto sdl_sound_close; + + dev_info(dev, "probed\n"); + return 0; + +sdl_sound_close: + sdl_sound_close(); +free_priv: + free(priv); + + return ret; +} + + +static __maybe_unused struct of_device_id sandbox_sound_dt_ids[] = { + { .compatible = "barebox,sandbox-sound" }, + { /* sentinel */ } +}; + +static struct driver_d sandbox_sound_drv = { + .name = "sandbox-sound", + .of_compatible = sandbox_sound_dt_ids, + .probe = sandbox_sound_probe, +}; +device_platform_driver(sandbox_sound_drv); 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/drivers/video/Kconfig b/drivers/video/Kconfig index 9ec6ea4248..b6d468c63c 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -82,6 +82,7 @@ endif config DRIVER_VIDEO_SDL bool "SDL framebuffer driver" depends on SANDBOX + select SDL config DRIVER_VIDEO_PXA bool "PXA27x framebuffer driver" diff --git a/drivers/video/sdl.c b/drivers/video/sdl.c index 9811b2cf12..e9debc51b1 100644 --- a/drivers/video/sdl.c +++ b/drivers/video/sdl.c @@ -13,23 +13,25 @@ #include <errno.h> #include <gui/graphic_utils.h> +#define to_mask(color) GENMASK(color.length - 1, color.offset) + static void sdlfb_enable(struct fb_info *info) { - int ret; - - ret = sdl_open(info->xres, info->yres, info->bits_per_pixel, - info->screen_base); - if (ret) - return; - sdl_get_bitfield_rgba(&info->red, &info->green, &info->blue, &info->transp); - - sdl_start_timer(); + struct sdl_fb_info sdl_info = { + .screen_base = info->screen_base, + .xres = info->xres, .yres = info->yres, .bpp = info->bits_per_pixel, + .rmask = to_mask(info->red), + .gmask = to_mask(info->green), + .bmask = to_mask(info->blue), + .amask = to_mask(info->transp), + }; + + sdl_video_open(&sdl_info); } static void sdlfb_disable(struct fb_info *info) { - sdl_stop_timer(); - sdl_close(); + sdl_video_close(); } static struct fb_ops sdlfb_ops = { @@ -48,10 +50,19 @@ static int sdlfb_probe(struct device_d *dev) fb = xzalloc(sizeof(*fb)); fb->modes.modes = fb->mode = dev->platform_data; fb->modes.num_modes = 1; - fb->bits_per_pixel = 4 << 3; fb->xres = fb->mode->xres; fb->yres = fb->mode->yres; + fb->bits_per_pixel = 32; + fb->transp.length = 8; + fb->red.length = 8; + fb->green.length = 8; + fb->blue.length = 8; + fb->transp.offset = 24; + fb->red.offset = 16; + fb->green.offset = 8; + fb->blue.offset = 0; + fb->priv = fb; fb->fbops = &sdlfb_ops; @@ -68,7 +79,6 @@ static int sdlfb_probe(struct device_d *dev) kfree(fb->screen_base); kfree(fb); - sdl_close(); return ret; } @@ -78,7 +88,6 @@ static void sdlfb_remove(struct device_d *dev) kfree(fb->screen_base); kfree(fb); - sdl_close(); } static struct driver_d sdlfb_driver = { 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/poller.h b/include/poller.h index 886557252b..db773265b2 100644 --- a/include/poller.h +++ b/include/poller.h @@ -34,6 +34,10 @@ int poller_async_unregister(struct poller_async *pa); int poller_call_async(struct poller_async *pa, uint64_t delay_ns, void (*fn)(void *), void *ctx); int poller_async_cancel(struct poller_async *pa); +static inline bool poller_async_active(struct poller_async *pa) +{ + return pa->active; +} #ifdef CONFIG_POLLER void poller_call(void); diff --git a/include/pwm.h b/include/pwm.h index b67ab13d2e..2bd59fb8d3 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; /** diff --git a/include/sound.h b/include/sound.h new file mode 100644 index 0000000000..62fef106ee --- /dev/null +++ b/include/sound.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: © 2021 Ahmad Fatoum */ + +#ifndef __SOUND_H_ +#define __SOUND_H_ + +#include <linux/list.h> +#include <poller.h> + +#define BELL_DEFAULT_FREQUENCY -1 + +struct sound_card { + const char *name; + int bell_frequency; + int (*beep)(struct sound_card *, unsigned freq, unsigned us); + + /* private */ + struct list_head list; + struct list_head tune; + struct poller_async poller; +}; + +int sound_card_register(struct sound_card *card); +int sound_card_beep_wait(struct sound_card *card, unsigned timeout_us); +int sound_card_beep(struct sound_card *card, int freq, unsigned int us); +int sound_card_beep_cancel(struct sound_card *card); + +struct sound_card *sound_card_get_default(void); + +static inline int beep(int freq, unsigned us) +{ + return sound_card_beep(sound_card_get_default(), freq, us); +} + +static inline int beep_wait(unsigned timeout_us) +{ + return sound_card_beep_wait(sound_card_get_default(), timeout_us); +} + +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 |