diff options
Diffstat (limited to 'target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4384.c')
-rw-r--r-- | target/linux/ubicom32/files/sound/ubicom32/ubi32-cs4384.c | 996 |
1 files changed, 996 insertions, 0 deletions
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 0000000..2679267 --- /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"); |