diff -urN linux/drivers/bluetooth/hci_usb.c linux.new/drivers/bluetooth/hci_usb.c --- linux/drivers/bluetooth/hci_usb.c 2004-08-23 18:46:43.000000000 +0200 +++ linux.new/drivers/bluetooth/hci_usb.c 2004-08-23 19:22:37.000000000 +0200 @@ -887,8 +887,12 @@ switch (ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { case USB_ENDPOINT_XFER_ISOC: - if (ep->desc.wMaxPacketSize < size || - uif->desc.bAlternateSetting > 2) + /* Use only the 9 byte + "One voice channel with 8 bit encoding" + endpoint until there is support for changing + the endpoint dynamically. See + Bluetooth 1.1 Part H:2, section 2.1 */ + if (ep->desc.wMaxPacketSize != 9) break; size = ep->desc.wMaxPacketSize; diff -urN linux/include/sound/asound.h linux.new/include/sound/asound.h --- linux/include/sound/asound.h 2004-08-23 18:46:43.000000000 +0200 +++ linux.new/include/sound/asound.h 2004-08-23 19:22:37.000000000 +0200 @@ -108,9 +108,10 @@ SNDRV_HWDEP_IFACE_MIXART, /* Digigram miXart cards */ SNDRV_HWDEP_IFACE_USX2Y, /* Tascam US122, US224 & US428 usb */ SNDRV_HWDEP_IFACE_EMUX_WAVETABLE, /* EmuX wavetable */ + SNDRV_HWDEP_IFACE_BT_SCO, /* Bluetooth SCO Headset */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_EMUX_WAVETABLE, + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_BT_SCO, }; struct sndrv_hwdep_info { @@ -135,7 +136,7 @@ struct sndrv_hwdep_dsp_image { unsigned int index; /* W: DSP index */ unsigned char name[64]; /* W: ID (e.g. file name) */ - unsigned char __user *image; /* W: binary image */ + unsigned char __user *image;/* W: binary image */ size_t length; /* W: size of image in bytes */ unsigned long driver_data; /* W: driver-specific data */ }; diff -urN linux/include/sound/btsco.h linux.new/include/sound/btsco.h --- linux/include/sound/btsco.h 1970-01-01 01:00:00.000000000 +0100 +++ linux.new/include/sound/btsco.h 2004-07-19 19:56:20.000000000 +0200 @@ -0,0 +1,5 @@ +#ifndef __SOUND_BT_SCO_H +#define __SOUND_BT_SCO_H + +#define SNDRV_BT_SCO_IOCTL_SET_SCO_SOCKET _IOW ('H', 0x10, int) +#endif /* __SOUND_BT_SCO_H */ diff -urN linux/include/sound/sndmagic.h linux.new/include/sound/sndmagic.h --- linux/include/sound/sndmagic.h 2004-08-23 18:46:43.000000000 +0200 +++ linux.new/include/sound/sndmagic.h 2004-08-23 19:22:37.000000000 +0200 @@ -201,6 +201,8 @@ #define snd_card_harmony_t_magic 0xa15a4300 #define bt87x_t_magic 0xa15a4400 #define pdacf_t_magic 0xa15a4500 +#define snd_card_bt_sco_t_magic 0xa15a4600 +#define snd_card_bt_sco_pcm_t_magic 0xa15a4601 #define vortex_t_magic 0xa15a4601 #define atiixp_t_magic 0xa15a4701 #define amd7930_t_magic 0xa15a4801 diff -urN linux/sound/bluetooth/btsco.c linux.new/sound/bluetooth/btsco.c --- linux/sound/bluetooth/btsco.c 1970-01-01 01:00:00.000000000 +0100 +++ linux.new/sound/bluetooth/btsco.c 2004-07-19 19:56:20.000000000 +0200 @@ -0,0 +1,1023 @@ +/* + * Bluetooth SCO soundcard + * Copyright (c) 2003, 2004 by Jonathan Paisley + * + * Based on dummy.c which is + * Copyright (c) by Jaroslav Kysela + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define chip_t snd_card_bt_sco_t + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#define SNDRV_GET_ID +#include + +MODULE_AUTHOR("Jonathan Paisley "); +MODULE_DESCRIPTION("Bluetooth SCO Headset Soundcard"); +MODULE_LICENSE("GPL"); +MODULE_CLASSES("{sound}"); +MODULE_DEVICES("{{ALSA,Bluetooth SCO Soundcard}}"); + +static char *mod_revision = "$Revision: ??$"; + +#undef dprintk +#if 1 +#define dprintk(fmt...) printk(KERN_INFO "snd-bt-sco: " fmt) +#else +#define dprintk(fmt...) do {} while(0) +#endif + +#define MAX_BUFFER_SIZE (32*1024) + +#define MIXER_ADDR_MASTER 0 +#define MIXER_ADDR_MIC 1 +#define MIXER_ADDR_LAST 1 + +#define MIXER_MASK_MASTER 1 +#define MIXER_MASK_MIC 2 + +#define MIXER_MIN_VOLUME 1 +#define MIXER_MAX_VOLUME 15 + +struct snd_card_bt_sco_pcm; + +typedef struct snd_card_bt_sco { + snd_card_t *card; + spinlock_t mixer_lock; + int mixer_volume[MIXER_ADDR_LAST + 1]; + snd_kcontrol_t *mixer_controls[MIXER_ADDR_LAST + 2]; /* also loopback */ + volatile int loopback; + + spinlock_t mixer_changed_lock; + volatile int mixer_changed; + wait_queue_head_t hwdep_wait; + + int thread_pid; + struct completion thread_done; + + volatile int thread_exit; + struct semaphore thread_sem; + + volatile struct socket *sco_sock; + struct semaphore sock_sem; + wait_queue_head_t wait; + + struct semaphore playback_sem; + struct snd_card_bt_sco_pcm *playback; + struct semaphore capture_sem; + struct snd_card_bt_sco_pcm *capture; +} snd_card_bt_sco_t; + +typedef struct snd_card_bt_sco_pcm { + snd_card_bt_sco_t *bt_sco; + spinlock_t lock; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_bps; /* bytes per second */ + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int pcm_buf_pos; /* position in buffer */ + snd_pcm_substream_t *substream; +} snd_card_bt_sco_pcm_t; + +static snd_card_t *snd_bt_sco_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +static int snd_card_bt_sco_playback_trigger(snd_pcm_substream_t * + substream, int cmd) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_pcm_t *bspcm = + snd_magic_cast(snd_card_bt_sco_pcm_t, runtime->private_data, + return -ENXIO); + snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); + + dprintk("playback_trigger %d\n", cmd); + + if (cmd == SNDRV_PCM_TRIGGER_START) { + bt_sco->playback = bspcm; + dprintk("setting playback to bspcm\n"); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + bt_sco->playback = NULL; + dprintk("setting playback to NULL\n"); + } else { + return -EINVAL; + } + return 0; +} + +static int snd_card_bt_sco_capture_trigger(snd_pcm_substream_t * + substream, int cmd) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_pcm_t *bspcm = + snd_magic_cast(snd_card_bt_sco_pcm_t, runtime->private_data, + return -ENXIO); + snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); + + dprintk("capture_trigger %d\n", cmd); + + if (cmd == SNDRV_PCM_TRIGGER_START) { + bt_sco->capture = bspcm; + dprintk("setting capture to bspcm\n"); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + bt_sco->capture = NULL; + dprintk("setting capture to NULL\n"); + } else { + return -EINVAL; + } + return 0; +} + +static int snd_card_bt_sco_pcm_prepare(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_pcm_t *bspcm = + snd_magic_cast(snd_card_bt_sco_pcm_t, runtime->private_data, + return -ENXIO); + unsigned int bps; + + bps = runtime->rate * runtime->channels; + bps *= snd_pcm_format_width(runtime->format); + bps /= 8; + if (bps <= 0) + return -EINVAL; + bspcm->pcm_bps = bps; + bspcm->pcm_size = snd_pcm_lib_buffer_bytes(substream); + bspcm->pcm_count = snd_pcm_lib_period_bytes(substream); + bspcm->pcm_irq_pos = 0; + bspcm->pcm_buf_pos = 0; + dprintk( + "prepare ok bps: %d size: %d count: %d\n", + bspcm->pcm_bps, bspcm->pcm_size, + bspcm->pcm_count); + return 0; +} + +static int snd_card_bt_sco_playback_prepare(snd_pcm_substream_t * + substream) +{ + return snd_card_bt_sco_pcm_prepare(substream); +} + +static int snd_card_bt_sco_capture_prepare(snd_pcm_substream_t * + substream) +{ + dprintk("capture_prepare\n"); + return snd_card_bt_sco_pcm_prepare(substream); +} + +static void snd_card_bt_sco_pcm_receive(snd_card_bt_sco_pcm_t * bspcm, + unsigned char *data, + unsigned int len) +{ + unsigned long flags; + unsigned int oldptr; + + spin_lock_irqsave(&bspcm->lock, flags); + oldptr = bspcm->pcm_buf_pos; + bspcm->pcm_irq_pos += len; + bspcm->pcm_buf_pos += len; + bspcm->pcm_buf_pos %= bspcm->pcm_size; + spin_unlock_irqrestore(&bspcm->lock, flags); + /* copy a data chunk */ + if (oldptr + len > bspcm->pcm_size) { + unsigned int cnt = bspcm->pcm_size - oldptr; + memcpy(bspcm->substream->runtime->dma_area + oldptr, data, + cnt); + memcpy(bspcm->substream->runtime->dma_area, data + cnt, + len - cnt); + } else { + memcpy(bspcm->substream->runtime->dma_area + oldptr, data, + len); + } + /* update the pointer, call callback if necessary */ + spin_lock_irqsave(&bspcm->lock, flags); + if (bspcm->pcm_irq_pos >= bspcm->pcm_count) { + bspcm->pcm_irq_pos %= bspcm->pcm_count; + spin_unlock_irqrestore(&bspcm->lock, flags); + snd_pcm_period_elapsed(bspcm->substream); + } else + spin_unlock_irqrestore(&bspcm->lock, flags); + +} + +static void snd_card_bt_sco_pcm_send(snd_card_bt_sco_pcm_t * bspcm, + unsigned char *data, + unsigned int len) +{ + unsigned long flags; + unsigned int oldptr; + + spin_lock_irqsave(&bspcm->lock, flags); + oldptr = bspcm->pcm_buf_pos; + bspcm->pcm_irq_pos += len; + bspcm->pcm_buf_pos += len; + bspcm->pcm_buf_pos %= bspcm->pcm_size; + spin_unlock_irqrestore(&bspcm->lock, flags); + /* copy a data chunk */ + if (oldptr + len > bspcm->pcm_size) { + unsigned int cnt = bspcm->pcm_size - oldptr; + memcpy(data, bspcm->substream->runtime->dma_area + oldptr, + cnt); + memcpy(data + cnt, bspcm->substream->runtime->dma_area, + len - cnt); + } else { + memcpy(data, bspcm->substream->runtime->dma_area + oldptr, + len); + } + /* update the pointer, call callback if necessary */ + spin_lock_irqsave(&bspcm->lock, flags); + if (bspcm->pcm_irq_pos >= bspcm->pcm_count) { + bspcm->pcm_irq_pos %= bspcm->pcm_count; + spin_unlock_irqrestore(&bspcm->lock, flags); + snd_pcm_period_elapsed(bspcm->substream); + } else + spin_unlock_irqrestore(&bspcm->lock, flags); +} + +static snd_pcm_uframes_t +snd_card_bt_sco_playback_pointer(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_pcm_t *bspcm = + snd_magic_cast(snd_card_bt_sco_pcm_t, runtime->private_data, + return -ENXIO); + + return bytes_to_frames(runtime, bspcm->pcm_buf_pos); +} + +static snd_pcm_uframes_t +snd_card_bt_sco_capture_pointer(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_pcm_t *bspcm = + snd_magic_cast(snd_card_bt_sco_pcm_t, runtime->private_data, + return -ENXIO); + + return bytes_to_frames(runtime, bspcm->pcm_buf_pos); +} + +static snd_pcm_hardware_t snd_card_bt_sco_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S8 /* | SNDRV_PCM_FMTBIT_S16_LE */ , + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = 24, + .period_bytes_max = MAX_BUFFER_SIZE, + .periods_min = 1, + .periods_max = 4*8000/24, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t snd_card_bt_sco_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S8 /* | SNDRV_PCM_FMTBIT_S16_LE */ , + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = 24, + .period_bytes_max = MAX_BUFFER_SIZE, + .periods_min = 1, + .periods_max = 4*8000/24, + .fifo_size = 0, +}; + +static void snd_card_bt_sco_runtime_free(snd_pcm_runtime_t * runtime) +{ + snd_card_bt_sco_pcm_t *bspcm = + snd_magic_cast(snd_card_bt_sco_pcm_t, runtime->private_data, + return); + snd_magic_kfree(bspcm); +} + +static int snd_card_bt_sco_playback_open(snd_pcm_substream_t * + substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_pcm_t *bspcm; + + dprintk("playback_open\n"); + + bspcm = snd_magic_kcalloc(snd_card_bt_sco_pcm_t, 0, GFP_KERNEL); + if (bspcm == NULL) + return -ENOMEM; + if ((runtime->dma_area = + snd_malloc_pages_fallback(MAX_BUFFER_SIZE, GFP_KERNEL, + &runtime->dma_bytes)) == NULL) { + snd_magic_kfree(bspcm); + return -ENOMEM; + } + spin_lock_init(&bspcm->lock); + bspcm->substream = substream; + runtime->private_data = bspcm; + runtime->private_free = snd_card_bt_sco_runtime_free; + runtime->hw = snd_card_bt_sco_playback; + return 0; +} + +static int snd_card_bt_sco_capture_open(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_pcm_t *bspcm; + + dprintk("capture_open\n"); + + bspcm = snd_magic_kcalloc(snd_card_bt_sco_pcm_t, 0, GFP_KERNEL); + if (bspcm == NULL) + return -ENOMEM; + if ((runtime->dma_area = + snd_malloc_pages_fallback(MAX_BUFFER_SIZE, GFP_KERNEL, + &runtime->dma_bytes)) == NULL) { + snd_magic_kfree(bspcm); + return -ENOMEM; + } + memset(runtime->dma_area, 0, runtime->dma_bytes); + spin_lock_init(&bspcm->lock); + bspcm->substream = substream; + runtime->private_data = bspcm; + runtime->private_free = snd_card_bt_sco_runtime_free; + runtime->hw = snd_card_bt_sco_capture; + return 0; +} + +static int snd_card_bt_sco_playback_close(snd_pcm_substream_t * + substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); + + snd_assert(bt_sco->playback == NULL, ;); + + /* Ensure any references to this in our thread have finished */ + down(&bt_sco->playback_sem); + up(&bt_sco->playback_sem); + + snd_free_pages(runtime->dma_area, runtime->dma_bytes); + return 0; +} + +static int snd_card_bt_sco_capture_close(snd_pcm_substream_t * + substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + struct snd_card_bt_sco *bt_sco = (struct snd_card_bt_sco *) substream->private_data; + + snd_assert(bt_sco->capture == NULL, ;); + + /* Ensure any references to this in our thread have finished */ + down(&bt_sco->capture_sem); + up(&bt_sco->capture_sem); + + snd_free_pages(runtime->dma_area, runtime->dma_bytes); + return 0; +} + +static snd_pcm_ops_t snd_card_bt_sco_playback_ops = { + .open = snd_card_bt_sco_playback_open, + .close = snd_card_bt_sco_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = snd_card_bt_sco_playback_prepare, + .trigger = snd_card_bt_sco_playback_trigger, + .pointer = snd_card_bt_sco_playback_pointer, +}; + +static snd_pcm_ops_t snd_card_bt_sco_capture_ops = { + .open = snd_card_bt_sco_capture_open, + .close = snd_card_bt_sco_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = snd_card_bt_sco_capture_prepare, + .trigger = snd_card_bt_sco_capture_trigger, + .pointer = snd_card_bt_sco_capture_pointer, +}; + +static int __init snd_card_bt_sco_pcm(snd_card_bt_sco_t * bt_sco) +{ + snd_pcm_t *pcm; + int err; + + if ((err = + snd_pcm_new(bt_sco->card, "Bluetooth SCO PCM", 0, 1, 1, + &pcm)) < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_bt_sco_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_bt_sco_capture_ops); + pcm->private_data = bt_sco; + pcm->info_flags = 0; + strcpy(pcm->name, "BT SCO PCM"); + return 0; +} + +#define BT_SCO_VOLUME(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_bt_sco_volume_info, \ + .get = snd_bt_sco_volume_get, .put = snd_bt_sco_volume_put, \ + .private_value = addr } + +static int snd_bt_sco_volume_info(snd_kcontrol_t * kcontrol, + snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = MIXER_MIN_VOLUME; + uinfo->value.integer.max = MIXER_MAX_VOLUME; + return 0; +} + +static int snd_bt_sco_volume_get(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_card_bt_sco_t *bt_sco = _snd_kcontrol_chip(kcontrol); + unsigned long flags; + int addr = kcontrol->private_value; + + spin_lock_irqsave(&bt_sco->mixer_lock, flags); + ucontrol->value.integer.value[0] = bt_sco->mixer_volume[addr]; + spin_unlock_irqrestore(&bt_sco->mixer_lock, flags); + return 0; +} + +static int snd_bt_sco_volume_put(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_card_bt_sco_t *bt_sco = _snd_kcontrol_chip(kcontrol); + unsigned long flags; + int changed, addr = kcontrol->private_value; + int vol; + + vol = ucontrol->value.integer.value[0]; + if (vol < MIXER_MIN_VOLUME) + vol =MIXER_MIN_VOLUME; + if (vol > MIXER_MAX_VOLUME) + vol = MIXER_MAX_VOLUME; + spin_lock_irqsave(&bt_sco->mixer_lock, flags); + changed = bt_sco->mixer_volume[addr] != vol; + bt_sco->mixer_volume[addr] = vol; + spin_unlock_irqrestore(&bt_sco->mixer_lock, flags); + if (changed) { + spin_lock_irqsave(&bt_sco->mixer_changed_lock, flags); + bt_sco->mixer_changed = 1; + spin_unlock_irqrestore(&bt_sco->mixer_changed_lock, flags); + wake_up(&bt_sco->hwdep_wait); + } + return changed; +} + +static int snd_bt_sco_boolean_info(snd_kcontrol_t *kcontrol, + snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_bt_sco_loopback_get(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_card_bt_sco_t *bt_sco = _snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&bt_sco->mixer_lock, flags); + ucontrol->value.integer.value[0] = bt_sco->loopback; + spin_unlock_irqrestore(&bt_sco->mixer_lock, flags); + return 0; +} + +static int snd_bt_sco_loopback_put(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_card_bt_sco_t *bt_sco = _snd_kcontrol_chip(kcontrol); + unsigned long flags; + int changed; + int loopback; + + loopback = !!ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&bt_sco->mixer_lock, flags); + changed = bt_sco->loopback != loopback; + bt_sco->loopback = loopback; + spin_unlock_irqrestore(&bt_sco->mixer_lock, flags); + return changed; +} + +#define BT_SCO_CONTROLS (sizeof(snd_bt_sco_controls)/sizeof(snd_kcontrol_new_t)) + +static snd_kcontrol_new_t snd_bt_sco_controls[] = { + BT_SCO_VOLUME("Master Volume", 0, MIXER_ADDR_MASTER), + BT_SCO_VOLUME("Mic Volume", 0, MIXER_ADDR_MIC), + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Switch", + .index = 0, + .info = snd_bt_sco_boolean_info, + .get = snd_bt_sco_loopback_get, + .put = snd_bt_sco_loopback_put, + } +}; + +int __init snd_card_bt_sco_new_mixer(snd_card_bt_sco_t *bt_sco) +{ + snd_card_t *card = bt_sco->card; + + unsigned int idx; + int err; + + snd_assert(bt_sco != NULL, return -EINVAL); + spin_lock_init(&bt_sco->mixer_lock); + strcpy(card->mixername, "BT Headset Mixer"); + + for (idx = 0; idx < BT_SCO_CONTROLS; idx++) { + bt_sco->mixer_controls[idx] = + snd_ctl_new1(&snd_bt_sco_controls[idx],bt_sco); + + if ((err = snd_ctl_add(card,bt_sco->mixer_controls[idx])) < 0) + return err; + } + return 0; +} + +static int snd_card_bt_open(snd_hwdep_t * hw, struct file *file) +{ + return 0; +} + +static int snd_card_bt_release(snd_hwdep_t * hw, struct file *file) +{ + return 0; +} + + +static int snd_card_bt_ioctl(snd_hwdep_t * hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + snd_card_bt_sco_t *bt_sco = + snd_magic_cast(snd_card_bt_sco_t,hw->card->private_data,return -ENXIO); + struct socket *sock; + int err = -EINVAL; + int fd = arg; + + switch (cmd) { + case SNDRV_BT_SCO_IOCTL_SET_SCO_SOCKET: + err = 0; + /* Interrupt any socket operations, so that we may + * change the socket */ + down(&bt_sco->sock_sem); + kill_proc(bt_sco->thread_pid, SIGINT, 1); + if (bt_sco->sco_sock) { + dprintk("Disposing of previous socket count %d\n",file_count(bt_sco->sco_sock->file)); + /* Extra brackets needed here since sockfd_put is a poorly implemented macro */ + sockfd_put(((struct socket*) bt_sco->sco_sock)); + + bt_sco->sco_sock = NULL; + } + + if (fd>=0) { + err = -EINVAL; + sock = sockfd_lookup(fd, &err); + if (sock) { + if (sock->sk->sk_family == PF_BLUETOOTH && + sock->sk->sk_protocol == BTPROTO_SCO) { + bt_sco->sco_sock = sock; + wake_up(&bt_sco->wait); + err = 0; + } else { + dprintk("Not a bluetooth SCO socket %d:%d\n", + sock->sk->sk_family, + sock->sk->sk_protocol); + sockfd_put(sock); + } + } + } + up(&bt_sco->sock_sem); + break; + } + return err; +} + +static long snd_card_bt_write(snd_hwdep_t * hw, const char *buf, long count, + loff_t * offset) +{ + snd_card_bt_sco_t *bt_sco = + snd_magic_cast(snd_card_bt_sco_t,hw->card->private_data,return -ENXIO); + int mixer_volume[MIXER_ADDR_LAST + 1]; + int retval; + int i; + + if (count != sizeof(mixer_volume)) + return -EINVAL; + + if (copy_from_user(mixer_volume,buf,sizeof(mixer_volume))) + return -EFAULT; + + retval = sizeof(mixer_volume); + + spin_lock_irq(&bt_sco->mixer_lock); + for (i=0;i<=MIXER_ADDR_LAST;i++) { + int vol = mixer_volume[i]; + if (vol>MIXER_MAX_VOLUME) + vol = MIXER_MAX_VOLUME; + if (volmixer_volume[i] != vol) { + bt_sco->mixer_volume[i] = vol; + snd_ctl_notify(bt_sco->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &bt_sco->mixer_controls[i]->id); + } + } + spin_unlock_irq(&bt_sco->mixer_lock); + + return retval; +} + +static long snd_card_bt_read(snd_hwdep_t * hw, char *buf, long count, + loff_t * offset) +{ + snd_card_bt_sco_t *bt_sco = + snd_magic_cast(snd_card_bt_sco_t,hw->card->private_data,return -ENXIO); + DECLARE_WAITQUEUE(wait, current); + ssize_t retval; + + if (count != sizeof(bt_sco->mixer_volume)) + return -EINVAL; + + add_wait_queue(&bt_sco->hwdep_wait, &wait); + current->state = TASK_INTERRUPTIBLE; + do { + int changed; + spin_lock_irq(&bt_sco->mixer_changed_lock); + changed = bt_sco->mixer_changed; + bt_sco->mixer_changed = 0; + spin_unlock_irq(&bt_sco->mixer_changed_lock); + + if (changed != 0) + break; + + if (signal_pending(current)) { + retval = -ERESTARTSYS; + goto out; + } + schedule(); + } while (1); + if (copy_to_user(buf, bt_sco->mixer_volume, sizeof(bt_sco->mixer_volume))) + retval = -EFAULT; + else retval = sizeof(bt_sco->mixer_volume); + + out: + current->state = TASK_RUNNING; + remove_wait_queue(&bt_sco->hwdep_wait, &wait); + return retval; +} + +static unsigned int snd_card_bt_poll(snd_hwdep_t * hw, + struct file *file, + poll_table * wait) +{ + snd_card_bt_sco_t *bt_sco = + snd_magic_cast(snd_card_bt_sco_t,hw->card->private_data,return 0); + int changed; + + poll_wait(file, &bt_sco->hwdep_wait, wait); + + spin_lock_irq(&bt_sco->mixer_changed_lock); + changed = bt_sco->mixer_changed; + spin_unlock_irq(&bt_sco->mixer_changed_lock); + + if (changed != 0) + return POLLIN | POLLRDNORM; + return 0; +} + +static int snd_card_bt_sco_thread(void *data) +{ + snd_card_t *card = (snd_card_t *) data; + snd_card_bt_sco_t *bt_sco = + snd_magic_cast(snd_card_bt_sco_t,card->private_data,return 0); + struct socket *sock; + int len; +#define BUF_SIZE 256 + unsigned char buf[BUF_SIZE]; + struct msghdr msg; + struct iovec iov; + sigset_t unblocked; + + lock_kernel(); + + daemonize("snd-bt-scod"); + sigemptyset(&unblocked); + sigaddset(&unblocked,SIGINT); + sigaddset(&unblocked,SIGTERM); + sigprocmask(SIG_UNBLOCK, &unblocked, NULL); + + /* Pretend so that copy_to_user and friends work */ + set_fs(KERNEL_DS); + + dprintk("snd-bt-scod thread starting\n"); + up(&bt_sco->thread_sem); + + do { + if (signal_pending(current)) + flush_signals(current); + + /* This may be woken up by a wake_up() when + * a new socket is installed, or by a signal. + * Signals are sent to terminate the thread, + * in which case thread_exit is set, and to force + * recvmesg() to wake up (from the ioctl handler) + */ + wait_event_interruptible(bt_sco->wait, + bt_sco->sco_sock!=0); + if (bt_sco->thread_exit) + break; + + down(&bt_sco->sock_sem); + sock = (struct socket*) bt_sco->sco_sock; + if (sock) + get_file(sock->file); + up(&bt_sco->sock_sem); + + if (!sock) + continue; + + /* We have a socket, let's read from it and write to it... */ + + memset(&msg,0,sizeof(msg)); + msg.msg_iov = &iov; + iov.iov_base = buf; + iov.iov_len = BUF_SIZE; + + /* This will block until we receive data or a signal */ + len = sock_recvmsg(sock, &msg, BUF_SIZE, 0); + if (len > 0) { + + down(&bt_sco->capture_sem); + if (bt_sco->capture) { + snd_card_bt_sco_pcm_receive + (bt_sco->capture, + buf, len); + } + up(&bt_sco->capture_sem); + + down(&bt_sco->playback_sem); + + if (bt_sco->playback || !bt_sco->loopback) { + memset(buf, 0, len); +#if 0 + /* fill with tone instead of silence */ + int i; + + for (i=0;iplayback) { + snd_card_bt_sco_pcm_send + (bt_sco->playback, + buf, len); + + /* Strangely, when the device is open but no audio is + being written by the app, there's an occasional glitch + in the silence data. This hack eliminates it. */ + + int i; + int notzero=-1; + for (i=0;i=0) + break; + notzero = i; + } + } + if (notzero>=0 && i>=len) { + buf[notzero] = 0; + } + } + up(&bt_sco->playback_sem); + +#if 0 + /* This chunk of code lets us record (using arecord) + what data alsa is sending out. + + e.g., when idle, we'd expect something like: + + 8080 8080 8080 8080 8483 8281 8182 8384 + 8080 8080 8080 8080 8080 8080 8080 8080 + 8080 8080 8080 8080 8483 8281 8182 8384 + 8080 8080 8080 8080 8080 8080 8080 8080 + + (this is from 'xxd' of a wav file, that data in + which is unsigned, whereas we are dealing with signed). + */ + + down(&bt_sco->capture_sem); + if (bt_sco->capture) { + snd_card_bt_sco_pcm_receive + (bt_sco->capture, + "\001\002\003\004", 4); + snd_card_bt_sco_pcm_receive + (bt_sco->capture, + buf, len); + snd_card_bt_sco_pcm_receive + (bt_sco->capture, + "\004\003\002\001", 4); + } + up(&bt_sco->capture_sem); +#endif + msg.msg_flags = 0; + msg.msg_iov = &iov; + iov.iov_base = buf; + iov.iov_len = BUF_SIZE; + sock_sendmsg(sock, &msg, len); + } + + /* Expect this to be 3 because we (this thead) have a copy, + the driver process keeps one, and the app has the socket open. + */ + if (file_count(sock->file) != 3) { + dprintk("file_count is %d (expected 3)\n",file_count(sock->file)); + } + fput(sock->file); + + schedule(); + } while (!bt_sco->thread_exit); + + dprintk("thread exiting\n"); + + unlock_kernel(); + complete_and_exit(&bt_sco->thread_done, 0); +} + +static void snd_card_bt_private_free(snd_card_t * card) +{ + snd_card_bt_sco_t *bt_sco = snd_magic_cast(snd_card_bt_sco_t,card->private_data,return); + + dprintk("private_free, killing thread\n"); + bt_sco->thread_exit = 1; + kill_proc(bt_sco->thread_pid, SIGTERM, 1); + wait_for_completion(&bt_sco->thread_done); + dprintk("private_free, thread exited\n"); + + if (bt_sco->sco_sock) { + dprintk("shutdown: freeing socket count %d\n",file_count(bt_sco->sco_sock->file)); + + sockfd_put(((struct socket*) bt_sco->sco_sock)); + } + + snd_magic_kfree(bt_sco); +} + +static int __init snd_card_bt_sco_probe(int dev) +{ + snd_card_t *card; + snd_card_bt_sco_t *bt_sco; + int err; + snd_hwdep_t *hw; + + card = + snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + bt_sco = snd_magic_kcalloc(snd_card_bt_sco_t, 0, GFP_KERNEL); + card->private_data = bt_sco; + card->private_free = snd_card_bt_private_free; + + bt_sco->card = card; + + init_completion(&bt_sco->thread_done); + init_MUTEX_LOCKED(&bt_sco->thread_sem); + init_MUTEX(&bt_sco->sock_sem); + init_MUTEX(&bt_sco->capture_sem); + init_MUTEX(&bt_sco->playback_sem); + init_waitqueue_head(&bt_sco->wait); + init_waitqueue_head(&bt_sco->hwdep_wait); + spin_lock_init(&bt_sco->mixer_changed_lock); + + /* These clone flags copied from some other driver. + Not sure that they're really correct... */ + bt_sco->thread_pid = + kernel_thread(snd_card_bt_sco_thread, card, CLONE_KERNEL); + if (bt_sco->thread_pid < 0) { + err = bt_sco->thread_pid; + goto __nodev; + } + + down(&bt_sco->thread_sem); + + if ((err = snd_card_bt_sco_pcm(bt_sco)) < 0) + goto __nodev; + if ((err = snd_card_bt_sco_new_mixer(bt_sco)) < 0) + goto __nodev; + strcpy(card->driver, "Bluetooth SCO"); + strcpy(card->shortname, "BT Headset"); + sprintf(card->longname, "BT Headset %i", dev + 1); + + err = snd_hwdep_new(card, "BTSCO", 0, &hw); + if (err < 0) + goto __nodev; + + sprintf(hw->name, "BTSCO"); + hw->iface = SNDRV_HWDEP_IFACE_BT_SCO; + hw->ops.open = snd_card_bt_open; + hw->ops.ioctl = snd_card_bt_ioctl; + hw->ops.release = snd_card_bt_release; + hw->ops.read = snd_card_bt_read; + hw->ops.write = snd_card_bt_write; + hw->ops.poll = snd_card_bt_poll; + + if ((err = snd_card_register(card)) == 0) { + snd_bt_sco_cards[dev] = card; + return 0; + } + __nodev: + snd_card_free(card); + return err; +} + +static int __init alsa_card_bt_sco_init(void) +{ + printk(KERN_INFO "snd-bt-sco revision %s\n",mod_revision+11); + + if (snd_card_bt_sco_probe(0) < 0) { +#ifdef MODULE + printk(KERN_ERR + "Bluetooth SCO soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_bt_sco_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_bt_sco_cards[idx]); +} + +module_init(alsa_card_bt_sco_init) + module_exit(alsa_card_bt_sco_exit) +#ifndef MODULE +static int __init alsa_card_bt_sco_setup(char *str) +{ + static unsigned __initdata nr_dev = 0; + + if (nr_dev >= SNDRV_CARDS) + return 0; + nr_dev++; + return 1; +} + +__setup("snd-bt-sco=", alsa_card_bt_sco_setup); + +#endif /* ifndef MODULE */ diff -urN linux/sound/bluetooth/Kconfig linux.new/sound/bluetooth/Kconfig --- linux/sound/bluetooth/Kconfig 1970-01-01 01:00:00.000000000 +0100 +++ linux.new/sound/bluetooth/Kconfig 2004-08-23 18:41:39.000000000 +0200 @@ -0,0 +1,20 @@ +# ALSA USB drivers + +menu "ALSA Bluetooth device" + depends on SND!=n && USB!=n + +config SND_BT_SCO + tristate "USB Bluetooth Alsa driver" + depends on SND && USB + select SND_HWDEP + select SND_USB_AUDIO + help + Say 'Y' or 'M' to include support for USB Bluetooth audio devices. + This will allow you to use a Bluetooth headset. The driver has to + be used with the btsco client program and will then create a new + ALSA device you can normally acces through plughw:Headset (see + README). You can get the client app and the driver patch from + http://www.gargan.org/linux/snd-bt-sco or maybe later from + sourceforge. + +endmenu diff -urN linux/sound/bluetooth/Makefile linux.new/sound/bluetooth/Makefile --- linux/sound/bluetooth/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ linux.new/sound/bluetooth/Makefile 2004-07-28 13:39:50.000000000 +0200 @@ -0,0 +1,26 @@ +# +# Makefile for ALSA +# + +snd-bt-sco-objs := btsco.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_BT_SCO) += snd-bt-sco.o + +#ifndef SND_TOPDIR +#SND_TOPDIR=.. +#endif + +#include $(SND_TOPDIR)/toplevel.config +#include $(SND_TOPDIR)/Makefile.conf + +#obj-$(CONFIG_SND_BT_SCO) += snd-bt-sco.o +#include $(SND_TOPDIR)/alsa-kernel/bluetooth/Makefile +# +#EXTRA_CFLAGS += -I$(SND_TOPDIR)/alsa-kernel/bluetooth +# +#include $(SND_TOPDIR)/Rules.make +# +#snd-bt-sco-objs := btsco.o +# +#btsco.o: ../alsa-kernel/bluetooth/btsco.c diff -urN linux/sound/Kconfig linux.new/sound/Kconfig --- linux/sound/Kconfig 2004-08-23 18:46:43.000000000 +0200 +++ linux.new/sound/Kconfig 2004-08-23 18:36:41.000000000 +0200 @@ -59,6 +59,8 @@ # here assuming USB is defined before ALSA source "sound/usb/Kconfig" +source "sound/bluetooth/Kconfig" + # the following will depenend on the order of config. # here assuming PCMCIA is defined before ALSA source "sound/pcmcia/Kconfig" diff -urN linux/sound/Makefile linux.new/sound/Makefile --- linux/sound/Makefile 2004-08-23 18:46:43.000000000 +0200 +++ linux.new/sound/Makefile 2004-08-23 19:22:46.000000000 +0200 @@ -4,7 +4,7 @@ obj-$(CONFIG_SOUND) += soundcore.o obj-$(CONFIG_SOUND_PRIME) += oss/ obj-$(CONFIG_DMASOUND) += oss/ -obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/ parisc/ pcmcia/ +obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/ parisc/ pcmcia/ bluetooth/ ifeq ($(CONFIG_SND),y) obj-y += last.o