From 0e1c2854a2496bcd48163f168361ea90d1e63ec8 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 26 Mar 2021 17:06:36 +0000 Subject: [PATCH 01/17] drm/panel-simple: Add a timing for the Raspberry Pi 7" panel The Raspberry Pi 7" 800x480 panel uses a Toshiba TC358762 DSI to DPI bridge chip, so there is a requirement for the timings to be specified for the end panel. Add such a definition. Signed-off-by: Dave Stevenson drm/panel-simple: Populate bpc when using panel-dpi panel-dpi doesn't know the bit depth, so in the same way that DPI is guessed for the connector type, guess that it'll be 8bpc. Signed-off-by: Dave Stevenson drm/panel-simple: Allow the bus format to be read from DT for panel-dpi The "panel-dpi" compatible string configures panel from device tree, but it doesn't provide any way of configuring the bus format (colour representation), nor does it populate it. Add a DT parameter "bus-format" that allows the MEDIA_BUS_FMT_xxx value to be specified from device tree. Signed-off-by: Dave Stevenson drm/panel: simple: add Geekworm MZP280 Panel Add support for the Geekworm MZP280 Panel Signed-off-by: Chris Morgan Acked-by: Maxime Ripard drm/panel: simple: Add Innolux AT056tN53V1 5.6" VGA Add support for the Innolux AT056tN53V1 5.6" VGA (640x480) TFT LCD panel. Signed-off-by: Joerg Quinten Signed-off-by: Phil Elwell drm/panel: simple: Alter the timing for the Pi 7" DSI display vc4 has always fixed up the timing, so the values defined have never actually appeared on the wire. The display appears to want a slightly longer HFP, so extend the timings and recompute the clock to give the same frame rate. Signed-off-by: Dave Stevenson drm/panel: add panel-dsi Equivalent to panel-dpi for configuring a simple DSI panel with device tree side timings and bus settings. Motiviation is the same as for panel-dpi of wanting to support new simple panels without needing to patch the kernel. Signed-off-by: Timon Skerutsch drm/panel-simple: Remove custom handling of orientation The framework now handles reading orientation from DT, therefore remove the custom get_orientation hook from panel-simple. Signed-off-by: Dave Stevenson drm/panel-simple: Fix 7inch panel mode for misalignment The 7inch panel is one line off the screen both horizontally and vertically. Alter the panel mode to correct this. Signed-off-by: Dave Stevenson drm/panel-simple: Increase pixel clock on Pi 7inch panel The Toshiba bridge is very fussy and doesn't like the CM3 output when being told to produce a 27.777MHz pixel clock, which is an almost perfect match to the DSI link integer divider. Increasing to 30MHz will switch the DSI link from 333MHz to 400MHz and makes the bridge happy with the same video timing as works on Pi4. (Pi4 will be using a link frequency of 375MHz due to a 3GHz parent PLL). Signed-off-by: Dave Stevenson --- .../bindings/display/panel/panel-simple.yaml | 2 + .../media/v4l/subdev-formats.rst | 111 ++++++++ drivers/gpu/drm/panel/panel-simple.c | 236 ++++++++++++++++-- 3 files changed, 327 insertions(+), 22 deletions(-) diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml index 2017428d8828e..fe667ac2ccbd3 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml @@ -158,6 +158,8 @@ properties: - hit,tx23d38vm0caa # Innolux AT043TN24 4.3" WQVGA TFT LCD panel - innolux,at043tn24 + # Innolux AT056tN53V1 5.6" VGA (640x480) TFT LCD panel + - innolux,at056tn53v1 # Innolux AT070TN92 7.0" WQVGA TFT LCD panel - innolux,at070tn92 # Innolux G070ACE-L01 7" WVGA (800x480) TFT LCD panel diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index 2a94371448dc0..b716b51f849d3 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -625,6 +625,43 @@ The following tables list existing packed RGB formats. - b\ :sub:`2` - b\ :sub:`1` - b\ :sub:`0` + * .. _MEDIA_BUS_FMT_RGB565_1X24_CPADHI: + + - MEDIA_BUS_FMT_RGB565_1X24_CPADHI + - 0x1022 + - + - + - + - + - + - + - + - + - + - 0 + - 0 + - 0 + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` + - 0 + - 0 + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - 0 + - 0 + - 0 + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` * .. _MEDIA-BUS-FMT-BGR565-2X8-BE: - MEDIA_BUS_FMT_BGR565_2X8_BE @@ -913,6 +950,43 @@ The following tables list existing packed RGB formats. - g\ :sub:`5` - g\ :sub:`4` - g\ :sub:`3` + * .. _MEDIA-BUS-FMT-BGR666-1X18: + + - MEDIA_BUS_FMT-BGR666_1X18 + - 0x1023 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` * .. _MEDIA-BUS-FMT-RGB666-1X18: - MEDIA_BUS_FMT_RGB666_1X18 @@ -1096,6 +1170,43 @@ The following tables list existing packed RGB formats. - g\ :sub:`2` - g\ :sub:`1` - g\ :sub:`0` + * .. _MEDIA-BUS-FMT-BGR666-1X24_CPADHI: + + - MEDIA_BUS_FMT_BGR666_1X24_CPADHI + - 0x1024 + - + - + - + - + - + - + - + - + - + - 0 + - 0 + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + - 0 + - 0 + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - 0 + - 0 + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` * .. _MEDIA-BUS-FMT-RGB666-1X24_CPADHI: - MEDIA_BUS_FMT_RGB666_1X24_CPADHI diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 0019de93be1b6..48502634b1e42 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -160,8 +160,6 @@ struct panel_simple { const struct drm_edid *drm_edid; struct drm_display_mode override_mode; - - enum drm_panel_orientation orientation; }; static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) @@ -396,12 +394,6 @@ static int panel_simple_get_modes(struct drm_panel *panel, /* add hard-coded panel modes */ num += panel_simple_get_non_edid_modes(p, connector); - /* - * TODO: Remove once all drm drivers call - * drm_connector_set_orientation_from_panel() - */ - drm_connector_set_panel_orientation(connector, p->orientation); - return num; } @@ -422,20 +414,12 @@ static int panel_simple_get_timings(struct drm_panel *panel, return p->desc->num_timings; } -static enum drm_panel_orientation panel_simple_get_orientation(struct drm_panel *panel) -{ - struct panel_simple *p = to_panel_simple(panel); - - return p->orientation; -} - static const struct drm_panel_funcs panel_simple_funcs = { .disable = panel_simple_disable, .unprepare = panel_simple_unprepare, .prepare = panel_simple_prepare, .enable = panel_simple_enable, .get_modes = panel_simple_get_modes, - .get_orientation = panel_simple_get_orientation, .get_timings = panel_simple_get_timings, }; @@ -469,6 +453,7 @@ static struct panel_desc *panel_dpi_probe(struct device *dev) of_property_read_u32(np, "width-mm", &desc->size.width); of_property_read_u32(np, "height-mm", &desc->size.height); + of_property_read_u32(np, "bus-format", &desc->bus_format); /* Extract bus_flags from display_timing */ bus_flags = 0; @@ -478,6 +463,8 @@ static struct panel_desc *panel_dpi_probe(struct device *dev) /* We do not know the connector for the DT node, so guess it */ desc->connector_type = DRM_MODE_CONNECTOR_DPI; + /* Likewise for the bit depth. */ + desc->bpc = 8; return desc; } @@ -640,12 +627,6 @@ static struct panel_simple *panel_simple_probe(struct device *dev) return dev_err_cast_probe(dev, panel->enable_gpio, "failed to request GPIO\n"); - err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation); - if (err) { - dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); - return ERR_PTR(err); - } - ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); if (ddc) { panel->ddc = of_find_i2c_adapter_by_node(ddc); @@ -2381,6 +2362,32 @@ static const struct panel_desc friendlyarm_hd702e = { }, }; +static const struct drm_display_mode geekworm_mzp280_mode = { + .clock = 32000, + .hdisplay = 480, + .hsync_start = 480 + 41, + .hsync_end = 480 + 41 + 20, + .htotal = 480 + 41 + 20 + 60, + .vdisplay = 640, + .vsync_start = 640 + 5, + .vsync_end = 640 + 5 + 10, + .vtotal = 640 + 5 + 10 + 10, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc geekworm_mzp280 = { + .modes = &geekworm_mzp280_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 47, + .height = 61, + }, + .bus_format = MEDIA_BUS_FMT_RGB565_1X24_CPADHI, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct drm_display_mode giantplus_gpg482739qs5_mode = { .clock = 9000, .hdisplay = 480, @@ -2561,6 +2568,38 @@ static const struct panel_desc innolux_at043tn24 = { .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, }; +static const struct display_timing innolux_at056tn53v1_timing = { + .pixelclock = { 39700000, 39700000, 39700000}, + .hactive = { 640, 640, 640 }, + .hfront_porch = { 16, 16, 16 }, + .hback_porch = { 134, 134, 134 }, + .hsync_len = { 10, 10, 10}, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 32, 32, 32}, + .vback_porch = { 11, 11, 11 }, + .vsync_len = { 2, 2, 2 }, + .flags = DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_PHSYNC, +}; + +static const struct panel_desc innolux_at056tn53v1 = { + .timings = &innolux_at056tn53v1_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 112, + .height = 84, + }, + .delay = { + .prepare = 50, + .enable = 200, + .disable = 110, + .unprepare = 200, + }, + .bus_format = MEDIA_BUS_FMT_BGR666_1X24_CPADHI, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct drm_display_mode innolux_at070tn92_mode = { .clock = 33333, .hdisplay = 800, @@ -4101,6 +4140,31 @@ static const struct panel_desc rocktech_rk043fn48h = { .connector_type = DRM_MODE_CONNECTOR_DPI, }; +static const struct drm_display_mode raspberrypi_7inch_mode = { + .clock = 30000, + .hdisplay = 800, + .hsync_start = 800 + 131, + .hsync_end = 800 + 131 + 2, + .htotal = 800 + 131 + 2 + 45, + .vdisplay = 480, + .vsync_start = 480 + 7, + .vsync_end = 480 + 7 + 2, + .vtotal = 480 + 7 + 2 + 22, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc raspberrypi_7inch = { + .modes = &raspberrypi_7inch_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_DSI, +}; + static const struct display_timing rocktech_rk070er9427_timing = { .pixelclock = { 26400000, 33300000, 46800000 }, .hactive = { 800, 800, 800 }, @@ -5151,6 +5215,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "friendlyarm,hd702e", .data = &friendlyarm_hd702e, + }, { + .compatible = "geekworm,mzp280", + .data = &geekworm_mzp280, }, { .compatible = "giantplus,gpg482739qs5", .data = &giantplus_gpg482739qs5 @@ -5172,6 +5239,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "innolux,at043tn24", .data = &innolux_at043tn24, + }, { + .compatible = "innolux,at056tn53v1", + .data = &innolux_at056tn53v1, }, { .compatible = "innolux,at070tn92", .data = &innolux_at070tn92, @@ -5346,6 +5416,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "rocktech,rk043fn48h", .data = &rocktech_rk043fn48h, + }, { + .compatible = "raspberrypi,7inch-dsi", + .data = &raspberrypi_7inch, }, { .compatible = "rocktech,rk070er9427", .data = &rocktech_rk070er9427, @@ -5709,6 +5782,9 @@ static const struct panel_desc_dsi osd101t2045_53ts = { .lanes = 4, }; +// for panels using generic panel-dsi binding +static struct panel_desc_dsi panel_dsi; + static const struct of_device_id dsi_of_match[] = { { .compatible = "auo,b080uan01", @@ -5731,12 +5807,113 @@ static const struct of_device_id dsi_of_match[] = { }, { .compatible = "osddisplays,osd101t2045-53ts", .data = &osd101t2045_53ts + }, { + /* Must be the last entry */ + .compatible = "panel-dsi", + .data = &panel_dsi, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dsi_of_match); + +/* Checks for DSI panel definition in device-tree, analog to panel_dpi */ +static int panel_dsi_dt_probe(struct device *dev, + struct panel_desc_dsi *desc_dsi) +{ + struct panel_desc *desc; + struct display_timing *timing; + const struct device_node *np; + const char *dsi_color_format; + const char *dsi_mode_flags; + struct property *prop; + int dsi_lanes, ret; + + np = dev->of_node; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL); + if (!timing) + return -ENOMEM; + + ret = of_get_display_timing(np, "panel-timing", timing); + if (ret < 0) { + dev_err(dev, "%pOF: no panel-timing node found for \"panel-dsi\" binding\n", + np); + return ret; + } + + desc->timings = timing; + desc->num_timings = 1; + + of_property_read_u32(np, "width-mm", &desc->size.width); + of_property_read_u32(np, "height-mm", &desc->size.height); + + dsi_lanes = drm_of_get_data_lanes_count_ep(np, 0, 0, 1, 4); + + if (dsi_lanes < 0) { + dev_err(dev, "%pOF: no or too many data-lanes defined", np); + return dsi_lanes; + } + + desc_dsi->lanes = dsi_lanes; + + of_property_read_string(np, "dsi-color-format", &dsi_color_format); + if (!strcmp(dsi_color_format, "RGB888")) { + desc_dsi->format = MIPI_DSI_FMT_RGB888; + desc->bpc = 8; + } else if (!strcmp(dsi_color_format, "RGB565")) { + desc_dsi->format = MIPI_DSI_FMT_RGB565; + desc->bpc = 6; + } else if (!strcmp(dsi_color_format, "RGB666")) { + desc_dsi->format = MIPI_DSI_FMT_RGB666; + desc->bpc = 6; + } else if (!strcmp(dsi_color_format, "RGB666_PACKED")) { + desc_dsi->format = MIPI_DSI_FMT_RGB666_PACKED; + desc->bpc = 6; + } else { + dev_err(dev, "%pOF: no valid dsi-color-format defined", np); + return -EINVAL; + } + + + of_property_for_each_string(np, "mode", prop, dsi_mode_flags) { + if (!strcmp(dsi_mode_flags, "MODE_VIDEO")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_BURST")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_BURST; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_SYNC_PULSE")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_AUTO_VERT")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_AUTO_VERT; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_HSE")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_HSE; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HFP")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HFP; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HBP")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HBP; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HSA")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HSA; + else if (!strcmp(dsi_mode_flags, "MODE_NO_EOT_PACKET")) + desc_dsi->flags |= MIPI_DSI_MODE_NO_EOT_PACKET; + else if (!strcmp(dsi_mode_flags, "CLOCK_NON_CONTINUOUS")) + desc_dsi->flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + else if (!strcmp(dsi_mode_flags, "MODE_LPM")) + desc_dsi->flags |= MIPI_DSI_MODE_LPM; + else if (!strcmp(dsi_mode_flags, "HS_PKT_END_ALIGNED")) + desc_dsi->flags |= MIPI_DSI_HS_PKT_END_ALIGNED; + } + + desc->connector_type = DRM_MODE_CONNECTOR_DSI; + desc_dsi->desc = *desc; + + return 0; +} + static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi) { const struct panel_desc_dsi *desc; @@ -5748,6 +5925,21 @@ static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi) return PTR_ERR(panel); desc = container_of(panel->desc, struct panel_desc_dsi, desc); + + if (desc == &panel_dsi) { + /* Handle the generic panel-dsi binding */ + struct panel_desc_dsi *dt_desc; + dt_desc = devm_kzalloc(&dsi->dev, sizeof(*dt_desc), GFP_KERNEL); + if (!dt_desc) + return -ENOMEM; + + err = panel_dsi_dt_probe(&dsi->dev, dt_desc); + if (err < 0) + return err; + + desc = dt_desc; + } + dsi->mode_flags = desc->flags; dsi->format = desc->format; dsi->lanes = desc->lanes; From 0698604e1b1176cd7e5b20b4311e1d82bd3623f9 Mon Sep 17 00:00:00 2001 From: Nick Hollinghurst Date: Thu, 22 Dec 2022 13:59:33 +0000 Subject: [PATCH 02/17] media/i2c: Add a driver for the Sony IMX708 image sensor The imx708 is a 12MP MIPI sensor with a 16:9 aspect ratio, here using two CSI-2 lanes. It is a "quad Bayer" sensor with all 3 modes offering 10-bit output: 12MP: 4608x2592 up to 14.35fps (full FoV) 1080p: 2304x1296 up to 56.02fps (full FoV) 720p: 1536x864 up to 120.12fps (cropped) This imx708 sensor driver is based heavily on the imx477 driver and has been tested on the Raspberry Pi platform using libcamera. Signed-off-by: Nick Hollinghurst Signed-off-by: Dave Stevenson drivers: media: imx708: Enable long exposure mode Enable long exposure modes by using the long exposure shift register setting in the imx708 sensor. Signed-off-by: Naushir Patuck drivers: media: i2c: imx708: Fix crop information The 1536x864 mode contained incorrect crop information. Signed-off-by: David Plowman drivers: media: i2c: imx708: Fix WIDE_DYNAMIC_RANGE control with long exposure Setting V4L2_CID_WIDE_DYNAMIC_RANGE was causing the long exposure shift count to be reset, which is incorrect if the user has already changed the frame length to cause it to have a non-zero value. Because it only updates control ranges and doesn't set any registers, the control can also be applied when the sensor is not powered on. Signed-off-by: David Plowman drivers: media: imx708: Increase usable link frequencies Add support for three different usable link frequencies (default 450Mhz, 447Mhz, and 453MHz) for the IMX708 camera sensor. The choice of frequency is handled thorugh the "link-frequency" overlay parameter. Signed-off-by: Naushir Patuck drivers: media: imx708: Remove unused control fields Remove unused and redundant control fields from the state structure. Signed-off-by: Naushir Patuck drivers: media: imx708: Tidy-ups to address upstream review comments This commit addresses vaious tidy-ups requesed for upstreaming the IMX708 driver. Notably: - Remove #define IMX708_NUM_SUPPLIES and use ARRAY_SIZE() directly - Use dev_err_probe where possible in imx708_probe() - Fix error handling paths in imx708_probe() Signed-off-by: Naushir Patuck drivers: media: imx708: Follow the standard devicetree labels Switch the system clock name from "xclk" to "inclk". Use lower case lables for all regulator names. Signed-off-by: Naushir Patuck drives: media: imx708: Put HFLIP and VFLIP controls in a cluster Create a cluster for the HVLIP and VFLIP controls so they are treated as a single composite control. Signed-off-by: Naushir Patuck drivers: media: imx708: Adjust broken line correction parameter In full-resolution mode, the LPF_INTENSITY_EN and LPF_INTENSITY registers control Quad Bayer Re-mosaic broken line correction. Expose this as a module parameter "qbc_adjust": zero disables the correction and values in the range 2 to 5 set its strength. There is a trade-off between coloured and monochrome patterns. The previous fixed value 4 could produce ladder/spots artefacts in coloured textures. The new default value 2 may suit a wider range of scenes. Signed-off-by: Nick Hollinghurst media: i2c: imx708: Squash fixes media: i2c: imx708: Fix lockdep issues. The driver had a lockdep_assert_held in imx708_get_format_code, but the calls from enum_mbus_code and enum_frame_size didn't take the mutex. Likewise imx708_set_framing_limits calling __v4l2_ctrl_modify_range had a lockdep, but when going through the probe function the mutex hadn't been taken. Fix both cases. Signed-off-by: Dave Stevenson media: i2c: Tweak default PDAF gain table in imx708 driver After analyzing more Raspberry Pi V3 cameras, adjust the default PDAF shield-pixel gain tables (they can still be overridden by camera OTP where programmed). Signed-off-by: Nick Hollinghurst drivers: i2c: imx708: Use pm_runtime_use_autosuspend() Switch the power management in the imx708 device driver to use auto- suspend with a 5s timeout. This improves mode switching time that avoids additional regulator switch-on delays and common register I2C writes. Signed-off-by: Naushir Patuck --- drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx708.c | 2134 ++++++++++++++++++++++++++++++++++++ 3 files changed, 2148 insertions(+) create mode 100644 drivers/media/i2c/imx708.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index cdd7ba5da0d50..bc5ade3af839c 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -277,6 +277,19 @@ config VIDEO_IMX415 To compile this driver as a module, choose M here: the module will be called imx415. +config VIDEO_IMX708 + tristate "Sony IMX708 sensor support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX708 camera. + + To compile this driver as a module, choose M here: the + module will be called imx708. + config VIDEO_MAX9271_LIB tristate diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 57cdd8dc96f63..f7ae97375c1dc 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o obj-$(CONFIG_VIDEO_IMX415) += imx415.o +obj-$(CONFIG_VIDEO_IMX708) += imx708.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c new file mode 100644 index 0000000000000..6ab38e6a30e56 --- /dev/null +++ b/drivers/media/i2c/imx708.c @@ -0,0 +1,2134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX708 cameras. + * Copyright (C) 2022, Raspberry Pi Ltd + * + * Based on Sony imx477 camera driver + * Copyright (C) 2020 Raspberry Pi Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Parameter to adjust Quad Bayer re-mosaic broken line correction + * strength, used in full-resolution mode only. Set zero to disable. + */ +static int qbc_adjust = 2; +module_param(qbc_adjust, int, 0644); +MODULE_PARM_DESC(qbc_adjust, "Quad Bayer broken line correction strength [0,2-5]"); + +#define IMX708_REG_VALUE_08BIT 1 +#define IMX708_REG_VALUE_16BIT 2 + +/* Chip ID */ +#define IMX708_REG_CHIP_ID 0x0016 +#define IMX708_CHIP_ID 0x0708 + +#define IMX708_REG_MODE_SELECT 0x0100 +#define IMX708_MODE_STANDBY 0x00 +#define IMX708_MODE_STREAMING 0x01 + +#define IMX708_REG_ORIENTATION 0x101 + +#define IMX708_INCLK_FREQ 24000000 + +/* Default initial pixel rate, will get updated for each mode. */ +#define IMX708_INITIAL_PIXEL_RATE 590000000 + +/* V_TIMING internal */ +#define IMX708_REG_FRAME_LENGTH 0x0340 +#define IMX708_FRAME_LENGTH_MAX 0xffff + +/* Long exposure multiplier */ +#define IMX708_LONG_EXP_SHIFT_MAX 7 +#define IMX708_LONG_EXP_SHIFT_REG 0x3100 + +/* Exposure control */ +#define IMX708_REG_EXPOSURE 0x0202 +#define IMX708_EXPOSURE_OFFSET 48 +#define IMX708_EXPOSURE_DEFAULT 0x640 +#define IMX708_EXPOSURE_STEP 1 +#define IMX708_EXPOSURE_MIN 1 +#define IMX708_EXPOSURE_MAX (IMX708_FRAME_LENGTH_MAX - \ + IMX708_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX708_REG_ANALOG_GAIN 0x0204 +#define IMX708_ANA_GAIN_MIN 112 +#define IMX708_ANA_GAIN_MAX 960 +#define IMX708_ANA_GAIN_STEP 1 +#define IMX708_ANA_GAIN_DEFAULT IMX708_ANA_GAIN_MIN + +/* Digital gain control */ +#define IMX708_REG_DIGITAL_GAIN 0x020e +#define IMX708_DGTL_GAIN_MIN 0x0100 +#define IMX708_DGTL_GAIN_MAX 0xffff +#define IMX708_DGTL_GAIN_DEFAULT 0x0100 +#define IMX708_DGTL_GAIN_STEP 1 + +/* Colour balance controls */ +#define IMX708_REG_COLOUR_BALANCE_RED 0x0b90 +#define IMX708_REG_COLOUR_BALANCE_BLUE 0x0b92 +#define IMX708_COLOUR_BALANCE_MIN 0x01 +#define IMX708_COLOUR_BALANCE_MAX 0xffff +#define IMX708_COLOUR_BALANCE_STEP 0x01 +#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 + +/* Test Pattern Control */ +#define IMX708_REG_TEST_PATTERN 0x0600 +#define IMX708_TEST_PATTERN_DISABLE 0 +#define IMX708_TEST_PATTERN_SOLID_COLOR 1 +#define IMX708_TEST_PATTERN_COLOR_BARS 2 +#define IMX708_TEST_PATTERN_GREY_COLOR 3 +#define IMX708_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX708_REG_TEST_PATTERN_R 0x0602 +#define IMX708_REG_TEST_PATTERN_GR 0x0604 +#define IMX708_REG_TEST_PATTERN_B 0x0606 +#define IMX708_REG_TEST_PATTERN_GB 0x0608 +#define IMX708_TEST_PATTERN_COLOUR_MIN 0 +#define IMX708_TEST_PATTERN_COLOUR_MAX 0x0fff +#define IMX708_TEST_PATTERN_COLOUR_STEP 1 + +#define IMX708_REG_BASE_SPC_GAINS_L 0x7b10 +#define IMX708_REG_BASE_SPC_GAINS_R 0x7c00 + +/* HDR exposure ratio (long:med == med:short) */ +#define IMX708_HDR_EXPOSURE_RATIO 4 +#define IMX708_REG_MID_EXPOSURE 0x3116 +#define IMX708_REG_SHT_EXPOSURE 0x0224 +#define IMX708_REG_MID_ANALOG_GAIN 0x3118 +#define IMX708_REG_SHT_ANALOG_GAIN 0x0216 + +/* QBC Re-mosaic broken line correction registers */ +#define IMX708_LPF_INTENSITY_EN 0xC428 +#define IMX708_LPF_INTENSITY_ENABLED 0x00 +#define IMX708_LPF_INTENSITY_DISABLED 0x01 +#define IMX708_LPF_INTENSITY 0xC429 + +/* + * Metadata buffer holds a variety of data, all sent with the same VC/DT (0x12). + * It comprises two scanlines (of up to 5760 bytes each, for 4608 pixels) + * of embedded data, one line of PDAF data, and two lines of AE-HIST data + * (AE histograms are valid for HDR mode and empty in non-HDR modes). + */ +#define IMX708_EMBEDDED_LINE_WIDTH (5 * 5760) +#define IMX708_NUM_EMBEDDED_LINES 1 + +enum pad_types { + IMAGE_PAD, + METADATA_PAD, + NUM_PADS +}; + +/* IMX708 native and active pixel array size. */ +#define IMX708_NATIVE_WIDTH 4640U +#define IMX708_NATIVE_HEIGHT 2658U +#define IMX708_PIXEL_ARRAY_LEFT 16U +#define IMX708_PIXEL_ARRAY_TOP 24U +#define IMX708_PIXEL_ARRAY_WIDTH 4608U +#define IMX708_PIXEL_ARRAY_HEIGHT 2592U + +struct imx708_reg { + u16 address; + u8 val; +}; + +struct imx708_reg_list { + unsigned int num_of_regs; + const struct imx708_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx708_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Highest possible framerate. */ + unsigned int vblank_min; + + /* Default framerate. */ + unsigned int vblank_default; + + /* Default register values */ + struct imx708_reg_list reg_list; + + /* Not all modes have the same pixel rate. */ + u64 pixel_rate; + + /* Not all modes have the same minimum exposure. */ + u32 exposure_lines_min; + + /* Not all modes have the same exposure lines step. */ + u32 exposure_lines_step; + + /* HDR flag, used for checking if the current mode is HDR */ + bool hdr; + + /* Quad Bayer Re-mosaic flag */ + bool remosaic; +}; + +/* Default PDAF pixel correction gains */ +static const u8 pdaf_gains[2][9] = { + { 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x39, 0x36, 0x36, 0x36 }, + { 0x36, 0x36, 0x36, 0x39, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } +}; + +/* Link frequency setup */ +enum { + IMX708_LINK_FREQ_450MHZ, + IMX708_LINK_FREQ_447MHZ, + IMX708_LINK_FREQ_453MHZ, +}; + +static const s64 link_freqs[] = { + [IMX708_LINK_FREQ_450MHZ] = 450000000, + [IMX708_LINK_FREQ_447MHZ] = 447000000, + [IMX708_LINK_FREQ_453MHZ] = 453000000, +}; + +/* 450MHz is the nominal "default" link frequency */ +static const struct imx708_reg link_450Mhz_regs[] = { + {0x030E, 0x01}, + {0x030F, 0x2c}, +}; + +static const struct imx708_reg link_447Mhz_regs[] = { + {0x030E, 0x01}, + {0x030F, 0x2a}, +}; + +static const struct imx708_reg link_453Mhz_regs[] = { + {0x030E, 0x01}, + {0x030F, 0x2e}, +}; + +static const struct imx708_reg_list link_freq_regs[] = { + [IMX708_LINK_FREQ_450MHZ] = { + .regs = link_450Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_450Mhz_regs) + }, + [IMX708_LINK_FREQ_447MHZ] = { + .regs = link_447Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_447Mhz_regs) + }, + [IMX708_LINK_FREQ_453MHZ] = { + .regs = link_453Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_453Mhz_regs) + }, +}; + +static const struct imx708_reg mode_common_regs[] = { + {0x0100, 0x00}, + {0x0136, 0x18}, + {0x0137, 0x00}, + {0x33F0, 0x02}, + {0x33F1, 0x05}, + {0x3062, 0x00}, + {0x3063, 0x12}, + {0x3068, 0x00}, + {0x3069, 0x12}, + {0x306A, 0x00}, + {0x306B, 0x30}, + {0x3076, 0x00}, + {0x3077, 0x30}, + {0x3078, 0x00}, + {0x3079, 0x30}, + {0x5E54, 0x0C}, + {0x6E44, 0x00}, + {0xB0B6, 0x01}, + {0xE829, 0x00}, + {0xF001, 0x08}, + {0xF003, 0x08}, + {0xF00D, 0x10}, + {0xF00F, 0x10}, + {0xF031, 0x08}, + {0xF033, 0x08}, + {0xF03D, 0x10}, + {0xF03F, 0x10}, + {0x0112, 0x0A}, + {0x0113, 0x0A}, + {0x0114, 0x01}, + {0x0B8E, 0x01}, + {0x0B8F, 0x00}, + {0x0B94, 0x01}, + {0x0B95, 0x00}, + {0x3400, 0x01}, + {0x3478, 0x01}, + {0x3479, 0x1c}, + {0x3091, 0x01}, + {0x3092, 0x00}, + {0x3419, 0x00}, + {0xBCF1, 0x02}, + {0x3094, 0x01}, + {0x3095, 0x01}, + {0x3362, 0x00}, + {0x3363, 0x00}, + {0x3364, 0x00}, + {0x3365, 0x00}, + {0x0138, 0x01}, +}; + +/* 10-bit. */ +static const struct imx708_reg mode_4608x2592_regs[] = { + {0x0342, 0x3D}, + {0x0343, 0x20}, + {0x0340, 0x0A}, + {0x0341, 0x59}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0A}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x32D5, 0x01}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x12}, + {0x040D, 0x00}, + {0x040E, 0x0A}, + {0x040F, 0x20}, + {0x034C, 0x12}, + {0x034D, 0x00}, + {0x034E, 0x0A}, + {0x034F, 0x20}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x7C}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x64}, + {0x3CA4, 0x00}, + {0x3CA5, 0x00}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x08}, + {0x3CBA, 0x00}, + {0x3CBB, 0x00}, + {0x3CBC, 0x00}, + {0x3CBD, 0x3C}, + {0x3CBE, 0x00}, + {0x3CBF, 0x00}, + {0x0202, 0x0A}, + {0x0203, 0x29}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x00}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x01}, + {0x341f, 0x20}, + {0x3420, 0x00}, + {0x3421, 0xd8}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_2x2binned_regs[] = { + {0x0342, 0x1E}, + {0x0343, 0x90}, + {0x0340, 0x05}, + {0x0341, 0x38}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x09}, + {0x040D, 0x00}, + {0x040E, 0x05}, + {0x040F, 0x10}, + {0x034C, 0x09}, + {0x034D, 0x00}, + {0x034E, 0x05}, + {0x034F, 0x10}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x7A}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x3C}, + {0x3CA4, 0x00}, + {0x3CA5, 0x3C}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x1C}, + {0x3CBA, 0x00}, + {0x3CBB, 0x08}, + {0x3CBC, 0x00}, + {0x3CBD, 0x1E}, + {0x3CBE, 0x00}, + {0x3CBF, 0x0A}, + {0x0202, 0x05}, + {0x0203, 0x08}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x70}, + {0x0216, 0x00}, + {0x0217, 0x70}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x70}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x90}, + {0x3420, 0x00}, + {0x3421, 0x6c}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_2x2binned_720p_regs[] = { + {0x0342, 0x14}, + {0x0343, 0x60}, + {0x0340, 0x04}, + {0x0341, 0xB6}, + {0x0344, 0x03}, + {0x0345, 0x00}, + {0x0346, 0x01}, + {0x0347, 0xB0}, + {0x0348, 0x0E}, + {0x0349, 0xFF}, + {0x034A, 0x08}, + {0x034B, 0x6F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x01}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x06}, + {0x040D, 0x00}, + {0x040E, 0x03}, + {0x040F, 0x60}, + {0x034C, 0x06}, + {0x034D, 0x00}, + {0x034E, 0x03}, + {0x034F, 0x60}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x76}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x3C}, + {0x3CA4, 0x01}, + {0x3CA5, 0x5E}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x0C}, + {0x3CBA, 0x00}, + {0x3CBB, 0x04}, + {0x3CBC, 0x00}, + {0x3CBD, 0x1E}, + {0x3CBE, 0x00}, + {0x3CBF, 0x05}, + {0x0202, 0x04}, + {0x0203, 0x86}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x70}, + {0x0216, 0x00}, + {0x0217, 0x70}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x70}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x60}, + {0x3420, 0x00}, + {0x3421, 0x48}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_hdr_regs[] = { + {0x0342, 0x14}, + {0x0343, 0x60}, + {0x0340, 0x0A}, + {0x0341, 0x5B}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x01}, + {0x0222, IMX708_HDR_EXPOSURE_RATIO}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0A}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x09}, + {0x040D, 0x00}, + {0x040E, 0x05}, + {0x040F, 0x10}, + {0x034C, 0x09}, + {0x034D, 0x00}, + {0x034E, 0x05}, + {0x034F, 0x10}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0xA2}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x00}, + {0x3CA4, 0x00}, + {0x3CA5, 0x00}, + {0x3CA6, 0x00}, + {0x3CA7, 0x28}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x30}, + {0x3CBA, 0x00}, + {0x3CBB, 0x00}, + {0x3CBC, 0x00}, + {0x3CBD, 0x32}, + {0x3CBE, 0x00}, + {0x3CBF, 0x00}, + {0x0202, 0x0A}, + {0x0203, 0x2B}, + {0x0224, 0x0A}, + {0x0225, 0x2B}, + {0x3116, 0x0A}, + {0x3117, 0x2B}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x00}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x90}, + {0x3420, 0x00}, + {0x3421, 0x6c}, + {0x3360, 0x01}, + {0x3361, 0x01}, + {0x3366, 0x09}, + {0x3367, 0x00}, + {0x3368, 0x05}, + {0x3369, 0x10}, +}; + +/* Mode configs. Keep separate lists for when HDR is enabled or not. */ +static const struct imx708_mode supported_modes_10bit_no_hdr[] = { + { + /* Full resolution. */ + .width = 4608, + .height = 2592, + .line_length_pix = 0x3d20, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 58, + .vblank_default = 58, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4608x2592_regs), + .regs = mode_4608x2592_regs, + }, + .pixel_rate = 595200000, + .exposure_lines_min = 8, + .exposure_lines_step = 1, + .hdr = false, + .remosaic = true + }, + { + /* regular 2x2 binned. */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1e90, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 40, + .vblank_default = 1198, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_regs), + .regs = mode_2x2binned_regs, + }, + .pixel_rate = 585600000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false, + .remosaic = false + }, + { + /* 2x2 binned and cropped for 720p. */ + .width = 1536, + .height = 864, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT + 768, + .top = IMX708_PIXEL_ARRAY_TOP + 432, + .width = 3072, + .height = 1728, + }, + .vblank_min = 40, + .vblank_default = 2755, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_720p_regs), + .regs = mode_2x2binned_720p_regs, + }, + .pixel_rate = 566400000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false, + .remosaic = false + }, +}; + +static const struct imx708_mode supported_modes_10bit_hdr[] = { + { + /* There's only one HDR mode, which is 2x2 downscaled */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 3673, + .vblank_default = 3673, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_hdr_regs), + .regs = mode_hdr_regs, + }, + .pixel_rate = 777600000, + .exposure_lines_min = 8 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, + .exposure_lines_step = 2 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, + .hdr = true, + .remosaic = false + } +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx708_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx708_test_pattern_val[] = { + IMX708_TEST_PATTERN_DISABLE, + IMX708_TEST_PATTERN_COLOR_BARS, + IMX708_TEST_PATTERN_SOLID_COLOR, + IMX708_TEST_PATTERN_GREY_COLOR, + IMX708_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx708_supply_name[] = { + /* Supplies can be enabled in any order */ + "vana1", /* Analog1 (2.8V) supply */ + "vana2", /* Analog2 (1.8V) supply */ + "vdig", /* Digital Core (1.1V) supply */ + "vddl", /* IF (1.8V) supply */ +}; + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 8ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 600us. + */ +#define IMX708_XCLR_MIN_DELAY_US 8000 +#define IMX708_XCLR_DELAY_RANGE_US 1000 + +struct imx708 { + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + + struct v4l2_mbus_framefmt fmt; + + struct clk *inclk; + u32 inclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(imx708_supply_name)]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *hdr_mode; + struct v4l2_ctrl *link_freq; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + + /* Current mode */ + const struct imx708_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; + + unsigned int link_freq_idx; +}; + +static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx708, sd); +} + +static inline void get_mode_table(unsigned int code, + const struct imx708_mode **mode_list, + unsigned int *num_modes, + bool hdr_enable) +{ + switch (code) { + /* 10-bit */ + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + if (hdr_enable) { + *mode_list = supported_modes_10bit_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_hdr); + } else { + *mode_list = supported_modes_10bit_no_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_no_hdr); + } + break; + default: + *mode_list = NULL; + *num_modes = 0; + } +} + +/* Read registers up to 2 at a time */ +static int imx708_read_reg(struct imx708 *imx708, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int imx708_write_reg(struct imx708 *imx708, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx708_write_regs(struct imx708 *imx708, + const struct imx708_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + unsigned int i; + + for (i = 0; i < len; i++) { + int ret; + + ret = imx708_write_reg(imx708, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx708_get_format_code(struct imx708 *imx708) +{ + unsigned int i; + + lockdep_assert_held(&imx708->mutex); + + i = (imx708->vflip->val ? 2 : 0) | + (imx708->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx708_set_default_format(struct imx708 *imx708) +{ + struct v4l2_mbus_framefmt *fmt = &imx708->fmt; + + /* Set default mode to max resolution */ + imx708->mode = &supported_modes_10bit_no_hdr[0]; + + /* fmt->code not set as it will always be computed based on flips */ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->width = imx708->mode->width; + fmt->height = imx708->mode->height; + fmt->field = V4L2_FIELD_NONE; +} + +static int imx708_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx708 *imx708 = to_imx708(sd); + struct v4l2_mbus_framefmt *try_fmt_img = + v4l2_subdev_state_get_format(fh->state, IMAGE_PAD); + struct v4l2_mbus_framefmt *try_fmt_meta = + v4l2_subdev_state_get_format(fh->state, METADATA_PAD); + struct v4l2_rect *try_crop; + + mutex_lock(&imx708->mutex); + + /* Initialize try_fmt for the image pad */ + if (imx708->hdr_mode->val) { + try_fmt_img->width = supported_modes_10bit_hdr[0].width; + try_fmt_img->height = supported_modes_10bit_hdr[0].height; + } else { + try_fmt_img->width = supported_modes_10bit_no_hdr[0].width; + try_fmt_img->height = supported_modes_10bit_no_hdr[0].height; + } + try_fmt_img->code = imx708_get_format_code(imx708); + try_fmt_img->field = V4L2_FIELD_NONE; + + /* Initialize try_fmt for the embedded metadata pad */ + try_fmt_meta->width = IMX708_EMBEDDED_LINE_WIDTH; + try_fmt_meta->height = IMX708_NUM_EMBEDDED_LINES; + try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; + try_fmt_meta->field = V4L2_FIELD_NONE; + + /* Initialize try_crop */ + try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD); + try_crop->left = IMX708_PIXEL_ARRAY_LEFT; + try_crop->top = IMX708_PIXEL_ARRAY_TOP; + try_crop->width = IMX708_PIXEL_ARRAY_WIDTH; + try_crop->height = IMX708_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&imx708->mutex); + + return 0; +} + +static int imx708_set_exposure(struct imx708 *imx708, unsigned int val) +{ + val = max(val, imx708->mode->exposure_lines_min); + val -= val % imx708->mode->exposure_lines_step; + + /* + * In HDR mode this will set the longest exposure. The sensor + * will automatically divide the medium and short ones by 4,16. + */ + return imx708_write_reg(imx708, IMX708_REG_EXPOSURE, + IMX708_REG_VALUE_16BIT, + val >> imx708->long_exp_shift); +} + +static void imx708_adjust_exposure_range(struct imx708 *imx708, + struct v4l2_ctrl *ctrl) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx708->mode->height + imx708->vblank->val - + IMX708_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx708->exposure->val); + __v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum, + exposure_max, imx708->exposure->step, + exposure_def); +} + +static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val) +{ + int ret; + + /* + * In HDR mode this will set the gain for the longest exposure, + * and by default the sensor uses the same gain for all of them. + */ + ret = imx708_write_reg(imx708, IMX708_REG_ANALOG_GAIN, + IMX708_REG_VALUE_16BIT, val); + + return ret; +} + +static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) +{ + int ret; + + imx708->long_exp_shift = 0; + + while (val > IMX708_FRAME_LENGTH_MAX) { + imx708->long_exp_shift++; + val >>= 1; + } + + ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, + IMX708_REG_VALUE_16BIT, val); + if (ret) + return ret; + + return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG, + IMX708_REG_VALUE_08BIT, imx708->long_exp_shift); +} + +static void imx708_set_framing_limits(struct imx708 *imx708) +{ + const struct imx708_mode *mode = imx708->mode; + unsigned int hblank; + + __v4l2_ctrl_modify_range(imx708->pixel_rate, + mode->pixel_rate, mode->pixel_rate, + 1, mode->pixel_rate); + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min, + ((1 << IMX708_LONG_EXP_SHIFT_MAX) * + IMX708_FRAME_LENGTH_MAX) - mode->height, + 1, mode->vblank_default); + + /* + * Currently PPL is fixed to the mode specified value, so hblank + * depends on mode->width only, and is not changeable in any + * way other than changing the mode. + */ + hblank = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx708->hblank, hblank, hblank, 1, hblank); +} + +static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx708 *imx708 = + container_of(ctrl->handler, struct imx708, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + const struct imx708_mode *mode_list; + unsigned int code, num_modes; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* + * The VBLANK control may change the limits of usable exposure, + * so check and adjust if necessary. + */ + imx708_adjust_exposure_range(imx708, ctrl); + break; + + case V4L2_CID_WIDE_DYNAMIC_RANGE: + /* + * The WIDE_DYNAMIC_RANGE control can also be applied immediately + * as it doesn't set any registers. Don't do anything if the mode + * already matches. + */ + if (imx708->mode && imx708->mode->hdr != ctrl->val) { + code = imx708_get_format_code(imx708); + get_mode_table(code, &mode_list, &num_modes, ctrl->val); + imx708->mode = v4l2_find_nearest_size(mode_list, + num_modes, + width, height, + imx708->mode->width, + imx708->mode->height); + imx708_set_framing_limits(imx708); + } + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + imx708_set_analogue_gain(imx708, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx708_set_exposure(imx708, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx708_write_reg(imx708, IMX708_REG_DIGITAL_GAIN, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN, + IMX708_REG_VALUE_16BIT, + imx708_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, 1, + imx708->hflip->val | + imx708->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx708_set_frame_length(imx708, + imx708->mode->height + ctrl->val); + break; + case V4L2_CID_NOTIFY_GAINS: + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, + IMX708_REG_VALUE_16BIT, + ctrl->p_new.p_u32[0]); + if (ret) + break; + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, + IMX708_REG_VALUE_16BIT, + ctrl->p_new.p_u32[3]); + break; + case V4L2_CID_WIDE_DYNAMIC_RANGE: + /* Already handled above. */ + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx708_ctrl_ops = { + .s_ctrl = imx708_set_ctrl, +}; + +static int imx708_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index >= (ARRAY_SIZE(codes) / 4)) + return -EINVAL; + + mutex_lock(&imx708->mutex); + code->code = imx708_get_format_code(imx708); + mutex_unlock(&imx708->mutex); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int imx708_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx708 *imx708 = to_imx708(sd); + u32 code; + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + const struct imx708_mode *mode_list; + unsigned int num_modes; + + get_mode_table(fse->code, &mode_list, &num_modes, + imx708->hdr_mode->val); + + if (fse->index >= num_modes) + return -EINVAL; + + mutex_lock(&imx708->mutex); + code = imx708_get_format_code(imx708); + mutex_unlock(&imx708->mutex); + + if (fse->code != code) + return -EINVAL; + + fse->min_width = mode_list[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = mode_list[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = IMX708_EMBEDDED_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = IMX708_NUM_EMBEDDED_LINES; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void imx708_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void imx708_update_image_pad_format(struct imx708 *imx708, + const struct imx708_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + imx708_reset_colorspace(&fmt->format); +} + +static void imx708_update_metadata_pad_format(struct v4l2_subdev_format *fmt) +{ + fmt->format.width = IMX708_EMBEDDED_LINE_WIDTH; + fmt->format.height = IMX708_NUM_EMBEDDED_LINES; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx708_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx708->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(sd_state, + fmt->pad); + /* update the code which could change due to vflip or hflip */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + imx708_get_format_code(imx708) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + imx708_update_image_pad_format(imx708, imx708->mode, + fmt); + fmt->format.code = imx708_get_format_code(imx708); + } else { + imx708_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx708->mutex); + return 0; +} + +static int imx708_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct imx708_mode *mode; + struct imx708 *imx708 = to_imx708(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx708->mutex); + + if (fmt->pad == IMAGE_PAD) { + const struct imx708_mode *mode_list; + unsigned int num_modes; + + /* Bayer order varies with flips */ + fmt->format.code = imx708_get_format_code(imx708); + + get_mode_table(fmt->format.code, &mode_list, &num_modes, + imx708->hdr_mode->val); + + mode = v4l2_find_nearest_size(mode_list, + num_modes, + width, height, + fmt->format.width, + fmt->format.height); + imx708_update_image_pad_format(imx708, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + imx708->mode = mode; + imx708_set_framing_limits(imx708); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + imx708_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx708->mutex); + + return 0; +} + +static const struct v4l2_rect * +__imx708_get_pad_crop(struct imx708 *imx708, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx708->mode->crop; + } + + return NULL; +} + +static int imx708_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx708 *imx708 = to_imx708(sd); + + mutex_lock(&imx708->mutex); + sel->r = *__imx708_get_pad_crop(imx708, sd_state, sel->pad, + sel->which); + mutex_unlock(&imx708->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX708_NATIVE_WIDTH; + sel->r.height = IMX708_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX708_PIXEL_ARRAY_LEFT; + sel->r.top = IMX708_PIXEL_ARRAY_TOP; + sel->r.width = IMX708_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX708_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +/* Start streaming */ +static int imx708_start_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + const struct imx708_reg_list *reg_list, *freq_regs; + int i, ret; + u32 val; + + if (!imx708->common_regs_written) { + ret = imx708_write_regs(imx708, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + + ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L, + IMX708_REG_VALUE_08BIT, &val); + if (ret == 0 && val == 0x40) { + for (i = 0; i < 54 && ret == 0; i++) { + ret = imx708_write_reg(imx708, + IMX708_REG_BASE_SPC_GAINS_L + i, + IMX708_REG_VALUE_08BIT, + pdaf_gains[0][i % 9]); + } + for (i = 0; i < 54 && ret == 0; i++) { + ret = imx708_write_reg(imx708, + IMX708_REG_BASE_SPC_GAINS_R + i, + IMX708_REG_VALUE_08BIT, + pdaf_gains[1][i % 9]); + } + } + if (ret) { + dev_err(&client->dev, "%s failed to set PDAF gains\n", + __func__); + return ret; + } + + imx708->common_regs_written = true; + } + + /* Apply default values of current mode */ + reg_list = &imx708->mode->reg_list; + ret = imx708_write_regs(imx708, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Update the link frequency registers */ + freq_regs = &link_freq_regs[imx708->link_freq_idx]; + ret = imx708_write_regs(imx708, freq_regs->regs, + freq_regs->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set link frequency registers\n", + __func__); + return ret; + } + + /* Quad Bayer re-mosaic adjustments (for full-resolution mode only) */ + if (imx708->mode->remosaic && qbc_adjust > 0) { + imx708_write_reg(imx708, IMX708_LPF_INTENSITY, + IMX708_REG_VALUE_08BIT, qbc_adjust); + imx708_write_reg(imx708, + IMX708_LPF_INTENSITY_EN, + IMX708_REG_VALUE_08BIT, + IMX708_LPF_INTENSITY_ENABLED); + } else { + imx708_write_reg(imx708, + IMX708_LPF_INTENSITY_EN, + IMX708_REG_VALUE_08BIT, + IMX708_LPF_INTENSITY_DISABLED); + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_REG_VALUE_08BIT, IMX708_MODE_STREAMING); +} + +/* Stop streaming */ +static void imx708_stop_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + + /* set stream off register */ + ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_REG_VALUE_08BIT, IMX708_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); +} + +static int imx708_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx708 *imx708 = to_imx708(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx708->mutex); + if (imx708->streaming == enable) { + mutex_unlock(&imx708->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto err_unlock; + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx708_start_streaming(imx708); + if (ret) { + pm_runtime_put_sync(&client->dev); + goto err_unlock; + } + } else { + imx708_stop_streaming(imx708); + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + } + + imx708->streaming = enable; + + /* vflip/hflip and hdr mode cannot change during streaming */ + __v4l2_ctrl_grab(imx708->vflip, enable); + __v4l2_ctrl_grab(imx708->hflip, enable); + __v4l2_ctrl_grab(imx708->hdr_mode, enable); + + mutex_unlock(&imx708->mutex); + + return ret; + +err_unlock: + mutex_unlock(&imx708->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx708_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx708->inclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(imx708->reset_gpio, 1); + usleep_range(IMX708_XCLR_MIN_DELAY_US, + IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + return ret; +} + +static int imx708_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + gpiod_set_value_cansleep(imx708->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + clk_disable_unprepare(imx708->inclk); + + /* Force reprogramming of the common registers when powered up again. */ + imx708->common_regs_written = false; + + return 0; +} + +static int __maybe_unused imx708_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + if (imx708->streaming) + imx708_stop_streaming(imx708); + + return 0; +} + +static int __maybe_unused imx708_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + if (imx708->streaming) { + ret = imx708_start_streaming(imx708); + if (ret) + goto error; + } + + return 0; + +error: + imx708_stop_streaming(imx708); + imx708->streaming = 0; + return ret; +} + +static int imx708_get_regulators(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(imx708_supply_name); i++) + imx708->supplies[i].supply = imx708_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(imx708_supply_name), + imx708->supplies); +} + +/* Verify chip ID */ +static int imx708_identify_module(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + u32 val; + + ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID, + IMX708_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x, with error %d\n", + IMX708_CHIP_ID, ret); + return ret; + } + + if (val != IMX708_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX708_CHIP_ID, val); + return -EIO; + } + + ret = imx708_read_reg(imx708, 0x0000, IMX708_REG_VALUE_16BIT, &val); + if (!ret) { + dev_info(&client->dev, "camera module ID 0x%04x\n", val); + snprintf(imx708->sd.name, sizeof(imx708->sd.name), "imx708%s%s", + val & 0x02 ? "_wide" : "", + val & 0x80 ? "_noir" : ""); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops imx708_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx708_video_ops = { + .s_stream = imx708_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx708_pad_ops = { + .enum_mbus_code = imx708_enum_mbus_code, + .get_fmt = imx708_get_pad_format, + .set_fmt = imx708_set_pad_format, + .get_selection = imx708_get_selection, + .enum_frame_size = imx708_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx708_subdev_ops = { + .core = &imx708_core_ops, + .video = &imx708_video_ops, + .pad = &imx708_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx708_internal_ops = { + .open = imx708_open, +}; + +static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { + .ops = &imx708_ctrl_ops, + .id = V4L2_CID_NOTIFY_GAINS, + .type = V4L2_CTRL_TYPE_U32, + .min = IMX708_COLOUR_BALANCE_MIN, + .max = IMX708_COLOUR_BALANCE_MAX, + .step = IMX708_COLOUR_BALANCE_STEP, + .def = IMX708_COLOUR_BALANCE_DEFAULT, + .dims = { 4 }, + .elem_size = sizeof(u32), +}; + +/* Initialize control handlers */ +static int imx708_init_controls(struct imx708 *imx708) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *ctrl; + unsigned int i; + int ret; + + ctrl_hdlr = &imx708->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&imx708->mutex); + ctrl_hdlr->lock = &imx708->mutex; + + /* By default, PIXEL_RATE is read only */ + imx708->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_PIXEL_RATE, + IMX708_INITIAL_PIXEL_RATE, + IMX708_INITIAL_PIXEL_RATE, 1, + IMX708_INITIAL_PIXEL_RATE); + + ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_LINK_FREQ, 0, 0, + &link_freqs[imx708->link_freq_idx]); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the imx708_set_framing_limits() call below. + */ + imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VBLANK, 0, 0xffff, 1, 0); + imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX708_EXPOSURE_MIN, + IMX708_EXPOSURE_MAX, + IMX708_EXPOSURE_STEP, + IMX708_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX, + IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX, + IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT); + + imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &imx708->hflip); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx708_test_pattern_menu) - 1, + 0, 0, imx708_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX708_TEST_PATTERN_COLOUR_MIN, + IMX708_TEST_PATTERN_COLOUR_MAX, + IMX708_TEST_PATTERN_COLOUR_STEP, + IMX708_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + v4l2_ctrl_new_custom(ctrl_hdlr, &imx708_notify_gains_ctrl, NULL); + + imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, + 0, 1, 1, 0); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->hdr_mode->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx708->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + mutex_lock(&imx708->mutex); + imx708_set_framing_limits(imx708); + mutex_unlock(&imx708->mutex); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx708->mutex); + + return ret; +} + +static void imx708_free_controls(struct imx708 *imx708) +{ + v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); + mutex_destroy(&imx708->mutex); +} + +static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + int i; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + for (i = 0; i < ARRAY_SIZE(link_freqs); i++) { + if (link_freqs[i] == ep_cfg.link_frequencies[0]) { + imx708->link_freq_idx = i; + break; + } + } + + if (i == ARRAY_SIZE(link_freqs)) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + ret = -EINVAL; + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int imx708_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx708 *imx708; + int ret; + + imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL); + if (!imx708) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx708_check_hwcfg(dev, imx708)) + return -EINVAL; + + /* Get system clock (inclk) */ + imx708->inclk = devm_clk_get(dev, "inclk"); + if (IS_ERR(imx708->inclk)) + return dev_err_probe(dev, PTR_ERR(imx708->inclk), + "failed to get inclk\n"); + + imx708->inclk_freq = clk_get_rate(imx708->inclk); + if (imx708->inclk_freq != IMX708_INCLK_FREQ) + return dev_err_probe(dev, -EINVAL, + "inclk frequency not supported: %d Hz\n", + imx708->inclk_freq); + + ret = imx708_get_regulators(imx708); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + /* Request optional enable pin */ + imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx708_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx708_power_on(dev); + if (ret) + return ret; + + ret = imx708_identify_module(imx708); + if (ret) + goto error_power_off; + + /* Initialize default format */ + imx708_set_default_format(imx708); + + /* + * Enable runtime PM with autosuspend. As the device has been powered + * manually, mark it as active, and increase the usage count without + * resuming the device. + */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 5000); + pm_runtime_use_autosuspend(dev); + + /* This needs the pm runtime to be registered. */ + ret = imx708_init_controls(imx708); + if (ret) + goto error_pm_runtime; + + /* Initialize subdev */ + imx708->sd.internal_ops = &imx708_internal_ops; + imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + imx708->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + imx708->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx708->sd.entity, NUM_PADS, imx708->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx708->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx708->sd.entity); + +error_handler_free: + imx708_free_controls(imx708); + +error_pm_runtime: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + +error_power_off: + imx708_power_off(&client->dev); + + return ret; +} + +static void imx708_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx708_free_controls(imx708); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx708_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id imx708_dt_ids[] = { + { .compatible = "sony,imx708" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx708_dt_ids); + +static const struct dev_pm_ops imx708_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume) + SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) +}; + +static struct i2c_driver imx708_i2c_driver = { + .driver = { + .name = "imx708", + .of_match_table = imx708_dt_ids, + .pm = &imx708_pm_ops, + }, + .probe = imx708_probe, + .remove = imx708_remove, +}; + +module_i2c_driver(imx708_i2c_driver); + +MODULE_AUTHOR("David Plowman "); +MODULE_DESCRIPTION("Sony IMX708 sensor driver"); +MODULE_LICENSE("GPL v2"); From 7326b4763dcbdbc755ae69eab7d7f41bcbcce58f Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 22 Apr 2026 13:33:25 +0100 Subject: [PATCH 03/17] media: imx708: Support configuring continuous clock from DT The driver was always using non-continuous clock mode. Add support for selecting continuous clock mode as well. Signed-off-by: Dave Stevenson --- drivers/media/i2c/imx708.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c index 6ab38e6a30e56..23af2f3f08bfa 100644 --- a/drivers/media/i2c/imx708.c +++ b/drivers/media/i2c/imx708.c @@ -112,6 +112,9 @@ MODULE_PARM_DESC(qbc_adjust, "Quad Bayer broken line correction strength [0,2-5] #define IMX708_REG_MID_ANALOG_GAIN 0x3118 #define IMX708_REG_SHT_ANALOG_GAIN 0x0216 +#define IMX708_REG_CLKLANE_BLANK 0x3220 +#define IMX708_CLKLANE_BLANK_NONCONT BIT(0) + /* QBC Re-mosaic broken line correction registers */ #define IMX708_LPF_INTENSITY_EN 0xC428 #define IMX708_LPF_INTENSITY_ENABLED 0x00 @@ -866,6 +869,8 @@ struct imx708 { unsigned int long_exp_shift; unsigned int link_freq_idx; + + unsigned int csi_flags; }; static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) @@ -1499,6 +1504,16 @@ static int imx708_start_streaming(struct imx708 *imx708) return ret; } + ret = imx708_write_reg(imx708, IMX708_REG_CLKLANE_BLANK, + IMX708_REG_VALUE_08BIT, + imx708->csi_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK ? + IMX708_CLKLANE_BLANK_NONCONT : 0); + if (ret) { + dev_err(&client->dev, "%s failed to set clock lane mode\n", + __func__); + return ret; + } + ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L, IMX708_REG_VALUE_08BIT, &val); if (ret == 0 && val == 0x40) { @@ -1972,6 +1987,8 @@ static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708) goto error_out; } + imx708->csi_flags = ep_cfg.bus.mipi_csi2.flags; + ret = 0; error_out: From cbfb8d5f9dde26d3f815e34b68158cf56a0a851b Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Tue, 21 Jan 2020 14:06:47 +0000 Subject: [PATCH 04/17] media: uapi: Add MEDIA_BUS_FMT_SENSOR_DATA media bus format This patch adds MEDIA_BUS_FMT_SENSOR_DATA used by the bcm2835-unicam driver to support CSI-2 embedded data streams from camera sensors. Signed-off-by: Naushir Patuck --- .../media/v4l/subdev-formats.rst | 30 +++++++++++++++++++ include/uapi/linux/media-bus-format.h | 3 ++ 2 files changed, 33 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index b716b51f849d3..165d0a3bf4fed 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -8824,3 +8824,33 @@ and finally the bit number in subscript. "x" indicates a padding bit. - x - x - x + +.. _v4l2-mbus-sensor-data: + +Sensor Ancillary Metadata Formats +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This section lists ancillary data generated by a camera sensor and +transmitted over a stream on the camera bus. + +The following table lists the existing sensor ancillary metadata formats: + + +.. _v4l2-mbus-pixelcode-sensor-metadata: + +.. tabularcolumns:: |p{8.0cm}|p{1.4cm}|p{7.7cm}| + +.. flat-table:: Sensor ancillary metadata formats + :header-rows: 1 + :stub-columns: 0 + + * - Identifier + - Code + - Comments + * .. _MEDIA_BUS_FMT_SENSOR_DATA: + + - MEDIA_BUS_FMT_SENSOR_DATA + - 0x7001 + - Sensor vendor specific ancillary metadata. Some vendors follow a generic + CSI-2/SMIA embedded data format as described in the `CSI-2 specification. + `_ diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h index ff62056feed5b..b434cb9c2b2cd 100644 --- a/include/uapi/linux/media-bus-format.h +++ b/include/uapi/linux/media-bus-format.h @@ -185,4 +185,7 @@ #define MEDIA_BUS_FMT_META_20 0x8006 #define MEDIA_BUS_FMT_META_24 0x8007 +/* Sensor ancillary metadata formats - next is 0x7002 */ +#define MEDIA_BUS_FMT_SENSOR_DATA 0x7002 + #endif /* __LINUX_MEDIA_BUS_FORMAT_H */ From 22359daa560dded3dd38cd451d55b5009d5001a4 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 3 Jan 2023 15:38:08 +0000 Subject: [PATCH 05/17] media: dw9807-vcm: Add support for DW9817 bidirectional VCM driver The DW9817 is effectively the same as DW9807 from a programming interface, however it drives +/-100mA instead of 0-100mA. This means that the power on ramp needs to take the lens from the midpoint, and power off return it there. It also changes the default position for the module. Signed-off-by: Dave Stevenson --- drivers/media/i2c/dw9807-vcm.c | 113 +++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 27 deletions(-) diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c index 4148009e0e017..4e62e092e9c83 100644 --- a/drivers/media/i2c/dw9807-vcm.c +++ b/drivers/media/i2c/dw9807-vcm.c @@ -1,6 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018 Intel Corporation +/* + * DW9807 is a 10-bit DAC driver, capable of sinking up to 100mA. + * + * DW9817 is a bidirectional 10-bit driver, driving up to +/- 100mA. + * Operationally it is identical to DW9807, except that the idle position is + * the mid-point, not 0. + */ + #include #include #include @@ -38,10 +46,16 @@ #define MAX_RETRY 10 +struct dw9807_cfg { + unsigned int idle_pos; + unsigned int default_pos; +}; + struct dw9807_device { struct v4l2_ctrl_handler ctrls_vcm; struct v4l2_subdev sd; u16 current_val; + u16 idle_pos; }; static inline struct dw9807_device *sd_to_dw9807_vcm( @@ -109,6 +123,40 @@ static int dw9807_set_dac(struct i2c_client *client, u16 data) return 0; } +/* + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. In all cases, even when "start" and + * "end" are the same, the lens will be set to the "end" position. + * + * (We don't use hardware slew rate control, because it differs widely + * between otherwise-compatible ICs, and may need lens-specific tuning.) + */ +static int dw9807_ramp(struct i2c_client *client, int start, int end) +{ + int step, val, ret; + + if (start < end) + step = DW9807_CTRL_STEPS; + else + step = -DW9807_CTRL_STEPS; + + val = start; + while (true) { + val += step; + if (step * (val - end) >= 0) + val = end; + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_ratelimited(&client->dev, "%s I2C failure: %d", + __func__, ret); + if (val == end) + break; + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + return ret; +} + static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) { struct dw9807_device *dev_vcm = container_of(ctrl->handler, @@ -118,7 +166,7 @@ static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); dev_vcm->current_val = ctrl->val; - return dw9807_set_dac(client, ctrl->val); + return dw9807_ramp(client, ctrl->val, ctrl->val); } return -EINVAL; @@ -163,7 +211,8 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm) v4l2_ctrl_handler_init(hdl, 1); v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, - 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0); + 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, + dev_vcm->current_val); dev_vcm->sd.ctrl_handler = hdl; if (hdl->error) { @@ -175,9 +224,31 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm) return 0; } +/* Compatible devices; in fact there are many similar chips. + * "data" holds the powered-off (zero current) lens position and a + * default/initial control value (which need not be the same as the powered-off + * value). + */ +static const struct dw9807_cfg dw9807_cfg = { + .idle_pos = 0, + .default_pos = 0 +}; + +static const struct dw9807_cfg dw9817_cfg = { + .idle_pos = 512, + .default_pos = 480, +}; + +static const struct of_device_id dw9807_of_table[] = { + { .compatible = "dongwoon,dw9807-vcm", .data = &dw9807_cfg }, + { .compatible = "dongwoon,dw9817-vcm", .data = &dw9817_cfg }, + { /* sentinel */ } +}; + static int dw9807_probe(struct i2c_client *client) { struct dw9807_device *dw9807_dev; + const struct dw9807_cfg *cfg; int rval; dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev), @@ -185,6 +256,12 @@ static int dw9807_probe(struct i2c_client *client) if (dw9807_dev == NULL) return -ENOMEM; + cfg = (const struct dw9807_cfg *)i2c_get_match_data(client); + if (cfg) { + dw9807_dev->idle_pos = cfg->idle_pos; + dw9807_dev->current_val = cfg->default_pos; + } + v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops); dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; dw9807_dev->sd.internal_ops = &dw9807_int_ops; @@ -203,7 +280,8 @@ static int dw9807_probe(struct i2c_client *client) if (rval < 0) goto err_cleanup; - pm_runtime_set_active(&client->dev); + if (!dw9807_dev->vdd) + pm_runtime_set_active(&client->dev); pm_runtime_enable(&client->dev); pm_runtime_idle(&client->dev); @@ -237,15 +315,10 @@ static int __maybe_unused dw9807_vcm_suspend(struct device *dev) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; - int ret, val; + int ret; - for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1); - val >= 0; val -= DW9807_CTRL_STEPS) { - ret = dw9807_set_dac(client, val); - if (ret) - dev_err_once(dev, "%s I2C failure: %d", __func__, ret); - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); - } + if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS) + dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos); /* Power down */ ret = i2c_master_send(client, tx_data, sizeof(tx_data)); @@ -269,7 +342,7 @@ static int __maybe_unused dw9807_vcm_resume(struct device *dev) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; - int ret, val; + int ret; /* Power on */ ret = i2c_master_send(client, tx_data, sizeof(tx_data)); @@ -278,25 +351,11 @@ static int __maybe_unused dw9807_vcm_resume(struct device *dev) return ret; } - for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS; - val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1; - val += DW9807_CTRL_STEPS) { - ret = dw9807_set_dac(client, val); - if (ret) - dev_err_ratelimited(dev, "%s I2C failure: %d", - __func__, ret); - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); - } + dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val); return 0; } -static const struct of_device_id dw9807_of_table[] = { - { .compatible = "dongwoon,dw9807-vcm" }, - /* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */ - { .compatible = "dongwoon,dw9807" }, - { /* sentinel */ } -}; MODULE_DEVICE_TABLE(of, dw9807_of_table); static const struct dev_pm_ops dw9807_pm_ops = { From 238d9ab64bc634de82fe61a302f5819e722d6113 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 3 Jan 2023 16:41:08 +0000 Subject: [PATCH 06/17] media: dt-bindings: Add regulator to dw9807-vcm The VCM driver will often be controlled via a regulator, therefore add in the relevant DT hooks. Signed-off-by: Dave Stevenson --- .../devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml | 4 ++++ drivers/media/i2c/dw9807-vcm.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml index aae246ca3fcf5..51573d6a18172 100644 --- a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml +++ b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml @@ -21,6 +21,10 @@ properties: reg: maxItems: 1 + VDD-supply: + description: + Definition of the regulator used as VDD power supply to the driver. + required: - compatible - reg diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c index 4e62e092e9c83..3ecdbee66b605 100644 --- a/drivers/media/i2c/dw9807-vcm.c +++ b/drivers/media/i2c/dw9807-vcm.c @@ -299,6 +299,10 @@ static void dw9807_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + if (dw9807_dev->vdd) + regulator_unregister_notifier(dw9807_dev->vdd, + &dw9807_dev->notifier); + pm_runtime_disable(&client->dev); dw9807_subdev_cleanup(dw9807_dev); From 470783275bb21c69d88912c068631834faa2c7a5 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 3 Jan 2023 16:35:59 +0000 Subject: [PATCH 07/17] media: dw9807-vcm: Add regulator support to the driver Uses the regulator notifier framework so that the current focus position will be restored whenever any user of the regulator powers it up. This means that should the VCM and sensor share a common regulator then starting the sensor will automatically restore the default position. If they have independent regulators then it will behave be powered up when the VCM subdev is opened. Signed-off-by: Dave Stevenson --- drivers/media/i2c/dw9807-vcm.c | 113 ++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 23 deletions(-) diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c index 3ecdbee66b605..5e7f5404178e3 100644 --- a/drivers/media/i2c/dw9807-vcm.c +++ b/drivers/media/i2c/dw9807-vcm.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,9 @@ #define MAX_RETRY 10 +#define DW9807_PW_MIN_DELAY_US 100 +#define DW9807_PW_DELAY_RANGE_US 10 + struct dw9807_cfg { unsigned int idle_pos; unsigned int default_pos; @@ -56,6 +60,8 @@ struct dw9807_device { struct v4l2_subdev sd; u16 current_val; u16 idle_pos; + struct regulator *vdd; + struct notifier_block notifier; }; static inline struct dw9807_device *sd_to_dw9807_vcm( @@ -157,6 +163,66 @@ static int dw9807_ramp(struct i2c_client *client, int start, int end) return ret; } +static int dw9807_active(struct dw9807_device *dw9807_dev) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9807_dev->sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; + int ret; + + /* Power on */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + return dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val); +} + +static int dw9807_standby(struct dw9807_device *dw9807_dev) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9807_dev->sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; + int ret; + + if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS) + dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos); + + /* Power down */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + return 0; +} + +static int dw9807_regulator_event(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct dw9807_device *dw9807_dev = + container_of(nb, struct dw9807_device, notifier); + + if (action & REGULATOR_EVENT_ENABLE) { + /* + * Initialisation delay between VDD low->high and the moment + * when the i2c command is available. + * From the datasheet, it should be 10ms + 2ms (max power + * up sequence duration) + */ + usleep_range(DW9807_PW_MIN_DELAY_US, + DW9807_PW_MIN_DELAY_US + + DW9807_PW_DELAY_RANGE_US); + + dw9807_active(dw9807_dev); + } else if (action & REGULATOR_EVENT_PRE_DISABLE) { + dw9807_standby(dw9807_dev); + } + + return 0; +} + static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) { struct dw9807_device *dev_vcm = container_of(ctrl->handler, @@ -256,6 +322,24 @@ static int dw9807_probe(struct i2c_client *client) if (dw9807_dev == NULL) return -ENOMEM; + dw9807_dev->vdd = devm_regulator_get_optional(&client->dev, "VDD"); + if (IS_ERR(dw9807_dev->vdd)) { + if (PTR_ERR(dw9807_dev->vdd) != -ENODEV) + return PTR_ERR(dw9807_dev->vdd); + + dw9807_dev->vdd = NULL; + } else { + dw9807_dev->notifier.notifier_call = dw9807_regulator_event; + + rval = regulator_register_notifier(dw9807_dev->vdd, + &dw9807_dev->notifier); + if (rval) { + dev_err(&client->dev, + "could not register regulator notifier\n"); + return rval; + } + } + cfg = (const struct dw9807_cfg *)i2c_get_match_data(client); if (cfg) { dw9807_dev->idle_pos = cfg->idle_pos; @@ -318,20 +402,11 @@ static int __maybe_unused dw9807_vcm_suspend(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); - const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; - int ret; - - if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS) - dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos); - /* Power down */ - ret = i2c_master_send(client, tx_data, sizeof(tx_data)); - if (ret < 0) { - dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); - return ret; - } + if (dw9807_dev->vdd) + return regulator_disable(dw9807_dev->vdd); - return 0; + return dw9807_standby(dw9807_dev); } /* @@ -345,19 +420,11 @@ static int __maybe_unused dw9807_vcm_resume(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); - const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; - int ret; - - /* Power on */ - ret = i2c_master_send(client, tx_data, sizeof(tx_data)); - if (ret < 0) { - dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); - return ret; - } - dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val); + if (dw9807_dev->vdd) + return regulator_enable(dw9807_dev->vdd); - return 0; + return dw9807_active(dw9807_dev); } MODULE_DEVICE_TABLE(of, dw9807_of_table); From 3e7ed581b872cb118e724254cbe1fb5cd17f32a0 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 3 Jan 2023 16:53:37 +0000 Subject: [PATCH 08/17] media: dw9807-vcm: Smooth the first user movement of the lens The power up/down sequence is already ramped. Extend this to the first user movement as well, as this will generally avoid the "tick" noises due to rapid movements and overshooting. Subsequent movements are generally smaller and so don't cause issues. Signed-off-by: Dave Stevenson --- drivers/media/i2c/dw9807-vcm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c index 5e7f5404178e3..e4966e9b0edd8 100644 --- a/drivers/media/i2c/dw9807-vcm.c +++ b/drivers/media/i2c/dw9807-vcm.c @@ -62,6 +62,7 @@ struct dw9807_device { u16 idle_pos; struct regulator *vdd; struct notifier_block notifier; + bool first; }; static inline struct dw9807_device *sd_to_dw9807_vcm( @@ -176,6 +177,8 @@ static int dw9807_active(struct dw9807_device *dw9807_dev) return ret; } + dw9807_dev->first = true; + return dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val); } @@ -230,9 +233,11 @@ static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) { struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + int start = (dev_vcm->first) ? dev_vcm->current_val : ctrl->val; + dev_vcm->first = false; dev_vcm->current_val = ctrl->val; - return dw9807_ramp(client, ctrl->val, ctrl->val); + return dw9807_ramp(client, start, ctrl->val); } return -EINVAL; From 5958e79587b70ee95d13259c4d46e6de55fc10bc Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:34:05 +0000 Subject: [PATCH 09/17] dt-bindings: eeprom: at24: add belling,bl24c16f compatible Add Belling BL24C16F EEPROM compatible string alongside the existing BL24C16A entry. Signed-off-by: Jiali Chen --- Documentation/devicetree/bindings/eeprom/at24.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/eeprom/at24.yaml b/Documentation/devicetree/bindings/eeprom/at24.yaml index 50af7ccf6e21a..e44f83899839d 100644 --- a/Documentation/devicetree/bindings/eeprom/at24.yaml +++ b/Documentation/devicetree/bindings/eeprom/at24.yaml @@ -124,6 +124,7 @@ properties: - items: - enum: - belling,bl24c16a + - belling,bl24c16f - renesas,r1ex24016 - const: atmel,24c16 - items: From 2783db1e3767e2cd17ca94cf5869efe38335aed9 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:34:18 +0000 Subject: [PATCH 10/17] iio: adc: qcom-spmi-adc5: add ADC7 GPIO1-GPIO4 Add GPIO1 through GPIO4 voltage channel definitions to the ADC7 PMIC channel table. Signed-off-by: Jiali Chen --- drivers/iio/adc/qcom-spmi-adc5.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index af3c2f659f5e9..ed5e419995415 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -574,6 +574,14 @@ static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_DEFAULT) [ADC7_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, SCALE_HW_CALIB_PMIC_THERM_PM7) + [ADC7_GPIO1] = ADC5_CHAN_VOLT("gpio1", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_GPIO2] = ADC5_CHAN_VOLT("gpio2", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_GPIO3] = ADC5_CHAN_VOLT("gpio3", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_GPIO4] = ADC5_CHAN_VOLT("gpio4", 0, + SCALE_HW_CALIB_DEFAULT) [ADC7_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_pu2", 0, SCALE_HW_CALIB_THERM_100K_PU_PM7) [ADC7_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_pu2", 0, From 7236d30baa619f1016716f5f409f4b2f7b83bd97 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:34:30 +0000 Subject: [PATCH 11/17] drm/msm/dsi: enable continuous clock for SC7280 DSI PHY Add set_continuous_clock callback to the 7nm 7280 PHY configuration, enabling continuous DSI clock mode support. Signed-off-by: Jiali Chen --- drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c index c5e1d2016bcca..00ae46848de15 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c @@ -1332,6 +1332,7 @@ const struct msm_dsi_phy_cfg dsi_phy_7nm_7280_cfgs = { .pll_init = dsi_pll_7nm_init, .save_pll_state = dsi_7nm_pll_save_state, .restore_pll_state = dsi_7nm_pll_restore_state, + .set_continuous_clock = dsi_7nm_set_continuous_clock, }, .min_pll_rate = 600000000UL, #ifdef CONFIG_64BIT From a41404be2223da41ffe4efb3e1b2f2f0508b3be2 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:36:10 +0000 Subject: [PATCH 12/17] drm/bridge: tc358762: switch from sync pulse to burst mode Change DSI mode from MIPI_DSI_MODE_VIDEO_SYNC_PULSE to MIPI_DSI_MODE_VIDEO_BURST for improved display compatibility. Signed-off-by: Jiali Chen --- drivers/gpu/drm/bridge/tc358762.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c index 98df3e667d4aa..02f94ce98ca03 100644 --- a/drivers/gpu/drm/bridge/tc358762.c +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -278,7 +278,7 @@ static int tc358762_probe(struct mipi_dsi_device *dsi) /* TODO: Find out how to get dual-lane mode working */ dsi->lanes = 1; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_HSE; ret = tc358762_parse_dt(ctx); From d69eb6b5df9386efab0cb2179993c99f67a190e1 Mon Sep 17 00:00:00 2001 From: Junhao Xie Date: Wed, 14 Jan 2026 10:48:52 +0800 Subject: [PATCH 13/17] WIP: drm/msm/dp: Use compatible table to filter out certain modes to avoid instability Signed-off-by: Junhao Xie --- drivers/gpu/drm/msm/dp/dp_display.c | 45 +++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index a5fcb4ba527ad..8c7e950addd6c 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -912,6 +912,48 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) return 0; } +static const struct msm_dp_bridge_mode_filter { + const char *compatible; + u32 max_pixels; + u32 max_vrefresh; +} msm_dp_bridge_mode_filters[] = { + { "radxa,dragon-q6a", 3440 * 1440, 30 }, + {} +}; + +static bool msm_dp_bridge_mode_filter_valid(const struct drm_display_mode *mode) +{ + const struct msm_dp_bridge_mode_filter *filter; + struct device_node *np; + int vrefresh; + u32 pixels; + + np = of_find_node_by_path("/"); + if (!np) + return true; + + pixels = mode->hdisplay * mode->vdisplay; + vrefresh = drm_mode_vrefresh(mode); + + for (filter = msm_dp_bridge_mode_filters; filter->compatible; filter++) { + if (!of_device_is_compatible(np, filter->compatible)) + continue; + + if (pixels <= filter->max_pixels || + vrefresh <= filter->max_vrefresh) + continue; + + DRM_INFO("mode filtered by %s: %ux%u@%u\n", + filter->compatible, mode->hdisplay, + mode->vdisplay, vrefresh); + of_node_put(np); + return false; + } + + of_node_put(np); + return true; +} + /** * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid * @bridge: Pointer to drm bridge structure @@ -929,7 +971,6 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; struct msm_dp *dp; int mode_pclk_khz = mode->clock; - int vrefresh = drm_mode_vrefresh(mode); dp = to_dp_bridge(bridge)->msm_dp_display; @@ -962,7 +1003,7 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, if (mode_rate_khz > supported_rate_khz) return MODE_BAD; - if ((mode->hdisplay * mode->vdisplay > 3440 * 1440) && vrefresh > 30) + if (!msm_dp_bridge_mode_filter_valid(mode)) return MODE_BAD; return MODE_OK; From 492201182ca3deb3e0875e9336afa6efe23d8340 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Mon, 27 Apr 2026 17:39:30 +0800 Subject: [PATCH 14/17] drm/bridge: lt8712: Add Lontium LT8712sx bridge driver Add driver for Lontium LT8712sx Single DisplayPort to Dual HDMI bridge. The LT8713sx is in place, but is untested. Signed-off-by: Jiali Chen --- drivers/gpu/drm/bridge/Kconfig | 14 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt8712sx.c | 1067 +++++++++++++++++++++ 3 files changed, 1082 insertions(+) create mode 100644 drivers/gpu/drm/bridge/lontium-lt8712sx.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index a250afd8d6622..714670ff375f5 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -184,6 +184,20 @@ config DRM_LONTIUM_LT9611UXC HDMI signals Please say Y if you have such hardware. +config DRM_LONTIUM_LT8712SX + tristate "Lontium LT8712SX DP/eDP to HDMI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + help + Driver for the Lontium LT8712SX and LT8713SX DP/eDP to HDMI + bridge chips. At runtime the chip behaves like a simple DRM bridge. + During probe the driver can optionally refresh onboard flash by + loading a firmware image referenced by the firmware-name property. + + Say M here if you want to build support as a module. The module + will be called lontium-lt8712sx. + config DRM_ITE_IT66121 tristate "ITE IT66121 HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c7dc03182e592..3641f0ff47352 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o +obj-$(CONFIG_DRM_LONTIUM_LT8712SX) += lontium-lt8712sx.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o diff --git a/drivers/gpu/drm/bridge/lontium-lt8712sx.c b/drivers/gpu/drm/bridge/lontium-lt8712sx.c new file mode 100644 index 0000000000000..a2da205c7f4e7 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt8712sx.c @@ -0,0 +1,1067 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Lontium LT8712SX DP/eDP to HDMI bridge driver + * + * The LT8712SX behaves like a mostly transparent bridge once valid + * firmware has been stored in its internal flash. Keep the DRM runtime + * model close to simple-bridge and integrate the optional I2C firmware + * update path into probe(). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define LT8712SX_PAGE_SIZE 256 +#define LT8712SX_MAIN_FW_SIZE SZ_64K +#define LT8712SX_MAIN_FW_PAYLOAD_SIZE (LT8712SX_MAIN_FW_SIZE - 1) +#define LT8712SX_BANK_FW_SIZE (12 * 1024) +#define LT8712SX_FLASH_BLOCK_SIZE 0x8000 +#define LT8712SX_FLASH_BLOCK_COUNT 8 +#define LT8712SX_MAX_FW_SIZE SZ_256K +#define LT8712SX_MAX_BANKS 16 + +#define LT8712SX_DEFAULT_FIRMWARE "LT8712SX.bin" +#define LT8713SX_DEFAULT_FIRMWARE "LT8713SX.bin" + +#define LT8712SX_POWER_ON_DELAY_MS 1000 +#define LT8712SX_MAIN_LOAD_DELAY_MS 200 +#define LT8712SX_BANK_LOAD_DELAY_MS 50 +#define LT8712SX_BLOCK_ERASE_DELAY_MS 100 +#define LT8712SX_STATUS_POLL_DELAY_MS 50 +#define LT8712SX_STATUS_POLL_RETRIES 50 + +struct lt8712sx_info { + unsigned int connector_type; + const char *firmware_name; +}; + +static const struct lt8712sx_info lt8712sx_hdmi_info = { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, +}; + +static const struct lt8712sx_info lt8712sx_info = { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, + .firmware_name = LT8712SX_DEFAULT_FIRMWARE, +}; + +static const struct lt8712sx_info lt8713sx_info = { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, + .firmware_name = LT8713SX_DEFAULT_FIRMWARE, +}; + +struct lt8712sx_firmware { + u8 *data; + size_t size; + u8 main_crc; + u8 bank_crc[LT8712SX_MAX_BANKS]; + unsigned int bank_count; +}; + +struct lt8712sx { + struct drm_bridge bridge; + struct drm_connector connector; + + struct device *dev; + struct regmap *regmap; + const struct lt8712sx_info *info; + struct drm_bridge *next_bridge; + struct regulator *vdd; + struct gpio_desc *power_gpio; + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + struct mutex lock; + bool powered; +}; + +static inline struct lt8712sx *bridge_to_lt8712sx(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt8712sx, bridge); +} + +static inline struct lt8712sx *connector_to_lt8712sx( + struct drm_connector *connector) +{ + return container_of(connector, struct lt8712sx, connector); +} + +static const struct regmap_config lt8712sx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static const struct reg_sequence lt8712sx_i2c_enable_seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x01 }, +}; + +static const struct reg_sequence lt8712sx_i2c_disable_seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_config_seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x01 }, + { 0x5e, 0xc1 }, + { 0x58, 0x00 }, + { 0x59, 0x50 }, + { 0x5a, 0x10 }, + { 0x5a, 0x00 }, + { 0x58, 0x21 }, +}; + +static const struct reg_sequence lt8712sx_wren_seq[] = { + { 0xff, 0xe1 }, + { 0x03, 0xbf }, + { 0x03, 0xff }, + { 0xff, 0xe0 }, + { 0x5a, 0x04 }, + { 0x5a, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_wrdi_seq[] = { + { 0x5a, 0x08 }, + { 0x5a, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_fifo_reset_seq[] = { + { 0xff, 0xe1 }, + { 0x03, 0xbf }, + { 0x03, 0xff }, +}; + +static const struct reg_sequence lt8712sx_disable_sram_write_seq[] = { + { 0xff, 0xe0 }, + { 0x55, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_sram_to_flash_seq[] = { + { 0x5a, 0x30 }, + { 0x5a, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_i2c_to_sram_seq[] = { + { 0x55, 0x80 }, + { 0x5e, 0xc0 }, + { 0x58, 0x21 }, +}; + +static int lt8712sx_write_seq(struct lt8712sx *lt8712sx, + const struct reg_sequence *seq, + size_t count) +{ + return regmap_multi_reg_write(lt8712sx->regmap, seq, count); +} + +static int lt8712sx_write(struct lt8712sx *lt8712sx, u8 reg, u8 value) +{ + return regmap_write(lt8712sx->regmap, reg, value); +} + +static int lt8712sx_read(struct lt8712sx *lt8712sx, u8 reg, u8 *value) +{ + unsigned int tmp; + int ret; + + ret = regmap_read(lt8712sx->regmap, reg, &tmp); + if (ret) + return ret; + + *value = tmp; + + return 0; +} + +static u8 lt8712sx_crc8_update(u8 crc, u8 value) +{ + int bit; + + crc ^= value; + + for (bit = 0; bit < 8; bit++) + crc = (crc & BIT(7)) ? (crc << 1) ^ 0x31 : crc << 1; + + return crc; +} + +static u8 lt8712sx_crc8_padded(const u8 *data, size_t len, size_t padded_len) +{ + u8 crc = 0; + size_t index; + + for (index = 0; index < len; index++) + crc = lt8712sx_crc8_update(crc, data[index]); + + for (; index < padded_len; index++) + crc = lt8712sx_crc8_update(crc, 0xff); + + return crc; +} + +static int lt8712sx_power_on(struct lt8712sx *lt8712sx) +{ + int ret; + + if (lt8712sx->powered) + return 0; + + if (lt8712sx->vdd) { + ret = regulator_enable(lt8712sx->vdd); + if (ret) + return ret; + } + + if (lt8712sx->power_gpio) + gpiod_set_value_cansleep(lt8712sx->power_gpio, 1); + + if (lt8712sx->reset_gpio) + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 1); + + lt8712sx->powered = true; + + msleep(10); + + return 0; +} + +static void lt8712sx_power_off_action(void *data) +{ + struct lt8712sx *lt8712sx = data; + + mutex_lock(<8712sx->lock); + + if (lt8712sx->enable_gpio) + gpiod_set_value_cansleep(lt8712sx->enable_gpio, 0); + + if (lt8712sx->reset_gpio) + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 0); + + if (lt8712sx->power_gpio) + gpiod_set_value_cansleep(lt8712sx->power_gpio, 0); + + if (lt8712sx->vdd && lt8712sx->powered) + regulator_disable(lt8712sx->vdd); + + lt8712sx->powered = false; + + mutex_unlock(<8712sx->lock); +} + +static int lt8712sx_hw_reset(struct lt8712sx *lt8712sx) +{ + if (!lt8712sx->reset_gpio) + return 0; + + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 1); + msleep(5); + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 0); + msleep(5); + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 1); + msleep(5); + + return 0; +} + +static int lt8712sx_i2c_enable(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_i2c_enable_seq, + ARRAY_SIZE(lt8712sx_i2c_enable_seq)); +} + +static int lt8712sx_i2c_disable(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_i2c_disable_seq, + ARRAY_SIZE(lt8712sx_i2c_disable_seq)); +} + +static int lt8712sx_configure(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_config_seq, + ARRAY_SIZE(lt8712sx_config_seq)); +} + +static int lt8712sx_wren(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_wren_seq, + ARRAY_SIZE(lt8712sx_wren_seq)); +} + +static int lt8712sx_wrdi(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_wrdi_seq, + ARRAY_SIZE(lt8712sx_wrdi_seq)); +} + +static int lt8712sx_fifo_reset(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_fifo_reset_seq, + ARRAY_SIZE(lt8712sx_fifo_reset_seq)); +} + +static int lt8712sx_disable_sram_write(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, + lt8712sx_disable_sram_write_seq, + ARRAY_SIZE(lt8712sx_disable_sram_write_seq)); +} + +static int lt8712sx_sram_to_flash(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_sram_to_flash_seq, + ARRAY_SIZE(lt8712sx_sram_to_flash_seq)); +} + +static int lt8712sx_i2c_to_sram(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_i2c_to_sram_seq, + ARRAY_SIZE(lt8712sx_i2c_to_sram_seq)); +} + +static int lt8712sx_flash_to_fifo(struct lt8712sx *lt8712sx, u32 address) +{ + int ret; + + ret = lt8712sx_write(lt8712sx, 0x5e, 0x40); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x20); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5b, FIELD_GET(0xff0000, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5c, FIELD_GET(0x00ff00, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5d, address & 0xff); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x10); + if (ret) + return ret; + + return lt8712sx_write(lt8712sx, 0x5a, 0x00); +} + +static int lt8712sx_flash_read_status(struct lt8712sx *lt8712sx, u8 *status) +{ + static const struct reg_sequence seq[] = { + { 0xff, 0xe1 }, + { 0x03, 0x3f }, + { 0x03, 0xff }, + { 0xff, 0xe0 }, + { 0x5e, 0x40 }, + { 0x56, 0x05 }, + { 0x55, 0x25 }, + { 0x55, 0x01 }, + { 0x58, 0x21 }, + }; + int ret; + + ret = lt8712sx_write_seq(lt8712sx, seq, ARRAY_SIZE(seq)); + if (ret) + return ret; + + return lt8712sx_read(lt8712sx, 0x5f, status); +} + +static int lt8712sx_block_erase(struct lt8712sx *lt8712sx) +{ + unsigned int block; + int ret; + + for (block = 0; block < LT8712SX_FLASH_BLOCK_COUNT; block++) { + u32 address = block * LT8712SX_FLASH_BLOCK_SIZE; + u8 status = 0; + unsigned int retry; + + ret = lt8712sx_write(lt8712sx, 0xff, 0xe0); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0xee, 0x01); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x04); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5b, + FIELD_GET(0xff0000, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5c, + FIELD_GET(0x00ff00, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5d, address & 0xff); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x01); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x00); + if (ret) + return ret; + + msleep(LT8712SX_BLOCK_ERASE_DELAY_MS); + + for (retry = 0; retry < LT8712SX_STATUS_POLL_RETRIES; retry++) { + ret = lt8712sx_flash_read_status(lt8712sx, &status); + if (ret) + return ret; + + if (!(status & BIT(0))) + break; + + msleep(LT8712SX_STATUS_POLL_DELAY_MS); + } + + if (status & BIT(0)) + return -ETIMEDOUT; + } + + return 0; +} + +static int lt8712sx_load_main_fw_to_sram(struct lt8712sx *lt8712sx) +{ + static const struct reg_sequence seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x01 }, + { 0x68, 0x00 }, + { 0x69, 0x00 }, + { 0x6a, 0x00 }, + { 0x65, 0x00 }, + { 0x66, 0xff }, + { 0x67, 0xff }, + { 0x6b, 0x00 }, + { 0x6c, 0x00 }, + { 0x60, 0x01 }, + }; + int ret; + + ret = lt8712sx_write_seq(lt8712sx, seq, ARRAY_SIZE(seq)); + if (ret) + return ret; + + msleep(LT8712SX_MAIN_LOAD_DELAY_MS); + + return lt8712sx_write(lt8712sx, 0x60, 0x00); +} + +static int lt8712sx_load_bank_fw_to_sram(struct lt8712sx *lt8712sx, + u32 address) +{ + int ret; + + ret = lt8712sx_write(lt8712sx, 0xff, 0xe0); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0xee, 0x01); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x68, FIELD_GET(0xff0000, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x69, FIELD_GET(0x00ff00, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x6a, address & 0xff); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x65, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x66, 0x30); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x67, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x6b, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x6c, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x60, 0x01); + if (ret) + return ret; + + msleep(LT8712SX_BANK_LOAD_DELAY_MS); + + return lt8712sx_write(lt8712sx, 0x60, 0x00); +} + +static int lt8712sx_write_firmware_pages(struct lt8712sx *lt8712sx, + const struct lt8712sx_firmware *fw) +{ + u8 page_buf[LT8712SX_PAGE_SIZE]; + unsigned int page_count; + unsigned int page; + int ret; + + page_count = DIV_ROUND_UP(fw->size, LT8712SX_PAGE_SIZE); + + for (page = 0; page < page_count; page++) { + size_t offset = page * LT8712SX_PAGE_SIZE; + size_t copy_len = min_t(size_t, fw->size - offset, + LT8712SX_PAGE_SIZE); + + memset(page_buf, 0xff, sizeof(page_buf)); + memcpy(page_buf, fw->data + offset, copy_len); + + ret = lt8712sx_i2c_to_sram(lt8712sx); + if (ret) + return ret; + + ret = regmap_noinc_write(lt8712sx->regmap, 0x59, page_buf, + sizeof(page_buf)); + if (ret) + return ret; + + ret = lt8712sx_wren(lt8712sx); + if (ret) + return ret; + + ret = lt8712sx_sram_to_flash(lt8712sx); + if (ret) + return ret; + } + + ret = lt8712sx_wrdi(lt8712sx); + if (ret) + return ret; + + return lt8712sx_disable_sram_write(lt8712sx); +} + +static int lt8712sx_needs_firmware_update(struct lt8712sx *lt8712sx, + u8 expected_crc, + bool *needs_update) +{ + u8 flash_crc; + int ret; + + ret = lt8712sx_configure(lt8712sx); + if (ret) + return ret; + + ret = lt8712sx_flash_to_fifo(lt8712sx, LT8712SX_MAIN_FW_PAYLOAD_SIZE); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x58, 0x21); + if (ret) + return ret; + + ret = lt8712sx_read(lt8712sx, 0x5f, &flash_crc); + if (ret) + return ret; + + ret = lt8712sx_wrdi(lt8712sx); + if (ret) + return ret; + + ret = lt8712sx_fifo_reset(lt8712sx); + if (ret) + return ret; + + *needs_update = flash_crc != expected_crc; + + return 0; +} + +static int lt8712sx_verify_main_firmware(struct lt8712sx *lt8712sx, + u8 expected_crc) +{ + u8 actual_crc; + int ret; + + ret = lt8712sx_read(lt8712sx, 0x23, &actual_crc); + if (ret) + return ret; + + if (actual_crc != expected_crc) + return -EIO; + + return 0; +} + +static int lt8712sx_verify_bank_firmware(struct lt8712sx *lt8712sx, + const struct lt8712sx_firmware *fw) +{ + unsigned int bank; + int ret; + + for (bank = 0; bank < fw->bank_count; bank++) { + u8 actual_crc; + u32 address = 0x010000 + bank * LT8712SX_BANK_FW_SIZE; + + ret = lt8712sx_load_bank_fw_to_sram(lt8712sx, address); + if (ret) + return ret; + + ret = lt8712sx_read(lt8712sx, 0x23, &actual_crc); + if (ret) + return ret; + + if (actual_crc != fw->bank_crc[bank]) + return -EIO; + } + + return 0; +} + +static int lt8712sx_prepare_firmware(const struct firmware *firmware, + struct lt8712sx_firmware *prepared) +{ + size_t bank_payload_size; + unsigned int bank; + + if (firmware->size > LT8712SX_MAX_FW_SIZE - 1) + return -EFBIG; + + prepared->data = kzalloc(LT8712SX_MAX_FW_SIZE, GFP_KERNEL); + if (!prepared->data) + return -ENOMEM; + + memset(prepared->data, 0xff, LT8712SX_MAX_FW_SIZE); + + if (firmware->size < LT8712SX_MAIN_FW_SIZE) { + memcpy(prepared->data, firmware->data, firmware->size); + prepared->main_crc = lt8712sx_crc8_padded(firmware->data, + firmware->size, + LT8712SX_MAIN_FW_PAYLOAD_SIZE); + prepared->data[LT8712SX_MAIN_FW_PAYLOAD_SIZE] = prepared->main_crc; + prepared->size = LT8712SX_MAIN_FW_SIZE; + + return 0; + } + + memcpy(prepared->data, firmware->data, LT8712SX_MAIN_FW_PAYLOAD_SIZE); + prepared->main_crc = lt8712sx_crc8_padded(prepared->data, + LT8712SX_MAIN_FW_PAYLOAD_SIZE, + LT8712SX_MAIN_FW_PAYLOAD_SIZE); + prepared->data[LT8712SX_MAIN_FW_PAYLOAD_SIZE] = prepared->main_crc; + + if (firmware->size > LT8712SX_MAIN_FW_SIZE) + memcpy(prepared->data + LT8712SX_MAIN_FW_SIZE, + firmware->data + LT8712SX_MAIN_FW_SIZE, + firmware->size - LT8712SX_MAIN_FW_SIZE); + + prepared->size = firmware->size; + prepared->bank_count = DIV_ROUND_UP(firmware->size - LT8712SX_MAIN_FW_SIZE, + LT8712SX_BANK_FW_SIZE); + if (prepared->bank_count > LT8712SX_MAX_BANKS) + return -EINVAL; + + bank_payload_size = firmware->size - LT8712SX_MAIN_FW_SIZE; + + for (bank = 0; bank < prepared->bank_count; bank++) { + size_t offset = LT8712SX_MAIN_FW_SIZE + bank * LT8712SX_BANK_FW_SIZE; + size_t valid = 0; + + if (bank * LT8712SX_BANK_FW_SIZE < bank_payload_size) + valid = min_t(size_t, + bank_payload_size - bank * LT8712SX_BANK_FW_SIZE, + LT8712SX_BANK_FW_SIZE); + + prepared->bank_crc[bank] = + lt8712sx_crc8_padded(prepared->data + offset, valid, + LT8712SX_BANK_FW_SIZE); + } + + return 0; +} + +static int lt8712sx_program_firmware(struct lt8712sx *lt8712sx, + const struct lt8712sx_firmware *fw) +{ + bool needs_update; + bool updated = false; + int ret; + + mutex_lock(<8712sx->lock); + + msleep(LT8712SX_POWER_ON_DELAY_MS); + + ret = lt8712sx_i2c_enable(lt8712sx); + if (ret) + goto unlock; + + ret = lt8712sx_needs_firmware_update(lt8712sx, fw->main_crc, + &needs_update); + if (ret) + goto disable_i2c; + + if (!needs_update) { + ret = 0; + goto disable_i2c; + } + + ret = lt8712sx_configure(lt8712sx); + if (ret) + goto disable_i2c; + + ret = lt8712sx_block_erase(lt8712sx); + if (ret) + goto disable_i2c; + + ret = lt8712sx_write_firmware_pages(lt8712sx, fw); + if (ret) + goto disable_i2c; + + ret = lt8712sx_load_main_fw_to_sram(lt8712sx); + if (ret) + goto disable_i2c; + + ret = lt8712sx_verify_main_firmware(lt8712sx, fw->main_crc); + if (ret) + goto disable_i2c; + + ret = lt8712sx_verify_bank_firmware(lt8712sx, fw); + if (ret) + goto disable_i2c; + + ret = lt8712sx_wrdi(lt8712sx); + if (ret) + goto disable_i2c; + + updated = true; + +disable_i2c: + if (lt8712sx_i2c_disable(lt8712sx) && !ret) + ret = -EIO; + + if (lt8712sx_hw_reset(lt8712sx) && !ret) + ret = -EIO; + +unlock: + mutex_unlock(<8712sx->lock); + + if (ret) + return ret; + + return updated; +} + +static void lt8712sx_try_optional_firmware(struct lt8712sx *lt8712sx) +{ + struct lt8712sx_firmware prepared = {}; + const struct firmware *firmware; + const char *firmware_name = lt8712sx->info->firmware_name; + int ret; + + ret = device_property_read_string(lt8712sx->dev, "firmware-name", + &firmware_name); + if (ret) + firmware_name = lt8712sx->info->firmware_name; + + if (!firmware_name) + return; + + ret = firmware_request_nowarn(&firmware, firmware_name, lt8712sx->dev); + if (ret) { + dev_info(lt8712sx->dev, + "optional firmware %s unavailable (%d), continuing\n", + firmware_name, ret); + return; + } + + ret = lt8712sx_prepare_firmware(firmware, &prepared); + if (ret) { + dev_warn(lt8712sx->dev, + "failed to prepare firmware %s (%d), continuing\n", + firmware_name, ret); + goto free_prepared; + } + + ret = lt8712sx_program_firmware(lt8712sx, &prepared); + if (ret < 0) { + dev_warn(lt8712sx->dev, + "failed to update firmware %s (%d), continuing\n", + firmware_name, ret); + } else if (ret > 0) { + dev_info(lt8712sx->dev, "updated firmware from %s\n", + firmware_name); + } else { + dev_dbg(lt8712sx->dev, "firmware %s already matches flash\n", + firmware_name); + } + + /* Fall through so partially prepared buffers also get cleaned up. */ + + free_prepared: + kfree(prepared.data); + release_firmware(firmware); +} + +static int lt8712sx_connector_get_modes(struct drm_connector *connector) +{ + struct lt8712sx *lt8712sx = connector_to_lt8712sx(connector); + const struct drm_edid *drm_edid; + int ret; + + if (lt8712sx->next_bridge->ops & DRM_BRIDGE_OP_EDID) { + drm_edid = drm_bridge_edid_read(lt8712sx->next_bridge, connector); + if (!drm_edid) + DRM_INFO("EDID read failed. Fallback to standard modes\n"); + } else { + drm_edid = NULL; + } + + drm_edid_connector_update(connector, drm_edid); + + if (!drm_edid) { + /* + * If the downstream HDMI connector does not expose DDC to the SoC, + * keep the same no-EDID fallback used by simple-bridge. + */ + ret = drm_add_modes_noedid(connector, 1920, 1200); + drm_set_preferred_mode(connector, 1024, 768); + return ret; + } + + ret = drm_edid_connector_add_modes(connector); + drm_edid_free(drm_edid); + + return ret; +} + +static const struct drm_connector_helper_funcs lt8712sx_con_helper_funcs = { + .get_modes = lt8712sx_connector_get_modes, +}; + +static enum drm_connector_status +lt8712sx_connector_detect(struct drm_connector *connector, bool force) +{ + struct lt8712sx *lt8712sx = connector_to_lt8712sx(connector); + + return drm_bridge_detect(lt8712sx->next_bridge, connector); +} + +static const struct drm_connector_funcs lt8712sx_con_funcs = { + .detect = lt8712sx_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int lt8712sx_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct lt8712sx *lt8712sx = bridge_to_lt8712sx(bridge); + int ret; + + ret = drm_bridge_attach(encoder, lt8712sx->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + drm_connector_helper_add(<8712sx->connector, + <8712sx_con_helper_funcs); + + ret = drm_connector_init_with_ddc(bridge->dev, <8712sx->connector, + <8712sx_con_funcs, + lt8712sx->info->connector_type, + lt8712sx->next_bridge->ddc); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + drm_connector_attach_encoder(<8712sx->connector, encoder); + + return 0; +} + +static void lt8712sx_enable(struct drm_bridge *bridge) +{ + struct lt8712sx *lt8712sx = bridge_to_lt8712sx(bridge); + + if (lt8712sx->enable_gpio) + gpiod_set_value_cansleep(lt8712sx->enable_gpio, 1); +} + +static void lt8712sx_disable(struct drm_bridge *bridge) +{ + struct lt8712sx *lt8712sx = bridge_to_lt8712sx(bridge); + + if (lt8712sx->enable_gpio) + gpiod_set_value_cansleep(lt8712sx->enable_gpio, 0); +} + +static const struct drm_bridge_funcs lt8712sx_bridge_funcs = { + .attach = lt8712sx_attach, + .enable = lt8712sx_enable, + .disable = lt8712sx_disable, +}; + +static int lt8712sx_probe(struct i2c_client *client) +{ + struct lt8712sx *lt8712sx; + struct device *dev = &client->dev; + struct device_node *remote; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENXIO; + + lt8712sx = devm_drm_bridge_alloc(dev, struct lt8712sx, bridge, + <8712sx_bridge_funcs); + if (IS_ERR(lt8712sx)) + return PTR_ERR(lt8712sx); + + lt8712sx->dev = dev; + lt8712sx->info = i2c_get_match_data(client); + if (!lt8712sx->info) + lt8712sx->info = <8712sx_hdmi_info; + + mutex_init(<8712sx->lock); + i2c_set_clientdata(client, lt8712sx); + + lt8712sx->regmap = devm_regmap_init_i2c(client, <8712sx_regmap_config); + if (IS_ERR(lt8712sx->regmap)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->regmap), + "failed to initialize regmap\n"); + + remote = of_graph_get_remote_node(dev->of_node, 1, -1); + if (!remote) + return dev_err_probe(dev, -ENODEV, "port@1 is unconnected\n"); + + lt8712sx->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!lt8712sx->next_bridge) { + dev_dbg(dev, "next bridge not found, deferring probe\n"); + return -EPROBE_DEFER; + } + + lt8712sx->vdd = devm_regulator_get_optional(dev, "vdd"); + if (IS_ERR(lt8712sx->vdd)) { + ret = PTR_ERR(lt8712sx->vdd); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + + lt8712sx->vdd = NULL; + dev_dbg(dev, "no vdd regulator found: %d\n", ret); + } + + lt8712sx->power_gpio = devm_gpiod_get_optional(dev, "power", + GPIOD_OUT_LOW); + if (IS_ERR(lt8712sx->power_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->power_gpio), + "failed to get power GPIO\n"); + + lt8712sx->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(lt8712sx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->reset_gpio), + "failed to get reset GPIO\n"); + + lt8712sx->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(lt8712sx->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->enable_gpio), + "failed to get enable GPIO\n"); + + ret = lt8712sx_power_on(lt8712sx); + if (ret) + return dev_err_probe(dev, ret, "failed to power on bridge\n"); + + ret = devm_add_action_or_reset(dev, lt8712sx_power_off_action, lt8712sx); + if (ret) + return ret; + + lt8712sx_try_optional_firmware(lt8712sx); + + lt8712sx->bridge.of_node = dev->of_node; + lt8712sx->bridge.type = lt8712sx->info->connector_type; + + return devm_drm_bridge_add(dev, <8712sx->bridge); +} + +static const struct of_device_id lt8712sx_of_match[] = { + { + .compatible = "lontium,lt8712sx", + .data = <8712sx_info, + }, { + .compatible = "lontium,lt8713sx", + .data = <8713sx_info, + }, {} +}; +MODULE_DEVICE_TABLE(of, lt8712sx_of_match); + +static const struct i2c_device_id lt8712sx_i2c_ids[] = { + { "lt8712sx", (kernel_ulong_t)<8712sx_info }, + { "lt8713sx", (kernel_ulong_t)<8713sx_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lt8712sx_i2c_ids); + +static struct i2c_driver lt8712sx_driver = { + .probe = lt8712sx_probe, + .id_table = lt8712sx_i2c_ids, + .driver = { + .name = "lontium-lt8712sx", + .of_match_table = lt8712sx_of_match, + }, +}; +module_i2c_driver(lt8712sx_driver); + +MODULE_AUTHOR("Chen Jiali"); +MODULE_DESCRIPTION("Lontium LT8712SX DP/eDP to HDMI bridge driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(LT8712SX_DEFAULT_FIRMWARE); +MODULE_FIRMWARE(LT8713SX_DEFAULT_FIRMWARE); From 92a37d7e9387e5dfd534552dc27888147d256877 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Mon, 27 Apr 2026 18:10:44 +0800 Subject: [PATCH 15/17] firmware: qcom: scm: Allow QSEECOM for Radxa CM-Q64 add "radxa,cm-q64" as compatible device for QSEECOM This is required to get access to efivars and uefi boot loader support. Signed-off-by: Jiali Chen --- drivers/firmware/qcom/qcom_scm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index b117e1b58e363..7e9a6c9525580 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -2039,6 +2039,7 @@ static const struct of_device_id qcom_scm_qseecom_allowlist[] __maybe_unused = { { .compatible = "qcom,x1e80100-crd" }, { .compatible = "qcom,x1e80100-qcp" }, { .compatible = "qcom,x1p42100-crd" }, + { .compatible = "radxa,cm-q64" }, { .compatible = "radxa,dragon-q6a" }, { } }; @@ -2318,6 +2319,7 @@ EXPORT_SYMBOL_GPL(qcom_scm_storage_send_cmd); * access on untested platforms. New platforms should be added here after validation. */ static const struct of_device_id qcom_scm_storage_allowlist[] = { + { .compatible = "radxa,cm-q64" }, { .compatible = "radxa,dragon-q6a" }, { } }; From 9ea35752d9a3fdde55a985ac16dc3c3fcb89964b Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Wed, 6 May 2026 19:59:35 +0800 Subject: [PATCH 16/17] arm64: dts: qcom: radxa-dragon-q6a: add hardware version detection Rework PM7325 GPIO pinctrl for hardware version selection. Add gpio-line-names, hardware_version_sel pin configuration, and hardware_version_adc VADC channel for board revision identification. Signed-off-by: Jiali Chen --- .../dts/qcom/qcs6490-radxa-dragon-q6a.dts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts b/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts index 518db4129c985..1295147346091 100644 --- a/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts @@ -640,10 +640,24 @@ }; &pm7325_gpios { - pm7325_adc_default: adc-default-state { - pins = "gpio2"; + pinctrl-0 = <&hardware_version_sel>; + pinctrl-names = "default"; + gpio-line-names = "", /* GPIO_01 */ + "UFS_THERM", + "Version_ADC", + "", + "", + "KYPD_VOL_UP_N", + "Version_SEL", + "", + "MICRO USB DET", + ""; + + hardware_version_sel: hardware-version-sel-state { + pins = "gpio7"; function = PMIC_GPIO_FUNC_NORMAL; bias-high-impedance; + power-source = <1>; }; }; @@ -685,9 +699,6 @@ }; &pmk8350_vadc { - pinctrl-0 = <&pm7325_adc_default>; - pinctrl-names = "default"; - channel@3 { reg = ; label = "pmk7325_die_temp"; @@ -708,6 +719,13 @@ qcom,pre-scaling = <1 1>; }; + channel@10b { + reg = ; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "hardware_version_adc"; + }; + channel@144 { reg = ; qcom,ratiometric; From 7b077c3633bb786889dffed81e58dcf075a8ce5c Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Wed, 6 May 2026 19:58:34 +0800 Subject: [PATCH 17/17] arm64: dts: qcom: add Radxa CM-Q64 on RPi CM5 IO carrier board Add device tree for Radxa CM-Q64 compute module on Raspberry Pi CM5 IO carrier board. Includes full PMIC, PCIe, USB, DP, audio (WCD938x), UFS, and SD card support. Add KVM overlay for hypervisor mode configuration. Signed-off-by: Jiali Chen --- arch/arm64/boot/dts/qcom/Makefile | 3 + .../qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso | 58 + .../qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts | 206 +++ .../boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi | 1179 +++++++++++++++++ 4 files changed, 1446 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso create mode 100644 arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts create mode 100644 arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index de83a99ff195f..07d718288aead 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -126,6 +126,9 @@ dtb-$(CONFIG_ARCH_QCOM) += qcm6490-shift-otter.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs404-evb-1000.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs404-evb-4000.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs615-ride.dtb +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-cm-q64-rpi-cm5-io.dtb +qcs6490-radxa-cm-q64-rpi-cm5-io-kvm-dtbs := qcs6490-radxa-cm-q64-rpi-cm5-io.dtb qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtbo +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-dragon-q6a.dtb qcs6490-radxa-dragon-q6a-kvm-dtbs := qcs6490-radxa-dragon-q6a.dtb qcs6490-radxa-dragon-q6a-kvm.dtbo dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-dragon-q6a-kvm.dtb diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso new file mode 100644 index 0000000000000..c77daabe769cc --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025 Radxa Computer (Shenzhen) Co., Ltd. + * + * This device tree overlay is supposed to be applied by UEFI firmware + * when Hypervisor Override is set to enabled. + */ + +#include + +/dts-v1/; +/plugin/; + +/* Required when Hypervisor Override is set to auto */ +&{/chosen} { + radxa,enable-kvm = <1>; +}; + +/* We can't and don't need to use zap shader in EL2 as linux can zap the gpu on it's own. */ +&gpu_zap_shader { + status = "disabled"; +}; + +&soc { + #address-cells = <2>; + #size-cells = <2>; + + pcie@1c08000 { + #address-cells = <3>; + #size-cells = <2>; + + /* Allow using upper PCIe space */ + ranges = <0x01000000 0x0 0x00000000 0x0 0x40200000 0x0 0x100000>, + <0x02000000 0x0 0x40300000 0x0 0x40300000 0x0 0x1fd00000>, + <0x03000000 0x4 0x00000000 0x4 0x00000000 0x3 0x00000000>; + }; +}; + +&remoteproc_adsp { + qcom,broken-reset; +}; + +&remoteproc_cdsp { + qcom,broken-reset; +}; + +&scm { + qcom,shm-bridge-vmid = ; +}; + +&venus { + iommus = <&apps_smmu 0x2180 0x20>, + <&apps_smmu 0x2184 0x20>; + + video-firmware { + iommus = <&apps_smmu 0x21a2 0x0>; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts new file mode 100644 index 0000000000000..d5fbb85009c29 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025 Radxa Computer (Shenzhen) Co., Ltd. + */ + +/dts-v1/; + +#include "qcs6490-radxa-cm-q64.dtsi" + +/ { + model = "Radxa CM-Q64 Raspberry Pi Compute Module 5 IO Board"; + compatible = "radxa,cm-q64-rpi-cm5-io", "radxa,cm-q64", "qcom,qcm6490"; + + pm8350c_gpio9_pwm: pm8350c-gpio9-pwm { + compatible = "pwm-gpio"; + gpios = <&pm8350c_gpios 9 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&fan_ctr_pwm>; + pinctrl-names = "default"; + #pwm-cells = <3>; + }; + + pm8350c_gpio9_pwm_fan: pwm-fan { + pinctrl-0 = <&fan_tacho>; + pinctrl-names = "default"; + compatible = "pwm-fan"; + pwms = <&pm8350c_gpio9_pwm 0 50000000 0>; + fan-supply = <&vcc_3v3>; + interrupt-parent = <&pm8350c_gpios>; + interrupts = <5 IRQ_TYPE_EDGE_FALLING>; + #cooling-cells = <2>; + cooling-levels = <0 120 150 180 210 240 255>; + }; +}; + +&pm8350c_gpios { + gpio-line-names = "", /* 1 */ + "", + "", + "", + "FAN_TACHO_IN", + "", + "LCD_BLEN", + "LCD_BLPWM", + "FAN_CTR_PWM"; + + fan_tacho: fan-tacho-state { + pins = "gpio5"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + power-source = <1>; + }; + + fan_ctr_pwm: fan-ctr-pwm-state { + pins = "gpio9"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + qcom,drive-strength = ; + output-low; + power-source = <1>; + }; +}; + + +&thermal_zones { + cpuss0-thermal { + trips { + cpuss0_active: trip-point2 { + temperature = <60000>; + hysteresis = <2000>; + type = "active"; + }; + }; + + cooling-maps { + map2 { + trip = <&cpuss0_active>; + cooling-device = <&pm8350c_gpio9_pwm_fan THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpuss1-thermal { + trips { + cpuss1_active: trip-point2 { + temperature = <60000>; + hysteresis = <2000>; + type = "active"; + }; + }; + + cooling-maps { + map2 { + trip = <&cpuss1_active>; + cooling-device = <&pm8350c_gpio9_pwm_fan THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; +}; + +&tlmm { + /* + * 12-17: reserved for QSPI flash + */ + gpio-reserved-ranges = <12 6>; + gpio-line-names = + /* GPIO_0 ~ GPIO_3 */ + "PIN_13", "PIN_15", "", "", + /* GPIO_4 ~ GPIO_7 */ + "", "", "", "", + /* GPIO_8 ~ GPIO_11 */ + "PIN_27", "PIN_28", "", "", + /* GPIO_12 ~ GPIO_15 */ + "", "", "", "", + /* GPIO_16 ~ GPIO_19 */ + "", "", "", "", + /* GPIO_20 ~ GPIO_23 */ + "", "", "PIN_8", "PIN_10", + /* GPIO_24 ~ GPIO_27 */ + "PIN_3", "PIN_5", "PIN_16", "PIN_18", + /* GPIO_28 ~ GPIO_31 */ + "PIN_31", "PIN_11", "PIN_32", "PIN_29", + /* GPIO_32 ~ GPIO_35 */ + "", "", "", "", + /* GPIO_36 ~ GPIO_39 */ + "", "", "", "", + /* GPIO_40 ~ GPIO_43 */ + "", "", "", "", + /* GPIO_44 ~ GPIO_47 */ + "", "", "", "", + /* GPIO_48 ~ GPIO_51 */ + "PIN_21", "PIN_19", "PIN_23", "PIN_24", + /* GPIO_52 ~ GPIO_55 */ + "", "", "", "PIN_26", + /* GPIO_56 ~ GPIO_59 */ + "PIN_33", "PIN_22", "PIN_37", "PIN_36", + /* GPIO_60 ~ GPIO_63 */ + "", "", "", "", + /* GPIO_64 ~ GPIO_67 */ + "", "", "", "", + /* GPIO_68 ~ GPIO_71 */ + "", "", "", "", + /* GPIO_72 ~ GPIO_75 */ + "", "", "", "", + /* GPIO_76 ~ GPIO_79 */ + "", "", "", "", + /* GPIO_80 ~ GPIO_83 */ + "", "", "", "", + /* GPIO_84 ~ GPIO_87 */ + "", "", "", "", + /* GPIO_88 ~ GPIO_91 */ + "", "", "", "", + /* GPIO_92 ~ GPIO_95 */ + "", "", "", "", + /* GPIO_96 ~ GPIO_99 */ + "PIN_7", "PIN_12", "PIN_38", "PIN_40", + /* GPIO_100 ~ GPIO_103 */ + "PIN_35", "", "", "", + /* GPIO_104 ~ GPIO_107 */ + "", "", "", "", + /* GPIO_108 ~ GPIO_111 */ + "", "", "", "", + /* GPIO_112 ~ GPIO_115 */ + "", "", "", "", + /* GPIO_116 ~ GPIO_119 */ + "", "", "", "", + /* GPIO_120 ~ GPIO_123 */ + "", "", "", "", + /* GPIO_124 ~ GPIO_127 */ + "", "", "", "", + /* GPIO_128 ~ GPIO_131 */ + "", "", "", "", + /* GPIO_132 ~ GPIO_135 */ + "", "", "", "", + /* GPIO_136 ~ GPIO_139 */ + "", "", "", "", + /* GPIO_140 ~ GPIO_143 */ + "", "", "", "", + /* GPIO_144 ~ GPIO_147 */ + "", "", "", "", + /* GPIO_148 ~ GPIO_151 */ + "", "", "", "", + /* GPIO_152 ~ GPIO_155 */ + "", "", "", "", + /* GPIO_156 ~ GPIO_159 */ + "", "", "", "", + /* GPIO_160 ~ GPIO_163 */ + "", "", "", "", + /* GPIO_164 ~ GPIO_167 */ + "", "", "", "", + /* GPIO_168 ~ GPIO_171 */ + "", "", "", "", + /* GPIO_172 ~ GPIO_174 */ + "", "", ""; +}; + +/* For uart_dbg */ +&qup_uart5_rx { + bias-disable; + drive-strength = <2>; +}; + +/* For uart_dbg */ +&qup_uart5_tx { + bias-disable; + drive-strength = <16>; +}; diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi new file mode 100644 index 0000000000000..811a2f4e77cb2 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi @@ -0,0 +1,1179 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025 Radxa Computer (Shenzhen) Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include "sc7280.dtsi" +#include "pm7325.dtsi" +#include "pm8350c.dtsi" /* PM7350C */ +#include "pmk8350.dtsi" /* PMK7325 */ +#include "qcs6490-audioreach.dtsi" + +/delete-node/ &adsp_mem; +/delete-node/ &cdsp_mem; +/delete-node/ &gpu_zap_mem; +/delete-node/ &ipa_fw_mem; +/delete-node/ &mpss_mem; +/delete-node/ &remoteproc_mpss; +/delete-node/ &remoteproc_wpss; +/delete-node/ &rmtfs_mem; +/delete-node/ &video_mem; +/delete-node/ &wifi; +/delete-node/ &wlan_ce_mem; +/delete-node/ &wlan_fw_mem; +/delete-node/ &wpss_mem; + +/ { + model = "Radxa CM-Q64"; + compatible = "radxa,cm-q64", "qcom,qcm6490"; + chassis-type = "embedded"; + + aliases { + mmc0 = &sdhc_1; + mmc1 = &sdhc_2; + serial0 = &uart5; + }; + + wcd938x: audio-codec { + compatible = "qcom,wcd9380-codec"; + + pinctrl-0 = <&wcd_default>; + pinctrl-names = "default"; + + reset-gpios = <&tlmm 83 GPIO_ACTIVE_LOW>; + + vdd-rxtx-supply = <&vreg_l18b_1p8>; + vdd-io-supply = <&vreg_l18b_1p8>; + vdd-buck-supply = <&vreg_l17b_1p8>; + vdd-mic-bias-supply = <&vreg_bob_3p296>; + + qcom,micbias1-microvolt = <1800000>; + qcom,micbias2-microvolt = <1800000>; + qcom,micbias3-microvolt = <1800000>; + qcom,micbias4-microvolt = <1800000>; + qcom,mbhc-buttons-vthreshold-microvolt = <75000 150000 237000 500000 500000 500000 500000 500000>; + qcom,mbhc-headset-vthreshold-microvolt = <1700000>; + qcom,mbhc-headphone-vthreshold-microvolt = <50000>; + qcom,rx-device = <&wcd_rx>; + qcom,tx-device = <&wcd_tx>; + + qcom,hphl-jack-type-normally-closed; + + #sound-dai-cells = <1>; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + usb2_1_con: connector-0 { + compatible = "usb-a-connector"; + vbus-supply = <&vbus>; + + port { + usb2_1_connector: endpoint { + remote-endpoint = <&usb_hub_2_1>; + }; + }; + }; + + usb2_2_con: connector-1 { + compatible = "usb-a-connector"; + vbus-supply = <&vbus>; + + port { + usb2_2_connector: endpoint { + remote-endpoint = <&usb_hub_2_2>; + }; + }; + }; + + usb2_3_con: connector-2 { + compatible = "usb-a-connector"; + vbus-supply = <&vbus>; + + port { + usb2_3_connector: endpoint { + remote-endpoint = <&usb_hub_2_3>; + }; + }; + }; + + usb3_con: connector { + compatible = "usb-a-connector"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb3_con_hs_in: endpoint { + remote-endpoint = <&usb_1_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + usb3_con_ss_in: endpoint { + remote-endpoint = <&usb_1_qmpphy_out_usb>; + }; + }; + }; + }; + + hdmi-edp-connector { + compatible = "hdmi-connector"; + label = "hdmi"; + type = "a"; + + port { + hdmi_edp_connector_in: endpoint { + remote-endpoint = <&hdmi_edp_bridge_out>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + + pinctrl-0 = <&user_led>; + pinctrl-names = "default"; + + user-led { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&tlmm 42 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + }; + + reserved-memory { + lpass_ml_mem: lpass-ml@81800000 { + reg = <0x0 0x81800000 0x0 0xf00000>; + no-map; + }; + + cdsp_secure_heap_mem: cdsp-secure-heap@82700000 { + reg = <0x0 0x82700000 0x0 0x10000>; + no-map; + }; + + adsp_mem: adsp@8b800000 { + reg = <0x0 0x8b800000 0x0 0x2800000>; + no-map; + }; + + cdsp_mem: cdsp@8e000000 { + reg = <0x0 0x8e000000 0x0 0x1e00000>; + no-map; + }; + + video_mem: video@8fe00000 { + reg = <0x0 0x8fe00000 0x0 0x500000>; + no-map; + }; + + gpu_zap_mem: zap@90300000 { + reg = <0x0 0x90300000 0x0 0x5000>; + no-map; + }; + + tz_stat_mem: tz-stat@c0000000 { + reg = <0x0 0xc0000000 0x0 0x100000>; + no-map; + }; + + tags_mem: tags@c0100000 { + reg = <0x0 0xc0100000 0x0 0x1200000>; + no-map; + }; + + qtee_mem: qtee@c1300000 { + reg = <0x0 0xc1300000 0x0 0x500000>; + no-map; + }; + + trusted_apps_mem: trusted-apps@c1800000 { + reg = <0x0 0xc1800000 0x0 0x2200000>; + no-map; + }; + + adsp_rpc_remote_heap_mem: adsp-rpc-remote-heap@c6500000 { + reg = <0x0 0xc6500000 0x0 0x800000>; + no-map; + }; + }; + + thermal-zones { + msm-skin-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 2>; + }; + + quiet-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 1>; + }; + + ufs-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 3>; + }; + + xo-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 0>; + }; + }; + + vcc_1v8: regulator-vcc-1v8 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vreg_l18b_1p8>; + + regulator-always-on; + }; + + vcc_3v3: regulator-vcc-3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vreg_l18b_1p8>; + + regulator-always-on; + }; + + vcc_3v3s: regulator-vcc-3v3s { + compatible = "regulator-fixed"; + regulator-name = "vcc_3v3s"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vreg_l7b_2p96>; + + regulator-always-on; + }; + + vbus: regulator-vbus { + compatible = "regulator-fixed"; + regulator-name = "vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc_3v3>; + }; + + vph_pwr: regulator-vph-pwr { + compatible = "regulator-fixed"; + regulator-name = "vph_pwr"; + regulator-min-microvolt = <3700000>; + regulator-max-microvolt = <3700000>; + + regulator-always-on; + }; +}; + +&apps_rsc { + regulators-0 { + compatible = "qcom,pm7325-rpmh-regulators"; + qcom,pmic-id = "b"; + + vdd-s1-supply = <&vph_pwr>; + vdd-s2-supply = <&vph_pwr>; + vdd-s3-supply = <&vph_pwr>; + vdd-s4-supply = <&vph_pwr>; + vdd-s5-supply = <&vph_pwr>; + vdd-s6-supply = <&vph_pwr>; + vdd-s7-supply = <&vph_pwr>; + vdd-s8-supply = <&vph_pwr>; + vdd-l1-l4-l12-l15-supply = <&vreg_s7b_0p536>; + vdd-l2-l7-supply = <&vreg_bob_3p296>; + vdd-l6-l9-l10-supply = <&vreg_s8b_1p2>; + vdd-l11-l17-l18-l19-supply = <&vreg_s1b_1p84>; + + vreg_s1b_1p84: smps1 { + regulator-name = "vreg_s1b_1p84"; + regulator-min-microvolt = <1840000>; + regulator-max-microvolt = <2040000>; + }; + + vreg_s7b_0p536: smps7 { + regulator-name = "vreg_s7b_0p536"; + regulator-min-microvolt = <536000>; + regulator-max-microvolt = <1120000>; + }; + + vreg_s8b_1p2: smps8 { + regulator-name = "vreg_s8b_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1496000>; + regulator-initial-mode = ; + }; + + vreg_l1b_0p912: ldo1 { + regulator-name = "vreg_l1b_0p912"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <920000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2b_3p072: ldo2 { + regulator-name = "vreg_l2b_3p072"; + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l6b_1p2: ldo6 { + regulator-name = "vreg_l6b_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1256000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l7b_2p96: ldo7 { + regulator-name = "vreg_l7b_2p96"; + regulator-min-microvolt = <2960000>; + regulator-max-microvolt = <2960000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l9b_1p2: ldo9 { + regulator-name = "vreg_l9b_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1304000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l17b_1p8: ldo17 { + regulator-name = "vreg_l17b_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1896000>; + regulator-initial-mode = ; + }; + + vreg_l18b_1p8: ldo18 { + regulator-name = "vreg_l18b_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + regulator-always-on; + }; + + vreg_l19b_1p8: ldo19 { + regulator-name = "vreg_l19b_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; + + regulators-1 { + compatible = "qcom,pm8350c-rpmh-regulators"; + qcom,pmic-id = "c"; + + vdd-s1-supply = <&vph_pwr>; + vdd-s2-supply = <&vph_pwr>; + vdd-s3-supply = <&vph_pwr>; + vdd-s4-supply = <&vph_pwr>; + vdd-s5-supply = <&vph_pwr>; + vdd-s6-supply = <&vph_pwr>; + vdd-s7-supply = <&vph_pwr>; + vdd-s8-supply = <&vph_pwr>; + vdd-s9-supply = <&vph_pwr>; + vdd-s10-supply = <&vph_pwr>; + vdd-l1-l12-supply = <&vreg_s1b_1p84>; + vdd-l6-l9-l11-supply = <&vreg_bob_3p296>; + vdd-l10-supply = <&vreg_s7b_0p536>; + vdd-bob-supply = <&vph_pwr>; + + vreg_l1c_1p8: ldo1 { + regulator-name = "vreg_l1c_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1976000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l6c_2p96: ldo6 { + regulator-name = "vreg_l6c_2p96"; + regulator-min-microvolt = <1650000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l9c_2p96: ldo9 { + regulator-name = "vreg_l9c_2p96"; + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l10c_0p88: ldo10 { + regulator-name = "vreg_l10c_0p88"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <1048000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_bob_3p296: bob { + regulator-name = "vreg_bob_3p296"; + regulator-min-microvolt = <3032000>; + regulator-max-microvolt = <3960000>; + }; + }; +}; + +&gcc { + protected-clocks = , + , + , + , + , + , + , + , + , + , + , + , + , + ; +}; + +&gpi_dma0 { + status = "okay"; +}; + +&gpi_dma1 { + status = "okay"; +}; + +&gpu { + status = "okay"; +}; + +&gpu_zap_shader { + firmware-name = "qcom/qcs6490/a660_zap.mbn"; +}; + +&i2c10 { + qcom,enable-gsi-dma; + status = "okay"; + + lt8712sx: hdmi-edp-bridge@4f { + compatible = "lontium,lt8712sx"; + reg = <0x4f>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + hdmi_edp_bridge_in: endpoint { + remote-endpoint = <&mdss_edp_out>; + }; + }; + + port@1 { + reg = <1>; + + hdmi_edp_bridge_out: endpoint { + remote-endpoint = <&hdmi_edp_connector_in>; + }; + }; + }; + }; + + eeprom: eeprom@50 { + compatible = "belling,bl24c16f", "atmel,24c16"; + reg = <0x50>; + pagesize = <16>; + vcc-supply = <&vcc_3v3>; + }; + + rtc: rtc@68 { + compatible = "st,m41t11"; + reg = <0x68>; + }; +}; + +&lpass_audiocc { + compatible = "qcom,qcm6490-lpassaudiocc"; + /delete-property/ power-domains; +}; + +&lpass_rx_macro { + status = "okay"; +}; + +&lpass_tx_macro { + status = "okay"; +}; + +&lpass_va_macro { + status = "okay"; +}; + +&mdss { + status = "okay"; +}; + +&mdss_dp { + sound-name-prefix = "Display Port0"; + + status = "disabled"; +}; + +&mdss_dp_out { + data-lanes = <0 1>; + remote-endpoint = <&usb_dp_qmpphy_dp_in>; +}; + +&mdss_edp { + status = "okay"; +}; + +&mdss_edp_out { + data-lanes = <0 1 2 3>; + link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>; + + remote-endpoint = <&hdmi_edp_bridge_in>; +}; + +&mdss_edp_phy { + status = "okay"; + vdda-pll-supply = <&vreg_l10c_0p88>; + vdda-phy-supply = <&vreg_l6b_1p2>; +}; + +&pcie0 { + perst-gpios = <&tlmm 87 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 89 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&pcie0_clkreq_n>, <&pcie0_reset_n>, <&pcie0_wake_n>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie0_phy { + vdda-phy-supply = <&vreg_l10c_0p88>; + vdda-pll-supply = <&vreg_l6b_1p2>; + + status = "okay"; +}; + +&pcie1 { + perst-gpios = <&tlmm 2 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&pcie1_clkreq_n>, <&pcie1_reset_n>, <&pcie1_wake_n>; + pinctrl-names = "default"; + + /* + * Support for many different bus topologies + * Tested devices: + * - QPS615 PCIe switch + * - ASM1182e PCIe switch + * - ASM2824 PCIe switch + * - NVIDIA dGPU + * - Chelsio T520-CR + */ + iommu-map = <0x0 &apps_smmu 0x1c80 0x1>, + <0x100 &apps_smmu 0x1c80 0x1>, + <0x101 &apps_smmu 0x1c80 0x1>, + <0x102 &apps_smmu 0x1c80 0x1>, + <0x103 &apps_smmu 0x1c80 0x1>, + <0x104 &apps_smmu 0x1c80 0x1>, + <0x105 &apps_smmu 0x1c80 0x1>, + <0x106 &apps_smmu 0x1c80 0x1>, + <0x200 &apps_smmu 0x1c80 0x1>, + <0x208 &apps_smmu 0x1c80 0x1>, + <0x210 &apps_smmu 0x1c80 0x1>, + <0x218 &apps_smmu 0x1c80 0x1>, + <0x220 &apps_smmu 0x1c80 0x1>, + <0x238 &apps_smmu 0x1c80 0x1>, + <0x240 &apps_smmu 0x1c80 0x1>, + <0x260 &apps_smmu 0x1c80 0x1>, + <0x300 &apps_smmu 0x1c80 0x1>, + <0x400 &apps_smmu 0x1c80 0x1>, + <0x500 &apps_smmu 0x1c80 0x1>, + <0x501 &apps_smmu 0x1c80 0x1>, + <0x600 &apps_smmu 0x1c80 0x1>; + + status = "okay"; +}; + +&pcie1_phy { + vdda-phy-supply = <&vreg_l10c_0p88>; + vdda-pll-supply = <&vreg_l6b_1p2>; + + status = "okay"; +}; + +&pm7325_gpios { + pinctrl-0 = <&hardware_version_sel>; + pinctrl-names = "default"; + gpio-line-names = "", /* GPIO_01 */ + "UFS_THERM", + "Version_ADC", + "", + "", + "KYPD_VOL_UP_N", + "Version_SEL", + "", + "MICRO USB DET", + ""; + + hardware_version_sel: hardware-version-sel-state { + pins = "gpio7"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + power-source = <1>; + }; +}; + +&pm7325_temp_alarm { + io-channels = <&pmk8350_vadc PM7325_ADC7_DIE_TEMP>; + io-channel-names = "thermal"; +}; + +&pm8350c_gpios { + gpio-line-names = "", /* 1 */ + "", + "", + "", + "", + "", + "LCD_BLEN", + "LCD_BLPWM", + ""; + + lcd_bl_en: lcd-bl-en-state { + pins = "gpio7"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + qcom,drive-strength = ; + output-low; + power-source = <1>; + }; +}; + +&pmk8350_adc_tm { + status = "okay"; + + xo-therm@0 { + reg = <0>; + io-channels = <&pmk8350_vadc PMK8350_ADC7_AMUX_THM1_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + quiet-therm@1 { + reg = <1>; + io-channels = <&pmk8350_vadc PM7325_ADC7_AMUX_THM1_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + msm-skin-therm@2 { + reg = <2>; + io-channels = <&pmk8350_vadc PM7325_ADC7_AMUX_THM3_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + ufs-therm@3 { + reg = <3>; + io-channels = <&pmk8350_vadc PM7325_ADC7_GPIO1_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; +}; + +&pmk8350_vadc { + channel@3 { + reg = ; + label = "pmk7325_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@44 { + reg = ; + label = "xo_therm"; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,ratiometric; + }; + + channel@103 { + reg = ; + label = "pm7325_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@10b { + reg = ; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "hardware_version_adc"; + }; + + channel@144 { + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "quiet_therm"; + }; + + channel@146 { + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "msm_skin_therm"; + }; + + channel@14a { + /* According to datasheet, 0x4a = AMUX1_GPIO = GPIO_02 */ + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "ufs_therm"; + }; +}; + +&pon_pwrkey { + status = "okay"; +}; + +&qupv3_id_0 { + firmware-name = "qcom/qcm6490/qupv3fw.elf"; + status = "okay"; +}; + +&qupv3_id_1 { + firmware-name = "qcom/qcm6490/qupv3fw.elf"; + status = "okay"; +}; + +&remoteproc_adsp { + firmware-name = "qcom/qcs6490/radxa/cm-q64/adsp.mbn"; + status = "okay"; +}; + +&remoteproc_cdsp { + firmware-name = "qcom/qcs6490/radxa/cm-q64/cdsp.mbn"; + status = "okay"; +}; + +&sdhc_1 { + non-removable; + no-sd; + no-sdio; + + vmmc-supply = <&vreg_l7b_2p96>; + vqmmc-supply = <&vreg_l19b_1p8>; + + status = "okay"; +}; + +&sdhc_2 { + pinctrl-0 = <&sdc2_clk>, <&sdc2_cmd>, <&sdc2_data>; + pinctrl-1 = <&sdc2_clk_sleep>, <&sdc2_cmd_sleep>, <&sdc2_data_sleep>; + + vmmc-supply = <&vreg_l9c_2p96>; + vqmmc-supply = <&vreg_l6c_2p96>; + broken-cd; + + status = "okay"; +}; + +&sound { + #address-cells = <1>; + #size-cells = <0>; + compatible = "qcom,qcs6490-rb3gen2-sndcard"; + model = "QCS6490-Radxa-CM-Q64"; + + audio-routing = "IN1_HPHL", "HPHL_OUT", + "IN2_HPHR", "HPHR_OUT", + "AMIC2", "MIC BIAS2", + "TX SWR_ADC1", "ADC2_OUTPUT"; + + dp0-dai-link { + link-name = "DP0 Playback"; + + codec { + sound-dai = <&mdss_dp>; + }; + + cpu { + sound-dai = <&q6apmbedai DISPLAY_PORT_RX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + wcd-playback-dai-link { + link-name = "WCD Playback"; + + codec { + sound-dai = <&wcd938x 0>, <&swr0 0>, <&lpass_rx_macro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai RX_CODEC_DMA_RX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + wcd-capture-dai-link { + link-name = "WCD Capture"; + + codec { + sound-dai = <&wcd938x 1>, <&swr1 0>, <&lpass_tx_macro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai TX_CODEC_DMA_TX_3>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; +}; + +&swr0 { + status = "okay"; + + wcd_rx: codec@0,4 { + compatible = "sdw20217010d00"; + reg = <0 4>; + qcom,rx-port-mapping = <1 2 3 4 5>; + }; +}; + +&swr1 { + status = "okay"; + + wcd_tx: codec@0,3 { + compatible = "sdw20217010d00"; + reg = <0 3>; + qcom,tx-port-mapping = <1 1 2 3>; + }; +}; + +&tlmm { + pcie0_reset_n: pcie0-reset-n-state { + pins = "gpio87"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + pcie0_wake_n: pcie0-wake-n-state { + pins = "gpio89"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + + pcie1_reset_n: pcie1-reset-n-state { + pins = "gpio2"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + pcie1_wake_n: pcie1-wake-n-state { + pins = "gpio3"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + + ufs_cd: ufs-cd-state { + pins = "gpio7"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + + user_led: user-led-state { + pins = "gpio42"; + function = "gpio"; + bias-pull-up; + }; + + wcd_default: wcd-reset-n-active-state { + pins = "gpio83"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + output-low; + }; +}; + +&uart5 { + status = "okay"; +}; + +&ufs_mem_hc { + cd-gpios = <&tlmm 7 GPIO_ACTIVE_LOW>; + reset-gpios = <&tlmm 175 GPIO_ACTIVE_LOW>; + vcc-supply = <&vreg_l7b_2p96>; + vcc-max-microamp = <800000>; + vccq-supply = <&vreg_l9b_1p2>; + vccq-max-microamp = <900000>; + vccq2-supply = <&vreg_l9b_1p2>; + vccq2-max-microamp = <1300000>; + + pinctrl-0 = <&ufs_cd>; + pinctrl-names = "default"; + + /* Gear-4 Rate-B is unstable due to board */ + /* and UFS module design limitations */ + limit-gear-rate = "rate-a"; + + status = "okay"; +}; + +&ufs_mem_phy { + vdda-phy-supply = <&vreg_l10c_0p88>; + vdda-pll-supply = <&vreg_l6b_1p2>; + + status = "okay"; +}; + +&usb_1 { + dr_mode = "host"; + + status = "okay"; +}; + +&usb_1_dwc3_hs { + remote-endpoint = <&usb3_con_hs_in>; +}; + +&usb_1_hsphy { + vdda-pll-supply = <&vreg_l10c_0p88>; + vdda33-supply = <&vreg_l2b_3p072>; + vdda18-supply = <&vreg_l1c_1p8>; + + status = "okay"; +}; + +&usb_1_qmpphy { + vdda-phy-supply = <&vreg_l6b_1p2>; + vdda-pll-supply = <&vreg_l1b_0p912>; + + /delete-property/ orientation-switch; + + status = "okay"; + + ports { + port@0 { + #address-cells = <1>; + #size-cells = <0>; + + /delete-node/ endpoint; + + /* RX0/TX0 is statically connected to RA620 bridge */ + usb_1_qmpphy_out_dp: endpoint@0 { + reg = <0>; + + data-lanes = <0 1>; + }; + + /* RX1/TX1 is statically connected to USB-A port */ + usb_1_qmpphy_out_usb: endpoint@1 { + reg = <1>; + + data-lanes = <2 3>; + remote-endpoint = <&usb3_con_ss_in>; + }; + }; + }; +}; + +&usb_2 { + dr_mode = "host"; + + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + /* Onboard USB 2.0 hub */ + usb_hub_2_x: hub@1 { + compatible = "usb1a86:8091"; + reg = <1>; + vdd-supply = <&vcc_3v3>; + #address-cells = <1>; + #size-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + usb_hub_2_1: endpoint { + remote-endpoint = <&usb2_1_connector>; + }; + }; + + port@2 { + reg = <2>; + + usb_hub_2_2: endpoint { + remote-endpoint = <&usb2_2_connector>; + }; + }; + + port@3 { + reg = <3>; + + usb_hub_2_3: endpoint { + remote-endpoint = <&usb2_3_connector>; + }; + }; + }; + + /* FCU760K Wi-Fi & Bluetooth module */ + wifi@4 { + compatible = "usba69c,8d80"; + reg = <4>; + }; + }; +}; + +&usb_2_hsphy { + vdda-pll-supply = <&vreg_l10c_0p88>; + vdda33-supply = <&vreg_l2b_3p072>; + vdda18-supply = <&vreg_l1c_1p8>; + + status = "okay"; +}; + +&venus { + status = "okay"; +}; + +/* PINCTRL - additions to nodes defined in sc7280.dtsi */ +&dp_hot_plug_det { + bias-disable; +}; + +/* PINCTRL - additions to nodes defined in sc7280.dtsi */ +&edp_hot_plug_det { + bias-disable; +}; + +&pcie0_clkreq_n { + bias-pull-up; + drive-strength = <2>; +}; + +&pcie1_clkreq_n { + bias-pull-up; + drive-strength = <2>; +}; + +/* For uart_dbg */ +&qup_uart5_rx { + bias-disable; + input-enable; + drive-strength = <2>; +}; + +/* For uart_dbg */ +&qup_uart5_tx { + bias-disable; + drive-strength = <16>; +}; + +&sdc1_clk { + bias-disable; + drive-strength = <16>; +}; + +&sdc1_cmd { + bias-pull-up; + drive-strength = <10>; +}; + +&sdc1_data { + bias-pull-up; + drive-strength = <10>; +}; + +&sdc1_rclk { + bias-pull-down; +}; + +&sdc2_clk { + bias-disable; + drive-strength = <16>; +}; + +&sdc2_cmd { + bias-pull-up; + drive-strength = <10>; +}; + +&sdc2_data { + bias-pull-up; + drive-strength = <10>; +};