summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/soc/qcom/dcc.c104
1 files changed, 77 insertions, 27 deletions
diff --git a/drivers/soc/qcom/dcc.c b/drivers/soc/qcom/dcc.c
index 42307b8fdda1..c1161b675018 100644
--- a/drivers/soc/qcom/dcc.c
+++ b/drivers/soc/qcom/dcc.c
@@ -61,6 +61,9 @@
#define DCC_REG_DUMP_MAGIC_V2 (0x42445953)
#define DCC_REG_DUMP_VER (1)
+#define MAX_DCC_OFFSET (0xFF * 4)
+#define MAX_DCC_LEN 0x7F
+
enum dcc_func_type {
DCC_FUNC_TYPE_CAPTURE,
DCC_FUNC_TYPE_CRC,
@@ -611,42 +614,84 @@ static ssize_t dcc_show_config(struct device *dev,
return count;
}
-static int dcc_config_add(struct dcc_drvdata *drvdata, unsigned base,
- unsigned offset, unsigned len)
+static int dcc_config_add(struct dcc_drvdata *drvdata, unsigned addr,
+ unsigned len)
{
int ret;
- struct dcc_config_entry *entry;
+ struct dcc_config_entry *entry, *pentry;
+ unsigned base, offset;
mutex_lock(&drvdata->mutex);
- /* Validate data */
- if (!len || len > BM(0, 6)) {
- dev_err(drvdata->dev,
- "DCC: Invalid length! Expected range [1, %u] in words.\n",
- (unsigned int)BM(0, 6));
+ if (!len) {
+ dev_err(drvdata->dev, "DCC: Invalid length!\n");
ret = -EINVAL;
goto err;
}
- if (base & BM(0, 3)) {
- dev_err(drvdata->dev,
- "DCC: Invalid base! Expected [31:4] bits of actual address.\n");
- ret = -EINVAL;
- goto err;
- }
+ base = addr & BM(4, 31);
- entry = devm_kzalloc(drvdata->dev, sizeof(*entry), GFP_KERNEL);
- if (!entry) {
- ret = -ENOMEM;
- goto err;
+ if (!list_empty(&drvdata->config_head)) {
+ pentry = list_last_entry(&drvdata->config_head,
+ struct dcc_config_entry, list);
+
+ if (addr >= (pentry->base + pentry->offset) &&
+ addr <= (pentry->base + pentry->offset + MAX_DCC_OFFSET)) {
+
+ /* Re-use base address from last entry */
+ base = pentry->base;
+
+ /*
+ * Check if new address is contiguous to last entry's
+ * addresses. If yes then we can re-use last entry and
+ * just need to update its length.
+ */
+ if ((pentry->len * 4 + pentry->base + pentry->offset)
+ == addr) {
+ len += pentry->len;
+
+ /*
+ * Check if last entry can hold additional new
+ * length. If yes then we don't need to create
+ * a new entry else we need to add a new entry
+ * with same base but updated offset.
+ */
+ if (len > MAX_DCC_LEN)
+ pentry->len = MAX_DCC_LEN;
+ else
+ pentry->len = len;
+
+ /*
+ * Update start addr and len for remaining
+ * addresses, which will be part of new
+ * entry.
+ */
+ addr = pentry->base + pentry->offset +
+ pentry->len * 4;
+ len -= pentry->len;
+ }
+ }
}
- entry->base = base;
- entry->offset = offset;
- entry->len = len;
- entry->index = drvdata->nr_config++;
- INIT_LIST_HEAD(&entry->list);
- list_add_tail(&entry->list, &drvdata->config_head);
+ offset = addr - base;
+
+ while (len) {
+ entry = devm_kzalloc(drvdata->dev, sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ entry->base = base;
+ entry->offset = offset;
+ entry->len = min_t(uint32_t, len, MAX_DCC_LEN);
+ entry->index = drvdata->nr_config++;
+ INIT_LIST_HEAD(&entry->list);
+ list_add_tail(&entry->list, &drvdata->config_head);
+
+ len -= entry->len;
+ offset += MAX_DCC_LEN * 4;
+ }
mutex_unlock(&drvdata->mutex);
return 0;
@@ -660,13 +705,18 @@ static ssize_t dcc_store_config(struct device *dev,
const char *buf, size_t size)
{
int ret;
- unsigned base, offset, len;
+ unsigned base, len;
struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+ int nval;
- if (sscanf(buf, "%x %x %u", &base, &offset, &len) != 3)
+ nval = sscanf(buf, "%x %i", &base, &len);
+ if (nval <= 0 || nval > 2)
return -EINVAL;
- ret = dcc_config_add(drvdata, base, offset, len);
+ if (nval == 1)
+ len = 1;
+
+ ret = dcc_config_add(drvdata, base, len);
if (ret)
return ret;