Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 82af5378 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "coresight: add qpdi driver support in upstream implementation"

parents da3de889 8ead7737
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -155,6 +155,14 @@ 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.

config CORESIGHT_HWEVENT
	bool "CoreSight Hardware Event driver"
	depends on CORESIGHT_STM
+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ obj-$(CONFIG_CORESIGHT_TPDA) += coresight-tpda.o
obj-$(CONFIG_CORESIGHT_TPDM) += coresight-tpdm.o
obj-$(CONFIG_CORESIGHT_EVENT) += coresight-event.o
obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o
obj-$(CONFIG_CORESIGHT_QPDI) += coresight-qpdi.o
obj-$(CONFIG_CORESIGHT_CSR) += coresight-csr.o
obj-$(CONFIG_CORESIGHT_HWEVENT) += coresight-hwevent.o
obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o
+2 −1
Original line number Diff line number Diff line
/* Copyright (c) 2011-2012, 2017, The Linux Foundation. All rights reserved.
/* Copyright (c) 2011-2012, 2017, 2020, The Linux Foundation. All rights reserved.
 *
 * Description: CoreSight Funnel driver
 *
@@ -253,6 +253,7 @@ static int funnel_probe(struct amba_device *adev, const struct amba_id *id)
	if (IS_ERR(drvdata->csdev))
		return PTR_ERR(drvdata->csdev);

	dev_info(drvdata->dev, "FUNNEL initialized\n");
	return 0;
}

+407 −0
Original line number Diff line number Diff line
/* Copyright (c) 2014-2016, 2020, 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, 0444
);

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			skip_ldo;
	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->skip_ldo) {
		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->skip_ldo)
		__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, 0644, 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;

	drvdata->skip_ldo = of_property_read_bool(node, "qcom,skip-ldo");
	if (drvdata->skip_ldo)
		return 0;

	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;
		}

		drvdata->skip_ldo = 1;
		return 0;
	}

	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");
	}

	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;

	dev_info(dev, "CoreSight QPDI start\n");
	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");