a5xelte: audio: Initial TFA98xx amplifier device implementation
authorChristopher N. Hesse <raymanfx@gmail.com>
Tue, 21 Nov 2017 19:57:41 +0000 (20:57 +0100)
committerJan Altensen <info@stricted.net>
Mon, 12 Aug 2019 07:12:00 +0000 (09:12 +0200)
Change-Id: Icb9d1a3603cd1c6ee6cc9edb22d6018b5c36c237

amplifier/Android.mk [new file with mode: 0644]
amplifier/amplifier.c [new file with mode: 0644]
amplifier/tfa.c [new file with mode: 0644]
amplifier/tfa.h [new file with mode: 0644]
device.mk
ramdisk/etc/init.target.rc

diff --git a/amplifier/Android.mk b/amplifier/Android.mk
new file mode 100644 (file)
index 0000000..d4fcd7c
--- /dev/null
@@ -0,0 +1,28 @@
+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)
diff --git a/amplifier/amplifier.c b/amplifier/amplifier.c
new file mode 100644 (file)
index 0000000..fb8ea33
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * 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,
+    },
+};
diff --git a/amplifier/tfa.c b/amplifier/tfa.c
new file mode 100644 (file)
index 0000000..293cc8f
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * 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);
+    }
+}
diff --git a/amplifier/tfa.h b/amplifier/tfa.h
new file mode 100644 (file)
index 0000000..13c9518
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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
index feb686a7ac3ab846b2c5084f1ee5c5e5d201ed17..92327533efb00180afc68a5a7bc17cd60ced9892 100644 (file)
--- a/device.mk
+++ b/device.mk
@@ -22,6 +22,12 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/languages_full.mk)
 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
index 17c6292b3beba63eab08e2279571a1750e7a8b11..610128735bfdad0644de1ccf127f76ef57cfe3ab 100644 (file)
@@ -14,6 +14,8 @@ on fs
     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