zephyr/drivers/eeprom/eeprom_mchp_xec.c

383 lines
9.7 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);
#ifdef CONFIG_PM_DEVICE
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
#endif
/* 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));
}
#ifdef CONFIG_PM_DEVICE
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
#endif
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);
#ifdef CONFIG_PM_DEVICE
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
#endif
/* 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));
}
#ifdef CONFIG_PM_DEVICE
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
#endif
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);