2011-03-22 16:34:40 -07:00
|
|
|
/*
|
|
|
|
|
* Load Analog Devices SigmaStudio firmware files
|
|
|
|
|
*
|
2014-11-19 18:29:05 +01:00
|
|
|
* Copyright 2009-2014 Analog Devices Inc.
|
2011-03-22 16:34:40 -07:00
|
|
|
*
|
|
|
|
|
* Licensed under the GPL-2 or later.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <linux/crc32.h>
|
|
|
|
|
#include <linux/firmware.h>
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
|
#include <linux/i2c.h>
|
2011-11-28 09:44:20 +01:00
|
|
|
#include <linux/regmap.h>
|
2011-07-25 17:13:21 -07:00
|
|
|
#include <linux/module.h>
|
2014-11-19 18:29:05 +01:00
|
|
|
#include <linux/slab.h>
|
|
|
|
|
|
|
|
|
|
#include <sound/soc.h>
|
2011-11-28 09:44:17 +01:00
|
|
|
|
|
|
|
|
#include "sigmadsp.h"
|
2011-03-22 16:34:40 -07:00
|
|
|
|
2011-11-28 09:44:19 +01:00
|
|
|
#define SIGMA_MAGIC "ADISIGM"
|
|
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
struct sigmadsp_data {
|
|
|
|
|
struct list_head head;
|
|
|
|
|
unsigned int addr;
|
|
|
|
|
unsigned int length;
|
|
|
|
|
uint8_t data[];
|
|
|
|
|
};
|
|
|
|
|
|
2011-11-28 09:44:19 +01:00
|
|
|
struct sigma_firmware_header {
|
|
|
|
|
unsigned char magic[7];
|
|
|
|
|
u8 version;
|
|
|
|
|
__le32 crc;
|
|
|
|
|
} __packed;
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
SIGMA_ACTION_WRITEXBYTES = 0,
|
|
|
|
|
SIGMA_ACTION_WRITESINGLE,
|
|
|
|
|
SIGMA_ACTION_WRITESAFELOAD,
|
|
|
|
|
SIGMA_ACTION_END,
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
struct sigma_action {
|
|
|
|
|
u8 instr;
|
|
|
|
|
u8 len_hi;
|
|
|
|
|
__le16 len;
|
|
|
|
|
__be16 addr;
|
|
|
|
|
unsigned char payload[];
|
|
|
|
|
} __packed;
|
|
|
|
|
|
|
|
|
|
static int sigmadsp_write(struct sigmadsp *sigmadsp, unsigned int addr,
|
|
|
|
|
const uint8_t data[], size_t len)
|
|
|
|
|
{
|
|
|
|
|
return sigmadsp->write(sigmadsp->control_data, addr, data, len);
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-28 09:44:19 +01:00
|
|
|
static inline u32 sigma_action_len(struct sigma_action *sa)
|
|
|
|
|
{
|
|
|
|
|
return (sa->len_hi << 16) | le16_to_cpu(sa->len);
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-28 09:44:14 +01:00
|
|
|
static size_t sigma_action_size(struct sigma_action *sa)
|
|
|
|
|
{
|
|
|
|
|
size_t payload = 0;
|
|
|
|
|
|
|
|
|
|
switch (sa->instr) {
|
|
|
|
|
case SIGMA_ACTION_WRITEXBYTES:
|
|
|
|
|
case SIGMA_ACTION_WRITESINGLE:
|
|
|
|
|
case SIGMA_ACTION_WRITESAFELOAD:
|
|
|
|
|
payload = sigma_action_len(sa);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
payload = ALIGN(payload, 2);
|
|
|
|
|
|
|
|
|
|
return payload + sizeof(struct sigma_action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Returns a negative error value in case of an error, 0 if processing of
|
|
|
|
|
* the firmware should be stopped after this action, 1 otherwise.
|
|
|
|
|
*/
|
2014-11-19 18:29:05 +01:00
|
|
|
static int process_sigma_action(struct sigmadsp *sigmadsp,
|
|
|
|
|
struct sigma_action *sa)
|
2011-03-22 16:34:40 -07:00
|
|
|
{
|
|
|
|
|
size_t len = sigma_action_len(sa);
|
2014-11-19 18:29:05 +01:00
|
|
|
struct sigmadsp_data *data;
|
2011-03-22 16:34:40 -07:00
|
|
|
|
|
|
|
|
pr_debug("%s: instr:%i addr:%#x len:%zu\n", __func__,
|
|
|
|
|
sa->instr, sa->addr, len);
|
|
|
|
|
|
|
|
|
|
switch (sa->instr) {
|
|
|
|
|
case SIGMA_ACTION_WRITEXBYTES:
|
|
|
|
|
case SIGMA_ACTION_WRITESINGLE:
|
|
|
|
|
case SIGMA_ACTION_WRITESAFELOAD:
|
2014-11-19 18:29:05 +01:00
|
|
|
if (len < 3)
|
2011-03-22 16:34:40 -07:00
|
|
|
return -EINVAL;
|
2014-11-19 18:29:05 +01:00
|
|
|
|
|
|
|
|
data = kzalloc(sizeof(*data) + len - 2, GFP_KERNEL);
|
|
|
|
|
if (!data)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
data->addr = be16_to_cpu(sa->addr);
|
|
|
|
|
data->length = len - 2;
|
|
|
|
|
memcpy(data->data, sa->payload, data->length);
|
|
|
|
|
list_add_tail(&data->head, &sigmadsp->data_list);
|
2011-03-22 16:34:40 -07:00
|
|
|
break;
|
|
|
|
|
case SIGMA_ACTION_END:
|
2011-11-28 09:44:14 +01:00
|
|
|
return 0;
|
2011-03-22 16:34:40 -07:00
|
|
|
default:
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-28 09:44:14 +01:00
|
|
|
return 1;
|
2011-03-22 16:34:40 -07:00
|
|
|
}
|
|
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
static int sigmadsp_fw_load_v1(struct sigmadsp *sigmadsp,
|
|
|
|
|
const struct firmware *fw)
|
2011-03-22 16:34:40 -07:00
|
|
|
{
|
2011-11-28 09:44:14 +01:00
|
|
|
struct sigma_action *sa;
|
2014-11-19 18:29:05 +01:00
|
|
|
size_t size, pos;
|
2011-11-28 09:44:14 +01:00
|
|
|
int ret;
|
|
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
pos = sizeof(struct sigma_firmware_header);
|
|
|
|
|
|
|
|
|
|
while (pos + sizeof(*sa) <= fw->size) {
|
|
|
|
|
sa = (struct sigma_action *)(fw->data + pos);
|
2011-11-28 09:44:14 +01:00
|
|
|
|
|
|
|
|
size = sigma_action_size(sa);
|
2014-11-19 18:29:05 +01:00
|
|
|
pos += size;
|
|
|
|
|
if (pos > fw->size || size == 0)
|
2011-11-28 09:44:14 +01:00
|
|
|
break;
|
|
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
ret = process_sigma_action(sigmadsp, sa);
|
2011-03-22 16:34:40 -07:00
|
|
|
|
|
|
|
|
pr_debug("%s: action returned %i\n", __func__, ret);
|
2011-11-28 09:44:14 +01:00
|
|
|
|
|
|
|
|
if (ret <= 0)
|
2011-03-22 16:34:40 -07:00
|
|
|
return ret;
|
|
|
|
|
}
|
2011-11-28 09:44:14 +01:00
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
if (pos != fw->size)
|
2011-11-28 09:44:14 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
return 0;
|
2011-03-22 16:34:40 -07:00
|
|
|
}
|
|
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
static void sigmadsp_firmware_release(struct sigmadsp *sigmadsp)
|
2011-03-22 16:34:40 -07:00
|
|
|
{
|
2014-11-19 18:29:05 +01:00
|
|
|
struct sigmadsp_data *data, *_data;
|
|
|
|
|
|
|
|
|
|
list_for_each_entry_safe(data, _data, &sigmadsp->data_list, head)
|
|
|
|
|
kfree(data);
|
|
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&sigmadsp->data_list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void devm_sigmadsp_release(struct device *dev, void *res)
|
|
|
|
|
{
|
|
|
|
|
sigmadsp_firmware_release((struct sigmadsp *)res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name)
|
|
|
|
|
{
|
|
|
|
|
const struct sigma_firmware_header *ssfw_head;
|
2011-03-22 16:34:40 -07:00
|
|
|
const struct firmware *fw;
|
2014-11-19 18:29:05 +01:00
|
|
|
int ret;
|
2011-03-22 16:34:40 -07:00
|
|
|
u32 crc;
|
|
|
|
|
|
|
|
|
|
/* first load the blob */
|
2014-11-19 18:29:05 +01:00
|
|
|
ret = request_firmware(&fw, name, sigmadsp->dev);
|
2011-03-22 16:34:40 -07:00
|
|
|
if (ret) {
|
|
|
|
|
pr_debug("%s: request_firmware() failed with %i\n", __func__, ret);
|
2014-11-19 18:29:05 +01:00
|
|
|
goto done;
|
2011-03-22 16:34:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* then verify the header */
|
|
|
|
|
ret = -EINVAL;
|
2011-11-28 09:44:14 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Reject too small or unreasonable large files. The upper limit has been
|
|
|
|
|
* chosen a bit arbitrarily, but it should be enough for all practical
|
|
|
|
|
* purposes and having the limit makes it easier to avoid integer
|
|
|
|
|
* overflows later in the loading process.
|
|
|
|
|
*/
|
2011-11-28 09:44:18 +01:00
|
|
|
if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) {
|
2014-11-19 18:29:05 +01:00
|
|
|
dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n");
|
2011-03-22 16:34:40 -07:00
|
|
|
goto done;
|
2011-11-28 09:44:18 +01:00
|
|
|
}
|
2011-03-22 16:34:40 -07:00
|
|
|
|
|
|
|
|
ssfw_head = (void *)fw->data;
|
2011-11-28 09:44:18 +01:00
|
|
|
if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) {
|
2014-11-19 18:29:05 +01:00
|
|
|
dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n");
|
2014-11-19 18:29:02 +01:00
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-28 09:44:15 +01:00
|
|
|
crc = crc32(0, fw->data + sizeof(*ssfw_head),
|
|
|
|
|
fw->size - sizeof(*ssfw_head));
|
2011-03-22 16:34:40 -07:00
|
|
|
pr_debug("%s: crc=%x\n", __func__, crc);
|
2011-11-28 09:44:18 +01:00
|
|
|
if (crc != le32_to_cpu(ssfw_head->crc)) {
|
2014-11-19 18:29:05 +01:00
|
|
|
dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n",
|
2011-11-28 09:44:18 +01:00
|
|
|
le32_to_cpu(ssfw_head->crc), crc);
|
2011-03-22 16:34:40 -07:00
|
|
|
goto done;
|
2011-11-28 09:44:18 +01:00
|
|
|
}
|
2011-03-22 16:34:40 -07:00
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
switch (ssfw_head->version) {
|
|
|
|
|
case 1:
|
|
|
|
|
ret = sigmadsp_fw_load_v1(sigmadsp, fw);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
dev_err(sigmadsp->dev,
|
|
|
|
|
"Failed to load firmware: Invalid version %d. Supported firmware versions: 1\n",
|
|
|
|
|
ssfw_head->version);
|
|
|
|
|
ret = -EINVAL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2011-03-22 16:34:40 -07:00
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
if (ret)
|
|
|
|
|
sigmadsp_firmware_release(sigmadsp);
|
2011-03-22 16:34:40 -07:00
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
done:
|
2011-03-22 16:34:40 -07:00
|
|
|
release_firmware(fw);
|
|
|
|
|
|
2014-11-19 18:29:05 +01:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int sigmadsp_init(struct sigmadsp *sigmadsp, struct device *dev,
|
|
|
|
|
const struct sigmadsp_ops *ops, const char *firmware_name)
|
|
|
|
|
{
|
|
|
|
|
sigmadsp->ops = ops;
|
|
|
|
|
sigmadsp->dev = dev;
|
|
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&sigmadsp->data_list);
|
|
|
|
|
|
|
|
|
|
return sigmadsp_firmware_load(sigmadsp, firmware_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* devm_sigmadsp_init() - Initialize SigmaDSP instance
|
|
|
|
|
* @dev: The parent device
|
|
|
|
|
* @ops: The sigmadsp_ops to use for this instance
|
|
|
|
|
* @firmware_name: Name of the firmware file to load
|
|
|
|
|
*
|
|
|
|
|
* Allocates a SigmaDSP instance and loads the specified firmware file.
|
|
|
|
|
*
|
|
|
|
|
* Returns a pointer to a struct sigmadsp on success, or a PTR_ERR() on error.
|
|
|
|
|
*/
|
|
|
|
|
struct sigmadsp *devm_sigmadsp_init(struct device *dev,
|
|
|
|
|
const struct sigmadsp_ops *ops, const char *firmware_name)
|
|
|
|
|
{
|
|
|
|
|
struct sigmadsp *sigmadsp;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
sigmadsp = devres_alloc(devm_sigmadsp_release, sizeof(*sigmadsp),
|
|
|
|
|
GFP_KERNEL);
|
|
|
|
|
if (!sigmadsp)
|
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
|
|
ret = sigmadsp_init(sigmadsp, dev, ops, firmware_name);
|
|
|
|
|
if (ret) {
|
|
|
|
|
devres_free(sigmadsp);
|
|
|
|
|
return ERR_PTR(ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
devres_add(dev, sigmadsp);
|
|
|
|
|
|
|
|
|
|
return sigmadsp;
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(devm_sigmadsp_init);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* sigmadsp_attach() - Attach a sigmadsp instance to a ASoC component
|
|
|
|
|
* @sigmadsp: The sigmadsp instance to attach
|
|
|
|
|
* @component: The component to attach to
|
|
|
|
|
*
|
|
|
|
|
* Typically called in the components probe callback.
|
|
|
|
|
*
|
|
|
|
|
* Note, once this function has been called the firmware must not be released
|
|
|
|
|
* until after the ALSA snd_card that the component belongs to has been
|
|
|
|
|
* disconnected, even if sigmadsp_attach() returns an error.
|
|
|
|
|
*/
|
|
|
|
|
int sigmadsp_attach(struct sigmadsp *sigmadsp,
|
|
|
|
|
struct snd_soc_component *component)
|
|
|
|
|
{
|
|
|
|
|
sigmadsp->component = component;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(sigmadsp_attach);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* sigmadsp_setup() - Setup the DSP for the specified samplerate
|
|
|
|
|
* @sigmadsp: The sigmadsp instance to configure
|
|
|
|
|
* @samplerate: The samplerate the DSP should be configured for
|
|
|
|
|
*
|
|
|
|
|
* Loads the appropriate firmware program and parameter memory (if not already
|
|
|
|
|
* loaded) and enables the controls for the specified samplerate. Any control
|
|
|
|
|
* parameter changes that have been made previously will be restored.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 on success, a negative error code otherwise.
|
|
|
|
|
*/
|
|
|
|
|
int sigmadsp_setup(struct sigmadsp *sigmadsp, unsigned int samplerate)
|
|
|
|
|
{
|
|
|
|
|
struct sigmadsp_data *data;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (sigmadsp->current_samplerate == samplerate)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(data, &sigmadsp->data_list, head) {
|
|
|
|
|
ret = sigmadsp_write(sigmadsp, data->addr, data->data,
|
|
|
|
|
data->length);
|
|
|
|
|
if (ret)
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sigmadsp->current_samplerate = samplerate;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
err:
|
|
|
|
|
sigmadsp_reset(sigmadsp);
|
2011-03-22 16:34:40 -07:00
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
2014-11-19 18:29:05 +01:00
|
|
|
EXPORT_SYMBOL_GPL(sigmadsp_setup);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* sigmadsp_reset() - Notify the sigmadsp instance that the DSP has been reset
|
|
|
|
|
* @sigmadsp: The sigmadsp instance to reset
|
|
|
|
|
*
|
|
|
|
|
* Should be called whenever the DSP has been reset and parameter and program
|
|
|
|
|
* memory need to be re-loaded.
|
|
|
|
|
*/
|
|
|
|
|
void sigmadsp_reset(struct sigmadsp *sigmadsp)
|
|
|
|
|
{
|
|
|
|
|
sigmadsp->current_samplerate = 0;
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(sigmadsp_reset);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* sigmadsp_restrict_params() - Applies DSP firmware specific constraints
|
|
|
|
|
* @sigmadsp: The sigmadsp instance
|
|
|
|
|
* @substream: The substream to restrict
|
|
|
|
|
*
|
|
|
|
|
* Applies samplerate constraints that may be required by the firmware Should
|
|
|
|
|
* typically be called from the CODEC/component drivers startup callback.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 on success, a negative error code otherwise.
|
|
|
|
|
*/
|
|
|
|
|
int sigmadsp_restrict_params(struct sigmadsp *sigmadsp,
|
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(sigmadsp_restrict_params);
|
2011-11-28 09:44:20 +01:00
|
|
|
|
2011-07-25 17:13:21 -07:00
|
|
|
MODULE_LICENSE("GPL");
|