summaryrefslogtreecommitdiff
path: root/drivers/mfd/m4sensorhub-stm32-fw.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/m4sensorhub-stm32-fw.c')
-rw-r--r--drivers/mfd/m4sensorhub-stm32-fw.c419
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;
+}