Linux Media Controller development
 help / color / mirror / Atom feed
From: Ext745 <stealth3m@yahoo.com>
To: linux-media@vger.kernel.org
Cc: isely@pobox.com, mkrufky@linuxtv.org, mchehab@kernel.org,
	hverkuil@kernel.org, Ext745 <stealth3m@yahoo.com>
Subject: [PATCH v2 4/4] media: pvrusb2: enable FM radio on the HVR-1950
Date: Thu, 22 Oct 2026 19:25:48 -0400	[thread overview]
Message-ID: <20261022232548.526232-5-stealth3m@yahoo.com> (raw)
In-Reply-To: <20261022232548.526232-1-stealth3m@yahoo.com>

The HVR-1950 (75xxx) has an FM tuner (the retail box lists FM stereo
radio, with a separate FM antenna input), but pvrusb2 never enabled it:
the 750xx and 751xx device descriptions lack flag_has_fmradio, so there
is no radio device. Their routing scheme can't simply gain it either:
its radio entry selects the line input meant for the external FM module
of the older PVR-USB2 models. On the HVR-1950 FM comes from the analog
tuner, like TV sound.

 - Set flag_has_fmradio for 750xx/751xx and give them a routing scheme
   whose radio input is the tuner (COMPOSITE7 / AUDIO8, as for TV).
 - Pass cx25840 platform data with radio_sif_5_5mhz (the TDA8295's FM
   output) through v4l2_i2c_new_subdev_board.
 - In radio mode ask the cx25840 again at the end of the tuner status
   poll, so that tuner-core's default stereo report doesn't override
   what the audio decoder can do.

The band is tuner-core's radio range, 65 to 108 MHz.

Tested on an HVR-1950 (model 75111): /dev/radio0 tunes the FM band
accurately, the signal meter works, stereo with the optional cx25840
firmware and mono without, and switching between radio, analog TV,
composite and ATSC (with tvheadend and v4l2-ctl/dvbv5-zap) works.

Assisted-by: Claude Opus 5.5
Signed-off-by: Ext745 <stealth3m@yahoo.com>
---
v2:
- keep the cx25840 platform data in struct pvr2_hdw instead of on the
  stack (the I2C core keeps the pointer)

 .../media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c   | 26 ++++++++++++++++++
 drivers/media/usb/pvrusb2/pvrusb2-devattr.c   |  8 ++++--
 drivers/media/usb/pvrusb2/pvrusb2-devattr.h   |  4 +++
 .../media/usb/pvrusb2/pvrusb2-hdw-internal.h  |  2 ++
 drivers/media/usb/pvrusb2/pvrusb2-hdw.c       | 27 ++++++++++++++++++-
 5 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c
index e4b31ae02..915f97792 100644
--- a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c
@@ -125,11 +125,37 @@ static const struct routing_scheme routing_def160xxx = {
 	.cnt = ARRAY_SIZE(routing_scheme160xxx),
 };
 
+/* HVR-1950: FM radio comes from the analog tuner, like television audio */
+static const struct routing_scheme_item routing_scheme75xxx[] = {
+	[PVR2_CVAL_INPUT_TV] = {
+		.vid = CX25840_COMPOSITE7,
+		.aud = CX25840_AUDIO8,
+	},
+	[PVR2_CVAL_INPUT_RADIO] = {
+		.vid = CX25840_COMPOSITE7,
+		.aud = CX25840_AUDIO8,
+	},
+	[PVR2_CVAL_INPUT_COMPOSITE] = {
+		.vid = CX25840_COMPOSITE3,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_SVIDEO] = {
+		.vid = CX25840_SVIDEO1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+};
+
+static const struct routing_scheme routing_def75xxx = {
+	.def = routing_scheme75xxx,
+	.cnt = ARRAY_SIZE(routing_scheme75xxx),
+};
+
 static const struct routing_scheme *routing_schemes[] = {
 	[PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0,
 	[PVR2_ROUTING_SCHEME_GOTVIEW] = &routing_defgv,
 	[PVR2_ROUTING_SCHEME_AV400] = &routing_defav400,
 	[PVR2_ROUTING_SCHEME_HAUP160XXX] = &routing_def160xxx,
+	[PVR2_ROUTING_SCHEME_HAUP75XXX] = &routing_def75xxx,
 };
 
 void pvr2_cx25840_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.c b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c
index d1b984ec7..2e74b97ef 100644
--- a/drivers/media/usb/pvrusb2/pvrusb2-devattr.c
+++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c
@@ -482,10 +482,12 @@ static const struct pvr2_device_desc pvr2_device_750xx = {
 		.flag_has_cx25840 = !0,
 		.flag_has_hauppauge_rom = !0,
 		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
 		.flag_has_composite = !0,
 		.flag_has_svideo = !0,
 		.flag_fx2_16kb = !0,
-		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.flag_radio_sif_5_5mhz = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUP75XXX,
 		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
 		.default_std_mask = V4L2_STD_NTSC_M,
 		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
@@ -505,10 +507,12 @@ static const struct pvr2_device_desc pvr2_device_751xx = {
 		.flag_has_cx25840 = !0,
 		.flag_has_hauppauge_rom = !0,
 		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
 		.flag_has_composite = !0,
 		.flag_has_svideo = !0,
 		.flag_fx2_16kb = !0,
-		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.flag_radio_sif_5_5mhz = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUP75XXX,
 		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
 		.default_std_mask = V4L2_STD_NTSC_M,
 		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.h b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h
index 3c88f05d8..2a6483956 100644
--- a/drivers/media/usb/pvrusb2/pvrusb2-devattr.h
+++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h
@@ -57,6 +57,7 @@ struct pvr2_string_table {
 #define PVR2_ROUTING_SCHEME_ONAIR 2
 #define PVR2_ROUTING_SCHEME_AV400 3
 #define PVR2_ROUTING_SCHEME_HAUP160XXX 4
+#define PVR2_ROUTING_SCHEME_HAUP75XXX 5
 
 #define PVR2_DIGITAL_SCHEME_NONE 0
 #define PVR2_DIGITAL_SCHEME_HAUPPAUGE 1
@@ -165,6 +166,9 @@ struct pvr2_device_desc {
 	unsigned int flag_has_svideo:1;        /* Has s-video input */
 	unsigned int flag_fx2_16kb:1;          /* 16KB FX2 firmware OK here */
 
+	/* FM radio reaches the cx2584x as a 5.5 MHz sound IF (TDA8295) */
+	unsigned int flag_radio_sif_5_5mhz:1;
+
 	/* If this driver is considered experimental, i.e. not all aspects
 	   are working correctly and/or it is untested, mark that fact
 	   with this flag. */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h
index 7c998ca61..8230dfb9a 100644
--- a/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h
@@ -26,6 +26,7 @@
 #include "pvrusb2-io.h"
 #include <media/v4l2-device.h>
 #include <media/drv-intf/cx2341x.h>
+#include <media/drv-intf/cx25840.h>
 #include <media/i2c/ir-kbd-i2c.h>
 #include "pvrusb2-devattr.h"
 
@@ -189,6 +190,7 @@ struct pvr2_hdw {
 	/* IR related */
 	unsigned int ir_scheme_active; /* IR scheme as seen from the outside */
 	struct IR_i2c_init_data ir_init_data; /* params passed to IR modules */
+	struct cx25840_platform_data cx25840_pdata; /* passed to cx25840 */
 
 	/* Frequency table */
 	unsigned int freqTable[FREQTABLE_SIZE];
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c
index c749ef7e4..32e7b4b76 100644
--- a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c
@@ -2015,7 +2015,22 @@ static int pvr2_hdw_load_subdev(struct pvr2_hdw *hdw,
 		return -EINVAL;
 	}
 
-	if (i2ccnt == 1) {
+	if (i2ccnt == 1 && mid == PVR2_CLIENT_ID_CX25840) {
+		/* in hdw: the I2C core keeps the platform_data pointer */
+		struct i2c_board_info info = {
+			.addr = i2caddr[0],
+			.platform_data = &hdw->cx25840_pdata,
+		};
+
+		hdw->cx25840_pdata.radio_sif_5_5mhz =
+			hdw->hdw_desc->flag_radio_sif_5_5mhz;
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Module ID %u: Setting up with specified i2c address 0x%x",
+			   mid, i2caddr[0]);
+		strscpy(info.type, fname, sizeof(info.type));
+		sd = v4l2_i2c_new_subdev_board(&hdw->v4l2_dev, &hdw->i2c_adap,
+					       &info, NULL);
+	} else if (i2ccnt == 1) {
 		pvr2_trace(PVR2_TRACE_INIT,
 			   "Module ID %u: Setting up with specified i2c address 0x%x",
 			   mid, i2caddr[0]);
@@ -5057,6 +5072,16 @@ void pvr2_hdw_status_poll(struct pvr2_hdw *hdw)
 	   now.  (Of course, no sub-drivers seem to implement it either.
 	   But now it's a chicken and egg problem...) */
 	v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, g_tuner, vtp);
+	if (vtp->type == V4L2_TUNER_RADIO &&
+	    hdw->hdw_desc->flag_radio_sif_5_5mhz) {
+		/*
+		 * Whether this FM path is stereo depends on the cx2584x
+		 * audio firmware: ask it again last, so tuner-core's
+		 * default doesn't decide.
+		 */
+		v4l2_device_call_all(&hdw->v4l2_dev, PVR2_CLIENT_ID_CX25840,
+				     tuner, g_tuner, vtp);
+	}
 	pvr2_trace(PVR2_TRACE_CHIPS, "subdev status poll type=%u strength=%u audio=0x%x cap=0x%x low=%u hi=%u",
 		   vtp->type,
 		   vtp->signal, vtp->rxsubchans, vtp->capability,
-- 
2.53.0


      parent reply	other threads:[~2026-10-22 23:26 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <20261022232548.526232-1-stealth3m.ref@yahoo.com>
2026-10-22 23:25 ` [PATCH v2 0/4] media: FM radio for the Hauppauge WinTV-HVR-1950 Ext745
2026-10-22 23:25   ` [PATCH v2 1/4] media: cx25840: demodulate FM radio from a 5.5 MHz sound IF Ext745
2026-10-22 23:25   ` [PATCH v2 2/4] media: cx25840: FM stereo with an optional HVR-1950 audio firmware Ext745
2026-10-22 23:25   ` [PATCH v2 3/4] media: tda8290: report FM signal strength on the TDA8295 Ext745
2026-10-22 23:25   ` Ext745 [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261022232548.526232-5-stealth3m@yahoo.com \
    --to=stealth3m@yahoo.com \
    --cc=hverkuil@kernel.org \
    --cc=isely@pobox.com \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=mkrufky@linuxtv.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox