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