You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
9.5 KiB
377 lines
9.5 KiB
/* |
|
* 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 DEVICE_API(eeprom, 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);
|
|
|