| /* |
| * 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 <spiflash/spiflash.h> |
| |
| #if MYNEWT_VAL(SPIFLASH) |
| |
| #if MYNEWT_VAL(SPIFLASH_MANUFACTURER) == 0 |
| #error SPIFLASH_MANUFACTURER must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| #if MYNEWT_VAL(SPIFLASH_MEMORY_TYPE) == 0 |
| #error SPIFLASH_MEMORY_TYPE must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| #if MYNEWT_VAL(SPIFLASH_MEMORY_CAPACITY) == 0 |
| #error SPIFLASH_MEMORY_CAPACITY must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| #if MYNEWT_VAL(SPIFLASH_SPI_CS_PIN) < 0 |
| #error SPIFLASH_SPI_CS_PIN must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| #if MYNEWT_VAL(SPIFLASH_SECTOR_COUNT) == 0 |
| #error SPIFLASH_SECTOR_COUNT must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| #if MYNEWT_VAL(SPIFLASH_SECTOR_SIZE) == 0 |
| #error SPIFLASH_SECTOR_SIZE must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| #if MYNEWT_VAL(SPIFLASH_PAGE_SIZE) == 0 |
| #error SPIFLASH_PAGE_SIZE must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| #if MYNEWT_VAL(SPIFLASH_BAUDRATE) == 0 |
| #error SPIFLASH_BAUDRATE must be set to the correct value in bsp syscfg.yml |
| #endif |
| |
| static int spiflash_read(const struct hal_flash *hal_flash_dev, uint32_t addr, |
| void *buf, uint32_t len); |
| static int spiflash_write(const struct hal_flash *hal_flash_dev, uint32_t addr, |
| const void *buf, uint32_t len); |
| static int spiflash_erase_sector(const struct hal_flash *hal_flash_dev, |
| uint32_t sector_address); |
| static int spiflash_sector_info(const struct hal_flash *hal_flash_dev, int idx, |
| uint32_t *address, uint32_t *sz); |
| |
| static const struct hal_flash_funcs spiflash_flash_funcs = { |
| .hff_read = spiflash_read, |
| .hff_write = spiflash_write, |
| .hff_erase_sector = spiflash_erase_sector, |
| .hff_sector_info = spiflash_sector_info, |
| .hff_init = spiflash_init, |
| }; |
| |
| struct spiflash_dev spiflash_dev = { |
| /* struct hal_flash for compatibility */ |
| .hal = { |
| .hf_itf = &spiflash_flash_funcs, |
| .hf_base_addr = 0, |
| .hf_size = MYNEWT_VAL(SPIFLASH_SECTOR_COUNT) * |
| MYNEWT_VAL(SPIFLASH_SECTOR_SIZE), |
| .hf_sector_cnt = MYNEWT_VAL(SPIFLASH_SECTOR_COUNT), |
| .hf_align = 1, |
| .hf_erased_val = 0xff, |
| }, |
| |
| /* SPI settings */ |
| .spi_settings = { |
| .data_order = HAL_SPI_MSB_FIRST, |
| .data_mode = HAL_SPI_MODE3, |
| .baudrate = MYNEWT_VAL(SPIFLASH_BAUDRATE), |
| .word_size = HAL_SPI_WORD_SIZE_8BIT, |
| }, |
| |
| .sector_size = MYNEWT_VAL(SPIFLASH_SECTOR_SIZE), |
| .page_size = MYNEWT_VAL(SPIFLASH_PAGE_SIZE), |
| .spi_num = MYNEWT_VAL(SPIFLASH_SPI_NUM), |
| .spi_cfg = NULL, |
| .ss_pin = MYNEWT_VAL(SPIFLASH_SPI_CS_PIN), |
| }; |
| |
| static inline void |
| spiflash_cs_activate(struct spiflash_dev *dev) |
| { |
| hal_gpio_write(dev->ss_pin, 0); |
| } |
| |
| static inline void |
| spiflash_cs_deactivate(struct spiflash_dev *dev) |
| { |
| hal_gpio_write(dev->ss_pin, 1); |
| } |
| |
| uint8_t |
| spiflash_release_power_down(struct spiflash_dev *dev, uint8_t *id) |
| { |
| uint8_t cmd[5] = { SPIFLASH_RELEASE_POWER_DOWN, 0xFF, 0xFF, 0xFF, 0 }; |
| |
| spiflash_cs_activate(dev); |
| |
| hal_spi_txrx(dev->spi_num, cmd, cmd, sizeof cmd); |
| |
| spiflash_cs_deactivate(dev); |
| |
| if (id) { |
| *id = cmd[4]; |
| } |
| |
| return 0; |
| } |
| |
| uint8_t |
| spiflash_read_jedec_id(struct spiflash_dev *dev, |
| uint8_t *manufacturer, uint8_t *memory_type, uint8_t *capacity) |
| { |
| uint8_t cmd[4] = { SPIFLASH_READ_JEDEC_ID, 0, 0, 0 }; |
| |
| spiflash_cs_activate(dev); |
| |
| hal_spi_txrx(dev->spi_num, cmd, cmd, sizeof cmd); |
| |
| spiflash_cs_deactivate(dev); |
| |
| if (manufacturer) { |
| *manufacturer = cmd[1]; |
| } |
| |
| if (memory_type) { |
| *memory_type = cmd[2]; |
| } |
| |
| if (capacity) { |
| *capacity = cmd[3]; |
| } |
| |
| return 0; |
| } |
| |
| uint8_t |
| spiflash_read_status(struct spiflash_dev *dev) |
| { |
| uint8_t val; |
| |
| spiflash_cs_activate(dev); |
| |
| hal_spi_tx_val(dev->spi_num, SPIFLASH_READ_STATUS_REGISTER); |
| val = hal_spi_tx_val(dev->spi_num, 0xFF); |
| |
| spiflash_cs_deactivate(dev); |
| |
| return val; |
| } |
| |
| bool |
| spiflash_device_ready(struct spiflash_dev *dev) |
| { |
| return !(spiflash_read_status(dev) & SPIFLASH_STATUS_BUSY); |
| } |
| |
| int |
| spiflash_wait_ready(struct spiflash_dev *dev, uint32_t timeout_ms) |
| { |
| uint32_t ticks; |
| os_time_t exp_time; |
| os_time_ms_to_ticks(timeout_ms, &ticks); |
| exp_time = os_time_get() + ticks; |
| |
| while (!spiflash_device_ready(dev)) { |
| if (os_time_get() > exp_time) { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| int |
| spiflash_write_enable(struct spiflash_dev *dev) |
| { |
| spiflash_cs_activate(dev); |
| |
| hal_spi_tx_val(dev->spi_num, SPIFLASH_WRITE_ENABLE); |
| |
| spiflash_cs_deactivate(dev); |
| |
| return 0; |
| } |
| |
| int |
| spiflash_read(const struct hal_flash *hal_flash_dev, uint32_t addr, void *buf, |
| uint32_t len) |
| { |
| int err = 0; |
| uint8_t cmd[] = { SPIFLASH_READ, |
| (uint8_t)(addr >> 16), (uint8_t)(addr >> 8), (uint8_t)(addr) }; |
| struct spiflash_dev *dev; |
| |
| dev = (struct spiflash_dev *)hal_flash_dev; |
| |
| err = spiflash_wait_ready(dev, 100); |
| if (!err) { |
| spiflash_cs_activate(dev); |
| |
| /* Send command + address */ |
| hal_spi_txrx(dev->spi_num, cmd, NULL, sizeof cmd); |
| /* For security mostly, do not output random data, fill it with FF */ |
| memset(buf, 0xFF, len); |
| /* Tx buf does not matter, for simplicity pass read buffer */ |
| hal_spi_txrx(dev->spi_num, buf, buf, len); |
| |
| spiflash_cs_deactivate(dev); |
| } |
| |
| return 0; |
| } |
| |
| int |
| spiflash_write(const struct hal_flash *hal_flash_dev, uint32_t addr, |
| const void *buf, uint32_t len) |
| { |
| uint8_t cmd[4] = { SPIFLASH_PAGE_PROGRAM }; |
| const uint8_t *u8buf = buf; |
| struct spiflash_dev *dev = (struct spiflash_dev *)hal_flash_dev; |
| uint32_t page_limit; |
| uint32_t to_write; |
| |
| u8buf = (uint8_t *)buf; |
| |
| while (len) { |
| if (spiflash_wait_ready(dev, 100) != 0) { |
| return -1; |
| } |
| |
| spiflash_write_enable(dev); |
| |
| cmd[1] = (uint8_t)(addr >> 16); |
| cmd[2] = (uint8_t)(addr >> 8); |
| cmd[3] = (uint8_t)(addr); |
| |
| page_limit = (addr & ~(dev->page_size - 1)) + dev->page_size; |
| to_write = page_limit - addr > len ? len : page_limit - addr; |
| |
| spiflash_cs_activate(dev); |
| hal_spi_txrx(dev->spi_num, cmd, NULL, sizeof cmd); |
| hal_spi_txrx(dev->spi_num, (void *)u8buf, NULL, to_write); |
| spiflash_cs_deactivate(dev); |
| |
| addr += to_write; |
| u8buf += to_write; |
| len -= to_write; |
| |
| spiflash_wait_ready(dev, 100); |
| } |
| |
| return 0; |
| } |
| |
| int |
| spiflash_erase_sector(const struct hal_flash *hal_flash_dev, |
| uint32_t addr) |
| { |
| struct spiflash_dev *dev; |
| uint8_t cmd[4] = { SPIFLASH_SECTOR_ERASE, |
| (uint8_t)(addr >> 16), (uint8_t)(addr >> 8), (uint8_t)addr }; |
| |
| dev = (struct spiflash_dev *)hal_flash_dev; |
| |
| if (spiflash_wait_ready(dev, 100) != 0) { |
| return -1; |
| } |
| |
| spiflash_write_enable(dev); |
| |
| spiflash_read_status(dev); |
| |
| spiflash_cs_activate(dev); |
| |
| hal_spi_txrx(dev->spi_num, cmd, NULL, sizeof cmd); |
| |
| spiflash_cs_deactivate(dev); |
| |
| spiflash_wait_ready(dev, 100); |
| |
| return 0; |
| } |
| |
| int |
| spiflash_sector_info(const struct hal_flash *hal_flash_dev, int idx, |
| uint32_t *address, uint32_t *sz) |
| { |
| const struct spiflash_dev *dev = (const struct spiflash_dev *)hal_flash_dev; |
| |
| *address = idx * dev->sector_size; |
| *sz = dev->sector_size; |
| return 0; |
| } |
| |
| int |
| spiflash_init(const struct hal_flash *hal_flash_dev) |
| { |
| int rc; |
| struct spiflash_dev *dev; |
| uint8_t manufacturer; |
| uint8_t memory_type; |
| uint8_t capacity; |
| |
| dev = (struct spiflash_dev *)hal_flash_dev; |
| |
| hal_gpio_init_out(dev->ss_pin, 1); |
| |
| rc = hal_spi_config(dev->spi_num, &dev->spi_settings); |
| if (rc) { |
| return (rc); |
| } |
| |
| hal_spi_set_txrx_cb(dev->spi_num, NULL, NULL); |
| hal_spi_enable(dev->spi_num); |
| |
| spiflash_release_power_down(dev, &manufacturer); |
| spiflash_read_jedec_id(dev, &manufacturer, &memory_type, &capacity); |
| /* If BSP defined SpiFlash manufacturer or memory type does not |
| * match SpiFlash is most likely not connected, connected to |
| * different pins, or of different type. |
| * It is unlikely that flash depended packaged will work correctly. |
| */ |
| assert(manufacturer == MYNEWT_VAL(SPIFLASH_MANUFACTURER) || |
| memory_type == MYNEWT_VAL(SPIFLASH_MEMORY_TYPE) || |
| capacity == MYNEWT_VAL(SPIFLASH_MEMORY_CAPACITY)); |
| if (manufacturer != MYNEWT_VAL(SPIFLASH_MANUFACTURER) || |
| memory_type != MYNEWT_VAL(SPIFLASH_MEMORY_TYPE) || |
| capacity != MYNEWT_VAL(SPIFLASH_MEMORY_CAPACITY)) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| #endif |
| |