420 lines
9.0 KiB
C
420 lines
9.0 KiB
C
/* 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 <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <soc/qcom/cmd-db.h>
|
|
|
|
#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);
|