summaryrefslogtreecommitdiff
path: root/hdmi_cec.c (plain)
blob: e729b0c1a39e56deded8b853a9e250e9ea31f3fc
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16/*
17 * Amlogic HDMITX CEC HAL
18 * Copyright (C) 2014
19 *
20 * This implements a hdmi cec hardware library for the Android emulator.
21 * the following code should be built as a shared library that will be
22 * placed into /system/lib/hw/hdmi_cec.so
23 *
24 * It will be loaded by the code in hardware/libhardware/hardware.c
25 * which is itself called from
26 * frameworks/base/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
27 */
28
29#include <cutils/log.h>
30#include <stdint.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34#include <errno.h>
35#include <fcntl.h>
36#include <pthread.h>
37#include <sys/ioctl.h>
38#include <sys/types.h>
39#include <hardware/hdmi_cec.h>
40#include <hardware/hardware.h>
41#include <cutils/properties.h>
42#include "hdmi_cec.h"
43
44#ifdef LOG_TAG
45#undef LOG_TAG
46#define LOG_TAG "CEC"
47#else
48#define LOG_TAG "CEC"
49#endif
50
51/* Set to 1 to enable debug messages to the log */
52#define DEBUG 1
53#if DEBUG
54# define D(format, args...) ALOGD("[%s]"format, __func__, ##args)
55#else
56# define D(...) do{}while(0)
57#endif
58
59#define E(format, args...) ALOGE("[%s]"format, __func__, ##args)
60
61#define CEC_FILE "/dev/cec"
62#define MAX_PORT 32
63
64/*
65 * structures for platform cec implement
66 * @device_type : indentify type of cec device, such as tv or mbox
67 * @run : run flag for rx poll thread
68 * @exit : if rx poll thread is exited
69 * @addr_bitmap : bit maps for each valid logical address
70 * @fd : file descriptor for global read/write
71 * @ThreadId : pthread for poll cec rx message
72 * @cb_data : data pointer for cec message RX call back
73 * @cb : event call back for cec message RX
74 * @dev : for hdmi_cec_device type
75 * @port_data : array for port data
76 */
77struct aml_cec_hal {
78 int device_type;
79 int run;
80 int exited;
81 int addr_bitmap;
82 int fd;
83 int total_port;
84 unsigned int con_status;
85 pthread_t ThreadId;
86 void *cb_data;
87 event_callback_t cb;
88 struct hdmi_cec_device *dev;
89 struct hdmi_port_info *port_data;
90};
91
92struct aml_cec_hal *hal_info = NULL;
93
94static int cec_rx_read_msg(unsigned char *buf, int msg_cnt)
95{
96 int i;
97 char *path = CEC_FILE;
98
99 if (msg_cnt <= 0 || !buf) {
100 return 0;
101 }
102 /* maybe blocked at driver */
103 i = read(hal_info->fd, buf, msg_cnt);
104 if (i < 0) {
105 E("read :%s failed, ret:%d\n", path, i);
106 return -1;
107 }
108 return i;
109}
110
111static void check_connect_status(struct aml_cec_hal *hal)
112{
113 unsigned int prev_status, bit;
114 int i, port, ret;
115 hdmi_event_t event;
116
117 prev_status = hal->con_status;
118 for (i = 0; i < hal->total_port; i++) {
119 port = hal->port_data[i].port_id;
120 ret = ioctl(hal_info->fd, CEC_IOC_GET_CONNECT_STATUS, &port);
121 if (ret) {
122 D("get port %d connected status failed, ret:%d\n", hal->port_data[i].port_id, ret);
123 continue;
124 }
125 bit = prev_status & (1 << i);
126 if (bit ^ ((port ? 1 : 0) << i)) {
127 D("port:%d, connect status changed, now:%d, prev_status:%x\n",
128 hal->port_data[i].port_id, port, prev_status);
129 event.type = HDMI_EVENT_HOT_PLUG;
130 event.dev = hal->dev;
131 event.hotplug.connected = port;
132 event.hotplug.port_id = hal->port_data[i].port_id;
133 if (hal->cb) {
134 hal->cb(&event, hal_info->cb_data);
135 }
136 prev_status &= ~(bit);
137 prev_status |= ((port ? 1 : 0) << i);
138 D("now status:%x\n", prev_status);
139 }
140 }
141 hal->con_status = prev_status;
142}
143
144static void *cec_rx_loop(void *data)
145{
146 struct aml_cec_hal *hal = (struct aml_cec_hal *)data;
147 hdmi_event_t event;
148 unsigned char msg_buf[CEC_MESSAGE_BODY_MAX_LENGTH];
149 int r;
150#if DEBUG
151 char buf[64] = {};
152 int size = 0, i;
153#endif
154
155 D("start\n");
156 while (hal_info->fd < 0) {
157 usleep(1000 * 1000);
158 hal_info->fd = open(CEC_FILE, O_RDWR);
159 }
160 D("file open ok\n");
161 while (hal && hal->run) {
162 check_connect_status(hal);
163 memset(&event, 0, sizeof(event));
164 memset(msg_buf, 0, sizeof(msg_buf));
165
166 /* try to got a message from dev */
167 r = cec_rx_read_msg(msg_buf, CEC_MESSAGE_BODY_MAX_LENGTH);
168 if (r <= 1) { /* ignore received ping messages */
169 continue;
170 }
171 #if DEBUG
172 size = 0;
173 memset(buf, 0, sizeof(buf));
174 for (i = 0; i < r; i++) {
175 size += sprintf(buf + size, "%02x ", msg_buf[i]);
176 }
177 D("msg:%s", buf);
178 #endif
179 memcpy(event.cec.body, msg_buf + 1, r - 1);
180 event.type = HDMI_EVENT_CEC_MESSAGE;
181 event.dev = hal->dev;
182 event.cec.initiator = (msg_buf[0] >> 4) & 0xf;
183 event.cec.destination = (msg_buf[0] >> 0) & 0xf;
184 event.cec.length = r - 1;
185 if (hal->cb) {
186 hal->cb(&event, hal_info->cb_data);
187 }
188 }
189 D("end\n");
190 hal->exited = 1;
191 return 0;
192}
193
194/*
195 * (*add_logical_address)() passes the logical address that will be used
196 * in this system.
197 *
198 * HAL may use it to configure the hardware so that the CEC commands addressed
199 * the given logical address can be filtered in. This method can be called
200 * as many times as necessary in order to support multiple logical devices.
201 * addr should be in the range of valid logical addresses for the call
202 * to succeed.
203 *
204 * Returns 0 on success or -errno on error.
205 */
206static int cec_add_logical_address(const struct hdmi_cec_device* dev, cec_logical_address_t addr)
207{
208 if (!hal_info || hal_info->fd < 0)
209 return -EINVAL;
210 if (addr < CEC_ADDR_BROADCAST)
211 hal_info->addr_bitmap |= (1 << addr);
212 D("dev:%p, addr:%x, bitmap:%x\n", dev, addr, hal_info->addr_bitmap);
213 return ioctl(hal_info->fd, CEC_IOC_ADD_LOGICAL_ADDR, addr);
214}
215
216/*
217 * (*clear_logical_address)() tells HAL to reset all the logical addresses.
218 *
219 * It is used when the system doesn't need to process CEC command any more,
220 * hence to tell HAL to stop receiving commands from the CEC bus, and change
221 * the state back to the beginning.
222 */
223static void cec_clear_logical_address(const struct hdmi_cec_device* dev)
224{
225 if (!hal_info || hal_info->fd < 0)
226 return ;
227 hal_info->addr_bitmap = (1 << CEC_ADDR_BROADCAST);
228 D("dev:%p, bitmap:%x\n", dev, hal_info->addr_bitmap);
229 ioctl(hal_info->fd, CEC_IOC_CLR_LOGICAL_ADDR, 0);
230}
231
232/*
233 * (*get_physical_address)() returns the CEC physical address. The
234 * address is written to addr.
235 *
236 * The physical address depends on the topology of the network formed
237 * by connected HDMI devices. It is therefore likely to change if the cable
238 * is plugged off and on again. It is advised to call get_physical_address
239 * to get the updated address when hot plug event takes place.
240 *
241 * Returns 0 on success or -errno on error.
242 */
243static int cec_get_physical_address(const struct hdmi_cec_device* dev, uint16_t* addr)
244{
245 int ret;
246 if (!hal_info || hal_info->fd < 0)
247 return -EINVAL;
248 ret = ioctl(hal_info->fd, CEC_IOC_GET_PHYSICAL_ADDR, addr);
249 D("dev:%p, physical addr:%x, ret:%d\n", dev, *addr, ret);
250 return ret;
251}
252
253/*
254 * (*send_message)() transmits HDMI-CEC message to other HDMI device.
255 *
256 * The method should be designed to return in a certain amount of time not
257 * hanging forever, which can happen if CEC signal line is pulled low for
258 * some reason. HAL implementation should take the situation into account
259 * so as not to wait forever for the message to get sent out.
260 *
261 * It should try retransmission at least once as specified in the standard.
262 *
263 * Returns error code. See HDMI_RESULT_SUCCESS, HDMI_RESULT_NACK, and
264 * HDMI_RESULT_BUSY.
265 */
266static int cec_send_message(const struct hdmi_cec_device* dev, const cec_message_t* msg)
267{
268 int i, ret;
269 unsigned fail_reason = 0;
270 unsigned char msg_buf[CEC_MESSAGE_BODY_MAX_LENGTH] = {};
271#if DEBUG
272 char buf[64] = {};
273 int size = 0;
274#endif
275
276 if (!hal_info || hal_info->fd < 0)
277 return HDMI_RESULT_FAIL;
278
279#if DEBUG
280 memset(buf, 0, sizeof(buf));
281 for (i = 0; i < (int)msg->length; i++) {
282 size += sprintf(buf + size, "%02x ", msg->body[i]);
283 }
284 if (msg->length) {
285 D("[%x -> %x],len:%d, body:%s",
286 msg->initiator, msg->destination, msg->length, buf);
287 }
288#endif
289
290 memset(msg_buf, 0, sizeof(msg_buf));
291 msg_buf[0] = ((msg->initiator & 0xf) << 4) | (msg->destination & 0xf);
292 memcpy(msg_buf + 1, msg->body, msg->length);
293 ret = write(hal_info->fd, msg_buf, msg->length + 1);
294 if (ret > 0) {
295 return HDMI_RESULT_SUCCESS;
296 } else {
297 ioctl(hal_info->fd, CEC_IOC_GET_SEND_FAIL_REASON, &fail_reason);
298 if (msg->length)
299 D("fail reason:%x\n", fail_reason);
300 switch (fail_reason) {
301 case CEC_FAIL_NACK:
302 return HDMI_RESULT_NACK;
303 case CEC_FAIL_BUSY:
304 return HDMI_RESULT_BUSY;
305 default:
306 return HDMI_RESULT_FAIL;
307 }
308 }
309}
310
311/*
312 * (*register_event_callback)() registers a callback that HDMI-CEC HAL
313 * can later use for incoming CEC messages or internal HDMI events.
314 * When calling from C++, use the argument arg to pass the calling object.
315 * It will be passed back when the callback is invoked so that the context
316 * can be retrieved.
317 */
318static void cec_register_event_callback(const struct hdmi_cec_device* dev,
319 event_callback_t callback, void* arg)
320{
321 if (!hal_info || hal_info->fd < 0)
322 return ;
323 D("dev:%p, callback:%p, arg:%p\n", callback, arg, dev);
324 hal_info->cb = callback;
325 hal_info->cb_data = arg;
326}
327
328/*
329 * (*get_version)() returns the CEC version supported by underlying hardware.
330 */
331static void cec_get_version(const struct hdmi_cec_device* dev, int* version)
332{
333 if (!hal_info || hal_info->fd < 0)
334 return ;
335 ioctl(hal_info->fd, CEC_IOC_GET_VERSION, version);
336 D("dev:%p, version:%x\n", dev, *version);
337}
338
339/*
340 * (*get_vendor_id)() returns the identifier of the vendor. It is
341 * the 24-bit unique company ID obtained from the IEEE Registration
342 * Authority Committee (RAC).
343 */
344static void cec_get_vendor_id(const struct hdmi_cec_device* dev, uint32_t* vendor_id)
345{
346 if (!hal_info || hal_info->fd < 0)
347 return ;
348 ioctl(hal_info->fd, CEC_IOC_GET_VENDOR_ID, vendor_id);
349 D("dev:%p, vendor_id:%x\n", dev, *vendor_id);
350}
351
352/*
353 * (*get_port_info)() returns the hdmi port information of underlying hardware.
354 * info is the list of HDMI port information, and 'total' is the number of
355 * HDMI ports in the system.
356 */
357static void cec_get_port_info(const struct hdmi_cec_device* dev,
358 struct hdmi_port_info* list[], int* total)
359{
360 int i;
361
362 if (!hal_info || hal_info->fd < 0)
363 return ;
364
365 ioctl(hal_info->fd, CEC_IOC_GET_PORT_NUM, total);
366 D("dev:%p, total port:%d\n", dev, *total);
367 if (*total > MAX_PORT)
368 *total = MAX_PORT;
369 hal_info->total_port = *total;
370 hal_info->port_data = malloc(sizeof(struct hdmi_port_info) * (*total));
371 if (!hal_info->port_data) {
372 E("alloc port_data failed\n");
373 *total = 0;
374 return ;
375 }
376 ioctl(hal_info->fd, CEC_IOC_GET_PORT_INFO, hal_info->port_data);
377 for (i = 0; i < *total; i++) {
378 D("port %d, type:%s, id:%d, cec support:%d, arc support:%d, physical address:%x\n",
379 i, hal_info->port_data[i].type ? "output" : "input",
380 hal_info->port_data[i].port_id,
381 hal_info->port_data[i].cec_supported,
382 hal_info->port_data[i].arc_supported,
383 hal_info->port_data[i].physical_address);
384 }
385 *list = hal_info->port_data;
386}
387
388/*
389 * (*set_option)() passes flags controlling the way HDMI-CEC service works down
390 * to HAL implementation. Those flags will be used in case the feature needs
391 * update in HAL itself, firmware or microcontroller.
392 */
393static void cec_set_option(const struct hdmi_cec_device* dev, int flag, int value)
394{
395 int ret;
396
397 if (!hal_info || hal_info->fd < 0)
398 return ;
399 switch (flag) {
400 case HDMI_OPTION_ENABLE_CEC:
401 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_ENALBE_CEC, value);
402 break;
403
404 case HDMI_OPTION_WAKEUP:
405 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_WAKEUP, value);
406 break;
407
408 case HDMI_OPTION_SYSTEM_CEC_CONTROL:
409 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_SYS_CTRL, value);
410 break;
411
412 case HDMI_OPTION_SET_LANG:
413 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_SET_LANG, value);
414 break;
415
416 default:
417 break;
418 }
419 D("dev:%p, flag:%x, value:%x, ret:%d\n", dev, flag, value, ret);
420}
421
422/*
423 * (*set_audio_return_channel)() configures ARC circuit in the hardware logic
424 * to start or stop the feature. Flag can be either 1 to start the feature
425 * or 0 to stop it.
426 *
427 * Returns 0 on success or -errno on error.
428 */
429static void cec_set_audio_return_channel(const struct hdmi_cec_device* dev, int port_id, int flag)
430{
431 int ret;
432 if (!hal_info || hal_info->fd < 0)
433 return ;
434 ret = ioctl(hal_info->fd, CEC_IOC_SET_ARC_ENABLE, flag);
435 D("dev:%p, port id:%d, flag:%x, ret:%d\n", dev, port_id, flag, ret);
436}
437
438/*
439 * (*is_connected)() returns the connection status of the specified port.
440 * Returns HDMI_CONNECTED if a device is connected, otherwise HDMI_NOT_CONNECTED.
441 * The HAL should watch for +5V power signal to determine the status.
442 */
443static int cec_is_connected(const struct hdmi_cec_device* dev, int port_id)
444{
445 int status = -1, ret;
446
447 if (!hal_info || hal_info->fd < 0)
448 return HDMI_NOT_CONNECTED;
449
450 /* use status pass port id */
451 status = port_id;
452 ret = ioctl(hal_info->fd, CEC_IOC_GET_CONNECT_STATUS, &status);
453 if (ret)
454 return HDMI_NOT_CONNECTED;
455 D("dev:%p, port:%d, connected:%s\n", dev, port_id, status ? "yes" : "no");
456 return (status) ? HDMI_CONNECTED : HDMI_NOT_CONNECTED;
457}
458
459/** Close the hdmi cec device */
460static int cec_close(struct hw_device_t *dev)
461{
462 if (!hal_info)
463 return -EINVAL;
464
465 hal_info->run = 0;
466 while (!hal_info->exited) {
467 usleep(100 * 1000);
468 }
469 free(dev);
470 close(hal_info->fd);
471 free(hal_info->port_data);
472 free(hal_info);
473 D("closed ok\n");
474 return 0;
475}
476
477/**
478 * module methods
479 */
480static int open_cec( const struct hw_module_t* module, char const *name,
481 struct hw_device_t **device )
482{
483 char value[PROPERTY_VALUE_MAX] = {};
484 int ret;
485
486 D("name:%s\n", name);
487 hal_info = malloc(sizeof(*hal_info));
488 if (!hal_info) {
489 D("%s, alloc memory failed\n", __func__);
490 return -EINVAL;
491 }
492 if (strcmp(name, HDMI_CEC_HARDWARE_INTERFACE) != 0) {
493 D("cec strcmp fail !!!");
494 return -EINVAL;
495 }
496 if (device == NULL) {
497 D("NULL cec device on open");
498 return -EINVAL;
499 }
500 property_get("ro.hdmi.device_type", value, "0");
501 D("get ro.hdmi.device_type:%s\n", value);
502 if (value[0] == '4') {
503 hal_info->device_type = DEV_TYPE_TX;
504 } else {
505 hal_info->device_type = DEV_TYPE_RX;
506 }
507
508 hdmi_cec_device_t *dev = malloc(sizeof(hdmi_cec_device_t));
509 memset(dev, 0, sizeof(hdmi_cec_device_t));
510
511 dev->common.tag = HARDWARE_DEVICE_TAG;
512 dev->common.version = 0;
513 dev->common.module = (struct hw_module_t*) module;
514 dev->common.close = cec_close;
515
516 dev->add_logical_address = cec_add_logical_address;
517 dev->clear_logical_address = cec_clear_logical_address;
518 dev->get_physical_address = cec_get_physical_address;
519 dev->send_message = cec_send_message;
520 dev->register_event_callback = cec_register_event_callback;
521 dev->get_version = cec_get_version;
522 dev->get_vendor_id = cec_get_vendor_id;
523 dev->get_port_info = cec_get_port_info;
524 dev->set_option = cec_set_option;
525 dev->set_audio_return_channel = cec_set_audio_return_channel;
526 dev->is_connected = cec_is_connected;
527
528 *device = (hw_device_t*) dev;
529
530 hal_info->run = 1;
531 hal_info->exited = 0;
532 hal_info->ThreadId = 0;
533 hal_info->dev = dev;
534 hal_info->total_port = 0;
535 hal_info->con_status = 0;
536 hal_info->port_data = NULL;
537 hal_info->addr_bitmap = (1 << CEC_ADDR_BROADCAST);
538 hal_info->cb_data = NULL;
539 hal_info->cb = NULL;
540 hal_info->fd = open(CEC_FILE, O_RDWR);
541 if (hal_info->fd < 0) {
542 E("can't open %s\n", CEC_FILE);
543 return -EINVAL;
544 }
545 ioctl(hal_info->fd, CEC_IOC_SET_DEV_TYPE, hal_info->device_type);
546 pthread_create(&hal_info->ThreadId, NULL, cec_rx_loop, hal_info);
547
548 D("creat thread:%ld for poll cec message, fd:%d\n",
549 hal_info->ThreadId, hal_info->fd);
550
551 return 0;
552}
553
554static struct hw_module_methods_t hdmi_cec_module_methods = {
555 .open = open_cec,
556};
557
558/*
559 * The hdmi cec Module
560 */
561struct hdmi_cec_module HAL_MODULE_INFO_SYM = {
562 .common = {
563 .tag = HARDWARE_MODULE_TAG,
564 .module_api_version = HDMI_CEC_MODULE_API_VERSION_1_0,
565 .hal_api_version = HARDWARE_HAL_API_VERSION,
566 .id = HDMI_CEC_HARDWARE_MODULE_ID,
567 .name = "Amlogic hdmi cec Module",
568 .author = "Amlogic Corp.",
569 .methods = &hdmi_cec_module_methods,
570 },
571};
572
573