[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