From d4901898654b664c41d8a03afc0e0fbf531b5812 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 10 Dec 2018 10:37:36 -0700 Subject: [PATCH] dm: sound: Create a uclass for sound The sound driver pulls together the audio codec and i2s drivers in order to actually make sounds. It supports setup() and play() methods. The sound_find_codec_i2s() function allows locating the linked codec and i2s devices. They can be referred to from uclass-private data. Add a uclass and a test for sound. Signed-off-by: Simon Glass --- arch/sandbox/dts/test.dts | 11 +++ arch/sandbox/include/asm/test.h | 20 +++++ cmd/sound.c | 23 +++++- drivers/sound/Makefile | 1 + drivers/sound/sandbox.c | 67 +++++++++++++++++ drivers/sound/sound-uclass.c | 127 ++++++++++++++++++++++++++++++++ include/dm/uclass-id.h | 1 + include/sound.h | 70 +++++++++++++++++- test/dm/Makefile | 1 + test/dm/sound.c | 34 +++++++++ 10 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 drivers/sound/sound-uclass.c create mode 100644 test/dm/sound.c diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 5c2910c583..82bd91936a 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -538,6 +538,17 @@ compatible = "sandbox,smem"; }; + sound { + compatible = "sandbox,sound"; + cpu { + sound-dai = <&i2s 0>; + }; + + codec { + sound-dai = <&audio 0>; + }; + }; + spi@0 { #address-cells = <1>; #size-cells = <0>; diff --git a/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h index 71bd50bd5b..74f9618822 100644 --- a/arch/sandbox/include/asm/test.h +++ b/arch/sandbox/include/asm/test.h @@ -141,4 +141,24 @@ void sandbox_get_codec_params(struct udevice *dev, int *interfacep, int *ratep, */ int sandbox_get_i2s_sum(struct udevice *dev); +/** + * sandbox_get_setup_called() - Returns the number of times setup(*) was called + * + * This is used in the sound test + * + * @dev: Device to check + * @return call count for the setup() method + */ +int sandbox_get_setup_called(struct udevice *dev); + +/** + * sandbox_get_sound_sum() - Read back the sum of the sound data so far + * + * This data is provided to the sandbox driver by the sound play() method. + * + * @dev: Device to check + * @return sum of audio data + */ +int sandbox_get_sound_sum(struct udevice *dev); + #endif diff --git a/cmd/sound.c b/cmd/sound.c index d1cbc14f8d..77f5152925 100644 --- a/cmd/sound.c +++ b/cmd/sound.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -14,11 +15,20 @@ DECLARE_GLOBAL_DATA_PTR; /* Initilaise sound subsystem */ static int do_init(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) { +#ifdef CONFIG_DM_SOUND + struct udevice *dev; +#endif int ret; +#ifdef CONFIG_DM_SOUND + ret = uclass_first_device_err(UCLASS_SOUND, &dev); + if (!ret) + ret = sound_setup(dev); +#else ret = sound_init(gd->fdt_blob); +#endif if (ret) { - printf("Initialise Audio driver failed\n"); + printf("Initialise Audio driver failed (ret=%d)\n", ret); return CMD_RET_FAILURE; } @@ -28,6 +38,9 @@ static int do_init(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) /* play sound from buffer */ static int do_play(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) { +#ifdef CONFIG_DM_SOUND + struct udevice *dev; +#endif int ret = 0; int msec = 1000; int freq = 400; @@ -37,9 +50,15 @@ static int do_play(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) if (argc > 2) freq = simple_strtoul(argv[2], NULL, 10); +#ifdef CONFIG_DM_SOUND + ret = uclass_first_device_err(UCLASS_SOUND, &dev); + if (!ret) + ret = sound_beep(dev, msec, freq); +#else ret = sound_play(msec, freq); +#endif if (ret) { - printf("play failed"); + printf("Sound device failed to play (err=%d)\n", ret); return CMD_RET_FAILURE; } diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile index 4aced9d22b..70d32c6d6f 100644 --- a/drivers/sound/Makefile +++ b/drivers/sound/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_SOUND) += sound.o obj-$(CONFIG_DM_SOUND) += codec-uclass.o obj-$(CONFIG_DM_SOUND) += i2s-uclass.o obj-$(CONFIG_I2S) += sound-i2s.o +obj-$(CONFIG_DM_SOUND) += sound-uclass.o obj-$(CONFIG_I2S_SAMSUNG) += samsung-i2s.o obj-$(CONFIG_SOUND_SANDBOX) += sandbox.o obj-$(CONFIG_SOUND_WM8994) += wm8994.o diff --git a/drivers/sound/sandbox.c b/drivers/sound/sandbox.c index 2f7c68be0c..ee2635f41d 100644 --- a/drivers/sound/sandbox.c +++ b/drivers/sound/sandbox.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,12 @@ struct sandbox_i2s_priv { int sum; /* Use to sum the provided audio data */ }; +struct sandbox_sound_priv { + int setup_called; + int sum; /* Use to sum the provided audio data */ +}; + +#ifndef CONFIG_DM_SOUND int sound_play(uint32_t msec, uint32_t frequency) { sandbox_sdl_sound_start(frequency); @@ -30,6 +37,7 @@ int sound_play(uint32_t msec, uint32_t frequency) return 0; } +#endif /* CONFIG_DM_SOUND */ int sound_init(const void *blob) { @@ -56,6 +64,20 @@ int sandbox_get_i2s_sum(struct udevice *dev) return priv->sum; } +int sandbox_get_setup_called(struct udevice *dev) +{ + struct sandbox_sound_priv *priv = dev_get_priv(dev); + + return priv->setup_called; +} + +int sandbox_get_sound_sum(struct udevice *dev) +{ + struct sandbox_sound_priv *priv = dev_get_priv(dev); + + return priv->sum; +} + static int sandbox_codec_set_params(struct udevice *dev, int interface, int rate, int mclk_freq, int bits_per_sample, uint channels) @@ -96,9 +118,35 @@ static int sandbox_i2s_probe(struct udevice *dev) uc_priv->channels = 2; uc_priv->id = 1; + return sandbox_sdl_sound_init(); +} + +static int sandbox_sound_setup(struct udevice *dev) +{ + struct sandbox_sound_priv *priv = dev_get_priv(dev); + + priv->setup_called++; + return 0; } +static int sandbox_sound_play(struct udevice *dev, void *data, uint data_size) +{ + struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev); + struct sandbox_sound_priv *priv = dev_get_priv(dev); + int i; + + for (i = 0; i < data_size; i++) + priv->sum += ((uint8_t *)data)[i]; + + return i2s_tx_data(uc_priv->i2s, data, data_size); +} + +static int sandbox_sound_probe(struct udevice *dev) +{ + return sound_find_codec_i2s(dev); +} + static const struct audio_codec_ops sandbox_codec_ops = { .set_params = sandbox_codec_set_params, }; @@ -133,3 +181,22 @@ U_BOOT_DRIVER(sandbox_i2s) = { .probe = sandbox_i2s_probe, .priv_auto_alloc_size = sizeof(struct sandbox_i2s_priv), }; + +static const struct sound_ops sandbox_sound_ops = { + .setup = sandbox_sound_setup, + .play = sandbox_sound_play, +}; + +static const struct udevice_id sandbox_sound_ids[] = { + { .compatible = "sandbox,sound" }, + { } +}; + +U_BOOT_DRIVER(sandbox_sound) = { + .name = "sandbox_sound", + .id = UCLASS_SOUND, + .of_match = sandbox_sound_ids, + .ops = &sandbox_sound_ops, + .priv_auto_alloc_size = sizeof(struct sandbox_sound_priv), + .probe = sandbox_sound_probe, +}; diff --git a/drivers/sound/sound-uclass.c b/drivers/sound/sound-uclass.c new file mode 100644 index 0000000000..71e753cb99 --- /dev/null +++ b/drivers/sound/sound-uclass.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2018 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include + +#define SOUND_BITS_IN_BYTE 8 + +int sound_setup(struct udevice *dev) +{ + struct sound_ops *ops = sound_get_ops(dev); + + if (!ops->setup) + return -ENOSYS; + + return ops->setup(dev); +} + +int sound_play(struct udevice *dev, void *data, uint data_size) +{ + struct sound_ops *ops = sound_get_ops(dev); + + if (!ops->play) + return -ENOSYS; + + return ops->play(dev, data, data_size); +} + +int sound_beep(struct udevice *dev, int msecs, int frequency_hz) +{ + struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev); + struct i2s_uc_priv *i2s_uc_priv = dev_get_uclass_priv(uc_priv->i2s); + unsigned short *data; + uint data_size; + int ret; + + ret = sound_setup(dev); + if (ret && ret != -EALREADY) + return ret; + + /* Buffer length computation */ + data_size = i2s_uc_priv->samplingrate * i2s_uc_priv->channels; + data_size *= (i2s_uc_priv->bitspersample / SOUND_BITS_IN_BYTE); + data = malloc(data_size); + if (!data) { + debug("%s: malloc failed\n", __func__); + return -ENOMEM; + } + + sound_create_square_wave(i2s_uc_priv->samplingrate, data, data_size, + frequency_hz); + + while (msecs >= 1000) { + ret = sound_play(dev, data, data_size); + msecs -= 1000; + } + if (msecs) { + unsigned long size = + (data_size * msecs) / (sizeof(int) * 1000); + + ret = sound_play(dev, data, size); + } + + free(data); + + return ret; +} + +int sound_find_codec_i2s(struct udevice *dev) +{ + struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev); + struct ofnode_phandle_args args; + ofnode node; + int ret; + + /* First the codec */ + node = ofnode_find_subnode(dev_ofnode(dev), "codec"); + if (!ofnode_valid(node)) { + debug("Failed to find /cpu subnode\n"); + return -EINVAL; + } + ret = ofnode_parse_phandle_with_args(node, "sound-dai", + "#sound-dai-cells", 0, 0, &args); + if (ret) { + debug("Cannot find phandle: %d\n", ret); + return ret; + } + ret = uclass_get_device_by_ofnode(UCLASS_AUDIO_CODEC, args.node, + &uc_priv->codec); + if (ret) { + debug("Cannot find codec: %d\n", ret); + return ret; + } + + /* Now the i2s */ + node = ofnode_find_subnode(dev_ofnode(dev), "cpu"); + if (!ofnode_valid(node)) { + debug("Failed to find /cpu subnode\n"); + return -EINVAL; + } + ret = ofnode_parse_phandle_with_args(node, "sound-dai", + "#sound-dai-cells", 0, 0, &args); + if (ret) { + debug("Cannot find phandle: %d\n", ret); + return ret; + } + ret = uclass_get_device_by_ofnode(UCLASS_I2S, args.node, &uc_priv->i2s); + if (ret) { + debug("Cannot find i2s: %d\n", ret); + return ret; + } + debug("Probed sound '%s' with codec '%s' and i2s '%s'\n", dev->name, + uc_priv->codec->name, uc_priv->i2s->name); + + return 0; +} + +UCLASS_DRIVER(sound) = { + .id = UCLASS_SOUND, + .name = "sound", + .per_device_auto_alloc_size = sizeof(struct sound_uc_priv), +}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 1dffb66932..f3bafb3c63 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -84,6 +84,7 @@ enum uclass_id { UCLASS_SERIAL, /* Serial UART */ UCLASS_SIMPLE_BUS, /* Bus with child devices */ UCLASS_SMEM, /* Shared memory interface */ + UCLASS_SOUND, /* Playing simple sounds */ UCLASS_SPI, /* SPI bus */ UCLASS_SPI_FLASH, /* SPI flash */ UCLASS_SPI_GENERIC, /* Generic SPI flash target */ diff --git a/include/sound.h b/include/sound.h index c4ac3193fe..c94d2c3726 100644 --- a/include/sound.h +++ b/include/sound.h @@ -19,12 +19,27 @@ struct sound_codec_info { int i2c_dev_addr; }; -/* +/** + * struct sound_uc_priv - private uclass information about each sound device + * + * This is used to line the codec and i2s together + * + * @codec: Codec that is used for this sound device + * @i2s: I2S bus that is used for this sound device + * @setup_done: true if setup() has been called + */ +struct sound_uc_priv { + struct udevice *codec; + struct udevice *i2s; + int setup_done; +}; + +/** * Generates square wave sound data for 1 second * * @param sample_rate Sample rate in Hz * @param data data buffer pointer - * @param size size of the buffer + * @param size size of the buffer in bytes * @param freq frequency of the wave */ void sound_create_square_wave(uint sample_rate, unsigned short *data, int size, @@ -37,6 +52,56 @@ void sound_create_square_wave(uint sample_rate, unsigned short *data, int size, */ int sound_init(const void *blob); +#ifdef CONFIG_DM_SOUND +/* + * The sound uclass brings together a data transport (currently only I2C) and a + * codec (currently connected over I2C). + */ + +/* Operations for sound */ +struct sound_ops { + /** + * setup() - Set up to play a sound + */ + int (*setup)(struct udevice *dev); + + /** + * play() - Play a beep + * + * @dev: Sound device + * @data: Data buffer to play + * @data_size: Size of data buffer in bytes + * @return 0 if OK, -ve on error + */ + int (*play)(struct udevice *dev, void *data, uint data_size); +}; + +#define sound_get_ops(dev) ((struct sound_ops *)(dev)->driver->ops) + +/** + * setup() - Set up to play a sound + */ +int sound_setup(struct udevice *dev); + +/** + * play() - Play a beep + * + * @dev: Sound device + * @msecs: Duration of beep in milliseconds + * @frequency_hz: Frequency of the beep in Hertz + * @return 0 if OK, -ve on error + */ +int sound_beep(struct udevice *dev, int msecs, int frequency_hz); + +/** + * sound_find_codec_i2s() - Called by sound drivers to locate codec and i2s + * + * This finds the audio codec and i2s devices and puts them in the uclass's + * private data for this device. + */ +int sound_find_codec_i2s(struct udevice *dev); + +#else /* * plays the pcm data buffer in pcm_data.h through i2s1 to make the * sine wave sound @@ -44,5 +109,6 @@ int sound_init(const void *blob); * @return int 0 for success, -1 for error */ int sound_play(uint32_t msec, uint32_t frequency); +#endif /* CONFIG_DM_SOUND */ #endif /* __SOUND__H__ */ diff --git a/test/dm/Makefile b/test/dm/Makefile index 28ede9a669..73f5dcf739 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_AXI) += axi.o obj-$(CONFIG_MISC) += misc.o obj-$(CONFIG_DM_SERIAL) += serial.o obj-$(CONFIG_CPU) += cpu.o +obj-$(CONFIG_DM_SOUND) += sound.o obj-$(CONFIG_TEE) += tee.o obj-$(CONFIG_VIRTIO_SANDBOX) += virtio.o obj-$(CONFIG_DMA) += dma.o diff --git a/test/dm/sound.c b/test/dm/sound.c new file mode 100644 index 0000000000..7d0b36e7a5 --- /dev/null +++ b/test/dm/sound.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2018 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include + +/* Basic test of the sound codec uclass */ +static int dm_test_sound(struct unit_test_state *uts) +{ + struct sound_uc_priv *uc_priv; + struct udevice *dev; + + /* check probe success */ + ut_assertok(uclass_first_device_err(UCLASS_SOUND, &dev)); + uc_priv = dev_get_uclass_priv(dev); + ut_asserteq_str("audio-codec", uc_priv->codec->name); + ut_asserteq_str("i2s", uc_priv->i2s->name); + ut_asserteq(0, sandbox_get_setup_called(dev)); + + ut_assertok(sound_beep(dev, 1, 100)); + ut_asserteq(4560, sandbox_get_sound_sum(dev)); + ut_assertok(sound_beep(dev, 1, 100)); + ut_asserteq(9120, sandbox_get_sound_sum(dev)); + + return 0; +} +DM_TEST(dm_test_sound, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); -- 2.34.1