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

Commit 22ce284f authored by Fenglin Wu's avatar Fenglin Wu
Browse files

misc: qpnp-misc: Add a snapshot of QPNP MISC driver



This is the snapshot of QPNP MISC driver taken as of msm-4.9 commit
463d8c28b7c3 ("Merge "pinctrl: msm: Add pinctrl driver support for
msm8909"").

Change-Id: Ibf21355bfa5f959de19b4a028ff3812511573442
Signed-off-by: default avatarFenglin Wu <fenglinw@codeaurora.org>
parent d6691876
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -544,6 +544,15 @@ config MEMORY_STATE_TIME
	help
	  Memory time statistics exported to /sys/kernel/memory_state_time

config QPNP_MISC
	tristate "QPNP Misc Peripheral"
	depends on MFD_SPMI_PMIC
	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
@@ -62,6 +62,7 @@ obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o

obj-$(CONFIG_UID_SYS_STATS)	+= uid_sys_stats.o
obj-$(CONFIG_MEMORY_STATE_TIME)	+= memory_state_time.o
obj-$(CONFIG_QPNP_MISC) 	+= qpnp-misc.o

lkdtm-$(CONFIG_LKDTM)		+= lkdtm_core.o
lkdtm-$(CONFIG_LKDTM)		+= lkdtm_bugs.o
+352 −0
Original line number Diff line number Diff line
/* Copyright (c) 2013-2014,2016-2017, 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/regmap.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/qpnp/qpnp-misc.h>

#define QPNP_MISC_DEV_NAME "qcom,qpnp-misc"

#define REG_DIG_MAJOR_REV	0x01
#define REG_SUBTYPE		0x05
#define REG_PWM_SEL		0x49
#define REG_GP_DRIVER_EN	0x4C

#define PWM_SEL_MAX		0x03
#define GP_DRIVER_EN_BIT	BIT(0)

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

struct qpnp_misc_version {
	u8	subtype;
	u8	dig_major_rev;
};

/**
 * 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
 * @regmap:			Regmap pointer to the misc device
 * @version:			struct that holds the subtype and dig_major_rev
 *				of the chip.
 */
struct qpnp_misc_dev {
	struct list_head		list;
	struct mutex			mutex;
	struct device			*dev;
	struct regmap			*regmap;
	struct qpnp_misc_version	version;

	u32				base;
	u8				pwm_sel;
	bool				enable_gp_driver;
};

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

enum qpnp_misc_version_name {
	INVALID,
	PM8941,
	PM8226,
	PMA8084,
	PMDCALIFORNIUM,
};

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

static int qpnp_write_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 val)
{
	int rc;

	rc = regmap_write(mdev->regmap, mdev->base + addr, val);
	if (rc)
		pr_err("regmap write failed rc=%d\n", rc);

	return rc;
}

static int qpnp_read_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 *val)
{
	unsigned int temp;
	int rc;

	rc = regmap_read(mdev->regmap, mdev->base + addr, &temp);
	if (rc) {
		pr_err("regmap read failed rc=%d\n", rc);
		return rc;
	}

	*val = (u8)temp;
	return rc;
}

static int get_qpnp_misc_version_name(struct qpnp_misc_dev *dev)
{
	int i;

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

	return INVALID;
}

static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
{
	int version_name = get_qpnp_misc_version_name(dev);

	if (version_name == INVALID)
		return 0;
	return 1;
}

int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val)
{
	struct qpnp_misc_dev *mdev = NULL;
	struct qpnp_misc_dev *mdev_found = NULL;
	int rc;
	u8 temp = 0;

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

	mutex_lock(&qpnp_misc_dev_list_mutex);
	list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
		if (mdev->dev->of_node == 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;
	}

	rc = qpnp_read_byte(mdev, addr, &temp);
	if (rc < 0) {
		dev_err(mdev->dev, "Failed to read addr %x, rc=%d\n", addr, rc);
		return rc;
	}

	*val = temp;
	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_dt_init(struct qpnp_misc_dev *mdev)
{
	struct device_node *node = mdev->dev->of_node;
	u32 val;
	int rc;

	rc = of_property_read_u32(node, "reg", &mdev->base);
	if (rc < 0 || !mdev->base) {
		dev_err(mdev->dev, "Base address not defined or invalid\n");
		return -EINVAL;
	}

	if (!of_property_read_u32(node, "qcom,pwm-sel", &val)) {
		if (val > PWM_SEL_MAX) {
			dev_err(mdev->dev, "Invalid value for pwm-sel\n");
			return -EINVAL;
		}
		mdev->pwm_sel = (u8)val;
	}
	mdev->enable_gp_driver = of_property_read_bool(node,
						"qcom,enable-gp-driver");

	WARN((mdev->pwm_sel > 0 && !mdev->enable_gp_driver),
			"Setting PWM source without enabling gp driver\n");
	WARN((mdev->pwm_sel == 0 && mdev->enable_gp_driver),
			"Enabling gp driver without setting PWM source\n");

	return 0;
}

static int qpnp_misc_config(struct qpnp_misc_dev *mdev)
{
	int rc, version_name;

	version_name = get_qpnp_misc_version_name(mdev);

	switch (version_name) {
	case PMDCALIFORNIUM:
		if (mdev->pwm_sel > 0 && mdev->enable_gp_driver) {
			rc = qpnp_write_byte(mdev, REG_PWM_SEL, mdev->pwm_sel);
			if (rc < 0) {
				dev_err(mdev->dev,
					"Failed to write PWM_SEL reg\n");
				return rc;
			}

			rc = qpnp_write_byte(mdev, REG_GP_DRIVER_EN,
					GP_DRIVER_EN_BIT);
			if (rc < 0) {
				dev_err(mdev->dev,
					"Failed to write GP_DRIVER_EN reg\n");
				return rc;
			}
		}
		break;
	default:
		break;
	}

	return 0;
}

static int qpnp_misc_probe(struct platform_device *pdev)
{
	struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);
	int rc;

	mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
	if (!mdev)
		return -ENOMEM;

	mdev->dev = &pdev->dev;
	mdev->regmap = dev_get_regmap(mdev->dev->parent, NULL);
	if (!mdev->regmap) {
		dev_err(mdev->dev, "Parent regmap is unavailable\n");
		return -ENXIO;
	}

	rc = qpnp_misc_dt_init(mdev);
	if (rc < 0) {
		dev_err(mdev->dev,
			"Error reading device tree properties, rc=%d\n", rc);
		return rc;
	}


	rc = qpnp_read_byte(mdev, REG_SUBTYPE, &mdev->version.subtype);
	if (rc < 0) {
		dev_err(mdev->dev, "Failed to read subtype, rc=%d\n", rc);
		return rc;
	}

	rc = qpnp_read_byte(mdev, REG_DIG_MAJOR_REV,
			&mdev->version.dig_major_rev);
	if (rc < 0) {
		dev_err(mdev->dev, "Failed to read dig_major_rev, rc=%d\n", rc);
		return rc;
	}

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

	rc = qpnp_misc_config(mdev);
	if (rc < 0) {
		dev_err(mdev->dev,
			"Error configuring module registers, rc=%d\n", rc);
		return rc;
	}

	dev_info(mdev->dev, "probe successful\n");
	return 0;
}

static struct platform_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 platform_driver_register(&qpnp_misc_driver);
}

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

subsys_initcall(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);
+56 −0
Original line number Diff line number Diff line
/* Copyright (c) 2013-2014, 2017, 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);

/**
 * qpnp_misc_read_reg - read register from misc device
 *
 * @node: device node pointer
 * @address: address offset in misc peripheral to be read
 * @val: data read from register
 *
 * This function returns zero if reading the MISC register succeeds.
 *
 */

int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val);
#else
static inline int qpnp_misc_irqs_available(struct device *consumer_dev)
{
	return 0;
}
static inline int qpnp_misc_read_reg(struct device_node *node, u16 addr,
					u8 *val)
{
	return 0;
}
#endif
#endif