* [PATCH] media: dvb-usb-v2: nerc: add support for NERC DtmbUSB DTMB receiver
@ 2026-07-10 6:31 David Yang
0 siblings, 0 replies; only message in thread
From: David Yang @ 2026-07-10 6:31 UTC (permalink / raw)
To: linux-media; +Cc: David Yang, Mauro Carvalho Chehab, linux-kernel
NERC DtmbUSB (04b4:1004) is a highly integrated DTMB USB receiver.
* USB Bridge: Cypress CY7C68013A
* Receiver: HDIC HD2312A
* Tuner: MaxLinear MxL608
* ROM: 24-series flash
Three hardware variants is found: Letv, Aiwa, and CVB.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
MAINTAINERS | 8 +
drivers/media/usb/dvb-usb-v2/Kconfig | 8 +
drivers/media/usb/dvb-usb-v2/Makefile | 3 +
drivers/media/usb/dvb-usb-v2/nerc.c | 362 ++++++++++++++++++++++++++
drivers/media/usb/dvb-usb-v2/nerc.h | 39 +++
include/media/dvb-usb-ids.h | 1 +
6 files changed, 421 insertions(+)
create mode 100644 drivers/media/usb/dvb-usb-v2/nerc.c
create mode 100644 drivers/media/usb/dvb-usb-v2/nerc.h
diff --git a/MAINTAINERS b/MAINTAINERS
index b007f20b2763..a9fc662b2411 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9070,6 +9070,14 @@ Q: http://patchwork.linuxtv.org/project/linux-media/list/
T: git git://linuxtv.org/mkrufky/mxl111sf.git
F: drivers/media/usb/dvb-usb-v2/mxl111sf*
+DVB_USB_NERC MEDIA DRIVER
+M: David Yang <mmyangfl@gmail.com>
+L: linux-media@vger.kernel.org
+S: Maintained
+W: https://linuxtv.org
+Q: http://patchwork.linuxtv.org/project/linux-media/list/
+F: drivers/media/usb/dvb-usb-v2/nerc*
+
DVB_USB_RTL28XXU MEDIA DRIVER
L: linux-media@vger.kernel.org
S: Orphan
diff --git a/drivers/media/usb/dvb-usb-v2/Kconfig b/drivers/media/usb/dvb-usb-v2/Kconfig
index 4eadc9539b4c..93c711f4da73 100644
--- a/drivers/media/usb/dvb-usb-v2/Kconfig
+++ b/drivers/media/usb/dvb-usb-v2/Kconfig
@@ -144,6 +144,14 @@ config DVB_USB_MXL111SF
help
Say Y here to support the MxL111SF USB2.0 DTV receiver.
+config DVB_USB_NERC
+ tristate "NERC DTMB USB2.0 support"
+ depends on DVB_USB_V2
+ select DVB_EC100
+ help
+ Say Y here to support the NERC DTMB (CY7C68013A + HDIC HD2312A)
+ USB2.0 receiver.
+
config DVB_USB_RTL28XXU
tristate "Realtek RTL28xxU DVB USB support"
depends on DVB_USB_V2 && I2C_MUX
diff --git a/drivers/media/usb/dvb-usb-v2/Makefile b/drivers/media/usb/dvb-usb-v2/Makefile
index 58c0140e19de..0c33dd50f2f7 100644
--- a/drivers/media/usb/dvb-usb-v2/Makefile
+++ b/drivers/media/usb/dvb-usb-v2/Makefile
@@ -35,6 +35,9 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += dvb-usb-mxl111sf.o
obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-demod.o
obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o
+dvb-usb-nerc-objs := nerc.o
+obj-$(CONFIG_DVB_USB_NERC) += dvb-usb-nerc.o
+
dvb-usb-rtl28xxu-objs := rtl28xxu.o
obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o
diff --git a/drivers/media/usb/dvb-usb-v2/nerc.c b/drivers/media/usb/dvb-usb-v2/nerc.c
new file mode 100644
index 000000000000..57d287b53a46
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/nerc.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * China NERC (National Engineering Research Center of Digital Television)
+ * DTMB (Cypress CY7C68013A + HDIC HD2312A) USB2.0 receiver.
+ *
+ * Copyright (c) 2026 David Yang
+ */
+
+#include "dvb_usb.h"
+
+#include "nerc.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static const char *nerc_variant_name(enum nerc_variant variant)
+{
+ switch (variant) {
+ case NERC_VARIANT_LETV:
+ return "Letv";
+ case NERC_VARIANT_AIWA:
+ return "Aiwa";
+ case NERC_VARIANT_CVB:
+ return "CVB";
+ default:
+ return "unknown";
+ }
+}
+
+static int
+nerc_control_msg(struct dvb_usb_device *d, u8 request, bool read,
+ void *data, u16 size)
+{
+ struct nerc_priv *priv = d_to_priv(d);
+ unsigned int pipe;
+ u8 requesttype;
+ int res;
+
+ if (WARN_ON(size > sizeof(priv->buf)))
+ return -EINVAL;
+
+ lockdep_assert_held_once(&d->usb_mutex);
+
+ if (read) {
+ requesttype = USB_TYPE_VENDOR | USB_DIR_IN;
+ pipe = usb_rcvctrlpipe(d->udev, 0);
+ } else {
+ requesttype = USB_TYPE_VENDOR | USB_DIR_OUT;
+ pipe = usb_sndctrlpipe(d->udev, 0);
+ if (size)
+ memcpy(priv->buf, data, size);
+ }
+
+ /* value seems to be ignored, but just play safe */
+ res = usb_control_msg(d->udev, pipe, request, requesttype, 0xfe,
+ 0, priv->buf, size, NERC_USB_TIMEOUT);
+ dvb_usb_dbg_usb_control_msg(d->udev, request, requesttype, 0xfe,
+ 0, priv->buf, size);
+
+ if (res >= 0 && res != size)
+ return -EIO;
+ if (size && read)
+ memcpy(data, priv->buf, res);
+ return 0;
+}
+
+static int nerc_read_status(struct dvb_frontend *fe, enum fe_status *status)
+{
+ struct dvb_usb_device *d = fe_to_d(fe);
+ bool has_signal;
+ bool has_lock;
+ int res;
+
+ mutex_lock(&d->usb_mutex);
+ res = nerc_control_msg(d, NERC_HAS_SIGNAL, true, &has_signal,
+ sizeof(has_signal));
+ if (!res && has_signal) {
+ res = nerc_control_msg(d, NERC_WAIT_LOCK, true, &has_lock,
+ sizeof(has_lock));
+ if (res == -ETIMEDOUT) {
+ res = 0;
+ has_lock = false;
+ }
+ }
+ mutex_unlock(&d->usb_mutex);
+ if (res)
+ return res;
+
+ if (has_lock)
+ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI |
+ FE_HAS_SYNC | FE_HAS_LOCK;
+ else if (has_signal)
+ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+ else
+ *status = 0;
+ return 0;
+}
+
+static int
+nerc_get_frontend(struct dvb_frontend *fe, struct dtv_frontend_properties *c)
+{
+ struct dvb_usb_device *d = fe_to_d(fe);
+ unsigned char frontend[6];
+ unsigned char snr[2];
+ unsigned char strength[4];
+ int res;
+
+ mutex_lock(&d->usb_mutex);
+
+ res = nerc_control_msg(d, NERC_FRONTEND, true,
+ frontend, sizeof(frontend));
+ if (res)
+ goto end;
+
+ res = nerc_control_msg(d, NERC_SNR, true, snr, sizeof(snr));
+ if (res)
+ goto end;
+
+ res = nerc_control_msg(d, NERC_STRENGTH, true,
+ strength, sizeof(strength));
+ if (res)
+ goto end;
+
+end:
+ mutex_unlock(&d->usb_mutex);
+ if (res)
+ return res;
+
+ switch (frontend[0]) {
+ case 0:
+ c->transmission_mode = TRANSMISSION_MODE_C1;
+ break;
+ case 1:
+ c->transmission_mode = TRANSMISSION_MODE_C3780;
+ break;
+ default:
+ c->transmission_mode = TRANSMISSION_MODE_AUTO;
+ }
+
+ switch (frontend[1]) {
+ case 0:
+ c->guard_interval = GUARD_INTERVAL_PN945;
+ break;
+ case 1:
+ c->guard_interval = GUARD_INTERVAL_PN595;
+ break;
+ case 2:
+ c->guard_interval = GUARD_INTERVAL_PN420;
+ break;
+ default:
+ c->guard_interval = GUARD_INTERVAL_AUTO;
+ }
+
+ switch (frontend[2]) {
+ case 0:
+ c->fec_inner = FEC_2_5;
+ break;
+ case 1:
+ c->fec_inner = FEC_3_5;
+ break;
+ case 2:
+ c->fec_inner = FEC_4_5;
+ break;
+ default:
+ c->fec_inner = FEC_AUTO;
+ }
+
+ switch (frontend[3]) {
+ case 0:
+ c->interleaving = INTERLEAVING_720;
+ break;
+ case 1:
+ c->interleaving = INTERLEAVING_240;
+ break;
+ default:
+ c->interleaving = INTERLEAVING_AUTO;
+ }
+
+ switch (frontend[4]) {
+ case 0:
+ c->modulation = QAM_4_NR;
+ break;
+ case 1:
+ c->modulation = QPSK;
+ break;
+ case 2:
+ c->modulation = QAM_16;
+ break;
+ case 3:
+ c->modulation = QAM_32;
+ break;
+ case 4:
+ c->modulation = QAM_64;
+ break;
+ default:
+ c->modulation = QAM_AUTO;
+ }
+
+ switch (frontend[5]) {
+ case 0:
+ c->inversion = INVERSION_ON;
+ break;
+ case 1:
+ c->inversion = INVERSION_OFF;
+ break;
+ default:
+ c->inversion = INVERSION_AUTO;
+ }
+
+ c->strength.stat[0].uvalue = strength[3] * 0xffff / 100;
+ c->cnr.stat[0].svalue = (snr[0] * 100 + snr[1]) * 10;
+
+ return 0;
+}
+
+static int nerc_set_frontend(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ __be32 freq = cpu_to_be32(c->frequency);
+ struct dvb_usb_device *d = fe_to_d(fe);
+ int res;
+
+ mutex_lock(&d->usb_mutex);
+ res = nerc_control_msg(d, NERC_FREQ_SET, false, &freq, sizeof(freq));
+ mutex_unlock(&d->usb_mutex);
+
+ return res;
+}
+
+static const struct dvb_frontend_ops nerc_ops = {
+ .delsys = { SYS_DTMB },
+ .info = {
+ .name = "HDIC HD2312A (in NERC DtmbUSB)",
+ .frequency_min_hz = 52 * MHz,
+ .frequency_max_hz = 866 * MHz,
+ .frequency_stepsize_hz = 10 * kHz,
+ .caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO |
+ FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO |
+ FE_CAN_BANDWIDTH_AUTO | FE_CAN_GUARD_INTERVAL_AUTO
+ },
+ .read_status = nerc_read_status,
+ .get_frontend = nerc_get_frontend,
+ .set_frontend = nerc_set_frontend,
+};
+
+static int nerc_streaming_ctrl(struct dvb_frontend *fe, int on)
+{
+ struct dvb_usb_device *d = fe_to_d(fe);
+ int res;
+
+ mutex_lock(&d->usb_mutex);
+ res = nerc_control_msg(d, on ? NERC_STREAM_START : NERC_STREAM_STOP,
+ false, NULL, 0);
+ mutex_unlock(&d->usb_mutex);
+
+ return res;
+}
+
+static int nerc_frontend_attach(struct dvb_usb_adapter *adap)
+{
+ struct nerc_priv *priv = adap_to_priv(adap);
+ struct dtv_frontend_properties *c = &priv->fe.dtv_property_cache;
+
+ memcpy(&priv->fe.ops, &nerc_ops, sizeof(struct dvb_frontend_ops));
+
+ c->strength.len = 1;
+ c->strength.stat[0].scale = FE_SCALE_RELATIVE;
+ c->cnr.len = 1;
+ c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+
+ adap->fe[0] = &priv->fe;
+ return 0;
+}
+
+static int nerc_power_ctrl(struct dvb_usb_device *d, int on)
+{
+ int res;
+
+ mutex_lock(&d->usb_mutex);
+ res = nerc_control_msg(d, on ? NERC_POWER_ON : NERC_POWER_OFF,
+ false, NULL, 0);
+ mutex_unlock(&d->usb_mutex);
+
+ return res;
+}
+
+static int nerc_probe(struct dvb_usb_device *d)
+{
+ struct nerc_priv *priv = d_to_priv(d);
+ unsigned char buf[4];
+ int res;
+
+ mutex_lock(&d->usb_mutex);
+ res = nerc_control_msg(d, NERC_VERSION, true, buf, sizeof(buf));
+ mutex_unlock(&d->usb_mutex);
+ if (res)
+ return res;
+
+ if (buf[1] == 8 && buf[2] == 32 && buf[3] == 68) {
+ if (buf[0] == 3)
+ priv->variant = NERC_VARIANT_LETV;
+ else if (buf[0] == 5)
+ priv->variant = NERC_VARIANT_AIWA;
+ else if (buf[0] == 6)
+ priv->variant = NERC_VARIANT_CVB;
+ }
+
+ if (priv->variant == NERC_VARIANT_UNKNOWN) {
+ dev_info(&d->udev->dev, "Unknown NERC DtmbUSB v%u.%u.%u%u",
+ buf[0], buf[1], buf[2], buf[3]);
+ return -ENODEV;
+ }
+
+ /* yes, missing the last dot */
+ dev_info(&d->udev->dev, "NERC DtmbUSB v%u.%u.%u%u (%s)",
+ buf[0], buf[1], buf[2], buf[3],
+ nerc_variant_name(priv->variant));
+ return 0;
+}
+
+static const struct dvb_usb_device_properties nerc_props = {
+ .driver_name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ .adapter_nr = adapter_nr,
+ .size_of_priv = sizeof(struct nerc_priv),
+
+ .streaming_ctrl = nerc_streaming_ctrl,
+ .frontend_attach = nerc_frontend_attach,
+ .power_ctrl = nerc_power_ctrl,
+ .probe = nerc_probe,
+
+ .num_adapters = 1,
+ .adapter = {
+ {
+ .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
+ },
+ },
+};
+
+static const struct usb_device_id nerc_id_table[] = {
+ { DVB_USB_DEVICE(USB_VID_CYPRESS, USB_PID_NERC_DTMBUSB,
+ &nerc_props, "NERC DtmbUSB", NULL) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, nerc_id_table);
+
+static struct usb_driver nerc_usb_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = nerc_id_table,
+ .probe = dvb_usbv2_probe,
+ .disconnect = dvb_usbv2_disconnect,
+ .suspend = dvb_usbv2_suspend,
+ .resume = dvb_usbv2_resume,
+ .reset_resume = dvb_usbv2_reset_resume,
+ .no_dynamic_id = 1,
+ .soft_unbind = 1,
+};
+
+module_usb_driver(nerc_usb_driver);
+
+MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>");
+MODULE_DESCRIPTION("Driver for NERC DtmbUSB");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/nerc.h b/drivers/media/usb/dvb-usb-v2/nerc.h
new file mode 100644
index 000000000000..c7bde3c7a6d7
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/nerc.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _DVB_USB_NERC_H_
+#define _DVB_USB_NERC_H_
+
+#include "dvb_usb.h"
+
+#define NERC_STREAM_START 0xab
+#define NERC_STREAM_STOP 0xac
+#define NERC_POWER_ON 0xad
+#define NERC_POWER_OFF 0xae
+#define NERC_FRONTEND 0xe7
+#define NERC_SNR 0xe8
+#define NERC_QUALITY 0xe9
+#define NERC_WAIT_LOCK 0xea
+#define NERC_STRENGTH 0xeb
+#define NERC_HAS_SIGNAL 0xec /* 0.7s delay required */
+#define NERC_VERSION 0xed
+#define NERC_FREQ_SET 0xfc
+
+#define NERC_USB_TIMEOUT 1000
+
+enum nerc_variant {
+ NERC_VARIANT_UNKNOWN,
+ NERC_VARIANT_LETV,
+ NERC_VARIANT_AIWA,
+ NERC_VARIANT_CVB,
+};
+
+struct nerc_priv {
+ unsigned char variant;
+ u8 buf[128];
+ struct dvb_frontend fe;
+};
+
+#endif
diff --git a/include/media/dvb-usb-ids.h b/include/media/dvb-usb-ids.h
index 1b7d10f3d4aa..7bc4e3c395f5 100644
--- a/include/media/dvb-usb-ids.h
+++ b/include/media/dvb-usb-ids.h
@@ -308,6 +308,7 @@
#define USB_PID_MYGICA_T230C2 0xc68a
#define USB_PID_MYGICA_T230C2_LITE 0xc69a
#define USB_PID_MYGICA_T230C_LITE 0xc699
+#define USB_PID_NERC_DTMBUSB 0x1004
#define USB_PID_NOXON_DAB_STICK 0x00b3
#define USB_PID_NOXON_DAB_STICK_REV2 0x00e0
#define USB_PID_NOXON_DAB_STICK_REV3 0x00b4
--
2.53.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-07-10 6:32 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 6:31 [PATCH] media: dvb-usb-v2: nerc: add support for NERC DtmbUSB DTMB receiver David Yang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox