blob: 197fabbaa4dfbef3b56ecc143f2906a578cfecff [file] [log] [blame]
/* arch/arm/mach-msm/mnemosyne.c
*
* Copyright (C) 2013 HTC Corporation.
*
* 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/debugfs.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <htc_mnemosyne/htc_mnemosyne.h>
#define MNEMOSYNE_MODULE_NAME "mnemosyne"
#define MNEMOSYNE_DT_NAME "htc_mnemosyne"
#define MNEMOSYNE_DT_MATCH "htc,mnemosyne"
#define INVALID_MAGIC (0xF0)
struct mnemosyne_data *mnemosyne_phys;
EXPORT_SYMBOL(mnemosyne_phys);
struct mnemosyne_data *mnemosyne_base;
EXPORT_SYMBOL(mnemosyne_base);
unsigned long long mnemosyne_size;
static atomic_t mnemosync_is_init = ATOMIC_INIT(0);
struct mnemosyne_meta {
char *name;
int num;
int index; /* start index in raw data */
};
#undef DECLARE_MNEMOSYNE_START
#undef DECLARE_MNEMOSYNE_END
#undef DECLARE_MNEMOSYNE
#undef DECLARE_MNEMOSYNE_ARRAY
#define DECLARE_MNEMOSYNE_START() struct mnemosyne_meta mnemosyne_meta_data[] = {
#define DECLARE_MNEMOSYNE_END() };
#define DECLARE_MNEMOSYNE_ARRAY(meta_name, meta_num) { \
.name = #meta_name, \
.num = meta_num, \
.index = -1, \
},
#define DECLARE_MNEMOSYNE(meta_name) DECLARE_MNEMOSYNE_ARRAY(meta_name, 1)
#include <htc_mnemosyne/htc_mnemosyne_footprint.inc>
struct mnemosyne_data *mnemosyne_get_base(void)
{
return mnemosyne_base;
}
EXPORT_SYMBOL(mnemosyne_get_base);
static void *mnemosyne_iomap(phys_addr_t phys, phys_addr_t size)
{
if (!request_mem_region(phys, size, MNEMOSYNE_MODULE_NAME)) {
pr_err("%s: request mem region (0x%llx@0x%llx) failed\n", MNEMOSYNE_MODULE_NAME, (unsigned long long)size, (unsigned long long)phys);
return NULL;
}
return ioremap(phys, size);
}
static int mnemosyne_setup(phys_addr_t phys, phys_addr_t size)
{
int i, index;
if (atomic_read(&mnemosync_is_init)) {
if ((phys_addr_t)mnemosyne_phys != phys || (phys_addr_t)mnemosyne_size != size)
WARN(1, "%s: init again with different old/new phys=0x%016llx/0x%016llx, old/new size=0x%016llx/0x%016llx!\n",
MNEMOSYNE_MODULE_NAME,
(phys_addr_t) mnemosyne_phys, phys,
(phys_addr_t) mnemosyne_size, size);
return 0;
}
/* dynamic map virtual address if base is not pre-set. */
mnemosyne_phys = (struct mnemosyne_data *) phys;
mnemosyne_base = (struct mnemosyne_data *) mnemosyne_iomap(phys, size);
mnemosyne_size = size;
if (!mnemosyne_base) {
pr_err("%s: cannot map physical address 0x%llx size %llu to a valid virtual address.\n", MNEMOSYNE_MODULE_NAME, phys, size);
return -ENOMEM;
}
if (sizeof(struct mnemosyne_data) > mnemosyne_size) {
pr_err("%s: mnemosyne data size (%lu) exceeds reserved region size (%llu).\n",
MNEMOSYNE_MODULE_NAME, sizeof(struct mnemosyne_data), mnemosyne_size);
return -ENOMEM;
}
memset(mnemosyne_base, INVALID_MAGIC, sizeof(struct mnemosyne_data));
pr_info("%s: phys: 0x%p\n", MNEMOSYNE_MODULE_NAME, mnemosyne_phys);
pr_info("%s: base: 0x%p\n", MNEMOSYNE_MODULE_NAME, mnemosyne_base);
pr_info("%s: size: %llu (%lu used)\n", MNEMOSYNE_MODULE_NAME, mnemosyne_size, sizeof(struct mnemosyne_data));
pr_info("%s: element info: %lu/%d/%d\n", MNEMOSYNE_MODULE_NAME,
sizeof(MNEMOSYNE_ELEMENT_TYPE),
MNEMOSYNE_ELEMENT_SIZE,
MNEMOSYNE_ELEMENT_SIZE_BIT_SHIFT);
pr_info("%s: init success.\n", MNEMOSYNE_MODULE_NAME);
/* fill start index of each footprint to save query time */
for (i=0, index=0; i<sizeof(mnemosyne_meta_data)/sizeof(struct mnemosyne_meta); i++) {
mnemosyne_meta_data[i].index = index;
index += mnemosyne_meta_data[i].num;
}
atomic_inc(&mnemosync_is_init);
return 0;
}
static int mnemosyne_parse_dt(struct device_node *node)
{
phys_addr_t phys = 0, size = 0;
//struct device_node *pnode = NULL;
int ret;
int i = 0, of_ret = 0;
char* mnemosyne_resource_name = "htc_mnemosyne_res";
struct resource r;
pr_info("%s: init from device tree.\n", MNEMOSYNE_MODULE_NAME);
if (node == NULL) {
pr_err("%s: Can't find device_node", MNEMOSYNE_MODULE_NAME);
ret = -ENODEV;
goto PARSE_DT_ERR_OUT;
}
/* Start DTB searching */
for(i = 0 ; (of_ret = of_address_to_resource(node, i, &r)) == 0 ; i++) {
if(!strcmp(mnemosyne_resource_name, r.name))
break;
}
if(of_ret) {
printk("couldn't found resource \n");
return 1;
}
/* End of DTB Searching */
size = resource_size(&r);
phys = r.start;
pr_info("mnemosyne size: 0x%x\n", (unsigned int)size);
pr_info("mnemosyne phys: 0x%x\n", (unsigned int)phys);
ret = mnemosyne_setup(phys, size);
PARSE_DT_ERR_OUT:
return ret;
}
static int mnemosyne_parse_pdata(struct device* dev)
{
int ret = 0;
struct mnemosyne_platform_data *pdata = (struct mnemosyne_platform_data *)dev->platform_data;
pr_info("%s: init from platform data.\n", MNEMOSYNE_MODULE_NAME);
if (pdata == NULL) {
pr_err("%s: No pdata\n", MNEMOSYNE_MODULE_NAME);
ret = -ENODEV;
goto PARSE_PDATA_ERR_OUT;
}
ret = mnemosyne_setup(pdata->phys, pdata->size);
PARSE_PDATA_ERR_OUT:
return ret;
}
#ifdef CONFIG_DEBUG_FS
static ssize_t is_init_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
char temp_buf[32];
int size = 0;
size = sprintf(temp_buf, "%d\n", atomic_read(&mnemosync_is_init));
return simple_read_from_buffer(buf, count, ppos, temp_buf, size);
}
static const struct file_operations is_init_fops = {
.read = is_init_read,
};
static void* rawdata_seq_start(struct seq_file *sfile, loff_t *pos)
{
/* early return for non-init driver. */
if (atomic_read(&mnemosync_is_init) == 0) {
pr_warn("%s: not init!\n", MNEMOSYNE_MODULE_NAME);
return NULL;
}
if (*pos < 0)
*pos = 0;
else if (*pos >= sizeof(mnemosyne_meta_data)/sizeof(struct mnemosyne_meta))
return NULL;
return pos;
}
static void* rawdata_seq_next(struct seq_file *sfile, void *v, loff_t *pos)
{
++*pos;
if (*pos >= sizeof(mnemosyne_meta_data)/sizeof(struct mnemosyne_meta))
return NULL;
return pos;
}
static void rawdata_seq_stop(struct seq_file *sfile, void *v)
{
return;
}
static int rawdata_seq_show(struct seq_file *sfile, void *v)
{
uint32_t pos = *(uint32_t *)v;
struct mnemosyne_meta* meta = (struct mnemosyne_meta*)mnemosyne_meta_data;
struct mnemosyne_data *data = mnemosyne_base;
MNEMOSYNE_ELEMENT_TYPE *rawdata = (MNEMOSYNE_ELEMENT_TYPE *)data;
int i;
for (i=0, rawdata+=meta[pos].index; i<meta[pos].num; i++) {
seq_printf(sfile, "%s", meta[pos].name);
if (meta[pos].num > 1)
seq_printf(sfile, "[%d]", i);
seq_printf(sfile, ": 0x%08llx = %llu\n", rawdata[i], rawdata[i]);
}
return 0;
}
static struct seq_operations rawdata_seq_ops = {
.start = rawdata_seq_start,
.next = rawdata_seq_next,
.stop = rawdata_seq_stop,
.show = rawdata_seq_show
};
static int rawdata_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &rawdata_seq_ops);
}
static const struct file_operations rawdata_fops = {
.open = rawdata_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
.owner = THIS_MODULE,
};
static struct dentry *base_dir;
static int mnemosyne_debugfs_setup(void)
{
base_dir = debugfs_create_dir(MNEMOSYNE_MODULE_NAME, NULL);
if (!base_dir) {
pr_err("%s: create debugfs dir failed\n", MNEMOSYNE_MODULE_NAME);
return -EIO;
}
debugfs_create_file("is_init", S_IRUGO, base_dir, NULL, &is_init_fops);
debugfs_create_file("rawdata", S_IRUGO, base_dir, NULL, &rawdata_fops);
return 0;
}
#endif
static int mnemosyne_probe(struct platform_device *pdev)
{
if (pdev->dev.of_node) {
if (mnemosyne_parse_dt(pdev->dev.of_node)) {
pr_err("%s: parse device tree fail.\n", MNEMOSYNE_MODULE_NAME);
return -ENODEV;
}
} else {
if (mnemosyne_parse_pdata(&pdev->dev)) {
pr_err("%s: parse pdata fail.\n", MNEMOSYNE_MODULE_NAME);
return -ENODEV;
}
}
return 0;
}
static int mnemosyne_remove(struct platform_device *pdev)
{
return 0;
}
static struct of_device_id mnemosyne_match[] = {
{ .compatible = MNEMOSYNE_DT_MATCH,},
{},
};
static struct platform_driver mnemosyne_driver = {
.probe = mnemosyne_probe,
.remove = mnemosyne_remove,
.driver = {
.name = MNEMOSYNE_MODULE_NAME,
.owner = THIS_MODULE,
.of_match_table = mnemosyne_match,
},
};
static int __init mnemosyne_init(void)
{
#ifdef CONFIG_DEBUG_FS
mnemosyne_debugfs_setup();
#endif
return platform_driver_register(&mnemosyne_driver);
}
/* do not move it before or equal to core_init, because we need /sys/kernel for SYSFS node. */
arch_initcall(mnemosyne_init);
int mnemosyne_is_ready(void)
{
return (!!atomic_read(&mnemosync_is_init));
}
/* If there are footprints written before smp_init, do init on driver detection are too late. */
int __init mnemosyne_early_init(void)
{
struct device_node *np;
pr_info("%s: init from early init.\n", MNEMOSYNE_MODULE_NAME);
np = of_find_node_by_name(NULL, MNEMOSYNE_DT_NAME);
if (!np) {
pr_warn("%s: cannot find mnemosyne device node named %s.\n", MNEMOSYNE_MODULE_NAME, MNEMOSYNE_DT_NAME);
return -ENODEV;
}
return mnemosyne_parse_dt(np);
}
early_initcall(mnemosyne_early_init);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Jimmy Chen <jimmy.cm_chen@htc.com>");
MODULE_DESCRIPTION("HTC Footprint driver");
MODULE_VERSION("1.1");
MODULE_ALIAS("platform:mnemosyne");