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