From: dgl <“dgl@rock-chips.com”> Date: Mon, 28 Jul 2014 02:07:40 +0000 (+0800) Subject: audio: rk3036 codec driver X-Git-Tag: firefly_0821_release~4916^2~149 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=f3fcb4ab6aa388b8ac2c17fa5852f24918aba3d2;p=firefly-linux-kernel-4.4.55.git audio: rk3036 codec driver add rk3036 audio codec driver program, include DTS node. Signed-off-by: dgl@rock-chips.com --- diff --git a/Documentation/devicetree/bindings/sound/rockchip-audio.txt b/Documentation/devicetree/bindings/sound/rockchip-audio.txt new file mode 100644 index 000000000000..a4d728a61d4a --- /dev/null +++ b/Documentation/devicetree/bindings/sound/rockchip-audio.txt @@ -0,0 +1,36 @@ +* Rockchip Audio controller + +Required SoC Specific Properties: + +- compatible : "rk3036-audio" +- reg: physical base address of the controller and length of memory mapped + region. +- clocks: must include clock specifiers corresponding to entries in the + clock-names property. +- clocks-names: list of clock names sorted in the same order as the clocks + property. Must contain "rockchip-i2s" and "rk3036-codec". +- interrupts: interrupt number to the cpu. +- dmas: list of DMA controller phandle and DMA request line ordered pairs. +- dma-names: identifier string for each DMA request line in the dmas property. + These strings correspond 1:1 with the ordered pairs in dmas. +- pinctrl-names: must contain a "default" entry. +- pinctrl-0: pin control group to be used for this controller. +- pinctrl-1: pin control group to be used for gpio. + +Example: + +rockchip-audio { + compatible = "rk3036-audio"; + dais { + dai0 { + audio-codec = <&codec>; + i2s-controller = <&i2s>; + format = "i2s"; + //continuous-clock; + //bitclock-inversion; + //frame-inversion; + //bitclock-master; + //frame-master; + }; + }; +}; diff --git a/arch/arm/boot/dts/rk3036-sdk.dts b/arch/arm/boot/dts/rk3036-sdk.dts old mode 100755 new mode 100644 index d83cb97b7f83..adfbc43b68a9 --- a/arch/arm/boot/dts/rk3036-sdk.dts +++ b/arch/arm/boot/dts/rk3036-sdk.dts @@ -106,6 +106,22 @@ }; }; }; + + rockchip-audio { + compatible = "rk3036-audio"; + dais { + dai0 { + audio-codec = <&codec>; + i2s-controller = <&i2s>; + format = "i2s"; + //continuous-clock; + //bitclock-inversion; + //frame-inversion; + //bitclock-master; + //frame-master; + }; + }; + }; }; &uart0{ diff --git a/arch/arm/boot/dts/rk3036.dtsi b/arch/arm/boot/dts/rk3036.dtsi old mode 100755 new mode 100644 index 30c955798067..d9f420b24ac8 --- a/arch/arm/boot/dts/rk3036.dtsi +++ b/arch/arm/boot/dts/rk3036.dtsi @@ -386,6 +386,18 @@ //pinctrl-1 = <&i2s_gpio>; }; + codec: codec@20030000 { + compatible = "rk3036-codec"; + reg = <0x20030000 0x1000>; + spk_ctl_io = <&gpio1 GPIO_A0 0>; + pinctrl-names = "default"; + pinctrl-0 = <&i2s0_gpio>; + + boot_depop = <1>; + pa_enable_time = <1000>; + clocks = <&clk_gates5 14>; + clock-names = "g_pclk_acodec"; + }; spdif: spdif@10204000 { compatible = "rockchip-spdif"; reg = <0x10204000 0x1000>; diff --git a/arch/arm/configs/rk3036_defconfig b/arch/arm/configs/rk3036_defconfig index 93672bac6c92..d4554a48dccf 100644 --- a/arch/arm/configs/rk3036_defconfig +++ b/arch/arm/configs/rk3036_defconfig @@ -375,8 +375,7 @@ CONFIG_SND_USB_AUDIO=y CONFIG_SND_SOC=y CONFIG_SND_RK_SOC=y CONFIG_SND_RK_SOC_HDMI_I2S=y -CONFIG_SND_RK_SOC_RT5631=y -CONFIG_SND_RK_SOC_RT3224=y +CONFIG_SND_RK_SOC_RK3036=y CONFIG_HID_BATTERY_STRENGTH=y CONFIG_HIDRAW=y CONFIG_UHID=y diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 998c628c479d..a11ae2b7dcb2 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -314,6 +314,9 @@ config SND_SOC_OMAP_HDMI_CODEC config SND_SOC_PCM3008 tristate +config SND_SOC_RK3036 + tristate + #Freescale sgtl5000 codec config SND_SOC_SGTL5000 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile old mode 100755 new mode 100644 index f02b0c158f11..7389cb5e4d89 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -129,12 +129,13 @@ snd-soc-rt5616-objs := rt5616.o snd-soc-rt5631-phone-objs := rt5631_phone.o snd-soc-rt5625-objs := rt5625.o obj-y := rt56xx_ioctl.o -snd-soc-rt5639-objs := rt5639.o rt5639_ioctl.o +snd-soc-rt5639-objs := rt5639.o rt5639_ioctl.o snd-soc-rt5640-objs := rt5640.o rt5640-dsp.o rt5640_ioctl.o snd-soc-rt3224-objs := rt3261.o rt3261_ioctl.o rt_codec_ioctl.o snd-soc-rt3261-objs := rt3261-dsp.o snd-soc-cs42l52-objs := cs42l52.o snd-soc-rk1000-objs := rk1000_codec.o +snd-soc-rk3036-objs := rk3036_codec.o snd-soc-rk610-objs := rk610_codec.o snd-soc-rk616-objs := rk616_codec.o snd-soc-rk2928-objs := rk2928_codec.o @@ -282,6 +283,7 @@ obj-$(CONFIG_SND_SOC_RT5639) += snd-soc-rt5639.o obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o obj-$(CONFIG_SND_SOC_RK1000) += snd-soc-rk1000.o +obj-$(CONFIG_SND_SOC_RK3036) += snd-soc-rk3036.o obj-$(CONFIG_SND_SOC_RK610) += snd-soc-rk610.o obj-$(CONFIG_SND_SOC_RK616) += snd-soc-rk616.o obj-$(CONFIG_SND_SOC_RK2928) += snd-soc-rk2928.o diff --git a/sound/soc/codecs/rk3036_codec.c b/sound/soc/codecs/rk3036_codec.c new file mode 100644 index 000000000000..81c84e963948 --- /dev/null +++ b/sound/soc/codecs/rk3036_codec.c @@ -0,0 +1,762 @@ +/* + * rk3036_codec.c + * + * Driver for rockchip rk3036 codec + * Copyright (C) 2014 + * + * 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. + * + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rk3036_codec.h" + +#define DEBUGOPEN 0 + +#if DEBUGOPEN +#define dbg_codec(level, fmt, arg...) do { \ + if (debug >= level) \ + printk(fmt , ## arg); } \ + while (0) + +#else +#define dbg_codec(level, fmt, arg...) +#endif + +#define INVALID_GPIO -1 +#define SPK_CTRL_OPEN 1 +#define SPK_CTRL_CLOSE 0 + +/* volume setting + * 0: -39dB + * 26: 0dB + * 31: 6dB + * Step: 1.5dB +*/ +#define OUT_VOLUME 31 + +/*with capacity or not*/ +#define WITH_CAP + +struct rk3036_codec_priv { + void __iomem *regbase; + struct snd_soc_codec *codec; + + unsigned int stereo_sysclk; + unsigned int rate; + + struct delayed_work codec_delayed_work; + int spk_ctl_gpio; + int delay_time; + + struct clk *pclk; +}; +static struct rk3036_codec_priv *rk3036_priv; + +static const unsigned int rk3036_reg_defaults[RK3036_CODEC_REG28+1] = { + [RK3036_CODEC_RESET] = 0x03, + [RK3036_CODEC_REG03] = 0x00, + [RK3036_CODEC_REG04] = 0x50, + [RK3036_CODEC_REG05] = 0x0E, + [RK3036_CODEC_REG22] = 0x00, + [RK3036_CODEC_REG23] = 0x00, + [RK3036_CODEC_REG24] = 0x00, + [RK3036_CODEC_REG25] = 0x00, + [RK3036_CODEC_REG26] = 0x00, + [RK3036_CODEC_REG27] = 0x05, + [RK3036_CODEC_REG28] = 0x00, +}; + +/* function declare: */ +static int rk3036_codec_register( + struct snd_soc_codec *codec, unsigned int reg); +static int rk3036_volatile_register( + struct snd_soc_codec *codec, unsigned int reg); +static int rk3036_set_bias_level( + struct snd_soc_codec *codec, enum snd_soc_bias_level level); +static unsigned int rk3036_codec_read( + struct snd_soc_codec *codec, unsigned int reg); +static inline void rk3036_write_reg_cache( + struct snd_soc_codec *codec, unsigned int reg, unsigned int value); + +static inline unsigned int rk3036_read_reg_cache(struct snd_soc_codec * + codec, unsigned int reg) +{ + unsigned int *cache = codec->reg_cache; + + if (rk3036_codec_register(codec, reg)) + return cache[reg]; + + dbg_codec(2, "%s : reg error!\n", __func__); + + return -EINVAL; +} + +static inline void rk3036_write_reg_cache(struct snd_soc_codec * + codec, unsigned int reg, unsigned int value) +{ + unsigned int *cache = codec->reg_cache; + + if (rk3036_codec_register(codec, reg)) { + cache[reg] = value; + return; + } + + dbg_codec(2, "%s : reg error!\n", __func__); +} + +static int rk3036_reset(struct snd_soc_codec *codec) +{ + writel(0x00, rk3036_priv->regbase+RK3036_CODEC_RESET); + mdelay(10); + writel(0x03, rk3036_priv->regbase+RK3036_CODEC_RESET); + mdelay(10); + memcpy(codec-> + reg_cache, rk3036_reg_defaults, + sizeof(rk3036_reg_defaults)); + return 0; +} + +static int rk3036_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct rk3036_codec_priv *rk3036 = rk3036_priv; + unsigned int dac_aif1 = 0, dac_aif2 = 0; + + if (!rk3036) { + dbg_codec(2, "%s : rk3036 is NULL\n", __func__); + return -EINVAL; + } + dac_aif2 |= RK3036_CR05_FRAMEH_32BITS; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dac_aif1 |= RK3036_CR04_HFVALID_16BITS; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + dac_aif1 |= RK3036_CR04_HFVALID_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + dac_aif1 |= RK3036_CR04_HFVALID_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + dac_aif1 |= RK3036_CR04_HFVALID_32BITS; + break; + default: + return -EINVAL; + } + + dac_aif1 |= RK3036_CR04_LR_SWAP_DIS; + dac_aif2 |= RK3036_CR05_DAC_RESET_DIS; + + snd_soc_update_bits( + codec, RK3036_CODEC_REG04, + RK3036_CR04_HFVALID_MASK + |RK3036_CR04_LR_SWAP_MASK, dac_aif1); + snd_soc_update_bits( + codec, RK3036_CODEC_REG05, + RK3036_CR05_FRAMEH_MASK + |RK3036_CR05_DAC_RESET_MASK, dac_aif2); + + return 0; +} + +static int rk3036_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int fmt_ms = 0, dac_aif1 = 0, dac_aif2 = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + fmt_ms |= RK3036_CR03_DIRECTION_IN|RK3036_CR03_I2SMODE_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + fmt_ms |= RK3036_CR03_DIRECTION_IOUT|RK3036_CR03_I2SMODE_MASTER; + break; + default: + dbg_codec(2, "%s : set master mask failed!\n", __func__); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + dac_aif1 |= RK3036_CR04_MODE_PCM; + break; + case SND_SOC_DAIFMT_DSP_B: + break; + case SND_SOC_DAIFMT_I2S: + dac_aif1 |= RK3036_CR04_MODE_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dac_aif1 |= RK3036_CR04_MODE_RIGHT; + break; + case SND_SOC_DAIFMT_LEFT_J: + dac_aif1 |= RK3036_CR04_MODE_LEFT; + break; + default: + dbg_codec(2, "%s : set format failed!\n", __func__); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + dac_aif1 |= RK3036_CR04_I2SLRC_NORMAL; + dac_aif2 |= RK3036_CR05_BCLKPOL_NORMAL; + break; + case SND_SOC_DAIFMT_IB_IF: + dac_aif1 |= RK3036_CR04_I2SLRC_REVERSAL; + dac_aif2 |= RK3036_CR05_BCLKPOL_REVERSAL; + break; + case SND_SOC_DAIFMT_IB_NF: + dac_aif1 |= RK3036_CR04_I2SLRC_REVERSAL; + dac_aif2 |= RK3036_CR05_BCLKPOL_NORMAL; + break; + case SND_SOC_DAIFMT_NB_IF: + dac_aif1 |= RK3036_CR04_I2SLRC_NORMAL; + dac_aif2 |= RK3036_CR05_BCLKPOL_REVERSAL; + break; + default: + dbg_codec(2, "%s : set dai format failed!\n", __func__); + return -EINVAL; + } + + snd_soc_update_bits( + codec, RK3036_CODEC_REG03, + RK3036_CR03_DIRECTION_MASK + |RK3036_CR03_I2SMODE_MASK, fmt_ms); + snd_soc_update_bits( + codec, RK3036_CODEC_REG04, + RK3036_CR04_I2SLRC_MASK + | RK3036_CR04_MODE_MASK, dac_aif1); + snd_soc_update_bits( + codec, RK3036_CODEC_REG05, + RK3036_CR05_BCLKPOL_MASK, dac_aif2); + return 0; +} + +static int rk3036_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct rk3036_codec_priv *rk3036 = rk3036_priv; + + if (!rk3036) { + dbg_codec(2, "%s : rk3036 is NULL\n", __func__); + return -EINVAL; + } + + rk3036->stereo_sysclk = freq; + + return 0; +} + +static int rk3036_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val; + + if (mute) { + val = snd_soc_read(codec, RK3036_CODEC_REG27); + if (val & (RK3036_CR27_HPOUTL_G_WORK + |RK3036_CR27_HPOUTR_G_WORK)) { + val &= ~RK3036_CR27_HPOUTL_G_WORK; + val &= ~RK3036_CR27_HPOUTR_G_WORK; + snd_soc_write(codec, RK3036_CODEC_REG27, val); + } + } else { + val = snd_soc_read(codec, RK3036_CODEC_REG27); + if ((val | ~RK3036_CR27_HPOUTL_G_WORK) || (val + | ~RK3036_CR27_HPOUTR_G_WORK)) { + val |= RK3036_CR27_HPOUTL_G_WORK + |RK3036_CR27_HPOUTR_G_WORK; + snd_soc_write(codec, RK3036_CODEC_REG27, val); + } + } + return 0; +} + +static struct rk3036_reg_val_typ playback_power_up_list[] = { + /*01*/{RK3036_CODEC_REG24, + RK3036_CR24_DAC_SOURCE_WORK}, + /* open current source. */ + + /*02*/{RK3036_CODEC_REG22, + RK3036_CR22_DACL_PATH_REFV_WORK|RK3036_CR22_DACR_PATH_REFV_WORK}, + /* power on dac path reference voltage. */ + + /*03*/{RK3036_CODEC_REG27, + RK3036_CR27_HPOUTL_POP_WORK|RK3036_CR27_HPOUTR_POP_WORK}, + /* pop precharge work. */ + + /*04*/{RK3036_CODEC_REG23, + RK3036_CR23_HPOUTL_EN_WORK|RK3036_CR23_HPOUTR_EN_WORK}, + /* start-up HPOUTL HPOUTR */ + + /*05*/{RK3036_CODEC_REG23, + RK3036_CR23_HPOUTL_EN_WORK|RK3036_CR23_HPOUTR_EN_WORK + |RK3036_CR23_HPOUTL_WORK|RK3036_CR23_HPOUTR_WORK}, + /* end the init state of HPOUTL HPOUTR */ + + /*06*/{RK3036_CODEC_REG24, + RK3036_CR24_DAC_SOURCE_WORK + |RK3036_CR24_DAC_PRECHARGE + |RK3036_CR24_DACL_REFV_WORK|RK3036_CR24_DACR_REFV_WORK}, + /* start-up special ref_v of DACL DACR */ + + /*07*/{RK3036_CODEC_REG22, + RK3036_CR22_DACL_PATH_REFV_WORK|RK3036_CR22_DACR_PATH_REFV_WORK + |RK3036_CR22_DACL_CLK_WORK|RK3036_CR22_DACR_CLK_WORK}, + /* start-up clock modul of LR channel */ + + /*08*/{RK3036_CODEC_REG22, + RK3036_CR22_DACL_PATH_REFV_WORK|RK3036_CR22_DACR_PATH_REFV_WORK + |RK3036_CR22_DACL_CLK_WORK|RK3036_CR22_DACR_CLK_WORK + |RK3036_CR22_DACL_WORK|RK3036_CR22_DACR_WORK}, + /* start-up DACL DACR module */ + + /*09*/{RK3036_CODEC_REG27, + RK3036_CR27_HPOUTL_POP_WORK|RK3036_CR27_HPOUTR_POP_WORK + |RK3036_CR27_DACL_WORK|RK3036_CR27_DACR_WORK}, + /* end the init state of DACL DACR */ + + /*10*/{RK3036_CODEC_REG25, OUT_VOLUME}, + /*11*/{RK3036_CODEC_REG26, OUT_VOLUME}, + + /*12*/{RK3036_CODEC_REG24, + RK3036_CR24_DAC_SOURCE_WORK + |RK3036_CR24_DAC_PRECHARGE + |RK3036_CR24_DACL_REFV_WORK|RK3036_CR24_DACR_REFV_WORK + |RK3036_CR24_VOUTL_ZEROD_WORK|RK3036_CR24_VOUTR_ZEROD_WORK}, + /* according to the need, open the zero-crossing detection function. */ + + /*13*/{RK3036_CODEC_REG27, + RK3036_CR27_HPOUTL_POP_WORK|RK3036_CR27_HPOUTR_POP_WORK + |RK3036_CR27_DACL_WORK|RK3036_CR27_DACR_WORK + |RK3036_CR27_HPOUTL_G_WORK|RK3036_CR27_HPOUTR_G_WORK}, + /* end initial status of HPOUTL HPOUTR module,start normal output. */ +}; + +#define PLAYBACK_POWER_UP_LIST_LEN ARRAY_SIZE(playback_power_up_list) + +static int rk3036_codec_power_up(void) +{ + struct snd_soc_codec *codec = rk3036_priv->codec; + int i, volume = 0; + + if (!rk3036_priv || !rk3036_priv->codec) + return -EINVAL; + + for (i = 0; i < PLAYBACK_POWER_UP_LIST_LEN; i++) { + if ((playback_power_up_list[i].reg == + RK3036_CODEC_REG25) && (volume < OUT_VOLUME)) { + snd_soc_write(codec, RK3036_CODEC_REG25, volume); + snd_soc_write(codec, RK3036_CODEC_REG26, volume); + volume++; + mdelay(10); + i--; + } else { + snd_soc_write(codec, playback_power_up_list[i]. + reg, playback_power_up_list[i].value); + mdelay(1); + } + } + + return 0; +} + +#define RK3036_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define RK3036_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + + +static struct snd_soc_dai_ops rk3036_dai_ops = { + .hw_params = rk3036_hw_params, + .set_fmt = rk3036_set_dai_fmt, + .set_sysclk = rk3036_set_dai_sysclk, + .digital_mute = rk3036_digital_mute, +}; + +static struct snd_soc_dai_driver rk3036_dai[] = { + { + .name = "rk3036-voice", + .id = RK3036_VOICE, + .playback = { + .stream_name = "RK3036 CODEC PCM", + .channels_min = 1, + .channels_max = 2, + .rates = RK3036_PLAYBACK_RATES, + .formats = RK3036_FORMATS, + }, + .ops = &rk3036_dai_ops, + }, +}; + +static unsigned int rk3036_codec_read(struct snd_soc_codec * + codec, unsigned int reg) +{ + unsigned int value; + + if (!rk3036_priv) { + dbg_codec(2, "%s : rk3036 is NULL\n", __func__); + return -EINVAL; + } + + if (!rk3036_codec_register(codec, reg)) { + dbg_codec(2, "%s : reg error!\n", __func__); + return -EINVAL; + } + + value = readl_relaxed(rk3036_priv->regbase+reg); + dbg_codec(2, "%s : reg = 0x%x, val= 0x%x\n", __func__, reg, value); + + return value; +} + +static int rk3036_hw_write(const struct i2c_client * + client, const char *buf, int count) +{ + unsigned int reg, value; + + if (!rk3036_priv || !rk3036_priv->codec) + return -EINVAL; + + if (count == 2) { + reg = (unsigned int)buf[0]; + value = (unsigned int)buf[1]; + writel(value, rk3036_priv->regbase+reg); + } else { + dbg_codec(2, "%s : i2c len error\n", __func__); + } + + return count; +} + +static int rk3036_codec_write(struct snd_soc_codec * + codec, unsigned int reg, unsigned int value) +{ + int new_value = -1; + + if (!rk3036_priv) { + dbg_codec(2, "%s : rk3036 is NULL\n", __func__); + return -EINVAL; + } else if (!rk3036_codec_register(codec, reg)) { + dbg_codec(2, "%s : reg error!\n", __func__); + return -EINVAL; + } + + /*new_value = rk3036_set_init_value(codec, reg, value);*/ + if (new_value == -1) { + writel(value, rk3036_priv->regbase+reg); + rk3036_write_reg_cache(codec, reg, value); + } + + return 0; +} + +static void spk_ctrl_fun(int status) +{ + if (rk3036_priv == NULL) + return; + + if (rk3036_priv->spk_ctl_gpio != INVALID_GPIO) + gpio_set_value(rk3036_priv->spk_ctl_gpio, status); +} + +static void rk3036_delayedwork_fun(struct work_struct *work) +{ + if (rk3036_priv == NULL) + return; + dbg_codec(2, "rk3036_delayedwork_fun\n"); + spk_ctrl_fun(SPK_CTRL_OPEN); +} + +static int rk3036_probe(struct snd_soc_codec *codec) +{ + struct rk3036_codec_priv *rk3036_codec + = snd_soc_codec_get_drvdata(codec); + unsigned int val; + int ret; + + rk3036_codec->codec = codec; + + clk_prepare_enable(rk3036_codec->pclk); + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret != 0) + goto err__; + + codec->hw_read = rk3036_codec_read; + codec->hw_write = (hw_write_t)rk3036_hw_write; + codec->read = rk3036_codec_read; + codec->write = rk3036_codec_write; + + INIT_DELAYED_WORK(&rk3036_codec-> + codec_delayed_work, rk3036_delayedwork_fun); + + val = snd_soc_read(codec, RK3036_CODEC_RESET); + if (val != rk3036_reg_defaults[RK3036_CODEC_RESET]) { + ret = -ENODEV; + goto err__; + } + + /* config i2s output to acodec module. */ + val = readl_relaxed(RK_GRF_VIRT + RK3036_GRF_SOC_CON0); + writel_relaxed(val | 0x04000400, RK_GRF_VIRT + RK3036_GRF_SOC_CON0); + + rk3036_reset(codec); + + snd_soc_write(codec, RK3036_CODEC_REG24, RK3036_CR24_DAC_SOURCE_STOP + | RK3036_CR24_DAC_PRECHARGE + | RK3036_CR24_DACL_REFV_STOP + | RK3036_CR24_DACR_REFV_STOP + | RK3036_CR24_VOUTL_ZEROD_STOP + | RK3036_CR24_VOUTR_ZEROD_STOP); +#ifdef WITH_CAP + /*set for capacity output,clear up noise*/ + snd_soc_write(codec, RK3036_CODEC_REG28, 0x37); + mdelay(50); + /*snd_soc_write(codec, 0xbc,0x28);*/ +#endif + + rk3036_codec_power_up(); + + schedule_delayed_work(&rk3036_codec-> + codec_delayed_work, msecs_to_jiffies(5000)); + codec->dapm.bias_level = SND_SOC_BIAS_PREPARE; + + return 0; + +err__: + dbg_codec(2, "%s err ret=%d\n", __func__, ret); + return ret; +} + +static int rk3036_remove(struct snd_soc_codec *codec) +{ + if (!rk3036_priv) { + dbg_codec(2, "%s : rk3036_priv is NULL\n", __func__); + return 0; + } + + spk_ctrl_fun(SPK_CTRL_CLOSE); + + mdelay(10); + snd_soc_write(codec, RK3036_CODEC_RESET, 0xfc); + mdelay(10); + snd_soc_write(codec, RK3036_CODEC_RESET, 0x3); + mdelay(10); + + return 0; +} + +static int rk3036_suspend(struct snd_soc_codec *codec) +{ + rk3036_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int rk3036_resume(struct snd_soc_codec *codec) +{ + rk3036_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +static int rk3036_set_bias_level(struct snd_soc_codec *codec, enum + snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + break; + + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +static int rk3036_volatile_register(struct snd_soc_codec * + codec, unsigned int reg) +{ + switch (reg) { + case RK3036_CODEC_RESET: + return 1; + default: + return 0; + } +} + +static int rk3036_codec_register(struct snd_soc_codec *codec, unsigned int reg) +{ + switch (reg) { + case RK3036_CODEC_RESET: + case RK3036_CODEC_REG03: + case RK3036_CODEC_REG04: + case RK3036_CODEC_REG05: + case RK3036_CODEC_REG22: + case RK3036_CODEC_REG23: + case RK3036_CODEC_REG24: + case RK3036_CODEC_REG25: + case RK3036_CODEC_REG26: + case RK3036_CODEC_REG27: + case RK3036_CODEC_REG28: + return 1; + default: + return 0; + } +} + +static struct snd_soc_codec_driver soc_codec_dev_rk3036 = { + .probe = rk3036_probe, + .remove = rk3036_remove, + .suspend = rk3036_suspend, + .resume = rk3036_resume, + .set_bias_level = rk3036_set_bias_level, + .reg_cache_size = ARRAY_SIZE(rk3036_reg_defaults), + .reg_word_size = sizeof(unsigned int), + .reg_cache_default = rk3036_reg_defaults, + .volatile_register = rk3036_volatile_register, + .readable_register = rk3036_codec_register, + .reg_cache_step = sizeof(unsigned int), +}; + +static int rk3036_platform_probe(struct platform_device *pdev) +{ + struct device_node *rk3036_np = pdev->dev.of_node; + struct rk3036_codec_priv *rk3036; + struct resource *res; + int ret; + + rk3036 = devm_kzalloc(&pdev->dev, sizeof(*rk3036), GFP_KERNEL); + if (!rk3036) { + dbg_codec(2, "%s : rk3036 priv kzalloc failed!\n", __func__); + return -ENOMEM; + } + rk3036_priv = rk3036; + platform_set_drvdata(pdev, rk3036); + + rk3036->spk_ctl_gpio = of_get_named_gpio(rk3036_np, "spk_ctl_io", 0); + if (!gpio_is_valid(rk3036->spk_ctl_gpio)) { + dbg_codec(2, "invalid reset_gpio: %d\n", rk3036->spk_ctl_gpio); + ret = -ENOENT; + goto err__; + } + + ret = devm_gpio_request(&pdev->dev, rk3036->spk_ctl_gpio, "spk_ctl"); + if (ret < 0) { + dbg_codec(2, "rk3036_platform_probe spk_ctl_gpio fail\n"); + goto err__; + } + + gpio_direction_output(rk3036->spk_ctl_gpio, SPK_CTRL_CLOSE); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rk3036->regbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rk3036->regbase)) + return PTR_ERR(rk3036->regbase); + + rk3036->pclk = devm_clk_get(&pdev->dev, "g_pclk_acodec"); + if (IS_ERR(rk3036->pclk)) { + dev_err(&pdev->dev, "Unable to get acodec hclk\n"); + ret = -ENXIO; + goto err__; + } + + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_rk3036, + rk3036_dai, ARRAY_SIZE(rk3036_dai)); + +err__: + platform_set_drvdata(pdev, NULL); + rk3036_priv = NULL; + return ret; +} + +static int rk3036_platform_remove(struct platform_device *pdev) +{ + rk3036_priv = NULL; + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +void rk3036_platform_shutdown(struct platform_device *pdev) +{ + if (!rk3036_priv || !rk3036_priv->codec) + return; + + spk_ctrl_fun(SPK_CTRL_CLOSE); + + mdelay(10); + writel(0xfc, rk3036_priv->regbase+RK3036_CODEC_RESET); + mdelay(10); + writel(0x03, rk3036_priv->regbase+RK3036_CODEC_RESET); +} + +#ifdef CONFIG_OF +static const struct of_device_id rk3036codec_of_match[] = { + { .compatible = "rk3036-codec"}, + {}, +}; +MODULE_DEVICE_TABLE(of, rk3036codec_of_match); +#endif + +static struct platform_driver rk3036_codec_driver = { + .driver = { + .name = "rk3036-codec", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rk3036codec_of_match), + }, + .probe = rk3036_platform_probe, + .remove = rk3036_platform_remove, + .shutdown = rk3036_platform_shutdown, +}; +module_platform_driver(rk3036_codec_driver); + +/* Module information */ +MODULE_AUTHOR("rockchip"); +MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rk3036_codec.h b/sound/soc/codecs/rk3036_codec.h new file mode 100644 index 000000000000..f2bd98103f31 --- /dev/null +++ b/sound/soc/codecs/rk3036_codec.h @@ -0,0 +1,160 @@ +/* + * + * Copyright (C) 2014 rockchip + * + * + * 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 _RK3036_CODEC_H +#define _RK3036_CODEC_H + +/* codec register */ +#define RK3036_CODEC_RESET (0x00 << 2) +#define RK3036_CODEC_REG03 (0x03 << 2) +#define RK3036_CODEC_REG04 (0x04 << 2) +#define RK3036_CODEC_REG05 (0x05 << 2) + +#define RK3036_CODEC_REG22 (0x22 << 2) +#define RK3036_CODEC_REG23 (0x23 << 2) +#define RK3036_CODEC_REG24 (0x24 << 2) +#define RK3036_CODEC_REG25 (0x25 << 2) +#define RK3036_CODEC_REG26 (0x26 << 2) +#define RK3036_CODEC_REG27 (0x27 << 2) +#define RK3036_CODEC_REG28 (0x28 << 2) + +/* RK3036_CODEC_RESET */ +#define RK3036_CR00_DIGITAL_RESET (0 << 1) +#define RK3036_CR00_DIGITAL_WORK (1 << 1) +#define RK3036_CR00_SYSTEM_RESET (0 << 0) +#define RK3036_CR00_SYSTEM_WORK (1 << 0) + +/*RK3036_CODEC_REG03*/ +#define RK3036_CR03_DIRECTION_MASK (1 << 5) +#define RK3036_CR03_DIRECTION_IN (0 << 5) +#define RK3036_CR03_DIRECTION_IOUT (1 << 5) +#define RK3036_CR03_I2SMODE_MASK (1 << 4) +#define RK3036_CR03_I2SMODE_SLAVE (0 << 4) +#define RK3036_CR03_I2SMODE_MASTER (1 << 4) + +/*RK3036_CODEC_REG04*/ +#define RK3036_CR04_I2SLRC_MASK (1 << 7) +#define RK3036_CR04_I2SLRC_NORMAL (0 << 7) +#define RK3036_CR04_I2SLRC_REVERSAL (1 << 7) +#define RK3036_CR04_HFVALID_MASK (3 << 5) +#define RK3036_CR04_HFVALID_16BITS (0 << 5) +#define RK3036_CR04_HFVALID_20BITS (1 << 5) +#define RK3036_CR04_HFVALID_24BITS (2 << 5) +#define RK3036_CR04_HFVALID_32BITS (3 << 5) +#define RK3036_CR04_MODE_MASK (3 << 3) +#define RK3036_CR04_MODE_RIGHT (0 << 3) +#define RK3036_CR04_MODE_LEFT (1 << 3) +#define RK3036_CR04_MODE_I2S (2 << 3) +#define RK3036_CR04_MODE_PCM (3 << 3) +#define RK3036_CR04_LR_SWAP_MASK (1 << 2) +#define RK3036_CR04_LR_SWAP_DIS (0 << 2) +#define RK3036_CR04_LR_SWAP_EN (1 << 2) + +/*RK3036_CODEC_REG05*/ +#define RK3036_CR05_FRAMEH_MASK (3 << 2) +#define RK3036_CR05_FRAMEH_16BITS (0 << 2) +#define RK3036_CR05_FRAMEH_20BITS (1 << 2) +#define RK3036_CR05_FRAMEH_24BITS (2 << 2) +#define RK3036_CR05_FRAMEH_32BITS (3 << 2) +#define RK3036_CR05_DAC_RESET_MASK (1 << 1) +#define RK3036_CR05_DAC_RESET_EN (0 << 1) +#define RK3036_CR05_DAC_RESET_DIS (1 << 1) +#define RK3036_CR05_BCLKPOL_MASK (1 << 0) +#define RK3036_CR05_BCLKPOL_NORMAL (0 << 0) +#define RK3036_CR05_BCLKPOL_REVERSAL (1 << 0) + +/*RK3036_CODEC_REG22*/ +#define RK3036_CR22_DACL_PATH_REFV_MASK (1 << 5) +#define RK3036_CR22_DACL_PATH_REFV_STOP (0 << 5) +#define RK3036_CR22_DACL_PATH_REFV_WORK (1 << 5) +#define RK3036_CR22_DACR_PATH_REFV_MASK (1 << 4) +#define RK3036_CR22_DACR_PATH_REFV_STOP (0 << 4) +#define RK3036_CR22_DACR_PATH_REFV_WORK (1 << 4) +#define RK3036_CR22_DACL_CLK_STOP (0 << 3) +#define RK3036_CR22_DACL_CLK_WORK (1 << 3) +#define RK3036_CR22_DACR_CLK_STOP (0 << 2) +#define RK3036_CR22_DACR_CLK_WORK (1 << 2) +#define RK3036_CR22_DACL_STOP (0 << 1) +#define RK3036_CR22_DACL_WORK (1 << 1) +#define RK3036_CR22_DACR_STOP (0 << 0) +#define RK3036_CR22_DACR_WORK (1 << 0) + +/*RK3036_CODEC_REG23*/ +#define RK3036_CR23_HPOUTL_INIT (0 << 3) +#define RK3036_CR23_HPOUTL_WORK (1 << 3) +#define RK3036_CR23_HPOUTR_INIT (0 << 2) +#define RK3036_CR23_HPOUTR_WORK (1 << 2) +#define RK3036_CR23_HPOUTL_EN_STOP (0 << 1) +#define RK3036_CR23_HPOUTL_EN_WORK (1 << 1) +#define RK3036_CR23_HPOUTR_EN_STOP (0 << 0) +#define RK3036_CR23_HPOUTR_EN_WORK (1 << 0) + +/*RK3036_CODEC_REG24*/ +#define RK3036_CR24_DAC_SOURCE_STOP (0 << 5) +#define RK3036_CR24_DAC_SOURCE_WORK (1 << 5) +#define RK3036_CR24_DAC_PRECHARGE (0 << 4) +#define RK3036_CR24_DAC_DISCHARGE (1 << 4) +#define RK3036_CR24_DACL_REFV_STOP (0 << 3) +#define RK3036_CR24_DACL_REFV_WORK (1 << 3) +#define RK3036_CR24_DACR_REFV_STOP (0 << 2) +#define RK3036_CR24_DACR_REFV_WORK (1 << 2) +#define RK3036_CR24_VOUTL_ZEROD_STOP (0 << 1) +#define RK3036_CR24_VOUTL_ZEROD_WORK (1 << 1) +#define RK3036_CR24_VOUTR_ZEROD_STOP (0 << 0) +#define RK3036_CR24_VOUTR_ZEROD_WORK (1 << 0) + +/*RK3036_CODEC_REG27*/ +#define RK3036_CR27_DACL_INIT (0 << 7) +#define RK3036_CR27_DACL_WORK (1 << 7) +#define RK3036_CR27_DACR_INIT (0 << 6) +#define RK3036_CR27_DACR_WORK (1 << 6) +#define RK3036_CR27_HPOUTL_G_MUTE (0 << 5) +#define RK3036_CR27_HPOUTL_G_WORK (1 << 5) +#define RK3036_CR27_HPOUTR_G_MUTE (0 << 4) +#define RK3036_CR27_HPOUTR_G_WORK (1 << 4) +#define RK3036_CR27_HPOUTL_POP_PRECHARGE (1 << 2) +#define RK3036_CR27_HPOUTL_POP_WORK (2 << 2) +#define RK3036_CR27_HPOUTR_POP_PRECHARGE (1 << 0) +#define RK3036_CR27_HPOUTR_POP_WORK (2 << 0) + +/*RK3036_CODEC_REG28*/ +#define RK3036_CR28_YES_027I (0 << 5) +#define RK3036_CR28_NON_027I (1 << 5) +#define RK3036_CR28_YES_050I (0 << 4) +#define RK3036_CR28_NON_050I (1 << 4) +#define RK3036_CR28_YES_100I (0 << 3) +#define RK3036_CR28_NON_100I (1 << 3) +#define RK3036_CR28_YES_130I (0 << 2) +#define RK3036_CR28_NON_130I (1 << 2) +#define RK3036_CR28_YES_260I (0 << 1) +#define RK3036_CR28_NON_260I (1 << 1) +#define RK3036_CR28_YES_400I (0 << 0) +#define RK3036_CR28_NON_400I (1 << 0) + +enum { + RK3036_HIFI, + RK3036_VOICE, +}; + +struct rk3036_reg_val_typ { + unsigned int reg; + unsigned int value; +}; + +struct rk3036_init_bit_typ { + unsigned int reg; + unsigned int power_bit; + unsigned int init2_bit; + unsigned int init1_bit; + unsigned int init0_bit; +}; + +#endif diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig old mode 100755 new mode 100644 index 50f1edf480e9..df792f1f0cb2 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -20,7 +20,7 @@ choice bool "Set audio support for HDMI" default SND_RK_SOC_HDMI_I2S config SND_RK_SOC_HDMI_I2S - depends on !RK616_USE_MCLK_12M || !SND_RK_SOC_RK616 + depends on (!RK616_USE_MCLK_12M || !SND_RK_SOC_RK616) && !SND_RK_SOC_RK3036 select SND_RK_SOC_I2S select SND_SOC_HDMI_I2S tristate "HDMI use I2S" @@ -240,6 +240,15 @@ config SND_RK_SOC_RK1000 Say Y if you want to add support for SoC audio on rockchip with the RK1000. +config SND_RK_SOC_RK3036 + tristate "SoC I2S Audio support for rockchip - RK3036" + depends on SND_RK_SOC + select SND_RK_SOC_I2S + select SND_SOC_RK3036 + help + Say Y if you want to add support for SoC audio on rockchip + with the RK3036 s40. + config SND_RK_SOC_RK610 tristate "SoC I2S Audio support for rockchip - RK610" depends on SND_RK_SOC && MFD_RK610 diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile old mode 100755 new mode 100644 index 2c731cc1318e..16c7e03069b7 --- a/sound/soc/rockchip/Makefile +++ b/sound/soc/rockchip/Makefile @@ -27,6 +27,7 @@ snd-soc-cs42l52-objs := rk_cs42l52.o snd-soc-aic3111-objs := rk_aic3111.o snd-soc-wm8988-objs := rk_wm8988.o snd-soc-rk1000-objs := rk_rk1000codec.o +snd-soc-rk3036-objs := rk_rk3036.o snd-soc-wm8994-objs := rk_wm8994.o snd-soc-rk610-objs := rk_jetta_codec.o snd-soc-rk616-objs := rk_rk616.o @@ -54,6 +55,7 @@ obj-$(CONFIG_SND_RK_SOC_RT3224) += snd-soc-rt3224.o obj-$(CONFIG_SND_RK_SOC_RT5639) += snd-soc-rt5639.o obj-$(CONFIG_SND_RK_SOC_RT5616) += snd-soc-rt5616.o obj-$(CONFIG_SND_RK_SOC_RK1000) += snd-soc-rk1000.o +obj-$(CONFIG_SND_RK_SOC_RK3036) += snd-soc-rk3036.o obj-$(CONFIG_SND_RK_SOC_CS42L52) += snd-soc-cs42l52.o obj-$(CONFIG_SND_RK_SOC_AIC3111) += snd-soc-aic3111.o obj-$(CONFIG_SND_RK_SOC_AIC3262) += snd-soc-aic3262.o diff --git a/sound/soc/rockchip/rk_rk3036.c b/sound/soc/rockchip/rk_rk3036.c new file mode 100644 index 000000000000..ae413af647c1 --- /dev/null +++ b/sound/soc/rockchip/rk_rk3036.c @@ -0,0 +1,123 @@ +/* +* rk_rk3036codec.c-- SoC audio for rockchip +* +* Driver for rockchip internal rk_rk3036codec audio +* +* 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. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/rk3036_codec.h" +#include "card_info.h" +#include "rk_pcm.h" +#include "rk_i2s.h" + +static int rk30_hw_params(struct snd_pcm_substream * + substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int dai_fmt = rtd->dai_link->dai_fmt; + int ret; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt); + if (ret < 0) + return ret; + + return 0; +} + +/* + * Logic for a rk3036 codec as connected on a rockchip board. + */ +static int rk30_rk3036_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + return 0; +} + +static struct snd_soc_ops rk30_ops = { + .hw_params = rk30_hw_params, +}; + +static struct snd_soc_dai_link rk30_dai[] = { + { + .name = "RK3036", + .stream_name = "RK3036 CODEC PCM", + .codec_dai_name = "rk3036-voice", + .init = rk30_rk3036_codec_init, + .ops = &rk30_ops, + } +}; + +static struct snd_soc_card rockchip_rk3036_snd_card = { + .name = "RK_RK3036", + .dai_link = rk30_dai, + .num_links = 1, +}; + +static int rockchip_rk3036_audio_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card = &rockchip_rk3036_snd_card; + + card->dev = &pdev->dev; + + ret = rockchip_of_get_sound_card_info(card); + if (ret < 0) + return ret; + + ret = snd_soc_register_card(card); + + return ret; +} + +static int rockchip_rk3036_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rockchip_rk3036_of_match[] = { + { .compatible = "rk3036-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_rk3036_of_match); +#endif /* CONFIG_OF */ + +static struct platform_driver rockchip_rk3036_audio_driver = { + .driver = { + .name = "rk3036-audio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rockchip_rk3036_of_match), + }, + .probe = rockchip_rk3036_audio_probe, + .remove = rockchip_rk3036_audio_remove, +}; +module_platform_driver(rockchip_rk3036_audio_driver); + +/* Module information */ +MODULE_AUTHOR("rockchip"); +MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface"); +MODULE_LICENSE("GPL");