[PATCH v3 1/4] media: uvcvideo: Import standard controls from uvcdynctrl

Ricardo Ribalda posted 4 patches 3 weeks ago
[PATCH v3 1/4] media: uvcvideo: Import standard controls from uvcdynctrl
Posted by Ricardo Ribalda 3 weeks ago
The uvcdynctrl tool from libwebcam:
https://sourceforge.net/projects/libwebcam/
maps proprietary controls into v4l2 controls using the UVCIOC_CTRL_MAP
ioctl.

The tool has not been updated for 10+ years now, and there is no reason
for the UVC driver to not do the mapping by itself.

This patch adds the mappings from the uvcdynctrl into the driver. Hopefully
this effort can help in deprecating the UVCIOC_CTRL_MAP ioctl.

Some background about UVCIOC_CTRL_MAP (thanks Laurent for the context):

```
this was envisioned as the base of a vibrant ecosystem where a large
number of vendors would submit XML files that describe their XU control
mappings, at a pace faster than could be supported by adding XU mappings
to the driver. This vision failed to materialize and the tool has not
been updated for 10+ years now. There is no reason to believe the
situation will change.
```

During the porting, the following mappings where NOT imported because
they were not using standard v4l2 IDs. It is recommended that userspace
moves to UVCIOC_CTRL_QUERY for non standard controls.

        {
                .id             = V4L2_CID_FLASH_MODE,
                .entity         = UVC_GUID_SIS_LED_HW_CONTROL,
                .selector       = 4,
                .size           = 4,
                .offset         = 0,
                .v4l2_type      = V4L2_CTRL_TYPE_MENU,
                .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
                .menu_mask      = 0x3,
                .menu_mapping   = { 0x20, 0x22 },
                .menu_names     = { "Off", "On" },

        },
        {
                .id             = V4L2_CID_FLASH_FREQUENCY,
                .entity         = UVC_GUID_SIS_LED_HW_CONTROL,
                .selector       = 4,
                .size           = 8,
                .offset         = 16,
                .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
                .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
        },
       {
               .id             = V4L2_CID_LED1_MODE,
               .entity         = UVC_GUID_LOGITECH_USER_HW_CONTROL_V1,
               .selector       = 1,
               .size           = 8,
               .offset         = 0,
               .v4l2_type      = V4L2_CTRL_TYPE_MENU,
               .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
               .menu_mask      = 0xF,
               .menu_mapping   = { 0, 1, 2, 3 },
               .menu_names     = { "Off", "On", "Blinking", "Auto" },

       },
       {
               .id             = V4L2_CID_LED1_FREQUENCY,
               .entity         = UVC_GUID_LOGITECH_USER_HW_CONTROL_V1,
               .selector       = 1,
               .size           = 8,
               .offset         = 16,
               .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
               .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
       },
       {
               .id             = V4L2_CID_DISABLE_PROCESSING,
               .entity         = UVC_GUID_LOGITECH_VIDEO_PIPE_V1,
               .selector       = 5,
               .size           = 8,
               .offset         = 0,
               .v4l2_type      = V4L2_CTRL_TYPE_BOOLEAN,
               .data_type      = UVC_CTRL_DATA_TYPE_BOOLEAN,
       },
       {
               .id             = V4L2_CID_RAW_BITS_PER_PIXEL,
               .entity         = UVC_GUID_LOGITECH_VIDEO_PIPE_V1,
               .selector       = 8,
               .size           = 8,
               .offset         = 0,
               .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
               .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
       },
       {
               .id             = V4L2_CID_LED1_MODE,
               .entity         = UVC_GUID_LOGITECH_PERIPHERAL,
               .selector       = 0x09,
               .size           = 2,
               .offset         = 8,
               .v4l2_type      = V4L2_CTRL_TYPE_MENU,
               .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
               .menu_mask      = 0xF,
               .menu_mapping   = { 0, 1, 2, 3 },
               .menu_names     = { "Off", "On", "Blink", "Auto" },

       },
       {
               .id             = V4L2_CID_LED1_FREQUENCY,
               .entity         = UVC_GUID_LOGITECH_PERIPHERAL,
               .selector       = 0x09,
               .size           = 8,
               .offset         = 24,
               .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
               .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
       },

This script has been used to generate the mappings. They were then
reformatted manually to follow the driver style.

import sys
import uuid
import re
import xml.etree.ElementTree as ET

def get_namespace(root):
    return re.match(r"\{.*\}", root.tag).group(0)

def get_single_guid(ns, constant):
    id = constant.find(ns + "id").text
    value = constant.find(ns + "value").text
    return (id, value)

def get_constants(ns, root):
    out = dict()
    for constant in root.iter(ns + "constant"):
        attr = constant.attrib
        if attr["type"] == "integer":
            id, value = get_single_guid(ns, constant)
            if id in out:
                print(f"dupe constant {id}")
            out[id] = value

    return out

def get_guids(ns, root):
    out = dict()
    for constant in root.iter(ns + "constant"):
        attr = constant.attrib
        if attr["type"] == "guid":
            id, value = get_single_guid(ns, constant)
            if id in out:
                print(f"dupe guid {id}")
            out[id] = value

    return out

def get_single_control(ns, control):
    out = {}
    for id in "entity", "selector", "index", "size", "description":
        v = control.find(ns + id)
        if v is None and id == "description":
            continue
        out[id] = v.text

    reqs = set()
    for r in control.find(ns + "requests"):
        reqs.add(r.text)
    out["requests"] = reqs

    return (control.attrib["id"], out)

def get_controls(ns, root):
    out = dict()
    for control in root.iter(ns + "control"):
        id, value = get_single_control(ns, control)
        if id in out:
            print(f"Dupe control id {id}")
        out[id] = value

    return out

def get_single_mapping(ns, mapping):
    out = {}
    out["name"] = mapping.find(ns + "name").text
    uvc = mapping.find(ns + "uvc")
    for id in "size", "offset", "uvc_type":
        out[id] = uvc.find(ns + id).text
    out["control_ref"] = uvc.find(ns + "control_ref").attrib["idref"]

    v4l2 = mapping.find(ns + "v4l2")
    for id in "id", "v4l2_type":
        out[id] = v4l2.find(ns + id).text

    menu = {}
    for entry in v4l2.iter(ns + "menu_entry"):
        menu[entry.attrib["name"]] = entry.attrib["value"]
    if menu:
        out["menu"] = menu

    return out

def get_mapping(ns, root):
    out = []
    for control in root.iter(ns + "mapping"):
        mapping = get_single_mapping(ns, control)
        out += [mapping]

    return out

def print_guids(guids):
    for g in guids:
        print(f"#define {g} \\")
        u_bytes = uuid.UUID(guids[g]).bytes_le
        u_bytes = [f"0x{b:02x}" for b in u_bytes]
        print("\t{ " + ", ".join(u_bytes) + " }")

def print_flags(flags):
    get_range = {"GET_MIN", "GET_DEF", "GET_MAX", "GET_CUR", "GET_RES"}
    if get_range.issubset(flags):
        flags -= get_range
        flags.add("GET_RANGE")

    flags = list(flags)
    flags.sort()
    out = ""
    for f in flags[:-1]:
        out += f"UVC_CTRL_FLAG_{f}\n\t\t\t\t| "

    out += f"UVC_CTRL_FLAG_{flags[-1]}"

    return out

def print_description(desc):
    print("/*")
    for line in desc.strip().splitlines():
        print(f" * {line.strip()}")
    print("*/")

def print_controls(controls, cons):
    for id in controls:
        c = controls[id]
        if "description" in c:
            print_description(c["description"])
        print(
            f"""\t{{
\t\t.entity\t\t= {c["entity"]},
\t\t.selector\t= {cons[c["selector"]]},
\t\t.index\t\t= {c["index"]},
\t\t.size\t\t= {c["size"]},
\t\t.flags\t\t= {print_flags(c["requests"])},
\t}},"""
        )

def menu_mapping_txt(menu):
    out = f"\n\t\t.menu_mask\t= 0x{((1<<len(menu))-1):X},\n"
    out += f"\t\t.menu_mapping\t= {{ {", ".join(menu.values())} }},\n"
    out += f"\t\t.menu_names\t= {{ \"{"\", \"".join(menu.keys())}\" }},\n"
    return out

def print_mappings(mappings, controls, cons):
    for m in mappings:
        c = controls[m["control_ref"]]

        if "menu" in m:
            menu_mapping = menu_mapping_txt(m["menu"])
        else:
            menu_mapping = ""
        print(
            f"""\t{{
\t\t.id\t\t= {m["id"]},
\t\t.entity\t\t= {c["entity"]},
\t\t.selector\t= {cons[c["selector"]]},
\t\t.size\t\t= {m["size"]},
\t\t.offset\t\t= {m["offset"]},
\t\t.v4l2_type\t= {m["v4l2_type"]},
\t\t.data_type\t= {m["uvc_type"]},{menu_mapping}
\t}},"""
        )

def print_code(guids, cons, controls, mappings):
    used_controls = set()
    for m in mappings:
        used_controls.add(m["control_ref"])

    used_guids = set()
    for c in used_controls:
        used_guids.add(controls[c]["entity"])

    print("\n######GUIDs#######\n")
    print_guids({id: guids[id] for id in guids if id in used_guids})
    print("\n######CONTROLS#######\n")
    print_controls({id: controls[id] for id in controls if id in used_controls}, cons)
    print("\n######MAPPINGS#######\n")
    print_mappings(mappings, controls, cons)
    # print(guids)
    # print(used_controls)

root = ET.fromstring(sys.stdin.read())
ns = get_namespace(root)
cons = get_constants(ns, root)
guids = get_guids(ns, root)
controls = get_controls(ns, root)
mappings = get_mapping(ns, root)
print_code(guids, cons, controls, mappings)

Cc: Manav Gautama <bandwidthcrunch@gmail.com>
Cc: Martin Rubli <martin_rubli@logitech.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Ricardo Ribalda <ribalda@chromium.org>
---
 drivers/media/usb/uvc/uvc_ctrl.c | 174 +++++++++++++++++++++++++++++++++++++++
 include/linux/usb/uvc.h          |   6 ++
 2 files changed, 180 insertions(+)

diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index b9a3d9257a11..b6e020b41671 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -385,6 +385,99 @@ static const struct uvc_control_info uvc_ctrls[] = {
 				| UVC_CTRL_FLAG_GET_RANGE
 				| UVC_CTRL_FLAG_RESTORE,
 	},
+	/*
+	 * Allows the control of pan/tilt motor movements for camera models
+	 * that support mechanical pan/tilt.
+	 *
+	 * Bits 0 to 15 control pan, bits 16 to 31 control tilt.
+	 * The unit of the pan/tilt values is 1/64th of a degree and the
+	 * resolution is 1 degree.
+	 */
+	{
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 1,
+		.index          = 0,
+		.size           = 4,
+		.flags          = UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_GET_MAX
+				| UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_SET_CUR,
+	},
+	/*
+	 * Reset the pan/tilt motors to their original position for camera
+	 * models that support mechanical pan/tilt.
+	 *
+	 * Setting bit 0 resets the pan position.
+	 * Setting bit 1 resets the tilt position.
+	 *
+	 * Both bits can be set at the same time to reset both, pan and tilt,
+	 * at the same time.
+	 */
+	{
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 2,
+		.index          = 1,
+		.size           = 1,
+		.flags          = UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_GET_MAX
+				| UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_SET_CUR,
+	},
+	/*
+	 * Allows the control of focus motor movements for camera models that
+	 * support mechanical focus.
+	 *
+	 * Bits 0 to 7 allow selection of the desired lens position.
+	 * There are no physical units, instead, the focus range is spread over
+	 * 256 logical units with 0 representing infinity focus and 255 being
+	 * macro focus.
+	 */
+	{
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 3,
+		.index          = 2,
+		.size           = 6,
+		.flags          = UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_GET_MAX
+				| UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_SET_CUR,
+	},
+	/*
+	 * Allows the control of pan/tilt motor movements for camera models
+	 * that support mechanical pan/tilt.
+	 *
+	 * Bits 0 to 15 control pan, bits 16 to 31 control tilt.
+	 */
+	{
+		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
+		.selector       = 1,
+		.index          = 0,
+		.size           = 4,
+		.flags          = UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_GET_MAX
+				| UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_GET_RES
+				| UVC_CTRL_FLAG_SET_CUR,
+	},
+	/*
+	 * Reset the pan/tilt motors to their original position for camera
+	 * models that support mechanical pan/tilt.
+	 *
+	 * Setting bit 0 resets the pan position.
+	 * Setting bit 1 resets the tilt position.
+	 */
+	{
+		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
+		.selector       = 2,
+		.index          = 1,
+		.size           = 1,
+		.flags          = UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_GET_MAX
+				| UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_GET_RES
+				| UVC_CTRL_FLAG_SET_CUR,
+	},
 };
 
 static const u32 uvc_control_classes[] = {
@@ -1009,6 +1102,87 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
 		.menu_mask	= BIT(V4L2_COLORFX_VIVID) |
 				  BIT(V4L2_COLORFX_NONE),
 	},
+	{
+		.id             = V4L2_CID_PAN_RELATIVE,
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 1,
+		.size           = 16,
+		.offset         = 0,
+		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
+		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id             = V4L2_CID_TILT_RELATIVE,
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 1,
+		.size           = 16,
+		.offset         = 16,
+		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
+		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id             = V4L2_CID_PAN_RESET,
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 2,
+		.size           = 1,
+		.offset         = 0,
+		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
+		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id             = V4L2_CID_TILT_RESET,
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 2,
+		.size           = 1,
+		.offset         = 1,
+		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
+		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id             = V4L2_CID_PAN_RELATIVE,
+		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
+		.selector       = 1,
+		.size           = 16,
+		.offset         = 0,
+		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
+		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id             = V4L2_CID_TILT_RELATIVE,
+		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
+		.selector       = 1,
+		.size           = 16,
+		.offset         = 16,
+		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
+		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id             = V4L2_CID_PAN_RESET,
+		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
+		.selector       = 2,
+		.size           = 1,
+		.offset         = 0,
+		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
+		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id             = V4L2_CID_TILT_RESET,
+		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
+		.selector       = 2,
+		.size           = 1,
+		.offset         = 1,
+		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
+		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id             = V4L2_CID_FOCUS_ABSOLUTE,
+		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
+		.selector       = 3,
+		.size           = 8,
+		.offset         = 0,
+		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
+		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
 };
 
 /* ------------------------------------------------------------------------
diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h
index ea92ac623a45..dea23aabbad4 100644
--- a/include/linux/usb/uvc.h
+++ b/include/linux/usb/uvc.h
@@ -43,6 +43,12 @@
 #define UVC_GUID_MSXU_1_5 \
 	{0xdc, 0x95, 0x3f, 0x0f, 0x32, 0x26, 0x4e, 0x4c, \
 	 0x92, 0xc9, 0xa0, 0x47, 0x82, 0xf4, 0x3b, 0xc8}
+#define UVC_GUID_LOGITECH_MOTOR_CONTROL_V1 \
+	{0x82, 0x06, 0x61, 0x63, 0x70, 0x50, 0xab, 0x49, \
+	 0xb8, 0xcc, 0xb3, 0x85, 0x5e, 0x8d, 0x22, 0x56 }
+#define UVC_GUID_LOGITECH_PERIPHERAL \
+	{0x21, 0x2d, 0xe5, 0xff, 0x30, 0x80, 0x2c, 0x4e, \
+	 0x82, 0xd9, 0xf5, 0x87, 0xd0, 0x05, 0x40, 0xbd }
 
 /* https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/uvc-extensions-1-5#222-extension-unit-controls */
 #define UVC_MSXU_CONTROL_FOCUS			0x01

-- 
2.53.0.851.ga537e3e6e9-goog
Re: [PATCH v3 1/4] media: uvcvideo: Import standard controls from uvcdynctrl
Posted by Hans de Goede 1 week ago
Hi,

On 16-Mar-26 14:34, Ricardo Ribalda wrote:
> The uvcdynctrl tool from libwebcam:
> https://sourceforge.net/projects/libwebcam/
> maps proprietary controls into v4l2 controls using the UVCIOC_CTRL_MAP
> ioctl.
> 
> The tool has not been updated for 10+ years now, and there is no reason
> for the UVC driver to not do the mapping by itself.
> 
> This patch adds the mappings from the uvcdynctrl into the driver. Hopefully
> this effort can help in deprecating the UVCIOC_CTRL_MAP ioctl.
> 
> Some background about UVCIOC_CTRL_MAP (thanks Laurent for the context):
> 
> ```
> this was envisioned as the base of a vibrant ecosystem where a large
> number of vendors would submit XML files that describe their XU control
> mappings, at a pace faster than could be supported by adding XU mappings
> to the driver. This vision failed to materialize and the tool has not
> been updated for 10+ years now. There is no reason to believe the
> situation will change.
> ```

Thanks, I've applied this to my local uvc/for-next branch and will
push this out to the public uvc repo for-next branch soon.

Regards,

Hans




> 
> During the porting, the following mappings where NOT imported because
> they were not using standard v4l2 IDs. It is recommended that userspace
> moves to UVCIOC_CTRL_QUERY for non standard controls.
> 
>         {
>                 .id             = V4L2_CID_FLASH_MODE,
>                 .entity         = UVC_GUID_SIS_LED_HW_CONTROL,
>                 .selector       = 4,
>                 .size           = 4,
>                 .offset         = 0,
>                 .v4l2_type      = V4L2_CTRL_TYPE_MENU,
>                 .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
>                 .menu_mask      = 0x3,
>                 .menu_mapping   = { 0x20, 0x22 },
>                 .menu_names     = { "Off", "On" },
> 
>         },
>         {
>                 .id             = V4L2_CID_FLASH_FREQUENCY,
>                 .entity         = UVC_GUID_SIS_LED_HW_CONTROL,
>                 .selector       = 4,
>                 .size           = 8,
>                 .offset         = 16,
>                 .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
>                 .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
>         },
>        {
>                .id             = V4L2_CID_LED1_MODE,
>                .entity         = UVC_GUID_LOGITECH_USER_HW_CONTROL_V1,
>                .selector       = 1,
>                .size           = 8,
>                .offset         = 0,
>                .v4l2_type      = V4L2_CTRL_TYPE_MENU,
>                .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
>                .menu_mask      = 0xF,
>                .menu_mapping   = { 0, 1, 2, 3 },
>                .menu_names     = { "Off", "On", "Blinking", "Auto" },
> 
>        },
>        {
>                .id             = V4L2_CID_LED1_FREQUENCY,
>                .entity         = UVC_GUID_LOGITECH_USER_HW_CONTROL_V1,
>                .selector       = 1,
>                .size           = 8,
>                .offset         = 16,
>                .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
>                .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
>        },
>        {
>                .id             = V4L2_CID_DISABLE_PROCESSING,
>                .entity         = UVC_GUID_LOGITECH_VIDEO_PIPE_V1,
>                .selector       = 5,
>                .size           = 8,
>                .offset         = 0,
>                .v4l2_type      = V4L2_CTRL_TYPE_BOOLEAN,
>                .data_type      = UVC_CTRL_DATA_TYPE_BOOLEAN,
>        },
>        {
>                .id             = V4L2_CID_RAW_BITS_PER_PIXEL,
>                .entity         = UVC_GUID_LOGITECH_VIDEO_PIPE_V1,
>                .selector       = 8,
>                .size           = 8,
>                .offset         = 0,
>                .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
>                .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
>        },
>        {
>                .id             = V4L2_CID_LED1_MODE,
>                .entity         = UVC_GUID_LOGITECH_PERIPHERAL,
>                .selector       = 0x09,
>                .size           = 2,
>                .offset         = 8,
>                .v4l2_type      = V4L2_CTRL_TYPE_MENU,
>                .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
>                .menu_mask      = 0xF,
>                .menu_mapping   = { 0, 1, 2, 3 },
>                .menu_names     = { "Off", "On", "Blink", "Auto" },
> 
>        },
>        {
>                .id             = V4L2_CID_LED1_FREQUENCY,
>                .entity         = UVC_GUID_LOGITECH_PERIPHERAL,
>                .selector       = 0x09,
>                .size           = 8,
>                .offset         = 24,
>                .v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
>                .data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
>        },
> 
> This script has been used to generate the mappings. They were then
> reformatted manually to follow the driver style.
> 
> import sys
> import uuid
> import re
> import xml.etree.ElementTree as ET
> 
> def get_namespace(root):
>     return re.match(r"\{.*\}", root.tag).group(0)
> 
> def get_single_guid(ns, constant):
>     id = constant.find(ns + "id").text
>     value = constant.find(ns + "value").text
>     return (id, value)
> 
> def get_constants(ns, root):
>     out = dict()
>     for constant in root.iter(ns + "constant"):
>         attr = constant.attrib
>         if attr["type"] == "integer":
>             id, value = get_single_guid(ns, constant)
>             if id in out:
>                 print(f"dupe constant {id}")
>             out[id] = value
> 
>     return out
> 
> def get_guids(ns, root):
>     out = dict()
>     for constant in root.iter(ns + "constant"):
>         attr = constant.attrib
>         if attr["type"] == "guid":
>             id, value = get_single_guid(ns, constant)
>             if id in out:
>                 print(f"dupe guid {id}")
>             out[id] = value
> 
>     return out
> 
> def get_single_control(ns, control):
>     out = {}
>     for id in "entity", "selector", "index", "size", "description":
>         v = control.find(ns + id)
>         if v is None and id == "description":
>             continue
>         out[id] = v.text
> 
>     reqs = set()
>     for r in control.find(ns + "requests"):
>         reqs.add(r.text)
>     out["requests"] = reqs
> 
>     return (control.attrib["id"], out)
> 
> def get_controls(ns, root):
>     out = dict()
>     for control in root.iter(ns + "control"):
>         id, value = get_single_control(ns, control)
>         if id in out:
>             print(f"Dupe control id {id}")
>         out[id] = value
> 
>     return out
> 
> def get_single_mapping(ns, mapping):
>     out = {}
>     out["name"] = mapping.find(ns + "name").text
>     uvc = mapping.find(ns + "uvc")
>     for id in "size", "offset", "uvc_type":
>         out[id] = uvc.find(ns + id).text
>     out["control_ref"] = uvc.find(ns + "control_ref").attrib["idref"]
> 
>     v4l2 = mapping.find(ns + "v4l2")
>     for id in "id", "v4l2_type":
>         out[id] = v4l2.find(ns + id).text
> 
>     menu = {}
>     for entry in v4l2.iter(ns + "menu_entry"):
>         menu[entry.attrib["name"]] = entry.attrib["value"]
>     if menu:
>         out["menu"] = menu
> 
>     return out
> 
> def get_mapping(ns, root):
>     out = []
>     for control in root.iter(ns + "mapping"):
>         mapping = get_single_mapping(ns, control)
>         out += [mapping]
> 
>     return out
> 
> def print_guids(guids):
>     for g in guids:
>         print(f"#define {g} \\")
>         u_bytes = uuid.UUID(guids[g]).bytes_le
>         u_bytes = [f"0x{b:02x}" for b in u_bytes]
>         print("\t{ " + ", ".join(u_bytes) + " }")
> 
> def print_flags(flags):
>     get_range = {"GET_MIN", "GET_DEF", "GET_MAX", "GET_CUR", "GET_RES"}
>     if get_range.issubset(flags):
>         flags -= get_range
>         flags.add("GET_RANGE")
> 
>     flags = list(flags)
>     flags.sort()
>     out = ""
>     for f in flags[:-1]:
>         out += f"UVC_CTRL_FLAG_{f}\n\t\t\t\t| "
> 
>     out += f"UVC_CTRL_FLAG_{flags[-1]}"
> 
>     return out
> 
> def print_description(desc):
>     print("/*")
>     for line in desc.strip().splitlines():
>         print(f" * {line.strip()}")
>     print("*/")
> 
> def print_controls(controls, cons):
>     for id in controls:
>         c = controls[id]
>         if "description" in c:
>             print_description(c["description"])
>         print(
>             f"""\t{{
> \t\t.entity\t\t= {c["entity"]},
> \t\t.selector\t= {cons[c["selector"]]},
> \t\t.index\t\t= {c["index"]},
> \t\t.size\t\t= {c["size"]},
> \t\t.flags\t\t= {print_flags(c["requests"])},
> \t}},"""
>         )
> 
> def menu_mapping_txt(menu):
>     out = f"\n\t\t.menu_mask\t= 0x{((1<<len(menu))-1):X},\n"
>     out += f"\t\t.menu_mapping\t= {{ {", ".join(menu.values())} }},\n"
>     out += f"\t\t.menu_names\t= {{ \"{"\", \"".join(menu.keys())}\" }},\n"
>     return out
> 
> def print_mappings(mappings, controls, cons):
>     for m in mappings:
>         c = controls[m["control_ref"]]
> 
>         if "menu" in m:
>             menu_mapping = menu_mapping_txt(m["menu"])
>         else:
>             menu_mapping = ""
>         print(
>             f"""\t{{
> \t\t.id\t\t= {m["id"]},
> \t\t.entity\t\t= {c["entity"]},
> \t\t.selector\t= {cons[c["selector"]]},
> \t\t.size\t\t= {m["size"]},
> \t\t.offset\t\t= {m["offset"]},
> \t\t.v4l2_type\t= {m["v4l2_type"]},
> \t\t.data_type\t= {m["uvc_type"]},{menu_mapping}
> \t}},"""
>         )
> 
> def print_code(guids, cons, controls, mappings):
>     used_controls = set()
>     for m in mappings:
>         used_controls.add(m["control_ref"])
> 
>     used_guids = set()
>     for c in used_controls:
>         used_guids.add(controls[c]["entity"])
> 
>     print("\n######GUIDs#######\n")
>     print_guids({id: guids[id] for id in guids if id in used_guids})
>     print("\n######CONTROLS#######\n")
>     print_controls({id: controls[id] for id in controls if id in used_controls}, cons)
>     print("\n######MAPPINGS#######\n")
>     print_mappings(mappings, controls, cons)
>     # print(guids)
>     # print(used_controls)
> 
> root = ET.fromstring(sys.stdin.read())
> ns = get_namespace(root)
> cons = get_constants(ns, root)
> guids = get_guids(ns, root)
> controls = get_controls(ns, root)
> mappings = get_mapping(ns, root)
> print_code(guids, cons, controls, mappings)
> 
> Cc: Manav Gautama <bandwidthcrunch@gmail.com>
> Cc: Martin Rubli <martin_rubli@logitech.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Ricardo Ribalda <ribalda@chromium.org>
> ---
>  drivers/media/usb/uvc/uvc_ctrl.c | 174 +++++++++++++++++++++++++++++++++++++++
>  include/linux/usb/uvc.h          |   6 ++
>  2 files changed, 180 insertions(+)
> 
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index b9a3d9257a11..b6e020b41671 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -385,6 +385,99 @@ static const struct uvc_control_info uvc_ctrls[] = {
>  				| UVC_CTRL_FLAG_GET_RANGE
>  				| UVC_CTRL_FLAG_RESTORE,
>  	},
> +	/*
> +	 * Allows the control of pan/tilt motor movements for camera models
> +	 * that support mechanical pan/tilt.
> +	 *
> +	 * Bits 0 to 15 control pan, bits 16 to 31 control tilt.
> +	 * The unit of the pan/tilt values is 1/64th of a degree and the
> +	 * resolution is 1 degree.
> +	 */
> +	{
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 1,
> +		.index          = 0,
> +		.size           = 4,
> +		.flags          = UVC_CTRL_FLAG_GET_DEF
> +				| UVC_CTRL_FLAG_GET_MAX
> +				| UVC_CTRL_FLAG_GET_MIN
> +				| UVC_CTRL_FLAG_SET_CUR,
> +	},
> +	/*
> +	 * Reset the pan/tilt motors to their original position for camera
> +	 * models that support mechanical pan/tilt.
> +	 *
> +	 * Setting bit 0 resets the pan position.
> +	 * Setting bit 1 resets the tilt position.
> +	 *
> +	 * Both bits can be set at the same time to reset both, pan and tilt,
> +	 * at the same time.
> +	 */
> +	{
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 2,
> +		.index          = 1,
> +		.size           = 1,
> +		.flags          = UVC_CTRL_FLAG_GET_DEF
> +				| UVC_CTRL_FLAG_GET_MAX
> +				| UVC_CTRL_FLAG_GET_MIN
> +				| UVC_CTRL_FLAG_SET_CUR,
> +	},
> +	/*
> +	 * Allows the control of focus motor movements for camera models that
> +	 * support mechanical focus.
> +	 *
> +	 * Bits 0 to 7 allow selection of the desired lens position.
> +	 * There are no physical units, instead, the focus range is spread over
> +	 * 256 logical units with 0 representing infinity focus and 255 being
> +	 * macro focus.
> +	 */
> +	{
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 3,
> +		.index          = 2,
> +		.size           = 6,
> +		.flags          = UVC_CTRL_FLAG_GET_CUR
> +				| UVC_CTRL_FLAG_GET_DEF
> +				| UVC_CTRL_FLAG_GET_MAX
> +				| UVC_CTRL_FLAG_GET_MIN
> +				| UVC_CTRL_FLAG_SET_CUR,
> +	},
> +	/*
> +	 * Allows the control of pan/tilt motor movements for camera models
> +	 * that support mechanical pan/tilt.
> +	 *
> +	 * Bits 0 to 15 control pan, bits 16 to 31 control tilt.
> +	 */
> +	{
> +		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
> +		.selector       = 1,
> +		.index          = 0,
> +		.size           = 4,
> +		.flags          = UVC_CTRL_FLAG_GET_DEF
> +				| UVC_CTRL_FLAG_GET_MAX
> +				| UVC_CTRL_FLAG_GET_MIN
> +				| UVC_CTRL_FLAG_GET_RES
> +				| UVC_CTRL_FLAG_SET_CUR,
> +	},
> +	/*
> +	 * Reset the pan/tilt motors to their original position for camera
> +	 * models that support mechanical pan/tilt.
> +	 *
> +	 * Setting bit 0 resets the pan position.
> +	 * Setting bit 1 resets the tilt position.
> +	 */
> +	{
> +		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
> +		.selector       = 2,
> +		.index          = 1,
> +		.size           = 1,
> +		.flags          = UVC_CTRL_FLAG_GET_DEF
> +				| UVC_CTRL_FLAG_GET_MAX
> +				| UVC_CTRL_FLAG_GET_MIN
> +				| UVC_CTRL_FLAG_GET_RES
> +				| UVC_CTRL_FLAG_SET_CUR,
> +	},
>  };
>  
>  static const u32 uvc_control_classes[] = {
> @@ -1009,6 +1102,87 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
>  		.menu_mask	= BIT(V4L2_COLORFX_VIVID) |
>  				  BIT(V4L2_COLORFX_NONE),
>  	},
> +	{
> +		.id             = V4L2_CID_PAN_RELATIVE,
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 1,
> +		.size           = 16,
> +		.offset         = 0,
> +		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
> +		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_TILT_RELATIVE,
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 1,
> +		.size           = 16,
> +		.offset         = 16,
> +		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
> +		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_PAN_RESET,
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 2,
> +		.size           = 1,
> +		.offset         = 0,
> +		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
> +		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_TILT_RESET,
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 2,
> +		.size           = 1,
> +		.offset         = 1,
> +		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
> +		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_PAN_RELATIVE,
> +		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
> +		.selector       = 1,
> +		.size           = 16,
> +		.offset         = 0,
> +		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
> +		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_TILT_RELATIVE,
> +		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
> +		.selector       = 1,
> +		.size           = 16,
> +		.offset         = 16,
> +		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
> +		.data_type      = UVC_CTRL_DATA_TYPE_SIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_PAN_RESET,
> +		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
> +		.selector       = 2,
> +		.size           = 1,
> +		.offset         = 0,
> +		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
> +		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_TILT_RESET,
> +		.entity         = UVC_GUID_LOGITECH_PERIPHERAL,
> +		.selector       = 2,
> +		.size           = 1,
> +		.offset         = 1,
> +		.v4l2_type      = V4L2_CTRL_TYPE_BUTTON,
> +		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
> +	},
> +	{
> +		.id             = V4L2_CID_FOCUS_ABSOLUTE,
> +		.entity         = UVC_GUID_LOGITECH_MOTOR_CONTROL_V1,
> +		.selector       = 3,
> +		.size           = 8,
> +		.offset         = 0,
> +		.v4l2_type      = V4L2_CTRL_TYPE_INTEGER,
> +		.data_type      = UVC_CTRL_DATA_TYPE_UNSIGNED,
> +	},
>  };
>  
>  /* ------------------------------------------------------------------------
> diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h
> index ea92ac623a45..dea23aabbad4 100644
> --- a/include/linux/usb/uvc.h
> +++ b/include/linux/usb/uvc.h
> @@ -43,6 +43,12 @@
>  #define UVC_GUID_MSXU_1_5 \
>  	{0xdc, 0x95, 0x3f, 0x0f, 0x32, 0x26, 0x4e, 0x4c, \
>  	 0x92, 0xc9, 0xa0, 0x47, 0x82, 0xf4, 0x3b, 0xc8}
> +#define UVC_GUID_LOGITECH_MOTOR_CONTROL_V1 \
> +	{0x82, 0x06, 0x61, 0x63, 0x70, 0x50, 0xab, 0x49, \
> +	 0xb8, 0xcc, 0xb3, 0x85, 0x5e, 0x8d, 0x22, 0x56 }
> +#define UVC_GUID_LOGITECH_PERIPHERAL \
> +	{0x21, 0x2d, 0xe5, 0xff, 0x30, 0x80, 0x2c, 0x4e, \
> +	 0x82, 0xd9, 0xf5, 0x87, 0xd0, 0x05, 0x40, 0xbd }
>  
>  /* https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/uvc-extensions-1-5#222-extension-unit-controls */
>  #define UVC_MSXU_CONTROL_FOCUS			0x01
>