diff options
105 files changed, 2460 insertions, 303 deletions
@@ -628,7 +628,8 @@ Nadav Amit <nadav.amit@gmail.com> <namit@cs.technion.ac.il> Nadia Yvette Chambers <nyc@holomorphy.com> William Lee Irwin III <wli@holomorphy.com> Naoya Horiguchi <nao.horiguchi@gmail.com> <n-horiguchi@ah.jp.nec.com> Naoya Horiguchi <nao.horiguchi@gmail.com> <naoya.horiguchi@nec.com> -Natalie Vock <natalie.vock@gmx.de> <friedrich.vock@gmx.de> +Natalie Vock <nat@pixelcluster.dev> <friedrich.vock@gmx.de> +Natalie Vock <nat@pixelcluster.dev> <natalie.vock@gmx.de> Nathan Chancellor <nathan@kernel.org> <natechancellor@gmail.com> Naveen N Rao <naveen@kernel.org> <naveen.n.rao@linux.ibm.com> Naveen N Rao <naveen@kernel.org> <naveen.n.rao@linux.vnet.ibm.com> diff --git a/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml b/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml new file mode 100644 index 000000000000..1e86f5329b22 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/novatek,nt36536.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Novatek NT36536 based DSI display Panels + +maintainers: + - Pengyu Luo <mitltlatltl@gmail.com> + +description: + The Novatek NT36536 is a generic DSI Panel IC used to drive dsi + panels. Support video mode panels from China Star Optoelectronics + Technology (CSOT), such as PP8807HB1-1 which is a dual-link 10-bit + panel. + +allOf: + - $ref: panel-common-dual.yaml# + +properties: + compatible: + items: + - enum: + - csot,pp8807hb1-1 + - const: novatek,nt36536 + + reg: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + vddio-supply: + description: I/O source voltage rail + + vsp-supply: + description: Positive source voltage rail + + vsn-supply: + description: Negative source voltage rail + + backlight: true + ports: true + +required: + - compatible + - reg + - vddio-supply + - reset-gpios + - ports + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + dsi { + #address-cells = <1>; + #size-cells = <0>; + + panel@0 { + compatible = "csot,pp8807hb1-1", "novatek,nt36536"; + reg = <0>; + + vddio-supply = <&vreg_iovdd_1p8>; + reset-gpios = <&tlmm 98 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + panel_in_0: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + + port@1{ + reg = <1>; + panel_in_1: endpoint { + remote-endpoint = <&dsi1_out>; + }; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml b/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml index b89f86bc0683..8382d4920bfd 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml @@ -58,6 +58,8 @@ properties: - hydis,hv070wx2-1e0 # Jenson Display BL-JT60050-01A 7" WSVGA (1024x600) color TFT LCD LVDS panel - jenson,bl-jt60050-01a + # Opto Logic SCX1001511GGC49 10.1" WXGA (1280x800) TFT LCD LVDS panel + - optologic,scx1001511ggc49 # Riverdi RVT101HVLNWC00 10.1" WXGA (1280x800) TFT LCD LVDS panel - riverdi,rvt101hvlnwc00 # Riverdi RVT70HSLNWCA0 7.0" WSVGA (1024x600) TFT LCD LVDS panel diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml index 7200095ef19e..bdf981781bd5 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml @@ -22,6 +22,7 @@ properties: - nvidia,tegra186-vic - nvidia,tegra194-vic - nvidia,tegra234-vic + - nvidia,tegra264-vic - items: - const: nvidia,tegra132-vic diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml index 3563378a01af..8312b7699cbe 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml @@ -25,6 +25,7 @@ properties: - nvidia,tegra186-host1x - nvidia,tegra194-host1x - nvidia,tegra234-host1x + - nvidia,tegra264-host1x - items: - const: nvidia,tegra132-host1x @@ -57,7 +58,8 @@ properties: enum: [1, 2] ranges: - maxItems: 1 + minItems: 1 + maxItems: 2 clocks: description: Must contain one entry, for the module clock. See @@ -192,6 +194,7 @@ allOf: contains: enum: - nvidia,tegra234-host1x + - nvidia,tegra264-host1x then: properties: reg-names: @@ -239,6 +242,21 @@ allOf: required: - reg-names + - if: + properties: + compatible: + contains: + enum: + - nvidia,tegra264-host1x + then: + properties: + ranges: + minItems: 2 + maxItems: 2 + else: + properties: + ranges: + maxItems: 1 examples: - | diff --git a/MAINTAINERS b/MAINTAINERS index 6dea93a41962..721ecafe4b2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6636,7 +6636,7 @@ F: tools/testing/selftests/cgroup/test_cpuset_v1_base.sh CONTROL GROUP - DEVICE MEMORY CONTROLLER (DMEM) M: Maarten Lankhorst <dev@lankhorst.se> M: Maxime Ripard <mripard@kernel.org> -M: Natalie Vock <natalie.vock@gmx.de> +M: Natalie Vock <nat@pixelcluster.dev> L: cgroups@vger.kernel.org L: dri-devel@lists.freedesktop.org S: Maintained diff --git a/drivers/accel/ivpu/ivpu_fw.c b/drivers/accel/ivpu/ivpu_fw.c index 5a8f7dfb54e6..b78b7c35cd9b 100644 --- a/drivers/accel/ivpu/ivpu_fw.c +++ b/drivers/accel/ivpu/ivpu_fw.c @@ -318,7 +318,7 @@ static int ivpu_fw_parse(struct ivpu_device *vdev) fw->shave_nn_size = PAGE_ALIGN(fw_hdr->shave_nn_fw_size); fw->cold_boot_entry_point = fw_hdr->entry_point; - fw->trace_level = min_t(u32, ivpu_fw_log_level, IVPU_FW_LOG_FATAL); + fw->trace_level = min(ivpu_fw_log_level, IVPU_FW_LOG_FATAL); fw->trace_destination_mask = VPU_TRACE_DESTINATION_VERBOSE_TRACING; fw->trace_hw_component_mask = -1; diff --git a/drivers/accel/ivpu/ivpu_mmu_context.c b/drivers/accel/ivpu/ivpu_mmu_context.c index c4014c83e727..bb3ca81838e3 100644 --- a/drivers/accel/ivpu/ivpu_mmu_context.c +++ b/drivers/accel/ivpu/ivpu_mmu_context.c @@ -588,8 +588,8 @@ void ivpu_mmu_context_init(struct ivpu_device *vdev, struct ivpu_mmu_context *ct start = vdev->hw->ranges.runtime.start; end = vdev->hw->ranges.shave.end; } else { - start = min_t(u64, vdev->hw->ranges.user.start, vdev->hw->ranges.shave.start); - end = max_t(u64, vdev->hw->ranges.user.end, vdev->hw->ranges.dma.end); + start = min(vdev->hw->ranges.user.start, vdev->hw->ranges.shave.start); + end = max(vdev->hw->ranges.user.end, vdev->hw->ranges.dma.end); } drm_mm_init(&ctx->mm, start, end - start); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c index eba5c6dcb5ad..d152c02419f5 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -663,6 +663,11 @@ static bool anx6345_get_chip_id(struct anx6345 *anx6345) return false; } +static void anx6345_panel_put_action(void *data) +{ + drm_panel_put(data); +} + static int anx6345_i2c_probe(struct i2c_client *client) { struct anx6345 *anx6345; @@ -691,6 +696,13 @@ static int anx6345_i2c_probe(struct i2c_client *client) if (err) DRM_DEBUG("No panel found\n"); + if (anx6345->panel) { + err = devm_add_action_or_reset(dev, anx6345_panel_put_action, + anx6345->panel); + if (err) + return err; + } + /* 1.2V digital core power regulator */ anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12"); if (IS_ERR(anx6345->dvdd12)) { diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c index cc1f88e7873e..26cc72948f31 100644 --- a/drivers/gpu/drm/bridge/fsl-ldb.c +++ b/drivers/gpu/drm/bridge/fsl-ldb.c @@ -348,6 +348,7 @@ static int fsl_ldb_probe(struct platform_device *pdev) fsl_ldb->use_termination_resistor = true; fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(fsl_ldb->panel_bridge)) return PTR_ERR(fsl_ldb->panel_bridge); diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c index f39d83a5ae37..6d97ae5640c4 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9211.c +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -660,6 +660,7 @@ static int lt9211_parse_dt(struct lt9211 *ctx) return ret; if (panel) { panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); } diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c index a82ea0c944eb..f97c8586273d 100644 --- a/drivers/gpu/drm/bridge/lvds-codec.c +++ b/drivers/gpu/drm/bridge/lvds-codec.c @@ -156,6 +156,7 @@ static int lvds_codec_probe(struct platform_device *pdev) lvds_codec->panel_bridge = devm_drm_panel_bridge_add_typed(dev, panel, lvds_codec->connector_type); + drm_panel_put(panel); if (IS_ERR(lvds_codec->panel_bridge)) return PTR_ERR(lvds_codec->panel_bridge); diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 62af499f1f5c..02388a3de626 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -294,7 +294,7 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel, return (void *)panel_bridge; panel_bridge->connector_type = connector_type; - panel_bridge->panel = panel; + panel_bridge->panel = drm_panel_get(panel); panel_bridge->bridge.of_node = panel->dev->of_node; panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES; @@ -316,6 +316,7 @@ EXPORT_SYMBOL(drm_panel_bridge_add_typed); void drm_panel_bridge_remove(struct drm_bridge *bridge) { struct panel_bridge *panel_bridge; + struct drm_panel *panel; if (!bridge) return; @@ -326,10 +327,12 @@ void drm_panel_bridge_remove(struct drm_bridge *bridge) } panel_bridge = drm_bridge_to_panel_bridge(bridge); + panel = panel_bridge->panel; drm_bridge_remove(bridge); /* TODO remove this after reworking panel_bridge lifetime */ - devm_drm_put_bridge(panel_bridge->panel->dev, bridge); + devm_drm_put_bridge(panel->dev, bridge); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_bridge_remove); @@ -357,11 +360,16 @@ EXPORT_SYMBOL(drm_panel_bridge_set_orientation); static void devm_drm_panel_bridge_release(struct device *dev, void *res) { struct drm_bridge *bridge = *(struct drm_bridge **)res; + struct panel_bridge *panel_bridge; + struct drm_panel *panel; if (!bridge) return; + panel_bridge = drm_bridge_to_panel_bridge(bridge); + panel = panel_bridge->panel; drm_bridge_remove(bridge); + drm_panel_put(panel); } /** @@ -507,8 +515,10 @@ struct drm_bridge *devm_drm_of_get_bridge(struct device *dev, if (ret) return ERR_PTR(ret); - if (panel) + if (panel) { bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); + } return bridge; } @@ -541,8 +551,10 @@ struct drm_bridge *drmm_of_get_bridge(struct drm_device *drm, if (ret) return ERR_PTR(ret); - if (panel) + if (panel) { bridge = drmm_panel_bridge_add(drm, panel); + drm_panel_put(panel); + } return bridge; } diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index 25ab475309e8..e2fc69fc51b6 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1934,6 +1934,7 @@ of_find_panel_or_bridge: panel = of_drm_find_panel(remote); if (!IS_ERR(panel)) { next_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(next_bridge)) { ret = PTR_ERR(next_bridge); next_bridge = NULL; // Inhibit the cleanup action on an ERR_PTR diff --git a/drivers/gpu/drm/bridge/ssd2825.c b/drivers/gpu/drm/bridge/ssd2825.c index 00a4ed4a0700..b0142d6d40dc 100644 --- a/drivers/gpu/drm/bridge/ssd2825.c +++ b/drivers/gpu/drm/bridge/ssd2825.c @@ -301,6 +301,7 @@ static int ssd2825_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_d if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index ce1b73214f26..8f1648f799a7 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -2329,6 +2329,7 @@ static int tc_probe_dpi_bridge_endpoint(struct tc_data *tc) if (panel) { bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } @@ -2359,6 +2360,7 @@ static int tc_probe_edp_bridge_endpoint(struct tc_data *tc) struct drm_bridge *panel_bridge; panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c index 0d85120fcc7a..112a421f99b5 100644 --- a/drivers/gpu/drm/bridge/tc358768.c +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -479,6 +479,7 @@ static int tc358768_dsi_host_attach(struct mipi_dsi_host *host, if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c index 9c9825c9b7c5..420f3b870a74 100644 --- a/drivers/gpu/drm/bridge/waveshare-dsi.c +++ b/drivers/gpu/drm/bridge/waveshare-dsi.c @@ -169,6 +169,7 @@ static int ws_bridge_probe(struct i2c_client *i2c) return dev_err_probe(dev, ret, "Failed to find remote panel\n"); ws->next_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(ws->next_bridge)) return PTR_ERR(ws->next_bridge); diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c index 1f4cff6bb4a7..e495117735e1 100644 --- a/drivers/gpu/drm/drm_of.c +++ b/drivers/gpu/drm/drm_of.c @@ -269,7 +269,8 @@ EXPORT_SYMBOL_GPL(drm_of_get_panel_orientation); * @np: device tree node containing encoder output ports * @port: port in the device tree node * @endpoint: endpoint in the device tree node - * @panel: pointer to hold returned drm_panel, must not be NULL + * @panel: pointer to hold returned drm_panel, must not be NULL. On success + * the caller must call drm_panel_put() when done with the panel * @bridge: pointer to hold returned drm_bridge * * Given a DT node's port and endpoint number, find the connected node and diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c index d7c6f4824b2d..f8f6082e637f 100644 --- a/drivers/gpu/drm/drm_panel.c +++ b/drivers/gpu/drm/drm_panel.c @@ -82,6 +82,7 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev, */ void drm_panel_add(struct drm_panel *panel) { + drm_panel_get(panel); mutex_lock(&panel_lock); list_add_tail(&panel->list, &panel_list); mutex_unlock(&panel_lock); @@ -99,6 +100,7 @@ void drm_panel_remove(struct drm_panel *panel) mutex_lock(&panel_lock); list_del_init(&panel->list); mutex_unlock(&panel_lock); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_remove); @@ -457,14 +459,17 @@ EXPORT_SYMBOL(__devm_drm_panel_alloc); #ifdef CONFIG_OF /** - * of_drm_find_panel - look up a panel using a device tree node + * of_drm_find_panel - look up and reference a panel by device tree node * @np: device tree node of the panel * * Searches the set of registered panels for one that matches the given device - * tree node. If a matching panel is found, return a pointer to it. + * tree node. If a matching panel is found, the panel's reference count is + * incremented before returning a pointer to it. The caller must call + * drm_panel_put() when it no longer needs the panel pointer. * - * Return: A pointer to the panel registered for the specified device tree - * node or an ERR_PTR() if no panel matching the device tree node can be found. + * Return: A reference-counted pointer to the panel registered for the specified + * device tree node or an ERR_PTR() if no panel matching the device tree node + * can be found. * * Possible error codes returned by this function: * @@ -483,6 +488,7 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np) list_for_each_entry(panel, &panel_list, list) { if (panel->dev->of_node == np) { + drm_panel_get(panel); mutex_unlock(&panel_lock); return panel; } @@ -494,7 +500,13 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np) EXPORT_SYMBOL(of_drm_find_panel); #endif -/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */ +/* + * Find panel by fwnode, returning a counted reference. + * + * Behaves identically to of_drm_find_panel(). On success the returned + * pointer has been passed through drm_panel_get(); the caller must call + * drm_panel_put() when done with it. + */ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode) { struct drm_panel *panel; @@ -506,6 +518,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode list_for_each_entry(panel, &panel_list, list) { if (dev_fwnode(panel->dev) == fwnode) { + drm_panel_get(panel); mutex_unlock(&panel_lock); return panel; } @@ -642,6 +655,7 @@ void drm_panel_remove_follower(struct drm_panel_follower *follower) mutex_unlock(&panel->follower_lock); put_device(panel->dev); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_remove_follower); diff --git a/drivers/gpu/drm/drm_panel.c.orig b/drivers/gpu/drm/drm_panel.c.orig new file mode 100644 index 000000000000..32fba1a562fb --- /dev/null +++ b/drivers/gpu/drm/drm_panel.c.orig @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2013, NVIDIA Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <linux/backlight.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +static DEFINE_MUTEX(panel_lock); +static LIST_HEAD(panel_list); + +/** + * DOC: drm panel + * + * The DRM panel helpers allow drivers to register panel objects with a + * central registry and provide functions to retrieve those panels in display + * drivers. + * + * For easy integration into drivers using the &drm_bridge infrastructure please + * take look at drm_panel_bridge_add() and devm_drm_panel_bridge_add(). + */ + +/** + * drm_panel_init - initialize a panel + * @panel: DRM panel + * @dev: parent device of the panel + * @funcs: panel operations + * @connector_type: the connector type (DRM_MODE_CONNECTOR_*) corresponding to + * the panel interface (must NOT be DRM_MODE_CONNECTOR_Unknown) + * + * Initialize the panel structure for subsequent registration with + * drm_panel_add(). + */ +static void drm_panel_init(struct drm_panel *panel, struct device *dev, + const struct drm_panel_funcs *funcs, + int connector_type) +{ + if (connector_type == DRM_MODE_CONNECTOR_Unknown) + DRM_WARN("%s: %s: a valid connector type is required!\n", __func__, dev_name(dev)); + + INIT_LIST_HEAD(&panel->list); + INIT_LIST_HEAD(&panel->followers); + mutex_init(&panel->follower_lock); + panel->dev = dev; + panel->funcs = funcs; + panel->connector_type = connector_type; +} + +/** + * drm_panel_add - add a panel to the global registry + * @panel: panel to add + * + * Add a panel to the global registry so that it can be looked + * up by display drivers. The panel to be added must have been + * allocated by devm_drm_panel_alloc(). + */ +void drm_panel_add(struct drm_panel *panel) +{ + drm_panel_get(panel); + mutex_lock(&panel_lock); + list_add_tail(&panel->list, &panel_list); + mutex_unlock(&panel_lock); +} +EXPORT_SYMBOL(drm_panel_add); + +/** + * drm_panel_remove - remove a panel from the global registry + * @panel: DRM panel + * + * Removes a panel from the global registry. + */ +void drm_panel_remove(struct drm_panel *panel) +{ + mutex_lock(&panel_lock); + list_del_init(&panel->list); + mutex_unlock(&panel_lock); + drm_panel_put(panel); +} +EXPORT_SYMBOL(drm_panel_remove); + +static void drm_panel_add_release(void *data) +{ + drm_panel_remove(data); +} + +/** + * devm_drm_panel_add - add a panel to the global registry using devres + * @dev: device to which the panel is attached + * @panel: panel to add + * + * Add a panel to the global registry so that it can be looked + * up by display drivers. The panel to be added must have been + * allocated by devm_drm_panel_alloc(). Unlike drm_panel_add() with this + * function there is no need to call drm_panel_remove(), it will be called + * automatically. + */ +int devm_drm_panel_add(struct device *dev, struct drm_panel *panel) +{ + drm_panel_add(panel); + + return devm_add_action_or_reset(dev, drm_panel_add_release, panel); +} +EXPORT_SYMBOL(devm_drm_panel_add); + +/** + * drm_panel_prepare - power on a panel + * @panel: DRM panel + * + * Calling this function will enable power and deassert any reset signals to + * the panel. After this has completed it is possible to communicate with any + * integrated circuitry via a command bus. This function cannot fail (as it is + * called from the pre_enable call chain). There will always be a call to + * drm_panel_disable() afterwards. + */ +void drm_panel_prepare(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + if (panel->prepared) { + dev_warn(panel->dev, "Skipping prepare of already prepared panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + if (panel->funcs && panel->funcs->prepare) { + ret = panel->funcs->prepare(panel); + if (ret < 0) + goto exit; + } + panel->prepared = true; + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_prepared) + continue; + + ret = follower->funcs->panel_prepared(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_prepared, ret); + } + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_prepare); + +/** + * drm_panel_unprepare - power off a panel + * @panel: DRM panel + * + * Calling this function will completely power off a panel (assert the panel's + * reset, turn off power supplies, ...). After this function has completed, it + * is usually no longer possible to communicate with the panel until another + * call to drm_panel_prepare(). + */ +void drm_panel_unprepare(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + /* + * If you are seeing the warning below it likely means one of two things: + * - Your panel driver incorrectly calls drm_panel_unprepare() in its + * shutdown routine. You should delete this. + * - You are using panel-edp or panel-simple and your DRM modeset + * driver's shutdown() callback happened after the panel's shutdown(). + * In this case the warning is harmless though ideally you should + * figure out how to reverse the order of the shutdown() callbacks. + */ + if (!panel->prepared) { + dev_warn(panel->dev, "Skipping unprepare of already unprepared panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_unpreparing) + continue; + + ret = follower->funcs->panel_unpreparing(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_unpreparing, ret); + } + + if (panel->funcs && panel->funcs->unprepare) { + ret = panel->funcs->unprepare(panel); + if (ret < 0) + goto exit; + } + panel->prepared = false; + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_unprepare); + +/** + * drm_panel_enable - enable a panel + * @panel: DRM panel + * + * Calling this function will cause the panel display drivers to be turned on + * and the backlight to be enabled. Content will be visible on screen after + * this call completes. This function cannot fail (as it is called from the + * enable call chain). There will always be a call to drm_panel_disable() + * afterwards. + */ +void drm_panel_enable(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + if (panel->enabled) { + dev_warn(panel->dev, "Skipping enable of already enabled panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + if (panel->funcs && panel->funcs->enable) { + ret = panel->funcs->enable(panel); + if (ret < 0) + goto exit; + } + panel->enabled = true; + + ret = backlight_enable(panel->backlight); + if (ret < 0) + DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n", + ret); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_enabled) + continue; + + ret = follower->funcs->panel_enabled(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_enabled, ret); + } + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_enable); + +/** + * drm_panel_disable - disable a panel + * @panel: DRM panel + * + * This will typically turn off the panel's backlight or disable the display + * drivers. For smart panels it should still be possible to communicate with + * the integrated circuitry via any command bus after this call. + */ +void drm_panel_disable(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + /* + * If you are seeing the warning below it likely means one of two things: + * - Your panel driver incorrectly calls drm_panel_disable() in its + * shutdown routine. You should delete this. + * - You are using panel-edp or panel-simple and your DRM modeset + * driver's shutdown() callback happened after the panel's shutdown(). + * In this case the warning is harmless though ideally you should + * figure out how to reverse the order of the shutdown() callbacks. + */ + if (!panel->enabled) { + dev_warn(panel->dev, "Skipping disable of already disabled panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_disabling) + continue; + + ret = follower->funcs->panel_disabling(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_disabling, ret); + } + + ret = backlight_disable(panel->backlight); + if (ret < 0) + DRM_DEV_INFO(panel->dev, "failed to disable backlight: %d\n", + ret); + + if (panel->funcs && panel->funcs->disable) { + ret = panel->funcs->disable(panel); + if (ret < 0) + goto exit; + } + panel->enabled = false; + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_disable); + +/** + * drm_panel_get_modes - probe the available display modes of a panel + * @panel: DRM panel + * @connector: DRM connector + * + * The modes probed from the panel are automatically added to the connector + * that the panel is attached to. + * + * Return: The number of modes available from the panel on success, or 0 on + * failure (no modes). + */ +int drm_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + if (!panel) + return 0; + + if (panel->funcs && panel->funcs->get_modes) { + int num; + + num = panel->funcs->get_modes(panel, connector); + if (num > 0) + return num; + } + + return 0; +} +EXPORT_SYMBOL(drm_panel_get_modes); + +static void __drm_panel_free(struct kref *kref) +{ + struct drm_panel *panel = container_of(kref, struct drm_panel, refcount); + + kfree(panel->container); +} + +/** + * drm_panel_get - Acquire a panel reference + * @panel: DRM panel + * + * This function increments the panel's refcount. + * Returns: + * Pointer to @panel + */ +struct drm_panel *drm_panel_get(struct drm_panel *panel) +{ + if (!panel) + return panel; + + kref_get(&panel->refcount); + + return panel; +} +EXPORT_SYMBOL(drm_panel_get); + +/** + * drm_panel_put - Release a panel reference + * @panel: DRM panel + * + * This function decrements the panel's reference count and frees the + * object if the reference count drops to zero. + */ +void drm_panel_put(struct drm_panel *panel) +{ + if (panel) + kref_put(&panel->refcount, __drm_panel_free); +} +EXPORT_SYMBOL(drm_panel_put); + +/** + * drm_panel_put_void - wrapper to drm_panel_put() taking a void pointer + * + * @data: pointer to @struct drm_panel, cast to a void pointer + * + * Wrapper of drm_panel_put() to be used when a function taking a void + * pointer is needed, for example as a devm action. + */ +static void drm_panel_put_void(void *data) +{ + struct drm_panel *panel = (struct drm_panel *)data; + + drm_panel_put(panel); +} + +void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset, + const struct drm_panel_funcs *funcs, + int connector_type) +{ + void *container; + struct drm_panel *panel; + int err; + + if (!funcs) { + dev_warn(dev, "Missing funcs pointer\n"); + return ERR_PTR(-EINVAL); + } + + container = kzalloc(size, GFP_KERNEL); + if (!container) + return ERR_PTR(-ENOMEM); + + panel = container + offset; + panel->container = container; + panel->funcs = funcs; + kref_init(&panel->refcount); + + err = devm_add_action_or_reset(dev, drm_panel_put_void, panel); + if (err) + return ERR_PTR(err); + + drm_panel_init(panel, dev, funcs, connector_type); + + return container; +} +EXPORT_SYMBOL(__devm_drm_panel_alloc); + +#ifdef CONFIG_OF +/** + * of_drm_find_panel - look up and reference a panel by device tree node + * @np: device tree node of the panel + * + * Searches the set of registered panels for one that matches the given device + * tree node. If a matching panel is found, the panel's reference count is + * incremented before returning a pointer to it. The caller must call + * drm_panel_put() when it no longer needs the panel pointer. + * + * Return: A reference-counted pointer to the panel registered for the specified + * device tree node or an ERR_PTR() if no panel matching the device tree node + * can be found. + * + * Possible error codes returned by this function: + * + * - EPROBE_DEFER: the panel device has not been probed yet, and the caller + * should retry later + * - ENODEV: the device is not available (status != "okay" or "ok") + */ +struct drm_panel *of_drm_find_panel(const struct device_node *np) +{ + struct drm_panel *panel; + + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); + + mutex_lock(&panel_lock); + + list_for_each_entry(panel, &panel_list, list) { + if (panel->dev->of_node == np) { + drm_panel_get(panel); + mutex_unlock(&panel_lock); + return panel; + } + } + + mutex_unlock(&panel_lock); + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(of_drm_find_panel); +#endif + +/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */ +static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode) +{ + struct drm_panel *panel; + + if (!fwnode_device_is_available(fwnode)) + return ERR_PTR(-ENODEV); + + mutex_lock(&panel_lock); + + list_for_each_entry(panel, &panel_list, list) { + if (dev_fwnode(panel->dev) == fwnode) { + mutex_unlock(&panel_lock); + return panel; + } + } + + mutex_unlock(&panel_lock); + + return ERR_PTR(-EPROBE_DEFER); +} + +/* Find panel by follower device */ +static struct drm_panel *find_panel_by_dev(struct device *follower_dev) +{ + struct fwnode_handle *fwnode; + struct drm_panel *panel; + + fwnode = fwnode_find_reference(dev_fwnode(follower_dev), "panel", 0); + if (IS_ERR(fwnode)) + return ERR_PTR(-ENODEV); + + panel = find_panel_by_fwnode(fwnode); + fwnode_handle_put(fwnode); + + return panel; +} + +/** + * drm_is_panel_follower() - Check if the device is a panel follower + * @dev: The 'struct device' to check + * + * This checks to see if a device needs to be power sequenced together with + * a panel using the panel follower API. + * + * The "panel" property of the follower points to the panel to be followed. + * + * Return: true if we should be power sequenced with a panel; false otherwise. + */ +bool drm_is_panel_follower(struct device *dev) +{ + /* + * The "panel" property is actually a phandle, but for simplicity we + * don't bother trying to parse it here. We just need to know if the + * property is there. + */ + return device_property_present(dev, "panel"); +} +EXPORT_SYMBOL(drm_is_panel_follower); + +/** + * drm_panel_add_follower() - Register something to follow panel state. + * @follower_dev: The 'struct device' for the follower. + * @follower: The panel follower descriptor for the follower. + * + * A panel follower is called right after preparing/enabling the panel and right + * before unpreparing/disabling the panel. It's primary intention is to power on + * an associated touchscreen, though it could be used for any similar devices. + * Multiple devices are allowed the follow the same panel. + * + * If a follower is added to a panel that's already been prepared/enabled, the + * follower's prepared/enabled callback is called right away. + * + * The "panel" property of the follower points to the panel to be followed. + * + * Return: 0 or an error code. Note that -ENODEV means that we detected that + * follower_dev is not actually following a panel. The caller may + * choose to ignore this return value if following a panel is optional. + */ +int drm_panel_add_follower(struct device *follower_dev, + struct drm_panel_follower *follower) +{ + struct drm_panel *panel; + int ret; + + panel = find_panel_by_dev(follower_dev); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + get_device(panel->dev); + follower->panel = panel; + + mutex_lock(&panel->follower_lock); + + list_add_tail(&follower->list, &panel->followers); + if (panel->prepared && follower->funcs->panel_prepared) { + ret = follower->funcs->panel_prepared(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_prepared, ret); + } + if (panel->enabled && follower->funcs->panel_enabled) { + ret = follower->funcs->panel_enabled(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_enabled, ret); + } + + mutex_unlock(&panel->follower_lock); + + return 0; +} +EXPORT_SYMBOL(drm_panel_add_follower); + +/** + * drm_panel_remove_follower() - Reverse drm_panel_add_follower(). + * @follower: The panel follower descriptor for the follower. + * + * Undo drm_panel_add_follower(). This includes calling the follower's + * unpreparing/disabling function if we're removed from a panel that's currently + * prepared/enabled. + * + * Return: 0 or an error code. + */ +void drm_panel_remove_follower(struct drm_panel_follower *follower) +{ + struct drm_panel *panel = follower->panel; + int ret; + + mutex_lock(&panel->follower_lock); + + if (panel->enabled && follower->funcs->panel_disabling) { + ret = follower->funcs->panel_disabling(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_disabling, ret); + } + if (panel->prepared && follower->funcs->panel_unpreparing) { + ret = follower->funcs->panel_unpreparing(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_unpreparing, ret); + } + list_del_init(&follower->list); + + mutex_unlock(&panel->follower_lock); + + put_device(panel->dev); +} +EXPORT_SYMBOL(drm_panel_remove_follower); + +static void drm_panel_remove_follower_void(void *follower) +{ + drm_panel_remove_follower(follower); +} + +/** + * devm_drm_panel_add_follower() - devm version of drm_panel_add_follower() + * @follower_dev: The 'struct device' for the follower. + * @follower: The panel follower descriptor for the follower. + * + * Handles calling drm_panel_remove_follower() using devm on the follower_dev. + * + * Return: 0 or an error code. + */ +int devm_drm_panel_add_follower(struct device *follower_dev, + struct drm_panel_follower *follower) +{ + int ret; + + ret = drm_panel_add_follower(follower_dev, follower); + if (ret) + return ret; + + return devm_add_action_or_reset(follower_dev, + drm_panel_remove_follower_void, follower); +} +EXPORT_SYMBOL(devm_drm_panel_add_follower); + +#if IS_REACHABLE(CONFIG_BACKLIGHT_CLASS_DEVICE) +/** + * drm_panel_of_backlight - use backlight device node for backlight + * @panel: DRM panel + * + * Use this function to enable backlight handling if your panel + * uses device tree and has a backlight phandle. + * + * When the panel is enabled backlight will be enabled after a + * successful call to &drm_panel_funcs.enable() + * + * When the panel is disabled backlight will be disabled before the + * call to &drm_panel_funcs.disable(). + * + * A typical implementation for a panel driver supporting device tree + * will call this function at probe time. Backlight will then be handled + * transparently without requiring any intervention from the driver. + * + * Return: 0 on success or a negative error code on failure. + */ +int drm_panel_of_backlight(struct drm_panel *panel) +{ + struct backlight_device *backlight; + + if (!panel || !panel->dev) + return -EINVAL; + + backlight = devm_of_find_backlight(panel->dev); + + if (IS_ERR(backlight)) + return PTR_ERR(backlight); + + panel->backlight = backlight; + return 0; +} +EXPORT_SYMBOL(drm_panel_of_backlight); +#endif + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("DRM panel infrastructure"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index d4dc8cb45bce..e26ead5b5e58 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -760,27 +760,32 @@ static void output_poll_execute(struct work_struct *work) struct drm_connector *connector; struct drm_connector_list_iter conn_iter; enum drm_connector_status old_status; - bool repoll = false, changed; + bool repoll = false, changed = false; u64 old_epoch_counter; if (!dev->mode_config.poll_enabled) return; - /* Pick up any changes detected by the probe functions. */ - changed = dev->mode_config.delayed_event; - dev->mode_config.delayed_event = false; - if (!drm_kms_helper_poll) { if (dev->mode_config.poll_running) { drm_kms_helper_disable_hpd(dev); dev->mode_config.poll_running = false; } - goto out; + + scoped_guard(mutex, &dev->mode_config.mutex) { + changed = dev->mode_config.delayed_event; + dev->mode_config.delayed_event = false; + } + + if (changed) + drm_kms_helper_hotplug_event(dev); + + return; } if (!mutex_trylock(&dev->mode_config.mutex)) { - repoll = true; - goto out; + schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); + return; } drm_connector_list_iter_begin(dev, &conn_iter); @@ -836,16 +841,23 @@ static void output_poll_execute(struct work_struct *work) connector->base.id, connector->name, old_epoch_counter, connector->epoch_counter); + drm_sysfs_connector_hotplug_event(connector); changed = true; } } drm_connector_list_iter_end(&conn_iter); + /* Pick up any changes detected by the probe functions. */ + if (dev->mode_config.delayed_event) { + dev->mode_config.delayed_event = false; + changed = true; + drm_sysfs_hotplug_event(dev); + } + mutex_unlock(&dev->mode_config.mutex); -out: if (changed) - drm_kms_helper_hotplug_event(dev); + drm_client_dev_hotplug(dev); if (repoll) schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); @@ -1081,9 +1093,9 @@ EXPORT_SYMBOL(drm_connector_helper_hpd_irq_event); */ bool drm_helper_hpd_irq_event(struct drm_device *dev) { - struct drm_connector *connector, *first_changed_connector = NULL; struct drm_connector_list_iter conn_iter; - int changed = 0; + struct drm_connector *connector; + bool changed = false; if (!dev->mode_config.poll_enabled) return false; @@ -1096,24 +1108,15 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev) continue; if (check_connector_changed(connector)) { - if (!first_changed_connector) { - drm_connector_get(connector); - first_changed_connector = connector; - } - - changed++; + changed = true; + drm_sysfs_connector_hotplug_event(connector); } } drm_connector_list_iter_end(&conn_iter); mutex_unlock(&dev->mode_config.mutex); - if (changed == 1) - drm_kms_helper_connector_hotplug_event(first_changed_connector); - else if (changed > 0) - drm_kms_helper_hotplug_event(dev); - - if (first_changed_connector) - drm_connector_put(first_changed_connector); + if (changed) + drm_client_dev_hotplug(dev); return changed; } diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c index 4dbc8a7ea6af..5c23182f4d33 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c @@ -545,13 +545,13 @@ static int etnaviv_hw_reset(struct etnaviv_gpu *gpu) u32 pulse_eater = 0x01590880; /* disable clock gating */ - gpu_write_power(gpu, VIVS_PM_POWER_CONTROLS, 0x0); + gpu_write_power_sync(gpu, VIVS_PM_POWER_CONTROLS, 0x0); /* disable pulse eater */ pulse_eater |= BIT(17); gpu_write_power(gpu, VIVS_PM_PULSE_EATER, pulse_eater); pulse_eater |= BIT(0); - gpu_write_power(gpu, VIVS_PM_PULSE_EATER, pulse_eater); + gpu_write_power_sync(gpu, VIVS_PM_PULSE_EATER, pulse_eater); /* enable clock */ control = VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(fscale); @@ -662,7 +662,7 @@ static void etnaviv_gpu_enable_mlcg(struct etnaviv_gpu *gpu) gpu->identity.revision == 0x4302) ppc |= VIVS_PM_POWER_CONTROLS_DISABLE_STALL_MODULE_CLOCK_GATING; - gpu_write_power(gpu, VIVS_PM_POWER_CONTROLS, ppc); + gpu_write_power_sync(gpu, VIVS_PM_POWER_CONTROLS, ppc); pmc = gpu_read_power(gpu, VIVS_PM_MODULE_CONTROLS); @@ -706,7 +706,7 @@ static void etnaviv_gpu_enable_mlcg(struct etnaviv_gpu *gpu) pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_HZ; pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_EZ; - gpu_write_power(gpu, VIVS_PM_MODULE_CONTROLS, pmc); + gpu_write_power_sync(gpu, VIVS_PM_MODULE_CONTROLS, pmc); } void etnaviv_gpu_start_fe(struct etnaviv_gpu *gpu, u32 address, u16 prefetch) @@ -772,7 +772,7 @@ static void etnaviv_gpu_setup_pulse_eater(struct etnaviv_gpu *gpu) pulse_eater |= BIT(18); } - gpu_write_power(gpu, VIVS_PM_PULSE_EATER, pulse_eater); + gpu_write_power_sync(gpu, VIVS_PM_PULSE_EATER, pulse_eater); } static void etnaviv_gpu_hw_init(struct etnaviv_gpu *gpu) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h index 5cb46c84e03a..e89d591d7404 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h @@ -204,6 +204,12 @@ static inline u32 gpu_read_power(struct etnaviv_gpu *gpu, u32 reg) return readl(gpu->mmio + gpu_fix_power_address(gpu, reg)); } +static inline void gpu_write_power_sync(struct etnaviv_gpu *gpu, u32 reg, u32 data) +{ + gpu_write_power(gpu, reg, data); + gpu_read_power(gpu, reg); +} + int etnaviv_gpu_get_param(struct etnaviv_gpu *gpu, u32 param, u64 *value); int etnaviv_gpu_init(struct etnaviv_gpu *gpu); diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c index b80540328150..a418011f7d4d 100644 --- a/drivers/gpu/drm/exynos/exynos_dp.c +++ b/drivers/gpu/drm/exynos/exynos_dp.c @@ -182,8 +182,15 @@ static int exynos_dp_probe(struct platform_device *pdev) out: dp->adp = analogix_dp_probe(dev, &dp->plat_data); - if (IS_ERR(dp->adp)) + if (IS_ERR(dp->adp)) { + /* + * The driver core does not invoke remove() for failed probes, + * so release the probe-time panel reference here. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); return PTR_ERR(dp->adp); + } if (dp->plat_data.panel || dp->plat_data.next_bridge) return component_add(&pdev->dev, &exynos_dp_ops); @@ -193,6 +200,16 @@ out: static void exynos_dp_remove(struct platform_device *pdev) { + struct exynos_dp_device *dp = platform_get_drvdata(pdev); + + /* + * Release the probe-time reference from of_drm_find_panel(). If bind + * ran, the panel_bridge holds a second reference that devm cleanup + * will release when the bridge is destroyed after remove() returns. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); + component_del(&pdev->dev, &exynos_dp_ops); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c index 0dc36df6ada3..9d15a0035ea9 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dpi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c @@ -245,5 +245,8 @@ int exynos_dpi_remove(struct drm_encoder *encoder) exynos_dpi_disable(&ctx->encoder); + if (ctx->panel) + drm_panel_put(ctx->panel); + return 0; } diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c index 84eff7519e32..ec71fbbb0eb8 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c @@ -109,6 +109,13 @@ err_cleanup: return ret; } +static void fsl_dcu_panel_put_action(void *data) +{ + struct drm_panel *panel = data; + + drm_panel_put(panel); +} + int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev) { struct device_node *panel_node; @@ -124,6 +131,12 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev) if (IS_ERR(fsl_dev->connector.panel)) return PTR_ERR(fsl_dev->connector.panel); + ret = devm_add_action_or_reset(fsl_dev->dev, + fsl_dcu_panel_put_action, + fsl_dev->connector.panel); + if (ret) + return ret; + return fsl_dcu_attach_panel(fsl_dev, fsl_dev->connector.panel); } @@ -132,6 +145,11 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev) return ret; if (panel) { + ret = devm_add_action_or_reset(fsl_dev->dev, + fsl_dcu_panel_put_action, panel); + if (ret) + return ret; + fsl_dev->connector.panel = panel; return fsl_dcu_attach_panel(fsl_dev, panel); } diff --git a/drivers/gpu/drm/gma500/cdv_intel_crt.c b/drivers/gpu/drm/gma500/cdv_intel_crt.c index 342a57c82846..e51a965ddeb8 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_crt.c +++ b/drivers/gpu/drm/gma500/cdv_intel_crt.c @@ -29,8 +29,8 @@ #include <linux/pm_runtime.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_modeset_helper_vtables.h> -#include <drm/drm_simple_kms_helper.h> #include "cdv_device.h" #include "intel_bios.h" @@ -217,6 +217,10 @@ static int cdv_intel_crt_set_property(struct drm_connector *connector, * Routines for controlling stuff on the analog port */ +static const struct drm_encoder_funcs cdv_intel_crt_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_intel_crt_helper_funcs = { .dpms = cdv_intel_crt_dpms, .prepare = gma_encoder_prepare, @@ -275,7 +279,8 @@ void cdv_intel_crt_init(struct drm_device *dev, goto err_ddc_destroy; encoder = &gma_encoder->base; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_DAC); + ret = drm_encoder_init(dev, encoder, &cdv_intel_crt_funcs, + DRM_MODE_ENCODER_DAC, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c index fbed35cf7603..8d0626ddf1c3 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_dp.c +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -33,9 +33,9 @@ #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_encoder.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> -#include <drm/drm_simple_kms_helper.h> #include "gma_display.h" #include "psb_drv.h" @@ -232,16 +232,17 @@ i2c_dp_aux_add_bus(struct i2c_adapter *adapter) } #define _wait_for(COND, MS, W) ({ \ - unsigned long timeout__ = jiffies + msecs_to_jiffies(MS); \ - int ret__ = 0; \ - while (! (COND)) { \ - if (time_after(jiffies, timeout__)) { \ - ret__ = -ETIMEDOUT; \ - break; \ - } \ - if (W && !in_dbg_master()) msleep(W); \ - } \ - ret__; \ + unsigned long timeout__ = jiffies + msecs_to_jiffies(MS); \ + int ret__ = 0; \ + while (!(COND)) { \ + if (time_after(jiffies, timeout__)) { \ + ret__ = -ETIMEDOUT; \ + break; \ + } \ + if (W && !in_dbg_master()) \ + msleep(W); \ + } \ + ret__; \ }) #define wait_for(COND, MS) _wait_for(COND, MS, 1) @@ -296,10 +297,10 @@ static struct ddi_regoff ddi_DP_train_table[] = { }; static uint32_t dp_vswing_premph_table[] = { - 0x55338954, 0x4000, - 0x554d8954, 0x2000, - 0x55668954, 0, - 0x559ac0d4, 0x6000, + 0x55338954, 0x4000, + 0x554d8954, 0x2000, + 0x55668954, 0, + 0x559ac0d4, 0x6000, }; /** * is_edp - is the given port attached to an eDP panel (either CPU or PCH) @@ -1141,7 +1142,7 @@ static void cdv_intel_dp_prepare(struct drm_encoder *encoder) cdv_intel_edp_backlight_off(intel_encoder); cdv_intel_edp_panel_off(intel_encoder); cdv_intel_edp_panel_vdd_on(intel_encoder); - } + } /* Wake up the sink first */ cdv_intel_dp_sink_dpms(intel_encoder, DRM_MODE_DPMS_ON); cdv_intel_dp_link_down(intel_encoder); @@ -1183,7 +1184,7 @@ cdv_intel_dp_dpms(struct drm_encoder *encoder, int mode) cdv_intel_edp_panel_off(intel_encoder); } } else { - if (edp) + if (edp) cdv_intel_edp_panel_on(intel_encoder); cdv_intel_dp_sink_dpms(intel_encoder, mode); if (!(dp_reg & DP_PORT_EN)) { @@ -1191,7 +1192,7 @@ cdv_intel_dp_dpms(struct drm_encoder *encoder, int mode) cdv_intel_dp_complete_link_train(intel_encoder); } if (edp) - cdv_intel_edp_backlight_on(intel_encoder); + cdv_intel_edp_backlight_on(intel_encoder); } } @@ -1419,8 +1420,8 @@ cdv_intel_dp_set_vswing_premph(struct gma_encoder *encoder, uint8_t signal_level DRM_DEBUG_KMS("Test2\n"); //return ; cdv_sb_reset(dev); - /* ;Swing voltage programming - ;gfx_dpio_set_reg(0xc058, 0x0505313A) */ + /* ;Swing voltage programming */ + /* ;gfx_dpio_set_reg(0xc058, 0x0505313A) */ cdv_sb_write(dev, ddi_reg->VSwing5, 0x0505313A); /* ;gfx_dpio_set_reg(0x8154, 0x43406055) */ @@ -1575,7 +1576,7 @@ cdv_intel_dp_complete_link_train(struct gma_encoder *encoder) intel_dp->train_set[0], intel_dp->link_configuration[0], intel_dp->link_configuration[1]); - /* channel eq pattern */ + /* channel eq pattern */ if (!cdv_intel_dp_set_link_train(encoder, reg, DP_TRAINING_PATTERN_2)) { @@ -1704,7 +1705,7 @@ cdv_intel_dp_detect(struct drm_connector *connector, bool force) if (edp) cdv_intel_edp_panel_vdd_off(encoder); return status; - } + } if (intel_dp->force_audio) { intel_dp->has_audio = intel_dp->force_audio > 0; @@ -1872,6 +1873,10 @@ cdv_intel_dp_destroy(struct drm_connector *connector) kfree(gma_connector); } +static const struct drm_encoder_funcs cdv_intel_dp_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_intel_dp_helper_funcs = { .dpms = cdv_intel_dp_dpms, .mode_fixup = cdv_intel_dp_mode_fixup, @@ -1956,12 +1961,12 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev gma_encoder = kzalloc_obj(struct gma_encoder); if (!gma_encoder) return; - gma_connector = kzalloc_obj(struct gma_connector); - if (!gma_connector) - goto err_connector; + gma_connector = kzalloc_obj(struct gma_connector); + if (!gma_connector) + goto err_connector; intel_dp = kzalloc_obj(struct cdv_intel_dp); if (!intel_dp) - goto err_priv; + goto err_priv; if ((output_reg == DP_C) && cdv_intel_dpc_is_edp(dev)) type = DRM_MODE_CONNECTOR_eDP; @@ -1970,13 +1975,14 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev encoder = &gma_encoder->base; drm_connector_init(dev, connector, &cdv_intel_dp_connector_funcs, type); - drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + drm_encoder_init(dev, encoder, &cdv_intel_dp_funcs, + DRM_MODE_ENCODER_TMDS, NULL); gma_connector_attach_encoder(gma_connector, gma_encoder); if (type == DRM_MODE_CONNECTOR_DisplayPort) gma_encoder->type = INTEL_OUTPUT_DISPLAYPORT; - else + else gma_encoder->type = INTEL_OUTPUT_EDP; @@ -2006,18 +2012,18 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev cdv_disable_intel_clock_gating(dev); cdv_intel_dp_i2c_init(gma_connector, gma_encoder, name); - /* FIXME:fail check */ + /* FIXME:fail check */ cdv_intel_dp_add_properties(connector); if (is_edp(gma_encoder)) { int ret; struct edp_power_seq cur; - u32 pp_on, pp_off, pp_div; + u32 pp_on, pp_off, pp_div; u32 pwm_ctrl; pp_on = REG_READ(PP_CONTROL); pp_on &= ~PANEL_UNLOCK_MASK; - pp_on |= PANEL_UNLOCK_REGS; + pp_on |= PANEL_UNLOCK_REGS; REG_WRITE(PP_CONTROL, pp_on); @@ -2025,42 +2031,42 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev pwm_ctrl |= PWM_PIPE_B; REG_WRITE(BLC_PWM_CTL2, pwm_ctrl); - pp_on = REG_READ(PP_ON_DELAYS); - pp_off = REG_READ(PP_OFF_DELAYS); - pp_div = REG_READ(PP_DIVISOR); + pp_on = REG_READ(PP_ON_DELAYS); + pp_off = REG_READ(PP_OFF_DELAYS); + pp_div = REG_READ(PP_DIVISOR); /* Pull timing values out of registers */ - cur.t1_t3 = (pp_on & PANEL_POWER_UP_DELAY_MASK) >> - PANEL_POWER_UP_DELAY_SHIFT; + cur.t1_t3 = (pp_on & PANEL_POWER_UP_DELAY_MASK) >> + PANEL_POWER_UP_DELAY_SHIFT; - cur.t8 = (pp_on & PANEL_LIGHT_ON_DELAY_MASK) >> - PANEL_LIGHT_ON_DELAY_SHIFT; + cur.t8 = (pp_on & PANEL_LIGHT_ON_DELAY_MASK) >> + PANEL_LIGHT_ON_DELAY_SHIFT; - cur.t9 = (pp_off & PANEL_LIGHT_OFF_DELAY_MASK) >> - PANEL_LIGHT_OFF_DELAY_SHIFT; + cur.t9 = (pp_off & PANEL_LIGHT_OFF_DELAY_MASK) >> + PANEL_LIGHT_OFF_DELAY_SHIFT; - cur.t10 = (pp_off & PANEL_POWER_DOWN_DELAY_MASK) >> - PANEL_POWER_DOWN_DELAY_SHIFT; + cur.t10 = (pp_off & PANEL_POWER_DOWN_DELAY_MASK) >> + PANEL_POWER_DOWN_DELAY_SHIFT; - cur.t11_t12 = ((pp_div & PANEL_POWER_CYCLE_DELAY_MASK) >> - PANEL_POWER_CYCLE_DELAY_SHIFT); + cur.t11_t12 = ((pp_div & PANEL_POWER_CYCLE_DELAY_MASK) >> + PANEL_POWER_CYCLE_DELAY_SHIFT); - DRM_DEBUG_KMS("cur t1_t3 %d t8 %d t9 %d t10 %d t11_t12 %d\n", - cur.t1_t3, cur.t8, cur.t9, cur.t10, cur.t11_t12); + DRM_DEBUG_KMS("cur t1_t3 %d t8 %d t9 %d t10 %d t11_t12 %d\n", + cur.t1_t3, cur.t8, cur.t9, cur.t10, cur.t11_t12); intel_dp->panel_power_up_delay = cur.t1_t3 / 10; - intel_dp->backlight_on_delay = cur.t8 / 10; - intel_dp->backlight_off_delay = cur.t9 / 10; - intel_dp->panel_power_down_delay = cur.t10 / 10; - intel_dp->panel_power_cycle_delay = (cur.t11_t12 - 1) * 100; + intel_dp->backlight_on_delay = cur.t8 / 10; + intel_dp->backlight_off_delay = cur.t9 / 10; + intel_dp->panel_power_down_delay = cur.t10 / 10; + intel_dp->panel_power_cycle_delay = (cur.t11_t12 - 1) * 100; - DRM_DEBUG_KMS("panel power up delay %d, power down delay %d, power cycle delay %d\n", - intel_dp->panel_power_up_delay, intel_dp->panel_power_down_delay, - intel_dp->panel_power_cycle_delay); + DRM_DEBUG_KMS("panel power up delay %d, power down delay %d, power cycle delay %d\n", + intel_dp->panel_power_up_delay, intel_dp->panel_power_down_delay, + intel_dp->panel_power_cycle_delay); - DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n", - intel_dp->backlight_on_delay, intel_dp->backlight_off_delay); + DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n", + intel_dp->backlight_on_delay, intel_dp->backlight_off_delay); cdv_intel_edp_panel_vdd_on(gma_encoder); @@ -2075,7 +2081,7 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev cdv_intel_dp_destroy(connector); goto err_connector; } else { - DRM_DEBUG_KMS("DPCD: Rev=%x LN_Rate=%x LN_CNT=%x LN_DOWNSP=%x\n", + DRM_DEBUG_KMS("DPCD: Rev=%x LN_Rate=%x LN_CNT=%x LN_DOWNSP=%x\n", intel_dp->dpcd[0], intel_dp->dpcd[1], intel_dp->dpcd[2], intel_dp->dpcd[3]); @@ -2083,7 +2089,7 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev /* The CDV reference driver moves pnale backlight setup into the displays that have a backlight: this is a good idea and one we should probably adopt, however we need to migrate all the drivers before we can do that */ - /*cdv_intel_panel_setup_backlight(dev); */ + /*cdv_intel_panel_setup_backlight(dev); */ } return; diff --git a/drivers/gpu/drm/gma500/cdv_intel_hdmi.c b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c index ce7850647778..757ff9408250 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_hdmi.c +++ b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c @@ -30,9 +30,9 @@ #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_encoder.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> -#include <drm/drm_simple_kms_helper.h> #include "cdv_device.h" #include "psb_drv.h" @@ -251,6 +251,10 @@ static void cdv_hdmi_destroy(struct drm_connector *connector) kfree(gma_connector); } +static const struct drm_encoder_funcs cdv_hdmi_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_hdmi_helper_funcs = { .dpms = cdv_hdmi_dpms, .prepare = gma_encoder_prepare, @@ -329,8 +333,8 @@ void cdv_hdmi_init(struct drm_device *dev, if (ret) goto err_ddc_destroy; - ret = drm_simple_encoder_init(dev, &gma_encoder->base, - DRM_MODE_ENCODER_TMDS); + ret = drm_encoder_init(dev, &gma_encoder->base, &cdv_hdmi_funcs, + DRM_MODE_ENCODER_TMDS, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/cdv_intel_lvds.c b/drivers/gpu/drm/gma500/cdv_intel_lvds.c index d7fd9a783cde..336ab411d699 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_lvds.c +++ b/drivers/gpu/drm/gma500/cdv_intel_lvds.c @@ -13,9 +13,9 @@ #include <linux/pm_runtime.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> -#include <drm/drm_simple_kms_helper.h> #include "cdv_device.h" #include "intel_bios.h" @@ -394,6 +394,10 @@ static int cdv_intel_lvds_set_property(struct drm_connector *connector, return 0; } +static const struct drm_encoder_funcs cdv_intel_lvds_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_intel_lvds_helper_funcs = { .dpms = cdv_intel_lvds_encoder_dpms, @@ -535,7 +539,8 @@ void cdv_intel_lvds_init(struct drm_device *dev, if (ret) goto err_destroy_ddc; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_LVDS); + ret = drm_encoder_init(dev, encoder, &cdv_intel_lvds_funcs, + DRM_MODE_ENCODER_LVDS, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/oaktrail_hdmi.c b/drivers/gpu/drm/gma500/oaktrail_hdmi.c index 403d21cbb3a2..5a6132176086 100644 --- a/drivers/gpu/drm/gma500/oaktrail_hdmi.c +++ b/drivers/gpu/drm/gma500/oaktrail_hdmi.c @@ -29,9 +29,9 @@ #include <drm/drm.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_encoder.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> -#include <drm/drm_simple_kms_helper.h> #include "psb_drv.h" #include "psb_intel_drv.h" @@ -605,6 +605,10 @@ static void oaktrail_hdmi_destroy(struct drm_connector *connector) return; } +static const struct drm_encoder_funcs oaktrail_hdmi_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs oaktrail_hdmi_helper_funcs = { .dpms = oaktrail_hdmi_dpms, .prepare = gma_encoder_prepare, @@ -648,7 +652,8 @@ void oaktrail_hdmi_init(struct drm_device *dev, &oaktrail_hdmi_connector_funcs, DRM_MODE_CONNECTOR_DVID); - drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + drm_encoder_init(dev, encoder, &oaktrail_hdmi_funcs, + DRM_MODE_ENCODER_TMDS, NULL); gma_connector_attach_encoder(gma_connector, gma_encoder); diff --git a/drivers/gpu/drm/gma500/oaktrail_lvds.c b/drivers/gpu/drm/gma500/oaktrail_lvds.c index e194d0cce067..425796d8d6c3 100644 --- a/drivers/gpu/drm/gma500/oaktrail_lvds.c +++ b/drivers/gpu/drm/gma500/oaktrail_lvds.c @@ -12,9 +12,9 @@ #include <linux/pm_runtime.h> #include <drm/drm_edid.h> +#include <drm/drm_encoder.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> -#include <drm/drm_simple_kms_helper.h> #include "intel_bios.h" #include "power.h" @@ -202,6 +202,10 @@ static void oaktrail_lvds_commit(struct drm_encoder *encoder) oaktrail_lvds_set_power(dev, gma_encoder, true); } +static const struct drm_encoder_funcs oaktrail_lvds_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs oaktrail_lvds_helper_funcs = { .dpms = oaktrail_lvds_dpms, .mode_fixup = psb_intel_lvds_mode_fixup, @@ -319,7 +323,8 @@ void oaktrail_lvds_init(struct drm_device *dev, if (ret) goto err_free_connector; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_LVDS); + ret = drm_encoder_init(dev, encoder, &oaktrail_lvds_funcs, + DRM_MODE_ENCODER_LVDS, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/psb_intel_lvds.c b/drivers/gpu/drm/gma500/psb_intel_lvds.c index 2ca164b21293..cd882786d946 100644 --- a/drivers/gpu/drm/gma500/psb_intel_lvds.c +++ b/drivers/gpu/drm/gma500/psb_intel_lvds.c @@ -12,9 +12,9 @@ #include <linux/pm_runtime.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> -#include <drm/drm_simple_kms_helper.h> #include "intel_bios.h" #include "power.h" @@ -593,6 +593,10 @@ set_prop_error: return -1; } +static const struct drm_encoder_funcs psb_intel_lvds_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs psb_intel_lvds_helper_funcs = { .dpms = psb_intel_lvds_encoder_dpms, .mode_fixup = psb_intel_lvds_mode_fixup, @@ -679,7 +683,8 @@ void psb_intel_lvds_init(struct drm_device *dev, if (ret) goto err_ddc_destroy; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_LVDS); + ret = drm_encoder_init(dev, encoder, &psb_intel_lvds_funcs, + DRM_MODE_ENCODER_LVDS, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c index 20f35c48c0b8..0efd0593d031 100644 --- a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c +++ b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c @@ -51,11 +51,12 @@ static void hv_drm_pci_remove(struct pci_dev *pdev) static const struct pci_device_id hv_drm_pci_tbl[] = { { - .vendor = PCI_VENDOR_ID_MICROSOFT, - .device = PCI_DEVICE_ID_HYPERV_VIDEO, + PCI_VDEVICE_SUB(MICROSOFT, PCI_DEVICE_ID_HYPERV_VIDEO, + 0, 0), }, { /* end of list */ } }; +MODULE_DEVICE_TABLE(pci, hv_drm_pci_tbl); /* * PCI stub to support gen1 VM. @@ -224,6 +225,7 @@ static const struct hv_vmbus_device_id hv_drm_vmbus_tbl[] = { {HV_SYNTHVID_GUID}, {} }; +MODULE_DEVICE_TABLE(vmbus, hv_drm_vmbus_tbl); static struct hv_driver hv_drm_hv_driver = { .name = KBUILD_MODNAME, @@ -249,7 +251,11 @@ static int __init hv_drm_init(void) if (ret != 0) return ret; - return vmbus_driver_register(&hv_drm_hv_driver); + ret = vmbus_driver_register(&hv_drm_hv_driver); + if (ret) + pci_unregister_driver(&hv_drm_pci_driver); + + return ret; } static void __exit hv_drm_exit(void) @@ -261,8 +267,6 @@ static void __exit hv_drm_exit(void) module_init(hv_drm_init); module_exit(hv_drm_exit); -MODULE_DEVICE_TABLE(pci, hv_drm_pci_tbl); -MODULE_DEVICE_TABLE(vmbus, hv_drm_vmbus_tbl); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Deepak Rawat <drawat.floss@gmail.com>"); MODULE_DESCRIPTION("DRM driver for Hyper-V synthetic video device"); diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c index 2691ef9af0ca..54fe4180c73c 100644 --- a/drivers/gpu/drm/imagination/pvr_device.c +++ b/drivers/gpu/drm/imagination/pvr_device.c @@ -531,12 +531,10 @@ pvr_gpu_support_level(const struct pvr_gpu_id *gpu_id) { switch (pvr_gpu_id_to_packed_bvnc(gpu_id)) { case PVR_PACKED_BVNC(33, 15, 11, 3): + case PVR_PACKED_BVNC(36, 52, 104, 182): case PVR_PACKED_BVNC(36, 53, 104, 796): return PVR_GPU_SUPPORTED; - case PVR_PACKED_BVNC(36, 52, 104, 182): - return PVR_GPU_EXPERIMENTAL; - default: return PVR_GPU_UNKNOWN; } diff --git a/drivers/gpu/drm/imagination/pvr_drv.c b/drivers/gpu/drm/imagination/pvr_drv.c index 8897b3bdeb4a..5c965ef0274f 100644 --- a/drivers/gpu/drm/imagination/pvr_drv.c +++ b/drivers/gpu/drm/imagination/pvr_drv.c @@ -43,8 +43,9 @@ * * This driver supports the following PowerVR/IMG graphics cores from Imagination Technologies: * - * * AXE-1-16M (found in Texas Instruments AM62) - * * BXS-4-64 MC1 (found in Texas Instruments J721S2/AM68) + * * AXE-1-16M (33.15.11.3) + * * BXM-4-64 MC1 (36.52.104.182) + * * BXS-4-64 MC1 (36.53.104.796) */ /** diff --git a/drivers/gpu/drm/imagination/pvr_queue.c b/drivers/gpu/drm/imagination/pvr_queue.c index 941c017399fc..54e88b4208d7 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.c +++ b/drivers/gpu/drm/imagination/pvr_queue.c @@ -3,6 +3,7 @@ #include <drm/drm_managed.h> #include <drm/gpu_scheduler.h> +#include <linux/overflow.h> #include "pvr_cccb.h" #include "pvr_context.h" @@ -36,9 +37,8 @@ static int get_xfer_ctx_state_size(struct pvr_device *pvr_dev) return err; } - return sizeof(struct rogue_fwif_frag_ctx_state) + - (num_isp_store_registers * - sizeof(((struct rogue_fwif_frag_ctx_state *)0)->frag_reg_isp_store[0])); + return struct_size_t(struct rogue_fwif_frag_ctx_state, + frag_reg_isp_store, num_isp_store_registers); } static int get_frag_ctx_state_size(struct pvr_device *pvr_dev) @@ -66,9 +66,8 @@ static int get_frag_ctx_state_size(struct pvr_device *pvr_dev) return err; } - return sizeof(struct rogue_fwif_frag_ctx_state) + - (num_isp_store_registers * - sizeof(((struct rogue_fwif_frag_ctx_state *)0)->frag_reg_isp_store[0])); + return struct_size_t(struct rogue_fwif_frag_ctx_state, + frag_reg_isp_store, num_isp_store_registers); } static int get_ctx_state_size(struct pvr_device *pvr_dev, enum drm_pvr_job_type type) diff --git a/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h b/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h index 51dc37e78f41..e72f4064af18 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h @@ -5,6 +5,8 @@ #define PVR_ROGUE_FWIF_CHECK_H #include <linux/build_bug.h> +#include <linux/overflow.h> +#include <linux/stddef.h> #define OFFSET_CHECK(type, member, offset) \ static_assert(offsetof(type, member) == (offset), \ @@ -13,6 +15,21 @@ #define SIZE_CHECK(type, size) \ static_assert(sizeof(type) == (size), #type " is incorrect size") +/* + * Where the last member of a struct is a flexible array member, using + * SIZE_CHECK() is pointless. If the structure is not already padded to + * alignment without the flexible array member, sizeof() will not match the + * offset of the flexible array member and the "correct" sizeof() value is + * completely meaningless. + * + * In those instances, use FLEX_ARRAY_CHECK() instead to assert that the final + * field is a flexible array member and that it behaves as expected. + */ +#define FLEX_ARRAY_CHECK(type, member) \ + static_assert(flex_array_size((type *)NULL, member, 1) == \ + sizeof_field(type, member[0]), \ + #type "->" #member " is incorrect size") + OFFSET_CHECK(struct rogue_fwif_file_info_buf, path, 0); OFFSET_CHECK(struct rogue_fwif_file_info_buf, info, 200); OFFSET_CHECK(struct rogue_fwif_file_info_buf, line_num, 400); @@ -157,7 +174,7 @@ OFFSET_CHECK(struct rogue_fwif_frag_ctx_state, frag_reg_pm_deallocated_mask_stat OFFSET_CHECK(struct rogue_fwif_frag_ctx_state, frag_reg_dm_pds_mtilefree_status, 4); OFFSET_CHECK(struct rogue_fwif_frag_ctx_state, ctx_state_flags, 8); OFFSET_CHECK(struct rogue_fwif_frag_ctx_state, frag_reg_isp_store, 12); -SIZE_CHECK(struct rogue_fwif_frag_ctx_state, 16); +FLEX_ARRAY_CHECK(struct rogue_fwif_frag_ctx_state, frag_reg_isp_store); OFFSET_CHECK(struct rogue_fwif_compute_ctx_state, ctx_state_flags, 0); SIZE_CHECK(struct rogue_fwif_compute_ctx_state, 4); diff --git a/drivers/gpu/drm/imx/dcss/dcss-kms.c b/drivers/gpu/drm/imx/dcss/dcss-kms.c index 50bd7f36d36d..7c5a6e2bc89a 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-kms.c +++ b/drivers/gpu/drm/imx/dcss/dcss-kms.c @@ -13,6 +13,7 @@ #include <drm/drm_gem_dma_helper.h> #include <drm/drm_gem_framebuffer_helper.h> #include <drm/drm_of.h> +#include <drm/drm_panel.h> #include <drm/drm_probe_helper.h> #include <drm/drm_vblank.h> @@ -77,6 +78,9 @@ static int dcss_kms_bridge_connector_init(struct dcss_kms_dev *kms) if (ret) return ret; + if (panel) + drm_panel_put(panel); + if (!bridge) { dev_err(ddev->dev, "No bridge found %d.\n", ret); return -ENODEV; diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c index 7e569af22391..738a80b2550f 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c @@ -1297,9 +1297,11 @@ static int ingenic_drm_bind(struct device *dev, bool has_components) goto err_drvdata; } - if (panel) + if (panel) { bridge = devm_drm_panel_bridge_add_typed(dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); + } ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder, NULL, DRM_MODE_ENCODER_DPI, NULL); diff --git a/drivers/gpu/drm/logicvc/logicvc_interface.c b/drivers/gpu/drm/logicvc/logicvc_interface.c index 689049d395c0..81f760dc07f8 100644 --- a/drivers/gpu/drm/logicvc/logicvc_interface.c +++ b/drivers/gpu/drm/logicvc/logicvc_interface.c @@ -28,6 +28,11 @@ #define logicvc_interface_from_drm_connector(c) \ container_of(c, struct logicvc_interface, drm_connector) +static void logicvc_panel_put_action(void *data) +{ + drm_panel_put(data); +} + static void logicvc_encoder_enable(struct drm_encoder *drm_encoder) { struct logicvc_drm *logicvc = logicvc_drm(drm_encoder->dev); @@ -160,6 +165,13 @@ int logicvc_interface_init(struct logicvc_drm *logicvc) if (ret == -EPROBE_DEFER) goto error_early; + if (interface->drm_panel) { + ret = devm_add_action_or_reset(dev, logicvc_panel_put_action, + interface->drm_panel); + if (ret) + goto error_early; + } + ret = drm_encoder_init(drm_dev, &interface->drm_encoder, &logicvc_encoder_funcs, encoder_type, NULL); if (ret) { diff --git a/drivers/gpu/drm/mcde/mcde_drv.c b/drivers/gpu/drm/mcde/mcde_drv.c index 5f2c462bad7e..53275b575f0c 100644 --- a/drivers/gpu/drm/mcde/mcde_drv.c +++ b/drivers/gpu/drm/mcde/mcde_drv.c @@ -153,6 +153,7 @@ static int mcde_modeset_init(struct drm_device *drm) if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) { dev_err(drm->dev, "Could not connect panel bridge\n"); diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c index 5cf44ccb02cf..694372581840 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi.c +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -1127,6 +1127,7 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) { dev_err(dev, "error adding panel bridge\n"); return PTR_ERR(bridge); diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c index 9b8fbda85d28..8b4a500347fb 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c @@ -29,6 +29,7 @@ #include <drm/drm_mode_config.h> #include <drm/drm_module.h> #include <drm/drm_of.h> +#include <drm/drm_panel.h> #include <drm/drm_probe_helper.h> #include <drm/drm_vblank.h> @@ -127,6 +128,7 @@ static int mxsfb_attach_bridge(struct mxsfb_drm_private *mxsfb) if (panel) { bridge = devm_drm_panel_bridge_add_typed(drm->dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 7ea95ab960a0..4d1ad718e09b 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1209,7 +1209,6 @@ nouveau_pmops_runtime_idle(struct device *dev) return -EBUSY; } - pm_runtime_mark_last_busy(dev); pm_runtime_autosuspend(dev); /* we don't want the main rpm_idle to call suspend - we want to autosuspend */ return 1; diff --git a/drivers/gpu/drm/omapdrm/dss/output.c b/drivers/gpu/drm/omapdrm/dss/output.c index ca891aba3820..6e9bc605ee22 100644 --- a/drivers/gpu/drm/omapdrm/dss/output.c +++ b/drivers/gpu/drm/omapdrm/dss/output.c @@ -43,6 +43,7 @@ int omapdss_device_init_output(struct omap_dss_device *out, struct drm_bridge *bridge; bridge = drm_panel_bridge_add(out->panel); + drm_panel_put(out->panel); if (IS_ERR(bridge)) { dev_err(out->dev, "unable to create panel bridge (%ld)\n", diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index cfbfb371bc67..53c0b95c26ca 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -662,6 +662,17 @@ config DRM_PANEL_NOVATEK_NT36523 around the Novatek NT36523 display controller, such as some Boe panels used in Xiaomi Mi Pad 5 and 5 Pro tablets. +config DRM_PANEL_NOVATEK_NT36536 + tristate "Novatek NT36536 panel driver" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_KMS_HELPER + help + Say Y here if you want to enable support for Novatek NT36536-based + display panels, such as the one found in the LENOVO Legion Y700 + Gen4. + config DRM_PANEL_NOVATEK_NT36672A tristate "Novatek NT36672A DSI panel" depends on GPIOLIB diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 0f29f22f589e..3b523cf37833 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35532) += panel-novatek-nt35532.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35560) += panel-novatek-nt35560.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36523) += panel-novatek-nt36523.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36536) += panel-novatek-nt36536.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672E) += panel-novatek-nt36672e.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37700F) += panel-novatek-nt37700f.o diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index f66474d3ef65..df7286679446 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -1936,7 +1936,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('A', 'U', 'O', 0x145c, &delay_200_500_e50, "B116XAB01.4"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x1999, &delay_200_500_e50, "Unknown"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x1e9b, &delay_200_500_e50, "B133UAN02.1"), - EDP_PANEL_ENTRY('A', 'U', 'O', 0x1ea5, &delay_200_500_e50, "B116XAK01.6"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x1ea5, &delay_200_500_e200, "B116XAK01.6"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x203d, &delay_200_500_e50, "B140HTN02.0"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x205c, &delay_200_500_e50, "B116XAN02.0"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x208d, &delay_200_500_e50, "B140HTN02.1"), @@ -1958,7 +1958,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('A', 'U', 'O', 0x635c, &delay_200_500_e50, "B116XAN06.3"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x639c, &delay_200_500_e50, "B140HAK02.7"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x643d, &delay_200_500_e50, "B140HAN06.4"), - EDP_PANEL_ENTRY('A', 'U', 'O', 0x67a8, &delay_200_500_e50, "B140XTK02.4"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x67a8, &delay_200_500_e200, "B140XTK02.4"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x723c, &delay_200_500_e50, "B140XTN07.2"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x73aa, &delay_200_500_e50, "B116XTN02.3"), EDP_PANEL_ENTRY('A', 'U', 'O', 0x8594, &delay_200_500_e50, "B133UAN01.0"), diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36536.c b/drivers/gpu/drm/panel/panel-novatek-nt36536.c new file mode 100644 index 000000000000..2a82b54880c3 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt36536.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Novatek NT36536 DriverIC panels driver + * Copyright (c) 2026 Pengyu Luo <mitltlatltl@gmail.com> + * + * Based on the sample code which is generated with + * linux-mdss-dsi-panel-driver-generator + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/device-id/of.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +#include <video/mipi_display.h> + +struct novatek { + struct drm_panel panel; + struct mipi_dsi_device *dsi[2]; + const struct panel_desc *desc; + struct drm_dsc_config dsc; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; + struct backlight_device *backlight; +}; + +struct panel_desc { + unsigned int width_mm; + unsigned int height_mm; + unsigned int bpc; + unsigned int lanes; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; + const struct drm_dsc_config *dsc_cfg; + const struct drm_display_mode *modes; + unsigned int num_modes; + + int (*init_sequence)(struct mipi_dsi_multi_context *dsi_ctx); + + bool is_dual_dsi; + bool has_dcs_backlight; +}; + +static const struct regulator_bulk_data novatek_supplies[] = { + { .supply = "vddio" }, + { .supply = "vsp" }, + { .supply = "vsn" }, +}; + +static inline struct novatek *to_novatek(struct drm_panel *panel) +{ + return container_of(panel, struct novatek, panel); +} + +static inline struct mipi_dsi_device *to_primary_dsi(struct novatek *ctx) +{ + /* Sync on DSI1 for dual dsi */ + return ctx->desc->is_dual_dsi ? ctx->dsi[1] : ctx->dsi[0]; +} + +static void novatek_reset(struct novatek *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(11000, 12000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(3000, 4000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int novatek_prepare(struct drm_panel *panel) +{ + struct novatek *ctx = to_novatek(panel); + struct mipi_dsi_device *dsi = to_primary_dsi(ctx); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + struct drm_dsc_picture_parameter_set pps; + struct device *dev = &dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(novatek_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + novatek_reset(ctx); + + ret = ctx->desc->init_sequence(&dsi_ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(novatek_supplies), + ctx->supplies); + return ret; + } + + drm_dsc_pps_payload_pack(&pps, &ctx->dsc); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + mipi_dsi_compression_mode_multi(&dsi_ctx, true); + mipi_dsi_msleep(&dsi_ctx, 28); + + return backlight_enable(ctx->backlight); +} + +static int novatek_off(struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_dcs_set_display_off_multi(dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(dsi_ctx); + + return dsi_ctx->accum_err; +} + +static int novatek_unprepare(struct drm_panel *panel) +{ + struct novatek *ctx = to_novatek(panel); + struct mipi_dsi_device *dsi = to_primary_dsi(ctx); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + struct device *dev = &dsi->dev; + int ret; + + backlight_disable(ctx->backlight); + + ret = novatek_off(&dsi_ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(novatek_supplies), ctx->supplies); + + return 0; +} + +static int novatek_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct novatek *ctx = to_novatek(panel); + const struct panel_desc *desc = ctx->desc; + int i; + + for (i = 0; i < desc->num_modes; i++) { + const struct drm_display_mode *m = &desc->modes[i]; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER; + if (i == 0) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + connector->display_info.width_mm = desc->width_mm; + connector->display_info.height_mm = desc->height_mm; + connector->display_info.bpc = desc->bpc; + + return desc->num_modes; +} + +static const struct drm_panel_funcs novatek_panel_funcs = { + .prepare = novatek_prepare, + .unprepare = novatek_unprepare, + .get_modes = novatek_get_modes, +}; + +static int novatek_bl_update_status(struct backlight_device *bl) +{ + struct novatek *ctx = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + + return mipi_dsi_dcs_set_display_brightness_large(to_primary_dsi(ctx), + brightness); +} + +static const struct backlight_ops novatek_bl_ops = { + .update_status = novatek_bl_update_status, +}; + +static struct backlight_device *novatek_create_backlight(struct novatek *ctx) +{ + struct mipi_dsi_device *dsi = to_primary_dsi(ctx); + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 512, + .max_brightness = 4095, + .scale = BACKLIGHT_SCALE_NON_LINEAR, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, ctx, + &novatek_bl_ops, &props); +} + +static int csot_pp8807hb1_1_init_seq(struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x20); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0x50); + + /* cabc */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x23); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x89, 0xa4); + + /* pen code */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x26); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcd, 0x8f, 0xa9, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xce, 0x8c, 0xa9, 0x00); + + /* esd init */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x27); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x79, 0x22); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd0, 0x31); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd1, 0x08, 0x08); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd2, 0x08); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xd0); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x00, 0x31); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x09, 0xee); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1c, 0x77); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1d, 0x07); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xe0); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbb, 0x00); + + /* IC transfer */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xf0); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xe6, 0x02); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3b, + 0x03, 0x4e, 0x1a, 0x04, 0x04, 0x01, 0x80, + 0x36, 0x36); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x60, 0x00); + /* enable DSC */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x90, 0x03, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x91, + 0xab, 0xa8, 0x00, 0x14, 0xd2, 0x00, 0x00, + 0x00, 0x01, 0xb9, 0x00, 0x06, 0x05, 0x7a, + 0x05, 0xb8); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x92, 0x10, 0xe0); + /* frame ctl */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x91, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb3, 0x40); + mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); + mipi_dsi_msleep(dsi_ctx, 105); + mipi_dsi_dcs_set_display_on_multi(dsi_ctx); + mipi_dsi_msleep(dsi_ctx, 40); + + return dsi_ctx->accum_err; +} + +static struct drm_dsc_config csot_pp8807hb1_1_dsc_cfg = { + .dsc_version_major = 1, + .dsc_version_minor = 2, + .slice_height = 20, + .slice_width = 476, + .slice_count = 2, + .bits_per_component = 10, + .bits_per_pixel = 8 << 4, + .block_pred_enable = true, +}; + +static const struct drm_display_mode csot_pp8807hb1_1_modes[] = { + /* 915552 KHz */ + { + .clock = (952 + 138 + 16 + 16) * 2 * (3040 + 26 + 4 + 330) * 120 / 1000, + .hdisplay = 952 * 2, + .hsync_start = (952 + 138) * 2, + .hsync_end = (952 + 138 + 16) * 2, + .htotal = (952 + 138 + 16 + 16) * 2, + .vdisplay = 3040, + .vsync_start = 3040 + 26, + .vsync_end = 3040 + 26 + 4, + .vtotal = 3040 + 26 + 4 + 330, + }, + { + .clock = (952 + 138 + 16 + 16) * 2 * (3040 + 3426 + 4 + 330) * 60 / 1000, + .hdisplay = 952 * 2, + .hsync_start = (952 + 138) * 2, + .hsync_end = (952 + 138 + 16) * 2, + .htotal = (952 + 138 + 16 + 16) * 2, + .vdisplay = 3040, + .vsync_start = 3040 + 3426, + .vsync_end = 3040 + 3426 + 4, + .vtotal = 3040 + 3426 + 4 + 330, + }, + { + .clock = (952 + 138 + 16 + 16) * 2 * (3040 + 10226 + 4 + 330) * 30 / 1000, + .hdisplay = 952 * 2, + .hsync_start = (952 + 138) * 2, + .hsync_end = (952 + 138 + 16) * 2, + .htotal = (952 + 138 + 16 + 16) * 2, + .vdisplay = 3040, + .vsync_start = 3040 + 10226, + .vsync_end = 3040 + 10226 + 4, + .vtotal = 3040 + 10226 + 4 + 330, + }, + /* 1064606.4 KHz */ + { + .clock = (952 + 50 + 16 + 16) * 2 * (3040 + 26 + 4 + 50) * 165 / 1000, + .hdisplay = 952 * 2, + .hsync_start = (952 + 50) * 2, + .hsync_end = (952 + 50 + 16) * 2, + .htotal = (952 + 50 + 16 + 16) * 2, + .vdisplay = 3040, + .vsync_start = 3040 + 26, + .vsync_end = 3040 + 26 + 4, + .vtotal = 3040 + 26 + 4 + 50, + }, + { + .clock = (952 + 50 + 16 + 16) * 2 * (3040 + 481 + 4 + 50) * 144 / 1000, + .hdisplay = 952 * 2, + .hsync_start = (952 + 50) * 2, + .hsync_end = (952 + 50 + 16) * 2, + .htotal = (952 + 50 + 16 + 16) * 2, + .vdisplay = 3040, + .vsync_start = 3040 + 481, + .vsync_end = 3040 + 481 + 4, + .vtotal = 3040 + 481 + 4 + 50, + }, + /* 737942.4 KHz */ + { + .clock = (952 + 330 + 16 + 16) * 2 * (3040 + 26 + 4 + 50) * 90 / 1000, + .hdisplay = 952 * 2, + .hsync_start = (952 + 330) * 2, + .hsync_end = (952 + 330 + 16) * 2, + .htotal = (952 + 330 + 16 + 16) * 2, + .vdisplay = 3040, + .vsync_start = 3040 + 26, + .vsync_end = 3040 + 26 + 4, + .vtotal = 3040 + 26 + 4 + 50, + }, +}; + +static int novatek_probe(struct mipi_dsi_device *dsi) +{ + struct mipi_dsi_device_info dsi_info = {"dsi-secondary", 0, NULL}; + struct mipi_dsi_host *dsi1_host; + struct device *dev = &dsi->dev; + const struct panel_desc *desc; + struct device_node *dsi1; + struct novatek *ctx; + int num_dsi = 1; + int ret, i; + + ctx = devm_drm_panel_alloc(dev, struct novatek, panel, + &novatek_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(novatek_supplies), + novatek_supplies, &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + desc = of_device_get_match_data(dev); + if (!desc) + return -ENODEV; + ctx->desc = desc; + ctx->dsc = *desc->dsc_cfg; + + if (desc->is_dual_dsi) { + num_dsi = 2; + dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); + if (!dsi1) { + dev_err(dev, "cannot get secondary DSI node.\n"); + return -ENODEV; + } + + dsi1_host = of_find_mipi_dsi_host_by_node(dsi1); + of_node_put(dsi1); + if (!dsi1_host) + return dev_err_probe(dev, -EPROBE_DEFER, + "cannot get secondary DSI host\n"); + + ctx->dsi[1] = devm_mipi_dsi_device_register_full(dev, dsi1_host, + &dsi_info); + if (IS_ERR(ctx->dsi[1])) { + dev_err(dev, "cannot get secondary DSI device\n"); + return PTR_ERR(ctx->dsi[1]); + } + + mipi_dsi_set_drvdata(ctx->dsi[1], ctx); + } + + ctx->dsi[0] = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->panel.prepare_prev_first = true; + + ret = devm_drm_panel_add(dev, &ctx->panel); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to add panel\n"); + + for (i = 0; i < num_dsi; i++) { + ctx->dsi[i]->lanes = desc->lanes; + ctx->dsi[i]->format = desc->format; + ctx->dsi[i]->mode_flags = desc->mode_flags; + ctx->dsi[i]->dsc = &ctx->dsc; + ret = devm_mipi_dsi_attach(dev, ctx->dsi[i]); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, + "Failed to attach to DSI host\n"); + } + } + + if (desc->has_dcs_backlight) { + ctx->backlight = novatek_create_backlight(ctx); + if (IS_ERR(ctx->backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->backlight), + "Failed to create backlight\n"); + } else { + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + } + + return 0; +} + +/* Model name: CSOT PP8807HB1-1 */ +static const struct panel_desc csot_pp8807hb1_1_desc = { + .width_mm = 118, + .height_mm = 190, + .bpc = 10, /* set this to 8 with RGB888 format to support 8-bit mode */ + .lanes = 3, + .format = MIPI_DSI_FMT_RGB101010, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT, + .dsc_cfg = &csot_pp8807hb1_1_dsc_cfg, + .modes = csot_pp8807hb1_1_modes, + .num_modes = ARRAY_SIZE(csot_pp8807hb1_1_modes), + .init_sequence = csot_pp8807hb1_1_init_seq, + .is_dual_dsi = true, + .has_dcs_backlight = false, +}; + +static const struct of_device_id novatek_of_match[] = { + { .compatible = "csot,pp8807hb1-1", .data = &csot_pp8807hb1_1_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, novatek_of_match); + +static struct mipi_dsi_driver novatek_driver = { + .probe = novatek_probe, + .driver = { + .name = "panel-novatek-nt36536", + .of_match_table = novatek_of_match, + }, +}; +module_mipi_dsi_driver(novatek_driver); + +MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>"); +MODULE_DESCRIPTION("Novatek NT36536 DriverIC panels driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c b/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c index 2334b77f348c..63ae9dc5712f 100644 --- a/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c +++ b/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c @@ -32,11 +32,11 @@ static inline struct osd101t2587_panel *ti_osd_panel(struct drm_panel *panel) static int osd101t2587_panel_disable(struct drm_panel *panel) { struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel); - int ret; + struct mipi_dsi_multi_context ctx = { .dsi = osd101t2587->dsi }; - ret = mipi_dsi_shutdown_peripheral(osd101t2587->dsi); + mipi_dsi_shutdown_peripheral_multi(&ctx); - return ret; + return ctx.accum_err; } static int osd101t2587_panel_unprepare(struct drm_panel *panel) @@ -58,13 +58,11 @@ static int osd101t2587_panel_prepare(struct drm_panel *panel) static int osd101t2587_panel_enable(struct drm_panel *panel) { struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel); - int ret; + struct mipi_dsi_multi_context ctx = { .dsi = osd101t2587->dsi }; - ret = mipi_dsi_turn_on_peripheral(osd101t2587->dsi); - if (ret) - return ret; + mipi_dsi_turn_on_peripheral_multi(&ctx); - return ret; + return ctx.accum_err; } static const struct drm_display_mode default_mode_osd101t2587 = { diff --git a/drivers/gpu/drm/panel/panel-samsung-sofef00.c b/drivers/gpu/drm/panel/panel-samsung-sofef00.c index e00a497a7c96..f07d6533ed69 100644 --- a/drivers/gpu/drm/panel/panel-samsung-sofef00.c +++ b/drivers/gpu/drm/panel/panel-samsung-sofef00.c @@ -175,9 +175,13 @@ static const struct drm_panel_funcs sofef00_panel_panel_funcs = { static int sofef00_panel_bl_update_status(struct backlight_device *bl) { struct mipi_dsi_device *dsi = bl_get_data(bl); + struct sofef00_panel *ctx = mipi_dsi_get_drvdata(dsi); int err; u16 brightness = (u16)backlight_get_brightness(bl); + if (!ctx->panel.prepared) + return 0; + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; err = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 5c169fdf265c..292212119fda 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -5910,6 +5910,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "koe,tx31d200vm0baa", .data = &koe_tx31d200vm0baa, }, { + .compatible = "kyo,tcg070wvlq", + .data = &lg_lb070wv8, + }, { .compatible = "kyo,tcg121xglp", .data = &kyo_tcg121xglp, }, { diff --git a/drivers/gpu/drm/panfrost/panfrost_device.c b/drivers/gpu/drm/panfrost/panfrost_device.c index 7fed22d555a5..d0a23c51be39 100644 --- a/drivers/gpu/drm/panfrost/panfrost_device.c +++ b/drivers/gpu/drm/panfrost/panfrost_device.c @@ -218,7 +218,6 @@ int panfrost_device_init(struct panfrost_device *pfdev) int err; mutex_init(&pfdev->sched_lock); - INIT_LIST_HEAD(&pfdev->scheduled_jobs); INIT_LIST_HEAD(&pfdev->as_lru_list); spin_lock_init(&pfdev->as_lock); diff --git a/drivers/gpu/drm/panfrost/panfrost_device.h b/drivers/gpu/drm/panfrost/panfrost_device.h index ec55c136b1b6..1fe1554f2652 100644 --- a/drivers/gpu/drm/panfrost/panfrost_device.h +++ b/drivers/gpu/drm/panfrost/panfrost_device.h @@ -156,7 +156,6 @@ struct panfrost_device { struct panfrost_job_slot *js; struct panfrost_job *jobs[NUM_JOB_SLOTS][2]; - struct list_head scheduled_jobs; struct panfrost_perfcnt *perfcnt; bool profile_mode; diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c index ac7b1d12a0f5..8ec659b3c08e 100644 --- a/drivers/gpu/drm/pl111/pl111_drv.c +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -145,6 +145,7 @@ static int pl111_modeset_init(struct drm_device *dev) if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_Unknown); + drm_panel_put(panel); if (IS_ERR(bridge)) { ret = PTR_ERR(bridge); goto finish; diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c index db2088529b48..d8e7e9877ba8 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c @@ -69,6 +69,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(no_free_ptr(bridge)); diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c index e433ce61d431..9527d3637e6c 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c @@ -791,6 +791,7 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds) if (lvds->panel) { lvds->next_bridge = devm_drm_panel_bridge_add(lvds->dev, lvds->panel); + drm_panel_put(lvds->panel); if (IS_ERR_OR_NULL(lvds->next_bridge)) { ret = -EINVAL; goto done; diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c index f50d166b764f..3d0999e4fcfd 100644 --- a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c @@ -90,6 +90,7 @@ int rzg2l_du_encoder_init(struct rzg2l_du_device *rcdu, bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(no_free_ptr(bridge)); diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 46c245e35d21..587e60232ec7 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -28,6 +28,7 @@ #include <drm/drm_bridge_connector.h> #include <drm/bridge/analogix_dp.h> #include <drm/drm_of.h> +#include <drm/drm_panel.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> @@ -485,6 +486,16 @@ static int rockchip_dp_probe(struct platform_device *pdev) static void rockchip_dp_remove(struct platform_device *pdev) { + struct rockchip_dp_device *dp = platform_get_drvdata(pdev); + + /* + * Release the probe-time reference from of_drm_find_panel(). If bind + * ran, the panel_bridge holds a second reference that devm cleanup + * will release when the bridge is destroyed after remove() returns. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); + component_del(&pdev->dev, &rockchip_dp_component_ops); } diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c index 262f81875278..505c6c56736d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c @@ -610,6 +610,8 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master, if (lvds->panel) { lvds->bridge = drm_panel_bridge_add_typed(lvds->panel, DRM_MODE_CONNECTOR_LVDS); + drm_panel_put(lvds->panel); + lvds->panel = NULL; if (IS_ERR(lvds->bridge)) { ret = PTR_ERR(lvds->bridge); goto err_free_encoder; @@ -646,6 +648,8 @@ err_free_bridge: err_free_encoder: drm_encoder_cleanup(encoder); err_put_remote: + if (lvds->panel) + drm_panel_put(lvds->panel); of_node_put(remote); err_put_port: of_node_put(port); diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c index 2ad24b914989..0e1830bc40ee 100644 --- a/drivers/gpu/drm/rockchip/rockchip_rgb.c +++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c @@ -135,6 +135,8 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev, if (ret < 0) { DRM_DEV_ERROR(drm_dev->dev, "failed to initialize encoder: %d\n", ret); + if (panel) + drm_panel_put(panel); return ERR_PTR(ret); } @@ -143,6 +145,7 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev, if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_LVDS); + drm_panel_put(panel); if (IS_ERR(bridge)) return ERR_CAST(bridge); } diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index 4907be694aec..0940eae7a2e4 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -897,7 +897,6 @@ static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); struct iosys_map dst; unsigned int dst_pitch; - int ret = 0; /* Align y to display page boundaries */ rect->y1 = round_down(rect->y1, SSD130X_PAGE_HEIGHT); @@ -910,7 +909,7 @@ static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, ssd130x_update_rect(ssd130x, rect, buf, data_array); - return ret; + return 0; } static int ssd132x_fb_blit_rect(struct drm_framebuffer *fb, @@ -922,7 +921,6 @@ static int ssd132x_fb_blit_rect(struct drm_framebuffer *fb, struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); unsigned int dst_pitch; struct iosys_map dst; - int ret = 0; /* Align x to display segment boundaries */ rect->x1 = round_down(rect->x1, SSD132X_SEGMENT_WIDTH); @@ -936,7 +934,7 @@ static int ssd132x_fb_blit_rect(struct drm_framebuffer *fb, ssd132x_update_rect(ssd130x, rect, buf, data_array); - return ret; + return 0; } static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, @@ -948,7 +946,6 @@ static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, const struct drm_format_info *fi = drm_format_info(DRM_FORMAT_RGB332); unsigned int dst_pitch; struct iosys_map dst; - int ret = 0; if (!fi) return -EINVAL; @@ -960,7 +957,7 @@ static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, ssd133x_update_rect(ssd130x, rect, data_array, dst_pitch); - return ret; + return 0; } static int ssd130x_primary_plane_atomic_check(struct drm_plane *plane, diff --git a/drivers/gpu/drm/sti/sti_dvo.c b/drivers/gpu/drm/sti/sti_dvo.c index b48099468eb9..cdb19b75f5e1 100644 --- a/drivers/gpu/drm/sti/sti_dvo.c +++ b/drivers/gpu/drm/sti/sti_dvo.c @@ -498,6 +498,9 @@ static void sti_dvo_unbind(struct device *dev, { struct sti_dvo *dvo = dev_get_drvdata(dev); + if (dvo->panel) + drm_panel_put(dvo->panel); + drm_bridge_remove(&dvo->bridge); } diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 95fcfa48d8be..8818dcf98da7 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -34,6 +34,7 @@ #include <drm/drm_gem_atomic_helper.h> #include <drm/drm_gem_dma_helper.h> #include <drm/drm_of.h> +#include <drm/drm_panel.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h> @@ -1982,6 +1983,7 @@ int ltdc_load(struct drm_device *ddev) if (panel) { bridge = drmm_panel_bridge_add(ddev, panel); + drm_panel_put(panel); if (IS_ERR(bridge)) { drm_err(ddev, "panel-bridge endpoint %d\n", i); ret = PTR_ERR(bridge); diff --git a/drivers/gpu/drm/stm/lvds.c b/drivers/gpu/drm/stm/lvds.c index 90a44e722057..1072dbe7bd6a 100644 --- a/drivers/gpu/drm/stm/lvds.c +++ b/drivers/gpu/drm/stm/lvds.c @@ -1068,20 +1068,20 @@ static int lvds_probe(struct platform_device *pdev) if (IS_ERR(lvds->base)) { ret = PTR_ERR(lvds->base); dev_err(dev, "Unable to get regs %d\n", ret); - return ret; + goto err_put_panel; } lvds->pclk = devm_clk_get(dev, "pclk"); if (IS_ERR(lvds->pclk)) { ret = PTR_ERR(lvds->pclk); dev_err(dev, "Unable to get peripheral clock: %d\n", ret); - return ret; + goto err_put_panel; } ret = clk_prepare_enable(lvds->pclk); if (ret) { dev_err(dev, "%s: Failed to enable peripheral clk\n", __func__); - return ret; + goto err_put_panel; } rstc = devm_reset_control_get_exclusive(dev, NULL); @@ -1181,6 +1181,9 @@ static int lvds_probe(struct platform_device *pdev) err_lvds_probe: clk_disable_unprepare(lvds->pclk); +err_put_panel: + if (lvds->panel) + drm_panel_put(lvds->panel); return ret; } @@ -1189,6 +1192,9 @@ static void lvds_remove(struct platform_device *pdev) { struct stm_lvds *lvds = platform_get_drvdata(pdev); + if (lvds->panel) + drm_panel_put(lvds->panel); + lvds_pixel_clk_unregister(lvds); drm_bridge_remove(&lvds->lvds_bridge); diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c index 35a3f987c37a..6b98d20a8890 100644 --- a/drivers/gpu/drm/sun4i/sun4i_lvds.c +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c @@ -18,6 +18,11 @@ #include "sun4i_tcon.h" #include "sun4i_lvds.h" +static void sun4i_panel_put_action(void *data) +{ + drm_panel_put(data); +} + struct sun4i_lvds { struct drm_connector connector; struct drm_encoder encoder; @@ -117,6 +122,14 @@ int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) return 0; } + if (lvds->panel) { + ret = devm_add_action_or_reset(tcon->dev, + sun4i_panel_put_action, + lvds->panel); + if (ret) + return ret; + } + drm_encoder_helper_add(&lvds->encoder, &sun4i_lvds_enc_helper_funcs); ret = drm_encoder_init(drm, &lvds->encoder, &sun4i_lvds_enc_funcs, diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index 9c3fbf1b949e..3da9a9283c6e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -43,6 +43,11 @@ drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) encoder); } +static void sun4i_panel_put_action(void *data) +{ + drm_panel_put(data); +} + static int sun4i_rgb_get_modes(struct drm_connector *connector) { struct sun4i_rgb *rgb = @@ -209,6 +214,14 @@ int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon) return 0; } + if (rgb->panel) { + ret = devm_add_action_or_reset(tcon->dev, + sun4i_panel_put_action, + rgb->panel); + if (ret) + return ret; + } + drm_encoder_helper_add(&rgb->encoder, &sun4i_rgb_enc_helper_funcs); ret = drm_encoder_init(drm, &rgb->encoder, &sun4i_rgb_enc_funcs, diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 960e83c8291d..d4c1723c5e3d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -1326,6 +1326,8 @@ static int sun4i_tcon_probe(struct platform_device *pdev) ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); if (ret == -EPROBE_DEFER) return ret; + if (panel) + drm_panel_put(panel); } return component_add(&pdev->dev, &sun4i_tcon_ops); diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c index f0c9f0e573d2..d504ae583294 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -971,8 +971,10 @@ static int sun6i_dsi_attach(struct mipi_dsi_host *host, if (IS_ERR(panel)) return PTR_ERR(panel); - if (!dsi->drm || !dsi->drm->registered) + if (!dsi->drm || !dsi->drm->registered) { + drm_panel_put(panel); return -EPROBE_DEFER; + } dsi->panel = panel; dsi->device = device; @@ -989,6 +991,8 @@ static int sun6i_dsi_detach(struct mipi_dsi_host *host, { struct sun6i_dsi *dsi = host_to_sun6i_dsi(host); + if (dsi->panel) + drm_panel_put(dsi->panel); dsi->panel = NULL; dsi->device = NULL; diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 24e8ef261836..324480c87fb1 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1398,6 +1398,7 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra194-nvdec", }, { .compatible = "nvidia,tegra234-vic", }, { .compatible = "nvidia,tegra234-nvdec", }, + { .compatible = "nvidia,tegra264-vic", }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index e7fdd8c7ac12..cb88aafbd36f 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -1527,6 +1527,10 @@ static int tegra_dsi_host_attach(struct mipi_dsi_host *host, if (!dsi->master) { struct tegra_output *output = &dsi->output; + /* + * tegra_output_probe() never populates a panel for DSI + * outputs, so output->panel is always NULL here. + */ output->panel = of_drm_find_panel(device->dev.of_node); if (IS_ERR(output->panel)) output->panel = NULL; @@ -1545,6 +1549,7 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host, struct tegra_output *output = &dsi->output; if (output->panel && &device->dev == output->panel->dev) { + drm_panel_put(output->panel); output->panel = NULL; if (output->connector.dev) diff --git a/drivers/gpu/drm/tegra/falcon.c b/drivers/gpu/drm/tegra/falcon.c index 17f616bbcb45..1172356b6af3 100644 --- a/drivers/gpu/drm/tegra/falcon.c +++ b/drivers/gpu/drm/tegra/falcon.c @@ -26,8 +26,12 @@ int falcon_wait_idle(struct falcon *falcon) { u32 value; - return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value, - (value == 0), 10, 100000); + if (falcon->riscv) + return readl_poll_timeout(falcon->regs + RISCV_CPUCTL, value, + (value & RISCV_CPUCTL_ACTIVE_STAT_ACTIVE), 10, 100000); + else + return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value, + (value == 0), 10, 100000); } static int falcon_dma_wait_not_full(struct falcon *falcon) @@ -122,6 +126,17 @@ static int falcon_parse_firmware_image(struct falcon *falcon) return 0; } +static void falcon_parse_firmware_desc(struct falcon *falcon) +{ + struct falcon_fw_riscv_desc *desc = + (struct falcon_fw_riscv_desc *)falcon->firmware.desc_firmware->data; + + falcon->firmware.code.offset = desc->code_offset; + falcon->firmware.code.size = desc->code_size; + falcon->firmware.data.offset = desc->data_offset; + falcon->firmware.data.size = desc->data_size; +} + int falcon_read_firmware(struct falcon *falcon, const char *name) { int err; @@ -133,7 +148,23 @@ int falcon_read_firmware(struct falcon *falcon, const char *name) falcon->firmware.size = falcon->firmware.firmware->size; + if (falcon->riscv) { + /* Load separate descriptor */ + char desc_name[128]; + + scnprintf(desc_name, sizeof(desc_name), "%s.desc", name); + err = request_firmware(&falcon->firmware.desc_firmware, desc_name, falcon->dev); + if (err < 0) + goto release_firmware; + } + return 0; + +release_firmware: + release_firmware(falcon->firmware.firmware); + falcon->firmware.firmware = NULL; + + return err; } int falcon_load_firmware(struct falcon *falcon) @@ -144,16 +175,22 @@ int falcon_load_firmware(struct falcon *falcon) /* copy firmware image into local area. this also ensures endianness */ falcon_copy_firmware_image(falcon, firmware); - /* parse the image data */ - err = falcon_parse_firmware_image(falcon); - if (err < 0) { - dev_err(falcon->dev, "failed to parse firmware image\n"); - return err; + if (falcon->riscv) { + falcon_parse_firmware_desc(falcon); + } else { + err = falcon_parse_firmware_image(falcon); + if (err < 0) { + dev_err(falcon->dev, "failed to parse firmware image\n"); + return err; + } } release_firmware(firmware); falcon->firmware.firmware = NULL; + release_firmware(falcon->firmware.desc_firmware); + falcon->firmware.desc_firmware = NULL; + return 0; } @@ -168,6 +205,9 @@ void falcon_exit(struct falcon *falcon) { if (falcon->firmware.firmware) release_firmware(falcon->firmware.firmware); + + if (falcon->firmware.desc_firmware) + release_firmware(falcon->firmware.desc_firmware); } int falcon_boot(struct falcon *falcon) @@ -229,9 +269,15 @@ int falcon_boot(struct falcon *falcon) FALCON_ITFEN_CTXEN, FALCON_ITFEN); - /* boot falcon */ - falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC); - falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL); + if (falcon->riscv) { + falcon_writel(falcon, RISCV_BCR_CTRL_CORE_SELECT_RISCV, RISCV_BCR_CTRL); + falcon_writel(falcon, 0x0, RISCV_BOOT_VECTOR_HI); + falcon_writel(falcon, 0x100000, RISCV_BOOT_VECTOR_LO); + falcon_writel(falcon, RISCV_CPUCTL_STARTCPU, RISCV_CPUCTL); + } else { + falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC); + falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL); + } err = falcon_wait_idle(falcon); if (err < 0) { diff --git a/drivers/gpu/drm/tegra/falcon.h b/drivers/gpu/drm/tegra/falcon.h index 902bb7e4fd0f..37a17c6136b3 100644 --- a/drivers/gpu/drm/tegra/falcon.h +++ b/drivers/gpu/drm/tegra/falcon.h @@ -55,6 +55,16 @@ #define FALCON_DMATRFFBOFFS 0x0000111c +#define RISCV_BOOT_VECTOR_LO 0x00001780 +#define RISCV_BOOT_VECTOR_HI 0x00001784 + +#define RISCV_CPUCTL 0x00001788 +#define RISCV_CPUCTL_STARTCPU (1 << 0) +#define RISCV_CPUCTL_ACTIVE_STAT_ACTIVE (1 << 7) + +#define RISCV_BCR_CTRL 0x00001a68 +#define RISCV_BCR_CTRL_CORE_SELECT_RISCV (1 << 4) + struct falcon_fw_bin_header_v1 { u32 magic; /* 0x10de */ u32 version; /* version of bin format (1) */ @@ -76,6 +86,14 @@ struct falcon_fw_os_header_v1 { u32 data_size; }; +struct falcon_fw_riscv_desc { + u32 reserved[74]; + u32 data_offset; + u32 data_size; + u32 code_offset; + u32 code_size; +}; + struct falcon_firmware_section { unsigned long offset; size_t size; @@ -84,6 +102,8 @@ struct falcon_firmware_section { struct falcon_firmware { /* Firmware after it is read but not loaded */ const struct firmware *firmware; + /* RISC-V firmware descriptor */ + const struct firmware *desc_firmware; /* Raw firmware data */ dma_addr_t iova; @@ -102,6 +122,9 @@ struct falcon { struct device *dev; void __iomem *regs; + /* Peregrine falcon, external boot */ + bool riscv; + struct falcon_firmware firmware; }; diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 49e4f63a5550..d0ba3bd0aab8 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -117,11 +117,19 @@ int tegra_output_probe(struct tegra_output *output) */ WARN_ON(output->panel || output->bridge); + if (output->panel) { + drm_panel_put(output->panel); + output->panel = NULL; + } + output->panel = of_drm_find_panel(panel); of_node_put(panel); - if (IS_ERR(output->panel)) - return PTR_ERR(output->panel); + if (IS_ERR(output->panel)) { + err = PTR_ERR(output->panel); + output->panel = NULL; + return err; + } } ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0); @@ -131,7 +139,7 @@ int tegra_output_probe(struct tegra_output *output) if (!output->ddc) { err = -EPROBE_DEFER; - return err; + goto put_i2c; } } @@ -185,6 +193,11 @@ int tegra_output_probe(struct tegra_output *output) return 0; put_i2c: + if (output->panel) { + drm_panel_put(output->panel); + output->panel = NULL; + } + if (output->ddc) i2c_put_adapter(output->ddc); @@ -195,6 +208,11 @@ put_i2c: void tegra_output_remove(struct tegra_output *output) { + if (output->panel) { + drm_panel_put(output->panel); + output->panel = NULL; + } + if (output->hpd_gpio) free_irq(output->hpd_irq, output); diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c index 332c9b563d3f..fe097e967551 100644 --- a/drivers/gpu/drm/tegra/vic.c +++ b/drivers/gpu/drm/tegra/vic.c @@ -8,6 +8,7 @@ #include <linux/dma-mapping.h> #include <linux/host1x.h> #include <linux/iommu.h> +#include <linux/iopoll.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -20,10 +21,16 @@ #include "falcon.h" #include "vic.h" +#define VIC_FALCON_DEBUGINFO 0x1094 +#define VIC_DEBUGINFO_DUMMY 0xabcd1234 +#define VIC_DEBUGINFO_CLEAR 0x0 + struct vic_config { const char *firmware; unsigned int version; bool supports_sid; + bool has_riscv; + unsigned int transcfg_offset; }; struct vic { @@ -54,8 +61,8 @@ static void vic_writel(struct vic *vic, u32 value, unsigned int offset) static int vic_boot(struct vic *vic) { - u32 fce_ucode_size, fce_bin_data_offset, stream_id; - void *hdr; + u32 stream_id; + u32 val; int err = 0; if (vic->config->supports_sid && tegra_dev_iommu_get_stream_id(vic->dev, &stream_id)) { @@ -63,7 +70,7 @@ static int vic_boot(struct vic *vic) value = TRANSCFG_ATT(1, TRANSCFG_SID_FALCON) | TRANSCFG_ATT(0, TRANSCFG_SID_HW); - vic_writel(vic, value, VIC_TFBIF_TRANSCFG); + vic_writel(vic, value, vic->config->transcfg_offset); /* * STREAMID0 is used for input/output buffers. Initialize it to SID_VIC in case @@ -85,31 +92,51 @@ static int vic_boot(struct vic *vic) CG_WAKEUP_DLY_CNT(4), NV_PVIC_MISC_PRI_VIC_CG); + if (vic->config->has_riscv) { + /* Write a known pattern into DEBUGINFO register */ + vic_writel(vic, VIC_DEBUGINFO_DUMMY, VIC_FALCON_DEBUGINFO); + } + err = falcon_boot(&vic->falcon); if (err < 0) return err; - hdr = vic->falcon.firmware.virt; - fce_bin_data_offset = *(u32 *)(hdr + VIC_UCODE_FCE_DATA_OFFSET); - - /* Old VIC firmware needs kernel help with setting up FCE microcode. */ - if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) { - hdr = vic->falcon.firmware.virt + - *(u32 *)(hdr + VIC_UCODE_FCE_HEADER_OFFSET); - fce_ucode_size = *(u32 *)(hdr + FCE_UCODE_SIZE_OFFSET); - - falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_SIZE, - fce_ucode_size); - falcon_execute_method( - &vic->falcon, VIC_SET_FCE_UCODE_OFFSET, - (vic->falcon.firmware.iova + fce_bin_data_offset) >> 8); - } + if (vic->config->has_riscv) { + /* Check VIC has reached a proper initialized state */ + err = readl_poll_timeout(vic->regs + VIC_FALCON_DEBUGINFO, val, + val == VIC_DEBUGINFO_CLEAR, + 1000, 2000000); + if (err) { + dev_err(vic->dev, "VIC not initialized, timeout, val=0x%x\n", val); + return err; + } + } else { + u32 fce_ucode_size, fce_bin_data_offset; + dma_addr_t iova; + void *hdr; + + iova = vic->falcon.firmware.iova; + hdr = vic->falcon.firmware.virt; + fce_bin_data_offset = *(u32 *)(hdr + VIC_UCODE_FCE_DATA_OFFSET); + + /* Old VIC firmware needs kernel help with setting up FCE microcode. */ + if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) { + hdr = vic->falcon.firmware.virt + + *(u32 *)(hdr + VIC_UCODE_FCE_HEADER_OFFSET); + fce_ucode_size = *(u32 *)(hdr + FCE_UCODE_SIZE_OFFSET); + + falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_SIZE, + fce_ucode_size); + falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_OFFSET, + (iova + fce_bin_data_offset) >> 8); + } - err = falcon_wait_idle(&vic->falcon); - if (err < 0) { - dev_err(vic->dev, - "failed to set application ID and FCE base\n"); - return err; + err = falcon_wait_idle(&vic->falcon); + if (err < 0) { + dev_err(vic->dev, + "failed to set application ID and FCE base\n"); + return err; + } } return 0; @@ -277,6 +304,8 @@ static int vic_load_firmware(struct vic *vic) if (!vic->config->supports_sid) { vic->can_use_context = false; + } else if (vic->config->has_riscv) { + vic->can_use_context = true; } else if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) { /* * Firmware will access FCE through STREAMID0, so context @@ -302,7 +331,6 @@ cleanup: return err; } - static int __maybe_unused vic_runtime_resume(struct device *dev) { struct vic *vic = dev_get_drvdata(dev); @@ -417,6 +445,7 @@ static const struct vic_config vic_t186_config = { .firmware = NVIDIA_TEGRA_186_VIC_FIRMWARE, .version = 0x18, .supports_sid = true, + .transcfg_offset = 0x2044, }; #define NVIDIA_TEGRA_194_VIC_FIRMWARE "nvidia/tegra194/vic.bin" @@ -425,6 +454,7 @@ static const struct vic_config vic_t194_config = { .firmware = NVIDIA_TEGRA_194_VIC_FIRMWARE, .version = 0x19, .supports_sid = true, + .transcfg_offset = 0x2044, }; #define NVIDIA_TEGRA_234_VIC_FIRMWARE "nvidia/tegra234/vic.bin" @@ -433,6 +463,18 @@ static const struct vic_config vic_t234_config = { .firmware = NVIDIA_TEGRA_234_VIC_FIRMWARE, .version = 0x23, .supports_sid = true, + .transcfg_offset = 0x2044, +}; + +#define NVIDIA_TEGRA_264_VIC_FIRMWARE "nvidia/tegra264/vic.bin" +#define NVIDIA_TEGRA_264_VIC_DESC "nvidia/tegra264/vic.bin.desc" + +static const struct vic_config vic_t264_config = { + .firmware = NVIDIA_TEGRA_264_VIC_FIRMWARE, + .version = 0x264, + .supports_sid = true, + .has_riscv = true, + .transcfg_offset = 0x2244, }; static const struct of_device_id tegra_vic_of_match[] = { @@ -441,6 +483,7 @@ static const struct of_device_id tegra_vic_of_match[] = { { .compatible = "nvidia,tegra186-vic", .data = &vic_t186_config }, { .compatible = "nvidia,tegra194-vic", .data = &vic_t194_config }, { .compatible = "nvidia,tegra234-vic", .data = &vic_t234_config }, + { .compatible = "nvidia,tegra264-vic", .data = &vic_t264_config }, { }, }; MODULE_DEVICE_TABLE(of, tegra_vic_of_match); @@ -495,6 +538,7 @@ static int vic_probe(struct platform_device *pdev) vic->falcon.dev = dev; vic->falcon.regs = vic->regs; + vic->falcon.riscv = vic->config->has_riscv; err = falcon_init(&vic->falcon); if (err < 0) @@ -571,3 +615,7 @@ MODULE_FIRMWARE(NVIDIA_TEGRA_194_VIC_FIRMWARE); #if IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) MODULE_FIRMWARE(NVIDIA_TEGRA_234_VIC_FIRMWARE); #endif +#if IS_ENABLED(CONFIG_ARCH_TEGRA_264_SOC) +MODULE_FIRMWARE(NVIDIA_TEGRA_264_VIC_FIRMWARE); +MODULE_FIRMWARE(NVIDIA_TEGRA_264_VIC_DESC); +#endif diff --git a/drivers/gpu/drm/tegra/vic.h b/drivers/gpu/drm/tegra/vic.h index acf35aac948b..aca98a09c9fb 100644 --- a/drivers/gpu/drm/tegra/vic.h +++ b/drivers/gpu/drm/tegra/vic.h @@ -21,11 +21,10 @@ #define CG_IDLE_CG_EN (1 << 6) #define CG_WAKEUP_DLY_CNT(val) ((val & 0xf) << 16) -#define VIC_TFBIF_TRANSCFG 0x00002044 -#define TRANSCFG_ATT(i, v) (((v) & 0x3) << (i * 4)) -#define TRANSCFG_SID_HW 0 -#define TRANSCFG_SID_PHY 1 -#define TRANSCFG_SID_FALCON 2 +#define TRANSCFG_ATT(i, v) (((v) & 0x3) << ((i) * 4)) +#define TRANSCFG_SID_HW 0 +#define TRANSCFG_SID_PHY 1 +#define TRANSCFG_SID_FALCON 2 /* Firmware offsets */ diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c index 1512ee2574b6..70c14c3be10d 100644 --- a/drivers/gpu/drm/tidss/tidss_kms.c +++ b/drivers/gpu/drm/tidss/tidss_kms.c @@ -162,6 +162,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) if (panel) { u32 conn_type; + int ret; dev_dbg(dev, "Setting up panel for port %d\n", i); @@ -176,7 +177,8 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) break; default: WARN_ON(1); - return -EINVAL; + ret = -EINVAL; + goto put_panel; } if (panel->connector_type != conn_type) { @@ -184,16 +186,20 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) "%s: Panel %s has incompatible connector type for vp%d (%d != %d)\n", __func__, dev_name(panel->dev), i, panel->connector_type, conn_type); - return -EINVAL; + ret = -EINVAL; + goto put_panel; } bridge = devm_drm_panel_bridge_add(dev, panel); - if (IS_ERR(bridge)) { + ret = PTR_ERR_OR_ZERO(bridge); + if (ret) dev_err(dev, "failed to set up panel bridge for port %d\n", i); - return PTR_ERR(bridge); - } +put_panel: + drm_panel_put(panel); + if (ret) + return ret; } pipes[num_pipes].hw_videoport = i; diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c index 562f3f11812a..8d0e7c7c82e8 100644 --- a/drivers/gpu/drm/tve200/tve200_drv.c +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -84,6 +84,7 @@ static int tve200_modeset_init(struct drm_device *dev) if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_Unknown); + drm_panel_put(panel); if (IS_ERR(bridge)) { ret = PTR_ERR(bridge); goto out_bridge; @@ -263,6 +264,7 @@ static const struct of_device_id tve200_of_match[] = { }, {}, }; +MODULE_DEVICE_TABLE(of, tve200_of_match); static struct platform_driver tve200_driver = { .driver = { diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 1b88b3dff757..8c46ed09f5c4 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -375,6 +375,11 @@ struct v3d_job { void (*free)(struct kref *ref); bool has_pm_ref; + + /* Whether the job needs implicit dependencies, i.e. must wait for + * other contexts still writing its BOs. + */ + bool has_implicit_dep; }; struct v3d_bin_job { diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index 5c05f1ea24bc..623df9d5bbee 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -46,6 +46,9 @@ v3d_submit_lock_reservations(struct v3d_submit *submit) for (i = 0; i < submit->job_count; i++) { struct v3d_job *job = submit->jobs[i]; + if (!job->has_implicit_dep) + continue; + for (j = 0; j < job->bo_count; j++) { ret = drm_sched_job_add_implicit_dependencies(&job->base, job->bo[j], @@ -71,7 +74,6 @@ v3d_submit_unlock_reservations(struct v3d_submit *submit) /** * v3d_lookup_bos() - Sets up job->bo[] with the GEM objects * referenced by the job. - * @dev: DRM device * @file_priv: DRM file for this fd * @job: V3D job being set up * @bo_handles: GEM handles @@ -85,23 +87,44 @@ v3d_submit_unlock_reservations(struct v3d_submit *submit) * failure, because that will happen at `v3d_job_free()`. */ static int -v3d_lookup_bos(struct v3d_submit *submit, u64 bo_handles, u32 bo_count) +v3d_lookup_bos(struct drm_file *file_priv, struct v3d_job *job, + u64 bo_handles, u32 bo_count) { - struct v3d_job *last_job = submit->jobs[submit->job_count - 1]; - - last_job->bo_count = bo_count; - - if (!last_job->bo_count) { - /* See comment on bo_index for why we have to check - * this. - */ - drm_warn(&submit->v3d->drm, "Rendering requires BOs\n"); + if (!bo_count) { + drm_warn(&job->v3d->drm, "Rendering requires BOs\n"); return -EINVAL; } - return drm_gem_objects_lookup(submit->file_priv, + job->bo_count = bo_count; + + return drm_gem_objects_lookup(file_priv, (void __user *)(uintptr_t)bo_handles, - last_job->bo_count, &last_job->bo); + job->bo_count, &job->bo); +} + +/** + * v3d_job_reference_bos() - Share another job's BOs with @dst + * @dst: job that acquires references to the BOs + * @src: job whose already-resolved BO list is shared + * + * For submissions with multiple jobs that use the same BOs, a trailing job + * shouldn't look the handles up again, as it could cause inconsistencies. + * Instead, it should reference the previous job's BOs. + */ +static int +v3d_job_reference_bos(struct v3d_job *dst, struct v3d_job *src) +{ + dst->bo = kvmalloc_objs(*dst->bo, src->bo_count); + if (!dst->bo) + return -ENOMEM; + + dst->bo_count = src->bo_count; + for (int i = 0; i < dst->bo_count; i++) { + dst->bo[i] = src->bo[i]; + drm_gem_object_get(dst->bo[i]); + } + + return 0; } static void @@ -223,13 +246,14 @@ v3d_job_add_syncobjs(struct v3d_job *job, struct drm_file *file_priv, static const struct { size_t size; void (*free)(struct kref *ref); + bool has_implicit_dep; } v3d_job_types[] = { - [V3D_BIN] = { sizeof(struct v3d_bin_job), v3d_job_free }, - [V3D_RENDER] = { sizeof(struct v3d_render_job), v3d_render_job_free }, - [V3D_TFU] = { sizeof(struct v3d_tfu_job), v3d_job_free }, - [V3D_CSD] = { sizeof(struct v3d_csd_job), v3d_job_free }, - [V3D_CACHE_CLEAN] = { sizeof(struct v3d_job), v3d_job_free }, - [V3D_CPU] = { sizeof(struct v3d_cpu_job), v3d_cpu_job_free }, + [V3D_BIN] = { sizeof(struct v3d_bin_job), v3d_job_free, false }, + [V3D_RENDER] = { sizeof(struct v3d_render_job), v3d_render_job_free, true }, + [V3D_TFU] = { sizeof(struct v3d_tfu_job), v3d_job_free, true }, + [V3D_CSD] = { sizeof(struct v3d_csd_job), v3d_job_free, true }, + [V3D_CACHE_CLEAN] = { sizeof(struct v3d_job), v3d_job_free, false }, + [V3D_CPU] = { sizeof(struct v3d_cpu_job), v3d_cpu_job_free, true }, }; static struct v3d_job * @@ -251,6 +275,7 @@ v3d_submit_add_job(struct v3d_submit *submit, enum v3d_queue queue) job->queue = queue; job->file_priv = v3d_priv; job->free = v3d_job_types[queue].free; + job->has_implicit_dep = v3d_job_types[queue].has_implicit_dep; ret = drm_sched_job_init(&job->base, &v3d_priv->sched_entity[queue], 1, v3d_priv, submit->file_priv->client_id); @@ -518,13 +543,18 @@ v3d_setup_csd_jobs_and_bos(struct v3d_submit *submit, if (ret) return ret; + ret = v3d_lookup_bos(submit->file_priv, &job->base, args->bo_handles, + args->bo_handle_count); + if (ret) + return ret; + job->args = *args; clean_job = v3d_submit_add_job(submit, V3D_CACHE_CLEAN); if (IS_ERR(clean_job)) return PTR_ERR(clean_job); - return v3d_lookup_bos(submit, args->bo_handles, args->bo_handle_count); + return v3d_job_reference_bos(clean_job, &job->base); } static void @@ -1163,22 +1193,33 @@ v3d_submit_cl_ioctl(struct drm_device *dev, void *data, if (ret) goto fail; + /* + * We don't associate the BOs with the BIN job. Fences are only + * attached to the last job in the submission chain, and BIN jobs + * don't need implicit dependencies since depending on results from + * another context is not a realistic scenario for binning. + */ + ret = v3d_lookup_bos(submit.file_priv, &render->base, + args->bo_handles, args->bo_handle_count); + if (ret) + goto fail; + if (args->flags & DRM_V3D_SUBMIT_CL_FLUSH_CACHE) { clean_job = v3d_submit_add_job(&submit, V3D_CACHE_CLEAN); if (IS_ERR(clean_job)) { ret = PTR_ERR(clean_job); goto fail; } + + ret = v3d_job_reference_bos(clean_job, &render->base); + if (ret) + goto fail; } ret = v3d_attach_perfmon_to_jobs(&submit, args->perfmon_id); if (ret) goto fail; - ret = v3d_lookup_bos(&submit, args->bo_handles, args->bo_handle_count); - if (ret) - goto fail; - ret = v3d_submit_lock_reservations(&submit); if (ret) goto fail; @@ -1557,7 +1598,8 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data, * the CSD and clean jobs in the case of indirect CSD job. */ if (args->bo_handle_count) { - ret = v3d_lookup_bos(&submit, args->bo_handles, args->bo_handle_count); + ret = v3d_lookup_bos(submit.file_priv, &cpu_job->base, + args->bo_handles, args->bo_handle_count); if (ret) goto fail; diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 74dce4be0c00..17c8635c5afa 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -3208,11 +3208,11 @@ err_disable_clk: return ret; } -static void vc4_hdmi_put_ddc_device(void *ptr) +static void vc4_hdmi_put_ddc(void *ptr) { struct vc4_hdmi *vc4_hdmi = ptr; - put_device(&vc4_hdmi->ddc->dev); + i2c_put_adapter(vc4_hdmi->ddc); } static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) @@ -3266,14 +3266,14 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return -ENODEV; } - vc4_hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); + vc4_hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); of_node_put(ddc_node); if (!vc4_hdmi->ddc) { drm_err(drm, "Failed to get ddc i2c adapter by node\n"); return -EPROBE_DEFER; } - ret = devm_add_action_or_reset(dev, vc4_hdmi_put_ddc_device, vc4_hdmi); + ret = devm_add_action_or_reset(dev, vc4_hdmi_put_ddc, vc4_hdmi); if (ret) return ret; diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile index fead483af0b4..b684fbf73841 100644 --- a/drivers/gpu/host1x/Makefile +++ b/drivers/gpu/host1x/Makefile @@ -17,7 +17,8 @@ host1x-y = \ hw/host1x05.o \ hw/host1x06.o \ hw/host1x07.o \ - hw/host1x08.o + hw/host1x08.o \ + hw/host1x10.o host1x-$(CONFIG_IOMMU_API) += \ context.o diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c index e3884096c2fe..825a943c64dd 100644 --- a/drivers/gpu/host1x/bus.c +++ b/drivers/gpu/host1x/bus.c @@ -508,7 +508,7 @@ static int host1x_device_add(struct host1x *host1x, * Add device even if there are no subdevs to ensure syncpoint functionality * is available regardless of whether any engine subdevices are present */ - if (list_empty(&device->subdevs)) { + if (list_empty(&device->subdevs) && !device->registered) { err = device_add(&device->dev); if (err < 0) dev_err(&device->dev, "failed to add device: %d\n", err); diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c index ba2e572567c0..f6d3db2c8c39 100644 --- a/drivers/gpu/host1x/cdma.c +++ b/drivers/gpu/host1x/cdma.c @@ -13,6 +13,7 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/kfifo.h> +#include <linux/overflow.h> #include <linux/slab.h> #include <trace/events/host1x.h> @@ -419,7 +420,7 @@ syncpt_incr: /* won't need a timeout when replayed */ job->timeout = 0; - syncpt_incrs = job->syncpt_end - syncpt_val; + syncpt_incrs = wrapping_sub(u32, job->syncpt_end, syncpt_val); dev_dbg(dev, "%s: CPU incr (%d)\n", __func__, syncpt_incrs); host1x_job_dump(dev, job); diff --git a/drivers/gpu/host1x/context.c b/drivers/gpu/host1x/context.c index 512f3d189ebf..94ecc8769c93 100644 --- a/drivers/gpu/host1x/context.c +++ b/drivers/gpu/host1x/context.c @@ -24,7 +24,7 @@ int host1x_memory_context_list_init(struct host1x *host1x) struct host1x_memory_context_list *cdl = &host1x->context_list; struct device_node *node = host1x->dev->of_node; struct host1x_memory_context *ctx; - unsigned int i; + unsigned int devs, i; int err; cdl->devs = NULL; @@ -35,7 +35,16 @@ int host1x_memory_context_list_init(struct host1x *host1x) if (err < 0) return 0; - cdl->len = err / 4; + devs = 0; + + for (i = 0; i < err / 4; i++) { + u32 length; + + of_property_read_u32_index(node, "iommu-map", i * 4 + 3, &length); + devs += length; + } + + cdl->len = devs; cdl->devs = kzalloc_objs(*cdl->devs, cdl->len); if (!cdl->devs) return -ENOMEM; diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c index 6433c00d5d7e..b828f773fc06 100644 --- a/drivers/gpu/host1x/debug.c +++ b/drivers/gpu/host1x/debug.c @@ -31,7 +31,7 @@ void host1x_debug_output(struct output *o, const char *fmt, ...) int len; va_start(args, fmt); - len = vsnprintf(o->buf, sizeof(o->buf), fmt, args); + len = vscnprintf(o->buf, sizeof(o->buf), fmt, args); va_end(args); o->fn(o->ctx, o->buf, len, false); @@ -43,7 +43,7 @@ void host1x_debug_cont(struct output *o, const char *fmt, ...) int len; va_start(args, fmt); - len = vsnprintf(o->buf, sizeof(o->buf), fmt, args); + len = vscnprintf(o->buf, sizeof(o->buf), fmt, args); va_end(args); o->fn(o->ctx, o->buf, len, true); diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index 3f475f0e6545..d2c64728f804 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -41,6 +41,7 @@ #include "hw/host1x06.h" #include "hw/host1x07.h" #include "hw/host1x08.h" +#include "hw/host1x10.h" void host1x_common_writel(struct host1x *host1x, u32 v, u32 r) { @@ -287,7 +288,47 @@ static const struct host1x_info host1x08_info = { .reserve_vblank_syncpts = false, }; +static const struct host1x_sid_entry tegra264_sid_table[] = { + { /* SE1 MMIO */ .base = 0x1650, .offset = 0x90, .limit = 0x90 }, + { /* SE2 MMIO */ .base = 0x1658, .offset = 0x90, .limit = 0x90 }, + { /* SE4 MMIO */ .base = 0x1660, .offset = 0x90, .limit = 0x90 }, + { /* SE1 ch */ .base = 0x1738, .offset = 0x90, .limit = 0x90 }, + { /* SE2 ch */ .base = 0x1740, .offset = 0x90, .limit = 0x90 }, + { /* SE4 ch */ .base = 0x1748, .offset = 0x90, .limit = 0x90 }, + { /* VIC ch */ .base = 0x1790, .offset = 0x30, .limit = 0x30 }, + { /* VIC MMIO */ .base = 0x1688, .offset = 0x34, .limit = 0x34 }, + { /* TSEC MMIO */ .base = 0x1690, .offset = 0x30, .limit = 0x34 }, + { /* VI MMIO */ .base = 0x1698, .offset = 0x800, .limit = 0x800 }, + { /* VI_THI MMIO */ .base = 0x16a0, .offset = 0x30, .limit = 0x34 }, + { /* ISP MMIO */ .base = 0x1680, .offset = 0x800, .limit = 0x800 }, + { /* ISP_THI MMIO */ .base = 0x16a8, .offset = 0x30, .limit = 0x34 }, + { /* VI2 MMIO */ .base = 0x16b8, .offset = 0x800, .limit = 0x800 }, + { /* VI2_THI MMIO */ .base = 0x16c0, .offset = 0x30, .limit = 0x34 }, + { /* ISP1 MMIO */ .base = 0x16c8, .offset = 0x800, .limit = 0x800 }, + { /* ISP1_THI MMIO */ .base = 0x16d0, .offset = 0x30, .limit = 0x34 }, +}; + +static const struct host1x_info host1x10_info = { + .nb_channels = 63, + .nb_pts = 1024, + .nb_mlocks = 24, + .nb_bases = 0, + .init = host1x10_init, + .sync_offset = 0x0, + .dma_mask = DMA_BIT_MASK(40), + .has_wide_gather = true, + .has_hypervisor = true, + .has_common = true, + .num_sid_entries = ARRAY_SIZE(tegra264_sid_table), + .sid_table = tegra264_sid_table, + .streamid_vm_table = { 0x1004, 128 }, + .classid_vm_table = { 0x1404, 25 }, + .mmio_vm_table = { 0x1504, 25 }, + .reserve_vblank_syncpts = false, +}; + static const struct of_device_id host1x_of_match[] = { + { .compatible = "nvidia,tegra264-host1x", .data = &host1x10_info, }, { .compatible = "nvidia,tegra234-host1x", .data = &host1x08_info, }, { .compatible = "nvidia,tegra194-host1x", .data = &host1x07_info, }, { .compatible = "nvidia,tegra186-host1x", .data = &host1x06_info, }, diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 3f3f0018eee0..08a3cf2b11a9 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -246,23 +246,24 @@ static void timeout_release_mlock(struct host1x_cdma *cdma) * so it turns out that if we don't /actually/ need MLOCKs, we can just * ignore them. * - * As such, for now just implement this on Tegra234 where things are - * stricter but also easy to implement. + * As such, for now just implement this on Tegra234 and above where things + * are stricter but also easy to implement. */ struct host1x_channel *ch = cdma_to_channel(cdma); struct host1x *host1x = cdma_to_host1x(cdma); u32 offset; switch (ch->client->class) { + case HOST1X_CLASS_VIC: + offset = HOST1X_COMMON_VIC_MLOCK; + break; +#if HOST1X_HW == 8 case HOST1X_CLASS_NVJPG1: offset = HOST1X_COMMON_NVJPG1_MLOCK; break; case HOST1X_CLASS_NVENC: offset = HOST1X_COMMON_NVENC_MLOCK; break; - case HOST1X_CLASS_VIC: - offset = HOST1X_COMMON_VIC_MLOCK; - break; case HOST1X_CLASS_NVJPG: offset = HOST1X_COMMON_NVJPG_MLOCK; break; @@ -272,6 +273,7 @@ static void timeout_release_mlock(struct host1x_cdma *cdma) case HOST1X_CLASS_OFA: offset = HOST1X_COMMON_OFA_MLOCK; break; +#endif default: WARN(1, "%s was not updated for class %u", __func__, ch->client->class); return; @@ -355,7 +357,7 @@ static int cdma_timeout_init(struct host1x_cdma *cdma) static void cdma_timeout_destroy(struct host1x_cdma *cdma) { if (cdma->timeout.initialized) - cancel_delayed_work(&cdma->timeout.wq); + cancel_delayed_work_sync(&cdma->timeout.wq); cdma->timeout.initialized = false; } diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index 2df6a16d484e..a8251ec0810c 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -7,6 +7,7 @@ #include <linux/host1x.h> #include <linux/iommu.h> +#include <linux/overflow.h> #include <linux/slab.h> #include <trace/events/host1x.h> @@ -36,10 +37,9 @@ static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo, for (i = 0; i < words; i += TRACE_MAX_LENGTH) { u32 num_words = min(words - i, TRACE_MAX_LENGTH); - offset += i * sizeof(u32); - trace_host1x_cdma_push_gather(dev_name(dev), bo, - num_words, offset, + num_words, + offset + i * sizeof(u32), mem); } @@ -121,7 +121,8 @@ static void submit_gathers(struct host1x_job *job, struct host1x_job_cmd *cmds, if (cmd->is_wait) { if (cmd->wait.relative) - threshold = job_syncpt_base + cmd->wait.threshold; + threshold = wrapping_add(u32, job_syncpt_base, + cmd->wait.threshold); else threshold = cmd->wait.threshold; @@ -260,7 +261,8 @@ prefences_done: /* Submit work. */ job->syncpt_end = host1x_syncpt_incr_max(sp, job->syncpt_incrs); - submit_gathers(job, job->cmds + i, job->num_cmds - i, job->syncpt_end - job->syncpt_incrs); + submit_gathers(job, job->cmds + i, job->num_cmds - i, + wrapping_sub(u32, job->syncpt_end, job->syncpt_incrs)); /* Before releasing MLOCK, ensure engine is idle again. */ fence = host1x_syncpt_incr_max(sp, 1); @@ -298,7 +300,8 @@ prefences_done: job->syncpt_end = host1x_syncpt_incr_max(sp, job->syncpt_incrs); - submit_gathers(job, job->cmds, job->num_cmds, job->syncpt_end - job->syncpt_incrs); + submit_gathers(job, job->cmds, job->num_cmds, + wrapping_sub(u32, job->syncpt_end, job->syncpt_incrs)); #endif } diff --git a/drivers/gpu/host1x/hw/host1x10.c b/drivers/gpu/host1x/hw/host1x10.c new file mode 100644 index 000000000000..2800f309bf6f --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x10.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Host1x init for Tegra264 SoCs + * + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +/* include hw specification */ +#include "host1x10.h" +#include "host1x10_hardware.h" + +/* include code */ +#define HOST1X_HW 10 + +#include "cdma_hw.c" +#include "channel_hw.c" +#include "debug_hw.c" +#include "intr_hw.c" +#include "syncpt_hw.c" + +#include "../dev.h" + +int host1x10_init(struct host1x *host) +{ + host->channel_op = &host1x_channel_ops; + host->cdma_op = &host1x_cdma_ops; + host->cdma_pb_op = &host1x_pushbuffer_ops; + host->syncpt_op = &host1x_syncpt_ops; + host->intr_op = &host1x_intr_ops; + host->debug_op = &host1x_debug_ops; + + return 0; +} diff --git a/drivers/gpu/host1x/hw/host1x10.h b/drivers/gpu/host1x/hw/host1x10.h new file mode 100644 index 000000000000..577f6ff3dff5 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x10.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Host1x init for Tegra264 SoCs + * + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef HOST1X_HOST1X10_H +#define HOST1X_HOST1X10_H + +struct host1x; + +int host1x10_init(struct host1x *host); + +#endif diff --git a/drivers/gpu/host1x/hw/host1x10_hardware.h b/drivers/gpu/host1x/hw/host1x10_hardware.h new file mode 100644 index 000000000000..abbead8190b1 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x10_hardware.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Tegra host1x Register Offsets for Tegra264 + * + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __HOST1X_HOST1X10_HARDWARE_H +#define __HOST1X_HOST1X10_HARDWARE_H + +#include <linux/types.h> +#include <linux/bitops.h> + +#include "hw_host1x10_uclass.h" +#include "hw_host1x10_vm.h" +#include "hw_host1x10_hypervisor.h" +#include "hw_host1x10_common.h" + +#include "opcodes.h" + +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x10_common.h b/drivers/gpu/host1x/hw/hw_host1x10_common.h new file mode 100644 index 000000000000..48a632672a47 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_common.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#define HOST1X_COMMON_VIC_MLOCK 0x4060 diff --git a/drivers/gpu/host1x/hw/hw_host1x10_hypervisor.h b/drivers/gpu/host1x/hw/hw_host1x10_hypervisor.h new file mode 100644 index 000000000000..8c9069caffa8 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_hypervisor.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#define HOST1X_HV_SYNCPT_PROT_EN 0x172c +#define HOST1X_HV_SYNCPT_PROT_EN_CH_EN BIT(1) +#define HOST1X_HV_CH_MLOCK_EN(x) (0x1708 + (x * 4)) +#define HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(x) (0x1718 + (x * 4)) +#define HOST1X_HV_SYNCPT_VM(x) (0x0 + 4 * (x)) diff --git a/drivers/gpu/host1x/hw/hw_host1x10_uclass.h b/drivers/gpu/host1x/hw/hw_host1x10_uclass.h new file mode 100644 index 000000000000..abe83e67fa83 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_uclass.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + + /* + * Function naming determines intended use: + * + * <x>_r(void) : Returns the offset for register <x>. + * + * <x>_w(void) : Returns the word offset for word (4 byte) element <x>. + * + * <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits. + * + * <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field <y> of register <x>. This value + * can be |'d with others to produce a full register value for + * register <x>. + * + * <x>_<y>_m(void) : Returns a mask for field <y> of register <x>. This + * value can be ~'d and then &'d to clear the value of field <y> for + * register <x>. + * + * <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted + * to place it at field <y> of register <x>. This value can be |'d + * with others to produce a full register value for <x>. + * + * <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register + * <x> value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field <y> of register <x>. + * + * <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for + * field <y> of register <x>. This value is suitable for direct + * comparison with unshifted values appropriate for use in field <y> + * of register <x>. + */ + +#ifndef HOST1X_HW_HOST1X10_UCLASS_H +#define HOST1X_HW_HOST1X10_UCLASS_H + +static inline u32 host1x_uclass_incr_syncpt_r(void) +{ + return 0x0; +} +#define HOST1X_UCLASS_INCR_SYNCPT \ + host1x_uclass_incr_syncpt_r() +static inline u32 host1x_uclass_incr_syncpt_cond_f(u32 v) +{ + return (v & 0xff) << 10; +} +#define HOST1X_UCLASS_INCR_SYNCPT_COND_F(v) \ + host1x_uclass_incr_syncpt_cond_f(v) +static inline u32 host1x_uclass_incr_syncpt_indx_f(u32 v) +{ + return (v & 0x3ff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_INDX_F(v) \ + host1x_uclass_incr_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_r(void) +{ + return 0x8; +} +#define HOST1X_UCLASS_WAIT_SYNCPT \ + host1x_uclass_wait_syncpt_r() +static inline u32 host1x_uclass_wait_syncpt_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_INDX_F(v) \ + host1x_uclass_wait_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_thresh_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_THRESH_F(v) \ + host1x_uclass_wait_syncpt_thresh_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_r(void) +{ + return 0x9; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE \ + host1x_uclass_wait_syncpt_base_r() +static inline u32 host1x_uclass_wait_syncpt_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_INDX_F(v) \ + host1x_uclass_wait_syncpt_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 16; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_wait_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_offset_f(u32 v) +{ + return (v & 0xffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_OFFSET_F(v) \ + host1x_uclass_wait_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_load_syncpt_base_r(void) +{ + return 0xb; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE \ + host1x_uclass_load_syncpt_base_r() +static inline u32 host1x_uclass_load_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_load_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_load_syncpt_base_value_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(v) \ + host1x_uclass_load_syncpt_base_value_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_incr_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_offset_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_OFFSET_F(v) \ + host1x_uclass_incr_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_indoff_r(void) +{ + return 0x2d; +} +#define HOST1X_UCLASS_INDOFF \ + host1x_uclass_indoff_r() +static inline u32 host1x_uclass_indoff_indbe_f(u32 v) +{ + return (v & 0xf) << 28; +} +#define HOST1X_UCLASS_INDOFF_INDBE_F(v) \ + host1x_uclass_indoff_indbe_f(v) +static inline u32 host1x_uclass_indoff_autoinc_f(u32 v) +{ + return (v & 0x1) << 27; +} +#define HOST1X_UCLASS_INDOFF_AUTOINC_F(v) \ + host1x_uclass_indoff_autoinc_f(v) +static inline u32 host1x_uclass_indoff_indmodid_f(u32 v) +{ + return (v & 0xff) << 18; +} +#define HOST1X_UCLASS_INDOFF_INDMODID_F(v) \ + host1x_uclass_indoff_indmodid_f(v) +static inline u32 host1x_uclass_indoff_indroffset_f(u32 v) +{ + return (v & 0xffff) << 2; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ + host1x_uclass_indoff_indroffset_f(v) +static inline u32 host1x_uclass_indoff_rwn_read_v(void) +{ + return 1; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ + host1x_uclass_indoff_indroffset_f(v) +static inline u32 host1x_uclass_load_syncpt_payload_32_r(void) +{ + return 0x4e; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_PAYLOAD_32 \ + host1x_uclass_load_syncpt_payload_32_r() +static inline u32 host1x_uclass_wait_syncpt_32_r(void) +{ + return 0x50; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_32 \ + host1x_uclass_wait_syncpt_32_r() + +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x10_vm.h b/drivers/gpu/host1x/hw/hw_host1x10_vm.h new file mode 100644 index 000000000000..75f5b881c561 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_vm.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#define HOST1X_CHANNEL_DMASTART 0x0000 +#define HOST1X_CHANNEL_DMASTART_HI 0x0004 +#define HOST1X_CHANNEL_DMAPUT 0x0008 +#define HOST1X_CHANNEL_DMAPUT_HI 0x000c +#define HOST1X_CHANNEL_DMAGET 0x0010 +#define HOST1X_CHANNEL_DMAGET_HI 0x0014 +#define HOST1X_CHANNEL_DMAEND 0x0018 +#define HOST1X_CHANNEL_DMAEND_HI 0x001c +#define HOST1X_CHANNEL_DMACTRL 0x0020 +#define HOST1X_CHANNEL_DMACTRL_DMASTOP BIT(0) +#define HOST1X_CHANNEL_DMACTRL_DMAGETRST BIT(1) +#define HOST1X_CHANNEL_DMACTRL_DMAINITGET BIT(2) +#define HOST1X_CHANNEL_CMDFIFO_STAT 0x0024 +#define HOST1X_CHANNEL_CMDFIFO_STAT_EMPTY BIT(13) +#define HOST1X_CHANNEL_CMDFIFO_RDATA 0x0028 +#define HOST1X_CHANNEL_CMDP_OFFSET 0x0030 +#define HOST1X_CHANNEL_CMDP_CLASS 0x0034 +#define HOST1X_CHANNEL_CHANNELSTAT 0x0038 +#define HOST1X_CHANNEL_CMDPROC_STOP 0x0048 +#define HOST1X_CHANNEL_TEARDOWN 0x004c +#define HOST1X_CHANNEL_SMMU_STREAMID 0x0084 + +#define HOST1X_SYNC_SYNCPT_CPU_INCR(x) (0x6400 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(x) (0x6600 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_INTR_DEST(x) (0x6684 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(x) (0x770c + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(x) (0x7790 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT(x) (0x8080 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_INT_THRESH(x) (0xa088 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_CH_APP(x) (0xb090 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_CH_APP_CH(v) (((v) & 0x3f) << 8) diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c index 723297250768..6a383e0c6eb9 100644 --- a/drivers/gpu/host1x/intr.c +++ b/drivers/gpu/host1x/intr.c @@ -7,6 +7,7 @@ #include <linux/clk.h> #include <linux/interrupt.h> +#include <linux/overflow.h> #include "dev.h" #include "fence.h" #include "intr.h" @@ -17,7 +18,7 @@ static void host1x_intr_add_fence_to_list(struct host1x_fence_list *list, struct host1x_syncpt_fence *fence_in_list; list_for_each_entry_reverse(fence_in_list, &list->list, list) { - if ((s32)(fence_in_list->threshold - fence->threshold) <= 0) { + if ((s32)wrapping_sub(u32, fence_in_list->threshold, fence->threshold) <= 0) { /* Fence in list is before us, we can insert here */ list_add(&fence->list, &fence_in_list->list); return; @@ -83,7 +84,7 @@ void host1x_intr_handle_interrupt(struct host1x *host, unsigned int id) spin_lock(&sp->fences.lock); list_for_each_entry_safe(fence, tmp, &sp->fences.list, list) { - if (((value - fence->threshold) & 0x80000000U) != 0U) { + if ((wrapping_sub(u32, value, fence->threshold) & 0x80000000U) != 0U) { /* Fence is not yet expired, we are done */ break; } diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index 70bda32f1ff4..71411bc344bb 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -138,7 +138,7 @@ void host1x_job_add_wait(struct host1x_job *job, u32 id, u32 thresh, } EXPORT_SYMBOL(host1x_job_add_wait); -static unsigned int pin_job(struct host1x *host, struct host1x_job *job) +static int pin_job(struct host1x *host, struct host1x_job *job) { unsigned long mask = HOST1X_RELOC_READ | HOST1X_RELOC_WRITE; struct host1x_client *client = job->client; diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index 807c74fc6a0a..2c111710f3bf 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -8,6 +8,7 @@ #include <linux/module.h> #include <linux/device.h> #include <linux/dma-fence.h> +#include <linux/overflow.h> #include <linux/slab.h> #include <trace/events/host1x.h> @@ -126,6 +127,10 @@ EXPORT_SYMBOL(host1x_syncpt_id); */ u32 host1x_syncpt_incr_max(struct host1x_syncpt *sp, u32 incrs) { + /* + * Syncpoint values are intended to be modulo 2^32, so overflow + * here is intended. + */ return (u32)atomic_add_return(incrs, &sp->max_val); } EXPORT_SYMBOL(host1x_syncpt_incr_max); @@ -279,7 +284,7 @@ bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh) current_val = (u32)atomic_read(&sp->min_val); - return ((current_val - thresh) & 0x80000000U) == 0U; + return (wrapping_sub(u32, current_val, thresh) & 0x80000000U) == 0U; } int host1x_syncpt_init(struct host1x *host) diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h index 4c3f3b2f0e9c..9eff42efc445 100644 --- a/drivers/gpu/host1x/syncpt.h +++ b/drivers/gpu/host1x/syncpt.h @@ -12,6 +12,7 @@ #include <linux/host1x.h> #include <linux/kernel.h> #include <linux/kref.h> +#include <linux/overflow.h> #include <linux/sched.h> #include "fence.h" @@ -77,7 +78,7 @@ static inline bool host1x_syncpt_check_max(struct host1x_syncpt *sp, u32 real) if (sp->client_managed) return true; max = host1x_syncpt_read_max(sp); - return (s32)(max - real) >= 0; + return (s32)wrapping_sub(u32, max, real) >= 0; } /* Return true if sync point is client managed. */ diff --git a/drivers/gpu/tests/gpu_buddy_test.c b/drivers/gpu/tests/gpu_buddy_test.c index 7df5c2ae83bb..89698563c61b 100644 --- a/drivers/gpu/tests/gpu_buddy_test.c +++ b/drivers/gpu/tests/gpu_buddy_test.c @@ -1002,6 +1002,47 @@ static void gpu_test_buddy_alloc_clear(struct kunit *test) "buddy_alloc hit an error size=%lu\n", ps); gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); gpu_buddy_fini(&mm); + + /* + * Using a non-power-of-two mm size, allocate alternating blocks of 4KiB in an + * even sequence and free them as cleared. All blocks should be marked as + * dirty and the split blocks should be merged back to their original + * size when the blocks clear reset function is called. + */ + KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps)); + KUNIT_EXPECT_EQ(test, mm.max_order, max_order); + + i = 0; + n_pages = mm_size / ps; + do { + if (i % 2 == 0) + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, + ps, ps, &allocated, 0), + "buddy_alloc hit an error size=%lu\n", ps); + } while (++i < n_pages); + + gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); + gpu_buddy_reset_clear(&mm, false); + KUNIT_EXPECT_EQ(test, mm.clear_avail, 0); + + /* + * Using a non-power-of-two mm size, allocate alternating blocks of 4KiB in an + * odd sequence and free them as cleared. All blocks should be marked as + * cleared and the split blocks should be merged back to their original + * size when the blocks clear reset function is called. + */ + i = 0; + do { + if (i % 2 != 0) + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, + ps, ps, &allocated, 0), + "buddy_alloc hit an error size=%lu\n", ps); + } while (++i < n_pages); + + gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); + gpu_buddy_reset_clear(&mm, true); + KUNIT_EXPECT_EQ(test, mm.clear_avail, mm_size); + gpu_buddy_fini(&mm); } static void gpu_test_buddy_alloc_contiguous(struct kunit *test) diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index af075c38f4db..a0cf0268de48 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -2377,7 +2377,10 @@ struct drm_connector { */ struct mutex edid_override_mutex; - /** @epoch_counter: used to detect any other changes in connector, besides status */ + /** + * @epoch_counter: Used to detect changes in connector. Increased when + * the connector, including its status, is changed. + */ u64 epoch_counter; /** diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index 6430c7ce1e03..39930c59cb76 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -734,57 +734,38 @@ static ssize_t dmemcg_limit_write(struct kernfs_open_file *of, void (*apply)(struct dmem_cgroup_pool_state *, u64)) { struct dmemcg_state *dmemcs = css_to_dmemcs(of_css(of)); - int err = 0; - - while (buf && !err) { - struct dmem_cgroup_pool_state *pool = NULL; - char *options, *region_name; - struct dmem_cgroup_region *region; - u64 new_limit; - - options = buf; - buf = strchr(buf, '\n'); - if (buf) - *buf++ = '\0'; - - options = strstrip(options); - - /* eat empty lines */ - if (!options[0]) - continue; - - region_name = strsep(&options, " \t"); - if (!region_name[0]) - continue; - - if (!options || !*options) - return -EINVAL; + struct dmem_cgroup_pool_state *pool; + struct dmem_cgroup_region *region; + char *region_name; + u64 new_limit; + int err; - rcu_read_lock(); - region = dmemcg_get_region_by_name(region_name); - rcu_read_unlock(); + buf = strstrip(buf); + region_name = strsep(&buf, " \t"); + if (!buf || !region_name[0]) + return -EINVAL; - if (!region) - return -EINVAL; + rcu_read_lock(); + region = dmemcg_get_region_by_name(region_name); + rcu_read_unlock(); + if (!region) + return -EINVAL; - err = dmemcg_parse_limit(options, &new_limit); - if (err < 0) - goto out_put; + err = dmemcg_parse_limit(buf, &new_limit); + if (err < 0) + goto out_put; - pool = get_cg_pool_unlocked(dmemcs, region); - if (IS_ERR(pool)) { - err = PTR_ERR(pool); - goto out_put; - } + pool = get_cg_pool_unlocked(dmemcs, region); + if (IS_ERR(pool)) { + err = PTR_ERR(pool); + goto out_put; + } - /* And commit */ - apply(pool, new_limit); - dmemcg_pool_put(pool); + apply(pool, new_limit); + dmemcg_pool_put(pool); out_put: - kref_put(®ion->ref, dmemcg_free_region); - } - + kref_put(®ion->ref, dmemcg_free_region); return err ?: nbytes; } |
