diff options
author | Bhalchandra Gajare <gajare@codeaurora.org> | 2016-06-14 17:26:54 -0700 |
---|---|---|
committer | Bhalchandra Gajare <gajare@codeaurora.org> | 2016-08-17 08:53:21 -0700 |
commit | fab72a97babaffa8683edc87999e64610505a156 (patch) | |
tree | 34fb5bf59fec4d5c761a3283886e6820462ad7a8 /sound | |
parent | 008f057bbab6dd6629b7e1a3b8c67b650a6b9ef1 (diff) |
ASoC: wcd34x-dsp-cntl: Add changes to control wcd dsp
WCD934X audio codec contain DSP to perform signal processing of
audio data. The wcd934x-dsp-cntl is refactored code from WCD934X
audio codec driver to perform control of the wcd dsp. Added changes
to control the clocks, memory, interrupts etc to the DSP.
CRs-Fixed: 1049012
Change-Id: Ic3fe1cebe83531d6cae2db9f12fd2fec0563a298
Signed-off-by: Bhalchandra Gajare <gajare@codeaurora.org>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.c | 961 | ||||
-rw-r--r-- | sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.h | 93 | ||||
-rw-r--r-- | sound/soc/codecs/wcd934x/wcd934x.c | 29 | ||||
-rw-r--r-- | sound/soc/codecs/wcd934x/wcd934x.h | 2 |
4 files changed, 1083 insertions, 2 deletions
diff --git a/sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.c b/sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.c new file mode 100644 index 000000000000..306a4352ca46 --- /dev/null +++ b/sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.c @@ -0,0 +1,961 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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/slab.h> +#include <linux/delay.h> +#include <linux/component.h> +#include <linux/debugfs.h> +#include <linux/mfd/wcd9xxx/core.h> +#include <linux/mfd/wcd9xxx/wcd9xxx-irq.h> +#include <linux/mfd/wcd934x/registers.h> +#include <sound/soc.h> +#include <sound/wcd-dsp-mgr.h> +#include "wcd934x.h" +#include "wcd934x-dsp-cntl.h" + +#define WCD_CNTL_DIR_NAME_LEN_MAX 32 +#define WCD_CPE_FLL_MAX_RETRIES 5 +#define WCD_MEM_ENABLE_MAX_RETRIES 20 +#define WCD_DSP_BOOT_TIMEOUT_MS 3000 +#define WCD_SYSFS_ENTRY_MAX_LEN 8 + +#define WCD_CNTL_MUTEX_LOCK(codec, lock) \ +{ \ + dev_dbg(codec->dev, "mutex_lock(%s)\n", \ + __func__); \ + mutex_lock(&lock); \ +} + +#define WCD_CNTL_MUTEX_UNLOCK(codec, lock) \ +{ \ + dev_dbg(codec->dev, "mutex_unlock(%s)\n",\ + __func__); \ + mutex_unlock(&lock); \ +} + +struct wcd_cntl_attribute { + struct attribute attr; + ssize_t (*show)(struct wcd_dsp_cntl *cntl, char *buf); + ssize_t (*store)(struct wcd_dsp_cntl *cntl, const char *buf, + ssize_t count); +}; + +#define WCD_CNTL_ATTR(_name, _mode, _show, _store) \ +static struct wcd_cntl_attribute cntl_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode}, \ + .show = _show, \ + .store = _store, \ +} + +#define to_wcd_cntl_attr(a) \ + container_of((a), struct wcd_cntl_attribute, attr) + +#define to_wcd_cntl(kobj) \ + container_of((kobj), struct wcd_dsp_cntl, wcd_kobj) + +static u8 mem_enable_values[] = { + 0xFE, 0xFC, 0xF8, 0xF0, + 0xE0, 0xC0, 0x80, 0x00, +}; + +static ssize_t wdsp_boot_show(struct wcd_dsp_cntl *cntl, char *buf) +{ + return snprintf(buf, WCD_SYSFS_ENTRY_MAX_LEN, + "%u", cntl->boot_reqs); +} + +static ssize_t wdsp_boot_store(struct wcd_dsp_cntl *cntl, + const char *buf, ssize_t count) +{ + u32 val; + bool vote; + int ret; + + ret = kstrtou32(buf, 10, &val); + if (ret) { + dev_err(cntl->codec->dev, + "%s: Invalid entry, ret = %d\n", __func__, ret); + return -EINVAL; + } + + if (val > 0) { + cntl->boot_reqs++; + vote = true; + } else { + cntl->boot_reqs--; + vote = false; + } + + if (cntl->m_dev && cntl->m_ops && + cntl->m_ops->vote_for_dsp) + ret = cntl->m_ops->vote_for_dsp(cntl->m_dev, vote); + else + ret = -EINVAL; + + if (IS_ERR_VALUE(ret)) + dev_err(cntl->codec->dev, + "%s: failed to %s dsp\n", __func__, + vote ? "enable" : "disable"); + return count; +} + +WCD_CNTL_ATTR(boot, 0660, wdsp_boot_show, wdsp_boot_store); + +static ssize_t wcd_cntl_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct wcd_cntl_attribute *wcd_attr = to_wcd_cntl_attr(attr); + struct wcd_dsp_cntl *cntl = to_wcd_cntl(kobj); + ssize_t ret = -EINVAL; + + if (cntl && wcd_attr->show) + ret = wcd_attr->show(cntl, buf); + + return ret; +} + +static ssize_t wcd_cntl_sysfs_store(struct kobject *kobj, + struct attribute *attr, const char *buf, + size_t count) +{ + struct wcd_cntl_attribute *wcd_attr = to_wcd_cntl_attr(attr); + struct wcd_dsp_cntl *cntl = to_wcd_cntl(kobj); + ssize_t ret = -EINVAL; + + if (cntl && wcd_attr->store) + ret = wcd_attr->store(cntl, buf, count); + + return ret; +} + +static const struct sysfs_ops wcd_cntl_sysfs_ops = { + .show = wcd_cntl_sysfs_show, + .store = wcd_cntl_sysfs_store, +}; + +static struct kobj_type wcd_cntl_ktype = { + .sysfs_ops = &wcd_cntl_sysfs_ops, +}; + +static int wcd_cntl_cpe_fll_calibrate(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + int ret = 0, retry = 0; + u8 cal_lsb, cal_msb; + u8 lock_det; + + /* Make sure clocks are gated */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPE_CTL, + 0x05, 0x00); + + /* Enable CPE FLL reference clock */ + snd_soc_update_bits(codec, WCD934X_CLK_SYS_MCLK2_PRG1, + 0x80, 0x80); + + snd_soc_update_bits(codec, WCD934X_CPE_FLL_USER_CTL_5, + 0xF3, 0x13); + snd_soc_write(codec, WCD934X_CPE_FLL_L_VAL_CTL_0, 0x50); + + /* Disable CPAR reset and Enable CPAR clk */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPAR_CTL, + 0x02, 0x02); + + /* Write calibration l-value based on cdc clk rate */ + if (cntl->clk_rate == 9600000) { + cal_lsb = 0x6d; + cal_msb = 0x00; + } else { + cal_lsb = 0x56; + cal_msb = 0x00; + } + snd_soc_write(codec, WCD934X_CPE_FLL_USER_CTL_6, cal_lsb); + snd_soc_write(codec, WCD934X_CPE_FLL_USER_CTL_7, cal_msb); + + /* FLL mode to follow power up sequence */ + snd_soc_update_bits(codec, WCD934X_CPE_FLL_FLL_MODE, + 0x60, 0x00); + + /* HW controlled CPE FLL */ + snd_soc_update_bits(codec, WCD934X_CPE_FLL_FLL_MODE, + 0x80, 0x80); + + /* Force on CPE FLL */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPAR_CFG, + 0x04, 0x04); + + do { + /* Time for FLL calibration to complete */ + usleep_range(1000, 1100); + lock_det = snd_soc_read(codec, WCD934X_CPE_FLL_STATUS_3); + retry++; + } while (!(lock_det & 0x01) && + retry <= WCD_CPE_FLL_MAX_RETRIES); + + if (!(lock_det & 0x01)) { + dev_err(codec->dev, "%s: lock detect not set, 0x%02x\n", + __func__, lock_det); + ret = -EIO; + goto err_lock_det; + } + + snd_soc_update_bits(codec, WCD934X_CPE_FLL_FLL_MODE, + 0x60, 0x20); + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPAR_CFG, + 0x04, 0x00); + return ret; + +err_lock_det: + /* Undo the register settings */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPAR_CFG, + 0x04, 0x00); + snd_soc_update_bits(codec, WCD934X_CPE_FLL_FLL_MODE, + 0x80, 0x00); + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPAR_CTL, + 0x02, 0x00); + return ret; +} + +static void wcd_cntl_config_cpar(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + u8 nom_lo, nom_hi, svs2_lo, svs2_hi; + + /* Configure CPAR */ + nom_hi = svs2_hi = 0; + if (cntl->clk_rate == 9600000) { + nom_lo = 0x90; + svs2_lo = 0x50; + } else { + nom_lo = 0x70; + svs2_lo = 0x3e; + } + + snd_soc_write(codec, WCD934X_TEST_DEBUG_LVAL_NOM_LOW, nom_lo); + snd_soc_write(codec, WCD934X_TEST_DEBUG_LVAL_NOM_HIGH, nom_hi); + snd_soc_write(codec, WCD934X_TEST_DEBUG_LVAL_SVS_SVS2_LOW, svs2_lo); + snd_soc_write(codec, WCD934X_TEST_DEBUG_LVAL_SVS_SVS2_HIGH, svs2_hi); + + snd_soc_update_bits(codec, WCD934X_CPE_SS_PWR_CPEFLL_CTL, + 0x03, 0x03); +} + +static int wcd_cntl_cpe_fll_ctrl(struct wcd_dsp_cntl *cntl, + bool enable) +{ + struct snd_soc_codec *codec = cntl->codec; + int ret = 0; + + if (enable) { + ret = wcd_cntl_cpe_fll_calibrate(cntl); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: cpe_fll_cal failed, err = %d\n", + __func__, ret); + goto done; + } + + wcd_cntl_config_cpar(cntl); + + /* Enable AHB CLK and CPE CLK*/ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPE_CTL, + 0x05, 0x05); + } else { + /* Disable AHB CLK and CPE CLK */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPE_CTL, + 0x05, 0x00); + /* Reset the CPAR mode for CPE FLL */ + snd_soc_write(codec, WCD934X_CPE_FLL_FLL_MODE, 0x20); + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPAR_CFG, + 0x04, 0x00); + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPAR_CTL, + 0x02, 0x00); + } +done: + return ret; +} + +static int wcd_cntl_clocks_enable(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + int ret; + + WCD_CNTL_MUTEX_LOCK(codec, cntl->clk_mutex); + /* Enable codec clock */ + if (cntl->cdc_cb && cntl->cdc_cb->cdc_clk_en) + ret = cntl->cdc_cb->cdc_clk_en(codec, true); + else + ret = -EINVAL; + + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: Failed to enable cdc clk, err = %d\n", + __func__, ret); + goto done; + } + + /* Configure and Enable CPE FLL clock */ + ret = wcd_cntl_cpe_fll_ctrl(cntl, true); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: Failed to enable cpe clk, err = %d\n", + __func__, ret); + goto err_cpe_clk; + } + cntl->is_clk_enabled = true; + + /* Ungate the CPR clock */ + snd_soc_update_bits(codec, WCD934X_CODEC_RPM_CLK_GATE, 0x10, 0x00); +done: + WCD_CNTL_MUTEX_UNLOCK(codec, cntl->clk_mutex); + return ret; + +err_cpe_clk: + if (cntl->cdc_cb && cntl->cdc_cb->cdc_clk_en) + cntl->cdc_cb->cdc_clk_en(codec, false); + + WCD_CNTL_MUTEX_UNLOCK(codec, cntl->clk_mutex); + return ret; +} + +static int wcd_cntl_clocks_disable(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + int ret = 0; + + WCD_CNTL_MUTEX_LOCK(codec, cntl->clk_mutex); + if (!cntl->is_clk_enabled) { + dev_info(codec->dev, "%s: clocks already disabled\n", + __func__); + goto done; + } + + /* Gate the CPR clock */ + snd_soc_update_bits(codec, WCD934X_CODEC_RPM_CLK_GATE, 0x10, 0x10); + + /* Disable CPE FLL clock */ + ret = wcd_cntl_cpe_fll_ctrl(cntl, false); + if (IS_ERR_VALUE(ret)) + dev_err(codec->dev, + "%s: Failed to disable cpe clk, err = %d\n", + __func__, ret); + + /* + * Even if CPE FLL disable failed, go ahead and disable + * the codec clock + */ + if (cntl->cdc_cb && cntl->cdc_cb->cdc_clk_en) + ret = cntl->cdc_cb->cdc_clk_en(codec, false); + else + ret = -EINVAL; + + cntl->is_clk_enabled = false; +done: + WCD_CNTL_MUTEX_UNLOCK(codec, cntl->clk_mutex); + return ret; +} + +static void wcd_cntl_cpar_ctrl(struct wcd_dsp_cntl *cntl, + bool enable) +{ + struct snd_soc_codec *codec = cntl->codec; + + if (enable) + snd_soc_write(codec, WCD934X_CPE_SS_CPAR_CTL, 0x03); + else + snd_soc_write(codec, WCD934X_CPE_SS_CPAR_CTL, 0x00); +} + +static int wcd_cntl_enable_memory(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + int loop_cnt = 0; + u8 status; + int ret = 0; + + snd_soc_update_bits(codec, WCD934X_CPE_SS_SOC_SW_COLLAPSE_CTL, + 0x04, 0x00); + snd_soc_update_bits(codec, WCD934X_TEST_DEBUG_MEM_CTRL, + 0x80, 0x80); + snd_soc_update_bits(codec, WCD934X_CPE_SS_SOC_SW_COLLAPSE_CTL, + 0x01, 0x01); + + do { + loop_cnt++; + /* Time to enable the power domain for memory */ + usleep_range(100, 150); + status = snd_soc_read(codec, + WCD934X_CPE_SS_SOC_SW_COLLAPSE_CTL); + } while ((status & 0x02) != 0x02 && + loop_cnt != WCD_MEM_ENABLE_MAX_RETRIES); + + if ((status & 0x02) != 0x02) { + dev_err(cntl->codec->dev, + "%s: power domain not enabled, status = 0x%02x\n", + __func__, status); + ret = -EIO; + goto done; + } + + /* 512KB of always on region */ + wcd9xxx_slim_write_repeat(wcd9xxx, + WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_0, + ARRAY_SIZE(mem_enable_values), + mem_enable_values); + wcd9xxx_slim_write_repeat(wcd9xxx, + WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_1, + ARRAY_SIZE(mem_enable_values), + mem_enable_values); + + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_DRAM1_SHUTDOWN, 0x05); + + /* Rest of the memory */ + wcd9xxx_slim_write_repeat(wcd9xxx, + WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_2, + ARRAY_SIZE(mem_enable_values), + mem_enable_values); + wcd9xxx_slim_write_repeat(wcd9xxx, + WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_3, + ARRAY_SIZE(mem_enable_values), + mem_enable_values); + + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_SYSMEM_DEEPSLP_0, 0x05); +done: + return ret; +} + +static void wcd_cntl_disable_memory(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_SYSMEM_DEEPSLP_0, 0xFF); + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_3, 0xFF); + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_2, 0xFF); + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_DRAM1_SHUTDOWN, 0x07); + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_1, 0xFF); + snd_soc_write(codec, WCD934X_CPE_SS_PWR_CPE_SYSMEM_SHUTDOWN_0, 0xFF); + + snd_soc_update_bits(codec, WCD934X_CPE_SS_SOC_SW_COLLAPSE_CTL, + 0x01, 0x00); + snd_soc_update_bits(codec, WCD934X_TEST_DEBUG_MEM_CTRL, + 0x80, 0x00); + snd_soc_update_bits(codec, WCD934X_CPE_SS_SOC_SW_COLLAPSE_CTL, + 0x04, 0x04); +} + +static void wcd_cntl_do_shutdown(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + + /* Disable WDOG */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_WDOG_CFG, + 0x3F, 0x01); + snd_soc_update_bits(codec, WCD934X_CODEC_RPM_CLK_MCLK_CFG, + 0x04, 0x00); + + /* Put WDSP in reset state */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPE_CTL, + 0x02, 0x00); +} + +static int wcd_cntl_do_boot(struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + int ret = 0; + + /* + * Debug mode is set from debugfs file node. If debug_mode + * is set, then do not configure the watchdog timer. This + * will be required for debugging the DSP firmware. + */ + if (cntl->debug_mode) { + snd_soc_update_bits(codec, WCD934X_CPE_SS_WDOG_CFG, + 0x3F, 0x01); + snd_soc_update_bits(codec, WCD934X_CODEC_RPM_CLK_MCLK_CFG, + 0x04, 0x00); + } else { + snd_soc_update_bits(codec, WCD934X_CODEC_RPM_CLK_MCLK_CFG, + 0x04, 0x04); + snd_soc_update_bits(codec, WCD934X_CPE_SS_WDOG_CFG, + 0x3F, 0x21); + } + + /* Make sure all the error interrupts are cleared */ + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_CLEAR_0A, 0xFF); + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_CLEAR_0B, 0xFF); + + reinit_completion(&cntl->boot_complete); + + /* Remove WDSP out of reset */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_CPE_CTL, + 0x02, 0x02); + + /* + * In debug mode, DSP may not boot up normally, + * wait indefinitely for DSP to boot. + */ + if (cntl->debug_mode) { + wait_for_completion(&cntl->boot_complete); + dev_dbg(codec->dev, "%s: WDSP booted in dbg mode\n", __func__); + goto done; + } + + /* Boot in normal mode */ + ret = wait_for_completion_timeout(&cntl->boot_complete, + msecs_to_jiffies(WCD_DSP_BOOT_TIMEOUT_MS)); + if (!ret) { + dev_err(codec->dev, "%s: WDSP boot timed out\n", + __func__); + ret = -ETIMEDOUT; + goto err_boot; + } + + dev_dbg(codec->dev, "%s: WDSP booted in normal mode\n", __func__); + + /* Enable WDOG */ + snd_soc_update_bits(codec, WCD934X_CPE_SS_WDOG_CFG, + 0x10, 0x10); +done: + return ret; +err_boot: + /* call shutdown to perform cleanup */ + wcd_cntl_do_shutdown(cntl); + return ret; +} + +static irqreturn_t wcd_cntl_ipc_irq(int irq, void *data) +{ + struct wcd_dsp_cntl *cntl = data; + int ret; + + complete(&cntl->boot_complete); + + if (cntl->m_dev && cntl->m_ops && + cntl->m_ops->intr_handler) + ret = cntl->m_ops->intr_handler(cntl->m_dev, WDSP_IPC1_INTR); + else + ret = -EINVAL; + + if (IS_ERR_VALUE(ret)) + dev_err(cntl->codec->dev, + "%s: Failed to handle irq %d\n", __func__, irq); + + return IRQ_HANDLED; +} + +static irqreturn_t wcd_cntl_err_irq(int irq, void *data) +{ + struct wcd_dsp_cntl *cntl = data; + struct snd_soc_codec *codec = cntl->codec; + u16 status = 0; + u8 reg_val; + + reg_val = snd_soc_read(codec, WCD934X_CPE_SS_SS_ERROR_INT_STATUS_0A); + status = status | reg_val; + + reg_val = snd_soc_read(codec, WCD934X_CPE_SS_SS_ERROR_INT_STATUS_0B); + status = status | (reg_val << 8); + + dev_info(codec->dev, "%s: error interrupt status = 0x%x\n", + __func__, status); + + return IRQ_HANDLED; +} + +static int wcd_control_handler(struct device *dev, void *priv_data, + enum wdsp_event_type event, void *data) +{ + struct wcd_dsp_cntl *cntl = priv_data; + struct snd_soc_codec *codec = cntl->codec; + int ret = 0; + + switch (event) { + case WDSP_EVENT_POST_DLOAD_CODE: + case WDSP_EVENT_DLOAD_FAILED: + case WDSP_EVENT_POST_SHUTDOWN: + + /* Disable CPAR */ + wcd_cntl_cpar_ctrl(cntl, false); + /* Disable all the clocks */ + ret = wcd_cntl_clocks_disable(cntl); + if (IS_ERR_VALUE(ret)) + dev_err(codec->dev, + "%s: Failed to disable clocks, err = %d\n", + __func__, ret); + break; + + case WDSP_EVENT_PRE_DLOAD_CODE: + + wcd_cntl_enable_memory(cntl); + break; + + case WDSP_EVENT_PRE_DLOAD_DATA: + + /* Enable all the clocks */ + ret = wcd_cntl_clocks_enable(cntl); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: Failed to enable clocks, err = %d\n", + __func__, ret); + goto done; + } + + /* Enable CPAR */ + wcd_cntl_cpar_ctrl(cntl, true); + break; + + case WDSP_EVENT_DO_BOOT: + + ret = wcd_cntl_do_boot(cntl); + if (IS_ERR_VALUE(ret)) + dev_err(codec->dev, + "%s: WDSP boot failed, err = %d\n", + __func__, ret); + break; + + case WDSP_EVENT_DO_SHUTDOWN: + + wcd_cntl_do_shutdown(cntl); + break; + + default: + dev_dbg(codec->dev, "%s: unhandled event %d\n", + __func__, event); + } + +done: + return ret; +} + +static int wcd_cntl_sysfs_init(char *dir, struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + int ret = 0; + + ret = kobject_init_and_add(&cntl->wcd_kobj, &wcd_cntl_ktype, + kernel_kobj, dir); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: Failed to add kobject %s, err = %d\n", + __func__, dir, ret); + goto done; + } + + ret = sysfs_create_file(&cntl->wcd_kobj, &cntl_attr_boot.attr); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: Failed to add wdsp_boot sysfs entry to %s\n", + __func__, dir); + goto fail_create_file; + } + + return ret; + +fail_create_file: + kobject_put(&cntl->wcd_kobj); +done: + return ret; +} + +static void wcd_cntl_sysfs_remove(struct wcd_dsp_cntl *cntl) +{ + sysfs_remove_file(&cntl->wcd_kobj, &cntl_attr_boot.attr); + kobject_put(&cntl->wcd_kobj); +} + +static void wcd_cntl_debugfs_init(char *dir, struct wcd_dsp_cntl *cntl) +{ + struct snd_soc_codec *codec = cntl->codec; + + cntl->entry = debugfs_create_dir(dir, NULL); + if (IS_ERR_OR_NULL(dir)) { + dev_err(codec->dev, "%s debugfs_create_dir failed for %s\n", + __func__, dir); + goto done; + } + + debugfs_create_u32("debug_mode", S_IRUGO | S_IWUSR, + cntl->entry, &cntl->debug_mode); +done: + return; +} + +static void wcd_cntl_debugfs_remove(struct wcd_dsp_cntl *cntl) +{ + if (cntl) + debugfs_remove(cntl->entry); +} + +static int wcd_control_init(struct device *dev, void *priv_data) +{ + struct wcd_dsp_cntl *cntl = priv_data; + struct snd_soc_codec *codec = cntl->codec; + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + struct wcd9xxx_core_resource *core_res = &wcd9xxx->core_res; + char wcd_cntl_dir_name[WCD_CNTL_DIR_NAME_LEN_MAX]; + int ret, ret1; + bool err_irq_requested = false; + + ret = wcd9xxx_request_irq(core_res, + cntl->irqs.cpe_ipc1_irq, + wcd_cntl_ipc_irq, "CPE IPC1", + cntl); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: Failed to request cpe ipc irq, err = %d\n", + __func__, ret); + goto done; + } + + /* Unmask the fatal irqs */ + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_MASK_0A, + ~(cntl->irqs.fatal_irqs & 0xFF)); + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_MASK_0B, + ~((cntl->irqs.fatal_irqs >> 8) & 0xFF)); + + /* + * CPE ERR irq is used only for error reporting from WCD DSP, + * even if this request fails, DSP can be function normally. + * Continuing with init even if the CPE ERR irq request fails. + */ + if (wcd9xxx_request_irq(core_res, cntl->irqs.cpe_err_irq, + wcd_cntl_err_irq, "CPE ERR", cntl)) + dev_info(codec->dev, "%s: Failed request_irq(cpe_err_irq)", + __func__); + else + err_irq_requested = true; + + + /* Enable all the clocks */ + ret = wcd_cntl_clocks_enable(cntl); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, "%s: Failed to enable clocks, err = %d\n", + __func__, ret); + goto err_clk_enable; + } + wcd_cntl_cpar_ctrl(cntl, true); + + snprintf(wcd_cntl_dir_name, WCD_CNTL_DIR_NAME_LEN_MAX, + "%s%d", "wdsp", cntl->dsp_instance); + wcd_cntl_debugfs_init(wcd_cntl_dir_name, cntl); + ret = wcd_cntl_sysfs_init(wcd_cntl_dir_name, cntl); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, + "%s: Failed to init sysfs %d\n", + __func__, ret); + goto err_sysfs_init; + } + + return 0; + +err_sysfs_init: + wcd_cntl_cpar_ctrl(cntl, false); + ret1 = wcd_cntl_clocks_disable(cntl); + if (IS_ERR_VALUE(ret1)) + dev_err(codec->dev, "%s: Failed to disable clocks, err = %d\n", + __func__, ret1); +err_clk_enable: + /* Mask all error interrupts */ + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_MASK_0A, 0xFF); + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_MASK_0B, 0xFF); + + /* Free the irq's requested */ + wcd9xxx_free_irq(core_res, cntl->irqs.cpe_ipc1_irq, cntl); + + if (err_irq_requested) + wcd9xxx_free_irq(core_res, cntl->irqs.cpe_err_irq, cntl); +done: + return ret; +} + +static int wcd_control_deinit(struct device *dev, void *priv_data) +{ + struct wcd_dsp_cntl *cntl = priv_data; + struct snd_soc_codec *codec = cntl->codec; + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + struct wcd9xxx_core_resource *core_res = &wcd9xxx->core_res; + + /* Remove the sysfs entries */ + wcd_cntl_sysfs_remove(cntl); + + /* Remove the debugfs entries */ + wcd_cntl_debugfs_remove(cntl); + + wcd_cntl_clocks_disable(cntl); + wcd_cntl_cpar_ctrl(cntl, false); + + /* Mask all error interrupts */ + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_MASK_0A, 0xFF); + snd_soc_write(codec, WCD934X_CPE_SS_SS_ERROR_INT_MASK_0B, 0xFF); + + /* Free the irq's requested */ + wcd9xxx_free_irq(core_res, cntl->irqs.cpe_err_irq, cntl); + wcd9xxx_free_irq(core_res, cntl->irqs.cpe_ipc1_irq, cntl); + + return 0; +} + +static struct wdsp_cmpnt_ops control_ops = { + .init = wcd_control_init, + .deinit = wcd_control_deinit, + .event_handler = wcd_control_handler, +}; + +static int wcd_ctrl_component_bind(struct device *dev, + struct device *master, + void *data) +{ + struct wcd_dsp_cntl *cntl; + int ret = 0; + + if (!dev || !master || !data) { + pr_err("%s: Invalid parameters\n", __func__); + return -EINVAL; + } + + cntl = tavil_get_wcd_dsp_cntl(dev); + if (!cntl) { + dev_err(dev, "%s: Failed to get cntl reference\n", + __func__); + return -EINVAL; + } + + cntl->m_dev = master; + cntl->m_ops = data; + + if (cntl->m_ops->register_cmpnt_ops) + ret = cntl->m_ops->register_cmpnt_ops(master, dev, cntl, + &control_ops); + + if (ret) + dev_err(dev, "%s: register_cmpnt_ops failed, err = %d\n", + __func__, ret); + return ret; +} + +static void wcd_ctrl_component_unbind(struct device *dev, + struct device *master, + void *data) +{ + struct wcd_dsp_cntl *cntl; + + if (!dev) { + pr_err("%s: Invalid device\n", __func__); + return; + } + + cntl = tavil_get_wcd_dsp_cntl(dev); + if (!cntl) { + dev_err(dev, "%s: Failed to get cntl reference\n", + __func__); + return; + } + + cntl->m_dev = NULL; + cntl->m_ops = NULL; +} + +static const struct component_ops wcd_ctrl_component_ops = { + .bind = wcd_ctrl_component_bind, + .unbind = wcd_ctrl_component_unbind, +}; + +/* + * wcd_dsp_cntl_init: Initialize the wcd-dsp control + * @codec: pointer to the codec handle + * @params: Parameters required to initialize wcd-dsp control + * + * This API is expected to be invoked by the codec driver and + * provide information essential for the wcd dsp control to + * configure and initialize the dsp + */ +void wcd_dsp_cntl_init(struct snd_soc_codec *codec, + struct wcd_dsp_params *params, + struct wcd_dsp_cntl **cntl) +{ + struct wcd_dsp_cntl *control; + int ret; + + if (!codec || !params) { + pr_err("%s: Invalid handle to %s\n", __func__, + (!codec) ? "codec" : "params"); + *cntl = NULL; + return; + } + + if (*cntl) { + pr_err("%s: cntl is non NULL, maybe already initialized ?\n", + __func__); + return; + } + + control = kzalloc(sizeof(*control), GFP_KERNEL); + if (!(control)) + return; + + control->codec = codec; + control->clk_rate = params->clk_rate; + control->cdc_cb = params->cb; + control->dsp_instance = params->dsp_instance; + memcpy(&control->irqs, ¶ms->irqs, sizeof(control->irqs)); + init_completion(&control->boot_complete); + mutex_init(&control->clk_mutex); + + /* + * If this is the last component needed by master to be ready, + * then component_bind will be called within the component_add. + * Hence, the data pointer should be assigned before component_add, + * so that we can access it during this component's bind call. + */ + *cntl = control; + ret = component_add(codec->dev, &wcd_ctrl_component_ops); + if (ret) { + dev_err(codec->dev, "%s: component_add failed, err = %d\n", + __func__, ret); + kfree(*cntl); + *cntl = NULL; + } +} +EXPORT_SYMBOL(wcd_dsp_cntl_init); + +/* + * wcd_dsp_cntl_deinit: De-initialize the wcd-dsp control + * @cntl: The struct wcd_dsp_cntl to de-initialize + * + * This API is intended to be invoked by the codec driver + * to de-initialize the wcd dsp control + */ +void wcd_dsp_cntl_deinit(struct wcd_dsp_cntl **cntl) +{ + struct wcd_dsp_cntl *control = *cntl; + struct snd_soc_codec *codec; + + /* If control is NULL, there is nothing to de-initialize */ + if (!control) + return; + codec = control->codec; + + /* + * Calling shutdown will cleanup all register states, + * irrespective of DSP was booted up or not. + */ + wcd_cntl_do_shutdown(control); + wcd_cntl_disable_memory(control); + + component_del(codec->dev, &wcd_ctrl_component_ops); + + mutex_destroy(&control->clk_mutex); + kfree(*cntl); + *cntl = NULL; +} +EXPORT_SYMBOL(wcd_dsp_cntl_deinit); diff --git a/sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.h b/sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.h new file mode 100644 index 000000000000..caa7edc02da8 --- /dev/null +++ b/sound/soc/codecs/wcd934x/wcd934x-dsp-cntl.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef __WCD934X_DSP_CNTL_H__ +#define __WCD934X_DSP_CNTL_H__ + +#include <sound/soc.h> +#include <sound/wcd-dsp-mgr.h> + +struct wcd_dsp_cdc_cb { + /* Callback to enable codec clock */ + int (*cdc_clk_en)(struct snd_soc_codec *, bool); +}; + +struct wcd_dsp_irq_info { + /* IPC interrupt */ + int cpe_ipc1_irq; + + /* CPE error summary interrupt */ + int cpe_err_irq; + + /* + * Bit mask to indicate which of the + * error interrupts are to be considered + * as fatal. + */ + u16 fatal_irqs; +}; + +struct wcd_dsp_params { + struct wcd_dsp_cdc_cb *cb; + struct wcd_dsp_irq_info irqs; + + /* Rate at which the codec clock operates */ + u32 clk_rate; + + /* + * Represents the dsp instance, will be used + * to create sysfs and debugfs entries with + * directory wdsp<dsp-instance> + */ + u32 dsp_instance; +}; + +struct wcd_dsp_cntl { + /* Handle to codec */ + struct snd_soc_codec *codec; + + /* Clk rate of the codec clock */ + u32 clk_rate; + + /* Callbacks to codec driver */ + const struct wcd_dsp_cdc_cb *cdc_cb; + + /* Completion to indicate WDSP boot done */ + struct completion boot_complete; + + struct wcd_dsp_irq_info irqs; + u32 dsp_instance; + + /* Sysfs entries related */ + int boot_reqs; + struct kobject wcd_kobj; + + /* Debugfs related */ + struct dentry *entry; + u32 debug_mode; + + /* WDSP manager drivers data */ + struct device *m_dev; + struct wdsp_mgr_ops *m_ops; + + /* clk related */ + struct mutex clk_mutex; + bool is_clk_enabled; +}; + +void wcd_dsp_cntl_init(struct snd_soc_codec *codec, + struct wcd_dsp_params *params, + struct wcd_dsp_cntl **cntl); +void wcd_dsp_cntl_deinit(struct wcd_dsp_cntl **cntl); + +#endif /* end __WCD_DSP_CONTROL_H__ */ diff --git a/sound/soc/codecs/wcd934x/wcd934x.c b/sound/soc/codecs/wcd934x/wcd934x.c index 8c2387dc194b..bd116ee8685c 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.c +++ b/sound/soc/codecs/wcd934x/wcd934x.c @@ -1,5 +1,4 @@ -/* - * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -479,6 +478,9 @@ struct tavil_priv { struct tx_mute_work tx_mute_dwork[WCD934X_NUM_DECIMATORS]; unsigned int vi_feed_value; + + /* DSP control */ + struct wcd_dsp_cntl *wdsp_cntl; }; static const struct tavil_reg_mask_val tavil_spkr_default[] = { @@ -5949,6 +5951,29 @@ static int __tavil_enable_efuse_sensing(struct tavil_priv *tavil) return rc; } +/* + * tavil_get_wcd_dsp_cntl: Get the reference to wcd_dsp_cntl + * @dev: Device pointer for codec device + * + * This API gets the reference to codec's struct wcd_dsp_cntl + */ +struct wcd_dsp_cntl *tavil_get_wcd_dsp_cntl(struct device *dev) +{ + struct platform_device *pdev; + struct tavil_priv *tavil; + + if (!dev) { + pr_err("%s: Invalid device\n", __func__); + return NULL; + } + + pdev = to_platform_device(dev); + tavil = platform_get_drvdata(pdev); + + return tavil->wdsp_cntl; +} +EXPORT_SYMBOL(tavil_get_wcd_dsp_cntl); + static int tavil_probe(struct platform_device *pdev) { int ret = 0; diff --git a/sound/soc/codecs/wcd934x/wcd934x.h b/sound/soc/codecs/wcd934x/wcd934x.h index f21b72d71ddb..c8a5328f5a72 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.h +++ b/sound/soc/codecs/wcd934x/wcd934x.h @@ -15,6 +15,7 @@ #include <sound/apr_audio-v2.h> #include <linux/mfd/wcd9xxx/wcd9xxx-slimslave.h> +#include "wcd934x-dsp-cntl.h" #define WCD934X_REGISTER_START_OFFSET 0x800 #define WCD934X_SB_PGD_PORT_RX_BASE 0x40 @@ -144,4 +145,5 @@ extern void *tavil_get_afe_config(struct snd_soc_codec *codec, extern int tavil_cdc_mclk_enable(struct snd_soc_codec *codec, bool enable); extern int tavil_set_spkr_mode(struct snd_soc_codec *codec, int mode); extern int tavil_set_spkr_gain_offset(struct snd_soc_codec *codec, int offset); +extern struct wcd_dsp_cntl *tavil_get_wcd_dsp_cntl(struct device *dev); #endif |