aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/bcm53xx/files/drivers/misc/bcm47xx-nvram.c
blob: 6ea715c2cde588d4e1c95f9eb9eeecc7f754894f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
 * BCM947xx nvram variable access
 *
 * Copyright (C) 2005 Broadcom Corporation
 * Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org>
 * Copyright (C) 2010-2014 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/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/of_address.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/bcm47xx_nvram.h>

struct bcm47xx_nvram {
	size_t nvram_len;
	char *nvram_buf;
};

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 __iomem *)(end - nvram_sizes[i]);
		if (__raw_readl(&header->magic) == NVRAM_HEADER)
			return nvram_sizes[i];
	}

	return 0;
}

/* Probe for NVRAM header */
static int nvram_find_and_copy(struct device *dev, void __iomem *base,
			       size_t len, char **nvram_buf,
			       size_t *nvram_len)
{
	struct nvram_header __iomem *header;
	int i;
	u32 off;
	u32 *dst;
	__le32 __iomem *src;
	u32 size;

	/* TODO: when nvram is on nand flash check for bad blocks first. */
	off = FLASH_MIN;
	while (off <= len) {
		/* Windowed flash access */
		size = find_nvram_size(base + off);
		if (size) {
			header = (struct nvram_header __iomem *)
					(base + off - size);
			goto found;
		}
		off += 0x10000;
	}

	/* Try embedded NVRAM at 4 KB and 1 KB as last resorts */
	header = (struct nvram_header __iomem *)(base + 4096);
	if (__raw_readl(&header->magic) == NVRAM_HEADER) {
		size = NVRAM_SPACE;
		goto found;
	}

	header = (struct nvram_header __iomem *)(base + 1024);
	if (__raw_readl(&header->magic) == NVRAM_HEADER) {
		size = NVRAM_SPACE;
		goto found;
	}

	*nvram_buf = NULL;
	*nvram_len = 0;
	pr_err("no nvram found\n");
	return -ENXIO;

found:
	if (readl(&header->len) > size)
		pr_err("The nvram size accoridng to the header seems to be bigger than the partition on flash\n");
	*nvram_len = min_t(u32, readl(&header->len), size);

	*nvram_buf = devm_kzalloc(dev, *nvram_len, GFP_KERNEL);
	if (!*nvram_buf)
		return -ENOMEM;

	src = (__le32 __iomem *) header;
	dst = (u32 *) *nvram_buf;
	for (i = 0; i < sizeof(struct nvram_header); i += 4)
		*dst++ = __raw_readl(src++);
	for (; i < *nvram_len; i += 4)
		*dst++ = readl(src++);

	return 0;
}

int bcm47xx_nvram_getenv(const struct device *dev, const char *name, char *val,
			 size_t val_len)
{
	char *var, *value, *end, *eq;
	struct bcm47xx_nvram *nvram;

	if (!dev)
		return -ENODEV;

	nvram = dev_get_drvdata(dev);

	if (!name || !nvram || !nvram->nvram_len)
		return -EINVAL;

	/* Look for name=value and return value */
	var = nvram->nvram_buf + sizeof(struct nvram_header);
	end = nvram->nvram_buf + nvram->nvram_len - 2;
	end[0] = end[1] = '\0';
	for (; *var; var = value + strlen(value) + 1) {
		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);
		}
	}
	return -ENOENT;
}
EXPORT_SYMBOL(bcm47xx_nvram_getenv);

int bcm47xx_nvram_gpio_pin(const struct device *dev, const char *name)
{
	int i, err;
	char nvram_var[10];
	char buf[30];

	if (!dev)
		return -ENODEV;

	for (i = 0; i < 32; i++) {
		err = snprintf(nvram_var, sizeof(nvram_var), "gpio%i", i);
		if (err <= 0)
			continue;
		err = bcm47xx_nvram_getenv(dev, nvram_var, buf, sizeof(buf));
		if (err <= 0)
			continue;
		if (!strcmp(name, buf))
			return i;
	}
	return -ENOENT;
}
EXPORT_SYMBOL(bcm47xx_nvram_gpio_pin);

static int bcm47xx_nvram_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct bcm47xx_nvram *nvram;
	int err;
	struct resource flash_mem;
	void __iomem *mmio;

	/* Alloc */
	nvram = devm_kzalloc(dev, sizeof(*nvram), GFP_KERNEL);
	if (!nvram)
		return -ENOMEM;

	err = of_address_to_resource(np, 0, &flash_mem);
	if (err)
		return err;

	mmio = ioremap_nocache(flash_mem.start, resource_size(&flash_mem));
	if (!mmio)
		return -ENOMEM;

	err = nvram_find_and_copy(dev, mmio, resource_size(&flash_mem),
				  &nvram->nvram_buf, &nvram->nvram_len);
	if (err)
		goto err_unmap_mmio;

	platform_set_drvdata(pdev, nvram);

err_unmap_mmio:
	iounmap(mmio);
	return err;
}

static const struct of_device_id bcm47xx_nvram_of_match_table[] = {
	{ .compatible = "brcm,bcm47xx-nvram", },
	{},
};
MODULE_DEVICE_TABLE(of, mvebu_pcie_of_match_table);

static struct platform_driver bcm47xx_nvram_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "bcm47xx-nvram",
		.of_match_table = bcm47xx_nvram_of_match_table,
		/* driver unloading/unbinding currently not supported */
		.suppress_bind_attrs = true,
	},
	.probe = bcm47xx_nvram_probe,
};
module_platform_driver(bcm47xx_nvram_driver);

MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
MODULE_LICENSE("GPLv2");