// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
 * Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <bindings/qcom,audio-ext-clk.h>
#include <linux/ratelimit.h>
#ifdef CONFIG_AUDIO_PRM
#include <dsp/audio_prm.h>
#else
#include "audio-ext-clk-up.h"
#endif
enum {
	AUDIO_EXT_CLK_PMI,
	AUDIO_EXT_CLK_LNBB2,
	AUDIO_EXT_CLK_LPASS,
	AUDIO_EXT_CLK_LPASS2,
	AUDIO_EXT_CLK_LPASS3,
	AUDIO_EXT_CLK_LPASS4,
	AUDIO_EXT_CLK_LPASS5,
	AUDIO_EXT_CLK_LPASS6,
	AUDIO_EXT_CLK_LPASS7,
	AUDIO_EXT_CLK_LPASS_CORE_HW_VOTE,
	AUDIO_EXT_CLK_LPASS8,
	AUDIO_EXT_CLK_LPASS_AUDIO_HW_VOTE,
	AUDIO_EXT_CLK_LPASS9,
	AUDIO_EXT_CLK_LPASS10,
	AUDIO_EXT_CLK_LPASS11,
	AUDIO_EXT_CLK_LPASS12,
	AUDIO_EXT_CLK_LPASS13,
	AUDIO_EXT_CLK_LPASS14,
	AUDIO_EXT_CLK_LPASS15,
	AUDIO_EXT_CLK_LPASS16,
	AUDIO_EXT_CLK_LPASS_MAX,
	AUDIO_EXT_CLK_EXTERNAL_PLL = AUDIO_EXT_CLK_LPASS_MAX,
	AUDIO_EXT_CLK_MAX,
};

struct pinctrl_info {
	struct pinctrl *pinctrl;
	struct pinctrl_state *sleep;
	struct pinctrl_state *active;
	char __iomem *base;
};

struct audio_ext_clk {
	struct pinctrl_info pnctrl_info;
	struct clk_fixed_factor fact;
};

struct audio_ext_clk_priv {
	struct device *dev;
	int clk_src;
	uint32_t enable;
#ifdef CONFIG_AUDIO_PRM
	struct clk_cfg prm_clk_cfg;
#endif
	struct audio_ext_clk audio_clk;
	const char *clk_name;
	uint32_t lpass_core_hwvote_client_handle;
	uint32_t lpass_audio_hwvote_client_handle;
};

static inline struct audio_ext_clk_priv *to_audio_clk(struct clk_hw *hw)
{
	return container_of(hw, struct audio_ext_clk_priv, audio_clk.fact.hw);
}

static int audio_ext_clk_prepare(struct clk_hw *hw)
{
	struct audio_ext_clk_priv *clk_priv = to_audio_clk(hw);
	struct pinctrl_info *pnctrl_info = &clk_priv->audio_clk.pnctrl_info;
	int ret;
	static DEFINE_RATELIMIT_STATE(rtl, 1 * HZ, 1);

	if ((clk_priv->clk_src >= AUDIO_EXT_CLK_LPASS) &&
		(clk_priv->clk_src < AUDIO_EXT_CLK_LPASS_MAX) && !clk_priv->enable)  {
#ifdef CONFIG_AUDIO_PRM
	    pr_debug("%s: clk_id %x ", __func__, clk_priv->prm_clk_cfg.clk_id);
		ret = audio_prm_set_lpass_clk_cfg(&clk_priv->prm_clk_cfg,1);
#else
		pr_debug("%s: audio prm not enabled", __func__);
		ret = -EPERM;
#endif
		if (ret < 0) {
			if (__ratelimit(&rtl))
				pr_err_ratelimited("%s prm set lpass clk failed\n",
				__func__);
			return ret;
		}
		clk_priv->enable = 1;
	}

	if (pnctrl_info->pinctrl) {
		ret = pinctrl_select_state(pnctrl_info->pinctrl,
				pnctrl_info->active);
		if (ret) {
			pr_err("%s: active state select failed with %d\n",
				__func__, ret);
			return -EIO;
		}
	}

	if (pnctrl_info->base)
		iowrite32(1, pnctrl_info->base);
	return 0;
}

static void audio_ext_clk_unprepare(struct clk_hw *hw)
{
	struct audio_ext_clk_priv *clk_priv = to_audio_clk(hw);
	struct pinctrl_info *pnctrl_info = &clk_priv->audio_clk.pnctrl_info;
	int ret;
	static DEFINE_RATELIMIT_STATE(rtl, 1 * HZ, 1);

	if (pnctrl_info->pinctrl) {
		ret = pinctrl_select_state(pnctrl_info->pinctrl,
					   pnctrl_info->sleep);
		if (ret) {
			pr_err("%s: active state select failed with %d\n",
				__func__, ret);
			return;
		}
	}

	if ((clk_priv->clk_src >= AUDIO_EXT_CLK_LPASS) &&
		(clk_priv->clk_src < AUDIO_EXT_CLK_LPASS_MAX))  {
		clk_priv->enable = 0;
#ifdef CONFIG_AUDIO_PRM
		pr_debug("%s: clk_id %x", __func__,
				clk_priv->prm_clk_cfg.clk_id);
		ret = audio_prm_set_lpass_clk_cfg(&clk_priv->prm_clk_cfg, 0);
#else
		pr_debug("%s: audio prm not enabled", __func__);
		ret = -EPERM;
#endif
		if (ret < 0) {
			if (__ratelimit(&rtl))
				pr_err_ratelimited("%s: unset lpass clk cfg failed, ret = %d\n",
				__func__, ret);
		}
	}

	if (pnctrl_info->base)
		iowrite32(0, pnctrl_info->base);
}

static u8 audio_ext_clk_get_parent(struct clk_hw *hw)
{
	struct audio_ext_clk_priv *clk_priv = to_audio_clk(hw);
	int num_parents = clk_hw_get_num_parents(hw);
	const char * const *parent_names = hw->init->parent_names;
	u8 i = 0, ret = hw->init->num_parents + 1;

	if ((clk_priv->clk_src == AUDIO_EXT_CLK_PMI) && clk_priv->clk_name) {
		for (i = 0; i < num_parents; i++) {
			if (!strcmp(parent_names[i], clk_priv->clk_name))
				ret = i;
		}
		pr_debug("%s: parent index = %u\n", __func__, ret);
		return ret;
	} else
		return 0;
}

static int lpass_hw_vote_prepare(struct clk_hw *hw)
{
	struct audio_ext_clk_priv *clk_priv = to_audio_clk(hw);
	int ret;
	static DEFINE_RATELIMIT_STATE(rtl, 1 * HZ, 1);

	if (clk_priv->clk_src == AUDIO_EXT_CLK_LPASS_CORE_HW_VOTE)  {
#ifdef CONFIG_AUDIO_PRM
		pr_debug("%s: core vote clk_id %x \n", __func__, clk_priv->prm_clk_cfg.clk_id);
		ret = audio_prm_set_lpass_hw_core_req(&clk_priv->prm_clk_cfg,
			HW_CORE_ID_LPASS, 1);
#else
		pr_debug("%s: audio prm not enabled", __func__);
		ret = -EPERM;
#endif
		if (ret < 0) {
			if (__ratelimit(&rtl))
				pr_err("%s lpass core hw vote failed %d\n",
					__func__, ret);
			return ret;
		}
	}

	if (clk_priv->clk_src == AUDIO_EXT_CLK_LPASS_AUDIO_HW_VOTE)  {
#ifdef CONFIG_AUDIO_PRM
		pr_debug("%s: audio vote clk_id %x \n", __func__, clk_priv->prm_clk_cfg.clk_id);
		ret = audio_prm_set_lpass_hw_core_req(&clk_priv->prm_clk_cfg,
			HW_CORE_ID_DCODEC, 1);
#else
		pr_debug("%s: audio prm not enabled", __func__);
		ret = -EPERM;
#endif
		if (ret < 0) {
			if (__ratelimit(&rtl))
				pr_err("%s lpass audio hw vote failed %d\n",
				__func__, ret);
			return ret;
		}
	}

	return 0;
}

static void lpass_hw_vote_unprepare(struct clk_hw *hw)
{
	struct audio_ext_clk_priv *clk_priv = to_audio_clk(hw);
	int ret = 0;

	if (clk_priv->clk_src == AUDIO_EXT_CLK_LPASS_CORE_HW_VOTE) {
#ifdef CONFIG_AUDIO_PRM
		pr_debug("%s: core vote clk_id %x \n", __func__, clk_priv->prm_clk_cfg.clk_id);
		ret = audio_prm_set_lpass_hw_core_req(&clk_priv->prm_clk_cfg,
				HW_CORE_ID_LPASS, 0);
#else
		pr_debug("%s: audio prm not enabled", __func__);
		ret = -EPERM;
#endif
		if (ret < 0) {
			pr_err("%s lpass core hw vote failed %d\n",
				__func__, ret);
		}
	}

	if (clk_priv->clk_src == AUDIO_EXT_CLK_LPASS_AUDIO_HW_VOTE) {

#ifdef CONFIG_AUDIO_PRM
		pr_debug("%s: audio vote clk_id %x \n", __func__, clk_priv->prm_clk_cfg.clk_id);
		ret = audio_prm_set_lpass_hw_core_req(&clk_priv->prm_clk_cfg,
				HW_CORE_ID_DCODEC, 0);
#else
		pr_debug("%s: audio prm not enabled", __func__);
		ret = -EPERM;
#endif
		if (ret < 0) {
			pr_err("%s lpass audio hw unvote failed %d\n",
				__func__, ret);
		}
	}
}

static const struct clk_ops audio_ext_clk_ops = {
	.prepare = audio_ext_clk_prepare,
	.unprepare = audio_ext_clk_unprepare,
	.get_parent = audio_ext_clk_get_parent,
};

static const struct clk_ops lpass_hw_vote_ops = {
	.prepare = lpass_hw_vote_prepare,
	.unprepare = lpass_hw_vote_unprepare,
};

static const char * const audio_ext_pmi_div_clk[] = {
	"qpnp_clkdiv_1",
	"pms405_div_clk1",
	"pm6150_div_clk1",
	"pm6125_div_clk1",
};

static int audio_ext_clk_dummy_prepare(struct clk_hw *hw)
{
	return 0;
}

static void audio_ext_clk_dummy_unprepare(struct clk_hw *hw)
{

}

static const struct clk_ops audio_ext_clk_dummy_ops = {
	.prepare = audio_ext_clk_dummy_prepare,
	.unprepare = audio_ext_clk_dummy_unprepare,
};

static struct audio_ext_clk audio_clk_array[] = {
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_ext_pmi_clk",
				.parent_names = audio_ext_pmi_div_clk,
				.num_parents =
					 ARRAY_SIZE(audio_ext_pmi_div_clk),
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_ext_pmi_lnbb_clk",
				.parent_names = (const char *[])
							{ "ln_bb_clk2" },
				.num_parents = 1,
				.ops = &audio_ext_clk_dummy_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk2",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk3",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk4",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk5",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk6",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk7",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.hw.init = &(struct clk_init_data){
				.name = "lpass_hw_vote_clk",
				.ops = &lpass_hw_vote_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk8",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.hw.init = &(struct clk_init_data){
				.name = "lpass_audio_hw_vote_clk",
				.ops = &lpass_hw_vote_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk9",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk10",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk11",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk12",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk13",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk14",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk15",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_lpass_mclk16",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
	{
		.pnctrl_info = {NULL},
		.fact = {
			.mult = 1,
			.div = 1,
			.hw.init = &(struct clk_init_data){
				.name = "audio_external_pll_clk",
				.ops = &audio_ext_clk_ops,
			},
		},
	},
};

static int audio_get_pinctrl(struct platform_device *pdev)
{
	struct device *dev =  &pdev->dev;
	struct audio_ext_clk_priv *clk_priv = platform_get_drvdata(pdev);
	struct pinctrl_info *pnctrl_info;
	struct pinctrl *pinctrl;
	int ret;
	u32 reg;

	pnctrl_info = &clk_priv->audio_clk.pnctrl_info;
	if (pnctrl_info->pinctrl) {
		dev_err(dev, "%s: already requested before\n",
			__func__);
		return -EINVAL;
	}

	pinctrl = devm_pinctrl_get(dev);
	if (IS_ERR_OR_NULL(pinctrl)) {
		dev_err(dev, "%s: Unable to get pinctrl handle\n",
			__func__);
		return -EINVAL;
	}
	pnctrl_info->pinctrl = pinctrl;
	/* get all state handles from Device Tree */
	pnctrl_info->sleep = pinctrl_lookup_state(pinctrl, "sleep");
	if (IS_ERR(pnctrl_info->sleep)) {
		dev_err(dev, "%s: could not get sleep pinstate\n",
			__func__);
		goto err;
	}
	pnctrl_info->active = pinctrl_lookup_state(pinctrl, "active");
	if (IS_ERR(pnctrl_info->active)) {
		dev_err(dev, "%s: could not get active pinstate\n",
			__func__);
		goto err;
	}
	/* Reset the TLMM pins to a default state */
	ret = pinctrl_select_state(pnctrl_info->pinctrl,
				   pnctrl_info->sleep);
	if (ret) {
		dev_err(dev, "%s: Disable TLMM pins failed with %d\n",
			__func__, ret);
		goto err;
	}

	ret = of_property_read_u32(dev->of_node, "qcom,mclk-clk-reg", &reg);
	if (ret < 0) {
		dev_dbg(dev, "%s: miss mclk reg\n", __func__);
	} else {
		pnctrl_info->base = ioremap(reg, sizeof(u32));
		if (pnctrl_info->base ==  NULL) {
			dev_err(dev, "%s ioremap failed\n", __func__);
			goto err;
		}
	}

	return 0;

err:
	devm_pinctrl_put(pnctrl_info->pinctrl);
	return -EINVAL;
}

static int audio_put_pinctrl(struct platform_device *pdev)
{
	struct audio_ext_clk_priv *clk_priv = platform_get_drvdata(pdev);
	struct pinctrl_info *pnctrl_info = NULL;

	pnctrl_info = &clk_priv->audio_clk.pnctrl_info;
	if (pnctrl_info && pnctrl_info->pinctrl) {
		devm_pinctrl_put(pnctrl_info->pinctrl);
		pnctrl_info->pinctrl = NULL;
	}

	return 0;
}

static int audio_get_clk_data(struct platform_device *pdev)
{
	int ret;
	struct clk *audio_clk;
	struct clk_hw *clkhw;
	struct clk_onecell_data *clk_data;
	struct audio_ext_clk_priv *clk_priv = platform_get_drvdata(pdev);

	clk_data = devm_kzalloc(&pdev->dev, sizeof(*clk_data), GFP_KERNEL);
	if (!clk_data)
		return -ENOMEM;

	clk_data->clk_num = 1;
	clk_data->clks = devm_kzalloc(&pdev->dev,
				sizeof(struct clk *),
				GFP_KERNEL);
	if (!clk_data->clks)
		return -ENOMEM;

	clkhw = &clk_priv->audio_clk.fact.hw;
	audio_clk = devm_clk_register(&pdev->dev, clkhw);
	if (IS_ERR(audio_clk)) {
		dev_err(&pdev->dev,
			"%s: clock register failed for clk_src = %d\\n",
			__func__, clk_priv->clk_src);
		ret = PTR_ERR(audio_clk);
		return ret;
	}
	clk_data->clks[0] = audio_clk;

	ret = of_clk_add_provider(pdev->dev.of_node,
			 of_clk_src_onecell_get, clk_data);
	if (ret)
		dev_err(&pdev->dev, "%s: clock add failed for clk_src = %d\n",
			__func__, clk_priv->clk_src);

	return ret;
}

static int audio_ref_clk_probe(struct platform_device *pdev)
{
	int ret;
	struct audio_ext_clk_priv *clk_priv;
	u32 clk_freq = 0, clk_id = 0, clk_src = 0, use_pinctrl = 0;

	clk_priv = devm_kzalloc(&pdev->dev, sizeof(*clk_priv), GFP_KERNEL);
	if (!clk_priv)
		return -ENOMEM;

	ret = of_property_read_u32(pdev->dev.of_node,
			"qcom,codec-ext-clk-src",
			&clk_src);
	if (ret) {
		dev_err(&pdev->dev, "%s: could not get clk source, ret = %d\n",
				__func__, ret);
		return ret;
	}

	if (clk_src >= AUDIO_EXT_CLK_MAX) {
		dev_err(&pdev->dev, "%s: Invalid clk source = %d\n",
				__func__, clk_src);
		return -EINVAL;
	}
	clk_priv->clk_name = NULL;
	clk_priv->clk_src = clk_src;
	memcpy(&clk_priv->audio_clk, &audio_clk_array[clk_src],
		   sizeof(struct audio_ext_clk));

#ifdef CONFIG_AUDIO_PRM
	/* Init prm clk cfg default values */
	clk_priv->prm_clk_cfg.clk_id = CLOCK_ID_QUI_MI2S_OSR;
	clk_priv->prm_clk_cfg.clk_freq_in_hz = OSR_CLOCK_9_P600_MHZ;
	clk_priv->prm_clk_cfg.clk_attri = CLOCK_ATTRIBUTE_COUPLE_NO;
	clk_priv->prm_clk_cfg.clk_root = 0;
#endif

	ret = of_property_read_u32(pdev->dev.of_node,
			"qcom,codec-lpass-ext-clk-freq",
			&clk_freq);
	if (!ret) {
#ifdef CONFIG_AUDIO_PRM
		clk_priv->prm_clk_cfg.clk_freq_in_hz = clk_freq;
#endif
	}

	ret = of_property_read_u32(pdev->dev.of_node,
			"qcom,codec-lpass-clk-id",
			&clk_id);
	if (!ret) {
#ifdef CONFIG_AUDIO_PRM
		clk_priv->prm_clk_cfg.clk_id = clk_id;
		dev_dbg(&pdev->dev, "%s: PRM ext-clk freq: %d, lpass clk_id: %d, clk_src: %d\n",
			__func__, clk_priv->prm_clk_cfg.clk_freq_in_hz,
			clk_priv->prm_clk_cfg.clk_id, clk_priv->clk_src);
#endif
	}

        dev_dbg(&pdev->dev, "%s: PRM2 ext-clk freq: %d, lpass clk_id: %d, clk_src: %d\n",
                        __func__, clk_priv->prm_clk_cfg.clk_freq_in_hz,
                        clk_priv->prm_clk_cfg.clk_id, clk_priv->clk_src);

	platform_set_drvdata(pdev, clk_priv);

	ret = of_property_read_string(pdev->dev.of_node, "pmic-clock-names",
				      &clk_priv->clk_name);
	if (ret)
		dev_dbg(&pdev->dev, "%s: could not find pmic clock names\n",
			__func__);
	/*
	 * property qcom,use-pinctrl to be defined in DTSI to val 1
	 * for clock nodes using pinctrl
	 */
	of_property_read_u32(pdev->dev.of_node, "qcom,use-pinctrl",
			     &use_pinctrl);
	dev_dbg(&pdev->dev, "%s: use-pinctrl : %d\n",
		__func__, use_pinctrl);

	if (use_pinctrl) {
		ret = audio_get_pinctrl(pdev);
		if (ret) {
			dev_err(&pdev->dev, "%s: Parsing PMI pinctrl failed\n",
				__func__);
			return ret;
		}
	}

	ret = audio_get_clk_data(pdev);
	if (ret) {
		dev_err(&pdev->dev, "%s: clk_init is failed\n",
			__func__);
		audio_put_pinctrl(pdev);
		return ret;
	}
	return 0;
}

static int audio_ref_clk_remove(struct platform_device *pdev)
{
	audio_put_pinctrl(pdev);

	return 0;
}

static const struct of_device_id audio_ref_clk_match[] = {
	{.compatible = "qcom,audio-ref-clk"},
	{}
};
MODULE_DEVICE_TABLE(of, audio_ref_clk_match);

static struct platform_driver audio_ref_clk_driver = {
	.driver = {
		.name = "audio-ref-clk",
		.owner = THIS_MODULE,
		.of_match_table = audio_ref_clk_match,
		.suppress_bind_attrs = true,
	},
	.probe = audio_ref_clk_probe,
	.remove = audio_ref_clk_remove,
};

int audio_ref_clk_platform_init(void)
{
	return platform_driver_register(&audio_ref_clk_driver);
}

void audio_ref_clk_platform_exit(void)
{
	platform_driver_unregister(&audio_ref_clk_driver);
}

MODULE_DESCRIPTION("Audio Ref Up Clock module platform driver");
MODULE_LICENSE("GPL v2");