kernel_samsung_a34x-permissive/sound/soc/codecs/cirrus-amp.c

420 lines
10 KiB
C
Raw Normal View History

/*
* Extended support for Cirrus Logic Smart Amplifiers
*
* Copyright 2017 Cirrus Logic
*
* Author: David Rhodes <david.rhodes@cirrus.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/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/cirrus/core.h>
#include <sound/cirrus/big_data.h>
#include <sound/cirrus/calibration.h>
#include <sound/cirrus/power.h>
#include "wmfw.h"
#include "wm_adsp.h"
struct class *cirrus_amp_class;
EXPORT_SYMBOL_GPL(cirrus_amp_class);
struct cirrus_amp_group *amp_group;
static struct cirrus_cal_ops *cirrus_cal_ops[3] = {
&cirrus_cspl_cal_ops,
&cirrus_cspl_cal_ops,
&cirrus_cs35l43_cal_ops
};
struct cirrus_amp *cirrus_get_amp_from_suffix(const char *suffix)
{
int i;
if (amp_group == NULL || (amp_group->num_amps == 0))
return NULL;
pr_debug("%s: suffix = %s\n", __func__, suffix);
for (i = 0; i < amp_group->num_amps; i++) {
if (amp_group->amps[i].bd.bd_suffix) {
pr_debug("%s: comparing %s & %s\n", __func__,
amp_group->amps[i].bd.bd_suffix, suffix);
if (!strcmp(amp_group->amps[i].bd.bd_suffix, suffix))
return &amp_group->amps[i];
}
}
for (i = 0; i < amp_group->num_amps; i++) {
pr_debug("%s: comparing %s & %s\n", __func__,
amp_group->amps[i].mfd_suffix, suffix);
if (!strcmp(amp_group->amps[i].mfd_suffix, suffix))
return &amp_group->amps[i];
}
return NULL;
}
EXPORT_SYMBOL_GPL(cirrus_get_amp_from_suffix);
void cirrus_amp_register_i2c_error_callback(const char *suffix, void *func)
{
struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix);
if (amp)
amp->i2c_callback = func;
}
EXPORT_SYMBOL_GPL(cirrus_amp_register_i2c_error_callback);
void cirrus_amp_register_error_callback(const char *suffix, void *func)
{
struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix);
if (amp)
amp->error_callback = func;
}
EXPORT_SYMBOL_GPL(cirrus_amp_register_error_callback);
int cirrus_amp_add(const char *mfd_suffix, struct cirrus_amp_config cfg)
{
struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix);
if (amp) {
dev_info(amp_group->cal_dev,
"Amp added, suffix: %s dsp_part_name: %s\n",
mfd_suffix, cfg.dsp_part_name);
amp->component = cfg.component;
amp->regmap = cfg.regmap;
amp->dsp_part_name = cfg.dsp_part_name;
amp->num_pre_configs = cfg.num_pre_configs;
amp->num_post_configs = cfg.num_post_configs;
amp->mbox_cmd = cfg.mbox_cmd;
amp->mbox_sts = cfg.mbox_sts;
amp->global_en = cfg.global_en;
amp->global_en_mask = cfg.global_en_mask;
amp->vimon_alg_id = cfg.vimon_alg_id;
amp->pwr.target_temp = cfg.target_temp;
amp->pwr.exit_temp = cfg.exit_temp;
amp->perform_vimon_cal = cfg.perform_vimon_cal;
amp->calibration_disable = cfg.calibration_disable;
amp->cal_vpk_id = cfg.cal_vpk_id ? cfg.cal_vpk_id :
CIRRUS_CAL_RTLOG_ID_V_PEAK;
amp->cal_ipk_id = cfg.cal_ipk_id ? cfg.cal_ipk_id :
CIRRUS_CAL_RTLOG_ID_I_PEAK;
amp->halo_alg_id = cfg.halo_alg_id ? cfg.halo_alg_id :
CIRRUS_AMP_ALG_ID_HALO;
amp->bd.bd_alg_id = cfg.bd_alg_id ? cfg.bd_alg_id :
CIRRUS_AMP_ALG_ID_CSPL;
amp->bd.bd_prefix = cfg.bd_prefix ? cfg.bd_prefix :
"BDLOG_";
amp->cal_vsc_ub = cfg.cal_vsc_ub ? cfg.cal_vsc_ub :
CIRRUS_CAL_VIMON_CAL_VSC_UB;
amp->cal_vsc_lb = cfg.cal_vsc_lb ? cfg.cal_vsc_lb :
CIRRUS_CAL_VIMON_CAL_VSC_LB;
amp->cal_isc_ub = cfg.cal_isc_ub ? cfg.cal_isc_ub :
CIRRUS_CAL_VIMON_CAL_ISC_UB;
amp->cal_isc_lb = cfg.cal_isc_lb ? cfg.cal_isc_lb :
CIRRUS_CAL_VIMON_CAL_ISC_LB;
amp->bd.max_temp_limit = cfg.bd_max_temp ?
cfg.bd_max_temp - 1 : 99;
amp->default_redc = cfg.default_redc ? cfg.default_redc :
CIRRUS_CAL_RDC_DEFAULT;
amp->cal_ops = cfg.cal_ops_idx ? cirrus_cal_ops[cfg.cal_ops_idx] :
cirrus_cal_ops[CIRRUS_CAL_CSPL_CAL_OPS_IDX];
amp->pre_config = kcalloc(cfg.num_pre_configs,
sizeof(struct reg_sequence),
GFP_KERNEL);
amp->post_config = kcalloc(cfg.num_post_configs,
sizeof(struct reg_sequence),
GFP_KERNEL);
amp->amp_reinit = cfg.amp_reinit;
amp->runtime_pm = cfg.runtime_pm;
amp_group->pwr_enable |= cfg.pwr_enable;
memcpy(amp->pre_config, cfg.pre_config,
sizeof(struct reg_sequence) * cfg.num_pre_configs);
memcpy(amp->post_config, cfg.post_config,
sizeof(struct reg_sequence) * cfg.num_post_configs);
} else {
dev_err(amp_group->cal_dev,
"No amp with suffix %s registered\n", mfd_suffix);
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(cirrus_amp_add);
int cirrus_amp_read_ctl(struct cirrus_amp *amp, const char *name,
int type, unsigned int id, unsigned int *value)
{
struct wm_adsp *dsp;
unsigned int tmp;
int ret = 0;
if (amp && amp->component)
dsp = snd_soc_component_get_drvdata(amp->component);
else
return -EINVAL;
if (dsp) {
ret = wm_adsp_read_ctl(dsp, name, type, id, (void *)&tmp, 4);
*value = (tmp & 0xff0000) >> 8 |
(tmp & 0xff00) << 8 |
(tmp & 0xff000000) >> 24;
}
return ret;
}
EXPORT_SYMBOL_GPL(cirrus_amp_read_ctl);
int cirrus_amp_write_ctl(struct cirrus_amp *amp, const char *name,
int type, unsigned int id, unsigned int value)
{
struct wm_adsp *dsp;
unsigned int tmp;
if (amp && amp->component)
dsp = snd_soc_component_get_drvdata(amp->component);
else
return -EINVAL;
tmp = (value & 0xff0000) >> 8 |
(value & 0xff00) << 8 |
(value & 0xff000000) >> 24 |
(value & 0xff) << 24;
if (dsp)
return wm_adsp_write_ctl(dsp, name, type, id, (void *)&tmp, 4);
return 0;
}
EXPORT_SYMBOL_GPL(cirrus_amp_write_ctl);
static const struct of_device_id cirrus_amp_of_match[] = {
{ .compatible = "cirrus-amp", },
{},
};
MODULE_DEVICE_TABLE(of, cirrus_amp_of_match);
static int cirrus_amp_probe(struct platform_device *pdev)
{
struct device_node *np;
struct device_node *amp_node;
const char **mfd_suffixes;
const char **bd_suffixes;
bool v_val_separate[CIRRUS_MAX_AMPS];
int ret = 0, num = 0, num_amps, i, j;
cirrus_amp_class = class_create(THIS_MODULE, "cirrus");
if (IS_ERR(cirrus_amp_class)) {
ret = PTR_ERR(cirrus_amp_class);
pr_err("%s: Unable to register cirrus_amp class (%d)",
__func__, ret);
return ret;
}
for_each_matching_node(np, cirrus_amp_of_match)
num++;
if (num != 1) {
pr_info("%s: Exactly 1 OF entry is allowed (%d detected)\n",
__func__, num);
ret = -EINVAL;
goto class_err;
}
np = of_find_matching_node(NULL, cirrus_amp_of_match);
if (!np) {
pr_err("%s: Device node required\n", __func__);
ret = -ENODEV;
goto class_err;
}
num_amps = of_count_phandle_with_args(np, "cirrus,amps", NULL);
if (num_amps <= 0) {
pr_err("%s: Failed to parse 'cirrus,amps'\n", __func__);
ret = -ENODEV;
goto class_err;
}
amp_group = kzalloc(sizeof(struct cirrus_amp_group) +
sizeof(struct cirrus_amp) * num_amps,
GFP_KERNEL);
mfd_suffixes = kcalloc(num_amps, sizeof(char *), GFP_KERNEL);
bd_suffixes = kcalloc(num_amps, sizeof(char *), GFP_KERNEL);
amp_group->num_amps = num_amps;
for (i = 0; i < num_amps; i++) {
amp_node = of_parse_phandle(np, "cirrus,amps", i);
if (IS_ERR(amp_node)) {
pr_err("%s: Failed to parse 'cirrus,amps' (%d)\n",
__func__, i);
ret = PTR_ERR(amp_node);
goto suffix_free;
}
pr_debug("%s: Found linked amp: %s\n", __func__,
amp_node->full_name);
ret = of_property_read_string(amp_node, "cirrus,mfd-suffix",
&mfd_suffixes[i]);
if (ret < 0) {
pr_err("%s: No MFD suffix found for amp: %s\n",
__func__, amp_node->full_name);
of_node_put(amp_node);
goto suffix_free;
}
ret = of_property_read_string(amp_node, "cirrus,bd-suffix",
&bd_suffixes[i]);
if (ret < 0)
pr_debug("%s: No BD suffix found for amp: %s\n",
__func__, amp_node->full_name);
v_val_separate[i] = of_property_read_bool(amp_node,
"cirrus,v-val_separate");
of_node_put(amp_node);
}
for (i = 0; i < num_amps; i++) {
for (j = 0; j < num_amps; j++) {
if (i == j)
continue;
if (strcmp(mfd_suffixes[i], mfd_suffixes[j]) == 0) {
pr_err("%s: MFD suffixes must be unique\n",
__func__);
pr_err("%s: Found duplicate suffix: %s\n",
__func__, mfd_suffixes[i]);
ret = -EINVAL;
goto suffix_err;
}
/* bd_suffixes can be empty but must be unique */
if (bd_suffixes[i] && bd_suffixes[j] &&
(strcmp(bd_suffixes[i], bd_suffixes[j]) == 0)) {
pr_err("%s: BD suffixes must be unique\n",
__func__);
pr_err("%s: Found duplicate suffix: %s\n",
__func__, bd_suffixes[i]);
ret = -EINVAL;
goto suffix_err;
}
}
pr_info("%s: Found MFD suffix: %s\n", __func__,
mfd_suffixes[i]);
amp_group->amps[i].mfd_suffix =
kstrdup(mfd_suffixes[i], GFP_KERNEL);
if (bd_suffixes[i]) {
pr_info("%s: Found BD suffix: %s\n", __func__,
bd_suffixes[i]);
amp_group->amps[i].bd.bd_suffix =
kstrdup(bd_suffixes[i], GFP_KERNEL);
}
if (v_val_separate[i])
amp_group->amps[i].v_val_separate = true;
}
ret = cirrus_bd_init();
if (ret < 0) {
pr_err("%s: Error in BD init (%d)\n", __func__, ret);
goto suffix_err;
}
ret = cirrus_cal_init();
if (ret < 0) {
pr_err("%s: Error in CAL init (%d)\n", __func__, ret);
cirrus_bd_exit();
goto suffix_err;
}
ret = cirrus_pwr_init();
if (ret < 0) {
pr_err("%s: Error in PWR init (%d)\n", __func__, ret);
cirrus_bd_exit();
cirrus_cal_exit();
goto suffix_err;
}
ret = 0;
goto suffix_free;
suffix_err:
for (i = 0; i < num_amps; i++) {
kfree(amp_group->amps[i].mfd_suffix);
kfree(amp_group->amps[i].bd.bd_suffix);
}
suffix_free:
kfree(mfd_suffixes);
kfree(bd_suffixes);
if (ret < 0)
kfree(amp_group);
class_err:
if (ret < 0)
class_destroy(cirrus_amp_class);
return ret;
}
static int cirrus_amp_remove(struct platform_device *pdev)
{
int i;
cirrus_cal_exit();
cirrus_bd_exit();
cirrus_pwr_exit();
for (i = 0; i < amp_group->num_amps; i++) {
kfree(amp_group->amps[i].pre_config);
kfree(amp_group->amps[i].post_config);
}
kfree(amp_group);
class_destroy(cirrus_amp_class);
return 0;
}
static struct platform_driver cirrus_amp_driver = {
.driver = {
.name = "cirrus-amp",
.of_match_table = cirrus_amp_of_match,
},
.probe = cirrus_amp_probe,
.remove = cirrus_amp_remove,
};
module_platform_driver(cirrus_amp_driver);
MODULE_AUTHOR("David Rhodes <David.Rhodes@cirrus.com>");
MODULE_DESCRIPTION("Cirrus Amp driver");
MODULE_LICENSE("GPL");