// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <sound/soc.h>
#include <asoc/wcd9xxx-resmgr-v2.h>
#include <asoc/core.h>

#define WCD9XXX_RCO_CALIBRATION_DELAY_INC_US 5000
/* This register is valid only for WCD9335 */
#define WCD93XX_ANA_CLK_TOP 0x0602

#define WCD93XX_ANA_BIAS 0x0601
#define WCD93XX_CDC_CLK_RST_CTRL_MCLK_CONTROL 0x0d41
#define WCD93XX_CDC_CLK_RST_CTRL_FS_CNT_CONTROL 0x0d42
#define WCD93XX_CLK_SYS_MCLK_PRG 0x711
#define WCD93XX_CODEC_RPM_CLK_GATE 0x002
#define WCD93XX_ANA_RCO 0x603
#define WCD93XX_ANA_BUCK_CTL 0x606

static const char *wcd_resmgr_clk_type_to_str(enum wcd_clock_type clk_type)
{
	if (clk_type == WCD_CLK_OFF)
		return "WCD_CLK_OFF";
	else if (clk_type == WCD_CLK_RCO)
		return "WCD_CLK_RCO";
	else if (clk_type == WCD_CLK_MCLK)
		return "WCD_CLK_MCLK";
	else
		return "WCD_CLK_UNDEFINED";
}

static int wcd_resmgr_codec_reg_update_bits(struct wcd9xxx_resmgr_v2 *resmgr,
					    u16 reg, u8 mask, u8 val)
{
	bool change;
	int ret;

	if (resmgr->codec_type != WCD9335) {
		/* Tavil and Pahu does not support ANA_CLK_TOP register */
		if (reg == WCD93XX_ANA_CLK_TOP)
			return 0;
	} else {
		/* Tasha does not support CLK_SYS_MCLK_PRG register */
		if (reg == WCD93XX_CLK_SYS_MCLK_PRG)
			return 0;
	}
	if (resmgr->component) {
		ret = snd_soc_component_update_bits(resmgr->component, reg,
					mask, val);
	} else if (resmgr->core_res->wcd_core_regmap) {
		ret = regmap_update_bits_check(
				resmgr->core_res->wcd_core_regmap,
				reg, mask, val, &change);
		if (!ret)
			ret = change;
	} else {
		pr_err("%s: codec/regmap not defined\n", __func__);
		ret = -EINVAL;
	}

	return ret;
}

static int wcd_resmgr_codec_reg_read(struct wcd9xxx_resmgr_v2 *resmgr,
				     unsigned int reg)
{
	int val, ret;

	if (resmgr->codec_type != WCD9335) {
		if (reg == WCD93XX_ANA_CLK_TOP)
			return 0;
	} else {
		if (reg == WCD93XX_CLK_SYS_MCLK_PRG)
			return 0;
	}
	if (resmgr->component) {
		val = snd_soc_component_read32(resmgr->component, reg);
	} else if (resmgr->core_res->wcd_core_regmap) {
		ret = regmap_read(resmgr->core_res->wcd_core_regmap,
				  reg, &val);
		if (ret)
			val = ret;
	} else {
		pr_err("%s: wcd regmap is null\n", __func__);
		return -EINVAL;
	}

	return val;
}

/*
 * wcd_resmgr_get_clk_type()
 * Returns clk type that is currently enabled
 */
int wcd_resmgr_get_clk_type(struct wcd9xxx_resmgr_v2 *resmgr)
{
	if (!resmgr) {
		pr_err("%s: resmgr not initialized\n", __func__);
		return -EINVAL;
	}
	return resmgr->clk_type;
}
EXPORT_SYMBOL(wcd_resmgr_get_clk_type);

static void wcd_resmgr_cdc_specific_get_clk(struct wcd9xxx_resmgr_v2 *resmgr,
						int clk_users)
{
	/* Caller of this function should have acquired BG_CLK lock */
	if (clk_users) {
		if (resmgr->resmgr_cb &&
		    resmgr->resmgr_cb->cdc_rco_ctrl) {
			while (clk_users--)
				resmgr->resmgr_cb->cdc_rco_ctrl(
					resmgr->component, true);
		}
	}
}

/*
 * wcd_resmgr_post_ssr_v2
 * @resmgr: handle to struct wcd9xxx_resmgr_v2
 */
void wcd_resmgr_post_ssr_v2(struct wcd9xxx_resmgr_v2 *resmgr)
{
	int old_bg_audio_users;
	int old_clk_rco_users, old_clk_mclk_users;

	WCD9XXX_V2_BG_CLK_LOCK(resmgr);

	old_bg_audio_users = resmgr->master_bias_users;
	old_clk_mclk_users = resmgr->clk_mclk_users;
	old_clk_rco_users = resmgr->clk_rco_users;
	resmgr->master_bias_users = 0;
	resmgr->clk_mclk_users = 0;
	resmgr->clk_rco_users = 0;
	resmgr->clk_type = WCD_CLK_OFF;

	pr_debug("%s: old_bg_audio_users=%d old_clk_mclk_users=%d old_clk_rco_users=%d\n",
		 __func__, old_bg_audio_users,
		 old_clk_mclk_users, old_clk_rco_users);

	if (old_bg_audio_users) {
		while (old_bg_audio_users--)
			wcd_resmgr_enable_master_bias(resmgr);
	}

	if (old_clk_mclk_users) {
		while (old_clk_mclk_users--)
			wcd_resmgr_enable_clk_block(resmgr, WCD_CLK_MCLK);
	}

	if (old_clk_rco_users)
		wcd_resmgr_cdc_specific_get_clk(resmgr, old_clk_rco_users);

	WCD9XXX_V2_BG_CLK_UNLOCK(resmgr);
}
EXPORT_SYMBOL(wcd_resmgr_post_ssr_v2);

/*
 * wcd_resmgr_enable_master_bias: enable codec master bias
 * @resmgr: handle to struct wcd9xxx_resmgr_v2
 */
int wcd_resmgr_enable_master_bias(struct wcd9xxx_resmgr_v2 *resmgr)
{
	mutex_lock(&resmgr->master_bias_lock);

	resmgr->master_bias_users++;
	if (resmgr->master_bias_users == 1) {
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BIAS,
						 0x80, 0x80);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BIAS,
						 0x40, 0x40);
		/*
		 * 1ms delay is required after pre-charge is enabled
		 * as per HW requirement
		 */
		usleep_range(1000, 1100);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BIAS,
						 0x40, 0x00);
		wcd_resmgr_codec_reg_update_bits(resmgr,
						WCD93XX_ANA_BIAS, 0x20, 0x00);
	}

	pr_debug("%s: current master bias users: %d\n", __func__,
		 resmgr->master_bias_users);

	mutex_unlock(&resmgr->master_bias_lock);
	return 0;
}
EXPORT_SYMBOL(wcd_resmgr_enable_master_bias);

/*
 * wcd_resmgr_disable_master_bias: disable codec master bias
 * @resmgr: handle to struct wcd9xxx_resmgr_v2
 */
int wcd_resmgr_disable_master_bias(struct wcd9xxx_resmgr_v2 *resmgr)
{
	mutex_lock(&resmgr->master_bias_lock);
	if (resmgr->master_bias_users <= 0) {
		mutex_unlock(&resmgr->master_bias_lock);
		return -EINVAL;
	}

	resmgr->master_bias_users--;
	if (resmgr->master_bias_users == 0) {
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BIAS,
						 0x80, 0x00);
		wcd_resmgr_codec_reg_update_bits(resmgr,
						WCD93XX_ANA_BIAS, 0x20, 0x00);
	}
	mutex_unlock(&resmgr->master_bias_lock);
	return 0;
}
EXPORT_SYMBOL(wcd_resmgr_disable_master_bias);

static int wcd_resmgr_enable_clk_mclk(struct wcd9xxx_resmgr_v2 *resmgr)
{
	/* Enable mclk requires master bias to be enabled first */
	if (resmgr->master_bias_users <= 0) {
		pr_err("%s: Cannot turn on MCLK, BG is not enabled\n",
			__func__);
		return -EINVAL;
	}

	if (((resmgr->clk_mclk_users == 0) &&
	     (resmgr->clk_type == WCD_CLK_MCLK)) ||
	    ((resmgr->clk_mclk_users > 0) &&
	    (resmgr->clk_type != WCD_CLK_MCLK))) {
		pr_err("%s: Error enabling MCLK, clk_type: %s\n",
			__func__,
			wcd_resmgr_clk_type_to_str(resmgr->clk_type));
		return -EINVAL;
	}

	if (++resmgr->clk_mclk_users == 1) {
		wcd_resmgr_codec_reg_update_bits(resmgr,
				WCD93XX_ANA_CLK_TOP, 0x80, 0x80);
		wcd_resmgr_codec_reg_update_bits(resmgr,
				WCD93XX_ANA_CLK_TOP, 0x08, 0x00);
		wcd_resmgr_codec_reg_update_bits(resmgr,
				WCD93XX_ANA_CLK_TOP, 0x04, 0x04);
		if (resmgr->codec_type != WCD9335) {
			/*
			 * In tavil clock contrl register is changed
			 * to CLK_SYS_MCLK_PRG
			 */
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x80, 0x80);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x30, 0x10);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x02, 0x00);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x01, 0x01);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x02, 0x00);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CDC_CLK_RST_CTRL_FS_CNT_CONTROL,
					0x01, 0x01);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CDC_CLK_RST_CTRL_MCLK_CONTROL,
					0x01, 0x01);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CODEC_RPM_CLK_GATE, 0x03, 0x00);
		} else {
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CDC_CLK_RST_CTRL_FS_CNT_CONTROL,
					0x01, 0x01);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CDC_CLK_RST_CTRL_MCLK_CONTROL,
					0x01, 0x01);
		}
		/*
		 * 10us sleep is required after clock is enabled
		 * as per HW requirement
		 */
		usleep_range(10, 15);
	}

	resmgr->clk_type = WCD_CLK_MCLK;

	pr_debug("%s: mclk_users: %d, clk_type: %s\n", __func__,
		 resmgr->clk_mclk_users,
		 wcd_resmgr_clk_type_to_str(resmgr->clk_type));

	return 0;
}

static int wcd_resmgr_disable_clk_mclk(struct wcd9xxx_resmgr_v2 *resmgr)
{
	if (resmgr->clk_mclk_users <= 0) {
		pr_err("%s: No mclk users, cannot disable mclk\n", __func__);
		return -EINVAL;
	}

	if (--resmgr->clk_mclk_users == 0) {
		if (resmgr->clk_rco_users > 0) {
			/* MCLK to RCO switch */
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_ANA_CLK_TOP,
					0x08, 0x08);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x02, 0x02);
			/* Disable clock buffer */
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x80, 0x00);
			resmgr->clk_type = WCD_CLK_RCO;
		} else {
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_ANA_CLK_TOP,
					0x04, 0x00);
			wcd_resmgr_codec_reg_update_bits(resmgr,
					WCD93XX_CLK_SYS_MCLK_PRG, 0x81, 0x00);
			resmgr->clk_type = WCD_CLK_OFF;
		}

		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_CLK_TOP,
						 0x80, 0x00);
	}

	if ((resmgr->codec_type != WCD9335) &&
	    (resmgr->clk_type == WCD_CLK_OFF))
		wcd_resmgr_set_sido_input_src(resmgr, SIDO_SOURCE_INTERNAL);

	pr_debug("%s: mclk_users: %d, clk_type: %s\n", __func__,
		 resmgr->clk_mclk_users,
		 wcd_resmgr_clk_type_to_str(resmgr->clk_type));

	return 0;
}

static void wcd_resmgr_set_buck_accuracy(struct wcd9xxx_resmgr_v2 *resmgr)
{
	wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
					 0x02, 0x02);
	/* 100us sleep needed after HIGH_ACCURACY_PRE_EN1 */
	usleep_range(100, 110);
	wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
					 0x01, 0x01);
	/* 100us sleep needed after HIGH_ACCURACY_PRE_EN2 */
	usleep_range(100, 110);
	wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
					 0x04, 0x04);
	/* 100us sleep needed after HIGH_ACCURACY_EN */
	usleep_range(100, 110);
}

static int wcd_resmgr_enable_clk_rco(struct wcd9xxx_resmgr_v2 *resmgr)
{
	bool rco_cal_done = true;

	resmgr->clk_rco_users++;
	if ((resmgr->clk_rco_users == 1) &&
	    ((resmgr->clk_type == WCD_CLK_OFF) ||
	     (resmgr->clk_mclk_users == 0))) {
		pr_warn("%s: RCO enable requires MCLK to be ON first\n",
			__func__);
		resmgr->clk_rco_users--;
		return -EINVAL;
	} else if ((resmgr->clk_rco_users == 1) &&
		   (resmgr->clk_mclk_users)) {
		/* RCO Enable */
		if (resmgr->sido_input_src == SIDO_SOURCE_INTERNAL) {
			wcd_resmgr_codec_reg_update_bits(resmgr,
				 WCD93XX_ANA_RCO,
				 0x80, 0x80);
			if (resmgr->codec_type != WCD9335)
				wcd_resmgr_set_buck_accuracy(resmgr);
		}

		/*
		 * 20us required after RCO BG is enabled as per HW
		 * requirements
		 */
		usleep_range(20, 25);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_RCO,
						 0x40, 0x40);
		/*
		 * 20us required after RCO is enabled as per HW
		 * requirements
		 */
		usleep_range(20, 25);
		/* RCO Calibration */
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_RCO,
						 0x04, 0x04);
		if (resmgr->codec_type != WCD9335)
			/*
			 * For wcd934x and wcd936x codecs, 20us sleep is needed
			 * after enabling RCO calibration
			 */
			usleep_range(20, 25);

		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_RCO,
						 0x04, 0x00);
		if (resmgr->codec_type != WCD9335)
			/*
			 * For wcd934x and wcd936x codecs, 20us sleep is needed
			 * after disabling RCO calibration
			 */
			usleep_range(20, 25);

		/* RCO calibration takes app. 5ms to complete */
		usleep_range(WCD9XXX_RCO_CALIBRATION_DELAY_INC_US,
		       WCD9XXX_RCO_CALIBRATION_DELAY_INC_US + 100);
		if (wcd_resmgr_codec_reg_read(resmgr, WCD93XX_ANA_RCO) & 0x02)
			rco_cal_done = false;

		WARN((!rco_cal_done), "RCO Calibration failed\n");

		/* Switch MUX to RCO */
		if (resmgr->clk_mclk_users == 1) {
			wcd_resmgr_codec_reg_update_bits(resmgr,
							WCD93XX_ANA_CLK_TOP,
							0x08, 0x08);
			wcd_resmgr_codec_reg_update_bits(resmgr,
						 WCD93XX_CLK_SYS_MCLK_PRG,
						 0x02, 0x02);
			resmgr->clk_type = WCD_CLK_RCO;
		}
	}
	pr_debug("%s: rco clk users: %d, clk_type: %s\n", __func__,
		 resmgr->clk_rco_users,
		 wcd_resmgr_clk_type_to_str(resmgr->clk_type));

	return 0;
}

static int wcd_resmgr_disable_clk_rco(struct wcd9xxx_resmgr_v2 *resmgr)
{
	if ((resmgr->clk_rco_users <= 0) ||
	    (resmgr->clk_type == WCD_CLK_OFF)) {
		pr_err("%s: rco_clk_users = %d, clk_type = %d, cannot disable\n",
			__func__, resmgr->clk_rco_users, resmgr->clk_type);
		return -EINVAL;
	}

	resmgr->clk_rco_users--;

	if ((resmgr->clk_rco_users == 0) &&
	    (resmgr->clk_type == WCD_CLK_RCO)) {
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_CLK_TOP,
						 0x08, 0x00);
		wcd_resmgr_codec_reg_update_bits(resmgr,
						 WCD93XX_CLK_SYS_MCLK_PRG,
						 0x02, 0x00);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_CLK_TOP,
						 0x04, 0x00);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_RCO,
						 0x40, 0x00);
		if (resmgr->sido_input_src == SIDO_SOURCE_INTERNAL)
			wcd_resmgr_codec_reg_update_bits(resmgr,
							 WCD93XX_ANA_RCO,
							 0x80, 0x00);
		wcd_resmgr_codec_reg_update_bits(resmgr,
						 WCD93XX_CLK_SYS_MCLK_PRG,
						 0x01, 0x00);
		resmgr->clk_type = WCD_CLK_OFF;
	} else if ((resmgr->clk_rco_users == 0) &&
	      (resmgr->clk_mclk_users)) {
		/* Disable RCO while MCLK is ON */
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_RCO,
						 0x40, 0x00);
		if (resmgr->sido_input_src == SIDO_SOURCE_INTERNAL)
			wcd_resmgr_codec_reg_update_bits(resmgr,
							 WCD93XX_ANA_RCO,
							 0x80, 0x00);
	}

	if ((resmgr->codec_type != WCD9335) &&
	    (resmgr->clk_type == WCD_CLK_OFF))
		wcd_resmgr_set_sido_input_src(resmgr, SIDO_SOURCE_INTERNAL);

	pr_debug("%s: rco clk users: %d, clk_type: %s\n", __func__,
		 resmgr->clk_rco_users,
		 wcd_resmgr_clk_type_to_str(resmgr->clk_type));

	return 0;
}

/*
 * wcd_resmgr_enable_clk_block: enable MCLK or RCO
 * @resmgr: handle to struct wcd9xxx_resmgr_v2
 * @type: Clock type to enable
 */
int wcd_resmgr_enable_clk_block(struct wcd9xxx_resmgr_v2 *resmgr,
				enum wcd_clock_type type)
{
	int ret;

	switch (type) {
	case WCD_CLK_MCLK:
		ret = wcd_resmgr_enable_clk_mclk(resmgr);
		break;
	case WCD_CLK_RCO:
		ret = wcd_resmgr_enable_clk_rco(resmgr);
		break;
	default:
		pr_err("%s: Unknown Clock type: %s\n", __func__,
			wcd_resmgr_clk_type_to_str(type));
		ret = -EINVAL;
		break;
	};

	if (ret)
		pr_err("%s: Enable clock %s failed\n", __func__,
			wcd_resmgr_clk_type_to_str(type));

	return ret;
}
EXPORT_SYMBOL(wcd_resmgr_enable_clk_block);

void wcd_resmgr_set_sido_input_src(struct wcd9xxx_resmgr_v2 *resmgr,
					  int sido_src)
{
	if (!resmgr)
		return;

	if (sido_src == resmgr->sido_input_src)
		return;

	if (sido_src == SIDO_SOURCE_INTERNAL) {
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
						 0x04, 0x00);
		usleep_range(100, 110);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
						 0x03, 0x00);
		usleep_range(100, 110);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_RCO,
						 0x80, 0x00);
		usleep_range(100, 110);
		resmgr->sido_input_src = SIDO_SOURCE_INTERNAL;
		pr_debug("%s: sido input src to internal\n", __func__);
	} else if (sido_src == SIDO_SOURCE_RCO_BG) {
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_RCO,
						 0x80, 0x80);
		usleep_range(100, 110);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
						 0x02, 0x02);
		usleep_range(100, 110);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
						 0x01, 0x01);
		usleep_range(100, 110);
		wcd_resmgr_codec_reg_update_bits(resmgr, WCD93XX_ANA_BUCK_CTL,
						 0x04, 0x04);
		usleep_range(100, 110);
		resmgr->sido_input_src = SIDO_SOURCE_RCO_BG;
		pr_debug("%s: sido input src to external\n", __func__);
	}
}
EXPORT_SYMBOL(wcd_resmgr_set_sido_input_src);

/*
 * wcd_resmgr_set_sido_input_src_locked:
 *   Set SIDO input in BG_CLK locked context
 *
 * @resmgr: handle to struct wcd9xxx_resmgr_v2
 * @sido_src: Select the SIDO input source
 */
void wcd_resmgr_set_sido_input_src_locked(struct wcd9xxx_resmgr_v2 *resmgr,
					  int sido_src)
{
	if (!resmgr)
		return;

	WCD9XXX_V2_BG_CLK_LOCK(resmgr);
	wcd_resmgr_set_sido_input_src(resmgr, sido_src);
	WCD9XXX_V2_BG_CLK_UNLOCK(resmgr);
}
EXPORT_SYMBOL(wcd_resmgr_set_sido_input_src_locked);

/*
 * wcd_resmgr_disable_clk_block: disable MCLK or RCO
 * @resmgr: handle to struct wcd9xxx_resmgr_v2
 * @type: Clock type to disable
 */
int wcd_resmgr_disable_clk_block(struct wcd9xxx_resmgr_v2 *resmgr,
				enum wcd_clock_type type)
{
	int ret;

	switch (type) {
	case WCD_CLK_MCLK:
		ret = wcd_resmgr_disable_clk_mclk(resmgr);
		break;
	case WCD_CLK_RCO:
		ret = wcd_resmgr_disable_clk_rco(resmgr);
		break;
	default:
		pr_err("%s: Unknown Clock type: %s\n", __func__,
			wcd_resmgr_clk_type_to_str(type));
		ret = -EINVAL;
		break;
	};

	if (ret)
		pr_err("%s: Disable clock %s failed\n", __func__,
			wcd_resmgr_clk_type_to_str(type));

	return ret;
}
EXPORT_SYMBOL(wcd_resmgr_disable_clk_block);

/*
 * wcd_resmgr_init: initialize wcd resource manager
 * @core_res: handle to struct wcd9xxx_core_resource
 *
 * Early init call without a handle to snd_soc_component *
 */
struct wcd9xxx_resmgr_v2 *wcd_resmgr_init(
		struct wcd9xxx_core_resource *core_res,
		struct snd_soc_component *component)
{
	struct wcd9xxx_resmgr_v2 *resmgr;
	struct wcd9xxx *wcd9xxx;

	resmgr = kzalloc(sizeof(struct wcd9xxx_resmgr_v2), GFP_KERNEL);
	if (!resmgr)
		return ERR_PTR(-ENOMEM);

	wcd9xxx = container_of(core_res, struct wcd9xxx, core_res);
	if (!wcd9xxx) {
		kfree(resmgr);
		pr_err("%s: Cannot get wcd9xx pointer\n", __func__);
		return ERR_PTR(-EINVAL);
	}

	mutex_init(&resmgr->codec_bg_clk_lock);
	mutex_init(&resmgr->master_bias_lock);
	resmgr->master_bias_users = 0;
	resmgr->clk_mclk_users = 0;
	resmgr->clk_rco_users = 0;
	resmgr->master_bias_users = 0;
	resmgr->component = component;
	resmgr->core_res = core_res;
	resmgr->sido_input_src = SIDO_SOURCE_INTERNAL;
	resmgr->codec_type = wcd9xxx->type;

	return resmgr;
}
EXPORT_SYMBOL(wcd_resmgr_init);

/*
 * wcd_resmgr_remove: Clean-up wcd resource manager
 * @resmgr: handle to struct wcd9xxx_resmgr_v2
 */
void wcd_resmgr_remove(struct wcd9xxx_resmgr_v2 *resmgr)
{
	mutex_destroy(&resmgr->master_bias_lock);
	kfree(resmgr);
}
EXPORT_SYMBOL(wcd_resmgr_remove);

/*
 * wcd_resmgr_post_init: post init call to assign codec handle
 * @resmgr: handle to struct wcd9xxx_resmgr_v2 created during early init
 * @resmgr_cb: codec callback function for resmgr
 * @component: handle to struct snd_soc_component
 */
int wcd_resmgr_post_init(struct wcd9xxx_resmgr_v2 *resmgr,
			 const struct wcd_resmgr_cb *resmgr_cb,
			 struct snd_soc_component *component)
{
	if (!resmgr) {
		pr_err("%s: resmgr not allocated\n", __func__);
		return -EINVAL;
	}

	if (!component) {
		pr_err("%s: Codec memory is NULL, nothing to post init\n",
			__func__);
		return -EINVAL;
	}

	resmgr->component = component;
	resmgr->resmgr_cb = resmgr_cb;

	return 0;
}
EXPORT_SYMBOL(wcd_resmgr_post_init);

MODULE_DESCRIPTION("wcd9xxx resmgr v2 module");
MODULE_LICENSE("GPL v2");
