From 89aa6dd15306a1ce11da0f2cb67bda74999e178e Mon Sep 17 00:00:00 2001 From: Tanmay Upadhyay Date: Tue, 24 Nov 2009 21:49:24 +0530 Subject: [PATCH] ARM: Kirkwood: Sound: Sound driver added The driver is based on the Marvell kirkwood sound driver available in 2.6.22.18 kernel. Signed-off-by: Tanmay Upadhyay --- arch/arm/mach-kirkwood/common.c | 39 + arch/arm/mach-kirkwood/common.h | 2 + arch/arm/mach-kirkwood/include/mach/kirkwood.h | 3 + arch/arm/mach-kirkwood/openrd_client-setup.c | 27 + include/linux/mv88fx_audio.h | 111 ++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/kirkwood/Kconfig | 29 + sound/soc/kirkwood/Makefile | 7 + sound/soc/kirkwood/cs42l51.c | 304 +++++ sound/soc/kirkwood/cs42l51.h | 59 + sound/soc/kirkwood/kirkwood_audio_hal.c | 821 +++++++++++++ sound/soc/kirkwood/kirkwood_audio_hal.h | 109 ++ sound/soc/kirkwood/kirkwood_audio_regs.h | 310 +++++ sound/soc/kirkwood/kirkwood_pcm.c | 1505 ++++++++++++++++++++++++ 15 files changed, 3328 insertions(+), 0 deletions(-) create mode 100644 include/linux/mv88fx_audio.h create mode 100644 sound/soc/kirkwood/Kconfig create mode 100644 sound/soc/kirkwood/Makefile create mode 100644 sound/soc/kirkwood/cs42l51.c create mode 100644 sound/soc/kirkwood/cs42l51.h create mode 100644 sound/soc/kirkwood/kirkwood_audio_hal.c create mode 100644 sound/soc/kirkwood/kirkwood_audio_hal.h create mode 100644 sound/soc/kirkwood/kirkwood_audio_regs.h create mode 100644 sound/soc/kirkwood/kirkwood_pcm.c diff --git a/arch/arm/mach-kirkwood/common.c b/arch/arm/mach-kirkwood/common.c index 0acb61f..4d66c06 100644 --- a/arch/arm/mach-kirkwood/common.c +++ b/arch/arm/mach-kirkwood/common.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -969,3 +970,41 @@ static int __init kirkwood_clock_gate(void) return 0; } late_initcall(kirkwood_clock_gate); + +/***************************************************************************** + * Audio + ****************************************************************************/ + +static struct resource kirkwood_audio_resources[] = { + [0] = { + .start = AUDIO_PHYS_BASE, + .end = AUDIO_PHYS_BASE + SZ_16K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_KIRKWOOD_I2S, + .end = IRQ_KIRKWOOD_I2S, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 kirkwood_audio_dmamask = 0xFFFFFFFFUL; + +static struct platform_device kirkwood_audio = { + .name = MV88FX_AUDIO_NAME, + .id = -1, + .num_resources = ARRAY_SIZE(kirkwood_audio_resources), + .resource = kirkwood_audio_resources, + .dev = { + .dma_mask = &kirkwood_audio_dmamask, + .coherent_dma_mask = 0xffffffff, + }, +}; + +void __init kirkwood_audio_init(struct mv88fx_snd_platform_data *audio_data) +{ + kirkwood_clk_ctrl |= CGC_AUDIO; + kirkwood_audio.dev.platform_data = audio_data; + + platform_device_register(&kirkwood_audio); +} diff --git a/arch/arm/mach-kirkwood/common.h b/arch/arm/mach-kirkwood/common.h index d7de434..b79a25c 100644 --- a/arch/arm/mach-kirkwood/common.h +++ b/arch/arm/mach-kirkwood/common.h @@ -16,6 +16,7 @@ struct mv643xx_eth_platform_data; struct mv_sata_platform_data; struct mvsdio_platform_data; struct mtd_partition; +struct mv88fx_snd_platform_data; /* * Basic Kirkwood init functions used early by machine-setup. @@ -41,6 +42,7 @@ void kirkwood_i2c_init(void); void kirkwood_uart0_init(void); void kirkwood_uart1_init(void); void kirkwood_nand_init(struct mtd_partition *parts, int nr_parts, int delay); +void kirkwood_audio_init(struct mv88fx_snd_platform_data *audio_data); extern int kirkwood_tclk; extern struct sys_timer kirkwood_timer; diff --git a/arch/arm/mach-kirkwood/include/mach/kirkwood.h b/arch/arm/mach-kirkwood/include/mach/kirkwood.h index 54c1327..90ced65 100644 --- a/arch/arm/mach-kirkwood/include/mach/kirkwood.h +++ b/arch/arm/mach-kirkwood/include/mach/kirkwood.h @@ -95,6 +95,9 @@ #define SDIO_PHYS_BASE (KIRKWOOD_REGS_PHYS_BASE | 0x90000) +#define AUDIO_PHYS_BASE (KIRKWOOD_REGS_PHYS_BASE | 0xA0000) +#define AUDIO_VIRT_BASE (KIRKWOOD_REGS_VIRT_BASE | 0xA0000) + /* * Supported devices and revisions. */ diff --git a/arch/arm/mach-kirkwood/openrd_client-setup.c b/arch/arm/mach-kirkwood/openrd_client-setup.c index a55a1bc..72acc22 100644 --- a/arch/arm/mach-kirkwood/openrd_client-setup.c +++ b/arch/arm/mach-kirkwood/openrd_client-setup.c @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "common.h" #include "mpp.h" @@ -59,6 +61,21 @@ static unsigned int openrd_client_mpp_config[] __initdata = { 0 }; +static struct mv88fx_snd_platform_data openrd_client_audio_data = { + .i2c_bus_no = 0, + .i2c_address = 0x4A, +/* 0 - NA, 1 - mono, 2 - stereo */ +#ifdef CONFIG_SND_MV88FX_SOC_I2S + .i2s_rec = 1, + .i2s_play = 2, +#else + .spdif_rec = 1, + .spdif_play = 2, +#endif + .dram = &kirkwood_mbus_dram_info, + .base_offset = AUDIO_PHYS_BASE - KIRKWOOD_REGS_PHYS_BASE, +}; + static void __init openrd_client_init(void) { /* @@ -78,6 +95,16 @@ static void __init openrd_client_init(void) kirkwood_sata_init(&openrd_client_sata_data); kirkwood_sdio_init(&openrd_client_mvsdio_data); + + /* initialize i2c */ + kirkwood_i2c_init(); + +#if defined(CONFIG_SND_MV88FX_SOC) || defined(CONFIG_SND_MV88FX_SOC_MODULE) + /* If built as a part of kernel or as a module + * initialize audio */ + openrd_client_audio_data.tclk = kirkwood_tclk, + kirkwood_audio_init(&openrd_client_audio_data); +#endif } MACHINE_START(OPENRD_CLIENT, "Marvell OpenRD Client Board") diff --git a/include/linux/mv88fx_audio.h b/include/linux/mv88fx_audio.h new file mode 100644 index 0000000..6d36a3f --- /dev/null +++ b/include/linux/mv88fx_audio.h @@ -0,0 +1,111 @@ +/* + * + * Marvell Orion Alsa Sound driver + * + * Author: Maen Suleiman + * Copyright (C) 2008 Marvell Ltd. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __LINUX_MV88FX_SND_H +#define __LINUX_MV88FX_SND_H + +#include + +#define MV88FX_AUDIO_NAME "mv88fx_snd" + +#undef MV88FX_SND_DEBUG +#ifdef MV88FX_SND_DEBUG +#define mv88fx_snd_debug(fmt, arg...) printk(KERN_DEBUG fmt, ##arg) +#else + #define mv88fx_snd_debug(a...) +#endif + +#define MV_AUDIO_MAX_ADDR_DECODE_WIN 2 +#define MV_AUDIO_RECORD_WIN_NUM 0 +#define MV_AUDIO_PLAYBACK_WIN_NUM 1 + +#define MV_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3)) +#define MV_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3)) + +struct mv88fx_snd_platform_data { + u8 i2c_bus_no; + u16 i2c_address; + u32 spdif_rec; + u32 spdif_play; + u32 i2s_rec; + u32 i2s_play; + u32 tclk; + u32 base_offset; + struct mbus_dram_target_info *dram; +}; + +struct mv88fx_snd_stream { + struct snd_pcm_substream *substream; + struct device *dev; + int direction; /* playback or capture */ + #define PLAYBACK 0 + #define CAPTURE 1 + unsigned int dig_mode; /* i2s,spdif,both */ + #define I2S 1 + #define SPDIF 2 + int stereo; /* mono, stereo */ + int mono_mode; /* both mono, left mono, right mono */ + #define MONO_BOTH 0 + #define MONO_LEFT 1 + #define MONO_RIGHT 2 + int clock_src; + #define DCO_CLOCK 0 + #define SPCR_CLOCK 1 + #define EXTERN_CLOCK 2 + int rate; + int stat_mem; /* Channel status source*/ + int format; + #define SAMPLE_32IN32 0 + #define SAMPLE_24IN32 1 + #define SAMPLE_20IN32 2 + #define SAMPLE_16IN32 3 + #define SAMPLE_16IN16 4 + unsigned int dma_addr; + unsigned int dma_size; + unsigned int period_size; + unsigned int spdif_status[4]; /* SPDIF status */ + unsigned char *area; /* virtual pointer */ + dma_addr_t addr; /* physical address */ +}; + +struct mv88fx_snd_chip { + struct mv88fx_snd_stream *stream[2]; /* run time values*/ + struct mv88fx_snd_stream *stream_defaults[2]; /* default values*/ + spinlock_t reg_lock; /* Register access spinlock */ + struct resource *res; /* resource for IRQ and base*/ + void __iomem *base; /* Audio base address of the host */ + unsigned int audio_offset; /* Offset to audio base register + * from internal base register */ + int irq; + int loopback; /* When Loopback is enabled, playback + * data is looped back to be recorded */ + int ch_stat_valid; /* Playback SPDIF channel validity bit + * value when REG selected */ + int burst; /* DMA Burst Size */ + + #define SPDIF_MEM_STAT 0 + #define SPDIF_REG_STAT 1 + unsigned int dco_ctrl_offst; + int pcm_mode; /* pcm, nonpcm*/ + #define PCM 0 + #define NON_PCM 1 + int stereo; +}; + +#define MV88FX_SND_MIN_PERIODS 8 +#define MV88FX_SND_MAX_PERIODS 16 +#define MV88FX_SND_MIN_PERIOD_BYTES 0x4000 +#define MV88FX_SND_MAX_PERIOD_BYTES 0x4000 + +#endif /* __LINUX_MV88FX_SND_H */ diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index b1749bc..9fc88d8 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -36,6 +36,7 @@ source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/kirkwood/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 0c5eac0..664850d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += kirkwood/ diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig new file mode 100644 index 0000000..d6a7e2f --- /dev/null +++ b/sound/soc/kirkwood/Kconfig @@ -0,0 +1,29 @@ +config SND_MV88FX_SOC + tristate "SoC Audio for the Marvell 88FX chip" + depends on ARCH_KIRKWOOD + help + Say Y or M if you want to add support for codecs attached to + the MV88FX I2S or SPD interface. You will also need + to select the audio interfaces to support below. + +choice + prompt "Audio Interface" + default SND_MV88FX_SOC_I2S + depends on SND_MV88FX_SOC + +config SND_MV88FX_SOC_I2S + bool "I2S" + +config SND_MV88FX_SOC_SPDIF + bool "SPDIF" + +endchoice + +choice + prompt "Codec IC" + default SND_SOC_CS42L51 + depends on SND_MV88FX_SOC + +config SND_SOC_CS42L51 + bool "CS42L51" +endchoice diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile new file mode 100644 index 0000000..57674ad --- /dev/null +++ b/sound/soc/kirkwood/Makefile @@ -0,0 +1,7 @@ + +snd-soc-kirkwood-objs := kirkwood_pcm.o kirkwood_audio_hal.o +ifdef CONFIG_SND_SOC_CS42L51 +snd-soc-kirkwood-objs += cs42l51.o +endif + +obj-$(CONFIG_SND_MV88FX_SOC) += snd-soc-kirkwood.o diff --git a/sound/soc/kirkwood/cs42l51.c b/sound/soc/kirkwood/cs42l51.c new file mode 100644 index 0000000..f5a22f9 --- /dev/null +++ b/sound/soc/kirkwood/cs42l51.c @@ -0,0 +1,304 @@ +/* + * + * Marvell Orion Alsa Sound driver + * + * Author: Maen Suleiman + * Copyright (C) 2008 Marvell Ltd. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs42l51.h" + +/* FIXME: This code is not written in driver module style. This is written as + * helper for SOC driver */ + +struct i2c_client *client; + +static int cs42l51_add_i2c_device(unsigned char i2c_bus_no, + unsigned short i2c_add) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = i2c_add; + strlcpy(info.type, "cs42l51", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(i2c_bus_no); + if (!adapter) { + snd_printk("can't get i2c adapter\n"); + return -ENODEV; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + snd_printk("can't add i2c device\n"); + return -ENODEV; + } + + return 0; +} + +void cs42l51_del_i2c_device(void) +{ + if (client) + i2c_unregister_device(client); + client = NULL; +} + +/* + * offset: Register offset to start reading with + * buf : Pointer to the buffer to store the read data + * num : Number of registers to read + * + * Returns -ve errorno else number of registers read + */ + +int cs42l51_reg_read(unsigned char offset, unsigned char *buf, int num) +{ + int ret = 0; + + /* Set autoincrement bit */ + offset |= CODEC_INCR_ADDR; + + /* Send register offset */ + ret = i2c_master_send(client, &offset, 1); + if (ret != 1) { + snd_printd("Could not write register offset\n"); + return 0; + } + + return i2c_master_recv(client, buf, num); +} + +/* + * offset: Register offset to write + * data : Data to be written + * + * Returns -ve errorno else number of registers written (=1) + */ + +int cs42l51_reg_write(unsigned char offset, unsigned char data) +{ + int ret = 0; + unsigned char buf[2]; + + buf[0] = offset; + buf[1] = data; + + /* Send register offset & data */ + ret = i2c_master_send(client, &buf[0], 2); + + return (ret == 2) ? 1 : ret; +} + +int codec_init(int adc_mode, int digital_if_format, + unsigned char i2c_bus_no, unsigned short i2c_add) +{ + unsigned char reg_data; + + if (cs42l51_add_i2c_device(i2c_bus_no, i2c_add)) + return 1; + + if (cs42l51_reg_read(CODEC_ID_REG, ®_data, 1) < 0) + goto codec_init_error; + + if (CODEC_CHIP_ID != (reg_data >> 3) || + CODEC_REV_ID != (reg_data & 0x7)) { + snd_printd("Error: Invalid Cirrus Logic chip/rev ID!\n"); + return 1; + } + + if (cs42l51_reg_read(CODEC_IF_CTRL_REG, ®_data, 1) < 0) + goto codec_init_error; + + reg_data = (reg_data & ~(0x7<<3)) | (digital_if_format << 3); + + if (LEFT_JUSTIFIED_MODE == adc_mode) + reg_data &= (~0x4); + else + reg_data |= 0x4; + + if (cs42l51_reg_write(CODEC_IF_CTRL_REG, reg_data) < 0) + goto codec_init_error; + + return 0; + +codec_init_error: + snd_printd("I2C error\n"); + return 1; +} + +/* + * Initialize the audio decoder. + */ + +int cs42l51_init(int adc_mode, int digital_if_format, int rec, + unsigned char i2c_bus_no, unsigned short i2c_add) +{ + if (codec_init(adc_mode, digital_if_format, i2c_bus_no, i2c_add)) { + snd_printk("Error: Audio Codec init failed\n"); + return 1; + } + + /* Use the signal processor */ + if (cs42l51_reg_write(0x9, 0x40) < 0) + goto error; + + /* Unmute PCM-A & PCM-B and set default */ + if (cs42l51_reg_write(0x10, 0x60) < 0) + goto error; + if (cs42l51_reg_write(0x11, 0x60) < 0) + goto error; + + /* default for AOUTx */ + if (cs42l51_reg_write(0x16, 0x05) < 0) + goto error; + if (cs42l51_reg_write(0x17, 0x05) < 0) + goto error; + + /* swap channels */ + if (cs42l51_reg_write(0x18, 0xff) < 0) + goto error; + + /* MIC Power Control: power down mIC in channel B, power on channel A + * Recommended seq. in datasheet: + * 1. Enable the PDN bit + * 2. Enable power-down for the selected channels + * 3. Disable the PDN bit */ + + /* Note: Tested for mono recording only */ + if (!rec) { + /* Enable power down */ + if (cs42l51_reg_write(0x2, 0x11) < 0) + goto error; + + /* No record - Power down both channels */ + if (cs42l51_reg_write(0x2, 0x17) < 0) + goto error; + + /* Disable power down */ + if (cs42l51_reg_write(0x2, 0x16) < 0) + goto error; + } else { + if (rec == 2) { + /* Setreo recording - by default both channels are up */ + + /* MIC In channel selection - Select channel 3 + * unmute both channels */ + if (cs42l51_reg_write(0x7, 0xF0) < 0) + goto error; + + /* Power up mic pre-amplifier for both channels */ + if (cs42l51_reg_write(0x3, 0xA0) < 0) + goto error; + } else { + /* Enable power down */ + if (cs42l51_reg_write(0x2, 0x11) < 0) + goto error; + + /* Mono recording - Power down Channel B */ + if (cs42l51_reg_write(0x2, 0x15) < 0) + goto error; + + /* Disable power down */ + if (cs42l51_reg_write(0x2, 0x14) < 0) + goto error; + + /* MIC In channel selection - Select channel 3 + * Mute Channel B */ + if (cs42l51_reg_write(0x7, 0xF2) < 0) + goto error; + + /* Power down mic pre-amplifier for Channel B*/ + if (cs42l51_reg_write(0x3, 0xA8) < 0) + goto error; + } + } + + return 0; +error: + snd_printk("I2C error\n"); + return 1; +} + +#define AUD_NUM_VOLUME_STEPS (40) +static unsigned char auddec_volume_mapping[AUD_NUM_VOLUME_STEPS] = +{ + 0x19, 0xB2, 0xB7, 0xBD, 0xC3, 0xC9, 0xCF, 0xD5, + 0xD8, 0xE1, 0xE7, 0xED, 0xF3, 0xF9, 0xFF, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 +}; + + +/* + * Get the audio decoder volume for both channels. + * 0 is lowest volume, AUD_NUM_VOLUME_STEPS-1 is the highest volume. + */ + +void cs42l51_vol_get(unsigned char *vol_list) +{ + unsigned char reg_data[2]; + unsigned char i, vol_idx = 0; + + if (cs42l51_reg_read(0x16 + vol_idx, reg_data, 2) < 0) { + snd_printd("I2C error\n"); + snd_printk("Couldn't get volume\n"); + return; + } + + for (; vol_idx < 2; vol_idx++) { + /* Look for the index that mapps to this dB value. */ + for (i = 0; i < AUD_NUM_VOLUME_STEPS; i++) { + if (reg_data[vol_idx] == auddec_volume_mapping[i]) + break; + if ((auddec_volume_mapping[i] > + auddec_volume_mapping[AUD_NUM_VOLUME_STEPS-1]) + && (reg_data[vol_idx] > auddec_volume_mapping[i]) + && (reg_data[vol_idx] < auddec_volume_mapping[i+1])) + break; + } + vol_list[vol_idx] = i; + } +} + +/* + * Set the audio decoder volume for both channels. + * 0 is lowest volume, AUD_NUM_VOLUME_STEPS-1 is the highest volume. + */ +void cs42l51_vol_set(unsigned char *vol_list) +{ + unsigned int vol_idx; + + for (vol_idx = 0; vol_idx < 2; vol_idx++) { + if (vol_list[vol_idx] >= AUD_NUM_VOLUME_STEPS) + vol_list[vol_idx] = AUD_NUM_VOLUME_STEPS - 1; + + if (cs42l51_reg_write(0x16 + vol_idx, + auddec_volume_mapping[vol_list[vol_idx]]) < 0) { + snd_printd("I2C error\n"); + snd_printk("Couldn't set volume\n"); + return; + } + } +} diff --git a/sound/soc/kirkwood/cs42l51.h b/sound/soc/kirkwood/cs42l51.h new file mode 100644 index 0000000..f4e7951 --- /dev/null +++ b/sound/soc/kirkwood/cs42l51.h @@ -0,0 +1,59 @@ +/* + * Audio codec CS42L51 data definition file + */ + +#ifndef _CS42L51_H_ +#define _CS42L51_H_ + +#define CODEC_CHIP_ID 0x1B +#define CODEC_REV_ID 0x1 + +#define CODEC_ID_REG 0x1 +#define CODEC_IF_CTRL_REG 0x4 +#define CODEC_ADC_INPUT_INV_MUTE_REG 0x7 +#define CODEC_DAC_OUTPUT_CTRL_REG 0x8 +#define CODEC_DAC_CTRL_REG 0x9 +#define CODEC_PGAA_VOL_CTRL_REG 0xa +#define CODEC_TONE_CTRL_REG 0x15 +#define CODEC_VOL_OUTA_CTRL_REG 0x16 + +/* Set bit # 7 to 1 to get into auto incremental addressing mode */ +#define CODEC_INCR_ADDR 0x80 + +#define FALSE 0 +#define TRUE 1 + +/* Selects the digital interface format used for the data in on SDIN. */ +enum dac_digital_if_format { + L_JUSTIFIED_UP_TO_24_BIT, + I2S_UP_TO_24_BIT, + R_JUSTIFIED_UP_TO_24_BIT, + R_JUSTIFIED_20_BIT, + R_JUSTIFIED_18_BIT, + R_JUSTIFIED_16_BIT + +}; + +/* Selects either the I2S or Left-Justified digital interface format for the + data on SDOUT. */ +enum adc_mode { + LEFT_JUSTIFIED_MODE, + I2S_MODE +}; + +/* Initialize the Cirrus Logic device */ +int cs42l51_init(int adc_mode, int digital_if_format, int rec, + unsigned char i2c_bus_no, unsigned short i2c_add); + +/* Function to control output volume (playback) */ +void cs42l51_vol_get(unsigned char *vol_list); +void cs42l51_vol_set(unsigned char *vol_list); + +/* Function to access the Cirrus Logic CODEC registers */ +int cs42l51_reg_read(unsigned char offset, unsigned char *buf, int num); +int cs42l51_reg_write(unsigned char offset, unsigned char data); + + +void cs42l51_del_i2c_device(void); +#endif /* _CS42L51_H_ */ + diff --git a/sound/soc/kirkwood/kirkwood_audio_hal.c b/sound/soc/kirkwood/kirkwood_audio_hal.c new file mode 100644 index 0000000..28305e3 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood_audio_hal.c @@ -0,0 +1,821 @@ +/* + * Sound driver for Marvell Kirkwood family SOCs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include "kirkwood_audio_hal.h" + +static void mv_audio_init(void __iomem *base); +static void audio_setup_wins(void __iomem *base, + struct mbus_dram_target_info *dram); +static int set_window_as_per_baseadd(void __iomem *base, unsigned int baseadd, + unsigned int audio_offset, int win_num); + +/* Clocks Control and Status related*/ +static int mv_audio_dco_ctrl_set(struct mv_audio_freq_data *dcoCtrl, + void __iomem *base); + +/* Audio PlayBack related*/ +static int mv_audio_playback_control_set(void __iomem *base, unsigned int + audio_offset, struct mv_audio_playback_ctrl *ctrl); + +/* Audio SPDIF PlayBack related*/ +static void mv_spdif_playback_ctrl_set(void __iomem *base, + struct mv_spdif_playback_ctrl *ctrl); + +/* Audio I2S PlayBack related*/ +static int mv_i2s_playback_ctrl_set(void __iomem *base, + struct mv_i2s_playback_ctrl *ctrl); + +/* Audio Recording*/ +static int mv_audio_record_control_set(struct mv_audio_record_ctrl *ctrl, + unsigned int audio_offset, void __iomem *base); + +/* SPDIF Recording Related*/ +static int spdif_record_tclock_set(void __iomem *base, unsigned int tclk); + +/* I2S Recording Related*/ +static int mv_i2s_record_cntrl_set(struct mv_i2s_record_ctrl *ctrl, + void __iomem *base); + +static inline int audio_burst_bytes_num_get(int burst) +{ + switch (burst) { + case AUDIO_32BYTE_BURST: + return 32; + case AUDIO_128BYTE_BURST: + return 128; + default: + return 0xffffffff; + } +} + +int mv88fx_snd_hw_init(struct snd_card *card) +{ + void __iomem *base = chip->base; + struct mv88fx_snd_platform_data *platform_data = + card->dev->platform_data; + + if (platform_data->i2s_rec || platform_data->i2s_play) + if (codec_init(I2S_MODE, I2S_UP_TO_24_BIT, + platform_data->i2s_rec, platform_data->i2c_bus_no, + platform_data->i2c_address)) { + snd_printk("Initializing CS42L51 failed\n"); + return 1; + } + + writel(0xffffffff, (base + MV_AUDIO_INT_CAUSE_REG)); + writel(0, (base + MV_AUDIO_INT_MASK_REG)); + writel(0, (base + MV_AUDIO_SPDIF_REC_INT_CAUSE_MASK_REG)); + + mv_audio_init(base); + + audio_setup_wins(base, platform_data->dram); + + /* Disable all playback/recording */ + writel(readl(base + MV_AUDIO_PLAYBACK_CTRL_REG) & + (~(APCR_PLAY_I2S_ENABLE_MASK | APCR_PLAY_SPDIF_ENABLE_MASK)), + (base + MV_AUDIO_PLAYBACK_CTRL_REG)); + + writel(readl(base + MV_AUDIO_RECORD_CTRL_REG) & + (~(ARCR_RECORD_SPDIF_EN_MASK | ARCR_RECORD_I2S_EN_MASK)), + (base + MV_AUDIO_RECORD_CTRL_REG)); + + if (spdif_record_tclock_set(base, platform_data->tclk)) { + snd_printk("Marvell ALSA driver ERR. SPDIF clock set failed\n"); + return 1; + } + + return 0; +} + +int mv88fx_snd_hw_playback_set(struct mv88fx_snd_chip *chip) +{ + struct mv88fx_snd_stream *audio_stream = chip->stream[PLAYBACK]; + struct snd_pcm_substream *substream = audio_stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + struct mv_audio_playback_ctrl pcm_play_ctrl; + struct mv_i2s_playback_ctrl i2s_play_ctrl; + struct mv_spdif_playback_ctrl spdif_play_ctrl; + struct mv_audio_freq_data dco_ctrl; + + dco_ctrl.offset = chip->dco_ctrl_offst; + + switch (audio_stream->rate) { + case 44100: + dco_ctrl.baseFreq = AUDIO_FREQ_44_1KH; + break; + case 48000: + dco_ctrl.baseFreq = AUDIO_FREQ_48KH; + break; + case 96000: + dco_ctrl.baseFreq = AUDIO_FREQ_96KH; + break; + default: + snd_printk("Requested rate %d is not supported\n", + runtime->rate); return -1; + } + + pcm_play_ctrl.burst = (chip->burst == 128) ? AUDIO_128BYTE_BURST : + AUDIO_32BYTE_BURST; + + pcm_play_ctrl.loopBack = chip->loopback; + + if (audio_stream->stereo) { + pcm_play_ctrl.monoMode = AUDIO_PLAY_MONO_OFF; + } else { + switch (audio_stream->mono_mode) { + case MONO_LEFT: + pcm_play_ctrl.monoMode = AUDIO_PLAY_LEFT_MONO; + break; + case MONO_RIGHT: + pcm_play_ctrl.monoMode = AUDIO_PLAY_RIGHT_MONO; + break; + case MONO_BOTH: + default: + pcm_play_ctrl.monoMode = AUDIO_PLAY_BOTH_MONO; + break; + } + } + + if (audio_stream->format == SAMPLE_16IN16) { + pcm_play_ctrl.sampleSize = SAMPLE_16BIT; + i2s_play_ctrl.sampleSize = SAMPLE_16BIT; + } else if (audio_stream->format == SAMPLE_24IN32) { + pcm_play_ctrl.sampleSize = SAMPLE_24BIT; + i2s_play_ctrl.sampleSize = SAMPLE_24BIT; + } else if (audio_stream->format == SAMPLE_32IN32) { + pcm_play_ctrl.sampleSize = SAMPLE_32BIT; + i2s_play_ctrl.sampleSize = SAMPLE_32BIT; + } else { + snd_printk("Requested format %d is not supported\n", + runtime->format); + return -1; + } + + /* buffer and period sizes in frame */ + pcm_play_ctrl.bufferPhyBase = audio_stream->dma_addr; + pcm_play_ctrl.bufferSize = audio_stream->dma_size; + pcm_play_ctrl.intByteCount = audio_stream->period_size; + + /* I2S playback streem stuff */ + /*i2s_play_ctrl.sampleSize = pcm_play_ctrl.sampleSize;*/ + i2s_play_ctrl.justification = I2S_JUSTIFIED; + i2s_play_ctrl.sendLastFrame = 0; + + spdif_play_ctrl.nonPcm = FALSE; + + spdif_play_ctrl.validity = chip->ch_stat_valid; + + if (audio_stream->stat_mem) { + spdif_play_ctrl.userBitsFromMemory = TRUE; + spdif_play_ctrl.validityFromMemory = TRUE; + spdif_play_ctrl.blockStartInternally = FALSE; + } else { + spdif_play_ctrl.userBitsFromMemory = FALSE; + spdif_play_ctrl.validityFromMemory = FALSE; + spdif_play_ctrl.blockStartInternally = TRUE; + } + + /* If this is non-PCM sound, mute I2S channel */ + spin_lock_irq(&chip->reg_lock); + + if (!(readl(chip->base + MV_AUDIO_PLAYBACK_CTRL_REG) & + (APCR_PLAY_I2S_ENABLE_MASK | APCR_PLAY_SPDIF_ENABLE_MASK))) { + + if (mv_audio_dco_ctrl_set(&dco_ctrl, chip->base)) { + snd_printk("Failed to initialize DCO clock control.\n"); + goto error; + } + } + + if (audio_stream->clock_src == DCO_CLOCK) + while ((readl(chip->base + MV_AUDIO_SPCR_DCO_STATUS_REG) & + ASDSR_DCO_LOCK_MASK) == 0) + cpu_relax(); + else if (audio_stream->clock_src == SPCR_CLOCK) + while ((readl(chip->base + MV_AUDIO_SPCR_DCO_STATUS_REG) & + ASDSR_SPCR_LOCK_MASK) == 0) + cpu_relax(); + + if (mv_audio_playback_control_set(chip->base, chip->audio_offset, + &pcm_play_ctrl)) { + snd_printk("Failed to initialize PCM playback control.\n"); + goto error; + } + + if (mv_i2s_playback_ctrl_set(chip->base, &i2s_play_ctrl)) { + snd_printk("Failed to initialize I2S playback control.\n"); + goto error; + } + + mv_spdif_playback_ctrl_set(chip->base, &spdif_play_ctrl); + + spin_unlock_irq(&chip->reg_lock); + + return 0; +error: + spin_unlock_irq(&chip->reg_lock); + return -1; +} + +int mv88fx_snd_hw_capture_set(struct mv88fx_snd_chip *chip) +{ + struct mv88fx_snd_stream *audio_stream = chip->stream[CAPTURE]; + struct snd_pcm_substream *substream = audio_stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + struct mv_audio_record_ctrl pcm_rec_ctrl; + struct mv_i2s_record_ctrl i2s_rec_ctrl; + struct mv_audio_freq_data dco_ctrl; + + dco_ctrl.offset = chip->dco_ctrl_offst; + + switch (audio_stream->rate) { + case 44100: + dco_ctrl.baseFreq = AUDIO_FREQ_44_1KH; + break; + case 48000: + dco_ctrl.baseFreq = AUDIO_FREQ_48KH; + break; + case 96000: + dco_ctrl.baseFreq = AUDIO_FREQ_96KH; + break; + default: + snd_printk("Requested rate %d is not supported\n", + runtime->rate); return -1; + } + + pcm_rec_ctrl.burst = (chip->burst == 128) ? AUDIO_128BYTE_BURST : + AUDIO_32BYTE_BURST; + + if (audio_stream->format == SAMPLE_16IN16) { + pcm_rec_ctrl.sampleSize = SAMPLE_16BIT; + } else if (audio_stream->format == SAMPLE_24IN32) { + pcm_rec_ctrl.sampleSize = SAMPLE_24BIT; + } else if (audio_stream->format == SAMPLE_32IN32) { + pcm_rec_ctrl.sampleSize = SAMPLE_32BIT; + } else { + snd_printk("Requested format %d is not supported\n", + runtime->format); + return -1; + } + + /* If request for tereo record comes on the boards that doesn't + * support stereo recording */ + if ((!chip->stereo) && audio_stream->stereo) { + snd_printk("Stereo recording is not supported\n"); + return -1; + } + + pcm_rec_ctrl.mono = (audio_stream->stereo) ? FALSE : TRUE; + + if (pcm_rec_ctrl.mono) { + switch (audio_stream->mono_mode) { + case MONO_LEFT: + pcm_rec_ctrl.monoChannel = AUDIO_REC_LEFT_MONO; + break; + default: + case MONO_RIGHT: + pcm_rec_ctrl.monoChannel = AUDIO_REC_RIGHT_MONO; + break; + } + + } else { + pcm_rec_ctrl.monoChannel = AUDIO_REC_LEFT_MONO; + } + + + pcm_rec_ctrl.bufferPhyBase = audio_stream->dma_addr; + pcm_rec_ctrl.bufferSize = audio_stream->dma_size; + + pcm_rec_ctrl.intByteCount = audio_stream->period_size; + + /* I2S record streem stuff */ + i2s_rec_ctrl.sample = pcm_rec_ctrl.sampleSize; + i2s_rec_ctrl.justf = I2S_JUSTIFIED; + + spin_lock_irq(&chip->reg_lock); + + /* set clock only if record is not enabled*/ + if (!(readl(chip->base + MV_AUDIO_RECORD_CTRL_REG) & + (ARCR_RECORD_SPDIF_EN_MASK | ARCR_RECORD_I2S_EN_MASK))) { + + if (mv_audio_dco_ctrl_set(&dco_ctrl, chip->base)) { + snd_printk("Failed to initialize DCO clock control.\n"); + return -1; + } + } + + if (mv_audio_record_control_set(&pcm_rec_ctrl, chip->audio_offset, + chip->base)) { + snd_printk("Failed to initialize PCM record control.\n"); + return -1; + } + + if (mv_i2s_record_cntrl_set(&i2s_rec_ctrl, chip->base)) { + snd_printk("Failed to initialize I2S record control.\n"); + return -1; + } + spin_unlock_irq(&chip->reg_lock); + + return 0; +} + +static void mv_audio_init(void __iomem *base) +{ + int timeout = 10000000; + unsigned int reg_data; + + reg_data = readl(base + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + + writel(reg_data, base + 0x1200); + + do { + timeout--; + } while (timeout); + + reg_data = readl(base + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + + writel(reg_data, base + 0x1200); +} + +static void audio_setup_wins(void __iomem *base, + struct mbus_dram_target_info *dram) +{ + int win_num; + + /* First disable and clear windows */ + for (win_num = 0; win_num < MV_AUDIO_MAX_ADDR_DECODE_WIN; win_num++) { + writel(0, base + MV_AUDIO_WIN_CTRL_REG(win_num)); + writel(0, base + MV_AUDIO_WIN_BASE_REG(win_num)); + } + + /* Setup windows for DDR */ + for (win_num = 0; win_num < MV_AUDIO_MAX_ADDR_DECODE_WIN; win_num++) { + /* We will set the Window to DRAM_CS1 in default */ + struct mbus_dram_window *cs = &dram->cs[1]; + + writel(cs->base & 0xffff0000, + base + MV_AUDIO_WIN_BASE_REG(win_num)); + writel(((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + base + MV_AUDIO_WIN_CTRL_REG(win_num)); + } +} + +#define MV_BOARD_TCLK_133MHZ 133333333 +#define MV_BOARD_TCLK_150MHZ 150000000 +#define MV_BOARD_TCLK_166MHZ 166666667 +#define MV_BOARD_TCLK_200MHZ 200000000 + +static int spdif_record_tclock_set(void __iomem *base, unsigned int tclk) +{ + unsigned int reg_data; + + reg_data = readl(base + MV_AUDIO_SPDIF_REC_GEN_REG); + + switch (tclk) { + case MV_BOARD_TCLK_133MHZ: + reg_data |= ASRGR_CORE_CLK_FREQ_133MHZ; + break; + case MV_BOARD_TCLK_150MHZ: + reg_data |= ASRGR_CORE_CLK_FREQ_150MHZ; + break; + case MV_BOARD_TCLK_166MHZ: + reg_data |= ASRGR_CORE_CLK_FREQ_166MHZ; + break; + case MV_BOARD_TCLK_200MHZ: + reg_data |= ASRGR_CORE_CLK_FREQ_200MHZ; + break; + default: + snd_printk("Not supported core clock %d\n", tclk); + return 1; + } + + writel(reg_data, base + MV_AUDIO_SPDIF_REC_GEN_REG); + + return 0; +} + +static int mv_audio_record_control_set(struct mv_audio_record_ctrl *ctrl, + unsigned int audio_offset, void __iomem *base) +{ + unsigned int reg, buff_start, buff_end; + unsigned int win_base, win_size; + + if (ctrl->monoChannel > AUDIO_REC_RIGHT_MONO) { + snd_printk("Error: Illegal monoChannel %x\n", + ctrl->monoChannel); + + return 1; + } + + if ((ctrl->burst != AUDIO_32BYTE_BURST) && + (ctrl->burst != AUDIO_128BYTE_BURST)) { + snd_printk("Error: Illegal burst %x\n", + ctrl->burst); + + return 1; + } + + if (ctrl->bufferPhyBase & (MV_AUDIO_BUFFER_MIN_ALIGN - 1)) { + snd_printk("Error bufferPhyBase is not aligned to 0x%x"\ + " bytes\n", MV_AUDIO_BUFFER_MIN_ALIGN); + + return 1; + } + + if ((ctrl->bufferSize <= audio_burst_bytes_num_get(ctrl->burst)) | + (ctrl->bufferSize & (audio_burst_bytes_num_get(ctrl->burst) - 1)) || + (ctrl->bufferSize > AUDIO_REG_TO_SIZE(APBBCR_SIZE_MAX))) { + snd_printk("Error bufferSize smaller than or not multiple "\ + "of 0x%x bytes or larger than 0x%x\n", + audio_burst_bytes_num_get(ctrl->burst), + AUDIO_REG_TO_SIZE(APBBCR_SIZE_MAX)); + + return 1; + } + + reg = readl(base + MV_AUDIO_RECORD_CTRL_REG); + reg &= ~(ARCR_RECORD_BURST_SIZE_MASK | ARCR_RECORDED_MONO_CHNL_MASK | + ARCR_RECORD_SAMPLE_SIZE_MASK); + + switch (ctrl->sampleSize) { + case SAMPLE_16BIT: + case SAMPLE_16BIT_NON_COMPACT: + case SAMPLE_20BIT: + case SAMPLE_24BIT: + case SAMPLE_32BIT: + reg |= ctrl->sampleSize << ARCR_RECORD_SAMPLE_SIZE_OFFS; + break; + default: + snd_printk("Error: Illegal sampleSize %x\n", + ctrl->sampleSize); + + return 1; + } + + reg |= ctrl->burst << ARCR_RECORD_BURST_SIZE_OFFS; + reg |= ctrl->monoChannel << ARCR_RECORDED_MONO_CHNL_OFFS; + + if (ctrl->mono) + reg |= ARCR_RECORD_MONO_MASK; + else + reg &= (~ARCR_RECORD_MONO_MASK); + + writel(reg, base + MV_AUDIO_RECORD_CTRL_REG); + + /* Get the details of the Record address window*/ + win_base = readl(base + MV_AUDIO_WIN_BASE_REG(MV_AUDIO_RECORD_WIN_NUM)); + win_size = readl(base + MV_AUDIO_WIN_CTRL_REG(MV_AUDIO_RECORD_WIN_NUM)); + + /* Window size bits are 31:16. Where size = + * (2 ^ no of ones) * 64 KB. e.g. 0x0FF says 16MB */ + win_size = ((win_size >> 16) + 1) << 16; + + buff_start = ctrl->bufferPhyBase; + buff_end = buff_start + ctrl->bufferSize - 1; + + /* If buffer address is not within window boundries then try to set a + * new value to the Record window by geting the target of where the + * buffer exist, if the buffer is within the window of the new target + * then set the Record window to that target else return Fail + */ + + if (!(((buff_start >= win_base) && + (buff_start <= (win_base + win_size - 1))) || + ((buff_end >= win_base) && + (buff_end <= (win_base + win_size - 1))))) { + snd_printd("Audio record buffer is not within window"); + + /* Set the window for the buffer that user require + for the palyback\recording window to the target window */ + if (set_window_as_per_baseadd(base, ctrl->bufferPhyBase, + audio_offset, MV_AUDIO_RECORD_WIN_NUM)) { + snd_printk("Playback buffer (%#x) is not " + "within a valid target\n", + ctrl->bufferPhyBase); + return 1; + } + } + + /* Set the interrupt byte count */ + reg = ctrl->intByteCount & ARBCI_BYTE_COUNT_MASK; + writel(reg, base + MV_AUDIO_RECORD_BYTE_CNTR_INT_REG); + + writel(ctrl->bufferPhyBase, base + MV_AUDIO_RECORD_START_ADDR_REG); + writel(AUDIO_SIZE_TO_REG(ctrl->bufferSize), + base + MV_AUDIO_RECORD_BUFF_SIZE_REG); + + + return 0; +} + +static int mv_i2s_record_cntrl_set(struct mv_i2s_record_ctrl *ctrl, + void __iomem *base) +{ + unsigned int reg; + + reg = readl(base + MV_AUDIO_I2S_REC_CTRL_REG); + reg &= ~(AIRCR_I2S_RECORD_JUSTF_MASK|AIRCR_I2S_SAMPLE_SIZE_MASK); + + switch (ctrl->justf) { + case I2S_JUSTIFIED: + case LEFT_JUSTIFIED: + case RIGHT_JUSTIFIED: + case RISE_BIT_CLCK_JUSTIFIED: + reg |= ctrl->justf << AIRCR_I2S_RECORD_JUSTF_OFFS; + break; + default: + return 1; + } + + reg |= ctrl->sample << AIRCR_I2S_SAMPLE_SIZE_OFFS; + + writel(reg, base + MV_AUDIO_I2S_REC_CTRL_REG); + return 0; +} + +/* We trust the value set in the window registers and don't check them. + * As there is some value already present in the register, we assume + * base and size are aligned */ + +static int set_window_as_per_baseadd(void __iomem *base, unsigned int baseadd, + unsigned int audio_offset, int win_num) +{ + int dram_cs, win; + unsigned int win_base, win_size, size; + unsigned char dram_attr[4] = {0x0E, 0x0D, 0x0B, 0x07}; + + /* Base passed is Audio base address. Audio base address is + * Internal register base address + audio_offset */ + void __iomem *internal_reg_base = base - audio_offset; + + for (dram_cs = 0; dram_cs < 4; dram_cs++) { + win_base = readl(internal_reg_base + 0x1500 + (8 * dram_cs)); + win_size = readl(internal_reg_base + 0x1504 + (8 * dram_cs)); + + /* skip if window is disabled */ + if (!(win_size & 1)) + continue; + + /* Window size bits are 31:24. Where size = + * (2 ^ no of ones) * 16 MB. e.g. 0x0F says 256MB */ + size = ((win_size >> 24) + 1) << 24; + + if ((baseadd >= win_base) && (baseadd < (win_base + size))) { + snd_printd("DRAM window %d set for %s window", + dram_cs, win_num ? "plaback" : "record"); + writel(win_base, + base + MV_AUDIO_WIN_BASE_REG(win_num)); + + /* DRAM window ctrl regs are bit different than Audio + * Set size, attribute and target ID */ + win_size = ((size - 1) & 0xffff0000) | 1; + win_size |= (dram_attr[dram_cs] << 8); /* Atribute */ + win_size &= (~(0xF << 4)); /* Target ID */ + + writel(win_size, + base + MV_AUDIO_WIN_CTRL_REG(win_num)); + snd_printd("win_base 0x%08x\twin_size 0x%08x", + win_base, win_size); + return 0; + } + } + + for (win = 0; win < 8; win++) { + win_base = readl(internal_reg_base + 0x2004 + (0x10 * win)); + win_size = readl(internal_reg_base + 0x2008 + (0x10 * win)); + + /* skip if window is disabled */ + if (!(win_size & 1)) + continue; + + /* Window size bits are 31:16. Where size = + * (2 ^ no of ones) * 64 KB. e.g. 0x0FF says 16MB */ + size = ((win_size >> 16) + 1) << 16; + if ((baseadd >= win_base) && (baseadd < (win_base + size))) { + snd_printd("CPU window %d set for %s window", + win_base, win_num ? "plaback" : "record"); + writel(win_base, + base + MV_AUDIO_WIN_BASE_REG(win_num)); + writel(win_size, + base + MV_AUDIO_WIN_CTRL_REG(win_num)); + return 0; + } + } + + return 1; +} + +static int mv_audio_playback_control_set(void __iomem *base, unsigned int + audio_offset, struct mv_audio_playback_ctrl *ctrl) +{ + unsigned int reg, buff_start, buff_end; + unsigned int win_base, win_size; + + if (ctrl->monoMode >= AUDIO_PLAY_OTHER_MONO) { + snd_printk("Error: Illegal monoMode %x\n", ctrl->monoMode); + return 1; + } + + if ((ctrl->burst != AUDIO_32BYTE_BURST) && + (ctrl->burst != AUDIO_128BYTE_BURST)) { + snd_printk("Error: Illegal burst %x\n", ctrl->burst); + return 1; + } + + if (ctrl->bufferPhyBase & (MV_AUDIO_BUFFER_MIN_ALIGN - 1)) { + snd_printk("Error, bufferPhyBase is not aligned to 0x%x "\ + "bytes\n", MV_AUDIO_BUFFER_MIN_ALIGN); + return 1; + } + + if ((ctrl->bufferSize <= audio_burst_bytes_num_get(ctrl->burst)) || + (ctrl->bufferSize & (audio_burst_bytes_num_get(ctrl->burst) - 1)) || + (ctrl->bufferSize > AUDIO_REG_TO_SIZE(APBBCR_SIZE_MAX))) { + snd_printk("Error, bufferSize smaller than or not multiple "\ + "of 0x%x bytes or larger than 0x%x", + audio_burst_bytes_num_get(ctrl->burst), + AUDIO_REG_TO_SIZE(APBBCR_SIZE_MAX)); + return 1; + } + + reg = readl(base + MV_AUDIO_PLAYBACK_CTRL_REG); + reg &= ~(APCR_PLAY_BURST_SIZE_MASK | APCR_LOOPBACK_MASK | + APCR_PLAY_MONO_MASK | APCR_PLAY_SAMPLE_SIZE_MASK); + reg |= ctrl->burst << APCR_PLAY_BURST_SIZE_OFFS; + reg |= ctrl->loopBack << APCR_LOOPBACK_OFFS; + reg |= ctrl->monoMode << APCR_PLAY_MONO_OFFS; + reg |= ctrl->sampleSize << APCR_PLAY_SAMPLE_SIZE_OFFS; + writel(reg, base + MV_AUDIO_PLAYBACK_CTRL_REG); + + /* Get the details of the Playback address window*/ + win_base = readl(base + + MV_AUDIO_WIN_BASE_REG(MV_AUDIO_PLAYBACK_WIN_NUM)); + win_size = readl(base + + MV_AUDIO_WIN_CTRL_REG(MV_AUDIO_PLAYBACK_WIN_NUM)); + + /* Window size bits are 31:16. Where size = + * (2 ^ no of ones) * 64 KB. e.g. 0x0FF says 16MB */ + win_size = ((win_size >> 16) + 1) << 16; + + buff_start = ctrl->bufferPhyBase; + buff_end = buff_start + ctrl->bufferSize - 1; + + /* If Playback window is not enabled or buffer address is not within + * window boundries then try to set a new value to the Playback window*/ + + if (!(((buff_start >= win_base) && + (buff_start <= (win_base + win_size - 1))) || + ((buff_end >= win_base) && + (buff_end <= (win_base + win_size - 1))))) { + snd_printd("Audio playback buffer is not within window\n"); + + /* Set the window for the buffer that user require + for the palyback\recording window to the target window */ + if (set_window_as_per_baseadd(base, ctrl->bufferPhyBase, + audio_offset, MV_AUDIO_PLAYBACK_WIN_NUM)) { + snd_printk("Record buffer (%#x) is not " + "within a valid target\n", + ctrl->bufferPhyBase); + return 1; + } + } + + /* Set the interrupt byte count */ + reg = ctrl->intByteCount & APBCI_BYTE_COUNT_MASK; + writel(reg, base + MV_AUDIO_PLAYBACK_BYTE_CNTR_INT_REG); + + writel(ctrl->bufferPhyBase, + base + MV_AUDIO_PLAYBACK_BUFF_START_REG); + writel(AUDIO_SIZE_TO_REG(ctrl->bufferSize), + base + MV_AUDIO_PLAYBACK_BUFF_SIZE_REG); + + return 0; +} + +static int mv_i2s_playback_ctrl_set(void __iomem *base, + struct mv_i2s_playback_ctrl *ctrl) +{ + unsigned int reg_data; + + reg_data = readl(base + MV_AUDIO_I2S_PLAY_CTRL_REG); + reg_data &= ~(AIPCR_I2S_PB_JUSTF_MASK | AIPCR_I2S_PB_SAMPLE_SIZE_MASK); + + if (ctrl->sampleSize > SAMPLE_16BIT) { + snd_printk("Illigal sample size\n"); + return 1; + } + + reg_data |= ctrl->sampleSize << AIPCR_I2S_PB_SAMPLE_SIZE_OFFS; + + if (ctrl->sendLastFrame) + reg_data |= AIPCR_I2S_SEND_LAST_FRM_MASK; + else + reg_data &= ~AIPCR_I2S_SEND_LAST_FRM_MASK; + + switch (ctrl->justification) { + case I2S_JUSTIFIED: + case LEFT_JUSTIFIED: + case RIGHT_JUSTIFIED: + reg_data |= ctrl->justification << AIPCR_I2S_PB_JUSTF_OFFS; + break; + default: + snd_printk("Illigal justification value\n"); + return 1; + } + + writel(reg_data, base + MV_AUDIO_I2S_PLAY_CTRL_REG); + + return 0; +} + +static void mv_spdif_playback_ctrl_set(void __iomem *base, + struct mv_spdif_playback_ctrl *ctrl) +{ + unsigned int reg_data; + + reg_data = readl(base + MV_AUDIO_SPDIF_PLAY_CTRL_REG); + + if (ctrl->blockStartInternally) + reg_data |= ASPCR_SPDIF_BLOCK_START_MASK; + else + reg_data &= ~ASPCR_SPDIF_BLOCK_START_MASK; + + if (ctrl->validityFromMemory) + reg_data |= ASPCR_SPDIF_PB_EN_MEM_VALIDITY_MASK; + else + reg_data &= ~ASPCR_SPDIF_PB_EN_MEM_VALIDITY_MASK; + + if (ctrl->userBitsFromMemory) + reg_data |= ASPCR_SPDIF_PB_MEM_USR_EN_MASK; + else + reg_data &= ~ASPCR_SPDIF_PB_MEM_USR_EN_MASK; + + if (ctrl->validity) + reg_data |= ASPCR_SPDIF_PB_REG_VALIDITY_MASK; + else + reg_data &= ~ASPCR_SPDIF_PB_REG_VALIDITY_MASK; + + if (ctrl->nonPcm) + reg_data |= ASPCR_SPDIF_PB_NONPCM_MASK; + else + reg_data &= ~ASPCR_SPDIF_PB_NONPCM_MASK; + + writel(reg_data, base + MV_AUDIO_SPDIF_PLAY_CTRL_REG); +} + +static int mv_audio_dco_ctrl_set(struct mv_audio_freq_data *dcoCtrl, + void __iomem *base) +{ + unsigned int reg; + + /* Check parameters*/ + if (dcoCtrl->baseFreq > AUDIO_FREQ_96KH) { + snd_printk("dcoCtrl->baseFreq value (0x%x) invalid\n", + dcoCtrl->baseFreq); + return 1; + } + + if ((dcoCtrl->offset > 0xFD0) || (dcoCtrl->offset < 0x20)) { + snd_printk("dcoCtrl->offset value (0x%x) invalid\n", + dcoCtrl->baseFreq); + return 1; + } + + reg = readl(base + MV_AUDIO_DCO_CTRL_REG); + + reg &= ~(ADCR_DCO_CTRL_FS_MASK | ADCR_DCO_CTRL_OFFSET_MASK); + reg |= ((dcoCtrl->baseFreq << ADCR_DCO_CTRL_FS_OFFS) | + (dcoCtrl->offset << ADCR_DCO_CTRL_OFFSET_OFFS)); + + writel(reg, base + MV_AUDIO_DCO_CTRL_REG); + + return 0; +} diff --git a/sound/soc/kirkwood/kirkwood_audio_hal.h b/sound/soc/kirkwood/kirkwood_audio_hal.h new file mode 100644 index 0000000..ce08102 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood_audio_hal.h @@ -0,0 +1,109 @@ +/* + * Sound driver data definition file for Marvell Kirkwood family SOCs + */ + +#ifndef __AUDIO_HAL_H +#define __AUDIO_HAL_H + +#include "kirkwood_audio_regs.h" + +#ifdef CONFIG_SND_SOC_CS42L51 +#include "cs42l51.h" + +#define codec_init cs42l51_init +#define codec_vol_get cs42l51_vol_get +#define codec_vol_set cs42l51_vol_set +#define codec_del_i2c_device cs42l51_del_i2c_device +#endif + +#define CODEC_I2C_BUS_NO 0 +#define CODEC_I2C_ADD 0x4A + +/*********************************/ +/* General enums and structures */ +/*******************************/ +/* Type of Audio operations*/ +enum mv_audio_operation { + AUDIO_PLAYBACK = 0, + AUDIO_RECORD = 1 +}; + +struct mv_audio_freq_data{ + int baseFreq; /* Control FS, selects the base frequency + * of the DCO */ + u32 offset; /* Offset control in which each step equals to + * 0.9536 ppm */ +}; + + +/***********************************/ +/* Play Back related structures */ +/*********************************/ + +struct mv_audio_playback_ctrl { + int burst; /* Specifies the Burst Size of the DMA */ + bool loopBack; /* When Loopback is enabled, playback data + * is looped back to be recorded */ + int monoMode; /* Mono Mode is used */ + unsigned int bufferPhyBase; /* Physical Address of DMA buffer */ + unsigned int bufferSize; /* Size of DMA buffer */ + unsigned int intByteCount; /* Number of bytes after which an + * interrupt will be issued.*/ + int sampleSize; /* Playback Sample Size*/ +}; + +struct mv_spdif_playback_ctrl { + bool nonPcm; /* PCM or non-PCM mode*/ + bool validity; /* Validity bit value when using + * registers (userBitsFromMemory=0) */ + bool underrunData; /* If true send last frame on mute/pause/ + * underrun otherwise send 24 binary */ + bool userBitsFromMemory; /* otherwise from intenal registers */ + bool validityFromMemory; /* otherwise from internal registers */ + bool blockStartInternally; /* When user and valid bits are form + * registers then this bit should be zero */ +}; + +struct mv_i2s_playback_ctrl { + int sampleSize; + int justification; + bool sendLastFrame; /* If true send last frame on + * mute/pause/underrun + * otherwise send 64 binary*/ +}; + + +/*********************************/ +/* Recording related structures */ +/*********************************/ + +struct mv_audio_record_ctrl { + int burst; /* Recording DMA Burst Size */ + int sampleSize; /*Recording Sample Size */ + bool mono; /* If true then recording mono else + * recording stereo */ + int monoChannel; /* Left or right moono */ + u32 bufferPhyBase; /* Physical Address of DMA buffer */ + u32 bufferSize; /* Size of DMA buffer */ + + u32 intByteCount; /* Number of bytes after which an + * interrupt will be issued.*/ + +}; + +struct mv_i2s_record_ctrl { + int sample; /* I2S Recording Sample Size*/ + int justf; +}; + +/******************/ +/* Functions API */ +/****************/ + +extern struct mv88fx_snd_chip *chip; + +int mv88fx_snd_hw_init(struct snd_card *card); +int mv88fx_snd_hw_capture_set(struct mv88fx_snd_chip *chip); +int mv88fx_snd_hw_playback_set(struct mv88fx_snd_chip *chip); + +#endif diff --git a/sound/soc/kirkwood/kirkwood_audio_regs.h b/sound/soc/kirkwood/kirkwood_audio_regs.h new file mode 100644 index 0000000..1d3df15 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood_audio_regs.h @@ -0,0 +1,310 @@ +/* + * Audio registers for Marvell Kirkwood family SOCs + */ + +#ifndef __KW_AUDIO_REGS_H +#define __KW_AUDIO_REGS_H + +enum mv_audio_freq { + AUDIO_FREQ_44_1KH = 0, /* 11.2896Mhz */ + AUDIO_FREQ_48KH = 1, /* 12.288Mhz */ + AUDIO_FREQ_96KH = 2, /* 24.576Mhz */ + AUDIO_FREQ_LOWER_44_1KH = 3 , /* Lower than 11.2896MHz */ + AUDIO_FREQ_HIGHER_96KH = 4, /* Higher than 24.576MHz */ + AUDIO_FREQ_OTHER = 7, /* Other frequency */ +}; + +enum mv_audio_burst_size { + AUDIO_32BYTE_BURST = 1, + AUDIO_128BYTE_BURST = 2, +}; + +enum mv_audio_playback_mono { + AUDIO_PLAY_MONO_OFF = 0, + AUDIO_PLAY_LEFT_MONO = 1, + AUDIO_PLAY_RIGHT_MONO = 2, + AUDIO_PLAY_BOTH_MONO = 3, + AUDIO_PLAY_OTHER_MONO = 4 +}; + +enum mv_audio_record_mono { + AUDIO_REC_LEFT_MONO = 0, + AUDIO_REC_RIGHT_MONO = 1, +}; + +enum mv_audio_sample_size { + SAMPLE_32BIT = 0, + SAMPLE_24BIT = 1, + SAMPLE_20BIT = 2, + SAMPLE_16BIT = 3, + SAMPLE_16BIT_NON_COMPACT = 7 +}; + +enum mv_audio_i2s_justification { + LEFT_JUSTIFIED = 0, + I2S_JUSTIFIED = 5, + RISE_BIT_CLCK_JUSTIFIED = 7, + RIGHT_JUSTIFIED = 8, +}; + +#define APBBCR_SIZE_MAX 0x3FFFFF +#define APBBCR_SIZE_SHIFT 0x2 + +#define AUDIO_REG_TO_SIZE(reg) (((reg) + 1) << APBBCR_SIZE_SHIFT) +#define AUDIO_SIZE_TO_REG(size) (((size) >> APBBCR_SIZE_SHIFT) - 1) + +#define MV_AUDIO_BUFFER_MIN_ALIGN 0x8 + +/********************/ +/* Clocking Control*/ +/*******************/ + +#define MV_AUDIO_DCO_CTRL_REG 0x1204 +#define MV_AUDIO_SPCR_DCO_STATUS_REG 0x120c +#define MV_AUDIO_SAMPLE_CNTR_CTRL_REG 0x1220 +#define MV_AUDIO_PLAYBACK_SAMPLE_CNTR_REG 0x1224 +#define MV_AUDIO_RECORD_SAMPLE_CNTR_REG 0x1228 +#define MV_AUDIO_CLOCK_CTRL_REG 0x1230 + +/* MV_AUDIO_DCO_CTRL_REG */ +#define ADCR_DCO_CTRL_FS_OFFS 0 +#define ADCR_DCO_CTRL_FS_MASK (0x3 << ADCR_DCO_CTRL_FS_OFFS) +#define ADCR_DCO_CTRL_FS_44_1KHZ (0x0 << ADCR_DCO_CTRL_FS_OFFS) +#define ADCR_DCO_CTRL_FS_48KHZ (0x1 << ADCR_DCO_CTRL_FS_OFFS) +#define ADCR_DCO_CTRL_FS_96KHZ (0x2 << ADCR_DCO_CTRL_FS_OFFS) + + +#define ADCR_DCO_CTRL_OFFSET_OFFS 2 +#define ADCR_DCO_CTRL_OFFSET_MASK (0xfff << ADCR_DCO_CTRL_OFFSET_OFFS) + +/* MV_AUDIO_SPCR_DCO_STATUS_REG */ +#define ASDSR_SPCR_CTRLFS_OFFS 0 +#define ASDSR_SPCR_CTRLFS_MASK (0x7 << ASDSR_SPCR_CTRLFS_OFFS) +#define ASDSR_SPCR_CTRLFS_44_1KHZ (0x0 << ASDSR_SPCR_CTRLFS_OFFS) +#define ASDSR_SPCR_CTRLFS_48KHZ (0x1 << ASDSR_SPCR_CTRLFS_OFFS) +#define ASDSR_SPCR_CTRLFS_96KHZ (0x2 << ASDSR_SPCR_CTRLFS_OFFS) +#define ASDSR_SPCR_CTRLFS_44_1KHZ_LESS (0x3 << ASDSR_SPCR_CTRLFS_OFFS) +#define ASDSR_SPCR_CTRLFS_96KHZ_MORE (0x4 << ASDSR_SPCR_CTRLFS_OFFS) +#define ASDSR_SPCR_CTRLFS_OTHER (0x7 << ASDSR_SPCR_CTRLFS_OFFS) + + +#define ASDSR_SPCR_CTRLOFFSET_OFFS 3 +#define ASDSR_SPCR_CTRLOFFSET_MASK (0xfff << ASDSR_SPCR_CTRLOFFSET_OFFS) + +#define ASDSR_SPCR_LOCK_OFFS 15 +#define ASDSR_SPCR_LOCK_MASK (0x1 << ASDSR_SPCR_LOCK_OFFS) + +#define ASDSR_DCO_LOCK_OFFS 16 +#define ASDSR_DCO_LOCK_MASK (0x1 << ASDSR_DCO_LOCK_OFFS) + +#define ASDSR_PLL_LOCK_OFFS 17 +#define ASDSR_PLL_LOCK_MASK (0x1 << ASDSR_PLL_LOCK_OFFS) + +/*MV_AUDIO_SAMPLE_CNTR_CTRL_REG */ + +#define ASCCR_CLR_PLAY_CNTR_OFFS 9 +#define ASCCR_CLR_PLAY_CNTR_MASK (0x1 << ASCCR_CLR_PLAY_CNTR_OFFS) + +#define ASCCR_CLR_REC_CNTR_OFFS 8 +#define ASCCR_CLR_REC_CNTR_MASK (0x1 << ASCCR_CLR_REC_CNTR_OFFS) + +#define ASCCR_ACTIVE_PLAY_CNTR_OFFS 1 +#define ASCCR_ACTIVE_PLAY_CNTR_MASK (0x1 << ASCCR_ACTIVE_PLAY_CNTR_OFFS) + +#define ASCCR_ACTIVE_REC_CNTR_OFFS 0 +#define ASCCR_ACTIVE_REC_CNTR_MASK (0x1 << ASCCR_ACTIVE_REC_CNTR_OFFS) + +/* MV_AUDIO_CLOCK_CTRL_REG */ +#define ACCR_MCLK_SOURCE_OFFS 0 +#define ACCR_MCLK_SOURCE_MASK (0x3 << ACCR_MCLK_SOURCE_OFFS) +#define ACCR_MCLK_SOURCE_DCO (0x0 << ACCR_MCLK_SOURCE_OFFS) +#define ACCR_MCLK_SOURCE_SPCR (0x2 << ACCR_MCLK_SOURCE_OFFS) +#define ACCR_MCLK_SOURCE_EXT (0x3 << ACCR_MCLK_SOURCE_OFFS) + + +/*********************/ +/* Interrupts */ +/*******************/ +#define MV_AUDIO_ERROR_CAUSE_REG 0x1300 +#define MV_AUDIO_ERROR_MASK_REG 0x1304 +#define MV_AUDIO_INT_CAUSE_REG 0x1308 +#define MV_AUDIO_INT_MASK_REG 0x130C +#define MV_AUDIO_RECORD_BYTE_CNTR_INT_REG 0x1310 +#define MV_AUDIO_PLAYBACK_BYTE_CNTR_INT_REG 0x1314 + +/* MV_AUDIO_INT_CAUSE_REG*/ +#define AICR_RECORD_BYTES_INT (0x1 << 13) +#define AICR_PLAY_BYTES_INT (0x1 << 14) + +#define ARBCI_BYTE_COUNT_MASK 0xFFFFFF +#define APBCI_BYTE_COUNT_MASK 0xFFFFFF + +/*********************/ +/* Audio Playback */ +/*******************/ +/* General */ +#define MV_AUDIO_PLAYBACK_CTRL_REG 0x1100 +#define MV_AUDIO_PLAYBACK_BUFF_START_REG 0x1104 +#define MV_AUDIO_PLAYBACK_BUFF_SIZE_REG 0x1108 +#define MV_AUDIO_PLAYBACK_BUFF_BYTE_CNTR_REG 0x110c + +/* SPDIF */ +#define MV_AUDIO_SPDIF_PLAY_CTRL_REG 0x2204 +#define MV_AUDIO_SPDIF_PLAY_CH_STATUS_LEFT_REG(ind) \ + (0x2280 + (ind << 2)) +#define MV_AUDIO_SPDIF_PLAY_CH_STATUS_RIGHT_REG(ind) \ + (0x22a0 + (ind << 2)) +#define MV_AUDIO_SPDIF_PLAY_USR_BITS_LEFT_REG(ind) \ + (0x22c0 + (ind << 2)) +#define MV_AUDIO_SPDIF_PLAY_USR_BITS_RIGHT_REG(ind) \ + (0x22e0 + (ind << 2)) + +/* I2S */ +#define MV_AUDIO_I2S_PLAY_CTRL_REG 0x2508 + + +/* MV_AUDIO_PLAYBACK_CTRL_REG */ +#define APCR_PLAY_SAMPLE_SIZE_OFFS 0 +#define APCR_PLAY_SAMPLE_SIZE_MASK (0x7 << APCR_PLAY_SAMPLE_SIZE_OFFS) + +#define APCR_PLAY_I2S_ENABLE_OFFS 3 +#define APCR_PLAY_I2S_ENABLE_MASK (0x1 << APCR_PLAY_I2S_ENABLE_OFFS) + +#define APCR_PLAY_SPDIF_ENABLE_OFFS 4 +#define APCR_PLAY_SPDIF_ENABLE_MASK (0x1 << APCR_PLAY_SPDIF_ENABLE_OFFS) + +#define APCR_PLAY_MONO_OFFS 5 +#define APCR_PLAY_MONO_MASK (0x3 << APCR_PLAY_MONO_OFFS) + +#define APCR_PLAY_I2S_MUTE_OFFS 7 +#define APCR_PLAY_I2S_MUTE_MASK (0x1 << APCR_PLAY_I2S_MUTE_OFFS) + +#define APCR_PLAY_SPDIF_MUTE_OFFS 8 +#define APCR_PLAY_SPDIF_MUTE_MASK (0x1 << APCR_PLAY_SPDIF_MUTE_OFFS) + +#define APCR_PLAY_PAUSE_OFFS 9 +#define APCR_PLAY_PAUSE_MASK (0x1 << APCR_PLAY_PAUSE_OFFS) + +#define APCR_LOOPBACK_OFFS 10 +#define APCR_LOOPBACK_MASK (0x1 << APCR_LOOPBACK_OFFS) + +#define APCR_PLAY_BURST_SIZE_OFFS 11 +#define APCR_PLAY_BURST_SIZE_MASK (0x3 << APCR_PLAY_BURST_SIZE_OFFS) + +#define APCR_PLAY_BUSY_OFFS 16 +#define APCR_PLAY_BUSY_MASK (0x1 << APCR_PLAY_BUSY_OFFS) + +/* MV_AUDIO_PLAYBACK_BUFF_BYTE_CNTR_REG */ +#define APBBCR_SIZE_MAX 0x3FFFFF +#define APBBCR_SIZE_SHIFT 0x2 + + +/* MV_AUDIO_SPDIF_PLAY_CTRL_REG */ +#define ASPCR_SPDIF_BLOCK_START_OFFS 0x0 +#define ASPCR_SPDIF_BLOCK_START_MASK (0x1 << ASPCR_SPDIF_BLOCK_START_OFFS) + +#define ASPCR_SPDIF_PB_EN_MEM_VALIDITY_OFFS 0x1 +#define ASPCR_SPDIF_PB_EN_MEM_VALIDITY_MASK (0x1 << \ + ASPCR_SPDIF_PB_EN_MEM_VALIDITY_OFFS) + +#define ASPCR_SPDIF_PB_MEM_USR_EN_OFFS 0x2 +#define ASPCR_SPDIF_PB_MEM_USR_EN_MASK (0x1 << ASPCR_SPDIF_PB_MEM_USR_EN_OFFS) + +#define ASPCR_SPDIF_UNDERRUN_DATA_OFFS 0x5 +#define ASPCR_SPDIF_UNDERRUN_DATA_MASK (0x1 << ASPCR_SPDIF_UNDERRUN_DATA_OFFS) + +#define ASPCR_SPDIF_PB_REG_VALIDITY_OFFS 16 +#define ASPCR_SPDIF_PB_REG_VALIDITY_MASK (0x1 << \ + ASPCR_SPDIF_PB_REG_VALIDITY_OFFS) + +#define ASPCR_SPDIF_PB_NONPCM_OFFS 17 +#define ASPCR_SPDIF_PB_NONPCM_MASK (0x1 << ASPCR_SPDIF_PB_NONPCM_OFFS) + + +/* MV_AUDIO_I2S_PLAY_CTRL_REG */ +#define AIPCR_I2S_SEND_LAST_FRM_OFFS 23 +#define AIPCR_I2S_SEND_LAST_FRM_MASK (1 << AIPCR_I2S_SEND_LAST_FRM_OFFS) + +#define AIPCR_I2S_PB_JUSTF_OFFS 26 +#define AIPCR_I2S_PB_JUSTF_MASK (0xf << AIPCR_I2S_PB_JUSTF_OFFS) + +#define AIPCR_I2S_PB_SAMPLE_SIZE_OFFS 30 +#define AIPCR_I2S_PB_SAMPLE_SIZE_MASK (0x3 << AIPCR_I2S_PB_SAMPLE_SIZE_OFFS) + +/*********************/ +/* Audio Recordnig */ +/*******************/ +/* General */ +#define MV_AUDIO_RECORD_CTRL_REG 0x1000 +#define MV_AUDIO_RECORD_START_ADDR_REG 0x1004 +#define MV_AUDIO_RECORD_BUFF_SIZE_REG 0x1008 +#define MV_AUDIO_RECORD_BUF_BYTE_CNTR_REG 0x100C + +/* SPDIF */ +#define MV_AUDIO_SPDIF_REC_GEN_REG 0x2004 +#define MV_AUDIO_SPDIF_REC_INT_CAUSE_MASK_REG 0x2008 +#define MV_AUDIO_SPDIF_REC_CH_STATUS_LEFT_REG(ind) \ + (0x2180 + ((ind) << 2)) +#define MV_AUDIO_SPDIF_REC_CH_STATUS_RIGHT_REG(ind) \ + (0x21a0 + ((ind) << 2)) +#define MV_AUDIO_SPDIF_REC_USR_BITS_LEFT_REG(ind) \ + (0x21c0 + ((ind) << 2)) +#define MV_AUDIO_SPDIF_REC_USR_BITS_RIGHT_REG(ind) \ + (0x21e0 + ((ind) << 2)) + +/* I2S */ +#define MV_AUDIO_I2S_REC_CTRL_REG 0x2408 + + +/* MV_AUDIO_RECORD_CTRL_REG*/ +#define ARCR_RECORD_SAMPLE_SIZE_OFFS 0 +#define ARCR_RECORD_SAMPLE_SIZE_MASK (0x7 << ARCR_RECORD_SAMPLE_SIZE_OFFS) + +#define ARCR_RECORDED_MONO_CHNL_OFFS 3 +#define ARCR_RECORDED_MONO_CHNL_MASK (0x1 << ARCR_RECORDED_MONO_CHNL_OFFS) + +#define ARCR_RECORD_MONO_OFFS 4 +#define ARCR_RECORD_MONO_MASK (0x1 << ARCR_RECORD_MONO_OFFS) + +#define ARCR_RECORD_BURST_SIZE_OFFS 5 +#define ARCR_RECORD_BURST_SIZE_MASK (0x3 << ARCR_RECORD_BURST_SIZE_OFFS) + +#define ARCR_RECORD_MUTE_OFFS 8 +#define ARCR_RECORD_MUTE_MASK (0x1 << ARCR_RECORD_MUTE_OFFS) + +#define ARCR_RECORD_PAUSE_OFFS 9 +#define ARCR_RECORD_PAUSE_MASK (0x1 << ARCR_RECORD_PAUSE_OFFS) + +#define ARCR_RECORD_I2S_EN_OFFS 10 +#define ARCR_RECORD_I2S_EN_MASK (0x1 << ARCR_RECORD_I2S_EN_OFFS) + +#define ARCR_RECORD_SPDIF_EN_OFFS 11 +#define ARCR_RECORD_SPDIF_EN_MASK (0x1 << ARCR_RECORD_SPDIF_EN_OFFS) + + +/* MV_AUDIO_SPDIF_REC_GEN_REG*/ +#define ASRGR_CORE_CLK_FREQ_OFFS 1 +#define ASRGR_CORE_CLK_FREQ_MASK (0x3 << ASRGR_CORE_CLK_FREQ_OFFS) +#define ASRGR_CORE_CLK_FREQ_133MHZ (0x0 << ASRGR_CORE_CLK_FREQ_OFFS) +#define ASRGR_CORE_CLK_FREQ_150MHZ (0x1 << ASRGR_CORE_CLK_FREQ_OFFS) +#define ASRGR_CORE_CLK_FREQ_166MHZ (0x2 << ASRGR_CORE_CLK_FREQ_OFFS) +#define ASRGR_CORE_CLK_FREQ_200MHZ (0x3 << ASRGR_CORE_CLK_FREQ_OFFS) + +#define ASRGR_VALID_PCM_INFO_OFFS 7 +#define ASRGR_VALID_PCM_INFO_MASK (0x1 << ASRGR_VALID_PCM_INFO_OFFS) + +#define ASRGR_SAMPLE_FREQ_OFFS 8 +#define ASRGR_SAMPLE_FREQ_MASK (0xf << ASRGR_SAMPLE_FREQ_OFFS) + +#define ASRGR_NON_PCM_OFFS 14 +#define ASRGR_NON_PCM_MASK (1 << ASRGR_NON_PCM_OFFS) + +/* MV_AUDIO_I2S_REC_CTRL_REG*/ +#define AIRCR_I2S_RECORD_JUSTF_OFFS 26 +#define AIRCR_I2S_RECORD_JUSTF_MASK (0xf << AIRCR_I2S_RECORD_JUSTF_OFFS) + +#define AIRCR_I2S_SAMPLE_SIZE_OFFS 30 +#define AIRCR_I2S_SAMPLE_SIZE_MASK (0x3 << AIRCR_I2S_SAMPLE_SIZE_OFFS) + +#endif /* __KW_AUDIO_REGS_H */ + diff --git a/sound/soc/kirkwood/kirkwood_pcm.c b/sound/soc/kirkwood/kirkwood_pcm.c new file mode 100644 index 0000000..ed35851 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood_pcm.c @@ -0,0 +1,1505 @@ +/* + * + * Marvell Orion Alsa Sound driver + * + * Author: Maen Suleiman + * Copyright (C) 2008 Marvell Ltd. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "kirkwood_audio_hal.h" + +struct mv88fx_snd_chip *chip; + +static int test_memory(struct mbus_dram_target_info *dram_info, + unsigned int base, unsigned int size) +{ + unsigned int i; + + for (i = 0; i <= dram_info->num_cs; i++) { + + /* check if we get to end */ + if ((dram_info->cs[i].base == 0) && + (dram_info->cs[i].size == 0)) + break; + + /* check if we fit into one memory window only */ + if ((base >= dram_info->cs[i].base) && + ((base + size) <= dram_info->cs[i].base + + dram_info->cs[i].size)) + return 1; + } + + return 0; +} + +static void devdma_hw_free(struct device *dev, struct snd_pcm_substream + *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + + if (runtime->dma_area == NULL) + return; + + if (buf != &substream->dma_buffer) + kfree(runtime->dma_buffer_p); + + snd_pcm_set_runtime_buffer(substream, NULL); +} + +static int devdma_hw_alloc(struct device *dev, struct snd_pcm_substream + *substream, size_t size) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + + int ret = 0; + + if (buf) { + if (buf->bytes >= size) { + snd_printd("buf->bytes >= size\n"); + goto out; + } + devdma_hw_free(dev, substream); + } + + if (substream->dma_buffer.area != NULL && + substream->dma_buffer.bytes >= size) { + buf = &substream->dma_buffer; + } else { + buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL); + if (!buf) { + snd_printk("buf == NULL\n"); + goto nomem; + } + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = dev; + buf->area = audio_stream->area; + buf->addr = audio_stream->addr; + buf->bytes = size; + buf->private_data = NULL; + + if (!buf->area) { + snd_printk("buf->area == NULL\n"); + goto free; + } + } + + snd_pcm_set_runtime_buffer(substream, buf); + ret = 1; +out: + runtime->dma_bytes = size; + return ret; + +free: + kfree(buf); +nomem: + return -ENOMEM; +} + +static int devdma_mmap(struct device *dev, struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_coherent(dev, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +/* + * hw preparation for spdif + */ + +static int mv88fx_snd_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int mv88fx_snd_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static struct snd_kcontrol_new mv88fx_snd_spdif_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .info = mv88fx_snd_spdif_mask_info, + .get = mv88fx_snd_spdif_mask_get, +}; + +static int mv88fx_snd_spdif_stream_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int mv88fx_snd_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct mv88fx_snd_chip *chip = snd_kcontrol_chip(kcontrol); + int i, word; + + spin_lock_irq(&chip->reg_lock); + + for (word = 0; word < 4; word++) { + chip->stream[PLAYBACK]->spdif_status[word] = + readl(chip->base + + MV_AUDIO_SPDIF_PLAY_CH_STATUS_LEFT_REG(word)); + + for (i = 0; i < 4; i++) + ucontrol->value.iec958.status[word + i] = + (chip->stream[PLAYBACK]->spdif_status[word] >> + (i * 8)) & 0xff; + } + + spin_unlock_irq(&chip->reg_lock); + return 0; +} + +static int mv88fx_snd_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct mv88fx_snd_chip *chip = snd_kcontrol_chip(kcontrol); + int i, change = 0, word; + + spin_lock_irq(&chip->reg_lock); + + for (word = 0; word < 4; word++) { + for (i = 0; i < 4; i++) { + chip->stream[PLAYBACK]->spdif_status[word] |= + ucontrol->value.iec958.status[word + i] << (i * 8); + } + + writel(chip->stream[PLAYBACK]->spdif_status[word], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_LEFT_REG(word)); + + writel(chip->stream[PLAYBACK]->spdif_status[word], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_RIGHT_REG(word)); + } + + if (chip->stream[PLAYBACK]->spdif_status[0] & IEC958_AES0_NONAUDIO) + chip->pcm_mode = NON_PCM; + + spin_unlock_irq(&chip->reg_lock); + + return change; +} + +static struct snd_kcontrol_new mv88fx_snd_spdif_stream = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + .info = mv88fx_snd_spdif_stream_info, + .get = mv88fx_snd_spdif_stream_get, + .put = mv88fx_snd_spdif_stream_put +}; + + +static int mv88fx_snd_spdif_default_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int mv88fx_snd_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct mv88fx_snd_chip *chip = snd_kcontrol_chip(kcontrol); + int i, word; + + spin_lock_irq(&chip->reg_lock); + + for (word = 0; word < 4; word++) { + chip->stream_defaults[PLAYBACK]->spdif_status[word] = + readl(chip->base + + MV_AUDIO_SPDIF_PLAY_CH_STATUS_LEFT_REG(word)); + + for (i = 0; i < 4; i++) + ucontrol->value.iec958.status[word + i] = + (chip->stream_defaults[PLAYBACK]->spdif_status[word] >> + (i * 8)) & 0xff; + } + + spin_unlock_irq(&chip->reg_lock); + + return 0; +} + +static int mv88fx_snd_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct mv88fx_snd_chip *chip = snd_kcontrol_chip(kcontrol); + int i, change = 0, word; + + spin_lock_irq(&chip->reg_lock); + + for (word = 0; word < 4; word++) { + for (i = 0; i < 4; i++) { + chip->stream_defaults[PLAYBACK]->spdif_status[word] |= + ucontrol->value.iec958.status[word + i] << (i * 8); + } + + writel(chip->stream_defaults[PLAYBACK]->spdif_status[word], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_LEFT_REG(word)); + + writel(chip->stream_defaults[PLAYBACK]->spdif_status[word], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_RIGHT_REG(word)); + } + + if (chip->stream_defaults[PLAYBACK]->spdif_status[0] & + IEC958_AES0_NONAUDIO) + chip->pcm_mode = NON_PCM; + + spin_unlock_irq(&chip->reg_lock); + + return change; +} + +/* static struct snd_kcontrol_new mv88fx_snd_spdif_default __devinitdata = */ +static struct snd_kcontrol_new mv88fx_snd_spdif_default = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = mv88fx_snd_spdif_default_info, + .get = mv88fx_snd_spdif_default_get, + .put = mv88fx_snd_spdif_default_put +}; + +unsigned char mv88fx_snd_vol[2]; + +static int mv88fx_snd_mixer_vol_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 = 39; + return 0; +} + +static int mv88fx_snd_mixer_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + codec_vol_get(mv88fx_snd_vol); + + ucontrol->value.integer.value[0] = (long)mv88fx_snd_vol[0]; + ucontrol->value.integer.value[1] = (long)mv88fx_snd_vol[1]; + + return 0; +} + +static int mv88fx_snd_mixer_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mv88fx_snd_vol[0] = (unsigned char)ucontrol->value.integer.value[0]; + mv88fx_snd_vol[1] = (unsigned char)ucontrol->value.integer.value[1]; + + codec_vol_set(mv88fx_snd_vol); + + return 0; +} + +static struct snd_kcontrol_new mv88fx_snd_dac_vol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Playback DAC Volume", + .info = mv88fx_snd_mixer_vol_info, + .get = mv88fx_snd_mixer_vol_get, + .put = mv88fx_snd_mixer_vol_put +}; + +struct mv88fx_snd_mixer_enum { + char **names; /* enum names*/ + int *values; /* values to be updated*/ + int count; /* number of elements */ + void *rec; /* field to be updated*/ +}; + +int mv88fx_snd_mixer_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct mv88fx_snd_mixer_enum *mixer_enum = + (struct mv88fx_snd_mixer_enum *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = mixer_enum->count; + + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item--; + + strcpy(uinfo->value.enumerated.name, + mixer_enum->names[uinfo->value.enumerated.item]); + + return 0; +} + +int mv88fx_snd_mixer_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct mv88fx_snd_mixer_enum *mixer_enum = + (struct mv88fx_snd_mixer_enum *)kcontrol->private_value; + int i; + unsigned int val; + + val = *(unsigned int *)mixer_enum->rec; + + for (i = 0; i < mixer_enum->count; i++) { + + if (val == (unsigned int)mixer_enum->values[i]) { + ucontrol->value.enumerated.item[0] = i; + break; + } + } + + return 0; +} + +int mv88fx_snd_mixer_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val, *rec; + struct mv88fx_snd_mixer_enum *mixer_enum = + (struct mv88fx_snd_mixer_enum *)kcontrol->private_value; + int i; + + rec = (unsigned int *)mixer_enum->rec; + val = ucontrol->value.enumerated.item[0]; + + if (val < 0) + val = 0; + if (val > mixer_enum->count) + val = mixer_enum->count; + + for (i = 0; i < mixer_enum->count; i++) { + + if (val == i) { + *rec = (unsigned int)mixer_enum->values[i]; + break; + } + } + + return 0; +} + +#define MV88FX_PCM_MIXER_ENUM(xname, xindex, value) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = mv88fx_snd_mixer_enum_info, \ + .get = mv88fx_snd_mixer_enum_get, \ + .put = mv88fx_snd_mixer_enum_put, \ + .private_value = (unsigned long)value, \ +} + +char *playback_src_mixer_names[] = {"SPDIF", "I2S", "SPDIF And I2S"}; +int playback_src_mixer_values[] = { SPDIF, I2S, (SPDIF | I2S)}; + +struct mv88fx_snd_mixer_enum playback_src_mixer = { + .names = playback_src_mixer_names, + .values = playback_src_mixer_values, + .count = 3, +}; + +char *playback_mono_mixer_names[] = {"Mono Both", "Mono Left", "Mono Right"}; +int playback_mono_mixer_values[] = { MONO_BOTH, MONO_LEFT, MONO_RIGHT}; + +struct mv88fx_snd_mixer_enum playback_mono_mixer = { + .names = playback_mono_mixer_names, + .values = playback_mono_mixer_values, + .count = 3, +}; + +char *capture_src_mixer_names[] = {"SPDIF", "I2S"}; +int capture_src_mixer_values[] = { SPDIF, I2S}; + +struct mv88fx_snd_mixer_enum capture_src_mixer = { + .names = capture_src_mixer_names, + .values = capture_src_mixer_values, + .count = 2, +}; + +char *capture_mono_mixer_names[] = {"Mono Left", "Mono Right"}; +int capture_mono_mixer_values[] = { MONO_LEFT, MONO_RIGHT}; + +struct mv88fx_snd_mixer_enum capture_mono_mixer = { + .names = capture_mono_mixer_names, + .values = capture_mono_mixer_values, + .count = 2, +}; + +static struct snd_kcontrol_new mv88fx_snd_mixers[] = { + MV88FX_PCM_MIXER_ENUM("Playback output type", 0, &playback_src_mixer), + + MV88FX_PCM_MIXER_ENUM("Playback mono type", 0, &playback_mono_mixer), + + MV88FX_PCM_MIXER_ENUM("Capture input Type", 0, &capture_src_mixer), + + MV88FX_PCM_MIXER_ENUM("Capture mono type", 0, &capture_mono_mixer), +}; + +#define PLAYBACK_MIX_INDX 0 +#define PLAYBACK_MONO_MIX_INDX 1 +#define CAPTURE_MIX_INDX 2 +#define CAPTURE_MONO_MIX_INDX 3 + +static int mv88fx_snd_ctrl_new(struct snd_card *card) +{ + struct mv88fx_snd_platform_data *pdata = card->dev->platform_data; + int err = 0; + + playback_src_mixer.rec = &chip->stream_defaults[PLAYBACK]->dig_mode; + playback_mono_mixer.rec = &chip->stream_defaults[PLAYBACK]->mono_mode; + + capture_src_mixer.rec = &chip->stream_defaults[CAPTURE]->dig_mode; + capture_mono_mixer.rec = &chip->stream_defaults[CAPTURE]->mono_mode; + + if ((pdata->i2s_play) && (pdata->spdif_play)) { + err = snd_ctl_add(card, + snd_ctl_new1(&mv88fx_snd_mixers[PLAYBACK_MIX_INDX], + chip)); + if (err < 0) + return err; + } + + err = snd_ctl_add(card, + snd_ctl_new1(&mv88fx_snd_mixers[PLAYBACK_MONO_MIX_INDX], chip)); + if (err < 0) + return err; + + if ((pdata->i2s_rec) && (pdata->spdif_rec)) { + err = snd_ctl_add(card, + snd_ctl_new1(&mv88fx_snd_mixers[CAPTURE_MIX_INDX], chip)); + if (err < 0) + return err; + } + + err = snd_ctl_add(card, snd_ctl_new1( + &mv88fx_snd_mixers[CAPTURE_MONO_MIX_INDX], chip)); + if (err < 0) + return err; + + if (pdata->i2s_play) { + err = snd_ctl_add(card, snd_ctl_new1(&mv88fx_snd_dac_vol, + chip)); + if (err < 0) + return err; + } + + err = snd_ctl_add(card, snd_ctl_new1(&mv88fx_snd_spdif_mask, + chip)); + if (err < 0) + return err; + + err = snd_ctl_add(card, snd_ctl_new1(&mv88fx_snd_spdif_default, + chip)); + if (err < 0) + return err; + + err = snd_ctl_add(card, snd_ctl_new1(&mv88fx_snd_spdif_stream, + chip)); + return err; +} + +static struct snd_pcm_hardware mv88fx_snd_capture_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE), + + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000), + + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (16*1024*1024), + .period_bytes_min = MV88FX_SND_MIN_PERIOD_BYTES, + .period_bytes_max = MV88FX_SND_MAX_PERIOD_BYTES, + .periods_min = MV88FX_SND_MIN_PERIODS, + .periods_max = MV88FX_SND_MAX_PERIODS, + .fifo_size = 0, +}; + +static int mv88fx_snd_capture_open(struct snd_pcm_substream *substream) +{ + int err; + + chip->stream_defaults[CAPTURE]->substream = substream; + chip->stream_defaults[CAPTURE]->direction = CAPTURE; + substream->private_data = chip->stream_defaults[CAPTURE]; + substream->runtime->hw = mv88fx_snd_capture_hw; + + if (chip->stream_defaults[CAPTURE]->dig_mode & SPDIF) + substream->runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_S32_LE; + + /* check if playback is already running with specific rate */ + if (chip->stream[PLAYBACK]->rate) { + switch (chip->stream[PLAYBACK]->rate) { + case 44100: + substream->runtime->hw.rates = SNDRV_PCM_RATE_44100; + break; + case 48000: + substream->runtime->hw.rates = SNDRV_PCM_RATE_48000; + break; + case 96000: + substream->runtime->hw.rates = SNDRV_PCM_RATE_96000; + break; + } + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + chip->burst * 2, + AUDIO_REG_TO_SIZE(APBBCR_SIZE_MAX)); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + chip->burst); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + chip->burst); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS, + MV88FX_SND_MIN_PERIODS, + MV88FX_SND_MAX_PERIODS); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + +static int mv88fx_snd_capture_close(struct snd_pcm_substream *substream) +{ + chip->stream_defaults[CAPTURE]->substream = NULL; + memset(chip->stream[CAPTURE], 0, sizeof(struct mv88fx_snd_stream)); + + return 0; +} + +static int mv88fx_snd_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + + return devdma_hw_alloc(audio_stream->dev, substream, + params_buffer_bytes(params)); +} + +static int mv88fx_snd_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + + /* + * Clear out the DMA and any allocated buffers. + */ + devdma_hw_free(audio_stream->dev, substream); + return 0; +} + +static int mv88fx_snd_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + + audio_stream->rate = runtime->rate; + audio_stream->stereo = (runtime->channels == 1) ? 0 : 1; + + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) { + audio_stream->format = SAMPLE_16IN16; + } else if (runtime->format == SNDRV_PCM_FORMAT_S24_LE) { + audio_stream->format = SAMPLE_24IN32; + } else if (runtime->format == SNDRV_PCM_FORMAT_S32_LE) { + audio_stream->format = SAMPLE_32IN32; + } else { + snd_printk("Requested format %d is not supported\n", + runtime->format); + return -1; + } + + /* buffer and period sizes in frame */ + audio_stream->dma_addr = runtime->dma_addr; + audio_stream->dma_size = frames_to_bytes(runtime, runtime->buffer_size); + audio_stream->period_size = + frames_to_bytes(runtime, runtime->period_size); + + memcpy(chip->stream[CAPTURE], chip->stream_defaults[CAPTURE], + sizeof(struct mv88fx_snd_stream)); + + return mv88fx_snd_hw_capture_set(chip); +} + +static int mv88fx_snd_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + int result = 0; + unsigned int reg_data; + + spin_lock(chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* FIXME: should check if busy before */ + + /* make sure the dma in pause state*/ + reg_data = readl(chip->base + MV_AUDIO_RECORD_CTRL_REG); + reg_data |= ARCR_RECORD_PAUSE_MASK; + writel(reg_data, chip->base + MV_AUDIO_RECORD_CTRL_REG); + + /* enable interrupt */ + reg_data = readl(chip->base + MV_AUDIO_INT_MASK_REG); + reg_data |= AICR_RECORD_BYTES_INT; + writel(reg_data, chip->base + MV_AUDIO_INT_MASK_REG); + + reg_data = readl(chip->base + MV_AUDIO_RECORD_CTRL_REG); + + /* enable dma */ + if (audio_stream->dig_mode & I2S) + reg_data |= ARCR_RECORD_I2S_EN_MASK; + + if (audio_stream->dig_mode & SPDIF) + reg_data |= ARCR_RECORD_SPDIF_EN_MASK; + + /* start dma */ + reg_data &= (~ARCR_RECORD_PAUSE_MASK); + writel(reg_data, chip->base + MV_AUDIO_RECORD_CTRL_REG); + break; + + case SNDRV_PCM_TRIGGER_STOP: + + /* make sure the dma in pause state */ + reg_data = readl(chip->base + MV_AUDIO_RECORD_CTRL_REG); + reg_data |= ARCR_RECORD_PAUSE_MASK; + writel(reg_data, chip->base + MV_AUDIO_RECORD_CTRL_REG); + + /* disable interrupt */ + reg_data = readl(chip->base + MV_AUDIO_INT_MASK_REG); + reg_data &= (~AICR_RECORD_BYTES_INT); + writel(reg_data, chip->base + MV_AUDIO_INT_MASK_REG); + + /* always stop both I2S and SPDIF */ + reg_data = readl(chip->base + MV_AUDIO_RECORD_CTRL_REG); + reg_data &= (~(ARCR_RECORD_I2S_EN_MASK | + ARCR_RECORD_SPDIF_EN_MASK)); + writel(reg_data, chip->base + MV_AUDIO_RECORD_CTRL_REG); + + /* FIXME: should check if busy after */ + + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + reg_data = readl(chip->base + MV_AUDIO_RECORD_CTRL_REG); + reg_data |= ARCR_RECORD_PAUSE_MASK; + writel(reg_data, chip->base + MV_AUDIO_RECORD_CTRL_REG); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg_data = readl(chip->base + MV_AUDIO_RECORD_CTRL_REG); + reg_data &= (~ARCR_RECORD_PAUSE_MASK); + writel(reg_data, chip->base + MV_AUDIO_RECORD_CTRL_REG); + break; + + default: + result = -EINVAL; + break; + } + + spin_unlock(&chip->reg_lock); + return result; +} + +static snd_pcm_uframes_t mv88fx_snd_capture_pointer(struct snd_pcm_substream + *substream) +{ + return bytes_to_frames(substream->runtime, + (ssize_t)readl(chip->base + MV_AUDIO_RECORD_BUF_BYTE_CNTR_REG)); +} + +int mv88fx_snd_capture_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return devdma_mmap(NULL, substream, vma); +} + +static struct snd_pcm_ops mv88fx_snd_capture_ops = { + .open = mv88fx_snd_capture_open, + .close = mv88fx_snd_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = mv88fx_snd_capture_hw_params, + .hw_free = mv88fx_snd_capture_hw_free, + .prepare = mv88fx_snd_capture_prepare, + .trigger = mv88fx_snd_capture_trigger, + .pointer = mv88fx_snd_capture_pointer, + .mmap = mv88fx_snd_capture_mmap, +}; + +struct snd_pcm_hardware mv88fx_snd_playback_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000), + + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (16*1024*1024), + .period_bytes_min = MV88FX_SND_MIN_PERIOD_BYTES, + .period_bytes_max = MV88FX_SND_MAX_PERIOD_BYTES, + .periods_min = MV88FX_SND_MIN_PERIODS, + .periods_max = MV88FX_SND_MAX_PERIODS, + .fifo_size = 0, +}; + +int mv88fx_snd_playback_open(struct snd_pcm_substream *substream) +{ + int err = 0; + + chip->stream_defaults[PLAYBACK]->substream = substream; + chip->stream_defaults[PLAYBACK]->direction = PLAYBACK; + substream->private_data = chip->stream_defaults[PLAYBACK]; + substream->runtime->hw = mv88fx_snd_playback_hw; + + if (chip->stream_defaults[PLAYBACK]->dig_mode & SPDIF) + substream->runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_S32_LE; + + /* check if capture is already running with specific rate */ + if (chip->stream[CAPTURE]->rate) { + switch (chip->stream[CAPTURE]->rate) { + case 44100: + substream->runtime->hw.rates = SNDRV_PCM_RATE_44100; + break; + case 48000: + substream->runtime->hw.rates = SNDRV_PCM_RATE_48000; + break; + case 96000: + substream->runtime->hw.rates = SNDRV_PCM_RATE_96000; + break; + + } + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + chip->burst * 2, + AUDIO_REG_TO_SIZE(APBBCR_SIZE_MAX)); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + chip->burst); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + chip->burst); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS, + MV88FX_SND_MIN_PERIODS, + MV88FX_SND_MAX_PERIODS); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + + if (err < 0) + return err; + + return 0; +} + +int mv88fx_snd_playback_close(struct snd_pcm_substream *substream) +{ + int i; + + chip->stream_defaults[PLAYBACK]->substream = NULL; + chip->pcm_mode = PCM; + + for (i = 0; i < 4; i++) { + chip->stream_defaults[PLAYBACK]->spdif_status[i] = 0; + chip->stream[PLAYBACK]->spdif_status[i] = 0; + + writel(chip->stream_defaults[PLAYBACK]->spdif_status[i], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_LEFT_REG(i)); + writel(chip->stream_defaults[PLAYBACK]->spdif_status[i], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_RIGHT_REG(i)); + } + + memset(chip->stream[PLAYBACK], 0, sizeof(struct mv88fx_snd_stream)); + + return 0; +} + +int mv88fx_snd_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + + return devdma_hw_alloc(audio_stream->dev, substream, + params_buffer_bytes(params)); +} + +int mv88fx_snd_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + + /* + * Clear out the DMA and any allocated buffers. + */ + devdma_hw_free(audio_stream->dev, substream); + return 0; +} + +int mv88fx_snd_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + + if ((audio_stream->dig_mode == I2S) && + (chip->pcm_mode == NON_PCM)) + return -1; + + audio_stream->rate = runtime->rate; + audio_stream->stereo = (runtime->channels == 1) ? 0 : 1; + + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) { + audio_stream->format = SAMPLE_16IN16; + } else if (runtime->format == SNDRV_PCM_FORMAT_S24_LE) { + audio_stream->format = SAMPLE_24IN32; + } else if (runtime->format == SNDRV_PCM_FORMAT_S32_LE) { + audio_stream->format = SAMPLE_32IN32; + } else { + snd_printk("Requested format %d is not supported\n", + runtime->format); + return -1; + } + + /* buffer and period sizes in frame */ + audio_stream->dma_addr = runtime->dma_addr; + audio_stream->dma_size = frames_to_bytes(runtime, runtime->buffer_size); + audio_stream->period_size = + frames_to_bytes(runtime, runtime->period_size); + + memcpy(chip->stream[PLAYBACK], chip->stream_defaults[PLAYBACK], + sizeof(struct mv88fx_snd_stream)); + + return mv88fx_snd_hw_playback_set(chip); +} + +int mv88fx_snd_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct mv88fx_snd_stream *audio_stream = + snd_pcm_substream_chip(substream); + int result = 0; + unsigned int reg_data; + + spin_lock(chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* enable interrupt */ + reg_data = readl(chip->base + MV_AUDIO_INT_MASK_REG); + reg_data |= AICR_PLAY_BYTES_INT; + writel(reg_data, chip->base + MV_AUDIO_INT_MASK_REG); + + /* make sure the dma in pause state*/ + reg_data = readl(chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + reg_data |= APCR_PLAY_PAUSE_MASK; + writel(reg_data, chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + + /* enable dma */ + if ((audio_stream->dig_mode & I2S) && + (chip->pcm_mode == PCM)) + reg_data |= APCR_PLAY_I2S_ENABLE_MASK; + + if (audio_stream->dig_mode & SPDIF) + reg_data |= APCR_PLAY_SPDIF_ENABLE_MASK; + + /* start dma */ + reg_data &= (~APCR_PLAY_PAUSE_MASK); + writel(reg_data, chip->base + MV_AUDIO_PLAYBACK_CTRL_REG); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + + /* disable interrupt */ + reg_data = readl(chip->base + MV_AUDIO_INT_MASK_REG); + reg_data &= (~AICR_PLAY_BYTES_INT); + writel(reg_data, chip->base + MV_AUDIO_INT_MASK_REG); + + /* make sure the dma in pause state*/ + reg_data = readl(chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + reg_data |= APCR_PLAY_PAUSE_MASK; + + /* always stop both I2S and SPDIF*/ + reg_data &= (~(APCR_PLAY_I2S_ENABLE_MASK | + APCR_PLAY_SPDIF_ENABLE_MASK)); + writel(reg_data, chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + + /* check if busy twice*/ + while (readl(chip->base + MV_AUDIO_PLAYBACK_CTRL_REG) & + APCR_PLAY_BUSY_MASK) + cpu_relax(); + while (readl(chip->base + MV_AUDIO_PLAYBACK_CTRL_REG) & + APCR_PLAY_BUSY_MASK) + cpu_relax(); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + reg_data = readl(chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + reg_data |= APCR_PLAY_PAUSE_MASK; + writel(reg_data, chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg_data = readl(chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + reg_data &= (~APCR_PLAY_PAUSE_MASK); + writel(reg_data, chip->base + + MV_AUDIO_PLAYBACK_CTRL_REG); + + break; + + default: + result = -EINVAL; + } + + spin_unlock(&chip->reg_lock); + return result; +} + + +snd_pcm_uframes_t mv88fx_snd_playback_pointer(struct snd_pcm_substream + *substream) +{ + return bytes_to_frames(substream->runtime, + (ssize_t)readl(chip->base + MV_AUDIO_PLAYBACK_BUFF_BYTE_CNTR_REG)); +} + +int mv88fx_snd_playback_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return devdma_mmap(NULL, substream, vma); +} + + +struct snd_pcm_ops mv88fx_snd_playback_ops = { + .open = mv88fx_snd_playback_open, + .close = mv88fx_snd_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = mv88fx_snd_playback_hw_params, + .hw_free = mv88fx_snd_playback_hw_free, + .prepare = mv88fx_snd_playback_prepare, + .trigger = mv88fx_snd_playback_trigger, + .pointer = mv88fx_snd_playback_pointer, + .mmap = mv88fx_snd_playback_mmap, +}; + +int __init mv88fx_snd_pcm_new(struct snd_card *card) +{ + struct snd_pcm *pcm; + struct mv88fx_snd_platform_data *pdata = card->dev->platform_data; + int err, i; + + snd_printd("card->dev=0x%x\n", (unsigned int)card->dev); + + err = snd_pcm_new(card, "Marvell mv88fx_snd IEC958 and I2S", 0, 1, 1, + &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &mv88fx_snd_playback_ops); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &mv88fx_snd_capture_ops); + + if ((pdata->i2s_play) && (pdata->spdif_play)) + chip->stream_defaults[PLAYBACK]->dig_mode = (SPDIF | I2S); + else if (pdata->i2s_play) + chip->stream_defaults[PLAYBACK]->dig_mode = I2S; + else if (pdata->spdif_play) + chip->stream_defaults[PLAYBACK]->dig_mode = SPDIF; + else + chip->stream_defaults[PLAYBACK]->dig_mode = 0; + + chip->stream_defaults[PLAYBACK]->mono_mode = MONO_BOTH; + chip->pcm_mode = PCM; + chip->stream_defaults[PLAYBACK]->stat_mem = 0; + chip->stream_defaults[PLAYBACK]->clock_src = DCO_CLOCK; + + if (pdata->i2s_rec) + chip->stream_defaults[CAPTURE]->dig_mode = I2S; + else if (pdata->spdif_rec) + chip->stream_defaults[CAPTURE]->dig_mode = SPDIF; + else + chip->stream_defaults[CAPTURE]->dig_mode = 0; + + chip->stream_defaults[CAPTURE]->mono_mode = MONO_LEFT; + chip->pcm_mode = PCM; + chip->stream_defaults[CAPTURE]->stat_mem = 0; + chip->stream_defaults[CAPTURE]->clock_src = DCO_CLOCK; + + for (i = 0; i < 4; i++) { + chip->stream_defaults[PLAYBACK]->spdif_status[i] = 0; + chip->stream[PLAYBACK]->spdif_status[i] = 0; + + writel(chip->stream_defaults[PLAYBACK]->spdif_status[i], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_LEFT_REG(i)); + + writel(chip->stream_defaults[PLAYBACK]->spdif_status[i], + chip->base + MV_AUDIO_SPDIF_PLAY_CH_STATUS_RIGHT_REG(i)); + + writel(0, chip->base + + MV_AUDIO_SPDIF_PLAY_USR_BITS_LEFT_REG(i)); + + writel(0, chip->base + + MV_AUDIO_SPDIF_PLAY_USR_BITS_RIGHT_REG(i)); + } + + pcm->private_data = chip; + pcm->info_flags = 0; + strcpy(pcm->name, "Marvell mv88fx_snd IEC958 and I2S"); + + return 0; +} + +irqreturn_t mv88fx_snd_interrupt(int irq, void *dev_id) +{ + struct mv88fx_snd_chip *chip = dev_id; + struct mv88fx_snd_stream *play_stream = chip->stream_defaults[PLAYBACK]; + struct mv88fx_snd_stream *capture_stream = + chip->stream_defaults[CAPTURE]; + + unsigned int status, mask; + + spin_lock(&chip->reg_lock); + + /* read the active interrupt */ + mask = readl(chip->base + MV_AUDIO_INT_MASK_REG); + status = readl(chip->base + MV_AUDIO_INT_CAUSE_REG) & mask; + + do { + if (status & ~(AICR_RECORD_BYTES_INT|AICR_PLAY_BYTES_INT)) { + spin_unlock(&chip->reg_lock); + snd_BUG(); /* FIXME: should enable error interrupts*/ + return IRQ_NONE; + } + + /* acknowledge interrupt */ + writel(status, chip->base + MV_AUDIO_INT_CAUSE_REG); + + /* This is record event */ + if (status & AICR_RECORD_BYTES_INT) + snd_pcm_period_elapsed(capture_stream->substream); + + /* This is play event */ + if (status & AICR_PLAY_BYTES_INT) + snd_pcm_period_elapsed(play_stream->substream); + + /* read the active interrupt */ + mask = readl(chip->base + MV_AUDIO_INT_MASK_REG); + status = readl(chip->base + MV_AUDIO_INT_CAUSE_REG) & mask; + } while (status); + + spin_unlock(&chip->reg_lock); + + return IRQ_HANDLED; +} + +void mv88fx_snd_free(struct snd_card *card) +{ + struct mv88fx_snd_chip *chip = card->private_data; + + /* free irq */ + free_irq(chip->irq, (void *)chip); + + snd_printd("chip->res =0x%x\n", (unsigned int)chip->res); + + if (chip->base) + iounmap(chip->base); + + if (chip->res) + release_resource(chip->res); + + chip->res = NULL; + + /* Free memory allocated for streems */ + if (chip->stream_defaults[PLAYBACK]->area) + dma_free_coherent(card->dev, + MV88FX_SND_MAX_PERIODS * MV88FX_SND_MAX_PERIOD_BYTES, + chip->stream_defaults[PLAYBACK]->area, + chip->stream_defaults[PLAYBACK]->addr); + + if (chip->stream_defaults[CAPTURE]->area) + dma_free_coherent(card->dev, + MV88FX_SND_MAX_PERIODS * MV88FX_SND_MAX_PERIOD_BYTES, + chip->stream_defaults[CAPTURE]->area, + chip->stream_defaults[CAPTURE]->addr); + + kfree(chip->stream_defaults[PLAYBACK]); + chip->stream_defaults[PLAYBACK] = NULL; + + kfree(chip->stream[PLAYBACK]); + chip->stream[PLAYBACK] = NULL; + + kfree(chip->stream_defaults[CAPTURE]); + chip->stream_defaults[CAPTURE] = NULL; + + kfree(chip->stream[CAPTURE]); + chip->stream[CAPTURE] = NULL; + + chip = NULL; +} + +int mv88fx_snd_probe(struct platform_device *dev) +{ + int err = 0, irq = NO_IRQ; + struct snd_card *card = NULL; + struct resource *r = NULL; + static struct snd_device_ops ops = { + .dev_free = NULL, + }; + struct mv88fx_snd_platform_data *pdata = NULL; + + err = snd_card_create(-1, "mv88fx_snd", THIS_MODULE, + sizeof(struct mv88fx_snd_chip), &card); + + if (err) { + snd_printk("snd_card_create failed\n"); + return err; + } + + card->dev = &dev->dev; + chip = card->private_data; + card->private_free = mv88fx_snd_free; + + pdata = (struct mv88fx_snd_platform_data *)dev->dev.platform_data; + + if (pdata->i2s_rec == 2 || pdata->spdif_rec == 2) + chip->stereo = 1; + else + chip->stereo = 0; + + chip->audio_offset = pdata->base_offset; + + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!r) { + snd_printk("platform_get_resource failed\n"); + err = -ENXIO; + goto error; + } + + snd_printd("chip->res =0x%x\n", (unsigned int)chip->res); + + r = request_mem_region(r->start, SZ_16K, MV88FX_AUDIO_NAME); + if (!r) { + snd_printk("request_mem_region failed\n"); + err = -EBUSY; + goto error; + } + chip->res = r; + + chip->base = ioremap(r->start, SZ_16K); + + if (!chip->base) { + snd_printk("ioremap failed\n"); + err = -ENOMEM; + goto error; + } + + snd_printd("chip->base=0x%x r->start0x%x\n", + (unsigned int)chip->base, r->start); + + irq = platform_get_irq(dev, 0); + if (irq == NO_IRQ) { + snd_printk("platform_get_irq failed\n"); + err = -ENXIO; + goto error; + } + + snd_printd("card = 0x%x dev 0x%x\n", + (unsigned int)card, (unsigned int)dev); + strncpy(card->driver, dev->dev.driver->name, sizeof(card->driver)); + + /* Allocate memory for our device */ + chip->stream_defaults[PLAYBACK] = + kzalloc(sizeof(struct mv88fx_snd_stream), GFP_KERNEL); + + if (chip->stream_defaults[PLAYBACK] == NULL) { + snd_printk("kzalloc failed for default playback\n"); + err = -ENOMEM; + goto error; + } + + chip->stream_defaults[PLAYBACK]->direction = PLAYBACK; + chip->stream_defaults[PLAYBACK]->dev = card->dev; + + chip->stream[PLAYBACK] = kzalloc(sizeof(struct mv88fx_snd_stream), + GFP_KERNEL); + + if (chip->stream[PLAYBACK] == NULL) { + snd_printk("kzalloc failed for runtime playback\n"); + err = -ENOMEM; + goto error; + } + + chip->stream_defaults[CAPTURE] = + kzalloc(sizeof(struct mv88fx_snd_stream), GFP_KERNEL); + + if (chip->stream_defaults[CAPTURE] == NULL) { + snd_printk("kzalloc failed for capture\n"); + err = -ENOMEM; + goto error; + } + + chip->stream_defaults[CAPTURE]->direction = CAPTURE; + chip->stream_defaults[CAPTURE]->dev = card->dev; + + chip->stream[CAPTURE] = kzalloc(sizeof(struct mv88fx_snd_stream), + GFP_KERNEL); + + if (chip->stream[CAPTURE] == NULL) { + snd_printk("kzalloc failed for runtime capture\n"); + err = -ENOMEM; + goto error; + } + + chip->irq = irq; + chip->stream_defaults[PLAYBACK]->area = + dma_alloc_coherent(&dev->dev, + MV88FX_SND_MAX_PERIODS * MV88FX_SND_MAX_PERIOD_BYTES, + &chip->stream_defaults[PLAYBACK]->addr, + GFP_KERNEL); + + if (!chip->stream_defaults[PLAYBACK]->area) { + snd_printk("dma_alloc_coherent failed for playback buffer\n"); + err = -ENOMEM; + goto error; + } + + if (0 == test_memory(pdata->dram, + (unsigned int)chip->stream_defaults[PLAYBACK]->addr, + (unsigned int)MV88FX_SND_MAX_PERIODS * MV88FX_SND_MAX_PERIOD_BYTES)) { + + snd_printk("error: playback buffer not in one memory window\n"); + err = -ENOMEM; + goto error; + } + + chip->stream_defaults[CAPTURE]->area = + dma_alloc_coherent(&dev->dev, + MV88FX_SND_MAX_PERIODS * MV88FX_SND_MAX_PERIOD_BYTES, + &chip->stream_defaults[CAPTURE]->addr, + GFP_KERNEL); + + if (!chip->stream_defaults[CAPTURE]->area) { + snd_printk("dma_alloc_coherent failed for capture buffer\n"); + err = -ENOMEM; + goto error; + } + + if (0 == test_memory(pdata->dram, + (unsigned int)chip->stream_defaults[CAPTURE]->addr, + (unsigned int)MV88FX_SND_MAX_PERIODS * MV88FX_SND_MAX_PERIOD_BYTES)) { + + snd_printk("error: playback buffer not in one memory window\n"); + err = -ENOMEM; + goto error; + } + + if (request_irq(chip->irq, mv88fx_snd_interrupt, 0, MV88FX_AUDIO_NAME, + (void *)chip)) { + + snd_printk("Unable to grab IRQ %d\n", chip->irq); + err = -ENOMEM; + goto error; + } + + chip->ch_stat_valid = 1; + chip->burst = 128; + chip->loopback = 0; + chip->dco_ctrl_offst = 0x800; + + err = mv88fx_snd_hw_init(card); + if (err) { + snd_printk("mv88fx_snd_hw_init failed\n"); + err = -ENOMEM; + goto error; + } + + /* Set default values */ + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + snd_printk("Creating chip device failed.\n"); + err = -ENOMEM; + goto error; + } + + /* create pcm devices */ + err = mv88fx_snd_pcm_new(card); + if (err < 0) { + snd_printk("Creating PCM device failed.\n"); + err = -ENOMEM; + goto error; + } + /* create controll interfaces & switches */ + err = mv88fx_snd_ctrl_new(card); + if (err < 0) { + snd_printk("Creating non-PCM device failed.\n"); + err = -ENOMEM; + goto error; + } + + strcpy(card->driver, "mv88fx_snd"); + strcpy(card->shortname, "Marvell mv88fx_snd"); + sprintf(card->longname, "Marvell mv88fx_snd ALSA driver"); + + err = snd_card_register(card); + if (err < 0) { + snd_printk("Card registeration failed.\n"); + err = -ENOMEM; + goto error; + } + + /* if (dma_set_mask(&dev->dev, 0xFFFFFFUL) < 0) { */ + if (dma_set_mask(&dev->dev, 0xFFFFFFFFUL) < 0) { + snd_printk("Could not set DMA mask\n"); + goto error; + } + + platform_set_drvdata(dev, card); + return 0; +error: + if (card) + snd_card_free(card); + platform_set_drvdata(dev, NULL); + return err; +} + +int mv88fx_snd_remove(struct platform_device *dev) +{ + struct snd_card *card = platform_get_drvdata(dev); + + if (card) + snd_card_free(card); + + /* FIXME: Once "../codecs/cs42l51.c" is fixed to behave as a module + * this should be removed */ + codec_del_i2c_device(); + + platform_set_drvdata(dev, NULL); + return 0; +} + +#define mv88fx_snd_resume NULL +#define mv88fx_snd_suspend NULL + +struct platform_driver mv88fx_snd_driver = { + .probe = mv88fx_snd_probe, + .remove = mv88fx_snd_remove, + .suspend = mv88fx_snd_suspend, + .resume = mv88fx_snd_resume, + .driver = { .name = MV88FX_AUDIO_NAME,}, +}; + +int __init mv88fx_snd_init(void) +{ + return platform_driver_register(&mv88fx_snd_driver); +} + +void __exit mv88fx_snd_exit(void) +{ + platform_driver_unregister(&mv88fx_snd_driver); +} + +MODULE_AUTHOR("Maen Suleiman "); +MODULE_DESCRIPTION("Marvell MV88Fx Alsa Sound driver"); +MODULE_LICENSE("GPL"); + +module_init(mv88fx_snd_init); +module_exit(mv88fx_snd_exit); + -- 1.6.5.2