git-subtree-dir: qcom/opensource/graphics-kernel git-subtree-mainline:992813d9c1
git-subtree-split:b4fdc4c042
Change-Id: repo: https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/graphics-kernel tag: GRAPHICS.LA.14.0.r1-07700-lanai.0
338 lines
8.5 KiB
C
338 lines
8.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include "kgsl_regmap.h"
|
|
#include "kgsl_trace.h"
|
|
|
|
#define region_addr(region, _offset) \
|
|
((region)->virt + (((_offset) - (region)->offset) << 2))
|
|
|
|
static int kgsl_regmap_init_region(struct kgsl_regmap *regmap,
|
|
struct platform_device *pdev,
|
|
struct kgsl_regmap_region *region,
|
|
struct resource *res, const struct kgsl_regmap_ops *ops,
|
|
void *priv)
|
|
{
|
|
void __iomem *ptr;
|
|
|
|
ptr = devm_ioremap(&pdev->dev, res->start, resource_size(res));
|
|
if (!ptr)
|
|
return -ENOMEM;
|
|
|
|
region->virt = ptr;
|
|
region->offset = (res->start - regmap->base->start) >> 2;
|
|
region->size = resource_size(res) >> 2;
|
|
region->ops = ops;
|
|
region->priv = priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Initialize the regmap with the base region. All added regions will be offset
|
|
* from this base
|
|
*/
|
|
int kgsl_regmap_init(struct platform_device *pdev, struct kgsl_regmap *regmap,
|
|
const char *name, const struct kgsl_regmap_ops *ops,
|
|
void *priv)
|
|
{
|
|
struct kgsl_regmap_region *region;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
|
|
if (!res)
|
|
return -ENODEV;
|
|
|
|
regmap->base = res;
|
|
|
|
region = ®map->region[0];
|
|
ret = kgsl_regmap_init_region(regmap, pdev, region, res, ops, priv);
|
|
|
|
if (!ret)
|
|
regmap->count = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Add a new region to the regmap */
|
|
int kgsl_regmap_add_region(struct kgsl_regmap *regmap, struct platform_device *pdev,
|
|
const char *name, const struct kgsl_regmap_ops *ops, void *priv)
|
|
{
|
|
struct kgsl_regmap_region *region;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
|
|
if (!res)
|
|
return -ENODEV;
|
|
|
|
if (WARN_ON(regmap->count >= ARRAY_SIZE(regmap->region)))
|
|
return -ENODEV;
|
|
|
|
region = ®map->region[regmap->count];
|
|
|
|
ret = kgsl_regmap_init_region(regmap, pdev, region, res, ops, priv);
|
|
if (!ret)
|
|
regmap->count++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define in_range(a, base, len) \
|
|
(((a) >= (base)) && ((a) < ((base) + (len))))
|
|
|
|
struct kgsl_regmap_region *kgsl_regmap_get_region(struct kgsl_regmap *regmap,
|
|
u32 offset)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < regmap->count; i++) {
|
|
struct kgsl_regmap_region *region = ®map->region[i];
|
|
|
|
if (in_range(offset, region->offset, region->size))
|
|
return region;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool kgsl_regmap_valid_offset(struct kgsl_regmap *regmap, u32 offset)
|
|
{
|
|
if (kgsl_regmap_get_region(regmap, offset))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
u32 kgsl_regmap_read(struct kgsl_regmap *regmap, u32 offset)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, offset);
|
|
u32 val;
|
|
|
|
if (WARN(!region, "Out of bounds register read offset: 0x%x\n", offset))
|
|
return 0;
|
|
|
|
if (region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
val = readl_relaxed(region_addr(region, offset));
|
|
/* Allow previous read to post before returning the value */
|
|
rmb();
|
|
|
|
return val;
|
|
}
|
|
|
|
void kgsl_regmap_write(struct kgsl_regmap *regmap, u32 value, u32 offset)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, offset);
|
|
|
|
if (WARN(!region, "Out of bounds register write offset: 0x%x\n", offset))
|
|
return;
|
|
|
|
if (region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
/* Make sure all pending writes have posted first */
|
|
wmb();
|
|
writel_relaxed(value, region_addr(region, offset));
|
|
|
|
trace_kgsl_regwrite(offset, value);
|
|
}
|
|
|
|
void kgsl_regmap_multi_write(struct kgsl_regmap *regmap,
|
|
const struct kgsl_regmap_list *list, int count)
|
|
{
|
|
struct kgsl_regmap_region *region, *prev = NULL;
|
|
int i;
|
|
|
|
/*
|
|
* do one write barrier to ensure all previous writes are done before
|
|
* starting the list
|
|
*/
|
|
wmb();
|
|
|
|
for (i = 0; i < count; i++) {
|
|
region = kgsl_regmap_get_region(regmap, list[i].offset);
|
|
|
|
if (WARN(!region, "Out of bounds register write offset: 0x%x\n",
|
|
list[i].offset))
|
|
continue;
|
|
|
|
/*
|
|
* The registers might be in different regions. If a region has
|
|
* a preaccess function we need to call it at least once before
|
|
* writing registers but we don't want to call it every time if
|
|
* we can avoid it. "cache" the current region and don't call
|
|
* pre-access if it is the same region from the previous access.
|
|
* This isn't perfect but it should cut down on some unneeded
|
|
* cpu cycles
|
|
*/
|
|
|
|
if (region != prev && region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
prev = region;
|
|
|
|
writel_relaxed(list[i].val, region_addr(region, list[i].offset));
|
|
trace_kgsl_regwrite(list[i].offset, list[i].val);
|
|
}
|
|
}
|
|
|
|
void kgsl_regmap_rmw(struct kgsl_regmap *regmap, u32 offset, u32 mask,
|
|
u32 or)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, offset);
|
|
u32 val;
|
|
|
|
if (WARN(!region, "Out of bounds register read-modify-write offset: 0x%x\n",
|
|
offset))
|
|
return;
|
|
|
|
if (region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
val = readl_relaxed(region_addr(region, offset));
|
|
/* Make sure the read posted and all pending writes are done */
|
|
mb();
|
|
writel_relaxed((val & ~mask) | or, region_addr(region, offset));
|
|
|
|
trace_kgsl_regwrite(offset, (val & ~mask) | or);
|
|
}
|
|
|
|
void kgsl_regmap_bulk_write(struct kgsl_regmap *regmap, u32 offset,
|
|
const void *data, int dwords)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, offset);
|
|
|
|
if (WARN(!region, "Out of bounds register bulk write offset: 0x%x\n", offset))
|
|
return;
|
|
|
|
if (region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
/*
|
|
* A bulk write operation can only be in one region - it cannot
|
|
* cross boundaries
|
|
*/
|
|
if (WARN((offset - region->offset) + dwords > region->size,
|
|
"OUt of bounds bulk write size: 0x%x\n", offset + dwords))
|
|
return;
|
|
|
|
/* Make sure all pending write are done first */
|
|
wmb();
|
|
memcpy_toio(region_addr(region, offset), data, dwords << 2);
|
|
}
|
|
|
|
void kgsl_regmap_bulk_read(struct kgsl_regmap *regmap, u32 offset,
|
|
const void *data, int dwords)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, offset);
|
|
|
|
if (WARN(!region, "Out of bounds register bulk read offset: 0x%x\n", offset))
|
|
return;
|
|
|
|
if (region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
/*
|
|
* A bulk read operation can only be in one region - it cannot
|
|
* cross boundaries
|
|
*/
|
|
if (WARN((offset - region->offset) + dwords > region->size,
|
|
"Out of bounds bulk read size: 0x%x\n", offset + dwords))
|
|
return;
|
|
|
|
memcpy_fromio(region_addr(region, offset), data, dwords << 2);
|
|
|
|
/* Make sure the copy is finished before moving on */
|
|
rmb();
|
|
}
|
|
|
|
void __iomem *kgsl_regmap_virt(struct kgsl_regmap *regmap, u32 offset)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, offset);
|
|
|
|
if (region)
|
|
return region_addr(region, offset);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void kgsl_regmap_read_indexed(struct kgsl_regmap *regmap, u32 addr,
|
|
u32 data, u32 *dest, int count)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, addr);
|
|
int i;
|
|
|
|
if (!region)
|
|
return;
|
|
|
|
/* Make sure the offset is in the same region */
|
|
if (kgsl_regmap_get_region(regmap, data) != region)
|
|
return;
|
|
|
|
if (region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
/* Write the address register */
|
|
writel_relaxed(0, region_addr(region, addr));
|
|
|
|
/* Make sure the write finishes */
|
|
wmb();
|
|
|
|
for (i = 0; i < count; i++)
|
|
dest[i] = readl_relaxed(region_addr(region, data));
|
|
|
|
/* Do one barrier at the end to make sure all the data is posted */
|
|
rmb();
|
|
}
|
|
|
|
void kgsl_regmap_read_indexed_interleaved(struct kgsl_regmap *regmap, u32 addr,
|
|
u32 data, u32 *dest, u32 start, int count)
|
|
{
|
|
struct kgsl_regmap_region *region = kgsl_regmap_get_region(regmap, addr);
|
|
int i;
|
|
|
|
if (!region)
|
|
return;
|
|
|
|
/* Make sure the offset is in the same region */
|
|
if (kgsl_regmap_get_region(regmap, data) != region)
|
|
return;
|
|
|
|
if (region->ops && region->ops->preaccess)
|
|
region->ops->preaccess(region);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
/* Write the address register */
|
|
writel_relaxed(start + i, region_addr(region, addr));
|
|
/* Make sure the write finishes */
|
|
wmb();
|
|
|
|
dest[i] = readl_relaxed(region_addr(region, data));
|
|
/* Make sure the read finishes */
|
|
rmb();
|
|
}
|
|
}
|
|
|
|
/* A special helper function to work with read_poll_timeout */
|
|
int kgsl_regmap_poll_read(struct kgsl_regmap_region *region, u32 offset,
|
|
u32 *val)
|
|
{
|
|
/* FIXME: WARN on !region? */
|
|
if (WARN(!region, "Out of bounds poll read: 0x%x\n", offset))
|
|
return -ENODEV;
|
|
|
|
*val = readl_relaxed(region_addr(region, offset));
|
|
/* Make sure the read is finished before moving on */
|
|
rmb();
|
|
|
|
return 0;
|
|
}
|