* [PATCH v2 1/4] media: cx25840: demodulate FM radio from a 5.5 MHz sound IF
2026-10-22 23:25 ` [PATCH v2 0/4] media: FM radio for the Hauppauge WinTV-HVR-1950 Ext745
@ 2026-10-22 23:25 ` Ext745
2026-10-22 23:25 ` [PATCH v2 2/4] media: cx25840: FM stereo with an optional HVR-1950 audio firmware Ext745
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Ext745 @ 2026-10-22 23:25 UTC (permalink / raw)
To: linux-media; +Cc: isely, mkrufky, mchehab, hverkuil, Ext745
Some boards feed FM radio to the cx2584x the same way as TV sound, as a
sound IF from the analog demodulator. On the Hauppauge HVR-1950 the
TDA8295 outputs FM radio as a 5.5 MHz SSIF on the cx25843's tuner audio
input. The radio setting cx25840 uses (0x808 = 0xf9, FM radio with
automatic detection) never locks onto that carrier with the standard
audio firmware: 0x805 keeps reading 0xff (no standard detected) and
only noise comes out.
Add a radio_sif_5_5mhz platform data flag. For such boards force the
5.5 MHz FM (A2-BG) standard, mono, in radio mode. That demodulates
broadcast FM cleanly, with 50 us de-emphasis (the hiss spectrum of an
empty channel follows the 50 us curve within 0.5 dB). The 4.5 MHz A2-M
standard would give 75 us but clips at broadcast FM deviation.
This path is mono only, so say so in g_tuner for radio: tuner-core
reports stereo capability for every radio.
Tested on an HVR-1950 (model 75111) with the pvrusb2 patch later in this
series: local stations across the band are clean, and switching between
radio, analog TV, composite and ATSC is unchanged.
Assisted-by: Claude Opus 5.5
Signed-off-by: Ext745 <stealth3m@yahoo.com>
---
v2: unchanged
drivers/media/i2c/cx25840/cx25840-core.c | 23 +++++++++++++++++++++--
drivers/media/i2c/cx25840/cx25840-core.h | 3 +++
include/media/drv-intf/cx25840.h | 6 +++++-
3 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c
index e90d71fe9..a7b4cae01 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.c
+++ b/drivers/media/i2c/cx25840/cx25840-core.c
@@ -1302,7 +1302,15 @@ static void input_change(struct i2c_client *client)
cx25840_and_or(client, 0x810, ~0x01, 1);
- if (state->radio) {
+ if (state->radio && state->radio_sif_5_5mhz) {
+ /*
+ * Forced 5.5 MHz FM (A2-BG standard), MONO1, which applies
+ * 50 us de-emphasis. The 4.5 MHz A2-M path (75 us) clips
+ * broadcast FM deviation.
+ */
+ cx25840_write(client, 0x808, 0x40);
+ cx25840_write(client, 0x80b, 0x00);
+ } else if (state->radio) {
cx25840_write(client, 0x808, 0xf9);
cx25840_write(client, 0x80b, 0x00);
} else if (std & V4L2_STD_525_60) {
@@ -2565,8 +2573,18 @@ static int cx25840_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
u8 mode;
int val = 0;
- if (state->radio)
+ if (state->radio) {
+ if (!state->radio_sif_5_5mhz)
+ return 0;
+ /*
+ * The bridge calls this after the tuner, so that the audio
+ * decoder has the last word: this FM path is mono only.
+ */
+ vt->capability &= ~V4L2_TUNER_CAP_STEREO;
+ vt->rxsubchans = V4L2_TUNER_SUB_MONO;
+ vt->audmode = V4L2_TUNER_MODE_MONO;
return 0;
+ }
vt->signal = vpres ? 0xffff : 0x0;
if (is_cx2583x(state))
@@ -3972,6 +3990,7 @@ static int cx25840_probe(struct i2c_client *client)
struct cx25840_platform_data *pdata = client->dev.platform_data;
state->pvr150_workaround = pdata->pvr150_workaround;
+ state->radio_sif_5_5mhz = pdata->radio_sif_5_5mhz;
}
cx25840_ir_probe(sd);
diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h
index 8b89e9068..94f17bd76 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.h
+++ b/drivers/media/i2c/cx25840/cx25840-core.h
@@ -43,6 +43,8 @@ enum cx25840_media_pads {
* @mute: audio mute V4L2 control (non-cx2583x devices only)
* @pvr150_workaround: whether we enable workaround for Hauppauge PVR150
* hardware bug (audio dropping out)
+ * @radio_sif_5_5mhz: FM radio arrives as a 5.5 MHz sound IF (e.g. from a
+ * TDA8295): demodulate it as 5.5 MHz FM mono
* @generic_mode: whether we disable ivtv-specific hacks
* this mode gets turned on when the bridge driver calls
* cx25840 subdevice init core op
@@ -78,6 +80,7 @@ struct cx25840_state {
struct v4l2_ctrl *mute;
};
int pvr150_workaround;
+ int radio_sif_5_5mhz;
bool generic_mode;
int radio;
v4l2_std_id std;
diff --git a/include/media/drv-intf/cx25840.h b/include/media/drv-intf/cx25840.h
index 8b455d9dd..c8ff9e65d 100644
--- a/include/media/drv-intf/cx25840.h
+++ b/include/media/drv-intf/cx25840.h
@@ -253,10 +253,14 @@ enum cx23885_io_pad {
* is to select the audio standard explicitly. Many thanks to Hauppauge for
* providing this information.
*
- * This platform data only needs to be supplied by the ivtv driver.
+ * radio_sif_5_5mhz is for boards whose analog tuner delivers FM radio to the
+ * cx2584x as a 5.5 MHz sound IF, as the TDA8295 does in FM radio mode (e.g.
+ * the Hauppauge HVR-1950). The audio firmware's FM radio mode does not find
+ * that carrier, so radio is demodulated as forced 5.5 MHz FM mono instead.
*/
struct cx25840_platform_data {
int pvr150_workaround;
+ int radio_sif_5_5mhz;
};
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 2/4] media: cx25840: FM stereo with an optional HVR-1950 audio firmware
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 ` 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 ` [PATCH v2 4/4] media: pvrusb2: enable FM radio on the HVR-1950 Ext745
3 siblings, 0 replies; 5+ messages in thread
From: Ext745 @ 2026-10-22 23:25 UTC (permalink / raw)
To: linux-media; +Cc: isely, mkrufky, mchehab, hverkuil, Ext745
The HVR-1950's Windows driver loads its own cx25843 audio firmware
(Hcw73MlC.rom) and puts the audio core in radio mode with 0x808 = 0xfa:
automatic standard detection, audio system 0xa, which the CX25840/1/2/3
data sheet doesn't document. With that firmware, 0xfa finds FM radio on
the 5.5 MHz sound IF (0x805 = 0x0e), detects the stereo pilot
(0x804 = 0x01) and decodes stereo; the firmware sets the dematrix
(0x8ce) to FM stereo itself. The linux-firmware image (v4l-cx25840.fw)
detects nothing with either 0xfa or 0xf9, and 0xf9 with the HVR-1950
firmware detects FM but stays mono.
That image can't go into linux-firmware, so make it optional: on
radio_sif_5_5mhz boards try v4l-cx25840-fmstereo.fw first, without a
warning when it is missing, and use v4l-cx25840.fw and the mono path
otherwise. With it:
- radio sets 0x808 = 0xfa. The audio soft reset is not pulsed again
afterwards: a second restart of the audio core left the stereo
decoder hissy in 23 of 24 tries, and in none without it.
- g_tuner reports stereo capability and, from 0x804, whether a stereo
pilot is received.
- s_tuner maps the audio mode to the preferred mode in 0x809: mono
gives mono (useful on weak stations), anything else stereo. Both
take effect immediately. Radio keeps its own mono/stereo setting,
so TV's audio mode is restored when leaving radio (both share
0x809).
De-emphasis is fixed at 75 us in this mode (measured within 0.5 dB; the
0x809 DE_EMPHASIS_TIME bit has no effect), as used in the Americas.
The preferred-mode switch moves out of s_tuner into a helper so that
input_change can use it too.
Tested on an HVR-1950: stereo on every start (20 of 20) on two local
stations, mono/stereo switching, BTSC TV after radio, and switching
between radio, analog TV, composite and ATSC. Without the file radio
stays mono as before.
Assisted-by: Claude Opus 5.5
Signed-off-by: Ext745 <stealth3m@yahoo.com>
---
v2:
- keep a separate radio_audmode so S_TUNER on the radio node doesn't
overwrite the TV audmode; radio modes other than mono map to stereo
- set fm_stereo only once check_fw_load() succeeds
drivers/media/i2c/cx25840/cx25840-core.c | 145 +++++++++++++------
drivers/media/i2c/cx25840/cx25840-core.h | 8 +-
drivers/media/i2c/cx25840/cx25840-firmware.c | 30 +++-
include/media/drv-intf/cx25840.h | 6 +-
4 files changed, 133 insertions(+), 56 deletions(-)
diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c
index a7b4cae01..5ceef7428 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.c
+++ b/drivers/media/i2c/cx25840/cx25840-core.c
@@ -1280,6 +1280,56 @@ void cx25840_std_setup(struct i2c_client *client)
/* ----------------------------------------------------------------------- */
+/* Program the audio decoder's preferred mode (0x809 [3:0]) for audmode */
+static int set_pref_mode(struct i2c_client *client, u32 audmode, bool radio)
+{
+ if (radio) {
+ /* FM radio is mono or stereo; anything but mono means stereo */
+ cx25840_and_or(client, 0x809, ~0xf,
+ audmode == V4L2_TUNER_MODE_MONO ? 0x00 : 0x04);
+ return 0;
+ }
+
+ switch (audmode) {
+ case V4L2_TUNER_MODE_MONO:
+ /*
+ * mono -> mono
+ * stereo -> mono
+ * bilingual -> lang1
+ */
+ cx25840_and_or(client, 0x809, ~0xf, 0x00);
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ case V4L2_TUNER_MODE_LANG1:
+ /*
+ * mono -> mono
+ * stereo -> stereo
+ * bilingual -> lang1
+ */
+ cx25840_and_or(client, 0x809, ~0xf, 0x04);
+ break;
+ case V4L2_TUNER_MODE_LANG1_LANG2:
+ /*
+ * mono -> mono
+ * stereo -> stereo
+ * bilingual -> lang1/lang2
+ */
+ cx25840_and_or(client, 0x809, ~0xf, 0x07);
+ break;
+ case V4L2_TUNER_MODE_LANG2:
+ /*
+ * mono -> mono
+ * stereo -> stereo
+ * bilingual -> lang2
+ */
+ cx25840_and_or(client, 0x809, ~0xf, 0x01);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
static void input_change(struct i2c_client *client)
{
struct cx25840_state *state = to_state(i2c_get_clientdata(client));
@@ -1302,11 +1352,23 @@ static void input_change(struct i2c_client *client)
cx25840_and_or(client, 0x810, ~0x01, 1);
- if (state->radio && state->radio_sif_5_5mhz) {
+ if (state->radio && state->radio_sif_5_5mhz && state->fm_stereo) {
+ /*
+ * The FM stereo firmware's automatic FM radio system (0xA)
+ * locks onto the 5.5 MHz sound IF and decodes the stereo
+ * pilot, with fixed 75 us de-emphasis. 0x809 picks mono or
+ * stereo. Don't pulse the soft reset again after this: a
+ * second restart of the audio core often leaves the stereo
+ * decoder hissy.
+ */
+ cx25840_write(client, 0x808, 0xfa);
+ cx25840_write(client, 0x80b, 0x00);
+ set_pref_mode(client, state->radio_audmode, true);
+ } else if (state->radio && state->radio_sif_5_5mhz) {
/*
- * Forced 5.5 MHz FM (A2-BG standard), MONO1, which applies
- * 50 us de-emphasis. The 4.5 MHz A2-M path (75 us) clips
- * broadcast FM deviation.
+ * Standard firmware: forced 5.5 MHz FM (A2-BG standard),
+ * MONO1, which applies 50 us de-emphasis. The 4.5 MHz A2-M
+ * path (75 us) clips broadcast FM deviation.
*/
cx25840_write(client, 0x808, 0x40);
cx25840_write(client, 0x80b, 0x00);
@@ -1365,6 +1427,10 @@ static void input_change(struct i2c_client *client)
}
}
+ /* Radio mode may have left 0x809 at its FM setting: restore TV's */
+ if (!state->radio && state->radio_sif_5_5mhz)
+ set_pref_mode(client, state->audmode, false);
+
cx25840_and_or(client, 0x810, ~0x01, 0);
}
@@ -2578,11 +2644,21 @@ static int cx25840_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
return 0;
/*
* The bridge calls this after the tuner, so that the audio
- * decoder has the last word: this FM path is mono only.
+ * decoder has the last word on FM stereo: only the FM stereo
+ * firmware decodes it.
*/
- vt->capability &= ~V4L2_TUNER_CAP_STEREO;
- vt->rxsubchans = V4L2_TUNER_SUB_MONO;
- vt->audmode = V4L2_TUNER_MODE_MONO;
+ if (!state->fm_stereo) {
+ vt->capability &= ~V4L2_TUNER_CAP_STEREO;
+ vt->rxsubchans = V4L2_TUNER_SUB_MONO;
+ vt->audmode = V4L2_TUNER_MODE_MONO;
+ return 0;
+ }
+ vt->capability |= V4L2_TUNER_CAP_STEREO;
+ mode = cx25840_read(client, 0x804);
+ vt->rxsubchans = (mode & 0xf) == 1 ?
+ V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO :
+ V4L2_TUNER_SUB_MONO;
+ vt->audmode = state->radio_audmode;
return 0;
}
@@ -2616,47 +2692,23 @@ static int cx25840_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt)
{
struct cx25840_state *state = to_state(sd);
struct i2c_client *client = v4l2_get_subdevdata(sd);
+ int ret;
- if (state->radio || is_cx2583x(state))
+ if (is_cx2583x(state))
+ return 0;
+ if (state->radio) {
+ if (!(state->radio_sif_5_5mhz && state->fm_stereo))
+ return 0;
+ /* FM radio is mono or stereo; keep TV's audmode for TV */
+ state->radio_audmode = vt->audmode == V4L2_TUNER_MODE_MONO ?
+ V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
+ set_pref_mode(client, state->radio_audmode, true);
return 0;
-
- switch (vt->audmode) {
- case V4L2_TUNER_MODE_MONO:
- /*
- * mono -> mono
- * stereo -> mono
- * bilingual -> lang1
- */
- cx25840_and_or(client, 0x809, ~0xf, 0x00);
- break;
- case V4L2_TUNER_MODE_STEREO:
- case V4L2_TUNER_MODE_LANG1:
- /*
- * mono -> mono
- * stereo -> stereo
- * bilingual -> lang1
- */
- cx25840_and_or(client, 0x809, ~0xf, 0x04);
- break;
- case V4L2_TUNER_MODE_LANG1_LANG2:
- /*
- * mono -> mono
- * stereo -> stereo
- * bilingual -> lang1/lang2
- */
- cx25840_and_or(client, 0x809, ~0xf, 0x07);
- break;
- case V4L2_TUNER_MODE_LANG2:
- /*
- * mono -> mono
- * stereo -> stereo
- * bilingual -> lang2
- */
- cx25840_and_or(client, 0x809, ~0xf, 0x01);
- break;
- default:
- return -EINVAL;
}
+
+ ret = set_pref_mode(client, vt->audmode, false);
+ if (ret)
+ return ret;
state->audmode = vt->audmode;
return 0;
}
@@ -3923,6 +3975,7 @@ static int cx25840_probe(struct i2c_client *client)
state->aud_input = CX25840_AUDIO8;
state->audclk_freq = 48000;
state->audmode = V4L2_TUNER_MODE_LANG1;
+ state->radio_audmode = V4L2_TUNER_MODE_STEREO;
state->vbi_line_offset = 8;
state->id = id;
state->rev = device_id;
diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h
index 94f17bd76..92c2da5b3 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.h
+++ b/drivers/media/i2c/cx25840/cx25840-core.h
@@ -44,7 +44,9 @@ enum cx25840_media_pads {
* @pvr150_workaround: whether we enable workaround for Hauppauge PVR150
* hardware bug (audio dropping out)
* @radio_sif_5_5mhz: FM radio arrives as a 5.5 MHz sound IF (e.g. from a
- * TDA8295): demodulate it as 5.5 MHz FM mono
+ * TDA8295)
+ * @fm_stereo: the FM stereo firmware is loaded: decode that IF as
+ * FM stereo, else as 5.5 MHz FM mono
* @generic_mode: whether we disable ivtv-specific hacks
* this mode gets turned on when the bridge driver calls
* cx25840 subdevice init core op
@@ -57,6 +59,8 @@ enum cx25840_media_pads {
* @aud_input: currently set audio input
* @audclk_freq: currently set audio sample rate
* @audmode: currently set audio mode (when in non-radio mode)
+ * @radio_audmode: audio mode set in radio mode, mono or stereo (FM stereo
+ * firmware only)
* @vbi_line_offset: vbi line number offset
* @id: exact device model
* @rev: raw device id read from the chip
@@ -81,6 +85,7 @@ struct cx25840_state {
};
int pvr150_workaround;
int radio_sif_5_5mhz;
+ int fm_stereo;
bool generic_mode;
int radio;
v4l2_std_id std;
@@ -89,6 +94,7 @@ struct cx25840_state {
enum cx25840_audio_input aud_input;
u32 audclk_freq;
int audmode;
+ int radio_audmode;
int vbi_line_offset;
enum cx25840_model id;
u32 rev;
diff --git a/drivers/media/i2c/cx25840/cx25840-firmware.c b/drivers/media/i2c/cx25840/cx25840-firmware.c
index 02df45ccf..290276f37 100644
--- a/drivers/media/i2c/cx25840/cx25840-firmware.c
+++ b/drivers/media/i2c/cx25840/cx25840-firmware.c
@@ -51,6 +51,12 @@ static void end_fw_load(struct i2c_client *client)
#define CX2388x_FIRMWARE "v4l-cx23885-avcore-01.fw"
#define CX231xx_FIRMWARE "v4l-cx231xx-avcore-01.fw"
#define CX25840_FIRMWARE "v4l-cx25840.fw"
+/*
+ * Optional audio firmware for boards whose FM radio arrives as a 5.5 MHz
+ * sound IF (cx25840_platform_data.radio_sif_5_5mhz). Hauppauge's HVR-1950
+ * image (Hcw73MlC.rom) decodes FM stereo there; the standard image doesn't.
+ */
+#define CX25840_FM_STEREO_FIRMWARE "v4l-cx25840-fmstereo.fw"
static const char *get_fw_name(struct i2c_client *client)
{
@@ -65,20 +71,19 @@ static const char *get_fw_name(struct i2c_client *client)
return CX25840_FIRMWARE;
}
-static int check_fw_load(struct i2c_client *client, int size)
+static int check_fw_load(struct i2c_client *client, const char *fwname,
+ int size)
{
/* DL_ADDR_HB DL_ADDR_LB */
int s = cx25840_read(client, 0x801) << 8;
s |= cx25840_read(client, 0x800);
if (size != s) {
- v4l_err(client, "firmware %s load failed\n",
- get_fw_name(client));
+ v4l_err(client, "firmware %s load failed\n", fwname);
return -EINVAL;
}
- v4l_info(client, "loaded %s firmware (%d bytes)\n",
- get_fw_name(client), size);
+ v4l_info(client, "loaded %s firmware (%d bytes)\n", fwname, size);
return 0;
}
@@ -101,6 +106,7 @@ int cx25840_loadfw(struct i2c_client *client)
const char *fwname = get_fw_name(client);
int size, retval;
int max_buf_size = FWSEND;
+ bool fm_stereo = false;
u32 gpio_oe = 0, gpio_da = 0;
if (is_cx2388x(state)) {
@@ -113,7 +119,13 @@ int cx25840_loadfw(struct i2c_client *client)
if (is_cx231xx(state) && max_buf_size > 16)
max_buf_size = 16;
- if (request_firmware(&fw, fwname, FWDEV(client)) != 0) {
+ state->fm_stereo = 0;
+ if (state->radio_sif_5_5mhz && !firmware[0] &&
+ !firmware_request_nowarn(&fw, CX25840_FM_STEREO_FIRMWARE,
+ FWDEV(client))) {
+ fwname = CX25840_FM_STEREO_FIRMWARE;
+ fm_stereo = true;
+ } else if (request_firmware(&fw, fwname, FWDEV(client)) != 0) {
v4l_err(client, "unable to open firmware %s\n", fwname);
return -EINVAL;
}
@@ -152,10 +164,14 @@ int cx25840_loadfw(struct i2c_client *client)
cx25840_write(client, 0x164, gpio_da);
}
- return check_fw_load(client, size);
+ retval = check_fw_load(client, fwname, size);
+ if (!retval)
+ state->fm_stereo = fm_stereo;
+ return retval;
}
MODULE_FIRMWARE(CX2388x_FIRMWARE);
MODULE_FIRMWARE(CX231xx_FIRMWARE);
MODULE_FIRMWARE(CX25840_FIRMWARE);
+MODULE_FIRMWARE(CX25840_FM_STEREO_FIRMWARE);
diff --git a/include/media/drv-intf/cx25840.h b/include/media/drv-intf/cx25840.h
index c8ff9e65d..0fd8fda19 100644
--- a/include/media/drv-intf/cx25840.h
+++ b/include/media/drv-intf/cx25840.h
@@ -255,8 +255,10 @@ enum cx23885_io_pad {
*
* radio_sif_5_5mhz is for boards whose analog tuner delivers FM radio to the
* cx2584x as a 5.5 MHz sound IF, as the TDA8295 does in FM radio mode (e.g.
- * the Hauppauge HVR-1950). The audio firmware's FM radio mode does not find
- * that carrier, so radio is demodulated as forced 5.5 MHz FM mono instead.
+ * the Hauppauge HVR-1950). The standard audio firmware's FM radio mode does
+ * not find that carrier, so radio is demodulated as forced 5.5 MHz FM mono.
+ * If the optional v4l-cx25840-fmstereo.fw (Hauppauge's HVR-1950 audio
+ * firmware) is installed, it is loaded instead and radio is FM stereo.
*/
struct cx25840_platform_data {
int pvr150_workaround;
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 3/4] media: tda8290: report FM signal strength on the TDA8295
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 ` Ext745
2026-10-22 23:25 ` [PATCH v2 4/4] media: pvrusb2: enable FM radio on the HVR-1950 Ext745
3 siblings, 0 replies; 5+ messages in thread
From: Ext745 @ 2026-10-22 23:25 UTC (permalink / raw)
To: linux-media; +Cc: isely, mkrufky, mchehab, hverkuil, Ext745
In FM radio mode the TDA8295's picture carrier PLL is off, so the H-PLL
lock bit that tda8295_has_signal() reads is never set and radio always
reports 0 % signal. In radio mode use the tuner IF AGC level instead
(0x28, T_IF_AGC_STAT): 0xff is maximum gain (no signal), strong local
stations read around 0x90.
Tested on an HVR-1950 in a dense urban FM band (stations 400 kHz
apart): 30 to 84 % on local stations, 0 to 26 % where the band is empty
(87.9-88.9 MHz); TV unchanged. The AGC acts on the whole IF passband,
so between closely spaced stations the meter stays near their level
(73 to 86 % across 100-104 MHz): it shows that a signal is there, not
that this exact channel holds a station. Seek in such a band can't rely
on the level alone.
Assisted-by: Claude Opus 5.5
Signed-off-by: Ext745 <stealth3m@yahoo.com>
---
v2:
- return -EREMOTEIO if the IF AGC read fails
drivers/media/tuners/tda8290.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/media/tuners/tda8290.c b/drivers/media/tuners/tda8290.c
index 9ff0aa8cb..7441a5872 100644
--- a/drivers/media/tuners/tda8290.c
+++ b/drivers/media/tuners/tda8290.c
@@ -384,8 +384,23 @@ static int tda8295_has_signal(struct dvb_frontend *fe, u16 *signal)
struct tda8290_priv *priv = fe->analog_demod_priv;
unsigned char hvpll_stat = 0x26;
+ unsigned char t_if_agc_stat = 0x28;
unsigned char ret;
+ if (priv->tda8290_easy_mode == 0x80) {
+ /*
+ * FM radio: the picture carrier PLL is off, so the H-PLL
+ * lock bit is never set. Use the tuner IF AGC level
+ * instead: 0xff is maximum gain (no signal), strong local
+ * stations read around 0x90.
+ */
+ if (tuner_i2c_xfer_send_recv(&priv->i2c_props,
+ &t_if_agc_stat, 1, &ret, 1) != 1)
+ return -EREMOTEIO;
+ *signal = min(0xff - ret, 0x80) * 65535 / 0x80;
+ return 0;
+ }
+
tuner_i2c_xfer_send_recv(&priv->i2c_props, &hvpll_stat, 1, &ret, 1);
*signal = (ret & 0x01) ? 65535 : 0;
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 4/4] media: pvrusb2: enable FM radio on the HVR-1950
2026-10-22 23:25 ` [PATCH v2 0/4] media: FM radio for the Hauppauge WinTV-HVR-1950 Ext745
` (2 preceding siblings ...)
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
3 siblings, 0 replies; 5+ messages in thread
From: Ext745 @ 2026-10-22 23:25 UTC (permalink / raw)
To: linux-media; +Cc: isely, mkrufky, mchehab, hverkuil, Ext745
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
^ permalink raw reply related [flat|nested] 5+ messages in thread