diff options
author | Mark Rawling <mwr@google.com> | 2022-03-25 11:47:20 -0700 |
---|---|---|
committer | Andrew Evans <andrewevans@google.com> | 2022-04-19 10:16:03 -0700 |
commit | 4936dfbddc45391716f119fff5aba55089e65a4d (patch) | |
tree | 91958ce27b3fc53e6b2bdaec95a16be32e861da1 | |
parent | c5c7e44b5aeeda5ea1a1560d01c37c3f72d1606f (diff) | |
download | nanohub-4936dfbddc45391716f119fff5aba55089e65a4d.tar.gz |
MCU fast boot via NXP bootloader over SPI
The Rohan MCU currently takes 30s to boot over UART. This is
unacceptable because:
- it adds 30s to the visible boot, ie, until the watch face is visible
- MCU restarts are so slow that sensor behaviour is impacted
We cannot use blhost over SPI because it requires either SPIDEV, or a
custom interface, precise timing control, and workarounds for bootloader
bugs. None of these things are viable in Rohan.
This CL adds support for a subset of the NXP bootloader commands, enough
to enable booting the MCU over SPI. Other commands can be easily added
on top of the basic interface.
- MCU boot time is improved from 30s to 2s
- bootloader sync and timing bugs are handled
- bootloader data aborts are detected and retried
- downloads run at elevated priority for speed and stability
- signed firmware is supported
See go/rh-mcu-boot-improvements
Bug: 206044802
Change-Id: I79b9d9d7ab670e1e758824dfaf212b46e41120c2
-rw-r--r-- | Kbuild | 3 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | bl_nxp.c | 200 | ||||
-rw-r--r-- | bl_nxp.h | 97 | ||||
-rw-r--r-- | bl_st.c (renamed from bl.c) | 2 | ||||
-rw-r--r-- | bl_st.h (renamed from bl.h) | 0 | ||||
-rw-r--r-- | main.c | 152 | ||||
-rw-r--r-- | main.h | 32 | ||||
-rw-r--r-- | nanohub.h | 2 | ||||
-rw-r--r-- | spi.c | 335 |
10 files changed, 784 insertions, 41 deletions
@@ -5,4 +5,5 @@ obj-$(CONFIG_NANOHUB) += nanohub.o nanohub-y := main.o comms.o nanohub-$(CONFIG_NANOHUB_SPI) += spi.o -nanohub-$(CONFIG_NANOHUB_BL) += bl.o +nanohub-$(CONFIG_NANOHUB_BL_NXP) += bl_nxp.o +nanohub-$(CONFIG_NANOHUB_BL_ST) += bl_st.o @@ -1,10 +1,12 @@ default: all KBUILD_OPTIONS := CONFIG_NANOHUB=m +KBUILD_OPTIONS += CONFIG_NANOHUB_BL_NXP=y KBUILD_OPTIONS += CONFIG_NANOHUB_SPI=y # Needed flags that aren't parsed in Kbuild KBUILD_FLAGS := -DCONFIG_NANOHUB +KBUILD_FLAGS += -DCONFIG_NANOHUB_BL_NXP KBUILD_FLAGS += -DCONFIG_NANOHUB_SPI all: diff --git a/bl_nxp.c b/bl_nxp.c new file mode 100644 index 0000000..b6433d2 --- /dev/null +++ b/bl_nxp.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/vmalloc.h> + +#include "nanohub.h" +#include "main.h" +#include "bl_nxp.h" + +#define MAX_BUFFER_SIZE 1024 + +int nanohub_bl_open(struct nanohub_data *data) +{ + int ret; + + data->bl.tx_buffer = kmalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA); + if (!data->bl.tx_buffer) { + ret = -ENOMEM; + goto out; + } + + data->bl.rx_buffer = kmalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA); + if (!data->bl.rx_buffer) { + ret = -ENOMEM; + goto free_tx; + } + + ret = data->bl.open(data); + if (ret == 0) + return ret; + + kfree(data->bl.rx_buffer); +free_tx: + kfree(data->bl.tx_buffer); +out: + return ret; +} + +void nanohub_bl_close(struct nanohub_data *data) +{ + data->bl.close(data); + kfree(data->bl.tx_buffer); + kfree(data->bl.rx_buffer); +} + +static int nanohub_bl_read_generic_response(struct nanohub_data *data, + uint8_t expected_command_tag) +{ + struct nanohub_bl *bl = &data->bl; + int ret, status; + + ret = bl->read_response(data); + if (ret < 0) + return ret; + if (bl->rx_buffer[1] != BL_FRAME_COMMAND || + bl->rx_buffer[6] != BL_RESPONSE_GENERIC || + bl->rx_buffer[14] != expected_command_tag) + return -BL_ERR_BAD_RESPONSE; + + status = get_le32(bl->rx_buffer + 10); + return -status; +} + +int nanohub_bl_set_property(struct nanohub_data *data, uint32_t tag, + uint32_t value) +{ + int ret = data->bl.write_cmd(data, BL_COMMAND_SET_PROPERTY, 0, 2, tag, + value); + if (ret < 0) + return ret; + return nanohub_bl_read_generic_response(data, BL_COMMAND_SET_PROPERTY); +} + +int nanohub_bl_write_memory(struct nanohub_data *data, const uint8_t *buffer, + uint32_t size, uint32_t address, + uint32_t *data_abort_offset) +{ + int sched = 0, chunk = BL_DATA_CHUNK_SIZE, offset; + int ret; + + // command + ret = data->bl.write_cmd(data, BL_COMMAND_WRITE_MEMORY, + BL_FLAG_HAS_DATA, 3, address, size, 0); + if (ret < 0) + return ret; + + // initial response + ret = nanohub_bl_read_generic_response(data, BL_COMMAND_WRITE_MEMORY); + if (ret < 0) + return ret; + + // data phase + for (offset = 0; offset < size; offset += BL_DATA_CHUNK_SIZE) { + if (size - offset < BL_DATA_CHUNK_SIZE) + chunk = size - offset; + ret = data->bl.write_data(data, buffer + offset, chunk); + if (ret == -BL_FRAME_ABORT) { + *data_abort_offset = offset; + break; + } + if (ret < 0) + return ret; + if (++sched % 20 == 0) + schedule(); // ~10-20ms + } + + // final response + return nanohub_bl_read_generic_response(data, BL_COMMAND_WRITE_MEMORY); +} + +static int nanohub_bl_write_memory_retry(struct nanohub_data *data, + const uint8_t *buffer, uint32_t size, + uint32_t address, int max_attempts) +{ + int attempt, offset = 0, data_abort_offset = 0; + int ret; + for (attempt = 0; attempt < max_attempts; attempt++) { + ret = nanohub_bl_write_memory(data, buffer + offset, + size - offset, address + offset, + &data_abort_offset); + if (!data_abort_offset) + break; + offset += data_abort_offset; + data_abort_offset = 0; + pr_warn("nanohub: %s data abort offset=%d\n", __func__, offset); + } + return ret; +} + +int nanohub_bl_execute(struct nanohub_data *data, uint32_t prgm_counter, + uint32_t ram_addr, uint32_t stack_ptr) +{ + int ret = data->bl.write_cmd(data, BL_COMMAND_EXECUTE, 0, 3, + prgm_counter, ram_addr, stack_ptr); + if (ret < 0) + return ret; + return nanohub_bl_read_generic_response(data, BL_COMMAND_EXECUTE); +} + +int nanohub_bl_download_firmware(struct nanohub_data *data, + const uint8_t *buffer, uint32_t size) +{ + const uint32_t kSpOffset = 0; + const uint32_t kPcOffset = 1; + const uint32_t kLoadOffset = 13; + const uint32_t kSignedOffset = 0x1000; + const uint32_t kMagicOffset = 0x400; + const uint8_t kMagic[] = { 'C', 'H', 'R', 'E' }; + uint32_t *firmware; + uint32_t ram_addr, stack_ptr, prgm_counter; + int ret = 0; + + // Handle signed firmware + if (size > kSignedOffset && + memcmp(buffer + kMagicOffset, kMagic, sizeof(kMagic)) == 0) { + buffer += kSignedOffset; + size -= kSignedOffset; + } + + if (size < 64) { + pr_err("nanohub: %s invalid size\n", __func__); + return -ETOOSMALL; + } + + firmware = (uint32_t *)buffer; + ram_addr = firmware[kLoadOffset]; + stack_ptr = firmware[kSpOffset]; + prgm_counter = firmware[kPcOffset]; + + // Enable GPIO mode using pin 4,29, shared with mcu_ap_wake_int. + ret = nanohub_bl_set_property(data, 0x1c, 0x8000041d); + if (ret < 0) { + pr_err("nanohub: %s set_property ret=%d\n", __func__, ret); + return ret; + } + + ret = nanohub_bl_write_memory_retry(data, buffer, size, ram_addr, 10); + if (ret < 0) { + pr_err("nanohub: %s write_memory ret=%d\n", __func__, ret); + return ret; + } + + ret = nanohub_bl_execute(data, prgm_counter, ram_addr, stack_ptr); + if (ret < 0) { + pr_err("nanohub: %s execute ret=%d\n", __func__, ret); + return ret; + } + return 0; +} diff --git a/bl_nxp.h b/bl_nxp.h new file mode 100644 index 0000000..35b733c --- /dev/null +++ b/bl_nxp.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _NANOHUB_BL_NXP_H +#define _NANOHUB_BL_NXP_H + +#include "nanohub.h" + +struct nanohub_data; + +struct nanohub_bl { + int (*open)(const void *); + void (*close)(const void *); + int (*read_ack)(const void *); + int (*read_response)(const void *); + int (*write_ack)(const void *); + int (*write_cmd)(const void *, uint8_t cmd, uint8_t flags, + int n_args, ...); + int (*write_data)(const void *, const uint8_t *buf, int size); + + uint8_t *tx_buffer; + uint8_t *rx_buffer; +}; + +int nanohub_bl_open(struct nanohub_data *); +void nanohub_bl_close(struct nanohub_data *); + +int nanohub_bl_set_property(struct nanohub_data *data, uint32_t tag, + uint32_t value); +int nanohub_bl_write_memory(struct nanohub_data *data, const uint8_t *firmware, + uint32_t size, uint32_t address, + uint32_t *data_abort_offset); +int nanohub_bl_execute(struct nanohub_data *data, uint32_t prgm_counter, + uint32_t ram_addr, uint32_t stack_ptr); + +int nanohub_bl_download_firmware(struct nanohub_data *, const uint8_t *firmware, + uint32_t size); + +#define BL_COMMAND_WRITE_MEMORY 0x04 +#define BL_COMMAND_EXECUTE 0x09 +#define BL_COMMAND_SET_PROPERTY 0x0C + +#define BL_RESPONSE_GENERIC 0xA0 + +#define BL_FLAG_HAS_DATA 0x01 + +#define BL_FRAME_SYNC 0x5A +#define BL_FRAME_ACK 0xA1 +#define BL_FRAME_NACK 0xA2 +#define BL_FRAME_ABORT 0xA3 +#define BL_FRAME_COMMAND 0xA4 +#define BL_FRAME_DATA 0xA5 +#define BL_FRAME_PING 0xA6 +#define BL_FRAME_PING_RESPONSE 0xA7 + +// XXX find system errors for these, will still clash with nxp status though +// so maybe these should be in a unique scope? +#define BL_ERR_TIMEOUT 52 +#define BL_ERR_CRC 53 +#define BL_ERR_SYNC 54 +#define BL_ERR_BAD_RESPONSE 55 + +// Optimized for burst transfers (total packet multiple of 4) +#define BL_DATA_CHUNK_SIZE 510 + +static inline void put_le16(uint8_t *buf, uint16_t i) { + buf[0] = i & 0xff; + buf[1] = (i >> 8) & 0xff; +} + +static inline void put_le32(uint8_t *buf, uint32_t i) { + buf[0] = i & 0xff; + buf[1] = (i >> 8) & 0xff; + buf[2] = (i >> 16) & 0xff; + buf[3] = (i >> 24) & 0xff; +} + +static inline uint16_t get_le16(uint8_t *buf) { + return buf[0] | buf[1] << 8; +} + +static inline uint32_t get_le32(uint8_t *buf) { + return buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; +} + +#endif @@ -16,7 +16,7 @@ #include "nanohub.h" #include "main.h" -#include "bl.h" +#include "bl_st.h" #define MAX_BUFFER_SIZE 1024 #define MAX_FLASH_BANKS 16 @@ -15,6 +15,7 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> +#include <linux/kthread.h> #include <linux/slab.h> #include <linux/iio/iio.h> #include <linux/firmware.h> @@ -41,9 +42,16 @@ #include "nanohub_exports.h" #include "main.h" #include "comms.h" -#include "bl.h" #include "spi.h" +#ifdef CONFIG_NANOHUB_BL_ST +#include "bl_st.h" +#endif + +#ifdef CONFIG_NANOHUB_BL_NXP +#include "bl_nxp.h" +#endif + #define READ_QUEUE_DEPTH 20 #define APP_FROM_HOST_EVENTID 0x000000F8 #define FIRST_SENSOR_EVENTID 0x00000200 @@ -722,7 +730,7 @@ static inline int nanohub_wakeup_lock(struct nanohub_data *data, int mode) return ret; } -#ifdef CONFIG_NANOHUB_BL +#if defined(CONFIG_NANOHUB_BL_ST) || defined(CONFIG_NANOHUB_BL_NXP) if (mode == LOCK_MODE_IO || mode == LOCK_MODE_IO_BL) ret = nanohub_bl_open(data); if (ret < 0) { @@ -747,7 +755,7 @@ static inline int nanohub_wakeup_unlock(struct nanohub_data *data) atomic_set(&data->lock_mode, LOCK_MODE_NONE); if (mode != LOCK_MODE_SUSPEND_RESUME) enable_irq(data->irq1); -#ifdef CONFIG_NANOHUB_BL +#if defined(CONFIG_NANOHUB_BL_ST) || defined(CONFIG_NANOHUB_BL_NXP) if (mode == LOCK_MODE_IO || mode == LOCK_MODE_IO_BL) nanohub_bl_close(data); #endif @@ -763,16 +771,11 @@ static inline int nanohub_wakeup_unlock(struct nanohub_data *data) static void __nanohub_hw_reset(struct nanohub_data *data, int boot0) { - //const struct nanohub_platform_data *pdata = data->pdata; - - //gpio_set_value(pdata->nreset_gpio, 0); - //gpio_set_value(pdata->boot0_gpio, boot0 > 0); - //usleep_range(30, 40); - //gpio_set_value(pdata->nreset_gpio, 1); - //if (boot0 > 0) - // usleep_range(70000, 75000); - //else if (!boot0) - // usleep_range(750000, 800000); + const struct nanohub_platform_data *pdata = data->pdata; + gpio_set_value(pdata->nreset_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value(pdata->nreset_gpio, 1); + usleep_range(10000, 20000); nanohub_clear_err_cnt(data); } @@ -801,7 +804,7 @@ static ssize_t nanohub_try_hw_reset(struct device *dev, return ret < 0 ? ret : count; } -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST static ssize_t nanohub_erase_shared(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -906,7 +909,7 @@ static ssize_t nanohub_download_kernel(struct device *dev, } -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST static ssize_t nanohub_download_kernel_bl(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -952,6 +955,92 @@ static ssize_t nanohub_download_kernel_bl(struct device *dev, } #endif +#ifdef CONFIG_NANOHUB_BL_NXP + +static ssize_t nanohub_download_firmware_request(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_drvdata(dev); + struct firmware_request *fw_request = &data->firmware_request; + char *fw_name = fw_request->fw_name; + int ret; + + if (atomic_cmpxchg(&fw_request->state, FW_IDLE, FW_PENDING) != + FW_IDLE) { + ret = -EBUSY; + goto out; + } + + if (count == 0 || count >= FW_NAME_SIZE) { + ret = -ENAMETOOLONG; + goto out; + } + + // Handle \n or unterminated name. + memcpy(fw_name, buf, count); + if (fw_name[count - 1] == '\n') + fw_name[count - 1] = 0; + fw_name[count] = 0; + + atomic_set(&fw_request->state, FW_REQUESTED); + nanohub_notify_thread(data); + wait_for_completion(&fw_request->fw_complete); + ret = fw_request->result; + +out: + if (ret < 0) + pr_err("nanohub: firmware request failed: %s err=%d\n", fw_name, + ret); + + atomic_set(&fw_request->state, FW_IDLE); + return ret < 0 ? ret : count; +} + +static void nanohub_download_firmware(struct device *dev) +{ + struct nanohub_data *data = dev_get_drvdata(dev); + struct firmware_request *fw_request = &data->firmware_request; + const struct firmware *fw_entry; + int ret; + + pr_info("nanohub: firmware download: %s\n", fw_request->fw_name); + + atomic_set(&fw_request->state, FW_IN_PROGRESS); + ret = request_firmware(&fw_entry, fw_request->fw_name, dev); + if (ret < 0) + goto out; + + ret = nanohub_wakeup_lock(data, LOCK_MODE_IO_BL); + if (ret < 0) + goto release_firmware; + + // Disable irq1 as its GPIO is used by the bootloader. + if (data->irq1) + disable_irq(data->irq1); + + __nanohub_hw_reset(data, -1); + + ret = nanohub_bl_download_firmware(data, fw_entry->data, + fw_entry->size); + + mcu_wakeup_gpio_set_value(data, 1); + if (data->irq1) + enable_irq(data->irq1); + nanohub_wakeup_unlock(data); + +release_firmware: + release_firmware(fw_entry); +out: + pr_info("nanohub: firmware download complete: %s err=%d\n", + fw_request->fw_name, ret); + fw_request->result = ret; + atomic_set(&fw_request->state, FW_COMPLETE); + complete(&fw_request->fw_complete); +} + +#endif + static ssize_t nanohub_download_app(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1040,7 +1129,7 @@ static ssize_t nanohub_download_app(struct device *dev, return count; } -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST static ssize_t nanohub_lock_bl(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1119,25 +1208,28 @@ static struct device_attribute attributes[] = { __ATTR(firmware_version, 0440, nanohub_firmware_query, NULL), __ATTR(time_sync, 0440, nanohub_time_sync, NULL), __ATTR(wake_lock, 0220, NULL, nanohub_mcu_wake_lock), -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST __ATTR(download_bl, 0220, NULL, nanohub_download_bl), #endif __ATTR(download_kernel, 0220, NULL, nanohub_download_kernel), -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST __ATTR(download_kernel_bl, 0220, NULL, nanohub_download_kernel_bl), #endif __ATTR(download_app, 0220, NULL, nanohub_download_app), -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST __ATTR(erase_shared, 0220, NULL, nanohub_erase_shared), __ATTR(erase_shared_bl, 0220, NULL, nanohub_erase_shared_bl), #endif __ATTR(hw_reset, 0220, NULL, nanohub_try_hw_reset), __ATTR(nreset, 0660, nanohub_pin_nreset_get, nanohub_pin_nreset_set), __ATTR(boot0, 0660, nanohub_pin_boot0_get, nanohub_pin_boot0_set), -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST __ATTR(lock, 0220, NULL, nanohub_lock_bl), __ATTR(unlock, 0220, NULL, nanohub_unlock_bl), #endif +#ifdef CONFIG_NANOHUB_BL_NXP + __ATTR(download_firmware, 0220, NULL, nanohub_download_firmware_request), +#endif __ATTR(wakeup_event_msec, 0660, nanohub_wakeup_event_msec_get, nanohub_wakeup_event_msec_set), }; @@ -1508,6 +1600,15 @@ static int nanohub_kthread(void *arg) break; } atomic_set(&data->kthread_run, 0); + +#ifdef CONFIG_NANOHUB_BL_NXP + if (atomic_read(&data->firmware_request.state) == FW_REQUESTED) { + nanohub_download_firmware(dev); + nanohub_set_state(data, ST_IDLE); + continue; + } +#endif + if (!buf) buf = nanohub_io_get_buf(&data->free_pool, false); @@ -1589,7 +1690,7 @@ static struct nanohub_platform_data *nanohub_parse_dt(struct device *dev) { struct nanohub_platform_data *pdata; struct device_node *dt = dev->of_node; -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST const uint32_t *tmp; struct property *prop; uint32_t u, i; @@ -1635,7 +1736,7 @@ static struct nanohub_platform_data *nanohub_parse_dt(struct device *dev) /* optional (spi) */ pdata->spi_cs_gpio = of_get_named_gpio(dt, "sensorhub,spi-cs-gpio", 0); -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST /* optional (bl-max-frequency) */ pdata->bl_max_speed_hz = BL_MAX_SPEED_HZ; ret = of_property_read_u32(dt, "sensorhub,bl-max-frequency", &u); @@ -1714,7 +1815,7 @@ static struct nanohub_platform_data *nanohub_parse_dt(struct device *dev) return pdata; -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST no_mem_shared: devm_kfree(dev, pdata->flash_banks); no_mem: @@ -1907,6 +2008,11 @@ struct iio_dev *nanohub_probe(struct device *dev, struct iio_dev *iio_dev) if (ret) goto fail_dev; +#ifdef CONFIG_NANOHUB_BL_NXP + atomic_set(&data->firmware_request.state, FW_IDLE); + init_completion(&data->firmware_request.fw_complete); +#endif + data->thread = kthread_create(nanohub_kthread, data, "nanohub"); if (!IS_ERR(data->thread)) { priv_nanohub_data = data; @@ -23,7 +23,15 @@ #include <linux/semaphore.h> #include "comms.h" -#include "bl.h" + +#ifdef CONFIG_NANOHUB_BL_ST +#include "bl_st.h" +#endif + +#ifdef CONFIG_NANOHUB_BL_NXP +#include "bl_nxp.h" +#endif + #define NANOHUB_NAME "nanohub" @@ -44,6 +52,24 @@ struct nanohub_io { uint8_t id; }; +#ifdef CONFIG_NANOHUB_BL_NXP +enum fw_state { + FW_IDLE, + FW_PENDING, + FW_REQUESTED, + FW_IN_PROGRESS, + FW_COMPLETE, +}; + +#define FW_NAME_SIZE 64 +struct firmware_request { + char fw_name[FW_NAME_SIZE]; + atomic_t state; + int result; + struct completion fw_complete; +}; +#endif + struct nanohub_data { /* indices for io[] array */ #define ID_NANOHUB_COMMS 0 @@ -97,6 +123,10 @@ struct nanohub_data { struct task_struct *thread; int wakeup_event_msec; + +#ifdef CONFIG_NANOHUB_BL_NXP + struct firmware_request firmware_request; +#endif }; enum { @@ -13,7 +13,7 @@ struct nanohub_platform_data { u32 irq1_gpio; u32 irq2_gpio; u32 spi_cs_gpio; -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST u32 bl_max_speed_hz; u32 bl_addr; u32 num_flash_banks; @@ -19,9 +19,16 @@ #include <linux/module.h> #include "main.h" -#include "bl.h" #include "comms.h" +#ifdef CONFIG_NANOHUB_BL_ST +#include "bl_st.h" +#endif + +#ifdef CONFIG_NANOHUB_BL_NXP +#include "bl_nxp.h" +#endif + #define SPI_TIMEOUT 65535 #define SPI_MIN_DMA 48 #define SPI_MAX_SPEED_HZ 10000000 @@ -38,7 +45,7 @@ struct nanohub_spi_data { uint16_t rx_offset; }; -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST static uint8_t bl_checksum(const uint8_t *bytes, int length) { int i; @@ -236,6 +243,294 @@ void nanohub_spi_bl_init(struct nanohub_spi_data *spi_data) #endif + +#ifdef CONFIG_NANOHUB_BL_NXP + +#define CRC_POLY 0x1021 + +static uint16_t crc_update(uint16_t crc_in, int incr) +{ + uint16_t xor = crc_in >> 15; + uint16_t out = crc_in << 1; + if (incr) + out++; + if (xor) + out ^= CRC_POLY; + return out; +} + +// Calculate a packet CRC, ignoring the CRC bytes at offset 4 & 5. +static uint16_t bl_crc16(const uint8_t *data, uint16_t size) +{ + uint16_t crc, i, j; + for (crc = 0, j = 0; j < size; j++, data++) { + if (j == 4 || j == 5) + continue; // skip the CRC word + for (i = 0x80; i; i >>= 1) + crc = crc_update(crc, *data & i); + } + for (i = 0; i < 16; i++) + crc = crc_update(crc, 0); + return crc; +} + +enum bl_readiness { + BL_READY_READ = 0, + BL_READY_WRITE = 1, + BL_READY_NONE = 2, +}; + +static int spi_bl_wait(const void *data, enum bl_readiness bl_ready_state) +{ + const struct nanohub_spi_data *spi_data = data; + const struct nanohub_platform_data *pdata = spi_data->data.pdata; + int retry, delay; + + if (bl_ready_state == BL_READY_NONE) + return 0; + for (retry = 0, delay = 0; retry < 20; retry++) { + if (gpio_get_value(pdata->irq1_gpio) == bl_ready_state) + return 0; + delay += 10; + udelay(delay); + } + pr_warn("nanohub: %s timed out waiting for bootloader\n", __func__); + return -BL_ERR_TIMEOUT; +} + +static int spi_bl_tx_rx(const void *data, struct spi_transfer *xfer, + enum bl_readiness bl_ready_state) +{ + const struct nanohub_spi_data *spi_data = data; + struct spi_message msg; + int ret; + + spi_message_init_with_transfers(&msg, xfer, 1); + ret = spi_bl_wait(data, bl_ready_state); + if (ret < 0) + return ret; + gpio_set_value(spi_data->cs, 0); + ret = spi_sync_locked(spi_data->device, &msg); + gpio_set_value(spi_data->cs, 1); + return ret; +} + +static int spi_bl_write_ack(const void *data) +{ + const struct nanohub_spi_data *spi_data = data; + const struct nanohub_bl *bl = &spi_data->data.bl; + struct spi_transfer xfer = { + .len = 2, + .tx_buf = bl->tx_buffer, + .rx_buf = NULL, + .cs_change = 1, + }; + + bl->tx_buffer[0] = BL_FRAME_SYNC; + bl->tx_buffer[1] = BL_FRAME_ACK; + + return spi_bl_tx_rx(data, &xfer, BL_READY_WRITE); +} + +// Read a packet beginning with a sync byte, eg, ACK and Frame packets. +// Works around some bootloader issues that can cause random errors. +static int spi_bl_read_sync_packet(const void *data, int length) +{ + const struct nanohub_spi_data *spi_data = data; + const struct nanohub_bl *bl = &spi_data->data.bl; + struct spi_transfer xfer = { + .len = length, + .tx_buf = NULL, + .rx_buf = bl->rx_buffer, + .cs_change = 1, + }; + int ret; + + ret = spi_bl_tx_rx(data, &xfer, BL_READY_READ); + if (ret < 0) + return ret; + + if (bl->rx_buffer[0] != BL_FRAME_SYNC) + return -BL_ERR_SYNC; + + if (bl->rx_buffer[1] == BL_FRAME_SYNC) { + // The NXP bootloader can sometimes return an extra sync byte. + // Discard the extra sync and refetch the last content byte. + // This also removes the need to wait 50us b/w sync and ack. + // See ref manual 18.7.2.6.7 + memmove(bl->rx_buffer, bl->rx_buffer + 1, length - 1); + xfer.len = 1; + xfer.rx_buf = bl->rx_buffer + length - 1; + ret = spi_bl_tx_rx(data, &xfer, BL_READY_NONE); + } + + return ret; +} + +static int spi_bl_read_ack(const void *data) +{ + const struct nanohub_spi_data *spi_data = data; + const struct nanohub_bl *bl = &spi_data->data.bl; + uint8_t ack; + int ret = spi_bl_read_sync_packet(data, 2); + if (ret < 0) + return ret; + ack = bl->rx_buffer[1]; + return ack == BL_FRAME_ACK ? 0 : -ack; +} + +static int spi_bl_read_frame(const void *data) +{ + return spi_bl_read_sync_packet(data, 6); +} + +// NXP bootloader command and response packet format +// Frame +// 0 sync 5A +// 1 type A4 +// 2 length 2 bytes, length of cmd packet, little endian +// 4 crc 2 bytes, crc of entire packet, not including crc, little endian +// Command or Response +// 6 cmd/resp 1 byte +// 7 flags 1 byte +// 8 reserved 00 +// 9 n_args 1 byte +// 10 arg1 4 bytes, little endian +// 14 ... +static int spi_bl_write_cmd(const void *data, uint8_t cmd, uint8_t flags, + int n_args, ...) +{ + const struct nanohub_spi_data *spi_data = data; + const struct nanohub_bl *bl = &spi_data->data.bl; + struct spi_transfer xfer = { + .len = 10 + 4 * n_args, + .tx_buf = bl->tx_buffer, + .rx_buf = NULL, + .cs_change = 1, + }; + int i, ret; + uint16_t crc = 0; + va_list args; + + bl->tx_buffer[0] = BL_FRAME_SYNC; + bl->tx_buffer[1] = BL_FRAME_COMMAND; + put_le16(bl->tx_buffer + 2, 4 + 4 * n_args); + bl->tx_buffer[6] = cmd; + bl->tx_buffer[7] = flags; + bl->tx_buffer[8] = 0; + bl->tx_buffer[9] = n_args; + va_start(args, n_args); + for (i = 0; i < n_args; i++) { + put_le32(bl->tx_buffer + 10 + i * 4, va_arg(args, uint32_t)); + } + va_end(args); + crc = bl_crc16(bl->tx_buffer, 10 + 4 * n_args); + put_le16(bl->tx_buffer + 4, crc); + + ret = spi_bl_tx_rx(data, &xfer, BL_READY_WRITE); + if (ret < 0) + return ret; + + return spi_bl_read_ack(data); +} + +static int spi_bl_write_data(const void *data, const uint8_t *buf, int length) +{ + const struct nanohub_spi_data *spi_data = data; + const struct nanohub_bl *bl = &spi_data->data.bl; + struct spi_transfer xfer = { + .len = 6 + length, + .tx_buf = bl->tx_buffer, + .rx_buf = NULL, + .cs_change = 1, + }; + uint16_t crc = 0; + int ret; + + bl->tx_buffer[0] = BL_FRAME_SYNC; + bl->tx_buffer[1] = BL_FRAME_DATA; + put_le16(bl->tx_buffer + 2, length); + memcpy(bl->tx_buffer + 6, buf, length); + crc = bl_crc16(bl->tx_buffer, 6 + length); + put_le16(bl->tx_buffer + 4, crc); + + ret = spi_bl_tx_rx(data, &xfer, BL_READY_WRITE); + if (ret < 0) + return ret; + + return spi_bl_read_ack(data); +} + +static int spi_bl_read_response(const void *data) +{ + const struct nanohub_spi_data *spi_data = data; + const struct nanohub_bl *bl = &spi_data->data.bl; + struct spi_transfer xfer = { + .tx_buf = NULL, + .cs_change = 1, + }; + int ret; + uint16_t length, expected_crc, actual_crc; + + // Read the frame + // This delay allows the MCU to begin a new data ready notification. + // It could be removed if we changed to an edge-driven interrupt. + udelay(60); + ret = spi_bl_read_frame(data); + if (ret < 0) + return ret; + + length = get_le16(bl->rx_buffer + 2); + expected_crc = get_le16(bl->rx_buffer + 4); + + if (length > 32) + return -BL_ERR_BAD_RESPONSE; + + // Read the response + xfer.len = length; + xfer.rx_buf = bl->rx_buffer + 6; + + ret = spi_bl_tx_rx(data, &xfer, BL_READY_NONE); + if (ret < 0) + return ret; + + actual_crc = bl_crc16(bl->rx_buffer, 6 + length); + if (actual_crc != expected_crc) + return -BL_ERR_CRC; + + return spi_bl_write_ack(data); +} + +static int spi_bl_open(const void *data) +{ + const struct nanohub_spi_data *spi_data = data; + + spi_bus_lock(spi_data->device->master); + return 0; +} + +static void spi_bl_close(const void *data) +{ + const struct nanohub_spi_data *spi_data = data; + + spi_bus_unlock(spi_data->device->master); +} + +void nanohub_spi_bl_init(struct nanohub_spi_data *spi_data) +{ + struct nanohub_bl *bl = &spi_data->data.bl; + + bl->open = spi_bl_open; + bl->write_ack = spi_bl_write_ack; + bl->write_cmd = spi_bl_write_cmd; + bl->write_data = spi_bl_write_data; + bl->read_ack = spi_bl_read_ack; + bl->read_response = spi_bl_read_response; + bl->close = spi_bl_close; +} + +#endif + int nanohub_spi_write(void *data, uint8_t *tx, int length, int timeout) { struct nanohub_spi_data *spi_data = data; @@ -379,20 +674,13 @@ int nanohub_spi_read(void *data, uint8_t *rx, int max_length, int timeout) static int nanohub_spi_open(void *data) { struct nanohub_spi_data *spi_data = data; - int ret; down(&spi_data->spi_sem); spi_bus_lock(spi_data->device->master); - spi_data->device->max_speed_hz = spi_data->data.max_speed_hz; - spi_data->device->mode = SPI_MODE_0; - spi_data->device->bits_per_word = SPI_BITS_PER_WORD; - ret = spi_setup(spi_data->device); - if (!ret) { - udelay(40); - gpio_set_value(spi_data->cs, 0); - udelay(30); - } - return ret; + udelay(40); + gpio_set_value(spi_data->cs, 0); + udelay(30); + return 0; } static void nanohub_spi_close(void *data) @@ -437,6 +725,12 @@ static int nanohub_spi_probe(struct spi_device *spi) { struct nanohub_spi_data *spi_data; struct iio_dev *iio_dev; + struct spi_message msg; + struct spi_transfer xfer = { + .len = 0, + .tx_buf = NULL, + .rx_buf = NULL + }; int error; iio_dev = iio_device_alloc(sizeof(struct nanohub_spi_data)); @@ -468,7 +762,7 @@ static int nanohub_spi_probe(struct spi_device *spi) spi_data->data.max_speed_hz = spi->max_speed_hz ? : SPI_MAX_SPEED_HZ; nanohub_spi_comms_init(spi_data); -#ifdef CONFIG_NANOHUB_BL +#ifdef CONFIG_NANOHUB_BL_ST spi_data->data.bl.cmd_erase = CMD_ERASE; spi_data->data.bl.cmd_read_memory = CMD_READ_MEMORY; spi_data->data.bl.cmd_write_memory = CMD_WRITE_MEMORY; @@ -480,6 +774,19 @@ static int nanohub_spi_probe(struct spi_device *spi) nanohub_spi_bl_init(spi_data); #endif +#ifdef CONFIG_NANOHUB_BL_NXP + nanohub_spi_bl_init(spi_data); +#endif + + // Set up the SPI bus and run an empty message to set the idle state. + // This setup can overridden in nanohub_spi_open and spi_bl_open. + spi_data->device->max_speed_hz = spi_data->data.max_speed_hz; + spi_data->device->mode = SPI_MODE_3; + spi_data->device->bits_per_word = SPI_BITS_PER_WORD; + spi_setup(spi_data->device); + spi_message_init_with_transfers(&msg, &xfer, 1); + spi_sync_locked(spi_data->device, &msg); + nanohub_start(&spi_data->data); return 0; |