blob: 0cea84f53cc373c61659e4d8bd4b4295c8964c45 [file] [log] [blame]
/*
* Copyright (C) 2014 NXP Semiconductors, 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.
*
*/
#include "dbgprint.h"
#include "tfa_container.h"
#include "tfa.h"
#include "tfa98xx_tfafieldnames.h"
#include "tfa_internal.h"
/* defines */
#define MODULE_BIQUADFILTERBANK 2
#define BIQUAD_COEFF_SIZE 6
#define ERR -1
/* module globals */
/* This is used to SET the slave with the --slave option */
static uint8_t gslave_address;
static int float_to_int(uint32_t x)
{
unsigned int e = (0x7F + 31) - ((*(unsigned int *)&x & 0x7F800000) >> 23);
unsigned int m = 0x80000000 | (*(unsigned int *)&x << 8);
return -(int)((m >> e) & -(e < 32));
}
/*
* check the container file
*/
enum tfa_error tfa_load_cnt(void *cnt, int length)
{
nxpTfaContainer_t *cntbuf = (nxpTfaContainer_t *)cnt;
if (length > TFA_MAX_CNT_LENGTH) {
pr_err("incorrect length\n");
return tfa_error_container;
}
if (HDR(cntbuf->id[0], cntbuf->id[1]) == 0) {
pr_err("header is 0\n");
return tfa_error_container;
}
if ((HDR(cntbuf->id[0], cntbuf->id[1])) != paramsHdr) {
pr_err("wrong header type: 0x%02x 0x%02x\n",
cntbuf->id[0], cntbuf->id[1]);
return tfa_error_container;
}
if (cntbuf->size == 0) {
pr_err("data size is 0\n");
return tfa_error_container;
}
/* check CRC */
if (tfaContCrcCheckContainer(cntbuf)) {
pr_err("CRC error\n");
return tfa_error_container;
}
/* check sub version level */
if ((cntbuf->subversion[1] != NXPTFA_PM_SUBVERSION) &&
(cntbuf->subversion[0] != '0')) {
pr_err("container sub-version not supported: %c%c\n",
cntbuf->subversion[0], cntbuf->subversion[1]);
return tfa_error_container;
}
return tfa_error_ok;
}
/*
* Dump the contents of the file header
*/
void tfaContShowHeader(nxpTfaHeader_t *hdr)
{
char _id[2];
pr_debug("File header\n");
_id[1] = hdr->id >> 8;
_id[0] = hdr->id & 0xff;
pr_debug("\tid:%.2s version:%.2s subversion:%.2s\n", _id,
hdr->version, hdr->subversion);
pr_debug("\tsize:%d CRC:0x%08x \n", hdr->size, hdr->CRC);
pr_debug("\tcustomer:%.8s application:%.8s type:%.8s\n", hdr->customer,
hdr->application, hdr->type);
}
/*
* return device list dsc from index
*/
nxpTfaDeviceList_t *tfaContGetDevList(nxpTfaContainer_t *cont, int dev_idx)
{
uint8_t *base = (uint8_t *)cont;
if (cont == NULL)
return NULL;
if ((dev_idx < 0) || (dev_idx >= cont->ndev))
return NULL;
if (cont->index[dev_idx].type != dscDevice)
return NULL;
base += cont->index[dev_idx].offset;
return (nxpTfaDeviceList_t *)base;
}
/*
* get the Nth profile for the Nth device
*/
nxpTfaProfileList_t *tfaContGetDevProfList(nxpTfaContainer_t *cont,
int devIdx, int profIdx)
{
nxpTfaDeviceList_t *dev;
int idx, hit;
uint8_t *base = (uint8_t *)cont;
dev = tfaContGetDevList(cont, devIdx);
if (dev) {
for (idx = 0, hit = 0; idx < dev->length; idx++) {
if (dev->list[idx].type == dscProfile) {
if (profIdx == hit++)
return (nxpTfaProfileList_t *)(dev->list[idx].offset + base);
}
}
}
return NULL;
}
/*
* get the number of profiles for the Nth device
*/
int tfa_cnt_get_dev_nprof(struct tfa_device *tfa)
{
nxpTfaDeviceList_t *dev;
int idx, nprof = 0;
if (tfa->cnt == NULL)
return 0;
if ((tfa->dev_idx < 0) || (tfa->dev_idx >= tfa->cnt->ndev))
return 0;
dev = tfaContGetDevList(tfa->cnt, tfa->dev_idx);
if (dev) {
for (idx = 0; idx < dev->length; idx++) {
if (dev->list[idx].type == dscProfile) {
nprof++;
}
}
}
return nprof;
}
/*
* get the Nth lifedata for the Nth device
*/
nxpTfaLiveDataList_t *tfaContGetDevLiveDataList(nxpTfaContainer_t *cont,
int devIdx, int lifeDataIdx)
{
nxpTfaDeviceList_t *dev;
int idx, hit;
uint8_t *base = (uint8_t *)cont;
dev = tfaContGetDevList(cont, devIdx);
if (dev) {
for (idx = 0, hit = 0; idx < dev->length; idx++) {
if (dev->list[idx].type == dscLiveData) {
if (lifeDataIdx == hit++)
return (nxpTfaLiveDataList_t *)
(dev->list[idx].offset + base);
}
}
}
return NULL;
}
/*
* Get the max volume step associated with Nth profile for the Nth device
*/
int tfacont_get_max_vstep(struct tfa_device *tfa, int prof_idx)
{
nxpTfaVolumeStep2File_t *vp;
struct nxpTfaVolumeStepMax2File *vp3;
int vstep_count = 0;
vp = (nxpTfaVolumeStep2File_t *)tfacont_getfiledata(tfa, prof_idx,
volstepHdr);
if (vp == NULL)
return 0;
/* check the header type to load different NrOfVStep appropriately */
if (tfa->tfa_family == 2) {
/* this is actually tfa2, so re-read the buffer*/
vp3 = (struct nxpTfaVolumeStepMax2File *)
tfacont_getfiledata(tfa, prof_idx, volstepHdr);
if (vp3) {
vstep_count = vp3->NrOfVsteps;
}
} else {
/* this is max1*/
if (vp) {
vstep_count = vp->vsteps;
}
}
return vstep_count;
}
/**
* Get the file contents associated with the device or profile
* Search within the device tree, if not found, search within the profile
* tree. There can only be one type of file within profile or device.
*/
nxpTfaFileDsc_t *tfacont_getfiledata(struct tfa_device *tfa,
int prof_idx, enum nxpTfaHeaderType type)
{
nxpTfaDeviceList_t *dev;
nxpTfaProfileList_t *prof;
nxpTfaFileDsc_t *file;
nxpTfaHeader_t *hdr;
unsigned int i;
if (tfa->cnt == NULL) {
pr_err("invalid pointer to container file\n");
return NULL;
}
dev = tfaContGetDevList(tfa->cnt, tfa->dev_idx);
if (dev == NULL) {
pr_err("invalid pointer to container file device list\n");
return NULL;
}
/* process the device list until a file type is encountered */
for (i = 0; i < dev->length; i++) {
if (dev->list[i].type == dscFile) {
file = (nxpTfaFileDsc_t *)(dev->list[i].offset + (uint8_t *)tfa->cnt);
if (file != NULL) {
hdr = (nxpTfaHeader_t *)file->data;
/* check for file type */
if (hdr->id == type) {
return (nxpTfaFileDsc_t *)&file->data;
}
}
}
}
/* File not found in device tree.
* So, look in the profile list until the file type is encountered
*/
prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, prof_idx);
if (prof == NULL) {
pr_err("invalid pointer to container file profile list\n");
return NULL;
}
for (i = 0; i < prof->length; i++) {
if (prof->list[i].type == dscFile) {
file = (nxpTfaFileDsc_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
if (file != NULL) {
hdr = (nxpTfaHeader_t *)file->data;
if (hdr != NULL) {
/* check for file type */
if (hdr->id == type) {
return (nxpTfaFileDsc_t *)&file->data;
}
}
}
}
}
if (tfa->verbose)
pr_debug("%s: no file found of type %d\n", __FUNCTION__, type);
return NULL;
}
/*
* write a parameter file to the device
*/
static enum Tfa98xx_Error tfaContWriteVstep(struct tfa_device *tfa,
nxpTfaVolumeStep2File_t *vp, int vstep)
{
enum Tfa98xx_Error err;
unsigned short vol;
if (vstep < vp->vsteps) {
/* vol = (unsigned short)(voldB / (-0.5f)); */
vol = (unsigned short)(-2 * float_to_int(*((uint32_t *)&vp->vstep[vstep].attenuation)));
if (vol > 255) /* restricted to 8 bits */
vol = 255;
err = tfa98xx_set_volume_level(tfa, vol);
if (err != Tfa98xx_Error_Ok)
return err;
err = tfa98xx_dsp_write_preset(tfa, sizeof(vp->vstep[0].preset), vp->vstep[vstep].preset);
if (err != Tfa98xx_Error_Ok)
return err;
err = tfa_cont_write_filterbank(tfa, vp->vstep[vstep].filter);
} else {
pr_err("Incorrect volume given. The value vstep[%d] >= %d\n", vstep, vp->vsteps);
err = Tfa98xx_Error_Bad_Parameter;
}
if (tfa->verbose)
pr_debug("vstep[%d][%d]\n", tfa->dev_idx, vstep);
return err;
}
static struct nxpTfaVolumeStepMessageInfo *
tfaContGetmsgInfoFromReg(struct nxpTfaVolumeStepRegisterInfo *regInfo)
{
char *p = (char *)regInfo;
p += sizeof(regInfo->NrOfRegisters) + (regInfo->NrOfRegisters * sizeof(uint32_t));
return (struct nxpTfaVolumeStepMessageInfo *) p;
}
static int
tfaContGetmsgLen(struct nxpTfaVolumeStepMessageInfo *msgInfo)
{
return (msgInfo->MessageLength.b[0] << 16) +
(msgInfo->MessageLength.b[1] << 8) + msgInfo->MessageLength.b[2];
}
static struct nxpTfaVolumeStepMessageInfo *
tfaContGetNextmsgInfo(struct nxpTfaVolumeStepMessageInfo *msgInfo)
{
char *p = (char *)msgInfo;
int msgLen = tfaContGetmsgLen(msgInfo);
int type = msgInfo->MessageType;
p += sizeof(msgInfo->MessageType) + sizeof(msgInfo->MessageLength);
if (type == 3)
p += msgLen;
else
p += msgLen * 3;
return (struct nxpTfaVolumeStepMessageInfo *) p;
}
static struct nxpTfaVolumeStepRegisterInfo*
tfaContGetNextRegFromEndInfo(struct nxpTfaVolumeStepMessageInfo *msgInfo)
{
char *p = (char *)msgInfo;
p += sizeof(msgInfo->NrOfMessages);
return (struct nxpTfaVolumeStepRegisterInfo *) p;
}
static struct nxpTfaVolumeStepRegisterInfo*
tfaContGetRegForVstep(nxpTfaVolumeStepMax2File_t *vp, int idx)
{
int i, j, nrMessage;
struct nxpTfaVolumeStepRegisterInfo *regInfo
= (struct nxpTfaVolumeStepRegisterInfo *) vp->vstepsBin;
struct nxpTfaVolumeStepMessageInfo *msgInfo = NULL;
for (i = 0; i < idx; i++) {
msgInfo = tfaContGetmsgInfoFromReg(regInfo);
nrMessage = msgInfo->NrOfMessages;
for (j = 0; j < nrMessage; j++) {
msgInfo = tfaContGetNextmsgInfo(msgInfo);
}
regInfo = tfaContGetNextRegFromEndInfo(msgInfo);
}
return regInfo;
}
#pragma pack (push, 1)
struct tfa_partial_msg_block {
uint8_t offset;
uint16_t change;
uint8_t update[16][3];
};
#pragma pack (pop)
static enum Tfa98xx_Error tfaContWriteVstepMax2_One(struct tfa_device *tfa,
struct nxpTfaVolumeStepMessageInfo *new_msg,
struct nxpTfaVolumeStepMessageInfo *old_msg,
int enable_partial_update)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
int len = (tfaContGetmsgLen(new_msg) - 1) * 3;
char *buf = (char *)new_msg->ParameterData;
uint8_t *partial = NULL;
uint8_t cmdid[3];
int use_partial_coeff = 0;
if (enable_partial_update) {
if (new_msg->MessageType != old_msg->MessageType) {
pr_debug("Message type differ - Disable Partial Update\n");
enable_partial_update = 0;
} else if (tfaContGetmsgLen(new_msg) != tfaContGetmsgLen(old_msg)) {
pr_debug("Message Length differ - Disable Partial Update\n");
enable_partial_update = 0;
}
}
if ((enable_partial_update) && (new_msg->MessageType == 1)) {
/* No patial updates for message type 1 (Coefficients) */
enable_partial_update = 0;
if ((tfa->rev & 0xff) == 0x88)
use_partial_coeff = 1;
else if ((tfa->rev & 0xff) == 0x13)
use_partial_coeff = 1;
}
/* Change Message Len to the actual buffer len */
memcpy(cmdid, new_msg->CmdId, sizeof(cmdid));
/* The algoparams and mbdrc msg id will be changed to the reset type when SBSL=0
* if SBSL=1 the msg will remain unchanged. It's up to the tuning engineer to choose the 'without_reset'
* types inside the vstep. In other words: the reset msg is applied during SBSL==0 else it remains unchanged.
*/
if (tfa_needs_reset(tfa) == 1) {
if (new_msg->MessageType == 0) {
cmdid[2] = SB_PARAM_SET_ALGO_PARAMS;
if (tfa->verbose)
pr_debug("P-ID for SetAlgoParams modified!\n");
} else if (new_msg->MessageType == 2) {
cmdid[2] = SB_PARAM_SET_MBDRC;
if (tfa->verbose)
pr_debug("P-ID for SetMBDrc modified!\n");
}
}
/*
* +sizeof(struct tfa_partial_msg_block) will allow to fit one
* additonnal partial block If the partial update goes over the len of
* a regular message ,we can safely write our block and check afterward
* that we are over the size of a usual update
*/
if (enable_partial_update) {
partial = kmem_cache_alloc(tfa->cachep, GFP_KERNEL);
if (!partial)
pr_debug("Partial update memory error - Disabling\n");
}
if (partial) {
uint8_t offset = 0, i = 0;
uint16_t *change;
uint8_t *n = new_msg->ParameterData;
uint8_t *o = old_msg->ParameterData;
uint8_t *p = partial;
uint8_t *trim = partial;
/* set dspFiltersReset */
*p++ = 0x02;
*p++ = 0x00;
*p++ = 0x00;
while ((o < (old_msg->ParameterData + len)) &&
(p < (partial + len - 3))) {
if ((offset == 0xff) ||
(memcmp(n, o, 3 * sizeof(uint8_t)))) {
*p++ = offset;
change = (uint16_t *)p;
*change = 0;
p += 2;
for (i = 0;
(i < 16) && (o < (old_msg->ParameterData + len));
i++, n += 3, o += 3) {
if (memcmp(n, o, 3 * sizeof(uint8_t))) {
*change |= BIT(i);
memcpy(p, n, 3);
p += 3;
trim = p;
}
}
offset = 0;
*change = cpu_to_be16(*change);
} else {
n += 3;
o += 3;
offset++;
}
}
if (trim == partial) {
pr_debug("No Change in message - discarding %d bytes\n", len);
len = 0;
} else if (trim < (partial + len - 3)) {
pr_debug("Using partial update: %d -> %d bytes\n", len, (int)(trim - partial + 3));
/* Add the termination marker */
memset(trim, 0x00, 3);
trim += 3;
/* Signal This will be a partial update */
cmdid[2] |= BIT(6);
buf = (char *)partial;
len = (int)(trim - partial);
} else {
pr_debug("Partial too big - use regular update\n");
}
}
if (use_partial_coeff) {
err = dsp_partial_coefficients(tfa, old_msg->ParameterData, new_msg->ParameterData);
} else if (len) {
uint8_t *buffer;
if (tfa->verbose)
pr_debug("Command-ID used: 0x%02x%02x%02x \n", cmdid[0], cmdid[1], cmdid[2]);
buffer = kmem_cache_alloc(tfa->cachep, GFP_KERNEL);
if (buffer == NULL) {
err = Tfa98xx_Error_Fail;
} else {
memcpy(&buffer[0], cmdid, 3);
memcpy(&buffer[3], buf, len);
err = dsp_msg(tfa, 3 + len, (char *)buffer);
kmem_cache_free(tfa->cachep, buffer);
}
}
if (partial)
kmem_cache_free(tfa->cachep, partial);
return err;
}
static enum Tfa98xx_Error tfaContWriteVstepMax2(struct tfa_device *tfa,
nxpTfaVolumeStepMax2File_t *vp, int vstep_idx,
int vstep_msg_idx)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
struct nxpTfaVolumeStepRegisterInfo *regInfo = NULL;
struct nxpTfaVolumeStepMessageInfo *msgInfo = NULL, *p_msgInfo = NULL;
nxpTfaBitfield_t bitF;
int i, nrMessages, enp = tfa->partial_enable;
if (vstep_idx >= vp->NrOfVsteps) {
pr_debug("Volumestep %d is not available \n", vstep_idx);
return Tfa98xx_Error_Bad_Parameter;
}
if (tfa->p_regInfo == NULL) {
if (tfa->verbose)
pr_debug("Inital vstep write\n");
enp = 0;
}
regInfo = tfaContGetRegForVstep(vp, vstep_idx);
msgInfo = tfaContGetmsgInfoFromReg(regInfo);
nrMessages = msgInfo->NrOfMessages;
if (enp) {
p_msgInfo = tfaContGetmsgInfoFromReg(tfa->p_regInfo);
if (nrMessages != p_msgInfo->NrOfMessages) {
pr_debug("Message different - Disable partial update\n");
enp = 0;
}
}
for (i = 0; i < nrMessages; i++) {
/* Messagetype(3) is Smartstudio Info! Dont send this! */
if (msgInfo->MessageType == 3) {
/* MessageLength is in bytes */
msgInfo = tfaContGetNextmsgInfo(msgInfo);
if (enp)
p_msgInfo = tfaContGetNextmsgInfo(p_msgInfo);
continue;
}
/* If no vstepMsgIndex is passed on, all message needs to be send */
if ((vstep_msg_idx >= TFA_MAX_VSTEP_MSG_MARKER) || (vstep_msg_idx == i)) {
err = tfaContWriteVstepMax2_One(tfa, msgInfo, p_msgInfo, enp);
if (err != Tfa98xx_Error_Ok) {
/*
* Force a full update for the next write
* As the current status of the DSP is unknown
*/
tfa->p_regInfo = NULL;
return err;
}
}
msgInfo = tfaContGetNextmsgInfo(msgInfo);
if (enp)
p_msgInfo = tfaContGetNextmsgInfo(p_msgInfo);
}
tfa->p_regInfo = regInfo;
for (i = 0; i < regInfo->NrOfRegisters * 2; i++) {
/* Byte swap the datasheetname */
bitF.field = (uint16_t)(regInfo->registerInfo[i] >> 8) | (regInfo->registerInfo[i] << 8);
i++;
bitF.value = (uint16_t)regInfo->registerInfo[i] >> 8;
err = tfaRunWriteBitfield(tfa, bitF);
if (err != Tfa98xx_Error_Ok)
return err;
}
/* Save the current vstep */
tfa_dev_set_swvstep(tfa, (unsigned short)vstep_idx);
return err;
}
/*
* Write DRC message to the dsp
* If needed modify the cmd-id
*/
enum Tfa98xx_Error tfaContWriteDrcFile(struct tfa_device *tfa,
int size, uint8_t data[])
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
uint8_t *msg = NULL;
msg = kmem_cache_alloc(tfa->cachep, GFP_KERNEL);
if (msg == NULL)
return Tfa98xx_Error_Fail;
memcpy(msg, data, size);
if (TFA_GET_BF(tfa, SBSL) == 0) {
/* Only do this when not set already */
if (msg[2] != SB_PARAM_SET_MBDRC) {
msg[2] = SB_PARAM_SET_MBDRC;
if (tfa->verbose) {
pr_debug("P-ID for SetMBDrc modified!: ");
pr_debug("Command-ID used: 0x%02x%02x%02x \n",
msg[0], msg[1], msg[2]);
}
}
}
/* Send cmdId + payload to dsp */
err = dsp_msg(tfa, size, (const char *)msg);
kmem_cache_free(tfa->cachep, msg);
return err;
}
/*
* write a parameter file to the device
* The VstepIndex and VstepMsgIndex are only used to write a specific msg from the vstep file.
*/
enum Tfa98xx_Error tfaContWriteFile(struct tfa_device *tfa,
nxpTfaFileDsc_t *file, int vstep_idx, int vstep_msg_idx)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaHeader_t *hdr = (nxpTfaHeader_t *)file->data;
nxpTfaHeaderType_t type;
int size, i;
char subVerString[8] = { 0 };
int subversion = 0;
if (tfa->verbose) {
tfaContShowHeader(hdr);
}
type = (nxpTfaHeaderType_t)hdr->id;
if ((type == msgHdr) || ((type == volstepHdr) && (tfa->tfa_family == 2))) {
subVerString[0] = hdr->subversion[0];
subVerString[1] = hdr->subversion[1];
subVerString[2] = '\0';
sscanf(subVerString, "%d", &subversion);
if ((subversion > 0) &&
(((hdr->customer[0]) == 'A') && ((hdr->customer[1]) == 'P') &&
((hdr->customer[2]) == 'I') && ((hdr->customer[3]) == 'V'))) {
if (tfa->is_probus_device) {
/* Temporary workaround (example: For climax --calibrate
* scenario for probus devices)
*/
err = tfaGetFwApiVersion(tfa,
(unsigned char *)&tfa->fw_itf_ver[0]);
if (err) {
pr_debug("[%s] cannot get FWAPI error = %d \n",
__FUNCTION__, err);
return err;
}
for (i = 0; i < 3; i++) {
//+4 to skip "?PIV" string part in the .msg file.
if (tfa->fw_itf_ver[i] != hdr->customer[i + 4]) {
ERRORMSG("Error: tfaContWriteFile: Expected FW API \
version = %d.%d.%d, Msg File version: %d.%d.%d \n",
tfa->fw_itf_ver[0],
tfa->fw_itf_ver[1],
tfa->fw_itf_ver[2],
hdr->customer[4],
hdr->customer[5],
hdr->customer[6]);
return Tfa98xx_Error_Bad_Parameter;
}
}
} else if ((tfa->fw_itf_ver[2] != hdr->customer[4]) ||
(tfa->fw_itf_ver[1] != hdr->customer[5]) ||
((tfa->fw_itf_ver[0] >> 6) & 0x03) != hdr->customer[6]) {
ERRORMSG("Error: tfaContWriteFile: Expected FW API version = %d.%d.%d, Msg File version: %d.%d.%d \n",
(tfa->fw_itf_ver[2]) & 0xff,
(tfa->fw_itf_ver[1]) & 0xff,
(tfa->fw_itf_ver[0] >> 6) & 0x03,
hdr->customer[4],
hdr->customer[5],
hdr->customer[6]);
return Tfa98xx_Error_Bad_Parameter;
}
}
}
switch (type) {
case msgHdr: /* generic DSP message */
size = hdr->size - sizeof(nxpTfaMsgFile_t);
err = dsp_msg(tfa, size, (const char *)((nxpTfaMsgFile_t *)hdr)->data);
break;
case volstepHdr:
if (tfa->tfa_family == 2) {
err = tfaContWriteVstepMax2(tfa,
(nxpTfaVolumeStepMax2File_t *)hdr,
vstep_idx, vstep_msg_idx);
} else {
err = tfaContWriteVstep(tfa, (nxpTfaVolumeStep2File_t *)hdr,
vstep_idx);
}
break;
case speakerHdr:
if (tfa->tfa_family == 2) {
/* Remove header and xml_id */
size = hdr->size - sizeof(struct nxpTfaSpkHeader)
- sizeof(struct nxpTfaFWVer);
err = dsp_msg(tfa, size,
(const char *)(((nxpTfaSpeakerFile_t *)hdr)->data +
(sizeof(struct nxpTfaFWVer))));
} else {
size = hdr->size - sizeof(nxpTfaSpeakerFile_t);
err = tfa98xx_dsp_write_speaker_parameters(tfa, size,
(const unsigned char *)((nxpTfaSpeakerFile_t *)hdr)->data);
}
break;
case presetHdr:
size = hdr->size - sizeof(nxpTfaPreset_t);
err = tfa98xx_dsp_write_preset(tfa, size,
(const unsigned char *)((nxpTfaPreset_t *)hdr)->data);
break;
case equalizerHdr:
err = tfa_cont_write_filterbank(tfa, ((nxpTfaEqualizerFile_t *)hdr)->filter);
break;
case patchHdr:
size = hdr->size - sizeof(nxpTfaPatch_t); // size is total length
err = tfa_dsp_patch(tfa, size, (const unsigned char *)((nxpTfaPatch_t *)hdr)->data);
break;
case configHdr:
size = hdr->size - sizeof(nxpTfaConfig_t);
err = tfa98xx_dsp_write_config(tfa, size,
(const unsigned char *)((nxpTfaConfig_t *)hdr)->data);
break;
case drcHdr:
if (hdr->version[0] == NXPTFA_DR3_VERSION) {
/* Size is total size - hdrsize(36) - xmlversion(3) */
size = hdr->size - sizeof(nxpTfaDrc2_t);
err = tfaContWriteDrcFile(tfa, size, ((nxpTfaDrc2_t *)hdr)->data);
} else {
/*
* The DRC file is split as:
* 36 bytes for generic header (customer, application, and type)
* 127x3 (381) bytes first block contains the device and sample rate
* independent settings
* 127x3 (381) bytes block the device and sample rate specific values.
* The second block can always be recalculated from the first block,
* if vlsCal and the sample rate are known.
*/
//size = hdr->size - sizeof(nxpTfaDrc_t);
size = 381; /* fixed size for first block */
//+381 is done to only send the second part of the drc block
err = tfa98xx_dsp_write_drc(tfa, size,
((const unsigned char *)((nxpTfaDrc_t *)hdr)->data + 381));
}
break;
case infoHdr:
/* Ignore */
break;
default:
pr_err("Header is of unknown type: 0x%x\n", type);
return Tfa98xx_Error_Bad_Parameter;
}
return err;
}
/**
* get the 1st of this dsc type this devicelist
*/
static nxpTfaDescPtr_t *tfa_cnt_get_dsc(nxpTfaContainer_t *cnt, nxpTfaDescriptorType_t type, int dev_idx)
{
nxpTfaDeviceList_t *dev = tfaContDevice(cnt, dev_idx);
nxpTfaDescPtr_t *_this;
int i;
if (!dev) {
return NULL;
}
/* process the list until a the type is encountered */
for (i = 0; i < dev->length; i++) {
if (dev->list[i].type == (uint32_t)type) {
_this = (nxpTfaDescPtr_t *)(dev->list[i].offset + (uint8_t *)cnt);
return _this;
}
}
return NULL;
}
/**
* get the device type from the patch in this devicelist
* - find the patch file for this devidx
* - return the devid from the patch or 0 if not found
*/
int tfa_cnt_get_devid(nxpTfaContainer_t *cnt, int dev_idx)
{
nxpTfaPatch_t *patchfile;
nxpTfaDescPtr_t *patchdsc;
uint8_t *patchheader;
unsigned short devid, checkaddress;
int checkvalue;
patchdsc = tfa_cnt_get_dsc(cnt, dscPatch, dev_idx);
if (!patchdsc) /* no patch for this device, assume non-i2c */
return 0;
patchdsc += 2; /* first the filename dsc and filesize, so skip them */
patchfile = (nxpTfaPatch_t *)patchdsc;
patchheader = patchfile->data;
checkaddress = (patchheader[1] << 8) + patchheader[2];
checkvalue =
(patchheader[3] << 16) + (patchheader[4] << 8) + patchheader[5];
devid = patchheader[0];
if (checkaddress == 0xFFFF && checkvalue != 0xFFFFFF && checkvalue != 0) {
devid = patchheader[5] << 8 | patchheader[0]; /* full revid */
}
return devid;
}
/**
* get the firmware version from the patch in this devicelist
*/
int tfa_cnt_get_patch_version(struct tfa_device *tfa)
{
nxpTfaPatch_t *patchfile;
nxpTfaDescPtr_t *patchdsc;
uint8_t *data;
int size, version;
if (tfa->cnt == NULL)
return ERR;
patchdsc = tfa_cnt_get_dsc(tfa->cnt, dscPatch, tfa->dev_idx);
patchdsc += 2; /* first the filename dsc and filesize, so skip them */
patchfile = (nxpTfaPatch_t *)patchdsc;
size = patchfile->hdr.size - sizeof(nxpTfaPatch_t);
data = patchfile->data;
version = (data[size - 3] << 16) + (data[size - 2] << 8) + data[size - 1];
return version;
}
/*
* get the slave for the device if it exists
*/
enum Tfa98xx_Error tfaContGetSlave(struct tfa_device *tfa, uint8_t *slave_addr)
{
nxpTfaDeviceList_t *dev = NULL;
/* Make sure the cnt file is loaded */
if (tfa->cnt != NULL) {
dev = tfaContDevice(tfa->cnt, tfa->dev_idx);
}
if (dev == NULL) {
/* Check if slave argument is used! */
if (gslave_address == 0) {
return Tfa98xx_Error_Bad_Parameter;
} else {
*slave_addr = gslave_address;
return Tfa98xx_Error_Ok;
}
}
*slave_addr = dev->dev;
return Tfa98xx_Error_Ok;
}
/* If no container file is given, we can always have used the slave argument */
void tfaContSetSlave(uint8_t slave_addr)
{
gslave_address = slave_addr;
}
/*
* lookup slave and return device index
*/
int tfa_cont_get_idx(struct tfa_device *tfa)
{
nxpTfaDeviceList_t *dev = NULL;
int i;
for (i = 0; i < tfa->cnt->ndev; i++) {
dev = tfaContDevice(tfa->cnt, i);
if (dev->dev == tfa->slave_address)
break;
}
if (i == tfa->cnt->ndev)
return ERR;
return i;
}
/*
* write a bit field
*/
enum Tfa98xx_Error tfaRunWriteBitfield(struct tfa_device *tfa, nxpTfaBitfield_t bf)
{
enum Tfa98xx_Error error;
uint16_t value;
union {
uint16_t field;
nxpTfaBfEnum_t Enum;
} bfUni;
value = bf.value;
bfUni.field = bf.field;
#ifdef TFA_DEBUG
if (tfa->verbose)
pr_debug("bitfield: %s=0x%x (0x%x[%d..%d]=0x%x)\n", tfaContBfName(bfUni.field, tfa->rev), value,
bfUni.Enum.address, bfUni.Enum.pos, bfUni.Enum.pos + bfUni.Enum.len, value);
#endif
error = tfa_set_bf(tfa, bfUni.field, value);
return error;
}
/*
* read a bit field
*/
enum Tfa98xx_Error tfaRunReadBitfield(struct tfa_device *tfa, nxpTfaBitfield_t *bf)
{
enum Tfa98xx_Error error;
union {
uint16_t field;
nxpTfaBfEnum_t Enum;
} bfUni;
uint16_t regvalue, msk;
bfUni.field = bf->field;
error = reg_read(tfa, (unsigned char)(bfUni.Enum.address), &regvalue);
if (error)
return error;
msk = ((1 << (bfUni.Enum.len + 1)) - 1) << bfUni.Enum.pos;
regvalue &= msk;
bf->value = regvalue >> bfUni.Enum.pos;
return error;
}
/*
dsp mem direct write
*/
static enum Tfa98xx_Error tfaRunWriteDspMem(struct tfa_device *tfa, nxpTfaDspMem_t *cfmem)
{
enum Tfa98xx_Error error = Tfa98xx_Error_Ok;
int i;
for (i = 0; i < cfmem->size; i++) {
if (tfa->verbose)
pr_debug("dsp mem (%d): 0x%02x=0x%04x\n", cfmem->type, cfmem->address, cfmem->words[i]);
error = mem_write(tfa, cfmem->address++, cfmem->words[i], cfmem->type);
if (error)
return error;
}
return error;
}
/*
* write filter payload to DSP
* note that the data is in an aligned union for all filter variants
* the aa data is used but it's the same for all of them
*/
static enum Tfa98xx_Error tfaRunWriteFilter(struct tfa_device *tfa, nxpTfaContBiquad_t *bq)
{
enum Tfa98xx_Error error = Tfa98xx_Error_Ok;
enum Tfa98xx_DMEM dmem;
uint16_t address;
uint8_t data[3 * 3 + sizeof(bq->aa.bytes)];
int i, channel = 0, runs = 1;
int8_t saved_index = bq->aa.index; /* This is used to set back the index */
/* Channel=1 is primary, Channel=2 is secondary*/
if (bq->aa.index > 100) {
bq->aa.index -= 100;
channel = 2;
} else if (bq->aa.index > 50) {
bq->aa.index -= 50;
channel = 1;
} else if ((tfa->rev & 0xff) == 0x88) {
runs = 2;
}
if (tfa->verbose) {
if (channel == 2)
pr_debug("filter[%d,S]", bq->aa.index);
else if (channel == 1)
pr_debug("filter[%d,P]", bq->aa.index);
else
pr_debug("filter[%d]", bq->aa.index);
}
for (i = 0; i < runs; i++) {
if (runs == 2)
channel++;
/* get the target address for the filter on this device */
dmem = tfa98xx_filter_mem(tfa, bq->aa.index, &address, channel);
if (dmem == Tfa98xx_DMEM_ERR) {
if (tfa->verbose) {
pr_debug("Warning: XFilter settings are applied via msg file (ini filter[x] format is skipped).\n");
}
/* Dont exit with an error here, We could continue without problems */
return Tfa98xx_Error_Ok;
}
/* send a DSP memory message that targets the devices specific memory for the filter
* msg params: which_mem, start_offset, num_words
*/
memset(data, 0, 3 * 3);
data[2] = dmem; /* output[0] = which_mem */
data[4] = address >> 8; /* output[1] = start_offset */
data[5] = address & 0xff;
data[8] = sizeof(bq->aa.bytes) / 3; /*output[2] = num_words */
memcpy(&data[9], bq->aa.bytes, sizeof(bq->aa.bytes)); /* payload */
if (tfa->tfa_family == 2)
error = tfa_dsp_cmd_id_write(tfa, MODULE_FRAMEWORK, FW_PAR_ID_SET_MEMORY, sizeof(data), data);
else
error = tfa_dsp_cmd_id_write(tfa, MODULE_FRAMEWORK, 4 /* param */, sizeof(data), data);
}
#ifdef TFA_DEBUG
if (tfa->verbose) {
if (bq->aa.index == 13) {
pr_debug("=%d,%.0f,%.2f \n",
bq->in.type, bq->in.cutOffFreq, bq->in.leakage);
} else if (bq->aa.index >= 10 && bq->aa.index <= 12) {
pr_debug("=%d,%.0f,%.1f,%.1f \n", bq->aa.type,
bq->aa.cutOffFreq, bq->aa.rippleDb, bq->aa.rolloff);
} else {
pr_debug("= unsupported filter index \n");
}
}
#endif
/* Because we can load the same filters multiple times
* For example: When we switch profile we re-write in operating mode.
* We then need to remember the index (primary, secondary or both)
*/
bq->aa.index = saved_index;
return error;
}
/*
* write the register based on the input address, value and mask
* only the part that is masked will be updated
*/
static enum Tfa98xx_Error tfaRunWriteRegister(struct tfa_device *tfa, nxpTfaRegpatch_t *reg)
{
enum Tfa98xx_Error error;
uint16_t value, newvalue;
if (tfa->verbose)
pr_debug("register: 0x%02x=0x%04x (msk=0x%04x)\n", reg->address, reg->value, reg->mask);
error = reg_read(tfa, reg->address, &value);
if (error)
return error;
value &= ~reg->mask;
newvalue = reg->value & reg->mask;
value |= newvalue;
error = reg_write(tfa, reg->address, value);
return error;
}
// write reg and bitfield items in the devicelist to the target
enum Tfa98xx_Error tfaContWriteRegsDev(struct tfa_device *tfa)
{
nxpTfaDeviceList_t *dev = tfaContDevice(tfa->cnt, tfa->dev_idx);
nxpTfaBitfield_t *bitF;
int i;
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
if (!dev) {
return Tfa98xx_Error_Bad_Parameter;
}
/* process the list until a patch, file of profile is encountered */
for (i = 0; i < dev->length; i++) {
if (dev->list[i].type == dscPatch ||
dev->list[i].type == dscFile ||
dev->list[i].type == dscProfile)
break;
if (dev->list[i].type == dscBitfield) {
bitF = (nxpTfaBitfield_t *)(dev->list[i].offset + (uint8_t *)tfa->cnt);
err = tfaRunWriteBitfield(tfa, *bitF);
}
if (dev->list[i].type == dscRegister) {
err = tfaRunWriteRegister(tfa, (nxpTfaRegpatch_t *)(dev->list[i].offset + (char *)tfa->cnt));
}
if (err)
break;
}
return err;
}
// write reg and bitfield items in the profilelist the target
enum Tfa98xx_Error tfaContWriteRegsProf(struct tfa_device *tfa, int prof_idx)
{
nxpTfaProfileList_t *prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, prof_idx);
nxpTfaBitfield_t *bitf;
unsigned int i;
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
if (!prof) {
return Tfa98xx_Error_Bad_Parameter;
}
if (tfa->verbose)
pr_debug("----- profile: %s (%d) -----\n", tfaContGetString(tfa->cnt, &prof->name), prof_idx);
/* process the list until the end of the profile or the default section */
for (i = 0; i < prof->length; i++) {
/* We only want to write the values before the default section when we switch profile */
if (prof->list[i].type == dscDefault)
break;
if (prof->list[i].type == dscBitfield) {
bitf = (nxpTfaBitfield_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
err = tfaRunWriteBitfield(tfa, *bitf);
}
if (prof->list[i].type == dscRegister) {
err = tfaRunWriteRegister(tfa, (nxpTfaRegpatch_t *)(prof->list[i].offset + (char *)tfa->cnt));
}
if (err)
break;
}
return err;
}
// write patchfile in the devicelist to the target
enum Tfa98xx_Error tfaContWritePatch(struct tfa_device *tfa)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaDeviceList_t *dev = tfaContDevice(tfa->cnt, tfa->dev_idx);
nxpTfaFileDsc_t *file;
nxpTfaPatch_t *patchfile;
int size, i;
if (!dev) {
return Tfa98xx_Error_Bad_Parameter;
}
/* process the list until a patch is encountered */
for (i = 0; i < dev->length; i++) {
if (dev->list[i].type == dscPatch) {
file = (nxpTfaFileDsc_t *)(dev->list[i].offset + (uint8_t *)tfa->cnt);
patchfile = (nxpTfaPatch_t *)&file->data;
if (tfa->verbose)
tfaContShowHeader(&patchfile->hdr);
size = patchfile->hdr.size - sizeof(nxpTfaPatch_t); // size is total length
err = tfa_dsp_patch(tfa, size, (const unsigned char *)patchfile->data);
if (err)
return err;
}
}
return Tfa98xx_Error_Ok;
}
/**
* Create a buffer which can be used to send to the dsp.
*/
static void create_dsp_buffer_msg(struct tfa_device *tfa, nxpTfaMsg_t *msg, char *buffer, int *size)
{
int i, nr = 0;
(void)tfa;
/* Copy cmdId. Remember that the cmdId is reversed */
buffer[nr++] = msg->cmdId[2];
buffer[nr++] = msg->cmdId[1];
buffer[nr++] = msg->cmdId[0];
/* Copy the data to the buffer */
for (i = 0; i < msg->msg_size; i++) {
buffer[nr++] = (uint8_t)((msg->data[i] >> 16) & 0xffff);
buffer[nr++] = (uint8_t)((msg->data[i] >> 8) & 0xff);
buffer[nr++] = (uint8_t)(msg->data[i] & 0xff);
}
*size = nr;
}
// write all param files in the devicelist to the target
enum Tfa98xx_Error tfaContWriteFiles(struct tfa_device *tfa)
{
nxpTfaDeviceList_t *dev = tfaContDevice(tfa->cnt, tfa->dev_idx);
nxpTfaFileDsc_t *file;
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
char buffer[(MEMTRACK_MAX_WORDS * 3) + 3] = { 0 }; //every word requires 3 and 3 is the msg
int i, size = 0;
if (!dev) {
return Tfa98xx_Error_Bad_Parameter;
}
/* process the list and write all files */
for (i = 0; i < dev->length; i++) {
if (dev->list[i].type == dscFile) {
file = (nxpTfaFileDsc_t *)(dev->list[i].offset + (uint8_t *)tfa->cnt);
if (tfaContWriteFile(tfa, file, 0, TFA_MAX_VSTEP_MSG_MARKER)) {
return Tfa98xx_Error_Bad_Parameter;
}
}
if (dev->list[i].type == dscSetInputSelect ||
dev->list[i].type == dscSetOutputSelect ||
dev->list[i].type == dscSetProgramConfig ||
dev->list[i].type == dscSetLagW ||
dev->list[i].type == dscSetGains ||
dev->list[i].type == dscSetvBatFactors ||
dev->list[i].type == dscSetSensesCal ||
dev->list[i].type == dscSetSensesDelay ||
dev->list[i].type == dscSetMBDrc ||
dev->list[i].type == dscSetFwkUseCase ||
dev->list[i].type == dscSetVddpConfig) {
create_dsp_buffer_msg(tfa, (nxpTfaMsg_t *)
(dev->list[i].offset + (char *)tfa->cnt), buffer, &size);
if (tfa->verbose) {
pr_debug("command: %s=0x%02x%02x%02x \n",
tfaContGetCommandString(dev->list[i].type),
(unsigned char)buffer[0], (unsigned char)buffer[1], (unsigned char)buffer[2]);
}
err = dsp_msg(tfa, size, buffer);
}
if (dev->list[i].type == dscCmd) {
size = *(uint16_t *)(dev->list[i].offset + (char *)tfa->cnt);
err = dsp_msg(tfa, size, dev->list[i].offset + 2 + (char *)tfa->cnt);
if (tfa->verbose) {
const char *cmd_id = dev->list[i].offset + 2 + (char *)tfa->cnt;
pr_debug("Writing cmd=0x%02x%02x%02x \n", (uint8_t)cmd_id[0], (uint8_t)cmd_id[1], (uint8_t)cmd_id[2]);
}
}
if (err != Tfa98xx_Error_Ok)
break;
if (dev->list[i].type == dscCfMem) {
err = tfaRunWriteDspMem(tfa, (nxpTfaDspMem_t *)(dev->list[i].offset + (uint8_t *)tfa->cnt));
}
if (err != Tfa98xx_Error_Ok)
break;
}
return err;
}
/*
* write all param files in the profilelist to the target
* this is used during startup when maybe ACS is set
*/
enum Tfa98xx_Error tfaContWriteFilesProf(struct tfa_device *tfa, int prof_idx, int vstep_idx)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaProfileList_t *prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, prof_idx);
char buffer[(MEMTRACK_MAX_WORDS * 3) + 3] = { 0 }; //every word requires 3 and 3 is the msg
unsigned int i;
nxpTfaFileDsc_t *file;
nxpTfaPatch_t *patchfile;
int size;
if (!prof) {
return Tfa98xx_Error_Bad_Parameter;
}
/* process the list and write all files */
for (i = 0; i < prof->length; i++) {
switch (prof->list[i].type) {
case dscFile:
file = (nxpTfaFileDsc_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
err = tfaContWriteFile(tfa, file, vstep_idx, TFA_MAX_VSTEP_MSG_MARKER);
break;
case dscPatch:
file = (nxpTfaFileDsc_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
patchfile = (nxpTfaPatch_t *)&file->data;
if (tfa->verbose)
tfaContShowHeader(&patchfile->hdr);
size = patchfile->hdr.size - sizeof(nxpTfaPatch_t); // size is total length
err = tfa_dsp_patch(tfa, size, (const unsigned char *)patchfile->data);
break;
case dscCfMem:
err = tfaRunWriteDspMem(tfa, (nxpTfaDspMem_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt));
break;
case dscSetInputSelect:
case dscSetOutputSelect:
case dscSetProgramConfig:
case dscSetLagW:
case dscSetGains:
case dscSetvBatFactors:
case dscSetSensesCal:
case dscSetSensesDelay:
case dscSetMBDrc:
case dscSetFwkUseCase:
case dscSetVddpConfig:
create_dsp_buffer_msg(tfa, (nxpTfaMsg_t *)
(prof->list[i].offset + (uint8_t *)tfa->cnt), buffer, &size);
if (tfa->verbose) {
pr_debug("command: %s=0x%02x%02x%02x \n",
tfaContGetCommandString(prof->list[i].type),
(unsigned char)buffer[0], (unsigned char)buffer[1], (unsigned char)buffer[2]);
}
err = dsp_msg(tfa, size, buffer);
break;
default:
/* ignore any other type */
break;
}
}
return err;
}
static enum Tfa98xx_Error tfaContWriteItem(struct tfa_device *tfa, nxpTfaDescPtr_t *dsc)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaRegpatch_t *reg;
nxpTfaMode_t *cas;
nxpTfaBitfield_t *bitf;
// When no DSP should only write to HW registers.
if (tfa->ext_dsp == 0 && !(dsc->type == dscBitfield || dsc->type == dscRegister)) {
return Tfa98xx_Error_Ok;
}
switch (dsc->type) {
case dscDefault:
case dscDevice: // ignore
case dscProfile: // profile list
break;
case dscRegister: // register patch
reg = (nxpTfaRegpatch_t *)(dsc->offset + (uint8_t *)tfa->cnt);
return tfaRunWriteRegister(tfa, reg);
//pr_debug("$0x%2x=0x%02x,0x%02x\n", reg->address, reg->mask, reg->value);
break;
case dscString: // ascii: zero terminated string
pr_debug(";string: %s\n", tfaContGetString(tfa->cnt, dsc));
break;
case dscFile: // filename + file contents
case dscPatch:
break;
case dscMode:
cas = (nxpTfaMode_t *)(dsc->offset + (uint8_t *)tfa->cnt);
if (cas->value == Tfa98xx_Mode_RCV)
tfa98xx_select_mode(tfa, Tfa98xx_Mode_RCV);
else
tfa98xx_select_mode(tfa, Tfa98xx_Mode_Normal);
break;
case dscCfMem:
err = tfaRunWriteDspMem(tfa, (nxpTfaDspMem_t *)(dsc->offset + (uint8_t *)tfa->cnt));
break;
case dscBitfield:
bitf = (nxpTfaBitfield_t *)(dsc->offset + (uint8_t *)tfa->cnt);
return tfaRunWriteBitfield(tfa, *bitf);
break;
case dscFilter:
return tfaRunWriteFilter(tfa, (nxpTfaContBiquad_t *)(dsc->offset + (uint8_t *)tfa->cnt));
break;
}
return err;
}
static unsigned int tfa98xx_sr_from_field(unsigned int field)
{
switch (field) {
case 0:
return 8000;
case 1:
return 11025;
case 2:
return 12000;
case 3:
return 16000;
case 4:
return 22050;
case 5:
return 24000;
case 6:
return 32000;
case 7:
return 44100;
case 8:
return 48000;
default:
return 0;
}
}
enum Tfa98xx_Error tfa_write_filters(struct tfa_device *tfa, int prof_idx)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaProfileList_t *prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, prof_idx);
unsigned int i;
int status;
if (!prof) {
return Tfa98xx_Error_Bad_Parameter;
}
if (tfa->verbose) {
pr_debug("----- profile: %s (%d) -----\n", tfaContGetString(tfa->cnt, &prof->name), prof_idx);
pr_debug("Waiting for CLKS... \n");
}
for (i = 10; i > 0; i--) {
err = tfa98xx_dsp_system_stable(tfa, &status);
if (status)
break;
else
msleep_interruptible(10);
}
if (i == 0) {
if (tfa->verbose)
pr_err("Unable to write filters, CLKS=0 \n");
return Tfa98xx_Error_StateTimedOut;
}
/* process the list until the end of the profile or the default section */
for (i = 0; i < prof->length; i++) {
if (prof->list[i].type == dscFilter) {
if (tfaContWriteItem(tfa, &prof->list[i]) != Tfa98xx_Error_Ok)
return Tfa98xx_Error_Bad_Parameter;
}
}
return err;
}
unsigned int tfa98xx_get_profile_sr(struct tfa_device *tfa, unsigned int prof_idx)
{
nxpTfaBitfield_t *bitf;
unsigned int i;
nxpTfaDeviceList_t *dev;
nxpTfaProfileList_t *prof;
int fs_profile = -1;
dev = tfaContDevice(tfa->cnt, tfa->dev_idx);
if (!dev)
return 0;
prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, prof_idx);
if (!prof)
return 0;
/* Check profile fields first */
for (i = 0; i < prof->length; i++) {
if (prof->list[i].type == dscDefault)
break;
/* check for profile settingd (AUDFS) */
if (prof->list[i].type == dscBitfield) {
bitf = (nxpTfaBitfield_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
if (bitf->field == TFA_FAM(tfa, AUDFS)) {
fs_profile = bitf->value;
break;
}
}
}
if (tfa->verbose)
pr_debug("%s - profile fs: 0x%x = %dHz (%d - %d)\n",
__FUNCTION__, fs_profile,
tfa98xx_sr_from_field(fs_profile),
tfa->dev_idx, prof_idx);
if (fs_profile != -1)
return tfa98xx_sr_from_field(fs_profile);
/* Check for container default setting */
/* process the list until a patch, file of profile is encountered */
for (i = 0; i < dev->length; i++) {
if (dev->list[i].type == dscPatch ||
dev->list[i].type == dscFile ||
dev->list[i].type == dscProfile)
break;
if (dev->list[i].type == dscBitfield) {
bitf = (nxpTfaBitfield_t *)(dev->list[i].offset + (uint8_t *)tfa->cnt);
if (bitf->field == TFA_FAM(tfa, AUDFS)) {
fs_profile = bitf->value;
break;
}
}
/* Ignore register case */
}
if (tfa->verbose)
pr_debug("%s - default fs: 0x%x = %dHz (%d - %d)\n",
__FUNCTION__, fs_profile,
tfa98xx_sr_from_field(fs_profile),
tfa->dev_idx, prof_idx);
if (fs_profile != -1)
return tfa98xx_sr_from_field(fs_profile);
return 48000; /* default of HW */
}
static enum Tfa98xx_Error get_sample_rate_info(struct tfa_device *tfa, nxpTfaProfileList_t *prof, nxpTfaProfileList_t *previous_prof, int fs_previous_profile)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaBitfield_t *bitf;
unsigned int i;
int fs_default_profile = 8; /* default is 48kHz */
int fs_next_profile = 8; /* default is 48kHz */
/* ---------- default settings previous profile ---------- */
for (i = 0; i < previous_prof->length; i++) {
/* Search for the default section */
if (i == 0) {
while (previous_prof->list[i].type != dscDefault && i < previous_prof->length) {
i++;
}
i++;
}
/* Only if we found the default section search for AUDFS */
if (i < previous_prof->length) {
if (previous_prof->list[i].type == dscBitfield) {
bitf = (nxpTfaBitfield_t *)(previous_prof->list[i].offset + (uint8_t *)tfa->cnt);
if (bitf->field == TFA_FAM(tfa, AUDFS)) {
fs_default_profile = bitf->value;
break;
}
}
}
}
/* ---------- settings next profile ---------- */
for (i = 0; i < prof->length; i++) {
/* We only want to write the values before the default section */
if (prof->list[i].type == dscDefault)
break;
/* search for AUDFS */
if (prof->list[i].type == dscBitfield) {
bitf = (nxpTfaBitfield_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
if (bitf->field == TFA_FAM(tfa, AUDFS)) {
fs_next_profile = bitf->value;
break;
}
}
}
/* Enable if needed for debugging!
if (tfa->verbose) {
pr_debug("sample rate from the previous profile: %d \n", fs_previous_profile);
pr_debug("sample rate in the default section: %d \n", fs_default_profile);
pr_debug("sample rate for the next profile: %d \n", fs_next_profile);
}
*/
if (fs_next_profile != fs_default_profile) {
if (tfa->verbose)
pr_debug("Writing delay tables for AUDFS=%d \n", fs_next_profile);
/* If the AUDFS from the next profile is not the same as
* the AUDFS from the default we need to write new delay tables
*/
err = tfa98xx_dsp_write_tables(tfa, fs_next_profile);
} else if (fs_default_profile != fs_previous_profile) {
if (tfa->verbose)
pr_debug("Writing delay tables for AUDFS=%d \n", fs_default_profile);
/* But if we do not have a new AUDFS in the next profile and
* the AUDFS from the default profile is not the same as the AUDFS
* from the previous profile we also need to write new delay tables
*/
err = tfa98xx_dsp_write_tables(tfa, fs_default_profile);
}
return err;
}
/*
* process all items in the profilelist
* NOTE an error return during processing will leave the device muted
*
*/
enum Tfa98xx_Error tfaContWriteProfile(struct tfa_device *tfa, int prof_idx, int vstep_idx)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaProfileList_t *prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, prof_idx);
nxpTfaProfileList_t *previous_prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, tfa_dev_get_swprof(tfa));
char buffer[(MEMTRACK_MAX_WORDS * 4) + 4] = { 0 }; //every word requires 3 or 4 bytes, and 3 or 4 is the msg
unsigned int i, k = 0, j = 0, tries = 0;
nxpTfaFileDsc_t *file;
int size = 0, ready, fs_previous_profile = 8; /* default fs is 48kHz*/
if (!prof || !previous_prof) {
pr_err("Error trying to get the (previous) swprofile \n");
return Tfa98xx_Error_Bad_Parameter;
}
if (tfa->verbose) {
tfa98xx_trace_printk("device:%s profile:%s vstep:%d\n", tfaContDeviceName(tfa->cnt, tfa->dev_idx),
tfaContProfileName(tfa->cnt, tfa->dev_idx, prof_idx), vstep_idx);
}
/* We only make a power cycle when the profiles are not in the same group */
if (prof->group == previous_prof->group && prof->group != 0) {
if (tfa->verbose) {
pr_debug("The new profile (%s) is in the same group as the current profile (%s) \n",
tfaContGetString(tfa->cnt, &prof->name), tfaContGetString(tfa->cnt, &previous_prof->name));
}
} else {
/* mute */
err = tfaRunMute(tfa);
if (err)
return err;
/* Get current sample rate before we start switching */
fs_previous_profile = TFA_GET_BF(tfa, AUDFS);
/* clear SBSL to make sure we stay in initCF state */
if (tfa->tfa_family == 2) {
TFA_SET_BF_VOLATILE(tfa, SBSL, 0);
}
/* When we switch profile we first power down the subsystem
* This should only be done when we are in operating mode
*/
if (((tfa->tfa_family == 2) && (TFA_GET_BF(tfa, MANSTATE) >= 6)) || (tfa->tfa_family != 2)) {
err = tfa98xx_powerdown(tfa, 1);
if (err)
return err;
/* Wait until we are in PLL powerdown */
do {
err = tfa98xx_dsp_system_stable(tfa, &ready);
if (!ready)
break;
else
msleep_interruptible(10); /* wait 10ms to avoid busload */
tries++;
} while (tries <= 100);
if (tries > 100) {
pr_debug("Wait for PLL powerdown timed out!\n");
return Tfa98xx_Error_StateTimedOut;
}
} else {
pr_debug("No need to go to powerdown now \n");
}
}
/* set all bitfield settings */
/* First set all default settings */
if (tfa->verbose) {
pr_debug("---------- default settings profile: %s (%d) ---------- \n",
tfaContGetString(tfa->cnt, &previous_prof->name), tfa_dev_get_swprof(tfa));
}
err = show_current_state(tfa);
/* Loop profile length */
for (i = 0; i < previous_prof->length; i++) {
/* Search for the default section */
if (i == 0) {
while (previous_prof->list[i].type != dscDefault && i < previous_prof->length) {
i++;
}
i++;
}
/* Only if we found the default section try writing the items */
if (i < previous_prof->length) {
if (tfaContWriteItem(tfa, &previous_prof->list[i]) != Tfa98xx_Error_Ok)
return Tfa98xx_Error_Bad_Parameter;
}
}
if (tfa->verbose)
pr_debug("---------- new settings profile: %s (%d) ---------- \n",
tfaContGetString(tfa->cnt, &prof->name), prof_idx);
/* set new settings */
for (i = 0; i < prof->length; i++) {
/* Remember where we currently are with writing items*/
j = i;
/* We only want to write the values before the default section when we switch profile */
/* process and write all non-file items */
switch (prof->list[i].type) {
case dscFile:
case dscPatch:
case dscSetInputSelect:
case dscSetOutputSelect:
case dscSetProgramConfig:
case dscSetLagW:
case dscSetGains:
case dscSetvBatFactors:
case dscSetSensesCal:
case dscSetSensesDelay:
case dscSetMBDrc:
case dscSetFwkUseCase:
case dscSetVddpConfig:
case dscCmd:
case dscFilter:
case dscDefault:
/* When one of these files are found, we exit */
i = prof->length;
break;
default:
err = tfaContWriteItem(tfa, &prof->list[i]);
if (err != Tfa98xx_Error_Ok)
return Tfa98xx_Error_Bad_Parameter;
break;
}
}
if (prof->group != previous_prof->group || prof->group == 0) {
if (tfa->tfa_family == 2)
TFA_SET_BF_VOLATILE(tfa, MANSCONF, 1);
/* Leave powerdown state */
err = tfa_cf_powerup(tfa);
if (err)
return err;
err = show_current_state(tfa);
if (tfa->tfa_family == 2) {
/* Reset SBSL to 0 (workaround of enbl_powerswitch=0) */
TFA_SET_BF_VOLATILE(tfa, SBSL, 0);
/* Sending commands to DSP we need to make sure RST is 0 (otherwise we get no response)*/
TFA_SET_BF(tfa, RST, 0);
}
}
/* Check if there are sample rate changes */
err = get_sample_rate_info(tfa, prof, previous_prof, fs_previous_profile);
if (err)
return err;
/* Write files from previous profile (default section)
* Should only be used for the patch&trap patch (file)
*/
if (tfa->ext_dsp != 0) {
if (tfa->tfa_family == 2) {
for (i = 0; i < previous_prof->length; i++) {
/* Search for the default section */
if (i == 0) {
while (previous_prof->list[i].type != dscDefault && i < previous_prof->length) {
i++;
}
i++;
}
/* Only if we found the default section try writing the file */
if (i < previous_prof->length) {
if (previous_prof->list[i].type == dscFile || previous_prof->list[i].type == dscPatch) {
/* Only write this once */
if (tfa->verbose && k == 0) {
pr_debug("---------- files default profile: %s (%d) ---------- \n",
tfaContGetString(tfa->cnt, &previous_prof->name), prof_idx);
k++;
}
file = (nxpTfaFileDsc_t *)(previous_prof->list[i].offset + (uint8_t *)tfa->cnt);
err = tfaContWriteFile(tfa, file, vstep_idx, TFA_MAX_VSTEP_MSG_MARKER);
}
}
}
}
if (tfa->verbose) {
pr_debug("---------- files new profile: %s (%d) ---------- \n",
tfaContGetString(tfa->cnt, &prof->name), prof_idx);
}
}
/* write everything until end or the default section starts
* Start where we currenly left */
for (i = j; i < prof->length; i++) {
/* We only want to write the values before the default section when we switch profile */
if (prof->list[i].type == dscDefault) {
break;
}
switch (prof->list[i].type) {
case dscFile:
case dscPatch:
/* For tiberius stereo 1 device does not have a dsp! */
if (tfa->ext_dsp != 0) {
file = (nxpTfaFileDsc_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
err = tfaContWriteFile(tfa, file, vstep_idx, TFA_MAX_VSTEP_MSG_MARKER);
}
break;
case dscSetInputSelect:
case dscSetOutputSelect:
case dscSetProgramConfig:
case dscSetLagW:
case dscSetGains:
case dscSetvBatFactors:
case dscSetSensesCal:
case dscSetSensesDelay:
case dscSetMBDrc:
case dscSetFwkUseCase:
case dscSetVddpConfig:
/* For tiberius stereo 1 device does not have a dsp! */
if (tfa->ext_dsp != 0) {
create_dsp_buffer_msg(tfa, (nxpTfaMsg_t *)
(prof->list[i].offset + (char *)tfa->cnt), buffer, &size);
err = dsp_msg(tfa, size, buffer);
if (tfa->verbose) {
pr_debug("command: %s=0x%02x%02x%02x \n",
tfaContGetCommandString(prof->list[i].type),
(unsigned char)buffer[0], (unsigned char)buffer[1], (unsigned char)buffer[2]);
}
}
break;
case dscCmd:
/* For tiberius stereo 1 device does not have a dsp! */
if (tfa->ext_dsp != 0) {
size = *(uint16_t *)(prof->list[i].offset + (char *)tfa->cnt);
err = dsp_msg(tfa, size, prof->list[i].offset + 2 + (char *)tfa->cnt);
if (tfa->verbose) {
const char *cmd_id = prof->list[i].offset + 2 + (char *)tfa->cnt;
pr_debug("Writing cmd=0x%02x%02x%02x \n", (uint8_t)cmd_id[0], (uint8_t)cmd_id[1], (uint8_t)cmd_id[2]);
}
}
break;
default:
/* This allows us to write bitfield, registers or xmem after files */
if (tfaContWriteItem(tfa, &prof->list[i]) != Tfa98xx_Error_Ok) {
return Tfa98xx_Error_Bad_Parameter;
}
break;
}
if (err != Tfa98xx_Error_Ok) {
return err;
}
}
if ((prof->group != previous_prof->group || prof->group == 0) && (tfa->tfa_family == 2)) {
if (TFA_GET_BF(tfa, REFCKSEL) == 0) {
/* set SBSL to go to operation mode */
TFA_SET_BF_VOLATILE(tfa, SBSL, 1);
}
}
return err;
}
/*
* process only vstep in the profilelist
*
*/
enum Tfa98xx_Error tfaContWriteFilesVstep(struct tfa_device *tfa, int prof_idx, int vstep_idx)
{
nxpTfaProfileList_t *prof = tfaContGetDevProfList(tfa->cnt, tfa->dev_idx, prof_idx);
unsigned int i;
nxpTfaFileDsc_t *file;
nxpTfaHeader_t *hdr;
nxpTfaHeaderType_t type;
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
if (!prof)
return Tfa98xx_Error_Bad_Parameter;
if (tfa->verbose)
tfa98xx_trace_printk("device:%s profile:%s vstep:%d\n", tfaContDeviceName(tfa->cnt, tfa->dev_idx),
tfaContProfileName(tfa->cnt, tfa->dev_idx, prof_idx), vstep_idx);
/* write vstep file only! */
for (i = 0; i < prof->length; i++) {
if (prof->list[i].type == dscFile) {
file = (nxpTfaFileDsc_t *)(prof->list[i].offset + (uint8_t *)tfa->cnt);
hdr = (nxpTfaHeader_t *)file->data;
type = (nxpTfaHeaderType_t)hdr->id;
switch (type) {
case volstepHdr:
if (tfaContWriteFile(tfa, file, vstep_idx, TFA_MAX_VSTEP_MSG_MARKER))
return Tfa98xx_Error_Bad_Parameter;
break;
default:
break;
}
}
}
return err;
}
char *tfaContGetString(nxpTfaContainer_t *cnt, nxpTfaDescPtr_t *dsc)
{
if (dsc->type != dscString)
return "Undefined string";
return dsc->offset + (char *)cnt;
}
char *tfaContGetCommandString(uint32_t type)
{
if (type == dscSetInputSelect)
return "SetInputSelector";
else if (type == dscSetOutputSelect)
return "SetOutputSelector";
else if (type == dscSetProgramConfig)
return "SetProgramConfig";
else if (type == dscSetLagW)
return "SetLagW";
else if (type == dscSetGains)
return "SetGains";
else if (type == dscSetvBatFactors)
return "SetvBatFactors";
else if (type == dscSetSensesCal)
return "SetSensesCal";
else if (type == dscSetSensesDelay)
return "SetSensesDelay";
else if (type == dscSetMBDrc)
return "SetMBDrc";
else if (type == dscSetFwkUseCase)
return "SetFwkUseCase";
else if (type == dscSetVddpConfig)
return "SetVddpConfig";
else if (type == dscFilter)
return "filter";
else
return "Undefined string";
}
/*
* Get the name of the device at a certain index in the container file
* return device name
*/
char *tfaContDeviceName(nxpTfaContainer_t *cnt, int dev_idx)
{
nxpTfaDeviceList_t *dev;
dev = tfaContDevice(cnt, dev_idx);
if (dev == NULL)
return "!ERROR!";
return tfaContGetString(cnt, &dev->name);
}
/*
* Get the application name from the container file application field
* note that the input stringbuffer should be sizeof(application field)+1
*
*/
int tfa_cnt_get_app_name(struct tfa_device *tfa, char *name)
{
unsigned int i;
int len = 0;
for (i = 0; i < sizeof(tfa->cnt->application); i++) {
if (isalnum(tfa->cnt->application[i])) /* copy char if valid */
name[len++] = tfa->cnt->application[i];
if (tfa->cnt->application[i] == '\0')
break;
}
name[len++] = '\0';
return len;
}
/*
* Get profile index of the calibration profile.
* Returns: (profile index) if found, (-2) if no
* calibration profile is found or (-1) on error
*/
int tfaContGetCalProfile(struct tfa_device *tfa)
{
int prof, cal_idx = -2;
if ((tfa->dev_idx < 0) || (tfa->dev_idx >= tfa->cnt->ndev))
return ERR;
/* search for the calibration profile in the list of profiles */
for (prof = 0; prof < tfa->cnt->nprof; prof++) {
if (strstr(tfaContProfileName(tfa->cnt,
tfa->dev_idx, prof), ".cal") != NULL) {
cal_idx = prof;
pr_debug("Using calibration profile: '%s'\n",
tfaContProfileName(tfa->cnt, tfa->dev_idx, prof));
break;
}
}
return cal_idx;
}
/**
* Is the profile a tap profile
*/
int tfaContIsTapProfile(struct tfa_device *tfa, int prof_idx)
{
if ((tfa->dev_idx < 0) || (tfa->dev_idx >= tfa->cnt->ndev))
return ERR;
/* Check if next profile is tap profile */
if (strstr(tfaContProfileName(tfa->cnt,
tfa->dev_idx, prof_idx), ".tap") != NULL) {
pr_debug("Using Tap profile: '%s'\n",
tfaContProfileName(tfa->cnt, tfa->dev_idx, prof_idx));
return 1;
}
return 0;
}
/*
* Get the name of the profile at certain index for a device in the container
* file return profile name
*/
char *tfaContProfileName(nxpTfaContainer_t *cnt, int dev_idx, int prof_idx)
{
nxpTfaProfileList_t *prof = NULL;
/* the Nth profiles for this device */
prof = tfaContGetDevProfList(cnt, dev_idx, prof_idx);
/* If the index is out of bound */
if (prof == NULL)
return "NONE";
return tfaContGetString(cnt, &prof->name);
}
/*
* return 1st profile list
*/
nxpTfaProfileList_t *tfaContGet1stProfList(nxpTfaContainer_t *cont)
{
nxpTfaProfileList_t *prof;
uint8_t *b = (uint8_t *)cont;
int maxdev = 0;
nxpTfaDeviceList_t *dev;
// get nr of devlists
maxdev = cont->ndev;
// get last devlist
dev = tfaContGetDevList(cont, maxdev - 1);
if (dev == NULL)
return NULL;
// the 1st profile starts after the last device list
b = (uint8_t *)dev + sizeof(nxpTfaDeviceList_t) +
dev->length * (sizeof(nxpTfaDescPtr_t));
prof = (nxpTfaProfileList_t *)b;
return prof;
}
/*
* return 1st livedata list
*/
nxpTfaLiveDataList_t *tfaContGet1stLiveDataList(nxpTfaContainer_t *cont)
{
nxpTfaLiveDataList_t *ldata;
nxpTfaProfileList_t *prof;
nxpTfaDeviceList_t *dev;
uint8_t *b = (uint8_t *)cont;
int maxdev, maxprof;
// get nr of devlists+1
maxdev = cont->ndev;
// get nr of proflists
maxprof = cont->nprof;
// get last devlist
dev = tfaContGetDevList(cont, maxdev - 1);
// the 1st livedata starts after the last device list
b = (uint8_t *)dev + sizeof(nxpTfaDeviceList_t) +
dev->length * (sizeof(nxpTfaDescPtr_t));
while (maxprof != 0) {
// get last proflist
prof = (nxpTfaProfileList_t *)b;
b += sizeof(nxpTfaProfileList_t) +
((prof->length - 1) * (sizeof(nxpTfaDescPtr_t)));
maxprof--;
}
/* Else the marker falls off */
b += 4; //bytes
ldata = (nxpTfaLiveDataList_t *)b;
return ldata;
}
/*
* return the device list pointer
*/
nxpTfaDeviceList_t *tfaContDevice(nxpTfaContainer_t *cnt, int dev_idx)
{
return tfaContGetDevList(cnt, dev_idx);
}
/*
* return the next profile:
* - assume that all profiles are adjacent
* - calculate the total length of the input
* - the input profile + its length is the next profile
*/
nxpTfaProfileList_t *tfaContNextProfile(nxpTfaProfileList_t *prof)
{
uint8_t *this, *next; /* byte pointers for byte pointer arithmetic */
nxpTfaProfileList_t *nextprof;
int listlength; /* total length of list in bytes */
if (prof == NULL)
return NULL;
if (prof->ID != TFA_PROFID)
return NULL; /* invalid input */
this = (uint8_t *)prof;
/* nr of items in the list, length includes name dsc so - 1*/
listlength = (prof->length - 1) * sizeof(nxpTfaDescPtr_t);
/* the sizeof(nxpTfaProfileList_t) includes the list[0] length */
next = this + listlength +
sizeof(nxpTfaProfileList_t);// - sizeof(nxpTfaDescPtr_t);
nextprof = (nxpTfaProfileList_t *)next;
if (nextprof->ID != TFA_PROFID)
return NULL;
return nextprof;
}
/*
* return the next livedata
*/
nxpTfaLiveDataList_t *tfaContNextLiveData(nxpTfaLiveDataList_t *livedata)
{
nxpTfaLiveDataList_t *nextlivedata = (nxpTfaLiveDataList_t *)
((char *)livedata + (livedata->length * 4) +
sizeof(nxpTfaLiveDataList_t) - 4);
if (nextlivedata->ID == TFA_LIVEDATAID)
return nextlivedata;
return NULL;
}
/*
* check CRC for container
* CRC is calculated over the bytes following the CRC field
*
* return non zero value on error
*/
int tfaContCrcCheckContainer(nxpTfaContainer_t *cont)
{
uint8_t *base;
size_t size;
uint32_t crc;
// ptr to bytes following the CRC field
base = (uint8_t *)&cont->CRC + 4;
// nr of bytes following the CRC field
size = (size_t)(cont->size - (base - (uint8_t *)cont));
crc = ~crc32_le(~0u, base, size);
return crc != cont->CRC;
}
static void get_all_features_from_cnt(struct tfa_device *tfa,
int *hw_feature_register, int sw_feature_register[2])
{
nxpTfaFeatures_t *features;
int i;
nxpTfaDeviceList_t *dev = tfaContDevice(tfa->cnt, tfa->dev_idx);
/* Init values in case no keyword is defined in cnt file: */
*hw_feature_register = -1;
sw_feature_register[0] = -1;
sw_feature_register[1] = -1;
if (dev == NULL)
return;
// process the device list
for (i = 0; i < dev->length; i++) {
if (dev->list[i].type == dscFeatures) {
features = (nxpTfaFeatures_t *)(dev->list[i].offset +
(uint8_t *)tfa->cnt);
*hw_feature_register = features->value[0];
sw_feature_register[0] = features->value[1];
sw_feature_register[1] = features->value[2];
break;
}
}
}
/* wrapper function */
void get_hw_features_from_cnt(struct tfa_device *tfa, int *hw_feature_register)
{
int sw_feature_register[2];
get_all_features_from_cnt(tfa, hw_feature_register, sw_feature_register);
}
/* wrapper function */
void get_sw_features_from_cnt(struct tfa_device *tfa,
int sw_feature_register[2])
{
int hw_feature_register;
get_all_features_from_cnt(tfa, &hw_feature_register, sw_feature_register);
}
enum Tfa98xx_Error tfa98xx_factory_trimmer(struct tfa_device *tfa)
{
return (tfa->dev_ops.factory_trimmer)(tfa);
}
enum Tfa98xx_Error tfa_set_filters(struct tfa_device *tfa, int prof_idx)
{
enum Tfa98xx_Error err = Tfa98xx_Error_Ok;
nxpTfaProfileList_t *prof = tfaContGetDevProfList(tfa->cnt,
tfa->dev_idx, prof_idx);
unsigned int i;
if (!prof)
return Tfa98xx_Error_Bad_Parameter;
/* If we are in powerdown there is no need to set filters */
if (TFA_GET_BF(tfa, PWDN) == 1)
return Tfa98xx_Error_Ok;
/* loop the profile to find filter settings */
for (i = 0; i < prof->length; i++) {
/* We only want to write the values before the default section */
if (prof->list[i].type == dscDefault)
break;
/* write all filter settings */
if (prof->list[i].type == dscFilter) {
if (tfaContWriteItem(tfa, &prof->list[i]) != Tfa98xx_Error_Ok)
return err;
}
}
return err;
}
int tfa_tib_dsp_msgmulti(struct tfa_device *tfa, int length,
const char *buffer)
{
uint8_t *buf = (uint8_t *)buffer;
static uint8_t *blob = NULL, *blobptr; /* TODO: not multi-thread safe */
static int total; /* TODO: not multi-thread safe */
int post_len = 0;
/* checks for 24b_BE or 32_LE */
int len_word_in_bytes = (tfa->convert_dsp32) ? 4 : 3;
/*
* TODO: get rid of these magic constants max size should depend
* on the tfa device type
*/
int tfadsp_max_msg_size = (tfa->convert_dsp32) ? 5336 : 4000;
/* No data found*/
if (length == -1 && blob == NULL) {
return ERR;
}
if (length == -1) {
int i;
/* set last length field to zero */
for (i = total; i < (total + len_word_in_bytes); i++) {
blob[i] = 0;
}
total += len_word_in_bytes;
memcpy(buf, blob, total);
kfree(blob);
/* Set to NULL pointer, otherwise no new malloc is done! */
blob = NULL;
return total;
}
if (blob == NULL) {
if (tfa->verbose)
pr_debug("%s, Creating the multi-message \n\n", __FUNCTION__);
blob = kmalloc(tfadsp_max_msg_size, GFP_KERNEL);
/* add command ID for multi-msg = 0x008015 */
if (tfa->convert_dsp32) {
blob[0] = 0x15;
blob[1] = 0x80;
blob[2] = 0x0;
blob[3] = 0x0;
} else {
blob[0] = 0x0;
blob[1] = 0x80;
blob[2] = 0x15;
}
blobptr = blob;
blobptr += len_word_in_bytes;
total = len_word_in_bytes;
}
if (tfa->verbose) {
pr_debug("%s, id:0x%02x%02x%02x, length:%d \n", __FUNCTION__,
buf[0], buf[1], buf[2], length);
}
/* check total message size after concatination */
post_len = total + length + (2 * len_word_in_bytes);
if (post_len > tfadsp_max_msg_size) {
/* pr_debug("New multi-message too large! (%d >= %d (max.)),
current length: %d\n", post_len, tfadsp_max_msg_size, total); */
return Tfa98xx_Error_Buffer_too_small;
}
/* add length field (length in words) to the multi message */
if (tfa->convert_dsp32) {
*blobptr++ = (uint8_t)((length / len_word_in_bytes) & 0xff); /* lsb */
*blobptr++ = (uint8_t)(((length / len_word_in_bytes) & 0xff00) >> 8); /* msb */
*blobptr++ = 0x0;
*blobptr++ = 0x0;
} else {
*blobptr++ = 0x0;
/* msb */
*blobptr++ = (uint8_t)(((length / len_word_in_bytes) & 0xff00) >> 8);
/* lsb */
*blobptr++ = (uint8_t)((length / len_word_in_bytes) & 0xff);
}
memcpy(blobptr, buf, length);
blobptr += length;
total += (length + len_word_in_bytes);
/* SetRe25 message is always the last message of the multi-msg */
if (tfa->convert_dsp32) {
if (buf[1] == 0x81 && buf[0] == SB_PARAM_SET_RE25C)
return 1; /* 1 means last message is done! */
} else {
if (buf[1] == 0x81 && buf[2] == SB_PARAM_SET_RE25C)
return 1; /* 1 means last message is done! */
}
return 0;
}