/*
 * dr7-evm.c  --  SoC audio for TI DRA7 EVM
 *
 * Author: Misael Lopez Cruz <misael.lopez@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.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>

//#define NUM_CODECS	3
#define NUM_CODECS	1

struct dra7_snd_data {
	struct platform_device *codec_pdev;
	unsigned int media_mclk_freq;
	unsigned int multichannel_mclk_freq;
//	unsigned int bt_bclk_freq;
	unsigned int media_slots;
	unsigned int multichannel_slots;
	int always_on;
	int media_shared;
	int multichannel_shared;
	const int *rates;
	int num_rates;
};

static const int dra7_snd_rates[] = {
	 5512,  8000, 11025, 16000, 22050,
	32000, 44100, 48000, //88200, 96000,
};

static int dra7_mcasp_reparent(struct snd_soc_card *card,
			       const char *fclk_name,
			       const char *parent_name)
{
	struct clk *gfclk, *parent_clk;
	int ret;

	gfclk = clk_get(card->dev, fclk_name);
	if (IS_ERR(gfclk)) {
		dev_err(card->dev, "failed to get %s\n", fclk_name);
		return PTR_ERR(gfclk);
	}

	parent_clk = clk_get(card->dev, parent_name);
	if (IS_ERR(parent_clk)) {
		dev_err(card->dev, "failed to get new parent clock %s\n",
			parent_name);
		ret = PTR_ERR(parent_clk);
		goto err1;
	}

	ret = clk_set_parent(gfclk, parent_clk);
	if (ret) {
		dev_err(card->dev, "failed to reparent %s\n", fclk_name);
		goto err2;
	}

err2:
	clk_put(parent_clk);
err1:
	clk_put(gfclk);
	return ret;
}

static unsigned int dra7_get_bclk(struct snd_pcm_hw_params *params, int slots)
{
	int sample_size = snd_pcm_format_width(params_format(params));
	int rate = params_rate(params);

//printk("sample_size= %d, slots=%d, rate=%d \n",sample_size, slots, rate);

	return sample_size * slots * rate;
}

static int dra7_snd_media_hwrule_rates(struct snd_pcm_hw_params *params,
				       struct snd_pcm_hw_rule *rule)
{
	struct dra7_snd_data *card_data = rule->private;
	unsigned int slots = card_data->media_slots;
	unsigned int mclk_freq = card_data->media_mclk_freq;
	unsigned int bclk_freq;
	int width = snd_pcm_format_physical_width(params_format(params));
	int rates[10], count = 0;
	int i;

//printk("RES::::%s,%s,%dwidth=%d,card_data->num_rates=%d,slots=%d\n",__FILE__,__func__,__LINE__, width, card_data->num_rates,slots);
	for (i = 0; i < card_data->num_rates; i++) {
		bclk_freq = slots * card_data->rates[i] * width;
//printk("bclk_freq:%d\n",bclk_freq);
		if ((mclk_freq % bclk_freq) == 0)
			rates[count++] = card_data->rates[i];
	}
//printk("RES::::%s,%s,%dcount=%d:var=%d\n",__FILE__,__func__,__LINE__, count,rule->var);
	return snd_interval_list(hw_param_interval(params, rule->var),
				 count, rates, 0);
}

static int dra7_snd_media_startup(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_card *card = rtd->card;
	struct dra7_snd_data *card_data = snd_soc_card_get_drvdata(card);

printk("RES:%s,%s,%d\n",__FILE__,__func__,__LINE__);

        int err;
        
	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
			    dra7_snd_media_hwrule_rates, card_data,
			    SNDRV_PCM_HW_PARAM_RATE, -1);

	return 0;
}

static int dra7_snd_media_init(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_card *card = rtd->card;
	struct dra7_snd_data *card_data = snd_soc_card_get_drvdata(card);
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
printk("RES:::%s,%s,%d\n",__FILE__,__func__,__LINE__);
	/* Minimize artifacts as much as possible if can be afforded */
	if (card_data->always_on)
		rtd->pmdown_time = INT_MAX;
	else
		rtd->pmdown_time = 0;

	/* Forbid runtime PM if the DAI is shared with radio */
	if (card_data->media_shared) {
		dev_info(card->dev, "%s is shared with radio\n", cpu_dai->name);
		pm_runtime_forbid(cpu_dai->dev);
	}

	return 0;
}

#define DS925_AUDIO_FORMAT (SND_SOC_DAIFMT_I2S | \
                SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_NB_IF)

static int ds925_hw_params(struct snd_pcm_substream *substream,
                         struct snd_pcm_hw_params *params)
{
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai *codec_dai = rtd->codec_dai;
        struct snd_soc_card *card = rtd->card;
        struct dra7_snd_data *card_data = snd_soc_card_get_drvdata(card);
        unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
        unsigned int bclk_freq;
        int ret;

        bclk_freq = dra7_get_bclk(params, card_data->media_slots);

printk(" Delphi: %s,%s,%d:bclk_freq=%d\n", __FILE__,__func__,__LINE__,bclk_freq);
        /* McASP driver requires inverted frame for I2S */
        ret = snd_soc_dai_set_fmt(cpu_dai, fmt | SND_SOC_DAIFMT_NB_IF);
        if (ret < 0) {
                dev_err(card->dev, "can't set CPU DAI format %d\n", ret);
                return ret;
        }
     
        /* Set McASP BCLK divider (clkid = 1) */
        ret = snd_soc_dai_set_clkdiv(cpu_dai, 1, 
                                     card_data->media_mclk_freq / bclk_freq);
        if (ret < 0) {
                dev_err(card->dev, "can't set CPU DAI clock divider %d\n", ret);
                return ret;
        }
     
printk(" Delphi: %s,%s,%d:card_data->media_mclk_freq=%d ,\n bclk_freq =%d\n", __FILE__,__func__,__LINE__,card_data->media_mclk_freq,bclk_freq);

        /* Set McASP sysclk from AHCLKX sourced from ATL */
        ret = snd_soc_dai_set_sysclk(cpu_dai, 0,
                                     card_data->media_mclk_freq,
                                     SND_SOC_CLOCK_IN);
        if (ret < 0) {
                dev_err(card->dev, "can't set CPU DAI sysclk %d\n", ret);
                return ret;
        }

        return ret ;
}

static struct snd_soc_ops ds925_ops = {
	.startup = dra7_snd_media_startup,
        .hw_params = ds925_hw_params,
};

static struct snd_soc_dai_link dra7_snd_dai[] = {
#if 0
	{
		/* Media: McASP3 + tlv320aic3106 */
		.name = "Media",
		.codec_dai_name = "tlv320aic3x-hifi",
		.platform_name = "omap-pcm-audio",
		.ops = &dra7_snd_media_ops,
		.init = dra7_snd_media_init,
	},
	{
		/* Multichannel: McASP6 + 3 * tlv320aic3106 */
		.name = "Multichannel",
		.platform_name = "omap-pcm-audio",
		.ops = &dra7_snd_multichannel_ops,
		.codecs = dra7_snd_multichannel_codecs,
		.num_codecs = ARRAY_SIZE(dra7_snd_multichannel_codecs),
		.init = dra7_snd_multichannel_init,
	},
	{
		/* Bluetooth: McASP7 + dummy codec */
		.name = "Bluetooth",
		.platform_name = "omap-pcm-audio",
		.codec_name = "snd-soc-dummy",
		.codec_dai_name = "snd-soc-dummy-dai",
		.ops = &dra7_snd_bt_ops,
	},

#endif
        {
                /* Media: McASP3 + tlv320aic3106 */
                .name = "ds925",
//                .codec_name = "snd-soc-dummy",
                //.codec_dai_name = "snd-soc-dummy-dai",
                .codec_dai_name = "omap-ds925-hifi",
                .platform_name = "omap-pcm-audio",
                .ops = &ds925_ops,
                .init = dra7_snd_media_init,
        },
#if 0
        {
                /* Media: McASP3 + tlv320aic3106 */
                .name = "headset",
//                .codec_name = "snd-soc-dummy",
                //.codec_dai_name = "snd-soc-dummy-dai",
                //.codec_dai_name = "omap-uda1334-hifi",
                .codec_dai_name = "uda134x-hifi",
                .platform_name = "omap-pcm-audio",
                .ops = &ds925_ops,
                .init = dra7_snd_media_init,
        },

#endif
# if 0  // Working with snd dummy combination.
                .name = "ds925",
                .cpu_dai_name = "48464000.mcasp",
                .platform_name = "omap-pcm-audio",
                .codec_name = "snd-soc-dummy",
                .codec_dai_name = "snd-soc-dummy-dai",
//		.init = dra7_snd_media_init,	
                .ops = &ds925_ops,
                
		.name = "ds925",
                .cpu_dai_name = "48464000.mcasp",
                //.cpu_dai_name = "48474000.mcasp",
                .platform_name = "omap-pcm-audio",
                .codec_name = "ds925-audio-codec",
                .codec_dai_name = "omap-ds925-hifi",
		.init = dra7_snd_media_init,	
                .ops = &ds925_ops,
#endif

};

static struct snd_soc_codec_conf dra7_codec_conf[NUM_CODECS];

static int dra7_snd_add_ds925_dai_link(struct snd_soc_card *card,
                                    struct snd_soc_dai_link *dai_link,
                                    const char *prefix)
{
        struct dra7_snd_data *card_data = snd_soc_card_get_drvdata(card);
        struct device_node *node = card->dev->of_node;
        struct device_node *dai_node;
        char prop[32];
        int ret;

        if (!node) {
                dev_err(card->dev, "ds925 node is invalid\n");
                return -EINVAL;
        }

        snprintf(prop, sizeof(prop), "%s-mclk-freq", prefix);
        dev_err(card->dev, "delphi--->%s\n",prop);
        of_property_read_u32(node, prop,
                             &card_data->media_mclk_freq);


        snprintf(prop, sizeof(prop), "%s-cpu", prefix);
	
        dai_node = of_parse_phandle(node, prop, 0);
        if (!dai_node) {
                dev_err(card->dev, "cpu dai node is invalid\n");
                return -EINVAL;
        }

        dai_link->cpu_of_node = dai_node;
printk("RESSSSSSSSSSSSSSSSSSSSSSSSSSS:%s : dai_link->cpu_of_node = %x \n",__func__, dai_link->cpu_of_node);

        snprintf(prop, sizeof(prop), "%s-codec", prefix);
        dev_err(card->dev, "delphi--->%s\n",prop);
        dai_node = of_parse_phandle(node, prop, 0);
        if (!dai_node) {
                dev_err(card->dev, "codec dai node is invalid\n");
                return -EINVAL;
        }

        dai_link->codec_of_node = dai_node;

        snprintf(prop, sizeof(prop), "%s-slots", prefix);
        dev_err(card->dev, "delphi--->%s\n",prop);
        of_property_read_u32(node, prop, &card_data->media_slots);
        if ((card_data->media_slots < 1) ||
            (card_data->media_slots > 32)) {
                dev_err(card->dev, "invalid media slot count %d\n",
                        card_data->media_slots);
                return -EINVAL;
        }

        ret = snd_soc_card_new_dai_links(card, dai_link, 1);
        if (ret < 0) {
                dev_err(card->dev, "failed to add bluetooth dai link %s\n",
                        dai_link->name);
		return ret;
	}

        snprintf(prop, sizeof(prop), "%s-shared", prefix);
        if (of_find_property(node, prop, NULL))
                card_data->media_shared = 1;


        return 0;
}

/* DRA7 CPU board widgets */
static const struct snd_soc_dapm_widget dra7_snd_dapm_widgets[] = {
	/* CPU board input */
	SND_SOC_DAPM_MIC("Main Mic", NULL),
	SND_SOC_DAPM_LINE("Line In", NULL),

	/* CPU board outputs */
	SND_SOC_DAPM_HP("Headphone", NULL),
	SND_SOC_DAPM_LINE("Line Out", NULL),

	/* JAMR3 board inputs */
	SND_SOC_DAPM_LINE("JAMR3 Stereo Aux In", NULL),
	SND_SOC_DAPM_LINE("JAMR3 Mono Mic 1", NULL),
	SND_SOC_DAPM_LINE("JAMR3 Mono Mic 2", NULL),

	/* JAMR3 board outputs */
	SND_SOC_DAPM_LINE("JAMR3 Line Out 1", NULL),
	SND_SOC_DAPM_LINE("JAMR3 Line Out 2", NULL),
	SND_SOC_DAPM_LINE("JAMR3 Line Out 3", NULL),
};

static const struct snd_soc_dapm_widget dra7_res_snd_dapm_widgets[] = {
	/* JRES board outputs */
	SND_SOC_DAPM_LINE("RES Line Out 1", NULL),
	SND_SOC_DAPM_LINE("RES Line Out 2", NULL),
	SND_SOC_DAPM_LINE("RES Line Out 3", NULL),
};
/* Audio machine driver */
static struct snd_soc_card dra7_snd_card = {
	.name = "res925",
	.owner = THIS_MODULE,
//        .dai_link = dra7_snd_dai,
        .num_links = 0, //ARRAY_SIZE(dra7_snd_dai),
//	.dapm_widgets = dra7_snd_dapm_widgets,
//	.dapm_widgets = dra7_res_snd_dapm_widgets,
//	.num_dapm_widgets = ARRAY_SIZE(dra7_res_snd_dapm_widgets),
//	.codec_conf = dra7_codec_conf,
//	.num_configs = ARRAY_SIZE(dra7_codec_conf),
};

static int dra7_snd_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	struct snd_soc_card *card = &dra7_snd_card;
	struct dra7_snd_data *card_data;
	int gpio;
	int ret;

	dev_err(card->dev, "sandy-------------------->%s,%s,%d\n",__FILE__,__func__,__LINE__);

	card->dev = &pdev->dev;
	
	dev_err(card->dev, "sandy-------------------->%s,%s,%d\n",__FILE__,__func__,__LINE__);

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

	if (!node) {
		dev_err(card->dev, "missing of_node\n");
		return -ENODEV;
	}

	card_data->rates = dra7_snd_rates;
	card_data->num_rates = ARRAY_SIZE(dra7_snd_rates);

	ret = snd_soc_of_parse_card_name(card, "ti,model");
	if (ret) {
		dev_err(card->dev, "card name is not provided\n");
		return -ENODEV;
	}

#if 0	
        }

        card_data->codec_pdev = platform_device_register_simple("ds925-audio-codec",
                                                            -1, NULL, 0);
        if (IS_ERR(card_data->codec_pdev)) {
                   dev_err(&pdev->dev,
                          "Cannot instantiate ds925-audio-codec\n");
                   return PTR_ERR(card_data->codec_pdev);
        }
#endif

	snd_soc_card_set_drvdata(card, card_data);

	if (of_find_property(node, "ti,always-on", NULL))
		card_data->always_on = 1;
#if 1 
	ret = dra7_snd_add_ds925_dai_link(card, &dra7_snd_dai[0], "ti,ds925");
        if (ret) {
                dev_err(card->dev, "failed to add media dai link %d\n", ret);
                return ret;
        }
#endif
#if 0
        ret = dra7_snd_add_ds925_dai_link(card, &dra7_snd_dai[0], "ti,headset");
        if (ret) {
                dev_err(card->dev, "failed to add headset media dai link %d\n", ret);
                return ret;
        }
#endif

	ret = snd_soc_register_card(card);
	if (ret) {
		dev_err(card->dev, "failed to register sound card %d\n", ret);
		goto err_card;
	}
#if 1
	ret = dra7_mcasp_reparent(card, "mcasp2_ahclkx_mux", "atl_clkin1_ck");
	if (ret) {
		dev_err(card->dev, "failed to reparent McASP3 %d\n", ret);
		goto err_reparent;
	}
#endif	
#if 0
        ret = dra7_mcasp_reparent(card, "mcasp5_ahclkx_mux", "atl_clkin2_ck");
        if (ret) {
                dev_err(card->dev, "failed to reparent McASP3 %d\n", ret);
                goto err_reparent;
        }
#endif

	return 0;

err_reparent:
	snd_soc_unregister_card(card);
err_card:
	snd_soc_card_reset_dai_links(card);
	return ret;
}
static int dra7_snd_remove(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

	snd_soc_unregister_card(card);
	snd_soc_card_reset_dai_links(card);

	return 0;
}

static const struct of_device_id dra7_snd_of_match[] = {
	{.compatible = "ti,dra7-evm-sound", },
	{ },
};
MODULE_DEVICE_TABLE(of, dra7_snd_of_match);

static struct platform_driver dra7_snd_driver = {
	.driver = {
		.name = "dra7-evm-sound",
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
		.of_match_table = dra7_snd_of_match,
	},
	.probe = dra7_snd_probe,
	.remove = dra7_snd_remove,
};

module_platform_driver(dra7_snd_driver);

MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
MODULE_DESCRIPTION("ALSA SoC for DRA7 EVM");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:dra7-evm-sound");
