/* * 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 #include #include #include #include #include #include #include #include #include #include #include "rk_hdmi_cec.h" static int logicaddr_to_type(cec_logical_address_t addr) { int type; ALOGV("%s", __func__); 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; ALOGV("%s", __func__); 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, retry_num = 10; int mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU; struct cec_log_addrs log_addr; ALOGD("%s, logic address:%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; } ALOGV("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]) { ALOGV("LA is existing, not need to set logic addr\n"); return 0; } la_retry: 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; ALOGD("%s, CEC_ADAP_S_LOG_ADDRS addr:%02x,log_addr.log_addr[0]:%02x,retry_num:%d\n", __func__, addr, log_addr.log_addr[0], retry_num); ret = ioctl(ctx->fd, CEC_ADAP_S_LOG_ADDRS, &log_addr); if (ret) { ALOGE("%s set logic address ioctl err ret:%d %s\n", __func__, ret, strerror(errno)); if ((errno == -EBUSY) && retry_num) { usleep(10000); retry_num--; goto la_retry; } return -EBUSY; } else if (log_addr.log_addr[0] == 0xff) { ALOGE("%s set logic address ioctl ret:%d LA:%02x\n", __func__, ret, log_addr.log_addr[0]); if (retry_num) { usleep(10000); retry_num--; goto la_retry; } ALOGE("%s set logic address claim err la:0xff\n", __func__); return -EINVAL; } ALOGI("%s claim LA success,log_addr.log_addr[0]:%02x\n", __func__, log_addr.log_addr[0]); return 0; } int hdmi_cec_add_logical_address(struct hdmi_cec_context_t* ctx ,cec_logical_address_t addr) { ALOGV("%s", __func__); return set_kernel_logical_address(ctx, addr); } void hdmi_cec_clear_logical_address(struct hdmi_cec_context_t* ctx) { int ret; struct cec_log_addrs log_addr; ALOGV("%s", __func__); if (ctx->fd < 0) { ALOGE("%s open error!", __func__); return; } if (!ctx->cec_init) { ALOGV("%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; } } int hdmi_cec_get_physical_address(struct hdmi_cec_context_t* ctx, uint16_t* addr) { int i = 0; uint16_t val = 0; ALOGV("%s", __func__); 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++) { int 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) { break; } usleep(20000); } if (i == 5) { ALOGE("get phy addr err!:%x\n", val); return -EINVAL; } *addr = val; ALOGV("%s val = %x", __func__, val); return 0; } int hdmi_cec_is_connected(struct hdmi_cec_context_t* ctx, int port_id) { (void)port_id; ALOGV("%s", __func__); if (ctx->hotplug) return HDMI_CONNECTED; else return HDMI_NOT_CONNECTED; } int hdmi_cec_send_message(struct hdmi_cec_context_t* ctx, const cec_message_t* message) { struct cec_msg cecframe; int i = 0; int ret = 0; if (!ctx->enable) { ALOGE("%s cec disabled\n", __func__); return -EPERM; } if (ctx->fd < 0) { ALOGE("%s open error", __func__); return -ENOENT; } #ifdef KER_CONFIG_HDMI_CEC if (!ctx->hotplug) { return -EPERM; } #endif 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]) { ALOGV("kernel logaddr is existing\n"); if (log_addr.log_addr[0] == message->initiator) { ALOGV("kernel logaddr is preferred logaddr\n"); return HDMI_RESULT_NACK; } else { ALOGV("preferred log addr is not kernel log addr\n"); return HDMI_RESULT_SUCCESS; } } else { ALOGV("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]; ALOGV("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]; i = 10; retry: ret = ioctl(ctx->fd, CEC_TRANSMIT, &cecframe); if (ret < 0) { ALOGE("ioctl err:%d %s\n", ret, strerror(errno)); return HDMI_RESULT_FAIL; } if (cecframe.tx_status & CEC_TX_STATUS_NACK) { ALOGW("HDMI_RESULT_NACK\n"); return HDMI_RESULT_NACK; } else if (cecframe.tx_status & CEC_TX_STATUS_OK) { ALOGD("HDMI_RESULT_SUCCESS\n"); #ifndef KER_CONFIG_HDMI_CEC ctx->hotplug = true; #endif return HDMI_RESULT_SUCCESS; } else if (cecframe.tx_status & CEC_TX_STATUS_ERROR) { ALOGW("HDMI_RESULT_BUSY\n"); if (i) { i--; usleep(10000); goto retry; } return HDMI_RESULT_BUSY; } return HDMI_RESULT_FAIL; } void hdmi_cec_register_event_callback(struct hdmi_cec_context_t* ctx, event_callback_t callback, void* arg) { ALOGV("%s", __func__); ctx->event_callback = callback; ctx->cec_arg = arg; } void hdmi_cec_get_version(struct hdmi_cec_context_t* ctx, int* version) { // struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev; (void)ctx; ALOGV("%s", __func__); *version = HDMI_CEC_VERSION; } void hdmi_cec_get_vendor_id(struct hdmi_cec_context_t* ctx, uint32_t* vendor_id) { // struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev; (void)ctx; ALOGV("%s", __func__); *vendor_id = HDMI_CEC_VENDOR_ID; } static int set_kernel_cec_standy(struct hdmi_cec_context_t* ctx, bool enable) { int ret = 0; int fd = 0; ALOGV("%s", __func__); fd = open(HDMI_WAKE_PATH, O_RDWR); if (fd < 0) { ALOGW("%s:%s.", __func__, strerror(errno)); return errno; } ret = ioctl(fd, CEC_STANDBY, &enable); if (ret) { ALOGE("%s set kernel cec standby err %s\n", __func__, strerror(errno)); close(fd); return ret; } close(fd); return 0; } #define CEC_ENABLE 0 #define CEC_WAKE 1 static int set_kernel_cec_wake_enable(struct hdmi_cec_context_t* ctx, int mask, bool enable) { int ret, fd; ALOGV("%s", __func__); fd = open(HDMI_WAKE_PATH, O_RDWR); if (fd < 0) { ALOGW("%s:%s.", __func__, strerror(errno)); return errno; } if (enable) ctx->en_mask |= (enable << mask); else ctx->en_mask &= ~(1 << mask); ALOGV("mask:%d, enable:%d, en_mask:%d\n", mask, enable, ctx->en_mask); ret = ioctl(fd, CEC_FUNC_EN, &ctx->en_mask); if (ret) { ALOGE("%s set kernel cec enable err ret:%d %s\n", __func__, ret, strerror(errno)); close(fd); return ret; } close(fd); return 0; } void hdmi_cec_set_option(struct hdmi_cec_context_t* ctx, int flag, int value) { int ret =0; ALOGV("%s", __func__); if (ctx->fd < 0) { ALOGE("%s open error", __func__); return; } switch (flag) { case HDMI_OPTION_WAKEUP: ALOGV("%s: Wakeup: value: %d", __FUNCTION__, value); set_kernel_cec_wake_enable(ctx, CEC_WAKE, !!value); break; case HDMI_OPTION_ENABLE_CEC: ALOGV("%s: Enable CEC: value: %d", __FUNCTION__, value); ctx->enable = !!value; set_kernel_cec_wake_enable(ctx, CEC_ENABLE, value); break; case HDMI_OPTION_SYSTEM_CEC_CONTROL: ALOGV("%s: system_control: value: %d", __FUNCTION__, value); ctx->system_control = !!value; set_kernel_cec_standy(ctx, ctx->system_control); break; } } void hdmi_cec_set_audio_return_channel(struct hdmi_cec_context_t* ctx, int port_id, int flag) { //struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev; (void)ctx;(void)flag; ALOGV("%s %d", __func__, port_id); } int rk_hdmi_cec_destroy(struct hdmi_cec_context_t *ctx) { ALOGI("rk_hdmi_cec_destroy."); if (ctx) { ctx->enable = false; ctx->phy_addr = 0; close(ctx->fd); //free(ctx); } return 0; } int rk_hdmi_cec_init(struct hdmi_cec_context_t *dev) { if(NULL == dev){ ALOGE("%s rk_hdmi_cec_init error!", __func__); return 0; } /* initialize our state here */ memset(dev, 0, sizeof(struct hdmi_cec_context_t)); dev->enable = true; dev->system_control = false; dev->cec_init = false; dev->phy_addr = 0; dev->en_mask = CEC_WAKE | CEC_ENABLE; dev->fd = open(HDMI_DEV_PATH,O_RDWR | O_CLOEXEC,0); if (dev->fd < 0) { ALOGE("%s failed to open %s, %d", __func__, HDMI_DEV_PATH, errno); } else { ALOGI("%s dev->fd = %d", __func__, dev->fd); } property_set("vendor.sys.hdmicec.version",HDMI_CEC_HAL_VERSION); init_uevent_thread(dev); ALOGI("rockchip hdmi cec modules loaded AIDL implementation library."); return 0; }