[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