twl4030-audio.c revision 41569a16e4e12910e14ce98edea9ef30bd89299b
1/* 2 * MFD driver for twl4030 audio submodule, which contains an audio codec, and 3 * the vibra control. 4 * 5 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> 6 * 7 * Copyright: (C) 2009 Nokia Corporation 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 * 13 * This program is distributed in the hope that it will be useful, but 14 * WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 21 * 02110-1301 USA 22 * 23 */ 24 25#include <linux/module.h> 26#include <linux/types.h> 27#include <linux/slab.h> 28#include <linux/kernel.h> 29#include <linux/fs.h> 30#include <linux/platform_device.h> 31#include <linux/i2c/twl.h> 32#include <linux/mfd/core.h> 33#include <linux/mfd/twl4030-audio.h> 34 35#define TWL4030_AUDIO_CELLS 2 36 37static struct platform_device *twl4030_audio_dev; 38 39struct twl4030_audio_resource { 40 int request_count; 41 u8 reg; 42 u8 mask; 43}; 44 45struct twl4030_audio { 46 unsigned int audio_mclk; 47 struct mutex mutex; 48 struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; 49 struct mfd_cell cells[TWL4030_AUDIO_CELLS]; 50}; 51 52/* 53 * Modify the resource, the function returns the content of the register 54 * after the modification. 55 */ 56static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) 57{ 58 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 59 u8 val; 60 61 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 62 audio->resource[id].reg); 63 64 if (enable) 65 val |= audio->resource[id].mask; 66 else 67 val &= ~audio->resource[id].mask; 68 69 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 70 val, audio->resource[id].reg); 71 72 return val; 73} 74 75static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) 76{ 77 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 78 u8 val; 79 80 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 81 audio->resource[id].reg); 82 83 return val; 84} 85 86/* 87 * Enable the resource. 88 * The function returns with error or the content of the register 89 */ 90int twl4030_audio_enable_resource(enum twl4030_audio_res id) 91{ 92 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 93 int val; 94 95 if (id >= TWL4030_AUDIO_RES_MAX) { 96 dev_err(&twl4030_audio_dev->dev, 97 "Invalid resource ID (%u)\n", id); 98 return -EINVAL; 99 } 100 101 mutex_lock(&audio->mutex); 102 if (!audio->resource[id].request_count) 103 /* Resource was disabled, enable it */ 104 val = twl4030_audio_set_resource(id, 1); 105 else 106 val = twl4030_audio_get_resource(id); 107 108 audio->resource[id].request_count++; 109 mutex_unlock(&audio->mutex); 110 111 return val; 112} 113EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); 114 115/* 116 * Disable the resource. 117 * The function returns with error or the content of the register 118 */ 119int twl4030_audio_disable_resource(unsigned id) 120{ 121 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 122 int val; 123 124 if (id >= TWL4030_AUDIO_RES_MAX) { 125 dev_err(&twl4030_audio_dev->dev, 126 "Invalid resource ID (%u)\n", id); 127 return -EINVAL; 128 } 129 130 mutex_lock(&audio->mutex); 131 if (!audio->resource[id].request_count) { 132 dev_err(&twl4030_audio_dev->dev, 133 "Resource has been disabled already (%u)\n", id); 134 mutex_unlock(&audio->mutex); 135 return -EPERM; 136 } 137 audio->resource[id].request_count--; 138 139 if (!audio->resource[id].request_count) 140 /* Resource can be disabled now */ 141 val = twl4030_audio_set_resource(id, 0); 142 else 143 val = twl4030_audio_get_resource(id); 144 145 mutex_unlock(&audio->mutex); 146 147 return val; 148} 149EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); 150 151unsigned int twl4030_audio_get_mclk(void) 152{ 153 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 154 155 return audio->audio_mclk; 156} 157EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); 158 159static int __devinit twl4030_audio_probe(struct platform_device *pdev) 160{ 161 struct twl4030_audio *audio; 162 struct twl4030_audio_data *pdata = pdev->dev.platform_data; 163 struct mfd_cell *cell = NULL; 164 int ret, childs = 0; 165 u8 val; 166 167 if (!pdata) { 168 dev_err(&pdev->dev, "Platform data is missing\n"); 169 return -EINVAL; 170 } 171 172 /* Configure APLL_INFREQ and disable APLL if enabled */ 173 val = 0; 174 switch (pdata->audio_mclk) { 175 case 19200000: 176 val |= TWL4030_APLL_INFREQ_19200KHZ; 177 break; 178 case 26000000: 179 val |= TWL4030_APLL_INFREQ_26000KHZ; 180 break; 181 case 38400000: 182 val |= TWL4030_APLL_INFREQ_38400KHZ; 183 break; 184 default: 185 dev_err(&pdev->dev, "Invalid audio_mclk\n"); 186 return -EINVAL; 187 } 188 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 189 val, TWL4030_REG_APLL_CTL); 190 191 audio = kzalloc(sizeof(struct twl4030_audio), GFP_KERNEL); 192 if (!audio) 193 return -ENOMEM; 194 195 platform_set_drvdata(pdev, audio); 196 197 twl4030_audio_dev = pdev; 198 mutex_init(&audio->mutex); 199 audio->audio_mclk = pdata->audio_mclk; 200 201 /* Codec power */ 202 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; 203 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; 204 205 /* PLL */ 206 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; 207 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; 208 209 if (pdata->codec) { 210 cell = &audio->cells[childs]; 211 cell->name = "twl4030-codec"; 212 cell->platform_data = pdata->codec; 213 cell->pdata_size = sizeof(*pdata->codec); 214 childs++; 215 } 216 if (pdata->vibra) { 217 cell = &audio->cells[childs]; 218 cell->name = "twl4030-vibra"; 219 cell->platform_data = pdata->vibra; 220 cell->pdata_size = sizeof(*pdata->vibra); 221 childs++; 222 } 223 224 if (childs) 225 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, 226 childs, NULL, 0, NULL); 227 else { 228 dev_err(&pdev->dev, "No platform data found for childs\n"); 229 ret = -ENODEV; 230 } 231 232 if (!ret) 233 return 0; 234 235 platform_set_drvdata(pdev, NULL); 236 kfree(audio); 237 twl4030_audio_dev = NULL; 238 return ret; 239} 240 241static int __devexit twl4030_audio_remove(struct platform_device *pdev) 242{ 243 struct twl4030_audio *audio = platform_get_drvdata(pdev); 244 245 mfd_remove_devices(&pdev->dev); 246 platform_set_drvdata(pdev, NULL); 247 kfree(audio); 248 twl4030_audio_dev = NULL; 249 250 return 0; 251} 252 253static struct platform_driver twl4030_audio_driver = { 254 .driver = { 255 .owner = THIS_MODULE, 256 .name = "twl4030-audio", 257 }, 258 .probe = twl4030_audio_probe, 259 .remove = __devexit_p(twl4030_audio_remove), 260}; 261 262module_platform_driver(twl4030_audio_driver); 263 264MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 265MODULE_DESCRIPTION("TWL4030 audio block MFD driver"); 266MODULE_LICENSE("GPL"); 267MODULE_ALIAS("platform:twl4030-audio"); 268