Home | History | Annotate | Download | only in hdmi_cec
      1 /*
      2 * Copyright (c) 2014, 2016, The Linux Foundation. All rights reserved.
      3 *
      4 * Redistribution and use in source and binary forms, with or without
      5 * modification, are permitted provided that the following conditions are
      6 * met:
      7 *    * Redistributions of source code must retain the above copyright
      8 *      notice, this list of conditions and the following disclaimer.
      9 *    * Redistributions in binary form must reproduce the above
     10 *      copyright notice, this list of conditions and the following
     11 *      disclaimer in the documentation and/or other materials provided
     12 *      with the distribution.
     13 *    * Neither the name of The Linux Foundation. nor the names of its
     14 *      contributors may be used to endorse or promote products derived
     15 *      from this software without specific prior written permission.
     16 *
     17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
     18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
     20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
     21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
     24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
     27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 */
     29 
     30 #define DEBUG 0
     31 #define ATRACE_TAG (ATRACE_TAG_GRAPHICS | ATRACE_TAG_HAL)
     32 #include <cstdlib>
     33 #include <cutils/log.h>
     34 #include <errno.h>
     35 #include <fcntl.h>
     36 #include <hardware/hdmi_cec.h>
     37 #include <utils/Trace.h>
     38 #include "qhdmi_cec.h"
     39 #include "QHDMIClient.h"
     40 
     41 namespace qhdmicec {
     42 
     43 const int NUM_HDMI_PORTS = 1;
     44 const int MAX_SYSFS_DATA = 128;
     45 const int MAX_CEC_FRAME_SIZE = 20;
     46 const int MAX_SEND_MESSAGE_RETRIES = 1;
     47 
     48 enum {
     49     LOGICAL_ADDRESS_SET   =  1,
     50     LOGICAL_ADDRESS_UNSET = -1,
     51 };
     52 
     53 // Offsets of members of struct hdmi_cec_msg
     54 // drivers/video/msm/mdss/mdss_hdmi_cec.c
     55 // XXX: Get this from a driver header
     56 enum {
     57     CEC_OFFSET_SENDER_ID,
     58     CEC_OFFSET_RECEIVER_ID,
     59     CEC_OFFSET_OPCODE,
     60     CEC_OFFSET_OPERAND,
     61     CEC_OFFSET_FRAME_LENGTH = 17,
     62     CEC_OFFSET_RETRANSMIT,
     63 };
     64 
     65 //Forward declarations
     66 static void cec_close_context(cec_context_t* ctx __unused);
     67 static int cec_enable(cec_context_t *ctx, int enable);
     68 static int cec_is_connected(const struct hdmi_cec_device* dev, int port_id);
     69 
     70 static ssize_t read_node(const char *path, char *data)
     71 {
     72     ssize_t err = 0;
     73     FILE *fp = NULL;
     74     err = access(path, R_OK);
     75     if (!err) {
     76         fp = fopen(path, "r");
     77         if (fp) {
     78             err = fread(data, sizeof(char), MAX_SYSFS_DATA ,fp);
     79             fclose(fp);
     80         }
     81     }
     82     return err;
     83 }
     84 
     85 static ssize_t write_node(const char *path, const char *data, size_t len)
     86 {
     87     ssize_t err = 0;
     88     int fd = -1;
     89     err = access(path, W_OK);
     90     if (!err) {
     91         fd = open(path, O_WRONLY);
     92         errno = 0;
     93         err = write(fd, data, len);
     94         if (err < 0) {
     95             err = -errno;
     96         }
     97         close(fd);
     98     } else {
     99         ALOGE("%s: Failed to access path: %s error: %s",
    100                 __FUNCTION__, path, strerror(errno));
    101         err = -errno;
    102     }
    103     return err;
    104 }
    105 
    106 // Helper function to write integer values to the full sysfs path
    107 static ssize_t write_int_to_node(cec_context_t *ctx,
    108         const char *path_postfix,
    109         const int value)
    110 {
    111     char sysfs_full_path[MAX_PATH_LENGTH];
    112     char sysfs_data[MAX_SYSFS_DATA];
    113     snprintf(sysfs_data, sizeof(sysfs_data), "%d",value);
    114     snprintf(sysfs_full_path,sizeof(sysfs_full_path), "%s/%s",
    115             ctx->fb_sysfs_path, path_postfix);
    116     ssize_t err = write_node(sysfs_full_path, sysfs_data, strlen(sysfs_data));
    117     return err;
    118 }
    119 
    120 static void hex_to_string(const char *msg, ssize_t len, char *str)
    121 {
    122     //Functions assumes sufficient memory in str
    123     char *ptr = str;
    124     for(int i=0; i < len ; i++) {
    125         ptr += snprintf(ptr, 3,  "%02X", msg[i]);
    126         // Overwrite null termination of snprintf in all except the last byte
    127         if (i < len - 1)
    128             *ptr = ':';
    129         ptr++;
    130     }
    131 }
    132 
    133 static ssize_t cec_get_fb_node_number(cec_context_t *ctx)
    134 {
    135     //XXX: Do this from a common utility library across the display HALs
    136     const int MAX_FB_DEVICES = 2;
    137     ssize_t len = 0;
    138     char fb_type_path[MAX_PATH_LENGTH];
    139     char fb_type[MAX_SYSFS_DATA];
    140     const char *dtv_panel_str = "dtv panel";
    141 
    142     for(int num = 0; num < MAX_FB_DEVICES; num++) {
    143         snprintf(fb_type_path, sizeof(fb_type_path),"%s%d/msm_fb_type",
    144                 SYSFS_BASE,num);
    145         ALOGD_IF(DEBUG, "%s: num: %d fb_type_path: %s", __FUNCTION__, num, fb_type_path);
    146         len = read_node(fb_type_path, fb_type);
    147         ALOGD_IF(DEBUG, "%s: fb_type:%s", __FUNCTION__, fb_type);
    148         if(len > 0 && (strncmp(fb_type, dtv_panel_str, strlen(dtv_panel_str)) == 0)){
    149             ALOGD_IF(DEBUG, "%s: Found DTV panel at fb%d", __FUNCTION__, num);
    150             ctx->fb_num = num;
    151             snprintf(ctx->fb_sysfs_path, sizeof(ctx->fb_sysfs_path),
    152                     "%s%d", SYSFS_BASE, num);
    153             break;
    154         }
    155     }
    156     if (len < 0)
    157         return len;
    158     else
    159         return 0;
    160 }
    161 
    162 static int cec_add_logical_address(const struct hdmi_cec_device* dev,
    163         cec_logical_address_t addr)
    164 {
    165     if (addr <  CEC_ADDR_TV || addr > CEC_ADDR_BROADCAST) {
    166         ALOGE("%s: Received invalid address: %d ", __FUNCTION__, addr);
    167         return -EINVAL;
    168     }
    169     cec_context_t* ctx = (cec_context_t*)(dev);
    170     ctx->logical_address[addr] = LOGICAL_ADDRESS_SET;
    171 
    172     //XXX: We can get multiple logical addresses here but we can only send one
    173     //to the driver. Store locally for now
    174     ssize_t err = write_int_to_node(ctx, "cec/logical_addr", addr);
    175     ALOGI("%s: Allocated logical address: %d ", __FUNCTION__, addr);
    176     return (int) err;
    177 }
    178 
    179 static void cec_clear_logical_address(const struct hdmi_cec_device* dev)
    180 {
    181     cec_context_t* ctx = (cec_context_t*)(dev);
    182     memset(ctx->logical_address, LOGICAL_ADDRESS_UNSET,
    183             sizeof(ctx->logical_address));
    184     //XXX: Find logical_addr that needs to be reset
    185     write_int_to_node(ctx, "cec/logical_addr", 15);
    186     ALOGD_IF(DEBUG, "%s: Cleared logical addresses", __FUNCTION__);
    187 }
    188 
    189 static int cec_get_physical_address(const struct hdmi_cec_device* dev,
    190         uint16_t* addr)
    191 {
    192     cec_context_t* ctx = (cec_context_t*)(dev);
    193     char pa_path[MAX_PATH_LENGTH];
    194     char pa_data[MAX_SYSFS_DATA];
    195     snprintf (pa_path, sizeof(pa_path),"%s/pa",
    196             ctx->fb_sysfs_path);
    197     int err = (int) read_node(pa_path, pa_data);
    198     *addr = (uint16_t) atoi(pa_data);
    199     ALOGD_IF(DEBUG, "%s: Physical Address: 0x%x", __FUNCTION__, *addr);
    200     if (err < 0)
    201         return err;
    202     else
    203         return 0;
    204 }
    205 
    206 static int cec_send_message(const struct hdmi_cec_device* dev,
    207         const cec_message_t* msg)
    208 {
    209     ATRACE_CALL();
    210     if(cec_is_connected(dev, 0) <= 0)
    211         return HDMI_RESULT_FAIL;
    212 
    213     cec_context_t* ctx = (cec_context_t*)(dev);
    214     ALOGD_IF(DEBUG, "%s: initiator: %d destination: %d length: %u",
    215             __FUNCTION__, msg->initiator, msg->destination,
    216             (uint32_t) msg->length);
    217 
    218     // Dump message received from framework
    219     char dump[128];
    220     if(msg->length > 0) {
    221         hex_to_string((char*)msg->body, msg->length, dump);
    222         ALOGD_IF(DEBUG, "%s: message from framework: %s", __FUNCTION__, dump);
    223     }
    224 
    225     char write_msg_path[MAX_PATH_LENGTH];
    226     char write_msg[MAX_CEC_FRAME_SIZE];
    227     memset(write_msg, 0, sizeof(write_msg));
    228     // See definition of struct hdmi_cec_msg in driver code
    229     // drivers/video/msm/mdss/mdss_hdmi_cec.c
    230     // Write header block
    231     // XXX: Include this from header in kernel
    232     write_msg[CEC_OFFSET_SENDER_ID] = msg->initiator;
    233     write_msg[CEC_OFFSET_RECEIVER_ID] = msg->destination;
    234     //Kernel splits opcode/operand, but Android sends it in one byte array
    235     write_msg[CEC_OFFSET_OPCODE] = msg->body[0];
    236     if(msg->length > 1) {
    237         memcpy(&write_msg[CEC_OFFSET_OPERAND], &msg->body[1],
    238                 sizeof(char)*(msg->length - 1));
    239     }
    240     //msg length + initiator + destination
    241     write_msg[CEC_OFFSET_FRAME_LENGTH] = (unsigned char) (msg->length + 1);
    242     hex_to_string(write_msg, sizeof(write_msg), dump);
    243     ALOGD_IF(DEBUG, "%s: message to driver: %s", __FUNCTION__, dump);
    244     snprintf(write_msg_path, sizeof(write_msg_path), "%s/cec/wr_msg",
    245             ctx->fb_sysfs_path);
    246     int retry_count = 0;
    247     ssize_t err = 0;
    248     //HAL spec requires us to retry at least once.
    249     while (true) {
    250         err = write_node(write_msg_path, write_msg, sizeof(write_msg));
    251         retry_count++;
    252         if (err == -EAGAIN && retry_count <= MAX_SEND_MESSAGE_RETRIES) {
    253             ALOGE("%s: CEC line busy, retrying", __FUNCTION__);
    254         } else {
    255             break;
    256         }
    257     }
    258 
    259     if (err < 0) {
    260        if (err == -ENXIO) {
    261            ALOGI("%s: No device exists with the destination address",
    262                    __FUNCTION__);
    263            return HDMI_RESULT_NACK;
    264        } else if (err == -EAGAIN) {
    265             ALOGE("%s: CEC line is busy, max retry count exceeded",
    266                     __FUNCTION__);
    267             return HDMI_RESULT_BUSY;
    268         } else {
    269             return HDMI_RESULT_FAIL;
    270             ALOGE("%s: Failed to send CEC message err: %zd - %s",
    271                     __FUNCTION__, err, strerror(int(-err)));
    272         }
    273     } else {
    274         ALOGD_IF(DEBUG, "%s: Sent CEC message - %zd bytes written",
    275                 __FUNCTION__, err);
    276         return HDMI_RESULT_SUCCESS;
    277     }
    278 }
    279 
    280 void cec_receive_message(cec_context_t *ctx, char *msg, ssize_t len)
    281 {
    282     if(!ctx->system_control)
    283         return;
    284 
    285     char dump[128];
    286     if(len > 0) {
    287         hex_to_string(msg, len, dump);
    288         ALOGD_IF(DEBUG, "%s: Message from driver: %s", __FUNCTION__, dump);
    289     }
    290 
    291     hdmi_event_t event;
    292     event.type = HDMI_EVENT_CEC_MESSAGE;
    293     event.dev = (hdmi_cec_device *) ctx;
    294     // Remove initiator/destination from this calculation
    295     event.cec.length = msg[CEC_OFFSET_FRAME_LENGTH] - 1;
    296     event.cec.initiator = (cec_logical_address_t) msg[CEC_OFFSET_SENDER_ID];
    297     event.cec.destination = (cec_logical_address_t) msg[CEC_OFFSET_RECEIVER_ID];
    298     //Copy opcode and operand
    299     memcpy(event.cec.body, &msg[CEC_OFFSET_OPCODE], event.cec.length);
    300     hex_to_string((char *) event.cec.body, event.cec.length, dump);
    301     ALOGD_IF(DEBUG, "%s: Message to framework: %s", __FUNCTION__, dump);
    302     ctx->callback.callback_func(&event, ctx->callback.callback_arg);
    303 }
    304 
    305 void cec_hdmi_hotplug(cec_context_t *ctx, int connected)
    306 {
    307     //Ignore unplug events when system control is disabled
    308     if(!ctx->system_control && connected == 0)
    309         return;
    310     hdmi_event_t event;
    311     event.type = HDMI_EVENT_HOT_PLUG;
    312     event.dev = (hdmi_cec_device *) ctx;
    313     event.hotplug.connected = connected ? HDMI_CONNECTED : HDMI_NOT_CONNECTED;
    314     ctx->callback.callback_func(&event, ctx->callback.callback_arg);
    315 }
    316 
    317 static void cec_register_event_callback(const struct hdmi_cec_device* dev,
    318             event_callback_t callback, void* arg)
    319 {
    320     ALOGD_IF(DEBUG, "%s: Registering callback", __FUNCTION__);
    321     cec_context_t* ctx = (cec_context_t*)(dev);
    322     ctx->callback.callback_func = callback;
    323     ctx->callback.callback_arg = arg;
    324 }
    325 
    326 static void cec_get_version(const struct hdmi_cec_device* dev, int* version)
    327 {
    328     cec_context_t* ctx = (cec_context_t*)(dev);
    329     *version = ctx->version;
    330     ALOGD_IF(DEBUG, "%s: version: %d", __FUNCTION__, *version);
    331 }
    332 
    333 static void cec_get_vendor_id(const struct hdmi_cec_device* dev,
    334         uint32_t* vendor_id)
    335 {
    336     cec_context_t* ctx = (cec_context_t*)(dev);
    337     *vendor_id = ctx->vendor_id;
    338     ALOGD_IF(DEBUG, "%s: vendor id: %u", __FUNCTION__, *vendor_id);
    339 }
    340 
    341 static void cec_get_port_info(const struct hdmi_cec_device* dev,
    342             struct hdmi_port_info* list[], int* total)
    343 {
    344     ALOGD_IF(DEBUG, "%s: Get port info", __FUNCTION__);
    345     cec_context_t* ctx = (cec_context_t*)(dev);
    346     *total = NUM_HDMI_PORTS;
    347     *list = ctx->port_info;
    348 }
    349 
    350 static void cec_set_option(const struct hdmi_cec_device* dev, int flag,
    351         int value)
    352 {
    353     cec_context_t* ctx = (cec_context_t*)(dev);
    354     switch (flag) {
    355         case HDMI_OPTION_WAKEUP:
    356             ALOGD_IF(DEBUG, "%s: Wakeup: value: %d", __FUNCTION__, value);
    357             //XXX
    358             break;
    359         case HDMI_OPTION_ENABLE_CEC:
    360             ALOGD_IF(DEBUG, "%s: Enable CEC: value: %d", __FUNCTION__, value);
    361             cec_enable(ctx, value? 1 : 0);
    362             break;
    363         case HDMI_OPTION_SYSTEM_CEC_CONTROL:
    364             ALOGD_IF(DEBUG, "%s: system_control: value: %d",
    365                     __FUNCTION__, value);
    366             ctx->system_control = !!value;
    367             break;
    368     }
    369 }
    370 
    371 static void cec_set_audio_return_channel(const struct hdmi_cec_device* dev,
    372         int port, int flag)
    373 {
    374     cec_context_t* ctx = (cec_context_t*)(dev);
    375     ctx->arc_enabled = flag ? true : false;
    376     ALOGD_IF(DEBUG, "%s: ARC flag: %d port: %d", __FUNCTION__, flag, port);
    377 }
    378 
    379 static int cec_is_connected(const struct hdmi_cec_device* dev, int port_id)
    380 {
    381     // Ignore port_id since we have only one port
    382     int connected = 0;
    383     cec_context_t* ctx = (cec_context_t*)(dev);
    384     char connected_path[MAX_PATH_LENGTH];
    385     char connected_data[MAX_SYSFS_DATA];
    386     snprintf (connected_path, sizeof(connected_path),"%s/connected",
    387             ctx->fb_sysfs_path);
    388     ssize_t err = read_node(connected_path, connected_data);
    389     connected = atoi(connected_data);
    390 
    391     ALOGD_IF(DEBUG, "%s: HDMI at port %d is - %s", __FUNCTION__, port_id,
    392             connected ? "connected":"disconnected");
    393     if (err < 0)
    394         return (int) err;
    395     else
    396         return connected;
    397 }
    398 
    399 static int cec_device_close(struct hw_device_t *dev)
    400 {
    401     ALOGD_IF(DEBUG, "%s: Close CEC HAL ", __FUNCTION__);
    402     if (!dev) {
    403         ALOGE("%s: NULL device pointer", __FUNCTION__);
    404         return -EINVAL;
    405     }
    406     cec_context_t* ctx = (cec_context_t*)(dev);
    407     cec_close_context(ctx);
    408     free(dev);
    409     return 0;
    410 }
    411 
    412 static int cec_enable(cec_context_t *ctx, int enable)
    413 {
    414     ssize_t err;
    415     // Enable CEC
    416     int value = enable ? 0x3 : 0x0;
    417     err = write_int_to_node(ctx, "cec/enable", value);
    418     if(err < 0) {
    419         ALOGE("%s: Failed to toggle CEC: enable: %d",
    420                 __FUNCTION__, enable);
    421         return (int) err;
    422     }
    423     ctx->enabled = enable;
    424     return 0;
    425 }
    426 
    427 static void cec_init_context(cec_context_t *ctx)
    428 {
    429     ALOGD_IF(DEBUG, "%s: Initializing context", __FUNCTION__);
    430     cec_get_fb_node_number(ctx);
    431 
    432     //Initialize ports - We support only one output port
    433     ctx->port_info = new hdmi_port_info[NUM_HDMI_PORTS];
    434     ctx->port_info[0].type = HDMI_OUTPUT;
    435     ctx->port_info[0].port_id = 1;
    436     ctx->port_info[0].cec_supported = 1;
    437     //XXX: Enable ARC if supported
    438     ctx->port_info[0].arc_supported = 0;
    439     cec_get_physical_address((hdmi_cec_device *) ctx,
    440             &ctx->port_info[0].physical_address );
    441 
    442     ctx->version = 0x4;
    443     ctx->vendor_id = 0xA47733;
    444     cec_clear_logical_address((hdmi_cec_device_t*)ctx);
    445 
    446     //Set up listener for HDMI events
    447     ctx->disp_client = new qClient::QHDMIClient();
    448     ctx->disp_client->setCECContext(ctx);
    449     ctx->disp_client->registerClient(ctx->disp_client);
    450 
    451     //Enable CEC - framework expects it to be enabled by default
    452     cec_enable(ctx, true);
    453 
    454     ALOGD("%s: CEC enabled", __FUNCTION__);
    455 }
    456 
    457 static void cec_close_context(cec_context_t* ctx __unused)
    458 {
    459     ALOGD("%s: Closing context", __FUNCTION__);
    460 }
    461 
    462 static int cec_device_open(const struct hw_module_t* module,
    463         const char* name,
    464         struct hw_device_t** device)
    465 {
    466     ALOGD_IF(DEBUG, "%s: name: %s", __FUNCTION__, name);
    467     int status = -EINVAL;
    468     if (!strcmp(name, HDMI_CEC_HARDWARE_INTERFACE )) {
    469         struct cec_context_t *dev;
    470         dev = (cec_context_t *) calloc (1, sizeof(*dev));
    471         if (dev) {
    472             cec_init_context(dev);
    473 
    474             //Setup CEC methods
    475             dev->device.common.tag       = HARDWARE_DEVICE_TAG;
    476             dev->device.common.version   = HDMI_CEC_DEVICE_API_VERSION_1_0;
    477             dev->device.common.module    = const_cast<hw_module_t* >(module);
    478             dev->device.common.close     = cec_device_close;
    479             dev->device.add_logical_address = cec_add_logical_address;
    480             dev->device.clear_logical_address = cec_clear_logical_address;
    481             dev->device.get_physical_address = cec_get_physical_address;
    482             dev->device.send_message = cec_send_message;
    483             dev->device.register_event_callback = cec_register_event_callback;
    484             dev->device.get_version = cec_get_version;
    485             dev->device.get_vendor_id = cec_get_vendor_id;
    486             dev->device.get_port_info = cec_get_port_info;
    487             dev->device.set_option = cec_set_option;
    488             dev->device.set_audio_return_channel = cec_set_audio_return_channel;
    489             dev->device.is_connected = cec_is_connected;
    490 
    491             *device = &dev->device.common;
    492             status = 0;
    493         } else {
    494             status = -EINVAL;
    495         }
    496     }
    497     return status;
    498 }
    499 }; //namespace qhdmicec
    500 
    501 // Standard HAL module, should be outside qhdmicec namespace
    502 static struct hw_module_methods_t cec_module_methods = {
    503         .open = qhdmicec::cec_device_open
    504 };
    505 
    506 hdmi_module_t HAL_MODULE_INFO_SYM = {
    507     .common = {
    508         .tag = HARDWARE_MODULE_TAG,
    509         .version_major = 1,
    510         .version_minor = 0,
    511         .id = HDMI_CEC_HARDWARE_MODULE_ID,
    512         .name = "QTI HDMI CEC module",
    513         .author = "The Linux Foundation",
    514         .methods = &cec_module_methods,
    515     }
    516 };
    517 
    518 
    519