regmap.c 11.2 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0+
2 3 4 5 6 7 8 9
/*
 * Copyright (c) 2015 Google, Inc
 * Written by Simon Glass <sjg@chromium.org>
 */

#include <common.h>
#include <dm.h>
#include <errno.h>
10
#include <log.h>
11
#include <linux/libfdt.h>
12 13 14
#include <malloc.h>
#include <mapmem.h>
#include <regmap.h>
15
#include <asm/io.h>
16
#include <dm/of_addr.h>
17
#include <dm/devres.h>
18
#include <linux/ioport.h>
19 20
#include <linux/compat.h>
#include <linux/err.h>
21

22 23
DECLARE_GLOBAL_DATA_PTR;

M
Mario Six 已提交
24 25 26 27 28 29
/**
 * regmap_alloc() - Allocate a regmap with a given number of ranges.
 *
 * @count: Number of ranges to be allocated for the regmap.
 * Return: A pointer to the newly allocated regmap, or NULL on error.
 */
30
static struct regmap *regmap_alloc(int count)
31 32 33
{
	struct regmap *map;

34
	map = malloc(sizeof(*map) + sizeof(map->ranges[0]) * count);
35 36 37 38 39 40 41
	if (!map)
		return NULL;
	map->range_count = count;

	return map;
}

42
#if CONFIG_IS_ENABLED(OF_PLATDATA)
43
int regmap_init_mem_platdata(struct udevice *dev, fdt_val_t *reg, int count,
44 45
			     struct regmap **mapp)
{
46 47 48
	struct regmap_range *range;
	struct regmap *map;

49
	map = regmap_alloc(count);
50 51 52
	if (!map)
		return -ENOMEM;

53
	for (range = map->ranges; count > 0; reg += 2, range++, count--) {
54 55 56 57 58 59
		range->start = *reg;
		range->size = reg[1];
	}

	*mapp = map;

60 61 62
	return 0;
}
#else
M
Mario Six 已提交
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
/**
 * init_range() - Initialize a single range of a regmap
 * @node:     Device node that will use the map in question
 * @range:    Pointer to a regmap_range structure that will be initialized
 * @addr_len: The length of the addr parts of the reg property
 * @size_len: The length of the size parts of the reg property
 * @index:    The index of the range to initialize
 *
 * This function will read the necessary 'reg' information from the device tree
 * (the 'addr' part, and the 'length' part), and initialize the range in
 * quesion.
 *
 * Return: 0 if OK, -ve on error
 */
static int init_range(ofnode node, struct regmap_range *range, int addr_len,
		      int size_len, int index)
{
	fdt_size_t sz;
	struct resource r;

	if (of_live_active()) {
		int ret;

		ret = of_address_to_resource(ofnode_to_np(node),
					     index, &r);
		if (ret) {
			debug("%s: Could not read resource of range %d (ret = %d)\n",
			      ofnode_get_name(node), index, ret);
			return ret;
		}

		range->start = r.start;
		range->size = r.end - r.start + 1;
	} else {
		int offset = ofnode_to_offset(node);

		range->start = fdtdec_get_addr_size_fixed(gd->fdt_blob, offset,
							  "reg", index,
							  addr_len, size_len,
							  &sz, true);
		if (range->start == FDT_ADDR_T_NONE) {
			debug("%s: Could not read start of range %d\n",
			      ofnode_get_name(node), index);
			return -EINVAL;
		}

		range->size = sz;
	}

	return 0;
}

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
int regmap_init_mem_index(ofnode node, struct regmap **mapp, int index)
{
	struct regmap *map;
	int addr_len, size_len;
	int ret;

	addr_len = ofnode_read_simple_addr_cells(ofnode_get_parent(node));
	if (addr_len < 0) {
		debug("%s: Error while reading the addr length (ret = %d)\n",
		      ofnode_get_name(node), addr_len);
		return addr_len;
	}

	size_len = ofnode_read_simple_size_cells(ofnode_get_parent(node));
	if (size_len < 0) {
		debug("%s: Error while reading the size length: (ret = %d)\n",
		      ofnode_get_name(node), size_len);
		return size_len;
	}

	map = regmap_alloc(1);
	if (!map)
		return -ENOMEM;

	ret = init_range(node, map->ranges, addr_len, size_len, index);
	if (ret)
F
Faiz Abbas 已提交
141
		goto err;
142 143 144 145 146 147 148 149 150 151 152 153

	if (ofnode_read_bool(node, "little-endian"))
		map->endianness = REGMAP_LITTLE_ENDIAN;
	else if (ofnode_read_bool(node, "big-endian"))
		map->endianness = REGMAP_BIG_ENDIAN;
	else if (ofnode_read_bool(node, "native-endian"))
		map->endianness = REGMAP_NATIVE_ENDIAN;
	else /* Default: native endianness */
		map->endianness = REGMAP_NATIVE_ENDIAN;

	*mapp = map;

F
Faiz Abbas 已提交
154 155 156 157
	return 0;
err:
	regmap_uninit(map);

158 159 160
	return ret;
}

161
int regmap_init_mem(ofnode node, struct regmap **mapp)
162 163 164 165 166 167
{
	struct regmap_range *range;
	struct regmap *map;
	int count;
	int addr_len, size_len, both_len;
	int len;
168
	int index;
F
Faiz Abbas 已提交
169
	int ret;
170

171
	addr_len = ofnode_read_simple_addr_cells(ofnode_get_parent(node));
M
Mario Six 已提交
172 173 174 175 176 177
	if (addr_len < 0) {
		debug("%s: Error while reading the addr length (ret = %d)\n",
		      ofnode_get_name(node), addr_len);
		return addr_len;
	}

178
	size_len = ofnode_read_simple_size_cells(ofnode_get_parent(node));
M
Mario Six 已提交
179 180 181 182 183 184
	if (size_len < 0) {
		debug("%s: Error while reading the size length: (ret = %d)\n",
		      ofnode_get_name(node), size_len);
		return size_len;
	}

185
	both_len = addr_len + size_len;
M
Mario Six 已提交
186 187 188 189 190
	if (!both_len) {
		debug("%s: Both addr and size length are zero\n",
		      ofnode_get_name(node));
		return -EINVAL;
	}
191

192
	len = ofnode_read_size(node, "reg");
M
Mario Six 已提交
193 194 195
	if (len < 0) {
		debug("%s: Error while reading reg size (ret = %d)\n",
		      ofnode_get_name(node), len);
196
		return len;
M
Mario Six 已提交
197
	}
198
	len /= sizeof(fdt32_t);
199
	count = len / both_len;
M
Mario Six 已提交
200 201 202
	if (!count) {
		debug("%s: Not enough data in reg property\n",
		      ofnode_get_name(node));
203
		return -EINVAL;
M
Mario Six 已提交
204
	}
205

206
	map = regmap_alloc(count);
207 208 209
	if (!map)
		return -ENOMEM;

210
	for (range = map->ranges, index = 0; count > 0;
211
	     count--, range++, index++) {
F
Faiz Abbas 已提交
212
		ret = init_range(node, range, addr_len, size_len, index);
M
Mario Six 已提交
213
		if (ret)
F
Faiz Abbas 已提交
214
			goto err;
215 216
	}

M
Mario Six 已提交
217 218 219 220 221 222 223 224 225
	if (ofnode_read_bool(node, "little-endian"))
		map->endianness = REGMAP_LITTLE_ENDIAN;
	else if (ofnode_read_bool(node, "big-endian"))
		map->endianness = REGMAP_BIG_ENDIAN;
	else if (ofnode_read_bool(node, "native-endian"))
		map->endianness = REGMAP_NATIVE_ENDIAN;
	else /* Default: native endianness */
		map->endianness = REGMAP_NATIVE_ENDIAN;

226 227 228
	*mapp = map;

	return 0;
F
Faiz Abbas 已提交
229 230 231 232
err:
	regmap_uninit(map);

	return ret;
233
}
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

static void devm_regmap_release(struct udevice *dev, void *res)
{
	regmap_uninit(*(struct regmap **)res);
}

struct regmap *devm_regmap_init(struct udevice *dev,
				const struct regmap_bus *bus,
				void *bus_context,
				const struct regmap_config *config)
{
	int rc;
	struct regmap **mapp;

	mapp = devres_alloc(devm_regmap_release, sizeof(struct regmap *),
			    __GFP_ZERO);
	if (unlikely(!mapp))
		return ERR_PTR(-ENOMEM);

	rc = regmap_init_mem(dev_ofnode(dev), mapp);
	if (rc)
		return ERR_PTR(rc);

	devres_add(dev, mapp);
	return *mapp;
}
260
#endif
261 262 263 264 265 266 267

void *regmap_get_range(struct regmap *map, unsigned int range_num)
{
	struct regmap_range *range;

	if (range_num >= map->range_count)
		return NULL;
268
	range = &map->ranges[range_num];
269 270 271 272 273 274 275 276 277 278

	return map_sysmem(range->start, range->size);
}

int regmap_uninit(struct regmap *map)
{
	free(map);

	return 0;
}
279

M
Mario Six 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
static inline u8 __read_8(u8 *addr, enum regmap_endianness_t endianness)
{
	return readb(addr);
}

static inline u16 __read_16(u16 *addr, enum regmap_endianness_t endianness)
{
	switch (endianness) {
	case REGMAP_LITTLE_ENDIAN:
		return in_le16(addr);
	case REGMAP_BIG_ENDIAN:
		return in_be16(addr);
	case REGMAP_NATIVE_ENDIAN:
		return readw(addr);
	}

	return readw(addr);
}

static inline u32 __read_32(u32 *addr, enum regmap_endianness_t endianness)
{
	switch (endianness) {
	case REGMAP_LITTLE_ENDIAN:
		return in_le32(addr);
	case REGMAP_BIG_ENDIAN:
		return in_be32(addr);
	case REGMAP_NATIVE_ENDIAN:
		return readl(addr);
	}

	return readl(addr);
}

#if defined(in_le64) && defined(in_be64) && defined(readq)
static inline u64 __read_64(u64 *addr, enum regmap_endianness_t endianness)
{
	switch (endianness) {
	case REGMAP_LITTLE_ENDIAN:
		return in_le64(addr);
	case REGMAP_BIG_ENDIAN:
		return in_be64(addr);
	case REGMAP_NATIVE_ENDIAN:
		return readq(addr);
	}

	return readq(addr);
}
#endif

329 330
int regmap_raw_read_range(struct regmap *map, uint range_num, uint offset,
			  void *valp, size_t val_len)
M
Mario Six 已提交
331
{
332
	struct regmap_range *range;
M
Mario Six 已提交
333 334
	void *ptr;

335 336 337 338 339 340 341 342 343 344 345
	if (range_num >= map->range_count) {
		debug("%s: range index %d larger than range count\n",
		      __func__, range_num);
		return -ERANGE;
	}
	range = &map->ranges[range_num];

	if (offset + val_len > range->size) {
		debug("%s: offset/size combination invalid\n", __func__);
		return -ERANGE;
	}
M
Mario Six 已提交
346

347 348
	ptr = map_physmem(range->start + offset, val_len, MAP_NOCACHE);

M
Mario Six 已提交
349 350
	switch (val_len) {
	case REGMAP_SIZE_8:
M
Mario Six 已提交
351
		*((u8 *)valp) = __read_8(ptr, map->endianness);
M
Mario Six 已提交
352 353
		break;
	case REGMAP_SIZE_16:
M
Mario Six 已提交
354
		*((u16 *)valp) = __read_16(ptr, map->endianness);
M
Mario Six 已提交
355 356
		break;
	case REGMAP_SIZE_32:
M
Mario Six 已提交
357
		*((u32 *)valp) = __read_32(ptr, map->endianness);
M
Mario Six 已提交
358
		break;
M
Mario Six 已提交
359
#if defined(in_le64) && defined(in_be64) && defined(readq)
M
Mario Six 已提交
360
	case REGMAP_SIZE_64:
M
Mario Six 已提交
361
		*((u64 *)valp) = __read_64(ptr, map->endianness);
M
Mario Six 已提交
362 363 364 365 366 367
		break;
#endif
	default:
		debug("%s: regmap size %zu unknown\n", __func__, val_len);
		return -EINVAL;
	}
368

M
Mario Six 已提交
369 370 371
	return 0;
}

372 373 374 375 376
int regmap_raw_read(struct regmap *map, uint offset, void *valp, size_t val_len)
{
	return regmap_raw_read_range(map, 0, offset, valp, val_len);
}

377 378
int regmap_read(struct regmap *map, uint offset, uint *valp)
{
M
Mario Six 已提交
379 380
	return regmap_raw_read(map, offset, valp, REGMAP_SIZE_32);
}
381

M
Mario Six 已提交
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
static inline void __write_8(u8 *addr, const u8 *val,
			     enum regmap_endianness_t endianness)
{
	writeb(*val, addr);
}

static inline void __write_16(u16 *addr, const u16 *val,
			      enum regmap_endianness_t endianness)
{
	switch (endianness) {
	case REGMAP_NATIVE_ENDIAN:
		writew(*val, addr);
		break;
	case REGMAP_LITTLE_ENDIAN:
		out_le16(addr, *val);
		break;
	case REGMAP_BIG_ENDIAN:
		out_be16(addr, *val);
		break;
	}
}

static inline void __write_32(u32 *addr, const u32 *val,
			      enum regmap_endianness_t endianness)
{
	switch (endianness) {
	case REGMAP_NATIVE_ENDIAN:
		writel(*val, addr);
		break;
	case REGMAP_LITTLE_ENDIAN:
		out_le32(addr, *val);
		break;
	case REGMAP_BIG_ENDIAN:
		out_be32(addr, *val);
		break;
	}
}

#if defined(out_le64) && defined(out_be64) && defined(writeq)
static inline void __write_64(u64 *addr, const u64 *val,
			      enum regmap_endianness_t endianness)
{
	switch (endianness) {
	case REGMAP_NATIVE_ENDIAN:
		writeq(*val, addr);
		break;
	case REGMAP_LITTLE_ENDIAN:
		out_le64(addr, *val);
		break;
	case REGMAP_BIG_ENDIAN:
		out_be64(addr, *val);
		break;
	}
}
#endif

438 439
int regmap_raw_write_range(struct regmap *map, uint range_num, uint offset,
			   const void *val, size_t val_len)
M
Mario Six 已提交
440
{
441
	struct regmap_range *range;
M
Mario Six 已提交
442 443
	void *ptr;

444 445 446 447 448 449 450 451 452 453 454
	if (range_num >= map->range_count) {
		debug("%s: range index %d larger than range count\n",
		      __func__, range_num);
		return -ERANGE;
	}
	range = &map->ranges[range_num];

	if (offset + val_len > range->size) {
		debug("%s: offset/size combination invalid\n", __func__);
		return -ERANGE;
	}
M
Mario Six 已提交
455

456 457
	ptr = map_physmem(range->start + offset, val_len, MAP_NOCACHE);

M
Mario Six 已提交
458 459
	switch (val_len) {
	case REGMAP_SIZE_8:
M
Mario Six 已提交
460
		__write_8(ptr, val, map->endianness);
M
Mario Six 已提交
461 462
		break;
	case REGMAP_SIZE_16:
M
Mario Six 已提交
463
		__write_16(ptr, val, map->endianness);
M
Mario Six 已提交
464 465
		break;
	case REGMAP_SIZE_32:
M
Mario Six 已提交
466
		__write_32(ptr, val, map->endianness);
M
Mario Six 已提交
467
		break;
M
Mario Six 已提交
468
#if defined(out_le64) && defined(out_be64) && defined(writeq)
M
Mario Six 已提交
469
	case REGMAP_SIZE_64:
M
Mario Six 已提交
470
		__write_64(ptr, val, map->endianness);
M
Mario Six 已提交
471 472 473 474 475 476
		break;
#endif
	default:
		debug("%s: regmap size %zu unknown\n", __func__, val_len);
		return -EINVAL;
	}
477 478 479 480

	return 0;
}

481 482 483 484 485 486
int regmap_raw_write(struct regmap *map, uint offset, const void *val,
		     size_t val_len)
{
	return regmap_raw_write_range(map, 0, offset, val, val_len);
}

487 488
int regmap_write(struct regmap *map, uint offset, uint val)
{
M
Mario Six 已提交
489
	return regmap_raw_write(map, offset, &val, REGMAP_SIZE_32);
490
}
491 492 493 494 495 496 497 498 499 500 501 502

int regmap_update_bits(struct regmap *map, uint offset, uint mask, uint val)
{
	uint reg;
	int ret;

	ret = regmap_read(map, offset, &reg);
	if (ret)
		return ret;

	reg &= ~mask;

503
	return regmap_write(map, offset, reg | (val & mask));
504
}