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> 3157fe7251SPeter Ujfalusi #include <linux/i2c/twl.h> 3257fe7251SPeter Ujfalusi #include <linux/mfd/core.h> 3357fe7251SPeter Ujfalusi #include <linux/mfd/twl4030-audio.h> 3457fe7251SPeter Ujfalusi 3557fe7251SPeter Ujfalusi #define TWL4030_AUDIO_CELLS 2 3657fe7251SPeter Ujfalusi 3757fe7251SPeter Ujfalusi static struct platform_device *twl4030_audio_dev; 3857fe7251SPeter Ujfalusi 3957fe7251SPeter Ujfalusi struct twl4030_audio_resource { 4057fe7251SPeter Ujfalusi int request_count; 4157fe7251SPeter Ujfalusi u8 reg; 4257fe7251SPeter Ujfalusi u8 mask; 4357fe7251SPeter Ujfalusi }; 4457fe7251SPeter Ujfalusi 4557fe7251SPeter Ujfalusi struct twl4030_audio { 4657fe7251SPeter Ujfalusi unsigned int audio_mclk; 4757fe7251SPeter Ujfalusi struct mutex mutex; 4857fe7251SPeter Ujfalusi struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; 4957fe7251SPeter Ujfalusi struct mfd_cell cells[TWL4030_AUDIO_CELLS]; 5057fe7251SPeter Ujfalusi }; 5157fe7251SPeter Ujfalusi 5257fe7251SPeter Ujfalusi /* 5357fe7251SPeter Ujfalusi * Modify the resource, the function returns the content of the register 5457fe7251SPeter Ujfalusi * after the modification. 5557fe7251SPeter Ujfalusi */ 5657fe7251SPeter Ujfalusi static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) 5757fe7251SPeter Ujfalusi { 5857fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 5957fe7251SPeter Ujfalusi u8 val; 6057fe7251SPeter Ujfalusi 6157fe7251SPeter Ujfalusi twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 6257fe7251SPeter Ujfalusi audio->resource[id].reg); 6357fe7251SPeter Ujfalusi 6457fe7251SPeter Ujfalusi if (enable) 6557fe7251SPeter Ujfalusi val |= audio->resource[id].mask; 6657fe7251SPeter Ujfalusi else 6757fe7251SPeter Ujfalusi val &= ~audio->resource[id].mask; 6857fe7251SPeter Ujfalusi 6957fe7251SPeter Ujfalusi twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 7057fe7251SPeter Ujfalusi val, audio->resource[id].reg); 7157fe7251SPeter Ujfalusi 7257fe7251SPeter Ujfalusi return val; 7357fe7251SPeter Ujfalusi } 7457fe7251SPeter Ujfalusi 7557fe7251SPeter Ujfalusi static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) 7657fe7251SPeter Ujfalusi { 7757fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 7857fe7251SPeter Ujfalusi u8 val; 7957fe7251SPeter Ujfalusi 8057fe7251SPeter Ujfalusi twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 8157fe7251SPeter Ujfalusi audio->resource[id].reg); 8257fe7251SPeter Ujfalusi 8357fe7251SPeter Ujfalusi return val; 8457fe7251SPeter Ujfalusi } 8557fe7251SPeter Ujfalusi 8657fe7251SPeter Ujfalusi /* 8757fe7251SPeter Ujfalusi * Enable the resource. 8857fe7251SPeter Ujfalusi * The function returns with error or the content of the register 8957fe7251SPeter Ujfalusi */ 9057fe7251SPeter Ujfalusi int twl4030_audio_enable_resource(enum twl4030_audio_res id) 9157fe7251SPeter Ujfalusi { 9257fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 9357fe7251SPeter Ujfalusi int val; 9457fe7251SPeter Ujfalusi 9557fe7251SPeter Ujfalusi if (id >= TWL4030_AUDIO_RES_MAX) { 9657fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev, 9757fe7251SPeter Ujfalusi "Invalid resource ID (%u)\n", id); 9857fe7251SPeter Ujfalusi return -EINVAL; 9957fe7251SPeter Ujfalusi } 10057fe7251SPeter Ujfalusi 10157fe7251SPeter Ujfalusi mutex_lock(&audio->mutex); 10257fe7251SPeter Ujfalusi if (!audio->resource[id].request_count) 10357fe7251SPeter Ujfalusi /* Resource was disabled, enable it */ 10457fe7251SPeter Ujfalusi val = twl4030_audio_set_resource(id, 1); 10557fe7251SPeter Ujfalusi else 10657fe7251SPeter Ujfalusi val = twl4030_audio_get_resource(id); 10757fe7251SPeter Ujfalusi 10857fe7251SPeter Ujfalusi audio->resource[id].request_count++; 10957fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex); 11057fe7251SPeter Ujfalusi 11157fe7251SPeter Ujfalusi return val; 11257fe7251SPeter Ujfalusi } 11357fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); 11457fe7251SPeter Ujfalusi 11557fe7251SPeter Ujfalusi /* 11657fe7251SPeter Ujfalusi * Disable the resource. 11757fe7251SPeter Ujfalusi * The function returns with error or the content of the register 11857fe7251SPeter Ujfalusi */ 11957fe7251SPeter Ujfalusi int twl4030_audio_disable_resource(unsigned id) 12057fe7251SPeter Ujfalusi { 12157fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 12257fe7251SPeter Ujfalusi int val; 12357fe7251SPeter Ujfalusi 12457fe7251SPeter Ujfalusi if (id >= TWL4030_AUDIO_RES_MAX) { 12557fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev, 12657fe7251SPeter Ujfalusi "Invalid resource ID (%u)\n", id); 12757fe7251SPeter Ujfalusi return -EINVAL; 12857fe7251SPeter Ujfalusi } 12957fe7251SPeter Ujfalusi 13057fe7251SPeter Ujfalusi mutex_lock(&audio->mutex); 13157fe7251SPeter Ujfalusi if (!audio->resource[id].request_count) { 13257fe7251SPeter Ujfalusi dev_err(&twl4030_audio_dev->dev, 13357fe7251SPeter Ujfalusi "Resource has been disabled already (%u)\n", id); 13457fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex); 13557fe7251SPeter Ujfalusi return -EPERM; 13657fe7251SPeter Ujfalusi } 13757fe7251SPeter Ujfalusi audio->resource[id].request_count--; 13857fe7251SPeter Ujfalusi 13957fe7251SPeter Ujfalusi if (!audio->resource[id].request_count) 14057fe7251SPeter Ujfalusi /* Resource can be disabled now */ 14157fe7251SPeter Ujfalusi val = twl4030_audio_set_resource(id, 0); 14257fe7251SPeter Ujfalusi else 14357fe7251SPeter Ujfalusi val = twl4030_audio_get_resource(id); 14457fe7251SPeter Ujfalusi 14557fe7251SPeter Ujfalusi mutex_unlock(&audio->mutex); 14657fe7251SPeter Ujfalusi 14757fe7251SPeter Ujfalusi return val; 14857fe7251SPeter Ujfalusi } 14957fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); 15057fe7251SPeter Ujfalusi 15157fe7251SPeter Ujfalusi unsigned int twl4030_audio_get_mclk(void) 15257fe7251SPeter Ujfalusi { 15357fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 15457fe7251SPeter Ujfalusi 15557fe7251SPeter Ujfalusi return audio->audio_mclk; 15657fe7251SPeter Ujfalusi } 15757fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); 15857fe7251SPeter Ujfalusi 15957fe7251SPeter Ujfalusi static int __devinit twl4030_audio_probe(struct platform_device *pdev) 16057fe7251SPeter Ujfalusi { 16157fe7251SPeter Ujfalusi struct twl4030_audio *audio; 1624ae6df5eSPeter Ujfalusi struct twl4030_audio_data *pdata = pdev->dev.platform_data; 16357fe7251SPeter Ujfalusi struct mfd_cell *cell = NULL; 16457fe7251SPeter Ujfalusi int ret, childs = 0; 16557fe7251SPeter Ujfalusi u8 val; 16657fe7251SPeter Ujfalusi 16757fe7251SPeter Ujfalusi if (!pdata) { 16857fe7251SPeter Ujfalusi dev_err(&pdev->dev, "Platform data is missing\n"); 16957fe7251SPeter Ujfalusi return -EINVAL; 17057fe7251SPeter Ujfalusi } 17157fe7251SPeter Ujfalusi 17257fe7251SPeter Ujfalusi /* Configure APLL_INFREQ and disable APLL if enabled */ 17357fe7251SPeter Ujfalusi val = 0; 17457fe7251SPeter Ujfalusi switch (pdata->audio_mclk) { 17557fe7251SPeter Ujfalusi case 19200000: 17657fe7251SPeter Ujfalusi val |= TWL4030_APLL_INFREQ_19200KHZ; 17757fe7251SPeter Ujfalusi break; 17857fe7251SPeter Ujfalusi case 26000000: 17957fe7251SPeter Ujfalusi val |= TWL4030_APLL_INFREQ_26000KHZ; 18057fe7251SPeter Ujfalusi break; 18157fe7251SPeter Ujfalusi case 38400000: 18257fe7251SPeter Ujfalusi val |= TWL4030_APLL_INFREQ_38400KHZ; 18357fe7251SPeter Ujfalusi break; 18457fe7251SPeter Ujfalusi default: 18557fe7251SPeter Ujfalusi dev_err(&pdev->dev, "Invalid audio_mclk\n"); 18657fe7251SPeter Ujfalusi return -EINVAL; 18757fe7251SPeter Ujfalusi } 18857fe7251SPeter Ujfalusi twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 18957fe7251SPeter Ujfalusi val, TWL4030_REG_APLL_CTL); 19057fe7251SPeter Ujfalusi 19157fe7251SPeter Ujfalusi audio = kzalloc(sizeof(struct twl4030_audio), GFP_KERNEL); 19257fe7251SPeter Ujfalusi if (!audio) 19357fe7251SPeter Ujfalusi return -ENOMEM; 19457fe7251SPeter Ujfalusi 19557fe7251SPeter Ujfalusi platform_set_drvdata(pdev, audio); 19657fe7251SPeter Ujfalusi 19757fe7251SPeter Ujfalusi twl4030_audio_dev = pdev; 19857fe7251SPeter Ujfalusi mutex_init(&audio->mutex); 19957fe7251SPeter Ujfalusi audio->audio_mclk = pdata->audio_mclk; 20057fe7251SPeter Ujfalusi 20157fe7251SPeter Ujfalusi /* Codec power */ 20257fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; 20357fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; 20457fe7251SPeter Ujfalusi 20557fe7251SPeter Ujfalusi /* PLL */ 20657fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; 20757fe7251SPeter Ujfalusi audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; 20857fe7251SPeter Ujfalusi 2094ae6df5eSPeter Ujfalusi if (pdata->codec) { 21057fe7251SPeter Ujfalusi cell = &audio->cells[childs]; 21157fe7251SPeter Ujfalusi cell->name = "twl4030-codec"; 2124ae6df5eSPeter Ujfalusi cell->platform_data = pdata->codec; 2134ae6df5eSPeter Ujfalusi cell->pdata_size = sizeof(*pdata->codec); 21457fe7251SPeter Ujfalusi childs++; 21557fe7251SPeter Ujfalusi } 21657fe7251SPeter Ujfalusi if (pdata->vibra) { 21757fe7251SPeter Ujfalusi cell = &audio->cells[childs]; 21857fe7251SPeter Ujfalusi cell->name = "twl4030-vibra"; 21957fe7251SPeter Ujfalusi cell->platform_data = pdata->vibra; 22057fe7251SPeter Ujfalusi cell->pdata_size = sizeof(*pdata->vibra); 22157fe7251SPeter Ujfalusi childs++; 22257fe7251SPeter Ujfalusi } 22357fe7251SPeter Ujfalusi 22457fe7251SPeter Ujfalusi if (childs) 22557fe7251SPeter Ujfalusi ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, 22657fe7251SPeter Ujfalusi childs, NULL, 0); 22757fe7251SPeter Ujfalusi else { 22857fe7251SPeter Ujfalusi dev_err(&pdev->dev, "No platform data found for childs\n"); 22957fe7251SPeter Ujfalusi ret = -ENODEV; 23057fe7251SPeter Ujfalusi } 23157fe7251SPeter Ujfalusi 23257fe7251SPeter Ujfalusi if (!ret) 23357fe7251SPeter Ujfalusi return 0; 23457fe7251SPeter Ujfalusi 23557fe7251SPeter Ujfalusi platform_set_drvdata(pdev, NULL); 23657fe7251SPeter Ujfalusi kfree(audio); 23757fe7251SPeter Ujfalusi twl4030_audio_dev = NULL; 23857fe7251SPeter Ujfalusi return ret; 23957fe7251SPeter Ujfalusi } 24057fe7251SPeter Ujfalusi 24157fe7251SPeter Ujfalusi static int __devexit twl4030_audio_remove(struct platform_device *pdev) 24257fe7251SPeter Ujfalusi { 24357fe7251SPeter Ujfalusi struct twl4030_audio *audio = platform_get_drvdata(pdev); 24457fe7251SPeter Ujfalusi 24557fe7251SPeter Ujfalusi mfd_remove_devices(&pdev->dev); 24657fe7251SPeter Ujfalusi platform_set_drvdata(pdev, NULL); 24757fe7251SPeter Ujfalusi kfree(audio); 24857fe7251SPeter Ujfalusi twl4030_audio_dev = NULL; 24957fe7251SPeter Ujfalusi 25057fe7251SPeter Ujfalusi return 0; 25157fe7251SPeter Ujfalusi } 25257fe7251SPeter Ujfalusi 25357fe7251SPeter Ujfalusi MODULE_ALIAS("platform:twl4030-audio"); 25457fe7251SPeter Ujfalusi 25557fe7251SPeter Ujfalusi static struct platform_driver twl4030_audio_driver = { 25657fe7251SPeter Ujfalusi .probe = twl4030_audio_probe, 25757fe7251SPeter Ujfalusi .remove = __devexit_p(twl4030_audio_remove), 25857fe7251SPeter Ujfalusi .driver = { 25957fe7251SPeter Ujfalusi .owner = THIS_MODULE, 26057fe7251SPeter Ujfalusi .name = "twl4030-audio", 26157fe7251SPeter Ujfalusi }, 26257fe7251SPeter Ujfalusi }; 26357fe7251SPeter Ujfalusi 26457fe7251SPeter Ujfalusi static int __devinit twl4030_audio_init(void) 26557fe7251SPeter Ujfalusi { 26657fe7251SPeter Ujfalusi return platform_driver_register(&twl4030_audio_driver); 26757fe7251SPeter Ujfalusi } 26857fe7251SPeter Ujfalusi module_init(twl4030_audio_init); 26957fe7251SPeter Ujfalusi 27057fe7251SPeter Ujfalusi static void __devexit twl4030_audio_exit(void) 27157fe7251SPeter Ujfalusi { 27257fe7251SPeter Ujfalusi platform_driver_unregister(&twl4030_audio_driver); 27357fe7251SPeter Ujfalusi } 27457fe7251SPeter Ujfalusi module_exit(twl4030_audio_exit); 27557fe7251SPeter Ujfalusi 27657fe7251SPeter Ujfalusi MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 27757fe7251SPeter Ujfalusi MODULE_LICENSE("GPL"); 278