xref: /openbmc/linux/drivers/mfd/twl4030-audio.c (revision b22f3152)
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