soc: tegra: rt5640: add speaker AMP EDP support
Dara Ramesh [Tue, 22 Jan 2013 14:44:33 +0000 (19:44 +0530)]
a) registered speaker AMP EDP client
b) implemented throttle callback function for EDP

Bug 1160686

Change-Id: I44d006613e9972709a3c0d8ffb7858c09271c1b2
Signed-off-by: Dara Ramesh <dramesh@nvidia.com>
Reviewed-on: http://git-master/r/193066
Reviewed-by: Hayden Du <haydend@nvidia.com>
Reviewed-by: Sivaram Nair <sivaramn@nvidia.com>
Reviewed-by: Automatic_Commit_Validation_User
Tested-by: Vijay Mali <vmali@nvidia.com>
Reviewed-by: Kerwin Wan <kerwinw@nvidia.com>
Reviewed-by: Sumit Bhattacharya <sumitb@nvidia.com>

sound/soc/tegra/tegra_rt5640.c

index 4460953..7b3ed82 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/gpio.h>
 #include <linux/regulator/consumer.h>
 #include <linux/delay.h>
+#include <linux/edp.h>
 #ifdef CONFIG_SWITCH
 #include <linux/switch.h>
 #endif
 #define GPIO_EXT_MIC_EN BIT(3)
 #define GPIO_HP_DET     BIT(4)
 
+#define DAI_LINK_HIFI          0
+#define DAI_LINK_SPDIF         1
+#define DAI_LINK_BTSCO         2
+#define NUM_DAI_LINKS  3
+
 struct tegra30_i2s *i2s_tfa = NULL;
 struct snd_soc_codec *codec_rt;
 
@@ -74,6 +80,8 @@ struct tegra_rt5640 {
        struct regulator *spk_reg;
        struct regulator *dmic_reg;
        struct regulator *cdc_en;
+       struct snd_soc_card *pcard;
+       struct edp_client *spk_edp_client;
        int gpio_requested;
 #ifdef CONFIG_SWITCH
        int jack_status;
@@ -439,6 +447,54 @@ static struct snd_soc_jack_pin tegra_rt5640_hp_jack_pins[] = {
 
 #endif
 
+static void tegra_speaker_edp_set_volume(struct snd_soc_codec *codec,
+                                        int l_vol,
+                                        int r_vol)
+{
+       snd_soc_update_bits(codec,
+                           RT5640_SPK_VOL,
+                           RT5640_L_VOL_MASK,
+                           l_vol << RT5640_L_VOL_SFT);
+       snd_soc_update_bits(codec,
+                           RT5640_SPK_VOL,
+                           RT5640_R_VOL_MASK,
+                           r_vol << RT5640_R_VOL_SFT);
+}
+
+static void tegra_speaker_throttle(unsigned int new_state,  void *priv_data)
+{
+       struct tegra_rt5640 *machine = priv_data;
+       struct snd_soc_card *card;
+       struct snd_soc_codec *codec;
+
+       if (!machine)
+               return;
+
+       card = machine->pcard;
+       codec = card->rtd[DAI_LINK_HIFI].codec;
+
+       /* set codec volume to reflect the new E-state */
+       switch (new_state) {
+       case TEGRA_SPK_EDP_NEG_1:
+               /* set codec voulme to 0dB (100%), E-1 state */
+               tegra_speaker_edp_set_volume(codec, 0x0, 0x0);
+               break;
+       case TEGRA_SPK_EDP_ZERO:
+               /* set codec volume to -16.5dB (78%), E0 state */
+               tegra_speaker_edp_set_volume(codec, 0x13, 0x13);
+               break;
+       case TEGRA_SPK_EDP_1:
+               /* turn off codec volume, -46.5 dB, E1 state */
+               tegra_speaker_edp_set_volume(codec, 0x27, 0x27);
+               break;
+       default:
+               pr_err("%s: New E-state %d don't support!\n",
+                       __func__, new_state);
+               break;
+       }
+
+}
+
 static int tegra_rt5640_event_int_spk(struct snd_soc_dapm_widget *w,
                                        struct snd_kcontrol *k, int event)
 {
@@ -446,6 +502,9 @@ static int tegra_rt5640_event_int_spk(struct snd_soc_dapm_widget *w,
        struct snd_soc_card *card = dapm->card;
        struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
        struct tegra_asoc_platform_data *pdata = machine->pdata;
+       struct snd_soc_codec *codec = card->rtd[DAI_LINK_HIFI].codec;
+       unsigned int approved = TEGRA_SPK_EDP_NUM_STATES;
+       int ret;
 
        if (machine->spk_reg) {
                if (SND_SOC_DAPM_EVENT_ON(event)) {
@@ -469,6 +528,36 @@ static int tegra_rt5640_event_int_spk(struct snd_soc_dapm_widget *w,
                                Tfa9887_Powerdown(1);
                }
        }
+
+       if (machine->spk_edp_client == NULL)
+               goto err_null_spk_edp_client;
+
+       if (SND_SOC_DAPM_EVENT_ON(event)) {
+               ret = edp_update_client_request(machine->spk_edp_client,
+                                               TEGRA_SPK_EDP_NEG_1,
+                                               &approved);
+               if (ret || approved != TEGRA_SPK_EDP_NEG_1) {
+                       if (approved == TEGRA_SPK_EDP_ZERO)
+                               /* set codec volume to -16.5dB (78%),E0 state */
+                               tegra_speaker_edp_set_volume(codec, 0x13, 0x13);
+                       else if (approved == TEGRA_SPK_EDP_1)
+                               /* turn off codec volume,-46.5 dB, E1 state */
+                               tegra_speaker_edp_set_volume(codec, 0x27, 0x27);
+               } else {
+                       /* set codec voulme to 0dB (100%), E-1 state */
+                       tegra_speaker_edp_set_volume(codec, 0x0, 0x0);
+               }
+       } else {
+               ret = edp_update_client_request(machine->spk_edp_client,
+                                               TEGRA_SPK_EDP_1,
+                                               NULL);
+               if (ret) {
+                       dev_err(card->dev,
+                               "E+1 state transition failed\n");
+               }
+       }
+
+err_null_spk_edp_client:
        if (!(machine->gpio_requested & GPIO_SPKR_EN))
                return 0;
 
@@ -671,8 +760,8 @@ static int tegra_rt5640_init(struct snd_soc_pcm_runtime *rtd)
        return 0;
 }
 
-static struct snd_soc_dai_link tegra_rt5640_dai[] = {
-       {
+static struct snd_soc_dai_link tegra_rt5640_dai[NUM_DAI_LINKS] = {
+       [DAI_LINK_HIFI] = {
                .name = "RT5640",
                .stream_name = "RT5640 PCM",
                .codec_name = "rt5640.4-001c",
@@ -682,7 +771,7 @@ static struct snd_soc_dai_link tegra_rt5640_dai[] = {
                .init = tegra_rt5640_init,
                .ops = &tegra_rt5640_ops,
        },
-       {
+       [DAI_LINK_SPDIF] = {
                .name = "SPDIF",
                .stream_name = "SPDIF PCM",
                .codec_name = "spdif-dit.0",
@@ -691,7 +780,7 @@ static struct snd_soc_dai_link tegra_rt5640_dai[] = {
                .codec_dai_name = "dit-hifi",
                .ops = &tegra_spdif_ops,
        },
-       {
+       [DAI_LINK_BTSCO] = {
                .name = "BT-SCO",
                .stream_name = "BT SCO PCM",
                .codec_name = "spdif-dit.1",
@@ -812,6 +901,8 @@ static __devinit int tegra_rt5640_driver_probe(struct platform_device *pdev)
        struct snd_soc_card *card = &snd_soc_tegra_rt5640;
        struct tegra_rt5640 *machine;
        struct tegra_asoc_platform_data *pdata;
+       struct snd_soc_codec *codec;
+       struct edp_manager *battery_manager = NULL;
        int ret;
 
        pdata = pdev->dev.platform_data;
@@ -889,6 +980,7 @@ static __devinit int tegra_rt5640_driver_probe(struct platform_device *pdev)
        }
 
        machine->pdata = pdata;
+       machine->pcard = card;
 
        ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev, card);
        if (ret)
@@ -949,6 +1041,55 @@ static __devinit int tegra_rt5640_driver_probe(struct platform_device *pdev)
        }
 #endif
 
+
+       if (!pdata->edp_support)
+               return 0;
+
+       machine->spk_edp_client = devm_kzalloc(&pdev->dev,
+                               sizeof(struct edp_client), GFP_KERNEL);
+       if (IS_ERR_OR_NULL(machine->spk_edp_client)) {
+               dev_err(&pdev->dev, "could not allocate edp client\n");
+               return 0;
+       }
+
+       strncpy(machine->spk_edp_client->name, "speaker", EDP_NAME_LEN - 1);
+       machine->spk_edp_client->name[EDP_NAME_LEN - 1] = '\0';
+       machine->spk_edp_client->states = pdata->edp_states;
+       machine->spk_edp_client->num_states = TEGRA_SPK_EDP_NUM_STATES;
+       machine->spk_edp_client->e0_index = TEGRA_SPK_EDP_ZERO;
+       machine->spk_edp_client->priority = EDP_MAX_PRIO + 2;
+       machine->spk_edp_client->throttle = tegra_speaker_throttle;
+       machine->spk_edp_client->private_data = machine;
+
+       battery_manager = edp_get_manager("battery");
+       if (!battery_manager) {
+               dev_err(&pdev->dev, "unable to get edp manager\n");
+       } else {
+               /* register speaker edp client */
+               ret = edp_register_client(battery_manager,
+                       machine->spk_edp_client);
+               if (ret) {
+                       dev_err(&pdev->dev, "unable to register edp client\n");
+                               devm_kfree(&pdev->dev, machine->spk_edp_client);
+                               machine->spk_edp_client = NULL;
+               }
+               codec = card->rtd[DAI_LINK_HIFI].codec;
+               /* set codec volume to -16.5dB (78%), E0 state */
+               tegra_speaker_edp_set_volume(codec, 0x13, 0x13);
+
+               /* request E0 */
+               ret = edp_update_client_request(machine->spk_edp_client,
+                                               TEGRA_SPK_EDP_ZERO,
+                                               NULL);
+               if (ret) {
+                       dev_err(&pdev->dev,
+                               "unable to set E0 EDP state\n");
+                       edp_unregister_client(machine->spk_edp_client);
+                       devm_kfree(&pdev->dev, machine->spk_edp_client);
+                       machine->spk_edp_client = NULL;
+               }
+       }
+
        return 0;
 
 err_unregister_card: