diff --git a/Makefile.am b/Makefile.am index d9724cd70d32..bd60e53769a6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,6 +42,7 @@ endif ifeq ($(TARGET_SUPPORT), $(filter $(TARGET_SUPPORT), qcs40x)) obj-m += asoc/codecs/bolero/ obj-m += asoc/codecs/csra66x0/ +obj-m += asoc/codecs/ep92/ endif all: diff --git a/asoc/codecs/ep92/Android.mk b/asoc/codecs/ep92/Android.mk new file mode 100644 index 000000000000..c2ac0599f7d8 --- /dev/null +++ b/asoc/codecs/ep92/Android.mk @@ -0,0 +1,50 @@ +# Android makefile for audio kernel modules + +# Assume no targets will be supported + +# Check if this driver needs be built for current target +ifeq ($(call is-board-platform,qcs405),true) +AUDIO_SELECT := CONFIG_SND_SOC_QCS405=m +endif + +AUDIO_CHIPSET := audio +# Build/Package only in case of supported target +ifeq ($(call is-board-platform-in-list,qcs405),true) + +LOCAL_PATH := $(call my-dir) + +# This makefile is only for DLKM +ifneq ($(findstring vendor,$(LOCAL_PATH)),) + +ifneq ($(findstring opensource,$(LOCAL_PATH)),) + AUDIO_BLD_DIR := $(ANDROID_BUILD_TOP)/vendor/qcom/opensource/audio-kernel +endif # opensource + +DLKM_DIR := $(TOP)/device/qcom/common/dlkm + +# Build audio.ko as $(AUDIO_CHIPSET)_audio.ko +########################################################### +# This is set once per LOCAL_PATH, not per (kernel) module +KBUILD_OPTIONS := AUDIO_ROOT=$(AUDIO_BLD_DIR) + +# We are actually building audio.ko here, as per the +# requirement we are specifying _audio.ko as LOCAL_MODULE. +# This means we need to rename the module to _audio.ko +# after audio.ko is built. +KBUILD_OPTIONS += MODNAME=ep92_dlkm +KBUILD_OPTIONS += BOARD_PLATFORM=$(TARGET_BOARD_PLATFORM) +KBUILD_OPTIONS += $(AUDIO_SELECT) + +########################################################### +include $(CLEAR_VARS) +LOCAL_MODULE := $(AUDIO_CHIPSET)_ep92.ko +LOCAL_MODULE_KBUILD_NAME := ep92_dlkm.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_DEBUG_ENABLE := true +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk +########################################################### +########################################################### + +endif # DLKM check +endif # supported target check diff --git a/asoc/codecs/ep92/Kbuild b/asoc/codecs/ep92/Kbuild new file mode 100644 index 000000000000..1c54a44dcfa9 --- /dev/null +++ b/asoc/codecs/ep92/Kbuild @@ -0,0 +1,106 @@ +# We can build either as part of a standalone Kernel build or as +# an external module. Determine which mechanism is being used +ifeq ($(MODNAME),) + KERNEL_BUILD := 1 +else + KERNEL_BUILD := 0 +endif + + + +ifeq ($(KERNEL_BUILD), 1) + # These are configurable via Kconfig for kernel-based builds + # Need to explicitly configure for Android-based builds + AUDIO_BLD_DIR := $(ANDROID_BUILD_TOP)/kernel/msm-4.14 + AUDIO_ROOT := $(AUDIO_BLD_DIR)/techpack/audio +endif + +ifeq ($(KERNEL_BUILD), 0) + ifeq ($(CONFIG_ARCH_QCS405), y) + include $(AUDIO_ROOT)/config/qcs405auto.conf + export + INCS += -include $(AUDIO_ROOT)/config/qcs405autoconf.h + endif +endif + +# As per target team, build is done as follows: +# Defconfig : build with default flags +# Slub : defconfig + CONFIG_SLUB_DEBUG := y + +# CONFIG_SLUB_DEBUG_ON := y + CONFIG_PAGE_POISONING := y +# Perf : Using appropriate msmXXXX-perf_defconfig +# +# Shipment builds (user variants) should not have any debug feature +# enabled. This is identified using 'TARGET_BUILD_VARIANT'. Slub builds +# are identified using the CONFIG_SLUB_DEBUG_ON configuration. Since +# there is no other way to identify defconfig builds, QTI internal +# representation of perf builds (identified using the string 'perf'), +# is used to identify if the build is a slub or defconfig one. This +# way no critical debug feature will be enabled for perf and shipment +# builds. Other OEMs are also protected using the TARGET_BUILD_VARIANT +# config. + +############ UAPI ############ +UAPI_DIR := uapi +UAPI_INC := -I$(AUDIO_ROOT)/include/$(UAPI_DIR) + +############ COMMON ############ +COMMON_DIR := include +COMMON_INC := -I$(AUDIO_ROOT)/$(COMMON_DIR) + +############ EP92 ############ + +# for EP92 Codec +ifdef CONFIG_SND_SOC_EP92 + EP92_OBJS += ep92.o +endif + +LINUX_INC += -Iinclude/linux + +INCS += $(COMMON_INC) \ + $(UAPI_INC) + +#EXTRA_CFLAGS += $(INCS) +ccflags-y += $(INCS) + + +CDEFINES += -DANI_LITTLE_BYTE_ENDIAN \ + -DANI_LITTLE_BIT_ENDIAN \ + -DDOT11F_LITTLE_ENDIAN_HOST \ + -DANI_COMPILER_TYPE_GCC \ + -DANI_OS_TYPE_ANDROID=6 \ + -DPTT_SOCK_SVC_ENABLE \ + -Wall\ + -Werror\ + -D__linux__ + +KBUILD_CPPFLAGS += $(CDEFINES) + +# Currently, for versions of gcc which support it, the kernel Makefile +# is disabling the maybe-uninitialized warning. Re-enable it for the +# AUDIO driver. Note that we must use EXTRA_CFLAGS here so that it +# will override the kernel settings. +ifeq ($(call cc-option-yn, -Wmaybe-uninitialized),y) +#EXTRA_CFLAGS += -Wmaybe-uninitialized +ccflags-y += -Wmaybe-uninitialized +endif +#EXTRA_CFLAGS += -Wmissing-prototypes + +ifeq ($(call cc-option-yn, -Wheader-guard),y) +#EXTRA_CFLAGS += -Wheader-guard +ccflags-y += -Wheader-guard +endif + +ifeq ($(KERNEL_BUILD), 0) +KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/ipc/Module.symvers +KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/dsp/Module.symvers +KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/asoc/Module.symvers +KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/asoc/codecs/Module.symvers +KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/soc/Module.symvers +endif + +# Module information used by KBuild framework +obj-$(CONFIG_SND_SOC_EP92) += ep92_dlkm.o +ep92_dlkm-y := $(EP92_OBJS) + +# inject some build related information +DEFINES += -DBUILD_TIMESTAMP=\"$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')\" diff --git a/asoc/codecs/ep92/ep92.c b/asoc/codecs/ep92/ep92.c new file mode 100644 index 000000000000..73110da7ec04 --- /dev/null +++ b/asoc/codecs/ep92/ep92.c @@ -0,0 +1,1382 @@ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ep92.h" + +#define EP92_POLL_INTERVAL_OFF_MSEC 2000 +#define EP92_POLL_INTERVAL_ON_MSEC 100 + + +#define EP92_RATES (SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define EP92_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +#define EP92_UEVENT_CTRL_NUM_KEYS 8 +#define EP92_UEVENT_AUDIO_NUM_KEYS 9 + +static const unsigned int ep92_samp_freq_table[8] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, 768000 +}; + +static const char hex_to_char[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + +struct ep92_uevent_data { + struct kobject kobj; + struct kobj_type ktype; +}; + +static struct kset *ep92_uevent_kset; +static struct ep92_uevent_data *ep92_uevent_ctrl; +static struct ep92_uevent_data *ep92_uevent_audio; + +static void ep92_release_uevent_data(struct kobject *kobj) +{ + struct ep92_uevent_data *data = container_of(kobj, + struct ep92_uevent_data, kobj); + + kfree(data); +} + +static int ep92_init_uevent_data(struct ep92_uevent_data *uevent_data, + char *name) +{ + int ret = -EINVAL; + + if (!uevent_data || !name) + return ret; + + /* Set kset for kobject before initializing the kobject */ + uevent_data->kobj.kset = ep92_uevent_kset; + + /* Initialize kobject and add it to kernel */ + ret = kobject_init_and_add(&uevent_data->kobj, &uevent_data->ktype, + NULL, "%s", name); + if (ret) { + pr_err("%s: error initializing uevent kernel object: %d", + __func__, ret); + kobject_put(&uevent_data->kobj); + return ret; + } + + /* Send kobject add event to the system */ + kobject_uevent(&uevent_data->kobj, KOBJ_ADD); + + return ret; +} + +/** + * ep92_destroy_uevent_data - destroy kernel object. + * + * @uevent_data: uevent data. + */ +static void ep92_destroy_uevent_data(struct ep92_uevent_data *uevent_data) +{ + if (uevent_data) + kobject_put(&uevent_data->kobj); +} + +static bool ep92_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case EP92_BI_GENERAL_INFO_0: + case EP92_BI_GENERAL_INFO_1: + case EP92_BI_GENERAL_INFO_2: + case EP92_BI_GENERAL_INFO_3: + case EP92_BI_GENERAL_INFO_4: + case EP92_BI_GENERAL_INFO_5: + case EP92_BI_GENERAL_INFO_6: + case EP92_GENERAL_CONTROL_0: + case EP92_GENERAL_CONTROL_1: + case EP92_GENERAL_CONTROL_2: + case EP92_GENERAL_CONTROL_3: + case EP92_GENERAL_CONTROL_4: + case EP92_AUDIO_INFO_SYSTEM_STATUS_0: + case EP92_AUDIO_INFO_SYSTEM_STATUS_1: + case EP92_AUDIO_INFO_AUDIO_STATUS: + case EP92_AUDIO_INFO_CHANNEL_STATUS_0: + case EP92_AUDIO_INFO_CHANNEL_STATUS_1: + case EP92_AUDIO_INFO_CHANNEL_STATUS_2: + case EP92_AUDIO_INFO_CHANNEL_STATUS_3: + case EP92_AUDIO_INFO_CHANNEL_STATUS_4: + case EP92_AUDIO_INFO_ADO_INFO_FRAME_0: + case EP92_AUDIO_INFO_ADO_INFO_FRAME_1: + case EP92_AUDIO_INFO_ADO_INFO_FRAME_2: + case EP92_AUDIO_INFO_ADO_INFO_FRAME_3: + case EP92_AUDIO_INFO_ADO_INFO_FRAME_4: + case EP92_AUDIO_INFO_ADO_INFO_FRAME_5: + return true; + default: + return false; + } +} + +static bool ep92_writeable_registers(struct device *dev, unsigned int reg) +{ + if (reg >= EP92_ISP_MODE_ENTER_ISP && reg <= EP92_GENERAL_CONTROL_4) + return true; + + return false; +} + +static bool ep92_readable_registers(struct device *dev, unsigned int reg) +{ + if (reg >= EP92_BI_VENDOR_ID_0 && reg <= EP92_MAX_REGISTER_ADDR) + return true; + + return false; +} + +/* codec private data */ +struct ep92_pdata { + struct regmap *regmap; + struct snd_soc_codec *codec; + struct timer_list timer; + struct work_struct read_status_worker; + int irq; + + struct { + u8 ctl; + u8 rx_sel; + u8 cec_volume; + } gc; /* General Control block */ + + struct { + u8 system_status_0; + u8 system_status_1; + u8 audio_status; + u8 cs[5]; + u8 cc; + u8 ca; + } ai; /* Audio Info block */ + + u8 old_mode; +}; + +/* + * EP92 Controls + */ + +/* enumerated controls */ +static const char * const ep92_off_on_text[] = {"Off", "On"}; +static const char * const ep92_aud_path_text[] = {"TV", "Speaker"}; +static const char * const ep92_rx_sel_text[] = {"Port 0", "Port 1", "Port 2", + "Res 3", "Res 4", "Res 5", "None", "Res 7"}; +static const char * const ep92_cec_mute_text[] = {"Normal", "Muted"}; + +static const char * const ep92_state_text[] = {"Inactive", "Active"}; +static const char * const ep92_avmute_text[] = {"Normal", "Muted"}; +static const char * const ep92_layout_text[] = {"Layout 0", "Layout 1"}; +static const char * const ep92_mode_text[] = {"LPCM", "Compr"}; + +SOC_ENUM_SINGLE_DECL(ep92_power_enum, EP92_GENERAL_CONTROL_0, + EP92_GC_POWER_SHIFT, ep92_off_on_text); +SOC_ENUM_SINGLE_DECL(ep92_audio_path_enum, EP92_GENERAL_CONTROL_0, + EP92_GC_AUDIO_PATH_SHIFT, ep92_aud_path_text); +SOC_ENUM_SINGLE_DECL(ep92_rx_sel_enum, EP92_GENERAL_CONTROL_1, + EP92_GC_RX_SEL_SHIFT, ep92_rx_sel_text); +SOC_ENUM_SINGLE_DECL(ep92_arc_en_enum, EP92_GENERAL_CONTROL_0, + EP92_GC_ARC_EN_SHIFT, ep92_off_on_text); +SOC_ENUM_SINGLE_DECL(ep92_cec_mute_enum, EP92_GENERAL_CONTROL_0, + EP92_GC_CEC_MUTE_SHIFT, ep92_cec_mute_text); + +SOC_ENUM_SINGLE_DECL(ep92_state_enum, EP92_AUDIO_INFO_SYSTEM_STATUS_0, + EP92_AI_MCLK_ON_SHIFT, ep92_state_text); +SOC_ENUM_SINGLE_DECL(ep92_avmute_enum, EP92_AUDIO_INFO_SYSTEM_STATUS_0, + EP92_AI_AVMUTE_SHIFT, ep92_avmute_text); +SOC_ENUM_SINGLE_DECL(ep92_layout_enum, EP92_AUDIO_INFO_SYSTEM_STATUS_0, + EP92_AI_LAYOUT_SHIFT, ep92_layout_text); +SOC_ENUM_SINGLE_DECL(ep92_mode_enum, EP92_AUDIO_INFO_AUDIO_STATUS, + EP92_AI_STD_ADO_SHIFT, ep92_mode_text); + +/* get/set functions */ +static int ep92_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = + (val >> e->shift_l) & EP92_2CHOICE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_off_on_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + val &= ~EP92_GC_POWER_MASK; + val |= (ucontrol->value.enumerated.item[0] & EP92_2CHOICE_MASK) + << e->shift_l; + snd_soc_write(codec, e->reg, val); + ep92->gc.ctl &= ~EP92_GC_POWER_MASK; + ep92->gc.ctl |= val & EP92_GC_POWER_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_off_on_text[ucontrol->value.enumerated.item[0] & + EP92_2CHOICE_MASK]); + return 0; +} + +static int ep92_audio_path_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & + EP92_2CHOICE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_aud_path_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_audio_path_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + val &= ~EP92_GC_AUDIO_PATH_MASK; + val |= (ucontrol->value.enumerated.item[0] & EP92_2CHOICE_MASK) + << e->shift_l; + snd_soc_write(codec, e->reg, val); + ep92->gc.ctl &= ~EP92_GC_AUDIO_PATH_MASK; + ep92->gc.ctl |= val & EP92_GC_AUDIO_PATH_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_aud_path_text[ucontrol->value.enumerated.item[0] & + EP92_2CHOICE_MASK]); + return 0; +} + +static int ep92_cec_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & + EP92_2CHOICE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_cec_mute_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_cec_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + val &= ~EP92_GC_CEC_MUTE_MASK; + val |= (ucontrol->value.enumerated.item[0] & EP92_2CHOICE_MASK) + << e->shift_l; + snd_soc_write(codec, e->reg, val); + ep92->gc.ctl &= ~EP92_GC_CEC_MUTE_MASK; + ep92->gc.ctl |= val & EP92_GC_CEC_MUTE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_cec_mute_text[ucontrol->value.enumerated.item[0] & + EP92_2CHOICE_MASK]); + return 0; +} + +static int ep92_arc_en_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & + EP92_2CHOICE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_off_on_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_arc_en_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + val &= ~EP92_GC_ARC_EN_MASK; + val |= (ucontrol->value.enumerated.item[0] & EP92_2CHOICE_MASK) + << e->shift_l; + snd_soc_write(codec, e->reg, val); + ep92->gc.ctl &= ~EP92_GC_ARC_EN_MASK; + ep92->gc.ctl |= val & EP92_GC_ARC_EN_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_off_on_text[ucontrol->value.enumerated.item[0] & 0x01]); + return 0; +} + +static int ep92_rx_sel_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & + EP92_GC_RX_SEL_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_rx_sel_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_rx_sel_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + val &= ~EP92_GC_RX_SEL_MASK; + val |= (ucontrol->value.enumerated.item[0] & EP92_GC_RX_SEL_MASK) + << e->shift_l; + snd_soc_write(codec, e->reg, val); + ep92->gc.rx_sel &= ~EP92_GC_RX_SEL_MASK; + ep92->gc.rx_sel |= val & EP92_GC_RX_SEL_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_rx_sel_text[ucontrol->value.enumerated.item[0] & + EP92_GC_RX_SEL_MASK]); + return 0; +} + +static int ep92_cec_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, mc->reg); + ucontrol->value.integer.value[0] = (val >> mc->shift) & + EP92_GC_CEC_VOLUME_MASK; + + pr_debug("%s: volume = %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int ep92_cec_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + val = ucontrol->value.integer.value[0] & EP92_GC_CEC_VOLUME_MASK; + if (val > EP92_GC_CEC_VOLUME_MAX) + val = EP92_GC_CEC_VOLUME_MAX; + snd_soc_write(codec, mc->reg, val); + ep92->gc.cec_volume = val; + + pr_debug("%s: volume = %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int ep92_state_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & + EP92_2CHOICE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_state_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_avmute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & + EP92_2CHOICE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_avmute_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_layout_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & + EP92_2CHOICE_MASK; + + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_layout_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + val = snd_soc_read(codec, e->reg); + if (val & EP92_AI_STD_ADO_MASK) { + val = snd_soc_read(codec, EP92_AUDIO_INFO_CHANNEL_STATUS_0); + if (val & EP92_AI_NPCM_MASK) + ucontrol->value.enumerated.item[0] = 1; /* Compr */ + else + ucontrol->value.enumerated.item[0] = 0; /* LPCM */ + } else if (val & EP92_AI_HBR_ADO_MASK) { + ucontrol->value.enumerated.item[0] = 1; /* Compr */ + } else { + ucontrol->value.enumerated.item[0] = ep92->old_mode; + } + pr_debug("%s: item = %d (%s)\n", __func__, + ucontrol->value.enumerated.item[0], + ep92_mode_text[ucontrol->value.enumerated.item[0]]); + return 0; +} + +static int ep92_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, mc->reg); + val &= EP92_AI_RATE_MASK; + val = ep92_samp_freq_table[val]; + ucontrol->value.integer.value[0] = val; + + pr_debug("%s: rate = %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int ep92_ch_count_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, mc->reg) & EP92_AI_CH_COUNT_MASK; + /* mapping is ch_count = reg_val + 1, with exception: 0 = unknown */ + if (val > 0) + val += 1; + + ucontrol->value.integer.value[0] = val; + + pr_debug("%s: ch_count = %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int ep92_ch_alloc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int val; + + val = snd_soc_read(codec, mc->reg); + ucontrol->value.integer.value[0] = (val >> mc->shift) & + EP92_AI_CH_ALLOC_MASK; + + pr_debug("%s: ch_alloc = 0x%02lx\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static const struct snd_kcontrol_new ep92_snd_controls[] = { + + SOC_ENUM_EXT("HDMI_IN POWER", ep92_power_enum, + ep92_power_get, ep92_power_put), + SOC_ENUM_EXT("HDMI_IN AUDIO_PATH", ep92_audio_path_enum, + ep92_audio_path_get, ep92_audio_path_put), + SOC_ENUM_EXT("HDMI_IN RX_SEL", ep92_rx_sel_enum, + ep92_rx_sel_get, ep92_rx_sel_put), + SOC_ENUM_EXT("HDMI_IN ARC_EN", ep92_arc_en_enum, + ep92_arc_en_get, ep92_arc_en_put), + SOC_ENUM_EXT("HDMI_IN CEC_MUTE", ep92_cec_mute_enum, + ep92_cec_mute_get, ep92_cec_mute_put), + SOC_SINGLE_EXT("HDMI_IN CEC_VOLUME", EP92_GENERAL_CONTROL_3, + EP92_GC_CEC_VOLUME_MIN, EP92_GC_CEC_VOLUME_MAX, + 0, ep92_cec_volume_get, ep92_cec_volume_put), + + SOC_ENUM_EXT("HDMI_IN STATE", ep92_state_enum, ep92_state_get, NULL), + SOC_ENUM_EXT("HDMI_IN AVMUTE", ep92_avmute_enum, ep92_avmute_get, NULL), + SOC_ENUM_EXT("HDMI_IN LAYOUT", ep92_layout_enum, ep92_layout_get, NULL), + SOC_ENUM_EXT("HDMI_IN MODE", ep92_mode_enum, ep92_mode_get, NULL), + SOC_SINGLE_EXT("HDMI_IN RATE", EP92_AUDIO_INFO_AUDIO_STATUS, + EP92_AI_RATE_MIN, EP92_AI_RATE_MAX, 0, ep92_rate_get, NULL), + SOC_SINGLE_EXT("HDMI_IN CH_COUNT", EP92_AUDIO_INFO_ADO_INFO_FRAME_1, + EP92_AI_CH_COUNT_MIN, EP92_AI_CH_COUNT_MAX, + 0, ep92_ch_count_get, NULL), + SOC_SINGLE_EXT("HDMI_IN CH_ALLOC", EP92_AUDIO_INFO_ADO_INFO_FRAME_4, + EP92_AI_CH_ALLOC_MIN, EP92_AI_CH_ALLOC_MAX, 0, + ep92_ch_alloc_get, NULL), +}; + +static int ep92_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +static void ep92_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ +} + +static int ep92_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + return 0; +} + +static struct snd_soc_dai_ops ep92_dai_ops = { + .startup = ep92_startup, + .shutdown = ep92_shutdown, + .hw_params = ep92_hw_params, +}; + +static struct snd_soc_dai_driver ep92_dai[] = { + { + .name = "ep92-hdmi", + .id = 1, + .capture = { + .stream_name = "HDMI Capture", + .rate_max = 192000, + .rate_min = 32000, + .channels_min = 1, + .channels_max = 8, + .rates = EP92_RATES, + .formats = EP92_FORMATS, + }, + .ops = &ep92_dai_ops, /* callbacks */ + }, + { + .name = "ep92-arc", + .id = 2, + .capture = { + .stream_name = "ARC Capture", + .rate_max = 192000, + .rate_min = 32000, + .channels_min = 1, + .channels_max = 2, + .rates = EP92_RATES, + .formats = EP92_FORMATS, + }, + .ops = &ep92_dai_ops, /* callbacks */ + }, +}; + +static const char * const ep92_event_power_text[] = { + "POWER=Off", + "POWER=On", +}; + +static const char * const ep92_event_arc_en_text[] = { + "ARC_EN=Off", + "ARC_EN=On", +}; + +static const char * const ep92_event_audio_path_text[] = { + "AUDIO_PATH=TV", + "AUDIO_PATH=Speaker", +}; + +static const char *const ep92_event_rx_sel_text[] = { + "RX_SEL=Port0", + "RX_SEL=Port1", + "RX_SEL=Port2", + "RX_SEL=Res3", + "RX_SEL=Res4", + "RX_SEL=Res5", + "RX_SEL=None", + "RX_SEL=Res7", +}; + +static const char *const ep92_event_cec_mute_text[] = { + "CEC_MUTE=Normal", + "CEC_MUTE=Muted", +}; + +static int ep92_send_uevent_ctrl(struct ep92_pdata *ep92) +{ + char *env[EP92_UEVENT_CTRL_NUM_KEYS]; + u8 idx = 0; + u8 cec_volume; + char cec_volume_text[] = "CEC_VOLUME=0x00"; + char *ptr; + + env[idx++] = "HDMI_CONTROL=TRUE"; + + if ((ep92->gc.ctl >> EP92_GC_POWER_SHIFT) & + EP92_2CHOICE_MASK) + env[idx++] = (char *)ep92_event_power_text[1]; + else + env[idx++] = (char *)ep92_event_power_text[0]; + + if (ep92->gc.ctl & EP92_2CHOICE_MASK) + env[idx++] = (char *)ep92_event_arc_en_text[1]; + else + env[idx++] = (char *)ep92_event_arc_en_text[0]; + + if ((ep92->gc.ctl >> EP92_GC_AUDIO_PATH_SHIFT) & + EP92_2CHOICE_MASK) + env[idx++] = (char *)ep92_event_audio_path_text[1]; + else + env[idx++] = (char *)ep92_event_audio_path_text[0]; + + switch (ep92->gc.rx_sel & EP92_GC_RX_SEL_MASK) { + case 0: + env[idx++] = (char *)ep92_event_rx_sel_text[0]; + break; + case 1: + env[idx++] = (char *)ep92_event_rx_sel_text[1]; + break; + case 2: + env[idx++] = (char *)ep92_event_rx_sel_text[2]; + break; + case 3: + env[idx++] = (char *)ep92_event_rx_sel_text[3]; + break; + case 4: + env[idx++] = (char *)ep92_event_rx_sel_text[4]; + break; + case 5: + env[idx++] = (char *)ep92_event_rx_sel_text[5]; + break; + case 6: + env[idx++] = (char *)ep92_event_rx_sel_text[6]; + break; + case 7: + env[idx++] = (char *)ep92_event_rx_sel_text[7]; + break; + default: + env[idx++] = (char *)ep92_event_rx_sel_text[0]; + } + + if ((ep92->gc.ctl >> EP92_GC_CEC_MUTE_SHIFT) & + EP92_2CHOICE_MASK) + env[idx++] = (char *)ep92_event_cec_mute_text[1]; + else + env[idx++] = (char *)ep92_event_cec_mute_text[0]; + + ptr = &cec_volume_text[strlen(cec_volume_text)-2]; + cec_volume = (ep92->ai.ca) & EP92_GC_CEC_VOLUME_MASK; + *ptr++ = hex_to_char[(cec_volume >> 4) & 0x0f]; + *ptr++ = hex_to_char[cec_volume & 0x0f]; + env[idx++] = (char *)cec_volume_text; + + env[idx++] = NULL; + + if (idx != EP92_UEVENT_CTRL_NUM_KEYS) { + pr_err("ep92 wrong number of audio uevent keys (%d).\n", + idx); + return -EINVAL; + } + + return kobject_uevent_env(&ep92_uevent_ctrl->kobj, KOBJ_CHANGE, env); +} + +static const char * const ep92_event_state_text[] = { + "STATE=Inactive", + "STATE=Active", +}; + +static const char *const ep92_event_rate_text[] = { + "RATE=32000", + "RATE=44100", + "RATE=48000", + "RATE=88200", + "RATE=96000", + "RATE=176400", + "RATE=192000", + "RATE=768000", +}; + +static const char *const ep92_event_format_text[] = { + "FORMAT=LPCM", + "FORMAT=Compr", +}; + +static const char *const ep92_event_layout_text[] = { + "LAYOUT=2ch", + "LAYOUT=8ch", +}; + +static const char *const ep92_event_avmute_text[] = { + "AVMUTE=Normal", + "AVMUTE=Muted", +}; + +static const char *const ep92_event_ch_count_text[] = { + "CH_COUNT=One", + "CH_COUNT=Two", + "CH_COUNT=Three", + "CH_COUNT=Four", + "CH_COUNT=Five", + "CH_COUNT=Six", + "CH_COUNT=Seven", + "CH_COUNT=Eight", +}; + +static int ep92_send_uevent_audio(struct ep92_pdata *ep92) +{ + char *env[EP92_UEVENT_AUDIO_NUM_KEYS]; + u8 idx = 0; + u8 ch_alloc; + char ch_alloc_text[] = "CH_ALLOC=0x00"; + char *ptr; + + env[idx++] = "HDMI_FMT_UPDATE=TRUE"; + + if (((ep92->ai.system_status_0 >> EP92_AI_MCLK_ON_SHIFT) & + EP92_2CHOICE_MASK) == EP92_STATUS_AUDIO_ACTIVE) + env[idx++] = (char *)ep92_event_state_text[1]; + else + env[idx++] = (char *)ep92_event_state_text[0]; + + switch (ep92->ai.audio_status & EP92_AI_RATE_MASK) { + case 0: + env[idx++] = (char *)ep92_event_rate_text[0]; + break; + case 1: + env[idx++] = (char *)ep92_event_rate_text[1]; + break; + case 2: + env[idx++] = (char *)ep92_event_rate_text[2]; + break; + case 3: + env[idx++] = (char *)ep92_event_rate_text[3]; + break; + case 4: + env[idx++] = (char *)ep92_event_rate_text[4]; + break; + case 5: + env[idx++] = (char *)ep92_event_rate_text[5]; + break; + case 6: + env[idx++] = (char *)ep92_event_rate_text[6]; + break; + case 7: + env[idx++] = (char *)ep92_event_rate_text[7]; + break; + default: + env[idx++] = (char *)ep92_event_rate_text[2]; + } + + if (ep92->old_mode) + env[idx++] = (char *)ep92_event_format_text[1]; + else + env[idx++] = (char *)ep92_event_format_text[0]; + + if (ep92->ai.system_status_0 & EP92_2CHOICE_MASK) + env[idx++] = (char *)ep92_event_layout_text[1]; + else + env[idx++] = (char *)ep92_event_layout_text[0]; + + if ((ep92->ai.system_status_0 >> EP92_AI_AVMUTE_SHIFT) & + EP92_2CHOICE_MASK) + env[idx++] = (char *)ep92_event_avmute_text[1]; + else + env[idx++] = (char *)ep92_event_avmute_text[0]; + + /* cc==0 signals n/a and is treated as stereo */ + switch (ep92->ai.cc & EP92_AI_CH_COUNT_MASK) { + case 0: + env[idx++] = (char *)ep92_event_ch_count_text[1]; + break; + case 1: + env[idx++] = (char *)ep92_event_ch_count_text[1]; + break; + case 2: + env[idx++] = (char *)ep92_event_ch_count_text[2]; + break; + case 3: + env[idx++] = (char *)ep92_event_ch_count_text[3]; + break; + case 4: + env[idx++] = (char *)ep92_event_ch_count_text[4]; + break; + case 5: + env[idx++] = (char *)ep92_event_ch_count_text[5]; + break; + case 6: + env[idx++] = (char *)ep92_event_ch_count_text[6]; + break; + case 7: + env[idx++] = (char *)ep92_event_ch_count_text[7]; + break; + default: + env[idx++] = (char *)ep92_event_ch_count_text[1]; + } + + ptr = &ch_alloc_text[strlen(ch_alloc_text)-2]; + ch_alloc = (ep92->ai.ca) & EP92_AI_CH_ALLOC_MASK; + *ptr++ = hex_to_char[(ch_alloc >> 4) & 0x0f]; + *ptr++ = hex_to_char[ch_alloc & 0x0f]; + env[idx++] = (char *)ch_alloc_text; + + env[idx++] = NULL; + + if (idx != EP92_UEVENT_AUDIO_NUM_KEYS) { + pr_err("ep92 wrong number of audio uevent keys (%d).\n", + idx); + return -EINVAL; + } + + return kobject_uevent_env(&ep92_uevent_audio->kobj, KOBJ_CHANGE, env); +} + +static void ep92_read_general_control(struct snd_soc_codec *codec, + struct ep92_pdata *ep92) +{ + u8 old, change; + bool send_uevent = false; + + old = ep92->gc.ctl; + ep92->gc.ctl = snd_soc_read(codec, EP92_GENERAL_CONTROL_0); + change = ep92->gc.ctl ^ old; + if (change & EP92_GC_POWER_MASK) { + pr_debug("ep92 power changed to %d (%s)\n", + (ep92->gc.ctl >> EP92_GC_POWER_SHIFT) & + EP92_2CHOICE_MASK, + ep92_off_on_text[(ep92->gc.ctl + >> EP92_GC_POWER_SHIFT) & EP92_2CHOICE_MASK]); + send_uevent = true; + } + if (change & EP92_GC_AUDIO_PATH_MASK) { + pr_debug("ep92 audio_path changed to %d (%s)\n", + (ep92->gc.ctl >> EP92_GC_AUDIO_PATH_SHIFT) & + EP92_2CHOICE_MASK, + ep92_aud_path_text[(ep92->gc.ctl + >> EP92_GC_AUDIO_PATH_SHIFT) & EP92_2CHOICE_MASK]); + send_uevent = true; + } + if (change & EP92_GC_CEC_MUTE_MASK) { + pr_debug("ep92 cec_mute changed to %d (%s)\n", + (ep92->gc.ctl >> EP92_GC_CEC_MUTE_SHIFT) & + EP92_2CHOICE_MASK, + ep92_cec_mute_text[(ep92->gc.ctl + >> EP92_GC_CEC_MUTE_SHIFT) & EP92_2CHOICE_MASK]); + send_uevent = true; + } + if (change & EP92_GC_ARC_EN_MASK) { + pr_debug("ep92 arc_en changed to %d (%s)\n", + ep92->gc.ctl & EP92_2CHOICE_MASK, + ep92_off_on_text[ep92->gc.ctl & EP92_2CHOICE_MASK]); + send_uevent = true; + } + + old = ep92->gc.rx_sel; + ep92->gc.rx_sel = snd_soc_read(codec, EP92_GENERAL_CONTROL_1); + change = ep92->gc.rx_sel ^ old; + if (change & EP92_GC_RX_SEL_MASK) { + pr_debug("ep92 rx_sel changed to %d (%s)\n", + ep92->gc.rx_sel & EP92_GC_RX_SEL_MASK, + ep92_rx_sel_text[ep92->gc.rx_sel & + EP92_GC_RX_SEL_MASK]); + send_uevent = true; + } + + old = ep92->gc.cec_volume; + ep92->gc.cec_volume = snd_soc_read(codec, EP92_GENERAL_CONTROL_3); + change = ep92->gc.cec_volume ^ old; + if (change & EP92_GC_CEC_VOLUME_MASK) { + pr_debug("ep92 cec_volume changed to %d\n", + ep92->gc.cec_volume & EP92_GC_CEC_VOLUME_MASK); + send_uevent = true; + } + + if (send_uevent) + ep92_send_uevent_ctrl(ep92); +} + +static void ep92_read_audio_info(struct snd_soc_codec *codec, + struct ep92_pdata *ep92) +{ + u8 old, change; + u8 new_mode; + bool send_uevent = false; + + old = ep92->ai.system_status_0; + ep92->ai.system_status_0 = snd_soc_read(codec, + EP92_AUDIO_INFO_SYSTEM_STATUS_0); + change = ep92->ai.system_status_0 ^ old; + if (change & EP92_AI_MCLK_ON_MASK) { + pr_debug("ep92 status changed to %d (%s)\n", + (ep92->ai.system_status_0 >> EP92_AI_MCLK_ON_SHIFT) & + EP92_2CHOICE_MASK, + ep92_state_text[(ep92->ai.system_status_0 + >> EP92_AI_MCLK_ON_SHIFT) & EP92_2CHOICE_MASK]); + send_uevent = true; + } + if (change & EP92_AI_AVMUTE_MASK) { + pr_debug("ep92 avmute changed to %d (%s)\n", + (ep92->ai.system_status_0 >> EP92_AI_AVMUTE_SHIFT) & + EP92_2CHOICE_MASK, + ep92_avmute_text[(ep92->ai.system_status_0 + >> EP92_AI_AVMUTE_SHIFT) & EP92_2CHOICE_MASK]); + send_uevent = true; + } + if (change & EP92_AI_LAYOUT_MASK) { + pr_debug("ep92 layout changed to %d (%s)\n", + (ep92->ai.system_status_0) & EP92_2CHOICE_MASK, + ep92_layout_text[(ep92->ai.system_status_0) & + EP92_2CHOICE_MASK]); + send_uevent = true; + } + + old = ep92->ai.audio_status; + ep92->ai.audio_status = snd_soc_read(codec, + EP92_AUDIO_INFO_AUDIO_STATUS); + change = ep92->ai.audio_status ^ old; + if (change & EP92_AI_RATE_MASK) { + pr_debug("ep92 rate changed to %d\n", + ep92_samp_freq_table[(ep92->ai.audio_status) & + EP92_AI_RATE_MASK]); + send_uevent = true; + } + + new_mode = ep92->old_mode; + if (ep92->ai.audio_status & EP92_AI_STD_ADO_MASK) { + ep92->ai.cs[0] = snd_soc_read(codec, + EP92_AUDIO_INFO_CHANNEL_STATUS_0); + if (ep92->ai.cs[0] & EP92_AI_NPCM_MASK) + new_mode = 1; /* Compr */ + else + new_mode = 0; /* LPCM */ + } else if (ep92->ai.audio_status & EP92_AI_HBR_ADO_MASK) + new_mode = 1; /* Compr */ + + if (ep92->old_mode != new_mode) { + pr_debug("ep92 mode changed to %d (%s)\n", new_mode, + ep92_mode_text[new_mode]); + send_uevent = true; + } + ep92->old_mode = new_mode; + + old = ep92->ai.cc; + ep92->ai.cc = snd_soc_read(codec, EP92_AUDIO_INFO_ADO_INFO_FRAME_1); + change = ep92->ai.cc ^ old; + if (change & EP92_AI_CH_COUNT_MASK) { + pr_debug("ep92 ch_count changed to %d (%d)\n", + ep92->ai.cc & EP92_AI_CH_COUNT_MASK, + (ep92->ai.cc & EP92_AI_CH_COUNT_MASK) == 0 ? 0 : + (ep92->ai.cc & EP92_AI_CH_COUNT_MASK) + 1); + send_uevent = true; + } + + old = ep92->ai.ca; + ep92->ai.ca = snd_soc_read(codec, EP92_AUDIO_INFO_ADO_INFO_FRAME_4); + change = ep92->ai.ca ^ old; + if (change & EP92_AI_CH_ALLOC_MASK) { + pr_debug("ep92 ch_alloc changed to 0x%02x\n", + (ep92->ai.ca) & EP92_AI_CH_ALLOC_MASK); + send_uevent = true; + } + + if (send_uevent) + ep92_send_uevent_audio(ep92); +} + +static void ep92_init(struct snd_soc_codec *codec, struct ep92_pdata *ep92) +{ + /* update the format information in mixer controls */ + ep92_read_general_control(codec, ep92); + ep92_read_audio_info(codec, ep92); +} + +static int ep92_probe(struct snd_soc_codec *codec) +{ + struct ep92_pdata *ep92 = snd_soc_codec_get_drvdata(codec); + + ep92->codec = codec; + ep92_init(codec, ep92); + + return 0; +} + +static int ep92_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct regmap *ep92_get_regmap(struct device *dev) +{ + struct ep92_pdata *ep92_ctrl = dev_get_drvdata(dev); + + if (!ep92_ctrl) + return NULL; + + return ep92_ctrl->regmap; +} + +static struct snd_soc_codec_driver soc_codec_drv_ep92 = { + .probe = ep92_probe, + .remove = ep92_remove, + .get_regmap = ep92_get_regmap, + .component_driver = { + .controls = ep92_snd_controls, + .num_controls = ARRAY_SIZE(ep92_snd_controls), + }, +}; + +static struct regmap_config ep92_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ep92_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ep92_reg_defaults), + .max_register = EP92_MAX_REGISTER_ADDR, + .volatile_reg = ep92_volatile_register, + .writeable_reg = ep92_writeable_registers, + .readable_reg = ep92_readable_registers, +}; + +void ep92_read_status(struct work_struct *work) +{ + struct ep92_pdata *ep92 = container_of(work, struct ep92_pdata, + read_status_worker); + struct snd_soc_codec *codec = ep92->codec; + u8 val; + + /* No polling before codec is initialized */ + if (codec == NULL) + return; + + /* check ADO_CHF that is set when audio format has changed */ + val = snd_soc_read(codec, EP92_BI_GENERAL_INFO_1); + if (val == 0xff) { + /* workaround for Nak'ed first read */ + val = snd_soc_read(codec, EP92_BI_GENERAL_INFO_1); + if (val == 0xff) + return; /* assume device not present */ + } + + if (val & EP92_GI_ADO_CHF_MASK) + pr_debug("ep92 audio mode change trigger.\n"); + + if (val & EP92_GI_CEC_ECF_MASK) + pr_debug("ep92 CEC change trigger.\n"); + + /* check for general control changes */ + ep92_read_general_control(codec, ep92); + + /* update the format information in mixer controls */ + ep92_read_audio_info(codec, ep92); +} + +static irqreturn_t ep92_irq(int irq, void *data) +{ + struct ep92_pdata *ep92 = data; + struct snd_soc_codec *codec = ep92->codec; + + /* Treat interrupt before codec is initialized as spurious */ + if (codec == NULL) + return IRQ_NONE; + + dev_dbg(codec->dev, "ep92_interrupt\n"); + + schedule_work(&ep92->read_status_worker); + + return IRQ_HANDLED; +}; + +void ep92_poll_status(unsigned long data) +{ + struct ep92_pdata *ep92 = (struct ep92_pdata *)data; + u32 poll_msec; + + if ((ep92->gc.ctl & EP92_GC_POWER_MASK) == 0) + poll_msec = EP92_POLL_INTERVAL_OFF_MSEC; + else + poll_msec = EP92_POLL_INTERVAL_ON_MSEC; + + mod_timer(&ep92->timer, jiffies + msecs_to_jiffies(poll_msec)); + + schedule_work(&ep92->read_status_worker); +} + +static const struct of_device_id ep92_of_match[] = { + { .compatible = "explore,ep92a6", }, + { } +}; +MODULE_DEVICE_TABLE(of, ep92_of_match); + +static int ep92_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ep92_pdata *ep92; + int ret; + + ep92 = devm_kzalloc(&client->dev, sizeof(struct ep92_pdata), + GFP_KERNEL); + if (ep92 == NULL) + return -ENOMEM; + + ep92->regmap = devm_regmap_init_i2c(client, &ep92_regmap_config); + if (IS_ERR(ep92->regmap)) { + ret = PTR_ERR(ep92->regmap); + dev_err(&client->dev, + "%s %d: Failed to allocate regmap for I2C device: %d\n", + __func__, __LINE__, ret); + return ret; + } + + i2c_set_clientdata(client, ep92); + + /* register interrupt handler */ + INIT_WORK(&ep92->read_status_worker, ep92_read_status); + ep92->irq = client->irq; + if (ep92->irq) { + ret = devm_request_threaded_irq(&client->dev, ep92->irq, + NULL, ep92_irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "ep92_irq", ep92); + if (ret) { + dev_err(&client->dev, + "%s: Failed to request IRQ %d: %d\n", + __func__, ep92->irq, ret); + ep92->irq = 0; + } + } + /* poll status if IRQ is not configured */ + if (ep92->irq == 0) { + setup_timer(&ep92->timer, ep92_poll_status, + (unsigned long)ep92); + mod_timer(&ep92->timer, jiffies + + msecs_to_jiffies(EP92_POLL_INTERVAL_OFF_MSEC)); + } + + /* register codec */ + ret = snd_soc_register_codec(&client->dev, &soc_codec_drv_ep92, + ep92_dai, ARRAY_SIZE(ep92_dai)); + if (ret) { + dev_err(&client->dev, + "%s %d: Failed to register CODEC: %d\n", + __func__, __LINE__, ret); + goto err_reg; + } + + /* Create a kset under /sys/kernel/ */ + ep92_uevent_kset = kset_create_and_add("ep92-hdmi", NULL, kernel_kobj); + if (!ep92_uevent_kset) { + pr_err("%s: error creating uevent kernel set", __func__); + ret = -EINVAL; + goto err_kset; + } + + /* uevent to signal control changes */ + ep92_uevent_ctrl = devm_kzalloc(&client->dev, + sizeof(*ep92_uevent_ctrl), GFP_KERNEL); + if (!ep92_uevent_ctrl) { + ret = -ENOMEM; + goto err_ue_ctrl; + } + + ep92_uevent_ctrl->ktype.release = ep92_release_uevent_data; + ret = ep92_init_uevent_data(ep92_uevent_ctrl, "ctrl-uevent"); + if (ret) { + dev_err(&client->dev, + "%s: Failed to init ctrl-uevent: %d\n", + __func__, ret); + goto err_ue_init_ctrl; + } + + /* uevent to signal audio format changes */ + ep92_uevent_audio = devm_kzalloc(&client->dev, + sizeof(*ep92_uevent_audio), GFP_KERNEL); + if (!ep92_uevent_audio) { + ret = -ENOMEM; + goto err_ue_audio; + } + + ep92_uevent_audio->ktype.release = ep92_release_uevent_data; + ret = ep92_init_uevent_data(ep92_uevent_audio, "audio-uevent"); + if (ret) { + dev_err(&client->dev, + "%s: Failed to init ctrl-uevent: %d\n", + __func__, ret); + goto err_ue_init_audio; + } + return 0; + +err_ue_init_audio: + devm_kfree(&client->dev, ep92_uevent_audio); +err_ue_audio: + ep92_destroy_uevent_data(ep92_uevent_ctrl); +err_ue_init_ctrl: + devm_kfree(&client->dev, ep92_uevent_ctrl); +err_ue_ctrl: + kset_unregister(ep92_uevent_kset); +err_kset: + snd_soc_unregister_codec(&client->dev); +err_reg: + if (ep92->irq == 0) + del_timer(&ep92->timer); + + return ret; +} + +static int ep92_i2c_remove(struct i2c_client *client) +{ + struct ep92_pdata *ep92; + + ep92 = i2c_get_clientdata(client); + if ((ep92 != NULL) && (ep92->irq == 0)) + del_timer(&ep92->timer); + + snd_soc_unregister_codec(&client->dev); + + ep92_destroy_uevent_data(ep92_uevent_ctrl); + devm_kfree(&client->dev, ep92_uevent_ctrl); + + ep92_destroy_uevent_data(ep92_uevent_audio); + devm_kfree(&client->dev, ep92_uevent_audio); + + if (ep92_uevent_kset) { + kset_unregister(ep92_uevent_kset); + ep92_uevent_kset = NULL; + } + + return 0; +} + +static const struct i2c_device_id ep92_i2c_id[] = { + { "ep92-dev", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ep92_i2c_id); + +static struct i2c_driver ep92_i2c_driver = { + .probe = ep92_i2c_probe, + .remove = ep92_i2c_remove, + .id_table = ep92_i2c_id, + .driver = { + .name = "ep92", + .owner = THIS_MODULE, + .of_match_table = ep92_of_match + }, +}; + +static int __init ep92_codec_init(void) +{ + int ret = 0; + + ret = i2c_add_driver(&ep92_i2c_driver); + if (ret) + pr_err("Failed to register EP92 I2C driver: %d\n", ret); + + return ret; +} +module_init(ep92_codec_init); + +static void __exit ep92_codec_exit(void) +{ + i2c_del_driver(&ep92_i2c_driver); +} +module_exit(ep92_codec_exit); + +MODULE_DESCRIPTION("EP92 HDMI repeater/switch driver"); +MODULE_LICENSE("GPL v2"); diff --git a/asoc/codecs/ep92/ep92.h b/asoc/codecs/ep92/ep92.h new file mode 100644 index 000000000000..237c9eac2a42 --- /dev/null +++ b/asoc/codecs/ep92/ep92.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * 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. + */ + +#ifndef __EP92_H__ +#define __EP92_H__ + +/* EP92 register addresses */ +/* BI = Basic Info */ +#define EP92_BI_VENDOR_ID_0 0x00 +#define EP92_BI_VENDOR_ID_1 0x01 +#define EP92_BI_DEVICE_ID_0 0x02 +#define EP92_BI_DEVICE_ID_1 0x03 +#define EP92_BI_VERSION_NUM 0x04 +#define EP92_BI_VERSION_YEAR 0x05 +#define EP92_BI_VERSION_MONTH 0x06 +#define EP92_BI_VERSION_DATE 0x07 +#define EP92_BI_GENERAL_INFO_0 0x08 +#define EP92_BI_GENERAL_INFO_1 0x09 +#define EP92_BI_GENERAL_INFO_2 0x0A +#define EP92_BI_GENERAL_INFO_3 0x0B +#define EP92_BI_GENERAL_INFO_4 0x0C +#define EP92_BI_GENERAL_INFO_5 0x0D +#define EP92_BI_GENERAL_INFO_6 0x0E + +#define EP92_ISP_MODE_ENTER_ISP 0x0F + +#define EP92_GENERAL_CONTROL_0 0x10 +#define EP92_GENERAL_CONTROL_1 0x11 +#define EP92_GENERAL_CONTROL_2 0x12 +#define EP92_GENERAL_CONTROL_3 0x13 +#define EP92_GENERAL_CONTROL_4 0x14 + +#define EP92_CEC_EVENT_CODE 0x15 +#define EP92_CEC_EVENT_PARAM_1 0x16 +#define EP92_CEC_EVENT_PARAM_2 0x17 +#define EP92_CEC_EVENT_PARAM_3 0x18 +#define EP92_CEC_EVENT_PARAM_4 0x19 +/* RESERVED 0x1A */ +/* ... ... */ +/* RESERVED 0x1F */ +#define EP92_AUDIO_INFO_SYSTEM_STATUS_0 0x20 +#define EP92_AUDIO_INFO_SYSTEM_STATUS_1 0x21 +#define EP92_AUDIO_INFO_AUDIO_STATUS 0x22 +#define EP92_AUDIO_INFO_CHANNEL_STATUS_0 0x23 +#define EP92_AUDIO_INFO_CHANNEL_STATUS_1 0x24 +#define EP92_AUDIO_INFO_CHANNEL_STATUS_2 0x25 +#define EP92_AUDIO_INFO_CHANNEL_STATUS_3 0x26 +#define EP92_AUDIO_INFO_CHANNEL_STATUS_4 0x27 +#define EP92_AUDIO_INFO_ADO_INFO_FRAME_0 0x28 +#define EP92_AUDIO_INFO_ADO_INFO_FRAME_1 0x29 +#define EP92_AUDIO_INFO_ADO_INFO_FRAME_2 0x2A +#define EP92_AUDIO_INFO_ADO_INFO_FRAME_3 0x2B +#define EP92_AUDIO_INFO_ADO_INFO_FRAME_4 0x2C +#define EP92_AUDIO_INFO_ADO_INFO_FRAME_5 0x2D + +#define EP92_OTHER_PACKETS_HDMI_VS_0 0x2E +#define EP92_OTHER_PACKETS_HDMI_VS_1 0x2F +#define EP92_OTHER_PACKETS_ACP_PACKET 0x30 +#define EP92_OTHER_PACKETS_AVI_INFO_FRAME_0 0x31 +#define EP92_OTHER_PACKETS_AVI_INFO_FRAME_1 0x32 +#define EP92_OTHER_PACKETS_AVI_INFO_FRAME_2 0x33 +#define EP92_OTHER_PACKETS_AVI_INFO_FRAME_3 0x34 +#define EP92_OTHER_PACKETS_AVI_INFO_FRAME_4 0x35 +#define EP92_OTHER_PACKETS_GC_PACKET_0 0x36 +#define EP92_OTHER_PACKETS_GC_PACKET_1 0x37 +#define EP92_OTHER_PACKETS_GC_PACKET_2 0x38 + +#define EP92_MAX_REGISTER_ADDR EP92_OTHER_PACKETS_GC_PACKET_2 + + +/* EP92 register default values */ +static struct reg_default ep92_reg_defaults[] = { + {EP92_BI_VENDOR_ID_0, 0x17}, + {EP92_BI_VENDOR_ID_1, 0x7A}, + {EP92_BI_DEVICE_ID_0, 0x94}, + {EP92_BI_DEVICE_ID_1, 0xA3}, + {EP92_BI_VERSION_NUM, 0x10}, + {EP92_BI_VERSION_YEAR, 0x09}, + {EP92_BI_VERSION_MONTH, 0x07}, + {EP92_BI_VERSION_DATE, 0x06}, + {EP92_BI_GENERAL_INFO_0, 0x00}, + {EP92_BI_GENERAL_INFO_1, 0x00}, + {EP92_BI_GENERAL_INFO_2, 0x00}, + {EP92_BI_GENERAL_INFO_3, 0x00}, + {EP92_BI_GENERAL_INFO_4, 0x00}, + {EP92_BI_GENERAL_INFO_5, 0x00}, + {EP92_BI_GENERAL_INFO_6, 0x00}, + {EP92_ISP_MODE_ENTER_ISP, 0x00}, + {EP92_GENERAL_CONTROL_0, 0x20}, + {EP92_GENERAL_CONTROL_1, 0x00}, + {EP92_GENERAL_CONTROL_2, 0x00}, + {EP92_GENERAL_CONTROL_3, 0x10}, + {EP92_GENERAL_CONTROL_4, 0x00}, + {EP92_CEC_EVENT_CODE, 0x00}, + {EP92_CEC_EVENT_PARAM_1, 0x00}, + {EP92_CEC_EVENT_PARAM_2, 0x00}, + {EP92_CEC_EVENT_PARAM_3, 0x00}, + {EP92_CEC_EVENT_PARAM_4, 0x00}, + {EP92_AUDIO_INFO_SYSTEM_STATUS_0, 0x00}, + {EP92_AUDIO_INFO_SYSTEM_STATUS_1, 0x00}, + {EP92_AUDIO_INFO_AUDIO_STATUS, 0x00}, + {EP92_AUDIO_INFO_CHANNEL_STATUS_0, 0x00}, + {EP92_AUDIO_INFO_CHANNEL_STATUS_1, 0x00}, + {EP92_AUDIO_INFO_CHANNEL_STATUS_2, 0x00}, + {EP92_AUDIO_INFO_CHANNEL_STATUS_3, 0x00}, + {EP92_AUDIO_INFO_CHANNEL_STATUS_4, 0x00}, + {EP92_AUDIO_INFO_ADO_INFO_FRAME_0, 0x00}, + {EP92_AUDIO_INFO_ADO_INFO_FRAME_1, 0x00}, + {EP92_AUDIO_INFO_ADO_INFO_FRAME_2, 0x00}, + {EP92_AUDIO_INFO_ADO_INFO_FRAME_3, 0x00}, + {EP92_AUDIO_INFO_ADO_INFO_FRAME_4, 0x00}, + {EP92_AUDIO_INFO_ADO_INFO_FRAME_5, 0x00}, + {EP92_OTHER_PACKETS_HDMI_VS_0, 0x00}, + {EP92_OTHER_PACKETS_HDMI_VS_1, 0x00}, + {EP92_OTHER_PACKETS_ACP_PACKET, 0x00}, + {EP92_OTHER_PACKETS_AVI_INFO_FRAME_0, 0x00}, + {EP92_OTHER_PACKETS_AVI_INFO_FRAME_1, 0x00}, + {EP92_OTHER_PACKETS_AVI_INFO_FRAME_2, 0x00}, + {EP92_OTHER_PACKETS_AVI_INFO_FRAME_3, 0x00}, + {EP92_OTHER_PACKETS_AVI_INFO_FRAME_4, 0x00}, + {EP92_OTHER_PACKETS_GC_PACKET_0, 0x00}, + {EP92_OTHER_PACKETS_GC_PACKET_1, 0x00}, + {EP92_OTHER_PACKETS_GC_PACKET_2, 0x00}, +}; + + +/* shift/masks for register bits + * GI = General Info + * GC = General Control + * AI = Audio Info + */ +#define EP92_GI_ADO_CHF_MASK 0x01 +#define EP92_GI_CEC_ECF_MASK 0x02 +#define EP92_GC_POWER_SHIFT 7 +#define EP92_GC_POWER_MASK 0x80 +#define EP92_GC_AUDIO_PATH_SHIFT 5 +#define EP92_GC_AUDIO_PATH_MASK 0x20 +#define EP92_GC_CEC_MUTE_SHIFT 1 +#define EP92_GC_CEC_MUTE_MASK 0x02 +#define EP92_GC_ARC_EN_SHIFT 0 +#define EP92_GC_ARC_EN_MASK 0x01 +#define EP92_GC_RX_SEL_SHIFT 0 +#define EP92_GC_RX_SEL_MASK 0x07 +#define EP92_GC_CEC_VOLUME_SHIFT 0 +#define EP92_GC_CEC_VOLUME_MASK 0xff +#define EP92_AI_MCLK_ON_SHIFT 6 +#define EP92_AI_MCLK_ON_MASK 0x40 +#define EP92_AI_AVMUTE_SHIFT 5 +#define EP92_AI_AVMUTE_MASK 0x20 +#define EP92_AI_LAYOUT_SHIFT 0 +#define EP92_AI_LAYOUT_MASK 0x01 +#define EP92_AI_HBR_ADO_SHIFT 5 +#define EP92_AI_HBR_ADO_MASK 0x20 +#define EP92_AI_STD_ADO_SHIFT 3 +#define EP92_AI_STD_ADO_MASK 0x08 +#define EP92_AI_RATE_MASK 0x07 +#define EP92_AI_NPCM_MASK 0x02 +#define EP92_AI_CH_COUNT_MASK 0x07 +#define EP92_AI_CH_ALLOC_MASK 0xff + +#define EP92_2CHOICE_MASK 1 +#define EP92_GC_CEC_VOLUME_MIN 0 +#define EP92_GC_CEC_VOLUME_MAX 100 +#define EP92_AI_RATE_MIN 0 +#define EP92_AI_RATE_MAX 768000 +#define EP92_AI_CH_COUNT_MIN 0 +#define EP92_AI_CH_COUNT_MAX 8 +#define EP92_AI_CH_ALLOC_MIN 0 +#define EP92_AI_CH_ALLOC_MAX 0xff + +#define EP92_STATUS_NO_SIGNAL 0 +#define EP92_STATUS_AUDIO_ACTIVE 1 + +/* kcontrol storage indices */ +enum { + EP92_KCTL_POWER = 0, + EP92_KCTL_AUDIO_PATH, + EP92_KCTL_CEC_MUTE, + EP92_KCTL_ARC_EN, + EP92_KCTL_RX_SEL, + EP92_KCTL_CEC_VOLUME, + EP92_KCTL_STATE, + EP92_KCTL_AVMUTE, + EP92_KCTL_LAYOUT, + EP92_KCTL_MODE, + EP92_KCTL_RATE, + EP92_KCTL_CH_COUNT, + EP92_KCTL_CH_ALLOC, + EP92_KCTL_MAX +}; + +#endif /* __EP92_H__ */ diff --git a/asoc/qcs405.c b/asoc/qcs405.c index a74bfe3efd43..0045bebc08ac 100644 --- a/asoc/qcs405.c +++ b/asoc/qcs405.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -8195,6 +8196,119 @@ static void msm_i2s_auxpcm_deinit(void) } } +static int msm_scan_i2c_addr(struct platform_device *pdev, + uint32_t busnum, uint32_t addr) +{ + struct i2c_adapter *adap; + u8 rbuf; + struct i2c_msg msg; + int status = 0; + + adap = i2c_get_adapter(busnum); + if (!adap) { + dev_err(&pdev->dev, "%s: Cannot get I2C adapter %d\n", + __func__, busnum); + return -EBUSY; + } + + /* to test presence, read one byte from device */ + msg.addr = addr; + msg.flags = I2C_M_RD; + msg.len = 1; + msg.buf = &rbuf; + + status = i2c_transfer(adap, &msg, 1); + + i2c_put_adapter(adap); + + if (status != 1) { + dev_dbg(&pdev->dev, "%s: I2C read from addr 0x%02x failed\n", + __func__, addr); + return -ENODEV; + } + + dev_dbg(&pdev->dev, "%s: I2C read from addr 0x%02x successful\n", + __func__, addr); + + return 0; +} + +static int msm_detect_ep92_dev(struct platform_device *pdev, + struct snd_soc_card *card) +{ + int i; + uint32_t ep92_busnum = 0; + uint32_t ep92_reg = 0; + const char *ep92_name = NULL; + struct snd_soc_dai_link *dai; + int rc = 0; + + rc = of_property_read_u32(pdev->dev.of_node, "qcom,ep92-busnum", + &ep92_busnum); + if (rc) { + dev_info(&pdev->dev, "%s: No DT match ep92-reg\n", __func__); + return 0; + } + + rc = of_property_read_u32(pdev->dev.of_node, "qcom,ep92-reg", + &ep92_reg); + if (rc) { + dev_info(&pdev->dev, "%s: No DT match ep92-busnum\n", __func__); + return 0; + } + + rc = of_property_read_string(pdev->dev.of_node, "qcom,ep92-name", + &ep92_name); + if (rc) { + dev_info(&pdev->dev, "%s: No DT match ep92-name\n", __func__); + return 0; + } + + /* check I2C bus for connected ep92 chip */ + if (msm_scan_i2c_addr(pdev, ep92_busnum, ep92_reg) < 0) { + /* check a second time after a short delay */ + msleep(20); + if (msm_scan_i2c_addr(pdev, ep92_busnum, ep92_reg) < 0) { + dev_info(&pdev->dev, "%s: No ep92 device found\n", + __func__); + /* continue with snd_card registration without ep92 */ + return 0; + } + } + + dev_info(&pdev->dev, "%s: ep92 device found\n", __func__); + + /* update codec info in MI2S dai link */ + dai = &msm_mi2s_be_dai_links[0]; + for (i=0; iname, LPASS_BE_SEC_MI2S_TX) == 0) { + dev_dbg(&pdev->dev, + "%s: Set Sec MI2S dai to ep92 codec\n", + __func__); + dai->codec_name = ep92_name; + dai->codec_dai_name = "ep92-hdmi"; + break; + } + dai++; + } + + /* update codec info in SPDIF dai link */ + dai = &msm_spdif_be_dai_links[0]; + for (i=0; iname, LPASS_BE_SEC_SPDIF_TX) == 0) { + dev_dbg(&pdev->dev, + "%s: Set Sec SPDIF dai to ep92 codec\n", + __func__); + dai->codec_name = ep92_name; + dai->codec_dai_name = "ep92-arc"; + break; + } + dai++; + } + + return 0; +} + static int msm_asoc_machine_probe(struct platform_device *pdev) { struct snd_soc_card *card; @@ -8212,6 +8326,11 @@ static int msm_asoc_machine_probe(struct platform_device *pdev) if (!pdata) return -ENOMEM; + /* test for ep92 HDMI bridge and update dai links accordingly */ + ret = msm_detect_ep92_dev(pdev, card); + if (ret) + goto err; + card = populate_snd_card_dailinks(&pdev->dev); if (!card) { dev_err(&pdev->dev, "%s: Card uninitialized\n", __func__); diff --git a/config/qcs405auto.conf b/config/qcs405auto.conf index a30f01a635c3..f5a99f1cee34 100644 --- a/config/qcs405auto.conf +++ b/config/qcs405auto.conf @@ -34,3 +34,4 @@ CONFIG_DTS_SRS_TM=m CONFIG_SND_SOC_MSM_STUB=m CONFIG_MSM_AVTIMER=m CONFIG_SND_SOC_MSM_HDMI_CODEC_RX=m +CONFIG_SND_SOC_EP92=m diff --git a/config/qcs405autoconf.h b/config/qcs405autoconf.h index 75332935ea6e..4562621c6b95 100644 --- a/config/qcs405autoconf.h +++ b/config/qcs405autoconf.h @@ -46,3 +46,4 @@ #define CONFIG_SND_SOC_MSM_STUB 1 #define CONFIG_MSM_AVTIMER 1 #define CONFIG_SND_SOC_MSM_HDMI_CODEC_RX 1 +#define CONFIG_SND_SOC_EP92 1