From 0e1c2854a2496bcd48163f168361ea90d1e63ec8 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 26 Mar 2021 17:06:36 +0000 Subject: [PATCH 01/11] 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 6e55d27a551abd7647d3f3e410fcdbb0fe874a3b Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:34:05 +0000 Subject: [PATCH 02/11] 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 57aa65d03fd691e4ea4b79817294120b29fb3aa1 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:34:18 +0000 Subject: [PATCH 03/11] 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 67cc24a17aa9e3e6d1d082304f11d885b30eaa8a Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:34:30 +0000 Subject: [PATCH 04/11] 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 abc633b7d81e82e88ca06f118766dccd4d30bf19 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:36:10 +0000 Subject: [PATCH 05/11] 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 b93eca3f56eaf7ed17e93e179bb5e487223245d1 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:36:35 +0000 Subject: [PATCH 06/11] media: i2c: add Sony IMX708 camera sensor driver Add V4L2 driver for the Sony IMX708 image sensor with CCI framework support. Includes 4608x2592 full resolution, 2x2 binned, 720p, and HDR modes. Add corresponding dt-bindings documentation. Signed-off-by: Jiali Chen --- .../devicetree/bindings/media/i2c/imx708.yaml | 119 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx708.c | 2195 +++++++++++++++++ 4 files changed, 2326 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/imx708.yaml create mode 100644 drivers/media/i2c/imx708.c diff --git a/Documentation/devicetree/bindings/media/i2c/imx708.yaml b/Documentation/devicetree/bindings/media/i2c/imx708.yaml new file mode 100644 index 0000000000000..8ea618fadecbe --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/imx708.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/imx708.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor + +maintainers: + - Raspberry Pi Kernel Maintenance + +description: |- + The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor + with an active array size of 4608H x 2592V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx708 + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.1 volts + + VANA1-supply: + description: + Analog1 voltage supply, 2.8 volts + + VANA2-supply: + description: + Analog2 voltage supply, 1.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + items: + - const: 1 + - const: 2 + + clock-noncontinuous: + type: boolean + description: |- + MIPI CSI-2 clock is non-continuous if this property is present, + otherwise it's continuous. + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA1-supply + - VANA2-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + imx708: sensor@1a { + compatible = "sony,imx708"; + reg = <0x1a>; + clocks = <&imx708_clk>; + VANA1-supply = <&imx708_vana1>; /* 2.8v */ + VANA2-supply = <&imx708_vana2>; /* 1.8v */ + VDIG-supply = <&imx708_vdig>; /* 1.1v */ + VDDL-supply = <&imx708_vddl>; /* 1.8v */ + + port { + imx708_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <450000000>; + }; + }; + }; + }; + +... diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index cdd7ba5da0d50..132ccdfc265f4 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -277,6 +277,17 @@ 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 OF_GPIO + select V4L2_CCI_I2C + 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..855996ce39a4f --- /dev/null +++ b/drivers/media/i2c/imx708.c @@ -0,0 +1,2195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX708 cameras. + * Copyright (C) 2022-2023, 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 +#include + +/* Chip ID */ +#define IMX708_REG_CHIP_ID CCI_REG16(0x0016) +#define IMX708_CHIP_ID 0x0708 + +#define IMX708_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX708_MODE_STANDBY 0x00 +#define IMX708_MODE_STREAMING 0x01 + +#define IMX708_REG_ORIENTATION CCI_REG8(0x0101) + +#define IMX708_XCLK_FREQ 24000000 + +/* + * 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]"); + +/* QBC Re-mosaic broken line correction registers */ +#define IMX708_LPF_INTENSITY_EN CCI_REG8(0xc428) +#define IMX708_LPF_INTENSITY_ENABLED 0x00 +#define IMX708_LPF_INTENSITY_DISABLED 0x01 +#define IMX708_LPF_INTENSITY CCI_REG8(0xc429) + +/* V_TIMING internal */ +#define IMX708_REG_FRAME_LENGTH CCI_REG16(0x0340) +#define IMX708_FRAME_LENGTH_MAX 0xffff + +/* Long exposure multiplier */ +#define IMX708_LONG_EXP_SHIFT_MAX 7 +#define IMX708_LONG_EXP_SHIFT_REG CCI_REG8(0x3100) + +/* Exposure control */ +#define IMX708_REG_EXPOSURE CCI_REG16(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 CCI_REG16(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 CCI_REG16(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 CCI_REG16(0x0b90) +#define IMX708_REG_COLOUR_BALANCE_BLUE CCI_REG16(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 CCI_REG16(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 CCI_REG16(0x0602) +#define IMX708_REG_TEST_PATTERN_GR CCI_REG16(0x0604) +#define IMX708_REG_TEST_PATTERN_B CCI_REG16(0x0606) +#define IMX708_REG_TEST_PATTERN_GB CCI_REG16(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 CCI_REG8(0x7b10) +#define IMX708_REG_BASE_SPC_GAINS_R CCI_REG8(0x7c00) + +/* HDR exposure ratio (long:med == med:short) */ +#define IMX708_HDR_EXPOSURE_RATIO 4 +#define IMX708_REG_MID_EXPOSURE CCI_REG16(0x3116) +#define IMX708_REG_SHT_EXPOSURE CCI_REG16(0x0224) +#define IMX708_REG_MID_ANALOG_GAIN CCI_REG16(0x3118) +#define IMX708_REG_SHT_ANALOG_GAIN CCI_REG16(0x0216) + +/* 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_list { + unsigned int num_of_regs; + const struct cci_reg_sequence *regs; +}; + +#define IMX708_REG_8BIT(addr, value) \ + { .reg = CCI_REG8(addr), .val = (value) } + +/* Link frequency setup */ +enum { + IMX708_LINK_FREQ_450MHZ, + IMX708_LINK_FREQ_447MHZ, + IMX708_LINK_FREQ_453MHZ, +}; + +static const s64 imx708_link_freq_menu[] = { + [IMX708_LINK_FREQ_450MHZ] = 450000000, + [IMX708_LINK_FREQ_447MHZ] = 447000000, + [IMX708_LINK_FREQ_453MHZ] = 453000000, +}; + +static const struct cci_reg_sequence link_450mhz_regs[] = { + IMX708_REG_8BIT(0x030e, 0x01), + IMX708_REG_8BIT(0x030f, 0x2c), +}; + +static const struct cci_reg_sequence link_447mhz_regs[] = { + IMX708_REG_8BIT(0x030e, 0x01), + IMX708_REG_8BIT(0x030f, 0x2a), +}; + +static const struct cci_reg_sequence link_453mhz_regs[] = { + IMX708_REG_8BIT(0x030e, 0x01), + IMX708_REG_8BIT(0x030f, 0x2e), +}; + +#define IMX708_REG_PLL_MPY_MSB CCI_REG8(0x030e) +#define IMX708_REG_PLL_MPY_LSB CCI_REG8(0x030f) + +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), + }, +}; + +/* 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, currently not used at runtime */ + 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, 0x38, 0x35, 0x35, 0x35 }, + { 0x35, 0x35, 0x35, 0x38, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } +}; + +static const struct cci_reg_sequence mode_common_regs[] = { + IMX708_REG_8BIT(0x0100, 0x00), + IMX708_REG_8BIT(0x0136, 0x18), + IMX708_REG_8BIT(0x0137, 0x00), + IMX708_REG_8BIT(0x33f0, 0x02), + IMX708_REG_8BIT(0x33f1, 0x05), + IMX708_REG_8BIT(0x3062, 0x00), + IMX708_REG_8BIT(0x3063, 0x12), + IMX708_REG_8BIT(0x3068, 0x00), + IMX708_REG_8BIT(0x3069, 0x12), + IMX708_REG_8BIT(0x306a, 0x00), + IMX708_REG_8BIT(0x306b, 0x30), + IMX708_REG_8BIT(0x3076, 0x00), + IMX708_REG_8BIT(0x3077, 0x30), + IMX708_REG_8BIT(0x3078, 0x00), + IMX708_REG_8BIT(0x3079, 0x30), + IMX708_REG_8BIT(0x5e54, 0x0c), + IMX708_REG_8BIT(0x6e44, 0x00), + IMX708_REG_8BIT(0xb0b6, 0x01), + IMX708_REG_8BIT(0xe829, 0x00), + IMX708_REG_8BIT(0xf001, 0x08), + IMX708_REG_8BIT(0xf003, 0x08), + IMX708_REG_8BIT(0xf00d, 0x10), + IMX708_REG_8BIT(0xf00f, 0x10), + IMX708_REG_8BIT(0xf031, 0x08), + IMX708_REG_8BIT(0xf033, 0x08), + IMX708_REG_8BIT(0xf03d, 0x10), + IMX708_REG_8BIT(0xf03f, 0x10), + IMX708_REG_8BIT(0x0112, 0x0a), + IMX708_REG_8BIT(0x0113, 0x0a), + IMX708_REG_8BIT(0x0114, 0x01), + IMX708_REG_8BIT(0x0b8e, 0x01), + IMX708_REG_8BIT(0x0b8f, 0x00), + IMX708_REG_8BIT(0x0b94, 0x01), + IMX708_REG_8BIT(0x0b95, 0x00), + IMX708_REG_8BIT(0x3400, 0x01), + IMX708_REG_8BIT(0x3478, 0x01), + IMX708_REG_8BIT(0x3479, 0x1c), + IMX708_REG_8BIT(0x3091, 0x01), + IMX708_REG_8BIT(0x3092, 0x00), + IMX708_REG_8BIT(0x3419, 0x00), + IMX708_REG_8BIT(0xbcf1, 0x02), + IMX708_REG_8BIT(0x3094, 0x01), + IMX708_REG_8BIT(0x3095, 0x01), + IMX708_REG_8BIT(0x3362, 0x00), + IMX708_REG_8BIT(0x3363, 0x00), + IMX708_REG_8BIT(0x3364, 0x00), + IMX708_REG_8BIT(0x3365, 0x00), + IMX708_REG_8BIT(0x0138, 0x01), +}; + +/* 10-bit. */ +static const struct cci_reg_sequence mode_4608x2592_regs[] = { + IMX708_REG_8BIT(0x0342, 0x3d), + IMX708_REG_8BIT(0x0343, 0x20), + IMX708_REG_8BIT(0x0340, 0x0a), + IMX708_REG_8BIT(0x0341, 0x59), + IMX708_REG_8BIT(0x0344, 0x00), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x00), + IMX708_REG_8BIT(0x0347, 0x00), + IMX708_REG_8BIT(0x0348, 0x11), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x0a), + IMX708_REG_8BIT(0x034b, 0x1f), + IMX708_REG_8BIT(0x0220, 0x62), + IMX708_REG_8BIT(0x0222, 0x01), + IMX708_REG_8BIT(0x0900, 0x00), + IMX708_REG_8BIT(0x0901, 0x11), + IMX708_REG_8BIT(0x0902, 0x0a), + IMX708_REG_8BIT(0x3200, 0x01), + IMX708_REG_8BIT(0x3201, 0x01), + IMX708_REG_8BIT(0x32d5, 0x01), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x00), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x12), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x0a), + IMX708_REG_8BIT(0x040f, 0x20), + IMX708_REG_8BIT(0x034c, 0x12), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x0a), + IMX708_REG_8BIT(0x034f, 0x20), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0x7c), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x64), + IMX708_REG_8BIT(0x3ca4, 0x00), + IMX708_REG_8BIT(0x3ca5, 0x00), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x00), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x08), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x00), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x3c), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x00), + IMX708_REG_8BIT(0x0202, 0x0a), + IMX708_REG_8BIT(0x0203, 0x29), + IMX708_REG_8BIT(0x0224, 0x01), + IMX708_REG_8BIT(0x0225, 0xf4), + IMX708_REG_8BIT(0x3116, 0x01), + IMX708_REG_8BIT(0x3117, 0xf4), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x00), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x00), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x00), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x01), + IMX708_REG_8BIT(0x341f, 0x20), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0xd8), + IMX708_REG_8BIT(0x3366, 0x00), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x00), + IMX708_REG_8BIT(0x3369, 0x00), +}; + +static const struct cci_reg_sequence mode_2x2binned_regs[] = { + IMX708_REG_8BIT(0x0342, 0x1e), + IMX708_REG_8BIT(0x0343, 0x90), + IMX708_REG_8BIT(0x0340, 0x05), + IMX708_REG_8BIT(0x0341, 0x38), + IMX708_REG_8BIT(0x0344, 0x00), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x00), + IMX708_REG_8BIT(0x0347, 0x00), + IMX708_REG_8BIT(0x0348, 0x11), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x0a), + IMX708_REG_8BIT(0x034b, 0x1f), + IMX708_REG_8BIT(0x0220, 0x62), + IMX708_REG_8BIT(0x0222, 0x01), + IMX708_REG_8BIT(0x0900, 0x01), + IMX708_REG_8BIT(0x0901, 0x22), + IMX708_REG_8BIT(0x0902, 0x08), + IMX708_REG_8BIT(0x3200, 0x41), + IMX708_REG_8BIT(0x3201, 0x41), + IMX708_REG_8BIT(0x32d5, 0x00), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x00), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x09), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x05), + IMX708_REG_8BIT(0x040f, 0x10), + IMX708_REG_8BIT(0x034c, 0x09), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x05), + IMX708_REG_8BIT(0x034f, 0x10), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0x7a), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x3c), + IMX708_REG_8BIT(0x3ca4, 0x00), + IMX708_REG_8BIT(0x3ca5, 0x3c), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x00), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x1c), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x08), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x1e), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x0a), + IMX708_REG_8BIT(0x0202, 0x05), + IMX708_REG_8BIT(0x0203, 0x08), + IMX708_REG_8BIT(0x0224, 0x01), + IMX708_REG_8BIT(0x0225, 0xf4), + IMX708_REG_8BIT(0x3116, 0x01), + IMX708_REG_8BIT(0x3117, 0xf4), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x70), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x70), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x70), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x00), + IMX708_REG_8BIT(0x341f, 0x90), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0x6c), + IMX708_REG_8BIT(0x3366, 0x00), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x00), + IMX708_REG_8BIT(0x3369, 0x00), +}; + +static const struct cci_reg_sequence mode_2x2binned_720p_regs[] = { + IMX708_REG_8BIT(0x0342, 0x14), + IMX708_REG_8BIT(0x0343, 0x60), + IMX708_REG_8BIT(0x0340, 0x04), + IMX708_REG_8BIT(0x0341, 0xb6), + IMX708_REG_8BIT(0x0344, 0x03), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x01), + IMX708_REG_8BIT(0x0347, 0xb0), + IMX708_REG_8BIT(0x0348, 0x0e), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x08), + IMX708_REG_8BIT(0x034b, 0x6f), + IMX708_REG_8BIT(0x0220, 0x62), + IMX708_REG_8BIT(0x0222, 0x01), + IMX708_REG_8BIT(0x0900, 0x01), + IMX708_REG_8BIT(0x0901, 0x22), + IMX708_REG_8BIT(0x0902, 0x08), + IMX708_REG_8BIT(0x3200, 0x41), + IMX708_REG_8BIT(0x3201, 0x41), + IMX708_REG_8BIT(0x32d5, 0x00), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x01), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x06), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x03), + IMX708_REG_8BIT(0x040f, 0x60), + IMX708_REG_8BIT(0x034c, 0x06), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x03), + IMX708_REG_8BIT(0x034f, 0x60), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0x76), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x3c), + IMX708_REG_8BIT(0x3ca4, 0x01), + IMX708_REG_8BIT(0x3ca5, 0x5e), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x00), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x0c), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x04), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x1e), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x05), + IMX708_REG_8BIT(0x0202, 0x04), + IMX708_REG_8BIT(0x0203, 0x86), + IMX708_REG_8BIT(0x0224, 0x01), + IMX708_REG_8BIT(0x0225, 0xf4), + IMX708_REG_8BIT(0x3116, 0x01), + IMX708_REG_8BIT(0x3117, 0xf4), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x70), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x70), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x70), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x00), + IMX708_REG_8BIT(0x341f, 0x60), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0x48), + IMX708_REG_8BIT(0x3366, 0x00), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x00), + IMX708_REG_8BIT(0x3369, 0x00), +}; + +static const struct cci_reg_sequence mode_hdr_regs[] = { + IMX708_REG_8BIT(0x0342, 0x14), + IMX708_REG_8BIT(0x0343, 0x60), + IMX708_REG_8BIT(0x0340, 0x0a), + IMX708_REG_8BIT(0x0341, 0x5b), + IMX708_REG_8BIT(0x0344, 0x00), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x00), + IMX708_REG_8BIT(0x0347, 0x00), + IMX708_REG_8BIT(0x0348, 0x11), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x0a), + IMX708_REG_8BIT(0x034b, 0x1f), + IMX708_REG_8BIT(0x0220, 0x01), + IMX708_REG_8BIT(0x0222, IMX708_HDR_EXPOSURE_RATIO), + IMX708_REG_8BIT(0x0900, 0x00), + IMX708_REG_8BIT(0x0901, 0x11), + IMX708_REG_8BIT(0x0902, 0x0a), + IMX708_REG_8BIT(0x3200, 0x01), + IMX708_REG_8BIT(0x3201, 0x01), + IMX708_REG_8BIT(0x32d5, 0x00), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x00), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x09), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x05), + IMX708_REG_8BIT(0x040f, 0x10), + IMX708_REG_8BIT(0x034c, 0x09), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x05), + IMX708_REG_8BIT(0x034f, 0x10), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0xa2), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x00), + IMX708_REG_8BIT(0x3ca4, 0x00), + IMX708_REG_8BIT(0x3ca5, 0x00), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x28), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x30), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x00), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x32), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x00), + IMX708_REG_8BIT(0x0202, 0x0a), + IMX708_REG_8BIT(0x0203, 0x2b), + IMX708_REG_8BIT(0x0224, 0x0a), + IMX708_REG_8BIT(0x0225, 0x2b), + IMX708_REG_8BIT(0x3116, 0x0a), + IMX708_REG_8BIT(0x3117, 0x2b), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x00), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x00), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x00), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x00), + IMX708_REG_8BIT(0x341f, 0x90), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0x6c), + IMX708_REG_8BIT(0x3360, 0x01), + IMX708_REG_8BIT(0x3361, 0x01), + IMX708_REG_8BIT(0x3366, 0x09), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x05), + IMX708_REG_8BIT(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 */ +}; + +#define IMX708_NUM_SUPPLIES ARRAY_SIZE(imx708_supply_name) + +/* + * 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; + struct regmap *regmap; + + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX708_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *red_balance; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *notify_gains; + struct v4l2_ctrl *hdr_mode; + + /* Current mode */ + const struct imx708_mode *mode; + + /* Mutex for serialized access */ + 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; + u32 csi2_flags; +}; + +static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx708, sd); +} + +static inline void get_mode_table(const struct imx708_mode **mode_list, + unsigned int *num_modes, + bool hdr_enable) +{ + 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); + } +} + +static const struct imx708_mode * +imx708_find_mode(bool hdr_enable, u32 width, u32 height) +{ + const struct imx708_mode *mode_list; + unsigned int num_modes; + + get_mode_table(&mode_list, &num_modes, hdr_enable); + + return v4l2_find_nearest_size(mode_list, num_modes, width, height, + width, height); +} + +static void imx708_calc_frame_interval(const struct imx708_mode *mode, + u32 vblank, struct v4l2_fract *interval) +{ + u32 frame_length = mode->height + vblank; + u64 numerator = (u64)mode->line_length_pix * frame_length; + u64 denominator = mode->pixel_rate; + u64 scale; + + /* + * struct v4l2_fract stores 32-bit numerators/denominators. Long + * exposure support allows frame lengths that overflow 32-bit if we + * keep the fraction unscaled, so shrink both terms proportionally. + */ + if (numerator > U32_MAX) { + scale = DIV64_U64_ROUND_UP(numerator, U32_MAX); + numerator = DIV64_U64_ROUND_CLOSEST(numerator, scale); + denominator = DIV64_U64_ROUND_CLOSEST(denominator, scale); + } + + if (!denominator) + denominator = 1; + + interval->numerator = numerator; + interval->denominator = denominator; +} + +static int imx708_read_reg(struct imx708 *imx708, u32 reg, u64 *val); + +static void imx708_log_mode_timing(struct imx708 *imx708, const char *tag) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct v4l2_fract interval; + const struct imx708_mode *mode = imx708->mode; + u32 vblank, frame_length; + u64 fps_milli; + + if (!mode) { + dev_dbg(&client->dev, + "%s: mode not initialized yet, link=%lld xclk=%u\n", + tag, + (long long)imx708_link_freq_menu[imx708->link_freq_idx], + imx708->xclk_freq); + return; + } + + vblank = imx708->vblank ? imx708->vblank->val : mode->vblank_default; + frame_length = mode->height + vblank; + + imx708_calc_frame_interval(mode, vblank, &interval); + fps_milli = DIV_ROUND_CLOSEST_ULL((u64)interval.denominator * 1000, + interval.numerator); + + dev_dbg(&client->dev, + "%s: mode=%ux%u hdr=%u remosaic=%u link=%lld pixel_rate=%llu line=%u vblank=%u frame_len=%u fps=%llu.%03llu xclk=%u\n", + tag, mode->width, mode->height, + mode->hdr, mode->remosaic, + (long long)imx708_link_freq_menu[imx708->link_freq_idx], + mode->pixel_rate, mode->line_length_pix, + vblank, frame_length, fps_milli / 1000, fps_milli % 1000, + imx708->xclk_freq); +} + +static int imx708_read_reg(struct imx708 *imx708, u32 reg, u64 *val) +{ + return cci_read(imx708->regmap, reg, val, NULL); +} + +static int imx708_write_reg(struct imx708 *imx708, u32 reg, u64 val) +{ + return cci_write(imx708->regmap, reg, val, NULL); +} + +/* Write a list of registers */ +static int imx708_write_regs(struct imx708 *imx708, + const struct cci_reg_sequence *regs, u32 len) +{ + return cci_multi_reg_write(imx708->regmap, regs, len, NULL); +} + +/* Get bayer order based on flip setting. */ +static u32 imx708_get_format_code(struct imx708 *imx708) +{ + unsigned int i; + + i = (imx708->vflip->val ? 2 : 0) | + (imx708->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx708_set_default_mode(struct imx708 *imx708) +{ + /* Set default mode to max resolution */ + imx708->mode = &supported_modes_10bit_no_hdr[0]; +} + +static void imx708_update_image_pad_format(struct v4l2_mbus_framefmt *fmt, + const struct imx708_mode *mode) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; + 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 int imx708_set_exposure(struct imx708 *imx708, unsigned int val) +{ + int ret; + + 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. + */ + ret = imx708_write_reg(imx708, IMX708_REG_EXPOSURE, + val >> imx708->long_exp_shift); + + return ret; +} + +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, + val); + + return ret; +} + +static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) +{ + int ret = 0; + + imx708->long_exp_shift = 0; + + while (val > IMX708_FRAME_LENGTH_MAX) { + imx708->long_exp_shift++; + val >>= 1; + if (imx708->long_exp_shift > IMX708_LONG_EXP_SHIFT_MAX) + return -EINVAL; + } + + ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, + val); + if (ret) + return ret; + + return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG, + imx708->long_exp_shift); +} + +static int imx708_set_framing_limits(struct imx708 *imx708) +{ + unsigned int hblank; + const struct imx708_mode *mode = imx708->mode; + int ret; + + /* Default to no long exposure multiplier */ + imx708->long_exp_shift = 0; + + __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); + + ret = __v4l2_ctrl_s_ctrl_int64(imx708->pixel_rate, mode->pixel_rate); + if (ret) + return ret; + + ret = __v4l2_ctrl_s_ctrl(imx708->vblank, mode->vblank_default); + if (ret) + return ret; + + return __v4l2_ctrl_s_ctrl(imx708->hblank, hblank); +} + +static void imx708_update_pad_state(struct imx708 *imx708, + struct v4l2_subdev_state *state, + const struct imx708_mode *mode, + unsigned int pad) +{ + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, pad); + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, pad); + + imx708_update_image_pad_format(fmt, mode); + fmt->code = imx708_get_format_code(imx708); + *crop = mode->crop; +} + +static int imx708_update_hdr_mode(struct imx708 *imx708, bool hdr_enable) +{ + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + const struct imx708_mode *mode; + u32 width = imx708->mode->width; + u32 height = imx708->mode->height; + int ret; + + state = v4l2_subdev_get_locked_active_state(&imx708->sd); + if (state) { + fmt = v4l2_subdev_state_get_format(state, 0); + width = fmt->width; + height = fmt->height; + } + + mode = imx708_find_mode(hdr_enable, width, height); + imx708->mode = mode; + + ret = imx708_set_framing_limits(imx708); + if (ret) + return ret; + + if (state) + imx708_update_pad_state(imx708, state, mode, 0); + + return 0; +} + +static int imx708_get_qbc_adjust(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int val = qbc_adjust; + + if (val <= 0) + return 0; + + if (val == 1) { + dev_warn_once(&client->dev, + "qbc_adjust=%d is unsupported, clamping to 2\n", + val); + return 2; + } + + if (val > 5) { + dev_warn_once(&client->dev, + "qbc_adjust=%d is unsupported, clamping to 5\n", + val); + return 5; + } + + return val; +} + +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); + int ret; + + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + imx708_adjust_exposure_range(imx708, ctrl); + + if (ctrl->id == V4L2_CID_WIDE_DYNAMIC_RANGE) + return imx708_update_hdr_mode(imx708, ctrl->val); + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = 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, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN, + imx708_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB, + ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, + 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_HBLANK: + case V4L2_CID_PIXEL_RATE: + /* + * Both controls are fixed by the selected mode. The mode tables + * program the timing registers directly, so replaying these + * read-only controls during handler setup is a no-op. + */ + ret = 0; + break; + case V4L2_CID_NOTIFY_GAINS: + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, + imx708->notify_gains->p_new.p_u32[0]); + if (ret) + break; + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, + imx708->notify_gains->p_new.p_u32[3]); + break; + default: + WARN_ONCE(1, "imx708: unhandled ctrl id 0x%x\n", ctrl->id); + 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->index >= 1) + return -EINVAL; + + code->code = imx708_get_format_code(imx708); + + 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); + const struct imx708_mode *mode_list; + unsigned int num_modes; + + get_mode_table(&mode_list, &num_modes, imx708->hdr_mode->val); + + if (fse->index >= num_modes) + return -EINVAL; + + if (fse->code != imx708_get_format_code(imx708)) + 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; + + return 0; +} + +static int imx708_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct imx708 *imx708 = to_imx708(sd); + const struct imx708_mode *mode_list; + unsigned int num_modes; + + if (fie->pad != 0) + return -EINVAL; + + get_mode_table(&mode_list, &num_modes, imx708->hdr_mode->val); + + if (fie->index >= num_modes) + return -EINVAL; + + if (fie->code != imx708_get_format_code(imx708)) + return -EINVAL; + + fie->width = mode_list[fie->index].width; + fie->height = mode_list[fie->index].height; + imx708_calc_frame_interval(&mode_list[fie->index], + mode_list[fie->index].vblank_default, + &fie->interval); + + return 0; +} + +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); + struct v4l2_mbus_framefmt *framefmt; + + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + fmt->format = *framefmt; + fmt->format.code = imx708_get_format_code(imx708); + + return 0; +} + +static int imx708_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx708 *imx708 = to_imx708(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_rect *crop; + const struct imx708_mode *mode; + u32 req_width = fmt->format.width; + u32 req_height = fmt->format.height; + + /* Bayer order varies with flips */ + fmt->format.code = imx708_get_format_code(imx708); + + mode = imx708_find_mode(imx708->hdr_mode->val, + fmt->format.width, fmt->format.height); + imx708_update_image_pad_format(&fmt->format, mode); + + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + crop = v4l2_subdev_state_get_crop(sd_state, fmt->pad); + *framefmt = fmt->format; + *crop = mode->crop; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + int ret; + + imx708->mode = mode; + ret = imx708_set_framing_limits(imx708); + if (ret) + return ret; + + dev_dbg(&client->dev, + "set_fmt active: req=%ux%u selected=%ux%u hdr=%u code=0x%x\n", + req_width, req_height, mode->width, mode->height, + imx708->hdr_mode ? imx708->hdr_mode->val : 0, + fmt->format.code); + imx708_log_mode_timing(imx708, "set_fmt"); + } + + return 0; +} + +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: + sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); + 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; +} + +static int imx708_get_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (fi->pad != 0) + return -EINVAL; + + imx708_calc_frame_interval(imx708->mode, imx708->vblank->val, + &fi->interval); + + return 0; +} + +/* 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; + const struct imx708_reg_list *freq_regs; + int qbc_strength; + int i, ret; + u64 val; + + imx708_log_mode_timing(imx708, "start_streaming"); + + 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, + &val); + if (ret == 0 && val == 0x40) { + for (i = 0; i < 54 && ret == 0; i++) { + u32 reg = CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_L) + + i); + + ret = imx708_write_reg(imx708, reg, + pdaf_gains[0][i % 9]); + } + for (i = 0; i < 54 && ret == 0; i++) { + u32 reg = CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_R) + + i); + + ret = imx708_write_reg(imx708, reg, + 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 after the mode table programs PLLs. */ + 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). */ + qbc_strength = imx708_get_qbc_adjust(imx708); + if (imx708->mode->remosaic && qbc_strength > 0) { + ret = imx708_write_reg(imx708, IMX708_LPF_INTENSITY, + qbc_strength); + if (ret) + return ret; + ret = imx708_write_reg(imx708, IMX708_LPF_INTENSITY_EN, + IMX708_LPF_INTENSITY_ENABLED); + } else { + ret = imx708_write_reg(imx708, IMX708_LPF_INTENSITY_EN, + IMX708_LPF_INTENSITY_DISABLED); + } + if (ret) + return ret; + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_MODE_STREAMING); + if (ret) + return ret; + + return 0; +} + +/* 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_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) { + dev_dbg(&client->dev, "set_stream: enable=%d already in requested state\n", + enable); + mutex_unlock(&imx708->mutex); + return 0; + } + + dev_dbg(&client->dev, "set_stream: enable=%d begin\n", enable); + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) { + dev_err(&client->dev, + "set_stream: runtime resume failed: %d\n", ret); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx708_start_streaming(imx708); + if (ret) { + dev_err(&client->dev, "set_stream: start_streaming failed: %d\n", + ret); + goto err_rpm_put; + } + } 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); + dev_dbg(&client->dev, "set_stream: enable=%d done\n", enable); + + mutex_unlock(&imx708->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put_sync(&client->dev); +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(IMX708_NUM_SUPPLIES, + imx708->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx708->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + dev_dbg(&client->dev, + "power_on: xclk=%u reset_gpio=%s link=%lld\n", + imx708->xclk_freq, + imx708->reset_gpio ? "present" : "absent", + (long long)imx708_link_freq_menu[imx708->link_freq_idx]); + + /* + * XCLR is active-low: keep it asserted until supplies and clock are + * stable, then release the sensor by driving the line high. + */ + if (imx708->reset_gpio) + gpiod_set_value_cansleep(imx708->reset_gpio, 0); + usleep_range(IMX708_XCLR_MIN_DELAY_US, + IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); + + imx708_log_mode_timing(imx708, "power_on"); + + return 0; + +reg_off: + regulator_bulk_disable(IMX708_NUM_SUPPLIES, 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); + + dev_dbg(&client->dev, "power_off\n"); + + if (imx708->reset_gpio) + gpiod_set_value_cansleep(imx708->reset_gpio, 1); + clk_disable_unprepare(imx708->xclk); + regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies); + + /* Force reprogramming of the common registers when powered up again. */ + imx708->common_regs_written = false; + + return 0; +} + +static int 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 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 < IMX708_NUM_SUPPLIES; i++) + imx708->supplies[i].supply = imx708_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + IMX708_NUM_SUPPLIES, + imx708->supplies); +} + +/* Verify chip ID */ +static int imx708_identify_module(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret, attempt; + u64 val; + + /* + * Some IMX708 modules take several milliseconds after power-on + * before the I2C interface becomes responsive. Retry chip ID + * reads to accommodate this hardware ramp-up latency. + */ + for (attempt = 0; attempt < 10; attempt++) { + ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID, &val); + if (!ret) + break; + usleep_range(5000, 6000); + } + if (ret) { + dev_err(&client->dev, + "failed to read chip id %x after %d attempts, error %d\n", + IMX708_CHIP_ID, attempt, ret); + return ret; + } + + if (val != IMX708_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX708_CHIP_ID, (unsigned int)val); + return -EIO; + } + + return 0; +} + +static int imx708_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (pad != 0) + return -EINVAL; + + config->type = V4L2_MBUS_CSI2_DPHY; + config->link_freq = imx708_link_freq_menu[imx708->link_freq_idx]; + config->bus.mipi_csi2.num_data_lanes = 2; + config->bus.mipi_csi2.flags = imx708->csi2_flags; + + return 0; +} + +static int imx708_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct imx708 *imx708 = to_imx708(sd); + const struct imx708_mode *mode_list; + unsigned int num_modes; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + }; + + get_mode_table(&mode_list, &num_modes, + imx708->hdr_mode && imx708->hdr_mode->val); + fmt.format.width = mode_list[0].width; + fmt.format.height = mode_list[0].height; + + return imx708_set_pad_format(sd, sd_state, &fmt); +} + +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, + .enum_frame_interval = imx708_enum_frame_interval, + .get_fmt = imx708_get_pad_format, + .set_fmt = imx708_set_pad_format, + .get_selection = imx708_get_selection, + .get_frame_interval = imx708_get_frame_interval, + .enum_frame_size = imx708_enum_frame_size, + .get_mbus_config = imx708_get_mbus_config, +}; + +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 = { + .init_state = imx708_init_state, +}; + +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; + unsigned int i; + int ret; + + ctrl_hdlr = &imx708->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 17); + 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->mode->pixel_rate, + imx708->mode->pixel_rate, 1, + imx708->mode->pixel_rate); + + imx708->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(imx708_link_freq_menu) - 1, + imx708->link_freq_idx, + imx708_link_freq_menu); + if (imx708->link_freq) + imx708->link_freq->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_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 */ + } + + imx708->notify_gains = 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; + + /* + * IMX708 is typically used as a pluggable camera module. If the board + * description does not provide explicit metadata, fall back to a sane + * default so userspace still gets the recommended read-only controls. + */ + if (props.orientation == V4L2_FWNODE_PROPERTY_UNSET) + props.orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL; + + if (props.rotation == V4L2_FWNODE_PROPERTY_UNSET) + props.rotation = 0; + + 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. */ + ret = imx708_set_framing_limits(imx708); + if (ret) + goto error; + + 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; + + 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; + } + + if (ep_cfg.nr_of_link_frequencies != 1) { + dev_err(dev, "exactly one link frequency must be provided\n"); + goto error_out; + } + + for (imx708->link_freq_idx = 0; + imx708->link_freq_idx < ARRAY_SIZE(imx708_link_freq_menu); + imx708->link_freq_idx++) { + if (ep_cfg.link_frequencies[0] == + imx708_link_freq_menu[imx708->link_freq_idx]) + break; + } + + if (imx708->link_freq_idx == ARRAY_SIZE(imx708_link_freq_menu)) { + dev_err(dev, "Link frequency not supported: %llu\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + imx708->csi2_flags = ep_cfg.bus.mipi_csi2.flags; + + 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; + + imx708_set_default_mode(imx708); + + 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; + + imx708->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx708->regmap)) + return dev_err_probe(dev, PTR_ERR(imx708->regmap), + "failed to initialize CCI regmap\n"); + + /* Get system clock (xclk) */ + imx708->xclk = devm_v4l2_sensor_clk_get(dev, NULL); + if (IS_ERR(imx708->xclk)) + return dev_err_probe(dev, PTR_ERR(imx708->xclk), + "failed to get xclk\n"); + + imx708->xclk_freq = clk_get_rate(imx708->xclk); + if (imx708->xclk_freq != IMX708_XCLK_FREQ) + return dev_err_probe(dev, -EINVAL, + "xclk frequency not supported: %d Hz\n", + imx708->xclk_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); + if (IS_ERR(imx708->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(imx708->reset_gpio), + "failed to get reset GPIO\n"); + + /* + * 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 err_power_off; + + /* + * 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, 1000); + pm_runtime_use_autosuspend(dev); + + /* This needs the pm runtime to be registered. */ + ret = imx708_init_controls(imx708); + if (ret) + goto err_pm; + + /* 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 pad */ + imx708->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx708->sd.entity, 1, &imx708->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto err_ctrls; + } + + imx708->sd.state_lock = &imx708->mutex; + ret = v4l2_subdev_init_finalize(&imx708->sd); + if (ret < 0) { + dev_err(dev, "subdev init error: %d\n", ret); + goto err_media_entity; + } + + ret = v4l2_async_register_subdev_sensor(&imx708->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto err_subdev_cleanup; + } + + /* + * Decrease the PM usage count. The device will get suspended after the + * autosuspend delay, turning the power off. + */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(&imx708->sd); + +err_media_entity: + media_entity_cleanup(&imx708->sd.entity); + +err_ctrls: + imx708_free_controls(imx708); + +err_pm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + +err_power_off: + imx708_power_off(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); + v4l2_subdev_cleanup(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 = { + SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume) + RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) +}; + +static struct i2c_driver imx708_i2c_driver = { + .probe = imx708_probe, + .remove = imx708_remove, + .driver = { + .name = "imx708", + .of_match_table = imx708_dt_ids, + .pm = pm_ptr(&imx708_pm_ops), + }, +}; + +module_i2c_driver(imx708_i2c_driver); + +MODULE_AUTHOR("David Plowman "); +MODULE_DESCRIPTION("Sony IMX708 sensor driver"); +MODULE_LICENSE("GPL"); From a4ac6f6391538db294bb4b8ae302501e3cd5c957 Mon Sep 17 00:00:00 2001 From: Junhao Xie Date: Wed, 14 Jan 2026 10:48:52 +0800 Subject: [PATCH 07/11] 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 17c3442facf8b7fd774d569258c8bd109f2edbd1 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Mon, 27 Apr 2026 17:39:30 +0800 Subject: [PATCH 08/11] 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/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt8712sx.c | 1067 +++++++++++++++++++++ 2 files changed, 1068 insertions(+) create mode 100644 drivers/gpu/drm/bridge/lontium-lt8712sx.c 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 bf3c3f31600dd64a9b42b127098bf300c0da72b2 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Mon, 27 Apr 2026 18:10:44 +0800 Subject: [PATCH 09/11] 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 204ce5bff36ab920b94f5e0f8f5fab1afb557e36 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:36:48 +0000 Subject: [PATCH 10/11] arm64: dts: qcom: qcs6490-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 | 29 +++++++++++++++---- 1 file changed, 24 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..fb601015973dd 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"; @@ -732,6 +743,14 @@ qcom,pre-scaling = <1 1>; label = "ufs_therm"; }; + + channel@14b { + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "hardware_version_adc"; + }; }; &pon_pwrkey { From 3f841ba0c66d831ff95a4a767bd960dfb82aadf1 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 31 Mar 2026 10:37:02 +0000 Subject: [PATCH 11/11] 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 | 1343 +++++++++++++++++ 3 files changed, 1404 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 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..ac5c062f2a6b9 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts @@ -0,0 +1,1343 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025 Radxa Computer (Shenzhen) Co., Ltd. + */ + +/dts-v1/; + +#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 Raspberry Pi Compute Module 5 IO Board"; + compatible = "radxa,cm-q64-rpi-cm5-io", "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>; + }; + }; + }; + }; + + 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>; + }; + + 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-levels = <0 120 150 180 210 240 255>; + }; + + hdmi-dp-bridge { + compatible = "radxa,ra620"; + + pinctrl-0 = <&dp_hot_plug_det>; + pinctrl-names = "default"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + hdmi_dp_bridge_in: endpoint { + remote-endpoint = <&usb_1_qmpphy_out_dp>; + }; + }; + + port@1 { + reg = <1>; + + hdmi_dp_bridge_out: endpoint { + remote-endpoint = <&hdmi_dp_connector_in>; + }; + }; + }; + }; + + hdmi-dp-connector { + compatible = "hdmi-connector"; + label = "hdmi"; + type = "a"; + + port { + hdmi_dp_connector_in: endpoint { + remote-endpoint = <&hdmi_dp_bridge_out>; + }; + }; + }; + + // hdmi-edp-connector { + // compatible = "hdmi-connector"; + // label = "hdmi"; + // type = "a"; + + // port { + // hdmi_edp_connector_in: endpoint { + // remote-endpoint = <&mdss_edp_out>; + // }; + // }; + // }; + dp-connector { + compatible = "dp-connector"; + label = "DP"; + type = "mini"; + + port { + hdmi_edp_connector_in: endpoint { + remote-endpoint = <&mdss_edp_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-boot-on; + 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-boot-on; + 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-boot-on; + regulator-always-on; + }; + + vbus: regulator-vbus { + compatible = "regulator-fixed"; + regulator-name = "vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc_3v3>; + + regulator-boot-on; + regulator-always-on; + }; + + vph_pwr: regulator-vph-pwr { + compatible = "regulator-fixed"; + regulator-name = "vph_pwr"; + regulator-min-microvolt = <3700000>; + regulator-max-microvolt = <3700000>; + + regulator-boot-on; + 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"; + + eeprom: eeprom@50 { + compatible = "belling,bl24c16f", "atmel,24c16"; + reg = <0x50>; + pagesize = <16>; + vcc-supply = <&vcc_3v3>; + }; + + rtc: rtc@68 { + compatible = "st,m41t11"; + reg = <0x68>; + }; +}; + +/* External touchscreen */ +&i2c13 { + qcom,enable-gsi-dma; + status = "okay"; +}; + +&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 = "okay"; +}; + +&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_connector_in>; +}; + +&mdss_edp_phy { + status = "okay"; +}; + +&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 */ + "", + "", + "", + "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>; + }; + + lcd_bl_en: lcd-bl-en-state { + pins = "gpio7"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + qcom,drive-strength = ; + output-low; + 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>; + }; +}; + +&pm8350c_pwm { + status = "okay"; +}; + +&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/dragon-q6a/adsp.mbn"; + status = "okay"; +}; + +&remoteproc_cdsp { + firmware-name = "qcom/qcs6490/radxa/dragon-q6a/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>, <&sd_cd>; + pinctrl-1 = <&sdc2_clk_sleep>, <&sdc2_cmd_sleep>, <&sdc2_data_sleep>, <&sd_cd>; + + vmmc-supply = <&vreg_l9c_2p96>; + vqmmc-supply = <&vreg_l6c_2p96>; + + broken-cd; + // cd-gpios = <&tlmm 91 GPIO_ACTIVE_LOW>; + status = "okay"; +}; + +&sound { + compatible = "qcom,qcs6490-rb3gen2-sndcard"; + model = "QCS6490-Radxa-Dragon-Q6A"; + + 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 { + /* + * 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 */ + "", "", ""; + + 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; + }; + + sd_cd: sd-cd-state { + pins = "gpio91"; + function = "gpio"; + 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>; + remote-endpoint = <&hdmi_dp_bridge_in>; + }; + + /* 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>; +}; + +&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>; +};