Add the sysfs variables to expose the user space functionalities like DCC enable, disable, configure addresses and software triggers. Also add the necessary methods along with the same. Signed-off-by: Souradeep Chowdhury <schowdhu@xxxxxxxxxxxxxx> --- drivers/soc/qcom/dcc.c | 1179 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1171 insertions(+), 8 deletions(-) diff --git a/drivers/soc/qcom/dcc.c b/drivers/soc/qcom/dcc.c index 89816bf..61e5225 100644 --- a/drivers/soc/qcom/dcc.c +++ b/drivers/soc/qcom/dcc.c @@ -17,12 +17,30 @@ #include <linux/slab.h> #include <linux/uaccess.h> + +#define TIMEOUT_US 100 + +#define dcc_writel(drvdata, val, off) \ + writel((val), drvdata->base + dcc_offset_conv(drvdata, off)) #define dcc_readl(drvdata, off) \ readl(drvdata->base + dcc_offset_conv(drvdata, off)) +#define dcc_sram_readl(drvdata, off) \ + readl(drvdata->ram_base + off) + /* DCC registers */ #define DCC_HW_INFO 0x04 #define DCC_LL_NUM_INFO 0x10 +#define DCC_STATUS 0x1C +#define DCC_LL_LOCK(m) (0x34 + 0x80 * m) +#define DCC_LL_CFG(m) (0x38 + 0x80 * m) +#define DCC_LL_BASE(m) (0x3c + 0x80 * m) +#define DCC_FD_BASE(m) (0x40 + 0x80 * m) +#define DCC_LL_TIMEOUT(m) (0x44 + 0x80 * m) +#define DCC_LL_INT_ENABLE(m) (0x4C + 0x80 * m) +#define DCC_LL_INT_STATUS(m) (0x50 + 0x80 * m) +#define DCC_LL_SW_TRIGGER(m) (0x60 + 0x80 * m) +#define DCC_LL_BUS_ACCESS_STATUS(m) (0x64 + 0x80 * m) #define DCC_MAP_LEVEL1 0x18 #define DCC_MAP_LEVEL2 0x34 @@ -36,24 +54,38 @@ #define DCC_FIX_LOOP_OFFSET 16 #define DCC_VER_INFO_BIT 9 +#define DCC_READ 0 +#define DCC_WRITE 1 +#define DCC_LOOP 2 +#define DCC_READ_WRITE 3 + +#define MAX_DCC_OFFSET GENMASK(9, 2) +#define MAX_DCC_LEN GENMASK(6, 0) +#define MAX_LOOP_CNT GENMASK(7, 0) + +#define DCC_ADDR_DESCRIPTOR 0x00 +#define DCC_LOOP_DESCRIPTOR BIT(30) +#define DCC_RD_MOD_WR_DESCRIPTOR BIT(31) +#define DCC_LINK_DESCRIPTOR GENMASK(31, 30) + +#define DCC_READ_IND 0x00 +#define DCC_WRITE_IND (BIT(28)) + +#define DCC_AHB_IND 0x00 +#define DCC_APB_IND BIT(29) + #define DCC_MAX_LINK_LIST 8 #define DCC_INVALID_LINK_LIST GENMASK(7, 0) #define DCC_VER_MASK1 GENMASK(6, 0) #define DCC_VER_MASK2 GENMASK(5, 0) -#define DCC_RD_MOD_WR_ADDR 0xC105E +#define DCC_RD_MOD_WR_ADDR 0xC105E struct qcom_dcc_config { const int dcc_ram_offset; }; -enum dcc_mem_map_ver { - DCC_MEM_MAP_VER1, - DCC_MEM_MAP_VER2, - DCC_MEM_MAP_VER3 -}; - enum dcc_descriptor_type { DCC_ADDR_TYPE, DCC_LOOP_TYPE, @@ -61,6 +93,12 @@ enum dcc_descriptor_type { DCC_WRITE_TYPE }; +enum dcc_mem_map_ver { + DCC_MEM_MAP_VER1, + DCC_MEM_MAP_VER2, + DCC_MEM_MAP_VER3 +}; + struct dcc_config_entry { u32 base; u32 offset; @@ -98,6 +136,22 @@ struct dcc_drvdata { u8 loopoff; }; +struct dcc_cfg_attr { + u32 addr; + u32 prev_addr; + u32 prev_off; + u32 link; + u32 sram_offset; +}; + +struct dcc_cfg_loop_attr { + u32 loop; + bool loop_start; + u32 loop_cnt; + u32 loop_len; + u32 loop_off; +}; + static u32 dcc_offset_conv(struct dcc_drvdata *drvdata, u32 off) { if (drvdata->mem_map_ver == DCC_MEM_MAP_VER1) { @@ -114,6 +168,832 @@ static u32 dcc_offset_conv(struct dcc_drvdata *drvdata, u32 off) return off; } +static int dcc_sram_writel(struct dcc_drvdata *drvdata, + u32 val, u32 off) +{ + if (unlikely(off > (drvdata->ram_size - 4))) + return -EINVAL; + + writel((val), drvdata->ram_base + off); + + return 0; +} + +static bool dcc_ready(struct dcc_drvdata *drvdata) +{ + u32 val; + + /* poll until DCC ready */ + if (!readl_poll_timeout((drvdata->base + DCC_STATUS), val, + (FIELD_GET(GENMASK(1, 0), val) == 0), 1, TIMEOUT_US)) + return true; + + return false; +} + +static int dcc_read_status(struct dcc_drvdata *drvdata) +{ + int curr_list; + u32 bus_status; + u32 ll_cfg = 0; + u32 tmp_ll_cfg = 0; + + for (curr_list = 0; curr_list < drvdata->nr_link_list; curr_list++) { + if (!drvdata->enable[curr_list]) + continue; + + bus_status = dcc_readl(drvdata, DCC_LL_BUS_ACCESS_STATUS(curr_list)); + + if (bus_status) { + dev_err(drvdata->dev, + "Read access error for list %d err: 0x%x.\n", + curr_list, bus_status); + + ll_cfg = dcc_readl(drvdata, DCC_LL_CFG(curr_list)); + tmp_ll_cfg = ll_cfg & ~BIT(9); + dcc_writel(drvdata, tmp_ll_cfg, DCC_LL_CFG(curr_list)); + dcc_writel(drvdata, 0x3, + DCC_LL_BUS_ACCESS_STATUS(curr_list)); + dcc_writel(drvdata, ll_cfg, DCC_LL_CFG(curr_list)); + return -ENODATA; + } + } + + return 0; +} + +static int dcc_sw_trigger(struct dcc_drvdata *drvdata) +{ + int ret = 0; + int curr_list; + u32 ll_cfg = 0; + u32 tmp_ll_cfg = 0; + + mutex_lock(&drvdata->mutex); + + for (curr_list = 0; curr_list < drvdata->nr_link_list; curr_list++) { + if (!drvdata->enable[curr_list]) + continue; + ll_cfg = dcc_readl(drvdata, DCC_LL_CFG(curr_list)); + tmp_ll_cfg = ll_cfg & ~BIT(9); + dcc_writel(drvdata, tmp_ll_cfg, DCC_LL_CFG(curr_list)); + dcc_writel(drvdata, 1, DCC_LL_SW_TRIGGER(curr_list)); + dcc_writel(drvdata, ll_cfg, DCC_LL_CFG(curr_list)); + } + + if (!dcc_ready(drvdata)) { + dev_err(drvdata->dev, + "DCC is busy after receiving sw tigger.\n"); + ret = -EBUSY; + goto err; + } + + ret = dcc_read_status(drvdata); + +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static void _dcc_ll_cfg_reset_link(struct dcc_cfg_attr *cfg) +{ + cfg->addr = 0; + cfg->link = 0; + cfg->prev_off = 0; + cfg->prev_addr = cfg->addr; +} + +static int _dcc_ll_cfg_read_write(struct dcc_drvdata *drvdata, + struct dcc_config_entry *entry, + struct dcc_cfg_attr *cfg) +{ + int ret; + + if (cfg->link) { + /* + * write new offset = 1 to continue + * processing the list + */ + + ret = dcc_sram_writel(drvdata, cfg->link, cfg->sram_offset); + if (ret) + return ret; + cfg->sram_offset += 4; + /* Reset link and prev_off */ + _dcc_ll_cfg_reset_link(cfg); + } + + cfg->addr = DCC_RD_MOD_WR_DESCRIPTOR; + ret = dcc_sram_writel(drvdata, cfg->addr, cfg->sram_offset); + if (ret) + return ret; + + cfg->sram_offset += 4; + ret = dcc_sram_writel(drvdata, entry->mask, cfg->sram_offset); + if (ret) + return ret; + + cfg->sram_offset += 4; + ret = dcc_sram_writel(drvdata, entry->write_val, cfg->sram_offset); + if (ret) + return ret; + + cfg->sram_offset += 4; + cfg->addr = 0; + return ret; +} + +static int _dcc_ll_cfg_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 ret; + + /* Check if we need to write link of prev entry */ + if (cfg->link) { + ret = dcc_sram_writel(drvdata, cfg->link, cfg->sram_offset); + if (ret) + return ret; + cfg->sram_offset += 4; + } + + if (cfg_loop->loop_start) { + cfg_loop->loop = (cfg->sram_offset - cfg_loop->loop_off) / 4; + cfg_loop->loop |= (cfg_loop->loop_cnt << drvdata->loopoff) & + GENMASK(27, drvdata->loopoff); + cfg_loop->loop |= DCC_LOOP_DESCRIPTOR; + *total_len += (*total_len - cfg_loop->loop_len) * cfg_loop->loop_cnt; + + ret = dcc_sram_writel(drvdata, cfg_loop->loop, cfg->sram_offset); + + if (ret) + return ret; + cfg->sram_offset += 4; + + 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); + + return ret; +} + +static int _dcc_ll_cfg_write(struct dcc_drvdata *drvdata, + struct dcc_config_entry *entry, + struct dcc_cfg_attr *cfg, + u32 *total_len) +{ + u32 off; + int ret; + + if (cfg->link) { + /* + * write new offset = 1 to continue + * processing the list + */ + ret = dcc_sram_writel(drvdata, cfg->link, cfg->sram_offset); + + if (ret) + return ret; + + cfg->sram_offset += 4; + /* Reset link and prev_off */ + cfg->addr = 0; + cfg->prev_off = 0; + cfg->prev_addr = cfg->addr; + } + + off = entry->offset/4; + /* write new offset-length pair to correct position */ + cfg->link |= ((off & GENMASK(7, 0)) | BIT(15) | ((entry->len << 8) & GENMASK(14, 8))); + cfg->link |= DCC_LINK_DESCRIPTOR; + + /* Address type */ + cfg->addr = (entry->base >> 4) & GENMASK(27, 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; + ret = dcc_sram_writel(drvdata, cfg->addr, cfg->sram_offset); + if (ret) + return ret; + + cfg->sram_offset += 4; + ret = dcc_sram_writel(drvdata, cfg->link, cfg->sram_offset); + if (ret) + return ret; + + cfg->sram_offset += 4; + ret = dcc_sram_writel(drvdata, entry->write_val, cfg->sram_offset); + if (ret) + return ret; + + cfg->sram_offset += 4; + cfg->addr = 0; + cfg->link = 0; + return ret; +} + +static int _dcc_ll_cfg_default(struct dcc_drvdata *drvdata, + struct dcc_config_entry *entry, + struct dcc_cfg_attr *cfg, + u32 *pos, u32 *total_len) +{ + int ret; + u32 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) { + ret = dcc_sram_writel(drvdata, cfg->link, cfg->sram_offset); + if (ret) + return ret; + cfg->sram_offset += 4; + } + dev_dbg(drvdata->dev, "DCC: sram address 0x%x\n", cfg->sram_offset); + + /* Write address */ + ret = dcc_sram_writel(drvdata, cfg->addr, cfg->sram_offset); + + if (ret) + return ret; + + cfg->sram_offset += 4; + + /* 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); + ret = -EINVAL; + return ret; + } + + 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 */ + cfg->link |= (((off-cfg->prev_off) & GENMASK(7, 0)) | + ((entry->len << 8) & GENMASK(14, 8))) << *pos; + cfg->link |= DCC_LINK_DESCRIPTOR; + + if (*pos) { + ret = dcc_sram_writel(drvdata, cfg->link, cfg->sram_offset); + if (ret) + return ret; + cfg->sram_offset += 4; + cfg->link = 0; + } + + cfg->prev_off = off + entry->len - 1; + cfg->prev_addr = cfg->addr; + return ret; +} + +static int __dcc_ll_cfg(struct dcc_drvdata *drvdata, int curr_list) +{ + int ret = 0; + u32 total_len, pos; + struct dcc_config_entry *entry; + struct dcc_cfg_attr cfg; + struct dcc_cfg_loop_attr cfg_loop; + + memset(&cfg, 0, sizeof(cfg)); + memset(&cfg_loop, 0, sizeof(cfg_loop)); + 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: + ret = _dcc_ll_cfg_read_write(drvdata, entry, &cfg); + if (ret) + goto overstep; + break; + + case DCC_LOOP_TYPE: + ret = _dcc_ll_cfg_loop(drvdata, entry, &cfg, &cfg_loop, &total_len); + if (ret) + goto overstep; + break; + + case DCC_WRITE_TYPE: + ret = _dcc_ll_cfg_write(drvdata, entry, &cfg, &total_len); + if (ret) + goto overstep; + break; + + default: + ret = _dcc_ll_cfg_default(drvdata, entry, &cfg, &pos, &total_len); + if (ret) + goto overstep; + break; + } + } + + if (cfg.link) { + ret = dcc_sram_writel(drvdata, cfg.link, cfg.sram_offset); + if (ret) + goto overstep; + cfg.sram_offset += 4; + } + + 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; + ret = dcc_sram_writel(drvdata, cfg.addr, cfg.sram_offset); + if (ret) + goto overstep; + cfg.sram_offset += 4; + } + + /* Setting zero to indicate end of the list */ + cfg.link = DCC_LINK_DESCRIPTOR; + ret = dcc_sram_writel(drvdata, cfg.link, cfg.sram_offset); + if (ret) + goto overstep; + cfg.sram_offset += 4; + + /* 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); + dev_err(drvdata->dev, "DCC SRAM oversteps, 0x%x (0x%x)\n", + cfg.sram_offset, drvdata->ram_size); + +err: + return ret; +} + +static int dcc_valid_list(struct dcc_drvdata *drvdata, int curr_list) +{ + u32 lock_reg; + + if (list_empty(&drvdata->cfg_head[curr_list])) + return -EINVAL; + + if (drvdata->enable[curr_list]) { + dev_err(drvdata->dev, "List %d is already enabled\n", + curr_list); + return -EINVAL; + } + + lock_reg = dcc_readl(drvdata, DCC_LL_LOCK(curr_list)); + if (lock_reg & 0x1) { + dev_err(drvdata->dev, "List %d is already locked\n", + curr_list); + return -EINVAL; + } + + dev_err(drvdata->dev, "DCC list passed %d\n", curr_list); + return 0; +} + +static bool is_dcc_enabled(struct dcc_drvdata *drvdata) +{ + bool dcc_enable = false; + int list; + + for (list = 0; list < DCC_MAX_LINK_LIST; list++) { + if (drvdata->enable[list]) { + dcc_enable = true; + break; + } + } + + return dcc_enable; +} + +static int dcc_enable(struct dcc_drvdata *drvdata) +{ + int ret = 0; + int list; + u32 ram_cfg_base; + + mutex_lock(&drvdata->mutex); + + if (!is_dcc_enabled(drvdata)) { + memset_io(drvdata->ram_base, + 0xDE, drvdata->ram_size); + } + + for (list = 0; list < drvdata->nr_link_list; list++) { + + if (dcc_valid_list(drvdata, list)) + continue; + + /* 1. Take ownership of the list */ + dcc_writel(drvdata, BIT(0), DCC_LL_LOCK(list)); + + /* 2. Program linked-list in the SRAM */ + ram_cfg_base = drvdata->ram_cfg; + ret = __dcc_ll_cfg(drvdata, list); + if (ret) { + dcc_writel(drvdata, 0, DCC_LL_LOCK(list)); + dev_info(drvdata->dev, "DCC ram programming failed\n"); + goto err; + } + + /* 3. program DCC_RAM_CFG reg */ + dcc_writel(drvdata, ram_cfg_base + + drvdata->ram_offset/4, DCC_LL_BASE(list)); + dcc_writel(drvdata, drvdata->ram_start + + drvdata->ram_offset/4, DCC_FD_BASE(list)); + dcc_writel(drvdata, 0xFFF, DCC_LL_TIMEOUT(list)); + + /* 4. Clears interrupt status register */ + dcc_writel(drvdata, 0, DCC_LL_INT_ENABLE(list)); + dcc_writel(drvdata, (BIT(0) | BIT(1) | BIT(2)), + DCC_LL_INT_STATUS(list)); + + dev_info(drvdata->dev, "All values written to enable.\n"); + /* Make sure all config is written in sram */ + mb(); + + drvdata->enable[list] = true; + + /* 5. Configure trigger */ + dcc_writel(drvdata, BIT(9), DCC_LL_CFG(list)); + } + +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static void dcc_disable(struct dcc_drvdata *drvdata) +{ + int curr_list; + + mutex_lock(&drvdata->mutex); + + if (!dcc_ready(drvdata)) + dev_err(drvdata->dev, "DCC is not ready Disabling DCC...\n"); + + for (curr_list = 0; curr_list < drvdata->nr_link_list; curr_list++) { + if (!drvdata->enable[curr_list]) + continue; + dcc_writel(drvdata, 0, DCC_LL_CFG(curr_list)); + dcc_writel(drvdata, 0, DCC_LL_BASE(curr_list)); + dcc_writel(drvdata, 0, DCC_FD_BASE(curr_list)); + dcc_writel(drvdata, 0, DCC_LL_LOCK(curr_list)); + drvdata->enable[curr_list] = false; + } + memset_io(drvdata->ram_base, 0, drvdata->ram_size); + drvdata->ram_cfg = 0; + drvdata->ram_start = 0; + mutex_unlock(&drvdata->mutex); +} + +static ssize_t curr_list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + mutex_lock(&drvdata->mutex); + if (drvdata->curr_list == DCC_INVALID_LINK_LIST) { + dev_err(dev, "curr_list is not set.\n"); + ret = -EINVAL; + goto err; + } + + ret = scnprintf(buf, PAGE_SIZE, "%d\n", drvdata->curr_list); +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static ssize_t curr_list_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + unsigned long val; + u32 lock_reg; + bool dcc_enable = false; + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val >= drvdata->nr_link_list) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + + dcc_enable = is_dcc_enabled(drvdata); + if (drvdata->curr_list != DCC_INVALID_LINK_LIST && dcc_enable) { + dev_err(drvdata->dev, "DCC is enabled, please disable it first.\n"); + mutex_unlock(&drvdata->mutex); + return -EINVAL; + } + + lock_reg = dcc_readl(drvdata, DCC_LL_LOCK(val)); + if (lock_reg & 0x1) { + dev_err(drvdata->dev, "DCC linked list is already configured\n"); + mutex_unlock(&drvdata->mutex); + return -EINVAL; + } + drvdata->curr_list = val; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static DEVICE_ATTR_RW(curr_list); + + +static ssize_t trigger_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret = 0; + unsigned long val; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + if (val != 1) + return -EINVAL; + + ret = dcc_sw_trigger(drvdata); + if (!ret) + ret = size; + + return ret; +} +static DEVICE_ATTR_WO(trigger); + +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + bool dcc_enable = false; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + mutex_lock(&drvdata->mutex); + if (drvdata->curr_list >= drvdata->nr_link_list) { + dev_err(dev, "Select link list to program using curr_list\n"); + ret = -EINVAL; + goto err; + } + + dcc_enable = is_dcc_enabled(drvdata); + + ret = scnprintf(buf, PAGE_SIZE, "%u\n", + (unsigned int)dcc_enable); +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static ssize_t enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret = 0; + unsigned long val; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val) + ret = dcc_enable(drvdata); + else + dcc_disable(drvdata); + + if (!ret) + ret = size; + + return ret; + +} + +static DEVICE_ATTR_RW(enable); + +static ssize_t config_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + struct dcc_config_entry *entry; + char local_buf[64]; + int len = 0, count = 0; + + buf[0] = '\0'; + + mutex_lock(&drvdata->mutex); + if (drvdata->curr_list >= drvdata->nr_link_list) { + dev_err(dev, "Select link list to program using curr_list\n"); + count = -EINVAL; + goto err; + } + + list_for_each_entry(entry, + &drvdata->cfg_head[drvdata->curr_list], list) { + switch (entry->desc_type) { + case DCC_READ_WRITE_TYPE: + len = snprintf(local_buf, 64, "Index: 0x%x, mask: 0x%x, val: 0x%x\n", + entry->index, entry->mask, entry->write_val); + break; + case DCC_LOOP_TYPE: + len = snprintf(local_buf, 64, "Index: 0x%x, Loop: %d\n", + entry->index, entry->loop_cnt); + break; + case DCC_WRITE_TYPE: + len = snprintf(local_buf, 64, + "Write Index: 0x%x, Base: 0x%x, Offset: 0x%x, len: 0x%x APB: %d\n", + entry->index, entry->base, entry->offset, entry->len, + entry->apb_bus); + break; + default: + len = snprintf(local_buf, 64, + "Read Index: 0x%x, Base: 0x%x, Offset: 0x%x, len: 0x%x APB: %d\n", + entry->index, entry->base, entry->offset, + entry->len, entry->apb_bus); + } + + if ((count + len) > PAGE_SIZE) { + dev_err(dev, "DCC: Couldn't write complete config\n"); + break; + } + strlcat(buf, local_buf, PAGE_SIZE); + count += len; + } + +err: + mutex_unlock(&drvdata->mutex); + return count; +} + +static int dcc_config_add(struct dcc_drvdata *drvdata, unsigned int addr, + unsigned int len, int apb_bus) +{ + int ret; + struct dcc_config_entry *entry, *pentry; + unsigned int base, offset; + + mutex_lock(&drvdata->mutex); + + if (drvdata->curr_list >= drvdata->nr_link_list) { + dev_err(drvdata->dev, "Select link list to program using curr_list\n"); + ret = -EINVAL; + goto err; + } + + if (!len || len > (drvdata->ram_size / 8)) { + dev_err(drvdata->dev, "DCC: Invalid length\n"); + ret = -EINVAL; + goto err; + } + + base = addr & GENMASK(31, 4); + + if (!list_empty(&drvdata->cfg_head[drvdata->curr_list])) { + pentry = list_last_entry(&drvdata->cfg_head[drvdata->curr_list], + struct dcc_config_entry, list); + + if (pentry->desc_type == DCC_ADDR_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 = devm_kzalloc(drvdata->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto err; + } + + entry->base = base; + entry->offset = offset; + entry->len = min_t(u32, len, MAX_DCC_LEN); + entry->index = drvdata->nr_config[drvdata->curr_list]++; + entry->desc_type = DCC_ADDR_TYPE; + entry->apb_bus = apb_bus; + INIT_LIST_HEAD(&entry->list); + list_add_tail(&entry->list, + &drvdata->cfg_head[drvdata->curr_list]); + + len -= entry->len; + offset += MAX_DCC_LEN * 4; + } + + mutex_unlock(&drvdata->mutex); + return 0; +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static ssize_t config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, len, apb_bus; + unsigned int base; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + int nval; + + nval = sscanf(buf, "%x %i %d", &base, &len, &apb_bus); + if (nval <= 0 || nval > 3) + return -EINVAL; + + if (nval == 1) { + len = 1; + apb_bus = 0; + } else if (nval == 2) { + apb_bus = 0; + } else { + apb_bus = 1; + } + + ret = dcc_config_add(drvdata, base, len, apb_bus); + if (ret) + return ret; + + return size; + +} + +static DEVICE_ATTR_RW(config); + static void dcc_config_reset(struct dcc_drvdata *drvdata) { struct dcc_config_entry *entry, *temp; @@ -135,6 +1015,286 @@ static void dcc_config_reset(struct dcc_drvdata *drvdata) mutex_unlock(&drvdata->mutex); } + +static ssize_t config_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val) + dcc_config_reset(drvdata); + + return size; +} + +static DEVICE_ATTR_WO(config_reset); + +static ssize_t ready_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + mutex_lock(&drvdata->mutex); + + if (drvdata->curr_list >= drvdata->nr_link_list) { + dev_err(dev, "Select link list to program using curr_list\n"); + ret = -EINVAL; + goto err; + } + + if (!drvdata->enable[drvdata->curr_list]) { + ret = -EINVAL; + goto err; + } + + ret = scnprintf(buf, PAGE_SIZE, "%u\n", + (unsigned int)FIELD_GET(BIT(1), dcc_readl(drvdata, DCC_STATUS))); +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static DEVICE_ATTR_RO(ready); + +static ssize_t interrupt_disable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + (unsigned int)drvdata->interrupt_disable); +} + +static ssize_t interrupt_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->interrupt_disable = (val ? 1:0); + mutex_unlock(&drvdata->mutex); + return size; +} + +static DEVICE_ATTR_RW(interrupt_disable); + +static int dcc_add_loop(struct dcc_drvdata *drvdata, unsigned long loop_cnt) +{ + struct dcc_config_entry *entry; + + entry = devm_kzalloc(drvdata->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->loop_cnt = min_t(u32, loop_cnt, MAX_LOOP_CNT); + entry->index = drvdata->nr_config[drvdata->curr_list]++; + entry->desc_type = DCC_LOOP_TYPE; + INIT_LIST_HEAD(&entry->list); + list_add_tail(&entry->list, &drvdata->cfg_head[drvdata->curr_list]); + + return 0; +} + +static ssize_t loop_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long loop_cnt; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + mutex_lock(&drvdata->mutex); + + if (kstrtoul(buf, 16, &loop_cnt)) { + ret = -EINVAL; + goto err; + } + + if (drvdata->curr_list >= drvdata->nr_link_list) { + dev_err(dev, "Select link list to program using curr_list\n"); + ret = -EINVAL; + goto err; + } + + ret = dcc_add_loop(drvdata, loop_cnt); + if (ret) + goto err; + + mutex_unlock(&drvdata->mutex); + return size; +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static DEVICE_ATTR_WO(loop); + +static int dcc_rd_mod_wr_add(struct dcc_drvdata *drvdata, unsigned int mask, + unsigned int val) +{ + int ret = 0; + struct dcc_config_entry *entry; + + mutex_lock(&drvdata->mutex); + + if (drvdata->curr_list >= drvdata->nr_link_list) { + dev_err(drvdata->dev, "Select link list to program using curr_list\n"); + ret = -EINVAL; + goto err; + } + + if (list_empty(&drvdata->cfg_head[drvdata->curr_list])) { + dev_err(drvdata->dev, "DCC: No read address programmed\n"); + ret = -EPERM; + goto err; + } + + entry = devm_kzalloc(drvdata->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto err; + } + + entry->desc_type = DCC_READ_WRITE_TYPE; + entry->mask = mask; + entry->write_val = val; + entry->index = drvdata->nr_config[drvdata->curr_list]++; + INIT_LIST_HEAD(&entry->list); + list_add_tail(&entry->list, &drvdata->cfg_head[drvdata->curr_list]); +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static ssize_t rd_mod_wr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + int nval; + unsigned int mask, val; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + nval = sscanf(buf, "%x %x", &mask, &val); + + if (nval <= 1 || nval > 2) + return -EINVAL; + + ret = dcc_rd_mod_wr_add(drvdata, mask, val); + if (ret) + return ret; + + return size; + +} + +static DEVICE_ATTR_WO(rd_mod_wr); + +static int dcc_add_write(struct dcc_drvdata *drvdata, unsigned int addr, + unsigned int write_val, int apb_bus) +{ + struct dcc_config_entry *entry; + + entry = devm_kzalloc(drvdata->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->desc_type = DCC_WRITE_TYPE; + entry->base = addr & GENMASK(31, 4); + entry->offset = addr - entry->base; + entry->write_val = write_val; + entry->index = drvdata->nr_config[drvdata->curr_list]++; + entry->len = 1; + entry->apb_bus = apb_bus; + INIT_LIST_HEAD(&entry->list); + list_add_tail(&entry->list, &drvdata->cfg_head[drvdata->curr_list]); + + return 0; +} + +static ssize_t config_write_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + int nval; + unsigned int addr, write_val; + int apb_bus = 0; + struct dcc_drvdata *drvdata = dev_get_drvdata(dev); + + mutex_lock(&drvdata->mutex); + + nval = sscanf(buf, "%x %x %d", &addr, &write_val, &apb_bus); + + if (nval <= 1 || nval > 3) { + ret = -EINVAL; + goto err; + } + + if (drvdata->curr_list >= drvdata->nr_link_list) { + dev_err(dev, "Select link list to program using curr_list\n"); + ret = -EINVAL; + goto err; + } + + if (nval == 3 && apb_bus != 0) + apb_bus = 1; + + ret = dcc_add_write(drvdata, addr, write_val, apb_bus); + if (ret) + goto err; + + mutex_unlock(&drvdata->mutex); + return size; +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static DEVICE_ATTR_WO(config_write); + +static const struct device_attribute *dcc_attrs[] = { + &dev_attr_trigger, + &dev_attr_enable, + &dev_attr_config, + &dev_attr_config_reset, + &dev_attr_ready, + &dev_attr_interrupt_disable, + &dev_attr_loop, + &dev_attr_rd_mod_wr, + &dev_attr_curr_list, + &dev_attr_config_write, + NULL, +}; + +static int dcc_create_files(struct device *dev, + const struct device_attribute **attrs) +{ + int ret = 0, i; + + for (i = 0; attrs[i] != NULL; i++) { + ret = device_create_file(dev, attrs[i]); + if (ret) { + dev_err(dev, "DCC: Couldn't create sysfs attribute: %s\n", + attrs[i]->attr.name); + break; + } + } + return ret; +} + static int dcc_sram_open(struct inode *inode, struct file *file) { struct dcc_drvdata *drvdata = container_of(inode->i_cdev, @@ -347,6 +1507,9 @@ static int dcc_probe(struct platform_device *pdev) ret = dcc_sram_dev_init(drvdata); if (ret) goto err; + ret = dcc_create_files(dev, dcc_attrs); + if (ret) + goto err; return ret; err: @@ -365,7 +1528,7 @@ static int dcc_remove(struct platform_device *pdev) } static const struct qcom_dcc_config sm8150_cfg = { - .dcc_ram_offset = 0x5000, + .dcc_ram_offset = 0x5000, }; static const struct of_device_id dcc_match_table[] = { -- QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation