Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors
2 *
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
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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.
14 */
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>
30 namespace mozilla {
31 namespace hal_impl {
33 uint32_t GetFMRadioFrequency();
35 static int sRadioFD;
36 static bool sRadioEnabled;
37 static pthread_t sRadioThread;
38 static hal::FMRadioSettings sRadioSettings;
39 static int sMsmFMVersion;
40 static bool sMsmFMMode;
42 static int
43 setControl(uint32_t id, int32_t value)
44 {
45 struct v4l2_control control;
46 control.id = id;
47 control.value = value;
48 return ioctl(sRadioFD, VIDIOC_S_CTRL, &control);
49 }
51 class RadioUpdate : public nsRunnable {
52 hal::FMRadioOperation mOp;
53 hal::FMRadioOperationStatus mStatus;
54 public:
55 RadioUpdate(hal::FMRadioOperation op, hal::FMRadioOperationStatus status)
56 : mOp(op)
57 , mStatus(status)
58 {}
60 NS_IMETHOD Run() {
61 hal::FMRadioOperationInformation info;
62 info.operation() = mOp;
63 info.status() = mStatus;
64 info.frequency() = GetFMRadioFrequency();
65 hal::NotifyFMRadioStatus(info);
66 return NS_OK;
67 }
68 };
70 /* Runs on the radio thread */
71 static void
72 initMsmFMRadio(hal::FMRadioSettings &aInfo)
73 {
74 mozilla::ScopedClose fd(sRadioFD);
75 char version[64];
76 int rc;
77 snprintf(version, sizeof(version), "%d", sMsmFMVersion);
78 property_set("hw.fm.version", version);
80 /* Set the mode for soc downloader */
81 property_set("hw.fm.mode", "normal");
82 /* start fm_dl service */
83 property_set("ctl.start", "fm_dl");
85 /*
86 * Fix bug 800263. Wait until the FM radio chips initialization is done
87 * then set other properties, or the system will hang and reboot. This
88 * work around is from codeaurora
89 * (git://codeaurora.org/platform/frameworks/base.git).
90 */
91 for (int i = 0; i < 4; ++i) {
92 sleep(1);
93 char value[PROPERTY_VALUE_MAX];
94 property_get("hw.fm.init", value, "0");
95 if (!strcmp(value, "1")) {
96 break;
97 }
98 }
100 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_RECV);
101 if (rc < 0) {
102 HAL_LOG(("Unable to turn on radio |%s|", strerror(errno)));
103 return;
104 }
106 int preEmphasis = aInfo.preEmphasis() <= 50;
107 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, preEmphasis);
108 if (rc) {
109 HAL_LOG(("Unable to configure preemphasis"));
110 return;
111 }
113 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_RDS_STD, 0);
114 if (rc) {
115 HAL_LOG(("Unable to configure RDS"));
116 return;
117 }
119 int spacing;
120 switch (aInfo.spaceType()) {
121 case 50:
122 spacing = FM_CH_SPACE_50KHZ;
123 break;
124 case 100:
125 spacing = FM_CH_SPACE_100KHZ;
126 break;
127 case 200:
128 spacing = FM_CH_SPACE_200KHZ;
129 break;
130 default:
131 HAL_LOG(("Unsupported space value - %d", aInfo.spaceType()));
132 return;
133 }
135 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SPACING, spacing);
136 if (rc) {
137 HAL_LOG(("Unable to configure spacing"));
138 return;
139 }
141 /*
142 * Frequency conversions
143 *
144 * HAL uses units of 1k for frequencies
145 * V4L2 uses units of 62.5kHz
146 * Multiplying by (10000 / 625) converts from HAL units to V4L2.
147 */
149 struct v4l2_tuner tuner = {0};
150 tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625;
151 tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625;
152 tuner.audmode = V4L2_TUNER_MODE_STEREO;
153 rc = ioctl(fd, VIDIOC_S_TUNER, &tuner);
154 if (rc < 0) {
155 HAL_LOG(("Unable to adjust band limits"));
156 return;
157 }
159 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_REGION, TAVARUA_REGION_OTHER);
160 if (rc < 0) {
161 HAL_LOG(("Unable to configure region"));
162 return;
163 }
165 // Some devices do not support analog audio routing. This should be
166 // indicated by the 'ro.moz.fm.noAnalog' property at build time.
167 char propval[PROPERTY_VALUE_MAX];
168 property_get("ro.moz.fm.noAnalog", propval, "");
169 bool noAnalog = !strcmp(propval, "true");
171 rc = setControl(V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH,
172 noAnalog ? FM_DIGITAL_PATH : FM_ANALOG_PATH);
173 if (rc < 0) {
174 HAL_LOG(("Unable to set audio path"));
175 return;
176 }
178 if (!noAnalog) {
179 /* Set the mode for soc downloader */
180 property_set("hw.fm.mode", "config_dac");
181 /* Use analog mode FM */
182 property_set("hw.fm.isAnalog", "true");
183 /* start fm_dl service */
184 property_set("ctl.start", "fm_dl");
186 for (int i = 0; i < 4; ++i) {
187 sleep(1);
188 char value[PROPERTY_VALUE_MAX];
189 property_get("hw.fm.init", value, "0");
190 if (!strcmp(value, "1")) {
191 break;
192 }
193 }
194 }
196 fd.forget();
197 sRadioEnabled = true;
198 }
200 /* Runs on the radio thread */
201 static void *
202 runMsmFMRadio(void *)
203 {
204 initMsmFMRadio(sRadioSettings);
205 if (!sRadioEnabled) {
206 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
207 hal::FM_RADIO_OPERATION_STATUS_FAIL));
208 return nullptr;
209 }
211 uint8_t buf[128];
212 struct v4l2_buffer buffer = {0};
213 buffer.index = 1;
214 buffer.type = V4L2_BUF_TYPE_PRIVATE;
215 buffer.length = sizeof(buf);
216 buffer.m.userptr = (long unsigned int)buf;
218 while (sRadioEnabled) {
219 if (ioctl(sRadioFD, VIDIOC_DQBUF, &buffer) < 0) {
220 if (errno == EINTR)
221 continue;
222 break;
223 }
225 /* The tavarua driver reports a number of things asynchronously.
226 * In those cases, the status update comes from this thread. */
227 for (unsigned int i = 0; i < buffer.bytesused; i++) {
228 switch (buf[i]) {
229 case TAVARUA_EVT_RADIO_READY:
230 // The driver sends RADIO_READY both when we turn the radio on and when we turn
231 // the radio off.
232 if (sRadioEnabled) {
233 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
234 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
235 }
236 break;
238 case TAVARUA_EVT_SEEK_COMPLETE:
239 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK,
240 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
241 break;
242 case TAVARUA_EVT_TUNE_SUCC:
243 NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_TUNE,
244 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
245 break;
246 default:
247 break;
248 }
249 }
250 }
252 return nullptr;
253 }
255 /* This runs on the main thread but most of the
256 * initialization is pushed to the radio thread. */
257 void
258 EnableFMRadio(const hal::FMRadioSettings& aInfo)
259 {
260 if (sRadioEnabled) {
261 HAL_LOG(("Radio already enabled!"));
262 return;
263 }
265 mozilla::ScopedClose fd(open("/dev/radio0", O_RDWR));
266 if (fd < 0) {
267 HAL_LOG(("Unable to open radio device"));
268 return;
269 }
271 struct v4l2_capability cap;
272 int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
273 if (rc < 0) {
274 HAL_LOG(("Unable to query radio device"));
275 return;
276 }
278 sMsmFMMode = !strcmp((char *)cap.driver, "radio-tavarua") ||
279 !strcmp((char *)cap.driver, "radio-iris");
280 HAL_LOG(("Radio: %s (%s)\n", cap.driver, cap.card));
282 if (!(cap.capabilities & V4L2_CAP_RADIO)) {
283 HAL_LOG(("/dev/radio0 isn't a radio"));
284 return;
285 }
287 if (!(cap.capabilities & V4L2_CAP_TUNER)) {
288 HAL_LOG(("/dev/radio0 doesn't support the tuner interface"));
289 return;
290 }
291 sRadioSettings = aInfo;
293 if (sMsmFMMode) {
294 sRadioFD = fd.forget();
295 sMsmFMVersion = cap.version;
296 pthread_create(&sRadioThread, nullptr, runMsmFMRadio, nullptr);
297 return;
298 }
300 struct v4l2_tuner tuner = {0};
301 tuner.type = V4L2_TUNER_RADIO;
302 tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625;
303 tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625;
304 tuner.audmode = V4L2_TUNER_MODE_STEREO;
305 rc = ioctl(fd, VIDIOC_S_TUNER, &tuner);
306 if (rc < 0) {
307 HAL_LOG(("Unable to adjust band limits"));
308 }
310 sRadioFD = fd.forget();
311 sRadioEnabled = true;
313 hal::FMRadioOperationInformation info;
314 info.operation() = hal::FM_RADIO_OPERATION_ENABLE;
315 info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
316 hal::NotifyFMRadioStatus(info);
317 }
319 void
320 DisableFMRadio()
321 {
322 if (!sRadioEnabled)
323 return;
325 sRadioEnabled = false;
327 if (sMsmFMMode) {
328 int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF);
329 if (rc < 0) {
330 HAL_LOG(("Unable to turn off radio"));
331 }
333 pthread_join(sRadioThread, nullptr);
334 }
336 close(sRadioFD);
338 hal::FMRadioOperationInformation info;
339 info.operation() = hal::FM_RADIO_OPERATION_DISABLE;
340 info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
341 hal::NotifyFMRadioStatus(info);
342 }
344 void
345 FMRadioSeek(const hal::FMRadioSeekDirection& aDirection)
346 {
347 struct v4l2_hw_freq_seek seek = {0};
348 seek.type = V4L2_TUNER_RADIO;
349 seek.seek_upward = aDirection == hal::FMRadioSeekDirection::FM_RADIO_SEEK_DIRECTION_UP;
351 /* ICS and older don't have the spacing field */
352 #if ANDROID_VERSION == 15
353 seek.reserved[0] = sRadioSettings.spaceType() * 1000;
354 #else
355 seek.spacing = sRadioSettings.spaceType() * 1000;
356 #endif
358 int rc = ioctl(sRadioFD, VIDIOC_S_HW_FREQ_SEEK, &seek);
359 if (sMsmFMMode && rc >= 0)
360 return;
362 hal::FMRadioOperationInformation info;
363 info.operation() = hal::FM_RADIO_OPERATION_SEEK;
364 info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL :
365 hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
366 hal::NotifyFMRadioStatus(info);
368 if (rc < 0) {
369 HAL_LOG(("Could not initiate hardware seek"));
370 return;
371 }
373 info.operation() = hal::FM_RADIO_OPERATION_TUNE;
374 info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
375 hal::NotifyFMRadioStatus(info);
376 }
378 void
379 GetFMRadioSettings(hal::FMRadioSettings* aInfo)
380 {
381 if (!sRadioEnabled) {
382 return;
383 }
385 struct v4l2_tuner tuner = {0};
386 int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner);
387 if (rc < 0) {
388 HAL_LOG(("Could not query fm radio for settings"));
389 return;
390 }
392 aInfo->upperLimit() = (tuner.rangehigh * 625) / 10000;
393 aInfo->lowerLimit() = (tuner.rangelow * 625) / 10000;
394 }
396 void
397 SetFMRadioFrequency(const uint32_t frequency)
398 {
399 struct v4l2_frequency freq = {0};
400 freq.type = V4L2_TUNER_RADIO;
401 freq.frequency = (frequency * 10000) / 625;
403 int rc = ioctl(sRadioFD, VIDIOC_S_FREQUENCY, &freq);
404 if (rc < 0)
405 HAL_LOG(("Could not set radio frequency"));
407 if (sMsmFMMode && rc >= 0)
408 return;
410 hal::FMRadioOperationInformation info;
411 info.operation() = hal::FM_RADIO_OPERATION_TUNE;
412 info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL :
413 hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
414 hal::NotifyFMRadioStatus(info);
415 }
417 uint32_t
418 GetFMRadioFrequency()
419 {
420 if (!sRadioEnabled)
421 return 0;
423 struct v4l2_frequency freq;
424 int rc = ioctl(sRadioFD, VIDIOC_G_FREQUENCY, &freq);
425 if (rc < 0) {
426 HAL_LOG(("Could not get radio frequency"));
427 return 0;
428 }
430 return (freq.frequency * 625) / 10000;
431 }
433 bool
434 IsFMRadioOn()
435 {
436 return sRadioEnabled;
437 }
439 uint32_t
440 GetFMRadioSignalStrength()
441 {
442 struct v4l2_tuner tuner = {0};
443 int rc = ioctl(sRadioFD, VIDIOC_G_TUNER, &tuner);
444 if (rc < 0) {
445 HAL_LOG(("Could not query fm radio for signal strength"));
446 return 0;
447 }
449 return tuner.signal;
450 }
452 void
453 CancelFMRadioSeek()
454 {}
456 } // hal_impl
457 } // namespace mozilla