1/*-
2 * Copyright 2018 Emmanuel Vadot <manu@FreeBSD.org>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26#include <sys/param.h>
27#include <sys/bus.h>
28#include <sys/kernel.h>
29#include <sys/malloc.h>
30#include <sys/mutex.h>
31
32#include <dev/fdt/fdt_common.h>
33#include <dev/ofw/ofw_bus.h>
34#include <dev/ofw/ofw_bus_subr.h>
35
36#include "nvmem.h"
37#include "nvmem_if.h"
38
39static int
40nvmem_get_cell_node(phandle_t node, int idx, phandle_t *cell)
41{
42	phandle_t *p_cell;
43	phandle_t cell_node;
44	int ncell;
45
46	if (!OF_hasprop(node, "nvmem-cells") ||
47	    !OF_hasprop(node, "nvmem-cell-names"))
48		return (ENOENT);
49
50	ncell = OF_getencprop_alloc_multi(node, "nvmem-cells", sizeof(*p_cell), (void **)&p_cell);
51	if (ncell <= 0)
52		return (ENOENT);
53
54	cell_node = OF_node_from_xref(p_cell[idx]);
55	if (cell_node == p_cell[idx]) {
56		if (bootverbose)
57			printf("nvmem_get_node: Cannot resolve phandle %x\n",
58			    p_cell[idx]);
59		OF_prop_free(p_cell);
60		return (ENOENT);
61	}
62
63	OF_prop_free(p_cell);
64	*cell = cell_node;
65
66	return (0);
67}
68
69int
70nvmem_get_cell_len(phandle_t node, const char *name)
71{
72	phandle_t cell_node;
73	uint32_t reg[2];
74	int rv, idx;
75
76	rv = ofw_bus_find_string_index(node, "nvmem-cell-names", name, &idx);
77	if (rv != 0)
78		return (rv);
79
80	rv = nvmem_get_cell_node(node, idx, &cell_node);
81	if (rv != 0)
82		return (rv);
83
84	if (OF_getencprop(cell_node, "reg", reg, sizeof(reg)) != sizeof(reg)) {
85		if (bootverbose)
86			printf("nvmem_get_cell_len: Cannot parse reg property of cell %s\n",
87			    name);
88		return (ENOENT);
89	}
90
91	return (reg[1]);
92}
93
94int
95nvmem_read_cell_by_idx(phandle_t node, int idx, void *cell, size_t buflen)
96{
97	phandle_t cell_node;
98	device_t provider;
99	uint32_t reg[2];
100	int rv;
101
102	rv = nvmem_get_cell_node(node, idx, &cell_node);
103	if (rv != 0)
104		return (rv);
105
106	/* Validate the reg property */
107	if (OF_getencprop(cell_node, "reg", reg, sizeof(reg)) != sizeof(reg)) {
108		if (bootverbose)
109			printf("nvmem_get_cell_by_name: Cannot parse reg property of cell %d\n",
110			    idx);
111		return (ENOENT);
112	}
113
114	if (buflen != reg[1])
115		return (EINVAL);
116
117	provider = OF_device_from_xref(OF_xref_from_node(OF_parent(cell_node)));
118	if (provider == NULL) {
119		if (bootverbose)
120			printf("nvmem_get_cell_by_idx: Cannot find the nvmem device\n");
121		return (ENXIO);
122	}
123
124	rv = NVMEM_READ(provider, reg[0], reg[1], cell);
125	if (rv != 0) {
126		return (rv);
127	}
128
129	return (0);
130}
131
132int
133nvmem_read_cell_by_name(phandle_t node, const char *name, void *cell, size_t buflen)
134{
135	int rv, idx;
136
137	rv = ofw_bus_find_string_index(node, "nvmem-cell-names", name, &idx);
138	if (rv != 0)
139		return (rv);
140
141	return (nvmem_read_cell_by_idx(node, idx, cell, buflen));
142}
143
144int
145nvmem_write_cell_by_idx(phandle_t node, int idx, void *cell, size_t buflen)
146{
147	phandle_t cell_node, prov_node;
148	device_t provider;
149	uint32_t reg[2];
150	int rv;
151
152	rv = nvmem_get_cell_node(node, idx, &cell_node);
153	if (rv != 0)
154		return (rv);
155
156	prov_node = OF_parent(cell_node);
157	if (OF_hasprop(prov_node, "read-only"))
158		return (ENXIO);
159
160	/* Validate the reg property */
161	if (OF_getencprop(cell_node, "reg", reg, sizeof(reg)) != sizeof(reg)) {
162		if (bootverbose)
163			printf("nvmem_get_cell_by_idx: Cannot parse reg property of cell %d\n",
164			    idx);
165		return (ENXIO);
166	}
167
168	if (buflen != reg[1])
169		return (EINVAL);
170
171	provider = OF_device_from_xref(OF_xref_from_node(prov_node));
172	if (provider == NULL) {
173		if (bootverbose)
174			printf("nvmem_get_cell_by_idx: Cannot find the nvmem device\n");
175		return (ENXIO);
176	}
177
178	rv = NVMEM_WRITE(provider, reg[0], reg[1], cell);
179	if (rv != 0) {
180		return (rv);
181	}
182
183	return (0);
184}
185
186int
187nvmem_write_cell_by_name(phandle_t node, const char *name, void *cell, size_t buflen)
188{
189	int rv, idx;
190
191	rv = ofw_bus_find_string_index(node, "nvmem-cell-names", name, &idx);
192	if (rv != 0)
193		return (rv);
194
195	return (nvmem_write_cell_by_idx(node, idx, cell, buflen));
196}
197