linux/drivers/mfd/twl4030-codec.c
<<
>>
Prefs
   1/*
   2 * MFD driver for twl4030 codec submodule
   3 *
   4 * Author:      Peter Ujfalusi <peter.ujfalusi@nokia.com>
   5 *
   6 * Copyright:   (C) 2009 Nokia Corporation
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 *
  12 * This program is distributed in the hope that it will be useful, but
  13 * WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15 * General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this program; if not, write to the Free Software
  19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  20 * 02110-1301 USA
  21 *
  22 */
  23
  24#include <linux/module.h>
  25#include <linux/types.h>
  26#include <linux/slab.h>
  27#include <linux/kernel.h>
  28#include <linux/fs.h>
  29#include <linux/platform_device.h>
  30#include <linux/i2c/twl.h>
  31#include <linux/mfd/core.h>
  32#include <linux/mfd/twl4030-codec.h>
  33
  34#define TWL4030_CODEC_CELLS     2
  35
  36static struct platform_device *twl4030_codec_dev;
  37
  38struct twl4030_codec_resource {
  39        int request_count;
  40        u8 reg;
  41        u8 mask;
  42};
  43
  44struct twl4030_codec {
  45        unsigned int audio_mclk;
  46        struct mutex mutex;
  47        struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX];
  48        struct mfd_cell cells[TWL4030_CODEC_CELLS];
  49};
  50
  51/*
  52 * Modify the resource, the function returns the content of the register
  53 * after the modification.
  54 */
  55static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable)
  56{
  57        struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
  58        u8 val;
  59
  60        twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
  61                        codec->resource[id].reg);
  62
  63        if (enable)
  64                val |= codec->resource[id].mask;
  65        else
  66                val &= ~codec->resource[id].mask;
  67
  68        twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
  69                                        val, codec->resource[id].reg);
  70
  71        return val;
  72}
  73
  74static inline int twl4030_codec_get_resource(enum twl4030_codec_res id)
  75{
  76        struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
  77        u8 val;
  78
  79        twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
  80                        codec->resource[id].reg);
  81
  82        return val;
  83}
  84
  85/*
  86 * Enable the resource.
  87 * The function returns with error or the content of the register
  88 */
  89int twl4030_codec_enable_resource(enum twl4030_codec_res id)
  90{
  91        struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
  92        int val;
  93
  94        if (id >= TWL4030_CODEC_RES_MAX) {
  95                dev_err(&twl4030_codec_dev->dev,
  96                                "Invalid resource ID (%u)\n", id);
  97                return -EINVAL;
  98        }
  99
 100        mutex_lock(&codec->mutex);
 101        if (!codec->resource[id].request_count)
 102                /* Resource was disabled, enable it */
 103                val = twl4030_codec_set_resource(id, 1);
 104        else
 105                val = twl4030_codec_get_resource(id);
 106
 107        codec->resource[id].request_count++;
 108        mutex_unlock(&codec->mutex);
 109
 110        return val;
 111}
 112EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource);
 113
 114/*
 115 * Disable the resource.
 116 * The function returns with error or the content of the register
 117 */
 118int twl4030_codec_disable_resource(unsigned id)
 119{
 120        struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
 121        int val;
 122
 123        if (id >= TWL4030_CODEC_RES_MAX) {
 124                dev_err(&twl4030_codec_dev->dev,
 125                                "Invalid resource ID (%u)\n", id);
 126                return -EINVAL;
 127        }
 128
 129        mutex_lock(&codec->mutex);
 130        if (!codec->resource[id].request_count) {
 131                dev_err(&twl4030_codec_dev->dev,
 132                        "Resource has been disabled already (%u)\n", id);
 133                mutex_unlock(&codec->mutex);
 134                return -EPERM;
 135        }
 136        codec->resource[id].request_count--;
 137
 138        if (!codec->resource[id].request_count)
 139                /* Resource can be disabled now */
 140                val = twl4030_codec_set_resource(id, 0);
 141        else
 142                val = twl4030_codec_get_resource(id);
 143
 144        mutex_unlock(&codec->mutex);
 145
 146        return val;
 147}
 148EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource);
 149
 150unsigned int twl4030_codec_get_mclk(void)
 151{
 152        struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
 153
 154        return codec->audio_mclk;
 155}
 156EXPORT_SYMBOL_GPL(twl4030_codec_get_mclk);
 157
 158static int __devinit twl4030_codec_probe(struct platform_device *pdev)
 159{
 160        struct twl4030_codec *codec;
 161        struct twl4030_codec_data *pdata = pdev->dev.platform_data;
 162        struct mfd_cell *cell = NULL;
 163        int ret, childs = 0;
 164        u8 val;
 165
 166        if (!pdata) {
 167                dev_err(&pdev->dev, "Platform data is missing\n");
 168                return -EINVAL;
 169        }
 170
 171        /* Configure APLL_INFREQ and disable APLL if enabled */
 172        val = 0;
 173        switch (pdata->audio_mclk) {
 174        case 19200000:
 175                val |= TWL4030_APLL_INFREQ_19200KHZ;
 176                break;
 177        case 26000000:
 178                val |= TWL4030_APLL_INFREQ_26000KHZ;
 179                break;
 180        case 38400000:
 181                val |= TWL4030_APLL_INFREQ_38400KHZ;
 182                break;
 183        default:
 184                dev_err(&pdev->dev, "Invalid audio_mclk\n");
 185                return -EINVAL;
 186        }
 187        twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
 188                                        val, TWL4030_REG_APLL_CTL);
 189
 190        codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL);
 191        if (!codec)
 192                return -ENOMEM;
 193
 194        platform_set_drvdata(pdev, codec);
 195
 196        twl4030_codec_dev = pdev;
 197        mutex_init(&codec->mutex);
 198        codec->audio_mclk = pdata->audio_mclk;
 199
 200        /* Codec power */
 201        codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
 202        codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ;
 203
 204        /* PLL */
 205        codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL;
 206        codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN;
 207
 208        if (pdata->audio) {
 209                cell = &codec->cells[childs];
 210                cell->name = "twl4030-codec";
 211                cell->platform_data = pdata->audio;
 212                cell->data_size = sizeof(*pdata->audio);
 213                childs++;
 214        }
 215        if (pdata->vibra) {
 216                cell = &codec->cells[childs];
 217                cell->name = "twl4030-vibra";
 218                cell->platform_data = pdata->vibra;
 219                cell->data_size = sizeof(*pdata->vibra);
 220                childs++;
 221        }
 222
 223        if (childs)
 224                ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells,
 225                                      childs, NULL, 0);
 226        else {
 227                dev_err(&pdev->dev, "No platform data found for childs\n");
 228                ret = -ENODEV;
 229        }
 230
 231        if (!ret)
 232                return 0;
 233
 234        platform_set_drvdata(pdev, NULL);
 235        kfree(codec);
 236        twl4030_codec_dev = NULL;
 237        return ret;
 238}
 239
 240static int __devexit twl4030_codec_remove(struct platform_device *pdev)
 241{
 242        struct twl4030_codec *codec = platform_get_drvdata(pdev);
 243
 244        mfd_remove_devices(&pdev->dev);
 245        platform_set_drvdata(pdev, NULL);
 246        kfree(codec);
 247        twl4030_codec_dev = NULL;
 248
 249        return 0;
 250}
 251
 252MODULE_ALIAS("platform:twl4030-audio");
 253
 254static struct platform_driver twl4030_codec_driver = {
 255        .probe          = twl4030_codec_probe,
 256        .remove         = __devexit_p(twl4030_codec_remove),
 257        .driver         = {
 258                .owner  = THIS_MODULE,
 259                .name   = "twl4030-audio",
 260        },
 261};
 262
 263static int __devinit twl4030_codec_init(void)
 264{
 265        return platform_driver_register(&twl4030_codec_driver);
 266}
 267module_init(twl4030_codec_init);
 268
 269static void __devexit twl4030_codec_exit(void)
 270{
 271        platform_driver_unregister(&twl4030_codec_driver);
 272}
 273module_exit(twl4030_codec_exit);
 274
 275MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>");
 276MODULE_LICENSE("GPL");
 277
 278