ALSA: hda: cs35l41: Read Speaker Calibration data from UEFI variables
authorStefan Binding <sbinding@opensource.cirrus.com>
Thu, 30 Jun 2022 00:23:31 +0000 (01:23 +0100)
committerTakashi Iwai <tiwai@suse.de>
Fri, 15 Jul 2022 14:22:40 +0000 (16:22 +0200)
Speaker Calibration data, specific to an individual speaker is
stored inside UEFI variables during calibration, and can be
used by the DSP.

Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
Signed-off-by: Vitaly Rodionov <vitalyr@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20220630002335.366545-11-vitalyr@opensource.cirrus.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/cs35l41_hda.c
sound/pci/hda/cs35l41_hda.h

index 61441bf..75edaff 100644 (file)
 #define HALO_STATE_DSP_CTL_NAME                "HALO_STATE"
 #define HALO_STATE_DSP_CTL_TYPE                5
 #define HALO_STATE_DSP_CTL_ALG         262308
+#define CAL_R_DSP_CTL_NAME             "CAL_R"
+#define CAL_STATUS_DSP_CTL_NAME                "CAL_STATUS"
+#define CAL_CHECKSUM_DSP_CTL_NAME      "CAL_CHECKSUM"
+#define CAL_AMBIENT_DSP_CTL_NAME       "CAL_AMBIENT"
+#define CAL_DSP_CTL_TYPE               5
+#define CAL_DSP_CTL_ALG                        205
 
 static const struct reg_sequence cs35l41_hda_config[] = {
        { CS35L41_PLL_CLK_CTRL,         0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1
@@ -282,6 +288,96 @@ static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41,
        return ret;
 }
 
+#if IS_ENABLED(CONFIG_EFI)
+static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, unsigned int ambient,
+                                    unsigned int r0, unsigned int status, unsigned int checksum)
+{
+       int ret;
+
+       ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_AMBIENT_DSP_CTL_NAME, CAL_DSP_CTL_TYPE,
+                                  CAL_DSP_CTL_ALG, &ambient, 4);
+       if (ret) {
+               dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_AMBIENT_DSP_CTL_NAME,
+                       ret);
+               return ret;
+       }
+       ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_R_DSP_CTL_NAME, CAL_DSP_CTL_TYPE,
+                                  CAL_DSP_CTL_ALG, &r0, 4);
+       if (ret) {
+               dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_R_DSP_CTL_NAME, ret);
+               return ret;
+       }
+       ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_STATUS_DSP_CTL_NAME, CAL_DSP_CTL_TYPE,
+                                  CAL_DSP_CTL_ALG, &status, 4);
+       if (ret) {
+               dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_STATUS_DSP_CTL_NAME,
+                       ret);
+               return ret;
+       }
+       ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_CHECKSUM_DSP_CTL_NAME, CAL_DSP_CTL_TYPE,
+                                  CAL_DSP_CTL_ALG, &checksum, 4);
+       if (ret) {
+               dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_CHECKSUM_DSP_CTL_NAME,
+                       ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41)
+{
+       static efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe,
+                                             0x5a, 0xa3, 0x5d, 0xb3);
+       static efi_char16_t efi_name[] = L"CirrusSmartAmpCalibrationData";
+       const struct cs35l41_amp_efi_data *efi_data;
+       const struct cs35l41_amp_cal_data *cl;
+       unsigned long data_size = 0;
+       efi_status_t status;
+       int ret = 0;
+       u8 *data = NULL;
+       u32 attr;
+
+       /* Get real size of UEFI variable */
+       status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data);
+       if (status == EFI_BUFFER_TOO_SMALL) {
+               ret = -ENODEV;
+               /* Allocate data buffer of data_size bytes */
+               data = vmalloc(data_size);
+               if (!data)
+                       return -ENOMEM;
+               /* Get variable contents into buffer */
+               status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data);
+               if (status == EFI_SUCCESS) {
+                       efi_data = (struct cs35l41_amp_efi_data *)data;
+                       dev_dbg(cs35l41->dev, "Calibration: Size=%d, Amp Count=%d\n",
+                               efi_data->size, efi_data->count);
+                       if (efi_data->count > cs35l41->index) {
+                               cl = &efi_data->data[cs35l41->index];
+                               dev_dbg(cs35l41->dev,
+                                       "Calibration: Ambient=%02x, Status=%02x, R0=%d\n",
+                                       cl->calAmbient, cl->calStatus, cl->calR);
+
+                               /* Calibration can only be applied whilst the DSP is not running */
+                               ret = cs35l41_apply_calibration(cs35l41,
+                                                               cpu_to_be32(cl->calAmbient),
+                                                               cpu_to_be32(cl->calR),
+                                                               cpu_to_be32(cl->calStatus),
+                                                               cpu_to_be32(cl->calR + 1));
+                       }
+               }
+               vfree(data);
+       }
+       return ret;
+}
+#else
+static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41)
+{
+       dev_warn(cs35l41->dev, "Calibration not supported without EFI support.\n");
+       return 0;
+}
+#endif
+
 static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41)
 {
        const struct firmware *coeff_firmware = NULL;
@@ -314,7 +410,12 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41)
 
        ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename,
                              FW_NAME);
+       if (ret)
+               goto err_release;
+
+       ret = cs35l41_save_calibration(cs35l41);
 
+err_release:
        release_firmware(wmfw_firmware);
        release_firmware(coeff_firmware);
        kfree(wmfw_filename);
index 439c4b7..59a9461 100644 (file)
@@ -10,6 +10,7 @@
 #ifndef __CS35L41_HDA_H__
 #define __CS35L41_HDA_H__
 
+#include <linux/efi.h>
 #include <linux/regulator/consumer.h>
 #include <linux/gpio/consumer.h>
 #include <linux/device.h>
 #include <linux/firmware/cirrus/cs_dsp.h>
 #include <linux/firmware/cirrus/wmfw.h>
 
+struct cs35l41_amp_cal_data {
+       u32 calTarget[2];
+       u32 calTime[2];
+       s8 calAmbient;
+       u8 calStatus;
+       u16 calR;
+} __packed;
+
+struct cs35l41_amp_efi_data {
+       u32 size;
+       u32 count;
+       struct cs35l41_amp_cal_data data[];
+} __packed;
+
 enum cs35l41_hda_spk_pos {
        CS35l41_LEFT,
        CS35l41_RIGHT,