soc: tegra: rt5640: add speaker AMP EDP support
Dara Ramesh [Thu, 11 Jul 2013 09:55:26 +0000 (14:55 +0530)]
a) registered speaker AMP EDP client
b) implemented throttle callback function for EDP

Bug 1160686

Reviewed-on: http://git-master/r/193066
(cherry picked from commit a1ff2bc465e71cac89edb78b9283bf41cc940228)

Change-Id: I736bdfc6b592dc388f6dde53da6f670be25125b8
Signed-off-by: Dara Ramesh <dramesh@nvidia.com>
Reviewed-on: http://git-master/r/247814
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Scott Peterson <speterson@nvidia.com>

sound/soc/tegra/tegra_rt5640.c

index 5299f2e..2c14396 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
@@ -78,6 +79,11 @@ const char *tegra_rt5640_i2s_dai_name[TEGRA30_NR_I2S_IFC] = {
 #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;
 
@@ -87,6 +93,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;
@@ -452,6 +460,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 0 dB (100%), E-1 state */
+               tegra_speaker_edp_set_volume(codec, 0x8, 0x8);
+               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)
 {
@@ -459,6 +515,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)) {
@@ -482,6 +541,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 0 dB (100%), E-1 state */
+                       tegra_speaker_edp_set_volume(codec, 0x8, 0x8);
+               }
+       } 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;
 
@@ -823,6 +912,8 @@ static 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;
        int codec_id;
 
@@ -901,6 +992,7 @@ static 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)
@@ -976,6 +1068,55 @@ static 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: