/*
 * Copyright (c) 2023 Renesas Electronics Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/crypto/crypto.h>
#include <zephyr/irq.h>
#include <DA1469xAB.h>
#include <da1469x_config.h>
#include <da1469x_otp.h>
#include <system_DA1469x.h>
#include <da1469x_pd.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/policy.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(crypto_smartbond_crypto, CONFIG_CRYPTO_LOG_LEVEL);

#define DT_DRV_COMPAT renesas_smartbond_crypto

#define SMARTBOND_IRQN      DT_INST_IRQN(0)
#define SMARTBOND_IRQ_PRIO  DT_INST_IRQ(0, priority)

#if defined(CONFIG_CRYPTO_ASYNC)
#define CRYPTO_HW_CAPS  (CAP_RAW_KEY | CAP_SEPARATE_IO_BUFS | CAP_ASYNC_OPS | CAP_NO_IV_PREFIX)
#else
#define CRYPTO_HW_CAPS  (CAP_RAW_KEY | CAP_SEPARATE_IO_BUFS | CAP_SYNC_OPS | CAP_NO_IV_PREFIX)
#endif

#define SWAP32(_w)   __REV(_w)

#define CRYPTO_CTRL_REG_SET(_field, _val) \
	AES_HASH->CRYPTO_CTRL_REG = \
	(AES_HASH->CRYPTO_CTRL_REG & ~AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Msk) | \
	((_val) << AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Pos)

#define CRYPTO_CTRL_REG_GET(_field) \
	((AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Msk) >> \
	AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Pos)


struct crypto_smartbond_data {
    /*
     * Semaphore to provide mutual exlusion when a crypto session is requested.
     */
	struct k_sem session_sem;

    /*
     * Semaphore to provide mutual exlusion when a cryptographic task is requested.
     * (a session should be requested at this point).
     */
	struct k_sem device_sem;
#if defined(CONFIG_CRYPTO_ASYNC)
    /*
     * User-defined callbacks to be called upon completion of asynchronous
     * cryptographic operations. Note that the AES and HASH modes can work
     * complementary to each other.
     */
	union {
		cipher_completion_cb cipher_user_cb;
		hash_completion_cb hash_user_cb;
	};

    /*
     * Packet context should be stored during a session so that can be rertieved
     * from within the crypto engine ISR context.
     */
	union {
		struct cipher_pkt *cipher_pkt;
		struct hash_pkt *hash_pkt;
	};
#else
    /*
     * Semaphore used to block for as long as a synchronous cryptographic operation
     * is in progress.
     */
	struct k_sem sync_sem;
#endif
};

/*
 * Status flag to indicate if the crypto engine resources have been granted. Note that the
 * device integrates a single crypto engine instance.
 */
static bool in_use;

static void crypto_smartbond_set_status(bool enable);

static void smartbond_crypto_isr(const void *arg)
{
	struct crypto_smartbond_data *data = ((const struct device *)arg)->data;
	uint32_t status = AES_HASH->CRYPTO_STATUS_REG;

	if (status & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_IRQ_ST_Msk) {
	    /* Clear interrupt source. Otherwise the handler will be fire constantly! */
		AES_HASH->CRYPTO_CLRIRQ_REG = 0x1;

#if defined(CONFIG_CRYPTO_ASYNC)
		/* Define the slected crypto mode (AES/HASH). */
		if (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk) {
			if (data->hash_user_cb) {
				data->hash_user_cb(data->hash_pkt, status);
			}
		} else {
			if (data->cipher_user_cb) {
				data->cipher_user_cb(data->cipher_pkt, status);
			}
		}
#else
		/* Designate the requested cryptographic tasks is finished. */
		k_sem_give(&data->sync_sem);
#endif
	}
}

static inline void crypto_smartbond_pm_policy_state_lock_get(const struct device *dev)
{
	/*
	 * Prevent the SoC from entering the normal sleep state as PDC does not support
	 * waking up the application core following AES/HASH events.
	 */
	pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
}

static inline void crypto_smartbond_pm_policy_state_lock_put(const struct device *dev)
{
	/* Allow the SoC to enter the normal sleep state once AES/HASH operations are done. */
	pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
}

static bool crypto_smartbond_lock_session(const struct device *dev)
{
	bool lock = false;
	struct crypto_smartbond_data *data = dev->data;

	k_sem_take(&data->session_sem, K_FOREVER);

	if (!in_use) {
		in_use = true;
		/* Prevent sleep as long as a cryptographic session is in place */
		da1469x_pd_acquire(MCU_PD_DOMAIN_SYS);
		crypto_smartbond_pm_policy_state_lock_get(dev);
		crypto_smartbond_set_status(true);
		lock = true;
	}

	k_sem_give(&data->session_sem);

	return lock;
}

static void crypto_smartbond_unlock_session(const struct device *dev)
{
	struct crypto_smartbond_data *data = dev->data;

	k_sem_take(&data->session_sem, K_FOREVER);

	if (in_use) {
		in_use = false;
		crypto_smartbond_set_status(false);
		crypto_smartbond_pm_policy_state_lock_put(dev);
		da1469x_pd_release_nowait(MCU_PD_DOMAIN_SYS);
	}

	k_sem_give(&data->session_sem);
}

/*
 * Input vector should comply with the following restrictions:
 *
 * mode        | CRYPTO_MORE_IN = true  | CRYPTO_MORE_IN = false
 * ------------| -----------------------| ----------------------
 * ECB         | multiple of 16 (bytes) | multiple of 16 (bytes)
 * CBC         | multiple of 16         | no restrictions
 * CTR         | multiple of 16         | no restrictions
 * MD5         | multiple of 8          | no restrictions
 * SHA_1       | multiple of 8          | no restrictions
 * SHA_256_224 | multiple of 8          | no restrictions
 * SHA_256     | multiple of 8          | no restrictions
 * SHA_384     | multiple of 8          | no restrictions
 * SHA_512     | multiple of 8          | no restrictions
 * SHA_512_224 | multiple of 8          | no restrictions
 * SHA_512_256 | multiple of 8          | no restrictions
 */
static int crypto_smartbond_check_in_restrictions(uint16_t in_len)
{
#define CRYPTO_ALG_MD_ECB_MAGIC_0   0x00
#define CRYPTO_ALG_MD_ECB_MAGIC_1   0x01

	bool not_last_in_block = !!(AES_HASH->CRYPTO_CTRL_REG &
						AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk);

	/* Define the slected crypto mode (AES/HASH). */
	if (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk) {
		if (not_last_in_block && (in_len & 0x7)) {
			return -EINVAL;
		}
	} else {
		if (in_len & 0xF) {
			if (not_last_in_block) {
				return -EINVAL;
			}

			uint32_t crypto_mode = CRYPTO_CTRL_REG_GET(CRYPTO_ALG_MD);

			/* Check if AES mode is ECB */
			if (crypto_mode == CRYPTO_ALG_MD_ECB_MAGIC_0 ||
							crypto_mode == CRYPTO_ALG_MD_ECB_MAGIC_1) {
				return -EINVAL;
			}
		}
	}

	return 0;
}

/*
 * The driver model does not define the max. output length. As such, the max supported length
 * per mode is applied.
 */
static int crypto_smartbond_hash_set_out_len(void)
{
	uint32_t hash_algo = (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Msk);

	if (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk) {
	    /* 64-bit HASH operations */
		switch (hash_algo) {
		case 0x0:
			/* SHA-384: 0..47 --> 1..48 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 47);
			break;
		case 0x1:
			/* SHA-512: 0..63 --> 1..64 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 63);
			break;
		case 0x2:
			/* SHA-512/224: 0..27 --> 1..28 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 27);
			break;
		case 0x3:
			/* SHA-512/256: 0..31 --> 1..32 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 31);
			break;
		default:
			break;
		}
	} else {
	    /* 32-bit HASH operations */
		switch (hash_algo) {
		case 0x0:
			/* MD5: 0..15 --> 1..16 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 15);
			break;
		case 0x1:
			/* SHA-1: 0..19 --> 1..20 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 19);
			break;
		case 0x2:
			/* SHA-256/224: 0..27 --> 1..28 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 27);
			break;
		case 0x3:
			/* SHA-256: 0..31 --> 1..32 bytes */
			CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 31);
			break;
		default:
			break;
		}
	}

	/* Return the OUT size applied. */
	return CRYPTO_CTRL_REG_GET(CRYPTO_HASH_OUT_LEN) + 1;
}

static uint32_t crypto_smartbond_swap_word(uint8_t *data)
{
    /* Check word boundaries of given address and if possible accellerate swapping */
	if ((uint32_t)data & 0x3) {
		return SWAP32(sys_get_le32(data));
	} else {
		return SWAP32(*(uint32_t *)data);
	}
}

static int crypto_smartbond_cipher_key_load(uint8_t *key, uint16_t key_len)
{
	if (key == NULL) {
		return -EIO;
	}

	AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEY_SZ_Msk);

	if (key_len == 32) {
		AES_HASH->CRYPTO_CTRL_REG |=
		(0x2 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEY_SZ_Pos);
	} else if (key_len == 24) {
		AES_HASH->CRYPTO_CTRL_REG |=
		(0x1 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEY_SZ_Pos);
	} else if (key_len == 16) {
		/* Nothing to do */
	} else {
		return -EINVAL;
	}

	/* Key expansion is performed by the crypto engine */
	AES_HASH->CRYPTO_CTRL_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEXP_Msk;

	/* Check whether the cipher key is located in OTP (user keys segment) */
	if (IS_ADDRESS_USER_DATA_KEYS_SEGMENT((uint32_t)key)) {

		/* User keys segmnet can be accessed if not locked (stick bits are not set) */
		if (CRG_TOP->SECURE_BOOT_REG & CRG_TOP_SECURE_BOOT_REG_PROT_AES_KEY_READ_Msk) {
			return -EIO;
		}

		uint32_t cell_offset = da1469x_otp_address_to_cell_offset((uint32_t)key);

		da1469x_otp_read(cell_offset,
				(void *)&AES_HASH->CRYPTO_KEYS_START, (uint32_t)key_len);
	} else {
		volatile uint32_t *kmem_ptr = &AES_HASH->CRYPTO_KEYS_START;

		do {
			*(kmem_ptr++) = crypto_smartbond_swap_word(key);
			key += 4;
			key_len -= 4;
		} while (key_len);
	}

	return 0;
}

static int crypto_smartbond_cipher_set_mode(enum cipher_mode mode)
{
	/* Select AES mode */
	AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk  |
						AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Msk        |
						AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk);
	switch (mode) {
	case CRYPTO_CIPHER_MODE_ECB:
		/* Already done; CRYPTO_ALG_MD = 0x0 or 0x1 defines ECB. */
		break;
	case CRYPTO_CIPHER_MODE_CTR:
		AES_HASH->CRYPTO_CTRL_REG |= (0x2 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Pos);
		break;
	case CRYPTO_CIPHER_MODE_CBC:
		AES_HASH->CRYPTO_CTRL_REG |= (0x3 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Pos);
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int crypto_smartbond_hash_set_algo(enum hash_algo algo)
{
	/* Select HASH mode and reset to 32-bit mode */
	AES_HASH->CRYPTO_CTRL_REG =
			(AES_HASH->CRYPTO_CTRL_REG & ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Msk |
						AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk)) |
					AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk;

	switch (algo) {
	case CRYPTO_HASH_ALGO_SHA224:
		/* CRYPTO_ALG_MD = 0x0 defines 32-bit operations */
		AES_HASH->CRYPTO_CTRL_REG |= (0x2 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Pos);
		break;
	case CRYPTO_HASH_ALGO_SHA256:
		/* CRYPTO_ALG_MD = 0x0 defines 32-bit operations */
		AES_HASH->CRYPTO_CTRL_REG |= (0x3 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Pos);
		break;
	case CRYPTO_HASH_ALGO_SHA384:
		/* CRYPTO_ALG_MD = 0x1 defines 64-bit operations */
		AES_HASH->CRYPTO_CLRIRQ_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk;
		break;
	case CRYPTO_HASH_ALGO_SHA512:
		/* CRYPTO_ALG_MD = 0x1 defines 64-bit operations */
		AES_HASH->CRYPTO_CTRL_REG |= (AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk |
						(0x1 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Pos));
		break;
	default:
		return -ENOTSUP;
	}

	return 0;
}

static int crypto_smartbond_set_in_out_buf(const uint8_t *in_buf, uint8_t *out_buf, int len)
{
	if (in_buf == NULL) {
		return -EIO;
	}

	/*
	 * Input data can reside in any address space. Cryto DMA can only access physical addresses
	 * (not remapped).
	 */
	uint32_t phy_addr = black_orca_phy_addr((uint32_t)in_buf);

	if (IS_QSPIF_CACHED_ADDRESS(phy_addr)) {
		/*
		 * To achiebe max. perfomance, peripherals should not access the Flash memory
		 * through the instruction cache controller (avoid cache misses).
		 */
		phy_addr += (MCU_QSPIF_M_BASE - MCU_QSPIF_M_CACHED_BASE);
	} else if (IS_OTP_ADDRESS(phy_addr)) {
		/* Peripherals should access the OTP memory through its peripheral address space. */
		phy_addr += (MCU_OTP_M_P_BASE - MCU_OTP_M_BASE);
	}

	AES_HASH->CRYPTO_FETCH_ADDR_REG = phy_addr;

	/*
	 * OUT buffer can be NULL in case of fregmented data processing. CRYPTO_DEST_ADDR and
	 * CRYPTO_FETCH_ADDR are being updated as calculations prceed and OUT data are written
	 * into memory.
	 */
	if (out_buf) {
		uint32_t remap_adr0 = CRG_TOP->SYS_CTRL_REG & CRG_TOP_SYS_CTRL_REG_REMAP_ADR0_Msk;

		/*
		 * OUT data can only be written in SYSRAM, non-cached remapped SYSRAM and
		 * cached non-remapped SYSRAM.
		 */
		if (IS_SYSRAM_ADDRESS(out_buf) ||
			(IS_REMAPPED_ADDRESS(out_buf) && remap_adr0 == 3)) {
			AES_HASH->CRYPTO_DEST_ADDR_REG = black_orca_phy_addr((uint32_t)out_buf);
		} else {
			return -EIO;
		}
	}

	AES_HASH->CRYPTO_LEN_REG = len;

	return 0;
}

static inline void crypto_smartbond_cipher_store_dep_data(uint32_t *words, uint32_t len_words)
{
	volatile uint32_t *mreg3 = &AES_HASH->CRYPTO_MREG3_REG;

	for (int i = 0; i < len_words; i++) {
		*(mreg3--) = crypto_smartbond_swap_word((uint8_t *)(words++));
	}
}

static int crypto_smartbond_cipher_set_mreg(uint8_t *mreg, uint32_t len_words)
{
	if (mreg == NULL || len_words == 0 || len_words > 4) {
		return -EINVAL;
	}

	AES_HASH->CRYPTO_MREG0_REG = 0;
	AES_HASH->CRYPTO_MREG1_REG = 0;
	AES_HASH->CRYPTO_MREG2_REG = 0;
	AES_HASH->CRYPTO_MREG3_REG = 0;

	crypto_smartbond_cipher_store_dep_data((uint32_t *)mreg, len_words);

	return 0;
}

static void crypto_smartbond_set_status(bool enable)
{
	unsigned int key;

	key = irq_lock();

	if (enable) {
		CRG_TOP->CLK_AMBA_REG |= (CRG_TOP_CLK_AMBA_REG_AES_CLK_ENABLE_Msk);

		AES_HASH->CRYPTO_CLRIRQ_REG = 0x1;
		AES_HASH->CRYPTO_CTRL_REG |= (AES_HASH_CRYPTO_CTRL_REG_CRYPTO_IRQ_EN_Msk);

		irq_enable(SMARTBOND_IRQN);
	} else {
		AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_IRQ_EN_Msk);
		AES_HASH->CRYPTO_CLRIRQ_REG = 0x1;

		irq_disable(SMARTBOND_IRQN);

		CRG_TOP->CLK_AMBA_REG &= ~(CRG_TOP_CLK_AMBA_REG_AES_CLK_ENABLE_Msk);
	}

	irq_unlock(key);
}

static int crypto_smartbond_query_hw_caps(const struct device *dev)
{
	return CRYPTO_HW_CAPS;
}

static int crypto_smartbond_cipher_ecb_handler(struct cipher_ctx *ctx, struct cipher_pkt *pkt)
{
	int ret;
	struct crypto_smartbond_data *data = ctx->device->data;

	if ((AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk) == 0) {
		LOG_ERR("Crypto engine is already employed");
		return -EINVAL;
	}

	if (pkt->out_buf_max < pkt->in_len) {
		LOG_ERR("OUT buffer cannot be less that IN buffer");
		return -EINVAL;
	}

	if (pkt->in_buf == NULL || pkt->out_buf == NULL) {
		LOG_ERR("Missing IN or OUT buffer declaration");
		return -EIO;
	}

	if (pkt->in_len > 16) {
		LOG_ERR("For security reasons, do not operate on more than 16 bytes");
		return -EINVAL;
	}

	k_sem_take(&data->device_sem, K_FOREVER);

	ret = crypto_smartbond_check_in_restrictions(pkt->in_len);
	if (ret < 0) {
		LOG_ERR("Unsupported IN buffer size");
		k_sem_give(&data->device_sem);
		return ret;
	}

	ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, pkt->out_buf, pkt->in_len);
	if (ret < 0) {
		LOG_ERR("Unsupported IN or OUT buffer location");
		k_sem_give(&data->device_sem);
		return ret;
	}

#if defined(CONFIG_CRYPTO_ASYNC)
	data->cipher_pkt = pkt;
#endif

	/* Start crypto processing */
	AES_HASH->CRYPTO_START_REG = 1;

#if !defined(CONFIG_CRYPTO_ASYNC)
	/* Wait for crypto to finish its task */
	k_sem_take(&data->sync_sem, K_FOREVER);
#endif

	/* Report that number of bytes operated upon. */
	pkt->out_len = pkt->in_len;

	k_sem_give(&data->device_sem);

	return 0;
}

static int
crypto_smartbond_cipher_cbc_handler(struct cipher_ctx *ctx, struct cipher_pkt *pkt, uint8_t *iv)
{
	int ret;
	int offset = 0;
	struct crypto_smartbond_data *data = ctx->device->data;
	bool is_op_encryption =
			!!(AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ENCDEC_Msk);

	if ((AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk) == 0) {
		LOG_ERR("Crypto engine is already employed");
		return -EINVAL;
	}

	if ((is_op_encryption && pkt->out_buf_max < (pkt->in_len + 16)) ||
							pkt->out_buf_max < (pkt->in_len - 16)) {
		LOG_ERR("Invalid OUT buffer size");
		return -EINVAL;
	}

	if (pkt->in_buf == NULL || pkt->out_buf == NULL) {
		LOG_ERR("Missing IN or OUT buffer declaration");
		return -EIO;
	}

	if ((ctx->flags & CAP_NO_IV_PREFIX) == 0) {
		offset = 16;
		if (is_op_encryption) {
			/* Prefix IV to ciphertet unless CAP_NO_IV_PREFIX is set. */
			memcpy(pkt->out_buf, iv, offset);
		}
	}

	k_sem_take(&data->device_sem, K_FOREVER);

	ret = crypto_smartbond_check_in_restrictions(pkt->in_len);
	if (ret < 0) {
		LOG_ERR("Unsupported IN buffer size");
		k_sem_give(&data->device_sem);
		return ret;
	}

	ret = crypto_smartbond_cipher_set_mreg(iv, 4);
	if (ret < 0) {
		LOG_ERR("Missing Initialization Vector (IV)");
		k_sem_give(&data->device_sem);
		return ret;
	}

	if (is_op_encryption) {
		ret = crypto_smartbond_set_in_out_buf(pkt->in_buf,
						pkt->out_buf + offset, pkt->in_len);
	} else {
		ret = crypto_smartbond_set_in_out_buf(pkt->in_buf + offset,
								pkt->out_buf, pkt->in_len - offset);
	}

	if (ret < 0) {
		LOG_ERR("Unsupported IN or OUT buffer location");
		k_sem_give(&data->device_sem);
		return ret;
	}

#if defined(CONFIG_CRYPTO_ASYNC)
	data->cipher_pkt = pkt;
#endif

    /* Start crypto processing */
	AES_HASH->CRYPTO_START_REG = 1;

#if !defined(CONFIG_CRYPTO_ASYNC)
    /* Wait for crypto to finish its task */
	k_sem_take(&data->sync_sem, K_FOREVER);
#endif

	/* Report that number of bytes operated upon. */
	if (is_op_encryption) {
		pkt->out_len = pkt->in_len + offset;
	} else {
		pkt->out_len = pkt->in_len - offset;
	}

	k_sem_give(&data->device_sem);

	return 0;
}

static int crypto_smartbond_cipher_ctr_handler(struct cipher_ctx *ctx,
								struct cipher_pkt *pkt, uint8_t *ic)
{
	int ret;
	/* ivlen + ctrlen = keylen, ctrl_len is expressed in bits */
	uint32_t iv_len = ctx->keylen - (ctx->mode_params.ctr_info.ctr_len >> 3);
	struct crypto_smartbond_data *data = ctx->device->data;

	if ((AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk) == 0) {
		LOG_ERR("Crypto engine is already employed");
		return -EINVAL;
	}

	if (pkt->out_buf_max < pkt->in_len) {
		LOG_ERR("OUT buffer cannot be less that IN buffer");
		return -EINVAL;
	}

	if (pkt->in_buf == NULL || pkt->out_buf == NULL) {
		LOG_ERR("Missing IN or OUT buffer declaration");
		return -EIO;
	}

	k_sem_take(&data->device_sem, K_FOREVER);

	ret = crypto_smartbond_check_in_restrictions(pkt->in_len);
	if (ret < 0) {
		LOG_ERR("Unsupported IN buffer size");
		k_sem_give(&data->device_sem);
		return ret;
	}

	ret = crypto_smartbond_cipher_set_mreg(ic, iv_len >> 2);
	if (ret < 0) {
		LOG_ERR("Missing Initialization Counter (IC)");
		k_sem_give(&data->device_sem);
		return ret;
	}

	ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, pkt->out_buf, pkt->in_len);
	if (ret < 0) {
		LOG_ERR("Unsupported IN or OUT buffer location");
		k_sem_give(&data->device_sem);
		return ret;
	}

#if defined(CONFIG_CRYPTO_ASYNC)
	data->cipher_pkt = pkt;
#endif

    /* Start crypto processing */
	AES_HASH->CRYPTO_START_REG = 1;

#if !defined(CONFIG_CRYPTO_ASYNC)
    /* Wait for crypto to finish its task */
	k_sem_take(&data->sync_sem, K_FOREVER);
#endif

	/* Report that number of bytes operated upon. */
	pkt->out_len = pkt->in_len;

	k_sem_give(&data->device_sem);

	return 0;
}

static int crypto_smartbond_hash_handler(struct hash_ctx *ctx, struct hash_pkt *pkt, bool finish)
{
	int ret;
	struct crypto_smartbond_data *data = ctx->device->data;
	/*
	 * In case of framgemented data processing crypto status should be visible as busy for
	 * as long as the last block is to be processed.
	 */
	bool is_multipart_started =
		(AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_WAIT_FOR_IN_Msk) &&
		!(AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk);

	if (pkt->in_buf == NULL || (pkt->out_buf == NULL)) {
		LOG_ERR("Missing IN or OUT buffer declaration");
		return -EIO;
	}

	k_sem_take(&data->device_sem, K_FOREVER);

	/* Check if this is the last block to process or more blocks will follow */
	if (finish) {
		AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk);
	} else {
		AES_HASH->CRYPTO_CTRL_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk;
	}

	/* CRYPTO_MORE_IN should be updated prior to checking for IN restrictions! */
	ret = crypto_smartbond_check_in_restrictions(pkt->in_len);
	if (ret < 0) {
		LOG_ERR("Unsupported IN buffer size");
		k_sem_give(&data->device_sem);
		return ret;
	}

	if (!is_multipart_started) {
		ret = crypto_smartbond_hash_set_out_len();
		if (ret < 0) {
			LOG_ERR("Invalid OUT buffer size");
			k_sem_give(&data->device_sem);
			return ret;
		}
	}

	if (!is_multipart_started) {
		ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, pkt->out_buf, pkt->in_len);
	} else {
		/* Destination buffer is being updated as fragmented input is being processed. */
		ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, NULL, pkt->in_len);
	}

	if (ret < 0) {
		LOG_ERR("Unsupported IN or OUT buffer location");
		k_sem_give(&data->device_sem);
		return ret;
	}

#if defined(CONFIG_CRYPTO_ASYNC)
	data->hash_pkt = pkt;
#endif

    /* Start hash processing */
	AES_HASH->CRYPTO_START_REG = 1;

#if !defined(CONFIG_CRYPTO_ASYNC)
	k_sem_take(&data->sync_sem, K_FOREVER);
#endif

	k_sem_give(&data->device_sem);

	return 0;
}

static int
crypto_smartbond_cipher_begin_session(const struct device *dev, struct cipher_ctx *ctx,
		enum cipher_algo algo, enum cipher_mode mode, enum cipher_op op_type)
{
	int ret;

	if (ctx->flags & ~(CRYPTO_HW_CAPS)) {
		LOG_ERR("Unsupported flag");
		return -EINVAL;
	}

	if (algo != CRYPTO_CIPHER_ALGO_AES) {
		LOG_ERR("Unsupported cipher algo");
		return -EINVAL;
	}

	if (!crypto_smartbond_lock_session(dev)) {
		LOG_ERR("No free session for now");
		return -ENOSPC;
	}

	/* First check if the requested cryptographic algo is supported */
	ret = crypto_smartbond_cipher_set_mode(mode);
	if (ret < 0) {
		LOG_ERR("Unsupported cipher mode");
		crypto_smartbond_unlock_session(dev);
		return ret;
	}

	ret = crypto_smartbond_cipher_key_load((uint8_t *)ctx->key.bit_stream, ctx->keylen);
	if (ret < 0) {
		LOG_ERR("Invalid key length or key cannot be accessed");
		crypto_smartbond_unlock_session(dev);
		return ret;
	}

	if (op_type == CRYPTO_CIPHER_OP_ENCRYPT) {
		AES_HASH->CRYPTO_CTRL_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ENCDEC_Msk;
	} else {
		AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ENCDEC_Msk);
	}

	/* IN buffer fragmentation is not supported by the driver model */
	AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk);

	switch (mode) {
	case CRYPTO_CIPHER_MODE_ECB:
		ctx->ops.block_crypt_hndlr = crypto_smartbond_cipher_ecb_handler;
		break;
	case CRYPTO_CIPHER_MODE_CBC:
		ctx->ops.cbc_crypt_hndlr = crypto_smartbond_cipher_cbc_handler;
		break;
	case CRYPTO_CIPHER_MODE_CTR:
		ctx->ops.ctr_crypt_hndlr = crypto_smartbond_cipher_ctr_handler;
		break;
	default:
		break;
	}

	ctx->drv_sessn_state = NULL;

	return 0;
}

static int crypto_smartbond_cipher_free_session(const struct device *dev, struct cipher_ctx *ctx)
{
	ARG_UNUSED(ctx);
	crypto_smartbond_unlock_session(dev);

	return 0;
}

#if defined(CONFIG_CRYPTO_ASYNC)
static int
crypto_smartbond_cipher_set_async_callback(const struct device *dev, cipher_completion_cb cb)
{
	struct crypto_smartbond_data *data = dev->data;

	data->cipher_user_cb = cb;

	return 0;
}
#endif

static int
crypto_smartbond_hash_begin_session(const struct device *dev,
					struct hash_ctx *ctx, enum hash_algo algo)
{
	int ret;

	if (ctx->flags & ~(CRYPTO_HW_CAPS)) {
		LOG_ERR("Unsupported flag");
		return -EINVAL;
	}

	if (!crypto_smartbond_lock_session(dev)) {
		LOG_ERR("No free session for now");
		return -ENOSPC;
	}

	/*
	 * Crypto should be disabled only if not used in other sessions. In case of failure,
	 * developer should next free the current session.
	 */
	crypto_smartbond_set_status(true);

	ret = crypto_smartbond_hash_set_algo(algo);
	if (ret < 0) {
		LOG_ERR("Unsupported HASH algo");
		crypto_smartbond_unlock_session(dev);
		return ret;
	}

	ctx->hash_hndlr = crypto_smartbond_hash_handler;

	ctx->drv_sessn_state = NULL;

	return 0;
}

static int crypto_smartbond_hash_free_session(const struct device *dev, struct hash_ctx *ctx)
{
	ARG_UNUSED(ctx);
	crypto_smartbond_unlock_session(dev);

	return 0;
}

#if defined(CONFIG_CRYPTO_ASYNC)
static int
crypto_smartbond_hash_set_async_callback(const struct device *dev, hash_completion_cb cb)
{
	struct crypto_smartbond_data *data = dev->data;

	data->hash_user_cb = cb;

	return 0;
}
#endif

static DEVICE_API(crypto, crypto_smartbond_driver_api) = {
	.cipher_begin_session = crypto_smartbond_cipher_begin_session,
	.cipher_free_session = crypto_smartbond_cipher_free_session,
#if defined(CONFIG_CRYPTO_ASYNC)
	.cipher_async_callback_set = crypto_smartbond_cipher_set_async_callback,
#endif
	.hash_begin_session = crypto_smartbond_hash_begin_session,
	.hash_free_session = crypto_smartbond_hash_free_session,
#if defined(CONFIG_CRYPTO_ASYNC)
	.hash_async_callback_set = crypto_smartbond_hash_set_async_callback,
#endif
	.query_hw_caps = crypto_smartbond_query_hw_caps
};

#if defined(CONFIG_PM_DEVICE)
static int crypto_smartbond_pm_action(const struct device *dev,
	enum pm_device_action action)
{
	int ret = 0;

	switch (action) {
	case PM_DEVICE_ACTION_SUSPEND:
		/*
		 * No need to perform any actions here as the AES/HASH controller
		 * should already be turned off.
		 */
		break;
	case PM_DEVICE_ACTION_RESUME:
		/*
		 * No need to perform any actions here as the AES/HASH controller
		 * will be initialized upon acquiring a cryptographic session.
		 */
		break;
	default:
		return -ENOTSUP;
	}

	return ret;
}
#endif

static int crypto_smartbond_init(const struct device *dev)
{
	struct crypto_smartbond_data *data = dev->data;

	/* Semaphore used during sessions (begin/free) */
	k_sem_init(&data->session_sem, 1, 1);

	/* Semaphore used to employ the crypto device */
	k_sem_init(&data->device_sem, 1, 1);

#if !defined(CONFIG_CRYPTO_ASYNC)
    /* Sempahore used when sync operations are enabled */
	k_sem_init(&data->sync_sem, 0, 1);
#endif

	IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_crypto_isr,
			DEVICE_DT_INST_GET(0), 0);

	/* Controller should be initialized once a crypyographic session is requested */
	crypto_smartbond_set_status(false);

	return 0;
}

/*
 * There is only one instance integrated on the SoC. Just in case that assumption becomes invalid
 * in the future, we use a BUILD_ASSERT().
 */
#define SMARTBOND_CRYPTO_INIT(inst)                                     \
	BUILD_ASSERT((inst) == 0,                                           \
		"multiple instances are not supported");                        \
	\
	PM_DEVICE_DT_INST_DEFINE(inst, crypto_smartbond_pm_action);	\
	\
	static struct crypto_smartbond_data crypto_smartbond_data_##inst;   \
	\
	DEVICE_DT_INST_DEFINE(0,                            \
		crypto_smartbond_init,		                    \
		PM_DEVICE_DT_INST_GET(inst),					\
		&crypto_smartbond_data_##inst, NULL,            \
		POST_KERNEL,                                    \
		CONFIG_CRYPTO_INIT_PRIORITY,                    \
		&crypto_smartbond_driver_api);

DT_INST_FOREACH_STATUS_OKAY(SMARTBOND_CRYPTO_INIT)
