blob: 3f7caf931b2f3bbd5f64ad56ac9f7bd4b06fda11 [file] [log] [blame]
/*
* mt6392-codec.c -- MT6392 ALSA SoC codec driver
*
* Copyright (c) 2016 MediaTek Inc.
*
* 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 "mt6392-codec.h"
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <sound/soc.h>
#include <sound/tlv.h>
/*
* Class D: has HW Trim mode and SW Trim mode
* Class AB: use the trim offset derived from Class D HW Trim
*
* The option used to choose the trim mode of Class D
*/
static int int_spk_amp_enable_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct mt6392_codec_priv *codec_data =
snd_soc_component_get_drvdata(component);
uint32_t reg_value = snd_soc_component_read32(codec_data->codec, SPK_CON0);
ucontrol->value.integer.value[0] = reg_value & BIT(0) ? 1 : 0;
return 0;
}
static int int_spk_amp_enable_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct mt6392_codec_priv *codec_data =
snd_soc_component_get_drvdata(component);
if (ucontrol->value.integer.value[0] == 0)
mt6392_int_spk_turn_off(codec_data->codec);
else
mt6392_int_spk_turn_on(codec_data->codec);
return 0;
}
#define USE_HW_TRIM_CLASS_D
/* Int Spk Amp Playback Volume
* {mute, 0, 4, 5, 6, 7, 8, ..., 17} dB
*/
static const unsigned int int_spk_amp_gain_tlv[] = {
TLV_DB_RANGE_HEAD(3),
0,
0,
TLV_DB_SCALE_ITEM(0, 0, 1),
1,
1,
TLV_DB_SCALE_ITEM(0, 0, 0),
2,
15,
TLV_DB_SCALE_ITEM(400, 100, 0),
};
/* Audio_Speaker_PGA_gain
* {mute, 0, 4, 5, 6, 7, 8, ..., 17} dB
*/
static const char *const int_spk_amp_gain_text[] = {
"MUTE",
"+0dB",
"+4dB",
"+5dB",
"+6dB",
"+7dB",
"+8dB",
"+9dB",
"+10dB",
"+11dB",
"+12dB",
"+13dB",
"+14dB",
"+15dB",
"+16dB",
"+17dB",
};
static const struct soc_enum int_spk_amp_gain_enum =
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(int_spk_amp_gain_text),
int_spk_amp_gain_text);
static int int_spk_amp_gain_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct mt6392_codec_priv *codec_data =
snd_soc_component_get_drvdata(component);
uint32_t value = 0;
value = codec_data->spk_amp_gain;
ucontrol->value.integer.value[0] = value;
return 0;
}
static int int_spk_amp_gain_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct mt6392_codec_priv *codec_data =
snd_soc_component_get_drvdata(component);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
uint32_t value = ucontrol->value.integer.value[0];
if (value >= e->items)
return -EINVAL;
snd_soc_component_update_bits(codec_data->codec, SPK_CON9, GENMASK(11, 8),
value << 8);
codec_data->spk_amp_gain = value;
dev_dbg(codec_data->codec->dev, "%s value = %u\n", __func__, value);
return 0;
}
/* Internal speaker mode (AB/D) */
static const char *const int_spk_amp_mode_texts[] = {
"Class D",
"Class AB",
};
static SOC_ENUM_SINGLE_EXT_DECL(mt6392_speaker_mode_enum,
int_spk_amp_mode_texts);
static int mt6392_spk_mode_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct mt6392_codec_priv *codec_data =
snd_soc_component_get_drvdata(component);
ucontrol->value.integer.value[0] = codec_data->speaker_mode;
return 0;
}
static int mt6392_spk_mode_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct mt6392_codec_priv *codec_data =
snd_soc_component_get_drvdata(component);
int ret = 0;
uint32_t mode = ucontrol->value.integer.value[0];
switch (mode) {
case MT6392_CLASS_D:
case MT6392_CLASS_AB:
codec_data->speaker_mode = ucontrol->value.integer.value[0];
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/* Check OC Flag */
static const char *const mt6392_speaker_oc_flag_texts[] = {
"NoOverCurrent",
"OverCurrent"
};
static SOC_ENUM_SINGLE_EXT_DECL(mt6392_speaker_oc_flag_enum,
mt6392_speaker_oc_flag_texts);
static int mt6392_speaker_oc_flag_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct mt6392_codec_priv *codec_data =
snd_soc_component_get_drvdata(component);
uint32_t reg_value = snd_soc_component_read32(codec_data->codec, SPK_CON6);
if (codec_data->speaker_mode == MT6392_CLASS_AB)
ucontrol->value.integer.value[0] =
(reg_value & BIT(15)) ? 1 : 0;
else
ucontrol->value.integer.value[0] =
(reg_value & BIT(14)) ? 1 : 0;
return 0;
}
static const struct snd_kcontrol_new mt6392_codec_controls[] = {
/* Internal speaker PGA gain control */
SOC_SINGLE_BOOL_EXT("Int Spk Amp Enable", 0, int_spk_amp_enable_get,
int_spk_amp_enable_put),
SOC_SINGLE_TLV("Int Spk Amp Playback Volume", SPK_CON9, 8, 15, 0,
int_spk_amp_gain_tlv),
/* Audio_Speaker_PGA_gain */
SOC_ENUM_EXT("Audio_Speaker_PGA_gain", int_spk_amp_gain_enum,
int_spk_amp_gain_get, int_spk_amp_gain_put),
/* Internal speaker mode (AB/D) */
SOC_ENUM_EXT("Int Spk Amp Mode", mt6392_speaker_mode_enum,
mt6392_spk_mode_get, mt6392_spk_mode_put),
/* Check OC Flag */
SOC_ENUM_EXT("Speaker_OC_Flag", mt6392_speaker_oc_flag_enum,
mt6392_speaker_oc_flag_get, NULL),
};
static void mt6392_codec_get_spk_trim_offset(struct snd_soc_component *codec)
{
struct mt6392_codec_priv *codec_data = snd_soc_component_get_drvdata(codec);
/* turn on spk (class D) and hw trim */
snd_soc_component_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E);
snd_soc_component_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4);
snd_soc_component_update_bits(codec, SPK_CON11, 0xFFFF, 0x0055);
snd_soc_component_update_bits(codec, SPK_CON9, 0xF0FF, 0x2018);
snd_soc_component_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414);
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3409);
usleep_range(20000, 21000);
/* save trim offset */
codec_data->spk_trim_offset =
snd_soc_component_read32(codec, SPK_CON1) & GENMASK(4, 0);
/* turn off trim */
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3401);
snd_soc_component_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000);
usleep_range(2000, 3000);
/* turn off spk */
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0x0000);
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3400);
snd_soc_component_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x0000);
}
static void mt6392_int_spk_on_with_trim(struct snd_soc_component *codec)
{
#if defined(USE_HW_TRIM_CLASS_D)
/* turn on spk (class D) and hw trim */
snd_soc_component_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E);
snd_soc_component_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4);
snd_soc_component_update_bits(codec, SPK_CON11, 0xFFFF, 0x0055);
snd_soc_component_update_bits(codec, SPK_CON9, 0xF0FF, 0x2018);
snd_soc_component_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414);
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3409);
usleep_range(20000, 21000);
/* turn off trim */
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3401);
snd_soc_component_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000);
#else
struct mt6392_codec_priv *codec_data = snd_soc_component_get_drvdata(codec);
/* turn on spk (class D) */
snd_soc_component_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E);
snd_soc_component_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4);
snd_soc_component_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414);
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3001);
snd_soc_component_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000);
/* enable sw trim */
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0x0009);
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0x0001);
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0x0283);
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0x0281);
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0x2A81);
snd_soc_component_update_bits(codec, SPK_CON1, 0xFFFF, 0x6000);
/* class D and class AB use the same trim offset value */
snd_soc_component_update_bits(codec, SPK_CON1, GENMASK(12, 8),
(codec_data->spk_trim_offset << 8));
/* trim stop */
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0xAA81);
#endif
usleep_range(2000, 3000);
}
int mt6392_int_spk_turn_on(struct snd_soc_component *codec)
{
struct mt6392_codec_priv *codec_data = snd_soc_component_get_drvdata(codec);
int ret = 0;
dev_dbg(codec->dev, "%s\n", __func__);
switch (codec_data->speaker_mode) {
case MT6392_CLASS_D:
mt6392_int_spk_on_with_trim(codec);
break;
case MT6392_CLASS_AB:
snd_soc_component_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E);
snd_soc_component_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4);
snd_soc_component_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414);
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3005);
snd_soc_component_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000);
snd_soc_component_update_bits(codec, SPK_CON1, 0xFFFF, 0x6000);
/* class D and class AB use the same trim offset value */
snd_soc_component_update_bits(codec, SPK_CON1, GENMASK(12, 8),
(codec_data->spk_trim_offset << 8));
usleep_range(2000, 3000);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(mt6392_int_spk_turn_on);
int mt6392_int_spk_turn_off(struct snd_soc_component *codec)
{
struct mt6392_codec_priv *codec_data = snd_soc_component_get_drvdata(codec);
int ret = 0;
dev_dbg(codec->dev, "%s\n", __func__);
switch (codec_data->speaker_mode) {
case MT6392_CLASS_D:
snd_soc_component_update_bits(codec, SPK_CON12, 0xFFFF, 0x0000);
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3400);
snd_soc_component_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x0000);
break;
case MT6392_CLASS_AB:
snd_soc_component_update_bits(codec, SPK_CON0, 0xFFFF, 0x3404);
snd_soc_component_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x0000);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(mt6392_int_spk_turn_off);
static int mt6392_int_spk_amp_wevent(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm);
dev_dbg(codec->dev, "%s, event %d\n", __func__, event);
switch (event) {
case SND_SOC_DAPM_POST_PMU:
mt6392_int_spk_turn_on(codec);
break;
case SND_SOC_DAPM_PRE_PMD:
mt6392_int_spk_turn_off(codec);
break;
default:
break;
}
return 0;
}
static const struct snd_soc_dapm_widget mt6392_codec_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Int Spk Amp", mt6392_int_spk_amp_wevent),
};
#ifdef CONFIG_DEBUG_FS
struct mt6392_codec_reg_attr {
uint32_t offset;
char *name;
};
#define DUMP_REG_ENTRY(reg) \
{ \
reg, #reg \
}
static const struct mt6392_codec_reg_attr mt6392_codec_dump_reg_list[] = {
DUMP_REG_ENTRY(SPK_CON0),
DUMP_REG_ENTRY(SPK_CON1),
DUMP_REG_ENTRY(SPK_CON2),
DUMP_REG_ENTRY(SPK_CON3),
DUMP_REG_ENTRY(SPK_CON4),
DUMP_REG_ENTRY(SPK_CON5),
DUMP_REG_ENTRY(SPK_CON6),
DUMP_REG_ENTRY(SPK_CON7),
DUMP_REG_ENTRY(SPK_CON8),
DUMP_REG_ENTRY(SPK_CON9),
DUMP_REG_ENTRY(SPK_CON10),
DUMP_REG_ENTRY(SPK_CON11),
DUMP_REG_ENTRY(SPK_CON12),
};
static ssize_t mt6392_codec_debug_read(struct file *file,
char __user * user_buf, size_t count,
loff_t * pos)
{
struct mt6392_codec_priv *codec_data = file->private_data;
ssize_t ret, i;
char *buf;
int n = 0;
if (*pos < 0 || !count)
return -EINVAL;
buf = kmalloc(count, GFP_KERNEL);
if (!buf)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(mt6392_codec_dump_reg_list); i++) {
n += scnprintf(buf + n, count - n, "%s = 0x%x\n",
mt6392_codec_dump_reg_list[i].name,
snd_soc_component_read32(codec_data->codec,
mt6392_codec_dump_reg_list[i].
offset));
}
ret = simple_read_from_buffer(user_buf, count, pos, buf, n);
kfree(buf);
return ret;
}
static const struct file_operations mt6392_codec_debug_ops = {
.open = simple_open,
.read = mt6392_codec_debug_read,
.llseek = default_llseek,
};
#endif
static void mt6392_codec_init_regs(struct mt6392_codec_priv *codec_data)
{
struct snd_soc_component *codec = codec_data->codec;
dev_dbg(codec->dev, "%s\n", __func__);
/* default PGA gain: 12dB */
codec_data->spk_amp_gain = 0xA;
snd_soc_component_update_bits(codec, SPK_CON9, GENMASK(11, 8),
(codec_data->spk_amp_gain) << 8);
}
static int mt6392_codec_parse_dt(struct snd_soc_component *codec)
{
struct mt6392_codec_priv *codec_data = snd_soc_component_get_drvdata(codec);
struct device *dev = codec->dev;
int ret = 0;
ret = of_property_read_u32(dev->of_node, "mediatek,speaker-mode",
&codec_data->speaker_mode);
if (ret) {
dev_warn(dev, "%s fail to read speaker-mode in node %s\n",
__func__, dev->of_node->full_name);
codec_data->speaker_mode = MT6392_CLASS_D;
} else if (codec_data->speaker_mode != MT6392_CLASS_D &&
codec_data->speaker_mode != MT6392_CLASS_AB) {
codec_data->speaker_mode = MT6392_CLASS_D;
}
return ret;
}
/* FIXME:
* attached to the mt8167 codec for now
* there is no dev for mt6392, thus use the dev from mt8167 codec
* need to parse the dt from mt6392 itself
* and detach it into a standalone codec driver
*/
int mt6392_codec_probe(struct snd_soc_component *codec)
{
struct mt6392_codec_priv *codec_data = snd_soc_component_get_drvdata(codec);
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(codec);
int ret = 0;
ret = snd_soc_add_component_controls(codec, mt6392_codec_controls,
ARRAY_SIZE(mt6392_codec_controls));
if (ret < 0)
goto error_probe;
ret = snd_soc_dapm_new_controls(dapm, mt6392_codec_dapm_widgets,
ARRAY_SIZE(mt6392_codec_dapm_widgets));
if (ret < 0)
goto error_probe;
codec_data->codec = codec;
mt6392_codec_parse_dt(codec);
mt6392_codec_init_regs(codec_data);
mt6392_codec_get_spk_trim_offset(codec);
#ifdef CONFIG_DEBUG_FS
codec_data->debugfs = debugfs_create_file("mt6392_codec_regs",
S_IFREG | S_IRUGO, NULL,
codec_data,
&mt6392_codec_debug_ops);
#endif
error_probe:
return ret;
}
EXPORT_SYMBOL_GPL(mt6392_codec_probe);
int mt6392_codec_remove(struct snd_soc_component *codec)
{
#ifdef CONFIG_DEBUG_FS
struct mt6392_codec_priv *codec_data = snd_soc_component_get_drvdata(codec);
debugfs_remove(codec_data->debugfs);
#endif
dev_dbg(codec->dev, "%s\n", __func__);
return 0;
}
EXPORT_SYMBOL_GPL(mt6392_codec_remove);
MODULE_DESCRIPTION("Mediatek ALSA SoC AFE MT6392 Codec Driver");
MODULE_LICENSE("GPL v2");