blob: fd1fc2b21abc88d2e447cefea29d8e4d472a0215 [file] [log] [blame]
/* Copyright (c) 2012, Code Aurora Forum. 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/export.h>
#include <mach/rpm.h>
#include "rpm_resources.h"
#include "qdss-priv.h"
#define MAX_STR_LEN (65535)
enum {
QDSS_CLK_OFF,
QDSS_CLK_ON_DBG,
QDSS_CLK_ON_HSDBG,
};
/*
* Exclusion rules for structure fields.
*
* S: qdss.sources_mutex protected.
* I: qdss.sink_mutex protected.
* C: qdss.clk_mutex protected.
*/
struct qdss_ctx {
struct kobject *modulekobj;
struct msm_qdss_platform_data *pdata;
struct list_head sources; /* S: sources list */
struct mutex sources_mutex;
uint8_t sink_count; /* I: sink count */
struct mutex sink_mutex;
uint8_t max_clk;
uint8_t clk_count; /* C: clk count */
struct mutex clk_mutex;
};
static struct qdss_ctx qdss;
/**
* qdss_get - get the qdss source handle
* @name: name of the qdss source
*
* Searches the sources list to get the qdss source handle for this source.
*
* CONTEXT:
* Typically called from init or probe functions
*
* RETURNS:
* pointer to struct qdss_source on success, %NULL on failure
*/
struct qdss_source *qdss_get(const char *name)
{
struct qdss_source *src, *source = NULL;
mutex_lock(&qdss.sources_mutex);
list_for_each_entry(src, &qdss.sources, link) {
if (src->name) {
if (strncmp(src->name, name, MAX_STR_LEN))
continue;
source = src;
break;
}
}
mutex_unlock(&qdss.sources_mutex);
return source ? source : ERR_PTR(-ENOENT);
}
EXPORT_SYMBOL(qdss_get);
/**
* qdss_put - release the qdss source handle
* @name: name of the qdss source
*
* CONTEXT:
* Typically called from driver remove or exit functions
*/
void qdss_put(struct qdss_source *src)
{
}
EXPORT_SYMBOL(qdss_put);
/**
* qdss_enable - enable qdss for the source
* @src: handle for the source making the call
*
* Enables qdss block (relevant funnel ports and sink) if not already
* enabled, otherwise increments the reference count
*
* CONTEXT:
* Might sleep. Uses a mutex lock. Should be called from a non-atomic context.
*
* RETURNS:
* 0 on success, non-zero on failure
*/
int qdss_enable(struct qdss_source *src)
{
int ret;
if (!src)
return -EINVAL;
ret = qdss_clk_enable();
if (ret)
goto err;
if ((qdss.pdata)->afamily) {
mutex_lock(&qdss.sink_mutex);
if (qdss.sink_count == 0) {
etb_disable();
tpiu_disable();
/* enable ETB first to avoid losing any trace data */
etb_enable();
}
qdss.sink_count++;
mutex_unlock(&qdss.sink_mutex);
}
funnel_enable(0x0, src->fport_mask);
return 0;
err:
return ret;
}
EXPORT_SYMBOL(qdss_enable);
/**
* qdss_disable - disable qdss for the source
* @src: handle for the source making the call
*
* Disables qdss block (relevant funnel ports and sink) if the reference count
* is one, otherwise decrements the reference count
*
* CONTEXT:
* Might sleep. Uses a mutex lock. Should be called from a non-atomic context.
*/
void qdss_disable(struct qdss_source *src)
{
if (!src)
return;
if ((qdss.pdata)->afamily) {
mutex_lock(&qdss.sink_mutex);
if (WARN(qdss.sink_count == 0, "qdss is unbalanced\n"))
goto out;
if (qdss.sink_count == 1) {
etb_dump();
etb_disable();
}
qdss.sink_count--;
mutex_unlock(&qdss.sink_mutex);
}
funnel_disable(0x0, src->fport_mask);
qdss_clk_disable();
return;
out:
mutex_unlock(&qdss.sink_mutex);
}
EXPORT_SYMBOL(qdss_disable);
/**
* qdss_disable_sink - force disable the current qdss sink(s)
*
* Force disable the current qdss sink(s) to stop the sink from accepting any
* trace generated subsequent to this call. This function should only be used
* as a way to stop the sink from getting polluted with trace data that is
* uninteresting after an event of interest has occured.
*
* CONTEXT:
* Can be called from atomic or non-atomic context.
*/
void qdss_disable_sink(void)
{
if ((qdss.pdata)->afamily) {
etb_dump();
etb_disable();
}
}
EXPORT_SYMBOL(qdss_disable_sink);
/**
* qdss_clk_enable - enable qdss clocks
*
* Enables qdss clocks via RPM if they aren't already enabled, otherwise
* increments the reference count.
*
* CONTEXT:
* Might sleep. Uses a mutex lock. Should be called from a non-atomic context.
*
* RETURNS:
* 0 on success, non-zero on failure
*/
int qdss_clk_enable(void)
{
int ret;
struct msm_rpm_iv_pair iv;
mutex_lock(&qdss.clk_mutex);
if (qdss.clk_count == 0) {
iv.id = MSM_RPM_ID_QDSS_CLK;
if (qdss.max_clk)
iv.value = QDSS_CLK_ON_HSDBG;
else
iv.value = QDSS_CLK_ON_DBG;
ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, &iv, 1);
if (WARN(ret, "qdss clks not enabled (%d)\n", ret))
goto err_clk;
}
qdss.clk_count++;
mutex_unlock(&qdss.clk_mutex);
return 0;
err_clk:
mutex_unlock(&qdss.clk_mutex);
return ret;
}
EXPORT_SYMBOL(qdss_clk_enable);
/**
* qdss_clk_disable - disable qdss clocks
*
* Disables qdss clocks via RPM if the reference count is one, otherwise
* decrements the reference count.
*
* CONTEXT:
* Might sleep. Uses a mutex lock. Should be called from a non-atomic context.
*/
void qdss_clk_disable(void)
{
int ret;
struct msm_rpm_iv_pair iv;
mutex_lock(&qdss.clk_mutex);
if (WARN(qdss.clk_count == 0, "qdss clks are unbalanced\n"))
goto out;
if (qdss.clk_count == 1) {
iv.id = MSM_RPM_ID_QDSS_CLK;
iv.value = QDSS_CLK_OFF;
ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, &iv, 1);
WARN(ret, "qdss clks not disabled (%d)\n", ret);
}
qdss.clk_count--;
out:
mutex_unlock(&qdss.clk_mutex);
}
EXPORT_SYMBOL(qdss_clk_disable);
struct kobject *qdss_get_modulekobj(void)
{
return qdss.modulekobj;
}
#define QDSS_ATTR(name) \
static struct kobj_attribute name##_attr = \
__ATTR(name, S_IRUGO | S_IWUSR, name##_show, name##_store)
static ssize_t max_clk_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
unsigned long val;
if (sscanf(buf, "%lx", &val) != 1)
return -EINVAL;
qdss.max_clk = val;
return n;
}
static ssize_t max_clk_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
unsigned long val = qdss.max_clk;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
QDSS_ATTR(max_clk);
static void __devinit qdss_add_sources(struct qdss_source *srcs, size_t num)
{
mutex_lock(&qdss.sources_mutex);
while (num--) {
list_add_tail(&srcs->link, &qdss.sources);
srcs++;
}
mutex_unlock(&qdss.sources_mutex);
}
static int __init qdss_sysfs_init(void)
{
int ret;
qdss.modulekobj = kset_find_obj(module_kset, KBUILD_MODNAME);
if (!qdss.modulekobj) {
pr_err("failed to find QDSS sysfs module kobject\n");
ret = -ENOENT;
goto err;
}
ret = sysfs_create_file(qdss.modulekobj, &max_clk_attr.attr);
if (ret) {
pr_err("failed to create QDSS sysfs max_clk attribute\n");
goto err;
}
return 0;
err:
return ret;
}
static void __devexit qdss_sysfs_exit(void)
{
sysfs_remove_file(qdss.modulekobj, &max_clk_attr.attr);
}
static int __devinit qdss_probe(struct platform_device *pdev)
{
int ret;
struct qdss_source *src_table;
size_t num_srcs;
mutex_init(&qdss.sources_mutex);
mutex_init(&qdss.clk_mutex);
mutex_init(&qdss.sink_mutex);
if (pdev->dev.platform_data == NULL) {
pr_err("%s: platform data is NULL\n", __func__);
ret = -ENODEV;
goto err_pdata;
}
qdss.pdata = pdev->dev.platform_data;
INIT_LIST_HEAD(&qdss.sources);
src_table = (qdss.pdata)->src_table;
num_srcs = (qdss.pdata)->size;
qdss_add_sources(src_table, num_srcs);
pr_info("QDSS arch initialized\n");
return 0;
err_pdata:
mutex_destroy(&qdss.sink_mutex);
mutex_destroy(&qdss.clk_mutex);
mutex_destroy(&qdss.sources_mutex);
pr_err("QDSS init failed\n");
return ret;
}
static int __devexit qdss_remove(struct platform_device *pdev)
{
qdss_sysfs_exit();
mutex_destroy(&qdss.sink_mutex);
mutex_destroy(&qdss.clk_mutex);
mutex_destroy(&qdss.sources_mutex);
return 0;
}
static struct platform_driver qdss_driver = {
.probe = qdss_probe,
.remove = __devexit_p(qdss_remove),
.driver = {
.name = "msm_qdss",
},
};
static int __init qdss_init(void)
{
return platform_driver_register(&qdss_driver);
}
arch_initcall(qdss_init);
static int __init qdss_module_init(void)
{
int ret;
ret = qdss_sysfs_init();
if (ret)
goto err_sysfs;
pr_info("QDSS module initialized\n");
return 0;
err_sysfs:
return ret;
}
module_init(qdss_module_init);
static void __exit qdss_exit(void)
{
platform_driver_unregister(&qdss_driver);
}
module_exit(qdss_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Qualcomm Debug SubSystem Driver");