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 |  17 +-
 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     | 396 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/ocmem/ocmem.h     |  46 ++++
 7 files changed, 459 insertions(+), 27 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..29bbb80 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)
                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..d3cdd64
--- /dev/null
+++ b/drivers/gpu/drm/msm/ocmem/ocmem.c
@@ -0,0 +1,396 @@
+/*
+ * 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/kernel.h>
+#include <linux/cpuset.h>
+#include <linux/qcom_scm.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;
+};
+
+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;
+
+       if (ocmem->iface_clk) {
+               ret = clk_prepare_enable(ocmem->iface_clk);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void ocmem_clk_disable(struct ocmem *ocmem)
+{
+       if (ocmem->iface_clk)
+               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);
+               /* Barrier to commit the region mode */
+               mb();
+       }
+
+       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]));
+       }
+}
+
+inline 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);
+
+       /*
+        * 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 lock: %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,msm-ocmem-8974", .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;
+       int i, j, ret;
+
+       struct device_node *of_node = dev->of_node;
+       const struct of_device_id *match;
+
+       match = of_match_node(dt_match, dev->of_node);
+       if (match)
+               config = match->data;
+
+       if (!config) {
+               dev_err(dev, "unknown config: %s\n", 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))
+               ocmem->iface_clk = NULL;
+
+       /* 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_is_available() && qcom_scm_ocmem_secure_available()) {
+               dev_info(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_kzalloc(dev, sizeof(struct ocmem_region) *
+                       config->num_regions, 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

Reply via email to