[alsa-devel] [RFC] Sound/HID: wiimote: add speaker support
David Henningsson
david.henningsson at canonical.com
Fri Apr 26 15:28:06 CEST 2013
On 04/26/2013 03:00 PM, David Herrmann wrote:
> Hi
>
> On Thu, Apr 25, 2013 at 9:11 AM, David Henningsson
> <david.henningsson at 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 at 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 at 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)
>>>
>>
>>
>>
>> --
>> David Henningsson, Canonical Ltd.
>> https://launchpad.net/~diwic
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
--
David Henningsson, Canonical Ltd.
https://launchpad.net/~diwic
More information about the Alsa-devel
mailing list