The OCMEM driver handles allocation and configuration of the On Chip MEMory that is present on some snapdragon devices. Devices which have OCMEM do not have GMEM inside the gpu core, so the gpu must instead use OCMEM to be functional. Since currently the gpu is the only OCMEM user with an upstream driver, this is just a minimal implementation sufficient for statically allocating to the gpu it's chunk of OCMEM. Signed-off-by: Rob Clark <robdclark@xxxxxxxxx> --- drivers/soc/qcom/Kconfig | 6 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/ocmem.c | 408 +++++++++++++++++++++++++++++++++++++++++++ drivers/soc/qcom/ocmem.xml.h | 106 +++++++++++ include/soc/qcom/ocmem.h | 40 +++++ 5 files changed, 561 insertions(+) create mode 100644 drivers/soc/qcom/ocmem.c create mode 100644 drivers/soc/qcom/ocmem.xml.h create mode 100644 include/soc/qcom/ocmem.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 266060b..b485043 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -74,3 +74,9 @@ config QCOM_WCNSS_CTRL help Client driver for the WCNSS_CTRL SMD channel, used to download nv firmware to a newly booted WCNSS chip. + +config QCOM_OCMEM + tristate "Qualcomm OCMEM driver" + depends on QCOM_SMD + help + Allocator for OCMEM (On Chip MEMory). diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index fdd664e..940bb35 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o +obj-$(CONFIG_QCOM_OCMEM) += ocmem.o diff --git a/drivers/soc/qcom/ocmem.c b/drivers/soc/qcom/ocmem.c new file mode 100644 index 0000000..2628aaf --- /dev/null +++ b/drivers/soc/qcom/ocmem.c @@ -0,0 +1,408 @@ +/* + * 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/clk.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/qcom_scm.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <soc/qcom/ocmem.h> +#include "ocmem.xml.h" + +enum region_mode { + WIDE_MODE = 0x0, + THIN_MODE, + MODE_DEFAULT = WIDE_MODE, +}; + +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; +}; + +#define FIELD(val, name) (((val) & name ## __MASK) >> name ## __SHIFT) + +static struct ocmem *ocmem; + +static bool ocmem_exists(void); + +static inline void ocmem_write(struct ocmem *ocmem, u32 reg, u32 data) +{ + writel(data, ocmem->mmio + reg); +} + +static inline u32 ocmem_read(struct ocmem *ocmem, u32 reg) +{ + return 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(QCOM_SCM_OCMEM_GRAPHICS_ID, + 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; +} +EXPORT_SYMBOL(ocmem_allocate); + +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(QCOM_SCM_OCMEM_GRAPHICS_ID, + 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); +} +EXPORT_SYMBOL(ocmem_free); + +static const struct ocmem_config ocmem_8974_config = { + .num_regions = 3, .macro_size = SZ_128K, +}; + +static const struct of_device_id ocmem_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; + const struct of_device_id *match; + struct resource *res; + uint32_t reg, num_banks, region_size; + int i, j, ret; + + /* we need scm to be available: */ + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + match = of_match_device(ocmem_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; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "ocmem_ctrl_physical"); + if (!res) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + ret = -EINVAL; + goto fail; + } + + ocmem->mmio = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (IS_ERR(ocmem->mmio)) { + dev_err(&pdev->dev, "failed to ioremap memory resource\n"); + ret = -EINVAL; + goto fail; + } + + 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; + } + + /* The core clock is synchronous with graphics */ + WARN_ON(clk_set_rate(ocmem->core_clk, 1000) < 0); + + ret = ocmem_clk_enable(ocmem); + if (ret) + goto fail; + + if (qcom_scm_restore_sec_config_available()) { + dev_dbg(dev, "configuring scm\n"); + ret = qcom_scm_restore_sec_config(QCOM_SCM_OCMEM_DEV_ID); + 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; +} + +MODULE_DEVICE_TABLE(of, ocmem_dt_match); + +static struct platform_driver ocmem_driver = { + .probe = ocmem_dev_probe, + .remove = ocmem_dev_remove, + .driver = { + .name = "ocmem", + .of_match_table = ocmem_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; +} + +module_platform_driver(ocmem_driver); diff --git a/drivers/soc/qcom/ocmem.xml.h b/drivers/soc/qcom/ocmem.xml.h new file mode 100644 index 0000000..51c12d5 --- /dev/null +++ b/drivers/soc/qcom/ocmem.xml.h @@ -0,0 +1,106 @@ +#ifndef OCMEM_XML +#define OCMEM_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://github.com/freedreno/envytools/ +git clone https://github.com/freedreno/envytools.git + +The rules-ng-ng source files this header was generated from are: +- /home/robclark/src/freedreno/envytools/rnndb/adreno/ocmem.xml ( 1773 bytes, from 2015-09-24 17:30:00) + +Copyright (C) 2013-2015 by the following authors: +- Rob Clark <robdclark@xxxxxxxxx> (robclark) + +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, sublicense, 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 NONINFRINGEMENT. +IN NO EVENT SHALL THE COPYRIGHT OWNER(S) 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. +*/ + + +enum ocmem_macro_state { + PASSTHROUGH = 0, + PERI_ON = 1, + CORE_ON = 2, + CLK_OFF = 4, +}; + +#define REG_OCMEM_HW_VERSION 0x00000000 + +#define REG_OCMEM_HW_PROFILE 0x00000004 +#define OCMEM_HW_PROFILE_NUM_PORTS__MASK 0x0000000f +#define OCMEM_HW_PROFILE_NUM_PORTS__SHIFT 0 +static inline uint32_t OCMEM_HW_PROFILE_NUM_PORTS(uint32_t val) +{ + return ((val) << OCMEM_HW_PROFILE_NUM_PORTS__SHIFT) & OCMEM_HW_PROFILE_NUM_PORTS__MASK; +} +#define OCMEM_HW_PROFILE_NUM_MACROS__MASK 0x00003f00 +#define OCMEM_HW_PROFILE_NUM_MACROS__SHIFT 8 +static inline uint32_t OCMEM_HW_PROFILE_NUM_MACROS(uint32_t val) +{ + return ((val) << OCMEM_HW_PROFILE_NUM_MACROS__SHIFT) & OCMEM_HW_PROFILE_NUM_MACROS__MASK; +} +#define OCMEM_HW_PROFILE_LAST_REGN_HALFSIZE 0x00010000 +#define OCMEM_HW_PROFILE_INTERLEAVING 0x00020000 + +#define REG_OCMEM_GEN_STATUS 0x0000000c + +#define REG_OCMEM_PSGSC_STATUS 0x00000038 + +static inline uint32_t REG_OCMEM_PSGSC(uint32_t i0) { return 0x0000003c + 0x1*i0; } + +static inline uint32_t REG_OCMEM_PSGSC_CTL(uint32_t i0) { return 0x0000003c + 0x1*i0; } +#define OCMEM_PSGSC_CTL_MACRO0_MODE__MASK 0x00000007 +#define OCMEM_PSGSC_CTL_MACRO0_MODE__SHIFT 0 +static inline uint32_t OCMEM_PSGSC_CTL_MACRO0_MODE(enum ocmem_macro_state val) +{ + return ((val) << OCMEM_PSGSC_CTL_MACRO0_MODE__SHIFT) & OCMEM_PSGSC_CTL_MACRO0_MODE__MASK; +} +#define OCMEM_PSGSC_CTL_MACRO1_MODE__MASK 0x00000070 +#define OCMEM_PSGSC_CTL_MACRO1_MODE__SHIFT 4 +static inline uint32_t OCMEM_PSGSC_CTL_MACRO1_MODE(enum ocmem_macro_state val) +{ + return ((val) << OCMEM_PSGSC_CTL_MACRO1_MODE__SHIFT) & OCMEM_PSGSC_CTL_MACRO1_MODE__MASK; +} +#define OCMEM_PSGSC_CTL_MACRO2_MODE__MASK 0x00000700 +#define OCMEM_PSGSC_CTL_MACRO2_MODE__SHIFT 8 +static inline uint32_t OCMEM_PSGSC_CTL_MACRO2_MODE(enum ocmem_macro_state val) +{ + return ((val) << OCMEM_PSGSC_CTL_MACRO2_MODE__SHIFT) & OCMEM_PSGSC_CTL_MACRO2_MODE__MASK; +} +#define OCMEM_PSGSC_CTL_MACRO3_MODE__MASK 0x00007000 +#define OCMEM_PSGSC_CTL_MACRO3_MODE__SHIFT 12 +static inline uint32_t OCMEM_PSGSC_CTL_MACRO3_MODE(enum ocmem_macro_state val) +{ + return ((val) << OCMEM_PSGSC_CTL_MACRO3_MODE__SHIFT) & OCMEM_PSGSC_CTL_MACRO3_MODE__MASK; +} + +#define REG_OCMEM_REGION_MODE_CTL 0x00001000 +#define OCMEM_REGION_MODE_CTL_REG0_THIN 0x00000001 +#define OCMEM_REGION_MODE_CTL_REG1_THIN 0x00000002 +#define OCMEM_REGION_MODE_CTL_REG2_THIN 0x00000004 +#define OCMEM_REGION_MODE_CTL_REG3_THIN 0x00000008 + +#define REG_OCMEM_GFX_MPU_START 0x00001004 + +#define REG_OCMEM_GFX_MPU_END 0x00001008 + + +#endif /* OCMEM_XML */ diff --git a/include/soc/qcom/ocmem.h b/include/soc/qcom/ocmem.h new file mode 100644 index 0000000..47f6548 --- /dev/null +++ b/include/soc/qcom/ocmem.h @@ -0,0 +1,40 @@ +/* + * 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, + /* + * TODO add more once ocmem_allocate() is clever enough to + * deal with multiple clients. + */ + 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