Bumping manifests a=b2g-bump
[gecko.git] / hal / gonk / GonkFMRadio.cpp
blob17108b0707f32aa974bbd17d70a1ef5930fa4b58
1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 #include "Hal.h"
17 #include "tavarua.h"
18 #include "nsThreadUtils.h"
19 #include "mozilla/FileUtils.h"
21 #include <cutils/properties.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <linux/videodev2.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
31 /* Bionic might not have the newer version of the v4l2 headers that
32 * define these controls, so we define them here if they're not found.
34 #ifndef V4L2_CTRL_CLASS_FM_RX
35 #define V4L2_CTRL_CLASS_FM_RX 0x00a10000
36 #define V4L2_CID_FM_RX_CLASS_BASE (V4L2_CTRL_CLASS_FM_RX | 0x900)
37 #define V4L2_CID_TUNE_DEEMPHASIS (V4L2_CID_FM_RX_CLASS_BASE + 1)
38 #define V4L2_DEEMPHASIS_DISABLED 0
39 #define V4L2_DEEMPHASIS_50_uS 1
40 #define V4L2_DEEMPHASIS_75_uS 2
41 #define V4L2_CID_RDS_RECEPTION (V4L2_CID_FM_RX_CLASS_BASE + 2)
42 #endif
44 namespace mozilla {
45 namespace hal_impl {
47 uint32_t GetFMRadioFrequency();
49 static int sRadioFD;
50 static bool sRadioEnabled;
51 static pthread_t sRadioThread;
52 static hal::FMRadioSettings sRadioSettings;
53 static int sMsmFMVersion;
54 static bool sMsmFMMode;
56 static int
57 setControl(uint32_t id, int32_t value)
59 struct v4l2_control control;
60 control.id = id;
61 control.value = value;
62 return ioctl(sRadioFD, VIDIOC_S_CTRL, &control);
65 class RadioUpdate : public nsRunnable {
66 hal::FMRadioOperation mOp;
67 hal::FMRadioOperationStatus mStatus;
68 public:
69 RadioUpdate(hal::FMRadioOperation op, hal::FMRadioOperationStatus status)
70 : mOp(op)
71 , mStatus(status)
74 NS_IMETHOD Run() {
75 hal::FMRadioOperationInformation info;
76 info.operation() = mOp;
77 info.status() = mStatus;
78 info.frequency() = GetFMRadioFrequency();
79 hal::NotifyFMRadioStatus(info);
80 return NS_OK;
84 /* Runs on the radio thread */
85 static void
86 initMsmFMRadio(hal::FMRadioSettings &aInfo)
88 mozilla::ScopedClose fd(sRadioFD);
89 char version[64];
90 int rc;
91 snprintf(version, sizeof(version), "%d", sMsmFMVersion);
92 property_set("hw.fm.version", version);
94 /* Set the mode for soc downloader */
95 property_set("hw.fm.mode", "normal");
96 /* start fm_dl service */
97 property_set("ctl.start", "fm_dl");
100 * Fix bug 800263. Wait until the FM radio chips initialization is done
101 * then set other properties, or the system will hang and reboot. This
102 * work around is from codeaurora
103 * (git://codeaurora.org/platform/frameworks/base.git).
105 for (int i = 0; i < 4; ++i) {
106 sleep(1);
107 char value[PROPERTY_VALUE_MAX];
108 property_get("hw.fm.init", value, "0");
109 if (!strcmp(value, "1")) {
110 break;
114 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_RECV);
115 if (rc < 0) {
116 HAL_LOG(("Unable to turn on radio |%s|", strerror(errno)));
117 return;
120 int preEmphasis = aInfo.preEmphasis() <= 50;
121 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, preEmphasis);
122 if (rc) {
123 HAL_LOG(("Unable to configure preemphasis"));
124 return;
127 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_RDS_STD, 0);
128 if (rc) {
129 HAL_LOG(("Unable to configure RDS"));
130 return;
133 int spacing;
134 switch (aInfo.spaceType()) {
135 case 50:
136 spacing = FM_CH_SPACE_50KHZ;
137 break;
138 case 100:
139 spacing = FM_CH_SPACE_100KHZ;
140 break;
141 case 200:
142 spacing = FM_CH_SPACE_200KHZ;
143 break;
144 default:
145 HAL_LOG(("Unsupported space value - %d", aInfo.spaceType()));
146 return;
149 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SPACING, spacing);
150 if (rc) {
151 HAL_LOG(("Unable to configure spacing"));
152 return;
156 * Frequency conversions
158 * HAL uses units of 1k for frequencies
159 * V4L2 uses units of 62.5kHz
160 * Multiplying by (10000 / 625) converts from HAL units to V4L2.
163 struct v4l2_tuner tuner = {0};
164 tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625;
165 tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625;
166 tuner.audmode = V4L2_TUNER_MODE_STEREO;
167 rc = ioctl(fd, VIDIOC_S_TUNER, &tuner);
168 if (rc < 0) {
169 HAL_LOG(("Unable to adjust band limits"));
170 return;
173 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_REGION, TAVARUA_REGION_OTHER);
174 if (rc < 0) {
175 HAL_LOG(("Unable to configure region"));
176 return;
179 // Some devices do not support analog audio routing. This should be
180 // indicated by the 'ro.moz.fm.noAnalog' property at build time.
181 char propval[PROPERTY_VALUE_MAX];
182 property_get("ro.moz.fm.noAnalog", propval, "");
183 bool noAnalog = !strcmp(propval, "true");
185 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH,
186 noAnalog ? FM_DIGITAL_PATH : FM_ANALOG_PATH);
187 if (rc < 0) {
188 HAL_LOG(("Unable to set audio path"));
189 return;
192 if (!noAnalog) {
193 /* Set the mode for soc downloader */
194 property_set("hw.fm.mode", "config_dac");
195 /* Use analog mode FM */
196 property_set("hw.fm.isAnalog", "true");
197 /* start fm_dl service */
198 property_set("ctl.start", "fm_dl");
200 for (int i = 0; i < 4; ++i) {
201 sleep(1);
202 char value[PROPERTY_VALUE_MAX];
203 property_get("hw.fm.init", value, "0");
204 if (!strcmp(value, "1")) {
205 break;
210 fd.forget();
211 sRadioEnabled = true;
214 /* Runs on the radio thread */
215 static void *
216 runMsmFMRadio(void *)
218 initMsmFMRadio(sRadioSettings);
219 if (!sRadioEnabled) {
220 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
221 hal::FM_RADIO_OPERATION_STATUS_FAIL));
222 return nullptr;
225 uint8_t buf[128];
226 struct v4l2_buffer buffer = {0};
227 buffer.index = 1;
228 buffer.type = V4L2_BUF_TYPE_PRIVATE;
229 buffer.length = sizeof(buf);
230 buffer.m.userptr = (long unsigned int)buf;
232 while (sRadioEnabled) {
233 if (ioctl(sRadioFD, VIDIOC_DQBUF, &buffer) < 0) {
234 if (errno == EINTR)
235 continue;
236 break;
239 /* The tavarua driver reports a number of things asynchronously.
240 * In those cases, the status update comes from this thread. */
241 for (unsigned int i = 0; i < buffer.bytesused; i++) {
242 switch (buf[i]) {
243 case TAVARUA_EVT_RADIO_READY:
244 // The driver sends RADIO_READY both when we turn the radio on and when we turn
245 // the radio off.
246 if (sRadioEnabled) {
247 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
248 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
250 break;
252 case TAVARUA_EVT_SEEK_COMPLETE:
253 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK,
254 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
255 break;
256 case TAVARUA_EVT_TUNE_SUCC:
257 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_TUNE,
258 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
259 break;
260 default:
261 break;
266 return nullptr;
269 /* This runs on the main thread but most of the
270 * initialization is pushed to the radio thread. */
271 void
272 EnableFMRadio(const hal::FMRadioSettings& aInfo)
274 if (sRadioEnabled) {
275 HAL_LOG(("Radio already enabled!"));
276 return;
279 hal::FMRadioOperationInformation info;
280 info.operation() = hal::FM_RADIO_OPERATION_ENABLE;
281 info.status() = hal::FM_RADIO_OPERATION_STATUS_FAIL;
283 mozilla::ScopedClose fd(open("/dev/radio0", O_RDWR));
284 if (fd < 0) {
285 HAL_LOG(("Unable to open radio device"));
286 hal::NotifyFMRadioStatus(info);
287 return;
290 struct v4l2_capability cap;
291 int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
292 if (rc < 0) {
293 HAL_LOG(("Unable to query radio device"));
294 hal::NotifyFMRadioStatus(info);
295 return;
298 sMsmFMMode = !strcmp((char *)cap.driver, "radio-tavarua") ||
299 !strcmp((char *)cap.driver, "radio-iris");
300 HAL_LOG(("Radio: %s (%s)\n", cap.driver, cap.card));
302 if (!(cap.capabilities & V4L2_CAP_RADIO)) {
303 HAL_LOG(("/dev/radio0 isn't a radio"));
304 hal::NotifyFMRadioStatus(info);
305 return;
308 if (!(cap.capabilities & V4L2_CAP_TUNER)) {
309 HAL_LOG(("/dev/radio0 doesn't support the tuner interface"));
310 hal::NotifyFMRadioStatus(info);
311 return;
313 sRadioSettings = aInfo;
315 if (sMsmFMMode) {
316 sRadioFD = fd.forget();
317 sMsmFMVersion = cap.version;
318 if (pthread_create(&sRadioThread, nullptr, runMsmFMRadio, nullptr)) {
319 HAL_LOG(("Couldn't create radio thread"));
320 hal::NotifyFMRadioStatus(info);
322 return;
325 struct v4l2_tuner tuner = {0};
326 tuner.type = V4L2_TUNER_RADIO;
327 tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625;
328 tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625;
329 tuner.audmode = V4L2_TUNER_MODE_STEREO;
330 rc = ioctl(fd, VIDIOC_S_TUNER, &tuner);
331 if (rc < 0) {
332 HAL_LOG(("Unable to adjust band limits"));
335 int emphasis;
336 switch (aInfo.preEmphasis()) {
337 case 0:
338 emphasis = V4L2_DEEMPHASIS_DISABLED;
339 break;
340 case 50:
341 emphasis = V4L2_DEEMPHASIS_50_uS;
342 break;
343 case 75:
344 emphasis = V4L2_DEEMPHASIS_75_uS;
345 break;
346 default:
347 MOZ_CRASH("Invalid preemphasis setting");
348 break;
350 rc = setControl(V4L2_CID_TUNE_DEEMPHASIS, emphasis);
351 if (rc < 0) {
352 HAL_LOG(("Unable to configure deemphasis"));
355 sRadioFD = fd.forget();
356 sRadioEnabled = true;
358 info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
359 hal::NotifyFMRadioStatus(info);
362 void
363 DisableFMRadio()
365 if (!sRadioEnabled)
366 return;
368 sRadioEnabled = false;
370 if (sMsmFMMode) {
371 int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF);
372 if (rc < 0) {
373 HAL_LOG(("Unable to turn off radio"));
376 pthread_join(sRadioThread, nullptr);
379 close(sRadioFD);
381 hal::FMRadioOperationInformation info;
382 info.operation() = hal::FM_RADIO_OPERATION_DISABLE;
383 info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
384 hal::NotifyFMRadioStatus(info);
387 void
388 FMRadioSeek(const hal::FMRadioSeekDirection& aDirection)
390 struct v4l2_hw_freq_seek seek = {0};
391 seek.type = V4L2_TUNER_RADIO;
392 seek.seek_upward = aDirection == hal::FMRadioSeekDirection::FM_RADIO_SEEK_DIRECTION_UP;
394 /* ICS and older don't have the spacing field */
395 #if ANDROID_VERSION == 15
396 seek.reserved[0] = sRadioSettings.spaceType() * 1000;
397 #else
398 seek.spacing = sRadioSettings.spaceType() * 1000;
399 #endif
401 int rc = ioctl(sRadioFD, VIDIOC_S_HW_FREQ_SEEK, &seek);
402 if (sMsmFMMode && rc >= 0)
403 return;
405 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK,
406 rc < 0 ?
407 hal::FM_RADIO_OPERATION_STATUS_FAIL :
408 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
410 if (rc < 0) {
411 HAL_LOG(("Could not initiate hardware seek"));
412 return;
415 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_TUNE,
416 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
419 void
420 GetFMRadioSettings(hal::FMRadioSettings* aInfo)
422 if (!sRadioEnabled) {
423 return;
426 struct v4l2_tuner tuner = {0};
427 int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner);
428 if (rc < 0) {
429 HAL_LOG(("Could not query fm radio for settings"));
430 return;
433 aInfo->upperLimit() = (tuner.rangehigh * 625) / 10000;
434 aInfo->lowerLimit() = (tuner.rangelow * 625) / 10000;
437 void
438 SetFMRadioFrequency(const uint32_t frequency)
440 struct v4l2_frequency freq = {0};
441 freq.type = V4L2_TUNER_RADIO;
442 freq.frequency = (frequency * 10000) / 625;
444 int rc = ioctl(sRadioFD, VIDIOC_S_FREQUENCY, &freq);
445 if (rc < 0)
446 HAL_LOG(("Could not set radio frequency"));
448 if (sMsmFMMode && rc >= 0)
449 return;
451 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_TUNE,
452 rc < 0 ?
453 hal::FM_RADIO_OPERATION_STATUS_FAIL :
454 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
457 uint32_t
458 GetFMRadioFrequency()
460 if (!sRadioEnabled)
461 return 0;
463 struct v4l2_frequency freq;
464 int rc = ioctl(sRadioFD, VIDIOC_G_FREQUENCY, &freq);
465 if (rc < 0) {
466 HAL_LOG(("Could not get radio frequency"));
467 return 0;
470 return (freq.frequency * 625) / 10000;
473 bool
474 IsFMRadioOn()
476 return sRadioEnabled;
479 uint32_t
480 GetFMRadioSignalStrength()
482 struct v4l2_tuner tuner = {0};
483 int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner);
484 if (rc < 0) {
485 HAL_LOG(("Could not query fm radio for signal strength"));
486 return 0;
489 return tuner.signal;
492 void
493 CancelFMRadioSeek()
496 } // hal_impl
497 } // namespace mozilla