// 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 #include #include #include #include #include #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(sizeof(*ctx), GFP_KERNEL); if (!ctx) return NULL; ctx->count = count; ctx->link_mask = res->link_mask; struct resource *sdw_res __free(kfree) = kzalloc(sizeof(*sdw_res), GFP_KERNEL); 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; 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(struct_size(ctx->peripherals, array, num_slaves), GFP_KERNEL); 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");