@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
uvcvideo-objs := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
- uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o
+ uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o \
+ uvc_swentity.o
ifeq ($(CONFIG_MEDIA_CONTROLLER),y)
uvcvideo-objs += uvc_entity.o
endif
@@ -376,6 +376,13 @@ static const struct uvc_control_info uvc_ctrls[] = {
| UVC_CTRL_FLAG_GET_DEF
| UVC_CTRL_FLAG_AUTO_UPDATE,
},
+ {
+ .entity = UVC_GUID_SWENTITY,
+ .selector = 0,
+ .index = 0,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_GET_CUR,
+ },
};
static const u32 uvc_control_classes[] = {
@@ -975,6 +982,17 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
.data_type = UVC_CTRL_DATA_TYPE_BITMASK,
.name = "Region of Interest Auto Ctrls",
},
+ {
+ .id = V4L2_CID_CAMERA_ORIENTATION,
+ .entity = UVC_GUID_SWENTITY,
+ .selector = 0,
+ .size = 8,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_MENU,
+ .data_type = UVC_CTRL_DATA_TYPE_ENUM,
+ .menu_mask = GENMASK(V4L2_CAMERA_ORIENTATION_EXTERNAL,
+ V4L2_CAMERA_ORIENTATION_FRONT),
+ },
};
/* ------------------------------------------------------------------------
@@ -3210,6 +3228,9 @@ static int uvc_ctrl_init_chain(struct uvc_video_chain *chain)
} else if (UVC_ENTITY_TYPE(entity) == UVC_EXT_GPIO_UNIT) {
bmControls = entity->gpio.bmControls;
bControlSize = entity->gpio.bControlSize;
+ } else if (UVC_ENTITY_TYPE(entity) == UVC_SWENTITY_UNIT) {
+ bmControls = entity->swentity.bmControls;
+ bControlSize = entity->swentity.bControlSize;
}
/* Remove bogus/blacklisted controls */
@@ -1869,11 +1869,15 @@ static int uvc_scan_device(struct uvc_device *dev)
return -1;
}
- /* Add GPIO entity to the first chain. */
- if (dev->gpio_unit) {
+ /* Add virtual entities to the first chain. */
+ if (dev->gpio_unit || dev->swentity_unit) {
chain = list_first_entry(&dev->chains,
struct uvc_video_chain, list);
- list_add_tail(&dev->gpio_unit->chain, &chain->entities);
+ if (dev->gpio_unit)
+ list_add_tail(&dev->gpio_unit->chain, &chain->entities);
+ if (dev->swentity_unit)
+ list_add_tail(&dev->swentity_unit->chain,
+ &chain->entities);
}
return 0;
@@ -2249,6 +2253,10 @@ static int uvc_probe(struct usb_interface *intf,
if (ret < 0)
goto error;
+ ret = uvc_swentity_init(dev);
+ if (ret < 0)
+ goto error;
+
dev_info(&dev->udev->dev, "Found UVC %u.%02x device %s (%04x:%04x)\n",
dev->uvc_version >> 8, dev->uvc_version & 0xff,
udev->product ? udev->product : "<unnamed>",
@@ -106,6 +106,7 @@ static int uvc_mc_init_entity(struct uvc_video_chain *chain,
case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
case UVC_EXTERNAL_VENDOR_SPECIFIC:
case UVC_EXT_GPIO_UNIT:
+ case UVC_SWENTITY_UNIT:
default:
function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
break;
new file mode 100644
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_swentity.c -- USB Video Class driver
+ *
+ * Copyright 2025 Google LLC
+ */
+
+#include <linux/kernel.h>
+#include <linux/usb/uvc.h>
+#include <media/v4l2-fwnode.h>
+#include "uvcvideo.h"
+
+static int uvc_swentity_get_cur(struct uvc_device *dev, struct uvc_entity *entity,
+ u8 cs, void *data, u16 size)
+{
+ if (size < 1)
+ return -EINVAL;
+
+ switch (entity->swentity.props.orientation) {
+ case V4L2_FWNODE_ORIENTATION_FRONT:
+ *(u8 *)data = V4L2_CAMERA_ORIENTATION_FRONT;
+ break;
+ case V4L2_FWNODE_ORIENTATION_BACK:
+ *(u8 *)data = V4L2_CAMERA_ORIENTATION_BACK;
+ break;
+ default:
+ *(u8 *)data = V4L2_CAMERA_ORIENTATION_EXTERNAL;
+ }
+
+ return 0;
+}
+
+static int uvc_swentity_get_info(struct uvc_device *dev,
+ struct uvc_entity *entity, u8 cs, u8 *caps)
+{
+ *caps = UVC_CONTROL_CAP_GET;
+ return 0;
+}
+
+int uvc_swentity_init(struct uvc_device *dev)
+{
+ static const u8 uvc_swentity_guid[] = UVC_GUID_SWENTITY;
+ struct v4l2_fwnode_device_properties props;
+ struct uvc_entity *unit;
+ int ret;
+
+ ret = v4l2_fwnode_device_parse(&dev->udev->dev, &props);
+ if (ret)
+ return dev_err_probe(&dev->intf->dev, ret,
+ "Can't parse fwnode\n");
+
+ if (props.orientation == V4L2_FWNODE_PROPERTY_UNSET)
+ return 0;
+
+ unit = uvc_alloc_entity(UVC_SWENTITY_UNIT, UVC_SWENTITY_UNIT_ID, 0, 1);
+ if (!unit)
+ return -ENOMEM;
+
+ memcpy(unit->guid, uvc_swentity_guid, sizeof(unit->guid));
+ unit->swentity.props = props;
+ unit->swentity.bControlSize = 1;
+ unit->swentity.bmControls = (u8 *)unit + sizeof(*unit);
+ unit->swentity.bmControls[0] = 1;
+ unit->get_cur = uvc_swentity_get_cur;
+ unit->get_info = uvc_swentity_get_info;
+ strscpy(unit->name, "SWENTITY", sizeof(unit->name));
+
+ list_add_tail(&unit->list, &dev->entities);
+
+ dev->swentity_unit = unit;
+
+ return 0;
+}
@@ -19,6 +19,7 @@
#include <media/v4l2-event.h>
#include <media/v4l2-fh.h>
#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-fwnode.h>
/* --------------------------------------------------------------------------
* UVC constants
@@ -41,6 +42,9 @@
#define UVC_EXT_GPIO_UNIT 0x7ffe
#define UVC_EXT_GPIO_UNIT_ID 0x100
+#define UVC_SWENTITY_UNIT 0x7ffd
+#define UVC_SWENTITY_UNIT_ID 0x101
+
/* ------------------------------------------------------------------------
* Driver specific constants.
*/
@@ -242,6 +246,12 @@ struct uvc_entity {
int irq;
bool initialized;
} gpio;
+
+ struct {
+ u8 bControlSize;
+ u8 *bmControls;
+ struct v4l2_fwnode_device_properties props;
+ } swentity;
};
u8 bNrInPins;
@@ -617,6 +627,7 @@ struct uvc_device {
} async_ctrl;
struct uvc_entity *gpio_unit;
+ struct uvc_entity *swentity_unit;
};
enum uvc_handle_state {
@@ -836,4 +847,7 @@ void uvc_debugfs_cleanup_stream(struct uvc_streaming *stream);
size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf,
size_t size);
+/* swentity */
+int uvc_swentity_init(struct uvc_device *dev);
+
#endif
@@ -29,6 +29,9 @@
#define UVC_GUID_EXT_GPIO_CONTROLLER \
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03}
+#define UVC_GUID_SWENTITY \
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04}
#define UVC_GUID_FORMAT_MJPEG \
{ 'M', 'J', 'P', 'G', 0x00, 0x00, 0x10, 0x00, \
Fetch the orientation from the fwnode and map it into a control. The uvc driver does not use the media controller, so we need to create a virtual entity, like we previously did with the external gpio. We do not re-purpose the external gpio entity because its is planned to remove it. Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> --- drivers/media/usb/uvc/Makefile | 3 +- drivers/media/usb/uvc/uvc_ctrl.c | 21 +++++++++++ drivers/media/usb/uvc/uvc_driver.c | 14 +++++-- drivers/media/usb/uvc/uvc_entity.c | 1 + drivers/media/usb/uvc/uvc_swentity.c | 73 ++++++++++++++++++++++++++++++++++++ drivers/media/usb/uvc/uvcvideo.h | 14 +++++++ include/linux/usb/uvc.h | 3 ++ 7 files changed, 125 insertions(+), 4 deletions(-)