blob: 14e1d73e3313affd32b6f65f4e286c76ec882c6c [file] [log] [blame]
/*
* 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;
}