| /* |
| * 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 <sound/soc.h> |
| #include <sound/tlv.h> |
| #include <linux/debugfs.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/of_platform.h> |
| #include "mt6392-codec.h" |
| |
| #define MT6392_CODEC_NAME "mt6392-codec" |
| |
| enum mt6392_speaker_mode { |
| MT6392_CLASS_D = 0, |
| MT6392_CLASS_AB, |
| }; |
| |
| struct mt6392_codec_priv { |
| struct snd_soc_codec *codec; |
| struct regmap *regmap; |
| uint32_t speaker_mode; |
| uint32_t spk_amp_gain; |
| uint16_t spk_trim_offset; |
| #ifdef CONFIG_DEBUG_FS |
| struct dentry *debugfs; |
| #endif |
| }; |
| |
| /* |
| * 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 |
| */ |
| #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_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; |
| } |
| |
| static int int_spk_amp_gain_put_volsw(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int ret = 0; |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_component_get_drvdata(component); |
| |
| ret = snd_soc_put_volsw(kcontrol, ucontrol); |
| if (ret < 0) |
| return ret; |
| |
| codec_data->spk_amp_gain = ucontrol->value.integer.value[0]; |
| |
| 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_read(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 void mt6392_codec_get_spk_trim_offset(struct snd_soc_codec *codec) |
| { |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_codec_get_drvdata(codec); |
| |
| /* turn on spk (class D) and hw trim */ |
| snd_soc_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E); |
| snd_soc_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4); |
| snd_soc_update_bits(codec, SPK_CON11, 0xFFFF, 0x0055); |
| snd_soc_update_bits(codec, SPK_CON9, 0xF0FF, 0x2018); |
| snd_soc_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414); |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3409); |
| usleep_range(20000, 21000); |
| |
| /* save trim offset */ |
| codec_data->spk_trim_offset = |
| snd_soc_read(codec, SPK_CON1) & GENMASK(4, 0); |
| |
| /* turn off trim */ |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3401); |
| snd_soc_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000); |
| usleep_range(2000, 3000); |
| |
| /* turn off spk */ |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0x0000); |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3400); |
| snd_soc_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x0000); |
| } |
| |
| static void mt6392_int_spk_on_with_trim(struct snd_soc_codec *codec) |
| { |
| #if defined(USE_HW_TRIM_CLASS_D) |
| /* turn on spk (class D) and hw trim */ |
| snd_soc_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E); |
| snd_soc_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4); |
| snd_soc_update_bits(codec, SPK_CON11, 0xFFFF, 0x0055); |
| snd_soc_update_bits(codec, SPK_CON9, 0xF0FF, 0x2018); |
| snd_soc_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414); |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3409); |
| usleep_range(20000, 21000); |
| |
| /* turn off trim */ |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3401); |
| snd_soc_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000); |
| #else |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_codec_get_drvdata(codec); |
| |
| /* turn on spk (class D) */ |
| snd_soc_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E); |
| snd_soc_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4); |
| snd_soc_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414); |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3001); |
| snd_soc_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000); |
| |
| /* enable sw trim */ |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0x0009); |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0x0001); |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0x0283); |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0x0281); |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0x2A81); |
| snd_soc_update_bits(codec, SPK_CON1, 0xFFFF, 0x6000); |
| /* class D and class AB use the same trim offset value */ |
| snd_soc_update_bits(codec, SPK_CON1, |
| GENMASK(12, 8), (codec_data->spk_trim_offset << 8)); |
| |
| /* trim stop */ |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0xAA81); |
| #endif |
| usleep_range(2000, 3000); |
| } |
| |
| static int mt6392_int_spk_turn_on(struct snd_soc_codec *codec) |
| { |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_codec_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_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x000E); |
| snd_soc_update_bits(codec, SPK_CON7, 0xFFFF, 0x48F4); |
| snd_soc_update_bits(codec, SPK_CON2, 0xFFFF, 0x0414); |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3005); |
| snd_soc_update_bits(codec, SPK_CON9, 0xF0FF, 0x2000); |
| snd_soc_update_bits(codec, SPK_CON1, 0xFFFF, 0x6000); |
| /* class D and class AB use the same trim offset value */ |
| snd_soc_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; |
| } |
| |
| static int mt6392_int_spk_turn_off(struct snd_soc_codec *codec) |
| { |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_codec_get_drvdata(codec); |
| int ret = 0; |
| |
| dev_dbg(codec->dev, "%s\n", __func__); |
| |
| switch (codec_data->speaker_mode) { |
| case MT6392_CLASS_D: |
| snd_soc_update_bits(codec, SPK_CON12, 0xFFFF, 0x0000); |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3400); |
| snd_soc_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x0000); |
| break; |
| case MT6392_CLASS_AB: |
| snd_soc_update_bits(codec, SPK_CON0, 0xFFFF, 0x3404); |
| snd_soc_update_bits(codec, TOP_CKPDN1_CLR, 0x000E, 0x0000); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int mt6392_int_spk_amp_wevent(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| struct snd_soc_codec *codec = snd_soc_dapm_to_codec(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; |
| } |
| |
| /* Int Spk Amp Switch */ |
| static int mt6392_codec_int_spk_amp_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); |
| struct snd_soc_codec *codec = codec_data->codec; |
| long int_spk_amp_en = snd_soc_read(codec, SPK_CON0) & BIT(0); |
| |
| ucontrol->value.integer.value[0] = int_spk_amp_en; |
| |
| return 0; |
| } |
| |
| static int mt6392_codec_int_spk_amp_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 snd_soc_codec *codec = codec_data->codec; |
| long int_spk_amp_en = ucontrol->value.integer.value[0]; |
| |
| if (int_spk_amp_en) |
| mt6392_int_spk_turn_on(codec); |
| else |
| mt6392_int_spk_turn_off(codec); |
| |
| return 0; |
| } |
| |
| static const struct snd_kcontrol_new mt6392_codec_controls[] = { |
| /* Internal speaker PGA gain control */ |
| SOC_SINGLE_EXT_TLV("Int Spk Amp Playback Volume", |
| SPK_CON9, 8, 15, 0, |
| snd_soc_get_volsw, |
| int_spk_amp_gain_put_volsw, |
| 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), |
| /* Int Spk Amp Switch */ |
| SOC_SINGLE_BOOL_EXT("Int Spk Amp Switch", |
| 0, |
| mt6392_codec_int_spk_amp_get, |
| mt6392_codec_int_spk_amp_put), |
| }; |
| |
| static const struct snd_soc_dapm_widget mt6392_codec_dapm_widgets[] = { |
| SND_SOC_DAPM_AIF_IN("MT6392 AIF RX", "MT6392 Playback", 0, |
| SND_SOC_NOPM, 0, 0), |
| SND_SOC_DAPM_SPK("Int Spk Amp", mt6392_int_spk_amp_wevent), |
| }; |
| |
| static const struct snd_soc_dapm_route mt6392_codec_dapm_routes[] = { |
| {"Int Spk Amp", NULL, "MT6392 AIF RX"}, |
| }; |
| |
| static int module_reg_read(void *context, unsigned int reg, unsigned int *val, |
| unsigned int offset) |
| { |
| struct mt6392_codec_priv *codec_data = |
| (struct mt6392_codec_priv *) context; |
| int ret = 0; |
| |
| if (!(codec_data && codec_data->regmap)) |
| return -1; |
| |
| ret = regmap_read(codec_data->regmap, |
| (reg & (~offset)), val); |
| |
| return ret; |
| } |
| |
| static int module_reg_write(void *context, unsigned int reg, unsigned int val, |
| unsigned int offset) |
| { |
| struct mt6392_codec_priv *codec_data = |
| (struct mt6392_codec_priv *) context; |
| int ret = 0; |
| |
| if (!(codec_data && codec_data->regmap)) |
| return -1; |
| |
| ret = regmap_write(codec_data->regmap, |
| (reg & (~offset)), val); |
| |
| return ret; |
| } |
| |
| static bool reg_is_in_pmic(unsigned int reg) |
| { |
| if (reg & PMIC_OFFSET) |
| return true; |
| else |
| return false; |
| } |
| |
| /* regmap functions */ |
| static int codec_reg_read(void *context, |
| unsigned int reg, unsigned int *val) |
| { |
| unsigned int offset; |
| |
| if (reg_is_in_pmic(reg)) |
| offset = PMIC_OFFSET; |
| else |
| return -1; |
| |
| return module_reg_read(context, reg, val, offset); |
| } |
| |
| static int codec_reg_write(void *context, |
| unsigned int reg, unsigned int val) |
| { |
| unsigned int offset; |
| |
| if (reg_is_in_pmic(reg)) |
| offset = PMIC_OFFSET; |
| else |
| return -1; |
| |
| return module_reg_write(context, reg, val, offset); |
| } |
| |
| static void codec_regmap_lock(void *lock_arg) |
| { |
| } |
| |
| static void codec_regmap_unlock(void *lock_arg) |
| { |
| } |
| |
| static struct regmap_config mt6392_codec_regmap_config = { |
| .reg_bits = 16, |
| .val_bits = 16, |
| .reg_read = codec_reg_read, |
| .reg_write = codec_reg_write, |
| .lock = codec_regmap_lock, |
| .unlock = codec_regmap_unlock, |
| .cache_type = REGCACHE_NONE, |
| }; |
| |
| #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_read(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_codec *codec = codec_data->codec; |
| |
| dev_dbg(codec->dev, "%s\n", __func__); |
| |
| /* default PGA gain: 12dB */ |
| codec_data->spk_amp_gain = 0xA; |
| snd_soc_update_bits(codec, SPK_CON9, |
| GENMASK(11, 8), (codec_data->spk_amp_gain) << 8); |
| } |
| |
| static struct regmap *mt6392_codec_get_regmap_from_dt(const char *phandle_name, |
| struct mt6392_codec_priv *codec_data) |
| { |
| struct device_node *self_node = NULL, *node = NULL; |
| struct platform_device *platdev = NULL; |
| struct device *dev = codec_data->codec->dev; |
| struct regmap *regmap = NULL; |
| |
| self_node = of_find_compatible_node(NULL, NULL, |
| "mediatek," MT6392_CODEC_NAME); |
| if (!self_node) { |
| dev_dbg(dev, "%s failed to find %s node\n", |
| __func__, MT6392_CODEC_NAME); |
| return NULL; |
| } |
| dev_dbg(dev, "%s found %s node\n", __func__, MT6392_CODEC_NAME); |
| |
| node = of_parse_phandle(self_node, phandle_name, 0); |
| if (!node) { |
| dev_dbg(dev, "%s failed to find %s node\n", |
| __func__, phandle_name); |
| return NULL; |
| } |
| dev_dbg(dev, "%s found %s\n", __func__, phandle_name); |
| |
| platdev = of_find_device_by_node(node); |
| if (!platdev) { |
| dev_dbg(dev, "%s failed to get platform device of %s\n", |
| __func__, phandle_name); |
| return NULL; |
| } |
| dev_dbg(dev, "%s found platform device of %s\n", |
| __func__, phandle_name); |
| |
| regmap = dev_get_regmap(&platdev->dev, NULL); |
| if (regmap) { |
| dev_dbg(dev, "%s found regmap of %s\n", __func__, phandle_name); |
| return regmap; |
| } |
| |
| return NULL; |
| } |
| |
| static int mt6392_codec_parse_dt(struct snd_soc_codec *codec) |
| { |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_codec_get_drvdata(codec); |
| struct device *dev = codec->dev; |
| int ret = 0; |
| |
| codec_data->regmap = mt6392_codec_get_regmap_from_dt( |
| "mediatek,pwrap-regmap", |
| codec_data); |
| if (!codec_data->regmap) { |
| dev_dbg(dev, "%s failed to get %s\n", |
| __func__, "mediatek,pwrap-regmap"); |
| devm_kfree(dev, codec_data); |
| ret = -EPROBE_DEFER; |
| return ret; |
| } |
| |
| ret = of_property_read_u32(dev->of_node, "mediatek,speaker-mode", |
| &codec_data->speaker_mode); |
| if (ret) { |
| dev_dbg(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; |
| } |
| |
| #define MT6392_CODEC_DL_RATES SNDRV_PCM_RATE_8000_48000 |
| |
| static struct snd_soc_dai_driver mt6392_codec_dai = { |
| .name = "mt6392-codec-dai", |
| .playback = { |
| .stream_name = "MT6392 Playback", |
| .channels_min = 1, |
| .channels_max = 2, |
| .rates = MT6392_CODEC_DL_RATES, |
| .formats = SNDRV_PCM_FMTBIT_S16_LE, |
| }, |
| }; |
| |
| static int mt6392_codec_probe(struct snd_soc_codec *codec) |
| { |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_codec_get_drvdata(codec); |
| int ret = 0; |
| |
| 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 | 0444, |
| NULL, codec_data, &mt6392_codec_debug_ops); |
| #endif |
| return ret; |
| } |
| |
| static int mt6392_codec_remove(struct snd_soc_codec *codec) |
| { |
| #ifdef CONFIG_DEBUG_FS |
| struct mt6392_codec_priv *codec_data = |
| snd_soc_codec_get_drvdata(codec); |
| debugfs_remove(codec_data->debugfs); |
| #endif |
| dev_dbg(codec->dev, "%s\n", __func__); |
| return 0; |
| } |
| |
| static struct snd_soc_codec_driver mt6392_codec_driver = { |
| .probe = mt6392_codec_probe, |
| .remove = mt6392_codec_remove, |
| |
| .component_driver = { |
| .controls = mt6392_codec_controls, |
| .num_controls = ARRAY_SIZE(mt6392_codec_controls), |
| .dapm_widgets = mt6392_codec_dapm_widgets, |
| .num_dapm_widgets = ARRAY_SIZE(mt6392_codec_dapm_widgets), |
| .dapm_routes = mt6392_codec_dapm_routes, |
| .num_dapm_routes = ARRAY_SIZE(mt6392_codec_dapm_routes), |
| }, |
| }; |
| |
| static int mt6392_codec_dev_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct mt6392_codec_priv *codec_data = NULL; |
| |
| dev_dbg(dev, "%s dev name %s\n", __func__, dev_name(dev)); |
| |
| if (dev->of_node) { |
| dev_set_name(dev, "%s", MT6392_CODEC_NAME); |
| dev_dbg(dev, "%s set dev name %s\n", __func__, dev_name(dev)); |
| } |
| |
| codec_data = devm_kzalloc(dev, |
| sizeof(struct mt6392_codec_priv), GFP_KERNEL); |
| pdev->name = pdev->dev.kobj.name; |
| |
| if (!codec_data) |
| return -ENOMEM; |
| |
| dev_set_drvdata(dev, codec_data); |
| |
| /* get regmap of codec */ |
| codec_data->regmap = devm_regmap_init(dev, NULL, codec_data, |
| &mt6392_codec_regmap_config); |
| if (IS_ERR(codec_data->regmap)) { |
| dev_dbg(dev, "%s failed to get regmap of codec\n", __func__); |
| devm_kfree(dev, codec_data); |
| codec_data->regmap = NULL; |
| return -EINVAL; |
| } |
| |
| return snd_soc_register_codec(dev, |
| &mt6392_codec_driver, &mt6392_codec_dai, 1); |
| } |
| |
| static int mt6392_codec_dev_remove(struct platform_device *pdev) |
| { |
| snd_soc_unregister_codec(&pdev->dev); |
| return 0; |
| } |
| |
| static const struct of_device_id mt6392_codec_dt_match[] = { |
| {.compatible = "mediatek," MT6392_CODEC_NAME,}, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(of, mt6392_codec_dt_match); |
| |
| static struct platform_driver mt6392_codec_device_driver = { |
| .driver = { |
| .name = MT6392_CODEC_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = mt6392_codec_dt_match, |
| }, |
| .probe = mt6392_codec_dev_probe, |
| .remove = mt6392_codec_dev_remove, |
| }; |
| |
| module_platform_driver(mt6392_codec_device_driver); |
| |
| /* Module information */ |
| MODULE_DESCRIPTION("ASoC MT6392 driver"); |
| MODULE_LICENSE("GPL v2"); |