radio_hw.c revision 97d2ba6a1130b36df752d631d002fd316165f710
1/*
2 * Copyright (C) 2015 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#define LOG_TAG "radio_hw_stub"
18#define LOG_NDEBUG 0
19
20#include <string.h>
21#include <stdlib.h>
22#include <errno.h>
23#include <pthread.h>
24#include <sys/prctl.h>
25#include <sys/time.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <fcntl.h>
29#include <unistd.h>
30#include <cutils/log.h>
31#include <cutils/list.h>
32#include <system/radio.h>
33#include <system/radio_metadata.h>
34#include <hardware/hardware.h>
35#include <hardware/radio.h>
36
37static const radio_hal_properties_t hw_properties = {
38    .class_id = RADIO_CLASS_AM_FM,
39    .implementor = "The Android Open Source Project",
40    .product = "Radio stub HAL",
41    .version = "0.1",
42    .serial = "0123456789",
43    .num_tuners = 1,
44    .num_audio_sources = 1,
45    .supports_capture = false,
46    .num_bands = 2,
47    .bands = {
48        {
49            .type = RADIO_BAND_FM,
50            .antenna_connected = false,
51            .lower_limit = 87900,
52            .upper_limit = 107900,
53            .num_spacings = 1,
54            .spacings = { 200 },
55            .fm = {
56                .deemphasis = RADIO_DEEMPHASIS_75,
57                .stereo = true,
58                .rds = RADIO_RDS_US,
59                .ta = false,
60                .af = false,
61            }
62        },
63        {
64            .type = RADIO_BAND_AM,
65            .antenna_connected = true,
66            .lower_limit = 540,
67            .upper_limit = 1610,
68            .num_spacings = 1,
69            .spacings = { 10 },
70            .am = {
71                .stereo = true,
72            }
73        }
74    }
75};
76
77struct stub_radio_tuner {
78    struct radio_tuner interface;
79    struct stub_radio_device *dev;
80    radio_callback_t callback;
81    void *cookie;
82    radio_hal_band_config_t config;
83    radio_program_info_t program;
84    bool audio;
85    pthread_t callback_thread;
86    pthread_mutex_t lock;
87    pthread_cond_t  cond;
88    struct listnode command_list;
89};
90
91struct stub_radio_device {
92    struct radio_hw_device device;
93    struct stub_radio_tuner *tuner;
94    pthread_mutex_t lock;
95};
96
97
98typedef enum {
99    CMD_EXIT,
100    CMD_CONFIG,
101    CMD_STEP,
102    CMD_SCAN,
103    CMD_TUNE,
104    CMD_CANCEL,
105    CMD_METADATA,
106} thread_cmd_type_t;
107
108struct thread_command {
109    struct listnode node;
110    thread_cmd_type_t type;
111    struct timespec ts;
112    union {
113        unsigned int param;
114        radio_hal_band_config_t config;
115    };
116};
117
118/* must be called with out->lock locked */
119static int send_command_l(struct stub_radio_tuner *tuner,
120                          thread_cmd_type_t type,
121                          unsigned int delay_ms,
122                          void *param)
123{
124    struct thread_command *cmd = (struct thread_command *)calloc(1, sizeof(struct thread_command));
125    struct timespec ts;
126
127    if (cmd == NULL)
128        return -ENOMEM;
129
130    ALOGV("%s %d delay_ms %d", __func__, type, delay_ms);
131
132    cmd->type = type;
133    if (param != NULL) {
134        if (cmd->type == CMD_CONFIG) {
135            cmd->config = *(radio_hal_band_config_t *)param;
136            ALOGV("%s CMD_CONFIG type %d", __func__, cmd->config.type);
137        } else
138            cmd->param = *(unsigned int *)param;
139    }
140
141    clock_gettime(CLOCK_REALTIME, &ts);
142
143    ts.tv_sec  += delay_ms/1000;
144    ts.tv_nsec += (delay_ms%1000) * 1000000;
145    if (ts.tv_nsec >= 1000000000) {
146        ts.tv_nsec -= 1000000000;
147        ts.tv_sec  += 1;
148    }
149    cmd->ts = ts;
150    list_add_tail(&tuner->command_list, &cmd->node);
151    pthread_cond_signal(&tuner->cond);
152    return 0;
153}
154
155#define BITMAP_FILE_PATH "/data/misc/media/android.png"
156
157static int add_bitmap_metadata(radio_metadata_t **metadata, radio_metadata_key_t key,
158                               const char *source)
159{
160    int fd;
161    ssize_t ret = 0;
162    struct stat info;
163    void *data = NULL;
164    size_t size;
165
166    fd = open(source, O_RDONLY);
167    if (fd < 0)
168        return -EPIPE;
169
170    fstat(fd, &info);
171    size = info.st_size;
172    data = malloc(size);
173    if (data == NULL) {
174        ret = -ENOMEM;
175        goto exit;
176    }
177    ret = read(fd, data, size);
178    if (ret < 0)
179        goto exit;
180    ret = radio_metadata_add_raw(metadata, key, (const unsigned char *)data, size);
181
182exit:
183    close(fd);
184    free(data);
185    ALOGE_IF(ret != 0, "%s error %d", __func__, ret);
186    return (int)ret;
187}
188
189static int prepare_metadata(struct stub_radio_tuner *tuner,
190                            radio_metadata_t **metadata, bool program)
191{
192    int ret = 0;
193    char text[RADIO_STRING_LEN_MAX];
194    struct timespec ts;
195
196    if (metadata == NULL)
197        return -EINVAL;
198
199    if (*metadata != NULL)
200        radio_metadata_deallocate(*metadata);
201
202    *metadata = NULL;
203
204    ret = radio_metadata_allocate(metadata, tuner->program.channel, 0);
205    if (ret != 0)
206        return ret;
207
208    if (program) {
209        ret = radio_metadata_add_int(metadata, RADIO_METADATA_KEY_RBDS_PTY, 5);
210        if (ret != 0)
211            goto exit;
212        ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_RDS_PS, "RockBand");
213        if (ret != 0)
214            goto exit;
215        ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ICON, BITMAP_FILE_PATH);
216        if (ret != 0)
217            goto exit;
218    } else {
219        ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ART, BITMAP_FILE_PATH);
220        if (ret != 0)
221            goto exit;
222    }
223
224    clock_gettime(CLOCK_REALTIME, &ts);
225    snprintf(text, RADIO_STRING_LEN_MAX, "Artist %ld", ts.tv_sec % 10);
226    ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_ARTIST, text);
227    if (ret != 0)
228        goto exit;
229
230    snprintf(text, RADIO_STRING_LEN_MAX, "Song %ld", ts.tv_nsec % 10);
231    ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_TITLE, text);
232    if (ret != 0)
233        goto exit;
234
235    return 0;
236
237exit:
238    radio_metadata_deallocate(*metadata);
239    *metadata = NULL;
240    return ret;
241}
242
243static void *callback_thread_loop(void *context)
244{
245    struct stub_radio_tuner *tuner = (struct stub_radio_tuner *)context;
246    struct timespec ts = {0, 0};
247
248    ALOGI("%s", __func__);
249
250    prctl(PR_SET_NAME, (unsigned long)"sound trigger callback", 0, 0, 0);
251
252    pthread_mutex_lock(&tuner->lock);
253
254    while (true) {
255        struct thread_command *cmd = NULL;
256        struct listnode *item;
257        struct listnode *tmp;
258        struct timespec cur_ts;
259
260        if (list_empty(&tuner->command_list) || ts.tv_sec != 0) {
261            ALOGV("%s SLEEPING", __func__);
262            if (ts.tv_sec != 0) {
263                ALOGV("%s SLEEPING with timeout", __func__);
264                pthread_cond_timedwait(&tuner->cond, &tuner->lock, &ts);
265            } else {
266                ALOGV("%s SLEEPING forever", __func__);
267                pthread_cond_wait(&tuner->cond, &tuner->lock);
268            }
269            ts.tv_sec = 0;
270            ALOGV("%s RUNNING", __func__);
271        }
272
273        clock_gettime(CLOCK_REALTIME, &cur_ts);
274
275        list_for_each_safe(item, tmp, &tuner->command_list) {
276            cmd = node_to_item(item, struct thread_command, node);
277
278            if ((cmd->ts.tv_sec < cur_ts.tv_sec) ||
279                    ((cmd->ts.tv_sec == cur_ts.tv_sec) && (cmd->ts.tv_nsec < cur_ts.tv_nsec))) {
280                radio_hal_event_t event;
281
282                event.type = RADIO_EVENT_HW_FAILURE;
283                list_remove(item);
284
285                ALOGV("%s processing command %d time %ld.%ld", __func__, cmd->type, cmd->ts.tv_sec,
286                      cmd->ts.tv_nsec);
287
288                switch (cmd->type) {
289                default:
290                case CMD_EXIT:
291                    free(cmd);
292                    goto exit;
293
294                case CMD_CONFIG: {
295                    tuner->config = cmd->config;
296                    event.type = RADIO_EVENT_CONFIG;
297                    event.config = tuner->config;
298                    ALOGV("%s CMD_CONFIG type %d low %d up %d",
299                          __func__, tuner->config.type,
300                          tuner->config.lower_limit, tuner->config.upper_limit);
301                    if (tuner->config.type == RADIO_BAND_FM) {
302                        ALOGV("  - stereo %d\n  - rds %d\n  - ta %d\n  - af %d",
303                              tuner->config.fm.stereo, tuner->config.fm.rds,
304                              tuner->config.fm.ta, tuner->config.fm.af);
305                    } else {
306                        ALOGV("  - stereo %d", tuner->config.am.stereo);
307                    }
308                } break;
309
310                case CMD_STEP: {
311                    int frequency;
312                    frequency = tuner->program.channel;
313                    if (cmd->param == RADIO_DIRECTION_UP) {
314                        frequency += tuner->config.spacings[0];
315                    } else {
316                        frequency -= tuner->config.spacings[0];
317                    }
318                    if (frequency > (int)tuner->config.upper_limit) {
319                        frequency = tuner->config.lower_limit;
320                    }
321                    if (frequency < (int)tuner->config.lower_limit) {
322                        frequency = tuner->config.upper_limit;
323                    }
324                    tuner->program.channel = frequency;
325                    tuner->program.tuned  = (frequency / (tuner->config.spacings[0] * 5)) % 2;
326                    tuner->program.signal_strength = 20;
327                    if (tuner->config.type == RADIO_BAND_FM)
328                        tuner->program.stereo = false;
329                    else
330                        tuner->program.stereo = false;
331
332                    event.type = RADIO_EVENT_TUNED;
333                    event.info = tuner->program;
334                } break;
335
336                case CMD_SCAN: {
337                    int frequency;
338                    frequency = tuner->program.channel;
339                    if (cmd->param == RADIO_DIRECTION_UP) {
340                        frequency += tuner->config.spacings[0] * 25;
341                    } else {
342                        frequency -= tuner->config.spacings[0] * 25;
343                    }
344                    if (frequency > (int)tuner->config.upper_limit) {
345                        frequency = tuner->config.lower_limit;
346                    }
347                    if (frequency < (int)tuner->config.lower_limit) {
348                        frequency = tuner->config.upper_limit;
349                    }
350                    tuner->program.channel = (unsigned int)frequency;
351                    tuner->program.tuned  = true;
352                    if (tuner->config.type == RADIO_BAND_FM)
353                        tuner->program.stereo = tuner->config.fm.stereo;
354                    else
355                        tuner->program.stereo = tuner->config.am.stereo;
356                    tuner->program.signal_strength = 50;
357
358                    event.type = RADIO_EVENT_TUNED;
359                    event.info = tuner->program;
360                    if (tuner->program.metadata != NULL)
361                        radio_metadata_deallocate(tuner->program.metadata);
362                    tuner->program.metadata = NULL;
363                    send_command_l(tuner, CMD_METADATA, 2000, NULL);
364                } break;
365
366                case CMD_TUNE: {
367                    tuner->program.channel = cmd->param;
368                    tuner->program.tuned  = (tuner->program.channel /
369                                                (tuner->config.spacings[0] * 5)) % 2;
370
371                    if (tuner->program.tuned) {
372                        prepare_metadata(tuner, &tuner->program.metadata, true);
373                        send_command_l(tuner, CMD_METADATA, 5000, NULL);
374                    } else {
375                        if (tuner->program.metadata != NULL)
376                            radio_metadata_deallocate(tuner->program.metadata);
377                        tuner->program.metadata = NULL;
378                    }
379                    tuner->program.signal_strength = 100;
380                    if (tuner->config.type == RADIO_BAND_FM)
381                        tuner->program.stereo =
382                                tuner->program.tuned ? tuner->config.fm.stereo : false;
383                    else
384                        tuner->program.stereo =
385                            tuner->program.tuned ? tuner->config.am.stereo : false;
386                    event.type = RADIO_EVENT_TUNED;
387                    event.info = tuner->program;
388                } break;
389
390                case CMD_METADATA: {
391                    prepare_metadata(tuner, &tuner->program.metadata, false);
392                    event.type = RADIO_EVENT_METADATA;
393                    event.metadata = tuner->program.metadata;
394                } break;
395
396                case CMD_CANCEL: {
397                    struct listnode *tmp2;
398                    list_for_each_safe(item, tmp2, &tuner->command_list) {
399                        cmd = node_to_item(item, struct thread_command, node);
400                        if (cmd->type == CMD_STEP || cmd->type == CMD_SCAN ||
401                                cmd->type == CMD_TUNE || cmd->type == CMD_METADATA) {
402                            list_remove(item);
403                            free(cmd);
404                        }
405                    }
406                } break;
407
408                }
409                if (event.type != RADIO_EVENT_HW_FAILURE && tuner->callback != NULL) {
410                    pthread_mutex_unlock(&tuner->lock);
411                    tuner->callback(&event, tuner->cookie);
412                    pthread_mutex_lock(&tuner->lock);
413                }
414                ALOGV("%s processed command %d", __func__, cmd->type);
415                free(cmd);
416            } else {
417                if ((ts.tv_sec == 0) ||
418                        (cmd->ts.tv_sec < ts.tv_sec) ||
419                        ((cmd->ts.tv_sec == ts.tv_sec) && (cmd->ts.tv_nsec < ts.tv_nsec))) {
420                    ts.tv_sec = cmd->ts.tv_sec;
421                    ts.tv_nsec = cmd->ts.tv_nsec;
422                }
423            }
424        }
425    }
426
427exit:
428    pthread_mutex_unlock(&tuner->lock);
429
430    ALOGV("%s Exiting", __func__);
431
432    return NULL;
433}
434
435
436static int tuner_set_configuration(const struct radio_tuner *tuner,
437                         const radio_hal_band_config_t *config)
438{
439    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
440    int status = 0;
441
442    ALOGI("%s stub_tuner %p", __func__, stub_tuner);
443    pthread_mutex_lock(&stub_tuner->lock);
444    if (config == NULL) {
445        status = -EINVAL;
446        goto exit;
447    }
448    send_command_l(stub_tuner, CMD_CANCEL, 0, NULL);
449    send_command_l(stub_tuner, CMD_CONFIG, 500, (void *)config);
450
451exit:
452    pthread_mutex_unlock(&stub_tuner->lock);
453    return status;
454}
455
456static int tuner_get_configuration(const struct radio_tuner *tuner,
457                         radio_hal_band_config_t *config)
458{
459    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
460    int status = 0;
461    struct listnode *item;
462    radio_hal_band_config_t *src_config;
463
464    ALOGI("%s stub_tuner %p", __func__, stub_tuner);
465    pthread_mutex_lock(&stub_tuner->lock);
466    src_config = &stub_tuner->config;
467
468    if (config == NULL) {
469        status = -EINVAL;
470        goto exit;
471    }
472    list_for_each(item, &stub_tuner->command_list) {
473        struct thread_command *cmd = node_to_item(item, struct thread_command, node);
474        if (cmd->type == CMD_CONFIG) {
475            src_config = &cmd->config;
476        }
477    }
478    *config = *src_config;
479
480exit:
481    pthread_mutex_unlock(&stub_tuner->lock);
482    return status;
483}
484
485static int tuner_step(const struct radio_tuner *tuner,
486                     radio_direction_t direction, bool skip_sub_channel)
487{
488    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
489
490    ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d",
491          __func__, stub_tuner, direction, skip_sub_channel);
492
493    pthread_mutex_lock(&stub_tuner->lock);
494    send_command_l(stub_tuner, CMD_STEP, 20, &direction);
495    pthread_mutex_unlock(&stub_tuner->lock);
496    return 0;
497}
498
499static int tuner_scan(const struct radio_tuner *tuner,
500                     radio_direction_t direction, bool skip_sub_channel)
501{
502    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
503
504    ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d",
505          __func__, stub_tuner, direction, skip_sub_channel);
506
507    pthread_mutex_lock(&stub_tuner->lock);
508    send_command_l(stub_tuner, CMD_SCAN, 200, &direction);
509    pthread_mutex_unlock(&stub_tuner->lock);
510    return 0;
511}
512
513static int tuner_tune(const struct radio_tuner *tuner,
514                     unsigned int channel, unsigned int sub_channel)
515{
516    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
517
518    ALOGI("%s stub_tuner %p channel %d, sub_channel %d",
519          __func__, stub_tuner, channel, sub_channel);
520
521    pthread_mutex_lock(&stub_tuner->lock);
522    if (channel < stub_tuner->config.lower_limit || channel > stub_tuner->config.upper_limit) {
523        pthread_mutex_unlock(&stub_tuner->lock);
524        ALOGI("%s channel out of range", __func__);
525        return -EINVAL;
526    }
527    send_command_l(stub_tuner, CMD_TUNE, 100, &channel);
528    pthread_mutex_unlock(&stub_tuner->lock);
529    return 0;
530}
531
532static int tuner_cancel(const struct radio_tuner *tuner)
533{
534    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
535
536    ALOGI("%s stub_tuner %p", __func__, stub_tuner);
537
538    pthread_mutex_lock(&stub_tuner->lock);
539    send_command_l(stub_tuner, CMD_CANCEL, 0, NULL);
540    pthread_mutex_unlock(&stub_tuner->lock);
541    return 0;
542}
543
544static int tuner_get_program_information(const struct radio_tuner *tuner,
545                                        radio_program_info_t *info)
546{
547    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
548    int status = 0;
549    radio_metadata_t *metadata;
550
551    ALOGI("%s stub_tuner %p", __func__, stub_tuner);
552    pthread_mutex_lock(&stub_tuner->lock);
553    if (info == NULL) {
554        status = -EINVAL;
555        goto exit;
556    }
557    metadata = info->metadata;
558    *info = stub_tuner->program;
559    info->metadata = metadata;
560    if (metadata != NULL)
561        radio_metadata_add_metadata(&info->metadata, stub_tuner->program.metadata);
562
563exit:
564    pthread_mutex_unlock(&stub_tuner->lock);
565    return status;
566}
567
568static int rdev_get_properties(const struct radio_hw_device *dev,
569                                radio_hal_properties_t *properties)
570{
571    struct stub_radio_device *rdev = (struct stub_radio_device *)dev;
572
573    ALOGI("%s", __func__);
574    if (properties == NULL)
575        return -EINVAL;
576    memcpy(properties, &hw_properties, sizeof(radio_hal_properties_t));
577    return 0;
578}
579
580static int rdev_open_tuner(const struct radio_hw_device *dev,
581                          const radio_hal_band_config_t *config,
582                          bool audio,
583                          radio_callback_t callback,
584                          void *cookie,
585                          const struct radio_tuner **tuner)
586{
587    struct stub_radio_device *rdev = (struct stub_radio_device *)dev;
588    int status = 0;
589
590    ALOGI("%s rdev %p", __func__, rdev);
591    pthread_mutex_lock(&rdev->lock);
592
593    if (rdev->tuner != NULL) {
594        status = -ENOSYS;
595        goto exit;
596    }
597
598    if (config == NULL || callback == NULL || tuner == NULL) {
599        status = -EINVAL;
600        goto exit;
601    }
602
603    rdev->tuner = (struct stub_radio_tuner *)calloc(1, sizeof(struct stub_radio_tuner));
604    if (rdev->tuner == NULL) {
605        status = -ENOMEM;
606        goto exit;
607    }
608
609    rdev->tuner->interface.set_configuration = tuner_set_configuration;
610    rdev->tuner->interface.get_configuration = tuner_get_configuration;
611    rdev->tuner->interface.scan = tuner_scan;
612    rdev->tuner->interface.step = tuner_step;
613    rdev->tuner->interface.tune = tuner_tune;
614    rdev->tuner->interface.cancel = tuner_cancel;
615    rdev->tuner->interface.get_program_information = tuner_get_program_information;
616
617    rdev->tuner->audio = audio;
618    rdev->tuner->callback = callback;
619    rdev->tuner->cookie = cookie;
620
621    rdev->tuner->dev = rdev;
622
623    pthread_mutex_init(&rdev->tuner->lock, (const pthread_mutexattr_t *) NULL);
624    pthread_cond_init(&rdev->tuner->cond, (const pthread_condattr_t *) NULL);
625    pthread_create(&rdev->tuner->callback_thread, (const pthread_attr_t *) NULL,
626                        callback_thread_loop, rdev->tuner);
627    list_init(&rdev->tuner->command_list);
628
629    pthread_mutex_lock(&rdev->tuner->lock);
630    send_command_l(rdev->tuner, CMD_CONFIG, 500, (void *)config);
631    pthread_mutex_unlock(&rdev->tuner->lock);
632
633    *tuner = &rdev->tuner->interface;
634
635exit:
636    pthread_mutex_unlock(&rdev->lock);
637    ALOGI("%s DONE", __func__);
638    return status;
639}
640
641static int rdev_close_tuner(const struct radio_hw_device *dev,
642                            const struct radio_tuner *tuner)
643{
644    struct stub_radio_device *rdev = (struct stub_radio_device *)dev;
645    struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner;
646    int status = 0;
647
648    ALOGI("%s tuner %p", __func__, tuner);
649    pthread_mutex_lock(&rdev->lock);
650
651    if (tuner == NULL) {
652        status = -EINVAL;
653        goto exit;
654    }
655
656    pthread_mutex_lock(&stub_tuner->lock);
657    stub_tuner->callback = NULL;
658    send_command_l(stub_tuner, CMD_EXIT, 0, NULL);
659    pthread_mutex_unlock(&stub_tuner->lock);
660    pthread_join(stub_tuner->callback_thread, (void **) NULL);
661
662    if (stub_tuner->program.metadata != NULL)
663        radio_metadata_deallocate(stub_tuner->program.metadata);
664
665    free(stub_tuner);
666    rdev->tuner = NULL;
667
668exit:
669    pthread_mutex_unlock(&rdev->lock);
670    return status;
671}
672
673static int rdev_close(hw_device_t *device)
674{
675    struct stub_radio_device *rdev = (struct stub_radio_device *)device;
676    if (rdev != NULL) {
677        free(rdev->tuner);
678    }
679    free(rdev);
680    return 0;
681}
682
683static int rdev_open(const hw_module_t* module, const char* name,
684                     hw_device_t** device)
685{
686    struct stub_radio_device *rdev;
687    int ret;
688
689    if (strcmp(name, RADIO_HARDWARE_DEVICE) != 0)
690        return -EINVAL;
691
692    rdev = calloc(1, sizeof(struct stub_radio_device));
693    if (!rdev)
694        return -ENOMEM;
695
696    rdev->device.common.tag = HARDWARE_DEVICE_TAG;
697    rdev->device.common.version = RADIO_DEVICE_API_VERSION_1_0;
698    rdev->device.common.module = (struct hw_module_t *) module;
699    rdev->device.common.close = rdev_close;
700    rdev->device.get_properties = rdev_get_properties;
701    rdev->device.open_tuner = rdev_open_tuner;
702    rdev->device.close_tuner = rdev_close_tuner;
703
704    pthread_mutex_init(&rdev->lock, (const pthread_mutexattr_t *) NULL);
705
706    *device = &rdev->device.common;
707
708    return 0;
709}
710
711
712static struct hw_module_methods_t hal_module_methods = {
713    .open = rdev_open,
714};
715
716struct radio_module HAL_MODULE_INFO_SYM = {
717    .common = {
718        .tag = HARDWARE_MODULE_TAG,
719        .module_api_version = RADIO_MODULE_API_VERSION_1_0,
720        .hal_api_version = HARDWARE_HAL_API_VERSION,
721        .id = RADIO_HARDWARE_MODULE_ID,
722        .name = "Stub radio HAL",
723        .author = "The Android Open Source Project",
724        .methods = &hal_module_methods,
725    },
726};
727