diff options
Diffstat (limited to 'src/drivers/ao_m25.c')
| -rw-r--r-- | src/drivers/ao_m25.c | 380 | 
1 files changed, 380 insertions, 0 deletions
| diff --git a/src/drivers/ao_m25.c b/src/drivers/ao_m25.c new file mode 100644 index 00000000..d7208273 --- /dev/null +++ b/src/drivers/ao_m25.c @@ -0,0 +1,380 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * 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; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" + +/* Total bytes of available storage */ +__pdata uint32_t	ao_storage_total; + +/* Block size - device is erased in these units. At least 256 bytes */ +__pdata uint32_t	ao_storage_block; + +/* Byte offset of config block. Will be ao_storage_block bytes long */ +__pdata uint32_t	ao_storage_config; + +/* Storage unit size - device reads and writes must be within blocks of this size. Usually 256 bytes. */ +__pdata uint16_t	ao_storage_unit; + +/* + * Each flash chip is arranged in 64kB sectors; the + * chip cannot erase in units smaller than that. + * + * Writing happens in units of 256 byte pages and + * can only change bits from 1 to 0. So, you can rewrite + * the same contents, or append to an existing page easily enough + */ + +#define M25_WREN	0x06	/* Write Enable */ +#define M25_WRDI	0x04	/* Write Disable */ +#define M25_RDID	0x9f	/* Read Identification */ +#define M25_RDSR	0x05	/* Read Status Register */ +#define M25_WRSR	0x01	/* Write Status Register */ +#define M25_READ	0x03	/* Read Data Bytes */ +#define M25_FAST_READ	0x0b	/* Read Data Bytes at Higher Speed */ +#define M25_PP		0x02	/* Page Program */ +#define M25_SE		0xd8	/* Sector Erase */ +#define M25_BE		0xc7	/* Bulk Erase */ +#define M25_DP		0xb9	/* Deep Power-down */ + +/* RDID response */ +#define M25_MANUF_OFFSET	0 +#define M25_MEMORY_TYPE_OFFSET	1 +#define M25_CAPACITY_OFFSET	2 +#define M25_UID_OFFSET		3 +#define M25_CFI_OFFSET		4 +#define M25_RDID_LEN		4	/* that's all we need */ + +#define M25_CAPACITY_128KB	0x11 +#define M25_CAPACITY_256KB	0x12 +#define M25_CAPACITY_512KB	0x13 +#define M25_CAPACITY_1MB	0x14 +#define M25_CAPACITY_2MB	0x15 + +/* + * Status register bits + */ + +#define M25_STATUS_SRWD		(1 << 7)	/* Status register write disable */ +#define M25_STATUS_BP_MASK	(7 << 2)	/* Block protect bits */ +#define M25_STATUS_BP_SHIFT	(2) +#define M25_STATUS_WEL		(1 << 1)	/* Write enable latch */ +#define M25_STATUS_WIP		(1 << 0)	/* Write in progress */ + +/* + * On teleterra, the m25 chip select pins are + * wired on P0_0 through P0_3. + */ + +#if M25_MAX_CHIPS > 1 +static uint8_t ao_m25_size[M25_MAX_CHIPS];	/* number of sectors in each chip */ +static uint8_t ao_m25_pin[M25_MAX_CHIPS];	/* chip select pin for each chip */ +static uint8_t ao_m25_numchips;			/* number of chips detected */ +#endif +static uint8_t ao_m25_total;			/* total sectors available */ +static uint8_t ao_m25_wip;			/* write in progress */ + +static __xdata uint8_t ao_m25_mutex; + +/* + * This little array is abused to send and receive data. A particular + * caution -- the read and write addresses are written into the last + * three bytes of the array by ao_m25_set_page_address and then the + * first byte is used by ao_m25_wait_wip and ao_m25_write_enable, neither + * of which touch those last three bytes. + */ + +static __xdata uint8_t	ao_m25_instruction[4]; + +#define M25_SELECT(cs)		ao_spi_get_mask(SPI_CS_PORT,cs) +#define M25_DESELECT(cs)	ao_spi_put_mask(SPI_CS_PORT,cs) + +#define M25_BLOCK_SHIFT			16 +#define M25_BLOCK			65536L +#define M25_POS_TO_SECTOR(pos)		((uint8_t) ((pos) >> M25_BLOCK_SHIFT)) +#define M25_SECTOR_TO_POS(sector)	(((uint32_t) (sector)) << M25_BLOCK_SHIFT) + +/* + * Block until the specified chip is done writing + */ +static void +ao_m25_wait_wip(uint8_t cs) +{ +	if (ao_m25_wip & cs) { +		M25_SELECT(cs); +		ao_m25_instruction[0] = M25_RDSR; +		ao_spi_send(ao_m25_instruction, 1); +		do { +			ao_spi_recv(ao_m25_instruction, 1); +		} while (ao_m25_instruction[0] & M25_STATUS_WIP); +		M25_DESELECT(cs); +		ao_m25_wip &= ~cs; +	} +} + +/* + * Set the write enable latch so that page program and sector + * erase commands will work. Also mark the chip as busy writing + * so that future operations will block until the WIP bit goes off + */ +static void +ao_m25_write_enable(uint8_t cs) +{ +	M25_SELECT(cs); +	ao_m25_instruction[0] = M25_WREN; +	ao_spi_send(&ao_m25_instruction, 1); +	M25_DESELECT(cs); +	ao_m25_wip |= cs; +} + + +/* + * Returns the number of 64kB sectors + */ +static uint8_t +ao_m25_read_capacity(uint8_t cs) +{ +	uint8_t	capacity; +	M25_SELECT(cs); +	ao_m25_instruction[0] = M25_RDID; +	ao_spi_send(ao_m25_instruction, 1); +	ao_spi_recv(ao_m25_instruction, M25_RDID_LEN); +	M25_DESELECT(cs); + +	/* Check to see if the chip is present */ +	if (ao_m25_instruction[0] == 0xff) +		return 0; +	capacity = ao_m25_instruction[M25_CAPACITY_OFFSET]; + +	/* Sanity check capacity number */ +	if (capacity < 0x11 || 0x1f < capacity) +		return 0; +	return 1 << (capacity - 0x10); +} + +static uint8_t +ao_m25_set_address(uint32_t pos) +{ +	uint8_t	chip; +#if M25_MAX_CHIPS > 1 +	uint8_t	size; + +	for (chip = 0; chip < ao_m25_numchips; chip++) { +		size = ao_m25_size[chip]; +		if (M25_POS_TO_SECTOR(pos) < size) +			break; +		pos -= M25_SECTOR_TO_POS(size); +	} +	if (chip == ao_m25_numchips) +		return 0xff; + +	chip = ao_m25_pin[chip]; +#else +	chip = M25_CS_MASK; +#endif +	ao_m25_wait_wip(chip); + +	ao_m25_instruction[1] = pos >> 16; +	ao_m25_instruction[2] = pos >> 8; +	ao_m25_instruction[3] = pos; +	return chip; +} + +/* + * Scan the possible chip select lines + * to see which flash chips are connected + */ +static uint8_t +ao_m25_scan(void) +{ +#if M25_MAX_CHIPS > 1 +	uint8_t	pin, size; +#endif + +	if (ao_m25_total) +		return 1; + +#if M25_MAX_CHIPS > 1 +	ao_m25_numchips = 0; +	for (pin = 1; pin != 0; pin <<= 1) { +		if (M25_CS_MASK & pin) { +			size = ao_m25_read_capacity(pin); +			if (size != 0) { +				ao_m25_size[ao_m25_numchips] = size; +				ao_m25_pin[ao_m25_numchips] = pin; +				ao_m25_total += size; +				ao_m25_numchips++; +			} +		} +	} +#else +	ao_m25_total = ao_m25_read_capacity(M25_CS_MASK); +#endif +	if (!ao_m25_total) +		return 0; +	ao_storage_total = M25_SECTOR_TO_POS(ao_m25_total); +	ao_storage_block = M25_BLOCK; +	ao_storage_config = ao_storage_total - M25_BLOCK; +	ao_storage_unit = 256; +	return 1; +} + +/* + * Erase the specified sector + */ +uint8_t +ao_storage_erase(uint32_t pos) __reentrant +{ +	uint8_t	cs; + +	if (pos >= ao_storage_total || pos + ao_storage_block > ao_storage_total) +		return 0; + +	ao_mutex_get(&ao_m25_mutex); +	ao_m25_scan(); + +	cs = ao_m25_set_address(pos); + +	ao_m25_wait_wip(cs); +	ao_m25_write_enable(cs); + +	ao_m25_instruction[0] = M25_SE; +	M25_SELECT(cs); +	ao_spi_send(ao_m25_instruction, 4); +	M25_DESELECT(cs); +	ao_m25_wip |= cs; + +	ao_mutex_put(&ao_m25_mutex); +	return 1; +} + +/* + * Write to flash + */ +uint8_t +ao_storage_device_write(uint32_t pos, __xdata void *d, uint16_t len) __reentrant +{ +	uint8_t	cs; + +	if (pos >= ao_storage_total || pos + len > ao_storage_total) +		return 0; + +	ao_mutex_get(&ao_m25_mutex); +	ao_m25_scan(); + +	cs = ao_m25_set_address(pos); +	ao_m25_write_enable(cs); + +	ao_m25_instruction[0] = M25_PP; +	M25_SELECT(cs); +	ao_spi_send(ao_m25_instruction, 4); +	ao_spi_send(d, len); +	M25_DESELECT(cs); + +	ao_mutex_put(&ao_m25_mutex); +	return 1; +} + +/* + * Read from flash + */ +uint8_t +ao_storage_device_read(uint32_t pos, __xdata void *d, uint16_t len) __reentrant +{ +	uint8_t	cs; + +	if (pos >= ao_storage_total || pos + len > ao_storage_total) +		return 0; +	ao_mutex_get(&ao_m25_mutex); +	ao_m25_scan(); + +	cs = ao_m25_set_address(pos); + +	/* No need to use the FAST_READ as we're running at only 8MHz */ +	ao_m25_instruction[0] = M25_READ; +	M25_SELECT(cs); +	ao_spi_send(ao_m25_instruction, 4); +	ao_spi_recv(d, len); +	M25_DESELECT(cs); + +	ao_mutex_put(&ao_m25_mutex); +	return 1; +} + +void +ao_storage_flush(void) __reentrant +{ +} + +void +ao_storage_setup(void) +{ +	ao_mutex_get(&ao_m25_mutex); +	ao_m25_scan(); +	ao_mutex_put(&ao_m25_mutex); +} + +void +ao_storage_device_info(void) __reentrant +{ +	uint8_t	cs; +#if M25_MAX_CHIPS > 1 +	uint8_t chip; +#endif + +	ao_mutex_get(&ao_m25_mutex); +	ao_m25_scan(); +	ao_mutex_put(&ao_m25_mutex); + +#if M25_MAX_CHIPS > 1 +	printf ("Detected chips %d size %d\n", ao_m25_numchips, ao_m25_total); +	for (chip = 0; chip < ao_m25_numchips; chip++) +		printf ("Flash chip %d select %02x size %d\n", +			chip, ao_m25_pin[chip], ao_m25_size[chip]); +#else +	printf ("Detected chips 1 size %d\n", ao_m25_total); +#endif + +	printf ("Available chips:\n"); +	for (cs = 1; cs != 0; cs <<= 1) { +		if ((M25_CS_MASK & cs) == 0) +			continue; + +		ao_mutex_get(&ao_m25_mutex); +		M25_SELECT(cs); +		ao_m25_instruction[0] = M25_RDID; +		ao_spi_send(ao_m25_instruction, 1); +		ao_spi_recv(ao_m25_instruction, M25_RDID_LEN); +		M25_DESELECT(cs); + +		printf ("Select %02x manf %02x type %02x cap %02x uid %02x\n", +			cs, +			ao_m25_instruction[M25_MANUF_OFFSET], +			ao_m25_instruction[M25_MEMORY_TYPE_OFFSET], +			ao_m25_instruction[M25_CAPACITY_OFFSET], +			ao_m25_instruction[M25_UID_OFFSET]); +		ao_mutex_put(&ao_m25_mutex); +	} +} + +void +ao_storage_device_init(void) +{ +	/* Set up chip select wires */ +	SPI_CS_PORT |= M25_CS_MASK;	/* raise all CS pins */ +	SPI_CS_DIR |= M25_CS_MASK;	/* set CS pins as outputs */ +	SPI_CS_SEL &= ~M25_CS_MASK;	/* set CS pins as GPIO */ +} | 
