diff options
author | Imre Kaloz <kaloz@openwrt.org> | 2010-02-22 13:54:47 +0000 |
---|---|---|
committer | Imre Kaloz <kaloz@openwrt.org> | 2010-02-22 13:54:47 +0000 |
commit | 212a481ea99ac04e2d3415cf3627362ead9a277d (patch) | |
tree | 1bbe491ef9470b8edd7be199072539d7e18a1996 /target/linux/ubicom32/files/sound | |
parent | 73f82eb93e48c01ee58eb27031e204332da54cd8 (diff) | |
download | upstream-212a481ea99ac04e2d3415cf3627362ead9a277d.tar.gz upstream-212a481ea99ac04e2d3415cf3627362ead9a277d.tar.bz2 upstream-212a481ea99ac04e2d3415cf3627362ead9a277d.zip |
[ubicom32]: move new files out from platform support patch
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@19815 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/ubicom32/files/sound')
8 files changed, 2808 insertions, 0 deletions
diff --git a/target/linux/ubicom32/files/sound/ubicom32/Kconfig b/target/linux/ubicom32/files/sound/ubicom32/Kconfig new file mode 100644 index 0000000000..c57bd34d97 --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/Kconfig @@ -0,0 +1,42 @@ +# ALSA Ubicom32 drivers + +menuconfig SND_UBI32 + tristate "Ubicom32 sound devices" + select SND_PCM + default n + help + Say Y here to include support for audio on the Ubicom32 platform. + To compile this driver as a module, say M here: the module will be + called snd_ubi32. + +if SND_UBI32 + +config SND_UBI32_AUDIO_GENERIC_CAPTURE + bool "Generic Capture Support" + default n + help + Use this option to support ADCs which don't require special drivers. + +config SND_UBI32_AUDIO_GENERIC + bool "Generic Playback Support" + default n + help + Use this option to support DACs which don't require special drivers. + +comment "I2C Based Codecs" + +config SND_UBI32_AUDIO_CS4350 + bool "Cirrus Logic CS4350 DAC" + depends on I2C + default n + help + Support for the Cirrus Logic CS4350 DAC. + +config SND_UBI32_AUDIO_CS4384 + bool "Cirrus Logic CS4384 DAC" + depends on I2C + default n + help + Support for the Cirrus Logic CS4384 DAC. + +endif #SND_UBI32 diff --git a/target/linux/ubicom32/files/sound/ubicom32/Makefile b/target/linux/ubicom32/files/sound/ubicom32/Makefile new file mode 100644 index 0000000000..ffdcc298a1 --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/Makefile @@ -0,0 +1,41 @@ +# +# sound/ubicom32/Makefile +# Makefile for ALSA +# +# (C) Copyright 2009, Ubicom, Inc. +# +# This file is part of the Ubicom32 Linux Kernel Port. +# +# The Ubicom32 Linux Kernel Port 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. +# +# The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, +# see <http://www.gnu.org/licenses/>. +# +# Ubicom32 implementation derived from (with many thanks): +# arch/m68knommu +# arch/blackfin +# arch/parisc +# + +CFLAGS_ubi32.o += -O2 +snd-ubi32-pcm-objs := ubi32-pcm.o +snd-ubi32-generic-objs := ubi32-generic.o +snd-ubi32-generic-capture-objs := ubi32-generic-capture.o +snd-ubi32-cs4350-objs := ubi32-cs4350.o +snd-ubi32-cs4384-objs := ubi32-cs4384.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_UBI32) += snd-ubi32-pcm.o +obj-$(CONFIG_SND_UBI32_AUDIO_GENERIC) += snd-ubi32-generic.o +obj-$(CONFIG_SND_UBI32_AUDIO_GENERIC_CAPTURE) += snd-ubi32-generic-capture.o +obj-$(CONFIG_SND_UBI32_AUDIO_CS4350) += snd-ubi32-cs4350.o +obj-$(CONFIG_SND_UBI32_AUDIO_CS4384) += snd-ubi32-cs4384.o diff --git a/target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4350.c b/target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4350.c new file mode 100644 index 0000000000..7e6f9acbb3 --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4350.c @@ -0,0 +1,583 @@ +/* + * sound/ubicom32/ubi32-cs4350.c + * Interface to ubicom32 virtual audio peripheral - using CS4350 DAC + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port 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. + * + * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, + * see <http://www.gnu.org/licenses/>. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/tlv.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include "ubi32.h" + +#define DRIVER_NAME "snd-ubi32-cs4350" + +/* + * Module properties + */ +static const struct i2c_device_id snd_ubi32_cs4350_id[] = { + {"cs4350", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ubicom32audio_id); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ + +/* + * The dB scale for the Cirrus Logic cs4350. The output range is from + * -127.5 dB to 0 dB. + */ +static const DECLARE_TLV_DB_SCALE(snd_ubi32_cs4350_db, -12750, 50, 0); + +#define ubi32_cs4350_mute_info snd_ctl_boolean_stereo_info + +/* + * Private data for cs4350 chip + */ +struct ubi32_cs4350_priv { + /* + * The current volume settings + */ + uint8_t volume[2]; + + /* + * Bitmask of mutes MSB (unused, ..., unused, right_ch, left_ch) LSB + */ + uint8_t mute; + + /* + * Lock to protect this struct because callbacks are not atomic. + */ + spinlock_t lock; +}; + +/* + * The info for the cs4350. The volume currently has one channel, + * and 255 possible settings. + */ +static int ubi32_cs4350_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; // 8 bits in cirrus logic cs4350 volume register + return 0; +} + +static int ubi32_cs4350_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *ubi32_priv = snd_kcontrol_chip(kcontrol); + struct ubi32_cs4350_priv *cs4350_priv; + unsigned long flags; + + cs4350_priv = snd_ubi32_priv_get_drv(ubi32_priv); + + spin_lock_irqsave(&cs4350_priv->lock, flags); + + ucontrol->value.integer.value[0] = cs4350_priv->volume[0]; + ucontrol->value.integer.value[1] = cs4350_priv->volume[1]; + + spin_unlock_irqrestore(&cs4350_priv->lock, flags); + + return 0; +} + +static int ubi32_cs4350_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *ubi32_priv = snd_kcontrol_chip(kcontrol); + struct i2c_client *client = (struct i2c_client *)ubi32_priv->client; + struct ubi32_cs4350_priv *cs4350_priv; + unsigned long flags; + int ret, changed; + char send[2]; + uint8_t volume_reg_value_left, volume_reg_value_right; + + changed = 0; + + cs4350_priv = snd_ubi32_priv_get_drv(ubi32_priv); + volume_reg_value_left = 255 - (ucontrol->value.integer.value[0] & 0xFF); + volume_reg_value_right = 255 - (ucontrol->value.integer.value[1] & 0xFF); + +#if SND_UBI32_DEBUG + snd_printk(KERN_INFO "Setting volume: writing %d,%d to CS4350 volume registers\n", volume_reg_value_left, volume_reg_value_right); +#endif + spin_lock_irqsave(&cs4350_priv->lock, flags); + + if (cs4350_priv->volume[0] != ucontrol->value.integer.value[0]) { + send[0] = 0x05; // left channel + send[1] = volume_reg_value_left; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set channel A volume on CS4350\n"); + return changed; + } + cs4350_priv->volume[0] = ucontrol->value.integer.value[0]; + changed = 1; + } + + if (cs4350_priv->volume[1] != ucontrol->value.integer.value[1]) { + send[0] = 0x06; // right channel + send[1] = volume_reg_value_right; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set channel B volume on CS4350\n"); + return changed; + } + cs4350_priv->volume[1] = ucontrol->value.integer.value[1]; + changed = 1; + } + + spin_unlock_irqrestore(&cs4350_priv->lock, flags); + + return changed; +} + +static struct snd_kcontrol_new ubi32_cs4350_volume __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "PCM Playback Volume", + .info = ubi32_cs4350_volume_info, + .get = ubi32_cs4350_volume_get, + .put = ubi32_cs4350_volume_put, + .tlv.p = snd_ubi32_cs4350_db, +}; + +static int ubi32_cs4350_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *ubi32_priv = snd_kcontrol_chip(kcontrol); + struct ubi32_cs4350_priv *cs4350_priv; + unsigned long flags; + + cs4350_priv = snd_ubi32_priv_get_drv(ubi32_priv); + + spin_lock_irqsave(&cs4350_priv->lock, flags); + + ucontrol->value.integer.value[0] = cs4350_priv->mute & 1; + ucontrol->value.integer.value[1] = (cs4350_priv->mute & (1 << 1)) ? 1 : 0; + + spin_unlock_irqrestore(&cs4350_priv->lock, flags); + + return 0; +} + +static int ubi32_cs4350_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *ubi32_priv = snd_kcontrol_chip(kcontrol); + struct i2c_client *client = (struct i2c_client *)ubi32_priv->client; + struct ubi32_cs4350_priv *cs4350_priv; + unsigned long flags; + int ret, changed; + char send[2]; + char recv[1]; + uint8_t mute; + + changed = 0; + + cs4350_priv = snd_ubi32_priv_get_drv(ubi32_priv); + + spin_lock_irqsave(&cs4350_priv->lock, flags); + + if ((cs4350_priv->mute & 1) != ucontrol->value.integer.value[0]) { + send[0] = 0x04; + ret = i2c_master_send(client, send, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed to write to mute register: channel 0\n"); + return changed; + } + + ret = i2c_master_recv(client, recv, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed to read mute register: channel 0\n"); + return changed; + } + + mute = recv[0]; + + if (ucontrol->value.integer.value[0]) { + cs4350_priv->mute |= 1; + mute &= ~(1 << 4); +#if SND_UBI32_DEBUG + snd_printk(KERN_INFO "Unmuted channel A\n"); +#endif + } else { + cs4350_priv->mute &= ~1; + mute |= (1 << 4); +#if SND_UBI32_DEBUG + snd_printk(KERN_INFO "Muted channel A\n"); +#endif + } + + send[0] = 0x04; + send[1] = mute; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set channel A mute on CS4350\n"); + return changed; + } + changed = 1; + } + + if (((cs4350_priv->mute & 2) >> 1) != ucontrol->value.integer.value[1]) { + send[0] = 0x04; + ret = i2c_master_send(client, send, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed to write to mute register: channel 1\n"); + return changed; + } + + ret = i2c_master_recv(client, recv, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed to read mute register: channel 1\n"); + return changed; + } + + mute = recv[0]; + + if (ucontrol->value.integer.value[1]) { + cs4350_priv->mute |= (1 << 1); + mute &= ~(1 << 3); +#if SND_UBI32_DEBUG + snd_printk(KERN_INFO "Unmuted channel B\n"); +#endif + } else { + cs4350_priv->mute &= ~(1 << 1); + mute |= (1 << 3); +#if SND_UBI32_DEBUG + snd_printk(KERN_INFO "Muted channel B\n"); +#endif + } + + send[0] = 0x04; + send[1] = mute; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set channel A mute on CS4350\n"); + return changed; + } + changed = 1; + } + + spin_unlock_irqrestore(&cs4350_priv->lock, flags); + + return changed; +} + +static struct snd_kcontrol_new ubi32_cs4350_mute __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "PCM Playback Switch", + .info = ubi32_cs4350_mute_info, + .get = ubi32_cs4350_mute_get, + .put = ubi32_cs4350_mute_put, +}; + +/* + * snd_ubi32_cs4350_free + * Card private data free function + */ +void snd_ubi32_cs4350_free(struct snd_card *card) +{ + struct ubi32_snd_priv *ubi32_priv; + struct ubi32_cs4350_priv *cs4350_priv; + + ubi32_priv = card->private_data; + cs4350_priv = snd_ubi32_priv_get_drv(ubi32_priv); + if (cs4350_priv) { + kfree(cs4350_priv); + } +} + +/* + * snd_ubi32_cs4350_dac_init + */ +static int snd_ubi32_cs4350_dac_init(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret; + char send[2]; + char recv[8]; + + /* + * Initialize the CS4350 DAC over the I2C interface + */ + snd_printk(KERN_INFO "Initializing CS4350 DAC\n"); + + /* + * Register 0x01: device/revid + */ + send[0] = 0x01; + ret = i2c_master_send(client, send, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed 1st attempt to write to CS4350 register 0x01\n"); + goto fail; + } + ret = i2c_master_recv(client, recv, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed initial read of CS4350 registers\n"); + goto fail; + } + snd_printk(KERN_INFO "CS4350 DAC Device/Rev: %08x\n", recv[0]); + + /* + * Register 0x02: Mode control + * I2S DIF[2:0] = 001, no De-Emphasis, Auto speed mode + */ + send[0] = 0x02; + send[1] = 0x10; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set CS4350 to I2S mode\n"); + goto fail; + } + + /* + * Register 0x05/0x06: Volume control + * Channel A volume set to 0 dB + * Channel B volume set to 0 dB + */ + send[0] = 0x05; + send[1] = 0x00; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set channel A volume on CS4350\n"); + goto fail; + } + + send[0] = 0x06; + send[1] = 0x00; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set channel A volume on CS4350\n"); + goto fail; + } + + /* + * Make sure the changes took place, this helps verify we are talking to + * the correct chip. + */ + send[0] = 0x81; + ret = i2c_master_send(client, send, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed to initiate readback\n"); + goto fail; + } + + ret = i2c_master_recv(client, recv, 8); + if (ret != 8) { + snd_printk(KERN_ERR "Failed second read of CS4350 registers\n"); + goto fail; + } + + if ((recv[1] != 0x10) || (recv[4] != 0x00) || (recv[5] != 0x00)) { + snd_printk(KERN_ERR "Failed to initialize CS4350 DAC\n"); + goto fail; + } + + snd_printk(KERN_INFO "CS4350 DAC Initialized\n"); + return 0; + +fail: + return -ENODEV; +} + +/* + * snd_ubi32_cs4350_i2c_probe + */ +static int snd_ubi32_cs4350_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + struct ubi32_cs4350_priv *cs4350_priv; + int err, ret; + struct platform_device *pdev; + + pdev = client->dev.platform_data; + if (!pdev) { + return -ENODEV; + } + + /* + * Initialize the CS4350 DAC + */ + ret = snd_ubi32_cs4350_dac_init(client, id); + if (ret < 0) { + /* + * Initialization failed. Propagate the error. + */ + return ret; + } + + /* + * Create a snd_card structure + */ + card = snd_card_new(index, "Ubi32-CS4350", THIS_MODULE, sizeof(struct ubi32_snd_priv)); + if (card == NULL) { + return -ENOMEM; + } + + card->private_free = snd_ubi32_cs4350_free; /* Not sure if correct */ + ubi32_priv = card->private_data; + + /* + * CS4350 DAC has a minimum sample rate of 30khz and an + * upper limit of 216khz for it's auto-detect. + */ + ubi32_priv->min_sample_rate = 30000; + ubi32_priv->max_sample_rate = 216000; + + /* + * Initialize the snd_card's private data structure + */ + ubi32_priv->card = card; + ubi32_priv->client = client; + + /* + * Create our private data structure + */ + cs4350_priv = kzalloc(sizeof(struct ubi32_cs4350_priv), GFP_KERNEL); + if (!cs4350_priv) { + snd_card_free(card); + return -ENOMEM; + } + snd_ubi32_priv_set_drv(ubi32_priv, cs4350_priv); + spin_lock_init(&cs4350_priv->lock); + + /* + * Initial volume is set to max by probe function + */ + cs4350_priv->volume[0] = 0xFF; + cs4350_priv->volume[1] = 0xFF; + + /* + * The CS4350 starts off unmuted (bit set = not muted) + */ + cs4350_priv->mute = 3; + + /* + * Create the new PCM instance + */ + err = snd_ubi32_pcm_probe(ubi32_priv, pdev); + if (err < 0) { + snd_card_free(card); + return err; /* What is err? Need to include correct file */ + } + + strcpy(card->driver, "Ubi32-CS4350"); + strcpy(card->shortname, "Ubi32-CS4350"); + snprintf(card->longname, sizeof(card->longname), + "%s at sendirq=%d.%d recvirq=%d.%d regs=%p", + card->shortname, ubi32_priv->tx_irq, ubi32_priv->irq_idx, + ubi32_priv->rx_irq, ubi32_priv->irq_idx, ubi32_priv->adr); + + snd_card_set_dev(card, &client->dev); + + /* + * Set up the mixer components + */ + err = snd_ctl_add(card, snd_ctl_new1(&ubi32_cs4350_volume, ubi32_priv)); + if (err) { + snd_printk(KERN_WARNING "Failed to add volume mixer control\n"); + } + err = snd_ctl_add(card, snd_ctl_new1(&ubi32_cs4350_mute, ubi32_priv)); + if (err) { + snd_printk(KERN_WARNING "Failed to add mute mixer control\n"); + } + + /* + * Register the sound card + */ + if ((err = snd_card_register(card)) != 0) { + snd_printk(KERN_WARNING "snd_card_register error\n"); + } + + /* + * Store card for access from other methods + */ + i2c_set_clientdata(client, card); + + return 0; +} + +/* + * snd_ubi32_cs4350_i2c_remove + */ +static int __devexit snd_ubi32_cs4350_i2c_remove(struct i2c_client *client) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + + card = i2c_get_clientdata(client); + + ubi32_priv = card->private_data; + snd_ubi32_pcm_remove(ubi32_priv); + + snd_card_free(i2c_get_clientdata(client)); + i2c_set_clientdata(client, NULL); + + return 0; +} + +/* + * I2C driver description + */ +static struct i2c_driver snd_ubi32_cs4350_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .id_table = snd_ubi32_cs4350_id, + .probe = snd_ubi32_cs4350_i2c_probe, + .remove = __devexit_p(snd_ubi32_cs4350_i2c_remove), +}; + +/* + * Driver init + */ +static int __init snd_ubi32_cs4350_init(void) +{ + return i2c_add_driver(&snd_ubi32_cs4350_driver); +} +module_init(snd_ubi32_cs4350_init); + +/* + * snd_ubi32_cs4350_exit + */ +static void __exit snd_ubi32_cs4350_exit(void) +{ + i2c_del_driver(&snd_ubi32_cs4350_driver); +} +module_exit(snd_ubi32_cs4350_exit); + +/* + * Module properties + */ +MODULE_ALIAS("i2c:" DRIVER_NAME); +MODULE_AUTHOR("Patrick Tjin"); +MODULE_DESCRIPTION("Driver for Ubicom32 audio devices CS4350"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4384.c b/target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4384.c new file mode 100644 index 0000000000..2679267732 --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4384.c @@ -0,0 +1,996 @@ +/* + * sound/ubicom32/ubi32-cs4384.c + * Interface to ubicom32 virtual audio peripheral - using CS4384 DAC + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port 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. + * + * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, + * see <http://www.gnu.org/licenses/>. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/tlv.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <asm/ip5000.h> +#include <asm/gpio.h> +#include <asm/audio.h> +#include <asm/ubi32-cs4384.h> +#include "ubi32.h" + +#define DRIVER_NAME "snd-ubi32-cs4384" + +/* + * Module properties + */ +static const struct i2c_device_id snd_ubi32_cs4384_id[] = { + {"cs4384", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ubicom32audio_id); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ + +/* + * Mixer properties + */ +enum { + /* + * Be careful of changing the order of these IDs, they + * are used to index the volume array. + */ + SND_UBI32_CS4384_FRONT_ID, + SND_UBI32_CS4384_SURROUND_ID, + SND_UBI32_CS4384_CENTER_ID, + SND_UBI32_CS4384_LFE_ID, + SND_UBI32_CS4384_REAR_ID, + + /* + * This should be the last ID + */ + SND_UBI32_CS4384_LAST_ID, +}; +static const u8_t snd_ubi32_cs4384_ch_ofs[] = {0, 2, 4, 5, 6}; + +static const DECLARE_TLV_DB_SCALE(snd_ubi32_cs4384_db, -12750, 50, 0); + +#define snd_ubi32_cs4384_info_mute snd_ctl_boolean_stereo_info +#define snd_ubi32_cs4384_info_mute_mono snd_ctl_boolean_mono_info + +/* + * Mixer controls + */ +static int snd_ubi32_cs4384_info_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); +static int snd_ubi32_cs4384_get_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +static int snd_ubi32_cs4384_put_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +static int snd_ubi32_cs4384_get_mute(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +static int snd_ubi32_cs4384_put_mute(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); + +/* + * Make sure to update these if the structure below is changed + */ +#define SND_UBI32_MUTE_CTL_START 5 +#define SND_UBI32_MUTE_CTL_END 9 +static struct snd_kcontrol_new snd_ubi32_cs4384_controls[] __devinitdata = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Front Playback Volume", + .info = snd_ubi32_cs4384_info_volume, + .get = snd_ubi32_cs4384_get_volume, + .put = snd_ubi32_cs4384_put_volume, + .private_value = SND_UBI32_CS4384_FRONT_ID, + .tlv = { + .p = snd_ubi32_cs4384_db, + }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Surround Playback Volume", + .info = snd_ubi32_cs4384_info_volume, + .get = snd_ubi32_cs4384_get_volume, + .put = snd_ubi32_cs4384_put_volume, + .private_value = SND_UBI32_CS4384_SURROUND_ID, + .tlv = { + .p = snd_ubi32_cs4384_db, + }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Center Playback Volume", + .info = snd_ubi32_cs4384_info_volume, + .get = snd_ubi32_cs4384_get_volume, + .put = snd_ubi32_cs4384_put_volume, + .private_value = SND_UBI32_CS4384_CENTER_ID, + .tlv = { + .p = snd_ubi32_cs4384_db, + }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "LFE Playback Volume", + .info = snd_ubi32_cs4384_info_volume, + .get = snd_ubi32_cs4384_get_volume, + .put = snd_ubi32_cs4384_put_volume, + .private_value = SND_UBI32_CS4384_LFE_ID, + .tlv = { + .p = snd_ubi32_cs4384_db, + }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Rear Playback Volume", + .info = snd_ubi32_cs4384_info_volume, + .get = snd_ubi32_cs4384_get_volume, + .put = snd_ubi32_cs4384_put_volume, + .private_value = SND_UBI32_CS4384_REAR_ID, + .tlv = { + .p = snd_ubi32_cs4384_db, + }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Front Playback Switch", + .info = snd_ubi32_cs4384_info_mute, + .get = snd_ubi32_cs4384_get_mute, + .put = snd_ubi32_cs4384_put_mute, + .private_value = SND_UBI32_CS4384_FRONT_ID, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Surround Playback Switch", + .info = snd_ubi32_cs4384_info_mute, + .get = snd_ubi32_cs4384_get_mute, + .put = snd_ubi32_cs4384_put_mute, + .private_value = SND_UBI32_CS4384_SURROUND_ID, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Center Playback Switch", + .info = snd_ubi32_cs4384_info_mute_mono, + .get = snd_ubi32_cs4384_get_mute, + .put = snd_ubi32_cs4384_put_mute, + .private_value = SND_UBI32_CS4384_CENTER_ID, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "LFE Playback Switch", + .info = snd_ubi32_cs4384_info_mute_mono, + .get = snd_ubi32_cs4384_get_mute, + .put = snd_ubi32_cs4384_put_mute, + .private_value = SND_UBI32_CS4384_LFE_ID, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Rear Playback Switch", + .info = snd_ubi32_cs4384_info_mute, + .get = snd_ubi32_cs4384_get_mute, + .put = snd_ubi32_cs4384_put_mute, + .private_value = SND_UBI32_CS4384_REAR_ID, + }, +}; + +/* + * Our private data + */ +struct snd_ubi32_cs4384_priv { + /* + * Array of current volumes + * (L, R, SL, SR, C, LFE, RL, RR) + */ + uint8_t volume[8]; + + /* + * Bitmask of mutes + * MSB (RR, RL, LFE, C, SR, SL, R, L) LSB + */ + uint8_t mute; + + /* + * Array of controls + */ + struct snd_kcontrol *kctls[ARRAY_SIZE(snd_ubi32_cs4384_controls)]; + + /* + * Lock to protect our card + */ + spinlock_t lock; +}; + +/* + * snd_ubi32_cs4384_info_volume + */ +static int snd_ubi32_cs4384_info_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + unsigned int id = (unsigned int)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + if ((id != SND_UBI32_CS4384_LFE_ID) && + (id != SND_UBI32_CS4384_CENTER_ID)) { + uinfo->count = 2; + } + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +/* + * snd_ubi32_cs4384_get_volume + */ +static int snd_ubi32_cs4384_get_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *priv = snd_kcontrol_chip(kcontrol); + struct snd_ubi32_cs4384_priv *cs4384_priv; + unsigned int id = (unsigned int)kcontrol->private_value; + int ch = snd_ubi32_cs4384_ch_ofs[id]; + unsigned long flags; + + if (id >= SND_UBI32_CS4384_LAST_ID) { + return -EINVAL; + } + + cs4384_priv = snd_ubi32_priv_get_drv(priv); + + spin_lock_irqsave(&cs4384_priv->lock, flags); + + ucontrol->value.integer.value[0] = cs4384_priv->volume[ch]; + if ((id != SND_UBI32_CS4384_LFE_ID) && + (id != SND_UBI32_CS4384_CENTER_ID)) { + ch++; + ucontrol->value.integer.value[1] = cs4384_priv->volume[ch]; + } + + spin_unlock_irqrestore(&cs4384_priv->lock, flags); + + return 0; +} + +/* + * snd_ubi32_cs4384_put_volume + */ +static int snd_ubi32_cs4384_put_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *priv = snd_kcontrol_chip(kcontrol); + struct i2c_client *client = (struct i2c_client *)priv->client; + struct snd_ubi32_cs4384_priv *cs4384_priv; + unsigned int id = (unsigned int)kcontrol->private_value; + int ch = snd_ubi32_cs4384_ch_ofs[id]; + unsigned long flags; + unsigned char send[3]; + int nch; + int ret = -EINVAL; + + if (id >= SND_UBI32_CS4384_LAST_ID) { + return -EINVAL; + } + + cs4384_priv = snd_ubi32_priv_get_drv(priv); + + spin_lock_irqsave(&cs4384_priv->lock, flags); + + send[0] = 0; + switch (id) { + case SND_UBI32_CS4384_REAR_ID: + send[0] = 0x06; + + /* + * Fall through + */ + + case SND_UBI32_CS4384_SURROUND_ID: + send[0] += 0x03; + + /* + * Fall through + */ + + case SND_UBI32_CS4384_FRONT_ID: + send[0] += 0x8B; + nch = 2; + send[1] = 255 - (ucontrol->value.integer.value[0] & 0xFF); + send[2] = 255 - (ucontrol->value.integer.value[1] & 0xFF); + cs4384_priv->volume[ch++] = send[1]; + cs4384_priv->volume[ch] = send[2]; + break; + + case SND_UBI32_CS4384_LFE_ID: + send[0] = 0x81; + + /* + * Fall through + */ + + case SND_UBI32_CS4384_CENTER_ID: + send[0] += 0x11; + nch = 1; + send[1] = 255 - (ucontrol->value.integer.value[0] & 0xFF); + cs4384_priv->volume[ch] = send[1]; + break; + + default: + spin_unlock_irqrestore(&cs4384_priv->lock, flags); + goto done; + + } + + /* + * Send the volume to the chip + */ + nch++; + ret = i2c_master_send(client, send, nch); + if (ret != nch) { + snd_printk(KERN_ERR "Failed to set volume on CS4384\n"); + } + +done: + spin_unlock_irqrestore(&cs4384_priv->lock, flags); + + return ret; +} + +/* + * snd_ubi32_cs4384_get_mute + */ +static int snd_ubi32_cs4384_get_mute(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *priv = snd_kcontrol_chip(kcontrol); + struct snd_ubi32_cs4384_priv *cs4384_priv; + unsigned int id = (unsigned int)kcontrol->private_value; + int ch = snd_ubi32_cs4384_ch_ofs[id]; + unsigned long flags; + + if (id >= SND_UBI32_CS4384_LAST_ID) { + return -EINVAL; + } + + cs4384_priv = snd_ubi32_priv_get_drv(priv); + + spin_lock_irqsave(&cs4384_priv->lock, flags); + + ucontrol->value.integer.value[0] = !(cs4384_priv->mute & (1 << ch)); + + if ((id != SND_UBI32_CS4384_LFE_ID) && + (id != SND_UBI32_CS4384_CENTER_ID)) { + ch++; + ucontrol->value.integer.value[1] = !(cs4384_priv->mute & (1 << ch)); + } + + spin_unlock_irqrestore(&cs4384_priv->lock, flags); + + return 0; +} + +/* + * snd_ubi32_cs4384_put_mute + */ +static int snd_ubi32_cs4384_put_mute(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct ubi32_snd_priv *priv = snd_kcontrol_chip(kcontrol); + struct i2c_client *client = (struct i2c_client *)priv->client; + struct snd_ubi32_cs4384_priv *cs4384_priv; + unsigned int id = (unsigned int)kcontrol->private_value; + int ch = snd_ubi32_cs4384_ch_ofs[id]; + unsigned long flags; + unsigned char send[2]; + int ret = -EINVAL; + + if (id >= SND_UBI32_CS4384_LAST_ID) { + return -EINVAL; + } + + cs4384_priv = snd_ubi32_priv_get_drv(priv); + + spin_lock_irqsave(&cs4384_priv->lock, flags); + + if (ucontrol->value.integer.value[0]) { + cs4384_priv->mute &= ~(1 << ch); + } else { + cs4384_priv->mute |= (1 << ch); + } + + if ((id != SND_UBI32_CS4384_LFE_ID) && (id != SND_UBI32_CS4384_CENTER_ID)) { + ch++; + if (ucontrol->value.integer.value[1]) { + cs4384_priv->mute &= ~(1 << ch); + } else { + cs4384_priv->mute |= (1 << ch); + } + } + + /* + * Update the chip's mute reigster + */ + send[0] = 0x09; + send[1] = cs4384_priv->mute; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set mute on CS4384\n"); + } + + spin_unlock_irqrestore(&cs4384_priv->lock, flags); + + return ret; +} + +/* + * snd_ubi32_cs4384_mixer + * Setup the mixer controls + */ +static int __devinit snd_ubi32_cs4384_mixer(struct ubi32_snd_priv *priv) +{ + struct snd_card *card = priv->card; + struct snd_ubi32_cs4384_priv *cs4384_priv; + int i; + + cs4384_priv = snd_ubi32_priv_get_drv(priv); + for (i = 0; i < ARRAY_SIZE(snd_ubi32_cs4384_controls); i++) { + int err; + + cs4384_priv->kctls[i] = snd_ctl_new1(&snd_ubi32_cs4384_controls[i], priv); + err = snd_ctl_add(card, cs4384_priv->kctls[i]); + if (err) { + snd_printk(KERN_WARNING "Failed to add control %d\n", i); + return err; + } + } + return 0; +} + +/* + * snd_ubi32_cs4384_free + * Card private data free function + */ +void snd_ubi32_cs4384_free(struct snd_card *card) +{ + struct snd_ubi32_cs4384_priv *cs4384_priv; + struct ubi32_snd_priv *ubi32_priv; + + ubi32_priv = card->private_data; + cs4384_priv = snd_ubi32_priv_get_drv(ubi32_priv); + if (cs4384_priv) { + kfree(cs4384_priv); + } +} + +/* + * snd_ubi32_cs4384_setup_mclk + */ +static int snd_ubi32_cs4384_setup_mclk(struct ubi32_cs4384_platform_data *pdata) +{ + struct ubicom32_io_port *ioa = (struct ubicom32_io_port *)RA; + struct ubicom32_io_port *ioc = (struct ubicom32_io_port *)RC; + struct ubicom32_io_port *iod = (struct ubicom32_io_port *)RD; + struct ubicom32_io_port *ioe = (struct ubicom32_io_port *)RE; + struct ubicom32_io_port *ioh = (struct ubicom32_io_port *)RH; + unsigned int ctl0; + unsigned int ctlx; + unsigned int div; + + div = pdata->mclk_entries[0].div; + + ctl0 = (1 << 13); + ctlx = ((div - 1) << 16) | (div / 2); + + switch (pdata->mclk_src) { + case UBI32_CS4384_MCLK_PWM_0: + ioc->function |= 2; + ioc->ctl0 |= ctl0; + ioc->ctl1 = ctlx; + if (!ioa->function) { + ioa->function = 3; + } + return 0; + + case UBI32_CS4384_MCLK_PWM_1: + ioc->function |= 2; + ioc->ctl0 |= ctl0 << 16; + ioc->ctl2 = ctlx; + if (!ioe->function) { + ioe->function = 3; + } + return 0; + + case UBI32_CS4384_MCLK_PWM_2: + ioh->ctl0 |= ctl0; + ioh->ctl1 = ctlx; + if (!iod->function) { + iod->function = 3; + } + return 0; + + case UBI32_CS4384_MCLK_CLKDIV_1: + ioa->gpio_mask &= (1 << 7); + ioa->ctl1 &= ~(0x7F << 14); + ioa->ctl1 |= ((div - 1) << 14); + return 0; + + case UBI32_CS4384_MCLK_OTHER: + return 0; + } + + return 1; +} + +/* + * snd_ubi32_cs4384_set_rate + */ +static int snd_ubi32_cs4384_set_rate(struct ubi32_snd_priv *priv, int rate) +{ + struct ubi32_cs4384_platform_data *cpd = priv->pdata->priv_data; + struct ubicom32_io_port *ioa = (struct ubicom32_io_port *)RA; + struct ubicom32_io_port *ioc = (struct ubicom32_io_port *)RC; + struct ubicom32_io_port *ioh = (struct ubicom32_io_port *)RH; + unsigned int ctl; + unsigned int div = 0; + const u16_t mult[] = {64, 96, 128, 192, 256, 384, 512, 768, 1024}; + int i; + int j; + + + for (i = 0; i < sizeof(mult) / sizeof(u16_t); i++) { + for (j = 0; j < cpd->n_mclk; j++) { + if (((unsigned int)rate * (unsigned int)mult[i]) == + cpd->mclk_entries[j].rate) { + div = cpd->mclk_entries[j].div; + break; + } + } + } + + ctl = ((div - 1) << 16) | (div / 2); + + switch (cpd->mclk_src) { + case UBI32_CS4384_MCLK_PWM_0: + ioc->ctl1 = ctl; + return 0; + + case UBI32_CS4384_MCLK_PWM_1: + ioc->ctl2 = ctl; + return 0; + + case UBI32_CS4384_MCLK_PWM_2: + ioh->ctl1 = ctl; + return 0; + + case UBI32_CS4384_MCLK_CLKDIV_1: + ioa->ctl1 &= ~(0x7F << 14); + ioa->ctl1 |= ((div - 1) << 14); + return 0; + + case UBI32_CS4384_MCLK_OTHER: + return 0; + } + + return 1; +} + +/* + * snd_ubi32_cs4384_set_channels + * Mute unused channels + */ +static int snd_ubi32_cs4384_set_channels(struct ubi32_snd_priv *priv, int channels) +{ + struct i2c_client *client = (struct i2c_client *)priv->client; + struct snd_ubi32_cs4384_priv *cs4384_priv; + unsigned char send[2]; + int ret; + int i; + unsigned long flags; + + /* + * Only support 0, 2, 4, 6, 8 channels + */ + if ((channels > 8) || (channels & 1)) { + return -EINVAL; + } + + cs4384_priv = snd_ubi32_priv_get_drv(priv); + spin_lock_irqsave(&cs4384_priv->lock, flags); + + /* + * Address 09h, Mute control + */ + send[0] = 0x09; + send[1] = (unsigned char)(0xFF << channels); + + ret = i2c_master_send(client, send, 2); + + spin_unlock_irqrestore(&cs4384_priv->lock, flags); + + /* + * Notify the system that we changed the mutes + */ + cs4384_priv->mute = (unsigned char)(0xFF << channels); + + for (i = SND_UBI32_MUTE_CTL_START; i < SND_UBI32_MUTE_CTL_END; i++) { + snd_ctl_notify(priv->card, SNDRV_CTL_EVENT_MASK_VALUE, + &cs4384_priv->kctls[i]->id); + } + + if (ret != 2) { + return -ENXIO; + } + + return 0; +} + +/* + * snd_ubi32_cs4384_dac_init + */ +static int snd_ubi32_cs4384_dac_init(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret; + unsigned char send[2]; + unsigned char recv[2]; + + /* + * Initialize the CS4384 DAC over the I2C interface + */ + snd_printk(KERN_INFO "Initializing CS4384 DAC\n"); + + /* + * Register 0x01: device/revid + */ + send[0] = 0x01; + ret = i2c_master_send(client, send, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed 1st attempt to write to CS4384 register 0x01\n"); + goto fail; + } + ret = i2c_master_recv(client, recv, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed initial read of CS4384 registers\n"); + goto fail; + } + snd_printk(KERN_INFO "CS4384 DAC Device/Rev: %08x\n", recv[0]); + + /* + * Register 0x02: Mode Control 1 + * Control Port Enable, PCM, All DACs enabled, Power Down + */ + send[0] = 0x02; + send[1] = 0x81; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set CPEN CS4384\n"); + goto fail; + } + + /* + * Register 0x08: Ramp and Mute + * RMP_UP, RMP_DN, PAMUTE, DAMUTE + */ + send[0] = 0x08; + send[1] = 0xBC; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set CPEN CS4384\n"); + goto fail; + } + + /* + * Register 0x03: PCM Control + * I2S DIF[3:0] = 0001, no De-Emphasis, Auto speed mode + */ + send[0] = 0x03; + send[1] = 0x13; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to set CS4384 to I2S mode\n"); + goto fail; + } + + /* + * Register 0x0B/0x0C: Volume control A1/B1 + * Register 0x0E/0x0F: Volume control A2/B2 + * Register 0x11/0x12: Volume control A3/B3 + * Register 0x14/0x15: Volume control A4/B4 + */ + send[0] = 0x80 | 0x0B; + send[1] = 0x00; + send[2] = 0x00; + ret = i2c_master_send(client, send, 3); + if (ret != 3) { + snd_printk(KERN_ERR "Failed to set ch1 volume on CS4384\n"); + goto fail; + } + + send[0] = 0x80 | 0x0E; + send[1] = 0x00; + send[2] = 0x00; + ret = i2c_master_send(client, send, 3); + if (ret != 3) { + snd_printk(KERN_ERR "Failed to set ch2 volume on CS4384\n"); + goto fail; + } + + send[0] = 0x80 | 0x11; + send[1] = 0x00; + send[2] = 0x00; + ret = i2c_master_send(client, send, 3); + if (ret != 3) { + snd_printk(KERN_ERR "Failed to set ch3 volume on CS4384\n"); + goto fail; + } + + send[0] = 0x80 | 0x14; + send[1] = 0x00; + send[2] = 0x00; + ret = i2c_master_send(client, send, 3); + if (ret != 3) { + snd_printk(KERN_ERR "Failed to set ch4 volume on CS4384\n"); + goto fail; + } + + /* + * Register 09h: Mute control + * Mute all (we will unmute channels as needed) + */ + send[0] = 0x09; + send[1] = 0xFF; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to power up CS4384\n"); + goto fail; + } + + /* + * Register 0x02: Mode Control 1 + * Control Port Enable, PCM, All DACs enabled, Power Up + */ + send[0] = 0x02; + send[1] = 0x80; + ret = i2c_master_send(client, send, 2); + if (ret != 2) { + snd_printk(KERN_ERR "Failed to power up CS4384\n"); + goto fail; + } + + /* + * Make sure the changes took place, this helps verify we are talking to + * the correct chip. + */ + send[0] = 0x80 | 0x03; + ret = i2c_master_send(client, send, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed to initiate readback\n"); + goto fail; + } + + ret = i2c_master_recv(client, recv, 1); + if (ret != 1) { + snd_printk(KERN_ERR "Failed second read of CS4384 registers\n"); + goto fail; + } + + if (recv[0] != 0x13) { + snd_printk(KERN_ERR "Failed to initialize CS4384 DAC\n"); + goto fail; + } + + snd_printk(KERN_INFO "CS4384 DAC Initialized\n"); + return 0; + +fail: + return -ENODEV; +} + +/* + * snd_ubi32_cs4384_i2c_probe + */ +static int snd_ubi32_cs4384_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + int err, ret; + struct platform_device *pdev; + struct ubi32_cs4384_platform_data *pdata; + struct snd_ubi32_cs4384_priv *cs4384_priv; + + /* + * pdev is audio device + */ + pdev = client->dev.platform_data; + if (!pdev) { + return -ENODEV; + } + + /* + * pdev->dev.platform_data is ubi32-pcm platform_data + */ + pdata = audio_device_priv(pdev); + if (!pdata) { + return -ENODEV; + } + + /* + * Initialize the CS4384 DAC + */ + ret = snd_ubi32_cs4384_dac_init(client, id); + if (ret < 0) { + /* + * Initialization failed. Propagate the error. + */ + return ret; + } + + if (snd_ubi32_cs4384_setup_mclk(pdata)) { + return -EINVAL; + } + + /* + * Create a snd_card structure + */ + card = snd_card_new(index, "Ubi32-CS4384", THIS_MODULE, sizeof(struct ubi32_snd_priv)); + if (card == NULL) { + return -ENOMEM; + } + + card->private_free = snd_ubi32_cs4384_free; + ubi32_priv = card->private_data; + + /* + * Initialize the snd_card's private data structure + */ + ubi32_priv->card = card; + ubi32_priv->client = client; + ubi32_priv->set_channels = snd_ubi32_cs4384_set_channels; + ubi32_priv->set_rate = snd_ubi32_cs4384_set_rate; + + /* + * CS4384 DAC has a minimum sample rate of 4khz and an + * upper limit of 216khz for it's auto-detect. + */ + ubi32_priv->min_sample_rate = 4000; + ubi32_priv->max_sample_rate = 216000; + + /* + * Create our private data (to manage volume, etc) + */ + cs4384_priv = kzalloc(sizeof(struct snd_ubi32_cs4384_priv), GFP_KERNEL); + if (!cs4384_priv) { + snd_card_free(card); + return -ENOMEM; + } + snd_ubi32_priv_set_drv(ubi32_priv, cs4384_priv); + spin_lock_init(&cs4384_priv->lock); + + /* + * We start off all muted and max volume + */ + cs4384_priv->mute = 0xFF; + memset(cs4384_priv->volume, 0xFF, 8); + + /* + * Create the new PCM instance + */ + err = snd_ubi32_pcm_probe(ubi32_priv, pdev); + if (err < 0) { + snd_card_free(card); + return err; /* What is err? Need to include correct file */ + } + + strcpy(card->driver, "Ubi32-CS4384"); + strcpy(card->shortname, "Ubi32-CS4384"); + snprintf(card->longname, sizeof(card->longname), + "%s at sendirq=%d.%d recvirq=%d.%d regs=%p", + card->shortname, ubi32_priv->tx_irq, ubi32_priv->irq_idx, + ubi32_priv->rx_irq, ubi32_priv->irq_idx, ubi32_priv->adr); + + snd_card_set_dev(card, &client->dev); + + /* + * Set up the mixer + */ + snd_ubi32_cs4384_mixer(ubi32_priv); + + /* + * Register the sound card + */ + if ((err = snd_card_register(card)) != 0) { + snd_printk(KERN_INFO "snd_card_register error\n"); + } + + /* + * Store card for access from other methods + */ + i2c_set_clientdata(client, card); + + return 0; +} + +/* + * snd_ubi32_cs4384_i2c_remove + */ +static int __devexit snd_ubi32_cs4384_i2c_remove(struct i2c_client *client) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + + card = i2c_get_clientdata(client); + + ubi32_priv = card->private_data; + snd_ubi32_pcm_remove(ubi32_priv); + + snd_card_free(i2c_get_clientdata(client)); + i2c_set_clientdata(client, NULL); + + return 0; +} + +/* + * I2C driver description + */ +static struct i2c_driver snd_ubi32_cs4384_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .id_table = snd_ubi32_cs4384_id, + .probe = snd_ubi32_cs4384_i2c_probe, + .remove = __devexit_p(snd_ubi32_cs4384_i2c_remove), +}; + +/* + * Driver init + */ +static int __init snd_ubi32_cs4384_init(void) +{ + return i2c_add_driver(&snd_ubi32_cs4384_driver); +} +module_init(snd_ubi32_cs4384_init); + +/* + * snd_ubi32_cs4384_exit + */ +static void __exit snd_ubi32_cs4384_exit(void) +{ + i2c_del_driver(&snd_ubi32_cs4384_driver); +} +module_exit(snd_ubi32_cs4384_exit); + +/* + * Module properties + */ +MODULE_ALIAS("i2c:" DRIVER_NAME); +MODULE_AUTHOR("Patrick Tjin"); +MODULE_DESCRIPTION("Driver for Ubicom32 audio devices CS4384"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/ubicom32/files/sound/ubicom32/ubi32-generic-capture.c b/target/linux/ubicom32/files/sound/ubicom32/ubi32-generic-capture.c new file mode 100644 index 0000000000..a911cc6a1e --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/ubi32-generic-capture.c @@ -0,0 +1,167 @@ +/* + * sound/ubicom32/ubi32-generic-capture.c + * Interface to ubicom32 virtual audio peripheral + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port 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. + * + * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, + * see <http://www.gnu.org/licenses/>. + * + * Ubicom32 implementation derived from (with many thanks): + * arch/m68knommu + * arch/blackfin + * arch/parisc + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include "ubi32.h" + +#define DRIVER_NAME "snd-ubi32-generic-capture" + +/* + * Module properties + */ +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ + +/* + * Card private data free function + */ +void snd_ubi32_generic_capture_free(struct snd_card *card) +{ + /* + * Free all the fields in the snd_ubi32_priv struct + */ + // Nothing to free at this time because ubi32_priv just maintains pointers +} + +/* + * Ubicom audio driver probe() method. Args change depending on whether we use + * platform_device or i2c_device. + */ +static int snd_ubi32_generic_capture_probe(struct platform_device *dev) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + int err; + + /* + * Create a snd_card structure + */ + card = snd_card_new(index, "Ubi32-Generic-C", THIS_MODULE, sizeof(struct ubi32_snd_priv)); + + if (card == NULL) { + return -ENOMEM; + } + + card->private_free = snd_ubi32_generic_capture_free; /* Not sure if correct */ + ubi32_priv = card->private_data; + + /* + * Initialize the snd_card's private data structure + */ + ubi32_priv->card = card; + ubi32_priv->is_capture = 1; + + /* + * Create the new PCM instance + */ + err = snd_ubi32_pcm_probe(ubi32_priv, dev); + if (err < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, "Ubi32-Generic-C"); + strcpy(card->shortname, "Ubi32-Generic-C"); + snprintf(card->longname, sizeof(card->longname), + "%s at sendirq=%d.%d recvirq=%d.%d regs=%p", + card->shortname, ubi32_priv->tx_irq, ubi32_priv->irq_idx, + ubi32_priv->rx_irq, ubi32_priv->irq_idx, ubi32_priv->adr); + + snd_card_set_dev(card, &dev->dev); + + /* Register the sound card */ + if ((err = snd_card_register(card)) != 0) { + snd_printk(KERN_INFO "snd_card_register error\n"); + } + + /* Store card for access from other methods */ + platform_set_drvdata(dev, card); + + return 0; +} + +/* + * Ubicom audio driver remove() method + */ +static int __devexit snd_ubi32_generic_capture_remove(struct platform_device *dev) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + + card = platform_get_drvdata(dev); + ubi32_priv = card->private_data; + snd_ubi32_pcm_remove(ubi32_priv); + + snd_card_free(platform_get_drvdata(dev)); + platform_set_drvdata(dev, NULL); + return 0; +} + +/* + * Platform driver definition + */ +static struct platform_driver snd_ubi32_generic_capture_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = snd_ubi32_generic_capture_probe, + .remove = __devexit_p(snd_ubi32_generic_capture_remove), +}; + +/* + * snd_ubi32_generic_capture_init + */ +static int __init snd_ubi32_generic_capture_init(void) +{ + return platform_driver_register(&snd_ubi32_generic_capture_driver); +} +module_init(snd_ubi32_generic_capture_init); + +/* + * snd_ubi32_generic_capture_exit + */ +static void __exit snd_ubi32_generic_capture_exit(void) +{ + platform_driver_unregister(&snd_ubi32_generic_capture_driver); +} +module_exit(snd_ubi32_generic_capture_exit); + +/* + * Module properties + */ +//#if defined(CONFIG_SND_UBI32_AUDIO_I2C) +//MODULE_ALIAS("i2c:snd-ubi32"); +//#endif +MODULE_AUTHOR("Patrick Tjin"); +MODULE_DESCRIPTION("Driver for Ubicom32 audio devices"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/ubicom32/files/sound/ubicom32/ubi32-generic.c b/target/linux/ubicom32/files/sound/ubicom32/ubi32-generic.c new file mode 100644 index 0000000000..eee6066ceb --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/ubi32-generic.c @@ -0,0 +1,166 @@ +/* + * sound/ubicom32/ubi32-generic.c + * Interface to ubicom32 virtual audio peripheral + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port 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. + * + * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, + * see <http://www.gnu.org/licenses/>. + * + * Ubicom32 implementation derived from (with many thanks): + * arch/m68knommu + * arch/blackfin + * arch/parisc + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include "ubi32.h" + +#define DRIVER_NAME "snd-ubi32-generic" + +/* + * Module properties + */ +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ + +/* + * Card private data free function + */ +void snd_ubi32_generic_free(struct snd_card *card) +{ + /* + * Free all the fields in the snd_ubi32_priv struct + */ + // Nothing to free at this time because ubi32_priv just maintains pointers +} + +/* + * Ubicom audio driver probe() method. Args change depending on whether we use + * platform_device or i2c_device. + */ +static int snd_ubi32_generic_probe(struct platform_device *dev) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + int err; + + /* + * Create a snd_card structure + */ + card = snd_card_new(index, "Ubi32-Generic", THIS_MODULE, sizeof(struct ubi32_snd_priv)); + + if (card == NULL) { + return -ENOMEM; + } + + card->private_free = snd_ubi32_generic_free; /* Not sure if correct */ + ubi32_priv = card->private_data; + + /* + * Initialize the snd_card's private data structure + */ + ubi32_priv->card = card; + + /* + * Create the new PCM instance + */ + err = snd_ubi32_pcm_probe(ubi32_priv, dev); + if (err < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, "Ubi32-Generic"); + strcpy(card->shortname, "Ubi32-Generic"); + snprintf(card->longname, sizeof(card->longname), + "%s at sendirq=%d.%d recvirq=%d.%d regs=%p", + card->shortname, ubi32_priv->tx_irq, ubi32_priv->irq_idx, + ubi32_priv->rx_irq, ubi32_priv->irq_idx, ubi32_priv->adr); + + snd_card_set_dev(card, &dev->dev); + + /* Register the sound card */ + if ((err = snd_card_register(card)) != 0) { + snd_printk(KERN_INFO "snd_card_register error\n"); + } + + /* Store card for access from other methods */ + platform_set_drvdata(dev, card); + + return 0; +} + +/* + * Ubicom audio driver remove() method + */ +static int __devexit snd_ubi32_generic_remove(struct platform_device *dev) +{ + struct snd_card *card; + struct ubi32_snd_priv *ubi32_priv; + + card = platform_get_drvdata(dev); + ubi32_priv = card->private_data; + snd_ubi32_pcm_remove(ubi32_priv); + + snd_card_free(platform_get_drvdata(dev)); + platform_set_drvdata(dev, NULL); + return 0; +} + +/* + * Platform driver definition + */ +static struct platform_driver snd_ubi32_generic_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = snd_ubi32_generic_probe, + .remove = __devexit_p(snd_ubi32_generic_remove), +}; + +/* + * snd_ubi32_generic_init + */ +static int __init snd_ubi32_generic_init(void) +{ + return platform_driver_register(&snd_ubi32_generic_driver); +} +module_init(snd_ubi32_generic_init); + +/* + * snd_ubi32_generic_exit + */ +static void __exit snd_ubi32_generic_exit(void) +{ + platform_driver_unregister(&snd_ubi32_generic_driver); +} +module_exit(snd_ubi32_generic_exit); + +/* + * Module properties + */ +//#if defined(CONFIG_SND_UBI32_AUDIO_I2C) +//MODULE_ALIAS("i2c:snd-ubi32"); +//#endif +MODULE_AUTHOR("Aaron Jow, Patrick Tjin"); +MODULE_DESCRIPTION("Driver for Ubicom32 audio devices"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/ubicom32/files/sound/ubicom32/ubi32-pcm.c b/target/linux/ubicom32/files/sound/ubicom32/ubi32-pcm.c new file mode 100644 index 0000000000..2bc300b855 --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/ubi32-pcm.c @@ -0,0 +1,711 @@ +/* + * sound/ubicom32/ubi32-pcm.c + * Interface to ubicom32 virtual audio peripheral + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port 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. + * + * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, + * see <http://www.gnu.org/licenses/>. + * + * Ubicom32 implementation derived from (with many thanks): + * arch/m68knommu + * arch/blackfin + * arch/parisc + */ + +#include <linux/interrupt.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <asm/ip5000.h> +#include <asm/ubi32-pcm.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include "ubi32.h" + +struct ubi32_snd_runtime_data { + dma_addr_t dma_buffer; /* Physical address of DMA buffer */ + dma_addr_t dma_buffer_end; /* First address beyond end of DMA buffer */ + size_t period_size; + dma_addr_t period_ptr; /* Physical address of next period */ + unsigned int flags; +}; + +static void snd_ubi32_vp_int_set(struct snd_pcm *pcm) +{ + struct ubi32_snd_priv *ubi32_priv = pcm->private_data; + ubi32_priv->ar->int_req |= (1 << ubi32_priv->irq_idx); + ubicom32_set_interrupt(ubi32_priv->tx_irq); +} + +static snd_pcm_uframes_t snd_ubi32_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct ubi32_snd_priv *ubi32_priv = snd_pcm_substream_chip(substream); + struct audio_dev_regs *adr = ubi32_priv->adr; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ubi32_snd_runtime_data *ubi32_rd = substream->runtime->private_data; + + dma_addr_t read_pos; + + snd_pcm_uframes_t frames; + if (!adr->primary_os_buffer_ptr) { + /* + * If primary_os_buffer_ptr is NULL (e.g. right after the HW is started or + * when the HW is stopped), then handle this case separately. + */ + return 0; + } + + read_pos = (dma_addr_t)adr->primary_os_buffer_ptr; + frames = bytes_to_frames(runtime, read_pos - ubi32_rd->dma_buffer); + if (frames == runtime->buffer_size) { + frames = 0; + } + return frames; +} + +/* + * Audio trigger + */ +static int snd_ubi32_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ubi32_snd_priv *ubi32_priv = substream->pcm->private_data; + struct audio_dev_regs *adr = ubi32_priv->adr; + struct ubi32_snd_runtime_data *ubi32_rd = substream->runtime->private_data; + int ret = 0; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "snd_ubi32_pcm_trigger cmd=%d=", cmd); +#endif + + if (adr->command != AUDIO_CMD_NONE) { + snd_printk(KERN_WARNING "Can't send command to audio device at this time\n"); + // Set a timer to call this function back later. How to do this? + return 0; + } + + /* + * Set interrupt flag to indicate that we interrupted audio device + * to send a command + */ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "START\n"); +#endif + /* + * Ready the DMA transfer + */ + ubi32_rd->period_ptr = ubi32_rd->dma_buffer; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "trigger period_ptr=%lx\n", (unsigned long)ubi32_rd->period_ptr); +#endif + adr->dma_xfer_requests[0].ptr = (void *)ubi32_rd->period_ptr; + adr->dma_xfer_requests[0].ctr = ubi32_rd->period_size; + adr->dma_xfer_requests[0].active = 1; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "xfer_request 0 ptr=0x%x ctr=%u\n", ubi32_rd->period_ptr, ubi32_rd->period_size); +#endif + + ubi32_rd->period_ptr += ubi32_rd->period_size; + adr->dma_xfer_requests[1].ptr = (void *)ubi32_rd->period_ptr; + adr->dma_xfer_requests[1].ctr = ubi32_rd->period_size; + adr->dma_xfer_requests[1].active = 1; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "xfer_request 1 ptr=0x%x ctr=%u\n", ubi32_rd->period_ptr, ubi32_rd->period_size); +#endif + + /* + * Tell the VP that we want to begin playback by filling in the + * command field and then interrupting the audio VP + */ + adr->int_flags |= AUDIO_INT_FLAG_COMMAND; + adr->command = AUDIO_CMD_START; + snd_ubi32_vp_int_set(substream->pcm); + break; + + case SNDRV_PCM_TRIGGER_STOP: + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "STOP\n"); +#endif + + /* + * Tell the VP that we want to stop playback by filling in the + * command field and then interrupting the audio VP + */ + adr->int_flags |= AUDIO_INT_FLAG_COMMAND; + adr->command = AUDIO_CMD_STOP; + snd_ubi32_vp_int_set(substream->pcm); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "PAUSE_PUSH\n"); +#endif + + /* + * Tell the VP that we want to pause playback by filling in the + * command field and then interrupting the audio VP + */ + adr->int_flags |= AUDIO_INT_FLAG_COMMAND; + adr->command = AUDIO_CMD_PAUSE; + snd_ubi32_vp_int_set(substream->pcm); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "PAUSE_RELEASE\n"); +#endif + /* + * Tell the VP that we want to resume paused playback by filling + * in the command field and then interrupting the audio VP + */ + adr->int_flags |= AUDIO_INT_FLAG_COMMAND; + adr->command = AUDIO_CMD_RESUME; + snd_ubi32_vp_int_set(substream->pcm); + break; + + default: + snd_printk(KERN_WARNING "Unhandled trigger\n"); + ret = -EINVAL; + break; + } + + return ret; +} + +/* + * Prepare to transfer an audio stream to the codec + */ +static int snd_ubi32_pcm_prepare(struct snd_pcm_substream *substream) +{ + /* + * Configure registers and setup the runtime instance for DMA transfers + */ + struct ubi32_snd_priv *ubi32_priv = substream->pcm->private_data; + struct audio_dev_regs *adr = ubi32_priv->adr; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "snd_ubi32_pcm_prepare: sending STOP command to audio device\n"); +#endif + + /* + * Make sure the audio device is stopped + */ + + /* + * Set interrupt flag to indicate that we interrupted audio device + * to send a command + */ + adr->int_flags |= AUDIO_INT_FLAG_COMMAND; + adr->command = AUDIO_CMD_STOP; + snd_ubi32_vp_int_set(substream->pcm); + + return 0; +} + +/* + * Allocate DMA buffers from preallocated memory. + * Preallocation was done in snd_ubi32_pcm_new() + */ +static int snd_ubi32_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ubi32_snd_priv *ubi32_priv = substream->pcm->private_data; + struct audio_dev_regs *adr = ubi32_priv->adr; + struct ubi32_snd_runtime_data *ubi32_rd = substream->runtime->private_data; + + /* + * Use pre-allocated memory from ubi32_snd_pcm_new() to satisfy + * this memory request. + */ + int ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (ret < 0) { + return ret; + } + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "snd_ubi32_pcm_hw_params\n"); +#endif + + if (!(adr->channel_mask & (1 << params_channels(hw_params)))) { + snd_printk(KERN_INFO "snd_ubi32_pcm_hw_params unsupported number of channels %d mask %08x\n", params_channels(hw_params), adr->channel_mask); + return -EINVAL; + } + + if (ubi32_priv->set_channels) { + int ret = ubi32_priv->set_channels(ubi32_priv, params_channels(hw_params)); + if (ret) { + snd_printk(KERN_WARNING "Unable to set channels to %d, ret=%d\n", params_channels(hw_params), ret); + return ret; + } + } + + if (ubi32_priv->set_rate) { + int ret = ubi32_priv->set_rate(ubi32_priv, params_rate(hw_params)); + if (ret) { + snd_printk(KERN_WARNING "Unable to set rate to %d, ret=%d\n", params_rate(hw_params), ret); + return ret; + } + } + + if (ubi32_priv->pdata->set_rate) { + int ret = ubi32_priv->pdata->set_rate(ubi32_priv->pdata->appdata, params_rate(hw_params)); + if (ret) { + snd_printk(KERN_WARNING "Unable to set rate to %d, ret=%d\n", params_rate(hw_params), ret); + return ret; + } + } + + if (adr->command != AUDIO_CMD_NONE) { + snd_printk(KERN_WARNING "snd_ubi32_pcm_hw_params: tio busy\n"); + return -EAGAIN; + } + + if (params_format(hw_params) == SNDRV_PCM_FORMAT_S16_LE) { + adr->flags |= CMD_START_FLAG_LE; + } else { + adr->flags &= ~CMD_START_FLAG_LE; + } + adr->channels = params_channels(hw_params); + adr->sample_rate = params_rate(hw_params); + adr->command = AUDIO_CMD_SETUP; + adr->int_flags |= AUDIO_INT_FLAG_COMMAND; + snd_ubi32_vp_int_set(substream->pcm); + + /* + * Wait for the command to complete + */ + while (adr->command != AUDIO_CMD_NONE) { + udelay(1); + } + + /* + * Put the DMA info into the DMA descriptor that we will + * use to do transfers to our audio VP "hardware" + */ + + /* + * Mark both DMA transfers as not ready/inactive + */ + adr->dma_xfer_requests[0].active = 0; + adr->dma_xfer_requests[1].active = 0; + + /* + * Put the location of the buffer into the runtime data instance + */ + ubi32_rd->dma_buffer = (dma_addr_t)runtime->dma_area; + ubi32_rd->dma_buffer_end = (dma_addr_t)(runtime->dma_area + runtime->dma_bytes); + + /* + * Get the period size + */ + ubi32_rd->period_size = params_period_bytes(hw_params); + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "DMA for ubi32 audio initialized dma_area=0x%x dma_bytes=%d, period_size=%d\n", (unsigned int)runtime->dma_area, (unsigned int)runtime->dma_bytes, ubi32_rd->period_size); + snd_printk(KERN_INFO "Private buffer ubi32_rd: dma_buffer=0x%x dma_buffer_end=0x%x ret=%d\n", ubi32_rd->dma_buffer, ubi32_rd->dma_buffer_end, ret); +#endif + + return ret; +} + +/* + * This is the reverse of snd_ubi32_pcm_hw_params + */ +static int snd_ubi32_pcm_hw_free(struct snd_pcm_substream *substream) +{ +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "snd_ubi32_pcm_hw_free\n"); +#endif + return snd_pcm_lib_free_pages(substream); +} + +/* + * Audio virtual peripheral capabilities (capture and playback are identical) + */ +static struct snd_pcm_hardware snd_ubi32_pcm_hw = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .buffer_bytes_max = (64*1024), + .period_bytes_min = 64, + .period_bytes_max = 8184,//8184,//8176, + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, // THIS IS IGNORED BY ALSA +}; + +/* + * We fill this in later + */ +static struct snd_pcm_hw_constraint_list ubi32_pcm_rates; + +/* + * snd_ubi32_pcm_close + */ +static int snd_ubi32_pcm_close(struct snd_pcm_substream *substream) +{ + /* Disable codec, stop DMA, free private data structures */ + //struct ubi32_snd_priv *ubi32_priv = snd_pcm_substream_chip(substream); + struct ubi32_snd_runtime_data *ubi32_rd = substream->runtime->private_data; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "snd_ubi32_pcm_close\n"); +#endif + + substream->runtime->private_data = NULL; + + kfree(ubi32_rd); + + return 0; +} + +/* + * snd_ubi32_pcm_open + */ +static int snd_ubi32_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ubi32_snd_runtime_data *ubi32_rd; + int ret = 0; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "ubi32 pcm open\n"); +#endif + + /* Associate capabilities with component */ + runtime->hw = snd_ubi32_pcm_hw; + + /* + * Inform ALSA about constraints of the audio device + */ + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &ubi32_pcm_rates); + if (ret < 0) { + snd_printk(KERN_INFO "invalid rate\n"); + goto out; + } + + /* Force the buffer size to be an integer multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + snd_printk(KERN_INFO "invalid period\n"); + goto out; + } + /* Initialize structures/registers */ + ubi32_rd = kzalloc(sizeof(struct ubi32_snd_runtime_data), GFP_KERNEL); + if (ubi32_rd == NULL) { + ret = -ENOMEM; + goto out; + } + + runtime->private_data = ubi32_rd; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "snd_ubi32_pcm_open returned 0\n"); +#endif + + return 0; +out: +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "snd_ubi32_pcm_open returned %d\n", ret); +#endif + + return ret; +} + +static struct snd_pcm_ops snd_ubi32_pcm_ops = { + .open = snd_ubi32_pcm_open, /* Open */ + .close = snd_ubi32_pcm_close, /* Close */ + .ioctl = snd_pcm_lib_ioctl, /* Generic IOCTL handler */ + .hw_params = snd_ubi32_pcm_hw_params, /* Hardware parameters/capabilities */ + .hw_free = snd_ubi32_pcm_hw_free, /* Free function for hw_params */ + .prepare = snd_ubi32_pcm_prepare, + .trigger = snd_ubi32_pcm_trigger, + .pointer = snd_ubi32_pcm_pointer, +}; + +/* + * Interrupt handler that gets called when the audio device + * interrupts Linux + */ +static irqreturn_t snd_ubi32_pcm_interrupt(int irq, void *appdata) +{ + struct snd_pcm *pcm = (struct snd_pcm *)appdata; + struct ubi32_snd_priv *ubi32_priv = pcm->private_data; + struct audio_dev_regs *adr = ubi32_priv->adr; + struct snd_pcm_substream *substream; + struct ubi32_snd_runtime_data *ubi32_rd; + int dma_to_fill = 0; + + /* + * Check to see if the interrupt is for us + */ + if (!(ubi32_priv->ar->int_status & (1 << ubi32_priv->irq_idx))) { + return IRQ_NONE; + } + + /* + * Clear the interrupt + */ + ubi32_priv->ar->int_status &= ~(1 << ubi32_priv->irq_idx); + + /* + * We only have one stream since we don't mix. Therefore + * we don't need to search through substreams. + */ + if (ubi32_priv->is_capture) { + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + } else { + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + } + + if (!substream->runtime) { + snd_printk(KERN_WARNING "No runtime data\n"); + return IRQ_NONE; + } + + ubi32_rd = substream->runtime->private_data; + +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "Ubi32 ALSA interrupt\n"); +#endif + + if (ubi32_rd == NULL) { + snd_printk(KERN_WARNING "No private data\n"); + return IRQ_NONE; + } + + // Check interrupt cause + if (0) { + // Handle the underflow case + } else if ((adr->status & AUDIO_STATUS_PLAY_DMA0_REQUEST) || + (adr->status & AUDIO_STATUS_PLAY_DMA1_REQUEST)) { + if (adr->status & AUDIO_STATUS_PLAY_DMA0_REQUEST) { + dma_to_fill = 0; + adr->status &= ~AUDIO_STATUS_PLAY_DMA0_REQUEST; + } else if (adr->status & AUDIO_STATUS_PLAY_DMA1_REQUEST) { + dma_to_fill = 1; + adr->status &= ~AUDIO_STATUS_PLAY_DMA1_REQUEST; + } + ubi32_rd->period_ptr += ubi32_rd->period_size; + if (ubi32_rd->period_ptr >= ubi32_rd->dma_buffer_end) { + ubi32_rd->period_ptr = ubi32_rd->dma_buffer; + } + adr->dma_xfer_requests[dma_to_fill].ptr = (void *)ubi32_rd->period_ptr; + adr->dma_xfer_requests[dma_to_fill].ctr = ubi32_rd->period_size; + adr->dma_xfer_requests[dma_to_fill].active = 1; +#ifdef CONFIG_SND_DEBUG + snd_printk(KERN_INFO "xfer_request %d ptr=0x%x ctr=%u\n", dma_to_fill, ubi32_rd->period_ptr, ubi32_rd->period_size); +#endif + adr->int_flags |= AUDIO_INT_FLAG_MORE_SAMPLES; + snd_ubi32_vp_int_set(substream->pcm); + } + // If we are interrupted by the VP, that means we completed + // processing one period of audio. We need to inform the upper + // layers of ALSA of this. + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +void __devexit snd_ubi32_pcm_remove(struct ubi32_snd_priv *ubi32_priv) +{ + struct snd_pcm *pcm = ubi32_priv->pcm; + free_irq(ubi32_priv->rx_irq, pcm); +} + +#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12 +#error "Change this table to match pcm.h" +#endif +static unsigned int rates[] __initdata = {5512, 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 64000, 88200, + 96000, 176400, 192000}; + +/* + * snd_ubi32_pcm_probe + */ +int __devinit snd_ubi32_pcm_probe(struct ubi32_snd_priv *ubi32_priv, struct platform_device *pdev) +{ + struct snd_pcm *pcm; + int ret, err; + int i; + int j; + int nrates; + unsigned int rate_max = 0; + unsigned int rate_min = 0xFFFFFFFF; + unsigned int rate_mask = 0; + struct audio_dev_regs *adr; + struct resource *res_adr; + struct resource *res_irq_tx; + struct resource *res_irq_rx; + struct ubi32pcm_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) { + return -ENODEV; + } + + /* + * Get our resources, adr is the hardware driver base address + * and the tx and rx irqs are used to communicate with the + * hardware driver. + */ + res_adr = platform_get_resource(pdev, IORESOURCE_MEM, AUDIO_MEM_RESOURCE); + res_irq_tx = platform_get_resource(pdev, IORESOURCE_IRQ, AUDIO_TX_IRQ_RESOURCE); + res_irq_rx = platform_get_resource(pdev, IORESOURCE_IRQ, AUDIO_RX_IRQ_RESOURCE); + if (!res_adr || !res_irq_tx || !res_irq_rx) { + snd_printk(KERN_WARNING "Could not get resources"); + return -ENODEV; + } + + ubi32_priv->ar = (struct audio_regs *)res_adr->start; + ubi32_priv->tx_irq = res_irq_tx->start; + ubi32_priv->rx_irq = res_irq_rx->start; + ubi32_priv->irq_idx = pdata->inst_num; + ubi32_priv->adr = &(ubi32_priv->ar->adr[pdata->inst_num]); + + /* + * Check the version + */ + adr = ubi32_priv->adr; + if (adr->version != AUDIO_DEV_REGS_VERSION) { + snd_printk(KERN_WARNING "This audio_dev_reg is not compatible with this driver\n"); + return -ENODEV; + } + + /* + * Find out the standard rates, also find max and min rates + */ + for (i = 0; i < ARRAY_SIZE(rates); i++) { + int found = 0; + for (j = 0; j < adr->n_sample_rates; j++) { + if (rates[i] == adr->sample_rates[j]) { + /* + * Check to see if it is supported by the dac + */ + if ((rates[i] >= ubi32_priv->min_sample_rate) && + (!ubi32_priv->max_sample_rate || + (ubi32_priv->max_sample_rate && (rates[i] <= ubi32_priv->max_sample_rate)))) { + found = 1; + rate_mask |= (1 << i); + nrates++; + if (rates[i] < rate_min) { + rate_min = rates[i]; + } + if (rates[i] > rate_max) { + rate_max = rates[i]; + } + break; + } + } + } + if (!found) { + rate_mask |= SNDRV_PCM_RATE_KNOT; + } + } + + snd_ubi32_pcm_hw.rates = rate_mask; + snd_ubi32_pcm_hw.rate_min = rate_min; + snd_ubi32_pcm_hw.rate_max = rate_max; + ubi32_pcm_rates.count = adr->n_sample_rates; + ubi32_pcm_rates.list = (unsigned int *)adr->sample_rates; + ubi32_pcm_rates.mask = 0; + + for (i = 0; i < 32; i++) { + if (adr->channel_mask & (1 << i)) { + if (!snd_ubi32_pcm_hw.channels_min) { + snd_ubi32_pcm_hw.channels_min = i; + } + snd_ubi32_pcm_hw.channels_max = i; + } + } + snd_printk(KERN_INFO "Ubi32PCM: channels_min:%u channels_max:%u\n", + snd_ubi32_pcm_hw.channels_min, + snd_ubi32_pcm_hw.channels_max); + + if (adr->caps & AUDIONODE_CAP_BE) { + snd_ubi32_pcm_hw.formats |= SNDRV_PCM_FMTBIT_S16_BE; + } + if (adr->caps & AUDIONODE_CAP_LE) { + snd_ubi32_pcm_hw.formats |= SNDRV_PCM_FMTBIT_S16_LE; + } + + snd_printk(KERN_INFO "Ubi32PCM: rates:%08x min:%u max:%u count:%d fmts:%016llx (%s)\n", + snd_ubi32_pcm_hw.rates, + snd_ubi32_pcm_hw.rate_min, + snd_ubi32_pcm_hw.rate_max, + ubi32_pcm_rates.count, + snd_ubi32_pcm_hw.formats, + ubi32_priv->is_capture ? "capture" : "playback"); + + if (ubi32_priv->is_capture) { + ret = snd_pcm_new(ubi32_priv->card, "Ubi32 PCM", 0, 0, 1, &pcm); + } else { + ret = snd_pcm_new(ubi32_priv->card, "Ubi32 PCM", 0, 1, 0, &pcm); + } + + if (ret < 0) { + return ret; + } + + pcm->private_data = ubi32_priv; + ubi32_priv->pcm = pcm; + ubi32_priv->pdata = pdata; + + pcm->info_flags = 0; + + strcpy(pcm->name, "Ubi32-PCM"); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 45*1024, 64*1024); + + if (ubi32_priv->is_capture) { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ubi32_pcm_ops); + } else { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ubi32_pcm_ops); + } + + /* + * Start up the audio device + */ + adr->int_flags |= AUDIO_INT_FLAG_COMMAND; + adr->command = AUDIO_CMD_ENABLE; + snd_ubi32_vp_int_set(pcm); + + /* + * Request IRQ + */ + err = request_irq(ubi32_priv->rx_irq, snd_ubi32_pcm_interrupt, IRQF_SHARED | IRQF_DISABLED, pcm->name, pcm); + if (err) { + snd_printk(KERN_WARNING "request_irq failed: irq=%d err=%d\n", ubi32_priv->rx_irq, err); + return -ENODEV; + } + + return ret; + +} diff --git a/target/linux/ubicom32/files/sound/ubicom32/ubi32.h b/target/linux/ubicom32/files/sound/ubicom32/ubi32.h new file mode 100644 index 0000000000..f43a2150b4 --- /dev/null +++ b/target/linux/ubicom32/files/sound/ubicom32/ubi32.h @@ -0,0 +1,102 @@ +/* + * sound/ubicom32/ubi32.h + * Common header file for all ubi32- sound drivers + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port 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. + * + * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, + * see <http://www.gnu.org/licenses/>. + */ + +#ifndef _UBI32_H +#define _UBI32_H + +#define SND_UBI32_DEBUG 0 // Debug flag + +#include <linux/platform_device.h> +#include <asm/devtree.h> +#include <asm/audio.h> +#include <asm/ubi32-pcm.h> + +struct ubi32_snd_priv; + +typedef int (*set_channels_t)(struct ubi32_snd_priv *priv, int channels); +typedef int (*set_rate_t)(struct ubi32_snd_priv *priv, int rate); + +struct ubi32_snd_priv { + /* + * Any variables that are needed locally here but NOT in + * the VP itself should go in here. + */ + struct snd_card *card; + struct snd_pcm *pcm; + + /* + * capture (1) or playback (0) + */ + int is_capture; + /* + * DAC parameters. These are the parameters for the specific + * DAC we are driving. The I2S component can run at a range + * of frequencies, but the DAC may be limited. We may want + * to make this an array of some sort in the future? + * + * min/max_sample_rate if set to 0 are ignored. + */ + int max_sample_rate; + int min_sample_rate; + + /* + * The size a period (group) of audio samples. The VP does + * not need to know this; each DMA transfer is made to be + * one period. + */ + u32_t period_size; + + spinlock_t ubi32_lock; + + struct audio_regs *ar; + struct audio_dev_regs *adr; + u32 irq_idx; + u8 tx_irq; + u8 rx_irq; + + void *client; + + /* + * Operations which the base DAC driver can implement + */ + set_channels_t set_channels; + set_rate_t set_rate; + + /* + * platform data + */ + struct ubi32pcm_platform_data *pdata; + + /* + * Private driver data (used for DAC driver control, etc) + */ + void *drvdata; +}; + +#define snd_ubi32_priv_get_drv(priv) ((priv)->drvdata) +#define snd_ubi32_priv_set_drv(priv, data) (((priv)->drvdata) = (void *)(data)) + +extern int snd_ubi32_pcm_probe(struct ubi32_snd_priv *ubi32_priv, struct platform_device *pdev); +extern void snd_ubi32_pcm_remove(struct ubi32_snd_priv *ubi32_priv); + +#endif |