378 lines
9.5 KiB
C
378 lines
9.5 KiB
C
/*
|
|
* Copyright (c) 2022 Microchip Technology Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT microchip_xec_eeprom
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/eeprom.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <soc.h>
|
|
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/pm/device.h>
|
|
#include <zephyr/pm/policy.h>
|
|
|
|
LOG_MODULE_REGISTER(eeprom_xec, CONFIG_EEPROM_LOG_LEVEL);
|
|
|
|
/* EEPROM Mode Register */
|
|
#define XEC_EEPROM_MODE_ACTIVATE BIT(0)
|
|
|
|
/* EEPROM Status Register */
|
|
#define XEC_EEPROM_STS_TRANSFER_COMPL BIT(0)
|
|
|
|
/* EEPROM Execute Register - Transfer size bit position */
|
|
#define XEC_EEPROM_EXC_TRANSFER_SZ_BITPOS (24)
|
|
|
|
/* EEPROM Execute Register - Commands */
|
|
#define XEC_EEPROM_EXC_CMD_READ 0x00000U
|
|
#define XEC_EEPROM_EXC_CMD_WRITE 0x10000U
|
|
#define XEC_EEPROM_EXC_CMD_READ_STS 0x20000U
|
|
#define XEC_EEPROM_EXC_CMD_WRITE_STS 0x30000U
|
|
|
|
/* EEPROM Execute Register - Address mask */
|
|
#define XEC_EEPROM_EXC_ADDR_MASK 0x7FFU
|
|
|
|
/* EEPROM Status Byte */
|
|
#define XEC_EEPROM_STS_BYTE_WIP BIT(0)
|
|
#define XEC_EEPROM_STS_BYTE_WENB BIT(1)
|
|
|
|
/* EEPROM Read/Write Transfer Size */
|
|
#define XEC_EEPROM_PAGE_SIZE 32U
|
|
#define XEC_EEPROM_TRANSFER_SIZE_READ XEC_EEPROM_PAGE_SIZE
|
|
#define XEC_EEPROM_TRANSFER_SIZE_WRITE XEC_EEPROM_PAGE_SIZE
|
|
|
|
#define XEC_EEPROM_DELAY_US 500U
|
|
#define XEC_EEPROM_DELAY_BUSY_POLL_US 50U
|
|
#define XEC_EEPROM_XFER_COMPL_RETRY_COUNT 10U
|
|
|
|
struct eeprom_xec_regs {
|
|
uint32_t mode;
|
|
uint32_t execute;
|
|
uint32_t status;
|
|
uint32_t intr_enable;
|
|
uint32_t password;
|
|
uint32_t unlock;
|
|
uint32_t lock;
|
|
uint32_t _reserved;
|
|
uint8_t buffer[XEC_EEPROM_PAGE_SIZE];
|
|
};
|
|
|
|
struct eeprom_xec_config {
|
|
struct eeprom_xec_regs * const regs;
|
|
size_t size;
|
|
const struct pinctrl_dev_config *pcfg;
|
|
};
|
|
|
|
struct eeprom_xec_data {
|
|
struct k_mutex lock_mtx;
|
|
};
|
|
|
|
static void eeprom_xec_execute_reg_set(struct eeprom_xec_regs * const regs,
|
|
uint32_t transfer_size, uint32_t command,
|
|
uint16_t eeprom_addr)
|
|
{
|
|
uint32_t temp = command + (eeprom_addr & XEC_EEPROM_EXC_ADDR_MASK);
|
|
|
|
if (transfer_size != XEC_EEPROM_PAGE_SIZE) {
|
|
temp += (transfer_size << XEC_EEPROM_EXC_TRANSFER_SZ_BITPOS);
|
|
}
|
|
regs->execute = temp;
|
|
}
|
|
|
|
static uint8_t eeprom_xec_data_buffer_read(struct eeprom_xec_regs * const regs,
|
|
uint8_t transfer_size, uint8_t *destination_ptr)
|
|
{
|
|
uint8_t count;
|
|
|
|
if (transfer_size > XEC_EEPROM_PAGE_SIZE) {
|
|
transfer_size = XEC_EEPROM_PAGE_SIZE;
|
|
}
|
|
for (count = 0; count < transfer_size; count++) {
|
|
*destination_ptr = regs->buffer[count];
|
|
destination_ptr++;
|
|
}
|
|
|
|
return transfer_size;
|
|
}
|
|
|
|
static uint8_t eeprom_xec_data_buffer_write(struct eeprom_xec_regs * const regs,
|
|
uint8_t transfer_size, uint8_t *source_ptr)
|
|
{
|
|
uint8_t count;
|
|
|
|
if (transfer_size > XEC_EEPROM_PAGE_SIZE) {
|
|
transfer_size = XEC_EEPROM_PAGE_SIZE;
|
|
}
|
|
for (count = 0; count < transfer_size; count++) {
|
|
regs->buffer[count] = *source_ptr;
|
|
source_ptr++;
|
|
}
|
|
|
|
return transfer_size;
|
|
}
|
|
|
|
static void eeprom_xec_wait_transfer_compl(struct eeprom_xec_regs * const regs)
|
|
{
|
|
uint8_t sts = 0;
|
|
uint8_t retry_count = 0;
|
|
|
|
k_sleep(K_USEC(XEC_EEPROM_DELAY_US));
|
|
|
|
do {
|
|
if (retry_count >= XEC_EEPROM_XFER_COMPL_RETRY_COUNT) {
|
|
LOG_ERR("XEC EEPROM retry count exceeded");
|
|
break;
|
|
}
|
|
k_sleep(K_USEC(XEC_EEPROM_DELAY_BUSY_POLL_US));
|
|
|
|
sts = XEC_EEPROM_STS_TRANSFER_COMPL & regs->status;
|
|
retry_count++;
|
|
|
|
} while (sts == 0);
|
|
|
|
if (sts != 0) {
|
|
/* Clear the appropriate status bits */
|
|
regs->status = XEC_EEPROM_STS_TRANSFER_COMPL;
|
|
}
|
|
}
|
|
|
|
static void eeprom_xec_wait_write_compl(struct eeprom_xec_regs * const regs)
|
|
{
|
|
uint8_t sts = 0;
|
|
uint8_t retry_count = 0;
|
|
|
|
do {
|
|
if (retry_count >= XEC_EEPROM_XFER_COMPL_RETRY_COUNT) {
|
|
LOG_ERR("XEC EEPROM retry count exceeded");
|
|
break;
|
|
}
|
|
|
|
regs->buffer[0] = 0;
|
|
|
|
/* Issue the READ_STS command */
|
|
regs->execute = XEC_EEPROM_EXC_CMD_READ_STS;
|
|
|
|
eeprom_xec_wait_transfer_compl(regs);
|
|
|
|
sts = regs->buffer[0] & (XEC_EEPROM_STS_BYTE_WIP |
|
|
XEC_EEPROM_STS_BYTE_WENB);
|
|
|
|
retry_count++;
|
|
|
|
} while (sts != 0);
|
|
}
|
|
|
|
static void eeprom_xec_data_read_32_bytes(struct eeprom_xec_regs * const regs,
|
|
uint8_t *buf, size_t len, off_t offset)
|
|
{
|
|
/* Issue the READ command to transfer buffer to EEPROM memory */
|
|
eeprom_xec_execute_reg_set(regs, len, XEC_EEPROM_EXC_CMD_READ, offset);
|
|
|
|
/* Wait until the read operation has completed */
|
|
eeprom_xec_wait_transfer_compl(regs);
|
|
|
|
/* Read the data in to the software buffer */
|
|
eeprom_xec_data_buffer_read(regs, len, buf);
|
|
}
|
|
|
|
static void eeprom_xec_data_write_32_bytes(struct eeprom_xec_regs * const regs,
|
|
uint8_t *buf, size_t len, off_t offset)
|
|
{
|
|
uint16_t sz;
|
|
uint16_t rem_bytes;
|
|
|
|
sz = offset % XEC_EEPROM_PAGE_SIZE;
|
|
|
|
/* If EEPROM Addr is not on page boundary */
|
|
if (sz != 0) {
|
|
/* Check if we are crossing page boundary */
|
|
if ((sz + len) > XEC_EEPROM_PAGE_SIZE) {
|
|
rem_bytes = (XEC_EEPROM_PAGE_SIZE - sz);
|
|
/* Update the EEPROM buffer */
|
|
eeprom_xec_data_buffer_write(regs, rem_bytes, buf);
|
|
|
|
/* Issue the WRITE command to transfer buffer to EEPROM memory */
|
|
eeprom_xec_execute_reg_set(regs, rem_bytes,
|
|
XEC_EEPROM_EXC_CMD_WRITE, offset);
|
|
|
|
eeprom_xec_wait_transfer_compl(regs);
|
|
|
|
eeprom_xec_wait_write_compl(regs);
|
|
|
|
offset += rem_bytes;
|
|
buf += rem_bytes;
|
|
len = (len - rem_bytes);
|
|
}
|
|
}
|
|
/* Update the EEPROM buffer */
|
|
eeprom_xec_data_buffer_write(regs, len, buf);
|
|
|
|
/* Issue the WRITE command to transfer buffer to EEPROM memory */
|
|
eeprom_xec_execute_reg_set(regs, len, XEC_EEPROM_EXC_CMD_WRITE, offset);
|
|
|
|
eeprom_xec_wait_transfer_compl(regs);
|
|
|
|
eeprom_xec_wait_write_compl(regs);
|
|
}
|
|
|
|
static int eeprom_xec_read(const struct device *dev, off_t offset,
|
|
void *buf,
|
|
size_t len)
|
|
{
|
|
const struct eeprom_xec_config *config = dev->config;
|
|
struct eeprom_xec_data * const data = dev->data;
|
|
struct eeprom_xec_regs * const regs = config->regs;
|
|
uint8_t *data_buf = (uint8_t *)buf;
|
|
uint32_t chunk_idx = 0;
|
|
uint32_t chunk_size = XEC_EEPROM_TRANSFER_SIZE_READ;
|
|
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if ((offset + len) > config->size) {
|
|
LOG_WRN("attempt to read past device boundary");
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_mutex_lock(&data->lock_mtx, K_FOREVER);
|
|
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
|
|
|
|
/* EEPROM HW READ */
|
|
for (chunk_idx = 0; chunk_idx < len; chunk_idx += XEC_EEPROM_TRANSFER_SIZE_READ) {
|
|
if ((len-chunk_idx) < XEC_EEPROM_TRANSFER_SIZE_READ) {
|
|
chunk_size = (len-chunk_idx);
|
|
}
|
|
eeprom_xec_data_read_32_bytes(regs, &data_buf[chunk_idx],
|
|
chunk_size, (offset+chunk_idx));
|
|
}
|
|
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
|
|
k_mutex_unlock(&data->lock_mtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int eeprom_xec_write(const struct device *dev, off_t offset,
|
|
const void *buf, size_t len)
|
|
{
|
|
const struct eeprom_xec_config *config = dev->config;
|
|
struct eeprom_xec_data * const data = dev->data;
|
|
struct eeprom_xec_regs * const regs = config->regs;
|
|
uint8_t *data_buf = (uint8_t *)buf;
|
|
uint32_t chunk_idx = 0;
|
|
uint32_t chunk_size = XEC_EEPROM_TRANSFER_SIZE_WRITE;
|
|
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if ((offset + len) > config->size) {
|
|
LOG_WRN("attempt to write past device boundary");
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_mutex_lock(&data->lock_mtx, K_FOREVER);
|
|
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
|
|
|
|
/* EEPROM HW WRITE */
|
|
for (chunk_idx = 0; chunk_idx < len; chunk_idx += XEC_EEPROM_TRANSFER_SIZE_WRITE) {
|
|
if ((len-chunk_idx) < XEC_EEPROM_TRANSFER_SIZE_WRITE) {
|
|
chunk_size = (len-chunk_idx);
|
|
}
|
|
eeprom_xec_data_write_32_bytes(regs, &data_buf[chunk_idx],
|
|
chunk_size, (offset+chunk_idx));
|
|
}
|
|
|
|
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
|
|
k_mutex_unlock(&data->lock_mtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t eeprom_xec_size(const struct device *dev)
|
|
{
|
|
const struct eeprom_xec_config *config = dev->config;
|
|
|
|
return config->size;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
static int eeprom_xec_pm_action(const struct device *dev, enum pm_device_action action)
|
|
{
|
|
const struct eeprom_xec_config *const devcfg = dev->config;
|
|
struct eeprom_xec_regs * const regs = devcfg->regs;
|
|
int ret;
|
|
|
|
switch (action) {
|
|
case PM_DEVICE_ACTION_RESUME:
|
|
ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret != 0) {
|
|
LOG_ERR("XEC EEPROM pinctrl setup failed (%d)", ret);
|
|
return ret;
|
|
}
|
|
regs->mode |= XEC_EEPROM_MODE_ACTIVATE;
|
|
break;
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
/* Disable EEPROM Controller */
|
|
regs->mode &= (~XEC_EEPROM_MODE_ACTIVATE);
|
|
ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_SLEEP);
|
|
/* pinctrl-1 does not exist. */
|
|
if (ret == -ENOENT) {
|
|
ret = 0;
|
|
}
|
|
break;
|
|
default:
|
|
ret = -ENOTSUP;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_PM_DEVICE */
|
|
|
|
static int eeprom_xec_init(const struct device *dev)
|
|
{
|
|
const struct eeprom_xec_config *config = dev->config;
|
|
struct eeprom_xec_data * const data = dev->data;
|
|
struct eeprom_xec_regs * const regs = config->regs;
|
|
|
|
k_mutex_init(&data->lock_mtx);
|
|
|
|
int ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
|
|
|
|
if (ret != 0) {
|
|
LOG_ERR("XEC EEPROM pinctrl init failed (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
regs->mode |= XEC_EEPROM_MODE_ACTIVATE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct eeprom_driver_api eeprom_xec_api = {
|
|
.read = eeprom_xec_read,
|
|
.write = eeprom_xec_write,
|
|
.size = eeprom_xec_size,
|
|
};
|
|
|
|
PINCTRL_DT_INST_DEFINE(0);
|
|
|
|
static const struct eeprom_xec_config eeprom_config = {
|
|
.regs = (struct eeprom_xec_regs * const)DT_INST_REG_ADDR(0),
|
|
.size = DT_INST_PROP(0, size),
|
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
|
|
};
|
|
|
|
static struct eeprom_xec_data eeprom_data;
|
|
|
|
PM_DEVICE_DT_INST_DEFINE(0, eeprom_xec_pm_action);
|
|
|
|
DEVICE_DT_INST_DEFINE(0, &eeprom_xec_init, PM_DEVICE_DT_INST_GET(0), &eeprom_data,
|
|
&eeprom_config, POST_KERNEL,
|
|
CONFIG_EEPROM_INIT_PRIORITY, &eeprom_xec_api);
|