1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/hal/gonk/GonkFMRadio.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,457 @@ 1.4 +/* Copyright 2012 Mozilla Foundation and Mozilla contributors 1.5 + * 1.6 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.7 + * you may not use this file except in compliance with the License. 1.8 + * You may obtain a copy of the License at 1.9 + * 1.10 + * http://www.apache.org/licenses/LICENSE-2.0 1.11 + * 1.12 + * Unless required by applicable law or agreed to in writing, software 1.13 + * distributed under the License is distributed on an "AS IS" BASIS, 1.14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.15 + * See the License for the specific language governing permissions and 1.16 + * limitations under the License. 1.17 + */ 1.18 + 1.19 +#include "Hal.h" 1.20 +#include "tavarua.h" 1.21 +#include "nsThreadUtils.h" 1.22 +#include "mozilla/FileUtils.h" 1.23 + 1.24 +#include <cutils/properties.h> 1.25 +#include <errno.h> 1.26 +#include <fcntl.h> 1.27 +#include <linux/videodev2.h> 1.28 +#include <stdio.h> 1.29 +#include <stdlib.h> 1.30 +#include <sys/stat.h> 1.31 +#include <sys/types.h> 1.32 + 1.33 +namespace mozilla { 1.34 +namespace hal_impl { 1.35 + 1.36 +uint32_t GetFMRadioFrequency(); 1.37 + 1.38 +static int sRadioFD; 1.39 +static bool sRadioEnabled; 1.40 +static pthread_t sRadioThread; 1.41 +static hal::FMRadioSettings sRadioSettings; 1.42 +static int sMsmFMVersion; 1.43 +static bool sMsmFMMode; 1.44 + 1.45 +static int 1.46 +setControl(uint32_t id, int32_t value) 1.47 +{ 1.48 + struct v4l2_control control; 1.49 + control.id = id; 1.50 + control.value = value; 1.51 + return ioctl(sRadioFD, VIDIOC_S_CTRL, &control); 1.52 +} 1.53 + 1.54 +class RadioUpdate : public nsRunnable { 1.55 + hal::FMRadioOperation mOp; 1.56 + hal::FMRadioOperationStatus mStatus; 1.57 +public: 1.58 + RadioUpdate(hal::FMRadioOperation op, hal::FMRadioOperationStatus status) 1.59 + : mOp(op) 1.60 + , mStatus(status) 1.61 + {} 1.62 + 1.63 + NS_IMETHOD Run() { 1.64 + hal::FMRadioOperationInformation info; 1.65 + info.operation() = mOp; 1.66 + info.status() = mStatus; 1.67 + info.frequency() = GetFMRadioFrequency(); 1.68 + hal::NotifyFMRadioStatus(info); 1.69 + return NS_OK; 1.70 + } 1.71 +}; 1.72 + 1.73 +/* Runs on the radio thread */ 1.74 +static void 1.75 +initMsmFMRadio(hal::FMRadioSettings &aInfo) 1.76 +{ 1.77 + mozilla::ScopedClose fd(sRadioFD); 1.78 + char version[64]; 1.79 + int rc; 1.80 + snprintf(version, sizeof(version), "%d", sMsmFMVersion); 1.81 + property_set("hw.fm.version", version); 1.82 + 1.83 + /* Set the mode for soc downloader */ 1.84 + property_set("hw.fm.mode", "normal"); 1.85 + /* start fm_dl service */ 1.86 + property_set("ctl.start", "fm_dl"); 1.87 + 1.88 + /* 1.89 + * Fix bug 800263. Wait until the FM radio chips initialization is done 1.90 + * then set other properties, or the system will hang and reboot. This 1.91 + * work around is from codeaurora 1.92 + * (git://codeaurora.org/platform/frameworks/base.git). 1.93 + */ 1.94 + for (int i = 0; i < 4; ++i) { 1.95 + sleep(1); 1.96 + char value[PROPERTY_VALUE_MAX]; 1.97 + property_get("hw.fm.init", value, "0"); 1.98 + if (!strcmp(value, "1")) { 1.99 + break; 1.100 + } 1.101 + } 1.102 + 1.103 + rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_RECV); 1.104 + if (rc < 0) { 1.105 + HAL_LOG(("Unable to turn on radio |%s|", strerror(errno))); 1.106 + return; 1.107 + } 1.108 + 1.109 + int preEmphasis = aInfo.preEmphasis() <= 50; 1.110 + rc = setControl(V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, preEmphasis); 1.111 + if (rc) { 1.112 + HAL_LOG(("Unable to configure preemphasis")); 1.113 + return; 1.114 + } 1.115 + 1.116 + rc = setControl(V4L2_CID_PRIVATE_TAVARUA_RDS_STD, 0); 1.117 + if (rc) { 1.118 + HAL_LOG(("Unable to configure RDS")); 1.119 + return; 1.120 + } 1.121 + 1.122 + int spacing; 1.123 + switch (aInfo.spaceType()) { 1.124 + case 50: 1.125 + spacing = FM_CH_SPACE_50KHZ; 1.126 + break; 1.127 + case 100: 1.128 + spacing = FM_CH_SPACE_100KHZ; 1.129 + break; 1.130 + case 200: 1.131 + spacing = FM_CH_SPACE_200KHZ; 1.132 + break; 1.133 + default: 1.134 + HAL_LOG(("Unsupported space value - %d", aInfo.spaceType())); 1.135 + return; 1.136 + } 1.137 + 1.138 + rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SPACING, spacing); 1.139 + if (rc) { 1.140 + HAL_LOG(("Unable to configure spacing")); 1.141 + return; 1.142 + } 1.143 + 1.144 + /* 1.145 + * Frequency conversions 1.146 + * 1.147 + * HAL uses units of 1k for frequencies 1.148 + * V4L2 uses units of 62.5kHz 1.149 + * Multiplying by (10000 / 625) converts from HAL units to V4L2. 1.150 + */ 1.151 + 1.152 + struct v4l2_tuner tuner = {0}; 1.153 + tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625; 1.154 + tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625; 1.155 + tuner.audmode = V4L2_TUNER_MODE_STEREO; 1.156 + rc = ioctl(fd, VIDIOC_S_TUNER, &tuner); 1.157 + if (rc < 0) { 1.158 + HAL_LOG(("Unable to adjust band limits")); 1.159 + return; 1.160 + } 1.161 + 1.162 + rc = setControl(V4L2_CID_PRIVATE_TAVARUA_REGION, TAVARUA_REGION_OTHER); 1.163 + if (rc < 0) { 1.164 + HAL_LOG(("Unable to configure region")); 1.165 + return; 1.166 + } 1.167 + 1.168 + // Some devices do not support analog audio routing. This should be 1.169 + // indicated by the 'ro.moz.fm.noAnalog' property at build time. 1.170 + char propval[PROPERTY_VALUE_MAX]; 1.171 + property_get("ro.moz.fm.noAnalog", propval, ""); 1.172 + bool noAnalog = !strcmp(propval, "true"); 1.173 + 1.174 + rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH, 1.175 + noAnalog ? FM_DIGITAL_PATH : FM_ANALOG_PATH); 1.176 + if (rc < 0) { 1.177 + HAL_LOG(("Unable to set audio path")); 1.178 + return; 1.179 + } 1.180 + 1.181 + if (!noAnalog) { 1.182 + /* Set the mode for soc downloader */ 1.183 + property_set("hw.fm.mode", "config_dac"); 1.184 + /* Use analog mode FM */ 1.185 + property_set("hw.fm.isAnalog", "true"); 1.186 + /* start fm_dl service */ 1.187 + property_set("ctl.start", "fm_dl"); 1.188 + 1.189 + for (int i = 0; i < 4; ++i) { 1.190 + sleep(1); 1.191 + char value[PROPERTY_VALUE_MAX]; 1.192 + property_get("hw.fm.init", value, "0"); 1.193 + if (!strcmp(value, "1")) { 1.194 + break; 1.195 + } 1.196 + } 1.197 + } 1.198 + 1.199 + fd.forget(); 1.200 + sRadioEnabled = true; 1.201 +} 1.202 + 1.203 +/* Runs on the radio thread */ 1.204 +static void * 1.205 +runMsmFMRadio(void *) 1.206 +{ 1.207 + initMsmFMRadio(sRadioSettings); 1.208 + if (!sRadioEnabled) { 1.209 + NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE, 1.210 + hal::FM_RADIO_OPERATION_STATUS_FAIL)); 1.211 + return nullptr; 1.212 + } 1.213 + 1.214 + uint8_t buf[128]; 1.215 + struct v4l2_buffer buffer = {0}; 1.216 + buffer.index = 1; 1.217 + buffer.type = V4L2_BUF_TYPE_PRIVATE; 1.218 + buffer.length = sizeof(buf); 1.219 + buffer.m.userptr = (long unsigned int)buf; 1.220 + 1.221 + while (sRadioEnabled) { 1.222 + if (ioctl(sRadioFD, VIDIOC_DQBUF, &buffer) < 0) { 1.223 + if (errno == EINTR) 1.224 + continue; 1.225 + break; 1.226 + } 1.227 + 1.228 + /* The tavarua driver reports a number of things asynchronously. 1.229 + * In those cases, the status update comes from this thread. */ 1.230 + for (unsigned int i = 0; i < buffer.bytesused; i++) { 1.231 + switch (buf[i]) { 1.232 + case TAVARUA_EVT_RADIO_READY: 1.233 + // The driver sends RADIO_READY both when we turn the radio on and when we turn 1.234 + // the radio off. 1.235 + if (sRadioEnabled) { 1.236 + NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE, 1.237 + hal::FM_RADIO_OPERATION_STATUS_SUCCESS)); 1.238 + } 1.239 + break; 1.240 + 1.241 + case TAVARUA_EVT_SEEK_COMPLETE: 1.242 + NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK, 1.243 + hal::FM_RADIO_OPERATION_STATUS_SUCCESS)); 1.244 + break; 1.245 + case TAVARUA_EVT_TUNE_SUCC: 1.246 + NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_TUNE, 1.247 + hal::FM_RADIO_OPERATION_STATUS_SUCCESS)); 1.248 + break; 1.249 + default: 1.250 + break; 1.251 + } 1.252 + } 1.253 + } 1.254 + 1.255 + return nullptr; 1.256 +} 1.257 + 1.258 +/* This runs on the main thread but most of the 1.259 + * initialization is pushed to the radio thread. */ 1.260 +void 1.261 +EnableFMRadio(const hal::FMRadioSettings& aInfo) 1.262 +{ 1.263 + if (sRadioEnabled) { 1.264 + HAL_LOG(("Radio already enabled!")); 1.265 + return; 1.266 + } 1.267 + 1.268 + mozilla::ScopedClose fd(open("/dev/radio0", O_RDWR)); 1.269 + if (fd < 0) { 1.270 + HAL_LOG(("Unable to open radio device")); 1.271 + return; 1.272 + } 1.273 + 1.274 + struct v4l2_capability cap; 1.275 + int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap); 1.276 + if (rc < 0) { 1.277 + HAL_LOG(("Unable to query radio device")); 1.278 + return; 1.279 + } 1.280 + 1.281 + sMsmFMMode = !strcmp((char *)cap.driver, "radio-tavarua") || 1.282 + !strcmp((char *)cap.driver, "radio-iris"); 1.283 + HAL_LOG(("Radio: %s (%s)\n", cap.driver, cap.card)); 1.284 + 1.285 + if (!(cap.capabilities & V4L2_CAP_RADIO)) { 1.286 + HAL_LOG(("/dev/radio0 isn't a radio")); 1.287 + return; 1.288 + } 1.289 + 1.290 + if (!(cap.capabilities & V4L2_CAP_TUNER)) { 1.291 + HAL_LOG(("/dev/radio0 doesn't support the tuner interface")); 1.292 + return; 1.293 + } 1.294 + sRadioSettings = aInfo; 1.295 + 1.296 + if (sMsmFMMode) { 1.297 + sRadioFD = fd.forget(); 1.298 + sMsmFMVersion = cap.version; 1.299 + pthread_create(&sRadioThread, nullptr, runMsmFMRadio, nullptr); 1.300 + return; 1.301 + } 1.302 + 1.303 + struct v4l2_tuner tuner = {0}; 1.304 + tuner.type = V4L2_TUNER_RADIO; 1.305 + tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625; 1.306 + tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625; 1.307 + tuner.audmode = V4L2_TUNER_MODE_STEREO; 1.308 + rc = ioctl(fd, VIDIOC_S_TUNER, &tuner); 1.309 + if (rc < 0) { 1.310 + HAL_LOG(("Unable to adjust band limits")); 1.311 + } 1.312 + 1.313 + sRadioFD = fd.forget(); 1.314 + sRadioEnabled = true; 1.315 + 1.316 + hal::FMRadioOperationInformation info; 1.317 + info.operation() = hal::FM_RADIO_OPERATION_ENABLE; 1.318 + info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS; 1.319 + hal::NotifyFMRadioStatus(info); 1.320 +} 1.321 + 1.322 +void 1.323 +DisableFMRadio() 1.324 +{ 1.325 + if (!sRadioEnabled) 1.326 + return; 1.327 + 1.328 + sRadioEnabled = false; 1.329 + 1.330 + if (sMsmFMMode) { 1.331 + int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF); 1.332 + if (rc < 0) { 1.333 + HAL_LOG(("Unable to turn off radio")); 1.334 + } 1.335 + 1.336 + pthread_join(sRadioThread, nullptr); 1.337 + } 1.338 + 1.339 + close(sRadioFD); 1.340 + 1.341 + hal::FMRadioOperationInformation info; 1.342 + info.operation() = hal::FM_RADIO_OPERATION_DISABLE; 1.343 + info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS; 1.344 + hal::NotifyFMRadioStatus(info); 1.345 +} 1.346 + 1.347 +void 1.348 +FMRadioSeek(const hal::FMRadioSeekDirection& aDirection) 1.349 +{ 1.350 + struct v4l2_hw_freq_seek seek = {0}; 1.351 + seek.type = V4L2_TUNER_RADIO; 1.352 + seek.seek_upward = aDirection == hal::FMRadioSeekDirection::FM_RADIO_SEEK_DIRECTION_UP; 1.353 + 1.354 + /* ICS and older don't have the spacing field */ 1.355 +#if ANDROID_VERSION == 15 1.356 + seek.reserved[0] = sRadioSettings.spaceType() * 1000; 1.357 +#else 1.358 + seek.spacing = sRadioSettings.spaceType() * 1000; 1.359 +#endif 1.360 + 1.361 + int rc = ioctl(sRadioFD, VIDIOC_S_HW_FREQ_SEEK, &seek); 1.362 + if (sMsmFMMode && rc >= 0) 1.363 + return; 1.364 + 1.365 + hal::FMRadioOperationInformation info; 1.366 + info.operation() = hal::FM_RADIO_OPERATION_SEEK; 1.367 + info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL : 1.368 + hal::FM_RADIO_OPERATION_STATUS_SUCCESS; 1.369 + hal::NotifyFMRadioStatus(info); 1.370 + 1.371 + if (rc < 0) { 1.372 + HAL_LOG(("Could not initiate hardware seek")); 1.373 + return; 1.374 + } 1.375 + 1.376 + info.operation() = hal::FM_RADIO_OPERATION_TUNE; 1.377 + info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS; 1.378 + hal::NotifyFMRadioStatus(info); 1.379 +} 1.380 + 1.381 +void 1.382 +GetFMRadioSettings(hal::FMRadioSettings* aInfo) 1.383 +{ 1.384 + if (!sRadioEnabled) { 1.385 + return; 1.386 + } 1.387 + 1.388 + struct v4l2_tuner tuner = {0}; 1.389 + int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner); 1.390 + if (rc < 0) { 1.391 + HAL_LOG(("Could not query fm radio for settings")); 1.392 + return; 1.393 + } 1.394 + 1.395 + aInfo->upperLimit() = (tuner.rangehigh * 625) / 10000; 1.396 + aInfo->lowerLimit() = (tuner.rangelow * 625) / 10000; 1.397 +} 1.398 + 1.399 +void 1.400 +SetFMRadioFrequency(const uint32_t frequency) 1.401 +{ 1.402 + struct v4l2_frequency freq = {0}; 1.403 + freq.type = V4L2_TUNER_RADIO; 1.404 + freq.frequency = (frequency * 10000) / 625; 1.405 + 1.406 + int rc = ioctl(sRadioFD, VIDIOC_S_FREQUENCY, &freq); 1.407 + if (rc < 0) 1.408 + HAL_LOG(("Could not set radio frequency")); 1.409 + 1.410 + if (sMsmFMMode && rc >= 0) 1.411 + return; 1.412 + 1.413 + hal::FMRadioOperationInformation info; 1.414 + info.operation() = hal::FM_RADIO_OPERATION_TUNE; 1.415 + info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL : 1.416 + hal::FM_RADIO_OPERATION_STATUS_SUCCESS; 1.417 + hal::NotifyFMRadioStatus(info); 1.418 +} 1.419 + 1.420 +uint32_t 1.421 +GetFMRadioFrequency() 1.422 +{ 1.423 + if (!sRadioEnabled) 1.424 + return 0; 1.425 + 1.426 + struct v4l2_frequency freq; 1.427 + int rc = ioctl(sRadioFD, VIDIOC_G_FREQUENCY, &freq); 1.428 + if (rc < 0) { 1.429 + HAL_LOG(("Could not get radio frequency")); 1.430 + return 0; 1.431 + } 1.432 + 1.433 + return (freq.frequency * 625) / 10000; 1.434 +} 1.435 + 1.436 +bool 1.437 +IsFMRadioOn() 1.438 +{ 1.439 + return sRadioEnabled; 1.440 +} 1.441 + 1.442 +uint32_t 1.443 +GetFMRadioSignalStrength() 1.444 +{ 1.445 + struct v4l2_tuner tuner = {0}; 1.446 + int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner); 1.447 + if (rc < 0) { 1.448 + HAL_LOG(("Could not query fm radio for signal strength")); 1.449 + return 0; 1.450 + } 1.451 + 1.452 + return tuner.signal; 1.453 +} 1.454 + 1.455 +void 1.456 +CancelFMRadioSeek() 1.457 +{} 1.458 + 1.459 +} // hal_impl 1.460 +} // namespace mozilla