157fe7251SPeter Ujfalusi /* 257fe7251SPeter Ujfalusi * MFD driver for twl4030 audio submodule, which contains an audio codec, and 357fe7251SPeter Ujfalusi * the vibra control. 457fe7251SPeter Ujfalusi * 557fe7251SPeter Ujfalusi * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> 657fe7251SPeter Ujfalusi * 757fe7251SPeter Ujfalusi * Copyright: (C) 2009 Nokia Corporation 857fe7251SPeter Ujfalusi * 957fe7251SPeter Ujfalusi * This program is free software; you can redistribute it and/or modify 1057fe7251SPeter Ujfalusi * it under the terms of the GNU General Public License version 2 as 1157fe7251SPeter Ujfalusi * published by the Free Software Foundation. 1257fe7251SPeter Ujfalusi * 1357fe7251SPeter Ujfalusi * This program is distributed in the hope that it will be useful, but 1457fe7251SPeter Ujfalusi * WITHOUT ANY WARRANTY; without even the implied warranty of 1557fe7251SPeter Ujfalusi * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1657fe7251SPeter Ujfalusi * General Public License for more details. 1757fe7251SPeter Ujfalusi * 1857fe7251SPeter Ujfalusi * You should have received a copy of the GNU General Public License 1957fe7251SPeter Ujfalusi * along with this program; if not, write to the Free Software 2057fe7251SPeter Ujfalusi * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 2157fe7251SPeter Ujfalusi * 02110-1301 USA 2257fe7251SPeter Ujfalusi * 2357fe7251SPeter Ujfalusi */ 2457fe7251SPeter Ujfalusi 2557fe7251SPeter Ujfalusi #include <linux/module.h> 2657fe7251SPeter Ujfalusi #include <linux/types.h> 2757fe7251SPeter Ujfalusi #include <linux/slab.h> 2857fe7251SPeter Ujfalusi #include <linux/kernel.h> 2957fe7251SPeter Ujfalusi #include <linux/fs.h> 3057fe7251SPeter Ujfalusi #include <linux/platform_device.h> 31019a7e6bSPeter Ujfalusi #include <linux/of.h> 32019a7e6bSPeter Ujfalusi #include <linux/of_platform.h> 3357fe7251SPeter Ujfalusi #include <linux/i2c/twl.h> 3457fe7251SPeter Ujfalusi #include <linux/mfd/core.h> 3557fe7251SPeter Ujfalusi #include <linux/mfd/twl4030-audio.h> 3657fe7251SPeter Ujfalusi 3757fe7251SPeter Ujfalusi #define TWL4030_AUDIO_CELLS 2 3857fe7251SPeter Ujfalusi 3957fe7251SPeter Ujfalusi static struct platform_device *twl4030_audio_dev; 4057fe7251SPeter Ujfalusi 4157fe7251SPeter Ujfalusi struct twl4030_audio_resource { 4257fe7251SPeter Ujfalusi int request_count; 4357fe7251SPeter Ujfalusi u8 reg; 4457fe7251SPeter Ujfalusi u8 mask; 4557fe7251SPeter Ujfalusi }; 4657fe7251SPeter Ujfalusi 4757fe7251SPeter Ujfalusi struct twl4030_audio { 4857fe7251SPeter Ujfalusi unsigned int audio_mclk; 4957fe7251SPeter Ujfalusi struct mutex mutex; 5057fe7251SPeter Ujfalusi struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; 5157fe7251SPeter Ujfalusi struct mfd_cell cells[TWL4030_AUDIO_CELLS]; 5257fe7251SPeter Ujfalusi }; 5357fe7251SPeter Ujfalusi 5457fe7251SPeter Ujfalusi /* 5557fe7251SPeter Ujfalusi * Modify the resource, the function returns the content of the register 5657fe7251SPeter Ujfalusi * after the modification. 5757fe7251SPeter Ujfalusi */ 5857fe7251SPeter Ujfalusi static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) 5957fe7251SPeter Ujfalusi { 6057fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 6157fe7251SPeter Ujfalusi u8 val; 6257fe7251SPeter Ujfalusi 6357fe7251SPeter Ujfalusi twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 6457fe7251SPeter Ujfalusi audio->resource[id].reg); 6557fe7251SPeter Ujfalusi 6657fe7251SPeter Ujfalusi if (enable) 6757fe7251SPeter Ujfalusi val |= audio->resource[id].mask; 6857fe7251SPeter Ujfalusi else 6957fe7251SPeter Ujfalusi val &= ~audio->resource[id].mask; 7057fe7251SPeter Ujfalusi 7157fe7251SPeter Ujfalusi twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 7257fe7251SPeter Ujfalusi val, audio->resource[id].reg); 7357fe7251SPeter Ujfalusi 7457fe7251SPeter Ujfalusi return val; 7557fe7251SPeter Ujfalusi } 7657fe7251SPeter Ujfalusi 7757fe7251SPeter Ujfalusi static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) 7857fe7251SPeter Ujfalusi { 7957fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 8057fe7251SPeter Ujfalusi u8 val; 8157fe7251SPeter Ujfalusi 8257fe7251SPeter Ujfalusi twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 8357fe7251SPeter Ujfalusi audio->resource[id].reg); 8457fe7251SPeter Ujfalusi 8557fe7251SPeter Ujfalusi return val; 8657fe7251SPeter Ujfalusi } 8757fe7251SPeter Ujfalusi 8857fe7251SPeter Ujfalusi /* 8957fe7251SPeter Ujfalusi * Enable the resource. 9057fe7251SPeter Ujfalusi * The function returns with error or the content of the register 9157fe7251SPeter Ujfalusi */ 9257fe7251SPeter Ujfalusi int twl4030_audio_enable_resource(enum twl4030_audio_res id) 9357fe7251SPeter Ujfalusi { 9457fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 9557fe7251SPeter Ujfalusi int val; 9657fe7251SPeter Ujfalusi 9757fe7251SPeter Ujfalusi if (id >= TWL4030_AUDIO_RES_MAX) { 9857fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev, 9957fe7251SPeter Ujfalusi "Invalid resource ID (%u)\n", id); 10057fe7251SPeter Ujfalusi return -EINVAL; 10157fe7251SPeter Ujfalusi } 10257fe7251SPeter Ujfalusi 10357fe7251SPeter Ujfalusi mutex_lock(&audio->mutex); 10457fe7251SPeter Ujfalusi if (!audio->resource[id].request_count) 10557fe7251SPeter Ujfalusi /* Resource was disabled, enable it */ 10657fe7251SPeter Ujfalusi val = twl4030_audio_set_resource(id, 1); 10757fe7251SPeter Ujfalusi else 10857fe7251SPeter Ujfalusi val = twl4030_audio_get_resource(id); 10957fe7251SPeter Ujfalusi 11057fe7251SPeter Ujfalusi audio->resource[id].request_count++; 11157fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex); 11257fe7251SPeter Ujfalusi 11357fe7251SPeter Ujfalusi return val; 11457fe7251SPeter Ujfalusi } 11557fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); 11657fe7251SPeter Ujfalusi 11757fe7251SPeter Ujfalusi /* 11857fe7251SPeter Ujfalusi * Disable the resource. 11957fe7251SPeter Ujfalusi * The function returns with error or the content of the register 12057fe7251SPeter Ujfalusi */ 1216049bcefSMark Brown int twl4030_audio_disable_resource(enum twl4030_audio_res id) 12257fe7251SPeter Ujfalusi { 12357fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 12457fe7251SPeter Ujfalusi int val; 12557fe7251SPeter Ujfalusi 12657fe7251SPeter Ujfalusi if (id >= TWL4030_AUDIO_RES_MAX) { 12757fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev, 12857fe7251SPeter Ujfalusi "Invalid resource ID (%u)\n", id); 12957fe7251SPeter Ujfalusi return -EINVAL; 13057fe7251SPeter Ujfalusi } 13157fe7251SPeter Ujfalusi 13257fe7251SPeter Ujfalusi mutex_lock(&audio->mutex); 13357fe7251SPeter Ujfalusi if (!audio->resource[id].request_count) { 13457fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev, 13557fe7251SPeter Ujfalusi "Resource has been disabled already (%u)\n", id); 13657fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex); 13757fe7251SPeter Ujfalusi return -EPERM; 13857fe7251SPeter Ujfalusi } 13957fe7251SPeter Ujfalusi audio->resource[id].request_count--; 14057fe7251SPeter Ujfalusi 14157fe7251SPeter Ujfalusi if (!audio->resource[id].request_count) 14257fe7251SPeter Ujfalusi /* Resource can be disabled now */ 14357fe7251SPeter Ujfalusi val = twl4030_audio_set_resource(id, 0); 14457fe7251SPeter Ujfalusi else 14557fe7251SPeter Ujfalusi val = twl4030_audio_get_resource(id); 14657fe7251SPeter Ujfalusi 14757fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex); 14857fe7251SPeter Ujfalusi 14957fe7251SPeter Ujfalusi return val; 15057fe7251SPeter Ujfalusi } 15157fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); 15257fe7251SPeter Ujfalusi 15357fe7251SPeter Ujfalusi unsigned int twl4030_audio_get_mclk(void) 15457fe7251SPeter Ujfalusi { 15557fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 15657fe7251SPeter Ujfalusi 15757fe7251SPeter Ujfalusi return audio->audio_mclk; 15857fe7251SPeter Ujfalusi } 15957fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); 16057fe7251SPeter Ujfalusi 161019a7e6bSPeter Ujfalusi static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata, 162019a7e6bSPeter Ujfalusi struct device_node *node) 163019a7e6bSPeter Ujfalusi { 164019a7e6bSPeter Ujfalusi if (pdata && pdata->codec) 165019a7e6bSPeter Ujfalusi return true; 166019a7e6bSPeter Ujfalusi 167019a7e6bSPeter Ujfalusi if (of_find_node_by_name(node, "codec")) 168019a7e6bSPeter Ujfalusi return true; 169019a7e6bSPeter Ujfalusi 170019a7e6bSPeter Ujfalusi return false; 171019a7e6bSPeter Ujfalusi } 172019a7e6bSPeter Ujfalusi 173019a7e6bSPeter Ujfalusi static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata, 174019a7e6bSPeter Ujfalusi struct device_node *node) 175019a7e6bSPeter Ujfalusi { 176019a7e6bSPeter Ujfalusi int vibra; 177019a7e6bSPeter Ujfalusi 178019a7e6bSPeter Ujfalusi if (pdata && pdata->vibra) 179019a7e6bSPeter Ujfalusi return true; 180019a7e6bSPeter Ujfalusi 181019a7e6bSPeter Ujfalusi if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra) 182019a7e6bSPeter Ujfalusi return true; 183019a7e6bSPeter Ujfalusi 184019a7e6bSPeter Ujfalusi return false; 185019a7e6bSPeter Ujfalusi } 186019a7e6bSPeter Ujfalusi 187f791be49SBill Pemberton static int twl4030_audio_probe(struct platform_device *pdev) 18857fe7251SPeter Ujfalusi { 18957fe7251SPeter Ujfalusi struct twl4030_audio *audio; 1904ae6df5eSPeter Ujfalusi struct twl4030_audio_data *pdata = pdev->dev.platform_data; 191019a7e6bSPeter Ujfalusi struct device_node *node = pdev->dev.of_node; 19257fe7251SPeter Ujfalusi struct mfd_cell *cell = NULL; 19357fe7251SPeter Ujfalusi int ret, childs = 0; 19457fe7251SPeter Ujfalusi u8 val; 19557fe7251SPeter Ujfalusi 196019a7e6bSPeter Ujfalusi if (!pdata && !node) { 19757fe7251SPeter Ujfalusi dev_err(&pdev->dev, "Platform data is missing\n"); 19857fe7251SPeter Ujfalusi return -EINVAL; 19957fe7251SPeter Ujfalusi } 20057fe7251SPeter Ujfalusi 20139c1421dSPeter Ujfalusi audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio), 20239c1421dSPeter Ujfalusi GFP_KERNEL); 20357fe7251SPeter Ujfalusi if (!audio) 20457fe7251SPeter Ujfalusi return -ENOMEM; 20557fe7251SPeter Ujfalusi 20657fe7251SPeter Ujfalusi mutex_init(&audio->mutex); 207c531241dSPeter Ujfalusi audio->audio_mclk = twl_get_hfclk_rate(); 20857fe7251SPeter Ujfalusi 209cdf4b670SPeter Ujfalusi /* Configure APLL_INFREQ and disable APLL if enabled */ 210cdf4b670SPeter Ujfalusi switch (audio->audio_mclk) { 211cdf4b670SPeter Ujfalusi case 19200000: 212cdf4b670SPeter Ujfalusi val = TWL4030_APLL_INFREQ_19200KHZ; 213cdf4b670SPeter Ujfalusi break; 214cdf4b670SPeter Ujfalusi case 26000000: 215cdf4b670SPeter Ujfalusi val = TWL4030_APLL_INFREQ_26000KHZ; 216cdf4b670SPeter Ujfalusi break; 217cdf4b670SPeter Ujfalusi case 38400000: 218cdf4b670SPeter Ujfalusi val = TWL4030_APLL_INFREQ_38400KHZ; 219cdf4b670SPeter Ujfalusi break; 220cdf4b670SPeter Ujfalusi default: 221cdf4b670SPeter Ujfalusi dev_err(&pdev->dev, "Invalid audio_mclk\n"); 222cdf4b670SPeter Ujfalusi return -EINVAL; 223cdf4b670SPeter Ujfalusi } 224cdf4b670SPeter Ujfalusi twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL); 225cdf4b670SPeter Ujfalusi 22657fe7251SPeter Ujfalusi /* Codec power */ 22757fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; 22857fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; 22957fe7251SPeter Ujfalusi 23057fe7251SPeter Ujfalusi /* PLL */ 23157fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; 23257fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; 23357fe7251SPeter Ujfalusi 234019a7e6bSPeter Ujfalusi if (twl4030_audio_has_codec(pdata, node)) { 23557fe7251SPeter Ujfalusi cell = &audio->cells[childs]; 23657fe7251SPeter Ujfalusi cell->name = "twl4030-codec"; 237019a7e6bSPeter Ujfalusi if (pdata) { 2384ae6df5eSPeter Ujfalusi cell->platform_data = pdata->codec; 2394ae6df5eSPeter Ujfalusi cell->pdata_size = sizeof(*pdata->codec); 240019a7e6bSPeter Ujfalusi } 24157fe7251SPeter Ujfalusi childs++; 24257fe7251SPeter Ujfalusi } 243019a7e6bSPeter Ujfalusi if (twl4030_audio_has_vibra(pdata, node)) { 24457fe7251SPeter Ujfalusi cell = &audio->cells[childs]; 24557fe7251SPeter Ujfalusi cell->name = "twl4030-vibra"; 246019a7e6bSPeter Ujfalusi if (pdata) { 24757fe7251SPeter Ujfalusi cell->platform_data = pdata->vibra; 24857fe7251SPeter Ujfalusi cell->pdata_size = sizeof(*pdata->vibra); 249019a7e6bSPeter Ujfalusi } 25057fe7251SPeter Ujfalusi childs++; 25157fe7251SPeter Ujfalusi } 25257fe7251SPeter Ujfalusi 253cdf4b670SPeter Ujfalusi platform_set_drvdata(pdev, audio); 254cdf4b670SPeter Ujfalusi twl4030_audio_dev = pdev; 255cdf4b670SPeter Ujfalusi 25657fe7251SPeter Ujfalusi if (childs) 25757fe7251SPeter Ujfalusi ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, 25855692af5SMark Brown childs, NULL, 0, NULL); 25957fe7251SPeter Ujfalusi else { 26057fe7251SPeter Ujfalusi dev_err(&pdev->dev, "No platform data found for childs\n"); 26157fe7251SPeter Ujfalusi ret = -ENODEV; 26257fe7251SPeter Ujfalusi } 26357fe7251SPeter Ujfalusi 26496ac2d1aSJingoo Han if (ret) 26557fe7251SPeter Ujfalusi twl4030_audio_dev = NULL; 26639c1421dSPeter Ujfalusi 26757fe7251SPeter Ujfalusi return ret; 26857fe7251SPeter Ujfalusi } 26957fe7251SPeter Ujfalusi 2704740f73fSBill Pemberton static int twl4030_audio_remove(struct platform_device *pdev) 27157fe7251SPeter Ujfalusi { 27257fe7251SPeter Ujfalusi mfd_remove_devices(&pdev->dev); 27357fe7251SPeter Ujfalusi twl4030_audio_dev = NULL; 27457fe7251SPeter Ujfalusi 27557fe7251SPeter Ujfalusi return 0; 27657fe7251SPeter Ujfalusi } 27757fe7251SPeter Ujfalusi 278019a7e6bSPeter Ujfalusi static const struct of_device_id twl4030_audio_of_match[] = { 279019a7e6bSPeter Ujfalusi {.compatible = "ti,twl4030-audio", }, 280019a7e6bSPeter Ujfalusi { }, 281019a7e6bSPeter Ujfalusi }; 282019a7e6bSPeter Ujfalusi MODULE_DEVICE_TABLE(of, twl4030_audio_of_match); 283019a7e6bSPeter Ujfalusi 28457fe7251SPeter Ujfalusi static struct platform_driver twl4030_audio_driver = { 28557fe7251SPeter Ujfalusi .driver = { 28657fe7251SPeter Ujfalusi .owner = THIS_MODULE, 28757fe7251SPeter Ujfalusi .name = "twl4030-audio", 288019a7e6bSPeter Ujfalusi .of_match_table = twl4030_audio_of_match, 28957fe7251SPeter Ujfalusi }, 29041569a16SPeter Ujfalusi .probe = twl4030_audio_probe, 29184449216SBill Pemberton .remove = twl4030_audio_remove, 29257fe7251SPeter Ujfalusi }; 29357fe7251SPeter Ujfalusi 29465349d60SMark Brown module_platform_driver(twl4030_audio_driver); 29557fe7251SPeter Ujfalusi 29657fe7251SPeter Ujfalusi MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 29741569a16SPeter Ujfalusi MODULE_DESCRIPTION("TWL4030 audio block MFD driver"); 29857fe7251SPeter Ujfalusi MODULE_LICENSE("GPL"); 29941569a16SPeter Ujfalusi MODULE_ALIAS("platform:twl4030-audio"); 300