ALSA: hda: cs35l41: Support Speaker ID for laptops
[platform/kernel/linux-starfive.git] / sound / pci / hda / cs35l41_hda.c
index d356c1a..d5d2323 100644 (file)
@@ -86,13 +86,19 @@ static const struct cs_dsp_client_ops client_ops = {
 static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41,
                                         const struct firmware **firmware, char **filename,
                                         const char *dir, const char *ssid, const char *amp_name,
-                                        const char *filetype)
+                                        int spkid, const char *filetype)
 {
        const char * const dsp_name = cs35l41->cs_dsp.name;
        char *s, c;
        int ret = 0;
 
-       if (ssid && amp_name)
+       if (spkid > -1 && ssid && amp_name)
+               *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d-%s.%s", dir, CS35L41_PART,
+                                     dsp_name, "spk-prot", ssid, spkid, amp_name, filetype);
+       else if (spkid > -1 && ssid)
+               *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d.%s", dir, CS35L41_PART,
+                                     dsp_name, "spk-prot", ssid, spkid, filetype);
+       else if (ssid && amp_name)
                *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, CS35L41_PART,
                                      dsp_name, "spk-prot", ssid, amp_name,
                                      filetype);
@@ -130,6 +136,93 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41,
        return ret;
 }
 
+static int cs35l41_request_firmware_files_spkid(struct cs35l41_hda *cs35l41,
+                                               const struct firmware **wmfw_firmware,
+                                               char **wmfw_filename,
+                                               const struct firmware **coeff_firmware,
+                                               char **coeff_filename)
+{
+       int ret;
+
+       /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.wmfw */
+       ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
+                                           CS35L41_FIRMWARE_ROOT,
+                                           cs35l41->acpi_subsystem_id, cs35l41->amp_name,
+                                           cs35l41->speaker_id, "wmfw");
+       if (!ret) {
+               /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */
+               cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+                                             CS35L41_FIRMWARE_ROOT,
+                                             cs35l41->acpi_subsystem_id, cs35l41->amp_name,
+                                             cs35l41->speaker_id, "bin");
+               return 0;
+       }
+
+       /* try cirrus/part-dspN-fwtype-sub<-ampname>.wmfw */
+       ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
+                                           CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
+                                           cs35l41->amp_name, -1, "wmfw");
+       if (!ret) {
+               /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */
+               cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+                                             CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
+                                             cs35l41->amp_name, cs35l41->speaker_id, "bin");
+               return 0;
+       }
+
+       /* try cirrus/part-dspN-fwtype-sub<-spkidN>.wmfw */
+       ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
+                                           CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
+                                           NULL, cs35l41->speaker_id, "wmfw");
+       if (!ret) {
+               /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */
+               ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+                                                   CS35L41_FIRMWARE_ROOT,
+                                                   cs35l41->acpi_subsystem_id,
+                                                   cs35l41->amp_name, cs35l41->speaker_id, "bin");
+               if (ret)
+                       /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */
+                       cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+                                               CS35L41_FIRMWARE_ROOT,
+                                               cs35l41->acpi_subsystem_id,
+                                               NULL, cs35l41->speaker_id, "bin");
+               return 0;
+       }
+
+       /* try cirrus/part-dspN-fwtype-sub.wmfw */
+       ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
+                                           CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
+                                           NULL, -1, "wmfw");
+       if (!ret) {
+               /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */
+               ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+                                                   CS35L41_FIRMWARE_ROOT,
+                                                   cs35l41->acpi_subsystem_id,
+                                                   cs35l41->amp_name, cs35l41->speaker_id, "bin");
+               if (ret)
+                       /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */
+                       cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+                                                     CS35L41_FIRMWARE_ROOT,
+                                                     cs35l41->acpi_subsystem_id,
+                                                     NULL, cs35l41->speaker_id, "bin");
+               return 0;
+       }
+
+       /* fallback try cirrus/part-dspN-fwtype.wmfw */
+       ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
+                                           CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "wmfw");
+       if (!ret) {
+               /* fallback try cirrus/part-dspN-fwtype.bin */
+               cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+                                             CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin");
+               return 0;
+       }
+
+       dev_warn(cs35l41->dev, "Failed to request firmware\n");
+
+       return ret;
+}
+
 static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41,
                                          const struct firmware **wmfw_firmware,
                                          char **wmfw_filename,
@@ -138,43 +231,48 @@ static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41,
 {
        int ret;
 
+       if (cs35l41->speaker_id > -1)
+               return cs35l41_request_firmware_files_spkid(cs35l41, wmfw_firmware, wmfw_filename,
+                                                           coeff_firmware, coeff_filename);
+
        /* try cirrus/part-dspN-fwtype-sub<-ampname>.wmfw */
        ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
                                            CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
-                                           cs35l41->amp_name, "wmfw");
+                                           cs35l41->amp_name, -1, "wmfw");
        if (!ret) {
                /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */
                cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
                                              CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
-                                             cs35l41->amp_name, "bin");
+                                             cs35l41->amp_name, -1, "bin");
                return 0;
        }
 
        /* try cirrus/part-dspN-fwtype-sub.wmfw */
        ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
                                            CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
-                                           NULL, "wmfw");
+                                           NULL, -1, "wmfw");
        if (!ret) {
                /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */
                ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
                                                    CS35L41_FIRMWARE_ROOT,
                                                    cs35l41->acpi_subsystem_id,
-                                                   cs35l41->amp_name, "bin");
+                                                   cs35l41->amp_name, -1, "bin");
                if (ret)
                        /* try cirrus/part-dspN-fwtype-sub.bin */
                        cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
                                                      CS35L41_FIRMWARE_ROOT,
-                                                     cs35l41->acpi_subsystem_id, NULL, "bin");
+                                                     cs35l41->acpi_subsystem_id,
+                                                     NULL, -1, "bin");
                return 0;
        }
 
        /* fallback try cirrus/part-dspN-fwtype.wmfw */
        ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
-                                           CS35L41_FIRMWARE_ROOT, NULL, NULL, "wmfw");
+                                           CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "wmfw");
        if (!ret) {
                /* fallback try cirrus/part-dspN-fwtype.bin */
                cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
-                                             CS35L41_FIRMWARE_ROOT, NULL, NULL, "bin");
+                                             CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin");
                return 0;
        }
 
@@ -614,6 +712,61 @@ static int cs35l41_get_acpi_sub_string(struct device *dev, struct acpi_device *a
        return ret;
 }
 
+static int cs35l41_get_speaker_id(struct device *dev, int amp_index,
+                                 int num_amps, int fixed_gpio_id)
+{
+       struct gpio_desc *speaker_id_desc;
+       int speaker_id = -ENODEV;
+
+       if (fixed_gpio_id >= 0) {
+               dev_dbg(dev, "Found Fixed Speaker ID GPIO (index = %d)\n", fixed_gpio_id);
+               speaker_id_desc = gpiod_get_index(dev, NULL, fixed_gpio_id, GPIOD_IN);
+               if (IS_ERR(speaker_id_desc)) {
+                       speaker_id = PTR_ERR(speaker_id_desc);
+                       return speaker_id;
+               }
+               speaker_id = gpiod_get_value_cansleep(speaker_id_desc);
+               gpiod_put(speaker_id_desc);
+               dev_dbg(dev, "Speaker ID = %d\n", speaker_id);
+       } else {
+               int base_index;
+               int gpios_per_amp;
+               int count;
+               int tmp;
+               int i;
+
+               count = gpiod_count(dev, "spk-id");
+               if (count > 0) {
+                       speaker_id = 0;
+                       gpios_per_amp = count / num_amps;
+                       base_index = gpios_per_amp * amp_index;
+
+                       if (count % num_amps)
+                               return -EINVAL;
+
+                       dev_dbg(dev, "Found %d Speaker ID GPIOs per Amp\n", gpios_per_amp);
+
+                       for (i = 0; i < gpios_per_amp; i++) {
+                               speaker_id_desc = gpiod_get_index(dev, "spk-id", i + base_index,
+                                                                 GPIOD_IN);
+                               if (IS_ERR(speaker_id_desc)) {
+                                       speaker_id = PTR_ERR(speaker_id_desc);
+                                       break;
+                               }
+                               tmp = gpiod_get_value_cansleep(speaker_id_desc);
+                               gpiod_put(speaker_id_desc);
+                               if (tmp < 0) {
+                                       speaker_id = tmp;
+                                       break;
+                               }
+                               speaker_id |= tmp << i;
+                       }
+                       dev_dbg(dev, "Speaker ID = %d\n", speaker_id);
+               }
+       }
+       return speaker_id;
+}
+
 static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id)
 {
        struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
@@ -719,6 +872,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
        else
                hw_cfg->bst_cap = -1;
 
+       cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, cs35l41->index, nval, -1);
+
        if (hw_cfg->bst_ind > 0 || hw_cfg->bst_cap > 0 || hw_cfg->bst_ipk > 0)
                hw_cfg->bst_type = CS35L41_INT_BOOST;
        else
@@ -752,6 +907,7 @@ no_acpi_dsd:
        cs35l41->channel_index = 0;
        cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH);
        cs35l41->hw_cfg.bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH;
+       cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2);
        hw_cfg->gpio2.func = CS35L41_GPIO2_INT_OPEN_DRAIN;
        hw_cfg->gpio2.valid = true;
        cs35l41->hw_cfg.valid = true;