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