12b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
257fe7251SPeter Ujfalusi /*
357fe7251SPeter Ujfalusi * MFD driver for twl4030 audio submodule, which contains an audio codec, and
457fe7251SPeter Ujfalusi * the vibra control.
557fe7251SPeter Ujfalusi *
657fe7251SPeter Ujfalusi * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
757fe7251SPeter Ujfalusi *
857fe7251SPeter Ujfalusi * Copyright: (C) 2009 Nokia Corporation
957fe7251SPeter Ujfalusi */
1057fe7251SPeter Ujfalusi
1157fe7251SPeter Ujfalusi #include <linux/module.h>
1257fe7251SPeter Ujfalusi #include <linux/types.h>
1357fe7251SPeter Ujfalusi #include <linux/slab.h>
1457fe7251SPeter Ujfalusi #include <linux/kernel.h>
1557fe7251SPeter Ujfalusi #include <linux/fs.h>
1657fe7251SPeter Ujfalusi #include <linux/platform_device.h>
17019a7e6bSPeter Ujfalusi #include <linux/of.h>
18019a7e6bSPeter Ujfalusi #include <linux/of_platform.h>
19a2054256SWolfram Sang #include <linux/mfd/twl.h>
2057fe7251SPeter Ujfalusi #include <linux/mfd/core.h>
2157fe7251SPeter Ujfalusi #include <linux/mfd/twl4030-audio.h>
2257fe7251SPeter Ujfalusi
2357fe7251SPeter Ujfalusi #define TWL4030_AUDIO_CELLS 2
2457fe7251SPeter Ujfalusi
2557fe7251SPeter Ujfalusi static struct platform_device *twl4030_audio_dev;
2657fe7251SPeter Ujfalusi
2757fe7251SPeter Ujfalusi struct twl4030_audio_resource {
2857fe7251SPeter Ujfalusi int request_count;
2957fe7251SPeter Ujfalusi u8 reg;
3057fe7251SPeter Ujfalusi u8 mask;
3157fe7251SPeter Ujfalusi };
3257fe7251SPeter Ujfalusi
3357fe7251SPeter Ujfalusi struct twl4030_audio {
3457fe7251SPeter Ujfalusi unsigned int audio_mclk;
3557fe7251SPeter Ujfalusi struct mutex mutex;
3657fe7251SPeter Ujfalusi struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];
3757fe7251SPeter Ujfalusi struct mfd_cell cells[TWL4030_AUDIO_CELLS];
3857fe7251SPeter Ujfalusi };
3957fe7251SPeter Ujfalusi
4057fe7251SPeter Ujfalusi /*
4157fe7251SPeter Ujfalusi * Modify the resource, the function returns the content of the register
4257fe7251SPeter Ujfalusi * after the modification.
4357fe7251SPeter Ujfalusi */
twl4030_audio_set_resource(enum twl4030_audio_res id,int enable)4457fe7251SPeter Ujfalusi static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)
4557fe7251SPeter Ujfalusi {
4657fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
4757fe7251SPeter Ujfalusi u8 val;
4857fe7251SPeter Ujfalusi
4957fe7251SPeter Ujfalusi twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
5057fe7251SPeter Ujfalusi audio->resource[id].reg);
5157fe7251SPeter Ujfalusi
5257fe7251SPeter Ujfalusi if (enable)
5357fe7251SPeter Ujfalusi val |= audio->resource[id].mask;
5457fe7251SPeter Ujfalusi else
5557fe7251SPeter Ujfalusi val &= ~audio->resource[id].mask;
5657fe7251SPeter Ujfalusi
5757fe7251SPeter Ujfalusi twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
5857fe7251SPeter Ujfalusi val, audio->resource[id].reg);
5957fe7251SPeter Ujfalusi
6057fe7251SPeter Ujfalusi return val;
6157fe7251SPeter Ujfalusi }
6257fe7251SPeter Ujfalusi
twl4030_audio_get_resource(enum twl4030_audio_res id)6357fe7251SPeter Ujfalusi static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)
6457fe7251SPeter Ujfalusi {
6557fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
6657fe7251SPeter Ujfalusi u8 val;
6757fe7251SPeter Ujfalusi
6857fe7251SPeter Ujfalusi twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
6957fe7251SPeter Ujfalusi audio->resource[id].reg);
7057fe7251SPeter Ujfalusi
7157fe7251SPeter Ujfalusi return val;
7257fe7251SPeter Ujfalusi }
7357fe7251SPeter Ujfalusi
7457fe7251SPeter Ujfalusi /*
7557fe7251SPeter Ujfalusi * Enable the resource.
7657fe7251SPeter Ujfalusi * The function returns with error or the content of the register
7757fe7251SPeter Ujfalusi */
twl4030_audio_enable_resource(enum twl4030_audio_res id)7857fe7251SPeter Ujfalusi int twl4030_audio_enable_resource(enum twl4030_audio_res id)
7957fe7251SPeter Ujfalusi {
8057fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
8157fe7251SPeter Ujfalusi int val;
8257fe7251SPeter Ujfalusi
8357fe7251SPeter Ujfalusi if (id >= TWL4030_AUDIO_RES_MAX) {
8457fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev,
8557fe7251SPeter Ujfalusi "Invalid resource ID (%u)\n", id);
8657fe7251SPeter Ujfalusi return -EINVAL;
8757fe7251SPeter Ujfalusi }
8857fe7251SPeter Ujfalusi
8957fe7251SPeter Ujfalusi mutex_lock(&audio->mutex);
9057fe7251SPeter Ujfalusi if (!audio->resource[id].request_count)
9157fe7251SPeter Ujfalusi /* Resource was disabled, enable it */
9257fe7251SPeter Ujfalusi val = twl4030_audio_set_resource(id, 1);
9357fe7251SPeter Ujfalusi else
9457fe7251SPeter Ujfalusi val = twl4030_audio_get_resource(id);
9557fe7251SPeter Ujfalusi
9657fe7251SPeter Ujfalusi audio->resource[id].request_count++;
9757fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex);
9857fe7251SPeter Ujfalusi
9957fe7251SPeter Ujfalusi return val;
10057fe7251SPeter Ujfalusi }
10157fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);
10257fe7251SPeter Ujfalusi
10357fe7251SPeter Ujfalusi /*
10457fe7251SPeter Ujfalusi * Disable the resource.
10557fe7251SPeter Ujfalusi * The function returns with error or the content of the register
10657fe7251SPeter Ujfalusi */
twl4030_audio_disable_resource(enum twl4030_audio_res id)1076049bcefSMark Brown int twl4030_audio_disable_resource(enum twl4030_audio_res id)
10857fe7251SPeter Ujfalusi {
10957fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
11057fe7251SPeter Ujfalusi int val;
11157fe7251SPeter Ujfalusi
11257fe7251SPeter Ujfalusi if (id >= TWL4030_AUDIO_RES_MAX) {
11357fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev,
11457fe7251SPeter Ujfalusi "Invalid resource ID (%u)\n", id);
11557fe7251SPeter Ujfalusi return -EINVAL;
11657fe7251SPeter Ujfalusi }
11757fe7251SPeter Ujfalusi
11857fe7251SPeter Ujfalusi mutex_lock(&audio->mutex);
11957fe7251SPeter Ujfalusi if (!audio->resource[id].request_count) {
12057fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev,
12157fe7251SPeter Ujfalusi "Resource has been disabled already (%u)\n", id);
12257fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex);
12357fe7251SPeter Ujfalusi return -EPERM;
12457fe7251SPeter Ujfalusi }
12557fe7251SPeter Ujfalusi audio->resource[id].request_count--;
12657fe7251SPeter Ujfalusi
12757fe7251SPeter Ujfalusi if (!audio->resource[id].request_count)
12857fe7251SPeter Ujfalusi /* Resource can be disabled now */
12957fe7251SPeter Ujfalusi val = twl4030_audio_set_resource(id, 0);
13057fe7251SPeter Ujfalusi else
13157fe7251SPeter Ujfalusi val = twl4030_audio_get_resource(id);
13257fe7251SPeter Ujfalusi
13357fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex);
13457fe7251SPeter Ujfalusi
13557fe7251SPeter Ujfalusi return val;
13657fe7251SPeter Ujfalusi }
13757fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);
13857fe7251SPeter Ujfalusi
twl4030_audio_get_mclk(void)13957fe7251SPeter Ujfalusi unsigned int twl4030_audio_get_mclk(void)
14057fe7251SPeter Ujfalusi {
14157fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
14257fe7251SPeter Ujfalusi
14357fe7251SPeter Ujfalusi return audio->audio_mclk;
14457fe7251SPeter Ujfalusi }
14557fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
14657fe7251SPeter Ujfalusi
twl4030_audio_has_codec(struct twl4030_audio_data * pdata,struct device_node * parent)147019a7e6bSPeter Ujfalusi static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata,
1480a423772SJohan Hovold struct device_node *parent)
149019a7e6bSPeter Ujfalusi {
1500a423772SJohan Hovold struct device_node *node;
1510a423772SJohan Hovold
152019a7e6bSPeter Ujfalusi if (pdata && pdata->codec)
153019a7e6bSPeter Ujfalusi return true;
154019a7e6bSPeter Ujfalusi
1550a423772SJohan Hovold node = of_get_child_by_name(parent, "codec");
1560a423772SJohan Hovold if (node) {
1570a423772SJohan Hovold of_node_put(node);
158019a7e6bSPeter Ujfalusi return true;
1590a423772SJohan Hovold }
160019a7e6bSPeter Ujfalusi
161019a7e6bSPeter Ujfalusi return false;
162019a7e6bSPeter Ujfalusi }
163019a7e6bSPeter Ujfalusi
twl4030_audio_has_vibra(struct twl4030_audio_data * pdata,struct device_node * node)164019a7e6bSPeter Ujfalusi static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata,
165019a7e6bSPeter Ujfalusi struct device_node *node)
166019a7e6bSPeter Ujfalusi {
167019a7e6bSPeter Ujfalusi int vibra;
168019a7e6bSPeter Ujfalusi
169019a7e6bSPeter Ujfalusi if (pdata && pdata->vibra)
170019a7e6bSPeter Ujfalusi return true;
171019a7e6bSPeter Ujfalusi
172019a7e6bSPeter Ujfalusi if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra)
173019a7e6bSPeter Ujfalusi return true;
174019a7e6bSPeter Ujfalusi
175019a7e6bSPeter Ujfalusi return false;
176019a7e6bSPeter Ujfalusi }
177019a7e6bSPeter Ujfalusi
twl4030_audio_probe(struct platform_device * pdev)178f791be49SBill Pemberton static int twl4030_audio_probe(struct platform_device *pdev)
17957fe7251SPeter Ujfalusi {
18057fe7251SPeter Ujfalusi struct twl4030_audio *audio;
181334a41ceSJingoo Han struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev);
182019a7e6bSPeter Ujfalusi struct device_node *node = pdev->dev.of_node;
18357fe7251SPeter Ujfalusi struct mfd_cell *cell = NULL;
18457fe7251SPeter Ujfalusi int ret, childs = 0;
18557fe7251SPeter Ujfalusi u8 val;
18657fe7251SPeter Ujfalusi
187019a7e6bSPeter Ujfalusi if (!pdata && !node) {
18857fe7251SPeter Ujfalusi dev_err(&pdev->dev, "Platform data is missing\n");
18957fe7251SPeter Ujfalusi return -EINVAL;
19057fe7251SPeter Ujfalusi }
19157fe7251SPeter Ujfalusi
19239c1421dSPeter Ujfalusi audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio),
19339c1421dSPeter Ujfalusi GFP_KERNEL);
19457fe7251SPeter Ujfalusi if (!audio)
19557fe7251SPeter Ujfalusi return -ENOMEM;
19657fe7251SPeter Ujfalusi
19757fe7251SPeter Ujfalusi mutex_init(&audio->mutex);
198c531241dSPeter Ujfalusi audio->audio_mclk = twl_get_hfclk_rate();
19957fe7251SPeter Ujfalusi
200cdf4b670SPeter Ujfalusi /* Configure APLL_INFREQ and disable APLL if enabled */
201cdf4b670SPeter Ujfalusi switch (audio->audio_mclk) {
202cdf4b670SPeter Ujfalusi case 19200000:
203cdf4b670SPeter Ujfalusi val = TWL4030_APLL_INFREQ_19200KHZ;
204cdf4b670SPeter Ujfalusi break;
205cdf4b670SPeter Ujfalusi case 26000000:
206cdf4b670SPeter Ujfalusi val = TWL4030_APLL_INFREQ_26000KHZ;
207cdf4b670SPeter Ujfalusi break;
208cdf4b670SPeter Ujfalusi case 38400000:
209cdf4b670SPeter Ujfalusi val = TWL4030_APLL_INFREQ_38400KHZ;
210cdf4b670SPeter Ujfalusi break;
211cdf4b670SPeter Ujfalusi default:
212cdf4b670SPeter Ujfalusi dev_err(&pdev->dev, "Invalid audio_mclk\n");
213cdf4b670SPeter Ujfalusi return -EINVAL;
214cdf4b670SPeter Ujfalusi }
215cdf4b670SPeter Ujfalusi twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL);
216cdf4b670SPeter Ujfalusi
21757fe7251SPeter Ujfalusi /* Codec power */
21857fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
21957fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
22057fe7251SPeter Ujfalusi
22157fe7251SPeter Ujfalusi /* PLL */
22257fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
22357fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
22457fe7251SPeter Ujfalusi
225019a7e6bSPeter Ujfalusi if (twl4030_audio_has_codec(pdata, node)) {
22657fe7251SPeter Ujfalusi cell = &audio->cells[childs];
22757fe7251SPeter Ujfalusi cell->name = "twl4030-codec";
228019a7e6bSPeter Ujfalusi if (pdata) {
2294ae6df5eSPeter Ujfalusi cell->platform_data = pdata->codec;
2304ae6df5eSPeter Ujfalusi cell->pdata_size = sizeof(*pdata->codec);
231019a7e6bSPeter Ujfalusi }
23257fe7251SPeter Ujfalusi childs++;
23357fe7251SPeter Ujfalusi }
234019a7e6bSPeter Ujfalusi if (twl4030_audio_has_vibra(pdata, node)) {
23557fe7251SPeter Ujfalusi cell = &audio->cells[childs];
23657fe7251SPeter Ujfalusi cell->name = "twl4030-vibra";
237019a7e6bSPeter Ujfalusi if (pdata) {
23857fe7251SPeter Ujfalusi cell->platform_data = pdata->vibra;
23957fe7251SPeter Ujfalusi cell->pdata_size = sizeof(*pdata->vibra);
240019a7e6bSPeter Ujfalusi }
24157fe7251SPeter Ujfalusi childs++;
24257fe7251SPeter Ujfalusi }
24357fe7251SPeter Ujfalusi
244cdf4b670SPeter Ujfalusi platform_set_drvdata(pdev, audio);
245cdf4b670SPeter Ujfalusi twl4030_audio_dev = pdev;
246cdf4b670SPeter Ujfalusi
24757fe7251SPeter Ujfalusi if (childs)
24857fe7251SPeter Ujfalusi ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
24955692af5SMark Brown childs, NULL, 0, NULL);
25057fe7251SPeter Ujfalusi else {
25157fe7251SPeter Ujfalusi dev_err(&pdev->dev, "No platform data found for childs\n");
25257fe7251SPeter Ujfalusi ret = -ENODEV;
25357fe7251SPeter Ujfalusi }
25457fe7251SPeter Ujfalusi
25596ac2d1aSJingoo Han if (ret)
25657fe7251SPeter Ujfalusi twl4030_audio_dev = NULL;
25739c1421dSPeter Ujfalusi
25857fe7251SPeter Ujfalusi return ret;
25957fe7251SPeter Ujfalusi }
26057fe7251SPeter Ujfalusi
twl4030_audio_remove(struct platform_device * pdev)2614740f73fSBill Pemberton static int twl4030_audio_remove(struct platform_device *pdev)
26257fe7251SPeter Ujfalusi {
26357fe7251SPeter Ujfalusi mfd_remove_devices(&pdev->dev);
26457fe7251SPeter Ujfalusi twl4030_audio_dev = NULL;
26557fe7251SPeter Ujfalusi
26657fe7251SPeter Ujfalusi return 0;
26757fe7251SPeter Ujfalusi }
26857fe7251SPeter Ujfalusi
269019a7e6bSPeter Ujfalusi static const struct of_device_id twl4030_audio_of_match[] = {
270019a7e6bSPeter Ujfalusi {.compatible = "ti,twl4030-audio", },
271019a7e6bSPeter Ujfalusi { },
272019a7e6bSPeter Ujfalusi };
273019a7e6bSPeter Ujfalusi MODULE_DEVICE_TABLE(of, twl4030_audio_of_match);
274019a7e6bSPeter Ujfalusi
27557fe7251SPeter Ujfalusi static struct platform_driver twl4030_audio_driver = {
27657fe7251SPeter Ujfalusi .driver = {
27757fe7251SPeter Ujfalusi .name = "twl4030-audio",
278019a7e6bSPeter Ujfalusi .of_match_table = twl4030_audio_of_match,
27957fe7251SPeter Ujfalusi },
28041569a16SPeter Ujfalusi .probe = twl4030_audio_probe,
28184449216SBill Pemberton .remove = twl4030_audio_remove,
28257fe7251SPeter Ujfalusi };
28357fe7251SPeter Ujfalusi
28465349d60SMark Brown module_platform_driver(twl4030_audio_driver);
28557fe7251SPeter Ujfalusi
28657fe7251SPeter Ujfalusi MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
28741569a16SPeter Ujfalusi MODULE_DESCRIPTION("TWL4030 audio block MFD driver");
28841569a16SPeter Ujfalusi MODULE_ALIAS("platform:twl4030-audio");
289