michael@0: /* Copyright 2012 Mozilla Foundation and Mozilla contributors michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include "Hal.h" michael@0: #include "tavarua.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/FileUtils.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace hal_impl { michael@0: michael@0: uint32_t GetFMRadioFrequency(); michael@0: michael@0: static int sRadioFD; michael@0: static bool sRadioEnabled; michael@0: static pthread_t sRadioThread; michael@0: static hal::FMRadioSettings sRadioSettings; michael@0: static int sMsmFMVersion; michael@0: static bool sMsmFMMode; michael@0: michael@0: static int michael@0: setControl(uint32_t id, int32_t value) michael@0: { michael@0: struct v4l2_control control; michael@0: control.id = id; michael@0: control.value = value; michael@0: return ioctl(sRadioFD, VIDIOC_S_CTRL, &control); michael@0: } michael@0: michael@0: class RadioUpdate : public nsRunnable { michael@0: hal::FMRadioOperation mOp; michael@0: hal::FMRadioOperationStatus mStatus; michael@0: public: michael@0: RadioUpdate(hal::FMRadioOperation op, hal::FMRadioOperationStatus status) michael@0: : mOp(op) michael@0: , mStatus(status) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() { michael@0: hal::FMRadioOperationInformation info; michael@0: info.operation() = mOp; michael@0: info.status() = mStatus; michael@0: info.frequency() = GetFMRadioFrequency(); michael@0: hal::NotifyFMRadioStatus(info); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: /* Runs on the radio thread */ michael@0: static void michael@0: initMsmFMRadio(hal::FMRadioSettings &aInfo) michael@0: { michael@0: mozilla::ScopedClose fd(sRadioFD); michael@0: char version[64]; michael@0: int rc; michael@0: snprintf(version, sizeof(version), "%d", sMsmFMVersion); michael@0: property_set("hw.fm.version", version); michael@0: michael@0: /* Set the mode for soc downloader */ michael@0: property_set("hw.fm.mode", "normal"); michael@0: /* start fm_dl service */ michael@0: property_set("ctl.start", "fm_dl"); michael@0: michael@0: /* michael@0: * Fix bug 800263. Wait until the FM radio chips initialization is done michael@0: * then set other properties, or the system will hang and reboot. This michael@0: * work around is from codeaurora michael@0: * (git://codeaurora.org/platform/frameworks/base.git). michael@0: */ michael@0: for (int i = 0; i < 4; ++i) { michael@0: sleep(1); michael@0: char value[PROPERTY_VALUE_MAX]; michael@0: property_get("hw.fm.init", value, "0"); michael@0: if (!strcmp(value, "1")) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_RECV); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Unable to turn on radio |%s|", strerror(errno))); michael@0: return; michael@0: } michael@0: michael@0: int preEmphasis = aInfo.preEmphasis() <= 50; michael@0: rc = setControl(V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, preEmphasis); michael@0: if (rc) { michael@0: HAL_LOG(("Unable to configure preemphasis")); michael@0: return; michael@0: } michael@0: michael@0: rc = setControl(V4L2_CID_PRIVATE_TAVARUA_RDS_STD, 0); michael@0: if (rc) { michael@0: HAL_LOG(("Unable to configure RDS")); michael@0: return; michael@0: } michael@0: michael@0: int spacing; michael@0: switch (aInfo.spaceType()) { michael@0: case 50: michael@0: spacing = FM_CH_SPACE_50KHZ; michael@0: break; michael@0: case 100: michael@0: spacing = FM_CH_SPACE_100KHZ; michael@0: break; michael@0: case 200: michael@0: spacing = FM_CH_SPACE_200KHZ; michael@0: break; michael@0: default: michael@0: HAL_LOG(("Unsupported space value - %d", aInfo.spaceType())); michael@0: return; michael@0: } michael@0: michael@0: rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SPACING, spacing); michael@0: if (rc) { michael@0: HAL_LOG(("Unable to configure spacing")); michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * Frequency conversions michael@0: * michael@0: * HAL uses units of 1k for frequencies michael@0: * V4L2 uses units of 62.5kHz michael@0: * Multiplying by (10000 / 625) converts from HAL units to V4L2. michael@0: */ michael@0: michael@0: struct v4l2_tuner tuner = {0}; michael@0: tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625; michael@0: tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625; michael@0: tuner.audmode = V4L2_TUNER_MODE_STEREO; michael@0: rc = ioctl(fd, VIDIOC_S_TUNER, &tuner); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Unable to adjust band limits")); michael@0: return; michael@0: } michael@0: michael@0: rc = setControl(V4L2_CID_PRIVATE_TAVARUA_REGION, TAVARUA_REGION_OTHER); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Unable to configure region")); michael@0: return; michael@0: } michael@0: michael@0: // Some devices do not support analog audio routing. This should be michael@0: // indicated by the 'ro.moz.fm.noAnalog' property at build time. michael@0: char propval[PROPERTY_VALUE_MAX]; michael@0: property_get("ro.moz.fm.noAnalog", propval, ""); michael@0: bool noAnalog = !strcmp(propval, "true"); michael@0: michael@0: rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH, michael@0: noAnalog ? FM_DIGITAL_PATH : FM_ANALOG_PATH); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Unable to set audio path")); michael@0: return; michael@0: } michael@0: michael@0: if (!noAnalog) { michael@0: /* Set the mode for soc downloader */ michael@0: property_set("hw.fm.mode", "config_dac"); michael@0: /* Use analog mode FM */ michael@0: property_set("hw.fm.isAnalog", "true"); michael@0: /* start fm_dl service */ michael@0: property_set("ctl.start", "fm_dl"); michael@0: michael@0: for (int i = 0; i < 4; ++i) { michael@0: sleep(1); michael@0: char value[PROPERTY_VALUE_MAX]; michael@0: property_get("hw.fm.init", value, "0"); michael@0: if (!strcmp(value, "1")) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: fd.forget(); michael@0: sRadioEnabled = true; michael@0: } michael@0: michael@0: /* Runs on the radio thread */ michael@0: static void * michael@0: runMsmFMRadio(void *) michael@0: { michael@0: initMsmFMRadio(sRadioSettings); michael@0: if (!sRadioEnabled) { michael@0: NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE, michael@0: hal::FM_RADIO_OPERATION_STATUS_FAIL)); michael@0: return nullptr; michael@0: } michael@0: michael@0: uint8_t buf[128]; michael@0: struct v4l2_buffer buffer = {0}; michael@0: buffer.index = 1; michael@0: buffer.type = V4L2_BUF_TYPE_PRIVATE; michael@0: buffer.length = sizeof(buf); michael@0: buffer.m.userptr = (long unsigned int)buf; michael@0: michael@0: while (sRadioEnabled) { michael@0: if (ioctl(sRadioFD, VIDIOC_DQBUF, &buffer) < 0) { michael@0: if (errno == EINTR) michael@0: continue; michael@0: break; michael@0: } michael@0: michael@0: /* The tavarua driver reports a number of things asynchronously. michael@0: * In those cases, the status update comes from this thread. */ michael@0: for (unsigned int i = 0; i < buffer.bytesused; i++) { michael@0: switch (buf[i]) { michael@0: case TAVARUA_EVT_RADIO_READY: michael@0: // The driver sends RADIO_READY both when we turn the radio on and when we turn michael@0: // the radio off. michael@0: if (sRadioEnabled) { michael@0: NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE, michael@0: hal::FM_RADIO_OPERATION_STATUS_SUCCESS)); michael@0: } michael@0: break; michael@0: michael@0: case TAVARUA_EVT_SEEK_COMPLETE: michael@0: NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK, michael@0: hal::FM_RADIO_OPERATION_STATUS_SUCCESS)); michael@0: break; michael@0: case TAVARUA_EVT_TUNE_SUCC: michael@0: NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_TUNE, michael@0: hal::FM_RADIO_OPERATION_STATUS_SUCCESS)); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: /* This runs on the main thread but most of the michael@0: * initialization is pushed to the radio thread. */ michael@0: void michael@0: EnableFMRadio(const hal::FMRadioSettings& aInfo) michael@0: { michael@0: if (sRadioEnabled) { michael@0: HAL_LOG(("Radio already enabled!")); michael@0: return; michael@0: } michael@0: michael@0: mozilla::ScopedClose fd(open("/dev/radio0", O_RDWR)); michael@0: if (fd < 0) { michael@0: HAL_LOG(("Unable to open radio device")); michael@0: return; michael@0: } michael@0: michael@0: struct v4l2_capability cap; michael@0: int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Unable to query radio device")); michael@0: return; michael@0: } michael@0: michael@0: sMsmFMMode = !strcmp((char *)cap.driver, "radio-tavarua") || michael@0: !strcmp((char *)cap.driver, "radio-iris"); michael@0: HAL_LOG(("Radio: %s (%s)\n", cap.driver, cap.card)); michael@0: michael@0: if (!(cap.capabilities & V4L2_CAP_RADIO)) { michael@0: HAL_LOG(("/dev/radio0 isn't a radio")); michael@0: return; michael@0: } michael@0: michael@0: if (!(cap.capabilities & V4L2_CAP_TUNER)) { michael@0: HAL_LOG(("/dev/radio0 doesn't support the tuner interface")); michael@0: return; michael@0: } michael@0: sRadioSettings = aInfo; michael@0: michael@0: if (sMsmFMMode) { michael@0: sRadioFD = fd.forget(); michael@0: sMsmFMVersion = cap.version; michael@0: pthread_create(&sRadioThread, nullptr, runMsmFMRadio, nullptr); michael@0: return; michael@0: } michael@0: michael@0: struct v4l2_tuner tuner = {0}; michael@0: tuner.type = V4L2_TUNER_RADIO; michael@0: tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625; michael@0: tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625; michael@0: tuner.audmode = V4L2_TUNER_MODE_STEREO; michael@0: rc = ioctl(fd, VIDIOC_S_TUNER, &tuner); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Unable to adjust band limits")); michael@0: } michael@0: michael@0: sRadioFD = fd.forget(); michael@0: sRadioEnabled = true; michael@0: michael@0: hal::FMRadioOperationInformation info; michael@0: info.operation() = hal::FM_RADIO_OPERATION_ENABLE; michael@0: info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS; michael@0: hal::NotifyFMRadioStatus(info); michael@0: } michael@0: michael@0: void michael@0: DisableFMRadio() michael@0: { michael@0: if (!sRadioEnabled) michael@0: return; michael@0: michael@0: sRadioEnabled = false; michael@0: michael@0: if (sMsmFMMode) { michael@0: int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Unable to turn off radio")); michael@0: } michael@0: michael@0: pthread_join(sRadioThread, nullptr); michael@0: } michael@0: michael@0: close(sRadioFD); michael@0: michael@0: hal::FMRadioOperationInformation info; michael@0: info.operation() = hal::FM_RADIO_OPERATION_DISABLE; michael@0: info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS; michael@0: hal::NotifyFMRadioStatus(info); michael@0: } michael@0: michael@0: void michael@0: FMRadioSeek(const hal::FMRadioSeekDirection& aDirection) michael@0: { michael@0: struct v4l2_hw_freq_seek seek = {0}; michael@0: seek.type = V4L2_TUNER_RADIO; michael@0: seek.seek_upward = aDirection == hal::FMRadioSeekDirection::FM_RADIO_SEEK_DIRECTION_UP; michael@0: michael@0: /* ICS and older don't have the spacing field */ michael@0: #if ANDROID_VERSION == 15 michael@0: seek.reserved[0] = sRadioSettings.spaceType() * 1000; michael@0: #else michael@0: seek.spacing = sRadioSettings.spaceType() * 1000; michael@0: #endif michael@0: michael@0: int rc = ioctl(sRadioFD, VIDIOC_S_HW_FREQ_SEEK, &seek); michael@0: if (sMsmFMMode && rc >= 0) michael@0: return; michael@0: michael@0: hal::FMRadioOperationInformation info; michael@0: info.operation() = hal::FM_RADIO_OPERATION_SEEK; michael@0: info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL : michael@0: hal::FM_RADIO_OPERATION_STATUS_SUCCESS; michael@0: hal::NotifyFMRadioStatus(info); michael@0: michael@0: if (rc < 0) { michael@0: HAL_LOG(("Could not initiate hardware seek")); michael@0: return; michael@0: } michael@0: michael@0: info.operation() = hal::FM_RADIO_OPERATION_TUNE; michael@0: info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS; michael@0: hal::NotifyFMRadioStatus(info); michael@0: } michael@0: michael@0: void michael@0: GetFMRadioSettings(hal::FMRadioSettings* aInfo) michael@0: { michael@0: if (!sRadioEnabled) { michael@0: return; michael@0: } michael@0: michael@0: struct v4l2_tuner tuner = {0}; michael@0: int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Could not query fm radio for settings")); michael@0: return; michael@0: } michael@0: michael@0: aInfo->upperLimit() = (tuner.rangehigh * 625) / 10000; michael@0: aInfo->lowerLimit() = (tuner.rangelow * 625) / 10000; michael@0: } michael@0: michael@0: void michael@0: SetFMRadioFrequency(const uint32_t frequency) michael@0: { michael@0: struct v4l2_frequency freq = {0}; michael@0: freq.type = V4L2_TUNER_RADIO; michael@0: freq.frequency = (frequency * 10000) / 625; michael@0: michael@0: int rc = ioctl(sRadioFD, VIDIOC_S_FREQUENCY, &freq); michael@0: if (rc < 0) michael@0: HAL_LOG(("Could not set radio frequency")); michael@0: michael@0: if (sMsmFMMode && rc >= 0) michael@0: return; michael@0: michael@0: hal::FMRadioOperationInformation info; michael@0: info.operation() = hal::FM_RADIO_OPERATION_TUNE; michael@0: info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL : michael@0: hal::FM_RADIO_OPERATION_STATUS_SUCCESS; michael@0: hal::NotifyFMRadioStatus(info); michael@0: } michael@0: michael@0: uint32_t michael@0: GetFMRadioFrequency() michael@0: { michael@0: if (!sRadioEnabled) michael@0: return 0; michael@0: michael@0: struct v4l2_frequency freq; michael@0: int rc = ioctl(sRadioFD, VIDIOC_G_FREQUENCY, &freq); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Could not get radio frequency")); michael@0: return 0; michael@0: } michael@0: michael@0: return (freq.frequency * 625) / 10000; michael@0: } michael@0: michael@0: bool michael@0: IsFMRadioOn() michael@0: { michael@0: return sRadioEnabled; michael@0: } michael@0: michael@0: uint32_t michael@0: GetFMRadioSignalStrength() michael@0: { michael@0: struct v4l2_tuner tuner = {0}; michael@0: int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner); michael@0: if (rc < 0) { michael@0: HAL_LOG(("Could not query fm radio for signal strength")); michael@0: return 0; michael@0: } michael@0: michael@0: return tuner.signal; michael@0: } michael@0: michael@0: void michael@0: CancelFMRadioSeek() michael@0: {} michael@0: michael@0: } // hal_impl michael@0: } // namespace mozilla