[RFC PATCH V2 3/8] PM / AVS: Introduce support for OMAP Voltage Processor(VP) with device tree nodes

Nishanth Menon nm at ti.com
Fri Jun 21 17:25:44 EDT 2013


Texas Instrument's OMAP SoC generations since OMAP3-5 introduced an TI
custom hardware block to better facilitate and standardize integration
of Power Management ICs which communicate over I2C called Voltage
Processor(VP).

This is an specialized hardware block meant to support PMIC chips only
over Voltage Controller(VC) interface. This provides an interface for
SmartReflex AVS module to send adjustment steps which is converted into
voltage values and send onwards by VP to VC. VP is also used to set the
voltage of the PMIC by commanding using "forceupdate".

We have an existing implementation in mach-omap2 which has been
re factored and moved out as a separate driver. This new driver is DT
only and the separation was meant to get a maintainable driver which
does not have to deal with legacy platform_data dependencies. The legacy
driver is retained to support non-DT boot and functionality will be
migrated to the DT-only version as we enable features.

Currently, this implementation considers only the basic steps needed for
voltage scaling and exposing voltage processor which hooks on to Voltage
Controller driver and OMAP PMIC driver to provide an regulator interface
over OMAP PMIC driver.

We may need to do additional timing configurations to enable Low power
mode voltage transitions and to hook the Adaptive Voltage
Scaling(AVS) implementation for OMAP which also uses the same voltage
controller to talk to PMICs. This needs to be addressed in a later
series.

This driver is meant to interface with OMAP PMIC driver when the
controller driver registers it's operations with OMAP PMIC driver and
associates with an voltage controller channel. This enables the full
communication path between the OMAP PMIC regulator to the external PMIC
hardware over OMAP Voltage Processor and OMAP Voltage Controller.

[grygorii.strashko at ti.com, taras at ti.com: co-developer]
Signed-off-by: Grygorii Strashko <grygorii.strashko at ti.com>
Signed-off-by: Taras Kondratiuk <taras at ti.com>
Signed-off-by: Nishanth Menon <nm at ti.com>
---
 .../devicetree/bindings/power/omap-vp.txt          |   39 +
 drivers/power/avs/Makefile                         |    2 +-
 drivers/power/avs/omap_vp.c                        |  892 ++++++++++++++++++++
 3 files changed, 932 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/power/omap-vp.txt
 create mode 100644 drivers/power/avs/omap_vp.c

diff --git a/Documentation/devicetree/bindings/power/omap-vp.txt b/Documentation/devicetree/bindings/power/omap-vp.txt
new file mode 100644
index 0000000..b690e35
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/omap-vp.txt
@@ -0,0 +1,39 @@
+Voltage Controller driver for Texas Instruments OMAP SoCs
+
+Voltage Controller Properties:
+The following are the properties of the voltage controller node
+Required Properties:
+- compatible: Should be one of:
+  - "ti,omap3-vp" - for OMAP3 family of devices
+  - "ti,omap4-vp" - for OMAP4 family of devices
+  - "ti,omap5-vp" - for OMAP5 family of devices
+- reg: Address and length of the register set for the device. It contains
+  the information of registers in the same order as described by reg-names
+- reg-names: Should contain the reg names
+  - "base-address"	- contains base address of VP module
+  - "int-address"	- contains base address of interrupt register for VP module
+- clocks: should point to the clock node used by VC module, usually sysclk
+- ti,min-micro-volts - SoC supported min operational voltage in micro-volts
+- ti,max-micro-volts - SoC supported max operational voltage in micro-volts
+- ti,min-step-micro-volts - SoC supported min operational voltage steps in micro-volts
+- ti,max-step-micro-volts - SoC supported max operational voltage steps in micro-volts
+- ti,tranxdone-status-mask: Mask to the int-register to write-to-clear mask
+	indicating VP has completed operation in sending command to Voltage Controller.
+- ti,vc-channel - phandle to the Voltage Controller Channel device used by this Voltage
+	Processor.
+Example:
+vp_mpu: vp at 0x4a307b58 {
+	compatible = "ti,omap4-vp";
+
+	reg = <0x4a307b58 0x18>, <0x4A306014 0x4>;
+	reg-names = "base-address", "int-address";
+	ti,tranxdone-status-mask=<0x20>;
+
+	clocks = <&sysclk_in>;
+
+	ti,vc-channel = <&vc_mpu>;
+	ti,min-step-micro-volts = <10000>;
+	ti,max-step-micro-volts = <50000>;
+	ti,min-micro-volts = <750000>;
+	ti,max-micro-volts = <1410000>;
+};
diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile
index 95d5f59..535cab5 100644
--- a/drivers/power/avs/Makefile
+++ b/drivers/power/avs/Makefile
@@ -3,7 +3,7 @@ obj-$(CONFIG_POWER_AVS_OMAP)		+= smartreflex.o
 ifneq ($(CONFIG_POWER_TI_HARDWARE_VOLTAGE_CONTROL),)
 
 # OMAP Common
-omap-volt-common			=  omap_vc.o
+omap-volt-common			=  omap_vc.o omap_vp.o
 
 # OMAP SoC specific
 ifneq ($(CONFIG_ARCH_OMAP3),)
diff --git a/drivers/power/avs/omap_vp.c b/drivers/power/avs/omap_vp.c
new file mode 100644
index 0000000..8ce42e1
--- /dev/null
+++ b/drivers/power/avs/omap_vp.c
@@ -0,0 +1,892 @@
+/*
+ * OMAP Voltage Processor (VP) interface
+ *
+ * Idea based on arch/arm/mach-omap2/vp.c
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated
+ * Taras Kondratiuk
+ * Grygorii Strashko
+ * Nishanth Menon
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/omap-pmic-regulator.h>
+#include <linux/regmap.h>
+#include "omap_vc.h"
+
+#define DRIVER_NAME	"omap-vp"
+
+/**
+ * omap_vp_test_timeout - busy-loop, testing a condition
+ * @cond: condition to test until it evaluates to true
+ * @timeout: maximum number of microseconds in the timeout
+ * @index: loop index (integer)
+ *
+ * Loop waiting for @cond to become true or until at least @timeout
+ * microseconds have passed.  To use, define some integer @index in the
+ * calling code.  After running, if @index == @timeout, then the loop has
+ * timed out.
+ */
+#define omap_vp_test_timeout(cond, timeout, index)		\
+({								\
+	for (index = 0; index < timeout; index++) {		\
+		if (cond)					\
+			break;					\
+		udelay(1);					\
+	}							\
+})
+
+/**
+ * struct omap_vp_reg_data - Voltage processor register offsets
+ * @config:	CONFIG register
+ * @status:	STATUS register
+ * @vlimitto:	VLIMITTO register
+ * @voltage:	VOLTAGE register
+ * @step_max:	STEP_MAX register
+ * @step_min:	STEP_MAX register
+ */
+struct omap_vp_reg_data {
+	u8 config;
+	u8 status;
+	u8 vlimitto;
+	u8 voltage;
+	u8 step_max;
+	u8 step_min;
+};
+
+static const struct omap_vp_reg_data omap_vp_reg_type1 = {
+	.config = 0x00,
+	.status = 0x14,
+	.vlimitto = 0x0C,
+	.voltage = 0x10,
+	.step_max = 0x08,
+	.step_min = 0x04,
+};
+
+static const struct omap_vp_reg_data omap_vp_reg_type2 = {
+	.config = 0x00,
+	.status = 0x04,
+	.vlimitto = 0x08,
+	.voltage = 0x0C,
+	.step_max = 0x10,
+	.step_min = 0x14,
+};
+
+/* Config register masks - All revisions */
+#define CONFIG_ERROR_OFFSET_MASK	(0xff << 24)
+#define CONFIG_ERROR_GAIN_MASK		(0xff << 16)
+#define CONFIG_INIT_VOLTAGE_MASK	(0xff << 8)
+#define CONFIG_TIMEOUT_ENABLE_MASK	(0x01 << 3)
+#define CONFIG_INITVDD_MASK		(0x01 << 2)
+#define CONFIG_FORCEUPDATE_MASK		(0x01 << 1)
+#define CONFIG_VP_ENABLE_MASK		(0x01 << 0)
+
+/* Status register masks - All revisions */
+#define STATUS_VP_IN_IDLE_MASK		(0x01 << 0)
+
+/* Vlmitto register masks - All revisions */
+#define VLIMITTO_VDDMAX_MASK		(0xff << 24)
+#define VLIMITTO_VDDMIN_MASK		(0xff << 16)
+#define VLIMITTO_TIMEOUT_MASK		(0xffff << 0)
+
+/* Voltage register masks - All revisions */
+#define VOLTAGE_MASK			(0xff << 0)
+
+/* Step max/min register masks - All revisions */
+#define STEP_SMPSTIMEOUT_MASK		(0xffff << 8)
+#define STEP_VSTEP_MASK			(0xff << 0)
+
+/* 32 bit voltage processor registers */
+static struct regmap_config omap_vp_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+};
+
+/**
+ * struct omap_vp - Structure representing Voltage Processor info
+ * @dev:		device pointer for Voltage Processor
+ * @list:		list head for VP list.
+ * @usage_count:	Usage count - only 1 user at a time.(not always module)
+ * @clk_rate:		Sysclk rate for VP computation.
+ * @vc:			Voltage controller channel corresponding to VP
+ * @pmic:		PMIC used for this path
+ * @regmap:		regmap for VP instance
+ * @regs:		register map
+ * @int_base:		interrupt register base address
+ * @txdone_mask:	TRANXDONE interrupt mask for this VP instance in intreg
+ * @min_uV:		minimum voltage allowed by VP in micro-volts
+ * @max_uV:		maximum voltage allowed by VP in micro-volts
+ * @min_step_uV:	minimum continous voltage step in micro-volts for VP
+ * @max_step_uV:	maximum continous voltage step in micro-volts for VP
+ */
+struct omap_vp {
+	struct device *dev;
+	struct list_head list;
+	int usage_count;
+
+	unsigned long clk_rate;
+	struct omap_vc_channel_info *vc;
+	struct omap_pmic *pmic;
+
+	struct regmap *regmap;
+	const struct omap_vp_reg_data *regs;
+
+	void __iomem *int_base;
+	u32 txdone_mask;
+
+	u32 min_uV;
+	u32 max_uV;
+	u32 min_step_uV;
+	u32 max_step_uV;
+};
+
+static const struct of_device_id omap_vp_of_match[] = {
+	{.compatible = "ti,omap3-vp", .data = &omap_vp_reg_type1},
+	{.compatible = "ti,omap4-vp", .data = &omap_vp_reg_type2},
+	{.compatible = "ti,omap5-vp", .data = &omap_vp_reg_type2},
+	{},
+};
+MODULE_DEVICE_TABLE(of, omap_vp_of_match);
+
+static LIST_HEAD(omap_vp_list);
+static DEFINE_MUTEX(omap_vp_list_mutex);
+
+/**
+ * omap_vp_check_txdone() - inline helper to see if TRANXDONE is set
+ * @vp:	pointer to voltage processor
+ */
+static inline bool omap_vp_check_txdone(const struct omap_vp *vp)
+{
+	return !!(readl(vp->int_base) & vp->txdone_mask);
+}
+
+/**
+ * omap_vp_clear_txdone() - inline helper to clear TRANXDONE
+ * @vp:	pointer to voltage processor
+ *
+ * write of 1 bit clears that interrupt bit only.
+ */
+static inline void omap_vp_clear_txdone(const struct omap_vp *vp)
+{
+	writel(vp->txdone_mask, vp->int_base);
+};
+
+/**
+ * omap_vp_read_idle() - inline helper to read idle register
+ * @regmap:	regmap for voltage processor
+ * @regs:	registers for voltage processor
+ */
+static inline u32 omap_vp_read_idle(struct regmap *regmap,
+				    const struct omap_vp_reg_data *regs)
+{
+	u32 val = 0;
+	regmap_read(regmap, regs->status, &val);
+	return val;
+}
+
+/**
+ * omap_vp_wait_for_idle() - Wait for Voltage processor to idle
+ * @vp:	pointer to voltage processor
+ *
+ * helper to ensure that VP is idle (no pending AVS / previous VP operations)
+ */
+static inline int omap_vp_wait_for_idle(struct omap_vp *vp)
+{
+	struct device *dev = vp->dev;
+	struct regmap *regmap = vp->regmap;
+	const struct omap_vp_reg_data *regs = vp->regs;
+	struct omap_pmic *pmic = vp->pmic;
+	const struct omap_pmic_info *pinfo = pmic->info;
+	int timeout;
+
+	omap_vp_test_timeout((omap_vp_read_idle(regmap, regs) &
+			      STATUS_VP_IN_IDLE_MASK), pinfo->i2c_timeout_us,
+			     timeout);
+
+	if (timeout >= pinfo->i2c_timeout_us) {
+		dev_warn_ratelimited(dev, "%s: idle timedout(%d)\n",
+				     __func__, pinfo->i2c_timeout_us);
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+/**
+ * omap_vp_set_init_voltage() - Setup voltage for transmission.
+ * @vp:	pointer to voltage processor
+ * @volt: voltage to setup the voltage processor with
+ */
+static int omap_vp_set_init_voltage(struct omap_vp *vp, u32 volt)
+{
+	struct regmap *regmap = vp->regmap;
+	const struct omap_vp_reg_data *regs = vp->regs;
+	struct omap_pmic *pmic = vp->pmic;
+	struct omap_pmic_ops *ops = pmic->ops;
+	char vsel;
+	int ret;
+
+	ret = ops->uv_to_vsel(pmic, volt, &vsel);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(regmap, regs->config,
+				 (CONFIG_INIT_VOLTAGE_MASK |
+				  CONFIG_FORCEUPDATE_MASK |
+				  CONFIG_INITVDD_MASK),
+				 vsel << __ffs(CONFIG_INIT_VOLTAGE_MASK));
+	if (ret)
+		return ret;
+
+	/* Trigger initVDD value copy to voltage processor */
+	ret = regmap_update_bits(regmap, regs->config,
+				 CONFIG_INITVDD_MASK, CONFIG_INITVDD_MASK);
+	if (ret)
+		return ret;
+
+	/* Clear initVDD copy trigger bit */
+	ret = regmap_update_bits(regmap, regs->config,
+				 CONFIG_INITVDD_MASK, 0x0);
+
+	return ret;
+}
+
+/**
+ * omap_vp_get_current_voltage() - get the current voltage processor voltage
+ * @vp:	pointer to voltage processor
+ * @uv:	returns with voltage in micro-volts if read was successful.
+ */
+static int omap_vp_get_current_voltage(struct omap_vp *vp, u32 *uv)
+{
+	struct device *dev = vp->dev;
+	struct regmap *regmap = vp->regmap;
+	const struct omap_vp_reg_data *regs = vp->regs;
+	struct omap_pmic *pmic = vp->pmic;
+	struct omap_pmic_ops *ops = pmic->ops;
+	u32 val;
+	u8 vsel;
+	int ret;
+
+	ret = regmap_read(regmap, regs->config, &val);
+	if (ret) {
+		dev_warn_ratelimited(dev,
+				     "%s: unable to read config reg (%d)\n",
+				     __func__, ret);
+		return ret;
+	}
+
+	val &= CONFIG_INIT_VOLTAGE_MASK;
+	vsel = val >> __ffs(CONFIG_INIT_VOLTAGE_MASK);
+	ret = ops->vsel_to_uv(pmic, vsel, &val);
+
+	if (!ret)
+		*uv = val;
+	return ret;
+}
+
+/**
+ * omap_vp_forceupdate_scale() - Update voltage on PMIC using VP "Forceupdate"
+ * @vp:			pointer to voltage processor
+ * @target_volt:	voltage to set the PMIC to
+ *
+ * This will wait for the slew duration to ensure that the voltage is sync-ed
+ * on the PMIC.
+ */
+static int omap_vp_forceupdate_scale(struct omap_vp *vp, u32 target_volt)
+{
+	struct device *dev = vp->dev;
+	struct regmap *regmap = vp->regmap;
+	const struct omap_vp_reg_data *regs = vp->regs;
+	struct omap_pmic *pmic = vp->pmic;
+	const struct omap_pmic_info *pinfo = pmic->info;
+	int ret, timeout = 0, max_timeout;
+	u32 old_volt = 0;
+	u32 smps_transition_uv, smps_delay;
+
+	ret = omap_vp_wait_for_idle(vp);
+	if (ret)
+		return ret;
+
+	ret = omap_vp_get_current_voltage(vp, &old_volt);
+	if (ret) {
+		dev_warn_ratelimited(dev,
+				     "%s: Unable to convert old voltage(%d)\n",
+				     __func__, ret);
+		/* We will use worst case start voltage - 0V for delay */
+	}
+
+	/*
+	 * Clear all pending TransactionDone interrupt/status. Typical latency
+	 * is <3us - use an conservative value from pmic info.
+	 */
+	max_timeout = 2 * pinfo->i2c_timeout_us;
+	while (timeout++ < max_timeout) {
+		omap_vp_clear_txdone(vp);
+		if (!omap_vp_check_txdone(vp))
+			break;
+		udelay(1);
+	}
+	if (timeout >= max_timeout) {
+		dev_warn_ratelimited(dev,
+				     "%s: TRANXDONE not clear(t=%d v=%d)\n",
+				     __func__, max_timeout, target_volt);
+		return -ETIMEDOUT;
+	}
+
+	ret = omap_vp_set_init_voltage(vp, target_volt);
+	if (ret) {
+		dev_warn_ratelimited(dev,
+				     "%s: Fail set init voltage at v=%d(%d)\n",
+				     __func__, target_volt, ret);
+		return ret;
+	}
+
+	/* Force update of voltage */
+	ret = regmap_update_bits(regmap, regs->config,
+				 CONFIG_FORCEUPDATE_MASK,
+				 CONFIG_FORCEUPDATE_MASK);
+	if (ret) {
+		dev_warn_ratelimited(dev,
+				     "%s: Forceupdate not set v=%d (%d)\n",
+				     __func__, target_volt, ret);
+		return ret;
+	}
+
+	/*
+	 * Wait for TransactionDone. Typical latency is <200us.
+	 * Depends on SMPSWAITTIMEMIN/MAX and voltage change
+	 */
+	timeout = 0;
+	omap_vp_test_timeout(omap_vp_check_txdone(vp), max_timeout, timeout);
+	if (timeout >= max_timeout) {
+		dev_warn_ratelimited(dev,
+				     "%s: TRANXDONE not set(t=%d v=%d)\n",
+				     __func__, max_timeout, target_volt);
+		return -ETIMEDOUT;
+	}
+
+	/*
+	 * Due to the inability of OMAP Voltage controller OR voltage processor
+	 * to precisely know when the voltage has achieved the requested value,
+	 * we need a delay loop to ensure that the voltage has transitioned to
+	 * the required level.
+	 */
+	smps_transition_uv = abs(target_volt - old_volt);
+
+	/* delta_voltage / slew_rate, 2uS added as buffer */
+	smps_delay = DIV_ROUND_UP(smps_transition_uv, pinfo->slew_rate_uV) + 2;
+
+	/* We dont want to sleep for too long either */
+	usleep_range(smps_delay, smps_delay + 2);
+
+	/*
+	 * Disable TransactionDone interrupt , clear all status, clear
+	 * control registers
+	 */
+	timeout = 0;
+	while (timeout++ < max_timeout) {
+		omap_vp_clear_txdone(vp);
+		if (!omap_vp_check_txdone(vp))
+			break;
+		udelay(1);
+	}
+	if (timeout >= max_timeout) {
+		dev_warn_ratelimited(dev,
+				     "%s: TRANXDONE not recleared(t=%d v=%d)\n",
+				     __func__, max_timeout, target_volt);
+		return -ETIMEDOUT;
+	}
+
+	/* Clear force bit */
+	ret = regmap_update_bits(regmap, regs->config,
+				 CONFIG_FORCEUPDATE_MASK, 0x0);
+	if (ret) {
+		dev_warn_ratelimited(dev,
+				     "%s: Forceupdate not cleared v=%d (%d)\n",
+				     __func__, target_volt, ret);
+		return ret;
+	}
+
+	/* Do the required updates */
+	ret = omap_vc_channel_set_on_voltage(vp->vc, target_volt);
+	if (ret) {
+		dev_warn_ratelimited(dev,
+				     "%s: Fail update VC onV at v=%d (%d)\n",
+				     __func__, target_volt, ret);
+		return ret;
+	}
+
+	/* Now, Wait for VP to idle down */
+	ret = omap_vp_wait_for_idle(vp);
+
+	return ret;
+}
+
+/**
+ * omap_vp_setup() - Setup voltage processor
+ * @vp:	pointer to voltage processor
+ */
+static int omap_vp_setup(struct omap_vp *vp)
+{
+	struct omap_pmic *pmic = vp->pmic;
+	const struct omap_pmic_info *pinfo = pmic->info;
+	struct omap_pmic_ops *ops = pmic->ops;
+	struct regmap *regmap = vp->regmap;
+	const struct omap_vp_reg_data *regs = vp->regs;
+	u32 val, clk_rate, timeout, waittime;
+	u8 vstepmin, vstepmax;
+	u8 vddmin, vddmax;
+	int ret;
+
+	/* Div 1000 to avoid overflow */
+	clk_rate = vp->clk_rate / 1000;
+
+	ret = ops->uv_to_vsel(pmic, vp->min_uV, &vddmin);
+	if (ret)
+		return ret;
+	ret = ops->uv_to_vsel(pmic, vp->max_uV, &vddmax);
+	if (ret)
+		return ret;
+
+	timeout = DIV_ROUND_UP_ULL(clk_rate * pinfo->i2c_timeout_us, 1000);
+	waittime = DIV_ROUND_UP_ULL(pinfo->step_size_uV * clk_rate,
+				    1000 * pinfo->slew_rate_uV);
+
+	vstepmin = DIV_ROUND_UP(vp->min_step_uV, pinfo->step_size_uV);
+	vstepmax = DIV_ROUND_UP(vp->max_step_uV, pinfo->step_size_uV);
+
+	/* VSTEPMIN */
+	val =
+	    (waittime << __ffs(STEP_SMPSTIMEOUT_MASK)) & STEP_SMPSTIMEOUT_MASK;
+	val |= (vstepmin << __ffs(STEP_VSTEP_MASK)) & STEP_VSTEP_MASK;
+	ret = regmap_write(regmap, regs->step_min, val);
+	if (ret)
+		return ret;
+
+	/* VSTEPMIN */
+	val =
+	    (waittime << __ffs(STEP_SMPSTIMEOUT_MASK)) & STEP_SMPSTIMEOUT_MASK;
+	val |= (vstepmax << __ffs(STEP_VSTEP_MASK)) & STEP_VSTEP_MASK;
+	ret = regmap_write(regmap, regs->step_max, val);
+	if (ret)
+		return ret;
+
+	/* VLIMITTO */
+	val = (vddmax << __ffs(VLIMITTO_VDDMAX_MASK)) & VLIMITTO_VDDMAX_MASK;
+	val |= (vddmin << __ffs(VLIMITTO_VDDMIN_MASK)) & VLIMITTO_VDDMIN_MASK;
+	val |=
+	    (timeout << __ffs(VLIMITTO_TIMEOUT_MASK)) & VLIMITTO_TIMEOUT_MASK;
+	ret = regmap_write(regmap, regs->vlimitto, val);
+	if (ret)
+		return ret;
+
+	/* CONFIG */
+	ret =
+	    regmap_update_bits(regmap, regs->config, CONFIG_TIMEOUT_ENABLE_MASK,
+			       CONFIG_TIMEOUT_ENABLE_MASK);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+/**
+ * devm_omap_vp_release() -  helper to keep track of free usage.
+ * @dev:	device
+ * @res:	resource
+ */
+static void devm_omap_vp_release(struct device *dev, void *res)
+{
+	struct omap_vp *vp = *((struct omap_vp **)res);
+
+	mutex_lock(&omap_vp_list_mutex);
+
+	if (!vp->usage_count) {
+		vp->pmic = NULL;
+		vp->vc = NULL;
+	}
+	module_put(vp->dev->driver->owner);
+
+	mutex_unlock(&omap_vp_list_mutex);
+
+	return;
+}
+
+/**
+ * of_get_omap_vp() - get the pmic node
+ * @dev:	device to pull information from
+ */
+static struct device_node *of_get_omap_vp(struct device *dev)
+{
+	struct device_node *pmic_node = NULL;
+	char *prop_name = "ti,vp";
+
+	dev_dbg(dev, "%s: Looking up %s from device tree\n", __func__,
+		prop_name);
+
+	pmic_node = of_parse_phandle(dev->of_node, prop_name, 0);
+
+	if (!pmic_node) {
+		dev_err(dev, "%s: Looking up %s property in node %s failed",
+			__func__, prop_name, dev->of_node->full_name);
+		return ERR_PTR(-ENODEV);
+	}
+	return pmic_node;
+}
+
+/**
+ * devm_omap_vp_get() - managed request to get a VP device
+ * @dev:	Generic device to handle the request for
+ * @pmic:	PMIC resource this will be assigned to
+ *
+ * Ensures that vp usage count is maintained. Uses managed device,
+ * so everything is undone on driver detach.
+ *
+ * Return: -EPROBE_DEFER if the node is present, however device is
+ * not yet probed.
+ * -EINVAL if bad pointers or node description is not found.
+ * -ENODEV if the property cannot be found
+ * -ENOMEM if allocation could not be done.
+ *  device pointer to vp dev if all successful.
+ *  Error handling should be performed with IS_ERR
+ */
+static struct device *devm_omap_vp_get(struct device *dev,
+				       struct omap_pmic *pmic)
+{
+	const struct omap_pmic_info *pinfo;
+	struct omap_vp *vp, **ptr;
+	struct device_node *node;
+	struct device *vp_dev;
+	struct omap_vc_channel_info *vc;
+	u32 min_uV, max_uV;
+	int ret = 0;
+
+	if (!dev || !dev->of_node) {
+		pr_err("%s: invalid parameters\n", __func__);
+		return ERR_PTR(-EINVAL);
+	}
+
+	node = of_get_omap_vp(dev);
+	if (IS_ERR(node))
+		return (void *)node;
+
+	mutex_lock(&omap_vp_list_mutex);
+	list_for_each_entry(vp, &omap_vp_list, list)
+	    if (vp->dev->of_node == node)
+		goto found;
+
+	/* Node definition is present, but not probed yet.. request defer */
+	vp_dev = ERR_PTR(-EPROBE_DEFER);
+	goto out_unlock;
+
+found:
+	vp_dev = vp->dev;
+	if (!try_module_get(vp_dev->driver->owner)) {
+		dev_err(dev, "%s: Cant get device owner\n", __func__);
+		vp_dev = ERR_PTR(-EINVAL);
+		goto out_unlock;
+	}
+
+	/* Allow ONLY 1 user at a time */
+	if (vp->usage_count) {
+		dev_err(dev, "%s: device %s is busy..\n", __func__,
+			dev_name(vp_dev));
+		ret = -EBUSY;
+		goto out;
+	}
+	vc = devm_omap_vc_channel_get(vp_dev, pmic);
+	if (IS_ERR(vc)) {
+		ret = PTR_ERR(vc);
+		dev_err(dev, "%s: vc channel not ready(%d) in %s?\n",
+			__func__, ret, dev_name(vp_dev));
+		goto out;
+	}
+	vp->vc = vc;
+	vp->pmic = pmic;
+	pinfo = pmic->info;
+
+	/* Adjust our voltages */
+	/* Cant go below PMIC min voltage */
+	min_uV = max(vp->min_uV, pinfo->min_uV);
+	/* Cant go below SoC retention voltage for operational case */
+	min_uV = max(min_uV, vc->retention_uV);
+	vp->min_uV = min_uV;
+
+	/* Cant go above PMIC max voltage */
+	max_uV = min(vp->max_uV, pinfo->max_uV);
+	vp->max_uV = max_uV;
+
+	ret = omap_vp_setup(vp);
+	if (ret) {
+		dev_err(dev, "%s: Failed to setup vp (%d) dev %s\n",
+			__func__, ret, dev_name(vp_dev));
+		goto out;
+	}
+
+	if (pmic->boot_voltage_uV) {
+		ret = omap_vp_forceupdate_scale(vp, pmic->boot_voltage_uV);
+		if (ret) {
+			dev_err(dev, "%s: Failed to set boot voltage %d(%d)\n",
+				__func__, pmic->boot_voltage_uV, ret);
+			goto out;
+		}
+	}
+
+	ptr = devres_alloc(devm_omap_vp_release, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	*ptr = vp;
+	vp->usage_count++;
+	devres_add(dev, ptr);
+
+out:
+	if (ret) {
+		module_put(vp_dev->driver->owner);
+		vp_dev = ERR_PTR(ret);
+	}
+out_unlock:
+	mutex_unlock(&omap_vp_list_mutex);
+
+	return vp_dev;
+}
+
+/**
+ * omap_vp_voltage_set() - controller operation to set voltage
+ * @dev:	VP device to set voltage
+ * @uv:		voltage in micro-volts to set
+ */
+static int omap_vp_voltage_set(struct device *dev, u32 uv)
+{
+	struct omap_vp *vp = dev_get_drvdata(dev);
+
+	if (!vp)
+		return -EINVAL;
+	if (!vp->pmic || !vp->vc)
+		return -EINVAL;
+
+	return omap_vp_forceupdate_scale(vp, uv);
+}
+
+/**
+ * omap_vp_voltage_get() - controller operation to get voltage
+ * @dev:	VP device to get voltage from
+ * @uv:		returns voltage in micro-volts if successful
+ */
+static int omap_vp_voltage_get(struct device *dev, u32 *uv)
+{
+	struct omap_vp *vp = dev_get_drvdata(dev);
+
+	if (!vp || !uv)
+		return -EINVAL;
+	if (!vp->pmic || !vp->vc)
+		return -EINVAL;
+
+	return omap_vp_get_current_voltage(vp, uv);
+}
+
+/**
+ * omap_vp_voltage_get_range() - controller function to return VP voltage range
+ * @dev:	VP device to query
+ * @min_uv:	if successful, returns min voltage supported by VP
+ * @max_uv:	if successful, returns max voltage supported by VP
+ */
+static int omap_vp_voltage_get_range(struct device *dev, u32 *min_uv,
+				     u32 *max_uv)
+{
+	struct omap_vp *vp = dev_get_drvdata(dev);
+
+	if (!vp || !min_uv || !max_uv)
+		return -EINVAL;
+	if (!vp->pmic || !vp->vc)
+		return -EINVAL;
+
+	*min_uv = vp->min_uV;
+	*max_uv = vp->max_uV;
+	return 0;
+}
+
+static struct omap_pmic_controller_ops voltage_processor_ops = {
+	.devm_pmic_register = devm_omap_vp_get,
+	.voltage_set = omap_vp_voltage_set,
+	.voltage_get = omap_vp_voltage_get,
+	.voltage_get_range = omap_vp_voltage_get_range,
+};
+static bool voltage_processor_ops_registered;
+
+static int omap_vp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	const struct of_device_id *match;
+	struct omap_vp *vp;
+	struct resource *res;
+	struct regmap *regmap;
+	char *pname;
+	struct clk *clk;
+	int ret = 0;
+	void __iomem *base, *int_base;
+
+	if (!node) {
+		dev_err(dev, "%s: missing device tree nodes?\n", __func__);
+		return -EINVAL;
+	}
+
+	match = of_match_device(omap_vp_of_match, dev);
+	if (!match) {
+		/* We do not expect this to happen */
+		dev_err(dev, "%s: Unable to match device\n", __func__);
+		return -ENODEV;
+	}
+	if (!match->data) {
+		dev_err(dev, "%s: Bad data in match\n", __func__);
+		return -EINVAL;
+	}
+
+	pname = "base-address";
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname);
+	base = devm_request_and_ioremap(dev, res);
+	if (!base) {
+		dev_err(dev, "Unable to map '%s'\n", pname);
+		return -EADDRNOTAVAIL;
+	}
+	pname = "int-address";
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname);
+	if (!res) {
+		dev_err(dev, "Missing '%s' IO resource\n", pname);
+		return -ENODEV;
+	}
+
+	/*
+	 * We may have shared interrupt register offsets which are
+	 * write-1-to-clear between domains ensuring exclusivity.
+	 */
+	int_base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (!int_base) {
+		dev_err(dev, "Unable to map '%s'\n", pname);
+		return -ENOMEM;
+	}
+
+	regmap = devm_regmap_init_mmio(dev, base, &omap_vp_regmap_config);
+	if (IS_ERR(regmap)) {
+		ret = PTR_ERR(regmap);
+		dev_err(dev, "regmap init failed(%d)\n", ret);
+		return ret;
+	}
+
+	vp = devm_kzalloc(dev, sizeof(*vp), GFP_KERNEL);
+	if (!vp) {
+		dev_err(dev, "%s: Unable to allocate VP\n", __func__);
+		return -ENOMEM;
+	}
+	vp->dev = dev;
+	vp->regs = match->data;
+	vp->regmap = regmap;
+	vp->int_base = int_base;
+
+	clk = clk_get(dev, NULL);
+	if (IS_ERR(clk)) {
+		ret = PTR_ERR(clk);
+		dev_err(dev, "%s: Unable to get clk(%d)\n", __func__, ret);
+		return ret;
+	}
+	vp->clk_rate = clk_get_rate(clk);
+	/* We dont need the clk any more */
+	clk_put(clk);
+
+	pname = "ti,min-micro-volts";
+	ret = of_property_read_u32(node, pname, &vp->min_uV);
+	if (ret)
+		goto invalid_of_property;
+
+	pname = "ti,max-micro-volts";
+	ret = of_property_read_u32(node, pname, &vp->max_uV);
+	if (ret || !vp->max_uV)
+		goto invalid_of_property;
+
+	pname = "ti,min-step-micro-volts";
+	ret = of_property_read_u32(node, pname, &vp->min_step_uV);
+	if (ret || !vp->min_step_uV)
+		goto invalid_of_property;
+
+	pname = "ti,max-step-micro-volts";
+	ret = of_property_read_u32(node, pname, &vp->max_step_uV);
+	if (ret || !vp->max_step_uV)
+		goto invalid_of_property;
+
+	pname = "ti,tranxdone-status-mask";
+	ret = of_property_read_u32(node, pname, &vp->txdone_mask);
+	if (ret || !vp->txdone_mask)
+		goto invalid_of_property;
+
+	platform_set_drvdata(pdev, vp);
+
+	mutex_lock(&omap_vp_list_mutex);
+	if (!voltage_processor_ops_registered) {
+		ret = omap_pmic_register_controller_ops(&voltage_processor_ops);
+		if (ret)
+			dev_err(dev, "Failed register pmic cops (%d)\n", ret);
+		else
+			voltage_processor_ops_registered = true;
+	}
+	if (!ret)
+		list_add(&vp->list, &omap_vp_list);
+
+	mutex_unlock(&omap_vp_list_mutex);
+	return ret;
+
+invalid_of_property:
+	if (!ret) {
+		dev_err(dev, "%s: Invalid value 0x0 in '%s' property.\n",
+			__func__, pname);
+		ret = -EINVAL;
+	} else {
+		dev_err(dev, "%s: Missing/Invalid '%s' property - error(%d)\n",
+			__func__, pname, ret);
+	}
+	return ret;
+}
+
+static int omap_vp_remove(struct platform_device *pdev)
+{
+	struct omap_vp *vp = platform_get_drvdata(pdev);
+
+	mutex_lock(&omap_vp_list_mutex);
+	list_del(&vp->list);
+	mutex_unlock(&omap_vp_list_mutex);
+
+	return 0;
+}
+
+static struct platform_driver omap_vp_driver = {
+	.probe = omap_vp_probe,
+	.remove = omap_vp_remove,
+	.driver = {
+		   .name = DRIVER_NAME,
+		   .owner = THIS_MODULE,
+		   .of_match_table = of_match_ptr(omap_vp_of_match),
+		   },
+};
+module_platform_driver(omap_vp_driver);
+
+MODULE_DESCRIPTION("OMAP Voltage Processor Regulator Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Texas Instruments Inc.");
-- 
1.7.9.5




More information about the linux-arm-kernel mailing list