1445 lines
30 KiB
C
1445 lines
30 KiB
C
/* sound/soc/samsung/lpass.c
|
|
*
|
|
* Low Power Audio SubSystem driver for Samsung Exynos
|
|
*
|
|
* Copyright (c) 2013 Samsung Electronics Co. Ltd.
|
|
* Yeongman Seo <yman.seo@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/err.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_qos.h>
|
|
#include <linux/fb.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/exynos_iovmm.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/rt.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include <asm/tlbflush.h>
|
|
|
|
#include <sound/exynos.h>
|
|
|
|
#include <soc/samsung/exynos-pm.h>
|
|
|
|
#if 0
|
|
#include <mach/map.h>
|
|
#include <mach/regs-pmu.h>
|
|
#include <mach/cpufreq.h>
|
|
#endif
|
|
|
|
#include "lpass.h"
|
|
|
|
#ifdef USE_EXYNOS_AUD_SCHED
|
|
#define AUD_TASK_CPU_UHQ (5)
|
|
#ifdef CONFIG_SOC_EXYNOS5422
|
|
#define USE_AUD_TASK_RT
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM_DEVFREQ
|
|
#define USE_AUD_DEVFREQ
|
|
#ifdef CONFIG_SOC_EXYNOS5422
|
|
#define AUD_CPU_FREQ_UHQA (1000000)
|
|
#define AUD_KFC_FREQ_UHQA (1300000)
|
|
#define AUD_MIF_FREQ_UHQA (413000)
|
|
#define AUD_INT_FREQ_UHQA (0)
|
|
#define AUD_CPU_FREQ_HIGH (0)
|
|
#define AUD_KFC_FREQ_HIGH (1300000)
|
|
#define AUD_MIF_FREQ_HIGH (0)
|
|
#define AUD_INT_FREQ_HIGH (0)
|
|
#define AUD_CPU_FREQ_NORM (0)
|
|
#define AUD_KFC_FREQ_NORM (0)
|
|
#define AUD_MIF_FREQ_NORM (0)
|
|
#define AUD_INT_FREQ_NORM (0)
|
|
#elif defined(CONFIG_SOC_EXYNOS5430)
|
|
#define AUD_CPU_FREQ_UHQA (1000000)
|
|
#define AUD_KFC_FREQ_UHQA (1300000)
|
|
#define AUD_MIF_FREQ_UHQA (413000)
|
|
#define AUD_INT_FREQ_UHQA (0)
|
|
#define AUD_CPU_FREQ_HIGH (0)
|
|
#define AUD_KFC_FREQ_HIGH (600000)
|
|
#define AUD_MIF_FREQ_HIGH (0)
|
|
#define AUD_INT_FREQ_HIGH (0)
|
|
#define AUD_CPU_FREQ_NORM (0)
|
|
#define AUD_KFC_FREQ_NORM (0)
|
|
#define AUD_MIF_FREQ_NORM (0)
|
|
#define AUD_INT_FREQ_NORM (0)
|
|
#elif defined(CONFIG_SOC_EXYNOS5433)
|
|
#define AUD_CPU_FREQ_UHQA (1000000)
|
|
#define AUD_KFC_FREQ_UHQA (1300000)
|
|
#define AUD_MIF_FREQ_UHQA (413000)
|
|
#define AUD_INT_FREQ_UHQA (0)
|
|
#define AUD_CPU_FREQ_HIGH (0)
|
|
#define AUD_KFC_FREQ_HIGH (500000)
|
|
#define AUD_MIF_FREQ_HIGH (0)
|
|
#define AUD_INT_FREQ_HIGH (0)
|
|
#define AUD_CPU_FREQ_NORM (0)
|
|
#define AUD_KFC_FREQ_NORM (0)
|
|
#define AUD_MIF_FREQ_NORM (0)
|
|
#define AUD_INT_FREQ_NORM (0)
|
|
#elif defined(CONFIG_SOC_EXYNOS7420)
|
|
#define AUD_CPU_FREQ_UHQA (1000000)
|
|
#define AUD_KFC_FREQ_UHQA (1300000)
|
|
#define AUD_MIF_FREQ_UHQA (413000)
|
|
#define AUD_INT_FREQ_UHQA (0)
|
|
#define AUD_CPU_FREQ_HIGH (0)
|
|
#define AUD_KFC_FREQ_HIGH (0)
|
|
#define AUD_MIF_FREQ_HIGH (416000)
|
|
#define AUD_INT_FREQ_HIGH (0)
|
|
#define AUD_CPU_FREQ_NORM (0)
|
|
#define AUD_KFC_FREQ_NORM (0)
|
|
#define AUD_MIF_FREQ_NORM (0)
|
|
#define AUD_INT_FREQ_NORM (0)
|
|
#else
|
|
#define AUD_CPU_FREQ_UHQA (1000000)
|
|
#define AUD_KFC_FREQ_UHQA (1300000)
|
|
#define AUD_MIF_FREQ_UHQA (413000)
|
|
#define AUD_INT_FREQ_UHQA (0)
|
|
#define AUD_CPU_FREQ_HIGH (0)
|
|
#define AUD_KFC_FREQ_HIGH (500000)
|
|
#define AUD_MIF_FREQ_HIGH (0)
|
|
#define AUD_INT_FREQ_HIGH (0)
|
|
#define AUD_CPU_FREQ_NORM (0)
|
|
#define AUD_KFC_FREQ_NORM (0)
|
|
#define AUD_MIF_FREQ_NORM (0)
|
|
#define AUD_INT_FREQ_NORM (0)
|
|
#endif
|
|
#endif
|
|
|
|
/* Default interrupt mask */
|
|
#define INTR_CA5_MASK_VAL (LPASS_INTR_SFR)
|
|
#define INTR_CPU_MASK_VAL (LPASS_INTR_I2S | \
|
|
LPASS_INTR_PCM | LPASS_INTR_SB | \
|
|
LPASS_INTR_UART | LPASS_INTR_SFR)
|
|
#define INTR_CPU_DMA_VAL (LPASS_INTR_DMA)
|
|
|
|
#define EXYNOS_PMU_PMU_DEBUG_OFFSET 0x0A00
|
|
#define EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET 0x1340
|
|
#define EXYNOS_PAD_RETENTION_AUD_OPTION_OFFSET 0x3028
|
|
|
|
#ifdef CONFIG_SOC_EXYNOS8890
|
|
#define SRAM_BASE 0x3000000
|
|
#define SRAM_SIZE 0x24000
|
|
#endif
|
|
|
|
/* Audio subsystem version */
|
|
enum {
|
|
LPASS_VER_000100 = 0, /* pega/carmen */
|
|
LPASS_VER_010100, /* hudson */
|
|
LPASS_VER_100100 = 16, /* gaia/adonis */
|
|
LPASS_VER_110100, /* ares */
|
|
LPASS_VER_270120, /* insel-d */
|
|
LPASS_VER_370100 = 32, /* rhea/helsinki */
|
|
LPASS_VER_370200, /* helsinki prime */
|
|
LPASS_VER_370210, /* istor */
|
|
LPASS_VER_MAX
|
|
};
|
|
|
|
static struct lpass_info lpass;
|
|
|
|
struct aud_reg {
|
|
void __iomem *reg;
|
|
u32 val;
|
|
struct list_head node;
|
|
};
|
|
|
|
struct subip_info {
|
|
struct device *dev;
|
|
const char *name;
|
|
void (*cb)(struct device *dev);
|
|
atomic_t use_cnt;
|
|
struct list_head node;
|
|
};
|
|
|
|
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
|
|
DEFINE_MUTEX(lpass_mutex);
|
|
|
|
unsigned int cpu_lock[14] =
|
|
{0, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, 1000000, 1104000, 1200000, 1296000, 1400000};
|
|
|
|
void lpass_set_cpu_lock(int level)
|
|
{
|
|
mutex_lock(&lpass_mutex);
|
|
#ifdef USE_AUD_DEVFREQ
|
|
pm_qos_update_request(&lpass.aud_cluster0_qos, cpu_lock[level]);
|
|
#endif
|
|
mutex_unlock(&lpass_mutex);
|
|
}
|
|
#endif
|
|
|
|
static LIST_HEAD(reg_list);
|
|
static LIST_HEAD(subip_list);
|
|
|
|
extern int check_adma_status(void);
|
|
extern int check_fdma_status(void);
|
|
extern int check_esa_status(void);
|
|
extern int check_eax_dma_status(void);
|
|
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
|
|
extern void esa_compr_alpa_notifier(bool on);
|
|
extern int check_esa_compr_state(void);
|
|
#endif
|
|
|
|
static void lpass_update_qos(void);
|
|
|
|
static bool cp_available;
|
|
static atomic_t dram_usage_cnt;
|
|
|
|
void lpass_disable_mif_status(bool on)
|
|
{
|
|
u32 val = on ? 1 << 2 : 0x0;
|
|
writel(val, lpass.regs + LPASS_MIF_POWER);
|
|
}
|
|
|
|
void lpass_inc_dram_usage_count(void)
|
|
{
|
|
atomic_inc(&dram_usage_cnt);
|
|
}
|
|
|
|
void lpass_dec_dram_usage_count(void)
|
|
{
|
|
atomic_dec(&dram_usage_cnt);
|
|
}
|
|
|
|
int lpass_get_dram_usage_count(void)
|
|
{
|
|
return atomic_read(&dram_usage_cnt);
|
|
}
|
|
|
|
bool lpass_i2s_master_mode(void)
|
|
{
|
|
return lpass.i2s_master_mode;
|
|
}
|
|
|
|
void update_cp_available(bool cpen)
|
|
{
|
|
cp_available = cpen;
|
|
}
|
|
|
|
bool is_cp_aud_enabled(void)
|
|
{
|
|
return cp_available;
|
|
}
|
|
EXPORT_SYMBOL(is_cp_aud_enabled);
|
|
|
|
/**
|
|
* LPASS version for Exynos7580 is 0x270120. The operation of this LPASS is
|
|
* closer to the LPASS versions 0x370100 and above, hence it is grouped with
|
|
* them.
|
|
*/
|
|
static inline bool is_old_ass(void)
|
|
{
|
|
return lpass.ver < LPASS_VER_270120 ? true : false;
|
|
}
|
|
|
|
/**
|
|
* LPASS version for Exynos7580 is 0x270120. This LPASS version doesn't support
|
|
* CA5, timer and interrupt clocks. Hence keeping this out of this class in
|
|
* these cases and the operations are protected by this check.
|
|
*/
|
|
static inline bool is_new_ass(void)
|
|
{
|
|
return lpass.ver >= LPASS_VER_370100 ? true : false;
|
|
}
|
|
|
|
static inline bool is_running_only(const char *name)
|
|
{
|
|
struct subip_info *si;
|
|
|
|
if (atomic_read(&lpass.use_cnt) != 1)
|
|
return false;
|
|
|
|
list_for_each_entry(si, &subip_list, node) {
|
|
if (atomic_read(&si->use_cnt) > 0 &&
|
|
!strncmp(name, si->name, strlen(si->name)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int exynos_check_aud_pwr(void)
|
|
{
|
|
int dram_used = check_adma_status();
|
|
|
|
#ifdef CONFIG_SND_SAMSUNG_FAKEDMA
|
|
dram_used |= check_fdma_status();
|
|
#endif
|
|
#if defined(CONFIG_SND_SAMSUNG_SEIREN) || defined(CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD)
|
|
dram_used |= check_esa_status();
|
|
#endif
|
|
dram_used |= check_eax_dma_status();
|
|
|
|
if (!lpass.enabled)
|
|
return AUD_PWR_SLEEP;
|
|
else if (is_running_only("aud-uart"))
|
|
return AUD_PWR_LPC;
|
|
else if (!dram_used)
|
|
return AUD_PWR_LPA;
|
|
|
|
if (is_new_ass())
|
|
return AUD_PWR_ALPA;
|
|
else
|
|
return AUD_PWR_AFTR;
|
|
}
|
|
|
|
void lpass_update_lpclock(u32 ctrlid, bool idle)
|
|
{
|
|
lpass_update_lpclock_impl(&lpass.pdev->dev, ctrlid, idle);
|
|
}
|
|
|
|
void __iomem *lpass_get_regs(void)
|
|
{
|
|
return lpass.regs;
|
|
}
|
|
|
|
void __iomem *lpass_get_regs_s(void)
|
|
{
|
|
return lpass.regs_s;
|
|
}
|
|
|
|
void __iomem *lpass_get_mem(void)
|
|
{
|
|
return lpass.mem;
|
|
}
|
|
|
|
struct iommu_domain *lpass_get_iommu_domain(void)
|
|
{
|
|
return lpass.domain;
|
|
}
|
|
|
|
void lpass_set_dma_intr(bool on)
|
|
{
|
|
u32 cfg = readl(lpass.regs + LPASS_INTR_CPU_MASK);
|
|
if (on)
|
|
cfg |= INTR_CPU_DMA_VAL;
|
|
else
|
|
cfg &= ~(INTR_CPU_DMA_VAL);
|
|
|
|
writel(cfg, lpass.regs + LPASS_INTR_CPU_MASK);
|
|
}
|
|
|
|
void lpass_dma_enable(bool on)
|
|
{
|
|
spin_lock(&lpass.lock);
|
|
if (on) {
|
|
atomic_inc(&lpass.dma_use_cnt);
|
|
if (atomic_read(&lpass.dma_use_cnt) == 1)
|
|
lpass_set_dma_intr(true);
|
|
} else {
|
|
atomic_dec(&lpass.dma_use_cnt);
|
|
if (atomic_read(&lpass.dma_use_cnt) == 0)
|
|
lpass_set_dma_intr(false);
|
|
}
|
|
spin_unlock(&lpass.lock);
|
|
}
|
|
|
|
void ass_reset(int ip, int op)
|
|
{
|
|
spin_lock(&lpass.lock);
|
|
|
|
spin_unlock(&lpass.lock);
|
|
}
|
|
|
|
void lpass_reset(int ip, int op)
|
|
{
|
|
u32 reg, val;
|
|
u32 bit = 0;
|
|
void __iomem *regs;
|
|
|
|
if (is_old_ass()) {
|
|
ass_reset(ip, op);
|
|
return;
|
|
}
|
|
|
|
spin_lock(&lpass.lock);
|
|
regs = lpass.regs;
|
|
reg = LPASS_CORE_SW_RESET;
|
|
switch (ip) {
|
|
case LPASS_IP_DMA:
|
|
bit = LPASS_SW_RESET_DMA;
|
|
break;
|
|
case LPASS_IP_MEM:
|
|
bit = LPASS_SW_RESET_MEM;
|
|
break;
|
|
case LPASS_IP_TIMER:
|
|
if (is_new_ass())
|
|
bit = LPASS_SW_RESET_TIMER;
|
|
break;
|
|
case LPASS_IP_I2S:
|
|
bit = LPASS_SW_RESET_I2S;
|
|
break;
|
|
case LPASS_IP_PCM:
|
|
bit = LPASS_SW_RESET_PCM;
|
|
break;
|
|
case LPASS_IP_UART:
|
|
bit = LPASS_SW_RESET_UART;
|
|
break;
|
|
case LPASS_IP_SLIMBUS:
|
|
if (is_new_ass())
|
|
bit = LPASS_SW_RESET_SB;
|
|
break;
|
|
case LPASS_IP_CA5:
|
|
if (is_new_ass()) {
|
|
regs = (lpass.ver >= LPASS_VER_370200) ?
|
|
lpass.regs_s : regs;
|
|
reg = LPASS_CA5_SW_RESET;
|
|
bit = LPASS_SW_RESET_CA5;
|
|
}
|
|
break;
|
|
default:
|
|
spin_unlock(&lpass.lock);
|
|
pr_err("%s: wrong ip type %d!\n", __func__, ip);
|
|
return;
|
|
}
|
|
|
|
val = readl(regs + reg);
|
|
switch (op) {
|
|
case LPASS_OP_RESET:
|
|
val &= ~bit;
|
|
break;
|
|
case LPASS_OP_NORMAL:
|
|
val |= bit;
|
|
break;
|
|
default:
|
|
spin_unlock(&lpass.lock);
|
|
pr_err("%s: wrong op type %d!\n", __func__, op);
|
|
return;
|
|
}
|
|
|
|
writel(val, regs + reg);
|
|
spin_unlock(&lpass.lock);
|
|
}
|
|
|
|
void lpass_reset_toggle(int ip)
|
|
{
|
|
pr_debug("%s: %d\n", __func__, ip);
|
|
|
|
lpass_reset(ip, LPASS_OP_RESET);
|
|
udelay(100);
|
|
lpass_reset(ip, LPASS_OP_NORMAL);
|
|
}
|
|
|
|
int lpass_register_subip(struct device *ip_dev, const char *ip_name)
|
|
{
|
|
struct device *dev = &lpass.pdev->dev;
|
|
struct subip_info *si;
|
|
|
|
si = devm_kzalloc(dev, sizeof(struct subip_info), GFP_KERNEL);
|
|
if (!si)
|
|
return -1;
|
|
|
|
si->dev = ip_dev;
|
|
si->name = ip_name;
|
|
si->cb = NULL;
|
|
atomic_set(&si->use_cnt, 0);
|
|
list_add(&si->node, &subip_list);
|
|
|
|
pr_info("%s: %s(%p) registered\n", __func__, ip_name, ip_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lpass_set_gpio_cb(struct device *ip_dev, void (*ip_cb)(struct device *dev))
|
|
{
|
|
struct subip_info *si;
|
|
|
|
list_for_each_entry(si, &subip_list, node) {
|
|
if (si->dev == ip_dev) {
|
|
si->cb = ip_cb;
|
|
pr_info("%s: %s(cb: %p)\n", __func__,
|
|
si->name, si->cb);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
void lpass_get_sync(struct device *ip_dev)
|
|
{
|
|
struct subip_info *si;
|
|
|
|
list_for_each_entry(si, &subip_list, node) {
|
|
if (si->dev == ip_dev) {
|
|
atomic_inc(&si->use_cnt);
|
|
atomic_inc(&lpass.use_cnt);
|
|
pr_info("%s: %s (use:%d)\n", __func__,
|
|
si->name, atomic_read(&si->use_cnt));
|
|
pm_runtime_get_sync(&lpass.pdev->dev);
|
|
}
|
|
}
|
|
|
|
lpass_update_qos();
|
|
}
|
|
|
|
void lpass_put_sync(struct device *ip_dev)
|
|
{
|
|
struct subip_info *si;
|
|
|
|
list_for_each_entry(si, &subip_list, node) {
|
|
if (si->dev == ip_dev) {
|
|
atomic_dec(&si->use_cnt);
|
|
atomic_dec(&lpass.use_cnt);
|
|
pr_info("%s: %s (use:%d)\n", __func__,
|
|
si->name, atomic_read(&si->use_cnt));
|
|
pm_runtime_put_sync(&lpass.pdev->dev);
|
|
}
|
|
}
|
|
|
|
lpass_update_qos();
|
|
}
|
|
|
|
#ifdef USE_EXYNOS_AUD_SCHED
|
|
void lpass_set_sched(pid_t pid, int mode)
|
|
{
|
|
#ifdef USE_AUD_TASK_RT
|
|
struct sched_param param_fifo = {.sched_priority = MAX_RT_PRIO >> 1};
|
|
struct task_struct *task = find_task_by_vpid(pid);
|
|
#endif
|
|
switch (mode) {
|
|
case AUD_MODE_UHQA:
|
|
lpass.uhqa_on = true;
|
|
break;
|
|
case AUD_MODE_NORM:
|
|
lpass.uhqa_on = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
lpass_update_qos();
|
|
|
|
#ifdef USE_AUD_TASK_RT
|
|
if (task) {
|
|
sched_setscheduler_nocheck(task,
|
|
SCHED_RR | SCHED_RESET_ON_FORK, ¶m_fifo);
|
|
pr_info("%s: [%s] pid = %d, prio = %d\n",
|
|
__func__, task->comm, pid, task->prio);
|
|
} else {
|
|
pr_err("%s: task not found (pid = %d)\n",
|
|
__func__, pid);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_EXYNOS_AUD_CPU_HOTPLUG
|
|
void lpass_get_cpu_hotplug(void)
|
|
{
|
|
pr_debug("%s ++\n", __func__);
|
|
cluster0_core1_hotplug_in(true);
|
|
}
|
|
|
|
void lpass_put_cpu_hotplug(void)
|
|
{
|
|
pr_debug("%s --\n", __func__);
|
|
cluster0_core1_hotplug_in(false);
|
|
}
|
|
#endif
|
|
|
|
void lpass_add_stream(void)
|
|
{
|
|
atomic_inc(&lpass.stream_cnt);
|
|
lpass_update_qos();
|
|
}
|
|
|
|
void lpass_remove_stream(void)
|
|
{
|
|
atomic_dec(&lpass.stream_cnt);
|
|
lpass_update_qos();
|
|
}
|
|
|
|
static void lpass_reg_save(void)
|
|
{
|
|
struct aud_reg *ar;
|
|
|
|
pr_debug("Registers of LPASS are saved\n");
|
|
|
|
list_for_each_entry(ar, ®_list, node)
|
|
ar->val = readl(ar->reg);
|
|
|
|
return;
|
|
}
|
|
|
|
static void lpass_reg_restore(void)
|
|
{
|
|
struct aud_reg *ar;
|
|
|
|
pr_debug("Registers of LPASS are restore\n");
|
|
|
|
list_for_each_entry(ar, ®_list, node)
|
|
writel(ar->val, ar->reg);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void lpass_retention_pad_reg(void)
|
|
{
|
|
regmap_update_bits(lpass.pmureg,
|
|
EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET,
|
|
0x1, 1);
|
|
|
|
// writel(1, EXYNOS_PMUREG(0x1340)); /* GPIO_MODE_AUD_SYS_PWR_REG */
|
|
}
|
|
|
|
void lpass_release_pad_reg(void)
|
|
{
|
|
regmap_update_bits(lpass.pmureg,
|
|
EXYNOS_PAD_RETENTION_AUD_OPTION_OFFSET,
|
|
0x10000000, 1);
|
|
|
|
regmap_update_bits(lpass.pmureg,
|
|
EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET,
|
|
0x1, 1);
|
|
|
|
// writel(1 << 28, EXYNOS_PMUREG(0x3028)); /* PAD_RETENTION_AUD_OPTION */
|
|
// writel(1, EXYNOS_PMUREG(0x1340)); /* GPIO_MODE_AUD_SYS_PWR_REG */
|
|
}
|
|
|
|
static void lpass_retention_pad(void)
|
|
{
|
|
struct subip_info *si;
|
|
|
|
/* Powerdown mode for gpio */
|
|
list_for_each_entry(si, &subip_list, node) {
|
|
if (si->cb != NULL)
|
|
(*si->cb)(si->dev);
|
|
}
|
|
|
|
/* Set PAD retention */
|
|
lpass_retention_pad_reg();
|
|
}
|
|
|
|
static void lpass_release_pad(void)
|
|
{
|
|
struct subip_info *si;
|
|
|
|
/* Restore gpio */
|
|
list_for_each_entry(si, &subip_list, node) {
|
|
if (si->cb != NULL)
|
|
(*si->cb)(si->dev);
|
|
}
|
|
|
|
/* Release PAD retention */
|
|
lpass_release_pad_reg();
|
|
}
|
|
|
|
static void ass_enable(void)
|
|
{
|
|
|
|
#ifdef CONFIG_SOC_EXYNOS8890
|
|
lpass.mem = ioremap_wc(SRAM_BASE, SRAM_SIZE);
|
|
if (!lpass.mem) {
|
|
pr_err("LPASS driver failed to ioremap sram \n");
|
|
return;
|
|
}
|
|
#endif
|
|
/* Enable PLL */
|
|
lpass_enable_pll(true);
|
|
|
|
lpass_reg_restore();
|
|
|
|
/* PLL path */
|
|
lpass_set_mux_pll();
|
|
|
|
clk_prepare_enable(lpass.clk_dmac);
|
|
clk_prepare_enable(lpass.clk_timer);
|
|
|
|
lpass.enabled = true;
|
|
#if 0
|
|
ret = iommu_attach_device(lpass.domain, &lpass.pdev->dev);
|
|
if (ret) {
|
|
dev_err(&lpass.pdev->dev,
|
|
"Unable to attach iommu device: %d\n", ret);
|
|
} else {
|
|
lpass.enabled = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void lpass_enable(void)
|
|
{
|
|
|
|
if (!lpass.valid) {
|
|
pr_debug("%s: LPASS is not available", __func__);
|
|
return;
|
|
}
|
|
|
|
if (is_old_ass()) {
|
|
ass_enable();
|
|
return;
|
|
}
|
|
|
|
/* Enable PLL */
|
|
lpass_enable_pll(true);
|
|
|
|
lpass_reg_restore();
|
|
|
|
/* PLL path */
|
|
lpass_set_mux_pll();
|
|
|
|
if (lpass.clk_dmac)
|
|
clk_prepare_enable(lpass.clk_dmac);
|
|
if (lpass.clk_sramc)
|
|
clk_prepare_enable(lpass.clk_sramc);
|
|
|
|
lpass_reset_toggle(LPASS_IP_MEM);
|
|
lpass_reset_toggle(LPASS_IP_I2S);
|
|
lpass_reset_toggle(LPASS_IP_DMA);
|
|
|
|
#ifdef CONFIG_SOC_EXYNOS8890
|
|
if (!lpass.mem) {
|
|
lpass.mem = ioremap_wc(SRAM_BASE, SRAM_SIZE);
|
|
if (!lpass.mem) {
|
|
lpass_enable_pll(false);
|
|
pr_err("LPASS driver failed to ioremap sram \n");
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (lpass.clk_dmac)
|
|
clk_disable_unprepare(lpass.clk_dmac);
|
|
|
|
/* PAD */
|
|
lpass_release_pad();
|
|
|
|
/* Clear memory */
|
|
memset(lpass.mem, 0, lpass.mem_size);
|
|
|
|
lpass.enabled = true;
|
|
#if 0
|
|
ret = iommu_attach_device(lpass.domain, &lpass.pdev->dev);
|
|
if (ret) {
|
|
dev_err(&lpass.pdev->dev,
|
|
"Unable to attach iommu device: %d\n", ret);
|
|
} else {
|
|
lpass.enabled = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void ass_disable(void)
|
|
{
|
|
lpass.enabled = false;
|
|
|
|
if (lpass.clk_dmac)
|
|
clk_disable_unprepare(lpass.clk_dmac);
|
|
if (lpass.clk_timer)
|
|
clk_disable_unprepare(lpass.clk_timer);
|
|
|
|
lpass_reg_save();
|
|
|
|
// iommu_detach_device(lpass.domain, &lpass.pdev->dev);
|
|
|
|
/* OSC path */
|
|
lpass_set_mux_osc();
|
|
|
|
/* Disable PLL */
|
|
lpass_enable_pll(false);
|
|
|
|
#ifdef CONFIG_SOC_EXYNOS8890
|
|
iounmap(lpass.mem);
|
|
lpass.mem = NULL;
|
|
#endif
|
|
|
|
}
|
|
|
|
static void lpass_disable(void)
|
|
{
|
|
#ifdef CONFIG_SOC_EXYNOS8890
|
|
unsigned long start;
|
|
unsigned long end;
|
|
#endif
|
|
|
|
if (!lpass.valid) {
|
|
pr_debug("%s: LPASS is not available", __func__);
|
|
return;
|
|
}
|
|
|
|
if (is_old_ass()) {
|
|
ass_disable();
|
|
return;
|
|
}
|
|
|
|
lpass.enabled = false;
|
|
|
|
/* PAD */
|
|
lpass_retention_pad();
|
|
|
|
if (lpass.clk_sramc)
|
|
clk_disable_unprepare(lpass.clk_sramc);
|
|
|
|
lpass_reg_save();
|
|
|
|
// iommu_detach_device(lpass.domain, &lpass.pdev->dev);
|
|
|
|
/* OSC path */
|
|
lpass_set_mux_osc();
|
|
|
|
/* Enable clocks */
|
|
lpass_reset_clk_default();
|
|
|
|
/* Disable PLL */
|
|
lpass_enable_pll(false);
|
|
|
|
#ifdef CONFIG_SOC_EXYNOS8890
|
|
start = (unsigned long)lpass.mem;
|
|
end = (unsigned long)lpass.mem + lpass.mem_size;
|
|
iounmap(lpass.mem);
|
|
flush_tlb_kernel_range(start, end);
|
|
lpass.mem = NULL;
|
|
#endif
|
|
}
|
|
|
|
#if 0
|
|
static int lpass_get_clk_ass(struct device *dev)
|
|
{
|
|
lpass.clk_dmac = clk_get(dev, "dmac");
|
|
if (IS_ERR(lpass.clk_dmac)) {
|
|
dev_err(dev, "dmac clk not found\n");
|
|
goto err0;
|
|
}
|
|
|
|
lpass.clk_timer = clk_get(dev, "timer");
|
|
if (IS_ERR(lpass.clk_timer)) {
|
|
dev_err(dev, "timer clk not found\n");
|
|
goto err1;
|
|
}
|
|
|
|
return 0;
|
|
err1:
|
|
clk_put(lpass.clk_dmac);
|
|
err0:
|
|
return -1;
|
|
}
|
|
|
|
static int lpass_get_clk(struct device *dev)
|
|
{
|
|
if (is_old_ass())
|
|
return lpass_get_clk_ass(dev);
|
|
|
|
lpass.clk_dmac = clk_get(dev, "dmac");
|
|
if (IS_ERR(lpass.clk_dmac)) {
|
|
dev_err(dev, "dmac clk not found\n");
|
|
goto err0;
|
|
}
|
|
|
|
lpass.clk_sramc = clk_get(dev, "sramc");
|
|
if (IS_ERR(lpass.clk_sramc)) {
|
|
dev_err(dev, "sramc clk not found\n");
|
|
goto err1;
|
|
}
|
|
|
|
/**
|
|
* Exynos7580 (LPASS_VER_270120) doesn't have timer and intr clocks,
|
|
* hence we need a special check for it.
|
|
*/
|
|
if (is_new_ass()) {
|
|
lpass.clk_intr = clk_get(dev, "intr");
|
|
if (IS_ERR(lpass.clk_intr)) {
|
|
dev_err(dev, "intr clk not found\n");
|
|
goto err2;
|
|
}
|
|
|
|
lpass.clk_timer = clk_get(dev, "timer");
|
|
if (IS_ERR(lpass.clk_timer)) {
|
|
dev_err(dev, "timer clk not found\n");
|
|
goto err3;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
err3:
|
|
clk_put(lpass.clk_intr);
|
|
err2:
|
|
clk_put(lpass.clk_sramc);
|
|
err1:
|
|
clk_put(lpass.clk_dmac);
|
|
err0:
|
|
return -1;
|
|
}
|
|
|
|
static void clk_put_all_ass(void)
|
|
{
|
|
clk_put(lpass.clk_dmac);
|
|
clk_put(lpass.clk_timer);
|
|
}
|
|
|
|
static void clk_put_all(void)
|
|
{
|
|
if (is_old_ass()) {
|
|
clk_put_all_ass();
|
|
return;
|
|
}
|
|
|
|
clk_put(lpass.clk_dmac);
|
|
clk_put(lpass.clk_sramc);
|
|
|
|
/* Exynos7580 (LPASS_VER_270120) doesn't have clk_intr and clk_timer */
|
|
if (is_new_ass()) {
|
|
clk_put(lpass.clk_intr);
|
|
clk_put(lpass.clk_timer);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void lpass_add_suspend_reg(void __iomem *reg)
|
|
{
|
|
struct device *dev = &lpass.pdev->dev;
|
|
struct aud_reg *ar;
|
|
|
|
ar = devm_kzalloc(dev, sizeof(struct aud_reg), GFP_KERNEL);
|
|
if (!ar)
|
|
return;
|
|
|
|
ar->reg = reg;
|
|
list_add(&ar->node, ®_list);
|
|
}
|
|
|
|
static void lpass_init_reg_list(void)
|
|
{
|
|
int n = 0;
|
|
|
|
do {
|
|
if (lpass_cmu_save[n] == NULL)
|
|
break;
|
|
|
|
lpass_add_suspend_reg(lpass_cmu_save[n]);
|
|
} while (++n);
|
|
|
|
if (is_new_ass())
|
|
lpass_add_suspend_reg(lpass.regs + LPASS_INTR_CA5_MASK);
|
|
|
|
lpass_add_suspend_reg(lpass.regs + LPASS_INTR_CPU_MASK);
|
|
|
|
if (lpass.sysmmu)
|
|
lpass_add_suspend_reg(lpass.sysmmu);
|
|
}
|
|
|
|
static int lpass_proc_show(struct seq_file *m, void *v) {
|
|
struct subip_info *si;
|
|
int pmode = exynos_check_aud_pwr();
|
|
|
|
seq_printf(m, "power: %s\n", lpass.enabled ? "on" : "off");
|
|
seq_printf(m, "canbe: %s\n",
|
|
(pmode == AUD_PWR_SLEEP) ? "sleep" :
|
|
(pmode == AUD_PWR_LPA) ? "lpa" :
|
|
(pmode == AUD_PWR_ALPA) ? "alpa" :
|
|
(pmode == AUD_PWR_AFTR) ? "aftr" : "unknown");
|
|
|
|
list_for_each_entry(si, &subip_list, node) {
|
|
seq_printf(m, "subip: %s (%d)\n",
|
|
si->name, atomic_read(&si->use_cnt));
|
|
}
|
|
|
|
seq_printf(m, "strm: %d\n", atomic_read(&lpass.stream_cnt));
|
|
seq_printf(m, "uhqa: %s\n", lpass.uhqa_on ? "on" : "off");
|
|
#ifdef USE_AUD_DEVFREQ
|
|
seq_printf(m, "cpu: %d, kfc: %d\n",
|
|
lpass.cpu_qos / 1000, lpass.kfc_qos / 1000);
|
|
seq_printf(m, "mif: %d, int: %d\n",
|
|
lpass.mif_qos / 1000, lpass.int_qos / 1000);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int lpass_proc_open(struct inode *inode, struct file *file) {
|
|
return single_open(file, lpass_proc_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations lpass_proc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = lpass_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int lpass_suspend(struct device *dev)
|
|
{
|
|
pr_debug("%s entered\n", __func__);
|
|
|
|
#ifdef CONFIG_PM
|
|
if (atomic_read(&lpass.use_cnt) > 0)
|
|
lpass_disable();
|
|
#else
|
|
lpass_disable();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int lpass_resume(struct device *dev)
|
|
{
|
|
pr_debug("%s entered\n", __func__);
|
|
|
|
#ifdef CONFIG_PM
|
|
if (atomic_read(&lpass.use_cnt) > 0)
|
|
lpass_enable();
|
|
#else
|
|
lpass_enable();
|
|
#endif
|
|
return 0;
|
|
}
|
|
#else
|
|
#define lpass_suspend NULL
|
|
#define lpass_resume NULL
|
|
#endif
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id exynos_lpass_match[];
|
|
|
|
static int lpass_get_ver(struct device_node *np)
|
|
{
|
|
const struct of_device_id *match;
|
|
int ver;
|
|
|
|
if (np) {
|
|
match = of_match_node(exynos_lpass_match, np);
|
|
ver = *(int *)(match->data);
|
|
} else {
|
|
ver = LPASS_VER_000100;
|
|
}
|
|
|
|
return ver;
|
|
}
|
|
#else
|
|
static int lpass_get_ver(struct device_node *np)
|
|
{
|
|
return LPASS_VER_000100;
|
|
}
|
|
#endif
|
|
|
|
static void lpass_update_qos(void)
|
|
{
|
|
#ifdef USE_AUD_DEVFREQ
|
|
int cpu_qos_new, kfc_qos_new, mif_qos_new, int_qos_new;
|
|
|
|
if (!lpass.enabled) {
|
|
cpu_qos_new = 0;
|
|
kfc_qos_new = 0;
|
|
mif_qos_new = 0;
|
|
int_qos_new = 0;
|
|
} else if (lpass.uhqa_on) {
|
|
cpu_qos_new = AUD_CPU_FREQ_UHQA;
|
|
kfc_qos_new = AUD_KFC_FREQ_UHQA;
|
|
mif_qos_new = AUD_MIF_FREQ_UHQA;
|
|
int_qos_new = AUD_INT_FREQ_UHQA;
|
|
} else if (atomic_read(&lpass.stream_cnt) > 1) {
|
|
cpu_qos_new = AUD_CPU_FREQ_HIGH;
|
|
kfc_qos_new = AUD_KFC_FREQ_HIGH;
|
|
mif_qos_new = AUD_MIF_FREQ_HIGH;
|
|
int_qos_new = AUD_INT_FREQ_HIGH;
|
|
} else {
|
|
cpu_qos_new = AUD_CPU_FREQ_NORM;
|
|
kfc_qos_new = AUD_KFC_FREQ_NORM;
|
|
mif_qos_new = AUD_MIF_FREQ_NORM;
|
|
int_qos_new = AUD_INT_FREQ_NORM;
|
|
}
|
|
|
|
if (lpass.cpu_qos != cpu_qos_new) {
|
|
lpass.cpu_qos = cpu_qos_new;
|
|
pm_qos_update_request(&lpass.aud_cluster1_qos, lpass.cpu_qos);
|
|
pr_debug("%s: cpu_qos = %d\n", __func__, lpass.cpu_qos);
|
|
}
|
|
|
|
if (lpass.kfc_qos != kfc_qos_new) {
|
|
lpass.kfc_qos = kfc_qos_new;
|
|
pm_qos_update_request(&lpass.aud_cluster0_qos, lpass.kfc_qos);
|
|
pr_debug("%s: kfc_qos = %d\n", __func__, lpass.kfc_qos);
|
|
}
|
|
|
|
if (lpass.mif_qos != mif_qos_new) {
|
|
lpass.mif_qos = mif_qos_new;
|
|
pm_qos_update_request(&lpass.aud_mif_qos, lpass.mif_qos);
|
|
pr_debug("%s: mif_qos = %d\n", __func__, lpass.mif_qos);
|
|
}
|
|
|
|
if (lpass.int_qos != int_qos_new) {
|
|
lpass.int_qos = int_qos_new;
|
|
pm_qos_update_request(&lpass.aud_int_qos, lpass.int_qos);
|
|
pr_debug("%s: int_qos = %d\n", __func__, lpass.int_qos);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int lpass_fb_state_chg(struct notifier_block *nb,
|
|
unsigned long val, void *data)
|
|
{
|
|
struct fb_event *evdata = data;
|
|
unsigned int blank;
|
|
|
|
if (val != FB_EVENT_BLANK)
|
|
return 0;
|
|
|
|
blank = *(int *)evdata->data;
|
|
|
|
switch (blank) {
|
|
case FB_BLANK_POWERDOWN:
|
|
lpass.display_on = false;
|
|
lpass_update_qos();
|
|
break;
|
|
case FB_BLANK_UNBLANK:
|
|
lpass.display_on = true;
|
|
lpass_update_qos();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block fb_noti_block = {
|
|
.notifier_call = lpass_fb_state_chg,
|
|
};
|
|
|
|
static int exynos_aud_alpa_notifier(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
switch (event) {
|
|
case SICD_AUD_ENTER:
|
|
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
|
|
esa_compr_alpa_notifier(true);
|
|
#endif
|
|
break;
|
|
case SICD_AUD_EXIT:
|
|
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
|
|
esa_compr_alpa_notifier(false);
|
|
#endif
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block lpass_lpa_nb = {
|
|
.notifier_call = exynos_aud_alpa_notifier,
|
|
};
|
|
|
|
static char banner[] =
|
|
KERN_INFO "Samsung Low Power Audio Subsystem driver, "\
|
|
"(c)2013 Samsung Electronics\n";
|
|
|
|
static int lpass_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret = 0;
|
|
|
|
printk(banner);
|
|
|
|
lpass.pdev = pdev;
|
|
|
|
/* LPASS version */
|
|
lpass.ver = lpass_get_ver(np);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(dev, "Unable to get LPASS SFRs\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
lpass.regs = ioremap(res->start, resource_size(res));
|
|
if (!lpass.regs) {
|
|
dev_err(dev, "SFR ioremap failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
pr_info("%s: regs_base = %08X (%08X bytes)\n",
|
|
__func__, (u32)res->start, (u32)resource_size(res));
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
if (!res) {
|
|
dev_err(dev, "Unable to get LPASS SRAM\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
#ifndef CONFIG_SOC_EXYNOS8890
|
|
lpass.mem = ioremap_wc(res->start, resource_size(res));
|
|
if (!lpass.mem) {
|
|
dev_err(dev, "SRAM ioremap failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
#else
|
|
lpass.mem = NULL;
|
|
#endif
|
|
lpass.mem_size = resource_size(res);
|
|
pr_info("%s: sram_base = %08X (%08X bytes)\n",
|
|
__func__, (u32)res->start, (u32)resource_size(res));
|
|
|
|
if (lpass.ver >= LPASS_VER_370200) {
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
|
if (!res) {
|
|
dev_err(dev, "Unable to get LPASS-s SFRs\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
lpass.regs_s = ioremap(res->start, resource_size(res));
|
|
if (!lpass.regs_s) {
|
|
dev_err(dev, "SFR ioremap failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
pr_info("%s: regs-s_base = %08X (%08X bytes)\n",
|
|
__func__, (u32)res->start, (u32)resource_size(res));
|
|
} else {
|
|
lpass.regs_s = lpass.regs;
|
|
pr_debug("%s: regs-s_base is set as regs", __func__);
|
|
}
|
|
|
|
ret = lpass_get_clk(&pdev->dev, &lpass);
|
|
if (ret) {
|
|
dev_err(dev, "failed to get clock\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
ret = lpass_set_clk_heirachy(&pdev->dev);
|
|
if (ret) {
|
|
dev_err(dev, "failed to set clock hierachy\n");
|
|
return -ENXIO;
|
|
}
|
|
lpass_init_clk_gate();
|
|
|
|
#ifdef CONFIG_SND_SAMSUNG_IOMMU
|
|
lpass.domain = get_domain_from_dev(dev);
|
|
if (!lpass.domain) {
|
|
dev_err(dev, "Unable to get iommu domain\n");
|
|
return -ENOENT;
|
|
}
|
|
#else
|
|
/* Bypass SysMMU */
|
|
if (lpass.ver >= LPASS_VER_370210) {
|
|
lpass.sysmmu = ioremap(0x114e0000, SZ_32);
|
|
writel(0, lpass.sysmmu);
|
|
}
|
|
#endif
|
|
|
|
lpass.proc_file = proc_create("driver/lpass", 0,
|
|
NULL, &lpass_proc_fops);
|
|
if (!lpass.proc_file)
|
|
pr_info("Failed to register /proc/driver/lpadd\n");
|
|
|
|
spin_lock_init(&lpass.lock);
|
|
atomic_set(&lpass.dma_use_cnt, 0);
|
|
atomic_set(&lpass.use_cnt, 0);
|
|
atomic_set(&lpass.stream_cnt, 0);
|
|
lpass_init_reg_list();
|
|
|
|
/* unmask irq source */
|
|
if (is_new_ass())
|
|
writel(INTR_CA5_MASK_VAL, lpass.regs + LPASS_INTR_CA5_MASK);
|
|
|
|
writel(INTR_CPU_MASK_VAL, lpass.regs + LPASS_INTR_CPU_MASK);
|
|
lpass_reg_save();
|
|
lpass.valid = true;
|
|
|
|
lpass.pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
"samsung,syscon-phandle");
|
|
if (IS_ERR(lpass.pmureg)) {
|
|
dev_err(&pdev->dev, "syscon regmap lookup failed.\n");
|
|
return PTR_ERR(lpass.pmureg);
|
|
}
|
|
|
|
regmap_update_bits(lpass.pmureg,
|
|
EXYNOS_PMU_PMU_DEBUG_OFFSET,
|
|
0x1F00, 0x1F00);
|
|
regmap_update_bits(lpass.pmureg,
|
|
EXYNOS_PMU_PMU_DEBUG_OFFSET,
|
|
0x1, 0x0);
|
|
|
|
ret = iommu_attach_device(lpass.domain, &lpass.pdev->dev);
|
|
if (ret) {
|
|
dev_err(&lpass.pdev->dev,
|
|
"Unable to attach iommu device: %d\n", ret);
|
|
return -ENXIO;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
pm_runtime_enable(&lpass.pdev->dev);
|
|
|
|
if (is_new_ass())
|
|
pm_runtime_get_sync(&lpass.pdev->dev);
|
|
#else
|
|
lpass_enable();
|
|
#endif
|
|
lpass.display_on = true;
|
|
fb_register_client(&fb_noti_block);
|
|
|
|
lpass_update_lpclock(LPCLK_CTRLID_LEGACY|LPCLK_CTRLID_OFFLOAD, false);
|
|
|
|
#ifdef USE_AUD_DEVFREQ
|
|
lpass.cpu_qos = 0;
|
|
lpass.kfc_qos = 0;
|
|
lpass.mif_qos = 0;
|
|
lpass.int_qos = 0;
|
|
pm_qos_add_request(&lpass.aud_cluster1_qos, PM_QOS_CLUSTER1_FREQ_MIN, 0);
|
|
pm_qos_add_request(&lpass.aud_cluster0_qos, PM_QOS_CLUSTER0_FREQ_MIN, 0);
|
|
pm_qos_add_request(&lpass.aud_mif_qos, PM_QOS_BUS_THROUGHPUT, 0);
|
|
pm_qos_add_request(&lpass.aud_int_qos, PM_QOS_DEVICE_THROUGHPUT, 0);
|
|
#endif
|
|
|
|
exynos_pm_register_notifier(&lpass_lpa_nb);
|
|
|
|
pr_info("%s: LPASS driver was registerd successfully\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int lpass_remove(struct platform_device *pdev)
|
|
{
|
|
#ifdef CONFIG_SND_SAMSUNG_IOMMU
|
|
iommu_detach_device(lpass.domain, &pdev->dev);
|
|
iommu_domain_free(lpass.domain);
|
|
#else
|
|
if (lpass.sysmmu)
|
|
iounmap(lpass.sysmmu);
|
|
#endif
|
|
#ifdef CONFIG_PM
|
|
pm_runtime_disable(&pdev->dev);
|
|
#else
|
|
lpass_disable();
|
|
#endif
|
|
iounmap(lpass.regs);
|
|
iounmap(lpass.regs_s);
|
|
#ifndef CONFIG_SOC_EXYNOS8890
|
|
iounmap(lpass.mem);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int lpass_runtime_suspend(struct device *dev)
|
|
{
|
|
pr_debug("%s entered\n", __func__);
|
|
|
|
lpass_disable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lpass_runtime_resume(struct device *dev)
|
|
{
|
|
pr_debug("%s entered\n", __func__);
|
|
|
|
lpass_enable();
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const int lpass_ver_data[] = {
|
|
[LPASS_VER_000100] = LPASS_VER_000100,
|
|
[LPASS_VER_010100] = LPASS_VER_010100,
|
|
[LPASS_VER_100100] = LPASS_VER_100100,
|
|
[LPASS_VER_110100] = LPASS_VER_110100,
|
|
[LPASS_VER_270120] = LPASS_VER_270120,
|
|
[LPASS_VER_370100] = LPASS_VER_370100,
|
|
[LPASS_VER_370200] = LPASS_VER_370200,
|
|
[LPASS_VER_370210] = LPASS_VER_370210,
|
|
};
|
|
|
|
static struct platform_device_id lpass_driver_ids[] = {
|
|
{
|
|
.name = "samsung-lpass",
|
|
}, {
|
|
.name = "samsung-audss",
|
|
}, {},
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, lpass_driver_ids);
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id exynos_lpass_match[] = {
|
|
{
|
|
.compatible = "samsung,exynos8890-lpass",
|
|
.data = &lpass_ver_data[LPASS_VER_370210],
|
|
}, {
|
|
.compatible = "samsung,exynos7580-lpass",
|
|
.data = &lpass_ver_data[LPASS_VER_270120],
|
|
}, {
|
|
.compatible = "samsung,exynos7420-lpass",
|
|
.data = &lpass_ver_data[LPASS_VER_370210],
|
|
}, {
|
|
.compatible = "samsung,exynos5433-lpass",
|
|
.data = &lpass_ver_data[LPASS_VER_370200],
|
|
}, {
|
|
.compatible = "samsung,exynos5430-lpass",
|
|
.data = &lpass_ver_data[LPASS_VER_370100],
|
|
}, {
|
|
.compatible = "samsung,exynos5-audss",
|
|
.data = &lpass_ver_data[LPASS_VER_110100],
|
|
}, {},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, exynos_lpass_match);
|
|
#endif
|
|
|
|
static const struct dev_pm_ops lpass_pmops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(
|
|
lpass_suspend,
|
|
lpass_resume
|
|
)
|
|
SET_RUNTIME_PM_OPS(
|
|
lpass_runtime_suspend,
|
|
lpass_runtime_resume,
|
|
NULL
|
|
)
|
|
};
|
|
|
|
static struct platform_driver lpass_driver = {
|
|
.probe = lpass_probe,
|
|
.remove = lpass_remove,
|
|
.id_table = lpass_driver_ids,
|
|
.driver = {
|
|
.name = "samsung-lpass",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(exynos_lpass_match),
|
|
.pm = &lpass_pmops,
|
|
},
|
|
};
|
|
|
|
static int __init lpass_driver_init(void)
|
|
{
|
|
return platform_driver_register(&lpass_driver);
|
|
}
|
|
fs_initcall(lpass_driver_init);
|
|
|
|
#ifdef CONFIG_PM
|
|
static int lpass_driver_rpm_begin(void)
|
|
{
|
|
pr_debug("%s entered\n", __func__);
|
|
|
|
if (is_new_ass())
|
|
pm_runtime_put_sync(&lpass.pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
late_initcall(lpass_driver_rpm_begin);
|
|
#endif
|
|
|
|
/* Module information */
|
|
MODULE_AUTHOR("Yeongman Seo, <yman.seo@samsung.com>");
|
|
MODULE_DESCRIPTION("Samsung Low Power Audio Subsystem Interface");
|
|
MODULE_ALIAS("platform:samsung-lpass");
|
|
MODULE_LICENSE("GPL");
|