[PATCH 6/6] drm/msm: add OCMEM driver

Rob Clark robdclark at gmail.com
Tue Sep 29 12:48:58 PDT 2015


For now, since the GPU is the only upstream consumer, just stuff this
into drm/msm.  Eventually if we have other consumers, we'll have to
split this out and make the allocation less hard coded.  But I'll punt
on that until I better understand the non-gpu uses-cases (and whether
the allocation *really* needs to be as complicated as it is in the
downstream driver).

Signed-off-by: Rob Clark <robdclark at gmail.com>
---
 drivers/gpu/drm/msm/Makefile          |   3 +-
 drivers/gpu/drm/msm/adreno/a3xx_gpu.c |  19 +-
 drivers/gpu/drm/msm/adreno/a4xx_gpu.c |  19 +-
 drivers/gpu/drm/msm/msm_drv.c         |   2 +
 drivers/gpu/drm/msm/msm_gpu.h         |   3 +
 drivers/gpu/drm/msm/ocmem/ocmem.c     | 399 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/ocmem/ocmem.h     |  46 ++++
 7 files changed, 463 insertions(+), 28 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/ocmem/ocmem.c
 create mode 100644 drivers/gpu/drm/msm/ocmem/ocmem.h

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 0a543eb..8ddf6fa 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -48,7 +48,8 @@ msm-y := \
 	msm_iommu.o \
 	msm_perf.o \
 	msm_rd.o \
-	msm_ringbuffer.o
+	msm_ringbuffer.o \
+	ocmem/ocmem.o
 
 msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o
 msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o
diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
index ca29688..2a8bf4c 100644
--- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
+++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c
@@ -17,10 +17,7 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifdef CONFIG_MSM_OCMEM
-#  include <mach/ocmem.h>
-#endif
-
+#include "ocmem/ocmem.h"
 #include "a3xx_gpu.h"
 
 #define A3XX_INT0_MASK \
@@ -322,10 +319,8 @@ static void a3xx_destroy(struct msm_gpu *gpu)
 
 	adreno_gpu_cleanup(adreno_gpu);
 
-#ifdef CONFIG_MSM_OCMEM
-	if (a3xx_gpu->ocmem_base)
+	if (a3xx_gpu->ocmem_hdl)
 		ocmem_free(OCMEM_GRAPHICS, a3xx_gpu->ocmem_hdl);
-#endif
 
 	kfree(a3xx_gpu);
 }
@@ -539,6 +534,7 @@ struct msm_gpu *a3xx_gpu_init(struct drm_device *dev)
 	struct msm_gpu *gpu;
 	struct msm_drm_private *priv = dev->dev_private;
 	struct platform_device *pdev = priv->gpu_pdev;
+	struct ocmem_buf *ocmem_hdl;
 	int ret;
 
 	if (!pdev) {
@@ -569,18 +565,13 @@ struct msm_gpu *a3xx_gpu_init(struct drm_device *dev)
 		goto fail;
 
 	/* if needed, allocate gmem: */
-	if (adreno_is_a330(adreno_gpu)) {
-#ifdef CONFIG_MSM_OCMEM
-		/* TODO this is different/missing upstream: */
-		struct ocmem_buf *ocmem_hdl =
-				ocmem_allocate(OCMEM_GRAPHICS, adreno_gpu->gmem);
-
+	ocmem_hdl = ocmem_allocate(OCMEM_GRAPHICS, adreno_gpu->gmem);
+	if (!IS_ERR(ocmem_hdl)) {
 		a3xx_gpu->ocmem_hdl = ocmem_hdl;
 		a3xx_gpu->ocmem_base = ocmem_hdl->addr;
 		adreno_gpu->gmem = ocmem_hdl->len;
 		DBG("using %dK of OCMEM at 0x%08x", adreno_gpu->gmem / 1024,
 				a3xx_gpu->ocmem_base);
-#endif
 	}
 
 	if (!gpu->mmu) {
diff --git a/drivers/gpu/drm/msm/adreno/a4xx_gpu.c b/drivers/gpu/drm/msm/adreno/a4xx_gpu.c
index a53f1be..17f084d 100644
--- a/drivers/gpu/drm/msm/adreno/a4xx_gpu.c
+++ b/drivers/gpu/drm/msm/adreno/a4xx_gpu.c
@@ -10,10 +10,9 @@
  * GNU General Public License for more details.
  *
  */
+
+#include "ocmem/ocmem.h"
 #include "a4xx_gpu.h"
-#ifdef CONFIG_MSM_OCMEM
-#  include <soc/qcom/ocmem.h>
-#endif
 
 #define A4XX_INT0_MASK \
 	(A4XX_INT0_RBBM_AHB_ERROR |        \
@@ -289,10 +288,8 @@ static void a4xx_destroy(struct msm_gpu *gpu)
 
 	adreno_gpu_cleanup(adreno_gpu);
 
-#ifdef CONFIG_MSM_OCMEM
-	if (a4xx_gpu->ocmem_base)
+	if (a4xx_gpu->ocmem_hdl)
 		ocmem_free(OCMEM_GRAPHICS, a4xx_gpu->ocmem_hdl);
-#endif
 
 	kfree(a4xx_gpu);
 }
@@ -538,6 +535,7 @@ struct msm_gpu *a4xx_gpu_init(struct drm_device *dev)
 	struct msm_gpu *gpu;
 	struct msm_drm_private *priv = dev->dev_private;
 	struct platform_device *pdev = priv->gpu_pdev;
+	struct ocmem_buf *ocmem_hdl;
 	int ret;
 
 	if (!pdev) {
@@ -568,18 +566,13 @@ struct msm_gpu *a4xx_gpu_init(struct drm_device *dev)
 		goto fail;
 
 	/* if needed, allocate gmem: */
-	if (adreno_is_a4xx(adreno_gpu)) {
-#ifdef CONFIG_MSM_OCMEM
-		/* TODO this is different/missing upstream: */
-		struct ocmem_buf *ocmem_hdl =
-				ocmem_allocate(OCMEM_GRAPHICS, adreno_gpu->gmem);
-
+	ocmem_hdl = ocmem_allocate(OCMEM_GRAPHICS, adreno_gpu->gmem);
+	if (!IS_ERR(ocmem_hdl)) {
 		a4xx_gpu->ocmem_hdl = ocmem_hdl;
 		a4xx_gpu->ocmem_base = ocmem_hdl->addr;
 		adreno_gpu->gmem = ocmem_hdl->len;
 		DBG("using %dK of OCMEM at 0x%08x", adreno_gpu->gmem / 1024,
 				a4xx_gpu->ocmem_base);
-#endif
 	}
 
 	if (!gpu->mmu) {
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index 28c9a2a..1b02c2d 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1165,6 +1165,7 @@ static int __init msm_drm_register(void)
 	msm_dsi_register();
 	msm_edp_register();
 	hdmi_register();
+	ocmem_register();
 	adreno_register();
 	return platform_driver_register(&msm_platform_driver);
 }
@@ -1175,6 +1176,7 @@ static void __exit msm_drm_unregister(void)
 	platform_driver_unregister(&msm_platform_driver);
 	hdmi_unregister();
 	adreno_unregister();
+	ocmem_unregister();
 	msm_edp_unregister();
 	msm_dsi_unregister();
 }
diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h
index 2bbe85a..f042ba8 100644
--- a/drivers/gpu/drm/msm/msm_gpu.h
+++ b/drivers/gpu/drm/msm/msm_gpu.h
@@ -172,4 +172,7 @@ struct msm_gpu *adreno_load_gpu(struct drm_device *dev);
 void __init adreno_register(void);
 void __exit adreno_unregister(void);
 
+void __init ocmem_register(void);
+void __exit ocmem_unregister(void);
+
 #endif /* __MSM_GPU_H__ */
diff --git a/drivers/gpu/drm/msm/ocmem/ocmem.c b/drivers/gpu/drm/msm/ocmem/ocmem.c
new file mode 100644
index 0000000..535d9f7
--- /dev/null
+++ b/drivers/gpu/drm/msm/ocmem/ocmem.c
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2015 Red Hat
+ * Author: Rob Clark <robdclark at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/qcom_scm.h>
+#include <linux/of_device.h>
+
+#include "msm_drv.h"
+#include "ocmem.h"
+#include "ocmem.xml.h"
+
+enum region_mode {
+	WIDE_MODE = 0x0,
+	THIN_MODE,
+	MODE_DEFAULT = WIDE_MODE,
+};
+
+enum ocmem_tz_client {
+	TZ_UNUSED = 0x0,
+	TZ_GRAPHICS,
+	TZ_VIDEO,
+	TZ_LP_AUDIO,
+	TZ_SENSORS,
+	TZ_OTHER_OS,
+	TZ_DEBUG,
+};
+
+struct ocmem_region {
+	unsigned psgsc_ctrl;
+	bool interleaved;
+	enum region_mode mode;
+	unsigned int num_macros;
+	enum ocmem_macro_state macro_state[4];
+	unsigned long macro_size;
+	unsigned long region_size;
+};
+
+struct ocmem_config {
+	uint8_t  num_regions;
+	uint32_t macro_size;
+};
+
+struct ocmem {
+	struct device *dev;
+	const struct ocmem_config *config;
+	struct resource *ocmem_mem;
+	struct clk *core_clk;
+	struct clk *iface_clk;
+	void __iomem *mmio;
+
+	unsigned num_ports;
+	unsigned num_macros;
+	bool interleaved;
+
+	struct ocmem_region *regions;
+};
+
+static struct ocmem *ocmem;
+
+static bool ocmem_exists(void);
+
+static inline void ocmem_write(struct ocmem *ocmem, u32 reg, u32 data)
+{
+	msm_writel(data, ocmem->mmio + reg);
+}
+
+static inline u32 ocmem_read(struct ocmem *ocmem, u32 reg)
+{
+	return msm_readl(ocmem->mmio + reg);
+}
+
+static int ocmem_clk_enable(struct ocmem *ocmem)
+{
+	int ret;
+
+	ret = clk_prepare_enable(ocmem->core_clk);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(ocmem->iface_clk);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void ocmem_clk_disable(struct ocmem *ocmem)
+{
+	clk_disable_unprepare(ocmem->iface_clk);
+	clk_disable_unprepare(ocmem->core_clk);
+}
+
+static int ocmem_dev_remove(struct platform_device *pdev)
+{
+	ocmem_clk_disable(ocmem);
+	return 0;
+}
+
+static void update_ocmem(struct ocmem *ocmem)
+{
+	uint32_t region_mode_ctrl = 0x0;
+	unsigned pos = 0;
+	unsigned i = 0;
+
+	if (!qcom_scm_ocmem_lock_available()) {
+		for (i = 0; i < ocmem->config->num_regions; i++) {
+			struct ocmem_region *region = &ocmem->regions[i];
+			pos = i << 2;
+			if (region->mode == THIN_MODE)
+				region_mode_ctrl |= BIT(pos);
+		}
+		dev_dbg(ocmem->dev, "ocmem_region_mode_control %x\n", region_mode_ctrl);
+		ocmem_write(ocmem, REG_OCMEM_REGION_MODE_CTL, region_mode_ctrl);
+	}
+
+	for (i = 0; i < ocmem->config->num_regions; i++) {
+		struct ocmem_region *region = &ocmem->regions[i];
+
+		ocmem_write(ocmem, REG_OCMEM_PSGSC_CTL(i),
+				OCMEM_PSGSC_CTL_MACRO0_MODE(region->macro_state[0]) |
+				OCMEM_PSGSC_CTL_MACRO1_MODE(region->macro_state[1]) |
+				OCMEM_PSGSC_CTL_MACRO2_MODE(region->macro_state[2]) |
+				OCMEM_PSGSC_CTL_MACRO3_MODE(region->macro_state[3]));
+	}
+}
+
+static unsigned long phys_to_offset(unsigned long addr)
+{
+	if ((addr < ocmem->ocmem_mem->start) ||
+		(addr >= ocmem->ocmem_mem->end))
+		return 0;
+	return addr - ocmem->ocmem_mem->start;
+}
+
+static unsigned long device_address(enum ocmem_client client, unsigned long addr)
+{
+	/* TODO, gpu uses phys_to_offset, but others do not.. */
+	return phys_to_offset(addr);
+}
+
+static void update_range(struct ocmem *ocmem, struct ocmem_buf *buf,
+		enum ocmem_macro_state mstate, enum region_mode rmode)
+{
+	unsigned long offset = 0;
+	int i, j;
+
+	/*
+	 * TODO probably should assert somewhere that range is aligned
+	 * to macro boundaries..
+	 */
+
+	for (i = 0; i < ocmem->config->num_regions; i++) {
+		struct ocmem_region *region = &ocmem->regions[i];
+		if ((buf->offset <= offset) && (offset < (buf->offset + buf->len)))
+			region->mode = rmode;
+		for (j = 0; j < region->num_macros; j++) {
+			if ((buf->offset <= offset) && (offset < (buf->offset + buf->len)))
+				region->macro_state[j] = mstate;
+			offset += region->macro_size;
+		}
+	}
+
+	update_ocmem(ocmem);
+}
+
+struct ocmem_buf *ocmem_allocate(enum ocmem_client client, unsigned long size)
+{
+	struct ocmem_buf *buf;
+
+	if (!ocmem) {
+		if (ocmem_exists())
+			return ERR_PTR(-EPROBE_DEFER);
+		return ERR_PTR(-ENXIO);
+	}
+
+	buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+	if (!buf)
+		return ERR_PTR(-ENOMEM);
+
+	/*
+	 * TODO less hard-coded allocation that works for more than
+	 * one user:
+	 */
+
+	buf->offset = 0;
+	buf->addr = device_address(client, buf->offset);
+	buf->len = size;
+
+	update_range(ocmem, buf, CORE_ON, WIDE_MODE);
+
+	if (qcom_scm_ocmem_lock_available()) {
+		int ret;
+		ret = qcom_scm_ocmem_lock(TZ_GRAPHICS, buf->offset, buf->len,
+				WIDE_MODE);
+		if (ret)
+			dev_err(ocmem->dev, "could not lock: %d\n", ret);
+	} else {
+		if (client == OCMEM_GRAPHICS) {
+			ocmem_write(ocmem, REG_OCMEM_GFX_MPU_START, buf->offset);
+			ocmem_write(ocmem, REG_OCMEM_GFX_MPU_END, buf->offset + buf->len);
+		}
+	}
+
+	return buf;
+}
+
+void ocmem_free(enum ocmem_client client, struct ocmem_buf *buf)
+{
+	update_range(ocmem, buf, CLK_OFF, MODE_DEFAULT);
+
+	if (qcom_scm_ocmem_lock_available()) {
+		int ret;
+		ret = qcom_scm_ocmem_unlock(TZ_GRAPHICS, buf->offset, buf->len);
+		if (ret)
+			dev_err(ocmem->dev, "could not unlock: %d\n", ret);
+	} else {
+		if (client == OCMEM_GRAPHICS) {
+			ocmem_write(ocmem, REG_OCMEM_GFX_MPU_START, 0x0);
+			ocmem_write(ocmem, REG_OCMEM_GFX_MPU_END, 0x0);
+		}
+	}
+
+	kfree(buf);
+}
+
+static const struct ocmem_config ocmem_8974_config = {
+	.num_regions = 3, .macro_size = SZ_128K,
+};
+
+static const struct of_device_id dt_match[] = {
+	{ .compatible = "qcom,ocmem-msm8974", .data = &ocmem_8974_config },
+	{}
+};
+
+static int ocmem_dev_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct ocmem_config *config = NULL;
+	uint32_t reg, num_banks, region_size;
+	const struct of_device_id *match;
+	int i, j, ret;
+
+	/* we need scm to be available: */
+	if (!qcom_scm_is_available())
+		return -EPROBE_DEFER;
+
+	match = of_match_device(dt_match, dev);
+	if (match)
+		config = match->data;
+
+	if (!config) {
+		dev_err(dev, "unknown config: %s\n", dev->of_node->name);
+		return -ENXIO;
+	}
+
+	ocmem = devm_kzalloc(dev, sizeof(*ocmem), GFP_KERNEL);
+	if (!ocmem)
+		return -ENOMEM;
+
+	ocmem->dev = dev;
+	ocmem->config = config;
+
+	ocmem->core_clk = devm_clk_get(dev, "core_clk");
+	if (IS_ERR(ocmem->core_clk)) {
+		dev_err(dev, "Unable to get the core clock\n");
+		return PTR_ERR(ocmem->core_clk);
+	}
+
+	ocmem->iface_clk = devm_clk_get(dev, "iface_clk");
+	if (IS_ERR_OR_NULL(ocmem->iface_clk)) {
+		ret = PTR_ERR(ocmem->iface_clk);
+		ocmem->iface_clk = NULL;
+		/* in probe-defer case, propagate error up and try again later: */
+		if (ret == -EPROBE_DEFER)
+			goto fail;
+	}
+
+	/* The core clock is synchronous with graphics */
+	WARN_ON(clk_set_rate(ocmem->core_clk, 1000) < 0);
+
+	ocmem->mmio = msm_ioremap(pdev, "ocmem_ctrl_physical", "OCMEM");
+	if (IS_ERR(ocmem->mmio))
+		return PTR_ERR(ocmem->mmio);
+
+	ocmem->ocmem_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+			"ocmem_physical");
+	if (!ocmem->ocmem_mem) {
+		dev_err(dev, "could not get OCMEM region\n");
+		return -ENXIO;
+	}
+
+	ret = ocmem_clk_enable(ocmem);
+	if (ret)
+		goto fail;
+
+	if (qcom_scm_ocmem_secure_available()) {
+		dev_dbg(dev, "configuring scm\n");
+		ret = qcom_scm_ocmem_secure_cfg(0x5);
+		if (ret)
+			goto fail;
+	}
+
+	reg = ocmem_read(ocmem, REG_OCMEM_HW_PROFILE);
+	ocmem->num_ports = FIELD(reg, OCMEM_HW_PROFILE_NUM_PORTS);
+	ocmem->num_macros = FIELD(reg, OCMEM_HW_PROFILE_NUM_MACROS);
+	ocmem->interleaved = !!(reg & OCMEM_HW_PROFILE_INTERLEAVING);
+
+	num_banks = ocmem->num_ports / 2;
+	region_size = config->macro_size * num_banks;
+
+	dev_info(dev, "%u ports, %u regions, %u macros, %sinterleaved\n",
+			ocmem->num_ports, config->num_regions, ocmem->num_macros,
+			ocmem->interleaved ? "" : "not ");
+
+	ocmem->regions = devm_kcalloc(dev, config->num_regions,
+			sizeof(struct ocmem_region), GFP_KERNEL);
+	if (!ocmem->regions) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	for (i = 0; i < config->num_regions; i++) {
+		struct ocmem_region *region = &ocmem->regions[i];
+
+		if (WARN_ON(num_banks > ARRAY_SIZE(region->macro_state))) {
+			ret = -EINVAL;
+			goto fail;
+		}
+
+		region->mode = MODE_DEFAULT;
+		region->num_macros = num_banks;
+
+		if ((i == (config->num_regions - 1)) &&
+				(reg & OCMEM_HW_PROFILE_LAST_REGN_HALFSIZE)) {
+			region->macro_size = config->macro_size / 2;
+			region->region_size = region_size / 2;
+		} else {
+			region->macro_size = config->macro_size;
+			region->region_size = region_size;
+		}
+
+		for (j = 0; j < ARRAY_SIZE(region->macro_state); j++)
+			region->macro_state[j] = CLK_OFF;
+	}
+
+	return 0;
+
+fail:
+	dev_err(dev, "probe failed\n");
+	ocmem_dev_remove(pdev);
+	return ret;
+}
+
+static struct platform_driver ocmem_driver = {
+	.probe = ocmem_dev_probe,
+	.remove = ocmem_dev_remove,
+	.driver = {
+		.name = "ocmem",
+		.of_match_table = dt_match,
+	},
+};
+
+static bool ocmem_exists(void)
+{
+	struct device_driver *drv = &ocmem_driver.driver;
+	struct device *d;
+
+	d = bus_find_device(&platform_bus_type, NULL, drv,
+			(void *)platform_bus_type.match);
+	if (d) {
+		put_device(d);
+		return true;
+	}
+
+	return false;
+}
+
+void __init ocmem_register(void)
+{
+	platform_driver_register(&ocmem_driver);
+}
+
+void __exit ocmem_unregister(void)
+{
+	platform_driver_unregister(&ocmem_driver);
+}
diff --git a/drivers/gpu/drm/msm/ocmem/ocmem.h b/drivers/gpu/drm/msm/ocmem/ocmem.h
new file mode 100644
index 0000000..199be98
--- /dev/null
+++ b/drivers/gpu/drm/msm/ocmem/ocmem.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 Red Hat
+ * Author: Rob Clark <robdclark at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __OCMEM_H__
+#define __OCMEM_H__
+
+enum ocmem_client {
+	/* GMEM clients */
+	OCMEM_GRAPHICS = 0x0,
+	/* TCMEM clients */
+	OCMEM_VIDEO,
+	OCMEM_CAMERA,
+	/* Dummy Clients */
+	OCMEM_HP_AUDIO,
+	OCMEM_VOICE,
+	/* IMEM Clients */
+	OCMEM_LP_AUDIO,
+	OCMEM_SENSORS,
+	OCMEM_OTHER_OS,
+	OCMEM_CLIENT_MAX,
+};
+
+struct ocmem_buf {
+	unsigned long offset;
+	unsigned long addr;
+	unsigned long len;
+};
+
+struct ocmem_buf *ocmem_allocate(enum ocmem_client client, unsigned long size);
+void ocmem_free(enum ocmem_client client, struct ocmem_buf *buf);
+
+#endif /* __OCMEM_H__ */
-- 
2.4.3



More information about the dri-devel mailing list