|
@@ -15,6 +15,7 @@
|
|
|
#include <linux/init.h>
|
|
|
#include <linux/delay.h>
|
|
|
#include <linux/firmware.h>
|
|
|
+#include <linux/list.h>
|
|
|
#include <linux/pm.h>
|
|
|
#include <linux/pm_runtime.h>
|
|
|
#include <linux/regmap.h>
|
|
@@ -103,6 +104,13 @@
|
|
|
#define ADSP1_START_SHIFT 0 /* DSP1_START */
|
|
|
#define ADSP1_START_WIDTH 1 /* DSP1_START */
|
|
|
|
|
|
+/*
|
|
|
+ * ADSP1 Control 31
|
|
|
+ */
|
|
|
+#define ADSP1_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */
|
|
|
+#define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */
|
|
|
+#define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */
|
|
|
+
|
|
|
#define ADSP2_CONTROL 0x0
|
|
|
#define ADSP2_CLOCKING 0x1
|
|
|
#define ADSP2_STATUS1 0x4
|
|
@@ -146,6 +154,109 @@
|
|
|
#define ADSP2_RAM_RDY_SHIFT 0
|
|
|
#define ADSP2_RAM_RDY_WIDTH 1
|
|
|
|
|
|
+struct wm_adsp_buf {
|
|
|
+ struct list_head list;
|
|
|
+ void *buf;
|
|
|
+};
|
|
|
+
|
|
|
+static struct wm_adsp_buf *wm_adsp_buf_alloc(const void *src, size_t len,
|
|
|
+ struct list_head *list)
|
|
|
+{
|
|
|
+ struct wm_adsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL);
|
|
|
+
|
|
|
+ if (buf == NULL)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ buf->buf = kmemdup(src, len, GFP_KERNEL | GFP_DMA);
|
|
|
+ if (!buf->buf) {
|
|
|
+ kfree(buf);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (list)
|
|
|
+ list_add_tail(&buf->list, list);
|
|
|
+
|
|
|
+ return buf;
|
|
|
+}
|
|
|
+
|
|
|
+static void wm_adsp_buf_free(struct list_head *list)
|
|
|
+{
|
|
|
+ while (!list_empty(list)) {
|
|
|
+ struct wm_adsp_buf *buf = list_first_entry(list,
|
|
|
+ struct wm_adsp_buf,
|
|
|
+ list);
|
|
|
+ list_del(&buf->list);
|
|
|
+ kfree(buf->buf);
|
|
|
+ kfree(buf);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#define WM_ADSP_NUM_FW 4
|
|
|
+
|
|
|
+static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = {
|
|
|
+ "MBC/VSS", "Tx", "Tx Speaker", "Rx ANC"
|
|
|
+};
|
|
|
+
|
|
|
+static struct {
|
|
|
+ const char *file;
|
|
|
+} wm_adsp_fw[WM_ADSP_NUM_FW] = {
|
|
|
+ { .file = "mbc-vss" },
|
|
|
+ { .file = "tx" },
|
|
|
+ { .file = "tx-spk" },
|
|
|
+ { .file = "rx-anc" },
|
|
|
+};
|
|
|
+
|
|
|
+static int wm_adsp_fw_get(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
|
+ struct wm_adsp *adsp = snd_soc_codec_get_drvdata(codec);
|
|
|
+
|
|
|
+ ucontrol->value.integer.value[0] = adsp[e->shift_l].fw;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int wm_adsp_fw_put(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
|
+ struct wm_adsp *adsp = snd_soc_codec_get_drvdata(codec);
|
|
|
+
|
|
|
+ if (ucontrol->value.integer.value[0] == adsp[e->shift_l].fw)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (ucontrol->value.integer.value[0] >= WM_ADSP_NUM_FW)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (adsp[e->shift_l].running)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ adsp[e->shift_l].fw = ucontrol->value.integer.value[0];
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static const struct soc_enum wm_adsp_fw_enum[] = {
|
|
|
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
|
+ SOC_ENUM_SINGLE(0, 1, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
|
+ SOC_ENUM_SINGLE(0, 2, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
|
+ SOC_ENUM_SINGLE(0, 3, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
|
+};
|
|
|
+
|
|
|
+const struct snd_kcontrol_new wm_adsp_fw_controls[] = {
|
|
|
+ SOC_ENUM_EXT("DSP1 Firmware", wm_adsp_fw_enum[0],
|
|
|
+ wm_adsp_fw_get, wm_adsp_fw_put),
|
|
|
+ SOC_ENUM_EXT("DSP2 Firmware", wm_adsp_fw_enum[1],
|
|
|
+ wm_adsp_fw_get, wm_adsp_fw_put),
|
|
|
+ SOC_ENUM_EXT("DSP3 Firmware", wm_adsp_fw_enum[2],
|
|
|
+ wm_adsp_fw_get, wm_adsp_fw_put),
|
|
|
+ SOC_ENUM_EXT("DSP4 Firmware", wm_adsp_fw_enum[3],
|
|
|
+ wm_adsp_fw_get, wm_adsp_fw_put),
|
|
|
+};
|
|
|
+EXPORT_SYMBOL_GPL(wm_adsp_fw_controls);
|
|
|
|
|
|
static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp,
|
|
|
int type)
|
|
@@ -159,8 +270,29 @@ static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp,
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
+static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *region,
|
|
|
+ unsigned int offset)
|
|
|
+{
|
|
|
+ switch (region->type) {
|
|
|
+ case WMFW_ADSP1_PM:
|
|
|
+ return region->base + (offset * 3);
|
|
|
+ case WMFW_ADSP1_DM:
|
|
|
+ return region->base + (offset * 2);
|
|
|
+ case WMFW_ADSP2_XM:
|
|
|
+ return region->base + (offset * 2);
|
|
|
+ case WMFW_ADSP2_YM:
|
|
|
+ return region->base + (offset * 2);
|
|
|
+ case WMFW_ADSP1_ZM:
|
|
|
+ return region->base + (offset * 2);
|
|
|
+ default:
|
|
|
+ WARN_ON(NULL != "Unknown memory region type");
|
|
|
+ return offset;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static int wm_adsp_load(struct wm_adsp *dsp)
|
|
|
{
|
|
|
+ LIST_HEAD(buf_list);
|
|
|
const struct firmware *firmware;
|
|
|
struct regmap *regmap = dsp->regmap;
|
|
|
unsigned int pos = 0;
|
|
@@ -172,7 +304,7 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
|
|
const struct wm_adsp_region *mem;
|
|
|
const char *region_name;
|
|
|
char *file, *text;
|
|
|
- void *buf;
|
|
|
+ struct wm_adsp_buf *buf;
|
|
|
unsigned int reg;
|
|
|
int regions = 0;
|
|
|
int ret, offset, type, sizes;
|
|
@@ -181,7 +313,8 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
|
|
if (file == NULL)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
- snprintf(file, PAGE_SIZE, "%s-dsp%d.wmfw", dsp->part, dsp->num);
|
|
|
+ snprintf(file, PAGE_SIZE, "%s-dsp%d-%s.wmfw", dsp->part, dsp->num,
|
|
|
+ wm_adsp_fw[dsp->fw].file);
|
|
|
file[PAGE_SIZE - 1] = '\0';
|
|
|
|
|
|
ret = request_firmware(&firmware, file, dsp->dev);
|
|
@@ -286,27 +419,27 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
|
|
case WMFW_ADSP1_PM:
|
|
|
BUG_ON(!mem);
|
|
|
region_name = "PM";
|
|
|
- reg = mem->base + (offset * 3);
|
|
|
+ reg = wm_adsp_region_to_reg(mem, offset);
|
|
|
break;
|
|
|
case WMFW_ADSP1_DM:
|
|
|
BUG_ON(!mem);
|
|
|
region_name = "DM";
|
|
|
- reg = mem->base + (offset * 2);
|
|
|
+ reg = wm_adsp_region_to_reg(mem, offset);
|
|
|
break;
|
|
|
case WMFW_ADSP2_XM:
|
|
|
BUG_ON(!mem);
|
|
|
region_name = "XM";
|
|
|
- reg = mem->base + (offset * 2);
|
|
|
+ reg = wm_adsp_region_to_reg(mem, offset);
|
|
|
break;
|
|
|
case WMFW_ADSP2_YM:
|
|
|
BUG_ON(!mem);
|
|
|
region_name = "YM";
|
|
|
- reg = mem->base + (offset * 2);
|
|
|
+ reg = wm_adsp_region_to_reg(mem, offset);
|
|
|
break;
|
|
|
case WMFW_ADSP1_ZM:
|
|
|
BUG_ON(!mem);
|
|
|
region_name = "ZM";
|
|
|
- reg = mem->base + (offset * 2);
|
|
|
+ reg = wm_adsp_region_to_reg(mem, offset);
|
|
|
break;
|
|
|
default:
|
|
|
adsp_warn(dsp,
|
|
@@ -326,18 +459,16 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
|
|
}
|
|
|
|
|
|
if (reg) {
|
|
|
- buf = kmemdup(region->data, le32_to_cpu(region->len),
|
|
|
- GFP_KERNEL | GFP_DMA);
|
|
|
+ buf = wm_adsp_buf_alloc(region->data,
|
|
|
+ le32_to_cpu(region->len),
|
|
|
+ &buf_list);
|
|
|
if (!buf) {
|
|
|
adsp_err(dsp, "Out of memory\n");
|
|
|
return -ENOMEM;
|
|
|
}
|
|
|
|
|
|
- ret = regmap_raw_write(regmap, reg, buf,
|
|
|
- le32_to_cpu(region->len));
|
|
|
-
|
|
|
- kfree(buf);
|
|
|
-
|
|
|
+ ret = regmap_raw_write_async(regmap, reg, buf->buf,
|
|
|
+ le32_to_cpu(region->len));
|
|
|
if (ret != 0) {
|
|
|
adsp_err(dsp,
|
|
|
"%s.%d: Failed to write %d bytes at %d in %s: %d\n",
|
|
@@ -351,12 +482,20 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
|
|
pos += le32_to_cpu(region->len) + sizeof(*region);
|
|
|
regions++;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ ret = regmap_async_complete(regmap);
|
|
|
+ if (ret != 0) {
|
|
|
+ adsp_err(dsp, "Failed to complete async write: %d\n", ret);
|
|
|
+ goto out_fw;
|
|
|
+ }
|
|
|
+
|
|
|
if (pos > firmware->size)
|
|
|
adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n",
|
|
|
file, regions, pos - firmware->size);
|
|
|
|
|
|
out_fw:
|
|
|
+ regmap_async_complete(regmap);
|
|
|
+ wm_adsp_buf_free(&buf_list);
|
|
|
release_firmware(firmware);
|
|
|
out:
|
|
|
kfree(file);
|
|
@@ -364,22 +503,222 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static int wm_adsp_setup_algs(struct wm_adsp *dsp)
|
|
|
+{
|
|
|
+ struct regmap *regmap = dsp->regmap;
|
|
|
+ struct wmfw_adsp1_id_hdr adsp1_id;
|
|
|
+ struct wmfw_adsp2_id_hdr adsp2_id;
|
|
|
+ struct wmfw_adsp1_alg_hdr *adsp1_alg;
|
|
|
+ struct wmfw_adsp2_alg_hdr *adsp2_alg;
|
|
|
+ void *alg, *buf;
|
|
|
+ struct wm_adsp_alg_region *region;
|
|
|
+ const struct wm_adsp_region *mem;
|
|
|
+ unsigned int pos, term;
|
|
|
+ size_t algs, buf_size;
|
|
|
+ __be32 val;
|
|
|
+ int i, ret;
|
|
|
+
|
|
|
+ switch (dsp->type) {
|
|
|
+ case WMFW_ADSP1:
|
|
|
+ mem = wm_adsp_find_region(dsp, WMFW_ADSP1_DM);
|
|
|
+ break;
|
|
|
+ case WMFW_ADSP2:
|
|
|
+ mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ mem = NULL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mem == NULL) {
|
|
|
+ BUG_ON(mem != NULL);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (dsp->type) {
|
|
|
+ case WMFW_ADSP1:
|
|
|
+ ret = regmap_raw_read(regmap, mem->base, &adsp1_id,
|
|
|
+ sizeof(adsp1_id));
|
|
|
+ if (ret != 0) {
|
|
|
+ adsp_err(dsp, "Failed to read algorithm info: %d\n",
|
|
|
+ ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf = &adsp1_id;
|
|
|
+ buf_size = sizeof(adsp1_id);
|
|
|
+
|
|
|
+ algs = be32_to_cpu(adsp1_id.algs);
|
|
|
+ adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n",
|
|
|
+ be32_to_cpu(adsp1_id.fw.id),
|
|
|
+ (be32_to_cpu(adsp1_id.fw.ver) & 0xff0000) >> 16,
|
|
|
+ (be32_to_cpu(adsp1_id.fw.ver) & 0xff00) >> 8,
|
|
|
+ be32_to_cpu(adsp1_id.fw.ver) & 0xff,
|
|
|
+ algs);
|
|
|
+
|
|
|
+ pos = sizeof(adsp1_id) / 2;
|
|
|
+ term = pos + ((sizeof(*adsp1_alg) * algs) / 2);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case WMFW_ADSP2:
|
|
|
+ ret = regmap_raw_read(regmap, mem->base, &adsp2_id,
|
|
|
+ sizeof(adsp2_id));
|
|
|
+ if (ret != 0) {
|
|
|
+ adsp_err(dsp, "Failed to read algorithm info: %d\n",
|
|
|
+ ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf = &adsp2_id;
|
|
|
+ buf_size = sizeof(adsp2_id);
|
|
|
+
|
|
|
+ algs = be32_to_cpu(adsp2_id.algs);
|
|
|
+ adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n",
|
|
|
+ be32_to_cpu(adsp2_id.fw.id),
|
|
|
+ (be32_to_cpu(adsp2_id.fw.ver) & 0xff0000) >> 16,
|
|
|
+ (be32_to_cpu(adsp2_id.fw.ver) & 0xff00) >> 8,
|
|
|
+ be32_to_cpu(adsp2_id.fw.ver) & 0xff,
|
|
|
+ algs);
|
|
|
+
|
|
|
+ pos = sizeof(adsp2_id) / 2;
|
|
|
+ term = pos + ((sizeof(*adsp2_alg) * algs) / 2);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ BUG_ON(NULL == "Unknown DSP type");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (algs == 0) {
|
|
|
+ adsp_err(dsp, "No algorithms\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (algs > 1024) {
|
|
|
+ adsp_err(dsp, "Algorithm count %zx excessive\n", algs);
|
|
|
+ print_hex_dump_bytes(dev_name(dsp->dev), DUMP_PREFIX_OFFSET,
|
|
|
+ buf, buf_size);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Read the terminator first to validate the length */
|
|
|
+ ret = regmap_raw_read(regmap, mem->base + term, &val, sizeof(val));
|
|
|
+ if (ret != 0) {
|
|
|
+ adsp_err(dsp, "Failed to read algorithm list end: %d\n",
|
|
|
+ ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (be32_to_cpu(val) != 0xbedead)
|
|
|
+ adsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbeadead\n",
|
|
|
+ term, be32_to_cpu(val));
|
|
|
+
|
|
|
+ alg = kzalloc((term - pos) * 2, GFP_KERNEL | GFP_DMA);
|
|
|
+ if (!alg)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ ret = regmap_raw_read(regmap, mem->base + pos, alg, (term - pos) * 2);
|
|
|
+ if (ret != 0) {
|
|
|
+ adsp_err(dsp, "Failed to read algorithm list: %d\n",
|
|
|
+ ret);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ adsp1_alg = alg;
|
|
|
+ adsp2_alg = alg;
|
|
|
+
|
|
|
+ for (i = 0; i < algs; i++) {
|
|
|
+ switch (dsp->type) {
|
|
|
+ case WMFW_ADSP1:
|
|
|
+ adsp_info(dsp, "%d: ID %x v%d.%d.%d DM@%x ZM@%x\n",
|
|
|
+ i, be32_to_cpu(adsp1_alg[i].alg.id),
|
|
|
+ (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff0000) >> 16,
|
|
|
+ (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff00) >> 8,
|
|
|
+ be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff,
|
|
|
+ be32_to_cpu(adsp1_alg[i].dm),
|
|
|
+ be32_to_cpu(adsp1_alg[i].zm));
|
|
|
+
|
|
|
+ region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
|
+ if (!region)
|
|
|
+ return -ENOMEM;
|
|
|
+ region->type = WMFW_ADSP1_DM;
|
|
|
+ region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
|
|
|
+ region->base = be32_to_cpu(adsp1_alg[i].dm);
|
|
|
+ list_add_tail(®ion->list, &dsp->alg_regions);
|
|
|
+
|
|
|
+ region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
|
+ if (!region)
|
|
|
+ return -ENOMEM;
|
|
|
+ region->type = WMFW_ADSP1_ZM;
|
|
|
+ region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
|
|
|
+ region->base = be32_to_cpu(adsp1_alg[i].zm);
|
|
|
+ list_add_tail(®ion->list, &dsp->alg_regions);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case WMFW_ADSP2:
|
|
|
+ adsp_info(dsp,
|
|
|
+ "%d: ID %x v%d.%d.%d XM@%x YM@%x ZM@%x\n",
|
|
|
+ i, be32_to_cpu(adsp2_alg[i].alg.id),
|
|
|
+ (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff0000) >> 16,
|
|
|
+ (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff00) >> 8,
|
|
|
+ be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff,
|
|
|
+ be32_to_cpu(adsp2_alg[i].xm),
|
|
|
+ be32_to_cpu(adsp2_alg[i].ym),
|
|
|
+ be32_to_cpu(adsp2_alg[i].zm));
|
|
|
+
|
|
|
+ region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
|
+ if (!region)
|
|
|
+ return -ENOMEM;
|
|
|
+ region->type = WMFW_ADSP2_XM;
|
|
|
+ region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
|
|
+ region->base = be32_to_cpu(adsp2_alg[i].xm);
|
|
|
+ list_add_tail(®ion->list, &dsp->alg_regions);
|
|
|
+
|
|
|
+ region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
|
+ if (!region)
|
|
|
+ return -ENOMEM;
|
|
|
+ region->type = WMFW_ADSP2_YM;
|
|
|
+ region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
|
|
+ region->base = be32_to_cpu(adsp2_alg[i].ym);
|
|
|
+ list_add_tail(®ion->list, &dsp->alg_regions);
|
|
|
+
|
|
|
+ region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
|
+ if (!region)
|
|
|
+ return -ENOMEM;
|
|
|
+ region->type = WMFW_ADSP2_ZM;
|
|
|
+ region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
|
|
+ region->base = be32_to_cpu(adsp2_alg[i].zm);
|
|
|
+ list_add_tail(®ion->list, &dsp->alg_regions);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+out:
|
|
|
+ kfree(alg);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
|
|
{
|
|
|
+ LIST_HEAD(buf_list);
|
|
|
struct regmap *regmap = dsp->regmap;
|
|
|
struct wmfw_coeff_hdr *hdr;
|
|
|
struct wmfw_coeff_item *blk;
|
|
|
const struct firmware *firmware;
|
|
|
+ const struct wm_adsp_region *mem;
|
|
|
+ struct wm_adsp_alg_region *alg_region;
|
|
|
const char *region_name;
|
|
|
int ret, pos, blocks, type, offset, reg;
|
|
|
char *file;
|
|
|
- void *buf;
|
|
|
+ struct wm_adsp_buf *buf;
|
|
|
+ int tmp;
|
|
|
|
|
|
file = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
|
if (file == NULL)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
- snprintf(file, PAGE_SIZE, "%s-dsp%d.bin", dsp->part, dsp->num);
|
|
|
+ snprintf(file, PAGE_SIZE, "%s-dsp%d-%s.bin", dsp->part, dsp->num,
|
|
|
+ wm_adsp_fw[dsp->fw].file);
|
|
|
file[PAGE_SIZE - 1] = '\0';
|
|
|
|
|
|
ret = request_firmware(&firmware, file, dsp->dev);
|
|
@@ -402,6 +741,16 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
|
|
goto out_fw;
|
|
|
}
|
|
|
|
|
|
+ switch (be32_to_cpu(hdr->rev) & 0xff) {
|
|
|
+ case 1:
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ adsp_err(dsp, "%s: Unsupported coefficient file format %d\n",
|
|
|
+ file, be32_to_cpu(hdr->rev) & 0xff);
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto out_fw;
|
|
|
+ }
|
|
|
+
|
|
|
adsp_dbg(dsp, "%s: v%d.%d.%d\n", file,
|
|
|
(le32_to_cpu(hdr->ver) >> 16) & 0xff,
|
|
|
(le32_to_cpu(hdr->ver) >> 8) & 0xff,
|
|
@@ -414,8 +763,8 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
|
|
pos - firmware->size > sizeof(*blk)) {
|
|
|
blk = (void*)(&firmware->data[pos]);
|
|
|
|
|
|
- type = be32_to_cpu(blk->type) & 0xff;
|
|
|
- offset = le32_to_cpu(blk->offset) & 0xffffff;
|
|
|
+ type = le16_to_cpu(blk->type);
|
|
|
+ offset = le16_to_cpu(blk->offset);
|
|
|
|
|
|
adsp_dbg(dsp, "%s.%d: %x v%d.%d.%d\n",
|
|
|
file, blocks, le32_to_cpu(blk->id),
|
|
@@ -428,52 +777,105 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
|
|
reg = 0;
|
|
|
region_name = "Unknown";
|
|
|
switch (type) {
|
|
|
- case WMFW_NAME_TEXT:
|
|
|
- case WMFW_INFO_TEXT:
|
|
|
+ case (WMFW_NAME_TEXT << 8):
|
|
|
+ case (WMFW_INFO_TEXT << 8):
|
|
|
break;
|
|
|
- case WMFW_ABSOLUTE:
|
|
|
+ case (WMFW_ABSOLUTE << 8):
|
|
|
region_name = "register";
|
|
|
reg = offset;
|
|
|
break;
|
|
|
+
|
|
|
+ case WMFW_ADSP1_DM:
|
|
|
+ case WMFW_ADSP1_ZM:
|
|
|
+ case WMFW_ADSP2_XM:
|
|
|
+ case WMFW_ADSP2_YM:
|
|
|
+ adsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n",
|
|
|
+ file, blocks, le32_to_cpu(blk->len),
|
|
|
+ type, le32_to_cpu(blk->id));
|
|
|
+
|
|
|
+ mem = wm_adsp_find_region(dsp, type);
|
|
|
+ if (!mem) {
|
|
|
+ adsp_err(dsp, "No base for region %x\n", type);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ reg = 0;
|
|
|
+ list_for_each_entry(alg_region,
|
|
|
+ &dsp->alg_regions, list) {
|
|
|
+ if (le32_to_cpu(blk->id) == alg_region->alg &&
|
|
|
+ type == alg_region->type) {
|
|
|
+ reg = alg_region->base;
|
|
|
+ reg = wm_adsp_region_to_reg(mem,
|
|
|
+ reg);
|
|
|
+ reg += offset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reg == 0)
|
|
|
+ adsp_err(dsp, "No %x for algorithm %x\n",
|
|
|
+ type, le32_to_cpu(blk->id));
|
|
|
+ break;
|
|
|
+
|
|
|
default:
|
|
|
- adsp_err(dsp, "Unknown region type %x\n", type);
|
|
|
+ adsp_err(dsp, "%s.%d: Unknown region type %x at %d\n",
|
|
|
+ file, blocks, type, pos);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (reg) {
|
|
|
- buf = kmemdup(blk->data, le32_to_cpu(blk->len),
|
|
|
- GFP_KERNEL | GFP_DMA);
|
|
|
+ buf = wm_adsp_buf_alloc(blk->data,
|
|
|
+ le32_to_cpu(blk->len),
|
|
|
+ &buf_list);
|
|
|
if (!buf) {
|
|
|
adsp_err(dsp, "Out of memory\n");
|
|
|
return -ENOMEM;
|
|
|
}
|
|
|
|
|
|
- ret = regmap_raw_write(regmap, reg, blk->data,
|
|
|
- le32_to_cpu(blk->len));
|
|
|
+ adsp_dbg(dsp, "%s.%d: Writing %d bytes at %x\n",
|
|
|
+ file, blocks, le32_to_cpu(blk->len),
|
|
|
+ reg);
|
|
|
+ ret = regmap_raw_write_async(regmap, reg, buf->buf,
|
|
|
+ le32_to_cpu(blk->len));
|
|
|
if (ret != 0) {
|
|
|
adsp_err(dsp,
|
|
|
"%s.%d: Failed to write to %x in %s\n",
|
|
|
file, blocks, reg, region_name);
|
|
|
}
|
|
|
-
|
|
|
- kfree(buf);
|
|
|
}
|
|
|
|
|
|
- pos += le32_to_cpu(blk->len) + sizeof(*blk);
|
|
|
+ tmp = le32_to_cpu(blk->len) % 4;
|
|
|
+ if (tmp)
|
|
|
+ pos += le32_to_cpu(blk->len) + (4 - tmp) + sizeof(*blk);
|
|
|
+ else
|
|
|
+ pos += le32_to_cpu(blk->len) + sizeof(*blk);
|
|
|
+
|
|
|
blocks++;
|
|
|
}
|
|
|
|
|
|
+ ret = regmap_async_complete(regmap);
|
|
|
+ if (ret != 0)
|
|
|
+ adsp_err(dsp, "Failed to complete async write: %d\n", ret);
|
|
|
+
|
|
|
if (pos > firmware->size)
|
|
|
adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n",
|
|
|
file, blocks, pos - firmware->size);
|
|
|
|
|
|
out_fw:
|
|
|
release_firmware(firmware);
|
|
|
+ wm_adsp_buf_free(&buf_list);
|
|
|
out:
|
|
|
kfree(file);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+int wm_adsp1_init(struct wm_adsp *adsp)
|
|
|
+{
|
|
|
+ INIT_LIST_HEAD(&adsp->alg_regions);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(wm_adsp1_init);
|
|
|
+
|
|
|
int wm_adsp1_event(struct snd_soc_dapm_widget *w,
|
|
|
struct snd_kcontrol *kcontrol,
|
|
|
int event)
|
|
@@ -482,16 +884,46 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
|
|
|
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
|
|
|
struct wm_adsp *dsp = &dsps[w->shift];
|
|
|
int ret;
|
|
|
+ int val;
|
|
|
|
|
|
switch (event) {
|
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
|
|
ADSP1_SYS_ENA, ADSP1_SYS_ENA);
|
|
|
|
|
|
+ /*
|
|
|
+ * For simplicity set the DSP clock rate to be the
|
|
|
+ * SYSCLK rate rather than making it configurable.
|
|
|
+ */
|
|
|
+ if(dsp->sysclk_reg) {
|
|
|
+ ret = regmap_read(dsp->regmap, dsp->sysclk_reg, &val);
|
|
|
+ if (ret != 0) {
|
|
|
+ adsp_err(dsp, "Failed to read SYSCLK state: %d\n",
|
|
|
+ ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ val = (val & dsp->sysclk_mask)
|
|
|
+ >> dsp->sysclk_shift;
|
|
|
+
|
|
|
+ ret = regmap_update_bits(dsp->regmap,
|
|
|
+ dsp->base + ADSP1_CONTROL_31,
|
|
|
+ ADSP1_CLK_SEL_MASK, val);
|
|
|
+ if (ret != 0) {
|
|
|
+ adsp_err(dsp, "Failed to set clock rate: %d\n",
|
|
|
+ ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
ret = wm_adsp_load(dsp);
|
|
|
if (ret != 0)
|
|
|
goto err;
|
|
|
|
|
|
+ ret = wm_adsp_setup_algs(dsp);
|
|
|
+ if (ret != 0)
|
|
|
+ goto err;
|
|
|
+
|
|
|
ret = wm_adsp_load_coeff(dsp);
|
|
|
if (ret != 0)
|
|
|
goto err;
|
|
@@ -563,6 +995,7 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
|
|
|
struct wm_adsp *dsp = &dsps[w->shift];
|
|
|
+ struct wm_adsp_alg_region *alg_region;
|
|
|
unsigned int val;
|
|
|
int ret;
|
|
|
|
|
@@ -628,6 +1061,10 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
|
|
if (ret != 0)
|
|
|
goto err;
|
|
|
|
|
|
+ ret = wm_adsp_setup_algs(dsp);
|
|
|
+ if (ret != 0)
|
|
|
+ goto err;
|
|
|
+
|
|
|
ret = wm_adsp_load_coeff(dsp);
|
|
|
if (ret != 0)
|
|
|
goto err;
|
|
@@ -638,9 +1075,13 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
|
|
ADSP2_CORE_ENA | ADSP2_START);
|
|
|
if (ret != 0)
|
|
|
goto err;
|
|
|
+
|
|
|
+ dsp->running = true;
|
|
|
break;
|
|
|
|
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
|
+ dsp->running = false;
|
|
|
+
|
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
|
|
|
ADSP2_SYS_ENA | ADSP2_CORE_ENA |
|
|
|
ADSP2_START, 0);
|
|
@@ -664,6 +1105,14 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
|
|
"Failed to enable supply: %d\n",
|
|
|
ret);
|
|
|
}
|
|
|
+
|
|
|
+ while (!list_empty(&dsp->alg_regions)) {
|
|
|
+ alg_region = list_first_entry(&dsp->alg_regions,
|
|
|
+ struct wm_adsp_alg_region,
|
|
|
+ list);
|
|
|
+ list_del(&alg_region->list);
|
|
|
+ kfree(alg_region);
|
|
|
+ }
|
|
|
break;
|
|
|
|
|
|
default:
|
|
@@ -693,6 +1142,8 @@ int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+ INIT_LIST_HEAD(&adsp->alg_regions);
|
|
|
+
|
|
|
if (dvfs) {
|
|
|
adsp->dvfs = devm_regulator_get(adsp->dev, "DCVDD");
|
|
|
if (IS_ERR(adsp->dvfs)) {
|