root/drivers/soundwire/amd_init.c
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * SoundWire AMD Manager Initialize routines
 *
 * Initializes and creates SDW devices based on ACPI and Hardware values
 *
 * Copyright 2024 Advanced Micro Devices, Inc.
 */

#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include "amd_init.h"

#define ACP_PAD_PULLDOWN_CTRL                           0x0001448
#define ACP_SW_PAD_KEEPER_EN                            0x0001454
#define AMD_SDW0_PAD_CTRL_MASK                          0x60
#define AMD_SDW1_PAD_CTRL_MASK                          5
#define AMD_SDW_PAD_CTRL_MASK           (AMD_SDW0_PAD_CTRL_MASK | AMD_SDW1_PAD_CTRL_MASK)
#define AMD_SDW0_PAD_EN                                 1
#define AMD_SDW1_PAD_EN                                 0x10
#define AMD_SDW_PAD_EN                  (AMD_SDW0_PAD_EN | AMD_SDW1_PAD_EN)

static int amd_enable_sdw_pads(void __iomem *mmio, u32 link_mask, struct device *dev)
{
        u32 pad_keeper_en, pad_pulldown_ctrl_mask;

        switch (link_mask) {
        case 1:
                pad_keeper_en = AMD_SDW0_PAD_EN;
                pad_pulldown_ctrl_mask = AMD_SDW0_PAD_CTRL_MASK;
                break;
        case 2:
                pad_keeper_en = AMD_SDW1_PAD_EN;
                pad_pulldown_ctrl_mask = AMD_SDW1_PAD_CTRL_MASK;
                break;
        case 3:
                pad_keeper_en = AMD_SDW_PAD_EN;
                pad_pulldown_ctrl_mask = AMD_SDW_PAD_CTRL_MASK;
                break;
        default:
                dev_err(dev, "No SDW Links are enabled\n");
                return -ENODEV;
        }

        amd_updatel(mmio, ACP_SW_PAD_KEEPER_EN, pad_keeper_en, pad_keeper_en);
        amd_updatel(mmio, ACP_PAD_PULLDOWN_CTRL, pad_pulldown_ctrl_mask, 0);

        return 0;
}

static int sdw_amd_cleanup(struct sdw_amd_ctx *ctx)
{
        int i;

        for (i = 0; i < ctx->count; i++) {
                if (!(ctx->link_mask & BIT(i)))
                        continue;
                platform_device_unregister(ctx->pdev[i]);
        }

        return 0;
}

static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res)
{
        struct sdw_amd_ctx *ctx;
        struct acpi_device *adev;
        struct acp_sdw_pdata sdw_pdata[2];
        struct platform_device_info pdevinfo[2];
        u32 link_mask;
        int count, index;
        int ret;

        if (!res)
                return NULL;

        adev = acpi_fetch_acpi_dev(res->handle);
        if (!adev)
                return NULL;

        if (!res->count)
                return NULL;

        count = res->count;
        dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count);
        ret = amd_enable_sdw_pads(res->mmio_base, res->link_mask, res->parent);
        if (ret)
                return NULL;

        /*
         * we need to alloc/free memory manually and can't use devm:
         * this routine may be called from a workqueue, and not from
         * the parent .probe.
         * If devm_ was used, the memory might never be freed on errors.
         */
        ctx = kzalloc_obj(*ctx);
        if (!ctx)
                return NULL;

        ctx->count = count;
        ctx->link_mask = res->link_mask;
        struct resource *sdw_res __free(kfree) = kzalloc_obj(*sdw_res);
        if (!sdw_res) {
                kfree(ctx);
                return NULL;
        }
        sdw_res->flags = IORESOURCE_MEM;
        sdw_res->start = res->addr;
        sdw_res->end = res->addr + res->reg_range;
        memset(&pdevinfo, 0, sizeof(pdevinfo));
        link_mask = ctx->link_mask;
        for (index = 0; index < count; index++) {
                if (!(link_mask & BIT(index)))
                        continue;

                sdw_pdata[index].instance = index;
                sdw_pdata[index].acp_sdw_lock = res->acp_lock;
                sdw_pdata[index].acp_rev = res->acp_rev;
                pdevinfo[index].name = "amd_sdw_manager";
                pdevinfo[index].id = index;
                pdevinfo[index].parent = res->parent;
                pdevinfo[index].num_res = 1;
                pdevinfo[index].res = sdw_res;
                pdevinfo[index].data = &sdw_pdata[index];
                pdevinfo[index].size_data = sizeof(struct acp_sdw_pdata);
                pdevinfo[index].fwnode = acpi_fwnode_handle(adev);
                ctx->pdev[index] = platform_device_register_full(&pdevinfo[index]);
                if (IS_ERR(ctx->pdev[index]))
                        goto err;
        }
        return ctx;
err:
        while (index--) {
                if (!(link_mask & BIT(index)))
                        continue;

                platform_device_unregister(ctx->pdev[index]);
        }

        kfree(ctx);
        return NULL;
}

static int sdw_amd_startup(struct sdw_amd_ctx *ctx)
{
        struct amd_sdw_manager *amd_manager;
        int i, ret;

        /* Startup SDW Manager devices */
        for (i = 0; i < ctx->count; i++) {
                if (!(ctx->link_mask & BIT(i)))
                        continue;
                amd_manager = dev_get_drvdata(&ctx->pdev[i]->dev);
                ret = amd_sdw_manager_start(amd_manager);
                if (ret)
                        return ret;
        }

        return 0;
}

int sdw_amd_probe(struct sdw_amd_res *res, struct sdw_amd_ctx **sdw_ctx)
{
        *sdw_ctx = sdw_amd_probe_controller(res);
        if (!*sdw_ctx)
                return -ENODEV;

        return sdw_amd_startup(*sdw_ctx);
}
EXPORT_SYMBOL_NS(sdw_amd_probe, "SOUNDWIRE_AMD_INIT");

void sdw_amd_exit(struct sdw_amd_ctx *ctx)
{
        sdw_amd_cleanup(ctx);
        kfree(ctx->peripherals);
        kfree(ctx);
}
EXPORT_SYMBOL_NS(sdw_amd_exit, "SOUNDWIRE_AMD_INIT");

int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx)
{
        struct amd_sdw_manager *amd_manager;
        struct sdw_bus *bus;
        struct sdw_slave *slave;
        struct list_head *node;
        int index;
        int i = 0;
        int num_slaves = 0;

        for (index = 0; index < ctx->count; index++) {
                if (!(ctx->link_mask & BIT(index)))
                        continue;
                amd_manager = dev_get_drvdata(&ctx->pdev[index]->dev);
                if (!amd_manager)
                        return -ENODEV;
                bus = &amd_manager->bus;
                /* Calculate number of slaves */
                list_for_each(node, &bus->slaves)
                        num_slaves++;
        }

        ctx->peripherals = kmalloc_flex(*ctx->peripherals, array, num_slaves);
        if (!ctx->peripherals)
                return -ENOMEM;
        ctx->peripherals->num_peripherals = num_slaves;
        for (index = 0; index < ctx->count; index++) {
                if (!(ctx->link_mask & BIT(index)))
                        continue;
                amd_manager = dev_get_drvdata(&ctx->pdev[index]->dev);
                if (amd_manager) {
                        bus = &amd_manager->bus;
                        list_for_each_entry(slave, &bus->slaves, node) {
                                ctx->peripherals->array[i] = slave;
                                i++;
                        }
                }
        }
        return 0;
}
EXPORT_SYMBOL_NS(sdw_amd_get_slave_info, "SOUNDWIRE_AMD_INIT");

MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD SoundWire Init Library");
MODULE_LICENSE("Dual BSD/GPL");