soc/qcom: add OCMEM driver
diff mbox

Message ID 1444686342-19344-1-git-send-email-robdclark@gmail.com
State Deferred, archived
Delegated to: Andy Gross
Headers show

Commit Message

Rob Clark Oct. 12, 2015, 9:45 p.m. UTC
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

Comments

Stanimir Varbanov Oct. 23, 2015, 12:11 p.m. UTC | #1
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>
Bryan Huntsman Oct. 23, 2015, 5:52 p.m. UTC | #2
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
Rob Clark Oct. 23, 2015, 7:05 p.m. UTC | #3
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
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch
diff mbox

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__ */