/*
 * Copyright (c) 2008, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the 
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <boot/boot.h>
#include <msm7k/shared.h>

static void get_version(char *s, unsigned id)
{
    unsigned *ver = (unsigned*) MSM7K_VERSION;
    unsigned n = ver[id];
    
    snprintf(s, 32, "%d.%d", n >> 16, n & 0xffff);
}

void get_version_modem(char *s)
{
    get_version(s, VERSION_MODEM);
}

void get_version_modem_sbl(char *s)
{
    get_version(s, VERSION_MODEM_SBL);
}

#define MSM_CSR_BASE 0xC0100000

#define MSM_A2M_INT(n) (MSM_CSR_BASE + 0x400 + (n) * 4)

static inline void notify_other_proc_comm(void)
{
	writel(1, MSM_A2M_INT(6));
}

#define APP_COMMAND (MSM7K_SHARED_PHYS + 0x00)
#define APP_STATUS  (MSM7K_SHARED_PHYS + 0x04)
#define APP_DATA1   (MSM7K_SHARED_PHYS + 0x08)
#define APP_DATA2   (MSM7K_SHARED_PHYS + 0x0C)

#define MDM_COMMAND (MSM7K_SHARED_PHYS + 0x10)
#define MDM_STATUS  (MSM7K_SHARED_PHYS + 0x14)
#define MDM_DATA1   (MSM7K_SHARED_PHYS + 0x18)
#define MDM_DATA2   (MSM7K_SHARED_PHYS + 0x1C)


enum
{
	PCOM_CMD_IDLE = 0x0,
	PCOM_CMD_DONE,
	PCOM_RESET_APPS,
	PCOM_RESET_CHIP,
	PCOM_CONFIG_NAND_MPU,
	PCOM_CONFIG_USB_CLKS,
	PCOM_GET_POWER_ON_STATUS,
	PCOM_GET_WAKE_UP_STATUS,
	PCOM_GET_BATT_LEVEL,
	PCOM_CHG_IS_CHARGING,
	PCOM_POWER_DOWN,
	PCOM_USB_PIN_CONFIG,
	PCOM_USB_PIN_SEL,
	PCOM_SET_RTC_ALARM,
	PCOM_NV_READ,
	PCOM_NV_WRITE,
	PCOM_GET_UUID_HIGH,
	PCOM_GET_UUID_LOW,
	PCOM_GET_HW_ENTROPY,
	PCOM_RPC_GPIO_TLMM_CONFIG_REMOTE,
	PCOM_CLKCTL_RPC_ENABLE,
	PCOM_CLKCTL_RPC_DISABLE,
	PCOM_CLKCTL_RPC_RESET,
	PCOM_CLKCTL_RPC_SET_FLAGS,
	PCOM_CLKCTL_RPC_SET_RATE,
	PCOM_CLKCTL_RPC_MIN_RATE,
	PCOM_CLKCTL_RPC_MAX_RATE,
	PCOM_CLKCTL_RPC_RATE,
	PCOM_CLKCTL_RPC_PLL_REQUEST,
	PCOM_CLKCTL_RPC_ENABLED,
	PCOM_VREG_SWITCH,
	PCOM_VREG_SET_LEVEL,
	PCOM_GPIO_TLMM_CONFIG_GROUP,
	PCOM_GPIO_TLMM_UNCONFIG_GROUP,
	PCOM_NV_READ_HIGH_BITS,
	PCOM_NV_WRITE_HIGH_BITS,
	PCOM_NUM_CMDS,
};

enum
{
	 PCOM_INVALID_STATUS = 0x0,
	 PCOM_READY,
	 PCOM_CMD_RUNNING,
	 PCOM_CMD_SUCCESS,
	 PCOM_CMD_FAIL,
};

int msm_proc_comm(unsigned cmd, unsigned *data1, unsigned *data2)
{
	int ret = -1;

	while (readl(MDM_STATUS) != PCOM_READY) {
		/* XXX check for A9 reset */
	}

	writel(cmd, APP_COMMAND);
	if (data1)
		writel(*data1, APP_DATA1);
	if (data2)
		writel(*data2, APP_DATA2);

	notify_other_proc_comm();
	while (readl(APP_COMMAND) != PCOM_CMD_DONE) {
		/* XXX check for A9 reset */
	}

	if (readl(APP_STATUS) != PCOM_CMD_FAIL) {
		if (data1)
			*data1 = readl(APP_DATA1);
		if (data2)
			*data2 = readl(APP_DATA2);
		ret = 0;
	}

	return ret;
}

int clock_enable(unsigned id)
{
    return msm_proc_comm(PCOM_CLKCTL_RPC_ENABLE, &id, 0);
}

int clock_disable(unsigned id)
{
    return msm_proc_comm(PCOM_CLKCTL_RPC_DISABLE, &id, 0);
}

int clock_set_rate(unsigned id, unsigned rate)
{
    return msm_proc_comm(PCOM_CLKCTL_RPC_SET_RATE, &id, &rate);
}

int clock_get_rate(unsigned id)
{
    if (msm_proc_comm(PCOM_CLKCTL_RPC_RATE, &id, 0)) {
        return -1;
    } else {
        return (int) id;
    }
}

void reboot(void)
{
    msm_proc_comm(PCOM_RESET_CHIP, 0, 0);
    for (;;) ;
}

int vreg_enable(unsigned id)
{
    unsigned n = 1;
    return msm_proc_comm(PCOM_VREG_SWITCH, &id, &n);
}

int vreg_disable(unsigned id)
{
    unsigned n = 0;
    return msm_proc_comm(PCOM_VREG_SWITCH, &id, &n);
}

int vreg_set_level(unsigned id, unsigned level)
{
    return msm_proc_comm(PCOM_VREG_SET_LEVEL, &id, &level);
}