1
0
Fork 0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

501 lines
12 KiB

/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 <hardware/hdmi_cec.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <stdlib.h>
#include <string.h>
#include <hdmicec.h>
#include <unistd.h>
#include <cutils/properties.h>
static int hdmi_cec_device_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device);
static struct hw_module_methods_t hdmi_cec_module_methods = {
open: hdmi_cec_device_open
};
hdmi_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: HDMI_CEC_HARDWARE_MODULE_ID,
name: "Rockchip hdmi cec module",
author: "Rockchip",
methods: &hdmi_cec_module_methods,
}
};
static int logicaddr_to_type(cec_logical_address_t addr)
{
int type;
switch (addr) {
case CEC_ADDR_TV:
type = CEC_LOG_ADDR_TYPE_TV;
break;
case CEC_ADDR_RECORDER_1:
case CEC_ADDR_RECORDER_2:
case CEC_ADDR_RECORDER_3:
type = CEC_LOG_ADDR_TYPE_RECORD;
break;
case CEC_ADDR_TUNER_1:
case CEC_ADDR_TUNER_2:
case CEC_ADDR_TUNER_3:
case CEC_ADDR_TUNER_4:
type = CEC_LOG_ADDR_TYPE_TUNER;
break;
case CEC_ADDR_PLAYBACK_1:
case CEC_ADDR_PLAYBACK_2:
case CEC_ADDR_PLAYBACK_3:
type = CEC_LOG_ADDR_TYPE_PLAYBACK;
break;
case CEC_ADDR_AUDIO_SYSTEM:
type = CEC_LOG_ADDR_TYPE_AUDIOSYSTEM;
break;
default:
type = -1;
}
return type;
}
static int latype_to_devtype(int latype)
{
int devtype;
switch (latype) {
case CEC_LOG_ADDR_TYPE_TV:
devtype = CEC_OP_PRIM_DEVTYPE_TV;
break;
case CEC_LOG_ADDR_TYPE_RECORD:
devtype = CEC_OP_PRIM_DEVTYPE_RECORD;
break;
case CEC_LOG_ADDR_TYPE_TUNER:
devtype = CEC_OP_PRIM_DEVTYPE_TUNER;
break;
case CEC_LOG_ADDR_TYPE_PLAYBACK:
devtype = CEC_OP_PRIM_DEVTYPE_PLAYBACK;
break;
case CEC_LOG_ADDR_TYPE_AUDIOSYSTEM:
devtype = CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM;
break;
default:
devtype = -1;
}
return devtype;
}
static int set_kernel_logical_address(struct hdmi_cec_context_t* ctx, cec_logical_address_t addr)
{
int ret, la_type, dev_type;
int mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU;
struct cec_log_addrs log_addr;
ALOGD("%s, logic addr:%02x\n", __func__, addr);
if (ctx->fd < 0) {
ALOGE("%s open error", __func__);
return -ENOENT;
}
la_type = logicaddr_to_type(addr);
if (la_type < 0) {
ALOGE("%s invalid logic type\n", __func__);
return -EINVAL;
}
dev_type = latype_to_devtype(la_type);
if (dev_type < 0) {
ALOGE("%s invalid device type\n", __func__);
return -EINVAL;
}
ret = ioctl(ctx->fd, CEC_S_MODE, &mode);
if (ret) {
ALOGE("CEC set mode error!\n");
return ret;
}
ret = ioctl(ctx->fd, CEC_ADAP_G_LOG_ADDRS, &log_addr);
if (ret) {
ALOGE("%s get logic address err ret:%d\n", __func__, ret);
return -EINVAL;
}
ALOGI("primary_device_type:%02x,log_addr_type:%02x,log_addr[0]:%02x\n",
log_addr.primary_device_type[0], log_addr.log_addr_type[0], log_addr.log_addr[0]);
if (log_addr.log_addr[0] != CEC_LOG_ADDR_INVALID && log_addr.log_addr[0]) {
ALOGD("LA is existing, not need to set logic addr\n");
return 0;
}
log_addr.cec_version = HDMI_CEC_VERSION;
log_addr.num_log_addrs = 1;
log_addr.log_addr[0] = addr;
log_addr.vendor_id = HDMI_CEC_VENDOR_ID;
log_addr.osd_name[0] = 'R';
log_addr.osd_name[1] = 'K';
log_addr.primary_device_type[0] = dev_type;
log_addr.log_addr_type[0] = la_type;
ret = ioctl(ctx->fd, CEC_ADAP_S_LOG_ADDRS, &log_addr);
if (ret) {
ALOGE("%s set logic address err ret:%d\n", __func__, ret);
return -1;
}
return 0;
}
static int hdmi_cec_add_logical_address(const struct hdmi_cec_device* dev,
cec_logical_address_t addr)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
return set_kernel_logical_address(ctx, addr);
}
static void hdmi_cec_clear_logical_address(const struct hdmi_cec_device* dev)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
int ret;
struct cec_log_addrs log_addr;
ALOGI("%s", __func__);
if (ctx->fd < 0) {
ALOGE("%s open error!", __func__);
return;
}
if (!ctx->cec_init) {
ALOGI("%s cec is not init!", __func__);
return;
}
log_addr.num_log_addrs = 0;
ret = ioctl(ctx->fd, CEC_ADAP_S_LOG_ADDRS, &log_addr);
if (ret) {
ALOGE("%s set logic address err ret:%d\n", __func__, ret);
return;
}
}
static int hdmi_cec_get_physical_address(const struct hdmi_cec_device* dev, uint16_t* addr)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
int ret, i;
uint16_t val = 0;
if (addr == NULL) {
ALOGE("%s addr is null", __func__);
return -ENXIO;
}
if (ctx->fd < 0) {
ALOGE("%s open error!", __func__);
return -ENOENT;
}
for (i = 0; i < 5; i++) {
ret = ioctl(ctx->fd, CEC_ADAP_G_PHYS_ADDR, &val);
if (ret) {
ALOGE("CEC read physical addr error! ret:%d\n", ret);
return ret;
}
if((val != 0xffff) && val) {
break;
}
usleep(20000);
}
if (i == 5) {
ALOGE("get phy addr err!:%x\n", val);
return -EINVAL;
}
*addr = val;
ALOGI("%s val = %x", __func__, val);
return 0;
}
static int hdmi_cec_is_connected(const struct hdmi_cec_device* dev, int port_id)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
if (ctx->hotplug)
return HDMI_CONNECTED;
else
return HDMI_NOT_CONNECTED;
}
static int hdmi_cec_send_message(const struct hdmi_cec_device* dev, const cec_message_t* message)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
struct cec_msg cecframe;
int i, ret = 0;
if (!ctx->enable) {
ALOGE("%s cec disabled\n", __func__);
return -EPERM;
}
if (ctx->fd < 0) {
ALOGE("%s open error", __func__);
return -ENOENT;
}
if(!ctx->hotplug)
return -EPERM;
memset(&cecframe, 0, sizeof(struct cec_msg));
if (message->initiator == message->destination) {
struct cec_log_addrs log_addr;
ret = ioctl(ctx->fd, CEC_ADAP_G_LOG_ADDRS, &log_addr);
if (ret) {
ALOGE("%s get logic address err ret:%d\n", __func__, ret);
return -EINVAL;
}
ALOGD("kernel logic addr:%02x, preferred logic addr:%02x",
log_addr.log_addr[0], message->initiator);
if (log_addr.log_addr[0] != CEC_LOG_ADDR_INVALID && log_addr.log_addr[0]) {
ALOGI("kernel logaddr is existing\n");
if (log_addr.log_addr[0] == message->initiator) {
ALOGI("kernel logaddr is preferred logaddr\n");
return HDMI_RESULT_NACK;
} else {
ALOGI("preferred log addr is not kernel log addr\n");
return HDMI_RESULT_SUCCESS;
}
} else {
ALOGI("kernel logaddr is not existing\n");
if(!set_kernel_logical_address(ctx, message->initiator)) {
for (i = 0; i < 5; i++) {
if (!ctx->phy_addr || ctx->phy_addr == 0xffff) {
ALOGE("phy addr not ready\n");
usleep(200000);
} else {
break;
}
}
}
if (i == 5) {
ALOGE("can't make kernel addr done\n");
return HDMI_RESULT_FAIL;
} else {
return HDMI_RESULT_NACK;
}
}
}
cecframe.msg[0] = (message->initiator << 4) | message->destination;
cecframe.len = message->length + 1;
cecframe.msg[1] = message->body[0];
ALOGI("send msg LEN:%d,opcode:%02x,addr:%02x\n",
cecframe.len ,cecframe.msg[1],cecframe.msg[0]);
if (cecframe.len > 16)
cecframe.len = 0;
for (ret = 0; ret < cecframe.len; ret++)
cecframe.msg[ret + 2] = message->body[ret + 1];
if (cecframe.msg[1] == 0x90)
cecframe.msg[2] = 0;
ret = ioctl(ctx->fd, CEC_TRANSMIT, &cecframe);
if (ret < 0) {
ALOGE("ioctl err:%d\n", ret);
return HDMI_RESULT_FAIL;
}
if (cecframe.tx_status & CEC_TX_STATUS_NACK) {
ALOGE("HDMI_RESULT_NACK\n");
return HDMI_RESULT_NACK;
}
else if (cecframe.tx_status & CEC_TX_STATUS_OK) {
ALOGE("HDMI_RESULT_SUCCESS\n");
return HDMI_RESULT_SUCCESS;
}
else {
ALOGE("HDMI_RESULT_BUSY\n");
return HDMI_RESULT_BUSY;
}
return HDMI_RESULT_FAIL;
}
static void hdmi_cec_register_event_callback(const struct hdmi_cec_device* dev,
event_callback_t callback, void* arg)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
ALOGI("%s", __func__);
ctx->event_callback = callback;
ctx->cec_arg = arg;
}
static void hdmi_cec_get_version(const struct hdmi_cec_device* dev, int* version)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
ALOGI("%s", __func__);
*version = HDMI_CEC_VERSION;
}
static void hdmi_cec_get_vendor_id(const struct hdmi_cec_device* dev, uint32_t* vendor_id)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
ALOGI("%s", __func__);
*vendor_id = HDMI_CEC_VENDOR_ID;
}
static void hdmi_cec_get_port_info(const struct hdmi_cec_device* dev,
struct hdmi_port_info* list[], int* total)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
int ret, val, support;
ret = 0;
val = 0;
support = 0;
ALOGI("%s", __func__);
if (ctx->fd > 0) {
ret = ioctl(ctx->fd, CEC_ADAP_G_PHYS_ADDR, &val);
if (!ret) {
ALOGE("%s get port phy addr %x\n", __func__, val);
if (val && (val != 0xffff))
support = 1;
}
} else {
ALOGE("%s open HDMI_DEV_PATH error", __FUNCTION__);
}
list[0] = &ctx->port;
list[0]->type = HDMI_OUTPUT;
list[0]->port_id = HDMI_CEC_PORT_ID;
list[0]->cec_supported = support;
list[0]->arc_supported = 0;
list[0]->physical_address = val;
*total = 1;
}
static void hdmi_cec_set_option(const struct hdmi_cec_device* dev, int flag, int value)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
int ret, fd;
if (ctx->fd < 0) {
ALOGE("%s open error", __func__);
return;
}
switch (flag) {
case HDMI_OPTION_WAKEUP:
ALOGI("%s: Wakeup: value: %d", __FUNCTION__, value);
break;
case HDMI_OPTION_ENABLE_CEC:
ALOGI("%s: Enable CEC: value: %d", __FUNCTION__, value);
ctx->enable = !!value;
break;
case HDMI_OPTION_SYSTEM_CEC_CONTROL:
ALOGI("%s: system_control: value: %d",
__FUNCTION__, value);
ctx->system_control = !!value;
break;
}
}
static void hdmi_cec_set_audio_return_channel(const struct hdmi_cec_device* dev, int port_id, int flag)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
ALOGI("%s %d", __func__, port_id);
}
static int hdmi_cec_device_close(struct hw_device_t *dev)
{
struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
if (ctx) {
close(ctx->fd);
free(ctx);
}
ctx->enable = false;
ctx->phy_addr = 0;
return 0;
}
static int hdmi_cec_device_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device)
{
if (strcmp(name, HDMI_CEC_HARDWARE_INTERFACE))
return -EINVAL;
struct hdmi_cec_context_t *dev;
dev = (hdmi_cec_context_t*)malloc(sizeof(*dev));
/* initialize our state here */
memset(dev, 0, sizeof(*dev));
dev->enable = true;
dev->system_control = false;
dev->cec_init = false;
/* initialize the procs */
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = HDMI_CEC_DEVICE_API_VERSION_1_0;
dev->device.common.module = const_cast<hw_module_t*>(module);
dev->device.common.close = hdmi_cec_device_close;
dev->device.add_logical_address = hdmi_cec_add_logical_address;
dev->device.clear_logical_address = hdmi_cec_clear_logical_address;
dev->device.get_physical_address = hdmi_cec_get_physical_address;
dev->device.send_message = hdmi_cec_send_message;
dev->device.register_event_callback = hdmi_cec_register_event_callback;
dev->device.get_version = hdmi_cec_get_version;
dev->device.get_vendor_id = hdmi_cec_get_vendor_id;
dev->device.get_port_info = hdmi_cec_get_port_info;
dev->device.set_option = hdmi_cec_set_option;
dev->device.set_audio_return_channel = hdmi_cec_set_audio_return_channel;
dev->device.is_connected = hdmi_cec_is_connected;
dev->phy_addr = 0;
dev->fd = open(HDMI_DEV_PATH,O_RDWR,0);
ALOGE(HDMI_DEV_PATH);
ALOGE("\n");
if (dev->fd < 0) {
ALOGE("%s open error!", __func__);
ALOGE("cec %s\n", strerror(errno));
}
ALOGI("%s dev->fd = %d", __func__, dev->fd);
property_set("vendor.sys.hdmicec.version",HDMI_CEC_HAL_VERSION);
*device = &dev->device.common;
init_uevent_thread(dev);
ALOGI("rockchip hdmi cec modules loaded");
return 0;
}