GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)
This commit is contained in:
@@ -0,0 +1,961 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <arch_helpers.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/st/bsec.h>
|
||||
#include <drivers/st/bsec2_reg.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/spinlock.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#define BSEC_IP_VERSION_1_1 U(0x11)
|
||||
#define BSEC_IP_VERSION_2_0 U(0x20)
|
||||
#define BSEC_IP_ID_2 U(0x100032)
|
||||
|
||||
#define OTP_ACCESS_SIZE (round_up(OTP_MAX_SIZE, __WORD_BIT) / __WORD_BIT)
|
||||
|
||||
static uint32_t otp_nsec_access[OTP_ACCESS_SIZE] __unused;
|
||||
|
||||
static uint32_t bsec_power_safmem(bool power);
|
||||
|
||||
/* BSEC access protection */
|
||||
static spinlock_t bsec_spinlock;
|
||||
static uintptr_t bsec_base;
|
||||
|
||||
static void bsec_lock(void)
|
||||
{
|
||||
if (stm32mp_lock_available()) {
|
||||
spin_lock(&bsec_spinlock);
|
||||
}
|
||||
}
|
||||
|
||||
static void bsec_unlock(void)
|
||||
{
|
||||
if (stm32mp_lock_available()) {
|
||||
spin_unlock(&bsec_spinlock);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_otp_invalid_mode(void)
|
||||
{
|
||||
bool ret = ((bsec_get_status() & BSEC_MODE_INVALID) == BSEC_MODE_INVALID);
|
||||
|
||||
if (ret) {
|
||||
ERROR("OTP mode is OTP-INVALID\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(IMAGE_BL32)
|
||||
static int bsec_get_dt_node(struct dt_node_info *info)
|
||||
{
|
||||
int node;
|
||||
|
||||
node = dt_get_node(info, -1, DT_BSEC_COMPAT);
|
||||
if (node < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static void enable_non_secure_access(uint32_t otp)
|
||||
{
|
||||
otp_nsec_access[otp / __WORD_BIT] |= BIT(otp % __WORD_BIT);
|
||||
|
||||
if (bsec_shadow_register(otp) != BSEC_OK) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
static bool non_secure_can_access(uint32_t otp)
|
||||
{
|
||||
return (otp_nsec_access[otp / __WORD_BIT] &
|
||||
BIT(otp % __WORD_BIT)) != 0U;
|
||||
}
|
||||
|
||||
static void bsec_dt_otp_nsec_access(void *fdt, int bsec_node)
|
||||
{
|
||||
int bsec_subnode;
|
||||
|
||||
fdt_for_each_subnode(bsec_subnode, fdt, bsec_node) {
|
||||
const fdt32_t *cuint;
|
||||
uint32_t otp;
|
||||
uint32_t i;
|
||||
uint32_t size;
|
||||
uint32_t offset;
|
||||
uint32_t length;
|
||||
|
||||
cuint = fdt_getprop(fdt, bsec_subnode, "reg", NULL);
|
||||
if (cuint == NULL) {
|
||||
panic();
|
||||
}
|
||||
|
||||
offset = fdt32_to_cpu(*cuint);
|
||||
cuint++;
|
||||
length = fdt32_to_cpu(*cuint);
|
||||
|
||||
otp = offset / sizeof(uint32_t);
|
||||
|
||||
if (otp < STM32MP1_UPPER_OTP_START) {
|
||||
unsigned int otp_end = round_up(offset + length,
|
||||
sizeof(uint32_t)) /
|
||||
sizeof(uint32_t);
|
||||
|
||||
if (otp_end > STM32MP1_UPPER_OTP_START) {
|
||||
/*
|
||||
* OTP crosses Lower/Upper boundary, consider
|
||||
* only the upper part.
|
||||
*/
|
||||
otp = STM32MP1_UPPER_OTP_START;
|
||||
length -= (STM32MP1_UPPER_OTP_START *
|
||||
sizeof(uint32_t)) - offset;
|
||||
offset = STM32MP1_UPPER_OTP_START *
|
||||
sizeof(uint32_t);
|
||||
|
||||
WARN("OTP crosses Lower/Upper boundary\n");
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ((fdt_getprop(fdt, bsec_subnode,
|
||||
"st,non-secure-otp", NULL)) == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((offset % sizeof(uint32_t)) != 0U) ||
|
||||
((length % sizeof(uint32_t)) != 0U)) {
|
||||
ERROR("Unaligned non-secure OTP\n");
|
||||
panic();
|
||||
}
|
||||
|
||||
size = length / sizeof(uint32_t);
|
||||
|
||||
for (i = otp; i < (otp + size); i++) {
|
||||
enable_non_secure_access(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bsec_late_init(void)
|
||||
{
|
||||
void *fdt;
|
||||
int node;
|
||||
struct dt_node_info bsec_info;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
node = bsec_get_dt_node(&bsec_info);
|
||||
if (node < 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
assert(bsec_base == bsec_info.base);
|
||||
|
||||
bsec_dt_otp_nsec_access(fdt, node);
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint32_t otp_bank_offset(uint32_t otp)
|
||||
{
|
||||
assert(otp <= STM32MP1_OTP_MAX_ID);
|
||||
|
||||
return ((otp & ~BSEC_OTP_MASK) >> BSEC_OTP_BANK_SHIFT) *
|
||||
sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_check_error: check BSEC error status.
|
||||
* otp: OTP number.
|
||||
* check_disturbed: check only error (false),
|
||||
* or error and disturbed status (true).
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
static uint32_t bsec_check_error(uint32_t otp, bool check_disturbed)
|
||||
{
|
||||
uint32_t bit = BIT(otp & BSEC_OTP_MASK);
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
|
||||
if ((mmio_read_32(bsec_base + BSEC_ERROR_OFF + bank) & bit) != 0U) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
if (!check_disturbed) {
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
if ((mmio_read_32(bsec_base + BSEC_DISTURBED_OFF + bank) & bit) != 0U) {
|
||||
return BSEC_DISTURBED;
|
||||
}
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_probe: initialize BSEC driver.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_probe(void)
|
||||
{
|
||||
bsec_base = BSEC_BASE;
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
if ((((bsec_get_version() & BSEC_IPVR_MSK) != BSEC_IP_VERSION_1_1) &&
|
||||
((bsec_get_version() & BSEC_IPVR_MSK) != BSEC_IP_VERSION_2_0)) ||
|
||||
(bsec_get_id() != BSEC_IP_ID_2)) {
|
||||
panic();
|
||||
}
|
||||
|
||||
#if defined(IMAGE_BL32)
|
||||
bsec_late_init();
|
||||
#endif
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_get_base: return BSEC base address.
|
||||
*/
|
||||
uint32_t bsec_get_base(void)
|
||||
{
|
||||
return bsec_base;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_set_config: enable and configure BSEC.
|
||||
* cfg: pointer to param structure used to set register.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_set_config(struct bsec_config *cfg)
|
||||
{
|
||||
uint32_t value;
|
||||
uint32_t result;
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
value = ((((uint32_t)cfg->freq << BSEC_CONF_FRQ_SHIFT) &
|
||||
BSEC_CONF_FRQ_MASK) |
|
||||
(((uint32_t)cfg->pulse_width << BSEC_CONF_PRG_WIDTH_SHIFT) &
|
||||
BSEC_CONF_PRG_WIDTH_MASK) |
|
||||
(((uint32_t)cfg->tread << BSEC_CONF_TREAD_SHIFT) &
|
||||
BSEC_CONF_TREAD_MASK));
|
||||
|
||||
bsec_lock();
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_CONF_OFF, value);
|
||||
|
||||
bsec_unlock();
|
||||
|
||||
result = bsec_power_safmem((bool)cfg->power &
|
||||
BSEC_CONF_POWER_UP_MASK);
|
||||
if (result != BSEC_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
value = ((((uint32_t)cfg->upper_otp_lock << UPPER_OTP_LOCK_SHIFT) &
|
||||
UPPER_OTP_LOCK_MASK) |
|
||||
(((uint32_t)cfg->den_lock << DENREG_LOCK_SHIFT) &
|
||||
DENREG_LOCK_MASK) |
|
||||
(((uint32_t)cfg->prog_lock << GPLOCK_LOCK_SHIFT) &
|
||||
GPLOCK_LOCK_MASK));
|
||||
|
||||
bsec_lock();
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_LOCK_OFF, value);
|
||||
|
||||
bsec_unlock();
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_get_config: return config parameters set in BSEC registers.
|
||||
* cfg: config param return.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_get_config(struct bsec_config *cfg)
|
||||
{
|
||||
uint32_t value;
|
||||
|
||||
if (cfg == NULL) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
value = mmio_read_32(bsec_base + BSEC_OTP_CONF_OFF);
|
||||
cfg->power = (uint8_t)((value & BSEC_CONF_POWER_UP_MASK) >>
|
||||
BSEC_CONF_POWER_UP_SHIFT);
|
||||
cfg->freq = (uint8_t)((value & BSEC_CONF_FRQ_MASK) >>
|
||||
BSEC_CONF_FRQ_SHIFT);
|
||||
cfg->pulse_width = (uint8_t)((value & BSEC_CONF_PRG_WIDTH_MASK) >>
|
||||
BSEC_CONF_PRG_WIDTH_SHIFT);
|
||||
cfg->tread = (uint8_t)((value & BSEC_CONF_TREAD_MASK) >>
|
||||
BSEC_CONF_TREAD_SHIFT);
|
||||
|
||||
value = mmio_read_32(bsec_base + BSEC_OTP_LOCK_OFF);
|
||||
cfg->upper_otp_lock = (uint8_t)((value & UPPER_OTP_LOCK_MASK) >>
|
||||
UPPER_OTP_LOCK_SHIFT);
|
||||
cfg->den_lock = (uint8_t)((value & DENREG_LOCK_MASK) >>
|
||||
DENREG_LOCK_SHIFT);
|
||||
cfg->prog_lock = (uint8_t)((value & GPLOCK_LOCK_MASK) >>
|
||||
GPLOCK_LOCK_SHIFT);
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_shadow_register: copy SAFMEM OTP to BSEC data.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_shadow_register(uint32_t otp)
|
||||
{
|
||||
uint32_t result;
|
||||
bool value;
|
||||
bool power_up = false;
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
result = bsec_read_sr_lock(otp, &value);
|
||||
if (result != BSEC_OK) {
|
||||
ERROR("BSEC: %u Sticky-read bit read Error %u\n", otp, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
VERBOSE("BSEC: OTP %u is locked and will not be refreshed\n",
|
||||
otp);
|
||||
}
|
||||
|
||||
if ((bsec_get_status() & BSEC_MODE_PWR_MASK) == 0U) {
|
||||
result = bsec_power_safmem(true);
|
||||
|
||||
if (result != BSEC_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
power_up = true;
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_CTRL_OFF, otp | BSEC_READ);
|
||||
|
||||
while ((bsec_get_status() & BSEC_MODE_BUSY_MASK) != 0U) {
|
||||
;
|
||||
}
|
||||
|
||||
result = bsec_check_error(otp, true);
|
||||
|
||||
bsec_unlock();
|
||||
|
||||
if (power_up) {
|
||||
if (bsec_power_safmem(false) != BSEC_OK) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_read_otp: read an OTP data value.
|
||||
* val: read value.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_read_otp(uint32_t *val, uint32_t otp)
|
||||
{
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*val = mmio_read_32(bsec_base + BSEC_OTP_DATA_OFF +
|
||||
(otp * sizeof(uint32_t)));
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_write_otp: write value in BSEC data register.
|
||||
* val: value to write.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_write_otp(uint32_t val, uint32_t otp)
|
||||
{
|
||||
uint32_t result;
|
||||
bool value;
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
result = bsec_read_sw_lock(otp, &value);
|
||||
if (result != BSEC_OK) {
|
||||
ERROR("BSEC: %u Sticky-write bit read Error %u\n", otp, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
VERBOSE("BSEC: OTP %u is locked and write will be ignored\n",
|
||||
otp);
|
||||
}
|
||||
|
||||
/* Ensure integrity of each register access sequence */
|
||||
bsec_lock();
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_DATA_OFF +
|
||||
(otp * sizeof(uint32_t)), val);
|
||||
|
||||
bsec_unlock();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_program_otp: program a bit in SAFMEM after the prog.
|
||||
* The OTP data is not refreshed.
|
||||
* val: value to program.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_program_otp(uint32_t val, uint32_t otp)
|
||||
{
|
||||
uint32_t result;
|
||||
bool power_up = false;
|
||||
bool sp_lock;
|
||||
bool perm_lock;
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
result = bsec_read_sp_lock(otp, &sp_lock);
|
||||
if (result != BSEC_OK) {
|
||||
ERROR("BSEC: %u Sticky-prog bit read Error %u\n", otp, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = bsec_read_permanent_lock(otp, &perm_lock);
|
||||
if (result != BSEC_OK) {
|
||||
ERROR("BSEC: %u permanent bit read Error %u\n", otp, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (sp_lock || perm_lock) {
|
||||
WARN("BSEC: OTP locked, prog will be ignored\n");
|
||||
return BSEC_PROG_FAIL;
|
||||
}
|
||||
|
||||
if ((mmio_read_32(bsec_base + BSEC_OTP_LOCK_OFF) &
|
||||
BIT(BSEC_LOCK_PROGRAM)) != 0U) {
|
||||
WARN("BSEC: GPLOCK activated, prog will be ignored\n");
|
||||
}
|
||||
|
||||
if ((bsec_get_status() & BSEC_MODE_PWR_MASK) == 0U) {
|
||||
result = bsec_power_safmem(true);
|
||||
|
||||
if (result != BSEC_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
power_up = true;
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_WRDATA_OFF, val);
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_CTRL_OFF, otp | BSEC_WRITE);
|
||||
|
||||
while ((bsec_get_status() & BSEC_MODE_BUSY_MASK) != 0U) {
|
||||
;
|
||||
}
|
||||
|
||||
if ((bsec_get_status() & BSEC_MODE_PROGFAIL_MASK) != 0U) {
|
||||
result = BSEC_PROG_FAIL;
|
||||
} else {
|
||||
result = bsec_check_error(otp, true);
|
||||
}
|
||||
|
||||
bsec_unlock();
|
||||
|
||||
if (power_up) {
|
||||
if (bsec_power_safmem(false) != BSEC_OK) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_permanent_lock_otp: permanent lock of OTP in SAFMEM.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_permanent_lock_otp(uint32_t otp)
|
||||
{
|
||||
uint32_t result;
|
||||
bool power_up = false;
|
||||
uint32_t data;
|
||||
uint32_t addr;
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if ((bsec_get_status() & BSEC_MODE_PWR_MASK) == 0U) {
|
||||
result = bsec_power_safmem(true);
|
||||
|
||||
if (result != BSEC_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
power_up = true;
|
||||
}
|
||||
|
||||
if (otp < STM32MP1_UPPER_OTP_START) {
|
||||
addr = otp >> ADDR_LOWER_OTP_PERLOCK_SHIFT;
|
||||
data = DATA_LOWER_OTP_PERLOCK_BIT <<
|
||||
((otp & DATA_LOWER_OTP_PERLOCK_MASK) << 1U);
|
||||
} else {
|
||||
addr = (otp >> ADDR_UPPER_OTP_PERLOCK_SHIFT) + 2U;
|
||||
data = DATA_UPPER_OTP_PERLOCK_BIT <<
|
||||
(otp & DATA_UPPER_OTP_PERLOCK_MASK);
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_WRDATA_OFF, data);
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_CTRL_OFF,
|
||||
addr | BSEC_WRITE | BSEC_LOCK);
|
||||
|
||||
while ((bsec_get_status() & BSEC_MODE_BUSY_MASK) != 0U) {
|
||||
;
|
||||
}
|
||||
|
||||
if ((bsec_get_status() & BSEC_MODE_PROGFAIL_MASK) != 0U) {
|
||||
result = BSEC_PROG_FAIL;
|
||||
} else {
|
||||
result = bsec_check_error(otp, false);
|
||||
}
|
||||
|
||||
bsec_unlock();
|
||||
|
||||
if (power_up) {
|
||||
if (bsec_power_safmem(false) != BSEC_OK) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_write_debug_conf: write value in debug feature.
|
||||
* to enable/disable debug service.
|
||||
* val: value to write.
|
||||
* return value: none.
|
||||
*/
|
||||
void bsec_write_debug_conf(uint32_t val)
|
||||
{
|
||||
if (is_otp_invalid_mode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
mmio_write_32(bsec_base + BSEC_DEN_OFF, val & BSEC_DEN_ALL_MSK);
|
||||
bsec_unlock();
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_read_debug_conf: return debug configuration register value.
|
||||
*/
|
||||
uint32_t bsec_read_debug_conf(void)
|
||||
{
|
||||
return mmio_read_32(bsec_base + BSEC_DEN_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_write_scratch: write value in scratch register.
|
||||
* val: value to write.
|
||||
* return value: none.
|
||||
*/
|
||||
void bsec_write_scratch(uint32_t val)
|
||||
{
|
||||
#if defined(IMAGE_BL32)
|
||||
if (is_otp_invalid_mode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
mmio_write_32(bsec_base + BSEC_SCRATCH_OFF, val);
|
||||
bsec_unlock();
|
||||
#else
|
||||
mmio_write_32(BSEC_BASE + BSEC_SCRATCH_OFF, val);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_read_scratch: return scratch register value.
|
||||
*/
|
||||
uint32_t bsec_read_scratch(void)
|
||||
{
|
||||
return mmio_read_32(bsec_base + BSEC_SCRATCH_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_get_status: return status register value.
|
||||
*/
|
||||
uint32_t bsec_get_status(void)
|
||||
{
|
||||
return mmio_read_32(bsec_base + BSEC_OTP_STATUS_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_get_hw_conf: return hardware configuration register value.
|
||||
*/
|
||||
uint32_t bsec_get_hw_conf(void)
|
||||
{
|
||||
return mmio_read_32(bsec_base + BSEC_IPHW_CFG_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_get_version: return BSEC version register value.
|
||||
*/
|
||||
uint32_t bsec_get_version(void)
|
||||
{
|
||||
return mmio_read_32(bsec_base + BSEC_IPVR_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_get_id: return BSEC ID register value.
|
||||
*/
|
||||
uint32_t bsec_get_id(void)
|
||||
{
|
||||
return mmio_read_32(bsec_base + BSEC_IP_ID_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_get_magic_id: return BSEC magic number register value.
|
||||
*/
|
||||
uint32_t bsec_get_magic_id(void)
|
||||
{
|
||||
return mmio_read_32(bsec_base + BSEC_IP_MAGIC_ID_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_set_sr_lock: set shadow-read lock.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_set_sr_lock(uint32_t otp)
|
||||
{
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
uint32_t otp_mask = BIT(otp & BSEC_OTP_MASK);
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
mmio_write_32(bsec_base + BSEC_SRLOCK_OFF + bank, otp_mask);
|
||||
bsec_unlock();
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_read_sr_lock: read shadow-read lock.
|
||||
* otp: OTP number.
|
||||
* value: read value (true or false).
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_read_sr_lock(uint32_t otp, bool *value)
|
||||
{
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
uint32_t otp_mask = BIT(otp & BSEC_OTP_MASK);
|
||||
uint32_t bank_value;
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
bank_value = mmio_read_32(bsec_base + BSEC_SRLOCK_OFF + bank);
|
||||
|
||||
*value = ((bank_value & otp_mask) != 0U);
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_set_sw_lock: set shadow-write lock.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_set_sw_lock(uint32_t otp)
|
||||
{
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
uint32_t otp_mask = BIT(otp & BSEC_OTP_MASK);
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
mmio_write_32(bsec_base + BSEC_SWLOCK_OFF + bank, otp_mask);
|
||||
bsec_unlock();
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_read_sw_lock: read shadow-write lock.
|
||||
* otp: OTP number.
|
||||
* value: read value (true or false).
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_read_sw_lock(uint32_t otp, bool *value)
|
||||
{
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
uint32_t otp_mask = BIT(otp & BSEC_OTP_MASK);
|
||||
uint32_t bank_value;
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
bank_value = mmio_read_32(bsec_base + BSEC_SWLOCK_OFF + bank);
|
||||
|
||||
*value = ((bank_value & otp_mask) != 0U);
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_set_sp_lock: set shadow-program lock.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_set_sp_lock(uint32_t otp)
|
||||
{
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
uint32_t otp_mask = BIT(otp & BSEC_OTP_MASK);
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
bsec_lock();
|
||||
mmio_write_32(bsec_base + BSEC_SPLOCK_OFF + bank, otp_mask);
|
||||
bsec_unlock();
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_read_sp_lock: read shadow-program lock.
|
||||
* otp: OTP number.
|
||||
* value: read value (true or false).
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_read_sp_lock(uint32_t otp, bool *value)
|
||||
{
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
uint32_t otp_mask = BIT(otp & BSEC_OTP_MASK);
|
||||
uint32_t bank_value;
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
bank_value = mmio_read_32(bsec_base + BSEC_SPLOCK_OFF + bank);
|
||||
|
||||
*value = ((bank_value & otp_mask) != 0U);
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_read_permanent_lock: Read permanent lock status.
|
||||
* otp: OTP number.
|
||||
* value: read value (true or false).
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_read_permanent_lock(uint32_t otp, bool *value)
|
||||
{
|
||||
uint32_t bank = otp_bank_offset(otp);
|
||||
uint32_t otp_mask = BIT(otp & BSEC_OTP_MASK);
|
||||
uint32_t bank_value;
|
||||
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
bank_value = mmio_read_32(bsec_base + BSEC_WRLOCK_OFF + bank);
|
||||
|
||||
*value = ((bank_value & otp_mask) != 0U);
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_otp_lock: Lock Upper OTP or Global Programming or Debug Enable.
|
||||
* service: Service to lock, see header file.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_otp_lock(uint32_t service)
|
||||
{
|
||||
uintptr_t reg = bsec_base + BSEC_OTP_LOCK_OFF;
|
||||
|
||||
if (is_otp_invalid_mode()) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
|
||||
switch (service) {
|
||||
case BSEC_LOCK_UPPER_OTP:
|
||||
mmio_write_32(reg, BIT(BSEC_LOCK_UPPER_OTP));
|
||||
break;
|
||||
case BSEC_LOCK_DEBUG:
|
||||
mmio_write_32(reg, BIT(BSEC_LOCK_DEBUG));
|
||||
break;
|
||||
case BSEC_LOCK_PROGRAM:
|
||||
mmio_write_32(reg, BIT(BSEC_LOCK_PROGRAM));
|
||||
break;
|
||||
default:
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_power_safmem: Activate or deactivate SAFMEM power.
|
||||
* power: true to power up, false to power down.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
static uint32_t bsec_power_safmem(bool power)
|
||||
{
|
||||
uint32_t register_val;
|
||||
uint32_t timeout = BSEC_TIMEOUT_VALUE;
|
||||
|
||||
bsec_lock();
|
||||
|
||||
register_val = mmio_read_32(bsec_base + BSEC_OTP_CONF_OFF);
|
||||
|
||||
if (power) {
|
||||
register_val |= BSEC_CONF_POWER_UP_MASK;
|
||||
} else {
|
||||
register_val &= ~BSEC_CONF_POWER_UP_MASK;
|
||||
}
|
||||
|
||||
mmio_write_32(bsec_base + BSEC_OTP_CONF_OFF, register_val);
|
||||
|
||||
if (power) {
|
||||
while (((bsec_get_status() & BSEC_MODE_PWR_MASK) == 0U) &&
|
||||
(timeout != 0U)) {
|
||||
timeout--;
|
||||
}
|
||||
} else {
|
||||
while (((bsec_get_status() & BSEC_MODE_PWR_MASK) != 0U) &&
|
||||
(timeout != 0U)) {
|
||||
timeout--;
|
||||
}
|
||||
}
|
||||
|
||||
bsec_unlock();
|
||||
|
||||
if (timeout == 0U) {
|
||||
return BSEC_TIMEOUT;
|
||||
}
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_shadow_read_otp: Load OTP from SAFMEM and provide its value.
|
||||
* otp_value: read value.
|
||||
* word: OTP number.
|
||||
* return value: BSEC_OK if no error.
|
||||
*/
|
||||
uint32_t bsec_shadow_read_otp(uint32_t *otp_value, uint32_t word)
|
||||
{
|
||||
uint32_t result;
|
||||
|
||||
result = bsec_shadow_register(word);
|
||||
if (result != BSEC_OK) {
|
||||
ERROR("BSEC: %u Shadowing Error %u\n", word, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = bsec_read_otp(otp_value, word);
|
||||
if (result != BSEC_OK) {
|
||||
ERROR("BSEC: %u Read Error %u\n", word, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* bsec_check_nsec_access_rights: check non-secure access rights to target OTP.
|
||||
* otp: OTP number.
|
||||
* return value: BSEC_OK if authorized access.
|
||||
*/
|
||||
uint32_t bsec_check_nsec_access_rights(uint32_t otp)
|
||||
{
|
||||
#if defined(IMAGE_BL32)
|
||||
if (otp > STM32MP1_OTP_MAX_ID) {
|
||||
return BSEC_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (otp >= STM32MP1_UPPER_OTP_START) {
|
||||
if (!non_secure_can_access(otp)) {
|
||||
return BSEC_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return BSEC_OK;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright (C) 2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef CLK_STM32_CORE_H
|
||||
#define CLK_STM32_CORE_H
|
||||
|
||||
struct mux_cfg {
|
||||
uint16_t offset;
|
||||
uint8_t shift;
|
||||
uint8_t width;
|
||||
uint8_t bitrdy;
|
||||
};
|
||||
|
||||
struct gate_cfg {
|
||||
uint16_t offset;
|
||||
uint8_t bit_idx;
|
||||
uint8_t set_clr;
|
||||
};
|
||||
|
||||
struct clk_div_table {
|
||||
unsigned int val;
|
||||
unsigned int div;
|
||||
};
|
||||
|
||||
struct div_cfg {
|
||||
uint16_t offset;
|
||||
uint8_t shift;
|
||||
uint8_t width;
|
||||
uint8_t flags;
|
||||
uint8_t bitrdy;
|
||||
const struct clk_div_table *table;
|
||||
};
|
||||
|
||||
struct parent_cfg {
|
||||
uint8_t num_parents;
|
||||
const uint16_t *id_parents;
|
||||
struct mux_cfg *mux;
|
||||
};
|
||||
|
||||
struct stm32_clk_priv;
|
||||
|
||||
struct stm32_clk_ops {
|
||||
unsigned long (*recalc_rate)(struct stm32_clk_priv *priv, int id, unsigned long rate);
|
||||
int (*get_parent)(struct stm32_clk_priv *priv, int id);
|
||||
int (*set_rate)(struct stm32_clk_priv *priv, int id, unsigned long rate,
|
||||
unsigned long prate);
|
||||
int (*enable)(struct stm32_clk_priv *priv, int id);
|
||||
void (*disable)(struct stm32_clk_priv *priv, int id);
|
||||
bool (*is_enabled)(struct stm32_clk_priv *priv, int id);
|
||||
void (*init)(struct stm32_clk_priv *priv, int id);
|
||||
};
|
||||
|
||||
struct clk_stm32 {
|
||||
uint16_t binding;
|
||||
uint16_t parent;
|
||||
uint8_t flags;
|
||||
void *clock_cfg;
|
||||
const struct stm32_clk_ops *ops;
|
||||
};
|
||||
|
||||
struct stm32_clk_priv {
|
||||
uintptr_t base;
|
||||
const uint32_t num;
|
||||
const struct clk_stm32 *clks;
|
||||
const struct parent_cfg *parents;
|
||||
const uint32_t nb_parents;
|
||||
const struct gate_cfg *gates;
|
||||
const uint32_t nb_gates;
|
||||
const struct div_cfg *div;
|
||||
const uint32_t nb_div;
|
||||
struct clk_oscillator_data *osci_data;
|
||||
const uint32_t nb_osci_data;
|
||||
uint32_t *gate_refcounts;
|
||||
void *pdata;
|
||||
};
|
||||
|
||||
struct stm32_clk_bypass {
|
||||
uint16_t offset;
|
||||
uint8_t bit_byp;
|
||||
uint8_t bit_digbyp;
|
||||
};
|
||||
|
||||
struct stm32_clk_css {
|
||||
uint16_t offset;
|
||||
uint8_t bit_css;
|
||||
};
|
||||
|
||||
struct stm32_clk_drive {
|
||||
uint16_t offset;
|
||||
uint8_t drv_shift;
|
||||
uint8_t drv_width;
|
||||
uint8_t drv_default;
|
||||
};
|
||||
|
||||
struct clk_oscillator_data {
|
||||
const char *name;
|
||||
uint16_t id_clk;
|
||||
unsigned long frequency;
|
||||
uint16_t gate_id;
|
||||
uint16_t gate_rdy_id;
|
||||
struct stm32_clk_bypass *bypass;
|
||||
struct stm32_clk_css *css;
|
||||
struct stm32_clk_drive *drive;
|
||||
};
|
||||
|
||||
struct clk_fixed_rate {
|
||||
const char *name;
|
||||
unsigned long fixed_rate;
|
||||
};
|
||||
|
||||
struct clk_gate_cfg {
|
||||
uint32_t offset;
|
||||
uint8_t bit_idx;
|
||||
};
|
||||
|
||||
/* CLOCK FLAGS */
|
||||
#define CLK_IS_CRITICAL BIT(0)
|
||||
#define CLK_IGNORE_UNUSED BIT(1)
|
||||
#define CLK_SET_RATE_PARENT BIT(2)
|
||||
|
||||
#define CLK_DIVIDER_ONE_BASED BIT(0)
|
||||
#define CLK_DIVIDER_POWER_OF_TWO BIT(1)
|
||||
#define CLK_DIVIDER_ALLOW_ZERO BIT(2)
|
||||
#define CLK_DIVIDER_HIWORD_MASK BIT(3)
|
||||
#define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
|
||||
#define CLK_DIVIDER_READ_ONLY BIT(5)
|
||||
#define CLK_DIVIDER_MAX_AT_ZERO BIT(6)
|
||||
#define CLK_DIVIDER_BIG_ENDIAN BIT(7)
|
||||
|
||||
#define MUX_MAX_PARENTS U(0x8000)
|
||||
#define MUX_PARENT_MASK GENMASK(14, 0)
|
||||
#define MUX_FLAG U(0x8000)
|
||||
#define MUX(mux) ((mux) | MUX_FLAG)
|
||||
|
||||
#define NO_GATE 0
|
||||
#define _NO_ID UINT16_MAX
|
||||
#define CLK_IS_ROOT UINT16_MAX
|
||||
#define MUX_NO_BIT_RDY UINT8_MAX
|
||||
#define DIV_NO_BIT_RDY UINT8_MAX
|
||||
|
||||
#define MASK_WIDTH_SHIFT(_width, _shift) \
|
||||
GENMASK(((_width) + (_shift) - 1U), (_shift))
|
||||
|
||||
int clk_stm32_init(struct stm32_clk_priv *priv, uintptr_t base);
|
||||
void clk_stm32_enable_critical_clocks(void);
|
||||
|
||||
struct stm32_clk_priv *clk_stm32_get_priv(void);
|
||||
|
||||
int clk_get_index(struct stm32_clk_priv *priv, unsigned long binding_id);
|
||||
const struct clk_stm32 *_clk_get(struct stm32_clk_priv *priv, int id);
|
||||
|
||||
void clk_oscillator_set_bypass(struct stm32_clk_priv *priv, int id, bool digbyp, bool bypass);
|
||||
void clk_oscillator_set_drive(struct stm32_clk_priv *priv, int id, uint8_t lsedrv);
|
||||
void clk_oscillator_set_css(struct stm32_clk_priv *priv, int id, bool css);
|
||||
|
||||
int _clk_stm32_gate_wait_ready(struct stm32_clk_priv *priv, uint16_t gate_id, bool ready_on);
|
||||
|
||||
int clk_oscillator_wait_ready(struct stm32_clk_priv *priv, int id, bool ready_on);
|
||||
int clk_oscillator_wait_ready_on(struct stm32_clk_priv *priv, int id);
|
||||
int clk_oscillator_wait_ready_off(struct stm32_clk_priv *priv, int id);
|
||||
|
||||
int clk_stm32_get_counter(unsigned long binding_id);
|
||||
|
||||
void _clk_stm32_gate_disable(struct stm32_clk_priv *priv, uint16_t gate_id);
|
||||
int _clk_stm32_gate_enable(struct stm32_clk_priv *priv, uint16_t gate_id);
|
||||
|
||||
int _clk_stm32_set_parent(struct stm32_clk_priv *priv, int id, int src_id);
|
||||
int _clk_stm32_set_parent_by_index(struct stm32_clk_priv *priv, int clk, int sel);
|
||||
|
||||
int _clk_stm32_get_parent(struct stm32_clk_priv *priv, int id);
|
||||
int _clk_stm32_get_parent_by_index(struct stm32_clk_priv *priv, int clk_id, int idx);
|
||||
int _clk_stm32_get_parent_index(struct stm32_clk_priv *priv, int clk_id);
|
||||
|
||||
unsigned long _clk_stm32_get_rate(struct stm32_clk_priv *priv, int id);
|
||||
unsigned long _clk_stm32_get_parent_rate(struct stm32_clk_priv *priv, int id);
|
||||
|
||||
bool _stm32_clk_is_flags(struct stm32_clk_priv *priv, int id, uint8_t flag);
|
||||
|
||||
int _clk_stm32_enable(struct stm32_clk_priv *priv, int id);
|
||||
void _clk_stm32_disable(struct stm32_clk_priv *priv, int id);
|
||||
|
||||
int clk_stm32_enable_call_ops(struct stm32_clk_priv *priv, uint16_t id);
|
||||
void clk_stm32_disable_call_ops(struct stm32_clk_priv *priv, uint16_t id);
|
||||
|
||||
bool _clk_stm32_is_enabled(struct stm32_clk_priv *priv, int id);
|
||||
|
||||
int _clk_stm32_divider_set_rate(struct stm32_clk_priv *priv, int div_id,
|
||||
unsigned long rate, unsigned long parent_rate);
|
||||
|
||||
int clk_stm32_divider_set_rate(struct stm32_clk_priv *priv, int id, unsigned long rate,
|
||||
unsigned long prate);
|
||||
|
||||
unsigned long _clk_stm32_divider_recalc(struct stm32_clk_priv *priv,
|
||||
int div_id,
|
||||
unsigned long prate);
|
||||
|
||||
unsigned long clk_stm32_divider_recalc(struct stm32_clk_priv *priv, int idx,
|
||||
unsigned long prate);
|
||||
|
||||
int clk_stm32_gate_enable(struct stm32_clk_priv *priv, int idx);
|
||||
void clk_stm32_gate_disable(struct stm32_clk_priv *priv, int idx);
|
||||
|
||||
bool _clk_stm32_gate_is_enabled(struct stm32_clk_priv *priv, int gate_id);
|
||||
bool clk_stm32_gate_is_enabled(struct stm32_clk_priv *priv, int idx);
|
||||
|
||||
uint32_t clk_stm32_div_get_value(struct stm32_clk_priv *priv, int div_id);
|
||||
int clk_stm32_set_div(struct stm32_clk_priv *priv, uint32_t div_id, uint32_t value);
|
||||
int clk_mux_set_parent(struct stm32_clk_priv *priv, uint16_t pid, uint8_t sel);
|
||||
int clk_mux_get_parent(struct stm32_clk_priv *priv, uint32_t mux_id);
|
||||
|
||||
int stm32_clk_parse_fdt_by_name(void *fdt, int node, const char *name, uint32_t *tab, uint32_t *nb);
|
||||
|
||||
#ifdef CFG_STM32_CLK_DEBUG
|
||||
void clk_stm32_display_clock_info(void);
|
||||
#endif
|
||||
|
||||
struct clk_stm32_div_cfg {
|
||||
int id;
|
||||
};
|
||||
|
||||
#define STM32_DIV(idx, _binding, _parent, _flags, _div_id) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_binding),\
|
||||
.parent = (_parent),\
|
||||
.flags = (_flags),\
|
||||
.clock_cfg = &(struct clk_stm32_div_cfg){\
|
||||
.id = (_div_id),\
|
||||
},\
|
||||
.ops = &clk_stm32_divider_ops,\
|
||||
}
|
||||
|
||||
struct clk_stm32_gate_cfg {
|
||||
int id;
|
||||
};
|
||||
|
||||
#define STM32_GATE(idx, _binding, _parent, _flags, _gate_id) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_binding),\
|
||||
.parent = (_parent),\
|
||||
.flags = (_flags),\
|
||||
.clock_cfg = &(struct clk_stm32_gate_cfg){\
|
||||
.id = (_gate_id),\
|
||||
},\
|
||||
.ops = &clk_stm32_gate_ops,\
|
||||
}
|
||||
|
||||
struct fixed_factor_cfg {
|
||||
unsigned int mult;
|
||||
unsigned int div;
|
||||
};
|
||||
|
||||
unsigned long fixed_factor_recalc_rate(struct stm32_clk_priv *priv,
|
||||
int _idx, unsigned long prate);
|
||||
|
||||
#define FIXED_FACTOR(idx, _idx, _parent, _mult, _div) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_idx),\
|
||||
.parent = (_parent),\
|
||||
.clock_cfg = &(struct fixed_factor_cfg){\
|
||||
.mult = (_mult),\
|
||||
.div = (_div),\
|
||||
},\
|
||||
.ops = &clk_fixed_factor_ops,\
|
||||
}
|
||||
|
||||
#define GATE(idx, _binding, _parent, _flags, _offset, _bit_idx) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_binding),\
|
||||
.parent = (_parent),\
|
||||
.flags = (_flags),\
|
||||
.clock_cfg = &(struct clk_gate_cfg){\
|
||||
.offset = (_offset),\
|
||||
.bit_idx = (_bit_idx),\
|
||||
},\
|
||||
.ops = &clk_gate_ops,\
|
||||
}
|
||||
|
||||
#define STM32_MUX(idx, _binding, _mux_id, _flags) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_binding),\
|
||||
.parent = (MUX(_mux_id)),\
|
||||
.flags = (_flags),\
|
||||
.clock_cfg = NULL,\
|
||||
.ops = (&clk_mux_ops),\
|
||||
}
|
||||
|
||||
struct clk_timer_cfg {
|
||||
uint32_t apbdiv;
|
||||
uint32_t timpre;
|
||||
};
|
||||
|
||||
#define CK_TIMER(idx, _idx, _parent, _flags, _apbdiv, _timpre) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_idx),\
|
||||
.parent = (_parent),\
|
||||
.flags = (CLK_SET_RATE_PARENT | (_flags)),\
|
||||
.clock_cfg = &(struct clk_timer_cfg){\
|
||||
.apbdiv = (_apbdiv),\
|
||||
.timpre = (_timpre),\
|
||||
},\
|
||||
.ops = &clk_timer_ops,\
|
||||
}
|
||||
|
||||
struct clk_stm32_fixed_rate_cfg {
|
||||
unsigned long rate;
|
||||
};
|
||||
|
||||
#define CLK_FIXED_RATE(idx, _binding, _rate) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_binding),\
|
||||
.parent = (CLK_IS_ROOT),\
|
||||
.clock_cfg = &(struct clk_stm32_fixed_rate_cfg){\
|
||||
.rate = (_rate),\
|
||||
},\
|
||||
.ops = &clk_stm32_fixed_rate_ops,\
|
||||
}
|
||||
|
||||
#define BYPASS(_offset, _bit_byp, _bit_digbyp) &(struct stm32_clk_bypass){\
|
||||
.offset = (_offset),\
|
||||
.bit_byp = (_bit_byp),\
|
||||
.bit_digbyp = (_bit_digbyp),\
|
||||
}
|
||||
|
||||
#define CSS(_offset, _bit_css) &(struct stm32_clk_css){\
|
||||
.offset = (_offset),\
|
||||
.bit_css = (_bit_css),\
|
||||
}
|
||||
|
||||
#define DRIVE(_offset, _shift, _width, _default) &(struct stm32_clk_drive){\
|
||||
.offset = (_offset),\
|
||||
.drv_shift = (_shift),\
|
||||
.drv_width = (_width),\
|
||||
.drv_default = (_default),\
|
||||
}
|
||||
|
||||
#define OSCILLATOR(idx_osc, _id, _name, _gate_id, _gate_rdy_id, _bypass, _css, _drive) \
|
||||
[(idx_osc)] = (struct clk_oscillator_data){\
|
||||
.name = (_name),\
|
||||
.id_clk = (_id),\
|
||||
.gate_id = (_gate_id),\
|
||||
.gate_rdy_id = (_gate_rdy_id),\
|
||||
.bypass = (_bypass),\
|
||||
.css = (_css),\
|
||||
.drive = (_drive),\
|
||||
}
|
||||
|
||||
struct clk_oscillator_data *clk_oscillator_get_data(struct stm32_clk_priv *priv, int id);
|
||||
|
||||
void clk_stm32_osc_init(struct stm32_clk_priv *priv, int id);
|
||||
bool clk_stm32_osc_gate_is_enabled(struct stm32_clk_priv *priv, int id);
|
||||
int clk_stm32_osc_gate_enable(struct stm32_clk_priv *priv, int id);
|
||||
void clk_stm32_osc_gate_disable(struct stm32_clk_priv *priv, int id);
|
||||
|
||||
struct stm32_osc_cfg {
|
||||
int osc_id;
|
||||
};
|
||||
|
||||
#define CLK_OSC(idx, _idx, _parent, _osc_id) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_idx),\
|
||||
.parent = (_parent),\
|
||||
.flags = CLK_IS_CRITICAL,\
|
||||
.clock_cfg = &(struct stm32_osc_cfg){\
|
||||
.osc_id = (_osc_id),\
|
||||
},\
|
||||
.ops = &clk_stm32_osc_ops,\
|
||||
}
|
||||
|
||||
#define CLK_OSC_FIXED(idx, _idx, _parent, _osc_id) \
|
||||
[(idx)] = (struct clk_stm32){ \
|
||||
.binding = (_idx),\
|
||||
.parent = (_parent),\
|
||||
.flags = CLK_IS_CRITICAL,\
|
||||
.clock_cfg = &(struct stm32_osc_cfg){\
|
||||
.osc_id = (_osc_id),\
|
||||
},\
|
||||
.ops = &clk_stm32_osc_nogate_ops,\
|
||||
}
|
||||
|
||||
extern const struct stm32_clk_ops clk_mux_ops;
|
||||
extern const struct stm32_clk_ops clk_stm32_divider_ops;
|
||||
extern const struct stm32_clk_ops clk_stm32_gate_ops;
|
||||
extern const struct stm32_clk_ops clk_fixed_factor_ops;
|
||||
extern const struct stm32_clk_ops clk_gate_ops;
|
||||
extern const struct stm32_clk_ops clk_timer_ops;
|
||||
extern const struct stm32_clk_ops clk_stm32_fixed_rate_ops;
|
||||
extern const struct stm32_clk_ops clk_stm32_osc_ops;
|
||||
extern const struct stm32_clk_ops clk_stm32_osc_nogate_ops;
|
||||
|
||||
#endif /* CLK_STM32_CORE_H */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <arch_helpers.h>
|
||||
#include <common/fdt_wrappers.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/generic_delay_timer.h>
|
||||
#include <drivers/st/stm32_gpio.h>
|
||||
#include <drivers/st/stm32mp_clkfunc.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
/*
|
||||
* Get the frequency of an oscillator from its name in device tree.
|
||||
* @param name: oscillator name
|
||||
* @param freq: stores the frequency of the oscillator
|
||||
* @return: 0 on success, and a negative FDT/ERRNO error code on failure.
|
||||
*/
|
||||
int fdt_osc_read_freq(const char *name, uint32_t *freq)
|
||||
{
|
||||
int node, subnode;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
node = fdt_path_offset(fdt, "/clocks");
|
||||
if (node < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
fdt_for_each_subnode(subnode, fdt, node) {
|
||||
const char *cchar;
|
||||
int ret;
|
||||
|
||||
cchar = fdt_get_name(fdt, subnode, &ret);
|
||||
if (cchar == NULL) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((strncmp(cchar, name, (size_t)ret) == 0) &&
|
||||
(fdt_get_status(subnode) != DT_DISABLED)) {
|
||||
const fdt32_t *cuint;
|
||||
|
||||
cuint = fdt_getprop(fdt, subnode, "clock-frequency",
|
||||
&ret);
|
||||
if (cuint == NULL) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
*freq = fdt32_to_cpu(*cuint);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Oscillator not found, freq=0 */
|
||||
*freq = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the presence of an oscillator property from its id.
|
||||
* @param node_label: clock node name
|
||||
* @param prop_name: property name
|
||||
* @return: true/false regarding search result.
|
||||
*/
|
||||
bool fdt_clk_read_bool(const char *node_label, const char *prop_name)
|
||||
{
|
||||
int node, subnode;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node = fdt_path_offset(fdt, "/clocks");
|
||||
if (node < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fdt_for_each_subnode(subnode, fdt, node) {
|
||||
const char *cchar;
|
||||
int ret;
|
||||
|
||||
cchar = fdt_get_name(fdt, subnode, &ret);
|
||||
if (cchar == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncmp(cchar, node_label, (size_t)ret) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fdt_getprop(fdt, subnode, prop_name, NULL) != NULL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the value of a oscillator property from its name.
|
||||
* @param node_label: oscillator name
|
||||
* @param prop_name: property name
|
||||
* @param dflt_value: default value
|
||||
* @return oscillator value on success, default value if property not found.
|
||||
*/
|
||||
uint32_t fdt_clk_read_uint32_default(const char *node_label,
|
||||
const char *prop_name, uint32_t dflt_value)
|
||||
{
|
||||
int node, subnode;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return dflt_value;
|
||||
}
|
||||
|
||||
node = fdt_path_offset(fdt, "/clocks");
|
||||
if (node < 0) {
|
||||
return dflt_value;
|
||||
}
|
||||
|
||||
fdt_for_each_subnode(subnode, fdt, node) {
|
||||
const char *cchar;
|
||||
int ret;
|
||||
|
||||
cchar = fdt_get_name(fdt, subnode, &ret);
|
||||
if (cchar == NULL) {
|
||||
return dflt_value;
|
||||
}
|
||||
|
||||
if (strncmp(cchar, node_label, (size_t)ret) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return fdt_read_uint32_default(fdt, subnode, prop_name,
|
||||
dflt_value);
|
||||
}
|
||||
|
||||
return dflt_value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the RCC node offset from the device tree
|
||||
* @param fdt: Device tree reference
|
||||
* @return: Node offset or a negative value on error
|
||||
*/
|
||||
static int fdt_get_rcc_node(void *fdt)
|
||||
{
|
||||
static int node;
|
||||
|
||||
if (node <= 0) {
|
||||
node = fdt_node_offset_by_compatible(fdt, -1, DT_RCC_CLK_COMPAT);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a series of parameters in rcc-clk section in device tree
|
||||
* @param prop_name: Name of the RCC property to be read
|
||||
* @param array: the array to store the property parameters
|
||||
* @param count: number of parameters to be read
|
||||
* @return: 0 on succes or a negative value on error
|
||||
*/
|
||||
int fdt_rcc_read_uint32_array(const char *prop_name, uint32_t count,
|
||||
uint32_t *array)
|
||||
{
|
||||
int node;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
node = fdt_get_rcc_node(fdt);
|
||||
if (node < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
return fdt_read_uint32_array(fdt, node, prop_name, count, array);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the subnode offset in rcc-clk section from its name in device tree
|
||||
* @param name: name of the RCC property
|
||||
* @return: offset on success, and a negative FDT/ERRNO error code on failure.
|
||||
*/
|
||||
int fdt_rcc_subnode_offset(const char *name)
|
||||
{
|
||||
int node, subnode;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
node = fdt_get_rcc_node(fdt);
|
||||
if (node < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
subnode = fdt_subnode_offset(fdt, node, name);
|
||||
if (subnode <= 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
return subnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the pointer to a rcc-clk property from its name.
|
||||
* @param name: name of the RCC property
|
||||
* @param lenp: stores the length of the property.
|
||||
* @return: pointer to the property on success, and NULL value on failure.
|
||||
*/
|
||||
const fdt32_t *fdt_rcc_read_prop(const char *prop_name, int *lenp)
|
||||
{
|
||||
const fdt32_t *cuint;
|
||||
int node, len;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node = fdt_get_rcc_node(fdt);
|
||||
if (node < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, node, prop_name, &len);
|
||||
if (cuint == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*lenp = len;
|
||||
return cuint;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the secure state for rcc node in device tree.
|
||||
* @return: true if rcc is configured for secure world access, false if not.
|
||||
*/
|
||||
bool fdt_get_rcc_secure_state(void)
|
||||
{
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fdt_node_offset_by_compatible(fdt, -1, DT_RCC_SEC_CLK_COMPAT) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the clock ID of the given node in device tree.
|
||||
* @param node: node offset
|
||||
* @return: Clock ID on success, and a negative FDT/ERRNO error code on failure.
|
||||
*/
|
||||
int fdt_get_clock_id(int node)
|
||||
{
|
||||
const fdt32_t *cuint;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "clocks", NULL);
|
||||
if (cuint == NULL) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
cuint++;
|
||||
return (int)fdt32_to_cpu(*cuint);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the frequency of the specified UART instance.
|
||||
* @param instance: UART interface registers base address.
|
||||
* @return: clock frequency on success, 0 value on failure.
|
||||
*/
|
||||
unsigned long fdt_get_uart_clock_freq(uintptr_t instance)
|
||||
{
|
||||
void *fdt;
|
||||
int node;
|
||||
int clk_id;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
/* Check for UART nodes */
|
||||
node = dt_match_instance_by_compatible(DT_UART_COMPAT, instance);
|
||||
if (node < 0) {
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
clk_id = fdt_get_clock_id(node);
|
||||
if (clk_id < 0) {
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
return clk_get_rate((unsigned long)clk_id);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function configures and restores the STGEN counter depending on the
|
||||
* connected clock.
|
||||
******************************************************************************/
|
||||
void stm32mp_stgen_config(unsigned long rate)
|
||||
{
|
||||
uint32_t cntfid0;
|
||||
unsigned long long counter;
|
||||
|
||||
cntfid0 = mmio_read_32(STGEN_BASE + CNTFID_OFF);
|
||||
|
||||
if (cntfid0 == rate) {
|
||||
return;
|
||||
}
|
||||
|
||||
mmio_clrbits_32(STGEN_BASE + CNTCR_OFF, CNTCR_EN);
|
||||
counter = stm32mp_stgen_get_counter() * rate / cntfid0;
|
||||
|
||||
mmio_write_32(STGEN_BASE + CNTCVL_OFF, (uint32_t)counter);
|
||||
mmio_write_32(STGEN_BASE + CNTCVU_OFF, (uint32_t)(counter >> 32));
|
||||
mmio_write_32(STGEN_BASE + CNTFID_OFF, rate);
|
||||
mmio_setbits_32(STGEN_BASE + CNTCR_OFF, CNTCR_EN);
|
||||
|
||||
write_cntfrq_el0(rate);
|
||||
|
||||
/* Need to update timer with new frequency */
|
||||
generic_delay_timer_init();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function returns the STGEN counter value.
|
||||
******************************************************************************/
|
||||
unsigned long long stm32mp_stgen_get_counter(void)
|
||||
{
|
||||
return (((unsigned long long)mmio_read_32(STGEN_BASE + CNTCVU_OFF) << 32) |
|
||||
mmio_read_32(STGEN_BASE + CNTCVL_OFF));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function restores the STGEN counter value.
|
||||
* It takes a first input value as a counter backup value to be restored and a
|
||||
* offset in ms to be added.
|
||||
******************************************************************************/
|
||||
void stm32mp_stgen_restore_counter(unsigned long long value,
|
||||
unsigned long long offset_in_ms)
|
||||
{
|
||||
unsigned long long cnt;
|
||||
|
||||
cnt = value + ((offset_in_ms *
|
||||
mmio_read_32(STGEN_BASE + CNTFID_OFF)) / 1000U);
|
||||
|
||||
mmio_clrbits_32(STGEN_BASE + CNTCR_OFF, CNTCR_EN);
|
||||
mmio_write_32(STGEN_BASE + CNTCVL_OFF, (uint32_t)cnt);
|
||||
mmio_write_32(STGEN_BASE + CNTCVU_OFF, (uint32_t)(cnt >> 32));
|
||||
mmio_setbits_32(STGEN_BASE + CNTCR_OFF, CNTCR_EN);
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <arch_helpers.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32_hash.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils.h>
|
||||
#include <libfdt.h>
|
||||
#include <plat/common/platform.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#if STM32_HASH_VER == 2
|
||||
#define DT_HASH_COMPAT "st,stm32f756-hash"
|
||||
#endif
|
||||
#if STM32_HASH_VER == 4
|
||||
#define DT_HASH_COMPAT "st,stm32mp13-hash"
|
||||
#endif
|
||||
|
||||
#define HASH_CR 0x00U
|
||||
#define HASH_DIN 0x04U
|
||||
#define HASH_STR 0x08U
|
||||
#define HASH_SR 0x24U
|
||||
#define HASH_HREG(x) (0x310U + ((x) * 0x04U))
|
||||
|
||||
/* Control Register */
|
||||
#define HASH_CR_INIT BIT(2)
|
||||
#define HASH_CR_DATATYPE_SHIFT U(4)
|
||||
#if STM32_HASH_VER == 2
|
||||
#define HASH_CR_ALGO_SHA1 0x0U
|
||||
#define HASH_CR_ALGO_MD5 BIT(7)
|
||||
#define HASH_CR_ALGO_SHA224 BIT(18)
|
||||
#define HASH_CR_ALGO_SHA256 (BIT(18) | BIT(7))
|
||||
#endif
|
||||
#if STM32_HASH_VER == 4
|
||||
#define HASH_CR_ALGO_SHIFT U(17)
|
||||
#define HASH_CR_ALGO_SHA1 (0x0U << HASH_CR_ALGO_SHIFT)
|
||||
#define HASH_CR_ALGO_SHA224 (0x2U << HASH_CR_ALGO_SHIFT)
|
||||
#define HASH_CR_ALGO_SHA256 (0x3U << HASH_CR_ALGO_SHIFT)
|
||||
#define HASH_CR_ALGO_SHA384 (0xCU << HASH_CR_ALGO_SHIFT)
|
||||
#define HASH_CR_ALGO_SHA512_224 (0xDU << HASH_CR_ALGO_SHIFT)
|
||||
#define HASH_CR_ALGO_SHA512_256 (0xEU << HASH_CR_ALGO_SHIFT)
|
||||
#define HASH_CR_ALGO_SHA512 (0xFU << HASH_CR_ALGO_SHIFT)
|
||||
#endif
|
||||
|
||||
/* Status Flags */
|
||||
#define HASH_SR_DCIS BIT(1)
|
||||
#define HASH_SR_BUSY BIT(3)
|
||||
|
||||
/* STR Register */
|
||||
#define HASH_STR_NBLW_MASK GENMASK(4, 0)
|
||||
#define HASH_STR_DCAL BIT(8)
|
||||
|
||||
#define MD5_DIGEST_SIZE 16U
|
||||
#define SHA1_DIGEST_SIZE 20U
|
||||
#define SHA224_DIGEST_SIZE 28U
|
||||
#define SHA256_DIGEST_SIZE 32U
|
||||
#define SHA384_DIGEST_SIZE 48U
|
||||
#define SHA512_224_DIGEST_SIZE 28U
|
||||
#define SHA512_256_DIGEST_SIZE 32U
|
||||
#define SHA512_DIGEST_SIZE 64U
|
||||
|
||||
#define RESET_TIMEOUT_US_1MS 1000U
|
||||
#define HASH_TIMEOUT_US 10000U
|
||||
|
||||
enum stm32_hash_data_format {
|
||||
HASH_DATA_32_BITS,
|
||||
HASH_DATA_16_BITS,
|
||||
HASH_DATA_8_BITS,
|
||||
HASH_DATA_1_BIT
|
||||
};
|
||||
|
||||
struct stm32_hash_instance {
|
||||
uintptr_t base;
|
||||
unsigned int clock;
|
||||
size_t digest_size;
|
||||
};
|
||||
|
||||
struct stm32_hash_remain {
|
||||
uint32_t buffer;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
/* Expect a single HASH peripheral */
|
||||
static struct stm32_hash_instance stm32_hash;
|
||||
static struct stm32_hash_remain stm32_remain;
|
||||
|
||||
static uintptr_t hash_base(void)
|
||||
{
|
||||
return stm32_hash.base;
|
||||
}
|
||||
|
||||
static int hash_wait_busy(void)
|
||||
{
|
||||
uint64_t timeout = timeout_init_us(HASH_TIMEOUT_US);
|
||||
|
||||
while ((mmio_read_32(hash_base() + HASH_SR) & HASH_SR_BUSY) != 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ERROR("%s: busy timeout\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hash_wait_computation(void)
|
||||
{
|
||||
uint64_t timeout = timeout_init_us(HASH_TIMEOUT_US);
|
||||
|
||||
while ((mmio_read_32(hash_base() + HASH_SR) & HASH_SR_DCIS) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ERROR("%s: busy timeout\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hash_write_data(uint32_t data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hash_wait_busy();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
mmio_write_32(hash_base() + HASH_DIN, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hash_hw_init(enum stm32_hash_algo_mode mode)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
reg = HASH_CR_INIT | (HASH_DATA_8_BITS << HASH_CR_DATATYPE_SHIFT);
|
||||
|
||||
switch (mode) {
|
||||
#if STM32_HASH_VER == 2
|
||||
case HASH_MD5SUM:
|
||||
reg |= HASH_CR_ALGO_MD5;
|
||||
stm32_hash.digest_size = MD5_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
case HASH_SHA1:
|
||||
reg |= HASH_CR_ALGO_SHA1;
|
||||
stm32_hash.digest_size = SHA1_DIGEST_SIZE;
|
||||
break;
|
||||
case HASH_SHA224:
|
||||
reg |= HASH_CR_ALGO_SHA224;
|
||||
stm32_hash.digest_size = SHA224_DIGEST_SIZE;
|
||||
break;
|
||||
#if STM32_HASH_VER == 4
|
||||
case HASH_SHA384:
|
||||
reg |= HASH_CR_ALGO_SHA384;
|
||||
stm32_hash.digest_size = SHA384_DIGEST_SIZE;
|
||||
break;
|
||||
case HASH_SHA512:
|
||||
reg |= HASH_CR_ALGO_SHA512;
|
||||
stm32_hash.digest_size = SHA512_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
/* Default selected algo is SHA256 */
|
||||
case HASH_SHA256:
|
||||
default:
|
||||
reg |= HASH_CR_ALGO_SHA256;
|
||||
stm32_hash.digest_size = SHA256_DIGEST_SIZE;
|
||||
break;
|
||||
}
|
||||
|
||||
mmio_write_32(hash_base() + HASH_CR, reg);
|
||||
}
|
||||
|
||||
static int hash_get_digest(uint8_t *digest)
|
||||
{
|
||||
int ret;
|
||||
uint32_t i;
|
||||
uint32_t dsg;
|
||||
|
||||
ret = hash_wait_computation();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0U; i < (stm32_hash.digest_size / sizeof(uint32_t)); i++) {
|
||||
dsg = __builtin_bswap32(mmio_read_32(hash_base() +
|
||||
HASH_HREG(i)));
|
||||
memcpy(digest + (i * sizeof(uint32_t)), &dsg, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean hardware context as HASH could be used later
|
||||
* by non-secure software
|
||||
*/
|
||||
hash_hw_init(HASH_SHA256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stm32_hash_update(const uint8_t *buffer, size_t length)
|
||||
{
|
||||
size_t remain_length = length;
|
||||
int ret = 0;
|
||||
|
||||
if ((length == 0U) || (buffer == NULL)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
clk_enable(stm32_hash.clock);
|
||||
|
||||
if (stm32_remain.length != 0U) {
|
||||
uint32_t copysize;
|
||||
|
||||
copysize = MIN((sizeof(uint32_t) - stm32_remain.length),
|
||||
length);
|
||||
memcpy(((uint8_t *)&stm32_remain.buffer) + stm32_remain.length,
|
||||
buffer, copysize);
|
||||
remain_length -= copysize;
|
||||
buffer += copysize;
|
||||
if (stm32_remain.length == sizeof(uint32_t)) {
|
||||
ret = hash_write_data(stm32_remain.buffer);
|
||||
if (ret != 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
zeromem(&stm32_remain, sizeof(stm32_remain));
|
||||
}
|
||||
}
|
||||
|
||||
while (remain_length / sizeof(uint32_t) != 0U) {
|
||||
uint32_t tmp_buf;
|
||||
|
||||
memcpy(&tmp_buf, buffer, sizeof(uint32_t));
|
||||
ret = hash_write_data(tmp_buf);
|
||||
if (ret != 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
buffer += sizeof(uint32_t);
|
||||
remain_length -= sizeof(uint32_t);
|
||||
}
|
||||
|
||||
if (remain_length != 0U) {
|
||||
assert(stm32_remain.length == 0U);
|
||||
|
||||
memcpy((uint8_t *)&stm32_remain.buffer, buffer, remain_length);
|
||||
stm32_remain.length = remain_length;
|
||||
}
|
||||
|
||||
exit:
|
||||
clk_disable(stm32_hash.clock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int stm32_hash_final(uint8_t *digest)
|
||||
{
|
||||
int ret;
|
||||
|
||||
clk_enable(stm32_hash.clock);
|
||||
|
||||
if (stm32_remain.length != 0U) {
|
||||
ret = hash_write_data(stm32_remain.buffer);
|
||||
if (ret != 0) {
|
||||
clk_disable(stm32_hash.clock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mmio_clrsetbits_32(hash_base() + HASH_STR, HASH_STR_NBLW_MASK,
|
||||
8U * stm32_remain.length);
|
||||
zeromem(&stm32_remain, sizeof(stm32_remain));
|
||||
} else {
|
||||
mmio_clrbits_32(hash_base() + HASH_STR, HASH_STR_NBLW_MASK);
|
||||
}
|
||||
|
||||
mmio_setbits_32(hash_base() + HASH_STR, HASH_STR_DCAL);
|
||||
|
||||
ret = hash_get_digest(digest);
|
||||
|
||||
clk_disable(stm32_hash.clock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int stm32_hash_final_update(const uint8_t *buffer, uint32_t length,
|
||||
uint8_t *digest)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = stm32_hash_update(buffer, length);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return stm32_hash_final(digest);
|
||||
}
|
||||
|
||||
void stm32_hash_init(enum stm32_hash_algo_mode mode)
|
||||
{
|
||||
clk_enable(stm32_hash.clock);
|
||||
|
||||
hash_hw_init(mode);
|
||||
|
||||
clk_disable(stm32_hash.clock);
|
||||
|
||||
zeromem(&stm32_remain, sizeof(stm32_remain));
|
||||
}
|
||||
|
||||
int stm32_hash_register(void)
|
||||
{
|
||||
struct dt_node_info hash_info;
|
||||
int node;
|
||||
|
||||
for (node = dt_get_node(&hash_info, -1, DT_HASH_COMPAT);
|
||||
node != -FDT_ERR_NOTFOUND;
|
||||
node = dt_get_node(&hash_info, node, DT_HASH_COMPAT)) {
|
||||
if (hash_info.status != DT_DISABLED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (node == -FDT_ERR_NOTFOUND) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (hash_info.clock < 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
stm32_hash.base = hash_info.base;
|
||||
stm32_hash.clock = hash_info.clock;
|
||||
|
||||
clk_enable(stm32_hash.clock);
|
||||
|
||||
if (hash_info.reset >= 0) {
|
||||
uint32_t id = (uint32_t)hash_info.reset;
|
||||
|
||||
if (stm32mp_reset_assert(id, RESET_TIMEOUT_US_1MS) != 0) {
|
||||
panic();
|
||||
}
|
||||
udelay(20);
|
||||
if (stm32mp_reset_deassert(id, RESET_TIMEOUT_US_1MS) != 0) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
clk_disable(stm32_hash.clock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
/*
|
||||
* Copyright (c) 2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32_pka.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils.h>
|
||||
#include <libfdt.h>
|
||||
#include <plat/common/platform.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
/*
|
||||
* For our comprehension in this file
|
||||
* _len are in BITs
|
||||
* _size are in BYTEs
|
||||
* _nbw are in number of PKA_word (PKA_word = u64)
|
||||
*/
|
||||
|
||||
#define UINT8_LEN 8U
|
||||
#define UINT64_LEN (UINT8_LEN * sizeof(uint64_t))
|
||||
#define WORD_SIZE (sizeof(uint64_t))
|
||||
#define OP_NBW_FROM_LEN(len) (DIV_ROUND_UP_2EVAL((len), UINT64_LEN) + 1)
|
||||
#define OP_NBW_FROM_SIZE(s) OP_NBW_FROM_LEN((s) * UINT8_LEN)
|
||||
#define OP_SIZE_FROM_SIZE(s) (OP_NBW_FROM_SIZE(s) * WORD_SIZE)
|
||||
|
||||
#define DT_PKA_COMPAT "st,stm32-pka64"
|
||||
|
||||
#define MAX_ECC_SIZE_LEN 640U
|
||||
#define MAX_EO_NBW OP_NBW_FROM_LEN(MAX_ECC_SIZE_LEN)
|
||||
|
||||
/* PKA registers */
|
||||
/* PKA control register */
|
||||
#define _PKA_CR 0x0U
|
||||
/* PKA status register */
|
||||
#define _PKA_SR 0x4U
|
||||
/* PKA clear flag register */
|
||||
#define _PKA_CLRFR 0x8U
|
||||
/* PKA version register */
|
||||
#define _PKA_VERR 0x1FF4U
|
||||
/* PKA identification register */
|
||||
#define _PKA_IPIDR 0x1FF8U
|
||||
|
||||
/* PKA control register fields */
|
||||
#define _PKA_CR_MODE_MASK GENMASK(13, 8)
|
||||
#define _PKA_CR_MODE_SHIFT 8U
|
||||
#define _PKA_CR_MODE_ADD 0x9U
|
||||
#define _PKA_CR_MODE_ECDSA_VERIF 0x26U
|
||||
#define _PKA_CR_START BIT(1)
|
||||
#define _PKA_CR_EN BIT(0)
|
||||
|
||||
/* PKA status register fields */
|
||||
#define _PKA_SR_BUSY BIT(16)
|
||||
#define _PKA_SR_LMF BIT(1)
|
||||
#define _PKA_SR_INITOK BIT(0)
|
||||
|
||||
/* PKA it flag fields (used in CR, SR and CLRFR) */
|
||||
#define _PKA_IT_MASK (GENMASK(21, 19) | BIT(17))
|
||||
#define _PKA_IT_SHIFT 17U
|
||||
#define _PKA_IT_OPERR BIT(21)
|
||||
#define _PKA_IT_ADDRERR BIT(20)
|
||||
#define _PKA_IT_RAMERR BIT(19)
|
||||
#define _PKA_IT_PROCEND BIT(17)
|
||||
|
||||
/* PKA version register fields */
|
||||
#define _PKA_VERR_MAJREV_MASK GENMASK(7, 4)
|
||||
#define _PKA_VERR_MAJREV_SHIFT 4U
|
||||
#define _PKA_VERR_MINREV_MASK GENMASK(3, 0)
|
||||
#define _PKA_VERR_MINREV_SHIFT 0U
|
||||
|
||||
/* RAM magic offset */
|
||||
#define _PKA_RAM_START 0x400U
|
||||
#define _PKA_RAM_SIZE 5336U
|
||||
|
||||
/* ECDSA verification */
|
||||
#define _PKA_RAM_N_LEN 0x408U /* 64 */
|
||||
#define _PKA_RAM_P_LEN 0x4C8U /* 64 */
|
||||
#define _PKA_RAM_A_SIGN 0x468U /* 64 */
|
||||
#define _PKA_RAM_A 0x470U /* EOS */
|
||||
#define _PKA_RAM_P 0x4D0U /* EOS */
|
||||
#define _PKA_RAM_XG 0x678U /* EOS */
|
||||
#define _PKA_RAM_YG 0x6D0U /* EOS */
|
||||
#define _PKA_RAM_XQ 0x12F8U /* EOS */
|
||||
#define _PKA_RAM_YQ 0x1350U /* EOS */
|
||||
#define _PKA_RAM_SIGN_R 0x10E0U /* EOS */
|
||||
#define _PKA_RAM_SIGN_S 0xC68U /* EOS */
|
||||
#define _PKA_RAM_HASH_Z 0x13A8U /* EOS */
|
||||
#define _PKA_RAM_PRIME_N 0x1088U /* EOS */
|
||||
#define _PKA_RAM_ECDSA_VERIFY 0x5D0U /* 64 */
|
||||
#define _PKA_RAM_ECDSA_VERIFY_VALID 0xD60DULL
|
||||
#define _PKA_RAM_ECDSA_VERIFY_INVALID 0xA3B7ULL
|
||||
|
||||
#define PKA_TIMEOUT_US 1000000U
|
||||
#define TIMEOUT_US_1MS 1000U
|
||||
#define PKA_RESET_DELAY 20U
|
||||
|
||||
struct curve_parameters {
|
||||
uint32_t a_sign; /* 0 positive, 1 negative */
|
||||
uint8_t *a; /* Curve coefficient |a| */
|
||||
size_t a_size;
|
||||
uint8_t *p; /* Curve modulus value */
|
||||
uint32_t p_len;
|
||||
uint8_t *xg; /* Curve base point G coordinate x */
|
||||
size_t xg_size;
|
||||
uint8_t *yg; /* Curve base point G coordinate y */
|
||||
size_t yg_size;
|
||||
uint8_t *n; /* Curve prime order n */
|
||||
uint32_t n_len;
|
||||
};
|
||||
|
||||
static const struct curve_parameters curve_def[] = {
|
||||
#if PKA_USE_NIST_P256
|
||||
[PKA_NIST_P256] = {
|
||||
.p_len = 256U,
|
||||
.n_len = 256U,
|
||||
.p = (uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||
.n = (uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
|
||||
0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51},
|
||||
.a_sign = 1U,
|
||||
.a = (uint8_t[]){0x03},
|
||||
.a_size = 1U,
|
||||
.xg = (uint8_t[]){0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47,
|
||||
0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2,
|
||||
0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0,
|
||||
0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96},
|
||||
.xg_size = 32U,
|
||||
.yg = (uint8_t[]){0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B,
|
||||
0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16,
|
||||
0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE,
|
||||
0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5},
|
||||
.yg_size = 32U,
|
||||
},
|
||||
#endif
|
||||
#if PKA_USE_BRAINPOOL_P256R1
|
||||
[PKA_BRAINPOOL_P256R1] = {
|
||||
.p_len = 256,
|
||||
.n_len = 256,
|
||||
.p = (uint8_t[]){0xA9, 0xFB, 0x57, 0xDB, 0xA1, 0xEE, 0xA9, 0xBC,
|
||||
0x3E, 0x66, 0x0A, 0x90, 0x9D, 0x83, 0x8D, 0x72,
|
||||
0x6E, 0x3B, 0xF6, 0x23, 0xD5, 0x26, 0x20, 0x28,
|
||||
0x20, 0x13, 0x48, 0x1D, 0x1F, 0x6E, 0x53, 0x77},
|
||||
.n = (uint8_t[]){0xA9, 0xFB, 0x57, 0xDB, 0xA1, 0xEE, 0xA9, 0xBC,
|
||||
0x3E, 0x66, 0x0A, 0x90, 0x9D, 0x83, 0x8D, 0x71,
|
||||
0x8C, 0x39, 0x7A, 0xA3, 0xB5, 0x61, 0xA6, 0xF7,
|
||||
0x90, 0x1E, 0x0E, 0x82, 0x97, 0x48, 0x56, 0xA7},
|
||||
.a = (uint8_t[]){0x7D, 0x5A, 0x09, 0x75, 0xFC, 0x2C, 0x30, 0x57,
|
||||
0xEE, 0xF6, 0x75, 0x30, 0x41, 0x7A, 0xFF, 0xE7,
|
||||
0xFB, 0x80, 0x55, 0xC1, 0x26, 0xDC, 0x5C, 0x6C,
|
||||
0xE9, 0x4A, 0x4B, 0x44, 0xF3, 0x30, 0xB5, 0xD9},
|
||||
.a_size = 32U,
|
||||
.xg = (uint8_t[]){0x8B, 0xD2, 0xAE, 0xB9, 0xCB, 0x7E, 0x57, 0xCB,
|
||||
0x2C, 0x4B, 0x48, 0x2F, 0xFC, 0x81, 0xB7, 0xAF,
|
||||
0xB9, 0xDE, 0x27, 0xE1, 0xE3, 0xBD, 0x23, 0xC2,
|
||||
0x3A, 0x44, 0x53, 0xBD, 0x9A, 0xCE, 0x32, 0x62},
|
||||
.xg_size = 32U,
|
||||
.yg = (uint8_t[]){0x54, 0x7E, 0xF8, 0x35, 0xC3, 0xDA, 0xC4, 0xFD,
|
||||
0x97, 0xF8, 0x46, 0x1A, 0x14, 0x61, 0x1D, 0xC9,
|
||||
0xC2, 0x77, 0x45, 0x13, 0x2D, 0xED, 0x8E, 0x54,
|
||||
0x5C, 0x1D, 0x54, 0xC7, 0x2F, 0x04, 0x69, 0x97},
|
||||
.yg_size = 32U,
|
||||
},
|
||||
#endif
|
||||
#if PKA_USE_BRAINPOOL_P256T1
|
||||
[PKA_BRAINPOOL_P256T1] = {
|
||||
.p_len = 256,
|
||||
.n_len = 256,
|
||||
.p = (uint8_t[]){0xA9, 0xFB, 0x57, 0xDB, 0xA1, 0xEE, 0xA9, 0xBC,
|
||||
0x3E, 0x66, 0x0A, 0x90, 0x9D, 0x83, 0x8D, 0x72,
|
||||
0x6E, 0x3B, 0xF6, 0x23, 0xD5, 0x26, 0x20, 0x28,
|
||||
0x20, 0x13, 0x48, 0x1D, 0x1F, 0x6E, 0x53, 0x77},
|
||||
.n = (uint8_t[]){0xA9, 0xFB, 0x57, 0xDB, 0xA1, 0xEE, 0xA9, 0xBC,
|
||||
0x3E, 0x66, 0x0A, 0x90, 0x9D, 0x83, 0x8D, 0x71,
|
||||
0x8C, 0x39, 0x7A, 0xA3, 0xB5, 0x61, 0xA6, 0xF7,
|
||||
0x90, 0x1E, 0x0E, 0x82, 0x97, 0x48, 0x56, 0xA7},
|
||||
.a = (uint8_t[]){0xA9, 0xFB, 0x57, 0xDB, 0xA1, 0xEE, 0xA9, 0xBC,
|
||||
0x3E, 0x66, 0x0A, 0x90, 0x9D, 0x83, 0x8D, 0x72,
|
||||
0x6E, 0x3B, 0xF6, 0x23, 0xD5, 0x26, 0x20, 0x28,
|
||||
0x20, 0x13, 0x48, 0x1D, 0x1F, 0x6E, 0x53, 0x74},
|
||||
.a_size = 32U,
|
||||
.xg = (uint8_t[]){0xA3, 0xE8, 0xEB, 0x3C, 0xC1, 0xCF, 0xE7, 0xB7,
|
||||
0x73, 0x22, 0x13, 0xB2, 0x3A, 0x65, 0x61, 0x49,
|
||||
0xAF, 0xA1, 0x42, 0xC4, 0x7A, 0xAF, 0xBC, 0x2B,
|
||||
0x79, 0xA1, 0x91, 0x56, 0x2E, 0x13, 0x05, 0xF4},
|
||||
.xg_size = 32U,
|
||||
.yg = (uint8_t[]){0x2D, 0x99, 0x6C, 0x82, 0x34, 0x39, 0xC5, 0x6D,
|
||||
0x7F, 0x7B, 0x22, 0xE1, 0x46, 0x44, 0x41, 0x7E,
|
||||
0x69, 0xBC, 0xB6, 0xDE, 0x39, 0xD0, 0x27, 0x00,
|
||||
0x1D, 0xAB, 0xE8, 0xF3, 0x5B, 0x25, 0xC9, 0xBE},
|
||||
.yg_size = 32U,
|
||||
},
|
||||
#endif
|
||||
#if PKA_USE_NIST_P521
|
||||
[PKA_NIST_P521] = {
|
||||
.p_len = 521,
|
||||
.n_len = 521,
|
||||
.p = (uint8_t[]){ 0x01, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
.n = (uint8_t[]){ 0x01, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa,
|
||||
0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f, 0x96, 0x6b,
|
||||
0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09, 0xa5, 0xd0,
|
||||
0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, 0x47, 0xae,
|
||||
0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, 0x64, 0x09},
|
||||
.a_sign = 1,
|
||||
.a = (uint8_t[]){0x03},
|
||||
.a_size = 1U,
|
||||
.xg = (uint8_t[]){ 0xc6,
|
||||
0x85, 0x8e, 0x06, 0xb7, 0x04, 0x04, 0xe9, 0xcd,
|
||||
0x9e, 0x3e, 0xcb, 0x66, 0x23, 0x95, 0xb4, 0x42,
|
||||
0x9c, 0x64, 0x81, 0x39, 0x05, 0x3f, 0xb5, 0x21,
|
||||
0xf8, 0x28, 0xaf, 0x60, 0x6b, 0x4d, 0x3d, 0xba,
|
||||
0xa1, 0x4b, 0x5e, 0x77, 0xef, 0xe7, 0x59, 0x28,
|
||||
0xfe, 0x1d, 0xc1, 0x27, 0xa2, 0xff, 0xa8, 0xde,
|
||||
0x33, 0x48, 0xb3, 0xc1, 0x85, 0x6a, 0x42, 0x9b,
|
||||
0xf9, 0x7e, 0x7e, 0x31, 0xc2, 0xe5, 0xbd, 0x66},
|
||||
.xg_size = 65U,
|
||||
.yg = (uint8_t[]){ 0x01, 0x18,
|
||||
0x39, 0x29, 0x6a, 0x78, 0x9a, 0x3b, 0xc0, 0x04,
|
||||
0x5c, 0x8a, 0x5f, 0xb4, 0x2c, 0x7d, 0x1b, 0xd9,
|
||||
0x98, 0xf5, 0x44, 0x49, 0x57, 0x9b, 0x44, 0x68,
|
||||
0x17, 0xaf, 0xbd, 0x17, 0x27, 0x3e, 0x66, 0x2c,
|
||||
0x97, 0xee, 0x72, 0x99, 0x5e, 0xf4, 0x26, 0x40,
|
||||
0xc5, 0x50, 0xb9, 0x01, 0x3f, 0xad, 0x07, 0x61,
|
||||
0x35, 0x3c, 0x70, 0x86, 0xa2, 0x72, 0xc2, 0x40,
|
||||
0x88, 0xbe, 0x94, 0x76, 0x9f, 0xd1, 0x66, 0x50},
|
||||
.yg_size = 66U,
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct stm32_pka_platdata pka_pdata;
|
||||
|
||||
#pragma weak stm32_pka_get_platdata
|
||||
|
||||
int stm32_pka_get_platdata(struct stm32_pka_platdata *pdata)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int stm32_pka_parse_fdt(void)
|
||||
{
|
||||
int node;
|
||||
struct dt_node_info info;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
node = dt_get_node(&info, -1, DT_PKA_COMPAT);
|
||||
if (node < 0) {
|
||||
ERROR("No PKA entry in DT\n");
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if (info.status == DT_DISABLED) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if ((info.base == 0) || (info.clock < 0) || (info.reset < 0)) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
pka_pdata.base = (uintptr_t)info.base;
|
||||
pka_pdata.clock_id = (unsigned long)info.clock;
|
||||
pka_pdata.reset_id = (unsigned int)info.reset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pka_wait_bit(uintptr_t base, uint32_t bit)
|
||||
{
|
||||
uint64_t timeout = timeout_init_us(PKA_TIMEOUT_US);
|
||||
|
||||
while ((mmio_read_32(base + _PKA_SR) & bit) != bit) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
WARN("timeout waiting %x\n", bit);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void pka_disable(uintptr_t base)
|
||||
{
|
||||
mmio_clrbits_32(base + _PKA_CR, _PKA_CR_EN);
|
||||
}
|
||||
|
||||
static int pka_enable(uintptr_t base, uint32_t mode)
|
||||
{
|
||||
/* Set mode and disable interrupts */
|
||||
mmio_clrsetbits_32(base + _PKA_CR, _PKA_IT_MASK | _PKA_CR_MODE_MASK,
|
||||
_PKA_CR_MODE_MASK & (mode << _PKA_CR_MODE_SHIFT));
|
||||
|
||||
mmio_setbits_32(base + _PKA_CR, _PKA_CR_EN);
|
||||
|
||||
return pka_wait_bit(base, _PKA_SR_INITOK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Data are already loaded in PKA internal RAM
|
||||
* MODE is set
|
||||
* We start process, and wait for its end.
|
||||
*/
|
||||
static int stm32_pka_process(uintptr_t base)
|
||||
{
|
||||
mmio_setbits_32(base + _PKA_CR, _PKA_CR_START);
|
||||
|
||||
return pka_wait_bit(base, _PKA_IT_PROCEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write ECC operand to PKA RAM.
|
||||
* @note PKA expect to write u64 word, each u64 are: the least significant bit is
|
||||
* bit 0; the most significant bit is bit 63.
|
||||
* We write eo_nbw (ECC operand Size) u64, value that depends of the chosen
|
||||
* prime modulus length in bits.
|
||||
* First less signicant u64 is written to low address
|
||||
* Most significant u64 to higher address.
|
||||
* And at last address we write a u64(0x0)
|
||||
* @note This function doesn't only manage endianness (as bswap64 do), but also
|
||||
* complete most significant incomplete u64 with 0 (if data is not a u64
|
||||
* multiple), and fill u64 last address with 0.
|
||||
* @param addr: PKA_RAM address to write the buffer 'data'
|
||||
* @param data: is a BYTE list with most significant bytes first
|
||||
* @param data_size: nb of byte in data
|
||||
* @param eo_nbw: is ECC Operand size in 64bits word (including the extra 0)
|
||||
* (note it depends of the prime modulus length, not the data size)
|
||||
* @retval 0 if OK.
|
||||
* -EINVAL if data_size and eo_nbw are inconsistent, ie data doesn't
|
||||
* fit in defined eo_nbw, or eo_nbw bigger than hardware limit.
|
||||
*/
|
||||
static int write_eo_data(uintptr_t addr, uint8_t *data, unsigned int data_size,
|
||||
unsigned int eo_nbw)
|
||||
{
|
||||
uint32_t word_index;
|
||||
int data_index;
|
||||
|
||||
if ((eo_nbw < OP_NBW_FROM_SIZE(data_size)) || (eo_nbw > MAX_EO_NBW)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Fill value */
|
||||
data_index = (int)data_size - 1;
|
||||
for (word_index = 0U; word_index < eo_nbw; word_index++) {
|
||||
uint64_t tmp = 0ULL;
|
||||
unsigned int i = 0U; /* index in the tmp U64 word */
|
||||
|
||||
/* Stop if end of tmp or end of data */
|
||||
while ((i < sizeof(tmp)) && (data_index >= 0)) {
|
||||
tmp |= (uint64_t)(data[data_index]) << (UINT8_LEN * i);
|
||||
i++; /* Move byte index in current (u64)tmp */
|
||||
data_index--; /* Move to just next most significat byte */
|
||||
}
|
||||
|
||||
mmio_write_64(addr + word_index * sizeof(tmp), tmp);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int get_ecc_op_nbword(enum stm32_pka_ecdsa_curve_id cid)
|
||||
{
|
||||
if (cid >= ARRAY_SIZE(curve_def)) {
|
||||
ERROR("CID %u is out of boundaries\n", cid);
|
||||
panic();
|
||||
}
|
||||
|
||||
return OP_NBW_FROM_LEN(curve_def[cid].n_len);
|
||||
}
|
||||
|
||||
static int stm32_pka_ecdsa_verif_configure_curve(uintptr_t base, enum stm32_pka_ecdsa_curve_id cid)
|
||||
{
|
||||
int ret;
|
||||
unsigned int eo_nbw = get_ecc_op_nbword(cid);
|
||||
|
||||
mmio_write_64(base + _PKA_RAM_N_LEN, curve_def[cid].n_len);
|
||||
mmio_write_64(base + _PKA_RAM_P_LEN, curve_def[cid].p_len);
|
||||
mmio_write_64(base + _PKA_RAM_A_SIGN, curve_def[cid].a_sign);
|
||||
|
||||
ret = write_eo_data(base + _PKA_RAM_A, curve_def[cid].a, curve_def[cid].a_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = write_eo_data(base + _PKA_RAM_PRIME_N,
|
||||
curve_def[cid].n, div_round_up(curve_def[cid].n_len, UINT8_LEN),
|
||||
eo_nbw);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = write_eo_data(base + _PKA_RAM_P, curve_def[cid].p,
|
||||
div_round_up(curve_def[cid].p_len, UINT8_LEN), eo_nbw);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = write_eo_data(base + _PKA_RAM_XG, curve_def[cid].xg, curve_def[cid].xg_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = write_eo_data(base + _PKA_RAM_YG, curve_def[cid].yg, curve_def[cid].yg_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_pka_ecdsa_verif_check_return(uintptr_t base)
|
||||
{
|
||||
uint64_t value;
|
||||
uint32_t sr;
|
||||
|
||||
sr = mmio_read_32(base + _PKA_SR);
|
||||
if ((sr & (_PKA_IT_OPERR | _PKA_IT_ADDRERR | _PKA_IT_RAMERR)) != 0) {
|
||||
WARN("Detected error(s): %s%s%s\n",
|
||||
(sr & _PKA_IT_OPERR) ? "Operation " : "",
|
||||
(sr & _PKA_IT_ADDRERR) ? "Address " : "",
|
||||
(sr & _PKA_IT_RAMERR) ? "RAM" : "");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
value = mmio_read_64(base + _PKA_RAM_ECDSA_VERIFY);
|
||||
if (value == _PKA_RAM_ECDSA_VERIFY_VALID) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (value == _PKA_RAM_ECDSA_VERIFY_INVALID) {
|
||||
return -EAUTH;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if BigInt stored in data is 0
|
||||
*
|
||||
* @param data: a BYTE array with most significant bytes first
|
||||
* @param size: data size
|
||||
*
|
||||
* @retval: true: if data represents a 0 value (ie all bytes == 0)
|
||||
* false: if data represents a non-zero value.
|
||||
*/
|
||||
static bool is_zero(uint8_t *data, unsigned int size)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0U; i < size; i++) {
|
||||
if (data[i] != 0U) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare two BigInt:
|
||||
* @param xdata_a: a BYTE array with most significant bytes first
|
||||
* @param size_a: nb of Byte of 'a'
|
||||
* @param data_b: a BYTE array with most significant bytes first
|
||||
* @param size_b: nb of Byte of 'b'
|
||||
*
|
||||
* @retval: true if data_a < data_b
|
||||
* false if data_a >= data_b
|
||||
*/
|
||||
static bool is_smaller(uint8_t *data_a, unsigned int size_a,
|
||||
uint8_t *data_b, unsigned int size_b)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
i = MAX(size_a, size_b) + 1U;
|
||||
do {
|
||||
uint8_t a, b;
|
||||
|
||||
i--;
|
||||
if (size_a < i) {
|
||||
a = 0U;
|
||||
} else {
|
||||
a = data_a[size_a - i];
|
||||
}
|
||||
|
||||
if (size_b < i) {
|
||||
b = 0U;
|
||||
} else {
|
||||
b = data_b[size_b - i];
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a > b) {
|
||||
return false;
|
||||
}
|
||||
} while (i != 0U);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int stm32_pka_ecdsa_check_param(void *sig_r_ptr, unsigned int sig_r_size,
|
||||
void *sig_s_ptr, unsigned int sig_s_size,
|
||||
void *pk_x_ptr, unsigned int pk_x_size,
|
||||
void *pk_y_ptr, unsigned int pk_y_size,
|
||||
enum stm32_pka_ecdsa_curve_id cid)
|
||||
{
|
||||
/* Public Key check */
|
||||
/* Check Xq < p */
|
||||
if (!is_smaller(pk_x_ptr, pk_x_size,
|
||||
curve_def[cid].p, div_round_up(curve_def[cid].p_len, UINT8_LEN))) {
|
||||
WARN("%s Xq < p inval\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check Yq < p */
|
||||
if (!is_smaller(pk_y_ptr, pk_y_size,
|
||||
curve_def[cid].p, div_round_up(curve_def[cid].p_len, UINT8_LEN))) {
|
||||
WARN("%s Yq < p inval\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Signature check */
|
||||
/* Check 0 < r < n */
|
||||
if (!is_smaller(sig_r_ptr, sig_r_size,
|
||||
curve_def[cid].n, div_round_up(curve_def[cid].n_len, UINT8_LEN)) &&
|
||||
!is_zero(sig_r_ptr, sig_r_size)) {
|
||||
WARN("%s 0< r < n inval\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check 0 < s < n */
|
||||
if (!is_smaller(sig_s_ptr, sig_s_size,
|
||||
curve_def[cid].n, div_round_up(curve_def[cid].n_len, UINT8_LEN)) &&
|
||||
!is_zero(sig_s_ptr, sig_s_size)) {
|
||||
WARN("%s 0< s < n inval\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Initialize the PKA driver.
|
||||
* @param None.
|
||||
* @retval 0 if OK, negative value else.
|
||||
*/
|
||||
int stm32_pka_init(void)
|
||||
{
|
||||
int err;
|
||||
#if LOG_LEVEL >= LOG_LEVEL_VERBOSE
|
||||
uint32_t ver;
|
||||
uint32_t id;
|
||||
#endif
|
||||
|
||||
err = stm32_pka_parse_fdt();
|
||||
if (err != 0) {
|
||||
err = stm32_pka_get_platdata(&pka_pdata);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
clk_enable(pka_pdata.clock_id);
|
||||
|
||||
if (stm32mp_reset_assert((unsigned long)pka_pdata.reset_id, TIMEOUT_US_1MS) != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
udelay(PKA_RESET_DELAY);
|
||||
if (stm32mp_reset_deassert((unsigned long)pka_pdata.reset_id, TIMEOUT_US_1MS) != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
#if LOG_LEVEL >= LOG_LEVEL_VERBOSE
|
||||
id = mmio_read_32(pka_pdata.base + _PKA_IPIDR);
|
||||
ver = mmio_read_32(pka_pdata.base + _PKA_VERR);
|
||||
|
||||
VERBOSE("STM32 PKA[%x] V%u.%u\n", id,
|
||||
(ver & _PKA_VERR_MAJREV_MASK) >> _PKA_VERR_MAJREV_SHIFT,
|
||||
(ver & _PKA_VERR_MINREV_MASK) >> _PKA_VERR_MINREV_SHIFT);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stm32_pka_ecdsa_verif(void *hash, unsigned int hash_size,
|
||||
void *sig_r_ptr, unsigned int sig_r_size,
|
||||
void *sig_s_ptr, unsigned int sig_s_size,
|
||||
void *pk_x_ptr, unsigned int pk_x_size,
|
||||
void *pk_y_ptr, unsigned int pk_y_size,
|
||||
enum stm32_pka_ecdsa_curve_id cid)
|
||||
{
|
||||
int ret;
|
||||
uintptr_t base = pka_pdata.base;
|
||||
unsigned int eo_nbw = get_ecc_op_nbword(cid);
|
||||
|
||||
if ((hash == NULL) || (sig_r_ptr == NULL) || (sig_s_ptr == NULL) ||
|
||||
(pk_x_ptr == NULL) || (pk_y_ptr == NULL)) {
|
||||
INFO("%s invalid input param\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = stm32_pka_ecdsa_check_param(sig_r_ptr, sig_r_size,
|
||||
sig_s_ptr, sig_s_size,
|
||||
pk_x_ptr, pk_x_size,
|
||||
pk_y_ptr, pk_y_size,
|
||||
cid);
|
||||
if (ret < 0) {
|
||||
INFO("%s check param error %d\n", __func__, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((mmio_read_32(base + _PKA_SR) & _PKA_SR_BUSY) == _PKA_SR_BUSY) {
|
||||
INFO("%s busy\n", __func__);
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Fill PKA RAM */
|
||||
/* With curve id values */
|
||||
ret = stm32_pka_ecdsa_verif_configure_curve(base, cid);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* With pubkey */
|
||||
ret = write_eo_data(base + _PKA_RAM_XQ, pk_x_ptr, pk_x_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = write_eo_data(base + _PKA_RAM_YQ, pk_y_ptr, pk_y_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* With hash */
|
||||
ret = write_eo_data(base + _PKA_RAM_HASH_Z, hash, hash_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* With signature */
|
||||
ret = write_eo_data(base + _PKA_RAM_SIGN_R, sig_r_ptr, sig_r_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = write_eo_data(base + _PKA_RAM_SIGN_S, sig_s_ptr, sig_s_size, eo_nbw);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Set mode to ecdsa signature verification */
|
||||
ret = pka_enable(base, _PKA_CR_MODE_ECDSA_VERIF);
|
||||
if (ret < 0) {
|
||||
WARN("%s set mode pka error %d\n", __func__, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Start processing and wait end */
|
||||
ret = stm32_pka_process(base);
|
||||
if (ret < 0) {
|
||||
WARN("%s process error %d\n", __func__, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Check return status */
|
||||
ret = stm32_pka_ecdsa_verif_check_return(base);
|
||||
|
||||
/* Unset end proc */
|
||||
mmio_setbits_32(base + _PKA_CLRFR, _PKA_IT_PROCEND);
|
||||
|
||||
out:
|
||||
/* Disable PKA (will stop all pending proccess and reset RAM) */
|
||||
pka_disable(base);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (c) 2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <arch_helpers.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32_rng.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#if STM32_RNG_VER == 2
|
||||
#define DT_RNG_COMPAT "st,stm32-rng"
|
||||
#endif
|
||||
#if STM32_RNG_VER == 4
|
||||
#define DT_RNG_COMPAT "st,stm32mp13-rng"
|
||||
#endif
|
||||
#define RNG_CR 0x00U
|
||||
#define RNG_SR 0x04U
|
||||
#define RNG_DR 0x08U
|
||||
|
||||
#define RNG_CR_RNGEN BIT(2)
|
||||
#define RNG_CR_IE BIT(3)
|
||||
#define RNG_CR_CED BIT(5)
|
||||
#define RNG_CR_CLKDIV GENMASK(19, 16)
|
||||
#define RNG_CR_CLKDIV_SHIFT 16U
|
||||
#define RNG_CR_CONDRST BIT(30)
|
||||
|
||||
#define RNG_SR_DRDY BIT(0)
|
||||
#define RNG_SR_CECS BIT(1)
|
||||
#define RNG_SR_SECS BIT(2)
|
||||
#define RNG_SR_CEIS BIT(5)
|
||||
#define RNG_SR_SEIS BIT(6)
|
||||
|
||||
#define RNG_TIMEOUT_US 100000U
|
||||
#define RNG_TIMEOUT_STEP_US 10U
|
||||
|
||||
#define TIMEOUT_US_1MS 1000U
|
||||
|
||||
#define RNG_NIST_CONFIG_A 0x00F40F00U
|
||||
#define RNG_NIST_CONFIG_B 0x01801000U
|
||||
#define RNG_NIST_CONFIG_C 0x00F00D00U
|
||||
#define RNG_NIST_CONFIG_MASK GENMASK(25, 8)
|
||||
|
||||
#define RNG_MAX_NOISE_CLK_FREQ 48000000U
|
||||
|
||||
struct stm32_rng_instance {
|
||||
uintptr_t base;
|
||||
unsigned long clock;
|
||||
};
|
||||
|
||||
static struct stm32_rng_instance stm32_rng;
|
||||
|
||||
static void seed_error_recovery(void)
|
||||
{
|
||||
uint8_t i __maybe_unused;
|
||||
|
||||
/* Recommended by the SoC reference manual */
|
||||
mmio_clrbits_32(stm32_rng.base + RNG_SR, RNG_SR_SEIS);
|
||||
dmbsy();
|
||||
|
||||
#if STM32_RNG_VER == 2
|
||||
/* No Auto-reset on version 2, need to clean FIFO */
|
||||
for (i = 12U; i != 0U; i--) {
|
||||
(void)mmio_read_32(stm32_rng.base + RNG_DR);
|
||||
}
|
||||
|
||||
dmbsy();
|
||||
#endif
|
||||
|
||||
if ((mmio_read_32(stm32_rng.base + RNG_SR) & RNG_SR_SEIS) != 0U) {
|
||||
ERROR("RNG noise\n");
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t stm32_rng_clock_freq_restrain(void)
|
||||
{
|
||||
unsigned long clock_rate;
|
||||
uint32_t clock_div = 0U;
|
||||
|
||||
clock_rate = clk_get_rate(stm32_rng.clock);
|
||||
|
||||
/*
|
||||
* Get the exponent to apply on the CLKDIV field in RNG_CR register
|
||||
* No need to handle the case when clock-div > 0xF as it is physically
|
||||
* impossible
|
||||
*/
|
||||
while ((clock_rate >> clock_div) > RNG_MAX_NOISE_CLK_FREQ) {
|
||||
clock_div++;
|
||||
}
|
||||
|
||||
VERBOSE("RNG clk rate : %lu\n", clk_get_rate(stm32_rng.clock) >> clock_div);
|
||||
|
||||
return clock_div;
|
||||
}
|
||||
|
||||
static int stm32_rng_enable(void)
|
||||
{
|
||||
uint32_t sr;
|
||||
uint64_t timeout;
|
||||
uint32_t clock_div __maybe_unused;
|
||||
|
||||
#if STM32_RNG_VER == 2
|
||||
mmio_write_32(stm32_rng.base + RNG_CR, RNG_CR_RNGEN | RNG_CR_CED);
|
||||
#endif
|
||||
#if STM32_RNG_VER == 4
|
||||
/* Reset internal block and disable CED bit */
|
||||
clock_div = stm32_rng_clock_freq_restrain();
|
||||
|
||||
/* Update configuration fields */
|
||||
mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_NIST_CONFIG_MASK,
|
||||
RNG_NIST_CONFIG_A | RNG_CR_CONDRST | RNG_CR_CED);
|
||||
|
||||
mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_CR_CLKDIV,
|
||||
(clock_div << RNG_CR_CLKDIV_SHIFT));
|
||||
|
||||
mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_CR_CONDRST, RNG_CR_RNGEN);
|
||||
#endif
|
||||
timeout = timeout_init_us(RNG_TIMEOUT_US);
|
||||
sr = mmio_read_32(stm32_rng.base + RNG_SR);
|
||||
while ((sr & RNG_SR_DRDY) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
WARN("Timeout waiting\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if ((sr & (RNG_SR_SECS | RNG_SR_SEIS)) != 0U) {
|
||||
seed_error_recovery();
|
||||
timeout = timeout_init_us(RNG_TIMEOUT_US);
|
||||
}
|
||||
|
||||
udelay(RNG_TIMEOUT_STEP_US);
|
||||
sr = mmio_read_32(stm32_rng.base + RNG_SR);
|
||||
}
|
||||
|
||||
VERBOSE("Init RNG done\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* stm32_rng_read - Read a number of random bytes from RNG
|
||||
* out: pointer to the output buffer
|
||||
* size: number of bytes to be read
|
||||
* Return 0 on success, non-0 on failure
|
||||
*/
|
||||
int stm32_rng_read(uint8_t *out, uint32_t size)
|
||||
{
|
||||
uint8_t *buf = out;
|
||||
size_t len = size;
|
||||
int nb_tries;
|
||||
uint32_t data32;
|
||||
int rc = 0;
|
||||
unsigned int count;
|
||||
|
||||
if (stm32_rng.base == 0U) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
while (len != 0U) {
|
||||
nb_tries = RNG_TIMEOUT_US / RNG_TIMEOUT_STEP_US;
|
||||
do {
|
||||
uint32_t status = mmio_read_32(stm32_rng.base + RNG_SR);
|
||||
|
||||
if ((status & (RNG_SR_SECS | RNG_SR_SEIS)) != 0U) {
|
||||
seed_error_recovery();
|
||||
}
|
||||
|
||||
udelay(RNG_TIMEOUT_STEP_US);
|
||||
nb_tries--;
|
||||
if (nb_tries == 0) {
|
||||
rc = -ETIMEDOUT;
|
||||
goto bail;
|
||||
}
|
||||
} while ((mmio_read_32(stm32_rng.base + RNG_SR) &
|
||||
RNG_SR_DRDY) == 0U);
|
||||
|
||||
count = 4U;
|
||||
while (len != 0U) {
|
||||
data32 = mmio_read_32(stm32_rng.base + RNG_DR);
|
||||
count--;
|
||||
|
||||
memcpy(buf, &data32, MIN(len, sizeof(uint32_t)));
|
||||
buf += MIN(len, sizeof(uint32_t));
|
||||
len -= MIN(len, sizeof(uint32_t));
|
||||
|
||||
if (count == 0U) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail:
|
||||
if (rc != 0) {
|
||||
memset(out, 0, buf - out);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* stm32_rng_init: Initialize rng from DT
|
||||
* return 0 on success, negative value on failure
|
||||
*/
|
||||
int stm32_rng_init(void)
|
||||
{
|
||||
void *fdt;
|
||||
struct dt_node_info dt_rng;
|
||||
int node;
|
||||
|
||||
if (stm32_rng.base != 0U) {
|
||||
/* Driver is already initialized */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
node = dt_get_node(&dt_rng, -1, DT_RNG_COMPAT);
|
||||
if (node < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dt_rng.status == DT_DISABLED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(dt_rng.base != 0U);
|
||||
|
||||
stm32_rng.base = dt_rng.base;
|
||||
|
||||
if (dt_rng.clock < 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
stm32_rng.clock = (unsigned long)dt_rng.clock;
|
||||
clk_enable(stm32_rng.clock);
|
||||
|
||||
if (dt_rng.reset >= 0) {
|
||||
int ret;
|
||||
|
||||
ret = stm32mp_reset_assert((unsigned long)dt_rng.reset,
|
||||
TIMEOUT_US_1MS);
|
||||
if (ret != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
udelay(20);
|
||||
|
||||
ret = stm32mp_reset_deassert((unsigned long)dt_rng.reset,
|
||||
TIMEOUT_US_1MS);
|
||||
if (ret != 0) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
return stm32_rng_enable();
|
||||
}
|
||||
@@ -0,0 +1,913 @@
|
||||
/*
|
||||
* Copyright (c) 2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <endian.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32_saes.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils_def.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#define UINT8_BIT 8U
|
||||
#define AES_BLOCK_SIZE_BIT 128U
|
||||
#define AES_BLOCK_SIZE (AES_BLOCK_SIZE_BIT / UINT8_BIT)
|
||||
|
||||
#define AES_KEYSIZE_128 16U
|
||||
#define AES_KEYSIZE_256 32U
|
||||
#define AES_IVSIZE 16U
|
||||
|
||||
/* SAES control register */
|
||||
#define _SAES_CR 0x0U
|
||||
/* SAES status register */
|
||||
#define _SAES_SR 0x04U
|
||||
/* SAES data input register */
|
||||
#define _SAES_DINR 0x08U
|
||||
/* SAES data output register */
|
||||
#define _SAES_DOUTR 0x0CU
|
||||
/* SAES key registers [0-3] */
|
||||
#define _SAES_KEYR0 0x10U
|
||||
#define _SAES_KEYR1 0x14U
|
||||
#define _SAES_KEYR2 0x18U
|
||||
#define _SAES_KEYR3 0x1CU
|
||||
/* SAES initialization vector registers [0-3] */
|
||||
#define _SAES_IVR0 0x20U
|
||||
#define _SAES_IVR1 0x24U
|
||||
#define _SAES_IVR2 0x28U
|
||||
#define _SAES_IVR3 0x2CU
|
||||
/* SAES key registers [4-7] */
|
||||
#define _SAES_KEYR4 0x30U
|
||||
#define _SAES_KEYR5 0x34U
|
||||
#define _SAES_KEYR6 0x38U
|
||||
#define _SAES_KEYR7 0x3CU
|
||||
/* SAES suspend registers [0-7] */
|
||||
#define _SAES_SUSPR0 0x40U
|
||||
#define _SAES_SUSPR1 0x44U
|
||||
#define _SAES_SUSPR2 0x48U
|
||||
#define _SAES_SUSPR3 0x4CU
|
||||
#define _SAES_SUSPR4 0x50U
|
||||
#define _SAES_SUSPR5 0x54U
|
||||
#define _SAES_SUSPR6 0x58U
|
||||
#define _SAES_SUSPR7 0x5CU
|
||||
/* SAES Interrupt Enable Register */
|
||||
#define _SAES_IER 0x300U
|
||||
/* SAES Interrupt Status Register */
|
||||
#define _SAES_ISR 0x304U
|
||||
/* SAES Interrupt Clear Register */
|
||||
#define _SAES_ICR 0x308U
|
||||
|
||||
/* SAES control register fields */
|
||||
#define _SAES_CR_RESET_VALUE 0x0U
|
||||
#define _SAES_CR_IPRST BIT(31)
|
||||
#define _SAES_CR_KEYSEL_MASK GENMASK(30, 28)
|
||||
#define _SAES_CR_KEYSEL_SHIFT 28U
|
||||
#define _SAES_CR_KEYSEL_SOFT 0x0U
|
||||
#define _SAES_CR_KEYSEL_DHUK 0x1U
|
||||
#define _SAES_CR_KEYSEL_BHK 0x2U
|
||||
#define _SAES_CR_KEYSEL_BHU_XOR_BH_K 0x4U
|
||||
#define _SAES_CR_KEYSEL_TEST 0x7U
|
||||
#define _SAES_CR_KSHAREID_MASK GENMASK(27, 26)
|
||||
#define _SAES_CR_KSHAREID_SHIFT 26U
|
||||
#define _SAES_CR_KSHAREID_CRYP 0x0U
|
||||
#define _SAES_CR_KEYMOD_MASK GENMASK(25, 24)
|
||||
#define _SAES_CR_KEYMOD_SHIFT 24U
|
||||
#define _SAES_CR_KEYMOD_NORMAL 0x0U
|
||||
#define _SAES_CR_KEYMOD_WRAPPED 0x1U
|
||||
#define _SAES_CR_KEYMOD_SHARED 0x2U
|
||||
#define _SAES_CR_NPBLB_MASK GENMASK(23, 20)
|
||||
#define _SAES_CR_NPBLB_SHIFT 20U
|
||||
#define _SAES_CR_KEYPROT BIT(19)
|
||||
#define _SAES_CR_KEYSIZE BIT(18)
|
||||
#define _SAES_CR_GCMPH_MASK GENMASK(14, 13)
|
||||
#define _SAES_CR_GCMPH_SHIFT 13U
|
||||
#define _SAES_CR_GCMPH_INIT 0U
|
||||
#define _SAES_CR_GCMPH_HEADER 1U
|
||||
#define _SAES_CR_GCMPH_PAYLOAD 2U
|
||||
#define _SAES_CR_GCMPH_FINAL 3U
|
||||
#define _SAES_CR_DMAOUTEN BIT(12)
|
||||
#define _SAES_CR_DMAINEN BIT(11)
|
||||
#define _SAES_CR_CHMOD_MASK (BIT(16) | GENMASK(6, 5))
|
||||
#define _SAES_CR_CHMOD_SHIFT 5U
|
||||
#define _SAES_CR_CHMOD_ECB 0x0U
|
||||
#define _SAES_CR_CHMOD_CBC 0x1U
|
||||
#define _SAES_CR_CHMOD_CTR 0x2U
|
||||
#define _SAES_CR_CHMOD_GCM 0x3U
|
||||
#define _SAES_CR_CHMOD_GMAC 0x3U
|
||||
#define _SAES_CR_CHMOD_CCM 0x800U
|
||||
#define _SAES_CR_MODE_MASK GENMASK(4, 3)
|
||||
#define _SAES_CR_MODE_SHIFT 3U
|
||||
#define _SAES_CR_MODE_ENC 0U
|
||||
#define _SAES_CR_MODE_KEYPREP 1U
|
||||
#define _SAES_CR_MODE_DEC 2U
|
||||
#define _SAES_CR_DATATYPE_MASK GENMASK(2, 1)
|
||||
#define _SAES_CR_DATATYPE_SHIFT 1U
|
||||
#define _SAES_CR_DATATYPE_NONE 0U
|
||||
#define _SAES_CR_DATATYPE_HALF_WORD 1U
|
||||
#define _SAES_CR_DATATYPE_BYTE 2U
|
||||
#define _SAES_CR_DATATYPE_BIT 3U
|
||||
#define _SAES_CR_EN BIT(0)
|
||||
|
||||
/* SAES status register fields */
|
||||
#define _SAES_SR_KEYVALID BIT(7)
|
||||
#define _SAES_SR_BUSY BIT(3)
|
||||
#define _SAES_SR_WRERR BIT(2)
|
||||
#define _SAES_SR_RDERR BIT(1)
|
||||
#define _SAES_SR_CCF BIT(0)
|
||||
|
||||
/* SAES interrupt registers fields */
|
||||
#define _SAES_I_RNG_ERR BIT(3)
|
||||
#define _SAES_I_KEY_ERR BIT(2)
|
||||
#define _SAES_I_RW_ERR BIT(1)
|
||||
#define _SAES_I_CC BIT(0)
|
||||
|
||||
#define SAES_TIMEOUT_US 100000U
|
||||
#define TIMEOUT_US_1MS 1000U
|
||||
#define SAES_RESET_DELAY 20U
|
||||
|
||||
#define IS_CHAINING_MODE(mod, cr) \
|
||||
(((cr) & _SAES_CR_CHMOD_MASK) == (_SAES_CR_CHMOD_##mod << _SAES_CR_CHMOD_SHIFT))
|
||||
|
||||
#define SET_CHAINING_MODE(mod, cr) \
|
||||
mmio_clrsetbits_32((cr), _SAES_CR_CHMOD_MASK, _SAES_CR_CHMOD_##mod << _SAES_CR_CHMOD_SHIFT)
|
||||
|
||||
#define pragma weak stm32_saes_get_platdata
|
||||
|
||||
static struct stm32_saes_platdata saes_pdata;
|
||||
|
||||
int stm32_saes_get_platdata(struct stm32_saes_platdata *pdata)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int stm32_saes_parse_fdt(struct stm32_saes_platdata *pdata)
|
||||
{
|
||||
int node;
|
||||
struct dt_node_info info;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
node = dt_get_node(&info, -1, DT_SAES_COMPAT);
|
||||
if (node < 0) {
|
||||
ERROR("No SAES entry in DT\n");
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if (info.status == DT_DISABLED) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if ((info.base == 0U) || (info.clock < 0) || (info.reset < 0)) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
pdata->base = (uintptr_t)info.base;
|
||||
pdata->clock_id = (unsigned long)info.clock;
|
||||
pdata->reset_id = (unsigned int)info.reset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool does_chaining_mode_need_iv(uint32_t cr)
|
||||
{
|
||||
return !(IS_CHAINING_MODE(ECB, cr));
|
||||
}
|
||||
|
||||
static bool is_encrypt(uint32_t cr)
|
||||
{
|
||||
return (cr & _SAES_CR_MODE_MASK) == (_SAES_CR_MODE_ENC << _SAES_CR_MODE_SHIFT);
|
||||
}
|
||||
|
||||
static bool is_decrypt(uint32_t cr)
|
||||
{
|
||||
return (cr & _SAES_CR_MODE_MASK) == (_SAES_CR_MODE_DEC << _SAES_CR_MODE_SHIFT);
|
||||
}
|
||||
|
||||
static int wait_computation_completed(uintptr_t base)
|
||||
{
|
||||
uint64_t timeout = timeout_init_us(SAES_TIMEOUT_US);
|
||||
|
||||
while ((mmio_read_32(base + _SAES_SR) & _SAES_SR_CCF) != _SAES_SR_CCF) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
WARN("%s: timeout\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void clear_computation_completed(uintptr_t base)
|
||||
{
|
||||
mmio_setbits_32(base + _SAES_ICR, _SAES_I_CC);
|
||||
}
|
||||
|
||||
static int saes_start(struct stm32_saes_context *ctx)
|
||||
{
|
||||
uint64_t timeout;
|
||||
|
||||
/* Reset IP */
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_IPRST);
|
||||
udelay(SAES_RESET_DELAY);
|
||||
mmio_clrbits_32(ctx->base + _SAES_CR, _SAES_CR_IPRST);
|
||||
|
||||
timeout = timeout_init_us(SAES_TIMEOUT_US);
|
||||
while ((mmio_read_32(ctx->base + _SAES_SR) & _SAES_SR_BUSY) == _SAES_SR_BUSY) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
WARN("%s: timeout\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void saes_end(struct stm32_saes_context *ctx, int prev_error)
|
||||
{
|
||||
if (prev_error != 0) {
|
||||
/* Reset IP */
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_IPRST);
|
||||
udelay(SAES_RESET_DELAY);
|
||||
mmio_clrbits_32(ctx->base + _SAES_CR, _SAES_CR_IPRST);
|
||||
}
|
||||
|
||||
/* Disable the SAES peripheral */
|
||||
mmio_clrbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
}
|
||||
|
||||
static void saes_write_iv(struct stm32_saes_context *ctx)
|
||||
{
|
||||
/* If chaining mode need to restore IV */
|
||||
if (does_chaining_mode_need_iv(ctx->cr)) {
|
||||
uint8_t i;
|
||||
|
||||
/* Restore the _SAES_IVRx */
|
||||
for (i = 0U; i < AES_IVSIZE / sizeof(uint32_t); i++) {
|
||||
mmio_write_32(ctx->base + _SAES_IVR0 + i * sizeof(uint32_t), ctx->iv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void saes_write_key(struct stm32_saes_context *ctx)
|
||||
{
|
||||
/* Restore the _SAES_KEYRx if SOFTWARE key */
|
||||
if ((ctx->cr & _SAES_CR_KEYSEL_MASK) == (_SAES_CR_KEYSEL_SOFT << _SAES_CR_KEYSEL_SHIFT)) {
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0U; i < AES_KEYSIZE_128 / sizeof(uint32_t); i++) {
|
||||
mmio_write_32(ctx->base + _SAES_KEYR0 + i * sizeof(uint32_t), ctx->key[i]);
|
||||
}
|
||||
|
||||
if ((ctx->cr & _SAES_CR_KEYSIZE) == _SAES_CR_KEYSIZE) {
|
||||
for (i = 0U; i < (AES_KEYSIZE_256 / 2U) / sizeof(uint32_t); i++) {
|
||||
mmio_write_32(ctx->base + _SAES_KEYR4 + i * sizeof(uint32_t),
|
||||
ctx->key[i + 4U]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int saes_prepare_key(struct stm32_saes_context *ctx)
|
||||
{
|
||||
/* Disable the SAES peripheral */
|
||||
mmio_clrbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
|
||||
/* Set key size */
|
||||
if ((ctx->cr & _SAES_CR_KEYSIZE) != 0U) {
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_KEYSIZE);
|
||||
} else {
|
||||
mmio_clrbits_32(ctx->base + _SAES_CR, _SAES_CR_KEYSIZE);
|
||||
}
|
||||
|
||||
saes_write_key(ctx);
|
||||
|
||||
/* For ECB/CBC decryption, key preparation mode must be selected to populate the key */
|
||||
if ((IS_CHAINING_MODE(ECB, ctx->cr) || IS_CHAINING_MODE(CBC, ctx->cr)) &&
|
||||
is_decrypt(ctx->cr)) {
|
||||
int ret;
|
||||
|
||||
/* Select Mode 2 */
|
||||
mmio_clrsetbits_32(ctx->base + _SAES_CR, _SAES_CR_MODE_MASK,
|
||||
_SAES_CR_MODE_KEYPREP << _SAES_CR_MODE_SHIFT);
|
||||
|
||||
/* Enable SAES */
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
|
||||
/* Wait Computation completed */
|
||||
ret = wait_computation_completed(ctx->base);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
clear_computation_completed(ctx->base);
|
||||
|
||||
/* Set Mode 3 */
|
||||
mmio_clrsetbits_32(ctx->base + _SAES_CR, _SAES_CR_MODE_MASK,
|
||||
_SAES_CR_MODE_DEC << _SAES_CR_MODE_SHIFT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int save_context(struct stm32_saes_context *ctx)
|
||||
{
|
||||
if ((mmio_read_32(ctx->base + _SAES_SR) & _SAES_SR_CCF) != 0U) {
|
||||
/* Device should not be in a processing phase */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Save CR */
|
||||
ctx->cr = mmio_read_32(ctx->base + _SAES_CR);
|
||||
|
||||
/* If chaining mode need to save current IV */
|
||||
if (does_chaining_mode_need_iv(ctx->cr)) {
|
||||
uint8_t i;
|
||||
|
||||
/* Save IV */
|
||||
for (i = 0U; i < AES_IVSIZE / sizeof(uint32_t); i++) {
|
||||
ctx->iv[i] = mmio_read_32(ctx->base + _SAES_IVR0 + i * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable the SAES peripheral */
|
||||
mmio_clrbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* To resume the processing of a message */
|
||||
static int restore_context(struct stm32_saes_context *ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* IP should be disabled */
|
||||
if ((mmio_read_32(ctx->base + _SAES_CR) & _SAES_CR_EN) != 0U) {
|
||||
VERBOSE("%s: Device is still enabled\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Reset internal state */
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_IPRST);
|
||||
|
||||
/* Restore the _SAES_CR */
|
||||
mmio_write_32(ctx->base + _SAES_CR, ctx->cr);
|
||||
|
||||
/* Preparation decrypt key */
|
||||
ret = saes_prepare_key(ctx);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
saes_write_iv(ctx);
|
||||
|
||||
/* Enable the SAES peripheral */
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize SAES driver.
|
||||
* @param None.
|
||||
* @retval 0 if OK; negative value else.
|
||||
*/
|
||||
int stm32_saes_driver_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = stm32_saes_parse_fdt(&saes_pdata);
|
||||
if (err != 0) {
|
||||
err = stm32_saes_get_platdata(&saes_pdata);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
clk_enable(saes_pdata.clock_id);
|
||||
if (stm32mp_reset_assert(saes_pdata.reset_id, TIMEOUT_US_1MS) != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
udelay(SAES_RESET_DELAY);
|
||||
if (stm32mp_reset_deassert(saes_pdata.reset_id, TIMEOUT_US_1MS) != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start a AES computation.
|
||||
* @param ctx: SAES process context
|
||||
* @param is_dec: true if decryption, false if encryption
|
||||
* @param ch_mode: define the chaining mode
|
||||
* @param key_select: define where the key comes from.
|
||||
* @param key: pointer to key (if key_select is KEY_SOFT, else unused)
|
||||
* @param key_size: key size
|
||||
* @param iv: pointer to initialization vectore (unsed if ch_mode is ECB)
|
||||
* @param iv_size: iv size
|
||||
* @note this function doesn't access to hardware but store in ctx the values
|
||||
*
|
||||
* @retval 0 if OK; negative value else.
|
||||
*/
|
||||
int stm32_saes_init(struct stm32_saes_context *ctx, bool is_dec,
|
||||
enum stm32_saes_chaining_mode ch_mode, enum stm32_saes_key_selection key_select,
|
||||
const void *key, size_t key_size, const void *iv, size_t iv_size)
|
||||
{
|
||||
unsigned int i;
|
||||
const uint32_t *iv_u32;
|
||||
const uint32_t *key_u32;
|
||||
|
||||
ctx->assoc_len = 0U;
|
||||
ctx->load_len = 0U;
|
||||
|
||||
ctx->base = saes_pdata.base;
|
||||
ctx->cr = _SAES_CR_RESET_VALUE;
|
||||
|
||||
/* We want buffer to be u32 aligned */
|
||||
assert((uintptr_t)key % __alignof__(uint32_t) == 0);
|
||||
assert((uintptr_t)iv % __alignof__(uint32_t) == 0);
|
||||
|
||||
iv_u32 = iv;
|
||||
key_u32 = key;
|
||||
|
||||
if (is_dec) {
|
||||
/* Save Mode 3 = decrypt */
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_MODE_MASK,
|
||||
_SAES_CR_MODE_DEC << _SAES_CR_MODE_SHIFT);
|
||||
} else {
|
||||
/* Save Mode 1 = crypt */
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_MODE_MASK,
|
||||
_SAES_CR_MODE_ENC << _SAES_CR_MODE_SHIFT);
|
||||
}
|
||||
|
||||
/* Save chaining mode */
|
||||
switch (ch_mode) {
|
||||
case STM32_SAES_MODE_ECB:
|
||||
SET_CHAINING_MODE(ECB, (uintptr_t)&(ctx->cr));
|
||||
break;
|
||||
case STM32_SAES_MODE_CBC:
|
||||
SET_CHAINING_MODE(CBC, (uintptr_t)&(ctx->cr));
|
||||
break;
|
||||
case STM32_SAES_MODE_CTR:
|
||||
SET_CHAINING_MODE(CTR, (uintptr_t)&(ctx->cr));
|
||||
break;
|
||||
case STM32_SAES_MODE_GCM:
|
||||
SET_CHAINING_MODE(GCM, (uintptr_t)&(ctx->cr));
|
||||
break;
|
||||
case STM32_SAES_MODE_CCM:
|
||||
SET_CHAINING_MODE(CCM, (uintptr_t)&(ctx->cr));
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* We will use HW Byte swap (_SAES_CR_DATATYPE_BYTE) for data.
|
||||
* so we won't need to
|
||||
* htobe32(data) before write to DINR
|
||||
* nor
|
||||
* be32toh after reading from DOUTR
|
||||
*
|
||||
* But note that wrap key only accept _SAES_CR_DATATYPE_NONE
|
||||
*/
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_DATATYPE_MASK,
|
||||
_SAES_CR_DATATYPE_BYTE << _SAES_CR_DATATYPE_SHIFT);
|
||||
|
||||
/* Configure keysize */
|
||||
switch (key_size) {
|
||||
case AES_KEYSIZE_128:
|
||||
mmio_clrbits_32((uintptr_t)&(ctx->cr), _SAES_CR_KEYSIZE);
|
||||
break;
|
||||
case AES_KEYSIZE_256:
|
||||
mmio_setbits_32((uintptr_t)&(ctx->cr), _SAES_CR_KEYSIZE);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Configure key */
|
||||
switch (key_select) {
|
||||
case STM32_SAES_KEY_SOFT:
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_KEYSEL_MASK,
|
||||
_SAES_CR_KEYSEL_SOFT << _SAES_CR_KEYSEL_SHIFT);
|
||||
/* Save key */
|
||||
switch (key_size) {
|
||||
case AES_KEYSIZE_128:
|
||||
/* First 16 bytes == 4 u32 */
|
||||
for (i = 0U; i < AES_KEYSIZE_128 / sizeof(uint32_t); i++) {
|
||||
mmio_write_32((uintptr_t)(ctx->key + i), htobe32(key_u32[3 - i]));
|
||||
/* /!\ we save the key in HW byte order
|
||||
* and word order : key[i] is for _SAES_KEYRi
|
||||
*/
|
||||
}
|
||||
break;
|
||||
case AES_KEYSIZE_256:
|
||||
for (i = 0U; i < AES_KEYSIZE_256 / sizeof(uint32_t); i++) {
|
||||
mmio_write_32((uintptr_t)(ctx->key + i), htobe32(key_u32[7 - i]));
|
||||
/* /!\ we save the key in HW byte order
|
||||
* and word order : key[i] is for _SAES_KEYRi
|
||||
*/
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
break;
|
||||
case STM32_SAES_KEY_DHU:
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_KEYSEL_MASK,
|
||||
_SAES_CR_KEYSEL_DHUK << _SAES_CR_KEYSEL_SHIFT);
|
||||
break;
|
||||
case STM32_SAES_KEY_BH:
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_KEYSEL_MASK,
|
||||
_SAES_CR_KEYSEL_BHK << _SAES_CR_KEYSEL_SHIFT);
|
||||
break;
|
||||
case STM32_SAES_KEY_BHU_XOR_BH:
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_KEYSEL_MASK,
|
||||
_SAES_CR_KEYSEL_BHU_XOR_BH_K << _SAES_CR_KEYSEL_SHIFT);
|
||||
break;
|
||||
case STM32_SAES_KEY_WRAPPED:
|
||||
mmio_clrsetbits_32((uintptr_t)&(ctx->cr), _SAES_CR_KEYSEL_MASK,
|
||||
_SAES_CR_KEYSEL_SOFT << _SAES_CR_KEYSEL_SHIFT);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Save IV */
|
||||
if (ch_mode != STM32_SAES_MODE_ECB) {
|
||||
if ((iv == NULL) || (iv_size != AES_IVSIZE)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0U; i < AES_IVSIZE / sizeof(uint32_t); i++) {
|
||||
mmio_write_32((uintptr_t)(ctx->iv + i), htobe32(iv_u32[3 - i]));
|
||||
/* /!\ We save the iv in HW byte order */
|
||||
}
|
||||
}
|
||||
|
||||
return saes_start(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update (or start) a AES authentificate process of associated data (CCM or GCM).
|
||||
* @param ctx: SAES process context
|
||||
* @param last_block: true if last assoc data block
|
||||
* @param data: pointer to associated data
|
||||
* @param data_size: data size
|
||||
*
|
||||
* @retval 0 if OK; negative value else.
|
||||
*/
|
||||
int stm32_saes_update_assodata(struct stm32_saes_context *ctx, bool last_block,
|
||||
uint8_t *data, size_t data_size)
|
||||
{
|
||||
int ret;
|
||||
uint32_t *data_u32;
|
||||
unsigned int i = 0U;
|
||||
|
||||
/* We want buffers to be u32 aligned */
|
||||
assert((uintptr_t)data % __alignof__(uint32_t) == 0);
|
||||
data_u32 = (uint32_t *)data;
|
||||
|
||||
/* Init phase */
|
||||
ret = restore_context(ctx);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = wait_computation_completed(ctx->base);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
clear_computation_completed(ctx->base);
|
||||
|
||||
if ((data == NULL) || (data_size == 0U)) {
|
||||
/* No associated data */
|
||||
/* ret already = 0 */
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* There is an header/associated data phase */
|
||||
mmio_clrsetbits_32(ctx->base + _SAES_CR, _SAES_CR_GCMPH_MASK,
|
||||
_SAES_CR_GCMPH_HEADER << _SAES_CR_GCMPH_SHIFT);
|
||||
|
||||
/* Enable the SAES peripheral */
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
|
||||
while (i < round_down(data_size, AES_BLOCK_SIZE)) {
|
||||
unsigned int w; /* Word index */
|
||||
|
||||
w = i / sizeof(uint32_t);
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_u32[w + 0U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_u32[w + 1U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_u32[w + 2U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_u32[w + 3U]);
|
||||
|
||||
ret = wait_computation_completed(ctx->base);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
clear_computation_completed(ctx->base);
|
||||
|
||||
/* Process next block */
|
||||
i += AES_BLOCK_SIZE;
|
||||
ctx->assoc_len += AES_BLOCK_SIZE_BIT;
|
||||
}
|
||||
|
||||
/* Manage last block if not a block size multiple */
|
||||
if ((last_block) && (i < data_size)) {
|
||||
/* We don't manage unaligned last block yet */
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
if (ret != 0) {
|
||||
saes_end(ctx, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update (or start) a AES authenticate and de/encrypt with payload data (CCM or GCM).
|
||||
* @param ctx: SAES process context
|
||||
* @param last_block: true if last payload data block
|
||||
* @param data_in: pointer to payload
|
||||
* @param data_out: pointer where to save de/encrypted payload
|
||||
* @param data_size: payload size
|
||||
*
|
||||
* @retval 0 if OK; negative value else.
|
||||
*/
|
||||
int stm32_saes_update_load(struct stm32_saes_context *ctx, bool last_block,
|
||||
uint8_t *data_in, uint8_t *data_out, size_t data_size)
|
||||
{
|
||||
int ret = 0;
|
||||
uint32_t *data_in_u32;
|
||||
uint32_t *data_out_u32;
|
||||
unsigned int i = 0U;
|
||||
uint32_t prev_cr;
|
||||
|
||||
/* We want buffers to be u32 aligned */
|
||||
assert((uintptr_t)data_in % __alignof__(uint32_t) == 0);
|
||||
assert((uintptr_t)data_out % __alignof__(uint32_t) == 0);
|
||||
data_in_u32 = (uint32_t *)data_in;
|
||||
data_out_u32 = (uint32_t *)data_out;
|
||||
|
||||
prev_cr = mmio_read_32(ctx->base + _SAES_CR);
|
||||
|
||||
if ((data_in == NULL) || (data_size == 0U)) {
|
||||
/* there is no data */
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* There is a load phase */
|
||||
mmio_clrsetbits_32(ctx->base + _SAES_CR, _SAES_CR_GCMPH_MASK,
|
||||
_SAES_CR_GCMPH_PAYLOAD << _SAES_CR_GCMPH_SHIFT);
|
||||
|
||||
if ((prev_cr & _SAES_CR_GCMPH_MASK) ==
|
||||
(_SAES_CR_GCMPH_INIT << _SAES_CR_GCMPH_SHIFT)) {
|
||||
/* Still in initialization phase, no header
|
||||
* We need to enable the SAES peripheral
|
||||
*/
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
}
|
||||
|
||||
while (i < round_down(data_size, AES_BLOCK_SIZE)) {
|
||||
unsigned int w; /* Word index */
|
||||
|
||||
w = i / sizeof(uint32_t);
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 0U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 1U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 2U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 3U]);
|
||||
|
||||
ret = wait_computation_completed(ctx->base);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
data_out_u32[w + 0U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
data_out_u32[w + 1U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
data_out_u32[w + 2U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
data_out_u32[w + 3U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
|
||||
clear_computation_completed(ctx->base);
|
||||
|
||||
/* Process next block */
|
||||
i += AES_BLOCK_SIZE;
|
||||
ctx->load_len += AES_BLOCK_SIZE_BIT;
|
||||
}
|
||||
/* Manage last block if not a block size multiple */
|
||||
if ((last_block) && (i < data_size)) {
|
||||
uint32_t block_in[AES_BLOCK_SIZE / sizeof(uint32_t)] = {0};
|
||||
uint32_t block_out[AES_BLOCK_SIZE / sizeof(uint32_t)] = {0};
|
||||
|
||||
memcpy(block_in, data_in + i, data_size - i);
|
||||
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
mmio_write_32(ctx->base + _SAES_DINR, block_in[0U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, block_in[1U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, block_in[2U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, block_in[3U]);
|
||||
|
||||
ret = wait_computation_completed(ctx->base);
|
||||
if (ret != 0) {
|
||||
VERBOSE("%s %d\n", __func__, __LINE__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
block_out[0U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
block_out[1U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
block_out[2U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
block_out[3U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
|
||||
clear_computation_completed(ctx->base);
|
||||
|
||||
memcpy(data_out + i, block_out, data_size - i);
|
||||
|
||||
ctx->load_len += (data_size - i) * UINT8_BIT;
|
||||
}
|
||||
|
||||
out:
|
||||
if (ret != 0) {
|
||||
saes_end(ctx, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get authentication tag for AES authenticated algorithms (CCM or GCM).
|
||||
* @param ctx: SAES process context
|
||||
* @param tag: pointer where to save the tag
|
||||
* @param data_size: tag size
|
||||
*
|
||||
* @retval 0 if OK; negative value else.
|
||||
*/
|
||||
int stm32_saes_final(struct stm32_saes_context *ctx, uint8_t *tag,
|
||||
size_t tag_size)
|
||||
{
|
||||
int ret;
|
||||
uint32_t tag_u32[4];
|
||||
uint32_t prev_cr;
|
||||
|
||||
prev_cr = mmio_read_32(ctx->base + _SAES_CR);
|
||||
|
||||
mmio_clrsetbits_32(ctx->base + _SAES_CR, _SAES_CR_GCMPH_MASK,
|
||||
_SAES_CR_GCMPH_FINAL << _SAES_CR_GCMPH_SHIFT);
|
||||
|
||||
if ((prev_cr & _SAES_CR_GCMPH_MASK) == (_SAES_CR_GCMPH_INIT << _SAES_CR_GCMPH_SHIFT)) {
|
||||
/* Still in initialization phase, no header
|
||||
* We need to enable the SAES peripheral
|
||||
*/
|
||||
mmio_setbits_32(ctx->base + _SAES_CR, _SAES_CR_EN);
|
||||
}
|
||||
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
mmio_write_32(ctx->base + _SAES_DINR, 0);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, ctx->assoc_len);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, 0);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, ctx->load_len);
|
||||
|
||||
ret = wait_computation_completed(ctx->base);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
tag_u32[0] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
tag_u32[1] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
tag_u32[2] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
tag_u32[3] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
|
||||
clear_computation_completed(ctx->base);
|
||||
|
||||
memcpy(tag, tag_u32, MIN(sizeof(tag_u32), tag_size));
|
||||
|
||||
out:
|
||||
saes_end(ctx, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update (or start) a AES de/encrypt process (ECB, CBC or CTR).
|
||||
* @param ctx: SAES process context
|
||||
* @param last_block: true if last payload data block
|
||||
* @param data_in: pointer to payload
|
||||
* @param data_out: pointer where to save de/encrypted payload
|
||||
* @param data_size: payload size
|
||||
*
|
||||
* @retval 0 if OK; negative value else.
|
||||
*/
|
||||
int stm32_saes_update(struct stm32_saes_context *ctx, bool last_block,
|
||||
uint8_t *data_in, uint8_t *data_out, size_t data_size)
|
||||
{
|
||||
int ret;
|
||||
uint32_t *data_in_u32;
|
||||
uint32_t *data_out_u32;
|
||||
unsigned int i = 0U;
|
||||
|
||||
/* We want buffers to be u32 aligned */
|
||||
assert((uintptr_t)data_in % __alignof__(uint32_t) == 0);
|
||||
assert((uintptr_t)data_out % __alignof__(uint32_t) == 0);
|
||||
data_in_u32 = (uint32_t *)data_in;
|
||||
data_out_u32 = (uint32_t *)data_out;
|
||||
|
||||
if ((!last_block) &&
|
||||
(round_down(data_size, AES_BLOCK_SIZE) != data_size)) {
|
||||
ERROR("%s: non last block must be multiple of 128 bits\n",
|
||||
__func__);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* In CBC encryption we need to manage specifically last 2 128bits
|
||||
* blocks if total size in not a block size aligned
|
||||
* work TODO. Currently return ENODEV.
|
||||
* Morevoer as we need to know last 2 block, if unaligned and
|
||||
* call with less than two block, return -EINVAL.
|
||||
*/
|
||||
if (last_block && IS_CHAINING_MODE(CBC, ctx->cr) && is_encrypt(ctx->cr) &&
|
||||
(round_down(data_size, AES_BLOCK_SIZE) != data_size)) {
|
||||
if (data_size < AES_BLOCK_SIZE * 2U) {
|
||||
ERROR("if CBC, last part size should be at least 2 * AES_BLOCK_SIZE\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
/* Moreover the CBC specific padding for encrypt is not yet implemented */
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = restore_context(ctx);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (i < round_down(data_size, AES_BLOCK_SIZE)) {
|
||||
unsigned int w; /* Word index */
|
||||
|
||||
w = i / sizeof(uint32_t);
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 0U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 1U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 2U]);
|
||||
mmio_write_32(ctx->base + _SAES_DINR, data_in_u32[w + 3U]);
|
||||
|
||||
ret = wait_computation_completed(ctx->base);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* No need to htobe() as we configure the HW to swap bytes */
|
||||
data_out_u32[w + 0U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
data_out_u32[w + 1U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
data_out_u32[w + 2U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
data_out_u32[w + 3U] = mmio_read_32(ctx->base + _SAES_DOUTR);
|
||||
|
||||
clear_computation_completed(ctx->base);
|
||||
|
||||
/* Process next block */
|
||||
i += AES_BLOCK_SIZE;
|
||||
}
|
||||
/* Manage last block if not a block size multiple */
|
||||
|
||||
if ((last_block) && (i < data_size)) {
|
||||
/* In and out buffer have same size so should be AES_BLOCK_SIZE multiple */
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!last_block) {
|
||||
ret = save_context(ctx);
|
||||
}
|
||||
|
||||
out:
|
||||
/* If last block or error, end of SAES process */
|
||||
if (last_block || (ret != 0)) {
|
||||
saes_end(ctx, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,764 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <arch.h>
|
||||
#include <arch_helpers.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32mp1_ddr.h>
|
||||
#include <drivers/st/stm32mp1_ddr_regs.h>
|
||||
#include <drivers/st/stm32mp1_pwr.h>
|
||||
#include <drivers/st/stm32mp1_ram.h>
|
||||
#include <drivers/st/stm32mp_ddr.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <plat/common/platform.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#define DDRCTL_REG(x, y) \
|
||||
{ \
|
||||
.name = #x, \
|
||||
.offset = offsetof(struct stm32mp_ddrctl, x), \
|
||||
.par_offset = offsetof(struct y, x) \
|
||||
}
|
||||
|
||||
#define DDRPHY_REG(x, y) \
|
||||
{ \
|
||||
.name = #x, \
|
||||
.offset = offsetof(struct stm32mp_ddrphy, x), \
|
||||
.par_offset = offsetof(struct y, x) \
|
||||
}
|
||||
|
||||
/*
|
||||
* PARAMETERS: value get from device tree :
|
||||
* size / order need to be aligned with binding
|
||||
* modification NOT ALLOWED !!!
|
||||
*/
|
||||
#define DDRCTL_REG_REG_SIZE 25 /* st,ctl-reg */
|
||||
#define DDRCTL_REG_TIMING_SIZE 12 /* st,ctl-timing */
|
||||
#define DDRCTL_REG_MAP_SIZE 9 /* st,ctl-map */
|
||||
#if STM32MP_DDR_DUAL_AXI_PORT
|
||||
#define DDRCTL_REG_PERF_SIZE 17 /* st,ctl-perf */
|
||||
#else
|
||||
#define DDRCTL_REG_PERF_SIZE 11 /* st,ctl-perf */
|
||||
#endif
|
||||
|
||||
#if STM32MP_DDR_32BIT_INTERFACE
|
||||
#define DDRPHY_REG_REG_SIZE 11 /* st,phy-reg */
|
||||
#else
|
||||
#define DDRPHY_REG_REG_SIZE 9 /* st,phy-reg */
|
||||
#endif
|
||||
#define DDRPHY_REG_TIMING_SIZE 10 /* st,phy-timing */
|
||||
|
||||
#define DDRCTL_REG_REG(x) DDRCTL_REG(x, stm32mp1_ddrctrl_reg)
|
||||
static const struct stm32mp_ddr_reg_desc ddr_reg[DDRCTL_REG_REG_SIZE] = {
|
||||
DDRCTL_REG_REG(mstr),
|
||||
DDRCTL_REG_REG(mrctrl0),
|
||||
DDRCTL_REG_REG(mrctrl1),
|
||||
DDRCTL_REG_REG(derateen),
|
||||
DDRCTL_REG_REG(derateint),
|
||||
DDRCTL_REG_REG(pwrctl),
|
||||
DDRCTL_REG_REG(pwrtmg),
|
||||
DDRCTL_REG_REG(hwlpctl),
|
||||
DDRCTL_REG_REG(rfshctl0),
|
||||
DDRCTL_REG_REG(rfshctl3),
|
||||
DDRCTL_REG_REG(crcparctl0),
|
||||
DDRCTL_REG_REG(zqctl0),
|
||||
DDRCTL_REG_REG(dfitmg0),
|
||||
DDRCTL_REG_REG(dfitmg1),
|
||||
DDRCTL_REG_REG(dfilpcfg0),
|
||||
DDRCTL_REG_REG(dfiupd0),
|
||||
DDRCTL_REG_REG(dfiupd1),
|
||||
DDRCTL_REG_REG(dfiupd2),
|
||||
DDRCTL_REG_REG(dfiphymstr),
|
||||
DDRCTL_REG_REG(odtmap),
|
||||
DDRCTL_REG_REG(dbg0),
|
||||
DDRCTL_REG_REG(dbg1),
|
||||
DDRCTL_REG_REG(dbgcmd),
|
||||
DDRCTL_REG_REG(poisoncfg),
|
||||
DDRCTL_REG_REG(pccfg),
|
||||
};
|
||||
|
||||
#define DDRCTL_REG_TIMING(x) DDRCTL_REG(x, stm32mp1_ddrctrl_timing)
|
||||
static const struct stm32mp_ddr_reg_desc ddr_timing[DDRCTL_REG_TIMING_SIZE] = {
|
||||
DDRCTL_REG_TIMING(rfshtmg),
|
||||
DDRCTL_REG_TIMING(dramtmg0),
|
||||
DDRCTL_REG_TIMING(dramtmg1),
|
||||
DDRCTL_REG_TIMING(dramtmg2),
|
||||
DDRCTL_REG_TIMING(dramtmg3),
|
||||
DDRCTL_REG_TIMING(dramtmg4),
|
||||
DDRCTL_REG_TIMING(dramtmg5),
|
||||
DDRCTL_REG_TIMING(dramtmg6),
|
||||
DDRCTL_REG_TIMING(dramtmg7),
|
||||
DDRCTL_REG_TIMING(dramtmg8),
|
||||
DDRCTL_REG_TIMING(dramtmg14),
|
||||
DDRCTL_REG_TIMING(odtcfg),
|
||||
};
|
||||
|
||||
#define DDRCTL_REG_MAP(x) DDRCTL_REG(x, stm32mp1_ddrctrl_map)
|
||||
static const struct stm32mp_ddr_reg_desc ddr_map[DDRCTL_REG_MAP_SIZE] = {
|
||||
DDRCTL_REG_MAP(addrmap1),
|
||||
DDRCTL_REG_MAP(addrmap2),
|
||||
DDRCTL_REG_MAP(addrmap3),
|
||||
DDRCTL_REG_MAP(addrmap4),
|
||||
DDRCTL_REG_MAP(addrmap5),
|
||||
DDRCTL_REG_MAP(addrmap6),
|
||||
DDRCTL_REG_MAP(addrmap9),
|
||||
DDRCTL_REG_MAP(addrmap10),
|
||||
DDRCTL_REG_MAP(addrmap11),
|
||||
};
|
||||
|
||||
#define DDRCTL_REG_PERF(x) DDRCTL_REG(x, stm32mp1_ddrctrl_perf)
|
||||
static const struct stm32mp_ddr_reg_desc ddr_perf[DDRCTL_REG_PERF_SIZE] = {
|
||||
DDRCTL_REG_PERF(sched),
|
||||
DDRCTL_REG_PERF(sched1),
|
||||
DDRCTL_REG_PERF(perfhpr1),
|
||||
DDRCTL_REG_PERF(perflpr1),
|
||||
DDRCTL_REG_PERF(perfwr1),
|
||||
DDRCTL_REG_PERF(pcfgr_0),
|
||||
DDRCTL_REG_PERF(pcfgw_0),
|
||||
DDRCTL_REG_PERF(pcfgqos0_0),
|
||||
DDRCTL_REG_PERF(pcfgqos1_0),
|
||||
DDRCTL_REG_PERF(pcfgwqos0_0),
|
||||
DDRCTL_REG_PERF(pcfgwqos1_0),
|
||||
#if STM32MP_DDR_DUAL_AXI_PORT
|
||||
DDRCTL_REG_PERF(pcfgr_1),
|
||||
DDRCTL_REG_PERF(pcfgw_1),
|
||||
DDRCTL_REG_PERF(pcfgqos0_1),
|
||||
DDRCTL_REG_PERF(pcfgqos1_1),
|
||||
DDRCTL_REG_PERF(pcfgwqos0_1),
|
||||
DDRCTL_REG_PERF(pcfgwqos1_1),
|
||||
#endif
|
||||
};
|
||||
|
||||
#define DDRPHY_REG_REG(x) DDRPHY_REG(x, stm32mp1_ddrphy_reg)
|
||||
static const struct stm32mp_ddr_reg_desc ddrphy_reg[DDRPHY_REG_REG_SIZE] = {
|
||||
DDRPHY_REG_REG(pgcr),
|
||||
DDRPHY_REG_REG(aciocr),
|
||||
DDRPHY_REG_REG(dxccr),
|
||||
DDRPHY_REG_REG(dsgcr),
|
||||
DDRPHY_REG_REG(dcr),
|
||||
DDRPHY_REG_REG(odtcr),
|
||||
DDRPHY_REG_REG(zq0cr1),
|
||||
DDRPHY_REG_REG(dx0gcr),
|
||||
DDRPHY_REG_REG(dx1gcr),
|
||||
#if STM32MP_DDR_32BIT_INTERFACE
|
||||
DDRPHY_REG_REG(dx2gcr),
|
||||
DDRPHY_REG_REG(dx3gcr),
|
||||
#endif
|
||||
};
|
||||
|
||||
#define DDRPHY_REG_TIMING(x) DDRPHY_REG(x, stm32mp1_ddrphy_timing)
|
||||
static const struct stm32mp_ddr_reg_desc ddrphy_timing[DDRPHY_REG_TIMING_SIZE] = {
|
||||
DDRPHY_REG_TIMING(ptr0),
|
||||
DDRPHY_REG_TIMING(ptr1),
|
||||
DDRPHY_REG_TIMING(ptr2),
|
||||
DDRPHY_REG_TIMING(dtpr0),
|
||||
DDRPHY_REG_TIMING(dtpr1),
|
||||
DDRPHY_REG_TIMING(dtpr2),
|
||||
DDRPHY_REG_TIMING(mr0),
|
||||
DDRPHY_REG_TIMING(mr1),
|
||||
DDRPHY_REG_TIMING(mr2),
|
||||
DDRPHY_REG_TIMING(mr3),
|
||||
};
|
||||
|
||||
/*
|
||||
* REGISTERS ARRAY: used to parse device tree and interactive mode
|
||||
*/
|
||||
static const struct stm32mp_ddr_reg_info ddr_registers[REG_TYPE_NB] = {
|
||||
[REG_REG] = {
|
||||
.name = "static",
|
||||
.desc = ddr_reg,
|
||||
.size = DDRCTL_REG_REG_SIZE,
|
||||
.base = DDR_BASE
|
||||
},
|
||||
[REG_TIMING] = {
|
||||
.name = "timing",
|
||||
.desc = ddr_timing,
|
||||
.size = DDRCTL_REG_TIMING_SIZE,
|
||||
.base = DDR_BASE
|
||||
},
|
||||
[REG_PERF] = {
|
||||
.name = "perf",
|
||||
.desc = ddr_perf,
|
||||
.size = DDRCTL_REG_PERF_SIZE,
|
||||
.base = DDR_BASE
|
||||
},
|
||||
[REG_MAP] = {
|
||||
.name = "map",
|
||||
.desc = ddr_map,
|
||||
.size = DDRCTL_REG_MAP_SIZE,
|
||||
.base = DDR_BASE
|
||||
},
|
||||
[REGPHY_REG] = {
|
||||
.name = "static",
|
||||
.desc = ddrphy_reg,
|
||||
.size = DDRPHY_REG_REG_SIZE,
|
||||
.base = DDRPHY_BASE
|
||||
},
|
||||
[REGPHY_TIMING] = {
|
||||
.name = "timing",
|
||||
.desc = ddrphy_timing,
|
||||
.size = DDRPHY_REG_TIMING_SIZE,
|
||||
.base = DDRPHY_BASE
|
||||
},
|
||||
};
|
||||
|
||||
static void stm32mp1_ddrphy_idone_wait(struct stm32mp_ddrphy *phy)
|
||||
{
|
||||
uint32_t pgsr;
|
||||
int error = 0;
|
||||
uint64_t timeout = timeout_init_us(TIMEOUT_US_1S);
|
||||
|
||||
do {
|
||||
pgsr = mmio_read_32((uintptr_t)&phy->pgsr);
|
||||
|
||||
VERBOSE(" > [0x%lx] pgsr = 0x%x &\n",
|
||||
(uintptr_t)&phy->pgsr, pgsr);
|
||||
|
||||
if (timeout_elapsed(timeout)) {
|
||||
panic();
|
||||
}
|
||||
|
||||
if ((pgsr & DDRPHYC_PGSR_DTERR) != 0U) {
|
||||
VERBOSE("DQS Gate Trainig Error\n");
|
||||
error++;
|
||||
}
|
||||
|
||||
if ((pgsr & DDRPHYC_PGSR_DTIERR) != 0U) {
|
||||
VERBOSE("DQS Gate Trainig Intermittent Error\n");
|
||||
error++;
|
||||
}
|
||||
|
||||
if ((pgsr & DDRPHYC_PGSR_DFTERR) != 0U) {
|
||||
VERBOSE("DQS Drift Error\n");
|
||||
error++;
|
||||
}
|
||||
|
||||
if ((pgsr & DDRPHYC_PGSR_RVERR) != 0U) {
|
||||
VERBOSE("Read Valid Training Error\n");
|
||||
error++;
|
||||
}
|
||||
|
||||
if ((pgsr & DDRPHYC_PGSR_RVEIRR) != 0U) {
|
||||
VERBOSE("Read Valid Training Intermittent Error\n");
|
||||
error++;
|
||||
}
|
||||
} while (((pgsr & DDRPHYC_PGSR_IDONE) == 0U) && (error == 0));
|
||||
VERBOSE("\n[0x%lx] pgsr = 0x%x\n",
|
||||
(uintptr_t)&phy->pgsr, pgsr);
|
||||
}
|
||||
|
||||
static void stm32mp1_ddrphy_init(struct stm32mp_ddrphy *phy, uint32_t pir)
|
||||
{
|
||||
uint32_t pir_init = pir | DDRPHYC_PIR_INIT;
|
||||
|
||||
mmio_write_32((uintptr_t)&phy->pir, pir_init);
|
||||
VERBOSE("[0x%lx] pir = 0x%x -> 0x%x\n",
|
||||
(uintptr_t)&phy->pir, pir_init,
|
||||
mmio_read_32((uintptr_t)&phy->pir));
|
||||
|
||||
/* Need to wait 10 configuration clock before start polling */
|
||||
udelay(10);
|
||||
|
||||
/* Wait DRAM initialization and Gate Training Evaluation complete */
|
||||
stm32mp1_ddrphy_idone_wait(phy);
|
||||
}
|
||||
|
||||
/* Wait quasi dynamic register update */
|
||||
static void stm32mp1_wait_operating_mode(struct stm32mp_ddr_priv *priv, uint32_t mode)
|
||||
{
|
||||
uint64_t timeout;
|
||||
uint32_t stat;
|
||||
int break_loop = 0;
|
||||
|
||||
timeout = timeout_init_us(TIMEOUT_US_1S);
|
||||
for ( ; ; ) {
|
||||
uint32_t operating_mode;
|
||||
uint32_t selref_type;
|
||||
|
||||
stat = mmio_read_32((uintptr_t)&priv->ctl->stat);
|
||||
operating_mode = stat & DDRCTRL_STAT_OPERATING_MODE_MASK;
|
||||
selref_type = stat & DDRCTRL_STAT_SELFREF_TYPE_MASK;
|
||||
VERBOSE("[0x%lx] stat = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->stat, stat);
|
||||
if (timeout_elapsed(timeout)) {
|
||||
panic();
|
||||
}
|
||||
|
||||
if (mode == DDRCTRL_STAT_OPERATING_MODE_SR) {
|
||||
/*
|
||||
* Self-refresh due to software
|
||||
* => checking also STAT.selfref_type.
|
||||
*/
|
||||
if ((operating_mode ==
|
||||
DDRCTRL_STAT_OPERATING_MODE_SR) &&
|
||||
(selref_type == DDRCTRL_STAT_SELFREF_TYPE_SR)) {
|
||||
break_loop = 1;
|
||||
}
|
||||
} else if (operating_mode == mode) {
|
||||
break_loop = 1;
|
||||
} else if ((mode == DDRCTRL_STAT_OPERATING_MODE_NORMAL) &&
|
||||
(operating_mode == DDRCTRL_STAT_OPERATING_MODE_SR) &&
|
||||
(selref_type == DDRCTRL_STAT_SELFREF_TYPE_ASR)) {
|
||||
/* Normal mode: handle also automatic self refresh */
|
||||
break_loop = 1;
|
||||
}
|
||||
|
||||
if (break_loop == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VERBOSE("[0x%lx] stat = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->stat, stat);
|
||||
}
|
||||
|
||||
/* Mode Register Writes (MRW or MRS) */
|
||||
static void stm32mp1_mode_register_write(struct stm32mp_ddr_priv *priv, uint8_t addr,
|
||||
uint32_t data)
|
||||
{
|
||||
uint32_t mrctrl0;
|
||||
|
||||
VERBOSE("MRS: %d = %x\n", addr, data);
|
||||
|
||||
/*
|
||||
* 1. Poll MRSTAT.mr_wr_busy until it is '0'.
|
||||
* This checks that there is no outstanding MR transaction.
|
||||
* No write should be performed to MRCTRL0 and MRCTRL1
|
||||
* if MRSTAT.mr_wr_busy = 1.
|
||||
*/
|
||||
while ((mmio_read_32((uintptr_t)&priv->ctl->mrstat) &
|
||||
DDRCTRL_MRSTAT_MR_WR_BUSY) != 0U) {
|
||||
;
|
||||
}
|
||||
|
||||
/*
|
||||
* 2. Write the MRCTRL0.mr_type, MRCTRL0.mr_addr, MRCTRL0.mr_rank
|
||||
* and (for MRWs) MRCTRL1.mr_data to define the MR transaction.
|
||||
*/
|
||||
mrctrl0 = DDRCTRL_MRCTRL0_MR_TYPE_WRITE |
|
||||
DDRCTRL_MRCTRL0_MR_RANK_ALL |
|
||||
(((uint32_t)addr << DDRCTRL_MRCTRL0_MR_ADDR_SHIFT) &
|
||||
DDRCTRL_MRCTRL0_MR_ADDR_MASK);
|
||||
mmio_write_32((uintptr_t)&priv->ctl->mrctrl0, mrctrl0);
|
||||
VERBOSE("[0x%lx] mrctrl0 = 0x%x (0x%x)\n",
|
||||
(uintptr_t)&priv->ctl->mrctrl0,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->mrctrl0), mrctrl0);
|
||||
mmio_write_32((uintptr_t)&priv->ctl->mrctrl1, data);
|
||||
VERBOSE("[0x%lx] mrctrl1 = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->mrctrl1,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->mrctrl1));
|
||||
|
||||
/*
|
||||
* 3. In a separate APB transaction, write the MRCTRL0.mr_wr to 1. This
|
||||
* bit is self-clearing, and triggers the MR transaction.
|
||||
* The uMCTL2 then asserts the MRSTAT.mr_wr_busy while it performs
|
||||
* the MR transaction to SDRAM, and no further access can be
|
||||
* initiated until it is deasserted.
|
||||
*/
|
||||
mrctrl0 |= DDRCTRL_MRCTRL0_MR_WR;
|
||||
mmio_write_32((uintptr_t)&priv->ctl->mrctrl0, mrctrl0);
|
||||
|
||||
while ((mmio_read_32((uintptr_t)&priv->ctl->mrstat) &
|
||||
DDRCTRL_MRSTAT_MR_WR_BUSY) != 0U) {
|
||||
;
|
||||
}
|
||||
|
||||
VERBOSE("[0x%lx] mrctrl0 = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->mrctrl0, mrctrl0);
|
||||
}
|
||||
|
||||
/* Switch DDR3 from DLL-on to DLL-off */
|
||||
static void stm32mp1_ddr3_dll_off(struct stm32mp_ddr_priv *priv)
|
||||
{
|
||||
uint32_t mr1 = mmio_read_32((uintptr_t)&priv->phy->mr1);
|
||||
uint32_t mr2 = mmio_read_32((uintptr_t)&priv->phy->mr2);
|
||||
uint32_t dbgcam;
|
||||
|
||||
VERBOSE("mr1: 0x%x\n", mr1);
|
||||
VERBOSE("mr2: 0x%x\n", mr2);
|
||||
|
||||
/*
|
||||
* 1. Set the DBG1.dis_hif = 1.
|
||||
* This prevents further reads/writes being received on the HIF.
|
||||
*/
|
||||
mmio_setbits_32((uintptr_t)&priv->ctl->dbg1, DDRCTRL_DBG1_DIS_HIF);
|
||||
VERBOSE("[0x%lx] dbg1 = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->dbg1,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->dbg1));
|
||||
|
||||
/*
|
||||
* 2. Ensure all commands have been flushed from the uMCTL2 by polling
|
||||
* DBGCAM.wr_data_pipeline_empty = 1,
|
||||
* DBGCAM.rd_data_pipeline_empty = 1,
|
||||
* DBGCAM.dbg_wr_q_depth = 0 ,
|
||||
* DBGCAM.dbg_lpr_q_depth = 0, and
|
||||
* DBGCAM.dbg_hpr_q_depth = 0.
|
||||
*/
|
||||
do {
|
||||
dbgcam = mmio_read_32((uintptr_t)&priv->ctl->dbgcam);
|
||||
VERBOSE("[0x%lx] dbgcam = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->dbgcam, dbgcam);
|
||||
} while ((((dbgcam & DDRCTRL_DBGCAM_DATA_PIPELINE_EMPTY) ==
|
||||
DDRCTRL_DBGCAM_DATA_PIPELINE_EMPTY)) &&
|
||||
((dbgcam & DDRCTRL_DBGCAM_DBG_Q_DEPTH) == 0U));
|
||||
|
||||
/*
|
||||
* 3. Perform an MRS command (using MRCTRL0 and MRCTRL1 registers)
|
||||
* to disable RTT_NOM:
|
||||
* a. DDR3: Write to MR1[9], MR1[6] and MR1[2]
|
||||
* b. DDR4: Write to MR1[10:8]
|
||||
*/
|
||||
mr1 &= ~(BIT(9) | BIT(6) | BIT(2));
|
||||
stm32mp1_mode_register_write(priv, 1, mr1);
|
||||
|
||||
/*
|
||||
* 4. For DDR4 only: Perform an MRS command
|
||||
* (using MRCTRL0 and MRCTRL1 registers) to write to MR5[8:6]
|
||||
* to disable RTT_PARK
|
||||
*/
|
||||
|
||||
/*
|
||||
* 5. Perform an MRS command (using MRCTRL0 and MRCTRL1 registers)
|
||||
* to write to MR2[10:9], to disable RTT_WR
|
||||
* (and therefore disable dynamic ODT).
|
||||
* This applies for both DDR3 and DDR4.
|
||||
*/
|
||||
mr2 &= ~GENMASK(10, 9);
|
||||
stm32mp1_mode_register_write(priv, 2, mr2);
|
||||
|
||||
/*
|
||||
* 6. Perform an MRS command (using MRCTRL0 and MRCTRL1 registers)
|
||||
* to disable the DLL. The timing of this MRS is automatically
|
||||
* handled by the uMCTL2.
|
||||
* a. DDR3: Write to MR1[0]
|
||||
* b. DDR4: Write to MR1[0]
|
||||
*/
|
||||
mr1 |= BIT(0);
|
||||
stm32mp1_mode_register_write(priv, 1, mr1);
|
||||
|
||||
/*
|
||||
* 7. Put the SDRAM into self-refresh mode by setting
|
||||
* PWRCTL.selfref_sw = 1, and polling STAT.operating_mode to ensure
|
||||
* the DDRC has entered self-refresh.
|
||||
*/
|
||||
mmio_setbits_32((uintptr_t)&priv->ctl->pwrctl,
|
||||
DDRCTRL_PWRCTL_SELFREF_SW);
|
||||
VERBOSE("[0x%lx] pwrctl = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->pwrctl,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->pwrctl));
|
||||
|
||||
/*
|
||||
* 8. Wait until STAT.operating_mode[1:0]==11 indicating that the
|
||||
* DWC_ddr_umctl2 core is in self-refresh mode.
|
||||
* Ensure transition to self-refresh was due to software
|
||||
* by checking that STAT.selfref_type[1:0]=2.
|
||||
*/
|
||||
stm32mp1_wait_operating_mode(priv, DDRCTRL_STAT_OPERATING_MODE_SR);
|
||||
|
||||
/*
|
||||
* 9. Set the MSTR.dll_off_mode = 1.
|
||||
* warning: MSTR.dll_off_mode is a quasi-dynamic type 2 field
|
||||
*/
|
||||
stm32mp_ddr_start_sw_done(priv->ctl);
|
||||
|
||||
mmio_setbits_32((uintptr_t)&priv->ctl->mstr, DDRCTRL_MSTR_DLL_OFF_MODE);
|
||||
VERBOSE("[0x%lx] mstr = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->mstr,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->mstr));
|
||||
|
||||
stm32mp_ddr_wait_sw_done_ack(priv->ctl);
|
||||
|
||||
/* 10. Change the clock frequency to the desired value. */
|
||||
|
||||
/*
|
||||
* 11. Update any registers which may be required to change for the new
|
||||
* frequency. This includes static and dynamic registers.
|
||||
* This includes both uMCTL2 registers and PHY registers.
|
||||
*/
|
||||
|
||||
/* Change Bypass Mode Frequency Range */
|
||||
if (clk_get_rate(DDRPHYC) < 100000000U) {
|
||||
mmio_clrbits_32((uintptr_t)&priv->phy->dllgcr,
|
||||
DDRPHYC_DLLGCR_BPS200);
|
||||
} else {
|
||||
mmio_setbits_32((uintptr_t)&priv->phy->dllgcr,
|
||||
DDRPHYC_DLLGCR_BPS200);
|
||||
}
|
||||
|
||||
mmio_setbits_32((uintptr_t)&priv->phy->acdllcr, DDRPHYC_ACDLLCR_DLLDIS);
|
||||
|
||||
mmio_setbits_32((uintptr_t)&priv->phy->dx0dllcr,
|
||||
DDRPHYC_DXNDLLCR_DLLDIS);
|
||||
mmio_setbits_32((uintptr_t)&priv->phy->dx1dllcr,
|
||||
DDRPHYC_DXNDLLCR_DLLDIS);
|
||||
#if STM32MP_DDR_32BIT_INTERFACE
|
||||
mmio_setbits_32((uintptr_t)&priv->phy->dx2dllcr,
|
||||
DDRPHYC_DXNDLLCR_DLLDIS);
|
||||
mmio_setbits_32((uintptr_t)&priv->phy->dx3dllcr,
|
||||
DDRPHYC_DXNDLLCR_DLLDIS);
|
||||
#endif
|
||||
|
||||
/* 12. Exit the self-refresh state by setting PWRCTL.selfref_sw = 0. */
|
||||
mmio_clrbits_32((uintptr_t)&priv->ctl->pwrctl,
|
||||
DDRCTRL_PWRCTL_SELFREF_SW);
|
||||
stm32mp1_wait_operating_mode(priv, DDRCTRL_STAT_OPERATING_MODE_NORMAL);
|
||||
|
||||
/*
|
||||
* 13. If ZQCTL0.dis_srx_zqcl = 0, the uMCTL2 performs a ZQCL command
|
||||
* at this point.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 14. Perform MRS commands as required to re-program timing registers
|
||||
* in the SDRAM for the new frequency
|
||||
* (in particular, CL, CWL and WR may need to be changed).
|
||||
*/
|
||||
|
||||
/* 15. Write DBG1.dis_hif = 0 to re-enable reads and writes. */
|
||||
mmio_clrbits_32((uintptr_t)&priv->ctl->dbg1, DDRCTRL_DBG1_DIS_HIF);
|
||||
VERBOSE("[0x%lx] dbg1 = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->dbg1,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->dbg1));
|
||||
}
|
||||
|
||||
static void stm32mp1_refresh_disable(struct stm32mp_ddrctl *ctl)
|
||||
{
|
||||
stm32mp_ddr_start_sw_done(ctl);
|
||||
/* Quasi-dynamic register update*/
|
||||
mmio_setbits_32((uintptr_t)&ctl->rfshctl3,
|
||||
DDRCTRL_RFSHCTL3_DIS_AUTO_REFRESH);
|
||||
mmio_clrbits_32((uintptr_t)&ctl->pwrctl, DDRCTRL_PWRCTL_POWERDOWN_EN);
|
||||
mmio_clrbits_32((uintptr_t)&ctl->dfimisc,
|
||||
DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN);
|
||||
stm32mp_ddr_wait_sw_done_ack(ctl);
|
||||
}
|
||||
|
||||
static void stm32mp1_refresh_restore(struct stm32mp_ddrctl *ctl,
|
||||
uint32_t rfshctl3, uint32_t pwrctl)
|
||||
{
|
||||
stm32mp_ddr_start_sw_done(ctl);
|
||||
if ((rfshctl3 & DDRCTRL_RFSHCTL3_DIS_AUTO_REFRESH) == 0U) {
|
||||
mmio_clrbits_32((uintptr_t)&ctl->rfshctl3,
|
||||
DDRCTRL_RFSHCTL3_DIS_AUTO_REFRESH);
|
||||
}
|
||||
if ((pwrctl & DDRCTRL_PWRCTL_POWERDOWN_EN) != 0U) {
|
||||
mmio_setbits_32((uintptr_t)&ctl->pwrctl,
|
||||
DDRCTRL_PWRCTL_POWERDOWN_EN);
|
||||
}
|
||||
mmio_setbits_32((uintptr_t)&ctl->dfimisc,
|
||||
DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN);
|
||||
stm32mp_ddr_wait_sw_done_ack(ctl);
|
||||
}
|
||||
|
||||
void stm32mp1_ddr_init(struct stm32mp_ddr_priv *priv,
|
||||
struct stm32mp_ddr_config *config)
|
||||
{
|
||||
uint32_t pir;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if ((config->c_reg.mstr & DDRCTRL_MSTR_DDR3) != 0U) {
|
||||
ret = stm32mp_board_ddr_power_init(STM32MP_DDR3);
|
||||
} else if ((config->c_reg.mstr & DDRCTRL_MSTR_LPDDR2) != 0U) {
|
||||
ret = stm32mp_board_ddr_power_init(STM32MP_LPDDR2);
|
||||
} else if ((config->c_reg.mstr & DDRCTRL_MSTR_LPDDR3) != 0U) {
|
||||
ret = stm32mp_board_ddr_power_init(STM32MP_LPDDR3);
|
||||
} else {
|
||||
ERROR("DDR type not supported\n");
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
VERBOSE("name = %s\n", config->info.name);
|
||||
VERBOSE("speed = %u kHz\n", config->info.speed);
|
||||
VERBOSE("size = 0x%x\n", config->info.size);
|
||||
|
||||
/* DDR INIT SEQUENCE */
|
||||
|
||||
/*
|
||||
* 1. Program the DWC_ddr_umctl2 registers
|
||||
* nota: check DFIMISC.dfi_init_complete = 0
|
||||
*/
|
||||
|
||||
/* 1.1 RESETS: presetn, core_ddrc_rstn, aresetn */
|
||||
mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAPBRST);
|
||||
mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAXIRST);
|
||||
mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCORERST);
|
||||
mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYAPBRST);
|
||||
mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYRST);
|
||||
mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYCTLRST);
|
||||
|
||||
/* 1.2. start CLOCK */
|
||||
if (stm32mp1_ddr_clk_enable(priv, config->info.speed) != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
/* 1.3. deassert reset */
|
||||
/* De-assert PHY rstn and ctl_rstn via DPHYRST and DPHYCTLRST. */
|
||||
mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYRST);
|
||||
mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYCTLRST);
|
||||
/*
|
||||
* De-assert presetn once the clocks are active
|
||||
* and stable via DDRCAPBRST bit.
|
||||
*/
|
||||
mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAPBRST);
|
||||
|
||||
/* 1.4. wait 128 cycles to permit initialization of end logic */
|
||||
udelay(2);
|
||||
/* For PCLK = 133MHz => 1 us is enough, 2 to allow lower frequency */
|
||||
|
||||
/* 1.5. initialize registers ddr_umctl2 */
|
||||
/* Stop uMCTL2 before PHY is ready */
|
||||
mmio_clrbits_32((uintptr_t)&priv->ctl->dfimisc,
|
||||
DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN);
|
||||
VERBOSE("[0x%lx] dfimisc = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->dfimisc,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->dfimisc));
|
||||
|
||||
stm32mp_ddr_set_reg(priv, REG_REG, &config->c_reg, ddr_registers);
|
||||
|
||||
/* DDR3 = don't set DLLOFF for init mode */
|
||||
if ((config->c_reg.mstr &
|
||||
(DDRCTRL_MSTR_DDR3 | DDRCTRL_MSTR_DLL_OFF_MODE))
|
||||
== (DDRCTRL_MSTR_DDR3 | DDRCTRL_MSTR_DLL_OFF_MODE)) {
|
||||
VERBOSE("deactivate DLL OFF in mstr\n");
|
||||
mmio_clrbits_32((uintptr_t)&priv->ctl->mstr,
|
||||
DDRCTRL_MSTR_DLL_OFF_MODE);
|
||||
VERBOSE("[0x%lx] mstr = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->mstr,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->mstr));
|
||||
}
|
||||
|
||||
stm32mp_ddr_set_reg(priv, REG_TIMING, &config->c_timing, ddr_registers);
|
||||
stm32mp_ddr_set_reg(priv, REG_MAP, &config->c_map, ddr_registers);
|
||||
|
||||
/* Skip CTRL init, SDRAM init is done by PHY PUBL */
|
||||
mmio_clrsetbits_32((uintptr_t)&priv->ctl->init0,
|
||||
DDRCTRL_INIT0_SKIP_DRAM_INIT_MASK,
|
||||
DDRCTRL_INIT0_SKIP_DRAM_INIT_NORMAL);
|
||||
VERBOSE("[0x%lx] init0 = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->init0,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->init0));
|
||||
|
||||
stm32mp_ddr_set_reg(priv, REG_PERF, &config->c_perf, ddr_registers);
|
||||
|
||||
/* 2. deassert reset signal core_ddrc_rstn, aresetn and presetn */
|
||||
mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCORERST);
|
||||
mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAXIRST);
|
||||
mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYAPBRST);
|
||||
|
||||
/*
|
||||
* 3. start PHY init by accessing relevant PUBL registers
|
||||
* (DXGCR, DCR, PTR*, MR*, DTPR*)
|
||||
*/
|
||||
stm32mp_ddr_set_reg(priv, REGPHY_REG, &config->p_reg, ddr_registers);
|
||||
stm32mp_ddr_set_reg(priv, REGPHY_TIMING, &config->p_timing, ddr_registers);
|
||||
|
||||
/* DDR3 = don't set DLLOFF for init mode */
|
||||
if ((config->c_reg.mstr &
|
||||
(DDRCTRL_MSTR_DDR3 | DDRCTRL_MSTR_DLL_OFF_MODE))
|
||||
== (DDRCTRL_MSTR_DDR3 | DDRCTRL_MSTR_DLL_OFF_MODE)) {
|
||||
VERBOSE("deactivate DLL OFF in mr1\n");
|
||||
mmio_clrbits_32((uintptr_t)&priv->phy->mr1, BIT(0));
|
||||
VERBOSE("[0x%lx] mr1 = 0x%x\n",
|
||||
(uintptr_t)&priv->phy->mr1,
|
||||
mmio_read_32((uintptr_t)&priv->phy->mr1));
|
||||
}
|
||||
|
||||
/*
|
||||
* 4. Monitor PHY init status by polling PUBL register PGSR.IDONE
|
||||
* Perform DDR PHY DRAM initialization and Gate Training Evaluation
|
||||
*/
|
||||
stm32mp1_ddrphy_idone_wait(priv->phy);
|
||||
|
||||
/*
|
||||
* 5. Indicate to PUBL that controller performs SDRAM initialization
|
||||
* by setting PIR.INIT and PIR CTLDINIT and pool PGSR.IDONE
|
||||
* DRAM init is done by PHY, init0.skip_dram.init = 1
|
||||
*/
|
||||
|
||||
pir = DDRPHYC_PIR_DLLSRST | DDRPHYC_PIR_DLLLOCK | DDRPHYC_PIR_ZCAL |
|
||||
DDRPHYC_PIR_ITMSRST | DDRPHYC_PIR_DRAMINIT | DDRPHYC_PIR_ICPC;
|
||||
|
||||
if ((config->c_reg.mstr & DDRCTRL_MSTR_DDR3) != 0U) {
|
||||
pir |= DDRPHYC_PIR_DRAMRST; /* Only for DDR3 */
|
||||
}
|
||||
|
||||
stm32mp1_ddrphy_init(priv->phy, pir);
|
||||
|
||||
/*
|
||||
* 6. SET DFIMISC.dfi_init_complete_en to 1
|
||||
* Enable quasi-dynamic register programming.
|
||||
*/
|
||||
stm32mp_ddr_start_sw_done(priv->ctl);
|
||||
|
||||
mmio_setbits_32((uintptr_t)&priv->ctl->dfimisc,
|
||||
DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN);
|
||||
VERBOSE("[0x%lx] dfimisc = 0x%x\n",
|
||||
(uintptr_t)&priv->ctl->dfimisc,
|
||||
mmio_read_32((uintptr_t)&priv->ctl->dfimisc));
|
||||
|
||||
stm32mp_ddr_wait_sw_done_ack(priv->ctl);
|
||||
|
||||
/*
|
||||
* 7. Wait for DWC_ddr_umctl2 to move to normal operation mode
|
||||
* by monitoring STAT.operating_mode signal
|
||||
*/
|
||||
|
||||
/* Wait uMCTL2 ready */
|
||||
stm32mp1_wait_operating_mode(priv, DDRCTRL_STAT_OPERATING_MODE_NORMAL);
|
||||
|
||||
/* Switch to DLL OFF mode */
|
||||
if ((config->c_reg.mstr & DDRCTRL_MSTR_DLL_OFF_MODE) != 0U) {
|
||||
stm32mp1_ddr3_dll_off(priv);
|
||||
}
|
||||
|
||||
VERBOSE("DDR DQS training : ");
|
||||
|
||||
/*
|
||||
* 8. Disable Auto refresh and power down by setting
|
||||
* - RFSHCTL3.dis_au_refresh = 1
|
||||
* - PWRCTL.powerdown_en = 0
|
||||
* - DFIMISC.dfiinit_complete_en = 0
|
||||
*/
|
||||
stm32mp1_refresh_disable(priv->ctl);
|
||||
|
||||
/*
|
||||
* 9. Program PUBL PGCR to enable refresh during training
|
||||
* and rank to train
|
||||
* not done => keep the programed value in PGCR
|
||||
*/
|
||||
|
||||
/*
|
||||
* 10. configure PUBL PIR register to specify which training step
|
||||
* to run
|
||||
* RVTRN is executed only on LPDDR2/LPDDR3
|
||||
*/
|
||||
pir = DDRPHYC_PIR_QSTRN;
|
||||
if ((config->c_reg.mstr & DDRCTRL_MSTR_DDR3) == 0U) {
|
||||
pir |= DDRPHYC_PIR_RVTRN;
|
||||
}
|
||||
|
||||
stm32mp1_ddrphy_init(priv->phy, pir);
|
||||
|
||||
/* 11. monitor PUB PGSR.IDONE to poll cpmpletion of training sequence */
|
||||
stm32mp1_ddrphy_idone_wait(priv->phy);
|
||||
|
||||
/*
|
||||
* 12. set back registers in step 8 to the orginal values if desidered
|
||||
*/
|
||||
stm32mp1_refresh_restore(priv->ctl, config->c_reg.rfshctl3,
|
||||
config->c_reg.pwrctl);
|
||||
|
||||
stm32mp_ddr_enable_axi_port(priv->ctl);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <drivers/st/stm32mp1_ddr_helpers.h>
|
||||
#include <lib/mmio.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
void ddr_enable_clock(void)
|
||||
{
|
||||
stm32mp1_clk_rcc_regs_lock();
|
||||
|
||||
mmio_setbits_32(stm32mp_rcc_base() + RCC_DDRITFCR,
|
||||
RCC_DDRITFCR_DDRC1EN |
|
||||
#if STM32MP_DDR_DUAL_AXI_PORT
|
||||
RCC_DDRITFCR_DDRC2EN |
|
||||
#endif
|
||||
RCC_DDRITFCR_DDRPHYCEN |
|
||||
RCC_DDRITFCR_DDRPHYCAPBEN |
|
||||
RCC_DDRITFCR_DDRCAPBEN);
|
||||
|
||||
stm32mp1_clk_rcc_regs_unlock();
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <arch_helpers.h>
|
||||
#include <common/debug.h>
|
||||
#include <common/fdt_wrappers.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/st/stm32mp1_ddr.h>
|
||||
#include <drivers/st/stm32mp1_ddr_helpers.h>
|
||||
#include <drivers/st/stm32mp1_ram.h>
|
||||
#include <drivers/st/stm32mp_ddr.h>
|
||||
#include <drivers/st/stm32mp_ddr_test.h>
|
||||
#include <drivers/st/stm32mp_ram.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
static struct stm32mp_ddr_priv ddr_priv_data;
|
||||
|
||||
int stm32mp1_ddr_clk_enable(struct stm32mp_ddr_priv *priv, uint32_t mem_speed)
|
||||
{
|
||||
unsigned long ddrphy_clk, ddr_clk, mem_speed_hz;
|
||||
|
||||
ddr_enable_clock();
|
||||
|
||||
ddrphy_clk = clk_get_rate(DDRPHYC);
|
||||
|
||||
VERBOSE("DDR: mem_speed (%u kHz), RCC %lu kHz\n",
|
||||
mem_speed, ddrphy_clk / 1000U);
|
||||
|
||||
mem_speed_hz = mem_speed * 1000U;
|
||||
|
||||
/* Max 10% frequency delta */
|
||||
if (ddrphy_clk > mem_speed_hz) {
|
||||
ddr_clk = ddrphy_clk - mem_speed_hz;
|
||||
} else {
|
||||
ddr_clk = mem_speed_hz - ddrphy_clk;
|
||||
}
|
||||
if (ddr_clk > (mem_speed_hz / 10)) {
|
||||
ERROR("DDR expected freq %u kHz, current is %lu kHz\n",
|
||||
mem_speed, ddrphy_clk / 1000U);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32mp1_ddr_setup(void)
|
||||
{
|
||||
struct stm32mp_ddr_priv *priv = &ddr_priv_data;
|
||||
int ret;
|
||||
struct stm32mp_ddr_config config;
|
||||
int node;
|
||||
uint32_t uret;
|
||||
void *fdt;
|
||||
|
||||
const struct stm32mp_ddr_param param[] = {
|
||||
CTL_PARAM(reg),
|
||||
CTL_PARAM(timing),
|
||||
CTL_PARAM(map),
|
||||
CTL_PARAM(perf),
|
||||
PHY_PARAM(reg),
|
||||
PHY_PARAM(timing),
|
||||
};
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
node = fdt_node_offset_by_compatible(fdt, -1, DT_DDR_COMPAT);
|
||||
if (node < 0) {
|
||||
ERROR("%s: Cannot read DDR node in DT\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = stm32mp_ddr_dt_get_info(fdt, node, &config.info);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = stm32mp_ddr_dt_get_param(fdt, node, param, ARRAY_SIZE(param), (uintptr_t)&config);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable axidcg clock gating during init */
|
||||
mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_AXIDCGEN);
|
||||
|
||||
stm32mp1_ddr_init(priv, &config);
|
||||
|
||||
/* Enable axidcg clock gating */
|
||||
mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_AXIDCGEN);
|
||||
|
||||
priv->info.size = config.info.size;
|
||||
|
||||
VERBOSE("%s : ram size(%x, %x)\n", __func__,
|
||||
(uint32_t)priv->info.base, (uint32_t)priv->info.size);
|
||||
|
||||
if (stm32mp_map_ddr_non_cacheable() != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
uret = stm32mp_ddr_test_data_bus();
|
||||
if (uret != 0U) {
|
||||
ERROR("DDR data bus test: can't access memory @ 0x%x\n",
|
||||
uret);
|
||||
panic();
|
||||
}
|
||||
|
||||
uret = stm32mp_ddr_test_addr_bus(config.info.size);
|
||||
if (uret != 0U) {
|
||||
ERROR("DDR addr bus test: can't access memory @ 0x%x\n",
|
||||
uret);
|
||||
panic();
|
||||
}
|
||||
|
||||
uret = stm32mp_ddr_check_size();
|
||||
if (uret < config.info.size) {
|
||||
ERROR("DDR size: 0x%x does not match DT config: 0x%x\n",
|
||||
uret, config.info.size);
|
||||
panic();
|
||||
}
|
||||
|
||||
if (stm32mp_unmap_ddr() != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stm32mp1_ddr_probe(void)
|
||||
{
|
||||
struct stm32mp_ddr_priv *priv = &ddr_priv_data;
|
||||
|
||||
VERBOSE("STM32MP DDR probe\n");
|
||||
|
||||
priv->ctl = (struct stm32mp_ddrctl *)stm32mp_ddrctrl_base();
|
||||
priv->phy = (struct stm32mp_ddrphy *)stm32mp_ddrphyc_base();
|
||||
priv->pwr = stm32mp_pwr_base();
|
||||
priv->rcc = stm32mp_rcc_base();
|
||||
|
||||
priv->info.base = STM32MP_DDR_BASE;
|
||||
priv->info.size = 0;
|
||||
|
||||
return stm32mp1_ddr_setup();
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32mp_ddr.h>
|
||||
#include <drivers/st/stm32mp_ddrctrl_regs.h>
|
||||
#include <drivers/st/stm32mp_pmic.h>
|
||||
#include <lib/mmio.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#define INVALID_OFFSET 0xFFU
|
||||
|
||||
static uintptr_t get_base_addr(const struct stm32mp_ddr_priv *priv, enum stm32mp_ddr_base_type base)
|
||||
{
|
||||
if (base == DDRPHY_BASE) {
|
||||
return (uintptr_t)priv->phy;
|
||||
} else {
|
||||
return (uintptr_t)priv->ctl;
|
||||
}
|
||||
}
|
||||
|
||||
void stm32mp_ddr_set_reg(const struct stm32mp_ddr_priv *priv, enum stm32mp_ddr_reg_type type,
|
||||
const void *param, const struct stm32mp_ddr_reg_info *ddr_registers)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int value;
|
||||
enum stm32mp_ddr_base_type base = ddr_registers[type].base;
|
||||
uintptr_t base_addr = get_base_addr(priv, base);
|
||||
const struct stm32mp_ddr_reg_desc *desc = ddr_registers[type].desc;
|
||||
|
||||
VERBOSE("init %s\n", ddr_registers[type].name);
|
||||
for (i = 0; i < ddr_registers[type].size; i++) {
|
||||
uintptr_t ptr = base_addr + desc[i].offset;
|
||||
|
||||
if (desc[i].par_offset == INVALID_OFFSET) {
|
||||
ERROR("invalid parameter offset for %s", desc[i].name);
|
||||
panic();
|
||||
} else {
|
||||
value = *((uint32_t *)((uintptr_t)param +
|
||||
desc[i].par_offset));
|
||||
mmio_write_32(ptr, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Start quasi dynamic register update */
|
||||
void stm32mp_ddr_start_sw_done(struct stm32mp_ddrctl *ctl)
|
||||
{
|
||||
mmio_clrbits_32((uintptr_t)&ctl->swctl, DDRCTRL_SWCTL_SW_DONE);
|
||||
VERBOSE("[0x%lx] swctl = 0x%x\n",
|
||||
(uintptr_t)&ctl->swctl, mmio_read_32((uintptr_t)&ctl->swctl));
|
||||
}
|
||||
|
||||
/* Wait quasi dynamic register update */
|
||||
void stm32mp_ddr_wait_sw_done_ack(struct stm32mp_ddrctl *ctl)
|
||||
{
|
||||
uint64_t timeout;
|
||||
uint32_t swstat;
|
||||
|
||||
mmio_setbits_32((uintptr_t)&ctl->swctl, DDRCTRL_SWCTL_SW_DONE);
|
||||
VERBOSE("[0x%lx] swctl = 0x%x\n",
|
||||
(uintptr_t)&ctl->swctl, mmio_read_32((uintptr_t)&ctl->swctl));
|
||||
|
||||
timeout = timeout_init_us(TIMEOUT_US_1S);
|
||||
do {
|
||||
swstat = mmio_read_32((uintptr_t)&ctl->swstat);
|
||||
VERBOSE("[0x%lx] swstat = 0x%x ",
|
||||
(uintptr_t)&ctl->swstat, swstat);
|
||||
if (timeout_elapsed(timeout)) {
|
||||
panic();
|
||||
}
|
||||
} while ((swstat & DDRCTRL_SWSTAT_SW_DONE_ACK) == 0U);
|
||||
|
||||
VERBOSE("[0x%lx] swstat = 0x%x\n",
|
||||
(uintptr_t)&ctl->swstat, swstat);
|
||||
}
|
||||
|
||||
void stm32mp_ddr_enable_axi_port(struct stm32mp_ddrctl *ctl)
|
||||
{
|
||||
/* Enable uMCTL2 AXI port 0 */
|
||||
mmio_setbits_32((uintptr_t)&ctl->pctrl_0, DDRCTRL_PCTRL_N_PORT_EN);
|
||||
VERBOSE("[0x%lx] pctrl_0 = 0x%x\n", (uintptr_t)&ctl->pctrl_0,
|
||||
mmio_read_32((uintptr_t)&ctl->pctrl_0));
|
||||
|
||||
#if STM32MP_DDR_DUAL_AXI_PORT
|
||||
/* Enable uMCTL2 AXI port 1 */
|
||||
mmio_setbits_32((uintptr_t)&ctl->pctrl_1, DDRCTRL_PCTRL_N_PORT_EN);
|
||||
VERBOSE("[0x%lx] pctrl_1 = 0x%x\n", (uintptr_t)&ctl->pctrl_1,
|
||||
mmio_read_32((uintptr_t)&ctl->pctrl_1));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
int stm32mp_board_ddr_power_init(enum ddr_type ddr_type)
|
||||
{
|
||||
if (dt_pmic_status() > 0) {
|
||||
return pmic_ddr_power_init(ddr_type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/st/stm32mp_ddr_test.h>
|
||||
#include <lib/mmio.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#define DDR_PATTERN 0xAAAAAAAAU
|
||||
#define DDR_ANTIPATTERN 0x55555555U
|
||||
|
||||
/*******************************************************************************
|
||||
* This function tests a simple read/write access to the DDR.
|
||||
* Note that the previous content is restored after test.
|
||||
* Returns 0 if success, and address value else.
|
||||
******************************************************************************/
|
||||
uint32_t stm32mp_ddr_test_rw_access(void)
|
||||
{
|
||||
uint32_t saved_value = mmio_read_32(STM32MP_DDR_BASE);
|
||||
|
||||
mmio_write_32(STM32MP_DDR_BASE, DDR_PATTERN);
|
||||
|
||||
if (mmio_read_32(STM32MP_DDR_BASE) != DDR_PATTERN) {
|
||||
return (uint32_t)STM32MP_DDR_BASE;
|
||||
}
|
||||
|
||||
mmio_write_32(STM32MP_DDR_BASE, saved_value);
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function tests the DDR data bus wiring.
|
||||
* This is inspired from the Data Bus Test algorithm written by Michael Barr
|
||||
* in "Programming Embedded Systems in C and C++" book.
|
||||
* resources.oreilly.com/examples/9781565923546/blob/master/Chapter6/
|
||||
* File: memtest.c - This source code belongs to Public Domain.
|
||||
* Returns 0 if success, and address value else.
|
||||
******************************************************************************/
|
||||
uint32_t stm32mp_ddr_test_data_bus(void)
|
||||
{
|
||||
uint32_t pattern;
|
||||
|
||||
for (pattern = 1U; pattern != 0U; pattern <<= 1U) {
|
||||
mmio_write_32(STM32MP_DDR_BASE, pattern);
|
||||
|
||||
if (mmio_read_32(STM32MP_DDR_BASE) != pattern) {
|
||||
return (uint32_t)STM32MP_DDR_BASE;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function tests the DDR address bus wiring.
|
||||
* This is inspired from the Data Bus Test algorithm written by Michael Barr
|
||||
* in "Programming Embedded Systems in C and C++" book.
|
||||
* resources.oreilly.com/examples/9781565923546/blob/master/Chapter6/
|
||||
* File: memtest.c - This source code belongs to Public Domain.
|
||||
* size: size in bytes of the DDR memory device.
|
||||
* Returns 0 if success, and address value else.
|
||||
******************************************************************************/
|
||||
uint32_t stm32mp_ddr_test_addr_bus(uint64_t size)
|
||||
{
|
||||
uint64_t addressmask = size - 1U;
|
||||
uint64_t offset;
|
||||
uint64_t testoffset = 0U;
|
||||
|
||||
/* Write the default pattern at each of the power-of-two offsets. */
|
||||
for (offset = sizeof(uint32_t); (offset & addressmask) != 0U;
|
||||
offset <<= 1U) {
|
||||
mmio_write_32(STM32MP_DDR_BASE + (uint32_t)offset,
|
||||
DDR_PATTERN);
|
||||
}
|
||||
|
||||
/* Check for address bits stuck high. */
|
||||
mmio_write_32(STM32MP_DDR_BASE + (uint32_t)testoffset,
|
||||
DDR_ANTIPATTERN);
|
||||
|
||||
for (offset = sizeof(uint32_t); (offset & addressmask) != 0U;
|
||||
offset <<= 1U) {
|
||||
if (mmio_read_32(STM32MP_DDR_BASE + (uint32_t)offset) !=
|
||||
DDR_PATTERN) {
|
||||
return (uint32_t)(STM32MP_DDR_BASE + offset);
|
||||
}
|
||||
}
|
||||
|
||||
mmio_write_32(STM32MP_DDR_BASE + (uint32_t)testoffset, DDR_PATTERN);
|
||||
|
||||
/* Check for address bits stuck low or shorted. */
|
||||
for (testoffset = sizeof(uint32_t); (testoffset & addressmask) != 0U;
|
||||
testoffset <<= 1U) {
|
||||
mmio_write_32(STM32MP_DDR_BASE + (uint32_t)testoffset,
|
||||
DDR_ANTIPATTERN);
|
||||
|
||||
if (mmio_read_32(STM32MP_DDR_BASE) != DDR_PATTERN) {
|
||||
return STM32MP_DDR_BASE;
|
||||
}
|
||||
|
||||
for (offset = sizeof(uint32_t); (offset & addressmask) != 0U;
|
||||
offset <<= 1) {
|
||||
if ((mmio_read_32(STM32MP_DDR_BASE +
|
||||
(uint32_t)offset) != DDR_PATTERN) &&
|
||||
(offset != testoffset)) {
|
||||
return (uint32_t)(STM32MP_DDR_BASE + offset);
|
||||
}
|
||||
}
|
||||
|
||||
mmio_write_32(STM32MP_DDR_BASE + (uint32_t)testoffset,
|
||||
DDR_PATTERN);
|
||||
}
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function checks the DDR size. It has to be run with Data Cache off.
|
||||
* This test is run before data have been put in DDR, and is only done for
|
||||
* cold boot. The DDR data can then be overwritten, and it is not useful to
|
||||
* restore its content.
|
||||
* Returns DDR computed size.
|
||||
******************************************************************************/
|
||||
uint32_t stm32mp_ddr_check_size(void)
|
||||
{
|
||||
uint32_t offset = sizeof(uint32_t);
|
||||
|
||||
mmio_write_32(STM32MP_DDR_BASE, DDR_PATTERN);
|
||||
|
||||
while (offset < STM32MP_DDR_MAX_SIZE) {
|
||||
mmio_write_32(STM32MP_DDR_BASE + offset, DDR_ANTIPATTERN);
|
||||
dsb();
|
||||
|
||||
if (mmio_read_32(STM32MP_DDR_BASE) != DDR_PATTERN) {
|
||||
break;
|
||||
}
|
||||
|
||||
offset <<= 1U;
|
||||
}
|
||||
|
||||
INFO("Memory size = 0x%x (%u MB)\n", offset, offset / (1024U * 1024U));
|
||||
|
||||
return offset;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <common/fdt_wrappers.h>
|
||||
#include <drivers/st/stm32mp_ram.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
int stm32mp_ddr_dt_get_info(void *fdt, int node, struct stm32mp_ddr_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = fdt_read_uint32(fdt, node, "st,mem-speed", &info->speed);
|
||||
if (ret < 0) {
|
||||
VERBOSE("%s: no st,mem-speed\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = fdt_read_uint32(fdt, node, "st,mem-size", &info->size);
|
||||
if (ret < 0) {
|
||||
VERBOSE("%s: no st,mem-size\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
info->name = fdt_getprop(fdt, node, "st,mem-name", NULL);
|
||||
if (info->name == NULL) {
|
||||
VERBOSE("%s: no st,mem-name\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
INFO("RAM: %s\n", info->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stm32mp_ddr_dt_get_param(void *fdt, int node, const struct stm32mp_ddr_param *param,
|
||||
uint32_t param_size, uintptr_t config)
|
||||
{
|
||||
int ret;
|
||||
uint32_t idx;
|
||||
|
||||
for (idx = 0U; idx < param_size; idx++) {
|
||||
ret = fdt_read_uint32_array(fdt, node, param[idx].name, param[idx].size,
|
||||
(void *)(config + param[idx].offset));
|
||||
|
||||
VERBOSE("%s: %s[0x%x] = %d\n", __func__, param[idx].name, param[idx].size, ret);
|
||||
if (ret != 0) {
|
||||
ERROR("%s: Cannot read %s, error=%d\n", __func__, param[idx].name, ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <arch_helpers.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/st/etzpc.h>
|
||||
#include <dt-bindings/soc/st,stm32-etzpc.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils_def.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
/* Device Tree related definitions */
|
||||
#define ETZPC_COMPAT "st,stm32-etzpc"
|
||||
#define ETZPC_LOCK_MASK 0x1U
|
||||
#define ETZPC_MODE_SHIFT 8
|
||||
#define ETZPC_MODE_MASK GENMASK(1, 0)
|
||||
#define ETZPC_ID_SHIFT 16
|
||||
#define ETZPC_ID_MASK GENMASK(7, 0)
|
||||
|
||||
/* ID Registers */
|
||||
#define ETZPC_TZMA0_SIZE 0x000U
|
||||
#define ETZPC_DECPROT0 0x010U
|
||||
#define ETZPC_DECPROT_LOCK0 0x030U
|
||||
#define ETZPC_HWCFGR 0x3F0U
|
||||
#define ETZPC_VERR 0x3F4U
|
||||
|
||||
/* ID Registers fields */
|
||||
#define ETZPC_TZMA0_SIZE_LOCK BIT(31)
|
||||
#define ETZPC_DECPROT0_MASK GENMASK(1, 0)
|
||||
#define ETZPC_HWCFGR_NUM_TZMA_SHIFT 0
|
||||
#define ETZPC_HWCFGR_NUM_PER_SEC_SHIFT 8
|
||||
#define ETZPC_HWCFGR_NUM_AHB_SEC_SHIFT 16
|
||||
#define ETZPC_HWCFGR_CHUNCKS1N4_SHIFT 24
|
||||
|
||||
#define DECPROT_SHIFT 1
|
||||
#define IDS_PER_DECPROT_REGS 16U
|
||||
#define IDS_PER_DECPROT_LOCK_REGS 32U
|
||||
|
||||
/*
|
||||
* etzpc_instance.
|
||||
* base : register base address set during init given by user
|
||||
* chunk_size : supported TZMA size steps
|
||||
* num_tzma: number of TZMA zone read from register at init
|
||||
* num_ahb_sec : number of securable AHB master zone read from register
|
||||
* num_per_sec : number of securable AHB & APB Peripherals read from register
|
||||
* revision : IP revision read from register at init
|
||||
*/
|
||||
struct etzpc_instance {
|
||||
uintptr_t base;
|
||||
uint8_t chunck_size;
|
||||
uint8_t num_tzma;
|
||||
uint8_t num_per_sec;
|
||||
uint8_t num_ahb_sec;
|
||||
uint8_t revision;
|
||||
};
|
||||
|
||||
/* Only 1 instance of the ETZPC is expected per platform */
|
||||
static struct etzpc_instance etzpc_dev;
|
||||
|
||||
/*
|
||||
* Implementation uses uint8_t to store each securable DECPROT configuration.
|
||||
* When resuming from deep suspend, the DECPROT configurations are restored.
|
||||
*/
|
||||
#define PERIPH_LOCK_BIT BIT(7)
|
||||
#define PERIPH_ATTR_MASK GENMASK(2, 0)
|
||||
|
||||
#if ENABLE_ASSERTIONS
|
||||
static bool valid_decprot_id(unsigned int id)
|
||||
{
|
||||
return id < (unsigned int)etzpc_dev.num_per_sec;
|
||||
}
|
||||
|
||||
static bool valid_tzma_id(unsigned int id)
|
||||
{
|
||||
return id < (unsigned int)etzpc_dev.num_tzma;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* etzpc_configure_decprot : Load a DECPROT configuration
|
||||
* decprot_id : ID of the IP
|
||||
* decprot_attr : Restriction access attribute
|
||||
*/
|
||||
void etzpc_configure_decprot(uint32_t decprot_id,
|
||||
enum etzpc_decprot_attributes decprot_attr)
|
||||
{
|
||||
uintptr_t offset = 4U * (decprot_id / IDS_PER_DECPROT_REGS);
|
||||
uint32_t shift = (decprot_id % IDS_PER_DECPROT_REGS) << DECPROT_SHIFT;
|
||||
uint32_t masked_decprot = (uint32_t)decprot_attr & ETZPC_DECPROT0_MASK;
|
||||
|
||||
assert(valid_decprot_id(decprot_id));
|
||||
|
||||
mmio_clrsetbits_32(etzpc_dev.base + ETZPC_DECPROT0 + offset,
|
||||
(uint32_t)ETZPC_DECPROT0_MASK << shift,
|
||||
masked_decprot << shift);
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_get_decprot : Get the DECPROT attribute
|
||||
* decprot_id : ID of the IP
|
||||
* return : Attribute of this DECPROT
|
||||
*/
|
||||
enum etzpc_decprot_attributes etzpc_get_decprot(uint32_t decprot_id)
|
||||
{
|
||||
uintptr_t offset = 4U * (decprot_id / IDS_PER_DECPROT_REGS);
|
||||
uint32_t shift = (decprot_id % IDS_PER_DECPROT_REGS) << DECPROT_SHIFT;
|
||||
uintptr_t base_decprot = etzpc_dev.base + offset;
|
||||
uint32_t value;
|
||||
|
||||
assert(valid_decprot_id(decprot_id));
|
||||
|
||||
value = (mmio_read_32(base_decprot + ETZPC_DECPROT0) >> shift) &
|
||||
ETZPC_DECPROT0_MASK;
|
||||
|
||||
return (enum etzpc_decprot_attributes)value;
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_lock_decprot : Lock access to the DECPROT attribute
|
||||
* decprot_id : ID of the IP
|
||||
*/
|
||||
void etzpc_lock_decprot(uint32_t decprot_id)
|
||||
{
|
||||
uintptr_t offset = 4U * (decprot_id / IDS_PER_DECPROT_LOCK_REGS);
|
||||
uint32_t shift = BIT(decprot_id % IDS_PER_DECPROT_LOCK_REGS);
|
||||
uintptr_t base_decprot = etzpc_dev.base + offset;
|
||||
|
||||
assert(valid_decprot_id(decprot_id));
|
||||
|
||||
mmio_write_32(base_decprot + ETZPC_DECPROT_LOCK0, shift);
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_configure_tzma : Configure the target TZMA read only size
|
||||
* tzma_id : ID of the memory
|
||||
* tzma_value : read-only size
|
||||
*/
|
||||
void etzpc_configure_tzma(uint32_t tzma_id, uint16_t tzma_value)
|
||||
{
|
||||
assert(valid_tzma_id(tzma_id));
|
||||
|
||||
mmio_write_32(etzpc_dev.base + ETZPC_TZMA0_SIZE +
|
||||
(sizeof(uint32_t) * tzma_id), tzma_value);
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_get_tzma : Get the target TZMA read only size
|
||||
* tzma_id : TZMA ID
|
||||
* return : Size of read only size
|
||||
*/
|
||||
uint16_t etzpc_get_tzma(uint32_t tzma_id)
|
||||
{
|
||||
assert(valid_tzma_id(tzma_id));
|
||||
|
||||
return (uint16_t)mmio_read_32(etzpc_dev.base + ETZPC_TZMA0_SIZE +
|
||||
(sizeof(uint32_t) * tzma_id));
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_lock_tzma : Lock the target TZMA
|
||||
* tzma_id : TZMA ID
|
||||
*/
|
||||
void etzpc_lock_tzma(uint32_t tzma_id)
|
||||
{
|
||||
assert(valid_tzma_id(tzma_id));
|
||||
|
||||
mmio_setbits_32(etzpc_dev.base + ETZPC_TZMA0_SIZE +
|
||||
(sizeof(uint32_t) * tzma_id), ETZPC_TZMA0_SIZE_LOCK);
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_get_lock_tzma : Return the lock status of the target TZMA
|
||||
* tzma_id : TZMA ID
|
||||
* return : True if TZMA is locked, false otherwise
|
||||
*/
|
||||
bool etzpc_get_lock_tzma(uint32_t tzma_id)
|
||||
{
|
||||
uint32_t tzma_size;
|
||||
|
||||
assert(valid_tzma_id(tzma_id));
|
||||
|
||||
tzma_size = mmio_read_32(etzpc_dev.base + ETZPC_TZMA0_SIZE +
|
||||
(sizeof(uint32_t) * tzma_id));
|
||||
|
||||
return (tzma_size & ETZPC_TZMA0_SIZE_LOCK) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_get_num_per_sec : Return the DECPROT ID limit value
|
||||
*/
|
||||
uint8_t etzpc_get_num_per_sec(void)
|
||||
{
|
||||
return etzpc_dev.num_per_sec;
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_get_revision : Return the ETZPC IP revision
|
||||
*/
|
||||
uint8_t etzpc_get_revision(void)
|
||||
{
|
||||
return etzpc_dev.revision;
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_get_base_address : Return the ETZPC IP base address
|
||||
*/
|
||||
uintptr_t etzpc_get_base_address(void)
|
||||
{
|
||||
return etzpc_dev.base;
|
||||
}
|
||||
|
||||
/*
|
||||
* etzpc_init : Initialize the ETZPC driver
|
||||
* Return 0 on success and a negative errno on failure
|
||||
*/
|
||||
int etzpc_init(void)
|
||||
{
|
||||
uint32_t hwcfg;
|
||||
|
||||
etzpc_dev.base = STM32MP1_ETZPC_BASE;
|
||||
|
||||
hwcfg = mmio_read_32(etzpc_dev.base + ETZPC_HWCFGR);
|
||||
|
||||
etzpc_dev.num_tzma = (uint8_t)(hwcfg >> ETZPC_HWCFGR_NUM_TZMA_SHIFT);
|
||||
etzpc_dev.num_per_sec = (uint8_t)(hwcfg >>
|
||||
ETZPC_HWCFGR_NUM_PER_SEC_SHIFT);
|
||||
etzpc_dev.num_ahb_sec = (uint8_t)(hwcfg >>
|
||||
ETZPC_HWCFGR_NUM_AHB_SEC_SHIFT);
|
||||
etzpc_dev.chunck_size = (uint8_t)(hwcfg >>
|
||||
ETZPC_HWCFGR_CHUNCKS1N4_SHIFT);
|
||||
|
||||
etzpc_dev.revision = mmio_read_8(etzpc_dev.base + ETZPC_VERR);
|
||||
|
||||
VERBOSE("ETZPC version 0x%x", etzpc_dev.revision);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,934 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/raw_nand.h>
|
||||
#include <drivers/st/stm32_fmc2_nand.h>
|
||||
#include <drivers/st/stm32_gpio.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils_def.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
/* Timeout for device interface reset */
|
||||
#define TIMEOUT_US_1_MS 1000U
|
||||
|
||||
/* FMC2 Compatibility */
|
||||
#define DT_FMC2_EBI_COMPAT "st,stm32mp1-fmc2-ebi"
|
||||
#define DT_FMC2_NFC_COMPAT "st,stm32mp1-fmc2-nfc"
|
||||
#define MAX_CS 2U
|
||||
#define MAX_BANK 5U
|
||||
|
||||
/* FMC2 Controller Registers */
|
||||
#define FMC2_BCR1 0x00U
|
||||
#define FMC2_PCR 0x80U
|
||||
#define FMC2_SR 0x84U
|
||||
#define FMC2_PMEM 0x88U
|
||||
#define FMC2_PATT 0x8CU
|
||||
#define FMC2_HECCR 0x94U
|
||||
#define FMC2_BCHISR 0x254U
|
||||
#define FMC2_BCHICR 0x258U
|
||||
#define FMC2_BCHDSR0 0x27CU
|
||||
#define FMC2_BCHDSR1 0x280U
|
||||
#define FMC2_BCHDSR2 0x284U
|
||||
#define FMC2_BCHDSR3 0x288U
|
||||
#define FMC2_BCHDSR4 0x28CU
|
||||
|
||||
/* FMC2_BCR1 register */
|
||||
#define FMC2_BCR1_FMC2EN BIT(31)
|
||||
/* FMC2_PCR register */
|
||||
#define FMC2_PCR_PWAITEN BIT(1)
|
||||
#define FMC2_PCR_PBKEN BIT(2)
|
||||
#define FMC2_PCR_PWID_MASK GENMASK_32(5, 4)
|
||||
#define FMC2_PCR_PWID(x) (((x) << 4) & FMC2_PCR_PWID_MASK)
|
||||
#define FMC2_PCR_PWID_8 0x0U
|
||||
#define FMC2_PCR_PWID_16 0x1U
|
||||
#define FMC2_PCR_ECCEN BIT(6)
|
||||
#define FMC2_PCR_ECCALG BIT(8)
|
||||
#define FMC2_PCR_TCLR_MASK GENMASK_32(12, 9)
|
||||
#define FMC2_PCR_TCLR(x) (((x) << 9) & FMC2_PCR_TCLR_MASK)
|
||||
#define FMC2_PCR_TCLR_DEFAULT 0xFU
|
||||
#define FMC2_PCR_TAR_MASK GENMASK_32(16, 13)
|
||||
#define FMC2_PCR_TAR(x) (((x) << 13) & FMC2_PCR_TAR_MASK)
|
||||
#define FMC2_PCR_TAR_DEFAULT 0xFU
|
||||
#define FMC2_PCR_ECCSS_MASK GENMASK_32(19, 17)
|
||||
#define FMC2_PCR_ECCSS(x) (((x) << 17) & FMC2_PCR_ECCSS_MASK)
|
||||
#define FMC2_PCR_ECCSS_512 0x1U
|
||||
#define FMC2_PCR_ECCSS_2048 0x3U
|
||||
#define FMC2_PCR_BCHECC BIT(24)
|
||||
#define FMC2_PCR_WEN BIT(25)
|
||||
/* FMC2_SR register */
|
||||
#define FMC2_SR_NWRF BIT(6)
|
||||
/* FMC2_PMEM register*/
|
||||
#define FMC2_PMEM_MEMSET(x) (((x) & GENMASK_32(7, 0)) << 0)
|
||||
#define FMC2_PMEM_MEMWAIT(x) (((x) & GENMASK_32(7, 0)) << 8)
|
||||
#define FMC2_PMEM_MEMHOLD(x) (((x) & GENMASK_32(7, 0)) << 16)
|
||||
#define FMC2_PMEM_MEMHIZ(x) (((x) & GENMASK_32(7, 0)) << 24)
|
||||
#define FMC2_PMEM_DEFAULT 0x0A0A0A0AU
|
||||
/* FMC2_PATT register */
|
||||
#define FMC2_PATT_ATTSET(x) (((x) & GENMASK_32(7, 0)) << 0)
|
||||
#define FMC2_PATT_ATTWAIT(x) (((x) & GENMASK_32(7, 0)) << 8)
|
||||
#define FMC2_PATT_ATTHOLD(x) (((x) & GENMASK_32(7, 0)) << 16)
|
||||
#define FMC2_PATT_ATTHIZ(x) (((x) & GENMASK_32(7, 0)) << 24)
|
||||
#define FMC2_PATT_DEFAULT 0x0A0A0A0AU
|
||||
/* FMC2_BCHISR register */
|
||||
#define FMC2_BCHISR_DERF BIT(1)
|
||||
/* FMC2_BCHICR register */
|
||||
#define FMC2_BCHICR_CLEAR_IRQ GENMASK_32(4, 0)
|
||||
/* FMC2_BCHDSR0 register */
|
||||
#define FMC2_BCHDSR0_DUE BIT(0)
|
||||
#define FMC2_BCHDSR0_DEF BIT(1)
|
||||
#define FMC2_BCHDSR0_DEN_MASK GENMASK_32(7, 4)
|
||||
#define FMC2_BCHDSR0_DEN_SHIFT 4U
|
||||
/* FMC2_BCHDSR1 register */
|
||||
#define FMC2_BCHDSR1_EBP1_MASK GENMASK_32(12, 0)
|
||||
#define FMC2_BCHDSR1_EBP2_MASK GENMASK_32(28, 16)
|
||||
#define FMC2_BCHDSR1_EBP2_SHIFT 16U
|
||||
/* FMC2_BCHDSR2 register */
|
||||
#define FMC2_BCHDSR2_EBP3_MASK GENMASK_32(12, 0)
|
||||
#define FMC2_BCHDSR2_EBP4_MASK GENMASK_32(28, 16)
|
||||
#define FMC2_BCHDSR2_EBP4_SHIFT 16U
|
||||
/* FMC2_BCHDSR3 register */
|
||||
#define FMC2_BCHDSR3_EBP5_MASK GENMASK_32(12, 0)
|
||||
#define FMC2_BCHDSR3_EBP6_MASK GENMASK_32(28, 16)
|
||||
#define FMC2_BCHDSR3_EBP6_SHIFT 16U
|
||||
/* FMC2_BCHDSR4 register */
|
||||
#define FMC2_BCHDSR4_EBP7_MASK GENMASK_32(12, 0)
|
||||
#define FMC2_BCHDSR4_EBP8_MASK GENMASK_32(28, 16)
|
||||
#define FMC2_BCHDSR4_EBP8_SHIFT 16U
|
||||
|
||||
/* Timings */
|
||||
#define FMC2_THIZ 0x01U
|
||||
#define FMC2_TIO 8000U
|
||||
#define FMC2_TSYNC 3000U
|
||||
#define FMC2_PCR_TIMING_MASK GENMASK_32(3, 0)
|
||||
#define FMC2_PMEM_PATT_TIMING_MASK GENMASK_32(7, 0)
|
||||
|
||||
#define FMC2_BBM_LEN 2U
|
||||
#define FMC2_MAX_ECC_BYTES 14U
|
||||
#define TIMEOUT_US_10_MS 10000U
|
||||
#define FMC2_PSEC_PER_MSEC (1000UL * 1000UL * 1000UL)
|
||||
|
||||
enum stm32_fmc2_ecc {
|
||||
FMC2_ECC_HAM = 1U,
|
||||
FMC2_ECC_BCH4 = 4U,
|
||||
FMC2_ECC_BCH8 = 8U
|
||||
};
|
||||
|
||||
struct stm32_fmc2_cs_reg {
|
||||
uintptr_t data_base;
|
||||
uintptr_t cmd_base;
|
||||
uintptr_t addr_base;
|
||||
};
|
||||
|
||||
struct stm32_fmc2_nand_timings {
|
||||
uint8_t tclr;
|
||||
uint8_t tar;
|
||||
uint8_t thiz;
|
||||
uint8_t twait;
|
||||
uint8_t thold_mem;
|
||||
uint8_t tset_mem;
|
||||
uint8_t thold_att;
|
||||
uint8_t tset_att;
|
||||
};
|
||||
|
||||
struct stm32_fmc2_nfc {
|
||||
uintptr_t reg_base;
|
||||
struct stm32_fmc2_cs_reg cs[MAX_CS];
|
||||
unsigned long clock_id;
|
||||
unsigned int reset_id;
|
||||
uint8_t cs_sel;
|
||||
};
|
||||
|
||||
static struct stm32_fmc2_nfc stm32_fmc2;
|
||||
|
||||
static uintptr_t fmc2_base(void)
|
||||
{
|
||||
return stm32_fmc2.reg_base;
|
||||
}
|
||||
|
||||
static void stm32_fmc2_nand_setup_timing(void)
|
||||
{
|
||||
struct stm32_fmc2_nand_timings tims;
|
||||
unsigned long hclk = clk_get_rate(stm32_fmc2.clock_id);
|
||||
unsigned long hclkp = FMC2_PSEC_PER_MSEC / (hclk / 1000U);
|
||||
unsigned long timing, tar, tclr, thiz, twait;
|
||||
unsigned long tset_mem, tset_att, thold_mem, thold_att;
|
||||
uint32_t pcr, pmem, patt;
|
||||
|
||||
tar = MAX(hclkp, NAND_TAR_MIN);
|
||||
timing = div_round_up(tar, hclkp) - 1U;
|
||||
tims.tar = MIN(timing, (unsigned long)FMC2_PCR_TIMING_MASK);
|
||||
|
||||
tclr = MAX(hclkp, NAND_TCLR_MIN);
|
||||
timing = div_round_up(tclr, hclkp) - 1U;
|
||||
tims.tclr = MIN(timing, (unsigned long)FMC2_PCR_TIMING_MASK);
|
||||
|
||||
tims.thiz = FMC2_THIZ;
|
||||
thiz = (tims.thiz + 1U) * hclkp;
|
||||
|
||||
/*
|
||||
* tWAIT > tRP
|
||||
* tWAIT > tWP
|
||||
* tWAIT > tREA + tIO
|
||||
*/
|
||||
twait = MAX(hclkp, NAND_TRP_MIN);
|
||||
twait = MAX(twait, NAND_TWP_MIN);
|
||||
twait = MAX(twait, NAND_TREA_MAX + FMC2_TIO);
|
||||
timing = div_round_up(twait, hclkp);
|
||||
tims.twait = CLAMP(timing, 1UL,
|
||||
(unsigned long)FMC2_PMEM_PATT_TIMING_MASK);
|
||||
|
||||
/*
|
||||
* tSETUP_MEM > tCS - tWAIT
|
||||
* tSETUP_MEM > tALS - tWAIT
|
||||
* tSETUP_MEM > tDS - (tWAIT - tHIZ)
|
||||
*/
|
||||
tset_mem = hclkp;
|
||||
if ((twait < NAND_TCS_MIN) && (tset_mem < (NAND_TCS_MIN - twait))) {
|
||||
tset_mem = NAND_TCS_MIN - twait;
|
||||
}
|
||||
if ((twait > thiz) && ((twait - thiz) < NAND_TDS_MIN) &&
|
||||
(tset_mem < (NAND_TDS_MIN - (twait - thiz)))) {
|
||||
tset_mem = NAND_TDS_MIN - (twait - thiz);
|
||||
}
|
||||
timing = div_round_up(tset_mem, hclkp);
|
||||
tims.tset_mem = CLAMP(timing, 1UL,
|
||||
(unsigned long)FMC2_PMEM_PATT_TIMING_MASK);
|
||||
|
||||
/*
|
||||
* tHOLD_MEM > tCH
|
||||
* tHOLD_MEM > tREH - tSETUP_MEM
|
||||
* tHOLD_MEM > max(tRC, tWC) - (tSETUP_MEM + tWAIT)
|
||||
*/
|
||||
thold_mem = MAX(hclkp, NAND_TCH_MIN);
|
||||
if ((tset_mem < NAND_TREH_MIN) &&
|
||||
(thold_mem < (NAND_TREH_MIN - tset_mem))) {
|
||||
thold_mem = NAND_TREH_MIN - tset_mem;
|
||||
}
|
||||
if (((tset_mem + twait) < NAND_TRC_MIN) &&
|
||||
(thold_mem < (NAND_TRC_MIN - (tset_mem + twait)))) {
|
||||
thold_mem = NAND_TRC_MIN - (tset_mem + twait);
|
||||
}
|
||||
if (((tset_mem + twait) < NAND_TWC_MIN) &&
|
||||
(thold_mem < (NAND_TWC_MIN - (tset_mem + twait)))) {
|
||||
thold_mem = NAND_TWC_MIN - (tset_mem + twait);
|
||||
}
|
||||
timing = div_round_up(thold_mem, hclkp);
|
||||
tims.thold_mem = CLAMP(timing, 1UL,
|
||||
(unsigned long)FMC2_PMEM_PATT_TIMING_MASK);
|
||||
|
||||
/*
|
||||
* tSETUP_ATT > tCS - tWAIT
|
||||
* tSETUP_ATT > tCLS - tWAIT
|
||||
* tSETUP_ATT > tALS - tWAIT
|
||||
* tSETUP_ATT > tRHW - tHOLD_MEM
|
||||
* tSETUP_ATT > tDS - (tWAIT - tHIZ)
|
||||
*/
|
||||
tset_att = hclkp;
|
||||
if ((twait < NAND_TCS_MIN) && (tset_att < (NAND_TCS_MIN - twait))) {
|
||||
tset_att = NAND_TCS_MIN - twait;
|
||||
}
|
||||
if ((thold_mem < NAND_TRHW_MIN) &&
|
||||
(tset_att < (NAND_TRHW_MIN - thold_mem))) {
|
||||
tset_att = NAND_TRHW_MIN - thold_mem;
|
||||
}
|
||||
if ((twait > thiz) && ((twait - thiz) < NAND_TDS_MIN) &&
|
||||
(tset_att < (NAND_TDS_MIN - (twait - thiz)))) {
|
||||
tset_att = NAND_TDS_MIN - (twait - thiz);
|
||||
}
|
||||
timing = div_round_up(tset_att, hclkp);
|
||||
tims.tset_att = CLAMP(timing, 1UL,
|
||||
(unsigned long)FMC2_PMEM_PATT_TIMING_MASK);
|
||||
|
||||
/*
|
||||
* tHOLD_ATT > tALH
|
||||
* tHOLD_ATT > tCH
|
||||
* tHOLD_ATT > tCLH
|
||||
* tHOLD_ATT > tCOH
|
||||
* tHOLD_ATT > tDH
|
||||
* tHOLD_ATT > tWB + tIO + tSYNC - tSETUP_MEM
|
||||
* tHOLD_ATT > tADL - tSETUP_MEM
|
||||
* tHOLD_ATT > tWH - tSETUP_MEM
|
||||
* tHOLD_ATT > tWHR - tSETUP_MEM
|
||||
* tHOLD_ATT > tRC - (tSETUP_ATT + tWAIT)
|
||||
* tHOLD_ATT > tWC - (tSETUP_ATT + tWAIT)
|
||||
*/
|
||||
thold_att = MAX(hclkp, NAND_TALH_MIN);
|
||||
thold_att = MAX(thold_att, NAND_TCH_MIN);
|
||||
thold_att = MAX(thold_att, NAND_TCLH_MIN);
|
||||
thold_att = MAX(thold_att, NAND_TCOH_MIN);
|
||||
thold_att = MAX(thold_att, NAND_TDH_MIN);
|
||||
if (((NAND_TWB_MAX + FMC2_TIO + FMC2_TSYNC) > tset_mem) &&
|
||||
(thold_att < (NAND_TWB_MAX + FMC2_TIO + FMC2_TSYNC - tset_mem))) {
|
||||
thold_att = NAND_TWB_MAX + FMC2_TIO + FMC2_TSYNC - tset_mem;
|
||||
}
|
||||
if ((tset_mem < NAND_TADL_MIN) &&
|
||||
(thold_att < (NAND_TADL_MIN - tset_mem))) {
|
||||
thold_att = NAND_TADL_MIN - tset_mem;
|
||||
}
|
||||
if ((tset_mem < NAND_TWH_MIN) &&
|
||||
(thold_att < (NAND_TWH_MIN - tset_mem))) {
|
||||
thold_att = NAND_TWH_MIN - tset_mem;
|
||||
}
|
||||
if ((tset_mem < NAND_TWHR_MIN) &&
|
||||
(thold_att < (NAND_TWHR_MIN - tset_mem))) {
|
||||
thold_att = NAND_TWHR_MIN - tset_mem;
|
||||
}
|
||||
if (((tset_att + twait) < NAND_TRC_MIN) &&
|
||||
(thold_att < (NAND_TRC_MIN - (tset_att + twait)))) {
|
||||
thold_att = NAND_TRC_MIN - (tset_att + twait);
|
||||
}
|
||||
if (((tset_att + twait) < NAND_TWC_MIN) &&
|
||||
(thold_att < (NAND_TWC_MIN - (tset_att + twait)))) {
|
||||
thold_att = NAND_TWC_MIN - (tset_att + twait);
|
||||
}
|
||||
timing = div_round_up(thold_att, hclkp);
|
||||
tims.thold_att = CLAMP(timing, 1UL,
|
||||
(unsigned long)FMC2_PMEM_PATT_TIMING_MASK);
|
||||
|
||||
VERBOSE("NAND timings: %u - %u - %u - %u - %u - %u - %u - %u\n",
|
||||
tims.tclr, tims.tar, tims.thiz, tims.twait,
|
||||
tims.thold_mem, tims.tset_mem,
|
||||
tims.thold_att, tims.tset_att);
|
||||
|
||||
/* Set tclr/tar timings */
|
||||
pcr = mmio_read_32(fmc2_base() + FMC2_PCR);
|
||||
pcr &= ~FMC2_PCR_TCLR_MASK;
|
||||
pcr |= FMC2_PCR_TCLR(tims.tclr);
|
||||
pcr &= ~FMC2_PCR_TAR_MASK;
|
||||
pcr |= FMC2_PCR_TAR(tims.tar);
|
||||
|
||||
/* Set tset/twait/thold/thiz timings in common bank */
|
||||
pmem = FMC2_PMEM_MEMSET(tims.tset_mem);
|
||||
pmem |= FMC2_PMEM_MEMWAIT(tims.twait);
|
||||
pmem |= FMC2_PMEM_MEMHOLD(tims.thold_mem);
|
||||
pmem |= FMC2_PMEM_MEMHIZ(tims.thiz);
|
||||
|
||||
/* Set tset/twait/thold/thiz timings in attribute bank */
|
||||
patt = FMC2_PATT_ATTSET(tims.tset_att);
|
||||
patt |= FMC2_PATT_ATTWAIT(tims.twait);
|
||||
patt |= FMC2_PATT_ATTHOLD(tims.thold_att);
|
||||
patt |= FMC2_PATT_ATTHIZ(tims.thiz);
|
||||
|
||||
mmio_write_32(fmc2_base() + FMC2_PCR, pcr);
|
||||
mmio_write_32(fmc2_base() + FMC2_PMEM, pmem);
|
||||
mmio_write_32(fmc2_base() + FMC2_PATT, patt);
|
||||
}
|
||||
|
||||
static void stm32_fmc2_set_buswidth_16(bool set)
|
||||
{
|
||||
mmio_clrsetbits_32(fmc2_base() + FMC2_PCR, FMC2_PCR_PWID_MASK,
|
||||
(set ? FMC2_PCR_PWID(FMC2_PCR_PWID_16) : 0U));
|
||||
}
|
||||
|
||||
static void stm32_fmc2_set_ecc(bool enable)
|
||||
{
|
||||
mmio_clrsetbits_32(fmc2_base() + FMC2_PCR, FMC2_PCR_ECCEN,
|
||||
(enable ? FMC2_PCR_ECCEN : 0U));
|
||||
}
|
||||
|
||||
static int stm32_fmc2_ham_correct(uint8_t *buffer, uint8_t *eccbuffer,
|
||||
uint8_t *ecc)
|
||||
{
|
||||
uint8_t xor_ecc_ones;
|
||||
uint16_t xor_ecc_1b, xor_ecc_2b, xor_ecc_3b;
|
||||
union {
|
||||
uint32_t val;
|
||||
uint8_t bytes[4];
|
||||
} xor_ecc;
|
||||
|
||||
/* Page size--------ECC_Code Size
|
||||
* 256---------------22 bits LSB (ECC_CODE & 0x003FFFFF)
|
||||
* 512---------------24 bits (ECC_CODE & 0x00FFFFFF)
|
||||
* 1024--------------26 bits (ECC_CODE & 0x03FFFFFF)
|
||||
* 2048--------------28 bits (ECC_CODE & 0x0FFFFFFF)
|
||||
* 4096--------------30 bits (ECC_CODE & 0x3FFFFFFF)
|
||||
* 8192--------------32 bits (ECC_CODE & 0xFFFFFFFF)
|
||||
*/
|
||||
|
||||
/* For Page size 512, ECC_Code size 24 bits */
|
||||
xor_ecc_1b = ecc[0] ^ eccbuffer[0];
|
||||
xor_ecc_2b = ecc[1] ^ eccbuffer[1];
|
||||
xor_ecc_3b = ecc[2] ^ eccbuffer[2];
|
||||
|
||||
xor_ecc.val = 0U;
|
||||
xor_ecc.bytes[2] = xor_ecc_3b;
|
||||
xor_ecc.bytes[1] = xor_ecc_2b;
|
||||
xor_ecc.bytes[0] = xor_ecc_1b;
|
||||
|
||||
if (xor_ecc.val == 0U) {
|
||||
return 0; /* No Error */
|
||||
}
|
||||
|
||||
xor_ecc_ones = __builtin_popcount(xor_ecc.val);
|
||||
if (xor_ecc_ones < 23U) {
|
||||
if (xor_ecc_ones == 12U) {
|
||||
uint16_t bit_address, byte_address;
|
||||
|
||||
/* Correctable ERROR */
|
||||
bit_address = ((xor_ecc_1b >> 1) & BIT(0)) |
|
||||
((xor_ecc_1b >> 2) & BIT(1)) |
|
||||
((xor_ecc_1b >> 3) & BIT(2));
|
||||
|
||||
byte_address = ((xor_ecc_1b >> 7) & BIT(0)) |
|
||||
((xor_ecc_2b) & BIT(1)) |
|
||||
((xor_ecc_2b >> 1) & BIT(2)) |
|
||||
((xor_ecc_2b >> 2) & BIT(3)) |
|
||||
((xor_ecc_2b >> 3) & BIT(4)) |
|
||||
((xor_ecc_3b << 4) & BIT(5)) |
|
||||
((xor_ecc_3b << 3) & BIT(6)) |
|
||||
((xor_ecc_3b << 2) & BIT(7)) |
|
||||
((xor_ecc_3b << 1) & BIT(8));
|
||||
|
||||
/* Correct bit error in the data */
|
||||
buffer[byte_address] =
|
||||
buffer[byte_address] ^ BIT(bit_address);
|
||||
VERBOSE("Hamming: 1 ECC error corrected\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Non Correctable ERROR */
|
||||
ERROR("%s: Uncorrectable ECC Errors\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ECC ERROR */
|
||||
ERROR("%s: Hamming correction error\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static int stm32_fmc2_ham_calculate(uint8_t *buffer, uint8_t *ecc)
|
||||
{
|
||||
uint32_t heccr;
|
||||
uint64_t timeout = timeout_init_us(TIMEOUT_US_10_MS);
|
||||
|
||||
while ((mmio_read_32(fmc2_base() + FMC2_SR) & FMC2_SR_NWRF) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
heccr = mmio_read_32(fmc2_base() + FMC2_HECCR);
|
||||
|
||||
ecc[0] = heccr;
|
||||
ecc[1] = heccr >> 8;
|
||||
ecc[2] = heccr >> 16;
|
||||
|
||||
/* Disable ECC */
|
||||
stm32_fmc2_set_ecc(false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_fmc2_bch_correct(uint8_t *buffer, unsigned int eccsize)
|
||||
{
|
||||
uint32_t bchdsr0, bchdsr1, bchdsr2, bchdsr3, bchdsr4;
|
||||
uint16_t pos[8];
|
||||
int i, den;
|
||||
uint64_t timeout = timeout_init_us(TIMEOUT_US_10_MS);
|
||||
|
||||
while ((mmio_read_32(fmc2_base() + FMC2_BCHISR) &
|
||||
FMC2_BCHISR_DERF) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
bchdsr0 = mmio_read_32(fmc2_base() + FMC2_BCHDSR0);
|
||||
bchdsr1 = mmio_read_32(fmc2_base() + FMC2_BCHDSR1);
|
||||
bchdsr2 = mmio_read_32(fmc2_base() + FMC2_BCHDSR2);
|
||||
bchdsr3 = mmio_read_32(fmc2_base() + FMC2_BCHDSR3);
|
||||
bchdsr4 = mmio_read_32(fmc2_base() + FMC2_BCHDSR4);
|
||||
|
||||
/* Disable ECC */
|
||||
stm32_fmc2_set_ecc(false);
|
||||
|
||||
/* No error found */
|
||||
if ((bchdsr0 & FMC2_BCHDSR0_DEF) == 0U) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Too many errors detected */
|
||||
if ((bchdsr0 & FMC2_BCHDSR0_DUE) != 0U) {
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
pos[0] = bchdsr1 & FMC2_BCHDSR1_EBP1_MASK;
|
||||
pos[1] = (bchdsr1 & FMC2_BCHDSR1_EBP2_MASK) >> FMC2_BCHDSR1_EBP2_SHIFT;
|
||||
pos[2] = bchdsr2 & FMC2_BCHDSR2_EBP3_MASK;
|
||||
pos[3] = (bchdsr2 & FMC2_BCHDSR2_EBP4_MASK) >> FMC2_BCHDSR2_EBP4_SHIFT;
|
||||
pos[4] = bchdsr3 & FMC2_BCHDSR3_EBP5_MASK;
|
||||
pos[5] = (bchdsr3 & FMC2_BCHDSR3_EBP6_MASK) >> FMC2_BCHDSR3_EBP6_SHIFT;
|
||||
pos[6] = bchdsr4 & FMC2_BCHDSR4_EBP7_MASK;
|
||||
pos[7] = (bchdsr4 & FMC2_BCHDSR4_EBP8_MASK) >> FMC2_BCHDSR4_EBP8_SHIFT;
|
||||
|
||||
den = (bchdsr0 & FMC2_BCHDSR0_DEN_MASK) >> FMC2_BCHDSR0_DEN_SHIFT;
|
||||
for (i = 0; i < den; i++) {
|
||||
if (pos[i] < (eccsize * 8U)) {
|
||||
uint8_t bitmask = BIT(pos[i] % 8U);
|
||||
uint32_t offset = pos[i] / 8U;
|
||||
|
||||
*(buffer + offset) ^= bitmask;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_fmc2_hwctl(struct nand_device *nand)
|
||||
{
|
||||
stm32_fmc2_set_ecc(false);
|
||||
|
||||
if (nand->ecc.max_bit_corr != FMC2_ECC_HAM) {
|
||||
mmio_clrbits_32(fmc2_base() + FMC2_PCR, FMC2_PCR_WEN);
|
||||
mmio_write_32(fmc2_base() + FMC2_BCHICR, FMC2_BCHICR_CLEAR_IRQ);
|
||||
}
|
||||
|
||||
stm32_fmc2_set_ecc(true);
|
||||
}
|
||||
|
||||
static int stm32_fmc2_read_page(struct nand_device *nand,
|
||||
unsigned int page, uintptr_t buffer)
|
||||
{
|
||||
unsigned int eccsize = nand->ecc.size;
|
||||
unsigned int eccbytes = nand->ecc.bytes;
|
||||
unsigned int eccsteps = nand->page_size / eccsize;
|
||||
uint8_t ecc_corr[FMC2_MAX_ECC_BYTES];
|
||||
uint8_t ecc_cal[FMC2_MAX_ECC_BYTES] = {0U};
|
||||
uint8_t *p;
|
||||
unsigned int i;
|
||||
unsigned int s;
|
||||
int ret;
|
||||
|
||||
VERBOSE(">%s page %u buffer %lx\n", __func__, page, buffer);
|
||||
|
||||
ret = nand_read_page_cmd(page, 0U, 0U, 0U);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (s = 0U, i = nand->page_size + FMC2_BBM_LEN, p = (uint8_t *)buffer;
|
||||
s < eccsteps;
|
||||
s++, i += eccbytes, p += eccsize) {
|
||||
stm32_fmc2_hwctl(nand);
|
||||
|
||||
/* Read the NAND page sector (512 bytes) */
|
||||
ret = nand_change_read_column_cmd(s * eccsize, (uintptr_t)p,
|
||||
eccsize);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (nand->ecc.max_bit_corr == FMC2_ECC_HAM) {
|
||||
ret = stm32_fmc2_ham_calculate(p, ecc_cal);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the corresponding ECC bytes */
|
||||
ret = nand_change_read_column_cmd(i, (uintptr_t)ecc_corr,
|
||||
eccbytes);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Correct the data */
|
||||
if (nand->ecc.max_bit_corr == FMC2_ECC_HAM) {
|
||||
ret = stm32_fmc2_ham_correct(p, ecc_corr, ecc_cal);
|
||||
} else {
|
||||
ret = stm32_fmc2_bch_correct(p, eccsize);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_fmc2_read_data(struct nand_device *nand,
|
||||
uint8_t *buff, unsigned int length,
|
||||
bool use_bus8)
|
||||
{
|
||||
uintptr_t data_base = stm32_fmc2.cs[stm32_fmc2.cs_sel].data_base;
|
||||
|
||||
if (use_bus8 && (nand->buswidth == NAND_BUS_WIDTH_16)) {
|
||||
stm32_fmc2_set_buswidth_16(false);
|
||||
}
|
||||
|
||||
if ((((uintptr_t)buff & BIT(0)) != 0U) && (length != 0U)) {
|
||||
*buff = mmio_read_8(data_base);
|
||||
buff += sizeof(uint8_t);
|
||||
length -= sizeof(uint8_t);
|
||||
}
|
||||
|
||||
if ((((uintptr_t)buff & GENMASK_32(1, 0)) != 0U) &&
|
||||
(length >= sizeof(uint16_t))) {
|
||||
*(uint16_t *)buff = mmio_read_16(data_base);
|
||||
buff += sizeof(uint16_t);
|
||||
length -= sizeof(uint16_t);
|
||||
}
|
||||
|
||||
/* 32bit aligned */
|
||||
while (length >= sizeof(uint32_t)) {
|
||||
*(uint32_t *)buff = mmio_read_32(data_base);
|
||||
buff += sizeof(uint32_t);
|
||||
length -= sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* Read remaining bytes */
|
||||
if (length >= sizeof(uint16_t)) {
|
||||
*(uint16_t *)buff = mmio_read_16(data_base);
|
||||
buff += sizeof(uint16_t);
|
||||
length -= sizeof(uint16_t);
|
||||
}
|
||||
|
||||
if (length != 0U) {
|
||||
*buff = mmio_read_8(data_base);
|
||||
}
|
||||
|
||||
if (use_bus8 && (nand->buswidth == NAND_BUS_WIDTH_16)) {
|
||||
/* Reconfigure bus width to 16-bit */
|
||||
stm32_fmc2_set_buswidth_16(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void stm32_fmc2_write_data(struct nand_device *nand,
|
||||
uint8_t *buff, unsigned int length,
|
||||
bool use_bus8)
|
||||
{
|
||||
uintptr_t data_base = stm32_fmc2.cs[stm32_fmc2.cs_sel].data_base;
|
||||
|
||||
if (use_bus8 && (nand->buswidth == NAND_BUS_WIDTH_16)) {
|
||||
/* Reconfigure bus width to 8-bit */
|
||||
stm32_fmc2_set_buswidth_16(false);
|
||||
}
|
||||
|
||||
if ((((uintptr_t)buff & BIT(0)) != 0U) && (length != 0U)) {
|
||||
mmio_write_8(data_base, *buff);
|
||||
buff += sizeof(uint8_t);
|
||||
length -= sizeof(uint8_t);
|
||||
}
|
||||
|
||||
if ((((uintptr_t)buff & GENMASK_32(1, 0)) != 0U) &&
|
||||
(length >= sizeof(uint16_t))) {
|
||||
mmio_write_16(data_base, *(uint16_t *)buff);
|
||||
buff += sizeof(uint16_t);
|
||||
length -= sizeof(uint16_t);
|
||||
}
|
||||
|
||||
/* 32bits aligned */
|
||||
while (length >= sizeof(uint32_t)) {
|
||||
mmio_write_32(data_base, *(uint32_t *)buff);
|
||||
buff += sizeof(uint32_t);
|
||||
length -= sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* Read remaining bytes */
|
||||
if (length >= sizeof(uint16_t)) {
|
||||
mmio_write_16(data_base, *(uint16_t *)buff);
|
||||
buff += sizeof(uint16_t);
|
||||
length -= sizeof(uint16_t);
|
||||
}
|
||||
|
||||
if (length != 0U) {
|
||||
mmio_write_8(data_base, *buff);
|
||||
}
|
||||
|
||||
if (use_bus8 && (nand->buswidth == NAND_BUS_WIDTH_16)) {
|
||||
/* Reconfigure bus width to 16-bit */
|
||||
stm32_fmc2_set_buswidth_16(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void stm32_fmc2_ctrl_init(void)
|
||||
{
|
||||
uint32_t pcr = mmio_read_32(fmc2_base() + FMC2_PCR);
|
||||
uint32_t bcr1 = mmio_read_32(fmc2_base() + FMC2_BCR1);
|
||||
|
||||
/* Enable wait feature and NAND flash memory bank */
|
||||
pcr |= FMC2_PCR_PWAITEN;
|
||||
pcr |= FMC2_PCR_PBKEN;
|
||||
|
||||
/* Set buswidth to 8 bits mode for identification */
|
||||
pcr &= ~FMC2_PCR_PWID_MASK;
|
||||
|
||||
/* ECC logic is disabled */
|
||||
pcr &= ~FMC2_PCR_ECCEN;
|
||||
|
||||
/* Default mode */
|
||||
pcr &= ~FMC2_PCR_ECCALG;
|
||||
pcr &= ~FMC2_PCR_BCHECC;
|
||||
pcr &= ~FMC2_PCR_WEN;
|
||||
|
||||
/* Set default ECC sector size */
|
||||
pcr &= ~FMC2_PCR_ECCSS_MASK;
|
||||
pcr |= FMC2_PCR_ECCSS(FMC2_PCR_ECCSS_2048);
|
||||
|
||||
/* Set default TCLR/TAR timings */
|
||||
pcr &= ~FMC2_PCR_TCLR_MASK;
|
||||
pcr |= FMC2_PCR_TCLR(FMC2_PCR_TCLR_DEFAULT);
|
||||
pcr &= ~FMC2_PCR_TAR_MASK;
|
||||
pcr |= FMC2_PCR_TAR(FMC2_PCR_TAR_DEFAULT);
|
||||
|
||||
/* Enable FMC2 controller */
|
||||
bcr1 |= FMC2_BCR1_FMC2EN;
|
||||
|
||||
mmio_write_32(fmc2_base() + FMC2_BCR1, bcr1);
|
||||
mmio_write_32(fmc2_base() + FMC2_PCR, pcr);
|
||||
mmio_write_32(fmc2_base() + FMC2_PMEM, FMC2_PMEM_DEFAULT);
|
||||
mmio_write_32(fmc2_base() + FMC2_PATT, FMC2_PATT_DEFAULT);
|
||||
}
|
||||
|
||||
static int stm32_fmc2_exec(struct nand_req *req)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (req->type & NAND_REQ_MASK) {
|
||||
case NAND_REQ_CMD:
|
||||
VERBOSE("Write CMD %x\n", (uint8_t)req->type);
|
||||
mmio_write_8(stm32_fmc2.cs[stm32_fmc2.cs_sel].cmd_base,
|
||||
(uint8_t)req->type);
|
||||
break;
|
||||
case NAND_REQ_ADDR:
|
||||
VERBOSE("Write ADDR %x\n", *(req->addr));
|
||||
mmio_write_8(stm32_fmc2.cs[stm32_fmc2.cs_sel].addr_base,
|
||||
*(req->addr));
|
||||
break;
|
||||
case NAND_REQ_DATAIN:
|
||||
VERBOSE("Read data\n");
|
||||
stm32_fmc2_read_data(req->nand, req->addr, req->length,
|
||||
((req->type & NAND_REQ_BUS_WIDTH_8) !=
|
||||
0U));
|
||||
break;
|
||||
case NAND_REQ_DATAOUT:
|
||||
VERBOSE("Write data\n");
|
||||
stm32_fmc2_write_data(req->nand, req->addr, req->length,
|
||||
((req->type & NAND_REQ_BUS_WIDTH_8) !=
|
||||
0U));
|
||||
break;
|
||||
case NAND_REQ_WAIT:
|
||||
VERBOSE("WAIT Ready\n");
|
||||
ret = nand_wait_ready(req->delay_ms);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void stm32_fmc2_setup(struct nand_device *nand)
|
||||
{
|
||||
uint32_t pcr = mmio_read_32(fmc2_base() + FMC2_PCR);
|
||||
|
||||
/* Set buswidth */
|
||||
pcr &= ~FMC2_PCR_PWID_MASK;
|
||||
if (nand->buswidth == NAND_BUS_WIDTH_16) {
|
||||
pcr |= FMC2_PCR_PWID(FMC2_PCR_PWID_16);
|
||||
}
|
||||
|
||||
if (nand->ecc.mode == NAND_ECC_HW) {
|
||||
nand->mtd_read_page = stm32_fmc2_read_page;
|
||||
|
||||
pcr &= ~FMC2_PCR_ECCALG;
|
||||
pcr &= ~FMC2_PCR_BCHECC;
|
||||
|
||||
pcr &= ~FMC2_PCR_ECCSS_MASK;
|
||||
pcr |= FMC2_PCR_ECCSS(FMC2_PCR_ECCSS_512);
|
||||
|
||||
switch (nand->ecc.max_bit_corr) {
|
||||
case FMC2_ECC_HAM:
|
||||
nand->ecc.bytes = 3;
|
||||
break;
|
||||
case FMC2_ECC_BCH8:
|
||||
pcr |= FMC2_PCR_ECCALG;
|
||||
pcr |= FMC2_PCR_BCHECC;
|
||||
nand->ecc.bytes = 13;
|
||||
break;
|
||||
default:
|
||||
/* Use FMC2 ECC BCH4 */
|
||||
pcr |= FMC2_PCR_ECCALG;
|
||||
nand->ecc.bytes = 7;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((nand->buswidth & NAND_BUS_WIDTH_16) != 0) {
|
||||
nand->ecc.bytes++;
|
||||
}
|
||||
}
|
||||
|
||||
mmio_write_32(stm32_fmc2.reg_base + FMC2_PCR, pcr);
|
||||
}
|
||||
|
||||
static const struct nand_ctrl_ops ctrl_ops = {
|
||||
.setup = stm32_fmc2_setup,
|
||||
.exec = stm32_fmc2_exec
|
||||
};
|
||||
|
||||
int stm32_fmc2_init(void)
|
||||
{
|
||||
int fmc_ebi_node;
|
||||
int fmc_nfc_node;
|
||||
int fmc_flash_node = 0;
|
||||
int nchips = 0;
|
||||
unsigned int i;
|
||||
void *fdt = NULL;
|
||||
const fdt32_t *cuint;
|
||||
struct dt_node_info info;
|
||||
uintptr_t bank_address[MAX_BANK] = { 0, 0, 0, 0, 0 };
|
||||
uint8_t bank_assigned = 0;
|
||||
uint8_t bank;
|
||||
int ret;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
fmc_ebi_node = dt_get_node(&info, -1, DT_FMC2_EBI_COMPAT);
|
||||
if (fmc_ebi_node < 0) {
|
||||
return fmc_ebi_node;
|
||||
}
|
||||
|
||||
if (info.status == DT_DISABLED) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
stm32_fmc2.reg_base = info.base;
|
||||
|
||||
if ((info.clock < 0) || (info.reset < 0)) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
stm32_fmc2.clock_id = (unsigned long)info.clock;
|
||||
stm32_fmc2.reset_id = (unsigned int)info.reset;
|
||||
|
||||
cuint = fdt_getprop(fdt, fmc_ebi_node, "ranges", NULL);
|
||||
if (cuint == NULL) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
for (i = 0U; i < MAX_BANK; i++) {
|
||||
bank = fdt32_to_cpu(*cuint);
|
||||
if ((bank >= MAX_BANK) || ((bank_assigned & BIT(bank)) != 0U)) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
bank_assigned |= BIT(bank);
|
||||
bank_address[bank] = fdt32_to_cpu(*(cuint + 2));
|
||||
cuint += 4;
|
||||
}
|
||||
|
||||
/* Pinctrl initialization */
|
||||
if (dt_set_pinctrl_config(fmc_ebi_node) != 0) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
/* Parse NFC controller node */
|
||||
fmc_nfc_node = fdt_node_offset_by_compatible(fdt, fmc_ebi_node,
|
||||
DT_FMC2_NFC_COMPAT);
|
||||
if (fmc_nfc_node < 0) {
|
||||
return fmc_nfc_node;
|
||||
}
|
||||
|
||||
if (fdt_get_status(fmc_nfc_node) == DT_DISABLED) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, fmc_nfc_node, "reg", NULL);
|
||||
if (cuint == NULL) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
for (i = 0U; i < MAX_CS; i++) {
|
||||
bank = fdt32_to_cpu(*cuint);
|
||||
if (bank >= MAX_BANK) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
stm32_fmc2.cs[i].data_base = fdt32_to_cpu(*(cuint + 1)) +
|
||||
bank_address[bank];
|
||||
|
||||
bank = fdt32_to_cpu(*(cuint + 3));
|
||||
if (bank >= MAX_BANK) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
stm32_fmc2.cs[i].cmd_base = fdt32_to_cpu(*(cuint + 4)) +
|
||||
bank_address[bank];
|
||||
|
||||
bank = fdt32_to_cpu(*(cuint + 6));
|
||||
if (bank >= MAX_BANK) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
stm32_fmc2.cs[i].addr_base = fdt32_to_cpu(*(cuint + 7)) +
|
||||
bank_address[bank];
|
||||
|
||||
cuint += 9;
|
||||
}
|
||||
|
||||
/* Parse flash nodes */
|
||||
fdt_for_each_subnode(fmc_flash_node, fdt, fmc_nfc_node) {
|
||||
nchips++;
|
||||
}
|
||||
|
||||
if (nchips != 1) {
|
||||
WARN("Only one SLC NAND device supported\n");
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
fdt_for_each_subnode(fmc_flash_node, fdt, fmc_nfc_node) {
|
||||
/* Get chip select */
|
||||
cuint = fdt_getprop(fdt, fmc_flash_node, "reg", NULL);
|
||||
if (cuint == NULL) {
|
||||
WARN("Chip select not well defined\n");
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
stm32_fmc2.cs_sel = fdt32_to_cpu(*cuint);
|
||||
if (stm32_fmc2.cs_sel >= MAX_CS) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
VERBOSE("NAND CS %i\n", stm32_fmc2.cs_sel);
|
||||
}
|
||||
|
||||
/* Enable Clock */
|
||||
clk_enable(stm32_fmc2.clock_id);
|
||||
|
||||
/* Reset IP */
|
||||
ret = stm32mp_reset_assert(stm32_fmc2.reset_id, TIMEOUT_US_1_MS);
|
||||
if (ret != 0) {
|
||||
panic();
|
||||
}
|
||||
ret = stm32mp_reset_deassert(stm32_fmc2.reset_id, TIMEOUT_US_1_MS);
|
||||
if (ret != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
/* Setup default IP registers */
|
||||
stm32_fmc2_ctrl_init();
|
||||
|
||||
/* Setup default timings */
|
||||
stm32_fmc2_nand_setup_timing();
|
||||
|
||||
/* Init NAND RAW framework */
|
||||
nand_raw_ctrl_init(&ctrl_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <common/bl_common.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/st/stm32_gpio.h>
|
||||
#include <drivers/st/stm32mp_clkfunc.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils_def.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#define DT_GPIO_BANK_SHIFT 12
|
||||
#define DT_GPIO_BANK_MASK GENMASK(16, 12)
|
||||
#define DT_GPIO_PIN_SHIFT 8
|
||||
#define DT_GPIO_PIN_MASK GENMASK(11, 8)
|
||||
#define DT_GPIO_MODE_MASK GENMASK(7, 0)
|
||||
|
||||
static void set_gpio(uint32_t bank, uint32_t pin, uint32_t mode, uint32_t type,
|
||||
uint32_t speed, uint32_t pull, uint32_t od,
|
||||
uint32_t alternate, uint8_t status);
|
||||
|
||||
/*******************************************************************************
|
||||
* This function gets GPIO bank node in DT.
|
||||
* Returns node offset if status is okay in DT, else return 0
|
||||
******************************************************************************/
|
||||
static int ckeck_gpio_bank(void *fdt, uint32_t bank, int pinctrl_node)
|
||||
{
|
||||
int pinctrl_subnode;
|
||||
uint32_t bank_offset = stm32_get_gpio_bank_offset(bank);
|
||||
|
||||
fdt_for_each_subnode(pinctrl_subnode, fdt, pinctrl_node) {
|
||||
const fdt32_t *cuint;
|
||||
|
||||
if (fdt_getprop(fdt, pinctrl_subnode,
|
||||
"gpio-controller", NULL) == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, pinctrl_subnode, "reg", NULL);
|
||||
if (cuint == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((fdt32_to_cpu(*cuint) == bank_offset) &&
|
||||
(fdt_get_status(pinctrl_subnode) != DT_DISABLED)) {
|
||||
return pinctrl_subnode;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function gets the pin settings from DT information.
|
||||
* When analyze and parsing is done, set the GPIO registers.
|
||||
* Returns 0 on success and a negative FDT error code on failure.
|
||||
******************************************************************************/
|
||||
static int dt_set_gpio_config(void *fdt, int node, uint8_t status)
|
||||
{
|
||||
const fdt32_t *cuint, *slewrate;
|
||||
int len;
|
||||
int pinctrl_node;
|
||||
uint32_t i;
|
||||
uint32_t speed = GPIO_SPEED_LOW;
|
||||
uint32_t pull = GPIO_NO_PULL;
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "pinmux", &len);
|
||||
if (cuint == NULL) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
pinctrl_node = fdt_parent_offset(fdt, fdt_parent_offset(fdt, node));
|
||||
if (pinctrl_node < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
slewrate = fdt_getprop(fdt, node, "slew-rate", NULL);
|
||||
if (slewrate != NULL) {
|
||||
speed = fdt32_to_cpu(*slewrate);
|
||||
}
|
||||
|
||||
if (fdt_getprop(fdt, node, "bias-pull-up", NULL) != NULL) {
|
||||
pull = GPIO_PULL_UP;
|
||||
} else if (fdt_getprop(fdt, node, "bias-pull-down", NULL) != NULL) {
|
||||
pull = GPIO_PULL_DOWN;
|
||||
} else {
|
||||
VERBOSE("No bias configured in node %d\n", node);
|
||||
}
|
||||
|
||||
for (i = 0U; i < ((uint32_t)len / sizeof(uint32_t)); i++) {
|
||||
uint32_t pincfg;
|
||||
uint32_t bank;
|
||||
uint32_t pin;
|
||||
uint32_t mode;
|
||||
uint32_t alternate = GPIO_ALTERNATE_(0);
|
||||
uint32_t type;
|
||||
uint32_t od = GPIO_OD_OUTPUT_LOW;
|
||||
int bank_node;
|
||||
int clk;
|
||||
|
||||
pincfg = fdt32_to_cpu(*cuint);
|
||||
cuint++;
|
||||
|
||||
bank = (pincfg & DT_GPIO_BANK_MASK) >> DT_GPIO_BANK_SHIFT;
|
||||
|
||||
pin = (pincfg & DT_GPIO_PIN_MASK) >> DT_GPIO_PIN_SHIFT;
|
||||
|
||||
mode = pincfg & DT_GPIO_MODE_MASK;
|
||||
|
||||
switch (mode) {
|
||||
case 0:
|
||||
mode = GPIO_MODE_INPUT;
|
||||
break;
|
||||
case 1 ... 16:
|
||||
alternate = mode - 1U;
|
||||
mode = GPIO_MODE_ALTERNATE;
|
||||
break;
|
||||
case 17:
|
||||
mode = GPIO_MODE_ANALOG;
|
||||
break;
|
||||
default:
|
||||
mode = GPIO_MODE_OUTPUT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fdt_getprop(fdt, node, "drive-open-drain", NULL) != NULL) {
|
||||
type = GPIO_TYPE_OPEN_DRAIN;
|
||||
} else {
|
||||
type = GPIO_TYPE_PUSH_PULL;
|
||||
}
|
||||
|
||||
if (fdt_getprop(fdt, node, "output-high", NULL) != NULL) {
|
||||
if (mode == GPIO_MODE_INPUT) {
|
||||
mode = GPIO_MODE_OUTPUT;
|
||||
od = GPIO_OD_OUTPUT_HIGH;
|
||||
}
|
||||
}
|
||||
|
||||
if (fdt_getprop(fdt, node, "output-low", NULL) != NULL) {
|
||||
if (mode == GPIO_MODE_INPUT) {
|
||||
mode = GPIO_MODE_OUTPUT;
|
||||
od = GPIO_OD_OUTPUT_LOW;
|
||||
}
|
||||
}
|
||||
|
||||
bank_node = ckeck_gpio_bank(fdt, bank, pinctrl_node);
|
||||
if (bank_node == 0) {
|
||||
ERROR("PINCTRL inconsistent in DT\n");
|
||||
panic();
|
||||
}
|
||||
|
||||
clk = fdt_get_clock_id(bank_node);
|
||||
if (clk < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
/* Platform knows the clock: assert it is okay */
|
||||
assert((unsigned long)clk == stm32_get_gpio_bank_clock(bank));
|
||||
|
||||
set_gpio(bank, pin, mode, type, speed, pull, od, alternate, status);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* This function gets the pin settings from DT information.
|
||||
* When analyze and parsing is done, set the GPIO registers.
|
||||
* Returns 0 on success and a negative FDT/ERRNO error code on failure.
|
||||
******************************************************************************/
|
||||
int dt_set_pinctrl_config(int node)
|
||||
{
|
||||
const fdt32_t *cuint;
|
||||
int lenp;
|
||||
uint32_t i;
|
||||
uint8_t status;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
status = fdt_get_status(node);
|
||||
if (status == DT_DISABLED) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "pinctrl-0", &lenp);
|
||||
if (cuint == NULL) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
for (i = 0; i < ((uint32_t)lenp / 4U); i++) {
|
||||
int p_node, p_subnode;
|
||||
|
||||
p_node = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*cuint));
|
||||
if (p_node < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
fdt_for_each_subnode(p_subnode, fdt, p_node) {
|
||||
int ret = dt_set_gpio_config(fdt, p_subnode, status);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
cuint++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_gpio(uint32_t bank, uint32_t pin, uint32_t mode, uint32_t type,
|
||||
uint32_t speed, uint32_t pull, uint32_t od,
|
||||
uint32_t alternate, uint8_t status)
|
||||
{
|
||||
uintptr_t base = stm32_get_gpio_bank_base(bank);
|
||||
unsigned long clock = stm32_get_gpio_bank_clock(bank);
|
||||
|
||||
assert(pin <= GPIO_PIN_MAX);
|
||||
|
||||
clk_enable(clock);
|
||||
|
||||
mmio_clrsetbits_32(base + GPIO_MODE_OFFSET,
|
||||
(uint32_t)GPIO_MODE_MASK << (pin << 1),
|
||||
mode << (pin << 1));
|
||||
|
||||
mmio_clrsetbits_32(base + GPIO_TYPE_OFFSET,
|
||||
(uint32_t)GPIO_TYPE_MASK << pin,
|
||||
type << pin);
|
||||
|
||||
mmio_clrsetbits_32(base + GPIO_SPEED_OFFSET,
|
||||
(uint32_t)GPIO_SPEED_MASK << (pin << 1),
|
||||
speed << (pin << 1));
|
||||
|
||||
mmio_clrsetbits_32(base + GPIO_PUPD_OFFSET,
|
||||
(uint32_t)GPIO_PULL_MASK << (pin << 1),
|
||||
pull << (pin << 1));
|
||||
|
||||
if (pin < GPIO_ALT_LOWER_LIMIT) {
|
||||
mmio_clrsetbits_32(base + GPIO_AFRL_OFFSET,
|
||||
(uint32_t)GPIO_ALTERNATE_MASK << (pin << 2),
|
||||
alternate << (pin << 2));
|
||||
} else {
|
||||
size_t shift = (pin - GPIO_ALT_LOWER_LIMIT) << 2;
|
||||
|
||||
mmio_clrsetbits_32(base + GPIO_AFRH_OFFSET,
|
||||
(uint32_t)GPIO_ALTERNATE_MASK << shift,
|
||||
alternate << shift);
|
||||
}
|
||||
|
||||
mmio_clrsetbits_32(base + GPIO_OD_OFFSET,
|
||||
(uint32_t)GPIO_OD_MASK << pin,
|
||||
od << pin);
|
||||
|
||||
VERBOSE("GPIO %u mode set to 0x%x\n", bank,
|
||||
mmio_read_32(base + GPIO_MODE_OFFSET));
|
||||
VERBOSE("GPIO %u type set to 0x%x\n", bank,
|
||||
mmio_read_32(base + GPIO_TYPE_OFFSET));
|
||||
VERBOSE("GPIO %u speed set to 0x%x\n", bank,
|
||||
mmio_read_32(base + GPIO_SPEED_OFFSET));
|
||||
VERBOSE("GPIO %u mode pull to 0x%x\n", bank,
|
||||
mmio_read_32(base + GPIO_PUPD_OFFSET));
|
||||
VERBOSE("GPIO %u mode alternate low to 0x%x\n", bank,
|
||||
mmio_read_32(base + GPIO_AFRL_OFFSET));
|
||||
VERBOSE("GPIO %u mode alternate high to 0x%x\n", bank,
|
||||
mmio_read_32(base + GPIO_AFRH_OFFSET));
|
||||
VERBOSE("GPIO %u output data set to 0x%x\n", bank,
|
||||
mmio_read_32(base + GPIO_OD_OFFSET));
|
||||
|
||||
clk_disable(clock);
|
||||
|
||||
if (status == DT_SECURE) {
|
||||
stm32mp_register_secure_gpio(bank, pin);
|
||||
#if !IMAGE_BL2
|
||||
set_gpio_secure_cfg(bank, pin, true);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
stm32mp_register_non_secure_gpio(bank, pin);
|
||||
#if !IMAGE_BL2
|
||||
set_gpio_secure_cfg(bank, pin, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void set_gpio_secure_cfg(uint32_t bank, uint32_t pin, bool secure)
|
||||
{
|
||||
uintptr_t base = stm32_get_gpio_bank_base(bank);
|
||||
unsigned long clock = stm32_get_gpio_bank_clock(bank);
|
||||
|
||||
assert(pin <= GPIO_PIN_MAX);
|
||||
|
||||
clk_enable(clock);
|
||||
|
||||
if (secure) {
|
||||
mmio_setbits_32(base + GPIO_SECR_OFFSET, BIT(pin));
|
||||
} else {
|
||||
mmio_clrbits_32(base + GPIO_SECR_OFFSET, BIT(pin));
|
||||
}
|
||||
|
||||
clk_disable(clock);
|
||||
}
|
||||
|
||||
void set_gpio_reset_cfg(uint32_t bank, uint32_t pin)
|
||||
{
|
||||
set_gpio(bank, pin, GPIO_MODE_ANALOG, GPIO_TYPE_PUSH_PULL,
|
||||
GPIO_SPEED_LOW, GPIO_NO_PULL, GPIO_OD_OUTPUT_LOW,
|
||||
GPIO_ALTERNATE_(0), DT_DISABLED);
|
||||
set_gpio_secure_cfg(bank, pin, stm32_gpio_is_secure_at_reset(bank));
|
||||
}
|
||||
@@ -0,0 +1,982 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2021, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32_gpio.h>
|
||||
#include <drivers/st/stm32_i2c.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils.h>
|
||||
|
||||
/* STM32 I2C registers offsets */
|
||||
#define I2C_CR1 0x00U
|
||||
#define I2C_CR2 0x04U
|
||||
#define I2C_OAR1 0x08U
|
||||
#define I2C_OAR2 0x0CU
|
||||
#define I2C_TIMINGR 0x10U
|
||||
#define I2C_TIMEOUTR 0x14U
|
||||
#define I2C_ISR 0x18U
|
||||
#define I2C_ICR 0x1CU
|
||||
#define I2C_PECR 0x20U
|
||||
#define I2C_RXDR 0x24U
|
||||
#define I2C_TXDR 0x28U
|
||||
|
||||
#define TIMINGR_CLEAR_MASK 0xF0FFFFFFU
|
||||
|
||||
#define MAX_NBYTE_SIZE 255U
|
||||
|
||||
#define I2C_NSEC_PER_SEC 1000000000L
|
||||
|
||||
/* I2C Timing hard-coded value, for I2C clock source is HSI at 64MHz */
|
||||
#define I2C_TIMING 0x10D07DB5
|
||||
|
||||
static void notif_i2c_timeout(struct i2c_handle_s *hi2c)
|
||||
{
|
||||
hi2c->i2c_err |= I2C_ERROR_TIMEOUT;
|
||||
hi2c->i2c_mode = I2C_MODE_NONE;
|
||||
hi2c->i2c_state = I2C_STATE_READY;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Configure I2C Analog noise filter.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C peripheral.
|
||||
* @param analog_filter: New state of the Analog filter
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_config_analog_filter(struct i2c_handle_s *hi2c,
|
||||
uint32_t analog_filter)
|
||||
{
|
||||
if ((hi2c->i2c_state != I2C_STATE_READY) || (hi2c->lock != 0U)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
hi2c->lock = 1;
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_BUSY;
|
||||
|
||||
/* Disable the selected I2C peripheral */
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_CR1, I2C_CR1_PE);
|
||||
|
||||
/* Reset I2Cx ANOFF bit */
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_CR1, I2C_CR1_ANFOFF);
|
||||
|
||||
/* Set analog filter bit*/
|
||||
mmio_setbits_32(hi2c->i2c_base_addr + I2C_CR1, analog_filter);
|
||||
|
||||
/* Enable the selected I2C peripheral */
|
||||
mmio_setbits_32(hi2c->i2c_base_addr + I2C_CR1, I2C_CR1_PE);
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_READY;
|
||||
|
||||
hi2c->lock = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Get I2C setup information from the device tree and set pinctrl
|
||||
* configuration.
|
||||
* @param fdt: Pointer to the device tree
|
||||
* @param node: I2C node offset
|
||||
* @param init: Ref to the initialization configuration structure
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
int stm32_i2c_get_setup_from_fdt(void *fdt, int node,
|
||||
struct stm32_i2c_init_s *init)
|
||||
{
|
||||
const fdt32_t *cuint;
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "i2c-scl-rising-time-ns", NULL);
|
||||
if (cuint == NULL) {
|
||||
init->rise_time = STM32_I2C_RISE_TIME_DEFAULT;
|
||||
} else {
|
||||
init->rise_time = fdt32_to_cpu(*cuint);
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "i2c-scl-falling-time-ns", NULL);
|
||||
if (cuint == NULL) {
|
||||
init->fall_time = STM32_I2C_FALL_TIME_DEFAULT;
|
||||
} else {
|
||||
init->fall_time = fdt32_to_cpu(*cuint);
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "clock-frequency", NULL);
|
||||
if (cuint == NULL) {
|
||||
init->speed_mode = STM32_I2C_SPEED_DEFAULT;
|
||||
} else {
|
||||
switch (fdt32_to_cpu(*cuint)) {
|
||||
case STANDARD_RATE:
|
||||
init->speed_mode = I2C_SPEED_STANDARD;
|
||||
break;
|
||||
case FAST_RATE:
|
||||
init->speed_mode = I2C_SPEED_FAST;
|
||||
break;
|
||||
case FAST_PLUS_RATE:
|
||||
init->speed_mode = I2C_SPEED_FAST_PLUS;
|
||||
break;
|
||||
default:
|
||||
init->speed_mode = STM32_I2C_SPEED_DEFAULT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dt_set_pinctrl_config(node);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Initialize the I2C device.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param init_data: Initialization configuration structure
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
int stm32_i2c_init(struct i2c_handle_s *hi2c,
|
||||
struct stm32_i2c_init_s *init_data)
|
||||
{
|
||||
int rc = 0;
|
||||
uint32_t timing = I2C_TIMING;
|
||||
|
||||
if (hi2c == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (hi2c->i2c_state == I2C_STATE_RESET) {
|
||||
hi2c->lock = 0;
|
||||
}
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_BUSY;
|
||||
|
||||
clk_enable(hi2c->clock);
|
||||
|
||||
/* Disable the selected I2C peripheral */
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_CR1, I2C_CR1_PE);
|
||||
|
||||
/* Configure I2Cx: Frequency range */
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_TIMINGR,
|
||||
timing & TIMINGR_CLEAR_MASK);
|
||||
|
||||
/* Disable Own Address1 before set the Own Address1 configuration */
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_OAR1, I2C_OAR1_OA1EN);
|
||||
|
||||
/* Configure I2Cx: Own Address1 and ack own address1 mode */
|
||||
if (init_data->addressing_mode == I2C_ADDRESSINGMODE_7BIT) {
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_OAR1,
|
||||
I2C_OAR1_OA1EN | init_data->own_address1);
|
||||
} else { /* I2C_ADDRESSINGMODE_10BIT */
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_OAR1,
|
||||
I2C_OAR1_OA1EN | I2C_OAR1_OA1MODE |
|
||||
init_data->own_address1);
|
||||
}
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_CR2, 0);
|
||||
|
||||
/* Configure I2Cx: Addressing Master mode */
|
||||
if (init_data->addressing_mode == I2C_ADDRESSINGMODE_10BIT) {
|
||||
mmio_setbits_32(hi2c->i2c_base_addr + I2C_CR2, I2C_CR2_ADD10);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable the AUTOEND by default, and enable NACK
|
||||
* (should be disabled only during Slave process).
|
||||
*/
|
||||
mmio_setbits_32(hi2c->i2c_base_addr + I2C_CR2,
|
||||
I2C_CR2_AUTOEND | I2C_CR2_NACK);
|
||||
|
||||
/* Disable Own Address2 before set the Own Address2 configuration */
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_OAR2, I2C_DUALADDRESS_ENABLE);
|
||||
|
||||
/* Configure I2Cx: Dual mode and Own Address2 */
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_OAR2,
|
||||
init_data->dual_address_mode |
|
||||
init_data->own_address2 |
|
||||
(init_data->own_address2_masks << 8));
|
||||
|
||||
/* Configure I2Cx: Generalcall and NoStretch mode */
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_CR1,
|
||||
init_data->general_call_mode |
|
||||
init_data->no_stretch_mode);
|
||||
|
||||
/* Enable the selected I2C peripheral */
|
||||
mmio_setbits_32(hi2c->i2c_base_addr + I2C_CR1, I2C_CR1_PE);
|
||||
|
||||
hi2c->i2c_err = I2C_ERROR_NONE;
|
||||
hi2c->i2c_state = I2C_STATE_READY;
|
||||
hi2c->i2c_mode = I2C_MODE_NONE;
|
||||
|
||||
rc = i2c_config_analog_filter(hi2c, init_data->analog_filter ?
|
||||
I2C_ANALOGFILTER_ENABLE :
|
||||
I2C_ANALOGFILTER_DISABLE);
|
||||
if (rc != 0) {
|
||||
ERROR("Cannot initialize I2C analog filter (%d)\n", rc);
|
||||
clk_disable(hi2c->clock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
clk_disable(hi2c->clock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief I2C Tx data register flush process.
|
||||
* @param hi2c: I2C handle
|
||||
* @retval None
|
||||
*/
|
||||
static void i2c_flush_txdr(struct i2c_handle_s *hi2c)
|
||||
{
|
||||
/*
|
||||
* If a pending TXIS flag is set,
|
||||
* write a dummy data in TXDR to clear it.
|
||||
*/
|
||||
if ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) & I2C_FLAG_TXIS) !=
|
||||
0U) {
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_TXDR, 0);
|
||||
}
|
||||
|
||||
/* Flush TX register if not empty */
|
||||
if ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) & I2C_FLAG_TXE) ==
|
||||
0U) {
|
||||
mmio_setbits_32(hi2c->i2c_base_addr + I2C_ISR,
|
||||
I2C_FLAG_TXE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief This function handles I2C Communication timeout.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param flag: Specifies the I2C flag to check
|
||||
* @param awaited_value: The awaited bit value for the flag (0 or 1)
|
||||
* @param timeout_ref: Reference to target timeout
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_wait_flag(struct i2c_handle_s *hi2c, uint32_t flag,
|
||||
uint8_t awaited_value, uint64_t timeout_ref)
|
||||
{
|
||||
for ( ; ; ) {
|
||||
uint32_t isr = mmio_read_32(hi2c->i2c_base_addr + I2C_ISR);
|
||||
|
||||
if (!!(isr & flag) != !!awaited_value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
notif_i2c_timeout(hi2c);
|
||||
hi2c->lock = 0;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief This function handles Acknowledge failed detection during
|
||||
* an I2C Communication.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param timeout_ref: Reference to target timeout
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_ack_failed(struct i2c_handle_s *hi2c, uint64_t timeout_ref)
|
||||
{
|
||||
if ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) & I2C_FLAG_AF) == 0U) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait until STOP Flag is reset.
|
||||
* AutoEnd should be initiate after AF.
|
||||
*/
|
||||
while ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) &
|
||||
I2C_FLAG_STOPF) == 0U) {
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
notif_i2c_timeout(hi2c);
|
||||
hi2c->lock = 0;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR, I2C_FLAG_AF);
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR, I2C_FLAG_STOPF);
|
||||
|
||||
i2c_flush_txdr(hi2c);
|
||||
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_CR2, I2C_RESET_CR2);
|
||||
|
||||
hi2c->i2c_err |= I2C_ERROR_AF;
|
||||
hi2c->i2c_state = I2C_STATE_READY;
|
||||
hi2c->i2c_mode = I2C_MODE_NONE;
|
||||
|
||||
hi2c->lock = 0;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief This function handles I2C Communication timeout for specific usage
|
||||
* of TXIS flag.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param timeout_ref: Reference to target timeout
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_wait_txis(struct i2c_handle_s *hi2c, uint64_t timeout_ref)
|
||||
{
|
||||
while ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) &
|
||||
I2C_FLAG_TXIS) == 0U) {
|
||||
if (i2c_ack_failed(hi2c, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
notif_i2c_timeout(hi2c);
|
||||
hi2c->lock = 0;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief This function handles I2C Communication timeout for specific
|
||||
* usage of STOP flag.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param timeout_ref: Reference to target timeout
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_wait_stop(struct i2c_handle_s *hi2c, uint64_t timeout_ref)
|
||||
{
|
||||
while ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) &
|
||||
I2C_FLAG_STOPF) == 0U) {
|
||||
if (i2c_ack_failed(hi2c, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
notif_i2c_timeout(hi2c);
|
||||
hi2c->lock = 0;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Handles I2Cx communication when starting transfer or during transfer
|
||||
* (TC or TCR flag are set).
|
||||
* @param hi2c: I2C handle
|
||||
* @param dev_addr: Specifies the slave address to be programmed
|
||||
* @param size: Specifies the number of bytes to be programmed.
|
||||
* This parameter must be a value between 0 and 255.
|
||||
* @param i2c_mode: New state of the I2C START condition generation.
|
||||
* This parameter can be one of the following values:
|
||||
* @arg @ref I2C_RELOAD_MODE: Enable Reload mode.
|
||||
* @arg @ref I2C_AUTOEND_MODE: Enable Automatic end mode.
|
||||
* @arg @ref I2C_SOFTEND_MODE: Enable Software end mode.
|
||||
* @param request: New state of the I2C START condition generation.
|
||||
* This parameter can be one of the following values:
|
||||
* @arg @ref I2C_NO_STARTSTOP: Don't Generate stop and start condition.
|
||||
* @arg @ref I2C_GENERATE_STOP: Generate stop condition
|
||||
* (size should be set to 0).
|
||||
* @arg @ref I2C_GENERATE_START_READ: Generate Restart for read request.
|
||||
* @arg @ref I2C_GENERATE_START_WRITE: Generate Restart for write request.
|
||||
* @retval None
|
||||
*/
|
||||
static void i2c_transfer_config(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint16_t size, uint32_t i2c_mode,
|
||||
uint32_t request)
|
||||
{
|
||||
uint32_t clr_value, set_value;
|
||||
|
||||
clr_value = (I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RELOAD |
|
||||
I2C_CR2_AUTOEND | I2C_CR2_START | I2C_CR2_STOP) |
|
||||
(I2C_CR2_RD_WRN & (request >> (31U - I2C_CR2_RD_WRN_OFFSET)));
|
||||
|
||||
set_value = ((uint32_t)dev_addr & I2C_CR2_SADD) |
|
||||
(((uint32_t)size << I2C_CR2_NBYTES_OFFSET) & I2C_CR2_NBYTES) |
|
||||
i2c_mode | request;
|
||||
|
||||
mmio_clrsetbits_32(hi2c->i2c_base_addr + I2C_CR2, clr_value, set_value);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Master sends target device address followed by internal memory
|
||||
* address for write request.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param mem_addr: Internal memory address
|
||||
* @param mem_add_size: Size of internal memory address
|
||||
* @param timeout_ref: Reference to target timeout
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_request_memory_write(struct i2c_handle_s *hi2c,
|
||||
uint16_t dev_addr, uint16_t mem_addr,
|
||||
uint16_t mem_add_size, uint64_t timeout_ref)
|
||||
{
|
||||
i2c_transfer_config(hi2c, dev_addr, mem_add_size, I2C_RELOAD_MODE,
|
||||
I2C_GENERATE_START_WRITE);
|
||||
|
||||
if (i2c_wait_txis(hi2c, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (mem_add_size == I2C_MEMADD_SIZE_8BIT) {
|
||||
/* Send Memory Address */
|
||||
mmio_write_8(hi2c->i2c_base_addr + I2C_TXDR,
|
||||
(uint8_t)(mem_addr & 0x00FFU));
|
||||
} else {
|
||||
/* Send MSB of Memory Address */
|
||||
mmio_write_8(hi2c->i2c_base_addr + I2C_TXDR,
|
||||
(uint8_t)((mem_addr & 0xFF00U) >> 8));
|
||||
|
||||
if (i2c_wait_txis(hi2c, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Send LSB of Memory Address */
|
||||
mmio_write_8(hi2c->i2c_base_addr + I2C_TXDR,
|
||||
(uint8_t)(mem_addr & 0x00FFU));
|
||||
}
|
||||
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_TCR, 0, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Master sends target device address followed by internal memory
|
||||
* address for read request.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param mem_addr: Internal memory address
|
||||
* @param mem_add_size: Size of internal memory address
|
||||
* @param timeout_ref: Reference to target timeout
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_request_memory_read(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint16_t mem_addr, uint16_t mem_add_size,
|
||||
uint64_t timeout_ref)
|
||||
{
|
||||
i2c_transfer_config(hi2c, dev_addr, mem_add_size, I2C_SOFTEND_MODE,
|
||||
I2C_GENERATE_START_WRITE);
|
||||
|
||||
if (i2c_wait_txis(hi2c, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (mem_add_size == I2C_MEMADD_SIZE_8BIT) {
|
||||
/* Send Memory Address */
|
||||
mmio_write_8(hi2c->i2c_base_addr + I2C_TXDR,
|
||||
(uint8_t)(mem_addr & 0x00FFU));
|
||||
} else {
|
||||
/* Send MSB of Memory Address */
|
||||
mmio_write_8(hi2c->i2c_base_addr + I2C_TXDR,
|
||||
(uint8_t)((mem_addr & 0xFF00U) >> 8));
|
||||
|
||||
if (i2c_wait_txis(hi2c, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Send LSB of Memory Address */
|
||||
mmio_write_8(hi2c->i2c_base_addr + I2C_TXDR,
|
||||
(uint8_t)(mem_addr & 0x00FFU));
|
||||
}
|
||||
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_TC, 0, timeout_ref) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* @brief Generic function to write an amount of data in blocking mode
|
||||
* (for Memory Mode and Master Mode)
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param mem_addr: Internal memory address (if Memory Mode)
|
||||
* @param mem_add_size: Size of internal memory address (if Memory Mode)
|
||||
* @param p_data: Pointer to data buffer
|
||||
* @param size: Amount of data to be sent
|
||||
* @param timeout_ms: Timeout duration in milliseconds
|
||||
* @param mode: Communication mode
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_write(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint16_t mem_addr, uint16_t mem_add_size,
|
||||
uint8_t *p_data, uint16_t size, uint32_t timeout_ms,
|
||||
enum i2c_mode_e mode)
|
||||
{
|
||||
uint64_t timeout_ref;
|
||||
int rc = -EIO;
|
||||
uint8_t *p_buff = p_data;
|
||||
uint32_t xfer_size;
|
||||
uint32_t xfer_count = size;
|
||||
|
||||
if ((mode != I2C_MODE_MASTER) && (mode != I2C_MODE_MEM)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((hi2c->i2c_state != I2C_STATE_READY) || (hi2c->lock != 0U)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if ((p_data == NULL) || (size == 0U)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk_enable(hi2c->clock);
|
||||
|
||||
hi2c->lock = 1;
|
||||
|
||||
timeout_ref = timeout_init_us(I2C_TIMEOUT_BUSY_MS * 1000);
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_BUSY, 1, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_BUSY_TX;
|
||||
hi2c->i2c_mode = mode;
|
||||
hi2c->i2c_err = I2C_ERROR_NONE;
|
||||
|
||||
timeout_ref = timeout_init_us(timeout_ms * 1000);
|
||||
|
||||
if (mode == I2C_MODE_MEM) {
|
||||
/* In Memory Mode, Send Slave Address and Memory Address */
|
||||
if (i2c_request_memory_write(hi2c, dev_addr, mem_addr,
|
||||
mem_add_size, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (xfer_count > MAX_NBYTE_SIZE) {
|
||||
xfer_size = MAX_NBYTE_SIZE;
|
||||
i2c_transfer_config(hi2c, dev_addr, xfer_size,
|
||||
I2C_RELOAD_MODE, I2C_NO_STARTSTOP);
|
||||
} else {
|
||||
xfer_size = xfer_count;
|
||||
i2c_transfer_config(hi2c, dev_addr, xfer_size,
|
||||
I2C_AUTOEND_MODE, I2C_NO_STARTSTOP);
|
||||
}
|
||||
} else {
|
||||
/* In Master Mode, Send Slave Address */
|
||||
if (xfer_count > MAX_NBYTE_SIZE) {
|
||||
xfer_size = MAX_NBYTE_SIZE;
|
||||
i2c_transfer_config(hi2c, dev_addr, xfer_size,
|
||||
I2C_RELOAD_MODE,
|
||||
I2C_GENERATE_START_WRITE);
|
||||
} else {
|
||||
xfer_size = xfer_count;
|
||||
i2c_transfer_config(hi2c, dev_addr, xfer_size,
|
||||
I2C_AUTOEND_MODE,
|
||||
I2C_GENERATE_START_WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
if (i2c_wait_txis(hi2c, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mmio_write_8(hi2c->i2c_base_addr + I2C_TXDR, *p_buff);
|
||||
p_buff++;
|
||||
xfer_count--;
|
||||
xfer_size--;
|
||||
|
||||
if ((xfer_count != 0U) && (xfer_size == 0U)) {
|
||||
/* Wait until TCR flag is set */
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_TCR, 0,
|
||||
timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (xfer_count > MAX_NBYTE_SIZE) {
|
||||
xfer_size = MAX_NBYTE_SIZE;
|
||||
i2c_transfer_config(hi2c, dev_addr,
|
||||
xfer_size,
|
||||
I2C_RELOAD_MODE,
|
||||
I2C_NO_STARTSTOP);
|
||||
} else {
|
||||
xfer_size = xfer_count;
|
||||
i2c_transfer_config(hi2c, dev_addr,
|
||||
xfer_size,
|
||||
I2C_AUTOEND_MODE,
|
||||
I2C_NO_STARTSTOP);
|
||||
}
|
||||
}
|
||||
|
||||
} while (xfer_count > 0U);
|
||||
|
||||
/*
|
||||
* No need to Check TC flag, with AUTOEND mode the stop
|
||||
* is automatically generated.
|
||||
* Wait until STOPF flag is reset.
|
||||
*/
|
||||
if (i2c_wait_stop(hi2c, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR, I2C_FLAG_STOPF);
|
||||
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_CR2, I2C_RESET_CR2);
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_READY;
|
||||
hi2c->i2c_mode = I2C_MODE_NONE;
|
||||
|
||||
rc = 0;
|
||||
|
||||
bail:
|
||||
hi2c->lock = 0;
|
||||
clk_disable(hi2c->clock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Write an amount of data in blocking mode to a specific memory
|
||||
* address.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param mem_addr: Internal memory address
|
||||
* @param mem_add_size: Size of internal memory address
|
||||
* @param p_data: Pointer to data buffer
|
||||
* @param size: Amount of data to be sent
|
||||
* @param timeout_ms: Timeout duration in milliseconds
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
int stm32_i2c_mem_write(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint16_t mem_addr, uint16_t mem_add_size,
|
||||
uint8_t *p_data, uint16_t size, uint32_t timeout_ms)
|
||||
{
|
||||
return i2c_write(hi2c, dev_addr, mem_addr, mem_add_size,
|
||||
p_data, size, timeout_ms, I2C_MODE_MEM);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Transmits in master mode an amount of data in blocking mode.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param p_data: Pointer to data buffer
|
||||
* @param size: Amount of data to be sent
|
||||
* @param timeout_ms: Timeout duration in milliseconds
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
int stm32_i2c_master_transmit(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint8_t *p_data, uint16_t size,
|
||||
uint32_t timeout_ms)
|
||||
{
|
||||
return i2c_write(hi2c, dev_addr, 0, 0,
|
||||
p_data, size, timeout_ms, I2C_MODE_MASTER);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Generic function to read an amount of data in blocking mode
|
||||
* (for Memory Mode and Master Mode)
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param mem_addr: Internal memory address (if Memory Mode)
|
||||
* @param mem_add_size: Size of internal memory address (if Memory Mode)
|
||||
* @param p_data: Pointer to data buffer
|
||||
* @param size: Amount of data to be sent
|
||||
* @param timeout_ms: Timeout duration in milliseconds
|
||||
* @param mode: Communication mode
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
static int i2c_read(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint16_t mem_addr, uint16_t mem_add_size,
|
||||
uint8_t *p_data, uint16_t size, uint32_t timeout_ms,
|
||||
enum i2c_mode_e mode)
|
||||
{
|
||||
uint64_t timeout_ref;
|
||||
int rc = -EIO;
|
||||
uint8_t *p_buff = p_data;
|
||||
uint32_t xfer_count = size;
|
||||
uint32_t xfer_size;
|
||||
|
||||
if ((mode != I2C_MODE_MASTER) && (mode != I2C_MODE_MEM)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((hi2c->i2c_state != I2C_STATE_READY) || (hi2c->lock != 0U)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if ((p_data == NULL) || (size == 0U)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk_enable(hi2c->clock);
|
||||
|
||||
hi2c->lock = 1;
|
||||
|
||||
timeout_ref = timeout_init_us(I2C_TIMEOUT_BUSY_MS * 1000);
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_BUSY, 1, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_BUSY_RX;
|
||||
hi2c->i2c_mode = mode;
|
||||
hi2c->i2c_err = I2C_ERROR_NONE;
|
||||
|
||||
if (mode == I2C_MODE_MEM) {
|
||||
/* Send Memory Address */
|
||||
if (i2c_request_memory_read(hi2c, dev_addr, mem_addr,
|
||||
mem_add_size, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Send Slave Address.
|
||||
* Set NBYTES to write and reload if xfer_count > MAX_NBYTE_SIZE
|
||||
* and generate RESTART.
|
||||
*/
|
||||
if (xfer_count > MAX_NBYTE_SIZE) {
|
||||
xfer_size = MAX_NBYTE_SIZE;
|
||||
i2c_transfer_config(hi2c, dev_addr, xfer_size,
|
||||
I2C_RELOAD_MODE, I2C_GENERATE_START_READ);
|
||||
} else {
|
||||
xfer_size = xfer_count;
|
||||
i2c_transfer_config(hi2c, dev_addr, xfer_size,
|
||||
I2C_AUTOEND_MODE, I2C_GENERATE_START_READ);
|
||||
}
|
||||
|
||||
do {
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_RXNE, 0, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
*p_buff = mmio_read_8(hi2c->i2c_base_addr + I2C_RXDR);
|
||||
p_buff++;
|
||||
xfer_size--;
|
||||
xfer_count--;
|
||||
|
||||
if ((xfer_count != 0U) && (xfer_size == 0U)) {
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_TCR, 0,
|
||||
timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (xfer_count > MAX_NBYTE_SIZE) {
|
||||
xfer_size = MAX_NBYTE_SIZE;
|
||||
i2c_transfer_config(hi2c, dev_addr,
|
||||
xfer_size,
|
||||
I2C_RELOAD_MODE,
|
||||
I2C_NO_STARTSTOP);
|
||||
} else {
|
||||
xfer_size = xfer_count;
|
||||
i2c_transfer_config(hi2c, dev_addr,
|
||||
xfer_size,
|
||||
I2C_AUTOEND_MODE,
|
||||
I2C_NO_STARTSTOP);
|
||||
}
|
||||
}
|
||||
} while (xfer_count > 0U);
|
||||
|
||||
/*
|
||||
* No need to Check TC flag, with AUTOEND mode the stop
|
||||
* is automatically generated.
|
||||
* Wait until STOPF flag is reset.
|
||||
*/
|
||||
if (i2c_wait_stop(hi2c, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR, I2C_FLAG_STOPF);
|
||||
|
||||
mmio_clrbits_32(hi2c->i2c_base_addr + I2C_CR2, I2C_RESET_CR2);
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_READY;
|
||||
hi2c->i2c_mode = I2C_MODE_NONE;
|
||||
|
||||
rc = 0;
|
||||
|
||||
bail:
|
||||
hi2c->lock = 0;
|
||||
clk_disable(hi2c->clock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Read an amount of data in blocking mode from a specific memory
|
||||
* address.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param mem_addr: Internal memory address
|
||||
* @param mem_add_size: Size of internal memory address
|
||||
* @param p_data: Pointer to data buffer
|
||||
* @param size: Amount of data to be sent
|
||||
* @param timeout_ms: Timeout duration in milliseconds
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
int stm32_i2c_mem_read(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint16_t mem_addr, uint16_t mem_add_size,
|
||||
uint8_t *p_data, uint16_t size, uint32_t timeout_ms)
|
||||
{
|
||||
return i2c_read(hi2c, dev_addr, mem_addr, mem_add_size,
|
||||
p_data, size, timeout_ms, I2C_MODE_MEM);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Receives in master mode an amount of data in blocking mode.
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param p_data: Pointer to data buffer
|
||||
* @param size: Amount of data to be sent
|
||||
* @param timeout_ms: Timeout duration in milliseconds
|
||||
* @retval 0 if OK, negative value else
|
||||
*/
|
||||
int stm32_i2c_master_receive(struct i2c_handle_s *hi2c, uint16_t dev_addr,
|
||||
uint8_t *p_data, uint16_t size,
|
||||
uint32_t timeout_ms)
|
||||
{
|
||||
return i2c_read(hi2c, dev_addr, 0, 0,
|
||||
p_data, size, timeout_ms, I2C_MODE_MASTER);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Checks if target device is ready for communication.
|
||||
* @note This function is used with Memory devices
|
||||
* @param hi2c: Pointer to a struct i2c_handle_s structure that contains
|
||||
* the configuration information for the specified I2C.
|
||||
* @param dev_addr: Target device address
|
||||
* @param trials: Number of trials
|
||||
* @param timeout_ms: Timeout duration in milliseconds
|
||||
* @retval True if device is ready, false else
|
||||
*/
|
||||
bool stm32_i2c_is_device_ready(struct i2c_handle_s *hi2c,
|
||||
uint16_t dev_addr, uint32_t trials,
|
||||
uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t i2c_trials = 0U;
|
||||
bool rc = false;
|
||||
|
||||
if ((hi2c->i2c_state != I2C_STATE_READY) || (hi2c->lock != 0U)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
clk_enable(hi2c->clock);
|
||||
|
||||
hi2c->lock = 1;
|
||||
hi2c->i2c_mode = I2C_MODE_NONE;
|
||||
|
||||
if ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) & I2C_FLAG_BUSY) !=
|
||||
0U) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_BUSY;
|
||||
hi2c->i2c_err = I2C_ERROR_NONE;
|
||||
|
||||
do {
|
||||
uint64_t timeout_ref;
|
||||
|
||||
/* Generate Start */
|
||||
if ((mmio_read_32(hi2c->i2c_base_addr + I2C_OAR1) &
|
||||
I2C_OAR1_OA1MODE) == 0) {
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_CR2,
|
||||
(((uint32_t)dev_addr & I2C_CR2_SADD) |
|
||||
I2C_CR2_START | I2C_CR2_AUTOEND) &
|
||||
~I2C_CR2_RD_WRN);
|
||||
} else {
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_CR2,
|
||||
(((uint32_t)dev_addr & I2C_CR2_SADD) |
|
||||
I2C_CR2_START | I2C_CR2_ADD10) &
|
||||
~I2C_CR2_RD_WRN);
|
||||
}
|
||||
|
||||
/*
|
||||
* No need to Check TC flag, with AUTOEND mode the stop
|
||||
* is automatically generated.
|
||||
* Wait until STOPF flag is set or a NACK flag is set.
|
||||
*/
|
||||
timeout_ref = timeout_init_us(timeout_ms * 1000);
|
||||
do {
|
||||
if ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) &
|
||||
(I2C_FLAG_STOPF | I2C_FLAG_AF)) != 0U) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
notif_i2c_timeout(hi2c);
|
||||
goto bail;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
if ((mmio_read_32(hi2c->i2c_base_addr + I2C_ISR) &
|
||||
I2C_FLAG_AF) == 0U) {
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_STOPF, 0,
|
||||
timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR,
|
||||
I2C_FLAG_STOPF);
|
||||
|
||||
hi2c->i2c_state = I2C_STATE_READY;
|
||||
|
||||
rc = true;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_STOPF, 0, timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR, I2C_FLAG_AF);
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR, I2C_FLAG_STOPF);
|
||||
|
||||
if (i2c_trials == trials) {
|
||||
mmio_setbits_32(hi2c->i2c_base_addr + I2C_CR2,
|
||||
I2C_CR2_STOP);
|
||||
|
||||
if (i2c_wait_flag(hi2c, I2C_FLAG_STOPF, 0,
|
||||
timeout_ref) != 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mmio_write_32(hi2c->i2c_base_addr + I2C_ICR,
|
||||
I2C_FLAG_STOPF);
|
||||
}
|
||||
|
||||
i2c_trials++;
|
||||
} while (i2c_trials < trials);
|
||||
|
||||
notif_i2c_timeout(hi2c);
|
||||
|
||||
bail:
|
||||
hi2c->lock = 0;
|
||||
clk_disable(hi2c->clock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2021, ARM Limited and Contributors. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/io/io_driver.h>
|
||||
#include <drivers/io/io_storage.h>
|
||||
#include <drivers/mmc.h>
|
||||
#include <drivers/st/io_mmc.h>
|
||||
#include <drivers/st/stm32_sdmmc2.h>
|
||||
|
||||
/* SDMMC device functions */
|
||||
static int mmc_dev_open(const uintptr_t init_params, io_dev_info_t **dev_info);
|
||||
static int mmc_block_open(io_dev_info_t *dev_info, const uintptr_t spec,
|
||||
io_entity_t *entity);
|
||||
static int mmc_dev_init(io_dev_info_t *dev_info, const uintptr_t init_params);
|
||||
static int mmc_block_seek(io_entity_t *entity, int mode,
|
||||
signed long long offset);
|
||||
static int mmc_block_read(io_entity_t *entity, uintptr_t buffer, size_t length,
|
||||
size_t *length_read);
|
||||
static int mmc_block_close(io_entity_t *entity);
|
||||
static int mmc_dev_close(io_dev_info_t *dev_info);
|
||||
static io_type_t device_type_mmc(void);
|
||||
|
||||
static signed long long seek_offset;
|
||||
static size_t (*_read_blocks)(int lba, uintptr_t buf, size_t size);
|
||||
|
||||
static const io_dev_connector_t mmc_dev_connector = {
|
||||
.dev_open = mmc_dev_open
|
||||
};
|
||||
|
||||
static const io_dev_funcs_t mmc_dev_funcs = {
|
||||
.type = device_type_mmc,
|
||||
.open = mmc_block_open,
|
||||
.seek = mmc_block_seek,
|
||||
.size = NULL,
|
||||
.read = mmc_block_read,
|
||||
.write = NULL,
|
||||
.close = mmc_block_close,
|
||||
.dev_init = mmc_dev_init,
|
||||
.dev_close = mmc_dev_close,
|
||||
};
|
||||
|
||||
static const io_dev_info_t mmc_dev_info = {
|
||||
.funcs = &mmc_dev_funcs,
|
||||
.info = 0,
|
||||
};
|
||||
|
||||
/* Identify the device type as mmc device */
|
||||
static io_type_t device_type_mmc(void)
|
||||
{
|
||||
return IO_TYPE_MMC;
|
||||
}
|
||||
|
||||
/* Open a connection to the mmc device */
|
||||
static int mmc_dev_open(const uintptr_t init_params, io_dev_info_t **dev_info)
|
||||
{
|
||||
struct io_mmc_dev_spec *device_spec =
|
||||
(struct io_mmc_dev_spec *)init_params;
|
||||
|
||||
assert(dev_info != NULL);
|
||||
*dev_info = (io_dev_info_t *)&mmc_dev_info;
|
||||
|
||||
_read_blocks = !device_spec->use_boot_part ?
|
||||
mmc_read_blocks : mmc_boot_part_read_blocks;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_dev_init(io_dev_info_t *dev_info, const uintptr_t init_params)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Close a connection to the mmc device */
|
||||
static int mmc_dev_close(io_dev_info_t *dev_info)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Open a file on the mmc device */
|
||||
static int mmc_block_open(io_dev_info_t *dev_info, const uintptr_t spec,
|
||||
io_entity_t *entity)
|
||||
{
|
||||
seek_offset = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Seek to a particular file offset on the mmc device */
|
||||
static int mmc_block_seek(io_entity_t *entity, int mode,
|
||||
signed long long offset)
|
||||
{
|
||||
seek_offset = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read data from a file on the mmc device */
|
||||
static int mmc_block_read(io_entity_t *entity, uintptr_t buffer,
|
||||
size_t length, size_t *length_read)
|
||||
{
|
||||
uint8_t retries;
|
||||
|
||||
for (retries = 0U; retries < 3U; retries++) {
|
||||
*length_read = _read_blocks(seek_offset / MMC_BLOCK_SIZE,
|
||||
buffer, length);
|
||||
|
||||
if (*length_read == length) {
|
||||
return 0;
|
||||
}
|
||||
WARN("%s: length_read = %lu (!= %lu), retry %u\n", __func__,
|
||||
(unsigned long)*length_read, (unsigned long)length,
|
||||
retries + 1U);
|
||||
}
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Close a file on the mmc device */
|
||||
static int mmc_block_close(io_entity_t *entity)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Register the mmc driver with the IO abstraction */
|
||||
int register_io_dev_mmc(const io_dev_connector_t **dev_con)
|
||||
{
|
||||
int result;
|
||||
|
||||
assert(dev_con != NULL);
|
||||
|
||||
result = io_register_device(&mmc_dev_info);
|
||||
if (result == 0) {
|
||||
*dev_con = &mmc_dev_connector;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2021, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#include <arch_helpers.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/arm/gicv2.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32_iwdg.h>
|
||||
#include <drivers/st/stm32mp_clkfunc.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils.h>
|
||||
#include <plat/common/platform.h>
|
||||
|
||||
/* IWDG registers offsets */
|
||||
#define IWDG_KR_OFFSET 0x00U
|
||||
|
||||
/* Registers values */
|
||||
#define IWDG_KR_RELOAD_KEY 0xAAAA
|
||||
|
||||
struct stm32_iwdg_instance {
|
||||
uintptr_t base;
|
||||
unsigned long clock;
|
||||
uint8_t flags;
|
||||
int num_irq;
|
||||
};
|
||||
|
||||
static struct stm32_iwdg_instance stm32_iwdg[IWDG_MAX_INSTANCE];
|
||||
|
||||
static int stm32_iwdg_get_dt_node(struct dt_node_info *info, int offset)
|
||||
{
|
||||
int node;
|
||||
|
||||
node = dt_get_node(info, offset, DT_IWDG_COMPAT);
|
||||
if (node < 0) {
|
||||
if (offset == -1) {
|
||||
VERBOSE("%s: No IDWG found\n", __func__);
|
||||
}
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void stm32_iwdg_refresh(void)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0U; i < IWDG_MAX_INSTANCE; i++) {
|
||||
struct stm32_iwdg_instance *iwdg = &stm32_iwdg[i];
|
||||
|
||||
/* 0x00000000 is not a valid address for IWDG peripherals */
|
||||
if (iwdg->base != 0U) {
|
||||
clk_enable(iwdg->clock);
|
||||
|
||||
mmio_write_32(iwdg->base + IWDG_KR_OFFSET,
|
||||
IWDG_KR_RELOAD_KEY);
|
||||
|
||||
clk_disable(iwdg->clock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int stm32_iwdg_init(void)
|
||||
{
|
||||
int node = -1;
|
||||
struct dt_node_info dt_info;
|
||||
void *fdt;
|
||||
uint32_t __unused count = 0;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
for (node = stm32_iwdg_get_dt_node(&dt_info, node);
|
||||
node != -FDT_ERR_NOTFOUND;
|
||||
node = stm32_iwdg_get_dt_node(&dt_info, node)) {
|
||||
struct stm32_iwdg_instance *iwdg;
|
||||
uint32_t hw_init;
|
||||
uint32_t idx;
|
||||
|
||||
count++;
|
||||
|
||||
idx = stm32_iwdg_get_instance(dt_info.base);
|
||||
iwdg = &stm32_iwdg[idx];
|
||||
iwdg->base = dt_info.base;
|
||||
iwdg->clock = (unsigned long)dt_info.clock;
|
||||
|
||||
/* DT can specify low power cases */
|
||||
if (fdt_getprop(fdt, node, "stm32,enable-on-stop", NULL) ==
|
||||
NULL) {
|
||||
iwdg->flags |= IWDG_DISABLE_ON_STOP;
|
||||
}
|
||||
|
||||
if (fdt_getprop(fdt, node, "stm32,enable-on-standby", NULL) ==
|
||||
NULL) {
|
||||
iwdg->flags |= IWDG_DISABLE_ON_STANDBY;
|
||||
}
|
||||
|
||||
/* Explicit list of supported bit flags */
|
||||
hw_init = stm32_iwdg_get_otp_config(idx);
|
||||
|
||||
if ((hw_init & IWDG_HW_ENABLED) != 0) {
|
||||
if (dt_info.status == DT_DISABLED) {
|
||||
ERROR("OTP enabled but iwdg%u DT-disabled\n",
|
||||
idx + 1U);
|
||||
panic();
|
||||
}
|
||||
iwdg->flags |= IWDG_HW_ENABLED;
|
||||
}
|
||||
|
||||
if (dt_info.status == DT_DISABLED) {
|
||||
zeromem((void *)iwdg,
|
||||
sizeof(struct stm32_iwdg_instance));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((hw_init & IWDG_DISABLE_ON_STOP) != 0) {
|
||||
iwdg->flags |= IWDG_DISABLE_ON_STOP;
|
||||
}
|
||||
|
||||
if ((hw_init & IWDG_DISABLE_ON_STANDBY) != 0) {
|
||||
iwdg->flags |= IWDG_DISABLE_ON_STANDBY;
|
||||
}
|
||||
|
||||
VERBOSE("IWDG%u found, %ssecure\n", idx + 1U,
|
||||
((dt_info.status & DT_NON_SECURE) != 0) ?
|
||||
"non-" : "");
|
||||
|
||||
if ((dt_info.status & DT_NON_SECURE) != 0) {
|
||||
stm32mp_register_non_secure_periph_iomem(iwdg->base);
|
||||
} else {
|
||||
stm32mp_register_secure_periph_iomem(iwdg->base);
|
||||
}
|
||||
|
||||
#if defined(IMAGE_BL2)
|
||||
if (stm32_iwdg_shadow_update(idx, iwdg->flags) != BSEC_OK) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
VERBOSE("%u IWDG instance%s found\n", count, (count > 1U) ? "s" : "");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,798 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <arch.h>
|
||||
#include <arch_helpers.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/mmc.h>
|
||||
#include <drivers/st/stm32_gpio.h>
|
||||
#include <drivers/st/stm32_sdmmc2.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils.h>
|
||||
#include <libfdt.h>
|
||||
#include <plat/common/platform.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
/* Registers offsets */
|
||||
#define SDMMC_POWER 0x00U
|
||||
#define SDMMC_CLKCR 0x04U
|
||||
#define SDMMC_ARGR 0x08U
|
||||
#define SDMMC_CMDR 0x0CU
|
||||
#define SDMMC_RESPCMDR 0x10U
|
||||
#define SDMMC_RESP1R 0x14U
|
||||
#define SDMMC_RESP2R 0x18U
|
||||
#define SDMMC_RESP3R 0x1CU
|
||||
#define SDMMC_RESP4R 0x20U
|
||||
#define SDMMC_DTIMER 0x24U
|
||||
#define SDMMC_DLENR 0x28U
|
||||
#define SDMMC_DCTRLR 0x2CU
|
||||
#define SDMMC_DCNTR 0x30U
|
||||
#define SDMMC_STAR 0x34U
|
||||
#define SDMMC_ICR 0x38U
|
||||
#define SDMMC_MASKR 0x3CU
|
||||
#define SDMMC_ACKTIMER 0x40U
|
||||
#define SDMMC_IDMACTRLR 0x50U
|
||||
#define SDMMC_IDMABSIZER 0x54U
|
||||
#define SDMMC_IDMABASE0R 0x58U
|
||||
#define SDMMC_IDMABASE1R 0x5CU
|
||||
#define SDMMC_FIFOR 0x80U
|
||||
|
||||
/* SDMMC power control register */
|
||||
#define SDMMC_POWER_PWRCTRL GENMASK(1, 0)
|
||||
#define SDMMC_POWER_PWRCTRL_PWR_CYCLE BIT(1)
|
||||
#define SDMMC_POWER_DIRPOL BIT(4)
|
||||
|
||||
/* SDMMC clock control register */
|
||||
#define SDMMC_CLKCR_WIDBUS_4 BIT(14)
|
||||
#define SDMMC_CLKCR_WIDBUS_8 BIT(15)
|
||||
#define SDMMC_CLKCR_NEGEDGE BIT(16)
|
||||
#define SDMMC_CLKCR_HWFC_EN BIT(17)
|
||||
#define SDMMC_CLKCR_SELCLKRX_0 BIT(20)
|
||||
|
||||
/* SDMMC command register */
|
||||
#define SDMMC_CMDR_CMDTRANS BIT(6)
|
||||
#define SDMMC_CMDR_CMDSTOP BIT(7)
|
||||
#define SDMMC_CMDR_WAITRESP GENMASK(9, 8)
|
||||
#define SDMMC_CMDR_WAITRESP_SHORT BIT(8)
|
||||
#define SDMMC_CMDR_WAITRESP_SHORT_NOCRC BIT(9)
|
||||
#define SDMMC_CMDR_CPSMEN BIT(12)
|
||||
|
||||
/* SDMMC data control register */
|
||||
#define SDMMC_DCTRLR_DTEN BIT(0)
|
||||
#define SDMMC_DCTRLR_DTDIR BIT(1)
|
||||
#define SDMMC_DCTRLR_DTMODE GENMASK(3, 2)
|
||||
#define SDMMC_DCTRLR_DBLOCKSIZE GENMASK(7, 4)
|
||||
#define SDMMC_DCTRLR_DBLOCKSIZE_SHIFT 4
|
||||
#define SDMMC_DCTRLR_FIFORST BIT(13)
|
||||
|
||||
#define SDMMC_DCTRLR_CLEAR_MASK (SDMMC_DCTRLR_DTEN | \
|
||||
SDMMC_DCTRLR_DTDIR | \
|
||||
SDMMC_DCTRLR_DTMODE | \
|
||||
SDMMC_DCTRLR_DBLOCKSIZE)
|
||||
|
||||
/* SDMMC status register */
|
||||
#define SDMMC_STAR_CCRCFAIL BIT(0)
|
||||
#define SDMMC_STAR_DCRCFAIL BIT(1)
|
||||
#define SDMMC_STAR_CTIMEOUT BIT(2)
|
||||
#define SDMMC_STAR_DTIMEOUT BIT(3)
|
||||
#define SDMMC_STAR_TXUNDERR BIT(4)
|
||||
#define SDMMC_STAR_RXOVERR BIT(5)
|
||||
#define SDMMC_STAR_CMDREND BIT(6)
|
||||
#define SDMMC_STAR_CMDSENT BIT(7)
|
||||
#define SDMMC_STAR_DATAEND BIT(8)
|
||||
#define SDMMC_STAR_DBCKEND BIT(10)
|
||||
#define SDMMC_STAR_DPSMACT BIT(12)
|
||||
#define SDMMC_STAR_RXFIFOHF BIT(15)
|
||||
#define SDMMC_STAR_RXFIFOE BIT(19)
|
||||
#define SDMMC_STAR_IDMATE BIT(27)
|
||||
#define SDMMC_STAR_IDMABTC BIT(28)
|
||||
|
||||
/* SDMMC DMA control register */
|
||||
#define SDMMC_IDMACTRLR_IDMAEN BIT(0)
|
||||
|
||||
#define SDMMC_STATIC_FLAGS (SDMMC_STAR_CCRCFAIL | \
|
||||
SDMMC_STAR_DCRCFAIL | \
|
||||
SDMMC_STAR_CTIMEOUT | \
|
||||
SDMMC_STAR_DTIMEOUT | \
|
||||
SDMMC_STAR_TXUNDERR | \
|
||||
SDMMC_STAR_RXOVERR | \
|
||||
SDMMC_STAR_CMDREND | \
|
||||
SDMMC_STAR_CMDSENT | \
|
||||
SDMMC_STAR_DATAEND | \
|
||||
SDMMC_STAR_DBCKEND | \
|
||||
SDMMC_STAR_IDMATE | \
|
||||
SDMMC_STAR_IDMABTC)
|
||||
|
||||
#define TIMEOUT_US_1_MS 1000U
|
||||
#define TIMEOUT_US_10_MS 10000U
|
||||
#define TIMEOUT_US_1_S 1000000U
|
||||
|
||||
/* Power cycle delays in ms */
|
||||
#define VCC_POWER_OFF_DELAY 2
|
||||
#define VCC_POWER_ON_DELAY 2
|
||||
#define POWER_CYCLE_DELAY 2
|
||||
#define POWER_OFF_DELAY 2
|
||||
#define POWER_ON_DELAY 1
|
||||
|
||||
#ifndef DT_SDMMC2_COMPAT
|
||||
#define DT_SDMMC2_COMPAT "st,stm32-sdmmc2"
|
||||
#endif
|
||||
|
||||
#define SDMMC_FIFO_SIZE 64U
|
||||
|
||||
static void stm32_sdmmc2_init(void);
|
||||
static int stm32_sdmmc2_send_cmd_req(struct mmc_cmd *cmd);
|
||||
static int stm32_sdmmc2_send_cmd(struct mmc_cmd *cmd);
|
||||
static int stm32_sdmmc2_set_ios(unsigned int clk, unsigned int width);
|
||||
static int stm32_sdmmc2_prepare(int lba, uintptr_t buf, size_t size);
|
||||
static int stm32_sdmmc2_read(int lba, uintptr_t buf, size_t size);
|
||||
static int stm32_sdmmc2_write(int lba, uintptr_t buf, size_t size);
|
||||
|
||||
static const struct mmc_ops stm32_sdmmc2_ops = {
|
||||
.init = stm32_sdmmc2_init,
|
||||
.send_cmd = stm32_sdmmc2_send_cmd,
|
||||
.set_ios = stm32_sdmmc2_set_ios,
|
||||
.prepare = stm32_sdmmc2_prepare,
|
||||
.read = stm32_sdmmc2_read,
|
||||
.write = stm32_sdmmc2_write,
|
||||
};
|
||||
|
||||
static struct stm32_sdmmc2_params sdmmc2_params;
|
||||
|
||||
static bool next_cmd_is_acmd;
|
||||
|
||||
#pragma weak plat_sdmmc2_use_dma
|
||||
bool plat_sdmmc2_use_dma(unsigned int instance, unsigned int memory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static void stm32_sdmmc2_init(void)
|
||||
{
|
||||
uint32_t clock_div;
|
||||
uint32_t freq = STM32MP_MMC_INIT_FREQ;
|
||||
uintptr_t base = sdmmc2_params.reg_base;
|
||||
int ret;
|
||||
|
||||
if (sdmmc2_params.max_freq != 0U) {
|
||||
freq = MIN(sdmmc2_params.max_freq, freq);
|
||||
}
|
||||
|
||||
if (sdmmc2_params.vmmc_regu != NULL) {
|
||||
ret = regulator_disable(sdmmc2_params.vmmc_regu);
|
||||
if (ret < 0) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
mdelay(VCC_POWER_OFF_DELAY);
|
||||
|
||||
mmio_write_32(base + SDMMC_POWER,
|
||||
SDMMC_POWER_PWRCTRL_PWR_CYCLE | sdmmc2_params.dirpol);
|
||||
mdelay(POWER_CYCLE_DELAY);
|
||||
|
||||
if (sdmmc2_params.vmmc_regu != NULL) {
|
||||
ret = regulator_enable(sdmmc2_params.vmmc_regu);
|
||||
if (ret < 0) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
mdelay(VCC_POWER_ON_DELAY);
|
||||
|
||||
mmio_write_32(base + SDMMC_POWER, sdmmc2_params.dirpol);
|
||||
mdelay(POWER_OFF_DELAY);
|
||||
|
||||
clock_div = div_round_up(sdmmc2_params.clk_rate, freq * 2U);
|
||||
|
||||
mmio_write_32(base + SDMMC_CLKCR, SDMMC_CLKCR_HWFC_EN | clock_div |
|
||||
sdmmc2_params.negedge |
|
||||
sdmmc2_params.pin_ckin);
|
||||
|
||||
mmio_write_32(base + SDMMC_POWER,
|
||||
SDMMC_POWER_PWRCTRL | sdmmc2_params.dirpol);
|
||||
|
||||
mdelay(POWER_ON_DELAY);
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_stop_transfer(void)
|
||||
{
|
||||
struct mmc_cmd cmd_stop;
|
||||
|
||||
zeromem(&cmd_stop, sizeof(struct mmc_cmd));
|
||||
|
||||
cmd_stop.cmd_idx = MMC_CMD(12);
|
||||
cmd_stop.resp_type = MMC_RESPONSE_R1B;
|
||||
|
||||
return stm32_sdmmc2_send_cmd(&cmd_stop);
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_send_cmd_req(struct mmc_cmd *cmd)
|
||||
{
|
||||
uint64_t timeout;
|
||||
uint32_t flags_cmd, status;
|
||||
uint32_t flags_data = 0;
|
||||
int err = 0;
|
||||
uintptr_t base = sdmmc2_params.reg_base;
|
||||
unsigned int cmd_reg, arg_reg;
|
||||
|
||||
if (cmd == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
flags_cmd = SDMMC_STAR_CTIMEOUT;
|
||||
arg_reg = cmd->cmd_arg;
|
||||
|
||||
if ((mmio_read_32(base + SDMMC_CMDR) & SDMMC_CMDR_CPSMEN) != 0U) {
|
||||
mmio_write_32(base + SDMMC_CMDR, 0);
|
||||
}
|
||||
|
||||
cmd_reg = cmd->cmd_idx | SDMMC_CMDR_CPSMEN;
|
||||
|
||||
if (cmd->resp_type == 0U) {
|
||||
flags_cmd |= SDMMC_STAR_CMDSENT;
|
||||
}
|
||||
|
||||
if ((cmd->resp_type & MMC_RSP_48) != 0U) {
|
||||
if ((cmd->resp_type & MMC_RSP_136) != 0U) {
|
||||
flags_cmd |= SDMMC_STAR_CMDREND;
|
||||
cmd_reg |= SDMMC_CMDR_WAITRESP;
|
||||
} else if ((cmd->resp_type & MMC_RSP_CRC) != 0U) {
|
||||
flags_cmd |= SDMMC_STAR_CMDREND | SDMMC_STAR_CCRCFAIL;
|
||||
cmd_reg |= SDMMC_CMDR_WAITRESP_SHORT;
|
||||
} else {
|
||||
flags_cmd |= SDMMC_STAR_CMDREND;
|
||||
cmd_reg |= SDMMC_CMDR_WAITRESP_SHORT_NOCRC;
|
||||
}
|
||||
}
|
||||
|
||||
switch (cmd->cmd_idx) {
|
||||
case MMC_CMD(1):
|
||||
arg_reg |= OCR_POWERUP;
|
||||
break;
|
||||
case MMC_CMD(6):
|
||||
if ((sdmmc2_params.device_info->mmc_dev_type == MMC_IS_SD_HC) &&
|
||||
(!next_cmd_is_acmd)) {
|
||||
cmd_reg |= SDMMC_CMDR_CMDTRANS;
|
||||
if (sdmmc2_params.use_dma) {
|
||||
flags_data |= SDMMC_STAR_DCRCFAIL |
|
||||
SDMMC_STAR_DTIMEOUT |
|
||||
SDMMC_STAR_DATAEND |
|
||||
SDMMC_STAR_RXOVERR |
|
||||
SDMMC_STAR_IDMATE |
|
||||
SDMMC_STAR_DBCKEND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MMC_CMD(8):
|
||||
if (sdmmc2_params.device_info->mmc_dev_type == MMC_IS_EMMC) {
|
||||
cmd_reg |= SDMMC_CMDR_CMDTRANS;
|
||||
}
|
||||
break;
|
||||
case MMC_CMD(12):
|
||||
cmd_reg |= SDMMC_CMDR_CMDSTOP;
|
||||
break;
|
||||
case MMC_CMD(17):
|
||||
case MMC_CMD(18):
|
||||
cmd_reg |= SDMMC_CMDR_CMDTRANS;
|
||||
if (sdmmc2_params.use_dma) {
|
||||
flags_data |= SDMMC_STAR_DCRCFAIL |
|
||||
SDMMC_STAR_DTIMEOUT |
|
||||
SDMMC_STAR_DATAEND |
|
||||
SDMMC_STAR_RXOVERR |
|
||||
SDMMC_STAR_IDMATE;
|
||||
}
|
||||
break;
|
||||
case MMC_ACMD(41):
|
||||
arg_reg |= OCR_3_2_3_3 | OCR_3_3_3_4;
|
||||
break;
|
||||
case MMC_ACMD(51):
|
||||
cmd_reg |= SDMMC_CMDR_CMDTRANS;
|
||||
if (sdmmc2_params.use_dma) {
|
||||
flags_data |= SDMMC_STAR_DCRCFAIL |
|
||||
SDMMC_STAR_DTIMEOUT |
|
||||
SDMMC_STAR_DATAEND |
|
||||
SDMMC_STAR_RXOVERR |
|
||||
SDMMC_STAR_IDMATE |
|
||||
SDMMC_STAR_DBCKEND;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
next_cmd_is_acmd = (cmd->cmd_idx == MMC_CMD(55));
|
||||
|
||||
mmio_write_32(base + SDMMC_ICR, SDMMC_STATIC_FLAGS);
|
||||
|
||||
/*
|
||||
* Clear the SDMMC_DCTRLR if the command does not await data.
|
||||
* Skip CMD55 as the next command could be data related, and
|
||||
* the register could have been set in prepare function.
|
||||
*/
|
||||
if (((cmd_reg & SDMMC_CMDR_CMDTRANS) == 0U) && !next_cmd_is_acmd) {
|
||||
mmio_write_32(base + SDMMC_DCTRLR, 0U);
|
||||
}
|
||||
|
||||
if ((cmd->resp_type & MMC_RSP_BUSY) != 0U) {
|
||||
mmio_write_32(base + SDMMC_DTIMER, UINT32_MAX);
|
||||
}
|
||||
|
||||
mmio_write_32(base + SDMMC_ARGR, arg_reg);
|
||||
|
||||
mmio_write_32(base + SDMMC_CMDR, cmd_reg);
|
||||
|
||||
status = mmio_read_32(base + SDMMC_STAR);
|
||||
|
||||
timeout = timeout_init_us(TIMEOUT_US_10_MS);
|
||||
|
||||
while ((status & flags_cmd) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
err = -ETIMEDOUT;
|
||||
ERROR("%s: timeout 10ms (cmd = %u,status = %x)\n",
|
||||
__func__, cmd->cmd_idx, status);
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
status = mmio_read_32(base + SDMMC_STAR);
|
||||
}
|
||||
|
||||
if ((status & (SDMMC_STAR_CTIMEOUT | SDMMC_STAR_CCRCFAIL)) != 0U) {
|
||||
if ((status & SDMMC_STAR_CTIMEOUT) != 0U) {
|
||||
err = -ETIMEDOUT;
|
||||
/*
|
||||
* Those timeouts can occur, and framework will handle
|
||||
* the retries. CMD8 is expected to return this timeout
|
||||
* for eMMC
|
||||
*/
|
||||
if (!((cmd->cmd_idx == MMC_CMD(1)) ||
|
||||
(cmd->cmd_idx == MMC_CMD(13)) ||
|
||||
((cmd->cmd_idx == MMC_CMD(8)) &&
|
||||
(cmd->resp_type == MMC_RESPONSE_R7)))) {
|
||||
ERROR("%s: CTIMEOUT (cmd = %u,status = %x)\n",
|
||||
__func__, cmd->cmd_idx, status);
|
||||
}
|
||||
} else {
|
||||
err = -EIO;
|
||||
ERROR("%s: CRCFAIL (cmd = %u,status = %x)\n",
|
||||
__func__, cmd->cmd_idx, status);
|
||||
}
|
||||
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
if ((cmd_reg & SDMMC_CMDR_WAITRESP) != 0U) {
|
||||
if ((cmd->cmd_idx == MMC_CMD(9)) &&
|
||||
((cmd_reg & SDMMC_CMDR_WAITRESP) == SDMMC_CMDR_WAITRESP)) {
|
||||
/* Need to invert response to match CSD structure */
|
||||
cmd->resp_data[0] = mmio_read_32(base + SDMMC_RESP4R);
|
||||
cmd->resp_data[1] = mmio_read_32(base + SDMMC_RESP3R);
|
||||
cmd->resp_data[2] = mmio_read_32(base + SDMMC_RESP2R);
|
||||
cmd->resp_data[3] = mmio_read_32(base + SDMMC_RESP1R);
|
||||
} else {
|
||||
cmd->resp_data[0] = mmio_read_32(base + SDMMC_RESP1R);
|
||||
if ((cmd_reg & SDMMC_CMDR_WAITRESP) ==
|
||||
SDMMC_CMDR_WAITRESP) {
|
||||
cmd->resp_data[1] = mmio_read_32(base +
|
||||
SDMMC_RESP2R);
|
||||
cmd->resp_data[2] = mmio_read_32(base +
|
||||
SDMMC_RESP3R);
|
||||
cmd->resp_data[3] = mmio_read_32(base +
|
||||
SDMMC_RESP4R);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags_data == 0U) {
|
||||
mmio_write_32(base + SDMMC_ICR, SDMMC_STATIC_FLAGS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = mmio_read_32(base + SDMMC_STAR);
|
||||
|
||||
timeout = timeout_init_us(TIMEOUT_US_10_MS);
|
||||
|
||||
while ((status & flags_data) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ERROR("%s: timeout 10ms (cmd = %u,status = %x)\n",
|
||||
__func__, cmd->cmd_idx, status);
|
||||
err = -ETIMEDOUT;
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
status = mmio_read_32(base + SDMMC_STAR);
|
||||
};
|
||||
|
||||
if ((status & (SDMMC_STAR_DTIMEOUT | SDMMC_STAR_DCRCFAIL |
|
||||
SDMMC_STAR_TXUNDERR | SDMMC_STAR_RXOVERR |
|
||||
SDMMC_STAR_IDMATE)) != 0U) {
|
||||
ERROR("%s: Error flag (cmd = %u,status = %x)\n", __func__,
|
||||
cmd->cmd_idx, status);
|
||||
err = -EIO;
|
||||
}
|
||||
|
||||
err_exit:
|
||||
mmio_write_32(base + SDMMC_ICR, SDMMC_STATIC_FLAGS);
|
||||
mmio_clrbits_32(base + SDMMC_CMDR, SDMMC_CMDR_CMDTRANS);
|
||||
|
||||
if ((err != 0) && ((status & SDMMC_STAR_DPSMACT) != 0U)) {
|
||||
int ret_stop = stm32_sdmmc2_stop_transfer();
|
||||
|
||||
if (ret_stop != 0) {
|
||||
return ret_stop;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_send_cmd(struct mmc_cmd *cmd)
|
||||
{
|
||||
uint8_t retry;
|
||||
int err;
|
||||
|
||||
assert(cmd != NULL);
|
||||
|
||||
for (retry = 0U; retry < 3U; retry++) {
|
||||
err = stm32_sdmmc2_send_cmd_req(cmd);
|
||||
if (err == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((cmd->cmd_idx == MMC_CMD(1)) ||
|
||||
(cmd->cmd_idx == MMC_CMD(13))) {
|
||||
return 0; /* Retry managed by framework */
|
||||
}
|
||||
|
||||
/* Command 8 is expected to fail for eMMC */
|
||||
if (cmd->cmd_idx != MMC_CMD(8)) {
|
||||
WARN(" CMD%u, Retry: %u, Error: %d\n",
|
||||
cmd->cmd_idx, retry + 1U, err);
|
||||
}
|
||||
|
||||
udelay(10U);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_set_ios(unsigned int clk, unsigned int width)
|
||||
{
|
||||
uintptr_t base = sdmmc2_params.reg_base;
|
||||
uint32_t bus_cfg = 0;
|
||||
uint32_t clock_div, max_freq, freq;
|
||||
uint32_t clk_rate = sdmmc2_params.clk_rate;
|
||||
uint32_t max_bus_freq = sdmmc2_params.device_info->max_bus_freq;
|
||||
|
||||
switch (width) {
|
||||
case MMC_BUS_WIDTH_1:
|
||||
break;
|
||||
case MMC_BUS_WIDTH_4:
|
||||
bus_cfg |= SDMMC_CLKCR_WIDBUS_4;
|
||||
break;
|
||||
case MMC_BUS_WIDTH_8:
|
||||
bus_cfg |= SDMMC_CLKCR_WIDBUS_8;
|
||||
break;
|
||||
default:
|
||||
panic();
|
||||
break;
|
||||
}
|
||||
|
||||
if (sdmmc2_params.device_info->mmc_dev_type == MMC_IS_EMMC) {
|
||||
if (max_bus_freq >= 52000000U) {
|
||||
max_freq = STM32MP_EMMC_HIGH_SPEED_MAX_FREQ;
|
||||
} else {
|
||||
max_freq = STM32MP_EMMC_NORMAL_SPEED_MAX_FREQ;
|
||||
}
|
||||
} else {
|
||||
if (max_bus_freq >= 50000000U) {
|
||||
max_freq = STM32MP_SD_HIGH_SPEED_MAX_FREQ;
|
||||
} else {
|
||||
max_freq = STM32MP_SD_NORMAL_SPEED_MAX_FREQ;
|
||||
}
|
||||
}
|
||||
|
||||
if (sdmmc2_params.max_freq != 0U) {
|
||||
freq = MIN(sdmmc2_params.max_freq, max_freq);
|
||||
} else {
|
||||
freq = max_freq;
|
||||
}
|
||||
|
||||
clock_div = div_round_up(clk_rate, freq * 2U);
|
||||
|
||||
mmio_write_32(base + SDMMC_CLKCR,
|
||||
SDMMC_CLKCR_HWFC_EN | clock_div | bus_cfg |
|
||||
sdmmc2_params.negedge |
|
||||
sdmmc2_params.pin_ckin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_prepare(int lba, uintptr_t buf, size_t size)
|
||||
{
|
||||
struct mmc_cmd cmd;
|
||||
int ret;
|
||||
uintptr_t base = sdmmc2_params.reg_base;
|
||||
uint32_t data_ctrl = SDMMC_DCTRLR_DTDIR;
|
||||
uint32_t arg_size;
|
||||
|
||||
assert(size != 0U);
|
||||
|
||||
if (size > MMC_BLOCK_SIZE) {
|
||||
arg_size = MMC_BLOCK_SIZE;
|
||||
} else {
|
||||
arg_size = size;
|
||||
}
|
||||
|
||||
sdmmc2_params.use_dma = plat_sdmmc2_use_dma(base, buf);
|
||||
|
||||
if (sdmmc2_params.use_dma) {
|
||||
inv_dcache_range(buf, size);
|
||||
}
|
||||
|
||||
/* Prepare CMD 16*/
|
||||
mmio_write_32(base + SDMMC_DTIMER, 0);
|
||||
|
||||
mmio_write_32(base + SDMMC_DLENR, 0);
|
||||
|
||||
mmio_write_32(base + SDMMC_DCTRLR, 0);
|
||||
|
||||
zeromem(&cmd, sizeof(struct mmc_cmd));
|
||||
|
||||
cmd.cmd_idx = MMC_CMD(16);
|
||||
cmd.cmd_arg = arg_size;
|
||||
cmd.resp_type = MMC_RESPONSE_R1;
|
||||
|
||||
ret = stm32_sdmmc2_send_cmd(&cmd);
|
||||
if (ret != 0) {
|
||||
ERROR("CMD16 failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Prepare data command */
|
||||
mmio_write_32(base + SDMMC_DTIMER, UINT32_MAX);
|
||||
|
||||
mmio_write_32(base + SDMMC_DLENR, size);
|
||||
|
||||
if (sdmmc2_params.use_dma) {
|
||||
mmio_write_32(base + SDMMC_IDMACTRLR,
|
||||
SDMMC_IDMACTRLR_IDMAEN);
|
||||
mmio_write_32(base + SDMMC_IDMABASE0R, buf);
|
||||
|
||||
flush_dcache_range(buf, size);
|
||||
}
|
||||
|
||||
data_ctrl |= __builtin_ctz(arg_size) << SDMMC_DCTRLR_DBLOCKSIZE_SHIFT;
|
||||
|
||||
mmio_clrsetbits_32(base + SDMMC_DCTRLR,
|
||||
SDMMC_DCTRLR_CLEAR_MASK,
|
||||
data_ctrl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_read(int lba, uintptr_t buf, size_t size)
|
||||
{
|
||||
uint32_t error_flags = SDMMC_STAR_RXOVERR | SDMMC_STAR_DCRCFAIL |
|
||||
SDMMC_STAR_DTIMEOUT;
|
||||
uint32_t flags = error_flags | SDMMC_STAR_DATAEND;
|
||||
uint32_t status;
|
||||
uint32_t *buffer;
|
||||
uintptr_t base = sdmmc2_params.reg_base;
|
||||
uintptr_t fifo_reg = base + SDMMC_FIFOR;
|
||||
uint64_t timeout;
|
||||
int ret;
|
||||
|
||||
/* Assert buf is 4 bytes aligned */
|
||||
assert((buf & GENMASK(1, 0)) == 0U);
|
||||
|
||||
buffer = (uint32_t *)buf;
|
||||
|
||||
if (sdmmc2_params.use_dma) {
|
||||
inv_dcache_range(buf, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (size <= MMC_BLOCK_SIZE) {
|
||||
flags |= SDMMC_STAR_DBCKEND;
|
||||
}
|
||||
|
||||
timeout = timeout_init_us(TIMEOUT_US_1_S);
|
||||
|
||||
do {
|
||||
status = mmio_read_32(base + SDMMC_STAR);
|
||||
|
||||
if ((status & error_flags) != 0U) {
|
||||
ERROR("%s: Read error (status = %x)\n", __func__,
|
||||
status);
|
||||
mmio_write_32(base + SDMMC_DCTRLR,
|
||||
SDMMC_DCTRLR_FIFORST);
|
||||
|
||||
mmio_write_32(base + SDMMC_ICR,
|
||||
SDMMC_STATIC_FLAGS);
|
||||
|
||||
ret = stm32_sdmmc2_stop_transfer();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ERROR("%s: timeout 1s (status = %x)\n",
|
||||
__func__, status);
|
||||
mmio_write_32(base + SDMMC_ICR,
|
||||
SDMMC_STATIC_FLAGS);
|
||||
|
||||
ret = stm32_sdmmc2_stop_transfer();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (size < (SDMMC_FIFO_SIZE / 2U)) {
|
||||
if ((mmio_read_32(base + SDMMC_DCNTR) > 0U) &&
|
||||
((status & SDMMC_STAR_RXFIFOE) == 0U)) {
|
||||
*buffer = mmio_read_32(fifo_reg);
|
||||
buffer++;
|
||||
}
|
||||
} else if ((status & SDMMC_STAR_RXFIFOHF) != 0U) {
|
||||
uint32_t count;
|
||||
|
||||
/* Read data from SDMMC Rx FIFO */
|
||||
for (count = 0; count < (SDMMC_FIFO_SIZE / 2U);
|
||||
count += sizeof(uint32_t)) {
|
||||
*buffer = mmio_read_32(fifo_reg);
|
||||
buffer++;
|
||||
}
|
||||
}
|
||||
} while ((status & flags) == 0U);
|
||||
|
||||
mmio_write_32(base + SDMMC_ICR, SDMMC_STATIC_FLAGS);
|
||||
|
||||
if ((status & SDMMC_STAR_DPSMACT) != 0U) {
|
||||
WARN("%s: DPSMACT=1, send stop\n", __func__);
|
||||
return stm32_sdmmc2_stop_transfer();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_write(int lba, uintptr_t buf, size_t size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_sdmmc2_dt_get_config(void)
|
||||
{
|
||||
int sdmmc_node;
|
||||
void *fdt = NULL;
|
||||
const fdt32_t *cuint;
|
||||
struct dt_node_info dt_info;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if (fdt == NULL) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
sdmmc_node = dt_match_instance_by_compatible(DT_SDMMC2_COMPAT,
|
||||
sdmmc2_params.reg_base);
|
||||
if (sdmmc_node == -FDT_ERR_NOTFOUND) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
dt_fill_device_info(&dt_info, sdmmc_node);
|
||||
if (dt_info.status == DT_DISABLED) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if (dt_set_pinctrl_config(sdmmc_node) != 0) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
sdmmc2_params.clock_id = dt_info.clock;
|
||||
sdmmc2_params.reset_id = dt_info.reset;
|
||||
|
||||
if ((fdt_getprop(fdt, sdmmc_node, "st,use-ckin", NULL)) != NULL) {
|
||||
sdmmc2_params.pin_ckin = SDMMC_CLKCR_SELCLKRX_0;
|
||||
}
|
||||
|
||||
if ((fdt_getprop(fdt, sdmmc_node, "st,sig-dir", NULL)) != NULL) {
|
||||
sdmmc2_params.dirpol = SDMMC_POWER_DIRPOL;
|
||||
}
|
||||
|
||||
if ((fdt_getprop(fdt, sdmmc_node, "st,neg-edge", NULL)) != NULL) {
|
||||
sdmmc2_params.negedge = SDMMC_CLKCR_NEGEDGE;
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, sdmmc_node, "bus-width", NULL);
|
||||
if (cuint != NULL) {
|
||||
switch (fdt32_to_cpu(*cuint)) {
|
||||
case 4:
|
||||
sdmmc2_params.bus_width = MMC_BUS_WIDTH_4;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
sdmmc2_params.bus_width = MMC_BUS_WIDTH_8;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, sdmmc_node, "max-frequency", NULL);
|
||||
if (cuint != NULL) {
|
||||
sdmmc2_params.max_freq = fdt32_to_cpu(*cuint);
|
||||
}
|
||||
|
||||
sdmmc2_params.vmmc_regu = regulator_get_by_supply_name(fdt, sdmmc_node, "vmmc");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long long stm32_sdmmc2_mmc_get_device_size(void)
|
||||
{
|
||||
return sdmmc2_params.device_info->device_size;
|
||||
}
|
||||
|
||||
int stm32_sdmmc2_mmc_init(struct stm32_sdmmc2_params *params)
|
||||
{
|
||||
assert((params != NULL) &&
|
||||
((params->reg_base & MMC_BLOCK_MASK) == 0U) &&
|
||||
((params->bus_width == MMC_BUS_WIDTH_1) ||
|
||||
(params->bus_width == MMC_BUS_WIDTH_4) ||
|
||||
(params->bus_width == MMC_BUS_WIDTH_8)));
|
||||
|
||||
memcpy(&sdmmc2_params, params, sizeof(struct stm32_sdmmc2_params));
|
||||
|
||||
sdmmc2_params.vmmc_regu = NULL;
|
||||
|
||||
if (stm32_sdmmc2_dt_get_config() != 0) {
|
||||
ERROR("%s: DT error\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
clk_enable(sdmmc2_params.clock_id);
|
||||
|
||||
if ((int)sdmmc2_params.reset_id >= 0) {
|
||||
int rc;
|
||||
|
||||
rc = stm32mp_reset_assert(sdmmc2_params.reset_id, TIMEOUT_US_1_MS);
|
||||
if (rc != 0) {
|
||||
panic();
|
||||
}
|
||||
udelay(2);
|
||||
rc = stm32mp_reset_deassert(sdmmc2_params.reset_id, TIMEOUT_US_1_MS);
|
||||
if (rc != 0) {
|
||||
panic();
|
||||
}
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
sdmmc2_params.clk_rate = clk_get_rate(sdmmc2_params.clock_id);
|
||||
sdmmc2_params.device_info->ocr_voltage = OCR_3_2_3_3 | OCR_3_3_3_4;
|
||||
|
||||
return mmc_init(&stm32_sdmmc2_ops, sdmmc2_params.clk_rate,
|
||||
sdmmc2_params.bus_width, sdmmc2_params.flags,
|
||||
sdmmc2_params.device_info);
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/regulator.h>
|
||||
#include <drivers/st/stm32_i2c.h>
|
||||
#include <drivers/st/stm32mp_pmic.h>
|
||||
#include <drivers/st/stpmic1.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils_def.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#define PMIC_NODE_NOT_FOUND 1
|
||||
|
||||
static struct i2c_handle_s i2c_handle;
|
||||
static uint32_t pmic_i2c_addr;
|
||||
|
||||
static int register_pmic(void);
|
||||
|
||||
static int dt_get_pmic_node(void *fdt)
|
||||
{
|
||||
static int node = -FDT_ERR_BADOFFSET;
|
||||
|
||||
if (node == -FDT_ERR_BADOFFSET) {
|
||||
node = fdt_node_offset_by_compatible(fdt, -1, "st,stpmic1");
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
int dt_pmic_status(void)
|
||||
{
|
||||
static int status = -FDT_ERR_BADVALUE;
|
||||
int node;
|
||||
void *fdt;
|
||||
|
||||
if (status != -FDT_ERR_BADVALUE) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
node = dt_get_pmic_node(fdt);
|
||||
if (node <= 0) {
|
||||
status = -FDT_ERR_NOTFOUND;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = (int)fdt_get_status(node);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool dt_pmic_is_secure(void)
|
||||
{
|
||||
int status = dt_pmic_status();
|
||||
|
||||
return (status >= 0) &&
|
||||
(status == DT_SECURE) &&
|
||||
(i2c_handle.dt_status == DT_SECURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get PMIC and its I2C bus configuration from the device tree.
|
||||
* Return 0 on success, negative on error, 1 if no PMIC node is defined.
|
||||
*/
|
||||
static int dt_pmic_i2c_config(struct dt_node_info *i2c_info,
|
||||
struct stm32_i2c_init_s *init)
|
||||
{
|
||||
static int i2c_node = -FDT_ERR_NOTFOUND;
|
||||
void *fdt;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if (i2c_node == -FDT_ERR_NOTFOUND) {
|
||||
int pmic_node;
|
||||
const fdt32_t *cuint;
|
||||
|
||||
pmic_node = dt_get_pmic_node(fdt);
|
||||
if (pmic_node < 0) {
|
||||
return PMIC_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, pmic_node, "reg", NULL);
|
||||
if (cuint == NULL) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
pmic_i2c_addr = fdt32_to_cpu(*cuint) << 1;
|
||||
if (pmic_i2c_addr > UINT16_MAX) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
i2c_node = fdt_parent_offset(fdt, pmic_node);
|
||||
if (i2c_node < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
}
|
||||
|
||||
dt_fill_device_info(i2c_info, i2c_node);
|
||||
if (i2c_info->base == 0U) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
return stm32_i2c_get_setup_from_fdt(fdt, i2c_node, init);
|
||||
}
|
||||
|
||||
bool initialize_pmic_i2c(void)
|
||||
{
|
||||
int ret;
|
||||
struct dt_node_info i2c_info;
|
||||
struct i2c_handle_s *i2c = &i2c_handle;
|
||||
struct stm32_i2c_init_s i2c_init;
|
||||
|
||||
ret = dt_pmic_i2c_config(&i2c_info, &i2c_init);
|
||||
if (ret < 0) {
|
||||
ERROR("I2C configuration failed %d\n", ret);
|
||||
panic();
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Initialize PMIC I2C */
|
||||
i2c->i2c_base_addr = i2c_info.base;
|
||||
i2c->dt_status = i2c_info.status;
|
||||
i2c->clock = i2c_info.clock;
|
||||
i2c->i2c_state = I2C_STATE_RESET;
|
||||
i2c_init.own_address1 = pmic_i2c_addr;
|
||||
i2c_init.addressing_mode = I2C_ADDRESSINGMODE_7BIT;
|
||||
i2c_init.dual_address_mode = I2C_DUALADDRESS_DISABLE;
|
||||
i2c_init.own_address2 = 0;
|
||||
i2c_init.own_address2_masks = I2C_OAR2_OA2NOMASK;
|
||||
i2c_init.general_call_mode = I2C_GENERALCALL_DISABLE;
|
||||
i2c_init.no_stretch_mode = I2C_NOSTRETCH_DISABLE;
|
||||
i2c_init.analog_filter = 1;
|
||||
i2c_init.digital_filter_coef = 0;
|
||||
|
||||
ret = stm32_i2c_init(i2c, &i2c_init);
|
||||
if (ret != 0) {
|
||||
ERROR("Cannot initialize I2C %x (%d)\n",
|
||||
i2c->i2c_base_addr, ret);
|
||||
panic();
|
||||
}
|
||||
|
||||
if (!stm32_i2c_is_device_ready(i2c, pmic_i2c_addr, 1,
|
||||
I2C_TIMEOUT_BUSY_MS)) {
|
||||
ERROR("I2C device not ready\n");
|
||||
panic();
|
||||
}
|
||||
|
||||
stpmic1_bind_i2c(i2c, (uint16_t)pmic_i2c_addr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void register_pmic_shared_peripherals(void)
|
||||
{
|
||||
uintptr_t i2c_base = i2c_handle.i2c_base_addr;
|
||||
|
||||
if (dt_pmic_is_secure()) {
|
||||
stm32mp_register_secure_periph_iomem(i2c_base);
|
||||
} else {
|
||||
if (i2c_base != 0U) {
|
||||
stm32mp_register_non_secure_periph_iomem(i2c_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initialize_pmic(void)
|
||||
{
|
||||
if (!initialize_pmic_i2c()) {
|
||||
VERBOSE("No PMIC\n");
|
||||
return;
|
||||
}
|
||||
|
||||
register_pmic_shared_peripherals();
|
||||
|
||||
if (register_pmic() < 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
if (stpmic1_powerctrl_on() < 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
void print_pmic_info_and_debug(void)
|
||||
{
|
||||
unsigned long pmic_version;
|
||||
|
||||
if (stpmic1_get_version(&pmic_version) != 0) {
|
||||
ERROR("Failed to access PMIC\n");
|
||||
panic();
|
||||
}
|
||||
|
||||
INFO("PMIC version = 0x%02lx\n", pmic_version);
|
||||
}
|
||||
#endif
|
||||
|
||||
int pmic_ddr_power_init(enum ddr_type ddr_type)
|
||||
{
|
||||
int status;
|
||||
uint16_t buck3_min_mv;
|
||||
struct rdev *buck2, *buck3, *vref;
|
||||
struct rdev *ldo3 __unused;
|
||||
|
||||
buck2 = regulator_get_by_name("buck2");
|
||||
if (buck2 == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
#if STM32MP15
|
||||
ldo3 = regulator_get_by_name("ldo3");
|
||||
if (ldo3 == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
#endif
|
||||
|
||||
vref = regulator_get_by_name("vref_ddr");
|
||||
if (vref == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
switch (ddr_type) {
|
||||
case STM32MP_DDR3:
|
||||
#if STM32MP15
|
||||
status = regulator_set_flag(ldo3, REGUL_SINK_SOURCE);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
status = regulator_set_min_voltage(buck2);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = regulator_enable(buck2);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = regulator_enable(vref);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
#if STM32MP15
|
||||
status = regulator_enable(ldo3);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case STM32MP_LPDDR2:
|
||||
case STM32MP_LPDDR3:
|
||||
/*
|
||||
* Set LDO3 to 1.8V
|
||||
* Set LDO3 to bypass mode if BUCK3 = 1.8V
|
||||
* Set LDO3 to normal mode if BUCK3 != 1.8V
|
||||
*/
|
||||
buck3 = regulator_get_by_name("buck3");
|
||||
if (buck3 == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
regulator_get_range(buck3, &buck3_min_mv, NULL);
|
||||
|
||||
#if STM32MP15
|
||||
if (buck3_min_mv != 1800) {
|
||||
status = regulator_set_min_voltage(ldo3);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
status = regulator_set_flag(ldo3, REGUL_ENABLE_BYPASS);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
status = regulator_set_min_voltage(buck2);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
#if STM32MP15
|
||||
status = regulator_enable(ldo3);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
status = regulator_enable(buck2);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = regulator_enable(vref);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pmic_voltages_init(void)
|
||||
{
|
||||
#if STM32MP13
|
||||
struct rdev *buck1, *buck4;
|
||||
int status;
|
||||
|
||||
buck1 = regulator_get_by_name("buck1");
|
||||
if (buck1 == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
buck4 = regulator_get_by_name("buck4");
|
||||
if (buck4 == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
status = regulator_set_min_voltage(buck1);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = regulator_set_min_voltage(buck4);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
STPMIC1_BUCK1 = 0,
|
||||
STPMIC1_BUCK2,
|
||||
STPMIC1_BUCK3,
|
||||
STPMIC1_BUCK4,
|
||||
STPMIC1_LDO1,
|
||||
STPMIC1_LDO2,
|
||||
STPMIC1_LDO3,
|
||||
STPMIC1_LDO4,
|
||||
STPMIC1_LDO5,
|
||||
STPMIC1_LDO6,
|
||||
STPMIC1_VREF_DDR,
|
||||
STPMIC1_BOOST,
|
||||
STPMIC1_VBUS_OTG,
|
||||
STPMIC1_SW_OUT,
|
||||
};
|
||||
|
||||
static int pmic_set_state(const struct regul_description *desc, bool enable)
|
||||
{
|
||||
VERBOSE("%s: set state to %d\n", desc->node_name, enable);
|
||||
|
||||
if (enable == STATE_ENABLE) {
|
||||
return stpmic1_regulator_enable(desc->node_name);
|
||||
} else {
|
||||
return stpmic1_regulator_disable(desc->node_name);
|
||||
}
|
||||
}
|
||||
|
||||
static int pmic_get_state(const struct regul_description *desc)
|
||||
{
|
||||
VERBOSE("%s: get state\n", desc->node_name);
|
||||
|
||||
return stpmic1_is_regulator_enabled(desc->node_name);
|
||||
}
|
||||
|
||||
static int pmic_get_voltage(const struct regul_description *desc)
|
||||
{
|
||||
VERBOSE("%s: get volt\n", desc->node_name);
|
||||
|
||||
return stpmic1_regulator_voltage_get(desc->node_name);
|
||||
}
|
||||
|
||||
static int pmic_set_voltage(const struct regul_description *desc, uint16_t mv)
|
||||
{
|
||||
VERBOSE("%s: get volt\n", desc->node_name);
|
||||
|
||||
return stpmic1_regulator_voltage_set(desc->node_name, mv);
|
||||
}
|
||||
|
||||
static int pmic_list_voltages(const struct regul_description *desc,
|
||||
const uint16_t **levels, size_t *count)
|
||||
{
|
||||
VERBOSE("%s: list volt\n", desc->node_name);
|
||||
|
||||
return stpmic1_regulator_levels_mv(desc->node_name, levels, count);
|
||||
}
|
||||
|
||||
static int pmic_set_flag(const struct regul_description *desc, uint16_t flag)
|
||||
{
|
||||
VERBOSE("%s: set_flag 0x%x\n", desc->node_name, flag);
|
||||
|
||||
switch (flag) {
|
||||
case REGUL_OCP:
|
||||
return stpmic1_regulator_icc_set(desc->node_name);
|
||||
|
||||
case REGUL_ACTIVE_DISCHARGE:
|
||||
return stpmic1_active_discharge_mode_set(desc->node_name);
|
||||
|
||||
case REGUL_PULL_DOWN:
|
||||
return stpmic1_regulator_pull_down_set(desc->node_name);
|
||||
|
||||
case REGUL_MASK_RESET:
|
||||
return stpmic1_regulator_mask_reset_set(desc->node_name);
|
||||
|
||||
case REGUL_SINK_SOURCE:
|
||||
return stpmic1_regulator_sink_mode_set(desc->node_name);
|
||||
|
||||
case REGUL_ENABLE_BYPASS:
|
||||
return stpmic1_regulator_bypass_mode_set(desc->node_name);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regul_ops pmic_ops = {
|
||||
.set_state = pmic_set_state,
|
||||
.get_state = pmic_get_state,
|
||||
.set_voltage = pmic_set_voltage,
|
||||
.get_voltage = pmic_get_voltage,
|
||||
.list_voltages = pmic_list_voltages,
|
||||
.set_flag = pmic_set_flag,
|
||||
};
|
||||
|
||||
#define DEFINE_REGU(name) { \
|
||||
.node_name = name, \
|
||||
.ops = &pmic_ops, \
|
||||
.driver_data = NULL, \
|
||||
.enable_ramp_delay = 1000, \
|
||||
}
|
||||
|
||||
static const struct regul_description pmic_regs[] = {
|
||||
[STPMIC1_BUCK1] = DEFINE_REGU("buck1"),
|
||||
[STPMIC1_BUCK2] = DEFINE_REGU("buck2"),
|
||||
[STPMIC1_BUCK3] = DEFINE_REGU("buck3"),
|
||||
[STPMIC1_BUCK4] = DEFINE_REGU("buck4"),
|
||||
[STPMIC1_LDO1] = DEFINE_REGU("ldo1"),
|
||||
[STPMIC1_LDO2] = DEFINE_REGU("ldo2"),
|
||||
[STPMIC1_LDO3] = DEFINE_REGU("ldo3"),
|
||||
[STPMIC1_LDO4] = DEFINE_REGU("ldo4"),
|
||||
[STPMIC1_LDO5] = DEFINE_REGU("ldo5"),
|
||||
[STPMIC1_LDO6] = DEFINE_REGU("ldo6"),
|
||||
[STPMIC1_VREF_DDR] = DEFINE_REGU("vref_ddr"),
|
||||
[STPMIC1_BOOST] = DEFINE_REGU("boost"),
|
||||
[STPMIC1_VBUS_OTG] = DEFINE_REGU("pwr_sw1"),
|
||||
[STPMIC1_SW_OUT] = DEFINE_REGU("pwr_sw2"),
|
||||
};
|
||||
|
||||
#define NB_REG ARRAY_SIZE(pmic_regs)
|
||||
|
||||
static int register_pmic(void)
|
||||
{
|
||||
void *fdt;
|
||||
int pmic_node, regulators_node, subnode;
|
||||
|
||||
VERBOSE("Register pmic\n");
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
pmic_node = dt_get_pmic_node(fdt);
|
||||
if (pmic_node < 0) {
|
||||
return pmic_node;
|
||||
}
|
||||
|
||||
regulators_node = fdt_subnode_offset(fdt, pmic_node, "regulators");
|
||||
if (regulators_node < 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
fdt_for_each_subnode(subnode, fdt, regulators_node) {
|
||||
const char *reg_name = fdt_get_name(fdt, subnode, NULL);
|
||||
const struct regul_description *desc;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
for (i = 0; i < NB_REG; i++) {
|
||||
desc = &pmic_regs[i];
|
||||
if (strcmp(desc->node_name, reg_name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(i < NB_REG);
|
||||
|
||||
ret = regulator_register(desc, subnode);
|
||||
if (ret != 0) {
|
||||
WARN("%s:%d failed to register %s\n", __func__,
|
||||
__LINE__, reg_name);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,937 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2021, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/st/stpmic1.h>
|
||||
|
||||
#define I2C_TIMEOUT_MS 25
|
||||
|
||||
struct regul_struct {
|
||||
const char *dt_node_name;
|
||||
const uint16_t *voltage_table;
|
||||
uint8_t voltage_table_size;
|
||||
uint8_t control_reg;
|
||||
uint8_t enable_mask;
|
||||
uint8_t low_power_reg;
|
||||
uint8_t pull_down_reg;
|
||||
uint8_t pull_down;
|
||||
uint8_t mask_reset_reg;
|
||||
uint8_t mask_reset;
|
||||
uint8_t icc_reg;
|
||||
uint8_t icc_mask;
|
||||
};
|
||||
|
||||
static struct i2c_handle_s *pmic_i2c_handle;
|
||||
static uint16_t pmic_i2c_addr;
|
||||
/*
|
||||
* Special mode corresponds to LDO3 in sink source mode or in bypass mode.
|
||||
* LDO3 doesn't switch back from special to normal mode.
|
||||
*/
|
||||
static bool ldo3_special_mode;
|
||||
|
||||
/* Voltage tables in mV */
|
||||
static const uint16_t buck1_voltage_table[] = {
|
||||
725,
|
||||
725,
|
||||
725,
|
||||
725,
|
||||
725,
|
||||
725,
|
||||
750,
|
||||
775,
|
||||
800,
|
||||
825,
|
||||
850,
|
||||
875,
|
||||
900,
|
||||
925,
|
||||
950,
|
||||
975,
|
||||
1000,
|
||||
1025,
|
||||
1050,
|
||||
1075,
|
||||
1100,
|
||||
1125,
|
||||
1150,
|
||||
1175,
|
||||
1200,
|
||||
1225,
|
||||
1250,
|
||||
1275,
|
||||
1300,
|
||||
1325,
|
||||
1350,
|
||||
1375,
|
||||
1400,
|
||||
1425,
|
||||
1450,
|
||||
1475,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
1500,
|
||||
};
|
||||
|
||||
static const uint16_t buck2_voltage_table[] = {
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1050,
|
||||
1050,
|
||||
1100,
|
||||
1100,
|
||||
1150,
|
||||
1150,
|
||||
1200,
|
||||
1200,
|
||||
1250,
|
||||
1250,
|
||||
1300,
|
||||
1300,
|
||||
1350,
|
||||
1350,
|
||||
1400,
|
||||
1400,
|
||||
1450,
|
||||
1450,
|
||||
1500,
|
||||
};
|
||||
|
||||
static const uint16_t buck3_voltage_table[] = {
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1000,
|
||||
1100,
|
||||
1100,
|
||||
1100,
|
||||
1100,
|
||||
1200,
|
||||
1200,
|
||||
1200,
|
||||
1200,
|
||||
1300,
|
||||
1300,
|
||||
1300,
|
||||
1300,
|
||||
1400,
|
||||
1400,
|
||||
1400,
|
||||
1400,
|
||||
1500,
|
||||
1600,
|
||||
1700,
|
||||
1800,
|
||||
1900,
|
||||
2000,
|
||||
2100,
|
||||
2200,
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2800,
|
||||
2900,
|
||||
3000,
|
||||
3100,
|
||||
3200,
|
||||
3300,
|
||||
3400,
|
||||
};
|
||||
|
||||
static const uint16_t buck4_voltage_table[] = {
|
||||
600,
|
||||
625,
|
||||
650,
|
||||
675,
|
||||
700,
|
||||
725,
|
||||
750,
|
||||
775,
|
||||
800,
|
||||
825,
|
||||
850,
|
||||
875,
|
||||
900,
|
||||
925,
|
||||
950,
|
||||
975,
|
||||
1000,
|
||||
1025,
|
||||
1050,
|
||||
1075,
|
||||
1100,
|
||||
1125,
|
||||
1150,
|
||||
1175,
|
||||
1200,
|
||||
1225,
|
||||
1250,
|
||||
1275,
|
||||
1300,
|
||||
1300,
|
||||
1350,
|
||||
1350,
|
||||
1400,
|
||||
1400,
|
||||
1450,
|
||||
1450,
|
||||
1500,
|
||||
1600,
|
||||
1700,
|
||||
1800,
|
||||
1900,
|
||||
2000,
|
||||
2100,
|
||||
2200,
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2800,
|
||||
2900,
|
||||
3000,
|
||||
3100,
|
||||
3200,
|
||||
3300,
|
||||
3400,
|
||||
3500,
|
||||
3600,
|
||||
3700,
|
||||
3800,
|
||||
3900,
|
||||
};
|
||||
|
||||
static const uint16_t ldo1_voltage_table[] = {
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1800,
|
||||
1900,
|
||||
2000,
|
||||
2100,
|
||||
2200,
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2800,
|
||||
2900,
|
||||
3000,
|
||||
3100,
|
||||
3200,
|
||||
3300,
|
||||
};
|
||||
|
||||
static const uint16_t ldo2_voltage_table[] = {
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1800,
|
||||
1900,
|
||||
2000,
|
||||
2100,
|
||||
2200,
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2800,
|
||||
2900,
|
||||
3000,
|
||||
3100,
|
||||
3200,
|
||||
3300,
|
||||
};
|
||||
|
||||
static const uint16_t ldo3_voltage_table[] = {
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1800,
|
||||
1900,
|
||||
2000,
|
||||
2100,
|
||||
2200,
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2800,
|
||||
2900,
|
||||
3000,
|
||||
3100,
|
||||
3200,
|
||||
3300,
|
||||
3300,
|
||||
3300,
|
||||
3300,
|
||||
3300,
|
||||
3300,
|
||||
3300,
|
||||
};
|
||||
|
||||
/* Special mode table is used for sink source OR bypass mode */
|
||||
static const uint16_t ldo3_special_mode_table[] = {
|
||||
0,
|
||||
};
|
||||
|
||||
static const uint16_t ldo5_voltage_table[] = {
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1700,
|
||||
1800,
|
||||
1900,
|
||||
2000,
|
||||
2100,
|
||||
2200,
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2800,
|
||||
2900,
|
||||
3000,
|
||||
3100,
|
||||
3200,
|
||||
3300,
|
||||
3400,
|
||||
3500,
|
||||
3600,
|
||||
3700,
|
||||
3800,
|
||||
3900,
|
||||
};
|
||||
|
||||
static const uint16_t ldo6_voltage_table[] = {
|
||||
900,
|
||||
1000,
|
||||
1100,
|
||||
1200,
|
||||
1300,
|
||||
1400,
|
||||
1500,
|
||||
1600,
|
||||
1700,
|
||||
1800,
|
||||
1900,
|
||||
2000,
|
||||
2100,
|
||||
2200,
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2800,
|
||||
2900,
|
||||
3000,
|
||||
3100,
|
||||
3200,
|
||||
3300,
|
||||
};
|
||||
|
||||
static const uint16_t ldo4_voltage_table[] = {
|
||||
3300,
|
||||
};
|
||||
|
||||
static const uint16_t vref_ddr_voltage_table[] = {
|
||||
3300,
|
||||
};
|
||||
|
||||
static const uint16_t fixed_5v_voltage_table[] = {
|
||||
5000,
|
||||
};
|
||||
|
||||
/* Table of Regulators in PMIC SoC */
|
||||
static const struct regul_struct regulators_table[] = {
|
||||
{
|
||||
.dt_node_name = "buck1",
|
||||
.voltage_table = buck1_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(buck1_voltage_table),
|
||||
.control_reg = BUCK1_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = BUCK1_PWRCTRL_REG,
|
||||
.pull_down_reg = BUCK_PULL_DOWN_REG,
|
||||
.pull_down = BUCK1_PULL_DOWN_SHIFT,
|
||||
.mask_reset_reg = MASK_RESET_BUCK_REG,
|
||||
.mask_reset = BUCK1_MASK_RESET,
|
||||
.icc_reg = BUCK_ICC_TURNOFF_REG,
|
||||
.icc_mask = BUCK1_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "buck2",
|
||||
.voltage_table = buck2_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(buck2_voltage_table),
|
||||
.control_reg = BUCK2_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = BUCK2_PWRCTRL_REG,
|
||||
.pull_down_reg = BUCK_PULL_DOWN_REG,
|
||||
.pull_down = BUCK2_PULL_DOWN_SHIFT,
|
||||
.mask_reset_reg = MASK_RESET_BUCK_REG,
|
||||
.mask_reset = BUCK2_MASK_RESET,
|
||||
.icc_reg = BUCK_ICC_TURNOFF_REG,
|
||||
.icc_mask = BUCK2_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "buck3",
|
||||
.voltage_table = buck3_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(buck3_voltage_table),
|
||||
.control_reg = BUCK3_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = BUCK3_PWRCTRL_REG,
|
||||
.pull_down_reg = BUCK_PULL_DOWN_REG,
|
||||
.pull_down = BUCK3_PULL_DOWN_SHIFT,
|
||||
.mask_reset_reg = MASK_RESET_BUCK_REG,
|
||||
.mask_reset = BUCK3_MASK_RESET,
|
||||
.icc_reg = BUCK_ICC_TURNOFF_REG,
|
||||
.icc_mask = BUCK3_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "buck4",
|
||||
.voltage_table = buck4_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(buck4_voltage_table),
|
||||
.control_reg = BUCK4_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = BUCK4_PWRCTRL_REG,
|
||||
.pull_down_reg = BUCK_PULL_DOWN_REG,
|
||||
.pull_down = BUCK4_PULL_DOWN_SHIFT,
|
||||
.mask_reset_reg = MASK_RESET_BUCK_REG,
|
||||
.mask_reset = BUCK4_MASK_RESET,
|
||||
.icc_reg = BUCK_ICC_TURNOFF_REG,
|
||||
.icc_mask = BUCK4_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "ldo1",
|
||||
.voltage_table = ldo1_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(ldo1_voltage_table),
|
||||
.control_reg = LDO1_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = LDO1_PWRCTRL_REG,
|
||||
.mask_reset_reg = MASK_RESET_LDO_REG,
|
||||
.mask_reset = LDO1_MASK_RESET,
|
||||
.icc_reg = LDO_ICC_TURNOFF_REG,
|
||||
.icc_mask = LDO1_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "ldo2",
|
||||
.voltage_table = ldo2_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(ldo2_voltage_table),
|
||||
.control_reg = LDO2_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = LDO2_PWRCTRL_REG,
|
||||
.mask_reset_reg = MASK_RESET_LDO_REG,
|
||||
.mask_reset = LDO2_MASK_RESET,
|
||||
.icc_reg = LDO_ICC_TURNOFF_REG,
|
||||
.icc_mask = LDO2_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "ldo3",
|
||||
.voltage_table = ldo3_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(ldo3_voltage_table),
|
||||
.control_reg = LDO3_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = LDO3_PWRCTRL_REG,
|
||||
.mask_reset_reg = MASK_RESET_LDO_REG,
|
||||
.mask_reset = LDO3_MASK_RESET,
|
||||
.icc_reg = LDO_ICC_TURNOFF_REG,
|
||||
.icc_mask = LDO3_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "ldo4",
|
||||
.voltage_table = ldo4_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(ldo4_voltage_table),
|
||||
.control_reg = LDO4_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = LDO4_PWRCTRL_REG,
|
||||
.mask_reset_reg = MASK_RESET_LDO_REG,
|
||||
.mask_reset = LDO4_MASK_RESET,
|
||||
.icc_reg = LDO_ICC_TURNOFF_REG,
|
||||
.icc_mask = LDO4_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "ldo5",
|
||||
.voltage_table = ldo5_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(ldo5_voltage_table),
|
||||
.control_reg = LDO5_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = LDO5_PWRCTRL_REG,
|
||||
.mask_reset_reg = MASK_RESET_LDO_REG,
|
||||
.mask_reset = LDO5_MASK_RESET,
|
||||
.icc_reg = LDO_ICC_TURNOFF_REG,
|
||||
.icc_mask = LDO5_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "ldo6",
|
||||
.voltage_table = ldo6_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(ldo6_voltage_table),
|
||||
.control_reg = LDO6_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = LDO6_PWRCTRL_REG,
|
||||
.mask_reset_reg = MASK_RESET_LDO_REG,
|
||||
.mask_reset = LDO6_MASK_RESET,
|
||||
.icc_reg = LDO_ICC_TURNOFF_REG,
|
||||
.icc_mask = LDO6_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "vref_ddr",
|
||||
.voltage_table = vref_ddr_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(vref_ddr_voltage_table),
|
||||
.control_reg = VREF_DDR_CONTROL_REG,
|
||||
.enable_mask = LDO_BUCK_ENABLE_MASK,
|
||||
.low_power_reg = VREF_DDR_PWRCTRL_REG,
|
||||
.mask_reset_reg = MASK_RESET_LDO_REG,
|
||||
.mask_reset = VREF_DDR_MASK_RESET,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "boost",
|
||||
.voltage_table = fixed_5v_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(fixed_5v_voltage_table),
|
||||
.control_reg = USB_CONTROL_REG,
|
||||
.enable_mask = BOOST_ENABLED,
|
||||
.icc_reg = BUCK_ICC_TURNOFF_REG,
|
||||
.icc_mask = BOOST_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "pwr_sw1",
|
||||
.voltage_table = fixed_5v_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(fixed_5v_voltage_table),
|
||||
.control_reg = USB_CONTROL_REG,
|
||||
.enable_mask = USBSW_OTG_SWITCH_ENABLED,
|
||||
.icc_reg = BUCK_ICC_TURNOFF_REG,
|
||||
.icc_mask = PWR_SW1_ICC_SHIFT,
|
||||
},
|
||||
{
|
||||
.dt_node_name = "pwr_sw2",
|
||||
.voltage_table = fixed_5v_voltage_table,
|
||||
.voltage_table_size = ARRAY_SIZE(fixed_5v_voltage_table),
|
||||
.control_reg = USB_CONTROL_REG,
|
||||
.enable_mask = SWIN_SWOUT_ENABLED,
|
||||
.icc_reg = BUCK_ICC_TURNOFF_REG,
|
||||
.icc_mask = PWR_SW2_ICC_SHIFT,
|
||||
},
|
||||
};
|
||||
|
||||
#define MAX_REGUL ARRAY_SIZE(regulators_table)
|
||||
|
||||
static const struct regul_struct *get_regulator_data(const char *name)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0 ; i < MAX_REGUL ; i++) {
|
||||
if (strncmp(name, regulators_table[i].dt_node_name,
|
||||
strlen(regulators_table[i].dt_node_name)) == 0) {
|
||||
return ®ulators_table[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Regulator not found */
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uint8_t voltage_to_index(const char *name, uint16_t millivolts)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0 ; i < regul->voltage_table_size ; i++) {
|
||||
if (regul->voltage_table[i] == millivolts) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Voltage not found */
|
||||
panic();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stpmic1_powerctrl_on(void)
|
||||
{
|
||||
return stpmic1_register_update(MAIN_CONTROL_REG, PWRCTRL_PIN_VALID,
|
||||
PWRCTRL_PIN_VALID);
|
||||
}
|
||||
|
||||
int stpmic1_switch_off(void)
|
||||
{
|
||||
return stpmic1_register_update(MAIN_CONTROL_REG, 1,
|
||||
SOFTWARE_SWITCH_OFF_ENABLED);
|
||||
}
|
||||
|
||||
int stpmic1_regulator_enable(const char *name)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
|
||||
return stpmic1_register_update(regul->control_reg, regul->enable_mask,
|
||||
regul->enable_mask);
|
||||
}
|
||||
|
||||
int stpmic1_regulator_disable(const char *name)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
|
||||
return stpmic1_register_update(regul->control_reg, 0,
|
||||
regul->enable_mask);
|
||||
}
|
||||
|
||||
bool stpmic1_is_regulator_enabled(const char *name)
|
||||
{
|
||||
uint8_t val;
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
|
||||
if (stpmic1_register_read(regul->control_reg, &val) != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
return (val & regul->enable_mask) == regul->enable_mask;
|
||||
}
|
||||
|
||||
int stpmic1_regulator_voltage_set(const char *name, uint16_t millivolts)
|
||||
{
|
||||
uint8_t voltage_index = voltage_to_index(name, millivolts);
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
uint8_t mask;
|
||||
|
||||
if ((strncmp(name, "ldo3", 5) == 0) && ldo3_special_mode) {
|
||||
/*
|
||||
* when the LDO3 is in special mode, we do not change voltage,
|
||||
* because by setting voltage, the LDO would leaves sink-source
|
||||
* mode. There is obviously no reason to leave sink-source mode
|
||||
* at runtime.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Voltage can be set for buck<N> or ldo<N> (except ldo4) regulators */
|
||||
if (strncmp(name, "buck", 4) == 0) {
|
||||
mask = BUCK_VOLTAGE_MASK;
|
||||
} else if ((strncmp(name, "ldo", 3) == 0) &&
|
||||
(strncmp(name, "ldo4", 5) != 0)) {
|
||||
mask = LDO_VOLTAGE_MASK;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return stpmic1_register_update(regul->control_reg,
|
||||
voltage_index << LDO_BUCK_VOLTAGE_SHIFT,
|
||||
mask);
|
||||
}
|
||||
|
||||
int stpmic1_regulator_pull_down_set(const char *name)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
|
||||
if (regul->pull_down_reg != 0) {
|
||||
return stpmic1_register_update(regul->pull_down_reg,
|
||||
BIT(regul->pull_down),
|
||||
LDO_BUCK_PULL_DOWN_MASK <<
|
||||
regul->pull_down);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stpmic1_regulator_mask_reset_set(const char *name)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
|
||||
if (regul->mask_reset_reg == 0U) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
return stpmic1_register_update(regul->mask_reset_reg,
|
||||
BIT(regul->mask_reset),
|
||||
LDO_BUCK_RESET_MASK <<
|
||||
regul->mask_reset);
|
||||
}
|
||||
|
||||
int stpmic1_regulator_icc_set(const char *name)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
|
||||
if (regul->mask_reset_reg == 0U) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
return stpmic1_register_update(regul->icc_reg,
|
||||
BIT(regul->icc_mask),
|
||||
BIT(regul->icc_mask));
|
||||
}
|
||||
|
||||
int stpmic1_regulator_sink_mode_set(const char *name)
|
||||
{
|
||||
if (strncmp(name, "ldo3", 5) != 0) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
ldo3_special_mode = true;
|
||||
|
||||
/* disable bypass mode, enable sink mode */
|
||||
return stpmic1_register_update(LDO3_CONTROL_REG,
|
||||
LDO3_DDR_SEL << LDO_BUCK_VOLTAGE_SHIFT,
|
||||
LDO3_BYPASS | LDO_VOLTAGE_MASK);
|
||||
}
|
||||
|
||||
int stpmic1_regulator_bypass_mode_set(const char *name)
|
||||
{
|
||||
if (strncmp(name, "ldo3", 5) != 0) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
ldo3_special_mode = true;
|
||||
|
||||
/* enable bypass mode, disable sink mode */
|
||||
return stpmic1_register_update(LDO3_CONTROL_REG,
|
||||
LDO3_BYPASS,
|
||||
LDO3_BYPASS | LDO_VOLTAGE_MASK);
|
||||
}
|
||||
|
||||
int stpmic1_active_discharge_mode_set(const char *name)
|
||||
{
|
||||
if (strncmp(name, "pwr_sw1", 8) == 0) {
|
||||
return stpmic1_register_update(USB_CONTROL_REG,
|
||||
VBUS_OTG_DISCHARGE,
|
||||
VBUS_OTG_DISCHARGE);
|
||||
}
|
||||
|
||||
if (strncmp(name, "pwr_sw2", 8) == 0) {
|
||||
return stpmic1_register_update(USB_CONTROL_REG,
|
||||
SW_OUT_DISCHARGE,
|
||||
SW_OUT_DISCHARGE);
|
||||
}
|
||||
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
int stpmic1_regulator_levels_mv(const char *name, const uint16_t **levels,
|
||||
size_t *levels_count)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
|
||||
if ((strncmp(name, "ldo3", 5) == 0) && ldo3_special_mode) {
|
||||
*levels_count = ARRAY_SIZE(ldo3_special_mode_table);
|
||||
*levels = ldo3_special_mode_table;
|
||||
} else {
|
||||
*levels_count = regul->voltage_table_size;
|
||||
*levels = regul->voltage_table;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stpmic1_regulator_voltage_get(const char *name)
|
||||
{
|
||||
const struct regul_struct *regul = get_regulator_data(name);
|
||||
uint8_t value;
|
||||
uint8_t mask;
|
||||
int status;
|
||||
|
||||
if ((strncmp(name, "ldo3", 5) == 0) && ldo3_special_mode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Voltage can be set for buck<N> or ldo<N> (except ldo4) regulators */
|
||||
if (strncmp(name, "buck", 4) == 0) {
|
||||
mask = BUCK_VOLTAGE_MASK;
|
||||
} else if ((strncmp(name, "ldo", 3) == 0) &&
|
||||
(strncmp(name, "ldo4", 5) != 0)) {
|
||||
mask = LDO_VOLTAGE_MASK;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = stpmic1_register_read(regul->control_reg, &value);
|
||||
if (status < 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
value = (value & mask) >> LDO_BUCK_VOLTAGE_SHIFT;
|
||||
|
||||
if (value > regul->voltage_table_size) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
return (int)regul->voltage_table[value];
|
||||
}
|
||||
|
||||
int stpmic1_register_read(uint8_t register_id, uint8_t *value)
|
||||
{
|
||||
return stm32_i2c_mem_read(pmic_i2c_handle, pmic_i2c_addr,
|
||||
(uint16_t)register_id,
|
||||
I2C_MEMADD_SIZE_8BIT, value,
|
||||
1, I2C_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
int stpmic1_register_write(uint8_t register_id, uint8_t value)
|
||||
{
|
||||
int status;
|
||||
|
||||
status = stm32_i2c_mem_write(pmic_i2c_handle, pmic_i2c_addr,
|
||||
(uint16_t)register_id,
|
||||
I2C_MEMADD_SIZE_8BIT, &value,
|
||||
1, I2C_TIMEOUT_MS);
|
||||
|
||||
#if ENABLE_ASSERTIONS
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if ((register_id != WATCHDOG_CONTROL_REG) && (register_id <= 0x40U)) {
|
||||
uint8_t readval;
|
||||
|
||||
status = stpmic1_register_read(register_id, &readval);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (readval != value) {
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int stpmic1_register_update(uint8_t register_id, uint8_t value, uint8_t mask)
|
||||
{
|
||||
int status;
|
||||
uint8_t val;
|
||||
|
||||
status = stpmic1_register_read(register_id, &val);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
val = (val & ~mask) | (value & mask);
|
||||
|
||||
return stpmic1_register_write(register_id, val);
|
||||
}
|
||||
|
||||
void stpmic1_bind_i2c(struct i2c_handle_s *i2c_handle, uint16_t i2c_addr)
|
||||
{
|
||||
pmic_i2c_handle = i2c_handle;
|
||||
pmic_i2c_addr = i2c_addr;
|
||||
}
|
||||
|
||||
void stpmic1_dump_regulators(void)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0U; i < MAX_REGUL; i++) {
|
||||
const char *name __unused = regulators_table[i].dt_node_name;
|
||||
|
||||
VERBOSE("PMIC regul %s: %sable, %dmV",
|
||||
name,
|
||||
stpmic1_is_regulator_enabled(name) ? "en" : "dis",
|
||||
stpmic1_regulator_voltage_get(name));
|
||||
}
|
||||
}
|
||||
|
||||
int stpmic1_get_version(unsigned long *version)
|
||||
{
|
||||
uint8_t read_val;
|
||||
int status;
|
||||
|
||||
status = stpmic1_register_read(VERSION_STATUS_REG, &read_val);
|
||||
if (status < 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
*version = (unsigned long)read_val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/regulator.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#define MAX_PROPERTY_LEN 64
|
||||
|
||||
static struct rdev rdev_array[PLAT_NB_RDEVS];
|
||||
|
||||
#define for_each_rdev(rdev) \
|
||||
for (rdev = rdev_array; rdev < (rdev_array + PLAT_NB_RDEVS); rdev++)
|
||||
|
||||
#define for_each_registered_rdev(rdev) \
|
||||
for (rdev = rdev_array; \
|
||||
(rdev < (rdev_array + PLAT_NB_RDEVS)) && (rdev->desc != NULL); rdev++)
|
||||
|
||||
static void lock_driver(const struct rdev *rdev)
|
||||
{
|
||||
if (rdev->desc->ops->lock != NULL) {
|
||||
rdev->desc->ops->lock(rdev->desc);
|
||||
}
|
||||
}
|
||||
|
||||
static void unlock_driver(const struct rdev *rdev)
|
||||
{
|
||||
if (rdev->desc->ops->unlock != NULL) {
|
||||
rdev->desc->ops->unlock(rdev->desc);
|
||||
}
|
||||
}
|
||||
|
||||
static struct rdev *regulator_get_by_phandle(int32_t phandle)
|
||||
{
|
||||
struct rdev *rdev;
|
||||
|
||||
for_each_registered_rdev(rdev) {
|
||||
if (rdev->phandle == phandle) {
|
||||
return rdev;
|
||||
}
|
||||
}
|
||||
|
||||
WARN("%s: phandle %d not found\n", __func__, phandle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a regulator from its node name
|
||||
*
|
||||
* @fdt - pointer to device tree memory
|
||||
* @node_name - name of the node "ldo1"
|
||||
* Return pointer to rdev if succeed, NULL else.
|
||||
*/
|
||||
struct rdev *regulator_get_by_name(const char *node_name)
|
||||
{
|
||||
struct rdev *rdev;
|
||||
|
||||
assert(node_name != NULL);
|
||||
VERBOSE("get %s\n", node_name);
|
||||
|
||||
for_each_registered_rdev(rdev) {
|
||||
if (strcmp(rdev->desc->node_name, node_name) == 0) {
|
||||
return rdev;
|
||||
}
|
||||
}
|
||||
|
||||
WARN("%s: %s not found\n", __func__, node_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int32_t get_supply_phandle(const void *fdt, int node, const char *name)
|
||||
{
|
||||
const fdt32_t *cuint;
|
||||
int len __unused;
|
||||
int supply_phandle = -FDT_ERR_NOTFOUND;
|
||||
char prop_name[MAX_PROPERTY_LEN];
|
||||
|
||||
len = snprintf(prop_name, MAX_PROPERTY_LEN - 1, "%s-supply", name);
|
||||
assert((len >= 0) && (len < MAX_PROPERTY_LEN - 1));
|
||||
|
||||
cuint = fdt_getprop(fdt, node, prop_name, NULL);
|
||||
if (cuint != NULL) {
|
||||
supply_phandle = fdt32_to_cpu(*cuint);
|
||||
VERBOSE("%s: supplied by %d\n", name, supply_phandle);
|
||||
}
|
||||
|
||||
return supply_phandle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a regulator from a supply name
|
||||
*
|
||||
* @fdt - pointer to device tree memory
|
||||
* @node - offset of the node that contains the supply description
|
||||
* @name - name of the supply "vdd" for "vdd-supply'
|
||||
* Return pointer to rdev if succeed, NULL else.
|
||||
*/
|
||||
struct rdev *regulator_get_by_supply_name(const void *fdt, int node, const char *name)
|
||||
{
|
||||
const int p = get_supply_phandle(fdt, node, name);
|
||||
|
||||
if (p < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return regulator_get_by_phandle(p);
|
||||
}
|
||||
|
||||
static int __regulator_set_state(struct rdev *rdev, bool state)
|
||||
{
|
||||
if (rdev->desc->ops->set_state == NULL) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return rdev->desc->ops->set_state(rdev->desc, state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable regulator
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
int regulator_enable(struct rdev *rdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(rdev != NULL);
|
||||
|
||||
ret = __regulator_set_state(rdev, STATE_ENABLE);
|
||||
|
||||
udelay(rdev->enable_ramp_delay);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable regulator
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
int regulator_disable(struct rdev *rdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(rdev != NULL);
|
||||
|
||||
if (rdev->flags & REGUL_ALWAYS_ON) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = __regulator_set_state(rdev, STATE_DISABLE);
|
||||
|
||||
udelay(rdev->enable_ramp_delay);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Regulator enabled query
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* Return 0 if disabled, 1 if enabled, <0 else.
|
||||
*/
|
||||
int regulator_is_enabled(const struct rdev *rdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(rdev != NULL);
|
||||
|
||||
VERBOSE("%s: is en\n", rdev->desc->node_name);
|
||||
|
||||
if (rdev->desc->ops->get_state == NULL) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
lock_driver(rdev);
|
||||
|
||||
ret = rdev->desc->ops->get_state(rdev->desc);
|
||||
if (ret < 0) {
|
||||
ERROR("regul %s get state failed: err:%d\n",
|
||||
rdev->desc->node_name, ret);
|
||||
}
|
||||
|
||||
unlock_driver(rdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set regulator voltage
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* @mvolt - Target voltage level in millivolt
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
int regulator_set_voltage(struct rdev *rdev, uint16_t mvolt)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(rdev != NULL);
|
||||
|
||||
VERBOSE("%s: set mvolt\n", rdev->desc->node_name);
|
||||
|
||||
if (rdev->desc->ops->set_voltage == NULL) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if ((mvolt < rdev->min_mv) || (mvolt > rdev->max_mv)) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
lock_driver(rdev);
|
||||
|
||||
ret = rdev->desc->ops->set_voltage(rdev->desc, mvolt);
|
||||
if (ret < 0) {
|
||||
ERROR("regul %s set volt failed: err:%d\n",
|
||||
rdev->desc->node_name, ret);
|
||||
}
|
||||
|
||||
unlock_driver(rdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set regulator min voltage
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
int regulator_set_min_voltage(struct rdev *rdev)
|
||||
{
|
||||
return regulator_set_voltage(rdev, rdev->min_mv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get regulator voltage
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* Return milli volts if succeed, <0 else.
|
||||
*/
|
||||
int regulator_get_voltage(const struct rdev *rdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(rdev != NULL);
|
||||
|
||||
VERBOSE("%s: get volt\n", rdev->desc->node_name);
|
||||
|
||||
if (rdev->desc->ops->get_voltage == NULL) {
|
||||
return rdev->min_mv;
|
||||
}
|
||||
|
||||
lock_driver(rdev);
|
||||
|
||||
ret = rdev->desc->ops->get_voltage(rdev->desc);
|
||||
if (ret < 0) {
|
||||
ERROR("regul %s get voltage failed: err:%d\n",
|
||||
rdev->desc->node_name, ret);
|
||||
}
|
||||
|
||||
unlock_driver(rdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* List regulator voltages
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* @levels - out: array of supported millitvolt levels from min to max value
|
||||
* @count - out: number of possible millivolt values
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
int regulator_list_voltages(const struct rdev *rdev, const uint16_t **levels, size_t *count)
|
||||
{
|
||||
int ret;
|
||||
size_t n;
|
||||
|
||||
assert(rdev != NULL);
|
||||
assert(levels != NULL);
|
||||
assert(count != NULL);
|
||||
|
||||
VERBOSE("%s: list volt\n", rdev->desc->node_name);
|
||||
|
||||
if (rdev->desc->ops->list_voltages == NULL) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
lock_driver(rdev);
|
||||
|
||||
ret = rdev->desc->ops->list_voltages(rdev->desc, levels, count);
|
||||
|
||||
unlock_driver(rdev);
|
||||
|
||||
if (ret < 0) {
|
||||
ERROR("regul %s list_voltages failed: err: %d\n",
|
||||
rdev->desc->node_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reduce the possible values depending on min and max from device-tree
|
||||
*/
|
||||
n = *count;
|
||||
while ((n > 1U) && ((*levels)[n - 1U] > rdev->max_mv)) {
|
||||
n--;
|
||||
}
|
||||
|
||||
/* Verify that max val is a valid value */
|
||||
if (rdev->max_mv != (*levels)[n - 1]) {
|
||||
ERROR("regul %s: max value %u is invalid\n",
|
||||
rdev->desc->node_name, rdev->max_mv);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while ((n > 1U) && ((*levels[0U]) < rdev->min_mv)) {
|
||||
(*levels)++;
|
||||
n--;
|
||||
}
|
||||
|
||||
/* Verify that min is not too high */
|
||||
if (n == 0U) {
|
||||
ERROR("regul %s set min voltage is too high\n",
|
||||
rdev->desc->node_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Verify that min val is a valid vlue */
|
||||
if (rdev->min_mv != (*levels)[0U]) {
|
||||
ERROR("regul %s: min value %u is invalid\n",
|
||||
rdev->desc->node_name, rdev->min_mv);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*count = n;
|
||||
|
||||
VERBOSE("rdev->min_mv=%u rdev->max_mv=%u\n", rdev->min_mv, rdev->max_mv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get regulator voltages range
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* @min_mv - out: min possible millivolt value
|
||||
* @max_mv - out: max possible millivolt value
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
void regulator_get_range(const struct rdev *rdev, uint16_t *min_mv, uint16_t *max_mv)
|
||||
{
|
||||
assert(rdev != NULL);
|
||||
|
||||
if (min_mv != NULL) {
|
||||
*min_mv = rdev->min_mv;
|
||||
}
|
||||
if (max_mv != NULL) {
|
||||
*max_mv = rdev->max_mv;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set regulator flag
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* @flag - flag value to set (eg: REGUL_OCP)
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
int regulator_set_flag(struct rdev *rdev, uint16_t flag)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* check that only one bit is set on flag */
|
||||
if (__builtin_popcount(flag) != 1) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* REGUL_ALWAYS_ON and REGUL_BOOT_ON are internal properties of the core */
|
||||
if ((flag == REGUL_ALWAYS_ON) || (flag == REGUL_BOOT_ON)) {
|
||||
rdev->flags |= flag;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rdev->desc->ops->set_flag == NULL) {
|
||||
ERROR("%s can not set any flag\n", rdev->desc->node_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
lock_driver(rdev);
|
||||
|
||||
ret = rdev->desc->ops->set_flag(rdev->desc, flag);
|
||||
|
||||
unlock_driver(rdev);
|
||||
|
||||
if (ret != 0) {
|
||||
ERROR("%s: could not set flag %d ret=%d\n",
|
||||
rdev->desc->node_name, flag, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rdev->flags |= flag;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_properties(const void *fdt, struct rdev *rdev, int node)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (fdt_getprop(fdt, node, "regulator-always-on", NULL) != NULL) {
|
||||
VERBOSE("%s: set regulator-always-on\n", rdev->desc->node_name);
|
||||
ret = regulator_set_flag(rdev, REGUL_ALWAYS_ON);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the device-tree for a regulator
|
||||
*
|
||||
* Read min/max voltage from dt and check its validity
|
||||
* Read the properties, and call the driver to set flags
|
||||
* Read power supply phandle
|
||||
* Read and store low power mode states
|
||||
*
|
||||
* @rdev - pointer to rdev struct
|
||||
* @node - device-tree node offset of the regulator
|
||||
* Return 0 if disabled, 1 if enabled, <0 else.
|
||||
*/
|
||||
static int parse_dt(struct rdev *rdev, int node)
|
||||
{
|
||||
void *fdt;
|
||||
const fdt32_t *cuint;
|
||||
const uint16_t *levels;
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
VERBOSE("%s: parse dt\n", rdev->desc->node_name);
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
rdev->phandle = fdt_get_phandle(fdt, node);
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "regulator-min-microvolt", NULL);
|
||||
if (cuint != NULL) {
|
||||
uint16_t min_mv;
|
||||
|
||||
min_mv = (uint16_t)(fdt32_to_cpu(*cuint) / 1000U);
|
||||
VERBOSE("%s: min_mv=%d\n", rdev->desc->node_name, (int)min_mv);
|
||||
if (min_mv <= rdev->max_mv) {
|
||||
rdev->min_mv = min_mv;
|
||||
} else {
|
||||
ERROR("%s: min_mv=%d is too high\n",
|
||||
rdev->desc->node_name, (int)min_mv);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
cuint = fdt_getprop(fdt, node, "regulator-max-microvolt", NULL);
|
||||
if (cuint != NULL) {
|
||||
uint16_t max_mv;
|
||||
|
||||
max_mv = (uint16_t)(fdt32_to_cpu(*cuint) / 1000U);
|
||||
VERBOSE("%s: max_mv=%d\n", rdev->desc->node_name, (int)max_mv);
|
||||
if (max_mv >= rdev->min_mv) {
|
||||
rdev->max_mv = max_mv;
|
||||
} else {
|
||||
ERROR("%s: max_mv=%d is too low\n",
|
||||
rdev->desc->node_name, (int)max_mv);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* validate that min and max values can be used */
|
||||
ret = regulator_list_voltages(rdev, &levels, &size);
|
||||
if ((ret != 0) && (ret != -ENODEV)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = parse_properties(fdt, rdev, node);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a regulator driver in regulator framework.
|
||||
* Initialize voltage range from driver description
|
||||
*
|
||||
* @desc - pointer to the regulator description
|
||||
* @node - device-tree node offset of the regulator
|
||||
* Return 0 if succeed, non 0 else.
|
||||
*/
|
||||
int regulator_register(const struct regul_description *desc, int node)
|
||||
{
|
||||
struct rdev *rdev;
|
||||
|
||||
assert(desc != NULL);
|
||||
|
||||
VERBOSE("register %s\n", desc->node_name);
|
||||
|
||||
for_each_rdev(rdev) {
|
||||
if (rdev->desc == NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rdev == rdev_array + PLAT_NB_RDEVS) {
|
||||
WARN("Not enough place for regulators, PLAT_NB_RDEVS should be increased.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rdev->desc = desc;
|
||||
rdev->enable_ramp_delay = rdev->desc->enable_ramp_delay;
|
||||
|
||||
if (rdev->desc->ops->list_voltages != NULL) {
|
||||
int ret;
|
||||
const uint16_t *levels;
|
||||
size_t count;
|
||||
|
||||
lock_driver(rdev);
|
||||
|
||||
ret = rdev->desc->ops->list_voltages(rdev->desc, &levels, &count);
|
||||
|
||||
unlock_driver(rdev);
|
||||
|
||||
if (ret < 0) {
|
||||
ERROR("regul %s set state failed: err:%d\n",
|
||||
rdev->desc->node_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rdev->min_mv = levels[0];
|
||||
rdev->max_mv = levels[count - 1U];
|
||||
} else {
|
||||
rdev->max_mv = UINT16_MAX;
|
||||
}
|
||||
|
||||
return parse_dt(rdev, node);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2021, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <common/fdt_wrappers.h>
|
||||
#include <drivers/st/regulator.h>
|
||||
#include <drivers/st/regulator_fixed.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#ifndef PLAT_NB_FIXED_REGS
|
||||
#error "Missing PLAT_NB_FIXED_REGS"
|
||||
#endif
|
||||
|
||||
#define FIXED_NAME_LEN 32
|
||||
|
||||
struct fixed_data {
|
||||
char name[FIXED_NAME_LEN];
|
||||
uint16_t volt;
|
||||
struct regul_description desc;
|
||||
};
|
||||
|
||||
static struct fixed_data data[PLAT_NB_FIXED_REGS];
|
||||
|
||||
static int fixed_set_state(const struct regul_description *desc, bool state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fixed_get_state(const struct regul_description *desc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct regul_ops fixed_ops = {
|
||||
.set_state = fixed_set_state,
|
||||
.get_state = fixed_get_state,
|
||||
};
|
||||
|
||||
int fixed_regulator_register(void)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
void *fdt;
|
||||
int node;
|
||||
|
||||
VERBOSE("fixed reg init!\n");
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
fdt_for_each_compatible_node(fdt, node, "regulator-fixed") {
|
||||
int len __unused;
|
||||
int ret;
|
||||
struct fixed_data *d = &data[count];
|
||||
const char *reg_name;
|
||||
|
||||
reg_name = fdt_get_name(fdt, node, NULL);
|
||||
|
||||
VERBOSE("register fixed reg %s!\n", reg_name);
|
||||
|
||||
len = snprintf(d->name, FIXED_NAME_LEN - 1, "%s", reg_name);
|
||||
assert((len > 0) && (len < (FIXED_NAME_LEN - 1)));
|
||||
|
||||
d->desc.node_name = d->name;
|
||||
d->desc.driver_data = d;
|
||||
d->desc.ops = &fixed_ops;
|
||||
|
||||
ret = regulator_register(&d->desc, node);
|
||||
if (ret != 0) {
|
||||
WARN("%s:%d failed to register %s\n", __func__,
|
||||
__LINE__, reg_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
count++;
|
||||
assert(count <= PLAT_NB_FIXED_REGS);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
#include <common/bl_common.h>
|
||||
#include <common/debug.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils_def.h>
|
||||
|
||||
static uint32_t id2reg_offset(unsigned int reset_id)
|
||||
{
|
||||
return ((reset_id & GENMASK(31, 5)) >> 5) * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
static uint8_t id2reg_bit_pos(unsigned int reset_id)
|
||||
{
|
||||
return (uint8_t)(reset_id & GENMASK(4, 0));
|
||||
}
|
||||
|
||||
int stm32mp_reset_assert(uint32_t id, unsigned int to_us)
|
||||
{
|
||||
uint32_t offset = id2reg_offset(id);
|
||||
uint32_t bitmsk = BIT(id2reg_bit_pos(id));
|
||||
uintptr_t rcc_base = stm32mp_rcc_base();
|
||||
|
||||
mmio_write_32(rcc_base + offset, bitmsk);
|
||||
|
||||
if (to_us != 0U) {
|
||||
uint64_t timeout_ref = timeout_init_us(to_us);
|
||||
|
||||
while ((mmio_read_32(rcc_base + offset) & bitmsk) == 0U) {
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stm32mp_reset_deassert(uint32_t id, unsigned int to_us)
|
||||
{
|
||||
uint32_t offset = id2reg_offset(id) + RCC_RSTCLRR_OFFSET;
|
||||
uint32_t bitmsk = BIT(id2reg_bit_pos(id));
|
||||
uintptr_t rcc_base = stm32mp_rcc_base();
|
||||
|
||||
mmio_write_32(rcc_base + offset, bitmsk);
|
||||
|
||||
if (to_us != 0U) {
|
||||
uint64_t timeout_ref = timeout_init_us(to_us);
|
||||
|
||||
while ((mmio_read_32(rcc_base + offset) & bitmsk) != 0U) {
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <common/fdt_wrappers.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/spi_mem.h>
|
||||
#include <drivers/st/stm32_gpio.h>
|
||||
#include <drivers/st/stm32_qspi.h>
|
||||
#include <drivers/st/stm32mp_reset.h>
|
||||
#include <lib/mmio.h>
|
||||
#include <lib/utils_def.h>
|
||||
#include <libfdt.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
/* Timeout for device interface reset */
|
||||
#define TIMEOUT_US_1_MS 1000U
|
||||
|
||||
/* QUADSPI registers */
|
||||
#define QSPI_CR 0x00U
|
||||
#define QSPI_DCR 0x04U
|
||||
#define QSPI_SR 0x08U
|
||||
#define QSPI_FCR 0x0CU
|
||||
#define QSPI_DLR 0x10U
|
||||
#define QSPI_CCR 0x14U
|
||||
#define QSPI_AR 0x18U
|
||||
#define QSPI_ABR 0x1CU
|
||||
#define QSPI_DR 0x20U
|
||||
#define QSPI_PSMKR 0x24U
|
||||
#define QSPI_PSMAR 0x28U
|
||||
#define QSPI_PIR 0x2CU
|
||||
#define QSPI_LPTR 0x30U
|
||||
|
||||
/* QUADSPI control register */
|
||||
#define QSPI_CR_EN BIT(0)
|
||||
#define QSPI_CR_ABORT BIT(1)
|
||||
#define QSPI_CR_DMAEN BIT(2)
|
||||
#define QSPI_CR_TCEN BIT(3)
|
||||
#define QSPI_CR_SSHIFT BIT(4)
|
||||
#define QSPI_CR_DFM BIT(6)
|
||||
#define QSPI_CR_FSEL BIT(7)
|
||||
#define QSPI_CR_FTHRES_SHIFT 8U
|
||||
#define QSPI_CR_TEIE BIT(16)
|
||||
#define QSPI_CR_TCIE BIT(17)
|
||||
#define QSPI_CR_FTIE BIT(18)
|
||||
#define QSPI_CR_SMIE BIT(19)
|
||||
#define QSPI_CR_TOIE BIT(20)
|
||||
#define QSPI_CR_APMS BIT(22)
|
||||
#define QSPI_CR_PMM BIT(23)
|
||||
#define QSPI_CR_PRESCALER_MASK GENMASK_32(31, 24)
|
||||
#define QSPI_CR_PRESCALER_SHIFT 24U
|
||||
|
||||
/* QUADSPI device configuration register */
|
||||
#define QSPI_DCR_CKMODE BIT(0)
|
||||
#define QSPI_DCR_CSHT_MASK GENMASK_32(10, 8)
|
||||
#define QSPI_DCR_CSHT_SHIFT 8U
|
||||
#define QSPI_DCR_FSIZE_MASK GENMASK_32(20, 16)
|
||||
#define QSPI_DCR_FSIZE_SHIFT 16U
|
||||
|
||||
/* QUADSPI status register */
|
||||
#define QSPI_SR_TEF BIT(0)
|
||||
#define QSPI_SR_TCF BIT(1)
|
||||
#define QSPI_SR_FTF BIT(2)
|
||||
#define QSPI_SR_SMF BIT(3)
|
||||
#define QSPI_SR_TOF BIT(4)
|
||||
#define QSPI_SR_BUSY BIT(5)
|
||||
|
||||
/* QUADSPI flag clear register */
|
||||
#define QSPI_FCR_CTEF BIT(0)
|
||||
#define QSPI_FCR_CTCF BIT(1)
|
||||
#define QSPI_FCR_CSMF BIT(3)
|
||||
#define QSPI_FCR_CTOF BIT(4)
|
||||
|
||||
/* QUADSPI communication configuration register */
|
||||
#define QSPI_CCR_DDRM BIT(31)
|
||||
#define QSPI_CCR_DHHC BIT(30)
|
||||
#define QSPI_CCR_SIOO BIT(28)
|
||||
#define QSPI_CCR_FMODE_SHIFT 26U
|
||||
#define QSPI_CCR_DMODE_SHIFT 24U
|
||||
#define QSPI_CCR_DCYC_SHIFT 18U
|
||||
#define QSPI_CCR_ABSIZE_SHIFT 16U
|
||||
#define QSPI_CCR_ABMODE_SHIFT 14U
|
||||
#define QSPI_CCR_ADSIZE_SHIFT 12U
|
||||
#define QSPI_CCR_ADMODE_SHIFT 10U
|
||||
#define QSPI_CCR_IMODE_SHIFT 8U
|
||||
#define QSPI_CCR_IND_WRITE 0U
|
||||
#define QSPI_CCR_IND_READ 1U
|
||||
#define QSPI_CCR_MEM_MAP 3U
|
||||
|
||||
#define QSPI_MAX_CHIP 2U
|
||||
|
||||
#define QSPI_FIFO_TIMEOUT_US 30U
|
||||
#define QSPI_CMD_TIMEOUT_US 1000U
|
||||
#define QSPI_BUSY_TIMEOUT_US 100U
|
||||
#define QSPI_ABT_TIMEOUT_US 100U
|
||||
|
||||
#define DT_QSPI_COMPAT "st,stm32f469-qspi"
|
||||
|
||||
#define FREQ_100MHZ 100000000U
|
||||
|
||||
struct stm32_qspi_ctrl {
|
||||
uintptr_t reg_base;
|
||||
uintptr_t mm_base;
|
||||
size_t mm_size;
|
||||
unsigned long clock_id;
|
||||
unsigned int reset_id;
|
||||
};
|
||||
|
||||
static struct stm32_qspi_ctrl stm32_qspi;
|
||||
|
||||
static uintptr_t qspi_base(void)
|
||||
{
|
||||
return stm32_qspi.reg_base;
|
||||
}
|
||||
|
||||
static int stm32_qspi_wait_for_not_busy(void)
|
||||
{
|
||||
uint64_t timeout = timeout_init_us(QSPI_BUSY_TIMEOUT_US);
|
||||
|
||||
while ((mmio_read_32(qspi_base() + QSPI_SR) & QSPI_SR_BUSY) != 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ERROR("%s: busy timeout\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_qspi_wait_cmd(const struct spi_mem_op *op)
|
||||
{
|
||||
int ret = 0;
|
||||
uint64_t timeout;
|
||||
|
||||
timeout = timeout_init_us(QSPI_CMD_TIMEOUT_US);
|
||||
while ((mmio_read_32(qspi_base() + QSPI_SR) & QSPI_SR_TCF) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ret = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
if ((mmio_read_32(qspi_base() + QSPI_SR) & QSPI_SR_TEF) != 0U) {
|
||||
ERROR("%s: transfer error\n", __func__);
|
||||
ret = -EIO;
|
||||
}
|
||||
} else {
|
||||
ERROR("%s: cmd timeout\n", __func__);
|
||||
}
|
||||
|
||||
/* Clear flags */
|
||||
mmio_write_32(qspi_base() + QSPI_FCR, QSPI_FCR_CTCF | QSPI_FCR_CTEF);
|
||||
|
||||
if (ret == 0) {
|
||||
ret = stm32_qspi_wait_for_not_busy();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void stm32_qspi_read_fifo(uint8_t *val, uintptr_t addr)
|
||||
{
|
||||
*val = mmio_read_8(addr);
|
||||
}
|
||||
|
||||
static void stm32_qspi_write_fifo(uint8_t *val, uintptr_t addr)
|
||||
{
|
||||
mmio_write_8(addr, *val);
|
||||
}
|
||||
|
||||
static int stm32_qspi_poll(const struct spi_mem_op *op)
|
||||
{
|
||||
void (*fifo)(uint8_t *val, uintptr_t addr);
|
||||
uint32_t len;
|
||||
uint8_t *buf;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
fifo = stm32_qspi_read_fifo;
|
||||
} else {
|
||||
fifo = stm32_qspi_write_fifo;
|
||||
}
|
||||
|
||||
buf = (uint8_t *)op->data.buf;
|
||||
|
||||
for (len = op->data.nbytes; len != 0U; len--) {
|
||||
uint64_t timeout = timeout_init_us(QSPI_FIFO_TIMEOUT_US);
|
||||
|
||||
while ((mmio_read_32(qspi_base() + QSPI_SR) &
|
||||
QSPI_SR_FTF) == 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ERROR("%s: fifo timeout\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
fifo(buf++, qspi_base() + QSPI_DR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_qspi_mm(const struct spi_mem_op *op)
|
||||
{
|
||||
memcpy(op->data.buf,
|
||||
(void *)(stm32_qspi.mm_base + (size_t)op->addr.val),
|
||||
op->data.nbytes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_qspi_tx(const struct spi_mem_op *op, uint8_t mode)
|
||||
{
|
||||
if (op->data.nbytes == 0U) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mode == QSPI_CCR_MEM_MAP) {
|
||||
return stm32_qspi_mm(op);
|
||||
}
|
||||
|
||||
return stm32_qspi_poll(op);
|
||||
}
|
||||
|
||||
static unsigned int stm32_qspi_get_mode(uint8_t buswidth)
|
||||
{
|
||||
if (buswidth == 4U) {
|
||||
return 3U;
|
||||
}
|
||||
|
||||
return buswidth;
|
||||
}
|
||||
|
||||
static int stm32_qspi_exec_op(const struct spi_mem_op *op)
|
||||
{
|
||||
uint64_t timeout;
|
||||
uint32_t ccr;
|
||||
size_t addr_max;
|
||||
uint8_t mode = QSPI_CCR_IND_WRITE;
|
||||
int ret;
|
||||
|
||||
VERBOSE("%s: cmd:%x mode:%d.%d.%d.%d addr:%" PRIx64 " len:%x\n",
|
||||
__func__, op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
|
||||
op->dummy.buswidth, op->data.buswidth,
|
||||
op->addr.val, op->data.nbytes);
|
||||
|
||||
addr_max = op->addr.val + op->data.nbytes + 1U;
|
||||
|
||||
if ((op->data.dir == SPI_MEM_DATA_IN) && (op->data.nbytes != 0U)) {
|
||||
if ((addr_max < stm32_qspi.mm_size) &&
|
||||
(op->addr.buswidth != 0U)) {
|
||||
mode = QSPI_CCR_MEM_MAP;
|
||||
} else {
|
||||
mode = QSPI_CCR_IND_READ;
|
||||
}
|
||||
}
|
||||
|
||||
if (op->data.nbytes != 0U) {
|
||||
mmio_write_32(qspi_base() + QSPI_DLR, op->data.nbytes - 1U);
|
||||
}
|
||||
|
||||
ccr = mode << QSPI_CCR_FMODE_SHIFT;
|
||||
ccr |= op->cmd.opcode;
|
||||
ccr |= stm32_qspi_get_mode(op->cmd.buswidth) << QSPI_CCR_IMODE_SHIFT;
|
||||
|
||||
if (op->addr.nbytes != 0U) {
|
||||
ccr |= (op->addr.nbytes - 1U) << QSPI_CCR_ADSIZE_SHIFT;
|
||||
ccr |= stm32_qspi_get_mode(op->addr.buswidth) <<
|
||||
QSPI_CCR_ADMODE_SHIFT;
|
||||
}
|
||||
|
||||
if ((op->dummy.buswidth != 0U) && (op->dummy.nbytes != 0U)) {
|
||||
ccr |= (op->dummy.nbytes * 8U / op->dummy.buswidth) <<
|
||||
QSPI_CCR_DCYC_SHIFT;
|
||||
}
|
||||
|
||||
if (op->data.nbytes != 0U) {
|
||||
ccr |= stm32_qspi_get_mode(op->data.buswidth) <<
|
||||
QSPI_CCR_DMODE_SHIFT;
|
||||
}
|
||||
|
||||
mmio_write_32(qspi_base() + QSPI_CCR, ccr);
|
||||
|
||||
if ((op->addr.nbytes != 0U) && (mode != QSPI_CCR_MEM_MAP)) {
|
||||
mmio_write_32(qspi_base() + QSPI_AR, op->addr.val);
|
||||
}
|
||||
|
||||
ret = stm32_qspi_tx(op, mode);
|
||||
|
||||
/*
|
||||
* Abort in:
|
||||
* - Error case.
|
||||
* - Memory mapped read: prefetching must be stopped if we read the last
|
||||
* byte of device (device size - fifo size). If device size is not
|
||||
* known then prefetching is always stopped.
|
||||
*/
|
||||
if ((ret != 0) || (mode == QSPI_CCR_MEM_MAP)) {
|
||||
goto abort;
|
||||
}
|
||||
|
||||
/* Wait end of TX in indirect mode */
|
||||
ret = stm32_qspi_wait_cmd(op);
|
||||
if (ret != 0) {
|
||||
goto abort;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
abort:
|
||||
mmio_setbits_32(qspi_base() + QSPI_CR, QSPI_CR_ABORT);
|
||||
|
||||
/* Wait clear of abort bit by hardware */
|
||||
timeout = timeout_init_us(QSPI_ABT_TIMEOUT_US);
|
||||
while ((mmio_read_32(qspi_base() + QSPI_CR) & QSPI_CR_ABORT) != 0U) {
|
||||
if (timeout_elapsed(timeout)) {
|
||||
ret = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mmio_write_32(qspi_base() + QSPI_FCR, QSPI_FCR_CTCF);
|
||||
|
||||
if (ret != 0) {
|
||||
ERROR("%s: exec op error\n", __func__);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_qspi_claim_bus(unsigned int cs)
|
||||
{
|
||||
uint32_t cr;
|
||||
|
||||
if (cs >= QSPI_MAX_CHIP) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Set chip select and enable the controller */
|
||||
cr = QSPI_CR_EN;
|
||||
if (cs == 1U) {
|
||||
cr |= QSPI_CR_FSEL;
|
||||
}
|
||||
|
||||
mmio_clrsetbits_32(qspi_base() + QSPI_CR, QSPI_CR_FSEL, cr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_qspi_release_bus(void)
|
||||
{
|
||||
mmio_clrbits_32(qspi_base() + QSPI_CR, QSPI_CR_EN);
|
||||
}
|
||||
|
||||
static int stm32_qspi_set_speed(unsigned int hz)
|
||||
{
|
||||
unsigned long qspi_clk = clk_get_rate(stm32_qspi.clock_id);
|
||||
uint32_t prescaler = UINT8_MAX;
|
||||
uint32_t csht;
|
||||
int ret;
|
||||
|
||||
if (qspi_clk == 0U) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hz > 0U) {
|
||||
prescaler = div_round_up(qspi_clk, hz) - 1U;
|
||||
if (prescaler > UINT8_MAX) {
|
||||
prescaler = UINT8_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
csht = div_round_up((5U * qspi_clk) / (prescaler + 1U), FREQ_100MHZ);
|
||||
csht = ((csht - 1U) << QSPI_DCR_CSHT_SHIFT) & QSPI_DCR_CSHT_MASK;
|
||||
|
||||
ret = stm32_qspi_wait_for_not_busy();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
mmio_clrsetbits_32(qspi_base() + QSPI_CR, QSPI_CR_PRESCALER_MASK,
|
||||
prescaler << QSPI_CR_PRESCALER_SHIFT);
|
||||
|
||||
mmio_clrsetbits_32(qspi_base() + QSPI_DCR, QSPI_DCR_CSHT_MASK, csht);
|
||||
|
||||
VERBOSE("%s: speed=%lu\n", __func__, qspi_clk / (prescaler + 1U));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_qspi_set_mode(unsigned int mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = stm32_qspi_wait_for_not_busy();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((mode & SPI_CS_HIGH) != 0U) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (((mode & SPI_CPHA) != 0U) && ((mode & SPI_CPOL) != 0U)) {
|
||||
mmio_setbits_32(qspi_base() + QSPI_DCR, QSPI_DCR_CKMODE);
|
||||
} else if (((mode & SPI_CPHA) == 0U) && ((mode & SPI_CPOL) == 0U)) {
|
||||
mmio_clrbits_32(qspi_base() + QSPI_DCR, QSPI_DCR_CKMODE);
|
||||
} else {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
VERBOSE("%s: mode=0x%x\n", __func__, mode);
|
||||
|
||||
if ((mode & SPI_RX_QUAD) != 0U) {
|
||||
VERBOSE("rx: quad\n");
|
||||
} else if ((mode & SPI_RX_DUAL) != 0U) {
|
||||
VERBOSE("rx: dual\n");
|
||||
} else {
|
||||
VERBOSE("rx: single\n");
|
||||
}
|
||||
|
||||
if ((mode & SPI_TX_QUAD) != 0U) {
|
||||
VERBOSE("tx: quad\n");
|
||||
} else if ((mode & SPI_TX_DUAL) != 0U) {
|
||||
VERBOSE("tx: dual\n");
|
||||
} else {
|
||||
VERBOSE("tx: single\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_bus_ops stm32_qspi_bus_ops = {
|
||||
.claim_bus = stm32_qspi_claim_bus,
|
||||
.release_bus = stm32_qspi_release_bus,
|
||||
.set_speed = stm32_qspi_set_speed,
|
||||
.set_mode = stm32_qspi_set_mode,
|
||||
.exec_op = stm32_qspi_exec_op,
|
||||
};
|
||||
|
||||
int stm32_qspi_init(void)
|
||||
{
|
||||
size_t size;
|
||||
int qspi_node;
|
||||
struct dt_node_info info;
|
||||
void *fdt = NULL;
|
||||
int ret;
|
||||
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
qspi_node = dt_get_node(&info, -1, DT_QSPI_COMPAT);
|
||||
if (qspi_node < 0) {
|
||||
ERROR("No QSPI ctrl found\n");
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if (info.status == DT_DISABLED) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
ret = fdt_get_reg_props_by_name(fdt, qspi_node, "qspi",
|
||||
&stm32_qspi.reg_base, &size);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = fdt_get_reg_props_by_name(fdt, qspi_node, "qspi_mm",
|
||||
&stm32_qspi.mm_base,
|
||||
&stm32_qspi.mm_size);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (dt_set_pinctrl_config(qspi_node) != 0) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
if ((info.clock < 0) || (info.reset < 0)) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
stm32_qspi.clock_id = (unsigned long)info.clock;
|
||||
stm32_qspi.reset_id = (unsigned int)info.reset;
|
||||
|
||||
clk_enable(stm32_qspi.clock_id);
|
||||
|
||||
ret = stm32mp_reset_assert(stm32_qspi.reset_id, TIMEOUT_US_1_MS);
|
||||
if (ret != 0) {
|
||||
panic();
|
||||
}
|
||||
ret = stm32mp_reset_deassert(stm32_qspi.reset_id, TIMEOUT_US_1_MS);
|
||||
if (ret != 0) {
|
||||
panic();
|
||||
}
|
||||
|
||||
mmio_write_32(qspi_base() + QSPI_CR, QSPI_CR_SSHIFT);
|
||||
mmio_write_32(qspi_base() + QSPI_DCR, QSPI_DCR_FSIZE_MASK);
|
||||
|
||||
return spi_mem_init_slave(fdt, qspi_node, &stm32_qspi_bus_ops);
|
||||
};
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2022, ARM Limited and Contributors. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include <asm_macros.S>
|
||||
#include <assert_macros.S>
|
||||
#include <console_macros.S>
|
||||
#include <drivers/st/stm32_console.h>
|
||||
#include <drivers/st/stm32_uart_regs.h>
|
||||
|
||||
#define USART_TIMEOUT 0x1000
|
||||
|
||||
/*
|
||||
* "core" functions are low-level implementations that don't require
|
||||
* writeable memory and are thus safe to call in BL1 crash context.
|
||||
*/
|
||||
.globl console_stm32_core_init
|
||||
.globl console_stm32_core_putc
|
||||
.globl console_stm32_core_getc
|
||||
.globl console_stm32_core_flush
|
||||
|
||||
.globl console_stm32_putc
|
||||
.globl console_stm32_flush
|
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
* int console_core_init(uintptr_t base_addr,
|
||||
* unsigned int uart_clk,
|
||||
* unsigned int baud_rate)
|
||||
*
|
||||
* Function to initialize the console without a C Runtime to print
|
||||
* debug information. This function will be accessed by console_init
|
||||
* and crash reporting.
|
||||
*
|
||||
* In: r0 - console base address
|
||||
* r1 - Uart clock in Hz
|
||||
* r2 - Baud rate
|
||||
* Out: return 1 on success else 0 on error
|
||||
* Clobber list : r1, r2, r3
|
||||
* -----------------------------------------------------------------
|
||||
*/
|
||||
func console_stm32_core_init
|
||||
/* Check the input base address */
|
||||
cmp r0, #0
|
||||
beq core_init_fail
|
||||
#if !defined(IMAGE_BL2)
|
||||
#if STM32MP_RECONFIGURE_CONSOLE
|
||||
/* UART clock rate is set to 0 in BL32, skip init in that case */
|
||||
cmp r1, #0
|
||||
beq 1f
|
||||
#else /* STM32MP_RECONFIGURE_CONSOLE */
|
||||
/* Skip UART initialization if it is already enabled */
|
||||
ldr r3, [r0, #USART_CR1]
|
||||
ands r3, r3, #USART_CR1_UE
|
||||
bne 1f
|
||||
#endif /* STM32MP_RECONFIGURE_CONSOLE */
|
||||
#endif /* IMAGE_BL2 */
|
||||
/* Check baud rate and uart clock for sanity */
|
||||
cmp r1, #0
|
||||
beq core_init_fail
|
||||
cmp r2, #0
|
||||
beq core_init_fail
|
||||
/* Disable UART */
|
||||
ldr r3, [r0, #USART_CR1]
|
||||
bic r3, r3, #USART_CR1_UE
|
||||
str r3, [r0, #USART_CR1]
|
||||
/* Configure UART */
|
||||
orr r3, r3, #(USART_CR1_TE | USART_CR1_FIFOEN)
|
||||
str r3, [r0, #USART_CR1]
|
||||
ldr r3, [r0, #USART_CR2]
|
||||
bic r3, r3, #USART_CR2_STOP
|
||||
str r3, [r0, #USART_CR2]
|
||||
/* Divisor = (Uart clock + (baudrate / 2)) / baudrate */
|
||||
lsr r3, r2, #1
|
||||
add r3, r1, r3
|
||||
udiv r3, r3, r2
|
||||
cmp r3, #16
|
||||
bhi 2f
|
||||
/* Oversampling 8 */
|
||||
/* Divisor = (2 * Uart clock + (baudrate / 2)) / baudrate */
|
||||
lsr r3, r2, #1
|
||||
add r3, r3, r1, lsl #1
|
||||
udiv r3, r3, r2
|
||||
and r1, r3, #USART_BRR_DIV_FRACTION
|
||||
lsr r1, r1, #1
|
||||
bic r3, r3, #USART_BRR_DIV_FRACTION
|
||||
orr r3, r3, r1
|
||||
ldr r1, [r0, #USART_CR1]
|
||||
orr r1, r1, #USART_CR1_OVER8
|
||||
str r1, [r0, #USART_CR1]
|
||||
2:
|
||||
str r3, [r0, #USART_BRR]
|
||||
/* Enable UART */
|
||||
ldr r3, [r0, #USART_CR1]
|
||||
orr r3, r3, #USART_CR1_UE
|
||||
str r3, [r0, #USART_CR1]
|
||||
/* Check TEACK bit */
|
||||
mov r2, #USART_TIMEOUT
|
||||
teack_loop:
|
||||
subs r2, r2, #1
|
||||
beq core_init_fail
|
||||
ldr r3, [r0, #USART_ISR]
|
||||
tst r3, #USART_ISR_TEACK
|
||||
beq teack_loop
|
||||
1:
|
||||
mov r0, #1
|
||||
bx lr
|
||||
core_init_fail:
|
||||
mov r0, #0
|
||||
bx lr
|
||||
endfunc console_stm32_core_init
|
||||
|
||||
.globl console_stm32_register
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* int console_stm32_register(uintptr_t baseaddr,
|
||||
* uint32_t clock, uint32_t baud,
|
||||
* console_t *console);
|
||||
* Function to initialize and register a new STM32
|
||||
* console. Storage passed in for the console struct
|
||||
* *must* be persistent (i.e. not from the stack).
|
||||
* In: r0 - UART register base address
|
||||
* r1 - UART clock in Hz
|
||||
* r2 - Baud rate
|
||||
* r3 - pointer to empty console_t struct
|
||||
* Out: return 1 on success, 0 on error
|
||||
* Clobber list : r0, r1, r2
|
||||
* -------------------------------------------------------
|
||||
*/
|
||||
func console_stm32_register
|
||||
push {r4, lr}
|
||||
mov r4, r3
|
||||
cmp r4, #0
|
||||
beq register_fail
|
||||
str r0, [r4, #CONSOLE_T_BASE]
|
||||
|
||||
bl console_stm32_core_init
|
||||
cmp r0, #0
|
||||
beq register_fail
|
||||
|
||||
mov r0, r4
|
||||
pop {r4, lr}
|
||||
finish_console_register stm32 putc=1, getc=0, flush=1
|
||||
|
||||
register_fail:
|
||||
pop {r4, pc}
|
||||
endfunc console_stm32_register
|
||||
|
||||
/* ---------------------------------------------------------------
|
||||
* int console_core_putc(int c, uintptr_t base_addr)
|
||||
*
|
||||
* Function to output a character over the console. It returns the
|
||||
* character printed on success or -1 on error.
|
||||
*
|
||||
* In : r0 - character to be printed
|
||||
* r1 - console base address
|
||||
* Out : return -1 on error else return character.
|
||||
* Clobber list : r2
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
func console_stm32_core_putc
|
||||
/* Check the input parameter */
|
||||
cmp r1, #0
|
||||
beq putc_error
|
||||
|
||||
/* Check Transmit Data Register Empty */
|
||||
txe_loop:
|
||||
ldr r2, [r1, #USART_ISR]
|
||||
tst r2, #USART_ISR_TXE
|
||||
beq txe_loop
|
||||
str r0, [r1, #USART_TDR]
|
||||
/* Check transmit complete flag */
|
||||
tc_loop:
|
||||
ldr r2, [r1, #USART_ISR]
|
||||
tst r2, #USART_ISR_TC
|
||||
beq tc_loop
|
||||
bx lr
|
||||
putc_error:
|
||||
mov r0, #-1
|
||||
bx lr
|
||||
endfunc console_stm32_core_putc
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
* int console_stm32_putc(int c, console_t *console)
|
||||
* Function to output a character over the console. It
|
||||
* returns the character printed on success or -1 on error.
|
||||
* In: r0 - character to be printed
|
||||
* r1 - pointer to console_t structure
|
||||
* Out : return -1 on error else return character.
|
||||
* Clobber list: r2
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
func console_stm32_putc
|
||||
#if ENABLE_ASSERTIONS
|
||||
cmp r1, #0
|
||||
ASM_ASSERT(ne)
|
||||
#endif /* ENABLE_ASSERTIONS */
|
||||
ldr r1, [r1, #CONSOLE_T_BASE]
|
||||
b console_stm32_core_putc
|
||||
endfunc console_stm32_putc
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
* int console_core_getc(uintptr_t base_addr)
|
||||
*
|
||||
* Function to get a character from the console.
|
||||
* It returns the character grabbed on success or -1 on error.
|
||||
*
|
||||
* In : r0 - console base address
|
||||
* Out : return -1.
|
||||
* Clobber list : r0, r1
|
||||
* -----------------------------------------------------------
|
||||
*/
|
||||
func console_stm32_core_getc
|
||||
/* Not supported */
|
||||
mov r0, #-1
|
||||
bx lr
|
||||
endfunc console_stm32_core_getc
|
||||
|
||||
/* ---------------------------------------------------------------
|
||||
* void console_core_flush(uintptr_t base_addr)
|
||||
*
|
||||
* Function to force a write of all buffered data that hasn't been
|
||||
* output.
|
||||
*
|
||||
* In : r0 - console base address
|
||||
* Out : void.
|
||||
* Clobber list : r0, r1
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
func console_stm32_core_flush
|
||||
#if ENABLE_ASSERTIONS
|
||||
cmp r0, #0
|
||||
ASM_ASSERT(ne)
|
||||
#endif /* ENABLE_ASSERTIONS */
|
||||
/* Check Transmit Data Register Empty */
|
||||
txe_loop_3:
|
||||
ldr r1, [r0, #USART_ISR]
|
||||
tst r1, #USART_ISR_TXE
|
||||
beq txe_loop_3
|
||||
bx lr
|
||||
endfunc console_stm32_core_flush
|
||||
|
||||
/* ------------------------------------------------------
|
||||
* void console_stm32_flush(console_t *console)
|
||||
* Function to force a write of all buffered
|
||||
* data that hasn't been output.
|
||||
* In : r0 - pointer to console_t structure
|
||||
* Out : void.
|
||||
* Clobber list: r0, r1
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
func console_stm32_flush
|
||||
#if ENABLE_ASSERTIONS
|
||||
cmp r0, #0
|
||||
ASM_ASSERT(ne)
|
||||
#endif /* ENABLE_ASSERTIONS */
|
||||
ldr r0, [r0, #CONSOLE_T_BASE]
|
||||
b console_stm32_core_flush
|
||||
endfunc console_stm32_flush
|
||||
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2022, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <common/bl_common.h>
|
||||
#include <drivers/clk.h>
|
||||
#include <drivers/delay_timer.h>
|
||||
#include <drivers/st/stm32_gpio.h>
|
||||
#include <drivers/st/stm32_uart.h>
|
||||
#include <drivers/st/stm32_uart_regs.h>
|
||||
#include <drivers/st/stm32mp_clkfunc.h>
|
||||
#include <lib/mmio.h>
|
||||
|
||||
#include <platform_def.h>
|
||||
|
||||
/* UART time-out value */
|
||||
#define STM32_UART_TIMEOUT_US 20000U
|
||||
|
||||
/* Mask to clear ALL the configuration registers */
|
||||
|
||||
#define STM32_UART_CR1_FIELDS \
|
||||
(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | \
|
||||
USART_CR1_RE | USART_CR1_OVER8 | USART_CR1_FIFOEN)
|
||||
|
||||
#define STM32_UART_CR2_FIELDS \
|
||||
(USART_CR2_SLVEN | USART_CR2_DIS_NSS | USART_CR2_ADDM7 | \
|
||||
USART_CR2_LBDL | USART_CR2_LBDIE | USART_CR2_LBCL | \
|
||||
USART_CR2_CPHA | USART_CR2_CPOL | USART_CR2_CLKEN | \
|
||||
USART_CR2_STOP | USART_CR2_LINEN | USART_CR2_SWAP | \
|
||||
USART_CR2_RXINV | USART_CR2_TXINV | USART_CR2_DATAINV | \
|
||||
USART_CR2_MSBFIRST | USART_CR2_ABREN | USART_CR2_ABRMODE | \
|
||||
USART_CR2_RTOEN | USART_CR2_ADD)
|
||||
|
||||
#define STM32_UART_CR3_FIELDS \
|
||||
(USART_CR3_EIE | USART_CR3_IREN | USART_CR3_IRLP | \
|
||||
USART_CR3_HDSEL | USART_CR3_NACK | USART_CR3_SCEN | \
|
||||
USART_CR3_DMAR | USART_CR3_DMAT | USART_CR3_RTSE | \
|
||||
USART_CR3_CTSE | USART_CR3_CTSIE | USART_CR3_ONEBIT | \
|
||||
USART_CR3_OVRDIS | USART_CR3_DDRE | USART_CR3_DEM | \
|
||||
USART_CR3_DEP | USART_CR3_SCARCNT | USART_CR3_WUS | \
|
||||
USART_CR3_WUFIE | USART_CR3_TXFTIE | USART_CR3_TCBGTIE | \
|
||||
USART_CR3_RXFTCFG | USART_CR3_RXFTIE | USART_CR3_TXFTCFG)
|
||||
|
||||
#define STM32_UART_ISR_ERRORS \
|
||||
(USART_ISR_ORE | USART_ISR_NE | USART_ISR_FE | USART_ISR_PE)
|
||||
|
||||
static const uint16_t presc_table[STM32_UART_PRESCALER_NB] = {
|
||||
1U, 2U, 4U, 6U, 8U, 10U, 12U, 16U, 32U, 64U, 128U, 256U
|
||||
};
|
||||
|
||||
/* @brief BRR division operation to set BRR register in 8-bit oversampling
|
||||
* mode.
|
||||
* @param clockfreq: UART clock.
|
||||
* @param baud_rate: Baud rate set by the user.
|
||||
* @param prescaler: UART prescaler value.
|
||||
* @retval Division result.
|
||||
*/
|
||||
static uint32_t uart_div_sampling8(unsigned long clockfreq,
|
||||
uint32_t baud_rate,
|
||||
uint32_t prescaler)
|
||||
{
|
||||
uint32_t scaled_freq = clockfreq / presc_table[prescaler];
|
||||
|
||||
return ((scaled_freq * 2) + (baud_rate / 2)) / baud_rate;
|
||||
|
||||
}
|
||||
|
||||
/* @brief BRR division operation to set BRR register in 16-bit oversampling
|
||||
* mode.
|
||||
* @param clockfreq: UART clock.
|
||||
* @param baud_rate: Baud rate set by the user.
|
||||
* @param prescaler: UART prescaler value.
|
||||
* @retval Division result.
|
||||
*/
|
||||
static uint32_t uart_div_sampling16(unsigned long clockfreq,
|
||||
uint32_t baud_rate,
|
||||
uint32_t prescaler)
|
||||
{
|
||||
uint32_t scaled_freq = clockfreq / presc_table[prescaler];
|
||||
|
||||
return (scaled_freq + (baud_rate / 2)) / baud_rate;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Return the UART clock frequency.
|
||||
* @param huart: UART handle.
|
||||
* @retval Frequency value in Hz.
|
||||
*/
|
||||
static unsigned long uart_get_clock_freq(struct stm32_uart_handle_s *huart)
|
||||
{
|
||||
return fdt_get_uart_clock_freq((uintptr_t)huart->base);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Configure the UART peripheral.
|
||||
* @param huart: UART handle.
|
||||
* @retval UART status.
|
||||
*/
|
||||
static int uart_set_config(struct stm32_uart_handle_s *huart,
|
||||
const struct stm32_uart_init_s *init)
|
||||
{
|
||||
uint32_t tmpreg;
|
||||
unsigned long clockfreq;
|
||||
unsigned long int_div;
|
||||
uint32_t brrtemp;
|
||||
uint32_t over_sampling;
|
||||
|
||||
/*---------------------- USART BRR configuration --------------------*/
|
||||
clockfreq = uart_get_clock_freq(huart);
|
||||
if (clockfreq == 0UL) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int_div = clockfreq / init->baud_rate;
|
||||
if (int_div < 16U) {
|
||||
uint32_t usartdiv = uart_div_sampling8(clockfreq,
|
||||
init->baud_rate,
|
||||
init->prescaler);
|
||||
|
||||
brrtemp = (usartdiv & USART_BRR_DIV_MANTISSA) |
|
||||
((usartdiv & USART_BRR_DIV_FRACTION) >> 1);
|
||||
over_sampling = USART_CR1_OVER8;
|
||||
} else {
|
||||
brrtemp = uart_div_sampling16(clockfreq,
|
||||
init->baud_rate,
|
||||
init->prescaler) &
|
||||
(USART_BRR_DIV_FRACTION | USART_BRR_DIV_MANTISSA);
|
||||
over_sampling = 0x0U;
|
||||
}
|
||||
mmio_write_32(huart->base + USART_BRR, brrtemp);
|
||||
|
||||
/*
|
||||
* ---------------------- USART CR1 Configuration --------------------
|
||||
* Clear M, PCE, PS, TE, RE and OVER8 bits and configure
|
||||
* the UART word length, parity, mode and oversampling:
|
||||
* - set the M bits according to init->word_length value,
|
||||
* - set PCE and PS bits according to init->parity value,
|
||||
* - set TE and RE bits according to init->mode value,
|
||||
* - set OVER8 bit according baudrate and clock.
|
||||
*/
|
||||
tmpreg = init->word_length |
|
||||
init->parity |
|
||||
init->mode |
|
||||
over_sampling |
|
||||
init->fifo_mode;
|
||||
mmio_clrsetbits_32(huart->base + USART_CR1, STM32_UART_CR1_FIELDS, tmpreg);
|
||||
|
||||
/*
|
||||
* --------------------- USART CR2 Configuration ---------------------
|
||||
* Configure the UART Stop Bits: Set STOP[13:12] bits according
|
||||
* to init->stop_bits value.
|
||||
*/
|
||||
mmio_clrsetbits_32(huart->base + USART_CR2, STM32_UART_CR2_FIELDS,
|
||||
init->stop_bits);
|
||||
|
||||
/*
|
||||
* --------------------- USART CR3 Configuration ---------------------
|
||||
* Configure:
|
||||
* - UART HardWare Flow Control: set CTSE and RTSE bits according
|
||||
* to init->hw_flow_control value,
|
||||
* - one-bit sampling method versus three samples' majority rule
|
||||
* according to init->one_bit_sampling (not applicable to
|
||||
* LPUART),
|
||||
* - set TXFTCFG bit according to init->tx_fifo_threshold value,
|
||||
* - set RXFTCFG bit according to init->rx_fifo_threshold value.
|
||||
*/
|
||||
tmpreg = init->hw_flow_control | init->one_bit_sampling;
|
||||
|
||||
if (init->fifo_mode == USART_CR1_FIFOEN) {
|
||||
tmpreg |= init->tx_fifo_threshold |
|
||||
init->rx_fifo_threshold;
|
||||
}
|
||||
|
||||
mmio_clrsetbits_32(huart->base + USART_CR3, STM32_UART_CR3_FIELDS, tmpreg);
|
||||
|
||||
/*
|
||||
* --------------------- USART PRESC Configuration -------------------
|
||||
* Configure UART Clock Prescaler : set PRESCALER according to
|
||||
* init->prescaler value.
|
||||
*/
|
||||
assert(init->prescaler < STM32_UART_PRESCALER_NB);
|
||||
mmio_clrsetbits_32(huart->base + USART_PRESC, USART_PRESC_PRESCALER,
|
||||
init->prescaler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Handle UART communication timeout.
|
||||
* @param huart: UART handle.
|
||||
* @param flag: Specifies the UART flag to check.
|
||||
* @retval UART status.
|
||||
*/
|
||||
static int stm32_uart_wait_flag(struct stm32_uart_handle_s *huart, uint32_t flag)
|
||||
{
|
||||
uint64_t timeout_ref = timeout_init_us(STM32_UART_TIMEOUT_US);
|
||||
|
||||
while ((mmio_read_32(huart->base + USART_ISR) & flag) == 0U) {
|
||||
if (timeout_elapsed(timeout_ref)) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Check the UART idle State.
|
||||
* @param huart: UART handle.
|
||||
* @retval UART status.
|
||||
*/
|
||||
static int stm32_uart_check_idle(struct stm32_uart_handle_s *huart)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Check if the transmitter is enabled */
|
||||
if ((mmio_read_32(huart->base + USART_CR1) & USART_CR1_TE) == USART_CR1_TE) {
|
||||
ret = stm32_uart_wait_flag(huart, USART_ISR_TEACK);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the receiver is enabled */
|
||||
if ((mmio_read_32(huart->base + USART_CR1) & USART_CR1_RE) == USART_CR1_RE) {
|
||||
ret = stm32_uart_wait_flag(huart, USART_ISR_REACK);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Compute RDR register mask depending on word length.
|
||||
* @param huart: UART handle.
|
||||
* @retval Mask value.
|
||||
*/
|
||||
static unsigned int stm32_uart_rdr_mask(const struct stm32_uart_init_s *init)
|
||||
{
|
||||
unsigned int mask = 0U;
|
||||
|
||||
switch (init->word_length) {
|
||||
case STM32_UART_WORDLENGTH_9B:
|
||||
mask = GENMASK(8, 0);
|
||||
break;
|
||||
case STM32_UART_WORDLENGTH_8B:
|
||||
mask = GENMASK(7, 0);
|
||||
break;
|
||||
case STM32_UART_WORDLENGTH_7B:
|
||||
mask = GENMASK(6, 0);
|
||||
break;
|
||||
default:
|
||||
break; /* not reached */
|
||||
}
|
||||
|
||||
if (init->parity != STM32_UART_PARITY_NONE) {
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Check interrupt and status errors.
|
||||
* @retval True if error detected, false otherwise.
|
||||
*/
|
||||
static bool stm32_uart_error_detected(struct stm32_uart_handle_s *huart)
|
||||
{
|
||||
return (mmio_read_32(huart->base + USART_ISR) & STM32_UART_ISR_ERRORS) != 0U;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Clear status errors.
|
||||
*/
|
||||
static void stm32_uart_error_clear(struct stm32_uart_handle_s *huart)
|
||||
{
|
||||
mmio_write_32(huart->base + USART_ICR, STM32_UART_ISR_ERRORS);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Stop the UART.
|
||||
* @param base: UART base address.
|
||||
*/
|
||||
void stm32_uart_stop(uintptr_t base)
|
||||
{
|
||||
mmio_clrbits_32(base + USART_CR1, USART_CR1_UE);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Initialize UART.
|
||||
* @param huart: UART handle.
|
||||
* @param base_addr: base address of UART.
|
||||
* @param init: UART initialization parameter.
|
||||
* @retval UART status.
|
||||
*/
|
||||
int stm32_uart_init(struct stm32_uart_handle_s *huart,
|
||||
uintptr_t base_addr,
|
||||
const struct stm32_uart_init_s *init)
|
||||
{
|
||||
int ret;
|
||||
int uart_node;
|
||||
int clk;
|
||||
void *fdt = NULL;
|
||||
|
||||
if (huart == NULL || init == NULL || base_addr == 0U) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
huart->base = base_addr;
|
||||
|
||||
/* Search UART instance in DT */
|
||||
if (fdt_get_address(&fdt) == 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
if (fdt == NULL) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
uart_node = dt_match_instance_by_compatible(DT_UART_COMPAT, base_addr);
|
||||
if (uart_node == -FDT_ERR_NOTFOUND) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
/* Pinctrl initialization */
|
||||
if (dt_set_pinctrl_config(uart_node) != 0) {
|
||||
return -FDT_ERR_BADVALUE;
|
||||
}
|
||||
|
||||
/* Clock initialization */
|
||||
clk = fdt_get_clock_id(uart_node);
|
||||
if (clk < 0) {
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
clk_enable(clk);
|
||||
|
||||
/* Disable the peripheral */
|
||||
stm32_uart_stop(huart->base);
|
||||
|
||||
/* Computation of UART mask to apply to RDR register */
|
||||
huart->rdr_mask = stm32_uart_rdr_mask(init);
|
||||
|
||||
/* Init the peripheral */
|
||||
ret = uart_set_config(huart, init);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable the peripheral */
|
||||
mmio_setbits_32(huart->base + USART_CR1, USART_CR1_UE);
|
||||
|
||||
/* TEACK and/or REACK to check */
|
||||
return stm32_uart_check_idle(huart);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Transmit one data in no blocking mode.
|
||||
* @param huart: UART handle.
|
||||
* @param c: data to sent.
|
||||
* @retval UART status.
|
||||
*/
|
||||
int stm32_uart_putc(struct stm32_uart_handle_s *huart, int c)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (huart == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = stm32_uart_wait_flag(huart, USART_ISR_TXE);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
mmio_write_32(huart->base + USART_TDR, c);
|
||||
if (stm32_uart_error_detected(huart)) {
|
||||
stm32_uart_error_clear(huart);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Flush TX Transmit fifo
|
||||
* @param huart: UART handle.
|
||||
* @retval UART status.
|
||||
*/
|
||||
int stm32_uart_flush(struct stm32_uart_handle_s *huart)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (huart == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = stm32_uart_wait_flag(huart, USART_ISR_TXE);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return stm32_uart_wait_flag(huart, USART_ISR_TC);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Receive a data in no blocking mode.
|
||||
* @retval value if >0 or UART status.
|
||||
*/
|
||||
int stm32_uart_getc(struct stm32_uart_handle_s *huart)
|
||||
{
|
||||
uint32_t data;
|
||||
|
||||
if (huart == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check if data is available */
|
||||
if ((mmio_read_32(huart->base + USART_ISR) & USART_ISR_RXNE) == 0U) {
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
data = mmio_read_32(huart->base + USART_RDR) & huart->rdr_mask;
|
||||
|
||||
if (stm32_uart_error_detected(huart)) {
|
||||
stm32_uart_error_clear(huart);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return (int)data;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user