1/*
2 * Copyright (c) 2006 Maxim Sobolev <sobomax@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
18 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
22 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
23 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29#include <sys/param.h>
30#include <sys/systm.h>
31#include <sys/module.h>
32#include <sys/bus.h>
33#include <sys/conf.h>
34#include <sys/kernel.h>
35#include <sys/uio.h>
36
37#include <dev/ofw/openfirm.h>
38#include <dev/ofw/ofw_bus.h>
39#include <dev/ofw/ofw_bus_subr.h>
40
41#include <machine/bus.h>
42#include <machine/md_var.h>
43#include <machine/pio.h>
44#include <machine/resource.h>
45
46#include <sys/rman.h>
47
48#include <dev/powermac_nvram/powermac_nvramvar.h>
49
50#include <vm/vm.h>
51#include <vm/pmap.h>
52
53/*
54 * Device interface.
55 */
56static int		powermac_nvram_probe(device_t);
57static int		powermac_nvram_attach(device_t);
58static int		powermac_nvram_detach(device_t);
59
60/* Helper functions */
61static int		powermac_nvram_check(void *data);
62static int		chrp_checksum(int sum, uint8_t *, uint8_t *);
63static uint32_t		adler_checksum(uint8_t *, int);
64static int		erase_bank(device_t, uint8_t *);
65static int		write_bank(device_t, uint8_t *, uint8_t *);
66
67/*
68 * Driver methods.
69 */
70static device_method_t	powermac_nvram_methods[] = {
71	/* Device interface */
72	DEVMETHOD(device_probe,		powermac_nvram_probe),
73	DEVMETHOD(device_attach,	powermac_nvram_attach),
74	DEVMETHOD(device_detach,	powermac_nvram_detach),
75
76	{ 0, 0 }
77};
78
79static driver_t	powermac_nvram_driver = {
80	"powermac_nvram",
81	powermac_nvram_methods,
82	sizeof(struct powermac_nvram_softc)
83};
84
85static devclass_t powermac_nvram_devclass;
86
87DRIVER_MODULE(powermac_nvram, ofwbus, powermac_nvram_driver, powermac_nvram_devclass, 0, 0);
88
89/*
90 * Cdev methods.
91 */
92
93static	d_open_t	powermac_nvram_open;
94static	d_close_t	powermac_nvram_close;
95static	d_read_t	powermac_nvram_read;
96static	d_write_t	powermac_nvram_write;
97
98static struct cdevsw powermac_nvram_cdevsw = {
99	.d_version =	D_VERSION,
100	.d_flags =	D_NEEDGIANT,
101	.d_open =	powermac_nvram_open,
102	.d_close =	powermac_nvram_close,
103	.d_read =	powermac_nvram_read,
104	.d_write =	powermac_nvram_write,
105	.d_name =	"powermac_nvram",
106};
107
108static int
109powermac_nvram_probe(device_t dev)
110{
111	const char	*type, *compatible;
112
113	type = ofw_bus_get_type(dev);
114	compatible = ofw_bus_get_compat(dev);
115
116	if (type == NULL || compatible == NULL)
117		return ENXIO;
118
119	if (strcmp(type, "nvram") != 0)
120		return ENXIO;
121	if (strcmp(compatible, "amd-0137") != 0 &&
122	    !ofw_bus_is_compatible(dev, "nvram,flash"))
123		return ENXIO;
124
125	device_set_desc(dev, "Apple NVRAM");
126	return 0;
127}
128
129static int
130powermac_nvram_attach(device_t dev)
131{
132	struct powermac_nvram_softc *sc;
133	const char	*compatible;
134	phandle_t node;
135	u_int32_t reg[3];
136	int gen0, gen1, i;
137
138	node = ofw_bus_get_node(dev);
139	sc = device_get_softc(dev);
140
141	if ((i = OF_getprop(node, "reg", reg, sizeof(reg))) < 8)
142		return ENXIO;
143
144	sc->sc_dev = dev;
145	sc->sc_node = node;
146
147	compatible = ofw_bus_get_compat(dev);
148	if (strcmp(compatible, "amd-0137") == 0)
149		sc->sc_type = FLASH_TYPE_AMD;
150	else
151		sc->sc_type = FLASH_TYPE_SM;
152
153	/*
154	 * Find which byte of reg corresponds to the 32-bit physical address.
155	 * We should probably read #address-cells from /chosen instead.
156	 */
157	i = (i/4) - 2;
158
159	sc->sc_bank0 = (vm_offset_t)pmap_mapdev(reg[i], NVRAM_SIZE * 2);
160	sc->sc_bank1 = sc->sc_bank0 + NVRAM_SIZE;
161
162	gen0 = powermac_nvram_check((void *)sc->sc_bank0);
163	gen1 = powermac_nvram_check((void *)sc->sc_bank1);
164
165	if (gen0 == -1 && gen1 == -1) {
166		if ((void *)sc->sc_bank0 != NULL)
167			pmap_unmapdev(sc->sc_bank0, NVRAM_SIZE * 2);
168		device_printf(dev, "both banks appear to be corrupt\n");
169		return ENXIO;
170	}
171	device_printf(dev, "bank0 generation %d, bank1 generation %d\n",
172	    gen0, gen1);
173
174	sc->sc_bank = (gen0 > gen1) ? sc->sc_bank0 : sc->sc_bank1;
175	bcopy((void *)sc->sc_bank, (void *)sc->sc_data, NVRAM_SIZE);
176
177	sc->sc_cdev = make_dev(&powermac_nvram_cdevsw, 0, 0, 0, 0600,
178	    "powermac_nvram");
179	sc->sc_cdev->si_drv1 = sc;
180
181	return 0;
182}
183
184static int
185powermac_nvram_detach(device_t dev)
186{
187	struct powermac_nvram_softc *sc;
188
189	sc = device_get_softc(dev);
190
191	if ((void *)sc->sc_bank0 != NULL)
192		pmap_unmapdev(sc->sc_bank0, NVRAM_SIZE * 2);
193
194	if (sc->sc_cdev != NULL)
195		destroy_dev(sc->sc_cdev);
196
197	return 0;
198}
199
200static int
201powermac_nvram_open(struct cdev *dev, int flags, int fmt, struct thread *td)
202{
203	struct powermac_nvram_softc *sc = dev->si_drv1;
204
205	if (sc->sc_isopen)
206		return EBUSY;
207	sc->sc_isopen = 1;
208	sc->sc_rpos = sc->sc_wpos = 0;
209	return 0;
210}
211
212static int
213powermac_nvram_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
214{
215	struct powermac_nvram_softc *sc = dev->si_drv1;
216	struct core99_header *header;
217	vm_offset_t bank;
218
219	if (sc->sc_wpos != sizeof(sc->sc_data)) {
220		/* Short write, restore in-memory copy */
221		bcopy((void *)sc->sc_bank, (void *)sc->sc_data, NVRAM_SIZE);
222		sc->sc_isopen = 0;
223		return 0;
224	}
225
226	header = (struct core99_header *)sc->sc_data;
227
228	header->generation = ((struct core99_header *)sc->sc_bank)->generation;
229	header->generation++;
230	header->chrp_header.signature = CORE99_SIGNATURE;
231
232	header->adler_checksum =
233	    adler_checksum((uint8_t *)&(header->generation),
234	    NVRAM_SIZE - offsetof(struct core99_header, generation));
235	header->chrp_header.chrp_checksum = chrp_checksum(header->chrp_header.signature,
236	    (uint8_t *)&(header->chrp_header.length),
237	    (uint8_t *)&(header->adler_checksum));
238
239	bank = (sc->sc_bank == sc->sc_bank0) ? sc->sc_bank1 : sc->sc_bank0;
240	if (erase_bank(sc->sc_dev, (uint8_t *)bank) != 0 ||
241	    write_bank(sc->sc_dev, (uint8_t *)bank, sc->sc_data) != 0) {
242		sc->sc_isopen = 0;
243		return ENOSPC;
244	}
245	sc->sc_bank = bank;
246	sc->sc_isopen = 0;
247	return 0;
248}
249
250static int
251powermac_nvram_read(struct cdev *dev, struct uio *uio, int ioflag)
252{
253	int rv, amnt, data_available;
254	struct powermac_nvram_softc *sc = dev->si_drv1;
255
256	rv = 0;
257	while (uio->uio_resid > 0) {
258		data_available = sizeof(sc->sc_data) - sc->sc_rpos;
259		if (data_available > 0) {
260			amnt = MIN(uio->uio_resid, data_available);
261			rv = uiomove((void *)(sc->sc_data + sc->sc_rpos),
262			    amnt, uio);
263			if (rv != 0)
264				break;
265			sc->sc_rpos += amnt;
266		} else {
267			break;
268		}
269	}
270	return rv;
271}
272
273static int
274powermac_nvram_write(struct cdev *dev, struct uio *uio, int ioflag)
275{
276	int rv, amnt, data_available;
277	struct powermac_nvram_softc *sc = dev->si_drv1;
278
279	if (sc->sc_wpos >= sizeof(sc->sc_data))
280		return EINVAL;
281
282	rv = 0;
283	while (uio->uio_resid > 0) {
284		data_available = sizeof(sc->sc_data) - sc->sc_wpos;
285		if (data_available > 0) {
286			amnt = MIN(uio->uio_resid, data_available);
287			rv = uiomove((void *)(sc->sc_data + sc->sc_wpos),
288			    amnt, uio);
289			if (rv != 0)
290				break;
291			sc->sc_wpos += amnt;
292		} else {
293			break;
294		}
295	}
296	return rv;
297}
298
299static int
300powermac_nvram_check(void *data)
301{
302	struct core99_header *header;
303
304	header = (struct core99_header *)data;
305
306	if (header->chrp_header.signature != CORE99_SIGNATURE)
307		return -1;
308	if (header->chrp_header.chrp_checksum !=
309	    chrp_checksum(header->chrp_header.signature,
310	    (uint8_t *)&(header->chrp_header.length),
311	    (uint8_t *)&(header->adler_checksum)))
312		return -1;
313	if (header->adler_checksum !=
314	    adler_checksum((uint8_t *)&(header->generation),
315	    NVRAM_SIZE - offsetof(struct core99_header, generation)))
316		return -1;
317	return header->generation;
318}
319
320static int
321chrp_checksum(int sum, uint8_t *data, uint8_t *end)
322{
323
324	for (; data < end; data++)
325		sum += data[0];
326	while (sum > 0xff)
327		sum = (sum & 0xff) + (sum >> 8);
328	return sum;
329}
330
331static uint32_t
332adler_checksum(uint8_t *data, int len)
333{
334	uint32_t low, high;
335	int i;
336
337	low = 1;
338	high = 0;
339	for (i = 0; i < len; i++) {
340		if ((i % 5000) == 0) {
341			high %= 65521UL;
342			high %= 65521UL;
343		}
344		low += data[i];
345		high += low;
346	}
347	low %= 65521UL;
348	high %= 65521UL;
349
350	return (high << 16) | low;
351}
352
353#define	OUTB_DELAY(a, v)	outb(a, v); DELAY(1);
354
355static int
356wait_operation_complete_amd(uint8_t *bank)
357{
358	int i;
359
360	for (i = 1000000; i != 0; i--)
361		if ((inb(bank) ^ inb(bank)) == 0)
362			return 0;
363	return -1;
364}
365
366static int
367erase_bank_amd(device_t dev, uint8_t *bank)
368{
369	unsigned int i;
370
371	/* Unlock 1 */
372	OUTB_DELAY(bank + 0x555, 0xaa);
373	/* Unlock 2 */
374	OUTB_DELAY(bank + 0x2aa, 0x55);
375
376	/* Sector-Erase */
377	OUTB_DELAY(bank + 0x555, 0x80);
378	OUTB_DELAY(bank + 0x555, 0xaa);
379	OUTB_DELAY(bank + 0x2aa, 0x55);
380	OUTB_DELAY(bank, 0x30);
381
382	if (wait_operation_complete_amd(bank) != 0) {
383		device_printf(dev, "flash erase timeout\n");
384		return -1;
385	}
386
387	/* Reset */
388	OUTB_DELAY(bank, 0xf0);
389
390	for (i = 0; i < NVRAM_SIZE; i++) {
391		if (bank[i] != 0xff) {
392			device_printf(dev, "flash erase has failed\n");
393			return -1;
394		}
395	}
396	return 0;
397}
398
399static int
400write_bank_amd(device_t dev, uint8_t *bank, uint8_t *data)
401{
402	unsigned int i;
403
404	for (i = 0; i < NVRAM_SIZE; i++) {
405		/* Unlock 1 */
406		OUTB_DELAY(bank + 0x555, 0xaa);
407		/* Unlock 2 */
408		OUTB_DELAY(bank + 0x2aa, 0x55);
409
410		/* Write single word */
411		OUTB_DELAY(bank + 0x555, 0xa0);
412		OUTB_DELAY(bank + i, data[i]);
413		if (wait_operation_complete_amd(bank) != 0) {
414			device_printf(dev, "flash write timeout\n");
415			return -1;
416		}
417	}
418
419	/* Reset */
420	OUTB_DELAY(bank, 0xf0);
421
422	for (i = 0; i < NVRAM_SIZE; i++) {
423		if (bank[i] != data[i]) {
424			device_printf(dev, "flash write has failed\n");
425			return -1;
426		}
427	}
428	return 0;
429}
430
431static int
432wait_operation_complete_sm(uint8_t *bank)
433{
434	int i;
435
436	for (i = 1000000; i != 0; i--) {
437		outb(bank, SM_FLASH_CMD_READ_STATUS);
438		if (inb(bank) & SM_FLASH_STATUS_DONE)
439			return (0);
440	}
441	return (-1);
442}
443
444static int
445erase_bank_sm(device_t dev, uint8_t *bank)
446{
447	unsigned int i;
448
449	outb(bank, SM_FLASH_CMD_ERASE_SETUP);
450	outb(bank, SM_FLASH_CMD_ERASE_CONFIRM);
451
452	if (wait_operation_complete_sm(bank) != 0) {
453		device_printf(dev, "flash erase timeout\n");
454		return (-1);
455	}
456
457	outb(bank, SM_FLASH_CMD_CLEAR_STATUS);
458	outb(bank, SM_FLASH_CMD_RESET);
459
460	for (i = 0; i < NVRAM_SIZE; i++) {
461		if (bank[i] != 0xff) {
462			device_printf(dev, "flash write has failed\n");
463			return (-1);
464		}
465	}
466	return (0);
467}
468
469static int
470write_bank_sm(device_t dev, uint8_t *bank, uint8_t *data)
471{
472	unsigned int i;
473
474	for (i = 0; i < NVRAM_SIZE; i++) {
475		OUTB_DELAY(bank + i, SM_FLASH_CMD_WRITE_SETUP);
476		outb(bank + i, data[i]);
477		if (wait_operation_complete_sm(bank) != 0) {
478			device_printf(dev, "flash write error/timeout\n");
479			break;
480		}
481	}
482
483	outb(bank, SM_FLASH_CMD_CLEAR_STATUS);
484	outb(bank, SM_FLASH_CMD_RESET);
485
486	for (i = 0; i < NVRAM_SIZE; i++) {
487		if (bank[i] != data[i]) {
488			device_printf(dev, "flash write has failed\n");
489			return (-1);
490		}
491	}
492	return (0);
493}
494
495static int
496erase_bank(device_t dev, uint8_t *bank)
497{
498	struct powermac_nvram_softc *sc;
499
500	sc = device_get_softc(dev);
501	if (sc->sc_type == FLASH_TYPE_AMD)
502		return (erase_bank_amd(dev, bank));
503	else
504		return (erase_bank_sm(dev, bank));
505}
506
507static int
508write_bank(device_t dev, uint8_t *bank, uint8_t *data)
509{
510	struct powermac_nvram_softc *sc;
511
512	sc = device_get_softc(dev);
513	if (sc->sc_type == FLASH_TYPE_AMD)
514		return (write_bank_amd(dev, bank, data));
515	else
516		return (write_bank_sm(dev, bank, data));
517}
518