blob: 1d61bcbca9425db2112d91f689baec66bbb9e80c [file] [log] [blame]
/*
* Copyright (C) NXP Semiconductors (PLMA)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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/cdev.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/crc32.h>
#include "tfa98xx-core.h"
#include "tfa98xx-regs.h"
#include "tfa_container.h"
#include "tfa_dsp.h"
#undef pr_fmt
#define pr_fmt(fmt) "%s(%s): " fmt, __func__, tfa98xx->fw.name
int tfa_get_profile_count(struct nxpTfaDevice *dev)
{
int i, hit = 0;
if (!dev)
return 0;
for (i = 0, hit = 0; i < dev->length; i++)
if (dev->list[i].type == dscProfile)
hit++;
return hit;
}
struct nxpTfaVolumeStep2File *tfa_get_vsteps(struct tfa98xx *tfa98xx, struct nxpTfaProfile *prof)
{
u8 *base = tfa98xx->fw.base;
int i;
// pr_debug("\n");
for(i = 0; i < prof->length; i++) {
if ( prof->list[i].type == dscFile ) {
struct nxpTfaFileDsc *file = (struct nxpTfaFileDsc *)(prof->list[i].offset + base);
struct nxpTfaHeader *hdr = (struct nxpTfaHeader *)file->data;
if (hdr->id == volstepHdr) {
struct nxpTfaVolumeStep2File *vp = (struct nxpTfaVolumeStep2File *)hdr;
pr_debug("vsteps: %d\n", vp->vsteps);
return vp;
}
}
}
return NULL;
}
int tfa_init_profile(struct tfa98xx *tfa98xx, struct nxpTfaProfile *prof, int hit)
{
struct nxpTfaVolumeStep2File *vp;
u8 *base = tfa98xx->fw.base;
vp = tfa_get_vsteps(tfa98xx, prof);
if (vp) {
tfa98xx->profiles[hit].vp = vp;
tfa98xx->profiles[hit].vsteps = vp->vsteps;
}
tfa98xx->profiles[hit].profile = prof;
tfa98xx->profiles[hit].name = base + prof->name.offset;
tfa98xx->profiles[hit].index = hit;
pr_debug("Init profile %d: %s\n", hit, tfa98xx->profiles[hit].name);
return 0;
}
static char *filename = "nxp/tfa98xx.cnt";
module_param(filename, charp, 0);
int tfa98xx_cnt_loadfile(struct tfa98xx *tfa98xx, int index)
{
struct snd_soc_codec *codec = tfa98xx->codec;
u16 addr = tfa98xx->i2c->addr;
const struct firmware *container = NULL;
struct nxpTfaContainer *data;
int i, j, hit, ret;
u32 crc32;
u8 *base;
pr_debug("I2C device: 0x%x\n", addr);
/* Load DSP config and firmware files */
ret = request_firmware(&container, filename, codec->dev);
if (ret) {
pr_err("Failed to read %s", filename);
return ret;
}
pr_debug("loaded %s - size: %zd\n", filename, container ? container->size : 0);
data = (struct nxpTfaContainer *)container->data;
pr_debug("%.2s%.2s\n", data->version, data->subversion);
pr_debug("%.8s\n", data->customer);
pr_debug("%.8s\n", data->application);
pr_debug("%.8s\n", data->type);
pr_debug("%d ndev\n", data->ndev);
pr_debug("%d nprof\n", data->nprof);
if (*(u16*)data->id != params) {
pr_err("Wrong container type %.2s", (char *)&data->id);
return -EINVAL;
}
if ((data->subversion[0] != '0') || (data->subversion[1] != '0')) {
pr_err("Container sub-version not supported: %c%c\n",
data->subversion[0] , data->subversion[1]);
return -EINVAL;
}
crc32 = ~crc32_le(~0, ((u8*)container->data)+14, container->size-14);
if (crc32 != data->CRC) {
pr_err("fw checksum test failed %x\n", crc32);
return -ENOEXEC;
}
base = (u8 *) data;
tfa98xx->fw.base = data;
for (i = 0; i < data->ndev; i++) {
struct nxpTfaDevice *dev;
struct nxpTfaProfile *prof;
struct nxpTfaFileDsc *dsc;
base = (u8*)data + data->index[i].offset;
dev = (struct nxpTfaDevice *)base;
/* Check if this is the proper device addr */
if (dev->dev != addr)
continue;
pr_debug("dev[0x%02x]: %s\n", dev->dev, (u8 *)data + dev->name.offset);
tfa98xx->fw.dev = (struct nxpTfaDevice *)base;
tfa98xx->fw.name = (u8 *)data + dev->name.offset;
tfa98xx->profile_count = tfa_get_profile_count(tfa98xx->fw.dev);
tfa98xx->profiles = devm_kzalloc(tfa98xx->codec->dev, tfa98xx->profile_count * sizeof(struct tfaprofile), GFP_KERNEL);
if (!tfa98xx->profiles)
return -ENOMEM;
for (j = 0, hit = 0; j < dev->length; j++) {
switch (dev->list[j].type) {
case dscProfile:
prof = (struct nxpTfaProfile *)((u8 *)data + dev->list[j].offset);
pr_debug("[%d] profile: %s\n", j, (u8 *)data + prof->name.offset);
tfa_init_profile(tfa98xx, prof, hit++);
break;
case dscFile:
case dscPatch:
dsc = (struct nxpTfaFileDsc *)((u8 *)data + dev->list[j].offset);
pr_debug("[%d] file: %s\n", j, (u8 *)data + dsc->name.offset);
break;
default:
//pr_debug("[%d] %d\n", j, dev->list[j].type);
break;
}
}
}
if (!tfa98xx->profiles) {
pr_err("[0x%02x] No profile data for the device\n", addr);
return -EINVAL;
}
return 0;
}
int tfa_get_vstep_count(struct tfa98xx *tfa98xx, struct nxpTfaProfile *prof)
{
u8 *base = tfa98xx->fw.base;
int i;
pr_debug("\n");
for(i = 0; i < prof->length; i++) {
if ( prof->list[i].type == dscFile ) {
struct nxpTfaFileDsc *file = (struct nxpTfaFileDsc *)(prof->list[i].offset + base);
struct nxpTfaHeader *hdr = (struct nxpTfaHeader *)file->data;
if (hdr->id == volstepHdr) {
struct nxpTfaVolumeStep2File *vp = (struct nxpTfaVolumeStep2File *)hdr;
pr_debug("vsteps: %d\n", vp->vsteps);
return vp->vsteps;
}
}
}
return 0;
}
struct nxpTfaProfile *tfaContProfile(struct tfa98xx *tfa98xx, int index)
{
struct nxpTfaDevice *dev = tfa98xx->fw.dev;
struct nxpTfaProfile *prof;
char *base = tfa98xx->fw.base;
int j, hit;
pr_debug("%d\n", index);
for (j = 0, hit = 0; j < dev->length; j++) {
if (dev->list[j].type == dscProfile) {
prof = (struct nxpTfaProfile *)(base + dev->list[j].offset);
if (hit++ == index) {
pr_debug("select profile[%d]='%s'\n", index, base + prof->name.offset);
return prof;
}
}
}
return NULL;
}
/*
* return the bitfield
*/
struct nxpTfaBitfield tfaContDsc2Bf(struct nxpTfaDescPtr dsc)
{
u32 *ptr = (u32 *) (&dsc);
union {
struct nxpTfaBitfield bf;
u32 num;
} num_bf;
num_bf.num = *ptr & TFA_BITFIELDDSCMSK;
return num_bf.bf;
}
TFA_NAMETABLE
char *tfaContBfName(u16 num)
{
int n = 0;
do {
if (TfaBfNames[n].bfEnum == num)
return TfaBfNames[n].bfName;
}
while (TfaBfNames[n++].bfEnum != 0xffff);
return TfaBfNames[n-1].bfName;
}
/*
* write reg and bitfield items in the profilelist the target
*/
int tfaContWriteRegsProf(struct tfa98xx *tfa98xx, int profile)
{
struct nxpTfaProfile *prof = tfaContProfile(tfa98xx, profile);
u8 *base = tfa98xx->fw.base;
int i;
int err = 0;
pr_debug("profile: %d\n", profile);
if (!prof) {
return -EINVAL;
}
/* process the list until a patch, file of profile is encountered */
for (i = 0; i < prof->length; i++) {
if ( prof->list[i].type == dscPatch ||
prof->list[i].type ==dscFile ||
prof->list[i].type ==dscProfile )
break;
if (prof->list[i].type & dscBitfieldBase) {
err = tfaRunWriteBitfield(tfa98xx, tfaContDsc2Bf(prof->list[i]));
}
if (!prof->list[i].type == dscRegister) {
err = tfaRunWriteRegister(tfa98xx, (struct nxpTfaRegpatch *)(base + prof->list[i].offset));
}
if (err)
break;
}
return err;
}
/*
* write reg and bitfield items in the devicelist to the target
*/
int tfaContWriteRegsDev(struct tfa98xx *tfa98xx)
{
struct nxpTfaDevice *dev = tfa98xx->fw.dev;
u8 *base = tfa98xx->fw.base;
int i;
int err = 0;
pr_debug("\n");
if (!dev)
return -EINVAL;
/* 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 & dscBitfieldBase) {
err = tfaRunWriteBitfield(tfa98xx, tfaContDsc2Bf(dev->list[i]));
}
if (dev->list[i].type == dscRegister) {
pr_debug("reg %x\n", dev->list[i].type);
err = tfaRunWriteRegister(tfa98xx, (struct nxpTfaRegpatch *)(dev->list[i].offset + base));
}
if (err)
break;
}
return err;
}
/*
* show the contents of the header
*/
void tfaContShowHeader(struct tfa98xx *tfa98xx, struct nxpTfaHeader *hdr)
{
char id[2];
pr_debug("File header\n");
id[1] = hdr->id >> 8;
id[0] = hdr->id & 0xff;
pr_debug(" id:%.2s version:%.2s subversion:%.2s\n", id, hdr->version, hdr->subversion);
pr_debug(" size:%d CRC:0x%08x \n", hdr->size, hdr->CRC);
pr_debug(" customer:%.8s application:%.8s type:%.8s\n", hdr->customer, hdr->application, hdr->type);
}
/*
* write patchfile in the devicelist to the target
*/
int tfaContWritePatch(struct tfa98xx *tfa98xx)
{
struct nxpTfaDevice *dev = tfa98xx->fw.dev;
u8 *base = tfa98xx->fw.base;
struct nxpTfaFileDsc *file;
struct nxpTfaPatch *patchfile;
int size;
int i;
pr_debug("\n");
/* process the list until a patch is encountered */
for(i = 0; i < dev->length; i++) {
if (dev->list[i].type == dscPatch) {
file = (struct nxpTfaFileDsc *)(dev->list[i].offset + base);
patchfile =(struct nxpTfaPatch *)&file->data;
tfaContShowHeader(tfa98xx, &patchfile->hdr);
/* size is total length including header */
size = patchfile->hdr.size - sizeof(struct nxpTfaPatch);
return tfa98xx_dsp_patch(tfa98xx, size, (const u8*) patchfile->data);
}
}
return -EINVAL;
}
int tfaContWriteFilterbank(struct tfa98xx *tfa98xx, struct nxpTfaFilter *filter)
{
unsigned char biquad_index;
int ret = 0;
for (biquad_index = 0; biquad_index < 10; biquad_index++) {
if (filter[biquad_index].enabled) {
ret = tfa98xx_dsp_biquad_set_coeff(tfa98xx, biquad_index + 1, // start @1
sizeof(filter[biquad_index].biquad.bytes),
filter[biquad_index].biquad.bytes);
} else {
ret = tfa98xx_dsp_biquad_disable(tfa98xx, biquad_index+1);
}
if (ret) {
pr_err("Error %d\n", ret);
return ret;
}
}
return 0;
}
int tfaContWriteEq(struct tfa98xx *tfa98xx, struct nxpTfaEqualizerFile *eqf)
{
return tfaContWriteFilterbank(tfa98xx, eqf->filter);
}
/*
* write a parameter file to de device
*/
int tfaContWriteVstep(struct tfa98xx *tfa98xx, struct nxpTfaVolumeStep2File *vp)
{
int vstep;
int err = 0;
vstep = tfa98xx->vstep_current;
pr_debug("vstep[%d/%d]\n", vstep, vp->vsteps - 1);
if (vstep < vp->vsteps) {
tfa98xx_set_volume(tfa98xx, vp->vstep[vstep].attenuation);
err = tfa98xx_dsp_write_preset(tfa98xx, sizeof(vp->vstep[0].preset), vp->vstep[vstep].preset);
if (err)
return err;
err = tfaContWriteFilterbank(tfa98xx, vp->vstep[vstep].filter);
if (err)
return err;
} else {
pr_err("vstep[%d] > %d\n", vstep , vp->vsteps - 1);
return -EINVAL;
}
return err;
}
/*
* write a parameter file to the device
*/
int tfaContWriteFile(struct tfa98xx *tfa98xx, struct nxpTfaFileDsc *file)
{
int size;
struct nxpTfaHeader *hdr = (struct nxpTfaHeader *)file->data;
enum tfa_cnt_header_type type;
int err = 0;
type = hdr->id;
switch (type) {
case volstepHdr:
pr_debug("volstep\n");
err = tfaContWriteVstep(tfa98xx, (struct nxpTfaVolumeStep2File *)hdr);
break;
case speakerHdr:
pr_debug("speaker\n");
size = hdr->size - sizeof(struct nxpTfaSpeakerFile);
err = tfa98xx_dsp_write_speaker_parameters(tfa98xx, size, (const u8 *)((struct nxpTfaSpeakerFile *)hdr)->data);
break;
case presetHdr:
pr_debug("preset\n");
size = hdr->size - sizeof(struct nxpTfaPresetFile);
err = tfa98xx_dsp_write_preset(tfa98xx, size, (const u8 *)((struct nxpTfaPresetFile *)hdr)->data);
break;
case configHdr:
pr_debug("config\n");
size = hdr->size - sizeof(struct nxpTfaConfigFile);
err = tfa98xx_dsp_write_config(tfa98xx, size, (const u8 *)((struct nxpTfaConfigFile *)hdr)->data);
break;
case equalizerHdr:
pr_debug("equalizer\n");
tfaContWriteEq(tfa98xx, (struct nxpTfaEqualizerFile *) hdr);
break;
case patchHdr:
pr_debug("patch\n");
size = hdr->size - sizeof(struct nxpTfaPatch );
err = tfa98xx_dsp_patch(tfa98xx, size, (const u8 *)((struct nxpTfaPatch *)hdr)->data);
break;
case drcHdr:
/*
* 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 = sizeof(struct drcParamBlock);
/* fixed size for first block */
err = tfa98xx_dsp_write_drc(tfa98xx, size, (const u8 *)((struct nxpTfaDrcFile *)hdr)->data);
break;
default:
pr_err("Header is of unknown type: 0x%x\n", type);
return -EINVAL;
}
return err;
}
int tfaContWriteItem(struct tfa98xx *tfa98xx, struct nxpTfaDescPtr * dsc)
{
struct nxpTfaFileDsc *file;
struct nxpTfaRegpatch *reg;
struct nxpTfaMode *mode;
u8 *base = tfa98xx->fw.base;
int ret = 0;
pr_debug("\n");
switch (dsc->type) {
case dscDevice:
case dscProfile:
/* ignore device and profile list */
break;
case dscRegister:
reg = (struct nxpTfaRegpatch *)(dsc->offset + base);
pr_debug("$0x%2x=0x%02x,0x%02x\n", reg->address, reg->mask, reg->value);
return tfaRunWriteRegister(tfa98xx, reg);
case dscString:
/* zero terminated string */
pr_debug(";string: %s\n", dsc->offset + base);
break;
case dscFile:
case dscPatch:
/* filename + file contents */
file = (struct nxpTfaFileDsc *)(dsc->offset + base);
pr_debug("file: %s\n", file->name.offset + base);
break;
case dscMode:
mode = (struct nxpTfaMode *)(dsc->offset + base);
if (mode->value == Tfa98xx_Mode_RCV)
tfa98xx_select_mode(tfa98xx, Tfa98xx_Mode_RCV);
else
tfa98xx_select_mode(tfa98xx, Tfa98xx_Mode_Normal);
pr_debug("mode = %d\n", mode->value);
break;
case dscCfMem:
ret = tfa98xx_write_dsp_mem(tfa98xx, (struct nxpTfaDspMem *)(dsc->offset + base));
if (ret)
ret = -EINVAL;
break;
case dscFilter:
ret = tfa98xx_write_filter(tfa98xx, (struct nxpTfaBiquadSettings *)(dsc->offset + base));
break;
default:
if (dsc->type & dscBitfieldBase)
return tfaRunWriteBitfield(tfa98xx , tfaContDsc2Bf(*dsc));
}
return ret;
}
/*
* process all items in the profilelist
* NOTE an error return during processing will leave the device muted
*/
int tfaContWriteProfile(struct tfa98xx *tfa98xx, int profile, int vstep)
{
struct nxpTfaProfile *prof = tfaContProfile(tfa98xx, profile);
struct nxpTfaFileDsc *file;
u8 *base = tfa98xx->fw.base;
int i;
pr_debug("\n");
if (!prof) {
return -EINVAL;
}
tfa98xx_mute(tfa98xx); /* this will wait for SWS */
tfa98xx_powerdown(tfa98xx, 1);
/* process and write registers first */
for(i=0; i<prof->length; i++) {
if ((prof->list[i].type == dscRegister)
|| (prof->list[i].type >= dscBitfieldBase)) {
if (tfaContWriteItem(tfa98xx, &prof->list[i]))
return -EINVAL;
}
}
/* Is the DSP bypassed? */
if (!tfa98xx_is_dsp_enabled(tfa98xx))
return 0;
/* power up CF */
tfa98xx_dsp_power_up(tfa98xx);
/* process and write all other items after power up of CF */
for(i=0; i<prof->length; i++) {
if ( prof->list[i].type == dscFile ) {
file = (struct nxpTfaFileDsc *)(prof->list[i].offset + base);
if (tfaContWriteFile(tfa98xx, file))
return -EINVAL;
} else if ((prof->list[i].type != dscRegister) &&
(prof->list[i].type < dscBitfieldBase)) {
if (tfaContWriteItem(tfa98xx, &prof->list[i]))
return -EINVAL;
}
}
return 0;
}
/*
* process only vstep in the profilelist
*
*/
int tfaContWriteFilesVstep(struct tfa98xx *tfa98xx, int profile, int vstep)
{
struct nxpTfaProfile *prof = tfaContProfile(tfa98xx, profile);
struct nxpTfaFileDsc *file;
struct nxpTfaHeader *hdr;
u8 *base = tfa98xx->fw.base;
unsigned int i;
int pwdn=-1;
int ret = 0;
if (!prof)
return -EINVAL;
for(i=0; i<prof->length; i++) {
if (prof->list[i].type == dscFile) {
if (pwdn<0)
pwdn = tfa98xx_is_pwdn(tfa98xx); // cache the value
if (pwdn) {
tfa98xx_dsp_power_up(tfa98xx);
pwdn=0;
}
file = (struct nxpTfaFileDsc *)(prof->list[i].offset + base);
hdr = (struct nxpTfaHeader *)file->data;
switch (hdr->id) {
case volstepHdr:
/* set current volume step to TFA */
ret = tfaContWriteVstep(tfa98xx, (struct nxpTfaVolumeStep2File *)hdr);
if (ret)
return ret;
break;
default:
break;
}
}
}
return ret;
}
/*
* write all param files in the profilelist to the target
* this is used during startup when may be ACS is set
*/
int tfaContWriteFilesProf(struct tfa98xx *tfa98xx, int profile, int vstep)
{
struct nxpTfaProfile *prof = tfaContProfile(tfa98xx, profile);
struct nxpTfaFileDsc *file;
u8 *base = tfa98xx->fw.base;
int i;
int ret = 0;
pr_debug("\n");
if (!prof) {
return -EINVAL;
}
/* process the list and write all files */
for(i = 0; i < prof->length; i++) {
switch (prof->list[i].type) {
case dscFile:
file = (struct nxpTfaFileDsc *)(prof->list[i].offset + base);
if (tfaContWriteFile(tfa98xx, file)) {
return -EINVAL;
}
break;
case dscCfMem:
ret = tfa98xx_write_dsp_mem(tfa98xx, (struct nxpTfaDspMem *)(prof->list[i].offset + base));
if (ret)
return -EINVAL;
break;
case dscFilter:
ret = tfa98xx_write_filter(tfa98xx, (struct nxpTfaBiquadSettings *)(prof->list[i].offset + base));
if (ret)
return ret;
break;
default:
/* ignore any other type */
break;
}
}
return 0;
}
/*
* write all param files in the devicelist to the target
*/
int tfaContWriteFiles(struct tfa98xx *tfa98xx)
{
struct nxpTfaDevice *dev = tfa98xx->fw.dev;
u8 *base = tfa98xx->fw.base;
struct nxpTfaFileDsc *file;
int ret = 0;
int i;
pr_debug("\n");
/* process the list and write all files */
for(i = 0; i < dev->length; i++) {
switch (dev->list[i].type) {
case dscFile:
file = (struct nxpTfaFileDsc *)(dev->list[i].offset + base);
if (tfaContWriteFile(tfa98xx, file)) {
pr_err("error\n");
return -EINVAL;
}
break;
case dscCfMem:
ret = tfa98xx_write_dsp_mem(tfa98xx, (struct nxpTfaDspMem *)(dev->list[i].offset + base));
if (ret)
return -EINVAL;
break;
default:
/* ignore any other type */
break;
}
}
return 0;
}