diff options
author | Rajeshwari Shinde <rajeshwari.s@samsung.com> | 2012-10-25 19:49:23 +0000 |
---|---|---|
committer | Minkyu Kang <mk7.kang@samsung.com> | 2012-11-15 21:08:20 +0900 |
commit | a2d8e0a717b01995eac162de205c90b3ea782440 (patch) | |
tree | c7162c38dca5dfa4b3a9aeeeac4e4ab9b7b90ce5 /drivers | |
parent | 511ed5fdd3892c6e4a42b14c7d80db15eee7fc4f (diff) | |
download | u-boot-midas-a2d8e0a717b01995eac162de205c90b3ea782440.tar.gz u-boot-midas-a2d8e0a717b01995eac162de205c90b3ea782440.tar.bz2 u-boot-midas-a2d8e0a717b01995eac162de205c90b3ea782440.zip |
SOUND: Add WM8994 codec
This patch adds driver for audio codec WM8994
Signed-off-by: R. Chandrasekar <rcsekar@samsung.com>
Signed-off-by: Rajeshwari Shinde <rajeshwari.s@samsung.com>
Acked-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/sound/Makefile | 1 | ||||
-rw-r--r-- | drivers/sound/wm8994.c | 792 | ||||
-rw-r--r-- | drivers/sound/wm8994.h | 87 | ||||
-rw-r--r-- | drivers/sound/wm8994_registers.h | 299 |
4 files changed, 1179 insertions, 0 deletions
diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile index 18ad2c9a71..8fdffb10ef 100644 --- a/drivers/sound/Makefile +++ b/drivers/sound/Makefile @@ -27,6 +27,7 @@ LIB := $(obj)libsound.o COBJS-$(CONFIG_SOUND) += sound.o COBJS-$(CONFIG_I2S) += samsung-i2s.o +COBJS-$(CONFIG_SOUND_WM8994) += wm8994.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/sound/wm8994.c b/drivers/sound/wm8994.c new file mode 100644 index 0000000000..293903ada2 --- /dev/null +++ b/drivers/sound/wm8994.c @@ -0,0 +1,792 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * R. Chandrasekar <rcsekar@samsung.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 <asm/arch/clk.h> +#include <asm/arch/cpu.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <common.h> +#include <div64.h> +#include <i2c.h> +#include <i2s.h> +#include <sound.h> +#include "wm8994.h" +#include "wm8994_registers.h" + +/* defines for wm8994 system clock selection */ +#define SEL_MCLK1 0x00 +#define SEL_MCLK2 0x08 +#define SEL_FLL1 0x10 +#define SEL_FLL2 0x18 + +/* fll config to configure fll */ +struct wm8994_fll_config { + int src; /* Source */ + int in; /* Input frequency in Hz */ + int out; /* output frequency in Hz */ +}; + +/* codec private data */ +struct wm8994_priv { + enum wm8994_type type; /* codec type of wolfson */ + int revision; /* Revision */ + int sysclk[WM8994_MAX_AIF]; /* System clock frequency in Hz */ + int mclk[WM8994_MAX_AIF]; /* master clock frequency in Hz */ + int aifclk[WM8994_MAX_AIF]; /* audio interface clock in Hz */ + struct wm8994_fll_config fll[2]; /* fll config to configure fll */ +}; + +/* wm 8994 supported sampling rate values */ +static unsigned int src_rate[] = { + 8000, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 88200, 96000 +}; + +/* op clock divisions */ +static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 }; + +/* lr clock frame size ratio */ +static int fs_ratios[] = { + 64, 128, 192, 256, 348, 512, 768, 1024, 1408, 1536 +}; + +/* bit clock divisors */ +static int bclk_divs[] = { + 10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480, + 640, 880, 960, 1280, 1760, 1920 +}; + +static struct wm8994_priv g_wm8994_info; +static unsigned char g_wm8994_i2c_dev_addr; + +/* + * Initialise I2C for wm 8994 + * + * @param bus no i2c bus number in which wm8994 is connected + */ +static void wm8994_i2c_init(int bus_no) +{ + i2c_set_bus_num(bus_no); +} + +/* + * Writes value to a device register through i2c + * + * @param reg reg number to be write + * @param data data to be writen to the above registor + * + * @return int value 1 for change, 0 for no change or negative error code. + */ +static int wm8994_i2c_write(unsigned int reg, unsigned short data) +{ + unsigned char val[2]; + + val[0] = (unsigned char)((data >> 8) & 0xff); + val[1] = (unsigned char)(data & 0xff); + debug("Write Addr : 0x%04X, Data : 0x%04X\n", reg, data); + + return i2c_write(g_wm8994_i2c_dev_addr, reg, 2, val, 2); +} + +/* + * Read a value from a device register through i2c + * + * @param reg reg number to be read + * @param data address of read data to be stored + * + * @return int value 0 for success, -1 in case of error. + */ +static unsigned int wm8994_i2c_read(unsigned int reg , unsigned short *data) +{ + unsigned char val[2]; + int ret; + + ret = i2c_read(g_wm8994_i2c_dev_addr, reg, 2, val, 2); + if (ret != 0) { + debug("%s: Error while reading register %#04x\n", + __func__, reg); + return -1; + } + + *data = val[0]; + *data <<= 8; + *data |= val[1]; + + return 0; +} + +/* + * update device register bits through i2c + * + * @param reg codec register + * @param mask register mask + * @param value new value + * + * @return int value 1 if change in the register value, + * 0 for no change or negative error code. + */ +static int wm8994_update_bits(unsigned int reg, unsigned short mask, + unsigned short value) +{ + int change , ret = 0; + unsigned short old, new; + + if (wm8994_i2c_read(reg, &old) != 0) + return -1; + new = (old & ~mask) | (value & mask); + change = (old != new) ? 1 : 0; + if (change) + ret = wm8994_i2c_write(reg, new); + if (ret < 0) + return ret; + + return change; +} + +/* + * Sets i2s set format + * + * @param aif_id Interface ID + * @param fmt i2S format + * + * @return -1 for error and 0 Success. + */ +int wm8994_set_fmt(int aif_id, unsigned int fmt) +{ + int ms_reg; + int aif_reg; + int ms = 0; + int aif = 0; + int aif_clk = 0; + int error = 0; + + switch (aif_id) { + case 1: + ms_reg = WM8994_AIF1_MASTER_SLAVE; + aif_reg = WM8994_AIF1_CONTROL_1; + aif_clk = WM8994_AIF1_CLOCKING_1; + break; + case 2: + ms_reg = WM8994_AIF2_MASTER_SLAVE; + aif_reg = WM8994_AIF2_CONTROL_1; + aif_clk = WM8994_AIF2_CLOCKING_1; + break; + default: + debug("%s: Invalid audio interface selection\n", __func__); + return -1; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ms = WM8994_AIF1_MSTR; + break; + default: + debug("%s: Invalid i2s master selection\n", __func__); + return -1; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8994_AIF1_LRCLK_INV; + case SND_SOC_DAIFMT_DSP_A: + aif |= 0x18; + break; + case SND_SOC_DAIFMT_I2S: + aif |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif |= 0x8; + break; + default: + debug("%s: Invalid i2s format selection\n", __func__); + return -1; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8994_AIF1_BCLK_INV; + break; + default: + debug("%s: Invalid i2s frame inverse selection\n", + __func__); + return -1; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8994_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8994_AIF1_LRCLK_INV; + break; + default: + debug("%s: Invalid i2s clock polarity selection\n", + __func__); + return -1; + } + break; + default: + debug("%s: Invalid i2s format selection\n", __func__); + return -1; + } + + error = wm8994_update_bits(aif_reg, WM8994_AIF1_BCLK_INV | + WM8994_AIF1_LRCLK_INV_MASK | WM8994_AIF1_FMT_MASK, aif); + + error |= wm8994_update_bits(ms_reg, WM8994_AIF1_MSTR_MASK, ms); + error |= wm8994_update_bits(aif_clk, WM8994_AIF1CLK_ENA_MASK, + WM8994_AIF1CLK_ENA); + if (error < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Sets hw params FOR WM8994 + * + * @param wm8994 wm8994 information pointer + * @param aif_id Audio interface ID + * @param sampling_rate Sampling rate + * @param bits_per_sample Bits per sample + * @param Channels Channels in the given audio input + * + * @return -1 for error and 0 Success. + */ +static int wm8994_hw_params(struct wm8994_priv *wm8994, int aif_id, + unsigned int sampling_rate, unsigned int bits_per_sample, + unsigned int channels) +{ + int aif1_reg; + int aif2_reg; + int bclk_reg; + int bclk = 0; + int rate_reg; + int aif1 = 0; + int aif2 = 0; + int rate_val = 0; + int id = aif_id - 1; + int i, cur_val, best_val, bclk_rate, best; + unsigned short reg_data; + int ret = 0; + + switch (aif_id) { + case 1: + aif1_reg = WM8994_AIF1_CONTROL_1; + aif2_reg = WM8994_AIF1_CONTROL_2; + bclk_reg = WM8994_AIF1_BCLK; + rate_reg = WM8994_AIF1_RATE; + break; + case 2: + aif1_reg = WM8994_AIF2_CONTROL_1; + aif2_reg = WM8994_AIF2_CONTROL_2; + bclk_reg = WM8994_AIF2_BCLK; + rate_reg = WM8994_AIF2_RATE; + break; + default: + return -1; + } + + bclk_rate = sampling_rate * 32; + switch (bits_per_sample) { + case 16: + bclk_rate *= 16; + break; + case 20: + bclk_rate *= 20; + aif1 |= 0x20; + break; + case 24: + bclk_rate *= 24; + aif1 |= 0x40; + break; + case 32: + bclk_rate *= 32; + aif1 |= 0x60; + break; + default: + return -1; + } + + /* Try to find an appropriate sample rate; look for an exact match. */ + for (i = 0; i < ARRAY_SIZE(src_rate); i++) + if (src_rate[i] == sampling_rate) + break; + + if (i == ARRAY_SIZE(src_rate)) { + debug("%s: Could not get the best matching samplingrate\n", + __func__); + return -1; + } + + rate_val |= i << WM8994_AIF1_SR_SHIFT; + + /* AIFCLK/fs ratio; look for a close match in either direction */ + best = 0; + best_val = abs((fs_ratios[0] * sampling_rate) + - wm8994->aifclk[id]); + + for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) { + cur_val = abs((fs_ratios[i] * sampling_rate) + - wm8994->aifclk[id]); + if (cur_val >= best_val) + continue; + best = i; + best_val = cur_val; + } + + rate_val |= best; + + /* + * We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + best = 0; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = (wm8994->aifclk[id] * 10 / bclk_divs[i]) - bclk_rate; + if (cur_val < 0) /* BCLK table is sorted */ + break; + best = i; + } + + if (i == ARRAY_SIZE(bclk_divs)) { + debug("%s: Could not get the best matching bclk division\n", + __func__); + return -1; + } + + bclk_rate = wm8994->aifclk[id] * 10 / bclk_divs[best]; + bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT; + + if (wm8994_i2c_read(aif1_reg, ®_data) != 0) { + debug("%s: AIF1 register read Failed\n", __func__); + return -1; + } + + if ((channels == 1) && ((reg_data & 0x18) == 0x18)) + aif2 |= WM8994_AIF1_MONO; + + if (wm8994->aifclk[id] == 0) { + debug("%s:Audio interface clock not set\n", __func__); + return -1; + } + + ret = wm8994_update_bits(aif1_reg, WM8994_AIF1_WL_MASK, aif1); + ret |= wm8994_update_bits(aif2_reg, WM8994_AIF1_MONO, aif2); + ret |= wm8994_update_bits(bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk); + ret |= wm8994_update_bits(rate_reg, WM8994_AIF1_SR_MASK | + WM8994_AIF1CLK_RATE_MASK, rate_val); + + debug("rate vale = %x , bclk val= %x\n", rate_val, bclk); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Configures Audio interface Clock + * + * @param wm8994 wm8994 information pointer + * @param aif Audio Interface ID + * + * @return -1 for error and 0 Success. + */ +static int configure_aif_clock(struct wm8994_priv *wm8994, int aif) +{ + int rate; + int reg1 = 0; + int offset; + int ret; + + /* AIF(1/0) register adress offset calculated */ + if (aif) + offset = 4; + else + offset = 0; + + switch (wm8994->sysclk[aif]) { + case WM8994_SYSCLK_MCLK1: + reg1 |= SEL_MCLK1; + rate = wm8994->mclk[0]; + break; + + case WM8994_SYSCLK_MCLK2: + reg1 |= SEL_MCLK2; + rate = wm8994->mclk[1]; + break; + + case WM8994_SYSCLK_FLL1: + reg1 |= SEL_FLL1; + rate = wm8994->fll[0].out; + break; + + case WM8994_SYSCLK_FLL2: + reg1 |= SEL_FLL2; + rate = wm8994->fll[1].out; + break; + + default: + debug("%s: Invalid input clock selection [%d]\n", + __func__, wm8994->sysclk[aif]); + return -1; + } + + /* if input clock frequenct is more than 135Mhz then divide */ + if (rate >= WM8994_MAX_INPUT_CLK_FREQ) { + rate /= 2; + reg1 |= WM8994_AIF1CLK_DIV; + } + + wm8994->aifclk[aif] = rate; + + ret = wm8994_update_bits(WM8994_AIF1_CLOCKING_1 + offset, + WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV, + reg1); + + ret |= wm8994_update_bits(WM8994_CLOCKING_1, + WM8994_SYSCLK_SRC | WM8994_AIF2DSPCLK_ENA_MASK | + WM8994_SYSDSPCLK_ENA_MASK, WM8994_SYSCLK_SRC | + WM8994_AIF2DSPCLK_ENA | WM8994_SYSDSPCLK_ENA); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Configures Audio interface for the given frequency + * + * @param wm8994 wm8994 information + * @param aif_id Audio Interface + * @param clk_id Input Clock ID + * @param freq Sampling frequency in Hz + * + * @return -1 for error and 0 success. + */ +static int wm8994_set_sysclk(struct wm8994_priv *wm8994, int aif_id, + int clk_id, unsigned int freq) +{ + int i; + int ret = 0; + + wm8994->sysclk[aif_id - 1] = clk_id; + + switch (clk_id) { + case WM8994_SYSCLK_MCLK1: + wm8994->mclk[0] = freq; + if (aif_id == 2) { + ret = wm8994_update_bits(WM8994_AIF1_CLOCKING_2 , + WM8994_AIF2DAC_DIV_MASK , 0); + } + break; + + case WM8994_SYSCLK_MCLK2: + /* TODO: Set GPIO AF */ + wm8994->mclk[1] = freq; + break; + + case WM8994_SYSCLK_FLL1: + case WM8994_SYSCLK_FLL2: + break; + + case WM8994_SYSCLK_OPCLK: + /* + * Special case - a division (times 10) is given and + * no effect on main clocking. + */ + if (freq) { + for (i = 0; i < ARRAY_SIZE(opclk_divs); i++) + if (opclk_divs[i] == freq) + break; + if (i == ARRAY_SIZE(opclk_divs)) { + debug("%s frequency divisor not found\n", + __func__); + return -1; + } + ret = wm8994_update_bits(WM8994_CLOCKING_2, + WM8994_OPCLK_DIV_MASK, i); + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, WM8994_OPCLK_ENA); + } else { + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, 0); + } + + default: + debug("%s Invalid input clock selection [%d]\n", + __func__, clk_id); + return -1; + } + + ret |= configure_aif_clock(wm8994, aif_id - 1); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Initializes Volume for AIF2 to HP path + * + * @returns -1 for error and 0 Success. + * + */ +static int wm8994_init_volume_aif2_dac1(void) +{ + int ret; + + /* Unmute AIF2DAC */ + ret = wm8994_update_bits(WM8994_AIF2_DAC_FILTERS_1, + WM8994_AIF2DAC_MUTE_MASK, 0); + + + ret |= wm8994_update_bits(WM8994_AIF2_DAC_LEFT_VOLUME, + WM8994_AIF2DAC_VU_MASK | WM8994_AIF2DACL_VOL_MASK, + WM8994_AIF2DAC_VU | 0xff); + + ret |= wm8994_update_bits(WM8994_AIF2_DAC_RIGHT_VOLUME, + WM8994_AIF2DAC_VU_MASK | WM8994_AIF2DACR_VOL_MASK, + WM8994_AIF2DAC_VU | 0xff); + + + ret |= wm8994_update_bits(WM8994_DAC1_LEFT_VOLUME, + WM8994_DAC1_VU_MASK | WM8994_DAC1L_VOL_MASK | + WM8994_DAC1L_MUTE_MASK, WM8994_DAC1_VU | 0xc0); + + ret |= wm8994_update_bits(WM8994_DAC1_RIGHT_VOLUME, + WM8994_DAC1_VU_MASK | WM8994_DAC1R_VOL_MASK | + WM8994_DAC1R_MUTE_MASK, WM8994_DAC1_VU | 0xc0); + /* Head Phone Volume */ + ret |= wm8994_i2c_write(WM8994_LEFT_OUTPUT_VOLUME, 0x12D); + ret |= wm8994_i2c_write(WM8994_RIGHT_OUTPUT_VOLUME, 0x12D); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Intialise wm8994 codec device + * + * @param wm8994 wm8994 information + * + * @returns -1 for error and 0 Success. + */ +static int wm8994_device_init(struct wm8994_priv *wm8994) +{ + const char *devname; + unsigned short reg_data; + int ret; + + wm8994_i2c_write(WM8994_SOFTWARE_RESET, WM8994_SW_RESET);/* Reset */ + + ret = wm8994_i2c_read(WM8994_SOFTWARE_RESET, ®_data); + if (ret < 0) { + debug("Failed to read ID register\n"); + goto err; + } + + if (reg_data == WM8994_ID) { + devname = "WM8994"; + debug("Device registered as type %d\n", wm8994->type); + wm8994->type = WM8994; + } else { + debug("Device is not a WM8994, ID is %x\n", ret); + ret = -1; + goto err; + } + + ret = wm8994_i2c_read(WM8994_CHIP_REVISION, ®_data); + if (ret < 0) { + debug("Failed to read revision register: %d\n", ret); + goto err; + } + wm8994->revision = reg_data; + debug("%s revision %c\n", devname, 'A' + wm8994->revision); + + /* VMID Selection */ + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_1, + WM8994_VMID_SEL_MASK | WM8994_BIAS_ENA_MASK, 0x3); + + /* Charge Pump Enable */ + ret |= wm8994_update_bits(WM8994_CHARGE_PUMP_1, WM8994_CP_ENA_MASK, + WM8994_CP_ENA); + + /* Head Phone Power Enable */ + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_1, + WM8994_HPOUT1L_ENA_MASK, WM8994_HPOUT1L_ENA); + + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_1, + WM8994_HPOUT1R_ENA_MASK, WM8994_HPOUT1R_ENA); + + /* Power enable for AIF2 and DAC1 */ + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_5, + WM8994_AIF2DACL_ENA_MASK | WM8994_AIF2DACR_ENA_MASK | + WM8994_DAC1L_ENA_MASK | WM8994_DAC1R_ENA_MASK, + WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA | WM8994_DAC1L_ENA | + WM8994_DAC1R_ENA); + + /* Head Phone Initialisation */ + ret |= wm8994_update_bits(WM8994_ANALOGUE_HP_1, + WM8994_HPOUT1L_DLY_MASK | WM8994_HPOUT1R_DLY_MASK, + WM8994_HPOUT1L_DLY | WM8994_HPOUT1R_DLY); + + ret |= wm8994_update_bits(WM8994_DC_SERVO_1, + WM8994_DCS_ENA_CHAN_0_MASK | + WM8994_DCS_ENA_CHAN_1_MASK , WM8994_DCS_ENA_CHAN_0 | + WM8994_DCS_ENA_CHAN_1); + + ret |= wm8994_update_bits(WM8994_ANALOGUE_HP_1, + WM8994_HPOUT1L_DLY_MASK | + WM8994_HPOUT1R_DLY_MASK | WM8994_HPOUT1L_OUTP_MASK | + WM8994_HPOUT1R_OUTP_MASK | + WM8994_HPOUT1L_RMV_SHORT_MASK | + WM8994_HPOUT1R_RMV_SHORT_MASK, WM8994_HPOUT1L_DLY | + WM8994_HPOUT1R_DLY | WM8994_HPOUT1L_OUTP | + WM8994_HPOUT1R_OUTP | WM8994_HPOUT1L_RMV_SHORT | + WM8994_HPOUT1R_RMV_SHORT); + + /* MIXER Config DAC1 to HP */ + ret |= wm8994_update_bits(WM8994_OUTPUT_MIXER_1, + WM8994_DAC1L_TO_HPOUT1L_MASK, WM8994_DAC1L_TO_HPOUT1L); + + ret |= wm8994_update_bits(WM8994_OUTPUT_MIXER_2, + WM8994_DAC1R_TO_HPOUT1R_MASK, WM8994_DAC1R_TO_HPOUT1R); + + /* Routing AIF2 to DAC1 */ + ret |= wm8994_update_bits(WM8994_DAC1_LEFT_MIXER_ROUTING, + WM8994_AIF2DACL_TO_DAC1L_MASK, + WM8994_AIF2DACL_TO_DAC1L); + + ret |= wm8994_update_bits(WM8994_DAC1_RIGHT_MIXER_ROUTING, + WM8994_AIF2DACR_TO_DAC1R_MASK, + WM8994_AIF2DACR_TO_DAC1R); + + /* GPIO Settings for AIF2 */ + /* B CLK */ + ret |= wm8994_update_bits(WM8994_GPIO_3, WM8994_GPIO_DIR_MASK | + WM8994_GPIO_FUNCTION_MASK , + WM8994_GPIO_DIR_OUTPUT | + WM8994_GPIO_FUNCTION_I2S_CLK); + + /* LR CLK */ + ret |= wm8994_update_bits(WM8994_GPIO_4, WM8994_GPIO_DIR_MASK | + WM8994_GPIO_FUNCTION_MASK, + WM8994_GPIO_DIR_OUTPUT | + WM8994_GPIO_FUNCTION_I2S_CLK); + + /* DATA */ + ret |= wm8994_update_bits(WM8994_GPIO_5, WM8994_GPIO_DIR_MASK | + WM8994_GPIO_FUNCTION_MASK, + WM8994_GPIO_DIR_OUTPUT | + WM8994_GPIO_FUNCTION_I2S_CLK); + + ret |= wm8994_init_volume_aif2_dac1(); + if (ret < 0) + goto err; + + debug("%s: Codec chip init ok\n", __func__); + return 0; +err: + debug("%s: Codec chip init error\n", __func__); + return -1; +} + +/*wm8994 Device Initialisation */ +int wm8994_init(struct sound_codec_info *pcodec_info, + enum en_audio_interface aif_id, + int sampling_rate, int mclk_freq, + int bits_per_sample, unsigned int channels) +{ + int ret = 0; + + /* shift the device address by 1 for 7 bit addressing */ + g_wm8994_i2c_dev_addr = pcodec_info->i2c_dev_addr; + wm8994_i2c_init(pcodec_info->i2c_bus); + + if (pcodec_info->codec_type == CODEC_WM_8994) + g_wm8994_info.type = WM8994; + else { + debug("%s: Codec id [%d] not defined\n", __func__, + pcodec_info->codec_type); + return -1; + } + + ret = wm8994_device_init(&g_wm8994_info); + if (ret < 0) { + debug("%s: wm8994 codec chip init failed\n", __func__); + return ret; + } + + ret = wm8994_set_sysclk(&g_wm8994_info, aif_id, WM8994_SYSCLK_MCLK1, + mclk_freq); + if (ret < 0) { + debug("%s: wm8994 codec set sys clock failed\n", __func__); + return ret; + } + + ret = wm8994_hw_params(&g_wm8994_info, aif_id, sampling_rate, + bits_per_sample, channels); + + if (ret == 0) { + ret = wm8994_set_fmt(aif_id, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + } + return ret; +} diff --git a/drivers/sound/wm8994.h b/drivers/sound/wm8994.h new file mode 100644 index 0000000000..a8f0de18c5 --- /dev/null +++ b/drivers/sound/wm8994.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * R. Chadrasekar <rcsekar@samsung.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 + */ + +#ifndef __WM8994_H__ +#define __WM8994_H__ + +/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */ +#define WM8994_SYSCLK_MCLK1 1 +#define WM8994_SYSCLK_MCLK2 2 +#define WM8994_SYSCLK_FLL1 3 +#define WM8994_SYSCLK_FLL2 4 + +/* Avilable audi interface ports in wm8994 codec */ +enum en_audio_interface { + WM8994_AIF1 = 1, + WM8994_AIF2, + WM8994_AIF3 +}; + +/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */ +#define WM8994_SYSCLK_OPCLK 5 + +#define WM8994_FLL1 1 +#define WM8994_FLL2 2 + +#define WM8994_FLL_SRC_MCLK1 1 +#define WM8994_FLL_SRC_MCLK2 2 +#define WM8994_FLL_SRC_LRCLK 3 +#define WM8994_FLL_SRC_BCLK 4 + +/* maximum available digital interfac in the dac to configure */ +#define WM8994_MAX_AIF 2 + +#define WM8994_MAX_INPUT_CLK_FREQ 13500000 +#define WM8994_ID 0x8994 + +enum wm8994_vmid_mode { + WM8994_VMID_NORMAL, + WM8994_VMID_FORCE, +}; + +/* wm 8994 family devices */ +enum wm8994_type { + WM8994 = 0, + WM8958 = 1, + WM1811 = 2, +}; + +/* + * intialise wm8994 sound codec device for the given configuration + * + * @param pcodec_info pointer value of the sound codec info structure + * parsed from device tree + * @param aif_id enum value of codec interface port in which + * soc i2s is connected + * @param sampling_rate Sampling rate ranges between from 8khz to 96khz + * @param mclk_freq Master clock frequency. + * @param bits_per_sample bits per Sample can be 16 or 24 + * @param channels Number of channnels, maximum 2 + * + * @returns -1 for error and 0 Success. + */ +int wm8994_init(struct sound_codec_info *pcodec_info, + enum en_audio_interface aif_id, + int sampling_rate, int mclk_freq, + int bits_per_sample, unsigned int channels); +#endif /*__WM8994_H__ */ diff --git a/drivers/sound/wm8994_registers.h b/drivers/sound/wm8994_registers.h new file mode 100644 index 0000000000..f455b112f0 --- /dev/null +++ b/drivers/sound/wm8994_registers.h @@ -0,0 +1,299 @@ +/* + * (C) Copyright 2012 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __WM8994_REGISTERS_H__ +#define __WM8994_REGISTERS_H__ + +/* + * Register values. + */ +#define WM8994_SOFTWARE_RESET 0x00 +#define WM8994_POWER_MANAGEMENT_1 0x01 +#define WM8994_POWER_MANAGEMENT_2 0x02 +#define WM8994_POWER_MANAGEMENT_5 0x05 +#define WM8994_LEFT_OUTPUT_VOLUME 0x1C +#define WM8994_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8994_OUTPUT_MIXER_1 0x2D +#define WM8994_OUTPUT_MIXER_2 0x2E +#define WM8994_CHARGE_PUMP_1 0x4C +#define WM8994_DC_SERVO_1 0x54 +#define WM8994_ANALOGUE_HP_1 0x60 +#define WM8994_CHIP_REVISION 0x100 +#define WM8994_AIF1_CLOCKING_1 0x200 +#define WM8994_AIF1_CLOCKING_2 0x201 +#define WM8994_AIF2_CLOCKING_1 0x204 +#define WM8994_CLOCKING_1 0x208 +#define WM8994_CLOCKING_2 0x209 +#define WM8994_AIF1_RATE 0x210 +#define WM8994_AIF2_RATE 0x211 +#define WM8994_RATE_STATUS 0x212 +#define WM8994_AIF1_CONTROL_1 0x300 +#define WM8994_AIF1_CONTROL_2 0x301 +#define WM8994_AIF1_MASTER_SLAVE 0x302 +#define WM8994_AIF1_BCLK 0x303 +#define WM8994_AIF2_CONTROL_1 0x310 +#define WM8994_AIF2_CONTROL_2 0x311 +#define WM8994_AIF2_MASTER_SLAVE 0x312 +#define WM8994_AIF2_BCLK 0x313 +#define WM8994_AIF2_DAC_LEFT_VOLUME 0x502 +#define WM8994_AIF2_DAC_RIGHT_VOLUME 0x503 +#define WM8994_AIF2_DAC_FILTERS_1 0x520 +#define WM8994_DAC1_LEFT_MIXER_ROUTING 0x601 +#define WM8994_DAC1_RIGHT_MIXER_ROUTING 0x602 +#define WM8994_DAC1_LEFT_VOLUME 0x610 +#define WM8994_DAC1_RIGHT_VOLUME 0x611 +#define WM8994_GPIO_3 0x702 +#define WM8994_GPIO_4 0x703 +#define WM8994_GPIO_5 0x704 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +/* SW_RESET */ +#define WM8994_SW_RESET 1 +/* + * R1 (0x01) - Power Management (1) + */ +/* HPOUT1L_ENA */ +#define WM8994_HPOUT1L_ENA 0x0200 +/* HPOUT1L_ENA */ +#define WM8994_HPOUT1L_ENA_MASK 0x0200 +/* HPOUT1R_ENA */ +#define WM8994_HPOUT1R_ENA 0x0100 +/* HPOUT1R_ENA */ +#define WM8994_HPOUT1R_ENA_MASK 0x0100 +/* VMID_SEL - [2:1] */ +#define WM8994_VMID_SEL_MASK 0x0006 +/* BIAS_ENA */ +#define WM8994_BIAS_ENA 0x0001 +/* BIAS_ENA */ +#define WM8994_BIAS_ENA_MASK 0x0001 + +/* + * R2 (0x02) - Power Management (2) + */ +/* OPCLK_ENA */ +#define WM8994_OPCLK_ENA 0x0800 + +/* + * R5 (0x05) - Power Management (5) + */ +/* AIF2DACL_ENA */ +#define WM8994_AIF2DACL_ENA 0x2000 +#define WM8994_AIF2DACL_ENA_MASK 0x2000 +/* AIF2DACR_ENA */ +#define WM8994_AIF2DACR_ENA 0x1000 +#define WM8994_AIF2DACR_ENA_MASK 0x1000 +/* DAC1L_ENA */ +#define WM8994_DAC1L_ENA 0x0002 +#define WM8994_DAC1L_ENA_MASK 0x0002 +/* DAC1R_ENA */ +#define WM8994_DAC1R_ENA 0x0001 +#define WM8994_DAC1R_ENA_MASK 0x0001 + +/* + * R45 (0x2D) - Output Mixer (1) + */ +/* DAC1L_TO_HPOUT1L */ +#define WM8994_DAC1L_TO_HPOUT1L 0x0100 +#define WM8994_DAC1L_TO_HPOUT1L_MASK 0x0100 + +/* + * R46 (0x2E) - Output Mixer (2) + */ +/* DAC1R_TO_HPOUT1R */ +#define WM8994_DAC1R_TO_HPOUT1R 0x0100 +#define WM8994_DAC1R_TO_HPOUT1R_MASK 0x0100 + +/* + * R76 (0x4C) - Charge Pump (1) + */ +/* CP_ENA */ +#define WM8994_CP_ENA 0x8000 +#define WM8994_CP_ENA_MASK 0x8000 +/* + * R84 (0x54) - DC Servo (1) + */ +/* DCS_ENA_CHAN_1 */ +#define WM8994_DCS_ENA_CHAN_1 0x0002 +#define WM8994_DCS_ENA_CHAN_1_MASK 0x0002 +/* DCS_ENA_CHAN_0 */ +#define WM8994_DCS_ENA_CHAN_0 0x0001 +#define WM8994_DCS_ENA_CHAN_0_MASK 0x0001 + +/* + * R96 (0x60) - Analogue HP (1) + */ +/* HPOUT1L_RMV_SHORT */ +#define WM8994_HPOUT1L_RMV_SHORT 0x0080 +#define WM8994_HPOUT1L_RMV_SHORT_MASK 0x0080 +/* HPOUT1L_OUTP */ +#define WM8994_HPOUT1L_OUTP 0x0040 +#define WM8994_HPOUT1L_OUTP_MASK 0x0040 +/* HPOUT1L_DLY */ +#define WM8994_HPOUT1L_DLY 0x0020 +#define WM8994_HPOUT1L_DLY_MASK 0x0020 +/* HPOUT1R_RMV_SHORT */ +#define WM8994_HPOUT1R_RMV_SHORT 0x0008 +#define WM8994_HPOUT1R_RMV_SHORT_MASK 0x0008 +/* HPOUT1R_OUTP */ +#define WM8994_HPOUT1R_OUTP 0x0004 +#define WM8994_HPOUT1R_OUTP_MASK 0x0004 +/* HPOUT1R_DLY */ +#define WM8994_HPOUT1R_DLY 0x0002 +#define WM8994_HPOUT1R_DLY_MASK 0x0002 + +/* + * R512 (0x200) - AIF1 Clocking (1) + */ +/* AIF1CLK_SRC - [4:3] */ +#define WM8994_AIF1CLK_SRC_MASK 0x0018 +/* AIF1CLK_DIV */ +#define WM8994_AIF1CLK_DIV 0x0002 +/* AIF1CLK_ENA */ +#define WM8994_AIF1CLK_ENA 0x0001 +#define WM8994_AIF1CLK_ENA_MASK 0x0001 + +/* + * R517 (0x205) - AIF2 Clocking (2) + */ +/* AIF2DAC_DIV - [5:3] */ +#define WM8994_AIF2DAC_DIV_MASK 0x0038 + +/* + * R520 (0x208) - Clocking (1) + */ +/* AIF2DSPCLK_ENA */ +#define WM8994_AIF2DSPCLK_ENA 0x0004 +#define WM8994_AIF2DSPCLK_ENA_MASK 0x0004 +/* SYSDSPCLK_ENA */ +#define WM8994_SYSDSPCLK_ENA 0x0002 +#define WM8994_SYSDSPCLK_ENA_MASK 0x0002 +/* SYSCLK_SRC */ +#define WM8994_SYSCLK_SRC 0x0001 + +/* + * R521 (0x209) - Clocking (2) + */ +/* OPCLK_DIV - [2:0] */ +#define WM8994_OPCLK_DIV_MASK 0x0007 + +/* + * R528 (0x210) - AIF1 Rate + */ +/* AIF1_SR - [7:4] */ +#define WM8994_AIF1_SR_MASK 0x00F0 +#define WM8994_AIF1_SR_SHIFT 4 +/* AIF1CLK_RATE - [3:0] */ +#define WM8994_AIF1CLK_RATE_MASK 0x000F + +/* + * R768 (0x300) - AIF1 Control (1) + */ +/* AIF1_BCLK_INV */ +#define WM8994_AIF1_BCLK_INV 0x0100 +/* AIF1_LRCLK_INV */ +#define WM8994_AIF1_LRCLK_INV 0x0080 +#define WM8994_AIF1_LRCLK_INV_MASK 0x0080 +/* AIF1_WL - [6:5] */ +#define WM8994_AIF1_WL_MASK 0x0060 +/* AIF1_FMT - [4:3] */ +#define WM8994_AIF1_FMT_MASK 0x0018 + +/* + * R769 (0x301) - AIF1 Control (2) + */ +/* AIF1_MONO */ +#define WM8994_AIF1_MONO 0x0100 + +/* + * R770 (0x302) - AIF1 Master/Slave + */ +/* AIF1_MSTR */ +#define WM8994_AIF1_MSTR 0x4000 +#define WM8994_AIF1_MSTR_MASK 0x4000 + +/* + * R771 (0x303) - AIF1 BCLK + */ +/* AIF1_BCLK_DIV - [8:4] */ +#define WM8994_AIF1_BCLK_DIV_MASK 0x01F0 +#define WM8994_AIF1_BCLK_DIV_SHIFT 4 + +/* + * R1282 (0x502) - AIF2 DAC Left Volume + */ +/* AIF2DAC_VU */ +#define WM8994_AIF2DAC_VU 0x0100 +#define WM8994_AIF2DAC_VU_MASK 0x0100 +/* AIF2DACL_VOL - [7:0] */ +#define WM8994_AIF2DACL_VOL_MASK 0x00FF + +/* + * R1283 (0x503) - AIF2 DAC Right Volume + */ +/* AIF2DACR_VOL - [7:0] */ +#define WM8994_AIF2DACR_VOL_MASK 0x00FF + +/* + * R1312 (0x520) - AIF2 DAC Filters (1) + */ +/* AIF2DAC_MUTE */ +#define WM8994_AIF2DAC_MUTE_MASK 0x0200 + +/* + * R1537 (0x601) - DAC1 Left Mixer Routing + */ +/* AIF2DACL_TO_DAC1L */ +#define WM8994_AIF2DACL_TO_DAC1L 0x0004 +#define WM8994_AIF2DACL_TO_DAC1L_MASK 0x0004 + +/* + * R1538 (0x602) - DAC1 Right Mixer Routing + */ +/* AIF2DACR_TO_DAC1R */ +#define WM8994_AIF2DACR_TO_DAC1R 0x0004 +#define WM8994_AIF2DACR_TO_DAC1R_MASK 0x0004 + +/* + * R1552 (0x610) - DAC1 Left Volume + */ +/* DAC1L_MUTE */ +#define WM8994_DAC1L_MUTE_MASK 0x0200 +/* DAC1_VU */ +#define WM8994_DAC1_VU 0x0100 +#define WM8994_DAC1_VU_MASK 0x0100 +/* DAC1L_VOL - [7:0] */ +#define WM8994_DAC1L_VOL_MASK 0x00FF + +/* + * R1553 (0x611) - DAC1 Right Volume + */ +/* DAC1R_MUTE */ +#define WM8994_DAC1R_MUTE_MASK 0x0200 +/* DAC1R_VOL - [7:0] */ +#define WM8994_DAC1R_VOL_MASK 0x00FF + +/* + * GPIO + */ +/* OUTPUT PIN */ +#define WM8994_GPIO_DIR_OUTPUT 0x8000 +/* GPIO PIN MASK */ +#define WM8994_GPIO_DIR_MASK 0xFFE0 +/* I2S CLK */ +#define WM8994_GPIO_FUNCTION_I2S_CLK 0x0000 +/* GPn FN */ +#define WM8994_GPIO_FUNCTION_MASK 0x001F +#endif |