diff options
Diffstat (limited to 'drivers/mfd/m4sensorhub-stm32-fw.c')
| -rw-r--r-- | drivers/mfd/m4sensorhub-stm32-fw.c | 419 | 
1 files changed, 419 insertions, 0 deletions
| diff --git a/drivers/mfd/m4sensorhub-stm32-fw.c b/drivers/mfd/m4sensorhub-stm32-fw.c new file mode 100644 index 00000000000..55aca3ebb05 --- /dev/null +++ b/drivers/mfd/m4sensorhub-stm32-fw.c @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2012 Motorola, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 <linux/delay.h> +#include <linux/firmware.h> +#include <linux/m4sensorhub.h> +#include <linux/slab.h> + +/* --------------- Global Declarations -------------- */ + +/* ------------ Local Function Prototypes ----------- */ +static int m4sensorhub_jump_to_user(struct m4sensorhub_data *m4sensorhub); + +/* --------------- Local Declarations -------------- */ +/* Firmware */ +#define FIRMWARE_NAME	"m4sensorhub.bin" +/* The M4 Flash Memory Map + * From the Flash Programming Manual: +  Sector  Start Address    Size              comments +  ------  ------------- ----------  ---------------------------- +  0       0x08000000    16 Kbytes   reserved for M4 bootload +  1       0x08004000    16 Kbytes   first M4 firmware code page +  2       0x08008000    16 Kbytes +  3       0x0800C000    16 Kbytes +  4       0x08010000    64 Kbytes +  5       0x08020000    128 Kbytes  last M4 firmware code page +  6       0x08040000    128 Kbytes  first M4 file system page +  7       0x08060000    128 Kbytes +  8       0x08080000    128 Kbytes +  9       0x080A0000    128 Kbytes +  10      0x080C0000    128 Kbytes +  11      0x080E0000    128 Kbytes  last M4 file system page + */ +enum { +	M4_FLASH_SECTOR0 = 0x08000000, +	M4_FLASH_SECTOR1 = 0x08004000, +	M4_FLASH_SECTOR2 = 0x08008000, +	M4_FLASH_SECTOR3 = 0x0800C000, +	M4_FLASH_SECTOR4 = 0x08010000, +	M4_FLASH_SECTOR5 = 0x08020000, +	M4_FLASH_SECTOR6 = 0x08040000, +	M4_FLASH_SECTOR7 = 0x08060000, +	M4_FLASH_SECTOR8 = 0x08080000, +	M4_FLASH_SECTOR9 = 0x080A0000, +	M4_FLASH_SECTORA = 0x080C0000, +	M4_FLASH_SECTORB = 0x080E0000, +	M4_FLASH_END     = 0x08100000 +}; +#define USER_FLASH_FIRST_PAGE_ADDRESS	M4_FLASH_SECTOR1 +#define USER_FLASH_FIRST_FILE_ADDRESS	M4_FLASH_SECTOR6 +#define VERSION_OFFSET	0x200 +#define VERSION_ADDRESS	(USER_FLASH_FIRST_PAGE_ADDRESS + VERSION_OFFSET) +#define BARKER_SIZE	4 +#define BARKER_ADDRESS	(USER_FLASH_FIRST_FILE_ADDRESS - BARKER_SIZE) +#define BARKER_NUMBER	0xACEC0DE +/* The MAX_FILE_SIZE is the size of sectors 1-5 where the firmware code + * will reside (minus the barker size). + */ +#define MAX_FILE_SIZE	(USER_FLASH_FIRST_FILE_ADDRESS \ +			- USER_FLASH_FIRST_PAGE_ADDRESS \ +			- BARKER_SIZE) /* bytes */ +#define MAX_TRANSFER_SIZE	1024 /* bytes */ +#define MAX_RETRIES	5 +#define OPC_READ	(uint8_t)(0x03) +#define OPC_WREN	(uint8_t)(0x06) +#define OPC_ERPG	(uint8_t)(0x20) +#define OPC_ERUSM	(uint8_t)(0x60) +#define OPC_USRCD	(uint8_t)(0x77) + +/* -------------- Local Data Structures ------------- */ +#define NUM_FLASH_TO_ERASE	5 +int flash_address[NUM_FLASH_TO_ERASE] = { +	M4_FLASH_SECTOR1, +	M4_FLASH_SECTOR2, +	M4_FLASH_SECTOR3, +	M4_FLASH_SECTOR4, +	M4_FLASH_SECTOR5 +}; +int flash_delay[NUM_FLASH_TO_ERASE] = { +	440, 440, 440, 1320, 4000 +}; + +/* -------------- Global Functions ----------------- */ + +/* m4sensorhub_load_firmware() + +   Check firmware and load if different from what's already on the M4. +   Then jump to user code on M4. + +   Returns 0 on success or negative error code on failure + +     m4sensorhub - pointer to the main m4sensorhub data struct +*/ + +int m4sensorhub_load_firmware(struct m4sensorhub_data *m4sensorhub, +	unsigned short force_upgrade) +{ +	const struct firmware *firmware; +	int i = MAX_RETRIES; +	int ret = 0; +	int bytes_left, bytes_to_write; +	int address_to_write; +	u8 *buf_to_read, *buf = NULL; +	u16 fw_version_file, fw_version_device; +	u32 barker_read_from_device; +	int j = 0; + +	buf = kzalloc(MAX_TRANSFER_SIZE+8, GFP_KERNEL); +	if (!buf) { +		ret = -ENOMEM; +		goto done; +	} + +	ret = request_firmware(&firmware, FIRMWARE_NAME, +		&m4sensorhub->i2c_client->dev); +	if (ret < 0) { +		KDEBUG(M4SH_ERROR, "%s: request_firmware failed for %s\n", +			__func__, FIRMWARE_NAME); +		KDEBUG(M4SH_ERROR, "Trying to run firmware already on hw.\n"); +		ret = 0; +		goto done; +	} + +	if (firmware->size > MAX_FILE_SIZE) { +		KDEBUG(M4SH_ERROR, "%s: firmware file size is too big.\n", +			__func__); +		ret = -EINVAL; +		goto done; +	} + +	fw_version_file = *(u16 *) (firmware->data + +		VERSION_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS); + +	if (!force_upgrade) { +		/* Verify Barker number from device */ +		buf[0] = OPC_READ; +		buf[1] = (BARKER_ADDRESS >> 24) & 0xFF; +		buf[2] = (BARKER_ADDRESS >> 16) & 0xFF; +		buf[3] = (BARKER_ADDRESS >> 8) & 0xFF; +		buf[4] = BARKER_ADDRESS & 0xFF; +		buf[5] = 0x00; +		buf[6] = 0x04; +		if (m4sensorhub_i2c_write_read(m4sensorhub, +				buf, 7, 0) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			ret = -EINVAL; +			goto done; +		} + +		msleep(100); + +		if (m4sensorhub_i2c_write_read(m4sensorhub, +			(u8 *) &barker_read_from_device, 0, BARKER_SIZE) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			ret = -EINVAL; +			goto done; +		} + +		if (barker_read_from_device != BARKER_NUMBER) { +			KDEBUG(M4SH_NOTICE, +				"Barker Number read 0x%8x does not match 0x%8x\n", +				barker_read_from_device, BARKER_NUMBER); +			KDEBUG(M4SH_NOTICE, +				"forcing firmware update from file\n"); +		} else { + +			/* Read firmware version from device */ +			buf[0] = OPC_READ; +			buf[1] = (VERSION_ADDRESS >> 24) & 0xFF; +			buf[2] = (VERSION_ADDRESS >> 16) & 0xFF; +			buf[3] = (VERSION_ADDRESS >> 8) & 0xFF; +			buf[4] = VERSION_ADDRESS & 0xFF; +			buf[5] = 0x00; +			buf[6] = 0x02; +			if (m4sensorhub_i2c_write_read( +					m4sensorhub, buf, 7, 0) < 0) { +				KDEBUG(M4SH_ERROR, +					"%s : %d : I2C transfer error\n", +					__func__, __LINE__); +				ret = -EINVAL; +				goto done; +			} + +			msleep(100); + +			if (m4sensorhub_i2c_write_read(m4sensorhub, +					(u8 *) &fw_version_device, 0, 2) < 0) { +				KDEBUG(M4SH_ERROR, +					"%s : %d : I2C transfer error\n", +					__func__, __LINE__); +				ret = -EINVAL; +				goto done; +			} + +			if (fw_version_file == fw_version_device) { +				KDEBUG(M4SH_NOTICE, +					"Version of firmware on device is 0x%04x\n", +					fw_version_device); +				KDEBUG(M4SH_NOTICE, +					"Firmware on device same as file, not loading firmware.\n"); +				goto done; +			} +			/* Print statement below isn't really an ERROR, but +			 * this ensures it is always printed */ +			KDEBUG(M4SH_ERROR, +				"Version of firmware on device is 0x%04x\n", +				fw_version_device); +			KDEBUG(M4SH_ERROR, +				"Version of firmware on file is 0x%04x\n", +				fw_version_file); +			KDEBUG(M4SH_ERROR, +				"Firmware on device different from file, updating...\n"); +		} +	} else { +		KDEBUG(M4SH_NOTICE, "Version of firmware on file is 0x%04x\n", +			fw_version_file); +	} + +	/* The flash memory to update has to be erased before updating */ +	for (j = 0; j < NUM_FLASH_TO_ERASE; j++) { +		buf[0] = OPC_ERPG; +		buf[1] = (flash_address[j] >> 24) & 0xFF; +		buf[2] = (flash_address[j] >> 16) & 0xFF; +		buf[3] = (flash_address[j] >> 8) & 0xFF; +		buf[4] = flash_address[j] & 0xFF; +		buf[5] = 0x00; +		buf[6] = 0x01; +		if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 7, 0) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			KDEBUG(M4SH_ERROR, "Erasing %8x address failed\n", +				flash_address[j]); +			ret = -EINVAL; +			goto done; +		} +		msleep(flash_delay[j]); +	} + +	bytes_left = firmware->size; +	address_to_write = USER_FLASH_FIRST_PAGE_ADDRESS; +	buf_to_read = (u8 *) firmware->data; + +	KDEBUG(M4SH_DEBUG, "%s: %d bytes to be written\n", __func__, +		firmware->size); + +	while (bytes_left && i) { +		if (bytes_left > MAX_TRANSFER_SIZE) +			bytes_to_write = MAX_TRANSFER_SIZE; +		else +			bytes_to_write = bytes_left; + +		buf[0] = OPC_WREN; +		buf[1] = (address_to_write >> 24) & 0xFF; +		buf[2] = (address_to_write >> 16) & 0xFF; +		buf[3] = (address_to_write >> 8) & 0xFF; +		buf[4] = address_to_write & 0xFF; +		buf[5] = (bytes_to_write >> 8) & 0xFF; +		buf[6] = bytes_to_write & 0xFF; +		buf[7] = 0xFF; +		memcpy(&buf[8], buf_to_read, bytes_to_write); +		if (m4sensorhub_i2c_write_read(m4sensorhub, buf, +				bytes_to_write+8, 0) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			ret = -EINVAL; +			goto done; +		} + +		msleep(20); + +		/* Read back the code that was written and validate it */ +		buf[0] = OPC_READ; +		buf[1] = (address_to_write >> 24) & 0xFF; +		buf[2] = (address_to_write >> 16) & 0xFF; +		buf[3] = (address_to_write >> 8) & 0xFF; +		buf[4] = address_to_write & 0xFF; +		buf[5] = (bytes_to_write >> 8) & 0xFF; +		buf[6] = bytes_to_write & 0xFF; +		if (m4sensorhub_i2c_write_read( +				m4sensorhub, buf, 7, 0) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			ret = -EINVAL; +			goto done; +		} + +		if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 0, +				bytes_to_write) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			ret = -EINVAL; +			goto done; +		} + +		if (memcmp(buf, buf_to_read, bytes_to_write) != 0) { +			/* If memory write fails, try again */ +			KDEBUG(M4SH_ERROR, +				"memory write to 0x%x of %d bytes failed, try again\n", +				address_to_write, bytes_to_write); +			i--; +		} else { +			address_to_write += bytes_to_write; +			buf_to_read += bytes_to_write; +			bytes_left -= bytes_to_write; +			/* Reset reter of retries */ +			i = MAX_RETRIES; +		} +	} + +	if (!i) { +		KDEBUG(M4SH_ERROR, "%s: firmware transfer failed\n", __func__); +		ret = -EINVAL; +	} else { +		/* Write barker number when firmware successfully written */ +		buf[0] = OPC_WREN; +		buf[1] = (BARKER_ADDRESS >> 24) & 0xFF; +		buf[2] = (BARKER_ADDRESS >> 16) & 0xFF; +		buf[3] = (BARKER_ADDRESS >> 8) & 0xFF; +		buf[4] = BARKER_ADDRESS & 0xFF; +		buf[5] = 0x00; +		buf[6] = 0x04; +		buf[7] = 0xFF; +		buf[8] = BARKER_NUMBER & 0xFF; +		buf[9] = (BARKER_NUMBER >> 8) & 0xFF; +		buf[10] = (BARKER_NUMBER >> 16) & 0xFF; +		buf[11] = (BARKER_NUMBER >> 24) & 0xFF; +		if (m4sensorhub_i2c_write_read(m4sensorhub, +				buf, 12, 0) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			ret = -EINVAL; +			goto done; +		} + +		KDEBUG(M4SH_NOTICE, "%s: %d bytes written successfully\n", +			__func__, firmware->size); +	} + +done: +	release_firmware(firmware); + +	/* If ret is invalid, then we don't try to jump to user code */ +	if (ret >= 0 && m4sensorhub_jump_to_user(m4sensorhub) < 0) +		/* If jump to user code fails, return failure */ +		ret = -EINVAL; + +	kfree(buf); + +	return ret; +} +EXPORT_SYMBOL_GPL(m4sensorhub_load_firmware); + + +/* -------------- Local Functions ----------------- */ + +static int m4sensorhub_jump_to_user(struct m4sensorhub_data *m4sensorhub) +{ +	int ret = -1; +	u8 buf[7] = {0}; +	u32 barker_read_from_device = 0; + +	buf[0] = OPC_READ; +	buf[1] = (BARKER_ADDRESS >> 24) & 0xFF; +	buf[2] = (BARKER_ADDRESS >> 16) & 0xFF; +	buf[3] = (BARKER_ADDRESS >> 8) & 0xFF; +	buf[4] = BARKER_ADDRESS & 0xFF; +	buf[5] = 0x00; +	buf[6] = 0x04; +	if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 7, 0) < 0) { +		KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +			__func__, __LINE__); +		return ret; +	} + +	msleep(100); + +	if (m4sensorhub_i2c_write_read(m4sensorhub, +			(u8 *) &barker_read_from_device, 0, BARKER_SIZE) < 0) { +		KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +			__func__, __LINE__); +		return ret; +	} + +	if (barker_read_from_device == BARKER_NUMBER) { +		buf[0] = OPC_USRCD; +		if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 1, 0) < 0) { +			KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", +				__func__, __LINE__); +			return ret; +		} +		KDEBUG(M4SH_NOTICE, "Executing M4 code \n"); +		msleep(5000); /* 5 secs delay */ +		ret = 0; +	} else { +		KDEBUG(M4SH_ERROR, +			"Barker Number read 0x%8x does not match 0x%8x\n", +			barker_read_from_device, BARKER_NUMBER); +		KDEBUG(M4SH_ERROR, "*** Not executing M4 code ***\n"); +	} + +	return ret; +} |