[alsa-devel] [RFC] Sound/HID: wiimote: add speaker support
Classic Wii Remotes provide a speaker on the device. We can stream PCM data, mute and change volume via special protocol requests and remote audio registers.
The device supports several different formats, but fully understood are only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM. Theoretically, we can set data-rates from 183Hz up to 12MHz, but a realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate. There is no way to read the current position. It would be way too slow, anyway.
Signed-off-by: David Herrmann dh.herrmann@gmail.com --- Hi
I'm reworking large parts of the HID Nintendo Wii Remote driver and wanted to add speaker support. I have never worked with the sound subsystem until now and need some help.
The Wii Remote is a Bluetooth gamepad that features a small speaker on the remote. All information was gathered via reverse-engineering so I cannot tell you any specifics about the hardware, sorry. What I figured out so far is how to set up the speaker, stream data, set volume and mute the speaker.
Supported formats are: signed 8-bit PCM 4-bit Yamaha ADPCM
I implemented only the 8-bit PCM as I couldn't figure out whether the kernel supports the other format?
The wiiproto_* helpers in hid-wiimote-core.c are used to send the requests. Data is streamed as 20-bytes packets and must be sent at a constant rate.
I would really appreciate if someone can have a look at hid-wiimote-speaker.c and help me fill the gaps. I figured out how to write the basic snd_card management and PCM setup, but I am stuck with buffer management now.
I have a function called wiiproto_req_audio() which sends up to 20bytes of PCM data to the device. However, I cannot figure out how to integrate that into the snd_pcm object. The problem is that all other drivers I found have large DMA buffers which are filled whenever an interrupt signals more data. However, I don't have this setup but instead I need a timer (does the sound-core provide that?) which causes the "copy" callback to be called with 20bytes of data at a constant rate.
Any help welcome! And if this whole idea is stupid, please tell me! ;)
Thanks David
drivers/hid/Kconfig | 3 + drivers/hid/Makefile | 3 + drivers/hid/hid-wiimote-core.c | 54 +++- drivers/hid/hid-wiimote-modules.c | 5 + drivers/hid/hid-wiimote-speaker.c | 539 ++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-wiimote.h | 17 ++ 6 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 drivers/hid/hid-wiimote-speaker.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e8ef86c..0b4d7e2 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -715,6 +715,9 @@ config HID_WIIMOTE the Wii U Gamepad) might be supported in the future. But currently support is limited to Bluetooth based devices.
+ If Alsa sound support (CONFIG_SND) is enabled, this driver also provides + a sound card interface for Wii Remote speakers. + If unsure, say N.
To compile this driver as a module, choose M here: the diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 8b7106b..d149e10 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -32,6 +32,9 @@ hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o ifdef CONFIG_DEBUG_FS hid-wiimote-y += hid-wiimote-debug.o endif +ifdef CONFIG_SND + hid-wiimote-y += hid-wiimote-speaker.o +endif
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o obj-$(CONFIG_HID_ACRUX) += hid-axff.o diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index 906c146..53f1e88 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/spinlock.h> +#include <linux/uaccess.h> #include "hid-ids.h" #include "hid-wiimote.h"
@@ -309,8 +310,8 @@ void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags) #define wiiproto_req_weeprom(wdata, os, buf, sz) \ wiiproto_req_wmem((wdata), true, (os), (buf), (sz))
-static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom, - __u32 offset, const __u8 *buf, __u8 size) +void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom, + __u32 offset, const __u8 *buf, __u8 size) { __u8 cmd[22];
@@ -359,6 +360,52 @@ void wiiproto_req_rmem(struct wiimote_data *wdata, bool eeprom, __u32 offset, wiimote_queue(wdata, cmd, sizeof(cmd)); }
+void wiiproto_req_speaker(struct wiimote_data *wdata, bool on) +{ + __u8 cmd[2]; + + cmd[0] = WIIPROTO_REQ_SPEAKER; + cmd[1] = on ? 0x04 : 0x00; + + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + +void wiiproto_req_mute(struct wiimote_data *wdata, bool on) +{ + __u8 cmd[2]; + + cmd[0] = WIIPROTO_REQ_MUTE; + cmd[1] = on ? 0x04 : 0x00; + + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + +/* must not be called from atomic contexts */ +int wiiproto_req_audio_user(struct wiimote_data *wdata, + void __user *buf, size_t len) +{ + unsigned long flags; + __u8 cmd[22]; + + if (len > 20) + len = 20; + + cmd[0] = WIIPROTO_REQ_AUDIO; + cmd[1] = (len & 0xff) << 3; + + if (copy_from_user(&cmd[2], buf, len)) + return -EFAULT; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + /* requries the cmd-mutex to be held */ int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8 size) @@ -570,6 +617,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR, + WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_GEN10] = (const __u8[]){ @@ -582,6 +630,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR, + WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_GEN20] = (const __u8[]){ @@ -595,6 +644,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_ACCEL, WIIMOD_IR, WIIMOD_BUILTIN_MP, + WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_BALANCE_BOARD] = (const __u8[]) { diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index 4ce0e02..7acf721 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c @@ -2072,6 +2072,11 @@ const struct wiimod_ops *wiimod_table[WIIMOD_NUM] = { [WIIMOD_IR] = &wiimod_ir, [WIIMOD_BUILTIN_MP] = &wiimod_builtin_mp, [WIIMOD_NO_MP] = &wiimod_no_mp, +#ifdef CONFIG_SND + [WIIMOD_SPEAKER] = &wiimod_speaker, +#else + [WIIMOD_SPEAKER] = &wiimod_dummy, +#endif };
const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM] = { diff --git a/drivers/hid/hid-wiimote-speaker.c b/drivers/hid/hid-wiimote-speaker.c new file mode 100644 index 0000000..ecec5d9 --- /dev/null +++ b/drivers/hid/hid-wiimote-speaker.c @@ -0,0 +1,539 @@ +/* + * Driver for audio speakers of Nintendo Wii / Wii U peripherals + * Copyright (c) 2012-2013 David Herrmann dh.herrmann@gmail.com + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* + * Audio Speakers + * Some Wii peripherals provide an audio speaker that supports 8bit PCM and + * some other mostly unknown formats. Not all setup options are known, but we + * know how to setup an 8bit PCM or 4bit ADPCM stream and adjust volume. Data + * is sent as 20bytes chunks and needs to be streamed at a constant rate. + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "hid-wiimote.h" + +struct wiimote_speaker { + struct snd_card *card; + unsigned int online : 1; + unsigned int mute : 1; + __u8 volume; +}; + +enum wiimod_speaker_mode { + WIIMOD_SPEAKER_MODE_PCM8, + WIIMOD_SPEAKER_MODE_ADPCM4, +}; + +static int wiimod_speaker_enable(struct wiimote_data *wdata) +{ + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + + if (!speaker->online) { + speaker->online = 1; + wiiproto_req_speaker(wdata, true); + wiiproto_req_mute(wdata, speaker->mute); + } + + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + +static void wiimod_speaker_disable(struct wiimote_data *wdata) +{ + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + + if (speaker->online) { + speaker->online = 0; + wiiproto_req_speaker(wdata, false); + } + + spin_unlock_irqrestore(&wdata->state.lock, flags); +} + +static void wiimod_speaker_set_mute(struct wiimote_data *wdata, bool mute) +{ + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + + if (speaker->mute != mute) { + speaker->mute = mute; + if (speaker->online) + wiiproto_req_mute(wdata, mute); + } + + spin_unlock_irqrestore(&wdata->state.lock, flags); +} + +static void wiimod_speaker_set_volume(struct wiimote_data *wdata, __u8 volume) +{ + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + + if (speaker->volume != volume) { + speaker->volume = volume; + if (speaker->online) + wiiproto_req_wmem(wdata, false, 0xa20005, &volume, + sizeof(volume)); + } + + spin_unlock_irqrestore(&wdata->state.lock, flags); +} + +/* Change speaker configuration. \mode can be one of WIIMOD_SPEAKER_MODE_*, + * \rate is the PCM sample rate and \volume is the requested volume. */ +static int wiimod_speaker_setup(struct wiimote_data *wdata, + __u8 mode, __u16 rate, __s16 volume) +{ + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + __u8 config[7], m, wmem; + __u16 r; + int ret; + + if (!rate) + return -EINVAL; + + switch (mode) { + case WIIMOD_SPEAKER_MODE_PCM8: + r = 12000000ULL / rate; + m = 0x40; + break; + case WIIMOD_SPEAKER_MODE_ADPCM4: + r = 6000000ULL / rate; + m = 0x00; + break; + default: + return -EINVAL; + } + + config[0] = 0x00; + config[1] = m; + config[2] = r & 0x00ff; + config[3] = (r & 0xff00) >> 8; + config[4] = volume & 0xff; + config[5] = 0x00; + config[6] = 0x00; + + wiimote_cmd_acquire_noint(wdata); + + /* mute speaker during setup and read/write volume field */ + spin_lock_irqsave(&wdata->state.lock, flags); + + wiiproto_req_mute(wdata, true); + if (volume < 0) + config[4] = speaker->volume; + else + speaker->volume = volume; + + spin_unlock_irqrestore(&wdata->state.lock, flags); + + /* power speaker */ + wmem = 0x01; + ret = wiimote_cmd_write(wdata, 0xa20009, &wmem, sizeof(wmem)); + if (ret) + goto out_unlock; + + /* prepare setup */ + wmem = 0x08; + ret = wiimote_cmd_write(wdata, 0xa20001, &wmem, sizeof(wmem)); + if (ret) + goto out_unlock; + + /* write configuration */ + ret = wiimote_cmd_write(wdata, 0xa20001, config, sizeof(config)); + if (ret) + goto out_unlock; + + /* enable speaker */ + wmem = 0x01; + ret = wiimote_cmd_write(wdata, 0xa20008, &wmem, sizeof(wmem)); + if (ret) + goto out_unlock; + + /* unmute speaker after setup if not muted */ + spin_lock_irqsave(&wdata->state.lock, flags); + if (!speaker->mute) + wiiproto_req_mute(wdata, false); + spin_unlock_irqrestore(&wdata->state.lock, flags); + +out_unlock: + wiimote_cmd_release(wdata); + return ret; +} + +/* PCM layer */ + +static const struct snd_pcm_hardware wiimod_speaker_playback_hw = { + .info = SNDRV_PCM_INFO_NONINTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 1500, + .rate_max = 4000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 20, + .period_bytes_min = 20, + .period_bytes_max = 20, + .periods_min = 1, + .periods_max = 1, +}; + +static int wiimod_speaker_playback_open(struct snd_pcm_substream *substream) +{ + struct wiimote_data *wdata = snd_pcm_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = wiimod_speaker_playback_hw; + runtime->private_data = wdata; + + return wiimod_speaker_enable(wdata); +} + +static int wiimod_speaker_playback_close(struct snd_pcm_substream *substream) +{ + struct wiimote_data *wdata = snd_pcm_chip(substream); + + wiimod_speaker_disable(wdata); + + return 0; +} + +static int wiimod_speaker_playback_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw) +{ + /* TODO: anything to do here? */ + + return 0; +} + +static int wiimod_speaker_playback_hw_free(struct snd_pcm_substream *subs) +{ + return 0; +} + +static int wiimod_speaker_playback_prepare(struct snd_pcm_substream *subs) +{ + struct wiimote_data *wdata = snd_pcm_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + size_t buf_size, period_size, periods; + int ret; + + /* TODO: If I have set the hw-params to a fixed 20 bytes buffer, do + * I actually need to do any computations here? */ + buf_size = frames_to_bytes(runtime, runtime->buffer_size); + period_size = frames_to_bytes(runtime, runtime->period_size); + periods = runtime->periods; + + switch (runtime->format) { + case SNDRV_PCM_FMTBIT_S8: + ret = wiimod_speaker_setup(wdata, WIIMOD_SPEAKER_MODE_PCM8, + runtime->rate, -1); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int wiimod_speaker_playback_trigger(struct snd_pcm_substream *subs, + int cmd) +{ + struct wiimote_data *wdata = snd_pcm_chip(subs); + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + /* unmute device on start if not muted by user-space */ + spin_lock_irqsave(&wdata->state.lock, flags); + if (!speaker->mute) + wiiproto_req_mute(wdata, false); + spin_unlock_irqrestore(&wdata->state.lock, flags); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + /* mute device when stopping transmission */ + spin_lock_irqsave(&wdata->state.lock, flags); + wiiproto_req_mute(wdata, true); + spin_unlock_irqrestore(&wdata->state.lock, flags); + break; + } + + return 0; +} + +static snd_pcm_uframes_t wiimod_speaker_playback_pointer(struct snd_pcm_substream *subs) +{ + struct wiimote_data *wdata = snd_pcm_chip(subs); + unsigned long pos; + + /* There is no way to read the current position. Even if there was a + * way to do that, it would be so slow that it would be useless. + * TODO: Should we interpolate via a timer? Is this function actually + * needed? When is it called? */ + pos = 0; + + pos %= 20; + + return bytes_to_frames(subs->runtime, pos); +} + +static int wiimod_speaker_playback_copy(struct snd_pcm_substream *subs, + int channel, snd_pcm_uframes_t pos, + void __user *buf, + snd_pcm_uframes_t count) +{ + struct wiimote_data *wdata = snd_pcm_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + + count = frames_to_bytes(runtime, count); + pos = frames_to_bytes(runtime, pos); + + /* TODO: copy from "buf" "count" bytes to "protobuf + pos" + * With the given hw-params, can pos be non-zero? Can count != 20? + * If not, should I simply send a 20byte frame to the device here? */ + + return 0; +} + +static int wiimod_speaker_playback_silence(struct snd_pcm_substream *subs, + int channel, snd_pcm_uframes_t pos, + snd_pcm_uframes_t count) +{ + struct wiimote_data *wdata = snd_pcm_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + + count = frames_to_bytes(runtime, count); + pos = frames_to_bytes(runtime, pos); + + /* TODO: set "count" bytes of "protobuf + pos" to zero + * Can "pos" actually be non-zero with the given hw-params? If not, + * can I simply send a 20byte zeroed frame to the remote device? */ + + return 0; +} + +/* TODO: is there a reason this cannot be "const"? */ +static struct snd_pcm_ops wiimod_speaker_playback_ops = { + .open = wiimod_speaker_playback_open, + .close = wiimod_speaker_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = wiimod_speaker_playback_hw_params, + .hw_free = wiimod_speaker_playback_hw_free, + .prepare = wiimod_speaker_playback_prepare, + .trigger = wiimod_speaker_playback_trigger, + .pointer = wiimod_speaker_playback_pointer, + .copy = wiimod_speaker_playback_copy, + .silence = wiimod_speaker_playback_silence, +}; + +/* volume control */ + +static int wiimod_speaker_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = 0; + info->value.integer.max = 0xff; + + return 0; +} + +static int wiimod_speaker_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *val) +{ + struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol); + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + val->value.integer.value[0] = speaker->volume; + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + +static int wiimod_speaker_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *val) +{ + struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol); + unsigned long value; + + value = val->value.integer.value[0]; + if (value > 0xff) + value = 0xff; + + wiimod_speaker_set_volume(wdata, value); + + return 0; +} + +static const struct snd_kcontrol_new wiimod_speaker_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "PCM Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = wiimod_speaker_volume_info, + .get = wiimod_speaker_volume_get, + .put = wiimod_speaker_volume_put, +}; + +/* mute control */ + +static int wiimod_speaker_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *val) +{ + struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol); + struct wiimote_speaker *speaker = wdata->speaker; + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + val->value.integer.value[0] = !!speaker->mute; + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + +static int wiimod_speaker_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *val) +{ + struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol); + + wiimod_speaker_set_mute(wdata, val->value.integer.value[0]); + + return 0; +} + +/* TODO: Is *_IFACE_CARD the right interface? */ +static const struct snd_kcontrol_new wiimod_speaker_mute = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "PCM Playback Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = wiimod_speaker_mute_get, + .put = wiimod_speaker_mute_put, +}; + +/* initialization and setup */ + +static int wiimod_speaker_probe(const struct wiimod_ops *ops, + struct wiimote_data *wdata) +{ + int ret; + struct wiimote_speaker *speaker; + struct snd_card *card; + struct snd_kcontrol *kcontrol; + struct snd_pcm *pcm; + + /* create sound card device */ + ret = snd_card_create(-1, NULL, THIS_MODULE, + sizeof(struct wiimote_speaker), &card); + if (ret) + return ret; + speaker = card->private_data; + + wdata->speaker = speaker; + speaker->card = card; + speaker->mute = 1; + speaker->volume = 0xff; + strcpy(card->driver, "hid-wiimote"); + strcpy(card->shortname, "wiimote"); + strcpy(card->longname, "Nintendo Wii Remote speaker"); + + /* create volume control */ + kcontrol = snd_ctl_new1(&wiimod_speaker_volume, wdata); + if (!kcontrol) { + ret = -ENOMEM; + goto err_free; + } + + ret = snd_ctl_add(card, kcontrol); + if (ret) { + snd_ctl_free_one(kcontrol); + goto err_free; + } + + /* create mute control */ + kcontrol = snd_ctl_new1(&wiimod_speaker_mute, wdata); + if (!kcontrol) { + ret = -ENOMEM; + goto err_free; + } + + ret = snd_ctl_add(card, kcontrol); + if (ret) { + snd_ctl_free_one(kcontrol); + goto err_free; + } + + /* create PCM sub-device for playback */ + ret = snd_pcm_new(card, "Speaker", 0, 1, 0, &pcm); + if (ret) + goto err_free; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &wiimod_speaker_playback_ops); + pcm->private_data = wdata; + + /* register sound card */ + snd_card_set_dev(card, &wdata->hdev->dev); + ret = snd_card_register(card); + if (ret) + goto err_free; + + return 0; + +err_free: + snd_card_free(card); + wdata->speaker = NULL; + return ret; +} + +static void wiimod_speaker_remove(const struct wiimod_ops *ops, + struct wiimote_data *wdata) +{ + struct wiimote_speaker *speaker = wdata->speaker; + + if (!speaker) + return; + + snd_card_free_when_closed(speaker->card); + wdata->speaker = NULL; +} + +const struct wiimod_ops wiimod_speaker = { + .flags = 0, + .arg = 0, + .probe = wiimod_speaker_probe, + .remove = wiimod_speaker_remove, +}; diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h index 30caef5..4907bc7 100644 --- a/drivers/hid/hid-wiimote.h +++ b/drivers/hid/hid-wiimote.h @@ -148,6 +148,7 @@ struct wiimote_data { struct timer_list timer; struct wiimote_ext *ext; struct wiimote_debug *debug; + struct wiimote_speaker *speaker;
union { struct input_dev *input; @@ -172,6 +173,7 @@ enum wiimod_module { WIIMOD_IR, WIIMOD_BUILTIN_MP, WIIMOD_NO_MP, + WIIMOD_SPEAKER, WIIMOD_NUM, WIIMOD_NULL = WIIMOD_NUM, }; @@ -208,9 +210,12 @@ enum wiiproto_reqs { WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, WIIPROTO_REQ_IR1 = 0x13, + WIIPROTO_REQ_SPEAKER = 0x14, WIIPROTO_REQ_SREQ = 0x15, WIIPROTO_REQ_WMEM = 0x16, WIIPROTO_REQ_RMEM = 0x17, + WIIPROTO_REQ_AUDIO = 0x18, + WIIPROTO_REQ_MUTE = 0x19, WIIPROTO_REQ_IR2 = 0x1a, WIIPROTO_REQ_STATUS = 0x20, WIIPROTO_REQ_DATA = 0x21, @@ -241,6 +246,12 @@ extern void wiiproto_req_status(struct wiimote_data *wdata); extern void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel); extern void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags); extern void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags); +extern void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom, + __u32 offset, const __u8 *buf, __u8 size); +extern void wiiproto_req_speaker(struct wiimote_data *wdata, bool on); +extern void wiiproto_req_mute(struct wiimote_data *wdata, bool on); +extern int wiiproto_req_audio_user(struct wiimote_data *wdata, + void __user *buf, size_t len); extern int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8 size); extern ssize_t wiimote_cmd_read(struct wiimote_data *wdata, __u32 offset, @@ -283,6 +294,12 @@ static inline void wiidebug_deinit(void *u) { }
#endif
+#ifdef CONFIG_SND + +extern const struct wiimod_ops wiimod_speaker; + +#endif + /* requires the state.lock spinlock to be held */ static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int cmd, __u32 opt)
On 04/20/2013 08:14 PM, David Herrmann wrote:
Classic Wii Remotes provide a speaker on the device. We can stream PCM data, mute and change volume via special protocol requests and remote audio registers.
The device supports several different formats, but fully understood are only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM. Theoretically, we can set data-rates from 183Hz up to 12MHz, but a realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate. There is no way to read the current position. It would be way too slow, anyway.
Hi,
Fun project!
I'm not sure I'm qualified to answer, but I would have allocated a buffer of kernel memory, say 64K (or maybe less, since the sample rate is so low). That would be the buffer you expose to userspace, and userspace would set period and buffer sizes according to this buffer.
Your code doesn't really expose if/how you can tell if you're allowed to send 20 more bytes or not. But assuming you have a timer running at a reasonable interval, that code would do something like:
if (ready_to_send_packet_to_wiimote()) { send_20_bytes_to_wiimote(kernel_buffer_ptr + offset); offset += 20; offset %= buffer_size; if (offset % period_size < 20) snd_pcm_period_elapsed(); }
I'm guessing that the hrtimer API would be appropriate, and some kind of RT priority. But I'm not really experienced in that area of kernel programming.
Signed-off-by: David Herrmann dh.herrmann@gmail.com
Hi
I'm reworking large parts of the HID Nintendo Wii Remote driver and wanted to add speaker support. I have never worked with the sound subsystem until now and need some help.
The Wii Remote is a Bluetooth gamepad that features a small speaker on the remote. All information was gathered via reverse-engineering so I cannot tell you any specifics about the hardware, sorry. What I figured out so far is how to set up the speaker, stream data, set volume and mute the speaker.
Supported formats are: signed 8-bit PCM 4-bit Yamaha ADPCM
I implemented only the 8-bit PCM as I couldn't figure out whether the kernel supports the other format?
The wiiproto_* helpers in hid-wiimote-core.c are used to send the requests. Data is streamed as 20-bytes packets and must be sent at a constant rate.
I would really appreciate if someone can have a look at hid-wiimote-speaker.c and help me fill the gaps. I figured out how to write the basic snd_card management and PCM setup, but I am stuck with buffer management now.
I have a function called wiiproto_req_audio() which sends up to 20bytes of PCM data to the device. However, I cannot figure out how to integrate that into the snd_pcm object. The problem is that all other drivers I found have large DMA buffers which are filled whenever an interrupt signals more data. However, I don't have this setup but instead I need a timer (does the sound-core provide that?) which causes the "copy" callback to be called with 20bytes of data at a constant rate.
Any help welcome! And if this whole idea is stupid, please tell me! ;)
Thanks David
drivers/hid/Kconfig | 3 + drivers/hid/Makefile | 3 + drivers/hid/hid-wiimote-core.c | 54 +++- drivers/hid/hid-wiimote-modules.c | 5 + drivers/hid/hid-wiimote-speaker.c | 539 ++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-wiimote.h | 17 ++ 6 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 drivers/hid/hid-wiimote-speaker.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e8ef86c..0b4d7e2 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -715,6 +715,9 @@ config HID_WIIMOTE the Wii U Gamepad) might be supported in the future. But currently support is limited to Bluetooth based devices.
If Alsa sound support (CONFIG_SND) is enabled, this driver also provides
a sound card interface for Wii Remote speakers.
If unsure, say N.
To compile this driver as a module, choose M here: the
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 8b7106b..d149e10 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -32,6 +32,9 @@ hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o ifdef CONFIG_DEBUG_FS hid-wiimote-y += hid-wiimote-debug.o endif +ifdef CONFIG_SND
- hid-wiimote-y += hid-wiimote-speaker.o
+endif
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o obj-$(CONFIG_HID_ACRUX) += hid-axff.o diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index 906c146..53f1e88 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/spinlock.h> +#include <linux/uaccess.h> #include "hid-ids.h" #include "hid-wiimote.h"
@@ -309,8 +310,8 @@ void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags) #define wiiproto_req_weeprom(wdata, os, buf, sz) \ wiiproto_req_wmem((wdata), true, (os), (buf), (sz))
-static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
__u32 offset, const __u8 *buf, __u8 size)
+void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
{ __u8 cmd[22];__u32 offset, const __u8 *buf, __u8 size)
@@ -359,6 +360,52 @@ void wiiproto_req_rmem(struct wiimote_data *wdata, bool eeprom, __u32 offset, wiimote_queue(wdata, cmd, sizeof(cmd)); }
+void wiiproto_req_speaker(struct wiimote_data *wdata, bool on) +{
- __u8 cmd[2];
- cmd[0] = WIIPROTO_REQ_SPEAKER;
- cmd[1] = on ? 0x04 : 0x00;
- wiiproto_keep_rumble(wdata, &cmd[1]);
- wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+void wiiproto_req_mute(struct wiimote_data *wdata, bool on) +{
- __u8 cmd[2];
- cmd[0] = WIIPROTO_REQ_MUTE;
- cmd[1] = on ? 0x04 : 0x00;
- wiiproto_keep_rumble(wdata, &cmd[1]);
- wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+/* must not be called from atomic contexts */ +int wiiproto_req_audio_user(struct wiimote_data *wdata,
void __user *buf, size_t len)
+{
- unsigned long flags;
- __u8 cmd[22];
- if (len > 20)
len = 20;
- cmd[0] = WIIPROTO_REQ_AUDIO;
- cmd[1] = (len & 0xff) << 3;
- if (copy_from_user(&cmd[2], buf, len))
return -EFAULT;
- spin_lock_irqsave(&wdata->state.lock, flags);
- wiiproto_keep_rumble(wdata, &cmd[1]);
- wiimote_queue(wdata, cmd, sizeof(cmd));
- spin_unlock_irqrestore(&wdata->state.lock, flags);
- return 0;
+}
- /* requries the cmd-mutex to be held */ int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8 size)
@@ -570,6 +617,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR,
WIIMOD_NULL, }, [WIIMOTE_DEV_GEN10] = (const __u8[]){WIIMOD_SPEAKER,
@@ -582,6 +630,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR,
WIIMOD_NULL, }, [WIIMOTE_DEV_GEN20] = (const __u8[]){WIIMOD_SPEAKER,
@@ -595,6 +644,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_ACCEL, WIIMOD_IR, WIIMOD_BUILTIN_MP,
WIIMOD_NULL, }, [WIIMOTE_DEV_BALANCE_BOARD] = (const __u8[]) {WIIMOD_SPEAKER,
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index 4ce0e02..7acf721 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c @@ -2072,6 +2072,11 @@ const struct wiimod_ops *wiimod_table[WIIMOD_NUM] = { [WIIMOD_IR] = &wiimod_ir, [WIIMOD_BUILTIN_MP] = &wiimod_builtin_mp, [WIIMOD_NO_MP] = &wiimod_no_mp, +#ifdef CONFIG_SND
- [WIIMOD_SPEAKER] = &wiimod_speaker,
+#else
- [WIIMOD_SPEAKER] = &wiimod_dummy,
+#endif };
const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM] = { diff --git a/drivers/hid/hid-wiimote-speaker.c b/drivers/hid/hid-wiimote-speaker.c new file mode 100644 index 0000000..ecec5d9 --- /dev/null +++ b/drivers/hid/hid-wiimote-speaker.c @@ -0,0 +1,539 @@ +/*
- Driver for audio speakers of Nintendo Wii / Wii U peripherals
- Copyright (c) 2012-2013 David Herrmann dh.herrmann@gmail.com
- */
+/*
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the Free
- Software Foundation; either version 2 of the License, or (at your option)
- any later version.
- */
+/*
- Audio Speakers
- Some Wii peripherals provide an audio speaker that supports 8bit PCM and
- some other mostly unknown formats. Not all setup options are known, but we
- know how to setup an 8bit PCM or 4bit ADPCM stream and adjust volume. Data
- is sent as 20bytes chunks and needs to be streamed at a constant rate.
- */
+#include <linux/device.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "hid-wiimote.h"
+struct wiimote_speaker {
- struct snd_card *card;
- unsigned int online : 1;
- unsigned int mute : 1;
- __u8 volume;
+};
+enum wiimod_speaker_mode {
- WIIMOD_SPEAKER_MODE_PCM8,
- WIIMOD_SPEAKER_MODE_ADPCM4,
+};
+static int wiimod_speaker_enable(struct wiimote_data *wdata) +{
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- spin_lock_irqsave(&wdata->state.lock, flags);
- if (!speaker->online) {
speaker->online = 1;
wiiproto_req_speaker(wdata, true);
wiiproto_req_mute(wdata, speaker->mute);
- }
- spin_unlock_irqrestore(&wdata->state.lock, flags);
- return 0;
+}
+static void wiimod_speaker_disable(struct wiimote_data *wdata) +{
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- spin_lock_irqsave(&wdata->state.lock, flags);
- if (speaker->online) {
speaker->online = 0;
wiiproto_req_speaker(wdata, false);
- }
- spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+static void wiimod_speaker_set_mute(struct wiimote_data *wdata, bool mute) +{
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- spin_lock_irqsave(&wdata->state.lock, flags);
- if (speaker->mute != mute) {
speaker->mute = mute;
if (speaker->online)
wiiproto_req_mute(wdata, mute);
- }
- spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+static void wiimod_speaker_set_volume(struct wiimote_data *wdata, __u8 volume) +{
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- spin_lock_irqsave(&wdata->state.lock, flags);
- if (speaker->volume != volume) {
speaker->volume = volume;
if (speaker->online)
wiiproto_req_wmem(wdata, false, 0xa20005, &volume,
sizeof(volume));
- }
- spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+/* Change speaker configuration. \mode can be one of WIIMOD_SPEAKER_MODE_*,
- \rate is the PCM sample rate and \volume is the requested volume. */
+static int wiimod_speaker_setup(struct wiimote_data *wdata,
__u8 mode, __u16 rate, __s16 volume)
+{
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- __u8 config[7], m, wmem;
- __u16 r;
- int ret;
- if (!rate)
return -EINVAL;
- switch (mode) {
- case WIIMOD_SPEAKER_MODE_PCM8:
r = 12000000ULL / rate;
m = 0x40;
break;
- case WIIMOD_SPEAKER_MODE_ADPCM4:
r = 6000000ULL / rate;
m = 0x00;
break;
- default:
return -EINVAL;
- }
- config[0] = 0x00;
- config[1] = m;
- config[2] = r & 0x00ff;
- config[3] = (r & 0xff00) >> 8;
- config[4] = volume & 0xff;
- config[5] = 0x00;
- config[6] = 0x00;
- wiimote_cmd_acquire_noint(wdata);
- /* mute speaker during setup and read/write volume field */
- spin_lock_irqsave(&wdata->state.lock, flags);
- wiiproto_req_mute(wdata, true);
- if (volume < 0)
config[4] = speaker->volume;
- else
speaker->volume = volume;
- spin_unlock_irqrestore(&wdata->state.lock, flags);
- /* power speaker */
- wmem = 0x01;
- ret = wiimote_cmd_write(wdata, 0xa20009, &wmem, sizeof(wmem));
- if (ret)
goto out_unlock;
- /* prepare setup */
- wmem = 0x08;
- ret = wiimote_cmd_write(wdata, 0xa20001, &wmem, sizeof(wmem));
- if (ret)
goto out_unlock;
- /* write configuration */
- ret = wiimote_cmd_write(wdata, 0xa20001, config, sizeof(config));
- if (ret)
goto out_unlock;
- /* enable speaker */
- wmem = 0x01;
- ret = wiimote_cmd_write(wdata, 0xa20008, &wmem, sizeof(wmem));
- if (ret)
goto out_unlock;
- /* unmute speaker after setup if not muted */
- spin_lock_irqsave(&wdata->state.lock, flags);
- if (!speaker->mute)
wiiproto_req_mute(wdata, false);
- spin_unlock_irqrestore(&wdata->state.lock, flags);
+out_unlock:
- wiimote_cmd_release(wdata);
- return ret;
+}
+/* PCM layer */
+static const struct snd_pcm_hardware wiimod_speaker_playback_hw = {
- .info = SNDRV_PCM_INFO_NONINTERLEAVED,
- .formats = SNDRV_PCM_FMTBIT_S8,
- .rates = SNDRV_PCM_RATE_CONTINUOUS,
- .rate_min = 1500,
- .rate_max = 4000,
- .channels_min = 1,
- .channels_max = 1,
- .buffer_bytes_max = 20,
- .period_bytes_min = 20,
- .period_bytes_max = 20,
- .periods_min = 1,
- .periods_max = 1,
+};
+static int wiimod_speaker_playback_open(struct snd_pcm_substream *substream) +{
- struct wiimote_data *wdata = snd_pcm_chip(substream);
- struct snd_pcm_runtime *runtime = substream->runtime;
- runtime->hw = wiimod_speaker_playback_hw;
- runtime->private_data = wdata;
- return wiimod_speaker_enable(wdata);
+}
+static int wiimod_speaker_playback_close(struct snd_pcm_substream *substream) +{
- struct wiimote_data *wdata = snd_pcm_chip(substream);
- wiimod_speaker_disable(wdata);
- return 0;
+}
+static int wiimod_speaker_playback_hw_params(struct snd_pcm_substream *subs,
struct snd_pcm_hw_params *hw)
+{
- /* TODO: anything to do here? */
- return 0;
+}
+static int wiimod_speaker_playback_hw_free(struct snd_pcm_substream *subs) +{
- return 0;
+}
+static int wiimod_speaker_playback_prepare(struct snd_pcm_substream *subs) +{
- struct wiimote_data *wdata = snd_pcm_chip(subs);
- struct snd_pcm_runtime *runtime = subs->runtime;
- size_t buf_size, period_size, periods;
- int ret;
- /* TODO: If I have set the hw-params to a fixed 20 bytes buffer, do
* I actually need to do any computations here? */
- buf_size = frames_to_bytes(runtime, runtime->buffer_size);
- period_size = frames_to_bytes(runtime, runtime->period_size);
- periods = runtime->periods;
- switch (runtime->format) {
- case SNDRV_PCM_FMTBIT_S8:
ret = wiimod_speaker_setup(wdata, WIIMOD_SPEAKER_MODE_PCM8,
runtime->rate, -1);
break;
- default:
return -EINVAL;
- }
- return ret;
+}
+static int wiimod_speaker_playback_trigger(struct snd_pcm_substream *subs,
int cmd)
+{
- struct wiimote_data *wdata = snd_pcm_chip(subs);
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_START:
/* unmute device on start if not muted by user-space */
spin_lock_irqsave(&wdata->state.lock, flags);
if (!speaker->mute)
wiiproto_req_mute(wdata, false);
spin_unlock_irqrestore(&wdata->state.lock, flags);
break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_STOP:
/* mute device when stopping transmission */
spin_lock_irqsave(&wdata->state.lock, flags);
wiiproto_req_mute(wdata, true);
spin_unlock_irqrestore(&wdata->state.lock, flags);
break;
- }
- return 0;
+}
+static snd_pcm_uframes_t wiimod_speaker_playback_pointer(struct snd_pcm_substream *subs) +{
- struct wiimote_data *wdata = snd_pcm_chip(subs);
- unsigned long pos;
- /* There is no way to read the current position. Even if there was a
* way to do that, it would be so slow that it would be useless.
* TODO: Should we interpolate via a timer? Is this function actually
* needed? When is it called? */
- pos = 0;
- pos %= 20;
- return bytes_to_frames(subs->runtime, pos);
+}
+static int wiimod_speaker_playback_copy(struct snd_pcm_substream *subs,
int channel, snd_pcm_uframes_t pos,
void __user *buf,
snd_pcm_uframes_t count)
+{
- struct wiimote_data *wdata = snd_pcm_chip(subs);
- struct snd_pcm_runtime *runtime = subs->runtime;
- count = frames_to_bytes(runtime, count);
- pos = frames_to_bytes(runtime, pos);
- /* TODO: copy from "buf" "count" bytes to "protobuf + pos"
* With the given hw-params, can pos be non-zero? Can count != 20?
* If not, should I simply send a 20byte frame to the device here? */
- return 0;
+}
+static int wiimod_speaker_playback_silence(struct snd_pcm_substream *subs,
int channel, snd_pcm_uframes_t pos,
snd_pcm_uframes_t count)
+{
- struct wiimote_data *wdata = snd_pcm_chip(subs);
- struct snd_pcm_runtime *runtime = subs->runtime;
- count = frames_to_bytes(runtime, count);
- pos = frames_to_bytes(runtime, pos);
- /* TODO: set "count" bytes of "protobuf + pos" to zero
* Can "pos" actually be non-zero with the given hw-params? If not,
* can I simply send a 20byte zeroed frame to the remote device? */
- return 0;
+}
+/* TODO: is there a reason this cannot be "const"? */ +static struct snd_pcm_ops wiimod_speaker_playback_ops = {
- .open = wiimod_speaker_playback_open,
- .close = wiimod_speaker_playback_close,
- .ioctl = snd_pcm_lib_ioctl,
- .hw_params = wiimod_speaker_playback_hw_params,
- .hw_free = wiimod_speaker_playback_hw_free,
- .prepare = wiimod_speaker_playback_prepare,
- .trigger = wiimod_speaker_playback_trigger,
- .pointer = wiimod_speaker_playback_pointer,
- .copy = wiimod_speaker_playback_copy,
- .silence = wiimod_speaker_playback_silence,
+};
+/* volume control */
+static int wiimod_speaker_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *info)
+{
- info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- info->count = 1;
- info->value.integer.min = 0;
- info->value.integer.max = 0xff;
- return 0;
+}
+static int wiimod_speaker_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
- struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- spin_lock_irqsave(&wdata->state.lock, flags);
- val->value.integer.value[0] = speaker->volume;
- spin_unlock_irqrestore(&wdata->state.lock, flags);
- return 0;
+}
+static int wiimod_speaker_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
- struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
- unsigned long value;
- value = val->value.integer.value[0];
- if (value > 0xff)
value = 0xff;
- wiimod_speaker_set_volume(wdata, value);
- return 0;
+}
+static const struct snd_kcontrol_new wiimod_speaker_volume = {
- .iface = SNDRV_CTL_ELEM_IFACE_CARD,
- .name = "PCM Playback Volume",
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
- .info = wiimod_speaker_volume_info,
- .get = wiimod_speaker_volume_get,
- .put = wiimod_speaker_volume_put,
+};
+/* mute control */
+static int wiimod_speaker_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
- struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
- struct wiimote_speaker *speaker = wdata->speaker;
- unsigned long flags;
- spin_lock_irqsave(&wdata->state.lock, flags);
- val->value.integer.value[0] = !!speaker->mute;
- spin_unlock_irqrestore(&wdata->state.lock, flags);
- return 0;
+}
+static int wiimod_speaker_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
- struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
- wiimod_speaker_set_mute(wdata, val->value.integer.value[0]);
- return 0;
+}
+/* TODO: Is *_IFACE_CARD the right interface? */ +static const struct snd_kcontrol_new wiimod_speaker_mute = {
- .iface = SNDRV_CTL_ELEM_IFACE_CARD,
- .name = "PCM Playback Switch",
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
- .info = snd_ctl_boolean_mono_info,
- .get = wiimod_speaker_mute_get,
- .put = wiimod_speaker_mute_put,
+};
+/* initialization and setup */
+static int wiimod_speaker_probe(const struct wiimod_ops *ops,
struct wiimote_data *wdata)
+{
- int ret;
- struct wiimote_speaker *speaker;
- struct snd_card *card;
- struct snd_kcontrol *kcontrol;
- struct snd_pcm *pcm;
- /* create sound card device */
- ret = snd_card_create(-1, NULL, THIS_MODULE,
sizeof(struct wiimote_speaker), &card);
- if (ret)
return ret;
- speaker = card->private_data;
- wdata->speaker = speaker;
- speaker->card = card;
- speaker->mute = 1;
- speaker->volume = 0xff;
- strcpy(card->driver, "hid-wiimote");
- strcpy(card->shortname, "wiimote");
- strcpy(card->longname, "Nintendo Wii Remote speaker");
- /* create volume control */
- kcontrol = snd_ctl_new1(&wiimod_speaker_volume, wdata);
- if (!kcontrol) {
ret = -ENOMEM;
goto err_free;
- }
- ret = snd_ctl_add(card, kcontrol);
- if (ret) {
snd_ctl_free_one(kcontrol);
goto err_free;
- }
- /* create mute control */
- kcontrol = snd_ctl_new1(&wiimod_speaker_mute, wdata);
- if (!kcontrol) {
ret = -ENOMEM;
goto err_free;
- }
- ret = snd_ctl_add(card, kcontrol);
- if (ret) {
snd_ctl_free_one(kcontrol);
goto err_free;
- }
- /* create PCM sub-device for playback */
- ret = snd_pcm_new(card, "Speaker", 0, 1, 0, &pcm);
- if (ret)
goto err_free;
- snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&wiimod_speaker_playback_ops);
- pcm->private_data = wdata;
- /* register sound card */
- snd_card_set_dev(card, &wdata->hdev->dev);
- ret = snd_card_register(card);
- if (ret)
goto err_free;
- return 0;
+err_free:
- snd_card_free(card);
- wdata->speaker = NULL;
- return ret;
+}
+static void wiimod_speaker_remove(const struct wiimod_ops *ops,
struct wiimote_data *wdata)
+{
- struct wiimote_speaker *speaker = wdata->speaker;
- if (!speaker)
return;
- snd_card_free_when_closed(speaker->card);
- wdata->speaker = NULL;
+}
+const struct wiimod_ops wiimod_speaker = {
- .flags = 0,
- .arg = 0,
- .probe = wiimod_speaker_probe,
- .remove = wiimod_speaker_remove,
+}; diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h index 30caef5..4907bc7 100644 --- a/drivers/hid/hid-wiimote.h +++ b/drivers/hid/hid-wiimote.h @@ -148,6 +148,7 @@ struct wiimote_data { struct timer_list timer; struct wiimote_ext *ext; struct wiimote_debug *debug;
struct wiimote_speaker *speaker;
union { struct input_dev *input;
@@ -172,6 +173,7 @@ enum wiimod_module { WIIMOD_IR, WIIMOD_BUILTIN_MP, WIIMOD_NO_MP,
- WIIMOD_SPEAKER, WIIMOD_NUM, WIIMOD_NULL = WIIMOD_NUM, };
@@ -208,9 +210,12 @@ enum wiiproto_reqs { WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, WIIPROTO_REQ_IR1 = 0x13,
- WIIPROTO_REQ_SPEAKER = 0x14, WIIPROTO_REQ_SREQ = 0x15, WIIPROTO_REQ_WMEM = 0x16, WIIPROTO_REQ_RMEM = 0x17,
- WIIPROTO_REQ_AUDIO = 0x18,
- WIIPROTO_REQ_MUTE = 0x19, WIIPROTO_REQ_IR2 = 0x1a, WIIPROTO_REQ_STATUS = 0x20, WIIPROTO_REQ_DATA = 0x21,
@@ -241,6 +246,12 @@ extern void wiiproto_req_status(struct wiimote_data *wdata); extern void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel); extern void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags); extern void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags); +extern void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
__u32 offset, const __u8 *buf, __u8 size);
+extern void wiiproto_req_speaker(struct wiimote_data *wdata, bool on); +extern void wiiproto_req_mute(struct wiimote_data *wdata, bool on); +extern int wiiproto_req_audio_user(struct wiimote_data *wdata,
extern int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8 size); extern ssize_t wiimote_cmd_read(struct wiimote_data *wdata, __u32 offset,void __user *buf, size_t len);
@@ -283,6 +294,12 @@ static inline void wiidebug_deinit(void *u) { }
#endif
+#ifdef CONFIG_SND
+extern const struct wiimod_ops wiimod_speaker;
+#endif
- /* requires the state.lock spinlock to be held */ static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int cmd, __u32 opt)
Hi
On Thu, Apr 25, 2013 at 9:11 AM, David Henningsson david.henningsson@canonical.com wrote:
On 04/20/2013 08:14 PM, David Herrmann wrote:
Classic Wii Remotes provide a speaker on the device. We can stream PCM data, mute and change volume via special protocol requests and remote audio registers.
The device supports several different formats, but fully understood are only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM. Theoretically, we can set data-rates from 183Hz up to 12MHz, but a realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate. There is no way to read the current position. It would be way too slow, anyway.
Hi,
Fun project!
I'm not sure I'm qualified to answer, but I would have allocated a buffer of kernel memory, say 64K (or maybe less, since the sample rate is so low). That would be the buffer you expose to userspace, and userspace would set period and buffer sizes according to this buffer.
Your code doesn't really expose if/how you can tell if you're allowed to send 20 more bytes or not.
I cannot tell at all. That's the problem. I just send data at the expected rate and let the device deal with it. For instance with 8bit PCM at 2000 Hz that makes one 20byte packet every 10ms. That's 16kbit/s, which should work via Bluetooth l2cap. Even 10x the sample-rate (20kHz) with 160kbit/s should be possible, but the latency gets pretty high that I doubt I can do that with a 20byte buffer. I haven't figured out how big the real buffer of the device is so until then I expect it to be as low as 20 bytes (the proprietary driver does that, too).
If the transmission buffer overflows, I just drop packages so there is currently no easy way for me to see whether there's enough room for a next package or not. But the connection is reliable so once a packet is in the buffer, it's "guaranteed" to be transmitted in order to the device.
What's the expected thing to do if the connection is too slow? Should I pause and resume or drop packets?
But assuming you have a timer running at a reasonable interval, that code would do something like:
if (ready_to_send_packet_to_wiimote()) { send_20_bytes_to_wiimote(kernel_buffer_ptr + offset); offset += 20; offset %= buffer_size; if (offset % period_size < 20) snd_pcm_period_elapsed(); }
Ah, ok, so I just send the data in the timer-interrupt and call snd_pcm_period_elapsed() if the fill-state gets smaller than 20 bytes? I guess snd_pcm_period_elapsed() is atomic so I can expect it to prepare the buffer so the next timer-interrupt is guaranteed to have a valid buffer or a cleared buffer?
I'm guessing that the hrtimer API would be appropriate, and some kind of RT priority. But I'm not really experienced in that area of kernel programming.
Yeah, the hrtimer should be perfect for this. I was just wondering whether the alsa-core already provides an optional timer for the drivers but it seems I need to do it myself.
Thanks a lot for the feedback! Lets see how it works out. David
Signed-off-by: David Herrmann dh.herrmann@gmail.com
Hi
I'm reworking large parts of the HID Nintendo Wii Remote driver and wanted to add speaker support. I have never worked with the sound subsystem until now and need some help.
The Wii Remote is a Bluetooth gamepad that features a small speaker on the remote. All information was gathered via reverse-engineering so I cannot tell you any specifics about the hardware, sorry. What I figured out so far is how to set up the speaker, stream data, set volume and mute the speaker.
Supported formats are: signed 8-bit PCM 4-bit Yamaha ADPCM
I implemented only the 8-bit PCM as I couldn't figure out whether the kernel supports the other format?
The wiiproto_* helpers in hid-wiimote-core.c are used to send the requests. Data is streamed as 20-bytes packets and must be sent at a constant rate.
I would really appreciate if someone can have a look at hid-wiimote-speaker.c and help me fill the gaps. I figured out how to write the basic snd_card management and PCM setup, but I am stuck with buffer management now.
I have a function called wiiproto_req_audio() which sends up to 20bytes of PCM data to the device. However, I cannot figure out how to integrate that into the snd_pcm object. The problem is that all other drivers I found have large DMA buffers which are filled whenever an interrupt signals more data. However, I don't have this setup but instead I need a timer (does the sound-core provide that?) which causes the "copy" callback to be called with 20bytes of data at a constant rate.
Any help welcome! And if this whole idea is stupid, please tell me! ;)
Thanks David
drivers/hid/Kconfig | 3 + drivers/hid/Makefile | 3 + drivers/hid/hid-wiimote-core.c | 54 +++- drivers/hid/hid-wiimote-modules.c | 5 + drivers/hid/hid-wiimote-speaker.c | 539 ++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-wiimote.h | 17 ++ 6 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 drivers/hid/hid-wiimote-speaker.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e8ef86c..0b4d7e2 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -715,6 +715,9 @@ config HID_WIIMOTE the Wii U Gamepad) might be supported in the future. But currently support is limited to Bluetooth based devices.
If Alsa sound support (CONFIG_SND) is enabled, this driver also
provides
a sound card interface for Wii Remote speakers.
If unsure, say N. To compile this driver as a module, choose M here: the
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 8b7106b..d149e10 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -32,6 +32,9 @@ hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o ifdef CONFIG_DEBUG_FS hid-wiimote-y += hid-wiimote-debug.o endif +ifdef CONFIG_SND
hid-wiimote-y += hid-wiimote-speaker.o
+endif
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o obj-$(CONFIG_HID_ACRUX) += hid-axff.o diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index 906c146..53f1e88 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/spinlock.h> +#include <linux/uaccess.h> #include "hid-ids.h" #include "hid-wiimote.h"
@@ -309,8 +310,8 @@ void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags) #define wiiproto_req_weeprom(wdata, os, buf, sz) \ wiiproto_req_wmem((wdata), true, (os), (buf), (sz))
-static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
__u32 offset, const __u8 *buf, __u8 size)
+void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
{ __u8 cmd[22];__u32 offset, const __u8 *buf, __u8 size)
@@ -359,6 +360,52 @@ void wiiproto_req_rmem(struct wiimote_data *wdata, bool eeprom, __u32 offset, wiimote_queue(wdata, cmd, sizeof(cmd)); }
+void wiiproto_req_speaker(struct wiimote_data *wdata, bool on) +{
__u8 cmd[2];
cmd[0] = WIIPROTO_REQ_SPEAKER;
cmd[1] = on ? 0x04 : 0x00;
wiiproto_keep_rumble(wdata, &cmd[1]);
wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+void wiiproto_req_mute(struct wiimote_data *wdata, bool on) +{
__u8 cmd[2];
cmd[0] = WIIPROTO_REQ_MUTE;
cmd[1] = on ? 0x04 : 0x00;
wiiproto_keep_rumble(wdata, &cmd[1]);
wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+/* must not be called from atomic contexts */ +int wiiproto_req_audio_user(struct wiimote_data *wdata,
void __user *buf, size_t len)
+{
unsigned long flags;
__u8 cmd[22];
if (len > 20)
len = 20;
cmd[0] = WIIPROTO_REQ_AUDIO;
cmd[1] = (len & 0xff) << 3;
if (copy_from_user(&cmd[2], buf, len))
return -EFAULT;
spin_lock_irqsave(&wdata->state.lock, flags);
wiiproto_keep_rumble(wdata, &cmd[1]);
wiimote_queue(wdata, cmd, sizeof(cmd));
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
- /* requries the cmd-mutex to be held */ int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8
size) @@ -570,6 +617,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR,
WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_GEN10] = (const __u8[]){
@@ -582,6 +630,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR,
WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_GEN20] = (const __u8[]){
@@ -595,6 +644,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_ACCEL, WIIMOD_IR, WIIMOD_BUILTIN_MP,
WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_BALANCE_BOARD] = (const __u8[]) {
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index 4ce0e02..7acf721 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c @@ -2072,6 +2072,11 @@ const struct wiimod_ops *wiimod_table[WIIMOD_NUM] = { [WIIMOD_IR] = &wiimod_ir, [WIIMOD_BUILTIN_MP] = &wiimod_builtin_mp, [WIIMOD_NO_MP] = &wiimod_no_mp, +#ifdef CONFIG_SND
[WIIMOD_SPEAKER] = &wiimod_speaker,
+#else
[WIIMOD_SPEAKER] = &wiimod_dummy,
+#endif };
const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM] = { diff --git a/drivers/hid/hid-wiimote-speaker.c b/drivers/hid/hid-wiimote-speaker.c new file mode 100644 index 0000000..ecec5d9 --- /dev/null +++ b/drivers/hid/hid-wiimote-speaker.c @@ -0,0 +1,539 @@ +/*
- Driver for audio speakers of Nintendo Wii / Wii U peripherals
- Copyright (c) 2012-2013 David Herrmann dh.herrmann@gmail.com
- */
+/*
- This program is free software; you can redistribute it and/or modify
it
- under the terms of the GNU General Public License as published by the
Free
- Software Foundation; either version 2 of the License, or (at your
option)
- any later version.
- */
+/*
- Audio Speakers
- Some Wii peripherals provide an audio speaker that supports 8bit PCM
and
- some other mostly unknown formats. Not all setup options are known,
but we
- know how to setup an 8bit PCM or 4bit ADPCM stream and adjust volume.
Data
- is sent as 20bytes chunks and needs to be streamed at a constant rate.
- */
+#include <linux/device.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "hid-wiimote.h"
+struct wiimote_speaker {
struct snd_card *card;
unsigned int online : 1;
unsigned int mute : 1;
__u8 volume;
+};
+enum wiimod_speaker_mode {
WIIMOD_SPEAKER_MODE_PCM8,
WIIMOD_SPEAKER_MODE_ADPCM4,
+};
+static int wiimod_speaker_enable(struct wiimote_data *wdata) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (!speaker->online) {
speaker->online = 1;
wiiproto_req_speaker(wdata, true);
wiiproto_req_mute(wdata, speaker->mute);
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
+static void wiimod_speaker_disable(struct wiimote_data *wdata) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (speaker->online) {
speaker->online = 0;
wiiproto_req_speaker(wdata, false);
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+static void wiimod_speaker_set_mute(struct wiimote_data *wdata, bool mute) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (speaker->mute != mute) {
speaker->mute = mute;
if (speaker->online)
wiiproto_req_mute(wdata, mute);
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+static void wiimod_speaker_set_volume(struct wiimote_data *wdata, __u8 volume) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (speaker->volume != volume) {
speaker->volume = volume;
if (speaker->online)
wiiproto_req_wmem(wdata, false, 0xa20005, &volume,
sizeof(volume));
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+/* Change speaker configuration. \mode can be one of WIIMOD_SPEAKER_MODE_*,
- \rate is the PCM sample rate and \volume is the requested volume. */
+static int wiimod_speaker_setup(struct wiimote_data *wdata,
__u8 mode, __u16 rate, __s16 volume)
+{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
__u8 config[7], m, wmem;
__u16 r;
int ret;
if (!rate)
return -EINVAL;
switch (mode) {
case WIIMOD_SPEAKER_MODE_PCM8:
r = 12000000ULL / rate;
m = 0x40;
break;
case WIIMOD_SPEAKER_MODE_ADPCM4:
r = 6000000ULL / rate;
m = 0x00;
break;
default:
return -EINVAL;
}
config[0] = 0x00;
config[1] = m;
config[2] = r & 0x00ff;
config[3] = (r & 0xff00) >> 8;
config[4] = volume & 0xff;
config[5] = 0x00;
config[6] = 0x00;
wiimote_cmd_acquire_noint(wdata);
/* mute speaker during setup and read/write volume field */
spin_lock_irqsave(&wdata->state.lock, flags);
wiiproto_req_mute(wdata, true);
if (volume < 0)
config[4] = speaker->volume;
else
speaker->volume = volume;
spin_unlock_irqrestore(&wdata->state.lock, flags);
/* power speaker */
wmem = 0x01;
ret = wiimote_cmd_write(wdata, 0xa20009, &wmem, sizeof(wmem));
if (ret)
goto out_unlock;
/* prepare setup */
wmem = 0x08;
ret = wiimote_cmd_write(wdata, 0xa20001, &wmem, sizeof(wmem));
if (ret)
goto out_unlock;
/* write configuration */
ret = wiimote_cmd_write(wdata, 0xa20001, config, sizeof(config));
if (ret)
goto out_unlock;
/* enable speaker */
wmem = 0x01;
ret = wiimote_cmd_write(wdata, 0xa20008, &wmem, sizeof(wmem));
if (ret)
goto out_unlock;
/* unmute speaker after setup if not muted */
spin_lock_irqsave(&wdata->state.lock, flags);
if (!speaker->mute)
wiiproto_req_mute(wdata, false);
spin_unlock_irqrestore(&wdata->state.lock, flags);
+out_unlock:
wiimote_cmd_release(wdata);
return ret;
+}
+/* PCM layer */
+static const struct snd_pcm_hardware wiimod_speaker_playback_hw = {
.info = SNDRV_PCM_INFO_NONINTERLEAVED,
.formats = SNDRV_PCM_FMTBIT_S8,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 1500,
.rate_max = 4000,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = 20,
.period_bytes_min = 20,
.period_bytes_max = 20,
.periods_min = 1,
.periods_max = 1,
+};
+static int wiimod_speaker_playback_open(struct snd_pcm_substream *substream) +{
struct wiimote_data *wdata = snd_pcm_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = wiimod_speaker_playback_hw;
runtime->private_data = wdata;
return wiimod_speaker_enable(wdata);
+}
+static int wiimod_speaker_playback_close(struct snd_pcm_substream *substream) +{
struct wiimote_data *wdata = snd_pcm_chip(substream);
wiimod_speaker_disable(wdata);
return 0;
+}
+static int wiimod_speaker_playback_hw_params(struct snd_pcm_substream *subs,
struct snd_pcm_hw_params *hw)
+{
/* TODO: anything to do here? */
return 0;
+}
+static int wiimod_speaker_playback_hw_free(struct snd_pcm_substream *subs) +{
return 0;
+}
+static int wiimod_speaker_playback_prepare(struct snd_pcm_substream *subs) +{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct snd_pcm_runtime *runtime = subs->runtime;
size_t buf_size, period_size, periods;
int ret;
/* TODO: If I have set the hw-params to a fixed 20 bytes buffer,
do
* I actually need to do any computations here? */
buf_size = frames_to_bytes(runtime, runtime->buffer_size);
period_size = frames_to_bytes(runtime, runtime->period_size);
periods = runtime->periods;
switch (runtime->format) {
case SNDRV_PCM_FMTBIT_S8:
ret = wiimod_speaker_setup(wdata,
WIIMOD_SPEAKER_MODE_PCM8,
runtime->rate, -1);
break;
default:
return -EINVAL;
}
return ret;
+}
+static int wiimod_speaker_playback_trigger(struct snd_pcm_substream *subs,
int cmd)
+{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
/* unmute device on start if not muted by user-space */
spin_lock_irqsave(&wdata->state.lock, flags);
if (!speaker->mute)
wiiproto_req_mute(wdata, false);
spin_unlock_irqrestore(&wdata->state.lock, flags);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
/* mute device when stopping transmission */
spin_lock_irqsave(&wdata->state.lock, flags);
wiiproto_req_mute(wdata, true);
spin_unlock_irqrestore(&wdata->state.lock, flags);
break;
}
return 0;
+}
+static snd_pcm_uframes_t wiimod_speaker_playback_pointer(struct snd_pcm_substream *subs) +{
struct wiimote_data *wdata = snd_pcm_chip(subs);
unsigned long pos;
/* There is no way to read the current position. Even if there was
a
* way to do that, it would be so slow that it would be useless.
* TODO: Should we interpolate via a timer? Is this function
actually
* needed? When is it called? */
pos = 0;
pos %= 20;
return bytes_to_frames(subs->runtime, pos);
+}
+static int wiimod_speaker_playback_copy(struct snd_pcm_substream *subs,
int channel, snd_pcm_uframes_t
pos,
void __user *buf,
snd_pcm_uframes_t count)
+{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct snd_pcm_runtime *runtime = subs->runtime;
count = frames_to_bytes(runtime, count);
pos = frames_to_bytes(runtime, pos);
/* TODO: copy from "buf" "count" bytes to "protobuf + pos"
* With the given hw-params, can pos be non-zero? Can count != 20?
* If not, should I simply send a 20byte frame to the device here?
*/
return 0;
+}
+static int wiimod_speaker_playback_silence(struct snd_pcm_substream *subs,
int channel, snd_pcm_uframes_t
pos,
snd_pcm_uframes_t count)
+{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct snd_pcm_runtime *runtime = subs->runtime;
count = frames_to_bytes(runtime, count);
pos = frames_to_bytes(runtime, pos);
/* TODO: set "count" bytes of "protobuf + pos" to zero
* Can "pos" actually be non-zero with the given hw-params? If
not,
* can I simply send a 20byte zeroed frame to the remote device?
*/
return 0;
+}
+/* TODO: is there a reason this cannot be "const"? */ +static struct snd_pcm_ops wiimod_speaker_playback_ops = {
.open = wiimod_speaker_playback_open,
.close = wiimod_speaker_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = wiimod_speaker_playback_hw_params,
.hw_free = wiimod_speaker_playback_hw_free,
.prepare = wiimod_speaker_playback_prepare,
.trigger = wiimod_speaker_playback_trigger,
.pointer = wiimod_speaker_playback_pointer,
.copy = wiimod_speaker_playback_copy,
.silence = wiimod_speaker_playback_silence,
+};
+/* volume control */
+static int wiimod_speaker_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *info)
+{
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
info->count = 1;
info->value.integer.min = 0;
info->value.integer.max = 0xff;
return 0;
+}
+static int wiimod_speaker_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
val->value.integer.value[0] = speaker->volume;
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
+static int wiimod_speaker_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
unsigned long value;
value = val->value.integer.value[0];
if (value > 0xff)
value = 0xff;
wiimod_speaker_set_volume(wdata, value);
return 0;
+}
+static const struct snd_kcontrol_new wiimod_speaker_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "PCM Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = wiimod_speaker_volume_info,
.get = wiimod_speaker_volume_get,
.put = wiimod_speaker_volume_put,
+};
+/* mute control */
+static int wiimod_speaker_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
val->value.integer.value[0] = !!speaker->mute;
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
+static int wiimod_speaker_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
wiimod_speaker_set_mute(wdata, val->value.integer.value[0]);
return 0;
+}
+/* TODO: Is *_IFACE_CARD the right interface? */ +static const struct snd_kcontrol_new wiimod_speaker_mute = {
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "PCM Playback Switch",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_ctl_boolean_mono_info,
.get = wiimod_speaker_mute_get,
.put = wiimod_speaker_mute_put,
+};
+/* initialization and setup */
+static int wiimod_speaker_probe(const struct wiimod_ops *ops,
struct wiimote_data *wdata)
+{
int ret;
struct wiimote_speaker *speaker;
struct snd_card *card;
struct snd_kcontrol *kcontrol;
struct snd_pcm *pcm;
/* create sound card device */
ret = snd_card_create(-1, NULL, THIS_MODULE,
sizeof(struct wiimote_speaker), &card);
if (ret)
return ret;
speaker = card->private_data;
wdata->speaker = speaker;
speaker->card = card;
speaker->mute = 1;
speaker->volume = 0xff;
strcpy(card->driver, "hid-wiimote");
strcpy(card->shortname, "wiimote");
strcpy(card->longname, "Nintendo Wii Remote speaker");
/* create volume control */
kcontrol = snd_ctl_new1(&wiimod_speaker_volume, wdata);
if (!kcontrol) {
ret = -ENOMEM;
goto err_free;
}
ret = snd_ctl_add(card, kcontrol);
if (ret) {
snd_ctl_free_one(kcontrol);
goto err_free;
}
/* create mute control */
kcontrol = snd_ctl_new1(&wiimod_speaker_mute, wdata);
if (!kcontrol) {
ret = -ENOMEM;
goto err_free;
}
ret = snd_ctl_add(card, kcontrol);
if (ret) {
snd_ctl_free_one(kcontrol);
goto err_free;
}
/* create PCM sub-device for playback */
ret = snd_pcm_new(card, "Speaker", 0, 1, 0, &pcm);
if (ret)
goto err_free;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&wiimod_speaker_playback_ops);
pcm->private_data = wdata;
/* register sound card */
snd_card_set_dev(card, &wdata->hdev->dev);
ret = snd_card_register(card);
if (ret)
goto err_free;
return 0;
+err_free:
snd_card_free(card);
wdata->speaker = NULL;
return ret;
+}
+static void wiimod_speaker_remove(const struct wiimod_ops *ops,
struct wiimote_data *wdata)
+{
struct wiimote_speaker *speaker = wdata->speaker;
if (!speaker)
return;
snd_card_free_when_closed(speaker->card);
wdata->speaker = NULL;
+}
+const struct wiimod_ops wiimod_speaker = {
.flags = 0,
.arg = 0,
.probe = wiimod_speaker_probe,
.remove = wiimod_speaker_remove,
+}; diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h index 30caef5..4907bc7 100644 --- a/drivers/hid/hid-wiimote.h +++ b/drivers/hid/hid-wiimote.h @@ -148,6 +148,7 @@ struct wiimote_data { struct timer_list timer; struct wiimote_ext *ext; struct wiimote_debug *debug;
struct wiimote_speaker *speaker; union { struct input_dev *input;
@@ -172,6 +173,7 @@ enum wiimod_module { WIIMOD_IR, WIIMOD_BUILTIN_MP, WIIMOD_NO_MP,
};WIIMOD_SPEAKER, WIIMOD_NUM, WIIMOD_NULL = WIIMOD_NUM,
@@ -208,9 +210,12 @@ enum wiiproto_reqs { WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, WIIPROTO_REQ_IR1 = 0x13,
WIIPROTO_REQ_SPEAKER = 0x14, WIIPROTO_REQ_SREQ = 0x15, WIIPROTO_REQ_WMEM = 0x16, WIIPROTO_REQ_RMEM = 0x17,
WIIPROTO_REQ_AUDIO = 0x18,
WIIPROTO_REQ_MUTE = 0x19, WIIPROTO_REQ_IR2 = 0x1a, WIIPROTO_REQ_STATUS = 0x20, WIIPROTO_REQ_DATA = 0x21,
@@ -241,6 +246,12 @@ extern void wiiproto_req_status(struct wiimote_data *wdata); extern void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel); extern void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags); extern void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags); +extern void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
__u32 offset, const __u8 *buf, __u8 size);
+extern void wiiproto_req_speaker(struct wiimote_data *wdata, bool on); +extern void wiiproto_req_mute(struct wiimote_data *wdata, bool on); +extern int wiiproto_req_audio_user(struct wiimote_data *wdata,
extern int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8void __user *buf, size_t len);
size); extern ssize_t wiimote_cmd_read(struct wiimote_data *wdata, __u32 offset, @@ -283,6 +294,12 @@ static inline void wiidebug_deinit(void *u) { }
#endif
+#ifdef CONFIG_SND
+extern const struct wiimod_ops wiimod_speaker;
+#endif
- /* requires the state.lock spinlock to be held */ static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int
cmd, __u32 opt)
-- David Henningsson, Canonical Ltd. https://launchpad.net/~diwic
On 04/26/2013 03:00 PM, David Herrmann wrote:
Hi
On Thu, Apr 25, 2013 at 9:11 AM, David Henningsson david.henningsson@canonical.com wrote:
On 04/20/2013 08:14 PM, David Herrmann wrote:
Classic Wii Remotes provide a speaker on the device. We can stream PCM data, mute and change volume via special protocol requests and remote audio registers.
The device supports several different formats, but fully understood are only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM. Theoretically, we can set data-rates from 183Hz up to 12MHz, but a realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate. There is no way to read the current position. It would be way too slow, anyway.
Hi,
Fun project!
I'm not sure I'm qualified to answer, but I would have allocated a buffer of kernel memory, say 64K (or maybe less, since the sample rate is so low). That would be the buffer you expose to userspace, and userspace would set period and buffer sizes according to this buffer.
Your code doesn't really expose if/how you can tell if you're allowed to send 20 more bytes or not.
I cannot tell at all. That's the problem. I just send data at the expected rate and let the device deal with it. For instance with 8bit PCM at 2000 Hz that makes one 20byte packet every 10ms. That's 16kbit/s, which should work via Bluetooth l2cap.
Btw, since this is something bluetooth related, have you checked with the bluez/bluetoothd project, so it isn't easier to integrate that way instead of in a kernel driver?
That's how bluetooth audio is usually done.
Even 10x the sample-rate (20kHz) with 160kbit/s should be possible, but the latency gets pretty high that I doubt I can do that with a 20byte buffer. I haven't figured out how big the real buffer of the device is so until then I expect it to be as low as 20 bytes (the proprietary driver does that, too).
If the transmission buffer overflows, I just drop packages so there is currently no easy way for me to see whether there's enough room for a next package or not. But the connection is reliable so once a packet is in the buffer, it's "guaranteed" to be transmitted in order to the device.
What's the expected thing to do if the connection is too slow? Should I pause and resume or drop packets?
Not sure what you mean here. Maybe bluetooth people know this problem better.
But assuming you have a timer running at a reasonable interval, that code would do something like:
if (ready_to_send_packet_to_wiimote()) { send_20_bytes_to_wiimote(kernel_buffer_ptr + offset); offset += 20; offset %= buffer_size; if (offset % period_size < 20) snd_pcm_period_elapsed(); }
Ah, ok, so I just send the data in the timer-interrupt and call snd_pcm_period_elapsed() if the fill-state gets smaller than 20 bytes? I guess snd_pcm_period_elapsed() is atomic so I can expect it to prepare the buffer so the next timer-interrupt is guaranteed to have a valid buffer or a cleared buffer?
snd_pcm_period_elapsed() just tells userspace that userspace can start filling up a new period in the ringbuffer. snd_pcm_period_elapsed() does not prepare anything.
E g, if userspace requests a buffer of 64K and period of 16K, you should call snd_pcm_period_elapsed() every time you cross a period boundary. Perhaps "offset % period_size < 20" can be more clearly written as "(offset / period_size) != (old_offset / period_size)", if that's more understandable?
I'm guessing that the hrtimer API would be appropriate, and some kind of RT priority. But I'm not really experienced in that area of kernel programming.
Yeah, the hrtimer should be perfect for this. I was just wondering whether the alsa-core already provides an optional timer for the drivers but it seems I need to do it myself.
Thanks a lot for the feedback! Lets see how it works out. David
Signed-off-by: David Herrmann dh.herrmann@gmail.com
Hi
I'm reworking large parts of the HID Nintendo Wii Remote driver and wanted to add speaker support. I have never worked with the sound subsystem until now and need some help.
The Wii Remote is a Bluetooth gamepad that features a small speaker on the remote. All information was gathered via reverse-engineering so I cannot tell you any specifics about the hardware, sorry. What I figured out so far is how to set up the speaker, stream data, set volume and mute the speaker.
Supported formats are: signed 8-bit PCM 4-bit Yamaha ADPCM
I implemented only the 8-bit PCM as I couldn't figure out whether the kernel supports the other format?
The wiiproto_* helpers in hid-wiimote-core.c are used to send the requests. Data is streamed as 20-bytes packets and must be sent at a constant rate.
I would really appreciate if someone can have a look at hid-wiimote-speaker.c and help me fill the gaps. I figured out how to write the basic snd_card management and PCM setup, but I am stuck with buffer management now.
I have a function called wiiproto_req_audio() which sends up to 20bytes of PCM data to the device. However, I cannot figure out how to integrate that into the snd_pcm object. The problem is that all other drivers I found have large DMA buffers which are filled whenever an interrupt signals more data. However, I don't have this setup but instead I need a timer (does the sound-core provide that?) which causes the "copy" callback to be called with 20bytes of data at a constant rate.
Any help welcome! And if this whole idea is stupid, please tell me! ;)
Thanks David
drivers/hid/Kconfig | 3 + drivers/hid/Makefile | 3 + drivers/hid/hid-wiimote-core.c | 54 +++- drivers/hid/hid-wiimote-modules.c | 5 + drivers/hid/hid-wiimote-speaker.c | 539 ++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-wiimote.h | 17 ++ 6 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 drivers/hid/hid-wiimote-speaker.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e8ef86c..0b4d7e2 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -715,6 +715,9 @@ config HID_WIIMOTE the Wii U Gamepad) might be supported in the future. But currently support is limited to Bluetooth based devices.
If Alsa sound support (CONFIG_SND) is enabled, this driver also
provides
a sound card interface for Wii Remote speakers.
If unsure, say N. To compile this driver as a module, choose M here: the
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 8b7106b..d149e10 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -32,6 +32,9 @@ hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o ifdef CONFIG_DEBUG_FS hid-wiimote-y += hid-wiimote-debug.o endif +ifdef CONFIG_SND
hid-wiimote-y += hid-wiimote-speaker.o
+endif
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o obj-$(CONFIG_HID_ACRUX) += hid-axff.o diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index 906c146..53f1e88 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/spinlock.h> +#include <linux/uaccess.h> #include "hid-ids.h" #include "hid-wiimote.h"
@@ -309,8 +310,8 @@ void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags) #define wiiproto_req_weeprom(wdata, os, buf, sz) \ wiiproto_req_wmem((wdata), true, (os), (buf), (sz))
-static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
__u32 offset, const __u8 *buf, __u8 size)
+void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
{ __u8 cmd[22];__u32 offset, const __u8 *buf, __u8 size)
@@ -359,6 +360,52 @@ void wiiproto_req_rmem(struct wiimote_data *wdata, bool eeprom, __u32 offset, wiimote_queue(wdata, cmd, sizeof(cmd)); }
+void wiiproto_req_speaker(struct wiimote_data *wdata, bool on) +{
__u8 cmd[2];
cmd[0] = WIIPROTO_REQ_SPEAKER;
cmd[1] = on ? 0x04 : 0x00;
wiiproto_keep_rumble(wdata, &cmd[1]);
wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+void wiiproto_req_mute(struct wiimote_data *wdata, bool on) +{
__u8 cmd[2];
cmd[0] = WIIPROTO_REQ_MUTE;
cmd[1] = on ? 0x04 : 0x00;
wiiproto_keep_rumble(wdata, &cmd[1]);
wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+/* must not be called from atomic contexts */ +int wiiproto_req_audio_user(struct wiimote_data *wdata,
void __user *buf, size_t len)
+{
unsigned long flags;
__u8 cmd[22];
if (len > 20)
len = 20;
cmd[0] = WIIPROTO_REQ_AUDIO;
cmd[1] = (len & 0xff) << 3;
if (copy_from_user(&cmd[2], buf, len))
return -EFAULT;
spin_lock_irqsave(&wdata->state.lock, flags);
wiiproto_keep_rumble(wdata, &cmd[1]);
wiimote_queue(wdata, cmd, sizeof(cmd));
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
- /* requries the cmd-mutex to be held */ int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8
size) @@ -570,6 +617,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR,
WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_GEN10] = (const __u8[]){
@@ -582,6 +630,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_LED4, WIIMOD_ACCEL, WIIMOD_IR,
WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_GEN20] = (const __u8[]){
@@ -595,6 +644,7 @@ static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = { WIIMOD_ACCEL, WIIMOD_IR, WIIMOD_BUILTIN_MP,
WIIMOD_SPEAKER, WIIMOD_NULL, }, [WIIMOTE_DEV_BALANCE_BOARD] = (const __u8[]) {
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index 4ce0e02..7acf721 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c @@ -2072,6 +2072,11 @@ const struct wiimod_ops *wiimod_table[WIIMOD_NUM] = { [WIIMOD_IR] = &wiimod_ir, [WIIMOD_BUILTIN_MP] = &wiimod_builtin_mp, [WIIMOD_NO_MP] = &wiimod_no_mp, +#ifdef CONFIG_SND
[WIIMOD_SPEAKER] = &wiimod_speaker,
+#else
[WIIMOD_SPEAKER] = &wiimod_dummy,
+#endif };
const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM] = { diff --git a/drivers/hid/hid-wiimote-speaker.c b/drivers/hid/hid-wiimote-speaker.c new file mode 100644 index 0000000..ecec5d9 --- /dev/null +++ b/drivers/hid/hid-wiimote-speaker.c @@ -0,0 +1,539 @@ +/*
- Driver for audio speakers of Nintendo Wii / Wii U peripherals
- Copyright (c) 2012-2013 David Herrmann dh.herrmann@gmail.com
- */
+/*
- This program is free software; you can redistribute it and/or modify
it
- under the terms of the GNU General Public License as published by the
Free
- Software Foundation; either version 2 of the License, or (at your
option)
- any later version.
- */
+/*
- Audio Speakers
- Some Wii peripherals provide an audio speaker that supports 8bit PCM
and
- some other mostly unknown formats. Not all setup options are known,
but we
- know how to setup an 8bit PCM or 4bit ADPCM stream and adjust volume.
Data
- is sent as 20bytes chunks and needs to be streamed at a constant rate.
- */
+#include <linux/device.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "hid-wiimote.h"
+struct wiimote_speaker {
struct snd_card *card;
unsigned int online : 1;
unsigned int mute : 1;
__u8 volume;
+};
+enum wiimod_speaker_mode {
WIIMOD_SPEAKER_MODE_PCM8,
WIIMOD_SPEAKER_MODE_ADPCM4,
+};
+static int wiimod_speaker_enable(struct wiimote_data *wdata) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (!speaker->online) {
speaker->online = 1;
wiiproto_req_speaker(wdata, true);
wiiproto_req_mute(wdata, speaker->mute);
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
+static void wiimod_speaker_disable(struct wiimote_data *wdata) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (speaker->online) {
speaker->online = 0;
wiiproto_req_speaker(wdata, false);
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+static void wiimod_speaker_set_mute(struct wiimote_data *wdata, bool mute) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (speaker->mute != mute) {
speaker->mute = mute;
if (speaker->online)
wiiproto_req_mute(wdata, mute);
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+static void wiimod_speaker_set_volume(struct wiimote_data *wdata, __u8 volume) +{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
if (speaker->volume != volume) {
speaker->volume = volume;
if (speaker->online)
wiiproto_req_wmem(wdata, false, 0xa20005, &volume,
sizeof(volume));
}
spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+/* Change speaker configuration. \mode can be one of WIIMOD_SPEAKER_MODE_*,
- \rate is the PCM sample rate and \volume is the requested volume. */
+static int wiimod_speaker_setup(struct wiimote_data *wdata,
__u8 mode, __u16 rate, __s16 volume)
+{
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
__u8 config[7], m, wmem;
__u16 r;
int ret;
if (!rate)
return -EINVAL;
switch (mode) {
case WIIMOD_SPEAKER_MODE_PCM8:
r = 12000000ULL / rate;
m = 0x40;
break;
case WIIMOD_SPEAKER_MODE_ADPCM4:
r = 6000000ULL / rate;
m = 0x00;
break;
default:
return -EINVAL;
}
config[0] = 0x00;
config[1] = m;
config[2] = r & 0x00ff;
config[3] = (r & 0xff00) >> 8;
config[4] = volume & 0xff;
config[5] = 0x00;
config[6] = 0x00;
wiimote_cmd_acquire_noint(wdata);
/* mute speaker during setup and read/write volume field */
spin_lock_irqsave(&wdata->state.lock, flags);
wiiproto_req_mute(wdata, true);
if (volume < 0)
config[4] = speaker->volume;
else
speaker->volume = volume;
spin_unlock_irqrestore(&wdata->state.lock, flags);
/* power speaker */
wmem = 0x01;
ret = wiimote_cmd_write(wdata, 0xa20009, &wmem, sizeof(wmem));
if (ret)
goto out_unlock;
/* prepare setup */
wmem = 0x08;
ret = wiimote_cmd_write(wdata, 0xa20001, &wmem, sizeof(wmem));
if (ret)
goto out_unlock;
/* write configuration */
ret = wiimote_cmd_write(wdata, 0xa20001, config, sizeof(config));
if (ret)
goto out_unlock;
/* enable speaker */
wmem = 0x01;
ret = wiimote_cmd_write(wdata, 0xa20008, &wmem, sizeof(wmem));
if (ret)
goto out_unlock;
/* unmute speaker after setup if not muted */
spin_lock_irqsave(&wdata->state.lock, flags);
if (!speaker->mute)
wiiproto_req_mute(wdata, false);
spin_unlock_irqrestore(&wdata->state.lock, flags);
+out_unlock:
wiimote_cmd_release(wdata);
return ret;
+}
+/* PCM layer */
+static const struct snd_pcm_hardware wiimod_speaker_playback_hw = {
.info = SNDRV_PCM_INFO_NONINTERLEAVED,
.formats = SNDRV_PCM_FMTBIT_S8,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 1500,
.rate_max = 4000,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = 20,
.period_bytes_min = 20,
.period_bytes_max = 20,
.periods_min = 1,
.periods_max = 1,
+};
+static int wiimod_speaker_playback_open(struct snd_pcm_substream *substream) +{
struct wiimote_data *wdata = snd_pcm_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = wiimod_speaker_playback_hw;
runtime->private_data = wdata;
return wiimod_speaker_enable(wdata);
+}
+static int wiimod_speaker_playback_close(struct snd_pcm_substream *substream) +{
struct wiimote_data *wdata = snd_pcm_chip(substream);
wiimod_speaker_disable(wdata);
return 0;
+}
+static int wiimod_speaker_playback_hw_params(struct snd_pcm_substream *subs,
struct snd_pcm_hw_params *hw)
+{
/* TODO: anything to do here? */
return 0;
+}
+static int wiimod_speaker_playback_hw_free(struct snd_pcm_substream *subs) +{
return 0;
+}
+static int wiimod_speaker_playback_prepare(struct snd_pcm_substream *subs) +{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct snd_pcm_runtime *runtime = subs->runtime;
size_t buf_size, period_size, periods;
int ret;
/* TODO: If I have set the hw-params to a fixed 20 bytes buffer,
do
* I actually need to do any computations here? */
buf_size = frames_to_bytes(runtime, runtime->buffer_size);
period_size = frames_to_bytes(runtime, runtime->period_size);
periods = runtime->periods;
switch (runtime->format) {
case SNDRV_PCM_FMTBIT_S8:
ret = wiimod_speaker_setup(wdata,
WIIMOD_SPEAKER_MODE_PCM8,
runtime->rate, -1);
break;
default:
return -EINVAL;
}
return ret;
+}
+static int wiimod_speaker_playback_trigger(struct snd_pcm_substream *subs,
int cmd)
+{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
/* unmute device on start if not muted by user-space */
spin_lock_irqsave(&wdata->state.lock, flags);
if (!speaker->mute)
wiiproto_req_mute(wdata, false);
spin_unlock_irqrestore(&wdata->state.lock, flags);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
/* mute device when stopping transmission */
spin_lock_irqsave(&wdata->state.lock, flags);
wiiproto_req_mute(wdata, true);
spin_unlock_irqrestore(&wdata->state.lock, flags);
break;
}
return 0;
+}
+static snd_pcm_uframes_t wiimod_speaker_playback_pointer(struct snd_pcm_substream *subs) +{
struct wiimote_data *wdata = snd_pcm_chip(subs);
unsigned long pos;
/* There is no way to read the current position. Even if there was
a
* way to do that, it would be so slow that it would be useless.
* TODO: Should we interpolate via a timer? Is this function
actually
* needed? When is it called? */
pos = 0;
pos %= 20;
return bytes_to_frames(subs->runtime, pos);
+}
+static int wiimod_speaker_playback_copy(struct snd_pcm_substream *subs,
int channel, snd_pcm_uframes_t
pos,
void __user *buf,
snd_pcm_uframes_t count)
+{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct snd_pcm_runtime *runtime = subs->runtime;
count = frames_to_bytes(runtime, count);
pos = frames_to_bytes(runtime, pos);
/* TODO: copy from "buf" "count" bytes to "protobuf + pos"
* With the given hw-params, can pos be non-zero? Can count != 20?
* If not, should I simply send a 20byte frame to the device here?
*/
return 0;
+}
+static int wiimod_speaker_playback_silence(struct snd_pcm_substream *subs,
int channel, snd_pcm_uframes_t
pos,
snd_pcm_uframes_t count)
+{
struct wiimote_data *wdata = snd_pcm_chip(subs);
struct snd_pcm_runtime *runtime = subs->runtime;
count = frames_to_bytes(runtime, count);
pos = frames_to_bytes(runtime, pos);
/* TODO: set "count" bytes of "protobuf + pos" to zero
* Can "pos" actually be non-zero with the given hw-params? If
not,
* can I simply send a 20byte zeroed frame to the remote device?
*/
return 0;
+}
+/* TODO: is there a reason this cannot be "const"? */ +static struct snd_pcm_ops wiimod_speaker_playback_ops = {
.open = wiimod_speaker_playback_open,
.close = wiimod_speaker_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = wiimod_speaker_playback_hw_params,
.hw_free = wiimod_speaker_playback_hw_free,
.prepare = wiimod_speaker_playback_prepare,
.trigger = wiimod_speaker_playback_trigger,
.pointer = wiimod_speaker_playback_pointer,
.copy = wiimod_speaker_playback_copy,
.silence = wiimod_speaker_playback_silence,
+};
+/* volume control */
+static int wiimod_speaker_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *info)
+{
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
info->count = 1;
info->value.integer.min = 0;
info->value.integer.max = 0xff;
return 0;
+}
+static int wiimod_speaker_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
val->value.integer.value[0] = speaker->volume;
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
+static int wiimod_speaker_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
unsigned long value;
value = val->value.integer.value[0];
if (value > 0xff)
value = 0xff;
wiimod_speaker_set_volume(wdata, value);
return 0;
+}
+static const struct snd_kcontrol_new wiimod_speaker_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "PCM Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = wiimod_speaker_volume_info,
.get = wiimod_speaker_volume_get,
.put = wiimod_speaker_volume_put,
+};
+/* mute control */
+static int wiimod_speaker_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
struct wiimote_speaker *speaker = wdata->speaker;
unsigned long flags;
spin_lock_irqsave(&wdata->state.lock, flags);
val->value.integer.value[0] = !!speaker->mute;
spin_unlock_irqrestore(&wdata->state.lock, flags);
return 0;
+}
+static int wiimod_speaker_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *val)
+{
struct wiimote_data *wdata = snd_kcontrol_chip(kcontrol);
wiimod_speaker_set_mute(wdata, val->value.integer.value[0]);
return 0;
+}
+/* TODO: Is *_IFACE_CARD the right interface? */ +static const struct snd_kcontrol_new wiimod_speaker_mute = {
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "PCM Playback Switch",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_ctl_boolean_mono_info,
.get = wiimod_speaker_mute_get,
.put = wiimod_speaker_mute_put,
+};
+/* initialization and setup */
+static int wiimod_speaker_probe(const struct wiimod_ops *ops,
struct wiimote_data *wdata)
+{
int ret;
struct wiimote_speaker *speaker;
struct snd_card *card;
struct snd_kcontrol *kcontrol;
struct snd_pcm *pcm;
/* create sound card device */
ret = snd_card_create(-1, NULL, THIS_MODULE,
sizeof(struct wiimote_speaker), &card);
if (ret)
return ret;
speaker = card->private_data;
wdata->speaker = speaker;
speaker->card = card;
speaker->mute = 1;
speaker->volume = 0xff;
strcpy(card->driver, "hid-wiimote");
strcpy(card->shortname, "wiimote");
strcpy(card->longname, "Nintendo Wii Remote speaker");
/* create volume control */
kcontrol = snd_ctl_new1(&wiimod_speaker_volume, wdata);
if (!kcontrol) {
ret = -ENOMEM;
goto err_free;
}
ret = snd_ctl_add(card, kcontrol);
if (ret) {
snd_ctl_free_one(kcontrol);
goto err_free;
}
/* create mute control */
kcontrol = snd_ctl_new1(&wiimod_speaker_mute, wdata);
if (!kcontrol) {
ret = -ENOMEM;
goto err_free;
}
ret = snd_ctl_add(card, kcontrol);
if (ret) {
snd_ctl_free_one(kcontrol);
goto err_free;
}
/* create PCM sub-device for playback */
ret = snd_pcm_new(card, "Speaker", 0, 1, 0, &pcm);
if (ret)
goto err_free;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&wiimod_speaker_playback_ops);
pcm->private_data = wdata;
/* register sound card */
snd_card_set_dev(card, &wdata->hdev->dev);
ret = snd_card_register(card);
if (ret)
goto err_free;
return 0;
+err_free:
snd_card_free(card);
wdata->speaker = NULL;
return ret;
+}
+static void wiimod_speaker_remove(const struct wiimod_ops *ops,
struct wiimote_data *wdata)
+{
struct wiimote_speaker *speaker = wdata->speaker;
if (!speaker)
return;
snd_card_free_when_closed(speaker->card);
wdata->speaker = NULL;
+}
+const struct wiimod_ops wiimod_speaker = {
.flags = 0,
.arg = 0,
.probe = wiimod_speaker_probe,
.remove = wiimod_speaker_remove,
+}; diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h index 30caef5..4907bc7 100644 --- a/drivers/hid/hid-wiimote.h +++ b/drivers/hid/hid-wiimote.h @@ -148,6 +148,7 @@ struct wiimote_data { struct timer_list timer; struct wiimote_ext *ext; struct wiimote_debug *debug;
struct wiimote_speaker *speaker; union { struct input_dev *input;
@@ -172,6 +173,7 @@ enum wiimod_module { WIIMOD_IR, WIIMOD_BUILTIN_MP, WIIMOD_NO_MP,
};WIIMOD_SPEAKER, WIIMOD_NUM, WIIMOD_NULL = WIIMOD_NUM,
@@ -208,9 +210,12 @@ enum wiiproto_reqs { WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, WIIPROTO_REQ_IR1 = 0x13,
WIIPROTO_REQ_SPEAKER = 0x14, WIIPROTO_REQ_SREQ = 0x15, WIIPROTO_REQ_WMEM = 0x16, WIIPROTO_REQ_RMEM = 0x17,
WIIPROTO_REQ_AUDIO = 0x18,
WIIPROTO_REQ_MUTE = 0x19, WIIPROTO_REQ_IR2 = 0x1a, WIIPROTO_REQ_STATUS = 0x20, WIIPROTO_REQ_DATA = 0x21,
@@ -241,6 +246,12 @@ extern void wiiproto_req_status(struct wiimote_data *wdata); extern void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel); extern void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags); extern void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags); +extern void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
__u32 offset, const __u8 *buf, __u8 size);
+extern void wiiproto_req_speaker(struct wiimote_data *wdata, bool on); +extern void wiiproto_req_mute(struct wiimote_data *wdata, bool on); +extern int wiiproto_req_audio_user(struct wiimote_data *wdata,
extern int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, const __u8 *wmem, __u8void __user *buf, size_t len);
size); extern ssize_t wiimote_cmd_read(struct wiimote_data *wdata, __u32 offset, @@ -283,6 +294,12 @@ static inline void wiidebug_deinit(void *u) { }
#endif
+#ifdef CONFIG_SND
+extern const struct wiimod_ops wiimod_speaker;
+#endif
- /* requires the state.lock spinlock to be held */ static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int
cmd, __u32 opt)
-- David Henningsson, Canonical Ltd. https://launchpad.net/~diwic
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Hi
On Fri, Apr 26, 2013 at 3:28 PM, David Henningsson david.henningsson@canonical.com wrote:
On 04/26/2013 03:00 PM, David Herrmann wrote:
Hi
On Thu, Apr 25, 2013 at 9:11 AM, David Henningsson david.henningsson@canonical.com wrote:
On 04/20/2013 08:14 PM, David Herrmann wrote:
Classic Wii Remotes provide a speaker on the device. We can stream PCM data, mute and change volume via special protocol requests and remote audio registers.
The device supports several different formats, but fully understood are only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM. Theoretically, we can set data-rates from 183Hz up to 12MHz, but a realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate. There is no way to read the current position. It would be way too slow, anyway.
Hi,
Fun project!
I'm not sure I'm qualified to answer, but I would have allocated a buffer of kernel memory, say 64K (or maybe less, since the sample rate is so low). That would be the buffer you expose to userspace, and userspace would set period and buffer sizes according to this buffer.
Your code doesn't really expose if/how you can tell if you're allowed to send 20 more bytes or not.
I cannot tell at all. That's the problem. I just send data at the expected rate and let the device deal with it. For instance with 8bit PCM at 2000 Hz that makes one 20byte packet every 10ms. That's 16kbit/s, which should work via Bluetooth l2cap.
Btw, since this is something bluetooth related, have you checked with the bluez/bluetoothd project, so it isn't easier to integrate that way instead of in a kernel driver?
That's how bluetooth audio is usually done.
Hehe, I'm involved in Bluetooth kernel development so I did check that first ;) The thing is, Nintendo did a pretty bad job with audio on the Wiimote. Bluetooth provides SCO channels specifically for audio but Nintendo tunnels audio through an HID connection, yay! On the other hand, the speaker is mostly used for small sound-effects so that explains why they didn't care for higher rates.
As l2cap+HID is handled in the kernel, I would have to provide a char-interface or sysfs PCM-stream or whatever for user-space so they can send HID reports with audio data while the kernel sends it's own HID reports for normal HID operation. That does work, but I thought if I already have all the device handling in the kernel, I should at least try to do it as sound-driver. If it doesn't work out I can always fall back to this option.
Even 10x the sample-rate (20kHz) with 160kbit/s should be possible, but the latency gets pretty high that I doubt I can do that with a 20byte buffer. I haven't figured out how big the real buffer of the device is so until then I expect it to be as low as 20 bytes (the proprietary driver does that, too).
If the transmission buffer overflows, I just drop packages so there is currently no easy way for me to see whether there's enough room for a next package or not. But the connection is reliable so once a packet is in the buffer, it's "guaranteed" to be transmitted in order to the device.
What's the expected thing to do if the connection is too slow? Should I pause and resume or drop packets?
Not sure what you mean here. Maybe bluetooth people know this problem better.
But assuming you have a timer running at a reasonable interval, that code would do something like:
if (ready_to_send_packet_to_wiimote()) { send_20_bytes_to_wiimote(kernel_buffer_ptr + offset); offset += 20; offset %= buffer_size; if (offset % period_size < 20) snd_pcm_period_elapsed(); }
Ah, ok, so I just send the data in the timer-interrupt and call snd_pcm_period_elapsed() if the fill-state gets smaller than 20 bytes? I guess snd_pcm_period_elapsed() is atomic so I can expect it to prepare the buffer so the next timer-interrupt is guaranteed to have a valid buffer or a cleared buffer?
snd_pcm_period_elapsed() just tells userspace that userspace can start filling up a new period in the ringbuffer. snd_pcm_period_elapsed() does not prepare anything.
E g, if userspace requests a buffer of 64K and period of 16K, you should call snd_pcm_period_elapsed() every time you cross a period boundary. Perhaps "offset % period_size < 20" can be more clearly written as "(offset / period_size) != (old_offset / period_size)", if that's more understandable?
Ah, now I understand. I thought the period-size is the maximum transmission size that I can use in the driver (20 in this case). Ok, your explanation helped. I think I understand the "period" concept now.
Apart from that, should I provide an mmap() interface for the buffer so I can map it into the clients address-space? Or should I prevent mmap() and let them use read/write? I guess I wouldn't need the .copy and .silence callbacks if I provide mmap(), right? And a vm_insert_pfn() would be quite simple to do..
What happens if user-space cannot keep up with the rates? Do I just continue sending garbage from the buffer or does the alsa core pause the transmission or silence it until user-space catches up?
Thanks David
I'm guessing that the hrtimer API would be appropriate, and some kind of RT priority. But I'm not really experienced in that area of kernel programming.
Yeah, the hrtimer should be perfect for this. I was just wondering whether the alsa-core already provides an optional timer for the drivers but it seems I need to do it myself.
Thanks a lot for the feedback! Lets see how it works out. David
On 04/26/2013 03:56 PM, David Herrmann wrote:
Hi
On Fri, Apr 26, 2013 at 3:28 PM, David Henningsson david.henningsson@canonical.com wrote:
On 04/26/2013 03:00 PM, David Herrmann wrote:
Hi
On Thu, Apr 25, 2013 at 9:11 AM, David Henningsson david.henningsson@canonical.com wrote:
On 04/20/2013 08:14 PM, David Herrmann wrote:
Classic Wii Remotes provide a speaker on the device. We can stream PCM data, mute and change volume via special protocol requests and remote audio registers.
The device supports several different formats, but fully understood are only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM. Theoretically, we can set data-rates from 183Hz up to 12MHz, but a realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate. There is no way to read the current position. It would be way too slow, anyway.
Hi,
Fun project!
I'm not sure I'm qualified to answer, but I would have allocated a buffer of kernel memory, say 64K (or maybe less, since the sample rate is so low). That would be the buffer you expose to userspace, and userspace would set period and buffer sizes according to this buffer.
Your code doesn't really expose if/how you can tell if you're allowed to send 20 more bytes or not.
I cannot tell at all. That's the problem. I just send data at the expected rate and let the device deal with it. For instance with 8bit PCM at 2000 Hz that makes one 20byte packet every 10ms. That's 16kbit/s, which should work via Bluetooth l2cap.
Btw, since this is something bluetooth related, have you checked with the bluez/bluetoothd project, so it isn't easier to integrate that way instead of in a kernel driver?
That's how bluetooth audio is usually done.
Hehe, I'm involved in Bluetooth kernel development so I did check that first ;)
Cool, does this mean I can contact you if I have problems with Bluetooth chips and kernel drivers? That can come in handy ;-)
The thing is, Nintendo did a pretty bad job with audio on the Wiimote. Bluetooth provides SCO channels specifically for audio but Nintendo tunnels audio through an HID connection, yay! On the other hand, the speaker is mostly used for small sound-effects so that explains why they didn't care for higher rates.
As l2cap+HID is handled in the kernel, I would have to provide a char-interface or sysfs PCM-stream or whatever for user-space so they can send HID reports with audio data while the kernel sends it's own HID reports for normal HID operation. That does work, but I thought if I already have all the device handling in the kernel, I should at least try to do it as sound-driver. If it doesn't work out I can always fall back to this option.
Ok.
Even 10x the sample-rate (20kHz) with 160kbit/s should be possible, but the latency gets pretty high that I doubt I can do that with a 20byte buffer. I haven't figured out how big the real buffer of the device is so until then I expect it to be as low as 20 bytes (the proprietary driver does that, too).
If the transmission buffer overflows, I just drop packages so there is currently no easy way for me to see whether there's enough room for a next package or not. But the connection is reliable so once a packet is in the buffer, it's "guaranteed" to be transmitted in order to the device.
What's the expected thing to do if the connection is too slow? Should I pause and resume or drop packets?
Not sure what you mean here. Maybe bluetooth people know this problem better.
But assuming you have a timer running at a reasonable interval, that code would do something like:
if (ready_to_send_packet_to_wiimote()) { send_20_bytes_to_wiimote(kernel_buffer_ptr + offset); offset += 20; offset %= buffer_size; if (offset % period_size < 20) snd_pcm_period_elapsed(); }
Ah, ok, so I just send the data in the timer-interrupt and call snd_pcm_period_elapsed() if the fill-state gets smaller than 20 bytes? I guess snd_pcm_period_elapsed() is atomic so I can expect it to prepare the buffer so the next timer-interrupt is guaranteed to have a valid buffer or a cleared buffer?
snd_pcm_period_elapsed() just tells userspace that userspace can start filling up a new period in the ringbuffer. snd_pcm_period_elapsed() does not prepare anything.
E g, if userspace requests a buffer of 64K and period of 16K, you should call snd_pcm_period_elapsed() every time you cross a period boundary. Perhaps "offset % period_size < 20" can be more clearly written as "(offset / period_size) != (old_offset / period_size)", if that's more understandable?
Ah, now I understand. I thought the period-size is the maximum transmission size that I can use in the driver (20 in this case). Ok, your explanation helped. I think I understand the "period" concept now.
Apart from that, should I provide an mmap() interface for the buffer so I can map it into the clients address-space? Or should I prevent mmap() and let them use read/write? I guess I wouldn't need the .copy and .silence callbacks if I provide mmap(), right? And a vm_insert_pfn() would be quite simple to do..
I believe a mmap() interface would be more efficient, and thus recommended. And some sound applications depend on an mmap interface being present (although, really, they shouldn't).
There are some mmap helper functions in pcm.h that can probably help you here - as for which callbacks you need to provide, you can probably figure that out as quick as I can, as you're used to kernel development.
What happens if user-space cannot keep up with the rates? Do I just continue sending garbage from the buffer or does the alsa core pause the transmission or silence it until user-space catches up?
If userspace cannot keep up, I believe snd_pcm_period_elapsed() will make sure userspace gets an underrun error.
Thanks David
I'm guessing that the hrtimer API would be appropriate, and some kind of RT priority. But I'm not really experienced in that area of kernel programming.
Yeah, the hrtimer should be perfect for this. I was just wondering whether the alsa-core already provides an optional timer for the drivers but it seems I need to do it myself.
Thanks a lot for the feedback! Lets see how it works out. David
Hi
On Fri, Apr 26, 2013 at 4:21 PM, David Henningsson david.henningsson@canonical.com wrote:
On 04/26/2013 03:56 PM, David Herrmann wrote:
Hi
On Fri, Apr 26, 2013 at 3:28 PM, David Henningsson david.henningsson@canonical.com wrote:
On 04/26/2013 03:00 PM, David Herrmann wrote:
Hi
On Thu, Apr 25, 2013 at 9:11 AM, David Henningsson david.henningsson@canonical.com wrote:
On 04/20/2013 08:14 PM, David Herrmann wrote:
Classic Wii Remotes provide a speaker on the device. We can stream PCM data, mute and change volume via special protocol requests and remote audio registers.
The device supports several different formats, but fully understood are only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM. Theoretically, we can set data-rates from 183Hz up to 12MHz, but a realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate. There is no way to read the current position. It would be way too slow, anyway.
Hi,
Fun project!
I'm not sure I'm qualified to answer, but I would have allocated a buffer of kernel memory, say 64K (or maybe less, since the sample rate is so low). That would be the buffer you expose to userspace, and userspace would set period and buffer sizes according to this buffer.
Your code doesn't really expose if/how you can tell if you're allowed to send 20 more bytes or not.
I cannot tell at all. That's the problem. I just send data at the expected rate and let the device deal with it. For instance with 8bit PCM at 2000 Hz that makes one 20byte packet every 10ms. That's 16kbit/s, which should work via Bluetooth l2cap.
Btw, since this is something bluetooth related, have you checked with the bluez/bluetoothd project, so it isn't easier to integrate that way instead of in a kernel driver?
That's how bluetooth audio is usually done.
Hehe, I'm involved in Bluetooth kernel development so I did check that first ;)
Cool, does this mean I can contact you if I have problems with Bluetooth chips and kernel drivers? That can come in handy ;-)
I don't get paid for this, but you can always try ;)
The thing is, Nintendo did a pretty bad job with audio on the Wiimote. Bluetooth provides SCO channels specifically for audio but Nintendo tunnels audio through an HID connection, yay! On the other hand, the speaker is mostly used for small sound-effects so that explains why they didn't care for higher rates.
As l2cap+HID is handled in the kernel, I would have to provide a char-interface or sysfs PCM-stream or whatever for user-space so they can send HID reports with audio data while the kernel sends it's own HID reports for normal HID operation. That does work, but I thought if I already have all the device handling in the kernel, I should at least try to do it as sound-driver. If it doesn't work out I can always fall back to this option.
Ok.
Even 10x the sample-rate (20kHz) with 160kbit/s should be possible, but the latency gets pretty high that I doubt I can do that with a 20byte buffer. I haven't figured out how big the real buffer of the device is so until then I expect it to be as low as 20 bytes (the proprietary driver does that, too).
If the transmission buffer overflows, I just drop packages so there is currently no easy way for me to see whether there's enough room for a next package or not. But the connection is reliable so once a packet is in the buffer, it's "guaranteed" to be transmitted in order to the device.
What's the expected thing to do if the connection is too slow? Should I pause and resume or drop packets?
Not sure what you mean here. Maybe bluetooth people know this problem better.
But assuming you have a timer running at a reasonable interval, that code would do something like:
if (ready_to_send_packet_to_wiimote()) { send_20_bytes_to_wiimote(kernel_buffer_ptr + offset); offset += 20; offset %= buffer_size; if (offset % period_size < 20) snd_pcm_period_elapsed(); }
Ah, ok, so I just send the data in the timer-interrupt and call snd_pcm_period_elapsed() if the fill-state gets smaller than 20 bytes? I guess snd_pcm_period_elapsed() is atomic so I can expect it to prepare the buffer so the next timer-interrupt is guaranteed to have a valid buffer or a cleared buffer?
snd_pcm_period_elapsed() just tells userspace that userspace can start filling up a new period in the ringbuffer. snd_pcm_period_elapsed() does not prepare anything.
E g, if userspace requests a buffer of 64K and period of 16K, you should call snd_pcm_period_elapsed() every time you cross a period boundary. Perhaps "offset % period_size < 20" can be more clearly written as "(offset / period_size) != (old_offset / period_size)", if that's more understandable?
Ah, now I understand. I thought the period-size is the maximum transmission size that I can use in the driver (20 in this case). Ok, your explanation helped. I think I understand the "period" concept now.
Apart from that, should I provide an mmap() interface for the buffer so I can map it into the clients address-space? Or should I prevent mmap() and let them use read/write? I guess I wouldn't need the .copy and .silence callbacks if I provide mmap(), right? And a vm_insert_pfn() would be quite simple to do..
I believe a mmap() interface would be more efficient, and thus recommended. And some sound applications depend on an mmap interface being present (although, really, they shouldn't).
There are some mmap helper functions in pcm.h that can probably help you here - as for which callbacks you need to provide, you can probably figure that out as quick as I can, as you're used to kernel development.
What happens if user-space cannot keep up with the rates? Do I just continue sending garbage from the buffer or does the alsa core pause the transmission or silence it until user-space catches up?
If userspace cannot keep up, I believe snd_pcm_period_elapsed() will make sure userspace gets an underrun error.
I now got a working implementation (with the snd_pcm_vmalloc_*() helpers) and I can (if I listen closely) recognize the sound that I play on the Wii-Remote, but quality is worse than horrible. I also get weird protocol errors. Seems like I need to do some more reverse-engineering to figure it out. But at least the driver works correctly. I will try to figure out why the remote discards some requests and if everything works out I will repost the driver here.
Thanks for your help! Regards David
participants (2)
-
David Henningsson
-
David Herrmann