aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml20
-rw-r--r--Documentation/devicetree/bindings/display/dp-aux-bus.yaml37
-rw-r--r--Documentation/devicetree/bindings/display/panel/panel-simple.yaml2
-rw-r--r--Documentation/gpu/drm-uapi.rst13
-rw-r--r--MAINTAINERS7
-rw-r--r--arch/arm64/boot/dts/qcom/sc7180-trogdor.dtsi30
-rw-r--r--drivers/dma-buf/udmabuf.c50
-rw-r--r--drivers/gpu/drm/Kconfig5
-rw-r--r--drivers/gpu/drm/Makefile2
-rw-r--r--drivers/gpu/drm/bridge/Kconfig1
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi83.c21
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi86.c129
-rw-r--r--drivers/gpu/drm/drm_debugfs_crc.c8
-rw-r--r--drivers/gpu/drm/drm_dp_aux_bus.c326
-rw-r--r--drivers/gpu/drm/drm_dp_helper.c347
-rw-r--r--drivers/gpu/drm/i915/display/intel_display_types.h2
-rw-r--r--drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c329
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/disp.c28
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_backlight.c166
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_bo.c6
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_connector.h9
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_encoder.h1
-rw-r--r--drivers/gpu/drm/panel/Kconfig11
-rw-r--r--drivers/gpu/drm/panel/Makefile1
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-db7430.c347
-rw-r--r--drivers/gpu/drm/panel/panel-simple.c68
-rw-r--r--drivers/gpu/drm/panfrost/panfrost_device.c3
-rw-r--r--drivers/gpu/drm/vmwgfx/Kconfig7
-rw-r--r--drivers/gpu/drm/vmwgfx/Makefile2
-rw-r--r--drivers/gpu/drm/vmwgfx/device_include/svga_types.h92
-rw-r--r--drivers/gpu/drm/vmwgfx/device_include/vm_basic_types.h22
-rw-r--r--drivers/gpu/drm/vmwgfx/ttm_memory.c2
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_binding.c20
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c6
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf.c2
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf_res.c4
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.c142
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.h50
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_drv.c58
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_drv.h38
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c15
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c109
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_mksstat.h144
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_mob.c4
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_msg.c579
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_resource.c8
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_so.c3
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_surface.c5
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c35
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_validation.c4
-rw-r--r--include/drm/drm_dp_aux_bus.h57
-rw-r--r--include/drm/drm_dp_helper.h48
-rw-r--r--include/uapi/drm/drm.h4
-rw-r--r--include/uapi/drm/vmwgfx_drm.h41
54 files changed, 2837 insertions, 633 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml
index 26932d2e86ab..12b876a20574 100644
--- a/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml
+++ b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.yaml
@@ -70,6 +70,9 @@ properties:
const: 1
description: See ../../pwm/pwm.yaml for description of the cell formats.
+ aux-bus:
+ $ref: /schemas/display/dp-aux-bus.yaml#
+
ports:
$ref: /schemas/graph.yaml#/properties/ports
@@ -201,11 +204,26 @@ examples:
port@1 {
reg = <1>;
- endpoint {
+ sn65dsi86_out: endpoint {
remote-endpoint = <&panel_in_edp>;
};
};
};
+
+ aux-bus {
+ panel {
+ compatible = "boe,nv133fhm-n62";
+ power-supply = <&pp3300_dx_edp>;
+ backlight = <&backlight>;
+ hpd-gpios = <&sn65dsi86_bridge 2 GPIO_ACTIVE_HIGH>;
+
+ port {
+ panel_in_edp: endpoint {
+ remote-endpoint = <&sn65dsi86_out>;
+ };
+ };
+ };
+ };
};
};
- |
diff --git a/Documentation/devicetree/bindings/display/dp-aux-bus.yaml b/Documentation/devicetree/bindings/display/dp-aux-bus.yaml
new file mode 100644
index 000000000000..5e4afe9f98fb
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/dp-aux-bus.yaml
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/dp-aux-bus.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: DisplayPort AUX bus
+
+maintainers:
+ - Douglas Anderson <[email protected]>
+
+description:
+ DisplayPort controllers provide a control channel to the sinks that
+ are hooked up to them. This is the DP AUX bus. Over the DP AUX bus
+ we can query properties about a sink and also configure it. In
+ particular, DP sinks support DDC over DP AUX which allows tunneling
+ a standard I2C DDC connection over the AUX channel.
+
+ To model this relationship, DP sinks should be placed as children
+ of the DP controller under the "aux-bus" node.
+
+ At the moment, this binding only handles the eDP case. It is
+ possible it will be extended in the future to handle the DP case.
+ For DP, presumably a connector would be listed under the DP AUX
+ bus instead of a panel.
+
+properties:
+ $nodename:
+ const: "aux-bus"
+
+ panel:
+ $ref: panel/panel-common.yaml#
+
+additionalProperties: false
+
+required:
+ - panel
diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
index b3797ba2698b..4a0a5e1ee252 100644
--- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
+++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
@@ -298,6 +298,8 @@ properties:
enable-gpios: true
port: true
power-supply: true
+ no-hpd: true
+ hpd-gpios: true
additionalProperties: false
diff --git a/Documentation/gpu/drm-uapi.rst b/Documentation/gpu/drm-uapi.rst
index 04bdc7a91d53..7e51dd40bf6e 100644
--- a/Documentation/gpu/drm-uapi.rst
+++ b/Documentation/gpu/drm-uapi.rst
@@ -457,6 +457,19 @@ Userspace API Structures
.. kernel-doc:: include/uapi/drm/drm_mode.h
:doc: overview
+.. _crtc_index:
+
+CRTC index
+----------
+
+CRTC's have both an object ID and an index, and they are not the same thing.
+The index is used in cases where a densely packed identifier for a CRTC is
+needed, for instance a bitmask of CRTC's. The member possible_crtcs of struct
+drm_mode_get_plane is an example.
+
+DRM_IOCTL_MODE_GETRESOURCES populates a structure with an array of CRTC ID's,
+and the CRTC index is its position in this array.
+
.. kernel-doc:: include/uapi/drm/drm.h
:internal:
diff --git a/MAINTAINERS b/MAINTAINERS
index bc336961ed52..a4ba46fb803a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5857,6 +5857,13 @@ S: Maintained
F: Documentation/devicetree/bindings/display/panel/raydium,rm67191.yaml
F: drivers/gpu/drm/panel/panel-raydium-rm67191.c
+DRM DRIVER FOR SAMSUNG DB7430 PANELS
+M: Linus Walleij <[email protected]>
+S: Maintained
+T: git git://anongit.freedesktop.org/drm/drm-misc
+F: Documentation/devicetree/bindings/display/panel/samsung,lms397kf04.yaml
+F: drivers/gpu/drm/panel/panel-samsung-db7430.c
+
DRM DRIVER FOR SITRONIX ST7703 PANELS
M: Guido Günther <[email protected]>
R: Purism Kernel Team <[email protected]>
diff --git a/arch/arm64/boot/dts/qcom/sc7180-trogdor.dtsi b/arch/arm64/boot/dts/qcom/sc7180-trogdor.dtsi
index 24d293ef56d7..c76afd857b54 100644
--- a/arch/arm64/boot/dts/qcom/sc7180-trogdor.dtsi
+++ b/arch/arm64/boot/dts/qcom/sc7180-trogdor.dtsi
@@ -260,21 +260,6 @@
#sound-dai-cells = <0>;
};
- panel: panel {
- /* Compatible will be filled in per-board */
- power-supply = <&pp3300_dx_edp>;
- backlight = <&backlight>;
- hpd-gpios = <&sn65dsi86_bridge 2 GPIO_ACTIVE_HIGH>;
-
- ports {
- port {
- panel_in_edp: endpoint {
- remote-endpoint = <&sn65dsi86_out>;
- };
- };
- };
- };
-
pwmleds {
compatible = "pwm-leds";
keyboard_backlight: keyboard-backlight {
@@ -674,6 +659,21 @@ edp_brij_i2c: &i2c2 {
};
};
};
+
+ aux-bus {
+ panel: panel {
+ /* Compatible will be filled in per-board */
+ power-supply = <&pp3300_dx_edp>;
+ backlight = <&backlight>;
+ hpd-gpios = <&sn65dsi86_bridge 2 GPIO_ACTIVE_HIGH>;
+
+ port {
+ panel_in_edp: endpoint {
+ remote-endpoint = <&sn65dsi86_out>;
+ };
+ };
+ };
+ };
};
};
diff --git a/drivers/dma-buf/udmabuf.c b/drivers/dma-buf/udmabuf.c
index db732f71e59a..1a79ce899b0f 100644
--- a/drivers/dma-buf/udmabuf.c
+++ b/drivers/dma-buf/udmabuf.c
@@ -11,6 +11,7 @@
#include <linux/shmem_fs.h>
#include <linux/slab.h>
#include <linux/udmabuf.h>
+#include <linux/hugetlb.h>
static const u32 list_limit = 1024; /* udmabuf_create_list->count limit */
static const size_t size_limit_mb = 64; /* total dmabuf size, in megabytes */
@@ -160,10 +161,13 @@ static long udmabuf_create(struct miscdevice *device,
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct file *memfd = NULL;
+ struct address_space *mapping = NULL;
struct udmabuf *ubuf;
struct dma_buf *buf;
pgoff_t pgoff, pgcnt, pgidx, pgbuf = 0, pglimit;
- struct page *page;
+ struct page *page, *hpage = NULL;
+ pgoff_t subpgoff, maxsubpgs;
+ struct hstate *hpstate;
int seals, ret = -EINVAL;
u32 i, flags;
@@ -194,7 +198,8 @@ static long udmabuf_create(struct miscdevice *device,
memfd = fget(list[i].memfd);
if (!memfd)
goto err;
- if (!shmem_mapping(file_inode(memfd)->i_mapping))
+ mapping = file_inode(memfd)->i_mapping;
+ if (!shmem_mapping(mapping) && !is_file_hugepages(memfd))
goto err;
seals = memfd_fcntl(memfd, F_GET_SEALS, 0);
if (seals == -EINVAL)
@@ -205,17 +210,48 @@ static long udmabuf_create(struct miscdevice *device,
goto err;
pgoff = list[i].offset >> PAGE_SHIFT;
pgcnt = list[i].size >> PAGE_SHIFT;
+ if (is_file_hugepages(memfd)) {
+ hpstate = hstate_file(memfd);
+ pgoff = list[i].offset >> huge_page_shift(hpstate);
+ subpgoff = (list[i].offset &
+ ~huge_page_mask(hpstate)) >> PAGE_SHIFT;
+ maxsubpgs = huge_page_size(hpstate) >> PAGE_SHIFT;
+ }
for (pgidx = 0; pgidx < pgcnt; pgidx++) {
- page = shmem_read_mapping_page(
- file_inode(memfd)->i_mapping, pgoff + pgidx);
- if (IS_ERR(page)) {
- ret = PTR_ERR(page);
- goto err;
+ if (is_file_hugepages(memfd)) {
+ if (!hpage) {
+ hpage = find_get_page_flags(mapping, pgoff,
+ FGP_ACCESSED);
+ if (IS_ERR(hpage)) {
+ ret = PTR_ERR(hpage);
+ goto err;
+ }
+ }
+ page = hpage + subpgoff;
+ get_page(page);
+ subpgoff++;
+ if (subpgoff == maxsubpgs) {
+ put_page(hpage);
+ hpage = NULL;
+ subpgoff = 0;
+ pgoff++;
+ }
+ } else {
+ page = shmem_read_mapping_page(mapping,
+ pgoff + pgidx);
+ if (IS_ERR(page)) {
+ ret = PTR_ERR(page);
+ goto err;
+ }
}
ubuf->pages[pgbuf++] = page;
}
fput(memfd);
memfd = NULL;
+ if (hpage) {
+ put_page(hpage);
+ hpage = NULL;
+ }
}
exp_info.ops = &udmabuf_ops;
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 7ff89690a976..1366d8d4610a 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -35,6 +35,11 @@ config DRM_MIPI_DSI
bool
depends on DRM
+config DRM_DP_AUX_BUS
+ tristate
+ depends on DRM
+ depends on OF
+
config DRM_DP_AUX_CHARDEV
bool "DRM DP AUX Interface"
depends on DRM
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index a118692a6df7..12e6f4e485ed 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -33,6 +33,8 @@ drm-$(CONFIG_PCI) += drm_pci.o
drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o
drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o
+obj-$(CONFIG_DRM_DP_AUX_BUS) += drm_dp_aux_bus.o
+
drm_vram_helper-y := drm_gem_vram_helper.o
obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 85b673613687..431b6e12a81f 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -303,6 +303,7 @@ config DRM_TI_SN65DSI86
select DRM_PANEL
select DRM_MIPI_DSI
select AUXILIARY_BUS
+ select DRM_DP_AUX_BUS
help
Texas Instruments SN65DSI86 DSI to eDP Bridge driver
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
index 750f2172ef08..8f3158f5281a 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -368,6 +368,7 @@ static void sn65dsi83_enable(struct drm_bridge *bridge)
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
unsigned int pval;
+ __le16 le16val;
u16 val;
int ret;
@@ -426,21 +427,21 @@ static void sn65dsi83_enable(struct drm_bridge *bridge)
REG_LVDS_LANE_CHB_LVDS_TERM);
regmap_write(ctx->regmap, REG_LVDS_CM, 0x00);
- val = cpu_to_le16(ctx->mode.hdisplay);
+ le16val = cpu_to_le16(ctx->mode.hdisplay);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
- &val, 2);
- val = cpu_to_le16(ctx->mode.vdisplay);
+ &le16val, 2);
+ le16val = cpu_to_le16(ctx->mode.vdisplay);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
- &val, 2);
+ &le16val, 2);
/* 32 + 1 pixel clock to ensure proper operation */
- val = cpu_to_le16(32 + 1);
- regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &val, 2);
- val = cpu_to_le16(ctx->mode.hsync_end - ctx->mode.hsync_start);
+ le16val = cpu_to_le16(32 + 1);
+ regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &le16val, 2);
+ le16val = cpu_to_le16(ctx->mode.hsync_end - ctx->mode.hsync_start);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
- &val, 2);
- val = cpu_to_le16(ctx->mode.vsync_end - ctx->mode.vsync_start);
+ &le16val, 2);
+ le16val = cpu_to_le16(ctx->mode.vsync_end - ctx->mode.vsync_start);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
- &val, 2);
+ &le16val, 2);
regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_BACK_PORCH,
ctx->mode.htotal - ctx->mode.hsync_end);
regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_BACK_PORCH,
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index 45a2969afb2b..5d712c8c3c3b 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -23,6 +23,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
+#include <drm/drm_dp_aux_bus.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
@@ -116,6 +117,7 @@
* struct ti_sn65dsi86 - Platform data for ti-sn65dsi86 driver.
* @bridge_aux: AUX-bus sub device for MIPI-to-eDP bridge functionality.
* @gpio_aux: AUX-bus sub device for GPIO controller functionality.
+ * @aux_aux: AUX-bus sub device for eDP AUX channel functionality.
*
* @dev: Pointer to the top level (i2c) device.
* @regmap: Regmap for accessing i2c.
@@ -124,7 +126,6 @@
* @connector: Our connector.
* @host_node: Remote DSI node.
* @dsi: Our MIPI DSI source.
- * @edid: Detected EDID of eDP panel.
* @refclk: Our reference clock.
* @panel: Our panel.
* @enable_gpio: The GPIO we toggle to enable the bridge.
@@ -148,13 +149,13 @@
struct ti_sn65dsi86 {
struct auxiliary_device bridge_aux;
struct auxiliary_device gpio_aux;
+ struct auxiliary_device aux_aux;
struct device *dev;
struct regmap *regmap;
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct drm_connector connector;
- struct edid *edid;
struct device_node *host_node;
struct mipi_dsi_device *dsi;
struct clk *refclk;
@@ -403,24 +404,6 @@ connector_to_ti_sn65dsi86(struct drm_connector *connector)
static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
{
struct ti_sn65dsi86 *pdata = connector_to_ti_sn65dsi86(connector);
- struct edid *edid = pdata->edid;
- int num, ret;
-
- if (!edid) {
- pm_runtime_get_sync(pdata->dev);
- edid = pdata->edid = drm_get_edid(connector, &pdata->aux.ddc);
- pm_runtime_put_autosuspend(pdata->dev);
- }
-
- if (edid && drm_edid_is_valid(edid)) {
- ret = drm_connector_update_edid_property(connector, edid);
- if (!ret) {
- num = drm_add_edid_modes(connector, edid);
- if (num)
- return num;
- }
- }
-
return drm_panel_get_modes(pdata->panel, connector);
}
@@ -1322,10 +1305,9 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
int ret;
ret = drm_of_find_panel_or_bridge(np, 1, 0, &pdata->panel, NULL);
- if (ret) {
- DRM_ERROR("could not find any panel node\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(&adev->dev, ret,
+ "could not find any panel node\n");
ti_sn_bridge_parse_lanes(pdata, np);
@@ -1333,11 +1315,6 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
if (ret)
return ret;
- pdata->aux.name = "ti-sn65dsi86-aux";
- pdata->aux.dev = pdata->dev;
- pdata->aux.transfer = ti_sn_aux_transfer;
- drm_dp_aux_init(&pdata->aux);
-
pdata->bridge.funcs = &ti_sn_bridge_funcs;
pdata->bridge.of_node = np;
@@ -1358,8 +1335,6 @@ static void ti_sn_bridge_remove(struct auxiliary_device *adev)
mipi_dsi_device_unregister(pdata->dsi);
}
- kfree(pdata->edid);
-
drm_bridge_remove(&pdata->bridge);
of_node_put(pdata->host_node);
@@ -1406,17 +1381,10 @@ static int ti_sn65dsi86_add_aux_device(struct ti_sn65dsi86 *pdata,
struct device *dev = pdata->dev;
int ret;
- /*
- * NOTE: It would be nice to set the "of_node" of our children to be
- * the same "of_node"" that the top-level component has. That doesn't
- * work, though, since pinctrl will try (and fail) to reserve the
- * pins again. Until that gets sorted out the children will just need
- * to look at the of_node of the main device.
- */
-
aux->name = name;
aux->dev.parent = dev;
aux->dev.release = ti_sn65dsi86_noop;
+ device_set_of_node_from_dev(&aux->dev, dev);
ret = auxiliary_device_init(aux);
if (ret)
return ret;
@@ -1432,6 +1400,39 @@ static int ti_sn65dsi86_add_aux_device(struct ti_sn65dsi86 *pdata,
return ret;
}
+static int ti_sn_aux_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+ int ret;
+
+ pdata->aux.name = "ti-sn65dsi86-aux";
+ pdata->aux.dev = &adev->dev;
+ pdata->aux.transfer = ti_sn_aux_transfer;
+ drm_dp_aux_init(&pdata->aux);
+
+ ret = devm_of_dp_aux_populate_ep_devices(&pdata->aux);
+ if (ret)
+ return ret;
+
+ /*
+ * The eDP to MIPI bridge parts don't work until the AUX channel is
+ * setup so we don't add it in the main driver probe, we add it now.
+ */
+ return ti_sn65dsi86_add_aux_device(pdata, &pdata->bridge_aux, "bridge");
+}
+
+static const struct auxiliary_device_id ti_sn_aux_id_table[] = {
+ { .name = "ti_sn65dsi86.aux", },
+ {},
+};
+
+static struct auxiliary_driver ti_sn_aux_driver = {
+ .name = "aux",
+ .probe = ti_sn_aux_probe,
+ .id_table = ti_sn_aux_id_table,
+};
+
static int ti_sn65dsi86_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -1454,27 +1455,23 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
pdata->regmap = devm_regmap_init_i2c(client,
&ti_sn65dsi86_regmap_config);
- if (IS_ERR(pdata->regmap)) {
- DRM_ERROR("regmap i2c init failed\n");
- return PTR_ERR(pdata->regmap);
- }
+ if (IS_ERR(pdata->regmap))
+ return dev_err_probe(dev, PTR_ERR(pdata->regmap),
+ "regmap i2c init failed\n");
pdata->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
- if (IS_ERR(pdata->enable_gpio)) {
- DRM_ERROR("failed to get enable gpio from DT\n");
- ret = PTR_ERR(pdata->enable_gpio);
- return ret;
- }
+ if (IS_ERR(pdata->enable_gpio))
+ return dev_err_probe(dev, PTR_ERR(pdata->enable_gpio),
+ "failed to get enable gpio from DT\n");
ret = ti_sn65dsi86_parse_regulators(pdata);
- if (ret) {
- DRM_ERROR("failed to parse regulators\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to parse regulators\n");
pdata->refclk = devm_clk_get_optional(dev, "refclk");
if (IS_ERR(pdata->refclk))
- return PTR_ERR(pdata->refclk);
+ return dev_err_probe(dev, PTR_ERR(pdata->refclk),
+ "failed to get reference clock\n");
pm_runtime_enable(dev);
ret = devm_add_action_or_reset(dev, ti_sn65dsi86_runtime_disable, dev);
@@ -1490,10 +1487,11 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
* motiviation here is to solve the chicken-and-egg problem of probe
* ordering. The bridge wants the panel to be there when it probes.
* The panel wants its HPD GPIO (provided by sn65dsi86 on some boards)
- * when it probes. There will soon be other devices (DDC I2C bus, PWM)
- * that have the same problem. Having sub-devices allows the some sub
- * devices to finish probing even if others return -EPROBE_DEFER and
- * gets us around the problems.
+ * when it probes. The panel and maybe backlight might want the DDC
+ * bus. Soon the PWM provided by the bridge chip will have the same
+ * problem. Having sub-devices allows the some sub devices to finish
+ * probing even if others return -EPROBE_DEFER and gets us around the
+ * problems.
*/
if (IS_ENABLED(CONFIG_OF_GPIO)) {
@@ -1502,7 +1500,13 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
return ret;
}
- return ti_sn65dsi86_add_aux_device(pdata, &pdata->bridge_aux, "bridge");
+ /*
+ * NOTE: At the end of the AUX channel probe we'll add the aux device
+ * for the bridge. This is because the bridge can't be used until the
+ * AUX channel is there and this is a very simple solution to the
+ * dependency problem.
+ */
+ return ti_sn65dsi86_add_aux_device(pdata, &pdata->aux_aux, "aux");
}
static struct i2c_device_id ti_sn65dsi86_id[] = {
@@ -1539,12 +1543,18 @@ static int __init ti_sn65dsi86_init(void)
if (ret)
goto err_main_was_registered;
- ret = auxiliary_driver_register(&ti_sn_bridge_driver);
+ ret = auxiliary_driver_register(&ti_sn_aux_driver);
if (ret)
goto err_gpio_was_registered;
+ ret = auxiliary_driver_register(&ti_sn_bridge_driver);
+ if (ret)
+ goto err_aux_was_registered;
+
return 0;
+err_aux_was_registered:
+ auxiliary_driver_unregister(&ti_sn_aux_driver);
err_gpio_was_registered:
ti_sn_gpio_unregister();
err_main_was_registered:
@@ -1557,6 +1567,7 @@ module_init(ti_sn65dsi86_init);
static void __exit ti_sn65dsi86_exit(void)
{
auxiliary_driver_unregister(&ti_sn_bridge_driver);
+ auxiliary_driver_unregister(&ti_sn_aux_driver);
ti_sn_gpio_unregister();
i2c_del_driver(&ti_sn65dsi86_driver);
}
diff --git a/drivers/gpu/drm/drm_debugfs_crc.c b/drivers/gpu/drm/drm_debugfs_crc.c
index 3dd70d813f69..bbc3bc4ba844 100644
--- a/drivers/gpu/drm/drm_debugfs_crc.c
+++ b/drivers/gpu/drm/drm_debugfs_crc.c
@@ -46,10 +46,10 @@
* it reached a given hardware component (a CRC sampling "source").
*
* Userspace can control generation of CRCs in a given CRTC by writing to the
- * file dri/0/crtc-N/crc/control in debugfs, with N being the index of the CRTC.
- * Accepted values are source names (which are driver-specific) and the "auto"
- * keyword, which will let the driver select a default source of frame CRCs
- * for this CRTC.
+ * file dri/0/crtc-N/crc/control in debugfs, with N being the :ref:`index of
+ * the CRTC<crtc_index>`. Accepted values are source names (which are
+ * driver-specific) and the "auto" keyword, which will let the driver select a
+ * default source of frame CRCs for this CRTC.
*
* Once frame CRC generation is enabled, userspace can capture them by reading
* the dri/0/crtc-N/crc/data file. Each line in that file contains the frame
diff --git a/drivers/gpu/drm/drm_dp_aux_bus.c b/drivers/gpu/drm/drm_dp_aux_bus.c
new file mode 100644
index 000000000000..e49a70f3691b
--- /dev/null
+++ b/drivers/gpu/drm/drm_dp_aux_bus.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * The DP AUX bus is used for devices that are connected over a DisplayPort
+ * AUX bus. The devices on the far side of the bus are referred to as
+ * endpoints in this code.
+ *
+ * Commonly there is only one device connected to the DP AUX bus: a panel.
+ * Though historically panels (even DP panels) have been modeled as simple
+ * platform devices, putting them under the DP AUX bus allows the panel driver
+ * to perform transactions on that bus.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_dp_aux_bus.h>
+#include <drm/drm_dp_helper.h>
+
+/**
+ * dp_aux_ep_match() - The match function for the dp_aux_bus.
+ * @dev: The device to match.
+ * @drv: The driver to try to match against.
+ *
+ * At the moment, we just match on device tree.
+ *
+ * Return: True if this driver matches this device; false otherwise.
+ */
+static int dp_aux_ep_match(struct device *dev, struct device_driver *drv)
+{
+ return !!of_match_device(drv->of_match_table, dev);
+}
+
+/**
+ * dp_aux_ep_probe() - The probe function for the dp_aux_bus.
+ * @dev: The device to probe.
+ *
+ * Calls through to the endpoint driver probe.
+ *
+ * Return: 0 if no error or negative error code.
+ */
+static int dp_aux_ep_probe(struct device *dev)
+{
+ struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver);
+ struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev);
+ int ret;
+
+ ret = dev_pm_domain_attach(dev, true);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to attach to PM Domain\n");
+
+ ret = aux_ep_drv->probe(aux_ep);
+ if (ret)
+ dev_pm_domain_detach(dev, true);
+
+ return ret;
+}
+
+/**
+ * dp_aux_ep_remove() - The remove function for the dp_aux_bus.
+ * @dev: The device to remove.
+ *
+ * Calls through to the endpoint driver remove.
+ *
+ * Return: 0 if no error or negative error code.
+ */
+static int dp_aux_ep_remove(struct device *dev)
+{
+ struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver);
+ struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev);
+
+ if (aux_ep_drv->remove)
+ aux_ep_drv->remove(aux_ep);
+ dev_pm_domain_detach(dev, true);
+
+ return 0;
+}
+
+/**
+ * dp_aux_ep_shutdown() - The shutdown function for the dp_aux_bus.
+ * @dev: The device to shutdown.
+ *
+ * Calls through to the endpoint driver shutdown.
+ */
+static void dp_aux_ep_shutdown(struct device *dev)
+{
+ struct dp_aux_ep_driver *aux_ep_drv;
+
+ if (!dev->driver)
+ return;
+
+ aux_ep_drv = to_dp_aux_ep_drv(dev->driver);
+ if (aux_ep_drv->shutdown)
+ aux_ep_drv->shutdown(to_dp_aux_ep_dev(dev));
+}
+
+static struct bus_type dp_aux_bus_type = {
+ .name = "dp-aux",
+ .match = dp_aux_ep_match,
+ .probe = dp_aux_ep_probe,
+ .remove = dp_aux_ep_remove,
+ .shutdown = dp_aux_ep_shutdown,
+};
+
+static ssize_t modalias_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return of_device_modalias(dev, buf, PAGE_SIZE);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *dp_aux_ep_dev_attrs[] = {
+ &dev_attr_modalias.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(dp_aux_ep_dev);
+
+/**
+ * dp_aux_ep_dev_release() - Free memory for the dp_aux_ep device
+ * @dev: The device to free.
+ *
+ * Return: 0 if no error or negative error code.
+ */
+static void dp_aux_ep_dev_release(struct device *dev)
+{
+ kfree(to_dp_aux_ep_dev(dev));
+}
+
+static struct device_type dp_aux_device_type_type = {
+ .groups = dp_aux_ep_dev_groups,
+ .uevent = of_device_uevent_modalias,
+ .release = dp_aux_ep_dev_release,
+};
+
+/**
+ * of_dp_aux_ep_destroy() - Destroy an DP AUX endpoint device
+ * @dev: The device to destroy.
+ * @data: Not used
+ *
+ * This is just used as a callback by of_dp_aux_depopulate_ep_devices() and
+ * is called for _all_ of the child devices of the device providing the AUX bus.
+ * We'll only act on those that are of type "dp_aux_bus_type".
+ *
+ * This function is effectively an inverse of what's in the loop
+ * in of_dp_aux_populate_ep_devices().
+ *
+ * Return: 0 if no error or negative error code.
+ */
+static int of_dp_aux_ep_destroy(struct device *dev, void *data)
+{
+ struct device_node *np = dev->of_node;
+
+ if (dev->bus != &dp_aux_bus_type)
+ return 0;
+
+ if (!of_node_check_flag(np, OF_POPULATED))
+ return 0;
+
+ of_node_clear_flag(np, OF_POPULATED);
+ of_node_put(np);
+
+ device_unregister(dev);
+
+ return 0;
+}
+
+/**
+ * of_dp_aux_depopulate_ep_devices() - Undo of_dp_aux_populate_ep_devices
+ * @aux: The AUX channel whose devices we want to depopulate
+ *
+ * This will destroy all devices that were created
+ * by of_dp_aux_populate_ep_devices().
+ */
+void of_dp_aux_depopulate_ep_devices(struct drm_dp_aux *aux)
+{
+ device_for_each_child_reverse(aux->dev, NULL, of_dp_aux_ep_destroy);
+}
+EXPORT_SYMBOL_GPL(of_dp_aux_depopulate_ep_devices);
+
+/**
+ * of_dp_aux_populate_ep_devices() - Populate the endpoint devices on the DP AUX
+ * @aux: The AUX channel whose devices we want to populate. It is required that
+ * drm_dp_aux_init() has already been called for this AUX channel.
+ *
+ * This will populate all the devices under the "aux-bus" node of the device
+ * providing the AUX channel (AKA aux->dev).
+ *
+ * When this function finishes, it is _possible_ (but not guaranteed) that
+ * our sub-devices will have finished probing. It should be noted that if our
+ * sub-devices return -EPROBE_DEFER that we will not return any error codes
+ * ourselves but our sub-devices will _not_ have actually probed successfully
+ * yet. There may be other cases (maybe added in the future?) where sub-devices
+ * won't have been probed yet when this function returns, so it's best not to
+ * rely on that.
+ *
+ * If this function succeeds you should later make sure you call
+ * of_dp_aux_depopulate_ep_devices() to undo it, or just use the devm version
+ * of this function.
+ *
+ * Return: 0 if no error or negative error code.
+ */
+int of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux)
+{
+ struct device_node *bus, *np;
+ struct dp_aux_ep_device *aux_ep;
+ int ret;
+
+ /* drm_dp_aux_init() should have been called already; warn if not */
+ WARN_ON_ONCE(!aux->ddc.algo);
+
+ if (!aux->dev->of_node)
+ return 0;
+
+ bus = of_get_child_by_name(aux->dev->of_node, "aux-bus");
+ if (!bus)
+ return 0;
+
+ for_each_available_child_of_node(bus, np) {
+ if (of_node_test_and_set_flag(np, OF_POPULATED))
+ continue;
+
+ aux_ep = kzalloc(sizeof(*aux_ep), GFP_KERNEL);
+ if (!aux_ep)
+ continue;
+ aux_ep->aux = aux;
+
+ aux_ep->dev.parent = aux->dev;
+ aux_ep->dev.bus = &dp_aux_bus_type;
+ aux_ep->dev.type = &dp_aux_device_type_type;
+ aux_ep->dev.of_node = of_node_get(np);
+ dev_set_name(&aux_ep->dev, "aux-%s", dev_name(aux->dev));
+
+ ret = device_register(&aux_ep->dev);
+ if (ret) {
+ dev_err(aux->dev, "Failed to create AUX EP for %pOF: %d\n", np, ret);
+ of_node_clear_flag(np, OF_POPULATED);
+ of_node_put(np);
+
+ /*
+ * As per docs of device_register(), call this instead
+ * of kfree() directly for error cases.
+ */
+ put_device(&aux_ep->dev);
+
+ /*
+ * Following in the footsteps of of_i2c_register_devices(),
+ * we won't fail the whole function here--we'll just
+ * continue registering any other devices we find.
+ */
+ }
+ }
+
+ of_node_put(bus);
+
+ return 0;
+}
+
+static void of_dp_aux_depopulate_ep_devices_void(void *data)
+{
+ of_dp_aux_depopulate_ep_devices(data);
+}
+
+/**
+ * devm_of_dp_aux_populate_ep_devices() - devm wrapper for of_dp_aux_populate_ep_devices()
+ * @aux: The AUX channel whose devices we want to populate
+ *
+ * Handles freeing w/ devm on the device "aux->dev".
+ *
+ * Return: 0 if no error or negative error code.
+ */
+int devm_of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux)
+{
+ int ret;
+
+ ret = of_dp_aux_populate_ep_devices(aux);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(aux->dev,
+ of_dp_aux_depopulate_ep_devices_void,
+ aux);
+}
+EXPORT_SYMBOL_GPL(devm_of_dp_aux_populate_ep_devices);
+
+int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *drv, struct module *owner)
+{
+ drv->driver.owner = owner;
+ drv->driver.bus = &dp_aux_bus_type;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(__dp_aux_dp_driver_register);
+
+void dp_aux_dp_driver_unregister(struct dp_aux_ep_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(dp_aux_dp_driver_unregister);
+
+static int __init dp_aux_bus_init(void)
+{
+ int ret;
+
+ ret = bus_register(&dp_aux_bus_type);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void __exit dp_aux_bus_exit(void)
+{
+ bus_unregister(&dp_aux_bus_type);
+}
+
+subsys_initcall(dp_aux_bus_init);
+module_exit(dp_aux_bus_exit);
+
+MODULE_AUTHOR("Douglas Anderson <[email protected]>");
+MODULE_DESCRIPTION("DRM DisplayPort AUX bus");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c
index 55b53df6ce34..24bbc710c825 100644
--- a/drivers/gpu/drm/drm_dp_helper.c
+++ b/drivers/gpu/drm/drm_dp_helper.c
@@ -3115,3 +3115,350 @@ int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc)
return 0;
}
EXPORT_SYMBOL(drm_dp_pcon_convert_rgb_to_ycbcr);
+
+/**
+ * drm_edp_backlight_set_level() - Set the backlight level of an eDP panel via AUX
+ * @aux: The DP AUX channel to use
+ * @bl: Backlight capability info from drm_edp_backlight_init()
+ * @level: The brightness level to set
+ *
+ * Sets the brightness level of an eDP panel's backlight. Note that the panel's backlight must
+ * already have been enabled by the driver by calling drm_edp_backlight_enable().
+ *
+ * Returns: %0 on success, negative error code on failure
+ */
+int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl,
+ u16 level)
+{
+ int ret;
+ u8 buf[2] = { 0 };
+
+ if (bl->lsb_reg_used) {
+ buf[0] = (level & 0xff00) >> 8;
+ buf[1] = (level & 0x00ff);
+ } else {
+ buf[0] = level;
+ }
+
+ ret = drm_dp_dpcd_write(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, sizeof(buf));
+ if (ret != sizeof(buf)) {
+ drm_err(aux->drm_dev,
+ "%s: Failed to write aux backlight level: %d\n",
+ aux->name, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_edp_backlight_set_level);
+
+static int
+drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl,
+ bool enable)
+{
+ int ret;
+ u8 buf;
+
+ /* The panel uses something other then DPCD for enabling its backlight */
+ if (!bl->aux_enable)
+ return 0;
+
+ ret = drm_dp_dpcd_readb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, &buf);
+ if (ret != 1) {
+ drm_err(aux->drm_dev, "%s: Failed to read eDP display control register: %d\n",
+ aux->name, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+ if (enable)
+ buf |= DP_EDP_BACKLIGHT_ENABLE;
+ else
+ buf &= ~DP_EDP_BACKLIGHT_ENABLE;
+
+ ret = drm_dp_dpcd_writeb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, buf);
+ if (ret != 1) {
+ drm_err(aux->drm_dev, "%s: Failed to write eDP display control register: %d\n",
+ aux->name, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * drm_edp_backlight_enable() - Enable an eDP panel's backlight using DPCD
+ * @aux: The DP AUX channel to use
+ * @bl: Backlight capability info from drm_edp_backlight_init()
+ * @level: The initial backlight level to set via AUX, if there is one
+ *
+ * This function handles enabling DPCD backlight controls on a panel over DPCD, while additionally
+ * restoring any important backlight state such as the given backlight level, the brightness byte
+ * count, backlight frequency, etc.
+ *
+ * Note that certain panels, while supporting brightness level controls over DPCD, may not support
+ * having their backlights enabled via the standard %DP_EDP_DISPLAY_CONTROL_REGISTER. On such panels
+ * &drm_edp_backlight_info.aux_enable will be set to %false, this function will skip the step of
+ * programming the %DP_EDP_DISPLAY_CONTROL_REGISTER, and the driver must perform the required
+ * implementation specific step for enabling the backlight after calling this function.
+ *
+ * Returns: %0 on success, negative error code on failure.
+ */
+int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl,
+ const u16 level)
+{
+ int ret;
+ u8 dpcd_buf, new_dpcd_buf;
+
+ ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf);
+ if (ret != 1) {
+ drm_dbg_kms(aux->drm_dev,
+ "%s: Failed to read backlight mode: %d\n", aux->name, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ new_dpcd_buf = dpcd_buf;
+
+ if ((dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK) != DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
+ new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK;
+ new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD;
+
+ ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count);
+ if (ret != 1)
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n",
+ aux->name, ret);
+ }
+
+ if (bl->pwm_freq_pre_divider) {
+ ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_FREQ_SET, bl->pwm_freq_pre_divider);
+ if (ret != 1)
+ drm_dbg_kms(aux->drm_dev,
+ "%s: Failed to write aux backlight frequency: %d\n",
+ aux->name, ret);
+ else
+ new_dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE;
+ }
+
+ if (new_dpcd_buf != dpcd_buf) {
+ ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf);
+ if (ret != 1) {
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n",
+ aux->name, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+ }
+
+ ret = drm_edp_backlight_set_level(aux, bl, level);
+ if (ret < 0)
+ return ret;
+ ret = drm_edp_backlight_set_enable(aux, bl, true);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_edp_backlight_enable);
+
+/**
+ * drm_edp_backlight_disable() - Disable an eDP backlight using DPCD, if supported
+ * @aux: The DP AUX channel to use
+ * @bl: Backlight capability info from drm_edp_backlight_init()
+ *
+ * This function handles disabling DPCD backlight controls on a panel over AUX. Note that some
+ * panels have backlights that are enabled/disabled by other means, despite having their brightness
+ * values controlled through DPCD. On such panels &drm_edp_backlight_info.aux_enable will be set to
+ * %false, this function will become a no-op (and we will skip updating
+ * %DP_EDP_DISPLAY_CONTROL_REGISTER), and the driver must take care to perform it's own
+ * implementation specific step for disabling the backlight.
+ *
+ * Returns: %0 on success or no-op, negative error code on failure.
+ */
+int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl)
+{
+ int ret;
+
+ ret = drm_edp_backlight_set_enable(aux, bl, false);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_edp_backlight_disable);
+
+static inline int
+drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl,
+ u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE])
+{
+ int fxp, fxp_min, fxp_max, fxp_actual, f = 1;
+ int ret;
+ u8 pn, pn_min, pn_max;
+
+ ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn);
+ if (ret != 1) {
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n",
+ aux->name, ret);
+ return -ENODEV;
+ }
+
+ pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK;
+ bl->max = (1 << pn) - 1;
+ if (!driver_pwm_freq_hz)
+ return 0;
+
+ /*
+ * Set PWM Frequency divider to match desired frequency provided by the driver.
+ * The PWM Frequency is calculated as 27Mhz / (F x P).
+ * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the
+ * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h)
+ * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the
+ * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h)
+ */
+
+ /* Find desired value of (F x P)
+ * Note that, if F x P is out of supported range, the maximum value or minimum value will
+ * applied automatically. So no need to check that.
+ */
+ fxp = DIV_ROUND_CLOSEST(1000 * DP_EDP_BACKLIGHT_FREQ_BASE_KHZ, driver_pwm_freq_hz);
+
+ /* Use highest possible value of Pn for more granularity of brightness adjustment while
+ * satifying the conditions below.
+ * - Pn is in the range of Pn_min and Pn_max
+ * - F is in the range of 1 and 255
+ * - FxP is within 25% of desired value.
+ * Note: 25% is arbitrary value and may need some tweak.
+ */
+ ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min);
+ if (ret != 1) {
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n",
+ aux->name, ret);
+ return 0;
+ }
+ ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max);
+ if (ret != 1) {
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n",
+ aux->name, ret);
+ return 0;
+ }
+ pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK;
+ pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK;
+
+ /* Ensure frequency is within 25% of desired value */
+ fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4);
+ fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4);
+ if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) {
+ drm_dbg_kms(aux->drm_dev,
+ "%s: Driver defined backlight frequency (%d) out of range\n",
+ aux->name, driver_pwm_freq_hz);
+ return 0;
+ }
+
+ for (pn = pn_max; pn >= pn_min; pn--) {
+ f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255);
+ fxp_actual = f << pn;
+ if (fxp_min <= fxp_actual && fxp_actual <= fxp_max)
+ break;
+ }
+
+ ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, pn);
+ if (ret != 1) {
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n",
+ aux->name, ret);
+ return 0;
+ }
+ bl->pwmgen_bit_count = pn;
+ bl->max = (1 << pn) - 1;
+
+ if (edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) {
+ bl->pwm_freq_pre_divider = f;
+ drm_dbg_kms(aux->drm_dev, "%s: Using backlight frequency from driver (%dHz)\n",
+ aux->name, driver_pwm_freq_hz);
+ }
+
+ return 0;
+}
+
+static inline int
+drm_edp_backlight_probe_level(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl,
+ u8 *current_mode)
+{
+ int ret;
+ u8 buf[2];
+ u8 mode_reg;
+
+ ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &mode_reg);
+ if (ret != 1) {
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight mode: %d\n",
+ aux->name, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ *current_mode = (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK);
+ if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
+ int size = 1 + bl->lsb_reg_used;
+
+ ret = drm_dp_dpcd_read(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, size);
+ if (ret != size) {
+ drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight level: %d\n",
+ aux->name, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ if (bl->lsb_reg_used)
+ return (buf[0] << 8) | buf[1];
+ else
+ return buf[0];
+ }
+
+ /*
+ * If we're not in DPCD control mode yet, the programmed brightness value is meaningless and
+ * the driver should assume max brightness
+ */
+ return bl->max;
+}
+
+/**
+ * drm_edp_backlight_init() - Probe a display panel's TCON using the standard VESA eDP backlight
+ * interface.
+ * @aux: The DP aux device to use for probing
+ * @bl: The &drm_edp_backlight_info struct to fill out with information on the backlight
+ * @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz
+ * @edp_dpcd: A cached copy of the eDP DPCD
+ * @current_level: Where to store the probed brightness level
+ * @current_mode: Where to store the currently set backlight control mode
+ *
+ * Initializes a &drm_edp_backlight_info struct by probing @aux for it's backlight capabilities,
+ * along with also probing the current and maximum supported brightness levels.
+ *
+ * If @driver_pwm_freq_hz is non-zero, this will be used as the backlight frequency. Otherwise, the
+ * default frequency from the panel is used.
+ *
+ * Returns: %0 on success, negative error code on failure.
+ */
+int
+drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl,
+ u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE],
+ u16 *current_level, u8 *current_mode)
+{
+ int ret;
+
+ if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP)
+ bl->aux_enable = true;
+ if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT)
+ bl->lsb_reg_used = true;
+
+ ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd);
+ if (ret < 0)
+ return ret;
+
+ ret = drm_edp_backlight_probe_level(aux, bl, current_mode);
+ if (ret < 0)
+ return ret;
+ *current_level = ret;
+
+ drm_dbg_kms(aux->drm_dev,
+ "%s: Found backlight level=%d/%d pwm_freq_pre_divider=%d mode=%x\n",
+ aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, *current_mode);
+ drm_dbg_kms(aux->drm_dev,
+ "%s: Backlight caps: pwmgen_bit_count=%d lsb_reg_used=%d aux_enable=%d\n",
+ aux->name, bl->pwmgen_bit_count, bl->lsb_reg_used, bl->aux_enable);
+ return 0;
+}
+EXPORT_SYMBOL(drm_edp_backlight_init);
diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index ee7cbdd7db87..15e91a99c8b9 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -310,7 +310,7 @@ struct intel_panel {
/* DPCD backlight */
union {
struct {
- u8 pwmgen_bit_count;
+ struct drm_edp_backlight_info info;
} vesa;
struct {
bool sdr_uses_aux;
diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
index 8e9ac9ba1d38..6ac568617ef3 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
@@ -107,7 +107,7 @@ intel_dp_aux_supports_hdr_backlight(struct intel_connector *connector)
u8 tcon_cap[4];
ret = drm_dp_dpcd_read(aux, INTEL_EDP_HDR_TCON_CAP0, tcon_cap, sizeof(tcon_cap));
- if (ret < 0)
+ if (ret != sizeof(tcon_cap))
return false;
if (!(tcon_cap[1] & INTEL_EDP_HDR_TCON_BRIGHTNESS_NITS_CAP))
@@ -137,7 +137,7 @@ intel_dp_aux_hdr_get_backlight(struct intel_connector *connector, enum pipe pipe
u8 tmp;
u8 buf[2] = { 0 };
- if (drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &tmp) < 0) {
+ if (drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &tmp) != 1) {
drm_err(&i915->drm, "Failed to read current backlight mode from DPCD\n");
return 0;
}
@@ -153,7 +153,8 @@ intel_dp_aux_hdr_get_backlight(struct intel_connector *connector, enum pipe pipe
return panel->backlight.max;
}
- if (drm_dp_dpcd_read(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, sizeof(buf)) < 0) {
+ if (drm_dp_dpcd_read(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf,
+ sizeof(buf)) != sizeof(buf)) {
drm_err(&i915->drm, "Failed to read brightness from DPCD\n");
return 0;
}
@@ -172,7 +173,8 @@ intel_dp_aux_hdr_set_aux_backlight(const struct drm_connector_state *conn_state,
buf[0] = level & 0xFF;
buf[1] = (level & 0xFF00) >> 8;
- if (drm_dp_dpcd_write(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, 4) < 0)
+ if (drm_dp_dpcd_write(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf,
+ sizeof(buf)) != sizeof(buf))
drm_err(dev, "Failed to write brightness level to DPCD\n");
}
@@ -203,7 +205,7 @@ intel_dp_aux_hdr_enable_backlight(const struct intel_crtc_state *crtc_state,
u8 old_ctrl, ctrl;
ret = drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &old_ctrl);
- if (ret < 0) {
+ if (ret != 1) {
drm_err(&i915->drm, "Failed to read current backlight control mode: %d\n", ret);
return;
}
@@ -221,7 +223,7 @@ intel_dp_aux_hdr_enable_backlight(const struct intel_crtc_state *crtc_state,
}
if (ctrl != old_ctrl)
- if (drm_dp_dpcd_writeb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, ctrl) < 0)
+ if (drm_dp_dpcd_writeb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, ctrl) != 1)
drm_err(&i915->drm, "Failed to configure DPCD brightness controls\n");
}
@@ -268,153 +270,19 @@ intel_dp_aux_hdr_setup_backlight(struct intel_connector *connector, enum pipe pi
}
/* VESA backlight callbacks */
-static void set_vesa_backlight_enable(struct intel_dp *intel_dp, bool enable)
-{
- struct drm_i915_private *i915 = dp_to_i915(intel_dp);
- u8 reg_val = 0;
-
- /* Early return when display use other mechanism to enable backlight. */
- if (!(intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP))
- return;
-
- if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER,
- &reg_val) < 0) {
- drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n",
- DP_EDP_DISPLAY_CONTROL_REGISTER);
- return;
- }
- if (enable)
- reg_val |= DP_EDP_BACKLIGHT_ENABLE;
- else
- reg_val &= ~(DP_EDP_BACKLIGHT_ENABLE);
-
- if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER,
- reg_val) != 1) {
- drm_dbg_kms(&i915->drm, "Failed to %s aux backlight\n",
- enabledisable(enable));
- }
-}
-
-static bool intel_dp_aux_vesa_backlight_dpcd_mode(struct intel_connector *connector)
-{
- struct intel_dp *intel_dp = intel_attached_dp(connector);
- struct drm_i915_private *i915 = dp_to_i915(intel_dp);
- u8 mode_reg;
-
- if (drm_dp_dpcd_readb(&intel_dp->aux,
- DP_EDP_BACKLIGHT_MODE_SET_REGISTER,
- &mode_reg) != 1) {
- drm_dbg_kms(&i915->drm,
- "Failed to read the DPCD register 0x%x\n",
- DP_EDP_BACKLIGHT_MODE_SET_REGISTER);
- return false;
- }
-
- return (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK) ==
- DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD;
-}
-
-/*
- * Read the current backlight value from DPCD register(s) based
- * on if 8-bit(MSB) or 16-bit(MSB and LSB) values are supported
- */
static u32 intel_dp_aux_vesa_get_backlight(struct intel_connector *connector, enum pipe unused)
{
- struct intel_dp *intel_dp = intel_attached_dp(connector);
- struct drm_i915_private *i915 = dp_to_i915(intel_dp);
- u8 read_val[2] = { 0x0 };
- u16 level = 0;
-
- /*
- * If we're not in DPCD control mode yet, the programmed brightness
- * value is meaningless and we should assume max brightness
- */
- if (!intel_dp_aux_vesa_backlight_dpcd_mode(connector))
- return connector->panel.backlight.max;
-
- if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB,
- &read_val, sizeof(read_val)) < 0) {
- drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n",
- DP_EDP_BACKLIGHT_BRIGHTNESS_MSB);
- return 0;
- }
- level = read_val[0];
- if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT)
- level = (read_val[0] << 8 | read_val[1]);
-
- return level;
+ return connector->panel.backlight.level;
}
-/*
- * Sends the current backlight level over the aux channel, checking if its using
- * 8-bit or 16 bit value (MSB and LSB)
- */
static void
-intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state,
- u32 level)
+intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state, u32 level)
{
struct intel_connector *connector = to_intel_connector(conn_state->connector);
- struct intel_dp *intel_dp = intel_attached_dp(connector);
- struct drm_i915_private *i915 = dp_to_i915(intel_dp);
- u8 vals[2] = { 0x0 };
-
- vals[0] = level;
-
- /* Write the MSB and/or LSB */
- if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) {
- vals[0] = (level & 0xFF00) >> 8;
- vals[1] = (level & 0xFF);
- }
- if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB,
- vals, sizeof(vals)) < 0) {
- drm_dbg_kms(&i915->drm,
- "Failed to write aux backlight level\n");
- return;
- }
-}
-
-/*
- * Set PWM Frequency divider to match desired frequency in vbt.
- * The PWM Frequency is calculated as 27Mhz / (F x P).
- * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the
- * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h)
- * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the
- * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h)
- */
-static bool intel_dp_aux_vesa_set_pwm_freq(struct intel_connector *connector)
-{
- struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
- struct intel_dp *intel_dp = intel_attached_dp(connector);
- const u8 pn = connector->panel.backlight.edp.vesa.pwmgen_bit_count;
- int freq, fxp, f, fxp_actual, fxp_min, fxp_max;
-
- freq = dev_priv->vbt.backlight.pwm_freq_hz;
- if (!freq) {
- drm_dbg_kms(&dev_priv->drm,
- "Use panel default backlight frequency\n");
- return false;
- }
-
- fxp = DIV_ROUND_CLOSEST(KHz(DP_EDP_BACKLIGHT_FREQ_BASE_KHZ), freq);
- f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255);
- fxp_actual = f << pn;
-
- /* Ensure frequency is within 25% of desired value */
- fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4);
- fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4);
-
- if (fxp_min > fxp_actual || fxp_actual > fxp_max) {
- drm_dbg_kms(&dev_priv->drm, "Actual frequency out of range\n");
- return false;
- }
+ struct intel_panel *panel = &connector->panel;
+ struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
- if (drm_dp_dpcd_writeb(&intel_dp->aux,
- DP_EDP_BACKLIGHT_FREQ_SET, (u8) f) < 0) {
- drm_dbg_kms(&dev_priv->drm,
- "Failed to write aux backlight freq\n");
- return false;
- }
- return true;
+ drm_edp_backlight_set_level(&intel_dp->aux, &panel->backlight.edp.vesa.info, level);
}
static void
@@ -422,159 +290,46 @@ intel_dp_aux_vesa_enable_backlight(const struct intel_crtc_state *crtc_state,
const struct drm_connector_state *conn_state, u32 level)
{
struct intel_connector *connector = to_intel_connector(conn_state->connector);
- struct intel_dp *intel_dp = intel_attached_dp(connector);
- struct drm_i915_private *i915 = dp_to_i915(intel_dp);
struct intel_panel *panel = &connector->panel;
- u8 dpcd_buf, new_dpcd_buf, edp_backlight_mode;
- u8 pwmgen_bit_count = panel->backlight.edp.vesa.pwmgen_bit_count;
-
- if (drm_dp_dpcd_readb(&intel_dp->aux,
- DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf) != 1) {
- drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n",
- DP_EDP_BACKLIGHT_MODE_SET_REGISTER);
- return;
- }
-
- new_dpcd_buf = dpcd_buf;
- edp_backlight_mode = dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK;
-
- switch (edp_backlight_mode) {
- case DP_EDP_BACKLIGHT_CONTROL_MODE_PWM:
- case DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET:
- case DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT:
- new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK;
- new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD;
-
- if (drm_dp_dpcd_writeb(&intel_dp->aux,
- DP_EDP_PWMGEN_BIT_COUNT,
- pwmgen_bit_count) < 0)
- drm_dbg_kms(&i915->drm,
- "Failed to write aux pwmgen bit count\n");
-
- break;
-
- /* Do nothing when it is already DPCD mode */
- case DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD:
- default:
- break;
- }
-
- if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP)
- if (intel_dp_aux_vesa_set_pwm_freq(connector))
- new_dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE;
-
- if (new_dpcd_buf != dpcd_buf) {
- if (drm_dp_dpcd_writeb(&intel_dp->aux,
- DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf) < 0) {
- drm_dbg_kms(&i915->drm,
- "Failed to write aux backlight mode\n");
- }
- }
+ struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
- intel_dp_aux_vesa_set_backlight(conn_state, level);
- set_vesa_backlight_enable(intel_dp, true);
+ drm_edp_backlight_enable(&intel_dp->aux, &panel->backlight.edp.vesa.info, level);
}
static void intel_dp_aux_vesa_disable_backlight(const struct drm_connector_state *old_conn_state,
u32 level)
{
- set_vesa_backlight_enable(enc_to_intel_dp(to_intel_encoder(old_conn_state->best_encoder)),
- false);
-}
-
-static u32 intel_dp_aux_vesa_calc_max_backlight(struct intel_connector *connector)
-{
- struct drm_i915_private *i915 = to_i915(connector->base.dev);
- struct intel_dp *intel_dp = intel_attached_dp(connector);
+ struct intel_connector *connector = to_intel_connector(old_conn_state->connector);
struct intel_panel *panel = &connector->panel;
- u32 max_backlight = 0;
- int freq, fxp, fxp_min, fxp_max, fxp_actual, f = 1;
- u8 pn, pn_min, pn_max;
-
- if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_PWMGEN_BIT_COUNT, &pn) == 1) {
- pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK;
- max_backlight = (1 << pn) - 1;
- }
-
- /* Find desired value of (F x P)
- * Note that, if F x P is out of supported range, the maximum value or
- * minimum value will applied automatically. So no need to check that.
- */
- freq = i915->vbt.backlight.pwm_freq_hz;
- drm_dbg_kms(&i915->drm, "VBT defined backlight frequency %u Hz\n",
- freq);
- if (!freq) {
- drm_dbg_kms(&i915->drm,
- "Use panel default backlight frequency\n");
- return max_backlight;
- }
-
- fxp = DIV_ROUND_CLOSEST(KHz(DP_EDP_BACKLIGHT_FREQ_BASE_KHZ), freq);
-
- /* Use highest possible value of Pn for more granularity of brightness
- * adjustment while satifying the conditions below.
- * - Pn is in the range of Pn_min and Pn_max
- * - F is in the range of 1 and 255
- * - FxP is within 25% of desired value.
- * Note: 25% is arbitrary value and may need some tweak.
- */
- if (drm_dp_dpcd_readb(&intel_dp->aux,
- DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min) != 1) {
- drm_dbg_kms(&i915->drm,
- "Failed to read pwmgen bit count cap min\n");
- return max_backlight;
- }
- if (drm_dp_dpcd_readb(&intel_dp->aux,
- DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max) != 1) {
- drm_dbg_kms(&i915->drm,
- "Failed to read pwmgen bit count cap max\n");
- return max_backlight;
- }
- pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK;
- pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK;
-
- fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4);
- fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4);
- if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) {
- drm_dbg_kms(&i915->drm,
- "VBT defined backlight frequency out of range\n");
- return max_backlight;
- }
-
- for (pn = pn_max; pn >= pn_min; pn--) {
- f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255);
- fxp_actual = f << pn;
- if (fxp_min <= fxp_actual && fxp_actual <= fxp_max)
- break;
- }
-
- drm_dbg_kms(&i915->drm, "Using eDP pwmgen bit count of %d\n", pn);
- if (drm_dp_dpcd_writeb(&intel_dp->aux,
- DP_EDP_PWMGEN_BIT_COUNT, pn) < 0) {
- drm_dbg_kms(&i915->drm,
- "Failed to write aux pwmgen bit count\n");
- return max_backlight;
- }
- panel->backlight.edp.vesa.pwmgen_bit_count = pn;
-
- max_backlight = (1 << pn) - 1;
+ struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
- return max_backlight;
+ drm_edp_backlight_disable(&intel_dp->aux, &panel->backlight.edp.vesa.info);
}
-static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector,
- enum pipe pipe)
+static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, enum pipe pipe)
{
+ struct intel_dp *intel_dp = intel_attached_dp(connector);
struct intel_panel *panel = &connector->panel;
+ struct drm_i915_private *i915 = dp_to_i915(intel_dp);
+ u16 current_level;
+ u8 current_mode;
+ int ret;
- panel->backlight.max = intel_dp_aux_vesa_calc_max_backlight(connector);
- if (!panel->backlight.max)
- return -ENODEV;
+ ret = drm_edp_backlight_init(&intel_dp->aux, &panel->backlight.edp.vesa.info,
+ i915->vbt.backlight.pwm_freq_hz, intel_dp->edp_dpcd,
+ &current_level, &current_mode);
+ if (ret < 0)
+ return ret;
+ panel->backlight.max = panel->backlight.edp.vesa.info.max;
panel->backlight.min = 0;
- panel->backlight.level = intel_dp_aux_vesa_get_backlight(connector, pipe);
- panel->backlight.enabled = intel_dp_aux_vesa_backlight_dpcd_mode(connector) &&
- panel->backlight.level != 0;
+ if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
+ panel->backlight.level = current_level;
+ panel->backlight.enabled = panel->backlight.level != 0;
+ } else {
+ panel->backlight.level = panel->backlight.max;
+ panel->backlight.enabled = false;
+ }
return 0;
}
@@ -585,16 +340,12 @@ intel_dp_aux_supports_vesa_backlight(struct intel_connector *connector)
struct intel_dp *intel_dp = intel_attached_dp(connector);
struct drm_i915_private *i915 = dp_to_i915(intel_dp);
- /* Check the eDP Display control capabilities registers to determine if
- * the panel can support backlight control over the aux channel.
- *
- * TODO: We currently only support AUX only backlight configurations, not backlights which
+ /* TODO: We currently only support AUX only backlight configurations, not backlights which
* require a mix of PWM and AUX controls to work. In the mean time, these machines typically
* work just fine using normal PWM controls anyway.
*/
- if (intel_dp->edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP &&
- (intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) &&
- (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) {
+ if ((intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) &&
+ drm_edp_backlight_supported(intel_dp->edp_dpcd)) {
drm_dbg_kms(&i915->drm, "AUX Backlight Control Supported!\n");
return true;
}
diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index f949767698fc..093e1f7163b3 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -31,6 +31,7 @@
#include <linux/dma-mapping.h>
#include <linux/hdmi.h>
#include <linux/component.h>
+#include <linux/iopoll.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
@@ -1649,15 +1650,30 @@ nv50_sor_update(struct nouveau_encoder *nv_encoder, u8 head,
core->func->sor->ctrl(core, nv_encoder->or, nv_encoder->ctrl, asyh);
}
+/* TODO: Should we extend this to PWM-only backlights?
+ * As well, should we add a DRM helper for waiting for the backlight to acknowledge
+ * the panel backlight has been shut off? Intel doesn't seem to do this, and uses a
+ * fixed time delay from the vbios…
+ */
static void
nv50_sor_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);
struct nouveau_crtc *nv_crtc = nouveau_crtc(nv_encoder->crtc);
struct nouveau_connector *nv_connector = nv50_outp_get_old_connector(state, nv_encoder);
+ struct nouveau_backlight *backlight = nv_connector->backlight;
struct drm_dp_aux *aux = &nv_connector->aux;
+ int ret;
u8 pwr;
+ if (backlight && backlight->uses_dpcd) {
+ ret = drm_edp_backlight_disable(aux, &backlight->edp_info);
+ if (ret < 0)
+ NV_ERROR(drm, "Failed to disable backlight on [CONNECTOR:%d:%s]: %d\n",
+ nv_connector->base.base.id, nv_connector->base.name, ret);
+ }
+
if (nv_encoder->dcb->type == DCB_OUTPUT_DP) {
int ret = drm_dp_dpcd_readb(aux, DP_SET_POWER, &pwr);
@@ -1696,6 +1712,9 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
struct drm_device *dev = encoder->dev;
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_connector *nv_connector;
+#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
+ struct nouveau_backlight *backlight;
+#endif
struct nvbios *bios = &drm->vbios;
bool hda = false;
u8 proto = NV507D_SOR_SET_CONTROL_PROTOCOL_CUSTOM;
@@ -1770,6 +1789,14 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
proto = NV887D_SOR_SET_CONTROL_PROTOCOL_DP_B;
nv50_audio_enable(encoder, nv_crtc, nv_connector, state, mode);
+
+#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
+ backlight = nv_connector->backlight;
+ if (backlight && backlight->uses_dpcd)
+ drm_edp_backlight_enable(&nv_connector->aux, &backlight->edp_info,
+ (u16)backlight->dev->props.brightness);
+#endif
+
break;
default:
BUG();
@@ -2295,6 +2322,7 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
nv50_crc_atomic_start_reporting(state);
if (!flushed)
nv50_crc_atomic_release_notifier_contexts(state);
+
drm_atomic_helper_commit_hw_done(state);
drm_atomic_helper_cleanup_planes(dev, state);
drm_atomic_helper_commit_cleanup_done(state);
diff --git a/drivers/gpu/drm/nouveau/nouveau_backlight.c b/drivers/gpu/drm/nouveau/nouveau_backlight.c
index 72f35a2babcb..1cbd71abc80a 100644
--- a/drivers/gpu/drm/nouveau/nouveau_backlight.c
+++ b/drivers/gpu/drm/nouveau/nouveau_backlight.c
@@ -42,11 +42,6 @@
static struct ida bl_ida;
#define BL_NAME_SIZE 15 // 12 for name + 2 for digits + 1 for '\0'
-struct nouveau_backlight {
- struct backlight_device *dev;
- int id;
-};
-
static bool
nouveau_get_backlight_name(char backlight_name[BL_NAME_SIZE],
struct nouveau_backlight *bl)
@@ -148,6 +143,98 @@ static const struct backlight_ops nv50_bl_ops = {
.update_status = nv50_set_intensity,
};
+/*
+ * eDP brightness callbacks need to happen under lock, since we need to
+ * enable/disable the backlight ourselves for modesets
+ */
+static int
+nv50_edp_get_brightness(struct backlight_device *bd)
+{
+ struct drm_connector *connector = dev_get_drvdata(bd->dev.parent);
+ struct drm_device *dev = connector->dev;
+ struct drm_crtc *crtc;
+ struct drm_modeset_acquire_ctx ctx;
+ int ret = 0;
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+retry:
+ ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
+ if (ret == -EDEADLK)
+ goto deadlock;
+ else if (ret < 0)
+ goto out;
+
+ crtc = connector->state->crtc;
+ if (!crtc)
+ goto out;
+
+ ret = drm_modeset_lock(&crtc->mutex, &ctx);
+ if (ret == -EDEADLK)
+ goto deadlock;
+ else if (ret < 0)
+ goto out;
+
+ if (!crtc->state->active)
+ goto out;
+
+ ret = bd->props.brightness;
+out:
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+ return ret;
+deadlock:
+ drm_modeset_backoff(&ctx);
+ goto retry;
+}
+
+static int
+nv50_edp_set_brightness(struct backlight_device *bd)
+{
+ struct drm_connector *connector = dev_get_drvdata(bd->dev.parent);
+ struct nouveau_connector *nv_connector = nouveau_connector(connector);
+ struct drm_device *dev = connector->dev;
+ struct drm_crtc *crtc;
+ struct drm_dp_aux *aux = &nv_connector->aux;
+ struct nouveau_backlight *nv_bl = nv_connector->backlight;
+ struct drm_modeset_acquire_ctx ctx;
+ int ret = 0;
+
+ drm_modeset_acquire_init(&ctx, 0);
+retry:
+ ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
+ if (ret == -EDEADLK)
+ goto deadlock;
+ else if (ret < 0)
+ goto out;
+
+ crtc = connector->state->crtc;
+ if (!crtc)
+ goto out;
+
+ ret = drm_modeset_lock(&crtc->mutex, &ctx);
+ if (ret == -EDEADLK)
+ goto deadlock;
+ else if (ret < 0)
+ goto out;
+
+ if (crtc->state->active)
+ ret = drm_edp_backlight_set_level(aux, &nv_bl->edp_info, bd->props.brightness);
+
+out:
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+ return ret;
+deadlock:
+ drm_modeset_backoff(&ctx);
+ goto retry;
+}
+
+static const struct backlight_ops nv50_edp_bl_ops = {
+ .get_brightness = nv50_edp_get_brightness,
+ .update_status = nv50_edp_set_brightness,
+};
+
static int
nva3_get_intensity(struct backlight_device *bd)
{
@@ -194,8 +281,13 @@ static const struct backlight_ops nva3_bl_ops = {
.update_status = nva3_set_intensity,
};
+/* FIXME: perform backlight probing for eDP _before_ this, this only gets called after connector
+ * registration which happens after the initial modeset
+ */
static int
-nv50_backlight_init(struct nouveau_encoder *nv_encoder,
+nv50_backlight_init(struct nouveau_backlight *bl,
+ struct nouveau_connector *nv_conn,
+ struct nouveau_encoder *nv_encoder,
struct backlight_properties *props,
const struct backlight_ops **ops)
{
@@ -205,6 +297,41 @@ nv50_backlight_init(struct nouveau_encoder *nv_encoder,
if (!nvif_rd32(device, NV50_PDISP_SOR_PWM_CTL(ffs(nv_encoder->dcb->or) - 1)))
return -ENODEV;
+ if (nv_conn->type == DCB_CONNECTOR_eDP) {
+ int ret;
+ u16 current_level;
+ u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE];
+ u8 current_mode;
+
+ ret = drm_dp_dpcd_read(&nv_conn->aux, DP_EDP_DPCD_REV, edp_dpcd,
+ EDP_DISPLAY_CTL_CAP_SIZE);
+ if (ret < 0)
+ return ret;
+
+ if (drm_edp_backlight_supported(edp_dpcd)) {
+ NV_DEBUG(drm, "DPCD backlight controls supported on %s\n",
+ nv_conn->base.name);
+
+ ret = drm_edp_backlight_init(&nv_conn->aux, &bl->edp_info, 0, edp_dpcd,
+ &current_level, &current_mode);
+ if (ret < 0)
+ return ret;
+
+ ret = drm_edp_backlight_enable(&nv_conn->aux, &bl->edp_info, current_level);
+ if (ret < 0) {
+ NV_ERROR(drm, "Failed to enable backlight on %s: %d\n",
+ nv_conn->base.name, ret);
+ return ret;
+ }
+
+ *ops = &nv50_edp_bl_ops;
+ props->brightness = current_level;
+ props->max_brightness = bl->edp_info.max;
+ bl->uses_dpcd = true;
+ return 0;
+ }
+ }
+
if (drm->client.device.info.chipset <= 0xa0 ||
drm->client.device.info.chipset == 0xaa ||
drm->client.device.info.chipset == 0xac)
@@ -245,6 +372,10 @@ nouveau_backlight_init(struct drm_connector *connector)
if (!nv_encoder)
return 0;
+ bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+ if (!bl)
+ return -ENOMEM;
+
switch (device->info.family) {
case NV_DEVICE_INFO_V0_CURIE:
ret = nv40_backlight_init(nv_encoder, &props, &ops);
@@ -257,20 +388,19 @@ nouveau_backlight_init(struct drm_connector *connector)
case NV_DEVICE_INFO_V0_VOLTA:
case NV_DEVICE_INFO_V0_TURING:
case NV_DEVICE_INFO_V0_AMPERE: //XXX: not confirmed
- ret = nv50_backlight_init(nv_encoder, &props, &ops);
+ ret = nv50_backlight_init(bl, nouveau_connector(connector),
+ nv_encoder, &props, &ops);
break;
default:
- return 0;
+ ret = 0;
+ goto fail_alloc;
}
- if (ret == -ENODEV)
- return 0;
- else if (ret)
- return ret;
-
- bl = kzalloc(sizeof(*bl), GFP_KERNEL);
- if (!bl)
- return -ENOMEM;
+ if (ret) {
+ if (ret == -ENODEV)
+ ret = 0;
+ goto fail_alloc;
+ }
if (!nouveau_get_backlight_name(backlight_name, bl)) {
NV_ERROR(drm, "Failed to retrieve a unique name for the backlight interface\n");
@@ -287,7 +417,9 @@ nouveau_backlight_init(struct drm_connector *connector)
}
nouveau_connector(connector)->backlight = bl;
- bl->dev->props.brightness = bl->dev->ops->get_brightness(bl->dev);
+ if (!bl->dev->props.brightness)
+ bl->dev->props.brightness =
+ bl->dev->ops->get_brightness(bl->dev);
backlight_update_status(bl->dev);
return 0;
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 520b1ea9d16c..085023624fb0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -149,6 +149,8 @@ nouveau_bo_del_ttm(struct ttm_buffer_object *bo)
*/
if (bo->base.dev)
drm_gem_object_release(&bo->base);
+ else
+ dma_resv_fini(&bo->base._resv);
kfree(nvbo);
}
@@ -330,6 +332,10 @@ nouveau_bo_new(struct nouveau_cli *cli, u64 size, int align,
if (IS_ERR(nvbo))
return PTR_ERR(nvbo);
+ nvbo->bo.base.size = size;
+ dma_resv_init(&nvbo->bo.base._resv);
+ drm_vma_node_reset(&nvbo->bo.base.vma_node);
+
ret = nouveau_bo_init(nvbo, size, align, domain, sg, robj);
if (ret)
return ret;
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h
index d0b859c4a80e..40f90e353540 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -46,7 +46,14 @@ struct nvkm_i2c_port;
struct dcb_output;
#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
-struct nouveau_backlight;
+struct nouveau_backlight {
+ struct backlight_device *dev;
+
+ struct drm_edp_backlight_info edp_info;
+ bool uses_dpcd : 1;
+
+ int id;
+};
#endif
#define nouveau_conn_atom(p) \
diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h
index 1ffcc0a491fd..77c2fed76e8b 100644
--- a/drivers/gpu/drm/nouveau/nouveau_encoder.h
+++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h
@@ -30,6 +30,7 @@
#include <subdev/bios/dcb.h>
#include <drm/drm_encoder_slave.h>
+#include <drm/drm_dp_helper.h>
#include <drm/drm_dp_mst_helper.h>
#include "dispnv04/disp.h"
struct nv50_head_atom;
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index ef87d92cdf49..7c32b040aa72 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -82,6 +82,7 @@ config DRM_PANEL_SIMPLE
depends on BACKLIGHT_CLASS_DEVICE
depends on PM
select VIDEOMODE_HELPERS
+ select DRM_DP_AUX_BUS
help
DRM panel driver for dumb panels that need at most a regulator and
a GPIO to be powered up. Optionally a backlight can be attached so
@@ -343,6 +344,16 @@ config DRM_PANEL_RONBO_RB070D30
Say Y here if you want to enable support for Ronbo Electronics
RB070D30 1024x600 DSI panel.
+config DRM_PANEL_SAMSUNG_DB7430
+ tristate "Samsung DB7430-based DPI panels"
+ depends on OF && SPI && GPIOLIB
+ depends on BACKLIGHT_CLASS_DEVICE
+ select DRM_MIPI_DBI
+ help
+ Say Y here if you want to enable support for the Samsung
+ DB7430 DPI display controller used in such devices as the
+ LMS397KF04 480x800 DPI panel.
+
config DRM_PANEL_SAMSUNG_S6D16D0
tristate "Samsung S6D16D0 DSI video mode panel"
depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index cae4d976c069..a350e0990d17 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen
obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM67191) += panel-raydium-rm67191.o
obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o
obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o
+obj-$(CONFIG_DRM_PANEL_SAMSUNG_DB7430) += panel-samsung-db7430.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D16D0) += panel-samsung-s6d16d0.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o
diff --git a/drivers/gpu/drm/panel/panel-samsung-db7430.c b/drivers/gpu/drm/panel/panel-samsung-db7430.c
new file mode 100644
index 000000000000..ead479719f00
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-db7430.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Panel driver for the Samsung LMS397KF04 480x800 DPI RGB panel.
+ * According to the data sheet the display controller is called DB7430.
+ * Found in the Samsung Galaxy Beam GT-I8350 mobile phone.
+ * Linus Walleij <[email protected]>
+ */
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+
+#define DB7430_ACCESS_PROT_OFF 0xb0
+#define DB7430_UNKNOWN_B4 0xb4
+#define DB7430_USER_SELECT 0xb5
+#define DB7430_UNKNOWN_B7 0xb7
+#define DB7430_UNKNOWN_B8 0xb8
+#define DB7430_PANEL_DRIVING 0xc0
+#define DB7430_SOURCE_CONTROL 0xc1
+#define DB7430_GATE_INTERFACE 0xc4
+#define DB7430_DISPLAY_H_TIMING 0xc5
+#define DB7430_RGB_SYNC_OPTION 0xc6
+#define DB7430_GAMMA_SET_RED 0xc8
+#define DB7430_GAMMA_SET_GREEN 0xc9
+#define DB7430_GAMMA_SET_BLUE 0xca
+#define DB7430_BIAS_CURRENT_CTRL 0xd1
+#define DB7430_DDV_CTRL 0xd2
+#define DB7430_GAMMA_CTRL_REF 0xd3
+#define DB7430_UNKNOWN_D4 0xd4
+#define DB7430_DCDC_CTRL 0xd5
+#define DB7430_VCL_CTRL 0xd6
+#define DB7430_UNKNOWN_F8 0xf8
+#define DB7430_UNKNOWN_FC 0xfc
+
+#define DATA_MASK 0x100
+
+/**
+ * struct db7430 - state container for a panel controlled by the DB7430
+ * controller
+ */
+struct db7430 {
+ /** @dev: the container device */
+ struct device *dev;
+ /** @dbi: the DBI bus abstraction handle */
+ struct mipi_dbi dbi;
+ /** @panel: the DRM panel instance for this device */
+ struct drm_panel panel;
+ /** @width: the width of this panel in mm */
+ u32 width;
+ /** @height: the height of this panel in mm */
+ u32 height;
+ /** @reset: reset GPIO line */
+ struct gpio_desc *reset;
+ /** @regulators: VCCIO and VIO supply regulators */
+ struct regulator_bulk_data regulators[2];
+};
+
+static const struct drm_display_mode db7430_480_800_mode = {
+ /*
+ * 31 ns period min (htotal*vtotal*vrefresh)/1000
+ * gives a Vrefresh of ~71 Hz.
+ */
+ .clock = 32258,
+ .hdisplay = 480,
+ .hsync_start = 480 + 10,
+ .hsync_end = 480 + 10 + 4,
+ .htotal = 480 + 10 + 4 + 40,
+ .vdisplay = 800,
+ .vsync_start = 800 + 6,
+ .vsync_end = 800 + 6 + 1,
+ .vtotal = 800 + 6 + 1 + 7,
+ .width_mm = 53,
+ .height_mm = 87,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static inline struct db7430 *to_db7430(struct drm_panel *panel)
+{
+ return container_of(panel, struct db7430, panel);
+}
+
+static int db7430_power_on(struct db7430 *db)
+{
+ struct mipi_dbi *dbi = &db->dbi;
+ int ret;
+
+ /* Power up */
+ ret = regulator_bulk_enable(ARRAY_SIZE(db->regulators),
+ db->regulators);
+ if (ret) {
+ dev_err(db->dev, "failed to enable regulators: %d\n", ret);
+ return ret;
+ }
+ msleep(50);
+
+ /* Assert reset >=1 ms */
+ gpiod_set_value_cansleep(db->reset, 1);
+ usleep_range(1000, 5000);
+ /* De-assert reset */
+ gpiod_set_value_cansleep(db->reset, 0);
+ /* Wait >= 10 ms */
+ msleep(10);
+ dev_dbg(db->dev, "de-asserted RESET\n");
+
+ /*
+ * This is set to 0x0a (RGB/BGR order + horizontal flip) in order
+ * to make the display behave normally. If this is not set the displays
+ * normal output behaviour is horizontally flipped and BGR ordered. Do
+ * it twice because the first message doesn't always "take".
+ */
+ mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x0a);
+ mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x0a);
+ mipi_dbi_command(dbi, DB7430_ACCESS_PROT_OFF, 0x00);
+ mipi_dbi_command(dbi, DB7430_PANEL_DRIVING, 0x28, 0x08);
+ mipi_dbi_command(dbi, DB7430_SOURCE_CONTROL,
+ 0x01, 0x30, 0x15, 0x05, 0x22);
+ mipi_dbi_command(dbi, DB7430_GATE_INTERFACE,
+ 0x10, 0x01, 0x00);
+ mipi_dbi_command(dbi, DB7430_DISPLAY_H_TIMING,
+ 0x06, 0x55, 0x03, 0x07, 0x0b,
+ 0x33, 0x00, 0x01, 0x03);
+ /*
+ * 0x00 in datasheet 0x01 in vendor code 0x00, it seems 0x01 means
+ * DE active high and 0x00 means DE active low.
+ */
+ mipi_dbi_command(dbi, DB7430_RGB_SYNC_OPTION, 0x01);
+ mipi_dbi_command(dbi, DB7430_GAMMA_SET_RED,
+ /* R positive gamma */ 0x00,
+ 0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62,
+ 0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08,
+ /* R negative gamma */ 0x00,
+ 0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62,
+ 0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08);
+ mipi_dbi_command(dbi, DB7430_GAMMA_SET_GREEN,
+ /* G positive gamma */ 0x00,
+ 0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59,
+ 0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A,
+ /* G negative gamma */ 0x00,
+ 0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59,
+ 0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A);
+ mipi_dbi_command(dbi, DB7430_GAMMA_SET_BLUE,
+ /* B positive gamma */ 0x00,
+ 0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D,
+ 0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C,
+ /* B negative gamma */ 0x00,
+ 0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D,
+ 0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C);
+ mipi_dbi_command(dbi, DB7430_BIAS_CURRENT_CTRL, 0x33, 0x13);
+ mipi_dbi_command(dbi, DB7430_DDV_CTRL, 0x11, 0x00, 0x00);
+ mipi_dbi_command(dbi, DB7430_GAMMA_CTRL_REF, 0x50, 0x50);
+ mipi_dbi_command(dbi, DB7430_DCDC_CTRL, 0x2f, 0x11, 0x1e, 0x46);
+ mipi_dbi_command(dbi, DB7430_VCL_CTRL, 0x11, 0x0a);
+
+ return 0;
+}
+
+static int db7430_power_off(struct db7430 *db)
+{
+ /* Go into RESET and disable regulators */
+ gpiod_set_value_cansleep(db->reset, 1);
+ return regulator_bulk_disable(ARRAY_SIZE(db->regulators),
+ db->regulators);
+}
+
+static int db7430_unprepare(struct drm_panel *panel)
+{
+ return db7430_power_off(to_db7430(panel));
+}
+
+static int db7430_disable(struct drm_panel *panel)
+{
+ struct db7430 *db = to_db7430(panel);
+ struct mipi_dbi *dbi = &db->dbi;
+
+ mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF);
+ msleep(25);
+ mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE);
+ msleep(120);
+
+ return 0;
+}
+
+static int db7430_prepare(struct drm_panel *panel)
+{
+ return db7430_power_on(to_db7430(panel));
+}
+
+static int db7430_enable(struct drm_panel *panel)
+{
+ struct db7430 *db = to_db7430(panel);
+ struct mipi_dbi *dbi = &db->dbi;
+
+ /* Exit sleep mode */
+ mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
+ msleep(20);
+
+ /* NVM (non-volatile memory) load sequence */
+ mipi_dbi_command(dbi, DB7430_UNKNOWN_D4, 0x52, 0x5e);
+ mipi_dbi_command(dbi, DB7430_UNKNOWN_F8, 0x01, 0xf5, 0xf2, 0x71, 0x44);
+ mipi_dbi_command(dbi, DB7430_UNKNOWN_FC, 0x00, 0x08);
+ msleep(150);
+
+ /* CABC turn on sequence (BC = backlight control) */
+ mipi_dbi_command(dbi, DB7430_UNKNOWN_B4, 0x0f, 0x00, 0x50);
+ mipi_dbi_command(dbi, DB7430_USER_SELECT, 0x80);
+ mipi_dbi_command(dbi, DB7430_UNKNOWN_B7, 0x24);
+ mipi_dbi_command(dbi, DB7430_UNKNOWN_B8, 0x01);
+
+ /* Turn on display */
+ mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
+
+ return 0;
+}
+
+/**
+ * db7430_get_modes() - return the mode
+ * @panel: the panel to get the mode for
+ * @connector: reference to the central DRM connector control structure
+ */
+static int db7430_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct db7430 *db = to_db7430(panel);
+ struct drm_display_mode *mode;
+ static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+ mode = drm_mode_duplicate(connector->dev, &db7430_480_800_mode);
+ if (!mode) {
+ dev_err(db->dev, "failed to add mode\n");
+ return -ENOMEM;
+ }
+
+ connector->display_info.bpc = 8;
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+ connector->display_info.bus_flags =
+ DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+
+ drm_mode_set_name(mode);
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static const struct drm_panel_funcs db7430_drm_funcs = {
+ .disable = db7430_disable,
+ .unprepare = db7430_unprepare,
+ .prepare = db7430_prepare,
+ .enable = db7430_enable,
+ .get_modes = db7430_get_modes,
+};
+
+static int db7430_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct db7430 *db;
+ int ret;
+
+ db = devm_kzalloc(dev, sizeof(*db), GFP_KERNEL);
+ if (!db)
+ return -ENOMEM;
+ db->dev = dev;
+
+ /*
+ * VCI is the analog voltage supply
+ * VCCIO is the digital I/O voltage supply
+ */
+ db->regulators[0].supply = "vci";
+ db->regulators[1].supply = "vccio";
+ ret = devm_regulator_bulk_get(dev,
+ ARRAY_SIZE(db->regulators),
+ db->regulators);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+ db->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(db->reset)) {
+ ret = PTR_ERR(db->reset);
+ return dev_err_probe(dev, ret, "no RESET GPIO\n");
+ }
+
+ ret = mipi_dbi_spi_init(spi, &db->dbi, NULL);
+ if (ret)
+ return dev_err_probe(dev, ret, "MIPI DBI init failed\n");
+
+ drm_panel_init(&db->panel, dev, &db7430_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ /* FIXME: if no external backlight, use internal backlight */
+ ret = drm_panel_of_backlight(&db->panel);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add backlight\n");
+
+ spi_set_drvdata(spi, db);
+
+ drm_panel_add(&db->panel);
+ dev_dbg(dev, "added panel\n");
+
+ return 0;
+}
+
+static int db7430_remove(struct spi_device *spi)
+{
+ struct db7430 *db = spi_get_drvdata(spi);
+
+ drm_panel_remove(&db->panel);
+ return 0;
+}
+
+/*
+ * The DB7430 display controller may be used in several display products,
+ * so list the different variants here and add per-variant data if needed.
+ */
+static const struct of_device_id db7430_match[] = {
+ { .compatible = "samsung,lms397kf04", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, db7430_match);
+
+static struct spi_driver db7430_driver = {
+ .probe = db7430_probe,
+ .remove = db7430_remove,
+ .driver = {
+ .name = "db7430-panel",
+ .of_match_table = db7430_match,
+ },
+};
+module_spi_driver(db7430_driver);
+
+MODULE_AUTHOR("Linus Walleij <[email protected]>");
+MODULE_DESCRIPTION("Samsung DB7430 panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index 21939d4352cf..df6fbd19e6fc 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -36,6 +36,8 @@
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
+#include <drm/drm_dp_aux_bus.h>
+#include <drm/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_panel.h>
@@ -185,6 +187,7 @@ struct panel_simple {
struct regulator *supply;
struct i2c_adapter *ddc;
+ struct drm_dp_aux *aux;
struct gpio_desc *enable_gpio;
struct gpio_desc *hpd_gpio;
@@ -657,7 +660,8 @@ static void panel_simple_parse_panel_timing_node(struct device *dev,
dev_err(dev, "Reject override mode: No display_timing found\n");
}
-static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
+static int panel_simple_probe(struct device *dev, const struct panel_desc *desc,
+ struct drm_dp_aux *aux)
{
struct panel_simple *panel;
struct display_timing dt;
@@ -673,6 +677,7 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
panel->enabled = false;
panel->prepared_time = 0;
panel->desc = desc;
+ panel->aux = aux;
panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd");
if (!panel->no_hpd) {
@@ -707,6 +712,8 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
if (!panel->ddc)
return -EPROBE_DEFER;
+ } else if (aux) {
+ panel->ddc = &aux->ddc;
}
if (desc == &panel_dpi) {
@@ -801,7 +808,7 @@ disable_pm_runtime:
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
free_ddc:
- if (panel->ddc)
+ if (panel->ddc && (!panel->aux || panel->ddc != &panel->aux->ddc))
put_device(&panel->ddc->dev);
return err;
@@ -817,7 +824,7 @@ static int panel_simple_remove(struct device *dev)
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
- if (panel->ddc)
+ if (panel->ddc && (!panel->aux || panel->ddc != &panel->aux->ddc))
put_device(&panel->ddc->dev);
return 0;
@@ -4632,7 +4639,7 @@ static int panel_simple_platform_probe(struct platform_device *pdev)
if (!id)
return -ENODEV;
- return panel_simple_probe(&pdev->dev, id->data);
+ return panel_simple_probe(&pdev->dev, id->data, NULL);
}
static int panel_simple_platform_remove(struct platform_device *pdev)
@@ -4912,7 +4919,7 @@ static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi)
desc = id->data;
- err = panel_simple_probe(&dsi->dev, &desc->desc);
+ err = panel_simple_probe(&dsi->dev, &desc->desc, NULL);
if (err < 0)
return err;
@@ -4957,6 +4964,38 @@ static struct mipi_dsi_driver panel_simple_dsi_driver = {
.shutdown = panel_simple_dsi_shutdown,
};
+static int panel_simple_dp_aux_ep_probe(struct dp_aux_ep_device *aux_ep)
+{
+ const struct of_device_id *id;
+
+ id = of_match_node(platform_of_match, aux_ep->dev.of_node);
+ if (!id)
+ return -ENODEV;
+
+ return panel_simple_probe(&aux_ep->dev, id->data, aux_ep->aux);
+}
+
+static void panel_simple_dp_aux_ep_remove(struct dp_aux_ep_device *aux_ep)
+{
+ panel_simple_remove(&aux_ep->dev);
+}
+
+static void panel_simple_dp_aux_ep_shutdown(struct dp_aux_ep_device *aux_ep)
+{
+ panel_simple_shutdown(&aux_ep->dev);
+}
+
+static struct dp_aux_ep_driver panel_simple_dp_aux_ep_driver = {
+ .driver = {
+ .name = "panel-simple-dp-aux",
+ .of_match_table = platform_of_match, /* Same as platform one! */
+ .pm = &panel_simple_pm_ops,
+ },
+ .probe = panel_simple_dp_aux_ep_probe,
+ .remove = panel_simple_dp_aux_ep_remove,
+ .shutdown = panel_simple_dp_aux_ep_shutdown,
+};
+
static int __init panel_simple_init(void)
{
int err;
@@ -4965,15 +5004,25 @@ static int __init panel_simple_init(void)
if (err < 0)
return err;
+ err = dp_aux_dp_driver_register(&panel_simple_dp_aux_ep_driver);
+ if (err < 0)
+ goto err_did_platform_register;
+
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
err = mipi_dsi_driver_register(&panel_simple_dsi_driver);
- if (err < 0) {
- platform_driver_unregister(&panel_simple_platform_driver);
- return err;
- }
+ if (err < 0)
+ goto err_did_aux_ep_register;
}
return 0;
+
+err_did_aux_ep_register:
+ dp_aux_dp_driver_unregister(&panel_simple_dp_aux_ep_driver);
+
+err_did_platform_register:
+ platform_driver_unregister(&panel_simple_platform_driver);
+
+ return err;
}
module_init(panel_simple_init);
@@ -4982,6 +5031,7 @@ static void __exit panel_simple_exit(void)
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
mipi_dsi_driver_unregister(&panel_simple_dsi_driver);
+ dp_aux_dp_driver_unregister(&panel_simple_dp_aux_ep_driver);
platform_driver_unregister(&panel_simple_platform_driver);
}
module_exit(panel_simple_exit);
diff --git a/drivers/gpu/drm/panfrost/panfrost_device.c b/drivers/gpu/drm/panfrost/panfrost_device.c
index 125ed973feaa..a2a09c51eed7 100644
--- a/drivers/gpu/drm/panfrost/panfrost_device.c
+++ b/drivers/gpu/drm/panfrost/panfrost_device.c
@@ -54,7 +54,8 @@ static int panfrost_clk_init(struct panfrost_device *pfdev)
if (IS_ERR(pfdev->bus_clock)) {
dev_err(pfdev->dev, "get bus_clock failed %ld\n",
PTR_ERR(pfdev->bus_clock));
- return PTR_ERR(pfdev->bus_clock);
+ err = PTR_ERR(pfdev->bus_clock);
+ goto disable_clock;
}
if (pfdev->bus_clock) {
diff --git a/drivers/gpu/drm/vmwgfx/Kconfig b/drivers/gpu/drm/vmwgfx/Kconfig
index 0060ef842b5a..a9052fae0bbc 100644
--- a/drivers/gpu/drm/vmwgfx/Kconfig
+++ b/drivers/gpu/drm/vmwgfx/Kconfig
@@ -22,3 +22,10 @@ config DRM_VMWGFX_FBCON
Choose this option if you are shipping a new vmwgfx
userspace driver that supports using the kernel driver.
+config DRM_VMWGFX_MKSSTATS
+ bool "Enable mksGuestStats instrumentation of vmwgfx by default"
+ depends on DRM_VMWGFX
+ default n
+ help
+ Choose this option to instrument the kernel driver for mksGuestStats.
+
diff --git a/drivers/gpu/drm/vmwgfx/Makefile b/drivers/gpu/drm/vmwgfx/Makefile
index 09f6dcac768b..bc323f7d4032 100644
--- a/drivers/gpu/drm/vmwgfx/Makefile
+++ b/drivers/gpu/drm/vmwgfx/Makefile
@@ -9,7 +9,7 @@ vmwgfx-y := vmwgfx_execbuf.o vmwgfx_gmr.o vmwgfx_kms.o vmwgfx_drv.o \
vmwgfx_cotable.o vmwgfx_so.o vmwgfx_binding.o vmwgfx_msg.o \
vmwgfx_simple_resource.o vmwgfx_va.o vmwgfx_blit.o \
vmwgfx_validation.o vmwgfx_page_dirty.o vmwgfx_streamoutput.o \
- ttm_object.o ttm_memory.o
+ vmwgfx_devcaps.o ttm_object.o ttm_memory.o
vmwgfx-$(CONFIG_DRM_FBDEV_EMULATION) += vmwgfx_fb.o
vmwgfx-$(CONFIG_TRANSPARENT_HUGEPAGE) += vmwgfx_thp.o
diff --git a/drivers/gpu/drm/vmwgfx/device_include/svga_types.h b/drivers/gpu/drm/vmwgfx/device_include/svga_types.h
index beddccee40f6..f5f79b114fac 100644
--- a/drivers/gpu/drm/vmwgfx/device_include/svga_types.h
+++ b/drivers/gpu/drm/vmwgfx/device_include/svga_types.h
@@ -23,9 +23,11 @@
* SOFTWARE.
*
**********************************************************/
-#ifndef _VM_BASIC_TYPES_H_
-#define _VM_BASIC_TYPES_H_
+#ifndef _SVGA_TYPES_H_
+#define _SVGA_TYPES_H_
#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <asm/page.h>
typedef u32 uint32;
typedef s32 int32;
@@ -48,4 +50,90 @@ typedef bool Bool;
#define CONST64U(x) x##ULL
+/*
+ * MKS Guest Stats types
+ */
+
+typedef struct MKSGuestStatCounter {
+ atomic64_t count;
+} MKSGuestStatCounter;
+
+typedef struct MKSGuestStatCounterTime {
+ MKSGuestStatCounter counter;
+ atomic64_t selfCycles;
+ atomic64_t totalCycles;
+} MKSGuestStatCounterTime;
+
+/*
+ * Flags for MKSGuestStatInfoEntry::flags below
+ */
+
+#define MKS_GUEST_STAT_FLAG_NONE 0
+#define MKS_GUEST_STAT_FLAG_TIME (1U << 0)
+
+typedef __attribute__((aligned(32))) struct MKSGuestStatInfoEntry {
+ union {
+ const char *s;
+ uint64 u;
+ } name;
+ union {
+ const char *s;
+ uint64 u;
+ } description;
+ uint64 flags;
+ union {
+ MKSGuestStatCounter *counter;
+ MKSGuestStatCounterTime *counterTime;
+ uint64 u;
+ } stat;
+} MKSGuestStatInfoEntry;
+
+#define INVALID_PPN64 ((PPN64)0x000fffffffffffffULL)
+#define vmw_num_pages(size) (PAGE_ALIGN(size) >> PAGE_SHIFT)
+
+#define MKS_GUEST_STAT_INSTANCE_DESC_LENGTH 1024
+#define MKS_GUEST_STAT_INSTANCE_MAX_STATS 4096
+#define MKS_GUEST_STAT_INSTANCE_MAX_STAT_PPNS \
+ (vmw_num_pages(MKS_GUEST_STAT_INSTANCE_MAX_STATS * \
+ sizeof(MKSGuestStatCounterTime)))
+#define MKS_GUEST_STAT_INSTANCE_MAX_INFO_PPNS \
+ (vmw_num_pages(MKS_GUEST_STAT_INSTANCE_MAX_STATS * \
+ sizeof(MKSGuestStatInfoEntry)))
+#define MKS_GUEST_STAT_AVERAGE_NAME_LENGTH 40
+#define MKS_GUEST_STAT_INSTANCE_MAX_STRS_PPNS \
+ (vmw_num_pages(MKS_GUEST_STAT_INSTANCE_MAX_STATS * \
+ MKS_GUEST_STAT_AVERAGE_NAME_LENGTH))
+
+/*
+ * The MKSGuestStatInstanceDescriptor is used as main interface to
+ * communicate guest stats back to the host code. The guest must
+ * allocate an instance of this structure at the start of a page and
+ * provide the physical address to the host. From there the host code
+ * can walk this structure to find other (pinned) pages containing the
+ * stats data.
+ *
+ * Since the MKSGuestStatInfoEntry structures contain userlevel
+ * pointers, the InstanceDescriptor also contains pointers to the
+ * begining of these sections allowing the host side code to correctly
+ * interpret the pointers.
+ *
+ * Because the host side code never acknowledges anything back to the
+ * guest there is no strict requirement to maintain compatability
+ * across releases. If the interface changes the host might not be
+ * able to log stats, but the guest will continue to run normally.
+ */
+
+typedef struct MKSGuestStatInstanceDescriptor {
+ uint64 reservedMBZ; /* must be zero for now. */
+ uint64 statStartVA; /* VA of the start of the stats section. */
+ uint64 strsStartVA; /* VA of the start of the strings section. */
+ uint64 statLength; /* length of the stats section in bytes. */
+ uint64 infoLength; /* length of the info entry section in bytes. */
+ uint64 strsLength; /* length of the strings section in bytes. */
+ PPN64 statPPNs[MKS_GUEST_STAT_INSTANCE_MAX_STAT_PPNS]; /* stat counters */
+ PPN64 infoPPNs[MKS_GUEST_STAT_INSTANCE_MAX_INFO_PPNS]; /* stat info */
+ PPN64 strsPPNs[MKS_GUEST_STAT_INSTANCE_MAX_STRS_PPNS]; /* strings */
+ char description[MKS_GUEST_STAT_INSTANCE_DESC_LENGTH];
+} MKSGuestStatInstanceDescriptor;
+
#endif
diff --git a/drivers/gpu/drm/vmwgfx/device_include/vm_basic_types.h b/drivers/gpu/drm/vmwgfx/device_include/vm_basic_types.h
deleted file mode 100644
index 3a195e8106b3..000000000000
--- a/drivers/gpu/drm/vmwgfx/device_include/vm_basic_types.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef _VM_BASIC_TYPES_H_
-#define _VM_BASIC_TYPES_H_
-#include <linux/kernel.h>
-
-typedef u32 uint32;
-typedef s32 int32;
-typedef u64 uint64;
-typedef u16 uint16;
-typedef s16 int16;
-typedef u8 uint8;
-typedef s8 int8;
-
-typedef uint64 PA;
-typedef uint32 PPN;
-typedef uint64 PPN64;
-
-typedef bool Bool;
-
-#define MAX_UINT32 U32_MAX
-
-#endif
diff --git a/drivers/gpu/drm/vmwgfx/ttm_memory.c b/drivers/gpu/drm/vmwgfx/ttm_memory.c
index aeb0a22a2c34..edd17c30d5a5 100644
--- a/drivers/gpu/drm/vmwgfx/ttm_memory.c
+++ b/drivers/gpu/drm/vmwgfx/ttm_memory.c
@@ -435,8 +435,10 @@ int ttm_mem_global_init(struct ttm_mem_global *glob, struct device *dev)
si_meminfo(&si);
+ spin_lock(&glob->lock);
/* set it as 0 by default to keep original behavior of OOM */
glob->lower_mem_limit = 0;
+ spin_unlock(&glob->lock);
ret = ttm_mem_init_kernel_zone(glob, &si);
if (unlikely(ret != 0))
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_binding.c b/drivers/gpu/drm/vmwgfx/vmwgfx_binding.c
index 05b324825900..ea6d8c86985f 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_binding.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_binding.c
@@ -715,7 +715,7 @@ static int vmw_binding_scrub_cb(struct vmw_ctx_bindinfo *bi, bool rebind)
* without checking which bindings actually need to be emitted
*
* @cbs: Pointer to the context's struct vmw_ctx_binding_state
- * @bi: Pointer to where the binding info array is stored in @cbs
+ * @biv: Pointer to where the binding info array is stored in @cbs
* @max_num: Maximum number of entries in the @bi array.
*
* Scans the @bi array for bindings and builds a buffer of view id data.
@@ -725,11 +725,9 @@ static int vmw_binding_scrub_cb(struct vmw_ctx_bindinfo *bi, bool rebind)
* contains the command data.
*/
static void vmw_collect_view_ids(struct vmw_ctx_binding_state *cbs,
- const struct vmw_ctx_bindinfo *bi,
+ const struct vmw_ctx_bindinfo_view *biv,
u32 max_num)
{
- const struct vmw_ctx_bindinfo_view *biv =
- container_of(bi, struct vmw_ctx_bindinfo_view, bi);
unsigned long i;
cbs->bind_cmd_count = 0;
@@ -838,7 +836,7 @@ static int vmw_emit_set_sr(struct vmw_ctx_binding_state *cbs,
*/
static int vmw_emit_set_rt(struct vmw_ctx_binding_state *cbs)
{
- const struct vmw_ctx_bindinfo *loc = &cbs->render_targets[0].bi;
+ const struct vmw_ctx_bindinfo_view *loc = &cbs->render_targets[0];
struct {
SVGA3dCmdHeader header;
SVGA3dCmdDXSetRenderTargets body;
@@ -874,7 +872,7 @@ static int vmw_emit_set_rt(struct vmw_ctx_binding_state *cbs)
* without checking which bindings actually need to be emitted
*
* @cbs: Pointer to the context's struct vmw_ctx_binding_state
- * @bi: Pointer to where the binding info array is stored in @cbs
+ * @biso: Pointer to where the binding info array is stored in @cbs
* @max_num: Maximum number of entries in the @bi array.
*
* Scans the @bi array for bindings and builds a buffer of SVGA3dSoTarget data.
@@ -884,11 +882,9 @@ static int vmw_emit_set_rt(struct vmw_ctx_binding_state *cbs)
* contains the command data.
*/
static void vmw_collect_so_targets(struct vmw_ctx_binding_state *cbs,
- const struct vmw_ctx_bindinfo *bi,
+ const struct vmw_ctx_bindinfo_so_target *biso,
u32 max_num)
{
- const struct vmw_ctx_bindinfo_so_target *biso =
- container_of(bi, struct vmw_ctx_bindinfo_so_target, bi);
unsigned long i;
SVGA3dSoTarget *so_buffer = (SVGA3dSoTarget *) cbs->bind_cmd_buffer;
@@ -919,7 +915,7 @@ static void vmw_collect_so_targets(struct vmw_ctx_binding_state *cbs,
*/
static int vmw_emit_set_so_target(struct vmw_ctx_binding_state *cbs)
{
- const struct vmw_ctx_bindinfo *loc = &cbs->so_targets[0].bi;
+ const struct vmw_ctx_bindinfo_so_target *loc = &cbs->so_targets[0];
struct {
SVGA3dCmdHeader header;
SVGA3dCmdDXSetSOTargets body;
@@ -1066,7 +1062,7 @@ static int vmw_emit_set_vb(struct vmw_ctx_binding_state *cbs)
static int vmw_emit_set_uav(struct vmw_ctx_binding_state *cbs)
{
- const struct vmw_ctx_bindinfo *loc = &cbs->ua_views[0].views[0].bi;
+ const struct vmw_ctx_bindinfo_view *loc = &cbs->ua_views[0].views[0];
struct {
SVGA3dCmdHeader header;
SVGA3dCmdDXSetUAViews body;
@@ -1096,7 +1092,7 @@ static int vmw_emit_set_uav(struct vmw_ctx_binding_state *cbs)
static int vmw_emit_set_cs_uav(struct vmw_ctx_binding_state *cbs)
{
- const struct vmw_ctx_bindinfo *loc = &cbs->ua_views[1].views[0].bi;
+ const struct vmw_ctx_bindinfo_view *loc = &cbs->ua_views[1].views[0];
struct {
SVGA3dCmdHeader header;
SVGA3dCmdDXSetCSUAViews body;
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c
index 956b85e35cef..30a837b5baa6 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c
@@ -30,6 +30,7 @@
#include <drm/ttm/ttm_placement.h>
#include "vmwgfx_drv.h"
+#include "vmwgfx_devcaps.h"
bool vmw_supports_3d(struct vmw_private *dev_priv)
{
@@ -45,10 +46,7 @@ bool vmw_supports_3d(struct vmw_private *dev_priv)
if (!dev_priv->has_mob)
return false;
- spin_lock(&dev_priv->cap_lock);
- vmw_write(dev_priv, SVGA_REG_DEV_CAP, SVGA3D_DEVCAP_3D);
- result = vmw_read(dev_priv, SVGA_REG_DEV_CAP);
- spin_unlock(&dev_priv->cap_lock);
+ result = vmw_devcap_get(dev_priv, SVGA3D_DEVCAP_3D);
return (result != 0);
}
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf.c
index 6bb4961e64a5..9656d4a2abff 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf.c
@@ -516,7 +516,7 @@ static void vmw_cmdbuf_work_func(struct work_struct *work)
struct vmw_cmdbuf_man *man =
container_of(work, struct vmw_cmdbuf_man, work);
struct vmw_cmdbuf_header *entry, *next;
- uint32_t dummy;
+ uint32_t dummy = 0;
bool send_fence = false;
struct list_head restart_head[SVGA_CB_CONTEXT_MAX];
int i;
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf_res.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf_res.c
index b262d61d839d..9487faff5229 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf_res.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cmdbuf_res.c
@@ -159,6 +159,7 @@ void vmw_cmdbuf_res_commit(struct list_head *list)
void vmw_cmdbuf_res_revert(struct list_head *list)
{
struct vmw_cmdbuf_res *entry, *next;
+ int ret;
list_for_each_entry_safe(entry, next, list, head) {
switch (entry->state) {
@@ -166,7 +167,8 @@ void vmw_cmdbuf_res_revert(struct list_head *list)
vmw_cmdbuf_res_free(entry->man, entry);
break;
case VMW_CMDBUF_RES_DEL:
- drm_ht_insert_item(&entry->man->resources, &entry->hash);
+ ret = drm_ht_insert_item(&entry->man->resources, &entry->hash);
+ BUG_ON(ret);
list_del(&entry->head);
list_add_tail(&entry->head, &entry->man->list);
entry->state = VMW_CMDBUF_RES_COMMITTED;
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.c b/drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.c
new file mode 100644
index 000000000000..04fc67d53563
--- /dev/null
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.c
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/**************************************************************************
+ *
+ * Copyright 2021 VMware, Inc., Palo Alto, CA., USA
+ *
+ * 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 COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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 "vmwgfx_devcaps.h"
+
+#include "vmwgfx_drv.h"
+
+
+struct svga_3d_compat_cap {
+ SVGA3dCapsRecordHeader header;
+ SVGA3dCapPair pairs[SVGA3D_DEVCAP_MAX];
+};
+
+
+static u32 vmw_mask_legacy_multisample(unsigned int cap, u32 fmt_value)
+{
+ /*
+ * A version of user-space exists which use MULTISAMPLE_MASKABLESAMPLES
+ * to check the sample count supported by virtual device. Since there
+ * never was support for multisample count for backing MOB return 0.
+ *
+ * MULTISAMPLE_MASKABLESAMPLES devcap is marked as deprecated by virtual
+ * device.
+ */
+ if (cap == SVGA3D_DEVCAP_DEAD5)
+ return 0;
+
+ return fmt_value;
+}
+
+static int vmw_fill_compat_cap(struct vmw_private *dev_priv, void *bounce,
+ size_t size)
+{
+ struct svga_3d_compat_cap *compat_cap =
+ (struct svga_3d_compat_cap *) bounce;
+ unsigned int i;
+ size_t pair_offset = offsetof(struct svga_3d_compat_cap, pairs);
+ unsigned int max_size;
+
+ if (size < pair_offset)
+ return -EINVAL;
+
+ max_size = (size - pair_offset) / sizeof(SVGA3dCapPair);
+
+ if (max_size > SVGA3D_DEVCAP_MAX)
+ max_size = SVGA3D_DEVCAP_MAX;
+
+ compat_cap->header.length =
+ (pair_offset + max_size * sizeof(SVGA3dCapPair)) / sizeof(u32);
+ compat_cap->header.type = SVGA3DCAPS_RECORD_DEVCAPS;
+
+ for (i = 0; i < max_size; ++i) {
+ compat_cap->pairs[i][0] = i;
+ compat_cap->pairs[i][1] = vmw_mask_legacy_multisample
+ (i, dev_priv->devcaps[i]);
+ }
+
+ return 0;
+}
+
+int vmw_devcaps_create(struct vmw_private *vmw)
+{
+ bool gb_objects = !!(vmw->capabilities & SVGA_CAP_GBOBJECTS);
+ uint32_t i;
+
+ if (gb_objects) {
+ vmw->devcaps = vzalloc(sizeof(uint32_t) * SVGA3D_DEVCAP_MAX);
+ if (!vmw->devcaps)
+ return -ENOMEM;
+ for (i = 0; i < SVGA3D_DEVCAP_MAX; ++i) {
+ vmw_write(vmw, SVGA_REG_DEV_CAP, i);
+ vmw->devcaps[i] = vmw_read(vmw, SVGA_REG_DEV_CAP);
+ }
+ }
+ return 0;
+}
+
+void vmw_devcaps_destroy(struct vmw_private *vmw)
+{
+ vfree(vmw->devcaps);
+ vmw->devcaps = NULL;
+}
+
+
+uint32 vmw_devcaps_size(const struct vmw_private *vmw,
+ bool gb_aware)
+{
+ bool gb_objects = !!(vmw->capabilities & SVGA_CAP_GBOBJECTS);
+ if (gb_objects && gb_aware)
+ return SVGA3D_DEVCAP_MAX * sizeof(uint32_t);
+ else if (gb_objects)
+ return sizeof(struct svga_3d_compat_cap) +
+ sizeof(uint32_t);
+ else if (vmw->fifo_mem != NULL)
+ return (SVGA_FIFO_3D_CAPS_LAST - SVGA_FIFO_3D_CAPS + 1) *
+ sizeof(uint32_t);
+ else
+ return 0;
+}
+
+int vmw_devcaps_copy(struct vmw_private *vmw, bool gb_aware,
+ void *dst, uint32_t dst_size)
+{
+ int ret;
+ bool gb_objects = !!(vmw->capabilities & SVGA_CAP_GBOBJECTS);
+ if (gb_objects && gb_aware) {
+ memcpy(dst, vmw->devcaps, dst_size);
+ } else if (gb_objects) {
+ ret = vmw_fill_compat_cap(vmw, dst, dst_size);
+ if (unlikely(ret != 0))
+ return ret;
+ } else if (vmw->fifo_mem) {
+ u32 *fifo_mem = vmw->fifo_mem;
+ memcpy(dst, &fifo_mem[SVGA_FIFO_3D_CAPS], dst_size);
+ } else
+ return -EINVAL;
+ return 0;
+}
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.h b/drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.h
new file mode 100644
index 000000000000..b7c43e5f07c3
--- /dev/null
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_devcaps.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/**************************************************************************
+ *
+ * Copyright 2021 VMware, Inc., Palo Alto, CA., USA
+ *
+ * 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 COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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.
+ *
+ **************************************************************************/
+
+#ifndef _VMWGFX_DEVCAPS_H_
+#define _VMWGFX_DEVCAPS_H_
+
+#include "vmwgfx_drv.h"
+
+#include "device_include/svga3d_caps.h"
+
+int vmw_devcaps_create(struct vmw_private *vmw);
+void vmw_devcaps_destroy(struct vmw_private *vmw);
+uint32_t vmw_devcaps_size(const struct vmw_private *vmw, bool gb_aware);
+int vmw_devcaps_copy(struct vmw_private *vmw, bool gb_aware,
+ void *dst, uint32_t dst_size);
+
+static inline uint32_t vmw_devcap_get(struct vmw_private *vmw,
+ uint32_t devcap)
+{
+ bool gb_objects = !!(vmw->capabilities & SVGA_CAP_GBOBJECTS);
+ if (gb_objects)
+ return vmw->devcaps[devcap];
+ return 0;
+}
+
+#endif
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c
index 6f5ea00973e0..48a62fb0da2f 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c
@@ -36,12 +36,15 @@
#include <drm/drm_ioctl.h>
#include <drm/drm_sysfs.h>
#include <drm/ttm/ttm_bo_driver.h>
+#include <drm/ttm/ttm_range_manager.h>
#include <drm/ttm/ttm_placement.h>
#include <generated/utsrelease.h>
#include "ttm_object.h"
#include "vmwgfx_binding.h"
+#include "vmwgfx_devcaps.h"
#include "vmwgfx_drv.h"
+#include "vmwgfx_mksstat.h"
#define VMWGFX_DRIVER_DESC "Linux drm driver for VMware graphics devices"
@@ -147,6 +150,14 @@
#define DRM_IOCTL_VMW_MSG \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VMW_MSG, \
struct drm_vmw_msg_arg)
+#define DRM_IOCTL_VMW_MKSSTAT_RESET \
+ DRM_IO(DRM_COMMAND_BASE + DRM_VMW_MKSSTAT_RESET)
+#define DRM_IOCTL_VMW_MKSSTAT_ADD \
+ DRM_IOWR(DRM_COMMAND_BASE + DRM_VMW_MKSSTAT_ADD, \
+ struct drm_vmw_mksstat_add_arg)
+#define DRM_IOCTL_VMW_MKSSTAT_REMOVE \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_VMW_MKSSTAT_REMOVE, \
+ struct drm_vmw_mksstat_remove_arg)
/*
* The core DRM version of this macro doesn't account for
@@ -243,6 +254,15 @@ static const struct drm_ioctl_desc vmw_ioctls[] = {
VMW_IOCTL_DEF(VMW_MSG,
vmw_msg_ioctl,
DRM_RENDER_ALLOW),
+ VMW_IOCTL_DEF(VMW_MKSSTAT_RESET,
+ vmw_mksstat_reset_ioctl,
+ DRM_RENDER_ALLOW),
+ VMW_IOCTL_DEF(VMW_MKSSTAT_ADD,
+ vmw_mksstat_add_ioctl,
+ DRM_RENDER_ALLOW),
+ VMW_IOCTL_DEF(VMW_MKSSTAT_REMOVE,
+ vmw_mksstat_remove_ioctl,
+ DRM_RENDER_ALLOW),
};
static const struct pci_device_id vmw_pci_id_list[] = {
@@ -253,7 +273,6 @@ static const struct pci_device_id vmw_pci_id_list[] = {
MODULE_DEVICE_TABLE(pci, vmw_pci_id_list);
static int enable_fbdev = IS_ENABLED(CONFIG_DRM_VMWGFX_FBCON);
-static int vmw_force_iommu;
static int vmw_restrict_iommu;
static int vmw_force_coherent;
static int vmw_restrict_dma_mask;
@@ -265,8 +284,6 @@ static int vmwgfx_pm_notifier(struct notifier_block *nb, unsigned long val,
MODULE_PARM_DESC(enable_fbdev, "Enable vmwgfx fbdev");
module_param_named(enable_fbdev, enable_fbdev, int, 0600);
-MODULE_PARM_DESC(force_dma_api, "Force using the DMA API for TTM pages");
-module_param_named(force_dma_api, vmw_force_iommu, int, 0600);
MODULE_PARM_DESC(restrict_iommu, "Try to limit IOMMU usage for TTM pages");
module_param_named(restrict_iommu, vmw_restrict_iommu, int, 0600);
MODULE_PARM_DESC(force_coherent, "Force coherent TTM pages");
@@ -626,7 +643,6 @@ static void vmw_get_initial_size(struct vmw_private *dev_priv)
static int vmw_dma_select_mode(struct vmw_private *dev_priv)
{
static const char *names[vmw_dma_map_max] = {
- [vmw_dma_phys] = "Using physical TTM page addresses.",
[vmw_dma_alloc_coherent] = "Using coherent TTM pages.",
[vmw_dma_map_populate] = "Caching DMA mappings.",
[vmw_dma_map_bind] = "Giving up DMA mappings early."};
@@ -660,8 +676,7 @@ static int vmw_dma_masks(struct vmw_private *dev_priv)
int ret = 0;
ret = dma_set_mask_and_coherent(dev->dev, DMA_BIT_MASK(64));
- if (dev_priv->map_mode != vmw_dma_phys &&
- (sizeof(unsigned long) == 4 || vmw_restrict_dma_mask)) {
+ if (sizeof(unsigned long) == 4 || vmw_restrict_dma_mask) {
DRM_INFO("Restricting DMA addresses to 44 bits.\n");
return dma_set_mask_and_coherent(dev->dev, DMA_BIT_MASK(44));
}
@@ -692,7 +707,7 @@ static void vmw_vram_manager_fini(struct vmw_private *dev_priv)
}
static int vmw_setup_pci_resources(struct vmw_private *dev,
- unsigned long pci_id)
+ u32 pci_id)
{
resource_size_t rmmio_start;
resource_size_t rmmio_size;
@@ -769,7 +784,7 @@ static int vmw_detect_version(struct vmw_private *dev)
svga_id = vmw_read(dev, SVGA_REG_ID);
if (svga_id != SVGA_ID_2 && svga_id != SVGA_ID_3) {
DRM_ERROR("Unsupported SVGA ID 0x%x on chipset 0x%x\n",
- svga_id, dev->vmw_chipset);
+ svga_id, dev->pci_id);
return -ENOSYS;
}
BUG_ON(vmw_is_svga_v3(dev) && (svga_id != SVGA_ID_3));
@@ -784,7 +799,6 @@ static int vmw_driver_load(struct vmw_private *dev_priv, u32 pci_id)
bool refuse_dma = false;
struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev);
- dev_priv->vmw_chipset = pci_id;
dev_priv->drm.dev_private = dev_priv;
mutex_init(&dev_priv->cmdbuf_mutex);
@@ -792,7 +806,6 @@ static int vmw_driver_load(struct vmw_private *dev_priv, u32 pci_id)
spin_lock_init(&dev_priv->resource_lock);
spin_lock_init(&dev_priv->hw_lock);
spin_lock_init(&dev_priv->waiter_lock);
- spin_lock_init(&dev_priv->cap_lock);
spin_lock_init(&dev_priv->cursor_lock);
ret = vmw_setup_pci_resources(dev_priv, pci_id);
@@ -982,6 +995,12 @@ static int vmw_driver_load(struct vmw_private *dev_priv, u32 pci_id)
goto out_no_vram;
}
+ ret = vmw_devcaps_create(dev_priv);
+ if (unlikely(ret != 0)) {
+ DRM_ERROR("Failed initializing device caps.\n");
+ goto out_no_vram;
+ }
+
/*
* "Guest Memory Regions" is an aperture like feature with
* one slot per bo. There is an upper limit of the number of
@@ -1008,11 +1027,8 @@ static int vmw_driver_load(struct vmw_private *dev_priv, u32 pci_id)
}
if (dev_priv->has_mob && (dev_priv->capabilities & SVGA_CAP_DX)) {
- spin_lock(&dev_priv->cap_lock);
- vmw_write(dev_priv, SVGA_REG_DEV_CAP, SVGA3D_DEVCAP_DXCONTEXT);
- if (vmw_read(dev_priv, SVGA_REG_DEV_CAP))
+ if (vmw_devcap_get(dev_priv, SVGA3D_DEVCAP_DXCONTEXT))
dev_priv->sm_type = VMW_SM_4;
- spin_unlock(&dev_priv->cap_lock);
}
vmw_validation_mem_init_ttm(dev_priv, VMWGFX_VALIDATION_MEM_GRAN);
@@ -1020,15 +1036,11 @@ static int vmw_driver_load(struct vmw_private *dev_priv, u32 pci_id)
/* SVGA_CAP2_DX2 (DefineGBSurface_v3) is needed for SM4_1 support */
if (has_sm4_context(dev_priv) &&
(dev_priv->capabilities2 & SVGA_CAP2_DX2)) {
- vmw_write(dev_priv, SVGA_REG_DEV_CAP, SVGA3D_DEVCAP_SM41);
-
- if (vmw_read(dev_priv, SVGA_REG_DEV_CAP))
+ if (vmw_devcap_get(dev_priv, SVGA3D_DEVCAP_SM41))
dev_priv->sm_type = VMW_SM_4_1;
-
if (has_sm4_1_context(dev_priv) &&
- (dev_priv->capabilities2 & SVGA_CAP2_DX3)) {
- vmw_write(dev_priv, SVGA_REG_DEV_CAP, SVGA3D_DEVCAP_SM5);
- if (vmw_read(dev_priv, SVGA_REG_DEV_CAP))
+ (dev_priv->capabilities2 & SVGA_CAP2_DX3)) {
+ if (vmw_devcap_get(dev_priv, SVGA3D_DEVCAP_SM5))
dev_priv->sm_type = VMW_SM_5;
}
}
@@ -1073,6 +1085,7 @@ out_no_kms:
vmw_gmrid_man_fini(dev_priv, VMW_PL_MOB);
if (dev_priv->has_gmr)
vmw_gmrid_man_fini(dev_priv, VMW_PL_GMR);
+ vmw_devcaps_destroy(dev_priv);
vmw_vram_manager_fini(dev_priv);
out_no_vram:
ttm_device_fini(&dev_priv->bdev);
@@ -1121,6 +1134,7 @@ static void vmw_driver_unload(struct drm_device *dev)
vmw_release_device_early(dev_priv);
if (dev_priv->has_mob)
vmw_gmrid_man_fini(dev_priv, VMW_PL_MOB);
+ vmw_devcaps_destroy(dev_priv);
vmw_vram_manager_fini(dev_priv);
ttm_device_fini(&dev_priv->bdev);
drm_vma_offset_manager_destroy(&dev_priv->vma_manager);
@@ -1136,6 +1150,8 @@ static void vmw_driver_unload(struct drm_device *dev)
for (i = vmw_res_context; i < vmw_res_max; ++i)
idr_destroy(&dev_priv->res_idr[i]);
+ vmw_mksstat_remove_all(dev_priv);
+
pci_release_regions(pdev);
}
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
index d1cef3b69e9d..9422967659d7 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/**************************************************************************
*
- * Copyright 2009-2015 VMware, Inc., Palo Alto, CA., USA
+ * Copyright 2009-2021 VMware, Inc., Palo Alto, CA., USA
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
@@ -91,6 +91,9 @@
#define VMW_RES_FENCE ttm_driver_type3
#define VMW_RES_SHADER ttm_driver_type4
+#define MKSSTAT_CAPACITY_LOG2 5U
+#define MKSSTAT_CAPACITY (1U << MKSSTAT_CAPACITY_LOG2)
+
struct vmw_fpriv {
struct ttm_object_file *tfile;
bool gb_aware; /* user-space is guest-backed aware */
@@ -311,7 +314,6 @@ struct vmw_res_cache_entry {
* enum vmw_dma_map_mode - indicate how to perform TTM page dma mappings.
*/
enum vmw_dma_map_mode {
- vmw_dma_phys, /* Use physical page addresses */
vmw_dma_alloc_coherent, /* Use TTM coherent pages */
vmw_dma_map_populate, /* Unmap from DMA just after unpopulate */
vmw_dma_map_bind, /* Unmap from DMA just before unbind */
@@ -356,7 +358,6 @@ struct vmw_piter {
unsigned long num_pages;
bool (*next)(struct vmw_piter *);
dma_addr_t (*dma_address)(struct vmw_piter *);
- struct page *(*page)(struct vmw_piter *);
};
/*
@@ -486,8 +487,7 @@ struct vmw_private {
struct ttm_device bdev;
struct drm_vma_offset_manager vma_manager;
- unsigned long pci_id;
- u32 vmw_chipset;
+ u32 pci_id;
resource_size_t io_start;
resource_size_t vram_start;
resource_size_t vram_size;
@@ -513,7 +513,6 @@ struct vmw_private {
bool has_gmr;
bool has_mob;
spinlock_t hw_lock;
- spinlock_t cap_lock;
bool assume_16bpp;
enum vmw_sm_type sm_type;
@@ -629,6 +628,20 @@ struct vmw_private {
/* Validation memory reservation */
struct vmw_validation_mem vvm;
+
+ uint32 *devcaps;
+
+ /*
+ * mksGuestStat instance-descriptor and pid arrays
+ */
+ struct page *mksstat_user_pages[MKSSTAT_CAPACITY];
+ atomic_t mksstat_user_pids[MKSSTAT_CAPACITY];
+
+#if IS_ENABLED(CONFIG_DRM_VMWGFX_MKSSTATS)
+ struct page *mksstat_kern_pages[MKSSTAT_CAPACITY];
+ u8 mksstat_kern_top_timer[MKSSTAT_CAPACITY];
+ atomic_t mksstat_kern_pids[MKSSTAT_CAPACITY];
+#endif
};
static inline struct vmw_surface *vmw_res_to_srf(struct vmw_resource *res)
@@ -1073,7 +1086,7 @@ static inline dma_addr_t vmw_piter_dma_addr(struct vmw_piter *viter)
*/
static inline struct page *vmw_piter_page(struct vmw_piter *viter)
{
- return viter->page(viter);
+ return viter->pages[viter->i];
}
/**
@@ -1502,6 +1515,17 @@ __printf(1, 2) int vmw_host_printf(const char *fmt, ...);
int vmw_msg_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv);
+/* Host mksGuestStats -vmwgfx_msg.c: */
+int vmw_mksstat_get_kern_slot(pid_t pid, struct vmw_private *dev_priv);
+
+int vmw_mksstat_reset_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+int vmw_mksstat_add_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+int vmw_mksstat_remove_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+int vmw_mksstat_remove_all(struct vmw_private *dev_priv);
+
/* VMW logging */
/**
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c
index a2b8464b3f56..cc8b1d943c55 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c
@@ -32,6 +32,7 @@
#include <drm/ttm/ttm_placement.h>
#include "vmwgfx_so.h"
#include "vmwgfx_binding.h"
+#include "vmwgfx_mksstat.h"
#define VMW_RES_HT_ORDER 12
@@ -2546,6 +2547,8 @@ static int vmw_cmd_dx_so_define(struct vmw_private *dev_priv,
so_type = vmw_so_cmd_to_type(header->id);
res = vmw_context_cotable(ctx_node->ctx, vmw_so_cotables[so_type]);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
cmd = container_of(header, typeof(*cmd), header);
ret = vmw_cotable_notify(res, cmd->defined_id);
@@ -4406,6 +4409,9 @@ int vmw_execbuf_ioctl(struct drm_device *dev, void *data,
int ret;
struct dma_fence *in_fence = NULL;
+ MKS_STAT_TIME_DECL(MKSSTAT_KERN_EXECBUF);
+ MKS_STAT_TIME_PUSH(MKSSTAT_KERN_EXECBUF);
+
/*
* Extend the ioctl argument while maintaining backwards compatibility:
* We take different code paths depending on the value of arg->version.
@@ -4415,7 +4421,8 @@ int vmw_execbuf_ioctl(struct drm_device *dev, void *data,
if (unlikely(arg->version > DRM_VMW_EXECBUF_VERSION ||
arg->version == 0)) {
VMW_DEBUG_USER("Incorrect execbuf version.\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto mksstats_out;
}
switch (arg->version) {
@@ -4435,7 +4442,8 @@ int vmw_execbuf_ioctl(struct drm_device *dev, void *data,
if (!in_fence) {
VMW_DEBUG_USER("Cannot get imported fence\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto mksstats_out;
}
ret = vmw_wait_dma_fence(dev_priv->fman, in_fence);
@@ -4458,5 +4466,8 @@ int vmw_execbuf_ioctl(struct drm_device *dev, void *data,
out:
if (in_fence)
dma_fence_put(in_fence);
+
+mksstats_out:
+ MKS_STAT_TIME_POP(MKSSTAT_KERN_EXECBUF);
return ret;
}
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c
index 4fdacf9924e6..c34f61ac4ce4 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c
@@ -26,14 +26,9 @@
**************************************************************************/
#include "vmwgfx_drv.h"
+#include "vmwgfx_devcaps.h"
#include <drm/vmwgfx_drm.h>
#include "vmwgfx_kms.h"
-#include "device_include/svga3d_caps.h"
-
-struct svga_3d_compat_cap {
- SVGA3dCapsRecordHeader header;
- SVGA3dCapPair pairs[SVGA3D_DEVCAP_MAX];
-};
int vmw_getparam_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
@@ -88,16 +83,7 @@ int vmw_getparam_ioctl(struct drm_device *dev, void *data,
param->value = dev_priv->memory_size;
break;
case DRM_VMW_PARAM_3D_CAPS_SIZE:
- if ((dev_priv->capabilities & SVGA_CAP_GBOBJECTS) &&
- vmw_fp->gb_aware)
- param->value = SVGA3D_DEVCAP_MAX * sizeof(uint32_t);
- else if (dev_priv->capabilities & SVGA_CAP_GBOBJECTS)
- param->value = sizeof(struct svga_3d_compat_cap) +
- sizeof(uint32_t);
- else
- param->value = (SVGA_FIFO_3D_CAPS_LAST -
- SVGA_FIFO_3D_CAPS + 1) *
- sizeof(uint32_t);
+ param->value = vmw_devcaps_size(dev_priv, vmw_fp->gb_aware);
break;
case DRM_VMW_PARAM_MAX_MOB_MEMORY:
vmw_fp->gb_aware = true;
@@ -126,55 +112,6 @@ int vmw_getparam_ioctl(struct drm_device *dev, void *data,
return 0;
}
-static u32 vmw_mask_legacy_multisample(unsigned int cap, u32 fmt_value)
-{
- /*
- * A version of user-space exists which use MULTISAMPLE_MASKABLESAMPLES
- * to check the sample count supported by virtual device. Since there
- * never was support for multisample count for backing MOB return 0.
- *
- * MULTISAMPLE_MASKABLESAMPLES devcap is marked as deprecated by virtual
- * device.
- */
- if (cap == SVGA3D_DEVCAP_DEAD5)
- return 0;
-
- return fmt_value;
-}
-
-static int vmw_fill_compat_cap(struct vmw_private *dev_priv, void *bounce,
- size_t size)
-{
- struct svga_3d_compat_cap *compat_cap =
- (struct svga_3d_compat_cap *) bounce;
- unsigned int i;
- size_t pair_offset = offsetof(struct svga_3d_compat_cap, pairs);
- unsigned int max_size;
-
- if (size < pair_offset)
- return -EINVAL;
-
- max_size = (size - pair_offset) / sizeof(SVGA3dCapPair);
-
- if (max_size > SVGA3D_DEVCAP_MAX)
- max_size = SVGA3D_DEVCAP_MAX;
-
- compat_cap->header.length =
- (pair_offset + max_size * sizeof(SVGA3dCapPair)) / sizeof(u32);
- compat_cap->header.type = SVGA3DCAPS_RECORD_DEVCAPS;
-
- spin_lock(&dev_priv->cap_lock);
- for (i = 0; i < max_size; ++i) {
- vmw_write(dev_priv, SVGA_REG_DEV_CAP, i);
- compat_cap->pairs[i][0] = i;
- compat_cap->pairs[i][1] = vmw_mask_legacy_multisample
- (i, vmw_read(dev_priv, SVGA_REG_DEV_CAP));
- }
- spin_unlock(&dev_priv->cap_lock);
-
- return 0;
-}
-
int vmw_get_cap_3d_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
@@ -183,11 +120,9 @@ int vmw_get_cap_3d_ioctl(struct drm_device *dev, void *data,
(struct drm_vmw_get_3d_cap_arg *) data;
struct vmw_private *dev_priv = vmw_priv(dev);
uint32_t size;
- u32 *fifo_mem;
void __user *buffer = (void __user *)((unsigned long)(arg->buffer));
- void *bounce;
+ void *bounce = NULL;
int ret;
- bool gb_objects = !!(dev_priv->capabilities & SVGA_CAP_GBOBJECTS);
struct vmw_fpriv *vmw_fp = vmw_fpriv(file_priv);
if (unlikely(arg->pad64 != 0 || arg->max_size == 0)) {
@@ -195,13 +130,11 @@ int vmw_get_cap_3d_ioctl(struct drm_device *dev, void *data,
return -EINVAL;
}
- if (gb_objects && vmw_fp->gb_aware)
- size = SVGA3D_DEVCAP_MAX * sizeof(uint32_t);
- else if (gb_objects)
- size = sizeof(struct svga_3d_compat_cap) + sizeof(uint32_t);
- else
- size = (SVGA_FIFO_3D_CAPS_LAST - SVGA_FIFO_3D_CAPS + 1) *
- sizeof(uint32_t);
+ size = vmw_devcaps_size(dev_priv, vmw_fp->gb_aware);
+ if (unlikely(size == 0)) {
+ DRM_ERROR("Failed to figure out the devcaps size (no 3D).\n");
+ return -ENOMEM;
+ }
if (arg->max_size < size)
size = arg->max_size;
@@ -212,29 +145,9 @@ int vmw_get_cap_3d_ioctl(struct drm_device *dev, void *data,
return -ENOMEM;
}
- if (gb_objects && vmw_fp->gb_aware) {
- int i, num;
- uint32_t *bounce32 = (uint32_t *) bounce;
-
- num = size / sizeof(uint32_t);
- if (num > SVGA3D_DEVCAP_MAX)
- num = SVGA3D_DEVCAP_MAX;
-
- spin_lock(&dev_priv->cap_lock);
- for (i = 0; i < num; ++i) {
- vmw_write(dev_priv, SVGA_REG_DEV_CAP, i);
- *bounce32++ = vmw_mask_legacy_multisample
- (i, vmw_read(dev_priv, SVGA_REG_DEV_CAP));
- }
- spin_unlock(&dev_priv->cap_lock);
- } else if (gb_objects) {
- ret = vmw_fill_compat_cap(dev_priv, bounce, size);
- if (unlikely(ret != 0))
- goto out_err;
- } else {
- fifo_mem = dev_priv->fifo_mem;
- memcpy(bounce, &fifo_mem[SVGA_FIFO_3D_CAPS], size);
- }
+ ret = vmw_devcaps_copy(dev_priv, vmw_fp->gb_aware, bounce, size);
+ if (unlikely (ret != 0))
+ goto out_err;
ret = copy_to_user(buffer, bounce, size);
if (ret)
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_mksstat.h b/drivers/gpu/drm/vmwgfx/vmwgfx_mksstat.h
new file mode 100644
index 000000000000..0509f55f07b4
--- /dev/null
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_mksstat.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/**************************************************************************
+ *
+ * Copyright 2021 VMware, Inc., Palo Alto, CA., USA
+ *
+ * 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 COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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.
+ *
+ **************************************************************************/
+
+#ifndef _VMWGFX_MKSSTAT_H_
+#define _VMWGFX_MKSSTAT_H_
+
+#include <asm/page.h>
+
+/* Reservation marker for mksstat pid's */
+#define MKSSTAT_PID_RESERVED -1
+
+#if IS_ENABLED(CONFIG_DRM_VMWGFX_MKSSTATS)
+/*
+ * Kernel-internal mksGuestStat counters. The order of this enum dictates the
+ * order of instantiation of these counters in the mksGuestStat pages.
+ */
+
+typedef enum {
+ MKSSTAT_KERN_EXECBUF, /* vmw_execbuf_ioctl */
+
+ MKSSTAT_KERN_COUNT /* Reserved entry; always last */
+} mksstat_kern_stats_t;
+
+/**
+ * vmw_mksstat_get_kern_pstat: Computes the address of the MKSGuestStatCounterTime
+ * array from the address of the base page.
+ *
+ * @page_addr: Pointer to the base page.
+ * Return: Pointer to the MKSGuestStatCounterTime array.
+ */
+
+static inline void *vmw_mksstat_get_kern_pstat(void *page_addr)
+{
+ return page_addr + PAGE_SIZE * 1;
+}
+
+/**
+ * vmw_mksstat_get_kern_pinfo: Computes the address of the MKSGuestStatInfoEntry
+ * array from the address of the base page.
+ *
+ * @page_addr: Pointer to the base page.
+ * Return: Pointer to the MKSGuestStatInfoEntry array.
+ */
+
+static inline void *vmw_mksstat_get_kern_pinfo(void *page_addr)
+{
+ return page_addr + PAGE_SIZE * 2;
+}
+
+/**
+ * vmw_mksstat_get_kern_pstrs: Computes the address of the mksGuestStat strings
+ * sequence from the address of the base page.
+ *
+ * @page_addr: Pointer to the base page.
+ * Return: Pointer to the mksGuestStat strings sequence.
+ */
+
+static inline void *vmw_mksstat_get_kern_pstrs(void *page_addr)
+{
+ return page_addr + PAGE_SIZE * 3;
+}
+
+/*
+ * MKS_STAT_TIME_DECL/PUSH/POP macros to be used in timer-counted routines.
+ */
+
+struct mksstat_timer_t {
+/* mutable */ mksstat_kern_stats_t old_top;
+ const u64 t0;
+ const int slot;
+};
+
+#define MKS_STAT_TIME_DECL(kern_cntr) \
+ struct mksstat_timer_t _##kern_cntr = { \
+ .t0 = rdtsc(), \
+ .slot = vmw_mksstat_get_kern_slot(current->pid, dev_priv) \
+ }
+
+#define MKS_STAT_TIME_PUSH(kern_cntr) \
+ do { \
+ if (_##kern_cntr.slot >= 0) { \
+ _##kern_cntr.old_top = dev_priv->mksstat_kern_top_timer[_##kern_cntr.slot]; \
+ dev_priv->mksstat_kern_top_timer[_##kern_cntr.slot] = kern_cntr; \
+ } \
+ } while (0)
+
+#define MKS_STAT_TIME_POP(kern_cntr) \
+ do { \
+ if (_##kern_cntr.slot >= 0) { \
+ const pid_t pid = atomic_cmpxchg(&dev_priv->mksstat_kern_pids[_##kern_cntr.slot], current->pid, MKSSTAT_PID_RESERVED); \
+ dev_priv->mksstat_kern_top_timer[_##kern_cntr.slot] = _##kern_cntr.old_top; \
+ \
+ if (pid == current->pid) { \
+ const u64 dt = rdtsc() - _##kern_cntr.t0; \
+ MKSGuestStatCounterTime *pstat; \
+ \
+ BUG_ON(!dev_priv->mksstat_kern_pages[_##kern_cntr.slot]); \
+ \
+ pstat = vmw_mksstat_get_kern_pstat(page_address(dev_priv->mksstat_kern_pages[_##kern_cntr.slot])); \
+ \
+ atomic64_inc(&pstat[kern_cntr].counter.count); \
+ atomic64_add(dt, &pstat[kern_cntr].selfCycles); \
+ atomic64_add(dt, &pstat[kern_cntr].totalCycles); \
+ \
+ if (_##kern_cntr.old_top != MKSSTAT_KERN_COUNT) \
+ atomic64_sub(dt, &pstat[_##kern_cntr.old_top].selfCycles); \
+ \
+ atomic_set(&dev_priv->mksstat_kern_pids[_##kern_cntr.slot], current->pid); \
+ } \
+ } \
+ } while (0)
+
+#else
+#define MKS_STAT_TIME_DECL(kern_cntr)
+#define MKS_STAT_TIME_PUSH(kern_cntr)
+#define MKS_STAT_TIME_POP(kern_cntr)
+
+#endif /* IS_ENABLED(CONFIG_DRM_VMWGFX_MKSSTATS */
+
+#endif
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c b/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c
index 5648664f71bc..fefd7a71d764 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c
@@ -507,11 +507,13 @@ static void vmw_mob_pt_setup(struct vmw_mob *mob,
{
unsigned long num_pt_pages = 0;
struct ttm_buffer_object *bo = mob->pt_bo;
- struct vmw_piter save_pt_iter;
+ struct vmw_piter save_pt_iter = {0};
struct vmw_piter pt_iter;
const struct vmw_sg_table *vsgt;
int ret;
+ BUG_ON(num_data_pages == 0);
+
ret = ttm_bo_reserve(bo, false, true, NULL);
BUG_ON(ret != 0);
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_msg.c b/drivers/gpu/drm/vmwgfx/vmwgfx_msg.c
index 3d08f5700bdb..74a3f09ad664 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_msg.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_msg.c
@@ -31,10 +31,12 @@
#include <linux/mem_encrypt.h>
#include <asm/hypervisor.h>
+#include <drm/drm_ioctl.h>
#include "vmwgfx_drv.h"
#include "vmwgfx_msg_x86.h"
#include "vmwgfx_msg_arm64.h"
+#include "vmwgfx_mksstat.h"
#define MESSAGE_STATUS_SUCCESS 0x0001
#define MESSAGE_STATUS_DORECV 0x0002
@@ -56,6 +58,11 @@
#define VMW_PORT_CMD_RECVSIZE (MSG_TYPE_RECVSIZE << 16 | VMW_PORT_CMD_MSG)
#define VMW_PORT_CMD_RECVSTATUS (MSG_TYPE_RECVSTATUS << 16 | VMW_PORT_CMD_MSG)
+#define VMW_PORT_CMD_MKS_GUEST_STATS 85
+#define VMW_PORT_CMD_MKSGS_RESET (0 << 16 | VMW_PORT_CMD_MKS_GUEST_STATS)
+#define VMW_PORT_CMD_MKSGS_ADD_PPN (1 << 16 | VMW_PORT_CMD_MKS_GUEST_STATS)
+#define VMW_PORT_CMD_MKSGS_REMOVE_PPN (2 << 16 | VMW_PORT_CMD_MKS_GUEST_STATS)
+
#define HIGH_WORD(X) ((X & 0xFFFF0000) >> 16)
#define MAX_USER_MSG_LENGTH PAGE_SIZE
@@ -155,6 +162,7 @@ static unsigned long vmw_port_hb_out(struct rpc_channel *channel,
/* HB port can't access encrypted memory. */
if (hb && !mem_encrypt_active()) {
unsigned long bp = channel->cookie_high;
+ u32 channel_id = (channel->channel_id << 16);
si = (uintptr_t) msg;
di = channel->cookie_low;
@@ -162,7 +170,7 @@ static unsigned long vmw_port_hb_out(struct rpc_channel *channel,
VMW_PORT_HB_OUT(
(MESSAGE_STATUS_SUCCESS << 16) | VMW_PORT_CMD_HB_MSG,
msg_len, si, di,
- VMWARE_HYPERVISOR_HB | (channel->channel_id << 16) |
+ VMWARE_HYPERVISOR_HB | channel_id |
VMWARE_HYPERVISOR_OUT,
VMW_HYPERVISOR_MAGIC, bp,
eax, ebx, ecx, edx, si, di);
@@ -210,6 +218,7 @@ static unsigned long vmw_port_hb_in(struct rpc_channel *channel, char *reply,
/* HB port can't access encrypted memory */
if (hb && !mem_encrypt_active()) {
unsigned long bp = channel->cookie_low;
+ u32 channel_id = (channel->channel_id << 16);
si = channel->cookie_high;
di = (uintptr_t) reply;
@@ -217,7 +226,7 @@ static unsigned long vmw_port_hb_in(struct rpc_channel *channel, char *reply,
VMW_PORT_HB_IN(
(MESSAGE_STATUS_SUCCESS << 16) | VMW_PORT_CMD_HB_MSG,
reply_len, si, di,
- VMWARE_HYPERVISOR_HB | (channel->channel_id << 16),
+ VMWARE_HYPERVISOR_HB | channel_id,
VMW_HYPERVISOR_MAGIC, bp,
eax, ebx, ecx, edx, si, di);
@@ -612,3 +621,569 @@ out_open:
return -EINVAL;
}
+
+/**
+ * reset_ppn_array: Resets a PPN64 array to INVALID_PPN64 content
+ *
+ * @arr: Array to reset.
+ * @size: Array length.
+ */
+static inline void reset_ppn_array(PPN64 *arr, size_t size)
+{
+ size_t i;
+
+ BUG_ON(!arr || size == 0);
+
+ for (i = 0; i < size; ++i)
+ arr[i] = INVALID_PPN64;
+}
+
+/**
+ * hypervisor_ppn_reset_all: Removes all mksGuestStat instance descriptors from
+ * the hypervisor. All related pages should be subsequently unpinned or freed.
+ *
+ */
+static inline void hypervisor_ppn_reset_all(void)
+{
+ unsigned long eax, ebx, ecx, edx, si = 0, di = 0;
+
+ VMW_PORT(VMW_PORT_CMD_MKSGS_RESET,
+ 0, si, di,
+ 0,
+ VMW_HYPERVISOR_MAGIC,
+ eax, ebx, ecx, edx, si, di);
+}
+
+/**
+ * hypervisor_ppn_add: Adds a single mksGuestStat instance descriptor to the
+ * hypervisor. Any related userspace pages should be pinned in advance.
+ *
+ * @pfn: Physical page number of the instance descriptor
+ */
+static inline void hypervisor_ppn_add(PPN64 pfn)
+{
+ unsigned long eax, ebx, ecx, edx, si = 0, di = 0;
+
+ VMW_PORT(VMW_PORT_CMD_MKSGS_ADD_PPN,
+ pfn, si, di,
+ 0,
+ VMW_HYPERVISOR_MAGIC,
+ eax, ebx, ecx, edx, si, di);
+}
+
+/**
+ * hypervisor_ppn_remove: Removes a single mksGuestStat instance descriptor from
+ * the hypervisor. All related pages should be subsequently unpinned or freed.
+ *
+ * @pfn: Physical page number of the instance descriptor
+ */
+static inline void hypervisor_ppn_remove(PPN64 pfn)
+{
+ unsigned long eax, ebx, ecx, edx, si = 0, di = 0;
+
+ VMW_PORT(VMW_PORT_CMD_MKSGS_REMOVE_PPN,
+ pfn, si, di,
+ 0,
+ VMW_HYPERVISOR_MAGIC,
+ eax, ebx, ecx, edx, si, di);
+}
+
+#if IS_ENABLED(CONFIG_DRM_VMWGFX_MKSSTATS)
+
+/* Order of the total number of pages used for kernel-internal mksGuestStat; at least 2 */
+#define MKSSTAT_KERNEL_PAGES_ORDER 2
+/* Header to the text description of mksGuestStat instance descriptor */
+#define MKSSTAT_KERNEL_DESCRIPTION "vmwgfx"
+
+/* Kernel mksGuestStats counter names and desciptions; same order as enum mksstat_kern_stats_t */
+static const char* const mksstat_kern_name_desc[MKSSTAT_KERN_COUNT][2] =
+{
+ { "vmw_execbuf_ioctl", "vmw_execbuf_ioctl" },
+};
+
+/**
+ * mksstat_init_record: Initializes an MKSGuestStatCounter-based record
+ * for the respective mksGuestStat index.
+ *
+ * @stat_idx: Index of the MKSGuestStatCounter-based mksGuestStat record.
+ * @pstat: Pointer to array of MKSGuestStatCounterTime.
+ * @pinfo: Pointer to array of MKSGuestStatInfoEntry.
+ * @pstrs: Pointer to current end of the name/description sequence.
+ * Return: Pointer to the new end of the names/description sequence.
+ */
+
+static inline char *mksstat_init_record(mksstat_kern_stats_t stat_idx,
+ MKSGuestStatCounterTime *pstat, MKSGuestStatInfoEntry *pinfo, char *pstrs)
+{
+ char *const pstrd = pstrs + strlen(mksstat_kern_name_desc[stat_idx][0]) + 1;
+ strcpy(pstrs, mksstat_kern_name_desc[stat_idx][0]);
+ strcpy(pstrd, mksstat_kern_name_desc[stat_idx][1]);
+
+ pinfo[stat_idx].name.s = pstrs;
+ pinfo[stat_idx].description.s = pstrd;
+ pinfo[stat_idx].flags = MKS_GUEST_STAT_FLAG_NONE;
+ pinfo[stat_idx].stat.counter = (MKSGuestStatCounter *)&pstat[stat_idx];
+
+ return pstrd + strlen(mksstat_kern_name_desc[stat_idx][1]) + 1;
+}
+
+/**
+ * mksstat_init_record_time: Initializes an MKSGuestStatCounterTime-based record
+ * for the respective mksGuestStat index.
+ *
+ * @stat_idx: Index of the MKSGuestStatCounterTime-based mksGuestStat record.
+ * @pstat: Pointer to array of MKSGuestStatCounterTime.
+ * @pinfo: Pointer to array of MKSGuestStatInfoEntry.
+ * @pstrs: Pointer to current end of the name/description sequence.
+ * Return: Pointer to the new end of the names/description sequence.
+ */
+
+static inline char *mksstat_init_record_time(mksstat_kern_stats_t stat_idx,
+ MKSGuestStatCounterTime *pstat, MKSGuestStatInfoEntry *pinfo, char *pstrs)
+{
+ char *const pstrd = pstrs + strlen(mksstat_kern_name_desc[stat_idx][0]) + 1;
+ strcpy(pstrs, mksstat_kern_name_desc[stat_idx][0]);
+ strcpy(pstrd, mksstat_kern_name_desc[stat_idx][1]);
+
+ pinfo[stat_idx].name.s = pstrs;
+ pinfo[stat_idx].description.s = pstrd;
+ pinfo[stat_idx].flags = MKS_GUEST_STAT_FLAG_TIME;
+ pinfo[stat_idx].stat.counterTime = &pstat[stat_idx];
+
+ return pstrd + strlen(mksstat_kern_name_desc[stat_idx][1]) + 1;
+}
+
+/**
+ * mksstat_init_kern_id: Creates a single mksGuestStat instance descriptor and
+ * kernel-internal counters. Adds PFN mapping to the hypervisor.
+ *
+ * Create a single mksGuestStat instance descriptor and corresponding structures
+ * for all kernel-internal counters. The corresponding PFNs are mapped with the
+ * hypervisor.
+ *
+ * @ppage: Output pointer to page containing the instance descriptor.
+ * Return: Zero on success, negative error code on error.
+ */
+
+static int mksstat_init_kern_id(struct page **ppage)
+{
+ MKSGuestStatInstanceDescriptor *pdesc;
+ MKSGuestStatCounterTime *pstat;
+ MKSGuestStatInfoEntry *pinfo;
+ char *pstrs, *pstrs_acc;
+
+ /* Allocate pages for the kernel-internal instance descriptor */
+ struct page *page = alloc_pages(GFP_KERNEL | __GFP_ZERO, MKSSTAT_KERNEL_PAGES_ORDER);
+
+ if (!page)
+ return -ENOMEM;
+
+ pdesc = page_address(page);
+ pstat = vmw_mksstat_get_kern_pstat(pdesc);
+ pinfo = vmw_mksstat_get_kern_pinfo(pdesc);
+ pstrs = vmw_mksstat_get_kern_pstrs(pdesc);
+
+ /* Set up all kernel-internal counters and corresponding structures */
+ pstrs_acc = pstrs;
+ pstrs_acc = mksstat_init_record_time(MKSSTAT_KERN_EXECBUF, pstat, pinfo, pstrs_acc);
+
+ /* Add new counters above, in their order of appearance in mksstat_kern_stats_t */
+
+ BUG_ON(pstrs_acc - pstrs > PAGE_SIZE);
+
+ /* Set up the kernel-internal instance descriptor */
+ pdesc->reservedMBZ = 0;
+ pdesc->statStartVA = (uintptr_t)pstat;
+ pdesc->strsStartVA = (uintptr_t)pstrs;
+ pdesc->statLength = sizeof(*pstat) * MKSSTAT_KERN_COUNT;
+ pdesc->infoLength = sizeof(*pinfo) * MKSSTAT_KERN_COUNT;
+ pdesc->strsLength = pstrs_acc - pstrs;
+ snprintf(pdesc->description, ARRAY_SIZE(pdesc->description) - 1, "%s pid=%d",
+ MKSSTAT_KERNEL_DESCRIPTION, current->pid);
+
+ pdesc->statPPNs[0] = page_to_pfn(virt_to_page(pstat));
+ reset_ppn_array(pdesc->statPPNs + 1, ARRAY_SIZE(pdesc->statPPNs) - 1);
+
+ pdesc->infoPPNs[0] = page_to_pfn(virt_to_page(pinfo));
+ reset_ppn_array(pdesc->infoPPNs + 1, ARRAY_SIZE(pdesc->infoPPNs) - 1);
+
+ pdesc->strsPPNs[0] = page_to_pfn(virt_to_page(pstrs));
+ reset_ppn_array(pdesc->strsPPNs + 1, ARRAY_SIZE(pdesc->strsPPNs) - 1);
+
+ *ppage = page;
+
+ hypervisor_ppn_add((PPN64)page_to_pfn(page));
+
+ return 0;
+}
+
+/**
+ * vmw_mksstat_get_kern_slot: Acquires a slot for a single kernel-internal
+ * mksGuestStat instance descriptor.
+ *
+ * Find a slot for a single kernel-internal mksGuestStat instance descriptor.
+ * In case no such was already present, allocate a new one and set up a kernel-
+ * internal mksGuestStat instance descriptor for the former.
+ *
+ * @pid: Process for which a slot is sought.
+ * @dev_priv: Identifies the drm private device.
+ * Return: Non-negative slot on success, negative error code on error.
+ */
+
+int vmw_mksstat_get_kern_slot(pid_t pid, struct vmw_private *dev_priv)
+{
+ const size_t base = (u32)hash_32(pid, MKSSTAT_CAPACITY_LOG2);
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(dev_priv->mksstat_kern_pids); ++i) {
+ const size_t slot = (i + base) % ARRAY_SIZE(dev_priv->mksstat_kern_pids);
+
+ /* Check if an instance descriptor for this pid is already present */
+ if (pid == (pid_t)atomic_read(&dev_priv->mksstat_kern_pids[slot]))
+ return (int)slot;
+
+ /* Set up a new instance descriptor for this pid */
+ if (!atomic_cmpxchg(&dev_priv->mksstat_kern_pids[slot], 0, MKSSTAT_PID_RESERVED)) {
+ const int ret = mksstat_init_kern_id(&dev_priv->mksstat_kern_pages[slot]);
+
+ if (!ret) {
+ /* Reset top-timer tracking for this slot */
+ dev_priv->mksstat_kern_top_timer[slot] = MKSSTAT_KERN_COUNT;
+
+ atomic_set(&dev_priv->mksstat_kern_pids[slot], pid);
+ return (int)slot;
+ }
+
+ atomic_set(&dev_priv->mksstat_kern_pids[slot], 0);
+ return ret;
+ }
+ }
+
+ return -ENOSPC;
+}
+
+#endif
+
+/**
+ * vmw_mksstat_cleanup_descriptor: Frees a single userspace-originating
+ * mksGuestStat instance-descriptor page and unpins all related user pages.
+ *
+ * Unpin all user pages realated to this instance descriptor and free
+ * the instance-descriptor page itself.
+ *
+ * @page: Page of the instance descriptor.
+ */
+
+static void vmw_mksstat_cleanup_descriptor(struct page *page)
+{
+ MKSGuestStatInstanceDescriptor *pdesc = page_address(page);
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(pdesc->statPPNs) && pdesc->statPPNs[i] != INVALID_PPN64; ++i)
+ unpin_user_page(pfn_to_page(pdesc->statPPNs[i]));
+
+ for (i = 0; i < ARRAY_SIZE(pdesc->infoPPNs) && pdesc->infoPPNs[i] != INVALID_PPN64; ++i)
+ unpin_user_page(pfn_to_page(pdesc->infoPPNs[i]));
+
+ for (i = 0; i < ARRAY_SIZE(pdesc->strsPPNs) && pdesc->strsPPNs[i] != INVALID_PPN64; ++i)
+ unpin_user_page(pfn_to_page(pdesc->strsPPNs[i]));
+
+ __free_page(page);
+}
+
+/**
+ * vmw_mksstat_remove_all: Resets all mksGuestStat instance descriptors
+ * from the hypervisor.
+ *
+ * Discard all hypervisor PFN mappings, containing active mksGuestState instance
+ * descriptors, unpin the related userspace pages and free the related kernel pages.
+ *
+ * @dev_priv: Identifies the drm private device.
+ * Return: Zero on success, negative error code on error.
+ */
+
+int vmw_mksstat_remove_all(struct vmw_private *dev_priv)
+{
+ int ret = 0;
+ size_t i;
+
+ /* Discard all PFN mappings with the hypervisor */
+ hypervisor_ppn_reset_all();
+
+ /* Discard all userspace-originating instance descriptors and unpin all related pages */
+ for (i = 0; i < ARRAY_SIZE(dev_priv->mksstat_user_pids); ++i) {
+ const pid_t pid0 = (pid_t)atomic_read(&dev_priv->mksstat_user_pids[i]);
+
+ if (!pid0)
+ continue;
+
+ if (pid0 != MKSSTAT_PID_RESERVED) {
+ const pid_t pid1 = atomic_cmpxchg(&dev_priv->mksstat_user_pids[i], pid0, MKSSTAT_PID_RESERVED);
+
+ if (!pid1)
+ continue;
+
+ if (pid1 == pid0) {
+ struct page *const page = dev_priv->mksstat_user_pages[i];
+
+ BUG_ON(!page);
+
+ dev_priv->mksstat_user_pages[i] = NULL;
+ atomic_set(&dev_priv->mksstat_user_pids[i], 0);
+
+ vmw_mksstat_cleanup_descriptor(page);
+ continue;
+ }
+ }
+
+ ret = -EAGAIN;
+ }
+
+#if IS_ENABLED(CONFIG_DRM_VMWGFX_MKSSTATS)
+ /* Discard all kernel-internal instance descriptors and free all related pages */
+ for (i = 0; i < ARRAY_SIZE(dev_priv->mksstat_kern_pids); ++i) {
+ const pid_t pid0 = (pid_t)atomic_read(&dev_priv->mksstat_kern_pids[i]);
+
+ if (!pid0)
+ continue;
+
+ if (pid0 != MKSSTAT_PID_RESERVED) {
+ const pid_t pid1 = atomic_cmpxchg(&dev_priv->mksstat_kern_pids[i], pid0, MKSSTAT_PID_RESERVED);
+
+ if (!pid1)
+ continue;
+
+ if (pid1 == pid0) {
+ struct page *const page = dev_priv->mksstat_kern_pages[i];
+
+ BUG_ON(!page);
+
+ dev_priv->mksstat_kern_pages[i] = NULL;
+ atomic_set(&dev_priv->mksstat_kern_pids[i], 0);
+
+ __free_pages(page, MKSSTAT_KERNEL_PAGES_ORDER);
+ continue;
+ }
+ }
+
+ ret = -EAGAIN;
+ }
+
+#endif
+ return ret;
+}
+
+/**
+ * vmw_mksstat_reset_ioctl: Resets all mksGuestStat instance descriptors
+ * from the hypervisor.
+ *
+ * Discard all hypervisor PFN mappings, containing active mksGuestStat instance
+ * descriptors, unpin the related userspace pages and free the related kernel pages.
+ *
+ * @dev: Identifies the drm device.
+ * @data: Pointer to the ioctl argument.
+ * @file_priv: Identifies the caller; unused.
+ * Return: Zero on success, negative error code on error.
+ */
+
+int vmw_mksstat_reset_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct vmw_private *const dev_priv = vmw_priv(dev);
+ return vmw_mksstat_remove_all(dev_priv);
+}
+
+/**
+ * vmw_mksstat_add_ioctl: Creates a single userspace-originating mksGuestStat
+ * instance descriptor and registers that with the hypervisor.
+ *
+ * Create a hypervisor PFN mapping, containing a single mksGuestStat instance
+ * descriptor and pin the corresponding userspace pages.
+ *
+ * @dev: Identifies the drm device.
+ * @data: Pointer to the ioctl argument.
+ * @file_priv: Identifies the caller; unused.
+ * Return: Zero on success, negative error code on error.
+ */
+
+int vmw_mksstat_add_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_vmw_mksstat_add_arg *arg =
+ (struct drm_vmw_mksstat_add_arg *) data;
+
+ struct vmw_private *const dev_priv = vmw_priv(dev);
+
+ struct page *page;
+ MKSGuestStatInstanceDescriptor *pdesc;
+ const size_t num_pages_stat = vmw_num_pages(arg->stat_len);
+ const size_t num_pages_info = vmw_num_pages(arg->info_len);
+ const size_t num_pages_strs = vmw_num_pages(arg->strs_len);
+ long desc_len;
+ long nr_pinned_stat;
+ long nr_pinned_info;
+ long nr_pinned_strs;
+ struct page *pages_stat[ARRAY_SIZE(pdesc->statPPNs)];
+ struct page *pages_info[ARRAY_SIZE(pdesc->infoPPNs)];
+ struct page *pages_strs[ARRAY_SIZE(pdesc->strsPPNs)];
+ size_t i, slot;
+
+ arg->id = -1;
+
+ if (!arg->stat || !arg->info || !arg->strs)
+ return -EINVAL;
+
+ if (!arg->stat_len || !arg->info_len || !arg->strs_len)
+ return -EINVAL;
+
+ if (!arg->description)
+ return -EINVAL;
+
+ if (num_pages_stat > ARRAY_SIZE(pdesc->statPPNs) ||
+ num_pages_info > ARRAY_SIZE(pdesc->infoPPNs) ||
+ num_pages_strs > ARRAY_SIZE(pdesc->strsPPNs))
+ return -EINVAL;
+
+ /* Find an available slot in the mksGuestStats user array and reserve it */
+ for (slot = 0; slot < ARRAY_SIZE(dev_priv->mksstat_user_pids); ++slot)
+ if (!atomic_cmpxchg(&dev_priv->mksstat_user_pids[slot], 0, MKSSTAT_PID_RESERVED))
+ break;
+
+ if (slot == ARRAY_SIZE(dev_priv->mksstat_user_pids))
+ return -ENOSPC;
+
+ BUG_ON(dev_priv->mksstat_user_pages[slot]);
+
+ /* Allocate a page for the instance descriptor */
+ page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+
+ if (!page) {
+ atomic_set(&dev_priv->mksstat_user_pids[slot], 0);
+ return -ENOMEM;
+ }
+
+ /* Set up the instance descriptor */
+ pdesc = page_address(page);
+
+ pdesc->reservedMBZ = 0;
+ pdesc->statStartVA = arg->stat;
+ pdesc->strsStartVA = arg->strs;
+ pdesc->statLength = arg->stat_len;
+ pdesc->infoLength = arg->info_len;
+ pdesc->strsLength = arg->strs_len;
+ desc_len = strncpy_from_user(pdesc->description, u64_to_user_ptr(arg->description),
+ ARRAY_SIZE(pdesc->description) - 1);
+
+ if (desc_len < 0) {
+ atomic_set(&dev_priv->mksstat_user_pids[slot], 0);
+ return -EFAULT;
+ }
+
+ reset_ppn_array(pdesc->statPPNs, ARRAY_SIZE(pdesc->statPPNs));
+ reset_ppn_array(pdesc->infoPPNs, ARRAY_SIZE(pdesc->infoPPNs));
+ reset_ppn_array(pdesc->strsPPNs, ARRAY_SIZE(pdesc->strsPPNs));
+
+ /* Pin mksGuestStat user pages and store those in the instance descriptor */
+ nr_pinned_stat = pin_user_pages(arg->stat, num_pages_stat, FOLL_LONGTERM, pages_stat, NULL);
+ if (num_pages_stat != nr_pinned_stat)
+ goto err_pin_stat;
+
+ for (i = 0; i < num_pages_stat; ++i)
+ pdesc->statPPNs[i] = page_to_pfn(pages_stat[i]);
+
+ nr_pinned_info = pin_user_pages(arg->info, num_pages_info, FOLL_LONGTERM, pages_info, NULL);
+ if (num_pages_info != nr_pinned_info)
+ goto err_pin_info;
+
+ for (i = 0; i < num_pages_info; ++i)
+ pdesc->infoPPNs[i] = page_to_pfn(pages_info[i]);
+
+ nr_pinned_strs = pin_user_pages(arg->strs, num_pages_strs, FOLL_LONGTERM, pages_strs, NULL);
+ if (num_pages_strs != nr_pinned_strs)
+ goto err_pin_strs;
+
+ for (i = 0; i < num_pages_strs; ++i)
+ pdesc->strsPPNs[i] = page_to_pfn(pages_strs[i]);
+
+ /* Send the descriptor to the host via a hypervisor call. The mksGuestStat
+ pages will remain in use until the user requests a matching remove stats
+ or a stats reset occurs. */
+ hypervisor_ppn_add((PPN64)page_to_pfn(page));
+
+ dev_priv->mksstat_user_pages[slot] = page;
+ atomic_set(&dev_priv->mksstat_user_pids[slot], task_pgrp_vnr(current));
+
+ arg->id = slot;
+
+ DRM_DEV_INFO(dev->dev, "pid=%d arg.description='%.*s' id=%lu\n", current->pid, (int)desc_len, pdesc->description, slot);
+
+ return 0;
+
+err_pin_strs:
+ if (nr_pinned_strs > 0)
+ unpin_user_pages(pages_strs, nr_pinned_strs);
+
+err_pin_info:
+ if (nr_pinned_info > 0)
+ unpin_user_pages(pages_info, nr_pinned_info);
+
+err_pin_stat:
+ if (nr_pinned_stat > 0)
+ unpin_user_pages(pages_stat, nr_pinned_stat);
+
+ atomic_set(&dev_priv->mksstat_user_pids[slot], 0);
+ __free_page(page);
+ return -ENOMEM;
+}
+
+/**
+ * vmw_mksstat_remove_ioctl: Removes a single userspace-originating mksGuestStat
+ * instance descriptor from the hypervisor.
+ *
+ * Discard a hypervisor PFN mapping, containing a single mksGuestStat instance
+ * descriptor and unpin the corresponding userspace pages.
+ *
+ * @dev: Identifies the drm device.
+ * @data: Pointer to the ioctl argument.
+ * @file_priv: Identifies the caller; unused.
+ * Return: Zero on success, negative error code on error.
+ */
+
+int vmw_mksstat_remove_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_vmw_mksstat_remove_arg *arg =
+ (struct drm_vmw_mksstat_remove_arg *) data;
+
+ struct vmw_private *const dev_priv = vmw_priv(dev);
+
+ const size_t slot = arg->id;
+ pid_t pgid, pid;
+
+ if (slot >= ARRAY_SIZE(dev_priv->mksstat_user_pids))
+ return -EINVAL;
+
+ DRM_DEV_INFO(dev->dev, "pid=%d arg.id=%lu\n", current->pid, slot);
+
+ pgid = task_pgrp_vnr(current);
+ pid = atomic_cmpxchg(&dev_priv->mksstat_user_pids[slot], pgid, MKSSTAT_PID_RESERVED);
+
+ if (!pid)
+ return 0;
+
+ if (pid == pgid) {
+ struct page *const page = dev_priv->mksstat_user_pages[slot];
+
+ BUG_ON(!page);
+
+ dev_priv->mksstat_user_pages[slot] = NULL;
+ atomic_set(&dev_priv->mksstat_user_pids[slot], 0);
+
+ hypervisor_ppn_remove((PPN64)page_to_pfn(page));
+
+ vmw_mksstat_cleanup_descriptor(page);
+ return 0;
+ }
+
+ return -EAGAIN;
+}
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
index 7b45393ad98e..3b6f6044c325 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
@@ -114,6 +114,7 @@ static void vmw_resource_release(struct kref *kref)
container_of(kref, struct vmw_resource, kref);
struct vmw_private *dev_priv = res->dev_priv;
int id;
+ int ret;
struct idr *idr = &dev_priv->res_idr[res->func->res_type];
spin_lock(&dev_priv->resource_lock);
@@ -122,7 +123,8 @@ static void vmw_resource_release(struct kref *kref)
if (res->backup) {
struct ttm_buffer_object *bo = &res->backup->base;
- ttm_bo_reserve(bo, false, false, NULL);
+ ret = ttm_bo_reserve(bo, false, false, NULL);
+ BUG_ON(ret);
if (vmw_resource_mob_attached(res) &&
res->func->unbind != NULL) {
struct ttm_validate_buffer val_buf;
@@ -1001,7 +1003,9 @@ int vmw_resource_pin(struct vmw_resource *res, bool interruptible)
if (res->backup) {
vbo = res->backup;
- ttm_bo_reserve(&vbo->base, interruptible, false, NULL);
+ ret = ttm_bo_reserve(&vbo->base, interruptible, false, NULL);
+ if (ret)
+ goto out_no_validate;
if (!vbo->base.pin_count) {
ret = ttm_bo_validate
(&vbo->base,
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_so.c b/drivers/gpu/drm/vmwgfx/vmwgfx_so.c
index c3a8d6e8380e..9efb4463ce99 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_so.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_so.c
@@ -539,7 +539,8 @@ const SVGACOTableType vmw_so_cotables[] = {
[vmw_so_ds] = SVGA_COTABLE_DEPTHSTENCIL,
[vmw_so_rs] = SVGA_COTABLE_RASTERIZERSTATE,
[vmw_so_ss] = SVGA_COTABLE_SAMPLER,
- [vmw_so_so] = SVGA_COTABLE_STREAMOUTPUT
+ [vmw_so_so] = SVGA_COTABLE_STREAMOUTPUT,
+ [vmw_so_max]= SVGA_COTABLE_MAX
};
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
index 0835468bb2ee..47c03a276515 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
@@ -1872,7 +1872,6 @@ static void vmw_surface_dirty_range_add(struct vmw_resource *res, size_t start,
static int vmw_surface_dirty_sync(struct vmw_resource *res)
{
struct vmw_private *dev_priv = res->dev_priv;
- bool has_dx = 0;
u32 i, num_dirty;
struct vmw_surface_dirty *dirty =
(struct vmw_surface_dirty *) res->dirty;
@@ -1899,7 +1898,7 @@ static int vmw_surface_dirty_sync(struct vmw_resource *res)
if (!num_dirty)
goto out;
- alloc_size = num_dirty * ((has_dx) ? sizeof(*cmd1) : sizeof(*cmd2));
+ alloc_size = num_dirty * ((has_sm4_context(dev_priv)) ? sizeof(*cmd1) : sizeof(*cmd2));
cmd = VMW_CMD_RESERVE(dev_priv, alloc_size);
if (!cmd)
return -ENOMEM;
@@ -1917,7 +1916,7 @@ static int vmw_surface_dirty_sync(struct vmw_resource *res)
* DX_UPDATE_SUBRESOURCE is aware of array surfaces.
* UPDATE_GB_IMAGE is not.
*/
- if (has_dx) {
+ if (has_sm4_context(dev_priv)) {
cmd1->header.id = SVGA_3D_CMD_DX_UPDATE_SUBRESOURCE;
cmd1->header.size = sizeof(cmd1->body);
cmd1->body.sid = res->id;
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c
index 0488042fb287..b0973c27e774 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c
@@ -222,36 +222,6 @@ static bool __vmw_piter_sg_next(struct vmw_piter *viter)
}
-/**
- * __vmw_piter_non_sg_page: Helper functions to return a pointer
- * to the current page.
- *
- * @viter: Pointer to the iterator
- *
- * These functions return a pointer to the page currently
- * pointed to by @viter. Functions are selected depending on the
- * current mapping mode.
- */
-static struct page *__vmw_piter_non_sg_page(struct vmw_piter *viter)
-{
- return viter->pages[viter->i];
-}
-
-/**
- * __vmw_piter_phys_addr: Helper functions to return the DMA
- * address of the current page.
- *
- * @viter: Pointer to the iterator
- *
- * These functions return the DMA address of the page currently
- * pointed to by @viter. Functions are selected depending on the
- * current mapping mode.
- */
-static dma_addr_t __vmw_piter_phys_addr(struct vmw_piter *viter)
-{
- return page_to_phys(viter->pages[viter->i]);
-}
-
static dma_addr_t __vmw_piter_dma_addr(struct vmw_piter *viter)
{
return viter->addrs[viter->i];
@@ -279,13 +249,8 @@ void vmw_piter_start(struct vmw_piter *viter, const struct vmw_sg_table *vsgt,
{
viter->i = p_offset - 1;
viter->num_pages = vsgt->num_pages;
- viter->page = &__vmw_piter_non_sg_page;
viter->pages = vsgt->pages;
switch (vsgt->mode) {
- case vmw_dma_phys:
- viter->next = &__vmw_piter_non_sg_next;
- viter->dma_address = &__vmw_piter_phys_addr;
- break;
case vmw_dma_alloc_coherent:
viter->next = &__vmw_piter_non_sg_next;
viter->dma_address = &__vmw_piter_dma_addr;
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
index 8338b1d20f2a..b09094b50c5d 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
@@ -586,13 +586,13 @@ int vmw_validation_bo_validate(struct vmw_validation_context *ctx, bool intr)
container_of(entry->base.bo, typeof(*vbo), base);
if (entry->cpu_blit) {
- struct ttm_operation_ctx ctx = {
+ struct ttm_operation_ctx ttm_ctx = {
.interruptible = intr,
.no_wait_gpu = false
};
ret = ttm_bo_validate(entry->base.bo,
- &vmw_nonfixed_placement, &ctx);
+ &vmw_nonfixed_placement, &ttm_ctx);
} else {
ret = vmw_validation_bo_validate_single
(entry->base.bo, intr, entry->as_mob);
diff --git a/include/drm/drm_dp_aux_bus.h b/include/drm/drm_dp_aux_bus.h
new file mode 100644
index 000000000000..4f19b20b1dd6
--- /dev/null
+++ b/include/drm/drm_dp_aux_bus.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * The DP AUX bus is used for devices that are connected over a DisplayPort
+ * AUX bus. The devices on the far side of the bus are referred to as
+ * endpoints in this code.
+ */
+
+#ifndef _DP_AUX_BUS_H_
+#define _DP_AUX_BUS_H_
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+
+/**
+ * struct dp_aux_ep_device - Main dev structure for DP AUX endpoints
+ *
+ * This is used to instantiate devices that are connected via a DP AUX
+ * bus. Usually the device is a panel, but conceivable other devices could
+ * be hooked up there.
+ */
+struct dp_aux_ep_device {
+ /** @dev: The normal dev pointer */
+ struct device dev;
+ /** @aux: Pointer to the aux bus */
+ struct drm_dp_aux *aux;
+};
+
+struct dp_aux_ep_driver {
+ int (*probe)(struct dp_aux_ep_device *aux_ep);
+ void (*remove)(struct dp_aux_ep_device *aux_ep);
+ void (*shutdown)(struct dp_aux_ep_device *aux_ep);
+ struct device_driver driver;
+};
+
+static inline struct dp_aux_ep_device *to_dp_aux_ep_dev(struct device *dev)
+{
+ return container_of(dev, struct dp_aux_ep_device, dev);
+}
+
+static inline struct dp_aux_ep_driver *to_dp_aux_ep_drv(struct device_driver *drv)
+{
+ return container_of(drv, struct dp_aux_ep_driver, driver);
+}
+
+int of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux);
+void of_dp_aux_depopulate_ep_devices(struct drm_dp_aux *aux);
+int devm_of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux);
+
+#define dp_aux_dp_driver_register(aux_ep_drv) \
+ __dp_aux_dp_driver_register(aux_ep_drv, THIS_MODULE)
+int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *aux_ep_drv,
+ struct module *owner);
+void dp_aux_dp_driver_unregister(struct dp_aux_ep_driver *aux_ep_drv);
+
+#endif /* _DP_AUX_BUS_H_ */
diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
index 06681bf46d81..0bd6396648b4 100644
--- a/include/drm/drm_dp_helper.h
+++ b/include/drm/drm_dp_helper.h
@@ -1801,6 +1801,24 @@ drm_dp_sink_can_do_video_without_timing_msa(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
DP_MSA_TIMING_PAR_IGNORED;
}
+/**
+ * drm_edp_backlight_supported() - Check an eDP DPCD for VESA backlight support
+ * @edp_dpcd: The DPCD to check
+ *
+ * Note that currently this function will return %false for panels which support various DPCD
+ * backlight features but which require the brightness be set through PWM, and don't support setting
+ * the brightness level via the DPCD. This is a TODO.
+ *
+ * Returns: %True if @edp_dpcd indicates that VESA backlight controls are supported, %false
+ * otherwise
+ */
+static inline bool
+drm_edp_backlight_supported(const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE])
+{
+ return (edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP) &&
+ (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP);
+}
+
/*
* DisplayPort AUX channel
*/
@@ -2107,6 +2125,36 @@ drm_dp_has_quirk(const struct drm_dp_desc *desc, enum drm_dp_quirk quirk)
return desc->quirks & BIT(quirk);
}
+/**
+ * struct drm_edp_backlight_info - Probed eDP backlight info struct
+ * @pwmgen_bit_count: The pwmgen bit count
+ * @pwm_freq_pre_divider: The PWM frequency pre-divider value being used for this backlight, if any
+ * @max: The maximum backlight level that may be set
+ * @lsb_reg_used: Do we also write values to the DP_EDP_BACKLIGHT_BRIGHTNESS_LSB register?
+ * @aux_enable: Does the panel support the AUX enable cap?
+ *
+ * This structure contains various data about an eDP backlight, which can be populated by using
+ * drm_edp_backlight_init().
+ */
+struct drm_edp_backlight_info {
+ u8 pwmgen_bit_count;
+ u8 pwm_freq_pre_divider;
+ u16 max;
+
+ bool lsb_reg_used : 1;
+ bool aux_enable : 1;
+};
+
+int
+drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl,
+ u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE],
+ u16 *current_level, u8 *current_mode);
+int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl,
+ u16 level);
+int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl,
+ u16 level);
+int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl);
+
#ifdef CONFIG_DRM_DP_CEC
void drm_dp_cec_irq(struct drm_dp_aux *aux);
void drm_dp_cec_register_connector(struct drm_dp_aux *aux,
diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h
index d043752a74cf..e1f49dd241f7 100644
--- a/include/uapi/drm/drm.h
+++ b/include/uapi/drm/drm.h
@@ -635,8 +635,8 @@ struct drm_gem_open {
/**
* DRM_CAP_VBLANK_HIGH_CRTC
*
- * If set to 1, the kernel supports specifying a CRTC index in the high bits of
- * &drm_wait_vblank_request.type.
+ * If set to 1, the kernel supports specifying a :ref:`CRTC index<crtc_index>`
+ * in the high bits of &drm_wait_vblank_request.type.
*
* Starting kernel version 2.6.39, this capability is always set to 1.
*/
diff --git a/include/uapi/drm/vmwgfx_drm.h b/include/uapi/drm/vmwgfx_drm.h
index 02e917507479..9078775feb51 100644
--- a/include/uapi/drm/vmwgfx_drm.h
+++ b/include/uapi/drm/vmwgfx_drm.h
@@ -72,6 +72,9 @@ extern "C" {
#define DRM_VMW_GB_SURFACE_CREATE_EXT 27
#define DRM_VMW_GB_SURFACE_REF_EXT 28
#define DRM_VMW_MSG 29
+#define DRM_VMW_MKSSTAT_RESET 30
+#define DRM_VMW_MKSSTAT_ADD 31
+#define DRM_VMW_MKSSTAT_REMOVE 32
/*************************************************************************/
/**
@@ -1236,6 +1239,44 @@ struct drm_vmw_msg_arg {
__u32 receive_len;
};
+/**
+ * struct drm_vmw_mksstat_add_arg
+ *
+ * @stat: Pointer to user-space stat-counters array, page-aligned.
+ * @info: Pointer to user-space counter-infos array, page-aligned.
+ * @strs: Pointer to user-space stat strings, page-aligned.
+ * @stat_len: Length in bytes of stat-counters array.
+ * @info_len: Length in bytes of counter-infos array.
+ * @strs_len: Length in bytes of the stat strings, terminators included.
+ * @description: Pointer to instance descriptor string; will be truncated
+ * to MKS_GUEST_STAT_INSTANCE_DESC_LENGTH chars.
+ * @id: Output identifier of the produced record; -1 if error.
+ *
+ * Argument to the DRM_VMW_MKSSTAT_ADD ioctl.
+ */
+struct drm_vmw_mksstat_add_arg {
+ __u64 stat;
+ __u64 info;
+ __u64 strs;
+ __u64 stat_len;
+ __u64 info_len;
+ __u64 strs_len;
+ __u64 description;
+ __u64 id;
+};
+
+/**
+ * struct drm_vmw_mksstat_remove_arg
+ *
+ * @id: Identifier of the record being disposed, originally obtained through
+ * DRM_VMW_MKSSTAT_ADD ioctl.
+ *
+ * Argument to the DRM_VMW_MKSSTAT_REMOVE ioctl.
+ */
+struct drm_vmw_mksstat_remove_arg {
+ __u64 id;
+};
+
#if defined(__cplusplus)
}
#endif