| /* | 
 |  * Copyright (c) 2014 Brian Swetland | 
 |  * Copyright (c) 2014 Travis Geiselbrecht | 
 |  * | 
 |  * Permission is hereby granted, free of charge, to any person obtaining | 
 |  * a copy of this software and associated documentation files | 
 |  * (the "Software"), to deal in the Software without restriction, | 
 |  * including without limitation the rights to use, copy, modify, merge, | 
 |  * publish, distribute, sublicense, and/or sell copies of the Software, | 
 |  * and to permit persons to whom the Software is furnished to do so, | 
 |  * subject to the following conditions: | 
 |  * | 
 |  * The above copyright notice and this permission notice shall be | 
 |  * included in all copies or substantial portions of the Software. | 
 |  * | 
 |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | 
 |  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | 
 |  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | 
 |  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | 
 |  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | 
 |  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | 
 |  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 
 |  */ | 
 | #include <debug.h> | 
 | #include <assert.h> | 
 | #include <trace.h> | 
 | #include <compiler.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <err.h> | 
 | #include <string.h> | 
 | #include <rand.h> | 
 | #include <reg.h> | 
 | #include <pow2.h> | 
 |  | 
 | #include <lib/bio.h> | 
 | #include <lib/console.h> | 
 | #include <dev/qspi.h> | 
 | #include <kernel/thread.h> | 
 |  | 
 | #include <platform/zynq.h> | 
 |  | 
 | #define LOCAL_TRACE 0 | 
 |  | 
 | // parameters specifically for the 16MB spansion S25FL128S flash | 
 | #define PARAMETER_AREA_SIZE (128*1024) | 
 | #define PAGE_PROGRAM_SIZE (256)     // can be something else based on the part | 
 | #define PAGE_ERASE_SLEEP_TIME (150) // amount of time before waiting to check if erase completed | 
 | #define SECTOR_ERASE_SIZE (4096) | 
 | #define LARGE_SECTOR_ERASE_SIZE (64*1024) | 
 |  | 
 | #define STS_PROGRAM_ERR (1<<6) | 
 | #define STS_ERASE_ERR (1<<5) | 
 | #define STS_BUSY (1<<0) | 
 |  | 
 | #define MAX_GEOMETRY_COUNT (2) | 
 |  | 
 | struct spi_flash { | 
 | 	bool detected; | 
 |  | 
 | 	struct qspi_ctxt qspi; | 
 | 	bdev_t bdev; | 
 | 	bio_erase_geometry_info_t geometry[MAX_GEOMETRY_COUNT]; | 
 |  | 
 | 	off_t size; | 
 | }; | 
 |  | 
 | static struct spi_flash flash; | 
 |  | 
 | static ssize_t spiflash_bdev_read(struct bdev *, void *buf, off_t offset, size_t len); | 
 | static ssize_t spiflash_bdev_read_block(struct bdev *, void *buf, bnum_t block, uint count); | 
 | static ssize_t spiflash_bdev_write_block(struct bdev *, const void *buf, bnum_t block, uint count); | 
 | static ssize_t spiflash_bdev_erase(struct bdev *, off_t offset, size_t len); | 
 | static int spiflash_ioctl(struct bdev *, int request, void *argp); | 
 |  | 
 | // adjust 24 bit address to be correct-byte-order for 32bit qspi commands | 
 | static uint32_t qspi_fix_addr(uint32_t addr) | 
 | { | 
 | 	DEBUG_ASSERT((addr & ~(0x00ffffff)) == 0); // only dealing with 24bit addresses | 
 |  | 
 | 	return ((addr & 0xff) << 24) | ((addr&0xff00) << 8) | ((addr>>8) & 0xff00); | 
 | } | 
 |  | 
 | static void qspi_rd32(struct qspi_ctxt *qspi, uint32_t addr, uint32_t *data, uint32_t count) | 
 | { | 
 | 	qspi_rd(qspi, qspi_fix_addr(addr) | 0x6B, 4, data, count); | 
 | } | 
 |  | 
 | static inline void qspi_wren(struct qspi_ctxt *qspi) | 
 | { | 
 | 	qspi_wr1(qspi, 0x06); | 
 | } | 
 |  | 
 | static inline void qspi_clsr(struct qspi_ctxt *qspi) | 
 | { | 
 | 	qspi_wr1(qspi, 0x30); | 
 | } | 
 |  | 
 | static inline uint32_t qspi_rd_cr1(struct qspi_ctxt *qspi) | 
 | { | 
 | 	return qspi_rd1(qspi, 0x35) >> 24; | 
 | } | 
 |  | 
 | static inline uint32_t qspi_rd_status(struct qspi_ctxt *qspi) | 
 | { | 
 | 	return qspi_rd1(qspi, 0x05) >> 24; | 
 | } | 
 |  | 
 | static inline void qspi_wr_status_cr1(struct qspi_ctxt *qspi, uint8_t status, uint8_t cr1) | 
 | { | 
 | 	uint32_t cmd = (cr1 << 16) | (status << 8) | 0x01; | 
 |  | 
 | 	qspi_wren(qspi); | 
 | 	qspi_wr3(qspi, cmd); | 
 | } | 
 |  | 
 | static ssize_t qspi_erase_sector(struct qspi_ctxt *qspi, uint32_t addr) | 
 | { | 
 | 	uint32_t cmd; | 
 | 	uint32_t status; | 
 | 	ssize_t toerase; | 
 |  | 
 | 	LTRACEF("addr 0x%x\n", addr); | 
 |  | 
 | 	DEBUG_ASSERT(qspi); | 
 |  | 
 | 	if (addr < PARAMETER_AREA_SIZE) { | 
 | 		// erase a small parameter sector (4K) | 
 | 		DEBUG_ASSERT(IS_ALIGNED(addr, SECTOR_ERASE_SIZE)); | 
 | 		if (!IS_ALIGNED(addr, SECTOR_ERASE_SIZE)) | 
 | 			return ERR_INVALID_ARGS; | 
 |  | 
 | 		cmd = 0x20; | 
 | 		toerase = SECTOR_ERASE_SIZE; | 
 | 	} else { | 
 | 		// erase a large sector (64k or 256k) | 
 | 		DEBUG_ASSERT(IS_ALIGNED(addr, LARGE_SECTOR_ERASE_SIZE)); | 
 | 		if (!IS_ALIGNED(addr, LARGE_SECTOR_ERASE_SIZE)) | 
 | 			return ERR_INVALID_ARGS; | 
 |  | 
 | 		cmd = 0xd8; | 
 | 		toerase = LARGE_SECTOR_ERASE_SIZE; | 
 | 	} | 
 |  | 
 | 	qspi_wren(qspi); | 
 | 	qspi_wr(qspi, qspi_fix_addr(addr) | cmd, 3, 0, 0); | 
 |  | 
 | 	thread_sleep(PAGE_ERASE_SLEEP_TIME); | 
 | 	while ((status = qspi_rd_status(qspi)) & STS_BUSY) | 
 | 		; | 
 |  | 
 | 	LTRACEF("status 0x%x\n", status); | 
 | 	if (status & (STS_PROGRAM_ERR | STS_ERASE_ERR)) { | 
 | 		TRACEF("failed @ 0x%x\n", addr); | 
 | 		qspi_clsr(qspi); | 
 | 		return ERR_IO; | 
 | 	} | 
 |  | 
 | 	return toerase; | 
 | } | 
 |  | 
 | static ssize_t qspi_write_page(struct qspi_ctxt *qspi, uint32_t addr, const uint8_t *data) | 
 | { | 
 | 	uint32_t oldkhz, status; | 
 |  | 
 | 	LTRACEF("addr 0x%x, data %p\n", addr, data); | 
 |  | 
 | 	DEBUG_ASSERT(qspi); | 
 | 	DEBUG_ASSERT(data); | 
 | 	DEBUG_ASSERT(IS_ALIGNED(addr, PAGE_PROGRAM_SIZE)); | 
 |  | 
 | 	if (!IS_ALIGNED(addr, PAGE_PROGRAM_SIZE)) | 
 | 		return ERR_INVALID_ARGS; | 
 |  | 
 | 	oldkhz = qspi->khz; | 
 | 	if (qspi_set_speed(qspi, 80000)) | 
 | 		return ERR_IO; | 
 |  | 
 | 	qspi_wren(qspi); | 
 | 	qspi_wr(qspi, qspi_fix_addr(addr) | 0x32, 3, (uint32_t *)data, PAGE_PROGRAM_SIZE / 4); | 
 | 	qspi_set_speed(qspi, oldkhz); | 
 |  | 
 | 	while ((status = qspi_rd_status(qspi)) & STS_BUSY) ; | 
 |  | 
 | 	if (status & (STS_PROGRAM_ERR | STS_ERASE_ERR)) { | 
 | 		printf("qspi_write_page failed @ %x\n", addr); | 
 | 		qspi_clsr(qspi); | 
 | 		return ERR_IO; | 
 | 	} | 
 | 	return PAGE_PROGRAM_SIZE; | 
 | } | 
 |  | 
 | static ssize_t spiflash_read_cfi(void *buf, size_t len) | 
 | { | 
 | 	DEBUG_ASSERT(len > 0 && (len % 4) == 0); | 
 |  | 
 | 	qspi_rd(&flash.qspi, 0x9f, 0, buf, len / 4); | 
 |  | 
 | 	if (len < 4) | 
 | 		return len; | 
 |  | 
 | 	/* look at byte 3 of the cfi, which says the total length of the cfi structure */ | 
 | 	size_t cfi_len = ((uint8_t *)buf)[3]; | 
 | 	if (cfi_len == 0) | 
 | 		cfi_len = 512; | 
 | 	else | 
 | 		cfi_len += 3; | 
 |  | 
 | 	return MIN(len, cfi_len); | 
 | } | 
 |  | 
 | static ssize_t spiflash_read_otp(void *buf, uint32_t addr, size_t len) | 
 | { | 
 | 	DEBUG_ASSERT(len > 0 && (len % 4) == 0); | 
 |  | 
 | 	if (len > 1024) | 
 | 		len = 1024; | 
 |  | 
 | 	qspi_rd(&flash.qspi, 0x4b, 4, buf, len / 4); | 
 |  | 
 | 	if (len < 4) | 
 | 		return len; | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | status_t spiflash_detect(void) | 
 | { | 
 | 	if (flash.detected) | 
 | 		return NO_ERROR; | 
 |  | 
 | 	qspi_init(&flash.qspi, 100000); | 
 |  | 
 | 	/* read and parse the cfi */ | 
 | 	uint8_t *buf = calloc(1, 512); | 
 | 	ssize_t len = spiflash_read_cfi(buf, 512); | 
 | 	if (len < 4) | 
 | 		goto nodetect; | 
 |  | 
 | 	LTRACEF("looking at vendor/device id combination: %02x:%02x:%02x\n", buf[0], buf[1], buf[2]); | 
 |  | 
 | 	/* at the moment, we only support particular spansion flashes */ | 
 | 	if (buf[0] != 0x01) goto nodetect; | 
 |  | 
 | 	if (buf[1] == 0x20 && buf[2] == 0x18) { | 
 | 		/* 128Mb version */ | 
 | 		flash.size = 16*1024*1024; | 
 | 	} else if (buf[1] == 0x02 && buf[2] == 0x19) { | 
 | 		/* 256Mb version */ | 
 | 		flash.size = 32*1024*1024; | 
 | 	} else { | 
 | 		TRACEF("unknown vendor/device id combination: %02x:%02x:%02x\n", | 
 | 			buf[0], buf[1], buf[2]); | 
 | 		goto nodetect; | 
 | 	} | 
 |  | 
 | 	/* Fill out our geometry info based on the CFI */ | 
 | 	size_t region_count = buf[0x2C]; | 
 | 	if (region_count > countof(flash.geometry)) { | 
 | 		TRACEF("erase region count (%zu) exceeds max allowed (%zu)\n", | 
 | 			   region_count, countof(flash.geometry)); | 
 | 		goto nodetect; | 
 | 	} | 
 |  | 
 | 	size_t offset = 0; | 
 | 	for (size_t i = 0; i < region_count; i++) { | 
 | 		const uint8_t* info = buf + 0x2D + (i << 2); | 
 | 		size_t pages      = ((((size_t)info[1]) << 8) | info[0]) + 1; | 
 | 		size_t erase_size = ((((size_t)info[3]) << 8) | info[2]) << 8; | 
 |  | 
 | 		if (!ispow2(erase_size)) { | 
 | 			TRACEF("Region %zu page size (%zu) is not a power of 2\n", | 
 | 					i, erase_size); | 
 | 			goto nodetect; | 
 | 		} | 
 |  | 
 | 		flash.geometry[i].erase_size  = erase_size; | 
 | 		flash.geometry[i].erase_shift = log2_uint(erase_size); | 
 | 		flash.geometry[i].start       = offset; | 
 | 		flash.geometry[i].size        = pages << flash.geometry[i].erase_shift; | 
 |  | 
 | 		size_t erase_mask = ((size_t)0x1 << flash.geometry[i].erase_shift) - 1; | 
 | 		if (offset & erase_mask) { | 
 | 			TRACEF("Region %zu not aligned to erase boundary (start %zu, erase size %zu)\n", | 
 | 					i, offset, erase_size); | 
 | 			goto nodetect; | 
 | 		} | 
 |  | 
 | 		offset += flash.geometry[i].size; | 
 | 	} | 
 |  | 
 | 	free(buf); | 
 |  | 
 | 	/* read the 16 byte random number out of the OTP area and add to the rand entropy pool */ | 
 | 	uint32_t r[4]; | 
 | 	memset(r, 0, sizeof(r)); | 
 | 	spiflash_read_otp(r, 0, 16); | 
 |  | 
 | 	LTRACEF("OTP random %08x%08x%08x%08x\n", r[0], r[1], r[2], r[3]); | 
 | 	rand_add_entropy(r, sizeof(r)); | 
 |  | 
 | 	flash.detected = true; | 
 |  | 
 | 	/* see if we're in serial mode */ | 
 | 	uint32_t cr1 = qspi_rd_cr1(&flash.qspi); | 
 | 	if ((cr1 & (1<<1)) == 0) { | 
 | 		printf("spiflash: device not in quad mode, cannot use for read/write\n"); | 
 | 		goto nouse; | 
 | 	} | 
 |  | 
 | 	/* construct the block device */ | 
 | 	bio_initialize_bdev(&flash.bdev, "spi0", | 
 | 						PAGE_PROGRAM_SIZE, flash.size / PAGE_PROGRAM_SIZE, | 
 | 						region_count, flash.geometry, BIO_FLAGS_NONE); | 
 |  | 
 | 	/* override our block device hooks */ | 
 | 	flash.bdev.read = &spiflash_bdev_read; | 
 | 	flash.bdev.read_block = &spiflash_bdev_read_block; | 
 | 	// flash.bdev.write has a default hook that will be okay | 
 | 	flash.bdev.write_block = &spiflash_bdev_write_block; | 
 | 	flash.bdev.erase = &spiflash_bdev_erase; | 
 | 	flash.bdev.ioctl = &spiflash_ioctl; | 
 |  | 
 | 	/* we erase to 0xff */ | 
 | 	flash.bdev.erase_byte = 0xff; | 
 |  | 
 | 	bio_register_device(&flash.bdev); | 
 |  | 
 | 	LTRACEF("found flash of size 0x%llx\n", flash.size); | 
 |  | 
 | nouse: | 
 | 	return NO_ERROR; | 
 |  | 
 | nodetect: | 
 | 	LTRACEF("flash not found\n"); | 
 |  | 
 | 	free(buf); | 
 | 	flash.detected = false; | 
 | 	return ERR_NOT_FOUND; | 
 | } | 
 |  | 
 | // bio layer hooks | 
 | static ssize_t spiflash_bdev_read(struct bdev *bdev, void *buf, off_t offset, size_t len) | 
 | { | 
 | 	LTRACEF("dev %p, buf %p, offset 0x%llx, len 0x%zx\n", bdev, buf, offset, len); | 
 |  | 
 | 	DEBUG_ASSERT(flash.detected); | 
 |  | 
 | 	len = bio_trim_range(bdev, offset, len); | 
 | 	if (len == 0) | 
 | 		return 0; | 
 |  | 
 | 	// XXX handle not mulitple of 4 | 
 | 	qspi_rd32(&flash.qspi, offset, buf, len / 4); | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t spiflash_bdev_read_block(struct bdev *bdev, void *buf, bnum_t block, uint count) | 
 | { | 
 | 	LTRACEF("dev %p, buf %p, block 0x%x, count %u\n", bdev, buf, block, count); | 
 |  | 
 | 	count = bio_trim_block_range(bdev, block, count); | 
 | 	if (count == 0) | 
 | 		return 0; | 
 |  | 
 | 	return spiflash_bdev_read(bdev, buf, block << bdev->block_shift, count << bdev->block_shift); | 
 | } | 
 |  | 
 | static ssize_t spiflash_bdev_write_block(struct bdev *bdev, const void *_buf, bnum_t block, uint count) | 
 | { | 
 | 	LTRACEF("dev %p, buf %p, block 0x%x, count %u\n", bdev, _buf, block, count); | 
 |  | 
 | 	DEBUG_ASSERT(bdev->block_size == PAGE_PROGRAM_SIZE); | 
 |  | 
 | 	count = bio_trim_block_range(bdev, block, count); | 
 | 	if (count == 0) | 
 | 		return 0; | 
 |  | 
 | 	const uint8_t *buf = _buf; | 
 |  | 
 | 	ssize_t written = 0; | 
 | 	while (count > 0) { | 
 | 		ssize_t err = qspi_write_page(&flash.qspi, block * PAGE_PROGRAM_SIZE, buf); | 
 | 		if (err < 0) | 
 | 			return err; | 
 |  | 
 | 		buf += PAGE_PROGRAM_SIZE; | 
 | 		written += err; | 
 | 		block++; | 
 | 		count--; | 
 | 	} | 
 |  | 
 | 	return written; | 
 | } | 
 |  | 
 | static ssize_t spiflash_bdev_erase(struct bdev *bdev, off_t offset, size_t len) | 
 | { | 
 | 	LTRACEF("dev %p, offset 0x%llx, len 0x%zx\n", bdev, offset, len); | 
 |  | 
 | 	len = bio_trim_range(bdev, offset, len); | 
 | 	if (len == 0) | 
 | 		return 0; | 
 |  | 
 | 	ssize_t erased = 0; | 
 | 	while (erased < (ssize_t)len) { | 
 | 		ssize_t err = qspi_erase_sector(&flash.qspi, offset); | 
 | 		if (err < 0) | 
 | 			return err; | 
 |  | 
 | 		erased += err; | 
 | 		offset += err; | 
 | 	} | 
 |  | 
 | 	return erased; | 
 | } | 
 |  | 
 | static int spiflash_ioctl(struct bdev *bdev, int request, void *argp) | 
 | { | 
 | 	LTRACEF("dev %p, request %d, argp %p\n", bdev, request, argp); | 
 |  | 
 | 	int ret = NO_ERROR; | 
 | 	switch (request) { | 
 | 		case BIO_IOCTL_GET_MEM_MAP: | 
 | 			/* put the device into linear mode */ | 
 | 			ret = qspi_enable_linear(&flash.qspi); | 
 | 			// Fallthrough. | 
 | 		case BIO_IOCTL_GET_MAP_ADDR: | 
 | 			if (argp) | 
 | 				*(void **)argp = (void *)QSPI_LINEAR_BASE; | 
 | 			break; | 
 | 		case BIO_IOCTL_PUT_MEM_MAP: | 
 | 			/* put the device back into regular mode */ | 
 | 			ret = qspi_disable_linear(&flash.qspi); | 
 | 			break; | 
 | 		default: | 
 | 			ret = ERR_NOT_SUPPORTED; | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | // debug tests | 
 | int cmd_spiflash(int argc, const cmd_args *argv) | 
 | { | 
 | 	if (argc < 2) { | 
 | notenoughargs: | 
 | 		printf("not enough arguments\n"); | 
 | usage: | 
 | 		printf("usage:\n"); | 
 | #if LK_DEBUGLEVEL > 1 | 
 | 		printf("\t%s detect\n", argv[0].str); | 
 | 		printf("\t%s cfi\n", argv[0].str); | 
 | 		printf("\t%s cr1\n", argv[0].str); | 
 | 		printf("\t%s otp\n", argv[0].str); | 
 | 		printf("\t%s linear [true/false]\n", argv[0].str); | 
 | 		printf("\t%s read <offset> <length>\n", argv[0].str); | 
 | 		printf("\t%s write <offset> <length> <address>\n", argv[0].str); | 
 | 		printf("\t%s erase <offset>\n", argv[0].str); | 
 | #endif | 
 | 		printf("\t%s setquad (dangerous)\n", argv[0].str); | 
 | 		return ERR_INVALID_ARGS; | 
 | 	} | 
 |  | 
 | #if LK_DEBUGLEVEL > 1 | 
 | 	if (!strcmp(argv[1].str, "detect")) { | 
 | 		spiflash_detect(); | 
 | 	} else if (!strcmp(argv[1].str, "cr1")) { | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		uint32_t cr1 = qspi_rd_cr1(&flash.qspi); | 
 | 		printf("cr1 0x%x\n", cr1); | 
 | 	} else if (!strcmp(argv[1].str, "cfi")) { | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		uint8_t *buf = calloc(1, 512); | 
 | 		ssize_t len = spiflash_read_cfi(buf, 512); | 
 | 		printf("returned cfi len %ld\n", len); | 
 |  | 
 | 		hexdump8(buf, len); | 
 |  | 
 | 		free(buf); | 
 | 	} else if (!strcmp(argv[1].str, "otp")) { | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		uint8_t *buf = calloc(1, 1024); | 
 | 		ssize_t len = spiflash_read_otp(buf, 0, 1024); | 
 | 		printf("spiflash_read_otp returns %ld\n", len); | 
 |  | 
 | 		hexdump8(buf, len); | 
 |  | 
 | 		free(buf); | 
 | 	} else if (!strcmp(argv[1].str, "linear")) { | 
 | 		if (argc < 3) goto notenoughargs; | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		if (argv[2].b) | 
 | 			qspi_enable_linear(&flash.qspi); | 
 | 		else | 
 | 			qspi_disable_linear(&flash.qspi); | 
 | 	} else if (!strcmp(argv[1].str, "read")) { | 
 | 		if (argc < 4) goto notenoughargs; | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		uint8_t *buf = calloc(1, argv[3].u); | 
 |  | 
 | 		qspi_rd32(&flash.qspi, argv[2].u, (uint32_t *)buf, argv[3].u / 4); | 
 |  | 
 | 		hexdump8(buf, argv[3].u); | 
 | 		free(buf); | 
 | 	} else if (!strcmp(argv[1].str, "write")) { | 
 | 		if (argc < 5) goto notenoughargs; | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		status_t err = qspi_write_page(&flash.qspi, argv[2].u, argv[4].p); | 
 | 		printf("write_page returns %d\n", err); | 
 | 	} else if (!strcmp(argv[1].str, "erase")) { | 
 | 		if (argc < 3) goto notenoughargs; | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		status_t err = qspi_erase_sector(&flash.qspi, argv[2].u); | 
 | 		printf("erase returns %d\n", err); | 
 | 	} else | 
 | #endif | 
 | 	if (!strcmp(argv[1].str, "setquad")) { | 
 | 		if (!flash.detected) { | 
 | 			printf("flash not detected\n"); | 
 | 			return -1; | 
 | 		} | 
 |  | 
 | 		uint32_t cr1 = qspi_rd_cr1(&flash.qspi); | 
 | 		printf("cr1 before 0x%x\n", cr1); | 
 |  | 
 | 		if (cr1 & (1<<1)) { | 
 | 			printf("flash already in quad mode\n"); | 
 | 			return 0; | 
 | 		} | 
 |  | 
 | 		qspi_wr_status_cr1(&flash.qspi, 0, cr1 | (1<<1)); | 
 |  | 
 | 		thread_sleep(500); | 
 | 		cr1 = qspi_rd_cr1(&flash.qspi); | 
 | 		printf("cr1 after 0x%x\n", cr1); | 
 | 	} else { | 
 | 		printf("unknown command\n"); | 
 | 		goto usage; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | #if defined(WITH_LIB_CONSOLE) | 
 | #include <lib/console.h> | 
 |  | 
 | STATIC_COMMAND_START | 
 | STATIC_COMMAND("spiflash", "spi flash manipulation utilities", cmd_spiflash) | 
 | STATIC_COMMAND_END(qspi); | 
 |  | 
 | #endif | 
 |  | 
 | // vim: set ts=4 sw=4 noexpandtab: |