浏览代码

Merge "asoc: qcs405: Add support for ep92 HDMI bridge chip"

Linux Build Service Account 6 年之前
父节点
当前提交
057e0e85de
共有 8 个文件被更改,包括 1862 次插入0 次删除
  1. 1 0
      Makefile.am
  2. 50 0
      asoc/codecs/ep92/Android.mk
  3. 106 0
      asoc/codecs/ep92/Kbuild
  4. 1382 0
      asoc/codecs/ep92/ep92.c
  5. 202 0
      asoc/codecs/ep92/ep92.h
  6. 119 0
      asoc/qcs405.c
  7. 1 0
      config/qcs405auto.conf
  8. 1 0
      config/qcs405autoconf.h

+ 1 - 0
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:

+ 50 - 0
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 <chipset>_audio.ko as LOCAL_MODULE.
+# This means we need to rename the module to <chipset>_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

+ 106 - 0
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')\"

+ 1382 - 0
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 <linux/init.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <linux/workqueue.h>
+#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");

+ 202 - 0
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__ */

+ 119 - 0
asoc/qcs405.c

@@ -16,6 +16,7 @@
 #include <linux/of_gpio.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
+#include <linux/i2c.h>
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/input.h>
@@ -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; i<ARRAY_SIZE(msm_mi2s_be_dai_links); i++) {
+		if (strcmp(dai->name, 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; i<ARRAY_SIZE(msm_spdif_be_dai_links); i++) {
+		if (strcmp(dai->name, 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__);

+ 1 - 0
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

+ 1 - 0
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