/*
 * QEMU I3C bus interface.
 *
 * Copyright 2023 Google LLC
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 */

#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_
#define QEMU_INCLUDE_HW_I3C_I3C_H_

#include "hw/qdev-core.h"
#include "qom/object.h"
#include "hw/i2c/i2c.h"

#define TYPE_I3C_TARGET "i3c-target"
OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET)

typedef enum I3CEvent {
    I3C_START_RECV,
    I3C_START_SEND,
    I3C_STOP,
    I3C_NACK,
} I3CEvent;

typedef enum I3CCCC {
    /* Broadcast CCCs */
    I3C_CCC_ENEC      = 0x00,
    I3C_CCC_DISEC     = 0x01,
    I3C_CCC_ENTAS0    = 0x02,
    I3C_CCC_ENTAS1    = 0x03,
    I3C_CCC_ENTAS2    = 0x04,
    I3C_CCC_ENTAS3    = 0x05,
    I3C_CCC_RSTDAA    = 0x06,
    I3C_CCC_ENTDAA    = 0x07,
    I3C_CCC_DEFTGTS   = 0x08,
    I3C_CCC_SETMWL    = 0x09,
    I3C_CCC_SETMRL    = 0x0a,
    I3C_CCC_ENTTM     = 0x0b,
    I3C_CCC_SETBUSCON = 0x0c,
    I3C_CCC_ENDXFER   = 0x12,
    I3C_CCC_ENTHDR0   = 0x20,
    I3C_CCC_ENTHDR1   = 0x21,
    I3C_CCC_ENTHDR2   = 0x22,
    I3C_CCC_ENTHDR3   = 0x23,
    I3C_CCC_ENTHDR4   = 0x24,
    I3C_CCC_ENTHDR5   = 0x25,
    I3C_CCC_ENTHDR6   = 0x26,
    I3C_CCC_ENTHDR7   = 0x27,
    I3C_CCC_SETXTIME  = 0x28,
    I3C_CCC_SETAASA   = 0x29,
    I3C_CCC_RSTACT    = 0x2a,
    I3C_CCC_DEFGRPA   = 0x2b,
    I3C_CCC_RSTGRPA   = 0x2c,
    I3C_CCC_MLANE     = 0x2d,
    /* Direct CCCs */
    I3C_CCCD_ENEC       = 0x80,
    I3C_CCCD_DISEC      = 0x81,
    I3C_CCCD_ENTAS0     = 0x82,
    I3C_CCCD_ENTAS1     = 0x83,
    I3C_CCCD_ENTAS2     = 0x84,
    I3C_CCCD_ENTAS3     = 0x85,
    I3C_CCCD_SETDASA    = 0x87,
    I3C_CCCD_SETNEWDA   = 0x88,
    I3C_CCCD_SETMWL     = 0x89,
    I3C_CCCD_SETMRL     = 0x8a,
    I3C_CCCD_GETMWL     = 0x8b,
    I3C_CCCD_GETMRL     = 0x8c,
    I3C_CCCD_GETPID     = 0x8d,
    I3C_CCCD_GETBCR     = 0x8e,
    I3C_CCCD_GETDCR     = 0x8f,
    I3C_CCCD_GETSTATUS  = 0x90,
    I3C_CCCD_GETACCCR   = 0x91,
    I3C_CCCD_ENDXFER    = 0x92,
    I3C_CCCD_SETBRGTGT  = 0x93,
    I3C_CCCD_GETMXDS    = 0x94,
    I3C_CCCD_GETCAPS    = 0x95,
    I3C_CCCD_SETROUTE   = 0x96,
    I3C_CCCD_SETXTIME   = 0x98,
    I3C_CCCD_GETXTIME   = 0x99,
    I3C_CCCD_RSTACT     = 0x9a,
    I3C_CCCD_SETGRPA    = 0x9b,
    I3C_CCCD_RSTGRPA    = 0x9c,
    I3C_CCCD_MLANE      = 0x9d,
} I3CCCC;

#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80)

#define I3C_BROADCAST 0x7e
#define I3C_HJ_ADDR 0x02
#define I3C_ENTDAA_SIZE 8

struct I3CTargetClass {
    DeviceClass parent;

    /*
     * Controller to target. Returns 0 for success, non-zero for NAK or other
     * error.
     */
    int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send,
                uint32_t *num_sent);
    /*
     * Target to controller. I3C targets are able to terminate reads early, so
     * this returns the number of bytes read from the target.
     */
    uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read);
    /* Notify the target of a bus state change. */
    int (*event)(I3CTarget *s, enum I3CEvent event);
    /*
     * Handle a read CCC transmitted from a controller.
     * CCCs are I3C commands that I3C targets support.
     * The target can NACK the CCC if it does not support it.
     */
    int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t num_to_read,
                           uint32_t *num_read);
    /*
     * Handle a write CCC transmitted from a controller.
     * CCCs are I3C commands that I3C targets support.
     * The target can NACK the CCC if it does not support it.
     */
    int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data,
                            uint32_t num_to_send, uint32_t *num_sent);
};

struct I3CTarget {
    DeviceState qdev;

    uint8_t address;
    uint8_t static_address;
    uint8_t dcr;
    uint8_t bcr;
    uint64_t pid;

    /* CCC State tracking. */
    I3CCCC curr_ccc;
    uint8_t ccc_byte_offset;
    bool in_ccc;
};

struct I3CNode {
    I3CTarget *target;
    QLIST_ENTRY(I3CNode) next;
};

typedef struct I3CNode I3CNode;

typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList;

#define TYPE_I3C_BUS "i3c-bus"
OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS)

struct I3CBus {
    BusState qbus;

    /* Legacy I2C. */
    I2CBus *i2c_bus;

    I3CNodeList current_devs;
    bool broadcast;
    uint8_t ccc;
    bool in_ccc;
    bool in_entdaa;
    uint8_t saved_address;
};

struct I3CBusClass {
    DeviceClass parent;

    /* Handle an incoming IBI request from a target */
    int (*ibi_handle) (I3CBus *bus, I3CTarget *target, uint8_t addr,
                       bool is_recv);
    /* Receive data from an IBI request */
    int (*ibi_recv) (I3CBus *bus, uint8_t data);
    /* Do anything that needs to be done, since the IBI is finished. */
    int (*ibi_finish) (I3CBus *bus);
};

I3CBus *i3c_init_bus(DeviceState *parent, const char *name);
I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
                          const char *name);
void i3c_set_target_address(I3CTarget *dev, uint8_t address);
bool i3c_bus_busy(I3CBus *bus);

/*
 * Start a transfer on an I3C bus.
 * If is_recv is known at compile-time (i.e. a device will always be sending or
 * will always be receiving at a certain point), prefer to use i3c_start_recv or
 * i3c_start_send instead.
 *
 * Returns 0 on success, non-zero on an error.
 */
int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);

/*
 * Start a receive transfer on an I3C bus.
 *
 * Returns 0 on success, non-zero on an error
 */
int i3c_start_recv(I3CBus *bus, uint8_t address);

/*
 * Start a send transfer on an I3C bus.
 *
 * Returns 0 on success, non-zero on an error
 */
int i3c_start_send(I3CBus *bus, uint8_t address);

void i3c_end_transfer(I3CBus *bus);
void i3c_nack(I3CBus *bus);
int i3c_send_byte(I3CBus *bus, uint8_t data);
int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
             uint32_t *num_sent);
/*
 * I3C receives can only NACK on a CCC. The target should NACK a CCC it does not
 * support.
 */
int i3c_recv_byte(I3CBus *bus, uint8_t *data);
int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
             uint32_t *num_read);
bool i3c_scan_bus(I3CBus *bus, uint8_t address);
int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t *bcr,
                  uint8_t *dcr);
int i3c_start_device_transfer(I3CTarget *dev, int send_length);
bool i3c_target_match(I3CBus *bus, I3CTarget *target, uint8_t address);
int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv);
int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data);
int i3c_target_ibi_finish(I3CTarget *t, uint8_t data);

/*
 * Legacy I2C functions.
 *
 * These are wrapper for I2C functions that take in an I3C bus instead of an I2C
 * bus. Internally they use the I2C bus (and devices attached to it) that's a
 * part of the I3C bus
 */
void legacy_i2c_nack(I3CBus *bus);
uint8_t legacy_i2c_recv(I3CBus *bus);
int legacy_i2c_send(I3CBus *bus, uint8_t data);
int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);
int legacy_i2c_start_recv(I3CBus *bus, uint8_t address);
int legacy_i2c_start_send(I3CBus *bus, uint8_t address);
void legacy_i2c_end_transfer(I3CBus *bus);
I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
                                          uint8_t addr);

/**
 * Create an I3C Target.
 *
 * The target returned from this function still needs to be realized.
 */
I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
                          uint8_t bcr, uint64_t pid);

/**
 * Create and realize an I3C target.
 *
 * Create the target, initialize it, put it on the specified I3C bus, and
 * realize it.
 */
I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name,
                                    uint8_t addr, uint8_t dcr, uint8_t bcr,
                                    uint64_t pid);

/* Realize and drop the reference count on an I3C target. */
bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp);

#endif  /* QEMU_INCLUDE_HW_I3C_I3C_H_ */