From ce34c40212d02a1ca5d9d74daad21cca67a5dd37 Mon Sep 17 00:00:00 2001 From: Frans Meulenbroeks Date: Sun, 29 Nov 2009 21:29:37 +0100 Subject: linux-kirkwood: added sound driver for openrd-client --- ...003-ARM-Kirkwood-Sound-Sound-driver-added.patch | 3514 ++++++++++++++++++++ 1 file changed, 3514 insertions(+) create mode 100644 recipes/linux/linux-kirkwood/0003-ARM-Kirkwood-Sound-Sound-driver-added.patch (limited to 'recipes/linux/linux-kirkwood') diff --git a/recipes/linux/linux-kirkwood/0003-ARM-Kirkwood-Sound-Sound-driver-added.patch b/recipes/linux/linux-kirkwood/0003-ARM-Kirkwood-Sound-Sound-driver-added.patch new file mode 100644 index 0000000000..fc17a089c5 --- /dev/null +++ b/recipes/linux/linux-kirkwood/0003-ARM-Kirkwood-Sound-Sound-driver-added.patch @@ -0,0 +1,3514 @@ +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 + -- cgit v1.2.3