diff options
Diffstat (limited to 'drivers/firmware')
-rw-r--r-- | drivers/firmware/Kconfig | 1 | ||||
-rw-r--r-- | drivers/firmware/Makefile | 1 | ||||
-rw-r--r-- | drivers/firmware/broadcom/Kconfig | 11 | ||||
-rw-r--r-- | drivers/firmware/broadcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/firmware/broadcom/bcm47xx_nvram.c | 248 |
5 files changed, 262 insertions, 0 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 6517132e5d8b..99c69a3205c4 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -136,6 +136,7 @@ config QCOM_SCM bool depends on ARM || ARM64 +source "drivers/firmware/broadcom/Kconfig" source "drivers/firmware/google/Kconfig" source "drivers/firmware/efi/Kconfig" diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 3fdd3912709a..210c6e0550d3 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o obj-$(CONFIG_QCOM_SCM) += qcom_scm.o CFLAGS_qcom_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) +obj-y += broadcom/ obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ obj-$(CONFIG_EFI) += efi/ obj-$(CONFIG_UEFI_CPER) += efi/ diff --git a/drivers/firmware/broadcom/Kconfig b/drivers/firmware/broadcom/Kconfig new file mode 100644 index 000000000000..6bed119930dd --- /dev/null +++ b/drivers/firmware/broadcom/Kconfig @@ -0,0 +1,11 @@ +config BCM47XX_NVRAM + bool "Broadcom NVRAM driver" + depends on BCM47XX || ARCH_BCM_5301X + help + Broadcom home routers contain flash partition called "nvram" with all + important hardware configuration as well as some minor user setup. + NVRAM partition contains a text-like data representing name=value + pairs. + This driver provides an easy way to get value of requested parameter. + It simply reads content of NVRAM and parses it. It doesn't control any + hardware part itself. diff --git a/drivers/firmware/broadcom/Makefile b/drivers/firmware/broadcom/Makefile new file mode 100644 index 000000000000..d0e683583cd6 --- /dev/null +++ b/drivers/firmware/broadcom/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_BCM47XX_NVRAM) += bcm47xx_nvram.o diff --git a/drivers/firmware/broadcom/bcm47xx_nvram.c b/drivers/firmware/broadcom/bcm47xx_nvram.c new file mode 100644 index 000000000000..87add3fdce52 --- /dev/null +++ b/drivers/firmware/broadcom/bcm47xx_nvram.c @@ -0,0 +1,248 @@ +/* + * BCM947xx nvram variable access + * + * Copyright (C) 2005 Broadcom Corporation + * Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org> + * Copyright (C) 2010-2012 Hauke Mehrtens <hauke@hauke-m.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/io.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/mtd/mtd.h> +#include <linux/bcm47xx_nvram.h> + +#define NVRAM_MAGIC 0x48534C46 /* 'FLSH' */ +#define NVRAM_SPACE 0x10000 +#define NVRAM_MAX_GPIO_ENTRIES 32 +#define NVRAM_MAX_GPIO_VALUE_LEN 30 + +#define FLASH_MIN 0x00020000 /* Minimum flash size */ + +struct nvram_header { + u32 magic; + u32 len; + u32 crc_ver_init; /* 0:7 crc, 8:15 ver, 16:31 sdram_init */ + u32 config_refresh; /* 0:15 sdram_config, 16:31 sdram_refresh */ + u32 config_ncdl; /* ncdl values for memc */ +}; + +static char nvram_buf[NVRAM_SPACE]; +static size_t nvram_len; +static const u32 nvram_sizes[] = {0x8000, 0xF000, 0x10000}; + +static u32 find_nvram_size(void __iomem *end) +{ + struct nvram_header __iomem *header; + int i; + + for (i = 0; i < ARRAY_SIZE(nvram_sizes); i++) { + header = (struct nvram_header *)(end - nvram_sizes[i]); + if (header->magic == NVRAM_MAGIC) + return nvram_sizes[i]; + } + + return 0; +} + +/* Probe for NVRAM header */ +static int nvram_find_and_copy(void __iomem *iobase, u32 lim) +{ + struct nvram_header __iomem *header; + int i; + u32 off; + u32 *src, *dst; + u32 size; + + if (nvram_len) { + pr_warn("nvram already initialized\n"); + return -EEXIST; + } + + /* TODO: when nvram is on nand flash check for bad blocks first. */ + off = FLASH_MIN; + while (off <= lim) { + /* Windowed flash access */ + size = find_nvram_size(iobase + off); + if (size) { + header = (struct nvram_header *)(iobase + off - size); + goto found; + } + off <<= 1; + } + + /* Try embedded NVRAM at 4 KB and 1 KB as last resorts */ + header = (struct nvram_header *)(iobase + 4096); + if (header->magic == NVRAM_MAGIC) { + size = NVRAM_SPACE; + goto found; + } + + header = (struct nvram_header *)(iobase + 1024); + if (header->magic == NVRAM_MAGIC) { + size = NVRAM_SPACE; + goto found; + } + + pr_err("no nvram found\n"); + return -ENXIO; + +found: + src = (u32 *)header; + dst = (u32 *)nvram_buf; + for (i = 0; i < sizeof(struct nvram_header); i += 4) + *dst++ = __raw_readl(src++); + header = (struct nvram_header *)nvram_buf; + nvram_len = header->len; + if (nvram_len > size) { + pr_err("The nvram size according to the header seems to be bigger than the partition on flash\n"); + nvram_len = size; + } + if (nvram_len >= NVRAM_SPACE) { + pr_err("nvram on flash (%i bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", + header->len, NVRAM_SPACE - 1); + nvram_len = NVRAM_SPACE - 1; + } + /* proceed reading data after header */ + for (; i < nvram_len; i += 4) + *dst++ = readl(src++); + nvram_buf[NVRAM_SPACE - 1] = '\0'; + + return 0; +} + +/* + * On bcm47xx we need access to the NVRAM very early, so we can't use mtd + * subsystem to access flash. We can't even use platform device / driver to + * store memory offset. + * To handle this we provide following symbol. It's supposed to be called as + * soon as we get info about flash device, before any NVRAM entry is needed. + */ +int bcm47xx_nvram_init_from_mem(u32 base, u32 lim) +{ + void __iomem *iobase; + int err; + + iobase = ioremap_nocache(base, lim); + if (!iobase) + return -ENOMEM; + + err = nvram_find_and_copy(iobase, lim); + + iounmap(iobase); + + return err; +} + +static int nvram_init(void) +{ +#ifdef CONFIG_MTD + struct mtd_info *mtd; + struct nvram_header header; + size_t bytes_read; + int err; + + mtd = get_mtd_device_nm("nvram"); + if (IS_ERR(mtd)) + return -ENODEV; + + err = mtd_read(mtd, 0, sizeof(header), &bytes_read, (uint8_t *)&header); + if (!err && header.magic == NVRAM_MAGIC && + header.len > sizeof(header)) { + nvram_len = header.len; + if (nvram_len >= NVRAM_SPACE) { + pr_err("nvram on flash (%i bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", + header.len, NVRAM_SPACE); + nvram_len = NVRAM_SPACE - 1; + } + + err = mtd_read(mtd, 0, nvram_len, &nvram_len, + (u8 *)nvram_buf); + return err; + } +#endif + + return -ENXIO; +} + +int bcm47xx_nvram_getenv(const char *name, char *val, size_t val_len) +{ + char *var, *value, *end, *eq; + int err; + + if (!name) + return -EINVAL; + + if (!nvram_len) { + err = nvram_init(); + if (err) + return err; + } + + /* Look for name=value and return value */ + var = &nvram_buf[sizeof(struct nvram_header)]; + end = nvram_buf + sizeof(nvram_buf); + while (var < end && *var) { + eq = strchr(var, '='); + if (!eq) + break; + value = eq + 1; + if (eq - var == strlen(name) && + strncmp(var, name, eq - var) == 0) + return snprintf(val, val_len, "%s", value); + var = value + strlen(value) + 1; + } + return -ENOENT; +} +EXPORT_SYMBOL(bcm47xx_nvram_getenv); + +int bcm47xx_nvram_gpio_pin(const char *name) +{ + int i, err; + char nvram_var[] = "gpioXX"; + char buf[NVRAM_MAX_GPIO_VALUE_LEN]; + + /* TODO: Optimize it to don't call getenv so many times */ + for (i = 0; i < NVRAM_MAX_GPIO_ENTRIES; i++) { + err = snprintf(nvram_var, sizeof(nvram_var), "gpio%i", i); + if (err <= 0) + continue; + err = bcm47xx_nvram_getenv(nvram_var, buf, sizeof(buf)); + if (err <= 0) + continue; + if (!strcmp(name, buf)) + return i; + } + return -ENOENT; +} +EXPORT_SYMBOL(bcm47xx_nvram_gpio_pin); + +char *bcm47xx_nvram_get_contents(size_t *nvram_size) +{ + int err; + char *nvram; + + if (!nvram_len) { + err = nvram_init(); + if (err) + return NULL; + } + + *nvram_size = nvram_len - sizeof(struct nvram_header); + nvram = vmalloc(*nvram_size); + if (!nvram) + return NULL; + memcpy(nvram, &nvram_buf[sizeof(struct nvram_header)], *nvram_size); + + return nvram; +} +EXPORT_SYMBOL(bcm47xx_nvram_get_contents); + +MODULE_LICENSE("GPLv2"); |