summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorChe-Liang Chiou <clchiou@chromium.org>2011-03-30 14:58:17 +0800
committerSimon Glass <sjg@chromium.org>2011-08-24 10:00:43 -0700
commit88299441f8dc6d00b1fcc742b754d804649ca156 (patch)
tree025e9f4d26ba05b7c069e6ad7f7c8c18cc8d0b80 /lib
parent71e94739ce1ef991e6d3630033b3d0ca4530682b (diff)
Add implementation of SPI firmware storage interface
R=robotboy@chromium.org,waihong@chromium.org BUG=chromium-os:13063 TEST=see cl/6676109 Review URL: http://codereview.chromium.org/6696066 Change-Id: Ief655c3055cf8e0857a67085f4c76d8862a7228b
Diffstat (limited to 'lib')
-rw-r--r--lib/chromeos/firmware_storage_spi.c217
1 files changed, 215 insertions, 2 deletions
diff --git a/lib/chromeos/firmware_storage_spi.c b/lib/chromeos/firmware_storage_spi.c
index 5f21cb8e752..fc71a2ad15b 100644
--- a/lib/chromeos/firmware_storage_spi.c
+++ b/lib/chromeos/firmware_storage_spi.c
@@ -11,10 +11,223 @@
/* Implementation of firmware storage access interface for SPI */
#include <common.h>
+#include <malloc.h>
+#include <spi_flash.h>
#include <chromeos/firmware_storage.h>
+#define PREFIX "firmware_storage_spi: "
+
+#ifndef CONFIG_SF_DEFAULT_SPEED
+# define CONFIG_SF_DEFAULT_SPEED 1000000
+#endif
+#ifndef CONFIG_SF_DEFAULT_MODE
+# define CONFIG_SF_DEFAULT_MODE SPI_MODE_3
+#endif
+
+struct context_t {
+ struct spi_flash *flash;
+ u32 offset;
+};
+
+static off_t seek_spi(void *context, off_t offset, enum whence_t whence)
+{
+ struct context_t *cxt = context;
+ u32 next_offset;
+
+ if (whence == SEEK_SET)
+ next_offset = offset;
+ else if (whence == SEEK_CUR)
+ next_offset = cxt->offset + offset;
+ else if (whence == SEEK_END)
+ next_offset = cxt->flash->size + offset;
+ else {
+ debug(PREFIX "unknown whence value: %d\n", whence);
+ return -1;
+ }
+
+ if (next_offset < 0) {
+ debug(PREFIX "negative offset: %d\n", next_offset);
+ return -1;
+ }
+
+ if (next_offset > cxt->flash->size) {
+ debug(PREFIX "offset overflow: 0x%08x > 0x%08x\n",
+ next_offset, cxt->flash->size);
+ return -1;
+ }
+
+ cxt->offset = next_offset;
+ return cxt->offset;
+}
+
+/*
+ * Check the right-exclusive range [offset:offset+*count_ptr), and adjust
+ * value pointed by <count_ptr> to form a valid range when needed.
+ *
+ * Return 0 if it is possible to form a valid range. and non-zero if not.
+ */
+static int border_check(struct spi_flash *flash, u32 offset,
+ size_t *count_ptr)
+{
+ if (offset >= flash->size) {
+ debug(PREFIX "at EOF\n");
+ return -1;
+ }
+
+ if (offset + *count_ptr > flash->size)
+ *count_ptr = flash->size - offset;
+
+ return 0;
+}
+
+static ssize_t read_spi(void *context, void *buf, size_t count)
+{
+ struct context_t *cxt = context;
+
+ if (border_check(cxt->flash, cxt->offset, &count))
+ return -1;
+
+ if (cxt->flash->read(cxt->flash, cxt->offset, count, buf)) {
+ debug(PREFIX "SPI read fail\n");
+ return -1;
+ }
+
+ cxt->offset += count;
+ return count;
+}
+
+/*
+ * FIXME: It is a reasonable assumption that sector size = 4096 bytes.
+ * Nevertheless, comparing to coding this magic number here, there should be a
+ * better way (maybe rewrite driver interface?) to expose this parameter from
+ * eeprom driver.
+ */
+#define SECTOR_SIZE 0x1000
+
+/*
+ * Align the right-exclusive range [*offset_ptr:*offset_ptr+*length_ptr) with
+ * SECTOR_SIZE.
+ * After alignment adjustment, both offset and length will be multiple of
+ * SECTOR_SIZE, and will be larger than or equal to the original range.
+ */
+static void align_to_sector(size_t *offset_ptr, size_t *length_ptr)
+{
+ debug(PREFIX "before adjustment\n");
+ debug(PREFIX "offset: 0x%lx\n", *offset_ptr);
+ debug(PREFIX "length: 0x%lx\n", *length_ptr);
+
+ /* Adjust if offset is not multiple of SECTOR_SIZE */
+ if (*offset_ptr & (SECTOR_SIZE - 1ul)) {
+ *offset_ptr &= ~(SECTOR_SIZE - 1ul);
+ }
+
+ /* Adjust if length is not multiple of SECTOR_SIZE */
+ if (*length_ptr & (SECTOR_SIZE - 1ul)) {
+ *length_ptr &= ~(SECTOR_SIZE - 1ul);
+ *length_ptr += SECTOR_SIZE;
+ }
+
+ debug(PREFIX "after adjustment\n");
+ debug(PREFIX "offset: 0x%lx\n", *offset_ptr);
+ debug(PREFIX "length: 0x%lx\n", *length_ptr);
+}
+
+static ssize_t write_spi(void *context, const void *buf, size_t count)
+{
+ struct context_t *cxt = context;
+ struct spi_flash *flash = cxt->flash;
+ uint8_t static_buf[SECTOR_SIZE];
+ uint8_t *backup_buf;
+ ssize_t ret = -1;
+ size_t offset, length, tmp;
+ int status;
+
+ /* We will erase <length> bytes starting from <offset> */
+ offset = cxt->offset;
+ length = count;
+ align_to_sector(&offset, &length);
+
+ tmp = length;
+ if (border_check(flash, offset, &tmp))
+ return -1;
+ if (tmp != length) {
+ debug(PREFIX "cannot erase range [%08lx:%08lx]: %08lx\n",
+ offset, offset + length, offset + tmp);
+ return -1;
+ }
+
+ backup_buf = length > sizeof(static_buf) ? malloc(length) : static_buf;
+
+ if ((status = flash->read(flash, offset, length, backup_buf))) {
+ debug(PREFIX "cannot backup data: %d\n", status);
+ goto EXIT;
+ }
+
+ if ((status = flash->erase(flash, offset, length))) {
+ debug(PREFIX "SPI erase fail: %d\n", status);
+ goto EXIT;
+ }
+
+ debug(PREFIX "cxt->offset: 0x%08lx\n", cxt->offset);
+ debug(PREFIX "offset: 0x%08lx\n", offset);
+
+ /* combine data we want to write and backup data */
+ memcpy(backup_buf + (cxt->offset - offset), buf, count);
+
+ if (flash->write(flash, offset, length, backup_buf)) {
+ debug(PREFIX "SPI write fail\n");
+ goto EXIT;
+ }
+
+ cxt->offset += count;
+ ret = count;
+
+EXIT:
+ if (backup_buf != static_buf)
+ free(backup_buf);
+
+ return ret;
+}
+
+static int close_spi(void *context)
+{
+ struct context_t *cxt = context;
+
+ spi_flash_free(cxt->flash);
+ free(cxt);
+
+ return 0;
+}
+
+static int lock_spi(void *context)
+{
+ /* TODO Implement lock device */
+ return 0;
+}
+
int firmware_storage_init_spi(firmware_storage_t *file)
{
- /* TODO Implement interface to SPI */
- return -1;
+ const unsigned int bus = 0;
+ const unsigned int cs = 0;
+ const unsigned int max_hz = CONFIG_SF_DEFAULT_SPEED;
+ const unsigned int spi_mode = CONFIG_SF_DEFAULT_MODE;
+ struct context_t *cxt;
+
+ cxt = malloc(sizeof(*cxt));
+ cxt->offset = 0;
+ cxt->flash = spi_flash_probe(bus, cs, max_hz, spi_mode);
+ if (!cxt->flash) {
+ debug(PREFIX "fail to init SPI flash at %u:%u\n", bus, cs);
+ free(cxt);
+ return -1;
+ }
+
+ file->seek = seek_spi;
+ file->read = read_spi;
+ file->write = write_spi;
+ file->close = close_spi;
+ file->lock_device = lock_spi;
+ file->context = (void*) cxt;
+
+ return 0;
}