Alsa-devel
Threads by month
- ----- 2026 -----
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
April 2013
- 96 participants
- 223 discussions
Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
The supported products are:
* M2Tech Young
* M2Tech hiFace
* M2Tech North Star
* M2Tech W4S Young
* M2Tech Corrson
* M2Tech AUDIA
* M2Tech SL Audio
* M2Tech Empirical
* M2Tech Rockna
* M2Tech Pathos
* M2Tech Metronome
* M2Tech CAD
* M2Tech Audio Esclusive
* M2Tech Rotel
* M2Tech Eeaudio
* The Chord Company CHORD
* AVA Group A/S Vitus
Signed-off-by: Antonio Ospite <ao2(a)amarulasolutions.com>
---
Hi,
this is my first ALSA driver I still have some doubts and I marked them in the
comments below with a XXX prefix, could someone please take a look at them?
I plan on submitting a version 2 of the driver after I get some feedback,
especially on those doubts.
I also noticed that many drivers under sound/usb/ have text in the Kconfig
entry stating "Say Y here to include support for ..." but I only see "[N/m/?]"
as options when I run "make oldconfig" maybe because of some dependency, I am
not sure, should such text be removed in order to avoid confusion?
Thanks,
Antonio
sound/usb/Kconfig | 33 +++
sound/usb/Makefile | 2 +-
sound/usb/hiface/Makefile | 2 +
sound/usb/hiface/chip.c | 331 ++++++++++++++++++++++
sound/usb/hiface/chip.h | 34 +++
sound/usb/hiface/pcm.c | 666 +++++++++++++++++++++++++++++++++++++++++++++
sound/usb/hiface/pcm.h | 24 ++
7 files changed, 1091 insertions(+), 1 deletion(-)
create mode 100644 sound/usb/hiface/Makefile
create mode 100644 sound/usb/hiface/chip.c
create mode 100644 sound/usb/hiface/chip.h
create mode 100644 sound/usb/hiface/pcm.c
create mode 100644 sound/usb/hiface/pcm.h
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index 225dfd7..2b206e8 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -115,5 +115,38 @@ config SND_USB_6FIRE
and further help can be found at
http://sixfireusb.sourceforge.net
+config SND_USB_HIFACE
+ tristate "M2Tech hiFace USB-SPDIF driver"
+ select SND_PCM
+ help
+ Say Y here to include support for M2Tech hiFace USB-SPDIF interface.
+
+ M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
+ Interface, see http://www.m2tech.biz/hiface.html
+
+ This driver supports the original M2Tech hiFace and some other
+ compatible devices. The supported products are:
+
+ * M2Tech Young
+ * M2Tech hiFace
+ * M2Tech North Star
+ * M2Tech W4S Young
+ * M2Tech Corrson
+ * M2Tech AUDIA
+ * M2Tech SL Audio
+ * M2Tech Empirical
+ * M2Tech Rockna
+ * M2Tech Pathos
+ * M2Tech Metronome
+ * M2Tech CAD
+ * M2Tech Audio Esclusive
+ * M2Tech Rotel
+ * M2Tech Eeaudio
+ * The Chord Company CHORD
+ * AVA Group A/S Vitus
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-usb-hiface.
+
endif # SND_USB
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index ac256dc..abe668f 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
new file mode 100644
index 0000000..463b136
--- /dev/null
+++ b/sound/usb/hiface/Makefile
@@ -0,0 +1,2 @@
+snd-usb-hiface-objs := chip.o pcm.o
+obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
new file mode 100644
index 0000000..dce74e9
--- /dev/null
+++ b/sound/usb/hiface/chip.c
@@ -0,0 +1,331 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael(a)amarulasolutions.com>
+ * Antonio Ospite <ao2(a)amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "chip.h"
+#include "pcm.h"
+
+MODULE_AUTHOR("Michael Trimarchi <michael(a)amarulasolutions.com>");
+MODULE_AUTHOR("Antonio Ospite <ao2(a)amarulasolutions.com>");
+MODULE_DESCRIPTION("M2Tech HiFace USB audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{HiFace, Evo}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+#define DRIVER_NAME "snd-usb-hiface"
+#define CARD_NAME "HiFace"
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+static DEFINE_MUTEX(register_mutex);
+
+struct hiface_vendor_quirk {
+ const char *device_name;
+ u8 extra_freq;
+};
+
+static int hiface_dev_free(struct snd_device *device)
+{
+ struct hiface_chip *chip = device->device_data;
+ kfree(chip);
+ return 0;
+}
+
+static int hiface_chip_create(struct usb_device *device, int idx,
+ const struct hiface_vendor_quirk *quirk,
+ struct hiface_chip **rchip)
+{
+ struct snd_card *card = NULL;
+ struct hiface_chip *chip;
+ int ret;
+ static struct snd_device_ops ops = {
+ .dev_free = hiface_dev_free,
+ };
+
+ *rchip = NULL;
+
+ /* if we are here, card can be registered in alsa. */
+ ret = snd_card_create(index[idx], id[idx], THIS_MODULE, 0, &card);
+ if (ret < 0) {
+ snd_printk(KERN_ERR "cannot create alsa card.\n");
+ return ret;
+ }
+
+ strcpy(card->driver, DRIVER_NAME);
+
+ if (quirk && quirk->device_name)
+ strcpy(card->shortname, quirk->device_name);
+ else
+ strcpy(card->shortname, "M2Tech generic audio");
+
+ sprintf(card->longname, "%s at %d:%d", card->shortname,
+ device->bus->busnum, device->devnum);
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ snd_card_free(card);
+ return -ENOMEM;
+ }
+
+ chip->dev = device;
+ chip->index = idx;
+ chip->card = card;
+
+ ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (ret < 0) {
+ kfree(chip);
+ snd_card_free(card);
+ return ret;
+ }
+
+ *rchip = chip;
+ return 0;
+}
+
+static int hiface_chip_probe(struct usb_interface *intf,
+ const struct usb_device_id *usb_id)
+{
+ const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
+ int ret;
+ int i;
+ struct hiface_chip *chip;
+ struct usb_device *device = interface_to_usbdev(intf);
+
+ pr_info("Probe " DRIVER_NAME " driver.\n");
+
+ ret = usb_set_interface(device, 0, 0);
+ if (ret != 0) {
+ snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
+ return -EIO;
+ }
+
+ /* check whether the card is already registered */
+ chip = NULL;
+ mutex_lock(®ister_mutex);
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ if (chips[i] && chips[i]->dev == device) {
+ if (chips[i]->shutdown) {
+ snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
+ ret = -ENODEV;
+ goto err;
+ }
+ chip = chips[i];
+ break;
+ }
+ }
+ if (!chip) {
+ /* it's a fresh one.
+ * now look for an empty slot and create a new card instance
+ */
+ for (i = 0; i < SNDRV_CARDS; i++)
+ if (enable[i] && !chips[i]) {
+ ret = hiface_chip_create(device, i, quirk,
+ &chip);
+ if (ret < 0)
+ goto err;
+
+ snd_card_set_dev(chip->card, &intf->dev);
+ break;
+ }
+ if (!chip) {
+ snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
+ ret = -ENODEV;
+ goto err;
+ }
+ }
+
+ ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
+ if (ret < 0)
+ goto err_chip_destroy;
+
+ ret = snd_card_register(chip->card);
+ if (ret < 0) {
+ snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
+ goto err_chip_destroy;
+ }
+
+ chips[chip->index] = chip;
+ chip->intf_count++;
+
+ mutex_unlock(®ister_mutex);
+
+ usb_set_intfdata(intf, chip);
+ return 0;
+
+err_chip_destroy:
+ snd_card_free(chip->card);
+err:
+ mutex_unlock(®ister_mutex);
+ return ret;
+}
+
+static void hiface_chip_disconnect(struct usb_interface *intf)
+{
+ struct hiface_chip *chip;
+ struct snd_card *card;
+
+ pr_debug("%s: called.\n", __func__);
+
+ chip = usb_get_intfdata(intf);
+ if (!chip)
+ return;
+
+ card = chip->card;
+ chip->intf_count--;
+ if (chip->intf_count <= 0) {
+ /* Make sure that the userspace cannot create new request */
+ snd_card_disconnect(card);
+
+ mutex_lock(®ister_mutex);
+ chips[chip->index] = NULL;
+ mutex_unlock(®ister_mutex);
+
+ chip->shutdown = true;
+ hiface_pcm_abort(chip);
+ snd_card_free_when_closed(card);
+ }
+}
+
+static struct usb_device_id device_table[] = {
+ {
+ USB_DEVICE(0x04b4, 0x0384),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Young",
+ .extra_freq = 1,
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x930b),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "hiFace",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931b),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "North Star",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "W4S Young",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931d),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Corrson",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931e),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "AUDIA",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931f),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "SL Audio",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x9320),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Empirical",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x9321),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Rockna",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9001),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Pathos",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9002),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Metronome",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9006),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "CAD",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9008),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Audio Esclusive",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x931c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Rotel",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x932c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Eeaudio",
+ }
+ },
+ {
+ USB_DEVICE(0x245f, 0x931c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "CHORD",
+ }
+ },
+ {
+ USB_DEVICE(0x25c6, 0x9002),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Vitus",
+ }
+ },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver hiface_usb_driver = {
+ .name = DRIVER_NAME,
+ .probe = hiface_chip_probe,
+ .disconnect = hiface_chip_disconnect,
+ .id_table = device_table,
+};
+
+module_usb_driver(hiface_usb_driver);
diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
new file mode 100644
index 0000000..c2e9fda
--- /dev/null
+++ b/sound/usb/hiface/chip.h
@@ -0,0 +1,34 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael(a)amarulasolutions.com>
+ * Antonio Ospite <ao2(a)amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#ifndef HIFACE_CHIP_H
+#define HIFACE_CHIP_H
+
+#include <linux/usb.h>
+#include <sound/core.h>
+
+struct pcm_runtime;
+
+struct hiface_chip {
+ struct usb_device *dev;
+ struct snd_card *card;
+ int intf_count; /* number of registered interfaces */
+ int index; /* index in module parameter arrays */
+ bool shutdown;
+
+ struct pcm_runtime *pcm;
+};
+#endif /* HIFACE_CHIP_H */
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
new file mode 100644
index 0000000..34fe0ed
--- /dev/null
+++ b/sound/usb/hiface/pcm.c
@@ -0,0 +1,666 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael(a)amarulasolutions.com>
+ * Antonio Ospite <ao2(a)amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+#include "pcm.h"
+#include "chip.h"
+
+#define OUT_EP 0x2
+#define PCM_N_URBS 8
+#define PCM_MAX_PACKET_SIZE 4096
+#define MAX_BUFSIZE (2 * PCM_N_URBS * PCM_MAX_PACKET_SIZE)
+
+struct pcm_urb {
+ struct hiface_chip *chip;
+
+ struct urb instance;
+ struct usb_anchor submitted;
+ u8 *buffer;
+};
+
+struct pcm_substream {
+ spinlock_t lock;
+ struct snd_pcm_substream *instance;
+
+ bool active;
+ snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
+ snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+enum { /* pcm streaming states */
+ STREAM_DISABLED, /* no pcm streaming */
+ STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+ STREAM_RUNNING, /* pcm streaming running */
+ STREAM_STOPPING
+};
+
+struct pcm_runtime {
+ struct hiface_chip *chip;
+ struct snd_pcm *instance;
+
+ struct pcm_substream playback;
+ bool panic; /* if set driver won't do anymore pcm on device */
+
+ struct pcm_urb out_urbs[PCM_N_URBS];
+
+ struct mutex stream_mutex;
+ u8 stream_state; /* one of STREAM_XXX */
+ u8 rate; /* one of PCM_RATE_XXX */
+ u8 extra_freq;
+ wait_queue_head_t stream_wait_queue;
+ bool stream_wait_cond;
+};
+
+static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
+ 352800, 384000 };
+static struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates) - 2, /* by default rates up to 192000 are supported */
+ .list = rates,
+ .mask = 0,
+};
+
+static const int rates_alsaid[] = {
+ SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
+ SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
+ SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000,
+ SNDRV_PCM_RATE_KNOT, SNDRV_PCM_RATE_KNOT };
+
+static const struct snd_pcm_hardware pcm_hw = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH,
+
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000 |
+ SNDRV_PCM_RATE_KNOT,
+
+ .rate_min = 44100,
+ .rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = MAX_BUFSIZE,
+ .period_bytes_min = PCM_MAX_PACKET_SIZE,
+ .period_bytes_max = MAX_BUFSIZE,
+ .periods_min = 2,
+ .periods_max = 1024
+};
+
+static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
+{
+ u8 rate_value[] = { 0x43, 0x4b, 0x42, 0x4a, 0x40, 0x48, 0x58, 0x68 };
+ int ret;
+ struct usb_device *device = rt->chip->dev;
+
+ /* We are already sure that the rate is supported here thanks to the
+ * contraints set in hiface_pcm_open(), but we have to retrieve the
+ * index to access rate_value.
+ */
+ for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+ if (rate == rates[rt->rate])
+ break;
+
+ if (unlikely(rt->rate == ARRAY_SIZE(rates))) {
+ pr_err("Unsupported rate %d\n", rate);
+ return -EINVAL;
+ }
+
+ /*
+ * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
+ * 43 b0 43 00 00 00 00 00
+ * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
+ * 43 b0 4b 00 00 00 00 00
+ * This control message doesn't have any ack from the
+ * other side
+ */
+ ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
+ 0xb0,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ rate_value[rt->rate], 0, NULL, 0, 100);
+ if (ret < 0) {
+ snd_printk(KERN_ERR "Error setting samplerate %d.\n",
+ rates[rt->rate]);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct pcm_substream *hiface_pcm_get_substream(
+ struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+ if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return &rt->playback;
+
+ pr_debug("Error getting pcm substream slot.\n");
+ return NULL;
+}
+
+/* call with stream_mutex locked */
+static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
+{
+ int i, time;
+
+ if (rt->stream_state != STREAM_DISABLED) {
+ rt->stream_state = STREAM_STOPPING;
+
+ for (i = 0; i < PCM_N_URBS; i++) {
+ time = usb_wait_anchor_empty_timeout(
+ &rt->out_urbs[i].submitted, 100);
+ if (!time)
+ usb_kill_anchored_urbs(
+ &rt->out_urbs[i].submitted);
+ usb_kill_urb(&rt->out_urbs[i].instance);
+ }
+
+ rt->stream_state = STREAM_DISABLED;
+ }
+}
+
+/* call with stream_mutex locked */
+static int hiface_pcm_stream_start(struct pcm_runtime *rt)
+{
+ int ret = 0, i;
+
+ if (rt->stream_state == STREAM_DISABLED) {
+ /* submit our out urbs zero init */
+ rt->stream_state = STREAM_STARTING;
+ for (i = 0; i < PCM_N_URBS; i++) {
+ memset(rt->out_urbs[i].buffer, 0, PCM_MAX_PACKET_SIZE);
+ usb_anchor_urb(&rt->out_urbs[i].instance,
+ &rt->out_urbs[i].submitted);
+ ret = usb_submit_urb(&rt->out_urbs[i].instance,
+ GFP_ATOMIC);
+ if (ret) {
+ hiface_pcm_stream_stop(rt);
+ return ret;
+ }
+ }
+
+ /* wait for first out urb to return (sent in in urb handler) */
+ wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+ HZ);
+ if (rt->stream_wait_cond) {
+ pr_debug("%s: Stream is running wakeup event\n",
+ __func__);
+ rt->stream_state = STREAM_RUNNING;
+ } else {
+ hiface_pcm_stream_stop(rt);
+ return -EIO;
+ }
+ }
+ return ret;
+}
+
+
+/* The hardware wants word-swapped 32-bit values */
+void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
+{
+ unsigned int i;
+
+ for (i = 0; i < n / 4; i++)
+ ((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
+}
+
+/* call with substream locked */
+static int hiface_pcm_playback(struct pcm_substream *sub,
+ struct pcm_urb *urb)
+{
+ struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+ u8 *source;
+ unsigned int pcm_buffer_size;
+
+ /* XXX Can an invalid format make it to here?
+ * Isn't ALSA checking pcm_hw.formats ?
+ */
+ if (alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE) {
+ pr_err("Unsupported sample format\n");
+ return -EINVAL;
+ }
+
+ pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
+
+ if (sub->dma_off + PCM_MAX_PACKET_SIZE <= pcm_buffer_size) {
+ pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
+ (unsigned int) pcm_buffer_size,
+ (unsigned int) sub->dma_off);
+
+ source = alsa_rt->dma_area + sub->dma_off;
+ memcpy_swahw32(urb->buffer, source, PCM_MAX_PACKET_SIZE);
+ } else {
+ /* wrap around at end of ring buffer */
+ unsigned int len;
+
+ pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
+ (unsigned int) pcm_buffer_size,
+ (unsigned int) sub->dma_off);
+
+ len = pcm_buffer_size - sub->dma_off;
+
+ source = alsa_rt->dma_area + sub->dma_off;
+ memcpy_swahw32(urb->buffer, source, len);
+
+ source = alsa_rt->dma_area;
+ memcpy_swahw32(urb->buffer + len, source,
+ PCM_MAX_PACKET_SIZE - len);
+ }
+ sub->dma_off += PCM_MAX_PACKET_SIZE;
+ if (sub->dma_off >= pcm_buffer_size)
+ sub->dma_off -= pcm_buffer_size;
+
+ sub->period_off += PCM_MAX_PACKET_SIZE;
+
+ return 0;
+}
+
+static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
+{
+ struct pcm_urb *out_urb = usb_urb->context;
+ struct pcm_runtime *rt = out_urb->chip->pcm;
+ struct pcm_substream *sub;
+ unsigned long flags;
+
+ pr_debug("%s: called.\n", __func__);
+
+ if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
+ return;
+
+ if (rt->stream_state == STREAM_STARTING) {
+ rt->stream_wait_cond = true;
+ wake_up(&rt->stream_wait_queue);
+ }
+
+ /* now send our playback data (if a free out urb was found) */
+ sub = &rt->playback;
+ spin_lock_irqsave(&sub->lock, flags);
+ if (sub->active) {
+ int ret;
+
+ ret = hiface_pcm_playback(sub, out_urb);
+ if (ret < 0) {
+ spin_unlock_irqrestore(&sub->lock, flags);
+ goto out_fail;
+ }
+ if (sub->period_off >= sub->instance->runtime->period_size) {
+ sub->period_off %= sub->instance->runtime->period_size;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ snd_pcm_period_elapsed(sub->instance);
+ } else {
+ spin_unlock_irqrestore(&sub->lock, flags);
+ }
+ } else {
+ memset(out_urb->buffer, 0, PCM_MAX_PACKET_SIZE);
+ spin_unlock_irqrestore(&sub->lock, flags);
+ }
+out_fail:
+ usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+}
+
+static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = NULL;
+ struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+ int ret;
+
+ pr_debug("%s: called.\n", __func__);
+
+ if (rt->panic)
+ return -EPIPE;
+
+ mutex_lock(&rt->stream_mutex);
+ alsa_rt->hw = pcm_hw;
+
+ if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+
+ /* XXX can we avoid setting hw.rates below?
+ *
+ * FWIU setting alsa_rt->hw.rates in the open callback can be
+ * useful in two cases:
+ *
+ * - different hardware revisions have different
+ * capabilities
+ *
+ * - different stream types must have the same capabilities
+ *
+ * but in our case it should not be needed as we only have one
+ * playback stream and we define the capabilities via
+ * constraints already to avoid invalid rates to be used.
+ *
+ * Moreover, following the code stolen from 6fire it seems
+ * impossible to get to setting alsa_rt->hw.rates here:
+ *
+ * hiface_pcm_init:
+ * rt->rate = ARRAY_SIZE(rates);
+ *
+ * hiface_pcm_open:
+ * if (rt->rate < ARRAY_SIZE(rates))
+ * alsa_rt->hw.rates = rates_alsaid[rt->rate];
+ * // So it does not get here on the first open
+ *
+ * hiface_pcm_prepare:
+ * hiface_pcm_set_rate:
+ * for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+ * if (rate == rates[rt->rate])
+ * break;
+ *
+ * hiface_pcm_close:
+ * rt->rate = ARRAY_SIZE(rates);
+ * // So it won't set alsa_rt->hw.rates at the next open either
+ *
+ * So the only way to set alsa_rt->hw.rates is to have two
+ * calls to the open callback, is that even possible? ALSA
+ * does not allow sharing PCM streams AFAIK and we use only
+ * one playback stream.
+ *
+ * Maybe the 6fire driver was setting alsa_rt->hw.rates in
+ * order to have the playback stream and the capture stream
+ * use the same rate? I.e. the first open,prepare,set_rate()
+ * sequence on one type of stream will decide the rate and the
+ * second open on the other type would restrict the supported
+ * rate to the one already in use, but I am not sure.
+ *
+ * If the rationale makes sense then we don't need to set
+ * alsa_rt->hw.rates and we can remove this and related code.
+ */
+ if (rt->rate < ARRAY_SIZE(rates))
+ alsa_rt->hw.rates = rates_alsaid[rt->rate];
+
+ sub = &rt->playback;
+ }
+
+ if (!sub) {
+ mutex_unlock(&rt->stream_mutex);
+ pr_err("Invalid stream type\n");
+ return -EINVAL;
+ }
+
+ if (rt->extra_freq) {
+ alsa_rt->hw.rate_max = 384000;
+ constraints_rates.count = ARRAY_SIZE(rates);
+ }
+
+ ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_rates);
+ if (ret < 0) {
+ mutex_unlock(&rt->stream_mutex);
+ return ret;
+ }
+
+ sub->instance = alsa_sub;
+ sub->active = false;
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ unsigned long flags;
+
+ if (rt->panic)
+ return 0;
+
+ pr_debug("%s: called.\n", __func__);
+
+ mutex_lock(&rt->stream_mutex);
+ if (sub) {
+ /* deactivate substream */
+ spin_lock_irqsave(&sub->lock, flags);
+ sub->instance = NULL;
+ sub->active = false;
+ spin_unlock_irqrestore(&sub->lock, flags);
+
+ /* all substreams closed? if so, stop streaming */
+ if (!rt->playback.instance) {
+ hiface_pcm_stream_stop(rt);
+ rt->rate = ARRAY_SIZE(rates);
+ }
+ }
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+ struct snd_pcm_hw_params *hw_params)
+{
+ pr_debug("%s: called.\n", __func__);
+ return snd_pcm_lib_malloc_pages(alsa_sub,
+ params_buffer_bytes(hw_params));
+}
+
+static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+ pr_debug("%s: called.\n", __func__);
+ return snd_pcm_lib_free_pages(alsa_sub);
+}
+
+static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+ int ret;
+
+ pr_debug("%s: called.\n", __func__);
+
+ if (rt->panic)
+ return -EPIPE;
+ if (!sub)
+ return -ENODEV;
+
+ mutex_lock(&rt->stream_mutex);
+
+ sub->dma_off = 0;
+ sub->period_off = 0;
+
+ if (rt->stream_state == STREAM_DISABLED) {
+
+ ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
+ if (ret) {
+ mutex_unlock(&rt->stream_mutex);
+ return ret;
+ }
+ ret = hiface_pcm_stream_start(rt);
+ if (ret) {
+ mutex_unlock(&rt->stream_mutex);
+ return ret;
+ }
+ }
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ unsigned long flags;
+
+ pr_debug("%s: called.\n", __func__);
+
+ if (rt->panic)
+ return -EPIPE;
+ if (!sub)
+ return -ENODEV;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&sub->lock, flags);
+ sub->active = true;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ return 0;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&sub->lock, flags);
+ sub->active = false;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ unsigned long flags;
+ snd_pcm_uframes_t dma_offset;
+
+ if (rt->panic || !sub)
+ return SNDRV_PCM_STATE_XRUN;
+
+ spin_lock_irqsave(&sub->lock, flags);
+ dma_offset = sub->dma_off;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ return bytes_to_frames(alsa_sub->runtime, dma_offset);
+}
+
+static struct snd_pcm_ops pcm_ops = {
+ .open = hiface_pcm_open,
+ .close = hiface_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = hiface_pcm_hw_params,
+ .hw_free = hiface_pcm_hw_free,
+ .prepare = hiface_pcm_prepare,
+ .trigger = hiface_pcm_trigger,
+ .pointer = hiface_pcm_pointer,
+};
+
+static int hiface_pcm_init_urb(struct pcm_urb *urb,
+ struct hiface_chip *chip,
+ unsigned int ep,
+ void (*handler)(struct urb *))
+{
+ urb->chip = chip;
+ usb_init_urb(&urb->instance);
+
+ urb->buffer = kzalloc(PCM_MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!urb->buffer)
+ return -ENOMEM;
+
+ usb_fill_bulk_urb(&urb->instance, chip->dev,
+ usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
+ PCM_MAX_PACKET_SIZE, handler, urb);
+ init_usb_anchor(&urb->submitted);
+
+ return 0;
+}
+
+void hiface_pcm_abort(struct hiface_chip *chip)
+{
+ struct pcm_runtime *rt = chip->pcm;
+
+ if (rt) {
+ rt->panic = true;
+
+ if (rt->playback.instance) {
+ snd_pcm_stop(rt->playback.instance,
+ SNDRV_PCM_STATE_XRUN);
+ }
+ mutex_lock(&rt->stream_mutex);
+ hiface_pcm_stream_stop(rt);
+ mutex_unlock(&rt->stream_mutex);
+ }
+}
+
+static void hiface_pcm_destroy(struct hiface_chip *chip)
+{
+ struct pcm_runtime *rt = chip->pcm;
+ int i;
+
+ for (i = 0; i < PCM_N_URBS; i++)
+ kfree(rt->out_urbs[i].buffer);
+
+ kfree(chip->pcm);
+ chip->pcm = NULL;
+}
+
+static void hiface_pcm_free(struct snd_pcm *pcm)
+{
+ struct pcm_runtime *rt = pcm->private_data;
+
+ pr_debug("%s: called.\n", __func__);
+
+ if (rt)
+ hiface_pcm_destroy(rt->chip);
+}
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
+{
+ int i;
+ int ret;
+ struct snd_pcm *pcm;
+ struct pcm_runtime *rt;
+
+ rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+ if (!rt)
+ return -ENOMEM;
+
+ rt->chip = chip;
+ rt->stream_state = STREAM_DISABLED;
+ rt->rate = ARRAY_SIZE(rates);
+ if (extra_freq)
+ rt->extra_freq = 1;
+
+ init_waitqueue_head(&rt->stream_wait_queue);
+ mutex_init(&rt->stream_mutex);
+ spin_lock_init(&rt->playback.lock);
+
+ for (i = 0; i < PCM_N_URBS; i++)
+ hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
+ hiface_pcm_out_urb_handler);
+
+ ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
+ if (ret < 0) {
+ kfree(rt);
+ pr_err("Cannot create pcm instance\n");
+ return ret;
+ }
+
+ pcm->private_data = rt;
+ pcm->private_free = hiface_pcm_free;
+
+ strcpy(pcm->name, "USB-SPDIF Audio");
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ MAX_BUFSIZE, MAX_BUFSIZE);
+ rt->instance = pcm;
+
+ chip->pcm = rt;
+ return 0;
+}
diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
new file mode 100644
index 0000000..ddc9fe7
--- /dev/null
+++ b/sound/usb/hiface/pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael(a)amarulasolutions.com>
+ * Antonio Ospite <ao2(a)amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#ifndef HIFACE_PCM_H
+#define HIFACE_PCM_H
+
+struct hiface_chip;
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
+void hiface_pcm_abort(struct hiface_chip *chip);
+#endif /* HIFACE_PCM_H */
--
1.7.10.4
6
22
10 Jun '13
Hello Clemens,
it's been a long time since I last inquired about the snd_dice module
provided by your firewire-kernel-streaming branch
(http://git.alsa-project.org/?p=alsa-kprivate.git;a=shortlog;h=refs/heads/fi…),
which is a good sign I guess. In the meantime we have been making heavy
use of the snd_dice module for over a year with all of our Weiss DICE
based products (obviously playback only), and it has proven to be
extremely stable (currently 3.4.18-rt29, patched with
firewire-kernel-streaming).
Therefore I have been meaning to ask you for some time now whether you
have any interest in merging this firewire-kernel-streaming branch into
the main kernel tree (at least as an experimental option, mind you), or
are there any major objections?
Given that you don't have unlimited time at hand to further develop the
snd_dice module, let alone provide capture support in the near future,
AND the fact that DICE is likely to see its EOL soon (and replaced by a
sophisticated successor by TCAT), I realize that this is probably not a
high priority. However from our perspective it would be sad to see these
ALSA Firewire proceedings be lost and not be appreciated by a larger
audience. With respect to this:
* what would be the customary approach to requesting such a merge?
* what are your reservations, resp. are there any existing shortcomings
that you are yet unhappy with?
* have you received any other (negative/positive) feedback from
snd_dice users? I know of the Vortexbox community, which has been
offering an optional patch to support Firewire plackback devices; as far
as I know, they're happy with it.
* if you need any backing or assistance, we'd be happy to help out
Keen to hear your feedback on this suggestion.
Best regards,
Rolf Anderegg
(Weiss Engineering Ltd.)
2
2
Hi,
So I have this hardware that has a few issues (all tested with tiwai/sound for-next)
Audio out/speakers:
- Audio does not work by default. It needs plugging a jack in either headphone or mic.
- disabling "Enable Automute" does make it work. But then it doesn't mute when jack is plugged (obviously).
Mic:
- needs model=laptop-dmic so that working "Internal Mic 1" appears
- "Internal Mic" is useless.
- no jack sense, need to select manually "Internal Mic 1" or "Mic" (external) as Input Source
Please find alsa-info.sh with and without model=laptop-dmic in attachement.
Regards,
Anisse
3
17
In my audio application when I sometimes need to switch to a
different sample format and/or rate, I usually leave the audio device
open and merely reconfigure the device to the new parameters. This
works with all the regular pcm devices, but it fails when I use the
"hdmi" device. It seemed to just silently fail and no audio output is
heard. As far as I know, no error condition is triggered (but I'll
admit, my knowledge of the alsa api is rather limited).
My question, Is this a bug or is it supposed to work this way and if
so are there ways to detect whether a devices needs to be reopened or
not.
Thanks,
Sander
2
2
22 May '13
This patch adds a driver for TI's TA5086 6-channel PWM processor.
This chip has a very unusual register layout, specifically because the
registers are of unequal size, and multi-byte registers require bulk
writes to take effect. Regmap does not support these kind of mappings.
Currently, the driver does not touch any of the registers >= 0x20, so
it doesn't matter, because the register map is mapped to an 8-bit array.
In case more features will be added in the future that require access
to higher registers, the entire regmap H/W I/O routines have to be
open-coded.
Signed-off-by: Daniel Mack <zonque(a)gmail.com>
---
.../devicetree/bindings/sound/ti,tas5086.txt | 32 ++
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tas5086.c | 587 +++++++++++++++++++++
4 files changed, 625 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/ti,tas5086.txt
create mode 100644 sound/soc/codecs/tas5086.c
diff --git a/Documentation/devicetree/bindings/sound/ti,tas5086.txt b/Documentation/devicetree/bindings/sound/ti,tas5086.txt
new file mode 100644
index 0000000..8ea4f5b
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ti,tas5086.txt
@@ -0,0 +1,32 @@
+Texas Instruments TAS5086 6-channel PWM Processor
+
+Required properties:
+
+ - compatible: Should contain "ti,tas5086".
+ - reg: The i2c address. Should contain <0x1b>.
+
+Optional properties:
+
+ - reset-gpio: A GPIO spec to define which pin is connected to the
+ chip's !RESET pin. If specified, the driver will
+ assert a hardware reset at probe time.
+
+ - ti,charge-period: This property should contain the time in microseconds
+ that closely matches the external single-ended
+ split-capacitor charge period. The hardware chip
+ waits for this period of time before starting the
+ PWM signals. This helps reduce pops and clicks.
+
+ When not specified, the hardware default of 1300ms
+ is retained.
+
+Examples:
+
+ i2c_bus {
+ tas5086@1b {
+ compatible = "ti,tas5086";
+ reg = <0x1b>;
+ reset-gpio = <&gpio 23 0>;
+ ti,charge-period = <156000>;
+ };
+ };
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 45b7256..86b3524 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -63,6 +63,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_STA32X if I2C
select SND_SOC_STA529 if I2C
select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
+ select SND_SOC_TAS5086 if I2C
select SND_SOC_TLV320AIC23 if I2C
select SND_SOC_TLV320AIC26 if SPI_MASTER
select SND_SOC_TLV320AIC32X4 if I2C
@@ -320,6 +321,9 @@ config SND_SOC_STA529
config SND_SOC_STAC9766
tristate
+config SND_SOC_TAS5086
+ tristate
+
config SND_SOC_TLV320AIC23
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 6a3b3c3..8077bc2 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -55,6 +55,7 @@ snd-soc-ssm2602-objs := ssm2602.o
snd-soc-sta32x-objs := sta32x.o
snd-soc-sta529-objs := sta529.o
snd-soc-stac9766-objs := stac9766.o
+snd-soc-tas5086-objs := tas5086.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
snd-soc-tlv320aic3x-objs := tlv320aic3x.o
@@ -177,6 +178,7 @@ obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o
obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o
obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
+obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
diff --git a/sound/soc/codecs/tas5086.c b/sound/soc/codecs/tas5086.c
new file mode 100644
index 0000000..cd3c267
--- /dev/null
+++ b/sound/soc/codecs/tas5086.c
@@ -0,0 +1,587 @@
+/*
+ * TAS5086 ASoC codec driver
+ *
+ * Copyright (c) 2013 Daniel Mack <zonque(a)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.
+ *
+ * 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.
+ *
+ * TODO:
+ * - implement DAPM and input muxing
+ * - implement modulation limit
+ * - implement non-default PWM start
+ *
+ * Note that this chip has a very unusual register layout, specifically
+ * because the registers are of unequal size, and multi-byte registers
+ * require bulk writes to take effect. Regmap does not support that kind
+ * of devices.
+ *
+ * Currently, the driver does not touch any of the registers >= 0x20, so
+ * it doesn't matter because the entire map can be accessed as 8-bit
+ * array. In case more features will be added in the future
+ * that require access to higher registers, the entire regmap H/W I/O
+ * routines have to be open-coded.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#define TAS5086_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE)
+
+#define TAS5086_PCM_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+/*
+ * TAS5086 registers
+ */
+#define TAS5086_CLOCK_CONTROL 0x00 /* Clock control register */
+#define TAS5086_CLOCK_RATE(val) (val << 5)
+#define TAS5086_CLOCK_RATE_MASK (0x7 << 5)
+#define TAS5086_CLOCK_RATIO(val) (val << 2)
+#define TAS5086_CLOCK_RATIO_MASK (0x7 << 2)
+#define TAS5086_CLOCK_SCLK_RATIO_48 (1 << 1)
+#define TAS5086_CLOCK_VALID (1 << 0)
+
+#define TAS5086_DEV_ID 0x01 /* Device ID register */
+#define TAS5086_ERROR_STATUS 0x02 /* Error status register */
+#define TAS5086_SYS_CONTROL_1 0x03 /* System control register 1 */
+#define TAS5086_SERIAL_DATA_IF 0x04 /* Serial data interface register */
+#define TAS5086_SYS_CONTROL_2 0x05 /* System control register 2 */
+#define TAS5086_SOFT_MUTE 0x06 /* Soft mute register */
+#define TAS5086_MASTER_VOL 0x07 /* Master volume */
+#define TAS5086_CHANNEL_VOL(X) (0x08 + (X)) /* Channel 1-6 volume */
+#define TAS5086_VOLUME_CONTROL 0x09 /* Volume control register */
+#define TAS5086_MOD_LIMIT 0x10 /* Modulation limit register */
+#define TAS5086_PWM_START 0x18 /* PWM start register */
+#define TAS5086_SURROUND 0x19 /* Surround register */
+#define TAS5086_SPLIT_CAP_CHARGE 0x1a /* Split cap charge period register */
+#define TAS5086_OSC_TRIM 0x1b /* Oscillator trim register */
+#define TAS5086_BKNDERR 0x1c
+
+#define TAS5086_DEEMPH_MASK 0x03
+
+/*
+ * Default TAS5086 power-up configuration
+ */
+static const struct reg_default tas5086_reg_defaults[] = {
+ { 0x00, 0x6c },
+ { 0x01, 0x03 },
+ { 0x02, 0x00 },
+ { 0x03, 0xa0 },
+ { 0x04, 0x05 },
+ { 0x05, 0x60 },
+ { 0x06, 0x00 },
+ { 0x07, 0xff },
+ { 0x08, 0x30 },
+ { 0x09, 0x30 },
+ { 0x0a, 0x30 },
+ { 0x0b, 0x30 },
+ { 0x0c, 0x30 },
+ { 0x0d, 0x30 },
+ { 0x0e, 0xb1 },
+ { 0x0f, 0x00 },
+ { 0x10, 0x02 },
+ { 0x11, 0x00 },
+ { 0x12, 0x00 },
+ { 0x13, 0x00 },
+ { 0x14, 0x00 },
+ { 0x15, 0x00 },
+ { 0x16, 0x00 },
+ { 0x17, 0x00 },
+ { 0x18, 0x3f },
+ { 0x19, 0x00 },
+ { 0x1a, 0x18 },
+ { 0x1b, 0x82 },
+ { 0x1c, 0x05 },
+};
+
+static bool tas5086_accessible_reg(struct device *dev, unsigned int reg)
+{
+ return !((reg == 0x0f) || (reg >= 0x11 && reg <= 0x17));
+}
+
+static bool tas5086_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return reg == TAS5086_DEV_ID ||
+ reg == TAS5086_ERROR_STATUS;
+}
+
+static bool tas5086_writeable_reg(struct device *dev, unsigned int reg)
+{
+ return tas5086_accessible_reg(dev, reg) && (reg != TAS5086_DEV_ID);
+}
+
+struct tas5086_private {
+ struct regmap *regmap;
+ unsigned int mclk, sclk;
+ unsigned int format;
+ bool deemph;
+ /* Current sample rate for de-emphasis control */
+ int rate;
+ /* GPIO driving Reset pin, if any */
+ int gpio_nreset;
+};
+
+static int tas5086_deemph[] = { 0, 32000, 44100, 48000 };
+
+static int tas5086_set_deemph(struct snd_soc_codec *codec)
+{
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+ int i, val = 0;
+
+ if (priv->deemph)
+ for (i = 0; i < ARRAY_SIZE(tas5086_deemph); i++)
+ if (tas5086_deemph[i] == priv->rate)
+ val = i;
+
+ return regmap_update_bits(priv->regmap, TAS5086_SYS_CONTROL_1,
+ TAS5086_DEEMPH_MASK, val);
+}
+
+static int tas5086_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = priv->deemph;
+
+ return 0;
+}
+
+static int tas5086_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ priv->deemph = ucontrol->value.enumerated.item[0];
+
+ return tas5086_set_deemph(codec);
+}
+
+
+static int tas5086_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case 0: /* MCLK */
+ priv->mclk = freq;
+ break;
+ case 1: /* SCLK */
+ priv->sclk = freq;
+ break;
+ }
+
+ return 0;
+}
+
+static int tas5086_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ /* The TAS5086 can only be slave to all clocks */
+ if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
+ dev_err(codec->dev, "Invalid clocking mode\n");
+ return -EINVAL;
+ }
+
+ /* we need to refer to the data format from hw_params() */
+ priv->format = format;
+
+ return 0;
+}
+
+static const int tas5086_sample_rates[] = {
+ 32000, 38000, 44100, 48000, 88200, 96000, 176400, 192000
+};
+
+static const int tas5086_ratios[] = {
+ 64, 128, 192, 256, 384, 512
+};
+
+static int index_in_array(const int *array, int len, int needle)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ if (array[i] == needle)
+ return i;
+
+ return -ENOENT;
+}
+
+static int tas5086_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+ int ret;
+
+ priv->rate = params_rate(params);
+
+ /* Look up the sample rate and refer to the offset in the list */
+ val = index_in_array(tas5086_sample_rates,
+ ARRAY_SIZE(tas5086_sample_rates), priv->rate);
+
+ if (val < 0) {
+ dev_err(codec->dev, "Invalid sample rate\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL,
+ TAS5086_CLOCK_RATE_MASK,
+ TAS5086_CLOCK_RATE(val));
+ if (ret < 0)
+ return ret;
+
+ /* MCLK / Fs ratio */
+ val = index_in_array(tas5086_ratios, ARRAY_SIZE(tas5086_ratios),
+ priv->mclk / priv->rate);
+ if (val < 0) {
+ dev_err(codec->dev, "Inavlid MCLK / Fs ratio\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL,
+ TAS5086_CLOCK_RATIO_MASK,
+ TAS5086_CLOCK_RATIO(val));
+ if (ret < 0)
+ return ret;
+
+
+ ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL,
+ TAS5086_CLOCK_SCLK_RATIO_48,
+ (priv->sclk == 48 * priv->rate) ?
+ TAS5086_CLOCK_SCLK_RATIO_48 : 0);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The chip has a very unituitive register mapping and muxes information
+ * about data format and sample depth into the same register, but not on
+ * a logical bit-boundary. Hence, we have to refer to the format passed
+ * in the set_dai_fmt() callback and set up everything from here.
+ *
+ * First, determine the 'base' value, using the format ...
+ */
+ switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val = 0x00;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val = 0x03;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ val = 0x06;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid DAI format\n");
+ return -EINVAL;
+ }
+
+ /* ... then add the offset for the sample bit depth. */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val += 0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ val += 1;
+ break;
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ val += 2;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid bit width\n");
+ return -EINVAL;
+ };
+
+ ret = regmap_write(priv->regmap, TAS5086_SERIAL_DATA_IF, val);
+ if (ret < 0)
+ return ret;
+
+ /* clock is considered valid now */
+ ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL,
+ TAS5086_CLOCK_VALID, TAS5086_CLOCK_VALID);
+ if (ret < 0)
+ return ret;
+
+ return tas5086_set_deemph(codec);
+}
+
+static int tas5086_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ return regmap_write(priv->regmap, TAS5086_SOFT_MUTE,
+ mute ? 0x3f : 0x00);
+}
+
+/* TAS5086 controls */
+static const DECLARE_TLV_DB_SCALE(tas5086_dac_tlv, -10350, 50, 1);
+
+static const struct snd_kcontrol_new tas5086_controls[] = {
+ SOC_SINGLE_TLV("Master Playback Volume", TAS5086_MASTER_VOL,
+ 0, 0xff, 1, tas5086_dac_tlv),
+ SOC_DOUBLE_R_TLV("Channel 1/2 Playback Volume",
+ TAS5086_CHANNEL_VOL(0), TAS5086_CHANNEL_VOL(1),
+ 0, 0xff, 1, tas5086_dac_tlv),
+ SOC_DOUBLE_R_TLV("Channel 3/4 Playback Volume",
+ TAS5086_CHANNEL_VOL(2), TAS5086_CHANNEL_VOL(3),
+ 0, 0xff, 1, tas5086_dac_tlv),
+ SOC_DOUBLE_R_TLV("Channel 5/6 Playback Volume",
+ TAS5086_CHANNEL_VOL(4), TAS5086_CHANNEL_VOL(5),
+ 0, 0xff, 1, tas5086_dac_tlv),
+ SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0,
+ tas5086_get_deemph, tas5086_put_deemph),
+};
+
+static const struct snd_soc_dai_ops tas5086_dai_ops = {
+ .hw_params = tas5086_hw_params,
+ .set_sysclk = tas5086_set_dai_sysclk,
+ .set_fmt = tas5086_set_dai_fmt,
+ .digital_mute = tas5086_digital_mute,
+};
+
+static struct snd_soc_dai_driver tas5086_dai = {
+ .name = "tas5086-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 6,
+ .rates = TAS5086_PCM_RATES,
+ .formats = TAS5086_PCM_FORMATS,
+ },
+ .ops = &tas5086_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int tas5086_soc_suspend(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+
+static int tas5086_soc_resume(struct snd_soc_codec *codec)
+{
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ /* Restore codec state */
+ return regcache_sync(priv->regmap);
+}
+#else
+#define tas5086_soc_suspend NULL
+#define tas5086_soc_resume NULL
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_OF
+static const struct of_device_id tas5086_dt_ids[] = {
+ { .compatible = "ti,tas5086", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tas5086_dt_ids);
+#endif
+
+/* charge period values in microseconds */
+static const int tas5086_charge_period[] = {
+ 13000, 16900, 23400, 31200, 41600, 54600, 72800, 96200,
+ 130000, 156000, 234000, 312000, 416000, 546000, 728000, 962000,
+ 1300000, 169000, 2340000, 3120000, 4160000, 5460000, 7280000, 9620000,
+};
+
+static int tas5086_probe(struct snd_soc_codec *codec)
+{
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+ int charge_period = 1300000; /* hardware default is 1300 ms */
+ int gpio_nreset = -EINVAL;
+ int i, ret;
+
+ if (of_match_device(of_match_ptr(tas5086_dt_ids), codec->dev)) {
+ struct device_node *of_node = codec->dev->of_node;
+
+ gpio_nreset = of_get_named_gpio(of_node, "reset-gpio", 0);
+ of_property_read_u32(of_node, "ti,charge-period", &charge_period);
+ }
+
+ if (gpio_nreset >= 0)
+ if (devm_gpio_request(codec->dev, gpio_nreset, "TAS5086 Reset"))
+ gpio_nreset = -EINVAL;
+ if (gpio_nreset >= 0) {
+ /* Reset codec - minimum assertion time is 400ns */
+ gpio_direction_output(gpio_nreset, 0);
+ udelay(1);
+ gpio_set_value(gpio_nreset, 1);
+
+ /* Codec needs ~15ms to wake up */
+ msleep(15);
+ }
+msleep(300);
+ priv->gpio_nreset = gpio_nreset;
+
+ /* The TAS5086 always returns 0x03 in its TAS5086_DEV_ID register */
+ ret = regmap_read(priv->regmap, TAS5086_DEV_ID, &i);
+ if (ret < 0)
+ return ret;
+
+ if (i != 0x3) {
+ dev_err(codec->dev,
+ "Failed to identify TAS5086 codec (got %02x)\n", i);
+ return -ENODEV;
+ }
+
+ /* lookup and set split-capacitor charge period */
+ if (charge_period == 0) {
+ regmap_write(priv->regmap, TAS5086_SPLIT_CAP_CHARGE, 0);
+ } else {
+ i = index_in_array(tas5086_charge_period,
+ ARRAY_SIZE(tas5086_charge_period),
+ charge_period);
+ if (i >= 0)
+ regmap_write(priv->regmap, TAS5086_SPLIT_CAP_CHARGE,
+ i + 0x08);
+ else
+ dev_warn(codec->dev,
+ "Invalid split-cap charge period of %d ns.\n",
+ charge_period);
+ }
+
+ /* enable factory trim */
+ ret = regmap_write(priv->regmap, TAS5086_OSC_TRIM, 0x00);
+ if (ret < 0)
+ return ret;
+
+ /* start all channels */
+ ret = regmap_write(priv->regmap, TAS5086_SYS_CONTROL_2, 0x20);
+ if (ret < 0)
+ return ret;
+
+ /* set master volume to 0 dB */
+ ret = regmap_write(priv->regmap, TAS5086_MASTER_VOL, 0x30);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int tas5086_remove(struct snd_soc_codec *codec)
+{
+ struct tas5086_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ if (gpio_is_valid(priv->gpio_nreset))
+ /* Set codec to the reset state */
+ gpio_set_value(priv->gpio_nreset, 0);
+
+ return 0;
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_tas5086 = {
+ .probe = tas5086_probe,
+ .remove = tas5086_remove,
+ .suspend = tas5086_soc_suspend,
+ .resume = tas5086_soc_resume,
+ .controls = tas5086_controls,
+ .num_controls = ARRAY_SIZE(tas5086_controls),
+};
+
+static const struct i2c_device_id tas5086_i2c_id[] = {
+ { "tas5086", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tas5086_i2c_id);
+
+static const struct regmap_config tas5086_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = ARRAY_SIZE(tas5086_reg_defaults),
+ .reg_defaults = tas5086_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tas5086_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = tas5086_volatile_reg,
+ .writeable_reg = tas5086_writeable_reg,
+ .readable_reg = tas5086_accessible_reg,
+};
+
+static int tas5086_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct tas5086_private *priv;
+ int ret;
+
+ priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = devm_regmap_init_i2c(i2c, &tas5086_regmap);
+ if (IS_ERR(priv->regmap)) {
+ ret = PTR_ERR(priv->regmap);
+ dev_err(&i2c->dev, "Failed to create regmap: %d\n", ret);
+ return ret;
+ }
+
+ i2c_set_clientdata(i2c, priv);
+
+ return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_tas5086,
+ &tas5086_dai, 1);
+}
+
+static int tas5086_i2c_remove(struct i2c_client *i2c)
+{
+ snd_soc_unregister_codec(&i2c->dev);
+ return 0;
+}
+
+static struct i2c_driver tas5086_i2c_driver = {
+ .driver = {
+ .name = "tas5086",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(tas5086_dt_ids),
+ },
+ .id_table = tas5086_i2c_id,
+ .probe = tas5086_i2c_probe,
+ .remove = tas5086_i2c_remove,
+};
+
+static int __init tas5086_modinit(void)
+{
+ return i2c_add_driver(&tas5086_i2c_driver);
+}
+module_init(tas5086_modinit);
+
+static void __exit tas5086_modexit(void)
+{
+ i2c_del_driver(&tas5086_i2c_driver);
+}
+module_exit(tas5086_modexit);
+
+MODULE_AUTHOR("Daniel Mack <zonque(a)gmail.com>");
+MODULE_DESCRIPTION("Texas Instruments TAS5086 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
--
1.8.1.4
4
13
The series removes the use of imx-pcm-audio/imx-fiq-pcm-audio platform
device, and deletes imx-pcm driver by moving the functions needed by
imx-pcm-fiq driver into imx-pcm-fiq.c. This can be done because
imx-pcm-dma has moved to generic dmaengine pcm driver and does not use
any helper from imx-pcm driver now.
Shawn Guo (5):
ASoC: fsl: remove use of imx-pcm-audio from fsl_ssi
ASoC: fsl: remove use of imx-pcm-audio from imx-ssi
ASoC: fsl: create function imx_pcm_fiq_exit()
ASoC: fsl: remove use of imx-fiq-pcm-audio from imx-ssi
ASoC: fsl: remove imx-pcm driver
sound/soc/fsl/Kconfig | 5 --
sound/soc/fsl/Makefile | 11 +--
sound/soc/fsl/eukrea-tlv320.c | 2 +-
sound/soc/fsl/fsl_ssi.c | 13 ++--
sound/soc/fsl/imx-mc13783.c | 2 +-
sound/soc/fsl/imx-pcm-dma.c | 2 +
sound/soc/fsl/imx-pcm-fiq.c | 92 +++++++++++++++++++++++++
sound/soc/fsl/imx-pcm.c | 145 ---------------------------------------
sound/soc/fsl/imx-pcm.h | 10 +--
sound/soc/fsl/imx-sgtl5000.c | 2 +-
sound/soc/fsl/imx-ssi.c | 44 +++---------
sound/soc/fsl/imx-ssi.h | 3 -
sound/soc/fsl/mx27vis-aic32x4.c | 2 +-
sound/soc/fsl/phycore-ac97.c | 2 +-
sound/soc/fsl/wm1133-ev1.c | 2 +-
15 files changed, 122 insertions(+), 215 deletions(-)
delete mode 100644 sound/soc/fsl/imx-pcm.c
--
1.7.9.5
2
13
[alsa-devel] [PATCH 1/8] ASoC: generic-dmaengine-pcm: Add support for half-duplex
by Lars-Peter Clausen 13 May '13
by Lars-Peter Clausen 13 May '13
13 May '13
Some platforms which are half-duplex share the same DMA channel between the
playback and capture stream. Add support for this to the generic dmaengine PCM
driver.
Signed-off-by: Lars-Peter Clausen <lars(a)metafoo.de>
---
include/sound/dmaengine_pcm.h | 5 ++++
sound/soc/soc-generic-dmaengine-pcm.c | 43 ++++++++++++++++++++++++-----------
2 files changed, 35 insertions(+), 13 deletions(-)
diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h
index b1d1150..f11c35c 100644
--- a/include/sound/dmaengine_pcm.h
+++ b/include/sound/dmaengine_pcm.h
@@ -91,6 +91,11 @@ void snd_dmaengine_pcm_set_config_from_dai_data(
* bytes that are still left to transfer.
*/
#define SND_DMAENGINE_PCM_FLAG_NO_RESIDUE BIT(2)
+/*
+ * The PCM is half duplex and the DMA channel is shared between capture and
+ * playback.
+ */
+#define SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX BIT(3)
/**
* struct snd_dmaengine_pcm_config - Configuration data for dmaengine based PCM
diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c
index ae0c37e..5fd5ed4 100644
--- a/sound/soc/soc-generic-dmaengine-pcm.c
+++ b/sound/soc/soc-generic-dmaengine-pcm.c
@@ -29,7 +29,7 @@ struct dmaengine_pcm {
struct dma_chan *chan[SNDRV_PCM_STREAM_CAPTURE + 1];
const struct snd_dmaengine_pcm_config *config;
struct snd_soc_platform platform;
- bool compat;
+ unsigned int flags;
};
static struct dmaengine_pcm *soc_platform_to_pcm(struct snd_soc_platform *p)
@@ -128,6 +128,9 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel(
{
struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
+ if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) && pcm->chan[0])
+ return pcm->chan[0];
+
if (pcm->config->compat_request_channel)
return pcm->config->compat_request_channel(rtd, substream);
@@ -148,7 +151,7 @@ static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
if (!substream)
continue;
- if (!pcm->chan[i] && pcm->compat) {
+ if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) {
pcm->chan[i] = dmaengine_pcm_compat_request_channel(rtd,
substream);
}
@@ -215,6 +218,25 @@ static const char * const dmaengine_pcm_dma_channel_names[] = {
[SNDRV_PCM_STREAM_CAPTURE] = "rx",
};
+static void dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
+ struct device_node *of_node)
+{
+ unsigned int i;
+
+ if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_DT) || !of_node)
+ return;
+
+ if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) {
+ pcm->chan[0] = of_dma_request_slave_channel(of_node, "tx_rx");
+ pcm->chan[1] = pcm->chan[0];
+ } else {
+ for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
+ pcm->chan[i] = of_dma_request_slave_channel(of_node,
+ dmaengine_pcm_dma_channel_names[i]);
+ }
+ }
+}
+
/**
* snd_dmaengine_pcm_register - Register a dmaengine based PCM device
* @dev: The parent device for the PCM device
@@ -225,23 +247,15 @@ int snd_dmaengine_pcm_register(struct device *dev,
const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
struct dmaengine_pcm *pcm;
- unsigned int i;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return -ENOMEM;
pcm->config = config;
+ pcm->flags = flags;
- if (flags & SND_DMAENGINE_PCM_FLAG_COMPAT)
- pcm->compat = true;
-
- if (!(flags & SND_DMAENGINE_PCM_FLAG_NO_DT) && dev->of_node) {
- for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
- pcm->chan[i] = of_dma_request_slave_channel(dev->of_node,
- dmaengine_pcm_dma_channel_names[i]);
- }
- }
+ dmaengine_pcm_request_chan_of(pcm, dev->of_node);
if (flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE)
return snd_soc_add_platform(dev, &pcm->platform,
@@ -272,8 +286,11 @@ void snd_dmaengine_pcm_unregister(struct device *dev)
pcm = soc_platform_to_pcm(platform);
for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
- if (pcm->chan[i])
+ if (pcm->chan[i]) {
dma_release_channel(pcm->chan[i]);
+ if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
+ break;
+ }
}
snd_soc_remove_platform(platform);
--
1.8.0
3
19
[alsa-devel] [PATCH 1/2] ASoC: mxs-sgtl5000: Remove unneeded 'ret' variable
by Fabio Estevam 06 May '13
by Fabio Estevam 06 May '13
06 May '13
From: Fabio Estevam <fabio.estevam(a)freescale.com>
Variable 'ret' is not needed here, so just remove it.
Signed-off-by: Fabio Estevam <fabio.estevam(a)freescale.com>
---
sound/soc/mxs/mxs-sgtl5000.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c
index b1d9b5e..4f74b05 100644
--- a/sound/soc/mxs/mxs-sgtl5000.c
+++ b/sound/soc/mxs/mxs-sgtl5000.c
@@ -116,7 +116,7 @@ static int mxs_sgtl5000_probe_dt(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *saif_np[2], *codec_np;
- int i, ret = 0;
+ int i;
if (!np)
return 1; /* no device tree */
@@ -142,7 +142,7 @@ static int mxs_sgtl5000_probe_dt(struct platform_device *pdev)
of_node_put(saif_np[0]);
of_node_put(saif_np[1]);
- return ret;
+ return 0;
}
static int mxs_sgtl5000_probe(struct platform_device *pdev)
--
1.7.9.5
3
5
[alsa-devel] [PATCH] ALSA: hda - delay resume haswell hdmi codec in system resume
by mengdong.lin@intel.com 06 May '13
by mengdong.lin@intel.com 06 May '13
06 May '13
From: Mengdong Lin <mengdong.lin(a)intel.com>
In system resume, Haswell codec cannot be programmed to D0 before Gfx driver
initializes the display pipeline and audio, which will trigger an unsol event
on the pin with HDMI/DP cable connected. Otherwise, the connected pin will
stay in D3 with right channel muted and thus no sound can be heard.
This patch
- adds a codec flag to delay resuming a codec. System resume will skip the
codecs if this flag is set, and these codecs will be resumed on later codec
access.
- adds a set_power_state ops for Haswell HDMI codec. In a delayed resume, this
ops will enable and wait for the unsol event, and then resume the codec. A
300ms timeout is set in case unsol event is lost.
Signed-off-by: Mengdong Lin <mengdong.lin(a)intel.com>
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index 04b5738..bcb7205 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -5508,11 +5508,15 @@ int snd_hda_resume(struct hda_bus *bus)
struct hda_codec *codec;
list_for_each_entry(codec, &bus->codec_list, list) {
- hda_call_codec_resume(codec);
+ if (codec->support_delay_resume)
+ codec->resume_delayed = 1;
+ else
+ hda_call_codec_resume(codec);
}
return 0;
}
EXPORT_SYMBOL_HDA(snd_hda_resume);
+
#endif /* CONFIG_PM */
/*
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index 23ca172..5b5e5f4 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -886,6 +886,8 @@ struct hda_codec {
unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */
unsigned int pm_down_notified:1; /* PM notified to controller */
unsigned int in_pm:1; /* suspend/resume being performed */
+ unsigned int support_delay_resume:1; /* codec support delay resume */
+ unsigned int resume_delayed:1; /* resume delayed by PM */
int power_transition; /* power-state in transition */
int power_count; /* current (global) power refcount */
struct delayed_work power_work; /* delayed task for powerdown */
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 78e1827..d116908 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -98,6 +98,14 @@ struct hdmi_spec {
*/
struct hda_multi_out multiout;
struct hda_pcm_stream pcm_playback;
+
+#ifdef CONFIG_PM
+ /*
+ * Non-generic Intel Haswell specific
+ */
+ unsigned int ready_to_resume:1;
+ wait_queue_head_t resume_wq;
+#endif
};
@@ -977,6 +985,13 @@ static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
if (pin_idx < 0)
return;
+#ifdef CONFIG_PM
+ if (codec->resume_delayed) {
+ spec->ready_to_resume = 1;
+ wake_up(&spec->resume_wq);
+ }
+#endif
+
hdmi_present_sense(&spec->pins[pin_idx], 1);
snd_hda_jack_report_sync(codec);
}
@@ -1846,6 +1861,63 @@ static const struct hda_fixup hdmi_fixups[] = {
};
+#ifdef CONFIG_PM
+static void intel_haswell_wait_ready_to_resume(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx;
+
+ spec->ready_to_resume = 0;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin;
+ hda_nid_t pin_nid;
+ struct hda_jack_tbl *jack;
+
+ per_pin = &spec->pins[pin_idx];
+ pin_nid = per_pin->pin_nid;
+ jack = snd_hda_jack_tbl_get(codec, pin_nid);
+ if (jack)
+ snd_hda_codec_write(codec, pin_nid, 0,
+ AC_VERB_SET_UNSOLICITED_ENABLE,
+ AC_USRSP_EN | jack->tag);
+ }
+
+ wait_event_timeout(spec->resume_wq,
+ spec->ready_to_resume, msecs_to_jiffies(300));
+ if (!spec->ready_to_resume)
+ snd_printd(KERN_WARNING "HDMI: Haswell not ready to resume\n");
+}
+
+static void intel_haswell_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+ unsigned int power_state)
+{
+ if (codec->resume_delayed && power_state == AC_PWRST_D0) {
+ intel_haswell_wait_ready_to_resume(codec);
+ codec->resume_delayed = 0;
+ }
+
+ snd_hda_codec_read(codec, fg, 0,
+ AC_VERB_SET_POWER_STATE,
+ power_state);
+
+ snd_hda_codec_set_power_to_all(codec, fg, power_state);
+}
+
+static inline void intel_haswell_allow_delay_resume(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+
+ init_waitqueue_head(&spec->resume_wq);
+ codec->support_delay_resume = 1;
+
+ codec->patch_ops.set_power_state =
+ intel_haswell_set_power_state;
+}
+#else
+define intel_haswell_allow_delay_resume NULL
+#endif
+
static int patch_generic_hdmi(struct hda_codec *codec)
{
struct hdmi_spec *spec;
@@ -1868,6 +1940,10 @@ static int patch_generic_hdmi(struct hda_codec *codec)
return -EINVAL;
}
codec->patch_ops = generic_hdmi_patch_ops;
+
+ if (codec->vendor_id == 0x80862807)
+ intel_haswell_allow_delay_resume(codec);
+
generic_hdmi_init_per_pins(codec);
init_channel_allocations();
--
1.7.10.4
4
51
Classic Wii Remotes provide a speaker on the device. We can stream PCM
data, mute and change volume via special protocol requests and remote
audio registers.
The device supports several different formats, but fully understood are
only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM.
Theoretically, we can set data-rates from 183Hz up to 12MHz, but a
realistic range for Bluetooth l2cap is 1500-4000 Hz.
Data is streamed as 20bytes blocks and must be sent in a constant rate.
There is no way to read the current position. It would be way too slow,
anyway.
Signed-off-by: David Herrmann <dh.herrmann(a)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(a)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)
--
1.8.2.1
2
6