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@xxxxxxxxx> --- 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@xxxxxxxxx> + * + * 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@xxxxxxxxx> + * + * 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 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html