public inbox for linux-media@vger.kernel.org
 help / color / mirror / Atom feed
* [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], &params);
+	}
+	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], &params);
+	}
+	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], &params);
+	}
+	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], &params);
   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