162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * BCM947xx nvram variable access 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2005 Broadcom Corporation 662306a36Sopenharmony_ci * Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org> 762306a36Sopenharmony_ci * Copyright (C) 2010-2012 Hauke Mehrtens <hauke@hauke-m.de> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/io.h> 1162306a36Sopenharmony_ci#include <linux/types.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/string.h> 1562306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1662306a36Sopenharmony_ci#include <linux/bcm47xx_nvram.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define NVRAM_MAGIC 0x48534C46 /* 'FLSH' */ 1962306a36Sopenharmony_ci#define NVRAM_SPACE 0x10000 2062306a36Sopenharmony_ci#define NVRAM_MAX_GPIO_ENTRIES 32 2162306a36Sopenharmony_ci#define NVRAM_MAX_GPIO_VALUE_LEN 30 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define FLASH_MIN 0x00020000 /* Minimum flash size */ 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct nvram_header { 2662306a36Sopenharmony_ci u32 magic; 2762306a36Sopenharmony_ci u32 len; 2862306a36Sopenharmony_ci u32 crc_ver_init; /* 0:7 crc, 8:15 ver, 16:31 sdram_init */ 2962306a36Sopenharmony_ci u32 config_refresh; /* 0:15 sdram_config, 16:31 sdram_refresh */ 3062306a36Sopenharmony_ci u32 config_ncdl; /* ncdl values for memc */ 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic char nvram_buf[NVRAM_SPACE]; 3462306a36Sopenharmony_cistatic size_t nvram_len; 3562306a36Sopenharmony_cistatic const u32 nvram_sizes[] = {0x6000, 0x8000, 0xF000, 0x10000}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/** 3862306a36Sopenharmony_ci * bcm47xx_nvram_is_valid - check for a valid NVRAM at specified memory 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_cistatic bool bcm47xx_nvram_is_valid(void __iomem *nvram) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci return ((struct nvram_header *)nvram)->magic == NVRAM_MAGIC; 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/** 4662306a36Sopenharmony_ci * bcm47xx_nvram_copy - copy NVRAM to internal buffer 4762306a36Sopenharmony_ci */ 4862306a36Sopenharmony_cistatic void bcm47xx_nvram_copy(void __iomem *nvram_start, size_t res_size) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci struct nvram_header __iomem *header = nvram_start; 5162306a36Sopenharmony_ci size_t copy_size; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci copy_size = header->len; 5462306a36Sopenharmony_ci if (copy_size > res_size) { 5562306a36Sopenharmony_ci pr_err("The nvram size according to the header seems to be bigger than the partition on flash\n"); 5662306a36Sopenharmony_ci copy_size = res_size; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci if (copy_size >= NVRAM_SPACE) { 5962306a36Sopenharmony_ci pr_err("nvram on flash (%zu bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", 6062306a36Sopenharmony_ci copy_size, NVRAM_SPACE - 1); 6162306a36Sopenharmony_ci copy_size = NVRAM_SPACE - 1; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci __ioread32_copy(nvram_buf, nvram_start, DIV_ROUND_UP(copy_size, 4)); 6562306a36Sopenharmony_ci nvram_buf[NVRAM_SPACE - 1] = '\0'; 6662306a36Sopenharmony_ci nvram_len = copy_size; 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/** 7062306a36Sopenharmony_ci * bcm47xx_nvram_find_and_copy - find NVRAM on flash mapping & copy it 7162306a36Sopenharmony_ci */ 7262306a36Sopenharmony_cistatic int bcm47xx_nvram_find_and_copy(void __iomem *flash_start, size_t res_size) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci size_t flash_size; 7562306a36Sopenharmony_ci size_t offset; 7662306a36Sopenharmony_ci int i; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (nvram_len) { 7962306a36Sopenharmony_ci pr_warn("nvram already initialized\n"); 8062306a36Sopenharmony_ci return -EEXIST; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* TODO: when nvram is on nand flash check for bad blocks first. */ 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* Try every possible flash size and check for NVRAM at its end */ 8662306a36Sopenharmony_ci for (flash_size = FLASH_MIN; flash_size <= res_size; flash_size <<= 1) { 8762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(nvram_sizes); i++) { 8862306a36Sopenharmony_ci offset = flash_size - nvram_sizes[i]; 8962306a36Sopenharmony_ci if (bcm47xx_nvram_is_valid(flash_start + offset)) 9062306a36Sopenharmony_ci goto found; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* Try embedded NVRAM at 4 KB and 1 KB as last resorts */ 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci offset = 4096; 9762306a36Sopenharmony_ci if (bcm47xx_nvram_is_valid(flash_start + offset)) 9862306a36Sopenharmony_ci goto found; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci offset = 1024; 10162306a36Sopenharmony_ci if (bcm47xx_nvram_is_valid(flash_start + offset)) 10262306a36Sopenharmony_ci goto found; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci pr_err("no nvram found\n"); 10562306a36Sopenharmony_ci return -ENXIO; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cifound: 10862306a36Sopenharmony_ci bcm47xx_nvram_copy(flash_start + offset, res_size - offset); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci return 0; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ciint bcm47xx_nvram_init_from_iomem(void __iomem *nvram_start, size_t res_size) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci if (nvram_len) { 11662306a36Sopenharmony_ci pr_warn("nvram already initialized\n"); 11762306a36Sopenharmony_ci return -EEXIST; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (!bcm47xx_nvram_is_valid(nvram_start)) { 12162306a36Sopenharmony_ci pr_err("No valid NVRAM found\n"); 12262306a36Sopenharmony_ci return -ENOENT; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci bcm47xx_nvram_copy(nvram_start, res_size); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci return 0; 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(bcm47xx_nvram_init_from_iomem); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/* 13262306a36Sopenharmony_ci * On bcm47xx we need access to the NVRAM very early, so we can't use mtd 13362306a36Sopenharmony_ci * subsystem to access flash. We can't even use platform device / driver to 13462306a36Sopenharmony_ci * store memory offset. 13562306a36Sopenharmony_ci * To handle this we provide following symbol. It's supposed to be called as 13662306a36Sopenharmony_ci * soon as we get info about flash device, before any NVRAM entry is needed. 13762306a36Sopenharmony_ci */ 13862306a36Sopenharmony_ciint bcm47xx_nvram_init_from_mem(u32 base, u32 lim) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci void __iomem *iobase; 14162306a36Sopenharmony_ci int err; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci iobase = ioremap(base, lim); 14462306a36Sopenharmony_ci if (!iobase) 14562306a36Sopenharmony_ci return -ENOMEM; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci err = bcm47xx_nvram_find_and_copy(iobase, lim); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci iounmap(iobase); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return err; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic int nvram_init(void) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci#ifdef CONFIG_MTD 15762306a36Sopenharmony_ci struct mtd_info *mtd; 15862306a36Sopenharmony_ci struct nvram_header header; 15962306a36Sopenharmony_ci size_t bytes_read; 16062306a36Sopenharmony_ci int err; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci mtd = get_mtd_device_nm("nvram"); 16362306a36Sopenharmony_ci if (IS_ERR(mtd)) 16462306a36Sopenharmony_ci return -ENODEV; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci err = mtd_read(mtd, 0, sizeof(header), &bytes_read, (uint8_t *)&header); 16762306a36Sopenharmony_ci if (!err && header.magic == NVRAM_MAGIC && 16862306a36Sopenharmony_ci header.len > sizeof(header)) { 16962306a36Sopenharmony_ci nvram_len = header.len; 17062306a36Sopenharmony_ci if (nvram_len >= NVRAM_SPACE) { 17162306a36Sopenharmony_ci pr_err("nvram on flash (%zu bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", 17262306a36Sopenharmony_ci nvram_len, NVRAM_SPACE); 17362306a36Sopenharmony_ci nvram_len = NVRAM_SPACE - 1; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci err = mtd_read(mtd, 0, nvram_len, &nvram_len, 17762306a36Sopenharmony_ci (u8 *)nvram_buf); 17862306a36Sopenharmony_ci return err; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci#endif 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return -ENXIO; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ciint bcm47xx_nvram_getenv(const char *name, char *val, size_t val_len) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci char *var, *value, *end, *eq; 18862306a36Sopenharmony_ci int err; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (!name) 19162306a36Sopenharmony_ci return -EINVAL; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (!nvram_len) { 19462306a36Sopenharmony_ci err = nvram_init(); 19562306a36Sopenharmony_ci if (err) 19662306a36Sopenharmony_ci return err; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* Look for name=value and return value */ 20062306a36Sopenharmony_ci var = &nvram_buf[sizeof(struct nvram_header)]; 20162306a36Sopenharmony_ci end = nvram_buf + sizeof(nvram_buf); 20262306a36Sopenharmony_ci while (var < end && *var) { 20362306a36Sopenharmony_ci eq = strchr(var, '='); 20462306a36Sopenharmony_ci if (!eq) 20562306a36Sopenharmony_ci break; 20662306a36Sopenharmony_ci value = eq + 1; 20762306a36Sopenharmony_ci if (eq - var == strlen(name) && 20862306a36Sopenharmony_ci strncmp(var, name, eq - var) == 0) 20962306a36Sopenharmony_ci return snprintf(val, val_len, "%s", value); 21062306a36Sopenharmony_ci var = value + strlen(value) + 1; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci return -ENOENT; 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ciEXPORT_SYMBOL(bcm47xx_nvram_getenv); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ciint bcm47xx_nvram_gpio_pin(const char *name) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci int i, err; 21962306a36Sopenharmony_ci char nvram_var[] = "gpioXX"; 22062306a36Sopenharmony_ci char buf[NVRAM_MAX_GPIO_VALUE_LEN]; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci /* TODO: Optimize it to don't call getenv so many times */ 22362306a36Sopenharmony_ci for (i = 0; i < NVRAM_MAX_GPIO_ENTRIES; i++) { 22462306a36Sopenharmony_ci err = snprintf(nvram_var, sizeof(nvram_var), "gpio%i", i); 22562306a36Sopenharmony_ci if (err <= 0) 22662306a36Sopenharmony_ci continue; 22762306a36Sopenharmony_ci err = bcm47xx_nvram_getenv(nvram_var, buf, sizeof(buf)); 22862306a36Sopenharmony_ci if (err <= 0) 22962306a36Sopenharmony_ci continue; 23062306a36Sopenharmony_ci if (!strcmp(name, buf)) 23162306a36Sopenharmony_ci return i; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci return -ENOENT; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ciEXPORT_SYMBOL(bcm47xx_nvram_gpio_pin); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cichar *bcm47xx_nvram_get_contents(size_t *nvram_size) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci int err; 24062306a36Sopenharmony_ci char *nvram; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci if (!nvram_len) { 24362306a36Sopenharmony_ci err = nvram_init(); 24462306a36Sopenharmony_ci if (err) 24562306a36Sopenharmony_ci return NULL; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci *nvram_size = nvram_len - sizeof(struct nvram_header); 24962306a36Sopenharmony_ci nvram = vmalloc(*nvram_size); 25062306a36Sopenharmony_ci if (!nvram) 25162306a36Sopenharmony_ci return NULL; 25262306a36Sopenharmony_ci memcpy(nvram, &nvram_buf[sizeof(struct nvram_header)], *nvram_size); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci return nvram; 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ciEXPORT_SYMBOL(bcm47xx_nvram_get_contents); 25762306a36Sopenharmony_ci 258