diff options
| -rw-r--r-- | Documentation/devicetree/bindings/power/omap-vp.txt | 39 | ||||
| -rw-r--r-- | drivers/power/avs/Makefile | 2 | ||||
| -rw-r--r-- | drivers/power/avs/omap_vp.c | 892 | 
3 files changed, 932 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/power/omap-vp.txt b/Documentation/devicetree/bindings/power/omap-vp.txt new file mode 100644 index 00000000000..b690e35b0ad --- /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@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 95d5f599616..535cab58d43 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 00000000000..8ce42e1f814 --- /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.");  |