linux/sound/soc/intel/mfld_machine.c
<<
>>
Prefs
   1/*
   2 *  mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform
   3 *
   4 *  Copyright (C) 2010 Intel Corp
   5 *  Author: Vinod Koul <vinod.koul@intel.com>
   6 *  Author: Harsha Priya <priya.harsha@intel.com>
   7 *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   8 *
   9 *  This program is free software; you can redistribute it and/or modify
  10 *  it under the terms of the GNU General Public License as published by
  11 *  the Free Software Foundation; version 2 of the License.
  12 *
  13 *  This program is distributed in the hope that it will be useful, but
  14 *  WITHOUT ANY WARRANTY; without even the implied warranty of
  15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16 *  General Public License for more details.
  17 *
  18 *  You should have received a copy of the GNU General Public License along
  19 *  with this program; if not, write to the Free Software Foundation, Inc.,
  20 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  21 *
  22 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  23 */
  24
  25#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  26
  27#include <linux/init.h>
  28#include <linux/device.h>
  29#include <linux/slab.h>
  30#include <linux/io.h>
  31#include <linux/module.h>
  32#include <sound/pcm.h>
  33#include <sound/pcm_params.h>
  34#include <sound/soc.h>
  35#include <sound/jack.h>
  36#include "../codecs/sn95031.h"
  37
  38#define MID_MONO 1
  39#define MID_STEREO 2
  40#define MID_MAX_CAP 5
  41#define MFLD_JACK_INSERT 0x04
  42
  43enum soc_mic_bias_zones {
  44        MFLD_MV_START = 0,
  45        /* mic bias volutage range for Headphones*/
  46        MFLD_MV_HP = 400,
  47        /* mic bias volutage range for American Headset*/
  48        MFLD_MV_AM_HS = 650,
  49        /* mic bias volutage range for Headset*/
  50        MFLD_MV_HS = 2000,
  51        MFLD_MV_UNDEFINED,
  52};
  53
  54static unsigned int     hs_switch;
  55static unsigned int     lo_dac;
  56static struct snd_soc_codec *mfld_codec;
  57
  58struct mfld_mc_private {
  59        void __iomem *int_base;
  60        u8 interrupt_status;
  61};
  62
  63struct snd_soc_jack mfld_jack;
  64
  65/*Headset jack detection DAPM pins */
  66static struct snd_soc_jack_pin mfld_jack_pins[] = {
  67        {
  68                .pin = "Headphones",
  69                .mask = SND_JACK_HEADPHONE,
  70        },
  71        {
  72                .pin = "AMIC1",
  73                .mask = SND_JACK_MICROPHONE,
  74        },
  75};
  76
  77/* jack detection voltage zones */
  78static struct snd_soc_jack_zone mfld_zones[] = {
  79        {MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE},
  80        {MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET},
  81};
  82
  83/* sound card controls */
  84static const char *headset_switch_text[] = {"Earpiece", "Headset"};
  85
  86static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"};
  87
  88static const struct soc_enum headset_enum =
  89        SOC_ENUM_SINGLE_EXT(2, headset_switch_text);
  90
  91static const struct soc_enum lo_enum =
  92        SOC_ENUM_SINGLE_EXT(4, lo_text);
  93
  94static int headset_get_switch(struct snd_kcontrol *kcontrol,
  95        struct snd_ctl_elem_value *ucontrol)
  96{
  97        ucontrol->value.integer.value[0] = hs_switch;
  98        return 0;
  99}
 100
 101static int headset_set_switch(struct snd_kcontrol *kcontrol,
 102        struct snd_ctl_elem_value *ucontrol)
 103{
 104        struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
 105        struct snd_soc_dapm_context *dapm = &card->dapm;
 106
 107        if (ucontrol->value.integer.value[0] == hs_switch)
 108                return 0;
 109
 110        snd_soc_dapm_mutex_lock(dapm);
 111
 112        if (ucontrol->value.integer.value[0]) {
 113                pr_debug("hs_set HS path\n");
 114                snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones");
 115                snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT");
 116        } else {
 117                pr_debug("hs_set EP path\n");
 118                snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones");
 119                snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT");
 120        }
 121
 122        snd_soc_dapm_sync_unlocked(dapm);
 123
 124        snd_soc_dapm_mutex_unlock(dapm);
 125
 126        hs_switch = ucontrol->value.integer.value[0];
 127
 128        return 0;
 129}
 130
 131static void lo_enable_out_pins(struct snd_soc_dapm_context *dapm)
 132{
 133        snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTL");
 134        snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTR");
 135        snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTL");
 136        snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTR");
 137        snd_soc_dapm_enable_pin_unlocked(dapm, "VIB1OUT");
 138        snd_soc_dapm_enable_pin_unlocked(dapm, "VIB2OUT");
 139        if (hs_switch) {
 140                snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones");
 141                snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT");
 142        } else {
 143                snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones");
 144                snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT");
 145        }
 146}
 147
 148static int lo_get_switch(struct snd_kcontrol *kcontrol,
 149        struct snd_ctl_elem_value *ucontrol)
 150{
 151        ucontrol->value.integer.value[0] = lo_dac;
 152        return 0;
 153}
 154
 155static int lo_set_switch(struct snd_kcontrol *kcontrol,
 156        struct snd_ctl_elem_value *ucontrol)
 157{
 158        struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
 159        struct snd_soc_dapm_context *dapm = &card->dapm;
 160
 161        if (ucontrol->value.integer.value[0] == lo_dac)
 162                return 0;
 163
 164        snd_soc_dapm_mutex_lock(dapm);
 165
 166        /* we dont want to work with last state of lineout so just enable all
 167         * pins and then disable pins not required
 168         */
 169        lo_enable_out_pins(dapm);
 170
 171        switch (ucontrol->value.integer.value[0]) {
 172        case 0:
 173                pr_debug("set vibra path\n");
 174                snd_soc_dapm_disable_pin_unlocked(dapm, "VIB1OUT");
 175                snd_soc_dapm_disable_pin_unlocked(dapm, "VIB2OUT");
 176                snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0);
 177                break;
 178
 179        case 1:
 180                pr_debug("set hs  path\n");
 181                snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones");
 182                snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT");
 183                snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x22);
 184                break;
 185
 186        case 2:
 187                pr_debug("set spkr path\n");
 188                snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTL");
 189                snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTR");
 190                snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x44);
 191                break;
 192
 193        case 3:
 194                pr_debug("set null path\n");
 195                snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTL");
 196                snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTR");
 197                snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x66);
 198                break;
 199        }
 200
 201        snd_soc_dapm_sync_unlocked(dapm);
 202
 203        snd_soc_dapm_mutex_unlock(dapm);
 204
 205        lo_dac = ucontrol->value.integer.value[0];
 206        return 0;
 207}
 208
 209static const struct snd_kcontrol_new mfld_snd_controls[] = {
 210        SOC_ENUM_EXT("Playback Switch", headset_enum,
 211                        headset_get_switch, headset_set_switch),
 212        SOC_ENUM_EXT("Lineout Mux", lo_enum,
 213                        lo_get_switch, lo_set_switch),
 214};
 215
 216static const struct snd_soc_dapm_widget mfld_widgets[] = {
 217        SND_SOC_DAPM_HP("Headphones", NULL),
 218        SND_SOC_DAPM_MIC("Mic", NULL),
 219};
 220
 221static const struct snd_soc_dapm_route mfld_map[] = {
 222        {"Headphones", NULL, "HPOUTR"},
 223        {"Headphones", NULL, "HPOUTL"},
 224        {"Mic", NULL, "AMIC1"},
 225};
 226
 227static void mfld_jack_check(unsigned int intr_status)
 228{
 229        struct mfld_jack_data jack_data;
 230
 231        jack_data.mfld_jack = &mfld_jack;
 232        jack_data.intr_id = intr_status;
 233
 234        sn95031_jack_detection(&jack_data);
 235        /* TODO: add american headset detection post gpiolib support */
 236}
 237
 238static int mfld_init(struct snd_soc_pcm_runtime *runtime)
 239{
 240        struct snd_soc_dapm_context *dapm = &runtime->card->dapm;
 241        int ret_val;
 242
 243        mfld_codec = runtime->codec;
 244
 245        /* default is earpiece pin, userspace sets it explcitly */
 246        snd_soc_dapm_disable_pin(dapm, "Headphones");
 247        /* default is lineout NC, userspace sets it explcitly */
 248        snd_soc_dapm_disable_pin(dapm, "LINEOUTL");
 249        snd_soc_dapm_disable_pin(dapm, "LINEOUTR");
 250        lo_dac = 3;
 251        hs_switch = 0;
 252        /* we dont use linein in this so set to NC */
 253        snd_soc_dapm_disable_pin(dapm, "LINEINL");
 254        snd_soc_dapm_disable_pin(dapm, "LINEINR");
 255
 256        /* Headset and button jack detection */
 257        ret_val = snd_soc_jack_new(mfld_codec, "Intel(R) MID Audio Jack",
 258                        SND_JACK_HEADSET | SND_JACK_BTN_0 |
 259                        SND_JACK_BTN_1, &mfld_jack);
 260        if (ret_val) {
 261                pr_err("jack creation failed\n");
 262                return ret_val;
 263        }
 264
 265        ret_val = snd_soc_jack_add_pins(&mfld_jack,
 266                        ARRAY_SIZE(mfld_jack_pins), mfld_jack_pins);
 267        if (ret_val) {
 268                pr_err("adding jack pins failed\n");
 269                return ret_val;
 270        }
 271        ret_val = snd_soc_jack_add_zones(&mfld_jack,
 272                        ARRAY_SIZE(mfld_zones), mfld_zones);
 273        if (ret_val) {
 274                pr_err("adding jack zones failed\n");
 275                return ret_val;
 276        }
 277
 278        /* we want to check if anything is inserted at boot,
 279         * so send a fake event to codec and it will read adc
 280         * to find if anything is there or not */
 281        mfld_jack_check(MFLD_JACK_INSERT);
 282        return ret_val;
 283}
 284
 285static struct snd_soc_dai_link mfld_msic_dailink[] = {
 286        {
 287                .name = "Medfield Headset",
 288                .stream_name = "Headset",
 289                .cpu_dai_name = "Headset-cpu-dai",
 290                .codec_dai_name = "SN95031 Headset",
 291                .codec_name = "sn95031",
 292                .platform_name = "sst-platform",
 293                .init = mfld_init,
 294        },
 295        {
 296                .name = "Medfield Speaker",
 297                .stream_name = "Speaker",
 298                .cpu_dai_name = "Speaker-cpu-dai",
 299                .codec_dai_name = "SN95031 Speaker",
 300                .codec_name = "sn95031",
 301                .platform_name = "sst-platform",
 302                .init = NULL,
 303        },
 304        {
 305                .name = "Medfield Vibra",
 306                .stream_name = "Vibra1",
 307                .cpu_dai_name = "Vibra1-cpu-dai",
 308                .codec_dai_name = "SN95031 Vibra1",
 309                .codec_name = "sn95031",
 310                .platform_name = "sst-platform",
 311                .init = NULL,
 312        },
 313        {
 314                .name = "Medfield Haptics",
 315                .stream_name = "Vibra2",
 316                .cpu_dai_name = "Vibra2-cpu-dai",
 317                .codec_dai_name = "SN95031 Vibra2",
 318                .codec_name = "sn95031",
 319                .platform_name = "sst-platform",
 320                .init = NULL,
 321        },
 322        {
 323                .name = "Medfield Compress",
 324                .stream_name = "Speaker",
 325                .cpu_dai_name = "Compress-cpu-dai",
 326                .codec_dai_name = "SN95031 Speaker",
 327                .codec_name = "sn95031",
 328                .platform_name = "sst-platform",
 329                .init = NULL,
 330        },
 331};
 332
 333/* SoC card */
 334static struct snd_soc_card snd_soc_card_mfld = {
 335        .name = "medfield_audio",
 336        .owner = THIS_MODULE,
 337        .dai_link = mfld_msic_dailink,
 338        .num_links = ARRAY_SIZE(mfld_msic_dailink),
 339
 340        .controls = mfld_snd_controls,
 341        .num_controls = ARRAY_SIZE(mfld_snd_controls),
 342        .dapm_widgets = mfld_widgets,
 343        .num_dapm_widgets = ARRAY_SIZE(mfld_widgets),
 344        .dapm_routes = mfld_map,
 345        .num_dapm_routes = ARRAY_SIZE(mfld_map),
 346};
 347
 348static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev)
 349{
 350        struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev;
 351
 352        memcpy_fromio(&mc_private->interrupt_status,
 353                        ((void *)(mc_private->int_base)),
 354                        sizeof(u8));
 355        return IRQ_WAKE_THREAD;
 356}
 357
 358static irqreturn_t snd_mfld_jack_detection(int irq, void *data)
 359{
 360        struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data;
 361
 362        if (mfld_jack.codec == NULL)
 363                return IRQ_HANDLED;
 364        mfld_jack_check(mc_drv_ctx->interrupt_status);
 365
 366        return IRQ_HANDLED;
 367}
 368
 369static int snd_mfld_mc_probe(struct platform_device *pdev)
 370{
 371        int ret_val = 0, irq;
 372        struct mfld_mc_private *mc_drv_ctx;
 373        struct resource *irq_mem;
 374
 375        pr_debug("snd_mfld_mc_probe called\n");
 376
 377        /* retrive the irq number */
 378        irq = platform_get_irq(pdev, 0);
 379
 380        /* audio interrupt base of SRAM location where
 381         * interrupts are stored by System FW */
 382        mc_drv_ctx = devm_kzalloc(&pdev->dev, sizeof(*mc_drv_ctx), GFP_ATOMIC);
 383        if (!mc_drv_ctx) {
 384                pr_err("allocation failed\n");
 385                return -ENOMEM;
 386        }
 387
 388        irq_mem = platform_get_resource_byname(
 389                                pdev, IORESOURCE_MEM, "IRQ_BASE");
 390        if (!irq_mem) {
 391                pr_err("no mem resource given\n");
 392                return -ENODEV;
 393        }
 394        mc_drv_ctx->int_base = devm_ioremap_nocache(&pdev->dev, irq_mem->start,
 395                                                    resource_size(irq_mem));
 396        if (!mc_drv_ctx->int_base) {
 397                pr_err("Mapping of cache failed\n");
 398                return -ENOMEM;
 399        }
 400        /* register for interrupt */
 401        ret_val = devm_request_threaded_irq(&pdev->dev, irq,
 402                        snd_mfld_jack_intr_handler,
 403                        snd_mfld_jack_detection,
 404                        IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx);
 405        if (ret_val) {
 406                pr_err("cannot register IRQ\n");
 407                return ret_val;
 408        }
 409        /* register the soc card */
 410        snd_soc_card_mfld.dev = &pdev->dev;
 411        ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_mfld);
 412        if (ret_val) {
 413                pr_debug("snd_soc_register_card failed %d\n", ret_val);
 414                return ret_val;
 415        }
 416        platform_set_drvdata(pdev, mc_drv_ctx);
 417        pr_debug("successfully exited probe\n");
 418        return 0;
 419}
 420
 421static struct platform_driver snd_mfld_mc_driver = {
 422        .driver = {
 423                .owner = THIS_MODULE,
 424                .name = "msic_audio",
 425        },
 426        .probe = snd_mfld_mc_probe,
 427};
 428
 429module_platform_driver(snd_mfld_mc_driver);
 430
 431MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver");
 432MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
 433MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
 434MODULE_LICENSE("GPL v2");
 435MODULE_ALIAS("platform:msic-audio");
 436