diff options
author | Yunmi Ha <yunmi.ha@samsung.com> | 2021-01-12 15:38:22 +0900 |
---|---|---|
committer | Yunmi Ha <yunmi.ha@samsung.com> | 2021-01-13 11:55:21 +0900 |
commit | 2eba93eb14b0ff625e13c76437181b09503f0d87 (patch) | |
tree | 77da88bbfd61adf895c0a659782037704385367e | |
parent | 1b0ea452fdc78a1569614d03f6f0e68620b72e4b (diff) | |
download | device-tm1-2eba93eb14b0ff625e13c76437181b09503f0d87.tar.gz device-tm1-2eba93eb14b0ff625e13c76437181b09503f0d87.tar.bz2 device-tm1-2eba93eb14b0ff625e13c76437181b09503f0d87.zip |
haptic: apply next HAL architecture (hal api + backend)
Change-Id: Ibd3071ed1bb2b0d825718898404db01bdabe9d02
Signed-off-by: Yunmi Ha <yunmi.ha@samsung.com>
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | hw/common/common.h | 36 | ||||
-rw-r--r-- | hw/haptic/CMakeLists.txt | 24 | ||||
-rw-r--r-- | hw/haptic/standard.c | 587 | ||||
-rw-r--r-- | packaging/device-manager-plugin-sc7730.spec | 4 |
5 files changed, 652 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index d14c8c0..9b7d08a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,3 +18,4 @@ ADD_SUBDIRECTORY(hw/thermal) ADD_SUBDIRECTORY(hw/usb_gadget) ADD_SUBDIRECTORY(hw/usb_client) ADD_SUBDIRECTORY(hw/board) +ADD_SUBDIRECTORY(hw/haptic) diff --git a/hw/common/common.h b/hw/common/common.h new file mode 100644 index 0000000..c8b84f1 --- /dev/null +++ b/hw/common/common.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 __HAL_BACKEND_COMMON_H__ +#define __HAL_BACKEND_COMMON_H__ + +#ifdef FEATURE_DLOG + #define LOG_TAG "HALBACKEND_DEVICE" + #include <dlog.h> + #define _D(fmt, args...) SLOGD(fmt, ##args) + #define _I(fmt, args...) SLOGI(fmt, ##args) + #define _W(fmt, args...) SLOGW(fmt, ##args) + #define _E(fmt, args...) SLOGE(fmt, ##args) +#else + #define _D(x, ...) + #define _I(x, ...) + #define _W(x, ...) + #define _E(x, ...) +#endif + +#define EXPORT __attribute__ ((visibility("default"))) + +#endif /* __HAL_BACKEND_COMMON_H__ */ diff --git a/hw/haptic/CMakeLists.txt b/hw/haptic/CMakeLists.txt new file mode 100644 index 0000000..23a7a50 --- /dev/null +++ b/hw/haptic/CMakeLists.txt @@ -0,0 +1,24 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(hal-backend-device-haptic C) + +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) + +INCLUDE_DIRECTORIES(../common) + +INCLUDE(FindPkgConfig) +pkg_check_modules(haptic_pkgs REQUIRED + dlog + glib-2.0 + libsyscommon) + +FOREACH(flag ${haptic_pkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") + +ADD_LIBRARY(${PROJECT_NAME} MODULE standard.c) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${haptic_pkgs_LDFLAGS}) + +INSTALL(TARGETS ${PROJECT_NAME} DESTINATION /hal/lib COMPONENT RuntimeLibraries) diff --git a/hw/haptic/standard.c b/hw/haptic/standard.c new file mode 100644 index 0000000..466dd54 --- /dev/null +++ b/hw/haptic/standard.c @@ -0,0 +1,587 @@ +/* + * hal-backend-device-haptic + * + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * 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. + */ + + +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <sys/types.h> +#include <fcntl.h> +#include <dirent.h> +#include <linux/input.h> +#include <libsyscommon/list.h> +#include <hal/device/hal-haptic-interface.h> + +#include "common.h" + +#define MAX_MAGNITUDE 0xFFFF +#define PERIODIC_MAX_MAGNITUDE 0x7FFF /* 0.5 * MAX_MAGNITUDE */ +#define RUMBLE_MAX_MAGNITUDE 0xFFFF + +#define DEV_INPUT "/dev/input" +#define EVENT "event" + +#define MAX_DATA 16 +#define FF_INFO_MAGIC 0xDEADFEED + +#define SET_OVERDRIVE_BIT(x,value) (x |= ((value > 0)? 1: 0)<<14) +#define SET_LEVEL_BIT(x,value) (x |= (value<<13)) + +#define BITS_PER_LONG (sizeof(long) * 8) +#define LONG(x) ((x) / BITS_PER_LONG) +#define OFFSET(x) ((x) & (BITS_PER_LONG - 1)) +#define test_bit(bit, array) ((array[LONG(bit)] >> OFFSET(bit)) & 1) + +#define safe_free(x) safe_free_memory((void**)&(x)) + +struct ff_info_header { + unsigned int magic; + int iteration; + int ff_info_data_count; +}; + +struct ff_info_data { + int type;/* play, stop etc */ + int magnitude; /* strength */ + int length; /* in ms for stop, play*/ +}; + +struct ff_info_buffer { + struct ff_info_header header; + struct ff_info_data data[MAX_DATA]; +}; + +struct ff_info { + int handle; + guint timer; + struct ff_effect effect; + struct ff_info_buffer *ffinfobuffer; + int currentindex; +}; + +static int ff_fd; +static GList *ff_list; +static GList *handle_list; +static char ff_path[PATH_MAX]; +static int unique_number; +static int current_effect_id = -1; + +static int stop_device(int device_handle); + +static inline void safe_free_memory(void** mem) +{ + if (mem && *mem) { + free(*mem); + *mem = NULL; + } +} + +static struct ff_info *read_from_list(int handle) +{ + struct ff_info *temp; + GList *elem; + + SYS_G_LIST_FOREACH(ff_list, elem, temp) { + if (temp->handle == handle) + return temp; + } + return NULL; +} + +static bool check_valid_handle(struct ff_info *info) +{ + struct ff_info *temp; + GList *elem; + + SYS_G_LIST_FOREACH(ff_list, elem, temp) { + if (temp == info) + break; + } + + if (!temp) + return false; + return true; +} + +static bool check_fd(int *fd) +{ + int ffd; + + if (*fd > 0) + return true; + + ffd = open(ff_path, O_RDWR); + if (ffd < 0) + return false; + + *fd = ffd; + return true; +} + +static int ff_stop(int fd, struct ff_effect *effect); +static gboolean timer_cb(void *data) +{ + struct ff_info *info = (struct ff_info *)data; + + if (!info) { + _E("Failed to check info."); + return G_SOURCE_REMOVE; + } + + if (!check_valid_handle(info)) { + _E("Failed to check valied info."); + return G_SOURCE_REMOVE; + } + + _I("Stop vibration by timer. id(%d)", info->effect.id); + + /* stop previous vibration */ + ff_stop(ff_fd, &info->effect); + + /* reset timer */ + info->timer = 0; + + return G_SOURCE_REMOVE; +} + +static int ff_find_device(void) +{ + DIR *dir; + struct dirent *dent; + char ev_path[PATH_MAX]; + unsigned long features[1+FF_MAX/sizeof(unsigned long)]; + int fd, ret; + + dir = opendir(DEV_INPUT); + if (!dir) + return -errno; + + while (1) { + dent = readdir(dir); + if (dent == NULL) + break; + + if (dent->d_type == DT_DIR || + !strstr(dent->d_name, "event")) + continue; + + snprintf(ev_path, sizeof(ev_path), "%s/%s", DEV_INPUT, dent->d_name); + + fd = open(ev_path, O_RDWR); + if (fd < 0) { + _E("Failed to open '%s'.: %d", ev_path, errno); + continue; + } + + /* get force feedback device */ + memset(features, 0, sizeof(features)); + ret = ioctl(fd, EVIOCGBIT(EV_FF, sizeof(features)), features); + if (ret == -1) { + close(fd); + continue; + } + + if (test_bit(FF_CONSTANT, features)) + _D("'%s' type: constant", ev_path); + if (test_bit(FF_PERIODIC, features)) + _D("'%s' type: periodic", ev_path); + if (test_bit(FF_SPRING, features)) + _D("'%s' type: spring", ev_path); + if (test_bit(FF_FRICTION, features)) + _D("'%s' type: friction", ev_path); + if (test_bit(FF_RUMBLE, features)) + _D("'%s' type: rumble", ev_path); + + if (test_bit(FF_RUMBLE, features)) { + memcpy(ff_path, ev_path, strlen(ev_path)+1); + close(fd); + closedir(dir); + return 0; + } + + close(fd); + } + + closedir(dir); + return -1; +} + +static int ff_init_effect(struct ff_effect *effect) +{ + if (!effect) + return -EINVAL; + + effect->type = FF_RUMBLE; + effect->replay.length = 0; + effect->replay.delay = 0; + effect->id = -1; + effect->u.rumble.strong_magnitude = 0; + effect->u.rumble.weak_magnitude = 0; + + return 0; +} + +static int ff_set_effect(struct ff_effect *effect, int duration, int level, int intensity, int frequency, int overdrive) +{ + if (!effect) { + _E("There is no valid effect."); + return -EINVAL; + } + + /* + __u16 strong_magnitude; // strong_magnitude[15:15] = 0 (Reserved) + // strong_magnitude[14:14] = Overdrive : 0 (Off) or 1 (On) + // strong_magnitude[13:0] = Intensity Value : 0 (Stop) or 1 ~ 10000 (Intensity) + __u16 weak_magnitude; // weak_magnitude[15:13] = Intensity Level : 1 ~ 5 + // weak_magnitude[12:0] = Frequency : 0 ~ 8191 (0 ~ 819.1 Hz) + */ + + effect->u.rumble.strong_magnitude = intensity; + SET_OVERDRIVE_BIT(effect->u.rumble.strong_magnitude, overdrive); + + effect->u.rumble.weak_magnitude = frequency; + SET_LEVEL_BIT(effect->u.rumble.weak_magnitude, level); + + /* set member variables in effect struct */ + effect->replay.length = duration; /* length millisecond */ + + //_D("rumble data: strong_magnitude = 0x%x, weak_magnitude = 0x%x", effect->u.rumble.strong_magnitude, effect->u.rumble.weak_magnitude); + return 0; +} + +static int ff_play(int fd, struct ff_effect *effect) +{ + struct input_event play; + int ret; + + if (fd < 0 || !effect) { + _E("Fd(%d) or effect(%s) is invalid", fd, effect ? "not null" : "null"); + return -EINVAL; + } + + /* upload an effect */ + if (current_effect_id == -1) { + if (ioctl(fd, EVIOCSFF, effect) == -1) { + _E("Failed to ioctl"); + return -errno; + } + current_effect_id = effect->id; + } else + effect->id = current_effect_id; + + /* play vibration*/ + play.type = EV_FF; + play.code = effect->id; + play.value = 1; /* 1 : PLAY, 0 : STOP */ + + ret = write(fd, (const void *)&play, sizeof(play)); + if (ret == -1) { + _E("Failed to write"); + return -errno; + } + + return 0; +} + +static int ff_stop(int fd, struct ff_effect *effect) +{ + struct input_event stop; + int ret; + + if (fd < 0 || !effect) + return -EINVAL; + + if (effect->id == -1) { + if (current_effect_id == -1) + return 0; + effect->id = current_effect_id; + } + + /* Stop vibration */ + stop.type = EV_FF; + stop.code = effect->id; + stop.value = 0; /* 1 : PLAY, 0 : STOP */ + ret = write(fd, (const void *)&stop, sizeof(stop)); + if (ret == -1) + return -errno; + + /* removing an effect from the device */ + if (ioctl(fd, EVIOCRMFF, effect->id) == -1) + return -errno; + + /* reset effect id */ + effect->id = -1; + current_effect_id = -1; + + return 0; +} + +static int get_device_count(int *count) +{ + /* suppose there is just one haptic device */ + if (count) + *count = 1; + + return 0; +} + +static int open_device(int *device_handle) +{ + struct ff_info *info; + int n; + bool found = false; + GList *elem; + + if (!device_handle) + return -EINVAL; + + /* if it is the first element */ + n = SYS_G_LIST_LENGTH(ff_list); + if (n == 0 && !ff_fd) { + _I("First element: open ff driver"); + /* open ff driver */ + ff_fd = open(ff_path, O_RDWR); + if (ff_fd < 0) { + _E("Failed to open %s : %d", ff_path, errno); + return -errno; + } + } + + /* allocate memory */ + info = calloc(sizeof(struct ff_info), 1); + if (!info) { + _E("Failed to allocate memory : %d", errno); + return -errno; + } + + /* initialize ff_effect structure */ + ff_init_effect(&info->effect); + + if (unique_number == INT_MAX) + unique_number = 0; + + while (found != true) { + ++unique_number; + elem = SYS_G_LIST_FIND(handle_list, (gpointer)(long)unique_number); + if (!elem) + found = true; + } + + info->handle = unique_number; + + /* add info to local list */ + SYS_G_LIST_APPEND(ff_list, info); + SYS_G_LIST_APPEND(handle_list, (gpointer)(long)info->handle); + + *device_handle = info->handle; + return 0; +} + +static int close_device(int device_handle) +{ + struct ff_info *info; + int n; + + info = read_from_list(device_handle); + if (!info) { + _E("Handle %d failed to check info", device_handle); + return -ENOENT; /* 2 */ + } + + if (!check_valid_handle(info)) { + _E("Handle %d failed to check valid handle", device_handle); + return -EINVAL; /* 22 */ + } + + if (!check_fd(&ff_fd)) { + _E("Handle %d failed to check fd", device_handle); + return -ENODEV; /* 19 */ + } + + /* stop vibration */ + stop_device(device_handle); + + SYS_G_LIST_REMOVE(handle_list, (gpointer)(long)info->handle); + + safe_free(info->ffinfobuffer); + /* remove info from local list */ + SYS_G_LIST_REMOVE(ff_list, info); + safe_free(info); + + /* if it is the last element */ + n = SYS_G_LIST_LENGTH(ff_list); + if (n == 0 && ff_fd) { + _I("Last element: close ff driver"); + /* close ff driver */ + close(ff_fd); + ff_fd = 0; + } + + return 0; +} + +static int vibrate_monotone(int device_handle, int duration, int frequency, int overdrive, int level, int intensity, int priority) +{ + struct ff_info *info; + int ret; + + info = read_from_list(device_handle); + if (!info) { + _E("Handle %d failed to check list.", device_handle); + return -EINVAL; + } + + if (!check_valid_handle(info)) { + _E("Handle %d failed to check handle.", device_handle); + return -EINVAL; + } + + if (!check_fd(&ff_fd)) + return -ENODEV; + + if (duration <= 0) { + _I("Handle %d skip requests with duration 0.", device_handle); + return 0; + } + + /* Zero(0) is the infinitely vibration value */ + if (duration == HAPTIC_MODULE_DURATION_UNLIMITED) + duration = 0; + + /* unregister existing timer */ + if (info->timer) { + ff_stop(ff_fd, &info->effect); + g_source_remove(info->timer); + info->timer = 0; + } + + /* set effect as per arguments */ + ff_init_effect(&info->effect); + ret = ff_set_effect(&info->effect, duration, level, intensity, frequency, overdrive); + if (ret < 0) { + _E("Handle %d fail to set effect(duration:%d, level:%d, intensity:%d, frequency:%d, overdrive:%d) : %d", + device_handle, duration, level, intensity, frequency, overdrive, ret); + return ret; + } + + /* play effect as per arguments */ + ret = ff_play(ff_fd, &info->effect); + if (ret < 0) { + _E("Handle %d fail to play haptic effect(fd:%d id:%d) : %d", + device_handle, ff_fd, info->effect.id, ret); + return ret; + } + + _I("Play vibration. Handle %d effect id : %d %dms", device_handle, info->effect.id, duration); + + /* register timer */ + if (duration) { + info->timer = g_timeout_add(duration, timer_cb, info); + if (!info->timer) + _E("Handle %d failed to add timer callback", device_handle); + } + + return 0; +} + +static int stop_device(int device_handle) +{ + struct ff_info *info; + int r; + + info = read_from_list(device_handle); + if (!info) { + _E("Handle %d fail to check info", device_handle); + return -ENOENT; /* 2 */ + } + + if (!check_fd(&ff_fd)) { + _E("Handle %d fail to check fd", device_handle); + return -ENODEV; /* 19 */ + } + + /* stop effect */ + r = ff_stop(ff_fd, &info->effect); + if (r < 0) + _E("failed to stop effect(id:%d) : %d", info->effect.id, r); + else + _I("Stop vibration by request. id(%d)", info->effect.id); + + /* unregister existing timer */ + if (r >= 0 && info->timer) { + g_source_remove(info->timer); + info->timer = 0; + } + + return 0; +} + +static bool is_valid(void) +{ + int ret; + + ret = ff_find_device(); + if (ret < 0) { + _E("Do not support standard haptic device"); + return false; + } + + _I("Support standard haptic device"); + return true; +} + +static int haptic_init(void **data) +{ + hal_backend_haptic_funcs *haptic_funcs; + + haptic_funcs = calloc(1, sizeof(hal_backend_haptic_funcs)); + if (!haptic_funcs) + return -ENOMEM; + + haptic_funcs->get_device_count = get_device_count; + haptic_funcs->open_device = open_device; + haptic_funcs->close_device = close_device; + haptic_funcs->vibrate_monotone = vibrate_monotone; + haptic_funcs->stop_device = stop_device; + haptic_funcs->is_valid = is_valid; + + *data = (void *)haptic_funcs; + + return 0; +} + +static int haptic_exit(void *data) +{ + if (!data) + return -EINVAL; + + free(data); + return 0; +} + +hal_backend EXPORT hal_backend_device_haptic_data = { + .name = "haptic-standard", + .vendor = "", + .abi_version = HAL_ABI_VERSION_TIZEN_6_5, + .init = haptic_init, + .exit = haptic_exit, +}; diff --git a/packaging/device-manager-plugin-sc7730.spec b/packaging/device-manager-plugin-sc7730.spec index 7c7c320..8e4af3c 100644 --- a/packaging/device-manager-plugin-sc7730.spec +++ b/packaging/device-manager-plugin-sc7730.spec @@ -13,6 +13,9 @@ BuildRequires: pkgconfig(dlog) BuildRequires: pkgconfig(hwcommon) BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(libudev) +BuildRequires: pkgconfig(hal-api-common) +BuildRequires: pkgconfig(hal-api-device) +BuildRequires: pkgconfig(libsyscommon) %description Device manager plugin sc7730 @@ -39,6 +42,7 @@ install -m 644 rules/61-video.rules %{buildroot}%{_prefix}/lib/udev/rules.d %postun -p /sbin/ldconfig %files +/hal/lib/*.so* %{_libdir}/hw/*.so %manifest %{name}.manifest %license LICENSE.Apache-2.0 |