blob: d57ce49cfd93f5d5009411f2eed6fd94130768fe [file] [log] [blame]
/*
* drivers/amlogic/ddr_tool/dmc_monitor.c
*
* Copyright (C) 2017 Amlogic, Inc. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/irqreturn.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/kallsyms.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/page_trace.h>
#include <linux/arm-smccc.h>
#include <linux/amlogic/dmc_monitor.h>
#include <linux/amlogic/ddr_port.h>
static struct dmc_monitor *dmc_mon;
unsigned long dmc_rw(unsigned long addr, unsigned long value, int rw)
{
struct arm_smccc_res smccc;
arm_smccc_smc(DMC_MON_RW, addr + dmc_mon->io_base,
value, rw, 0, 0, 0, 0, &smccc);
return smccc.a0;
}
static int dev_name_to_id(const char *dev_name)
{
int i, len;
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id >= PORT_MAJOR)
return -1;
len = strlen(dmc_mon->port[i].port_name);
if (!strncmp(dmc_mon->port[i].port_name, dev_name, len))
break;
}
if (i >= dmc_mon->port_num)
return -1;
return dmc_mon->port[i].port_id;
}
char *to_ports(int id)
{
int i;
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id == id)
return dmc_mon->port[i].port_name;
}
return NULL;
}
char *to_sub_ports(int mid, int sid, char *id_str)
{
int i;
/* 7 is device port id */
if (mid == 7) {
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id == sid + PORT_MAJOR)
return dmc_mon->port[i].port_name;
}
}
sprintf(id_str, "%2d", sid);
return id_str;
}
unsigned int get_all_dev_mask(void)
{
unsigned int ret = 0;
int i;
for (i = 0; i < PORT_MAJOR; i++) {
if (dmc_mon->port[i].port_id >= PORT_MAJOR)
break;
ret |= (1 << dmc_mon->port[i].port_id);
}
return ret;
}
static unsigned int get_other_dev_mask(void)
{
unsigned int ret = 0;
int i;
for (i = 0; i < PORT_MAJOR; i++) {
if (dmc_mon->port[i].port_id >= PORT_MAJOR)
break;
/*
* we don't want id with arm mali and device
* because these devices can access all ddr range
* and generate value-less report
*/
if (strstr(dmc_mon->port[i].port_name, "ARM") ||
strstr(dmc_mon->port[i].port_name, "MALI") ||
strstr(dmc_mon->port[i].port_name, "DEVICE"))
continue;
ret |= (1 << dmc_mon->port[i].port_id);
}
return ret;
}
static size_t dump_reg(char *buf)
{
size_t sz = 0, i;
if (dmc_mon->ops && dmc_mon->ops->dump_reg)
sz += dmc_mon->ops->dump_reg(buf);
sz += sprintf(buf + sz, "IO_BASE:%lx\n", dmc_mon->io_base);
sz += sprintf(buf + sz, "RANGE:%lx - %lx\n",
dmc_mon->addr_start, dmc_mon->addr_end);
sz += sprintf(buf + sz, "MONITOR DEVICE:\n");
for (i = 0; i < sizeof(dmc_mon->device) * 8; i++) {
if (dmc_mon->device & (1 << i))
sz += sprintf(buf + sz, " %s\n", to_ports(i));
}
return sz;
}
static irqreturn_t dmc_monitor_irq_handler(int irq, void *dev_instance)
{
if (dmc_mon->ops && dmc_mon->ops->handle_irq)
dmc_mon->ops->handle_irq(dmc_mon);
return IRQ_HANDLED;
}
static void clear_irq_work(struct work_struct *work)
{
/*
* DMC VIOLATION may happen very quickly and irq re-generated
* again before CPU leave IRQ mode, once this scenario happened,
* DMC protection would not generate IRQ again until we cleared
* it manually.
* Since no parameters used for irq handler, so we just call IRQ
* handler again to save code size.
*/
dmc_monitor_irq_handler(0, NULL);
schedule_delayed_work(&dmc_mon->work, HZ);
}
int dmc_set_monitor(unsigned long start, unsigned long end,
unsigned long dev_mask, int en)
{
if (!dmc_mon)
return -EINVAL;
dmc_mon->addr_start = start;
dmc_mon->addr_end = end;
if (en)
dmc_mon->device |= dev_mask;
else
dmc_mon->device &= ~(dev_mask);
if (start < end && dmc_mon->ops && dmc_mon->ops->set_montor)
return dmc_mon->ops->set_montor(dmc_mon);
return -EINVAL;
}
EXPORT_SYMBOL(dmc_set_monitor);
int dmc_set_monitor_by_name(unsigned long start, unsigned long end,
const char *port_name, int en)
{
long id;
id = dev_name_to_id(port_name);
if (id < 0 || id >= BITS_PER_LONG)
return -EINVAL;
return dmc_set_monitor(start, end, 1UL << id, en);
}
EXPORT_SYMBOL(dmc_set_monitor_by_name);
void dmc_monitor_disable(void)
{
if (dmc_mon->ops && dmc_mon->ops->disable)
return dmc_mon->ops->disable(dmc_mon);
}
EXPORT_SYMBOL(dmc_monitor_disable);
static ssize_t protect_range_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%08lx - %08lx\n",
dmc_mon->addr_start, dmc_mon->addr_end);
}
static ssize_t protect_range_store(struct class *cla,
struct class_attribute *attr, const char *buf, size_t count)
{
int ret;
unsigned long start, end;
ret = sscanf(buf, "%lx %lx", &start, &end);
if (ret != 2) {
pr_info("%s, bad input:%s\n", __func__, buf);
return count;
}
dmc_set_monitor(start, end, dmc_mon->device, 1);
return count;
}
static ssize_t dev_store(struct class *cla,
struct class_attribute *attr, const char *buf, size_t count)
{
int i;
if (!strncmp(buf, "none", 4)) {
dmc_monitor_disable();
return count;
}
if (!strncmp(buf, "all", 3))
dmc_mon->device = get_all_dev_mask();
else if (!strncmp(buf, "other", 5))
dmc_mon->device = get_other_dev_mask();
else {
i = dev_name_to_id(buf);
if (i < 0) {
pr_info("bad device:%s\n", buf);
return -EINVAL;
}
dmc_mon->device |= (1 << i);
}
dmc_set_monitor(dmc_mon->addr_start, dmc_mon->addr_end,
dmc_mon->device, 1);
return count;
}
static ssize_t dev_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
int i, s = 0;
s += sprintf(buf + s, "supported device:\n");
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id >= PORT_MAJOR)
break;
s += sprintf(buf + s, "%2d:%s\n",
dmc_mon->port[i].port_id,
dmc_mon->port[i].port_name);
}
return s;
}
static ssize_t dump_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return dump_reg(buf);
}
static struct class_attribute dmc_monitor_attr[] = {
__ATTR(range, 0664, protect_range_show, protect_range_store),
__ATTR(device, 0664, dev_show, dev_store),
__ATTR_RO(dump),
__ATTR_NULL
};
static struct class dmc_monitor_class = {
.name = "dmc_monitor",
.class_attrs = dmc_monitor_attr,
};
static int dmc_monitor_probe(struct platform_device *pdev)
{
int r = 0, irq, ports;
unsigned int io;
struct device_node *node = pdev->dev.of_node;
struct ddr_port_desc *desc = NULL;
pr_info("%s\n", __func__);
r = get_cpu_type();
dmc_mon = kzalloc(sizeof(struct dmc_monitor), GFP_KERNEL);
if (!dmc_mon)
return -ENOMEM;
ports = ddr_find_port_desc(r, &desc);
if (ports < 0) {
pr_info("can't get port desc\n");
goto inval;
}
dmc_mon->chip = r;
dmc_mon->port_num = ports;
dmc_mon->port = desc;
if (dmc_mon->chip >= MESON_CPU_MAJOR_ID_G12A)
dmc_mon->ops = &g12_dmc_mon_ops;
else
dmc_mon->ops = &gx_dmc_mon_ops;
r = of_property_read_u32(node, "reg_base", &io);
if (r < 0) {
pr_info("can't find iobase\n");
goto inval;
}
dmc_mon->io_base = io;
irq = of_irq_get(node, 0);
r = request_irq(irq, dmc_monitor_irq_handler,
IRQF_SHARED, "dmc_monitor", pdev);
if (r < 0) {
pr_info("request irq failed:%d, r:%d\n", irq, r);
goto inval;
}
r = class_register(&dmc_monitor_class);
if (r) {
pr_err("regist dmc_monitor_class failed\n");
goto inval;
}
INIT_DELAYED_WORK(&dmc_mon->work, clear_irq_work);
schedule_delayed_work(&dmc_mon->work, HZ);
return 0;
inval:
kfree(dmc_mon);
dmc_mon = NULL;
return -EINVAL;
}
static int dmc_monitor_remove(struct platform_device *pdev)
{
cancel_delayed_work_sync(&dmc_mon->work);
class_unregister(&dmc_monitor_class);
kfree(dmc_mon);
dmc_mon = NULL;
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id dmc_monitor_match[] = {
{
.compatible = "amlogic, dmc_monitor",
},
{}
};
#endif
static struct platform_driver dmc_monitor_driver = {
.driver = {
.name = "dmc_monitor",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = dmc_monitor_match,
#endif
},
.probe = dmc_monitor_probe,
.remove = dmc_monitor_remove,
};
static int __init dmc_monitor_init(void)
{
int ret;
ret = platform_driver_register(&dmc_monitor_driver);
return ret;
}
static void __exit dmc_monitor_exit(void)
{
platform_driver_unregister(&dmc_monitor_driver);
}
module_init(dmc_monitor_init);
module_exit(dmc_monitor_exit);
MODULE_DESCRIPTION("amlogic dmc monitor driver");
MODULE_LICENSE("GPL");