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

Commit a90d253c authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "misc: Add snapshot of QPNP misc driver"

parents 26745e9a 6f3eff93
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
QPNP-MISC

QPNP-MISC provides a way to read the PMIC part number and revision.

Required properties:
- compatible : should be "qcom,qpnp-misc"
- reg : offset and length of the PMIC peripheral register map.

Example:
	qcom,spmi@fc4c0000 {
		#address-cells = <1>;
		#size-cells = <0>;
		interrupt-controller;
		#interrupt-cells = <3>;

		qcom,pm8941@0 {
			spmi-slave-container;
			reg = <0x0>;
			#address-cells = <1>;
			#size-cells = <1>;

			qcom,misc@900 {
				compatible = "qcom,qpnp-misc";
				reg = <0x900 0x100>;
			};
		}
	};
+9 −0
Original line number Diff line number Diff line
@@ -572,6 +572,15 @@ config TI_DRV2667
	  To compile this driver as a module, choose M here: the
	  module will be called ti_drv2667.

config QPNP_MISC
	tristate "QPNP Misc Peripheral"
	depends on SPMI
	help
	  Say 'y' here to include support for the QTI QPNP MISC
	  peripheral. The MISC peripheral holds the USB ID interrupt
	  and the driver provides an API to check if this interrupt
	  is available on the current PMIC chip.

source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
+1 −0
Original line number Diff line number Diff line
@@ -67,3 +67,4 @@ obj-y += qcom/
obj-$(CONFIG_TI_DRV2667) += ti_drv2667.o
obj-$(CONFIG_USB_EXT_TYPE_C_PERICOM)	+= type-c-pericom.o
obj-$(CONFIG_USB_EXT_TYPE_C_TI)		+= type-c-ti.o
obj-$(CONFIG_QPNP_MISC) 	+= qpnp-misc.o
+187 −0
Original line number Diff line number Diff line
/* Copyright (c) 2013-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.
 */
#define pr_fmt(fmt)	"%s: " fmt, __func__

#include <linux/module.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/spmi.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/qpnp-misc.h>

#define QPNP_MISC_DEV_NAME "qcom,qpnp-misc"

#define REG_DIG_MAJOR_REV	0x01
#define REG_SUBTYPE		0x05

static DEFINE_MUTEX(qpnp_misc_dev_list_mutex);
static LIST_HEAD(qpnp_misc_dev_list);

/**
 * struct qpnp_misc_dev - holds controller device specific information
 * @list:			Doubly-linked list parameter linking to other
 *				qpnp_misc devices.
 * @mutex:			Mutex lock that is used to ensure mutual
 *				exclusion between probing and accessing misc
 *				driver information
 * @dev:			Device pointer to the misc device
 * @resource:			Resource pointer that holds base address
 * @spmi:			Spmi pointer which holds spmi information
 */
struct qpnp_misc_dev {
	struct list_head		list;
	struct mutex			mutex;
	struct device			*dev;
	struct resource			*resource;
	struct spmi_device		*spmi;
};

struct qpnp_misc_version {
	u8				subtype;
	u8				dig_major_rev;
};

static struct of_device_id qpnp_misc_match_table[] = {
	{ .compatible = QPNP_MISC_DEV_NAME },
	{}
};

static u8 qpnp_read_byte(struct spmi_device *spmi, u16 addr)
{
	int rc;
	u8 val;

	rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, &val, 1);
	if (rc) {
		pr_err("SPMI read failed rc=%d\n", rc);
		return 0;
	}
	return val;
}

static struct qpnp_misc_version irq_support_version[] = {
	{0x01, 0x02}, /* PM8941 */
	{0x07, 0x00}, /* PM8226 */
	{0x09, 0x00}, /* PMA8084 */
};

static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
{
	int i;
	u8 subtype, dig_major_rev;

	subtype = qpnp_read_byte(dev->spmi, dev->resource->start + REG_SUBTYPE);
	pr_debug("subtype = 0x%02X\n", subtype);

	dig_major_rev = qpnp_read_byte(dev->spmi,
		dev->resource->start + REG_DIG_MAJOR_REV);
	pr_debug("dig_major rev = 0x%02X\n", dig_major_rev);

	for (i = 0; i < ARRAY_SIZE(irq_support_version); i++)
		if (subtype == irq_support_version[i].subtype
		    && dig_major_rev >= irq_support_version[i].dig_major_rev)
			return 1;

	return 0;
}

int qpnp_misc_irqs_available(struct device *consumer_dev)
{
	struct device_node *misc_node = NULL;
	struct qpnp_misc_dev *mdev = NULL;
	struct qpnp_misc_dev *mdev_found = NULL;

	if (IS_ERR_OR_NULL(consumer_dev)) {
		pr_err("Invalid consumer device pointer\n");
		return -EINVAL;
	}

	misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0);
	if (!misc_node) {
		pr_debug("Could not find qcom,misc-ref property in %s\n",
			consumer_dev->of_node->full_name);
		return 0;
	}

	mutex_lock(&qpnp_misc_dev_list_mutex);
	list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
		if (mdev->dev->of_node == misc_node) {
			mdev_found = mdev;
			break;
		}
	}
	mutex_unlock(&qpnp_misc_dev_list_mutex);

	if (!mdev_found) {
		/* No MISC device was found. This API should only
		 * be called by drivers which have specified the
		 * misc phandle in their device tree node */
		pr_err("no probed misc device found\n");
		return -EPROBE_DEFER;
	}

	return __misc_irqs_available(mdev_found);
}

static int qpnp_misc_probe(struct spmi_device *spmi)
{
	struct resource *resource;
	struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);

	resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
	if (!resource) {
		pr_err("Unable to get spmi resource for MISC\n");
		return -EINVAL;
	}

	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
	if (!mdev)
		return -ENOMEM;

	mdev->spmi = spmi;
	mdev->dev = &(spmi->dev);
	mdev->resource = resource;

	mutex_lock(&qpnp_misc_dev_list_mutex);
	list_add_tail(&mdev->list, &qpnp_misc_dev_list);
	mutex_unlock(&qpnp_misc_dev_list_mutex);

	pr_debug("probed successfully\n");
	return 0;
}

static struct spmi_driver qpnp_misc_driver = {
	.probe	= qpnp_misc_probe,
	.driver	= {
		.name		= QPNP_MISC_DEV_NAME,
		.owner		= THIS_MODULE,
		.of_match_table	= qpnp_misc_match_table,
	},
};

static int __init qpnp_misc_init(void)
{
	return spmi_driver_register(&qpnp_misc_driver);
}

static void __exit qpnp_misc_exit(void)
{
	return spmi_driver_unregister(&qpnp_misc_driver);
}

module_init(qpnp_misc_init);
module_exit(qpnp_misc_exit);

MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME);
+38 −0
Original line number Diff line number Diff line
/* Copyright (c) 2013-2014, 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.
 */

#ifndef __QPNP_MISC_H
#define __QPNP_MISC_H

#include <linux/errno.h>

#ifdef CONFIG_QPNP_MISC
/**
 * qpnp_misc_irqs_available - check if IRQs are available
 *
 * @consumer_dev: device struct
 *
 * This function returns true if the MISC interrupts are available
 * based on a check in the MISC peripheral revision registers.
 *
 * Any consumer of this function needs to reference a MISC device phandle
 * using the "qcom,misc-ref" property in their device tree node.
 */

int qpnp_misc_irqs_available(struct device *consumer_dev);
#else
static int qpnp_misc_irqs_available(struct device *consumer_dev)
{
	return 0;
}
#endif
#endif