162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2021 Intel Corporation
462306a36Sopenharmony_ci * Author: Johannes Berg <johannes@sipsolutions.net>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/types.h>
762306a36Sopenharmony_ci#include <linux/slab.h>
862306a36Sopenharmony_ci#include <linux/logic_iomem.h>
962306a36Sopenharmony_ci#include <asm/io.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_cistruct logic_iomem_region {
1262306a36Sopenharmony_ci	const struct resource *res;
1362306a36Sopenharmony_ci	const struct logic_iomem_region_ops *ops;
1462306a36Sopenharmony_ci	struct list_head list;
1562306a36Sopenharmony_ci};
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistruct logic_iomem_area {
1862306a36Sopenharmony_ci	const struct logic_iomem_ops *ops;
1962306a36Sopenharmony_ci	void *priv;
2062306a36Sopenharmony_ci};
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define AREA_SHIFT	24
2362306a36Sopenharmony_ci#define MAX_AREA_SIZE	(1 << AREA_SHIFT)
2462306a36Sopenharmony_ci#define MAX_AREAS	((1U << 31) / MAX_AREA_SIZE)
2562306a36Sopenharmony_ci#define AREA_BITS	((MAX_AREAS - 1) << AREA_SHIFT)
2662306a36Sopenharmony_ci#define AREA_MASK	(MAX_AREA_SIZE - 1)
2762306a36Sopenharmony_ci#ifdef CONFIG_64BIT
2862306a36Sopenharmony_ci#define IOREMAP_BIAS	0xDEAD000000000000UL
2962306a36Sopenharmony_ci#define IOREMAP_MASK	0xFFFFFFFF00000000UL
3062306a36Sopenharmony_ci#else
3162306a36Sopenharmony_ci#define IOREMAP_BIAS	0x80000000UL
3262306a36Sopenharmony_ci#define IOREMAP_MASK	0x80000000UL
3362306a36Sopenharmony_ci#endif
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic DEFINE_MUTEX(regions_mtx);
3662306a36Sopenharmony_cistatic LIST_HEAD(regions_list);
3762306a36Sopenharmony_cistatic struct logic_iomem_area mapped_areas[MAX_AREAS];
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ciint logic_iomem_add_region(struct resource *resource,
4062306a36Sopenharmony_ci			   const struct logic_iomem_region_ops *ops)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	struct logic_iomem_region *rreg;
4362306a36Sopenharmony_ci	int err;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	if (WARN_ON(!resource || !ops))
4662306a36Sopenharmony_ci		return -EINVAL;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	if (WARN_ON((resource->flags & IORESOURCE_TYPE_BITS) != IORESOURCE_MEM))
4962306a36Sopenharmony_ci		return -EINVAL;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	rreg = kzalloc(sizeof(*rreg), GFP_KERNEL);
5262306a36Sopenharmony_ci	if (!rreg)
5362306a36Sopenharmony_ci		return -ENOMEM;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	err = request_resource(&iomem_resource, resource);
5662306a36Sopenharmony_ci	if (err) {
5762306a36Sopenharmony_ci		kfree(rreg);
5862306a36Sopenharmony_ci		return -ENOMEM;
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	mutex_lock(&regions_mtx);
6262306a36Sopenharmony_ci	rreg->res = resource;
6362306a36Sopenharmony_ci	rreg->ops = ops;
6462306a36Sopenharmony_ci	list_add_tail(&rreg->list, &regions_list);
6562306a36Sopenharmony_ci	mutex_unlock(&regions_mtx);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return 0;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ciEXPORT_SYMBOL(logic_iomem_add_region);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci#ifndef CONFIG_INDIRECT_IOMEM_FALLBACK
7262306a36Sopenharmony_cistatic void __iomem *real_ioremap(phys_addr_t offset, size_t size)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	WARN(1, "invalid ioremap(0x%llx, 0x%zx)\n",
7562306a36Sopenharmony_ci	     (unsigned long long)offset, size);
7662306a36Sopenharmony_ci	return NULL;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic void real_iounmap(volatile void __iomem *addr)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	WARN(1, "invalid iounmap for addr 0x%llx\n",
8262306a36Sopenharmony_ci	     (unsigned long long)(uintptr_t __force)addr);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci#endif /* CONFIG_INDIRECT_IOMEM_FALLBACK */
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_civoid __iomem *ioremap(phys_addr_t offset, size_t size)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	void __iomem *ret = NULL;
8962306a36Sopenharmony_ci	struct logic_iomem_region *rreg, *found = NULL;
9062306a36Sopenharmony_ci	int i;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	mutex_lock(&regions_mtx);
9362306a36Sopenharmony_ci	list_for_each_entry(rreg, &regions_list, list) {
9462306a36Sopenharmony_ci		if (rreg->res->start > offset)
9562306a36Sopenharmony_ci			continue;
9662306a36Sopenharmony_ci		if (rreg->res->end < offset + size - 1)
9762306a36Sopenharmony_ci			continue;
9862306a36Sopenharmony_ci		found = rreg;
9962306a36Sopenharmony_ci		break;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	if (!found)
10362306a36Sopenharmony_ci		goto out;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	for (i = 0; i < MAX_AREAS; i++) {
10662306a36Sopenharmony_ci		long offs;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		if (mapped_areas[i].ops)
10962306a36Sopenharmony_ci			continue;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci		offs = rreg->ops->map(offset - found->res->start,
11262306a36Sopenharmony_ci				      size, &mapped_areas[i].ops,
11362306a36Sopenharmony_ci				      &mapped_areas[i].priv);
11462306a36Sopenharmony_ci		if (offs < 0) {
11562306a36Sopenharmony_ci			mapped_areas[i].ops = NULL;
11662306a36Sopenharmony_ci			break;
11762306a36Sopenharmony_ci		}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		if (WARN_ON(!mapped_areas[i].ops)) {
12062306a36Sopenharmony_ci			mapped_areas[i].ops = NULL;
12162306a36Sopenharmony_ci			break;
12262306a36Sopenharmony_ci		}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci		ret = (void __iomem *)(IOREMAP_BIAS + (i << AREA_SHIFT) + offs);
12562306a36Sopenharmony_ci		break;
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ciout:
12862306a36Sopenharmony_ci	mutex_unlock(&regions_mtx);
12962306a36Sopenharmony_ci	if (ret)
13062306a36Sopenharmony_ci		return ret;
13162306a36Sopenharmony_ci	return real_ioremap(offset, size);
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ciEXPORT_SYMBOL(ioremap);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic inline struct logic_iomem_area *
13662306a36Sopenharmony_ciget_area(const volatile void __iomem *addr)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	unsigned long a = (unsigned long)addr;
13962306a36Sopenharmony_ci	unsigned int idx;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	if (WARN_ON((a & IOREMAP_MASK) != IOREMAP_BIAS))
14262306a36Sopenharmony_ci		return NULL;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	idx = (a & AREA_BITS) >> AREA_SHIFT;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if (mapped_areas[idx].ops)
14762306a36Sopenharmony_ci		return &mapped_areas[idx];
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return NULL;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_civoid iounmap(volatile void __iomem *addr)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct logic_iomem_area *area = get_area(addr);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	if (!area) {
15762306a36Sopenharmony_ci		real_iounmap(addr);
15862306a36Sopenharmony_ci		return;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (area->ops->unmap)
16262306a36Sopenharmony_ci		area->ops->unmap(area->priv);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	mutex_lock(&regions_mtx);
16562306a36Sopenharmony_ci	area->ops = NULL;
16662306a36Sopenharmony_ci	area->priv = NULL;
16762306a36Sopenharmony_ci	mutex_unlock(&regions_mtx);
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ciEXPORT_SYMBOL(iounmap);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci#ifndef CONFIG_INDIRECT_IOMEM_FALLBACK
17262306a36Sopenharmony_ci#define MAKE_FALLBACK(op, sz) 						\
17362306a36Sopenharmony_cistatic u##sz real_raw_read ## op(const volatile void __iomem *addr)	\
17462306a36Sopenharmony_ci{									\
17562306a36Sopenharmony_ci	WARN(1, "Invalid read" #op " at address %llx\n",		\
17662306a36Sopenharmony_ci	     (unsigned long long)(uintptr_t __force)addr);		\
17762306a36Sopenharmony_ci	return (u ## sz)~0ULL;						\
17862306a36Sopenharmony_ci}									\
17962306a36Sopenharmony_ci									\
18062306a36Sopenharmony_cistatic void real_raw_write ## op(u ## sz val,				\
18162306a36Sopenharmony_ci				 volatile void __iomem *addr)		\
18262306a36Sopenharmony_ci{									\
18362306a36Sopenharmony_ci	WARN(1, "Invalid writeq" #op " of 0x%llx at address %llx\n",	\
18462306a36Sopenharmony_ci	     (unsigned long long)val,					\
18562306a36Sopenharmony_ci	     (unsigned long long)(uintptr_t __force)addr);\
18662306a36Sopenharmony_ci}									\
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ciMAKE_FALLBACK(b, 8);
18962306a36Sopenharmony_ciMAKE_FALLBACK(w, 16);
19062306a36Sopenharmony_ciMAKE_FALLBACK(l, 32);
19162306a36Sopenharmony_ci#ifdef CONFIG_64BIT
19262306a36Sopenharmony_ciMAKE_FALLBACK(q, 64);
19362306a36Sopenharmony_ci#endif
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic void real_memset_io(volatile void __iomem *addr, int value, size_t size)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	WARN(1, "Invalid memset_io at address 0x%llx\n",
19862306a36Sopenharmony_ci	     (unsigned long long)(uintptr_t __force)addr);
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic void real_memcpy_fromio(void *buffer, const volatile void __iomem *addr,
20262306a36Sopenharmony_ci			       size_t size)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	WARN(1, "Invalid memcpy_fromio at address 0x%llx\n",
20562306a36Sopenharmony_ci	     (unsigned long long)(uintptr_t __force)addr);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	memset(buffer, 0xff, size);
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic void real_memcpy_toio(volatile void __iomem *addr, const void *buffer,
21162306a36Sopenharmony_ci			     size_t size)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	WARN(1, "Invalid memcpy_toio at address 0x%llx\n",
21462306a36Sopenharmony_ci	     (unsigned long long)(uintptr_t __force)addr);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci#endif /* CONFIG_INDIRECT_IOMEM_FALLBACK */
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci#define MAKE_OP(op, sz) 						\
21962306a36Sopenharmony_ciu##sz __raw_read ## op(const volatile void __iomem *addr)		\
22062306a36Sopenharmony_ci{									\
22162306a36Sopenharmony_ci	struct logic_iomem_area *area = get_area(addr);			\
22262306a36Sopenharmony_ci									\
22362306a36Sopenharmony_ci	if (!area)							\
22462306a36Sopenharmony_ci		return real_raw_read ## op(addr);			\
22562306a36Sopenharmony_ci									\
22662306a36Sopenharmony_ci	return (u ## sz) area->ops->read(area->priv,			\
22762306a36Sopenharmony_ci					 (unsigned long)addr & AREA_MASK,\
22862306a36Sopenharmony_ci					 sz / 8);			\
22962306a36Sopenharmony_ci}									\
23062306a36Sopenharmony_ciEXPORT_SYMBOL(__raw_read ## op);					\
23162306a36Sopenharmony_ci									\
23262306a36Sopenharmony_civoid __raw_write ## op(u ## sz val, volatile void __iomem *addr)	\
23362306a36Sopenharmony_ci{									\
23462306a36Sopenharmony_ci	struct logic_iomem_area *area = get_area(addr);			\
23562306a36Sopenharmony_ci									\
23662306a36Sopenharmony_ci	if (!area) {							\
23762306a36Sopenharmony_ci		real_raw_write ## op(val, addr);			\
23862306a36Sopenharmony_ci		return;							\
23962306a36Sopenharmony_ci	}								\
24062306a36Sopenharmony_ci									\
24162306a36Sopenharmony_ci	area->ops->write(area->priv,					\
24262306a36Sopenharmony_ci			 (unsigned long)addr & AREA_MASK,		\
24362306a36Sopenharmony_ci			 sz / 8, val);					\
24462306a36Sopenharmony_ci}									\
24562306a36Sopenharmony_ciEXPORT_SYMBOL(__raw_write ## op)
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ciMAKE_OP(b, 8);
24862306a36Sopenharmony_ciMAKE_OP(w, 16);
24962306a36Sopenharmony_ciMAKE_OP(l, 32);
25062306a36Sopenharmony_ci#ifdef CONFIG_64BIT
25162306a36Sopenharmony_ciMAKE_OP(q, 64);
25262306a36Sopenharmony_ci#endif
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_civoid memset_io(volatile void __iomem *addr, int value, size_t size)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	struct logic_iomem_area *area = get_area(addr);
25762306a36Sopenharmony_ci	unsigned long offs, start;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	if (!area) {
26062306a36Sopenharmony_ci		real_memset_io(addr, value, size);
26162306a36Sopenharmony_ci		return;
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	start = (unsigned long)addr & AREA_MASK;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	if (area->ops->set) {
26762306a36Sopenharmony_ci		area->ops->set(area->priv, start, value, size);
26862306a36Sopenharmony_ci		return;
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	for (offs = 0; offs < size; offs++)
27262306a36Sopenharmony_ci		area->ops->write(area->priv, start + offs, 1, value);
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ciEXPORT_SYMBOL(memset_io);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_civoid memcpy_fromio(void *buffer, const volatile void __iomem *addr,
27762306a36Sopenharmony_ci                   size_t size)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	struct logic_iomem_area *area = get_area(addr);
28062306a36Sopenharmony_ci	u8 *buf = buffer;
28162306a36Sopenharmony_ci	unsigned long offs, start;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	if (!area) {
28462306a36Sopenharmony_ci		real_memcpy_fromio(buffer, addr, size);
28562306a36Sopenharmony_ci		return;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	start = (unsigned long)addr & AREA_MASK;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	if (area->ops->copy_from) {
29162306a36Sopenharmony_ci		area->ops->copy_from(area->priv, buffer, start, size);
29262306a36Sopenharmony_ci		return;
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	for (offs = 0; offs < size; offs++)
29662306a36Sopenharmony_ci		buf[offs] = area->ops->read(area->priv, start + offs, 1);
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ciEXPORT_SYMBOL(memcpy_fromio);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_civoid memcpy_toio(volatile void __iomem *addr, const void *buffer, size_t size)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	struct logic_iomem_area *area = get_area(addr);
30362306a36Sopenharmony_ci	const u8 *buf = buffer;
30462306a36Sopenharmony_ci	unsigned long offs, start;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	if (!area) {
30762306a36Sopenharmony_ci		real_memcpy_toio(addr, buffer, size);
30862306a36Sopenharmony_ci		return;
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	start = (unsigned long)addr & AREA_MASK;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	if (area->ops->copy_to) {
31462306a36Sopenharmony_ci		area->ops->copy_to(area->priv, start, buffer, size);
31562306a36Sopenharmony_ci		return;
31662306a36Sopenharmony_ci	}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	for (offs = 0; offs < size; offs++)
31962306a36Sopenharmony_ci		area->ops->write(area->priv, start + offs, 1, buf[offs]);
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ciEXPORT_SYMBOL(memcpy_toio);
322