| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include <string.h> |
| |
| #include "os/mynewt.h" |
| #include <hal/hal_spi.h> |
| #include <hal/hal_gpio.h> |
| #include <hal/hal_flash.h> |
| #include <hal/hal_flash_int.h> |
| #include <at45db/at45db.h> |
| |
| /** |
| * Memory Architecture: |
| * |
| * Device can be addressed using pages, blocks or sectors. |
| * |
| * 1) Page |
| * - device has 8192 pages of 512 (or 528) bytes. |
| * |
| * 2) Block |
| * - device has 1024 blocks of 4K (or 4K + 128) bytes. |
| * - Each block contains 8 pages, eg, Block 0 == Page 0 to 7, etc. |
| * |
| * 3) Sector |
| * - Sector 0 == Block 0. |
| * - Sector 1 == Blocks 1 to 63 (252K + 8064). |
| * - Sector 2 to 16 contain 64 blocks (256K + 8192). |
| */ |
| |
| #define MEM_READ 0x52 /* Read page bypassing buffer */ |
| #define BUF1_READ 0x54 |
| #define BUF2_READ 0x56 |
| #define MEM_TO_BUF1_TRANSFER 0x53 |
| #define MEM_TO_BUF2_TRANSFER 0x55 |
| #define MEM_TO_BUF1_CMP 0x60 |
| #define MEM_TO_BUF2_CMP 0x61 |
| #define BUF1_WRITE 0x84 |
| #define BUF2_WRITE 0x87 |
| #define BUF1_TO_MEM_ERASE 0x83 |
| #define BUF2_TO_MEM_ERASE 0x86 |
| #define BUF1_TO_MEM_NO_ERASE 0x88 |
| #define BUF2_TO_MEM_NO_ERASE 0x89 |
| #define PAGE_ERASE 0x81 |
| #define BLOCK_ERASE 0x50 |
| |
| #define STATUS_REGISTER 0x57 |
| |
| #define STATUS_BUSY (1 << 7) |
| #define STATUS_CMP (1 << 6) |
| |
| #define MAX_PAGE_SIZE 528 |
| |
| static const struct hal_flash_funcs at45db_flash_funcs = { |
| .hff_read = at45db_read, |
| .hff_write = at45db_write, |
| .hff_erase_sector = at45db_erase_sector, |
| .hff_sector_info = at45db_sector_info, |
| .hff_init = at45db_init, |
| }; |
| |
| static struct at45db_dev at45db_default_dev = { |
| /* struct hal_flash for compatibility */ |
| .hal = { |
| .hf_itf = &at45db_flash_funcs, |
| .hf_base_addr = 0, |
| .hf_size = 8192 * 512, /* FIXME: depends on page size */ |
| .hf_sector_cnt = 8192, |
| .hf_align = 0, |
| .hf_erased_val = 0xff, |
| }, |
| |
| /* SPI settings + updates baudrate on _init */ |
| .settings = NULL, |
| |
| /* Configurable fields that must be populated by user app */ |
| .spi_num = 0, |
| .spi_cfg = NULL, |
| .ss_pin = 0, |
| .baudrate = 100, |
| .page_size = 512, |
| .disable_auto_erase = 0, |
| }; |
| |
| static struct hal_spi_settings at45db_default_settings = { |
| .data_order = HAL_SPI_MSB_FIRST, |
| .data_mode = HAL_SPI_MODE3, |
| .baudrate = 100, |
| .word_size = HAL_SPI_WORD_SIZE_8BIT, |
| }; |
| |
| static uint8_t g_page_buffer[MAX_PAGE_SIZE]; |
| |
| static uint8_t |
| at45db_read_status(struct at45db_dev *dev) |
| { |
| uint8_t val; |
| |
| hal_gpio_write(dev->ss_pin, 0); |
| |
| hal_spi_tx_val(dev->spi_num, STATUS_REGISTER); |
| val = hal_spi_tx_val(dev->spi_num, 0xff); |
| |
| hal_gpio_write(dev->ss_pin, 1); |
| |
| return val; |
| } |
| |
| static inline bool |
| at45db_device_ready(struct at45db_dev *dev) |
| { |
| return ((at45db_read_status(dev) & STATUS_BUSY) != 0); |
| } |
| |
| static inline bool |
| at45db_buffer_equal(struct at45db_dev *dev) |
| { |
| return ((at45db_read_status(dev) & STATUS_CMP) == 0); |
| } |
| |
| /* FIXME: add timeout */ |
| static inline void |
| at45db_wait_ready(struct at45db_dev *dev) |
| { |
| while (!at45db_device_ready(dev)) { |
| os_time_delay(OS_TICKS_PER_SEC / 10000); |
| } |
| } |
| |
| static inline uint16_t |
| at45db_calc_page_count(struct at45db_dev *dev, uint32_t addr, size_t len) |
| { |
| uint16_t page_count; |
| uint16_t page_size = dev->page_size; |
| |
| page_count = 1 + (len / (page_size + 1)); |
| if ((addr % page_size) + len > page_size * page_count) { |
| page_count++; |
| } |
| |
| return page_count; |
| } |
| |
| static inline uint32_t |
| at45db_page_start_address(struct at45db_dev *dev, uint32_t addr) |
| { |
| /* FIXME: works only for 512? (powers of 2) */ |
| return (addr & ~(dev->page_size - 1)); |
| } |
| |
| static inline uint32_t |
| at45db_page_next_addr(struct at45db_dev *dev, uint32_t addr) |
| { |
| return (at45db_page_start_address(dev, addr) + dev->page_size); |
| } |
| |
| /* FIXME: assume buf has enough space? */ |
| static uint16_t |
| at45db_read_page(struct at45db_dev *dev, uint32_t addr, |
| uint16_t len, uint8_t *buf) |
| { |
| uint16_t amount; |
| uint16_t pa; |
| uint16_t ba; |
| uint16_t n; |
| uint8_t val; |
| uint16_t page_size; |
| |
| hal_gpio_write(dev->ss_pin, 0); |
| |
| hal_spi_tx_val(dev->spi_num, MEM_READ); |
| |
| page_size = dev->page_size; |
| pa = addr / page_size; |
| ba = addr % page_size; |
| |
| val = (pa >> 6) & ~0x80; |
| hal_spi_tx_val(dev->spi_num, val); |
| |
| if (page_size <= 512) { |
| val = (pa << 2) | ((ba >> 8) & 1); |
| } else { |
| val = (pa << 2) | ((ba >> 8) & 3); |
| } |
| hal_spi_tx_val(dev->spi_num, val); |
| |
| hal_spi_tx_val(dev->spi_num, ba); |
| |
| hal_spi_tx_val(dev->spi_num, 0xff); |
| hal_spi_tx_val(dev->spi_num, 0xff); |
| hal_spi_tx_val(dev->spi_num, 0xff); |
| hal_spi_tx_val(dev->spi_num, 0xff); |
| |
| if (len + ba <= page_size) { |
| amount = len; |
| } else { |
| amount = page_size - ba; |
| } |
| |
| for (n = 0; n < amount; n++) { |
| buf[n] = hal_spi_tx_val(dev->spi_num, 0xff); |
| } |
| |
| hal_gpio_write(dev->ss_pin, 1); |
| |
| return amount; |
| } |
| |
| int |
| at45db_read(const struct hal_flash *hal_flash_dev, uint32_t addr, void *buf, |
| uint32_t len) |
| { |
| uint16_t page_count; |
| uint16_t amount; |
| uint16_t index; |
| uint8_t *u8buf; |
| struct at45db_dev *dev; |
| |
| dev = (struct at45db_dev *) hal_flash_dev; |
| |
| page_count = at45db_calc_page_count(dev, addr, len); |
| u8buf = (uint8_t *) buf; |
| index = 0; |
| |
| while (page_count--) { |
| at45db_wait_ready(dev); |
| |
| amount = at45db_read_page(dev, addr, len, &u8buf[index]); |
| |
| addr = at45db_page_next_addr(dev, addr); |
| index += amount; |
| len -= amount; |
| } |
| |
| return 0; |
| } |
| |
| int |
| at45db_write(const struct hal_flash *hal_flash_dev, uint32_t addr, |
| const void *buf, uint32_t len) |
| { |
| uint16_t pa; |
| uint16_t bfa; |
| uint32_t n; |
| uint32_t start_addr; |
| uint16_t index; |
| uint16_t amount; |
| int page_count; |
| uint8_t *u8buf; |
| uint16_t page_size; |
| struct at45db_dev *dev; |
| |
| dev = (struct at45db_dev *) hal_flash_dev; |
| |
| page_size = dev->page_size; |
| page_count = at45db_calc_page_count(dev, addr, len); |
| u8buf = (uint8_t *) buf; |
| index = 0; |
| |
| while (page_count--) { |
| at45db_wait_ready(dev); |
| |
| bfa = addr % page_size; |
| start_addr = at45db_page_start_address(dev, addr); |
| |
| /** |
| * If the page is not being written from the beginning, |
| * read first the current data to rewrite back. |
| * |
| * The whole page is read here for the case of some the |
| * real data ending before the end of the page, data must |
| * be written back again. |
| */ |
| if (bfa || len < page_size) { |
| at45db_read_page(dev, start_addr, page_size, g_page_buffer); |
| at45db_wait_ready(dev); |
| } |
| |
| hal_gpio_write(dev->ss_pin, 0); |
| |
| /* TODO: ping-pong between page 1 and 2? */ |
| hal_spi_tx_val(dev->spi_num, BUF1_WRITE); |
| |
| hal_spi_tx_val(dev->spi_num, 0xff); |
| |
| /* Always write at offset 0 of internal buffer */ |
| hal_spi_tx_val(dev->spi_num, 0); |
| hal_spi_tx_val(dev->spi_num, 0); |
| |
| /** |
| * Write back extra stuff at the beginning of page. |
| */ |
| if (bfa) { |
| amount = addr - start_addr; |
| for (n = 0; n < amount; n++) { |
| hal_spi_tx_val(dev->spi_num, g_page_buffer[n]); |
| } |
| } |
| |
| if (len + bfa <= page_size) { |
| amount = len; |
| } else { |
| amount = page_size - bfa; |
| } |
| |
| /** |
| * Write the stuff we're really want to write! |
| */ |
| for (n = 0; n < amount; n++) { |
| hal_spi_tx_val(dev->spi_num, u8buf[index++]); |
| } |
| |
| /** |
| * Write back extra stuff at the ending of page. |
| */ |
| if (bfa + len < page_size) { |
| for (n = len + bfa; n < page_size; n++) { |
| hal_spi_tx_val(dev->spi_num, g_page_buffer[n]); |
| } |
| } |
| |
| hal_gpio_write(dev->ss_pin, 1); |
| |
| at45db_wait_ready(dev); |
| |
| hal_gpio_write(dev->ss_pin, 0); |
| |
| if (dev->disable_auto_erase) { |
| hal_spi_tx_val(dev->spi_num, BUF1_TO_MEM_NO_ERASE); |
| } else { |
| hal_spi_tx_val(dev->spi_num, BUF1_TO_MEM_ERASE); |
| } |
| |
| /* FIXME: check that pa doesn't overflow capacity */ |
| pa = addr / page_size; |
| |
| hal_spi_tx_val(dev->spi_num, (pa >> 6) & ~0x80); |
| hal_spi_tx_val(dev->spi_num, pa << 2); |
| hal_spi_tx_val(dev->spi_num, 0xff); |
| |
| hal_gpio_write(dev->ss_pin, 1); |
| |
| addr = at45db_page_next_addr(dev, addr); |
| len -= amount; |
| } |
| |
| return 0; |
| } |
| |
| int |
| at45db_erase_sector(const struct hal_flash *hal_flash_dev, |
| uint32_t sector_address) |
| { |
| struct at45db_dev *dev; |
| uint16_t pa; |
| |
| dev = (struct at45db_dev *) hal_flash_dev; |
| pa = sector_address / dev->page_size; |
| |
| at45db_wait_ready(dev); |
| |
| hal_gpio_write(dev->ss_pin, 0); |
| |
| hal_spi_tx_val(dev->spi_num, PAGE_ERASE); |
| hal_spi_tx_val(dev->spi_num, (pa >> 6) & ~0x80); |
| hal_spi_tx_val(dev->spi_num, pa << 2); |
| hal_spi_tx_val(dev->spi_num, 0xff); |
| |
| hal_gpio_write(dev->ss_pin, 1); |
| |
| return 0; |
| } |
| |
| int |
| at45db_sector_info(const struct hal_flash *hal_flash_dev, int idx, |
| uint32_t *address, uint32_t *sz) |
| { |
| struct at45db_dev *dev = (struct at45db_dev *) hal_flash_dev; |
| |
| *address = idx * dev->page_size; |
| *sz = dev->page_size; |
| return 0; |
| } |
| |
| struct at45db_dev * |
| at45db_default_config(void) |
| { |
| struct at45db_dev *dev; |
| |
| dev = malloc(sizeof(at45db_default_dev)); |
| if (!dev) { |
| return NULL; |
| } |
| |
| memcpy(dev, &at45db_default_dev, sizeof(at45db_default_dev)); |
| return dev; |
| } |
| |
| int |
| at45db_init(const struct hal_flash *hal_flash_dev) |
| { |
| int rc; |
| struct hal_spi_settings *settings; |
| struct at45db_dev *dev; |
| |
| dev = (struct at45db_dev *) hal_flash_dev; |
| |
| /* only alloc new settings if using non-default */ |
| if (dev->baudrate == at45db_default_settings.baudrate) { |
| dev->settings = &at45db_default_settings; |
| } else { |
| settings = malloc(sizeof(at45db_default_settings)); |
| if (!settings) { |
| return -1; |
| } |
| memcpy(settings, &at45db_default_settings, sizeof(at45db_default_settings)); |
| at45db_default_settings.baudrate = dev->baudrate; |
| } |
| |
| hal_gpio_init_out(dev->ss_pin, 1); |
| |
| rc = hal_spi_init(dev->spi_num, dev->spi_cfg, HAL_SPI_TYPE_MASTER); |
| if (rc) { |
| return (rc); |
| } |
| |
| rc = hal_spi_config(dev->spi_num, dev->settings); |
| if (rc) { |
| return (rc); |
| } |
| |
| hal_spi_set_txrx_cb(dev->spi_num, NULL, NULL); |
| hal_spi_enable(dev->spi_num); |
| |
| return 0; |
| } |