229 lines · 5.6 KB · C
RawDownload
// 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");