--- /dev/null
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libutils \
+ libcutils \
+ libtinyalsa
+
+LOCAL_C_INCLUDES := \
+ external/tinyalsa/include \
+ external/tinycompress/include \
+ hardware/libhardware/include \
+ hardware/samsung/audio \
+ $(call include-path-for, audio-utils) \
+ $(call include-path-for, audio-route) \
+ $(call include-path-for, audio-effects)
+
+LOCAL_SRC_FILES := \
+ amplifier.c \
+ tfa.c
+
+LOCAL_MODULE := audio_amplifier.$(TARGET_BOOTLOADER_BOARD_NAME)
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
--- /dev/null
+/*
+ * Copyright (C) 2017 Christopher N. Hesse <raymanfx@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "audio_hw_amplifier"
+#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <audio_hw.h>
+#include <hardware/audio_amplifier.h>
+
+#include "tfa.h"
+
+typedef struct amp_device {
+ amplifier_device_t amp_dev;
+ tfa_device_t *tfa_dev;
+ audio_mode_t current_mode;
+ int refcount;
+} amp_device_t;
+
+static amp_device_t *amp_dev = NULL;
+
+/*
+ * Returns the internal TFA mode appropriate for the device.
+ *
+ * @param snd_device The current sound device.
+ *
+ * @return tfa_mode_t identifying the internal amplifier mode.
+ */
+static tfa_mode_t classify_snd_device(uint32_t snd_device) {
+ tfa_mode_t mode = Audio_Mode_None;
+
+ switch (snd_device) {
+ case SND_DEVICE_OUT_SPEAKER:
+ // our audio HAL splits this up
+ //case SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES:
+ mode = Audio_Mode_Music_Normal;
+ break;
+ case SND_DEVICE_OUT_VOICE_SPEAKER:
+ case SND_DEVICE_OUT_VOICE_SPEAKER_WB:
+ mode = Audio_Mode_Voice;
+ break;
+ default:
+ break;
+ }
+
+ return mode;
+}
+
+/*
+ * Hook into amplifier HAL
+ */
+static int amp_enable_output_devices(amplifier_device_t *device,
+ uint32_t devices, bool enable)
+{
+ amp_device_t *dev = (amp_device_t *) device;
+ tfa_mode_t tfa_mode = classify_snd_device(devices);
+ int old_refcount = dev->refcount;
+ int rc = 0;
+
+ ALOGV("%s: devices=0x%x, enable=%d, tfa_mode=%d", __func__, devices, enable, tfa_mode);
+
+ if (tfa_mode == Audio_Mode_None && dev->refcount == 0) {
+ return 0;
+ }
+
+ if (enable) {
+ dev->refcount++;
+ } else if (!enable && dev->refcount > 0) {
+ dev->refcount--;
+ }
+
+ ALOGV("%s: old_refcount=%d, dev->refcount=%d", __func__, old_refcount, dev->refcount);
+
+ if (old_refcount == 0 && dev->refcount > 0) {
+ rc = tfa_power(dev->tfa_dev, true);
+ ALOGE("%s: tfa_power(true) with rc=%d", __func__, rc);
+ } else if (old_refcount > 0 && dev->refcount == 0) {
+ rc = tfa_power(dev->tfa_dev, false);
+ ALOGE("%s: tfa_power(false) with rc=%d", __func__, rc);
+ }
+
+ // TODO: Distinguish between tfa modes
+
+ return rc;
+}
+
+static int amp_set_mode(amplifier_device_t *device, audio_mode_t mode)
+{
+ amp_device_t *dev = (amp_device_t *) device;
+
+ dev->current_mode = mode;
+
+ return 0;
+}
+
+static int amp_dev_close(hw_device_t *device)
+{
+ amp_device_t *dev = (amp_device_t *) device;
+
+ tfa_device_close(dev->tfa_dev);
+
+ free(dev);
+
+ return 0;
+}
+
+static int amp_module_open(const hw_module_t *module, const char *name,
+ hw_device_t **device)
+{
+ tfa_device_t *tfa_dev;
+
+ if (strcmp(name, AMPLIFIER_HARDWARE_INTERFACE)) {
+ ALOGE("%s: %s does not match amplifier hardware interface name",
+ __func__, name);
+ return -ENODEV;
+ }
+
+ if (amp_dev) {
+ ALOGE("%s: Unable to open second instance of the amplifier", __func__);
+ return -EBUSY;
+ }
+
+ tfa_dev = tfa_device_open();
+ if (tfa_dev == NULL) {
+ ALOGE("%s: Unable to open amplifier device", __func__);
+ return -ENOENT;
+ }
+
+ amp_dev = calloc(1, sizeof(amp_device_t));
+ if (!amp_dev) {
+ ALOGE("%s: Unable to allocate memory for amplifier device", __func__);
+ return -ENOMEM;
+ }
+
+ amp_dev->amp_dev.common.tag = HARDWARE_DEVICE_TAG;
+ amp_dev->amp_dev.common.module = (hw_module_t *) module;
+ amp_dev->amp_dev.common.version = HARDWARE_DEVICE_API_VERSION(1, 0);
+ amp_dev->amp_dev.common.close = amp_dev_close;
+
+ amp_dev->amp_dev.set_input_devices = NULL;
+ amp_dev->amp_dev.set_output_devices = NULL;
+ amp_dev->amp_dev.enable_input_devices = NULL;
+ amp_dev->amp_dev.enable_output_devices = amp_enable_output_devices;
+ amp_dev->amp_dev.set_mode = amp_set_mode;
+ amp_dev->amp_dev.output_stream_start = NULL;
+ amp_dev->amp_dev.input_stream_start = NULL;
+ amp_dev->amp_dev.output_stream_standby = NULL;
+ amp_dev->amp_dev.input_stream_standby = NULL;
+ amp_dev->amp_dev.set_parameters = NULL;
+
+ amp_dev->current_mode = AUDIO_MODE_NORMAL;
+ amp_dev->refcount = 0;
+
+ amp_dev->tfa_dev = tfa_dev;
+
+ *device = (hw_device_t *) amp_dev;
+
+ return 0;
+}
+
+static struct hw_module_methods_t hal_module_methods = {
+ .open = amp_module_open,
+};
+
+amplifier_module_t HAL_MODULE_INFO_SYM = {
+ .common = {
+ .tag = HARDWARE_MODULE_TAG,
+ .module_api_version = AMPLIFIER_MODULE_API_VERSION_0_1,
+ .hal_api_version = HARDWARE_HAL_API_VERSION,
+ .id = AMPLIFIER_HARDWARE_MODULE_ID,
+ .name = "Samsung TFA9896 amplifier HAL",
+ .author = "Christopher N. Hesse",
+ .methods = &hal_module_methods,
+ },
+};
--- /dev/null
+/*
+ * Copyright (C) 2017 Christopher N. Hesse <raymanfx@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "audio_hw_amplifier_tfa"
+#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <tinyalsa/asoundlib.h>
+
+#include "tfa.h"
+
+static void * write_dummy_data(void *param) {
+ tfa_device_t *t = (tfa_device_t *) param;
+ uint8_t *buffer;
+ int size;
+ struct pcm *pcm;
+ struct pcm_config config = {
+ .channels = 2,
+ .rate = 48000,
+ .period_size = 256,
+ .period_count = 2,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = config.period_size * config.period_count - 1,
+ .stop_threshold = UINT_MAX,
+ .silence_threshold = 0,
+ .avail_min = 1,
+ };
+
+ pcm = pcm_open(0, 0, PCM_OUT | PCM_MONOTONIC, &config);
+ if (!pcm || !pcm_is_ready(pcm)) {
+ ALOGE("pcm_open failed: %s", pcm_get_error(pcm));
+ if (pcm) {
+ goto err_close_pcm;
+ }
+ goto exit;
+ }
+
+ size = 1024 * 8;
+ buffer = calloc(1, size);
+ if (!buffer) {
+ ALOGE("%s: failed to allocate buffer", __func__);
+ goto err_close_pcm;
+ }
+
+ bool signaled = false;
+ do {
+ if (pcm_write(pcm, buffer, size)) {
+ ALOGE("%s: pcm_write failed", __func__);
+ }
+ if (!signaled) {
+ pthread_mutex_lock(&t->mutex);
+ t->writing = true;
+ pthread_cond_signal(&t->cond);
+ pthread_mutex_unlock(&t->mutex);
+ signaled = true;
+ }
+ } while (t->initializing);
+
+ t->writing = false;
+
+err_free:
+ free(buffer);
+err_close_pcm:
+ pcm_close(pcm);
+exit:
+ return NULL;
+}
+
+static int tfa_clock_on(tfa_device_t *tfa_dev)
+{
+ if (tfa_dev->clock_enabled) {
+ ALOGW("%s: clocks already on", __func__);
+ return -EBUSY;
+ }
+
+ tfa_dev->initializing = true;
+ pthread_create(&tfa_dev->write_thread, NULL, write_dummy_data, tfa_dev);
+ pthread_mutex_lock(&tfa_dev->mutex);
+ while (!tfa_dev->writing) {
+ pthread_cond_wait(&tfa_dev->cond, &tfa_dev->mutex);
+ }
+ pthread_mutex_unlock(&tfa_dev->mutex);
+ tfa_dev->clock_enabled = true;
+
+ ALOGI("%s: clocks enabled", __func__);
+
+ return 0;
+}
+
+static int tfa_clock_off(tfa_device_t *tfa_dev)
+{
+ if (!tfa_dev->clock_enabled) {
+ ALOGW("%s: clocks already off", __func__);
+ return 0;
+ }
+
+ tfa_dev->initializing = false;
+ pthread_join(tfa_dev->write_thread, NULL);
+ tfa_dev->clock_enabled = false;
+
+ ALOGI("%s: clocks disabled", __func__);
+
+ return 0;
+}
+
+/*
+ * Loads the vendor amplifier library and grabs the needed functions.
+ *
+ * @param tfa_dev Device handle.
+ *
+ * @return 0 on success, <0 on error.
+ */
+static int load_tfa_lib(tfa_device_t *tfa_dev) {
+ if (access(TFA_LIBRARY_PATH, R_OK) < 0) {
+ ALOGE("%s: amplifier library %s not found", __func__, TFA_LIBRARY_PATH);
+ return -errno;
+ }
+
+ tfa_dev->lib_handle = dlopen(TFA_LIBRARY_PATH, RTLD_NOW);
+ if (tfa_dev->lib_handle == NULL) {
+ ALOGE("%s: dlopen failed for %s (%s)", __func__, TFA_LIBRARY_PATH, dlerror());
+ return -1;
+ } else {
+ ALOGV("%s: dlopen successful for %s", __func__, TFA_LIBRARY_PATH);
+ }
+
+ tfa_dev->tfa_device_open = (tfa_device_open_t)dlsym(tfa_dev->lib_handle, "tfa_device_open");
+ if (tfa_dev->tfa_device_open == NULL) {
+ ALOGE("%s: dlsym error %s for tfa_device_open", __func__, dlerror());
+ tfa_dev->tfa_device_open = 0;
+ return -1;
+ }
+
+ tfa_dev->tfa_enable = (tfa_enable_t)dlsym(tfa_dev->lib_handle, "tfa_enable");
+ if (tfa_dev->tfa_enable == NULL) {
+ ALOGE("%s: dlsym error %s for tfa_enable", __func__, dlerror());
+ tfa_dev->tfa_enable = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Hooks into the vendor library and enables/disables the amplifier IC.
+ *
+ * @param tfa_dev Device handle.
+ * @param on true or false for enabling/disabling of the IC.
+ *
+ * @return 0 on success, != 0 on error.
+ */
+int tfa_power(tfa_device_t *tfa_dev, bool on) {
+ int rc = 0;
+
+ ALOGV("%s: %s amplifier device", __func__, on ? "Enabling" : "Disabling");
+ pthread_mutex_lock(&tfa_dev->tfa_lock);
+ if (on) {
+ if (tfa_dev->tfa_handle->a1 != 0) {
+ tfa_dev->tfa_handle->a1 = 0;
+ }
+ }
+
+ // this implementation requires explicit clock control
+ if (on && !tfa_dev->clock_enabled) {
+ tfa_clock_on(tfa_dev);
+ } else if (!on && tfa_dev->clock_enabled) {
+ tfa_clock_off(tfa_dev);
+ }
+
+ rc = tfa_dev->tfa_enable(tfa_dev->tfa_handle, on ? 1 : 0);
+ if (rc) {
+ ALOGE("%s: Failed to %s amplifier device", __func__, on ? "enable" : "disable");
+ }
+
+ if (tfa_dev->clock_enabled) {
+ tfa_clock_off(tfa_dev);
+ }
+ pthread_mutex_unlock(&tfa_dev->tfa_lock);
+
+ return rc;
+}
+
+/*
+ * Initializes the amplifier device and local class data.
+ *
+ * @return tfa_device_t on success, NULL on error.
+ */
+tfa_device_t * tfa_device_open() {
+ tfa_device_t *tfa_dev;
+ int rc;
+
+ ALOGV("Opening amplifier device");
+
+ tfa_dev = (tfa_device_t *) malloc(sizeof(tfa_device_t));
+ if (tfa_dev == NULL) {
+ ALOGE("%s: Not enough memory to load the lib handle", __func__);
+ return NULL;
+ }
+
+ // allocate memory for tfa handle
+ tfa_dev->tfa_handle = malloc(sizeof(tfa_handle_t));
+ if (tfa_dev->tfa_handle == NULL) {
+ ALOGE("%s: Not enough memory to load the tfa handle", __func__);
+ return NULL;
+ }
+
+ rc = load_tfa_lib(tfa_dev);
+ if (rc < 0) {
+ ALOGE("%s: Failed to load amplifier library", __func__);
+ return NULL;
+ }
+
+ rc = tfa_dev->tfa_device_open(tfa_dev->tfa_handle, 0);
+ if (rc < 0) {
+ ALOGE("%s: Failed to open amplifier device", __func__);
+ return NULL;
+ }
+
+ pthread_mutex_init(&tfa_dev->tfa_lock, (const pthread_mutexattr_t *) NULL);
+
+ rc = tfa_power(tfa_dev, false);
+ if (rc < 0) {
+ ALOGE("%s: Failed to do initial amplifier powerdown", __func__);
+ return NULL;
+ }
+
+ // do a full powerup - powerdown cycle and initialize clocks
+ tfa_dev->writing = false;
+ tfa_dev->clock_enabled = false;
+ pthread_mutex_init(&tfa_dev->mutex, NULL);
+ pthread_cond_init(&tfa_dev->cond, NULL);
+
+ rc = tfa_power(tfa_dev, true);
+ if (rc < 0) {
+ ALOGE("%s: Failed to do initial amplifier powerup", __func__);
+ return NULL;
+ } else {
+ rc = tfa_power(tfa_dev, false);
+ if (rc < 0) {
+ ALOGE("%s: Failed to do initial amplifier powerdown (2)", __func__);
+ return NULL;
+ }
+ }
+
+ return tfa_dev;
+}
+
+/*
+ * De-Initializes the amplifier device.
+ */
+void tfa_device_close(tfa_device_t *tfa_dev) {
+ ALOGV("%s: Closing amplifier device", __func__);
+ tfa_power(tfa_dev, false);
+
+ pthread_mutex_destroy(&tfa_dev->tfa_lock);
+
+ if (tfa_dev->tfa_handle) {
+ free(tfa_dev->tfa_handle);
+ }
+
+ if (tfa_dev->lib_handle) {
+ dlclose(tfa_dev->lib_handle);
+ }
+
+ if (tfa_dev) {
+ free(tfa_dev);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 Christopher N. Hesse <raymanfx@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TFA_H
+#define TFA_H
+
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+
+#define TFA_LIBRARY_PATH "/system/lib/libtfa98xx.so"
+
+
+/*
+ * Amplifier audio modes for different usecases.
+ */
+typedef enum {
+ Audio_Mode_None = -1,
+ Audio_Mode_Music_Normal,
+ Audio_Mode_Voice,
+ Audio_Mode_Max
+} tfa_mode_t;
+
+/*
+ * It doesn't really matter what this is, apparently we just need a continuous
+ * chunk of memory...
+ */
+typedef struct {
+ volatile int a1;
+ volatile unsigned char a2[500];
+} tfa_handle_t;
+
+/*
+ * Vendor functions that we dlsym.
+ */
+typedef int (*tfa_device_open_t)(tfa_handle_t*, int);
+typedef int (*tfa_enable_t)(tfa_handle_t*, int);
+
+/*
+ * TFA Amplifier device abstraction.
+ *
+ * lib_handle: The prebuilt vendor blob, loaded into memory
+ * tfa_handle: Misc data we need to pass to the vendor function calls
+ * tfa_lock: A mutex guarding amplifier enable/disable operations
+ * tfa_device_open: Vendor function for initializing the amplifier
+ * tfa_enable: Vendor function for enabling/disabling the amplifier
+ * mode: Audio mode for the current audio device
+ */
+typedef struct {
+ void *lib_handle;
+ tfa_handle_t* tfa_handle;
+ pthread_mutex_t tfa_lock;
+ tfa_device_open_t tfa_device_open;
+ tfa_enable_t tfa_enable;
+ tfa_mode_t mode;
+
+ // for clock init
+ atomic_bool initializing;
+ bool clock_enabled;
+ bool writing;
+ pthread_t write_thread;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+} tfa_device_t;
+
+/*
+ * Public API
+ */
+int tfa_power(tfa_device_t *tfa_dev, bool on);
+tfa_device_t * tfa_device_open();
+void tfa_device_close(tfa_device_t *tfa_dev);
+
+#endif // TFA_H
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/configs/audio/mixer_paths.xml:$(TARGET_COPY_OUT_VENDOR)/etc/mixer_paths_0.xml
+
+PRODUCT_PACKAGES += \
+ libtfa98xx \
+ audio_amplifier.universal7580 \
+ libtinycompress
+
# Boot animation
TARGET_BOOTANIMATION_PRELOAD := true
TARGET_BOOTANIMATION_TEXTURE_CACHE := true
chown system radio /sys/class/sec/sec_touchkey/sar_enable
chown system radio /sys/class/sec/sec_touchkey/sw_reset
chown system radio /sys/class/sec/sec_touchkey/touchkey_earjack
+ chmod 0660 /dev/i2c-0
+ chown audio audio /dev/i2c-0
# Accelerometer_sensor
chown system radio /sys/class/sensors/accelerometer_sensor/mcu_rev