diff options
Diffstat (limited to 'binding')
-rw-r--r-- | binding/CMakeLists.txt | 8 | ||||
-rw-r--r-- | binding/radio-binding.c | 222 | ||||
-rw-r--r-- | binding/radio_impl.h | 6 | ||||
-rw-r--r-- | binding/radio_impl_kingfisher.c | 158 | ||||
-rw-r--r-- | binding/radio_impl_null.c | 267 | ||||
-rw-r--r-- | binding/radio_impl_null.h | 25 | ||||
-rw-r--r-- | binding/radio_impl_rtlsdr.c | 16 | ||||
-rw-r--r-- | binding/radio_impl_tef665x.c | 2415 | ||||
-rw-r--r-- | binding/radio_impl_tef665x.h | 23 | ||||
-rw-r--r-- | binding/radio_output_gstreamer.c | 17 | ||||
-rw-r--r-- | binding/tef665x.h | 397 |
11 files changed, 3444 insertions, 110 deletions
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt index 97bc2a6..560dcaf 100644 --- a/binding/CMakeLists.txt +++ b/binding/CMakeLists.txt @@ -1,6 +1,6 @@ ########################################################################### # Copyright 2015, 2016, 2017 IoT.bzh -# Copyright (C) 2018 Konsulko Group +# Copyright (C) 2018, 2019 Konsulko Group # # author: Fulup Ar Foll <fulup@iot.bzh> # contrib: Romain Forlot <romain.forlot@iot.bzh> @@ -21,13 +21,15 @@ # Add target to project dependency list PROJECT_TARGET_ADD(radio-binding) - add_definitions(-DAFB_BINDING_VERSION=2) + add_definitions(-DAFB_BINDING_VERSION=3) # Define project Targets set(radio_SOURCES radio-binding.c radio_impl_kingfisher.c - radio_impl_rtlsdr.c) + radio_impl_null.c + radio_impl_rtlsdr.c + radio_impl_tef665x.c) PKG_CHECK_MODULES(SOUND REQUIRED gstreamer-1.0) diff --git a/binding/radio-binding.c b/binding/radio-binding.c index 847e822..34eb53e 100644 --- a/binding/radio-binding.c +++ b/binding/radio-binding.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Konsulko Group + * Copyright (C) 2017, 2019 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,18 +24,29 @@ #include <unistd.h> #include <sys/types.h> #include <json-c/json.h> - #include <afb/afb-binding.h> #include "radio_impl.h" +#include "radio_impl_null.h" #include "radio_impl_rtlsdr.h" #include "radio_impl_kingfisher.h" +#include "radio_impl_tef665x.h" static radio_impl_ops_t *radio_impl_ops; -static struct afb_event freq_event; -static struct afb_event scan_event; -static struct afb_event status_event; +static afb_event_t freq_event; +static afb_event_t scan_event; +static afb_event_t status_event; +static afb_event_t rds_event; + +static bool playing; + +static const char *signalcomposer_events[] = { + "event.media.next", + "event.media.previous", + "event.media.mode", + NULL, +}; static void freq_callback(uint32_t frequency, void *data) { @@ -55,6 +66,12 @@ static void scan_callback(uint32_t frequency, void *data) afb_event_push(scan_event, json_object_get(jresp)); } +static void rds_callback(void *rds_data) +{ + //rds_data is a json object + afb_event_push(rds_event, json_object_get(rds_data)); +} + /* * Binding verb handlers */ @@ -62,10 +79,10 @@ static void scan_callback(uint32_t frequency, void *data) /* * @brief Get (and optionally set) frequency * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void frequency(struct afb_req request) +static void frequency(afb_req_t request) { json_object *ret_json; const char *value = afb_req_value(request, "value"); @@ -77,28 +94,28 @@ static void frequency(struct afb_req request) if(frequency && *p == '\0') { radio_impl_ops->set_frequency(frequency); } else { - afb_req_fail(request, "failed", "Invalid scan direction"); + afb_req_reply(request, NULL, "failed", "Invalid scan direction"); return; } } ret_json = json_object_new_object(); frequency = radio_impl_ops->get_frequency(); json_object_object_add(ret_json, "frequency", json_object_new_int((int32_t) frequency)); - afb_req_success(request, ret_json, NULL); + afb_req_reply(request, ret_json, NULL, NULL); } /* @brief Get RDS information * -* @param struct afb_req : an afb request structure +* @param afb_req_t : an afb request structure * */ -static void rds(struct afb_req request) +static void rds(afb_req_t request) { json_object *ret_json; char * rds; if (radio_impl_ops->get_rds_info == NULL) { - afb_req_fail(request, "failed", "not supported"); + afb_req_reply(request, NULL, "failed", "not supported"); return; } @@ -107,16 +124,16 @@ static void rds(struct afb_req request) json_object_object_add(ret_json, "rds", json_object_new_string(rds?rds:"")); free(rds); - afb_req_success(request, ret_json, NULL); + afb_req_reply(request, ret_json, NULL, NULL); } /* * @brief Get (and optionally set) frequency band * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void band(struct afb_req request) +static void band(afb_req_t request) { json_object *ret_json; const char *value = afb_req_value(request, "value"); @@ -148,7 +165,7 @@ static void band(struct afb_req request) if(valid) { radio_impl_ops->set_band(band); } else { - afb_req_fail(request, "failed", "Invalid band"); + afb_req_reply(request, NULL, "failed", "Invalid band"); return; } } @@ -156,16 +173,16 @@ static void band(struct afb_req request) band = radio_impl_ops->get_band(); sprintf(band_name, "%s", band == BAND_AM ? "AM" : "FM"); json_object_object_add(ret_json, "band", json_object_new_string(band_name)); - afb_req_success(request, ret_json, NULL); + afb_req_reply(request, ret_json, NULL, NULL); } /* * @brief Check if band is supported * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void band_supported(struct afb_req request) +static void band_supported(afb_req_t request) { json_object *ret_json; const char *value = afb_req_value(request, "band"); @@ -195,23 +212,23 @@ static void band_supported(struct afb_req request) } } if(!valid) { - afb_req_fail(request, "failed", "Invalid band"); + afb_req_reply(request, NULL, "failed", "Invalid band"); return; } ret_json = json_object_new_object(); json_object_object_add(ret_json, "supported", json_object_new_int(radio_impl_ops->band_supported(band))); - afb_req_success(request, ret_json, NULL); + afb_req_reply(request, ret_json, NULL, NULL); } /* * @brief Get frequency range for a band * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void frequency_range(struct afb_req request) +static void frequency_range(afb_req_t request) { json_object *ret_json; const char *value = afb_req_value(request, "band"); @@ -243,7 +260,7 @@ static void frequency_range(struct afb_req request) } } if(!valid) { - afb_req_fail(request, "failed", "Invalid band"); + afb_req_reply(request, NULL, "failed", "Invalid band"); return; } ret_json = json_object_new_object(); @@ -251,16 +268,16 @@ static void frequency_range(struct afb_req request) max_frequency = radio_impl_ops->get_max_frequency(band); json_object_object_add(ret_json, "min", json_object_new_int((int32_t) min_frequency)); json_object_object_add(ret_json, "max", json_object_new_int((int32_t) max_frequency)); - afb_req_success(request, ret_json, NULL); + afb_req_reply(request, ret_json, NULL, NULL); } /* * @brief Get frequency step size (Hz) for a band * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void frequency_step(struct afb_req request) +static void frequency_step(afb_req_t request) { json_object *ret_json; const char *value = afb_req_value(request, "band"); @@ -291,26 +308,27 @@ static void frequency_step(struct afb_req request) } } if(!valid) { - afb_req_fail(request, "failed", "Invalid band"); + afb_req_reply(request, NULL, "failed", "Invalid band"); return; } ret_json = json_object_new_object(); step = radio_impl_ops->get_frequency_step(band); json_object_object_add(ret_json, "step", json_object_new_int((int32_t) step)); - afb_req_success(request, ret_json, NULL); + afb_req_reply(request, ret_json, NULL, NULL); } /* * @brief Start radio playback * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void start(struct afb_req request) +static void start(afb_req_t request) { radio_impl_ops->set_output(NULL); radio_impl_ops->start(); - afb_req_success(request, NULL, NULL); + playing = true; + afb_req_reply(request, NULL, NULL, NULL); json_object *jresp = json_object_new_object(); json_object *value = json_object_new_string("playing"); @@ -321,13 +339,14 @@ static void start(struct afb_req request) /* * @brief Stop radio playback * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void stop(struct afb_req request) +static void stop(afb_req_t request) { radio_impl_ops->stop(); - afb_req_success(request, NULL, NULL); + playing = false; + afb_req_reply(request, NULL, NULL, NULL); json_object *jresp = json_object_new_object(); json_object *value = json_object_new_string("stopped"); @@ -338,10 +357,10 @@ static void stop(struct afb_req request) /* * @brief Scan for a station in the specified direction * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void scan_start(struct afb_req request) +static void scan_start(afb_req_t request) { const char *value = afb_req_value(request, "direction"); int valid = 0; @@ -370,32 +389,32 @@ static void scan_start(struct afb_req request) } } if(!valid) { - afb_req_fail(request, "failed", "Invalid direction"); + afb_req_reply(request, NULL, "failed", "Invalid direction"); return; } radio_impl_ops->scan_start(direction, scan_callback, NULL); - afb_req_success(request, NULL, NULL); + afb_req_reply(request, NULL, NULL, NULL); } /* * @brief Stop station scan * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void scan_stop(struct afb_req request) +static void scan_stop(afb_req_t request) { radio_impl_ops->scan_stop(); - afb_req_success(request, NULL, NULL); + afb_req_reply(request, NULL, NULL, NULL); } /* * @brief Get (and optionally set) stereo mode * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void stereo_mode(struct afb_req request) +static void stereo_mode(afb_req_t request) { json_object *ret_json; const char *value = afb_req_value(request, "value"); @@ -426,7 +445,7 @@ static void stereo_mode(struct afb_req request) if(valid) { radio_impl_ops->set_stereo_mode(mode); } else { - afb_req_fail(request, "failed", "Invalid mode"); + afb_req_reply(request, NULL, "failed", "Invalid mode"); return; } } @@ -434,16 +453,16 @@ static void stereo_mode(struct afb_req request) mode = radio_impl_ops->get_stereo_mode(); json_object_object_add(ret_json, "mode", json_object_new_string(mode == MONO ? "mono" : "stereo")); - afb_req_success(request, ret_json, NULL); + afb_req_reply(request, ret_json, NULL, NULL); } /* * @brief Subscribe for an event * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void subscribe(struct afb_req request) +static void subscribe(afb_req_t request) { const char *value = afb_req_value(request, "value"); if(value) { @@ -453,21 +472,24 @@ static void subscribe(struct afb_req request) afb_req_subscribe(request, scan_event); } else if(!strcasecmp(value, "status")) { afb_req_subscribe(request, status_event); - } else { - afb_req_fail(request, "failed", "Invalid event"); + } else if(!strcasecmp(value, "rds")) { + afb_req_subscribe(request, rds_event); + } + else { + afb_req_reply(request, NULL, "failed", "Invalid event"); return; } } - afb_req_success(request, NULL, NULL); + afb_req_reply(request, NULL, NULL, NULL); } /* * @brief Unsubscribe for an event * - * @param struct afb_req : an afb request structure + * @param afb_req_t : an afb request structure * */ -static void unsubscribe(struct afb_req request) +static void unsubscribe(afb_req_t request) { const char *value = afb_req_value(request, "value"); if(value) { @@ -477,15 +499,19 @@ static void unsubscribe(struct afb_req request) afb_req_unsubscribe(request, scan_event); } else if(!strcasecmp(value, "status")) { afb_req_unsubscribe(request, status_event); + + } else if(!strcasecmp(value, "rds")) { + afb_req_unsubscribe(request, rds_event); + } else { - afb_req_fail(request, "failed", "Invalid event"); + afb_req_reply(request, NULL, "failed", "Invalid event"); return; } } - afb_req_success(request, NULL, NULL); + afb_req_reply(request, NULL, NULL, NULL); } -static const struct afb_verb_v2 verbs[]= { +static const afb_verb_t verbs[]= { { .verb = "frequency", .session = AFB_SESSION_NONE, .callback = frequency, .info = "Get/Set frequency" }, { .verb = "band", .session = AFB_SESSION_NONE, .callback = band, .info = "Get/Set band" }, { .verb = "rds", .session = AFB_SESSION_NONE, .callback = rds, .info = "Get RDS information" }, @@ -502,7 +528,46 @@ static const struct afb_verb_v2 verbs[]= { { } }; -static int init() +static void onevent(afb_api_t api, const char *event, struct json_object *object) +{ + json_object *tmp = NULL; + const char *uid; + const char *value; + + json_object_object_get_ex(object, "uid", &tmp); + if (tmp == NULL) + return; + + uid = json_object_get_string(tmp); + if (strncmp(uid, "event.media.", 12)) + return; + + if (!playing || + (radio_impl_ops->get_corking_state && + radio_impl_ops->get_corking_state())) { + return; + } + + json_object_object_get_ex(object, "value", &tmp); + if (tmp == NULL) + return; + + value = json_object_get_string(tmp); + if (strncmp(value, "true", 4)) + return; + + if (!strcmp(uid, "event.media.next")) { + radio_impl_ops->scan_start(SCAN_FORWARD, scan_callback, NULL); + } else if (!strcmp(uid, "event.media.previous")) { + radio_impl_ops->scan_start(SCAN_BACKWARD, scan_callback, NULL); + } else if (!strcmp(uid, "event.media.mode")) { + // Do nothing ATM + } else { + AFB_WARNING("Unhandled signal-composer uid '%s'", uid); + } +} + +static int init(afb_api_t api) { // Look for RTL-SDR USB adapter radio_impl_ops = &rtlsdr_impl_ops; @@ -512,27 +577,62 @@ static int init() radio_impl_ops = &kf_impl_ops; rc = radio_impl_ops->init(); } + if(rc != 0) { + radio_impl_ops = &tef665x_impl_ops; + rc = radio_impl_ops->init(); + } + if (rc != 0) { + radio_impl_ops = &null_impl_ops; + rc = radio_impl_ops->init(); + } if (rc != 0) { - AFB_ERROR("No radio device found, exiting"); + // We don't expect the null implementation to fail init, but just in case... + AFB_API_ERROR(afbBindingV3root, "No radio device found, exiting"); } if(rc == 0) { - AFB_NOTICE("%s found\n", radio_impl_ops->name); + AFB_API_NOTICE(afbBindingV3root, "%s found\n", radio_impl_ops->name); radio_impl_ops->set_frequency_callback(freq_callback, NULL); + if(radio_impl_ops->set_rds_callback) + { + radio_impl_ops->set_rds_callback(rds_callback); + } } else { return rc; } + rc = afb_daemon_require_api("signal-composer", 1); + if (rc) { + AFB_WARNING("unable to initialize signal-composer binding"); + } else { + const char **tmp = signalcomposer_events; + json_object *args = json_object_new_object(); + json_object *signals = json_object_new_array(); + + while (*tmp) { + json_object_array_add(signals, json_object_new_string(*tmp++)); + } + json_object_object_add(args, "signal", signals); + if(json_object_array_length(signals)) { + afb_api_call_sync(api, "signal-composer", "subscribe", + args, NULL, NULL, NULL); + } else { + json_object_put(args); + } + } + // Initialize event structures freq_event = afb_daemon_make_event("frequency"); scan_event = afb_daemon_make_event("station_found"); status_event = afb_daemon_make_event("status"); - + rds_event = afb_daemon_make_event("rds"); return 0; } -const struct afb_binding_v2 afbBindingV2 = { +const afb_binding_t afbBindingExport = { .info = "radio service", .api = "radio", + .specification = "Radio API", .verbs = verbs, + .onevent = onevent, .init = init, }; diff --git a/binding/radio_impl.h b/binding/radio_impl.h index 0216f69..2867d7f 100644 --- a/binding/radio_impl.h +++ b/binding/radio_impl.h @@ -33,6 +33,8 @@ typedef void (*radio_scan_callback_t)(uint32_t frequency, void *data); typedef void (*radio_freq_callback_t)(uint32_t frequency, void *data); +typedef void (*radio_rds_callback_t)(void *rds_data); + typedef enum { MONO = 0, STEREO @@ -52,6 +54,8 @@ typedef struct { void (*set_frequency_callback)(radio_freq_callback_t callback, void *data); + void (*set_rds_callback)(radio_rds_callback_t callback); + radio_band_t (*get_band)(void); void (*set_band)(radio_band_t band); @@ -64,6 +68,8 @@ typedef struct { uint32_t (*get_frequency_step)(radio_band_t band); + bool (*get_corking_state)(void); + void (*start)(void); void (*stop)(void); diff --git a/binding/radio_impl_kingfisher.c b/binding/radio_impl_kingfisher.c index dcd3077..dc59916 100644 --- a/binding/radio_impl_kingfisher.c +++ b/binding/radio_impl_kingfisher.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017,2018 Konsulko Group + * Copyright (C) 2017-2019 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,17 +23,23 @@ #include <fcntl.h> #include <sys/stat.h> #include <json-c/json.h> +#include <gst/gst.h> #include <afb/afb-binding.h> #include "radio_impl.h" #define SI_NODE "/sys/firmware/devicetree/base/si468x@0/compatible" -#define SI_INIT "/usr/bin/si_init" #define SI_CTL "/usr/bin/si_ctl" #define SI_CTL_CMDLINE_MAXLEN 128 #define SI_CTL_OUTPUT_MAXLEN 128 +#define GST_PIPELINE_LEN 256 + +// Flag to enable using GST_STATE_READY instead of GST_STATE_PAUSED to trigger +// Wireplumber policy mechanism. Hopefully temporary. +#define WIREPLUMBER_WORKAROUND + // Structure to describe FM band plans, all values in Hz. typedef struct { char *name; @@ -51,6 +57,7 @@ static fm_band_plan_t known_fm_band_plans[5] = { }; static unsigned int bandplan = 0; +static bool corking = false; static bool present = false; static uint32_t current_frequency; static int scan_valid_snr_threshold = 128; @@ -58,7 +65,7 @@ static int scan_valid_rssi_threshold = 128; static bool scanning = false; // stream state - +static GstElement *pipeline; static bool running; static void (*freq_callback)(uint32_t, void*); @@ -67,6 +74,36 @@ static void *freq_callback_data; static uint32_t kf_get_min_frequency(radio_band_t band); static void kf_scan_stop(void); +static gboolean handle_message(GstBus *bus, GstMessage *msg, __attribute__((unused)) void *ptr) +{ + GstState state; + + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_REQUEST_STATE) { + gst_message_parse_request_state(msg, &state); + + if (state == GST_STATE_PAUSED) { + corking = true; + + // NOTE: Explicitly using PAUSED here, this case currently + // is separate from the general PAUSED/READY issue wrt + // Wireplumber policy. + gst_element_set_state(pipeline, GST_STATE_PAUSED); + } else if (state == GST_STATE_PLAYING) { + corking = false; + + gst_element_set_state(pipeline, GST_STATE_PLAYING); + } + } + + return TRUE; +} + +static void *gstreamer_loop_thread(void *ptr) +{ + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return NULL; +} + static int kf_init(void) { GKeyFile* conf_file; @@ -75,6 +112,8 @@ static int kf_init(void) char *value_str; char cmd[SI_CTL_CMDLINE_MAXLEN]; int rc; + char gst_pipeline_str[GST_PIPELINE_LEN]; + pthread_t thread_id; if(present) return 0; @@ -83,9 +122,7 @@ static int kf_init(void) if(stat(SI_NODE, &statbuf) != 0) return -1; - // Check for Cogent's si_init script and si_ctl utility - if(stat(SI_INIT, &statbuf) != 0) - return -1; + // Check for Cogent's si_ctl utility if(stat(SI_CTL, &statbuf) != 0) return -1; @@ -129,7 +166,7 @@ static int kf_init(void) "scan_valid_snr_threshold", &error); if(!error) { - AFB_INFO("Scan valid SNR level set to %d", n); + AFB_API_INFO(afbBindingV3root, "Scan valid SNR level set to %d", n); scan_valid_snr_threshold = n; } @@ -139,20 +176,14 @@ static int kf_init(void) "scan_valid_rssi_threshold", &error); if(!error) { - AFB_INFO("Scan valid SNR level set to %d", n); + AFB_API_INFO(afbBindingV3root, "Scan valid SNR level set to %d", n); scan_valid_rssi_threshold = n; } g_key_file_free(conf_file); } - rc = system(SI_INIT); - if(rc != 0) { - AFB_ERROR("si_init failed, rc = %d", rc); - return -1; - } - - AFB_INFO("Using FM Bandplan: %s", known_fm_band_plans[bandplan].name); + AFB_API_INFO(afbBindingV3root, "Using FM Bandplan: %s", known_fm_band_plans[bandplan].name); current_frequency = kf_get_min_frequency(BAND_FM); snprintf(cmd, sizeof(cmd), @@ -164,10 +195,40 @@ static int kf_init(void) current_frequency / 1000); rc = system(cmd); if(rc != 0) { - AFB_ERROR("%s failed, rc = %d", SI_CTL, rc); + AFB_API_ERROR(afbBindingV3root, "%s failed, rc = %d", SI_CTL, rc); return -1; } + // Initialize GStreamer + gst_init(NULL, NULL); + + // Use PipeWire output + rc = snprintf(gst_pipeline_str, + GST_PIPELINE_LEN, + "alsasrc device=hw:radio ! queue ! audioconvert ! audioresample ! pwaudiosink stream-properties=\"p,media.role=Multimedia\""); + if(rc >= GST_PIPELINE_LEN) { + AFB_API_ERROR(afbBindingV3root, "pipeline string too long"); + return -1; + } + pipeline = gst_parse_launch(gst_pipeline_str, NULL); + if(!pipeline) { + AFB_API_ERROR(afbBindingV3root, "pipeline construction failed!"); + return -1; + } + + // Start pipeline in paused state +#ifdef WIREPLUMBER_WORKAROUND + gst_element_set_state(pipeline, GST_STATE_READY); +#else + gst_element_set_state(pipeline, GST_STATE_PAUSED); +#endif + + gst_bus_add_watch(gst_element_get_bus(pipeline), (GstBusFunc) handle_message, NULL); + + rc = pthread_create(&thread_id, NULL, gstreamer_loop_thread, NULL); + if(rc != 0) + return rc; + present = true; return 0; } @@ -295,30 +356,54 @@ static uint32_t kf_get_frequency_step(radio_band_t band) return ret; } +static bool kf_get_corking_state(void) +{ + return corking; +} + static void kf_start(void) { if(!present) return; - if (running) - return; - - /* TODO: start the stream in pipewire */ - - running = true; + if(!running || corking) { + // Start pipeline + gst_element_set_state(pipeline, GST_STATE_PLAYING); + running = true; + corking = false; + } } static void kf_stop(void) { - if (!present) - return; - - if (!running) - return; - - /* TODO: stop the stream in pipewire */ - - running = false; + GstEvent *event; + + if(present && running) { + // Stop pipeline + running = false; + +#ifdef WIREPLUMBER_WORKAROUND + // NOTE: Using NULL here instead of READY, as it seems to trigger + // some odd behavior in the pipeline; alsasrc does not seem to + // stop, and things get hung up on restart as there are a bunch + // of "old" samples that seemingly confuse pwaudiosink. Going + // to NULL state seems to tear down things enough to avoid + // whatever happens. + gst_element_set_state(pipeline, GST_STATE_NULL); +#else + gst_element_set_state(pipeline, GST_STATE_PAUSED); +#endif + corking = false; + +#ifndef WIREPLUMBER_WORKAROUND + // Flush pipeline + // This seems required to avoid stutters on starts after a stop + event = gst_event_new_flush_start(); + gst_element_send_event(GST_ELEMENT(pipeline), event); + event = gst_event_new_flush_stop(TRUE); + gst_element_send_event(GST_ELEMENT(pipeline), event); +#endif + } } static void kf_scan_start(radio_scan_direction_t direction, @@ -339,19 +424,19 @@ static void kf_scan_start(radio_scan_direction_t direction, scanning = true; snprintf(cmd, - SI_CTL_CMDLINE_MAXLEN, - "%s /dev/i2c-12 0x65 -l %s", - SI_CTL, direction == SCAN_FORWARD ? "up" : "down"); + SI_CTL_CMDLINE_MAXLEN, + "%s /dev/i2c-12 0x65 -l %s", + SI_CTL, direction == SCAN_FORWARD ? "up" : "down"); fp = popen(cmd, "r"); if(fp == NULL) { - AFB_ERROR("Could not run: %s!", cmd); + AFB_API_ERROR(afbBindingV3root, "Could not run: %s!", cmd); return; } // Look for "Frequency:" in output while(fgets(line, SI_CTL_OUTPUT_MAXLEN, fp) != NULL) { if(strncmp("Frequency:", line, 10) == 0) { new_frequency = atoi(line + 10); - //AFB_DEBUG("%s: got new_frequency = %d", __FUNCTION__, new_frequency); + //AFB_API_DEBUG(afbBindingV3root, "%s: got new_frequency = %d", __FUNCTION__, new_frequency); break; } } @@ -413,6 +498,7 @@ radio_impl_ops_t kf_impl_ops = { .get_min_frequency = kf_get_min_frequency, .get_max_frequency = kf_get_max_frequency, .get_frequency_step = kf_get_frequency_step, + .get_corking_state = kf_get_corking_state, .start = kf_start, .stop = kf_stop, .scan_start = kf_scan_start, diff --git a/binding/radio_impl_null.c b/binding/radio_impl_null.c new file mode 100644 index 0000000..8c28bca --- /dev/null +++ b/binding/radio_impl_null.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2017,2018,2020 Konsulko Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: Some locking around frequency and scanning state may be + * required if this null implementation ever needs to be used + * beyond basic functionality testing. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> +#include <sys/stat.h> +#include <errno.h> +#include <glib.h> +#include <afb/afb-binding.h> + +#include "radio_impl.h" + +// Structure to describe FM band plans, all values in Hz. +typedef struct { + char *name; + uint32_t min; + uint32_t max; + uint32_t step; +} fm_band_plan_t; + +static fm_band_plan_t known_fm_band_plans[5] = { + { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 }, + { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 }, + { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 }, + { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 }, + { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 } +}; + +static unsigned int bandplan; +static bool present; +static bool active; +static bool scanning; +static uint32_t current_frequency; +static radio_freq_callback_t freq_callback; +static void *freq_callback_data; + +static uint32_t null_get_min_frequency(radio_band_t band); +static void null_set_frequency(uint32_t frequency); +//static void null_scan_stop(void); + +static int null_init(void) +{ + GKeyFile *conf_file; + char *value_str; + char *rootdir; + char *helper_path; + + if(present) + return 0; + + // Load settings from configuration file if it exists + conf_file = g_key_file_new(); + if(conf_file && + g_key_file_load_from_dirs(conf_file, + "AGL.conf", + (const gchar**) g_get_system_config_dirs(), + NULL, + G_KEY_FILE_KEEP_COMMENTS, + NULL) == TRUE) { + + // Set band plan if it is specified + value_str = g_key_file_get_string(conf_file, + "radio", + "fmbandplan", + NULL); + if(value_str) { + unsigned int i; + for(i = 0; + i < sizeof(known_fm_band_plans) / sizeof(fm_band_plan_t); + i++) { + if(!strcasecmp(value_str, known_fm_band_plans[i].name)) { + bandplan = i; + break; + } + } + } + } + + // Start off with minimum bandplan frequency + current_frequency = null_get_min_frequency(BAND_FM); + + present = true; + null_set_frequency(current_frequency); + + return 0; +} + +static void null_set_output(const char *output) +{ +} + +static uint32_t null_get_frequency(void) +{ + return current_frequency; +} + +static void null_set_frequency(uint32_t frequency) +{ + current_frequency = frequency; + + if(freq_callback) + freq_callback(current_frequency, freq_callback_data); +} + +static void null_set_frequency_callback(radio_freq_callback_t callback, + void *data) +{ + freq_callback = callback; + freq_callback_data = data; +} + +static radio_band_t null_get_band(void) +{ + // We only support FM + return BAND_FM; +} + +static void null_set_band(radio_band_t band) +{ + // We only support FM, so do nothing +} + +static int null_band_supported(radio_band_t band) +{ + if(band == BAND_FM) + return 1; + return 0; +} + +static uint32_t null_get_min_frequency(radio_band_t band) +{ + return known_fm_band_plans[bandplan].min; +} + +static uint32_t null_get_max_frequency(radio_band_t band) +{ + return known_fm_band_plans[bandplan].max; +} + +static uint32_t null_get_frequency_step(radio_band_t band) +{ + uint32_t ret = 0; + + switch (band) { + case BAND_AM: + ret = 1000; // 1 kHz + break; + case BAND_FM: + ret = known_fm_band_plans[bandplan].step; + break; + default: + break; + } + return ret; +} + +static void null_start(void) +{ + if(!present) + return; + + if(active) + return; + + active = true; +} + +static void null_stop(void) +{ + if(!present) + return; + + if (!active) + return; + + active = false; +} + +static void null_scan_start(radio_scan_direction_t direction, + radio_scan_callback_t callback, + void *data) +{ + int frequency; + + if(!active || scanning) + return; + + scanning = true; + + // Just go to the next frequency step up or down + frequency = current_frequency; + if(direction == SCAN_FORWARD) { + frequency += known_fm_band_plans[bandplan].step; + } else { + frequency -= known_fm_band_plans[bandplan].step; + } + if(frequency < known_fm_band_plans[bandplan].min) + frequency = known_fm_band_plans[bandplan].max; + else if(frequency > known_fm_band_plans[bandplan].max) + frequency = known_fm_band_plans[bandplan].min; + + scanning = false; + null_set_frequency(frequency); + if(callback) + callback(frequency, data); +} + +static void null_scan_stop(void) +{ + scanning = false; +} + +static radio_stereo_mode_t null_get_stereo_mode(void) +{ + // We only support stereo + return STEREO; +} + +static void null_set_stereo_mode(radio_stereo_mode_t mode) +{ + // We only support stereo, so do nothing +} + +radio_impl_ops_t null_impl_ops = { + .name = "null/mock radio", + .init = null_init, + .set_output = null_set_output, + .get_frequency = null_get_frequency, + .set_frequency = null_set_frequency, + .set_frequency_callback = null_set_frequency_callback, + .get_band = null_get_band, + .set_band = null_set_band, + .band_supported = null_band_supported, + .get_min_frequency = null_get_min_frequency, + .get_max_frequency = null_get_max_frequency, + .get_frequency_step = null_get_frequency_step, + .start = null_start, + .stop = null_stop, + .scan_start = null_scan_start, + .scan_stop = null_scan_stop, + .get_stereo_mode = null_get_stereo_mode, + .set_stereo_mode = null_set_stereo_mode +}; diff --git a/binding/radio_impl_null.h b/binding/radio_impl_null.h new file mode 100644 index 0000000..16bd5e6 --- /dev/null +++ b/binding/radio_impl_null.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 Konsulko Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _RADIO_IMPL_NULL_H +#define _RADIO_IMPL_NULL_H + +#include "radio_impl.h" + +extern radio_impl_ops_t null_impl_ops; + +#endif /* _RADIO_IMPL_NULL_H */ + diff --git a/binding/radio_impl_rtlsdr.c b/binding/radio_impl_rtlsdr.c index 044da0e..62ec623 100644 --- a/binding/radio_impl_rtlsdr.c +++ b/binding/radio_impl_rtlsdr.c @@ -178,7 +178,7 @@ static int rtlsdr_init(void) if(!helper_path) return -ENOMEM; if(snprintf(helper_path, HELPER_MAX, "%s/bin/%s --detect", rootdir, HELPER_NAME) == HELPER_MAX) { - AFB_ERROR("Could not create command for \"%s --detect\"", HELPER_NAME); + AFB_API_ERROR(afbBindingV3root, "Could not create command for \"%s --detect\"", HELPER_NAME); return -EINVAL; } if(system(helper_path) != 0) { @@ -316,7 +316,7 @@ static int rtlsdr_start_helper(void) if(helper_output) { // Indicate desired output to helper - AFB_INFO("Setting RADIO_OUTPUT=%s", helper_output); + AFB_API_INFO(afbBindingV3root, "Setting RADIO_OUTPUT=%s", helper_output); setenv("RADIO_OUTPUT", helper_output, 1); } @@ -325,15 +325,15 @@ static int rtlsdr_start_helper(void) if(!helper_path) return -ENOMEM; if(snprintf(helper_path, PATH_MAX, "%s/bin/%s", rootdir, HELPER_NAME) == PATH_MAX) { - AFB_ERROR("Could not create path to %s", HELPER_NAME); + AFB_API_ERROR(afbBindingV3root, "Could not create path to %s", HELPER_NAME); return -EINVAL; } helper_pid = popen2(helper_path, &helper_out, &helper_in); if(helper_pid < 0) { - AFB_ERROR("Could not run %s!", HELPER_NAME); + AFB_API_ERROR(afbBindingV3root, "Could not run %s!", HELPER_NAME); return -1; } - AFB_DEBUG("%s started", HELPER_NAME); + AFB_API_DEBUG(afbBindingV3root, "%s started", HELPER_NAME); helper_started = true; free(helper_path); @@ -359,7 +359,7 @@ static void rtlsdr_start(void) rc = write(helper_in, cmd, strlen(cmd)); pthread_mutex_unlock(&helper_mutex); if (rc < 0) { - AFB_ERROR("Failed to ask \"%s\" to start", HELPER_NAME); + AFB_API_ERROR(afbBindingV3root, "Failed to ask \"%s\" to start", HELPER_NAME); return; } active = true; @@ -381,7 +381,7 @@ static void rtlsdr_stop(void) rc = write(helper_in, cmd, strlen(cmd)); pthread_mutex_unlock(&helper_mutex); if (rc < 0) { - AFB_ERROR("Failed to ask \"%s\" to stop", HELPER_NAME); + AFB_API_ERROR(afbBindingV3root, "Failed to ask \"%s\" to stop", HELPER_NAME); return; } active = false; @@ -454,7 +454,7 @@ static void rtlsdr_scan_stop(void) rc = write(helper_in, cmd, strlen(cmd)); pthread_mutex_unlock(&helper_mutex); if (rc < 0) - AFB_ERROR("Failed to ask \"%s\" to stop scan", HELPER_NAME); + AFB_API_ERROR(afbBindingV3root, "Failed to ask \"%s\" to stop scan", HELPER_NAME); } static radio_stereo_mode_t rtlsdr_get_stereo_mode(void) diff --git a/binding/radio_impl_tef665x.c b/binding/radio_impl_tef665x.c new file mode 100644 index 0000000..17454b8 --- /dev/null +++ b/binding/radio_impl_tef665x.c @@ -0,0 +1,2415 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + TODO: + at this point: + - complete the set stereo_mode verb. + - find a way to tell the service which i2c chanel is used. + - separate the functions of driver from the verbs by creating new c file. + - find a way of monitoring the quality of tuning and correct it time by time. + - use Interupt for getting RDS data +*/ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <glib.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +#include <stdarg.h> +#include <error.h> +#include <gst/gst.h> +#include <time.h> +//#include <json-c/json.h> +//#include <gst/gst.h> + +#include <afb/afb-binding.h> + +#include <pthread.h> +#include "radio_impl.h" +#include "tef665x.h" + + +#define I2C_ADDRESS 0x64 +#define I2C_DEV "/dev/i2c-3" +#define VERSION "0.1" + +#define TEF665x_CMD_LEN_MAX 20 +#define SET_SUCCESS 1 +#define TEF665X_SPLIT_SIZE 24 + +#define TEF665x_REF_CLK 9216000 //reference clock frequency +#define TEF665x_IS_CRYSTAL_CLK 0 //crstal +#define TEF665x_IS_EXT_CLK 1 //external clock input + +#define High_16bto8b(a) ((uint8_t)((a) >> 8)) +#define Low_16bto8b(a) ((uint8_t)(a)) +#define Convert8bto16b(a) ((uint16_t)(((uint16_t)(*(a))) << 8 |((uint16_t)(*(a+1))))) + +#define GST_PIPELINE_LEN 256 + +const uint8_t tef665x_patch_cmdTab1[] = {3, 0x1c,0x00,0x00}; +const uint8_t tef665x_patch_cmdTab2[] = {3, 0x1c,0x00,0x74}; +const uint8_t tef665x_patch_cmdTab3[] = {3, 0x1c,0x00,0x75}; + + +typedef struct { + char *name; + uint32_t min; + uint32_t max; + uint32_t step; +} band_plan_t; + +typedef struct +{ + int16_t level; + uint16_t usn; + uint16_t wam; + int16_t offset; + uint16_t bandw; +}sig_quality_t; + +typedef struct{ + radio_scan_callback_t callback; + radio_scan_direction_t direction; + void* data; +}scan_data_t; +typedef struct rds_data +{ + bool Text_Changed; + bool TrafficAnnouncement; + bool TrafficProgram; + bool Music_Speech; + + uint32_t Alternative_Freq[25]; + uint8_t Alternative_Freq_Counter; + uint8_t Num_AlterFreq; + + uint16_t PICode; + uint8_t DI_Seg; + uint8_t PTY_Code; + + uint8_t Year; + uint8_t Month; + uint8_t Day; + uint8_t Hour; + uint8_t Min; + + uint8_t PTYN_Size; + uint8_t raw_data[12]; + + char PS_Name[16]; + char RT[128]; + char PTYN[16]; +} rds_data_t; + +//thread for handling RDS and Mutex +pthread_t rds_thread; +rds_data_t RDS_Message; +pthread_mutex_t RDS_Mutex; + + +//Threads for handling Scan +pthread_t scan_thread; + +pthread_mutex_t scan_mutex; + +char _Temp[64]={0}; + +static band_plan_t known_fm_band_plans[5] = { + { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 }, + { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 }, + { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 }, + { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 }, + { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 } +}; +static band_plan_t known_am_band_plans[1] = { + { .name = "W-ASIA", .min = 522000, .max = 1620000, .step = 9000 } +}; + +static unsigned int fm_bandplan = 2; +static unsigned int am_bandplan = 0; +static bool corking = false; +static bool present = false; +static bool scanning = false; + +// stream state +static GstElement *pipeline; +static bool running; + + +uint32_t AlterFreqOffset=0; + +static void (*freq_callback)(uint32_t, void*); +static void (*rds_callback) (void*); +static void *freq_callback_data; + +void Get_quality_status (sig_quality_t *); +int tef665x_set_rds (uint32_t i2c_file_desc); +#define DEBUG 0 + +#if DEBUG == 1 +#define _debug(x, y) printf("function: %s, %s : %d\n", __FUNCTION__, #x, y) +#else +#define _debug(x, y) +#endif + +static uint32_t file_desc; + +static radio_band_t current_band; +static uint32_t current_am_frequency; +static uint32_t current_fm_frequency; + +static void tef665x_scan_stop (void); +static void tef665x_set_frequency (uint32_t); +static void tef665x_search_frequency (uint32_t); + +static uint32_t tef665x_get_min_frequency (radio_band_t); +static uint32_t tef665x_get_max_frequency (radio_band_t); +static uint32_t tef665x_get_frequency_step (radio_band_t); + +static gboolean handle_message(GstBus *bus, GstMessage *msg, __attribute__((unused)) void *ptr) +{ + GstState state; + + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_REQUEST_STATE) { + + gst_message_parse_request_state(msg, &state); + + if (state == GST_STATE_PAUSED) + corking = true; + else if (state == GST_STATE_PLAYING) + corking = false; + + } + + return TRUE; +} + +static int tef665x_set_cmd(int i2c_file_desc, TEF665x_MODULE module, uint8_t cmd, int len, ...) +{ + int i, ret; + uint8_t buf[TEF665x_CMD_LEN_MAX]; + uint16_t temp; + va_list vArgs; + + va_start(vArgs, len); + + buf[0] = module; //module, FM/AM/APP + buf[1] = cmd; //cmd, 1,2,10,... + buf[2] = 0x01; //index, always 1 + + for(i = 3; i < len; i++) + { + temp = va_arg(vArgs,int); + + buf[i++] = High_16bto8b(temp); + buf[i] = Low_16bto8b(temp); + } + + va_end(vArgs); + + ret = write(i2c_file_desc, buf, len); + + temp = (ret == len) ? 1 : 0; + _debug("return value", temp); + return temp; +} + +static int tef665x_get_cmd(int i2c_file_desc, TEF665x_MODULE module, uint8_t cmd, uint8_t *receive, int len) +{ + uint8_t temp; + uint8_t buf[3]; + int ret; + + buf[0]= module; //module, FM/AM/APP + buf[1]= cmd; //cmd, 1,2,10,... + buf[2]= 1; //index, always 1 + + write(i2c_file_desc, buf, 3); + + ret = read(i2c_file_desc, receive, len); + temp = (ret == len) ? 1 : 0; + _debug("return value", temp); + if(temp==0) + AFB_ERROR("Error Number: %d: %s",errno,strerror(errno)); + return temp; +} + +/* +module 64 APPL +cmd 128 Get_Operation_Status | status +index +1 status + Device operation status + 0 = boot state; no command support + 1 = idle state + 2 = active state; radio standby + 3 = active state; FM + 4 = active state; AM +*/ +static int appl_get_operation_status(int i2c_file_desc ,uint8_t *status) +{ + uint8_t buf[2]; + int ret; + + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_APPL, + TEF665X_Cmd_Get_Operation_Status, + buf, sizeof(buf)); + + if(ret == SET_SUCCESS) + { + *status = Convert8bto16b(buf); + _debug("return value", 1); + return 1; + } + _debug("return value", 0); + return 0; +} + +static int get_operation_status(int i2c_file_desc, TEF665x_STATE *status) +{ + TEF665x_STATE data; + int ret; + if(SET_SUCCESS ==(ret = appl_get_operation_status(i2c_file_desc, &data))) + { + //printk( "appl_get_operation_status1 data= %d \n",data); + _debug("got status", ret); + switch(data) + { + case 0: + _debug("status: boot", ret); + *status = eDevTEF665x_Boot_state; + break; + case 1: + _debug("status: idle", ret); + *status = eDevTEF665x_Idle_state; + break; + default: + _debug("status: active", ret); + *status = eDevTEF665x_Active_state; + break; + } + } + return ret; +} + +static int tef665x_power_on(int i2c_file_desc) +{ + int ret; + TEF665x_STATE status; + usleep(5000); + if(SET_SUCCESS == (ret = get_operation_status(i2c_file_desc, &status))) //[ w 40 80 01 [ r 0000 ] + { + _debug("Powered ON", ret); + } + else + { + _debug("Powered ON FAILED!", ret); + } + + return ret; +} + +static int tef665x_writeTab(int i2c_file_desc,const uint8_t *tab) +{ + int ret; + ret = write(i2c_file_desc, tab + 1, tab[0]); + return (ret != tab[0]) ? 0 : 1; +} + +static int tef665x_patch_load(int i2c_file_desc, const uint8_t *bytes, uint16_t size) +{ + uint8_t buf[25]; //the size which we break the data into, is 24 bytes. + int ret, i; + + uint16_t num = size / 24; + uint16_t rem = size % 24; + + buf[0] = 0x1b; + + usleep(10000); + + for(i = 0; i < num; i++) + { + memcpy(buf + 1, bytes + (24 * i), 24); + + ret = write(i2c_file_desc, buf, 25); + + if(ret != 25) + { + _debug("FAILED, send patch error! in pack no", i); + return false; + } + usleep(50); + } + + memcpy(buf + 1, bytes + (num * 24), rem); + + ret = write(i2c_file_desc, buf, rem); + if(ret != rem) + { + _debug("FAILED, send patch error at the end!", 0); + return false; + } + usleep(50); + + _debug("return value", 1); + return true; +} + +static int tef665x_patch_init(int i2c_file_desc) +{ + int ret = 0; + ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab1); //[ w 1C 0000 ] + if(!ret) + { + _debug("1- tab1 load FAILED", ret); + return ret; + } + + ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab2); //[ w 1C 0074 ] + if(!ret) + { + _debug("2- tab2 load FAILED", ret); + return ret; + } + + ret = tef665x_patch_load(i2c_file_desc, pPatchBytes, patchSize); //table1 + if(!ret) + { + _debug("3- pPatchBytes load FAILED", ret); + return ret; + } + + ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab1); //[ w 1C 0000 ] + if(!ret) + { + _debug("4- tab1 load FAILED", ret); + return ret; + } + + ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab3); //[ w 1C 0075 ] + if(!ret) + { + _debug("5- tab3 load FAILED", ret); + return ret; + } + + ret = tef665x_patch_load(i2c_file_desc, pLutBytes, lutSize); //table2 + if(!ret) + { + _debug("6- pLutBytes load FAILED", ret); + return ret; + } + + ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab1); //[ w 1C 0000 ] + if(!ret) + { + _debug("7- tab1 load FAILED", ret); + return ret; + } + _debug("patch loaded", ret); + return ret; +} + +//Command start will bring the device into? idle state�: [ w 14 0001 ] +static int tef665x_start_cmd(int i2c_file_desc) +{ + + int ret; + unsigned char buf[3]; + + buf[0] = 0x14; + buf[1] = 0; + buf[2] = 1; + + ret = write(i2c_file_desc, buf, 3); + + if (ret != 3) + { + _debug("start cmd FAILED", 0); + return 0; + } + _debug("return true", 1); + return 1; +} + +static int tef665x_boot_state(int i2c_file_desc) +{ + int ret=0; + if(1 == tef665x_patch_init(i2c_file_desc)) + { + _debug("return true", 1); + } + else + { + _debug("return value", 0); + return 0; + } + + usleep(50000); + + if(1 == tef665x_start_cmd(i2c_file_desc)) + { + _debug("'start cmd'return true", 1); + } + else + { + _debug("return value", 0); + return 0; + } + + usleep(50000); + + return ret; +} + +/* +module 64 APPL +cmd 4 Set_ReferenceClock frequency + +index +1 frequency_high + [ 15:0 ] + MSB part of the reference clock frequency + [ 31:16 ] +2 frequency_low + [ 15:0 ] + LSB part of the reference clock frequency + [ 15:0 ] + frequency [*1 Hz] (default = 9216000) +3 type + [ 15:0 ] + clock type + 0 = crystal oscillator operation (default) + 1 = external clock input operation +*/ +static int tef665x_appl_set_referenceClock(uint32_t i2c_file_desc, uint16_t frequency_high, uint16_t frequency_low, uint16_t type) +{ + return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_APPL, + TEF665X_Cmd_Set_ReferenceClock, + 9, + frequency_high, frequency_low, type); +} + +static int appl_set_referenceClock(uint32_t i2c_file_desc, uint32_t frequency, bool is_ext_clk) //0x3d 0x900 +{ + return tef665x_appl_set_referenceClock(i2c_file_desc,(uint16_t)(frequency >> 16), (uint16_t)frequency, is_ext_clk); +} + +/* +module 64 APPL +cmd 5 Activate mode + +index +1 mode + [ 15:0 ] + 1 = goto �active� state with operation mode of �radio standby� +*/ +static int tef665x_appl_activate(uint32_t i2c_file_desc ,uint16_t mode) +{ + return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_APPL, + TEF665X_Cmd_Activate, + 5, + mode); +} + +static int appl_activate(uint32_t i2c_file_desc) +{ + return tef665x_appl_activate(i2c_file_desc, 1); +} +/* +module 48 AUDIO +cmd 22 set_dig_io signal, format, operation, samplerate + +index +1 signal +[ 15:0 ] + digital audio input / output + 32 = I²S digital audio IIS_SD_0 (input) + 33 = I²S digital audio IIS_SD_1 (output) +(2) mode + 0 = off (default) + 1 = input (only available for signal = 32) + 2 = output (only available for signal = 33) +(3) format + [ 15:0 ] + digital audio format select + 16 = I²S 16 bits (fIIS_BCK = 32 * samplerate) + 32 = I²S 32 bits (fIIS_BCK = 64 * samplerate) (default) + 272 = lsb aligned 16 bit (fIIS_BCK = 64 * samplerate) + 274 = lsb aligned 18 bit (fIIS_BCK = 64 * samplerate) + 276 = lsb aligned 20 bit (fIIS_BCK = 64 * samplerate) + 280 = lsb aligned 24 bit (fIIS_BCK = 64 * samplerate) +(4) operation + [ 15:0 ] + operation mode + 0 = slave mode; IIS_BCK and IIS_WS input defined by source (default) + 256 = master mode; IIS_BCK and IIS_WS output defined by device +(5) samplerate + [ 15:0 ] 3200 = 32.0 kHz + 4410 = 44.1 kHz (default) + 4800 = 48.0 kHz +*/ +static int tef665x_audio_set_dig_io(uint8_t i2c_file_desc, uint16_t signal, uint16_t mode, uint16_t format, uint16_t operation, uint16_t samplerate) +{ + int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO, + TEF665X_Cmd_Set_Dig_IO, + 13, + signal, mode, format, operation, samplerate); + if(ret) + { + _debug("Digital In/Out is set ", signal); + } + else + { + _debug("FAILED, return", 0); + return 0; + } + return 1; +} + +/* +module 32 / 33 FM / AM +cmd 85 Set_Specials ana_out, dig_out + +index +1 signal + [ 15:0 ] + analog audio output + 128 = DAC L/R output +2 mode + [ 15:0 ] + output mode + 0 = off (power down) + 1 = output enabled (default) +*/ + +static int tef665x_audio_set_ana_out(uint32_t i2c_file_desc, uint16_t signal,uint16_t mode) +{ + int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO, + TEF665X_Cmd_Set_Ana_Out, + 7, + signal, mode); + if(ret) + { + _debug("analog output is set to ", mode); + } + else + { + _debug("FAILED, return", 0); + return 0; + } + return 1; + +} + +/* +module 48 AUDIO +cmd 13 Set_Output_Source + +index +1 signal + [ 15:0 ] + audio output + 33 = I2S Digital audio + 128 = DAC L/R output (default) +2 source + [ 15:0 ] + source + 4 = analog radio + 32 = i2s digital audio input + 224 = audio processor (default) + 240 = sin wave generator +*/ +static int tef665x_set_output_src(uint32_t i2c_file_desc, uint8_t signal, uint8_t src) +{ + int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO, + TEF665X_Cmd_Set_Output_Source, + 7, + signal, src); + if(ret) + { + _debug("Output is set ", signal); + } + else + { + _debug("FAILED, return", 0); + return 0; + } + return 1; +} + +static int tef665x_idle_state(int i2c_file_desc) +{ + TEF665x_STATE status; + + //mdelay(50); + + if(SET_SUCCESS == get_operation_status(i2c_file_desc, &status)) + { + _debug("got operation status", 1); + if(status != eDevTEF665x_Boot_state) + { + _debug("not in boot status", 1); + + if(SET_SUCCESS == appl_set_referenceClock(i2c_file_desc, TEF665x_REF_CLK, TEF665x_IS_CRYSTAL_CLK)) //TEF665x_IS_EXT_CLK + { + _debug("set the clock", TEF665x_REF_CLK); + if(SET_SUCCESS == appl_activate(i2c_file_desc))// APPL_Activate mode = 1.[ w 40 05 01 0001 ] + { + //usleep(100000); //Wait 100 ms + _debug("activate succeed", 1); + return 1; + } + else + { + _debug("activate FAILED", 1); + } + } + else + { + _debug("set the clock FAILED", TEF665x_REF_CLK); + } + + } + else + { + _debug("did not get operation status", 0); + } + + } + _debug("return value", 0); + return 0; +} + +static int tef665x_para_load(uint32_t i2c_file_desc) +{ + int i; + int r; + const uint8_t *p = init_para; + + for(i = 0; i < sizeof(init_para); i += (p[i]+1)) + { + if(SET_SUCCESS != (r = tef665x_writeTab(i2c_file_desc, p + i))) + { + break; + } + } + + //Initiate RDS + tef665x_set_rds(i2c_file_desc); + + _debug("return value", r); + return r; +} + +/* +module 32 / 33 FM / AM +cmd 1 Tune_To mode, frequency + +index +1 mode + [ 15:0 ] + tuning actions + 0 = no action (radio mode does not change as function of module band) + 1 = Preset Tune to new program with short mute time + 2 = Search Tune to new program and stay muted + FM 3 = AF-Update Tune to alternative frequency, store quality + and tune back with inaudible mute + 4 = Jump Tune to alternative frequency with short + inaudible mute + 5 = Check Tune to alternative frequency and stay + muted + AM 3 � 5 = reserved + 6 = reserved + 7 = End Release the mute of a Search or Check action + (frequency is not required and ignored) +2 frequency +[ 15:0 ] + tuning frequency + FM 6500 � 10800 65.00 � 108.00 MHz / 10 kHz step size + AM LW 144 � 288 144 � 288 kHz / 1 kHz step size + MW 522 � 1710 522 � 1710 kHz / 1 kHz step size + SW 2300 � 27000 2.3 � 27 MHz / 1 kHz step size +*/ +static int tef665x_radio_tune_to (uint32_t i2c_file_desc, bool fm, uint16_t mode,uint16_t frequency ) +{ + return tef665x_set_cmd(i2c_file_desc, fm ? TEF665X_MODULE_FM: TEF665X_MODULE_AM, + TEF665X_Cmd_Tune_To, + ( mode <= 5 ) ? 7 : 5, + mode, frequency); +} + +static int FM_tune_to(uint32_t i2c_file_desc, AR_TuningAction_t mode, uint16_t frequency) +{ + int ret = tef665x_radio_tune_to(i2c_file_desc, 1, (uint16_t)mode, frequency); + _debug("return value", ret); + return ret; +} + +static int AM_tune_to(uint32_t i2c_file_desc, AR_TuningAction_t mode,uint16_t frequency) +{ + int ret = tef665x_radio_tune_to(i2c_file_desc, 0, (uint16_t)mode, frequency); + _debug("return value", ret); + return ret; +} + +/* +module 48 AUDIO +cmd 11 Set_Mute mode + +index +1 mode + [ 15:0 ] + audio mute + 0 = mute disabled + 1 = mute active (default) +*/ +int tef665x_audio_set_mute(uint32_t i2c_file_desc, uint16_t mode) +{ + int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO, + TEF665X_Cmd_Set_Mute, + 5, + mode); + if(ret) + { + _debug("mute state changed , mode", mode); + } + else + { + _debug("FAILED, return", 0); + return 0; + } + return 1; +} + +/* +module 48 AUDIO +cmd 10 Set_Volume volume + +index +1 volume + [ 15:0 ] (signed) + audio volume + -599 � +240 = -60 � +24 dB volume + 0 = 0 dB (default)f665x_patch_init function: "3"t,int16_t volume) +*/ +static int tef665x_audio_set_volume(uint32_t i2c_file_desc, uint16_t volume) +{ + return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO, + TEF665X_Cmd_Set_Volume, + 5, + volume*10); +} +/* +module 64 APPL +cmd 130 Get_Identification +index +1 device +2 hw_version +3 sw_version +*/ +int appl_get_identification(int i2c_file_desc) +{ + uint8_t buf[6]; + int ret; + + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_APPL, + TEF665X_Cmd_Get_Identification, + buf, sizeof(buf)); +// should be completed for further use +// extracting chip versions ... + if(ret == SET_SUCCESS) + { + for(int i = 0; i<6;i++) + printf("buf[%i] = %x\n", i, buf[i]); + return 1; + } + _debug("return value", 0); + return 0; +} + + +//mute=1, unmute=0 +int audio_set_mute(uint32_t i2c_file_desc, bool mute) +{ + return tef665x_audio_set_mute(i2c_file_desc, mute);//AUDIO_Set_Mute mode = 0 : disable mute +} + +//-60 � +24 dB volume +int audio_set_volume(uint32_t i2c_file_desc, int vol) +{ + return tef665x_audio_set_volume(i2c_file_desc, (uint16_t)vol); +} + +/* +module 64 APPL +cmd 1 Set_OperationMode mode + +index +1 mode + [ 15:0 ] + device operation mode + 0 = normal operation + 1 = radio standby mode (low-power mode without radio functionality) + (default) +*/ + +static int tef665x_audio_set_operationMode(uint32_t i2c_file_desc, uint16_t mode) +{ + _debug("normal: 0 standby: 1 requested", 1); + int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_APPL, + TEF665X_Cmd_Set_OperationMode, + 5, + mode); + if(ret) + { + _debug("was able to set the mode", ret); + } + else + { + _debug("FAILED, return", 0); + return 0; + } + return 1; +} + + + +//TRUE = ON; +//FALSE = OFF +static void radio_powerSwitch(uint32_t i2c_file_desc, bool OnOff) +{ + tef665x_audio_set_operationMode(i2c_file_desc, OnOff? 0 : 1);//standby mode = 1 +} + +static void radio_modeSwitch(uint32_t i2c_file_desc, bool mode_switch, AR_TuningAction_t mode, uint16_t frequency) +{ + + if(mode_switch) //FM + { + FM_tune_to(i2c_file_desc, mode, frequency); + } + else //AM + { + AM_tune_to(i2c_file_desc, mode, frequency); + } +} + +/* +module 32 FM +cmd 81 Set_RDS + +index +1 mode + [ 15:0 ] + RDS operation mode + 0 = OFF + 1 = decoder mode (default), output of RDS groupe data (Block A, B, C, D) + from get_rds_status, get_rds_data FM cmd 130/131 + +2 restart + [ 15:0 ] + RDS decoder restart + 0 = no control + 1 = manual restart, starlooking for new RDS data immidiately + 2 = automatic restart after tuning (default) + 3 = flush, empty RDS output buffer. + +3 interface + [ 15:0 ] + 0 = no pin interface. + 2 = data available status output; active low (GPIO feature 'DAVN') + 4 = legecy 2-wire demodulator data and clock output ('RDDA' and 'RDCL') +*/ +int tef665x_set_rds(uint32_t i2c_file_desc) +{ + return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_FM, + TEF665X_Cmd_Set_RDS, + 9,//Total Bytes to be sent + TEF665X_Cmd_Set_RDS_mode, // default + TEF665X_Cmd_Set_RDS_autorestart, // restart after tune + 0x002 // no interface + ); +} + + +/* + * @brief Adding Alternative Frequencies to RDS Data Structure + * + * @param uint8_t* : raw data of alternative frequency (Group 0A of RDS) + * @param rds_data_t* : Pointer to RDS Data Structure + * @return void + */ +void Extract_Alt_Freqs(uint8_t* buf,rds_data_t *Rds_STU) +{ + for(int Buffer_Index=6;Buffer_Index<8;Buffer_Index++) + { + if(buf[Buffer_Index]>204){ + if(250>buf[Buffer_Index]&&buf[Buffer_Index]>224) + { + Rds_STU->Num_AlterFreq=buf[Buffer_Index]-224; + + if(Rds_STU->Alternative_Freq_Counter == Rds_STU->Num_AlterFreq) + { + Rds_STU->Alternative_Freq_Counter = 0; + } + AlterFreqOffset=87500000; + } + else if(buf[Buffer_Index]==205) + { + AFB_ERROR("Filler Code"); + } + else if(buf[Buffer_Index]==224) + { + AFB_ERROR("No AF Exists"); + } + else if(buf[Buffer_Index]==250) + { + AFB_ERROR("An LF/MF Frequency Follows"); + AlterFreqOffset=144000; + } + else if(buf[Buffer_Index]>250) + { + AFB_WARNING("Alternative Frequency Not Assigned"); + } + } + else if(buf[Buffer_Index]>0) + { + if(AlterFreqOffset == 87500000) + { + Rds_STU->Alternative_Freq[Rds_STU->Alternative_Freq_Counter]= + buf[Buffer_Index] * 100000 + AlterFreqOffset; + + Rds_STU->Alternative_Freq_Counter++; + + if(Rds_STU->Alternative_Freq_Counter == Rds_STU->Num_AlterFreq) + { + Rds_STU->Alternative_Freq_Counter = 0; + } + } + else if(AlterFreqOffset == 144000) + { + Rds_STU->Alternative_Freq[Rds_STU->Alternative_Freq_Counter]= + ((uint32_t)buf[Buffer_Index]) * 9000 + AlterFreqOffset; + + Rds_STU->Alternative_Freq_Counter++; + + if(Rds_STU->Alternative_Freq_Counter == Rds_STU->Num_AlterFreq) + { + Rds_STU->Alternative_Freq_Counter = 0; + } + } + else + { + AFB_WARNING("Alternative Frequency is not defined"); + } + } + else + { + AFB_ERROR("Alternative Frequency- Not to be used"); + } + } +} + +/* + * @brief Checking rds error code (determined by decoder) + * + * 0 : no error; block data was received with matching data and syndrome + * 1 : small error; possible 1 bit reception error detected; data is corrected + * 2 : large error; theoretical correctable error detected; data is corrected + * 3 : uncorrectable error; no data correction possible + * + * @param Errors : Error Code of blocks A,B,C and D of RDS + * @return void + */ +void Check_RDS_Error(uint8_t Errors[]) +{ + for (int i=0;i<4;i++){ + if(Errors[i]==1){ + AFB_WARNING("RDS Block %d Reception Error; small error; possible 1 bit reception error detected; data is corrected",i+1); + } + else if(Errors[i]==2){ + AFB_WARNING("RDS Block %d Reception Error; large error; theoretical correctable error detected; data is corrected",i+1); + } + else if(Errors[i]==3){ + AFB_ERROR("RDS Block %d Reception Error; uncorrectable error; no data correction possible",i+1); + } + } +} + +/* + * @brief Getting rds_data_t and Process its raw_data + * + * @param rds_data_t * : Pointer to latest RDS Data Structure + */ +void *Process_RDS_Words(void* rds_words){ + pthread_detach(pthread_self()); + + rds_data_t *Rds_STU = rds_words; + uint8_t *raw_data = Rds_STU->raw_data; + int8_t group_Ver = -1; + uint8_t GType0 = 0; + bool DI_Seg = 0; + bool M_S = 0; + bool TA = 0; + + //Parse 1st Section + bool DataAvailable = (raw_data[0] >> 7) & 1; + bool DataLoss = (raw_data[0] >> 6) & 1 == 1; + bool DataAvailType = (raw_data[0] >> 5) & 1 == 0; + bool GroupType = (raw_data[0] >> 4) & 1; + bool SyncStatus = (raw_data[0] >> 1) & 1; + + //Parse Last Section(Error Codes) + uint8_t Error_A = raw_data[10] >> 6; + uint8_t Error_B = raw_data[10] >> 4 & 3; + uint8_t Error_C = raw_data[10] >> 2 & 3; + uint8_t Error_D = raw_data[10] & 3; + uint8_t Errors[]={Error_A,Error_B,Error_C,Error_D}; + + //Inform user about Error Blocks Status Codes + Check_RDS_Error(Errors); + + if(Error_A==0){ + //Bytes 2 and 3 are inside Block A + //raw_data[2]and raw_data[3] Contains PI Code + Rds_STU->PICode=Convert8bto16b(&raw_data[2]); + } + else{ + AFB_ERROR("Error_A=%d",Error_A); + } + + bool GTypeVer=GType0; + uint16_t GType=raw_data[4]>>4; + //Bytes 4 and 5 are inside Block B + if(Error_B==0){ + GTypeVer=raw_data[4]>>3 & 1; + GType=raw_data[4]>>4; + Rds_STU->TrafficProgram=raw_data[4]>>2&1; + Rds_STU->PTY_Code= (raw_data[4] & 3) << 3 | raw_data[5] >> 5; + } + + //Position Of Character + uint8_t CharPos=0; + + //Extract Data based on Group Type values + switch (GType) + { + case 0: + { + if(Error_B==0) + { + CharPos = raw_data[5] & 3; + + Rds_STU->TrafficAnnouncement = raw_data[5] >> 4 & 1; + Rds_STU->Music_Speech = raw_data[5] >> 3 & 1; + Rds_STU->DI_Seg = (raw_data[5] >> 2 & 1) * (2 ^ (3 - CharPos)); + } + + if(Error_C==0) + { + //Group Type 0A + if (GType==0) + { + Extract_Alt_Freqs(raw_data,Rds_STU); + } + + //Group Type 0B + else + { + Rds_STU->PICode=Convert8bto16b(&raw_data[6]); + } + } + + if(Error_D == 0 && Error_B == 0) + { + if(raw_data[8] != 0x7f) + { + Rds_STU->PS_Name[CharPos*2] = raw_data[8]; + } + else + { + Rds_STU->PS_Name[CharPos*2] = (char)'\0'; + } + if(raw_data[9] != 0x7f) + { + Rds_STU->PS_Name[CharPos*2+1] = raw_data[9]; + } + else + { + Rds_STU->PS_Name[CharPos*2+1] = (char)'\0'; + } + } + } + break; + case 1: + { + //Group Type 1A + if(GTypeVer == 0) + { + if(Error_D == 0) + { + Rds_STU->Day = raw_data[8] >> 3; + Rds_STU->Hour = raw_data[8] >> 3; + Rds_STU->Min = ((raw_data[8] & 7) << 2) | (raw_data[9] >> 6) ; + } + } + } + break; + case 2: + { + //Group Type 2A: + if(GTypeVer == 0) + { + uint8_t Text_pos = raw_data[5] & 15; + + if(Error_B == 0 && Error_C == 0) + { + + if(raw_data[6] !=0x7f && raw_data[6] != '\n') + { + Rds_STU->RT[Text_pos*4] = raw_data[6]; + } + else{ + Rds_STU->RT[Text_pos*4] = (char)'\0'; + } + if(raw_data[7]!=0x7f&&raw_data[7]!='\n') + { + Rds_STU->RT[Text_pos*4+1] = raw_data[7]; + } + else + { + Rds_STU->RT[Text_pos*4+1] = (char)'\0'; + } + } + if(Error_B == 0 && Error_D == 0) + { + if(raw_data[8] != 0x7f && raw_data[8] != '\n') + { + Rds_STU->RT[Text_pos*4+2] = raw_data[8]; + } + else{ + Rds_STU->RT[Text_pos*4+2] = (char)'\0'; + } + if(raw_data[9] != 0x7f && raw_data[9] != '\n') + { + Rds_STU->RT[Text_pos*4+3] = raw_data[9]; + } + else + { + Rds_STU->RT[Text_pos*4+3] = (char)'\0'; + } + } + } + + //Group Type 2B: + else{ + if(Error_B==0 && Error_D==0) + { + //Clear All Radio Text if flag was changed + if(raw_data[5] >> 4 & 1 != Rds_STU->Text_Changed) + { + memcpy(Rds_STU->RT, _Temp , 64); + } + + uint8_t Text_pos = raw_data[5] & 15; + if(raw_data[8] != 0x7f && raw_data[8] != '\n') + { + + Rds_STU->RT[Text_pos*2] = raw_data[8]; + } + else{ + Rds_STU->RT[Text_pos*2] = (char)'\0'; + } + if(raw_data[9] != 0x7f && raw_data[9] != '\n') + { + Rds_STU->RT[Text_pos*2+1] = raw_data[9]; + } + else + { + Rds_STU->RT[Text_pos*2+1] = (char)'\0'; + } + } + } + } + break; + case 4: + { + //Group Type 4A + if(GTypeVer == 0) + { + if(Error_B == 0 && Error_C == 0 && Error_D == 0) + { + //Following caclulations are based on RDS Standard + uint32_t Modified_Julian_Day = ((raw_data[5] & 3) << 15) | (raw_data[6] << 7) | (raw_data[7]>>1); + int y2 = (int)((((double)Modified_Julian_Day)-15078.2)/365.25); + int m2 = (int)((((double)Modified_Julian_Day)-14956.1-((double)y2*365.25))/30.6001); + int d2 = (double)Modified_Julian_Day-14956-(int)(y2*365.25)-(int)(m2*30.6001); + int k = 0; + + if(m2 == 14 || m2 == 15) + { + k = 1; + } + + Rds_STU->Day = d2; + Rds_STU->Month = m2 - 1 + k * 12; + Rds_STU->Year = y2 + k; + + uint8_t UTCHour = ((raw_data[7] & 1) << 4) | (raw_data[8] >> 4); + uint8_t UTCMinute = ((raw_data[8] & 15) << 2) | (raw_data[9] >> 6); + + //Check Negative Offset + bool NegOff = raw_data[9] & 32; + uint8_t LocTimeOff = raw_data[9] & 31; + + if(!NegOff) + { + Rds_STU->Min = UTCMinute + LocTimeOff % 2; + while(UTCMinute > 60) + { + UTCHour++; + UTCMinute = UTCMinute - 60; + } + + Rds_STU->Hour = UTCHour + LocTimeOff / 2; + while(Rds_STU->Hour > 24){ + Rds_STU->Hour = Rds_STU->Hour - 24; + } + + + } + else{ + Rds_STU->Min = UTCMinute + LocTimeOff % 2; + while(UTCMinute < 0) + { + UTCHour--; + UTCMinute = UTCMinute + 60; + } + Rds_STU->Hour = UTCHour + LocTimeOff / 2; + while(Rds_STU->Hour<0) + { + Rds_STU->Hour = Rds_STU->Hour + 24; + + } + } + } + } + //Group Type 4B + else + { + AFB_WARNING("Groupe Type 4B are not supported yet"); + } + } + case 8: + { + AFB_WARNING("Groupe Type 8A and 8B are not supported yet"); + + } + case 10: + { + AFB_WARNING("Groupe Type 10A and 10B are not supported yet"); + /* + if(Error_B == 0){ + uint8_t pos = 0; + pos=(raw_data[5] & 1) * 4; + + if(Error_C == 0){ + Rds_STU->PTYN[pos] = raw_data[6]; + Rds_STU->PTYN[pos+1] = raw_data[7]; + Rds_STU->PTYN_Size = pos + 2; + } + if(Error_D == 0){ + Rds_STU->PTYN[pos+2] = raw_data[8]; + Rds_STU->PTYN[pos+3] = raw_data[9]; + Rds_STU->PTYN_Size = pos + 4; + } + } + /**/ + } + break; + default: + AFB_ERROR("Unsupported Group %d",GType); + break; + } + + if(!DataAvailable) + { + AFB_ERROR("RDS Data is not available"); + } + + if(DataLoss) + { + AFB_ERROR("previous data was not read, replaced by newer data"); + } + + if(GroupType == 0) + { + group_Ver = 0; + } + else + { + group_Ver = 1; + } + + if(!SyncStatus) + { + AFB_ERROR(" RDS decoder not synchronized; no RDS data found"); + } + + if(GroupType != GTypeVer) + { + AFB_ERROR("Version is not Correct?"); + } +} + +/* +module 32 FM +cmd 131 get RDS data + +index +1 status + [ 15:0 ] + FM RDS reception status. + [15] = dta availableflag + 0 = no data + 1 = data available + [14] = data loss flag + 0 = no data loss + 1 = previose data not read, replaced by newer data. + [13] = data available type + 0 = group data; continuos operation. + 1 = first PI data;data with PI code following decoder sync. + [12] = groupe type. + 0 = type A; A-B-C-D group (with PI code in the block A) + 1 = type B; A-B-C'-D group (with PI code in the block A and C') + [ 8:0 ] reserved + +2 A_Block + [ 15:0 ] = A block data + +3 B_Block + [ 15:0 ] = B block data + +4 C_Block + [ 15:0 ] = C block data + +5 D_Block + [ 15:0 ] = D block data + +6 dec error + [ 15:0 ] + error code determined by decoder + [ 15:14 ] = A block error + [ 13:12 ] = B block error + [ 11:10 ] = C block error + [ 9:8 ] = D block error + 0 = no error found + 1 = small error, correctable. data is corrected. + 2 = larg error, correctable. data is corrected. + 3 = uncorrectable error. + [ 7:0 ] = reserved. +*/ +/* + * @brief Get RDS Data fron Tef-665 + * + * Getting RDS Data From I2C and Calling a thread to process raw data + * + * @param i2c_file_desc : I2C File Descriptor + * @param Rds_STU : RDS Data Structure + * + */ +int tef665x_get_rds_data(uint32_t i2c_file_desc, rds_data_t *Rds_STU) +{ + + int ret; + uint8_t buf[12]; + + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM, + TEF665X_Cmd_Get_RDS_Data, + buf, sizeof(buf)); + + if(ret == 1) { + memcpy(Rds_STU->raw_data,buf,12); + pthread_t t0; + pthread_create(&t0, NULL,Process_RDS_Words ,(void *) (Rds_STU)); + } + return ret; +} + +void Clear_RDS_Data(rds_data_t *Rds_STU){ + + Rds_STU-> Text_Changed=0; + Rds_STU-> TrafficAnnouncement=0; + Rds_STU-> TrafficProgram=0; + Rds_STU-> Music_Speech=0; + + Rds_STU-> DI_Seg=0; + Rds_STU-> PTY_Code=0; + Rds_STU-> Num_AlterFreq=0; + Rds_STU->PTYN_Size=0; + + Rds_STU-> Day=0; + Rds_STU-> Month=0; + Rds_STU-> Year=0; + + Rds_STU-> Hour=0; + Rds_STU-> Min=0; + + /*memcpy(Rds_STU->Alternative_Freq,_Temp,25);/**/ + for(uint8_t i=0;i<25;i++){ + Rds_STU->Alternative_Freq[i]=0; + } + memcpy(Rds_STU-> PS_Name,_Temp,8); + Rds_STU-> PS_Name[0]='\0'; + memcpy(Rds_STU-> RT,_Temp,64); + Rds_STU-> RT[0]='\0'; + memcpy(Rds_STU-> PTYN,_Temp,8); + Rds_STU-> PTYN[0]='\0'; + + Rds_STU-> PICode=0; + Rds_STU->Alternative_Freq_Counter=0; + Rds_STU->PTYN_Size=0; +} + +//Check if RDS is available +int tef665x_get_rds_status(uint32_t i2c_file_desc, uint16_t *status) +{ + int ret = 0; + uint8_t buf[2]; + + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM, + TEF665X_Cmd_Get_RDS_Status, + buf, sizeof(buf)); + + if(ret == 1){ + status[0] =buf[0]; + status[1] =buf[1]; + } + + return ret; +} + +static int tef665x_wait_active(uint32_t i2c_file_desc) +{ + TEF665x_STATE status; + //usleep(50000); + if(SET_SUCCESS == appl_get_operation_status(i2c_file_desc, &status)) + { + AFB_INFO("got status", 1); + if((status != eDevTEF665x_Boot_state) && (status != eDevTEF665x_Idle_state)) + { + AFB_INFO("active status", 1); + + if(SET_SUCCESS == tef665x_para_load(i2c_file_desc)) + { + _debug("parameters loaded", 1); + } + else + { + _debug("parameters not loaded", 0); + return 0; + } + + if(current_band == BAND_FM){ + FM_tune_to(i2c_file_desc, eAR_TuningAction_Preset, current_fm_frequency / 10000);// tune to min + } else { + AM_tune_to(i2c_file_desc, eAR_TuningAction_Preset, current_am_frequency / 1000);// tune to min + } + + if(SET_SUCCESS == audio_set_mute(i2c_file_desc, 1))//unmute=0 + { + _debug("muted", 1); + } + else + { + _debug("not muted", 0); + return 0; + } + + // //if(SET_SUCCESS == audio_set_volume(i2c_file_desc, 35))//set to -10db + // { + // _debug("set vol to", 25); + // } + + // else + // { + // _debug("vol not set", 0); + // return 0; + // } + return 1; + } + } + + return 0; +} + +static void tef665x_chip_init(int i2c_file_desc) +{ + if(1 == tef665x_power_on(i2c_file_desc)) _debug("tef665x_power_on", 1); + usleep(50000); + if(1 == tef665x_boot_state(i2c_file_desc)) _debug("tef665x_boot_state", 1); + usleep(100000); + if(1 == tef665x_idle_state(i2c_file_desc)) _debug("tef665x_idle_state", 1); + usleep(200000); + if(1 == tef665x_wait_active(i2c_file_desc)) _debug("tef665x_wait_active", 1); + //if you want to use analog output comment below command, or pass 1 to it. + if(SET_SUCCESS != tef665x_audio_set_ana_out(i2c_file_desc, TEF665X_Cmd_Set_Output_signal_dac, 0)) + { + _debug("Set DAC to OFF failed", 0); + //return 0; + } + + if(SET_SUCCESS != tef665x_set_output_src(i2c_file_desc, TEF665X_Cmd_Set_Output_signal_i2s, + TEF665X_Cmd_Set_Output_source_aProcessor)) + { + _debug("Set output failed", 0); + //return 0; + } + //this is needed to use digital output + if(SET_SUCCESS != tef665x_audio_set_dig_io(i2c_file_desc, TEF665X_AUDIO_CMD_22_SIGNAL_i2s1, + TEF665X_AUDIO_CMD_22_MODE_voltage, + TEF665X_AUDIO_CMD_22_FORMAT_16, + TEF665X_AUDIO_CMD_22_OPERATION_slave, + TEF665X_AUDIO_CMD_22_SAMPLERATE_48K)) + { + _debug("Setup i2s failed", 0); + //return 0; + } + + +} + + +static int i2c_init(const char *i2c, int state, uint32_t *i2c_file_desc) +{ + int fd = 0, t; + + if(state == _open) + { + fd = open(i2c, O_RDWR); + + if(fd < 0) + { + _debug("could not open %s", i2c); + return fd; + } + + t = ioctl(fd, I2C_SLAVE, I2C_ADDRESS); + if (t < 0) + { + _debug("could not set up slave ", 0); + return t; + } + *i2c_file_desc = fd; + } + else + { + close(*i2c_file_desc); + } + + return 0; +} + +/* +module 32/33 FM/AM +cmd 129 Get_Quality_Data + +index +1 status + [ 15:0 ] + quality detector status + [15] = AF_update flag + 0 = continuous quality data with time stamp + 1 = AF_Update sampled data + [14:10] = reserved + 0 = no data loss + 1 = previose data not read, replaced by newer data. + [9:0] = quality time stamp + 0 = tuning is in progress, no quality data available + 1 … 320 (* 0.1 ms) = 0.1 … 32 ms after tuning, + quality data available, reliability depending on time stamp + 1000 = > 32 ms after tuning + quality data continuously updated + +2 level + [ 15:0 ] (signed) + level detector result + -200 … 1200 (0.1 * dBuV) = -20 … 120 dBuV RF input level + actual range and accuracy is limited by noise and agc + +3 usn + [ 15:0 ] = noise detector + FM ultrasonic noise detector + 0 … 1000 (*0.1 %) = 0 … 100% relative usn detector result + +4 wam + [ 15:0 ] = radio frequency offset + FM ‘wideband-AM’ multipath detector + 0 … 1000 (*0.1 %) = 0 … 100% relative wam detector result + +5 offset + [ 15:0 ] (signed) = radio frequency offset + -1200 … 1200 (*0.1 kHz) = -120 kHz … 120 kHz radio frequency error + actual range and accuracy is limited by noise and bandwidth + +6 bandwidth + [ 15:0 ] = IF bandwidth + FM 560 … 3110 [*0.1 kHz] = IF bandwidth 56 … 311 kHz; narrow … wide + AM 30 … 80 [*0.1 kHz] = IF bandwidth 3 … 8 kHz; narrow … wide + +7 modulation + [ 15:0 ] = modulation detector + FM 0 … 1000 [*0.1 %] = 0 … 100% modulation = 0 … 75 kHz FM dev. +*/ +void Get_quality_status(sig_quality_t *quality){ + + uint32_t i2c_file_desc=0; + int ret = i2c_init(I2C_DEV, _open, &i2c_file_desc); + + uint8_t data[14]; + int16_t level=0; + uint16_t usn=0; + uint16_t wam=0; + int16_t offset=0; + uint16_t bw=0; + + for (int looper=0;looper<1;looper++){ + if(current_band==BAND_FM){ + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM, + TEF665X_Cmd_Get_Quality_Data, + data, sizeof(data)); + } + else{ + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_AM, + TEF665X_Cmd_Get_Quality_Data, + data, sizeof(data)); + } + + + int status=((data[0]&0b00000011)<<8|data[1]); + if(status!=1000){ + AFB_ERROR("Data is not ready, try again; quality time stamp: %d",status); + looper--; + usleep(1000); + continue; + } + + level =(data[2] <<8|data[3] ); + usn =(data[4] <<8|data[5] ); + wam =(data[6] <<8|data[7] ); + offset =(data[8] <<8|data[9] ); + bw =(data[10]<<8|data[11]); + } + i2c_init(I2C_DEV, _close, &i2c_file_desc); + + quality->offset = offset; + quality->bandw = bw; + quality->level = level; + quality->wam = wam; + quality->usn = usn; + + + return; +} + +static void tef665x_start(void) +{ + int ret; + if(!present) + return; + + _debug("file_desc ", file_desc); + + audio_set_mute(file_desc, 0); + + if(!running) { + + // Start pipeline + ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); + _debug("gst_element_set_state to play", ret); + running = true; + } + } + +/* + * @brief Send_Rds_Result to rds subscribers + * + * @param rds_data_t : a rds message structure + * @return The JsonObject of rds info + */ +void *Send_Rds_Result(rds_data_t* RDS_Message){ + //Kill the thread when it was over + pthread_detach(pthread_self()); + + json_object *ret_json; + json_object *Alternative_Freqs; + + + ret_json = json_object_new_object(); + Alternative_Freqs=json_object_new_array(); + + + + for(uint8_t af=0 ; af<25 ; af++) + { + if(RDS_Message->Alternative_Freq[af]!=NULL&&RDS_Message->Alternative_Freq[af]!=0) + { + json_object_array_add(Alternative_Freqs,json_object_new_int(RDS_Message->Alternative_Freq[af])); + } + } + + //Prepare JSon Object + json_object_object_add(ret_json, "name" , json_object_new_string(RDS_Message->PS_Name)); + json_object_object_add(ret_json, "radiotext" , json_object_new_string(RDS_Message->RT)); + json_object_object_add(ret_json, "alternatives" , (Alternative_Freqs)); + json_object_object_add(ret_json, "minute" , json_object_new_int (RDS_Message->Min)); + json_object_object_add(ret_json, "hour" , json_object_new_int (RDS_Message->Hour)); + json_object_object_add(ret_json, "day" , json_object_new_int (RDS_Message->Day)); + json_object_object_add(ret_json, "month" , json_object_new_int (RDS_Message->Month)); + json_object_object_add(ret_json, "year" , json_object_new_int (RDS_Message->Year)); + json_object_object_add(ret_json, "pi" , json_object_new_int (RDS_Message->PICode)); + json_object_object_add(ret_json, "pty" , json_object_new_int (RDS_Message->PTY_Code)); + json_object_object_add(ret_json, "ta" , json_object_new_int (RDS_Message->TrafficAnnouncement)); + json_object_object_add(ret_json, "tp" , json_object_new_int (RDS_Message->TrafficProgram)); + json_object_object_add(ret_json, "ms" , json_object_new_int (RDS_Message->Music_Speech)); + + //Send JsonObject to rds Subscribers + if(rds_callback){ + rds_callback(ret_json); + } + + return ret_json; +} + +/* + * @brief Create an infinit Loop to get RDS Packets and Send them to subscribers + * + * RDS data will be available every 85 ms; + * Currently availability of RDS is checkes by tef665x_get_rds_status function + * + * @param rds_data_t : a rds message structure + * @return The JsonObject of latest rds info + */ +void *Get_RDS_Packets(rds_data_t *StuRDS){ + pthread_detach(pthread_self()); + uint32_t fd = 0; + + int ret = i2c_init(I2C_DEV, _open, &fd); + uint8_t status[2]; + + ret=tef665x_get_rds_status(fd, status); + + if(ret==1){ + if(status[0]>7){ + //RDS must update all the time, except the times we are scanning or changing frequency + //when scanning or changing frequncy, we unlock RDS_Mutex and it will end this thread + for (int ref_cnt=0; pthread_mutex_trylock(&RDS_Mutex) != 0;ref_cnt++){ + //Get New RDS Data + tef665x_get_rds_data(fd,StuRDS); + + //Send RDS Data after rexeiving 22 Packets + if(ref_cnt%22==0){ + pthread_t t0; + pthread_create(&t0, NULL,Send_Rds_Result ,(void *) (&RDS_Message)); + } + + //Wait for 85ms before reading available rds data + usleep(85000); + } + pthread_mutex_unlock (&RDS_Mutex); + } + + else{ + AFB_ERROR("RDS is Not Valid0"); + } + } + + else{ + AFB_ERROR("RDS is Not Valid1"); + } + i2c_init(I2C_DEV, _close, &fd); +} + +/* + * @brief Free Allocated Memory for Scan Thread and Unlock Scan Mutex + * + * @param scan_data : scan_data_t contains direction of search and callback + * for station_found event + */ +static void scan_cleanup_handler(void *scan_data) +{ + pthread_mutex_unlock(&scan_mutex); + free(scan_data); + scanning=false; +} + +/* + * @brief Create a loop to scan from current frequency to find a valid frequency + * + * If found a valid frequency, send station_found to subscribers and break the loop; + * If the direction was forward and reach the maximum frequency, Search Must continue + * from minimum frequency + * If the direction was backward and reach the minimum frequency, Search Must continue + * from maximum frequency + * If no valid frequency found, scan will stop at the begining point + * If stop_scan called, scan_mutex will be unlocked and thread must be stopped + * + * @param scan_data : scan_data_t contains direction of search and callback + * for station_found event + */ +void *scan_frequencies(scan_data_t* scan_data){ + pthread_cleanup_push(scan_cleanup_handler, (void *)scan_data); + + //Kill the thread when it was over + pthread_detach(pthread_self()); + + //Set Scan Flag + scanning=true; + + //"Unlock Mutex" Flag + bool unlck_mtx = false; + uint32_t new_freq = 0; + uint32_t init_freq = 0; + + init_freq = current_band == BAND_FM ? current_fm_frequency : current_am_frequency; + + //First Mute Current Frequency + tef665x_search_frequency(init_freq); + + //freq_step will be negative if direction was backward and positive if direction was forward + uint32_t freq_step = tef665x_get_frequency_step(current_band) * (scan_data->direction==SCAN_FORWARD?1:-1); + + //Continue loop until reaching the initial frequency + while(init_freq != new_freq) + { + //Check Status of scan_mutex + unlck_mtx = pthread_mutex_trylock(&scan_mutex)==0; + + //break the loop if scan_mutex was unlocked + if(unlck_mtx) + { + break; + } + + if(current_band==BAND_FM) + { + new_freq = current_fm_frequency + freq_step; + + //Searching Step is 100 KHz + //If frequency reached to initial point, the search must stop + while (((new_freq/10000)%10)!=0 && init_freq != new_freq){ + new_freq = new_freq+freq_step; + } + } + else + { + new_freq = current_am_frequency + freq_step; + } + + //Set Freq to min when it was more than Max Value + if(new_freq>tef665x_get_max_frequency(current_band)) + { + new_freq=tef665x_get_min_frequency(current_band); + } + + //Set Freq to max when it was less than Min Value + if(new_freq<tef665x_get_min_frequency(current_band)) + { + new_freq=tef665x_get_max_frequency(current_band); + } + + //Tune to new frequency + tef665x_search_frequency(new_freq); + + //wait 30 ms to make sure quality data is available + for(int i=0;i<30;i++) + { + usleep(1000); + + //Check scan_mutex lock for handling stop_scan + unlck_mtx=pthread_mutex_trylock(&scan_mutex)==0; + if(unlck_mtx) + { + break; + } + } + if(unlck_mtx) + { + break; + } + + //Get Quality of tuned frequeency + sig_quality_t quality; + Get_quality_status(&quality); + + if((quality.level >260 && quality.usn<100) || quality.bandw>1200) + { + AFB_DEBUG("Quality is valid"); + AFB_INFO("level is: 0x%4X : %d",quality.level,quality.level); + AFB_INFO("usn is: 0x%4X : %d",quality.usn,quality.usn); + AFB_INFO("offset is: 0x%4X : %d",quality.offset,quality.offset); + AFB_INFO("wam is: 0x%4X : %d",quality.wam,quality.wam); + AFB_INFO("Bandwidth is: 0x%4X : %d",quality.bandw,quality.bandw); + + //Send + if(scan_data->callback) + { + scan_data->callback(new_freq,NULL); + } + else + { + AFB_DEBUG("callback is not valid"); + } + + break; + } + else + { + AFB_DEBUG("Quality is not valid"); + AFB_ERROR("level is: 0x%4X : %d",quality.level,quality.level); + AFB_ERROR("usn is: 0x%4X %d",quality.usn,quality.usn); + AFB_ERROR("offset is: 0x%4X %d",quality.offset,quality.offset); + AFB_ERROR("wam is: 0x%4X %d",quality.wam,quality.wam); + AFB_ERROR("bandwidth is: 0x%4X %d",quality.bandw,quality.bandw); + } + + usleep(100); + } + + //Calling last pthread_cleanup_push + pthread_cleanup_pop(1); +} + +/* + * @brief Get latest RDS Info and send rds jsonObject as response + * + * @return: cast rds_json(json_object) to (char *) and return result as response + */ +static char *tef665x_get_rds_info(void) +{ + //If Getting RDS Result wasn't already started, Start it now + if(pthread_mutex_trylock(&RDS_Mutex) == 0) + { + AFB_DEBUG("Create the thread."); + pthread_create(&rds_thread, NULL,Get_RDS_Packets ,(void *) (&RDS_Message)); + } + + //Send latest available rds data + json_object *rds_json=(json_object *)Send_Rds_Result(&RDS_Message); + + //Convert json_object to char* and send it as response + return (char *)json_object_to_json_string(rds_json); +} + +/* + * @brief Start Scan + * + * @param radio_scan_direction_t direction which is the scan direction and can be + * SCAN_FORWARD or SCAN_BACKWARD + * @param radio_scan_callback_t callback which is the callback for sending result of search to + * station_found ecent subscribers + * @return void + */ +static void tef665x_scan_start(radio_scan_direction_t direction, + radio_scan_callback_t callback, + void *data) +{ + + //Stop RDS if enabled + pthread_mutex_unlock (&RDS_Mutex); + + //Stop current scan: + if(scanning) + { + tef665x_scan_stop(); + } + + scan_data_t *inputs; + + //Clean RDS Message since frequency will change + Clear_RDS_Data(&RDS_Message); + usleep(10000); + + AFB_DEBUG("check Mutex Condition"); + + //check if is there any activated search + if(pthread_mutex_trylock(&scan_mutex)==0&&!scanning) + { + AFB_DEBUG("Start Scanning..."); + + inputs=malloc(sizeof(*inputs)); + if(!inputs) + return -ENOMEM; + + inputs->direction= direction; + inputs->callback= callback; + inputs->data=data; + + pthread_create(&scan_thread, NULL,scan_frequencies ,(void *) inputs); + } +} + +/* + * @brief Stop Scan + * + * By unlocking scan_mutex, Scan thread will be stopped safely and update scanning flag + * + * @return void + */ +static void tef665x_scan_stop(void) +{ + pthread_mutex_unlock(&scan_mutex); + while(scanning) + { + usleep(100); + AFB_DEBUG(" Wait for unlocking scan Thread"); + } +} + + +/* +module 32 / 33 FM / AM +cmd 133 Get_Signal_Status | status +index +1 status + [ 15:0 ] = Radio signal information + [15] = 0 : mono signal + [15] = 1 : FM stereo signal (stereo pilot detected) + + [14] = 0 : analog signal + [14] = 1 : digital signal (blend input activated by digital processor or control) + (TEF6659 only) +*/ +radio_stereo_mode_t tef665x_get_stereo_mode(void) +{ + uint32_t i2c_file_desc = 0; + int ret = i2c_init(I2C_DEV, _open, &i2c_file_desc); + uint8_t data[2]; + if(current_band==BAND_FM){ + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM, + TEF665X_Cmd_Get_Signal_Status, + data, sizeof(data)); + } + else{ + ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_AM, + TEF665X_Cmd_Get_Signal_Status, + data, sizeof(data)); + } + i2c_init(I2C_DEV, _close, &i2c_file_desc); + return data[0]>>7 ==1 ? STEREO:MONO; +} + +static void tef665x_stop(void) +{ + int ret; + GstEvent *event; + audio_set_mute(file_desc, 1); + + if(present && running) { + // Stop pipeline + running = false; + ret = gst_element_set_state(pipeline, GST_STATE_PAUSED); + _debug("gst_element_set_state to pause", ret); + + // Flush pipeline + // This seems required to avoidstatic stutters on starts after a stop + event = gst_event_new_flush_start(); + gst_element_send_event(GST_ELEMENT(pipeline), event); + event = gst_event_new_flush_stop(TRUE); + gst_element_send_event(GST_ELEMENT(pipeline), event); + } +} + +static int tef665x_init() +{ + char gst_pipeline_str[GST_PIPELINE_LEN]; + int rc; + + current_am_frequency = known_am_band_plans[am_bandplan].min; + current_fm_frequency = known_fm_band_plans[fm_bandplan].min; + + rc = i2c_init(I2C_DEV, _open, &file_desc); + if(rc < 0) { + AFB_NOTICE("tef665x not present"); + return -1; + } + _debug("file_desc= ", file_desc); + + rc = appl_get_identification(file_desc); + if(rc != 1){ + AFB_ERROR("no tef665x!"); + return -1; + } + + current_band = BAND_AM; + + radio_powerSwitch(file_desc, 1); + + tef665x_chip_init(file_desc); + + // Initialize GStreamer + gst_init(NULL, NULL); + + // Use PipeWire output + // This pipeline is working on imx6solo, the important thing, up to now, is that it gets xrun error every few seconds. + // I believe it's related to wireplumber on imx6. + rc = snprintf(gst_pipeline_str, + GST_PIPELINE_LEN, + "alsasrc device=hw:1,0 ! audioconvert ! audioresample ! audio/x-raw, rate=48000, channels=2 \ + ! pwaudiosink stream-properties=\"p,media.role=Multimedia\" latency-time=35000"); + + if(rc >= GST_PIPELINE_LEN) { + AFB_ERROR("pipeline string too long"); + return -1; + } + printf("pipeline: , %s\n", gst_pipeline_str); + + pipeline = gst_parse_launch(gst_pipeline_str, NULL); + if(!pipeline) { + AFB_ERROR("pipeline construction failed!"); + return -1; + } + + // Start pipeline in paused state + rc = gst_element_set_state(pipeline, GST_STATE_PAUSED); + _debug("gst_element_set_state to pause (at the begining)", rc); + + rc = gst_bus_add_watch(gst_element_get_bus(pipeline), (GstBusFunc) handle_message, NULL); + _debug("gst_bus_add_watch rc", rc); + + present = true; + + //Initialize Mutex Lock for Scan and RDS + pthread_mutex_init(&scan_mutex, NULL); + pthread_mutex_init (&RDS_Mutex, NULL); + + tef665x_start(); + return 0; +} + +static void tef665x_set_frequency_callback(radio_freq_callback_t callback, + void *data) +{ + freq_callback = callback; + freq_callback_data = data; +} +static void tef665x_set_rds_callback(radio_rds_callback_t callback) +{ + rds_callback = callback; + +} +static void tef665x_set_output(const char *output) +{ +} + +static radio_band_t tef665x_get_band(void) +{ + _debug("band", current_band); + return current_band; +} + +static void tef665x_set_band(radio_band_t band) +{ + uint32_t fd = 0; + int ret = i2c_init(I2C_DEV, _open, &fd); + + _debug("i2c_init ret value", ret); + + if(band == BAND_FM){ + current_band = band; + FM_tune_to(fd, eAR_TuningAction_Preset, current_fm_frequency / 10000); + } else { + current_band = band; + AM_tune_to(fd, eAR_TuningAction_Preset, current_am_frequency / 1000); + } + + i2c_init(I2C_DEV, _close, &fd); + + _debug("band", current_band); +} + +static uint32_t tef665x_get_frequency(void) +{ + if(current_band == BAND_FM){ + return current_fm_frequency; + } else { + return current_am_frequency; + } +} + +static void tef665x_set_frequency(uint32_t frequency) +{ + uint32_t fd = 0; + if(!present) + return; + + if(scanning) + return; + + if(current_band == BAND_FM) { + if(frequency < known_fm_band_plans[fm_bandplan].min || + frequency > known_fm_band_plans[fm_bandplan].max ) { + _debug("invalid FM frequency", frequency); + return; + } + } else { + if(frequency < known_am_band_plans[am_bandplan].min || + frequency > known_am_band_plans[am_bandplan].max ) { + _debug("invalid AM frequency", frequency); + return; + } + } + + int ret = i2c_init(I2C_DEV, _open, &fd); + + if(current_band == BAND_FM){ + current_fm_frequency = frequency; + _debug("frequency set to FM :", frequency); + FM_tune_to(fd, eAR_TuningAction_Preset, frequency / 10000); + } else { + current_am_frequency = frequency; + _debug("frequency set to AM :", frequency); + AM_tune_to(fd, eAR_TuningAction_Preset, frequency / 1000); + } + i2c_init(I2C_DEV, _close, &fd); + + //Send Frequency data to subscribers + if(freq_callback) + { + freq_callback(frequency, freq_callback_data); + } + + //Start RDS if the band was FM + if(current_band==BAND_FM){ + //Unlock Mutex + pthread_mutex_unlock (&RDS_Mutex); + + //Clean RDS Message + Clear_RDS_Data(&RDS_Message); + + //Wait to make sure rds thread is finished + usleep(300000); + + //Restart RDS + tef665x_get_rds_info(); + } +} + +/* + * @brief Tune to a frequency in search mode + * + * Tune to new program and stay muted + * Sending new frequency to subscribers + * + * @param uint32_t which is the frequecy to be tuned + * @return void + */ +static void tef665x_search_frequency(uint32_t frequency) +{ + uint32_t fd = 0; + int ret = i2c_init(I2C_DEV, _open, &fd); + if(current_band == BAND_FM) + { + current_fm_frequency = frequency; + _debug("frequency set to FM :", frequency); + FM_tune_to(fd, eAR_TuningAction_Search, frequency / 10000); + + } + else + { + current_am_frequency = frequency; + _debug("frequency set to AM :", frequency); + AM_tune_to(fd, eAR_TuningAction_Search, frequency / 1000); + } + i2c_init(I2C_DEV, _close, &fd); + + //Send Frequency data to subscribers + if(freq_callback) + { + freq_callback(frequency, freq_callback_data); + } +} + +static int tef665x_band_supported(radio_band_t band) +{ + if(band == BAND_FM || band == BAND_AM) + return 1; + return 0; +} + +static uint32_t tef665x_get_min_frequency(radio_band_t band) +{ + if(band == BAND_FM) { + return known_fm_band_plans[fm_bandplan].min; + } else { + return known_am_band_plans[am_bandplan].min; + } +} + +static uint32_t tef665x_get_max_frequency(radio_band_t band) +{ + if(band == BAND_FM) { + return known_fm_band_plans[fm_bandplan].max; + } else { + return known_am_band_plans[am_bandplan].max; + } +} + +static uint32_t tef665x_get_frequency_step(radio_band_t band) +{ + uint32_t ret = 0; + + switch (band) { + case BAND_AM: + ret = known_am_band_plans[am_bandplan].step; + break; + case BAND_FM: + ret = known_fm_band_plans[fm_bandplan].step; + break; + default: + break; + } + return ret; +} +radio_impl_ops_t tef665x_impl_ops = { + .name = "TEF665x", + .init = tef665x_init, + .start = tef665x_start, + .stop = tef665x_stop, + .set_output = tef665x_set_output, + .get_frequency = tef665x_get_frequency, + .set_frequency = tef665x_set_frequency, + .set_frequency_callback = tef665x_set_frequency_callback, + .set_rds_callback=tef665x_set_rds_callback, + .get_band = tef665x_get_band, + .set_band = tef665x_set_band, + .band_supported = tef665x_band_supported, + .get_min_frequency = tef665x_get_min_frequency, + .get_max_frequency = tef665x_get_max_frequency, + .get_frequency_step = tef665x_get_frequency_step, + .scan_start = tef665x_scan_start, + .scan_stop = tef665x_scan_stop, + .get_stereo_mode = tef665x_get_stereo_mode, + //.set_stereo_mode = tef665x_set_stereo_mode,*/ + .get_rds_info = tef665x_get_rds_info +}; diff --git a/binding/radio_impl_tef665x.h b/binding/radio_impl_tef665x.h new file mode 100644 index 0000000..a95fd99 --- /dev/null +++ b/binding/radio_impl_tef665x.h @@ -0,0 +1,23 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _RADIO_IMPL_TEF665X_H +#define _RADIO_IMPL_TEF665X_H + +#include "radio_impl.h" + +extern radio_impl_ops_t tef665x_impl_ops; + + +#endif /* _RADIO_IMPL_TEF665X_H */ + diff --git a/binding/radio_output_gstreamer.c b/binding/radio_output_gstreamer.c index f492bb5..5359deb 100644 --- a/binding/radio_output_gstreamer.c +++ b/binding/radio_output_gstreamer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Konsulko Group + * Copyright (C) 2018, 2019 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,10 @@ #include "radio_output.h" #include "rtl_fm.h" +// Flag to enable using GST_STATE_READY instead of GST_STATE_PAUSED to trigger +// Wireplumber policy mechanism. Hopefully temporary. +#define WIREPLUMBER_WORKAROUND + // Output buffer static unsigned int extra; static int16_t extra_buf[1]; @@ -61,7 +65,7 @@ int radio_output_open() queue = gst_element_factory_make("queue", "queue"); convert = gst_element_factory_make("audioconvert", "convert"); resample = gst_element_factory_make("audioresample", "resample"); - sink = gst_element_factory_make("alsasink", "sink"); + sink = gst_element_factory_make("pwaudiosink", "sink"); if(!(pipeline && appsrc && queue && convert && resample && sink)) { fprintf(stderr, "pipeline element construction failed!\n"); } @@ -73,6 +77,7 @@ int radio_output_open() "layout", G_TYPE_STRING, "interleaved", "channel-mask", G_TYPE_UINT64, 3, NULL), NULL); + gst_util_set_object_arg(sink, "stream-properties", "p,media.role=Multimedia"); if((p = getenv("RADIO_OUTPUT"))) { fprintf(stderr, "Using output device %s\n", p); @@ -95,7 +100,11 @@ int radio_output_open() NULL); // Start pipeline in paused state +#ifdef WIREPLUMBER_WORKAROUND + gst_element_set_state(pipeline, GST_STATE_READY); +#else gst_element_set_state(pipeline, GST_STATE_PAUSED); +#endif // Set up output buffer extra = 0; @@ -128,7 +137,11 @@ void radio_output_stop(void) if(pipeline && running) { // Stop pipeline running = false; +#ifdef WIREPLUMBER_WORKAROUND + gst_element_set_state(pipeline, GST_STATE_READY); +#else gst_element_set_state(pipeline, GST_STATE_PAUSED); +#endif // Flush pipeline // This seems required to avoid stutters on starts after a stop diff --git a/binding/tef665x.h b/binding/tef665x.h new file mode 100644 index 0000000..10874e8 --- /dev/null +++ b/binding/tef665x.h @@ -0,0 +1,397 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEF665X_H +#define TEF665X_H + +typedef uint8_t u8; +typedef uint16_t ushort; +typedef uint32_t uint; + +typedef enum +{ +_close = 0, +_open +} I2C_STATE; + +typedef enum +{ + TEF665X_MODULE_FM = 0x20, //32 + TEF665X_MODULE_AM = 0x21, //31 + TEF665X_MODULE_AUDIO = 0x30, //48 + TEF665X_MODULE_APPL = 0x40 //64 +} TEF665x_MODULE; + +enum { + TEF6657_CMD_tune = 0, + TEF6657_CMD_open = 1, + TEF6657_CMD_close = 2, + TEF6657_CMD_am = 3, + TEF6657_CMD_fm = 4, + TEF6657_CMD_GETamSTAUS = 5, + TEF6657_CMD_GETfmSTAUS = 6, + TEF6657_CMD_GEToirtSTAUS +}; + +enum +{ + RADIO_BOOT_STATE = 0, + RADIO_IDLE_STATE, + RADIO_STANBDY_STATE, + RADIO_FM_STATE, + RADIO_AM_STATE + +}; + +typedef enum +{ + TEF665X_Cmd_Set_OperationMode = 0x01, + TEF665X_Cmd_Set_GPIO = 0x03, + TEF665X_Cmd_Set_ReferenceClock = 0x04, + TEF665X_Cmd_Activate = 0x05, + + TEF665X_Cmd_Get_Operation_Status = 0x80, //128, + TEF665X_Cmd_Get_GPIO_Status = 0x81, //129, + TEF665X_Cmd_Get_Identification = 0x82, //130, + TEF665X_Cmd_Get_LastWrite = 0x83, //131 +} TEF665x_APPL_COMMAND; + +typedef enum +{ + TEF665X_Cmd_Set_RDS_mode = 0x01, // default + TEF665X_Cmd_Set_RDS_autorestart= 0x02, // restart after tune + TEF665X_Cmd_Set_RDS_interface = 0, // no interface +} TEF665x_FM_COMMAND; + +typedef enum +{ + TEF665X_Cmd_Tune_To = 0x01, + TEF665X_Cmd_Set_Tune_Options =0x02, + TEF665X_Cmd_Set_Bandwidth =0x0A, //10, + TEF665X_Cmd_Set_RFAGC =0x0B, //11, + TEF665X_Cmd_Set_Antenna =0x0C, //12, + + TEF665X_Cmd_Set_MphSuppression =0x14, //20, + TEF665X_Cmd_Set_NoiseBlanker =0x17, //23, + TEF665X_Cmd_Set_NoiseBlanker_Audio =0x18, //24, + + TEF665X_Cmd_Set_DigitalRadio =0x1E, //30, + TEF665X_Cmd_Set_Deemphasis =0x1F, //31, + + TEF665X_Cmd_Set_LevelStep = 0x26, //38, + TEF665X_Cmd_Set_LevelOffset = 0x27, //39, + + TEF665X_Cmd_Set_Softmute_Time =0x28, //40, + TEF665X_Cmd_Set_Softmute_Mod = 0x29, //41, + TEF665X_Cmd_Set_Softmute_Level =0x2A, //42, + TEF665X_Cmd_Set_Softmute_Noise =0x2B, //43, + TEF665X_Cmd_Set_Softmute_Mph =0x2C, //44, + TEF665X_Cmd_Set_Softmute_Max =0x2D, //45, + + TEF665X_Cmd_Set_Highcut_Time =0x32, //50, + TEF665X_Cmd_Set_Highcut_Mod = 0x33, //51, + TEF665X_Cmd_Set_Highcut_Level =0x34, //52, + TEF665X_Cmd_Set_Highcut_Noise = 0x35, //53, + TEF665X_Cmd_Set_Highcut_Mph =0x36, //54, + TEF665X_Cmd_Set_Highcut_Max =0x37, //55, + TEF665X_Cmd_Set_Highcut_Min =0x38, //56, + TEF665X_Cmd_Set_Lowcut_Min =0x3A, //58, + + TEF665X_Cmd_Set_Stereo_Time =0x3C, //60, + TEF665X_Cmd_Set_Stereo_Mod =0x3D, //61, + TEF665X_Cmd_Set_Stereo_Level =0x3E, //62, + TEF665X_Cmd_Set_Stereo_Noise = 0x3F, //63, + TEF665X_Cmd_Set_Stereo_Mph =0x40, //64, + TEF665X_Cmd_Set_Stereo_Max = 0x41, //65, + TEF665X_Cmd_Set_Stereo_Min = 0x42, //66, + + TEF665X_Cmd_Set_Scaler = 0x50, //80, + TEF665X_Cmd_Set_RDS = 0x51, //81, + TEF665X_Cmd_Set_QualityStatus = 0x52, //82, + TEF665X_Cmd_Set_DR_Blend = 0x53, //83, + TEF665X_Cmd_Set_DR_Options = 0x54, //84, + TEF665X_Cmd_Set_Specials = 0x55, //85, + + TEF665X_Cmd_Get_Quality_Status = 0x80, //128, + TEF665X_Cmd_Get_Quality_Data = 0x81, //129, + TEF665X_Cmd_Get_RDS_Status = 0x82, //130, + TEF665X_Cmd_Get_RDS_Data = 0x83, //131, + TEF665X_Cmd_Get_AGC = 0x84, //132, + TEF665X_Cmd_Get_Signal_Status = 0x85, //133, + TEF665X_Cmd_Get_Processing_Status = 0x86, //134, + TEF665X_Cmd_Get_Interface_Status = 0x87, //135, + + TEF665X_Cmd_Set_Output_signal_i2s = 0x21, //33, + TEF665X_Cmd_Set_Output_signal_dac = 0x80, //128, + TEF665X_Cmd_Set_Output_source_aRadio = 0x04, //4, + TEF665X_Cmd_Set_Output_source_dInput = 0x20, //32, + TEF665X_Cmd_Set_Output_source_aProcessor = 0xe0, //224, + TEF665X_Cmd_Set_Output_source_SinWave = 0xf0, //240, + } TEF665x_RADIO_COMMAND; + +typedef enum +{ + TEF665X_Cmd_Set_Volume = 0x0A, //10, + TEF665X_Cmd_Set_Mute = 0x0B, //11, + TEF665X_Cmd_Set_Input = 0x0C, //12, + TEF665X_Cmd_Set_Output_Source = 0x0D, //13, + + TEF665X_Cmd_Set_Ana_Out = 0x15, //21, + TEF665X_Cmd_Set_Dig_IO = 0x16, //22, + TEF665X_Cmd_Set_Input_Scaler = 0x17, //23, + TEF665X_Cmd_Set_WaveGen = 0x18, //24 +} TEF665x_AUDIO_COMMAND; + +typedef enum +{ + TEF665X_AUDIO_CMD_22_SIGNAL_i2s1 = 0x21, //33, + TEF665X_AUDIO_CMD_22_MODE_voltage = 0x02, //2, + TEF665X_AUDIO_CMD_22_FORMAT_16 = 0x10, //16, + TEF665X_AUDIO_CMD_22_FORMAT_32 = 0x20, //32, + TEF665X_AUDIO_CMD_22_OPERATION_slave = 0, //0, + TEF665X_AUDIO_CMD_22_OPERATION_master = 0x0100,//256, + TEF665X_AUDIO_CMD_22_SAMPLERATE_48K = 0x12c0 //4800 +} TEF665x_AUDIO_CMD_22; + +typedef enum{ + eDevTEF665x_Boot_state , + eDevTEF665x_Idle_state, + eDevTEF665x_Wait_Active, + eDevTEF665x_Active_state, + + eDevTEF665x_Power_on, + + eDevTEF665x_Not_Exist, + + eDevTEF665x_Last +}TEF665x_STATE; + +const u8 patchByteValues[]= +{ + 0xF0, 0x00, 0x38, 0x16, 0xD0, 0x80, 0x43, 0xB2, 0x38, 0x1D, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xC2, 0xF7, 0xF0, 0x00, 0x38, 0x4E, 0xD0, 0x80, + 0xF0, 0x00, 0x38, 0xF1, 0xD0, 0x80, 0xC4, 0xA2, 0x02, 0x0C, 0x60, 0x04, 0x90, 0x01, 0x39, 0x07, 0xD0, 0x80, 0xF0, 0x00, 0x38, 0xD3, 0xD0, 0x80, + 0xF0, 0x00, 0x39, 0x0E, 0xD2, 0x80, 0xF0, 0x00, 0x39, 0x12, 0xD0, 0x80, 0x40, 0x20, 0x39, 0x1E, 0xD0, 0x80, 0x9E, 0x30, 0x18, 0xF9, 0xD2, 0x80, + 0xF0, 0x00, 0x39, 0x20, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x23, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x38, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x3B, 0xD0, 0x80, + 0x00, 0x43, 0x39, 0x43, 0xD9, 0x80, 0xF0, 0x00, 0x39, 0x46, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x60, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x71, 0xD0, 0x80, + 0xF0, 0x00, 0x39, 0x7F, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x82, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x14, 0xF0, 0x00, 0x70, 0x00, 0xA0, 0xD4, + 0xF0, 0x00, 0x70, 0x00, 0xA0, 0xDB, 0xF0, 0x00, 0x70, 0x00, 0xA1, 0x0C, 0xF0, 0x00, 0x70, 0x00, 0xA1, 0x12, 0xF0, 0x00, 0x70, 0x00, 0xA1, 0x2D, + 0xF0, 0x00, 0x20, 0x31, 0xD0, 0x80, 0x00, 0x7F, 0x60, 0x02, 0xE2, 0x00, 0xF0, 0x00, 0x0E, 0x22, 0x60, 0x0A, 0xF0, 0x00, 0x00, 0xFF, 0x60, 0x03, + 0xF0, 0x00, 0x01, 0x42, 0xD2, 0x80, 0x90, 0x03, 0x40, 0x02, 0xF0, 0x00, 0x90, 0x43, 0x01, 0x70, 0xD1, 0x80, 0xF0, 0x00, 0x01, 0x69, 0xD0, 0x80, + 0x0E, 0x69, 0x60, 0x0A, 0xA1, 0x60, 0x20, 0x23, 0x00, 0x01, 0x60, 0x01, 0xF0, 0x00, 0x70, 0x00, 0xF0, 0x00, 0xC4, 0xCB, 0x70, 0x00, 0xF0, 0x00, + 0xCA, 0x09, 0x30, 0x23, 0xF0, 0x00, 0xC2, 0xCB, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x23, 0xD0, 0x08, 0x82, 0x00, 0x0D, 0x50, 0x60, 0x08, + 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x09, 0x30, 0x00, 0x21, 0x80, 0x60, 0x01, 0xF0, 0x00, 0x40, 0x32, 0xF0, 0x00, 0x30, 0x11, 0x45, 0xF3, 0xF0, 0x00, + 0x30, 0x92, 0x2D, 0x30, 0x60, 0x04, 0x31, 0x13, 0x2D, 0x40, 0x60, 0x05, 0x31, 0x94, 0x7F, 0xFF, 0x60, 0x06, 0x32, 0x15, 0x0D, 0x61, 0x60, 0x0A, + 0x32, 0x96, 0x0D, 0x6B, 0x60, 0x0B, 0x33, 0x10, 0x0D, 0x50, 0x60, 0x01, 0x33, 0x90, 0x0D, 0x5C, 0x60, 0x02, 0x30, 0x21, 0x0D, 0x63, 0x60, 0x03, + 0x30, 0x31, 0x0D, 0x75, 0x60, 0x0C, 0x30, 0xA2, 0x8D, 0x00, 0x60, 0x01, 0x30, 0xB3, 0x01, 0x73, 0x60, 0x02, 0x30, 0x41, 0x00, 0x25, 0x60, 0x03, + 0x30, 0xC2, 0x40, 0x44, 0xF0, 0x00, 0x31, 0x43, 0x40, 0x35, 0xF0, 0x00, 0x31, 0xC4, 0x64, 0x00, 0x60, 0x06, 0x32, 0x45, 0x1F, 0x40, 0x60, 0x07, + 0x32, 0xC6, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x47, 0x1E, 0xBC, 0x60, 0x0D, 0x33, 0xC0, 0x01, 0x22, 0x60, 0x01, 0x34, 0x40, 0xFD, 0xEE, 0x60, 0x02, + 0x30, 0x51, 0x7B, 0x8F, 0x60, 0x03, 0x30, 0xD2, 0xC4, 0x29, 0x60, 0x04, 0x31, 0x51, 0x1E, 0xC2, 0x60, 0x0E, 0x32, 0x53, 0xFF, 0x0D, 0x60, 0x02, + 0x32, 0xD4, 0x7D, 0x2E, 0x60, 0x03, 0x30, 0x61, 0xC1, 0x9A, 0x60, 0x04, 0x30, 0xE2, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x61, 0x70, 0x00, 0xF0, 0x00, + 0x32, 0x63, 0x70, 0x00, 0xF0, 0x00, 0x32, 0xE4, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x03, 0x70, 0xD2, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x02, + 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x59, 0xF0, 0x00, 0x02, 0x15, 0xD0, 0x80, 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x0F, 0xF0, 0x00, 0x05, 0x17, 0x60, 0x0E, + 0x23, 0xF6, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x21, 0x63, 0x41, 0xF5, 0x91, 0x8F, 0x21, 0xF8, 0x40, 0x74, 0xC3, 0xEF, 0x21, 0xE0, 0xF0, 0x00, + 0xC3, 0xA4, 0x33, 0xF7, 0xF0, 0x00, 0xD8, 0x5B, 0x70, 0x00, 0xF0, 0x00, 0x82, 0x18, 0x70, 0x00, 0xF0, 0x00, 0x9F, 0xAF, 0x18, 0x00, 0xF0, 0x00, + 0x9F, 0x0F, 0x31, 0xF8, 0x90, 0x02, 0xF0, 0x00, 0x70, 0x00, 0x90, 0x28, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x22, 0x78, 0xF0, 0x00, + 0x16, 0xD3, 0x60, 0x09, 0xA0, 0x7F, 0x35, 0xF0, 0x1E, 0xBC, 0x60, 0x0D, 0xF0, 0x00, 0x0D, 0x61, 0x60, 0x08, 0xF0, 0x00, 0x03, 0xA5, 0xD2, 0x80, + 0xF0, 0x00, 0x1E, 0xC2, 0x60, 0x0D, 0xF0, 0x00, 0x0D, 0x6B, 0x60, 0x08, 0xF0, 0x00, 0x03, 0xA5, 0xD2, 0x80, 0xF0, 0x00, 0x21, 0x00, 0xF0, 0x00, + 0x83, 0x6D, 0x22, 0xF1, 0xF0, 0x00, 0xF0, 0x00, 0x23, 0x77, 0xF0, 0x00, 0x90, 0x41, 0x36, 0x70, 0xF0, 0x00, 0x9E, 0x79, 0x70, 0x00, 0x90, 0x01, + 0xF0, 0x00, 0x32, 0xF1, 0xD0, 0x08, 0x91, 0xC7, 0x33, 0x75, 0xF0, 0x00, 0xF0, 0x00, 0x34, 0x70, 0xE6, 0x00, 0xF0, 0x00, 0x34, 0xF0, 0xE6, 0x00, + 0xF0, 0x00, 0x24, 0x74, 0xF0, 0x00, 0xF0, 0x00, 0x24, 0xF3, 0xF0, 0x00, 0x8C, 0x24, 0x26, 0xF2, 0x40, 0x16, 0x8A, 0x1B, 0x34, 0x74, 0x4F, 0xF5, + 0x82, 0xB7, 0x34, 0xF3, 0xF0, 0x00, 0xF0, 0x00, 0x20, 0x71, 0x90, 0x05, 0x83, 0x04, 0x70, 0x00, 0xF0, 0x00, 0x8E, 0x67, 0x70, 0x00, 0xF0, 0x00, + 0xF0, 0x00, 0x70, 0x00, 0x90, 0x02, 0xF0, 0x00, 0x36, 0xF6, 0xF0, 0x00, 0xF0, 0x00, 0x34, 0xF0, 0x80, 0x06, 0x82, 0xAF, 0x70, 0x00, 0xF0, 0x00, + 0x82, 0x1B, 0x70, 0x00, 0xD0, 0x09, 0x8E, 0x5F, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x09, 0xF0, 0x00, 0x36, 0xF5, 0xF0, 0x00, + 0xF0, 0x00, 0x34, 0x70, 0xF0, 0x00, 0x40, 0x11, 0x27, 0x72, 0xA1, 0x03, 0x90, 0x8A, 0x20, 0xF3, 0xA1, 0x02, 0x8E, 0xD7, 0x37, 0x72, 0xF0, 0x00, + 0xF0, 0x00, 0x37, 0xF1, 0xE6, 0x00, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x22, 0x7A, 0xF0, 0x00, 0x16, 0xC3, 0x60, 0x09, 0xA0, 0x58, + 0xF0, 0x00, 0x18, 0x20, 0xF0, 0x00, 0xF0, 0x00, 0x35, 0x70, 0xF0, 0x00, 0xF0, 0x00, 0x32, 0x7A, 0xD0, 0x08, 0x82, 0x00, 0x0D, 0x51, 0x60, 0x08, + 0x40, 0x03, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x80, 0x70, 0x00, 0xF0, 0x00, 0x21, 0x06, 0x70, 0x00, 0xF0, 0x00, 0x37, 0x00, 0x70, 0x00, 0xF0, 0x00, + 0x37, 0x80, 0x40, 0x15, 0xF0, 0x00, 0x36, 0x83, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x05, 0x0D, 0x61, 0x60, 0x09, 0x32, 0x86, 0x0D, 0x6B, 0x60, 0x0A, + 0x32, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x32, 0x90, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x90, 0x70, 0x00, 0xF0, 0x00, + 0x34, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x34, 0x90, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x90, 0x70, 0x00, 0xF0, 0x00, + 0x32, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x32, 0xA0, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x33, 0xA0, 0x70, 0x00, 0xF0, 0x00, + 0x34, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x34, 0xA0, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x31, 0xA0, 0x70, 0x00, 0xF0, 0x00, + 0x82, 0x00, 0x0D, 0x30, 0x60, 0x0A, 0x0D, 0x40, 0x60, 0x0B, 0xC0, 0x10, 0xF0, 0x00, 0x10, 0x20, 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x0C, 0xC0, 0x10, + 0xF0, 0x00, 0x10, 0x30, 0xF0, 0x00, 0xF0, 0x00, 0x35, 0xC0, 0xD0, 0x08, 0xF0, 0x00, 0x0D, 0x75, 0x60, 0x0F, 0xF0, 0x00, 0x05, 0x63, 0x60, 0x0E, + 0x24, 0xF7, 0x05, 0x1D, 0x60, 0x0D, 0x25, 0x76, 0x70, 0x00, 0xF0, 0x00, 0x91, 0xC7, 0x20, 0xE8, 0x40, 0x15, 0x91, 0x8F, 0x21, 0xE9, 0xD4, 0x09, + 0xC3, 0xEF, 0x20, 0x00, 0x40, 0x12, 0x9F, 0xBE, 0x20, 0x11, 0x58, 0x03, 0xA0, 0x80, 0x35, 0x77, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, + 0xF0, 0x00, 0x21, 0xF5, 0xF0, 0x00, 0xA0, 0xCA, 0x22, 0x54, 0xF0, 0x00, 0xCC, 0x09, 0x05, 0x17, 0x60, 0x0C, 0x83, 0x2C, 0x70, 0x00, 0xF0, 0x00, + 0x8A, 0x61, 0x70, 0x00, 0xF0, 0x00, 0xAE, 0x48, 0x22, 0x45, 0xA0, 0xCB, 0xA2, 0x28, 0x20, 0x78, 0xF0, 0x00, 0xF0, 0x00, 0x35, 0xF0, 0xF0, 0x00, + 0xF0, 0x00, 0x18, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x78, 0xF0, 0x00, 0x16, 0xE3, 0x60, 0x09, 0xA0, 0x27, 0x89, 0x01, 0x23, 0xF4, 0xF0, 0x00, + 0xF0, 0x00, 0x20, 0xF2, 0xF0, 0x00, 0x82, 0x61, 0x21, 0x73, 0xF0, 0x00, 0xA0, 0x50, 0x36, 0x70, 0xF0, 0x00, 0xA0, 0x58, 0x23, 0x72, 0xE1, 0x40, + 0xA8, 0x01, 0x22, 0xF3, 0xF0, 0x00, 0x90, 0x49, 0x22, 0x75, 0xE0, 0x40, 0x80, 0x61, 0x70, 0x00, 0xF0, 0x00, 0x8A, 0x51, 0x33, 0xF1, 0xF0, 0x00, + 0xA0, 0x58, 0x70, 0x00, 0xF0, 0x00, 0xAF, 0x48, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x34, 0x70, 0xD0, 0x08, 0x82, 0x00, 0x0D, 0x75, 0x60, 0x08, + 0x90, 0x09, 0x0D, 0x00, 0x60, 0x09, 0xF0, 0x00, 0x35, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x33, 0x80, 0xC0, 0x28, 0xF0, 0x00, 0x10, 0x10, 0xF0, 0x00, + 0xF0, 0x00, 0x34, 0x81, 0xD0, 0x08, 0x82, 0x49, 0x0D, 0x75, 0x60, 0x08, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xFD, 0x04, 0x00, 0x60, 0x00, 0xA0, 0xB1, + 0x8E, 0xC0, 0x40, 0x00, 0x60, 0x05, 0x60, 0x00, 0x60, 0x05, 0xE6, 0x00, 0xC8, 0x1B, 0x70, 0x00, 0xF0, 0x00, 0xD8, 0xDB, 0x0D, 0x51, 0x60, 0x08, + 0x83, 0x5B, 0x70, 0x00, 0xF0, 0x00, 0x9E, 0xBA, 0x30, 0x03, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x84, 0xD4, 0x09, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xAF, + 0xF0, 0x00, 0x0D, 0x75, 0x60, 0x08, 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x09, 0xF0, 0x00, 0x24, 0x03, 0xF0, 0x00, 0xF0, 0x00, 0x27, 0x94, 0xD0, 0x08, + 0xA0, 0x03, 0x70, 0x00, 0xF0, 0x00, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x00, 0x00, 0x11, 0x08, 0x00, 0xC0, 0x0E, 0xA0, 0x09, 0x00, 0x11, 0x08, 0x00, + 0xA0, 0x09, 0x70, 0x00, 0xF0, 0x00, 0xA4, 0x08, 0x70, 0x00, 0xD0, 0x08, 0xA0, 0x03, 0x70, 0x00, 0xF0, 0x00, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x00, + 0x00, 0x11, 0x08, 0x00, 0xC0, 0x26, 0xA0, 0x09, 0x00, 0x11, 0x08, 0x00, 0xA0, 0x09, 0x70, 0x00, 0xF0, 0x00, 0xA4, 0x08, 0x70, 0x00, 0xD0, 0x08, + 0xF0, 0x00, 0x1D, 0x01, 0x60, 0x08, 0xF0, 0x00, 0x0A, 0x2C, 0x60, 0x00, 0xF0, 0x00, 0x01, 0x1A, 0x60, 0x01, 0x31, 0x00, 0x70, 0x00, 0xF0, 0x00, + 0x31, 0x81, 0x70, 0x00, 0xD0, 0x08, 0x10, 0x00, 0x60, 0x03, 0xA0, 0x93, 0x30, 0x23, 0x07, 0x73, 0xD2, 0x80, 0xF0, 0x00, 0x07, 0xC6, 0xD0, 0x80, + 0x40, 0xE0, 0x00, 0x1F, 0x60, 0x01, 0x13, 0xD5, 0x60, 0x07, 0xA0, 0x06, 0x13, 0xFB, 0x60, 0x06, 0xF0, 0x00, 0x90, 0x40, 0x0D, 0x28, 0xD2, 0x80, + 0x14, 0x05, 0x60, 0x06, 0xF0, 0x00, 0xF0, 0x00, 0x0D, 0x28, 0xD2, 0x80, 0x14, 0x0F, 0x60, 0x06, 0xF0, 0x00, 0xF0, 0x00, 0x0D, 0x28, 0xD0, 0x80, + 0xD7, 0xCA, 0x00, 0xFF, 0x60, 0x04, 0x81, 0xD7, 0x0C, 0xF7, 0x60, 0x09, 0xD0, 0x56, 0x70, 0x00, 0xF0, 0x00, 0x82, 0x76, 0x30, 0x17, 0xF0, 0x00, + 0xD0, 0xF6, 0x40, 0x83, 0xF0, 0x00, 0xC1, 0xA4, 0x20, 0x19, 0xF0, 0x00, 0x82, 0xF6, 0x70, 0x00, 0xF0, 0x00, 0xC1, 0x80, 0x20, 0x17, 0xA0, 0x81, + 0xC3, 0xE7, 0x70, 0x00, 0xF0, 0x00, 0xC5, 0xC7, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0xB1, 0xD0, 0x80, 0x40, 0x00, 0x70, 0x00, 0xAF, 0xF0, + 0x41, 0xF1, 0x40, 0x40, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0xB2, 0xD0, 0x80, 0x40, 0x71, 0x10, 0xB2, 0xD2, 0x80, 0xF0, 0x00, 0x10, 0x81, 0xD0, 0x80, + 0xF0, 0x00, 0x0B, 0xC9, 0x60, 0x08, 0xF0, 0x00, 0x1D, 0x8D, 0xD2, 0x80, 0xF0, 0x00, 0x16, 0xD5, 0xD0, 0x80, 0xF0, 0x00, 0x0B, 0xC9, 0x60, 0x08, + 0xF0, 0x00, 0x1D, 0x8F, 0xD2, 0x80, 0xF0, 0x00, 0x16, 0xDA, 0xD0, 0x80, 0xF0, 0x00, 0x0B, 0x60, 0x60, 0x0E, 0xF0, 0x00, 0x00, 0x04, 0x60, 0x03, + 0xF0, 0x00, 0x3F, 0xFC, 0x60, 0x04, 0x32, 0x63, 0x80, 0x08, 0x60, 0x05, 0x32, 0xE4, 0x19, 0x9A, 0x60, 0x06, 0x33, 0x65, 0x70, 0x00, 0xF0, 0x00, + 0x31, 0xE6, 0x70, 0x00, 0xD0, 0x08, 0x83, 0x6D, 0x0C, 0x35, 0x60, 0x08, 0x40, 0x60, 0x39, 0x36, 0x60, 0x01, 0x41, 0xE2, 0x21, 0x96, 0x60, 0x03, + 0x33, 0x00, 0x41, 0x44, 0xF0, 0x00, 0x33, 0x81, 0x70, 0x00, 0xF0, 0x00, 0x34, 0x02, 0x70, 0x00, 0xF0, 0x00, 0x34, 0x83, 0x70, 0x00, 0xF0, 0x00, + 0x35, 0x04, 0x70, 0x00, 0xF0, 0x00, 0x35, 0x85, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x70, 0x00, 0xAF, 0x54, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0x99, + 0xF0, 0x00, 0x70, 0x00, 0xAF, 0x92, 0xF0, 0x00, 0x0C, 0x51, 0xD2, 0x80, 0xF0, 0x00, 0x21, 0xA0, 0xD0, 0x80, 0xF0, 0x00, 0x05, 0x2E, 0xD2, 0x80, + 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x01, 0xF0, 0x00, 0x21, 0xB7, 0xD0, 0x80, 0xF0, 0x00, 0x20, 0xF0, 0xD2, 0x80, 0x90, 0x02, 0x27, 0xDF, 0xD2, 0x80, + 0x9E, 0x69, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x07, 0x00, 0xD1, 0x80, 0xF0, 0x00, 0x23, 0x20, 0xD0, 0x80, 0x17, 0x0B, 0x60, 0x0C, 0xA0, 0x41, + 0x00, 0x40, 0x40, 0x05, 0xF0, 0x00, 0x00, 0x41, 0x24, 0x3F, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xAE, 0xDD, 0xF0, 0x00, 0x0C, 0x8D, 0x60, 0x08, + 0xF0, 0x00, 0x26, 0x0A, 0xD0, 0x80, 0x83, 0xFF, 0x0D, 0x83, 0x60, 0x08, 0xF0, 0x00, 0x01, 0xF4, 0x60, 0x00, 0xF0, 0x00, 0x03, 0xB1, 0x60, 0x01, + 0x10, 0x00, 0x03, 0xB2, 0x60, 0x02, 0x10, 0x01, 0x04, 0x0E, 0x60, 0x00, 0x10, 0x02, 0x04, 0x0F, 0x60, 0x01, 0x10, 0x00, 0x04, 0x5C, 0x60, 0x02, + 0x10, 0x01, 0x04, 0x5D, 0x60, 0x00, 0x10, 0x02, 0x13, 0x80, 0x60, 0x01, 0x10, 0x00, 0x0D, 0x8E, 0x60, 0x09, 0x10, 0x01, 0x02, 0xEE, 0x60, 0x00, + 0x10, 0x07, 0x43, 0x06, 0x60, 0x01, 0x10, 0x10, 0x04, 0x69, 0x60, 0x02, 0x10, 0x11, 0x44, 0x87, 0x60, 0x00, 0x10, 0x12, 0x05, 0xE3, 0x60, 0x01, + 0x10, 0x10, 0x46, 0x08, 0x60, 0x02, 0x10, 0x11, 0x06, 0xAE, 0x60, 0x00, 0x10, 0x12, 0x0D, 0x8C, 0x60, 0x08, 0x10, 0x10, 0x9E, 0x3C, 0x60, 0x00, + 0x10, 0x17, 0x0D, 0x82, 0x60, 0x09, 0x10, 0x00, 0x70, 0x00, 0xF0, 0x00, 0x10, 0x07, 0x70, 0x00, 0xF0, 0x00, 0x10, 0x17, 0x70, 0x00, 0xD0, 0x08, + 0x0D, 0x83, 0x60, 0x08, 0xA0, 0x24, 0xF0, 0x00, 0x00, 0x02, 0xA0, 0x23, 0x90, 0x82, 0x70, 0x00, 0xF0, 0x00, 0x82, 0x8A, 0x70, 0x00, 0x90, 0x03, + 0x90, 0x8A, 0x70, 0x00, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xFB, 0x82, 0xBF, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x01, 0x99, 0xD2, 0x80, + 0xF0, 0x00, 0x0D, 0x82, 0x60, 0x08, 0xF0, 0x00, 0x26, 0xC1, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x02, 0xA0, 0x1A, 0x90, 0x82, 0x70, 0x00, 0xF0, 0x00, + 0x82, 0x8A, 0x70, 0x00, 0x90, 0x03, 0x90, 0x8A, 0x70, 0x00, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xFB, 0x82, 0x80, 0x70, 0x00, 0xF0, 0x00, + 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x0D, 0x8E, 0x60, 0x0D, 0xF0, 0x00, 0x3F, 0xFF, 0x60, 0x02, 0xF0, 0x00, 0x00, 0x51, 0xA0, 0x11, + 0xC2, 0x53, 0x70, 0x00, 0xF0, 0x00, 0x8E, 0xC4, 0x70, 0x00, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0x97, 0xFC, 0xD4, 0x8F, 0x01, 0x99, 0xD2, 0x80, + 0x40, 0x05, 0x0D, 0x8C, 0x60, 0x0D, 0x40, 0x17, 0x3F, 0xFF, 0x60, 0x02, 0x9F, 0x7D, 0x00, 0x51, 0xA0, 0x0A, 0xC2, 0x53, 0x70, 0x00, 0xF0, 0x00, + 0x82, 0xC4, 0x27, 0x1C, 0xD1, 0x80, 0xF0, 0x00, 0x70, 0x00, 0x97, 0xFC, 0x91, 0x46, 0x27, 0x20, 0xD0, 0x80, 0xF0, 0x00, 0x29, 0x15, 0xD2, 0x80, + 0xF0, 0x00, 0x39, 0x86, 0xD2, 0x80, 0xF0, 0x00, 0x28, 0x88, 0xD0, 0x80, 0x01, 0x52, 0x60, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x28, 0xDC, 0xD2, 0x80, + 0xF0, 0x00, 0x28, 0xD5, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x16, 0xC3, 0x60, 0x08, 0xF0, 0x00, 0x00, 0x9A, 0x60, 0x00, + 0xF0, 0x00, 0x02, 0xE3, 0x60, 0x00, 0x10, 0x00, 0x04, 0xF4, 0x60, 0x00, 0x10, 0x00, 0x06, 0xFF, 0x60, 0x00, 0x10, 0x00, 0x09, 0x07, 0x60, 0x00, + 0x10, 0x00, 0x0B, 0x10, 0x60, 0x00, 0x10, 0x00, 0x0D, 0x1F, 0x60, 0x00, 0x10, 0x00, 0x0F, 0x65, 0x60, 0x00, 0x10, 0x00, 0x0F, 0x65, 0x60, 0x00, + 0x10, 0x00, 0x0D, 0x1F, 0x60, 0x00, 0x10, 0x00, 0x0B, 0x10, 0x60, 0x00, 0x10, 0x00, 0x09, 0x07, 0x60, 0x00, 0x10, 0x00, 0x06, 0xFF, 0x60, 0x00, + 0x10, 0x00, 0x04, 0xF4, 0x60, 0x00, 0x10, 0x00, 0x02, 0xE3, 0x60, 0x00, 0x10, 0x00, 0x00, 0x9A, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, + 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x16, 0xD3, 0x60, 0x08, 0xF0, 0x00, 0xFF, 0x93, 0x60, 0x00, 0xF0, 0x00, 0xFE, 0x37, 0x60, 0x00, + 0x10, 0x00, 0xFD, 0xD9, 0x60, 0x00, 0x10, 0x00, 0xFE, 0x9F, 0x60, 0x00, 0x10, 0x00, 0x03, 0x85, 0x60, 0x00, 0x10, 0x00, 0x0D, 0x6B, 0x60, 0x00, + 0x10, 0x00, 0x16, 0x84, 0x60, 0x00, 0x10, 0x00, 0x1E, 0x49, 0x60, 0x00, 0x10, 0x00, 0x1E, 0x49, 0x60, 0x00, 0x10, 0x00, 0x16, 0x84, 0x60, 0x00, + 0x10, 0x00, 0x0D, 0x6B, 0x60, 0x00, 0x10, 0x00, 0x03, 0x85, 0x60, 0x00, 0x10, 0x00, 0xFE, 0x9F, 0x60, 0x00, 0x10, 0x00, 0xFD, 0xD9, 0x60, 0x00, + 0x10, 0x00, 0xFE, 0x37, 0x60, 0x00, 0x10, 0x00, 0xFF, 0x93, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, + 0xF0, 0x00, 0x16, 0xE3, 0x60, 0x08, 0xF0, 0x00, 0x00, 0x64, 0x60, 0x00, 0xF0, 0x00, 0xFF, 0xA8, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xA6, 0x60, 0x00, + 0x10, 0x00, 0xFF, 0xDF, 0x60, 0x00, 0x10, 0x00, 0x00, 0x01, 0x60, 0x00, 0x10, 0x00, 0x01, 0x28, 0x60, 0x00, 0x10, 0x00, 0x01, 0x2F, 0x60, 0x00, + 0x10, 0x00, 0xFD, 0x23, 0x60, 0x00, 0x10, 0x00, 0xFD, 0xA1, 0x60, 0x00, 0x10, 0x00, 0x03, 0x89, 0x60, 0x00, 0x10, 0x00, 0x02, 0x54, 0x60, 0x00, + 0x10, 0x00, 0x0E, 0xA2, 0x60, 0x00, 0x10, 0x00, 0x0C, 0x47, 0x60, 0x00, 0x10, 0x00, 0xF9, 0x42, 0x60, 0x00, 0x10, 0x00, 0xFB, 0xE1, 0x60, 0x00, + 0x10, 0x00, 0x00, 0x8C, 0x60, 0x00, 0x10, 0x00, 0xFE, 0x7D, 0x60, 0x00, 0x10, 0x00, 0x02, 0x54, 0x60, 0x00, 0x10, 0x00, 0x03, 0x89, 0x60, 0x00, + 0x10, 0x00, 0xFD, 0xA1, 0x60, 0x00, 0x10, 0x00, 0xFD, 0x23, 0x60, 0x00, 0x10, 0x00, 0x01, 0x2F, 0x60, 0x00, 0x10, 0x00, 0x01, 0x28, 0x60, 0x00, + 0x10, 0x00, 0x00, 0x01, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xDF, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xA6, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xA8, 0x60, 0x00, + 0x10, 0x00, 0x00, 0x64, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x17, 0x0B, 0x60, 0x08, + 0xF0, 0x00, 0x00, 0x03, 0x60, 0x00, 0xF0, 0x00, 0x54, 0xC0, 0x60, 0x00, 0x10, 0x00, 0x00, 0x05, 0x60, 0x00, 0x10, 0x00, 0x00, 0x05, 0x60, 0x00, + 0x10, 0x00, 0x00, 0x0F, 0x60, 0x00, 0x10, 0x00, 0x00, 0x0F, 0x60, 0x00, 0x10, 0x00, 0x09, 0xC0, 0x60, 0x00, 0x10, 0x00, 0x0A, 0x20, 0x60, 0x00, + 0x10, 0x00, 0x1D, 0x40, 0x60, 0x00, 0x10, 0x00, 0x1E, 0x60, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, + 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08 +}; + +unsigned char lutByteValues[]= +{ + /* + 0x40, 0x13, 0x41, 0x68, 0x41, 0xC1, 0x42, 0x14, + 0x47, 0xC5, 0x4D, 0x83, 0x4E, 0x49, 0x4E, 0x53, + 0x4F, 0x92, 0x4F, 0xEA, 0x50, 0x80, 0x56, 0x60, + 0x56, 0xD4, 0x56, 0xD9, 0x61, 0x9F, 0x61, 0xB6, + 0x64, 0x3B, 0x66, 0x09, 0x67, 0x0B, 0x67, 0x1A, + 0x68, 0x87, 0x68, 0xD4 + */ + 0x80, 0x17, 0x80, 0x45, 0x80, 0x96, 0x81, 0x5B, + 0x82, 0xD6, 0x88, 0x0B, 0x88, 0x10, 0x88, 0xDB, + 0x89, 0x48, 0x8B, 0x0A, 0x8B, 0x12, 0x8B, 0x13, + 0x8B, 0x21, 0x8B, 0x26, 0x8C, 0x6F, 0x94, 0xD0, + 0x95, 0x20, 0x95, 0x33, 0x95, 0x6F, 0x95, 0xA8, + 0x95, 0xAC, 0x95, 0xC3, 0x95, 0xE1, 0x97, 0xC9, + 0x97, 0xF6, 0x98, 0x8D, 0x99, 0x00, 0x9A, 0x00, + 0x9E, 0x73, 0xA0, 0x12, 0xA1, 0x03, 0xA2, 0xF0, + 0xA3, 0x0D, 0xA3, 0x4C, 0xA3, 0x52, 0xA3, 0xED, + 0xA8, 0x31, 0xAB, 0x01, 0xAB, 0x1F, 0xAB, 0x4C, + 0xAC, 0x76, 0xAC, 0x88, 0xAD, 0xB0, 0xAD, 0xB7, + 0xB0, 0xE2, 0xB1, 0x01, 0xB1, 0x0B, 0xB1, 0x35, + 0xB1, 0x3B, 0xB1, 0x97, 0xB3, 0x03 +}; + +const uint32_t patchSize = sizeof(patchByteValues); +const uint8_t *pPatchBytes = &patchByteValues[0]; + +const uint32_t lutSize = sizeof(lutByteValues); +const uint8_t *pLutBytes = &lutByteValues[0]; + +static const u8 init_para[] = { +//Set the Band related API settings... +11, 0x20, 0x0A, 0x01, 0x00, 0x01, 0x09, 0x38, 0x05, 0xDC, 0x05, 0xDC, //FM_BandWidth Auto, FM_Set_Bandwidth (1, 1, 2360, 1500, 1200) +5, 0x20, 0x14, 0x01, 0x00, 0x01, //FM_MphSuppression, FM_Set_MphSuppression (1, 1) +//5, 0x20, 0x16, 0x01, 0x00, 0x01, +7, 0x20, 0x17, 0x01, 0x00, 0x01, 0x05, 0xDC, //FM_NoiseBlanker, FM_Set_NoiseBlanker (1, 1, 1200) + +//Set all Weaksignal API settings (LevelOffset)... +//Set the SoftMute API settings... +9, 0x20, 0x2A, 0x01, 0x00, 0x03, 0x00, 0x64, 0x00, 0xFA, //FM_SmlMode, FM_Set_SoftMute_Level (1, 3, 100, 250) +11, 0x20, 0x28, 0x01, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x0A, 0x00, 0x14, //FM_SmSlowAttack,FM_Set_SoftMute_Time (1, 60, 120, 10, 20) +9, 0x20, 0x2C, 0x01, 0x00, 0x03, 0x01, 0x90, 0x03, 0xE8, //FM_Smm,FM_Set_SoftMute_Mph (1, 3, 400, 1000) +9, 0x20, 0x2B, 0x01, 0x00, 0x03, 0x01, 0x90, 0x03, 0xE8, //FM_Smn,FM_Set_SoftMute_Noise (1, 3, 400, 1000) +7, 0x20, 0x2D, 0x01, 0x00, 0x01, 0x00, 0x64, //FM_SmMaximum,FM_Set_SoftMute_Max (1, 1, 100) + +//Set the HighCut API settings... +9, 0x20, 0x34, 0x01, 0x00, 0x01, 0x01, 0xF4, 0x00, 0xC8, //FM_HclMode,FM_Set_HighCut_Level (1, 1, 500, 200) +11, 0x20, 0x32, 0x01, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x14, 0x00, 0x14, //FM_HcSlowAttack,FM_Set_HighCut_Time (1, 60, 120, 20, 20) +11, 0x20, 0x33, 0x01, 0x00, 0x01, 0x01, 0x90, 0x00, 0xC8, 0x03, 0x20, //FM_Hco,FM_Set_HighCut_Mod (1, 1, 400, 200, 800) +9, 0x20, 0x36, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Hcm,FM_Set_HighCut_Mph (1, 1, 100, 100) +9, 0x20, 0x35, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Hcn,FM_Set_HighCut_Noise (1, 1, 100, 100) +7, 0x20, 0x38, 0x01, 0x00, 0x01, 0x3A, 0x98, //FM_HcMinimum,FM_Set_HighCut_Min (1, 1, 15000) +7, 0x20, 0x3A, 0x01, 0x00, 0x01, 0x00, 0x0A, //FM_HcLowCutMinimum,FM_Set_LowCut_Min (1, 1, 100) +7, 0x20, 0x37, 0x01, 0x00, 0x01, 0x05, 0xDC, //FM_HcMaximum,FM_Set_HighCut_Max (1, 1, 1500) + +//Set the Stereo API settings... +9, 0x20, 0x3E, 0x01, 0x00, 0x01, 0x01, 0xF4, 0x00, 0xFA, //FM_StlMode,FM_Set_Stereo_Level (1, 1, 500, 250) +11, 0x20, 0x3C, 0x01, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x0A, 0x00, 0x14, //FM_StSlowAttack,FM_Set_Stereo_Time (1, 60, 120, 10, 20) +11, 0x20, 0x3D, 0x01, 0x00, 0x01, 0x00, 0xC8, 0x00, 0xC8, 0x03, 0xE8, //FM_Sto,FM_Set_Stereo_Mod (1, 1, 200, 200, 1000) +9, 0x20, 0x40, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Stm,FM_Set_Stereo_Mph (1, 1, 100, 100) +9, 0x20, 0x3F, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Stn,FM_Set_Stereo_Noise (1, 1, 100, 100) +7, 0x20, 0x39, 0x01, 0x00, 0x01, 0x00, 0x50, +9, 0x20, 0x4A, 0x01, 0x00, 0x03, 0x00, 0x50, 0x00, 0x80, +9, 0x20, 0x49, 0x01, 0x00, 0x03, 0x00, 0x50, 0x00, 0x80, + +//Set_Deemphasis +5, 0x20, 0x1F, 0x01, 0x02, 0xEE, + +//Set RDS +9, 0x20, 0x51, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + +//Set the Audio and Application related API settings... +9, 0x40, 0x03, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, //AM_GPIO 0 Feature +9, 0x40, 0x03, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, //AM_GPIO 1 Feature +9, 0x40, 0x03, 0x01, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, //AM_GPIO 2 Feature +9, 0x40, 0x03, 0x01, 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, //FM_GPIO 0 Feature set for RDS +9, 0x40, 0x03, 0x01, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, //FM_GPIO 1 Feature +9, 0x40, 0x03, 0x01, 0x00, 0x02, 0x00, 0x20, 0x00, 0x00, //FM_GPIO 2 Feature + +5, 0x20, 0x1E, 0x01, 0x00, 0x01 , +5, 0x20, 0x54, 0x01, 0x00, 0x00 , +7, 0x20, 0x0B, 0x01, 0x03, 0x7A, 0x00, 0x00 +//13, 0x30, 0x16, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x11, 0x3A, //Dig_IO_IIS_SD_1 Mode +//5, 0x30, 0x0B, 0x01, 0x00, 0x00, //Mute +//5, 0x30, 0x0A, 0x01, 0xFF, 0xf0, //Volume +//7, 0x30, 0x0D, 0x01, 0x00, 0x80, 0x00, 0xE0,//Audio Output Source DAC L/R +}; + +typedef enum +{ + eAR_TuningAction_Standby = 0, + eAR_TuningAction_Preset = 1, /*!< Tune to new program with short mute time */ + eAR_TuningAction_Search = 2, /*!< Tune to new program and stay muted */ + eAR_TuningAction_AF_Update = 3, /*!< Tune to alternative frequency, store quality and tune back with inaudible mute */ + eAR_TuningAction_Jump = 4, /*!< Tune to alternative frequency with short inaudible mute */ + eAR_TuningAction_Check = 5, /*!< Tune to alternative frequency and stay muted */ + eAR_TuningAction_End = 7 /*!< Release the mute of a Search/Check action (frequency is ignored) */ +} AR_TuningAction_t, *pAR_TuningAction_t; +#endif |