| // 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].val, list[i].offset); |
| } |
| } |
| |
| 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; |
| } |