All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] soc/qcom: add OCMEM driver
@ 2015-10-12 21:45 Rob Clark
  2015-10-23 12:11 ` Stanimir Varbanov
  0 siblings, 1 reply; 8+ messages in thread
From: Rob Clark @ 2015-10-12 21:45 UTC (permalink / raw)
  To: linux-arm-msm; +Cc: Stephen Boyd, Bjorn Andersson, Stanimir Varbanov, Rob Clark

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.

v2: minor updates from review comments (use devm_ioremap_resource(),
devm_clk_get() doesn't return NULL, etc..)

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/soc/qcom/Kconfig     |   6 +
 drivers/soc/qcom/Makefile    |   1 +
 drivers/soc/qcom/ocmem.c     | 401 +++++++++++++++++++++++++++++++++++++++++++
 drivers/soc/qcom/ocmem.xml.h | 106 ++++++++++++
 include/soc/qcom/ocmem.h     |  40 +++++
 5 files changed, 554 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..506a072
--- /dev/null
+++ b/drivers/soc/qcom/ocmem.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2015 Red Hat
+ * Author: Rob Clark <robdclark@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/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_info(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(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");
+	ocmem->mmio = devm_ioremap_resource(&pdev->dev, 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@gmail.com> (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@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,
+	/*
+	 * 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.5.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH] soc/qcom: add OCMEM driver
  2015-10-12 21:45 [PATCH] soc/qcom: add OCMEM driver Rob Clark
@ 2015-10-23 12:11 ` Stanimir Varbanov
  2015-10-23 17:52   ` Bryan Huntsman
  2015-10-23 19:05   ` Rob Clark
  0 siblings, 2 replies; 8+ messages in thread
From: Stanimir Varbanov @ 2015-10-23 12:11 UTC (permalink / raw)
  To: Rob Clark, linux-arm-msm; +Cc: Stephen Boyd, Bjorn Andersson

Hi Rob,

On 10/13/2015 12:45 AM, Rob Clark wrote:
> 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.
> 
> v2: minor updates from review comments (use devm_ioremap_resource(),
> devm_clk_get() doesn't return NULL, etc..)
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/soc/qcom/Kconfig     |   6 +
>  drivers/soc/qcom/Makefile    |   1 +
>  drivers/soc/qcom/ocmem.c     | 401 +++++++++++++++++++++++++++++++++++++++++++
>  drivers/soc/qcom/ocmem.xml.h | 106 ++++++++++++
>  include/soc/qcom/ocmem.h     |  40 +++++
>  5 files changed, 554 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

Why this driver depends on SMD, I cannot see where smd is used here.

Maybe depends on ARCH_QCOM should be sufficient?

> +	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..506a072
> --- /dev/null
> +++ b/drivers/soc/qcom/ocmem.c
> @@ -0,0 +1,401 @@
> +/*
> + * Copyright (C) 2015 Red Hat
> + * Author: Rob Clark <robdclark@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/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>

Do we need to update the Kconfig to enable/depend on scm driver?

> +#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;

this is not used

> +	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;
> +};
> +

<snip>

> +
> +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");

We have a convention to avoid _clk suffix for qcom clocks in dt nodes.

> +	if (IS_ERR(ocmem->core_clk)) {
> +		dev_info(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(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");
> +	ocmem->mmio = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(ocmem->mmio)) {
> +		dev_err(&pdev->dev, "failed to ioremap memory resource\n");
> +		ret = -EINVAL;

ret = PTR_ERR(ocmem->mmio); ?

> +		goto fail;
> +	}
> +

<snip>

> +
> +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;
> +}

do you have a dt binding document? I think that ocmem dt client nodes
should have a phandle to ocmem dt node and this could eliminate the need
of ocmem_exist(), no ?


<snip>

-- 
regards,
Stan

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] soc/qcom: add OCMEM driver
  2015-10-23 12:11 ` Stanimir Varbanov
@ 2015-10-23 17:52   ` Bryan Huntsman
  2015-10-23 19:05   ` Rob Clark
  1 sibling, 0 replies; 8+ messages in thread
From: Bryan Huntsman @ 2015-10-23 17:52 UTC (permalink / raw)
  To: Stanimir Varbanov, Rob Clark, linux-arm-msm; +Cc: Stephen Boyd, Bjorn Andersson

On 10/23/2015 05:11 AM, Stanimir Varbanov wrote:
> Hi Rob,
...
>> 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
> 
> Why this driver depends on SMD, I cannot see where smd is used here.
> 
> Maybe depends on ARCH_QCOM should be sufficient?

AFAIK, Stan is correct.  OCMEM should only be used by GPU w/o any of the
SMD stuff, so ARCH_QCOM looks better.

- Bryan

-- 
Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] soc/qcom: add OCMEM driver
  2015-10-23 12:11 ` Stanimir Varbanov
  2015-10-23 17:52   ` Bryan Huntsman
@ 2015-10-23 19:05   ` Rob Clark
  1 sibling, 0 replies; 8+ messages in thread
From: Rob Clark @ 2015-10-23 19:05 UTC (permalink / raw)
  To: Stanimir Varbanov; +Cc: linux-arm-msm, Stephen Boyd, Bjorn Andersson

On Fri, Oct 23, 2015 at 8:11 AM, Stanimir Varbanov <svarbanov@mm-sol.com> wrote:
> Hi Rob,
>
> On 10/13/2015 12:45 AM, Rob Clark wrote:
>> 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.
>>
>> v2: minor updates from review comments (use devm_ioremap_resource(),
>> devm_clk_get() doesn't return NULL, etc..)
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/soc/qcom/Kconfig     |   6 +
>>  drivers/soc/qcom/Makefile    |   1 +
>>  drivers/soc/qcom/ocmem.c     | 401 +++++++++++++++++++++++++++++++++++++++++++
>>  drivers/soc/qcom/ocmem.xml.h | 106 ++++++++++++
>>  include/soc/qcom/ocmem.h     |  40 +++++
>>  5 files changed, 554 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
>
> Why this driver depends on SMD, I cannot see where smd is used here.
>
> Maybe depends on ARCH_QCOM should be sufficient?

yeah, that should have been "SCM".. the acronyms are too close together ;-)

>
>> +     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..506a072
>> --- /dev/null
>> +++ b/drivers/soc/qcom/ocmem.c
>> @@ -0,0 +1,401 @@
>> +/*
>> + * Copyright (C) 2015 Red Hat
>> + * Author: Rob Clark <robdclark@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/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>
>
> Do we need to update the Kconfig to enable/depend on scm driver?
>
>> +#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;
>
> this is not used
>
>> +     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;
>> +};
>> +
>
> <snip>
>
>> +
>> +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");
>
> We have a convention to avoid _clk suffix for qcom clocks in dt nodes.
>
>> +     if (IS_ERR(ocmem->core_clk)) {
>> +             dev_info(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(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");
>> +     ocmem->mmio = devm_ioremap_resource(&pdev->dev, res);
>> +     if (IS_ERR(ocmem->mmio)) {
>> +             dev_err(&pdev->dev, "failed to ioremap memory resource\n");
>> +             ret = -EINVAL;
>
> ret = PTR_ERR(ocmem->mmio); ?
>
>> +             goto fail;
>> +     }
>> +
>
> <snip>
>
>> +
>> +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;
>> +}
>
> do you have a dt binding document? I think that ocmem dt client nodes
> should have a phandle to ocmem dt node and this could eliminate the need
> of ocmem_exist(), no ?

so far just generic bindings (ie. compatible/clocks/reg) so I didn't
write one..  are thinks which don't introduce any custom properties
supposed to be documented?

Not sure about the phandle link.. I didn't think it influenced probe
order (and if another ocmem client needed to access it from it's
probe()...)

BR,
-R

>
> <snip>
>
> --
> regards,
> Stan

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] soc/qcom: add OCMEM driver
  2015-10-06  8:35 ` Stanimir Varbanov
@ 2015-10-06 14:36   ` Rob Clark
  0 siblings, 0 replies; 8+ messages in thread
From: Rob Clark @ 2015-10-06 14:36 UTC (permalink / raw)
  To: Stanimir Varbanov; +Cc: linux-arm-msm, Stephen Boyd, Bjorn Andersson

On Tue, Oct 6, 2015 at 4:35 AM, Stanimir Varbanov <svarbanov@mm-sol.com> wrote:
> Hi Rob,
>
> <snip>
>
>> +
>> +struct ocmem_config {
>> +     uint8_t  num_regions;
>
> u8
>
>> +     uint32_t macro_size;
>
> u32

tbh, only place where stdint types are potentially an issue is in
uapi.  I changed the qcom-scm patches to use uN types instead since
the rest of the file was already using those (and when in Rome..)

but otherwise I prefer stdint types, since they are, well, standard..

>
>> +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 },
>
> Just a suggestion. As we have REG_OCMEM_HW_PROFILE and
> REG_OCMEM_HW_VERSION register why not distinguish by version and hw
> profile instead of SoC?

I take what I can from HW_PROFILE reg, but afaict macro_size and
num_regions is missing..

>> +     {}
>> +};
>> +
>> +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");
>
> this dev_err will flood console in EPROBE_DEFER case, IMO should be removed.

It is kind of nice to have some hints when debugging probe-defer or
fail to load cases.. I guess I could change that to dev_info() for the
probe-defer case..

>> +             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;
>
> Why error path of the iface clock is different comparing with core clk.

I guess there are some cases where there is no iface_clk?  This is
just based on what downstream driver was doing, which is the only
documentation I have ;-)

If someone knows better, I'd be happy to change it..

> Also I failed to found a case when common clk framework will return NULL?
>
> if (IS_ERR(ocmem->iface_clk)
>         return PTR_ERR(ocmem->iface_clk);
>
> this should be enough.

ok

>> +     }
>> +
>> +     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));
>
> I think you could use following to simplify ioremap:
>
>         res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>                         "ocmem_ctrl_physical");
>         ocmem->mmio = devm_ioremap_resource(dev, res);
>         if (IS_ERR(ocmem->mmio))
>                 return PTR_ERR(ocmem->mmio);

sure

BR,
-R


>> +     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;
>> +     }
>> +
>
> <snip>
>
> --
> regards,
> Stan

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] soc/qcom: add OCMEM driver
  2015-10-05 15:44 Rob Clark
  2015-10-05 15:46 ` Rob Clark
@ 2015-10-06  8:35 ` Stanimir Varbanov
  2015-10-06 14:36   ` Rob Clark
  1 sibling, 1 reply; 8+ messages in thread
From: Stanimir Varbanov @ 2015-10-06  8:35 UTC (permalink / raw)
  To: Rob Clark, linux-arm-msm; +Cc: Stephen Boyd, Bjorn Andersson

Hi Rob,

<snip>

> +
> +struct ocmem_config {
> +	uint8_t  num_regions;

u8

> +	uint32_t macro_size;

u32


> +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 },

Just a suggestion. As we have REG_OCMEM_HW_PROFILE and
REG_OCMEM_HW_VERSION register why not distinguish by version and hw
profile instead of SoC?

> +	{}
> +};
> +
> +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");

this dev_err will flood console in EPROBE_DEFER case, IMO should be removed.

> +		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;

Why error path of the iface clock is different comparing with core clk.
Also I failed to found a case when common clk framework will return NULL?

if (IS_ERR(ocmem->iface_clk)
	return PTR_ERR(ocmem->iface_clk);

this should be enough.

> +	}
> +
> +	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));

I think you could use following to simplify ioremap:

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
			"ocmem_ctrl_physical");
	ocmem->mmio = devm_ioremap_resource(dev, res);
	if (IS_ERR(ocmem->mmio))
		return PTR_ERR(ocmem->mmio);

> +	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;
> +	}
> +

<snip>

-- 
regards,
Stan

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] soc/qcom: add OCMEM driver
  2015-10-05 15:44 Rob Clark
@ 2015-10-05 15:46 ` Rob Clark
  2015-10-06  8:35 ` Stanimir Varbanov
  1 sibling, 0 replies; 8+ messages in thread
From: Rob Clark @ 2015-10-05 15:46 UTC (permalink / raw)
  To: linux-arm-msm; +Cc: Stephen Boyd, Bjorn Andersson, Rob Clark, Stanimir Varbanov

opps, +Stanimir

On Mon, Oct 5, 2015 at 11:44 AM, Rob Clark <robdclark@gmail.com> wrote:
> 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@gmail.com>
> ---
>  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@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/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@gmail.com> (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@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,
> +       /*
> +        * 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
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH] soc/qcom: add OCMEM driver
@ 2015-10-05 15:44 Rob Clark
  2015-10-05 15:46 ` Rob Clark
  2015-10-06  8:35 ` Stanimir Varbanov
  0 siblings, 2 replies; 8+ messages in thread
From: Rob Clark @ 2015-10-05 15:44 UTC (permalink / raw)
  To: linux-arm-msm; +Cc: Stephen Boyd, Bjorn Andersson, Rob Clark

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@gmail.com>
---
 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@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/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@gmail.com> (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@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,
+	/*
+	 * 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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2015-10-23 19:05 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-10-12 21:45 [PATCH] soc/qcom: add OCMEM driver Rob Clark
2015-10-23 12:11 ` Stanimir Varbanov
2015-10-23 17:52   ` Bryan Huntsman
2015-10-23 19:05   ` Rob Clark
  -- strict thread matches above, loose matches on Subject: below --
2015-10-05 15:44 Rob Clark
2015-10-05 15:46 ` Rob Clark
2015-10-06  8:35 ` Stanimir Varbanov
2015-10-06 14:36   ` Rob Clark

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.