| /* |
| * 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/gpio.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 |
| |
| /* size of the data buffer used for I2C transfer */ |
| #define TFA98XX_MAX_I2C_SIZE 252 |
| |
| |
| #define TFA98XX_XMEM_CALIBRATION_DONE 231 /* 0xe7 */ |
| #define TFA98XX_XMEM_IMPEDANCE 232 |
| #define TFA98XX_XMEM_COUNT_BOOT 161 /* 0xa1 */ |
| |
| /* |
| * Maximum number of retries for DSP result |
| * Keep this value low! |
| * If certain calls require longer wait conditions, the |
| * application should poll, not the API |
| * The total wait time depends on device settings. Those |
| * are application specific. |
| */ |
| #define TFA98XX_WAITRESULT_NTRIES 50 |
| #define TFA98XX_WAITRESULT_NTRIES_LONG 2000 |
| |
| |
| /* DSP module IDs */ |
| #define MODULE_FRAMEWORK 0 |
| #define MODULE_SPEAKERBOOST 1 |
| #define MODULE_BIQUADFILTERBANK 2 |
| #define MODULE_SETRE 9 |
| |
| |
| /* RPC commands IDs */ |
| /* Load a full model into SpeakerBoost. */ |
| #define SB_PARAM_SET_LSMODEL 0x06 |
| #define SB_PARAM_SET_EQ 0x0A /* 2 Equaliser Filters */ |
| #define SB_PARAM_SET_PRESET 0x0D /* Load a preset */ |
| #define SB_PARAM_SET_CONFIG 0x0E /* Load a config */ |
| #define SB_PARAM_SET_DRC 0x0F |
| #define SB_PARAM_SET_AGCINS 0x10 |
| |
| |
| /* gets the speaker calibration impedance (@25 degrees celsius) */ |
| #define SB_PARAM_GET_RE0 0x85 |
| #define SB_PARAM_GET_LSMODEL 0x86 /* Gets LoudSpeaker Model */ |
| #define SB_PARAM_GET_CONFIG_PRESET 0x80 |
| #define SB_PARAM_GET_STATE 0xC0 |
| #define SB_PARAM_GET_XMODEL 0xC1 /* Gets Excursion Model */ |
| |
| #define SPKRBST_TEMPERATURE_EXP 9 |
| |
| /* Framework params */ |
| #define FW_PARAM_SET_CURRENT_DELAY 0x03 |
| #define FW_PARAM_SET_CURFRAC_DELAY 0x06 |
| #define FW_PARAM_GET_STATE 0x84 |
| #define FW_PARAM_GET_FEATURE_BITS 0x85 |
| |
| |
| /* |
| * write a bit field |
| */ |
| int tfaRunWriteBitfield(struct tfa98xx *tfa98xx, struct nxpTfaBitfield bf) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value, oldvalue, msk, tmp; |
| union { |
| u16 field; |
| struct nxpTfaBfEnum Enum; |
| } bfUni; |
| |
| value = bf.value; |
| bfUni.field = bf.field; |
| /* bfUni.field &= 0x7fff; */ /* mask of high bit, done before */ |
| |
| pr_debug("bitfield: %s=%d (0x%x[%d..%d]=0x%x)\n", |
| tfaContBfName(bfUni.field), value, bfUni.Enum.address, |
| bfUni.Enum.pos, bfUni.Enum.pos+bfUni.Enum.len, value); |
| if (((struct nxpTfaBfEnum *)&bf.field)->address & 0x80) |
| pr_err("WARNING:not a persistant write of MTP\n"); |
| |
| oldvalue = (u16)snd_soc_read(codec, bfUni.Enum.address); |
| tmp = oldvalue; |
| |
| msk = ((1 << (bfUni.Enum.len + 1)) - 1) << bfUni.Enum.pos; |
| oldvalue &= ~msk; |
| oldvalue |= value << bfUni.Enum.pos; |
| pr_debug("bitfield: %s=%d (0x%x -> 0x%x)\n", tfaContBfName(bfUni.field), |
| value, tmp, oldvalue); |
| snd_soc_write(codec, bfUni.Enum.address, oldvalue); |
| |
| return 0; |
| } |
| |
| /* |
| * write the register based on the input address, value and mask |
| * only the part that is masked will be updated |
| */ |
| int tfaRunWriteRegister(struct tfa98xx *tfa98xx, struct nxpTfaRegpatch *reg) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value, newvalue; |
| |
| pr_debug("register: 0x%02x=0x%04x (msk=0x%04x)\n", reg->address, |
| reg->value, reg->mask); |
| |
| value = (u16)snd_soc_read(codec, reg->address); |
| value &= ~reg->mask; |
| newvalue = reg->value & reg->mask; |
| value |= newvalue; |
| snd_soc_write(codec, reg->address, value); |
| |
| return 0; |
| } |
| |
| /* |
| * tfa98xx_dsp_system_stable will compensate for the wrong behavior of CLKS |
| * to determine if the DSP subsystem is ready for patch and config loading. |
| * |
| * A MTP calibration register is checked for non-zero. |
| * |
| * Note: This only works after i2c reset as this will clear the MTP contents. |
| * When we are configured then the DSP communication will synchronize access. |
| */ |
| int tfa98xx_dsp_system_stable(struct tfa98xx *tfa98xx, int *ready) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 status, mtp0, sysctrl; |
| int tries; |
| |
| *ready = 0; |
| |
| /* check the contents of the STATUS register */ |
| status = (u16)snd_soc_read(codec, TFA98XX_STATUSREG); |
| sysctrl = (u16)snd_soc_read(codec, TFA98XX_SYS_CTRL); |
| |
| pr_debug("statusreg = 0x%04x, sysctrl=0x%04x\n", status, sysctrl); |
| |
| /* |
| * if AMPS is set then we were already configured and running |
| * no need to check further |
| */ |
| *ready = (status & TFA98XX_STATUSREG_AMPS_MSK) == |
| (TFA98XX_STATUSREG_AMPS_MSK); |
| |
| pr_debug("AMPS %d\n", *ready); |
| if (*ready) |
| return 0; |
| |
| /* check AREFS and CLKS: not ready if either is clear */ |
| *ready = (status & |
| (TFA98XX_STATUSREG_AREFS_MSK | TFA98XX_STATUSREG_CLKS_MSK)) == |
| (TFA98XX_STATUSREG_AREFS_MSK | TFA98XX_STATUSREG_CLKS_MSK); |
| pr_debug("AREFS | CLKS %d\n", *ready); |
| if (!*ready) /* if not ready go back */ |
| return 0; |
| |
| if (tfa98xx->rev != REV_TFA9890) { |
| *ready = 1; |
| return 0; |
| } |
| |
| /* |
| * check MTPB |
| * mtpbusy will be active when the subsys copies MTP to I2C |
| * 2 times retry avoids catching this short mtpbusy active period |
| */ |
| for (tries = 2; tries > 0; tries--) { |
| status = (u16)snd_soc_read(codec, TFA98XX_STATUSREG); |
| /* check the contents of the STATUS register */ |
| *ready = (status & TFA98XX_STATUSREG_MTPB_MSK) == 0; |
| if (*ready) /* if ready go on */ |
| break; |
| } |
| pr_debug("MTPB %d\n", *ready); |
| if (tries == 0) { /* ready will be 0 if retries exausted */ |
| pr_debug("Not ready %d\n", !*ready); |
| return 0; |
| } |
| |
| /* |
| * check the contents of MTP register for non-zero, |
| * this indicates that the subsys is ready |
| */ |
| mtp0 = (u16)snd_soc_read(codec, 0x84); |
| |
| *ready = (mtp0 != 0); /* The MTP register written? */ |
| pr_debug("MTP0 %d\n", *ready); |
| |
| return ret; |
| } |
| |
| /* |
| * Disable clock gating |
| */ |
| static int tfa98xx_clockgating(struct tfa98xx *tfa98xx, int on) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| |
| pr_debug("%d\n", on); |
| |
| /* The clockgating hack is used only for the tfa9890 */ |
| if (tfa98xx->rev != REV_TFA9890) |
| return 0; |
| |
| /* TFA9890: temporarily disable clock gating when dsp reset is used */ |
| value = snd_soc_read(codec, TFA98XX_CURRENTSENSE4); |
| |
| if (on) /* clock gating on - clear the bit */ |
| value &= ~TFA98XX_CURRENTSENSE4_CTRL_CLKGATECFOFF; |
| else /* clock gating off - set the bit */ |
| value |= TFA98XX_CURRENTSENSE4_CTRL_CLKGATECFOFF; |
| |
| return snd_soc_write(codec, TFA98XX_CURRENTSENSE4, value); |
| } |
| |
| /* |
| * tfa98xx_dsp_reset will deal with clock gating control in order |
| * to reset the DSP for warm state restart |
| */ |
| static int tfa98xx_dsp_reset(struct tfa98xx *tfa98xx, int state) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| |
| pr_debug("\n"); |
| |
| /* TFA9890: temporarily disable clock gating when dsp reset is used */ |
| tfa98xx_clockgating(tfa98xx, 0); |
| |
| value = snd_soc_read(codec, TFA98XX_CF_CONTROLS); |
| |
| /* set requested the DSP reset signal state */ |
| value = state ? (value | TFA98XX_CF_CONTROLS_RST_MSK) : |
| (value & ~TFA98XX_CF_CONTROLS_RST_MSK); |
| |
| snd_soc_write(codec, TFA98XX_CF_CONTROLS, value); |
| |
| /* clock gating restore */ |
| return tfa98xx_clockgating(tfa98xx, 1); |
| } |
| |
| int tfa98xx_powerdown(struct tfa98xx *tfa98xx, int powerdown) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| |
| pr_debug("%d\n", powerdown); |
| |
| /* read the SystemControl register, modify the bit and write again */ |
| value = snd_soc_read(codec, TFA98XX_SYS_CTRL); |
| |
| switch (powerdown) { |
| case 1: |
| value |= TFA98XX_SYS_CTRL_PWDN_MSK; |
| break; |
| case 0: |
| value &= ~(TFA98XX_SYS_CTRL_PWDN_MSK); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return snd_soc_write(codec, TFA98XX_SYS_CTRL, value); |
| } |
| |
| |
| static int tfa98xx_read_data(struct tfa98xx *tfa98xx, u8 address, int len, |
| u8 *data) |
| { |
| /* pr_debug("@%02x, #%d\n", address, len); */ |
| |
| if (tfa98xx_i2c_read(tfa98xx->i2c, address, data, len)) { |
| pr_err("Error during I2C read\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| void tfa98xx_convert_data2bytes(int num_data, const int *data, u8 *bytes) |
| { |
| int i, k, d; |
| /* |
| * note: cannot just take the lowest 3 bytes from the 32 bit |
| * integer, because also need to take care of clipping any |
| * value > 2&23 |
| */ |
| for (i = 0, k = 0; i < num_data; ++i, k += 3) { |
| if (data[i] >= 0) |
| d = MIN(data[i], (1 << 23) - 1); |
| else { |
| /* 2's complement */ |
| d = (1 << 24) - MIN(-data[i], 1 << 23); |
| } |
| bytes[k] = (d >> 16) & 0xFF; /* MSB */ |
| bytes[k + 1] = (d >> 8) & 0xFF; |
| bytes[k + 2] = (d) & 0xFF; /* LSB */ |
| } |
| } |
| |
| |
| /* |
| * convert DSP memory bytes to signed 24 bit integers |
| * data contains "len/3" elements |
| * bytes contains "len" elements |
| */ |
| void tfa98xx_convert_bytes2data(int len, const u8 *bytes, int *data) |
| { |
| int i, k, d; |
| int num_data = len / 3; |
| |
| for (i = 0, k = 0; i < num_data; ++i, k += 3) { |
| d = (bytes[k] << 16) | (bytes[k + 1] << 8) | (bytes[k + 2]); |
| if (bytes[k] & 0x80) /* sign bit was set */ |
| d = -((1 << 24) - d); |
| |
| data[i] = d; |
| } |
| } |
| |
| |
| int tfa98xx_dsp_read_mem(struct tfa98xx *tfa98xx, u16 start_offset, |
| int num_words, int *values) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 cf_ctrl; /* to sent to the CF_CONTROLS register */ |
| u8 bytes[MAX_PARAM_SIZE]; |
| int burst_size; /* number of words per burst size */ |
| int bytes_per_word = 3; |
| int len; |
| int *p; |
| |
| /* pr_debug("@0x%04x, #%d\n", start_offset, num_words); */ |
| |
| /* first set DMEM and AIF, leaving other bits intact */ |
| cf_ctrl = snd_soc_read(codec, TFA98XX_CF_CONTROLS); |
| cf_ctrl &= ~0x000E; /* clear AIF & DMEM */ |
| /* set DMEM, leave AIF cleared for autoincrement */ |
| cf_ctrl |= (Tfa98xx_DMEM_XMEM << 1); |
| |
| snd_soc_write(codec, TFA98XX_CF_CONTROLS, cf_ctrl); |
| |
| snd_soc_write(codec, TFA98XX_CF_MAD, start_offset); |
| |
| len = num_words * bytes_per_word; |
| p = values; |
| for (; len > 0;) { |
| burst_size = ROUND_DOWN(16, bytes_per_word); |
| if (len < burst_size) |
| burst_size = len; |
| |
| ret = tfa98xx_read_data(tfa98xx, TFA98XX_CF_MEM, burst_size, |
| bytes); |
| if (ret) |
| return ret; |
| |
| tfa98xx_convert_bytes2data(burst_size, bytes, p); |
| /* pr_debug("0x%06x\n", *p); */ |
| len -= burst_size; |
| p += burst_size / bytes_per_word; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Write all the bytes specified by len and data |
| */ |
| static int tfa98xx_write_data(struct tfa98xx *tfa98xx, u8 subaddress, int len, |
| const u8 *data) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u8 write_data[MAX_PARAM_SIZE]; |
| /* subaddress followed by data */ |
| int count = len + 1; |
| |
| /* pr_debug("%d\n", len); */ |
| |
| if (count > MAX_PARAM_SIZE) { |
| pr_err("Error param size too big %d\n", len); |
| return -EINVAL; |
| } |
| |
| write_data[0] = subaddress; |
| memcpy(write_data + 1, data, len); |
| |
| return tfa98xx_bulk_write_raw(codec, write_data, count); |
| } |
| |
| |
| static int tfa98xx_dsp_write_mem(struct tfa98xx *tfa98xx, unsigned int address, |
| int value, int memtype) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 cf_ctrl; /* the value to sent to the CF_CONTROLS register */ |
| u8 bytes[3]; |
| |
| pr_debug("@0x%04x=%d\n", address, value); |
| |
| /* first set DMEM and AIF, leaving other bits intact */ |
| cf_ctrl = snd_soc_read(codec, TFA98XX_CF_CONTROLS); |
| cf_ctrl &= ~0x000E; /* clear AIF & DMEM */ |
| |
| switch (memtype) { |
| case Tfa98xx_DMEM_PMEM: |
| cf_ctrl |= (Tfa98xx_DMEM_PMEM << 1); |
| break; |
| case Tfa98xx_DMEM_XMEM: |
| cf_ctrl |= (Tfa98xx_DMEM_XMEM << 1); |
| break; |
| case Tfa98xx_DMEM_YMEM: |
| cf_ctrl |= (Tfa98xx_DMEM_YMEM << 1); |
| break; |
| case Tfa98xx_DMEM_IOMEM: |
| cf_ctrl |= (Tfa98xx_DMEM_IOMEM << 1); |
| break; |
| } |
| snd_soc_write(codec, TFA98XX_CF_CONTROLS, cf_ctrl); |
| snd_soc_write(codec, TFA98XX_CF_MAD, address & 0xffff); |
| |
| tfa98xx_convert_data2bytes(1, &value, bytes); |
| ret = tfa98xx_write_data(tfa98xx, TFA98XX_CF_MEM, 3, bytes); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| /* tfaRunWriteDspMem */ |
| int tfa98xx_write_dsp_mem(struct tfa98xx *tfa98xx, struct nxpTfaDspMem *cfmem) |
| { |
| int ret = 0; |
| int i; |
| |
| for(i=0; (i<cfmem->size) && (ret==0); i++) { |
| ret = tfa98xx_dsp_write_mem(tfa98xx, cfmem->address++, |
| cfmem->words[i], cfmem->type); |
| } |
| |
| return ret; |
| } |
| |
| |
| static int tfa98xx_is87(struct tfa98xx *tfa98xx); |
| |
| /* |
| return the target address for the filter on this device |
| |
| filter_index: |
| [0..9] reserved for EQ (not deployed, calc. is available) |
| [10..12] anti-alias filter |
| [13] integrator filter |
| |
| */ |
| static int tfa98xx_filter_mem(struct tfa98xx *tfa98xx, |
| int filter_index, u16 *address) |
| { |
| int dmem = -1; |
| int idx; |
| const u16 bq_table[4][4] ={ |
| /* index: 10, 11, 12, 13 */ |
| {346,351,356,288}, /* 87 BRA_MAX_MRA4-2_7.00 */ |
| {346,351,356,288}, /* 90 BRA_MAX_MRA6_9.02 */ |
| {467,472,477,409}, /* 95 BRA_MAX_MRA7_10.02 */ |
| {406,411,416,348} /* 97 BRA_MAX_MRA9_12.01 */ |
| }; |
| |
| |
| if ( (10 <= filter_index) && (filter_index <= 13) ) { |
| dmem = Tfa98xx_DMEM_YMEM; /* for all devices */ |
| idx = filter_index-10; |
| |
| switch (tfa98xx->rev) { |
| case 0x12: |
| if (tfa98xx_is87(tfa98xx)) |
| *address = bq_table[0][idx]; |
| else |
| *address = bq_table[2][idx]; |
| break; |
| case 0x97: |
| *address = bq_table[3][idx]; |
| break; |
| case 0x80: |
| case 0x81: /* for the RAM version */ |
| case 0x91: |
| *address = bq_table[1][idx]; |
| break; |
| default: |
| pr_err("Unsupported device: rev=0x%x, subrev=0x%x\n", |
| tfa98xx->rev, tfa98xx->subrev); |
| return -EINVAL; |
| } |
| } |
| |
| return dmem; |
| } |
| |
| /* tfaRunWriteFilter */ |
| int tfa98xx_write_filter(struct tfa98xx *tfa98xx, |
| struct nxpTfaBiquadSettings *bq) |
| { |
| int dmem; |
| u16 address; |
| u8 data[3*3 + sizeof(bq->bytes)]; |
| |
| /* get the target address for the filter on this device */ |
| dmem = tfa98xx_filter_mem(tfa98xx, bq->index, &address); |
| if (dmem < 0) |
| return dmem; |
| |
| /* |
| * send a DSP memory message that targets the devices specific memory |
| * for the filter msg params: which_mem, start_offset, num_words msg |
| */ |
| 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->bytes)/3; /* output[2] = num_words */ |
| memcpy( &data[9], bq->bytes, sizeof(bq->bytes)); /* payload */ |
| |
| return tfa98xx_dsp_set_param(tfa98xx, /*framework*/0, /*param*/4, |
| sizeof(data), data); |
| } |
| |
| int tfa98xx_dsp_reset_count(struct tfa98xx *tfa98xx) |
| { |
| int count; |
| |
| tfa98xx_dsp_read_mem(tfa98xx, TFA98XX_XMEM_COUNT_BOOT, 1, &count); |
| |
| return count; |
| } |
| |
| /* |
| * wait for calibrate done |
| */ |
| static int tfa98xx_wait_calibration(struct tfa98xx *tfa98xx, int *done) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| int tries = 0; |
| u16 mtp; |
| |
| *done = 0; |
| |
| mtp = snd_soc_read(codec, TFA98XX_MTP); |
| |
| pr_debug("TFA98XX_MTP 0x%04x\n", mtp); |
| |
| /* in case of calibrate once wait for MTPEX */ |
| if (mtp & TFA98XX_MTP_MTPOTC) { |
| pr_debug("calibrate once wait for MTPEX\n"); |
| while ((*done == 0) && (tries < TFA98XX_WAITRESULT_NTRIES)) { |
| msleep_interruptible(5); |
| mtp = snd_soc_read(codec, TFA98XX_MTP); |
| /* check MTP bit1 (MTPEX) */ |
| *done = (mtp & TFA98XX_MTP_MTPEX); |
| tries++; |
| } |
| } else { /* poll xmem for calibrate always */ |
| pr_debug("poll xmem for calibrate always\n"); |
| while ((*done == 0) && (tries < TFA98XX_WAITRESULT_NTRIES)) { |
| msleep_interruptible(5); |
| ret = tfa98xx_dsp_read_mem(tfa98xx, |
| TFA98XX_XMEM_CALIBRATION_DONE, |
| 1, done); |
| tries++; |
| } |
| } |
| |
| if (tries == TFA98XX_WAITRESULT_NTRIES) { |
| pr_err("Calibrate Done timedout\n"); |
| return -ETIMEDOUT; |
| } |
| |
| return ret; |
| } |
| |
| static int tfa9887_specific(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| |
| pr_debug("\n"); |
| |
| value = snd_soc_read(codec, TFA98XX_SYS_CTRL); |
| /* DSP must be in control of the amplifier to avoid plops */ |
| value |= TFA98XX_SYS_CTRL_AMPE_MSK; |
| snd_soc_write(codec, TFA98XX_SYS_CTRL, value); |
| |
| /* some other registers must be set for optimal amplifier behaviour */ |
| snd_soc_write(codec, TFA98XX_BAT_PROT, 0x13AB); |
| snd_soc_write(codec, TFA98XX_AUDIO_CTR, 0x001F); |
| /* peak voltage protection is always on, but may be written */ |
| snd_soc_write(codec, 0x08, 0x3C4E); |
| /* TFA98XX_SYSCTRL_DCA = 0 */ |
| snd_soc_write(codec, TFA98XX_SYS_CTRL, 0x024D); |
| snd_soc_write(codec, 0x0A, 0x3EC3); |
| snd_soc_write(codec, 0x41, 0x0308); |
| snd_soc_write(codec, 0x49, 0x0E82); |
| |
| return 0; |
| } |
| |
| static int tfa9887B_specific(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| |
| pr_debug("\n"); |
| |
| /* all i2C registers are already set to default */ |
| value = snd_soc_read(codec, TFA98XX_SYS_CTRL); |
| /* DSP must be in control of the amplifier to avoid plops */ |
| value |= TFA98XX_SYS_CTRL_AMPE_MSK; |
| snd_soc_write(codec, TFA98XX_SYS_CTRL, value); |
| |
| /* some other registers must be set for optimal amplifier behaviour */ |
| snd_soc_write(codec, TFA98XX_BAT_PROT, 0x13AB); |
| snd_soc_write(codec, TFA98XX_AUDIO_CTR, 0x001F); |
| /* peak voltage protection is always on, but may be written */ |
| snd_soc_write(codec, 0x08, 0x3C4E); |
| /* TFA98XX_SYSCTRL_DCA = 0 */ |
| snd_soc_write(codec, TFA98XX_SYS_CTRL, 0x024D); |
| snd_soc_write(codec, 0x41, 0x0308); |
| snd_soc_write(codec, 0x49, 0x0E82); |
| |
| return 0; |
| } |
| |
| static int tfa9890_specific(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 reg; |
| |
| pr_debug("\n"); |
| |
| /* all i2C registers are already set to default for N1C2 */ |
| |
| /* some PLL registers must be set optimal for amplifier behaviour */ |
| snd_soc_write(codec, 0x40, 0x5a6b); |
| reg = snd_soc_read(codec, 0x59); |
| |
| reg |= 0x3; |
| |
| snd_soc_write(codec, 0x59, reg); |
| snd_soc_write(codec, 0x40, 0x0000); |
| |
| return 0; |
| } |
| |
| static int tfa9897_specific(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 reg; |
| |
| pr_debug("\n"); |
| |
| /* all i2C registers must already set to default POR value */ |
| |
| /* $48:[3] - 1 ==> 0; iddqtestbst - default value changed. */ |
| snd_soc_write(codec, 0x48, 0x0300); /* POR value = 0x308 */ |
| |
| /* $49:[0] - 1 ==> 0; CLIP - default value changed. 0 means CLIPPER on*/ |
| reg = snd_soc_read(codec, TFA98XX_CURRENTSENSE4); |
| reg &= ~(TFA98XX_CURRENTSENSE4_CLIP_MSK); |
| snd_soc_write(codec, TFA98XX_CURRENTSENSE4, reg); |
| |
| return 0; |
| } |
| |
| /* |
| * clockless way to determine if this is the tfa9887 or tfa9895 |
| * by testing if the PVP bit is writable |
| */ |
| static int tfa98xx_is87(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 save_value, check_value; |
| |
| pr_debug("\n"); |
| |
| save_value = snd_soc_read(codec, 0x08); |
| |
| /* if clear it's 87 */ |
| if ((save_value & 0x0400) == 0) |
| return 1; |
| |
| /* try to clear pvp bit */ |
| snd_soc_write(codec, 0x08, (save_value & ~0x0400)); |
| check_value = snd_soc_read(codec, 0x08); |
| |
| /* restore */ |
| snd_soc_write(codec, 0x08, save_value); |
| /* could we write the bit */ |
| |
| /* if changed it's the 87 */ |
| return (check_value != save_value) ? 1 : 0; |
| } |
| |
| /* |
| * I2C register init should be done at probe/recover time (TBC) |
| */ |
| static int tfa98xx_init(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret; |
| |
| pr_debug("Reset all i2c registers\n"); |
| |
| /* reset all i2c registers to default */ |
| snd_soc_write(codec, TFA98XX_SYS_CTRL, TFA98XX_SYS_CTRL_I2CR_MSK); |
| |
| switch (tfa98xx->rev) { |
| case 0x12: |
| if (tfa98xx_is87(tfa98xx)) |
| ret = tfa9887_specific(tfa98xx); |
| else { |
| ret = tfa9887B_specific(tfa98xx); |
| tfa98xx->has_drc = 1; |
| } |
| break; |
| case 0x80: |
| ret = tfa9890_specific(tfa98xx); |
| break; |
| case 0x91: |
| break; |
| case 0x97: |
| ret = tfa9897_specific(tfa98xx); |
| break; |
| case 0x81: |
| /* for the RAM version disable clock-gating */ |
| ret = tfa9890_specific(tfa98xx); |
| tfa98xx_clockgating(tfa98xx, 0); |
| break; |
| default: |
| pr_err("Unsupported device: rev=0x%x, subrev=0x%x\n", |
| tfa98xx->rev, tfa98xx->subrev); |
| return -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| |
| /* |
| * start the clocks and wait until the AMP is switching |
| * on return the DSP sub system will be ready for loading |
| */ |
| static int tfa98xx_startup(struct tfa98xx *tfa98xx) |
| { |
| int tries, status, ret; |
| |
| pr_debug("\n"); |
| |
| /* load the optimal TFA98XX in HW settings */ |
| ret = tfa98xx_init(tfa98xx); |
| |
| /* |
| * I2S settings to define the audio input properties |
| * these must be set before the subsys is up |
| * this will run the list until a non-register item is encountered |
| */ |
| ret = tfaContWriteRegsDev(tfa98xx); /* write device register settings */ |
| |
| /* |
| * also write register the settings from the default profile |
| * NOTE we may still have ACS=1 so we can switch sample rate here |
| */ |
| ret = tfaContWriteRegsProf(tfa98xx, tfa98xx->profile_current); |
| |
| /* powered on stall DSP only for 90 |
| * - now it is allowed to access DSP specifics |
| */ |
| switch (tfa98xx->rev) { |
| case 0x80: |
| tfa98xx_dsp_reset(tfa98xx, 1); |
| break; |
| default: |
| break; |
| } |
| |
| /* power on the sub system */ |
| ret = tfa98xx_powerdown(tfa98xx, 0); |
| |
| /* |
| * wait until the DSP subsystem hardware is ready |
| * note that the DSP CPU is not running (RST=1) |
| */ |
| pr_debug("Waiting for DSP system stable...()\n"); |
| for (tries = 1; tries < CFSTABLE_TRIES; tries++) { |
| ret = tfa98xx_dsp_system_stable(tfa98xx, &status); |
| if (status) |
| break; |
| } |
| |
| if (tries == CFSTABLE_TRIES) { |
| pr_err("Time out\n"); |
| return -ETIMEDOUT; |
| } else { |
| pr_debug("OK (tries=%d)\n", tries); |
| } |
| |
| /* the CF subsystem is enabled */ |
| pr_debug("reset count:0x%x\n", tfa98xx_dsp_reset_count(tfa98xx)); |
| |
| return 0; |
| } |
| |
| static int tfa98xx_is_coldboot(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 status; |
| |
| /* check status ACS bit to set */ |
| status = snd_soc_read(codec, TFA98XX_STATUSREG); |
| |
| pr_debug("ACS %d\n", (status & TFA98XX_STATUSREG_ACS) != 0); |
| |
| return (status & TFA98XX_STATUSREG_ACS) != 0; |
| } |
| |
| /* |
| * report if we are in powerdown state |
| * use AREFS from the status register iso the actual PWDN bit |
| * return true if powered down |
| */ |
| int tfa98xx_is_pwdn(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 status; |
| |
| /* check if PWDN bit is clear by looking at AREFS */ |
| status = snd_soc_read(codec, TFA98XX_STATUSREG); |
| |
| pr_debug("AREFS %d\n", (status & TFA98XX_STATUSREG_AREFS) != 0); |
| |
| return (status & TFA98XX_STATUSREG_AREFS) == 0; |
| } |
| |
| int tfa98xx_is_amp_running(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 status; |
| |
| /* check status SWS bit to set */ |
| status = snd_soc_read(codec, TFA98XX_STATUSREG); |
| |
| pr_debug("SWS %d\n", (status & TFA98XX_STATUSREG_SWS_MSK) != 0); |
| |
| return (status & TFA98XX_STATUSREG_SWS_MSK) != 0; |
| } |
| |
| int tfa98xx_is_dsp_enabled(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 sysctrl; |
| u16 cfe; |
| |
| sysctrl = snd_soc_read(tfa98xx->codec, TFA98XX_SYS_CTRL); |
| cfe = (sysctrl & TFA98XX_SYS_CTRL_CFE_MSK) != 0; |
| |
| return cfe; |
| } |
| |
| #define CF_CONTROL 0x8100 |
| |
| int tfa98xx_coldboot(struct tfa98xx *tfa98xx, int state) |
| { |
| int ret = 0; |
| int tries = 10; |
| |
| pr_debug("\n"); |
| |
| /* repeat set ACS bit until set as requested */ |
| while (state == !tfa98xx_is_coldboot(tfa98xx)) { |
| /* set coldstarted in CF_CONTROL to force ACS */ |
| ret = tfa98xx_dsp_write_mem(tfa98xx, CF_CONTROL, state, |
| Tfa98xx_DMEM_IOMEM); |
| |
| if (tries-- == 0) { |
| pr_debug("coldboot (ACS) did not %s\n", |
| state ? "set" : "clear"); |
| return -EINVAL; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * powerup the coolflux subsystem and wait for it |
| */ |
| int tfa98xx_dsp_power_up(struct tfa98xx *tfa98xx) |
| { |
| int ret = 0; |
| int tries, status; |
| |
| pr_debug("\n"); |
| |
| /* power on the sub system */ |
| ret = tfa98xx_powerdown(tfa98xx, 0); |
| |
| pr_debug("Waiting for DSP system stable...\n"); |
| |
| /* wait until everything is stable, in case clock has been off */ |
| for (tries = CFSTABLE_TRIES; tries > 0; tries--) { |
| ret = tfa98xx_dsp_system_stable(tfa98xx, &status); |
| if (status) |
| break; |
| } |
| |
| if (tries == 0) { |
| /* timedout */ |
| pr_err("DSP subsystem start timed out\n"); |
| return -ETIMEDOUT; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * the patch contains a header with the following |
| * IC revision register: 1 byte, 0xFF means don't care |
| * XMEM address to check: 2 bytes, big endian, 0xFFFF means don't care |
| * XMEM value to expect: 3 bytes, big endian |
| */ |
| int tfa98xx_check_ic_rom_version(struct tfa98xx *tfa98xx, |
| const u8 patchheader[]) |
| { |
| int ret = 0; |
| u16 checkrev; |
| u16 checkaddress; |
| int checkvalue; |
| int value = 0; |
| int status; |
| |
| pr_debug("FW rev: %x, IC rev %x\n", patchheader[0], tfa98xx->rev); |
| |
| checkrev = patchheader[0]; |
| if ((checkrev != 0xff) && (checkrev != tfa98xx->rev)) |
| return -EINVAL; |
| |
| checkaddress = (patchheader[1] << 8) + patchheader[2]; |
| checkvalue = (patchheader[3] << 16) + (patchheader[4] << 8) + |
| patchheader[5]; |
| |
| if (checkaddress != 0xffff) { |
| pr_debug("checkvalue: 0x%04x, checkvalue 0x%08x\n", |
| checkvalue, checkvalue); |
| /* before reading XMEM, check if we can access the DSP */ |
| ret = tfa98xx_dsp_system_stable(tfa98xx, &status); |
| if (!ret) { |
| if (!status) { |
| /* DSP subsys not running */ |
| ret = -EBUSY; |
| } |
| } |
| |
| /* read register to check the correct ROM version */ |
| if (!ret) { |
| ret = tfa98xx_dsp_read_mem(tfa98xx, checkaddress, 1, |
| &value); |
| pr_debug("checkvalue: 0x%08x, DSP 0x%08x\n", checkvalue, |
| value); |
| } |
| |
| if (!ret) { |
| if (value != checkvalue) |
| ret = -EINVAL; |
| } |
| } |
| |
| return ret; |
| } |
| |
| |
| int tfa98xx_process_patch_file(struct tfa98xx *tfa98xx, int len, const u8 *data) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 size; |
| int index; |
| int ret = 0; |
| u8 chunk_buf[TFA98XX_MAX_I2C_SIZE + 1]; |
| |
| pr_debug("len %d\n", len); |
| |
| /* |
| * expect following format in patchBytes: |
| * 2 bytes len of I2C transaction in little endian, then the bytes, |
| * excluding the slave address which is added from the handle |
| * This repeats for the whole file |
| */ |
| index = 0; |
| while (index < len) { |
| /* extract little endian length */ |
| size = data[index] + data[index + 1] * 256; |
| if (size > TFA98XX_MAX_I2C_SIZE) |
| pr_err("Patch chunk size %d > %d\n", size, |
| TFA98XX_MAX_I2C_SIZE); |
| |
| index += 2; |
| |
| if ((index + size) > len) { |
| /* outside the buffer, error in the input data */ |
| return -EINVAL; |
| } |
| |
| /* |
| * Need to copy data from the fw into local memory to avoid |
| * trouble with some i2c controller |
| */ |
| memcpy(chunk_buf, data + index, size); |
| |
| ret = tfa98xx_bulk_write_raw(codec, chunk_buf, size); |
| if (ret) { |
| pr_err("writing dsp patch failed %d\n", ret); |
| break; |
| } |
| |
| index += size; |
| } |
| |
| return ret; |
| } |
| |
| #define PATCH_HEADER_LENGTH 6 |
| int tfa98xx_dsp_patch(struct tfa98xx *tfa98xx, int patchLength, |
| const u8 *patchBytes) |
| { |
| int ret = 0; |
| |
| pr_debug("\n"); |
| |
| if (patchLength < PATCH_HEADER_LENGTH) |
| return -EINVAL; |
| |
| ret = tfa98xx_check_ic_rom_version(tfa98xx, patchBytes); |
| if (ret) { |
| pr_err("Error, patch in container file does not match device!\n"); |
| return ret; |
| |
| } |
| |
| ret = tfa98xx_process_patch_file(tfa98xx, |
| patchLength - PATCH_HEADER_LENGTH, |
| patchBytes + PATCH_HEADER_LENGTH); |
| return ret; |
| } |
| |
| |
| int tfa98xx_set_configured(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| |
| pr_debug("\n"); |
| |
| /* read the SystemControl register, modify the bit and write again */ |
| value = snd_soc_read(codec, TFA98XX_SYS_CTRL); |
| value |= TFA98XX_SYS_CTRL_SBSL_MSK; |
| snd_soc_write(codec, TFA98XX_SYS_CTRL, value); |
| |
| return 0; |
| } |
| |
| #define TO_LONG_LONG(x) ((s64)(x)<<32) |
| #define TO_INT(x) ((x)>>32) |
| #define TO_FIXED(e) e |
| |
| int float_to_int(u32 x) |
| { |
| unsigned e = (0x7F + 31) - ((*(unsigned *) &x & 0x7F800000) >> 23); |
| unsigned m = 0x80000000 | (*(unsigned *) &x << 8); |
| return (int)((m >> e) & -(e < 32)); |
| } |
| |
| int tfa98xx_set_volume(struct tfa98xx *tfa98xx, u32 voldB) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| int volume_value; |
| |
| value = snd_soc_read(codec, TFA98XX_AUDIO_CTR); |
| |
| /* |
| * 0x00 -> 0.0 dB |
| * 0x01 -> -0.5 dB |
| * ... |
| * 0xFE -> -127dB |
| * 0xFF -> muted |
| */ |
| volume_value = 2 * float_to_int(voldB); |
| if (volume_value > 255) |
| volume_value = 255; |
| |
| pr_debug("%d, attenuation -%d dB\n", volume_value, float_to_int(voldB)); |
| |
| /* volume value is in the top 8 bits of the register */ |
| value = (value & 0x00FF) | (u16)(volume_value << 8); |
| snd_soc_write(codec, TFA98XX_AUDIO_CTR, value); |
| |
| return 0; |
| } |
| |
| |
| int tfa98xx_get_volume(struct tfa98xx *tfa98xx, s64 *pVoldB) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 value; |
| |
| pr_debug("\n"); |
| |
| value = snd_soc_read(codec, TFA98XX_AUDIO_CTR); |
| value >>= 8; |
| *pVoldB = TO_FIXED(value) / -2; |
| |
| return ret; |
| } |
| |
| |
| static int tfa98xx_set_mute(struct tfa98xx *tfa98xx, enum Tfa98xx_Mute mute) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 audioctrl_value; |
| u16 sysctrl_value; |
| |
| pr_debug("\n"); |
| |
| audioctrl_value = snd_soc_read(codec, TFA98XX_AUDIO_CTR); |
| sysctrl_value = snd_soc_read(codec, TFA98XX_SYS_CTRL); |
| |
| switch (mute) { |
| case Tfa98xx_Mute_Off: |
| /* |
| * previous state can be digital or amplifier mute, |
| * clear the cf_mute and set the enbl_amplifier bits |
| * |
| * To reduce PLOP at power on it is needed to switch the |
| * amplifier on with the DCDC in follower mode |
| * (enbl_boost = 0 ?). |
| * This workaround is also needed when toggling the |
| * powerdown bit! |
| */ |
| audioctrl_value &= ~(TFA98XX_AUDIO_CTR_CFSM_MSK); |
| sysctrl_value |= (TFA98XX_SYS_CTRL_AMPE_MSK | |
| TFA98XX_SYS_CTRL_DCA_MSK); |
| break; |
| case Tfa98xx_Mute_Digital: |
| /* set the cf_mute bit */ |
| audioctrl_value |= TFA98XX_AUDIO_CTR_CFSM_MSK; |
| /* set the enbl_amplifier bit */ |
| sysctrl_value |= (TFA98XX_SYS_CTRL_AMPE_MSK); |
| /* clear active mode */ |
| sysctrl_value &= ~(TFA98XX_SYS_CTRL_DCA_MSK); |
| break; |
| case Tfa98xx_Mute_Amplifier: |
| /* clear the cf_mute bit */ |
| audioctrl_value &= ~TFA98XX_AUDIO_CTR_CFSM_MSK; |
| /* clear the enbl_amplifier bit and active mode */ |
| sysctrl_value &= ~(TFA98XX_SYS_CTRL_AMPE_MSK | |
| TFA98XX_SYS_CTRL_DCA_MSK); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| ret = snd_soc_write(codec, TFA98XX_AUDIO_CTR, audioctrl_value); |
| if (ret) |
| return ret; |
| |
| ret = snd_soc_write(codec, TFA98XX_SYS_CTRL, sysctrl_value); |
| return ret; |
| } |
| |
| |
| int tfa98xx_mute(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 status; |
| int tries = 0; |
| |
| pr_debug("\n"); |
| |
| /* signal the TFA98XX to mute plop free and turn off the amplifier */ |
| ret = tfa98xx_set_mute(tfa98xx, Tfa98xx_Mute_Amplifier); |
| if (ret) |
| return ret; |
| |
| /* now wait for the amplifier to turn off */ |
| status = snd_soc_read(codec, TFA98XX_STATUSREG); |
| while (((status & TFA98XX_STATUSREG_SWS) == TFA98XX_STATUSREG_SWS) |
| && (tries < TFA98XX_WAITRESULT_NTRIES)) { |
| usleep_range(10000, 11000); |
| status = snd_soc_read(codec, TFA98XX_STATUSREG); |
| tries++; |
| } |
| |
| /* The amplifier is always switching */ |
| if (tries == TFA98XX_WAITRESULT_NTRIES) |
| return -ETIMEDOUT; |
| |
| pr_debug("-------------------- muted --------------------\n"); |
| |
| return 0; |
| } |
| |
| |
| int tfa98xx_unmute(struct tfa98xx *tfa98xx) |
| { |
| int ret = 0; |
| |
| /* signal the TFA98XX to mute */ |
| ret = tfa98xx_set_mute(tfa98xx, Tfa98xx_Mute_Off); |
| |
| pr_debug("-------------------unmuted ------------------\n"); |
| |
| return ret; |
| } |
| |
| |
| /* check that num_byte matches the memory type selected */ |
| int tfa98xx_check_size(enum Tfa98xx_DMEM which_mem, int len) |
| { |
| int ret = 0; |
| int modulo_size = 1; |
| |
| switch (which_mem) { |
| case Tfa98xx_DMEM_PMEM: |
| /* 32 bit PMEM */ |
| modulo_size = 4; |
| break; |
| case Tfa98xx_DMEM_XMEM: |
| case Tfa98xx_DMEM_YMEM: |
| case Tfa98xx_DMEM_IOMEM: |
| /* 24 bit MEM */ |
| modulo_size = 3; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| if ((len % modulo_size) != 0) |
| return -EINVAL; |
| |
| return ret; |
| } |
| |
| |
| int tfa98xx_execute_param(struct tfa98xx *tfa98xx) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| |
| /* the value to be sent to the CF_CONTROLS register: cf_req=00000000, |
| * cf_int=0, cf_aif=0, cf_dmem=XMEM=01, cf_rst_dsp=0 */ |
| u16 cf_ctrl = 0x0002; |
| cf_ctrl |= (1 << 8) | (1 << 4); /* set the cf_req1 and cf_int bit */ |
| return snd_soc_write(codec, TFA98XX_CF_CONTROLS, cf_ctrl); |
| } |
| |
| int tfa98xx_wait_result(struct tfa98xx *tfa98xx, int waitRetryCount) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 cf_status; /* the contents of the CF_STATUS register */ |
| int tries = 0; |
| |
| /* don't wait forever, DSP is pretty quick to respond (< 1ms) */ |
| do { |
| cf_status = snd_soc_read(codec, TFA98XX_CF_STATUS); |
| tries++; |
| } while ((!ret) && ((cf_status & 0x0100) == 0) |
| && (tries < waitRetryCount)); |
| /* pr_debug("tries: %d\n", tries); */ |
| if (tries >= waitRetryCount) { |
| /* something wrong with communication with DSP */ |
| pr_err("Error DSP not running\n"); |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* read the return code for the RPC call */ |
| int tfa98xx_check_rpc_status(struct tfa98xx *tfa98xx, int *status) |
| { |
| int ret = 0; |
| /* the value to sent to the * CF_CONTROLS register: cf_req=00000000, |
| * cf_int=0, cf_aif=0, cf_dmem=XMEM=01, cf_rst_dsp=0 */ |
| u16 cf_ctrl = 0x0002; |
| /* memory address to be accessed (0: Status, 1: ID, 2: parameters) */ |
| u16 cf_mad = 0x0000; |
| u8 mem[3]; /* for the status read from DSP memory */ |
| u8 buffer[4]; |
| |
| /* minimize the number of I2C transactions by making use |
| * of the autoincrement in I2C */ |
| /* first the data for CF_CONTROLS */ |
| buffer[0] = (u8)((cf_ctrl >> 8) & 0xFF); |
| buffer[1] = (u8)(cf_ctrl & 0xFF); |
| /* write the contents of CF_MAD which is the subaddress |
| * following CF_CONTROLS */ |
| buffer[2] = (u8)((cf_mad >> 8) & 0xFF); |
| buffer[3] = (u8)(cf_mad & 0xFF); |
| |
| ret = tfa98xx_write_data(tfa98xx, TFA98XX_CF_CONTROLS, sizeof(buffer), |
| buffer); |
| if (ret) |
| return ret; |
| |
| /* read 1 word (24 bit) from XMEM */ |
| ret = tfa98xx_read_data(tfa98xx, TFA98XX_CF_MEM, 3, mem); |
| if (ret) |
| return ret; |
| |
| *status = mem[0] << 16 | mem[1] << 8 | mem[2]; |
| |
| return 0; |
| } |
| |
| int tfa98xx_write_parameter(struct tfa98xx *tfa98xx, |
| u8 module_id, |
| u8 param_id, |
| int len, const u8 data[]) |
| { |
| int ret; |
| /* |
| * the value to be sent to the CF_CONTROLS register: cf_req=00000000, |
| * cf_int=0, cf_aif=0, cf_dmem=XMEM=01, cf_rst_dsp=0 |
| */ |
| u16 cf_ctrl = 0x0002; |
| /* memory address to be accessed (0 : Status, 1 : ID, 2 : parameters)*/ |
| u16 cf_mad = 0x0001; |
| u8 buffer[7]; |
| int offset = 0; |
| int chunk_size = ROUND_DOWN(TFA98XX_MAX_I2C_SIZE, 3); |
| int remaining_bytes = len; |
| |
| /* pr_debug("%d\n", len); */ |
| |
| ret = tfa98xx_check_size(Tfa98xx_DMEM_XMEM, len); |
| if (!ret) { |
| if ((len <= 0) || (len > MAX_PARAM_SIZE)) { |
| pr_err("Error in parameters size\n"); |
| return -EINVAL; |
| } |
| } |
| |
| /* |
| * minimize the number of I2C transactions by making use of |
| * the autoincrement in I2C |
| */ |
| |
| /* first the data for CF_CONTROLS */ |
| buffer[0] = (u8)((cf_ctrl >> 8) & 0xFF); |
| buffer[1] = (u8)(cf_ctrl & 0xFF); |
| /* |
| * write the contents of CF_MAD which is the subaddress |
| * following CF_CONTROLS |
| */ |
| buffer[2] = (u8)((cf_mad >> 8) & 0xFF); |
| buffer[3] = (u8)(cf_mad & 0xFF); |
| /* |
| * write the module and RPC id into CF_MEM, which |
| * follows CF_MAD |
| */ |
| buffer[4] = 0; |
| buffer[5] = module_id + 128; |
| buffer[6] = param_id; |
| |
| ret = tfa98xx_write_data(tfa98xx, TFA98XX_CF_CONTROLS, sizeof(buffer), |
| buffer); |
| if (ret) |
| return ret; |
| |
| /* |
| * Thanks to autoincrement in cf_ctrl, next write will |
| * happen at the next address |
| */ |
| while ((!ret) && (remaining_bytes > 0)) { |
| if (remaining_bytes < chunk_size) |
| chunk_size = remaining_bytes; |
| |
| ret = tfa98xx_write_data(tfa98xx, TFA98XX_CF_MEM, chunk_size, |
| data + offset); |
| |
| remaining_bytes -= chunk_size; |
| offset += chunk_size; |
| } |
| |
| return ret; |
| } |
| |
| |
| /* Execute RPC protocol to write something to the DSP */ |
| int tfa98xx_dsp_set_param_var_wait(struct tfa98xx *tfa98xx, |
| u8 module_id, |
| u8 param_id, int len, |
| const u8 data[], int waitRetryCount) |
| { |
| int ret = 0; |
| int status = 0; |
| |
| /* pr_debug("\n"); */ |
| |
| /* 1) write the id and data to the DSP XMEM */ |
| ret = tfa98xx_write_parameter(tfa98xx, module_id, param_id, len, data); |
| if (ret) |
| return ret; |
| |
| /* 2) wake up the DSP and let it process the data */ |
| ret = tfa98xx_execute_param(tfa98xx); |
| if (ret) |
| return ret; |
| |
| /* 3) wait for the ack */ |
| ret = tfa98xx_wait_result(tfa98xx, waitRetryCount); |
| if (ret) |
| return ret; |
| |
| /* 4) check the RPC return value */ |
| ret = tfa98xx_check_rpc_status(tfa98xx, &status); |
| if (ret) |
| return ret; |
| |
| if (status) { |
| /* DSP RPC call returned an error */ |
| pr_err("DSP RPC error %d\n", status + ERROR_RPC_BASE); |
| return -EIO; |
| } |
| |
| return ret; |
| } |
| |
| /* Execute RPC protocol to write something to the DSP */ |
| int tfa98xx_dsp_set_param(struct tfa98xx *tfa98xx, u8 module_id, |
| u8 param_id, int len, |
| const u8 *data) |
| { |
| /* Use small WaitResult retry count */ |
| return tfa98xx_dsp_set_param_var_wait(tfa98xx, module_id, param_id, |
| len, data, |
| TFA98XX_WAITRESULT_NTRIES); |
| } |
| |
| int tfa98xx_dsp_write_config(struct tfa98xx *tfa98xx, int len, const u8 *data) |
| { |
| int ret = 0; |
| int has_drc = 0; |
| |
| pr_debug("\n"); |
| |
| ret = tfa98xx_dsp_set_param(tfa98xx, MODULE_SPEAKERBOOST, |
| SB_PARAM_SET_CONFIG, len, data); |
| if (ret) |
| return ret; |
| |
| ret = tfa98xx_dsp_support_drc(tfa98xx, &has_drc); |
| if (ret) |
| return ret; |
| |
| if (has_drc) { |
| /* |
| * Need to set AgcGainInsert back to PRE, as |
| * the SetConfig forces it to POST |
| */ |
| ret = tfa98xx_dsp_set_agc_gain_insert(tfa98xx, |
| Tfa98xx_AgcGainInsert_PreDrc); |
| } |
| |
| return ret; |
| } |
| |
| |
| /* the number of biquads supported */ |
| #define TFA98XX_BIQUAD_NUM 10 |
| #define BIQUAD_COEFF_SIZE 6 |
| |
| int tfa98xx_dsp_biquad_disable(struct tfa98xx *tfa98xx, int biquad_index) |
| { |
| int coeff_buffer[BIQUAD_COEFF_SIZE]; |
| u8 data[BIQUAD_COEFF_SIZE * 3]; |
| |
| if (biquad_index > TFA98XX_BIQUAD_NUM) |
| return -EINVAL; |
| |
| if (biquad_index < 1) |
| return -EINVAL; |
| |
| /* set in correct order and format for the DSP */ |
| coeff_buffer[0] = (int)-8388608; /* -1.0f */ |
| coeff_buffer[1] = 0; |
| coeff_buffer[2] = 0; |
| coeff_buffer[3] = 0; |
| coeff_buffer[4] = 0; |
| coeff_buffer[5] = 0; |
| |
| /* |
| * convert to fixed point and then bytes suitable for |
| * transmission over I2C |
| */ |
| tfa98xx_convert_data2bytes(BIQUAD_COEFF_SIZE, coeff_buffer, data); |
| return tfa98xx_dsp_set_param(tfa98xx, MODULE_BIQUADFILTERBANK, |
| (u8)biquad_index, |
| (u8)(BIQUAD_COEFF_SIZE * 3), |
| data); |
| } |
| |
| int tfa98xx_dsp_biquad_set_coeff(struct tfa98xx *tfa98xx, int biquad_index, |
| int len, u8 *data) |
| { |
| return tfa98xx_dsp_set_param(tfa98xx, MODULE_BIQUADFILTERBANK, |
| biquad_index, len, data); |
| } |
| |
| /* |
| * The AgcGainInsert functions are static because they are not public: |
| * only allowed mode is PRE. |
| * The functions are nevertheless needed because the mode is forced to |
| * POST by each SetLSmodel and each SetConfig => it should be reset to |
| * PRE afterwards. |
| */ |
| int tfa98xx_dsp_set_agc_gain_insert(struct tfa98xx *tfa98xx, |
| enum Tfa98xx_AgcGainInsert |
| agcGainInsert) |
| { |
| int ret = 0; |
| unsigned char bytes[3]; |
| |
| pr_debug("\n"); |
| |
| tfa98xx_convert_data2bytes(1, (int *) &agcGainInsert, bytes); |
| |
| ret = tfa98xx_dsp_set_param(tfa98xx, MODULE_SPEAKERBOOST, |
| SB_PARAM_SET_AGCINS, 3, bytes); |
| |
| return ret; |
| } |
| |
| |
| int tfa98xx_dsp_write_speaker_parameters(struct tfa98xx *tfa98xx, int len, |
| const u8 *data) |
| { |
| int ret = 0; |
| int has_drc = 0; |
| |
| if (!data) |
| return -EINVAL; |
| |
| pr_debug("%d\n", len); |
| |
| ret = tfa98xx_dsp_set_param_var_wait(tfa98xx, MODULE_SPEAKERBOOST, |
| SB_PARAM_SET_LSMODEL, len, |
| data, |
| TFA98XX_WAITRESULT_NTRIES_LONG); |
| if (ret) |
| return ret; |
| |
| ret = tfa98xx_dsp_support_drc(tfa98xx, &has_drc); |
| if (ret) |
| return ret; |
| |
| if (has_drc) { |
| /* |
| * Need to set AgcGainInsert back to PRE, as |
| * the SetConfig forces it to POST |
| */ |
| ret = tfa98xx_dsp_set_agc_gain_insert(tfa98xx, |
| Tfa98xx_AgcGainInsert_PreDrc); |
| } |
| |
| return ret; |
| } |
| |
| |
| int tfa98xx_dsp_write_preset(struct tfa98xx *tfa98xx, int len, const u8 *data) |
| { |
| if (!data) |
| return -EINVAL; |
| |
| pr_debug("\n"); |
| |
| return tfa98xx_dsp_set_param(tfa98xx, MODULE_SPEAKERBOOST, |
| SB_PARAM_SET_PRESET, len, data); |
| } |
| |
| /* load all the parameters for the DRC settings from a file */ |
| int tfa98xx_dsp_write_drc(struct tfa98xx *tfa98xx, int len, const u8 *data) |
| { |
| if (!data) |
| return -EINVAL; |
| |
| pr_debug("\n"); |
| |
| return tfa98xx_dsp_set_param(tfa98xx, MODULE_SPEAKERBOOST, |
| SB_PARAM_SET_DRC, len, data); |
| } |
| |
| |
| /* Execute RPC protocol to read something from the DSP */ |
| int tfa98xx_dsp_get_param(struct tfa98xx *tfa98xx, u8 module_id, |
| u8 param_id, int len, u8 *data) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| int ret = 0; |
| u16 cf_mad; |
| int status = 0; |
| int offset = 0; |
| int chunk_size = ROUND_DOWN(TFA98XX_MAX_I2C_SIZE, 3); |
| int remaining_bytes = len; |
| |
| pr_debug("\n"); |
| |
| ret = tfa98xx_check_size(Tfa98xx_DMEM_XMEM, len); |
| if (!ret) { |
| if ((len <= 0) || (len > MAX_PARAM_SIZE)) |
| return -EINVAL; |
| } |
| |
| /* 1) write the id and data to the DSP XMEM */ |
| ret = tfa98xx_write_parameter(tfa98xx, module_id, param_id, len, data); |
| if (ret) |
| return ret; |
| |
| /* 2) wake up the DSP and let it process the data */ |
| ret = tfa98xx_execute_param(tfa98xx); |
| if (ret) |
| return ret; |
| |
| /* 3) wait for the ack */ |
| ret = tfa98xx_wait_result(tfa98xx, TFA98XX_WAITRESULT_NTRIES); |
| if (ret) |
| return ret; |
| |
| /* 4) check the RPC return value */ |
| ret = tfa98xx_check_rpc_status(tfa98xx, &status); |
| if (ret) |
| return ret; |
| |
| if (status) { |
| /* DSP RPC call returned an error |
| * Note: when checking for features and a features |
| * is not supported on a device, it returns ERROR_RPC_PARAMID |
| */ |
| pr_warn("DSP RPC error %d\n", status + ERROR_RPC_BASE); |
| return -EIO; |
| } |
| |
| /* 5) read the resulting data */ |
| /* memory address to be accessed (0: Status, |
| * 1: ID, 2: parameters) */ |
| cf_mad = 0x0002; |
| snd_soc_write(codec, TFA98XX_CF_MAD, cf_mad); |
| |
| /* due to autoincrement in cf_ctrl, next write will happen at |
| * the next address */ |
| while ((!ret) && (remaining_bytes > 0)) { |
| if (remaining_bytes < TFA98XX_MAX_I2C_SIZE) |
| chunk_size = remaining_bytes; |
| |
| /* else chunk_size remains at initialize value above */ |
| ret = tfa98xx_read_data(tfa98xx, TFA98XX_CF_MEM, chunk_size, |
| data + offset); |
| remaining_bytes -= chunk_size; |
| offset += chunk_size; |
| } |
| |
| return ret; |
| } |
| |
| |
| int tfa98xx_dsp_get_sw_feature_bits(struct tfa98xx *tfa98xx, int features[2]) |
| { |
| int ret = 0; |
| unsigned char bytes[3 * 2]; |
| |
| pr_debug("\n"); |
| |
| ret = tfa98xx_dsp_get_param(tfa98xx, MODULE_FRAMEWORK, |
| FW_PARAM_GET_FEATURE_BITS, |
| sizeof(bytes), bytes); |
| /* old ROM code may respond with ERROR_RPC_PARAMID -> -EIO */ |
| if (ret) |
| return ret; |
| |
| tfa98xx_convert_bytes2data(sizeof(bytes), bytes, features); |
| |
| return ret; |
| } |
| |
| |
| int tfa98xx_dsp_support_drc(struct tfa98xx *tfa98xx, int *has_drc) |
| { |
| int ret = 0; |
| *has_drc = 0; |
| |
| pr_debug("\n"); |
| |
| if (tfa98xx->has_drc) { |
| *has_drc = tfa98xx->has_drc; |
| } else { |
| int features[2]; |
| |
| if ((tfa98xx->rev == REV_TFA9887) || (tfa98xx->rev == REV_TFA9890)) { |
| /* older ROM code, doesn't support it */ |
| *has_drc = 0; |
| ret = 0; |
| } else { |
| ret = tfa98xx_dsp_get_sw_feature_bits(tfa98xx, features); |
| if (!ret) { |
| /* easy case: new API available */ |
| /* bit=0 means DRC enabled */ |
| *has_drc = (features[0] & FEATURE1_DRC) == 0; |
| } else if (ret == -EIO) { |
| /* older ROM code, doesn't support it */ |
| *has_drc = 0; |
| ret = 0; |
| } |
| } |
| |
| if (!ret) |
| tfa98xx->has_drc = *has_drc; |
| } |
| return ret; |
| } |
| |
| int tfa98xx_resolve_incident(struct tfa98xx *tfa98xx) |
| { |
| if (tfa98xx->rev == REV_TFA9897) { |
| pr_warn("OCDS TFA9897 trigger\n"); |
| /* TFA9897 need to reset the DSP to take the newly set re0 */ |
| tfa98xx_dsp_reset(tfa98xx, 1); |
| tfa98xx_dsp_reset(tfa98xx, 0); |
| } else { |
| /* TFA98xx need power cycle */ |
| tfa98xx_powerdown(tfa98xx, 1); |
| tfa98xx_powerdown(tfa98xx, 0); |
| } |
| |
| return 0; |
| } |
| |
| int tfa98xx_dsp_get_calibration_impedance(struct tfa98xx *tfa98xx, u32 *re25) |
| { |
| int ret = 0; |
| u8 bytes[3]; |
| int data[1]; |
| int done; |
| |
| pr_debug("\n"); |
| |
| ret = tfa98xx_dsp_read_mem(tfa98xx, TFA98XX_XMEM_CALIBRATION_DONE, 1, |
| &done); |
| if (ret) |
| return ret; |
| |
| if (!done) { |
| pr_err("Calibration not done %d\n", done); |
| return -EINVAL; |
| } |
| |
| ret = tfa98xx_dsp_get_param(tfa98xx, MODULE_SPEAKERBOOST, |
| SB_PARAM_GET_RE0, 3, bytes); |
| |
| tfa98xx_convert_bytes2data(3, bytes, data); |
| |
| /* /2^23*2^(def.SPKRBST_TEMPERATURE_EXP) */ |
| *re25 = TO_FIXED(data[0]) / (1 << (23 - SPKRBST_TEMPERATURE_EXP)); |
| |
| return ret; |
| } |
| |
| static int tfa98xx_aec_output(struct tfa98xx *tfa98xx, int enable) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 value; |
| int ret = 0; |
| |
| if (tfa98xx->rev == REV_TFA9897) { |
| /* |
| * 97 powerdown already handled this internally by disabling |
| * TDM interface |
| */ |
| return ret; |
| } |
| |
| value = snd_soc_read(codec, TFA98XX_I2SREG); |
| if (enable == 0) |
| value &= ~TFA98XX_I2SREG_I2SDOE; |
| else |
| value |= TFA98XX_I2SREG_I2SDOE; |
| ret = snd_soc_write(codec, TFA98XX_I2SREG, value); |
| |
| return ret; |
| } |
| |
| |
| int tfa98xx_select_mode(struct tfa98xx *tfa98xx, enum Tfa98xx_Mode mode) |
| { |
| struct snd_soc_codec *codec = tfa98xx->codec; |
| u16 i2s_value, sysctrl_value, temp_value; |
| u16 bat_volt; |
| int ret = 0; |
| int timeoutloop = 100; |
| |
| pr_debug("%d\n", mode); |
| |
| if (tfa98xx->rev != REV_TFA9897) |
| return -EINVAL; |
| |
| i2s_value = snd_soc_read(codec, TFA98XX_I2SREG); |
| sysctrl_value = snd_soc_read(codec, TFA98XX_SYS_CTRL); |
| |
| switch (mode) { |
| case Tfa98xx_Mode_Normal: |
| /* clear the 2 bits RCV */ |
| i2s_value &= ~(TFA98XX_AUDIOREG_RCV_MSK); |
| sysctrl_value |= (TFA98XX_SYS_CTRL_DCA_MSK); |
| |
| ret = snd_soc_write(codec, TFA98XX_I2SREG, i2s_value); |
| if (ret == 0) { |
| ret = snd_soc_write(codec, TFA98XX_SYS_CTRL, |
| sysctrl_value); |
| } |
| break; |
| |
| case Tfa98xx_Mode_RCV: |
| do { |
| temp_value = snd_soc_read(codec, |
| TFA98XX_TEMPERATURE); |
| if (temp_value == -1) |
| ret = -EIO; |
| /* wait until th ADC's are up, 0x100 means not |
| ready yet */ |
| } while ((--timeoutloop) && (temp_value >= 0x100) && |
| (ret == 0)); |
| |
| if ((timeoutloop == 0) && (temp_value >= 0x100)) { |
| pr_err("ADC's startup timed out"); |
| ret = -ETIMEDOUT; |
| } |
| |
| if (ret == 0) { |
| bat_volt = snd_soc_read(codec, |
| TFA98XX_BATTERYVOLTAGE); |
| if (bat_volt < 838) { |
| i2s_value |= TFA98XX_AUDIOREG_RCV_MSK; |
| sysctrl_value &= ~TFA98XX_SYS_CTRL_DCA_MSK; |
| |
| ret = snd_soc_write(codec, |
| TFA98XX_I2SREG, i2s_value); |
| if (ret == 0) { |
| ret = snd_soc_write(codec, |
| TFA98XX_SYS_CTRL, |
| sysctrl_value); |
| } |
| } else { |
| pr_err("Battery voltage too high"); |
| } |
| } else { |
| pr_err("Failed to read ADC's"); |
| } |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * the int24 values for the vsfw delay table |
| */ |
| static u8 vsfwdelay_table[] = { |
| 0, 0, 2, /* Index 0 - Current/Volt Fractional Delay for 8KHz */ |
| 0, 0, 0, /* Index 1 - Current/Volt Fractional Delay for 11KHz */ |
| 0, 0, 0, /* Index 2 - Current/Volt Fractional Delay for 12KHz */ |
| 0, 0, 2, /* Index 3 - Current/Volt Fractional Delay for 16KHz */ |
| 0, 0, 2, /* Index 4 - Current/Volt Fractional Delay for 22KHz */ |
| 0, 0, 2, /* Index 5 - Current/Volt Fractional Delay for 24KHz */ |
| 0, 0, 2, /* Index 6 - Current/Volt Fractional Delay for 32KHz */ |
| 0, 0, 2, /* Index 7 - Current/Volt Fractional Delay for 44KHz */ |
| 0, 0, 3 /* Index 8 - Current/Volt Fractional Delay for 48KHz */ |
| }; |
| |
| static int tfa9897_dsp_write_vsfwdelay_table(struct tfa98xx *tfa98xx) |
| { |
| return tfa98xx_dsp_set_param(tfa98xx, MODULE_FRAMEWORK, |
| FW_PARAM_SET_CURRENT_DELAY, |
| sizeof(vsfwdelay_table), |
| vsfwdelay_table); |
| } |
| |
| /* |
| * The int24 values for the fracdelay table |
| * For now applicable only for 8 and 48 kHz |
| */ |
| static u8 cvfracdelay_table[] = { |
| 0, 0, 51, /* Index 0 - Current/Volt Fractional Delay for 8KHz */ |
| 0, 0, 0, /* Index 1 - Current/Volt Fractional Delay for 11KHz */ |
| 0, 0, 0, /* Index 2 - Current/Volt Fractional Delay for 12KHz */ |
| 0, 0, 38, /* Index 3 - Current/Volt Fractional Delay for 16KHz */ |
| 0, 0, 34, /* Index 4 - Current/Volt Fractional Delay for 22KHz */ |
| 0, 0, 33, /* Index 5 - Current/Volt Fractional Delay for 24KHz */ |
| 0, 0, 11, /* Index 6 - Current/Volt Fractional Delay for 32KHz */ |
| 0, 0, 2, /* Index 7 - Current/Volt Fractional Delay for 44KHz */ |
| 0, 0, 62 /* Index 8 - Current/Volt Fractional Delay for 48KHz */ |
| }; |
| |
| static int tfa9897_dsp_write_cvfracdelay_table(struct tfa98xx *tfa98xx) |
| { |
| return tfa98xx_dsp_set_param(tfa98xx, MODULE_FRAMEWORK, |
| FW_PARAM_SET_CURFRAC_DELAY, |
| sizeof(cvfracdelay_table), |
| cvfracdelay_table); |
| } |
| |
| /* |
| * load the tables to the DSP, called after patch load is done |
| */ |
| int tfa98xx_dsp_write_tables(struct tfa98xx *tfa98xx) |
| { |
| int ret = 0; |
| |
| if (tfa98xx->rev == REV_TFA9897) { |
| ret = tfa9897_dsp_write_vsfwdelay_table(tfa98xx); |
| pr_debug("tfa9897_dsp_write_vsfwdelay_table %d\n", ret); |
| if (!ret) { |
| ret = tfa9897_dsp_write_cvfracdelay_table(tfa98xx); |
| pr_debug("tfa9897_dsp_write_cvfracdelay_table %d\n", |
| ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * this will load the patch witch will implicitly start the DSP |
| * if no patch is available the DPS is started immediately |
| */ |
| static int tfaRunStartDSP(struct tfa98xx *tfa98xx) |
| { |
| int ret; |
| |
| ret = tfaContWritePatch(tfa98xx); |
| if (ret) { |
| pr_err("Error, writing patch failed (%d)\n", ret); |
| return ret; |
| } |
| |
| ret = tfa98xx_dsp_write_tables(tfa98xx); |
| |
| return ret; |
| } |
| |
| /* |
| * Run the startup/init sequence and set ACS bit |
| */ |
| int tfaRunColdStartup(struct tfa98xx *tfa98xx) |
| { |
| int ret; |
| |
| pr_debug("\n"); |
| |
| ret = tfa98xx_startup(tfa98xx); |
| pr_debug("tfa98xx_startup %d\n", ret); |
| if (ret) |
| return ret; |
| |
| /* force cold boot */ |
| ret = tfa98xx_coldboot(tfa98xx, 1); /* set ACS */ |
| pr_debug("tfa98xx_coldboot %d\n", ret); |
| if (ret) |
| return ret; |
| |
| if (!tfa98xx_is_dsp_enabled(tfa98xx)) |
| return ret; |
| |
| ret = tfaRunStartDSP(tfa98xx); |
| |
| return ret; |
| } |
| |
| |
| static int coldboot; |
| module_param(coldboot, int, S_IRUGO | S_IWUSR); |
| |
| /* |
| * Start the maximus speakerboost algorithm this implies a full system |
| * startup when the system was not already started. |
| */ |
| int tfaRunSpeakerBoost(struct tfa98xx *tfa98xx, int force, int power) |
| { |
| int ret = 0; |
| |
| pr_debug("force: %d\n", force); |
| |
| if (force) { |
| ret = tfaRunColdStartup(tfa98xx); |
| if (ret) |
| return ret; |
| /* DSP is running now */ |
| } |
| |
| if (force || tfa98xx_is_coldboot(tfa98xx)) { |
| int done; |
| |
| pr_debug("coldstart%s\n", force ? " (forced)" : ""); |
| |
| /* in case of force CF already runnning */ |
| if (!force) { |
| ret = tfa98xx_startup(tfa98xx); |
| if (ret) { |
| pr_err("tfa98xx_startup %d\n", ret); |
| return ret; |
| } |
| |
| /* Is the DSP bypassed? */ |
| if (!tfa98xx_is_dsp_enabled(tfa98xx)) { |
| if (power) |
| tfa98xx_unmute(tfa98xx); |
| return 0; |
| } |
| |
| /* load patch and start the DSP */ |
| ret = tfaRunStartDSP(tfa98xx); |
| } |
| |
| /* |
| * DSP is running now |
| * NOTE that ACS may be active |
| * no DSP reset/sample rate may be done until configured |
| * (SBSL) |
| */ |
| |
| /* soft mute */ |
| ret = tfa98xx_set_mute(tfa98xx, Tfa98xx_Mute_Digital); |
| if (ret) { |
| pr_err("Tfa98xx_SetMute error: %d\n", ret); |
| return ret; |
| } |
| |
| /* |
| * For the first configuration the DSP expects at least |
| * the speaker, config and a preset. |
| * Therefore all files from the device list as well as the file |
| * from the default profile are loaded before SBSL is set. |
| * |
| * Note that the register settings were already done before |
| * loading the patch |
| * |
| * write all the files from the device list (typically spk and |
| * config) |
| */ |
| ret = tfaContWriteFiles(tfa98xx); |
| if (ret) { |
| pr_err("tfaContWriteFiles error: %d\n", ret); |
| return ret; |
| } |
| |
| /* |
| * write all the files from the profile list (typically preset) |
| * use volumestep 0 |
| */ |
| tfaContWriteFilesProf(tfa98xx, tfa98xx->profile_current, 0); |
| |
| /* tell DSP it's loaded SBSL = 1 */ |
| ret = tfa98xx_set_configured(tfa98xx); |
| if (ret) { |
| pr_err("tfa98xx_set_configured error: %d\n", ret); |
| return ret; |
| } |
| |
| /* await calibration, this should return ok */ |
| tfa98xx_wait_calibration(tfa98xx, &done); |
| if (!done) { |
| pr_err("Calibration not done!\n"); |
| /* |
| * Don't return on timeout, otherwise we cannot power |
| * down the DSP |
| */ |
| } else { |
| pr_info("Calibration done\n"); |
| } |
| |
| switch (tfa98xx->rev) { |
| case 0x97: |
| tfa98xx_dsp_reset(tfa98xx, 1); |
| tfa98xx_dsp_reset(tfa98xx, 0); |
| break; |
| default: |
| break; |
| } |
| } else { |
| /* already warm, so just pwr on */ |
| if (power) { |
| /* Is the DSP bypassed? */ |
| if (tfa98xx_is_dsp_enabled(tfa98xx)) |
| ret = tfa98xx_dsp_power_up(tfa98xx); /* DSP power up */ |
| else |
| ret = tfa98xx_powerdown(tfa98xx, 0); /* AMP power up */ |
| } |
| } |
| |
| if (power) |
| tfa98xx_unmute(tfa98xx); |
| |
| return ret; |
| } |
| |
| int tfa98xx_dsp_start(struct tfa98xx *tfa98xx, int power, int next_profile, int vstep) |
| { |
| int forcecoldboot = coldboot; |
| int active_profile; |
| int active_vstep; |
| int ret = 0; |
| |
| if (!tfa98xx->profile_count || !tfa98xx->profiles) |
| return -EINVAL; |
| |
| |
| if (tfa98xx->dsp_init == TFA98XX_DSP_INIT_RECOVER) { |
| pr_warn("Restart for recovery\n"); |
| forcecoldboot = 1; |
| } |
| |
| /* |
| * set the current profile and vsteps |
| * in case they get written during cold start |
| */ |
| |
| active_profile = tfa98xx->profile_current; |
| tfa98xx->profile_current = next_profile; |
| |
| active_vstep = tfa98xx->vstep_current; |
| tfa98xx->vstep_current = vstep; |
| |
| pr_debug("Starting device\n"); |
| if (forcecoldboot || tfa98xx_is_coldboot(tfa98xx)) { |
| /* enable I2S output */ |
| ret = tfa98xx_aec_output(tfa98xx, 1); |
| if (ret) |
| return ret; |
| /* cold start up without unmute*/ |
| if (tfaRunSpeakerBoost(tfa98xx, forcecoldboot, power)) |
| return -EINVAL; |
| } else { |
| /* was it not done already */ |
| if (next_profile != active_profile) { |
| ret = tfaContWriteProfile(tfa98xx, next_profile, vstep); |
| if (ret) /* if error, set to original profile*/ |
| tfa98xx->profile_current = active_profile; |
| } else { |
| if (tfa98xx_is_pwdn(tfa98xx)) { |
| /* enable I2S output */ |
| ret = tfa98xx_aec_output(tfa98xx, 1); |
| if (ret) |
| return ret; |
| ret = tfaRunSpeakerBoost(tfa98xx, 0, power); |
| } |
| if (ret) |
| return -EINVAL; |
| |
| if (vstep != active_vstep) { |
| ret = tfaContWriteFilesVstep(tfa98xx, |
| next_profile, |
| vstep); |
| if (ret) |
| tfa98xx->vstep_current = active_vstep; |
| } |
| } |
| } |
| |
| if (power) |
| tfa98xx_unmute(tfa98xx); |
| |
| return ret; |
| } |
| |
| int tfa98xx_dsp_stop(struct tfa98xx *tfa98xx) |
| { |
| int ret = 0; |
| |
| pr_debug("Stopping device [%s]\n", tfa98xx->fw.name); |
| |
| /* mute + SWS wait */ |
| ret = tfa98xx_mute(tfa98xx); |
| if (ret) |
| return ret; |
| |
| /* powerdown CF */ |
| ret = tfa98xx_powerdown(tfa98xx, 1); |
| if (ret) |
| return ret; |
| |
| /* disable I2S output */ |
| ret = tfa98xx_aec_output(tfa98xx, 0); |
| |
| return ret; |
| } |