+ *off += 4;
+ return;
+ }
+
+ writel(val, drvdata->ram_base + *off);
+
+ *off += 4;
+}
+
+static int dcc_sw_trigger(struct dcc_drvdata *drvdata)
+{
+ void __iomem *addr;
+ int i;
+ u32 status;
+ u32 ll_cfg;
+ u32 tmp_ll_cfg;
+ u32 val;
+ int ret = 0;
+
+ mutex_lock(&drvdata->mutex);
+
+ for (i = 0; i < drvdata->nr_link_list; i++) {
+ if (!test_bit(i, drvdata->enable_bitmap))
+ continue;
+ ll_cfg = dcc_list_readl(drvdata, i, DCC_LL_CFG);
+ tmp_ll_cfg = ll_cfg & ~DCC_TRIGGER_MASK;
+ dcc_list_writel(drvdata, tmp_ll_cfg, i, DCC_LL_CFG);
+ dcc_list_writel(drvdata, 1, i, DCC_LL_SW_TRIGGER);
+ dcc_list_writel(drvdata, ll_cfg, i, DCC_LL_CFG);
+ }
+
+ addr = drvdata->base + dcc_status(drvdata->mem_map_ver);
+ if (readl_poll_timeout(addr, val, !FIELD_GET(DCC_STATUS_MASK, val),
+ 1, STATUS_READY_TIMEOUT)) {
+ dev_err(drvdata->dev, "DCC is busy after receiving sw trigger\n");
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ for (i = 0; i < drvdata->nr_link_list; i++) {
+ if (!test_bit(i, drvdata->enable_bitmap))
+ continue;
+
+ status = dcc_list_readl(drvdata, i, DCC_LL_BUS_ACCESS_STATUS);
+ if (!status)
+ continue;
+
+ dev_err(drvdata->dev, "Read access error for list %d err: 0x%x\n",
+ i, status);
+ ll_cfg = dcc_list_readl(drvdata, i, DCC_LL_CFG);
+ tmp_ll_cfg = ll_cfg & ~DCC_TRIGGER_MASK;
+ dcc_list_writel(drvdata, tmp_ll_cfg, i, DCC_LL_CFG);
+ dcc_list_writel(drvdata, DCC_STATUS_MASK, i, DCC_LL_BUS_ACCESS_STATUS);
+ dcc_list_writel(drvdata, ll_cfg, i, DCC_LL_CFG);
+ ret = -ENODATA;
+ break;
+ }
+
+out_unlock:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+
+static void dcc_ll_cfg_reset_link(struct dcc_cfg_attr *cfg)
+{
+ cfg->addr = 0x00;
+ cfg->link = 0;
+ cfg->prev_off = 0;
+ cfg->prev_addr = cfg->addr;
+}
+
+static void dcc_emit_read_write(struct dcc_drvdata *drvdata,
+ struct dcc_config_entry *entry,
+ struct dcc_cfg_attr *cfg)
+{
+ if (cfg->link) {
+ /*
+ * write new offset = 1 to continue
+ * processing the list
+ */
+
+ dcc_sram_write_auto(drvdata, cfg->link, &cfg->sram_offset);
+
+ /* Reset link and prev_off */
+ dcc_ll_cfg_reset_link(cfg);
+ }
+
+ cfg->addr = DCC_RD_MOD_WR_DESCRIPTOR;
+ dcc_sram_write_auto(drvdata, cfg->addr, &cfg->sram_offset);
+
+ dcc_sram_write_auto(drvdata, entry->mask, &cfg->sram_offset);
+
+ dcc_sram_write_auto(drvdata, entry->write_val, &cfg->sram_offset);
+
+ cfg->addr = 0;
+}
+
+static void dcc_emit_loop(struct dcc_drvdata *drvdata, struct dcc_config_entry *entry,
+ struct dcc_cfg_attr *cfg,
+ struct dcc_cfg_loop_attr *cfg_loop,
+ u32 *total_len)
+{
+ int loop;
+
+ /* Check if we need to write link of prev entry */
+ if (cfg->link)
+ dcc_sram_write_auto(drvdata, cfg->link, &cfg->sram_offset);
+
+ if (cfg_loop->loop_start) {
+ loop = (cfg->sram_offset - cfg_loop->loop_off) / 4;
+ loop |= (cfg_loop->loop_cnt << drvdata->loop_shift) &
+ GENMASK(DCC_ADDR_LIMIT, drvdata->loop_shift);
+ loop |= DCC_LOOP_DESCRIPTOR;
+ *total_len += (*total_len - cfg_loop->loop_len) * cfg_loop->loop_cnt;
+
+ dcc_sram_write_auto(drvdata, loop, &cfg->sram_offset);
+
+ cfg_loop->loop_start = false;
+ cfg_loop->loop_len = 0;
+ cfg_loop->loop_off = 0;
+ } else {
+ cfg_loop->loop_start = true;
+ cfg_loop->loop_cnt = entry->loop_cnt - 1;
+ cfg_loop->loop_len = *total_len;
+ cfg_loop->loop_off = cfg->sram_offset;
+ }
+
+ /* Reset link and prev_off */
+ dcc_ll_cfg_reset_link(cfg);
+}
+
+static void dcc_emit_write(struct dcc_drvdata *drvdata,
+ struct dcc_config_entry *entry,
+ struct dcc_cfg_attr *cfg)
+{
+ u32 off;
+
+ if (cfg->link) {
+ /*
+ * write new offset = 1 to continue
+ * processing the list
+ */
+ dcc_sram_write_auto(drvdata, cfg->link, &cfg->sram_offset);
+
+ /* Reset link and prev_off */
+ cfg->addr = 0x00;
+ cfg->prev_off = 0;
+ cfg->prev_addr = cfg->addr;
+ }
+
+ off = entry->offset / 4;
+ /* write new offset-length pair to correct position */
+ cfg->link |= ((off & DCC_WRITE_OFF_MASK) | DCC_WRITE_MASK |
+ FIELD_PREP(DCC_WRITE_LEN_MASK, entry->len));
+ cfg->link |= DCC_LINK_DESCRIPTOR;
+
+ /* Address type */
+ cfg->addr = (entry->base >> 4) & GENMASK(DCC_ADDR_LIMIT, 0);
+ if (entry->apb_bus)
+ cfg->addr |= DCC_ADDR_DESCRIPTOR | DCC_WRITE_IND | DCC_APB_IND;
+ else
+ cfg->addr |= DCC_ADDR_DESCRIPTOR | DCC_WRITE_IND | DCC_AHB_IND;
+ dcc_sram_write_auto(drvdata, cfg->addr, &cfg->sram_offset);
+
+ dcc_sram_write_auto(drvdata, cfg->link, &cfg->sram_offset);
+
+ dcc_sram_write_auto(drvdata, entry->write_val, &cfg->sram_offset);
+
+ cfg->addr = 0x00;
+ cfg->link = 0;
+}
+
+static int dcc_emit_read(struct dcc_drvdata *drvdata,
+ struct dcc_config_entry *entry,
+ struct dcc_cfg_attr *cfg,
+ u32 *pos, u32 *total_len)
+{
+ u32 off;
+ u32 temp_off;
+
+ cfg->addr = (entry->base >> 4) & GENMASK(27, 0);
+
+ if (entry->apb_bus)
+ cfg->addr |= DCC_ADDR_DESCRIPTOR | DCC_READ_IND | DCC_APB_IND;
+ else
+ cfg->addr |= DCC_ADDR_DESCRIPTOR | DCC_READ_IND | DCC_AHB_IND;
+
+ off = entry->offset / 4;
+
+ *total_len += entry->len * 4;
+
+ if (!cfg->prev_addr || cfg->prev_addr != cfg->addr || cfg->prev_off > off) {
+ /* Check if we need to write prev link entry */
+ if (cfg->link)
+ dcc_sram_write_auto(drvdata, cfg->link, &cfg->sram_offset);
+ dev_dbg(drvdata->dev, "DCC: sram address 0x%x\n", cfg->sram_offset);
+
+ /* Write address */
+ dcc_sram_write_auto(drvdata, cfg->addr, &cfg->sram_offset);
+
+ /* Reset link and prev_off */
+ cfg->link = 0;
+ cfg->prev_off = 0;
+ }
+
+ if ((off - cfg->prev_off) > 0xff || entry->len > MAX_DCC_LEN) {
+ dev_err(drvdata->dev, "DCC: Programming error Base: 0x%x, offset 0x%x\n",
+ entry->base, entry->offset);
+ return -EINVAL;
+ }
+
+ if (cfg->link) {
+ /*
+ * link already has one offset-length so new
+ * offset-length needs to be placed at
+ * bits [29:15]
+ */
+ *pos = 15;
+
+ /* Clear bits [31:16] */
+ cfg->link &= GENMASK(14, 0);
+ } else {
+ /*
+ * link is empty, so new offset-length needs
+ * to be placed at bits [15:0]
+ */
+ *pos = 0;
+ cfg->link = 1 << 15;
+ }
+
+ /* write new offset-length pair to correct position */
+ temp_off = (off - cfg->prev_off) & GENMASK(7, 0);
+ cfg->link |= temp_off | ((entry->len << 8) & GENMASK(14, 8)) << *pos;
+
+ cfg->link |= DCC_LINK_DESCRIPTOR;
+
+ if (*pos) {
+ dcc_sram_write_auto(drvdata, cfg->link, &cfg->sram_offset);
+ cfg->link = 0;
+ }
+
+ cfg->prev_off = off + entry->len - 1;
+ cfg->prev_addr = cfg->addr;
+ return 0;
+}
+
+static int dcc_emit_config(struct dcc_drvdata *drvdata, unsigned int curr_list)
+{
+ int ret;
+ u32 total_len, pos;
+ struct dcc_config_entry *entry;
+ struct dcc_cfg_attr cfg = {0};
+ struct dcc_cfg_loop_attr cfg_loop = {0};
+
+ cfg.sram_offset = drvdata->ram_cfg * 4;
+ total_len = 0;
+
+ list_for_each_entry(entry, &drvdata->cfg_head[curr_list], list) {
+ switch (entry->desc_type) {
+ case DCC_READ_WRITE_TYPE:
+ dcc_emit_read_write(drvdata, entry, &cfg);
+ break;
+
+ case DCC_LOOP_TYPE:
+ dcc_emit_loop(drvdata, entry, &cfg, &cfg_loop, &total_len);
+ break;
+
+ case DCC_WRITE_TYPE:
+ dcc_emit_write(drvdata, entry, &cfg);
+ break;
+
+ case DCC_READ_TYPE:
+ ret = dcc_emit_read(drvdata, entry, &cfg, &pos, &total_len);
+ if (ret)
+ goto err;
+ break;
+ }
+ }
+
+ if (cfg.link)
+ dcc_sram_write_auto(drvdata, cfg.link, &cfg.sram_offset);
+
+ if (cfg_loop.loop_start) {
+ dev_err(drvdata->dev, "DCC: Programming error: Loop unterminated\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* Handling special case of list ending with a rd_mod_wr */
+ if (cfg.addr == DCC_RD_MOD_WR_DESCRIPTOR) {
+ cfg.addr = (DCC_RD_MOD_WR_ADDR) & GENMASK(27, 0);
+ cfg.addr |= DCC_ADDR_DESCRIPTOR;
+ dcc_sram_write_auto(drvdata, cfg.addr, &cfg.sram_offset);
+ }
+
+ /* Setting zero to indicate end of the list */
+ cfg.link = DCC_LINK_DESCRIPTOR;
+ dcc_sram_write_auto(drvdata, cfg.link, &cfg.sram_offset);
+
+ /* Check if sram offset exceeds the ram size */
+ if (cfg.sram_offset > drvdata->ram_size)
+ goto overstep;
+
+ /* Update ram_cfg and check if the data will overstep */
+ drvdata->ram_cfg = (cfg.sram_offset + total_len) / 4;
+
+ if (cfg.sram_offset + total_len > drvdata->ram_size) {
+ cfg.sram_offset += total_len;
+ goto overstep;
+ }
+
+ drvdata->ram_start = cfg.sram_offset / 4;
+ return 0;
+overstep:
+ ret = -EINVAL;
+ memset_io(drvdata->ram_base, 0, drvdata->ram_size);
+
+err:
+ return ret;
+}
+
+static bool dcc_valid_list(struct dcc_drvdata *drvdata, unsigned int curr_list)
+{
+ u32 lock_reg;
+
+ if (list_empty(&drvdata->cfg_head[curr_list]))
+ return false;
+
+ if (test_bit(curr_list, drvdata->enable_bitmap)) {
+ dev_err(drvdata->dev, "List %d is already enabled\n", curr_list);
+ return false;
+ }
+
+ lock_reg = dcc_list_readl(drvdata, curr_list, DCC_LL_LOCK);
+ if (lock_reg & DCC_LOCK_MASK) {
+ dev_err(drvdata->dev, "List %d is already locked\n", curr_list);
+ return false;
+ }
+
+ return true;
+}
+
+static bool is_dcc_enabled(struct dcc_drvdata *drvdata)
+{
+ int list;
+
+ for (list = 0; list < drvdata->nr_link_list; list++)
+ if (test_bit(list, drvdata->enable_bitmap))
+ return true;
+
+ return false;
+}
+
+static int dcc_enable(struct dcc_drvdata *drvdata, unsigned int curr_list)
+{
+ int ret;
+ u32 ram_cfg_base;
+
+ mutex_lock(&drvdata->mutex);
+
+ if (!dcc_valid_list(drvdata, curr_list)) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ /* Fill dcc sram with the poison value.
+ * This helps in understanding bus
+ * hang from registers returning a zero
+ */
+ if (!is_dcc_enabled(drvdata))
+ memset_io(drvdata->ram_base, 0xde, drvdata->ram_size);
+
+ /* 1. Take ownership of the list */
+ dcc_list_writel(drvdata, DCC_LOCK_MASK, curr_list, DCC_LL_LOCK);
+
+ /* 2. Program linked-list in the SRAM */
+ ram_cfg_base = drvdata->ram_cfg;
+ ret = dcc_emit_config(drvdata, curr_list);
+ if (ret) {
+ dcc_list_writel(drvdata, 0, curr_list, DCC_LL_LOCK);
+ goto out_unlock;
+ }
+
+ /* 3. Program DCC_RAM_CFG reg */
+ dcc_list_writel(drvdata, ram_cfg_base +
+ drvdata->ram_offset / 4, curr_list, DCC_LL_BASE);
+ dcc_list_writel(drvdata, drvdata->ram_start +
+ drvdata->ram_offset / 4, curr_list, DCC_FD_BASE);
+ dcc_list_writel(drvdata, 0xFFF, curr_list, DCC_LL_TIMEOUT);
+
+ /* 4. Clears interrupt status register */
+ dcc_list_writel(drvdata, 0, curr_list, DCC_LL_INT_ENABLE);
+ dcc_list_writel(drvdata, (BIT(0) | BIT(1) | BIT(2)),
+ curr_list, DCC_LL_INT_STATUS);
+
+ set_bit(curr_list, drvdata->enable_bitmap);
+
+ /* 5. Configure trigger */
+ dcc_list_writel(drvdata, DCC_TRIGGER_MASK,
+ curr_list, DCC_LL_CFG);
+
+out_unlock:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+
+static void dcc_disable(struct dcc_drvdata *drvdata, int curr_list)
+{
+ mutex_lock(&drvdata->mutex);
+
+ if (!test_bit(curr_list, drvdata->enable_bitmap))
+ goto out_unlock;
+ dcc_list_writel(drvdata, 0, curr_list, DCC_LL_CFG);
+ dcc_list_writel(drvdata, 0, curr_list, DCC_LL_BASE);
+ dcc_list_writel(drvdata, 0, curr_list, DCC_FD_BASE);
+ dcc_list_writel(drvdata, 0, curr_list, DCC_LL_LOCK);
+ clear_bit(curr_list, drvdata->enable_bitmap);
+out_unlock:
+ mutex_unlock(&drvdata->mutex);
+}
+
+static u32 dcc_filp_curr_list(const struct file *filp)
+{
+ struct dentry *dentry = file_dentry(filp);
+ int curr_list, ret;
+
+ ret = kstrtoint(dentry->d_parent->d_name.name, 0, &curr_list);
+ if (ret)
+ return ret;
+
+ return curr_list;
+}
+
+static ssize_t enable_read(struct file *filp, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ char *buf;
+ struct dcc_drvdata *drvdata = filp->private_data;
+
+ mutex_lock(&drvdata->mutex);
+
+ if (is_dcc_enabled(drvdata))
+ buf = "Y\n";
+ else
+ buf = "N\n";
+
+ mutex_unlock(&drvdata->mutex);
+
+ return simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf));
+}
+
+static ssize_t enable_write(struct file *filp, const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ int ret = 0, curr_list;
+ bool val;
+ struct dcc_drvdata *drvdata = filp->private_data;
+
+ curr_list = dcc_filp_curr_list(filp);
+ if (curr_list < 0)
+ return curr_list;
+
+ ret = kstrtobool_from_user(userbuf, count, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val) {
+ ret = dcc_enable(drvdata, curr_list);
+ if (ret)
+ return ret;
+ } else {
+ dcc_disable(drvdata, curr_list);
+ }
+
+ return count;
+}
+
+static const struct file_operations enable_fops = {
+ .read = enable_read,
+ .write = enable_write,
+ .open = simple_open,
+ .llseek = generic_file_llseek,
+};
+
+static ssize_t trigger_write(struct file *filp,
+ const char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ int ret;
+ unsigned int val;
+ struct dcc_drvdata *drvdata = filp->private_data;
+
+ ret = kstrtouint_from_user(user_buf, count, 0, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val != 1)
+ return -EINVAL;
+
+ ret = dcc_sw_trigger(drvdata);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static const struct file_operations trigger_fops = {
+ .write = trigger_write,
+ .open = simple_open,
+ .llseek = generic_file_llseek,
+};
+
+static int dcc_config_add(struct dcc_drvdata *drvdata, unsigned int addr,
+ unsigned int len, bool apb_bus, int curr_list)
+{
+ int ret = 0;
+ struct dcc_config_entry *entry, *pentry;
+ unsigned int base, offset;
+
+ mutex_lock(&drvdata->mutex);
+
+ if (!len || len > drvdata->ram_size / DCC_WORD_SIZE) {
+ dev_err(drvdata->dev, "DCC: Invalid length\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ base = addr & DCC_ADDR_RANGE_MASK;
+
+ if (!list_empty(&drvdata->cfg_head[curr_list])) {
+ pentry = list_last_entry(&drvdata->cfg_head[curr_list],
+ struct dcc_config_entry, list);
+
+ if (pentry->desc_type == DCC_READ_TYPE &&
+ addr >= (pentry->base + pentry->offset) &&
+ addr <= (pentry->base + pentry->offset + MAX_DCC_OFFSET)) {
+ /* Re-use base address from last entry */
+ base = pentry->base;
+
+ if ((pentry->len * 4 + pentry->base + pentry->offset)
+ == addr) {
+ len += pentry->len;
+
+ if (len > MAX_DCC_LEN)
+ pentry->len = MAX_DCC_LEN;
+ else
+ pentry->len = len;
+
+ addr = pentry->base + pentry->offset +
+ pentry->len * 4;
+ len -= pentry->len;
+ }
+ }
+ }
+
+ offset = addr - base;
+
+ while (len) {
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
+ entry->base = base;
+ entry->offset = offset;
+ entry->len = min_t(u32, len, MAX_DCC_LEN);
+ entry->desc_type = DCC_READ_TYPE;
+ entry->apb_bus = apb_bus;
+ INIT_LIST_HEAD(&entry->list);
+ list_add_tail(&entry->list,
+ &drvdata->cfg_head[curr_list]);
+
+ len -= entry->len;
+ offset += MAX_DCC_LEN * 4;
+ }
+
+out_unlock:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+
+static ssize_t dcc_config_add_read(struct dcc_drvdata *drvdata, char *buf, int curr_list)
+{
+ bool bus;
+ int len, nval;
+ unsigned int base;
+ char apb_bus[4];
+
+ nval = sscanf(buf, "%x %i %3s", &base, &len, apb_bus);
+ if (nval <= 0 || nval > 3)
+ return -EINVAL;
+
+ if (nval == 1) {
+ len = 1;
+ bus = false;
+ } else if (nval == 2) {
+ bus = false;
+ } else if (!strcmp("apb", apb_bus)) {
+ bus = true;
+ } else if (!strcmp("ahb", apb_bus)) {
+ bus = false;
+ } else {
+ return -EINVAL;
+ }
+
+ return dcc_config_add(drvdata, base, len, bus, curr_list);
+}
+
+static void dcc_config_reset(struct dcc_drvdata *drvdata)
+{
+ struct dcc_config_entry *entry, *temp;
+ int curr_list;
+
+ mutex_lock(&drvdata->mutex);
+
+ for (curr_list = 0; curr_list < drvdata->nr_link_list; curr_list++) {
+ list_for_each_entry_safe(entry, temp,
+ &drvdata->cfg_head[curr_list], list) {
+ list_del(&entry->list);
+ kfree(entry);
+ }
+ }
+ drvdata->ram_start = 0;
+ drvdata->ram_cfg = 0;
+ mutex_unlock(&drvdata->mutex);
+}
+
+static ssize_t config_reset_write(struct file *filp,
+ const char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ unsigned int val, ret;
+ struct dcc_drvdata *drvdata = filp->private_data;
+
+ ret = kstrtouint_from_user(user_buf, count, 0, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val)
+ dcc_config_reset(drvdata);
+
+ return count;
+}
+
+static const struct file_operations config_reset_fops = {
+ .write = config_reset_write,
+ .open = simple_open,
+ .llseek = generic_file_llseek,
+};
+
+static ssize_t ready_read(struct file *filp, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ int ret = 0;
+ char *buf;
+ struct dcc_drvdata *drvdata = filp->private_data;
+
+ mutex_lock(&drvdata->mutex);
+
+ if (!is_dcc_enabled(drvdata)) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (!FIELD_GET(BIT(1), readl(drvdata->base + dcc_status(drvdata->mem_map_ver))))
+ buf = "Y\n";
+ else
+ buf = "N\n";
+out_unlock:
+ mutex_unlock(&drvdata->mutex);
+
+ if (ret < 0)
+ return -EINVAL;
+ else