* [PATCH 0/2] em28xx: Add empia EM2828X chip
@ 2026-03-12 22:49 Bradford Love
2026-03-12 22:49 ` [PATCH 1/2] em28xx: Add support for Empia em2828X bridge Bradford Love
2026-03-12 22:49 ` [PATCH 2/2] em28xx: Add Hauppauge USB Live2 Bradford Love
0 siblings, 2 replies; 10+ messages in thread
From: Bradford Love @ 2026-03-12 22:49 UTC (permalink / raw)
To: linux-media; +Cc: Bradford Love
The Empia 2828X chips have some video input capabilities.
Included here is minimum support for NTSC and PAL formats,
for Composite video, S-Video, and analog TV. While only
"plain" NTSC and PAL code is supplied, this appears to
work on a variety of tested NTSC and PAL flavours.
For analog TV the em2828x requires already decoded CVBS
signal. The device included which uses the new chipset is
the latest revision of Hauppauge USB Live2, which has moved
cx231xx usb bridge to em2828x. In a subsequent series I will
submit si2177 tuner support, which implements analog TV in
this way.
A media controller decoder entity has been implemented,
so that graph generation can succeed.
I have left the long lines as they are in em28xx-cards.c,
they were originally left long as it is more clear as is.
Regards,
Bradford
Bradford Love (2):
em28xx: Add support for Empia em2828X bridge
em28xx: Add Hauppauge USB Live2
drivers/media/usb/em28xx/em28xx-cards.c | 47 ++++-
drivers/media/usb/em28xx/em28xx-core.c | 146 ++++++++++++++--
drivers/media/usb/em28xx/em28xx-dvb.c | 15 ++
drivers/media/usb/em28xx/em28xx-i2c.c | 2 +
drivers/media/usb/em28xx/em28xx-reg.h | 3 +
drivers/media/usb/em28xx/em28xx-video.c | 217 ++++++++++++++++++++++--
drivers/media/usb/em28xx/em28xx.h | 20 +++
7 files changed, 426 insertions(+), 24 deletions(-)
--
2.35.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/2] em28xx: Add support for Empia em2828X bridge
2026-03-12 22:49 [PATCH 0/2] em28xx: Add empia EM2828X chip Bradford Love
@ 2026-03-12 22:49 ` Bradford Love
2026-03-13 18:37 ` [PATCH V2 " Bradford Love
` (3 more replies)
2026-03-12 22:49 ` [PATCH 2/2] em28xx: Add Hauppauge USB Live2 Bradford Love
1 sibling, 4 replies; 10+ messages in thread
From: Bradford Love @ 2026-03-12 22:49 UTC (permalink / raw)
To: linux-media; +Cc: Bradford Love
The empia em2828X usb bridge contains previous functionality,
but also contains an embedded video decoder. The implemented
capabilities include composite and s-video inputs, as well as
analog TV. Analog TV is expected in CVBS format, it must be
demodulated already.
Media controller decoder entity is included so pipeline
verification passes and graph is property constructed.
Analog TV bits based off cx231xx driver.
Signed-off-by: Bradford Love <brad@nextdimension.cc>
---
drivers/media/usb/em28xx/em28xx-cards.c | 27 ++-
drivers/media/usb/em28xx/em28xx-core.c | 146 ++++++++++++++--
drivers/media/usb/em28xx/em28xx-dvb.c | 15 ++
drivers/media/usb/em28xx/em28xx-i2c.c | 2 +
drivers/media/usb/em28xx/em28xx-reg.h | 3 +
drivers/media/usb/em28xx/em28xx-video.c | 217 ++++++++++++++++++++++--
drivers/media/usb/em28xx/em28xx.h | 19 +++
7 files changed, 405 insertions(+), 24 deletions(-)
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index d7075ebabceb..67266bddb713 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -3633,6 +3633,11 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev,
}
/* NOTE: the em2820 is used in webcams, too ! */
break;
+ case CHIP_ID_EM2828X:
+ chip_name = "em2828X";
+ dev->wait_after_write = 0;
+ dev->eeprom_addrwidth_16bit = 1;
+ break;
case CHIP_ID_EM2840:
chip_name = "em2840";
break;
@@ -3791,6 +3796,7 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
* 0x84 bulk => analog or digital**
* 0x85 isoc => digital TS2
* 0x85 bulk => digital TS2
+ * 0x8a isoc => digital video
* (*: audio should always be isoc)
* (**: analog, if ep 0x82 is isoc, otherwise digital)
*
@@ -3814,6 +3820,8 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
/* Only inspect input endpoints */
switch (e->bEndpointAddress) {
+ case 0x81: /* unknown function */
+ return;
case 0x82:
*has_video = true;
if (usb_endpoint_xfer_isoc(e)) {
@@ -3831,7 +3839,10 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
"error: skipping audio endpoint 0x83, because it uses bulk transfers !\n");
return;
case 0x84:
- if (*has_video && (usb_endpoint_xfer_bulk(e))) {
+ if (*has_dvb && (usb_endpoint_xfer_bulk(e))) {
+ *has_dvb = true;
+ dev->dvb_ep_bulk = e->bEndpointAddress;
+ } else if (*has_video && (usb_endpoint_xfer_bulk(e))) {
dev->analog_ep_bulk = e->bEndpointAddress;
} else {
if (usb_endpoint_xfer_isoc(e)) {
@@ -3865,7 +3876,17 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
dev->dvb_ep_bulk_ts2 = e->bEndpointAddress;
}
return;
- }
+ case 0x8a:
+ *has_video = true;
+ *has_dvb = true;
+ if (usb_endpoint_xfer_isoc(e)) {
+ dev->analog_ep_isoc = e->bEndpointAddress;
+ dev->alt_max_pkt_size_isoc[alt] = size;
+ } else if (usb_endpoint_xfer_bulk(e)) {
+ dev->analog_ep_bulk = e->bEndpointAddress;
+ }
+ return;
+ };
}
/*
@@ -4047,6 +4068,8 @@ static int em28xx_usb_probe(struct usb_interface *intf,
try_bulk = 1;
else
try_bulk = 0;
+ } else if (dev->board.decoder == EM28XX_BUILTIN && dev->analog_xfer_mode) {
+ try_bulk = 1;
} else {
try_bulk = usb_xfer_mode > 0;
}
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
index 29a7f3f19b56..303f6f10fda5 100644
--- a/drivers/media/usb/em28xx/em28xx-core.c
+++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -499,7 +499,8 @@ int em28xx_audio_setup(struct em28xx *dev)
if (dev->chip_id == CHIP_ID_EM2870 ||
dev->chip_id == CHIP_ID_EM2874 ||
dev->chip_id == CHIP_ID_EM28174 ||
- dev->chip_id == CHIP_ID_EM28178) {
+ dev->chip_id == CHIP_ID_EM28178 ||
+ dev->chip_id == CHIP_ID_EM2828X) {
/* Digital only device - don't load any alsa module */
dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
@@ -619,6 +620,65 @@ const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
}
EXPORT_SYMBOL_GPL(em28xx_find_led);
+void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin)
+{
+ switch (vin) {
+ case EM2828X_TELEVISION:
+ dev_dbg(&dev->intf->dev, "EM2828X_TELEVISION\n");
+ break;
+ case EM2828X_COMPOSITE:
+ dev_dbg(&dev->intf->dev, "EM2828X_COMPOSITE\n");
+ break;
+ default:
+ dev_dbg(&dev->intf->dev, "EM2828X_SVIDEO\n");
+ break;
+ };
+
+ em28xx_write_reg(dev, 0x24, 0x00);
+ em28xx_write_reg(dev, 0x25, 0x02);
+ em28xx_write_reg(dev, 0x2E, 0x00);
+
+ if (vin == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A0B, 0xfc);
+ em28xx_write_reg(dev, 0xB6, 0x8F);
+ em28xx_write_reg(dev, 0xB8, 0x01);
+ } else {
+ em28xx_write_reg(dev, 0x7A0B, 0x00);
+ em28xx_write_reg(dev, 0xB6, 0x8F);
+ em28xx_write_reg(dev, 0xB8, 0x00);
+ }
+
+ em28xx_write_reg(dev, 0x7A1C, 0x1E);
+ em28xx_write_reg(dev, 0x7A1D, 0x99);
+ em28xx_write_reg(dev, 0x7A1E, 0x99);
+ em28xx_write_reg(dev, 0x7A1F, 0x9A);
+ em28xx_write_reg(dev, 0x7A20, 0x3d);
+ em28xx_write_reg(dev, 0x7A21, 0x3e);
+ em28xx_write_reg(dev, 0x7A29, 0x00);
+ em28xx_write_reg(dev, 0x7A2F, 0x52);
+ em28xx_write_reg(dev, 0x7A40, 0x05);
+ em28xx_write_reg(dev, 0x7A51, 0x00);
+ em28xx_write_reg(dev, 0x7AC1, 0x1B);
+
+ if (vin == EM2828X_COMPOSITE || vin == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x38, 0x01);
+ em28xx_write_reg(dev, 0xB1, 0x70);
+ em28xx_write_reg(dev, 0xB3, 0x00);
+ em28xx_write_reg(dev, 0xB5, 0x00);
+ em28xx_write_reg(dev, 0x7A02, 0x4f);
+ } else { /* EM2828X_SVIDEO */
+ em28xx_write_reg(dev, 0x38, 0x00);
+ em28xx_write_reg(dev, 0xB1, 0x60);
+ em28xx_write_reg(dev, 0xB3, 0x10);
+ em28xx_write_reg(dev, 0xB5, 0x10);
+ em28xx_write_reg(dev, 0x7A02, 0x4e);
+ }
+
+ em28xx_write_reg(dev, 0x7A3F, 0x01);
+ em28xx_write_reg(dev, 0x7A3F, 0x00);
+}
+EXPORT_SYMBOL_GPL(em2828X_decoder_vmux);
+
int em28xx_capture_start(struct em28xx *dev, int start)
{
int rc;
@@ -628,6 +688,7 @@ int em28xx_capture_start(struct em28xx *dev, int start)
dev->chip_id == CHIP_ID_EM2884 ||
dev->chip_id == CHIP_ID_EM28174 ||
dev->chip_id == CHIP_ID_EM28178) {
+
/* The Transport Stream Enable Register moved in em2874 */
if (dev->dvb_xfer_bulk) {
/* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
@@ -664,26 +725,87 @@ int em28xx_capture_start(struct em28xx *dev, int start)
if (dev->is_webcam)
rc = em28xx_write_reg(dev, 0x13, 0x0c);
- /* Enable video capture */
- rc = em28xx_write_reg(dev, 0x48, 0x00);
- if (rc < 0)
- return rc;
+ if (dev->mode == EM28XX_ANALOG_MODE) {
+ /* Enable video capture */
+ dev_dbg(&dev->intf->dev, "EM28XX_ANALOG_MODE 1\n");
+ rc = em28xx_write_reg(dev, 0x48, 0x00);
+ if (rc < 0)
+ return rc;
- if (dev->mode == EM28XX_ANALOG_MODE)
rc = em28xx_write_reg(dev,
EM28XX_R12_VINENABLE,
0x67);
- else
- rc = em28xx_write_reg(dev,
- EM28XX_R12_VINENABLE,
- 0x37);
+
+ } else if (dev->chip_id == CHIP_ID_EM2828X) {
+ dev_err(&dev->intf->dev, "%s() CHIP_ID_EM2828X\n", __func__);
+ /* The Transport Stream Enable Register moved in em2874 */
+ if (dev->dvb_xfer_bulk) {
+ /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ 0xff);
+ } else {
+ /* ISOC Maximum Transfer Size = 188 * 5 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ dev->dvb_max_pkt_size_isoc / 188);
+ }
+
+ if (dev->ts == PRIMARY_TS)
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
+ EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
+ else
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
+ EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
+ } else {
+ /* Enable video capture */
+ rc = em28xx_write_reg(dev, 0x48, 0x00);
+ if (rc < 0)
+ return rc;
+ rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
+ }
+
if (rc < 0)
return rc;
usleep_range(10000, 11000);
} else {
- /* disable video capture */
- rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
+ if (dev->mode == EM28XX_DIGITAL_MODE && dev->chip_id == CHIP_ID_EM2828X) {
+ /* The Transport Stream Enable Register moved in em2874 */
+ if (dev->dvb_xfer_bulk) {
+ /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ 0xff);
+ } else {
+ /* ISOC Maximum Transfer Size = 188 * 5 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ dev->dvb_max_pkt_size_isoc / 188);
+ }
+
+ if (dev->ts == PRIMARY_TS)
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
+ EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
+ else
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
+ EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
+ } else {
+ /* disable video capture */
+ rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
+ }
}
}
diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c
index 2eb9a88e595e..4d632ca91e30 100644
--- a/drivers/media/usb/em28xx/em28xx-dvb.c
+++ b/drivers/media/usb/em28xx/em28xx-dvb.c
@@ -296,6 +296,21 @@ static int em28xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
return em28xx_set_mode(dev, EM28XX_SUSPEND);
}
+int em28xx_set_analog_freq(struct em28xx *dev, u32 freq)
+{
+ const struct dvb_tuner_ops *dops = &dev->dvb->fe[0]->ops.tuner_ops;
+
+ if (dops->set_analog_params != NULL) {
+ struct analog_parameters params;
+
+ params.frequency = freq;
+ params.std = dev->v4l2->norm;
+ params.mode = 0;
+
+ dops->set_analog_params(dev->dvb->fe[0], ¶ms);
+ }
+ return 0;
+}
/* ------------------------------------------------------------------ */
static struct lgdt330x_config em2880_lgdt3303_dev = {
diff --git a/drivers/media/usb/em28xx/em28xx-i2c.c b/drivers/media/usb/em28xx/em28xx-i2c.c
index a7eb11f7fb34..f0a901c3b49a 100644
--- a/drivers/media/usb/em28xx/em28xx-i2c.c
+++ b/drivers/media/usb/em28xx/em28xx-i2c.c
@@ -864,6 +864,8 @@ static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned int bus,
le16_to_cpu(dev_config->string2),
le16_to_cpu(dev_config->string3));
+ dev->analog_xfer_mode = data[67] & 0x01;
+
return 0;
error:
diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h
index d7c60862874a..b974873e4e86 100644
--- a/drivers/media/usb/em28xx/em28xx-reg.h
+++ b/drivers/media/usb/em28xx/em28xx-reg.h
@@ -167,6 +167,8 @@
#define EM28XX_R26_COMPR 0x26
#define EM28XX_R27_OUTFMT 0x27
+#define LOCK_STATUS_DEFAULT 0x00
+
/* em28xx Output Format Register (0x27) */
#define EM28XX_OUTFMT_RGB_8_RGRG 0x00
#define EM28XX_OUTFMT_RGB_8_GRGR 0x01
@@ -283,6 +285,7 @@ enum em28xx_chip_id {
CHIP_ID_EM2884 = 68,
CHIP_ID_EM28174 = 113,
CHIP_ID_EM28178 = 114,
+ CHIP_ID_EM2828X = 148,
};
/*
diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c
index b0c184f237a7..578ba3d88d01 100644
--- a/drivers/media/usb/em28xx/em28xx-video.c
+++ b/drivers/media/usb/em28xx/em28xx-video.c
@@ -161,13 +161,19 @@ static int em28xx_vbi_supported(struct em28xx *dev)
/* FIXME: check subdevices for VBI support */
if (dev->chip_id == CHIP_ID_EM2860 ||
- dev->chip_id == CHIP_ID_EM2883)
+ dev->chip_id == CHIP_ID_EM2883 ||
+ dev->board.decoder == EM28XX_BUILTIN)
return 1;
/* Version of em28xx that does not support VBI */
return 0;
}
+static int em28xx_analogtv_supported(struct em28xx *dev)
+{
+ return 0;
+}
+
/*
* em28xx_wake_i2c()
* configure i2c attached devices
@@ -344,6 +350,120 @@ static int em28xx_resolution_set(struct em28xx *dev)
return em28xx_scaler_set(dev, v4l2->hscale, v4l2->vscale);
}
+static void em2828X_decoder_set_std(struct em28xx *dev, v4l2_std_id norm)
+{
+ if (norm & V4L2_STD_525_60) {
+ dev_dbg(&dev->intf->dev, "V4L2_STD_525_60");
+ em28xx_write_reg(dev, 0x7A01, 0x0d); // 0x05
+ em28xx_write_reg(dev, 0x7A04, 0xDD);
+ em28xx_write_reg(dev, 0x7A07, 0x60);
+ em28xx_write_reg(dev, 0x7A08, 0x7A);
+ em28xx_write_reg(dev, 0x7A09, 0x02);
+ em28xx_write_reg(dev, 0x7A0A, 0x7C);
+ em28xx_write_reg(dev, 0x7A0C, 0x8A);
+ em28xx_write_reg(dev, 0x7A0F, 0x1C);
+ em28xx_write_reg(dev, 0x7A18, 0x20);
+ em28xx_write_reg(dev, 0x7A19, 0x74);
+ em28xx_write_reg(dev, 0x7A1A, 0x5D);
+ em28xx_write_reg(dev, 0x7A1B, 0x17);
+ em28xx_write_reg(dev, 0x7A2E, 0x85);
+ em28xx_write_reg(dev, 0x7A31, 0x63);
+ em28xx_write_reg(dev, 0x7A82, 0x42);
+ em28xx_write_reg(dev, 0x7AC0, 0xD4);
+
+ if (INPUT(dev->ctl_input)->vmux == EM2828X_COMPOSITE) {
+ em28xx_write_reg(dev, 0x7A00, 0x00);
+ em28xx_write_reg(dev, 0x7A03, 0x00);
+ em28xx_write_reg(dev, 0x7A30, 0x22);
+ em28xx_write_reg(dev, 0x7A80, 0x03);
+ } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A17, 0xc3);
+ em28xx_write_reg(dev, 0x7A31, 0x62); // BRL 0x63
+ em28xx_write_reg(dev, 0x7A82, 0x42);
+ em28xx_write_reg(dev, 0x7AC0, 0xD4);
+ em28xx_write_reg(dev, 0x7A00, 0x00);
+ em28xx_write_reg(dev, 0x7A03, 0x00);
+ em28xx_write_reg(dev, 0x7A30, 0x20);
+ em28xx_write_reg(dev, 0x7A80, 0x00);
+
+ em28xx_write_reg(dev, 0x7A50, 0xdd);
+ em28xx_write_reg(dev, 0x7A5d, 0x0e);
+ em28xx_write_reg(dev, 0x7A5e, 0xea);
+ em28xx_write_reg(dev, 0x7A60, 0x64);
+ em28xx_write_reg(dev, 0x7A67, 0x5a);
+ } else {
+ em28xx_write_reg(dev, 0x7A00, 0x01);
+ em28xx_write_reg(dev, 0x7A03, 0x03);
+ em28xx_write_reg(dev, 0x7A30, 0x20);
+ em28xx_write_reg(dev, 0x7A80, 0x04);
+ }
+ } else if (norm & V4L2_STD_625_50) {
+ dev_dbg(&dev->intf->dev, "V4L2_STD_625_50");
+ em28xx_write_reg(dev, 0x7A04, 0xDC);
+ em28xx_write_reg(dev, 0x7A0C, 0x67);
+ em28xx_write_reg(dev, 0x7A0F, 0x1C);
+ em28xx_write_reg(dev, 0x7A18, 0x28);
+ em28xx_write_reg(dev, 0x7A19, 0x32);
+ em28xx_write_reg(dev, 0x7A1A, 0xB9);
+ em28xx_write_reg(dev, 0x7A1B, 0x86);
+ em28xx_write_reg(dev, 0x7A31, 0xC3);
+ em28xx_write_reg(dev, 0x7A82, 0x52);
+
+ if (INPUT(dev->ctl_input)->vmux == EM2828X_COMPOSITE) {
+ em28xx_write_reg(dev, 0x7A00, 0x32);
+ em28xx_write_reg(dev, 0x7A01, 0x10);
+ em28xx_write_reg(dev, 0x7A03, 0x06);
+ em28xx_write_reg(dev, 0x7A07, 0x2f);
+ em28xx_write_reg(dev, 0x7A08, 0x77);
+ em28xx_write_reg(dev, 0x7A09, 0x0f);
+ em28xx_write_reg(dev, 0x7A0A, 0x8c);
+ em28xx_write_reg(dev, 0x7A20, 0x3d);
+ em28xx_write_reg(dev, 0x7A2E, 0x88);
+ em28xx_write_reg(dev, 0x7A30, 0x2c);
+ em28xx_write_reg(dev, 0x7A80, 0x07);
+ } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A00, 0x32);
+ em28xx_write_reg(dev, 0x7A03, 0x09);
+ em28xx_write_reg(dev, 0x7A30, 0x2a);
+ em28xx_write_reg(dev, 0x7A80, 0x03);
+ em28xx_write_reg(dev, 0x7A20, 0x35);
+ em28xx_write_reg(dev, 0x7A2e, 0x88);
+ em28xx_write_reg(dev, 0x7A53, 0xcc);
+ em28xx_write_reg(dev, 0x7A5d, 0x16);
+ em28xx_write_reg(dev, 0x7A5e, 0x50);
+ em28xx_write_reg(dev, 0x7A60, 0xb4);
+ em28xx_write_reg(dev, 0x7A67, 0x64);
+ } else {
+ em28xx_write_reg(dev, 0x7A00, 0x33);
+ em28xx_write_reg(dev, 0x7A01, 0x04);
+ em28xx_write_reg(dev, 0x7A03, 0x04);
+ em28xx_write_reg(dev, 0x7A07, 0x20);
+ em28xx_write_reg(dev, 0x7A08, 0x6a);
+ em28xx_write_reg(dev, 0x7A09, 0x16);
+ em28xx_write_reg(dev, 0x7A0A, 0x80);
+ em28xx_write_reg(dev, 0x7A2E, 0x8a);
+ em28xx_write_reg(dev, 0x7A30, 0x26);
+ em28xx_write_reg(dev, 0x7A80, 0x08);
+ }
+ } else {
+ dev_err(&dev->intf->dev, "%s() Unsupported STD: %X", __func__, (unsigned int)norm);
+ }
+
+ em28xx_write_reg(dev, 0x7A3F, 0x01);
+ em28xx_write_reg(dev, 0x7A3F, 0x00);
+}
+
+static int em2828X_decoder_get_lock_status(struct em28xx *dev)
+{
+ int lock_status = 0x0;
+
+ lock_status = em28xx_read_reg(dev, 0x7A3A);
+ if ((lock_status & 0x02))
+ return 1;
+ else
+ return 0;
+}
+
/* Set USB alternate setting for analog video */
static int em28xx_set_alternate(struct em28xx *dev)
{
@@ -880,6 +1000,12 @@ static void em28xx_v4l2_media_release(struct em28xx *dev)
#ifdef CONFIG_MEDIA_CONTROLLER
int i;
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ media_device_unregister_entity(dev->v4l2->decoder);
+ kfree(dev->v4l2->decoder);
+ dev->v4l2->decoder = NULL;
+ }
+
for (i = 0; i < MAX_EM28XX_INPUT; i++) {
if (!INPUT(i)->type)
return;
@@ -1003,7 +1129,7 @@ static void em28xx_v4l2_create_entities(struct em28xx *dev)
ent->function = MEDIA_ENT_F_CONN_SVIDEO;
break;
default: /* EM28XX_VMUX_TELEVISION or EM28XX_RADIO */
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
ent->function = MEDIA_ENT_F_CONN_RF;
break;
}
@@ -1018,6 +1144,26 @@ static void em28xx_v4l2_create_entities(struct em28xx *dev)
dev_err(&dev->intf->dev,
"failed to register input entity %d!\n", i);
}
+
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ v4l2->decoder_pads[EM2828X_PAD_INPUT].flags = MEDIA_PAD_FL_SINK;
+ v4l2->decoder_pads[EM2828X_PAD_INPUT].sig_type = PAD_SIGNAL_ANALOG;
+ v4l2->decoder_pads[EM2828X_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE;
+ v4l2->decoder_pads[EM2828X_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV;
+
+ v4l2->decoder = kzalloc_obj(v4l2->decoder, GFP_KERNEL);
+ v4l2->decoder->name = "em2828x_builtin";
+ v4l2->decoder->function = MEDIA_ENT_F_ATV_DECODER;
+
+ ret = media_entity_pads_init(v4l2->decoder, EM2828X_NUM_PADS, &v4l2->decoder_pads[0]);
+ if (ret < 0)
+ dev_err(&dev->intf->dev, "failed to initialize decoder pads %d!\n", ret);
+
+ ret = media_device_register_entity(dev->media_dev, v4l2->decoder);
+ if (ret < 0)
+ dev_err(&dev->intf->dev, "failed to register decoder entity %d!\n", ret);
+ }
+
#endif
}
@@ -1297,6 +1443,13 @@ static void video_mux(struct em28xx *dev, int index)
MSP_OUTPUT(MSP_SC_IN_DSP_SCART1), 0);
}
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ em2828X_decoder_vmux(dev, INPUT(index)->vmux);
+ em2828X_decoder_set_std(dev, dev->v4l2->norm);
+
+ em28xx_gpio_set(dev, INPUT(dev->ctl_input)->gpio);
+ }
+
if (dev->board.adecoder != EM28XX_NOADECODER) {
v4l2_device_call_all(v4l2_dev, 0, audio, s_routing,
dev->ctl_ainput, dev->ctl_aoutput, 0);
@@ -1366,8 +1519,40 @@ static int em28xx_s_ctrl(struct v4l2_ctrl *ctrl)
return (ret < 0) ? ret : 0;
}
+static int em28xx_g_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct em28xx_v4l2 *v4l2 =
+ container_of(ctrl->handler, struct em28xx_v4l2, ctrl_handler);
+ struct em28xx *dev = v4l2->dev;
+ int ret = -EINVAL;
+
+ switch (ctrl->id) {
+ case V4L2_CID_LOCK_STATUS:
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ ctrl->val = em2828X_decoder_get_lock_status(dev);
+ ret = 0;
+ }
+ break;
+ }
+
+ return (ret < 0) ? ret : 0;
+}
+
static const struct v4l2_ctrl_ops em28xx_ctrl_ops = {
.s_ctrl = em28xx_s_ctrl,
+ .g_volatile_ctrl = em28xx_g_ctrl,
+};
+
+static const struct v4l2_ctrl_config em28xx_lock_status_config = {
+ .ops = &em28xx_ctrl_ops,
+ .id = V4L2_CID_LOCK_STATUS,
+ .name = "Lock Status",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ .def = 0,
};
static void size_to_scale(struct em28xx *dev,
@@ -1586,6 +1771,9 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
em28xx_resolution_set(dev);
v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
+ if (dev->board.decoder == EM28XX_BUILTIN)
+ em2828X_decoder_set_std(dev, v4l2->norm);
+
return 0;
}
@@ -1829,6 +2017,11 @@ static int vidioc_g_tuner(struct file *file, void *priv,
strscpy(t->name, "Tuner", sizeof(t->name));
+ t->type = V4L2_TUNER_ANALOG_TV;
+ t->capability = V4L2_TUNER_CAP_NORM;
+ t->rangehigh = 0xffffffffUL;
+ t->signal = 0xffff; /* LOCKED */
+
v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, g_tuner, t);
return 0;
}
@@ -1978,7 +2171,7 @@ static int vidioc_querycap(struct file *file, void *priv,
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
cap->capabilities |= V4L2_CAP_AUDIO;
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
cap->capabilities |= V4L2_CAP_TUNER;
if (video_is_registered(&v4l2->vbi_dev))
cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
@@ -2549,7 +2742,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
}
hdl = &v4l2->ctrl_handler;
- v4l2_ctrl_handler_init(hdl, 8);
+ v4l2_ctrl_handler_init(hdl, 9);
v4l2->v4l2_dev.ctrl_handler = hdl;
if (dev->is_webcam)
@@ -2675,7 +2868,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
}
/* set default norm */
- v4l2->norm = V4L2_STD_PAL;
+ v4l2->norm = -1;
v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
v4l2->interlaced_fieldmode = EM28XX_INTERLACED_DEFAULT;
@@ -2738,6 +2931,8 @@ static int em28xx_v4l2_init(struct em28xx *dev)
V4L2_CID_SHARPNESS,
0, 0x0f, 1, SHARPNESS_DEFAULT);
+ v4l2_ctrl_new_custom(hdl, &em28xx_lock_status_config, NULL);
+
/* Reset image controls */
em28xx_colorlevels_set_default(dev);
v4l2_ctrl_handler_setup(hdl);
@@ -2755,10 +2950,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
V4L2_CAP_STREAMING;
if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
v4l2->vdev.device_caps |= V4L2_CAP_AUDIO;
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
v4l2->vdev.device_caps |= V4L2_CAP_TUNER;
-
/* disable inapplicable ioctls */
if (dev->is_webcam) {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_QUERYSTD);
@@ -2767,7 +2961,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
} else {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_PARM);
}
- if (dev->tuner_type == TUNER_ABSENT) {
+ if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0) {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_TUNER);
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_TUNER);
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_FREQUENCY);
@@ -2778,6 +2972,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_AUDIO);
}
+ if (dev->chip_id == CHIP_ID_EM2828X || dev->board.decoder == EM28XX_BUILTIN)
+ v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_ENUM_FRAMESIZES);
+
/* register v4l2 video video_device */
ret = video_register_device(&v4l2->vdev, VFL_TYPE_VIDEO,
video_nr[dev->devno]);
@@ -2796,12 +2993,12 @@ static int em28xx_v4l2_init(struct em28xx *dev)
v4l2->vbi_dev.queue->lock = &v4l2->vb_vbi_queue_lock;
v4l2->vbi_dev.device_caps = V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE;
- if (dev->tuner_type != TUNER_ABSENT)
+ if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0)
v4l2->vbi_dev.device_caps |= V4L2_CAP_TUNER;
/* disable inapplicable ioctls */
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_PARM);
- if (dev->tuner_type == TUNER_ABSENT) {
+ if ((v4l2->vbi_dev.device_caps & V4L2_CAP_TUNER) == 0) {
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_TUNER);
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_TUNER);
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_FREQUENCY);
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
index f3449c240d21..b6b8c4ae93af 100644
--- a/drivers/media/usb/em28xx/em28xx.h
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -425,8 +425,15 @@ enum em28xx_decoder {
EM28XX_NODECODER = 0,
EM28XX_TVP5150,
EM28XX_SAA711X,
+ EM28XX_BUILTIN,
};
+/* Built in decoder capture options */
+#define V4L2_CID_LOCK_STATUS V4L2_CID_LASTP1
+#define EM2828X_COMPOSITE 0
+#define EM2828X_SVIDEO 1
+#define EM2828X_TELEVISION 2
+
enum em28xx_sensor {
EM28XX_NOSENSOR = 0,
EM28XX_MT9V011,
@@ -469,6 +476,12 @@ struct em28xx_button {
bool inverted;
};
+enum em2828x_media_pads {
+ EM2828X_PAD_INPUT,
+ EM2828X_PAD_VID_OUT,
+ EM2828X_NUM_PADS
+};
+
struct em28xx_board {
char *name;
int vchannels;
@@ -593,6 +606,7 @@ struct em28xx_v4l2 {
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_pad video_pad, vbi_pad;
+ struct media_pad decoder_pads[EM2828X_NUM_PADS];
struct media_entity *decoder;
#endif
};
@@ -752,6 +766,8 @@ struct em28xx {
char *buf, int len);
int (*em28xx_read_reg_req)(struct em28xx *dev, u8 req, u16 reg);
+ int (*em28xx_set_analog_freq)(struct em28xx *dev, u32 freq);
+
enum em28xx_mode mode;
// Button state polling
@@ -763,6 +779,7 @@ struct em28xx {
// Snapshot button input device
char snapshot_button_path[30]; // path of the input dev
struct input_dev *sbutton_input_dev;
+ int analog_xfer_mode;
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_device *media_dev;
@@ -811,6 +828,8 @@ int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val);
int em28xx_audio_analog_set(struct em28xx *dev);
int em28xx_audio_setup(struct em28xx *dev);
+void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin);
+
const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
enum em28xx_led_role role);
int em28xx_capture_start(struct em28xx *dev, int start);
--
2.35.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/2] em28xx: Add Hauppauge USB Live2
2026-03-12 22:49 [PATCH 0/2] em28xx: Add empia EM2828X chip Bradford Love
2026-03-12 22:49 ` [PATCH 1/2] em28xx: Add support for Empia em2828X bridge Bradford Love
@ 2026-03-12 22:49 ` Bradford Love
2026-03-17 20:46 ` [PATCH v2 2/2] media: " Bradford Love
1 sibling, 1 reply; 10+ messages in thread
From: Bradford Love @ 2026-03-12 22:49 UTC (permalink / raw)
To: linux-media; +Cc: Bradford Love
New revision of Hauppauge USB Live2 switches from cx231xx usb bridge
to Empia em2828X bridge. Inputs for the USB Live2 remain the same:
- Composite video
- S-Video
- Analog stereo audio
Signed-off-by: Bradford Love <brad@nextdimension.cc>
---
drivers/media/usb/em28xx/em28xx-cards.c | 20 ++++++++++++++++++++
drivers/media/usb/em28xx/em28xx.h | 1 +
2 files changed, 21 insertions(+)
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index 67266bddb713..0c5851bf4ef0 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -2623,6 +2623,23 @@ const struct em28xx_board em28xx_boards[] = {
.gpio = mygica_utv3_tuner_audio_gpio,
} },
},
+ [EM2828X_BOARD_HAUPPAUGE_USB_LIVE2] = {
+ .name = "Hauppauge USB Live2",
+ .vchannels = 2,
+ .tuner_type = TUNER_ABSENT,
+ .has_dvb = 0,
+ .decoder = EM28XX_BUILTIN,
+ .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ,
+ .input = { {
+ .type = EM28XX_VMUX_COMPOSITE,
+ .vmux = 0,
+ .amux = EM28XX_AMUX_LINE_IN,
+ }, {
+ .type = EM28XX_VMUX_SVIDEO,
+ .vmux = 1,
+ .amux = EM28XX_AMUX_LINE_IN,
+ } },
+ },
};
EXPORT_SYMBOL_GPL(em28xx_boards);
@@ -2770,6 +2787,8 @@ struct usb_device_id em28xx_id_table[] = {
.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595 },
{ USB_DEVICE(0x2040, 0x846d),
.driver_info = EM2874_BOARD_HAUPPAUGE_USB_QUADHD },
+ { USB_DEVICE(0x2040, 0xc220),
+ .driver_info = EM2828X_BOARD_HAUPPAUGE_USB_LIVE2 },
{ USB_DEVICE(0x0438, 0xb002),
.driver_info = EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600 },
{ USB_DEVICE(0x2001, 0xf112),
@@ -3260,6 +3279,7 @@ static void em28xx_card_setup(struct em28xx *dev)
case EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C:
case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595:
+ case EM2828X_BOARD_HAUPPAUGE_USB_LIVE2:
{
struct tveeprom tv;
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
index b6b8c4ae93af..9fcaebd78bae 100644
--- a/drivers/media/usb/em28xx/em28xx.h
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -144,6 +144,7 @@
#define EM2860_BOARD_MYGICA_IGRABBER 105
#define EM2874_BOARD_HAUPPAUGE_USB_QUADHD 106
#define EM2860_BOARD_MYGICA_UTV3 107
+#define EM2828X_BOARD_HAUPPAUGE_USB_LIVE2 108
/* Limits minimum and default number of buffers */
#define EM28XX_MIN_BUF 4
--
2.35.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH V2 1/2] em28xx: Add support for Empia em2828X bridge
2026-03-12 22:49 ` [PATCH 1/2] em28xx: Add support for Empia em2828X bridge Bradford Love
@ 2026-03-13 18:37 ` Bradford Love
2026-03-16 15:12 ` Hans Verkuil
2026-03-17 20:45 ` [PATCH v2 1/2] media: " Bradford Love
` (2 subsequent siblings)
3 siblings, 1 reply; 10+ messages in thread
From: Bradford Love @ 2026-03-13 18:37 UTC (permalink / raw)
To: linux-media; +Cc: Bradford Love
The empia em2828X usb bridge contains previous functionality,
but also contains an embedded video decoder. The implemented
capabilities include composite and s-video inputs, as well as
analog TV. Analog TV is expected in CVBS format, it must be
demodulated already.
Media controller decoder entity is included so pipeline
verification passes and graph is properly constructed.
Analog TV bits based off cx231xx driver.
Signed-off-by: Bradford Love <brad@nextdimension.cc>
---
Changes in v2:
- fixed kzalloc_obj compilation issue
---
drivers/media/usb/em28xx/em28xx-cards.c | 27 ++-
drivers/media/usb/em28xx/em28xx-core.c | 146 ++++++++++++++--
drivers/media/usb/em28xx/em28xx-dvb.c | 15 ++
drivers/media/usb/em28xx/em28xx-i2c.c | 2 +
drivers/media/usb/em28xx/em28xx-reg.h | 3 +
drivers/media/usb/em28xx/em28xx-video.c | 217 ++++++++++++++++++++++--
drivers/media/usb/em28xx/em28xx.h | 19 +++
7 files changed, 405 insertions(+), 24 deletions(-)
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index d7075ebabceb..67266bddb713 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -3633,6 +3633,11 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev,
}
/* NOTE: the em2820 is used in webcams, too ! */
break;
+ case CHIP_ID_EM2828X:
+ chip_name = "em2828X";
+ dev->wait_after_write = 0;
+ dev->eeprom_addrwidth_16bit = 1;
+ break;
case CHIP_ID_EM2840:
chip_name = "em2840";
break;
@@ -3791,6 +3796,7 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
* 0x84 bulk => analog or digital**
* 0x85 isoc => digital TS2
* 0x85 bulk => digital TS2
+ * 0x8a isoc => digital video
* (*: audio should always be isoc)
* (**: analog, if ep 0x82 is isoc, otherwise digital)
*
@@ -3814,6 +3820,8 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
/* Only inspect input endpoints */
switch (e->bEndpointAddress) {
+ case 0x81: /* unknown function */
+ return;
case 0x82:
*has_video = true;
if (usb_endpoint_xfer_isoc(e)) {
@@ -3831,7 +3839,10 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
"error: skipping audio endpoint 0x83, because it uses bulk transfers !\n");
return;
case 0x84:
- if (*has_video && (usb_endpoint_xfer_bulk(e))) {
+ if (*has_dvb && (usb_endpoint_xfer_bulk(e))) {
+ *has_dvb = true;
+ dev->dvb_ep_bulk = e->bEndpointAddress;
+ } else if (*has_video && (usb_endpoint_xfer_bulk(e))) {
dev->analog_ep_bulk = e->bEndpointAddress;
} else {
if (usb_endpoint_xfer_isoc(e)) {
@@ -3865,7 +3876,17 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
dev->dvb_ep_bulk_ts2 = e->bEndpointAddress;
}
return;
- }
+ case 0x8a:
+ *has_video = true;
+ *has_dvb = true;
+ if (usb_endpoint_xfer_isoc(e)) {
+ dev->analog_ep_isoc = e->bEndpointAddress;
+ dev->alt_max_pkt_size_isoc[alt] = size;
+ } else if (usb_endpoint_xfer_bulk(e)) {
+ dev->analog_ep_bulk = e->bEndpointAddress;
+ }
+ return;
+ };
}
/*
@@ -4047,6 +4068,8 @@ static int em28xx_usb_probe(struct usb_interface *intf,
try_bulk = 1;
else
try_bulk = 0;
+ } else if (dev->board.decoder == EM28XX_BUILTIN && dev->analog_xfer_mode) {
+ try_bulk = 1;
} else {
try_bulk = usb_xfer_mode > 0;
}
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
index 29a7f3f19b56..303f6f10fda5 100644
--- a/drivers/media/usb/em28xx/em28xx-core.c
+++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -499,7 +499,8 @@ int em28xx_audio_setup(struct em28xx *dev)
if (dev->chip_id == CHIP_ID_EM2870 ||
dev->chip_id == CHIP_ID_EM2874 ||
dev->chip_id == CHIP_ID_EM28174 ||
- dev->chip_id == CHIP_ID_EM28178) {
+ dev->chip_id == CHIP_ID_EM28178 ||
+ dev->chip_id == CHIP_ID_EM2828X) {
/* Digital only device - don't load any alsa module */
dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
@@ -619,6 +620,65 @@ const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
}
EXPORT_SYMBOL_GPL(em28xx_find_led);
+void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin)
+{
+ switch (vin) {
+ case EM2828X_TELEVISION:
+ dev_dbg(&dev->intf->dev, "EM2828X_TELEVISION\n");
+ break;
+ case EM2828X_COMPOSITE:
+ dev_dbg(&dev->intf->dev, "EM2828X_COMPOSITE\n");
+ break;
+ default:
+ dev_dbg(&dev->intf->dev, "EM2828X_SVIDEO\n");
+ break;
+ };
+
+ em28xx_write_reg(dev, 0x24, 0x00);
+ em28xx_write_reg(dev, 0x25, 0x02);
+ em28xx_write_reg(dev, 0x2E, 0x00);
+
+ if (vin == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A0B, 0xfc);
+ em28xx_write_reg(dev, 0xB6, 0x8F);
+ em28xx_write_reg(dev, 0xB8, 0x01);
+ } else {
+ em28xx_write_reg(dev, 0x7A0B, 0x00);
+ em28xx_write_reg(dev, 0xB6, 0x8F);
+ em28xx_write_reg(dev, 0xB8, 0x00);
+ }
+
+ em28xx_write_reg(dev, 0x7A1C, 0x1E);
+ em28xx_write_reg(dev, 0x7A1D, 0x99);
+ em28xx_write_reg(dev, 0x7A1E, 0x99);
+ em28xx_write_reg(dev, 0x7A1F, 0x9A);
+ em28xx_write_reg(dev, 0x7A20, 0x3d);
+ em28xx_write_reg(dev, 0x7A21, 0x3e);
+ em28xx_write_reg(dev, 0x7A29, 0x00);
+ em28xx_write_reg(dev, 0x7A2F, 0x52);
+ em28xx_write_reg(dev, 0x7A40, 0x05);
+ em28xx_write_reg(dev, 0x7A51, 0x00);
+ em28xx_write_reg(dev, 0x7AC1, 0x1B);
+
+ if (vin == EM2828X_COMPOSITE || vin == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x38, 0x01);
+ em28xx_write_reg(dev, 0xB1, 0x70);
+ em28xx_write_reg(dev, 0xB3, 0x00);
+ em28xx_write_reg(dev, 0xB5, 0x00);
+ em28xx_write_reg(dev, 0x7A02, 0x4f);
+ } else { /* EM2828X_SVIDEO */
+ em28xx_write_reg(dev, 0x38, 0x00);
+ em28xx_write_reg(dev, 0xB1, 0x60);
+ em28xx_write_reg(dev, 0xB3, 0x10);
+ em28xx_write_reg(dev, 0xB5, 0x10);
+ em28xx_write_reg(dev, 0x7A02, 0x4e);
+ }
+
+ em28xx_write_reg(dev, 0x7A3F, 0x01);
+ em28xx_write_reg(dev, 0x7A3F, 0x00);
+}
+EXPORT_SYMBOL_GPL(em2828X_decoder_vmux);
+
int em28xx_capture_start(struct em28xx *dev, int start)
{
int rc;
@@ -628,6 +688,7 @@ int em28xx_capture_start(struct em28xx *dev, int start)
dev->chip_id == CHIP_ID_EM2884 ||
dev->chip_id == CHIP_ID_EM28174 ||
dev->chip_id == CHIP_ID_EM28178) {
+
/* The Transport Stream Enable Register moved in em2874 */
if (dev->dvb_xfer_bulk) {
/* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
@@ -664,26 +725,87 @@ int em28xx_capture_start(struct em28xx *dev, int start)
if (dev->is_webcam)
rc = em28xx_write_reg(dev, 0x13, 0x0c);
- /* Enable video capture */
- rc = em28xx_write_reg(dev, 0x48, 0x00);
- if (rc < 0)
- return rc;
+ if (dev->mode == EM28XX_ANALOG_MODE) {
+ /* Enable video capture */
+ dev_dbg(&dev->intf->dev, "EM28XX_ANALOG_MODE 1\n");
+ rc = em28xx_write_reg(dev, 0x48, 0x00);
+ if (rc < 0)
+ return rc;
- if (dev->mode == EM28XX_ANALOG_MODE)
rc = em28xx_write_reg(dev,
EM28XX_R12_VINENABLE,
0x67);
- else
- rc = em28xx_write_reg(dev,
- EM28XX_R12_VINENABLE,
- 0x37);
+
+ } else if (dev->chip_id == CHIP_ID_EM2828X) {
+ dev_err(&dev->intf->dev, "%s() CHIP_ID_EM2828X\n", __func__);
+ /* The Transport Stream Enable Register moved in em2874 */
+ if (dev->dvb_xfer_bulk) {
+ /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ 0xff);
+ } else {
+ /* ISOC Maximum Transfer Size = 188 * 5 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ dev->dvb_max_pkt_size_isoc / 188);
+ }
+
+ if (dev->ts == PRIMARY_TS)
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
+ EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
+ else
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
+ EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
+ } else {
+ /* Enable video capture */
+ rc = em28xx_write_reg(dev, 0x48, 0x00);
+ if (rc < 0)
+ return rc;
+ rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
+ }
+
if (rc < 0)
return rc;
usleep_range(10000, 11000);
} else {
- /* disable video capture */
- rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
+ if (dev->mode == EM28XX_DIGITAL_MODE && dev->chip_id == CHIP_ID_EM2828X) {
+ /* The Transport Stream Enable Register moved in em2874 */
+ if (dev->dvb_xfer_bulk) {
+ /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ 0xff);
+ } else {
+ /* ISOC Maximum Transfer Size = 188 * 5 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ dev->dvb_max_pkt_size_isoc / 188);
+ }
+
+ if (dev->ts == PRIMARY_TS)
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
+ EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
+ else
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
+ EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
+ } else {
+ /* disable video capture */
+ rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
+ }
}
}
diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c
index 2eb9a88e595e..4d632ca91e30 100644
--- a/drivers/media/usb/em28xx/em28xx-dvb.c
+++ b/drivers/media/usb/em28xx/em28xx-dvb.c
@@ -296,6 +296,21 @@ static int em28xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
return em28xx_set_mode(dev, EM28XX_SUSPEND);
}
+int em28xx_set_analog_freq(struct em28xx *dev, u32 freq)
+{
+ const struct dvb_tuner_ops *dops = &dev->dvb->fe[0]->ops.tuner_ops;
+
+ if (dops->set_analog_params != NULL) {
+ struct analog_parameters params;
+
+ params.frequency = freq;
+ params.std = dev->v4l2->norm;
+ params.mode = 0;
+
+ dops->set_analog_params(dev->dvb->fe[0], ¶ms);
+ }
+ return 0;
+}
/* ------------------------------------------------------------------ */
static struct lgdt330x_config em2880_lgdt3303_dev = {
diff --git a/drivers/media/usb/em28xx/em28xx-i2c.c b/drivers/media/usb/em28xx/em28xx-i2c.c
index a7eb11f7fb34..f0a901c3b49a 100644
--- a/drivers/media/usb/em28xx/em28xx-i2c.c
+++ b/drivers/media/usb/em28xx/em28xx-i2c.c
@@ -864,6 +864,8 @@ static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned int bus,
le16_to_cpu(dev_config->string2),
le16_to_cpu(dev_config->string3));
+ dev->analog_xfer_mode = data[67] & 0x01;
+
return 0;
error:
diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h
index d7c60862874a..b974873e4e86 100644
--- a/drivers/media/usb/em28xx/em28xx-reg.h
+++ b/drivers/media/usb/em28xx/em28xx-reg.h
@@ -167,6 +167,8 @@
#define EM28XX_R26_COMPR 0x26
#define EM28XX_R27_OUTFMT 0x27
+#define LOCK_STATUS_DEFAULT 0x00
+
/* em28xx Output Format Register (0x27) */
#define EM28XX_OUTFMT_RGB_8_RGRG 0x00
#define EM28XX_OUTFMT_RGB_8_GRGR 0x01
@@ -283,6 +285,7 @@ enum em28xx_chip_id {
CHIP_ID_EM2884 = 68,
CHIP_ID_EM28174 = 113,
CHIP_ID_EM28178 = 114,
+ CHIP_ID_EM2828X = 148,
};
/*
diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c
index b0c184f237a7..578ba3d88d01 100644
--- a/drivers/media/usb/em28xx/em28xx-video.c
+++ b/drivers/media/usb/em28xx/em28xx-video.c
@@ -161,13 +161,19 @@ static int em28xx_vbi_supported(struct em28xx *dev)
/* FIXME: check subdevices for VBI support */
if (dev->chip_id == CHIP_ID_EM2860 ||
- dev->chip_id == CHIP_ID_EM2883)
+ dev->chip_id == CHIP_ID_EM2883 ||
+ dev->board.decoder == EM28XX_BUILTIN)
return 1;
/* Version of em28xx that does not support VBI */
return 0;
}
+static int em28xx_analogtv_supported(struct em28xx *dev)
+{
+ return 0;
+}
+
/*
* em28xx_wake_i2c()
* configure i2c attached devices
@@ -344,6 +350,120 @@ static int em28xx_resolution_set(struct em28xx *dev)
return em28xx_scaler_set(dev, v4l2->hscale, v4l2->vscale);
}
+static void em2828X_decoder_set_std(struct em28xx *dev, v4l2_std_id norm)
+{
+ if (norm & V4L2_STD_525_60) {
+ dev_dbg(&dev->intf->dev, "V4L2_STD_525_60");
+ em28xx_write_reg(dev, 0x7A01, 0x0d); // 0x05
+ em28xx_write_reg(dev, 0x7A04, 0xDD);
+ em28xx_write_reg(dev, 0x7A07, 0x60);
+ em28xx_write_reg(dev, 0x7A08, 0x7A);
+ em28xx_write_reg(dev, 0x7A09, 0x02);
+ em28xx_write_reg(dev, 0x7A0A, 0x7C);
+ em28xx_write_reg(dev, 0x7A0C, 0x8A);
+ em28xx_write_reg(dev, 0x7A0F, 0x1C);
+ em28xx_write_reg(dev, 0x7A18, 0x20);
+ em28xx_write_reg(dev, 0x7A19, 0x74);
+ em28xx_write_reg(dev, 0x7A1A, 0x5D);
+ em28xx_write_reg(dev, 0x7A1B, 0x17);
+ em28xx_write_reg(dev, 0x7A2E, 0x85);
+ em28xx_write_reg(dev, 0x7A31, 0x63);
+ em28xx_write_reg(dev, 0x7A82, 0x42);
+ em28xx_write_reg(dev, 0x7AC0, 0xD4);
+
+ if (INPUT(dev->ctl_input)->vmux == EM2828X_COMPOSITE) {
+ em28xx_write_reg(dev, 0x7A00, 0x00);
+ em28xx_write_reg(dev, 0x7A03, 0x00);
+ em28xx_write_reg(dev, 0x7A30, 0x22);
+ em28xx_write_reg(dev, 0x7A80, 0x03);
+ } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A17, 0xc3);
+ em28xx_write_reg(dev, 0x7A31, 0x62); // BRL 0x63
+ em28xx_write_reg(dev, 0x7A82, 0x42);
+ em28xx_write_reg(dev, 0x7AC0, 0xD4);
+ em28xx_write_reg(dev, 0x7A00, 0x00);
+ em28xx_write_reg(dev, 0x7A03, 0x00);
+ em28xx_write_reg(dev, 0x7A30, 0x20);
+ em28xx_write_reg(dev, 0x7A80, 0x00);
+
+ em28xx_write_reg(dev, 0x7A50, 0xdd);
+ em28xx_write_reg(dev, 0x7A5d, 0x0e);
+ em28xx_write_reg(dev, 0x7A5e, 0xea);
+ em28xx_write_reg(dev, 0x7A60, 0x64);
+ em28xx_write_reg(dev, 0x7A67, 0x5a);
+ } else {
+ em28xx_write_reg(dev, 0x7A00, 0x01);
+ em28xx_write_reg(dev, 0x7A03, 0x03);
+ em28xx_write_reg(dev, 0x7A30, 0x20);
+ em28xx_write_reg(dev, 0x7A80, 0x04);
+ }
+ } else if (norm & V4L2_STD_625_50) {
+ dev_dbg(&dev->intf->dev, "V4L2_STD_625_50");
+ em28xx_write_reg(dev, 0x7A04, 0xDC);
+ em28xx_write_reg(dev, 0x7A0C, 0x67);
+ em28xx_write_reg(dev, 0x7A0F, 0x1C);
+ em28xx_write_reg(dev, 0x7A18, 0x28);
+ em28xx_write_reg(dev, 0x7A19, 0x32);
+ em28xx_write_reg(dev, 0x7A1A, 0xB9);
+ em28xx_write_reg(dev, 0x7A1B, 0x86);
+ em28xx_write_reg(dev, 0x7A31, 0xC3);
+ em28xx_write_reg(dev, 0x7A82, 0x52);
+
+ if (INPUT(dev->ctl_input)->vmux == EM2828X_COMPOSITE) {
+ em28xx_write_reg(dev, 0x7A00, 0x32);
+ em28xx_write_reg(dev, 0x7A01, 0x10);
+ em28xx_write_reg(dev, 0x7A03, 0x06);
+ em28xx_write_reg(dev, 0x7A07, 0x2f);
+ em28xx_write_reg(dev, 0x7A08, 0x77);
+ em28xx_write_reg(dev, 0x7A09, 0x0f);
+ em28xx_write_reg(dev, 0x7A0A, 0x8c);
+ em28xx_write_reg(dev, 0x7A20, 0x3d);
+ em28xx_write_reg(dev, 0x7A2E, 0x88);
+ em28xx_write_reg(dev, 0x7A30, 0x2c);
+ em28xx_write_reg(dev, 0x7A80, 0x07);
+ } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A00, 0x32);
+ em28xx_write_reg(dev, 0x7A03, 0x09);
+ em28xx_write_reg(dev, 0x7A30, 0x2a);
+ em28xx_write_reg(dev, 0x7A80, 0x03);
+ em28xx_write_reg(dev, 0x7A20, 0x35);
+ em28xx_write_reg(dev, 0x7A2e, 0x88);
+ em28xx_write_reg(dev, 0x7A53, 0xcc);
+ em28xx_write_reg(dev, 0x7A5d, 0x16);
+ em28xx_write_reg(dev, 0x7A5e, 0x50);
+ em28xx_write_reg(dev, 0x7A60, 0xb4);
+ em28xx_write_reg(dev, 0x7A67, 0x64);
+ } else {
+ em28xx_write_reg(dev, 0x7A00, 0x33);
+ em28xx_write_reg(dev, 0x7A01, 0x04);
+ em28xx_write_reg(dev, 0x7A03, 0x04);
+ em28xx_write_reg(dev, 0x7A07, 0x20);
+ em28xx_write_reg(dev, 0x7A08, 0x6a);
+ em28xx_write_reg(dev, 0x7A09, 0x16);
+ em28xx_write_reg(dev, 0x7A0A, 0x80);
+ em28xx_write_reg(dev, 0x7A2E, 0x8a);
+ em28xx_write_reg(dev, 0x7A30, 0x26);
+ em28xx_write_reg(dev, 0x7A80, 0x08);
+ }
+ } else {
+ dev_err(&dev->intf->dev, "%s() Unsupported STD: %X", __func__, (unsigned int)norm);
+ }
+
+ em28xx_write_reg(dev, 0x7A3F, 0x01);
+ em28xx_write_reg(dev, 0x7A3F, 0x00);
+}
+
+static int em2828X_decoder_get_lock_status(struct em28xx *dev)
+{
+ int lock_status = 0x0;
+
+ lock_status = em28xx_read_reg(dev, 0x7A3A);
+ if ((lock_status & 0x02))
+ return 1;
+ else
+ return 0;
+}
+
/* Set USB alternate setting for analog video */
static int em28xx_set_alternate(struct em28xx *dev)
{
@@ -880,6 +1000,12 @@ static void em28xx_v4l2_media_release(struct em28xx *dev)
#ifdef CONFIG_MEDIA_CONTROLLER
int i;
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ media_device_unregister_entity(dev->v4l2->decoder);
+ kfree(dev->v4l2->decoder);
+ dev->v4l2->decoder = NULL;
+ }
+
for (i = 0; i < MAX_EM28XX_INPUT; i++) {
if (!INPUT(i)->type)
return;
@@ -1003,7 +1129,7 @@ static void em28xx_v4l2_create_entities(struct em28xx *dev)
ent->function = MEDIA_ENT_F_CONN_SVIDEO;
break;
default: /* EM28XX_VMUX_TELEVISION or EM28XX_RADIO */
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
ent->function = MEDIA_ENT_F_CONN_RF;
break;
}
@@ -1018,6 +1144,26 @@ static void em28xx_v4l2_create_entities(struct em28xx *dev)
dev_err(&dev->intf->dev,
"failed to register input entity %d!\n", i);
}
+
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ v4l2->decoder_pads[EM2828X_PAD_INPUT].flags = MEDIA_PAD_FL_SINK;
+ v4l2->decoder_pads[EM2828X_PAD_INPUT].sig_type = PAD_SIGNAL_ANALOG;
+ v4l2->decoder_pads[EM2828X_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE;
+ v4l2->decoder_pads[EM2828X_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV;
+
+ v4l2->decoder = kzalloc_obj(*v4l2->decoder);
+ v4l2->decoder->name = "em2828x_builtin";
+ v4l2->decoder->function = MEDIA_ENT_F_ATV_DECODER;
+
+ ret = media_entity_pads_init(v4l2->decoder, EM2828X_NUM_PADS, &v4l2->decoder_pads[0]);
+ if (ret < 0)
+ dev_err(&dev->intf->dev, "failed to initialize decoder pads %d!\n", ret);
+
+ ret = media_device_register_entity(dev->media_dev, v4l2->decoder);
+ if (ret < 0)
+ dev_err(&dev->intf->dev, "failed to register decoder entity %d!\n", ret);
+ }
+
#endif
}
@@ -1297,6 +1443,13 @@ static void video_mux(struct em28xx *dev, int index)
MSP_OUTPUT(MSP_SC_IN_DSP_SCART1), 0);
}
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ em2828X_decoder_vmux(dev, INPUT(index)->vmux);
+ em2828X_decoder_set_std(dev, dev->v4l2->norm);
+
+ em28xx_gpio_set(dev, INPUT(dev->ctl_input)->gpio);
+ }
+
if (dev->board.adecoder != EM28XX_NOADECODER) {
v4l2_device_call_all(v4l2_dev, 0, audio, s_routing,
dev->ctl_ainput, dev->ctl_aoutput, 0);
@@ -1366,8 +1519,40 @@ static int em28xx_s_ctrl(struct v4l2_ctrl *ctrl)
return (ret < 0) ? ret : 0;
}
+static int em28xx_g_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct em28xx_v4l2 *v4l2 =
+ container_of(ctrl->handler, struct em28xx_v4l2, ctrl_handler);
+ struct em28xx *dev = v4l2->dev;
+ int ret = -EINVAL;
+
+ switch (ctrl->id) {
+ case V4L2_CID_LOCK_STATUS:
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ ctrl->val = em2828X_decoder_get_lock_status(dev);
+ ret = 0;
+ }
+ break;
+ }
+
+ return (ret < 0) ? ret : 0;
+}
+
static const struct v4l2_ctrl_ops em28xx_ctrl_ops = {
.s_ctrl = em28xx_s_ctrl,
+ .g_volatile_ctrl = em28xx_g_ctrl,
+};
+
+static const struct v4l2_ctrl_config em28xx_lock_status_config = {
+ .ops = &em28xx_ctrl_ops,
+ .id = V4L2_CID_LOCK_STATUS,
+ .name = "Lock Status",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ .def = 0,
};
static void size_to_scale(struct em28xx *dev,
@@ -1586,6 +1771,9 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
em28xx_resolution_set(dev);
v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
+ if (dev->board.decoder == EM28XX_BUILTIN)
+ em2828X_decoder_set_std(dev, v4l2->norm);
+
return 0;
}
@@ -1829,6 +2017,11 @@ static int vidioc_g_tuner(struct file *file, void *priv,
strscpy(t->name, "Tuner", sizeof(t->name));
+ t->type = V4L2_TUNER_ANALOG_TV;
+ t->capability = V4L2_TUNER_CAP_NORM;
+ t->rangehigh = 0xffffffffUL;
+ t->signal = 0xffff; /* LOCKED */
+
v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, g_tuner, t);
return 0;
}
@@ -1978,7 +2171,7 @@ static int vidioc_querycap(struct file *file, void *priv,
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
cap->capabilities |= V4L2_CAP_AUDIO;
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
cap->capabilities |= V4L2_CAP_TUNER;
if (video_is_registered(&v4l2->vbi_dev))
cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
@@ -2549,7 +2742,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
}
hdl = &v4l2->ctrl_handler;
- v4l2_ctrl_handler_init(hdl, 8);
+ v4l2_ctrl_handler_init(hdl, 9);
v4l2->v4l2_dev.ctrl_handler = hdl;
if (dev->is_webcam)
@@ -2675,7 +2868,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
}
/* set default norm */
- v4l2->norm = V4L2_STD_PAL;
+ v4l2->norm = -1;
v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
v4l2->interlaced_fieldmode = EM28XX_INTERLACED_DEFAULT;
@@ -2738,6 +2931,8 @@ static int em28xx_v4l2_init(struct em28xx *dev)
V4L2_CID_SHARPNESS,
0, 0x0f, 1, SHARPNESS_DEFAULT);
+ v4l2_ctrl_new_custom(hdl, &em28xx_lock_status_config, NULL);
+
/* Reset image controls */
em28xx_colorlevels_set_default(dev);
v4l2_ctrl_handler_setup(hdl);
@@ -2755,10 +2950,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
V4L2_CAP_STREAMING;
if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
v4l2->vdev.device_caps |= V4L2_CAP_AUDIO;
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
v4l2->vdev.device_caps |= V4L2_CAP_TUNER;
-
/* disable inapplicable ioctls */
if (dev->is_webcam) {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_QUERYSTD);
@@ -2767,7 +2961,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
} else {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_PARM);
}
- if (dev->tuner_type == TUNER_ABSENT) {
+ if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0) {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_TUNER);
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_TUNER);
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_FREQUENCY);
@@ -2778,6 +2972,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_AUDIO);
}
+ if (dev->chip_id == CHIP_ID_EM2828X || dev->board.decoder == EM28XX_BUILTIN)
+ v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_ENUM_FRAMESIZES);
+
/* register v4l2 video video_device */
ret = video_register_device(&v4l2->vdev, VFL_TYPE_VIDEO,
video_nr[dev->devno]);
@@ -2796,12 +2993,12 @@ static int em28xx_v4l2_init(struct em28xx *dev)
v4l2->vbi_dev.queue->lock = &v4l2->vb_vbi_queue_lock;
v4l2->vbi_dev.device_caps = V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE;
- if (dev->tuner_type != TUNER_ABSENT)
+ if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0)
v4l2->vbi_dev.device_caps |= V4L2_CAP_TUNER;
/* disable inapplicable ioctls */
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_PARM);
- if (dev->tuner_type == TUNER_ABSENT) {
+ if ((v4l2->vbi_dev.device_caps & V4L2_CAP_TUNER) == 0) {
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_TUNER);
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_TUNER);
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_FREQUENCY);
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
index f3449c240d21..b6b8c4ae93af 100644
--- a/drivers/media/usb/em28xx/em28xx.h
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -425,8 +425,15 @@ enum em28xx_decoder {
EM28XX_NODECODER = 0,
EM28XX_TVP5150,
EM28XX_SAA711X,
+ EM28XX_BUILTIN,
};
+/* Built in decoder capture options */
+#define V4L2_CID_LOCK_STATUS V4L2_CID_LASTP1
+#define EM2828X_COMPOSITE 0
+#define EM2828X_SVIDEO 1
+#define EM2828X_TELEVISION 2
+
enum em28xx_sensor {
EM28XX_NOSENSOR = 0,
EM28XX_MT9V011,
@@ -469,6 +476,12 @@ struct em28xx_button {
bool inverted;
};
+enum em2828x_media_pads {
+ EM2828X_PAD_INPUT,
+ EM2828X_PAD_VID_OUT,
+ EM2828X_NUM_PADS
+};
+
struct em28xx_board {
char *name;
int vchannels;
@@ -593,6 +606,7 @@ struct em28xx_v4l2 {
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_pad video_pad, vbi_pad;
+ struct media_pad decoder_pads[EM2828X_NUM_PADS];
struct media_entity *decoder;
#endif
};
@@ -752,6 +766,8 @@ struct em28xx {
char *buf, int len);
int (*em28xx_read_reg_req)(struct em28xx *dev, u8 req, u16 reg);
+ int (*em28xx_set_analog_freq)(struct em28xx *dev, u32 freq);
+
enum em28xx_mode mode;
// Button state polling
@@ -763,6 +779,7 @@ struct em28xx {
// Snapshot button input device
char snapshot_button_path[30]; // path of the input dev
struct input_dev *sbutton_input_dev;
+ int analog_xfer_mode;
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_device *media_dev;
@@ -811,6 +828,8 @@ int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val);
int em28xx_audio_analog_set(struct em28xx *dev);
int em28xx_audio_setup(struct em28xx *dev);
+void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin);
+
const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
enum em28xx_led_role role);
int em28xx_capture_start(struct em28xx *dev, int start);
--
2.35.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH V2 1/2] em28xx: Add support for Empia em2828X bridge
2026-03-13 18:37 ` [PATCH V2 " Bradford Love
@ 2026-03-16 15:12 ` Hans Verkuil
2026-03-16 18:28 ` Bradford Love
0 siblings, 1 reply; 10+ messages in thread
From: Hans Verkuil @ 2026-03-16 15:12 UTC (permalink / raw)
To: Bradford Love, linux-media
Hi Brad,
On 13/03/2026 19:37, Bradford Love wrote:
> The empia em2828X usb bridge contains previous functionality,
> but also contains an embedded video decoder. The implemented
> capabilities include composite and s-video inputs, as well as
> analog TV. Analog TV is expected in CVBS format, it must be
> demodulated already.
>
> Media controller decoder entity is included so pipeline
> verification passes and graph is properly constructed.
>
> Analog TV bits based off cx231xx driver.
Nice, it's been a long time since there were substantial changes to em28xx.
Review below...
>
> Signed-off-by: Bradford Love <brad@nextdimension.cc>
> ---
> Changes in v2:
> - fixed kzalloc_obj compilation issue
>
> ---
> drivers/media/usb/em28xx/em28xx-cards.c | 27 ++-
> drivers/media/usb/em28xx/em28xx-core.c | 146 ++++++++++++++--
> drivers/media/usb/em28xx/em28xx-dvb.c | 15 ++
> drivers/media/usb/em28xx/em28xx-i2c.c | 2 +
> drivers/media/usb/em28xx/em28xx-reg.h | 3 +
> drivers/media/usb/em28xx/em28xx-video.c | 217 ++++++++++++++++++++++--
> drivers/media/usb/em28xx/em28xx.h | 19 +++
> 7 files changed, 405 insertions(+), 24 deletions(-)
<snip>
> diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
> index 29a7f3f19b56..303f6f10fda5 100644
> --- a/drivers/media/usb/em28xx/em28xx-core.c
> +++ b/drivers/media/usb/em28xx/em28xx-core.c
> @@ -499,7 +499,8 @@ int em28xx_audio_setup(struct em28xx *dev)
> if (dev->chip_id == CHIP_ID_EM2870 ||
> dev->chip_id == CHIP_ID_EM2874 ||
> dev->chip_id == CHIP_ID_EM28174 ||
> - dev->chip_id == CHIP_ID_EM28178) {
> + dev->chip_id == CHIP_ID_EM28178 ||
> + dev->chip_id == CHIP_ID_EM2828X) {
> /* Digital only device - don't load any alsa module */
> dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
> dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
> @@ -619,6 +620,65 @@ const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
> }
> EXPORT_SYMBOL_GPL(em28xx_find_led);
>
> +void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin)
> +{
> + switch (vin) {
> + case EM2828X_TELEVISION:
> + dev_dbg(&dev->intf->dev, "EM2828X_TELEVISION\n");
> + break;
> + case EM2828X_COMPOSITE:
> + dev_dbg(&dev->intf->dev, "EM2828X_COMPOSITE\n");
> + break;
> + default:
> + dev_dbg(&dev->intf->dev, "EM2828X_SVIDEO\n");
> + break;
> + };
> +
> + em28xx_write_reg(dev, 0x24, 0x00);
> + em28xx_write_reg(dev, 0x25, 0x02);
> + em28xx_write_reg(dev, 0x2E, 0x00);
> +
> + if (vin == EM2828X_TELEVISION) {
> + em28xx_write_reg(dev, 0x7A0B, 0xfc);
> + em28xx_write_reg(dev, 0xB6, 0x8F);
> + em28xx_write_reg(dev, 0xB8, 0x01);
> + } else {
> + em28xx_write_reg(dev, 0x7A0B, 0x00);
> + em28xx_write_reg(dev, 0xB6, 0x8F);
> + em28xx_write_reg(dev, 0xB8, 0x00);
> + }
> +
> + em28xx_write_reg(dev, 0x7A1C, 0x1E);
> + em28xx_write_reg(dev, 0x7A1D, 0x99);
> + em28xx_write_reg(dev, 0x7A1E, 0x99);
> + em28xx_write_reg(dev, 0x7A1F, 0x9A);
> + em28xx_write_reg(dev, 0x7A20, 0x3d);
> + em28xx_write_reg(dev, 0x7A21, 0x3e);
> + em28xx_write_reg(dev, 0x7A29, 0x00);
> + em28xx_write_reg(dev, 0x7A2F, 0x52);
> + em28xx_write_reg(dev, 0x7A40, 0x05);
> + em28xx_write_reg(dev, 0x7A51, 0x00);
> + em28xx_write_reg(dev, 0x7AC1, 0x1B);
> +
> + if (vin == EM2828X_COMPOSITE || vin == EM2828X_TELEVISION) {
> + em28xx_write_reg(dev, 0x38, 0x01);
> + em28xx_write_reg(dev, 0xB1, 0x70);
> + em28xx_write_reg(dev, 0xB3, 0x00);
> + em28xx_write_reg(dev, 0xB5, 0x00);
> + em28xx_write_reg(dev, 0x7A02, 0x4f);
> + } else { /* EM2828X_SVIDEO */
> + em28xx_write_reg(dev, 0x38, 0x00);
> + em28xx_write_reg(dev, 0xB1, 0x60);
> + em28xx_write_reg(dev, 0xB3, 0x10);
> + em28xx_write_reg(dev, 0xB5, 0x10);
> + em28xx_write_reg(dev, 0x7A02, 0x4e);
> + }
> +
> + em28xx_write_reg(dev, 0x7A3F, 0x01);
> + em28xx_write_reg(dev, 0x7A3F, 0x00);
> +}
> +EXPORT_SYMBOL_GPL(em2828X_decoder_vmux);
> +
> int em28xx_capture_start(struct em28xx *dev, int start)
> {
> int rc;
> @@ -628,6 +688,7 @@ int em28xx_capture_start(struct em28xx *dev, int start)
> dev->chip_id == CHIP_ID_EM2884 ||
> dev->chip_id == CHIP_ID_EM28174 ||
> dev->chip_id == CHIP_ID_EM28178) {
> +
> /* The Transport Stream Enable Register moved in em2874 */
> if (dev->dvb_xfer_bulk) {
> /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
> @@ -664,26 +725,87 @@ int em28xx_capture_start(struct em28xx *dev, int start)
> if (dev->is_webcam)
> rc = em28xx_write_reg(dev, 0x13, 0x0c);
>
> - /* Enable video capture */
> - rc = em28xx_write_reg(dev, 0x48, 0x00);
> - if (rc < 0)
> - return rc;
> + if (dev->mode == EM28XX_ANALOG_MODE) {
> + /* Enable video capture */
> + dev_dbg(&dev->intf->dev, "EM28XX_ANALOG_MODE 1\n");
> + rc = em28xx_write_reg(dev, 0x48, 0x00);
> + if (rc < 0)
> + return rc;
>
> - if (dev->mode == EM28XX_ANALOG_MODE)
> rc = em28xx_write_reg(dev,
> EM28XX_R12_VINENABLE,
> 0x67);
> - else
> - rc = em28xx_write_reg(dev,
> - EM28XX_R12_VINENABLE,
> - 0x37);
> +
> + } else if (dev->chip_id == CHIP_ID_EM2828X) {
> + dev_err(&dev->intf->dev, "%s() CHIP_ID_EM2828X\n", __func__);
> + /* The Transport Stream Enable Register moved in em2874 */
> + if (dev->dvb_xfer_bulk) {
> + /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
> + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> + EM2874_R5D_TS1_PKT_SIZE :
> + EM2874_R5E_TS2_PKT_SIZE,
> + 0xff);
> + } else {
> + /* ISOC Maximum Transfer Size = 188 * 5 */
> + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> + EM2874_R5D_TS1_PKT_SIZE :
> + EM2874_R5E_TS2_PKT_SIZE,
> + dev->dvb_max_pkt_size_isoc / 188);
> + }
> +
> + if (dev->ts == PRIMARY_TS)
> + rc = em28xx_write_reg_bits(dev,
> + EM2874_R5F_TS_ENABLE,
> + start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
> + EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
> + else
> + rc = em28xx_write_reg_bits(dev,
> + EM2874_R5F_TS_ENABLE,
> + start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
> + EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
These lines are REALLY long. Try something like this:
rc = em28xx_write_reg_bits(dev,
EM2874_R5F_TS_ENABLE,
start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
EM2874_TS2_CAPTURE_ENABLE |
EM2874_TS2_FILTER_ENABLE |
EM2874_TS2_NULL_DISCARD);
I noticed that there were pre-existing equally long lines in this function.
Try to do make the same change for those. At least keep it within 100 columns.
> + } else {
> + /* Enable video capture */
> + rc = em28xx_write_reg(dev, 0x48, 0x00);
> + if (rc < 0)
> + return rc;
> + rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
> + }
> +
> if (rc < 0)
> return rc;
>
> usleep_range(10000, 11000);
> } else {
> - /* disable video capture */
> - rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
> + if (dev->mode == EM28XX_DIGITAL_MODE && dev->chip_id == CHIP_ID_EM2828X) {
> + /* The Transport Stream Enable Register moved in em2874 */
> + if (dev->dvb_xfer_bulk) {
> + /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
> + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> + EM2874_R5D_TS1_PKT_SIZE :
> + EM2874_R5E_TS2_PKT_SIZE,
> + 0xff);
> + } else {
> + /* ISOC Maximum Transfer Size = 188 * 5 */
> + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> + EM2874_R5D_TS1_PKT_SIZE :
> + EM2874_R5E_TS2_PKT_SIZE,
> + dev->dvb_max_pkt_size_isoc / 188);
> + }
> +
> + if (dev->ts == PRIMARY_TS)
> + rc = em28xx_write_reg_bits(dev,
> + EM2874_R5F_TS_ENABLE,
> + start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
> + EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
> + else
> + rc = em28xx_write_reg_bits(dev,
> + EM2874_R5F_TS_ENABLE,
> + start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
> + EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
> + } else {
> + /* disable video capture */
> + rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
> + }
> }
> }
>
<snip>
> @@ -1366,8 +1519,40 @@ static int em28xx_s_ctrl(struct v4l2_ctrl *ctrl)
> return (ret < 0) ? ret : 0;
> }
>
> +static int em28xx_g_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct em28xx_v4l2 *v4l2 =
> + container_of(ctrl->handler, struct em28xx_v4l2, ctrl_handler);
> + struct em28xx *dev = v4l2->dev;
> + int ret = -EINVAL;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_LOCK_STATUS:
> + if (dev->board.decoder == EM28XX_BUILTIN) {
> + ctrl->val = em2828X_decoder_get_lock_status(dev);
> + ret = 0;
> + }
> + break;
> + }
> +
> + return (ret < 0) ? ret : 0;
> +}
> +
> static const struct v4l2_ctrl_ops em28xx_ctrl_ops = {
> .s_ctrl = em28xx_s_ctrl,
> + .g_volatile_ctrl = em28xx_g_ctrl,
> +};
> +
> +static const struct v4l2_ctrl_config em28xx_lock_status_config = {
> + .ops = &em28xx_ctrl_ops,
> + .id = V4L2_CID_LOCK_STATUS,
> + .name = "Lock Status",
> + .type = V4L2_CTRL_TYPE_BOOLEAN,
> + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
> + .min = 0,
> + .max = 1,
> + .step = 1,
> + .def = 0,
What does this control do? Why it is here? It's readonly, so I suspect it is
meant for debugging only?
Driver specific controls need to define a range in v4l2-controls.h (e.g. search
for V4L2_CID_USER_MALI_C55_BASE). And they need to be documented.
It might be better to add support for VIDIOC_LOG_STATUS to em28xx: there you can
just log the lock status. You have a lot more flexibility that way.
Regards,
Hans
> };
>
> static void size_to_scale(struct em28xx *dev,
> @@ -1586,6 +1771,9 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
> em28xx_resolution_set(dev);
> v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
>
> + if (dev->board.decoder == EM28XX_BUILTIN)
> + em2828X_decoder_set_std(dev, v4l2->norm);
> +
> return 0;
> }
>
> @@ -1829,6 +2017,11 @@ static int vidioc_g_tuner(struct file *file, void *priv,
>
> strscpy(t->name, "Tuner", sizeof(t->name));
>
> + t->type = V4L2_TUNER_ANALOG_TV;
> + t->capability = V4L2_TUNER_CAP_NORM;
> + t->rangehigh = 0xffffffffUL;
> + t->signal = 0xffff; /* LOCKED */
> +
> v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, g_tuner, t);
> return 0;
> }
> @@ -1978,7 +2171,7 @@ static int vidioc_querycap(struct file *file, void *priv,
> V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
> cap->capabilities |= V4L2_CAP_AUDIO;
> - if (dev->tuner_type != TUNER_ABSENT)
> + if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
> cap->capabilities |= V4L2_CAP_TUNER;
> if (video_is_registered(&v4l2->vbi_dev))
> cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
> @@ -2549,7 +2742,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> }
>
> hdl = &v4l2->ctrl_handler;
> - v4l2_ctrl_handler_init(hdl, 8);
> + v4l2_ctrl_handler_init(hdl, 9);
> v4l2->v4l2_dev.ctrl_handler = hdl;
>
> if (dev->is_webcam)
> @@ -2675,7 +2868,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> }
>
> /* set default norm */
> - v4l2->norm = V4L2_STD_PAL;
> + v4l2->norm = -1;
> v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
> v4l2->interlaced_fieldmode = EM28XX_INTERLACED_DEFAULT;
>
> @@ -2738,6 +2931,8 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> V4L2_CID_SHARPNESS,
> 0, 0x0f, 1, SHARPNESS_DEFAULT);
>
> + v4l2_ctrl_new_custom(hdl, &em28xx_lock_status_config, NULL);
> +
> /* Reset image controls */
> em28xx_colorlevels_set_default(dev);
> v4l2_ctrl_handler_setup(hdl);
> @@ -2755,10 +2950,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> V4L2_CAP_STREAMING;
> if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
> v4l2->vdev.device_caps |= V4L2_CAP_AUDIO;
> - if (dev->tuner_type != TUNER_ABSENT)
> + if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
> v4l2->vdev.device_caps |= V4L2_CAP_TUNER;
>
> -
> /* disable inapplicable ioctls */
> if (dev->is_webcam) {
> v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_QUERYSTD);
> @@ -2767,7 +2961,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> } else {
> v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_PARM);
> }
> - if (dev->tuner_type == TUNER_ABSENT) {
> + if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0) {
> v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_TUNER);
> v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_TUNER);
> v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_FREQUENCY);
> @@ -2778,6 +2972,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_AUDIO);
> }
>
> + if (dev->chip_id == CHIP_ID_EM2828X || dev->board.decoder == EM28XX_BUILTIN)
> + v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_ENUM_FRAMESIZES);
> +
> /* register v4l2 video video_device */
> ret = video_register_device(&v4l2->vdev, VFL_TYPE_VIDEO,
> video_nr[dev->devno]);
> @@ -2796,12 +2993,12 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> v4l2->vbi_dev.queue->lock = &v4l2->vb_vbi_queue_lock;
> v4l2->vbi_dev.device_caps = V4L2_CAP_STREAMING |
> V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE;
> - if (dev->tuner_type != TUNER_ABSENT)
> + if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0)
> v4l2->vbi_dev.device_caps |= V4L2_CAP_TUNER;
>
> /* disable inapplicable ioctls */
> v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_PARM);
> - if (dev->tuner_type == TUNER_ABSENT) {
> + if ((v4l2->vbi_dev.device_caps & V4L2_CAP_TUNER) == 0) {
> v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_TUNER);
> v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_TUNER);
> v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_FREQUENCY);
> diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
> index f3449c240d21..b6b8c4ae93af 100644
> --- a/drivers/media/usb/em28xx/em28xx.h
> +++ b/drivers/media/usb/em28xx/em28xx.h
> @@ -425,8 +425,15 @@ enum em28xx_decoder {
> EM28XX_NODECODER = 0,
> EM28XX_TVP5150,
> EM28XX_SAA711X,
> + EM28XX_BUILTIN,
> };
>
> +/* Built in decoder capture options */
> +#define V4L2_CID_LOCK_STATUS V4L2_CID_LASTP1
> +#define EM2828X_COMPOSITE 0
> +#define EM2828X_SVIDEO 1
> +#define EM2828X_TELEVISION 2
> +
> enum em28xx_sensor {
> EM28XX_NOSENSOR = 0,
> EM28XX_MT9V011,
> @@ -469,6 +476,12 @@ struct em28xx_button {
> bool inverted;
> };
>
> +enum em2828x_media_pads {
> + EM2828X_PAD_INPUT,
> + EM2828X_PAD_VID_OUT,
> + EM2828X_NUM_PADS
> +};
> +
> struct em28xx_board {
> char *name;
> int vchannels;
> @@ -593,6 +606,7 @@ struct em28xx_v4l2 {
>
> #ifdef CONFIG_MEDIA_CONTROLLER
> struct media_pad video_pad, vbi_pad;
> + struct media_pad decoder_pads[EM2828X_NUM_PADS];
> struct media_entity *decoder;
> #endif
> };
> @@ -752,6 +766,8 @@ struct em28xx {
> char *buf, int len);
> int (*em28xx_read_reg_req)(struct em28xx *dev, u8 req, u16 reg);
>
> + int (*em28xx_set_analog_freq)(struct em28xx *dev, u32 freq);
> +
> enum em28xx_mode mode;
>
> // Button state polling
> @@ -763,6 +779,7 @@ struct em28xx {
> // Snapshot button input device
> char snapshot_button_path[30]; // path of the input dev
> struct input_dev *sbutton_input_dev;
> + int analog_xfer_mode;
>
> #ifdef CONFIG_MEDIA_CONTROLLER
> struct media_device *media_dev;
> @@ -811,6 +828,8 @@ int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val);
> int em28xx_audio_analog_set(struct em28xx *dev);
> int em28xx_audio_setup(struct em28xx *dev);
>
> +void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin);
> +
> const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
> enum em28xx_led_role role);
> int em28xx_capture_start(struct em28xx *dev, int start);
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH V2 1/2] em28xx: Add support for Empia em2828X bridge
2026-03-16 15:12 ` Hans Verkuil
@ 2026-03-16 18:28 ` Bradford Love
0 siblings, 0 replies; 10+ messages in thread
From: Bradford Love @ 2026-03-16 18:28 UTC (permalink / raw)
To: Hans Verkuil; +Cc: linux-media
Hi Hans,
Thank you for the review, I'll make these changes now. The lock
control is a remnant of a customer implementation. I'll investigate
the log status ioctl and see if it makes sense to include or if I
should just remove the control.
Regards,
Bradford
On Mon, Mar 16, 2026 at 10:12 AM Hans Verkuil <hverkuil+cisco@kernel.org> wrote:
>
> Hi Brad,
>
> On 13/03/2026 19:37, Bradford Love wrote:
> > The empia em2828X usb bridge contains previous functionality,
> > but also contains an embedded video decoder. The implemented
> > capabilities include composite and s-video inputs, as well as
> > analog TV. Analog TV is expected in CVBS format, it must be
> > demodulated already.
> >
> > Media controller decoder entity is included so pipeline
> > verification passes and graph is properly constructed.
> >
> > Analog TV bits based off cx231xx driver.
>
> Nice, it's been a long time since there were substantial changes to em28xx.
>
> Review below...
>
> >
> > Signed-off-by: Bradford Love <brad@nextdimension.cc>
> > ---
> > Changes in v2:
> > - fixed kzalloc_obj compilation issue
> >
> > ---
> > drivers/media/usb/em28xx/em28xx-cards.c | 27 ++-
> > drivers/media/usb/em28xx/em28xx-core.c | 146 ++++++++++++++--
> > drivers/media/usb/em28xx/em28xx-dvb.c | 15 ++
> > drivers/media/usb/em28xx/em28xx-i2c.c | 2 +
> > drivers/media/usb/em28xx/em28xx-reg.h | 3 +
> > drivers/media/usb/em28xx/em28xx-video.c | 217 ++++++++++++++++++++++--
> > drivers/media/usb/em28xx/em28xx.h | 19 +++
> > 7 files changed, 405 insertions(+), 24 deletions(-)
>
> <snip>
>
> > diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
> > index 29a7f3f19b56..303f6f10fda5 100644
> > --- a/drivers/media/usb/em28xx/em28xx-core.c
> > +++ b/drivers/media/usb/em28xx/em28xx-core.c
> > @@ -499,7 +499,8 @@ int em28xx_audio_setup(struct em28xx *dev)
> > if (dev->chip_id == CHIP_ID_EM2870 ||
> > dev->chip_id == CHIP_ID_EM2874 ||
> > dev->chip_id == CHIP_ID_EM28174 ||
> > - dev->chip_id == CHIP_ID_EM28178) {
> > + dev->chip_id == CHIP_ID_EM28178 ||
> > + dev->chip_id == CHIP_ID_EM2828X) {
> > /* Digital only device - don't load any alsa module */
> > dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
> > dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
> > @@ -619,6 +620,65 @@ const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
> > }
> > EXPORT_SYMBOL_GPL(em28xx_find_led);
> >
> > +void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin)
> > +{
> > + switch (vin) {
> > + case EM2828X_TELEVISION:
> > + dev_dbg(&dev->intf->dev, "EM2828X_TELEVISION\n");
> > + break;
> > + case EM2828X_COMPOSITE:
> > + dev_dbg(&dev->intf->dev, "EM2828X_COMPOSITE\n");
> > + break;
> > + default:
> > + dev_dbg(&dev->intf->dev, "EM2828X_SVIDEO\n");
> > + break;
> > + };
> > +
> > + em28xx_write_reg(dev, 0x24, 0x00);
> > + em28xx_write_reg(dev, 0x25, 0x02);
> > + em28xx_write_reg(dev, 0x2E, 0x00);
> > +
> > + if (vin == EM2828X_TELEVISION) {
> > + em28xx_write_reg(dev, 0x7A0B, 0xfc);
> > + em28xx_write_reg(dev, 0xB6, 0x8F);
> > + em28xx_write_reg(dev, 0xB8, 0x01);
> > + } else {
> > + em28xx_write_reg(dev, 0x7A0B, 0x00);
> > + em28xx_write_reg(dev, 0xB6, 0x8F);
> > + em28xx_write_reg(dev, 0xB8, 0x00);
> > + }
> > +
> > + em28xx_write_reg(dev, 0x7A1C, 0x1E);
> > + em28xx_write_reg(dev, 0x7A1D, 0x99);
> > + em28xx_write_reg(dev, 0x7A1E, 0x99);
> > + em28xx_write_reg(dev, 0x7A1F, 0x9A);
> > + em28xx_write_reg(dev, 0x7A20, 0x3d);
> > + em28xx_write_reg(dev, 0x7A21, 0x3e);
> > + em28xx_write_reg(dev, 0x7A29, 0x00);
> > + em28xx_write_reg(dev, 0x7A2F, 0x52);
> > + em28xx_write_reg(dev, 0x7A40, 0x05);
> > + em28xx_write_reg(dev, 0x7A51, 0x00);
> > + em28xx_write_reg(dev, 0x7AC1, 0x1B);
> > +
> > + if (vin == EM2828X_COMPOSITE || vin == EM2828X_TELEVISION) {
> > + em28xx_write_reg(dev, 0x38, 0x01);
> > + em28xx_write_reg(dev, 0xB1, 0x70);
> > + em28xx_write_reg(dev, 0xB3, 0x00);
> > + em28xx_write_reg(dev, 0xB5, 0x00);
> > + em28xx_write_reg(dev, 0x7A02, 0x4f);
> > + } else { /* EM2828X_SVIDEO */
> > + em28xx_write_reg(dev, 0x38, 0x00);
> > + em28xx_write_reg(dev, 0xB1, 0x60);
> > + em28xx_write_reg(dev, 0xB3, 0x10);
> > + em28xx_write_reg(dev, 0xB5, 0x10);
> > + em28xx_write_reg(dev, 0x7A02, 0x4e);
> > + }
> > +
> > + em28xx_write_reg(dev, 0x7A3F, 0x01);
> > + em28xx_write_reg(dev, 0x7A3F, 0x00);
> > +}
> > +EXPORT_SYMBOL_GPL(em2828X_decoder_vmux);
> > +
> > int em28xx_capture_start(struct em28xx *dev, int start)
> > {
> > int rc;
> > @@ -628,6 +688,7 @@ int em28xx_capture_start(struct em28xx *dev, int start)
> > dev->chip_id == CHIP_ID_EM2884 ||
> > dev->chip_id == CHIP_ID_EM28174 ||
> > dev->chip_id == CHIP_ID_EM28178) {
> > +
> > /* The Transport Stream Enable Register moved in em2874 */
> > if (dev->dvb_xfer_bulk) {
> > /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
> > @@ -664,26 +725,87 @@ int em28xx_capture_start(struct em28xx *dev, int start)
> > if (dev->is_webcam)
> > rc = em28xx_write_reg(dev, 0x13, 0x0c);
> >
> > - /* Enable video capture */
> > - rc = em28xx_write_reg(dev, 0x48, 0x00);
> > - if (rc < 0)
> > - return rc;
> > + if (dev->mode == EM28XX_ANALOG_MODE) {
> > + /* Enable video capture */
> > + dev_dbg(&dev->intf->dev, "EM28XX_ANALOG_MODE 1\n");
> > + rc = em28xx_write_reg(dev, 0x48, 0x00);
> > + if (rc < 0)
> > + return rc;
> >
> > - if (dev->mode == EM28XX_ANALOG_MODE)
> > rc = em28xx_write_reg(dev,
> > EM28XX_R12_VINENABLE,
> > 0x67);
> > - else
> > - rc = em28xx_write_reg(dev,
> > - EM28XX_R12_VINENABLE,
> > - 0x37);
> > +
> > + } else if (dev->chip_id == CHIP_ID_EM2828X) {
> > + dev_err(&dev->intf->dev, "%s() CHIP_ID_EM2828X\n", __func__);
> > + /* The Transport Stream Enable Register moved in em2874 */
> > + if (dev->dvb_xfer_bulk) {
> > + /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
> > + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> > + EM2874_R5D_TS1_PKT_SIZE :
> > + EM2874_R5E_TS2_PKT_SIZE,
> > + 0xff);
> > + } else {
> > + /* ISOC Maximum Transfer Size = 188 * 5 */
> > + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> > + EM2874_R5D_TS1_PKT_SIZE :
> > + EM2874_R5E_TS2_PKT_SIZE,
> > + dev->dvb_max_pkt_size_isoc / 188);
> > + }
> > +
> > + if (dev->ts == PRIMARY_TS)
> > + rc = em28xx_write_reg_bits(dev,
> > + EM2874_R5F_TS_ENABLE,
> > + start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
> > + EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
> > + else
> > + rc = em28xx_write_reg_bits(dev,
> > + EM2874_R5F_TS_ENABLE,
> > + start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
> > + EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
>
> These lines are REALLY long. Try something like this:
>
> rc = em28xx_write_reg_bits(dev,
> EM2874_R5F_TS_ENABLE,
> start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
> EM2874_TS2_CAPTURE_ENABLE |
> EM2874_TS2_FILTER_ENABLE |
> EM2874_TS2_NULL_DISCARD);
>
> I noticed that there were pre-existing equally long lines in this function.
> Try to do make the same change for those. At least keep it within 100 columns.
>
> > + } else {
> > + /* Enable video capture */
> > + rc = em28xx_write_reg(dev, 0x48, 0x00);
> > + if (rc < 0)
> > + return rc;
> > + rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
> > + }
> > +
> > if (rc < 0)
> > return rc;
> >
> > usleep_range(10000, 11000);
> > } else {
> > - /* disable video capture */
> > - rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
> > + if (dev->mode == EM28XX_DIGITAL_MODE && dev->chip_id == CHIP_ID_EM2828X) {
> > + /* The Transport Stream Enable Register moved in em2874 */
> > + if (dev->dvb_xfer_bulk) {
> > + /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
> > + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> > + EM2874_R5D_TS1_PKT_SIZE :
> > + EM2874_R5E_TS2_PKT_SIZE,
> > + 0xff);
> > + } else {
> > + /* ISOC Maximum Transfer Size = 188 * 5 */
> > + em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
> > + EM2874_R5D_TS1_PKT_SIZE :
> > + EM2874_R5E_TS2_PKT_SIZE,
> > + dev->dvb_max_pkt_size_isoc / 188);
> > + }
> > +
> > + if (dev->ts == PRIMARY_TS)
> > + rc = em28xx_write_reg_bits(dev,
> > + EM2874_R5F_TS_ENABLE,
> > + start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
> > + EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
> > + else
> > + rc = em28xx_write_reg_bits(dev,
> > + EM2874_R5F_TS_ENABLE,
> > + start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
> > + EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
> > + } else {
> > + /* disable video capture */
> > + rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
> > + }
> > }
> > }
> >
>
> <snip>
>
> > @@ -1366,8 +1519,40 @@ static int em28xx_s_ctrl(struct v4l2_ctrl *ctrl)
> > return (ret < 0) ? ret : 0;
> > }
> >
> > +static int em28xx_g_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > + struct em28xx_v4l2 *v4l2 =
> > + container_of(ctrl->handler, struct em28xx_v4l2, ctrl_handler);
> > + struct em28xx *dev = v4l2->dev;
> > + int ret = -EINVAL;
> > +
> > + switch (ctrl->id) {
> > + case V4L2_CID_LOCK_STATUS:
> > + if (dev->board.decoder == EM28XX_BUILTIN) {
> > + ctrl->val = em2828X_decoder_get_lock_status(dev);
> > + ret = 0;
> > + }
> > + break;
> > + }
> > +
> > + return (ret < 0) ? ret : 0;
> > +}
> > +
> > static const struct v4l2_ctrl_ops em28xx_ctrl_ops = {
> > .s_ctrl = em28xx_s_ctrl,
> > + .g_volatile_ctrl = em28xx_g_ctrl,
> > +};
> > +
> > +static const struct v4l2_ctrl_config em28xx_lock_status_config = {
> > + .ops = &em28xx_ctrl_ops,
> > + .id = V4L2_CID_LOCK_STATUS,
> > + .name = "Lock Status",
> > + .type = V4L2_CTRL_TYPE_BOOLEAN,
> > + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
> > + .min = 0,
> > + .max = 1,
> > + .step = 1,
> > + .def = 0,
>
> What does this control do? Why it is here? It's readonly, so I suspect it is
> meant for debugging only?
>
> Driver specific controls need to define a range in v4l2-controls.h (e.g. search
> for V4L2_CID_USER_MALI_C55_BASE). And they need to be documented.
>
> It might be better to add support for VIDIOC_LOG_STATUS to em28xx: there you can
> just log the lock status. You have a lot more flexibility that way.
>
> Regards,
>
> Hans
>
> > };
> >
> > static void size_to_scale(struct em28xx *dev,
> > @@ -1586,6 +1771,9 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
> > em28xx_resolution_set(dev);
> > v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
> >
> > + if (dev->board.decoder == EM28XX_BUILTIN)
> > + em2828X_decoder_set_std(dev, v4l2->norm);
> > +
> > return 0;
> > }
> >
> > @@ -1829,6 +2017,11 @@ static int vidioc_g_tuner(struct file *file, void *priv,
> >
> > strscpy(t->name, "Tuner", sizeof(t->name));
> >
> > + t->type = V4L2_TUNER_ANALOG_TV;
> > + t->capability = V4L2_TUNER_CAP_NORM;
> > + t->rangehigh = 0xffffffffUL;
> > + t->signal = 0xffff; /* LOCKED */
> > +
> > v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, g_tuner, t);
> > return 0;
> > }
> > @@ -1978,7 +2171,7 @@ static int vidioc_querycap(struct file *file, void *priv,
> > V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> > if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
> > cap->capabilities |= V4L2_CAP_AUDIO;
> > - if (dev->tuner_type != TUNER_ABSENT)
> > + if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
> > cap->capabilities |= V4L2_CAP_TUNER;
> > if (video_is_registered(&v4l2->vbi_dev))
> > cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
> > @@ -2549,7 +2742,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> > }
> >
> > hdl = &v4l2->ctrl_handler;
> > - v4l2_ctrl_handler_init(hdl, 8);
> > + v4l2_ctrl_handler_init(hdl, 9);
> > v4l2->v4l2_dev.ctrl_handler = hdl;
> >
> > if (dev->is_webcam)
> > @@ -2675,7 +2868,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> > }
> >
> > /* set default norm */
> > - v4l2->norm = V4L2_STD_PAL;
> > + v4l2->norm = -1;
> > v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
> > v4l2->interlaced_fieldmode = EM28XX_INTERLACED_DEFAULT;
> >
> > @@ -2738,6 +2931,8 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> > V4L2_CID_SHARPNESS,
> > 0, 0x0f, 1, SHARPNESS_DEFAULT);
> >
> > + v4l2_ctrl_new_custom(hdl, &em28xx_lock_status_config, NULL);
> > +
> > /* Reset image controls */
> > em28xx_colorlevels_set_default(dev);
> > v4l2_ctrl_handler_setup(hdl);
> > @@ -2755,10 +2950,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> > V4L2_CAP_STREAMING;
> > if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
> > v4l2->vdev.device_caps |= V4L2_CAP_AUDIO;
> > - if (dev->tuner_type != TUNER_ABSENT)
> > + if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
> > v4l2->vdev.device_caps |= V4L2_CAP_TUNER;
> >
> > -
> > /* disable inapplicable ioctls */
> > if (dev->is_webcam) {
> > v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_QUERYSTD);
> > @@ -2767,7 +2961,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> > } else {
> > v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_PARM);
> > }
> > - if (dev->tuner_type == TUNER_ABSENT) {
> > + if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0) {
> > v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_TUNER);
> > v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_TUNER);
> > v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_FREQUENCY);
> > @@ -2778,6 +2972,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> > v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_AUDIO);
> > }
> >
> > + if (dev->chip_id == CHIP_ID_EM2828X || dev->board.decoder == EM28XX_BUILTIN)
> > + v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_ENUM_FRAMESIZES);
> > +
> > /* register v4l2 video video_device */
> > ret = video_register_device(&v4l2->vdev, VFL_TYPE_VIDEO,
> > video_nr[dev->devno]);
> > @@ -2796,12 +2993,12 @@ static int em28xx_v4l2_init(struct em28xx *dev)
> > v4l2->vbi_dev.queue->lock = &v4l2->vb_vbi_queue_lock;
> > v4l2->vbi_dev.device_caps = V4L2_CAP_STREAMING |
> > V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE;
> > - if (dev->tuner_type != TUNER_ABSENT)
> > + if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0)
> > v4l2->vbi_dev.device_caps |= V4L2_CAP_TUNER;
> >
> > /* disable inapplicable ioctls */
> > v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_PARM);
> > - if (dev->tuner_type == TUNER_ABSENT) {
> > + if ((v4l2->vbi_dev.device_caps & V4L2_CAP_TUNER) == 0) {
> > v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_TUNER);
> > v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_TUNER);
> > v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_FREQUENCY);
> > diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
> > index f3449c240d21..b6b8c4ae93af 100644
> > --- a/drivers/media/usb/em28xx/em28xx.h
> > +++ b/drivers/media/usb/em28xx/em28xx.h
> > @@ -425,8 +425,15 @@ enum em28xx_decoder {
> > EM28XX_NODECODER = 0,
> > EM28XX_TVP5150,
> > EM28XX_SAA711X,
> > + EM28XX_BUILTIN,
> > };
> >
> > +/* Built in decoder capture options */
> > +#define V4L2_CID_LOCK_STATUS V4L2_CID_LASTP1
> > +#define EM2828X_COMPOSITE 0
> > +#define EM2828X_SVIDEO 1
> > +#define EM2828X_TELEVISION 2
> > +
> > enum em28xx_sensor {
> > EM28XX_NOSENSOR = 0,
> > EM28XX_MT9V011,
> > @@ -469,6 +476,12 @@ struct em28xx_button {
> > bool inverted;
> > };
> >
> > +enum em2828x_media_pads {
> > + EM2828X_PAD_INPUT,
> > + EM2828X_PAD_VID_OUT,
> > + EM2828X_NUM_PADS
> > +};
> > +
> > struct em28xx_board {
> > char *name;
> > int vchannels;
> > @@ -593,6 +606,7 @@ struct em28xx_v4l2 {
> >
> > #ifdef CONFIG_MEDIA_CONTROLLER
> > struct media_pad video_pad, vbi_pad;
> > + struct media_pad decoder_pads[EM2828X_NUM_PADS];
> > struct media_entity *decoder;
> > #endif
> > };
> > @@ -752,6 +766,8 @@ struct em28xx {
> > char *buf, int len);
> > int (*em28xx_read_reg_req)(struct em28xx *dev, u8 req, u16 reg);
> >
> > + int (*em28xx_set_analog_freq)(struct em28xx *dev, u32 freq);
> > +
> > enum em28xx_mode mode;
> >
> > // Button state polling
> > @@ -763,6 +779,7 @@ struct em28xx {
> > // Snapshot button input device
> > char snapshot_button_path[30]; // path of the input dev
> > struct input_dev *sbutton_input_dev;
> > + int analog_xfer_mode;
> >
> > #ifdef CONFIG_MEDIA_CONTROLLER
> > struct media_device *media_dev;
> > @@ -811,6 +828,8 @@ int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val);
> > int em28xx_audio_analog_set(struct em28xx *dev);
> > int em28xx_audio_setup(struct em28xx *dev);
> >
> > +void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin);
> > +
> > const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
> > enum em28xx_led_role role);
> > int em28xx_capture_start(struct em28xx *dev, int start);
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/2] media: em28xx: Add support for Empia em2828X bridge
2026-03-12 22:49 ` [PATCH 1/2] em28xx: Add support for Empia em2828X bridge Bradford Love
2026-03-13 18:37 ` [PATCH V2 " Bradford Love
@ 2026-03-17 20:45 ` Bradford Love
2026-03-18 16:21 ` [PATCH 1/2] " kernel test robot
2026-03-18 19:12 ` kernel test robot
3 siblings, 0 replies; 10+ messages in thread
From: Bradford Love @ 2026-03-17 20:45 UTC (permalink / raw)
To: linux-media, hverkuil+cisco; +Cc: Bradford Love
The empia em2828X usb bridge contains previous functionality,
but also contains an embedded video decoder. The implemented
capabilities include composite and s-video inputs, as well as
analog TV. Analog TV is expected in CVBS format, it must be
demodulated already.
Media controller decoder entity is included so pipeline
verification passes and graph is properly constructed.
Analog TV bits based off cx231xx driver.
Signed-off-by: Bradford Love <brad@nextdimension.cc>
---
Changes in v3:
- Fixed line length issues
- Removed remnant custom control
- Prune debug statements
- Add media: tag to subject
Changes in v2:
- fixed kzalloc_obj compilation issue
drivers/media/usb/em28xx/em28xx-cards.c | 27 +++-
drivers/media/usb/em28xx/em28xx-core.c | 160 ++++++++++++++++++++--
drivers/media/usb/em28xx/em28xx-dvb.c | 15 +++
drivers/media/usb/em28xx/em28xx-i2c.c | 2 +
drivers/media/usb/em28xx/em28xx-reg.h | 1 +
drivers/media/usb/em28xx/em28xx-video.c | 172 ++++++++++++++++++++++--
drivers/media/usb/em28xx/em28xx.h | 18 +++
7 files changed, 369 insertions(+), 26 deletions(-)
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index d7075ebabceb..67266bddb713 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -3633,6 +3633,11 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev,
}
/* NOTE: the em2820 is used in webcams, too ! */
break;
+ case CHIP_ID_EM2828X:
+ chip_name = "em2828X";
+ dev->wait_after_write = 0;
+ dev->eeprom_addrwidth_16bit = 1;
+ break;
case CHIP_ID_EM2840:
chip_name = "em2840";
break;
@@ -3791,6 +3796,7 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
* 0x84 bulk => analog or digital**
* 0x85 isoc => digital TS2
* 0x85 bulk => digital TS2
+ * 0x8a isoc => digital video
* (*: audio should always be isoc)
* (**: analog, if ep 0x82 is isoc, otherwise digital)
*
@@ -3814,6 +3820,8 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
/* Only inspect input endpoints */
switch (e->bEndpointAddress) {
+ case 0x81: /* unknown function */
+ return;
case 0x82:
*has_video = true;
if (usb_endpoint_xfer_isoc(e)) {
@@ -3831,7 +3839,10 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
"error: skipping audio endpoint 0x83, because it uses bulk transfers !\n");
return;
case 0x84:
- if (*has_video && (usb_endpoint_xfer_bulk(e))) {
+ if (*has_dvb && (usb_endpoint_xfer_bulk(e))) {
+ *has_dvb = true;
+ dev->dvb_ep_bulk = e->bEndpointAddress;
+ } else if (*has_video && (usb_endpoint_xfer_bulk(e))) {
dev->analog_ep_bulk = e->bEndpointAddress;
} else {
if (usb_endpoint_xfer_isoc(e)) {
@@ -3865,7 +3876,17 @@ static void em28xx_check_usb_descriptor(struct em28xx *dev,
dev->dvb_ep_bulk_ts2 = e->bEndpointAddress;
}
return;
- }
+ case 0x8a:
+ *has_video = true;
+ *has_dvb = true;
+ if (usb_endpoint_xfer_isoc(e)) {
+ dev->analog_ep_isoc = e->bEndpointAddress;
+ dev->alt_max_pkt_size_isoc[alt] = size;
+ } else if (usb_endpoint_xfer_bulk(e)) {
+ dev->analog_ep_bulk = e->bEndpointAddress;
+ }
+ return;
+ };
}
/*
@@ -4047,6 +4068,8 @@ static int em28xx_usb_probe(struct usb_interface *intf,
try_bulk = 1;
else
try_bulk = 0;
+ } else if (dev->board.decoder == EM28XX_BUILTIN && dev->analog_xfer_mode) {
+ try_bulk = 1;
} else {
try_bulk = usb_xfer_mode > 0;
}
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
index 29a7f3f19b56..ea433bad4410 100644
--- a/drivers/media/usb/em28xx/em28xx-core.c
+++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -499,7 +499,8 @@ int em28xx_audio_setup(struct em28xx *dev)
if (dev->chip_id == CHIP_ID_EM2870 ||
dev->chip_id == CHIP_ID_EM2874 ||
dev->chip_id == CHIP_ID_EM28174 ||
- dev->chip_id == CHIP_ID_EM28178) {
+ dev->chip_id == CHIP_ID_EM28178 ||
+ dev->chip_id == CHIP_ID_EM2828X) {
/* Digital only device - don't load any alsa module */
dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
@@ -619,6 +620,65 @@ const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
}
EXPORT_SYMBOL_GPL(em28xx_find_led);
+void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin)
+{
+ switch (vin) {
+ case EM2828X_TELEVISION:
+ dev_dbg(&dev->intf->dev, "EM2828X_TELEVISION\n");
+ break;
+ case EM2828X_COMPOSITE:
+ dev_dbg(&dev->intf->dev, "EM2828X_COMPOSITE\n");
+ break;
+ default:
+ dev_dbg(&dev->intf->dev, "EM2828X_SVIDEO\n");
+ break;
+ };
+
+ em28xx_write_reg(dev, 0x24, 0x00);
+ em28xx_write_reg(dev, 0x25, 0x02);
+ em28xx_write_reg(dev, 0x2E, 0x00);
+
+ if (vin == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A0B, 0xfc);
+ em28xx_write_reg(dev, 0xB6, 0x8F);
+ em28xx_write_reg(dev, 0xB8, 0x01);
+ } else {
+ em28xx_write_reg(dev, 0x7A0B, 0x00);
+ em28xx_write_reg(dev, 0xB6, 0x8F);
+ em28xx_write_reg(dev, 0xB8, 0x00);
+ }
+
+ em28xx_write_reg(dev, 0x7A1C, 0x1E);
+ em28xx_write_reg(dev, 0x7A1D, 0x99);
+ em28xx_write_reg(dev, 0x7A1E, 0x99);
+ em28xx_write_reg(dev, 0x7A1F, 0x9A);
+ em28xx_write_reg(dev, 0x7A20, 0x3d);
+ em28xx_write_reg(dev, 0x7A21, 0x3e);
+ em28xx_write_reg(dev, 0x7A29, 0x00);
+ em28xx_write_reg(dev, 0x7A2F, 0x52);
+ em28xx_write_reg(dev, 0x7A40, 0x05);
+ em28xx_write_reg(dev, 0x7A51, 0x00);
+ em28xx_write_reg(dev, 0x7AC1, 0x1B);
+
+ if (vin == EM2828X_COMPOSITE || vin == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x38, 0x01);
+ em28xx_write_reg(dev, 0xB1, 0x70);
+ em28xx_write_reg(dev, 0xB3, 0x00);
+ em28xx_write_reg(dev, 0xB5, 0x00);
+ em28xx_write_reg(dev, 0x7A02, 0x4f);
+ } else { /* EM2828X_SVIDEO */
+ em28xx_write_reg(dev, 0x38, 0x00);
+ em28xx_write_reg(dev, 0xB1, 0x60);
+ em28xx_write_reg(dev, 0xB3, 0x10);
+ em28xx_write_reg(dev, 0xB5, 0x10);
+ em28xx_write_reg(dev, 0x7A02, 0x4e);
+ }
+
+ em28xx_write_reg(dev, 0x7A3F, 0x01);
+ em28xx_write_reg(dev, 0x7A3F, 0x00);
+}
+EXPORT_SYMBOL_GPL(em2828X_decoder_vmux);
+
int em28xx_capture_start(struct em28xx *dev, int start)
{
int rc;
@@ -628,6 +688,7 @@ int em28xx_capture_start(struct em28xx *dev, int start)
dev->chip_id == CHIP_ID_EM2884 ||
dev->chip_id == CHIP_ID_EM28174 ||
dev->chip_id == CHIP_ID_EM28178) {
+
/* The Transport Stream Enable Register moved in em2874 */
if (dev->dvb_xfer_bulk) {
/* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
@@ -646,12 +707,16 @@ int em28xx_capture_start(struct em28xx *dev, int start)
rc = em28xx_write_reg_bits(dev,
EM2874_R5F_TS_ENABLE,
start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
- EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
+ EM2874_TS1_CAPTURE_ENABLE |
+ EM2874_TS1_FILTER_ENABLE |
+ EM2874_TS1_NULL_DISCARD);
else
rc = em28xx_write_reg_bits(dev,
EM2874_R5F_TS_ENABLE,
start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
- EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
+ EM2874_TS2_CAPTURE_ENABLE |
+ EM2874_TS2_FILTER_ENABLE |
+ EM2874_TS2_NULL_DISCARD);
} else {
/* FIXME: which is the best order? */
/* video registers are sampled by VREF */
@@ -664,26 +729,93 @@ int em28xx_capture_start(struct em28xx *dev, int start)
if (dev->is_webcam)
rc = em28xx_write_reg(dev, 0x13, 0x0c);
- /* Enable video capture */
- rc = em28xx_write_reg(dev, 0x48, 0x00);
- if (rc < 0)
- return rc;
+ if (dev->mode == EM28XX_ANALOG_MODE) {
+ /* Enable video capture */
+ rc = em28xx_write_reg(dev, 0x48, 0x00);
+ if (rc < 0)
+ return rc;
- if (dev->mode == EM28XX_ANALOG_MODE)
rc = em28xx_write_reg(dev,
EM28XX_R12_VINENABLE,
0x67);
- else
- rc = em28xx_write_reg(dev,
- EM28XX_R12_VINENABLE,
- 0x37);
+
+ } else if (dev->chip_id == CHIP_ID_EM2828X) {
+ /* The Transport Stream Enable Register moved in em2874 */
+ if (dev->dvb_xfer_bulk) {
+ /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ 0xff);
+ } else {
+ /* ISOC Maximum Transfer Size = 188 * 5 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ dev->dvb_max_pkt_size_isoc / 188);
+ }
+
+ if (dev->ts == PRIMARY_TS)
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
+ EM2874_TS1_CAPTURE_ENABLE |
+ EM2874_TS1_FILTER_ENABLE |
+ EM2874_TS1_NULL_DISCARD);
+ else
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
+ EM2874_TS2_CAPTURE_ENABLE |
+ EM2874_TS2_FILTER_ENABLE |
+ EM2874_TS2_NULL_DISCARD);
+ } else {
+ /* Enable video capture */
+ rc = em28xx_write_reg(dev, 0x48, 0x00);
+ if (rc < 0)
+ return rc;
+ rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
+ }
+
if (rc < 0)
return rc;
usleep_range(10000, 11000);
} else {
- /* disable video capture */
- rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
+ if (dev->mode == EM28XX_DIGITAL_MODE && dev->chip_id == CHIP_ID_EM2828X) {
+ /* The Transport Stream Enable Register moved in em2874 */
+ if (dev->dvb_xfer_bulk) {
+ /* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ 0xff);
+ } else {
+ /* ISOC Maximum Transfer Size = 188 * 5 */
+ em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+ EM2874_R5D_TS1_PKT_SIZE :
+ EM2874_R5E_TS2_PKT_SIZE,
+ dev->dvb_max_pkt_size_isoc / 188);
+ }
+
+ if (dev->ts == PRIMARY_TS)
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
+ EM2874_TS1_CAPTURE_ENABLE |
+ EM2874_TS1_FILTER_ENABLE |
+ EM2874_TS1_NULL_DISCARD);
+ else
+ rc = em28xx_write_reg_bits(dev,
+ EM2874_R5F_TS_ENABLE,
+ start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
+ EM2874_TS2_CAPTURE_ENABLE |
+ EM2874_TS2_FILTER_ENABLE |
+ EM2874_TS2_NULL_DISCARD);
+ } else {
+ /* disable video capture */
+ rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
+ }
}
}
diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c
index 2eb9a88e595e..395d7a2c4354 100644
--- a/drivers/media/usb/em28xx/em28xx-dvb.c
+++ b/drivers/media/usb/em28xx/em28xx-dvb.c
@@ -296,6 +296,21 @@ static int em28xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
return em28xx_set_mode(dev, EM28XX_SUSPEND);
}
+static int em28xx_set_analog_freq(struct em28xx *dev, u32 freq)
+{
+ const struct dvb_tuner_ops *dops = &dev->dvb->fe[0]->ops.tuner_ops;
+
+ if (dops->set_analog_params != NULL) {
+ struct analog_parameters params;
+
+ params.frequency = freq;
+ params.std = dev->v4l2->norm;
+ params.mode = 0;
+
+ dops->set_analog_params(dev->dvb->fe[0], ¶ms);
+ }
+ return 0;
+}
/* ------------------------------------------------------------------ */
static struct lgdt330x_config em2880_lgdt3303_dev = {
diff --git a/drivers/media/usb/em28xx/em28xx-i2c.c b/drivers/media/usb/em28xx/em28xx-i2c.c
index a7eb11f7fb34..f0a901c3b49a 100644
--- a/drivers/media/usb/em28xx/em28xx-i2c.c
+++ b/drivers/media/usb/em28xx/em28xx-i2c.c
@@ -864,6 +864,8 @@ static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned int bus,
le16_to_cpu(dev_config->string2),
le16_to_cpu(dev_config->string3));
+ dev->analog_xfer_mode = data[67] & 0x01;
+
return 0;
error:
diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h
index d7c60862874a..68a0fcc2fa72 100644
--- a/drivers/media/usb/em28xx/em28xx-reg.h
+++ b/drivers/media/usb/em28xx/em28xx-reg.h
@@ -283,6 +283,7 @@ enum em28xx_chip_id {
CHIP_ID_EM2884 = 68,
CHIP_ID_EM28174 = 113,
CHIP_ID_EM28178 = 114,
+ CHIP_ID_EM2828X = 148,
};
/*
diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c
index b0c184f237a7..b8d3c39ee4b2 100644
--- a/drivers/media/usb/em28xx/em28xx-video.c
+++ b/drivers/media/usb/em28xx/em28xx-video.c
@@ -161,13 +161,19 @@ static int em28xx_vbi_supported(struct em28xx *dev)
/* FIXME: check subdevices for VBI support */
if (dev->chip_id == CHIP_ID_EM2860 ||
- dev->chip_id == CHIP_ID_EM2883)
+ dev->chip_id == CHIP_ID_EM2883 ||
+ dev->board.decoder == EM28XX_BUILTIN)
return 1;
/* Version of em28xx that does not support VBI */
return 0;
}
+static int em28xx_analogtv_supported(struct em28xx *dev)
+{
+ return 0;
+}
+
/*
* em28xx_wake_i2c()
* configure i2c attached devices
@@ -344,6 +350,109 @@ static int em28xx_resolution_set(struct em28xx *dev)
return em28xx_scaler_set(dev, v4l2->hscale, v4l2->vscale);
}
+static void em2828X_decoder_set_std(struct em28xx *dev, v4l2_std_id norm)
+{
+ if (norm & V4L2_STD_525_60) {
+ dev_dbg(&dev->intf->dev, "V4L2_STD_525_60");
+ em28xx_write_reg(dev, 0x7A01, 0x0d); // 0x05
+ em28xx_write_reg(dev, 0x7A04, 0xDD);
+ em28xx_write_reg(dev, 0x7A07, 0x60);
+ em28xx_write_reg(dev, 0x7A08, 0x7A);
+ em28xx_write_reg(dev, 0x7A09, 0x02);
+ em28xx_write_reg(dev, 0x7A0A, 0x7C);
+ em28xx_write_reg(dev, 0x7A0C, 0x8A);
+ em28xx_write_reg(dev, 0x7A0F, 0x1C);
+ em28xx_write_reg(dev, 0x7A18, 0x20);
+ em28xx_write_reg(dev, 0x7A19, 0x74);
+ em28xx_write_reg(dev, 0x7A1A, 0x5D);
+ em28xx_write_reg(dev, 0x7A1B, 0x17);
+ em28xx_write_reg(dev, 0x7A2E, 0x85);
+ em28xx_write_reg(dev, 0x7A31, 0x63);
+ em28xx_write_reg(dev, 0x7A82, 0x42);
+ em28xx_write_reg(dev, 0x7AC0, 0xD4);
+
+ if (INPUT(dev->ctl_input)->vmux == EM2828X_COMPOSITE) {
+ em28xx_write_reg(dev, 0x7A00, 0x00);
+ em28xx_write_reg(dev, 0x7A03, 0x00);
+ em28xx_write_reg(dev, 0x7A30, 0x22);
+ em28xx_write_reg(dev, 0x7A80, 0x03);
+ } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A17, 0xc3);
+ em28xx_write_reg(dev, 0x7A31, 0x62); // BRL 0x63
+ em28xx_write_reg(dev, 0x7A82, 0x42);
+ em28xx_write_reg(dev, 0x7AC0, 0xD4);
+ em28xx_write_reg(dev, 0x7A00, 0x00);
+ em28xx_write_reg(dev, 0x7A03, 0x00);
+ em28xx_write_reg(dev, 0x7A30, 0x20);
+ em28xx_write_reg(dev, 0x7A80, 0x00);
+
+ em28xx_write_reg(dev, 0x7A50, 0xdd);
+ em28xx_write_reg(dev, 0x7A5d, 0x0e);
+ em28xx_write_reg(dev, 0x7A5e, 0xea);
+ em28xx_write_reg(dev, 0x7A60, 0x64);
+ em28xx_write_reg(dev, 0x7A67, 0x5a);
+ } else {
+ em28xx_write_reg(dev, 0x7A00, 0x01);
+ em28xx_write_reg(dev, 0x7A03, 0x03);
+ em28xx_write_reg(dev, 0x7A30, 0x20);
+ em28xx_write_reg(dev, 0x7A80, 0x04);
+ }
+ } else if (norm & V4L2_STD_625_50) {
+ dev_dbg(&dev->intf->dev, "V4L2_STD_625_50");
+ em28xx_write_reg(dev, 0x7A04, 0xDC);
+ em28xx_write_reg(dev, 0x7A0C, 0x67);
+ em28xx_write_reg(dev, 0x7A0F, 0x1C);
+ em28xx_write_reg(dev, 0x7A18, 0x28);
+ em28xx_write_reg(dev, 0x7A19, 0x32);
+ em28xx_write_reg(dev, 0x7A1A, 0xB9);
+ em28xx_write_reg(dev, 0x7A1B, 0x86);
+ em28xx_write_reg(dev, 0x7A31, 0xC3);
+ em28xx_write_reg(dev, 0x7A82, 0x52);
+
+ if (INPUT(dev->ctl_input)->vmux == EM2828X_COMPOSITE) {
+ em28xx_write_reg(dev, 0x7A00, 0x32);
+ em28xx_write_reg(dev, 0x7A01, 0x10);
+ em28xx_write_reg(dev, 0x7A03, 0x06);
+ em28xx_write_reg(dev, 0x7A07, 0x2f);
+ em28xx_write_reg(dev, 0x7A08, 0x77);
+ em28xx_write_reg(dev, 0x7A09, 0x0f);
+ em28xx_write_reg(dev, 0x7A0A, 0x8c);
+ em28xx_write_reg(dev, 0x7A20, 0x3d);
+ em28xx_write_reg(dev, 0x7A2E, 0x88);
+ em28xx_write_reg(dev, 0x7A30, 0x2c);
+ em28xx_write_reg(dev, 0x7A80, 0x07);
+ } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) {
+ em28xx_write_reg(dev, 0x7A00, 0x32);
+ em28xx_write_reg(dev, 0x7A03, 0x09);
+ em28xx_write_reg(dev, 0x7A30, 0x2a);
+ em28xx_write_reg(dev, 0x7A80, 0x03);
+ em28xx_write_reg(dev, 0x7A20, 0x35);
+ em28xx_write_reg(dev, 0x7A2e, 0x88);
+ em28xx_write_reg(dev, 0x7A53, 0xcc);
+ em28xx_write_reg(dev, 0x7A5d, 0x16);
+ em28xx_write_reg(dev, 0x7A5e, 0x50);
+ em28xx_write_reg(dev, 0x7A60, 0xb4);
+ em28xx_write_reg(dev, 0x7A67, 0x64);
+ } else {
+ em28xx_write_reg(dev, 0x7A00, 0x33);
+ em28xx_write_reg(dev, 0x7A01, 0x04);
+ em28xx_write_reg(dev, 0x7A03, 0x04);
+ em28xx_write_reg(dev, 0x7A07, 0x20);
+ em28xx_write_reg(dev, 0x7A08, 0x6a);
+ em28xx_write_reg(dev, 0x7A09, 0x16);
+ em28xx_write_reg(dev, 0x7A0A, 0x80);
+ em28xx_write_reg(dev, 0x7A2E, 0x8a);
+ em28xx_write_reg(dev, 0x7A30, 0x26);
+ em28xx_write_reg(dev, 0x7A80, 0x08);
+ }
+ } else {
+ dev_err(&dev->intf->dev, "%s() Unsupported STD: %X", __func__, (unsigned int)norm);
+ }
+
+ em28xx_write_reg(dev, 0x7A3F, 0x01);
+ em28xx_write_reg(dev, 0x7A3F, 0x00);
+}
+
/* Set USB alternate setting for analog video */
static int em28xx_set_alternate(struct em28xx *dev)
{
@@ -880,6 +989,12 @@ static void em28xx_v4l2_media_release(struct em28xx *dev)
#ifdef CONFIG_MEDIA_CONTROLLER
int i;
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ media_device_unregister_entity(dev->v4l2->decoder);
+ kfree(dev->v4l2->decoder);
+ dev->v4l2->decoder = NULL;
+ }
+
for (i = 0; i < MAX_EM28XX_INPUT; i++) {
if (!INPUT(i)->type)
return;
@@ -1003,7 +1118,7 @@ static void em28xx_v4l2_create_entities(struct em28xx *dev)
ent->function = MEDIA_ENT_F_CONN_SVIDEO;
break;
default: /* EM28XX_VMUX_TELEVISION or EM28XX_RADIO */
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
ent->function = MEDIA_ENT_F_CONN_RF;
break;
}
@@ -1018,6 +1133,26 @@ static void em28xx_v4l2_create_entities(struct em28xx *dev)
dev_err(&dev->intf->dev,
"failed to register input entity %d!\n", i);
}
+
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ v4l2->decoder_pads[EM2828X_PAD_INPUT].flags = MEDIA_PAD_FL_SINK;
+ v4l2->decoder_pads[EM2828X_PAD_INPUT].sig_type = PAD_SIGNAL_ANALOG;
+ v4l2->decoder_pads[EM2828X_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE;
+ v4l2->decoder_pads[EM2828X_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV;
+
+ v4l2->decoder = kzalloc_obj(*v4l2->decoder);
+ v4l2->decoder->name = "em2828x_builtin";
+ v4l2->decoder->function = MEDIA_ENT_F_ATV_DECODER;
+
+ ret = media_entity_pads_init(v4l2->decoder, EM2828X_NUM_PADS, &v4l2->decoder_pads[0]);
+ if (ret < 0)
+ dev_err(&dev->intf->dev, "failed to initialize decoder pads %d!\n", ret);
+
+ ret = media_device_register_entity(dev->media_dev, v4l2->decoder);
+ if (ret < 0)
+ dev_err(&dev->intf->dev, "failed to register decoder entity %d!\n", ret);
+ }
+
#endif
}
@@ -1297,6 +1432,13 @@ static void video_mux(struct em28xx *dev, int index)
MSP_OUTPUT(MSP_SC_IN_DSP_SCART1), 0);
}
+ if (dev->board.decoder == EM28XX_BUILTIN) {
+ em2828X_decoder_vmux(dev, INPUT(index)->vmux);
+ em2828X_decoder_set_std(dev, dev->v4l2->norm);
+
+ em28xx_gpio_set(dev, INPUT(dev->ctl_input)->gpio);
+ }
+
if (dev->board.adecoder != EM28XX_NOADECODER) {
v4l2_device_call_all(v4l2_dev, 0, audio, s_routing,
dev->ctl_ainput, dev->ctl_aoutput, 0);
@@ -1586,6 +1728,9 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
em28xx_resolution_set(dev);
v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
+ if (dev->board.decoder == EM28XX_BUILTIN)
+ em2828X_decoder_set_std(dev, v4l2->norm);
+
return 0;
}
@@ -1829,6 +1974,11 @@ static int vidioc_g_tuner(struct file *file, void *priv,
strscpy(t->name, "Tuner", sizeof(t->name));
+ t->type = V4L2_TUNER_ANALOG_TV;
+ t->capability = V4L2_TUNER_CAP_NORM;
+ t->rangehigh = 0xffffffffUL;
+ t->signal = 0xffff; /* LOCKED */
+
v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, g_tuner, t);
return 0;
}
@@ -1978,7 +2128,7 @@ static int vidioc_querycap(struct file *file, void *priv,
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
cap->capabilities |= V4L2_CAP_AUDIO;
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
cap->capabilities |= V4L2_CAP_TUNER;
if (video_is_registered(&v4l2->vbi_dev))
cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
@@ -2549,7 +2699,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
}
hdl = &v4l2->ctrl_handler;
- v4l2_ctrl_handler_init(hdl, 8);
+ v4l2_ctrl_handler_init(hdl, 9);
v4l2->v4l2_dev.ctrl_handler = hdl;
if (dev->is_webcam)
@@ -2675,7 +2825,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
}
/* set default norm */
- v4l2->norm = V4L2_STD_PAL;
+ v4l2->norm = -1;
v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
v4l2->interlaced_fieldmode = EM28XX_INTERLACED_DEFAULT;
@@ -2755,10 +2905,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
V4L2_CAP_STREAMING;
if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
v4l2->vdev.device_caps |= V4L2_CAP_AUDIO;
- if (dev->tuner_type != TUNER_ABSENT)
+ if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
v4l2->vdev.device_caps |= V4L2_CAP_TUNER;
-
/* disable inapplicable ioctls */
if (dev->is_webcam) {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_QUERYSTD);
@@ -2767,7 +2916,7 @@ static int em28xx_v4l2_init(struct em28xx *dev)
} else {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_PARM);
}
- if (dev->tuner_type == TUNER_ABSENT) {
+ if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0) {
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_TUNER);
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_TUNER);
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_FREQUENCY);
@@ -2778,6 +2927,9 @@ static int em28xx_v4l2_init(struct em28xx *dev)
v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_AUDIO);
}
+ if (dev->chip_id == CHIP_ID_EM2828X || dev->board.decoder == EM28XX_BUILTIN)
+ v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_ENUM_FRAMESIZES);
+
/* register v4l2 video video_device */
ret = video_register_device(&v4l2->vdev, VFL_TYPE_VIDEO,
video_nr[dev->devno]);
@@ -2796,12 +2948,12 @@ static int em28xx_v4l2_init(struct em28xx *dev)
v4l2->vbi_dev.queue->lock = &v4l2->vb_vbi_queue_lock;
v4l2->vbi_dev.device_caps = V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE;
- if (dev->tuner_type != TUNER_ABSENT)
+ if ((v4l2->vdev.device_caps & V4L2_CAP_TUNER) == 0)
v4l2->vbi_dev.device_caps |= V4L2_CAP_TUNER;
/* disable inapplicable ioctls */
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_PARM);
- if (dev->tuner_type == TUNER_ABSENT) {
+ if ((v4l2->vbi_dev.device_caps & V4L2_CAP_TUNER) == 0) {
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_TUNER);
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_TUNER);
v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_FREQUENCY);
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
index f3449c240d21..b77357f71cf3 100644
--- a/drivers/media/usb/em28xx/em28xx.h
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -425,8 +425,14 @@ enum em28xx_decoder {
EM28XX_NODECODER = 0,
EM28XX_TVP5150,
EM28XX_SAA711X,
+ EM28XX_BUILTIN,
};
+/* Built in decoder capture options */
+#define EM2828X_COMPOSITE 0
+#define EM2828X_SVIDEO 1
+#define EM2828X_TELEVISION 2
+
enum em28xx_sensor {
EM28XX_NOSENSOR = 0,
EM28XX_MT9V011,
@@ -469,6 +475,12 @@ struct em28xx_button {
bool inverted;
};
+enum em2828x_media_pads {
+ EM2828X_PAD_INPUT,
+ EM2828X_PAD_VID_OUT,
+ EM2828X_NUM_PADS
+};
+
struct em28xx_board {
char *name;
int vchannels;
@@ -593,6 +605,7 @@ struct em28xx_v4l2 {
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_pad video_pad, vbi_pad;
+ struct media_pad decoder_pads[EM2828X_NUM_PADS];
struct media_entity *decoder;
#endif
};
@@ -752,6 +765,8 @@ struct em28xx {
char *buf, int len);
int (*em28xx_read_reg_req)(struct em28xx *dev, u8 req, u16 reg);
+ int (*em28xx_set_analog_freq)(struct em28xx *dev, u32 freq);
+
enum em28xx_mode mode;
// Button state polling
@@ -763,6 +778,7 @@ struct em28xx {
// Snapshot button input device
char snapshot_button_path[30]; // path of the input dev
struct input_dev *sbutton_input_dev;
+ int analog_xfer_mode;
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_device *media_dev;
@@ -811,6 +827,8 @@ int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val);
int em28xx_audio_analog_set(struct em28xx *dev);
int em28xx_audio_setup(struct em28xx *dev);
+void em2828X_decoder_vmux(struct em28xx *dev, unsigned int vin);
+
const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
enum em28xx_led_role role);
int em28xx_capture_start(struct em28xx *dev, int start);
--
2.35.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/2] media: em28xx: Add Hauppauge USB Live2
2026-03-12 22:49 ` [PATCH 2/2] em28xx: Add Hauppauge USB Live2 Bradford Love
@ 2026-03-17 20:46 ` Bradford Love
0 siblings, 0 replies; 10+ messages in thread
From: Bradford Love @ 2026-03-17 20:46 UTC (permalink / raw)
To: linux-media, hverkuil+cisco; +Cc: Bradford Love
New revision of Hauppauge USB Live2 switches from cx231xx usb bridge
to Empia em2828X bridge. Inputs for the USB Live2 remain the same:
- Composite video
- S-Video
- Analog stereo audio
Signed-off-by: Bradford Love <brad@nextdimension.cc>
---
Changes since v1:
- Added media: to subject line
drivers/media/usb/em28xx/em28xx-cards.c | 20 ++++++++++++++++++++
drivers/media/usb/em28xx/em28xx.h | 1 +
2 files changed, 21 insertions(+)
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index 67266bddb713..0c5851bf4ef0 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -2623,6 +2623,23 @@ const struct em28xx_board em28xx_boards[] = {
.gpio = mygica_utv3_tuner_audio_gpio,
} },
},
+ [EM2828X_BOARD_HAUPPAUGE_USB_LIVE2] = {
+ .name = "Hauppauge USB Live2",
+ .vchannels = 2,
+ .tuner_type = TUNER_ABSENT,
+ .has_dvb = 0,
+ .decoder = EM28XX_BUILTIN,
+ .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ,
+ .input = { {
+ .type = EM28XX_VMUX_COMPOSITE,
+ .vmux = 0,
+ .amux = EM28XX_AMUX_LINE_IN,
+ }, {
+ .type = EM28XX_VMUX_SVIDEO,
+ .vmux = 1,
+ .amux = EM28XX_AMUX_LINE_IN,
+ } },
+ },
};
EXPORT_SYMBOL_GPL(em28xx_boards);
@@ -2770,6 +2787,8 @@ struct usb_device_id em28xx_id_table[] = {
.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595 },
{ USB_DEVICE(0x2040, 0x846d),
.driver_info = EM2874_BOARD_HAUPPAUGE_USB_QUADHD },
+ { USB_DEVICE(0x2040, 0xc220),
+ .driver_info = EM2828X_BOARD_HAUPPAUGE_USB_LIVE2 },
{ USB_DEVICE(0x0438, 0xb002),
.driver_info = EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600 },
{ USB_DEVICE(0x2001, 0xf112),
@@ -3260,6 +3279,7 @@ static void em28xx_card_setup(struct em28xx *dev)
case EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C:
case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595:
+ case EM2828X_BOARD_HAUPPAUGE_USB_LIVE2:
{
struct tveeprom tv;
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
index b77357f71cf3..a4a91c0eb2fc 100644
--- a/drivers/media/usb/em28xx/em28xx.h
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -144,6 +144,7 @@
#define EM2860_BOARD_MYGICA_IGRABBER 105
#define EM2874_BOARD_HAUPPAUGE_USB_QUADHD 106
#define EM2860_BOARD_MYGICA_UTV3 107
+#define EM2828X_BOARD_HAUPPAUGE_USB_LIVE2 108
/* Limits minimum and default number of buffers */
#define EM28XX_MIN_BUF 4
--
2.35.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] em28xx: Add support for Empia em2828X bridge
2026-03-12 22:49 ` [PATCH 1/2] em28xx: Add support for Empia em2828X bridge Bradford Love
2026-03-13 18:37 ` [PATCH V2 " Bradford Love
2026-03-17 20:45 ` [PATCH v2 1/2] media: " Bradford Love
@ 2026-03-18 16:21 ` kernel test robot
2026-03-18 19:12 ` kernel test robot
3 siblings, 0 replies; 10+ messages in thread
From: kernel test robot @ 2026-03-18 16:21 UTC (permalink / raw)
To: Bradford Love, linux-media; +Cc: llvm, oe-kbuild-all, Bradford Love
Hi Bradford,
kernel test robot noticed the following build errors:
[auto build test ERROR on linuxtv-media-pending/master]
[also build test ERROR on media-tree/master sailus-media-tree/master linus/master v7.0-rc4 next-20260317]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Bradford-Love/em28xx-Add-support-for-Empia-em2828X-bridge/20260318-090020
base: https://git.linuxtv.org/media-ci/media-pending.git master
patch link: https://lore.kernel.org/r/20260312224915.2907539-2-brad%40nextdimension.cc
patch subject: [PATCH 1/2] em28xx: Add support for Empia em2828X bridge
config: sparc64-allmodconfig (https://download.01.org/0day-ci/archive/20260319/202603190036.DvoHb1dH-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project 4abb927bacf37f18f6359a41639a6d1b3bffffb5)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260319/202603190036.DvoHb1dH-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603190036.DvoHb1dH-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/media/usb/em28xx/em28xx-video.c:1154:17: error: incompatible pointer types assigning to 'struct media_entity *' from 'typeof (v4l2->decoder) *' (aka 'struct media_entity **'); dereference with * [-Wincompatible-pointer-types]
1154 | v4l2->decoder = kzalloc_obj(v4l2->decoder, GFP_KERNEL);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| *( )
1 error generated.
vim +1154 drivers/media/usb/em28xx/em28xx-video.c
1086
1087 static void em28xx_v4l2_create_entities(struct em28xx *dev)
1088 {
1089 #if defined(CONFIG_MEDIA_CONTROLLER)
1090 struct em28xx_v4l2 *v4l2 = dev->v4l2;
1091 int ret, i;
1092
1093 /* Initialize Video, VBI and Radio pads */
1094 v4l2->video_pad.flags = MEDIA_PAD_FL_SINK;
1095 ret = media_entity_pads_init(&v4l2->vdev.entity, 1, &v4l2->video_pad);
1096 if (ret < 0)
1097 dev_err(&dev->intf->dev,
1098 "failed to initialize video media entity!\n");
1099
1100 if (em28xx_vbi_supported(dev)) {
1101 v4l2->vbi_pad.flags = MEDIA_PAD_FL_SINK;
1102 ret = media_entity_pads_init(&v4l2->vbi_dev.entity, 1,
1103 &v4l2->vbi_pad);
1104 if (ret < 0)
1105 dev_err(&dev->intf->dev,
1106 "failed to initialize vbi media entity!\n");
1107 }
1108
1109 /* Webcams don't have input connectors */
1110 if (dev->is_webcam)
1111 return;
1112
1113 /* Create entities for each input connector */
1114 for (i = 0; i < MAX_EM28XX_INPUT; i++) {
1115 struct media_entity *ent = &dev->input_ent[i];
1116
1117 if (!INPUT(i)->type)
1118 break;
1119
1120 ent->name = iname[INPUT(i)->type];
1121 ent->flags = MEDIA_ENT_FL_CONNECTOR;
1122 dev->input_pad[i].flags = MEDIA_PAD_FL_SOURCE;
1123
1124 switch (INPUT(i)->type) {
1125 case EM28XX_VMUX_COMPOSITE:
1126 ent->function = MEDIA_ENT_F_CONN_COMPOSITE;
1127 break;
1128 case EM28XX_VMUX_SVIDEO:
1129 ent->function = MEDIA_ENT_F_CONN_SVIDEO;
1130 break;
1131 default: /* EM28XX_VMUX_TELEVISION or EM28XX_RADIO */
1132 if (dev->tuner_type != TUNER_ABSENT || em28xx_analogtv_supported(dev))
1133 ent->function = MEDIA_ENT_F_CONN_RF;
1134 break;
1135 }
1136
1137 ret = media_entity_pads_init(ent, 1, &dev->input_pad[i]);
1138 if (ret < 0)
1139 dev_err(&dev->intf->dev,
1140 "failed to initialize input pad[%d]!\n", i);
1141
1142 ret = media_device_register_entity(dev->media_dev, ent);
1143 if (ret < 0)
1144 dev_err(&dev->intf->dev,
1145 "failed to register input entity %d!\n", i);
1146 }
1147
1148 if (dev->board.decoder == EM28XX_BUILTIN) {
1149 v4l2->decoder_pads[EM2828X_PAD_INPUT].flags = MEDIA_PAD_FL_SINK;
1150 v4l2->decoder_pads[EM2828X_PAD_INPUT].sig_type = PAD_SIGNAL_ANALOG;
1151 v4l2->decoder_pads[EM2828X_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE;
1152 v4l2->decoder_pads[EM2828X_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV;
1153
> 1154 v4l2->decoder = kzalloc_obj(v4l2->decoder, GFP_KERNEL);
1155 v4l2->decoder->name = "em2828x_builtin";
1156 v4l2->decoder->function = MEDIA_ENT_F_ATV_DECODER;
1157
1158 ret = media_entity_pads_init(v4l2->decoder, EM2828X_NUM_PADS, &v4l2->decoder_pads[0]);
1159 if (ret < 0)
1160 dev_err(&dev->intf->dev, "failed to initialize decoder pads %d!\n", ret);
1161
1162 ret = media_device_register_entity(dev->media_dev, v4l2->decoder);
1163 if (ret < 0)
1164 dev_err(&dev->intf->dev, "failed to register decoder entity %d!\n", ret);
1165 }
1166
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] em28xx: Add support for Empia em2828X bridge
2026-03-12 22:49 ` [PATCH 1/2] em28xx: Add support for Empia em2828X bridge Bradford Love
` (2 preceding siblings ...)
2026-03-18 16:21 ` [PATCH 1/2] " kernel test robot
@ 2026-03-18 19:12 ` kernel test robot
3 siblings, 0 replies; 10+ messages in thread
From: kernel test robot @ 2026-03-18 19:12 UTC (permalink / raw)
To: Bradford Love, linux-media; +Cc: oe-kbuild-all, Bradford Love
Hi Bradford,
kernel test robot noticed the following build warnings:
[auto build test WARNING on linuxtv-media-pending/master]
[also build test WARNING on media-tree/master sailus-media-tree/master linus/master sailus-media-tree/streams v7.0-rc4 next-20260318]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Bradford-Love/em28xx-Add-support-for-Empia-em2828X-bridge/20260318-090020
base: https://git.linuxtv.org/media-ci/media-pending.git master
patch link: https://lore.kernel.org/r/20260312224915.2907539-2-brad%40nextdimension.cc
patch subject: [PATCH 1/2] em28xx: Add support for Empia em2828X bridge
config: xtensa-randconfig-r073-20260319 (https://download.01.org/0day-ci/archive/20260319/202603190334.ERNDESst-lkp@intel.com/config)
compiler: xtensa-linux-gcc (GCC) 8.5.0
smatch: v0.5.0-9004-gb810ac53
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260319/202603190334.ERNDESst-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603190334.ERNDESst-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/media/usb/em28xx/em28xx-dvb.c:299:5: warning: no previous prototype for 'em28xx_set_analog_freq' [-Wmissing-prototypes]
int em28xx_set_analog_freq(struct em28xx *dev, u32 freq)
^~~~~~~~~~~~~~~~~~~~~~
vim +/em28xx_set_analog_freq +299 drivers/media/usb/em28xx/em28xx-dvb.c
298
> 299 int em28xx_set_analog_freq(struct em28xx *dev, u32 freq)
300 {
301 const struct dvb_tuner_ops *dops = &dev->dvb->fe[0]->ops.tuner_ops;
302
303 if (dops->set_analog_params != NULL) {
304 struct analog_parameters params;
305
306 params.frequency = freq;
307 params.std = dev->v4l2->norm;
308 params.mode = 0;
309
310 dops->set_analog_params(dev->dvb->fe[0], ¶ms);
311 }
312 return 0;
313 }
314 /* ------------------------------------------------------------------ */
315
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-03-18 19:13 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-12 22:49 [PATCH 0/2] em28xx: Add empia EM2828X chip Bradford Love
2026-03-12 22:49 ` [PATCH 1/2] em28xx: Add support for Empia em2828X bridge Bradford Love
2026-03-13 18:37 ` [PATCH V2 " Bradford Love
2026-03-16 15:12 ` Hans Verkuil
2026-03-16 18:28 ` Bradford Love
2026-03-17 20:45 ` [PATCH v2 1/2] media: " Bradford Love
2026-03-18 16:21 ` [PATCH 1/2] " kernel test robot
2026-03-18 19:12 ` kernel test robot
2026-03-12 22:49 ` [PATCH 2/2] em28xx: Add Hauppauge USB Live2 Bradford Love
2026-03-17 20:46 ` [PATCH v2 2/2] media: " Bradford Love
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox