/*
 * Copyright (c) 2025 Medtech Sherpa LLC
 * Copyright (c) 2024 Antmicro <www.antmicro.com> (Original MB85RSXX driver)
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * CY15B108QN FRAM Driver for Zephyr RTOS
 * Based on Zephyr's MB85RSXX EEPROM driver with Infineon ID support added.
 *
 * Supports both:
 * - Fujitsu MB85RSXX series (Manufacturer ID: 0x04)
 * - Infineon CY15B108QN series (Manufacturer ID: 0x02)
 */

#define DT_DRV_COMPAT infineon_cy15b108qn

#include <zephyr/device.h>
#include <zephyr/drivers/eeprom.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(cy15b108qn, CONFIG_CY15B108QN_LOG_LEVEL);

/* FRAM SPI instruction set (common across Fujitsu/Infineon) */
#define FRAM_CMD_WREN   0x06U  /* Set Write Enable Latch */
#define FRAM_CMD_WRDI   0x04U  /* Reset Write Enable Latch */
#define FRAM_CMD_RDSR   0x05U  /* Read Status Register */
#define FRAM_CMD_WRSR   0x01U  /* Write Status Register */
#define FRAM_CMD_READ   0x03U  /* Read Memory */
#define FRAM_CMD_WRITE  0x02U  /* Write Memory */
#define FRAM_CMD_RDID   0x9F  /* Read Device ID */
#define FRAM_CMD_RUID   0x4C  /* Read Unique ID */
#define FRAM_CMD_FSTRD  0x0BU  /* Fast Read Memory */
#define FRAM_CMD_SLEEP  0xB9U  /* Sleep Mode */

/* Manufacturer IDs */
#define FRAM_MFR_FUJITSU    0x04U
#define FRAM_MFR_INFINEON   0x03U  /* Cypress/Infineon CY15B series */

/* Fujitsu MB85RSXX expected ID pattern */
#define FUJITSU_CON_CODE    0x7FU
#define FUJITSU_PROD_ID     0x20U
#define FUJITSU_PROD_MASK   0xE0U  /* GENMASK(7, 5) */
#define FUJITSU_PROD_ID2    0x03U

struct cy15b108qn_config {
    struct spi_dt_spec spi;
    size_t size;
    bool readonly;
};

struct cy15b108qn_data {
    struct k_mutex lock;
};

/* ============================================================================
 * CS Timing Control
 * ============================================================================ */

/* CS timing delays (in microseconds) - from CY15B108QN datasheet */
#define CS_SETUP_DELAY_US    0   /* tCSS: CS low to first clock (min 5ns) */
#define CS_HOLD_DELAY_US     0   /* tCSH: last clock to CS high (min 5ns) */
#define CS_DESELECT_DELAY_US 0   /* tCSD: CS high between commands (min 25ns) */

static inline void fram_cs_assert(const struct cy15b108qn_config *config)
{
    gpio_pin_set_dt(&config->spi.config.cs.gpio, 1);  /* Active LOW: set=1 means LOW */
    // k_busy_wait(CS_SETUP_DELAY_US);                   /* Wait for CS setup time */
}

static inline void fram_cs_deassert(const struct cy15b108qn_config *config)
{
    // k_busy_wait(CS_HOLD_DELAY_US);                    /* Wait for CS hold time */
    gpio_pin_set_dt(&config->spi.config.cs.gpio, 0);  /* Active LOW: set=0 means HIGH */
    // k_busy_wait(CS_DESELECT_DELAY_US);                /* Wait for CS deselect time */
}

/* ============================================================================
 * SPI Operations
 * ============================================================================ */

static int fram_read(const struct device *dev, off_t offset, void *buf, size_t len)
{
    const struct cy15b108qn_config *config = dev->config;
    struct cy15b108qn_data *data = dev->data;
    uint8_t cmd[4] = {FRAM_CMD_READ, 0, 0, 0};
    uint8_t *paddr = &cmd[1];
    int err;

    if (offset + len > config->size) {
        LOG_ERR("attempt to read past device boundary");
        return -EINVAL;
    }

    if (!len) {
        return 0;
    }

    /* Populate 24-bit address */
    *paddr++ = (offset >> 16) & 0xFF;
    *paddr++ = (offset >> 8) & 0xFF;
    *paddr++ = offset & 0xFF;

    const struct spi_buf tx_buf = {
        .buf = cmd,
        .len = sizeof(cmd),
    };
    const struct spi_buf_set tx = {
        .buffers = &tx_buf,
        .count = 1,
    };
    const struct spi_buf rx_bufs[2] = {
        {
            .buf = NULL,
            .len = sizeof(cmd),
        },
        {
            .buf = buf,
            .len = len,
        },
    };
    const struct spi_buf_set rx = {
        .buffers = rx_bufs,
        .count = ARRAY_SIZE(rx_bufs),
    };

    /* SPI config without automatic CS */
    struct spi_config spi_cfg = config->spi.config;
    spi_cfg.cs.gpio.port = NULL;

    k_mutex_lock(&data->lock, K_FOREVER);

    fram_cs_assert(config);
    err = spi_transceive(config->spi.bus, &spi_cfg, &tx, &rx);
    fram_cs_deassert(config);

    k_mutex_unlock(&data->lock);

    if (err < 0) {
        LOG_ERR("failed to read FRAM (err %d)", err);
    }

    return err;
}

static int fram_wren(const struct device *dev)
{
    const struct cy15b108qn_config *config = dev->config;
    uint8_t cmd = FRAM_CMD_WREN;
    int err;

    const struct spi_buf tx_buf = {
        .buf = &cmd,
        .len = sizeof(cmd),
    };
    const struct spi_buf_set tx = {
        .buffers = &tx_buf,
        .count = 1,
    };

    /* SPI config without automatic CS */
    struct spi_config spi_cfg = config->spi.config;
    spi_cfg.cs.gpio.port = NULL;

    fram_cs_assert(config);
    err = spi_write(config->spi.bus, &spi_cfg, &tx);
    fram_cs_deassert(config);

    return err;
}

static int fram_wrdi(const struct device *dev)
{
    const struct cy15b108qn_config *config = dev->config;
    uint8_t cmd = FRAM_CMD_WRDI;
    int err;

    const struct spi_buf tx_buf = {
        .buf = &cmd,
        .len = sizeof(cmd),
    };
    const struct spi_buf_set tx = {
        .buffers = &tx_buf,
        .count = 1,
    };

    /* SPI config without automatic CS */
    struct spi_config spi_cfg = config->spi.config;
    spi_cfg.cs.gpio.port = NULL;

    fram_cs_assert(config);
    err = spi_write(config->spi.bus, &spi_cfg, &tx);
    fram_cs_deassert(config);

    return err;
}

static int fram_write(const struct device *dev, off_t offset, const void *buf, size_t len)
{
    const struct cy15b108qn_config *config = dev->config;
    struct cy15b108qn_data *data = dev->data;
    uint8_t cmd[4] = {FRAM_CMD_WRITE, 0, 0, 0};
    uint8_t *paddr = &cmd[1];
    int err;

    if (config->readonly) {
        LOG_ERR("attempt to write to read-only device");
        return -EACCES;
    }

    if (offset + len > config->size) {
        LOG_ERR("attempt to write past device boundary");
        return -EINVAL;
    }

    /* Populate 24-bit address */
    *paddr++ = (offset >> 16) & 0xFF;
    *paddr++ = (offset >> 8) & 0xFF;
    *paddr++ = offset & 0xFF;

    const struct spi_buf tx_bufs[2] = {
        {
            .buf = cmd,
            .len = sizeof(cmd),
        },
        {
            .buf = (void *)buf,
            .len = len,
        },
    };
    const struct spi_buf_set tx = {
        .buffers = tx_bufs,
        .count = ARRAY_SIZE(tx_bufs),
    };

    /* SPI config without automatic CS */
    struct spi_config spi_cfg = config->spi.config;
    spi_cfg.cs.gpio.port = NULL;

    k_mutex_lock(&data->lock, K_FOREVER);

    err = fram_wren(dev);
    if (err < 0) {
        LOG_ERR("failed to enable write (err %d)", err);
        k_mutex_unlock(&data->lock);
        return err;
    }

    fram_cs_assert(config);
    err = spi_write(config->spi.bus, &spi_cfg, &tx);
    fram_cs_deassert(config);

    if (err < 0) {
        LOG_ERR("failed to write to FRAM (err %d)", err);
        k_mutex_unlock(&data->lock);
        return err;
    }

    err = fram_wrdi(dev);
    if (err < 0) {
        LOG_ERR("failed to disable write (err %d)", err);
    }

    k_mutex_unlock(&data->lock);

    return err;
}

static size_t fram_size(const struct device *dev)
{
    const struct cy15b108qn_config *config = dev->config;
    return config->size;
}

static int fram_rdid(const struct device *dev)
{
    const struct cy15b108qn_config *config = dev->config;
    struct cy15b108qn_data *data = dev->data;
    uint8_t cmd = FRAM_CMD_RDID;
    uint8_t id[9] = {0};
    int err;

    /* TX: just the command byte */
    const struct spi_buf tx_buf = {
        .buf = &cmd,
        .len = 1,
    };
    const struct spi_buf_set tx = {
        .buffers = &tx_buf,
        .count = 1,
    };

    /* RX: discard during cmd TX, then receive 9 ID bytes */
    const struct spi_buf rx_bufs[2] = {
        { .buf = NULL, .len = 1 },           /* Discard during command TX */
        { .buf = id, .len = sizeof(id) },    /* Receive ID bytes */
    };
    const struct spi_buf_set rx = {
        .buffers = rx_bufs,
        .count = ARRAY_SIZE(rx_bufs),
    };

    /* SPI config without automatic CS (we control CS manually) */
    struct spi_config spi_cfg = config->spi.config;
    spi_cfg.cs.gpio.port = NULL;  /* Disable automatic CS */

    k_mutex_lock(&data->lock, K_FOREVER);

    /* Manual CS control with proper timing */
    fram_cs_assert(config);
    err = spi_transceive(config->spi.bus, &spi_cfg, &tx, &rx);
    // err = spi_write(config->spi.bus, &spi_cfg, &tx);
    // k_busy_wait(1);  /* Small delay between TX and RX */
    // err = spi_read(config->spi.bus, &spi_cfg, &rx);
    fram_cs_deassert(config);

    k_mutex_unlock(&data->lock);

    if (err < 0) {
        LOG_ERR("failed to read RDID (err %d)", err);
        return err;
    }

    LOG_INF("Device ID: %02X %02X %02X %02X %02X %02X %02X %02X %02X",
            id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8]);

    /* Unknown manufacturer - check if device responds at all */
    bool all_ff = true, all_00 = true;
    for (int i = 0; i < 4; i++) {
        if (id[i] != 0xFF) all_ff = false;
        if (id[i] != 0x00) all_00 = false;
    }

    if (all_ff || all_00) {
        LOG_ERR("FRAM not responding (ID all %s)", all_ff ? "0xFF" : "0x00");
        return -EIO;
    }

    /* Unknown but responding device - warn but allow */
    LOG_WRN("Unknown FRAM manufacturer ID: %02X, proceeding anyway", id[0]);
    return 0;
}

static int fram_ruid(const struct device *dev)
{
    const struct cy15b108qn_config *config = dev->config;
    struct cy15b108qn_data *data = dev->data;
    uint8_t cmd = FRAM_CMD_RUID;
    uint8_t uid[8] = {0};
    int err;

    /* TX: just the command byte */
    const struct spi_buf tx_buf = {
        .buf = &cmd,
        .len = 1,
    };
    const struct spi_buf_set tx = {
        .buffers = &tx_buf,
        .count = 1,
    };

    /* RX: discard during cmd TX, then receive 8 UID bytes */
    const struct spi_buf rx_bufs[2] = {
        { .buf = NULL, .len = 1 },
        { .buf = uid, .len = sizeof(uid) },
    };
    const struct spi_buf_set rx = {
        .buffers = rx_bufs,
        .count = ARRAY_SIZE(rx_bufs),
    };

    /* SPI config without automatic CS */
    struct spi_config spi_cfg = config->spi.config;
    spi_cfg.cs.gpio.port = NULL;

    k_mutex_lock(&data->lock, K_FOREVER);

    fram_cs_assert(config);
    err = spi_transceive(config->spi.bus, &spi_cfg, &tx, &rx);
    fram_cs_deassert(config);

    k_mutex_unlock(&data->lock);

    if (err < 0) {
        LOG_ERR("failed to read RUID (err %d)", err);
        return err;
    }

    LOG_INF("Unique ID: %02X %02X %02X %02X %02X %02X %02X %02X",
            uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7]);

    return 0;
}


/* ============================================================================
 * Driver Initialization
 * ============================================================================ */

static int cy15b108qn_init(const struct device *dev)
{
    const struct cy15b108qn_config *config = dev->config;
    struct cy15b108qn_data *data = dev->data;
    int err;

    k_mutex_init(&data->lock);

    LOG_INF("Initializing FRAM");

    if (!spi_is_ready_dt(&config->spi)) {
        LOG_ERR("SPI bus not ready");
        return -EINVAL;
    }

    /* Configure CS GPIO for manual control */
    if (!gpio_is_ready_dt(&config->spi.config.cs.gpio)) {
        LOG_ERR("CS GPIO not ready");
        return -EINVAL;
    }

    err = gpio_pin_configure_dt(&config->spi.config.cs.gpio, GPIO_OUTPUT_INACTIVE);
    if (err < 0) {
        LOG_ERR("Failed to configure CS GPIO (err %d)", err);
        return err;
    }

    /* Ensure CS is high (inactive) initially */
    gpio_pin_set_dt(&config->spi.config.cs.gpio, 0);  /* 0 = HIGH for active-low */
    k_busy_wait(CS_DESELECT_DELAY_US);

    err = fram_rdid(dev);
    if (err < 0) {
        LOG_ERR("FRAM initialization failed (err %d)", err);
        return err;
    }

    k_sleep(K_MSEC(10));

    err = fram_ruid(dev);
    if (err < 0) {
        LOG_ERR("Failed to read Unique ID (err %d)", err);
        return err;
    }

    LOG_INF("FRAM initialized: %zu KB", config->size / 1024);
    return 0;
}

/* ============================================================================
 * EEPROM API Implementation
 * ============================================================================ */

static DEVICE_API(eeprom, cy15b108qn_eeprom_api) = {
    .read = fram_read,
    .write = fram_write,
    .size = fram_size,
};

/* ============================================================================
 * Device Instantiation
 * ============================================================================ */

/* CS GPIO is obtained from SPI spec (SPI_DT_SPEC_INST_GET extracts cs-gpios) */
#define CY15B108QN_INIT(inst)                                                     \
    static struct cy15b108qn_data cy15b108qn_data_##inst;                         \
                                                                                   \
    static const struct cy15b108qn_config cy15b108qn_config_##inst = {            \
        .spi = SPI_DT_SPEC_INST_GET(                                              \
            inst, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0),   \
        .size = DT_INST_PROP(inst, size),                                         \
        .readonly = DT_INST_PROP_OR(inst, read_only, false),                      \
    };                                                                             \
                                                                                   \
    DEVICE_DT_INST_DEFINE(inst, cy15b108qn_init, NULL,                            \
                          &cy15b108qn_data_##inst,                                \
                          &cy15b108qn_config_##inst,                              \
                          POST_KERNEL,                                            \
                          CONFIG_EEPROM_INIT_PRIORITY,                            \
                          &cy15b108qn_eeprom_api);

DT_INST_FOREACH_STATUS_OKAY(CY15B108QN_INIT)
