From de70fc6026854cfa80ddc0c279c1003bc5033a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gilerson?= Date: Tue, 3 Mar 2026 12:00:00 +0100 Subject: [PATCH 1/6] HID: multitouch: Surface Pro 11 Type Cover touchpad support The Surface Pro 11 (Intel/Lunar Lake) Type Cover touchpad (045E:0C8D) needs three fixes: 1. Force clickpad mode: The firmware reports Pad Type = 1 (PRESSUREPAD) and exposes 3 HID buttons, so hid-multitouch's clickpad detection fails. Add MT_QUIRK_FORCE_BUTTONPAD to force INPUT_PROP_BUTTONPAD. 2. Enable firmware palm rejection: The touchpad firmware detects palms and reports Confidence=0 per-contact, but MT_QUIRK_CONFIDENCE was only enabled for a whitelist of Win8 classes. Add the Surface Type Cover 11 class to the whitelist so the kernel reports MT_TOOL_PALM to libinput. 3. Expose whole-pad force sensor: The HID descriptor includes a 16-bit force measurement field (Usage Page 0x20 Sensor, Usage 0x0494) that was being discarded. Map it to ABS_PRESSURE, guarded by MT_QUIRK_FORCE_BUTTONPAD so only the Surface class is affected. --- drivers/hid/hid-multitouch.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 8f2ed29b4f35..1484668ea7c8 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -83,6 +83,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) #define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) #define MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE BIT(26) +#define MT_QUIRK_FORCE_BUTTONPAD BIT(27) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -244,6 +245,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0115 #define MT_CLS_SURFACE_TOUCHPAD 0x0116 +#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER_11 0x0117 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -455,6 +457,18 @@ static const struct mt_class mt_classes[] = { .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER_11, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | + MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS | + MT_QUIRK_FORCE_BUTTONPAD, + .export_all_inputs = true + }, { } }; @@ -860,7 +874,8 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, if ((cls->name == MT_CLS_WIN_8 || cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT || cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU || - cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP) && + cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP || + cls->name == MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER_11) && (field->application == HID_DG_TOUCHPAD || field->application == HID_DG_TOUCHSCREEN)) app->quirks |= MT_QUIRK_CONFIDENCE; @@ -984,6 +999,16 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, case 0xff000000: /* we do not want to map these: no input-oriented meaning */ return -1; + + case HID_UP_SENSOR: + /* Microsoft Surface Type Cover: whole-pad force sensor */ + if ((usage->hid & HID_USAGE) == 0x0494 && + (app->quirks & MT_QUIRK_FORCE_BUTTONPAD)) { + hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_PRESSURE); + set_abs(hi->input, ABS_PRESSURE, field, cls->sn_pressure); + return 1; + } + return -1; } return 0; @@ -1421,6 +1446,9 @@ static int mt_touch_input_configured(struct hid_device *hdev, (app->buttons_count == 1)) td->is_buttonpad = true; + if (app->quirks & MT_QUIRK_FORCE_BUTTONPAD) + td->is_buttonpad = true; + if (td->is_buttonpad) __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); @@ -2651,6 +2679,11 @@ static const struct hid_device_id mt_devices[] = { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_MICROSOFT, 0x0C46) }, + /* Microsoft Surface Pro 11 Type Cover touchpad */ + { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER_11, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x0C8D) }, + /* Google MT devices */ { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, From 1bfb647cd119dbea7189d199b6c543e2e7621acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gilerson?= Date: Sat, 28 Feb 2026 08:34:13 +0100 Subject: [PATCH 2/6] Add lid wake support for Surface Pro 11 Intel. Add the lid GPE (0x2E) used by the Surface Pro 11 with Intel Lunar Lake processor. The GPE was identified from the DSDT _L2E handler which reads the lid GPIO pad (0x0018108E) and sends Notify(LID0, 0x80). --- drivers/platform/surface/surface_gpe.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index b4496db79f39..d7844e26f4fe 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -51,6 +51,11 @@ static const struct property_entry lid_device_props_l57[] = { {}, }; +static const struct property_entry lid_device_props_l2E[] = { + PROPERTY_ENTRY_U32("gpe", 0x2E), + {}, +}; + /* * Note: When changing this, don't forget to check that the MODULE_ALIAS below * still fits. @@ -208,6 +213,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { }, .driver_data = (void *)lid_device_props_l4B, }, + { + .ident = "Surface Pro 11 (Intel)", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_11th_Edition_With_Intel_For_Business_2103"), + }, + .driver_data = (void *)lid_device_props_l2E, + }, { } }; From a2d374a7791a0938a0d3e90bdad844519c0a247b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gilerson?= Date: Fri, 15 May 2026 16:09:26 +0200 Subject: [PATCH 3/6] ASoC: SOF SDW: Add Surface Pro 11 (Intel Lunar Lake) audio support Enable speakers and microphone on the Microsoft Surface Pro 11 Business (Intel Lunar Lake) via SoundWire: - Add Lunar Lake SoundWire link tables and DMI gate (surface_lnl_rt1320_check) for the SP11 RT1320 amplifier + DMIC configuration. - Teach soc_sdw_rt_dmic to bind the RT1320 part IDs so the RT1320 DMIC shows up in the HiFi profile (linux-surface issue #1876). - Force "Playback-/Capture-SmartAmp" stream names for SDW amplifier dailinks so the topology binding matches the sof-lnl-rt1320-l0.tplg expectations. --- sound/soc/intel/boards/sof_sdw.c | 10 ++- .../intel/common/soc-acpi-intel-lnl-match.c | 72 +++++++++++++++++++ sound/soc/sdw_utils/soc_sdw_rt_dmic.c | 11 ++- sound/soc/sdw_utils/soc_sdw_utils.c | 9 ++- sound/soc/soc-acpi.c | 11 +-- 5 files changed, 104 insertions(+), 9 deletions(-) diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index c013e31d098e..683658612ca2 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -879,15 +879,21 @@ static int create_sdw_dailink(struct snd_soc_card *card, } /* create stream name according to first link id */ - if (ctx->append_dai_type) + if (ctx->append_dai_type) { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream + 2], ffs(sof_end->link_mask) - 1, type_strings[sof_end->dai_info->dai_type]); - else + } else if (sof_end->dai_info->dai_type == SOC_SDW_DAI_TYPE_AMP) { + /* Force SmartAmp name for amplifiers to match topology expectations */ + name = devm_kasprintf(dev, GFP_KERNEL, "%s-%s", + stream == SNDRV_PCM_STREAM_PLAYBACK ? "Playback" : "Capture", + type_strings[SOC_SDW_DAI_TYPE_AMP]); + } else { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream], ffs(sof_end->link_mask) - 1); + } if (!name) return -ENOMEM; diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c index 937a74a5d523..908a9556eff6 100644 --- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c @@ -8,6 +8,7 @@ #include #include +#include #include "sof-function-topology-lib.h" #include "soc-acpi-intel-sdca-quirks.h" #include "soc-acpi-intel-sdw-mockup-match.h" @@ -419,6 +420,41 @@ static const struct snd_soc_acpi_adr_device rt1320_1_group1_adr[] = { } }; +static const struct snd_soc_acpi_endpoint rt1320_amp_mic_endpoints[] = { + /* AMP Endpoint */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* DMIC Endpoint */ + { + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_adr_device rt1320_0_single_adr[] = { + { + .adr = 0x000030025D132001ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1320-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1320_0_amp_mic_adr[] = { + { + .adr = 0x000030025D132001ull, + .num_endpoints = ARRAY_SIZE(rt1320_amp_mic_endpoints), + .endpoints = rt1320_amp_mic_endpoints, + .name_prefix = "rt1320-1" + } +}; + static const struct snd_soc_acpi_adr_device rt1320_2_group2_adr[] = { { .adr = 0x000231025D132001ull, @@ -543,6 +579,24 @@ static const struct snd_soc_acpi_link_adr lnl_cs42l43_l2_cs35l56x6_l13[] = { {} }; +static const struct snd_soc_acpi_link_adr lnl_sdw_rt1320_l0[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt1320_0_single_adr), + .adr_d = rt1320_0_single_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr lnl_sdw_rt1320_l0_amp_mic[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt1320_0_amp_mic_adr), + .adr_d = rt1320_0_amp_mic_adr, + }, + {} +}; + static const struct snd_soc_acpi_link_adr lnl_rvp[] = { { .mask = BIT(0), @@ -686,7 +740,25 @@ static const struct snd_soc_acpi_link_adr lnl_sdw_rt712_vb_l2_rt1320_l1[] = { /* this table is used when there is no I2S codec present */ /* this table is used when there is no I2S codec present */ +static bool surface_lnl_rt1320_check(void *arg) +{ + if (dmi_match(DMI_SYS_VENDOR, "Microsoft Corporation") && + (dmi_match(DMI_PRODUCT_NAME, "Surface Pro for Business 11th Edition with Intel") || + dmi_match(DMI_PRODUCT_NAME, "Surface Pro 11th Edition with Intel"))) + return true; + + return false; +} + struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + { + .link_mask = BIT(0), + .links = lnl_sdw_rt1320_l0_amp_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-sdca-1amp-id2.tplg", + .machine_check = surface_lnl_rt1320_check, + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, /* mockup tests need to be first */ { .link_mask = GENMASK(3, 0), diff --git a/sound/soc/sdw_utils/soc_sdw_rt_dmic.c b/sound/soc/sdw_utils/soc_sdw_rt_dmic.c index 97be110a59b6..daf7693b76ae 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_dmic.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_dmic.c @@ -22,11 +22,18 @@ int asoc_sdw_rt_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_da component = dai->component; /* - * rt715-sdca (aka rt714) is a special case that uses different name in card->components - * and component->name_prefix. + * Some codecs use a different name in card->components than + * component->name_prefix. + * rt715-sdca (aka rt714) is one such case. + * rt1320-1 is another: the SDCA mic function on a single RT1320 + * uses name_prefix "rt1320-1" (instance suffix), but UCM expects + * "rt1320-dmic" following the naming convention for standalone mic + * functions (rt712-dmic, rt713-dmic). */ if (!strcmp(component->name_prefix, "rt714")) mic_name = devm_kasprintf(card->dev, GFP_KERNEL, "rt715-sdca"); + else if (!strcmp(component->name_prefix, "rt1320-1")) + mic_name = devm_kasprintf(card->dev, GFP_KERNEL, "rt1320-dmic"); else mic_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s", component->name_prefix); if (!mic_name) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 3848c7df1916..9b71e16fcd48 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -328,8 +328,15 @@ struct asoc_sdw_codec_info codec_info_list[] = { .widgets = generic_spk_widgets, .num_widgets = ARRAY_SIZE(generic_spk_widgets), }, + { + .direction = {false, true}, + .dai_name = "rt1320-aif2", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, }, - .dai_num = 1, + .dai_num = 2, }, { .part_id = 0x714, diff --git a/sound/soc/soc-acpi.c b/sound/soc/soc-acpi.c index 270f9777942f..aeca2a7dd0ba 100644 --- a/sound/soc/soc-acpi.c +++ b/sound/soc/soc-acpi.c @@ -126,14 +126,14 @@ struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg) EXPORT_SYMBOL_GPL(snd_soc_acpi_codec_list); #define SDW_CODEC_ADR_MASK(_adr) ((_adr) & (SDW_DISCO_LINK_ID_MASK | SDW_VERSION_MASK | \ - SDW_MFG_ID_MASK | SDW_PART_ID_MASK)) + SDW_MFG_ID_MASK | SDW_PART_ID_MASK | SDW_CLASS_ID_MASK)) /* Check if all Slaves defined on the link can be found */ bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev, const struct snd_soc_acpi_link_adr *link, struct sdw_peripherals *peripherals) { - unsigned int part_id, link_id, unique_id, mfg_id, version; + unsigned int part_id, link_id, unique_id, mfg_id, version, class_id; int i, j, k; for (i = 0; i < link->num_adr; i++) { @@ -144,6 +144,7 @@ bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev, part_id = SDW_PART_ID(adr); link_id = SDW_DISCO_LINK_ID(adr); version = SDW_VERSION(adr); + class_id = SDW_CLASS_ID(adr); for (j = 0; j < peripherals->num_peripherals; j++) { struct sdw_slave *peripheral = peripherals->array[j]; @@ -152,7 +153,8 @@ bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev, if (peripheral->bus->link_id == link_id && peripheral->id.part_id == part_id && peripheral->id.mfg_id == mfg_id && - peripheral->id.sdw_version == version) + peripheral->id.sdw_version == version && + peripheral->id.class_id == class_id) reported_part_count++; } @@ -163,7 +165,8 @@ bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev, if (peripheral->bus->link_id != link_id || peripheral->id.part_id != part_id || peripheral->id.mfg_id != mfg_id || - peripheral->id.sdw_version != version) + peripheral->id.sdw_version != version || + peripheral->id.class_id != class_id) continue; /* find out how many identical parts are expected */ From c5e9b4b2eaa360425258b27c73d365847d1f3b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gilerson?= Date: Fri, 15 May 2026 16:10:04 +0200 Subject: [PATCH 4/6] PCI: Add Surface Pro 11 (Lunar Lake) devices to shutdown ops quirk On the Microsoft Surface Pro 11 Business (Intel Lunar Lake), the EFI ResetSystem call with EFI_RESET_SHUTDOWN fails to power off the system when PCI shutdown callbacks have run for certain devices. Instead of shutting down, the call returns and the system stays on, draining the battery completely overnight. This is the same class of firmware bug seen on the Surface Pro 9 and Surface Laptop 5 (Alder Lake), where Thunderbolt and GPU driver shutdown sequences leave the EFI firmware in a state that prevents power off. Add the Surface Pro 11 to the no_shutdown DMI table (matched via SKU, since the product name is just "Surface Pro" and overlaps with other SKUs), and register PCI fixups for the five Lunar Lake devices that trigger the bug: 8086:a831 Thunderbolt 4 USB Controller 8086:a833 Thunderbolt 4 NHI 8086:a84e Thunderbolt 4 PCI Express Root Port #0 8086:a84f Thunderbolt 4 PCI Express Root Port #1 8086:64a0 GPU (Intel Arc Graphics 130V/140V) --- drivers/pci/quirks.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index fdc410bf70e5..bbfc3f33b835 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6367,6 +6367,15 @@ static const struct dmi_system_id no_shutdown_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio 2"), }, }, + { + .ident = "Microsoft Surface Pro 11 (Intel)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro"), + DMI_MATCH(DMI_PRODUCT_SKU, + "Surface_Pro_11th_Edition_With_Intel"), + }, + }, {} }; @@ -6390,3 +6399,10 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa71e, quirk_no_shutdown); // Thu DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73e, quirk_no_shutdown); // Thunderbolt 4 NHI DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa7a0, quirk_no_shutdown); // GPU + +/* Lunar Lake (Surface Pro 11 Business) */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa831, quirk_no_shutdown); // Thunderbolt 4 USB Controller +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa833, quirk_no_shutdown); // Thunderbolt 4 NHI +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa84e, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port #0 +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa84f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port #1 +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x64a0, quirk_no_shutdown); // GPU (Arc Graphics 130V/140V) From b7469c4fa7714bb90ed59648397ef9a62bcae177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gilerson?= Date: Fri, 15 May 2026 16:12:01 +0200 Subject: [PATCH 5/6] media: Add Surface Pro 11 (Intel IPU7) camera support Add support for cameras on the Microsoft Surface Pro 11 Business (Intel Lunar Lake / IPU7): - New Sony IMX681 front camera driver (3844x2640, RAW10, 2-lane CSI-2). Register sequences reverse-engineered from Windows I2C traces and other IMX sensors. Uses H-flip (reg 0x0101=0x01) to convert native RGGB Bayer to GRBG matching the Windows AIQB calibration data. - OV13858 rear camera: add get_selection pad op for crop / native size reporting (required by the IPU7 userspace stack). The v6.17-era clock-frequency probe-defer workaround is dropped because v6.18's devm_v4l2_sensor_clk_get() helper now handles deferral internally. - ipu-bridge: register IMX681 (SONY0681) at 969.6 MHz link frequency so IPU7 MIPI CSI-2 enumeration sees the SP11 front sensor. - ipu7: declare MODULE_FIRMWARE entries for the three IPU firmware blobs (ipu7, ipu7p5, ipu8) so initrd builders pick them up. --- drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx681.c | 900 +++++++++++++++++++++++++++ drivers/media/i2c/ov13858.c | 21 + drivers/media/pci/intel/ipu-bridge.c | 2 + drivers/staging/media/ipu7/ipu7.c | 4 + 6 files changed, 938 insertions(+) create mode 100644 drivers/media/i2c/imx681.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index cdd7ba5da0d5..ebeae9e2ae0a 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -196,6 +196,16 @@ config VIDEO_IMX283 To compile this driver as a module, choose M here: the module will be called imx283. +config VIDEO_IMX681 + tristate "Sony IMX681 sensor support" + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for the Sony IMX681 + CMOS image sensor (front camera on Surface Pro 11). + + To compile this driver as a module, choose M here: the + module will be called imx681. + config VIDEO_IMX290 tristate "Sony IMX290 sensor support" select REGMAP_I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 57cdd8dc96f6..ce8ec464d8d4 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_VIDEO_IMX219) += imx219.o obj-$(CONFIG_VIDEO_IMX258) += imx258.o obj-$(CONFIG_VIDEO_IMX274) += imx274.o obj-$(CONFIG_VIDEO_IMX283) += imx283.o +obj-$(CONFIG_VIDEO_IMX681) += imx681.o obj-$(CONFIG_VIDEO_IMX290) += imx290.o obj-$(CONFIG_VIDEO_IMX296) += imx296.o obj-$(CONFIG_VIDEO_IMX319) += imx319.o diff --git a/drivers/media/i2c/imx681.c b/drivers/media/i2c/imx681.c new file mode 100644 index 000000000000..8a07aaa006bd --- /dev/null +++ b/drivers/media/i2c/imx681.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sony IMX681 CMOS Image Sensor Driver + * + * Front camera on Surface Pro 11 Business (Intel/Lunar Lake). + * Register sequences reverse-engineered from Windows I2C traces. + * + * Copyright (C) 2025 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Chip ID register and expected value */ +#define IMX681_REG_CHIP_ID CCI_REG16(0x0016) +#define IMX681_CHIP_ID 0x0681 + +/* Mode select */ +#define IMX681_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX681_MODE_STANDBY 0x00 +#define IMX681_MODE_STREAMING 0x01 + +/* Group parameter hold */ +#define IMX681_REG_GROUP_HOLD CCI_REG8(0x0104) + +/* Exposure (coarse integration time, 24-bit) */ +#define IMX681_REG_EXPOSURE CCI_REG24(0x0229) +#define IMX681_EXPOSURE_MIN 4 +#define IMX681_EXPOSURE_MAX 3173 /* frame_length - 4 */ +#define IMX681_EXPOSURE_OFFSET 4 /* frame_length - exposure_max */ +#define IMX681_EXPOSURE_DEFAULT 1907 /* from Windows trace */ + +/* Analog gain */ +#define IMX681_REG_ANALOG_GAIN CCI_REG16(0x0204) +#define IMX681_ANA_GAIN_MIN 0 +#define IMX681_ANA_GAIN_MAX 1020 /* combined analog+digital */ +#define IMX681_ANA_GAIN_DEFAULT 0 + +/* Digital gain */ +#define IMX681_REG_DIGITAL_GAIN CCI_REG16(0x020E) +#define IMX681_DIG_GAIN_MIN 0x0100 /* 1.0x */ +#define IMX681_DIG_GAIN_MAX 0x0FFF +#define IMX681_DIG_GAIN_DEFAULT 0x0100 + +/* Test pattern */ +#define IMX681_REG_TEST_PATTERN CCI_REG16(0x0600) + +/* Frame length (24-bit, for streaming updates) */ +#define IMX681_REG_FRAME_LENGTH CCI_REG24(0x033D) + +/* Image dimensions — native sensor output */ +#define IMX681_WIDTH 3844 +#define IMX681_HEIGHT 2640 +#define IMX681_LINE_LENGTH_PCK 7552 /* 0x1D80 */ +#define IMX681_FRAME_LENGTH_LINES 3177 /* 0x0C69 */ +#define IMX681_FRAME_LENGTH_MAX 0xFFFF /* 24-bit reg, limit to 16-bit */ + +/* MIPI lanes */ +#define IMX681_NUM_LANES 2 + +/* + * Link frequency derived from PLL settings in Windows trace: + * EXCK=19.2MHz, PLL2_MUL=303, PLL2_PRE_DIV=3 + * OP output = 19.2 * 303 / 3 = 1939.2 MHz (MIPI bit rate) + * Link freq = 1939.2 / 2 (DDR) = 969.6 MHz + */ +#define IMX681_LINK_FREQ 969600000LL + +/* Pixel rate = link_freq * 2 (DDR) * num_lanes / bpp */ +#define IMX681_PIXEL_RATE (IMX681_LINK_FREQ * 2 * IMX681_NUM_LANES / 10) + +/* Power-on delay after reset deassert */ +#define IMX681_RESET_DELAY_US 1000 +#define IMX681_RESET_DELAY_RANGE_US 1000 + +/* Post-standby-cancel stabilisation delays */ +#define IMX681_INIT_DELAY_US 10000 + +#define IMAGE_PAD 0 + +static const s64 imx681_link_frequencies[] = { + IMX681_LINK_FREQ, +}; + +/* + * Sensor init register sequence, captured from Windows I2C traces. + * This configures the sensor for 3844x2640 RAW10 output at ~30fps + * with 2-lane MIPI CSI-2, 19.2MHz input clock. + */ +static const struct cci_reg_sequence imx681_init_regs[] = { + /* Software standby */ + { CCI_REG8(0x0100), 0x00 }, + /* External clock frequency = 19.2 MHz (encoded as MHz * 256) */ + { CCI_REG16(0x0136), 0x1333 }, + /* Vendor specific configuration */ + { CCI_REG16(0x002C), 0x0505 }, + /* CSI-2 signaling mode */ + { CCI_REG8(0x0111), 0x02 }, + /* Image orientation: H-flip to match Windows AIQB (RGGB native → GRBG) */ + { CCI_REG8(0x0101), 0x01 }, + /* Vendor access unlock sequence */ + { CCI_REG8(0x30EB), 0x05 }, + { CCI_REG8(0x30EB), 0x0C }, + /* Vendor specific */ + { CCI_REG16(0x300A), 0xFFFF }, + { CCI_REG16(0x3532), 0xFFFF }, + /* LINE_LENGTH_PCK = 7552 */ + { CCI_REG16(0x0342), 0x1D80 }, + /* Frame length = 3177 */ + { CCI_REG16(0x033E), 0x0C69 }, + /* Crop window start: X_ADD_STA[7:0]=100, Y_ADD_STA=256 */ + { CCI_REG24(0x0345), 0x640100 }, + /* Crop window end: X_ADD_END[7:0]=103, Y_ADD_END=2895 */ + { CCI_REG24(0x0349), 0x670B4F }, + /* Digital crop / vendor config */ + { CCI_REG24(0x040D), 0x040A50 }, + /* X_OUTPUT_SIZE=3844, Y_OUTPUT_SIZE=2640 */ + { CCI_REG32(0x034C), 0x0F040A50 }, + /* PLL multiplier = 225 */ + { CCI_REG8(0x0307), 0xE1 }, + /* PLL2: pre_div=3, multiplier=303 (0x012F) */ + { CCI_REG24(0x030D), 0x03012F }, + /* Frame duration initial */ + { CCI_REG16(0x022A), 0x0C61 }, + /* Vendor specific registers */ + { CCI_REG8(0x7E9B), 0x02 }, + { CCI_REG8(0x0368), 0x00 }, + { CCI_REG8(0xD383), 0x01 }, +}; + +/* + * First exposure settings applied before stream-on. + * Uses group parameter hold to ensure atomic update. + */ +static const struct cci_reg_sequence imx681_first_exposure[] = { + { CCI_REG8(0x0104), 0x01 }, /* Group hold ON */ + { CCI_REG24(0x033D), 0x000C69 }, /* Frame length = 3177 */ + { CCI_REG24(0x0229), 0x000773 }, /* Exposure = 1907 lines */ + { CCI_REG16(0x0204), 0x0000 }, /* Analog gain = 0 (1x) */ + { CCI_REG16(0x020E), 0x0100 }, /* Digital gain = 1.0x */ + { CCI_REG8(0x0104), 0x00 }, /* Group hold OFF */ +}; + +static const char * const imx681_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "Eight Vertical Colour Bars", + "Colour Bars With Fade to Grey", + "Pseudorandom Sequence (PN9)", +}; + +static const u32 imx681_mbus_codes[] = { + MEDIA_BUS_FMT_SGRBG10_1X10, +}; + +/* Regulator supplies */ +static const char * const imx681_supply_names[] = { + "avdd", /* Analog 2.8V */ + "dvdd", /* Digital 1.05V */ + "dovdd", /* I/O 1.8V */ +}; + +#define IMX681_NUM_SUPPLIES ARRAY_SIZE(imx681_supply_names) + +struct imx681 { + struct device *dev; + struct regmap *cci; + + struct v4l2_subdev sd; + struct media_pad pad; + + struct clk *xclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX681_NUM_SUPPLIES]; + + /* V4L2 Controls */ + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + + unsigned long link_freq_bitmap; +}; + +static inline struct imx681 *to_imx681(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct imx681, sd); +} + +static int imx681_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx681 *imx681 = container_of(ctrl->handler, struct imx681, + ctrl_handler); + s64 exposure_max; + int ret = 0; + + /* Update exposure max when VBLANK changes (even when not streaming) */ + if (ctrl->id == V4L2_CID_VBLANK) { + exposure_max = IMX681_HEIGHT + ctrl->val - IMX681_EXPOSURE_OFFSET; + __v4l2_ctrl_modify_range(imx681->exposure, + IMX681_EXPOSURE_MIN, exposure_max, + 1, IMX681_EXPOSURE_DEFAULT); + } + + /* Only apply controls to hardware when streaming */ + if (!pm_runtime_get_if_active(imx681->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x01, &ret); + cci_write(imx681->cci, IMX681_REG_FRAME_LENGTH, + IMX681_HEIGHT + ctrl->val, &ret); + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x00, &ret); + dev_dbg(imx681->dev, "set frame_length: %d, ret=%d\n", + IMX681_HEIGHT + ctrl->val, ret); + break; + + case V4L2_CID_EXPOSURE: + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x01, &ret); + cci_write(imx681->cci, IMX681_REG_EXPOSURE, ctrl->val, &ret); + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x00, &ret); + dev_dbg(imx681->dev, "set exposure: %d, ret=%d\n", + ctrl->val, ret); + break; + + case V4L2_CID_ANALOGUE_GAIN: { + /* + * Gain formula: gain = 1024/(1024-code). + * Analog max is code 960 (16x). For codes above 960, + * set analog to max and use digital gain for the rest. + * Digital gain register: 0x0100 = 1.0x, value = gain * 256. + */ + int ana_code = min(ctrl->val, 960); + int dig_reg = IMX681_DIG_GAIN_MIN; /* 0x0100 = 1.0x */ + + if (ctrl->val > 960) { + /* + * digital = total_gain / analog_gain + * = (1024/(1024-code)) / (1024/64) + * = 64 / (1024-code) + * dig_reg = digital * 256 = 16384 / (1024-code) + */ + dig_reg = 16384 / (1024 - ctrl->val); + dig_reg = clamp(dig_reg, (int)IMX681_DIG_GAIN_MIN, + (int)IMX681_DIG_GAIN_MAX); + } + + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x01, &ret); + cci_write(imx681->cci, IMX681_REG_ANALOG_GAIN, ana_code, + &ret); + cci_write(imx681->cci, IMX681_REG_DIGITAL_GAIN, dig_reg, + &ret); + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x00, &ret); + dev_dbg(imx681->dev, "set gain code %d: analog=%d digital=0x%x, ret=%d\n", + ctrl->val, ana_code, dig_reg, ret); + break; + } + + case V4L2_CID_DIGITAL_GAIN: + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x01, &ret); + cci_write(imx681->cci, IMX681_REG_DIGITAL_GAIN, ctrl->val, + &ret); + cci_write(imx681->cci, IMX681_REG_GROUP_HOLD, 0x00, &ret); + dev_dbg(imx681->dev, "set digital gain: %d, ret=%d\n", + ctrl->val, ret); + break; + + case V4L2_CID_TEST_PATTERN: + ret = cci_write(imx681->cci, IMX681_REG_TEST_PATTERN, + ctrl->val, NULL); + break; + + default: + dev_dbg(imx681->dev, "unhandled ctrl id: 0x%x val: 0x%x\n", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(imx681->dev); + return ret; +} + +static const struct v4l2_ctrl_ops imx681_ctrl_ops = { + .s_ctrl = imx681_set_ctrl, +}; + +static int imx681_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(imx681_mbus_codes)) + return -EINVAL; + + code->code = imx681_mbus_codes[code->index]; + return 0; +} + +static bool imx681_is_valid_mbus_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(imx681_mbus_codes); i++) + if (imx681_mbus_codes[i] == code) + return true; + return false; +} + +static int imx681_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index > 0) + return -EINVAL; + + if (!imx681_is_valid_mbus_code(fse->code)) + return -EINVAL; + + fse->min_width = IMX681_WIDTH; + fse->max_width = IMX681_WIDTH; + fse->min_height = IMX681_HEIGHT; + fse->max_height = IMX681_HEIGHT; + + return 0; +} + +static int imx681_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_state_get_format(state, IMAGE_PAD); + format->width = IMX681_WIDTH; + format->height = IMX681_HEIGHT; + format->code = MEDIA_BUS_FMT_SGRBG10_1X10; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + format->ycbcr_enc = V4L2_YCBCR_ENC_601; + format->quantization = V4L2_QUANTIZATION_FULL_RANGE; + format->xfer_func = V4L2_XFER_FUNC_NONE; + + return 0; +} + +static int imx681_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *format; + + /* Fixed resolution, fixed Bayer order */ + fmt->format.width = IMX681_WIDTH; + fmt->format.height = IMX681_HEIGHT; + if (!imx681_is_valid_mbus_code(fmt->format.code)) + fmt->format.code = imx681_mbus_codes[0]; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + + format = v4l2_subdev_state_get_format(state, fmt->pad); + *format = fmt->format; + + return 0; +} + +static int imx681_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = IMX681_WIDTH; + sel->r.height = IMX681_HEIGHT; + return 0; + default: + return -EINVAL; + } +} + +static int imx681_start_streaming(struct imx681 *imx681) +{ + int ret; + + dev_dbg(imx681->dev, "starting stream: %dx%d RAW10 2-lane\n", + IMX681_WIDTH, IMX681_HEIGHT); + + /* Write init register sequence */ + ret = cci_multi_reg_write(imx681->cci, imx681_init_regs, + ARRAY_SIZE(imx681_init_regs), NULL); + if (ret) { + dev_err(imx681->dev, "failed to write init regs: %d\n", ret); + return ret; + } + + dev_dbg(imx681->dev, "init registers written successfully\n"); + + /* Wait for sensor to stabilise after configuration */ + usleep_range(IMX681_INIT_DELAY_US, IMX681_INIT_DELAY_US + 1000); + + /* Apply first exposure settings with group hold */ + ret = cci_multi_reg_write(imx681->cci, imx681_first_exposure, + ARRAY_SIZE(imx681_first_exposure), NULL); + if (ret) { + dev_err(imx681->dev, "failed to write exposure: %d\n", ret); + return ret; + } + + /* Apply any pending V4L2 control values */ + ret = __v4l2_ctrl_handler_setup(imx681->sd.ctrl_handler); + if (ret) { + dev_err(imx681->dev, "failed to apply controls: %d\n", ret); + return ret; + } + + /* Start streaming */ + ret = cci_write(imx681->cci, IMX681_REG_MODE_SELECT, + IMX681_MODE_STREAMING, NULL); + if (ret) { + dev_err(imx681->dev, "failed to start streaming: %d\n", ret); + return ret; + } + + dev_dbg(imx681->dev, "streaming started\n"); + return 0; +} + +static int imx681_stop_streaming(struct imx681 *imx681) +{ + int ret; + + ret = cci_write(imx681->cci, IMX681_REG_MODE_SELECT, + IMX681_MODE_STANDBY, NULL); + if (ret) + dev_err(imx681->dev, "failed to stop streaming: %d\n", ret); + + return ret; +} + +static int imx681_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct imx681 *imx681 = to_imx681(sd); + int ret; + + if (pad != IMAGE_PAD) + return -EINVAL; + + ret = pm_runtime_get_sync(imx681->dev); + if (ret < 0) { + pm_runtime_put_noidle(imx681->dev); + return ret; + } + + ret = imx681_start_streaming(imx681); + if (ret) + pm_runtime_put_autosuspend(imx681->dev); + + return ret; +} + +static int imx681_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct imx681 *imx681 = to_imx681(sd); + + if (pad != IMAGE_PAD) + return -EINVAL; + + imx681_stop_streaming(imx681); + pm_runtime_put_autosuspend(imx681->dev); + + return 0; +} + +static const struct v4l2_subdev_video_ops imx681_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops imx681_pad_ops = { + .enum_mbus_code = imx681_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx681_set_pad_format, + .get_selection = imx681_get_selection, + .enum_frame_size = imx681_enum_frame_size, + .enable_streams = imx681_enable_streams, + .disable_streams = imx681_disable_streams, +}; + +static const struct v4l2_subdev_ops imx681_subdev_ops = { + .video = &imx681_video_ops, + .pad = &imx681_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx681_internal_ops = { + .init_state = imx681_init_state, +}; + +/* Power management */ +static int imx681_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx681 *imx681 = to_imx681(sd); + int ret; + + dev_dbg(imx681->dev, "power on\n"); + + ret = regulator_bulk_enable(IMX681_NUM_SUPPLIES, imx681->supplies); + if (ret) { + dev_err(imx681->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(imx681->xclk); + if (ret) { + dev_err(imx681->dev, "failed to enable clock: %d\n", ret); + goto err_reg_disable; + } + + /* Deassert reset (active low) */ + gpiod_set_value_cansleep(imx681->reset_gpio, 0); + + usleep_range(IMX681_RESET_DELAY_US, + IMX681_RESET_DELAY_US + IMX681_RESET_DELAY_RANGE_US); + + return 0; + +err_reg_disable: + regulator_bulk_disable(IMX681_NUM_SUPPLIES, imx681->supplies); + return ret; +} + +static int imx681_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx681 *imx681 = to_imx681(sd); + + dev_dbg(imx681->dev, "power off\n"); + + /* Assert reset */ + gpiod_set_value_cansleep(imx681->reset_gpio, 1); + clk_disable_unprepare(imx681->xclk); + regulator_bulk_disable(IMX681_NUM_SUPPLIES, imx681->supplies); + + return 0; +} + +static int imx681_identify_module(struct imx681 *imx681) +{ + u64 val; + int ret; + + ret = cci_read(imx681->cci, IMX681_REG_CHIP_ID, &val, NULL); + if (ret) { + dev_err(imx681->dev, + "failed to read chip ID register 0x0016: %d\n", ret); + return ret; + } + + if (val != IMX681_CHIP_ID) { + dev_err(imx681->dev, "chip ID mismatch: 0x%04llx != 0x%04x\n", + val, IMX681_CHIP_ID); + return -EIO; + } + + return 0; +} + +static int imx681_init_controls(struct imx681 *imx681) +{ + struct v4l2_ctrl_handler *ctrl_hdlr = &imx681->ctrl_handler; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *link_freq; + s64 hblank, vblank; + int ret; + + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9); + if (ret) + return ret; + + /* Pixel rate (read-only) */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx681_ctrl_ops, + V4L2_CID_PIXEL_RATE, IMX681_PIXEL_RATE, + IMX681_PIXEL_RATE, 1, IMX681_PIXEL_RATE); + + /* Link frequency (read-only) */ + link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx681_ctrl_ops, + V4L2_CID_LINK_FREQ, + __fls(imx681->link_freq_bitmap), + __ffs(imx681->link_freq_bitmap), + imx681_link_frequencies); + if (link_freq) + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* Horizontal blanking (read-only, fixed) */ + hblank = IMX681_LINE_LENGTH_PCK - IMX681_WIDTH; + imx681->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx681_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + if (imx681->hblank) + imx681->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* Vertical blanking (writable to allow longer exposures) */ + vblank = IMX681_FRAME_LENGTH_LINES - IMX681_HEIGHT; + imx681->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx681_ctrl_ops, + V4L2_CID_VBLANK, vblank, + IMX681_FRAME_LENGTH_MAX - IMX681_HEIGHT, + 1, vblank); + + /* Exposure */ + imx681->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx681_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX681_EXPOSURE_MIN, + IMX681_EXPOSURE_MAX, 1, + IMX681_EXPOSURE_DEFAULT); + + /* Analog gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx681_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX681_ANA_GAIN_MIN, IMX681_ANA_GAIN_MAX, 1, + IMX681_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx681_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX681_DIG_GAIN_MIN, IMX681_DIG_GAIN_MAX, 1, + IMX681_DIG_GAIN_DEFAULT); + + /* Test pattern */ + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx681_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx681_test_pattern_menu) - 1, + 0, 0, imx681_test_pattern_menu); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(imx681->dev, "control init failed: %d\n", ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(imx681->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx681_ctrl_ops, + &props); + if (ret) + goto error; + + imx681->sd.ctrl_handler = ctrl_hdlr; + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + return ret; +} + +static int imx681_parse_endpoint(struct imx681 *imx681) +{ + struct fwnode_handle *fwnode = dev_fwnode(imx681->dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) { + dev_err(imx681->dev, "no endpoint found in firmware node\n"); + return -ENXIO; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) { + dev_err(imx681->dev, "failed to parse endpoint: %d\n", ret); + return ret; + } + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX681_NUM_LANES) { + dev_err(imx681->dev, + "expected %d data lanes, got %d\n", + IMX681_NUM_LANES, + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto done; + } + + ret = v4l2_link_freq_to_bitmap(imx681->dev, + bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + imx681_link_frequencies, + ARRAY_SIZE(imx681_link_frequencies), + &imx681->link_freq_bitmap); + if (ret) + dev_err(imx681->dev, "link frequency mismatch: %d\n", ret); + +done: + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; +} + +static int imx681_probe(struct i2c_client *client) +{ + struct imx681 *imx681; + unsigned int i; + int ret; + + imx681 = devm_kzalloc(&client->dev, sizeof(*imx681), GFP_KERNEL); + if (!imx681) + return -ENOMEM; + + imx681->dev = &client->dev; + + /* Initialise V4L2 subdev */ + v4l2_i2c_subdev_init(&imx681->sd, client, &imx681_subdev_ops); + + /* Initialise CCI regmap for 16-bit register addresses */ + imx681->cci = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx681->cci)) { + ret = PTR_ERR(imx681->cci); + dev_err(imx681->dev, "failed to init CCI: %d\n", ret); + return ret; + } + + /* Get clock (optional - INT3472 provides it on Surface devices) */ + imx681->xclk = devm_clk_get_optional(imx681->dev, NULL); + if (IS_ERR(imx681->xclk)) + return dev_err_probe(imx681->dev, PTR_ERR(imx681->xclk), + "failed to get clock\n"); + + if (imx681->xclk) + dev_dbg(imx681->dev, "clock rate: %lu Hz\n", + clk_get_rate(imx681->xclk)); + + /* Get regulators (optional) */ + for (i = 0; i < IMX681_NUM_SUPPLIES; i++) + imx681->supplies[i].supply = imx681_supply_names[i]; + + ret = devm_regulator_bulk_get(imx681->dev, IMX681_NUM_SUPPLIES, + imx681->supplies); + if (ret) { + dev_dbg(imx681->dev, + "regulators not available (expected on ACPI): %d\n", + ret); + /* Continue without regulators - INT3472 may handle power */ + } + + /* Get reset GPIO (optional) */ + imx681->reset_gpio = devm_gpiod_get_optional(imx681->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(imx681->reset_gpio)) + return dev_err_probe(imx681->dev, + PTR_ERR(imx681->reset_gpio), + "failed to get reset GPIO\n"); + + /* Parse CSI-2 endpoint */ + ret = imx681_parse_endpoint(imx681); + if (ret) { + dev_err(imx681->dev, "endpoint parse failed: %d\n", ret); + return ret; + } + + /* Power on and verify chip ID */ + ret = imx681_power_on(imx681->dev); + if (ret) { + dev_err(imx681->dev, "power on failed: %d\n", ret); + return ret; + } + + ret = imx681_identify_module(imx681); + if (ret) + goto error_power_off; + + /* Enable runtime PM */ + pm_runtime_set_active(imx681->dev); + pm_runtime_get_noresume(imx681->dev); + pm_runtime_enable(imx681->dev); + pm_runtime_set_autosuspend_delay(imx681->dev, 1000); + pm_runtime_use_autosuspend(imx681->dev); + + /* Init V4L2 controls */ + ret = imx681_init_controls(imx681); + if (ret) + goto error_pm; + + /* Setup subdev */ + imx681->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx681->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + imx681->sd.internal_ops = &imx681_internal_ops; + + /* Init media entity */ + imx681->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&imx681->sd.entity, 1, &imx681->pad); + if (ret) { + dev_err(imx681->dev, "media entity init failed: %d\n", ret); + goto error_handler_free; + } + + imx681->sd.state_lock = imx681->ctrl_handler.lock; + ret = v4l2_subdev_init_finalize(&imx681->sd); + if (ret < 0) { + dev_err(imx681->dev, "subdev init finalize failed: %d\n", ret); + goto error_media_entity; + } + + ret = v4l2_async_register_subdev_sensor(&imx681->sd); + if (ret < 0) { + dev_err(imx681->dev, + "async register subdev failed: %d\n", ret); + goto error_subdev_cleanup; + } + + pm_runtime_put_autosuspend(imx681->dev); + + dev_info(imx681->dev, + "IMX681 probed successfully: %dx%d @ %lld Hz link freq\n", + IMX681_WIDTH, IMX681_HEIGHT, IMX681_LINK_FREQ); + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&imx681->sd); +error_media_entity: + media_entity_cleanup(&imx681->sd.entity); +error_handler_free: + v4l2_ctrl_handler_free(imx681->sd.ctrl_handler); +error_pm: + pm_runtime_disable(imx681->dev); + pm_runtime_set_suspended(imx681->dev); +error_power_off: + imx681_power_off(imx681->dev); + return ret; +} + +static void imx681_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx681 *imx681 = to_imx681(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(&imx681->sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(imx681->sd.ctrl_handler); + + pm_runtime_disable(imx681->dev); + if (!pm_runtime_status_suspended(imx681->dev)) + imx681_power_off(imx681->dev); + pm_runtime_set_suspended(imx681->dev); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx681_pm_ops, imx681_power_off, + imx681_power_on, NULL); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id imx681_acpi_ids[] = { + { "SONY0681" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, imx681_acpi_ids); +#endif + +static const struct of_device_id imx681_dt_ids[] = { + { .compatible = "sony,imx681" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx681_dt_ids); + +static struct i2c_driver imx681_i2c_driver = { + .driver = { + .name = "imx681", + .pm = pm_ptr(&imx681_pm_ops), + .acpi_match_table = ACPI_PTR(imx681_acpi_ids), + .of_match_table = imx681_dt_ids, + }, + .probe = imx681_probe, + .remove = imx681_remove, +}; +module_i2c_driver(imx681_i2c_driver); + +MODULE_DESCRIPTION("Sony IMX681 CMOS Image Sensor Driver"); +MODULE_AUTHOR("Andre Gilerson "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c index 85cd0c98af03..67bba924f413 100644 --- a/drivers/media/i2c/ov13858.c +++ b/drivers/media/i2c/ov13858.c @@ -1649,10 +1649,31 @@ static const struct v4l2_subdev_video_ops ov13858_video_ops = { .s_stream = ov13858_set_stream, }; +static int ov13858_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + /* Pixel array is 4224x3136 (max supported mode) */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = 4224; + sel->r.height = 3136; + return 0; + default: + return -EINVAL; + } +} + static const struct v4l2_subdev_pad_ops ov13858_pad_ops = { .enum_mbus_code = ov13858_enum_mbus_code, .get_fmt = ov13858_get_pad_format, .set_fmt = ov13858_set_pad_format, + .get_selection = ov13858_get_selection, .enum_frame_size = ov13858_enum_frame_size, }; diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 1ef31d655392..b1d0ce873ecf 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -65,6 +65,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), /* Omnivision OV13858 - Surface Pro 9 */ IPU_SENSOR_CONFIG("OVTID858", 4, 540000000), + /* Sony IMX681 - Surface Pro 11 */ + IPU_SENSOR_CONFIG("SONY0681", 1, 969600000), /* Omnivision OV2740 */ IPU_SENSOR_CONFIG("INT3474", 1, 180000000), /* Omnivision OV5670 */ diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index 5cddc09c72bf..65c42da2ad85 100644 --- a/drivers/staging/media/ipu7/ipu7.c +++ b/drivers/staging/media/ipu7/ipu7.c @@ -2776,3 +2776,7 @@ MODULE_AUTHOR("Qingwu Zhang "); MODULE_AUTHOR("Intel"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Intel ipu7 pci driver"); + +MODULE_FIRMWARE(IPU7_FIRMWARE_NAME); +MODULE_FIRMWARE(IPU7P5_FIRMWARE_NAME); +MODULE_FIRMWARE(IPU8_FIRMWARE_NAME); From 8ab9347b169233b08950086f49dafd1d58bb5bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gilerson?= Date: Fri, 15 May 2026 16:12:55 +0200 Subject: [PATCH 6/6] media: i2c: Add VD55G0 IR camera driver for Surface Pro 11 Add support for the STMicroelectronics VD55G0 global shutter IR sensor used in the Surface Pro 11 (Intel Lunar Lake) for Windows Hello face authentication. The driver is based on the official STMicroelectronics out-of-tree driver (https://github.com/STMicroelectronics/vd55g0-linux-driver) with the following adaptations for kernel integration and Surface Pro 11 support: - Added ACPI device ID table for SMO55F0 and acpi_match_table to the i2c_driver, enabling automatic binding on ACPI-enumerated devices. - Changed fwnode endpoint lookup from of_fwnode_handle() to dev_fwnode() with ACPI fallback: when no fwnode graph endpoint is found on ACPI systems, defaults to 1-lane MIPI at 600 Mbps data rate. - ipu-bridge: register SMO55F0 so the IPU7 MIPI CSI-2 framework can find the IR sensor. - int3472/common.c: recognise VD55G0 as a sensor device. - int3472/discrete.c: add a "0x10" GPIO power-rail type so the SP11's secondary power rail is registered as a regulator. --- drivers/media/i2c/Kconfig | 12 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/vd55g0.c | 2181 +++++++++++++++++ drivers/media/i2c/vd55g0_patches.h | 587 +++++ drivers/media/pci/intel/ipu-bridge.c | 2 + drivers/platform/x86/intel/int3472/common.c | 2 +- drivers/platform/x86/intel/int3472/discrete.c | 11 +- 7 files changed, 2794 insertions(+), 2 deletions(-) create mode 100644 drivers/media/i2c/vd55g0.c create mode 100644 drivers/media/i2c/vd55g0_patches.h diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index ebeae9e2ae0a..086ae87b7fba 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -755,6 +755,18 @@ config VIDEO_S5K6A3 This is a V4L2 sensor driver for Samsung S5K6A3 raw camera sensor. +config VIDEO_VD55G0 + tristate "ST VD55G0 IR sensor support" + select V4L2_CCI_I2C + depends on GPIOLIB + help + This is a Video4Linux2 sensor driver for the ST VD55G0 + IR global shutter camera sensor, used in the Surface Pro 11 + for Windows Hello face authentication. + + To compile this driver as a module, choose M here: the + module will be called vd55g0. + config VIDEO_VD55G1 tristate "ST VD55G1 sensor support" select V4L2_CCI_I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index ce8ec464d8d4..bd452de9a05c 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -159,6 +159,7 @@ obj-$(CONFIG_VIDEO_TW9910) += tw9910.o obj-$(CONFIG_VIDEO_UDA1342) += uda1342.o obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o +obj-$(CONFIG_VIDEO_VD55G0) += vd55g0.o obj-$(CONFIG_VIDEO_VD55G1) += vd55g1.o obj-$(CONFIG_VIDEO_VD56G3) += vd56g3.o obj-$(CONFIG_VIDEO_VGXY61) += vgxy61.o diff --git a/drivers/media/i2c/vd55g0.c b/drivers/media/i2c/vd55g0.c new file mode 100644 index 000000000000..943dffec5ad6 --- /dev/null +++ b/drivers/media/i2c/vd55g0.c @@ -0,0 +1,2181 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for VD55G0 global shutter sensor family driver + * + * Copyright (C) 2024 STMicroelectronics SA + * + * Adapter with small changes by André Gilerson + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Backward compatibility */ +#if KERNEL_VERSION(6, 12, 0) > LINUX_VERSION_CODE +#include +#else +#include +#endif + +#if KERNEL_VERSION(5, 18, 0) > LINUX_VERSION_CODE +#define MIPI_CSI2_DT_RAW8 0x2a +#define MIPI_CSI2_DT_RAW10 0x2b +#define MIPI_CSI2_DT_RAW12 0x2c +#define MIPI_CSI2_DT_RAW14 0x2d +#define MIPI_CSI2_DT_RAW16 0x2e +#else +#include +#endif + +#if KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE +#define HZ_PER_MHZ 1000000UL +#else +#include +#endif + +#define VD55G0_REG_8BIT(n) ((1 << 16) | (n)) +#define VD55G0_REG_16BIT(n) ((2 << 16) | (n)) +#define VD55G0_REG_32BIT(n) ((4 << 16) | (n)) +#define VD55G0_REG_SIZE_SHIFT 16 +#define VD55G0_REG_ADDR_MASK 0xffff + +#define VD55G0_REG_MODEL_ID VD55G0_REG_16BIT(0x0000) +#define VD55G0_MODEL_ID 0x4730 +#define VD55G0_REG_REVISION VD55G0_REG_16BIT(0x0004) +#define VD55G0_REVISION_CUT1 0x1111 +#define VD55G0_REVISION_CUT2 0x1120 +#define VD55G0_REG_FWPATCH_REVISION VD55G0_REG_16BIT(0x0022) +#define VD55G0_REG_FWPATCH_START_ADDR VD55G0_REG_8BIT(0x2000) +#define VD55G0_REG_SYSTEM_FSM VD55G0_REG_8BIT(0x002c) +#define VD55G0_SYSTEM_FSM_READY_TO_BOOT 0x01 +#define VD55G0_SYSTEM_FSM_SW_STBY 0x02 +#define VD55G0_SYSTEM_FSM_STREAMING 0x03 +#define VD55G0_REG_TEMPERATURE VD55G0_REG_16BIT(0x004c) +#define VD55G0_REG_BOOT VD55G0_REG_8BIT(0x0200) +#define VD55G0_BOOT_BOOT 1 +#define VD55G0_BOOT_PATCH_SETUP 2 +#define VD55G0_REG_SW_STBY VD55G0_REG_8BIT(0x0201) +#define VD55G0_SW_STBY_START_STREAM 1 +#define VD55G0_SW_STBY_THSENS_READ 4 +#define VD55G0_REG_STREAMING VD55G0_REG_8BIT(0x0202) +#define VD55G0_STREAMING_STOP_STREAM 1 +#define VD55G0_REG_EXT_CLOCK VD55G0_REG_32BIT(0x0220) +#define VD55G0_REG_LINE_LENGTH VD55G0_REG_16BIT(0x0300) +#define VD55G0_REG_ORIENTATION VD55G0_REG_8BIT(0x0302) +#define VD55G0_REG_FORMAT_CTRL VD55G0_REG_8BIT(0x030a) +#define VD55G0_REG_OIF_CTRL VD55G0_REG_16BIT(0x030c) +#define VD55G0_REG_OIF_IMG_CTRL VD55G0_REG_8BIT(0x030f) +#define VD55G0_REG_CLK_PLL_MIPI VD55G0_REG_32BIT(0x0224) +#define VD55G0_REG_PATGEN_CTRL VD55G0_REG_16BIT(0x0400) +#define VD55G0_PATGEN_TYPE_SHIFT 4 +#define VD55G0_PATGEN_ENABLE BIT(0) +#define VD55G0_REG_MANUAL_ANALOG_GAIN VD55G0_REG_8BIT(0x044d) +#define VD55G0_REG_MANUAL_COARSE_EXPOSURE VD55G0_REG_16BIT(0x044e) +#define VD55G0_REG_MANUAL_DIGITAL_GAIN VD55G0_REG_16BIT(0x0450) +#define VD55G0_REG_APPLIED_COARSE_EXPOSURE VD55G0_REG_16BIT(0x0064) +#define VD55G0_REG_APPLIED_ANALOG_GAIN VD55G0_REG_8BIT(0x0066) +#define VD55G0_REG_APPLIED_DIGITAL_GAIN VD55G0_REG_16BIT(0x0068) +#define VD55G0_REG_AE_COLDSTART_COARSE_EXPOSURE VD55G0_REG_16BIT(0x042e) +#define VD55G0_REG_AE_COLDSTART_ANALOG_GAIN VD55G0_REG_8BIT(0x0430) +#define VD55G0_REG_AE_COLDSTART_DIGITAL_GAIN VD55G0_REG_16BIT(0x0432) +#define VD55G0_REG_EXP_MODE VD55G0_REG_8BIT(0x044c) +#define VD55G0_EXP_MODE_AUTO 0 +#define VD55G0_EXP_MODE_FREEZE 1 +#define VD55G0_EXP_MODE_MANUAL 2 +#define VD55G0_REG_FRAME_LENGTH VD55G0_REG_16BIT(0x0458) +#define VD55G0_REG_ROI_X_START VD55G0_REG_16BIT(0x045e) +#define VD55G0_REG_ROI_X_END VD55G0_REG_16BIT(0x0460) +#define VD55G0_REG_ROI_Y_START VD55G0_REG_16BIT(0x0462) +#define VD55G0_REG_ROI_Y_END VD55G0_REG_16BIT(0x0464) +#define VD55G0_REG_Y_START VD55G0_REG_16BIT(0x045a) +#define VD55G0_REG_Y_END VD55G0_REG_16BIT(0x045c) +#define VD55G0_REG_AE_ROI_START_H VD55G0_REG_16BIT(0x0436) +#define VD55G0_REG_AE_ROI_START_V VD55G0_REG_16BIT(0x0438) +#define VD55G0_REG_AE_ROI_END_H VD55G0_REG_16BIT(0x043a) +#define VD55G0_REG_AE_ROI_END_V VD55G0_REG_16BIT(0x043c) +#define VD55G0_REG_GPIO_0_CTRL VD55G0_REG_8BIT(0x0467) +#define VD55G0_REG_GPIO_1_CTRL VD55G0_REG_8BIT(0x0468) +#define VD55G0_REG_GPIO_2_CTRL VD55G0_REG_8BIT(0x0469) +#define VD55G0_REG_GPIO_3_CTRL VD55G0_REG_8BIT(0x046a) +#define VD55G0_REG_READOUT_CTRL VD55G0_REG_8BIT(0x047a) +#define VD55G0_REG_DARKCAL_CTRL VD55G0_REG_8BIT(0x032c) +#define VD55G0_DARKCAL_BYPASS 0 +#define VD55G0_DARKCAL_AUTO 1 +#define VD55G0_REG_DUSTER_CTRL VD55G0_REG_8BIT(0x0316) +#define VD55G0_DUSTER_ENABLE BIT(0) +#define VD55G0_DUSTER_DISABLE 0 +#define VD55G0_DUSTER_DYN_ENABLE BIT(1) +#define VD55G0_DUSTER_RING_ENABLE BIT(4) +#define VD55G0_REG_DARKCAL_PEDESTAL VD55G0_REG_8BIT(0x0416) +#define VD55G0_REG_AE_TARGET_PERCENTAGE VD55G0_REG_8BIT(0x0440) +#define VD55G0_REG_VT_CTRL VD55G0_REG_8BIT(0x0309) +#define VD55G0_VT_SLAVE_GPIO 1 + +#define VD55G0_WIDTH 644 +#define VD55G0_HEIGHT 604 +#define VD55G0_DEFAULT_MODE 1 +#define VD55G0_WRITE_MULTIPLE_CHUNK_MAX 16 +#define VD55G0_NB_GPIOS 4 +#define VD55G0_NB_POLARITIES 3 +#define VD55G0_MIN_FRAME_LENGTH (605 + 76) +#define VD55G0_FRAME_LENGTH_DEF 1860 /* 60 fps */ +#define VD55G0_TIMEOUT_MS 500 +#define VD55G0_MEDIA_BUS_FMT_DEF MEDIA_BUS_FMT_Y8_1X8 +#define VD55G0_DARKCAL_PEDESTAL_DEF 0x40 +#define VD55G0_EXPO_MAX_TERM 64 +#define VD55G0_EXPO_DEF 200 +#define VD55G0_LINE_LENGTH_SLOW 1200 +#define VD55G0_LINE_LENGTH_FAST 1128 + +#define V4L2_CID_TEMPERATURE (V4L2_CID_USER_BASE | 0x1020) +#define V4L2_CID_DARKCAL_PEDESTAL (V4L2_CID_USER_BASE | 0x1021) +#define V4L2_CID_SLAVE (V4L2_CID_USER_BASE | 0x1022) + +#include "vd55g0_patches.h" + +static const char * const vd55g0_test_pattern_menu[] = { + "Disabled", + "Solid", + "Colorbar", + "Gradbar", + "Hgrey", + "Vgrey", + "Dgrey", + "PN28", +}; + +static const s64 vd55g0_ev_bias_menu[] = { + -3000, -2500, -2000, -1500, -1000, -500, + 0, + 500, 1000, 1500, 2000, 2500, 3000, +}; + +/* + * DT equivalents: "VCORE", "VDDIO", "VANA" + * INT3472 on Surface Pro 11 registers: GPIO type 0x0B → "avdd", + * type 0x10 → "pwr2". Only 2 supplies provided via INT3472. + */ +static const char * const vd55g0_supply_name[] = { + "avdd", + "pwr2", +}; + +static const s64 link_freq[] = { + /* + * MIPI output freq is 804Mhz / 2, as it uses both rising edge and + * falling edges to send data + */ + 402000000ULL +}; + +enum vd55g0_bin_mode { + VD55G0_BIN_MODE_NORMAL, + VD55G0_BIN_MODE_DIGITAL_X2, + VD55G0_BIN_MODE_DIGITAL_X4, +}; + +enum vd55g0_gpio_modes { + VD55G0_GPIO_MODE_DISABLED, + VD55G0_GPIO_MODE_STROBE, + VD55G0_GPIO_MODE_VSYNC_OUT_0, + VD55G0_GPIO_MODE_VTSLAVE, +}; + +struct vd55g0_mode_info { + u32 width; + u32 height; + enum vd55g0_bin_mode bin_mode; + struct v4l2_rect crop; +}; + +struct vd55g0_fmt_desc { + u32 code; + u8 bpp; + u8 data_type; +}; + +static const struct vd55g0_fmt_desc vd55g0_supported_codes[] = { + { + .code = MEDIA_BUS_FMT_Y8_1X8, + .bpp = 8, + .data_type = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_Y10_1X10, + .bpp = 10, + .data_type = MIPI_CSI2_DT_RAW10, + }, + /* SGRBG aliases: same bpp/data_type as Y8/Y10, allows IPU7 Bayer pipeline */ + { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .bpp = 8, + .data_type = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .bpp = 10, + .data_type = MIPI_CSI2_DT_RAW10, + }, +}; + +static const struct vd55g0_mode_info vd55g0_mode_data[] = { + { + .width = VD55G0_WIDTH, + .height = VD55G0_HEIGHT, + .bin_mode = VD55G0_BIN_MODE_NORMAL, + .crop = { + .left = 0, + .top = 0, + .width = VD55G0_WIDTH, + .height = VD55G0_HEIGHT, + }, + }, + { + .width = 640, + .height = 600, + .bin_mode = VD55G0_BIN_MODE_NORMAL, + .crop = { + .left = 2, + .top = 2, + .width = 640, + .height = 600, + }, + }, + { + .width = 640, + .height = 480, + .bin_mode = VD55G0_BIN_MODE_NORMAL, + .crop = { + .left = 2, + .top = 62, + .width = 640, + .height = 480, + }, + }, + { + .width = 320, + .height = 240, + .bin_mode = VD55G0_BIN_MODE_DIGITAL_X2, + .crop = { + .left = 2, + .top = 62, + .width = 640, + .height = 480, + }, + }, +}; + +enum vd55g0_expo_state { + VD55G0_EXP_AUTO, + VD55G0_EXP_FREEZE, + VD55G0_EXP_MANUAL +}; + +struct vd55g0_gpios { + u32 leds[VD55G0_NB_GPIOS]; + u32 out_sync[VD55G0_NB_GPIOS]; + u32 in_sync; +}; + +struct vd55g0_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct media_pad pad; + struct regulator_bulk_data supplies[ARRAY_SIZE(vd55g0_supply_name)]; + struct gpio_desc *reset_gpio; + struct clk *xclk; + u32 clk_freq; + u16 oif_ctrl; + int nb_of_lane; + int data_rate_in_mbps; + u16 line_length; + u16 revision; + /* Lock to protect all members below */ + struct mutex lock; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *pixel_rate_ctrl; + struct v4l2_ctrl *vblank_ctrl; + struct v4l2_ctrl *hblank_ctrl; + struct v4l2_ctrl *vflip_ctrl; + struct v4l2_ctrl *hflip_ctrl; + struct v4l2_ctrl *pattern_ctrl; + struct v4l2_ctrl *slave_ctrl; + struct v4l2_ctrl *expo_ctrl; + bool streaming; + struct v4l2_mbus_framefmt fmt; + const struct vd55g0_mode_info *current_mode; + bool hflip; + bool vflip; + u16 manual_expo; + enum vd55g0_expo_state expo_state; + u16 digital_gain; + u8 analog_gain; + u16 vblank; + u16 vblank_min; + u16 frame_length; + bool ae_frozen; + u32 pattern; + u8 darkcal_pedestal; + u16 exposure_target; + struct vd55g0_gpios gpios; + bool is_slave; + bool flash_en; + struct { + u16 expo; + u16 digital_gain; + u8 analog_gain; + } cold_start; +}; + +static inline struct vd55g0_dev *to_vd55g0_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct vd55g0_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct vd55g0_dev, + ctrl_handler)->sd; +} + +static u8 get_bpp_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vd55g0_supported_codes); i++) { + if (vd55g0_supported_codes[i].code == code) + return vd55g0_supported_codes[i].bpp; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to 8 bpp", code); + return 8; +} + +static u8 get_data_type_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vd55g0_supported_codes); i++) { + if (vd55g0_supported_codes[i].code == code) + return vd55g0_supported_codes[i].data_type; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", + code); + return MIPI_CSI2_DT_RAW8; +} + +static s32 get_pixel_rate(struct vd55g0_dev *sensor) +{ + return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane, + get_bpp_by_code(sensor->fmt.code)); +} + +static int get_chunk_size(struct vd55g0_dev *sensor) +{ + int max_write_len = VD55G0_WRITE_MULTIPLE_CHUNK_MAX; + struct i2c_adapter *adapter = sensor->i2c_client->adapter; + + if (adapter->quirks && adapter->quirks->max_write_len) + max_write_len = adapter->quirks->max_write_len - 2; + + max_write_len = min(max_write_len, VD55G0_WRITE_MULTIPLE_CHUNK_MAX); + + return max(max_write_len, 1); +} + +static int vd55g0_read_multiple(struct vd55g0_dev *sensor, u32 reg, + unsigned int len) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg[2]; + u8 buf[2]; + u8 val[sizeof(u32)] = {0}; + int ret; + + if (len > sizeof(u32)) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = val; + msg[1].len = len; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", + __func__, client->addr, reg, ret); + return ret; + } + + return get_unaligned_le32(val); +} + +static inline int vd55g0_read_reg(struct vd55g0_dev *sensor, u32 reg) +{ + return vd55g0_read_multiple(sensor, reg & VD55G0_REG_ADDR_MASK, + (reg >> VD55G0_REG_SIZE_SHIFT) & 7); +} + +static int vd55g0_write_multiple(struct vd55g0_dev *sensor, u32 reg, + const u8 *data, unsigned int len, int *err) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[VD55G0_WRITE_MULTIPLE_CHUNK_MAX + 2]; + unsigned int i; + int ret; + + if (err && *err) + return *err; + + if (len > VD55G0_WRITE_MULTIPLE_CHUNK_MAX) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + for (i = 0; i < len; i++) + buf[i + 2] = data[i]; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = len + 2; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", + __func__, reg, ret); + if (err) + *err = ret; + return ret; + } + + return 0; +} + +static int vd55g0_write_array(struct vd55g0_dev *sensor, u32 reg, + unsigned int nb, const u8 *array) +{ + const unsigned int chunk_size = get_chunk_size(sensor); + int ret; + unsigned int sz; + + while (nb) { + sz = min(nb, chunk_size); + ret = vd55g0_write_multiple(sensor, reg, array, sz, NULL); + if (ret < 0) + return ret; + nb -= sz; + reg += sz; + array += sz; + } + + return 0; +} + +static inline int vd55g0_write_reg(struct vd55g0_dev *sensor, u32 reg, u32 val, + int *err) +{ + return vd55g0_write_multiple(sensor, reg & VD55G0_REG_ADDR_MASK, + (u8 *)&val, + (reg >> VD55G0_REG_SIZE_SHIFT) & 7, err); +} + +static int vd55g0_poll_reg(struct vd55g0_dev *sensor, u32 reg, u8 poll_val, + unsigned int timeout_ms) +{ + const unsigned int loop_delay_ms = 10; + int ret; +#if KERNEL_VERSION(5, 7, 0) > LINUX_VERSION_CODE + int loop_nb = timeout_ms / loop_delay_ms; + + while (--loop_nb) { + ret = vd55g0_read_reg(sensor, reg); + if (ret < 0) + return ret; + if (ret == poll_val) + return 0; + msleep(loop_delay_ms); + } + return -ETIMEDOUT; +#else + return read_poll_timeout(vd55g0_read_reg, ret, + ((ret < 0) || (ret == poll_val)), + loop_delay_ms * 1000, timeout_ms * 1000, + false, sensor, reg); +#endif +} + +static int vd55g0_wait_state(struct vd55g0_dev *sensor, int state, + unsigned int timeout_ms) +{ + return vd55g0_poll_reg(sensor, VD55G0_REG_SYSTEM_FSM, state, + timeout_ms); +} + +static int vd55g0_update_gpio_mode(struct vd55g0_dev *sensor, u32 mode, + int gpio) +{ + u8 index2val[] = {0x01, 0x02, 0x06, 0x0a}; + + return vd55g0_write_reg(sensor, VD55G0_REG_GPIO_0_CTRL + gpio, + index2val[mode], NULL); +} + +static int vd55g0_set_gpios_array(struct vd55g0_dev *sensor, u32 *array, + int size, enum vd55g0_gpio_modes mode) +{ + unsigned int i; + int ret; + + for (i = 0; i < size; i++) { + if (array[i] == ~0) + break; + ret = vd55g0_update_gpio_mode(sensor, mode, array[i]); + if (ret) + return -EINVAL; + } + + return 0; +} + +static int vd55g0_apply_exposure_auto(struct vd55g0_dev *sensor) +{ + enum vd55g0_expo_state exp = sensor->expo_state; + + if (sensor->ae_frozen && sensor->expo_state == VD55G0_EXP_AUTO) + exp = VD55G0_EXP_FREEZE; + + return vd55g0_write_reg(sensor, VD55G0_REG_EXP_MODE, exp, NULL); +} + +static int vd55g0_get_regulators(struct vd55g0_dev *sensor) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vd55g0_supply_name); i++) + sensor->supplies[i].supply = vd55g0_supply_name[i]; + + return devm_regulator_bulk_get(&sensor->i2c_client->dev, + ARRAY_SIZE(vd55g0_supply_name), + sensor->supplies); +} + +static int vd55g0_apply_patgen(struct vd55g0_dev *sensor) +{ + static const u8 index2val[] = { + 0x0, 0x0, 0x8, 0x10, 0x20, 0x21, 0x22, 0x28 + }; + u32 pattern = index2val[sensor->pattern]; + u32 reg = pattern << VD55G0_PATGEN_TYPE_SHIFT; + u8 darkcal = VD55G0_DARKCAL_AUTO; + u8 duster = VD55G0_DUSTER_RING_ENABLE | VD55G0_DUSTER_DYN_ENABLE | + VD55G0_DUSTER_ENABLE; + int ret = 0; + + if (sensor->pattern != 0) { + reg |= VD55G0_PATGEN_ENABLE; + /* + * Take care of dark calibaration and duster to not mess up the + * test pattern output. + */ + darkcal = VD55G0_DARKCAL_BYPASS; + duster = VD55G0_DUSTER_DISABLE; + } + + vd55g0_write_reg(sensor, VD55G0_REG_DARKCAL_CTRL, darkcal, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_DUSTER_CTRL, duster, &ret); + if (ret) + return ret; + + return vd55g0_write_reg(sensor, VD55G0_REG_PATGEN_CTRL, reg, NULL); +} + +static int vd55g0_apply_flash(struct vd55g0_dev *sensor) +{ + struct vd55g0_gpios *gpios = &sensor->gpios; + + enum vd55g0_gpio_modes mode = sensor->flash_en ? + VD55G0_GPIO_MODE_STROBE : + VD55G0_GPIO_MODE_DISABLED; + + return vd55g0_set_gpios_array(sensor, gpios->leds, + ARRAY_SIZE(gpios->leds), mode); +} + +static int vd55g0_update_exposure_auto(struct vd55g0_dev *sensor, u32 index) +{ + int ret; + + switch (index) { + case V4L2_EXPOSURE_AUTO: + sensor->expo_state = VD55G0_EXP_AUTO; + break; + case V4L2_EXPOSURE_MANUAL: + sensor->expo_state = VD55G0_EXP_MANUAL; + break; + default: + /* Should never happen */ + ret = -EINVAL; + } + + if (sensor->streaming) + return vd55g0_apply_exposure_auto(sensor); + return 0; +} + +static int vd55g0_lock_exposure(struct vd55g0_dev *sensor, + struct v4l2_ctrl *ctrl) +{ + /* Only exposure lock is supported */ + bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE; + int ret; + + sensor->ae_frozen = ae_lock; + + /* Cap control value to reflect the hardware state */ + ctrl->val = ae_lock; + + ret = vd55g0_apply_exposure_auto(sensor); + if (ret) + return ret; + return 0; +} + +static int vd55g0_get_temp_stream_enable(struct vd55g0_dev *sensor, int *temp) +{ + int temperature; + + temperature = vd55g0_read_reg(sensor, VD55G0_REG_TEMPERATURE); + if (temperature < 0) + return temperature; + + /* temperature is signed 10 bits value. extend sign */ + temperature = (temperature << 6) >> 6; + *temp = temperature; + + return 0; +} + +static int vd55g0_apply_framelength(struct vd55g0_dev *sensor) +{ + return vd55g0_write_reg(sensor, VD55G0_REG_FRAME_LENGTH, + sensor->frame_length, NULL); +} + +static int vd55g0_update_vblank(struct vd55g0_dev *sensor, u16 vblank) +{ + sensor->vblank_min = VD55G0_MIN_FRAME_LENGTH - + sensor->current_mode->crop.height; + sensor->vblank = vblank; + sensor->frame_length = sensor->current_mode->crop.height + + sensor->vblank; + + if (sensor->streaming) + return vd55g0_apply_framelength(sensor); + return 0; +} + +static int vd55g0_get_temp_stream_disable(struct vd55g0_dev *sensor, int *temp) +{ + int ret; + + /* request temperature read */ + ret = vd55g0_write_reg(sensor, VD55G0_REG_SW_STBY, + VD55G0_SW_STBY_THSENS_READ, NULL); + if (ret) + return ret; + ret = vd55g0_poll_reg(sensor, VD55G0_REG_SW_STBY, 0, VD55G0_TIMEOUT_MS); + if (ret) + return ret; + + return vd55g0_get_temp_stream_enable(sensor, temp); +} + +static int vd55g0_get_temp(struct vd55g0_dev *sensor, int *temp) +{ + *temp = 0; + if (sensor->streaming) + return vd55g0_get_temp_stream_enable(sensor, temp); + else + return vd55g0_get_temp_stream_disable(sensor, temp); +} + +static int vd55g0_update_analog_gain(struct vd55g0_dev *sensor, u32 target) +{ + sensor->analog_gain = target; + + if (sensor->streaming) + return vd55g0_write_reg(sensor, VD55G0_REG_MANUAL_ANALOG_GAIN, + target, NULL); + return 0; +} + +static int vd55g0_update_digital_gain(struct vd55g0_dev *sensor, u32 target) +{ + sensor->digital_gain = target; + + if (sensor->streaming) + return vd55g0_write_reg(sensor, VD55G0_REG_MANUAL_DIGITAL_GAIN, + target, NULL); + return 0; +} + +static int vd55g0_update_exposure(struct vd55g0_dev *sensor, int expo_ms) +{ + sensor->manual_expo = expo_ms; + if (sensor->streaming) + return vd55g0_write_reg(sensor, + VD55G0_REG_MANUAL_COARSE_EXPOSURE, + sensor->manual_expo, NULL); + + return 0; +} + +static int vd55g0_update_darkcal_pedestal(struct vd55g0_dev *sensor, + int pedestal) +{ + sensor->darkcal_pedestal = pedestal; + if (sensor->streaming) + return vd55g0_write_reg(sensor, VD55G0_REG_DARKCAL_PEDESTAL, + sensor->darkcal_pedestal, NULL); + + return 0; +} + +static int vd55g0_update_exposure_target(struct vd55g0_dev *sensor, int index) +{ + /* + * Find auto exposure target with: default target exposure * 2^EV + * Defaut target exposure being 27 for the sensor. + */ + static const unsigned int index2exposure_target[] = { + 3, 5, 7, 10, 14, 19, 27, 38, 54, 76, 108, 153, 216, + }; + + sensor->exposure_target = index2exposure_target[index]; + if (sensor->streaming) + return vd55g0_write_reg(sensor, VD55G0_REG_AE_TARGET_PERCENTAGE, + sensor->exposure_target, NULL); + + return 0; +} + +static int vd55g0_update_flash(struct vd55g0_dev *sensor, int flash_en) +{ + sensor->flash_en = flash_en; + if (sensor->streaming) + return vd55g0_apply_flash(sensor); + + return 0; +} + +static void vd55g0_apply_reset(struct vd55g0_dev *sensor) +{ + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(5000, 10000); +} + +static void vd55g0_fill_framefmt(struct vd55g0_dev *sensor, + const struct vd55g0_mode_info *mode, + struct v4l2_mbus_framefmt *fmt, u32 code) +{ + fmt->code = code; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->field = V4L2_FIELD_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int vd55g0_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + const struct vd55g0_mode_info **new_mode) +{ + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + const struct vd55g0_mode_info *mode = vd55g0_mode_data; + unsigned int index; + + for (index = 0; index < ARRAY_SIZE(vd55g0_supported_codes); index++) { + if (vd55g0_supported_codes[index].code == fmt->code) + break; + } + if (index == ARRAY_SIZE(vd55g0_supported_codes)) + index = 0; + + mode = v4l2_find_nearest_size(vd55g0_mode_data, + ARRAY_SIZE(vd55g0_mode_data), width, + height, fmt->width, fmt->height); + if (new_mode) + *new_mode = mode; + + vd55g0_fill_framefmt(sensor, mode, fmt, + vd55g0_supported_codes[index].code); + + return 0; +} + +static int vd55g0_apply_cold_start(struct vd55g0_dev *sensor) +{ + int ret = 0; + + vd55g0_write_reg(sensor, VD55G0_REG_AE_COLDSTART_COARSE_EXPOSURE, + sensor->cold_start.expo, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_AE_COLDSTART_ANALOG_GAIN, + sensor->cold_start.analog_gain, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_AE_COLDSTART_DIGITAL_GAIN, + sensor->cold_start.digital_gain, &ret); + + return ret; +} + +static int vd55g0_apply_settings(struct vd55g0_dev *sensor) +{ + int ret; + + ret = vd55g0_apply_framelength(sensor); + if (ret) + return ret; + + ret = vd55g0_apply_exposure_auto(sensor); + if (ret) + return ret; + + vd55g0_write_reg(sensor, VD55G0_REG_MANUAL_COARSE_EXPOSURE, + sensor->manual_expo, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_MANUAL_ANALOG_GAIN, + sensor->analog_gain, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_MANUAL_DIGITAL_GAIN, + sensor->digital_gain, &ret); + if (ret) + return ret; + + ret = vd55g0_apply_cold_start(sensor); + if (ret) + return ret; + + vd55g0_write_reg(sensor, VD55G0_REG_ORIENTATION, + sensor->hflip | (sensor->vflip << 1), &ret); + vd55g0_write_reg(sensor, VD55G0_REG_DARKCAL_PEDESTAL, + sensor->darkcal_pedestal, &ret); + if (ret) + return ret; + + ret = vd55g0_apply_patgen(sensor); + if (ret) + return ret; + + return 0; +} + +static int vd55g0_apply_frame_format(struct vd55g0_dev *sensor) +{ + const struct v4l2_rect *crop = &sensor->current_mode->crop; + int ret = 0; + + vd55g0_write_reg(sensor, VD55G0_REG_READOUT_CTRL, + sensor->current_mode->bin_mode, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_ROI_X_START, crop->left, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_ROI_X_END, + crop->left + crop->width - 1, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_ROI_Y_START, crop->top, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_ROI_Y_END, + crop->top + crop->height - 1, &ret); + + /* + * Use the same auto exposure crop as the image crop, performing auto + * exposure computation only on image boundaries. + */ + vd55g0_write_reg(sensor, VD55G0_REG_AE_ROI_START_H, crop->left, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_AE_ROI_END_H, + crop->left + crop->width - 1, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_AE_ROI_START_V, crop->top, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_AE_ROI_END_V, + crop->top + crop->height - 1, &ret); + + /* + * Only aquire lines required for this image format, optimizing power + * usage + */ +/* + * vd55g0_write_reg(sensor, VD55G0_REG_Y_START, crop->top - 50, &ret); + * vd55g0_write_reg(sensor, VD55G0_REG_Y_END, + * crop->top + crop->height - 1, &ret); + */ + + return ret; +} + +static int vd55g0_set_gpios(struct vd55g0_dev *sensor) +{ + struct vd55g0_gpios *gpios = &sensor->gpios; + int ret; + unsigned int i; + + /* GPIOs in input (disabled) by default */ + for (i = 0; i < VD55G0_NB_GPIOS; i++) { + ret = vd55g0_update_gpio_mode(sensor, + VD55G0_GPIO_MODE_DISABLED, i); + if (ret) + return ret; + } + + ret = vd55g0_apply_flash(sensor); + if (ret) + return ret; + + ret = vd55g0_set_gpios_array(sensor, gpios->out_sync, + ARRAY_SIZE(gpios->out_sync), + VD55G0_GPIO_MODE_VSYNC_OUT_0); + if (ret) + return ret; + + if (!sensor->is_slave) + return 0; + + ret = vd55g0_update_gpio_mode(sensor, VD55G0_GPIO_MODE_VTSLAVE, + gpios->in_sync); + if (ret) + return -EINVAL; + ret = vd55g0_write_reg(sensor, VD55G0_REG_VT_CTRL, VD55G0_VT_SLAVE_GPIO, + NULL); + + return ret; +} + +static int vd55g0_stream_enable(struct vd55g0_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + int ret; + + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(&client->dev); + return ret; + } + + /* pm_runtime_get_sync() can return 1 as a valid return code */ + ret = 0; + + ret = vd55g0_set_gpios(sensor); + if (ret) + goto err_rpm_put; + + vd55g0_write_reg(sensor, VD55G0_REG_FORMAT_CTRL, + get_bpp_by_code(sensor->fmt.code), &ret); + vd55g0_write_reg(sensor, VD55G0_REG_OIF_IMG_CTRL, + get_data_type_by_code(sensor->fmt.code), &ret); + if (ret) + goto err_rpm_put; + + ret = vd55g0_apply_frame_format(sensor); + if (ret) + goto err_rpm_put; + + ret = vd55g0_apply_settings(sensor); + if (ret) + goto err_rpm_put; + + ret = vd55g0_write_reg(sensor, VD55G0_REG_SW_STBY, + VD55G0_SW_STBY_START_STREAM, NULL); + if (ret) + goto err_rpm_put; + + ret = vd55g0_poll_reg(sensor, VD55G0_REG_SW_STBY, 0, VD55G0_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + ret = vd55g0_wait_state(sensor, VD55G0_SYSTEM_FSM_STREAMING, + VD55G0_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + return 0; + +err_rpm_put: + pm_runtime_put(&client->dev); + return ret; +} + +static void vd55g0_save_exposure(struct vd55g0_dev *sensor) +{ + int ret; + + ret = vd55g0_read_reg(sensor, VD55G0_REG_APPLIED_COARSE_EXPOSURE); + sensor->cold_start.expo = ret < 0 ? 0 : ret; + ret = vd55g0_read_reg(sensor, VD55G0_REG_APPLIED_DIGITAL_GAIN); + sensor->cold_start.digital_gain = ret < 0 ? 0 : ret; + ret = vd55g0_read_reg(sensor, VD55G0_REG_APPLIED_ANALOG_GAIN); + sensor->cold_start.analog_gain = ret < 0 ? 0 : ret; +} + +static int vd55g0_stream_disable(struct vd55g0_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + int ret; + + /* Keep exposure values for next cold start boot */ + vd55g0_save_exposure(sensor); + + ret = vd55g0_write_reg(sensor, VD55G0_REG_STREAMING, + VD55G0_STREAMING_STOP_STREAM, NULL); + if (ret) + goto err_str_dis; + + ret = vd55g0_poll_reg(sensor, VD55G0_REG_STREAMING, 0, 2000); + if (ret) + goto err_str_dis; + + ret = vd55g0_wait_state(sensor, VD55G0_SYSTEM_FSM_SW_STBY, + VD55G0_TIMEOUT_MS); + +err_str_dis: + if (ret) + WARN(1, "Can't disable stream"); + pm_runtime_put(&client->dev); + + return ret; +} + +static int vd55g0_tx_from_ep(struct vd55g0_dev *sensor, + struct fwnode_handle *handle) +{ + struct i2c_client *client = sensor->i2c_client; + struct v4l2_fwnode_endpoint *ep; + u32 log2phy[VD55G0_NB_POLARITIES] = {~0, ~0, ~0}; + u32 phy2log[VD55G0_NB_POLARITIES] = {~0, ~0, ~0}; + int polarities[VD55G0_NB_POLARITIES] = {0, 0, 0}; + int l_nb; + int p, l; + int i; + +#if KERNEL_VERSION(4, 20, 0) > LINUX_VERSION_CODE + ep = v4l2_fwnode_endpoint_alloc_parse(handle); +#else + struct v4l2_fwnode_endpoint ep_node = { .bus_type = + V4L2_MBUS_CSI2_DPHY + }; + int ret; + + ep = &ep_node; + ret = v4l2_fwnode_endpoint_alloc_parse(handle, ep); + if (ret) + return -EINVAL; +#endif + + l_nb = ep->bus.mipi_csi2.num_data_lanes; + if (l_nb != 1 && l_nb != 2) { + dev_err(&client->dev, "invalid data lane number %d\n", l_nb); + goto error_ep; + } + + /* build log2phy, phy2log and polarities from ep info */ + log2phy[0] = ep->bus.mipi_csi2.clock_lane; + phy2log[log2phy[0]] = 0; + for (l = 1; l < l_nb + 1; l++) { + log2phy[l] = ep->bus.mipi_csi2.data_lanes[l - 1]; + phy2log[log2phy[l]] = l; + } + /* + * then fill remaining slots for every physical slot have something + * valid for hardware stuff. + */ + for (p = 0; p < VD55G0_NB_POLARITIES; p++) { + if (phy2log[p] != ~0) + continue; + phy2log[p] = l; + log2phy[l] = p; + l++; + } + for (l = 0; l < l_nb + 1; l++) + polarities[l] = ep->bus.mipi_csi2.lane_polarities[l]; + + if (log2phy[0] != 0) { + dev_err(&client->dev, "clk lane must be map to physical lane 0\n"); + goto error_ep; + } + sensor->oif_ctrl = l_nb | + (polarities[0] << 3) | + ((phy2log[1] - 1) << 4) | + (polarities[1] << 6) | + ((phy2log[2] - 1) << 7) | + (polarities[2] << 9); + sensor->nb_of_lane = l_nb; + + dev_dbg(&client->dev, "tx uses %d lanes", l_nb); + for (i = 0; i < VD55G0_NB_POLARITIES; i++) { + dev_dbg(&client->dev, "log2phy[%d] = %d", i, log2phy[i]); + dev_dbg(&client->dev, "phy2log[%d] = %d", i, phy2log[i]); + dev_dbg(&client->dev, "polarity[%d] = %d", i, polarities[i]); + } + dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl); + + v4l2_fwnode_endpoint_free(ep); + + return 0; + +error_ep: + v4l2_fwnode_endpoint_free(ep); + + return -EINVAL; +} + +static int vd55g0_patch(struct vd55g0_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + struct vd55g0_patch *patch; + int patch_version, ret; + + /* Choose appropriate patch for sensor revision */ + if (sensor->revision == VD55G0_REVISION_CUT1) + patch = &vd55g0_patch_cut1; + else if (sensor->revision == VD55G0_REVISION_CUT2) + patch = &vd55g0_patch_cut2; + else { + dev_warn(&client->dev, + "unknown revision 0x%04x, skipping patch\n", + sensor->revision); + return 0; + } + + /* Write patch */ + ret = vd55g0_write_array(sensor, VD55G0_REG_FWPATCH_START_ADDR, + patch->size, patch->bin); + if (ret) { + dev_warn(&client->dev, "patch write failed %d, continuing\n", + ret); + return 0; + } + + ret = vd55g0_write_reg(sensor, VD55G0_REG_BOOT, + VD55G0_BOOT_PATCH_SETUP, NULL); + if (ret) { + dev_warn(&client->dev, + "patch setup failed %d, continuing\n", ret); + return 0; + } + + ret = vd55g0_poll_reg(sensor, VD55G0_REG_BOOT, 0, VD55G0_TIMEOUT_MS); + if (ret) { + dev_warn(&client->dev, + "patch boot timeout %d, continuing\n", ret); + return 0; + } + + /* Read back patch version */ + patch_version = vd55g0_read_reg(sensor, VD55G0_REG_FWPATCH_REVISION); + if (patch_version < 0) { + dev_warn(&client->dev, + "patch version read failed %d, continuing\n", + patch_version); + return 0; + } + + if (patch_version != (patch->major << 8) + patch->minor) { + dev_warn(&client->dev, + "patch version mismatch: expected %d.%d got %d.%d, continuing\n", + patch->major, patch->minor, + patch_version >> 8, patch_version & 0xff); + return 0; + } + dev_dbg(&client->dev, "patch %d.%d applied", + patch_version >> 8, patch_version & 0xff); + + return 0; +} + +static int vd55g0_boot(struct vd55g0_dev *sensor) +{ + int ret; + + ret = vd55g0_write_reg(sensor, VD55G0_REG_BOOT, VD55G0_BOOT_BOOT, NULL); + if (ret) + return ret; + + ret = vd55g0_poll_reg(sensor, VD55G0_REG_BOOT, 0, VD55G0_TIMEOUT_MS); + if (ret) + return ret; + + ret = vd55g0_wait_state(sensor, VD55G0_SYSTEM_FSM_SW_STBY, + VD55G0_TIMEOUT_MS); + if (ret) + return ret; + + return 0; +} + +static int vd55g0_configure(struct vd55g0_dev *sensor) +{ + /* Double data rate */ + u32 mipi_bps = link_freq[0] * 2; + int ret = 0; + + /* Frequency to data rate is 1:1 ratio for MIPI */ + sensor->data_rate_in_mbps = mipi_bps; + + sensor->line_length = VD55G0_LINE_LENGTH_FAST; + if (mipi_bps < 900 * HZ_PER_MHZ) + sensor->line_length = VD55G0_LINE_LENGTH_SLOW; + vd55g0_write_reg(sensor, VD55G0_REG_LINE_LENGTH, sensor->line_length, + &ret); + + /* + * PLL_PREDIV and PLL_MULT are not accessible. We rely on the firmware + * to set the correct multiplier for the clock. Hence we don't do + * anything more here. + */ + vd55g0_write_reg(sensor, VD55G0_REG_EXT_CLOCK, sensor->clk_freq, &ret); + + vd55g0_write_reg(sensor, VD55G0_REG_OIF_CTRL, sensor->oif_ctrl, &ret); + vd55g0_write_reg(sensor, VD55G0_REG_CLK_PLL_MIPI, mipi_bps, &ret); + + return ret; +} + +static inline bool vd55g0_can_be_slave(struct vd55g0_dev *sensor) +{ + return sensor->gpios.in_sync != ~0; +} + +static int vd55g0_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + ret = enable ? vd55g0_stream_enable(sensor) : + vd55g0_stream_disable(sensor); + if (!ret) + sensor->streaming = enable; + + mutex_unlock(&sensor->lock); + + if (!ret) { + /* These settings cannot change during streaming */ + v4l2_ctrl_grab(sensor->vflip_ctrl, enable); + v4l2_ctrl_grab(sensor->hflip_ctrl, enable); + v4l2_ctrl_grab(sensor->pattern_ctrl, enable); + if (vd55g0_can_be_slave(sensor)) + v4l2_ctrl_grab(sensor->slave_ctrl, enable); + } + + return ret; +} + +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE +static int vd55g0_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +#else +static int vd55g0_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +#endif +{ + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = sensor->current_mode->crop; + return 0; + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = VD55G0_WIDTH; + sel->r.height = VD55G0_HEIGHT; + return 0; + } + + return -EINVAL; +} + +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE +static int vd55g0_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +#else +static int vd55g0_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +#endif +{ + if (code->index >= ARRAY_SIZE(vd55g0_supported_codes)) + return -EINVAL; + + code->code = vd55g0_supported_codes[code->index].code; + + return 0; +} + +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE +static int vd55g0_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +#else +static int vd55g0_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +#endif +{ + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE + fmt = v4l2_subdev_get_try_format(&sensor->sd, cfg, + format->pad); +#elif KERNEL_VERSION(5, 19, 0) > LINUX_VERSION_CODE + fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, + format->pad); +#elif KERNEL_VERSION(6, 8, 0) > LINUX_VERSION_CODE + fmt = v4l2_subdev_get_pad_format(&sensor->sd, sd_state, + format->pad); +#else + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); +#endif + else + fmt = &sensor->fmt; + + format->format = *fmt; + + mutex_unlock(&sensor->lock); + + return 0; +} + +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE +static int vd55g0_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +#else +static int vd55g0_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +#endif +{ + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + const struct vd55g0_mode_info *new_mode; + struct v4l2_mbus_framefmt *fmt; + unsigned int expo_max, hblank; + int ret; + + mutex_lock(&sensor->lock); + + ret = vd55g0_try_fmt_internal(sd, &format->format, &new_mode); + if (ret) + goto out; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE + fmt = v4l2_subdev_get_try_format(sd, cfg, 0); +#elif KERNEL_VERSION(5, 19, 0) > LINUX_VERSION_CODE + fmt = v4l2_subdev_get_try_format(sd, sd_state, 0); +#elif KERNEL_VERSION(6, 8, 0) > LINUX_VERSION_CODE + fmt = v4l2_subdev_get_pad_format(sd, sd_state, 0); +#else + fmt = v4l2_subdev_state_get_format(sd_state, 0); +#endif + *fmt = format->format; + } else if (sensor->current_mode != new_mode || + sensor->fmt.code != format->format.code) { + fmt = &sensor->fmt; + *fmt = format->format; + + sensor->current_mode = new_mode; + + /* Reset vblank and framelength to default */ + ret = vd55g0_update_vblank(sensor, + VD55G0_FRAME_LENGTH_DEF - + new_mode->crop.height); + + /* Update controls to reflect new mode */ + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, + get_pixel_rate(sensor)); + __v4l2_ctrl_modify_range(sensor->vblank_ctrl, + sensor->vblank_min, + 0xffff - new_mode->crop.height, + 1, sensor->vblank); + __v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank); + /* Max exposure changes with vblank */ + expo_max = sensor->frame_length - VD55G0_EXPO_MAX_TERM; + __v4l2_ctrl_modify_range(sensor->expo_ctrl, 0, expo_max, 1, + VD55G0_EXPO_DEF); + /* Update hblank according to new width */ + hblank = sensor->line_length - sensor->current_mode->width; + __v4l2_ctrl_modify_range(sensor->hblank_ctrl, hblank, hblank, 1, + hblank); + ret = __v4l2_ctrl_s_ctrl(sensor->hblank_ctrl, hblank); + } + +out: + mutex_unlock(&sensor->lock); + + return ret; +} + +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE +static int vd55g0_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +#elif KERNEL_VERSION(6, 8, 0) > LINUX_VERSION_CODE +static int vd55g0_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +#else +static int vd55g0_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +#endif +{ + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + struct v4l2_subdev_format fmt = { 0 }; + + vd55g0_fill_framefmt(sensor, sensor->current_mode, &fmt.format, + VD55G0_MEDIA_BUS_FMT_DEF); + +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE + return vd55g0_set_fmt(sd, cfg, &fmt); +#else + return vd55g0_set_fmt(sd, sd_state, &fmt); +#endif +} + +#if KERNEL_VERSION(5, 14, 0) > LINUX_VERSION_CODE +static int vd55g0_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +#else +static int vd55g0_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +#endif +{ + if (fse->index >= ARRAY_SIZE(vd55g0_mode_data)) + return -EINVAL; + + fse->min_width = vd55g0_mode_data[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = vd55g0_mode_data[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static const struct v4l2_subdev_core_ops vd55g0_core_ops = { +}; + +static const struct v4l2_subdev_video_ops vd55g0_video_ops = { + .s_stream = vd55g0_s_stream, +}; + +static const struct v4l2_subdev_pad_ops vd55g0_pad_ops = { +#if KERNEL_VERSION(6, 8, 0) > LINUX_VERSION_CODE + .init_cfg = vd55g0_init_cfg, +#endif + .enum_mbus_code = vd55g0_enum_mbus_code, + .get_fmt = vd55g0_get_fmt, + .set_fmt = vd55g0_set_fmt, + .get_selection = vd55g0_get_selection, + .enum_frame_size = vd55g0_enum_frame_size, +}; + +static const struct v4l2_subdev_ops vd55g0_subdev_ops = { + .core = &vd55g0_core_ops, + .video = &vd55g0_video_ops, + .pad = &vd55g0_pad_ops, +}; + +static const struct media_entity_operations vd55g0_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +#if KERNEL_VERSION(6, 8, 0) > LINUX_VERSION_CODE +#else +static const struct v4l2_subdev_internal_ops vd55g0_internal_ops = { + .init_state = vd55g0_init_state, +}; +#endif + +static int vd55g0_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + int temperature; + int ret; + + switch (ctrl->id) { + case V4L2_CID_PIXEL_RATE: + ret = __v4l2_ctrl_s_ctrl_int64(ctrl, get_pixel_rate(sensor)); + break; + case V4L2_CID_TEMPERATURE: + ret = vd55g0_get_temp(sensor, &temperature); + if (ret) + break; + ret = __v4l2_ctrl_s_ctrl(ctrl, temperature); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int vd55g0_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + unsigned int expo_max; + int ret; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + case V4L2_CID_HFLIP: + if (sensor->streaming) { + ret = -EBUSY; + break; + } + if (ctrl->id == V4L2_CID_VFLIP) + sensor->vflip = ctrl->val; + if (ctrl->id == V4L2_CID_HFLIP) + sensor->hflip = ctrl->val; + ret = 0; + break; + case V4L2_CID_TEST_PATTERN: + /* Can't be done while streaming because of duster disabling */ + sensor->pattern = ctrl->val; + ret = 0; + break; + case V4L2_CID_EXPOSURE_AUTO: + ret = vd55g0_update_exposure_auto(sensor, ctrl->val); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = vd55g0_update_analog_gain(sensor, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = vd55g0_update_digital_gain(sensor, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = vd55g0_update_exposure(sensor, ctrl->val); + break; + case V4L2_CID_3A_LOCK: + ret = vd55g0_lock_exposure(sensor, ctrl); + break; + case V4L2_CID_DARKCAL_PEDESTAL: + ret = vd55g0_update_darkcal_pedestal(sensor, ctrl->val); + break; + case V4L2_CID_VBLANK: + ret = vd55g0_update_vblank(sensor, ctrl->val); + /* Max exposure changes with vblank */ + expo_max = sensor->frame_length - VD55G0_EXPO_MAX_TERM; + __v4l2_ctrl_modify_range(sensor->expo_ctrl, 0, expo_max, 1, + VD55G0_EXPO_DEF); + break; + case V4L2_CID_HBLANK: + /* Read only control, can only be activated by V4L2 framework */ + ret = 0; + break; + case V4L2_CID_AUTO_EXPOSURE_BIAS: + /* + * We use auto exposure target percentage register to control + * exposure bias for more precision. + */ + ret = vd55g0_update_exposure_target(sensor, ctrl->val); + break; + case V4L2_CID_SLAVE: + sensor->is_slave = ctrl->val; + ret = 0; + break; + case V4L2_CID_FLASH_LED_MODE: + ret = vd55g0_update_flash(sensor, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops vd55g0_ctrl_ops = { + .g_volatile_ctrl = vd55g0_g_volatile_ctrl, + .s_ctrl = vd55g0_s_ctrl, +}; + +static const struct v4l2_ctrl_config vd55g0_temp_ctrl = { + .ops = &vd55g0_ctrl_ops, + .id = V4L2_CID_TEMPERATURE, + .name = "Temperature in celsius", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = -1024, + .max = 1023, + .step = 1, +}; + +static const struct v4l2_ctrl_config vd55g0_darkcal_pedestal_ctrl = { + .ops = &vd55g0_ctrl_ops, + .id = V4L2_CID_DARKCAL_PEDESTAL, + .name = "Dark Calibration Pedestal", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 255, + .step = 1, + .def = VD55G0_DARKCAL_PEDESTAL_DEF, +}; + +static const struct v4l2_ctrl_config vd55g0_slave_ctrl = { + .ops = &vd55g0_ctrl_ops, + .id = V4L2_CID_SLAVE, + .name = "VT Slave Mode", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static int vd55g0_init_controls(struct vd55g0_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &vd55g0_ctrl_ops; + struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler; + const struct vd55g0_mode_info *cur_mode = sensor->current_mode; + struct v4l2_ctrl *ctrl; + unsigned int patgen_size = ARRAY_SIZE(vd55g0_test_pattern_menu) - 1; + unsigned int hblank = sensor->line_length - sensor->current_mode->width; + unsigned int expo_mode = sensor->expo_state == VD55G0_EXP_AUTO ? + V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL; + int ret; + + v4l2_ctrl_handler_init(hdl, 16); + /* we can use our own mutex for the ctrl lock */ + hdl->lock = &sensor->lock; + v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_AUTO_EXPOSURE_BIAS, + ARRAY_SIZE(vd55g0_ev_bias_menu) - 1, + ARRAY_SIZE(vd55g0_ev_bias_menu) / 2, + vd55g0_ev_bias_menu); + ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - 1, 0, link_freq); + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_EXPOSURE_AUTO, 1, ~0x3, + expo_mode); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 24, 1, + sensor->analog_gain); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 256, 2048, 1, + sensor->digital_gain); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_3A_LOCK, 0, 1, 0, 0); + ctrl = v4l2_ctrl_new_custom(hdl, &vd55g0_temp_ctrl, NULL); + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY; + v4l2_ctrl_new_custom(hdl, &vd55g0_darkcal_pedestal_ctrl, NULL); + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_FLASH_LED_MODE, + V4L2_FLASH_LED_MODE_FLASH, ~0x7, + sensor->flash_en); + + /* + * Keep a pointer to these controls as we need to update them when + * setting the format + */ + sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_PIXEL_RATE, 1, + INT_MAX, 1, + get_pixel_rate(sensor)); + if (sensor->pixel_rate_ctrl) + sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + sensor->vblank_min, + 0xffff - cur_mode->crop.height, + 1, sensor->vblank); + sensor->hblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, + hblank, hblank, 1, hblank); + if (sensor->hblank_ctrl) + sensor->hblank_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, + 0, 1, 1, sensor->vflip); + sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, + 0, 1, 1, sensor->hflip); + sensor->pattern_ctrl = + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + patgen_size, 0, 0, + vd55g0_test_pattern_menu); + sensor->slave_ctrl = v4l2_ctrl_new_custom(hdl, &vd55g0_slave_ctrl, + NULL); + sensor->expo_ctrl = + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 0, + sensor->frame_length - VD55G0_EXPO_MAX_TERM, + 1, sensor->manual_expo); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + /* Disable this control if not possible by device tree */ + if (!vd55g0_can_be_slave(sensor)) { + v4l2_ctrl_s_ctrl(sensor->slave_ctrl, false); + v4l2_ctrl_grab(sensor->slave_ctrl, true); + } + + sensor->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static int vd55g0_detect(struct vd55g0_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int revision, id = 0; + + id = vd55g0_read_reg(sensor, VD55G0_REG_MODEL_ID); + if (id < 0) + return id; + + if (id != VD55G0_MODEL_ID) { + dev_warn(&client->dev, "Unsupported sensor id %x", id); + return -ENODEV; + } + + revision = vd55g0_read_reg(sensor, VD55G0_REG_REVISION); + dev_dbg(&client->dev, "Sensor revision 0x%x", revision); + if (revision < 0) + return revision; + + if (revision != VD55G0_REVISION_CUT1 && + revision != VD55G0_REVISION_CUT2) { + dev_err(&client->dev, "Unsupported device revision 0x%x\n", + revision); + return -ENODEV; + } + + sensor->revision = revision; + + return 0; +} + +/* Power/clock management functions */ +static int vd55g0_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(vd55g0_supply_name), + sensor->supplies); + if (ret) { + dev_err(&client->dev, "failed to enable regulators %d", ret); + return ret; + } + + ret = clk_prepare_enable(sensor->xclk); + if (ret) { + dev_err(&client->dev, "failed to enable clock %d", ret); + goto disable_bulk; + } + + if (sensor->reset_gpio) + vd55g0_apply_reset(sensor); + + ret = vd55g0_wait_state(sensor, VD55G0_SYSTEM_FSM_READY_TO_BOOT, + VD55G0_TIMEOUT_MS); + if (ret) { + dev_err(&client->dev, "sensor reset failed %d\n", ret); + goto disable_clock; + } + + ret = vd55g0_detect(sensor); + if (ret) { + dev_err(&client->dev, "sensor detect failed %d", ret); + goto disable_clock; + } + + ret = vd55g0_patch(sensor); + if (ret) { + dev_err(&client->dev, "sensor patch failed %d", ret); + goto disable_clock; + } + + ret = vd55g0_boot(sensor); + if (ret) { + dev_err(&client->dev, "sensor boot failed %d", ret); + goto disable_clock; + } + + ret = vd55g0_configure(sensor); + if (ret) { + dev_err(&client->dev, "sensor configuration failed %d", ret); + goto disable_clock; + } + + return 0; + +disable_clock: + clk_disable_unprepare(sensor->xclk); +disable_bulk: + regulator_bulk_disable(ARRAY_SIZE(vd55g0_supply_name), + sensor->supplies); + + return ret; +} + +static int vd55g0_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + + clk_disable_unprepare(sensor->xclk); + regulator_bulk_disable(ARRAY_SIZE(vd55g0_supply_name), + sensor->supplies); + return 0; +} + +static int vd55g0_parse_dt_gpios_array(struct vd55g0_dev *sensor, + char *prop_name, u32 *array, int *nb) +{ + struct i2c_client *client = sensor->i2c_client; + struct device_node *np = client->dev.of_node; + unsigned int i; + + *nb = of_property_read_variable_u32_array(np, prop_name, array, 0, + VD55G0_NB_GPIOS); + *nb = max(0, *nb); + if (*nb > 0) { + for (i = 0; i < *nb; i++) { + if (array[i] >= VD55G0_NB_GPIOS) { + dev_err(&client->dev, "invalid GPIO %d for leds\n", + array[i]); + return -EINVAL; + } + } + } + + return 0; +} + +static int vd55g0_parse_dt_gpios(struct vd55g0_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + struct device_node *np = client->dev.of_node; + struct vd55g0_gpios *gpios = &sensor->gpios; + int nb_gpios_leds, nb_gpios_out; + int ret; + unsigned int i, j; + + memset(gpios->leds, ~0, + ARRAY_SIZE(gpios->leds) * sizeof(gpios->leds[0])); + memset(gpios->out_sync, ~0, + ARRAY_SIZE(gpios->out_sync) * sizeof(gpios->out_sync[0])); + gpios->in_sync = ~0; + + ret = vd55g0_parse_dt_gpios_array(sensor, "st,leds", + (u32 *)&gpios->leds, + &nb_gpios_leds); + if (ret) + return ret; + + ret = vd55g0_parse_dt_gpios_array(sensor, "st,out-sync", + (u32 *)&gpios->out_sync, + &nb_gpios_out); + if (ret) + return ret; + + ret = of_property_read_u32(np, "st,in-sync", &gpios->in_sync); + if (ret == 0) { + if (gpios->in_sync != 0) { + dev_err(&client->dev, "input sync gpio must be 0 if present, found %d\n", + gpios->in_sync); + return -EINVAL; + } + + /* Check no other gpios array use gpio 0 */ + for (i = 0; i < nb_gpios_leds; i++) { + if (gpios->leds[i] == gpios->in_sync) { + dev_err(&client->dev, "in-sync GPIO %d is used by another led gpio\n", + gpios->in_sync); + return -EINVAL; + } + } + for (i = 0; i < nb_gpios_out; i++) { + if (gpios->out_sync[i] == gpios->in_sync) { + dev_err(&client->dev, "in-sync GPIO %d is used by another out-sync gpio\n", + gpios->in_sync); + return -EINVAL; + } + } + + dev_dbg(&client->dev, "GPIO %d in input slave mode\n", + gpios->in_sync); + + sensor->is_slave = true; + } + + /* Check mutual exclusivity between leds and out_sync */ + for (i = 0; i < nb_gpios_leds; i++) { + for (j = 0; j < nb_gpios_out; j++) { + if (gpios->leds[i] == gpios->out_sync[j]) { + dev_err(&client->dev, "GPIO %d used in both leds and out-sync\n", + gpios->leds[i]); + return -EINVAL; + } + } + } + + return 0; +} + +static int vd55g0_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *handle; + struct vd55g0_dev *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->analog_gain = 0; + sensor->digital_gain = 256; + + sensor->i2c_client = client; + sensor->streaming = false; + sensor->fmt.code = MEDIA_BUS_FMT_SGBRG8_1X8; + sensor->fmt.field = V4L2_FIELD_NONE; + sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB; + sensor->manual_expo = VD55G0_EXPO_DEF; + sensor->vflip = false; + sensor->hflip = false; + sensor->darkcal_pedestal = VD55G0_DARKCAL_PEDESTAL_DEF; + sensor->flash_en = false; + sensor->cold_start.expo = 0x32; + sensor->cold_start.digital_gain = 256; + sensor->cold_start.analog_gain = 0; + + sensor->current_mode = &vd55g0_mode_data[VD55G0_DEFAULT_MODE]; + + handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!handle && ACPI_COMPANION(dev)) { + /* + * ACPI: IPU bridge creates software nodes with endpoint info, + * but if unavailable, use Surface Pro 11 defaults. + */ + dev_dbg(dev, "ACPI mode: using default endpoint config\n"); + sensor->oif_ctrl = 1; /* 1 lane, no polarity adjustments */ + sensor->nb_of_lane = 1; + sensor->data_rate_in_mbps = 600; /* 300MHz link x 2 (DDR) */ + goto skip_endpoint; + } + if (!handle) { + dev_err(dev, "handle node not found\n"); + return -EINVAL; + } + + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* DT GPIO properties only available with device tree */ + if (dev->of_node) { + ret = vd55g0_parse_dt_gpios(sensor); + if (ret) { + dev_err(dev, "Failed to get gpios\n"); + return ret; + } + } else { + /* ACPI: initialize GPIO arrays to disabled defaults */ + memset(sensor->gpios.leds, ~0, sizeof(sensor->gpios.leds)); + memset(sensor->gpios.out_sync, ~0, sizeof(sensor->gpios.out_sync)); + sensor->gpios.in_sync = ~0; + } + + ret = vd55g0_tx_from_ep(sensor, handle); + fwnode_handle_put(handle); + if (ret) { + dev_err(dev, "Failed to parse handle %d\n", ret); + return ret; + } + +skip_endpoint: + sensor->xclk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(sensor->xclk); + } + if (sensor->xclk) { + sensor->clk_freq = clk_get_rate(sensor->xclk); + if (sensor->clk_freq < 6 * HZ_PER_MHZ || + sensor->clk_freq > 27 * HZ_PER_MHZ) { + dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n", + sensor->clk_freq / HZ_PER_MHZ); + return -EINVAL; + } + } else { + /* ACPI/INT3472 manages clock externally; default 24MHz */ + sensor->clk_freq = 24 * HZ_PER_MHZ; + } + + v4l2_i2c_subdev_init(&sensor->sd, client, &vd55g0_subdev_ops); +#if KERNEL_VERSION(6, 8, 0) > LINUX_VERSION_CODE +#else + sensor->sd.internal_ops = &vd55g0_internal_ops; +#endif + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.ops = &vd55g0_subdev_entity_ops; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = vd55g0_get_regulators(sensor); + if (ret) { + dev_err(&client->dev, "failed to get regulators %d", ret); + return ret; + } + + //TODO power on should not be necessary + ret = vd55g0_power_on(dev); + if (ret) + return ret; + + vd55g0_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt, + VD55G0_MEDIA_BUS_FMT_DEF); + + mutex_init(&sensor->lock); + + ret = vd55g0_update_vblank(sensor, VD55G0_FRAME_LENGTH_DEF - + sensor->current_mode->crop.height); + if (ret) + goto error_power_off; + + ret = vd55g0_init_controls(sensor); + if (ret) { + dev_err(&client->dev, "controls initialization failed %d", ret); + goto error_power_off; + } + + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) { + dev_err(&client->dev, "pads init failed %d", ret); + goto error_handler_free; + } + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret) { + dev_err(&client->dev, "async subdev register failed %d", ret); + goto error_pm_runtime; + } + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + dev_dbg(&client->dev, "vd55g0 probe successfully"); + + return 0; + +error_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + media_entity_cleanup(&sensor->sd.entity); +error_handler_free: + v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); +error_power_off: + mutex_destroy(&sensor->lock); + vd55g0_power_off(dev); + + return ret; +} + +#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE +static int vd55g0_remove(struct i2c_client *client) +#else +static void vd55g0_remove(struct i2c_client *client) +#endif +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vd55g0_dev *sensor = to_vd55g0_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + mutex_destroy(&sensor->lock); + media_entity_cleanup(&sensor->sd.entity); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + vd55g0_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE + return 0; +#endif +} + +static const struct of_device_id vd55g0_dt_ids[] = { + { .compatible = "st,vd55g0" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vd55g0_dt_ids); + +static const struct acpi_device_id vd55g0_acpi_ids[] = { + { "SMO55F0" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, vd55g0_acpi_ids); + +static const struct dev_pm_ops vd55g0_pm_ops = { + SET_RUNTIME_PM_OPS(vd55g0_power_off, vd55g0_power_on, NULL) +}; + +static struct i2c_driver vd55g0_i2c_driver = { + .driver = { + .name = "vd55g0", + .of_match_table = vd55g0_dt_ids, + .acpi_match_table = ACPI_PTR(vd55g0_acpi_ids), + .pm = &vd55g0_pm_ops, + }, +#if KERNEL_VERSION(6, 3, 0) > LINUX_VERSION_CODE + .probe_new = vd55g0_probe, +#else + .probe = vd55g0_probe, +#endif + .remove = vd55g0_remove, +}; + +module_i2c_driver(vd55g0_i2c_driver); + +MODULE_AUTHOR("Benjamin Mugnier "); +MODULE_AUTHOR("Mickael Guene "); +MODULE_AUTHOR("Sylvain Petinot "); +MODULE_DESCRIPTION("VD55G0 camera subdev driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/vd55g0_patches.h b/drivers/media/i2c/vd55g0_patches.h new file mode 100644 index 000000000000..0f31b59b94a9 --- /dev/null +++ b/drivers/media/i2c/vd55g0_patches.h @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Firmware patches for vd55g0 sensor familly + * + * Copyright (C) 2024 STMicroelectronics SA + * + * Adapter with small changes by André Gilerson + */ + +#include +#include + +static const u8 cut1_patch[] = { +0x81, 0x06, 0x0b, 0x02, 0x00, 0x09, 0x42, 0x00, 0xd2, 0x08, 0x42, 0x00, 0xe6, +0x08, 0x42, 0x00, 0x12, 0x09, 0x42, 0x00, 0xe0, 0x3f, 0x80, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x40, 0x01, 0x60, 0x10, 0x40, 0x11, +0x81, 0x16, 0x80, 0x93, 0xdd, 0x4c, 0x04, 0x04, 0xfa, 0x06, 0x4c, 0x94, 0x60, +0xe6, 0x0e, 0xdc, 0xe0, 0x04, 0x18, 0x70, 0x02, 0x08, 0xe7, 0xce, 0x44, 0x01, +0x60, 0x10, 0x40, 0x31, 0x85, 0x1e, 0x08, 0x4e, 0x43, 0x01, 0x60, 0x10, 0xc0, +0x31, 0x40, 0x20, 0xe0, 0x04, 0x98, 0x29, 0x00, 0x50, 0xeb, 0x2e, 0x84, 0x24, +0x84, 0x29, 0x86, 0x2e, 0x84, 0x24, 0x84, 0x2b, 0xa1, 0x15, 0xfe, 0xd8, 0x41, +0x00, 0xe8, 0xf8, 0x47, 0x00, 0xe8, 0x78, 0x44, 0x40, 0xef, 0x0a, 0x9c, 0x1a, +0x9e, 0x2a, 0xdc, 0x3a, 0xde, 0x03, 0x4e, 0x7c, 0xf9, 0x13, 0x4e, 0x78, 0xf9, +0x23, 0x4e, 0x74, 0xf9, 0x33, 0x4e, 0x70, 0xf9, 0x18, 0x50, 0x40, 0xef, 0x38, +0x49, 0x44, 0xef, 0x04, 0x18, 0x11, 0x83, 0x11, 0x40, 0x20, 0xe0, 0x4c, 0x0c, +0x04, 0xf2, 0x93, 0xdd, 0x86, 0x44, 0x88, 0xe0, 0x05, 0x04, 0x40, 0xf8, 0x09, +0x00, 0x24, 0xe0, 0x4a, 0x40, 0x88, 0x60, 0x06, 0x40, 0x94, 0xe0, 0x04, 0x80, +0x65, 0x86, 0x31, 0x01, 0x06, 0x02, 0x0c, 0xe1, 0x33, 0x40, 0xc8, 0xe2, 0x33, +0x40, 0x70, 0xe2, 0x14, 0x84, 0x18, 0x48, 0x1b, 0xa3, 0x55, 0x8a, 0x0a, 0x02, +0x08, 0x70, 0x16, 0x43, 0x58, 0xe0, 0x26, 0x08, 0x00, 0x40, 0x16, 0x0a, 0x00, +0xc0, 0x41, 0x44, 0x08, 0x40, 0x06, 0x46, 0x08, 0xe0, 0x51, 0x44, 0x04, 0xc0, +0x45, 0x85, 0x66, 0x02, 0x04, 0xe0, 0x13, 0x40, 0x70, 0x62, 0x06, 0x02, 0x0c, +0xe1, 0x14, 0x84, 0x18, 0x48, 0x1b, 0xa3, 0xb5, 0x86, 0x0a, 0x02, 0x08, 0x70, +0xa6, 0x02, 0x68, 0xe0, 0x26, 0x0a, 0x00, 0x40, 0x16, 0x18, 0x00, 0xc0, 0x31, +0x01, 0xc6, 0x09, 0x0c, 0xe0, 0x51, 0x44, 0x08, 0xc0, 0xc1, 0x44, 0x04, 0xc0, +0x95, 0x83, 0x0a, 0x02, 0x04, 0xf0, 0x16, 0x02, 0x00, 0xc0, 0x11, 0x44, 0x08, +0xc0, 0x11, 0x44, 0x04, 0x40, 0x11, 0x81, 0x33, 0x40, 0x18, 0xe3, 0x15, 0x83, +0x0a, 0x02, 0x04, 0x70, 0x11, 0x81, 0x16, 0x0a, 0x00, 0x40, 0x31, 0x9d, 0x51, +0x44, 0x08, 0x40, 0x06, 0x09, 0x04, 0xe0, 0x51, 0x44, 0x04, 0xc0, 0x43, 0x40, +0x18, 0xe3, 0x33, 0x40, 0xc8, 0xe2, 0x13, 0x40, 0xc0, 0x62, 0xa6, 0x05, 0x78, +0xe0, 0x23, 0x40, 0xd0, 0xe3, 0x93, 0xdd, 0xc3, 0xc1, 0x4c, 0x04, 0x7c, 0xfa, +0x86, 0x42, 0x84, 0x60, 0x06, 0x00, 0x0c, 0xe1, 0x14, 0x04, 0xf6, 0x50, 0x78, +0xe3, 0x04, 0x00, 0x26, 0x0f, 0x28, 0xe2, 0x16, 0x15, 0x2c, 0x64, 0xb1, 0xbb, +0x6a, 0x84, 0x0b, 0xa3, 0x36, 0x10, 0x10, 0x41, 0x2e, 0x54, 0x08, 0xd8, 0x16, +0x4f, 0x44, 0x43, 0x06, 0x16, 0x00, 0xc0, 0x0b, 0x10, 0x1b, 0x8c, 0xb8, 0x4f, +0x00, 0x6f, 0xa4, 0x12, 0x80, 0xfb, 0x06, 0x42, 0x1a, 0x60, 0x00, 0x40, 0xfc, +0x73, 0x03, 0xc0, 0x83, 0x22, 0x0e, 0x02, 0xc1, 0x7f, 0x1f, 0xc0, 0x87, 0xd4, +0x8f, 0x02, 0x1b, 0x8c, 0x8f, 0x20, 0x0b, 0x8e, 0x98, 0x4e, 0x00, 0xef, 0x06, +0x42, 0x1a, 0x60, 0x00, 0x40, 0xfc, 0xf0, 0x73, 0xa2, 0x77, 0xcc, 0x70, 0x42, +0x01, 0x73, 0xff, 0xc7, 0x12, 0x4e, 0x80, 0x79, 0x0b, 0x92, 0xb8, 0x4d, 0x00, +0x6f, 0x1b, 0x8c, 0x06, 0x42, 0x94, 0x60, 0x06, 0x46, 0x1a, 0xe0, 0x24, 0x04, +0x00, 0x40, 0xfd, 0x73, 0x03, 0xc0, 0xa3, 0xa6, 0xa7, 0xd4, 0xb3, 0x44, 0x60, +0xe3, 0x85, 0x44, 0x9c, 0xe0, 0x24, 0x84, 0x75, 0x44, 0xa0, 0x60, 0x0e, 0x04, +0xc0, 0x7f, 0x1f, 0xc0, 0x14, 0x04, 0xaf, 0x84, 0xaf, 0xa0, 0xa5, 0x42, 0xa4, +0xe0, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x0c, 0x04, 0x0c, 0xfa, 0x46, 0x0d, +0xcc, 0xe0, 0x09, 0x0c, 0xbc, 0xee, 0x0a, 0x40, 0x80, 0xe0, 0x45, 0x82, 0xa6, +0x40, 0x08, 0xe0, 0x06, 0x98, 0x08, 0x7f, 0x74, 0xef, 0x0b, 0xa1, 0xb5, 0xfe, +0x01, 0x87, 0x06, 0x98, 0x06, 0x4c, 0x94, 0x60, 0x06, 0x42, 0x0c, 0xe0, 0x05, +0x0c, 0x14, 0xe0, 0x11, 0x40, 0x30, 0x62, 0x26, 0x42, 0x00, 0xe0, 0x05, 0x0c, +0x14, 0xe0, 0x11, 0x40, 0xa0, 0x62, 0x56, 0x43, 0x04, 0xe0, 0x05, 0x0c, 0x14, +0xe0, 0x11, 0x40, 0x40, 0x62, 0x76, 0x43, 0x04, 0xe0, 0x05, 0x0c, 0x14, 0xe0, +0x11, 0x40, 0x50, 0xe2, 0x04, 0x98, 0x19, 0x00, 0x50, 0xeb, 0x4a, 0x42, 0x88, +0xe0, 0xc5, 0x80, 0x28, 0x67, 0x54, 0x6f, 0x66, 0x00, 0xa0, 0xe0, 0x04, 0x98, +0x19, 0x00, 0x30, 0xe0, 0x19, 0xc4, 0x11, 0x40, 0x30, 0x60, 0x11, 0x83, 0x04, +0x9a, 0x11, 0x40, 0x10, 0xe0, 0x04, 0x98, 0x11, 0x40, 0x20, 0x60, 0x11, 0x81, +0x04, 0x98, 0x11, 0x40, 0xb0, 0xe7, 0x19, 0x00, 0x50, 0xeb, 0x4a, 0x02, 0x0c, +0xfc, 0x85, 0xfe, 0xc6, 0x4c, 0xb4, 0xe0, 0x60, 0x10, 0xb1, 0x64, 0x01, 0xc0, +0x0a, 0xe0, 0x0b, 0xa1, 0x95, 0x8c, 0x04, 0xe0, 0x0b, 0xa1, 0x25, 0x8c, 0xd8, +0x43, 0x5c, 0xef, 0x0a, 0xe0, 0x0b, 0xa1, 0x95, 0x8a, 0x04, 0xe0, 0x0b, 0xa1, +0x25, 0x8a, 0xc6, 0x4e, 0x84, 0xe0, 0x04, 0x9c, 0x09, 0x00, 0x20, 0xe1, 0x4a, +0x40, 0x80, 0xe0, 0x25, 0x86, 0x08, 0x22, 0x46, 0x43, 0x00, 0xe0, 0x06, 0x40, +0x06, 0xe0, 0x63, 0x80, 0x09, 0x0c, 0x40, 0xe2, 0x09, 0x86, 0x0b, 0xa5, 0xd5, +0x82, 0x68, 0x4e, 0x48, 0x6f, 0x01, 0x85, 0x78, 0x44, 0x48, 0x6f, 0x06, 0x00, +0x80, 0xe1, 0xf8, 0x75, 0x4c, 0x6f, 0x06, 0x00, 0x80, 0xe1, 0x78, 0x48, 0x48, +0xef, 0x09, 0x0e, 0x48, 0xe0, 0x0b, 0xa1, 0xb5, 0x80, 0x58, 0x7b, 0x3c, 0xef, +0x78, 0x74, 0x40, 0xef, 0xa6, 0x40, 0x08, 0xe0, 0x0c, 0x0c, 0x0c, 0xf2, 0x18, +0x12, 0x50, 0xff, 0xc3, 0xc1, 0x4c, 0x04, 0x04, 0xfa, 0x7b, 0x00, 0x06, 0x01, +0x08, 0xe1, 0x08, 0x00, 0x6b, 0x82, 0x0b, 0xa1, 0x15, 0x82, 0xa6, 0x41, 0x84, +0xe0, 0x18, 0x00, 0x01, 0x89, 0x18, 0x42, 0x00, 0xef, 0x63, 0x80, 0x04, 0x1c, +0x64, 0x02, 0x84, 0xe0, 0x78, 0x40, 0x00, 0x68, 0x00, 0x00, 0x40, 0xe6, 0x4c, +0x0c, 0x04, 0xf2, 0x93, 0xdd, 0x1e, 0x80, 0x93, 0xdd, 0x0c, 0x04, 0x3c, 0xfa, +0xf0, 0x1e, 0x40, 0x6c, 0x86, 0x0f, 0xe0, 0xe0, 0x01, 0x05, 0x91, 0x81, 0x06, +0x02, 0x00, 0x62, 0x81, 0xb1, 0x01, 0x4e, 0xd8, 0x69, 0x0e, 0x00, 0x00, 0x60, +0x01, 0xc0, 0x0e, 0x7c, 0x6b, 0x9e, 0x93, 0x4e, 0x14, 0x65, 0x86, 0x00, 0x30, +0xe1, 0x93, 0x4e, 0x18, 0x65, 0x2b, 0x8c, 0x91, 0x4e, 0x50, 0xe1, 0x81, 0x4e, +0xc8, 0xe9, 0x1e, 0xbe, 0x1e, 0x7e, 0x11, 0x8f, 0x9e, 0xbc, 0xd8, 0x55, 0x30, +0xef, 0x06, 0x00, 0x00, 0x64, 0x46, 0x02, 0x54, 0xe1, 0x0e, 0x3e, 0x06, 0x00, +0x20, 0xe0, 0x0e, 0x7e, 0x86, 0x00, 0x30, 0xe1, 0x1e, 0x7c, 0x11, 0x91, 0x9e, +0x3c, 0x2b, 0x8c, 0xd8, 0x54, 0x30, 0xef, 0x06, 0x00, 0x00, 0x66, 0xa1, 0x83, +0x0e, 0x3e, 0xf6, 0x41, 0xfc, 0xe9, 0x0e, 0x7e, 0x06, 0x00, 0x01, 0x60, 0x20, +0xc0, 0x0e, 0x7c, 0x86, 0x00, 0x30, 0xe1, 0xae, 0x3c, 0x11, 0x93, 0xc8, 0x53, +0x30, 0x6f, 0x2b, 0x8c, 0x06, 0x40, 0x88, 0x60, 0xf6, 0x45, 0xfc, 0x63, 0xff, +0xc3, 0x14, 0x00, 0xf1, 0xa0, 0x95, 0x4e, 0xa8, 0xe2, 0xa1, 0x4e, 0x44, 0xea, +0x81, 0x4e, 0xf0, 0x69, 0x10, 0x42, 0x00, 0x7c, 0x00, 0xcc, 0x1f, 0x04, 0x21, +0x8b, 0x1e, 0x00, 0x86, 0x40, 0xfc, 0xe7, 0x05, 0x4e, 0xa4, 0x62, 0xc6, 0x01, +0x5c, 0xe0, 0x05, 0x4e, 0xac, 0x62, 0x9e, 0x00, 0x00, 0x60, 0x02, 0xc0, 0x05, +0x4e, 0x94, 0x62, 0x86, 0x40, 0x3c, 0x60, 0x10, 0xe7, 0x0e, 0x9c, 0x09, 0x0e, +0x0c, 0xe1, 0x15, 0x4e, 0x3c, 0x79, 0x06, 0x02, 0x80, 0xe0, 0x13, 0x4e, 0xf4, +0x64, 0x86, 0x02, 0x00, 0x60, 0x02, 0xc0, 0x21, 0x4e, 0x5c, 0x69, 0x00, 0x40, +0xc0, 0xf3, 0x1e, 0x9e, 0x01, 0x4e, 0x0c, 0xe1, 0x0c, 0x0c, 0x3c, 0xf2, 0x93, +0xdd, 0xc6, 0x01, 0xcc, 0x60, 0x06, 0x42, 0x88, 0xe0, 0x29, 0x00, 0x98, 0xee, +0x34, 0x84, 0x4a, 0x44, 0x80, 0x60, 0x86, 0x04, 0x30, 0xe1, 0x24, 0x08, 0x30, +0x46, 0xfd, 0x73, 0xff, 0xc3, 0x3e, 0x04, 0xa6, 0x08, 0xa0, 0x80, 0x34, 0x06, +0xc8, 0x9c, 0x45, 0x44, 0xa0, 0x80, 0x35, 0x42, 0x00, 0x00, 0x06, 0x06, 0x00, +0xc0, 0x35, 0x44, 0xa0, 0xc0, 0x35, 0x02, 0x00, 0xc0, 0x3e, 0x80, 0x93, 0xdd, +0x4c, 0x00, 0x00, 0xfa, 0x88, 0x40, 0x00, 0xe8, 0x86, 0x02, 0x0c, 0xe1, 0x0e, +0x84, 0x4c, 0x08, 0x00, 0xf2, 0x93, 0xdd, 0x93, 0xdd, 0xc3, 0xc1, 0x4c, 0x04, +0x00, 0xf8, 0xc6, 0x40, 0xb4, 0xe0, 0x00, 0x02, 0xb1, 0x64, 0x01, 0xc0, 0x2a, +0xc4, 0x2b, 0xa1, 0x35, 0x92, 0x24, 0xc4, 0x2b, 0xa1, 0xc5, 0x90, 0x38, 0x06, +0x46, 0x45, 0x00, 0xe0, 0x36, 0x46, 0x0a, 0xe0, 0x33, 0x80, 0x49, 0x06, 0xf0, +0xe2, 0x4d, 0xe6, 0xc6, 0x40, 0x48, 0x41, 0x16, 0x02, 0x00, 0xc0, 0x11, 0x40, +0x00, 0xc0, 0xd5, 0x8c, 0xc9, 0x06, 0x44, 0x62, 0x06, 0x4b, 0x00, 0xe0, 0x46, +0x48, 0x16, 0x60, 0x86, 0x4a, 0xc8, 0xe0, 0x53, 0x08, 0xc0, 0x58, 0x80, 0xf3, +0x49, 0x0a, 0xa4, 0xe7, 0x6b, 0x0a, 0xd8, 0xe3, 0x49, 0xbe, 0xcf, 0xa8, 0xc1, +0x46, 0x44, 0xe2, 0x48, 0x86, 0xc9, 0x0a, 0xa0, 0xe7, 0x46, 0x48, 0x0a, 0xe0, +0xc1, 0x46, 0x40, 0xe2, 0xcb, 0x0a, 0xe0, 0x63, 0x43, 0x80, 0x63, 0x48, 0x28, +0xe1, 0x38, 0x86, 0x36, 0x46, 0x0a, 0xe0, 0x33, 0x80, 0xc3, 0x46, 0x30, 0xe1, +0x18, 0x86, 0xcb, 0x0a, 0xd4, 0xe3, 0x2b, 0x0a, 0xdc, 0x63, 0x16, 0x42, 0x0a, +0xe0, 0x5b, 0x0a, 0xe4, 0xe3, 0xc3, 0x48, 0x24, 0x61, 0x03, 0x82, 0x23, 0x46, +0x2c, 0xe1, 0x53, 0x40, 0x34, 0xe1, 0x4c, 0x0c, 0x00, 0xf0, 0x93, 0xdd, 0xc3, +0xc1, 0x0c, 0x00, 0x80, 0xfa, 0x86, 0x43, 0xcc, 0x60, 0x0b, 0xa3, 0x29, 0x02, +0xc4, 0xee, 0xb5, 0x84, 0x4a, 0x04, 0xfc, 0xfb, 0x45, 0x84, 0xc6, 0x40, 0x84, +0xe0, 0x14, 0x82, 0x18, 0x84, 0x19, 0x86, 0x1b, 0xa5, 0x85, 0x8c, 0x04, 0x80, +0x09, 0x00, 0x20, 0xe1, 0x4a, 0x40, 0x80, 0xe0, 0xc5, 0x8a, 0x0c, 0x08, 0x80, +0xf2, 0x08, 0x34, 0x44, 0xff, 0x0b, 0xa5, 0x15, 0x8a, 0x0a, 0x04, 0x04, 0xf0, +0xb5, 0x88, 0xc6, 0x40, 0x84, 0xe0, 0x24, 0x82, 0x28, 0x88, 0x29, 0x86, 0x2b, +0xa5, 0xc5, 0x86, 0x04, 0x80, 0x09, 0x00, 0x20, 0xe1, 0x4a, 0x40, 0x80, 0xe0, +0x05, 0x86, 0x09, 0x02, 0x24, 0xee, 0x0b, 0xa3, 0xd5, 0x80, 0x08, 0x84, 0x0a, +0x40, 0x80, 0xe0, 0x75, 0xf8, 0xf5, 0x81, 0x38, 0x72, 0x44, 0xef, 0xf8, 0x51, +0xfc, 0xef, 0x4e, 0x40, 0x01, 0x60, 0x10, 0xc0, 0x08, 0x80, 0x0b, 0xa1, 0xa5, +0x80, 0x0c, 0x08, 0x80, 0xf2, 0xb8, 0x11, 0xfc, 0xff, 0x0c, 0x08, 0x80, 0xf2, +0x93, 0xdd, 0xcc, 0x04, 0x0c, 0xfa, 0x06, 0x4f, 0x84, 0x60, 0x86, 0x50, 0x4c, +0xe1, 0x04, 0x1c, 0x80, 0x0c, 0x21, 0x6f, 0x01, 0xc0, 0xf0, 0x04, 0x50, 0x64, +0x11, 0x81, 0x09, 0x00, 0x6c, 0xe0, 0x06, 0x29, 0x0b, 0x8c, 0x58, 0x4c, 0x58, +0xef, 0x04, 0x9c, 0x19, 0x00, 0x6c, 0xe0, 0x10, 0x44, 0x3c, 0xf0, 0x2b, 0xa3, +0x35, 0x82, 0x28, 0x2b, 0x10, 0x42, 0xbc, 0xf3, 0x27, 0xc8, 0x29, 0xa0, 0x2f, +0xa2, 0x21, 0x40, 0x6c, 0xe0, 0x86, 0x43, 0xcc, 0xe0, 0x29, 0x02, 0x24, 0xee, +0x2b, 0xa3, 0xd5, 0x80, 0x18, 0x84, 0x4a, 0x42, 0x80, 0x60, 0x11, 0x8b, 0x65, +0x80, 0x19, 0x00, 0x70, 0xe0, 0x16, 0x29, 0xf0, 0x04, 0x50, 0xe4, 0x11, 0x03, +0x0b, 0x8c, 0x28, 0x4a, 0x58, 0xef, 0x04, 0x9c, 0x19, 0x00, 0x70, 0xe0, 0x10, +0x44, 0x3c, 0xf0, 0x2b, 0xa3, 0x35, 0x82, 0x28, 0x2b, 0x10, 0x42, 0xbc, 0xf3, +0x27, 0xc8, 0x29, 0xa0, 0x2f, 0xa2, 0x21, 0x40, 0x70, 0xe0, 0x09, 0x00, 0x74, +0x60, 0xf0, 0x04, 0x50, 0xe4, 0x11, 0x85, 0x06, 0x29, 0x0b, 0x8c, 0xa8, 0x48, +0x58, 0xef, 0x04, 0x9c, 0x19, 0x00, 0x74, 0xe0, 0x10, 0x44, 0x3c, 0xf0, 0x2b, +0xa3, 0x35, 0x82, 0x28, 0x2b, 0x10, 0x42, 0xbc, 0xf3, 0x27, 0xc8, 0x29, 0xa0, +0x2f, 0xa2, 0x21, 0x40, 0x74, 0xe0, 0x09, 0x00, 0x78, 0x60, 0xf0, 0x04, 0x50, +0xe4, 0x11, 0x87, 0x06, 0x29, 0x0b, 0x8c, 0x28, 0x47, 0x58, 0xef, 0x04, 0x9c, +0x19, 0x00, 0x78, 0xe0, 0x10, 0x44, 0x3c, 0xf0, 0x2b, 0xa3, 0x35, 0x82, 0x28, +0x2b, 0x10, 0x42, 0xbc, 0xf3, 0x27, 0xc8, 0x29, 0xa0, 0x2f, 0xa2, 0x21, 0x40, +0x78, 0xe0, 0x19, 0x0e, 0xd0, 0xe0, 0x1b, 0xa1, 0x85, 0x86, 0x05, 0x00, 0x24, +0xe0, 0x1a, 0xa2, 0x04, 0x04, 0xc0, 0xe1, 0x2b, 0x83, 0xd5, 0x80, 0x19, 0x10, +0x10, 0x60, 0x09, 0x9e, 0x0b, 0x83, 0x85, 0x84, 0x08, 0xa0, 0x0b, 0xa1, 0x38, +0x61, 0x74, 0xcf, 0x58, 0x49, 0x34, 0xef, 0x14, 0x9c, 0x25, 0x02, 0x24, 0xe0, +0x24, 0x02, 0xc0, 0x61, 0x29, 0x9e, 0x18, 0x58, 0x74, 0xef, 0x55, 0x81, 0x08, +0xa0, 0x0b, 0xa1, 0x48, 0x60, 0x74, 0xcf, 0x28, 0x72, 0x4c, 0x6f, 0x46, 0x01, +0x08, 0xe0, 0xcc, 0x0c, 0x0c, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x65, 0x5e, 0x04, +0xfa, 0xf0, 0x0c, 0x10, 0xec, 0x60, 0x5e, 0x1c, 0x74, 0x61, 0x88, 0x6e, 0xbc, +0xfc, 0x07, 0x80, 0xfe, 0xf0, 0x1e, 0x20, 0x6c, 0xe6, 0x40, 0x00, 0xe0, 0xc8, +0x70, 0x4c, 0xef, 0x0b, 0xa1, 0xb5, 0x88, 0xc6, 0x0d, 0xdc, 0xe0, 0x09, 0x0c, +0x94, 0xea, 0x0b, 0xa3, 0xf5, 0x80, 0x09, 0x0c, 0x91, 0x79, 0x01, 0xc0, 0x0a, +0x40, 0x80, 0xe0, 0xe5, 0x82, 0xc8, 0x71, 0x44, 0xef, 0x04, 0x18, 0x86, 0x02, +0x84, 0xe0, 0x24, 0x04, 0x31, 0x83, 0x01, 0x82, 0x0e, 0x18, 0xe6, 0x00, 0x48, +0xe1, 0x36, 0x00, 0x21, 0x82, 0x2e, 0x84, 0x46, 0x00, 0x2c, 0x61, 0x21, 0x83, +0x14, 0x00, 0x46, 0x40, 0x34, 0xe1, 0x25, 0x42, 0x44, 0xe0, 0x28, 0x51, 0x28, +0xef, 0x88, 0x40, 0x00, 0xe8, 0xf1, 0x90, 0xfc, 0x0f, 0x80, 0xf6, 0xf4, 0xbc, +0x64, 0xfd, 0x53, 0xc1, 0x4c, 0x00, 0x00, 0xfa, 0x01, 0x03, 0x11, 0x81, 0x48, +0x79, 0x08, 0xef, 0x4c, 0x08, 0x00, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x4c, 0x00, +0x00, 0xfa, 0x0d, 0x62, 0x8e, 0x45, 0xf0, 0xff, 0x75, 0x82, 0x4e, 0x05, 0x01, +0x60, 0x10, 0x40, 0x07, 0xc8, 0x24, 0x88, 0x23, 0x80, 0x1e, 0x8a, 0x78, 0x40, +0x00, 0xe8, 0x21, 0x81, 0x0b, 0x84, 0x4c, 0x08, 0x00, 0xf2, 0x93, 0xdd, 0x83, +0xc1, 0x93, 0xdd, 0x0c, 0x04, 0x00, 0xfa, 0x41, 0xb9, 0x06, 0x48, 0x12, 0xe0, +0x33, 0x88, 0x45, 0x06, 0x14, 0xe0, 0x4b, 0xa3, 0xc5, 0x80, 0x35, 0x06, 0x18, +0xe0, 0x3b, 0x23, 0x31, 0x81, 0x55, 0x80, 0x31, 0x83, 0x0d, 0xe6, 0x04, 0x00, +0x88, 0x00, 0x4e, 0x08, 0x00, 0x00, 0x10, 0x80, 0x05, 0x00, 0x10, 0x9c, 0x08, +0x40, 0x00, 0x98, 0x25, 0x0b, 0x8e, 0x40, 0xf0, 0xff, 0x3b, 0xa1, 0x45, 0x8a, +0x2b, 0x23, 0x06, 0x4c, 0x00, 0xe0, 0x45, 0x90, 0x2b, 0x25, 0x6e, 0x40, 0xf0, +0xff, 0x25, 0x87, 0x3b, 0xa1, 0x25, 0x8a, 0x2b, 0x23, 0x06, 0x0c, 0x08, 0xe0, +0x05, 0x8e, 0x2b, 0x25, 0x5e, 0x40, 0xf0, 0xff, 0x85, 0x0c, 0x06, 0x0c, 0x04, +0xe0, 0x95, 0x87, 0x3b, 0xa1, 0xc5, 0x88, 0x2b, 0x23, 0x61, 0x91, 0x85, 0x8a, +0x2b, 0x25, 0x7e, 0x40, 0xf0, 0xff, 0x25, 0x83, 0x2b, 0xa3, 0xc5, 0x08, 0x06, +0x0c, 0x08, 0x80, 0x2b, 0x25, 0x4e, 0x40, 0xf0, 0xff, 0x15, 0x8a, 0xf5, 0x03, +0x06, 0x0c, 0x04, 0xe0, 0x2b, 0x23, 0x61, 0x91, 0x45, 0x86, 0x2b, 0x25, 0x6e, +0x40, 0xf0, 0xff, 0xc5, 0x04, 0x61, 0x89, 0xc5, 0x83, 0x2b, 0x23, 0x06, 0x4c, +0x00, 0xe0, 0x05, 0x84, 0x2b, 0x25, 0x5e, 0x40, 0xf0, 0xff, 0x85, 0x02, 0x61, +0xa1, 0x25, 0x83, 0x2b, 0x23, 0x61, 0x85, 0xe5, 0x80, 0x2b, 0x25, 0x7e, 0x40, +0xf0, 0xff, 0x65, 0x00, 0x61, 0x83, 0x95, 0x81, 0x0b, 0x02, 0x1b, 0x8c, 0x48, +0x78, 0xfc, 0xef, 0x0b, 0xa1, 0x00, 0x4c, 0x00, 0xdc, 0x0b, 0x8c, 0x0c, 0x0c, +0x00, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x04, 0xfa, 0x6b, 0x00, 0xc6, +0x40, 0xdc, 0xe0, 0x04, 0x00, 0xf0, 0x1e, 0x30, 0xec, 0x48, 0x55, 0xfc, 0x6e, +0x0e, 0x02, 0x24, 0x74, 0x03, 0xc0, 0xb8, 0x68, 0xfc, 0xee, 0x58, 0x71, 0x08, +0xef, 0x78, 0x68, 0xfc, 0x6e, 0x0b, 0x8c, 0x71, 0x01, 0x5b, 0x80, 0x7e, 0x3c, +0x0e, 0x00, 0x05, 0x60, 0x10, 0xc0, 0x11, 0x03, 0x21, 0x81, 0x31, 0x05, 0x41, +0x81, 0xa8, 0x77, 0x08, 0xef, 0x0e, 0x3e, 0x0e, 0x00, 0x05, 0x60, 0x10, 0xc0, +0x48, 0x4b, 0x0c, 0xef, 0x0e, 0x7c, 0x11, 0x81, 0x04, 0x3e, 0x21, 0x83, 0x68, +0x76, 0xfc, 0x6f, 0x0e, 0x06, 0x04, 0x60, 0x10, 0xc0, 0xce, 0x0d, 0x01, 0x60, +0x10, 0xc0, 0x76, 0x98, 0x04, 0xfc, 0xc8, 0x59, 0x0c, 0xef, 0x13, 0xc3, 0x08, +0x98, 0x0b, 0xa1, 0xa5, 0xfe, 0x04, 0xfc, 0x18, 0x5c, 0x0c, 0xef, 0x14, 0x3e, +0x0e, 0x00, 0x05, 0x60, 0x10, 0xc0, 0x38, 0x46, 0x0c, 0xef, 0x01, 0x01, 0xf1, +0x98, 0x0c, 0x0c, 0x04, 0xf2, 0x93, 0xdd, 0x65, 0x5e, 0x04, 0xfa, 0xf0, 0x0c, +0x10, 0xec, 0x60, 0x5e, 0x1c, 0x74, 0x61, 0x88, 0x6e, 0xbc, 0xfc, 0x03, 0x80, +0xfe, 0x26, 0x00, 0x4c, 0x61, 0x11, 0x83, 0x16, 0x00, 0xce, 0x01, 0x01, 0x60, +0x10, 0xc0, 0x16, 0x00, 0x46, 0x00, 0x2c, 0xe1, 0x04, 0x00, 0xf0, 0x1e, 0x30, +0xec, 0x14, 0xc0, 0x14, 0x02, 0x20, 0xfc, 0x1e, 0xc0, 0x98, 0x40, 0x00, 0xe8, +0xf1, 0x98, 0xfc, 0x0b, 0x80, 0xf6, 0xf4, 0xbc, 0x64, 0xfd, 0x53, 0xc1, 0xc3, +0xc1, 0x06, 0x00, 0x01, 0x60, 0x00, 0xe0, 0x06, 0x00, 0x62, 0xf4, 0x93, 0xdd, +0x4c, 0x04, 0x7c, 0xfa, 0xc6, 0x54, 0x84, 0x60, 0x06, 0x0f, 0xdc, 0xe0, 0x04, +0x2a, 0xae, 0x4d, 0x01, 0x60, 0x10, 0xc0, 0x29, 0x14, 0x70, 0x60, 0x8e, 0x51, +0x00, 0x60, 0x10, 0xc0, 0x3b, 0x0e, 0x9c, 0x65, 0x06, 0x52, 0x94, 0xe0, 0x1b, +0x00, 0x20, 0x60, 0x0e, 0x57, 0x08, 0x60, 0x10, 0xc0, 0x0b, 0x00, 0x1c, 0xe0, +0x13, 0x20, 0x01, 0x81, 0x13, 0x84, 0x13, 0x86, 0x11, 0x9e, 0x1c, 0x98, 0x0c, +0xa0, 0x04, 0xa4, 0x0b, 0x00, 0xa0, 0xe5, 0x0c, 0xac, 0x0a, 0x98, 0x1a, 0xac, +0x0d, 0xc3, 0xf5, 0x82, 0x04, 0xa4, 0x0b, 0x00, 0xa0, 0xe5, 0x0c, 0x2c, 0x01, +0x8b, 0x08, 0x56, 0x2c, 0xef, 0x0a, 0xa0, 0x01, 0x82, 0x0c, 0xa0, 0x0a, 0xa0, +0x8a, 0x00, 0x24, 0xe7, 0xf5, 0xfa, 0x05, 0x0e, 0xbc, 0x61, 0x06, 0x04, 0x00, +0x60, 0x00, 0xf0, 0x13, 0x0e, 0xb8, 0x79, 0x06, 0x06, 0x00, 0x60, 0x00, 0xe0, +0x2f, 0x80, 0x2b, 0x07, 0x0e, 0x45, 0x01, 0x60, 0x10, 0xc0, 0x1c, 0x88, 0x95, +0x86, 0x07, 0xe1, 0x09, 0xbe, 0x0b, 0xa1, 0x05, 0x86, 0x21, 0xff, 0x1d, 0x84, +0x85, 0x02, 0x00, 0x00, 0x08, 0x8c, 0x10, 0x04, 0x18, 0xec, 0x8a, 0x04, 0x14, +0xed, 0xb5, 0x80, 0x8a, 0x02, 0x40, 0xe1, 0x75, 0x82, 0x01, 0x84, 0x14, 0x24, +0x09, 0xbe, 0x29, 0x02, 0x00, 0xe3, 0x20, 0x44, 0x80, 0xf3, 0x2f, 0xa0, 0x21, +0x42, 0x00, 0xe3, 0x8e, 0x41, 0x09, 0x60, 0x10, 0x40, 0xc6, 0x0d, 0x30, 0xe1, +0x04, 0x80, 0x0b, 0xa3, 0x75, 0xa0, 0x19, 0x0c, 0xcc, 0xe5, 0x4a, 0x02, 0xfc, +0xfb, 0xc5, 0x9e, 0xa8, 0x54, 0x00, 0x68, 0x01, 0x81, 0x04, 0x24, 0x11, 0x85, +0x11, 0x40, 0x20, 0x60, 0x4e, 0x43, 0x00, 0x60, 0x10, 0xc0, 0x04, 0xa4, 0x29, +0x00, 0x50, 0xeb, 0x2e, 0x84, 0x24, 0x84, 0x29, 0x86, 0x2e, 0x84, 0x24, 0x84, +0x2b, 0xa1, 0x15, 0xfe, 0xa8, 0x52, 0x44, 0xef, 0x04, 0xaa, 0x08, 0x80, 0x0b, +0xa5, 0xe5, 0x80, 0x04, 0xa8, 0x09, 0x00, 0x20, 0xe1, 0x0b, 0xa1, 0x48, 0x4a, +0x44, 0x8f, 0x46, 0x10, 0x74, 0xe1, 0x04, 0xa0, 0x14, 0xc0, 0x14, 0x02, 0x2c, +0xfc, 0x1e, 0xc0, 0x14, 0xc0, 0x14, 0x02, 0x08, 0xfc, 0x1e, 0x40, 0x11, 0x83, +0x04, 0xa4, 0x11, 0x40, 0xb0, 0x67, 0x11, 0x81, 0x04, 0xa6, 0x11, 0x40, 0x10, +0xe0, 0xb8, 0x73, 0x74, 0xef, 0x84, 0x20, 0x70, 0x06, 0x00, 0xe7, 0x8e, 0x17, +0x01, 0x60, 0x10, 0x40, 0xce, 0x54, 0x01, 0x60, 0x10, 0xc0, 0x04, 0xe0, 0x04, +0x00, 0x04, 0xfc, 0x0e, 0xe0, 0x04, 0xe0, 0x04, 0x00, 0x0c, 0xfc, 0x0e, 0xe0, +0x04, 0xe0, 0x04, 0x00, 0x10, 0xfc, 0x0e, 0xe0, 0x45, 0x10, 0x2c, 0xe0, 0x44, +0x00, 0x00, 0xfc, 0x05, 0x50, 0x2c, 0x60, 0x4e, 0x41, 0x08, 0x60, 0x10, 0xc0, +0x1b, 0x0e, 0xa0, 0xe1, 0x04, 0x80, 0x2b, 0x0e, 0x9c, 0xe1, 0x1b, 0xc2, 0x03, +0x02, 0xce, 0x42, 0x01, 0x60, 0x10, 0xc0, 0x3e, 0x04, 0x03, 0x84, 0x00, 0x00, +0xf4, 0xec, 0x0e, 0xac, 0x09, 0x0e, 0x00, 0xe3, 0x4e, 0x8f, 0x0b, 0xa5, 0x0b, +0x0e, 0x10, 0xd8, 0x95, 0x96, 0x0b, 0x0e, 0x84, 0xe1, 0x15, 0x0e, 0x10, 0xe0, +0x03, 0x4e, 0x10, 0xf8, 0x1d, 0xc1, 0x13, 0x4e, 0x10, 0x58, 0x10, 0x40, 0x00, +0xdc, 0x1a, 0x5e, 0x04, 0x04, 0x80, 0xfb, 0x2d, 0xc3, 0x13, 0x4e, 0x10, 0x58, +0x10, 0x40, 0x00, 0xdc, 0x85, 0x89, 0x0b, 0xa1, 0x98, 0x0c, 0x00, 0xc0, 0x68, +0x7b, 0x68, 0x6f, 0x01, 0x85, 0x60, 0x00, 0x10, 0x64, 0x11, 0x81, 0xc8, 0x71, +0x54, 0xef, 0x28, 0x45, 0x70, 0xef, 0x09, 0x0e, 0xe8, 0xea, 0x4a, 0x40, 0x90, +0xe0, 0x06, 0x00, 0x00, 0xc0, 0xa8, 0x4b, 0x00, 0xc8, 0x09, 0x0e, 0x5c, 0xeb, +0x0a, 0x40, 0x80, 0xe0, 0x25, 0xac, 0x19, 0x0c, 0xcc, 0xe5, 0x4a, 0x02, 0xfc, +0xfb, 0x85, 0xaa, 0x0a, 0x40, 0x90, 0xe0, 0x45, 0x82, 0x46, 0x00, 0x74, 0xe1, +0x04, 0x80, 0x14, 0xc0, 0x14, 0x02, 0x2c, 0xfc, 0x1e, 0xc0, 0x09, 0x0e, 0x5c, +0xeb, 0x0a, 0x40, 0x84, 0xe0, 0x25, 0x82, 0xb8, 0x68, 0x30, 0x6f, 0x01, 0x81, +0xc8, 0x58, 0x2c, 0x6f, 0x0b, 0x8c, 0x09, 0x0e, 0x5c, 0xeb, 0x0a, 0x40, 0x94, +0xe0, 0x45, 0x82, 0x46, 0x00, 0x74, 0xe1, 0x04, 0x80, 0x14, 0xc0, 0x14, 0x02, +0x08, 0xfc, 0x1e, 0xc0, 0x09, 0x0e, 0x5c, 0xeb, 0x0a, 0x40, 0x98, 0xe0, 0x18, +0x49, 0x00, 0xc8, 0x45, 0x91, 0x14, 0x2c, 0x04, 0x00, 0x80, 0xfb, 0x13, 0x20, +0x06, 0x41, 0x2c, 0xe0, 0x16, 0x40, 0x02, 0x60, 0x46, 0x42, 0x04, 0xe0, 0xc8, +0x7a, 0xf8, 0xee, 0x0e, 0x2c, 0x0e, 0x4c, 0x01, 0x60, 0x10, 0xc0, 0x04, 0xac, +0x00, 0x00, 0xd1, 0x6f, 0x0f, 0xc0, 0x0e, 0xac, 0x05, 0x10, 0x40, 0xe0, 0x04, +0x00, 0x38, 0xfc, 0x0e, 0x98, 0x05, 0x50, 0x40, 0xe0, 0x04, 0xac, 0xd8, 0x63, +0xfc, 0xef, 0x46, 0x10, 0x74, 0xe1, 0x04, 0xa0, 0x15, 0x00, 0x40, 0xe0, 0x14, +0x02, 0xb8, 0xfc, 0x1e, 0x98, 0x15, 0x40, 0x40, 0xe0, 0x98, 0x52, 0x30, 0xef, +0x0b, 0xa1, 0xa5, 0xfe, 0x34, 0x28, 0xc6, 0x0d, 0x30, 0xe1, 0x08, 0x8c, 0x0b, +0xa5, 0x0b, 0x0e, 0x04, 0xd8, 0x1b, 0x0e, 0x00, 0xc0, 0x2b, 0x0e, 0x04, 0xc0, +0x3b, 0x0e, 0x08, 0xc0, 0x75, 0x84, 0x08, 0x8e, 0x19, 0x0e, 0x10, 0xeb, 0x12, +0x40, 0x80, 0xfb, 0x01, 0x4e, 0x10, 0xf8, 0x0a, 0xcc, 0x03, 0x4e, 0x04, 0xf8, +0x1a, 0xce, 0x1c, 0x9c, 0x2b, 0x06, 0x10, 0xe0, 0x2c, 0x9e, 0x3b, 0x06, 0x14, +0xe0, 0x3c, 0xdc, 0xa4, 0x8f, 0x03, 0x4e, 0x80, 0xf9, 0x13, 0x4e, 0x7c, 0xf9, +0x23, 0x4e, 0x78, 0xf9, 0x33, 0x4e, 0x74, 0xf9, 0xc8, 0x6d, 0x38, 0xef, 0xe8, +0x66, 0x3c, 0xef, 0x04, 0x20, 0xa9, 0xc2, 0xa5, 0x40, 0x2c, 0xe0, 0x19, 0x0c, +0x40, 0xe6, 0x1b, 0xa1, 0x55, 0x88, 0x14, 0xc0, 0x19, 0xd0, 0x1e, 0xc0, 0x14, +0xc0, 0x19, 0xe0, 0x1e, 0xc0, 0x14, 0xc0, 0x19, 0xc4, 0x1e, 0x40, 0x01, 0x83, +0xa8, 0x41, 0x00, 0xe8, 0x04, 0xa0, 0x14, 0xc0, 0x14, 0x02, 0xac, 0xfc, 0x1e, +0xc0, 0x14, 0xc0, 0x19, 0xc8, 0x1e, 0xc0, 0x48, 0x51, 0x74, 0xef, 0x04, 0x26, +0x11, 0x83, 0x11, 0x40, 0x10, 0xe0, 0x04, 0xa4, 0x11, 0x40, 0x20, 0x60, 0x11, +0x81, 0x04, 0xa4, 0x11, 0x40, 0xb0, 0xe7, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, +0x1b, 0x00, 0x06, 0x01, 0x38, 0xe1, 0x04, 0x80, 0xc8, 0x20, 0x04, 0x60, 0x01, +0xb8, 0xc3, 0xc1, 0x83, 0xc1, 0x13, 0xc3, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, +0x00, 0xfa, 0xe6, 0x41, 0x84, 0x60, 0x8e, 0x42, 0x00, 0x60, 0x10, 0xc0, 0x08, +0x80, 0x14, 0x84, 0x0b, 0x21, 0xa6, 0x41, 0x04, 0xe1, 0x1c, 0x80, 0x05, 0x82, +0x28, 0x47, 0x64, 0xef, 0x88, 0x7b, 0x28, 0x6f, 0x46, 0x40, 0x04, 0xe0, 0x35, +0x81, 0x38, 0x56, 0x40, 0xef, 0xd8, 0x67, 0x64, 0xef, 0x38, 0x63, 0x64, 0xef, +0xd8, 0x44, 0x44, 0xef, 0x06, 0x40, 0x94, 0xe0, 0x04, 0x80, 0x09, 0x00, 0x50, +0xeb, 0x0a, 0x40, 0x88, 0xe0, 0x76, 0x00, 0xa0, 0xc0, 0x68, 0x4e, 0x50, 0xcf, +0xc6, 0x0d, 0x30, 0xe1, 0x09, 0x0c, 0x30, 0xe7, 0x0b, 0xa1, 0x78, 0x6c, 0x70, +0xcf, 0x98, 0x7f, 0x6c, 0xef, 0x38, 0x4b, 0x2c, 0x6f, 0x0b, 0x8c, 0xc8, 0x5a, +0x30, 0x6f, 0x01, 0x81, 0x58, 0x49, 0x30, 0xef, 0xb8, 0x5f, 0x70, 0xef, 0x01, +0x83, 0x01, 0x4c, 0xc0, 0xe5, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, +0x0c, 0x04, 0x84, 0xfa, 0x06, 0x4e, 0x88, 0x60, 0x06, 0x0c, 0x0c, 0xe1, 0x05, +0x0e, 0x38, 0x78, 0x06, 0x04, 0x00, 0x70, 0x40, 0xc0, 0x19, 0x0c, 0x94, 0x78, +0x06, 0x06, 0x10, 0x60, 0x40, 0xc0, 0x08, 0xc0, 0x4a, 0x02, 0x00, 0xe0, 0x09, +0x86, 0x07, 0xc6, 0x2f, 0x61, 0x3f, 0xe1, 0x21, 0x4e, 0x10, 0xf8, 0x0c, 0x00, +0x00, 0xe2, 0x31, 0x4e, 0x0c, 0xf8, 0x18, 0x62, 0x30, 0xef, 0x05, 0x0e, 0x38, +0xf8, 0x14, 0x98, 0x09, 0x00, 0x24, 0xe0, 0x4a, 0x40, 0x88, 0xe0, 0x65, 0x82, +0x0b, 0x0c, 0x28, 0x78, 0x1b, 0xa1, 0x0c, 0xde, 0x05, 0x84, 0x0a, 0x01, 0x20, +0xec, 0xf5, 0x84, 0x55, 0x03, 0x01, 0x93, 0x0b, 0x0c, 0x24, 0x78, 0x1b, 0xa1, +0x0c, 0xde, 0x65, 0x82, 0x0a, 0x01, 0x24, 0xec, 0xb5, 0x82, 0xb5, 0x01, 0x01, +0x95, 0x0a, 0x01, 0x48, 0xec, 0x15, 0x82, 0x65, 0x01, 0x01, 0xa7, 0x0a, 0x01, +0x50, 0xec, 0x75, 0x80, 0x01, 0xab, 0x0c, 0xde, 0x15, 0x0c, 0x20, 0x79, 0x06, +0x04, 0x00, 0x60, 0x00, 0xf0, 0x49, 0x0c, 0x64, 0x78, 0x06, 0x06, 0x00, 0x60, +0x00, 0xe0, 0x2f, 0x82, 0x3b, 0x0c, 0x8c, 0x78, 0x2b, 0x87, 0x30, 0x42, 0x00, +0x5c, 0x4a, 0x08, 0x14, 0xfc, 0x14, 0x04, 0x80, 0x7a, 0xce, 0x43, 0x00, 0x60, +0x10, 0xc0, 0x2c, 0x04, 0x8e, 0x48, 0x01, 0x60, 0x10, 0xc0, 0x3e, 0x90, 0x85, +0x88, 0x45, 0x0e, 0x30, 0xf8, 0x3b, 0x08, 0x18, 0xe0, 0x5b, 0x08, 0x20, 0xe0, +0x4b, 0x08, 0x1c, 0xe0, 0x32, 0x4a, 0x94, 0xf1, 0x53, 0x88, 0x8a, 0x0a, 0x89, +0x63, 0x05, 0xc0, 0xb5, 0x84, 0x54, 0x1a, 0x8e, 0x59, 0x09, 0x60, 0x10, 0xc0, +0x71, 0x83, 0x7e, 0x30, 0x2e, 0x59, 0x09, 0x60, 0x10, 0xc0, 0x5c, 0x30, 0x54, +0x08, 0x80, 0xfb, 0x4d, 0x44, 0x4e, 0x45, 0x09, 0x60, 0x10, 0xc0, 0x3e, 0x88, +0x43, 0x42, 0x00, 0xc0, 0x65, 0x81, 0x8e, 0x43, 0x09, 0x60, 0x10, 0x40, 0x21, +0x81, 0x2e, 0x84, 0x03, 0x4c, 0xd5, 0x79, 0x02, 0x40, 0xd6, 0x41, 0x00, 0xe0, +0x0c, 0x0c, 0x84, 0xf2, 0x18, 0x34, 0x48, 0xff, 0xc3, 0xc1, 0x0c, 0x04, 0xfc, +0xfa, 0x6b, 0x00, 0x06, 0x41, 0x08, 0xe0, 0x98, 0x73, 0x48, 0xef, 0x0b, 0xa1, +0x78, 0x14, 0x00, 0xc0, 0x86, 0x52, 0x84, 0xe0, 0x74, 0xe4, 0x08, 0x9c, 0x09, +0x86, 0x0b, 0xa3, 0x36, 0x40, 0x98, 0x40, 0x06, 0x02, 0x00, 0xc0, 0x11, 0x40, +0x00, 0xc0, 0xf5, 0x80, 0x21, 0x01, 0x36, 0x40, 0x98, 0xe0, 0x2e, 0x18, 0x11, +0x83, 0x16, 0x80, 0x84, 0x26, 0x16, 0x44, 0x98, 0xe0, 0x18, 0x9c, 0x09, 0x10, +0xb4, 0xe0, 0x0a, 0x02, 0x0c, 0xfc, 0x0c, 0x02, 0x00, 0xe2, 0x01, 0x4c, 0x10, +0xe1, 0x0b, 0x10, 0x80, 0xe0, 0x16, 0x88, 0xe8, 0x75, 0xf8, 0xee, 0x05, 0x4c, +0x48, 0xe0, 0x08, 0x9c, 0x00, 0x42, 0x08, 0xfc, 0x19, 0x86, 0x1b, 0xa5, 0xd5, +0x80, 0x4a, 0x00, 0x0c, 0xf8, 0x0c, 0x00, 0x00, 0xe2, 0x0e, 0x98, 0x09, 0x10, +0xb0, 0xe0, 0x98, 0x51, 0x00, 0x68, 0x09, 0x82, 0x01, 0x4c, 0x18, 0x60, 0x11, +0x9f, 0x0b, 0x10, 0x88, 0xe0, 0x78, 0x51, 0x00, 0xe8, 0x14, 0xa6, 0x05, 0x4c, +0x4c, 0xe0, 0x0b, 0x02, 0x84, 0x60, 0x11, 0x91, 0xf8, 0x50, 0x00, 0xe8, 0xb4, +0xa6, 0x05, 0x4c, 0x50, 0xe0, 0x09, 0x16, 0xd0, 0xe0, 0x0a, 0x40, 0x84, 0x60, +0x06, 0x00, 0x00, 0x60, 0x48, 0xd0, 0x56, 0x01, 0x55, 0x15, 0x41, 0xd0, 0x05, +0x4c, 0x34, 0xe0, 0x09, 0x16, 0xd0, 0xe0, 0x68, 0x4f, 0x00, 0x68, 0x09, 0x82, +0x14, 0x64, 0x06, 0x54, 0xdc, 0xe0, 0x01, 0x4c, 0x10, 0x60, 0xce, 0x41, 0x00, +0x60, 0x10, 0xc0, 0x0a, 0x80, 0x2b, 0x02, 0x18, 0xe0, 0x23, 0xa0, 0x23, 0x4c, +0x60, 0xe0, 0x09, 0x14, 0x70, 0xea, 0x22, 0x40, 0x80, 0xf1, 0x03, 0x4c, 0x60, +0xe0, 0x25, 0x14, 0xe0, 0xe2, 0x2b, 0xa3, 0x35, 0x88, 0x24, 0xa4, 0x3b, 0x02, +0x18, 0xe0, 0x4b, 0x02, 0x20, 0xe0, 0x1b, 0x02, 0x1c, 0xe0, 0x59, 0x04, 0x24, +0xe0, 0x33, 0xa8, 0x32, 0x44, 0x84, 0x70, 0x2e, 0x47, 0x08, 0x60, 0x10, 0xc0, +0x20, 0x02, 0xfc, 0x6c, 0x0a, 0x4a, 0x88, 0xe0, 0x1c, 0x0c, 0x20, 0x02, 0x14, +0xcc, 0x10, 0x02, 0xf0, 0xcc, 0x13, 0x46, 0x00, 0x40, 0x0a, 0x41, 0x04, 0xec, +0x13, 0x4c, 0x60, 0x40, 0x10, 0x40, 0x00, 0xdc, 0x15, 0x14, 0xdc, 0x62, 0x04, +0x00, 0x80, 0xfb, 0x86, 0x05, 0x74, 0xe1, 0x1d, 0xc1, 0x13, 0x4c, 0x60, 0x40, +0x14, 0x00, 0x80, 0xdb, 0x14, 0xa4, 0x0e, 0xa8, 0x04, 0x88, 0x7a, 0x84, 0x73, +0x4c, 0x64, 0xe0, 0xe8, 0x6d, 0xf8, 0xee, 0x1b, 0x00, 0x8e, 0x40, 0xb1, 0x66, +0x9b, 0xd3, 0x08, 0x6d, 0xf8, 0xee, 0x8b, 0x00, 0x0b, 0x8e, 0x85, 0x4c, 0x3c, +0xe0, 0x28, 0x6d, 0xf8, 0xee, 0x1b, 0x00, 0x0b, 0x90, 0x28, 0x6d, 0xf8, 0xee, +0x48, 0x6c, 0xf8, 0x6e, 0x0e, 0x02, 0x00, 0x60, 0x1e, 0xd1, 0x05, 0x4c, 0x40, +0x60, 0x11, 0x91, 0x0b, 0x16, 0x64, 0xe0, 0x7b, 0x16, 0x5c, 0xe0, 0x89, 0x16, +0xc0, 0xe0, 0x08, 0x49, 0x00, 0xe8, 0x3b, 0x00, 0x80, 0x44, 0x7c, 0xf0, 0x81, +0x01, 0x11, 0x81, 0xb8, 0x40, 0x40, 0x6f, 0x0b, 0x8e, 0x4b, 0x0c, 0x60, 0x60, +0x06, 0x0e, 0x00, 0x60, 0xe0, 0xcf, 0x0e, 0x5a, 0x11, 0x81, 0x21, 0x01, 0x06, +0x06, 0x01, 0x60, 0xe0, 0xcf, 0xd8, 0x7f, 0x3c, 0x6f, 0x0b, 0x88, 0x14, 0x64, +0x86, 0x57, 0x94, 0xe1, 0x05, 0x4c, 0x2c, 0xe0, 0x0a, 0x86, 0x83, 0x4c, 0xe4, +0xe0, 0x03, 0x4c, 0xe0, 0xe0, 0x08, 0x86, 0x09, 0xbe, 0x03, 0x4c, 0xe8, 0xe0, +0x08, 0x86, 0x09, 0xbe, 0x07, 0xc4, 0x05, 0x00, 0x2c, 0xfc, 0x05, 0x4c, 0x54, +0xe0, 0x0a, 0xc4, 0x03, 0x4c, 0xec, 0xe0, 0x0a, 0x44, 0x11, 0x91, 0xe8, 0x45, +0x00, 0xe8, 0x14, 0xe4, 0x05, 0x4c, 0x58, 0xe0, 0x0a, 0x46, 0x11, 0x91, 0x78, +0x45, 0x00, 0xe8, 0x14, 0xe4, 0x05, 0x4c, 0x5c, 0xe0, 0x0b, 0x02, 0x10, 0x60, +0x11, 0x91, 0xf8, 0x44, 0x00, 0xe8, 0x14, 0xe4, 0x05, 0x4c, 0x60, 0xe0, 0x0b, +0x02, 0x14, 0x60, 0x11, 0x91, 0x78, 0x44, 0x00, 0xe8, 0x14, 0xa6, 0x05, 0x4c, +0x64, 0xe0, 0x85, 0x4c, 0x14, 0xe0, 0x85, 0x4c, 0x18, 0xe0, 0x0b, 0x02, 0x7c, +0x60, 0x11, 0x91, 0xb8, 0x43, 0x00, 0xe8, 0x05, 0x4c, 0x10, 0x60, 0x11, 0x91, +0x81, 0x4c, 0x14, 0xe0, 0x75, 0x4c, 0x1c, 0xe0, 0x09, 0x14, 0xd0, 0xea, 0x07, +0xc4, 0x05, 0x00, 0x2c, 0xfc, 0x05, 0x4c, 0x20, 0xe0, 0x0b, 0x14, 0x94, 0xe5, +0x98, 0x42, 0x00, 0xe8, 0x05, 0x4c, 0x38, 0x60, 0x11, 0x91, 0x0b, 0x14, 0x64, +0xe5, 0x28, 0x42, 0x00, 0xe8, 0x05, 0x4c, 0x28, 0x60, 0x11, 0x81, 0x09, 0x14, +0x8c, 0x6a, 0x21, 0x81, 0x0b, 0x12, 0x7c, 0x60, 0x0a, 0x40, 0x88, 0xe0, 0xd6, +0x0e, 0xcd, 0x0c, 0x73, 0xcf, 0x75, 0x4c, 0x24, 0x60, 0x3b, 0x8e, 0x28, 0x79, +0x3c, 0xef, 0x0e, 0xd8, 0x16, 0x41, 0x08, 0xe0, 0x0c, 0x0c, 0xfc, 0xf2, 0xb8, +0x1e, 0x48, 0xff, 0xc3, 0xc1, 0x4a, 0x00, 0x00, 0xe0, 0x0c, 0x00, 0x00, 0xe2, +0x93, 0xdd, 0xc3, 0xc1, 0x4c, 0x00, 0x00, 0xfa, 0x88, 0x61, 0x28, 0xef, 0x4c, +0x08, 0x00, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x88, 0x5d, +0x48, 0x6f, 0xf6, 0x40, 0x04, 0xe0, 0x0b, 0xa1, 0x85, 0x80, 0x0c, 0x0c, 0x00, +0xf2, 0x93, 0xdd, 0xb8, 0x6b, 0x54, 0xef, 0xc6, 0x4c, 0x84, 0xe0, 0x04, 0x98, +0x09, 0x00, 0x20, 0xe1, 0x4a, 0x40, 0x80, 0xe0, 0x85, 0x88, 0xc6, 0x40, 0xb4, +0x60, 0x46, 0x45, 0x00, 0xe0, 0x19, 0x00, 0xb4, 0xe4, 0x16, 0x42, 0x0a, 0xe0, +0x03, 0x82, 0x09, 0x00, 0x40, 0xe2, 0x0b, 0xa5, 0xf5, 0x84, 0x48, 0x56, 0x40, +0x6f, 0x01, 0x85, 0x58, 0x4c, 0x40, 0x6f, 0x06, 0x00, 0x80, 0xe1, 0xd8, 0x7d, +0x44, 0x6f, 0x06, 0x00, 0x80, 0xe1, 0x58, 0x50, 0x40, 0xef, 0x18, 0x70, 0xf4, +0xef, 0x4e, 0x40, 0x01, 0x60, 0x10, 0xc0, 0x08, 0x80, 0x0b, 0xa1, 0x08, 0x70, +0xf4, 0xcf, 0x09, 0x0c, 0x48, 0xe0, 0x0b, 0xa1, 0x95, 0x84, 0xe8, 0x70, 0x38, +0xef, 0x06, 0x40, 0x94, 0x60, 0x16, 0x07, 0x04, 0xe1, 0x04, 0x80, 0x1b, 0x00, +0x18, 0xe1, 0x2b, 0x00, 0x50, 0xe1, 0x48, 0x8c, 0x39, 0x06, 0xac, 0xe0, 0x13, +0xa4, 0x13, 0xa8, 0x13, 0xa6, 0x15, 0x40, 0x2c, 0xe0, 0xc6, 0x00, 0x2c, 0x61, +0x11, 0xa1, 0x04, 0x80, 0x13, 0x40, 0xd0, 0x65, 0xe6, 0x40, 0xe0, 0xe0, 0x0a, +0x00, 0x06, 0x42, 0x94, 0xe0, 0x14, 0x84, 0x03, 0x42, 0x10, 0x62, 0x06, 0x41, +0x04, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x98, 0x17, 0x48, 0xff, 0xc3, 0xc1, 0x66, +0x01, 0xcc, 0xe0, 0x18, 0x80, 0x0a, 0x42, 0x84, 0xe0, 0x10, 0x42, 0xf8, 0xd3, +0x11, 0x40, 0x00, 0xc0, 0x93, 0xdd, 0x4a, 0x40, 0x80, 0xe0, 0x55, 0x84, 0x14, +0x04, 0xa0, 0x60, 0x0e, 0x06, 0x00, 0x70, 0x3f, 0xc0, 0x2f, 0x06, 0x14, 0x06, +0xa0, 0xe1, 0x14, 0x00, 0xe0, 0x61, 0x30, 0x46, 0x00, 0x70, 0xff, 0xc0, 0x17, +0x70, 0x3f, 0xa0, 0x1f, 0xa4, 0x1f, 0xa6, 0x93, 0x5d, 0x0b, 0x82, 0x4c, 0x04, +0x04, 0xfa, 0x86, 0x0d, 0xcc, 0x60, 0x71, 0x81, 0x7c, 0x98, 0x88, 0x55, 0x68, +0xef, 0x0a, 0x98, 0x00, 0x42, 0xf1, 0x73, 0xff, 0xc0, 0x19, 0xc2, 0x1c, 0x18, +0x60, 0x02, 0xe1, 0x65, 0x02, 0xc0, 0x65, 0x81, 0x24, 0x84, 0x2b, 0xa1, 0xf5, +0x80, 0x71, 0x02, 0x11, 0x88, 0x0a, 0x0e, 0x00, 0xe1, 0x35, 0xfe, 0x75, 0x81, +0x77, 0x50, 0x00, 0x40, 0xf0, 0xf3, 0x0f, 0xae, 0x09, 0xc4, 0x0c, 0x98, 0x4c, +0x0c, 0x04, 0xf2, 0x93, 0xdd, 0x4c, 0x04, 0x7c, 0xfa, 0xbb, 0x06, 0x6b, 0x82, +0x14, 0x2c, 0x9b, 0x80, 0x74, 0x24, 0xab, 0x84, 0xa4, 0x00, 0xa0, 0x61, 0x8b, +0x88, 0x1b, 0x81, 0x65, 0x82, 0x0e, 0x2c, 0x0b, 0x8e, 0x78, 0x56, 0x30, 0x6f, +0x1b, 0x8c, 0x24, 0x2c, 0x0b, 0x8e, 0x28, 0x59, 0x30, 0x6f, 0x1b, 0x8c, 0x04, +0x18, 0xb1, 0x83, 0x11, 0x01, 0x21, 0x83, 0x38, 0x44, 0x00, 0x68, 0x31, 0x85, +0xa4, 0x02, 0x80, 0x7a, 0x21, 0x8b, 0xd8, 0x43, 0x00, 0x68, 0x06, 0x46, 0xfc, +0xe1, 0xab, 0x00, 0x0b, 0x8e, 0x78, 0x40, 0xf8, 0x6f, 0x1b, 0x94, 0x09, 0x12, +0x10, 0x60, 0x1b, 0x90, 0x98, 0x79, 0xfc, 0x6f, 0x70, 0x12, 0x10, 0xe4, 0x1b, +0x00, 0x0b, 0x92, 0xb8, 0x7f, 0xf4, 0xef, 0x11, 0x03, 0x21, 0x85, 0x31, 0x09, +0x0b, 0x94, 0x58, 0x42, 0x00, 0xe8, 0x8b, 0x00, 0x0b, 0x8e, 0x18, 0x7f, 0xf4, +0x6f, 0x1b, 0x90, 0x70, 0x12, 0x50, 0x64, 0x36, 0x54, 0x48, 0xe1, 0x08, 0x42, +0x00, 0x68, 0x0b, 0x92, 0x0a, 0x00, 0x00, 0xfb, 0xb1, 0x54, 0x00, 0x40, 0x0a, +0x40, 0xa0, 0xe0, 0xe5, 0xfc, 0x11, 0x01, 0x21, 0x85, 0x31, 0x09, 0x0b, 0x90, +0xc8, 0x40, 0x00, 0xe8, 0x8b, 0x00, 0x0b, 0x8e, 0x88, 0x7d, 0xf4, 0x6f, 0x1b, +0x90, 0x8e, 0x98, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x1f, 0x25, +0x32, 0x40, 0x80, 0xf8, 0x1f, 0x86, 0x93, 0x5d, 0x0f, 0xa2, 0x04, 0x80, 0x93, +0xdd, 0x0c, 0x00, 0x80, 0xfa, 0xa8, 0x4c, 0x48, 0x6f, 0xe6, 0x40, 0x08, 0xe0, +0x0b, 0xa1, 0x95, 0x84, 0xc8, 0x46, 0x40, 0xef, 0x86, 0x02, 0x84, 0x60, 0xc6, +0x04, 0x84, 0xe0, 0x14, 0x04, 0x5a, 0x00, 0x08, 0xe0, 0x24, 0x88, 0x4a, 0x42, +0x08, 0x60, 0x46, 0x02, 0x84, 0xe0, 0x0a, 0x42, 0x80, 0xe1, 0x0c, 0x00, 0x00, +0xe2, 0x06, 0x84, 0xf6, 0x40, 0x08, 0xe0, 0x0c, 0x08, 0x80, 0xf2, 0xd8, 0x0a, +0x48, 0xff, 0xc3, 0xc1, 0x0c, 0x04, 0x0c, 0xfa, 0xf0, 0x1e, 0x60, 0x6c, 0xa6, +0x01, 0x04, 0xe0, 0x48, 0x4a, 0x48, 0xef, 0x0b, 0xa1, 0x15, 0x90, 0xc8, 0x5d, +0x2c, 0xef, 0xe8, 0x7a, 0xf4, 0x6e, 0x0e, 0x02, 0x24, 0x74, 0x03, 0xc0, 0x86, +0x50, 0x84, 0xe0, 0x69, 0x10, 0x44, 0xe0, 0x58, 0x7a, 0xf4, 0x6e, 0x1b, 0x8c, +0x04, 0x60, 0x7b, 0x80, 0x1b, 0x00, 0x28, 0xe0, 0x0b, 0x00, 0x24, 0xe0, 0x13, +0xa0, 0x10, 0x00, 0x04, 0x64, 0x1b, 0x8c, 0x88, 0x79, 0xf4, 0xee, 0x14, 0xa0, +0x19, 0x02, 0x28, 0xe0, 0x19, 0xbe, 0x06, 0x44, 0x06, 0x60, 0x06, 0x41, 0xdc, +0xe0, 0x04, 0x00, 0x16, 0x42, 0x1e, 0xe0, 0x21, 0xbe, 0x1e, 0x7c, 0x27, 0xcb, +0x2c, 0x7c, 0x0e, 0x02, 0x25, 0x74, 0x03, 0xc0, 0x48, 0x78, 0xf4, 0xee, 0x0e, +0x7e, 0x11, 0x81, 0x1e, 0x09, 0xf0, 0x00, 0x10, 0xe4, 0x1c, 0x95, 0x88, 0x7e, +0x1c, 0xef, 0x0a, 0x95, 0x18, 0x41, 0x00, 0x68, 0x01, 0xa0, 0x06, 0x00, 0x38, +0x61, 0x11, 0x83, 0x78, 0x7c, 0x1c, 0xef, 0x18, 0x46, 0x48, 0x6f, 0xb6, 0x01, +0x04, 0xe0, 0xf1, 0xb0, 0x0c, 0x0c, 0x0c, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x1b, +0x00, 0x06, 0x00, 0x38, 0xe1, 0x04, 0x80, 0x08, 0x35, 0xf4, 0x7f, 0x01, 0xc8, +0xc3, 0xc1, 0x0c, 0x04, 0x3c, 0xfa, 0x86, 0x4e, 0x84, 0x60, 0xf0, 0x1e, 0x20, +0xee, 0x84, 0x9c, 0x19, 0x0e, 0x48, 0xe0, 0x0a, 0xe2, 0x2a, 0xe0, 0x03, 0xa4, +0x01, 0x82, 0x38, 0x75, 0xf4, 0x6e, 0x04, 0x00, 0x80, 0xfb, 0x0b, 0x10, 0x64, +0x60, 0x6b, 0x80, 0x0b, 0xa1, 0x05, 0x0e, 0x04, 0xc0, 0x99, 0x00, 0xa0, 0xc0, +0xd5, 0x84, 0x09, 0x10, 0x24, 0x60, 0x06, 0x03, 0xd0, 0xe0, 0x1a, 0x84, 0x07, +0xc5, 0x09, 0x82, 0x1f, 0x21, 0x06, 0x00, 0x04, 0xe0, 0x10, 0x42, 0x01, 0x70, +0xff, 0xc0, 0x17, 0xd1, 0x1b, 0xa1, 0x78, 0x73, 0xf4, 0x6e, 0x16, 0x02, 0x00, +0x80, 0x00, 0x12, 0x04, 0xec, 0x18, 0x42, 0x48, 0x6f, 0x36, 0x01, 0x04, 0xe0, +0x0b, 0xa1, 0xb5, 0xb0, 0x04, 0x1c, 0x06, 0x11, 0xd0, 0xe0, 0x19, 0x10, 0x24, +0x6e, 0x46, 0x05, 0xb8, 0xe0, 0xa4, 0x9e, 0x09, 0x00, 0xb0, 0xe0, 0x18, 0x08, +0x5a, 0x02, 0x00, 0xe0, 0x1c, 0x06, 0x00, 0xe2, 0x36, 0x3e, 0x09, 0x86, 0x0b, +0x21, 0x19, 0x82, 0x0c, 0x04, 0x00, 0x62, 0x0b, 0xa5, 0x16, 0x7c, 0x64, 0x02, +0x80, 0xfb, 0x26, 0x3c, 0x6b, 0x9e, 0x1e, 0xbe, 0x19, 0x0e, 0x60, 0xe0, 0x2b, +0x10, 0x30, 0xf8, 0x0c, 0x00, 0x00, 0xe2, 0x16, 0x1b, 0x11, 0x81, 0x06, 0x7e, +0x2b, 0xa3, 0x19, 0x14, 0x5c, 0xc0, 0x09, 0x0e, 0x80, 0xe0, 0x25, 0x10, 0xa4, +0xe3, 0x35, 0x10, 0xa8, 0x63, 0x10, 0x42, 0x3c, 0xd0, 0x0c, 0x0b, 0x01, 0x81, +0x16, 0x1d, 0x11, 0x81, 0x26, 0x1f, 0x46, 0x05, 0x04, 0xe0, 0x0c, 0x89, 0x06, +0x99, 0x06, 0xa5, 0x0c, 0x91, 0x3e, 0x8b, 0x0e, 0x91, 0x0e, 0x8f, 0x0e, 0x0d, +0x60, 0x00, 0xa0, 0xe4, 0x91, 0x5e, 0x90, 0xe0, 0xc8, 0x7a, 0xf4, 0xee, 0x09, +0x14, 0x58, 0xe0, 0x19, 0x10, 0xf0, 0xf8, 0x0e, 0x3f, 0xfe, 0x41, 0x3d, 0x70, +0xff, 0xc0, 0x1b, 0x25, 0xf6, 0x43, 0x3c, 0xe0, 0x05, 0x5e, 0x80, 0x60, 0x16, +0x00, 0x00, 0x80, 0x15, 0x5e, 0x84, 0xe0, 0x01, 0x5e, 0x34, 0x80, 0x05, 0x14, +0x18, 0xe0, 0x38, 0x47, 0x00, 0x68, 0x00, 0x40, 0xfc, 0x73, 0xff, 0xc7, 0x2a, +0x20, 0x11, 0x91, 0x0e, 0x97, 0x78, 0x5d, 0xfc, 0x6f, 0x0b, 0x84, 0x19, 0x10, +0xc0, 0xe9, 0x0e, 0xb7, 0x4a, 0x42, 0x80, 0xe0, 0xc5, 0x82, 0x60, 0x00, 0xc0, +0x65, 0x11, 0xfb, 0x75, 0x81, 0x24, 0x37, 0x11, 0x82, 0x2e, 0xc1, 0x24, 0x97, +0x25, 0x40, 0x44, 0xf8, 0x1b, 0xa1, 0x35, 0xfe, 0xe5, 0x85, 0x04, 0x9e, 0x05, +0x00, 0x1c, 0xe0, 0x28, 0x45, 0x00, 0x68, 0x00, 0x40, 0xfc, 0x73, 0xff, 0xc7, +0x14, 0x9e, 0x0e, 0x99, 0x05, 0x02, 0x20, 0xe0, 0x98, 0x44, 0x00, 0x68, 0x00, +0x40, 0xfc, 0x73, 0xff, 0xc7, 0x14, 0x9e, 0x0e, 0x9b, 0x05, 0x02, 0x24, 0xe0, +0x08, 0x44, 0x00, 0x68, 0x00, 0x40, 0xfc, 0x73, 0xff, 0xc7, 0x2a, 0x22, 0x11, +0x91, 0x0e, 0x9d, 0x48, 0x5a, 0xfc, 0x6f, 0x0b, 0x84, 0x2a, 0x60, 0x11, 0x91, +0x0e, 0xb9, 0xe8, 0x59, 0xfc, 0x6f, 0x0b, 0x84, 0x2a, 0x62, 0x11, 0x91, 0x0e, +0xbb, 0x88, 0x59, 0xfc, 0x6f, 0x0b, 0x84, 0x0e, 0xbd, 0x04, 0x9c, 0x09, 0x00, +0x24, 0xe0, 0x0a, 0x40, 0x88, 0xe0, 0xa5, 0x84, 0x04, 0xb7, 0xf8, 0x7a, 0xf4, +0x6e, 0x1b, 0x80, 0x14, 0xb9, 0x0e, 0xb7, 0xa8, 0x7a, 0xf4, 0x6e, 0x0b, 0x82, +0x14, 0xbb, 0x0e, 0xb9, 0x58, 0x7a, 0xf4, 0x6e, 0x0b, 0x82, 0x14, 0xbd, 0x0e, +0xbb, 0x08, 0x7a, 0xf4, 0x6e, 0x0b, 0x82, 0x0e, 0xbd, 0x86, 0x41, 0x30, 0x61, +0x1b, 0x9e, 0xd8, 0x4c, 0x08, 0xef, 0x78, 0x75, 0x44, 0x6f, 0x46, 0x01, 0x04, +0xe0, 0xf0, 0x1e, 0x20, 0xe6, 0x0c, 0x0c, 0x3c, 0xf2, 0x93, 0xdd, 0x4c, 0x00, +0x00, 0xfa, 0x08, 0x74, 0x24, 0x6f, 0x11, 0x8f, 0x4c, 0x08, 0x00, 0xf2, 0x93, +0xdd, 0x0c, 0x04, 0x7c, 0xfe, 0x6b, 0x04, 0xf0, 0x1e, 0x60, 0xec, 0xb5, 0x0c, +0x10, 0xe0, 0x85, 0x0c, 0x18, 0xe0, 0x79, 0x0c, 0x24, 0xe0, 0xa4, 0xda, 0x1e, +0x0b, 0x11, 0x81, 0x0e, 0x7e, 0x0b, 0x96, 0x68, 0x79, 0xf4, 0xee, 0x0b, 0xa1, +0x15, 0x82, 0x14, 0x18, 0x0b, 0x96, 0x88, 0x79, 0xf4, 0xee, 0x0b, 0xa1, 0xb5, +0x4c, 0x00, 0xc0, 0x11, 0x01, 0x0b, 0x94, 0x98, 0x78, 0xf4, 0xee, 0x94, 0x18, +0x0b, 0xa1, 0x05, 0x1e, 0x14, 0xc0, 0xbe, 0xbe, 0x75, 0x82, 0x0b, 0x14, 0x1b, +0x92, 0xf8, 0x78, 0xf4, 0xee, 0x04, 0x0b, 0x0b, 0xa1, 0xa5, 0x4c, 0x00, 0x40, +0xa0, 0x52, 0x00, 0xdc, 0xae, 0x7c, 0xab, 0x80, 0x77, 0x42, 0x0b, 0x92, 0x15, +0x14, 0x1c, 0xfe, 0x58, 0x75, 0xf4, 0xee, 0x0b, 0x14, 0x44, 0x60, 0x9b, 0x80, +0x88, 0x75, 0xf4, 0xee, 0xbb, 0x00, 0x0b, 0x92, 0x08, 0x77, 0xf4, 0x6e, 0x1b, +0x96, 0x0b, 0xa1, 0x90, 0x56, 0x00, 0x1c, 0xa0, 0x12, 0x88, 0xe4, 0xc8, 0x75, +0xf4, 0x6e, 0x0b, 0x96, 0xab, 0x00, 0x01, 0x81, 0x01, 0x4c, 0x20, 0x60, 0x8b, +0xa1, 0x04, 0x7e, 0x76, 0x03, 0x71, 0x7b, 0x34, 0xce, 0xac, 0xd8, 0x03, 0x8e, +0x00, 0x00, 0x70, 0xe7, 0x0e, 0x89, 0x65, 0x88, 0x8b, 0xa3, 0x35, 0x08, 0x06, +0x02, 0x01, 0x00, 0xc0, 0xcf, 0x58, 0x74, 0xf4, 0x6e, 0x0b, 0x96, 0x28, 0x73, +0xf4, 0x6e, 0x8b, 0x80, 0x1b, 0x00, 0x0b, 0x96, 0x58, 0x73, 0xf4, 0xee, 0xd8, +0x49, 0x00, 0xe8, 0x18, 0x74, 0xf4, 0x6e, 0x76, 0x03, 0x70, 0x7b, 0x34, 0xce, +0x0b, 0xa1, 0x25, 0x84, 0x11, 0x01, 0x0b, 0x96, 0x48, 0x74, 0xf4, 0xee, 0x0b, +0xa1, 0x80, 0x14, 0x04, 0x04, 0x80, 0x54, 0x00, 0xdc, 0x75, 0x81, 0xc8, 0x70, +0xf4, 0x6e, 0x0b, 0x96, 0x58, 0x72, 0xf4, 0xee, 0xab, 0x80, 0xac, 0xd8, 0x0a, +0xa4, 0x08, 0x71, 0xf4, 0xee, 0x04, 0x0b, 0x9b, 0x80, 0x73, 0x80, 0x0b, 0x0e, +0x40, 0xe0, 0x98, 0x70, 0xf4, 0xee, 0xbb, 0x00, 0xa4, 0x00, 0x80, 0xfb, 0x48, +0x70, 0xf4, 0xee, 0x1b, 0x00, 0x0b, 0x96, 0x68, 0x49, 0x00, 0xe8, 0x1b, 0x00, +0x0b, 0x92, 0xc8, 0x47, 0x00, 0xe8, 0xa8, 0x70, 0xf4, 0xee, 0x04, 0x09, 0xab, +0x80, 0xac, 0xd8, 0x9a, 0x80, 0x9c, 0xda, 0x0b, 0x0e, 0x4c, 0xe0, 0x18, 0x6f, +0xf4, 0xee, 0x0b, 0x0e, 0x48, 0x60, 0xbb, 0x80, 0xc8, 0x6e, 0xf4, 0xee, 0x8b, +0x00, 0x0b, 0x92, 0x88, 0x6e, 0xf4, 0xee, 0x1b, 0x00, 0x0b, 0x90, 0xa8, 0x47, +0x00, 0xe8, 0x1b, 0x00, 0x0b, 0x96, 0x08, 0x46, 0x00, 0xe8, 0xe8, 0x6e, 0xf4, +0xee, 0xb4, 0x3e, 0x9b, 0x80, 0x84, 0xfc, 0x9c, 0xda, 0x1b, 0x96, 0xf8, 0x6d, +0xf4, 0x6e, 0x0b, 0x90, 0x68, 0x44, 0x00, 0xe8, 0xe8, 0x6e, 0xf4, 0x6e, 0x11, +0x81, 0x0b, 0xa1, 0x85, 0x8e, 0x11, 0x01, 0x0b, 0x96, 0xf8, 0x6d, 0xf4, 0xee, +0x2a, 0x00, 0x00, 0x60, 0x0b, 0x94, 0x24, 0x7e, 0x1b, 0x92, 0x34, 0x8b, 0xf8, +0x74, 0x18, 0xef, 0x58, 0x01, 0x01, 0x40, 0x00, 0x40, 0x9b, 0x80, 0x0b, 0x16, +0x1b, 0x92, 0x78, 0x6d, 0xf4, 0xee, 0x0b, 0xa1, 0x85, 0x82, 0x1a, 0xd8, 0x09, +0x0c, 0x20, 0xe0, 0x11, 0x82, 0x04, 0x09, 0x0b, 0xa1, 0x1c, 0x58, 0x70, 0x00, +0x90, 0xc4, 0x0a, 0x80, 0x0c, 0xda, 0x11, 0x01, 0x0b, 0x90, 0xe8, 0x6b, 0xf4, +0xee, 0x0b, 0xa1, 0xb5, 0x84, 0x0b, 0x10, 0x1b, 0x92, 0x88, 0x6c, 0xf4, 0xee, +0x0b, 0xa1, 0xe5, 0x82, 0x09, 0x0c, 0x20, 0xe0, 0x1a, 0xd8, 0x0b, 0xa1, 0x70, +0x0e, 0x98, 0x44, 0x10, 0x00, 0x04, 0xec, 0x75, 0x5e, 0x10, 0xc0, 0x0c, 0xd8, +0x04, 0x89, 0x0a, 0x80, 0x0c, 0xda, 0xf1, 0xb0, 0x0c, 0x0c, 0x7c, 0xf6, 0x93, +0xdd, 0x0c, 0x04, 0x00, 0xfa, 0x6b, 0x00, 0x11, 0x81, 0xe8, 0x6a, 0xf4, 0xee, +0x0b, 0xa1, 0x64, 0x0c, 0x7c, 0xdd, 0x0b, 0x8c, 0x0c, 0x0c, 0x00, 0xf2, 0x93, +0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x0c, 0xfa, 0x6b, 0x02, 0x7b, 0x80, 0x08, 0x6a, +0xf4, 0xee, 0x8b, 0x00, 0x0b, 0x8e, 0x48, 0x69, 0xf4, 0x6e, 0x1b, 0x8c, 0x0b, +0xa1, 0x70, 0x4c, 0x00, 0x1c, 0x8b, 0xa1, 0x70, 0x4c, 0x00, 0xdc, 0x0b, 0x8c, +0x0c, 0x0c, 0x0c, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x4c, 0x04, 0x04, 0xfa, 0x6b, +0x02, 0x7b, 0x80, 0xa8, 0x68, 0xf4, 0xee, 0x0b, 0xa1, 0x70, 0x4c, 0x00, 0x9c, +0x0b, 0x8c, 0x4c, 0x0c, 0x04, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, +0xfa, 0x6b, 0x00, 0x21, 0x81, 0x04, 0x18, 0x31, 0x83, 0x68, 0x53, 0xfc, 0xef, +0x0e, 0x98, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x18, 0xa9, 0x00, 0x00, 0x38, +0x90, 0x40, 0x00, 0x08, 0x14, 0xb8, 0xe0, 0x80, 0x2c, 0x40, 0x00, 0xa8, 0x3a, +0xe8, 0xe0, 0x08, 0x9f, 0x40, 0x00, 0x28, 0x28, 0xb0, 0xe0, 0x18, 0x72, 0x40, +0x00, 0x88, 0x19, 0xc8, 0xe0, 0xe0, 0xbb, 0x40, 0x00, 0x08, 0x0c, 0xa4, 0xe0, +0xa8, 0xe3, 0x40, 0x00, 0x28, 0x13, 0x90, 0xe0, 0xf0, 0xec, 0x40, 0x00, 0x68, +0x0d, 0x8c, 0xe0, 0x48, 0xa8, 0x40, 0x00, 0xa8, 0x3c, 0xac, 0xe0, 0x28, 0xe4, +0x40, 0x00, 0x08, 0x0e, 0x94, 0xe0, 0xb8, 0xe2, 0x40, 0x00, 0x28, 0x3d, 0x90, +0xe0, 0x90, 0xa7, 0x40, 0x00, 0xc8, 0x13, 0xb0, 0xe0, 0x18, 0xc1, 0x40, 0x00, +0xa8, 0x2a, 0xa4, 0xe0, 0xa8, 0x91, 0x40, 0x00, 0x68, 0x2f, 0xbc, 0xe0, 0x88, +0xf1, 0x40, 0x00, 0x08, 0x1c, 0x88, 0xe0, 0xd0, 0x8e, 0x40, 0x00, 0xa8, 0x19, +0xb8, 0xe0, 0x38, 0x82, 0x40, 0x00, 0x28, 0x38, 0xbc, 0xe0, 0x88, 0xe0, 0x40, +0x00, 0xa8, 0x0e, 0x98, 0xe0, 0xd8, 0xe4, 0x40, 0x00, 0x68, 0x32, 0x94, 0xe0, +0x18, 0xd7, 0x40, 0x00, 0x28, 0x2d, 0x9c, 0xe0, 0x40, 0xe8, 0x40, 0x00, 0x28, +0x19, 0x94, 0xe0, 0xa8, 0x78, 0x40, 0x00, 0x28, 0x18, 0xcc, 0xe0, 0xf0, 0x95, +0x40, 0x00, 0x48, 0x34, 0xbc, 0xe0, 0xa8, 0xd2, 0x40, 0x00, 0x48, 0x16, 0xa0, +0xe0, 0x18, 0x52, 0x40, 0x00, 0x68, 0x2b, 0xe0, 0xe0, 0x00, 0xb9, 0x00, 0x00, +0x23, 0xdb, 0x00, 0x00 +}; + +static const u8 cut2_patch[] = { +0x37, 0x00, 0x05, 0x00, 0x0c, 0x04, 0x3c, 0xfa, 0x86, 0x0e, 0x84, 0x60, 0x06, +0x11, 0x84, 0xe0, 0x04, 0x1c, 0x06, 0x13, 0x88, 0xe1, 0x14, 0xa0, 0x0d, 0xc2, +0x95, 0x80, 0x04, 0xa4, 0x0b, 0xa3, 0xb5, 0x88, 0x78, 0x6c, 0x40, 0xef, 0x6b, +0x00, 0x46, 0x01, 0x84, 0xe0, 0x88, 0x53, 0x48, 0x6f, 0x46, 0x15, 0x84, 0xe0, +0x6b, 0x25, 0xc6, 0x0d, 0x84, 0xe0, 0x95, 0x84, 0x08, 0xa8, 0x4a, 0x00, 0xfc, +0xfb, 0x05, 0x84, 0x09, 0x12, 0x1c, 0xe0, 0xa8, 0x46, 0x48, 0xef, 0x04, 0x1c, +0x11, 0x83, 0x16, 0x18, 0xc6, 0x04, 0x84, 0xe0, 0x0e, 0x08, 0x01, 0x87, 0xf8, +0x6a, 0x40, 0xef, 0x46, 0x0c, 0x84, 0xe0, 0x04, 0x1c, 0x11, 0x81, 0x16, 0x98, +0x0e, 0xa0, 0x06, 0x41, 0x84, 0x60, 0xc6, 0x02, 0xd8, 0xe0, 0x04, 0x80, 0x08, +0x80, 0x06, 0x84, 0x0c, 0x0c, 0x3c, 0xf2, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x00, +0x80, 0xfa, 0xc6, 0x40, 0x84, 0x60, 0x86, 0x43, 0xd8, 0xe0, 0x04, 0x80, 0x09, +0x00, 0x18, 0xe1, 0x06, 0x84, 0x88, 0x41, 0x68, 0xef, 0x0c, 0x08, 0x80, 0xf2, +0x48, 0x20, 0x68, 0xff, 0x1e, 0xc0, 0xb5, 0x00, 0x2c, 0xe0, 0xb4, 0x02, 0x00, +0xfc, 0x15, 0x40, 0x2c, 0xe0, 0x19, 0x10, 0x31, 0x78, 0x01, 0x40, 0x86, 0x04, +0x0c, 0xe0, 0x4a, 0x04, 0x00, 0x64, 0x31, 0x81, 0x30, 0x0a, 0x04, 0x64, 0x4b, +0x82, 0x1b, 0x06, 0x3b, 0x8a, 0xc8, 0x44, 0xfc, 0x87, 0x41, 0x50, 0x31, 0x78, +0x01, 0x40, 0xa1, 0xe1, 0xe6, 0x43, 0x99, 0x6c, 0x10, 0xc0, 0x08, 0x42, 0x00, +0xf8, 0x00, 0x00, 0x03, 0xa9, 0x00, 0x00, 0x30, 0x85, 0x40, 0x00, 0xa8, 0x16, +0xbc, 0xe0, 0xcc, 0xc1, 0x40, 0x00, 0xa8, 0x35, 0x9c, 0xe0, 0xb0, 0xc9, 0x40, +0x00, 0x88, 0x37, 0x98, 0xe0, 0x00, 0xb9, 0x00, 0x00, 0x49, 0xa7, 0x00, 0x00, +}; + +struct vd55g0_patch { + const u8 *bin; + const unsigned int size; + const u16 major; + const u16 minor; +}; + +static struct vd55g0_patch vd55g0_patch_cut1 = { + .bin = cut1_patch, + .size = ARRAY_SIZE(cut1_patch), + .major = 2, + .minor = 11, +}; + +static struct vd55g0_patch vd55g0_patch_cut2 = { + .bin = cut2_patch, + .size = ARRAY_SIZE(cut2_patch), + .major = 0, + .minor = 5, +}; diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index b1d0ce873ecf..58185c641f14 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -65,6 +65,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), /* Omnivision OV13858 - Surface Pro 9 */ IPU_SENSOR_CONFIG("OVTID858", 4, 540000000), + /* ST VD55G0 IR - Surface Pro 11 */ + IPU_SENSOR_CONFIG("SMO55F0", 1, 300000000), /* Sony IMX681 - Surface Pro 11 */ IPU_SENSOR_CONFIG("SONY0681", 1, 969600000), /* Omnivision OV2740 */ diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c index 6dc38d5cbd0b..991947614f1c 100644 --- a/drivers/platform/x86/intel/int3472/common.c +++ b/drivers/platform/x86/intel/int3472/common.c @@ -69,7 +69,7 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev, return -ENODEV; } - dev_dbg(dev, "Sensor name %s\n", acpi_dev_name(sensor)); + dev_info(dev, "Sensor ACPI name %s\n", acpi_dev_name(sensor)); *name_ret = devm_kasprintf(dev, GFP_KERNEL, I2C_DEV_NAME_FORMAT, acpi_dev_name(sensor)); diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 20962e8acb26..0e63b239abad 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -384,7 +384,16 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, dev_err(int3472->dev, "Failed to register type 0x02x: %d\n", type, ret); } break; - case 0x10: + case 0x10: /* Surface Pro 11 - secondary power rail */ + dev_info(int3472->dev, "GPIO type 0x10 detected on pin 0x%02x\n", + agpio->pin_table[0]); + dev_info(int3472->dev, " con_id=%s, flags=0x%lx\n", con_id, gpio_flags); + ret = skl_int3472_register_regulator(int3472, gpio, + GPIO_REGULATOR_ENABLE_TIME, + con_id, NULL); + dev_info(int3472->dev, " register_regulator returned: %d\n", ret); + if (ret) + dev_err(int3472->dev, "Failed to register type 0x10: %d\n", ret); break; default: /* Never reached */ ret = -EINVAL;