| /* |
| * 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); |