summaryrefslogtreecommitdiff
path: root/hdmi_cec.c (plain)
blob: 9ef9d23800cabe7fe86fecc6351dea0288509392
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 }
139 }
140 hal->con_status = prev_status;
141}
142
143static void *cec_rx_loop(void *data)
144{
145 struct aml_cec_hal *hal = (struct aml_cec_hal *)data;
146 hdmi_event_t event;
147 unsigned char msg_buf[CEC_MESSAGE_BODY_MAX_LENGTH];
148 int r;
149#if DEBUG
150 char buf[64] = {};
151 int size = 0, i;
152#endif
153
154 D("start\n");
155 while (hal_info->fd < 0) {
156 usleep(1000 * 1000);
157 hal_info->fd = open(CEC_FILE, O_RDWR);
158 }
159 D("file open ok\n");
160 while (hal && hal->run) {
161 check_connect_status(hal);
162 memset(&event, 0, sizeof(event));
163 memset(msg_buf, 0, sizeof(msg_buf));
164
165 /* try to got a message from dev */
166 r = cec_rx_read_msg(msg_buf, CEC_MESSAGE_BODY_MAX_LENGTH);
167 if (r <= 1) { /* ignore received ping messages */
168 continue;
169 }
170 #if DEBUG
171 size = 0;
172 memset(buf, 0, sizeof(buf));
173 for (i = 0; i < r; i++) {
174 size += sprintf(buf + size, "%02x ", msg_buf[i]);
175 }
176 D("msg:%s", buf);
177 #endif
178 memcpy(event.cec.body, msg_buf + 1, r - 1);
179 event.type = HDMI_EVENT_CEC_MESSAGE;
180 event.dev = hal->dev;
181 event.cec.initiator = (msg_buf[0] >> 4) & 0xf;
182 event.cec.destination = (msg_buf[0] >> 0) & 0xf;
183 event.cec.length = r - 1;
184 if (hal->cb) {
185 hal->cb(&event, hal_info->cb_data);
186 }
187 }
188 D("end\n");
189 hal->exited = 1;
190 return 0;
191}
192
193/*
194 * (*add_logical_address)() passes the logical address that will be used
195 * in this system.
196 *
197 * HAL may use it to configure the hardware so that the CEC commands addressed
198 * the given logical address can be filtered in. This method can be called
199 * as many times as necessary in order to support multiple logical devices.
200 * addr should be in the range of valid logical addresses for the call
201 * to succeed.
202 *
203 * Returns 0 on success or -errno on error.
204 */
205static int cec_add_logical_address(const struct hdmi_cec_device* dev, cec_logical_address_t addr)
206{
207 if (!hal_info || hal_info->fd < 0)
208 return -EINVAL;
209 if (addr < CEC_ADDR_BROADCAST)
210 hal_info->addr_bitmap |= (1 << addr);
211 D("dev:%p, addr:%x, bitmap:%x\n", dev, addr, hal_info->addr_bitmap);
212 return ioctl(hal_info->fd, CEC_IOC_ADD_LOGICAL_ADDR, addr);
213}
214
215/*
216 * (*clear_logical_address)() tells HAL to reset all the logical addresses.
217 *
218 * It is used when the system doesn't need to process CEC command any more,
219 * hence to tell HAL to stop receiving commands from the CEC bus, and change
220 * the state back to the beginning.
221 */
222static void cec_clear_logical_address(const struct hdmi_cec_device* dev)
223{
224 if (!hal_info || hal_info->fd < 0)
225 return ;
226 hal_info->addr_bitmap = (1 << CEC_ADDR_BROADCAST);
227 D("dev:%p, bitmap:%x\n", dev, hal_info->addr_bitmap);
228 ioctl(hal_info->fd, CEC_IOC_CLR_LOGICAL_ADDR, 0);
229}
230
231/*
232 * (*get_physical_address)() returns the CEC physical address. The
233 * address is written to addr.
234 *
235 * The physical address depends on the topology of the network formed
236 * by connected HDMI devices. It is therefore likely to change if the cable
237 * is plugged off and on again. It is advised to call get_physical_address
238 * to get the updated address when hot plug event takes place.
239 *
240 * Returns 0 on success or -errno on error.
241 */
242static int cec_get_physical_address(const struct hdmi_cec_device* dev, uint16_t* addr)
243{
244 int ret;
245 if (!hal_info || hal_info->fd < 0)
246 return -EINVAL;
247 ret = ioctl(hal_info->fd, CEC_IOC_GET_PHYSICAL_ADDR, addr);
248 D("dev:%p, physical addr:%x, ret:%d\n", dev, *addr, ret);
249 return ret;
250}
251
252/*
253 * (*send_message)() transmits HDMI-CEC message to other HDMI device.
254 *
255 * The method should be designed to return in a certain amount of time not
256 * hanging forever, which can happen if CEC signal line is pulled low for
257 * some reason. HAL implementation should take the situation into account
258 * so as not to wait forever for the message to get sent out.
259 *
260 * It should try retransmission at least once as specified in the standard.
261 *
262 * Returns error code. See HDMI_RESULT_SUCCESS, HDMI_RESULT_NACK, and
263 * HDMI_RESULT_BUSY.
264 */
265static int cec_send_message(const struct hdmi_cec_device* dev, const cec_message_t* msg)
266{
267 int i, ret;
268 unsigned fail_reason = 0;
269 unsigned char msg_buf[CEC_MESSAGE_BODY_MAX_LENGTH] = {};
270#if DEBUG
271 char buf[64] = {};
272 int size = 0;
273#endif
274
275 if (!hal_info || hal_info->fd < 0)
276 return HDMI_RESULT_FAIL;
277
278#if DEBUG
279 memset(buf, 0, sizeof(buf));
280 for (i = 0; i < (int)msg->length; i++) {
281 size += sprintf(buf + size, "%02x ", msg->body[i]);
282 }
283 if (msg->length) {
284 D("[%x -> %x],len:%d, body:%s",
285 msg->initiator, msg->destination, msg->length, buf);
286 }
287#endif
288
289 memset(msg_buf, 0, sizeof(msg_buf));
290 msg_buf[0] = ((msg->initiator & 0xf) << 4) | (msg->destination & 0xf);
291 memcpy(msg_buf + 1, msg->body, msg->length);
292 ret = write(hal_info->fd, msg_buf, msg->length + 1);
293 if (ret > 0) {
294 return HDMI_RESULT_SUCCESS;
295 } else {
296 ioctl(hal_info->fd, CEC_IOC_GET_SEND_FAIL_REASON, &fail_reason);
297 if (msg->length)
298 D("fail reason:%x\n", fail_reason);
299 switch (fail_reason) {
300 case CEC_FAIL_NACK:
301 return HDMI_RESULT_NACK;
302 case CEC_FAIL_BUSY:
303 return HDMI_RESULT_BUSY;
304 default:
305 return HDMI_RESULT_FAIL;
306 }
307 }
308}
309
310/*
311 * (*register_event_callback)() registers a callback that HDMI-CEC HAL
312 * can later use for incoming CEC messages or internal HDMI events.
313 * When calling from C++, use the argument arg to pass the calling object.
314 * It will be passed back when the callback is invoked so that the context
315 * can be retrieved.
316 */
317static void cec_register_event_callback(const struct hdmi_cec_device* dev,
318 event_callback_t callback, void* arg)
319{
320 if (!hal_info || hal_info->fd < 0)
321 return ;
322 D("dev:%p, callback:%p, arg:%p\n", callback, arg, dev);
323 hal_info->cb = callback;
324 hal_info->cb_data = arg;
325}
326
327/*
328 * (*get_version)() returns the CEC version supported by underlying hardware.
329 */
330static void cec_get_version(const struct hdmi_cec_device* dev, int* version)
331{
332 if (!hal_info || hal_info->fd < 0)
333 return ;
334 ioctl(hal_info->fd, CEC_IOC_GET_VERSION, version);
335 D("dev:%p, version:%x\n", dev, *version);
336}
337
338/*
339 * (*get_vendor_id)() returns the identifier of the vendor. It is
340 * the 24-bit unique company ID obtained from the IEEE Registration
341 * Authority Committee (RAC).
342 */
343static void cec_get_vendor_id(const struct hdmi_cec_device* dev, uint32_t* vendor_id)
344{
345 if (!hal_info || hal_info->fd < 0)
346 return ;
347 ioctl(hal_info->fd, CEC_IOC_GET_VENDOR_ID, vendor_id);
348 D("dev:%p, vendor_id:%x\n", dev, *vendor_id);
349}
350
351/*
352 * (*get_port_info)() returns the hdmi port information of underlying hardware.
353 * info is the list of HDMI port information, and 'total' is the number of
354 * HDMI ports in the system.
355 */
356static void cec_get_port_info(const struct hdmi_cec_device* dev,
357 struct hdmi_port_info* list[], int* total)
358{
359 int i;
360
361 if (!hal_info || hal_info->fd < 0)
362 return ;
363
364 ioctl(hal_info->fd, CEC_IOC_GET_PORT_NUM, total);
365 D("dev:%p, total port:%d\n", dev, *total);
366 if (*total > MAX_PORT)
367 *total = MAX_PORT;
368 hal_info->total_port = *total;
369 hal_info->port_data = malloc(sizeof(struct hdmi_port_info) * (*total));
370 if (!hal_info->port_data) {
371 E("alloc port_data failed\n");
372 *total = 0;
373 return ;
374 }
375 ioctl(hal_info->fd, CEC_IOC_GET_PORT_INFO, hal_info->port_data);
376 for (i = 0; i < *total; i++) {
377 D("port %d, type:%s, id:%d, cec support:%d, arc support:%d, physical address:%x\n",
378 i, hal_info->port_data[i].type ? "output" : "input",
379 hal_info->port_data[i].port_id,
380 hal_info->port_data[i].cec_supported,
381 hal_info->port_data[i].arc_supported,
382 hal_info->port_data[i].physical_address);
383 }
384 *list = hal_info->port_data;
385}
386
387/*
388 * (*set_option)() passes flags controlling the way HDMI-CEC service works down
389 * to HAL implementation. Those flags will be used in case the feature needs
390 * update in HAL itself, firmware or microcontroller.
391 */
392static void cec_set_option(const struct hdmi_cec_device* dev, int flag, int value)
393{
394 int ret;
395
396 if (!hal_info || hal_info->fd < 0)
397 return ;
398 switch (flag) {
399 case HDMI_OPTION_ENABLE_CEC:
400 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_ENALBE_CEC, value);
401 break;
402
403 case HDMI_OPTION_WAKEUP:
404 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_WAKEUP, value);
405 break;
406
407 case HDMI_OPTION_SYSTEM_CEC_CONTROL:
408 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_SYS_CTRL, value);
409 break;
410
411 case HDMI_OPTION_SET_LANG:
412 ret = ioctl(hal_info->fd, CEC_IOC_SET_OPTION_SET_LANG, value);
413 break;
414
415 default:
416 break;
417 }
418 D("dev:%p, flag:%x, value:%x, ret:%d\n", dev, flag, value, ret);
419}
420
421/*
422 * (*set_audio_return_channel)() configures ARC circuit in the hardware logic
423 * to start or stop the feature. Flag can be either 1 to start the feature
424 * or 0 to stop it.
425 *
426 * Returns 0 on success or -errno on error.
427 */
428static void cec_set_audio_return_channel(const struct hdmi_cec_device* dev, int port_id, int flag)
429{
430 if (!hal_info || hal_info->fd < 0)
431 return ;
432 /* TODO: */
433 D("dev:%p, port id:%d, flag:%x\n", dev, port_id, flag);
434}
435
436/*
437 * (*is_connected)() returns the connection status of the specified port.
438 * Returns HDMI_CONNECTED if a device is connected, otherwise HDMI_NOT_CONNECTED.
439 * The HAL should watch for +5V power signal to determine the status.
440 */
441static int cec_is_connected(const struct hdmi_cec_device* dev, int port_id)
442{
443 int status = -1, ret;
444
445 if (!hal_info || hal_info->fd < 0)
446 return HDMI_NOT_CONNECTED;
447
448 /* use status pass port id */
449 status = port_id;
450 ret = ioctl(hal_info->fd, CEC_IOC_GET_CONNECT_STATUS, &status);
451 if (ret)
452 return HDMI_NOT_CONNECTED;
453 D("dev:%p, port:%d, connected:%s\n", dev, port_id, status ? "yes" : "no");
454 return (status) ? HDMI_CONNECTED : HDMI_NOT_CONNECTED;
455}
456
457/** Close the hdmi cec device */
458static int cec_close(struct hw_device_t *dev)
459{
460 if (!hal_info)
461 return -EINVAL;
462
463 hal_info->run = 0;
464 while (!hal_info->exited) {
465 usleep(100 * 1000);
466 }
467 free(dev);
468 close(hal_info->fd);
469 free(hal_info->port_data);
470 free(hal_info);
471 D("closed ok\n");
472 return 0;
473}
474
475/**
476 * module methods
477 */
478static int open_cec( const struct hw_module_t* module, char const *name,
479 struct hw_device_t **device )
480{
481 char value[PROPERTY_VALUE_MAX] = {};
482 int ret;
483
484 D("name:%s\n", name);
485 hal_info = malloc(sizeof(*hal_info));
486 if (!hal_info) {
487 D("%s, alloc memory failed\n", __func__);
488 return -EINVAL;
489 }
490 if (strcmp(name, HDMI_CEC_HARDWARE_INTERFACE) != 0) {
491 D("cec strcmp fail !!!");
492 return -EINVAL;
493 }
494 if (device == NULL) {
495 D("NULL cec device on open");
496 return -EINVAL;
497 }
498 property_get("ro.hdmi.device_type", value, "0");
499 D("get ro.hdmi.device_type:%s\n", value);
500 if (value[0] == '4') {
501 hal_info->device_type = DEV_TYPE_TX;
502 } else {
503 hal_info->device_type = DEV_TYPE_RX;
504 }
505
506 hdmi_cec_device_t *dev = malloc(sizeof(hdmi_cec_device_t));
507 memset(dev, 0, sizeof(hdmi_cec_device_t));
508
509 dev->common.tag = HARDWARE_DEVICE_TAG;
510 dev->common.version = 0;
511 dev->common.module = (struct hw_module_t*) module;
512 dev->common.close = cec_close;
513
514 dev->add_logical_address = cec_add_logical_address;
515 dev->clear_logical_address = cec_clear_logical_address;
516 dev->get_physical_address = cec_get_physical_address;
517 dev->send_message = cec_send_message;
518 dev->register_event_callback = cec_register_event_callback;
519 dev->get_version = cec_get_version;
520 dev->get_vendor_id = cec_get_vendor_id;
521 dev->get_port_info = cec_get_port_info;
522 dev->set_option = cec_set_option;
523 dev->set_audio_return_channel = cec_set_audio_return_channel;
524 dev->is_connected = cec_is_connected;
525
526 *device = (hw_device_t*) dev;
527
528 hal_info->run = 1;
529 hal_info->exited = 0;
530 hal_info->ThreadId = 0;
531 hal_info->dev = dev;
532 hal_info->total_port = 0;
533 hal_info->con_status = 0;
534 hal_info->port_data = NULL;
535 hal_info->addr_bitmap = (1 << CEC_ADDR_BROADCAST);
536 hal_info->cb_data = NULL;
537 hal_info->cb = NULL;
538 hal_info->fd = open(CEC_FILE, O_RDWR);
539 if (hal_info->fd < 0) {
540 E("can't open %s\n", CEC_FILE);
541 return -EINVAL;
542 }
543 ioctl(hal_info->fd, CEC_IOC_SET_DEV_TYPE, hal_info->device_type);
544 pthread_create(&hal_info->ThreadId, NULL, cec_rx_loop, hal_info);
545
546 D("creat thread:%ld for poll cec message, fd:%d\n",
547 hal_info->ThreadId, hal_info->fd);
548
549 return 0;
550}
551
552static struct hw_module_methods_t hdmi_cec_module_methods = {
553 .open = open_cec,
554};
555
556/*
557 * The hdmi cec Module
558 */
559struct hdmi_cec_module HAL_MODULE_INFO_SYM = {
560 .common = {
561 .tag = HARDWARE_MODULE_TAG,
562 .module_api_version = HDMI_CEC_MODULE_API_VERSION_1_0,
563 .hal_api_version = HARDWARE_HAL_API_VERSION,
564 .id = HDMI_CEC_HARDWARE_MODULE_ID,
565 .name = "Amlogic hdmi cec Module",
566 .author = "Amlogic Corp.",
567 .methods = &hdmi_cec_module_methods,
568 },
569};
570
571