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/of.h> 32#include <linux/of_platform.h> 33#include <linux/i2c/twl.h> 34#include <linux/mfd/core.h> 35#include <linux/mfd/twl4030-audio.h> 36 37#define TWL4030_AUDIO_CELLS 2 38 39static struct platform_device *twl4030_audio_dev; 40 41struct twl4030_audio_resource { 42 int request_count; 43 u8 reg; 44 u8 mask; 45}; 46 47struct twl4030_audio { 48 unsigned int audio_mclk; 49 struct mutex mutex; 50 struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; 51 struct mfd_cell cells[TWL4030_AUDIO_CELLS]; 52}; 53 54/* 55 * Modify the resource, the function returns the content of the register 56 * after the modification. 57 */ 58static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) 59{ 60 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 61 u8 val; 62 63 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 64 audio->resource[id].reg); 65 66 if (enable) 67 val |= audio->resource[id].mask; 68 else 69 val &= ~audio->resource[id].mask; 70 71 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 72 val, audio->resource[id].reg); 73 74 return val; 75} 76 77static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) 78{ 79 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 80 u8 val; 81 82 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 83 audio->resource[id].reg); 84 85 return val; 86} 87 88/* 89 * Enable the resource. 90 * The function returns with error or the content of the register 91 */ 92int twl4030_audio_enable_resource(enum twl4030_audio_res id) 93{ 94 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 95 int val; 96 97 if (id >= TWL4030_AUDIO_RES_MAX) { 98 dev_err(&twl4030_audio_dev->dev, 99 "Invalid resource ID (%u)\n", id); 100 return -EINVAL; 101 } 102 103 mutex_lock(&audio->mutex); 104 if (!audio->resource[id].request_count) 105 /* Resource was disabled, enable it */ 106 val = twl4030_audio_set_resource(id, 1); 107 else 108 val = twl4030_audio_get_resource(id); 109 110 audio->resource[id].request_count++; 111 mutex_unlock(&audio->mutex); 112 113 return val; 114} 115EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); 116 117/* 118 * Disable the resource. 119 * The function returns with error or the content of the register 120 */ 121int twl4030_audio_disable_resource(enum twl4030_audio_res id) 122{ 123 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 124 int val; 125 126 if (id >= TWL4030_AUDIO_RES_MAX) { 127 dev_err(&twl4030_audio_dev->dev, 128 "Invalid resource ID (%u)\n", id); 129 return -EINVAL; 130 } 131 132 mutex_lock(&audio->mutex); 133 if (!audio->resource[id].request_count) { 134 dev_err(&twl4030_audio_dev->dev, 135 "Resource has been disabled already (%u)\n", id); 136 mutex_unlock(&audio->mutex); 137 return -EPERM; 138 } 139 audio->resource[id].request_count--; 140 141 if (!audio->resource[id].request_count) 142 /* Resource can be disabled now */ 143 val = twl4030_audio_set_resource(id, 0); 144 else 145 val = twl4030_audio_get_resource(id); 146 147 mutex_unlock(&audio->mutex); 148 149 return val; 150} 151EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); 152 153unsigned int twl4030_audio_get_mclk(void) 154{ 155 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 156 157 return audio->audio_mclk; 158} 159EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); 160 161static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata, 162 struct device_node *node) 163{ 164 if (pdata && pdata->codec) 165 return true; 166 167 if (of_find_node_by_name(node, "codec")) 168 return true; 169 170 return false; 171} 172 173static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata, 174 struct device_node *node) 175{ 176 int vibra; 177 178 if (pdata && pdata->vibra) 179 return true; 180 181 if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra) 182 return true; 183 184 return false; 185} 186 187static int twl4030_audio_probe(struct platform_device *pdev) 188{ 189 struct twl4030_audio *audio; 190 struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev); 191 struct device_node *node = pdev->dev.of_node; 192 struct mfd_cell *cell = NULL; 193 int ret, childs = 0; 194 u8 val; 195 196 if (!pdata && !node) { 197 dev_err(&pdev->dev, "Platform data is missing\n"); 198 return -EINVAL; 199 } 200 201 audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio), 202 GFP_KERNEL); 203 if (!audio) 204 return -ENOMEM; 205 206 mutex_init(&audio->mutex); 207 audio->audio_mclk = twl_get_hfclk_rate(); 208 209 /* Configure APLL_INFREQ and disable APLL if enabled */ 210 switch (audio->audio_mclk) { 211 case 19200000: 212 val = TWL4030_APLL_INFREQ_19200KHZ; 213 break; 214 case 26000000: 215 val = TWL4030_APLL_INFREQ_26000KHZ; 216 break; 217 case 38400000: 218 val = TWL4030_APLL_INFREQ_38400KHZ; 219 break; 220 default: 221 dev_err(&pdev->dev, "Invalid audio_mclk\n"); 222 return -EINVAL; 223 } 224 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL); 225 226 /* Codec power */ 227 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; 228 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; 229 230 /* PLL */ 231 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; 232 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; 233 234 if (twl4030_audio_has_codec(pdata, node)) { 235 cell = &audio->cells[childs]; 236 cell->name = "twl4030-codec"; 237 if (pdata) { 238 cell->platform_data = pdata->codec; 239 cell->pdata_size = sizeof(*pdata->codec); 240 } 241 childs++; 242 } 243 if (twl4030_audio_has_vibra(pdata, node)) { 244 cell = &audio->cells[childs]; 245 cell->name = "twl4030-vibra"; 246 if (pdata) { 247 cell->platform_data = pdata->vibra; 248 cell->pdata_size = sizeof(*pdata->vibra); 249 } 250 childs++; 251 } 252 253 platform_set_drvdata(pdev, audio); 254 twl4030_audio_dev = pdev; 255 256 if (childs) 257 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, 258 childs, NULL, 0, NULL); 259 else { 260 dev_err(&pdev->dev, "No platform data found for childs\n"); 261 ret = -ENODEV; 262 } 263 264 if (ret) 265 twl4030_audio_dev = NULL; 266 267 return ret; 268} 269 270static int twl4030_audio_remove(struct platform_device *pdev) 271{ 272 mfd_remove_devices(&pdev->dev); 273 twl4030_audio_dev = NULL; 274 275 return 0; 276} 277 278static const struct of_device_id twl4030_audio_of_match[] = { 279 {.compatible = "ti,twl4030-audio", }, 280 { }, 281}; 282MODULE_DEVICE_TABLE(of, twl4030_audio_of_match); 283 284static struct platform_driver twl4030_audio_driver = { 285 .driver = { 286 .owner = THIS_MODULE, 287 .name = "twl4030-audio", 288 .of_match_table = twl4030_audio_of_match, 289 }, 290 .probe = twl4030_audio_probe, 291 .remove = twl4030_audio_remove, 292}; 293 294module_platform_driver(twl4030_audio_driver); 295 296MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 297MODULE_DESCRIPTION("TWL4030 audio block MFD driver"); 298MODULE_LICENSE("GPL"); 299MODULE_ALIAS("platform:twl4030-audio"); 300