/***************************************************************************
 * Copyright (C) 2026 Eclipse ThreadX contributors
 *
 * This program and the accompanying materials are made available under the
 * terms of the MIT License which is available at
 * https://opensource.org/licenses/MIT.
 *
 * AI Disclosure: This file was largely AI-generated by Copilot (Sonnet 4.6).
 * The AI-generated portions may be considered public domain (CC0-1.0)
 * and not subject to the project's licence. The human contributor has
 * reviewed and verified that the code is correct.
 *
 * SPDX-License-Identifier: MIT and CC0-1.0
 **************************************************************************/

#include <stddef.h>
#include <stdint.h>

#include "bits.h"
#include "i2c_master.h"
#include "io.h"
#include "pulp_mem_map.h"

#define UDMA_I2C_RX_SADDR_OFFSET 0x00U
#define UDMA_I2C_RX_SIZE_OFFSET  0x04U
#define UDMA_I2C_RX_CFG_OFFSET   0x08U
#define UDMA_I2C_TX_SADDR_OFFSET 0x10U
#define UDMA_I2C_TX_SIZE_OFFSET  0x14U
#define UDMA_I2C_TX_CFG_OFFSET   0x18U
#define UDMA_I2C_STATUS_OFFSET   0x20U
#define UDMA_I2C_SETUP_OFFSET    0x24U

#define UDMA_CTRL_CG_OFFSET      0x00U
#define UDMA_CFG_EN_BIT          BIT(4)
#define UDMA_CFG_PENDING_BIT     BIT(5)
#define UDMA_I2C_STATUS_AL_BIT   BIT(1)
#define UDMA_I2C_SETUP_RESET_BIT BIT(0)
#define I2C_MASTER_COUNT         2U
#define I2C_TIMEOUT_ITERATIONS   1000000U

#define I2C_CMD_CFG     0xE0U
#define I2C_CMD_START   0x00U
#define I2C_CMD_STOP    0x20U
#define I2C_CMD_RD_ACK  0x40U
#define I2C_CMD_RD_NACK 0x60U
#define I2C_CMD_WR      0x80U
#define I2C_CMD_WAIT    0xA0U
#define I2C_CMD_RPT     0xC0U

static uint8_t s_cmd_buf[32];
static uint8_t s_clkdiv[I2C_MASTER_COUNT];

static uintptr_t i2c_master_base(uint8_t id)
{
    return (id == 0U) ? (uintptr_t)UDMA_CH_ADDR_I2CM0 : (uintptr_t)UDMA_CH_ADDR_I2CM1;
}

int i2c_master_init(uint8_t id, uint32_t i2c_freq, uint32_t periph_freq)
{
    uint32_t cg;
    uint32_t clk_div;
    uintptr_t base;

    if ((id >= I2C_MASTER_COUNT) || (i2c_freq == 0U) || (periph_freq == 0U))
    {
        return -1;
    }

    cg = readw((uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));
    cg |= (id == 0U) ? UDMA_CTRL_I2CM0_CLKEN : UDMA_CTRL_I2CM1_CLKEN;
    writew(cg, (uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));

    clk_div = periph_freq / i2c_freq;
    if (clk_div == 0U)
    {
        clk_div = 1U;
    }
    else if (clk_div > 255U)
    {
        clk_div = 255U;
    }
    s_clkdiv[id] = (uint8_t)clk_div;

    base = i2c_master_base(id);
    writew(UDMA_I2C_SETUP_RESET_BIT, base + UDMA_I2C_SETUP_OFFSET);
    writew(0U, base + UDMA_I2C_SETUP_OFFSET);
    writew(0U, base + UDMA_I2C_RX_CFG_OFFSET);
    writew(0U, base + UDMA_I2C_TX_CFG_OFFSET);

    return 0;
}

int i2c_master_read_reg(uint8_t id, uint8_t dev_addr7, uint8_t reg_addr,
                        uint8_t *buf, uint8_t len)
{
    uintptr_t base;
    uint32_t timeout;
    uint32_t cmd_len;
    uint32_t status;

    if ((id >= I2C_MASTER_COUNT) || (dev_addr7 > 0x7FU) || (buf == NULL) || (len == 0U))
    {
        return -1;
    }

    cmd_len = 0U;
    s_cmd_buf[cmd_len++] = I2C_CMD_CFG;
    s_cmd_buf[cmd_len++] = 0U;
    s_cmd_buf[cmd_len++] = s_clkdiv[id];
    s_cmd_buf[cmd_len++] = I2C_CMD_START;
    s_cmd_buf[cmd_len++] = I2C_CMD_WR;
    s_cmd_buf[cmd_len++] = (uint8_t)(dev_addr7 << 1U);
    s_cmd_buf[cmd_len++] = I2C_CMD_WR;
    s_cmd_buf[cmd_len++] = reg_addr;
    s_cmd_buf[cmd_len++] = I2C_CMD_START;
    s_cmd_buf[cmd_len++] = I2C_CMD_WR;
    s_cmd_buf[cmd_len++] = (uint8_t)((dev_addr7 << 1U) | 1U);
    if (len > 1U)
    {
        s_cmd_buf[cmd_len++] = I2C_CMD_RPT;
        s_cmd_buf[cmd_len++] = (uint8_t)(len - 1U);
        s_cmd_buf[cmd_len++] = I2C_CMD_RD_ACK;
    }
    s_cmd_buf[cmd_len++] = I2C_CMD_RD_NACK;
    s_cmd_buf[cmd_len++] = I2C_CMD_STOP;
    s_cmd_buf[cmd_len++] = I2C_CMD_WAIT;
    s_cmd_buf[cmd_len++] = 0U;

    base = i2c_master_base(id);
    writew((uint32_t)(uintptr_t)buf, base + UDMA_I2C_RX_SADDR_OFFSET);
    writew((uint32_t)len, base + UDMA_I2C_RX_SIZE_OFFSET);
    writew(UDMA_CFG_EN_BIT, base + UDMA_I2C_RX_CFG_OFFSET);

    writew((uint32_t)(uintptr_t)s_cmd_buf, base + UDMA_I2C_TX_SADDR_OFFSET);
    writew(cmd_len, base + UDMA_I2C_TX_SIZE_OFFSET);
    writew(UDMA_CFG_EN_BIT, base + UDMA_I2C_TX_CFG_OFFSET);

    for (timeout = 0U; timeout < I2C_TIMEOUT_ITERATIONS; ++timeout)
    {
        status = readw(base + UDMA_I2C_STATUS_OFFSET);
        if ((status & UDMA_I2C_STATUS_AL_BIT) != 0U)
        {
            return -1;
        }
        if (((readw(base + UDMA_I2C_TX_CFG_OFFSET) & UDMA_CFG_PENDING_BIT) == 0U) &&
            ((readw(base + UDMA_I2C_RX_CFG_OFFSET) & UDMA_CFG_PENDING_BIT) == 0U))
        {
            return 0;
        }
    }

    return -1;
}
