linux/sound/soc/codecs/jz4740.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License version 2 as
   6 * published by the Free Software Foundation.
   7 *
   8 *  You should have received a copy of the  GNU General Public License along
   9 *  with this program; if not, write  to the Free Software Foundation, Inc.,
  10 *  675 Mass Ave, Cambridge, MA 02139, USA.
  11 *
  12 */
  13
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/platform_device.h>
  17#include <linux/slab.h>
  18
  19#include <linux/delay.h>
  20
  21#include <sound/core.h>
  22#include <sound/pcm.h>
  23#include <sound/pcm_params.h>
  24#include <sound/initval.h>
  25#include <sound/soc.h>
  26
  27#define JZ4740_REG_CODEC_1 0x0
  28#define JZ4740_REG_CODEC_2 0x1
  29
  30#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
  31#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
  32#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
  33#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
  34#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
  35#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
  36#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
  37#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
  38#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
  39#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
  40#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
  41#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
  42#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
  43#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
  44#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
  45#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
  46#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
  47#define JZ4740_CODEC_1_SUSPEND BIT(1)
  48#define JZ4740_CODEC_1_RESET BIT(0)
  49
  50#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
  51#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
  52#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
  53#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
  54#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
  55#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
  56#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
  57#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
  58
  59#define JZ4740_CODEC_2_INPUT_VOLUME_MASK                0x1f0000
  60#define JZ4740_CODEC_2_SAMPLE_RATE_MASK                 0x000f00
  61#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK              0x000030
  62#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK    0x000003
  63
  64#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET              16
  65#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET                8
  66#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET     4
  67#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET   0
  68
  69static const uint32_t jz4740_codec_regs[] = {
  70        0x021b2302, 0x00170803,
  71};
  72
  73struct jz4740_codec {
  74        void __iomem *base;
  75        struct resource *mem;
  76};
  77
  78static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
  79        unsigned int reg)
  80{
  81        struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec);
  82        return readl(jz4740_codec->base + (reg << 2));
  83}
  84
  85static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
  86        unsigned int val)
  87{
  88        struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec);
  89        u32 *cache = codec->reg_cache;
  90
  91        cache[reg] = val;
  92        writel(val, jz4740_codec->base + (reg << 2));
  93
  94        return 0;
  95}
  96
  97static const struct snd_kcontrol_new jz4740_codec_controls[] = {
  98        SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
  99                        JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
 100        SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
 101                        JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
 102        SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
 103                        JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
 104        SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
 105                        JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
 106};
 107
 108static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
 109        SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
 110                        JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
 111        SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
 112                        JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
 113};
 114
 115static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
 116        SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
 117                        JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
 118        SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
 119                        JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
 120};
 121
 122static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
 123        SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
 124                        JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
 125        SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
 126                        JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
 127
 128        SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
 129                        JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
 130                        jz4740_codec_output_controls,
 131                        ARRAY_SIZE(jz4740_codec_output_controls)),
 132
 133        SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
 134                        jz4740_codec_input_controls,
 135                        ARRAY_SIZE(jz4740_codec_input_controls)),
 136        SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
 137
 138        SND_SOC_DAPM_OUTPUT("LOUT"),
 139        SND_SOC_DAPM_OUTPUT("ROUT"),
 140
 141        SND_SOC_DAPM_INPUT("MIC"),
 142        SND_SOC_DAPM_INPUT("LIN"),
 143        SND_SOC_DAPM_INPUT("RIN"),
 144};
 145
 146static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
 147        {"Line Input", NULL, "LIN"},
 148        {"Line Input", NULL, "RIN"},
 149
 150        {"Input Mixer", "Line Capture Switch", "Line Input"},
 151        {"Input Mixer", "Mic Capture Switch", "MIC"},
 152
 153        {"ADC", NULL, "Input Mixer"},
 154
 155        {"Output Mixer", "Bypass Switch", "Input Mixer"},
 156        {"Output Mixer", "DAC Switch", "DAC"},
 157
 158        {"LOUT", NULL, "Output Mixer"},
 159        {"ROUT", NULL, "Output Mixer"},
 160};
 161
 162static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
 163        struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
 164{
 165        uint32_t val;
 166        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 167        struct snd_soc_codec *codec =rtd->codec;
 168
 169        switch (params_rate(params)) {
 170        case 8000:
 171                val = 0;
 172                break;
 173        case 11025:
 174                val = 1;
 175                break;
 176        case 12000:
 177                val = 2;
 178                break;
 179        case 16000:
 180                val = 3;
 181                break;
 182        case 22050:
 183                val = 4;
 184                break;
 185        case 24000:
 186                val = 5;
 187                break;
 188        case 32000:
 189                val = 6;
 190                break;
 191        case 44100:
 192                val = 7;
 193                break;
 194        case 48000:
 195                val = 8;
 196                break;
 197        default:
 198                return -EINVAL;
 199        }
 200
 201        val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
 202
 203        snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
 204                                JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
 205
 206        return 0;
 207}
 208
 209static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
 210        .hw_params = jz4740_codec_hw_params,
 211};
 212
 213static struct snd_soc_dai_driver jz4740_codec_dai = {
 214        .name = "jz4740-hifi",
 215        .playback = {
 216                .stream_name = "Playback",
 217                .channels_min = 2,
 218                .channels_max = 2,
 219                .rates = SNDRV_PCM_RATE_8000_48000,
 220                .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
 221        },
 222        .capture = {
 223                .stream_name = "Capture",
 224                .channels_min = 2,
 225                .channels_max = 2,
 226                .rates = SNDRV_PCM_RATE_8000_48000,
 227                .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
 228        },
 229        .ops = &jz4740_codec_dai_ops,
 230        .symmetric_rates = 1,
 231};
 232
 233static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
 234{
 235        int i;
 236        uint32_t *cache = codec->reg_cache;
 237
 238        snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
 239                JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
 240        udelay(2);
 241
 242        snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
 243                JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
 244
 245        for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
 246                jz4740_codec_write(codec, i, cache[i]);
 247}
 248
 249static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
 250        enum snd_soc_bias_level level)
 251{
 252        unsigned int mask;
 253        unsigned int value;
 254
 255        switch (level) {
 256        case SND_SOC_BIAS_ON:
 257                break;
 258        case SND_SOC_BIAS_PREPARE:
 259                mask = JZ4740_CODEC_1_VREF_DISABLE |
 260                                JZ4740_CODEC_1_VREF_AMP_DISABLE |
 261                                JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
 262                value = 0;
 263
 264                snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
 265                break;
 266        case SND_SOC_BIAS_STANDBY:
 267                /* The only way to clear the suspend flag is to reset the codec */
 268                if (codec->dapm.bias_level == SND_SOC_BIAS_OFF)
 269                        jz4740_codec_wakeup(codec);
 270
 271                mask = JZ4740_CODEC_1_VREF_DISABLE |
 272                        JZ4740_CODEC_1_VREF_AMP_DISABLE |
 273                        JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
 274                value = JZ4740_CODEC_1_VREF_DISABLE |
 275                        JZ4740_CODEC_1_VREF_AMP_DISABLE |
 276                        JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
 277
 278                snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
 279                break;
 280        case SND_SOC_BIAS_OFF:
 281                mask = JZ4740_CODEC_1_SUSPEND;
 282                value = JZ4740_CODEC_1_SUSPEND;
 283
 284                snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
 285                break;
 286        default:
 287                break;
 288        }
 289
 290        codec->dapm.bias_level = level;
 291
 292        return 0;
 293}
 294
 295static int jz4740_codec_dev_probe(struct snd_soc_codec *codec)
 296{
 297        struct snd_soc_dapm_context *dapm = &codec->dapm;
 298
 299        snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
 300                        JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
 301
 302        snd_soc_add_controls(codec, jz4740_codec_controls,
 303                ARRAY_SIZE(jz4740_codec_controls));
 304
 305        snd_soc_dapm_new_controls(dapm, jz4740_codec_dapm_widgets,
 306                ARRAY_SIZE(jz4740_codec_dapm_widgets));
 307
 308        snd_soc_dapm_add_routes(dapm, jz4740_codec_dapm_routes,
 309                ARRAY_SIZE(jz4740_codec_dapm_routes));
 310
 311        snd_soc_dapm_new_widgets(codec);
 312
 313        jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 314
 315        return 0;
 316}
 317
 318static int jz4740_codec_dev_remove(struct snd_soc_codec *codec)
 319{
 320        jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
 321
 322        return 0;
 323}
 324
 325#ifdef CONFIG_PM_SLEEP
 326
 327static int jz4740_codec_suspend(struct snd_soc_codec *codec, pm_message_t state)
 328{
 329        return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
 330}
 331
 332static int jz4740_codec_resume(struct snd_soc_codec *codec)
 333{
 334        return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 335}
 336
 337#else
 338#define jz4740_codec_suspend NULL
 339#define jz4740_codec_resume NULL
 340#endif
 341
 342static struct snd_soc_codec_driver soc_codec_dev_jz4740_codec = {
 343        .probe = jz4740_codec_dev_probe,
 344        .remove = jz4740_codec_dev_remove,
 345        .suspend = jz4740_codec_suspend,
 346        .resume = jz4740_codec_resume,
 347        .read = jz4740_codec_read,
 348        .write = jz4740_codec_write,
 349        .set_bias_level = jz4740_codec_set_bias_level,
 350        .reg_cache_default      = jz4740_codec_regs,
 351        .reg_word_size = sizeof(u32),
 352        .reg_cache_size = 2,
 353};
 354
 355static int __devinit jz4740_codec_probe(struct platform_device *pdev)
 356{
 357        int ret;
 358        struct jz4740_codec *jz4740_codec;
 359        struct resource *mem;
 360
 361        jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
 362        if (!jz4740_codec)
 363                return -ENOMEM;
 364
 365        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 366        if (!mem) {
 367                dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
 368                ret = -ENOENT;
 369                goto err_free_codec;
 370        }
 371
 372        mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
 373        if (!mem) {
 374                dev_err(&pdev->dev, "Failed to request mmio memory region\n");
 375                ret = -EBUSY;
 376                goto err_free_codec;
 377        }
 378
 379        jz4740_codec->base = ioremap(mem->start, resource_size(mem));
 380        if (!jz4740_codec->base) {
 381                dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
 382                ret = -EBUSY;
 383                goto err_release_mem_region;
 384        }
 385        jz4740_codec->mem = mem;
 386
 387        platform_set_drvdata(pdev, jz4740_codec);
 388
 389        ret = snd_soc_register_codec(&pdev->dev,
 390                        &soc_codec_dev_jz4740_codec, &jz4740_codec_dai, 1);
 391        if (ret) {
 392                dev_err(&pdev->dev, "Failed to register codec\n");
 393                goto err_iounmap;
 394        }
 395
 396        return 0;
 397
 398err_iounmap:
 399        iounmap(jz4740_codec->base);
 400err_release_mem_region:
 401        release_mem_region(mem->start, resource_size(mem));
 402err_free_codec:
 403        kfree(jz4740_codec);
 404
 405        return ret;
 406}
 407
 408static int __devexit jz4740_codec_remove(struct platform_device *pdev)
 409{
 410        struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
 411        struct resource *mem = jz4740_codec->mem;
 412
 413        snd_soc_unregister_codec(&pdev->dev);
 414
 415        iounmap(jz4740_codec->base);
 416        release_mem_region(mem->start, resource_size(mem));
 417
 418        platform_set_drvdata(pdev, NULL);
 419        kfree(jz4740_codec);
 420
 421        return 0;
 422}
 423
 424static struct platform_driver jz4740_codec_driver = {
 425        .probe = jz4740_codec_probe,
 426        .remove = __devexit_p(jz4740_codec_remove),
 427        .driver = {
 428                .name = "jz4740-codec",
 429                .owner = THIS_MODULE,
 430        },
 431};
 432
 433static int __init jz4740_codec_init(void)
 434{
 435        return platform_driver_register(&jz4740_codec_driver);
 436}
 437module_init(jz4740_codec_init);
 438
 439static void __exit jz4740_codec_exit(void)
 440{
 441        platform_driver_unregister(&jz4740_codec_driver);
 442}
 443module_exit(jz4740_codec_exit);
 444
 445MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
 446MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
 447MODULE_LICENSE("GPL v2");
 448MODULE_ALIAS("platform:jz4740-codec");
 449