/* $NetBSD: qcompep.c,v 1.2 2025/01/08 22:58:05 jmcneill Exp $ */
/*      $OpenBSD: qcaoss.c,v 1.1 2023/05/23 14:10:27 patrick Exp $      */
/*      $OpenBSD: qccpucp.c,v 1.1 2024/11/16 21:17:54 tobhe Exp $       */
/*
* Copyright (c) 2023 Patrick Wildt <[email protected]>
* Copyright (c) 2024 Tobias Heider <[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kmem.h>

#include <dev/acpi/acpivar.h>
#include <dev/acpi/qcompep.h>
#include <dev/acpi/qcomipcc.h>

#include <dev/ic/scmi.h>

#define AOSS_DESC_MAGIC                 0x0
#define AOSS_DESC_VERSION               0x4
#define AOSS_DESC_FEATURES              0x8
#define AOSS_DESC_UCORE_LINK_STATE      0xc
#define AOSS_DESC_UCORE_LINK_STATE_ACK  0x10
#define AOSS_DESC_UCORE_CH_STATE        0x14
#define AOSS_DESC_UCORE_CH_STATE_ACK    0x18
#define AOSS_DESC_UCORE_MBOX_SIZE       0x1c
#define AOSS_DESC_UCORE_MBOX_OFFSET     0x20
#define AOSS_DESC_MCORE_LINK_STATE      0x24
#define AOSS_DESC_MCORE_LINK_STATE_ACK  0x28
#define AOSS_DESC_MCORE_CH_STATE        0x2c
#define AOSS_DESC_MCORE_CH_STATE_ACK    0x30
#define AOSS_DESC_MCORE_MBOX_SIZE       0x34
#define AOSS_DESC_MCORE_MBOX_OFFSET     0x38

#define AOSS_MAGIC                      0x4d41494c
#define AOSS_VERSION                    1

#define AOSS_STATE_UP                   (0xffffU << 0)
#define AOSS_STATE_DOWN                 (0xffffU << 16)

#define AOSSREAD4(sc, reg)                                              \
       bus_space_read_4((sc)->sc_iot, (sc)->sc_aoss_ioh, (reg))
#define AOSSWRITE4(sc, reg, val)                                        \
       bus_space_write_4((sc)->sc_iot, (sc)->sc_aoss_ioh, (reg), (val))

#define CPUCP_REG_CMD(i)        (0x104 + ((i) * 8))
#define CPUCP_MASK_CMD          0xffffffffffffffffULL
#define CPUCP_REG_RX_MAP        0x4000
#define CPUCP_REG_RX_STAT       0x4400
#define CPUCP_REG_RX_CLEAR      0x4800
#define CPUCP_REG_RX_EN         0x4C00

#define RXREAD8(sc, reg)                                                \
       (bus_space_read_8((sc)->sc_iot, (sc)->sc_cpucp_rx_ioh, (reg)))
#define RXWRITE8(sc, reg, val)                                          \
       bus_space_write_8((sc)->sc_iot, (sc)->sc_cpucp_rx_ioh, (reg), (val))

#define TXWRITE4(sc, reg, val)                                          \
       bus_space_write_4((sc)->sc_iot, (sc)->sc_cpucp_tx_ioh, (reg), (val))


struct qcpep_data {
       bus_addr_t              aoss_base;
       bus_size_t              aoss_size;
       uint32_t                aoss_client_id;
       uint32_t                aoss_signal_id;
       bus_addr_t              cpucp_rx_base;
       bus_size_t              cpucp_rx_size;
       bus_addr_t              cpucp_tx_base;
       bus_size_t              cpucp_tx_size;
       bus_addr_t              cpucp_shmem_base;
       bus_size_t              cpucp_shmem_size;
};

struct qcpep_softc {
       device_t                sc_dev;
       bus_space_tag_t         sc_iot;

       const struct qcpep_data *sc_data;

       bus_space_handle_t      sc_aoss_ioh;
       size_t                  sc_aoss_offset;
       size_t                  sc_aoss_size;
       void *                  sc_aoss_ipcc;

       bus_space_handle_t      sc_cpucp_rx_ioh;
       bus_space_handle_t      sc_cpucp_tx_ioh;

       struct scmi_softc       sc_scmi;
};

struct qcpep_softc *qcpep_sc;

static const struct qcpep_data qcpep_x1e_data = {
       .aoss_base = 0x0c300000,
       .aoss_size = 0x400,
       .aoss_client_id = 0,    /* IPCC_CLIENT_AOP */
       .aoss_signal_id = 0,    /* IPCC_MPROC_SIGNAL_GLINK_QMP */
       .cpucp_rx_base = 0x17430000,
       .cpucp_rx_size = 0x10000,
       .cpucp_tx_base = 0x18830000,
       .cpucp_tx_size = 0x10000,
       .cpucp_shmem_base = 0x18b4e000,
       .cpucp_shmem_size = 0x400,
};

static const struct device_compatible_entry compat_data[] = {
       { .compat = "QCOM0C17",         .data = &qcpep_x1e_data },
       DEVICE_COMPAT_EOL
};

static int      qcpep_match(device_t, cfdata_t, void *);
static void     qcpep_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(qcompep, sizeof(struct qcpep_softc),
   qcpep_match, qcpep_attach, NULL, NULL);

static int
qcpep_match(device_t parent, cfdata_t match, void *aux)
{
       struct acpi_attach_args *aa = aux;

       return acpi_compatible_match(aa, compat_data);
}

static void
qcpep_attach(device_t parent, device_t self, void *aux)
{
       struct qcpep_softc *sc = device_private(self);
       struct acpi_attach_args *aa = aux;
       CPU_INFO_ITERATOR cii;
       struct cpu_info *ci;
       struct acpi_resources res;
       uint8_t *scmi_shmem;
       ACPI_STATUS rv;
       int i, last_pkg;;

       rv = acpi_resource_parse(self, aa->aa_node->ad_handle,
           "_CRS", &res, &acpi_resource_parse_ops_default);
       if (ACPI_FAILURE(rv)) {
               return;
       }
       acpi_resource_cleanup(&res);

       sc->sc_dev = self;
       sc->sc_iot = aa->aa_memt;
       sc->sc_data = acpi_compatible_lookup(aa, compat_data)->data;

       if (bus_space_map(sc->sc_iot, sc->sc_data->aoss_base,
           sc->sc_data->aoss_size, BUS_SPACE_MAP_NONPOSTED, &sc->sc_aoss_ioh)) {
               aprint_error_dev(self, "couldn't map aoss registers\n");
               return;
       }
       if (bus_space_map(sc->sc_iot, sc->sc_data->cpucp_rx_base,
           sc->sc_data->cpucp_rx_size, BUS_SPACE_MAP_NONPOSTED,
           &sc->sc_cpucp_rx_ioh)) {
               aprint_error_dev(self, "couldn't map cpucp rx registers\n");
               return;
       }
       if (bus_space_map(sc->sc_iot, sc->sc_data->cpucp_tx_base,
           sc->sc_data->cpucp_tx_size, BUS_SPACE_MAP_NONPOSTED,
           &sc->sc_cpucp_tx_ioh)) {
               aprint_error_dev(self, "couldn't map cpucp tx registers\n");
               return;
       }

       sc->sc_aoss_ipcc = qcipcc_channel(sc->sc_data->aoss_client_id,
                                         sc->sc_data->aoss_signal_id);
       if (sc->sc_aoss_ipcc == NULL) {
               aprint_error_dev(self, "couldn't find ipcc mailbox\n");
               return;
       }

       if (AOSSREAD4(sc, AOSS_DESC_MAGIC) != AOSS_MAGIC ||
           AOSSREAD4(sc, AOSS_DESC_VERSION) != AOSS_VERSION) {
               aprint_error_dev(self, "invalid QMP info\n");
               return;
       }

       sc->sc_aoss_offset = AOSSREAD4(sc, AOSS_DESC_MCORE_MBOX_OFFSET);
       sc->sc_aoss_size = AOSSREAD4(sc, AOSS_DESC_MCORE_MBOX_SIZE);
       if (sc->sc_aoss_size == 0) {
               aprint_error_dev(self, "invalid AOSS mailbox size\n");
               return;
       }

       AOSSWRITE4(sc, AOSS_DESC_UCORE_LINK_STATE_ACK,
           AOSSREAD4(sc, AOSS_DESC_UCORE_LINK_STATE));

       AOSSWRITE4(sc, AOSS_DESC_MCORE_LINK_STATE, AOSS_STATE_UP);
       qcipcc_send(sc->sc_aoss_ipcc);

       for (i = 1000; i > 0; i--) {
               if (AOSSREAD4(sc, AOSS_DESC_MCORE_LINK_STATE_ACK) == AOSS_STATE_UP)
                       break;
               delay(1000);
       }
       if (i == 0) {
               aprint_error_dev(self, "didn't get link state ack\n");
               return;
       }

       AOSSWRITE4(sc, AOSS_DESC_MCORE_CH_STATE, AOSS_STATE_UP);
       qcipcc_send(sc->sc_aoss_ipcc);

       for (i = 1000; i > 0; i--) {
               if (AOSSREAD4(sc, AOSS_DESC_UCORE_CH_STATE) == AOSS_STATE_UP)
                       break;
               delay(1000);
       }
       if (i == 0) {
               aprint_error_dev(self, "didn't get open channel\n");
               return;
       }

       AOSSWRITE4(sc, AOSS_DESC_UCORE_CH_STATE_ACK, AOSS_STATE_UP);
       qcipcc_send(sc->sc_aoss_ipcc);

       for (i = 1000; i > 0; i--) {
               if (AOSSREAD4(sc, AOSS_DESC_MCORE_CH_STATE_ACK) == AOSS_STATE_UP)
                       break;
               delay(1000);
       }
       if (i == 0) {
               aprint_error_dev(self, "didn't get channel ack\n");
               return;
       }

       RXWRITE8(sc, CPUCP_REG_RX_EN, 0);
       RXWRITE8(sc, CPUCP_REG_RX_CLEAR, 0);
       RXWRITE8(sc, CPUCP_REG_RX_MAP, 0);
       RXWRITE8(sc, CPUCP_REG_RX_MAP, CPUCP_MASK_CMD);

       qcpep_sc = sc;

       /* SCMI setup */
       scmi_shmem = AcpiOsMapMemory(sc->sc_data->cpucp_shmem_base,
           sc->sc_data->cpucp_shmem_size);
       if (scmi_shmem == NULL) {
               aprint_error_dev(self, "couldn't map SCMI shared memory\n");
               return;
       }

       sc->sc_scmi.sc_dev = self;
       sc->sc_scmi.sc_iot = sc->sc_iot;
       sc->sc_scmi.sc_shmem_tx = (struct scmi_shmem *)(scmi_shmem + 0x000);
       sc->sc_scmi.sc_shmem_rx = (struct scmi_shmem *)(scmi_shmem + 0x200);
       sc->sc_scmi.sc_mbox_tx = qccpucp_channel(0);
       sc->sc_scmi.sc_mbox_tx_send = qccpucp_send;
       sc->sc_scmi.sc_mbox_rx = qccpucp_channel(2);
       sc->sc_scmi.sc_mbox_rx_send = qccpucp_send;
       /* Build performance domain to CPU map. */
       sc->sc_scmi.sc_perf_ndmap = 0;
       last_pkg = -1;
       for (CPU_INFO_FOREACH(cii, ci)) {
               if (ci->ci_package_id != last_pkg) {
                       sc->sc_scmi.sc_perf_ndmap++;
                       last_pkg = ci->ci_package_id;
               }
       }
       sc->sc_scmi.sc_perf_dmap = kmem_zalloc(
           sizeof(*sc->sc_scmi.sc_perf_dmap) * sc->sc_scmi.sc_perf_ndmap,
           KM_SLEEP);
       last_pkg = -1;
       i = 0;
       for (CPU_INFO_FOREACH(cii, ci)) {
               if (ci->ci_package_id != last_pkg) {
                       sc->sc_scmi.sc_perf_dmap[i].pm_domain = i;
                       sc->sc_scmi.sc_perf_dmap[i].pm_ci = ci;
                       last_pkg = ci->ci_package_id;
                       i++;
               }
       }
       if (scmi_init_mbox(&sc->sc_scmi) != 0) {
               aprint_error_dev(self, "couldn't setup SCMI\n");
               return;
       }
       scmi_attach_perf(&sc->sc_scmi);
}

int
qcaoss_send(char *data, size_t len)
{
       struct qcpep_softc *sc = qcpep_sc;
       uint32_t reg;
       int i;

       if (sc == NULL)
               return ENXIO;

       if (data == NULL || sizeof(uint32_t) + len > sc->sc_aoss_size ||
           (len % sizeof(uint32_t)) != 0)
               return EINVAL;

       /* Write data first, needs to be 32-bit access. */
       for (i = 0; i < len; i += 4) {
               memcpy(&reg, data + i, sizeof(reg));
               AOSSWRITE4(sc, sc->sc_aoss_offset + sizeof(uint32_t) + i, reg);
       }

       /* Commit transaction by writing length. */
       AOSSWRITE4(sc, sc->sc_aoss_offset, len);

       /* Assert it's stored and inform peer. */
       if (AOSSREAD4(sc, sc->sc_aoss_offset) != len) {
               device_printf(sc->sc_dev,
                   "aoss message readback failed\n");
       }
       qcipcc_send(sc->sc_aoss_ipcc);

       for (i = 1000; i > 0; i--) {
               if (AOSSREAD4(sc, sc->sc_aoss_offset) == 0)
                       break;
               delay(1000);
       }
       if (i == 0) {
               device_printf(sc->sc_dev, "timeout sending message\n");
               AOSSWRITE4(sc, sc->sc_aoss_offset, 0);
               return ETIMEDOUT;
       }

       return 0;
}

void *
qccpucp_channel(u_int id)
{
       struct qcpep_softc *sc = qcpep_sc;
       uint64_t val;

       if (sc == NULL || id > 2) {
               return NULL;
       }

       val = RXREAD8(sc, CPUCP_REG_RX_EN);
       val |= (1 << id);
       RXWRITE8(sc, CPUCP_REG_RX_EN, val);

       return (void *)(uintptr_t)(id + 1);
}

int
qccpucp_send(void *cookie)
{
       struct qcpep_softc *sc = qcpep_sc;
       uintptr_t id = (uintptr_t)cookie - 1;

       TXWRITE4(sc, CPUCP_REG_CMD(id), 0);

       return 0;
}