mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-06 02:24:14 +08:00
2b27bdcc20
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license version 2 as published by the free software foundation this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 51 franklin st fifth floor boston ma 02110 1301 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 246 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Alexios Zavras <alexios.zavras@intel.com> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190530000436.674189849@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
290 lines
6.7 KiB
C
290 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* MFD driver for twl4030 audio submodule, which contains an audio codec, and
|
|
* the vibra control.
|
|
*
|
|
* Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
|
|
*
|
|
* Copyright: (C) 2009 Nokia Corporation
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/mfd/twl.h>
|
|
#include <linux/mfd/core.h>
|
|
#include <linux/mfd/twl4030-audio.h>
|
|
|
|
#define TWL4030_AUDIO_CELLS 2
|
|
|
|
static struct platform_device *twl4030_audio_dev;
|
|
|
|
struct twl4030_audio_resource {
|
|
int request_count;
|
|
u8 reg;
|
|
u8 mask;
|
|
};
|
|
|
|
struct twl4030_audio {
|
|
unsigned int audio_mclk;
|
|
struct mutex mutex;
|
|
struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];
|
|
struct mfd_cell cells[TWL4030_AUDIO_CELLS];
|
|
};
|
|
|
|
/*
|
|
* Modify the resource, the function returns the content of the register
|
|
* after the modification.
|
|
*/
|
|
static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)
|
|
{
|
|
struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
|
|
u8 val;
|
|
|
|
twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
|
|
audio->resource[id].reg);
|
|
|
|
if (enable)
|
|
val |= audio->resource[id].mask;
|
|
else
|
|
val &= ~audio->resource[id].mask;
|
|
|
|
twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
|
|
val, audio->resource[id].reg);
|
|
|
|
return val;
|
|
}
|
|
|
|
static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)
|
|
{
|
|
struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
|
|
u8 val;
|
|
|
|
twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
|
|
audio->resource[id].reg);
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* Enable the resource.
|
|
* The function returns with error or the content of the register
|
|
*/
|
|
int twl4030_audio_enable_resource(enum twl4030_audio_res id)
|
|
{
|
|
struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
|
|
int val;
|
|
|
|
if (id >= TWL4030_AUDIO_RES_MAX) {
|
|
dev_err(&twl4030_audio_dev->dev,
|
|
"Invalid resource ID (%u)\n", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&audio->mutex);
|
|
if (!audio->resource[id].request_count)
|
|
/* Resource was disabled, enable it */
|
|
val = twl4030_audio_set_resource(id, 1);
|
|
else
|
|
val = twl4030_audio_get_resource(id);
|
|
|
|
audio->resource[id].request_count++;
|
|
mutex_unlock(&audio->mutex);
|
|
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);
|
|
|
|
/*
|
|
* Disable the resource.
|
|
* The function returns with error or the content of the register
|
|
*/
|
|
int twl4030_audio_disable_resource(enum twl4030_audio_res id)
|
|
{
|
|
struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
|
|
int val;
|
|
|
|
if (id >= TWL4030_AUDIO_RES_MAX) {
|
|
dev_err(&twl4030_audio_dev->dev,
|
|
"Invalid resource ID (%u)\n", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&audio->mutex);
|
|
if (!audio->resource[id].request_count) {
|
|
dev_err(&twl4030_audio_dev->dev,
|
|
"Resource has been disabled already (%u)\n", id);
|
|
mutex_unlock(&audio->mutex);
|
|
return -EPERM;
|
|
}
|
|
audio->resource[id].request_count--;
|
|
|
|
if (!audio->resource[id].request_count)
|
|
/* Resource can be disabled now */
|
|
val = twl4030_audio_set_resource(id, 0);
|
|
else
|
|
val = twl4030_audio_get_resource(id);
|
|
|
|
mutex_unlock(&audio->mutex);
|
|
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);
|
|
|
|
unsigned int twl4030_audio_get_mclk(void)
|
|
{
|
|
struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
|
|
|
|
return audio->audio_mclk;
|
|
}
|
|
EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
|
|
|
|
static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata,
|
|
struct device_node *parent)
|
|
{
|
|
struct device_node *node;
|
|
|
|
if (pdata && pdata->codec)
|
|
return true;
|
|
|
|
node = of_get_child_by_name(parent, "codec");
|
|
if (node) {
|
|
of_node_put(node);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata,
|
|
struct device_node *node)
|
|
{
|
|
int vibra;
|
|
|
|
if (pdata && pdata->vibra)
|
|
return true;
|
|
|
|
if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int twl4030_audio_probe(struct platform_device *pdev)
|
|
{
|
|
struct twl4030_audio *audio;
|
|
struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev);
|
|
struct device_node *node = pdev->dev.of_node;
|
|
struct mfd_cell *cell = NULL;
|
|
int ret, childs = 0;
|
|
u8 val;
|
|
|
|
if (!pdata && !node) {
|
|
dev_err(&pdev->dev, "Platform data is missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio),
|
|
GFP_KERNEL);
|
|
if (!audio)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&audio->mutex);
|
|
audio->audio_mclk = twl_get_hfclk_rate();
|
|
|
|
/* Configure APLL_INFREQ and disable APLL if enabled */
|
|
switch (audio->audio_mclk) {
|
|
case 19200000:
|
|
val = TWL4030_APLL_INFREQ_19200KHZ;
|
|
break;
|
|
case 26000000:
|
|
val = TWL4030_APLL_INFREQ_26000KHZ;
|
|
break;
|
|
case 38400000:
|
|
val = TWL4030_APLL_INFREQ_38400KHZ;
|
|
break;
|
|
default:
|
|
dev_err(&pdev->dev, "Invalid audio_mclk\n");
|
|
return -EINVAL;
|
|
}
|
|
twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL);
|
|
|
|
/* Codec power */
|
|
audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
|
|
audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
|
|
|
|
/* PLL */
|
|
audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
|
|
audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
|
|
|
|
if (twl4030_audio_has_codec(pdata, node)) {
|
|
cell = &audio->cells[childs];
|
|
cell->name = "twl4030-codec";
|
|
if (pdata) {
|
|
cell->platform_data = pdata->codec;
|
|
cell->pdata_size = sizeof(*pdata->codec);
|
|
}
|
|
childs++;
|
|
}
|
|
if (twl4030_audio_has_vibra(pdata, node)) {
|
|
cell = &audio->cells[childs];
|
|
cell->name = "twl4030-vibra";
|
|
if (pdata) {
|
|
cell->platform_data = pdata->vibra;
|
|
cell->pdata_size = sizeof(*pdata->vibra);
|
|
}
|
|
childs++;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, audio);
|
|
twl4030_audio_dev = pdev;
|
|
|
|
if (childs)
|
|
ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
|
|
childs, NULL, 0, NULL);
|
|
else {
|
|
dev_err(&pdev->dev, "No platform data found for childs\n");
|
|
ret = -ENODEV;
|
|
}
|
|
|
|
if (ret)
|
|
twl4030_audio_dev = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int twl4030_audio_remove(struct platform_device *pdev)
|
|
{
|
|
mfd_remove_devices(&pdev->dev);
|
|
twl4030_audio_dev = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id twl4030_audio_of_match[] = {
|
|
{.compatible = "ti,twl4030-audio", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, twl4030_audio_of_match);
|
|
|
|
static struct platform_driver twl4030_audio_driver = {
|
|
.driver = {
|
|
.name = "twl4030-audio",
|
|
.of_match_table = twl4030_audio_of_match,
|
|
},
|
|
.probe = twl4030_audio_probe,
|
|
.remove = twl4030_audio_remove,
|
|
};
|
|
|
|
module_platform_driver(twl4030_audio_driver);
|
|
|
|
MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
|
|
MODULE_DESCRIPTION("TWL4030 audio block MFD driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:twl4030-audio");
|