]> git.sesse.net Git - vlc/blobdiff - modules/access/dtv/linux.c
DTV: do not assume __linux__ == HAVE_LINUX_DVB
[vlc] / modules / access / dtv / linux.c
index 268a3e8f22a6c9807bd937957b5aa1318da728ec..84d78a5d818877825de263b56110f40bd98f7e26 100644 (file)
@@ -5,26 +5,27 @@
 /*****************************************************************************
  * Copyright © 2011 Rémi Denis-Courmont
  *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 2.1
- * of the License, or (at your option) any later version.
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
  *
- * This library is distributed in the hope that it will be useful,
+ * 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.
+ * GNU Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- ****************************************************************************/
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #endif
 
 #include <vlc_common.h>
+#include <vlc_fs.h>
 
 #include <errno.h>
 #include <assert.h>
 #include <poll.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
+#include <linux/dvb/version.h>
 #include <linux/dvb/frontend.h>
 #include <linux/dvb/dmx.h>
 
 #include "dtv/dtv.h"
+#ifdef HAVE_DVBPSI
+# include "dtv/en50221.h"
+#endif
 
 #ifndef O_SEARCH
 # define O_SEARCH O_RDONLY
 #endif
 
-/** Opens the device directory for the specified DVB adapter */
-static int dvb_open_adapter (uint8_t adapter)
-{
-    char dir[20];
-
-    snprintf (dir, sizeof (dir), "/dev/dvb/adapter%"PRIu8, adapter);
-    return open (dir, O_SEARCH|O_DIRECTORY|O_CLOEXEC);
-}
-
-/** Opens the DVB device node of the specified type */
-static int dvb_open_node (int dirfd, uint8_t dev, const char *type, int flags)
-{
-    int fd;
-    char path[strlen (type) + 4];
-
-    snprintf (path, sizeof (path), "%s%"PRIu8, type, dev);
-    fd = openat (dirfd, path, flags|O_CLOEXEC);
-    if (fd != -1)
-        fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK);
-    return fd;
-}
+#define DVBv5(minor) \
+        (DVB_API_VERSION > 5 \
+     || (DVB_API_VERSION == 5 && DVB_API_VERSION_MINOR >= (minor)))
 
 typedef struct
 {
@@ -132,24 +119,26 @@ static int dvb_parse_modulation (const char *str, int def)
     return dvb_parse_str (str, mods, sizeof (mods) / sizeof (*mods), def);
 }
 
-static int dvb_parse_fec (const char *str)
+static int dvb_parse_fec (uint32_t fec)
 {
-    static const dvb_str_map_t rates[] =
+    static const dvb_int_map_t rates[] =
     {
-        { "",     FEC_AUTO },
-        { "1/2",  FEC_1_2  },
+        { 0,             FEC_NONE },
+        { VLC_FEC(1,2),  FEC_1_2  },
         // TODO: 1/3
         // TODO: 1/4
-        { "2/3",  FEC_2_3  },
-        { "3/4",  FEC_3_4  },
-        { "4/5",  FEC_4_5  },
-        { "5/6",  FEC_5_6  },
-        { "6/7",  FEC_6_7  },
-        { "7/8",  FEC_7_8  },
-        { "8/9",  FEC_8_9  },
-        { "9/10", FEC_9_10 },
+        { VLC_FEC(2,3),  FEC_2_3  },
+        { VLC_FEC(3,4),  FEC_3_4  },
+        { VLC_FEC(3,5),  FEC_3_5  },
+        { VLC_FEC(4,5),  FEC_4_5  },
+        { VLC_FEC(5,6),  FEC_5_6  },
+        { VLC_FEC(6,7),  FEC_6_7  },
+        { VLC_FEC(7,8),  FEC_7_8  },
+        { VLC_FEC(8,9),  FEC_8_9  },
+        { VLC_FEC(9,10), FEC_9_10 },
+        { VLC_FEC_AUTO,  FEC_AUTO },
     };
-    return dvb_parse_str (str, rates, sizeof (rates) / sizeof (*rates),
+    return dvb_parse_int (fec, rates, sizeof (rates) / sizeof (*rates),
                           FEC_AUTO);
 }
 
@@ -157,61 +146,84 @@ static int dvb_parse_fec (const char *str)
 struct dvb_device
 {
     vlc_object_t *obj;
-    int frontend;
+    int dir;
     int demux;
+    int frontend;
 #ifndef USE_DMX
 # define MAX_PIDS 256
-    int dir;
-    uint8_t dev_id;
     struct
     {
         int fd;
         uint16_t pid;
     } pids[MAX_PIDS];
 #endif
-    int ca;
-    struct dvb_frontend_info info;
+#ifdef HAVE_DVBPSI
+    cam_t *cam;
+#endif
+    uint8_t device;
     bool budget;
     //size_t buffer_size;
 };
 
+/** Opens the device directory for the specified DVB adapter */
+static int dvb_open_adapter (uint8_t adapter)
+{
+    char dir[20];
+
+    snprintf (dir, sizeof (dir), "/dev/dvb/adapter%"PRIu8, adapter);
+    return vlc_open (dir, O_SEARCH|O_DIRECTORY);
+}
+
+/** Opens the DVB device node of the specified type */
+static int dvb_open_node (dvb_device_t *d, const char *type, int flags)
+{
+    int fd;
+    char path[strlen (type) + 4];
+
+    snprintf (path, sizeof (path), "%s%u", type, d->device);
+    fd = vlc_openat (d->dir, path, flags);
+    if (fd != -1)
+        fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK);
+    return fd;
+}
+
 /**
  * Opens the DVB tuner
  */
-dvb_device_t *dvb_open (vlc_object_t *obj, bool tune)
+dvb_device_t *dvb_open (vlc_object_t *obj)
 {
+    dvb_device_t *d = malloc (sizeof (*d));
+    if (unlikely(d == NULL))
+        return NULL;
+
+    d->obj = obj;
+
     uint8_t adapter = var_InheritInteger (obj, "dvb-adapter");
-    uint8_t device  = var_InheritInteger (obj, "dvb-device");
+    d->device = var_InheritInteger (obj, "dvb-device");
 
-    int dirfd = dvb_open_adapter (adapter);
-    if (dirfd == -1)
+    d->dir = dvb_open_adapter (adapter);
+    if (d->dir == -1)
     {
         msg_Err (obj, "cannot access adapter %"PRIu8": %m", adapter);
+        free (d);
         return NULL;
     }
-
-    dvb_device_t *d = malloc (sizeof (*d));
-    if (unlikely(d == NULL))
-    {
-        close (dirfd);
-        return NULL;
-    }
-
-    d->obj = obj;
     d->frontend = -1;
-    d->ca = -1;
+#ifdef HAVE_DVBPSI
+    d->cam = NULL;
+#endif
     d->budget = var_InheritBool (obj, "dvb-budget-mode");
 
 #ifndef USE_DMX
     if (d->budget)
 #endif
     {
-       d->demux = dvb_open_node (dirfd, device, "demux", O_RDONLY);
+       d->demux = dvb_open_node (d, "demux", O_RDONLY);
        if (d->demux == -1)
        {
            msg_Err (obj, "cannot access demultiplexer: %m");
+           close (d->dir);
            free (d);
-           close (dirfd);
            return NULL;
        }
 
@@ -236,52 +248,33 @@ dvb_device_t *dvb_open (vlc_object_t *obj, bool tune)
     }
     else
     {
-        d->dir = fcntl (dirfd, F_DUPFD_CLOEXEC);
-        d->dev_id = device;
-
         for (size_t i = 0; i < MAX_PIDS; i++)
             d->pids[i].pid = d->pids[i].fd = -1;
-        d->demux = dvb_open_node (d->dir, device, "dvr", O_RDONLY);
+        d->demux = dvb_open_node (d, "dvr", O_RDONLY);
         if (d->demux == -1)
         {
             msg_Err (obj, "cannot access DVR: %m");
+            close (d->dir);
             free (d);
-            close (dirfd);
             return NULL;
         }
 #endif
     }
 
-    if (tune)
+#ifdef HAVE_DVBPSI
+    int ca = dvb_open_node (d, "ca", O_RDWR);
+    if (ca != -1)
     {
-        d->frontend = dvb_open_node (dirfd, device, "frontend", O_RDWR);
-        if (d->frontend == -1)
-        {
-            msg_Err (obj, "cannot access frontend %"PRIu8
-                     " of adapter %"PRIu8": %m", device, adapter);
-            goto error;
-        }
-
-        if (ioctl (d->frontend, FE_GET_INFO, &d->info) < 0)
-        {
-            msg_Err (obj, "cannot get frontend info: %m");
-            goto error;
-        }
-
-        msg_Dbg (obj, "using frontend: %s", d->info.name);
-        msg_Dbg (obj, " type %u, capabilities 0x%08X", d->info.type,
-                 d->info.caps);
-
-        d->ca = dvb_open_node (dirfd, device, "ca", O_RDWR);
-        if (d->ca == -1)
-            msg_Dbg (obj, "conditional access module not available (%m)");
-
+        d->cam = en50221_Init (obj, ca);
+        if (d->cam == NULL)
+            close (ca);
     }
-    close (dirfd);
+    else
+        msg_Dbg (obj, "conditional access module not available (%m)");
+#endif
     return d;
 
 error:
-    close (dirfd);
     dvb_close (d);
     return NULL;
 }
@@ -291,17 +284,19 @@ void dvb_close (dvb_device_t *d)
 #ifndef USE_DMX
     if (!d->budget)
     {
-        close (d->dir);
         for (size_t i = 0; i < MAX_PIDS; i++)
             if (d->pids[i].fd != -1)
                 close (d->pids[i].fd);
     }
 #endif
-    if (d->ca != -1)
-        close (d->ca);
+#ifdef HAVE_DVBPSI
+    if (d->cam != NULL)
+        en50221_End (d->cam);
+#endif
     if (d->frontend != -1)
         close (d->frontend);
     close (d->demux);
+    close (d->dir);
     free (d);
 }
 
@@ -314,6 +309,11 @@ ssize_t dvb_read (dvb_device_t *d, void *buf, size_t len)
     struct pollfd ufd[2];
     int n;
 
+#ifdef HAVE_DVBPSI
+    if (d->cam != NULL)
+        en50221_Poll (d->cam);
+#endif
+
     ufd[0].fd = d->demux;
     ufd[0].events = POLLIN;
     if (d->frontend != -1)
@@ -380,7 +380,7 @@ int dvb_add_pid (dvb_device_t *d, uint16_t pid)
         if (d->pids[i].fd != -1)
             continue;
 
-        int fd = dvb_open_node (d->dir, d->dev_id, "demux", O_RDONLY);
+        int fd = dvb_open_node (d, "demux", O_RDONLY);
         if (fd == -1)
             goto error;
 
@@ -429,27 +429,170 @@ void dvb_remove_pid (dvb_device_t *d, uint16_t pid)
 #endif
 }
 
-const delsys_t *dvb_guess_system (dvb_device_t *d)
+/** Finds a frontend of the correct type */
+static int dvb_open_frontend (dvb_device_t *d)
 {
-    assert (d->frontend != -1);
+    if (d->frontend != -1)
+        return 0;
+    int fd = dvb_open_node (d, "frontend", O_RDWR);
+    if (fd == -1)
+    {
+        msg_Err (d->obj, "cannot access frontend: %m");
+        return -1;
+    }
 
-    //bool v2 = d->info.caps & FE_CAN_2G_MODULATION;
+    d->frontend = fd;
+    return 0;
+}
+#define dvb_find_frontend(d, sys) (dvb_open_frontend(d))
 
-    switch (d->info.type)
+/**
+ * Detects supported delivery systems.
+ * @return a bit mask of supported systems (zero on failure)
+ */
+unsigned dvb_enum_systems (dvb_device_t *d)
+{
+    if (dvb_open_frontend (d))
+        return 0;
+#if DVBv5(5)
+    struct dtv_property prop[2] = {
+        { .cmd = DTV_API_VERSION },
+        { .cmd = DTV_ENUM_DELSYS },
+    };
+    struct dtv_properties props = {
+        .num = 2,
+        .props = prop
+    };
+
+    if (ioctl (d->frontend, FE_GET_PROPERTY, &props) < 0)
     {
-        case FE_QPSK: return /*v2 ? &dvbs2 :*/ &dvbs;
-        case FE_QAM:  return &dvbc;
-        case FE_OFDM: return &dvbt;
-        case FE_ATSC: return &atsc;
+         msg_Err (d->obj, "cannot enumerate frontend systems: %m");
+         goto legacy;
     }
-    return NULL;
+
+    static const unsigned systab[] = {
+        [SYS_UNDEFINED]    = 0,
+        [SYS_DVBC_ANNEX_A] = DVB_C,
+        [SYS_DVBC_ANNEX_B] = CQAM,
+        [SYS_DVBT]         = DVB_T,
+        //[SYS_DSS]
+        [SYS_DVBS]         = DVB_S,
+        [SYS_DVBS2]        = DVB_S2,
+        //[SYS_DVBH]
+        [SYS_ISDBT]        = ISDB_T,
+        [SYS_ISDBS]        = ISDB_S,
+        [SYS_ISDBC]        = ISDB_C, // no drivers exist (as of 3.3-rc6)
+        [SYS_ATSC]         = ATSC,
+        //[SYS_ATSCMH]
+        //[SYS_DMBTH]
+        //[SYS_CMMB]
+        //[SYS_DAB]
+        [SYS_DVBT2]        = DVB_T2,
+        //[SYS_TURBO]
+        [SYS_DVBC_ANNEX_C] = ISDB_C, // another name for ISDB-C?
+    };
+    unsigned systems = 0;
+
+    msg_Dbg (d->obj, "probing frontend (kernel API v%u.%u, user API v%u.%u)",
+             prop[0].u.data >> 8, prop[0].u.data & 0xFF,
+             DVB_API_VERSION, DVB_API_VERSION_MINOR);
+
+    for (size_t i = 0; i < prop[1].u.buffer.len; i++)
+    {
+        unsigned sys = prop[1].u.buffer.data[i];
+
+        if (sys >= (sizeof (systab) / sizeof (systab[0])) || !systab[sys])
+        {
+            msg_Warn (d->obj, "unknown delivery system %u", sys);
+            continue;
+        }
+        msg_Dbg (d->obj, " system %u", sys);
+        systems |= systab[sys];
+    }
+
+    return systems;
+legacy:
+    props.num = 1;
+#else
+    struct dtv_property prop[1] = {
+        { .cmd = DTV_API_VERSION },
+    };
+    struct dtv_properties props = {
+        .num = 1,
+        .props = prop
+    };
+    unsigned systems = 0;
+#endif
+    if (ioctl (d->frontend, FE_GET_PROPERTY, &props) < 0)
+    {
+        msg_Err (d->obj, "unsupported kernel DVB version 3 or older (%m)");
+        return 0;
+    }
+
+    msg_Dbg (d->obj, "probing frontend (kernel API v%u.%u, user API v%u.%u)",
+             prop[0].u.data >> 8, prop[0].u.data & 0xFF,
+             DVB_API_VERSION, DVB_API_VERSION_MINOR);
+#if !DVBv5(5)
+    /* Some delivery systems cannot be detected without the DVB API v5.5.
+     * To run correctly on recent kernels (Linux >= 3.3),
+     * VLC needs to be compiled with up-to-date kernel headers. */
+    if ((prop[0].u.data >> 8) > 5
+     || ((prop[0].u.data >> 8) == 5 && (prop[0].u.data & 0xFF) >= 5))
+        msg_Err (d->obj, "obsolete user API version running on a new kernel");
+        msg_Info (d->obj, "please recompile "PACKAGE_NAME" "PACKAGE_VERSION);
+#endif
+    struct dvb_frontend_info info;
+    if (ioctl (d->frontend, FE_GET_INFO, &info) < 0)
+    {
+        msg_Err (d->obj, "cannot get frontend info: %m");
+        return 0;
+    }
+    msg_Dbg (d->obj, " name %s", info.name);
+    msg_Dbg (d->obj, " type %u, capabilities 0x%08X", info.type, info.caps);
+    msg_Dbg (d->obj, " frequencies %10"PRIu32" to %10"PRIu32,
+             info.frequency_min, info.frequency_max);
+    msg_Dbg (d->obj, " (%"PRIu32" tolerance, %"PRIu32" per step)",
+             info.frequency_tolerance, info.frequency_stepsize);
+    msg_Dbg (d->obj, " bauds rates %10"PRIu32" to %10"PRIu32,
+             info.symbol_rate_min, info.symbol_rate_max);
+    msg_Dbg (d->obj, " (%"PRIu32" tolerance)", info.symbol_rate_tolerance);
+
+    /* DVB first generation and ATSC */
+    switch (info.type)
+    {
+        case FE_QPSK: systems = DVB_S; break;
+        case FE_QAM:  systems = DVB_C; break;
+        case FE_OFDM: systems = DVB_T; break;
+        case FE_ATSC: systems = ATSC | CQAM; break;
+        default:
+            msg_Err (d->obj, "unknown frontend type %u", info.type);
+    }
+
+    /* DVB 2nd generation */
+    switch (info.type)
+    {
+        case FE_QPSK:
+        case FE_QAM:
+        case FE_OFDM:
+            if (info.caps & FE_CAN_2G_MODULATION)
+                systems |= systems << 1; /* DVB_foo -> DVB_foo|DVB_foo2 */
+        default:
+            break;
+    }
+
+    /* ISDB (only terrestrial before DVBv5.5)  */
+    if (info.type == FE_OFDM)
+        systems |= ISDB_T;
+
+    return systems;
 }
 
 float dvb_get_signal_strength (dvb_device_t *d)
 {
     uint16_t strength;
 
-    if (ioctl (d->frontend, FE_READ_SIGNAL_STRENGTH, &strength) < 0)
+    if (d->frontend == -1
+     || ioctl (d->frontend, FE_READ_SIGNAL_STRENGTH, &strength) < 0)
         return 0.;
     return strength / 65535.;
 }
@@ -458,23 +601,33 @@ float dvb_get_snr (dvb_device_t *d)
 {
     uint16_t snr;
 
-    if (ioctl (d->frontend, FE_READ_SNR, &snr) < 0)
+    if (d->frontend == -1 || ioctl (d->frontend, FE_READ_SNR, &snr) < 0)
         return 0.;
     return snr / 65535.;
 }
 
+#ifdef HAVE_DVBPSI
+void dvb_set_ca_pmt (dvb_device_t *d, struct dvbpsi_pmt_s *pmt)
+{
+    if (d->cam != NULL)
+        en50221_SetCAPMT (d->cam, pmt);
+}
+#endif
+
 static int dvb_vset_props (dvb_device_t *d, size_t n, va_list ap)
 {
+    assert (n <= DTV_IOCTL_MAX_MSGS);
+
     struct dtv_property buf[n], *prop = buf;
     struct dtv_properties props = { .num = n, .props = buf };
 
-    memset (prop, 0, sizeof (prop));
+    memset (buf, 0, sizeof (buf));
 
     while (n > 0)
     {
         prop->cmd = va_arg (ap, uint32_t);
         prop->u.data = va_arg (ap, uint32_t);
-        msg_Dbg (d->obj, "setting property %"PRIu32" to %"PRIu32,
+        msg_Dbg (d->obj, "setting property %2"PRIu32" to %"PRIu32,
                  prop->cmd, prop->u.data);
         prop++;
         n--;
@@ -523,14 +676,20 @@ int dvb_tune (dvb_device_t *d)
 
 /*** DVB-C ***/
 int dvb_set_dvbc (dvb_device_t *d, uint32_t freq, const char *modstr,
-                  uint32_t srate, const char *fecstr)
+                  uint32_t srate, uint32_t fec)
 {
     unsigned mod = dvb_parse_modulation (modstr, QAM_AUTO);
-    unsigned fec = dvb_parse_fec (fecstr);
+    fec = dvb_parse_fec (fec);
 
+    if (dvb_find_frontend (d, DVB_C))
+        return -1;
     return dvb_set_props (d, 6, DTV_CLEAR, 0,
+#if DVBv5(5)
+                          DTV_DELIVERY_SYSTEM, SYS_DVBC_ANNEX_A,
+#else
                           DTV_DELIVERY_SYSTEM, SYS_DVBC_ANNEX_AC,
-                          DTV_FREQUENCY, freq * 1000, DTV_MODULATION, mod,
+#endif
+                          DTV_FREQUENCY, freq, DTV_MODULATION, mod,
                           DTV_SYMBOL_RATE, srate, DTV_INNER_FEC, fec);
 }
 
@@ -548,9 +707,11 @@ static unsigned dvb_parse_polarization (char pol)
     return dvb_parse_int (pol, tab, 5, SEC_VOLTAGE_OFF);
 }
 
-int dvb_set_sec (dvb_device_t *d, uint32_t freq, char pol,
+int dvb_set_sec (dvb_device_t *d, uint64_t freq_Hz, char pol,
                  uint32_t lowf, uint32_t highf, uint32_t switchf)
 {
+    uint32_t freq = freq_Hz / 1000;
+
     /* Always try to configure high voltage, but only warn on enable failure */
     int val = var_InheritBool (d->obj, "dvb-high-voltage");
     if (ioctl (d->frontend, FE_ENABLE_HIGH_LNB_VOLTAGE, &val) < 0 && val)
@@ -571,10 +732,10 @@ int dvb_set_sec (dvb_device_t *d, uint32_t freq, char pol,
              {  2500,  2700,  3650,     0 }, /* S band */
              {   950,  2150,     0,     0 }, /* adjusted IF (L band) */
         };
-        uint_fast16_t mhz = freq / 1000;
+        uint_fast16_t mHz = freq / 1000;
 
         for (size_t i = 0; i < sizeof (tab) / sizeof (tab[0]); i++)
-             if (mhz >= tab[i].min && mhz <= tab[i].max)
+             if (mHz >= tab[i].min && mHz <= tab[i].max)
              {
                  lowf = tab[i].low * 1000;
                  highf = tab[i].high * 1000;
@@ -595,9 +756,6 @@ known:
         freq *= -1;
     assert (freq < 0x7fffffff);
 
-    /* TODO: DiSEqC */
-
-    /* Continuous tone (to select high oscillator frequency) */
     int tone;
     switch (var_InheritInteger (d->obj, "dvb-tone"))
     {
@@ -606,27 +764,105 @@ known:
         default: tone = high ? SEC_TONE_ON : SEC_TONE_OFF;
     }
 
+    /*** LNB selection / DiSEqC ***/
     unsigned voltage = dvb_parse_polarization (pol);
+    if (dvb_set_props (d, 2, DTV_TONE, SEC_TONE_OFF, DTV_VOLTAGE, voltage))
+        return -1;
 
-    return dvb_set_props (d, 3, DTV_FREQUENCY, freq,
-                          DTV_VOLTAGE, voltage, DTV_TONE, tone);
+    unsigned satno = var_InheritInteger (d->obj, "dvb-satno");
+    if (satno > 0)
+    {
+#undef msleep /* we know what we are doing! */
+
+        /* DiSEqC Bus Specification:
+ http://www.eutelsat.com/satellites/pdf/Diseqc/Reference%20docs/bus_spec.pdf */
+
+        /* DiSEqC 1.1 */
+        struct dvb_diseqc_master_cmd uncmd;
+
+        /* DiSEqC 1.0 */
+        struct dvb_diseqc_master_cmd cmd;
+
+        satno = (satno - 1) & 3;
+        cmd.msg[0] = 0xE0; /* framing: master, no reply, 1st TX */
+        cmd.msg[1] = 0x10; /* address: all LNB/switch */
+        cmd.msg[2] = 0x38; /* command: Write Port Group 0 (committed) */
+        cmd.msg[3] = 0xF0  /* data[0]: clear all bits */
+                   | (satno << 2) /* LNB (A, B, C or D) */
+                   | ((voltage == SEC_VOLTAGE_18) << 1) /* polarization */
+                   | (tone == SEC_TONE_ON); /* option */
+        cmd.msg[4] = cmd.msg[5] = 0; /* unused */
+        cmd.msg_len = 4; /* length*/
+
+        msleep (15000); /* wait 15 ms before DiSEqC command */
+        unsigned uncommitted = var_InheritInteger (d->obj, "dvb-uncommitted");
+        if (uncommitted > 0)
+        {
+          uncommitted = (uncommitted - 1) & 3;
+          uncmd.msg[0] = 0xE0; /* framing: master, no reply, 1st TX */
+          uncmd.msg[1] = 0x10; /* address: all LNB/switch */
+          uncmd.msg[2] = 0x39; /* command: Write Port Group 1 (uncommitted) */
+          uncmd.msg[3] = 0xF0  /* data[0]: clear all bits */
+                       | (uncommitted << 2) /* LNB (A, B, C or D) */
+                       | ((voltage == SEC_VOLTAGE_18) << 1) /* polarization */
+                       | (tone == SEC_TONE_ON); /* option */
+          uncmd.msg[4] = uncmd.msg[5] = 0; /* unused */
+          uncmd.msg_len = 4; /* length*/
+          if (ioctl (d->frontend, FE_DISEQC_SEND_MASTER_CMD, &uncmd) < 0)
+          {
+              msg_Err (d->obj, "cannot send DiSEqC command: %m");
+              return -1;
+          }
+          /* Repeat uncommitted command */
+          uncmd.msg[0] = 0xE1; /* framing: master, no reply, repeated TX */
+          if (ioctl (d->frontend, FE_DISEQC_SEND_MASTER_CMD, &uncmd) < 0)
+          {
+              msg_Err (d->obj, "cannot send DiSEqC command: %m");
+              return -1;
+          }
+          msleep(125000); /* wait 125 ms before committed DiSEqC command */
+        }
+        if (ioctl (d->frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd) < 0)
+        {
+            msg_Err (d->obj, "cannot send DiSEqC command: %m");
+            return -1;
+        }
+        msleep (54000 + 15000);
+
+        /* Mini-DiSEqC */
+        satno &= 1;
+        if (ioctl (d->frontend, FE_DISEQC_SEND_BURST,
+                   satno ? SEC_MINI_B : SEC_MINI_A) < 0)
+        {
+            msg_Err (d->obj, "cannot send Mini-DiSEqC tone burst: %m");
+            return -1;
+        }
+        msleep (15000);
+    }
+
+    /* Continuous tone (to select high oscillator frequency) */
+    return dvb_set_props (d, 2, DTV_FREQUENCY, freq, DTV_TONE, tone);
 }
 
-int dvb_set_dvbs (dvb_device_t *d, uint32_t freq,
-                  uint32_t srate, const char *fecstr)
+int dvb_set_dvbs (dvb_device_t *d, uint64_t freq_Hz,
+                  uint32_t srate, uint32_t fec)
 {
-    unsigned fec = dvb_parse_fec (fecstr);
+    uint32_t freq = freq_Hz / 1000;
+    fec = dvb_parse_fec (fec);
 
+    if (dvb_find_frontend (d, DVB_S))
+        return -1;
     return dvb_set_props (d, 5, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_DVBS,
                           DTV_FREQUENCY, freq, DTV_SYMBOL_RATE, srate,
                           DTV_INNER_FEC, fec);
 }
 
-int dvb_set_dvbs2 (dvb_device_t *d, uint32_t freq, const char *modstr,
-                   uint32_t srate, const char *fecstr, int pilot, int rolloff)
+int dvb_set_dvbs2 (dvb_device_t *d, uint64_t freq_Hz, const char *modstr,
+                   uint32_t srate, uint32_t fec, int pilot, int rolloff)
 {
+    uint32_t freq = freq_Hz / 1000;
     unsigned mod = dvb_parse_modulation (modstr, QPSK);
-    unsigned fec = dvb_parse_fec (fecstr);
+    fec = dvb_parse_fec (fec);
 
     switch (pilot)
     {
@@ -643,6 +879,8 @@ int dvb_set_dvbs2 (dvb_device_t *d, uint32_t freq, const char *modstr,
         default: rolloff = PILOT_AUTO; break;
     }
 
+    if (dvb_find_frontend (d, DVB_S2))
+        return -1;
     return dvb_set_props (d, 8, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_DVBS2,
                           DTV_FREQUENCY, freq, DTV_MODULATION, mod,
                           DTV_SYMBOL_RATE, srate, DTV_INNER_FEC, fec,
@@ -651,14 +889,27 @@ int dvb_set_dvbs2 (dvb_device_t *d, uint32_t freq, const char *modstr,
 
 
 /*** DVB-T ***/
+static uint32_t dvb_parse_bandwidth (uint32_t i)
+{
+    switch (i)
+    {
+      //case  0: return 0;
+        case  2: return 1712000;
+        default: return i * 1000000;
+    }
+}
+
 static int dvb_parse_transmit_mode (int i)
 {
     static const dvb_int_map_t tab[] = {
         { -1, TRANSMISSION_MODE_AUTO },
+#if DVBv5(3)
+        {  1, TRANSMISSION_MODE_1K   },
+#endif
         {  2, TRANSMISSION_MODE_2K   },
         {  4, TRANSMISSION_MODE_4K   },
         {  8, TRANSMISSION_MODE_8K   },
-#if 0
+#if DVBv5(3)
         { 16, TRANSMISSION_MODE_16K  },
         { 32, TRANSMISSION_MODE_32K  },
 #endif
@@ -667,19 +918,21 @@ static int dvb_parse_transmit_mode (int i)
                           TRANSMISSION_MODE_AUTO);
 }
 
-static int dvb_parse_guard (const char *str)
+static int dvb_parse_guard (uint32_t guard)
 {
-    static const dvb_str_map_t tab[] = {
-        { "",       GUARD_INTERVAL_AUTO },
-      /*{ "1/128",  GUARD_INTERVAL_1_128 },*/
-        { "1/16",   GUARD_INTERVAL_1_16 },
-        { "1/32",   GUARD_INTERVAL_1_32 },
-        { "1/4",    GUARD_INTERVAL_1_4 },
-        { "1/8",    GUARD_INTERVAL_1_8 },
-      /*{ "19/128", GUARD_INTERVAL_19_128 },*/
-      /*{ "9/256",  GUARD_INTERVAL_9_256 },*/
+    static const dvb_int_map_t tab[] = {
+        { VLC_GUARD(1,4),    GUARD_INTERVAL_1_4 },
+        { VLC_GUARD(1,8),    GUARD_INTERVAL_1_8 },
+        { VLC_GUARD(1,16),   GUARD_INTERVAL_1_16 },
+        { VLC_GUARD(1,32),   GUARD_INTERVAL_1_32 },
+#if DVBv5(3)
+        { VLC_GUARD(1,128),  GUARD_INTERVAL_1_128 },
+        { VLC_GUARD(19,128), GUARD_INTERVAL_19_128 },
+        { VLC_GUARD(19,256), GUARD_INTERVAL_19_256 },
+#endif
+        { VLC_GUARD_AUTO,    GUARD_INTERVAL_AUTO },
     };
-    return dvb_parse_str (str, tab, sizeof (tab) / sizeof (*tab),
+    return dvb_parse_int (guard, tab, sizeof (tab) / sizeof (*tab),
                           GUARD_INTERVAL_AUTO);
 }
 
@@ -697,32 +950,148 @@ static int dvb_parse_hierarchy (int i)
 }
 
 int dvb_set_dvbt (dvb_device_t *d, uint32_t freq, const char *modstr,
-                  const char *fechstr, const char *feclstr, uint32_t bandwidth,
-                  int transmit_val, const char *guardstr, int hierarchy_val)
+                  uint32_t fec_hp, uint32_t fec_lp, uint32_t bandwidth,
+                  int transmit_mode, uint32_t guard, int hierarchy)
 {
     uint32_t mod = dvb_parse_modulation (modstr, QAM_AUTO);
-    uint32_t fec_hp = dvb_parse_fec (fechstr);
-    uint32_t fec_lp = dvb_parse_fec (feclstr);
-    bandwidth *= 1000000;
-    uint32_t transmit_mode = dvb_parse_transmit_mode (transmit_val);
-    uint32_t guard_it = dvb_parse_guard (guardstr);
-    uint32_t hierarchy = dvb_parse_hierarchy (hierarchy_val);
-
+    fec_hp = dvb_parse_fec (fec_hp);
+    fec_lp = dvb_parse_fec (fec_lp);
+    bandwidth = dvb_parse_bandwidth (bandwidth);
+    transmit_mode = dvb_parse_transmit_mode (transmit_mode);
+    guard = dvb_parse_guard (guard);
+    hierarchy = dvb_parse_hierarchy (hierarchy);
+
+    if (dvb_find_frontend (d, DVB_T))
+        return -1;
     return dvb_set_props (d, 10, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_DVBT,
-                          DTV_FREQUENCY, freq * 1000, DTV_MODULATION, mod,
+                          DTV_FREQUENCY, freq, DTV_MODULATION, mod,
                           DTV_CODE_RATE_HP, fec_hp, DTV_CODE_RATE_LP, fec_lp,
                           DTV_BANDWIDTH_HZ, bandwidth,
                           DTV_TRANSMISSION_MODE, transmit_mode,
-                          DTV_GUARD_INTERVAL, guard_it,
+                          DTV_GUARD_INTERVAL, guard,
                           DTV_HIERARCHY, hierarchy);
 }
 
+int dvb_set_dvbt2 (dvb_device_t *d, uint32_t freq, const char *modstr,
+                   uint32_t fec, uint32_t bandwidth,
+                   int transmit_mode, uint32_t guard, uint32_t plp)
+{
+#if DVBv5(3)
+    uint32_t mod = dvb_parse_modulation (modstr, QAM_AUTO);
+    fec = dvb_parse_fec (fec);
+    bandwidth = dvb_parse_bandwidth (bandwidth);
+    transmit_mode = dvb_parse_transmit_mode (transmit_mode);
+    guard = dvb_parse_guard (guard);
+
+    if (dvb_find_frontend (d, DVB_T2))
+        return -1;
+    return dvb_set_props (d, 8, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_DVBT2,
+                          DTV_FREQUENCY, freq, DTV_MODULATION, mod,
+                          DTV_INNER_FEC, fec, DTV_BANDWIDTH_HZ, bandwidth,
+                          DTV_TRANSMISSION_MODE, transmit_mode,
+                          DTV_GUARD_INTERVAL, guard,
+                          DTV_DVBT2_PLP_ID, plp);
+#else
+# warning DVB-T2 needs Linux DVB version 5.3 or later.
+    msg_Err (d->obj, "DVB-T2 support not compiled-in");
+    (void) freq; (void) modstr; (void) fec; (void) bandwidth;
+    (void) transmit_mode; (void) guard;
+    return -1;
+#endif
+}
+
+
+/*** ISDB-C ***/
+int dvb_set_isdbc (dvb_device_t *d, uint32_t freq, const char *modstr,
+                   uint32_t srate, uint32_t fec)
+{
+    unsigned mod = dvb_parse_modulation (modstr, QAM_AUTO);
+    fec = dvb_parse_fec (fec);
+
+    if (dvb_find_frontend (d, ISDB_C))
+        return -1;
+    return dvb_set_props (d, 6, DTV_CLEAR, 0,
+#if DVBv5(5)
+                          DTV_DELIVERY_SYSTEM, SYS_DVBC_ANNEX_C,
+#else
+# warning ISDB-C might need Linux DVB version 5.5 or later.
+                          DTV_DELIVERY_SYSTEM, SYS_DVBC_ANNEX_AC,
+#endif
+                          DTV_FREQUENCY, freq, DTV_MODULATION, mod,
+                          DTV_SYMBOL_RATE, srate, DTV_INNER_FEC, fec);
+}
+
+
+/*** ISDB-S ***/
+int dvb_set_isdbs (dvb_device_t *d, uint64_t freq_Hz, uint16_t ts_id)
+{
+    uint32_t freq = freq_Hz / 1000;
+
+    if (dvb_find_frontend (d, ISDB_S))
+        return -1;
+    return dvb_set_props (d, 5, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_ISDBS,
+                          DTV_FREQUENCY, freq,
+                          DTV_ISDBS_TS_ID, (uint32_t)ts_id);
+}
+
+
+/*** ISDB-T ***/
+static int dvb_set_isdbt_layer (dvb_device_t *d, unsigned num,
+                                const isdbt_layer_t *l)
+{
+    uint32_t mod = dvb_parse_modulation (l->modulation, QAM_AUTO);
+    uint32_t fec = dvb_parse_fec (l->code_rate);
+    uint32_t count = l->segment_count;
+    uint32_t ti = l->time_interleaving;
+
+    num *= DTV_ISDBT_LAYERB_FEC - DTV_ISDBT_LAYERA_FEC;
+
+    return dvb_set_props (d, 5, DTV_DELIVERY_SYSTEM, SYS_ISDBT,
+                          DTV_ISDBT_LAYERA_FEC + num, fec,
+                          DTV_ISDBT_LAYERA_MODULATION + num, mod,
+                          DTV_ISDBT_LAYERA_SEGMENT_COUNT + num, count,
+                          DTV_ISDBT_LAYERA_TIME_INTERLEAVING + num, ti);
+}
+
+int dvb_set_isdbt (dvb_device_t *d, uint32_t freq, uint32_t bandwidth,
+                   int transmit_mode, uint32_t guard,
+                   const isdbt_layer_t layers[3])
+{
+    bandwidth = dvb_parse_bandwidth (bandwidth);
+    transmit_mode = dvb_parse_transmit_mode (transmit_mode);
+    guard = dvb_parse_guard (guard);
+
+    if (dvb_find_frontend (d, ISDB_T))
+        return -1;
+    if (dvb_set_props (d, 5, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_ISDBT,
+                       DTV_FREQUENCY, freq, DTV_BANDWIDTH_HZ, bandwidth,
+                       DTV_GUARD_INTERVAL, guard))
+        return -1;
+    for (unsigned i = 0; i < 3; i++)
+        if (dvb_set_isdbt_layer (d, i, layers + i))
+            return -1;
+    return 0;
+}
+
 
 /*** ATSC ***/
 int dvb_set_atsc (dvb_device_t *d, uint32_t freq, const char *modstr)
 {
     unsigned mod = dvb_parse_modulation (modstr, VSB_8);
 
+    if (dvb_find_frontend (d, ATSC))
+        return -1;
     return dvb_set_props (d, 4, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_ATSC,
-                          DTV_FREQUENCY, freq * 1000, DTV_MODULATION, mod);
+                          DTV_FREQUENCY, freq, DTV_MODULATION, mod);
+}
+
+int dvb_set_cqam (dvb_device_t *d, uint32_t freq, const char *modstr)
+{
+    unsigned mod = dvb_parse_modulation (modstr, QAM_AUTO);
+
+    if (dvb_find_frontend (d, CQAM))
+        return -1;
+    return dvb_set_props (d, 4, DTV_CLEAR, 0,
+                          DTV_DELIVERY_SYSTEM, SYS_DVBC_ANNEX_B,
+                          DTV_FREQUENCY, freq, DTV_MODULATION, mod);
 }