/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define RESOURCE_ID_LEN 8 #define NUM_PRIORITY 2 #define MAX_SLV_ID 8 #define CMD_DB_MAGIC 0x0C0330DBUL #define SLAVE_ID_MASK 0x7 #define SLAVE_ID_SHIFT 16 #define CMD_DB_STANDALONE_MASK BIT(0) struct entry_header { uint64_t res_id; u32 priority[NUM_PRIORITY]; u32 addr; u16 len; u16 offset; }; struct rsc_hdr { u16 slv_id; u16 header_offset; /* Entry header offset from data */ u16 data_offset; /* Entry offset for data location */ u16 cnt; /* Number of entries for HW type */ u16 version; /* MSB is Major and LSB is Minor version * identifies the HW type of Aux Data */ u16 reserved[3]; }; struct cmd_db_header { u32 version; u32 magic_num; struct rsc_hdr header[MAX_SLV_ID]; u32 check_sum; u32 reserved; u8 data[]; }; struct cmd_db_entry { const char resource_id[RESOURCE_ID_LEN + 1]; /* Unique id per entry */ const u32 addr; /* TCS Addr Slave ID + Offset address */ const u32 priority[NUM_PRIORITY]; /* Bitmask for DRV IDs */ u32 len; /* Aux data len */ u16 version; u8 data[]; }; /* CMD DB QUERY TYPES */ enum cmd_db_query_type { CMD_DB_QUERY_RES_ID = 0, CMD_DB_QUERY_ADDRESS, CMD_DB_QUERY_INVALID, CMD_DB_QUERY_MAX = 0x7ffffff, }; static void __iomem *start_addr; static struct cmd_db_header *cmd_db_header; static int cmd_db_status = -EPROBE_DEFER; static u64 cmd_db_get_u64_id(const char *id) { uint64_t rsc_id = 0; uint8_t *ch = (uint8_t *)&rsc_id; int i; for (i = 0; ((i < sizeof(rsc_id)) && id[i]); i++) ch[i] = id[i]; return rsc_id; } static int cmd_db_get_header(u64 query, struct entry_header *eh, struct rsc_hdr *rh, bool use_addr) { struct rsc_hdr *rsc_hdr; int i, j; if (!cmd_db_header) return -EPROBE_DEFER; if (!eh || !rh) return -EINVAL; rsc_hdr = &cmd_db_header->header[0]; for (i = 0; i < MAX_SLV_ID ; i++, rsc_hdr++) { struct entry_header *ent; if (!rsc_hdr->slv_id) break; ent = (struct entry_header *)(start_addr + sizeof(*cmd_db_header) + rsc_hdr->header_offset); for (j = 0; j < rsc_hdr->cnt; j++, ent++) { if (use_addr) { if (ent->addr == (u32)(query)) break; } else if (ent->res_id == query) break; } if (j < rsc_hdr->cnt) { memcpy(eh, ent, sizeof(*ent)); memcpy(rh, &cmd_db_header->header[i], sizeof(*rh)); return 0; } } return -ENODEV; } static int cmd_db_get_header_by_addr(u32 addr, struct entry_header *ent_hdr, struct rsc_hdr *rsc_hdr) { return cmd_db_get_header((u64)addr, ent_hdr, rsc_hdr, true); } static int cmd_db_get_header_by_rsc_id(const char *resource_id, struct entry_header *ent_hdr, struct rsc_hdr *rsc_hdr) { u64 rsc_id = cmd_db_get_u64_id(resource_id); return cmd_db_get_header(rsc_id, ent_hdr, rsc_hdr, false); } u32 cmd_db_get_addr(const char *resource_id) { int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; ret = cmd_db_get_header_by_rsc_id(resource_id, &ent, &rsc_hdr); return ret < 0 ? 0 : ent.addr; } bool cmd_db_get_priority(u32 addr, u8 drv_id) { int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; ret = cmd_db_get_header_by_addr(addr, &ent, &rsc_hdr); return ret < 0 ? false : (bool)(ent.priority[0] & (1 << drv_id)); } int cmd_db_get_aux_data(const char *resource_id, u8 *data, int len) { int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; if (!data) return -EINVAL; ret = cmd_db_get_header_by_rsc_id(resource_id, &ent, &rsc_hdr); if (ret) return ret; if (ent.len < len) return -EINVAL; len = (ent.len < len) ? ent.len : len; memcpy_fromio(data, start_addr + sizeof(*cmd_db_header) + rsc_hdr.data_offset + ent.offset, len); return len; } int cmd_db_get_aux_data_len(const char *resource_id) { int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; ret = cmd_db_get_header_by_rsc_id(resource_id, &ent, &rsc_hdr); return ret < 0 ? 0 : ent.len; } u16 cmd_db_get_version(const char *resource_id) { int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; ret = cmd_db_get_header_by_rsc_id(resource_id, &ent, &rsc_hdr); return ret < 0 ? 0 : rsc_hdr.version; } int cmd_db_ready(void) { return cmd_db_status; } int cmd_db_is_standalone(void) { if (cmd_db_status < 0) return cmd_db_status; return !!(cmd_db_header->reserved & CMD_DB_STANDALONE_MASK); } int cmd_db_get_slave_id(const char *resource_id) { int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; ret = cmd_db_get_header_by_rsc_id(resource_id, &ent, &rsc_hdr); return ret < 0 ? 0 : (ent.addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK; } static void *cmd_db_start(struct seq_file *m, loff_t *pos) { struct cmd_db_header *hdr = m->private; int slv_idx, ent_idx; struct entry_header *ent; int total = 0; for (slv_idx = 0; slv_idx < MAX_SLV_ID; slv_idx++) { if (!hdr->header[slv_idx].cnt) continue; ent_idx = *pos - total; if (ent_idx < hdr->header[slv_idx].cnt) break; total += hdr->header[slv_idx].cnt; } if (slv_idx == MAX_SLV_ID) return NULL; ent = start_addr + hdr->header[slv_idx].header_offset + sizeof(*hdr); return &ent[ent_idx]; } static void cmd_db_stop(struct seq_file *m, void *v) { } static void *cmd_db_next(struct seq_file *m, void *v, loff_t *pos) { (*pos)++; return cmd_db_start(m, pos); } static int cmd_db_seq_show(struct seq_file *m, void *v) { struct entry_header *eh = v; struct cmd_db_header *hdr = m->private; char buf[9] = {0}; if (!eh) return 0; memcpy(buf, &eh->res_id, min(sizeof(eh->res_id), sizeof(buf))); seq_printf(m, "Address: 0x%05x, id: %s", eh->addr, buf); if (eh->len) { int slv_id = (eh->addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK; u8 aux[32] = {0}; int len; int k; len = min_t(u32, eh->len, sizeof(aux)); for (k = 0; k < MAX_SLV_ID; k++) { if (hdr->header[k].slv_id == slv_id) break; } if (k == MAX_SLV_ID) return -EINVAL; memcpy_fromio(aux, start_addr + hdr->header[k].data_offset + eh->offset + sizeof(*cmd_db_header), len); seq_puts(m, ", aux data: "); for (k = 0; k < len; k++) seq_printf(m, "%02x ", aux[k]); } seq_puts(m, "\n"); return 0; } static const struct seq_operations cmd_db_seq_ops = { .start = cmd_db_start, .stop = cmd_db_stop, .next = cmd_db_next, .show = cmd_db_seq_show, }; static int cmd_db_file_open(struct inode *inode, struct file *file) { int ret = seq_open(file, &cmd_db_seq_ops); struct seq_file *s = (struct seq_file *)(file->private_data); s->private = inode->i_private; return ret; } static const struct file_operations cmd_db_fops = { .owner = THIS_MODULE, .open = cmd_db_file_open, .read = seq_read, .release = seq_release, .llseek = no_llseek, }; static int cmd_db_dev_probe(struct platform_device *pdev) { struct resource res; void __iomem *dict; dict = of_iomap(pdev->dev.of_node, 0); if (!dict) { cmd_db_status = -ENOMEM; pr_err("Command DB dictionary addr not found.\n"); goto failed; } /* * Read start address and size of the command DB address from * shared dictionary location */ res.start = readl_relaxed(dict); res.end = res.start + readl_relaxed(dict + 0x4); res.flags = IORESOURCE_MEM; res.name = NULL; iounmap(dict); start_addr = devm_ioremap_resource(&pdev->dev, &res); cmd_db_header = devm_kzalloc(&pdev->dev, sizeof(*cmd_db_header), GFP_KERNEL); if (!cmd_db_header) { cmd_db_status = -ENOMEM; pr_err("Command DB header not found.\n"); goto failed; } memcpy(cmd_db_header, start_addr, sizeof(*cmd_db_header)); if (cmd_db_header->magic_num != CMD_DB_MAGIC) { pr_err("%s(): Invalid Magic\n", __func__); cmd_db_status = -EINVAL; goto failed; } cmd_db_status = 0; of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); if (!debugfs_create_file("cmd_db", 0444, NULL, cmd_db_header, &cmd_db_fops)) pr_err("Couldn't create debugfs\n"); if (cmd_db_is_standalone() == 1) pr_info("Command DB is initialized in standalone mode.\n"); failed: return cmd_db_status; } static const struct of_device_id cmd_db_match_table[] = { {.compatible = "qcom,cmd-db"}, {}, }; static struct platform_driver cmd_db_dev_driver = { .probe = cmd_db_dev_probe, .driver = { .name = "cmd-db", .owner = THIS_MODULE, .of_match_table = cmd_db_match_table, }, }; int __init cmd_db_device_init(void) { return platform_driver_register(&cmd_db_dev_driver); } arch_initcall(cmd_db_device_init);