| // 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(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; |
| 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(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"); |