linux/drivers/mfd/twl4030-audio.c
<<
>>
Prefs
   1/*
   2 * MFD driver for twl4030 audio submodule, which contains an audio codec, and
   3 * the vibra control.
   4 *
   5 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
   6 *
   7 * Copyright:   (C) 2009 Nokia Corporation
   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 version 2 as
  11 * published by the Free Software Foundation.
  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
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  21 * 02110-1301 USA
  22 *
  23 */
  24
  25#include <linux/module.h>
  26#include <linux/types.h>
  27#include <linux/slab.h>
  28#include <linux/kernel.h>
  29#include <linux/fs.h>
  30#include <linux/platform_device.h>
  31#include <linux/of.h>
  32#include <linux/of_platform.h>
  33#include <linux/i2c/twl.h>
  34#include <linux/mfd/core.h>
  35#include <linux/mfd/twl4030-audio.h>
  36
  37#define TWL4030_AUDIO_CELLS     2
  38
  39static struct platform_device *twl4030_audio_dev;
  40
  41struct twl4030_audio_resource {
  42        int request_count;
  43        u8 reg;
  44        u8 mask;
  45};
  46
  47struct twl4030_audio {
  48        unsigned int audio_mclk;
  49        struct mutex mutex;
  50        struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];
  51        struct mfd_cell cells[TWL4030_AUDIO_CELLS];
  52};
  53
  54/*
  55 * Modify the resource, the function returns the content of the register
  56 * after the modification.
  57 */
  58static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)
  59{
  60        struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
  61        u8 val;
  62
  63        twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
  64                        audio->resource[id].reg);
  65
  66        if (enable)
  67                val |= audio->resource[id].mask;
  68        else
  69                val &= ~audio->resource[id].mask;
  70
  71        twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
  72                                        val, audio->resource[id].reg);
  73
  74        return val;
  75}
  76
  77static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)
  78{
  79        struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
  80        u8 val;
  81
  82        twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
  83                        audio->resource[id].reg);
  84
  85        return val;
  86}
  87
  88/*
  89 * Enable the resource.
  90 * The function returns with error or the content of the register
  91 */
  92int twl4030_audio_enable_resource(enum twl4030_audio_res id)
  93{
  94        struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
  95        int val;
  96
  97        if (id >= TWL4030_AUDIO_RES_MAX) {
  98                dev_err(&twl4030_audio_dev->dev,
  99                                "Invalid resource ID (%u)\n", id);
 100                return -EINVAL;
 101        }
 102
 103        mutex_lock(&audio->mutex);
 104        if (!audio->resource[id].request_count)
 105                /* Resource was disabled, enable it */
 106                val = twl4030_audio_set_resource(id, 1);
 107        else
 108                val = twl4030_audio_get_resource(id);
 109
 110        audio->resource[id].request_count++;
 111        mutex_unlock(&audio->mutex);
 112
 113        return val;
 114}
 115EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);
 116
 117/*
 118 * Disable the resource.
 119 * The function returns with error or the content of the register
 120 */
 121int twl4030_audio_disable_resource(enum twl4030_audio_res id)
 122{
 123        struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
 124        int val;
 125
 126        if (id >= TWL4030_AUDIO_RES_MAX) {
 127                dev_err(&twl4030_audio_dev->dev,
 128                                "Invalid resource ID (%u)\n", id);
 129                return -EINVAL;
 130        }
 131
 132        mutex_lock(&audio->mutex);
 133        if (!audio->resource[id].request_count) {
 134                dev_err(&twl4030_audio_dev->dev,
 135                        "Resource has been disabled already (%u)\n", id);
 136                mutex_unlock(&audio->mutex);
 137                return -EPERM;
 138        }
 139        audio->resource[id].request_count--;
 140
 141        if (!audio->resource[id].request_count)
 142                /* Resource can be disabled now */
 143                val = twl4030_audio_set_resource(id, 0);
 144        else
 145                val = twl4030_audio_get_resource(id);
 146
 147        mutex_unlock(&audio->mutex);
 148
 149        return val;
 150}
 151EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);
 152
 153unsigned int twl4030_audio_get_mclk(void)
 154{
 155        struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
 156
 157        return audio->audio_mclk;
 158}
 159EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
 160
 161static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata,
 162                              struct device_node *node)
 163{
 164        if (pdata && pdata->codec)
 165                return true;
 166
 167        if (of_find_node_by_name(node, "codec"))
 168                return true;
 169
 170        return false;
 171}
 172
 173static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata,
 174                              struct device_node *node)
 175{
 176        int vibra;
 177
 178        if (pdata && pdata->vibra)
 179                return true;
 180
 181        if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra)
 182                return true;
 183
 184        return false;
 185}
 186
 187static int twl4030_audio_probe(struct platform_device *pdev)
 188{
 189        struct twl4030_audio *audio;
 190        struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev);
 191        struct device_node *node = pdev->dev.of_node;
 192        struct mfd_cell *cell = NULL;
 193        int ret, childs = 0;
 194        u8 val;
 195
 196        if (!pdata && !node) {
 197                dev_err(&pdev->dev, "Platform data is missing\n");
 198                return -EINVAL;
 199        }
 200
 201        audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio),
 202                             GFP_KERNEL);
 203        if (!audio)
 204                return -ENOMEM;
 205
 206        mutex_init(&audio->mutex);
 207        audio->audio_mclk = twl_get_hfclk_rate();
 208
 209        /* Configure APLL_INFREQ and disable APLL if enabled */
 210        switch (audio->audio_mclk) {
 211        case 19200000:
 212                val = TWL4030_APLL_INFREQ_19200KHZ;
 213                break;
 214        case 26000000:
 215                val = TWL4030_APLL_INFREQ_26000KHZ;
 216                break;
 217        case 38400000:
 218                val = TWL4030_APLL_INFREQ_38400KHZ;
 219                break;
 220        default:
 221                dev_err(&pdev->dev, "Invalid audio_mclk\n");
 222                return -EINVAL;
 223        }
 224        twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL);
 225
 226        /* Codec power */
 227        audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
 228        audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
 229
 230        /* PLL */
 231        audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
 232        audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
 233
 234        if (twl4030_audio_has_codec(pdata, node)) {
 235                cell = &audio->cells[childs];
 236                cell->name = "twl4030-codec";
 237                if (pdata) {
 238                        cell->platform_data = pdata->codec;
 239                        cell->pdata_size = sizeof(*pdata->codec);
 240                }
 241                childs++;
 242        }
 243        if (twl4030_audio_has_vibra(pdata, node)) {
 244                cell = &audio->cells[childs];
 245                cell->name = "twl4030-vibra";
 246                if (pdata) {
 247                        cell->platform_data = pdata->vibra;
 248                        cell->pdata_size = sizeof(*pdata->vibra);
 249                }
 250                childs++;
 251        }
 252
 253        platform_set_drvdata(pdev, audio);
 254        twl4030_audio_dev = pdev;
 255
 256        if (childs)
 257                ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
 258                                      childs, NULL, 0, NULL);
 259        else {
 260                dev_err(&pdev->dev, "No platform data found for childs\n");
 261                ret = -ENODEV;
 262        }
 263
 264        if (ret)
 265                twl4030_audio_dev = NULL;
 266
 267        return ret;
 268}
 269
 270static int twl4030_audio_remove(struct platform_device *pdev)
 271{
 272        mfd_remove_devices(&pdev->dev);
 273        twl4030_audio_dev = NULL;
 274
 275        return 0;
 276}
 277
 278static const struct of_device_id twl4030_audio_of_match[] = {
 279        {.compatible = "ti,twl4030-audio", },
 280        { },
 281};
 282MODULE_DEVICE_TABLE(of, twl4030_audio_of_match);
 283
 284static struct platform_driver twl4030_audio_driver = {
 285        .driver         = {
 286                .owner  = THIS_MODULE,
 287                .name   = "twl4030-audio",
 288                .of_match_table = twl4030_audio_of_match,
 289        },
 290        .probe          = twl4030_audio_probe,
 291        .remove         = twl4030_audio_remove,
 292};
 293
 294module_platform_driver(twl4030_audio_driver);
 295
 296MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
 297MODULE_DESCRIPTION("TWL4030 audio block MFD driver");
 298MODULE_LICENSE("GPL");
 299MODULE_ALIAS("platform:twl4030-audio");
 300
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.