uboot/drivers/bootcount/bootcount_syscon.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) Vaisala Oyj. All rights reserved.
   4 */
   5
   6#include <common.h>
   7#include <bootcount.h>
   8#include <dm.h>
   9#include <dm/device_compat.h>
  10#include <linux/ioport.h>
  11#include <regmap.h>
  12#include <syscon.h>
  13
  14#define BYTES_TO_BITS(bytes) ((bytes) << 3)
  15#define GEN_REG_MASK(val_size, val_addr)                                       \
  16        (GENMASK(BYTES_TO_BITS(val_size) - 1, 0)                               \
  17         << (!!((val_addr) == 0x02) * BYTES_TO_BITS(2)))
  18#define GET_DEFAULT_VALUE(val_size)                                            \
  19        (CONFIG_SYS_BOOTCOUNT_MAGIC >>                                         \
  20         (BYTES_TO_BITS((sizeof(u32) - (val_size)))))
  21
  22/**
  23 * struct bootcount_syscon_priv - driver's private data
  24 *
  25 * @regmap: syscon regmap
  26 * @reg_addr: register address used to store the bootcount value
  27 * @size: size of the bootcount value (2 or 4 bytes)
  28 * @magic: magic used to validate/save the bootcount value
  29 * @magic_mask: magic value bitmask
  30 * @reg_mask: mask used to identify the location of the bootcount value
  31 * in the register when 2 bytes length is used
  32 * @shift: value used to extract the botcount value from the register
  33 */
  34struct bootcount_syscon_priv {
  35        struct regmap *regmap;
  36        fdt_addr_t reg_addr;
  37        fdt_size_t size;
  38        u32 magic;
  39        u32 magic_mask;
  40        u32 reg_mask;
  41        int shift;
  42};
  43
  44static int bootcount_syscon_set(struct udevice *dev, const u32 val)
  45{
  46        struct bootcount_syscon_priv *priv = dev_get_priv(dev);
  47        u32 regval;
  48
  49        if ((val & priv->magic_mask) != 0)
  50                return -EINVAL;
  51
  52        regval = (priv->magic & priv->magic_mask) | (val & ~priv->magic_mask);
  53
  54        if (priv->size == 2) {
  55                regval &= 0xffff;
  56                regval |= (regval & 0xffff) << BYTES_TO_BITS(priv->size);
  57        }
  58
  59        debug("%s: Prepare to write reg value: 0x%08x with register mask: 0x%08x\n",
  60              __func__, regval, priv->reg_mask);
  61
  62        return regmap_update_bits(priv->regmap, priv->reg_addr, priv->reg_mask,
  63                                  regval);
  64}
  65
  66static int bootcount_syscon_get(struct udevice *dev, u32 *val)
  67{
  68        struct bootcount_syscon_priv *priv = dev_get_priv(dev);
  69        u32 regval;
  70        int ret;
  71
  72        ret = regmap_read(priv->regmap, priv->reg_addr, &regval);
  73        if (ret)
  74                return ret;
  75
  76        regval &= priv->reg_mask;
  77        regval >>= priv->shift;
  78
  79        if ((regval & priv->magic_mask) == (priv->magic & priv->magic_mask)) {
  80                *val = regval & ~priv->magic_mask;
  81        } else {
  82                dev_err(dev, "%s: Invalid bootcount magic\n", __func__);
  83                return -EINVAL;
  84        }
  85
  86        debug("%s: Read bootcount value: 0x%08x from regval: 0x%08x\n",
  87              __func__, *val, regval);
  88        return 0;
  89}
  90
  91static int bootcount_syscon_of_to_plat(struct udevice *dev)
  92{
  93        struct bootcount_syscon_priv *priv = dev_get_priv(dev);
  94        fdt_addr_t bootcount_offset;
  95        fdt_size_t reg_size;
  96
  97        priv->regmap = syscon_regmap_lookup_by_phandle(dev, "syscon");
  98        if (IS_ERR(priv->regmap)) {
  99                dev_err(dev, "%s: Unable to find regmap (%ld)\n", __func__,
 100                        PTR_ERR(priv->regmap));
 101                return PTR_ERR(priv->regmap);
 102        }
 103
 104        priv->reg_addr = dev_read_addr_size_name(dev, "syscon_reg", &reg_size);
 105        if (priv->reg_addr == FDT_ADDR_T_NONE) {
 106                dev_err(dev, "%s: syscon_reg address not found\n", __func__);
 107                return -EINVAL;
 108        }
 109        if (reg_size != 4) {
 110                dev_err(dev, "%s: Unsupported register size: %d\n", __func__,
 111                        reg_size);
 112                return -EINVAL;
 113        }
 114
 115        bootcount_offset = dev_read_addr_size_name(dev, "offset", &priv->size);
 116        if (bootcount_offset == FDT_ADDR_T_NONE) {
 117                dev_err(dev, "%s: offset configuration not found\n", __func__);
 118                return -EINVAL;
 119        }
 120        if (bootcount_offset + priv->size > reg_size) {
 121                dev_err(dev,
 122                        "%s: Bootcount value doesn't fit in the reserved space\n",
 123                        __func__);
 124                return -EINVAL;
 125        }
 126        if (priv->size != 2 && priv->size != 4) {
 127                dev_err(dev,
 128                        "%s: Driver supports only 2 and 4 bytes bootcount size\n",
 129                        __func__);
 130                return -EINVAL;
 131        }
 132
 133        priv->magic = GET_DEFAULT_VALUE(priv->size);
 134        priv->magic_mask = GENMASK(BYTES_TO_BITS(priv->size) - 1,
 135                                   BYTES_TO_BITS(priv->size >> 1));
 136        priv->shift = !!(bootcount_offset == 0x02) * BYTES_TO_BITS(priv->size);
 137        priv->reg_mask = GEN_REG_MASK(priv->size, bootcount_offset);
 138
 139        return 0;
 140}
 141
 142static const struct bootcount_ops bootcount_syscon_ops = {
 143        .get = bootcount_syscon_get,
 144        .set = bootcount_syscon_set,
 145};
 146
 147static const struct udevice_id bootcount_syscon_ids[] = {
 148        { .compatible = "u-boot,bootcount-syscon" },
 149        {}
 150};
 151
 152U_BOOT_DRIVER(bootcount_syscon) = {
 153        .name = "bootcount-syscon",
 154        .id = UCLASS_BOOTCOUNT,
 155        .of_to_plat = bootcount_syscon_of_to_plat,
 156        .priv_auto = sizeof(struct bootcount_syscon_priv),
 157        .of_match = bootcount_syscon_ids,
 158        .ops = &bootcount_syscon_ops,
 159};
 160