Merge branch 'fix/intel' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[sfrench/cifs-2.6.git] / sound / soc / intel / skylake / skl-nhlt.c
index d14c50a602894c4ad8aa76f2259839552aff2f6a..d9b3dc89a1dc49cefbe7d5d89907b72c261a9ba9 100644 (file)
@@ -19,6 +19,7 @@
  */
 #include <linux/pci.h>
 #include "skl.h"
+#include "skl-i2s.h"
 
 #define NHLT_ACPI_HEADER_SIG   "NHLT"
 
@@ -43,7 +44,8 @@ struct nhlt_acpi_table *skl_nhlt_init(struct device *dev)
        obj = acpi_evaluate_dsm(handle, &osc_guid, 1, 1, NULL);
        if (obj && obj->type == ACPI_TYPE_BUFFER) {
                nhlt_ptr = (struct nhlt_resource_desc  *)obj->buffer.pointer;
-               nhlt_table = (struct nhlt_acpi_table *)
+               if (nhlt_ptr->length)
+                       nhlt_table = (struct nhlt_acpi_table *)
                                memremap(nhlt_ptr->min_addr, nhlt_ptr->length,
                                MEMREMAP_WB);
                ACPI_FREE(obj);
@@ -271,3 +273,157 @@ void skl_nhlt_remove_sysfs(struct skl *skl)
 
        sysfs_remove_file(&dev->kobj, &dev_attr_platform_id.attr);
 }
+
+/*
+ * Queries NHLT for all the fmt configuration for a particular endpoint and
+ * stores all possible rates supported in a rate table for the corresponding
+ * sclk/sclkfs.
+ */
+static void skl_get_ssp_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks,
+                               struct nhlt_fmt *fmt, u8 id)
+{
+       struct skl_i2s_config_blob_legacy *i2s_config;
+       struct skl_clk_parent_src *parent;
+       struct skl_ssp_clk *sclk, *sclkfs;
+       struct nhlt_fmt_cfg *fmt_cfg;
+       struct wav_fmt_ext *wav_fmt;
+       unsigned long rate = 0;
+       bool present = false;
+       int rate_index = 0;
+       u16 channels, bps;
+       u8 clk_src;
+       int i, j;
+       u32 fs;
+
+       sclk = &ssp_clks[SKL_SCLK_OFS];
+       sclkfs = &ssp_clks[SKL_SCLKFS_OFS];
+
+       if (fmt->fmt_count == 0)
+               return;
+
+       for (i = 0; i < fmt->fmt_count; i++) {
+               fmt_cfg = &fmt->fmt_config[i];
+               wav_fmt = &fmt_cfg->fmt_ext;
+
+               channels = wav_fmt->fmt.channels;
+               bps = wav_fmt->fmt.bits_per_sample;
+               fs = wav_fmt->fmt.samples_per_sec;
+
+               /*
+                * In case of TDM configuration on a ssp, there can
+                * be more than one blob in which channel masks are
+                * different for each usecase for a specific rate and bps.
+                * But the sclk rate will be generated for the total
+                * number of channels used for that endpoint.
+                *
+                * So for the given fs and bps, choose blob which has
+                * the superset of all channels for that endpoint and
+                * derive the rate.
+                */
+               for (j = i; j < fmt->fmt_count; j++) {
+                       fmt_cfg = &fmt->fmt_config[j];
+                       wav_fmt = &fmt_cfg->fmt_ext;
+                       if ((fs == wav_fmt->fmt.samples_per_sec) &&
+                          (bps == wav_fmt->fmt.bits_per_sample))
+                               channels = max_t(u16, channels,
+                                               wav_fmt->fmt.channels);
+               }
+
+               rate = channels * bps * fs;
+
+               /* check if the rate is added already to the given SSP's sclk */
+               for (j = 0; (j < SKL_MAX_CLK_RATES) &&
+                           (sclk[id].rate_cfg[j].rate != 0); j++) {
+                       if (sclk[id].rate_cfg[j].rate == rate) {
+                               present = true;
+                               break;
+                       }
+               }
+
+               /* Fill rate and parent for sclk/sclkfs */
+               if (!present) {
+                       /* MCLK Divider Source Select */
+                       i2s_config = (struct skl_i2s_config_blob_legacy *)
+                                               fmt->fmt_config[0].config.caps;
+                       clk_src = ((i2s_config->mclk.mdivctrl)
+                                       & SKL_MNDSS_DIV_CLK_SRC_MASK) >>
+                                       SKL_SHIFT(SKL_MNDSS_DIV_CLK_SRC_MASK);
+
+                       parent = skl_get_parent_clk(clk_src);
+
+                       /*
+                        * Do not copy the config data if there is no parent
+                        * clock available for this clock source select
+                        */
+                       if (!parent)
+                               continue;
+
+                       sclk[id].rate_cfg[rate_index].rate = rate;
+                       sclk[id].rate_cfg[rate_index].config = fmt_cfg;
+                       sclkfs[id].rate_cfg[rate_index].rate = rate;
+                       sclkfs[id].rate_cfg[rate_index].config = fmt_cfg;
+                       sclk[id].parent_name = parent->name;
+                       sclkfs[id].parent_name = parent->name;
+
+                       rate_index++;
+               }
+       }
+}
+
+static void skl_get_mclk(struct skl *skl, struct skl_ssp_clk *mclk,
+                               struct nhlt_fmt *fmt, u8 id)
+{
+       struct skl_i2s_config_blob_legacy *i2s_config;
+       struct nhlt_specific_cfg *fmt_cfg;
+       struct skl_clk_parent_src *parent;
+       u32 clkdiv, div_ratio;
+       u8 clk_src;
+
+       fmt_cfg = &fmt->fmt_config[0].config;
+       i2s_config = (struct skl_i2s_config_blob_legacy *)fmt_cfg->caps;
+
+       /* MCLK Divider Source Select */
+       clk_src = ((i2s_config->mclk.mdivctrl) & SKL_MCLK_DIV_CLK_SRC_MASK) >>
+                                       SKL_SHIFT(SKL_MCLK_DIV_CLK_SRC_MASK);
+
+       clkdiv = i2s_config->mclk.mdivr & SKL_MCLK_DIV_RATIO_MASK;
+
+       /* bypass divider */
+       div_ratio = 1;
+
+       if (clkdiv != SKL_MCLK_DIV_RATIO_MASK)
+               /* Divider is 2 + clkdiv */
+               div_ratio = clkdiv + 2;
+
+       /* Calculate MCLK rate from source using div value */
+       parent = skl_get_parent_clk(clk_src);
+       if (!parent)
+               return;
+
+       mclk[id].rate_cfg[0].rate = parent->rate/div_ratio;
+       mclk[id].rate_cfg[0].config = &fmt->fmt_config[0];
+       mclk[id].parent_name = parent->name;
+}
+
+void skl_get_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks)
+{
+       struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt;
+       struct nhlt_endpoint *epnt;
+       struct nhlt_fmt *fmt;
+       int i;
+       u8 id;
+
+       epnt = (struct nhlt_endpoint *)nhlt->desc;
+       for (i = 0; i < nhlt->endpoint_count; i++) {
+               if (epnt->linktype == NHLT_LINK_SSP) {
+                       id = epnt->virtual_bus_id;
+
+                       fmt = (struct nhlt_fmt *)(epnt->config.caps
+                                       + epnt->config.size);
+
+                       skl_get_ssp_clks(skl, ssp_clks, fmt, id);
+                       skl_get_mclk(skl, ssp_clks, fmt, id);
+               }
+               epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
+       }
+}