368 lines
8.5 KiB
C
368 lines
8.5 KiB
C
/*
|
|
* Copyright (c) 2016 Intel Corporation.
|
|
*
|
|
* Licensed 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 <errno.h>
|
|
|
|
#include <device.h>
|
|
#include <init.h>
|
|
|
|
#include <flash.h>
|
|
|
|
#include "qm_flash.h"
|
|
#include "qm_soc_regs.h"
|
|
|
|
struct soc_flash_data {
|
|
#ifdef CONFIG_SOC_FLASH_QMSI_API_REENTRANCY
|
|
struct k_sem sem;
|
|
#endif
|
|
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
|
uint32_t device_power_state;
|
|
#ifdef CONFIG_SYS_POWER_DEEP_SLEEP
|
|
qm_flash_context_t saved_ctx[QM_FLASH_NUM];
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
#define FLASH_HAS_CONTEXT_DATA \
|
|
(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY || CONFIG_DEVICE_POWER_MANAGEMENT)
|
|
|
|
#if FLASH_HAS_CONTEXT_DATA
|
|
static struct soc_flash_data soc_flash_context;
|
|
#define FLASH_CONTEXT (&soc_flash_context)
|
|
#else
|
|
#define FLASH_CONTEXT (NULL)
|
|
#endif /* FLASH_HAS_CONTEXT_DATA */
|
|
|
|
#ifdef CONFIG_SOC_FLASH_QMSI_API_REENTRANCY
|
|
static const int reentrancy_protection = 1;
|
|
#define RP_GET(dev) (&((struct soc_flash_data *)(dev->driver_data))->sem)
|
|
#else
|
|
static const int reentrancy_protection;
|
|
#define RP_GET(dev) (NULL)
|
|
#endif
|
|
|
|
static void flash_reentrancy_init(struct device *dev)
|
|
{
|
|
if (!reentrancy_protection) {
|
|
return;
|
|
}
|
|
|
|
k_sem_init(RP_GET(dev), 0, UINT_MAX);
|
|
k_sem_give(RP_GET(dev));
|
|
}
|
|
|
|
static void flash_critical_region_start(struct device *dev)
|
|
{
|
|
if (!reentrancy_protection) {
|
|
return;
|
|
}
|
|
|
|
k_sem_take(RP_GET(dev), K_FOREVER);
|
|
}
|
|
|
|
static void flash_critical_region_end(struct device *dev)
|
|
{
|
|
if (!reentrancy_protection) {
|
|
return;
|
|
}
|
|
|
|
k_sem_give(RP_GET(dev));
|
|
}
|
|
|
|
static inline bool is_aligned_32(uint32_t data)
|
|
{
|
|
return (data & 0x3) ? false : true;
|
|
}
|
|
|
|
static qm_flash_region_t flash_region(uint32_t addr)
|
|
{
|
|
if ((addr >= QM_FLASH_REGION_SYS_0_BASE) && (addr <
|
|
(QM_FLASH_REGION_SYS_0_BASE + CONFIG_SOC_FLASH_QMSI_SYS_SIZE))) {
|
|
return QM_FLASH_REGION_SYS;
|
|
}
|
|
|
|
#if defined(CONFIG_SOC_QUARK_D2000)
|
|
if ((addr >= QM_FLASH_REGION_DATA_0_BASE) &&
|
|
(addr < (QM_FLASH_REGION_DATA_0_BASE +
|
|
QM_FLASH_REGION_DATA_0_SIZE))) {
|
|
return QM_FLASH_REGION_DATA;
|
|
}
|
|
#endif
|
|
|
|
/* invalid address */
|
|
return QM_FLASH_REGION_NUM;
|
|
}
|
|
|
|
static uint32_t get_page_num(uint32_t addr)
|
|
{
|
|
switch (flash_region(addr)) {
|
|
case QM_FLASH_REGION_SYS:
|
|
return (addr - QM_FLASH_REGION_SYS_0_BASE) >>
|
|
QM_FLASH_PAGE_SIZE_BITS;
|
|
#if defined(CONFIG_SOC_QUARK_D2000)
|
|
case QM_FLASH_REGION_DATA:
|
|
return (addr - QM_FLASH_REGION_DATA_0_BASE) >>
|
|
QM_FLASH_PAGE_SIZE_BITS;
|
|
#endif
|
|
default:
|
|
/* invalid address */
|
|
return 0xffffffff;
|
|
}
|
|
}
|
|
|
|
static int flash_qmsi_read(struct device *dev, off_t addr,
|
|
void *data, size_t len)
|
|
{
|
|
if ((!is_aligned_32(len)) || (!is_aligned_32(addr))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (flash_region(addr) == QM_FLASH_REGION_NUM) {
|
|
/* starting address is not within flash */
|
|
return -EIO;
|
|
}
|
|
|
|
if (flash_region(addr + len - 4) == QM_FLASH_REGION_NUM) {
|
|
/* data area is not within flash */
|
|
return -EIO;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < (len >> 2); i++) {
|
|
UNALIGNED_PUT(sys_read32(addr + (i << 2)),
|
|
(uint32_t *)data + i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flash_qmsi_write(struct device *dev, off_t addr,
|
|
const void *data, size_t len)
|
|
{
|
|
qm_flash_t flash = QM_FLASH_0;
|
|
qm_flash_region_t reg;
|
|
uint32_t data_word = 0, offset = 0, f_addr = 0;
|
|
|
|
if ((!is_aligned_32(len)) || (!is_aligned_32(addr))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
reg = flash_region(addr);
|
|
if (reg == QM_FLASH_REGION_NUM) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (flash_region(addr + len - 4) == QM_FLASH_REGION_NUM) {
|
|
return -EIO;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < (len >> 2); i++) {
|
|
data_word = UNALIGNED_GET((uint32_t *)data + i);
|
|
reg = flash_region(addr + (i << 2));
|
|
f_addr = addr + (i << 2);
|
|
|
|
switch (reg) {
|
|
case QM_FLASH_REGION_SYS:
|
|
offset = f_addr - QM_FLASH_REGION_SYS_0_BASE;
|
|
break;
|
|
#if defined(CONFIG_SOC_QUARK_D2000)
|
|
case QM_FLASH_REGION_DATA:
|
|
offset = f_addr - QM_FLASH_REGION_DATA_0_BASE;
|
|
break;
|
|
#endif
|
|
default:
|
|
return -EIO;
|
|
}
|
|
|
|
#if defined(CONFIG_SOC_QUARK_SE_C1000)
|
|
if (offset >= (CONFIG_SOC_FLASH_QMSI_SYS_SIZE >> 1)) {
|
|
flash = QM_FLASH_1;
|
|
offset -= CONFIG_SOC_FLASH_QMSI_SYS_SIZE >> 1;
|
|
}
|
|
#endif
|
|
|
|
flash_critical_region_start(dev);
|
|
qm_flash_word_write(flash, reg, offset, data_word);
|
|
flash_critical_region_end(dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flash_qmsi_erase(struct device *dev, off_t addr, size_t size)
|
|
{
|
|
qm_flash_t flash = QM_FLASH_0;
|
|
qm_flash_region_t reg;
|
|
uint32_t page = 0;
|
|
|
|
/* starting address needs to be a 2KB aligned address */
|
|
if (addr & QM_FLASH_ADDRESS_MASK) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* size needs to be multiple of 2KB */
|
|
if (size & QM_FLASH_ADDRESS_MASK) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
reg = flash_region(addr);
|
|
if (reg == QM_FLASH_REGION_NUM) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (flash_region(addr + size - (QM_FLASH_PAGE_SIZE_DWORDS << 2)) ==
|
|
QM_FLASH_REGION_NUM) {
|
|
return -EIO;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < (size >> QM_FLASH_PAGE_SIZE_BITS); i++) {
|
|
page = get_page_num(addr) + i;
|
|
#if defined(CONFIG_SOC_QUARK_SE_C1000)
|
|
if (page >= (CONFIG_SOC_FLASH_QMSI_SYS_SIZE >>
|
|
(QM_FLASH_PAGE_SIZE_BITS + 1))) {
|
|
flash = QM_FLASH_1;
|
|
page -= (CONFIG_SOC_FLASH_QMSI_SYS_SIZE >>
|
|
(QM_FLASH_PAGE_SIZE_BITS + 1));
|
|
}
|
|
#endif
|
|
flash_critical_region_start(dev);
|
|
qm_flash_page_erase(flash, reg, page);
|
|
flash_critical_region_end(dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flash_qmsi_write_protection(struct device *dev, bool enable)
|
|
{
|
|
qm_flash_config_t qm_cfg;
|
|
|
|
qm_cfg.us_count = CONFIG_SOC_FLASH_QMSI_CLK_COUNT_US;
|
|
qm_cfg.wait_states = CONFIG_SOC_FLASH_QMSI_WAIT_STATES;
|
|
|
|
if (enable) {
|
|
qm_cfg.write_disable = QM_FLASH_WRITE_DISABLE;
|
|
} else {
|
|
qm_cfg.write_disable = QM_FLASH_WRITE_ENABLE;
|
|
}
|
|
|
|
flash_critical_region_start(dev);
|
|
qm_flash_set_config(QM_FLASH_0, &qm_cfg);
|
|
|
|
#if defined(CONFIG_SOC_QUARK_SE_C1000)
|
|
qm_flash_set_config(QM_FLASH_1, &qm_cfg);
|
|
#endif
|
|
|
|
flash_critical_region_end(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct flash_driver_api flash_qmsi_api = {
|
|
.read = flash_qmsi_read,
|
|
.write = flash_qmsi_write,
|
|
.erase = flash_qmsi_erase,
|
|
.write_protection = flash_qmsi_write_protection,
|
|
};
|
|
|
|
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
|
static void flash_qmsi_set_power_state(struct device *dev, uint32_t power_state)
|
|
{
|
|
struct soc_flash_data *ctx = dev->driver_data;
|
|
|
|
ctx->device_power_state = power_state;
|
|
}
|
|
|
|
static uint32_t flash_qmsi_get_power_state(struct device *dev)
|
|
{
|
|
struct soc_flash_data *ctx = dev->driver_data;
|
|
|
|
return ctx->device_power_state;
|
|
}
|
|
|
|
#ifdef CONFIG_SYS_POWER_DEEP_SLEEP
|
|
static int flash_qmsi_suspend_device(struct device *dev)
|
|
{
|
|
struct soc_flash_data *ctx = dev->driver_data;
|
|
qm_flash_t i;
|
|
|
|
for (i = QM_FLASH_0; i < QM_FLASH_NUM; i++) {
|
|
qm_flash_save_context(i, &ctx->saved_ctx[i]);
|
|
}
|
|
flash_qmsi_set_power_state(dev, DEVICE_PM_SUSPEND_STATE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flash_qmsi_resume_device(struct device *dev)
|
|
{
|
|
struct soc_flash_data *ctx = dev->driver_data;
|
|
qm_flash_t i;
|
|
|
|
for (i = QM_FLASH_0; i < QM_FLASH_NUM; i++) {
|
|
qm_flash_restore_context(i, &ctx->saved_ctx[i]);
|
|
}
|
|
flash_qmsi_set_power_state(dev, DEVICE_PM_ACTIVE_STATE);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int flash_qmsi_device_ctrl(struct device *dev, uint32_t ctrl_command,
|
|
void *context)
|
|
{
|
|
if (ctrl_command == DEVICE_PM_SET_POWER_STATE) {
|
|
#ifdef CONFIG_SYS_POWER_DEEP_SLEEP
|
|
if (*((uint32_t *)context) == DEVICE_PM_SUSPEND_STATE) {
|
|
return flash_qmsi_suspend_device(dev);
|
|
} else if (*((uint32_t *)context) == DEVICE_PM_ACTIVE_STATE) {
|
|
return flash_qmsi_resume_device(dev);
|
|
}
|
|
#endif
|
|
} else if (ctrl_command == DEVICE_PM_GET_POWER_STATE) {
|
|
*((uint32_t *)context) = flash_qmsi_get_power_state(dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
#define flash_qmsi_set_power_state(...)
|
|
#endif
|
|
|
|
static int quark_flash_init(struct device *dev)
|
|
{
|
|
qm_flash_config_t qm_cfg;
|
|
|
|
qm_cfg.us_count = CONFIG_SOC_FLASH_QMSI_CLK_COUNT_US;
|
|
qm_cfg.wait_states = CONFIG_SOC_FLASH_QMSI_WAIT_STATES;
|
|
qm_cfg.write_disable = QM_FLASH_WRITE_ENABLE;
|
|
|
|
qm_flash_set_config(QM_FLASH_0, &qm_cfg);
|
|
|
|
#if defined(CONFIG_SOC_QUARK_SE_C1000)
|
|
qm_flash_set_config(QM_FLASH_1, &qm_cfg);
|
|
#endif
|
|
|
|
flash_reentrancy_init(dev);
|
|
|
|
flash_qmsi_set_power_state(dev, DEVICE_PM_ACTIVE_STATE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_DEFINE(quark_flash, CONFIG_SOC_FLASH_QMSI_DEV_NAME, quark_flash_init,
|
|
flash_qmsi_device_ctrl, FLASH_CONTEXT, NULL, POST_KERNEL,
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, (void *)&flash_qmsi_api);
|