[PATCH 5/6] soc: qcom: add OCMEM driver

Bjorn Andersson bjorn.andersson at linaro.org
Sun Jun 16 17:41:06 UTC 2019


On Sun 16 Jun 06:29 PDT 2019, Brian Masney wrote:

> From: Rob Clark <robdclark at gmail.com>
> 
> The OCMEM driver handles allocation and configuration of the On Chip
> MEMory that is present on some Snapdragon SoCs.
> 
> 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 at gmail.com>
> Co-developed-by: Brian Masney <masneyb at onstation.org>
> Signed-off-by: Brian Masney <masneyb at onstation.org>
> ---
> Changes since Rob's last version of this patch:
> https://patchwork.kernel.org/patch/7379801/
> - reformatted driver to allow multiple instances
> - updated logging of error paths during device probing
> - remove unused psgsc_ctrl
> - remove _clk from clock names
> - propagate error code from devm_ioremap_resource()
> - use device_get_match_data()
> - SPDX license tags
> - remove QCOM_SMD in Kconfig
> - select ARCH_QCOM in Kconfig
> - select QCOM_SCM in Kconfig
> - longer description in Kconfig
> 
>  drivers/soc/qcom/Kconfig     |  10 +
>  drivers/soc/qcom/Makefile    |   1 +
>  drivers/soc/qcom/ocmem.c     | 402 +++++++++++++++++++++++++++++++++++
>  drivers/soc/qcom/ocmem.xml.h |  86 ++++++++
>  include/soc/qcom/ocmem.h     |  34 +++
>  5 files changed, 533 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 880cf0290962..998d94d60a3c 100644
> --- a/drivers/soc/qcom/Kconfig
> +++ b/drivers/soc/qcom/Kconfig
> @@ -62,6 +62,16 @@ config QCOM_MDT_LOADER
>  	tristate
>  	select QCOM_SCM
>  
> +config QCOM_OCMEM
> +	tristate "Qualcomm On Chip Memory (OCMEM) driver"
> +	depends on ARCH_QCOM
> +	select QCOM_SCM
> +	help
> +          The On Chip Memory (OCMEM) allocator allows various clients to
> +          allocate memory from OCMEM based on performance, latency and power
> +          requirements. This is typically used by the GPU, camera/video, and
> +          audio components on some Snapdragon SoCs.
> +
>  config QCOM_PM
>  	bool "Qualcomm Power Management"
>  	depends on ARCH_QCOM && !ARM64
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index ffe519b0cb66..76ac469f548c 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -5,6 +5,7 @@ obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
>  obj-$(CONFIG_QCOM_GLINK_SSR) +=	glink_ssr.o
>  obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
>  obj-$(CONFIG_QCOM_MDT_LOADER)	+= mdt_loader.o
> +obj-$(CONFIG_QCOM_OCMEM)	+= ocmem.o
>  obj-$(CONFIG_QCOM_PM)	+=	spm.o
>  obj-$(CONFIG_QCOM_QMI_HELPERS)	+= qmi_helpers.o
>  qmi_helpers-y	+= qmi_encdec.o qmi_interface.o
> diff --git a/drivers/soc/qcom/ocmem.c b/drivers/soc/qcom/ocmem.c
> new file mode 100644
> index 000000000000..5ebf5031b6c5
> --- /dev/null
> +++ b/drivers/soc/qcom/ocmem.c
> @@ -0,0 +1,402 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2019 Brian Masney <masneyb at onstation.org>
> + * Copyright (C) 2015 Red Hat. Author: Rob Clark <robdclark at gmail.com>
> + */
> +
> +#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 {
> +	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 int num_ports;
> +	unsigned int num_macros;
> +	bool interleaved;
> +	struct ocmem_region *regions;
> +};
> +
> +#define FIELD(val, name) (((val) & name ## __MASK) >> name ## __SHIFT)

include/linux/bitfield.h has standard macros for this, please use that
instead.

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

Use clk_bulk_* instead, it will reduce the amount of duplication in the
three places you poke at your clocks.

And with that I would suggest that you just inline these into probe and
remove.

> +	if (ret) {
> +		dev_info(ocmem->dev, "Fail to enable core clk\n");
> +		return ret;
> +	}
> +
> +	ret = clk_prepare_enable(ocmem->iface_clk);
> +	if (ret) {
> +		dev_info(ocmem->dev, "Fail to enable iface clk\n");
> +		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)

Please move this below probe().

> +{
> +	struct ocmem *ocmem = platform_get_drvdata(pdev);
> +
> +	ocmem_clk_disable(ocmem);
> +
> +	return 0;
> +}
> +
> +static void update_ocmem(struct ocmem *ocmem)
> +{
> +	uint32_t region_mode_ctrl = 0x0;
> +	unsigned int pos = 0, i = 0;

Both pos and i are initialized before use, no need to do it here.

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

Just use i * 4 in the BIT() operation below.


But the generated macros has the "thin bits" as 1, 2, 4, 8; so shouldn't
this be BIT(i)?

> +			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];
> +		u32 data;
> +
> +		data = 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]);
> +
> +		ocmem_write(ocmem, REG_OCMEM_PSGSC_CTL(i), data);
> +	}
> +}
> +
> +static unsigned long phys_to_offset(struct ocmem *ocmem,
> +				    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(struct ocmem *ocmem,
> +				    enum ocmem_client client,
> +				    unsigned long addr)
> +{
> +	/* TODO, gpu uses phys_to_offset, but others do not.. */

Perhaps WARN_ON(client != OCMEM_GRAPHICS) as well?

> +	return phys_to_offset(ocmem, 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 *of_get_ocmem(struct device *dev)
> +{
> +	struct platform_device *pdev;
> +	struct device_node *devnode;
> +
> +	devnode = of_parse_phandle(dev->of_node, "ocmem", 0);
> +	if (!devnode) {
> +		dev_err(dev, "Cannot look up ocmem phandle\n");
> +		return NULL;

return ERR_PTR(-EINVAL);

> +	}
> +
> +	pdev = of_find_device_by_node(devnode);
> +	if (!pdev) {
> +		dev_err(dev, "Cannot find device node %s\n", devnode->name);
> +		return NULL;

return ERR_PTR(-EPROBE_DEFER)

> +	}
> +
> +	return platform_get_drvdata(pdev);
> +}
> +EXPORT_SYMBOL(of_get_ocmem);
> +
> +struct ocmem_buf *ocmem_allocate(struct ocmem *ocmem, enum ocmem_client client,
> +				 unsigned long size)
> +{
> +	struct ocmem_buf *buf;
> +
> +	buf = kzalloc(sizeof(*buf), GFP_KERNEL);
> +	if (!buf)
> +		return ERR_PTR(-ENOMEM);
> +

This is a very simple allocator... It would be nice to add the minimal
functionality of making sure that you only return successfully if
there's not already a live allocation.

But at least you should add a comment here stating the "limitation" on
the algorithm.

> +	buf->offset = 0;
> +	buf->addr = device_address(ocmem, 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) {

Isn't the lock_available case also graphcis only? Perhaps it's worth
swapping the inner and outer blocks.

> +			ocmem_write(ocmem, REG_OCMEM_GFX_MPU_START,
> +				    buf->offset);
> +			ocmem_write(ocmem, REG_OCMEM_GFX_MPU_END,
> +				    buf->offset + buf->len);
> +		}

And it's probably good to warn and fail if the client isn't graphics.

> +	}
> +
> +	return buf;
> +}
> +EXPORT_SYMBOL(ocmem_allocate);
> +
> +void ocmem_free(struct ocmem *ocmem, 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 int ocmem_dev_probe(struct platform_device *pdev)
> +{
> +	struct ocmem *ocmem;
> +	uint32_t reg, num_banks, region_size;
> +	struct device *dev = &pdev->dev;
> +	struct resource *res;
> +	int i, j, ret;
> +
> +	if (!qcom_scm_is_available())
> +		return -EPROBE_DEFER;
> +
> +	ocmem = devm_kzalloc(dev, sizeof(*ocmem), GFP_KERNEL);
> +	if (!ocmem)
> +		return -ENOMEM;
> +
> +	ocmem->dev = dev;
> +	ocmem->config = device_get_match_data(dev);
> +
> +	ocmem->core_clk = devm_clk_get(dev, "core");

devm_clk_bulk_get()

> +	if (IS_ERR(ocmem->core_clk)) {
> +		if (PTR_ERR(ocmem->core_clk) != -EPROBE_DEFER)
> +			dev_err(dev, "Unable to get the core clock\n");
> +
> +		return PTR_ERR(ocmem->core_clk);
> +	}
> +
> +	ocmem->iface_clk = devm_clk_get(dev, "iface");
> +	if (IS_ERR(ocmem->iface_clk)) {
> +		if (PTR_ERR(ocmem->iface_clk) != -EPROBE_DEFER)
> +			dev_err(dev, "Unable to get the iface clock\n");
> +
> +		return PTR_ERR(ocmem->iface_clk);
> +	}
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +					   "ocmem_ctrl_physical");

We know it's the "_physical", so drop that from the name.

> +	if (!res) {

It's idiomatic to ignore this check and rely on ioremap_resource() to
fail gracefully if passed NULL.

> +		dev_err(dev, "Could not get ocmem_ctrl_physical region\n");
> +		return -ENXIO;
> +	}
> +
> +	ocmem->mmio = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(ocmem->mmio)) {
> +		dev_err(&pdev->dev,
> +			"Failed to ioremap ocmem_ctrl_physical resource\n");
> +		return PTR_ERR(ocmem->mmio);
> +	}
> +
> +	ocmem->ocmem_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +							"ocmem_physical");
> +	if (!ocmem->ocmem_mem) {
> +		dev_err(dev, "Could not get ocmem_physical 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)
> +		return ret;
> +
> +	if (qcom_scm_restore_sec_config_available()) {
> +		dev_dbg(dev, "configuring scm\n");
> +		ret = qcom_scm_restore_sec_config(&pdev->dev,
> +						  QCOM_SCM_OCMEM_DEV_ID);
> +		if (ret) {
> +			dev_err(dev, "Could not enable secure configuration\n");
> +			goto err_clk_disable;
> +		}
> +	}
> +
> +	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 = ocmem->config->macro_size * num_banks;
> +
> +	dev_info(dev, "%u ports, %u regions, %u macros, %sinterleaved\n",
> +		 ocmem->num_ports, ocmem->config->num_regions,
> +		 ocmem->num_macros, ocmem->interleaved ? "" : "not ");
> +
> +	ocmem->regions = devm_kcalloc(dev, ocmem->config->num_regions,
> +				      sizeof(struct ocmem_region), GFP_KERNEL);
> +	if (!ocmem->regions) {
> +		ret = -ENOMEM;
> +		goto err_clk_disable;
> +	}
> +
> +	for (i = 0; i < ocmem->config->num_regions; i++) {
> +		struct ocmem_region *region = &ocmem->regions[i];
> +
> +		if (WARN_ON(num_banks > ARRAY_SIZE(region->macro_state))) {
> +			ret = -EINVAL;
> +			goto err_clk_disable;
> +		}
> +
> +		region->mode = MODE_DEFAULT;
> +		region->num_macros = num_banks;
> +
> +		if (i == (ocmem->config->num_regions - 1) &&
> +		    reg & OCMEM_HW_PROFILE_LAST_REGN_HALFSIZE) {
> +			region->macro_size = ocmem->config->macro_size / 2;
> +			region->region_size = region_size / 2;
> +		} else {
> +			region->macro_size = ocmem->config->macro_size;
> +			region->region_size = region_size;
> +		}
> +
> +		for (j = 0; j < ARRAY_SIZE(region->macro_state); j++)
> +			region->macro_state[j] = CLK_OFF;
> +	}
> +
> +	platform_set_drvdata(pdev, ocmem);
> +
> +	return 0;
> +
> +err_clk_disable:
> +	ocmem_clk_disable(ocmem);
> +	return ret;
> +}
> +
> +static const struct ocmem_config ocmem_8974_config = {
> +	.num_regions = 3,
> +	.macro_size = SZ_128K,
> +};
> +
> +static const struct of_device_id ocmem_of_match[] = {
> +	{ .compatible = "qcom,ocmem-msm8974", .data = &ocmem_8974_config },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, ocmem_of_match);
> +
> +static struct platform_driver ocmem_driver = {
> +	.probe = ocmem_dev_probe,
> +	.remove = ocmem_dev_remove,
> +	.driver = {
> +		.name = "ocmem",
> +		.of_match_table = ocmem_of_match,
> +	},
> +};
> +
> +module_platform_driver(ocmem_driver);

MODULE_LICENSE()

> diff --git a/drivers/soc/qcom/ocmem.xml.h b/drivers/soc/qcom/ocmem.xml.h

I would prefer that these lived at the top of the c file, rather than
being generated.

> new file mode 100644
> index 000000000000..b4bfb85d1e33
> --- /dev/null
> +++ b/drivers/soc/qcom/ocmem.xml.h
> @@ -0,0 +1,86 @@
> +/* SPDX-License-Identifier: MIT */
> +
> +#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 at gmail.com> (robclark)
> +*/
> +
> +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 000000000000..e56ce220096d
> --- /dev/null
> +++ b/include/soc/qcom/ocmem.h
> @@ -0,0 +1,34 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2015 Red Hat
> + * Author: Rob Clark <robdclark at gmail.com>
> + */
> +
> +#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;
> +
> +struct ocmem_buf {
> +	unsigned long offset;
> +	unsigned long addr;
> +	unsigned long len;
> +};
> +
> +struct ocmem *of_get_ocmem(struct device *dev);
> +struct ocmem_buf *ocmem_allocate(struct ocmem *ocmem, enum ocmem_client client,
> +				 unsigned long size);
> +void ocmem_free(struct ocmem *ocmem, enum ocmem_client client,
> +		struct ocmem_buf *buf);
> +
> +#endif /* __OCMEM_H__ */
> -- 

Regards,
Bjorn


More information about the dri-devel mailing list