The existing device_set_power_state() API works only in synchronous mode and this is not desirable for devices(ex: Gyro) which take longer time (few 100 mSec) to suspend/resume. To support async mode, a new callback argument is added to the API. The device drivers can asynchronously suspend/resume and call the callback function upon completion of the async request. This commit adds the missing callback parameter to all the drivers to make it compliant with the new API. Signed-off-by: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
356 lines
8.2 KiB
C
356 lines
8.2 KiB
C
/*
|
|
* Copyright (c) 2016 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <device.h>
|
|
#include <init.h>
|
|
|
|
#include <flash.h>
|
|
#include <misc/util.h>
|
|
#include "flash_priv.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
|
|
u32_t device_power_state;
|
|
qm_flash_context_t saved_ctx[QM_FLASH_NUM];
|
|
#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
|
|
#define RP_GET(dev) (&((struct soc_flash_data *)(dev->driver_data))->sem)
|
|
#else
|
|
#define RP_GET(dev) (NULL)
|
|
#endif
|
|
|
|
static inline bool is_aligned_32(u32_t data)
|
|
{
|
|
return (data & 0x3) ? false : true;
|
|
}
|
|
|
|
static qm_flash_region_t flash_region(u32_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 u32_t get_page_num(u32_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)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
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 (u32_t i = 0; i < (len >> 2); i++) {
|
|
UNALIGNED_PUT(sys_read32(addr + (i << 2)),
|
|
(u32_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;
|
|
u32_t data_word = 0U, offset = 0U, f_addr = 0U;
|
|
|
|
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 (u32_t i = 0; i < (len >> 2); i++) {
|
|
data_word = UNALIGNED_GET((u32_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) || defined(CONFIG_SOC_QUARK_SE_C1000_SS)
|
|
if (offset >= (CONFIG_SOC_FLASH_QMSI_SYS_SIZE >> 1)) {
|
|
flash = QM_FLASH_1;
|
|
offset -= CONFIG_SOC_FLASH_QMSI_SYS_SIZE >> 1;
|
|
}
|
|
#endif
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY)) {
|
|
k_sem_take(RP_GET(dev), K_FOREVER);
|
|
}
|
|
|
|
qm_flash_word_write(flash, reg, offset, data_word);
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY)) {
|
|
k_sem_give(RP_GET(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;
|
|
u32_t page = 0U;
|
|
|
|
/* 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 (u32_t i = 0; i < (size >> QM_FLASH_PAGE_SIZE_BITS); i++) {
|
|
page = get_page_num(addr) + i;
|
|
#if defined(CONFIG_SOC_QUARK_SE_C1000) || defined(CONFIG_SOC_QUARK_SE_C1000_SS)
|
|
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
|
|
if (IS_ENABLED(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY)) {
|
|
k_sem_take(RP_GET(dev), K_FOREVER);
|
|
}
|
|
|
|
qm_flash_page_erase(flash, reg, page);
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY)) {
|
|
k_sem_give(RP_GET(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;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY)) {
|
|
k_sem_take(RP_GET(dev), K_FOREVER);
|
|
}
|
|
|
|
qm_flash_set_config(QM_FLASH_0, &qm_cfg);
|
|
|
|
#if defined(CONFIG_SOC_QUARK_SE_C1000) || defined(CONFIG_SOC_QUARK_SE_C1000_SS)
|
|
qm_flash_set_config(QM_FLASH_1, &qm_cfg);
|
|
#endif
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY)) {
|
|
k_sem_give(RP_GET(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,
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
.page_layout = (flash_api_pages_layout)
|
|
flash_page_layout_not_implemented,
|
|
#endif
|
|
.write_block_size = 4,
|
|
};
|
|
|
|
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
|
static void flash_qmsi_set_power_state(struct device *dev, u32_t power_state)
|
|
{
|
|
struct soc_flash_data *ctx = dev->driver_data;
|
|
|
|
ctx->device_power_state = power_state;
|
|
}
|
|
|
|
static u32_t flash_qmsi_get_power_state(struct device *dev)
|
|
{
|
|
struct soc_flash_data *ctx = dev->driver_data;
|
|
|
|
return ctx->device_power_state;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int flash_qmsi_device_ctrl(struct device *dev, u32_t ctrl_command,
|
|
void *context, device_pm_cb cb, void *arg)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (ctrl_command == DEVICE_PM_SET_POWER_STATE) {
|
|
if (*((u32_t *)context) == DEVICE_PM_SUSPEND_STATE) {
|
|
ret = flash_qmsi_suspend_device(dev);
|
|
} else if (*((u32_t *)context) == DEVICE_PM_ACTIVE_STATE) {
|
|
ret = flash_qmsi_resume_device(dev);
|
|
}
|
|
} else if (ctrl_command == DEVICE_PM_GET_POWER_STATE) {
|
|
*((u32_t *)context) = flash_qmsi_get_power_state(dev);
|
|
}
|
|
|
|
if (cb) {
|
|
cb(dev, ret, context, arg);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#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) || defined(CONFIG_SOC_QUARK_SE_C1000_SS)
|
|
qm_flash_set_config(QM_FLASH_1, &qm_cfg);
|
|
#endif
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_FLASH_QMSI_API_REENTRANCY)) {
|
|
k_sem_init(RP_GET(dev), 1, UINT_MAX);
|
|
}
|
|
|
|
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);
|