root/sys/dev/acpi/qcompep.c
/* $NetBSD: qcompep.c,v 1.3 2026/05/23 06:04:46 andvar 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 <patrick@blueri.se>
 * Copyright (c) 2024 Tobias Heider <tobhe@openbsd.org>
 *
 * 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;
}