| /* | 
 |  * Intel SST Firmware Loader | 
 |  * | 
 |  * Copyright (C) 2013, Intel Corporation. All rights reserved. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU General Public License version | 
 |  * 2 as published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/sched.h> | 
 | #include <linux/firmware.h> | 
 | #include <linux/export.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/dma-mapping.h> | 
 | #include <linux/dmaengine.h> | 
 | #include <linux/pci.h> | 
 | #include <linux/acpi.h> | 
 |  | 
 | /* supported DMA engine drivers */ | 
 | #include <linux/dma/dw.h> | 
 |  | 
 | #include <asm/page.h> | 
 | #include <asm/pgtable.h> | 
 |  | 
 | #include "sst-dsp.h" | 
 | #include "sst-dsp-priv.h" | 
 |  | 
 | #define SST_DMA_RESOURCES	2 | 
 | #define SST_DSP_DMA_MAX_BURST	0x3 | 
 | #define SST_HSW_BLOCK_ANY	0xffffffff | 
 |  | 
 | #define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000 | 
 |  | 
 | struct sst_dma { | 
 | 	struct sst_dsp *sst; | 
 |  | 
 | 	struct dw_dma_chip *chip; | 
 |  | 
 | 	struct dma_async_tx_descriptor *desc; | 
 | 	struct dma_chan *ch; | 
 | }; | 
 |  | 
 | static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) | 
 | { | 
 | 	u32 tmp = 0; | 
 | 	int i, m, n; | 
 | 	const u8 *src_byte = src; | 
 |  | 
 | 	m = bytes / 4; | 
 | 	n = bytes % 4; | 
 |  | 
 | 	/* __iowrite32_copy use 32bit size values so divide by 4 */ | 
 | 	__iowrite32_copy((void *)dest, src, m); | 
 |  | 
 | 	if (n) { | 
 | 		for (i = 0; i < n; i++) | 
 | 			tmp |= (u32)*(src_byte + m * 4 + i) << (i * 8); | 
 | 		__iowrite32_copy((void *)(dest + m * 4), &tmp, 1); | 
 | 	} | 
 |  | 
 | } | 
 |  | 
 | static void sst_dma_transfer_complete(void *arg) | 
 | { | 
 | 	struct sst_dsp *sst = (struct sst_dsp *)arg; | 
 |  | 
 | 	dev_dbg(sst->dev, "DMA: callback\n"); | 
 | } | 
 |  | 
 | static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr, | 
 | 	dma_addr_t src_addr, size_t size) | 
 | { | 
 | 	struct dma_async_tx_descriptor *desc; | 
 | 	struct sst_dma *dma = sst->dma; | 
 |  | 
 | 	if (dma->ch == NULL) { | 
 | 		dev_err(sst->dev, "error: no DMA channel\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n", | 
 | 		(unsigned long)src_addr, (unsigned long)dest_addr, size); | 
 |  | 
 | 	desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr, | 
 | 		src_addr, size, DMA_CTRL_ACK); | 
 | 	if (!desc){ | 
 | 		dev_err(sst->dev, "error: dma prep memcpy failed\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	desc->callback = sst_dma_transfer_complete; | 
 | 	desc->callback_param = sst; | 
 |  | 
 | 	desc->tx_submit(desc); | 
 | 	dma_wait_for_async_tx(desc); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* copy to DSP */ | 
 | int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, | 
 | 	dma_addr_t src_addr, size_t size) | 
 | { | 
 | 	return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP, | 
 | 			src_addr, size); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto); | 
 |  | 
 | /* copy from DSP */ | 
 | int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, | 
 | 	dma_addr_t src_addr, size_t size) | 
 | { | 
 | 	return sst_dsp_dma_copy(sst, dest_addr, | 
 | 		src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom); | 
 |  | 
 | /* remove module from memory - callers hold locks */ | 
 | static void block_list_remove(struct sst_dsp *dsp, | 
 | 	struct list_head *block_list) | 
 | { | 
 | 	struct sst_mem_block *block, *tmp; | 
 | 	int err; | 
 |  | 
 | 	/* disable each block  */ | 
 | 	list_for_each_entry(block, block_list, module_list) { | 
 |  | 
 | 		if (block->ops && block->ops->disable) { | 
 | 			err = block->ops->disable(block); | 
 | 			if (err < 0) | 
 | 				dev_err(dsp->dev, | 
 | 					"error: cant disable block %d:%d\n", | 
 | 					block->type, block->index); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* mark each block as free */ | 
 | 	list_for_each_entry_safe(block, tmp, block_list, module_list) { | 
 | 		list_del(&block->module_list); | 
 | 		list_move(&block->list, &dsp->free_block_list); | 
 | 		dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n", | 
 | 			block->type, block->index, block->offset); | 
 | 	} | 
 | } | 
 |  | 
 | /* prepare the memory block to receive data from host - callers hold locks */ | 
 | static int block_list_prepare(struct sst_dsp *dsp, | 
 | 	struct list_head *block_list) | 
 | { | 
 | 	struct sst_mem_block *block; | 
 | 	int ret = 0; | 
 |  | 
 | 	/* enable each block so that's it'e ready for data */ | 
 | 	list_for_each_entry(block, block_list, module_list) { | 
 |  | 
 | 		if (block->ops && block->ops->enable && !block->users) { | 
 | 			ret = block->ops->enable(block); | 
 | 			if (ret < 0) { | 
 | 				dev_err(dsp->dev, | 
 | 					"error: cant disable block %d:%d\n", | 
 | 					block->type, block->index); | 
 | 				goto err; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return ret; | 
 |  | 
 | err: | 
 | 	list_for_each_entry(block, block_list, module_list) { | 
 | 		if (block->ops && block->ops->disable) | 
 | 			block->ops->disable(block); | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem, | 
 | 	int irq) | 
 | { | 
 | 	struct dw_dma_chip *chip; | 
 | 	int err; | 
 |  | 
 | 	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); | 
 | 	if (!chip) | 
 | 		return ERR_PTR(-ENOMEM); | 
 |  | 
 | 	chip->irq = irq; | 
 | 	chip->regs = devm_ioremap_resource(dev, mem); | 
 | 	if (IS_ERR(chip->regs)) | 
 | 		return ERR_CAST(chip->regs); | 
 |  | 
 | 	err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); | 
 | 	if (err) | 
 | 		return ERR_PTR(err); | 
 |  | 
 | 	chip->dev = dev; | 
 |  | 
 | 	err = dw_dma_probe(chip); | 
 | 	if (err) | 
 | 		return ERR_PTR(err); | 
 |  | 
 | 	return chip; | 
 | } | 
 |  | 
 | static void dw_remove(struct dw_dma_chip *chip) | 
 | { | 
 | 	dw_dma_remove(chip); | 
 | } | 
 |  | 
 | static bool dma_chan_filter(struct dma_chan *chan, void *param) | 
 | { | 
 | 	struct sst_dsp *dsp = (struct sst_dsp *)param; | 
 |  | 
 | 	return chan->device->dev == dsp->dma_dev; | 
 | } | 
 |  | 
 | int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id) | 
 | { | 
 | 	struct sst_dma *dma = dsp->dma; | 
 | 	struct dma_slave_config slave; | 
 | 	dma_cap_mask_t mask; | 
 | 	int ret; | 
 |  | 
 | 	dma_cap_zero(mask); | 
 | 	dma_cap_set(DMA_SLAVE, mask); | 
 | 	dma_cap_set(DMA_MEMCPY, mask); | 
 |  | 
 | 	dma->ch = dma_request_channel(mask, dma_chan_filter, dsp); | 
 | 	if (dma->ch == NULL) { | 
 | 		dev_err(dsp->dev, "error: DMA request channel failed\n"); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	memset(&slave, 0, sizeof(slave)); | 
 | 	slave.direction = DMA_MEM_TO_DEV; | 
 | 	slave.src_addr_width = | 
 | 		slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; | 
 | 	slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST; | 
 |  | 
 | 	ret = dmaengine_slave_config(dma->ch, &slave); | 
 | 	if (ret) { | 
 | 		dev_err(dsp->dev, "error: unable to set DMA slave config %d\n", | 
 | 			ret); | 
 | 		dma_release_channel(dma->ch); | 
 | 		dma->ch = NULL; | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel); | 
 |  | 
 | void sst_dsp_dma_put_channel(struct sst_dsp *dsp) | 
 | { | 
 | 	struct sst_dma *dma = dsp->dma; | 
 |  | 
 | 	if (!dma->ch) | 
 | 		return; | 
 |  | 
 | 	dma_release_channel(dma->ch); | 
 | 	dma->ch = NULL; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel); | 
 |  | 
 | int sst_dma_new(struct sst_dsp *sst) | 
 | { | 
 | 	struct sst_pdata *sst_pdata = sst->pdata; | 
 | 	struct sst_dma *dma; | 
 | 	struct resource mem; | 
 | 	const char *dma_dev_name; | 
 | 	int ret = 0; | 
 |  | 
 | 	if (sst->pdata->resindex_dma_base == -1) | 
 | 		/* DMA is not used, return and squelsh error messages */ | 
 | 		return 0; | 
 |  | 
 | 	/* configure the correct platform data for whatever DMA engine | 
 | 	* is attached to the ADSP IP. */ | 
 | 	switch (sst->pdata->dma_engine) { | 
 | 	case SST_DMA_TYPE_DW: | 
 | 		dma_dev_name = "dw_dmac"; | 
 | 		break; | 
 | 	default: | 
 | 		dev_err(sst->dev, "error: invalid DMA engine %d\n", | 
 | 			sst->pdata->dma_engine); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL); | 
 | 	if (!dma) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	dma->sst = sst; | 
 |  | 
 | 	memset(&mem, 0, sizeof(mem)); | 
 |  | 
 | 	mem.start = sst->addr.lpe_base + sst_pdata->dma_base; | 
 | 	mem.end   = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1; | 
 | 	mem.flags = IORESOURCE_MEM; | 
 |  | 
 | 	/* now register DMA engine device */ | 
 | 	dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq); | 
 | 	if (IS_ERR(dma->chip)) { | 
 | 		dev_err(sst->dev, "error: DMA device register failed\n"); | 
 | 		ret = PTR_ERR(dma->chip); | 
 | 		goto err_dma_dev; | 
 | 	} | 
 |  | 
 | 	sst->dma = dma; | 
 | 	sst->fw_use_dma = true; | 
 | 	return 0; | 
 |  | 
 | err_dma_dev: | 
 | 	devm_kfree(sst->dev, dma); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL(sst_dma_new); | 
 |  | 
 | void sst_dma_free(struct sst_dma *dma) | 
 | { | 
 |  | 
 | 	if (dma == NULL) | 
 | 		return; | 
 |  | 
 | 	if (dma->ch) | 
 | 		dma_release_channel(dma->ch); | 
 |  | 
 | 	if (dma->chip) | 
 | 		dw_remove(dma->chip); | 
 |  | 
 | } | 
 | EXPORT_SYMBOL(sst_dma_free); | 
 |  | 
 | /* create new generic firmware object */ | 
 | struct sst_fw *sst_fw_new(struct sst_dsp *dsp,  | 
 | 	const struct firmware *fw, void *private) | 
 | { | 
 | 	struct sst_fw *sst_fw; | 
 | 	int err; | 
 |  | 
 | 	if (!dsp->ops->parse_fw) | 
 | 		return NULL; | 
 |  | 
 | 	sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL); | 
 | 	if (sst_fw == NULL) | 
 | 		return NULL; | 
 |  | 
 | 	sst_fw->dsp = dsp; | 
 | 	sst_fw->private = private; | 
 | 	sst_fw->size = fw->size; | 
 |  | 
 | 	/* allocate DMA buffer to store FW data */ | 
 | 	sst_fw->dma_buf = dma_alloc_coherent(dsp->dma_dev, sst_fw->size, | 
 | 				&sst_fw->dmable_fw_paddr, GFP_DMA | GFP_KERNEL); | 
 | 	if (!sst_fw->dma_buf) { | 
 | 		dev_err(dsp->dev, "error: DMA alloc failed\n"); | 
 | 		kfree(sst_fw); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	/* copy FW data to DMA-able memory */ | 
 | 	memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); | 
 |  | 
 | 	if (dsp->fw_use_dma) { | 
 | 		err = sst_dsp_dma_get_channel(dsp, 0); | 
 | 		if (err < 0) | 
 | 			goto chan_err; | 
 | 	} | 
 |  | 
 | 	/* call core specific FW paser to load FW data into DSP */ | 
 | 	err = dsp->ops->parse_fw(sst_fw); | 
 | 	if (err < 0) { | 
 | 		dev_err(dsp->dev, "error: parse fw failed %d\n", err); | 
 | 		goto parse_err; | 
 | 	} | 
 |  | 
 | 	if (dsp->fw_use_dma) | 
 | 		sst_dsp_dma_put_channel(dsp); | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_add(&sst_fw->list, &dsp->fw_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 |  | 
 | 	return sst_fw; | 
 |  | 
 | parse_err: | 
 | 	if (dsp->fw_use_dma) | 
 | 		sst_dsp_dma_put_channel(dsp); | 
 | chan_err: | 
 | 	dma_free_coherent(dsp->dma_dev, sst_fw->size, | 
 | 				sst_fw->dma_buf, | 
 | 				sst_fw->dmable_fw_paddr); | 
 | 	sst_fw->dma_buf = NULL; | 
 | 	kfree(sst_fw); | 
 | 	return NULL; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_fw_new); | 
 |  | 
 | int sst_fw_reload(struct sst_fw *sst_fw) | 
 | { | 
 | 	struct sst_dsp *dsp = sst_fw->dsp; | 
 | 	int ret; | 
 |  | 
 | 	dev_dbg(dsp->dev, "reloading firmware\n"); | 
 |  | 
 | 	/* call core specific FW paser to load FW data into DSP */ | 
 | 	ret = dsp->ops->parse_fw(sst_fw); | 
 | 	if (ret < 0) | 
 | 		dev_err(dsp->dev, "error: parse fw failed %d\n", ret); | 
 |  | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_fw_reload); | 
 |  | 
 | void sst_fw_unload(struct sst_fw *sst_fw) | 
 | { | 
 | 	struct sst_dsp *dsp = sst_fw->dsp; | 
 | 	struct sst_module *module, *mtmp; | 
 | 	struct sst_module_runtime *runtime, *rtmp; | 
 |  | 
 | 	dev_dbg(dsp->dev, "unloading firmware\n"); | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	/* check module by module */ | 
 | 	list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) { | 
 | 		if (module->sst_fw == sst_fw) { | 
 |  | 
 | 			/* remove runtime modules */ | 
 | 			list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) { | 
 |  | 
 | 				block_list_remove(dsp, &runtime->block_list); | 
 | 				list_del(&runtime->list); | 
 | 				kfree(runtime); | 
 | 			} | 
 |  | 
 | 			/* now remove the module */ | 
 | 			block_list_remove(dsp, &module->block_list); | 
 | 			list_del(&module->list); | 
 | 			kfree(module); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* remove all scratch blocks */ | 
 | 	block_list_remove(dsp, &dsp->scratch_block_list); | 
 |  | 
 | 	mutex_unlock(&dsp->mutex); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_fw_unload); | 
 |  | 
 | /* free single firmware object */ | 
 | void sst_fw_free(struct sst_fw *sst_fw) | 
 | { | 
 | 	struct sst_dsp *dsp = sst_fw->dsp; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_del(&sst_fw->list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 |  | 
 | 	if (sst_fw->dma_buf) | 
 | 		dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, | 
 | 			sst_fw->dmable_fw_paddr); | 
 | 	kfree(sst_fw); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_fw_free); | 
 |  | 
 | /* free all firmware objects */ | 
 | void sst_fw_free_all(struct sst_dsp *dsp) | 
 | { | 
 | 	struct sst_fw *sst_fw, *t; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { | 
 |  | 
 | 		list_del(&sst_fw->list); | 
 | 		dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf, | 
 | 			sst_fw->dmable_fw_paddr); | 
 | 		kfree(sst_fw); | 
 | 	} | 
 | 	mutex_unlock(&dsp->mutex); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_fw_free_all); | 
 |  | 
 | /* create a new SST generic module from FW template */ | 
 | struct sst_module *sst_module_new(struct sst_fw *sst_fw, | 
 | 	struct sst_module_template *template, void *private) | 
 | { | 
 | 	struct sst_dsp *dsp = sst_fw->dsp; | 
 | 	struct sst_module *sst_module; | 
 |  | 
 | 	sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL); | 
 | 	if (sst_module == NULL) | 
 | 		return NULL; | 
 |  | 
 | 	sst_module->id = template->id; | 
 | 	sst_module->dsp = dsp; | 
 | 	sst_module->sst_fw = sst_fw; | 
 | 	sst_module->scratch_size = template->scratch_size; | 
 | 	sst_module->persistent_size = template->persistent_size; | 
 | 	sst_module->entry = template->entry; | 
 | 	sst_module->state = SST_MODULE_STATE_UNLOADED; | 
 |  | 
 | 	INIT_LIST_HEAD(&sst_module->block_list); | 
 | 	INIT_LIST_HEAD(&sst_module->runtime_list); | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_add(&sst_module->list, &dsp->module_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 |  | 
 | 	return sst_module; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_new); | 
 |  | 
 | /* free firmware module and remove from available list */ | 
 | void sst_module_free(struct sst_module *sst_module) | 
 | { | 
 | 	struct sst_dsp *dsp = sst_module->dsp; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_del(&sst_module->list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 |  | 
 | 	kfree(sst_module); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_free); | 
 |  | 
 | struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, | 
 | 	int id, void *private) | 
 | { | 
 | 	struct sst_dsp *dsp = module->dsp; | 
 | 	struct sst_module_runtime *runtime; | 
 |  | 
 | 	runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); | 
 | 	if (runtime == NULL) | 
 | 		return NULL; | 
 |  | 
 | 	runtime->id = id; | 
 | 	runtime->dsp = dsp; | 
 | 	runtime->module = module; | 
 | 	INIT_LIST_HEAD(&runtime->block_list); | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_add(&runtime->list, &module->runtime_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 |  | 
 | 	return runtime; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_runtime_new); | 
 |  | 
 | void sst_module_runtime_free(struct sst_module_runtime *runtime) | 
 | { | 
 | 	struct sst_dsp *dsp = runtime->dsp; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_del(&runtime->list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 |  | 
 | 	kfree(runtime); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_runtime_free); | 
 |  | 
 | static struct sst_mem_block *find_block(struct sst_dsp *dsp, | 
 | 	struct sst_block_allocator *ba) | 
 | { | 
 | 	struct sst_mem_block *block; | 
 |  | 
 | 	list_for_each_entry(block, &dsp->free_block_list, list) { | 
 | 		if (block->type == ba->type && block->offset == ba->offset) | 
 | 			return block; | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | /* Block allocator must be on block boundary */ | 
 | static int block_alloc_contiguous(struct sst_dsp *dsp, | 
 | 	struct sst_block_allocator *ba, struct list_head *block_list) | 
 | { | 
 | 	struct list_head tmp = LIST_HEAD_INIT(tmp); | 
 | 	struct sst_mem_block *block; | 
 | 	u32 block_start = SST_HSW_BLOCK_ANY; | 
 | 	int size = ba->size, offset = ba->offset; | 
 |  | 
 | 	while (ba->size > 0) { | 
 |  | 
 | 		block = find_block(dsp, ba); | 
 | 		if (!block) { | 
 | 			list_splice(&tmp, &dsp->free_block_list); | 
 |  | 
 | 			ba->size = size; | 
 | 			ba->offset = offset; | 
 | 			return -ENOMEM; | 
 | 		} | 
 |  | 
 | 		list_move_tail(&block->list, &tmp); | 
 | 		ba->offset += block->size; | 
 | 		ba->size -= block->size; | 
 | 	} | 
 | 	ba->size = size; | 
 | 	ba->offset = offset; | 
 |  | 
 | 	list_for_each_entry(block, &tmp, list) { | 
 |  | 
 | 		if (block->offset < block_start) | 
 | 			block_start = block->offset; | 
 |  | 
 | 		list_add(&block->module_list, block_list); | 
 |  | 
 | 		dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", | 
 | 			block->type, block->index, block->offset); | 
 | 	} | 
 |  | 
 | 	list_splice(&tmp, &dsp->used_block_list); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* allocate first free DSP blocks for data - callers hold locks */ | 
 | static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba, | 
 | 	struct list_head *block_list) | 
 | { | 
 | 	struct sst_mem_block *block, *tmp; | 
 | 	int ret = 0; | 
 |  | 
 | 	if (ba->size == 0) | 
 | 		return 0; | 
 |  | 
 | 	/* find first free whole blocks that can hold module */ | 
 | 	list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { | 
 |  | 
 | 		/* ignore blocks with wrong type */ | 
 | 		if (block->type != ba->type) | 
 | 			continue; | 
 |  | 
 | 		if (ba->size > block->size) | 
 | 			continue; | 
 |  | 
 | 		ba->offset = block->offset; | 
 | 		block->bytes_used = ba->size % block->size; | 
 | 		list_add(&block->module_list, block_list); | 
 | 		list_move(&block->list, &dsp->used_block_list); | 
 | 		dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", | 
 | 			block->type, block->index, block->offset); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* then find free multiple blocks that can hold module */ | 
 | 	list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { | 
 |  | 
 | 		/* ignore blocks with wrong type */ | 
 | 		if (block->type != ba->type) | 
 | 			continue; | 
 |  | 
 | 		/* do we span > 1 blocks */ | 
 | 		if (ba->size > block->size) { | 
 |  | 
 | 			/* align ba to block boundary */ | 
 | 			ba->offset = block->offset; | 
 |  | 
 | 			ret = block_alloc_contiguous(dsp, ba, block_list); | 
 | 			if (ret == 0) | 
 | 				return ret; | 
 |  | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* not enough free block space */ | 
 | 	return -ENOMEM; | 
 | } | 
 |  | 
 | int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, | 
 | 	struct list_head *block_list) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", | 
 | 		ba->size, ba->offset, ba->type); | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	ret = block_alloc(dsp, ba, block_list); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* prepare DSP blocks for module usage */ | 
 | 	ret = block_list_prepare(dsp, block_list); | 
 | 	if (ret < 0) | 
 | 		dev_err(dsp->dev, "error: prepare failed\n"); | 
 |  | 
 | out: | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_alloc_blocks); | 
 |  | 
 | int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list) | 
 | { | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	block_list_remove(dsp, block_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_free_blocks); | 
 |  | 
 | /* allocate memory blocks for static module addresses - callers hold locks */ | 
 | static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba, | 
 | 	struct list_head *block_list) | 
 | { | 
 | 	struct sst_mem_block *block, *tmp; | 
 | 	struct sst_block_allocator ba_tmp = *ba; | 
 | 	u32 end = ba->offset + ba->size, block_end; | 
 | 	int err; | 
 |  | 
 | 	/* only IRAM/DRAM blocks are managed */ | 
 | 	if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM) | 
 | 		return 0; | 
 |  | 
 | 	/* are blocks already attached to this module */ | 
 | 	list_for_each_entry_safe(block, tmp, block_list, module_list) { | 
 |  | 
 | 		/* ignore blocks with wrong type */ | 
 | 		if (block->type != ba->type) | 
 | 			continue; | 
 |  | 
 | 		block_end = block->offset + block->size; | 
 |  | 
 | 		/* find block that holds section */ | 
 | 		if (ba->offset >= block->offset && end <= block_end) | 
 | 			return 0; | 
 |  | 
 | 		/* does block span more than 1 section */ | 
 | 		if (ba->offset >= block->offset && ba->offset < block_end) { | 
 |  | 
 | 			/* align ba to block boundary */ | 
 | 			ba_tmp.size -= block_end - ba->offset; | 
 | 			ba_tmp.offset = block_end; | 
 | 			err = block_alloc_contiguous(dsp, &ba_tmp, block_list); | 
 | 			if (err < 0) | 
 | 				return -ENOMEM; | 
 |  | 
 | 			/* module already owns blocks */ | 
 | 			return 0; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* find first free blocks that can hold section in free list */ | 
 | 	list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { | 
 | 		block_end = block->offset + block->size; | 
 |  | 
 | 		/* ignore blocks with wrong type */ | 
 | 		if (block->type != ba->type) | 
 | 			continue; | 
 |  | 
 | 		/* find block that holds section */ | 
 | 		if (ba->offset >= block->offset && end <= block_end) { | 
 |  | 
 | 			/* add block */ | 
 | 			list_move(&block->list, &dsp->used_block_list); | 
 | 			list_add(&block->module_list, block_list); | 
 | 			dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", | 
 | 				block->type, block->index, block->offset); | 
 | 			return 0; | 
 | 		} | 
 |  | 
 | 		/* does block span more than 1 section */ | 
 | 		if (ba->offset >= block->offset && ba->offset < block_end) { | 
 |  | 
 | 			/* add block */ | 
 | 			list_move(&block->list, &dsp->used_block_list); | 
 | 			list_add(&block->module_list, block_list); | 
 | 			/* align ba to block boundary */ | 
 | 			ba_tmp.size -= block_end - ba->offset; | 
 | 			ba_tmp.offset = block_end; | 
 |  | 
 | 			err = block_alloc_contiguous(dsp, &ba_tmp, block_list); | 
 | 			if (err < 0) | 
 | 				return -ENOMEM; | 
 |  | 
 | 			return 0; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return -ENOMEM; | 
 | } | 
 |  | 
 | /* Load fixed module data into DSP memory blocks */ | 
 | int sst_module_alloc_blocks(struct sst_module *module) | 
 | { | 
 | 	struct sst_dsp *dsp = module->dsp; | 
 | 	struct sst_fw *sst_fw = module->sst_fw; | 
 | 	struct sst_block_allocator ba; | 
 | 	int ret; | 
 |  | 
 | 	memset(&ba, 0, sizeof(ba)); | 
 | 	ba.size = module->size; | 
 | 	ba.type = module->type; | 
 | 	ba.offset = module->offset; | 
 |  | 
 | 	dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", | 
 | 		ba.size, ba.offset, ba.type); | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	/* alloc blocks that includes this section */ | 
 | 	ret = block_alloc_fixed(dsp, &ba, &module->block_list); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dsp->dev, | 
 | 			"error: no free blocks for section at offset 0x%x size 0x%x\n", | 
 | 			module->offset, module->size); | 
 | 		mutex_unlock(&dsp->mutex); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	/* prepare DSP blocks for module copy */ | 
 | 	ret = block_list_prepare(dsp, &module->block_list); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dsp->dev, "error: fw module prepare failed\n"); | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	/* copy partial module data to blocks */ | 
 | 	if (dsp->fw_use_dma) { | 
 | 		ret = sst_dsp_dma_copyto(dsp, | 
 | 			dsp->addr.lpe_base + module->offset, | 
 | 			sst_fw->dmable_fw_paddr + module->data_offset, | 
 | 			module->size); | 
 | 		if (ret < 0) { | 
 | 			dev_err(dsp->dev, "error: module copy failed\n"); | 
 | 			goto err; | 
 | 		} | 
 | 	} else | 
 | 		sst_memcpy32(dsp->addr.lpe + module->offset, module->data, | 
 | 			module->size); | 
 |  | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return ret; | 
 |  | 
 | err: | 
 | 	block_list_remove(dsp, &module->block_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_alloc_blocks); | 
 |  | 
 | /* Unload entire module from DSP memory */ | 
 | int sst_module_free_blocks(struct sst_module *module) | 
 | { | 
 | 	struct sst_dsp *dsp = module->dsp; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	block_list_remove(dsp, &module->block_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_free_blocks); | 
 |  | 
 | int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, | 
 | 	int offset) | 
 | { | 
 | 	struct sst_dsp *dsp = runtime->dsp; | 
 | 	struct sst_module *module = runtime->module; | 
 | 	struct sst_block_allocator ba; | 
 | 	int ret; | 
 |  | 
 | 	if (module->persistent_size == 0) | 
 | 		return 0; | 
 |  | 
 | 	memset(&ba, 0, sizeof(ba)); | 
 | 	ba.size = module->persistent_size; | 
 | 	ba.type = SST_MEM_DRAM; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	/* do we need to allocate at a fixed address ? */ | 
 | 	if (offset != 0) { | 
 |  | 
 | 		ba.offset = offset; | 
 |  | 
 | 		dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n", | 
 | 			ba.size, ba.type, ba.offset); | 
 |  | 
 | 		/* alloc blocks that includes this section */ | 
 | 		ret = block_alloc_fixed(dsp, &ba, &runtime->block_list); | 
 |  | 
 | 	} else { | 
 | 		dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n", | 
 | 			ba.size, ba.type); | 
 |  | 
 | 		/* alloc blocks that includes this section */ | 
 | 		ret = block_alloc(dsp, &ba, &runtime->block_list); | 
 | 	} | 
 | 	if (ret < 0) { | 
 | 		dev_err(dsp->dev, | 
 | 		"error: no free blocks for runtime module size 0x%x\n", | 
 | 			module->persistent_size); | 
 | 		mutex_unlock(&dsp->mutex); | 
 | 		return -ENOMEM; | 
 | 	} | 
 | 	runtime->persistent_offset = ba.offset; | 
 |  | 
 | 	/* prepare DSP blocks for module copy */ | 
 | 	ret = block_list_prepare(dsp, &runtime->block_list); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dsp->dev, "error: runtime block prepare failed\n"); | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return ret; | 
 |  | 
 | err: | 
 | 	block_list_remove(dsp, &module->block_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks); | 
 |  | 
 | int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime) | 
 | { | 
 | 	struct sst_dsp *dsp = runtime->dsp; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	block_list_remove(dsp, &runtime->block_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks); | 
 |  | 
 | int sst_module_runtime_save(struct sst_module_runtime *runtime, | 
 | 	struct sst_module_runtime_context *context) | 
 | { | 
 | 	struct sst_dsp *dsp = runtime->dsp; | 
 | 	struct sst_module *module = runtime->module; | 
 | 	int ret = 0; | 
 |  | 
 | 	dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n", | 
 | 		runtime->id, runtime->persistent_offset, | 
 | 		module->persistent_size); | 
 |  | 
 | 	context->buffer = dma_alloc_coherent(dsp->dma_dev, | 
 | 		module->persistent_size, | 
 | 		&context->dma_buffer, GFP_DMA | GFP_KERNEL); | 
 | 	if (!context->buffer) { | 
 | 		dev_err(dsp->dev, "error: DMA context alloc failed\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	if (dsp->fw_use_dma) { | 
 |  | 
 | 		ret = sst_dsp_dma_get_channel(dsp, 0); | 
 | 		if (ret < 0) | 
 | 			goto err; | 
 |  | 
 | 		ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer, | 
 | 			dsp->addr.lpe_base + runtime->persistent_offset, | 
 | 			module->persistent_size); | 
 | 		sst_dsp_dma_put_channel(dsp); | 
 | 		if (ret < 0) { | 
 | 			dev_err(dsp->dev, "error: context copy failed\n"); | 
 | 			goto err; | 
 | 		} | 
 | 	} else | 
 | 		sst_memcpy32(context->buffer, dsp->addr.lpe + | 
 | 			runtime->persistent_offset, | 
 | 			module->persistent_size); | 
 |  | 
 | err: | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_runtime_save); | 
 |  | 
 | int sst_module_runtime_restore(struct sst_module_runtime *runtime, | 
 | 	struct sst_module_runtime_context *context) | 
 | { | 
 | 	struct sst_dsp *dsp = runtime->dsp; | 
 | 	struct sst_module *module = runtime->module; | 
 | 	int ret = 0; | 
 |  | 
 | 	dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n", | 
 | 		runtime->id, runtime->persistent_offset, | 
 | 		module->persistent_size); | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	if (!context->buffer) { | 
 | 		dev_info(dsp->dev, "no context buffer need to restore!\n"); | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	if (dsp->fw_use_dma) { | 
 |  | 
 | 		ret = sst_dsp_dma_get_channel(dsp, 0); | 
 | 		if (ret < 0) | 
 | 			goto err; | 
 |  | 
 | 		ret = sst_dsp_dma_copyto(dsp, | 
 | 			dsp->addr.lpe_base + runtime->persistent_offset, | 
 | 			context->dma_buffer, module->persistent_size); | 
 | 		sst_dsp_dma_put_channel(dsp); | 
 | 		if (ret < 0) { | 
 | 			dev_err(dsp->dev, "error: module copy failed\n"); | 
 | 			goto err; | 
 | 		} | 
 | 	} else | 
 | 		sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset, | 
 | 			context->buffer, module->persistent_size); | 
 |  | 
 | 	dma_free_coherent(dsp->dma_dev, module->persistent_size, | 
 | 				context->buffer, context->dma_buffer); | 
 | 	context->buffer = NULL; | 
 |  | 
 | err: | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_runtime_restore); | 
 |  | 
 | /* register a DSP memory block for use with FW based modules */ | 
 | struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, | 
 | 	u32 size, enum sst_mem_type type, const struct sst_block_ops *ops, | 
 | 	u32 index, void *private) | 
 | { | 
 | 	struct sst_mem_block *block; | 
 |  | 
 | 	block = kzalloc(sizeof(*block), GFP_KERNEL); | 
 | 	if (block == NULL) | 
 | 		return NULL; | 
 |  | 
 | 	block->offset = offset; | 
 | 	block->size = size; | 
 | 	block->index = index; | 
 | 	block->type = type; | 
 | 	block->dsp = dsp; | 
 | 	block->private = private; | 
 | 	block->ops = ops; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	list_add(&block->list, &dsp->free_block_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 |  | 
 | 	return block; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_mem_block_register); | 
 |  | 
 | /* unregister all DSP memory blocks */ | 
 | void sst_mem_block_unregister_all(struct sst_dsp *dsp) | 
 | { | 
 | 	struct sst_mem_block *block, *tmp; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	/* unregister used blocks */ | 
 | 	list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) { | 
 | 		list_del(&block->list); | 
 | 		kfree(block); | 
 | 	} | 
 |  | 
 | 	/* unregister free blocks */ | 
 | 	list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { | 
 | 		list_del(&block->list); | 
 | 		kfree(block); | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&dsp->mutex); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); | 
 |  | 
 | /* allocate scratch buffer blocks */ | 
 | int sst_block_alloc_scratch(struct sst_dsp *dsp) | 
 | { | 
 | 	struct sst_module *module; | 
 | 	struct sst_block_allocator ba; | 
 | 	int ret; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	/* calculate required scratch size */ | 
 | 	dsp->scratch_size = 0; | 
 | 	list_for_each_entry(module, &dsp->module_list, list) { | 
 | 		dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n", | 
 | 			module->id, module->scratch_size); | 
 | 		if (dsp->scratch_size < module->scratch_size) | 
 | 			dsp->scratch_size = module->scratch_size; | 
 | 	} | 
 |  | 
 | 	dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n", | 
 | 		dsp->scratch_size); | 
 |  | 
 | 	if (dsp->scratch_size == 0) { | 
 | 		dev_info(dsp->dev, "no modules need scratch buffer\n"); | 
 | 		mutex_unlock(&dsp->mutex); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* allocate blocks for module scratch buffers */ | 
 | 	dev_dbg(dsp->dev, "allocating scratch blocks\n"); | 
 |  | 
 | 	ba.size = dsp->scratch_size; | 
 | 	ba.type = SST_MEM_DRAM; | 
 |  | 
 | 	/* do we need to allocate at fixed offset */ | 
 | 	if (dsp->scratch_offset != 0) { | 
 |  | 
 | 		dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n", | 
 | 			ba.size, ba.type, ba.offset); | 
 |  | 
 | 		ba.offset = dsp->scratch_offset; | 
 |  | 
 | 		/* alloc blocks that includes this section */ | 
 | 		ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list); | 
 |  | 
 | 	} else { | 
 | 		dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n", | 
 | 			ba.size, ba.type); | 
 |  | 
 | 		ba.offset = 0; | 
 | 		ret = block_alloc(dsp, &ba, &dsp->scratch_block_list); | 
 | 	} | 
 | 	if (ret < 0) { | 
 | 		dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); | 
 | 		mutex_unlock(&dsp->mutex); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	ret = block_list_prepare(dsp, &dsp->scratch_block_list); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dsp->dev, "error: scratch block prepare failed\n"); | 
 | 		mutex_unlock(&dsp->mutex); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	/* assign the same offset of scratch to each module */ | 
 | 	dsp->scratch_offset = ba.offset; | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return dsp->scratch_size; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_block_alloc_scratch); | 
 |  | 
 | /* free all scratch blocks */ | 
 | void sst_block_free_scratch(struct sst_dsp *dsp) | 
 | { | 
 | 	mutex_lock(&dsp->mutex); | 
 | 	block_list_remove(dsp, &dsp->scratch_block_list); | 
 | 	mutex_unlock(&dsp->mutex); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_block_free_scratch); | 
 |  | 
 | /* get a module from it's unique ID */ | 
 | struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) | 
 | { | 
 | 	struct sst_module *module; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	list_for_each_entry(module, &dsp->module_list, list) { | 
 | 		if (module->id == id) { | 
 | 			mutex_unlock(&dsp->mutex); | 
 | 			return module; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return NULL; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_get_from_id); | 
 |  | 
 | struct sst_module_runtime *sst_module_runtime_get_from_id( | 
 | 	struct sst_module *module, u32 id) | 
 | { | 
 | 	struct sst_module_runtime *runtime; | 
 | 	struct sst_dsp *dsp = module->dsp; | 
 |  | 
 | 	mutex_lock(&dsp->mutex); | 
 |  | 
 | 	list_for_each_entry(runtime, &module->runtime_list, list) { | 
 | 		if (runtime->id == id) { | 
 | 			mutex_unlock(&dsp->mutex); | 
 | 			return runtime; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&dsp->mutex); | 
 | 	return NULL; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id); | 
 |  | 
 | /* returns block address in DSP address space */ | 
 | u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, | 
 | 	enum sst_mem_type type) | 
 | { | 
 | 	switch (type) { | 
 | 	case SST_MEM_IRAM: | 
 | 		return offset - dsp->addr.iram_offset + | 
 | 			dsp->addr.dsp_iram_offset; | 
 | 	case SST_MEM_DRAM: | 
 | 		return offset - dsp->addr.dram_offset + | 
 | 			dsp->addr.dsp_dram_offset; | 
 | 	default: | 
 | 		return 0; | 
 | 	} | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_dsp_get_offset); | 
 |  | 
 | struct sst_dsp *sst_dsp_new(struct device *dev, | 
 | 	struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) | 
 | { | 
 | 	struct sst_dsp *sst; | 
 | 	int err; | 
 |  | 
 | 	dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); | 
 |  | 
 | 	sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); | 
 | 	if (sst == NULL) | 
 | 		return NULL; | 
 |  | 
 | 	spin_lock_init(&sst->spinlock); | 
 | 	mutex_init(&sst->mutex); | 
 | 	sst->dev = dev; | 
 | 	sst->dma_dev = pdata->dma_dev; | 
 | 	sst->thread_context = sst_dev->thread_context; | 
 | 	sst->sst_dev = sst_dev; | 
 | 	sst->id = pdata->id; | 
 | 	sst->irq = pdata->irq; | 
 | 	sst->ops = sst_dev->ops; | 
 | 	sst->pdata = pdata; | 
 | 	INIT_LIST_HEAD(&sst->used_block_list); | 
 | 	INIT_LIST_HEAD(&sst->free_block_list); | 
 | 	INIT_LIST_HEAD(&sst->module_list); | 
 | 	INIT_LIST_HEAD(&sst->fw_list); | 
 | 	INIT_LIST_HEAD(&sst->scratch_block_list); | 
 |  | 
 | 	/* Initialise SST Audio DSP */ | 
 | 	if (sst->ops->init) { | 
 | 		err = sst->ops->init(sst, pdata); | 
 | 		if (err < 0) | 
 | 			return NULL; | 
 | 	} | 
 |  | 
 | 	/* Register the ISR */ | 
 | 	err = request_threaded_irq(sst->irq, sst->ops->irq_handler, | 
 | 		sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); | 
 | 	if (err) | 
 | 		goto irq_err; | 
 |  | 
 | 	err = sst_dma_new(sst); | 
 | 	if (err) | 
 | 		dev_warn(dev, "sst_dma_new failed %d\n", err); | 
 |  | 
 | 	return sst; | 
 |  | 
 | irq_err: | 
 | 	if (sst->ops->free) | 
 | 		sst->ops->free(sst); | 
 |  | 
 | 	return NULL; | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_dsp_new); | 
 |  | 
 | void sst_dsp_free(struct sst_dsp *sst) | 
 | { | 
 | 	free_irq(sst->irq, sst); | 
 | 	if (sst->ops->free) | 
 | 		sst->ops->free(sst); | 
 |  | 
 | 	sst_dma_free(sst->dma); | 
 | } | 
 | EXPORT_SYMBOL_GPL(sst_dsp_free); | 
 |  | 
 | MODULE_DESCRIPTION("Intel SST Firmware Loader"); | 
 | MODULE_LICENSE("GPL v2"); |