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/mfd/twl.h> 34 #include <linux/mfd/core.h> 35 #include <linux/mfd/twl4030-audio.h> 36 37 #define TWL4030_AUDIO_CELLS 2 38 39 static struct platform_device *twl4030_audio_dev; 40 41 struct twl4030_audio_resource { 42 int request_count; 43 u8 reg; 44 u8 mask; 45 }; 46 47 struct 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 */ 58 static 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 77 static 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 */ 92 int 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 } 115 EXPORT_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 */ 121 int 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 } 151 EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); 152 153 unsigned 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 } 159 EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); 160 161 static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata, 162 struct device_node *parent) 163 { 164 struct device_node *node; 165 166 if (pdata && pdata->codec) 167 return true; 168 169 node = of_get_child_by_name(parent, "codec"); 170 if (node) { 171 of_node_put(node); 172 return true; 173 } 174 175 return false; 176 } 177 178 static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata, 179 struct device_node *node) 180 { 181 int vibra; 182 183 if (pdata && pdata->vibra) 184 return true; 185 186 if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra) 187 return true; 188 189 return false; 190 } 191 192 static int twl4030_audio_probe(struct platform_device *pdev) 193 { 194 struct twl4030_audio *audio; 195 struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev); 196 struct device_node *node = pdev->dev.of_node; 197 struct mfd_cell *cell = NULL; 198 int ret, childs = 0; 199 u8 val; 200 201 if (!pdata && !node) { 202 dev_err(&pdev->dev, "Platform data is missing\n"); 203 return -EINVAL; 204 } 205 206 audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio), 207 GFP_KERNEL); 208 if (!audio) 209 return -ENOMEM; 210 211 mutex_init(&audio->mutex); 212 audio->audio_mclk = twl_get_hfclk_rate(); 213 214 /* Configure APLL_INFREQ and disable APLL if enabled */ 215 switch (audio->audio_mclk) { 216 case 19200000: 217 val = TWL4030_APLL_INFREQ_19200KHZ; 218 break; 219 case 26000000: 220 val = TWL4030_APLL_INFREQ_26000KHZ; 221 break; 222 case 38400000: 223 val = TWL4030_APLL_INFREQ_38400KHZ; 224 break; 225 default: 226 dev_err(&pdev->dev, "Invalid audio_mclk\n"); 227 return -EINVAL; 228 } 229 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL); 230 231 /* Codec power */ 232 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; 233 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; 234 235 /* PLL */ 236 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; 237 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; 238 239 if (twl4030_audio_has_codec(pdata, node)) { 240 cell = &audio->cells[childs]; 241 cell->name = "twl4030-codec"; 242 if (pdata) { 243 cell->platform_data = pdata->codec; 244 cell->pdata_size = sizeof(*pdata->codec); 245 } 246 childs++; 247 } 248 if (twl4030_audio_has_vibra(pdata, node)) { 249 cell = &audio->cells[childs]; 250 cell->name = "twl4030-vibra"; 251 if (pdata) { 252 cell->platform_data = pdata->vibra; 253 cell->pdata_size = sizeof(*pdata->vibra); 254 } 255 childs++; 256 } 257 258 platform_set_drvdata(pdev, audio); 259 twl4030_audio_dev = pdev; 260 261 if (childs) 262 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, 263 childs, NULL, 0, NULL); 264 else { 265 dev_err(&pdev->dev, "No platform data found for childs\n"); 266 ret = -ENODEV; 267 } 268 269 if (ret) 270 twl4030_audio_dev = NULL; 271 272 return ret; 273 } 274 275 static int twl4030_audio_remove(struct platform_device *pdev) 276 { 277 mfd_remove_devices(&pdev->dev); 278 twl4030_audio_dev = NULL; 279 280 return 0; 281 } 282 283 static const struct of_device_id twl4030_audio_of_match[] = { 284 {.compatible = "ti,twl4030-audio", }, 285 { }, 286 }; 287 MODULE_DEVICE_TABLE(of, twl4030_audio_of_match); 288 289 static struct platform_driver twl4030_audio_driver = { 290 .driver = { 291 .name = "twl4030-audio", 292 .of_match_table = twl4030_audio_of_match, 293 }, 294 .probe = twl4030_audio_probe, 295 .remove = twl4030_audio_remove, 296 }; 297 298 module_platform_driver(twl4030_audio_driver); 299 300 MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 301 MODULE_DESCRIPTION("TWL4030 audio block MFD driver"); 302 MODULE_LICENSE("GPL"); 303 MODULE_ALIAS("platform:twl4030-audio"); 304