/** * cs_dsp_mem_region_name() - Return a name string for a memory type * @type: the memory type to match * * Return: A const string identifying the memory region.
*/ constchar *cs_dsp_mem_region_name(unsignedint type)
{ switch (type) { case WMFW_ADSP1_PM: return"PM"; case WMFW_HALO_PM_PACKED: return"PM_PACKED"; case WMFW_ADSP1_DM: return"DM"; case WMFW_ADSP2_XM: return"XM"; case WMFW_HALO_XM_PACKED: return"XM_PACKED"; case WMFW_ADSP2_YM: return"YM"; case WMFW_HALO_YM_PACKED: return"YM_PACKED"; case WMFW_ADSP1_ZM: return"ZM"; default: return NULL;
}
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_mem_region_name, "FW_CS_DSP");
if (!dsp->wmfw_file_name || !dsp->booted)
ret = 0; else
ret = simple_read_from_buffer(user_buf, count, ppos,
dsp->wmfw_file_name,
strlen(dsp->wmfw_file_name));
if (!dsp->bin_file_name || !dsp->booted)
ret = 0; else
ret = simple_read_from_buffer(user_buf, count, ppos,
dsp->bin_file_name,
strlen(dsp->bin_file_name));
staticconststruct cs_dsp_region *cs_dsp_find_region(struct cs_dsp *dsp, int type)
{ int i;
for (i = 0; i < dsp->num_mems; i++) if (dsp->mem[i].type == type) return &dsp->mem[i];
return NULL;
}
staticunsignedint cs_dsp_region_to_reg(struct cs_dsp_region const *mem, unsignedint offset)
{ switch (mem->type) { case WMFW_ADSP1_PM: return mem->base + (offset * 3); case WMFW_ADSP1_DM: case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: case WMFW_ADSP1_ZM: return mem->base + (offset * 2); default:
WARN(1, "Unknown memory region type"); return offset;
}
}
staticunsignedint cs_dsp_halo_region_to_reg(struct cs_dsp_region const *mem, unsignedint offset)
{ switch (mem->type) { case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: return mem->base + (offset * 4); case WMFW_HALO_XM_PACKED: case WMFW_HALO_YM_PACKED: return (mem->base + (offset * 3)) & ~0x3; case WMFW_HALO_PM_PACKED: return mem->base + (offset * 5); default:
WARN(1, "Unknown memory region type"); return offset;
}
}
staticvoid cs_dsp_read_fw_status(struct cs_dsp *dsp, int noffs, unsignedint *offs)
{ unsignedint i; int ret;
for (i = 0; i < noffs; ++i) {
ret = regmap_read(dsp->regmap, dsp->base + offs[i], &offs[i]); if (ret) {
cs_dsp_err(dsp, "Failed to read SCRATCH%u: %d\n", i, ret); return;
}
}
}
/** * cs_dsp_coeff_write_acked_control() - Sends event_id to the acked control * @ctl: pointer to acked coefficient control * @event_id: the value to write to the given acked control * * Once the value has been written to the control the function shall block * until the running firmware acknowledges the write or timeout is exceeded. * * Must be called with pwr_lock held. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_coeff_write_acked_control(struct cs_dsp_coeff_ctl *ctl, unsignedint event_id)
{ struct cs_dsp *dsp = ctl->dsp;
__be32 val = cpu_to_be32(event_id); unsignedint reg; int i, ret;
lockdep_assert_held(&dsp->pwr_lock);
if (!dsp->running) return -EPERM;
ret = cs_dsp_coeff_base_reg(ctl, ®, 0); if (ret) return ret;
cs_dsp_dbg(dsp, "Sending 0x%x to acked control alg 0x%x %s:0x%x\n",
event_id, ctl->alg_region.alg,
cs_dsp_mem_region_name(ctl->alg_region.type), ctl->offset);
ret = regmap_raw_write(dsp->regmap, reg, &val, sizeof(val)); if (ret) {
cs_dsp_err(dsp, "Failed to write %x: %d\n", reg, ret); return ret;
}
/* * Poll for ack, we initially poll at ~1ms intervals for firmwares * that respond quickly, then go to ~10ms polls. A firmware is unlikely * to ack instantly so we do the first 1ms delay before reading the * control to avoid a pointless bus transaction
*/ for (i = 0; i < CS_DSP_ACKED_CTL_TIMEOUT_MS;) { switch (i) { case 0 ... CS_DSP_ACKED_CTL_N_QUICKPOLLS - 1:
usleep_range(1000, 2000);
i++; break; default:
usleep_range(10000, 20000);
i += 10; break;
}
ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); if (ret) {
cs_dsp_err(dsp, "Failed to read %x: %d\n", reg, ret); return ret;
}
if (val == 0) {
cs_dsp_dbg(dsp, "Acked control ACKED at poll %u\n", i); return 0;
}
}
ret = regmap_raw_write(dsp->regmap, reg, scratch,
len); if (ret) {
cs_dsp_err(dsp, "Failed to write %zu bytes to %x: %d\n",
len, reg, ret);
kfree(scratch); return ret;
}
cs_dsp_dbg(dsp, "Wrote %zu bytes to %x\n", len, reg);
kfree(scratch);
return 0;
}
/** * cs_dsp_coeff_write_ctrl() - Writes the given buffer to the given coefficient control * @ctl: pointer to coefficient control * @off: word offset at which data should be written * @buf: the buffer to write to the given control * @len: the length of the buffer in bytes * * Must be called with pwr_lock held. * * Return: < 0 on error, 1 when the control value changed and 0 when it has not.
*/ int cs_dsp_coeff_write_ctrl(struct cs_dsp_coeff_ctl *ctl, unsignedint off, constvoid *buf, size_t len)
{ int ret = 0;
if (!ctl) return -ENOENT;
lockdep_assert_held(&ctl->dsp->pwr_lock);
if (ctl->flags && !(ctl->flags & WMFW_CTL_FLAG_WRITEABLE)) return -EPERM;
if (len + off * sizeof(u32) > ctl->len) return -EINVAL;
if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) {
ret = -EPERM;
} elseif (buf != ctl->cache) { if (memcmp(ctl->cache + off * sizeof(u32), buf, len))
memcpy(ctl->cache + off * sizeof(u32), buf, len); else return 0;
}
ctl->set = 1; if (ctl->enabled && ctl->dsp->running)
ret = cs_dsp_coeff_write_ctrl_raw(ctl, off, buf, len);
/** * cs_dsp_coeff_lock_and_write_ctrl() - Writes the given buffer to the given coefficient control * @ctl: pointer to coefficient control * @off: word offset at which data should be written * @buf: the buffer to write to the given control * @len: the length of the buffer in bytes * * Same as cs_dsp_coeff_write_ctrl() but takes pwr_lock. * * Return: A negative number on error, 1 when the control value changed and 0 when it has not.
*/ int cs_dsp_coeff_lock_and_write_ctrl(struct cs_dsp_coeff_ctl *ctl, unsignedint off, constvoid *buf, size_t len)
{ struct cs_dsp *dsp = ctl->dsp; int ret;
lockdep_assert_not_held(&dsp->pwr_lock);
mutex_lock(&dsp->pwr_lock);
ret = cs_dsp_coeff_write_ctrl(ctl, off, buf, len);
mutex_unlock(&dsp->pwr_lock);
ret = cs_dsp_coeff_base_reg(ctl, ®, off); if (ret) return ret;
scratch = kmalloc(len, GFP_KERNEL | GFP_DMA); if (!scratch) return -ENOMEM;
ret = regmap_raw_read(dsp->regmap, reg, scratch, len); if (ret) {
cs_dsp_err(dsp, "Failed to read %zu bytes from %x: %d\n",
len, reg, ret);
kfree(scratch); return ret;
}
cs_dsp_dbg(dsp, "Read %zu bytes from %x\n", len, reg);
memcpy(buf, scratch, len);
kfree(scratch);
return 0;
}
/** * cs_dsp_coeff_read_ctrl() - Reads the given coefficient control into the given buffer * @ctl: pointer to coefficient control * @off: word offset at which data should be read * @buf: the buffer to store to the given control * @len: the length of the buffer in bytes * * Must be called with pwr_lock held. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_coeff_read_ctrl(struct cs_dsp_coeff_ctl *ctl, unsignedint off, void *buf, size_t len)
{ int ret = 0;
if (!ctl) return -ENOENT;
lockdep_assert_held(&ctl->dsp->pwr_lock);
if (len + off * sizeof(u32) > ctl->len) return -EINVAL;
if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) { if (ctl->enabled && ctl->dsp->running) return cs_dsp_coeff_read_ctrl_raw(ctl, off, buf, len); else return -EPERM;
} else { if (!ctl->flags && ctl->enabled && ctl->dsp->running)
ret = cs_dsp_coeff_read_ctrl_raw(ctl, 0, ctl->cache, ctl->len);
if (buf != ctl->cache)
memcpy(buf, ctl->cache + off * sizeof(u32), len);
}
/** * cs_dsp_coeff_lock_and_read_ctrl() - Reads the given coefficient control into the given buffer * @ctl: pointer to coefficient control * @off: word offset at which data should be read * @buf: the buffer to store to the given control * @len: the length of the buffer in bytes * * Same as cs_dsp_coeff_read_ctrl() but takes pwr_lock. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_coeff_lock_and_read_ctrl(struct cs_dsp_coeff_ctl *ctl, unsignedint off, void *buf, size_t len)
{ struct cs_dsp *dsp = ctl->dsp; int ret;
lockdep_assert_not_held(&dsp->pwr_lock);
mutex_lock(&dsp->pwr_lock);
ret = cs_dsp_coeff_read_ctrl(ctl, off, buf, len);
mutex_unlock(&dsp->pwr_lock);
staticint cs_dsp_coeff_init_control_caches(struct cs_dsp *dsp)
{ struct cs_dsp_coeff_ctl *ctl; int ret;
list_for_each_entry(ctl, &dsp->ctl_list, list) { if (!ctl->enabled || ctl->set) continue; if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) continue;
/* * For readable controls populate the cache from the DSP memory. * For non-readable controls the cache was zero-filled when * created so we don't need to do anything.
*/ if (!ctl->flags || (ctl->flags & WMFW_CTL_FLAG_READABLE)) {
ret = cs_dsp_coeff_read_ctrl_raw(ctl, 0, ctl->cache, ctl->len); if (ret < 0) return ret;
}
}
return 0;
}
staticint cs_dsp_coeff_sync_controls(struct cs_dsp *dsp)
{ struct cs_dsp_coeff_ctl *ctl; int ret;
list_for_each_entry(ctl, &dsp->ctl_list, list) { if (!ctl->enabled) continue; if (ctl->set && !(ctl->flags & WMFW_CTL_FLAG_VOLATILE)) {
ret = cs_dsp_coeff_write_ctrl_raw(ctl, 0, ctl->cache,
ctl->len); if (ret < 0) return ret;
}
}
if (le32_to_cpu(header->len) != pos) {
ret = -EOVERFLOW; goto out_fw;
}
cs_dsp_info(dsp, "%s: format %d timestamp %#llx\n", file, header->ver,
le64_to_cpu(footer->timestamp));
while (pos < firmware->size) { /* Is there enough data for a complete block header? */ if (sizeof(*region) > firmware->size - pos) {
ret = -EOVERFLOW; goto out_fw;
}
region = (void *)&(firmware->data[pos]);
if (le32_to_cpu(region->len) > firmware->size - pos - sizeof(*region)) {
ret = -EOVERFLOW; goto out_fw;
}
switch (type) { case WMFW_INFO_TEXT: case WMFW_NAME_TEXT:
region_name = "Info/Name";
cs_dsp_info(dsp, "%s: %.*s\n", file,
min(le32_to_cpu(region->len), 100), region->data); break; case WMFW_ALGORITHM_DATA:
region_name = "Algorithm";
ret = cs_dsp_parse_coeff(dsp, region); if (ret != 0) goto out_fw; break; case WMFW_ABSOLUTE:
region_name = "Absolute";
reg = offset; break; case WMFW_ADSP1_PM: case WMFW_ADSP1_DM: case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: case WMFW_ADSP1_ZM: case WMFW_HALO_PM_PACKED: case WMFW_HALO_XM_PACKED: case WMFW_HALO_YM_PACKED:
mem = cs_dsp_find_region(dsp, type); if (!mem) {
cs_dsp_err(dsp, "No region of type: %x\n", type);
ret = -EINVAL; goto out_fw;
}
region_name = cs_dsp_mem_region_name(type);
reg = dsp->ops->region_to_reg(mem, offset); break; default:
cs_dsp_warn(dsp, "%s.%d: Unknown region type %x at %d(%x)\n",
file, regions, type, pos, pos); break;
}
cs_dsp_dbg(dsp, "%s.%d: %d bytes at %d in %s\n", file,
regions, le32_to_cpu(region->len), offset,
region_name);
if (reg) {
buf = cs_dsp_buf_alloc(region->data,
le32_to_cpu(region->len),
&buf_list); if (!buf) {
cs_dsp_err(dsp, "Out of memory\n");
ret = -ENOMEM; goto out_fw;
}
ret = regmap_raw_write(regmap, reg, buf->buf,
le32_to_cpu(region->len)); if (ret != 0) {
cs_dsp_err(dsp, "%s.%d: Failed to write %d bytes at %d in %s: %d\n",
file, regions,
le32_to_cpu(region->len), offset,
region_name, ret); goto out_fw;
}
}
/** * cs_dsp_get_ctl() - Finds a matching coefficient control * @dsp: pointer to DSP structure * @name: pointer to string to match with a control's subname * @type: the algorithm type to match * @alg: the algorithm id to match * * Find cs_dsp_coeff_ctl with input name as its subname * * Return: pointer to the control on success, NULL if not found
*/ struct cs_dsp_coeff_ctl *cs_dsp_get_ctl(struct cs_dsp *dsp, constchar *name, int type, unsignedint alg)
{ struct cs_dsp_coeff_ctl *pos, *rslt = NULL;
/* Read the terminator first to validate the length */
reg = dsp->ops->region_to_reg(mem, pos + len);
ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); if (ret != 0) {
cs_dsp_err(dsp, "Failed to read algorithm list end: %d\n",
ret); return ERR_PTR(ret);
}
if (be32_to_cpu(val) != 0xbedead)
cs_dsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbedead\n",
reg, be32_to_cpu(val));
/* Convert length from DSP words to bytes */
len *= sizeof(u32);
alg = kzalloc(len, GFP_KERNEL | GFP_DMA); if (!alg) return ERR_PTR(-ENOMEM);
reg = dsp->ops->region_to_reg(mem, pos);
ret = regmap_raw_read(dsp->regmap, reg, alg, len); if (ret != 0) {
cs_dsp_err(dsp, "Failed to read algorithm list: %d\n", ret);
kfree(alg); return ERR_PTR(ret);
}
return alg;
}
/** * cs_dsp_find_alg_region() - Finds a matching algorithm region * @dsp: pointer to DSP structure * @type: the algorithm type to match * @id: the algorithm id to match * * Return: Pointer to matching algorithm region, or NULL if not found.
*/ struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp, int type, unsignedint id)
{ struct cs_dsp_alg_region_list_item *item;
lockdep_assert_held(&dsp->pwr_lock);
list_for_each_entry(item, &dsp->alg_regions, list) { if (id == item->alg_region.alg && type == item->alg_region.type) return &item->alg_region;
}
for (i = 0; i < n_algs; i++) {
cs_dsp_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));
alg_region = cs_dsp_create_region(dsp, WMFW_ADSP1_DM,
adsp1_alg[i].alg.id,
adsp1_alg[i].alg.ver,
adsp1_alg[i].dm); if (IS_ERR(alg_region)) {
ret = PTR_ERR(alg_region); goto out;
} if (dsp->wmfw_ver == 0) { if (i + 1 < n_algs) {
len = be32_to_cpu(adsp1_alg[i + 1].dm);
len -= be32_to_cpu(adsp1_alg[i].dm);
len *= 4;
cs_dsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0,
WMFW_CTL_TYPE_BYTES);
} else {
cs_dsp_warn(dsp, "Missing length info for region DM with ID %x\n",
be32_to_cpu(adsp1_alg[i].alg.id));
}
}
alg_region = cs_dsp_create_region(dsp, WMFW_ADSP1_ZM,
adsp1_alg[i].alg.id,
adsp1_alg[i].alg.ver,
adsp1_alg[i].zm); if (IS_ERR(alg_region)) {
ret = PTR_ERR(alg_region); goto out;
} if (dsp->wmfw_ver == 0) { if (i + 1 < n_algs) {
len = be32_to_cpu(adsp1_alg[i + 1].zm);
len -= be32_to_cpu(adsp1_alg[i].zm);
len *= 4;
cs_dsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0,
WMFW_CTL_TYPE_BYTES);
} else {
cs_dsp_warn(dsp, "Missing length info for region ZM with ID %x\n",
be32_to_cpu(adsp1_alg[i].alg.id));
}
}
}
for (i = 0; i < n_algs; i++) {
cs_dsp_dbg(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));
alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_XM,
adsp2_alg[i].alg.id,
adsp2_alg[i].alg.ver,
adsp2_alg[i].xm); if (IS_ERR(alg_region)) {
ret = PTR_ERR(alg_region); goto out;
} if (dsp->wmfw_ver == 0) { if (i + 1 < n_algs) {
len = be32_to_cpu(adsp2_alg[i + 1].xm);
len -= be32_to_cpu(adsp2_alg[i].xm);
len *= 4;
cs_dsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0,
WMFW_CTL_TYPE_BYTES);
} else {
cs_dsp_warn(dsp, "Missing length info for region XM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
}
}
alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_YM,
adsp2_alg[i].alg.id,
adsp2_alg[i].alg.ver,
adsp2_alg[i].ym); if (IS_ERR(alg_region)) {
ret = PTR_ERR(alg_region); goto out;
} if (dsp->wmfw_ver == 0) { if (i + 1 < n_algs) {
len = be32_to_cpu(adsp2_alg[i + 1].ym);
len -= be32_to_cpu(adsp2_alg[i].ym);
len *= 4;
cs_dsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0,
WMFW_CTL_TYPE_BYTES);
} else {
cs_dsp_warn(dsp, "Missing length info for region YM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
}
}
alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_ZM,
adsp2_alg[i].alg.id,
adsp2_alg[i].alg.ver,
adsp2_alg[i].zm); if (IS_ERR(alg_region)) {
ret = PTR_ERR(alg_region); goto out;
} if (dsp->wmfw_ver == 0) { if (i + 1 < n_algs) {
len = be32_to_cpu(adsp2_alg[i + 1].zm);
len -= be32_to_cpu(adsp2_alg[i].zm);
len *= 4;
cs_dsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0,
WMFW_CTL_TYPE_BYTES);
} else {
cs_dsp_warn(dsp, "Missing length info for region ZM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
}
}
}
blocks = 0; while (pos < firmware->size) { /* Is there enough data for a complete block header? */ if (sizeof(*blk) > firmware->size - pos) {
ret = -EOVERFLOW; goto out_fw;
}
blk = (void *)(&firmware->data[pos]);
if (le32_to_cpu(blk->len) > firmware->size - pos - sizeof(*blk)) {
ret = -EOVERFLOW; goto out_fw;
}
type = le16_to_cpu(blk->type);
offset = le16_to_cpu(blk->offset);
version = le32_to_cpu(blk->ver) >> 8;
case WMFW_ADSP1_DM: case WMFW_ADSP1_ZM: case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: case WMFW_HALO_XM_PACKED: case WMFW_HALO_YM_PACKED: case WMFW_HALO_PM_PACKED:
cs_dsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n",
file, blocks, le32_to_cpu(blk->len),
type, le32_to_cpu(blk->id));
region_name = cs_dsp_mem_region_name(type);
mem = cs_dsp_find_region(dsp, type); if (!mem) {
cs_dsp_err(dsp, "No base for region %x\n", type); break;
}
alg_region = cs_dsp_find_alg_region(dsp, type,
le32_to_cpu(blk->id)); if (alg_region) { if (version != alg_region->ver)
cs_dsp_warn(dsp, "Algorithm coefficient version %d.%d.%d but expected %d.%d.%d\n",
(version >> 16) & 0xFF,
(version >> 8) & 0xFF,
version & 0xFF,
(alg_region->ver >> 16) & 0xFF,
(alg_region->ver >> 8) & 0xFF,
alg_region->ver & 0xFF);
#ifdef CONFIG_DEBUG_FS /* Ensure this is invalid if client never provides a debugfs root */
dsp->debugfs_root = ERR_PTR(-ENODEV); #endif
return 0;
}
/** * cs_dsp_adsp1_init() - Initialise a cs_dsp structure representing a ADSP1 device * @dsp: pointer to DSP structure * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_adsp1_init(struct cs_dsp *dsp)
{
dsp->ops = &cs_dsp_adsp1_ops;
/** * cs_dsp_adsp1_power_up() - Load and start the named firmware * @dsp: pointer to DSP structure * @wmfw_firmware: the firmware to be sent * @wmfw_filename: file name of firmware to be sent * @coeff_firmware: the coefficient data to be sent * @coeff_filename: file name of coefficient to data be sent * @fw_name: the user-friendly firmware name * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_adsp1_power_up(struct cs_dsp *dsp, conststruct firmware *wmfw_firmware, constchar *wmfw_filename, conststruct firmware *coeff_firmware, constchar *coeff_filename, constchar *fw_name)
{ unsignedint val; int ret;
/* * 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) {
cs_dsp_err(dsp, "Failed to read SYSCLK state: %d\n", ret); goto err_mutex;
}
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) {
cs_dsp_err(dsp, "Failed to set clock rate: %d\n", ret); goto err_mutex;
}
}
ret = cs_dsp_load(dsp, wmfw_firmware, wmfw_filename); if (ret != 0) goto err_ena;
ret = cs_dsp_adsp1_setup_algs(dsp); if (ret != 0) goto err_ena;
ret = cs_dsp_load_coeff(dsp, coeff_firmware, coeff_filename); if (ret != 0) goto err_ena;
/* Initialize caches for enabled and unset controls */
ret = cs_dsp_coeff_init_control_caches(dsp); if (ret != 0) goto err_ena;
/* Sync set controls */
ret = cs_dsp_coeff_sync_controls(dsp); if (ret != 0) goto err_ena;
staticint cs_dsp_adsp2v2_enable_core(struct cs_dsp *dsp)
{ unsignedint val; int ret, count;
/* Wait for the RAM to start, should be near instantaneous */ for (count = 0; count < 10; ++count) {
ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, &val); if (ret != 0) return ret;
if (val & ADSP2_RAM_RDY) break;
usleep_range(250, 500);
}
if (!(val & ADSP2_RAM_RDY)) {
cs_dsp_err(dsp, "Failed to start DSP RAM\n"); return -EBUSY;
}
cs_dsp_dbg(dsp, "RAM ready after %d polls\n", count);
return 0;
}
staticint cs_dsp_adsp2_enable_core(struct cs_dsp *dsp)
{ int ret;
ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
ADSP2_SYS_ENA, ADSP2_SYS_ENA); if (ret != 0) return ret;
/** * cs_dsp_set_dspclk() - Applies the given frequency to the given cs_dsp * @dsp: pointer to DSP structure * @freq: clock rate to set * * This is only for use on ADSP2 cores. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_set_dspclk(struct cs_dsp *dsp, unsignedint freq)
{ int ret;
ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CLOCKING,
ADSP2_CLK_SEL_MASK,
freq << ADSP2_CLK_SEL_SHIFT); if (ret)
cs_dsp_err(dsp, "Failed to set clock rate: %d\n", ret);
/** * cs_dsp_power_up() - Downloads firmware to the DSP * @dsp: pointer to DSP structure * @wmfw_firmware: the firmware to be sent * @wmfw_filename: file name of firmware to be sent * @coeff_firmware: the coefficient data to be sent * @coeff_filename: file name of coefficient to data be sent * @fw_name: the user-friendly firmware name * * This function is used on ADSP2 and Halo DSP cores, it powers-up the DSP core * and downloads the firmware but does not start the firmware running. The * cs_dsp booted flag will be set once completed and if the core has a low-power * memory retention mode it will be put into this state after the firmware is * downloaded. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_power_up(struct cs_dsp *dsp, conststruct firmware *wmfw_firmware, constchar *wmfw_filename, conststruct firmware *coeff_firmware, constchar *coeff_filename, constchar *fw_name)
{ int ret;
mutex_lock(&dsp->pwr_lock);
dsp->fw_name = fw_name;
if (dsp->ops->enable_memory) {
ret = dsp->ops->enable_memory(dsp); if (ret != 0) goto err_mutex;
}
if (dsp->ops->enable_core) {
ret = dsp->ops->enable_core(dsp); if (ret != 0) goto err_mem;
}
ret = cs_dsp_load(dsp, wmfw_firmware, wmfw_filename); if (ret != 0) goto err_ena;
ret = dsp->ops->setup_algs(dsp); if (ret != 0) goto err_ena;
ret = cs_dsp_load_coeff(dsp, coeff_firmware, coeff_filename); if (ret != 0) goto err_ena;
/* Initialize caches for enabled and unset controls */
ret = cs_dsp_coeff_init_control_caches(dsp); if (ret != 0) goto err_ena;
if (dsp->ops->disable_core)
dsp->ops->disable_core(dsp);
dsp->booted = true;
mutex_unlock(&dsp->pwr_lock);
return 0;
err_ena: if (dsp->ops->disable_core)
dsp->ops->disable_core(dsp);
err_mem: if (dsp->ops->disable_memory)
dsp->ops->disable_memory(dsp);
err_mutex:
mutex_unlock(&dsp->pwr_lock);
/** * cs_dsp_power_down() - Powers-down the DSP * @dsp: pointer to DSP structure * * cs_dsp_stop() must have been called before this function. The core will be * fully powered down and so the memory will not be retained.
*/ void cs_dsp_power_down(struct cs_dsp *dsp)
{ struct cs_dsp_coeff_ctl *ctl;
mutex_lock(&dsp->pwr_lock);
cs_dsp_debugfs_clear(dsp);
dsp->fw_id = 0;
dsp->fw_id_version = 0;
dsp->booted = false;
if (dsp->ops->disable_memory)
dsp->ops->disable_memory(dsp);
/** * cs_dsp_run() - Starts the firmware running * @dsp: pointer to DSP structure * * cs_dsp_power_up() must have previously been called successfully. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_run(struct cs_dsp *dsp)
{ int ret;
mutex_lock(&dsp->pwr_lock);
if (!dsp->booted) {
ret = -EIO; goto err;
}
if (dsp->ops->enable_core) {
ret = dsp->ops->enable_core(dsp); if (ret != 0) goto err;
}
if (dsp->client_ops->pre_run) {
ret = dsp->client_ops->pre_run(dsp); if (ret) goto err;
}
/* Sync set controls */
ret = cs_dsp_coeff_sync_controls(dsp); if (ret != 0) goto err;
if (dsp->ops->lock_memory) {
ret = dsp->ops->lock_memory(dsp, dsp->lock_regions); if (ret != 0) {
cs_dsp_err(dsp, "Error configuring MPU: %d\n", ret); goto err;
}
}
if (dsp->ops->start_core) {
ret = dsp->ops->start_core(dsp); if (ret != 0) goto err;
}
dsp->running = true;
if (dsp->client_ops->post_run) {
ret = dsp->client_ops->post_run(dsp); if (ret) goto err;
}
mutex_unlock(&dsp->pwr_lock);
return 0;
err: if (dsp->ops->stop_core)
dsp->ops->stop_core(dsp); if (dsp->ops->disable_core)
dsp->ops->disable_core(dsp);
mutex_unlock(&dsp->pwr_lock);
/** * cs_dsp_stop() - Stops the firmware * @dsp: pointer to DSP structure * * Memory will not be disabled so firmware will remain loaded.
*/ void cs_dsp_stop(struct cs_dsp *dsp)
{ /* Tell the firmware to cleanup */
cs_dsp_signal_event_controls(dsp, CS_DSP_FW_EVENT_SHUTDOWN);
if (dsp->ops->stop_watchdog)
dsp->ops->stop_watchdog(dsp);
/* Log firmware state, it can be useful for analysis */ if (dsp->ops->show_fw_status)
dsp->ops->show_fw_status(dsp);
mutex_lock(&dsp->pwr_lock);
if (dsp->client_ops->pre_stop)
dsp->client_ops->pre_stop(dsp);
dsp->running = false;
if (dsp->ops->stop_core)
dsp->ops->stop_core(dsp); if (dsp->ops->disable_core)
dsp->ops->disable_core(dsp);
if (dsp->client_ops->post_stop)
dsp->client_ops->post_stop(dsp);
/** * cs_dsp_adsp2_init() - Initialise a cs_dsp structure representing a ADSP2 core * @dsp: pointer to DSP structure * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_adsp2_init(struct cs_dsp *dsp)
{ int ret;
switch (dsp->rev) { case 0: /* * Disable the DSP memory by default when in reset for a small * power saving.
*/
ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
ADSP2_MEM_ENA, 0); if (ret) {
cs_dsp_err(dsp, "Failed to clear memory retention: %d\n", ret); return ret;
}
/** * cs_dsp_halo_init() - Initialise a cs_dsp structure representing a HALO Core DSP * @dsp: pointer to DSP structure * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_halo_init(struct cs_dsp *dsp)
{ if (dsp->no_core_startstop)
dsp->ops = &cs_dsp_halo_ao_ops; else
dsp->ops = &cs_dsp_halo_ops;
/** * cs_dsp_read_raw_data_block() - Reads a block of data from DSP memory * @dsp: pointer to DSP structure * @mem_type: the type of DSP memory containing the data to be read * @mem_addr: the address of the data within the memory region * @num_words: the length of the data to read * @data: a buffer to store the fetched data * * If this is used to read unpacked 24-bit memory, each 24-bit DSP word will * occupy 32-bits in data (MSbyte will be 0). This padding can be removed using * cs_dsp_remove_padding() * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_read_raw_data_block(struct cs_dsp *dsp, int mem_type, unsignedint mem_addr, unsignedint num_words, __be32 *data)
{ struct cs_dsp_region const *mem = cs_dsp_find_region(dsp, mem_type); unsignedint reg; int ret;
lockdep_assert_held(&dsp->pwr_lock);
if (!mem) return -EINVAL;
reg = dsp->ops->region_to_reg(mem, mem_addr);
ret = regmap_raw_read(dsp->regmap, reg, data, sizeof(*data) * num_words); if (ret < 0) return ret;
/** * cs_dsp_read_data_word() - Reads a word from DSP memory * @dsp: pointer to DSP structure * @mem_type: the type of DSP memory containing the data to be read * @mem_addr: the address of the data within the memory region * @data: a buffer to store the fetched data * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_read_data_word(struct cs_dsp *dsp, int mem_type, unsignedint mem_addr, u32 *data)
{
__be32 raw; int ret;
ret = cs_dsp_read_raw_data_block(dsp, mem_type, mem_addr, 1, &raw); if (ret < 0) return ret;
/** * cs_dsp_write_data_word() - Writes a word to DSP memory * @dsp: pointer to DSP structure * @mem_type: the type of DSP memory containing the data to be written * @mem_addr: the address of the data within the memory region * @data: the data to be written * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_write_data_word(struct cs_dsp *dsp, int mem_type, unsignedint mem_addr, u32 data)
{ struct cs_dsp_region const *mem = cs_dsp_find_region(dsp, mem_type);
__be32 val = cpu_to_be32(data & 0x00ffffffu); unsignedint reg;
/** * cs_dsp_remove_padding() - Convert unpacked words to packed bytes * @buf: buffer containing DSP words read from DSP memory * @nwords: number of words to convert * * DSP words from the register map have pad bytes and the data bytes * are in swapped order. This swaps to the native endian order and * strips the pad bytes.
*/ void cs_dsp_remove_padding(u32 *buf, int nwords)
{ const __be32 *pack_in = (__be32 *)buf;
u8 *pack_out = (u8 *)buf; int i;
for (i = 0; i < nwords; i++) {
u32 word = be32_to_cpu(*pack_in++);
*pack_out++ = (u8)word;
*pack_out++ = (u8)(word >> 8);
*pack_out++ = (u8)(word >> 16);
}
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_remove_padding, "FW_CS_DSP");
/** * cs_dsp_adsp2_bus_error() - Handle a DSP bus error interrupt * @dsp: pointer to DSP structure * * The firmware and DSP state will be logged for future analysis.
*/ void cs_dsp_adsp2_bus_error(struct cs_dsp *dsp)
{ unsignedint val; struct regmap *regmap = dsp->regmap; int ret = 0;
mutex_lock(&dsp->pwr_lock);
ret = regmap_read(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, &val); if (ret) {
cs_dsp_err(dsp, "Failed to read Region Lock Ctrl register: %d\n", ret); goto error;
}
if (val & ADSP2_WDT_TIMEOUT_STS_MASK) {
cs_dsp_err(dsp, "watchdog timeout error\n");
dsp->ops->stop_watchdog(dsp); if (dsp->client_ops->watchdog_expired)
dsp->client_ops->watchdog_expired(dsp);
}
if (val & (ADSP2_ADDR_ERR_MASK | ADSP2_REGION_LOCK_ERR_MASK)) { if (val & ADSP2_ADDR_ERR_MASK)
cs_dsp_err(dsp, "bus error: address error\n"); else
cs_dsp_err(dsp, "bus error: region lock error\n");
ret = regmap_read(regmap, dsp->base + ADSP2_BUS_ERR_ADDR, &val); if (ret) {
cs_dsp_err(dsp, "Failed to read Bus Err Addr register: %d\n",
ret); goto error;
}
cs_dsp_err(dsp, "bus error address = 0x%x\n",
val & ADSP2_BUS_ERR_ADDR_MASK);
ret = regmap_read(regmap,
dsp->base + ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR,
&val); if (ret) {
cs_dsp_err(dsp, "Failed to read Pmem Xmem Err Addr register: %d\n",
ret); goto error;
}
/** * cs_dsp_chunk_write() - Format data to a DSP memory chunk * @ch: Pointer to the chunk structure * @nbits: Number of bits to write * @val: Value to write * * This function sequentially writes values into the format required for DSP * memory, it handles both inserting of the padding bytes and converting to * big endian. Note that data is only committed to the chunk when a whole DSP * words worth of data is available. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_chunk_write(struct cs_dsp_chunk *ch, int nbits, u32 val)
{ int nwrite, i;
/** * cs_dsp_chunk_flush() - Pad remaining data with zero and commit to chunk * @ch: Pointer to the chunk structure * * As cs_dsp_chunk_write only writes data when a whole DSP word is ready to * be written out it is possible that some data will remain in the cache, this * function will pad that data with zeros upto a whole DSP word and write out. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_chunk_flush(struct cs_dsp_chunk *ch)
{ if (!ch->cachebits) return 0;
/** * cs_dsp_chunk_read() - Parse data from a DSP memory chunk * @ch: Pointer to the chunk structure * @nbits: Number of bits to read * * This function sequentially reads values from a DSP memory formatted buffer, * it handles both removing of the padding bytes and converting from big endian. * * Return: A negative number is returned on error, otherwise the read value.
*/ int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits)
{ int nread, i;
u32 result;
if (!ch->cachebits) { if (cs_dsp_chunk_end(ch)) return -ENOSPC;
switch (op->operation) { case CS_DSP_WSEQ_END:
op->data = WSEQ_END_OF_SCRIPT; break; case CS_DSP_WSEQ_UNLOCK:
op->data = cs_dsp_chunk_read(&chunk, 16); break; case CS_DSP_WSEQ_ADDR8:
op->address = cs_dsp_chunk_read(&chunk, 8);
op->data = cs_dsp_chunk_read(&chunk, 32); break; case CS_DSP_WSEQ_H16: case CS_DSP_WSEQ_L16:
op->address = cs_dsp_chunk_read(&chunk, 24);
op->data = cs_dsp_chunk_read(&chunk, 16); break; case CS_DSP_WSEQ_FULL:
op->address = cs_dsp_chunk_read(&chunk, 32);
op->data = cs_dsp_chunk_read(&chunk, 32); break; default:
ret = -EINVAL;
cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation);
devm_kfree(dsp->dev, op); goto err_free;
}
list_add_tail(&op->list, &wseq->ops);
if (op->operation == CS_DSP_WSEQ_END) break;
}
if (op && op->operation != CS_DSP_WSEQ_END) {
cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname);
ret = -ENOENT;
}
err_free:
kfree(words);
return ret;
}
/** * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware * @dsp: Pointer to DSP structure * @wseqs: List of write sequences to initialize * @num_wseqs: Number of write sequences to initialize * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsignedint num_wseqs)
{ int i, ret;
lockdep_assert_held(&dsp->pwr_lock);
for (i = 0; i < num_wseqs; i++) {
ret = cs_dsp_populate_wseq(dsp, &wseqs[i]); if (ret) {
cs_dsp_wseq_clear(dsp, &wseqs[i]); return ret;
}
}
/** * cs_dsp_wseq_write() - Add or update an entry in a write sequence * @dsp: Pointer to a DSP structure * @wseq: Write sequence to write to * @addr: Address of the register to be written to * @data: Data to be written * @op_code: The type of operation of the new entry * @update: If true, searches for the first entry in the write sequence with * the same address and op_code, and replaces it. If false, creates a new entry * at the tail * * This function formats register address and value pairs into the format * required for write sequence entries, and either updates or adds the * new entry into the write sequence. * * If update is set to true and no matching entry is found, it will add a new entry. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
u32 addr, u32 data, u8 op_code, bool update)
{ struct cs_dsp_wseq_op *op_end, *op_new = NULL;
u32 words[WSEQ_OP_MAX_WORDS]; struct cs_dsp_chunk chunk; int new_op_size, ret;
if (update)
op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops);
/* If entry to update is not found, treat it as a new operation */ if (!op_new) {
op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops); if (!op_end) {
cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname); return -EINVAL;
}
op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL); if (!op_new) return -ENOMEM;
switch (op_code) { case CS_DSP_WSEQ_FULL:
cs_dsp_chunk_write(&chunk, 32, op_new->address);
cs_dsp_chunk_write(&chunk, 32, op_new->data); break; case CS_DSP_WSEQ_L16: case CS_DSP_WSEQ_H16:
cs_dsp_chunk_write(&chunk, 24, op_new->address);
cs_dsp_chunk_write(&chunk, 16, op_new->data); break; default:
ret = -EINVAL;
cs_dsp_err(dsp, "Operation %X not supported\n", op_code); goto op_new_free;
}
new_op_size = cs_dsp_chunk_bytes(&chunk);
if (!update) { if (wseq->ctl->len - op_end->offset < new_op_size) {
cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname);
ret = -E2BIG; goto op_new_free;
}
op_end->offset += new_op_size;
ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32),
&op_end->data, sizeof(u32)); if (ret) goto op_new_free;
list_add_tail(&op_new->list, &op_end->list);
}
ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32),
words, new_op_size); if (ret) goto op_new_free;
/** * cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence * @dsp: Pointer to a DSP structure * @wseq: Write sequence to write to * @reg_seq: List of address-data pairs * @num_regs: Number of address-data pairs * @op_code: The types of operations of the new entries * @update: If true, searches for the first entry in the write sequence with * the same address and op_code, and replaces it. If false, creates a new entry * at the tail * * This function calls cs_dsp_wseq_write() for multiple address-data pairs. * * Return: Zero for success, a negative number on error.
*/ int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, conststruct reg_sequence *reg_seq, int num_regs,
u8 op_code, bool update)
{ int i, ret;
for (i = 0; i < num_regs; i++) {
ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg,
reg_seq[i].def, op_code, update); if (ret) return ret;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.