mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-11-04 05:50:17 +00:00 
			
		
		
		
	As part of bringing the master branch back in to next, we need to allow for all of these changes to exist here. Reported-by: Jonas Karlman <jonas@kwiboo.se> Signed-off-by: Tom Rini <trini@konsulko.com>
		
			
				
	
	
		
			159 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0-only
 | 
						|
/*
 | 
						|
 * Copyright (c) Vaisala Oyj. All rights reserved.
 | 
						|
 */
 | 
						|
 | 
						|
#include <bootcount.h>
 | 
						|
#include <dm.h>
 | 
						|
#include <dm/device_compat.h>
 | 
						|
#include <linux/ioport.h>
 | 
						|
#include <regmap.h>
 | 
						|
#include <syscon.h>
 | 
						|
 | 
						|
#define BYTES_TO_BITS(bytes) ((bytes) << 3)
 | 
						|
#define GEN_REG_MASK(val_size, val_addr)                                       \
 | 
						|
	(GENMASK(BYTES_TO_BITS(val_size) - 1, 0)                               \
 | 
						|
	 << (!!((val_addr) == 0x02) * BYTES_TO_BITS(2)))
 | 
						|
#define GET_DEFAULT_VALUE(val_size)                                            \
 | 
						|
	(CONFIG_SYS_BOOTCOUNT_MAGIC >>                                         \
 | 
						|
	 (BYTES_TO_BITS((sizeof(u32) - (val_size)))))
 | 
						|
 | 
						|
/**
 | 
						|
 * struct bootcount_syscon_priv - driver's private data
 | 
						|
 *
 | 
						|
 * @regmap: syscon regmap
 | 
						|
 * @reg_addr: register address used to store the bootcount value
 | 
						|
 * @size: size of the bootcount value (2 or 4 bytes)
 | 
						|
 * @magic: magic used to validate/save the bootcount value
 | 
						|
 * @magic_mask: magic value bitmask
 | 
						|
 * @reg_mask: mask used to identify the location of the bootcount value
 | 
						|
 * in the register when 2 bytes length is used
 | 
						|
 * @shift: value used to extract the botcount value from the register
 | 
						|
 */
 | 
						|
struct bootcount_syscon_priv {
 | 
						|
	struct regmap *regmap;
 | 
						|
	fdt_addr_t reg_addr;
 | 
						|
	fdt_size_t size;
 | 
						|
	u32 magic;
 | 
						|
	u32 magic_mask;
 | 
						|
	u32 reg_mask;
 | 
						|
	int shift;
 | 
						|
};
 | 
						|
 | 
						|
static int bootcount_syscon_set(struct udevice *dev, const u32 val)
 | 
						|
{
 | 
						|
	struct bootcount_syscon_priv *priv = dev_get_priv(dev);
 | 
						|
	u32 regval;
 | 
						|
 | 
						|
	if ((val & priv->magic_mask) != 0)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	regval = (priv->magic & priv->magic_mask) | (val & ~priv->magic_mask);
 | 
						|
 | 
						|
	if (priv->size == 2) {
 | 
						|
		regval &= 0xffff;
 | 
						|
		regval |= (regval & 0xffff) << BYTES_TO_BITS(priv->size);
 | 
						|
	}
 | 
						|
 | 
						|
	debug("%s: Prepare to write reg value: 0x%08x with register mask: 0x%08x\n",
 | 
						|
	      __func__, regval, priv->reg_mask);
 | 
						|
 | 
						|
	return regmap_update_bits(priv->regmap, priv->reg_addr, priv->reg_mask,
 | 
						|
				  regval);
 | 
						|
}
 | 
						|
 | 
						|
static int bootcount_syscon_get(struct udevice *dev, u32 *val)
 | 
						|
{
 | 
						|
	struct bootcount_syscon_priv *priv = dev_get_priv(dev);
 | 
						|
	u32 regval;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	ret = regmap_read(priv->regmap, priv->reg_addr, ®val);
 | 
						|
	if (ret)
 | 
						|
		return ret;
 | 
						|
 | 
						|
	regval &= priv->reg_mask;
 | 
						|
	regval >>= priv->shift;
 | 
						|
 | 
						|
	if ((regval & priv->magic_mask) == (priv->magic & priv->magic_mask)) {
 | 
						|
		*val = regval & ~priv->magic_mask;
 | 
						|
	} else {
 | 
						|
		dev_err(dev, "%s: Invalid bootcount magic\n", __func__);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	debug("%s: Read bootcount value: 0x%08x from regval: 0x%08x\n",
 | 
						|
	      __func__, *val, regval);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int bootcount_syscon_of_to_plat(struct udevice *dev)
 | 
						|
{
 | 
						|
	struct bootcount_syscon_priv *priv = dev_get_priv(dev);
 | 
						|
	fdt_addr_t bootcount_offset;
 | 
						|
	fdt_size_t reg_size;
 | 
						|
 | 
						|
	priv->regmap = syscon_regmap_lookup_by_phandle(dev, "syscon");
 | 
						|
	if (IS_ERR(priv->regmap)) {
 | 
						|
		dev_err(dev, "%s: Unable to find regmap (%ld)\n", __func__,
 | 
						|
			PTR_ERR(priv->regmap));
 | 
						|
		return PTR_ERR(priv->regmap);
 | 
						|
	}
 | 
						|
 | 
						|
	priv->reg_addr = dev_read_addr_size_name(dev, "syscon_reg", ®_size);
 | 
						|
	if (priv->reg_addr == FDT_ADDR_T_NONE) {
 | 
						|
		dev_err(dev, "%s: syscon_reg address not found\n", __func__);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	if (reg_size != 4) {
 | 
						|
		dev_err(dev, "%s: Unsupported register size: %pa\n", __func__,
 | 
						|
			®_size);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	bootcount_offset = dev_read_addr_size_name(dev, "offset", &priv->size);
 | 
						|
	if (bootcount_offset == FDT_ADDR_T_NONE) {
 | 
						|
		dev_err(dev, "%s: offset configuration not found\n", __func__);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	if (bootcount_offset + priv->size > reg_size) {
 | 
						|
		dev_err(dev,
 | 
						|
			"%s: Bootcount value doesn't fit in the reserved space\n",
 | 
						|
			__func__);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	if (priv->size != 2 && priv->size != 4) {
 | 
						|
		dev_err(dev,
 | 
						|
			"%s: Driver supports only 2 and 4 bytes bootcount size\n",
 | 
						|
			__func__);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	priv->magic = GET_DEFAULT_VALUE(priv->size);
 | 
						|
	priv->magic_mask = GENMASK(BYTES_TO_BITS(priv->size) - 1,
 | 
						|
				   BYTES_TO_BITS(priv->size >> 1));
 | 
						|
	priv->shift = !!(bootcount_offset == 0x02) * BYTES_TO_BITS(priv->size);
 | 
						|
	priv->reg_mask = GEN_REG_MASK(priv->size, bootcount_offset);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const struct bootcount_ops bootcount_syscon_ops = {
 | 
						|
	.get = bootcount_syscon_get,
 | 
						|
	.set = bootcount_syscon_set,
 | 
						|
};
 | 
						|
 | 
						|
static const struct udevice_id bootcount_syscon_ids[] = {
 | 
						|
	{ .compatible = "u-boot,bootcount-syscon" },
 | 
						|
	{}
 | 
						|
};
 | 
						|
 | 
						|
U_BOOT_DRIVER(bootcount_syscon) = {
 | 
						|
	.name = "bootcount-syscon",
 | 
						|
	.id = UCLASS_BOOTCOUNT,
 | 
						|
	.of_to_plat = bootcount_syscon_of_to_plat,
 | 
						|
	.priv_auto = sizeof(struct bootcount_syscon_priv),
 | 
						|
	.of_match = bootcount_syscon_ids,
 | 
						|
	.ops = &bootcount_syscon_ops,
 | 
						|
};
 |