coresight: add qpdi driver support in upstream implementation

Add qpdi driver in upstream implementation of Coresight driver.
This change copies coresight-qpdi.c file from drivers/coresight
(commit f9b3004b242c ("msm: pcie: enable/disable PCIe AER from
debugfs"))
to driver/hwtracing/coresight directory.

Change-Id: I8f29f386a31aec1a10a1398e0753a3b4355b3449
Signed-off-by: Shashank Mittal <mittals@codeaurora.org>
This commit is contained in:
Shashank Mittal 2016-03-24 08:46:03 -07:00 committed by Jeevan Shriram
parent 8185b206af
commit 46c271e86b
4 changed files with 412 additions and 0 deletions

View file

@ -48,6 +48,7 @@ its hardware characteristcs.
- "qcom,coresight-tpda"
- "qcom,coresight-tpdm"
- "qcom,coresight-remote-etm"
- "qcom,coresight-qpdi"
* port or ports: same as above.
@ -104,6 +105,11 @@ its hardware characteristcs.
* qcom,inst-id: must be present. QMI instance id for remote ETMs.
* Optional property for QPDI:
* qcom,pmic-carddetect-gpio: indicates the hotplug capabilities of the
qpdi driver
Example:
1. Sinks

View file

@ -146,4 +146,12 @@ config CORESIGHT_TPDM_DEFAULT_ENABLE
If unsure, say 'N' here to avoid potential power and performance
penalty.
config CORESIGHT_QPDI
bool "CoreSight PMIC debug interface support"
help
This driver provides support for controlling the PMIC debug interface
feature. When enabled via sysfs it allows disagnostic access to the
PMIC. Similarly this debug feature can be disabled via sysfs which
prevents debug dongle detection.
endif

View file

@ -17,3 +17,4 @@ obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o
obj-$(CONFIG_CORESIGHT_TPDA) += coresight-tpda.o
obj-$(CONFIG_CORESIGHT_TPDM) += coresight-tpdm.o
obj-$(CONFIG_CORESIGHT_REMOTE_ETM) += coresight-remote-etm.o
obj-$(CONFIG_CORESIGHT_QPDI) += coresight-qpdi.o

View file

@ -0,0 +1,397 @@
/* Copyright (c) 2014-2016, 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/coresight.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include "coresight-priv.h"
#define qpdi_writel(drvdata, val, off) __raw_writel((val), drvdata->base + off)
#define qpdi_readl(drvdata, off) __raw_readl(drvdata->base + off)
#define QPDI_DISABLE_CFG (0x0)
static int boot_enable;
module_param_named(
boot_enable, boot_enable, int, S_IRUGO
);
struct qpdi_drvdata {
void __iomem *base;
struct device *dev;
struct coresight_device *csdev;
struct mutex mutex;
struct regulator *reg;
unsigned int reg_low;
unsigned int reg_high;
unsigned int reg_lpm;
unsigned int reg_hpm;
struct regulator *reg_io;
unsigned int reg_low_io;
unsigned int reg_high_io;
unsigned int reg_lpm_io;
unsigned int reg_hpm_io;
int pmic_gpio_vote;
bool enable;
};
static int qpdi_reg_set_optimum_mode(struct regulator *reg,
unsigned int reg_hpm)
{
if (regulator_count_voltages(reg) <= 0)
return 0;
return regulator_set_load(reg, reg_hpm);
}
static int qpdi_reg_set_voltage(struct regulator *reg, unsigned int reg_low,
unsigned int reg_high)
{
if (regulator_count_voltages(reg) <= 0)
return 0;
return regulator_set_voltage(reg, reg_low, reg_high);
}
static int __qpdi_enable(struct qpdi_drvdata *drvdata)
{
int ret;
if (!drvdata->reg || !drvdata->reg_io)
return -EINVAL;
ret = qpdi_reg_set_optimum_mode(drvdata->reg, drvdata->reg_hpm);
if (ret < 0)
return ret;
ret = qpdi_reg_set_voltage(drvdata->reg, drvdata->reg_low,
drvdata->reg_high);
if (ret)
goto err0;
ret = regulator_enable(drvdata->reg);
if (ret)
goto err1;
ret = qpdi_reg_set_optimum_mode(drvdata->reg_io, drvdata->reg_hpm_io);
if (ret < 0)
goto err2;
ret = qpdi_reg_set_voltage(drvdata->reg_io, drvdata->reg_low_io,
drvdata->reg_high_io);
if (ret)
goto err3;
ret = regulator_enable(drvdata->reg_io);
if (ret)
goto err4;
return 0;
err4:
qpdi_reg_set_voltage(drvdata->reg_io, 0, drvdata->reg_high_io);
err3:
qpdi_reg_set_optimum_mode(drvdata->reg_io, 0);
err2:
regulator_disable(drvdata->reg);
err1:
qpdi_reg_set_voltage(drvdata->reg, 0, drvdata->reg_high);
err0:
qpdi_reg_set_optimum_mode(drvdata->reg, 0);
return ret;
}
static int qpdi_enable(struct qpdi_drvdata *drvdata)
{
int ret;
mutex_lock(&drvdata->mutex);
if (drvdata->enable)
goto out;
if (drvdata->pmic_gpio_vote < 0) {
ret = __qpdi_enable(drvdata);
if (ret)
goto err;
}
qpdi_writel(drvdata, 0x2, QPDI_DISABLE_CFG);
drvdata->enable = true;
dev_info(drvdata->dev, "qpdi enabled\n");
out:
mutex_unlock(&drvdata->mutex);
return 0;
err:
mutex_unlock(&drvdata->mutex);
return ret;
}
static void __qpdi_disable(struct qpdi_drvdata *drvdata)
{
regulator_disable(drvdata->reg);
qpdi_reg_set_voltage(drvdata->reg, 0, drvdata->reg_high);
qpdi_reg_set_optimum_mode(drvdata->reg, 0);
regulator_disable(drvdata->reg_io);
qpdi_reg_set_voltage(drvdata->reg_io, 0, drvdata->reg_high_io);
qpdi_reg_set_optimum_mode(drvdata->reg_io, 0);
}
static void qpdi_disable(struct qpdi_drvdata *drvdata)
{
mutex_lock(&drvdata->mutex);
if (!drvdata->enable) {
mutex_unlock(&drvdata->mutex);
return;
}
qpdi_writel(drvdata, 0x3, QPDI_DISABLE_CFG);
if (drvdata->pmic_gpio_vote < 0)
__qpdi_disable(drvdata);
drvdata->enable = false;
mutex_unlock(&drvdata->mutex);
dev_info(drvdata->dev, "qpdi disabled\n");
}
static ssize_t qpdi_show_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qpdi_drvdata *drvdata = dev_get_drvdata(dev->parent);
unsigned long val = drvdata->enable;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static ssize_t qpdi_store_enable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct qpdi_drvdata *drvdata = dev_get_drvdata(dev->parent);
unsigned long val;
int ret = 0;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
if (val)
ret = qpdi_enable(drvdata);
else
qpdi_disable(drvdata);
if (ret)
return ret;
return size;
}
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, qpdi_show_enable,
qpdi_store_enable);
static struct attribute *qpdi_attrs[] = {
&dev_attr_enable.attr,
NULL,
};
static struct attribute_group qpdi_attr_grp = {
.attrs = qpdi_attrs,
};
static const struct attribute_group *qpdi_attr_grps[] = {
&qpdi_attr_grp,
NULL,
};
static int qpdi_parse_of_data(struct platform_device *pdev,
struct qpdi_drvdata *drvdata)
{
struct device_node *node = pdev->dev.of_node;
struct device_node *reg_node = NULL;
struct device *dev = &pdev->dev;
const __be32 *prop;
int len, ret;
reg_node = of_parse_phandle(node, "vdd-supply", 0);
if (reg_node) {
drvdata->reg = devm_regulator_get(dev, "vdd");
if (IS_ERR(drvdata->reg))
return PTR_ERR(drvdata->reg);
prop = of_get_property(node, "qcom,vdd-voltage-level", &len);
if (!prop || (len != (2 * sizeof(__be32)))) {
dev_err(dev, "sdc voltage levels not specified\n");
} else {
drvdata->reg_low = be32_to_cpup(&prop[0]);
drvdata->reg_high = be32_to_cpup(&prop[1]);
}
prop = of_get_property(node, "qcom,vdd-current-level", &len);
if (!prop || (len != (2 * sizeof(__be32)))) {
dev_err(dev, "sdc current levels not specified\n");
} else {
drvdata->reg_lpm = be32_to_cpup(&prop[0]);
drvdata->reg_hpm = be32_to_cpup(&prop[1]);
}
of_node_put(reg_node);
} else {
dev_err(dev, "sdc voltage supply not specified or available\n");
}
reg_node = of_parse_phandle(node, "vdd-io-supply", 0);
if (reg_node) {
drvdata->reg_io = devm_regulator_get(dev, "vdd-io");
if (IS_ERR(drvdata->reg_io))
return PTR_ERR(drvdata->reg_io);
prop = of_get_property(node, "qcom,vdd-io-voltage-level", &len);
if (!prop || (len != (2 * sizeof(__be32)))) {
dev_err(dev, "sdc io voltage levels not specified\n");
} else {
drvdata->reg_low_io = be32_to_cpup(&prop[0]);
drvdata->reg_high_io = be32_to_cpup(&prop[1]);
}
prop = of_get_property(node, "qcom,vdd-io-current-level", &len);
if (!prop || (len != (2 * sizeof(__be32)))) {
dev_err(dev, "sdc io current levels not specified\n");
} else {
drvdata->reg_lpm_io = be32_to_cpup(&prop[0]);
drvdata->reg_hpm_io = be32_to_cpup(&prop[1]);
}
of_node_put(reg_node);
} else {
dev_err(dev,
"sdc io voltage supply not specified or available\n");
}
drvdata->pmic_gpio_vote = of_get_named_gpio(pdev->dev.of_node,
"qcom,pmic-carddetect-gpio", 0);
if (drvdata->pmic_gpio_vote < 0)
dev_info(dev, "QPDI hotplug card detection is not supported\n");
else {
ret = gpio_request(drvdata->pmic_gpio_vote, "qpdi_gpio_hp");
if (ret) {
dev_err(dev, "failed to allocate the GPIO\n");
return ret;
}
ret = gpio_direction_input(drvdata->pmic_gpio_vote);
if (ret) {
dev_err(dev, "failed to set the gpio to input\n");
gpio_free(drvdata->pmic_gpio_vote);
return ret;
}
}
return 0;
}
static int qpdi_probe(struct platform_device *pdev)
{
int ret;
struct device *dev = &pdev->dev;
struct coresight_platform_data *pdata;
struct qpdi_drvdata *drvdata;
struct resource *res;
struct coresight_desc *desc;
pdata = of_get_coresight_platform_data(dev, pdev->dev.of_node);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
pdev->dev.platform_data = pdata;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
drvdata->dev = &pdev->dev;
platform_set_drvdata(pdev, drvdata);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qpdi-base");
if (!res)
return -ENODEV;
drvdata->base = devm_ioremap(dev, res->start, resource_size(res));
if (!drvdata->base)
return -ENOMEM;
mutex_init(&drvdata->mutex);
ret = qpdi_parse_of_data(pdev, drvdata);
if (ret)
return ret;
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
desc->type = CORESIGHT_DEV_TYPE_NONE;
desc->pdata = pdev->dev.platform_data;
desc->dev = &pdev->dev;
desc->groups = qpdi_attr_grps;
drvdata->csdev = coresight_register(desc);
if (IS_ERR(drvdata->csdev))
return PTR_ERR(drvdata->csdev);
if (boot_enable)
qpdi_enable(drvdata);
dev_info(dev, "CoreSight QPDI driver initialized\n");
return 0;
}
static int qpdi_remove(struct platform_device *pdev)
{
struct qpdi_drvdata *drvdata = platform_get_drvdata(pdev);
if (drvdata->pmic_gpio_vote > -1)
gpio_free(drvdata->pmic_gpio_vote);
coresight_unregister(drvdata->csdev);
return 0;
}
static const struct of_device_id qpdi_match[] = {
{.compatible = "qcom,coresight-qpdi"},
{}
};
static struct platform_driver qpdi_driver = {
.probe = qpdi_probe,
.remove = qpdi_remove,
.driver = {
.name = "coresight-qpdi",
.owner = THIS_MODULE,
.of_match_table = qpdi_match,
},
};
static int __init qpdi_init(void)
{
return platform_driver_register(&qpdi_driver);
}
module_init(qpdi_init);
static void __exit qpdi_exit(void)
{
platform_driver_unregister(&qpdi_driver);
}
module_exit(qpdi_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("CoreSight QPDI driver");