/*
 * OMAP3/OMAP4 Voltage Management Routines
 *
 * Author: Thara Gopinath	<thara@ti.com>
 *
 * Copyright (C) 2007 Texas Instruments, Inc.
 * Rajendra Nayak <rnayak@ti.com>
 * Lesly A M <x0080970@ti.com>
 *
 * Copyright (C) 2008, 2011 Nokia Corporation
 * Kalle Jokiniemi
 * Paul Walmsley
 *
 * Copyright (C) 2010 Texas Instruments, Inc.
 * Thara Gopinath <thara@ti.com>
 *
 * 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.
 */

#include <linux/delay.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/clk.h>

#include "common.h"

#include "prm-regbits-34xx.h"
#include "prm-regbits-44xx.h"
#include "prm44xx.h"
#include "prcm44xx.h"
#include "prminst44xx.h"
#include "control.h"

#include "voltage.h"
#include "powerdomain.h"

#include "vc.h"
#include "vp.h"

static LIST_HEAD(voltdm_list);

/* Public functions */
/**
 * voltdm_get_voltage() - Gets the current non-auto-compensated voltage
 * @voltdm:	pointer to the voltdm for which current voltage info is needed
 *
 * API to get the current non-auto-compensated voltage for a voltage domain.
 * Returns 0 in case of error else returns the current voltage.
 */
unsigned long voltdm_get_voltage(struct voltagedomain *voltdm)
{
	if (!voltdm || IS_ERR(voltdm)) {
		pr_warning("%s: VDD specified does not exist!\n", __func__);
		return 0;
	}

	return voltdm->nominal_volt;
}

/**
 * voltdm_scale() - API to scale voltage of a particular voltage domain.
 * @voltdm: pointer to the voltage domain which is to be scaled.
 * @target_volt: The target voltage of the voltage domain
 *
 * This API should be called by the kernel to do the voltage scaling
 * for a particular voltage domain during DVFS.
 */
int voltdm_scale(struct voltagedomain *voltdm,
		 unsigned long target_volt)
{
	int ret, i;
	unsigned long volt = 0;

	if (!voltdm || IS_ERR(voltdm)) {
		pr_warning("%s: VDD specified does not exist!\n", __func__);
		return -EINVAL;
	}

	if (!voltdm->scale) {
		pr_err("%s: No voltage scale API registered for vdd_%s\n",
			__func__, voltdm->name);
		return -ENODATA;
	}

	/* Adjust voltage to the exact voltage from the OPP table */
	for (i = 0; voltdm->volt_data[i].volt_nominal != 0; i++) {
		if (voltdm->volt_data[i].volt_nominal >= target_volt) {
			volt = voltdm->volt_data[i].volt_nominal;
			break;
		}
	}

	if (!volt) {
		pr_warning("%s: not scaling. OPP voltage for %lu, not found.\n",
			   __func__, target_volt);
		return -EINVAL;
	}

	ret = voltdm->scale(voltdm, volt);
	if (!ret)
		voltdm->nominal_volt = volt;

	return ret;
}

/**
 * voltdm_reset() - Resets the voltage of a particular voltage domain
 *		    to that of the current OPP.
 * @voltdm: pointer to the voltage domain whose voltage is to be reset.
 *
 * This API finds out the correct voltage the voltage domain is supposed
 * to be at and resets the voltage to that level. Should be used especially
 * while disabling any voltage compensation modules.
 */
void voltdm_reset(struct voltagedomain *voltdm)
{
	unsigned long target_volt;

	if (!voltdm || IS_ERR(voltdm)) {
		pr_warning("%s: VDD specified does not exist!\n", __func__);
		return;
	}

	target_volt = voltdm_get_voltage(voltdm);
	if (!target_volt) {
		pr_err("%s: unable to find current voltage for vdd_%s\n",
			__func__, voltdm->name);
		return;
	}

	voltdm_scale(voltdm, target_volt);
}

/**
 * omap_voltage_get_volttable() - API to get the voltage table associated with a
 *				particular voltage domain.
 * @voltdm:	pointer to the VDD for which the voltage table is required
 * @volt_data:	the voltage table for the particular vdd which is to be
 *		populated by this API
 *
 * This API populates the voltage table associated with a VDD into the
 * passed parameter pointer. Returns the count of distinct voltages
 * supported by this vdd.
 *
 */
void omap_voltage_get_volttable(struct voltagedomain *voltdm,
				struct omap_volt_data **volt_data)
{
	if (!voltdm || IS_ERR(voltdm)) {
		pr_warning("%s: VDD specified does not exist!\n", __func__);
		return;
	}

	*volt_data = voltdm->volt_data;
}
