summaryrefslogtreecommitdiff
path: root/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c')
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c
new file mode 100644
index 00000000..739a1908
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c
@@ -0,0 +1,374 @@
+/*
+ * sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+
+#include "sprd-asoc-common.h"
+#include "vbc-comm.h"
+
+static DEFINE_SPINLOCK(vbc_lock);
+
+/* vbc local power suppliy and chan on */
+static struct vbc_refcount {
+ atomic_t vbc_power_on; /*vbc reg power enable */
+ atomic_t vbc_on; /*vbc enable */
+ atomic_t chan_on[VBC_IDX_MAX][2];
+} vbc_refcnt;
+
+struct sprd_vbc_src_reg_info {
+ int reg;
+ int clr_bit;
+ int f1f2f3_bp_bit;
+ int f1_sel_bit;
+ int en_bit;
+};
+
+struct sprd_vbc_src_info {
+ struct sprd_vbc_src_reg_info reg_info;
+};
+
+static struct sprd_vbc_src_info vbc_src[VBC_IDX_MAX] = {
+ {{0}},
+ {{ADCSRCCTL, VBADCSRC_CLR_01, VBADCSRC_F1F2F3_BP_01, VBADCSRC_F1_SEL_01,
+ VBADCSRC_EN_01}},
+ {{ADCSRCCTL, VBADCSRC_CLR_23, VBADCSRC_F1F2F3_BP_23, VBADCSRC_F1_SEL_23,
+ VBADCSRC_EN_23}},
+};
+
+static const char *sprd_vbc_name[VBC_IDX_MAX] = {
+ "DAC",
+ "ADC01",
+ "ADC23",
+};
+
+static inline const char *vbc_get_name(int vbc_idx)
+{
+ return sprd_vbc_name[vbc_idx];
+}
+
+static inline int vbc_reg_read(unsigned int reg)
+{
+ return __raw_readl((void *__iomem)(reg - VBC_BASE + sprd_vbc_base));
+}
+
+static inline void vbc_reg_raw_write(unsigned int reg, int val)
+{
+ __raw_writel(val, (void *__iomem)(reg - VBC_BASE + sprd_vbc_base));
+}
+
+static int vbc_reg_write(unsigned int reg, int val)
+{
+ spin_lock(&vbc_lock);
+ vbc_reg_raw_write(reg, val);
+ spin_unlock(&vbc_lock);
+ sp_asoc_pr_reg("VBC:[0x%04x] W:[0x%08x] R:[0x%08x]\n", (reg - VBC_BASE) & 0xFFFF, val,
+ vbc_reg_read(reg));
+ return 0;
+}
+
+/*
+ * Returns 1 for change, 0 for no change, or negative error code.
+ */
+static int vbc_reg_update(unsigned int reg, int val, int mask)
+{
+ int new, old;
+ spin_lock(&vbc_lock);
+ old = vbc_reg_read(reg);
+ new = (old & ~mask) | (val & mask);
+ vbc_reg_raw_write(reg, new);
+ spin_unlock(&vbc_lock);
+ sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new,
+ vbc_reg_read(reg));
+ return old != new;
+}
+
+static struct clk *s_vbc_clk = 0;
+static void vbc_clk_set(struct clk *clk)
+{
+ s_vbc_clk = clk;
+}
+
+static struct clk *vbc_clk_get(void)
+{
+ return s_vbc_clk;
+}
+
+static inline void vbc_reg_enable(void)
+{
+ int ret = 0;
+ if (0/*s_vbc_clk*/) {
+ ret = clk_prepare(s_vbc_clk);
+ WARN(ret != 0, "clk_vbc prepare failed, return %d", ret );
+ ret = clk_enable(s_vbc_clk);
+ WARN(ret != 0, "clk_vbc enable failed, return %d", ret );
+ } else {
+ arch_audio_vbc_reg_enable();
+ }
+}
+
+static inline void vbc_reg_disable(void)
+{
+ if (0/*s_vbc_clk*/) {
+ clk_disable(s_vbc_clk);
+ clk_unprepare(s_vbc_clk);
+ } else {
+ arch_audio_vbc_reg_disable();
+ }
+}
+
+static int vbc_power(int enable)
+{
+ int i;
+ atomic_t *vbc_power_on = &vbc_refcnt.vbc_power_on;
+ if (enable) {
+ atomic_inc(vbc_power_on);
+ if (atomic_read(vbc_power_on) == 1) {
+ arch_audio_vbc_enable();
+ vbc_reg_enable();
+ for (i = 0; i < SPRD_VBC_PLAYBACK_COUNT; i++) {
+ vbc_da_buffer_clear_all(i);
+ }
+ arch_audio_sleep_xtl_enable();
+ arch_audio_memory_sleep_enable();
+ sp_asoc_pr_dbg("VBC Power On\n");
+ }
+ } else {
+ if (atomic_dec_and_test(vbc_power_on)) {
+ arch_audio_vbc_reset();
+ arch_audio_vbc_disable();
+ vbc_reg_disable();
+ arch_audio_sleep_xtl_disable();
+ arch_audio_memory_sleep_disable();
+ sp_asoc_pr_dbg("VBC Power Off\n");
+ }
+ if (atomic_read(vbc_power_on) < 0) {
+ atomic_set(vbc_power_on, 0);
+ }
+ }
+ sp_asoc_pr_dbg("VBC Power REF: %d", atomic_read(vbc_power_on));
+ return 0;
+}
+
+static inline int vbc_da_enable_raw(int enable, int chan)
+{
+ vbc_reg_update(VBCHNEN, ((enable ? 1 : 0) << (VBDACHEN_SHIFT + chan)),
+ (1 << (VBDACHEN_SHIFT + chan)));
+ return 0;
+}
+
+static inline int vbc_ad_enable_raw(int enable, int chan)
+{
+ if (enable) {
+ vbc_reg_update(VBCHNEN, (1 << (VBADCHEN_SHIFT + chan)),
+ (1 << (VBADCHEN_SHIFT + chan)));
+ } else {
+ pr_info("Not close ad%d chan!", (chan ? 1:0));
+ }
+
+ return 0;
+}
+
+static inline int vbc_ad23_enable_raw(int enable, int chan)
+{
+ if (enable) {
+ vbc_reg_update(VBCHNEN, (1 << (VBAD23CHEN_SHIFT + chan)),
+ (1 << (VBAD23CHEN_SHIFT + chan)));
+ } else {
+ pr_info("Not close ad%d chan!", (chan ? 3:2));
+ }
+ return 0;
+}
+
+static inline void vbc_da_eq6_enable(int enable)
+{
+ vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_EQ6_EN) : 0),
+ BIT(VBDAC_EQ6_EN));
+}
+
+static inline void vbc_da_eq4_enable(int enable)
+{
+ vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_EQ4_EN) : 0),
+ BIT(VBDAC_EQ4_EN));
+}
+
+static inline void vbc_da_alc_enable(int enable)
+{
+ vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_ALC_EN) : 0),
+ BIT(VBDAC_ALC_EN));
+}
+
+static inline void vbc_ad01_eq6_enable(int enable)
+{
+ vbc_reg_update(ADHPCTL, (enable ? BIT(VBADC01_EQ6_EN) : 0),
+ BIT(VBADC01_EQ6_EN));
+}
+
+static inline void vbc_ad23_eq6_enable(int enable)
+{
+ vbc_reg_update(ADHPCTL, (enable ? BIT(VBADC23_EQ6_EN) : 0),
+ BIT(VBADC23_EQ6_EN));
+}
+
+static inline int vbc_enable_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBENABLE),
+ (1 << VBENABLE));
+ return 0;
+}
+
+typedef int (*vbc_chan_enable_raw) (int enable, int chan);
+static vbc_chan_enable_raw vbc_chan_enable_fun[VBC_IDX_MAX] = {
+ vbc_da_enable_raw,
+ vbc_ad_enable_raw,
+ vbc_ad23_enable_raw,
+};
+
+static int vbc_chan_enable(int enable, int vbc_idx, int chan)
+{
+ atomic_t *chan_on = &vbc_refcnt.chan_on[vbc_idx][chan];
+ if (enable) {
+ atomic_inc(chan_on);
+ if (atomic_read(chan_on) == 1) {
+ vbc_chan_enable_fun[vbc_idx] (1, chan);
+ sp_asoc_pr_dbg("VBC %s%d On\n", vbc_get_name(vbc_idx),
+ chan);
+ }
+ } else {
+ if (atomic_dec_and_test(chan_on)) {
+ vbc_chan_enable_fun[vbc_idx] (0, chan);
+ sp_asoc_pr_dbg("VBC %s%d Off\n", vbc_get_name(vbc_idx),
+ chan);
+ }
+ if (atomic_read(chan_on) < 0) {
+ atomic_set(chan_on, 0);
+ }
+ }
+ sp_asoc_pr_dbg("%s%d REF: %d", vbc_get_name(vbc_idx), chan,
+ atomic_read(chan_on));
+ return 0;
+}
+
+static DEFINE_MUTEX(vbc_mutex);
+static int vbc_enable(int enable)
+{
+ atomic_t *vbc_on = &vbc_refcnt.vbc_on;
+ mutex_lock(&vbc_mutex);
+ if (enable) {
+ atomic_inc(vbc_on);
+ if (atomic_read(vbc_on) == 1) {
+#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ vbc_da_eq6_enable(1);
+ vbc_da_alc_enable(1);
+#endif
+ /*todo?? */
+ /*vbc_da_eq4_enable(1); */
+ /*vbc_ad01_eq6_enable(1); */
+ /*vbc_ad23_eq6_enable(1); */
+ vbc_enable_set(1);
+ sp_asoc_pr_dbg("VBC Enable\n");
+ }
+ } else {
+ if (atomic_dec_and_test(vbc_on)) {
+ vbc_enable_set(0);
+#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ vbc_da_eq6_enable(0);
+ vbc_da_alc_enable(0);
+#endif
+ /*vbc_da_eq4_enable(0); */
+ /*vbc_ad01_eq6_enable(0); */
+ /*vbc_ad23_eq6_enable(0); */
+ sp_asoc_pr_dbg("VBC Disable");
+ }
+ if (atomic_read(vbc_on) < 0) {
+ atomic_set(vbc_on, 0);
+ }
+ }
+ mutex_unlock(&vbc_mutex);
+
+ sp_asoc_pr_dbg("VBC EN REF: %d", atomic_read(vbc_on));
+ return 0;
+}
+
+static int vbc_src_set(int rate, int vbc_idx)
+{
+ int f1f2f3_bp;
+ int f1_sel;
+ int en_sel;
+ int val;
+ int mask;
+ struct sprd_vbc_src_reg_info *reg_info = &vbc_src[vbc_idx].reg_info;
+
+ if (!vbc_src[vbc_idx].reg_info.reg) {
+ return -EINVAL;
+ }
+
+ sp_asoc_pr_dbg("Rate:%d, Chan: %s", rate, vbc_get_name(vbc_idx));
+
+ /*src_clr */
+ vbc_reg_update(vbc_src[vbc_idx].reg_info.reg,
+ (1 << reg_info->clr_bit), (1 << reg_info->clr_bit));
+ udelay(10);
+ vbc_reg_update(reg_info->reg, 0, (1 << reg_info->clr_bit));
+
+ switch (rate) {
+ case 32000:
+ f1f2f3_bp = 0;
+ f1_sel = 1;
+ en_sel = 1;
+ break;
+ case 48000:
+ f1f2f3_bp = 0;
+ f1_sel = 0;
+ en_sel = 1;
+ break;
+ case 44100:
+ f1f2f3_bp = 1;
+ f1_sel = 0;
+ en_sel = 1;
+ break;
+ default:
+ f1f2f3_bp = 0;
+ f1_sel = 0;
+ en_sel = 0;
+ break;
+ }
+
+ /*src_set */
+ mask = (1 << reg_info->f1f2f3_bp_bit)
+ | (1 << reg_info->f1_sel_bit)
+ | (1 << reg_info->en_bit);
+
+ val = (f1f2f3_bp << reg_info->f1f2f3_bp_bit)
+ | (f1_sel << reg_info->f1_sel_bit)
+ | (en_sel << reg_info->en_bit);
+
+ vbc_reg_update(reg_info->reg, val, mask);
+
+ return 0;
+}