#ifndef _MMC_SDHCI_H_
#define _MMC_SDHCI_H_

#include <lyos/types.h>
#include <asm/io.h>

#include "mmchost.h"

#define GENMASK(h, l) \
    (((~0UL) << (l)) & (~0UL >> ((sizeof(unsigned long) * 8) - 1 - (h))))

/*
 * Controller registers
 */

#define SDHCI_DMA_ADDRESS   0x00
#define SDHCI_ARGUMENT2     SDHCI_DMA_ADDRESS
#define SDHCI_32BIT_BLK_CNT SDHCI_DMA_ADDRESS

#define SDHCI_BLOCK_SIZE             0x04
#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))

#define SDHCI_BLOCK_COUNT 0x06

#define SDHCI_ARGUMENT 0x08

#define SDHCI_TRANSFER_MODE   0x0C
#define SDHCI_TRNS_DMA        0x01
#define SDHCI_TRNS_BLK_CNT_EN 0x02
#define SDHCI_TRNS_AUTO_CMD12 0x04
#define SDHCI_TRNS_AUTO_CMD23 0x08
#define SDHCI_TRNS_AUTO_SEL   0x0C
#define SDHCI_TRNS_READ       0x10
#define SDHCI_TRNS_MULTI      0x20

#define SDHCI_COMMAND       0x0E
#define SDHCI_CMD_RESP_MASK 0x03
#define SDHCI_CMD_CRC       0x08
#define SDHCI_CMD_INDEX     0x10
#define SDHCI_CMD_DATA      0x20
#define SDHCI_CMD_ABORTCMD  0xC0

#define SDHCI_CMD_RESP_NONE       0x00
#define SDHCI_CMD_RESP_LONG       0x01
#define SDHCI_CMD_RESP_SHORT      0x02
#define SDHCI_CMD_RESP_SHORT_BUSY 0x03

#define SDHCI_MAKE_CMD(c, f) (((c & 0xff) << 8) | (f & 0xff))
#define SDHCI_GET_CMD(c)     ((c >> 8) & 0x3f)

#define SDHCI_RESPONSE 0x10

#define SDHCI_BUFFER 0x20

#define SDHCI_PRESENT_STATE   0x24
#define SDHCI_CMD_INHIBIT     0x00000001
#define SDHCI_DATA_INHIBIT    0x00000002
#define SDHCI_DOING_WRITE     0x00000100
#define SDHCI_DOING_READ      0x00000200
#define SDHCI_SPACE_AVAILABLE 0x00000400
#define SDHCI_DATA_AVAILABLE  0x00000800
#define SDHCI_CARD_PRESENT    0x00010000
#define SDHCI_CARD_PRES_SHIFT 16
#define SDHCI_CD_STABLE       0x00020000
#define SDHCI_CD_LVL          0x00040000
#define SDHCI_CD_LVL_SHIFT    18
#define SDHCI_WRITE_PROTECT   0x00080000
#define SDHCI_DATA_LVL_MASK   0x00F00000
#define SDHCI_DATA_LVL_SHIFT  20
#define SDHCI_DATA_0_LVL_MASK 0x00100000
#define SDHCI_CMD_LVL         0x01000000

#define SDHCI_HOST_CONTROL    0x28
#define SDHCI_CTRL_LED        0x01
#define SDHCI_CTRL_4BITBUS    0x02
#define SDHCI_CTRL_HISPD      0x04
#define SDHCI_CTRL_DMA_MASK   0x18
#define SDHCI_CTRL_SDMA       0x00
#define SDHCI_CTRL_ADMA1      0x08
#define SDHCI_CTRL_ADMA32     0x10
#define SDHCI_CTRL_ADMA64     0x18
#define SDHCI_CTRL_ADMA3      0x18
#define SDHCI_CTRL_8BITBUS    0x20
#define SDHCI_CTRL_CDTEST_INS 0x40
#define SDHCI_CTRL_CDTEST_EN  0x80

#define SDHCI_POWER_CONTROL 0x29
#define SDHCI_POWER_ON      0x01
#define SDHCI_POWER_180     0x0A
#define SDHCI_POWER_300     0x0C
#define SDHCI_POWER_330     0x0E

#define SDHCI_BLOCK_GAP_CONTROL 0x2A

#define SDHCI_WAKE_UP_CONTROL 0x2B
#define SDHCI_WAKE_ON_INT     0x01
#define SDHCI_WAKE_ON_INSERT  0x02
#define SDHCI_WAKE_ON_REMOVE  0x04

#define SDHCI_CLOCK_CONTROL    0x2C
#define SDHCI_DIVIDER_SHIFT    8
#define SDHCI_DIVIDER_HI_SHIFT 6
#define SDHCI_DIV_MASK         0xFF
#define SDHCI_DIV_MASK_LEN     8
#define SDHCI_DIV_HI_MASK      0x300
#define SDHCI_PROG_CLOCK_MODE  0x0020
#define SDHCI_CLOCK_CARD_EN    0x0004
#define SDHCI_CLOCK_PLL_EN     0x0008
#define SDHCI_CLOCK_INT_STABLE 0x0002
#define SDHCI_CLOCK_INT_EN     0x0001

#define SDHCI_TIMEOUT_CONTROL 0x2E

#define SDHCI_SOFTWARE_RESET 0x2F
#define SDHCI_RESET_ALL      0x01
#define SDHCI_RESET_CMD      0x02
#define SDHCI_RESET_DATA     0x04

#define SDHCI_INT_STATUS       0x30
#define SDHCI_INT_ENABLE       0x34
#define SDHCI_SIGNAL_ENABLE    0x38
#define SDHCI_INT_RESPONSE     0x00000001
#define SDHCI_INT_DATA_END     0x00000002
#define SDHCI_INT_BLK_GAP      0x00000004
#define SDHCI_INT_DMA_END      0x00000008
#define SDHCI_INT_SPACE_AVAIL  0x00000010
#define SDHCI_INT_DATA_AVAIL   0x00000020
#define SDHCI_INT_CARD_INSERT  0x00000040
#define SDHCI_INT_CARD_REMOVE  0x00000080
#define SDHCI_INT_CARD_INT     0x00000100
#define SDHCI_INT_RETUNE       0x00001000
#define SDHCI_INT_CQE          0x00004000
#define SDHCI_INT_ERROR        0x00008000
#define SDHCI_INT_TIMEOUT      0x00010000
#define SDHCI_INT_CRC          0x00020000
#define SDHCI_INT_END_BIT      0x00040000
#define SDHCI_INT_INDEX        0x00080000
#define SDHCI_INT_DATA_TIMEOUT 0x00100000
#define SDHCI_INT_DATA_CRC     0x00200000
#define SDHCI_INT_DATA_END_BIT 0x00400000
#define SDHCI_INT_BUS_POWER    0x00800000
#define SDHCI_INT_AUTO_CMD_ERR 0x01000000
#define SDHCI_INT_ADMA_ERROR   0x02000000

#define SDHCI_INT_NORMAL_MASK 0x00007FFF
#define SDHCI_INT_ERROR_MASK  0xFFFF8000

#define SDHCI_INT_CMD_MASK                                    \
    (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | \
     SDHCI_INT_END_BIT | SDHCI_INT_INDEX | SDHCI_INT_AUTO_CMD_ERR)
#define SDHCI_INT_DATA_MASK                                                \
    (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | SDHCI_INT_DATA_AVAIL |       \
     SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \
     SDHCI_INT_DATA_END_BIT | SDHCI_INT_ADMA_ERROR | SDHCI_INT_BLK_GAP)
#define SDHCI_INT_ALL_MASK ((unsigned int)-1)

#define SDHCI_CQE_INT_ERR_MASK                                             \
    (SDHCI_INT_ADMA_ERROR | SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | \
     SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX |       \
     SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT)

#define SDHCI_CQE_INT_MASK (SDHCI_CQE_INT_ERR_MASK | SDHCI_INT_CQE)

#define SDHCI_AUTO_CMD_STATUS  0x3C
#define SDHCI_AUTO_CMD_TIMEOUT 0x00000002
#define SDHCI_AUTO_CMD_CRC     0x00000004
#define SDHCI_AUTO_CMD_END_BIT 0x00000008
#define SDHCI_AUTO_CMD_INDEX   0x00000010

#define SDHCI_HOST_CONTROL2          0x3E
#define SDHCI_CTRL_UHS_MASK          0x0007
#define SDHCI_CTRL_UHS_SDR12         0x0000
#define SDHCI_CTRL_UHS_SDR25         0x0001
#define SDHCI_CTRL_UHS_SDR50         0x0002
#define SDHCI_CTRL_UHS_SDR104        0x0003
#define SDHCI_CTRL_UHS_DDR50         0x0004
#define SDHCI_CTRL_HS400             0x0005 /* Non-standard */
#define SDHCI_CTRL_VDD_180           0x0008
#define SDHCI_CTRL_DRV_TYPE_MASK     0x0030
#define SDHCI_CTRL_DRV_TYPE_B        0x0000
#define SDHCI_CTRL_DRV_TYPE_A        0x0010
#define SDHCI_CTRL_DRV_TYPE_C        0x0020
#define SDHCI_CTRL_DRV_TYPE_D        0x0030
#define SDHCI_CTRL_EXEC_TUNING       0x0040
#define SDHCI_CTRL_TUNED_CLK         0x0080
#define SDHCI_CMD23_ENABLE           0x0800
#define SDHCI_CTRL_V4_MODE           0x1000
#define SDHCI_CTRL_64BIT_ADDR        0x2000
#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000

#define SDHCI_CAPABILITIES       0x40
#define SDHCI_TIMEOUT_CLK_MASK   GENMASK(5, 0)
#define SDHCI_TIMEOUT_CLK_SHIFT  0
#define SDHCI_TIMEOUT_CLK_UNIT   0x00000080
#define SDHCI_CLOCK_BASE_MASK    GENMASK(13, 8)
#define SDHCI_CLOCK_BASE_SHIFT   8
#define SDHCI_CLOCK_V3_BASE_MASK GENMASK(15, 8)
#define SDHCI_MAX_BLOCK_MASK     0x00030000
#define SDHCI_MAX_BLOCK_SHIFT    16
#define SDHCI_CAN_DO_8BIT        0x00040000
#define SDHCI_CAN_DO_ADMA2       0x00080000
#define SDHCI_CAN_DO_ADMA1       0x00100000
#define SDHCI_CAN_DO_HISPD       0x00200000
#define SDHCI_CAN_DO_SDMA        0x00400000
#define SDHCI_CAN_DO_SUSPEND     0x00800000
#define SDHCI_CAN_VDD_330        0x01000000
#define SDHCI_CAN_VDD_300        0x02000000
#define SDHCI_CAN_VDD_180        0x04000000
#define SDHCI_CAN_64BIT_V4       0x08000000
#define SDHCI_CAN_64BIT          0x10000000

#define SDHCI_CAPABILITIES_1            0x44
#define SDHCI_SUPPORT_SDR50             0x00000001
#define SDHCI_SUPPORT_SDR104            0x00000002
#define SDHCI_SUPPORT_DDR50             0x00000004
#define SDHCI_DRIVER_TYPE_A             0x00000010
#define SDHCI_DRIVER_TYPE_C             0x00000020
#define SDHCI_DRIVER_TYPE_D             0x00000040
#define SDHCI_RETUNING_TIMER_COUNT_MASK GENMASK(11, 8)
#define SDHCI_USE_SDR50_TUNING          0x00002000
#define SDHCI_RETUNING_MODE_MASK        GENMASK(15, 14)
#define SDHCI_CLOCK_MUL_SHIFT           16
#define SDHCI_CLOCK_MUL_MASK            GENMASK(23, 16)
#define SDHCI_CAN_DO_ADMA3              0x08000000
#define SDHCI_SUPPORT_HS400             0x80000000 /* Non-standard */

#define SDHCI_MAX_CURRENT            0x48
#define SDHCI_MAX_CURRENT_LIMIT      GENMASK(7, 0)
#define SDHCI_MAX_CURRENT_330_MASK   GENMASK(7, 0)
#define SDHCI_MAX_CURRENT_300_MASK   GENMASK(15, 8)
#define SDHCI_MAX_CURRENT_180_MASK   GENMASK(23, 16)
#define SDHCI_MAX_CURRENT_MULTIPLIER 4

/* 4C-4F reserved for more max current */

#define SDHCI_SET_ACMD12_ERROR 0x50
#define SDHCI_SET_INT_ERROR    0x52

#define SDHCI_ADMA_ERROR 0x54

/* 55-57 reserved */

#define SDHCI_ADMA_ADDRESS    0x58
#define SDHCI_ADMA_ADDRESS_HI 0x5C

/* 60-FB reserved */

#define SDHCI_PRESET_FOR_HIGH_SPEED  0x64
#define SDHCI_PRESET_FOR_SDR12       0x66
#define SDHCI_PRESET_FOR_SDR25       0x68
#define SDHCI_PRESET_FOR_SDR50       0x6A
#define SDHCI_PRESET_FOR_SDR104      0x6C
#define SDHCI_PRESET_FOR_DDR50       0x6E
#define SDHCI_PRESET_FOR_HS400       0x74 /* Non-standard */
#define SDHCI_PRESET_DRV_MASK        GENMASK(15, 14)
#define SDHCI_PRESET_CLKGEN_SEL      BIT(10)
#define SDHCI_PRESET_SDCLK_FREQ_MASK GENMASK(9, 0)

#define SDHCI_SLOT_INT_STATUS 0xFC

#define SDHCI_HOST_VERSION     0xFE
#define SDHCI_VENDOR_VER_MASK  0xFF00
#define SDHCI_VENDOR_VER_SHIFT 8
#define SDHCI_SPEC_VER_MASK    0x00FF
#define SDHCI_SPEC_VER_SHIFT   0
#define SDHCI_SPEC_100         0
#define SDHCI_SPEC_200         1
#define SDHCI_SPEC_300         2
#define SDHCI_SPEC_400         3
#define SDHCI_SPEC_410         4
#define SDHCI_SPEC_420         5

/*
 * End of controller registers.
 */

#define SDHCI_MAX_DIV_SPEC_200 256
#define SDHCI_MAX_DIV_SPEC_300 2046

struct mmc_command;

struct sdhci_host_ops;

struct sdhci_host {
    struct mmc_host* mmc;

    void* ioaddr;
    int irq;
    int irq_hook;

    unsigned int version;
    unsigned int max_clk;
    unsigned int clk_mul;
    unsigned int clock;

    unsigned int caps;
    unsigned int caps1;

    u32 ier;

    struct mmc_command* cmd;
    struct mmc_command* data_cmd;
    struct mmc_data* data;

    unsigned int blocks;

    const struct sdhci_host_ops* ops;

    unsigned long private[];
};

struct sdhci_host_ops {
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
    u32 (*read_l)(struct sdhci_host* host, int reg);
    u16 (*read_w)(struct sdhci_host* host, int reg);
    u8 (*read_b)(struct sdhci_host* host, int reg);
    void (*write_l)(struct sdhci_host* host, int reg, u32 val);
    void (*write_w)(struct sdhci_host* host, int reg, u16 val);
    void (*write_b)(struct sdhci_host* host, int reg, u8 val);
#endif

    void (*set_clock)(struct sdhci_host* host, unsigned int clock);
    unsigned int (*get_max_clock)(struct sdhci_host* host);
    void (*set_bus_width)(struct sdhci_host* host, int width);
    void (*hw_reset)(struct sdhci_host* host);
};

static inline void* sdhci_priv(struct sdhci_host* host)
{
    return host->private;
}

struct sdhci_host* sdhci_alloc_host(size_t priv_size);
int sdhci_add_host(struct sdhci_host* host);

void sdhci_set_clock(struct sdhci_host* host, unsigned int clock);
void sdhci_set_bus_width(struct sdhci_host* host, int width);

#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS

static inline void sdhci_writel(struct sdhci_host* host, int reg, u32 val)
{
    if (host->ops->write_l)
        host->ops->write_l(host, reg, val);
    else
        writel(host->ioaddr + reg, val);
}

static inline void sdhci_writew(struct sdhci_host* host, int reg, u16 val)
{
    if (host->ops->write_w)
        host->ops->write_w(host, reg, val);
    else
        writew(host->ioaddr + reg, val);
}

static inline void sdhci_writeb(struct sdhci_host* host, int reg, u8 val)
{
    if (host->ops->write_b)
        host->ops->write_b(host, reg, val);
    else
        writeb(host->ioaddr + reg, val);
}

static inline u32 sdhci_readl(struct sdhci_host* host, int reg)
{
    if (host->ops->read_l)
        return host->ops->read_l(host, reg);
    else
        return readl(host->ioaddr + reg);
}

static inline u16 sdhci_readw(struct sdhci_host* host, int reg)
{
    if (host->ops->read_w)
        return host->ops->read_w(host, reg);
    else
        return readw(host->ioaddr + reg);
}

static inline u8 sdhci_readb(struct sdhci_host* host, int reg)
{
    if (host->ops->read_b)
        return host->ops->read_b(host, reg);
    else
        return readb(host->ioaddr + reg);
}

#else

static inline void sdhci_writel(struct sdhci_host* host, int reg, u32 val)
{
    writel(host->ioaddr + reg, val);
}

static inline void sdhci_writew(struct sdhci_host* host, int reg, u16 val)
{
    writew(host->ioaddr + reg, val);
}

static inline void sdhci_writeb(struct sdhci_host* host, int reg, u8 val)
{
    writeb(host->ioaddr + reg, val);
}

static inline u32 sdhci_readl(struct sdhci_host* host, int reg)
{
    return readl(host->ioaddr + reg);
}

static inline u16 sdhci_readw(struct sdhci_host* host, int reg)
{
    return readw(host->ioaddr + reg);
}

static inline u8 sdhci_readb(struct sdhci_host* host, int reg)
{
    return readb(host->ioaddr + reg);
}

#endif /* CONFIG_MMC_SDHCI_IO_ACCESSORS */

#if CONFIG_MMC_SDHCI_OF
struct sdhci_host* sdhci_of_init(const void* blob, unsigned long offset,
                                 const struct sdhci_host_ops* ops,
                                 size_t priv_size);
#endif

#if CONFIG_MMC_SDHCI_IPROC
void sdhci_iproc_scan(void);
#endif

#endif
