231 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			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(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->ids);
 | |
| 	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->ids = kcalloc(num_slaves, sizeof(*ctx->ids), GFP_KERNEL);
 | |
| 	if (!ctx->ids)
 | |
| 		return -ENOMEM;
 | |
| 	ctx->num_slaves = 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->ids[i].id = slave->id;
 | |
| 				ctx->ids[i].link_id = bus->link_id;
 | |
| 				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");
 |