diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 8f2ed29b4f353..1484668ea7c8d 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, diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index cdd7ba5da0d50..086ae87b7fba5 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 @@ -745,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 57cdd8dc96f63..bd452de9a05cd 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 @@ -158,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/imx681.c b/drivers/media/i2c/imx681.c new file mode 100644 index 0000000000000..8a07aaa006bdf --- /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 85cd0c98af031..67bba924f4133 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/i2c/vd55g0.c b/drivers/media/i2c/vd55g0.c new file mode 100644 index 0000000000000..943dffec5ad65 --- /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 0000000000000..0f31b59b94a93 --- /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 1ef31d6553923..58185c641f142 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -65,6 +65,10 @@ 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 */ IPU_SENSOR_CONFIG("INT3474", 1, 180000000), /* Omnivision OV5670 */ diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index fdc410bf70e51..bbfc3f33b8357 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) diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index b4496db79f392..d7844e26f4fe5 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, + }, { } }; diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c index 6dc38d5cbd0ba..991947614f1ce 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 20962e8acb263..0e63b239abade 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; diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index 5cddc09c72bf2..65c42da2ad852 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); diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index c013e31d098e7..683658612ca2d 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 937a74a5d5237..908a9556eff6a 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 97be110a59b63..daf7693b76aef 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 3848c7df1916f..9b71e16fcd483 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 270f9777942f0..aeca2a7dd0bad 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 */