summaryrefslogtreecommitdiff
path: root/tuner.c
diff options
context:
space:
mode:
Diffstat (limited to 'tuner.c')
-rw-r--r--tuner.c208
1 files changed, 208 insertions, 0 deletions
diff --git a/tuner.c b/tuner.c
new file mode 100644
index 0000000..eb0cda8
--- /dev/null
+++ b/tuner.c
@@ -0,0 +1,208 @@
+/*
+ * fmrxd - a daemon to enable and multiplex access to the N950/N9 radio tuner
+ * Copyright (C) 2011 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <glib.h>
+#include <linux/videodev2.h>
+
+#include "fmrxd.h"
+
+int tuner_fd = -1;
+
+static bool tuner_precise;
+static unsigned long tuner_min, tuner_max;
+
+static unsigned long tuner_freq = 0;
+
+static inline unsigned long v4l_to_hz(unsigned f)
+{
+ if (tuner_precise) {
+ /* f * 62.5 */
+ return (f * 125UL) / 2;
+ } else {
+ return f * 62500UL;
+ }
+}
+
+static inline unsigned hz_to_v4l(unsigned long f)
+{
+ if (tuner_precise) {
+ /* f / 62.5 */
+ return (f * 2UL) / 125;
+ } else {
+ return f / 62500;
+ }
+}
+
+static inline unsigned long mhz_to_hz(double f)
+{
+ return f * 1000000.0;
+}
+
+static inline double hz_to_mhz(unsigned long f)
+{
+ return f / 1000000.0;
+}
+
+static bool tuner_tune(unsigned long hz)
+{
+ struct v4l2_frequency t_freq = {
+ .tuner = TUNER_DEVICE_ID,
+ .type = V4L2_TUNER_RADIO,
+ .frequency = hz_to_v4l(hz)
+ };
+
+ g_return_val_if_fail(tuner_fd != -1, false);
+
+ if (ioctl(tuner_fd, VIDIOC_S_FREQUENCY, &t_freq) < 0) {
+ g_warning("Failed to tune: %s", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static unsigned long tuner_get_tuned_freq()
+{
+ struct v4l2_frequency t_freq = {
+ .tuner = TUNER_DEVICE_ID
+ };
+
+ g_return_val_if_fail(tuner_fd != -1, 0);
+
+ if (ioctl(tuner_fd, VIDIOC_G_FREQUENCY, &t_freq) < 0) {
+ g_warning("Failed to get freq: %s", strerror(errno));
+ return 0;
+ }
+
+ return v4l_to_hz(t_freq.frequency);
+}
+
+static uint16_t tuner_get_signal()
+{
+ struct v4l2_tuner t_tuner = {
+ .index = TUNER_DEVICE_ID
+ };
+
+ g_return_val_if_fail(tuner_fd != -1, 0);
+
+ if (ioctl(tuner_fd, VIDIOC_G_TUNER, &t_tuner) < 0) {
+ g_warning("Failed to get signal level: %s", strerror(errno));
+ return 0;
+ }
+
+ return t_tuner.signal;
+}
+
+bool configure_tuner(bool on)
+{
+ if (on) {
+ struct v4l2_tuner tuner = { 0 };
+ int res;
+
+ tuner_fd = open(TUNER_DEVICE, O_RDONLY);
+ if (tuner_fd == -1) {
+ g_critical("Couldn't open V4L2 tuner");
+ return false;
+ }
+
+ tuner.index = 0;
+ res = ioctl(tuner_fd, VIDIOC_G_TUNER, &tuner);
+ if (res < 0) {
+ g_critical("Couldn't get V4L2 tuner information");
+ return false;
+ }
+
+ if (tuner.type != V4L2_TUNER_RADIO) {
+ g_critical("Not a radio tuner\n");
+ return false;
+ }
+
+ tuner_precise = (tuner.capability & V4L2_TUNER_CAP_LOW) ?
+ TRUE : FALSE;
+ tuner_min = v4l_to_hz(tuner.rangelow);
+ tuner_max = v4l_to_hz(tuner.rangelow);
+
+ if (tuner_freq >= tuner_min && tuner_freq <= tuner_max &&
+ tuner_tune(tuner_freq)) {
+ // All is well, we are on air!
+ } else {
+ // Use whatever frequency the tuner is currently using
+ tuner_freq = tuner_get_tuned_freq();
+ }
+
+ server_notify_tuned(hz_to_mhz(tuner_freq));
+
+ return true;
+ } else {
+ if (tuner_fd != -1) {
+ close(tuner_fd);
+ tuner_fd = -1;
+ }
+ return true;
+ }
+}
+
+bool tuner_set_frequency(double mhz)
+{
+ unsigned long hz = mhz_to_hz(mhz);
+
+ if (tuner_tune(hz)) {
+ g_message("Tuned to %.1f Mhz", mhz);
+ server_notify_tuned(mhz);
+ tuner_freq = hz;
+ rds_reset();
+ return true;
+ }
+
+ return false;
+}
+
+bool tuner_search(bool forward)
+{
+ struct v4l2_hw_freq_seek t_freq_seek = {
+ .tuner = TUNER_DEVICE_ID,
+ .type = V4L2_TUNER_RADIO,
+ .seek_upward = forward
+ };
+
+ g_return_val_if_fail(tuner_fd != -1, false);
+
+ if (ioctl(tuner_fd, VIDIOC_S_HW_FREQ_SEEK, &t_freq_seek) < 0) {
+ g_warning("Failed to start seek: %s", strerror(errno));
+ return false;
+ }
+
+ // Search complete, get new frequency
+ unsigned long hz = tuner_get_tuned_freq();
+ if (!hz) {
+ return false;
+ }
+
+ // Got new frequency, fire signals.
+ tuner_freq = hz;
+ server_notify_tuned(hz_to_mhz(hz));
+ rds_reset();
+ return true;
+}