From: Christopher N. Hesse Date: Tue, 21 Nov 2017 19:57:41 +0000 (+0100) Subject: a5xelte: audio: Initial TFA98xx amplifier device implementation X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=562312cba9d3d7b36ae77255b3824fa563d5ae1d;p=GitHub%2FLineageOS%2Fandroid_device_samsung_a5xelte.git a5xelte: audio: Initial TFA98xx amplifier device implementation Change-Id: Icb9d1a3603cd1c6ee6cc9edb22d6018b5c36c237 --- diff --git a/amplifier/Android.mk b/amplifier/Android.mk new file mode 100644 index 0000000..d4fcd7c --- /dev/null +++ b/amplifier/Android.mk @@ -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 index 0000000..fb8ea33 --- /dev/null +++ b/amplifier/amplifier.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2017 Christopher N. Hesse + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 index 0000000..293cc8f --- /dev/null +++ b/amplifier/tfa.c @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2017 Christopher N. Hesse + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 index 0000000..13c9518 --- /dev/null +++ b/amplifier/tfa.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 Christopher N. Hesse + * + * 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 +#include +#include + +#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 diff --git a/device.mk b/device.mk index feb686a..9232753 100644 --- 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 diff --git a/ramdisk/etc/init.target.rc b/ramdisk/etc/init.target.rc index 17c6292..6101287 100644 --- a/ramdisk/etc/init.target.rc +++ b/ramdisk/etc/init.target.rc @@ -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