blob: a5387b1d2ac8a3ba9ebcbbb72537370d371cf9a7 [file] [log] [blame]
/* sound/soc/samsung/abox/abox_effect.c
*
* ALSA SoC Audio Layer - Samsung Abox Effect driver
*
* Copyright (c) 2016 Samsung Electronics Co. Ltd.
*
* 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.
*/
//#define DEBUG
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/samsung/abox.h>
#include "abox.h"
#include "abox_util.h"
#include "abox_effect.h"
struct abox_ctl_eq_switch {
unsigned int base;
unsigned int count;
unsigned int min;
unsigned int max;
};
static int abox_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct device *dev = codec->dev;
struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value;
dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = params->count;
uinfo->value.integer.min = params->min;
uinfo->value.integer.max = params->max;
return 0;
}
static int abox_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct device *dev = codec->dev;
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value;
int i;
dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name);
pm_runtime_get_sync(dev);
for (i = 0; i < params->count; i++) {
unsigned int val;
snd_soc_component_read(component, params->base + PARAM_OFFSET + (i * sizeof(u32)), &val);
ucontrol->value.integer.value[i] = val;
dev_dbg(dev, "%s[%d] = %u\n", kcontrol->id.name, i, val);
}
pm_runtime_put_autosuspend(dev);
return 0;
}
static int abox_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct device *dev = codec->dev;
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value;
int i;
dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name);
pm_runtime_get_sync(dev);
for (i = 0; i < params->count; i++) {
unsigned int val = ucontrol->value.integer.value[i];
snd_soc_component_write(component, params->base + PARAM_OFFSET + (i * sizeof(u32)), val);
dev_dbg(dev, "%s[%d] <= %u\n", kcontrol->id.name, i, val);
}
snd_soc_component_write(component, params->base, CHANGE_BIT);
pm_runtime_put_autosuspend(dev);
return 0;
}
#define ABOX_CTL_EQ_SWITCH(xname, xbase, xcount, xmin, xmax) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = abox_ctl_info, .get = abox_ctl_get, \
.put = abox_ctl_put, .private_value = \
((unsigned long)&(struct abox_ctl_eq_switch) \
{.base = xbase, .count = xcount, \
.min = xmin, .max = xmax}) }
#define DECLARE_ABOX_CTL_EQ_SWITCH(name, prefix) \
ABOX_CTL_EQ_SWITCH(name, prefix##_BASE, \
prefix##_MAX_COUNT, prefix##_VALUE_MIN, \
prefix##_VALUE_MAX)
static const struct snd_kcontrol_new abox_effect_controls[] = {
DECLARE_ABOX_CTL_EQ_SWITCH("SA data", SA),
DECLARE_ABOX_CTL_EQ_SWITCH("Audio DHA data", MYSOUND),
DECLARE_ABOX_CTL_EQ_SWITCH("VSP data", VSP),
DECLARE_ABOX_CTL_EQ_SWITCH("LRSM data", LRSM),
DECLARE_ABOX_CTL_EQ_SWITCH("MSP data", MYSPACE),
DECLARE_ABOX_CTL_EQ_SWITCH("ESA BBoost data", BB),
DECLARE_ABOX_CTL_EQ_SWITCH("ESA EQ data", EQ),
DECLARE_ABOX_CTL_EQ_SWITCH("NXP BDL data", NXPBDL),
DECLARE_ABOX_CTL_EQ_SWITCH("NXP RVB ctx data", NXPRVB_CTX),
DECLARE_ABOX_CTL_EQ_SWITCH("NXP RVB param data", NXPRVB_PARAM),
};
#define ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(name, reg) \
(name##_BASE <= reg && reg <= name##_BASE + PARAM_OFFSET + (name##_MAX_COUNT * sizeof(u32)))
static bool abox_effect_accessible_reg(struct device *dev, unsigned int reg) {
return ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(SA, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(MYSOUND, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(VSP, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(LRSM, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(MYSPACE, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(BB, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(EQ, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(ELPE, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(NXPBDL, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(NXPRVB_CTX, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG_CONDITION(NXPRVB_PARAM, reg);
}
#define ABOX_EFFECT_VOLATILE_REG_CONDITION(name, reg) (name##_BASE == reg)
static bool abox_effect_volatile_reg(struct device *dev, unsigned int reg) {
return ABOX_EFFECT_VOLATILE_REG_CONDITION(SA, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(MYSOUND, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(VSP, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(LRSM, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(MYSPACE, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(BB, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(EQ, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(ELPE, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(NXPBDL, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(NXPRVB_CTX, reg) ||
ABOX_EFFECT_VOLATILE_REG_CONDITION(NXPRVB_PARAM, reg);
}
static const struct regmap_config abox_effect_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = ABOX_MAX_REGISTERS,
.writeable_reg = abox_effect_accessible_reg,
.readable_reg = abox_effect_accessible_reg,
.volatile_reg = abox_effect_volatile_reg,
.cache_type = REGCACHE_FLAT,
};
static struct regmap * abox_effect_get_regmap(struct device *dev)
{
struct abox_effect_data *data = dev_get_drvdata(dev);
return data->regmap;
}
static const struct snd_soc_codec_driver abox_effect = {
.controls = abox_effect_controls,
.num_controls = ARRAY_SIZE(abox_effect_controls),
.get_regmap = abox_effect_get_regmap,
.idle_bias_off = true,
.suspend_bias_off = true,
.ignore_pmdown_time = true,
};
static int abox_effect_runtime_suspend(struct device *dev)
{
struct abox_effect_data *data = dev_get_drvdata(dev);
dev_dbg(dev, "%s\n", __func__);
regcache_cache_only(data->regmap, true);
regcache_mark_dirty(data->regmap);
return 0;
}
static int abox_effect_runtime_resume(struct device *dev)
{
struct abox_effect_data *data = dev_get_drvdata(dev);
dev_dbg(dev, "%s\n", __func__);
regcache_cache_only(data->regmap, false);
regcache_sync(data->regmap);
return 0;
}
const struct dev_pm_ops samsung_abox_effect_pm = {
SET_RUNTIME_PM_OPS(abox_effect_runtime_suspend, abox_effect_runtime_resume, NULL)
};
static int samsung_abox_effect_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *np_tmp;
struct abox_effect_data *data;
dev_dbg(dev, "%s\n", __func__);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (data == NULL) {
return -ENOMEM;
}
platform_set_drvdata(pdev, data);
np_tmp = of_parse_phandle(np, "abox", 0);
if (!np_tmp) {
dev_err(dev, "Failed to get abox device node\n");
return -EPROBE_DEFER;
}
data->pdev_abox = of_find_device_by_node(np_tmp);
if (!data->pdev_abox) {
dev_err(dev, "Failed to get abox platform device\n");
return -EPROBE_DEFER;
}
data->pdev = pdev;
data->base = devm_not_request_and_map(pdev, "reg", 0, NULL, NULL);
if (IS_ERR(data->base)) {
dev_err(dev, "base address request failed: %ld\n", PTR_ERR(data->base));
return PTR_ERR(data->base);
}
data->regmap = devm_regmap_init_mmio(dev, data->base, &abox_effect_regmap_config);
if (IS_ERR(data->regmap)) {
dev_err(dev, "regmap init failed: %ld\n", PTR_ERR(data->regmap));
return PTR_ERR(data->regmap);
}
pm_runtime_enable(dev);
pm_runtime_set_autosuspend_delay(dev, 1000);
pm_runtime_use_autosuspend(dev);
return snd_soc_register_codec(&pdev->dev, &abox_effect, NULL, 0);
}
static int samsung_abox_effect_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_dbg(dev, "%s\n", __func__);
pm_runtime_disable(dev);
snd_soc_unregister_codec(&pdev->dev);
return 0;
}
static const struct of_device_id samsung_abox_effect_match[] = {
{
.compatible = "samsung,abox-effect",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_effect_match);
static struct platform_driver samsung_abox_effect_driver = {
.probe = samsung_abox_effect_probe,
.remove = samsung_abox_effect_remove,
.driver = {
.name = "samsung-abox-effect",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_effect_match),
.pm = &samsung_abox_effect_pm,
},
};
module_platform_driver(samsung_abox_effect_driver);
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box Effect Driver");
MODULE_ALIAS("platform:samsung-abox-effect");
MODULE_LICENSE("GPL");